1#!/usr/bin/env python 2# 3# This Source Code Form is subject to the terms of the Mozilla Public 4# License, v. 2.0. If a copy of the MPL was not distributed with this 5# file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7""" 8Helper library for creating a Signed Certificate Timestamp given the 9details of a signing key, when to sign, and the certificate data to 10sign. Currently only supports precert_entry types. See RFC 6962. 11""" 12 13from pyasn1.codec.der import encoder 14from struct import pack 15import binascii 16import calendar 17import hashlib 18 19import pykey 20 21 22class InvalidKeyError(Exception): 23 """Helper exception to handle unknown key types.""" 24 25 def __init__(self, key): 26 self.key = key 27 28 def __str__(self): 29 return 'Invalid key: "%s"' % str(self.key) 30 31 32class SCT(object): 33 """SCT represents a Signed Certificate Timestamp.""" 34 35 def __init__(self, key, date, tbsCertificate, issuerKey): 36 self.key = key 37 self.timestamp = calendar.timegm(date.timetuple()) * 1000 38 self.tbsCertificate = tbsCertificate 39 self.issuerKey = issuerKey 40 41 def signAndEncode(self): 42 """Returns a signed and encoded representation of the SCT as a 43 string.""" 44 # The signature is over the following data: 45 # sct_version (one 0 byte) 46 # signature_type (one 0 byte) 47 # timestamp (8 bytes, milliseconds since the epoch) 48 # entry_type (two bytes [0, 1] - currently only precert_entry is 49 # supported) 50 # signed_entry (bytes of PreCert) 51 # extensions (2-byte-length-prefixed, currently empty (so two 0 52 # bytes)) 53 # A PreCert is: 54 # issuer_key_hash (32 bytes of SHA-256 hash of the issuing 55 # public key, as DER-encoded SPKI) 56 # tbs_certificate (3-byte-length-prefixed data) 57 timestamp = pack("!Q", self.timestamp) 58 hasher = hashlib.sha256() 59 hasher.update(encoder.encode(self.issuerKey.asSubjectPublicKeyInfo())) 60 issuer_key_hash = hasher.digest() 61 len_prefix = pack("!L", len(self.tbsCertificate))[1:] 62 data = ( 63 b"\0\0" 64 + timestamp 65 + b"\0\1" 66 + issuer_key_hash 67 + len_prefix 68 + self.tbsCertificate 69 + b"\0\0" 70 ) 71 if isinstance(self.key, pykey.ECCKey): 72 signatureByte = b"\3" 73 elif isinstance(self.key, pykey.RSAKey): 74 signatureByte = b"\1" 75 else: 76 raise InvalidKeyError(self.key) 77 # sign returns a hex string like "'<hex bytes>'H", but we want 78 # bytes here 79 hexSignature = self.key.sign(data, pykey.HASH_SHA256) 80 signature = binascii.unhexlify(hexSignature[1:-2]) 81 # The actual data returned is the following: 82 # sct_version (one 0 byte) 83 # id (32 bytes of SHA-256 hash of the signing key, as 84 # DER-encoded SPKI) 85 # timestamp (8 bytes, milliseconds since the epoch) 86 # extensions (2-byte-length-prefixed data, currently 87 # empty) 88 # hash (one 4 byte representing sha256) 89 # signature (one byte - 1 for RSA and 3 for ECDSA) 90 # signature (2-byte-length-prefixed data) 91 hasher = hashlib.sha256() 92 hasher.update(encoder.encode(self.key.asSubjectPublicKeyInfo())) 93 key_id = hasher.digest() 94 signature_len_prefix = pack("!H", len(signature)) 95 return ( 96 b"\0" 97 + key_id 98 + timestamp 99 + b"\0\0\4" 100 + signatureByte 101 + signature_len_prefix 102 + signature 103 ) 104