summaryrefslogtreecommitdiffstats
path: root/security/ct/tests/gtest/createSTHTestData.py
diff options
context:
space:
mode:
Diffstat (limited to 'security/ct/tests/gtest/createSTHTestData.py')
-rwxr-xr-xsecurity/ct/tests/gtest/createSTHTestData.py133
1 files changed, 133 insertions, 0 deletions
diff --git a/security/ct/tests/gtest/createSTHTestData.py b/security/ct/tests/gtest/createSTHTestData.py
new file mode 100755
index 0000000000..ab61d4ba0e
--- /dev/null
+++ b/security/ct/tests/gtest/createSTHTestData.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+This utility is used by the build system to create test inputs for the
+signed tree head decoding and verification implementation. The format is
+generally lines of <key>:<value> pairs except for the to-be-signed
+section, which consists of one or more lines of hex bytes. Comments may
+appear at the end of lines and begin with '//'.
+The following keys are valid:
+signingKey: A pykey key identifier to use to sign the to-be-signed data.
+ Required.
+spki: A pykey key identifier to create an encoded SubjectPublicKeyInfo
+ to be included with the test data. The tests will use this spki to
+ validate the signature. Required.
+prefix: Hex bytes to include at the beginning of the signed tree head
+ data. This data is not covered by the signature (typically this
+ is used for the log_id field). Optional. Defaults to the empty
+ string.
+hash: The name of a hash algorithm to use when signing. Optional.
+ Defaults to 'sha256'.
+"""
+
+import binascii
+import os
+import sys
+
+from pyasn1.codec.der import encoder
+
+sys.path.append(
+ os.path.join(os.path.dirname(__file__), "..", "..", "..", "manager", "tools")
+)
+import pykey
+
+
+def sign(signingKey, hashAlgorithm, hexToSign):
+ """Given a pykey, the name of a hash function, and hex bytes to
+ sign, signs the data (as binary) and returns a hex string consisting
+ of the signature."""
+ # key.sign returns a hex string in the format "'<hex bytes>'H",
+ # so we have to strip off the "'"s and trailing 'H'
+ return signingKey.sign(binascii.unhexlify(hexToSign), "hash:%s" % hashAlgorithm)[
+ 1:-2
+ ]
+
+
+class Error(Exception):
+ """Base class for exceptions in this module."""
+
+ pass
+
+
+class UnknownParameterTypeError(Error):
+ """Base class for handling unexpected input in this module."""
+
+ def __init__(self, value):
+ super(Error, self).__init__()
+ self.value = value
+ self.category = "key"
+
+ def __str__(self):
+ return 'Unknown %s type "%s"' % (self.category, repr(self.value))
+
+
+class InputTooLongError(Error):
+ """Helper exception type for inputs that are too long."""
+
+ def __init__(self, length):
+ super(InputTooLongError, self).__init__()
+ self.length = length
+
+ def __str__(self):
+ return "Input too long: %s > 65535" % self.length
+
+
+def getTwoByteLenAsHex(callLenOnMe):
+ """Given something that len can be called on, returns a hex string
+ representing the two-byte length of the something, in network byte
+ order (the length must be less than or equal to 65535)."""
+ length = len(callLenOnMe)
+ if length > 65535:
+ raise InputTooLongError(length)
+ return bytes([length // 256, length % 256]).hex()
+
+
+def createSTH(configStream):
+ """Given a stream that will provide the specification for a signed
+ tree head (see the comment at the top of this file), creates the
+ corresponding signed tree head. Returns a string that can be
+ compiled as C/C++ that declares two const char*s kSTHHex and
+ kSPKIHex corresponding to the hex encoding of the signed tree head
+ and the hex encoding of the subject public key info from the
+ specification, respectively."""
+ toSign = ""
+ prefix = ""
+ hashAlgorithm = "sha256"
+ for line in configStream.readlines():
+ if ":" in line:
+ param = line.split(":")[0]
+ arg = line.split(":")[1].split("//")[0].strip()
+ if param == "signingKey":
+ signingKey = pykey.keyFromSpecification(arg)
+ elif param == "spki":
+ spki = pykey.keyFromSpecification(arg)
+ elif param == "prefix":
+ prefix = arg
+ elif param == "hash":
+ hashAlgorithm = arg
+ else:
+ raise UnknownParameterTypeError(param)
+ else:
+ toSign = toSign + line.split("//")[0].strip()
+ signature = sign(signingKey, hashAlgorithm, toSign)
+ lengthBytesHex = getTwoByteLenAsHex(binascii.unhexlify(signature))
+ sth = prefix + toSign + lengthBytesHex + signature
+ spkiHex = encoder.encode(spki.asSubjectPublicKeyInfo()).hex()
+ return 'const char* kSTHHex = "%s";\nconst char* kSPKIHex = "%s";\n' % (
+ sth,
+ spkiHex,
+ )
+
+
+def main(output, inputPath):
+ """Given a file-like output and the path to a signed tree head
+ specification (see the comment at the top of this file), reads the
+ specification, creates the signed tree head, and outputs test data
+ that can be included by a gtest corresponding to the
+ specification."""
+ with open(inputPath) as configStream:
+ output.write(createSTH(configStream))