Bug 1943761 - Add class alignment to the mozsearch analysis file. r=asuth
[gecko.git] / security / ct / tests / gtest / createSTHTestData.py
blobab61d4ba0eef58a9dcc8d221d1fbe84ebb847e6c
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 This utility is used by the build system to create test inputs for the
9 signed tree head decoding and verification implementation. The format is
10 generally lines of <key>:<value> pairs except for the to-be-signed
11 section, which consists of one or more lines of hex bytes. Comments may
12 appear at the end of lines and begin with '//'.
13 The following keys are valid:
14 signingKey: A pykey key identifier to use to sign the to-be-signed data.
15 Required.
16 spki: A pykey key identifier to create an encoded SubjectPublicKeyInfo
17 to be included with the test data. The tests will use this spki to
18 validate the signature. Required.
19 prefix: Hex bytes to include at the beginning of the signed tree head
20 data. This data is not covered by the signature (typically this
21 is used for the log_id field). Optional. Defaults to the empty
22 string.
23 hash: The name of a hash algorithm to use when signing. Optional.
24 Defaults to 'sha256'.
25 """
27 import binascii
28 import os
29 import sys
31 from pyasn1.codec.der import encoder
33 sys.path.append(
34 os.path.join(os.path.dirname(__file__), "..", "..", "..", "manager", "tools")
36 import pykey
39 def sign(signingKey, hashAlgorithm, hexToSign):
40 """Given a pykey, the name of a hash function, and hex bytes to
41 sign, signs the data (as binary) and returns a hex string consisting
42 of the signature."""
43 # key.sign returns a hex string in the format "'<hex bytes>'H",
44 # so we have to strip off the "'"s and trailing 'H'
45 return signingKey.sign(binascii.unhexlify(hexToSign), "hash:%s" % hashAlgorithm)[
46 1:-2
50 class Error(Exception):
51 """Base class for exceptions in this module."""
53 pass
56 class UnknownParameterTypeError(Error):
57 """Base class for handling unexpected input in this module."""
59 def __init__(self, value):
60 super(Error, self).__init__()
61 self.value = value
62 self.category = "key"
64 def __str__(self):
65 return 'Unknown %s type "%s"' % (self.category, repr(self.value))
68 class InputTooLongError(Error):
69 """Helper exception type for inputs that are too long."""
71 def __init__(self, length):
72 super(InputTooLongError, self).__init__()
73 self.length = length
75 def __str__(self):
76 return "Input too long: %s > 65535" % self.length
79 def getTwoByteLenAsHex(callLenOnMe):
80 """Given something that len can be called on, returns a hex string
81 representing the two-byte length of the something, in network byte
82 order (the length must be less than or equal to 65535)."""
83 length = len(callLenOnMe)
84 if length > 65535:
85 raise InputTooLongError(length)
86 return bytes([length // 256, length % 256]).hex()
89 def createSTH(configStream):
90 """Given a stream that will provide the specification for a signed
91 tree head (see the comment at the top of this file), creates the
92 corresponding signed tree head. Returns a string that can be
93 compiled as C/C++ that declares two const char*s kSTHHex and
94 kSPKIHex corresponding to the hex encoding of the signed tree head
95 and the hex encoding of the subject public key info from the
96 specification, respectively."""
97 toSign = ""
98 prefix = ""
99 hashAlgorithm = "sha256"
100 for line in configStream.readlines():
101 if ":" in line:
102 param = line.split(":")[0]
103 arg = line.split(":")[1].split("//")[0].strip()
104 if param == "signingKey":
105 signingKey = pykey.keyFromSpecification(arg)
106 elif param == "spki":
107 spki = pykey.keyFromSpecification(arg)
108 elif param == "prefix":
109 prefix = arg
110 elif param == "hash":
111 hashAlgorithm = arg
112 else:
113 raise UnknownParameterTypeError(param)
114 else:
115 toSign = toSign + line.split("//")[0].strip()
116 signature = sign(signingKey, hashAlgorithm, toSign)
117 lengthBytesHex = getTwoByteLenAsHex(binascii.unhexlify(signature))
118 sth = prefix + toSign + lengthBytesHex + signature
119 spkiHex = encoder.encode(spki.asSubjectPublicKeyInfo()).hex()
120 return 'const char* kSTHHex = "%s";\nconst char* kSPKIHex = "%s";\n' % (
121 sth,
122 spkiHex,
126 def main(output, inputPath):
127 """Given a file-like output and the path to a signed tree head
128 specification (see the comment at the top of this file), reads the
129 specification, creates the signed tree head, and outputs test data
130 that can be included by a gtest corresponding to the
131 specification."""
132 with open(inputPath) as configStream:
133 output.write(createSTH(configStream))