87 lines
2.6 KiB
Python

import os
import struct
from typing import Optional
from asn1crypto import cms, tsp
from cryptography.hazmat.primitives import hashes
from pyhanko_certvalidator.registry import CertificateStore
from ..general import get_pyca_cryptography_hash
__all__ = [
'TimestampRequestError',
'get_nonce',
'extract_ts_certs',
'dummy_digest',
'handle_tsp_response',
'set_tsp_headers',
]
class TimestampRequestError(IOError):
"""
Raised when an error occurs while requesting a timestamp.
"""
pass
def get_nonce():
# generate a random 8-byte integer
# we initialise it like this to guarantee a fixed width
return struct.unpack('>q', b'\x01' + os.urandom(7))[0]
def extract_ts_certs(ts_token, store: CertificateStore):
ts_signed_data = ts_token['content']
ts_certs = ts_signed_data['certificates']
def extract_ts_sid(si):
sid = si['sid'].chosen
# FIXME handle subject key identifier
assert isinstance(sid, cms.IssuerAndSerialNumber)
return sid['issuer'].dump(), sid['serial_number'].native
ts_leaves = set(extract_ts_sid(si) for si in ts_signed_data['signer_infos'])
for wrapped_c in ts_certs:
c: cms.Certificate = wrapped_c.chosen
store.register(c)
if (c.issuer.dump(), c.serial_number) in ts_leaves:
yield c
def dummy_digest(md_algorithm: str) -> bytes:
md_spec = get_pyca_cryptography_hash(md_algorithm)
return hashes.Hash(md_spec).finalize()
def handle_tsp_response(
response: tsp.TimeStampResp, nonce: Optional[bytes]
) -> cms.ContentInfo:
pki_status_info = response['status']
if pki_status_info['status'].native != 'granted':
status_strs = pki_status_info['status_string'].native or []
status_string = '; '.join(status_strs)
fail_infos = pki_status_info['fail_info'].native or []
fail_info = '; '.join(fail_infos)
raise TimestampRequestError(
f'Timestamp server refused our request: statusString '
f'\"{status_string}\", failInfo \"{fail_info}\"'
)
tst = response['time_stamp_token']
tst_info = tst['content']['encap_content_info']['content']
nonce_received = tst_info.parsed['nonce'].native
if nonce is not None and nonce_received != nonce:
raise TimestampRequestError(
f'Time stamping authority sent back bad nonce value. Expected '
f'{nonce.hex()}, but got {hex(nonce_received)}.'
)
return tst
def set_tsp_headers(headers: dict):
headers['Content-Type'] = 'application/timestamp-query'
headers['Accept'] = 'application/timestamp-reply'
return headers