Skip to content

Commit 98d27c8

Browse files
committed
Don't blindly drop X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION error
1 parent 5c30c84 commit 98d27c8

File tree

2 files changed

+37
-10
lines changed

2 files changed

+37
-10
lines changed

Sources/PackageCollectionsSigning/Certificate/CertificatePolicy.swift

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ import Security
2424
@_implementationOnly import PackageCollectionsSigningLibc
2525
#endif
2626

27+
let appleDistributionIOSMarker = "1.2.840.113635.100.6.1.4"
28+
let appleDistributionMacOSMarker = "1.2.840.113635.100.6.1.7"
29+
let appleIntermediateMarker = "1.2.840.113635.100.6.2.1"
30+
31+
// For BoringSSL only - the Security framework recognizes these marker extensions
32+
#if os(Linux) || os(Windows)
33+
let supportedCriticalExtensions: Set<String> = [appleDistributionIOSMarker, appleDistributionMacOSMarker]
34+
#endif
35+
2736
protocol CertificatePolicy {
2837
/// Validates the given certificate chain.
2938
///
@@ -162,9 +171,29 @@ extension CertificatePolicy {
162171
// Custom error handling
163172
let errorCode = CCryptoBoringSSL_X509_STORE_CTX_get_error(ctx)
164173
// Certs could have unknown critical extensions and cause them to be rejected.
165-
// Instead of disabling all critical extension checks with X509_V_FLAG_IGNORE_CRITICAL
166-
// we will just ignore this specific error.
174+
// Check if they are tolerable.
167175
if errorCode == X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION {
176+
guard let cert = ctx?.pointee.current_cert else {
177+
return result
178+
}
179+
for i in 0 ..< CCryptoBoringSSL_X509_get_ext_count(cert) {
180+
let ext = CCryptoBoringSSL_X509_get_ext(cert, numericCast(i))
181+
// Skip if extension is not critical or it is supported by BoringSSL
182+
if CCryptoBoringSSL_X509_EXTENSION_get_critical(ext) <= 0 || CCryptoBoringSSL_X509_supported_extension(ext) > 0 { continue }
183+
184+
// Extract OID of the critical extension
185+
let capacity = 100
186+
let oidBuffer = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
187+
defer { oidBuffer.deallocate() }
188+
189+
let extObj = CCryptoBoringSSL_X509_EXTENSION_get_object(ext)
190+
guard CCryptoBoringSSL_OBJ_obj2txt(oidBuffer, numericCast(capacity), extObj, numericCast(1)) > 0,
191+
let oid = String(cString: oidBuffer, encoding: .utf8),
192+
supportedCriticalExtensions.contains(oid) else {
193+
return result
194+
}
195+
}
196+
// No actual unhandled critical extension found, so trust the cert chain
168197
return 1
169198
}
170199
return result
@@ -173,7 +202,7 @@ extension CertificatePolicy {
173202

174203
guard CCryptoBoringSSL_X509_verify_cert(x509StoreCtx) == 1 else {
175204
let error = CCryptoBoringSSL_X509_verify_cert_error_string(numericCast(CCryptoBoringSSL_X509_STORE_CTX_get_error(x509StoreCtx)))
176-
diagnosticsEngine.emit(warning: "The certificate is invalid: \(String(describing: error))")
205+
diagnosticsEngine.emit(warning: "The certificate is invalid: \(String(describing: error.flatMap { String(cString: $0, encoding: .utf8) }))")
177206
return wrappedCallback(.failure(CertificatePolicyError.invalidCertChain))
178207
}
179208

@@ -481,6 +510,7 @@ enum CertificatePolicyError: Error, Equatable {
481510
case unexpectedCertChainLength
482511
case missingRequiredExtension
483512
case extensionFailure
513+
case unhandledCriticalException
484514
case noTrustedRootCertsConfigured
485515
case ocspSetupFailure
486516
case ocspFailure
@@ -595,9 +625,6 @@ struct DefaultCertificatePolicy: CertificatePolicy {
595625
/// marker extensions for Apple Distribution certifiications.
596626
struct AppleDeveloperCertificatePolicy: CertificatePolicy {
597627
private static let expectedCertChainLength = 3
598-
private static let appleDistributionIOSMarker = "1.2.840.113635.100.6.1.4"
599-
private static let appleDistributionMacOSMarker = "1.2.840.113635.100.6.1.7"
600-
private static let appleIntermediateMarker = "1.2.840.113635.100.6.2.1"
601628

602629
let trustedRoots: [Certificate]?
603630
let expectedSubjectUserID: String?
@@ -666,10 +693,10 @@ struct AppleDeveloperCertificatePolicy: CertificatePolicy {
666693
}
667694

668695
// Check marker extensions (certificates issued post WWDC 2019 have both extensions but earlier ones have just one depending on platform)
669-
guard try (self.hasExtension(oid: Self.appleDistributionIOSMarker, in: certChain[0]) || self.hasExtension(oid: Self.appleDistributionMacOSMarker, in: certChain[0])) else {
696+
guard try (self.hasExtension(oid: appleDistributionIOSMarker, in: certChain[0]) || self.hasExtension(oid: appleDistributionMacOSMarker, in: certChain[0])) else {
670697
return wrappedCallback(.failure(CertificatePolicyError.missingRequiredExtension))
671698
}
672-
guard try self.hasExtension(oid: Self.appleIntermediateMarker, in: certChain[1]) else {
699+
guard try self.hasExtension(oid: appleIntermediateMarker, in: certChain[1]) else {
673700
return wrappedCallback(.failure(CertificatePolicyError.missingRequiredExtension))
674701
}
675702

Tests/PackageCollectionsSigningTests/Utilities.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import XCTest
1515
@testable import PackageCollectionsSigning
1616
import TSCBasic
1717

18-
// Update this when running ENABLE_REAL_CERT_TEST tests
19-
let expectedSubjectUserID = "<USER ID>"
18+
// Set `REAL_CERT_USER_ID` env var when running ENABLE_REAL_CERT_TEST tests
19+
let expectedSubjectUserID = ProcessInfo.processInfo.environment["REAL_CERT_USER_ID"] ?? "<USER ID>"
2020

2121
let callbackQueue = DispatchQueue(label: "org.swift.swiftpm.PackageCollectionsSigningTests", attributes: .concurrent)
2222
let diagnosticsEngine = DiagnosticsEngine()

0 commit comments

Comments
 (0)