Skip to content

[Collections] Support Swift Package Collection cert #3605

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

Merged
merged 1 commit into from
Jul 13, 2021
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ import TSCBasic
/// collections be signed using certificate that belongs to certain subject user ID.
internal struct PackageCollectionSourceCertificatePolicy {
private static let defaultSourceCertPolicies: [String: [CertificatePolicyConfig]] = [
"developer.apple.com": [CertificatePolicyConfig(
certPolicyKey: .appleDistribution(subjectUserID: "XLVRDL8TZV"),
base64EncodedRootCerts: ["MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUh"]
)],
"developer.apple.com": [
CertificatePolicyConfig(
certPolicyKey: .appleDistribution(subjectUserID: "XLVRDL8TZV"),
base64EncodedRootCerts: ["MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUh"]
),
CertificatePolicyConfig(
certPolicyKey: .appleSwiftPackageCollection(subjectUserID: "XLVRDL8TZV"),
base64EncodedRootCerts: ["MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUh"]
),
],
]

private let sourceCertPolicies: [String: [CertificatePolicyConfig]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import Security

let appleDistributionIOSMarker = "1.2.840.113635.100.6.1.4"
let appleDistributionMacOSMarker = "1.2.840.113635.100.6.1.7"
let appleSwiftPackageCollectionMarker = "1.2.840.113635.100.6.1.35"
let appleIntermediateMarker = "1.2.840.113635.100.6.2.1"

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

/// Policy for validating developer.apple.com certificates.
/// Policy for validating developer.apple.com Swift Package Collection certificates.
///
/// This has the same requirements as `DefaultCertificatePolicy` plus additional
/// marker extensions for Swift Package Collection certifiications.
struct AppleSwiftPackageCollectionCertificatePolicy: CertificatePolicy {
private static let expectedCertChainLength = 3

let trustedRoots: [Certificate]?
let expectedSubjectUserID: String?

private let callbackQueue: DispatchQueue
private let diagnosticsEngine: DiagnosticsEngine

#if os(Linux) || os(Windows) || os(Android)
private let httpClient: HTTPClient
#endif

/// Initializes a `AppleSwiftPackageCollectionCertificatePolicy`.
/// - Parameters:
/// - trustedRootCertsDir: On Apple platforms, all root certificates that come preinstalled with the OS are automatically trusted.
/// Users may specify additional certificates to trust by placing them in `trustedRootCertsDir` and
/// configure the signing tool or SwiftPM to use it. On non-Apple platforms, only trust root certificates in
/// `trustedRootCertsDir` are trusted.
/// - additionalTrustedRootCerts: Root certificates to be trusted in addition to those in `trustedRootCertsDir`. The difference
/// between this and `trustedRootCertsDir` is that the latter is user configured and dynamic,
/// while this is configured by SwiftPM and static.
/// - expectedSubjectUserID: The subject user ID that must match if specified.
/// - callbackQueue: The `DispatchQueue` to use for callbacks
/// - diagnosticsEngine: The `DiagnosticsEngine` for emitting warnings and errors.
init(trustedRootCertsDir: URL?, additionalTrustedRootCerts: [Certificate]?, expectedSubjectUserID: String? = nil, callbackQueue: DispatchQueue, diagnosticsEngine: DiagnosticsEngine) {
#if !(os(macOS) || os(Linux) || os(Windows) || os(Android))
fatalError("Unsupported: \(#function)")
#else
var trustedRoots = [Certificate]()
if let trustedRootCertsDir = trustedRootCertsDir {
trustedRoots.append(contentsOf: Self.loadCerts(at: trustedRootCertsDir, diagnosticsEngine: diagnosticsEngine))
}
if let additionalTrustedRootCerts = additionalTrustedRootCerts {
trustedRoots.append(contentsOf: additionalTrustedRootCerts)
}
self.trustedRoots = trustedRoots.isEmpty ? nil : trustedRoots
self.expectedSubjectUserID = expectedSubjectUserID
self.callbackQueue = callbackQueue
self.diagnosticsEngine = diagnosticsEngine

#if os(Linux) || os(Windows) || os(Android)
self.httpClient = HTTPClient.makeDefault(callbackQueue: callbackQueue)
#endif
#endif
}

func validate(certChain: [Certificate], callback: @escaping (Result<Void, Error>) -> Void) {
#if !(os(macOS) || os(Linux) || os(Windows) || os(Android))
fatalError("Unsupported: \(#function)")
#else
let wrappedCallback: (Result<Void, Error>) -> Void = { result in self.callbackQueue.async { callback(result) } }

guard !certChain.isEmpty else {
return wrappedCallback(.failure(CertificatePolicyError.emptyCertChain))
}
// developer.apple.com cert chain is always 3-long
guard certChain.count == Self.expectedCertChainLength else {
return wrappedCallback(.failure(CertificatePolicyError.unexpectedCertChainLength))
}

do {
// Check if subject user ID matches
if let expectedSubjectUserID = self.expectedSubjectUserID {
guard try certChain[0].subject().userID == expectedSubjectUserID else {
return wrappedCallback(.failure(CertificatePolicyError.subjectUserIDMismatch))
}
}

// Check marker extension
guard try self.hasExtension(oid: appleSwiftPackageCollectionMarker, in: certChain[0]) else {
return wrappedCallback(.failure(CertificatePolicyError.missingRequiredExtension))
}
guard try self.hasExtension(oid: appleIntermediateMarker, in: certChain[1]) else {
return wrappedCallback(.failure(CertificatePolicyError.missingRequiredExtension))
}

// Must be a code signing certificate
guard try self.hasExtendedKeyUsage(.codeSigning, in: certChain[0]) else {
return wrappedCallback(.failure(CertificatePolicyError.codeSigningCertRequired))
}
// Must support OCSP
guard try self.supportsOCSP(certificate: certChain[0]) else {
return wrappedCallback(.failure(CertificatePolicyError.ocspSupportRequired))
}

// Verify the cert chain - if it is trusted then cert chain is valid
#if os(macOS)
self.verify(certChain: certChain, anchorCerts: self.trustedRoots, diagnosticsEngine: self.diagnosticsEngine, callbackQueue: self.callbackQueue, callback: callback)
#elseif os(Linux) || os(Windows) || os(Android)
self.verify(certChain: certChain, anchorCerts: self.trustedRoots, httpClient: self.httpClient, diagnosticsEngine: self.diagnosticsEngine, callbackQueue: self.callbackQueue, callback: callback)
#endif
} catch {
return wrappedCallback(.failure(error))
}
#endif
}
}

/// Policy for validating developer.apple.com Apple Distribution certificates.
///
/// This has the same requirements as `DefaultCertificatePolicy` plus additional
/// marker extensions for Apple Distribution certifiications.
struct AppleDeveloperCertificatePolicy: CertificatePolicy {
struct AppleDistributionCertificatePolicy: CertificatePolicy {
private static let expectedCertChainLength = 3

let trustedRoots: [Certificate]?
Expand All @@ -676,7 +781,7 @@ struct AppleDeveloperCertificatePolicy: CertificatePolicy {
private let httpClient: HTTPClient
#endif

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

public enum CertificatePolicyKey: Hashable {
case `default`(subjectUserID: String?)
case appleSwiftPackageCollection(subjectUserID: String?)
case appleDistribution(subjectUserID: String?)

/// For internal-use only
case custom

public static let `default` = CertificatePolicyKey.default(subjectUserID: nil)
public static let appleSwiftPackageCollection = CertificatePolicyKey.appleSwiftPackageCollection(subjectUserID: nil)
public static let appleDistribution = CertificatePolicyKey.appleDistribution(subjectUserID: nil)
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,14 @@ public struct PackageCollectionSigning: PackageCollectionSigner, PackageCollecti
// Create new instance each time since contents of trustedRootCertsDir might change
return DefaultCertificatePolicy(trustedRootCertsDir: self.trustedRootCertsDir, additionalTrustedRootCerts: self.additionalTrustedRootCerts,
expectedSubjectUserID: subjectUserID, callbackQueue: self.callbackQueue, diagnosticsEngine: self.diagnosticsEngine)
case .appleSwiftPackageCollection(let subjectUserID):
// Create new instance each time since contents of trustedRootCertsDir might change
return AppleSwiftPackageCollectionCertificatePolicy(trustedRootCertsDir: self.trustedRootCertsDir, additionalTrustedRootCerts: self.additionalTrustedRootCerts,
expectedSubjectUserID: subjectUserID, callbackQueue: self.callbackQueue, diagnosticsEngine: self.diagnosticsEngine)
case .appleDistribution(let subjectUserID):
// Create new instance each time since contents of trustedRootCertsDir might change
return AppleDeveloperCertificatePolicy(trustedRootCertsDir: self.trustedRootCertsDir, additionalTrustedRootCerts: self.additionalTrustedRootCerts,
expectedSubjectUserID: subjectUserID, callbackQueue: self.callbackQueue, diagnosticsEngine: self.diagnosticsEngine)
return AppleDistributionCertificatePolicy(trustedRootCertsDir: self.trustedRootCertsDir, additionalTrustedRootCerts: self.additionalTrustedRootCerts,
expectedSubjectUserID: subjectUserID, callbackQueue: self.callbackQueue, diagnosticsEngine: self.diagnosticsEngine)
case .custom:
// Custom `CertificatePolicy` can be set using the internal initializer only
guard let certPolicy = self.certPolicies[key] else {
Expand Down
Loading