diff options
author | rsleevi <rsleevi@chromium.org> | 2014-09-04 19:05:24 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-05 02:07:19 +0000 |
commit | 94e6d102779db64b3cd875d693ec1ad9bed22257 (patch) | |
tree | f633e3b76c62fcc3e5247666004bdf758b401711 /net | |
parent | 3d3113fad32b368747eafd55551f6770ae080b03 (diff) | |
download | chromium_src-94e6d102779db64b3cd875d693ec1ad9bed22257.zip chromium_src-94e6d102779db64b3cd875d693ec1ad9bed22257.tar.gz chromium_src-94e6d102779db64b3cd875d693ec1ad9bed22257.tar.bz2 |
Add a script for generating testing CRLSets
This allows the test CRLSets to be kept in sync whenever test
certificates are updated.
BUG=403640
Review URL: https://codereview.chromium.org/515043002
Cr-Commit-Position: refs/heads/master@{#293401}
Diffstat (limited to 'net')
-rwxr-xr-x | net/data/ssl/scripts/crlsetutil.py | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/net/data/ssl/scripts/crlsetutil.py b/net/data/ssl/scripts/crlsetutil.py new file mode 100755 index 0000000..49a1a86 --- /dev/null +++ b/net/data/ssl/scripts/crlsetutil.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python +# Copyright (c) 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +This utility takes a JSON input that describes a CRLSet and produces a +CRLSet from it. + +The input is taken on stdin and is a dict with the following keys: + - BlockedBySPKI: An array of strings, where each string is a filename + containing a PEM certificate, from which an SPKI will be extracted. + - BlockedByHash: A dict of string to an array of ints, where the string is + a filename containing a PEM format certificate, and the ints are the + serial numbers. The listed serial numbers will be blocked when issued by + the given certificate. + +For example: + +{ + "BlockedBySPKI": ["/tmp/blocked-certificate"], + "BlockedByHash": { + "/tmp/intermediate-certificate": [1, 2, 3] + } +} +""" + +import hashlib +import json +import optparse +import struct +import sys + + +def _pem_cert_to_binary(pem_filename): + """Decodes the first PEM-encoded certificate in a given file into binary + + Args: + pem_filename: A filename that contains a PEM-encoded certificate. It may + contain additional data (keys, textual representation) which will be + ignored + + Returns: + A byte array containing the decoded certificate data + """ + base64 = "" + started = False + + with open(pem_filename, 'r') as pem_file: + for line in pem_file: + if not started: + if line.startswith('-----BEGIN CERTIFICATE'): + started = True + else: + if line.startswith('-----END CERTIFICATE'): + break + base64 += line[:-1].strip() + + return base64.decode('base64') + + +def _parse_asn1_element(der_bytes): + """Parses a DER-encoded tag/Length/Value into its component parts + + Args: + der_bytes: A DER-encoded ASN.1 data type + + Returns: + A tuple of the ASN.1 tag value, the length of the ASN.1 header that was + read, the sequence of bytes for the value, and then any data from der_bytes + that was not part of the tag/Length/Value. + """ + tag = ord(der_bytes[0]) + length = ord(der_bytes[1]) + header_length = 2 + + if length & 0x80: + num_length_bytes = length & 0x7f + length = 0 + for i in xrange(2, 2 + num_length_bytes): + length <<= 8 + length += ord(der_bytes[i]) + header_length = 2 + num_length_bytes + + contents = der_bytes[:header_length + length] + rest = der_bytes[header_length + length:] + + return (tag, header_length, contents, rest) + + +class ASN1Iterator(object): + """Iterator that parses and iterates through a ASN.1 DER structure""" + + def __init__(self, contents): + self._tag = 0 + self._header_length = 0 + self._rest = None + self._contents = contents + self.step_into() + + def step_into(self): + """Begins processing the inner contents of the next ASN.1 element""" + (self._tag, self._header_length, self._contents, self._rest) = ( + _parse_asn1_element(self._contents[self._header_length:])) + + def step_over(self): + """Skips/ignores the next ASN.1 element""" + (self._tag, self._header_length, self._contents, self._rest) = ( + _parse_asn1_element(self._rest)) + + def tag(self): + """Returns the ASN.1 tag of the current element""" + return self._tag + + def contents(self): + """Returns the raw data of the current element""" + return self._contents + + +def _der_cert_to_spki(der_bytes): + """Returns the subjectPublicKeyInfo of a DER-encoded certificate + + Args: + der_bytes: A DER-encoded certificate (RFC 5280) + + Returns: + A byte array containing the subjectPublicKeyInfo + """ + iterator = ASN1Iterator(der_bytes) + iterator.step_into() # enter certificate structure + iterator.step_into() # enter TBSCertificate + iterator.step_over() # over version + iterator.step_over() # over serial + iterator.step_over() # over signature algorithm + iterator.step_over() # over issuer name + iterator.step_over() # over validity + iterator.step_over() # over subject name + return iterator.contents() + + +def pem_cert_file_to_spki_hash(pem_filename): + """Gets the SHA-256 hash of the subjectPublicKeyInfo of a cert in a file + + Args: + pem_filename: A file containing a PEM-encoded certificate. + + Returns: + The SHA-256 hash of the first certificate in the file, as a byte sequence + """ + return hashlib.sha256( + _der_cert_to_spki(_pem_cert_to_binary(pem_filename))).digest() + + +def main(): + parser = optparse.OptionParser(description=sys.modules[__name__].__doc__) + parser.add_option('-o', '--output', + help='Specifies the output file. The default is stdout.') + options, _ = parser.parse_args() + outfile = sys.stdout + if options.output and options.output != '-': + outfile = open(options.output, 'wb') + + config = json.load(sys.stdin) + blocked_spkis = [ + pem_cert_file_to_spki_hash(pem_file).encode('base64').strip() + for pem_file in config.get('BlockedBySPKI', [])] + parents = { + pem_cert_file_to_spki_hash(pem_file): serials + for pem_file, serials in config.get('BlockedByHash', {}).iteritems() + } + header_json = { + 'Version': 0, + 'ContentType': 'CRLSet', + 'Sequence': 0, + 'DeltaFrom': 0, + 'NumParents': len(parents), + 'BlockedSPKIs': blocked_spkis, + } + header = json.dumps(header_json) + outfile.write(struct.pack('<H', len(header))) + outfile.write(header) + for spki, serials in sorted(parents.iteritems()): + outfile.write(spki) + outfile.write(struct.pack('<I', len(serials))) + for serial in serials: + raw_serial = [] + if not serial: + raw_serial = ['\x00'] + else: + while serial: + raw_serial.insert(0, chr(serial & 0xff)) + serial >>= 8 + + outfile.write(struct.pack('<B', len(raw_serial))) + outfile.write(''.join(raw_serial)) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) |