1#!/usr/bin/env python
2# Copyright 2016 The Chromium Authors
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     https://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""
17Given a path to a XXX.pem file, re-generates a CERTIFICATE.
18
19The .pem file is expected to contain comments that resemble:
20
21#-----BEGIN XXX-----
22<ascii-der values in here>
23#-----END XXX-----
24
25These are interpreted as substitutions to make inside of the Certificate
26template (v3_certificate_template.txt)
27"""
28
29import sys
30import os
31import re
32import base64
33import subprocess
34
35
36def read_file_to_string(path):
37  """Reads a file entirely to a string"""
38  with open(path, 'r') as f:
39    return f.read()
40
41
42def write_string_to_file(data, path):
43  """Writes a string to a file"""
44  print "Writing file %s ..." % (path)
45  with open(path, "w") as f:
46    f.write(data)
47
48
49def replace_string(original, start, end, replacement):
50  """Replaces the specified range of |original| with |replacement|"""
51  return original[0:start] + replacement + original[end:]
52
53
54def apply_substitution(template, name, value):
55  """Finds a section named |name| in |template| and replaces it with |value|."""
56  # Find the section |name| in |template|.
57  regex = re.compile(r'#-----BEGIN %s-----(.*?)#-----END %s-----' %
58                    (re.escape(name), re.escape(name)), re.DOTALL)
59  m = regex.search(template)
60  if not m:
61    print "Couldn't find a section named %s in the template" % (name)
62    sys.exit(1)
63
64  return replace_string(template, m.start(1), m.end(1), value)
65
66
67def main():
68  if len(sys.argv) != 2:
69    print 'Usage: %s <PATH_TO_PEM>' % (sys.argv[0])
70    sys.exit(1)
71
72  pem_path = sys.argv[1]
73  orig = read_file_to_string(pem_path)
74
75  cert_ascii = read_file_to_string("v3_certificate_template.txt")
76
77  # Apply all substitutions described by comments in |orig|
78  regex = re.compile(r'#-----BEGIN ([\w ]+)-----(.*?)#-----END \1-----',
79                     re.DOTALL)
80  num_matches = 0
81  for m in regex.finditer(orig):
82    num_matches += 1
83    cert_ascii = apply_substitution(cert_ascii, m.group(1), m.group(2))
84
85  if num_matches == 0:
86    print "Input did not contain any substitutions"
87    sys.exit(1)
88
89  # Convert the ascii-der to actual DER binary.
90  cert_der = None
91  try:
92    p = subprocess.Popen(['ascii2der'], stdout=subprocess.PIPE,
93                         stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
94    cert_der = p.communicate(input=cert_ascii)[0]
95  except OSError as e:
96    print ('ERROR: Failed executing ascii2der.\n'
97           'Make sure this is in your path\n'
98           'Obtain it from https://github.com/google/der-ascii')
99    sys.exit(1)
100
101  # Replace the CERTIFICATE block with the newly generated one.
102  regex = re.compile(r'-----BEGIN CERTIFICATE-----\n(.*?)\n'
103                     '-----END CERTIFICATE-----', re.DOTALL)
104  m = regex.search(orig)
105  if not m:
106    print "ERROR: Cannot find CERTIFICATE block in input"
107    sys.exit(1)
108  modified = replace_string(orig, m.start(1), m.end(1),
109                            base64.b64encode(cert_der))
110
111  # Write back the .pem file.
112  write_string_to_file(modified, pem_path)
113
114main()
115