Source code

Revision control

Copy as Markdown

Other Tools

#!/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))