Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / net / data / ssl / scripts / crlsetutil.py
blob49a1a860cdfaa81e83b4ffa4a5155cebfe46d08a
1 #!/usr/bin/env python
2 # Copyright (c) 2014 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """
7 This utility takes a JSON input that describes a CRLSet and produces a
8 CRLSet from it.
10 The input is taken on stdin and is a dict with the following keys:
11 - BlockedBySPKI: An array of strings, where each string is a filename
12 containing a PEM certificate, from which an SPKI will be extracted.
13 - BlockedByHash: A dict of string to an array of ints, where the string is
14 a filename containing a PEM format certificate, and the ints are the
15 serial numbers. The listed serial numbers will be blocked when issued by
16 the given certificate.
18 For example:
21 "BlockedBySPKI": ["/tmp/blocked-certificate"],
22 "BlockedByHash": {
23 "/tmp/intermediate-certificate": [1, 2, 3]
26 """
28 import hashlib
29 import json
30 import optparse
31 import struct
32 import sys
35 def _pem_cert_to_binary(pem_filename):
36 """Decodes the first PEM-encoded certificate in a given file into binary
38 Args:
39 pem_filename: A filename that contains a PEM-encoded certificate. It may
40 contain additional data (keys, textual representation) which will be
41 ignored
43 Returns:
44 A byte array containing the decoded certificate data
45 """
46 base64 = ""
47 started = False
49 with open(pem_filename, 'r') as pem_file:
50 for line in pem_file:
51 if not started:
52 if line.startswith('-----BEGIN CERTIFICATE'):
53 started = True
54 else:
55 if line.startswith('-----END CERTIFICATE'):
56 break
57 base64 += line[:-1].strip()
59 return base64.decode('base64')
62 def _parse_asn1_element(der_bytes):
63 """Parses a DER-encoded tag/Length/Value into its component parts
65 Args:
66 der_bytes: A DER-encoded ASN.1 data type
68 Returns:
69 A tuple of the ASN.1 tag value, the length of the ASN.1 header that was
70 read, the sequence of bytes for the value, and then any data from der_bytes
71 that was not part of the tag/Length/Value.
72 """
73 tag = ord(der_bytes[0])
74 length = ord(der_bytes[1])
75 header_length = 2
77 if length & 0x80:
78 num_length_bytes = length & 0x7f
79 length = 0
80 for i in xrange(2, 2 + num_length_bytes):
81 length <<= 8
82 length += ord(der_bytes[i])
83 header_length = 2 + num_length_bytes
85 contents = der_bytes[:header_length + length]
86 rest = der_bytes[header_length + length:]
88 return (tag, header_length, contents, rest)
91 class ASN1Iterator(object):
92 """Iterator that parses and iterates through a ASN.1 DER structure"""
94 def __init__(self, contents):
95 self._tag = 0
96 self._header_length = 0
97 self._rest = None
98 self._contents = contents
99 self.step_into()
101 def step_into(self):
102 """Begins processing the inner contents of the next ASN.1 element"""
103 (self._tag, self._header_length, self._contents, self._rest) = (
104 _parse_asn1_element(self._contents[self._header_length:]))
106 def step_over(self):
107 """Skips/ignores the next ASN.1 element"""
108 (self._tag, self._header_length, self._contents, self._rest) = (
109 _parse_asn1_element(self._rest))
111 def tag(self):
112 """Returns the ASN.1 tag of the current element"""
113 return self._tag
115 def contents(self):
116 """Returns the raw data of the current element"""
117 return self._contents
120 def _der_cert_to_spki(der_bytes):
121 """Returns the subjectPublicKeyInfo of a DER-encoded certificate
123 Args:
124 der_bytes: A DER-encoded certificate (RFC 5280)
126 Returns:
127 A byte array containing the subjectPublicKeyInfo
129 iterator = ASN1Iterator(der_bytes)
130 iterator.step_into() # enter certificate structure
131 iterator.step_into() # enter TBSCertificate
132 iterator.step_over() # over version
133 iterator.step_over() # over serial
134 iterator.step_over() # over signature algorithm
135 iterator.step_over() # over issuer name
136 iterator.step_over() # over validity
137 iterator.step_over() # over subject name
138 return iterator.contents()
141 def pem_cert_file_to_spki_hash(pem_filename):
142 """Gets the SHA-256 hash of the subjectPublicKeyInfo of a cert in a file
144 Args:
145 pem_filename: A file containing a PEM-encoded certificate.
147 Returns:
148 The SHA-256 hash of the first certificate in the file, as a byte sequence
150 return hashlib.sha256(
151 _der_cert_to_spki(_pem_cert_to_binary(pem_filename))).digest()
154 def main():
155 parser = optparse.OptionParser(description=sys.modules[__name__].__doc__)
156 parser.add_option('-o', '--output',
157 help='Specifies the output file. The default is stdout.')
158 options, _ = parser.parse_args()
159 outfile = sys.stdout
160 if options.output and options.output != '-':
161 outfile = open(options.output, 'wb')
163 config = json.load(sys.stdin)
164 blocked_spkis = [
165 pem_cert_file_to_spki_hash(pem_file).encode('base64').strip()
166 for pem_file in config.get('BlockedBySPKI', [])]
167 parents = {
168 pem_cert_file_to_spki_hash(pem_file): serials
169 for pem_file, serials in config.get('BlockedByHash', {}).iteritems()
171 header_json = {
172 'Version': 0,
173 'ContentType': 'CRLSet',
174 'Sequence': 0,
175 'DeltaFrom': 0,
176 'NumParents': len(parents),
177 'BlockedSPKIs': blocked_spkis,
179 header = json.dumps(header_json)
180 outfile.write(struct.pack('<H', len(header)))
181 outfile.write(header)
182 for spki, serials in sorted(parents.iteritems()):
183 outfile.write(spki)
184 outfile.write(struct.pack('<I', len(serials)))
185 for serial in serials:
186 raw_serial = []
187 if not serial:
188 raw_serial = ['\x00']
189 else:
190 while serial:
191 raw_serial.insert(0, chr(serial & 0xff))
192 serial >>= 8
194 outfile.write(struct.pack('<B', len(raw_serial)))
195 outfile.write(''.join(raw_serial))
196 return 0
199 if __name__ == '__main__':
200 sys.exit(main())