1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCertificates open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftCertificates project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCertificates project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Foundation
import SwiftASN1
/// A sub-policy of the ``RFC5280Policy`` that polices the basicConstraints extension.
@usableFromInline
struct BasicConstraintsPolicy: VerifierPolicy {
@usableFromInline
let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
.X509ExtensionID.basicConstraints
]
@inlinable
init() {}
@inlinable
func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
// The rules for BasicConstraints come from https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.9,
// but roughly can be summarised as:
//
// 0. If the cert is a v1 cert then shrug our shoulders, it can do whatever.
// 1. If basicConstraints is absent, the cert must not be used as an issuing certificate.
// 2. If basicConstraints is present and does not assert that this is a CA, this must not be used
// as an issuing certificate.
// 3. If basic constraints is present, and the CA bit is present, and there is a path length constraint,
// then this certificate may not have more sub CAs than the path length constraint allows.
//
// RFC 5280 also wants us to enforce key usage. Unfortunately, as a practical matter, browsers don't. That
// means that other implementations, like Go and webpki, also don't. To maximise compatibility, we don't either.
var chain = chain[...]
guard let leaf = chain.popFirst() else {
// This is conceptually impossible, but we'll tolerate it.
return .failsToMeetPolicy(reason: "RFC5280Policy: Empty certificate chain")
}
// We check for the special-case of a trust root being presented as the end entity cert. If that's what's
// happening, we require that this cert be marked as a CA.
if chain.count == 0 && leaf.version != .v1 {
do {
switch try leaf.extensions.basicConstraints {
case .some(.isCertificateAuthority):
return .meetsPolicy
case .some(.notCertificateAuthority), .none:
return .failsToMeetPolicy(reason: "RFC5280Policy: Self-signed cert \(leaf) is not marked as a CA")
}
} catch {
return .failsToMeetPolicy(
reason: "RFC5280Policy: Error processing basic constraints for \(leaf): \(error)"
)
}
}
// Now we check the chain.
var subCACount = 0
for cert in chain {
do {
switch try (cert.extensions.basicConstraints, cert.version) {
case (_, .v1):
// Is a v1 cert. Basic constraints don't apply here. Continue to the next cert.
// Note that we _do_ include this in the path length, in case there are basic constraints further along
// the path.
()
case (.some(.isCertificateAuthority(.some(let maxPathLength))), _) where maxPathLength < subCACount:
// Is a CA, but the max path length is smaller than the number of sub CAs we have.
let subCACount = subCACount
return .failsToMeetPolicy(
reason:
"RFC5280Policy: CA \(cert) has maximum path length \(maxPathLength), but chain has \(subCACount) subCAs"
)
case (.some(.isCertificateAuthority), _):
// Is a CA, but either the max path length is at least as large as our current set of sub CAs, or there isn't one.
// Continue to the next cert.
()
case (.some(.notCertificateAuthority), _), (.none, _):
return .failsToMeetPolicy(reason: "RFC5280Policy: Certificate \(cert) is not marked as a CA")
}
} catch {
return .failsToMeetPolicy(
reason: "RFC5280Policy: Error processing basic constraints for \(cert): \(error)"
)
}
if cert.issuer != cert.subject {
// only non-self-issued certificates count against the maxPathLength limit
//
// RFC Section 4.2.1.9. Basic Constraints
// [...]
// The pathLenConstraint field is meaningful only if the cA boolean is
// asserted and the key usage extension, if present, asserts the
// keyCertSign bit (Section 4.2.1.3). In this case, it gives the
// maximum number of non-self-issued intermediate certificates that may
// follow this certificate in a valid certification path. (Note: The
// last certificate in the certification path is not an intermediate
// certificate, and is not included in this limit. Usually, the last
// certificate is an end entity certificate, but it can be a CA
// certificate.) A pathLenConstraint of zero indicates that no non-
// self-issued intermediate CA certificates may follow in a valid
// certification path. Where it appears, the pathLenConstraint field
// MUST be greater than or equal to zero. Where pathLenConstraint does
// not appear, no limit is imposed.
// [...]
// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.9
subCACount += 1
}
}
return .meetsPolicy
}
}
|