Bug 1935611 - Fix libyuv/libpng link failed for loongarch64. r=glandium,tnikkel,ng
[gecko.git] / security / manager / tools / pyct.py
blob168cf7447f20e85445cb603f02a33b93b2834c75
1 #!/usr/bin/env python
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/.
7 """
8 Helper library for creating a Signed Certificate Timestamp given the
9 details of a signing key, when to sign, and the certificate data to
10 sign. See RFC 6962.
12 When run with an output file-like object and a path to a file containing
13 a specification, creates an SCT from the given information and writes it
14 to the output object. The specification is as follows:
16 timestamp:<YYYYMMDD>
17 [key:<key specification>]
18 [tamper]
19 certificate:
20 <certificate specification>
22 Where:
23 [] indicates an optional field or component of a field
24 <> indicates a required component of a field
26 By default, the "default" key is used (logs are essentially identified
27 by key). Other keys known to pykey can be specified.
29 The certificate specification must come last.
30 """
32 import binascii
33 import calendar
34 import datetime
35 import hashlib
36 from io import StringIO
37 from struct import pack
39 import pycert
40 import pykey
41 from pyasn1.codec.der import encoder
44 class InvalidKeyError(Exception):
45 """Helper exception to handle unknown key types."""
47 def __init__(self, key):
48 self.key = key
50 def __str__(self):
51 return 'Invalid key: "%s"' % str(self.key)
54 class UnknownSignedEntryType(Exception):
55 """Helper exception to handle unknown SignedEntry types."""
57 def __init__(self, signedEntry):
58 self.signedEntry = signedEntry
60 def __str__(self):
61 return 'Unknown SignedEntry type: "%s"' % str(self.signedEntry)
64 class SignedEntry(object):
65 """Base class for CT entries. Use PrecertEntry or
66 X509Entry."""
69 class PrecertEntry(SignedEntry):
70 """Precertificate entry type for SCT."""
72 def __init__(self, tbsCertificate, issuerKey):
73 self.tbsCertificate = tbsCertificate
74 self.issuerKey = issuerKey
77 class X509Entry(SignedEntry):
78 """x509 certificate entry type for SCT."""
80 def __init__(self, certificate):
81 self.certificate = certificate
84 class SCT(object):
85 """SCT represents a Signed Certificate Timestamp."""
87 def __init__(self, key, date, signedEntry):
88 self.key = key
89 self.timestamp = calendar.timegm(date.timetuple()) * 1000
90 self.signedEntry = signedEntry
91 self.tamper = False
93 def signAndEncode(self):
94 """Returns a signed and encoded representation of the
95 SCT as a string."""
96 # The signature is over the following data:
97 # sct_version (one 0 byte)
98 # signature_type (one 0 byte)
99 # timestamp (8 bytes, milliseconds since the epoch)
100 # entry_type (two bytes (one 0 byte followed by one 0 byte for
101 # X509Entry or one 1 byte for PrecertEntry)
102 # signed_entry (bytes of X509Entry or PrecertEntry)
103 # extensions (2-byte-length-prefixed, currently empty (so two 0
104 # bytes))
105 # A X509Entry is:
106 # certificate (3-byte-length-prefixed data)
107 # A PrecertEntry is:
108 # issuer_key_hash (32 bytes of SHA-256 hash of the issuing
109 # public key, as DER-encoded SPKI)
110 # tbs_certificate (3-byte-length-prefixed data)
111 timestamp = pack("!Q", self.timestamp)
113 if isinstance(self.signedEntry, X509Entry):
114 len_prefix = pack("!L", len(self.signedEntry.certificate))[1:]
115 entry_with_type = b"\0" + len_prefix + self.signedEntry.certificate
116 elif isinstance(self.signedEntry, PrecertEntry):
117 hasher = hashlib.sha256()
118 hasher.update(
119 encoder.encode(self.signedEntry.issuerKey.asSubjectPublicKeyInfo())
121 issuer_key_hash = hasher.digest()
122 len_prefix = pack("!L", len(self.signedEntry.tbsCertificate))[1:]
123 entry_with_type = (
124 b"\1" + issuer_key_hash + len_prefix + self.signedEntry.tbsCertificate
126 else:
127 raise UnknownSignedEntryType(self.signedEntry)
128 data = b"\0\0" + timestamp + b"\0" + entry_with_type + b"\0\0"
129 if isinstance(self.key, pykey.ECCKey):
130 signatureByte = b"\3"
131 elif isinstance(self.key, pykey.RSAKey):
132 signatureByte = b"\1"
133 else:
134 raise InvalidKeyError(self.key)
135 # sign returns a hex string like "'<hex bytes>'H", but we want
136 # bytes here
137 hexSignature = self.key.sign(data, pykey.HASH_SHA256)
138 signature = bytearray(binascii.unhexlify(hexSignature[1:-2]))
139 if self.tamper:
140 signature[-1] = ~signature[-1] & 0xFF
141 # The actual data returned is the following:
142 # sct_version (one 0 byte)
143 # id (32 bytes of SHA-256 hash of the signing key, as
144 # DER-encoded SPKI)
145 # timestamp (8 bytes, milliseconds since the epoch)
146 # extensions (2-byte-length-prefixed data, currently
147 # empty)
148 # hash (one 4 byte representing sha256)
149 # signature (one byte - 1 for RSA and 3 for ECDSA)
150 # signature (2-byte-length-prefixed data)
151 hasher = hashlib.sha256()
152 hasher.update(encoder.encode(self.key.asSubjectPublicKeyInfo()))
153 key_id = hasher.digest()
154 signature_len_prefix = pack("!H", len(signature))
155 return (
156 b"\0"
157 + key_id
158 + timestamp
159 + b"\0\0\4"
160 + signatureByte
161 + signature_len_prefix
162 + signature
165 @staticmethod
166 def fromSpecification(specStream):
167 key = pykey.keyFromSpecification("default")
168 certificateSpecification = StringIO()
169 readingCertificateSpecification = False
170 tamper = False
171 for line in specStream.readlines():
172 line = line.strip()
173 if readingCertificateSpecification:
174 print(line, file=certificateSpecification)
175 elif line == "certificate:":
176 readingCertificateSpecification = True
177 elif line.startswith("key:"):
178 key = pykey.keyFromSpecification(line[len("key:") :])
179 elif line.startswith("timestamp:"):
180 timestamp = datetime.datetime.strptime(
181 line[len("timestamp:") :], "%Y%m%d"
183 elif line == "tamper":
184 tamper = True
185 else:
186 raise pycert.UnknownParameterTypeError(line)
187 certificateSpecification.seek(0)
188 certificate = pycert.Certificate(certificateSpecification).toDER()
189 sct = SCT(key, timestamp, X509Entry(certificate))
190 sct.tamper = tamper
191 return sct
194 # The build harness will call this function with an output
195 # file-like object and a path to a file containing an SCT
196 # specification. This will read the specification and output
197 # the SCT as bytes.
198 def main(output, inputPath):
199 with open(inputPath) as configStream:
200 output.write(SCT.fromSpecification(configStream).signAndEncode())