1#!/usr/bin/env python3
2# Copyright 2015 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
16import base64
17import copy
18import os
19import subprocess
20import tempfile
21
22
23class RDN:
24  def __init__(self):
25    self.attrs = []
26
27  def add_attr(self, attr_type, attr_value_type, attr_value,
28               attr_modifier=None):
29    self.attrs.append((attr_type, attr_value_type, attr_value, attr_modifier))
30    return self
31
32  def __str__(self):
33    s = ''
34    for n, attr in enumerate(self.attrs):
35      s += 'attrTypeAndValue%i=SEQUENCE:attrTypeAndValueSequence%i_%i\n' % (
36          n, id(self), n)
37
38    s += '\n'
39    for n, attr in enumerate(self.attrs):
40      attr_type, attr_value_type, attr_value, attr_modifier = attr
41      s += '[attrTypeAndValueSequence%i_%i]\n' % (id(self), n)
42      # Note the quotes around the string value here, which is necessary for
43      # trailing whitespace to be included by openssl.
44      s += 'type=OID:%s\n' % attr_type
45      s += 'value='
46      if attr_modifier:
47        s += attr_modifier + ','
48      s += '%s:"%s"\n' % (attr_value_type, attr_value)
49
50    return s
51
52
53class NameGenerator:
54  def __init__(self):
55    self.rdns = []
56
57  def token(self):
58    return b"NAME"
59
60  def add_rdn(self):
61    rdn = RDN()
62    self.rdns.append(rdn)
63    return rdn
64
65  def __str__(self):
66    s = 'asn1 = SEQUENCE:rdnSequence%i\n\n[rdnSequence%i]\n' % (
67        id(self), id(self))
68    for n, rdn in enumerate(self.rdns):
69      s += 'rdn%i = SET:rdnSet%i_%i\n' % (n, id(self), n)
70
71    s += '\n'
72
73    for n, rdn in enumerate(self.rdns):
74      s += '[rdnSet%i_%i]\n%s\n' % (id(self), n, rdn)
75
76    return s
77
78
79def generate(s, fn):
80  out_fn = os.path.join('..', 'names', fn + '.pem')
81  conf_tempfile = tempfile.NamedTemporaryFile(mode='wt', encoding='utf-8')
82  conf_tempfile.write(str(s))
83  conf_tempfile.flush()
84  der_tmpfile = tempfile.NamedTemporaryFile()
85  subprocess.check_call([
86      'openssl', 'asn1parse', '-genconf', conf_tempfile.name, '-i', '-out',
87      der_tmpfile.name
88  ],
89                        stdout=subprocess.DEVNULL)
90  conf_tempfile.close()
91
92  description_tmpfile = tempfile.NamedTemporaryFile()
93  subprocess.check_call(['der2ascii', '-i', der_tmpfile.name],
94                        stdout=description_tmpfile)
95
96  output_file = open(out_fn, 'wb')
97  description_tmpfile.seek(0)
98  output_file.write(description_tmpfile.read())
99  output_file.write(b'-----BEGIN NAME-----\n')
100  output_file.write(base64.encodebytes(der_tmpfile.read()))
101  output_file.write(b'-----END NAME-----\n')
102  output_file.close()
103
104
105def unmangled(s):
106  return s
107
108
109def extra_whitespace(s):
110  return '  ' + s.replace(' ', '   ') + '  '
111
112
113def case_swap(s):
114  return s.swapcase()
115
116
117def main():
118  for valuetype in ('PRINTABLESTRING', 'T61STRING', 'UTF8', 'BMPSTRING',
119                    'UNIVERSALSTRING'):
120    for string_mangler in (unmangled, extra_whitespace, case_swap):
121      n=NameGenerator()
122      n.add_rdn().add_attr('countryName', 'PRINTABLESTRING', 'US')
123      n.add_rdn().add_attr('stateOrProvinceName',
124                           valuetype,
125                           string_mangler('New York'))
126      n.add_rdn().add_attr('localityName',
127                           valuetype,
128                           string_mangler("ABCDEFGHIJKLMNOPQRSTUVWXYZ "
129                                          "abcdefghijklmnopqrstuvwxyz "
130                                          "0123456789 '()+,-./:=?"))
131
132      n_extra_attr = copy.deepcopy(n)
133      n_extra_attr.rdns[-1].add_attr('organizationName',
134                                     valuetype,
135                                     string_mangler('Name of company'))
136
137      n_dupe_attr = copy.deepcopy(n)
138      n_dupe_attr.rdns[-1].add_attr(*n_dupe_attr.rdns[-1].attrs[-1])
139
140      n_extra_rdn = copy.deepcopy(n)
141      n_extra_rdn.add_rdn().add_attr('organizationName',
142                                     valuetype,
143                                     string_mangler('Name of company'))
144
145      filename_base = 'ascii-' + valuetype + '-' + string_mangler.__name__
146
147      generate(n, filename_base)
148      generate(n_extra_attr, filename_base + '-extra_attr')
149      generate(n_dupe_attr, filename_base + '-dupe_attr')
150      generate(n_extra_rdn, filename_base + '-extra_rdn')
151
152  for valuetype in ('UTF8', 'BMPSTRING', 'UNIVERSALSTRING'):
153    n=NameGenerator()
154    n.add_rdn().add_attr('countryName', 'PRINTABLESTRING', 'JP')
155    n.add_rdn().add_attr('localityName', valuetype, "\u6771\u4eac",
156                         "FORMAT:UTF8")
157
158    filename_base = 'unicode_bmp-' + valuetype + '-' + 'unmangled'
159    generate(n, filename_base)
160
161  for valuetype in ('UTF8', 'UNIVERSALSTRING'):
162    n=NameGenerator()
163    n.add_rdn().add_attr('countryName', 'PRINTABLESTRING', 'JP')
164    n.add_rdn().add_attr('localityName', valuetype, "\U0001d400\U0001d419",
165                         "FORMAT:UTF8")
166
167    filename_base = 'unicode_supplementary-' + valuetype + '-' + 'unmangled'
168    generate(n, filename_base)
169
170  generate("""asn1 = SEQUENCE:rdnSequence
171[rdnSequence]
172rdn0 = SET:rdnSet0
173[rdnSet0]
174attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0
175[attrTypeAndValueSequence0_0]
176type=OID:countryName
177value=PRINTABLESTRING:"US"
178extra=PRINTABLESTRING:"hello world"
179""", "invalid-AttributeTypeAndValue-extradata")
180
181  generate("""asn1 = SEQUENCE:rdnSequence
182[rdnSequence]
183rdn0 = SET:rdnSet0
184[rdnSet0]
185attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0
186[attrTypeAndValueSequence0_0]
187type=OID:countryName
188""", "invalid-AttributeTypeAndValue-onlyOneElement")
189
190  generate("""asn1 = SEQUENCE:rdnSequence
191[rdnSequence]
192rdn0 = SET:rdnSet0
193[rdnSet0]
194attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0
195[attrTypeAndValueSequence0_0]
196""", "invalid-AttributeTypeAndValue-empty")
197
198  generate("""asn1 = SEQUENCE:rdnSequence
199[rdnSequence]
200rdn0 = SET:rdnSet0
201[rdnSet0]
202attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0
203[attrTypeAndValueSequence0_0]
204type=PRINTABLESTRING:"hello world"
205value=PRINTABLESTRING:"US"
206""", "invalid-AttributeTypeAndValue-badAttributeType")
207
208  generate("""asn1 = SEQUENCE:rdnSequence
209[rdnSequence]
210rdn0 = SET:rdnSet0
211[rdnSet0]
212attrTypeAndValue0=SET:attrTypeAndValueSequence0_0
213[attrTypeAndValueSequence0_0]
214type=OID:countryName
215value=PRINTABLESTRING:"US"
216""", "invalid-AttributeTypeAndValue-setNotSequence")
217
218  generate("""asn1 = SEQUENCE:rdnSequence
219[rdnSequence]
220rdn0 = SEQUENCE:rdnSet0
221[rdnSet0]
222attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0
223[attrTypeAndValueSequence0_0]
224type=OID:countryName
225value=PRINTABLESTRING:"US"
226""", "invalid-RDN-sequenceInsteadOfSet")
227
228  generate("""asn1 = SEQUENCE:rdnSequence
229[rdnSequence]
230rdn0 = SET:rdnSet0
231[rdnSet0]
232""", "invalid-RDN-empty")
233
234  generate("""asn1 = SET:rdnSequence
235[rdnSequence]
236rdn0 = SET:rdnSet0
237[rdnSet0]
238attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0
239[attrTypeAndValueSequence0_0]
240type=OID:countryName
241value=PRINTABLESTRING:"US"
242""", "invalid-Name-setInsteadOfSequence")
243
244  generate("""asn1 = SEQUENCE:rdnSequence
245[rdnSequence]
246""", "valid-Name-empty")
247
248  # Certs with a RDN that is sorted differently due to length of the values, but
249  # which should compare equal when normalized.
250  generate("""asn1 = SEQUENCE:rdnSequence
251[rdnSequence]
252rdn0 = SET:rdnSet0
253[rdnSet0]
254attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0
255attrTypeAndValue1=SEQUENCE:attrTypeAndValueSequence0_1
256[attrTypeAndValueSequence0_0]
257type=OID:stateOrProvinceName
258value=PRINTABLESTRING:"    state"
259[attrTypeAndValueSequence0_1]
260type=OID:localityName
261value=PRINTABLESTRING:"locality"
262""", "ascii-PRINTABLESTRING-rdn_sorting_1")
263
264  generate("""asn1 = SEQUENCE:rdnSequence
265[rdnSequence]
266rdn0 = SET:rdnSet0
267[rdnSet0]
268attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0
269attrTypeAndValue1=SEQUENCE:attrTypeAndValueSequence0_1
270[attrTypeAndValueSequence0_0]
271type=OID:stateOrProvinceName
272value=PRINTABLESTRING:"state"
273[attrTypeAndValueSequence0_1]
274type=OID:localityName
275value=PRINTABLESTRING:" locality"
276""", "ascii-PRINTABLESTRING-rdn_sorting_2")
277
278  # Certs with a RDN that is sorted differently due to length of the values, and
279  # also contains multiple values with the same type.
280  generate("""asn1 = SEQUENCE:rdnSequence
281[rdnSequence]
282rdn0 = SET:rdnSet0
283[rdnSet0]
284attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0
285attrTypeAndValue1=SEQUENCE:attrTypeAndValueSequence0_1
286attrTypeAndValue2=SEQUENCE:attrTypeAndValueSequence0_2
287attrTypeAndValue3=SEQUENCE:attrTypeAndValueSequence0_3
288attrTypeAndValue4=SEQUENCE:attrTypeAndValueSequence0_4
289[attrTypeAndValueSequence0_0]
290type=OID:domainComponent
291value=IA5STRING:"     cOm"
292[attrTypeAndValueSequence0_1]
293type=OID:domainComponent
294value=IA5STRING:"eXaMple"
295[attrTypeAndValueSequence0_2]
296type=OID:domainComponent
297value=IA5STRING:"wWw"
298[attrTypeAndValueSequence0_3]
299type=OID:localityName
300value=PRINTABLESTRING:"NEw"
301[attrTypeAndValueSequence0_4]
302type=OID:localityName
303value=PRINTABLESTRING:"   yORk    "
304""", "ascii-mixed-rdn_dupetype_sorting_1")
305
306  generate("""asn1 = SEQUENCE:rdnSequence
307[rdnSequence]
308rdn0 = SET:rdnSet0
309[rdnSet0]
310attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0
311attrTypeAndValue1=SEQUENCE:attrTypeAndValueSequence0_1
312attrTypeAndValue2=SEQUENCE:attrTypeAndValueSequence0_2
313attrTypeAndValue3=SEQUENCE:attrTypeAndValueSequence0_3
314attrTypeAndValue4=SEQUENCE:attrTypeAndValueSequence0_4
315[attrTypeAndValueSequence0_0]
316type=OID:domainComponent
317value=IA5STRING:"cOM"
318[attrTypeAndValueSequence0_1]
319type=OID:domainComponent
320value=IA5STRING:"eXampLE"
321[attrTypeAndValueSequence0_2]
322type=OID:domainComponent
323value=IA5STRING:"    Www  "
324[attrTypeAndValueSequence0_3]
325type=OID:localityName
326value=PRINTABLESTRING:"   nEw            "
327[attrTypeAndValueSequence0_4]
328type=OID:localityName
329value=PRINTABLESTRING:"yoRK"
330""", "ascii-mixed-rdn_dupetype_sorting_2")
331
332  # Minimal valid config. Copy and modify this one when generating new invalid
333  # configs.
334  generate("""asn1 = SEQUENCE:rdnSequence
335[rdnSequence]
336rdn0 = SET:rdnSet0
337[rdnSet0]
338attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0
339[attrTypeAndValueSequence0_0]
340type=OID:countryName
341value=PRINTABLESTRING:"US"
342""", "valid-minimal")
343
344  # Single Name that exercises all of the string types, unicode (basic and
345  # supplemental planes), whitespace collapsing, case folding, as well as SET
346  # sorting.
347  n = NameGenerator()
348  rdn1 = n.add_rdn()
349  rdn1.add_attr('countryName', 'PRINTABLESTRING', 'AA')
350  rdn1.add_attr('stateOrProvinceName', 'T61STRING', '  AbCd  Ef  ')
351  rdn1.add_attr('localityName', 'UTF8', "  Ab\u6771\u4eac ", "FORMAT:UTF8")
352  rdn1.add_attr('organizationName', 'BMPSTRING', " aB  \u6771\u4eac  cD ",
353                "FORMAT:UTF8")
354  rdn1.add_attr('organizationalUnitName', 'UNIVERSALSTRING',
355                " \U0001d400  A  bC ", "FORMAT:UTF8")
356  rdn1.add_attr('domainComponent', 'IA5STRING', 'eXaMpLe')
357  rdn2 = n.add_rdn()
358  rdn2.add_attr('localityName', 'UTF8', "AAA")
359  rdn2.add_attr('localityName', 'BMPSTRING', "aaa")
360  rdn3 = n.add_rdn()
361  rdn3.add_attr('localityName', 'PRINTABLESTRING', "cCcC")
362  generate(n, "unicode-mixed-unnormalized")
363  # Expected normalized version of above.
364  n = NameGenerator()
365  rdn1 = n.add_rdn()
366  rdn1.add_attr('countryName', 'UTF8', 'aa')
367  rdn1.add_attr('stateOrProvinceName', 'T61STRING', '  AbCd  Ef  ')
368  rdn1.add_attr('localityName', 'UTF8', "ab\u6771\u4eac", "FORMAT:UTF8")
369  rdn1.add_attr('organizationName', 'UTF8', "ab \u6771\u4eac cd", "FORMAT:UTF8")
370  rdn1.add_attr('organizationalUnitName', 'UTF8', "\U0001d400 a bc",
371                "FORMAT:UTF8")
372  rdn1.add_attr('domainComponent', 'UTF8', 'example')
373  rdn2 = n.add_rdn()
374  rdn2.add_attr('localityName', 'UTF8', "aaa")
375  rdn2.add_attr('localityName', 'UTF8', "aaa")
376  rdn3 = n.add_rdn()
377  rdn3.add_attr('localityName', 'UTF8', "cccc")
378  generate(n, "unicode-mixed-normalized")
379
380
381if __name__ == '__main__':
382  main()
383