tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

createSTHTestData.py (4835B)


      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 """
      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 """
     26 
     27 import binascii
     28 import os
     29 import sys
     30 
     31 from pyasn1.codec.der import encoder
     32 
     33 sys.path.append(
     34    os.path.join(os.path.dirname(__file__), "..", "..", "..", "manager", "tools")
     35 )
     36 import pykey
     37 
     38 
     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
     47    ]
     48 
     49 
     50 class Error(Exception):
     51    """Base class for exceptions in this module."""
     52 
     53    pass
     54 
     55 
     56 class UnknownParameterTypeError(Error):
     57    """Base class for handling unexpected input in this module."""
     58 
     59    def __init__(self, value):
     60        super().__init__()
     61        self.value = value
     62        self.category = "key"
     63 
     64    def __str__(self):
     65        return 'Unknown %s type "%s"' % (self.category, repr(self.value))
     66 
     67 
     68 class InputTooLongError(Error):
     69    """Helper exception type for inputs that are too long."""
     70 
     71    def __init__(self, length):
     72        super().__init__()
     73        self.length = length
     74 
     75    def __str__(self):
     76        return "Input too long: %s > 65535" % self.length
     77 
     78 
     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()
     87 
     88 
     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,
    123    )
    124 
    125 
    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))