xref: /aosp_15_r20/external/cronet/net/tools/print_certificates.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*6777b538SAndroid Build Coastguard Worker# Copyright 2017 The Chromium Authors
3*6777b538SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
4*6777b538SAndroid Build Coastguard Worker# found in the LICENSE file.
5*6777b538SAndroid Build Coastguard Worker
6*6777b538SAndroid Build Coastguard Worker"""Pretty-prints certificates as an openssl-annotated PEM file."""
7*6777b538SAndroid Build Coastguard Worker
8*6777b538SAndroid Build Coastguard Workerimport argparse
9*6777b538SAndroid Build Coastguard Workerimport base64
10*6777b538SAndroid Build Coastguard Workerimport errno
11*6777b538SAndroid Build Coastguard Workerimport hashlib
12*6777b538SAndroid Build Coastguard Workerimport os
13*6777b538SAndroid Build Coastguard Workerimport re
14*6777b538SAndroid Build Coastguard Workerimport subprocess
15*6777b538SAndroid Build Coastguard Workerimport sys
16*6777b538SAndroid Build Coastguard Workerimport traceback
17*6777b538SAndroid Build Coastguard Worker
18*6777b538SAndroid Build Coastguard Worker
19*6777b538SAndroid Build Coastguard Workerdef read_file_to_string(path):
20*6777b538SAndroid Build Coastguard Worker  with open(path, 'rb') as f:
21*6777b538SAndroid Build Coastguard Worker    return f.read()
22*6777b538SAndroid Build Coastguard Worker
23*6777b538SAndroid Build Coastguard Worker
24*6777b538SAndroid Build Coastguard Workerdef read_certificates_data_from_server(hostname):
25*6777b538SAndroid Build Coastguard Worker  """Uses openssl to fetch the PEM-encoded certificates for an SSL server."""
26*6777b538SAndroid Build Coastguard Worker  p = subprocess.Popen(["openssl", "s_client", "-showcerts",
27*6777b538SAndroid Build Coastguard Worker                        "-servername", hostname,
28*6777b538SAndroid Build Coastguard Worker                        "-connect", hostname + ":443"],
29*6777b538SAndroid Build Coastguard Worker                        stdin=subprocess.PIPE,
30*6777b538SAndroid Build Coastguard Worker                        stdout=subprocess.PIPE,
31*6777b538SAndroid Build Coastguard Worker                        stderr=subprocess.PIPE)
32*6777b538SAndroid Build Coastguard Worker  result = p.communicate()
33*6777b538SAndroid Build Coastguard Worker
34*6777b538SAndroid Build Coastguard Worker  if p.returncode == 0:
35*6777b538SAndroid Build Coastguard Worker    return result[0]
36*6777b538SAndroid Build Coastguard Worker
37*6777b538SAndroid Build Coastguard Worker  sys.stderr.write("Failed getting certificates for %s:\n%s\n" % (
38*6777b538SAndroid Build Coastguard Worker      hostname, result[1]))
39*6777b538SAndroid Build Coastguard Worker  return b""
40*6777b538SAndroid Build Coastguard Worker
41*6777b538SAndroid Build Coastguard Worker
42*6777b538SAndroid Build Coastguard Workerdef read_sources_from_commandline(sources):
43*6777b538SAndroid Build Coastguard Worker  """Processes the command lines and returns an array of all the sources
44*6777b538SAndroid Build Coastguard Worker  bytes."""
45*6777b538SAndroid Build Coastguard Worker  sources_bytes = []
46*6777b538SAndroid Build Coastguard Worker
47*6777b538SAndroid Build Coastguard Worker  if not sources:
48*6777b538SAndroid Build Coastguard Worker    # If no command-line arguments were given to the program, read input from
49*6777b538SAndroid Build Coastguard Worker    # stdin.
50*6777b538SAndroid Build Coastguard Worker    sources_bytes.append(sys.stdin.buffer.read())
51*6777b538SAndroid Build Coastguard Worker  else:
52*6777b538SAndroid Build Coastguard Worker    for arg in sources:
53*6777b538SAndroid Build Coastguard Worker      # If the argument identifies a file path, read it
54*6777b538SAndroid Build Coastguard Worker      if os.path.exists(arg):
55*6777b538SAndroid Build Coastguard Worker        sources_bytes.append(read_file_to_string(arg))
56*6777b538SAndroid Build Coastguard Worker      else:
57*6777b538SAndroid Build Coastguard Worker        # Otherwise treat it as a web server address.
58*6777b538SAndroid Build Coastguard Worker        sources_bytes.append(read_certificates_data_from_server(arg))
59*6777b538SAndroid Build Coastguard Worker
60*6777b538SAndroid Build Coastguard Worker  return sources_bytes
61*6777b538SAndroid Build Coastguard Worker
62*6777b538SAndroid Build Coastguard Worker
63*6777b538SAndroid Build Coastguard Workerdef strip_indentation_whitespace(text):
64*6777b538SAndroid Build Coastguard Worker  """Strips leading whitespace from each line."""
65*6777b538SAndroid Build Coastguard Worker  stripped_lines = [line.lstrip() for line in text.split(b"\n")]
66*6777b538SAndroid Build Coastguard Worker  return b"\n".join(stripped_lines)
67*6777b538SAndroid Build Coastguard Worker
68*6777b538SAndroid Build Coastguard Worker
69*6777b538SAndroid Build Coastguard Workerdef strip_all_whitespace(text):
70*6777b538SAndroid Build Coastguard Worker  pattern = re.compile(rb'\s+')
71*6777b538SAndroid Build Coastguard Worker  return re.sub(pattern, b'', text).replace(rb'\n', b'\n')
72*6777b538SAndroid Build Coastguard Worker
73*6777b538SAndroid Build Coastguard Worker
74*6777b538SAndroid Build Coastguard Workerdef extract_certificates_from_pem(pem_bytes):
75*6777b538SAndroid Build Coastguard Worker  certificates_der = []
76*6777b538SAndroid Build Coastguard Worker
77*6777b538SAndroid Build Coastguard Worker  regex = re.compile(
78*6777b538SAndroid Build Coastguard Worker      rb'-----BEGIN (CERTIFICATE|PKCS7)-----(.*?)(-----END \1-----|$)',
79*6777b538SAndroid Build Coastguard Worker      re.DOTALL)
80*6777b538SAndroid Build Coastguard Worker
81*6777b538SAndroid Build Coastguard Worker  for match in regex.finditer(pem_bytes):
82*6777b538SAndroid Build Coastguard Worker    if not match.group(3):
83*6777b538SAndroid Build Coastguard Worker      sys.stderr.write(
84*6777b538SAndroid Build Coastguard Worker          "\nUnterminated %s block, input is corrupt or truncated\n" %
85*6777b538SAndroid Build Coastguard Worker          match.group(1))
86*6777b538SAndroid Build Coastguard Worker      continue
87*6777b538SAndroid Build Coastguard Worker    der = base64.b64decode(strip_all_whitespace(match.group(2)))
88*6777b538SAndroid Build Coastguard Worker    if match.group(1) == b'CERTIFICATE':
89*6777b538SAndroid Build Coastguard Worker      certificates_der.append(der)
90*6777b538SAndroid Build Coastguard Worker    else:
91*6777b538SAndroid Build Coastguard Worker      certificates_der.extend(extract_certificates_from_der_pkcs7(der))
92*6777b538SAndroid Build Coastguard Worker
93*6777b538SAndroid Build Coastguard Worker  return certificates_der
94*6777b538SAndroid Build Coastguard Worker
95*6777b538SAndroid Build Coastguard Worker
96*6777b538SAndroid Build Coastguard Workerdef extract_certificates_from_der_pkcs7(der_bytes):
97*6777b538SAndroid Build Coastguard Worker  pkcs7_certs_pem = process_data_with_command(
98*6777b538SAndroid Build Coastguard Worker      ['openssl','pkcs7','-print_certs', '-inform', 'DER'], der_bytes)
99*6777b538SAndroid Build Coastguard Worker  # The output will be one or more PEM encoded certificates.
100*6777b538SAndroid Build Coastguard Worker  # (Or CRLS, but those will be ignored.)
101*6777b538SAndroid Build Coastguard Worker  if pkcs7_certs_pem:
102*6777b538SAndroid Build Coastguard Worker    return extract_certificates_from_pem(pkcs7_certs_pem)
103*6777b538SAndroid Build Coastguard Worker  return []
104*6777b538SAndroid Build Coastguard Worker
105*6777b538SAndroid Build Coastguard Worker
106*6777b538SAndroid Build Coastguard Workerdef extract_certificates_from_der_ascii(input_text):
107*6777b538SAndroid Build Coastguard Worker  certificates_der = []
108*6777b538SAndroid Build Coastguard Worker
109*6777b538SAndroid Build Coastguard Worker  # Look for beginning and end of Certificate SEQUENCE. The indentation is
110*6777b538SAndroid Build Coastguard Worker  # significant. (The SEQUENCE must be non-indented, and the rest of the DER
111*6777b538SAndroid Build Coastguard Worker  # ASCII must be indented until the closing } which again is non-indented.)
112*6777b538SAndroid Build Coastguard Worker  # The output of der2ascii meets this, but it is not a requirement of the DER
113*6777b538SAndroid Build Coastguard Worker  # ASCII language.
114*6777b538SAndroid Build Coastguard Worker  # TODO(mattm): consider alternate approach of doing ascii2der on entire
115*6777b538SAndroid Build Coastguard Worker  # input, and handling the multiple concatenated DER certificates.
116*6777b538SAndroid Build Coastguard Worker  regex = re.compile(r'^(SEQUENCE {.*?^})', re.DOTALL | re.MULTILINE)
117*6777b538SAndroid Build Coastguard Worker
118*6777b538SAndroid Build Coastguard Worker  for match in regex.finditer(input_text):
119*6777b538SAndroid Build Coastguard Worker    der_ascii_bytes = match.group(1)
120*6777b538SAndroid Build Coastguard Worker    der_bytes = process_data_with_command(["ascii2der"], der_ascii_bytes)
121*6777b538SAndroid Build Coastguard Worker    if der_bytes:
122*6777b538SAndroid Build Coastguard Worker      certificates_der.append(der_bytes)
123*6777b538SAndroid Build Coastguard Worker
124*6777b538SAndroid Build Coastguard Worker  return certificates_der
125*6777b538SAndroid Build Coastguard Worker
126*6777b538SAndroid Build Coastguard Worker
127*6777b538SAndroid Build Coastguard Workerdef decode_netlog_hexdump(netlog_text):
128*6777b538SAndroid Build Coastguard Worker  lines = netlog_text.splitlines()
129*6777b538SAndroid Build Coastguard Worker
130*6777b538SAndroid Build Coastguard Worker  # Skip the text preceeding the actual hexdump.
131*6777b538SAndroid Build Coastguard Worker  while lines and 'bytes =' not in lines[0]:
132*6777b538SAndroid Build Coastguard Worker    del lines[0]
133*6777b538SAndroid Build Coastguard Worker  if not lines:
134*6777b538SAndroid Build Coastguard Worker    return None
135*6777b538SAndroid Build Coastguard Worker  del lines[0]
136*6777b538SAndroid Build Coastguard Worker
137*6777b538SAndroid Build Coastguard Worker  bytes = []
138*6777b538SAndroid Build Coastguard Worker  hex_re = re.compile('\s*([0-9A-Fa-f ]{48})')
139*6777b538SAndroid Build Coastguard Worker  for line in lines:
140*6777b538SAndroid Build Coastguard Worker    m = hex_re.search(line)
141*6777b538SAndroid Build Coastguard Worker    if not m:
142*6777b538SAndroid Build Coastguard Worker      break
143*6777b538SAndroid Build Coastguard Worker    hex_string = m.group(1)
144*6777b538SAndroid Build Coastguard Worker    bytes.extend(chr(int(part, 16)) for part in hex_string.split())
145*6777b538SAndroid Build Coastguard Worker
146*6777b538SAndroid Build Coastguard Worker  return ''.join(bytes)
147*6777b538SAndroid Build Coastguard Worker
148*6777b538SAndroid Build Coastguard Worker
149*6777b538SAndroid Build Coastguard Workerclass ByteReader:
150*6777b538SAndroid Build Coastguard Worker  """Iteratively consume data from a byte string.
151*6777b538SAndroid Build Coastguard Worker
152*6777b538SAndroid Build Coastguard Worker  Automatically tracks and advances current position in the string as data is
153*6777b538SAndroid Build Coastguard Worker  consumed, and will throw an exception if attempting to read past the end of
154*6777b538SAndroid Build Coastguard Worker  the string.
155*6777b538SAndroid Build Coastguard Worker  """
156*6777b538SAndroid Build Coastguard Worker  def __init__(self, data):
157*6777b538SAndroid Build Coastguard Worker    self.data = data
158*6777b538SAndroid Build Coastguard Worker    self.pos = 0
159*6777b538SAndroid Build Coastguard Worker
160*6777b538SAndroid Build Coastguard Worker  def consume_byte(self):
161*6777b538SAndroid Build Coastguard Worker    i = ord(self.data[self.pos])
162*6777b538SAndroid Build Coastguard Worker    self.pos += 1
163*6777b538SAndroid Build Coastguard Worker    return i
164*6777b538SAndroid Build Coastguard Worker
165*6777b538SAndroid Build Coastguard Worker  def consume_int16(self):
166*6777b538SAndroid Build Coastguard Worker    return ((self.consume_byte() << 8) + self.consume_byte())
167*6777b538SAndroid Build Coastguard Worker
168*6777b538SAndroid Build Coastguard Worker  def consume_int24(self):
169*6777b538SAndroid Build Coastguard Worker    return ((self.consume_byte() << 16) + (self.consume_byte() << 8) +
170*6777b538SAndroid Build Coastguard Worker            self.consume_byte())
171*6777b538SAndroid Build Coastguard Worker
172*6777b538SAndroid Build Coastguard Worker  def consume_bytes(self, n):
173*6777b538SAndroid Build Coastguard Worker    b = self.data[self.pos:self.pos+n]
174*6777b538SAndroid Build Coastguard Worker    if len(b) != n:
175*6777b538SAndroid Build Coastguard Worker      raise IndexError('requested:%d bytes  actual:%d bytes'%(n, len(b)))
176*6777b538SAndroid Build Coastguard Worker    self.pos += n
177*6777b538SAndroid Build Coastguard Worker    return b
178*6777b538SAndroid Build Coastguard Worker
179*6777b538SAndroid Build Coastguard Worker  def remaining_byte_count(self):
180*6777b538SAndroid Build Coastguard Worker    return len(self.data) - self.pos
181*6777b538SAndroid Build Coastguard Worker
182*6777b538SAndroid Build Coastguard Worker
183*6777b538SAndroid Build Coastguard Workerdef decode_tls10_certificate_message(reader):
184*6777b538SAndroid Build Coastguard Worker  message_length = reader.consume_int24()
185*6777b538SAndroid Build Coastguard Worker  if reader.remaining_byte_count() != message_length:
186*6777b538SAndroid Build Coastguard Worker    raise RuntimeError(
187*6777b538SAndroid Build Coastguard Worker        'message_length(%d) != remaining_byte_count(%d)\n' % (
188*6777b538SAndroid Build Coastguard Worker            message_length, reader.remaining_byte_count()))
189*6777b538SAndroid Build Coastguard Worker
190*6777b538SAndroid Build Coastguard Worker  certificate_list_length = reader.consume_int24()
191*6777b538SAndroid Build Coastguard Worker  if reader.remaining_byte_count() != certificate_list_length:
192*6777b538SAndroid Build Coastguard Worker    raise RuntimeError(
193*6777b538SAndroid Build Coastguard Worker        'certificate_list_length(%d) != remaining_byte_count(%d)\n' % (
194*6777b538SAndroid Build Coastguard Worker            certificate_list_length, reader.remaining_byte_count()))
195*6777b538SAndroid Build Coastguard Worker
196*6777b538SAndroid Build Coastguard Worker  certificates_der = []
197*6777b538SAndroid Build Coastguard Worker  while reader.remaining_byte_count():
198*6777b538SAndroid Build Coastguard Worker    cert_len = reader.consume_int24()
199*6777b538SAndroid Build Coastguard Worker    certificates_der.append(reader.consume_bytes(cert_len))
200*6777b538SAndroid Build Coastguard Worker
201*6777b538SAndroid Build Coastguard Worker  return certificates_der
202*6777b538SAndroid Build Coastguard Worker
203*6777b538SAndroid Build Coastguard Worker
204*6777b538SAndroid Build Coastguard Workerdef decode_tls13_certificate_message(reader):
205*6777b538SAndroid Build Coastguard Worker  message_length = reader.consume_int24()
206*6777b538SAndroid Build Coastguard Worker  if reader.remaining_byte_count() != message_length:
207*6777b538SAndroid Build Coastguard Worker    raise RuntimeError(
208*6777b538SAndroid Build Coastguard Worker        'message_length(%d) != remaining_byte_count(%d)\n' % (
209*6777b538SAndroid Build Coastguard Worker            message_length, reader.remaining_byte_count()))
210*6777b538SAndroid Build Coastguard Worker
211*6777b538SAndroid Build Coastguard Worker  # Ignore certificate_request_context.
212*6777b538SAndroid Build Coastguard Worker  certificate_request_context_length = reader.consume_byte()
213*6777b538SAndroid Build Coastguard Worker  reader.consume_bytes(certificate_request_context_length)
214*6777b538SAndroid Build Coastguard Worker
215*6777b538SAndroid Build Coastguard Worker  certificate_list_length = reader.consume_int24()
216*6777b538SAndroid Build Coastguard Worker  if reader.remaining_byte_count() != certificate_list_length:
217*6777b538SAndroid Build Coastguard Worker    raise RuntimeError(
218*6777b538SAndroid Build Coastguard Worker        'certificate_list_length(%d) != remaining_byte_count(%d)\n' % (
219*6777b538SAndroid Build Coastguard Worker            certificate_list_length, reader.remaining_byte_count()))
220*6777b538SAndroid Build Coastguard Worker
221*6777b538SAndroid Build Coastguard Worker  certificates_der = []
222*6777b538SAndroid Build Coastguard Worker  while reader.remaining_byte_count():
223*6777b538SAndroid Build Coastguard Worker    # Assume certificate_type is X.509.
224*6777b538SAndroid Build Coastguard Worker    cert_len = reader.consume_int24()
225*6777b538SAndroid Build Coastguard Worker    certificates_der.append(reader.consume_bytes(cert_len))
226*6777b538SAndroid Build Coastguard Worker    # Ignore extensions.
227*6777b538SAndroid Build Coastguard Worker    extension_len = reader.consume_int16()
228*6777b538SAndroid Build Coastguard Worker    reader.consume_bytes(extension_len)
229*6777b538SAndroid Build Coastguard Worker
230*6777b538SAndroid Build Coastguard Worker  return certificates_der
231*6777b538SAndroid Build Coastguard Worker
232*6777b538SAndroid Build Coastguard Worker
233*6777b538SAndroid Build Coastguard Workerdef decode_tls_certificate_message(certificate_message):
234*6777b538SAndroid Build Coastguard Worker  reader = ByteReader(certificate_message)
235*6777b538SAndroid Build Coastguard Worker  if reader.consume_byte() != 11:
236*6777b538SAndroid Build Coastguard Worker    sys.stderr.write('HandshakeType != 11. Not a Certificate Message.\n')
237*6777b538SAndroid Build Coastguard Worker    return []
238*6777b538SAndroid Build Coastguard Worker
239*6777b538SAndroid Build Coastguard Worker  # The TLS certificate message encoding changed in TLS 1.3. Rather than
240*6777b538SAndroid Build Coastguard Worker  # require pasting in and parsing the whole handshake to discover the TLS
241*6777b538SAndroid Build Coastguard Worker  # version, just try parsing the message with both the old and new encodings.
242*6777b538SAndroid Build Coastguard Worker
243*6777b538SAndroid Build Coastguard Worker  # First try the old style certificate message:
244*6777b538SAndroid Build Coastguard Worker  try:
245*6777b538SAndroid Build Coastguard Worker    return decode_tls10_certificate_message(reader)
246*6777b538SAndroid Build Coastguard Worker  except (IndexError, RuntimeError):
247*6777b538SAndroid Build Coastguard Worker    tls10_traceback = traceback.format_exc()
248*6777b538SAndroid Build Coastguard Worker
249*6777b538SAndroid Build Coastguard Worker  # Restart the ByteReader and consume the HandshakeType byte again.
250*6777b538SAndroid Build Coastguard Worker  reader = ByteReader(certificate_message)
251*6777b538SAndroid Build Coastguard Worker  reader.consume_byte()
252*6777b538SAndroid Build Coastguard Worker  # Try the new style certificate message:
253*6777b538SAndroid Build Coastguard Worker  try:
254*6777b538SAndroid Build Coastguard Worker    return decode_tls13_certificate_message(reader)
255*6777b538SAndroid Build Coastguard Worker  except (IndexError, RuntimeError):
256*6777b538SAndroid Build Coastguard Worker    tls13_traceback = traceback.format_exc()
257*6777b538SAndroid Build Coastguard Worker
258*6777b538SAndroid Build Coastguard Worker  # Neither attempt succeeded, just dump some error info:
259*6777b538SAndroid Build Coastguard Worker  sys.stderr.write("Couldn't parse TLS certificate message\n")
260*6777b538SAndroid Build Coastguard Worker  sys.stderr.write("TLS1.0 parse attempt:\n%s\n" % tls10_traceback)
261*6777b538SAndroid Build Coastguard Worker  sys.stderr.write("TLS1.3 parse attempt:\n%s\n" % tls13_traceback)
262*6777b538SAndroid Build Coastguard Worker  sys.stderr.write("\n")
263*6777b538SAndroid Build Coastguard Worker
264*6777b538SAndroid Build Coastguard Worker  return []
265*6777b538SAndroid Build Coastguard Worker
266*6777b538SAndroid Build Coastguard Worker
267*6777b538SAndroid Build Coastguard Workerdef extract_tls_certificate_message(netlog_text):
268*6777b538SAndroid Build Coastguard Worker  raw_certificate_message = decode_netlog_hexdump(netlog_text)
269*6777b538SAndroid Build Coastguard Worker  if not raw_certificate_message:
270*6777b538SAndroid Build Coastguard Worker    return []
271*6777b538SAndroid Build Coastguard Worker  return decode_tls_certificate_message(raw_certificate_message)
272*6777b538SAndroid Build Coastguard Worker
273*6777b538SAndroid Build Coastguard Worker
274*6777b538SAndroid Build Coastguard Workerdef extract_certificates(source_bytes):
275*6777b538SAndroid Build Coastguard Worker  if b"BEGIN CERTIFICATE" in source_bytes or b"BEGIN PKCS7" in source_bytes:
276*6777b538SAndroid Build Coastguard Worker    return extract_certificates_from_pem(source_bytes)
277*6777b538SAndroid Build Coastguard Worker
278*6777b538SAndroid Build Coastguard Worker  if b"SEQUENCE {" in source_bytes:
279*6777b538SAndroid Build Coastguard Worker    return extract_certificates_from_der_ascii(source_bytes)
280*6777b538SAndroid Build Coastguard Worker
281*6777b538SAndroid Build Coastguard Worker  if b"SSL_HANDSHAKE_MESSAGE_RECEIVED" in source_bytes:
282*6777b538SAndroid Build Coastguard Worker    return extract_tls_certificate_message(source_bytes)
283*6777b538SAndroid Build Coastguard Worker
284*6777b538SAndroid Build Coastguard Worker  # DER encoding of PKCS #7 signedData OID (1.2.840.113549.1.7.2)
285*6777b538SAndroid Build Coastguard Worker  if b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02" in source_bytes:
286*6777b538SAndroid Build Coastguard Worker    return extract_certificates_from_der_pkcs7(source_bytes)
287*6777b538SAndroid Build Coastguard Worker
288*6777b538SAndroid Build Coastguard Worker  # Otherwise assume it is the DER for a single certificate
289*6777b538SAndroid Build Coastguard Worker  return [source_bytes]
290*6777b538SAndroid Build Coastguard Worker
291*6777b538SAndroid Build Coastguard Worker
292*6777b538SAndroid Build Coastguard Workerdef process_data_with_command(command, data):
293*6777b538SAndroid Build Coastguard Worker  try:
294*6777b538SAndroid Build Coastguard Worker    p = subprocess.Popen(command,
295*6777b538SAndroid Build Coastguard Worker                         stdin=subprocess.PIPE,
296*6777b538SAndroid Build Coastguard Worker                         stdout=subprocess.PIPE,
297*6777b538SAndroid Build Coastguard Worker                         stderr=subprocess.PIPE)
298*6777b538SAndroid Build Coastguard Worker  except OSError as e:
299*6777b538SAndroid Build Coastguard Worker    if e.errno == errno.ENOENT:
300*6777b538SAndroid Build Coastguard Worker      sys.stderr.write("Failed to execute %s\n" % command[0])
301*6777b538SAndroid Build Coastguard Worker      return b""
302*6777b538SAndroid Build Coastguard Worker    raise
303*6777b538SAndroid Build Coastguard Worker
304*6777b538SAndroid Build Coastguard Worker  result = p.communicate(data)
305*6777b538SAndroid Build Coastguard Worker
306*6777b538SAndroid Build Coastguard Worker  if p.returncode == 0:
307*6777b538SAndroid Build Coastguard Worker    return result[0]
308*6777b538SAndroid Build Coastguard Worker
309*6777b538SAndroid Build Coastguard Worker  # Otherwise failed.
310*6777b538SAndroid Build Coastguard Worker  sys.stderr.write("Failed: %s: %s\n" % (" ".join(command), result[1]))
311*6777b538SAndroid Build Coastguard Worker  return b""
312*6777b538SAndroid Build Coastguard Worker
313*6777b538SAndroid Build Coastguard Worker
314*6777b538SAndroid Build Coastguard Workerdef openssl_text_pretty_printer(certificate_der, unused_certificate_number):
315*6777b538SAndroid Build Coastguard Worker  return process_data_with_command(["openssl", "x509", "-text", "-inform",
316*6777b538SAndroid Build Coastguard Worker                                   "DER", "-noout"], certificate_der)
317*6777b538SAndroid Build Coastguard Worker
318*6777b538SAndroid Build Coastguard Worker
319*6777b538SAndroid Build Coastguard Workerdef pem_pretty_printer(certificate_der, unused_certificate_number):
320*6777b538SAndroid Build Coastguard Worker  return process_data_with_command(["openssl", "x509", "-inform", "DER",
321*6777b538SAndroid Build Coastguard Worker                                   "-outform", "PEM"], certificate_der)
322*6777b538SAndroid Build Coastguard Worker
323*6777b538SAndroid Build Coastguard Worker
324*6777b538SAndroid Build Coastguard Workerdef der2ascii_pretty_printer(certificate_der, unused_certificate_number):
325*6777b538SAndroid Build Coastguard Worker  return process_data_with_command(["der2ascii"], certificate_der)
326*6777b538SAndroid Build Coastguard Worker
327*6777b538SAndroid Build Coastguard Worker
328*6777b538SAndroid Build Coastguard Workerdef header_pretty_printer(certificate_der, certificate_number):
329*6777b538SAndroid Build Coastguard Worker  cert_hash = hashlib.sha256(certificate_der).hexdigest()
330*6777b538SAndroid Build Coastguard Worker  s = """===========================================
331*6777b538SAndroid Build Coastguard WorkerCertificate%d: %s
332*6777b538SAndroid Build Coastguard Worker===========================================""" % (certificate_number, cert_hash)
333*6777b538SAndroid Build Coastguard Worker  return s.encode("ascii")
334*6777b538SAndroid Build Coastguard Worker
335*6777b538SAndroid Build Coastguard Worker
336*6777b538SAndroid Build Coastguard Worker# This is actually just used as a magic value, since pretty_print_certificates
337*6777b538SAndroid Build Coastguard Worker# special-cases der output.
338*6777b538SAndroid Build Coastguard Workerdef der_printer():
339*6777b538SAndroid Build Coastguard Worker  raise RuntimeError
340*6777b538SAndroid Build Coastguard Worker
341*6777b538SAndroid Build Coastguard Worker
342*6777b538SAndroid Build Coastguard Workerdef pretty_print_certificates(certificates_der, pretty_printers):
343*6777b538SAndroid Build Coastguard Worker  # Need to special-case DER output to avoid adding any newlines, and to
344*6777b538SAndroid Build Coastguard Worker  # only allow a single certificate to be output.
345*6777b538SAndroid Build Coastguard Worker  if pretty_printers == [der_printer]:
346*6777b538SAndroid Build Coastguard Worker    if len(certificates_der) > 1:
347*6777b538SAndroid Build Coastguard Worker      sys.stderr.write("DER output only supports a single certificate, "
348*6777b538SAndroid Build Coastguard Worker                       "ignoring %d remaining certs\n" % (
349*6777b538SAndroid Build Coastguard Worker                           len(certificates_der) - 1))
350*6777b538SAndroid Build Coastguard Worker    return certificates_der[0]
351*6777b538SAndroid Build Coastguard Worker
352*6777b538SAndroid Build Coastguard Worker  result = b""
353*6777b538SAndroid Build Coastguard Worker  for i in range(len(certificates_der)):
354*6777b538SAndroid Build Coastguard Worker    certificate_der = certificates_der[i]
355*6777b538SAndroid Build Coastguard Worker    pretty = []
356*6777b538SAndroid Build Coastguard Worker    for pretty_printer in pretty_printers:
357*6777b538SAndroid Build Coastguard Worker      pretty_printed = pretty_printer(certificate_der, i)
358*6777b538SAndroid Build Coastguard Worker      if pretty_printed:
359*6777b538SAndroid Build Coastguard Worker        pretty.append(pretty_printed)
360*6777b538SAndroid Build Coastguard Worker    result += b"\n".join(pretty) + b"\n"
361*6777b538SAndroid Build Coastguard Worker  return result
362*6777b538SAndroid Build Coastguard Worker
363*6777b538SAndroid Build Coastguard Worker
364*6777b538SAndroid Build Coastguard Workerdef parse_outputs(outputs):
365*6777b538SAndroid Build Coastguard Worker  pretty_printers = []
366*6777b538SAndroid Build Coastguard Worker  output_map = {"der2ascii": der2ascii_pretty_printer,
367*6777b538SAndroid Build Coastguard Worker                "openssl_text": openssl_text_pretty_printer,
368*6777b538SAndroid Build Coastguard Worker                "pem": pem_pretty_printer,
369*6777b538SAndroid Build Coastguard Worker                "header": header_pretty_printer,
370*6777b538SAndroid Build Coastguard Worker                "der": der_printer}
371*6777b538SAndroid Build Coastguard Worker  for output_name in outputs.split(','):
372*6777b538SAndroid Build Coastguard Worker    if output_name not in output_map:
373*6777b538SAndroid Build Coastguard Worker      sys.stderr.write("Invalid output type: %s\n" % output_name)
374*6777b538SAndroid Build Coastguard Worker      return []
375*6777b538SAndroid Build Coastguard Worker    pretty_printers.append(output_map[output_name])
376*6777b538SAndroid Build Coastguard Worker  if der_printer in pretty_printers and len(pretty_printers) > 1:
377*6777b538SAndroid Build Coastguard Worker    sys.stderr.write("Output type der must be used alone.\n")
378*6777b538SAndroid Build Coastguard Worker    return []
379*6777b538SAndroid Build Coastguard Worker  return pretty_printers
380*6777b538SAndroid Build Coastguard Worker
381*6777b538SAndroid Build Coastguard Worker
382*6777b538SAndroid Build Coastguard Workerdef main():
383*6777b538SAndroid Build Coastguard Worker  parser = argparse.ArgumentParser(
384*6777b538SAndroid Build Coastguard Worker      description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
385*6777b538SAndroid Build Coastguard Worker
386*6777b538SAndroid Build Coastguard Worker  parser.add_argument('sources', metavar='SOURCE', nargs='*',
387*6777b538SAndroid Build Coastguard Worker                      help='''Each SOURCE can be one of:
388*6777b538SAndroid Build Coastguard Worker  (1) A server name such as www.google.com.
389*6777b538SAndroid Build Coastguard Worker  (2) A PEM [*] file containing one or more CERTIFICATE or PKCS7 blocks
390*6777b538SAndroid Build Coastguard Worker  (3) A file containing one or more DER ASCII certificates
391*6777b538SAndroid Build Coastguard Worker  (4) A text NetLog dump of a TLS certificate message
392*6777b538SAndroid Build Coastguard Worker      (must include the SSL_HANDSHAKE_MESSAGE_RECEIVED line)
393*6777b538SAndroid Build Coastguard Worker  (5) A binary file containing DER-encoded PKCS #7 signedData
394*6777b538SAndroid Build Coastguard Worker  (6) A binary file containing DER-encoded certificate
395*6777b538SAndroid Build Coastguard Worker
396*6777b538SAndroid Build Coastguard WorkerWhen multiple SOURCEs are listed, all certificates in them
397*6777b538SAndroid Build Coastguard Workerare concatenated. If no SOURCE is given then data will be
398*6777b538SAndroid Build Coastguard Workerread from stdin.
399*6777b538SAndroid Build Coastguard Worker
400*6777b538SAndroid Build Coastguard Worker[*] Parsing of PEM files is relaxed - leading indentation
401*6777b538SAndroid Build Coastguard Workerwhitespace will be stripped (needed for copy-pasting data
402*6777b538SAndroid Build Coastguard Workerfrom NetLogs).''')
403*6777b538SAndroid Build Coastguard Worker
404*6777b538SAndroid Build Coastguard Worker  parser.add_argument('--output',
405*6777b538SAndroid Build Coastguard Worker                      dest='outputs',
406*6777b538SAndroid Build Coastguard Worker                      action='store',
407*6777b538SAndroid Build Coastguard Worker                      default="header,openssl_text,pem",
408*6777b538SAndroid Build Coastguard Worker                      help='output formats to use. Default: %(default)s')
409*6777b538SAndroid Build Coastguard Worker
410*6777b538SAndroid Build Coastguard Worker  args = parser.parse_args()
411*6777b538SAndroid Build Coastguard Worker
412*6777b538SAndroid Build Coastguard Worker  sources_bytes = read_sources_from_commandline(args.sources)
413*6777b538SAndroid Build Coastguard Worker
414*6777b538SAndroid Build Coastguard Worker  pretty_printers = parse_outputs(args.outputs)
415*6777b538SAndroid Build Coastguard Worker  if not pretty_printers:
416*6777b538SAndroid Build Coastguard Worker    sys.stderr.write('No pretty printers selected.\n')
417*6777b538SAndroid Build Coastguard Worker    sys.exit(1)
418*6777b538SAndroid Build Coastguard Worker
419*6777b538SAndroid Build Coastguard Worker  certificates_der = []
420*6777b538SAndroid Build Coastguard Worker  for source_bytes in sources_bytes:
421*6777b538SAndroid Build Coastguard Worker    certificates_der.extend(extract_certificates(source_bytes))
422*6777b538SAndroid Build Coastguard Worker
423*6777b538SAndroid Build Coastguard Worker  sys.stdout.buffer.write(
424*6777b538SAndroid Build Coastguard Worker      pretty_print_certificates(certificates_der, pretty_printers))
425*6777b538SAndroid Build Coastguard Worker
426*6777b538SAndroid Build Coastguard Worker
427*6777b538SAndroid Build Coastguard Workerif __name__ == "__main__":
428*6777b538SAndroid Build Coastguard Worker  main()
429