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