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.
7 This utility takes a JSON input that describes a CRLSet and produces a
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.
21 "BlockedBySPKI": ["/tmp/blocked-certificate"],
23 "/tmp/intermediate-certificate": [1, 2, 3]
35 def _pem_cert_to_binary(pem_filename
):
36 """Decodes the first PEM-encoded certificate in a given file into binary
39 pem_filename: A filename that contains a PEM-encoded certificate. It may
40 contain additional data (keys, textual representation) which will be
44 A byte array containing the decoded certificate data
49 with
open(pem_filename
, 'r') as pem_file
:
52 if line
.startswith('-----BEGIN CERTIFICATE'):
55 if line
.startswith('-----END CERTIFICATE'):
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
66 der_bytes: A DER-encoded ASN.1 data type
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.
73 tag
= ord(der_bytes
[0])
74 length
= ord(der_bytes
[1])
78 num_length_bytes
= length
& 0x7f
80 for i
in xrange(2, 2 + num_length_bytes
):
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
):
96 self
._header
_length
= 0
98 self
._contents
= contents
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
:]))
107 """Skips/ignores the next ASN.1 element"""
108 (self
._tag
, self
._header
_length
, self
._contents
, self
._rest
) = (
109 _parse_asn1_element(self
._rest
))
112 """Returns the ASN.1 tag of the current element"""
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
124 der_bytes: A DER-encoded certificate (RFC 5280)
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
145 pem_filename: A file containing a PEM-encoded certificate.
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()
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()
160 if options
.output
and options
.output
!= '-':
161 outfile
= open(options
.output
, 'wb')
163 config
= json
.load(sys
.stdin
)
165 pem_cert_file_to_spki_hash(pem_file
).encode('base64').strip()
166 for pem_file
in config
.get('BlockedBySPKI', [])]
168 pem_cert_file_to_spki_hash(pem_file
): serials
169 for pem_file
, serials
in config
.get('BlockedByHash', {}).iteritems()
173 'ContentType': 'CRLSet',
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()):
184 outfile
.write(struct
.pack('<I', len(serials
)))
185 for serial
in serials
:
188 raw_serial
= ['\x00']
191 raw_serial
.insert(0, chr(serial
& 0xff))
194 outfile
.write(struct
.pack('<B', len(raw_serial
)))
195 outfile
.write(''.join(raw_serial
))
199 if __name__
== '__main__':