OLD | NEW |
| (Empty) |
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. | |
5 | |
6 """ | |
7 This utility takes a JSON input that describes a CRLSet and produces a | |
8 CRLSet from it. | |
9 | |
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. | |
17 | |
18 For example: | |
19 | |
20 { | |
21 "BlockedBySPKI": ["/tmp/blocked-certificate"], | |
22 "BlockedByHash": { | |
23 "/tmp/intermediate-certificate": [1, 2, 3] | |
24 } | |
25 } | |
26 """ | |
27 | |
28 import hashlib | |
29 import json | |
30 import optparse | |
31 import struct | |
32 import sys | |
33 | |
34 | |
35 def _pem_cert_to_binary(pem_filename): | |
36 """Decodes the first PEM-encoded certificate in a given file into binary | |
37 | |
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 | |
42 | |
43 Returns: | |
44 A byte array containing the decoded certificate data | |
45 """ | |
46 base64 = "" | |
47 started = False | |
48 | |
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() | |
58 | |
59 return base64.decode('base64') | |
60 | |
61 | |
62 def _parse_asn1_element(der_bytes): | |
63 """Parses a DER-encoded tag/Length/Value into its component parts | |
64 | |
65 Args: | |
66 der_bytes: A DER-encoded ASN.1 data type | |
67 | |
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 | |
76 | |
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 | |
84 | |
85 contents = der_bytes[:header_length + length] | |
86 rest = der_bytes[header_length + length:] | |
87 | |
88 return (tag, header_length, contents, rest) | |
89 | |
90 | |
91 class ASN1Iterator(object): | |
92 """Iterator that parses and iterates through a ASN.1 DER structure""" | |
93 | |
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() | |
100 | |
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:])) | |
105 | |
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)) | |
110 | |
111 def tag(self): | |
112 """Returns the ASN.1 tag of the current element""" | |
113 return self._tag | |
114 | |
115 def contents(self): | |
116 """Returns the raw data of the current element""" | |
117 return self._contents | |
118 | |
119 | |
120 def _der_cert_to_spki(der_bytes): | |
121 """Returns the subjectPublicKeyInfo of a DER-encoded certificate | |
122 | |
123 Args: | |
124 der_bytes: A DER-encoded certificate (RFC 5280) | |
125 | |
126 Returns: | |
127 A byte array containing the subjectPublicKeyInfo | |
128 """ | |
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() | |
139 | |
140 | |
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 | |
143 | |
144 Args: | |
145 pem_filename: A file containing a PEM-encoded certificate. | |
146 | |
147 Returns: | |
148 The SHA-256 hash of the first certificate in the file, as a byte sequence | |
149 """ | |
150 return hashlib.sha256( | |
151 _der_cert_to_spki(_pem_cert_to_binary(pem_filename))).digest() | |
152 | |
153 | |
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') | |
162 | |
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() | |
170 } | |
171 header_json = { | |
172 'Version': 0, | |
173 'ContentType': 'CRLSet', | |
174 'Sequence': 0, | |
175 'DeltaFrom': 0, | |
176 'NumParents': len(parents), | |
177 'BlockedSPKIs': blocked_spkis, | |
178 } | |
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 | |
193 | |
194 outfile.write(struct.pack('<B', len(raw_serial))) | |
195 outfile.write(''.join(raw_serial)) | |
196 return 0 | |
197 | |
198 | |
199 if __name__ == '__main__': | |
200 sys.exit(main()) | |
OLD | NEW |