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