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