Skip to content

Commit 47b5991

Browse files
authored
[Collections] Support Swift Package Collection cert type (#3605) (#3606)
rdar://78839393, rdar://78840475
1 parent c6ea1f4 commit 47b5991

File tree

5 files changed

+431
-32
lines changed

5 files changed

+431
-32
lines changed

Sources/PackageCollections/PackageCollections+CertificatePolicy.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,16 @@ import TSCBasic
1818
/// collections be signed using certificate that belongs to certain subject user ID.
1919
internal struct PackageCollectionSourceCertificatePolicy {
2020
private static let defaultSourceCertPolicies: [String: [CertificatePolicyConfig]] = [
21-
"developer.apple.com": [CertificatePolicyConfig(
22-
certPolicyKey: .appleDistribution(subjectUserID: "XLVRDL8TZV"),
23-
base64EncodedRootCerts: ["MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUh"]
24-
)],
21+
"developer.apple.com": [
22+
CertificatePolicyConfig(
23+
certPolicyKey: .appleDistribution(subjectUserID: "XLVRDL8TZV"),
24+
base64EncodedRootCerts: ["MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUh"]
25+
),
26+
CertificatePolicyConfig(
27+
certPolicyKey: .appleSwiftPackageCollection(subjectUserID: "XLVRDL8TZV"),
28+
base64EncodedRootCerts: ["MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUh"]
29+
),
30+
],
2531
]
2632

2733
private let sourceCertPolicies: [String: [CertificatePolicyConfig]]

Sources/PackageCollectionsSigning/Certificate/CertificatePolicy.swift

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ import Security
2626

2727
let appleDistributionIOSMarker = "1.2.840.113635.100.6.1.4"
2828
let appleDistributionMacOSMarker = "1.2.840.113635.100.6.1.7"
29+
let appleSwiftPackageCollectionMarker = "1.2.840.113635.100.6.1.35"
2930
let appleIntermediateMarker = "1.2.840.113635.100.6.2.1"
3031

3132
// For BoringSSL only - the Security framework recognizes these marker extensions
3233
#if os(Linux) || os(Windows) || os(Android)
33-
let supportedCriticalExtensions: Set<String> = [appleDistributionIOSMarker, appleDistributionMacOSMarker,
34+
let supportedCriticalExtensions: Set<String> = [appleSwiftPackageCollectionMarker, // This isn't a critical extension but including it just in case,
35+
appleDistributionIOSMarker, appleDistributionMacOSMarker,
3436
// Support "Apple Development" cert markers--they are valid code signing certs after all and satisfy DefaultCertificatePolicy
3537
"1.2.840.113635.100.6.1.2", "1.2.840.113635.100.6.1.12"]
3638
#endif
@@ -659,11 +661,114 @@ struct DefaultCertificatePolicy: CertificatePolicy {
659661
}
660662
}
661663

662-
/// Policy for validating developer.apple.com certificates.
664+
/// Policy for validating developer.apple.com Swift Package Collection certificates.
665+
///
666+
/// This has the same requirements as `DefaultCertificatePolicy` plus additional
667+
/// marker extensions for Swift Package Collection certifiications.
668+
struct AppleSwiftPackageCollectionCertificatePolicy: CertificatePolicy {
669+
private static let expectedCertChainLength = 3
670+
671+
let trustedRoots: [Certificate]?
672+
let expectedSubjectUserID: String?
673+
674+
private let callbackQueue: DispatchQueue
675+
private let diagnosticsEngine: DiagnosticsEngine
676+
677+
#if os(Linux) || os(Windows) || os(Android)
678+
private let httpClient: HTTPClient
679+
#endif
680+
681+
/// Initializes a `AppleSwiftPackageCollectionCertificatePolicy`.
682+
/// - Parameters:
683+
/// - trustedRootCertsDir: On Apple platforms, all root certificates that come preinstalled with the OS are automatically trusted.
684+
/// Users may specify additional certificates to trust by placing them in `trustedRootCertsDir` and
685+
/// configure the signing tool or SwiftPM to use it. On non-Apple platforms, only trust root certificates in
686+
/// `trustedRootCertsDir` are trusted.
687+
/// - additionalTrustedRootCerts: Root certificates to be trusted in addition to those in `trustedRootCertsDir`. The difference
688+
/// between this and `trustedRootCertsDir` is that the latter is user configured and dynamic,
689+
/// while this is configured by SwiftPM and static.
690+
/// - expectedSubjectUserID: The subject user ID that must match if specified.
691+
/// - callbackQueue: The `DispatchQueue` to use for callbacks
692+
/// - diagnosticsEngine: The `DiagnosticsEngine` for emitting warnings and errors.
693+
init(trustedRootCertsDir: URL?, additionalTrustedRootCerts: [Certificate]?, expectedSubjectUserID: String? = nil, callbackQueue: DispatchQueue, diagnosticsEngine: DiagnosticsEngine) {
694+
#if !(os(macOS) || os(Linux) || os(Windows) || os(Android))
695+
fatalError("Unsupported: \(#function)")
696+
#else
697+
var trustedRoots = [Certificate]()
698+
if let trustedRootCertsDir = trustedRootCertsDir {
699+
trustedRoots.append(contentsOf: Self.loadCerts(at: trustedRootCertsDir, diagnosticsEngine: diagnosticsEngine))
700+
}
701+
if let additionalTrustedRootCerts = additionalTrustedRootCerts {
702+
trustedRoots.append(contentsOf: additionalTrustedRootCerts)
703+
}
704+
self.trustedRoots = trustedRoots.isEmpty ? nil : trustedRoots
705+
self.expectedSubjectUserID = expectedSubjectUserID
706+
self.callbackQueue = callbackQueue
707+
self.diagnosticsEngine = diagnosticsEngine
708+
709+
#if os(Linux) || os(Windows) || os(Android)
710+
self.httpClient = HTTPClient.makeDefault(callbackQueue: callbackQueue)
711+
#endif
712+
#endif
713+
}
714+
715+
func validate(certChain: [Certificate], callback: @escaping (Result<Void, Error>) -> Void) {
716+
#if !(os(macOS) || os(Linux) || os(Windows) || os(Android))
717+
fatalError("Unsupported: \(#function)")
718+
#else
719+
let wrappedCallback: (Result<Void, Error>) -> Void = { result in self.callbackQueue.async { callback(result) } }
720+
721+
guard !certChain.isEmpty else {
722+
return wrappedCallback(.failure(CertificatePolicyError.emptyCertChain))
723+
}
724+
// developer.apple.com cert chain is always 3-long
725+
guard certChain.count == Self.expectedCertChainLength else {
726+
return wrappedCallback(.failure(CertificatePolicyError.unexpectedCertChainLength))
727+
}
728+
729+
do {
730+
// Check if subject user ID matches
731+
if let expectedSubjectUserID = self.expectedSubjectUserID {
732+
guard try certChain[0].subject().userID == expectedSubjectUserID else {
733+
return wrappedCallback(.failure(CertificatePolicyError.subjectUserIDMismatch))
734+
}
735+
}
736+
737+
// Check marker extension
738+
guard try self.hasExtension(oid: appleSwiftPackageCollectionMarker, in: certChain[0]) else {
739+
return wrappedCallback(.failure(CertificatePolicyError.missingRequiredExtension))
740+
}
741+
guard try self.hasExtension(oid: appleIntermediateMarker, in: certChain[1]) else {
742+
return wrappedCallback(.failure(CertificatePolicyError.missingRequiredExtension))
743+
}
744+
745+
// Must be a code signing certificate
746+
guard try self.hasExtendedKeyUsage(.codeSigning, in: certChain[0]) else {
747+
return wrappedCallback(.failure(CertificatePolicyError.codeSigningCertRequired))
748+
}
749+
// Must support OCSP
750+
guard try self.supportsOCSP(certificate: certChain[0]) else {
751+
return wrappedCallback(.failure(CertificatePolicyError.ocspSupportRequired))
752+
}
753+
754+
// Verify the cert chain - if it is trusted then cert chain is valid
755+
#if os(macOS)
756+
self.verify(certChain: certChain, anchorCerts: self.trustedRoots, diagnosticsEngine: self.diagnosticsEngine, callbackQueue: self.callbackQueue, callback: callback)
757+
#elseif os(Linux) || os(Windows) || os(Android)
758+
self.verify(certChain: certChain, anchorCerts: self.trustedRoots, httpClient: self.httpClient, diagnosticsEngine: self.diagnosticsEngine, callbackQueue: self.callbackQueue, callback: callback)
759+
#endif
760+
} catch {
761+
return wrappedCallback(.failure(error))
762+
}
763+
#endif
764+
}
765+
}
766+
767+
/// Policy for validating developer.apple.com Apple Distribution certificates.
663768
///
664769
/// This has the same requirements as `DefaultCertificatePolicy` plus additional
665770
/// marker extensions for Apple Distribution certifiications.
666-
struct AppleDeveloperCertificatePolicy: CertificatePolicy {
771+
struct AppleDistributionCertificatePolicy: CertificatePolicy {
667772
private static let expectedCertChainLength = 3
668773

669774
let trustedRoots: [Certificate]?
@@ -676,7 +781,7 @@ struct AppleDeveloperCertificatePolicy: CertificatePolicy {
676781
private let httpClient: HTTPClient
677782
#endif
678783

679-
/// Initializes a `AppleDeveloperCertificatePolicy`.
784+
/// Initializes a `AppleDistributionCertificatePolicy`.
680785
/// - Parameters:
681786
/// - trustedRootCertsDir: On Apple platforms, all root certificates that come preinstalled with the OS are automatically trusted.
682787
/// Users may specify additional certificates to trust by placing them in `trustedRootCertsDir` and
@@ -764,11 +869,13 @@ struct AppleDeveloperCertificatePolicy: CertificatePolicy {
764869

765870
public enum CertificatePolicyKey: Hashable {
766871
case `default`(subjectUserID: String?)
872+
case appleSwiftPackageCollection(subjectUserID: String?)
767873
case appleDistribution(subjectUserID: String?)
768874

769875
/// For internal-use only
770876
case custom
771877

772878
public static let `default` = CertificatePolicyKey.default(subjectUserID: nil)
879+
public static let appleSwiftPackageCollection = CertificatePolicyKey.appleSwiftPackageCollection(subjectUserID: nil)
773880
public static let appleDistribution = CertificatePolicyKey.appleDistribution(subjectUserID: nil)
774881
}

Sources/PackageCollectionsSigning/PackageCollectionSigning.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,14 @@ public struct PackageCollectionSigning: PackageCollectionSigner, PackageCollecti
107107
// Create new instance each time since contents of trustedRootCertsDir might change
108108
return DefaultCertificatePolicy(trustedRootCertsDir: self.trustedRootCertsDir, additionalTrustedRootCerts: self.additionalTrustedRootCerts,
109109
expectedSubjectUserID: subjectUserID, callbackQueue: self.callbackQueue, diagnosticsEngine: self.diagnosticsEngine)
110+
case .appleSwiftPackageCollection(let subjectUserID):
111+
// Create new instance each time since contents of trustedRootCertsDir might change
112+
return AppleSwiftPackageCollectionCertificatePolicy(trustedRootCertsDir: self.trustedRootCertsDir, additionalTrustedRootCerts: self.additionalTrustedRootCerts,
113+
expectedSubjectUserID: subjectUserID, callbackQueue: self.callbackQueue, diagnosticsEngine: self.diagnosticsEngine)
110114
case .appleDistribution(let subjectUserID):
111115
// Create new instance each time since contents of trustedRootCertsDir might change
112-
return AppleDeveloperCertificatePolicy(trustedRootCertsDir: self.trustedRootCertsDir, additionalTrustedRootCerts: self.additionalTrustedRootCerts,
113-
expectedSubjectUserID: subjectUserID, callbackQueue: self.callbackQueue, diagnosticsEngine: self.diagnosticsEngine)
116+
return AppleDistributionCertificatePolicy(trustedRootCertsDir: self.trustedRootCertsDir, additionalTrustedRootCerts: self.additionalTrustedRootCerts,
117+
expectedSubjectUserID: subjectUserID, callbackQueue: self.callbackQueue, diagnosticsEngine: self.diagnosticsEngine)
114118
case .custom:
115119
// Custom `CertificatePolicy` can be set using the internal initializer only
116120
guard let certPolicy = self.certPolicies[key] else {

0 commit comments

Comments
 (0)