@@ -16,99 +16,149 @@ import struct Foundation.Date
16
16
import struct Foundation. URL
17
17
18
18
import Basics
19
- @_implementationOnly import X509
19
+ @_implementationOnly import SwiftASN1
20
+ @_implementationOnly @_spi ( DisableValidityCheck) import X509
20
21
21
22
extension SignatureProviderProtocol {
22
23
func buildPolicySet( configuration: VerifierConfiguration , httpClient: HTTPClient ) -> PolicySet {
23
- var policies = [ VerifierPolicy] ( )
24
-
25
- if case . enabled( let validationTime) = configuration. certificateExpiration {
26
- policies. append ( RFC5280Policy ( validationTime: validationTime ?? Date ( ) ) )
27
- }
28
-
29
- switch configuration. certificateRevocation {
30
- case . strict( let validationTime) :
31
- policies. append ( _OCSPVerifierPolicy ( httpClient: httpClient, mode: . strict, validationTime: validationTime) )
32
- case . allowSoftFail( let validationTime) :
24
+ var policies : [ VerifierPolicy ] = [
25
+ _CodeSigningPolicy ( ) ,
26
+ _ADPCertificatePolicy ( ) ,
27
+ ]
28
+
29
+ let now = Date ( )
30
+ switch ( configuration. certificateExpiration, configuration. certificateRevocation) {
31
+ case ( . enabled( let expiryValidationTime) , . strict( let revocationValidationTime) ) :
32
+ policies. append ( RFC5280Policy ( validationTime: expiryValidationTime ?? now) )
33
+ policies
34
+ . append ( _OCSPVerifierPolicy (
35
+ failureMode: . hard,
36
+ httpClient: httpClient,
37
+ validationTime: revocationValidationTime ?? now
38
+ ) )
39
+ case ( . enabled( let expiryValidationTime) , . allowSoftFail( let revocationValidationTime) ) :
40
+ policies. append ( RFC5280Policy ( validationTime: expiryValidationTime ?? now) )
41
+ policies
42
+ . append ( _OCSPVerifierPolicy (
43
+ failureMode: . soft,
44
+ httpClient: httpClient,
45
+ validationTime: revocationValidationTime ?? now
46
+ ) )
47
+ case ( . enabled( let expiryValidationTime) , . disabled) :
48
+ policies. append ( RFC5280Policy ( validationTime: expiryValidationTime ?? now) )
49
+ case ( . disabled, . strict( let revocationValidationTime) ) :
50
+ // Always do expiry check (and before) if revocation check is enabled
51
+ policies. append ( RFC5280Policy ( validationTime: revocationValidationTime ?? now) )
33
52
policies
34
53
. append ( _OCSPVerifierPolicy (
54
+ failureMode: . hard,
35
55
httpClient: httpClient,
36
- mode: . allowSoftFail,
37
- validationTime: validationTime
56
+ validationTime: revocationValidationTime ?? now
38
57
) )
39
- case . disabled:
40
- ( )
58
+ case ( . disabled, . allowSoftFail( let revocationValidationTime) ) :
59
+ // Always do expiry check (and before) if revocation check is enabled
60
+ policies. append ( RFC5280Policy ( validationTime: revocationValidationTime ?? now) )
61
+ policies
62
+ . append ( _OCSPVerifierPolicy (
63
+ failureMode: . soft,
64
+ httpClient: httpClient,
65
+ validationTime: revocationValidationTime ?? now
66
+ ) )
67
+ case ( . disabled, . disabled) :
68
+ // We should still do basic certificate validations even if expiry check is disabled
69
+ policies. append ( RFC5280Policy . withValidityCheckDisabled ( ) )
41
70
}
42
71
43
72
return PolicySet ( policies: policies)
44
73
}
45
74
}
46
75
76
+ /// Policy for code signing certificates.
77
+ struct _CodeSigningPolicy : VerifierPolicy {
78
+ let verifyingCriticalExtensions : [ ASN1ObjectIdentifier ] = [
79
+ ASN1ObjectIdentifier . X509ExtensionID. keyUsage,
80
+ ASN1ObjectIdentifier . X509ExtensionID. extendedKeyUsage,
81
+ ]
82
+
83
+ func chainMeetsPolicyRequirements( chain: UnverifiedCertificateChain ) async -> PolicyEvaluationResult {
84
+ let isCodeSigning = (
85
+ try ? chain. leaf. extensions. extendedKeyUsage? . contains ( ExtendedKeyUsage . Usage. codeSigning)
86
+ ) ??
87
+ false
88
+ guard isCodeSigning else {
89
+ return . failsToMeetPolicy( reason: " Certificate \( chain. leaf) does not have code signing extended key usage " )
90
+ }
91
+ return . meetsPolicy
92
+ }
93
+ }
94
+
95
+ /// Policy for ADP certificates.
96
+ struct _ADPCertificatePolicy : VerifierPolicy {
97
+ /// Include custom marker extensions (which can be critical) so they would not
98
+ /// be considered unhandled and cause certificate chain validation to fail.
99
+ let verifyingCriticalExtensions : [ ASN1ObjectIdentifier ] = Self . swiftPackageMarkers
100
+ + Self. developmentMarkers
101
+
102
+ // Marker extensions for Swift Package certificate
103
+ private static let swiftPackageMarkers : [ ASN1ObjectIdentifier ] = [
104
+ // This is not a critical extension but including it just in case
105
+ ASN1ObjectIdentifier . NameAttributes. adpSwiftPackageMarker,
106
+ ]
107
+
108
+ // Marker extensions for Development certificate (included for testing)
109
+ private static let developmentMarkers : [ ASN1ObjectIdentifier ] = [
110
+ [ 1 , 2 , 840 , 113_635 , 100 , 6 , 1 , 2 ] ,
111
+ [ 1 , 2 , 840 , 113_635 , 100 , 6 , 1 , 12 ] ,
112
+ ]
113
+
114
+ func chainMeetsPolicyRequirements( chain: UnverifiedCertificateChain ) async -> PolicyEvaluationResult {
115
+ // Not policing anything here. This policy is mainly for
116
+ // listing marker extensions to prevent chain validation
117
+ // from failing prematurely.
118
+ . meetsPolicy
119
+ }
120
+ }
121
+
47
122
struct _OCSPVerifierPolicy : VerifierPolicy {
48
123
private static let cacheTTL : DispatchTimeInterval = . seconds( 5 * 60 )
49
124
private let cache = ThreadSafeKeyValueStore <
50
125
UnverifiedCertificateChain ,
51
126
( result: PolicyEvaluationResult , expires: DispatchTime )
52
127
> ( )
53
128
54
- private let underlying : OCSPVerifierPolicy < _OCSPRequester >
55
- private let mode : Mode
56
- private let validationTime : Date
57
-
58
- init ( httpClient: HTTPClient , mode: Mode , validationTime: Date ? ) {
59
- self . underlying = OCSPVerifierPolicy ( requester: _OCSPRequester ( httpClient: httpClient) )
60
- self . mode = mode
61
- self . validationTime = validationTime ?? Date ( )
129
+ private var underlying : OCSPVerifierPolicy < _OCSPRequester >
130
+
131
+ let verifyingCriticalExtensions : [ ASN1ObjectIdentifier ] = [ ]
132
+
133
+ /// Initializes an `_OCSPVerifierPolicy` that caches its results.
134
+ ///
135
+ /// - Parameters:
136
+ /// - failureMode: `OCSPFailureMode` that defines policy failure in event of failure.
137
+ /// Possible values are `hard` (OCSP request failure and unknown status
138
+ /// not allowed) or `soft` (OCSP request failure and unknown status allowed).
139
+ /// - httpClient: `HTTPClient` that backs`_OCSPRequester` for making OCSP requests.
140
+ /// - validationTime: The time used to decide if the OCSP request is relatively recent. It is
141
+ /// considered a failure if the request is too old.
142
+ init ( failureMode: OCSPFailureMode , httpClient: HTTPClient , validationTime: Date ) {
143
+ self . underlying = OCSPVerifierPolicy (
144
+ failureMode: failureMode,
145
+ requester: _OCSPRequester ( httpClient: httpClient) ,
146
+ validationTime: validationTime
147
+ )
62
148
}
63
149
64
- func chainMeetsPolicyRequirements( chain: UnverifiedCertificateChain ) async -> PolicyEvaluationResult {
65
- // Check for expiration of the leaf before revocation
66
- let leaf = chain. leaf
67
- if leaf. notValidBefore > leaf. notValidAfter {
68
- return . failsToMeetPolicy(
69
- reason: " OCSPVerifierPolicy: leaf certificate \( leaf) has invalid expiry, notValidAfter is earlier than notValidBefore "
70
- )
71
- }
72
- if self . validationTime < leaf. notValidBefore {
73
- return . failsToMeetPolicy( reason: " OCSPVerifierPolicy: leaf certificate \( leaf) is not yet valid " )
74
- }
75
- if self . validationTime > leaf. notValidAfter {
76
- return . failsToMeetPolicy( reason: " OCSPVerifierPolicy: leaf certificate \( leaf) has expired " )
77
- }
78
-
150
+ mutating func chainMeetsPolicyRequirements( chain: UnverifiedCertificateChain ) async -> PolicyEvaluationResult {
79
151
// Look for cached result
80
152
if let cached = self . cache [ chain] , cached. expires < . now( ) {
81
153
return cached. result
82
154
}
83
155
84
156
// This makes HTTP requests
85
157
let result = await self . underlying. chainMeetsPolicyRequirements ( chain: chain)
86
- let actualResult : PolicyEvaluationResult
87
- switch result {
88
- case . meetsPolicy:
89
- actualResult = result
90
- case . failsToMeetPolicy( let reason) :
91
- switch self . mode {
92
- case . strict:
93
- actualResult = result
94
- case . allowSoftFail:
95
- // Allow 'unknown' status and failed OCSP request in this mode
96
- if reason. lowercased ( ) . contains ( " revoked through ocsp " ) {
97
- actualResult = result
98
- } else {
99
- actualResult = . meetsPolicy
100
- }
101
- }
102
- }
103
158
104
159
// Save result to cache
105
- self . cache [ chain] = ( result: actualResult, expires: . now( ) + Self. cacheTTL)
106
- return actualResult
107
- }
108
-
109
- enum Mode {
110
- case strict
111
- case allowSoftFail
160
+ self . cache [ chain] = ( result: result, expires: . now( ) + Self. cacheTTL)
161
+ return result
112
162
}
113
163
}
114
164
0 commit comments