1#!/usr/bin/python
2
3"""Extract certificates from a multi-certificate pem file.
4
5Each certificate in the file is extracted into a format appropriate for use with
6Brillo or Android. On success, the contents of the output directory match the
7input file exactly. Existing files in the output directory will be deleted.
8
9The current date will be written into the timestamp file, './TIMESTAMP' by
10default.
11
12Typical usage (extracting from ./roots.pem and output into ./files):
13> ./extract_from_pem.py
14"""
15
16import argparse
17import datetime
18import os
19import re
20
21import M2Crypto  # sudo apt-get install python-m2crypto
22
23
24def WriteCertificateFile(content, base_name, output_dir):
25  """Writes a certificate file to the output directory.
26
27  Args:
28    content: The file content to write.
29    base_name: The file name will be base_name.n where n is the first available
30               non-negative integer. Ex. if myfile.0 exists and has different
31               content, the output file will be myfile.1.
32    output_dir: The output directory.
33  """
34  i = 0
35  file_path = os.path.join(output_dir, '%s.%d' % (base_name, i))
36  while os.path.exists(file_path):
37    with open(file_path) as existing_file:
38      if content == existing_file.read():
39        # Ignore identical duplicate.
40        return
41    i += 1
42    file_path = os.path.join(output_dir, '%s.%d' % (base_name, i))
43  with open(file_path, 'w') as new_file:
44    new_file.write(content)
45
46
47def GetFingerprintString(x509):
48  """Computes a fingerprint string as output by 'openssl x509 -fingerprint'.
49
50  Args:
51    x509: A M2Crypto.X509.X509 object.
52
53  Returns:
54    The fingerprint as a string.
55  """
56  # Zero filled and with ':' between bytes.
57  return ':'.join(re.findall(r'..', x509.get_fingerprint('sha1').zfill(40)))
58
59
60def main():
61  parser = argparse.ArgumentParser(description='PEM Certificate Importer')
62  parser.add_argument('--pem_file', nargs='?', default='roots.pem')
63  parser.add_argument('--output_dir', nargs='?', default='files')
64  parser.add_argument('--timestamp_file', nargs='?', default='TIMESTAMP')
65  args = parser.parse_args()
66  assert os.path.isdir(args.output_dir) and os.path.isfile(args.pem_file)
67  if 'y' != raw_input('All files in \'%s\' will be deleted. Proceed? [y,N]: ' %
68                          args.output_dir):
69    print 'Aborted.'
70    return
71  for existing_file in os.listdir(args.output_dir):
72    os.remove(os.path.join(args.output_dir, existing_file))
73  with open(args.pem_file) as pem_file:
74    pattern = r'-----BEGIN CERTIFICATE-----[^-]*-----END CERTIFICATE-----'
75    pem_certs = re.findall(pattern, pem_file.read())
76    for pem_cert in pem_certs:
77      x509 = M2Crypto.X509.load_cert_string(pem_cert)
78      content = '%s%sSHA1 Fingerprint=%s\n' % (x509.as_pem(),
79                                               x509.as_text(),
80                                               GetFingerprintString(x509))
81      base_name = '%08x' % x509.get_subject().as_hash()
82      WriteCertificateFile(content, base_name, args.output_dir)
83  with open(args.timestamp_file, 'w') as timestamp_file:
84    timestamp_file.write('Last Update (YYYY-MM-DD): %s\n' %
85                             datetime.date.today().isoformat())
86
87
88if __name__ == '__main__':
89  main()
90