#!/usr/bin/env python3 # Copyright 2015 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import base64 import copy import os import subprocess import tempfile class RDN: def __init__(self): self.attrs = [] def add_attr(self, attr_type, attr_value_type, attr_value, attr_modifier=None): self.attrs.append((attr_type, attr_value_type, attr_value, attr_modifier)) return self def __str__(self): s = '' for n, attr in enumerate(self.attrs): s += 'attrTypeAndValue%i=SEQUENCE:attrTypeAndValueSequence%i_%i\n' % ( n, id(self), n) s += '\n' for n, attr in enumerate(self.attrs): attr_type, attr_value_type, attr_value, attr_modifier = attr s += '[attrTypeAndValueSequence%i_%i]\n' % (id(self), n) # Note the quotes around the string value here, which is necessary for # trailing whitespace to be included by openssl. s += 'type=OID:%s\n' % attr_type s += 'value=' if attr_modifier: s += attr_modifier + ',' s += '%s:"%s"\n' % (attr_value_type, attr_value) return s class NameGenerator: def __init__(self): self.rdns = [] def token(self): return b"NAME" def add_rdn(self): rdn = RDN() self.rdns.append(rdn) return rdn def __str__(self): s = 'asn1 = SEQUENCE:rdnSequence%i\n\n[rdnSequence%i]\n' % ( id(self), id(self)) for n, rdn in enumerate(self.rdns): s += 'rdn%i = SET:rdnSet%i_%i\n' % (n, id(self), n) s += '\n' for n, rdn in enumerate(self.rdns): s += '[rdnSet%i_%i]\n%s\n' % (id(self), n, rdn) return s def generate(s, fn): out_fn = os.path.join('..', 'names', fn + '.pem') conf_tempfile = tempfile.NamedTemporaryFile(mode='wt', encoding='utf-8') conf_tempfile.write(str(s)) conf_tempfile.flush() der_tmpfile = tempfile.NamedTemporaryFile() subprocess.check_call([ 'openssl', 'asn1parse', '-genconf', conf_tempfile.name, '-i', '-out', der_tmpfile.name ], stdout=subprocess.DEVNULL) conf_tempfile.close() description_tmpfile = tempfile.NamedTemporaryFile() subprocess.check_call(['der2ascii', '-i', der_tmpfile.name], stdout=description_tmpfile) output_file = open(out_fn, 'wb') description_tmpfile.seek(0) output_file.write(description_tmpfile.read()) output_file.write(b'-----BEGIN NAME-----\n') output_file.write(base64.encodebytes(der_tmpfile.read())) output_file.write(b'-----END NAME-----\n') output_file.close() def unmangled(s): return s def extra_whitespace(s): return ' ' + s.replace(' ', ' ') + ' ' def case_swap(s): return s.swapcase() def main(): for valuetype in ('PRINTABLESTRING', 'T61STRING', 'UTF8', 'BMPSTRING', 'UNIVERSALSTRING'): for string_mangler in (unmangled, extra_whitespace, case_swap): n=NameGenerator() n.add_rdn().add_attr('countryName', 'PRINTABLESTRING', 'US') n.add_rdn().add_attr('stateOrProvinceName', valuetype, string_mangler('New York')) n.add_rdn().add_attr('localityName', valuetype, string_mangler("ABCDEFGHIJKLMNOPQRSTUVWXYZ " "abcdefghijklmnopqrstuvwxyz " "0123456789 '()+,-./:=?")) n_extra_attr = copy.deepcopy(n) n_extra_attr.rdns[-1].add_attr('organizationName', valuetype, string_mangler('Name of company')) n_dupe_attr = copy.deepcopy(n) n_dupe_attr.rdns[-1].add_attr(*n_dupe_attr.rdns[-1].attrs[-1]) n_extra_rdn = copy.deepcopy(n) n_extra_rdn.add_rdn().add_attr('organizationName', valuetype, string_mangler('Name of company')) filename_base = 'ascii-' + valuetype + '-' + string_mangler.__name__ generate(n, filename_base) generate(n_extra_attr, filename_base + '-extra_attr') generate(n_dupe_attr, filename_base + '-dupe_attr') generate(n_extra_rdn, filename_base + '-extra_rdn') for valuetype in ('UTF8', 'BMPSTRING', 'UNIVERSALSTRING'): n=NameGenerator() n.add_rdn().add_attr('countryName', 'PRINTABLESTRING', 'JP') n.add_rdn().add_attr('localityName', valuetype, "\u6771\u4eac", "FORMAT:UTF8") filename_base = 'unicode_bmp-' + valuetype + '-' + 'unmangled' generate(n, filename_base) for valuetype in ('UTF8', 'UNIVERSALSTRING'): n=NameGenerator() n.add_rdn().add_attr('countryName', 'PRINTABLESTRING', 'JP') n.add_rdn().add_attr('localityName', valuetype, "\U0001d400\U0001d419", "FORMAT:UTF8") filename_base = 'unicode_supplementary-' + valuetype + '-' + 'unmangled' generate(n, filename_base) generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] rdn0 = SET:rdnSet0 [rdnSet0] attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0 [attrTypeAndValueSequence0_0] type=OID:countryName value=PRINTABLESTRING:"US" extra=PRINTABLESTRING:"hello world" """, "invalid-AttributeTypeAndValue-extradata") generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] rdn0 = SET:rdnSet0 [rdnSet0] attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0 [attrTypeAndValueSequence0_0] type=OID:countryName """, "invalid-AttributeTypeAndValue-onlyOneElement") generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] rdn0 = SET:rdnSet0 [rdnSet0] attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0 [attrTypeAndValueSequence0_0] """, "invalid-AttributeTypeAndValue-empty") generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] rdn0 = SET:rdnSet0 [rdnSet0] attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0 [attrTypeAndValueSequence0_0] type=PRINTABLESTRING:"hello world" value=PRINTABLESTRING:"US" """, "invalid-AttributeTypeAndValue-badAttributeType") generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] rdn0 = SET:rdnSet0 [rdnSet0] attrTypeAndValue0=SET:attrTypeAndValueSequence0_0 [attrTypeAndValueSequence0_0] type=OID:countryName value=PRINTABLESTRING:"US" """, "invalid-AttributeTypeAndValue-setNotSequence") generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] rdn0 = SEQUENCE:rdnSet0 [rdnSet0] attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0 [attrTypeAndValueSequence0_0] type=OID:countryName value=PRINTABLESTRING:"US" """, "invalid-RDN-sequenceInsteadOfSet") generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] rdn0 = SET:rdnSet0 [rdnSet0] """, "invalid-RDN-empty") generate("""asn1 = SET:rdnSequence [rdnSequence] rdn0 = SET:rdnSet0 [rdnSet0] attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0 [attrTypeAndValueSequence0_0] type=OID:countryName value=PRINTABLESTRING:"US" """, "invalid-Name-setInsteadOfSequence") generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] """, "valid-Name-empty") # Certs with a RDN that is sorted differently due to length of the values, but # which should compare equal when normalized. generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] rdn0 = SET:rdnSet0 [rdnSet0] attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0 attrTypeAndValue1=SEQUENCE:attrTypeAndValueSequence0_1 [attrTypeAndValueSequence0_0] type=OID:stateOrProvinceName value=PRINTABLESTRING:" state" [attrTypeAndValueSequence0_1] type=OID:localityName value=PRINTABLESTRING:"locality" """, "ascii-PRINTABLESTRING-rdn_sorting_1") generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] rdn0 = SET:rdnSet0 [rdnSet0] attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0 attrTypeAndValue1=SEQUENCE:attrTypeAndValueSequence0_1 [attrTypeAndValueSequence0_0] type=OID:stateOrProvinceName value=PRINTABLESTRING:"state" [attrTypeAndValueSequence0_1] type=OID:localityName value=PRINTABLESTRING:" locality" """, "ascii-PRINTABLESTRING-rdn_sorting_2") # Certs with a RDN that is sorted differently due to length of the values, and # also contains multiple values with the same type. generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] rdn0 = SET:rdnSet0 [rdnSet0] attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0 attrTypeAndValue1=SEQUENCE:attrTypeAndValueSequence0_1 attrTypeAndValue2=SEQUENCE:attrTypeAndValueSequence0_2 attrTypeAndValue3=SEQUENCE:attrTypeAndValueSequence0_3 attrTypeAndValue4=SEQUENCE:attrTypeAndValueSequence0_4 [attrTypeAndValueSequence0_0] type=OID:domainComponent value=IA5STRING:" cOm" [attrTypeAndValueSequence0_1] type=OID:domainComponent value=IA5STRING:"eXaMple" [attrTypeAndValueSequence0_2] type=OID:domainComponent value=IA5STRING:"wWw" [attrTypeAndValueSequence0_3] type=OID:localityName value=PRINTABLESTRING:"NEw" [attrTypeAndValueSequence0_4] type=OID:localityName value=PRINTABLESTRING:" yORk " """, "ascii-mixed-rdn_dupetype_sorting_1") generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] rdn0 = SET:rdnSet0 [rdnSet0] attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0 attrTypeAndValue1=SEQUENCE:attrTypeAndValueSequence0_1 attrTypeAndValue2=SEQUENCE:attrTypeAndValueSequence0_2 attrTypeAndValue3=SEQUENCE:attrTypeAndValueSequence0_3 attrTypeAndValue4=SEQUENCE:attrTypeAndValueSequence0_4 [attrTypeAndValueSequence0_0] type=OID:domainComponent value=IA5STRING:"cOM" [attrTypeAndValueSequence0_1] type=OID:domainComponent value=IA5STRING:"eXampLE" [attrTypeAndValueSequence0_2] type=OID:domainComponent value=IA5STRING:" Www " [attrTypeAndValueSequence0_3] type=OID:localityName value=PRINTABLESTRING:" nEw " [attrTypeAndValueSequence0_4] type=OID:localityName value=PRINTABLESTRING:"yoRK" """, "ascii-mixed-rdn_dupetype_sorting_2") # Minimal valid config. Copy and modify this one when generating new invalid # configs. generate("""asn1 = SEQUENCE:rdnSequence [rdnSequence] rdn0 = SET:rdnSet0 [rdnSet0] attrTypeAndValue0=SEQUENCE:attrTypeAndValueSequence0_0 [attrTypeAndValueSequence0_0] type=OID:countryName value=PRINTABLESTRING:"US" """, "valid-minimal") # Single Name that exercises all of the string types, unicode (basic and # supplemental planes), whitespace collapsing, case folding, as well as SET # sorting. n = NameGenerator() rdn1 = n.add_rdn() rdn1.add_attr('countryName', 'PRINTABLESTRING', 'AA') rdn1.add_attr('stateOrProvinceName', 'T61STRING', ' AbCd Ef ') rdn1.add_attr('localityName', 'UTF8', " Ab\u6771\u4eac ", "FORMAT:UTF8") rdn1.add_attr('organizationName', 'BMPSTRING', " aB \u6771\u4eac cD ", "FORMAT:UTF8") rdn1.add_attr('organizationalUnitName', 'UNIVERSALSTRING', " \U0001d400 A bC ", "FORMAT:UTF8") rdn1.add_attr('domainComponent', 'IA5STRING', 'eXaMpLe') rdn2 = n.add_rdn() rdn2.add_attr('localityName', 'UTF8', "AAA") rdn2.add_attr('localityName', 'BMPSTRING', "aaa") rdn3 = n.add_rdn() rdn3.add_attr('localityName', 'PRINTABLESTRING', "cCcC") generate(n, "unicode-mixed-unnormalized") # Expected normalized version of above. n = NameGenerator() rdn1 = n.add_rdn() rdn1.add_attr('countryName', 'UTF8', 'aa') rdn1.add_attr('stateOrProvinceName', 'T61STRING', ' AbCd Ef ') rdn1.add_attr('localityName', 'UTF8', "ab\u6771\u4eac", "FORMAT:UTF8") rdn1.add_attr('organizationName', 'UTF8', "ab \u6771\u4eac cd", "FORMAT:UTF8") rdn1.add_attr('organizationalUnitName', 'UTF8', "\U0001d400 a bc", "FORMAT:UTF8") rdn1.add_attr('domainComponent', 'UTF8', 'example') rdn2 = n.add_rdn() rdn2.add_attr('localityName', 'UTF8', "aaa") rdn2.add_attr('localityName', 'UTF8', "aaa") rdn3 = n.add_rdn() rdn3.add_attr('localityName', 'UTF8', "cccc") generate(n, "unicode-mixed-normalized") if __name__ == '__main__': main()