Skip to content

Revert "Update to swift-certificates 0.4.0" #6464

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified Fixtures/Signing/Certificates/TestIntermediateCA.cer
Binary file not shown.
Binary file modified Fixtures/Signing/Certificates/TestRootCA.cer
Binary file not shown.
Binary file modified Fixtures/Signing/Certificates/Test_ec.cer
Binary file not shown.
Binary file modified Fixtures/Signing/Certificates/Test_ec_key.p8
Binary file not shown.
Binary file modified Fixtures/Signing/Certificates/Test_ec_self_signed.cer
Binary file not shown.
Binary file modified Fixtures/Signing/Certificates/Test_ec_self_signed_key.p8
Binary file not shown.
Binary file modified Fixtures/Signing/Certificates/Test_rsa.cer
Binary file not shown.
Binary file modified Fixtures/Signing/Certificates/Test_rsa_key.p8
Binary file not shown.
Binary file modified Fixtures/Signing/Certificates/Test_rsa_self_signed.cer
Binary file not shown.
Binary file modified Fixtures/Signing/Certificates/Test_rsa_self_signed_key.p8
Binary file not shown.
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -751,10 +751,10 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
// dependency version changes here with those projects.
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.2.2")),
.package(url: "https://github.com/apple/swift-driver.git", branch: relatedDependenciesBranch),
.package(url: "https://github.com/apple/swift-crypto.git", .upToNextMinor(from: "2.5.0")),
.package(url: "https://github.com/apple/swift-crypto.git", .upToNextMinor(from: "2.4.1")),
.package(url: "https://github.com/apple/swift-system.git", .upToNextMinor(from: "1.1.1")),
.package(url: "https://github.com/apple/swift-collections.git", .upToNextMinor(from: "1.0.1")),
.package(url: "https://github.com/apple/swift-certificates.git", .upToNextMinor(from: "0.4.0")),
.package(url: "https://github.com/apple/swift-certificates.git", .upToNextMinor(from: "0.1.0")),
]
} else {
package.dependencies += [
Expand Down
72 changes: 7 additions & 65 deletions Sources/PackageSigning/SignatureProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ public enum SignatureProvider {
let provider = format.provider
return try await provider.extractSigningEntity(
signature: signature,
format: format,
verifierConfiguration: verifierConfiguration
)
}
Expand Down Expand Up @@ -120,9 +119,6 @@ public enum SigningError: Error {
case keyDoesNotSupportSignatureAlgorithm
case signingIdentityNotSupported
case unableToValidateSignature(String)
case invalidSignature(String)
case certificateInvalid(String)
case certificateNotTrusted(SigningEntity)
}

// MARK: - Signature formats and providers
Expand Down Expand Up @@ -181,7 +177,6 @@ protocol SignatureProviderProtocol {

func extractSigningEntity(
signature: [UInt8],
format: SignatureFormat,
verifierConfiguration: VerifierConfiguration
) async throws -> SigningEntity
}
Expand Down Expand Up @@ -254,6 +249,13 @@ struct CMSSignatureProvider: SignatureProviderProtocol {
}
}

func extractSigningEntity(
signature: [UInt8],
verifierConfiguration: VerifierConfiguration
) async throws -> SigningEntity {
throw StringError("not implemented")
}

func status(
signature: [UInt8],
content: [UInt8],
Expand Down Expand Up @@ -308,66 +310,6 @@ struct CMSSignatureProvider: SignatureProviderProtocol {
throw SigningError.unableToValidateSignature("\(error)")
}
}

func extractSigningEntity(
signature: [UInt8],
format: SignatureFormat,
verifierConfiguration: VerifierConfiguration
) async throws -> SigningEntity {
switch format {
case .cms_1_0_0:
do {
let cmsSignature = try CMSSignature(derEncoded: signature)
let signers = try cmsSignature.signers
guard signers.count == 1, let signer = signers.first else {
throw SigningError.invalidSignature("expected 1 signer but got \(signers.count)")
}

let signingCertificate = signer.certificate

var trustRoots: [Certificate] = []
if verifierConfiguration.includeDefaultTrustStore {
trustRoots.append(contentsOf: CertificateStores.defaultTrustRoots)
}
trustRoots.append(contentsOf: try verifierConfiguration.trustedRoots.map { try Certificate($0) })

// Verifier uses these to build cert chain for validation
// (see also notes in `status` method)
var untrustedIntermediates: [Certificate] = []
// WWDR intermediates are not required when signing with ADP certs,
// (i.e., these intermediates may not be in the signature), hence
// we include them here to ensure Verifier can build cert chain.
untrustedIntermediates.append(contentsOf: Certificates.wwdrIntermediates)
// For self-signed certificate, the signature should include intermediate(s).
untrustedIntermediates.append(contentsOf: cmsSignature.certificates)

let policySet = self.buildPolicySet(configuration: verifierConfiguration, httpClient: self.httpClient)

var verifier = Verifier(rootCertificates: CertificateStore(trustRoots), policy: policySet)
let result = await verifier.validate(
leafCertificate: signingCertificate,
intermediates: CertificateStore(untrustedIntermediates)
)

switch result {
case .validCertificate:
return SigningEntity.from(certificate: signingCertificate)
case .couldNotValidate(let validationFailures):
if validationFailures.isEmpty {
let signingEntity = SigningEntity.from(certificate: signingCertificate)
throw SigningError.certificateNotTrusted(signingEntity)
} else {
throw SigningError
.certificateInvalid("failures: \(validationFailures.map(\.policyFailureReason))")
}
}
} catch let error as SigningError {
throw error
} catch {
throw SigningError.invalidSignature("\(error)")
}
}
}
}

#if canImport(Security)
Expand Down
174 changes: 62 additions & 112 deletions Sources/PackageSigning/VerifierPolicies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,149 +16,99 @@ import struct Foundation.Date
import struct Foundation.URL

import Basics
@_implementationOnly import SwiftASN1
@_implementationOnly @_spi(DisableValidityCheck) import X509
@_implementationOnly import X509

extension SignatureProviderProtocol {
func buildPolicySet(configuration: VerifierConfiguration, httpClient: HTTPClient) -> PolicySet {
var policies: [VerifierPolicy] = [
_CodeSigningPolicy(),
_ADPCertificatePolicy(),
]

let now = Date()
switch (configuration.certificateExpiration, configuration.certificateRevocation) {
case (.enabled(let expiryValidationTime), .strict(let revocationValidationTime)):
policies.append(RFC5280Policy(validationTime: expiryValidationTime ?? now))
policies
.append(_OCSPVerifierPolicy(
failureMode: .hard,
httpClient: httpClient,
validationTime: revocationValidationTime ?? now
))
case (.enabled(let expiryValidationTime), .allowSoftFail(let revocationValidationTime)):
policies.append(RFC5280Policy(validationTime: expiryValidationTime ?? now))
policies
.append(_OCSPVerifierPolicy(
failureMode: .soft,
httpClient: httpClient,
validationTime: revocationValidationTime ?? now
))
case (.enabled(let expiryValidationTime), .disabled):
policies.append(RFC5280Policy(validationTime: expiryValidationTime ?? now))
case (.disabled, .strict(let revocationValidationTime)):
// Always do expiry check (and before) if revocation check is enabled
policies.append(RFC5280Policy(validationTime: revocationValidationTime ?? now))
policies
.append(_OCSPVerifierPolicy(
failureMode: .hard,
httpClient: httpClient,
validationTime: revocationValidationTime ?? now
))
case (.disabled, .allowSoftFail(let revocationValidationTime)):
// Always do expiry check (and before) if revocation check is enabled
policies.append(RFC5280Policy(validationTime: revocationValidationTime ?? now))
var policies = [VerifierPolicy]()

if case .enabled(let validationTime) = configuration.certificateExpiration {
policies.append(RFC5280Policy(validationTime: validationTime ?? Date()))
}

switch configuration.certificateRevocation {
case .strict(let validationTime):
policies.append(_OCSPVerifierPolicy(httpClient: httpClient, mode: .strict, validationTime: validationTime))
case .allowSoftFail(let validationTime):
policies
.append(_OCSPVerifierPolicy(
failureMode: .soft,
httpClient: httpClient,
validationTime: revocationValidationTime ?? now
mode: .allowSoftFail,
validationTime: validationTime
))
case (.disabled, .disabled):
// We should still do basic certificate validations even if expiry check is disabled
policies.append(RFC5280Policy.withValidityCheckDisabled())
case .disabled:
()
}

return PolicySet(policies: policies)
}
}

/// Policy for code signing certificates.
struct _CodeSigningPolicy: VerifierPolicy {
let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
ASN1ObjectIdentifier.X509ExtensionID.keyUsage,
ASN1ObjectIdentifier.X509ExtensionID.extendedKeyUsage,
]

func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult {
let isCodeSigning = (
try? chain.leaf.extensions.extendedKeyUsage?.contains(ExtendedKeyUsage.Usage.codeSigning)
) ??
false
guard isCodeSigning else {
return .failsToMeetPolicy(reason: "Certificate \(chain.leaf) does not have code signing extended key usage")
}
return .meetsPolicy
}
}

/// Policy for ADP certificates.
struct _ADPCertificatePolicy: VerifierPolicy {
/// Include custom marker extensions (which can be critical) so they would not
/// be considered unhandled and cause certificate chain validation to fail.
let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = Self.swiftPackageMarkers
+ Self.developmentMarkers

// Marker extensions for Swift Package certificate
private static let swiftPackageMarkers: [ASN1ObjectIdentifier] = [
// This is not a critical extension but including it just in case
ASN1ObjectIdentifier.NameAttributes.adpSwiftPackageMarker,
]

// Marker extensions for Development certificate (included for testing)
private static let developmentMarkers: [ASN1ObjectIdentifier] = [
[1, 2, 840, 113_635, 100, 6, 1, 2],
[1, 2, 840, 113_635, 100, 6, 1, 12],
]

func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult {
// Not policing anything here. This policy is mainly for
// listing marker extensions to prevent chain validation
// from failing prematurely.
.meetsPolicy
}
}

struct _OCSPVerifierPolicy: VerifierPolicy {
private static let cacheTTL: DispatchTimeInterval = .seconds(5 * 60)
private let cache = ThreadSafeKeyValueStore<
UnverifiedCertificateChain,
(result: PolicyEvaluationResult, expires: DispatchTime)
>()

private var underlying: OCSPVerifierPolicy<_OCSPRequester>

let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = []

/// Initializes an `_OCSPVerifierPolicy` that caches its results.
///
/// - Parameters:
/// - failureMode: `OCSPFailureMode` that defines policy failure in event of failure.
/// Possible values are `hard` (OCSP request failure and unknown status
/// not allowed) or `soft` (OCSP request failure and unknown status allowed).
/// - httpClient: `HTTPClient` that backs`_OCSPRequester` for making OCSP requests.
/// - validationTime: The time used to decide if the OCSP request is relatively recent. It is
/// considered a failure if the request is too old.
init(failureMode: OCSPFailureMode, httpClient: HTTPClient, validationTime: Date) {
self.underlying = OCSPVerifierPolicy(
failureMode: failureMode,
requester: _OCSPRequester(httpClient: httpClient),
validationTime: validationTime
)
private let underlying: OCSPVerifierPolicy<_OCSPRequester>
private let mode: Mode
private let validationTime: Date

init(httpClient: HTTPClient, mode: Mode, validationTime: Date?) {
self.underlying = OCSPVerifierPolicy(requester: _OCSPRequester(httpClient: httpClient))
self.mode = mode
self.validationTime = validationTime ?? Date()
}

mutating func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult {
func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult {
// Check for expiration of the leaf before revocation
let leaf = chain.leaf
if leaf.notValidBefore > leaf.notValidAfter {
return .failsToMeetPolicy(
reason: "OCSPVerifierPolicy: leaf certificate \(leaf) has invalid expiry, notValidAfter is earlier than notValidBefore"
)
}
if self.validationTime < leaf.notValidBefore {
return .failsToMeetPolicy(reason: "OCSPVerifierPolicy: leaf certificate \(leaf) is not yet valid")
}
if self.validationTime > leaf.notValidAfter {
return .failsToMeetPolicy(reason: "OCSPVerifierPolicy: leaf certificate \(leaf) has expired")
}

// Look for cached result
if let cached = self.cache[chain], cached.expires < .now() {
return cached.result
}

// This makes HTTP requests
let result = await self.underlying.chainMeetsPolicyRequirements(chain: chain)
let actualResult: PolicyEvaluationResult
switch result {
case .meetsPolicy:
actualResult = result
case .failsToMeetPolicy(let reason):
switch self.mode {
case .strict:
actualResult = result
case .allowSoftFail:
// Allow 'unknown' status and failed OCSP request in this mode
if reason.lowercased().contains("revoked through ocsp") {
actualResult = result
} else {
actualResult = .meetsPolicy
}
}
}

// Save result to cache
self.cache[chain] = (result: result, expires: .now() + Self.cacheTTL)
return result
self.cache[chain] = (result: actualResult, expires: .now() + Self.cacheTTL)
return actualResult
}

enum Mode {
case strict
case allowSoftFail
}
}

Expand Down
12 changes: 6 additions & 6 deletions Tests/PackageSigningTests/SigningIdentityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ final class SigningIdentityTests: XCTestCase {
let certificate = try Certificate(certificateBytes)

let subject = certificate.subject
XCTAssertEqual("Test (EC) leaf", subject.commonName)
XCTAssertEqual("Test (EC) org unit", subject.organizationalUnitName)
XCTAssertEqual("Test (EC) org", subject.organizationName)
XCTAssertEqual("Test (EC)", subject.commonName)
XCTAssertEqual("Test (EC)", subject.organizationalUnitName)
XCTAssertEqual("Test (EC)", subject.organizationName)

let privateKeyBytes = try readFileContents(
in: fixturePath,
Expand Down Expand Up @@ -63,9 +63,9 @@ final class SigningIdentityTests: XCTestCase {
let certificate = try Certificate(certificateBytes)

let subject = certificate.subject
XCTAssertEqual("Test (RSA) leaf", subject.commonName)
XCTAssertEqual("Test (RSA) org unit", subject.organizationalUnitName)
XCTAssertEqual("Test (RSA) org", subject.organizationName)
XCTAssertEqual("Test (RSA)", subject.commonName)
XCTAssertEqual("Test (RSA)", subject.organizationalUnitName)
XCTAssertEqual("Test (RSA)", subject.organizationName)

let privateKeyBytes = try readFileContents(
in: fixturePath,
Expand Down
Loading