21
21
import base64
22
22
import logging
23
23
from datetime import datetime , timezone
24
- from typing import List , cast
24
+ from typing import List , Union , cast
25
25
26
26
import rekor_types
27
27
from cryptography .exceptions import InvalidSignature
51
51
from sigstore .errors import VerificationError
52
52
from sigstore .hashes import Hashed
53
53
from sigstore .models import Bundle
54
+ from sigstore .timestamp import TimestampSource , TimestampVerificationResult
54
55
from sigstore .verify .policy import VerificationPolicy
55
56
56
57
_logger = logging .getLogger (__name__ )
@@ -120,7 +121,7 @@ def _from_trust_config(cls, trust_config: ClientTrustConfig) -> Verifier:
120
121
121
122
def _verify_signed_timestamp (
122
123
self , timestamp_response : TimeStampResponse , signature : bytes
123
- ) -> bool :
124
+ ) -> Union [ None , TimestampVerificationResult ] :
124
125
"""
125
126
Verify a Signed Timestamp using the TSA provided by the Trusted Root.
126
127
"""
@@ -150,7 +151,10 @@ def _verify_signed_timestamp(
150
151
<= timestamp_response .tst_info .gen_time
151
152
< certificate_authority .validity_period_end
152
153
):
153
- return True
154
+ return TimestampVerificationResult (
155
+ source = TimestampSource .TIMESTAMP_AUTHORITY ,
156
+ time = timestamp_response .tst_info .gen_time ,
157
+ )
154
158
155
159
_logger .debug (
156
160
"Unable to verify Timestamp because not in CA time range."
@@ -160,9 +164,11 @@ def _verify_signed_timestamp(
160
164
"Unable to verify Timestamp because no validity provided."
161
165
)
162
166
163
- return False
167
+ return None
164
168
165
- def _verify_timestamp_authority (self , bundle : Bundle ) -> int :
169
+ def _verify_timestamp_authority (
170
+ self , bundle : Bundle
171
+ ) -> List [TimestampVerificationResult ]:
166
172
"""
167
173
Verify that the given bundle has been timestamped by a trusted timestamp authority
168
174
and that the timestamp is valid.
@@ -183,10 +189,85 @@ def _verify_timestamp_authority(self, bundle: Bundle) -> int:
183
189
# The Signer sends a hash of the signature as the messageImprint in a TimeStampReq
184
190
# to the Timestamping Service
185
191
signature_hash = sha256_digest (bundle .signature ).digest
186
- return [
187
- self ._verify_signed_timestamp (tsr , signature_hash )
188
- for tsr in timestamp_responses
189
- ].count (True )
192
+ verified_timestamps : List [TimestampVerificationResult ] = []
193
+ for tsr in timestamp_responses :
194
+ if verified_timestamp := self ._verify_signed_timestamp (tsr , signature_hash ):
195
+ verified_timestamps .append (verified_timestamp )
196
+
197
+ return verified_timestamps
198
+
199
+ def _establish_time (self , bundle : Bundle ) -> List [TimestampVerificationResult ]:
200
+ """
201
+ Establish timestamps source for the verification.
202
+
203
+ We both source signed timestamp (per RFC3161) and Transparency Log timestamp as
204
+ time sources. As per the spec, if both are available, the Verifier performs
205
+ path validation twice. If either fails, verification fails.
206
+ """
207
+ verified_timestamps : List [TimestampVerificationResult ] = []
208
+
209
+ # If a timestamp from the timestamping service is available, the Verifier MUST
210
+ # perform path validation using the timestamp from the Timestamping Service.
211
+ if bundle .verification_material .timestamp_verification_data .rfc3161_timestamps :
212
+ if not self ._trusted_root .get_timestamp_authorities ():
213
+ msg = (
214
+ "no Timestamp Authorities have been provided to validate this "
215
+ "bundle but it contains a signed timestamp"
216
+ )
217
+ raise VerificationError (msg )
218
+
219
+ timestamp_from_tsa = self ._verify_timestamp_authority (bundle )
220
+ if len (timestamp_from_tsa ) < VERIFY_TIMESTAMP_THRESHOLD :
221
+ msg = (
222
+ f"not enough timestamps validated to meet the validation "
223
+ f"threshold ({ len (timestamp_from_tsa )} /{ VERIFY_TIMESTAMP_THRESHOLD } )"
224
+ )
225
+ raise VerificationError (msg )
226
+
227
+ verified_timestamps .extend (timestamp_from_tsa )
228
+
229
+ # If a timestamp from the Transparency Service is available, the Verifier MUST
230
+ # perform path validation using the timestamp from the Transparency Service.
231
+ if timestamp := bundle .log_entry .integrated_time :
232
+ verified_timestamps .append (
233
+ TimestampVerificationResult (
234
+ source = TimestampSource .TRANSPARENCY_SERVICE ,
235
+ time = datetime .fromtimestamp (timestamp , tz = timezone .utc ),
236
+ )
237
+ )
238
+ return verified_timestamps
239
+
240
+ def _verify_chain_at_time (
241
+ self , certificate : X509 , timestamp_result : TimestampVerificationResult
242
+ ) -> List [X509 ]:
243
+ """
244
+ Verify the validity of the certificate chain at the given tive.
245
+
246
+ Raises a VerificationError if the chain can't be built or be verified.
247
+ """
248
+ # NOTE: The `X509Store` object cannot have its time reset once the `set_time`
249
+ # method been called on it. To get around this, we construct a new one in each
250
+ # call.
251
+ store = X509Store ()
252
+ # NOTE: By explicitly setting the flags here, we ensure that OpenSSL's
253
+ # PARTIAL_CHAIN default does not change on us. Enabling PARTIAL_CHAIN
254
+ # would be strictly more conformant of OpenSSL, but we currently
255
+ # *want* the "long" chain behavior of performing path validation
256
+ # down to a self-signed root.
257
+ store .set_flags (X509StoreFlags .X509_STRICT )
258
+ for parent_cert_ossl in self ._fulcio_certificate_chain :
259
+ store .add_cert (parent_cert_ossl )
260
+
261
+ store .set_time (timestamp_result .time )
262
+
263
+ store_ctx = X509StoreContext (store , certificate )
264
+
265
+ try :
266
+ # get_verified_chain returns the full chain including the end-entity certificate
267
+ # and chain should contain only CA certificates
268
+ return store_ctx .get_verified_chain ()[1 :]
269
+ except X509StoreContextError as e :
270
+ raise VerificationError (f"failed to build chain: { e } " )
190
271
191
272
def _verify_common_signing_cert (
192
273
self , bundle : Bundle , policy : VerificationPolicy
@@ -240,38 +321,17 @@ def _verify_common_signing_cert(
240
321
# While this step is optional and only performed if timestamp data has been
241
322
# provided within the bundle, providing a signed timestamp without a TSA to
242
323
# verify it result in a VerificationError.
243
- if bundle .verification_material .timestamp_verification_data .rfc3161_timestamps :
244
- if not self ._trusted_root .get_timestamp_authorities ():
245
- msg = (
246
- "No Timestamp Authorities have been provided to validate this "
247
- "bundle but it contains a signed timestamp"
248
- )
249
- raise VerificationError (msg )
250
-
251
- verified_timestamp = self ._verify_timestamp_authority (bundle )
252
- # The threshold is set to (1) by default but kept as a variable to allow
253
- # this value to change
254
- if verified_timestamp < VERIFY_TIMESTAMP_THRESHOLD :
255
- msg = (
256
- f"Not enough Timestamp validated to meet the Validation "
257
- f"Threshold ({ verified_timestamp } /{ VERIFY_TIMESTAMP_THRESHOLD } )"
258
- )
259
- raise VerificationError (msg )
324
+ verified_timestamps = self ._establish_time (bundle )
325
+ if not verified_timestamps :
326
+ raise VerificationError ("not enough sources of verified time" )
260
327
261
328
# (1): verify that the signing certificate is signed by the root
262
329
# certificate and that the signing certificate was valid at the
263
330
# time of signing.
264
- sign_date = cert .not_valid_before_utc
265
331
cert_ossl = X509 .from_cryptography (cert )
266
-
267
- store .set_time (sign_date )
268
- store_ctx = X509StoreContext (store , cert_ossl )
269
- try :
270
- # get_verified_chain returns the full chain including the end-entity certificate
271
- # and chain should contain only CA certificates
272
- chain = store_ctx .get_verified_chain ()[1 :]
273
- except X509StoreContextError as e :
274
- raise VerificationError (f"failed to build chain: { e } " )
332
+ chain : list [X509 ] = []
333
+ for vts in verified_timestamps :
334
+ chain = self ._verify_chain_at_time (cert_ossl , vts )
275
335
276
336
# (2): verify the signing certificate's SCT.
277
337
sct = _get_precertificate_signed_certificate_timestamps (cert )[0 ]
0 commit comments