blob: e2da02935d87ff731fc8c0ed5fdce8db50a2116b [file] [log] [blame]
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001#!/usr/bin/env vpython3
2
Guo-wei Shiehee408212015-12-09 11:25:38 -08003# -*- coding:utf-8 -*-
4# Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
5#
6# Use of this source code is governed by a BSD-style license
7# that can be found in the LICENSE file in the root of the source
8# tree. An additional intellectual property rights grant can be found
9# in the file PATENTS. All contributing project authors may
10# be found in the AUTHORS file in the root of the source tree.
Guo-wei Shiehee408212015-12-09 11:25:38 -080011"""This is a tool to transform a crt file into a C/C++ header.
12
13Usage:
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +010014python3 generate_sslroots.py certfile.pem [--verbose | -v] [--full_cert | -f]
Guo-wei Shiehee408212015-12-09 11:25:38 -080015
16Arguments:
17 -v Print output while running.
18 -f Add public key and certificate name. Default is to skip and reduce
19 generated file size.
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +010020
21The supported cert files are:
22 - Google: https://pki.goog/roots.pem
23 - Mozilla: https://curl.se/docs/caextract.html
Guo-wei Shiehee408212015-12-09 11:25:38 -080024"""
25
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010026import subprocess
Guo-wei Shiehee408212015-12-09 11:25:38 -080027from optparse import OptionParser
28import os
29import re
Guo-wei Shiehee408212015-12-09 11:25:38 -080030
Mirko Bonadei86bd33a2020-04-24 21:53:49 +020031_GENERATED_FILE = 'ssl_roots.h'
Guo-wei Shiehee408212015-12-09 11:25:38 -080032_PREFIX = '__generated__'
33_EXTENSION = '.crt'
34_SUBJECT_NAME_ARRAY = 'subject_name'
35_SUBJECT_NAME_VARIABLE = 'SubjectName'
36_PUBLIC_KEY_ARRAY = 'public_key'
37_PUBLIC_KEY_VARIABLE = 'PublicKey'
38_CERTIFICATE_ARRAY = 'certificate'
39_CERTIFICATE_VARIABLE = 'Certificate'
40_CERTIFICATE_SIZE_VARIABLE = 'CertificateSize'
41_INT_TYPE = 'size_t'
Taylor Brandstetterbee59832020-10-28 14:06:21 -070042_CHAR_TYPE = 'unsigned char* const'
Guo-wei Shiehee408212015-12-09 11:25:38 -080043_VERBOSE = 'verbose'
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +010044_MOZILLA_BUNDLE_CHECK = '## Certificate data from Mozilla as of:'
Guo-wei Shiehee408212015-12-09 11:25:38 -080045
46
47def main():
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010048 """The main entrypoint."""
49 parser = OptionParser('usage %prog FILE')
50 parser.add_option('-v', '--verbose', dest='verbose', action='store_true')
51 parser.add_option('-f', '--full_cert', dest='full_cert', action='store_true')
52 options, args = parser.parse_args()
53 if len(args) < 1:
54 parser.error('No crt file specified.')
55 return
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +010056 root_dir, bundle_type = _SplitCrt(args[0], options)
57 _GenCFiles(root_dir, options, bundle_type)
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010058 _Cleanup(root_dir)
Guo-wei Shiehee408212015-12-09 11:25:38 -080059
60
61def _SplitCrt(source_file, options):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010062 sub_file_blocks = []
63 label_name = ''
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +010064 prev_line = None
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010065 root_dir = os.path.dirname(os.path.abspath(source_file)) + '/'
66 _PrintOutput(root_dir, options)
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +010067 lines = None
68 with open(source_file) as f:
69 lines = f.readlines()
70 mozilla_bundle = any(l.startswith(_MOZILLA_BUNDLE_CHECK) for l in lines)
71 for line in lines:
72 if line.startswith('#'):
73 if mozilla_bundle:
74 continue
75 if line.startswith('# Label: '):
76 sub_file_blocks.append(line)
77 label = re.search(r'\".*\"', line)
78 temp_label = label.group(0)
79 end = len(temp_label) - 1
80 label_name = _SafeName(temp_label[1:end])
81 if mozilla_bundle and line.startswith('==='):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010082 sub_file_blocks.append(line)
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +010083 label_name = _SafeName(prev_line)
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010084 elif line.startswith('-----END CERTIFICATE-----'):
85 sub_file_blocks.append(line)
86 new_file_name = root_dir + _PREFIX + label_name + _EXTENSION
87 _PrintOutput('Generating: ' + new_file_name, options)
88 new_file = open(new_file_name, 'w')
89 for out_line in sub_file_blocks:
90 new_file.write(out_line)
91 new_file.close()
92 sub_file_blocks = []
93 else:
94 sub_file_blocks.append(line)
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +010095 prev_line = line
96 return root_dir, 'Mozilla' if mozilla_bundle else 'Google'
Guo-wei Shiehee408212015-12-09 11:25:38 -080097
98
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +010099def _GenCFiles(root_dir, options, bundle_type):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100100 output_header_file = open(root_dir + _GENERATED_FILE, 'w')
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +0100101 output_header_file.write(_CreateOutputHeader(bundle_type))
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100102 if options.full_cert:
103 subject_name_list = _CreateArraySectionHeader(_SUBJECT_NAME_VARIABLE,
104 _CHAR_TYPE, options)
105 public_key_list = _CreateArraySectionHeader(_PUBLIC_KEY_VARIABLE,
106 _CHAR_TYPE, options)
107 certificate_list = _CreateArraySectionHeader(_CERTIFICATE_VARIABLE,
108 _CHAR_TYPE, options)
109 certificate_size_list = _CreateArraySectionHeader(_CERTIFICATE_SIZE_VARIABLE,
110 _INT_TYPE, options)
Guo-wei Shiehee408212015-12-09 11:25:38 -0800111
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100112 for _, _, files in os.walk(root_dir):
113 for current_file in files:
114 if current_file.startswith(_PREFIX):
115 prefix_length = len(_PREFIX)
116 length = len(current_file) - len(_EXTENSION)
117 label = current_file[prefix_length:length]
118 filtered_output, cert_size = _CreateCertSection(root_dir, current_file,
119 label, options)
120 output_header_file.write(filtered_output + '\n\n\n')
121 if options.full_cert:
122 subject_name_list += _AddLabelToArray(label, _SUBJECT_NAME_ARRAY)
123 public_key_list += _AddLabelToArray(label, _PUBLIC_KEY_ARRAY)
124 certificate_list += _AddLabelToArray(label, _CERTIFICATE_ARRAY)
125 certificate_size_list += (' %s,\n') % (cert_size)
Guo-wei Shiehee408212015-12-09 11:25:38 -0800126
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100127 if options.full_cert:
128 subject_name_list += _CreateArraySectionFooter()
129 output_header_file.write(subject_name_list)
130 public_key_list += _CreateArraySectionFooter()
131 output_header_file.write(public_key_list)
132 certificate_list += _CreateArraySectionFooter()
133 output_header_file.write(certificate_list)
134 certificate_size_list += _CreateArraySectionFooter()
135 output_header_file.write(certificate_size_list)
136 output_header_file.write(_CreateOutputFooter())
137 output_header_file.close()
Guo-wei Shiehee408212015-12-09 11:25:38 -0800138
139
140def _Cleanup(root_dir):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100141 for f in os.listdir(root_dir):
142 if f.startswith(_PREFIX):
143 os.remove(root_dir + f)
Guo-wei Shiehee408212015-12-09 11:25:38 -0800144
145
146def _CreateCertSection(root_dir, source_file, label, options):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100147 command = 'openssl x509 -in %s%s -noout -C' % (root_dir, source_file)
148 _PrintOutput(command, options)
149 output = subprocess.getstatusoutput(command)[1]
150 renamed_output = output.replace('unsigned char XXX_',
151 'const unsigned char ' + label + '_')
152 filtered_output = ''
153 cert_block = '^const unsigned char.*?};$'
154 prog = re.compile(cert_block, re.IGNORECASE | re.MULTILINE | re.DOTALL)
155 if not options.full_cert:
156 filtered_output = prog.sub('', renamed_output, count=2)
157 else:
158 filtered_output = renamed_output
Guo-wei Shiehee408212015-12-09 11:25:38 -0800159
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100160 cert_size_block = r'\d\d\d+'
161 prog2 = re.compile(cert_size_block, re.MULTILINE | re.VERBOSE)
162 result = prog2.findall(renamed_output)
163 cert_size = result[len(result) - 1]
Guo-wei Shiehee408212015-12-09 11:25:38 -0800164
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100165 return filtered_output, cert_size
Guo-wei Shiehee408212015-12-09 11:25:38 -0800166
167
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +0100168def _CreateOutputHeader(bundle_type):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100169 output = ('/*\n'
170 ' * Copyright 2004 The WebRTC Project Authors. All rights '
171 'reserved.\n'
172 ' *\n'
173 ' * Use of this source code is governed by a BSD-style license\n'
174 ' * that can be found in the LICENSE file in the root of the '
175 'source\n'
176 ' * tree. An additional intellectual property rights grant can be '
177 'found\n'
178 ' * in the file PATENTS. All contributing project authors may\n'
179 ' * be found in the AUTHORS file in the root of the source tree.\n'
180 ' */\n\n'
181 '#ifndef RTC_BASE_SSL_ROOTS_H_\n'
182 '#define RTC_BASE_SSL_ROOTS_H_\n\n'
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +0100183 '// This file is the root certificates in C form.\n\n'
184 '// It was generated with the following script:\n'
185 '// tools_webrtc/sslroots/generate_sslroots.py'
186 ' %s_CA_bundle.pem\n\n'
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100187 '// clang-format off\n'
188 '// Don\'t bother formatting generated code,\n'
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +0100189 '// also it would breaks subject/issuer lines.\n\n' % bundle_type)
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100190 return output
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100191
Guo-wei Shiehee408212015-12-09 11:25:38 -0800192
Taylor Brandstetterbee59832020-10-28 14:06:21 -0700193def _CreateOutputFooter():
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +0100194 return '// clang-format on\n\n#endif // RTC_BASE_SSL_ROOTS_H_\n'
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100195
Guo-wei Shiehee408212015-12-09 11:25:38 -0800196
197def _CreateArraySectionHeader(type_name, type_type, options):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100198 output = ('const %s kSSLCert%sList[] = {\n') % (type_type, type_name)
199 _PrintOutput(output, options)
200 return output
Guo-wei Shiehee408212015-12-09 11:25:38 -0800201
202
203def _AddLabelToArray(label, type_name):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100204 return ' %s_%s,\n' % (label, type_name)
Guo-wei Shiehee408212015-12-09 11:25:38 -0800205
206
207def _CreateArraySectionFooter():
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100208 return '};\n\n'
Guo-wei Shiehee408212015-12-09 11:25:38 -0800209
210
211def _SafeName(original_file_name):
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +0100212 bad_chars = ' -./\\()áéíőú\r\n'
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100213 replacement_chars = ''
214 for _ in bad_chars:
215 replacement_chars += '_'
Saúl Ibarra Corretgéfabc3a52022-03-04 14:19:48 +0100216 translation_table = str.maketrans(bad_chars, replacement_chars)
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100217 return original_file_name.translate(translation_table)
Guo-wei Shiehee408212015-12-09 11:25:38 -0800218
219
220def _PrintOutput(output, options):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100221 if options.verbose:
222 print(output)
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100223
Guo-wei Shiehee408212015-12-09 11:25:38 -0800224
225if __name__ == '__main__':
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100226 main()