195b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn#!/usr/bin/python
295b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
395b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn"""Extract certificates from a multi-certificate pem file.
495b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
595b0a57771b92fa238882ffbcf5ee377c8110f75Darren KrahnEach certificate in the file is extracted into a format appropriate for use with
695b0a57771b92fa238882ffbcf5ee377c8110f75Darren KrahnBrillo or Android. On success, the contents of the output directory match the
795b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahninput file exactly. Existing files in the output directory will be deleted.
895b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
995b0a57771b92fa238882ffbcf5ee377c8110f75Darren KrahnThe current date will be written into the timestamp file, './TIMESTAMP' by
1095b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahndefault.
1195b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
1295b0a57771b92fa238882ffbcf5ee377c8110f75Darren KrahnTypical usage (extracting from ./roots.pem and output into ./files):
1395b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn> ./extract_from_pem.py
1495b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn"""
1595b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
1695b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahnimport argparse
1795b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahnimport datetime
1895b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahnimport os
1995b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahnimport re
2095b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
2195b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahnimport M2Crypto  # sudo apt-get install python-m2crypto
2295b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
2395b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
2495b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahndef WriteCertificateFile(content, base_name, output_dir):
2595b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  """Writes a certificate file to the output directory.
2695b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
2795b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  Args:
2895b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    content: The file content to write.
2995b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    base_name: The file name will be base_name.n where n is the first available
3095b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn               non-negative integer. Ex. if myfile.0 exists and has different
3195b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn               content, the output file will be myfile.1.
3295b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    output_dir: The output directory.
3395b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  """
3495b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  i = 0
3595b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  file_path = os.path.join(output_dir, '%s.%d' % (base_name, i))
3695b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  while os.path.exists(file_path):
3795b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    with open(file_path) as existing_file:
3895b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn      if content == existing_file.read():
3995b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn        # Ignore identical duplicate.
4095b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn        return
4195b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    i += 1
4295b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    file_path = os.path.join(output_dir, '%s.%d' % (base_name, i))
4395b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  with open(file_path, 'w') as new_file:
4495b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    new_file.write(content)
4595b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
4695b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
4795b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahndef GetFingerprintString(x509):
4895b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  """Computes a fingerprint string as output by 'openssl x509 -fingerprint'.
4995b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
5095b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  Args:
5195b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    x509: A M2Crypto.X509.X509 object.
5295b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
5395b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  Returns:
5495b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    The fingerprint as a string.
5595b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  """
5695b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  # Zero filled and with ':' between bytes.
5795b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  return ':'.join(re.findall(r'..', x509.get_fingerprint('sha1').zfill(40)))
5895b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
5995b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
6095b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahndef main():
6195b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  parser = argparse.ArgumentParser(description='PEM Certificate Importer')
6295b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  parser.add_argument('--pem_file', nargs='?', default='roots.pem')
6395b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  parser.add_argument('--output_dir', nargs='?', default='files')
6495b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  parser.add_argument('--timestamp_file', nargs='?', default='TIMESTAMP')
6595b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  args = parser.parse_args()
6695b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  assert os.path.isdir(args.output_dir) and os.path.isfile(args.pem_file)
6795b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  if 'y' != raw_input('All files in \'%s\' will be deleted. Proceed? [y,N]: ' %
6895b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn                          args.output_dir):
6995b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    print 'Aborted.'
7095b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    return
7195b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  for existing_file in os.listdir(args.output_dir):
7295b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    os.remove(os.path.join(args.output_dir, existing_file))
7395b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  with open(args.pem_file) as pem_file:
7495b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    pattern = r'-----BEGIN CERTIFICATE-----[^-]*-----END CERTIFICATE-----'
7595b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    pem_certs = re.findall(pattern, pem_file.read())
7695b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    for pem_cert in pem_certs:
7795b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn      x509 = M2Crypto.X509.load_cert_string(pem_cert)
7895b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn      content = '%s%sSHA1 Fingerprint=%s\n' % (x509.as_pem(),
7995b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn                                               x509.as_text(),
8095b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn                                               GetFingerprintString(x509))
8195b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn      base_name = '%08x' % x509.get_subject().as_hash()
8295b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn      WriteCertificateFile(content, base_name, args.output_dir)
8395b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  with open(args.timestamp_file, 'w') as timestamp_file:
8495b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn    timestamp_file.write('Last Update (YYYY-MM-DD): %s\n' %
8595b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn                             datetime.date.today().isoformat())
8695b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
8795b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn
8895b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahnif __name__ == '__main__':
8995b0a57771b92fa238882ffbcf5ee377c8110f75Darren Krahn  main()
90