Re: How to get a list of SubjectAltNames of a cert in NSS
Paul Wouters <[email protected]>
| Newsgroups | gmane.comp.mozilla.crypto |
|---|---|
| Message-ID | <[email protected]> |
On Fri, 3 Mar 2017, Robert Relyea wrote: > Yes, NSS only looks at the first subjectAltName section. That section can and should hold all the alt names (That's certainly the case with SSL > certificates). Ok, so maybe we have generated certificates in a bad way :) See the attached python script that generates these :) > So there are 3 subjectAltName sections. > > Hmm Are you sure these aren't being colapsed into a single subjectAltName section with multiple subjectAltNames in it? Yes because I was testing the IP: and DNS: and email= SAN's and my code showed it only looped over the first one. > CERT_VerifyCertName() verifies against either the SAN or the CN (SSL processing). it supports multiple subjectAltNames, but they all are expected to > be in the same section. So it seems that this function would do everything I need. I'll see about changing my certificates to have one SAN section and then confirm if it can pick up the DNS name or email address from CN and SAN. Thanks, Paul -- dev-tech-crypto mailing list [email protected] https://lists.mozilla.org/listinfo/dev-tech-crypto
dist_certs.py
(text/plain, 18.9 KB)
#!/usr/bin/python """ dist_certs.py: create a suite of x509 certificates for the Libreswan test harness Copyright (C) 2014-2015 Matt Rogers <[email protected]> Copyright (C) 2015 Andrew Cagney <[email protected]> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. WARNING! Your PyOpenSSL needs a patch from here: https://github.com/pyca/pyopenssl/pull/161 NSS doesn't allow md5 CRL signatures. This patch lets you use the CRL export method and specify an acceptable signature type. """ import os import sys import ssl import shutil import subprocess import time from datetime import datetime, timedelta import pexpect from OpenSSL import crypto CRL_URI = 'URI:http://nic.testing.libreswan.org/revoked.crl' dates = {} ca_certs = {} end_certs = {} endrev_name = "" top_caname="" def reset_files(): for dir in ['keys/', 'cacerts/', 'certs/', 'pkcs12/', 'pkcs12/curveca', 'pkcs12/mainca', 'pkcs12/otherca', 'pkcs12/badca', 'crls/']: if os.path.isdir(dir): shutil.rmtree(dir) os.mkdir(dir) for file in ['nss-pw']: if os.path.isfile(file): os.remove(file) def writeout_cert(filename, item, type=crypto.FILETYPE_PEM): with open(filename, "w") as f: f.write(crypto.dump_certificate(type, item)) def writeout_privkey(filename, item, type=crypto.FILETYPE_PEM): with open(filename, "w") as f: f.write(crypto.dump_privatekey(type, item)) def create_keypair(algo=crypto.TYPE_RSA, bits=1024): """ Create an OpenSSL keypair """ pkey = crypto.PKey() pkey.generate_key(algo, bits) return pkey def create_csr(pkey, CN, C=None, ST=None, L=None, O=None, OU=None, emailAddress=None, algo='sha1'): """ Create the certreq """ req = crypto.X509Req() subject = req.get_subject() subject.CN = CN subject.C = C subject.ST = ST subject.L = L subject.O = O subject.OU = OU subject.CN = CN subject.emailAddress = emailAddress req.set_pubkey(pkey) req.sign(pkey, algo) return req def add_ext(cert, kind, crit, string): cert.add_extensions([crypto.X509Extension(kind, crit, string)]) def set_cert_extensions(cert, issuer, isCA=False, isRoot=False, ocsp=False, ocspuri=True): ku_str = 'digitalSignature' eku_str = '' ocspeku = 'serverAuth,clientAuth,codeSigning,OCSPSigning' cnstr = str(cert.get_subject().commonName) if isCA: ku_str = ku_str + ',keyCertSign,cRLSign' if "badca" in str(issuer.get_subject().commonName): bc = "CA:FALSE" else: bc = "CA:TRUE" else: bc = "CA:FALSE" add_ext(cert, 'basicConstraints', False, bc) if not isCA: dnsname = "DNS: " + cnstr if cnstr == "east.testing.libreswan.org": dnsname = "%s,%s"%(dnsname , "DNS:east.alias") add_ext(cert, 'subjectAltName', False, "IP: 192.1.2.23") add_ext(cert, 'subjectAltName', False, dnsname) add_ext(cert, 'subjectAltName', False, "email: user1@%s"%cnstr) if cnstr == "west.testing.libreswan.org": dnsname = "%s,%s"%(dnsname , "DNS:west.alias") add_ext(cert, 'subjectAltName', False, "IP: 192.1.2.45") add_ext(cert, 'subjectAltName', False, dnsname) add_ext(cert, 'subjectAltName', False, "email: user1@%s"%cnstr) if cnstr == 'usage-server.testing.libreswan.org': eku_str = 'serverAuth' ku_str = ku_str + ',keyEncipherment' elif cnstr == 'usage-client.testing.libreswan.org': eku_str = 'clientAuth' ku_str = ku_str + ',nonRepudiation' elif cnstr == 'usage-both.testing.libreswan.org': eku_str = 'serverAuth,clientAuth' ku_str = ku_str + ',keyEncipherment,nonRepudiation' if ocsp: ku_str = ku_str + ',keyCertSign,cRLSign' eku_str = ocspeku add_ext(cert, 'keyUsage', False, ku_str) if eku_str is not '': add_ext(cert, 'extendedKeyUsage', False, eku_str) if ocspuri: add_ext(cert, 'authorityInfoAccess', False, 'OCSP;URI:http://nic.testing.libreswan.org:2560') add_ext(cert, 'crlDistributionPoints', False, CRL_URI) def create_sub_cert(CN, CACert, CAkey, snum, START, END, C='CA', ST='Ontario', L='Toronto', O='Libreswan', OU='Test Department', emailAddress='[email protected]', ty=crypto.TYPE_RSA, keybits=1024, sign_alg='sha1', isCA=False, ocsp=False): """ Create a subordinate cert and return the cert, key tuple This could be a CA for an intermediate, or not for an EE """ certkey = create_keypair(ty, keybits) certreq = create_csr(certkey, CN, C, ST, L, O, OU, emailAddress, sign_alg) cert = crypto.X509() cert.set_serial_number(snum) cert.set_notBefore(START) cert.set_notAfter(END) cert.set_issuer(CACert.get_subject()) cert.set_subject(certreq.get_subject()) cert.set_pubkey(certreq.get_pubkey()) cert.set_version(2) if CN == 'nic-nourl.testing.libreswan.org': ocspuri = False else: ocspuri = True set_cert_extensions(cert, CACert, isCA=isCA, isRoot=False, ocsp=ocsp, ocspuri=ocspuri) cert.sign(CAkey, sign_alg) return cert, certkey def create_root_ca(CN, START, END, C='CA', ST='Ontario', L='Toronto', O='Libreswan', OU='Test Department', emailAddress='[email protected]', ty=crypto.TYPE_RSA, keybits=1024, sign_alg='sha1'): """ Create a root CA - Returns the cert, key tuple """ cakey = create_keypair(ty, keybits) careq = create_csr(cakey, CN, C, ST, L, O, OU, emailAddress, sign_alg) cacert = crypto.X509() cacert.set_serial_number(0) cacert.set_notBefore(START) cacert.set_notAfter(END) cacert.set_issuer(careq.get_subject()) cacert.set_subject(careq.get_subject()) cacert.set_pubkey(careq.get_pubkey()) cacert.set_version(2) set_cert_extensions(cacert, cacert, isCA=True, isRoot=True, ocsp=True, ocspuri=True) cacert.sign(cakey, sign_alg) return cacert, cakey def gmc(timestamp): return time.strftime("%Y%m%d%H%M%SZ", time.gmtime(timestamp)) def gen_gmtime_dates(): """ Generate the dates used for this run. Creating openssl gmtime dates may be simpler than this. """ gmtfmt = "%b %d %H:%M:%S %Y GMT" ok_stamp = ssl.cert_time_to_seconds( time.strftime(gmtfmt, time.gmtime())) - (60*60*24) two_days_ago_stamp = ok_stamp - (60*60*48) two_days_ago_end_stamp = two_days_ago_stamp + (60*60*24) # Make future certs only +300 days, so we have a time overlap # between currently valid certs (1 year) and these futuristic certs future_stamp = ok_stamp + (60*60*24*365*1) future_end_stamp = future_stamp + (60*60*24*365*2) return dict(OK_NOW=gmc(ok_stamp), OLD=gmc(two_days_ago_stamp), OLD_END=gmc(two_days_ago_end_stamp), FUTURE=gmc(future_stamp), FUTURE_END=gmc(future_end_stamp)) def store_cert_and_key(name, cert, key): """ Places a ca or end cert and key in the script's global store """ global ca_certs global end_certs ext = cert.get_extension(0) if ext.get_short_name() == 'basicConstraints': # compare the bytes for CA:True if name == "badca" or '0\x03\x01\x01\xff' == ext.get_data(): ca_certs[name] = cert, key else: end_certs[name] = cert, key def writeout_cert_and_key(certdir, name, cert, privkey): """ Write the cert and key files """ writeout_cert(certdir + name + ".crt", cert) writeout_privkey("keys/" + name + ".key", privkey) def create_basic_pluto_cas(ca_names): """ Create the core root certs """ print "creating CA certs" for name in ca_names: print " - creating %s" % name ca, key = create_root_ca(CN="Libreswan test CA for " + name, START=dates['OK_NOW'], END=dates['FUTURE_END']) writeout_cert_and_key("cacerts/", name, ca, key) store_cert_and_key(name, ca, key) def create_pkcs12(path, name, cert, key, ca_cert): """ Package and write out a .p12 file """ p12 = crypto.PKCS12() p12.set_certificate(cert) p12.set_privatekey(key) p12.set_friendlyname(name) p12.set_ca_certificates([ca_cert]) with open(path + name + ".p12", "wb") as f: f.write(p12.export(passphrase="foobar")) def create_mainca_end_certs(mainca_end_certs): """ Create the core set of end certs from mainca """ serial = 2 print "creating mainca's end certs" for name in mainca_end_certs: # put special cert handling here print " - creating %s" % name if name == 'bigkey': keysize = 2048 else: if name == 'key4096': keysize = 4096 else: keysize = 1024 if name == 'notyetvalid': startdate = dates['FUTURE'] enddate = dates['FUTURE_END'] elif name == 'notvalidanymore': startdate = dates['OLD'] enddate = dates['OLD_END'] else: startdate = dates['OK_NOW'] enddate = dates['FUTURE_END'] if name == 'signedbyother': signer = 'otherca' elif name[:3] == 'bad': signer = 'badca' else: signer = 'mainca' if name == 'nic': ocsp_resp = True else: ocsp_resp = False if name == 'wrongdnorg': org = "No Such Agency" else: org = "Libreswan" if name == 'unwisechar': common_name = 'unwisechar ~!@#$%^&*()-'\ '_=+;:/?<>.testing.libreswan.org' elif name == 'spaceincn': common_name = 'space invaders.testing.libreswan.org' elif name == 'cnofca': common_name = 'Libreswan test CA for mainca' else: common_name = name + '.testing.libreswan.org' if name == 'hashsha2': alg = 'sha256' else: alg = 'sha1' cert, key = create_sub_cert(common_name, ca_certs[signer][0], ca_certs[signer][1], serial, O=org, START=startdate, END=enddate, keybits=keysize, sign_alg=alg, ocsp=ocsp_resp) writeout_cert_and_key("certs/", name, cert, key) store_cert_and_key(name, cert, key) create_pkcs12("pkcs12/"+ signer + '/', name, cert, key, ca_certs[signer][0]) serial += 1 def create_chained_certs(chain_ca_roots, max_path, prefix=''): """ Create the EE->IA1->IA2->IAx-->CA chains. Last in the chain is the end cert TODO: Add more complex trust chain situations """ global endrev_name global top_caname min_path = 1 ca_cnt = 0 for chainca in chain_ca_roots: serial = len(end_certs) + ca_cnt lastca = "" #note there's an issue with the authkeyid in the chain #signpair = () print "creating %s chain" % chainca for level in range(min_path, max_path): cname = prefix + chainca + '_int_' + str(level) print "level %d cname %s serial %d" % (level, cname, serial) if level == min_path: lastca = "mainca" signpair = ca_certs[lastca] print " - creating %s with the last ca of %s" % (cname, lastca) ca, key = create_sub_cert(cname + '.testing.libreswan.org', signpair[0], signpair[1], serial, START=dates['OK_NOW'], END=dates['FUTURE'], isCA=True, ocsp=False) writeout_cert_and_key("certs/", cname, ca, key) store_cert_and_key(cname, ca, key) lastca = cname serial += 1 ca_cnt += 1 if level == max_path - 1: endcert_name = prefix + chainca + "_endcert" signpair = ca_certs[lastca] print " - creating %s" % endcert_name ecert, ekey = create_sub_cert(endcert_name + ".testing.libreswan.org", signpair[0], signpair[1], serial, START=dates['OK_NOW'], END=dates['FUTURE']) writeout_cert_and_key("certs/", endcert_name, ecert, ekey) store_cert_and_key(endcert_name, ecert, ekey) create_pkcs12("pkcs12/", endcert_name, ecert, ekey, signpair[0]) serial += 1 endrev_name = prefix + chainca + "_revoked" top_caname = cname print " - creating %s" % endrev_name ercert, erkey = create_sub_cert(endrev_name + ".testing.libreswan.org", signpair[0], signpair[1], serial, START=dates['OK_NOW'], END=dates['FUTURE']) writeout_cert_and_key("certs/", endrev_name, ercert, erkey) store_cert_and_key(endrev_name, ercert, erkey) create_pkcs12("pkcs12/", endrev_name, ercert, erkey, signpair[0]) # this special crl was for a openswan/nss freebl combo bug, both of which should # long be done with. def create_leading_zero_crl(): """ Create our special crl with a signature that starts out with '00:' This signs a CRL and checks for a '00' beginning. Each try increments the days parameter to result in a different signature """ zerosig = crypto.CRL() signcert, signkey = ca_certs['mainca'] days = 1 print "creating a CRL with a leading zero byte signature.." while True: good = False nl = '' crl = zerosig.export(signcert, signkey, type=crypto.FILETYPE_TEXT, days=days, digest='sha1') der = zerosig.export(signcert, signkey, type=crypto.FILETYPE_ASN1, days=days, digest='sha1') for index, line in enumerate(crl.splitlines()): if "Signature Algorithm" in line and index >= 5: nl = crl.splitlines()[index + 1].strip() if nl.startswith('00'): good = True break if good: print nl print "found after %d signatures!" % (days) with open("crls/crl-leading-zero-byte.crl", "wb") as f: f.write(der) break days += 1 def create_crlsets(): """ Create test CRLs """ print "creating crl set" revoked = crypto.Revoked() chainrev = crypto.Revoked() future_revoked = crypto.Revoked() revoked.set_rev_date(dates['OK_NOW']) chainrev.set_rev_date(dates['OK_NOW']) future_revoked.set_rev_date(dates['FUTURE']) # the get_serial_number method results in a hex str like '0x17' # but set_serial needs a hex str like '17' revoked.set_serial( hex(end_certs['revoked'][0].get_serial_number())[2:]) chainrev.set_serial( hex(end_certs['west_chain_revoked'][0].get_serial_number())[2:]) future_revoked.set_serial( hex(end_certs['revoked'][0].get_serial_number())[2:]) needupdate = crypto.CRL() needupdate.add_revoked(revoked) needupdate.add_revoked(chainrev) with open("crls/needupdate.crl", "wb") as f: f.write(needupdate.export(ca_certs['mainca'][0], ca_certs['mainca'][1], type=crypto.FILETYPE_ASN1, days=0, digest='sha1')) print "sleeping for needupdate/valid crl time difference" time.sleep(5) validcrl = crypto.CRL() validcrl.add_revoked(revoked) validcrl.add_revoked(chainrev) with open("crls/cacrlvalid.crl", "wb") as f: f.write(validcrl.export(ca_certs['mainca'][0], ca_certs['mainca'][1], type=crypto.FILETYPE_ASN1, days=15, digest='sha1')) othercrl = crypto.CRL() othercrl.add_revoked(revoked) othercrl.add_revoked(chainrev) with open("crls/othercacrl.crl", "wb") as f: f.write(othercrl.export(ca_certs['otherca'][0], ca_certs['otherca'][1], type=crypto.FILETYPE_ASN1, days=15, digest='sha1')) notyet = crypto.CRL() notyet.add_revoked(future_revoked) with open("crls/futurerevoke.crl", "wb") as f: f.write(notyet.export(ca_certs['mainca'][0], ca_certs['mainca'][1], type=crypto.FILETYPE_ASN1, days=15, digest='sha1')) #create_leading_zero_crl() def create_ec_certs(): """ The OpenSSL module doesn't appear to have support for curves so we do it with pexpect """ print "creating EC certs" #create CA pexpect.run('openssl ecparam -out keys/curveca.key ' '-name secp384r1 -genkey -noout') child = pexpect.spawn('openssl req -x509 ' '-new -key keys/curveca.key ' '-out cacerts/curveca.crt ' '-days 3650 -set_serial 1') child.expect('Country Name') child.sendline('CA') child.expect('State') child.sendline('Ontario') child.expect('Locality') child.sendline('Toronto') child.expect('Organization') child.sendline('Libreswan') child.expect('Organizational') child.sendline('Test Department') child.expect('Common') child.sendline('Libreswan test EC CA') child.expect('Email') child.sendline('[email protected]') child.expect(pexpect.EOF) serial = 2 for name in ['east', 'west', 'north', 'road']: print "- creating %s-ec" % name #create end certs pexpect.run('openssl ecparam -out keys/' + name + '-ec.key -name secp384r1 -genkey -noout') child = pexpect.spawn('openssl req -x509 ' '-new -key keys/curveca.key ' '-out certs/' + name + '-ec.crt -days 365 -set_serial ' + str(serial)) child.expect('Country Name') child.sendline('CA') child.expect('State') child.sendline('Ontario') child.expect('Locality') child.sendline('Toronto') child.expect('Organization') child.sendline('Libreswan') child.expect('Organizational') child.sendline('Test Department') child.expect('Common') child.sendline(name + '-ec.testing.libreswan.org') child.expect('Email') child.sendline('[email protected]') child.expect(pexpect.EOF) serial += 1 #package p12 pexpect.run('openssl pkcs12 -export ' '-inkey keys/%s-ec.key ' '-in certs/%s-ec.crt -name %s-ec ' '-certfile cacerts/curveca.crt ' '-caname "curveca" ' '-out pkcs12/curveca/%s-ec.p12 ' '-passin pass:foobar -passout pass:foobar' % (name, name, name, name)) def run_dist_certs(): """ Generate the pluto test harness x509 certificates, p12 files, keys, and CRLs """ # Add root CAs here basic_pluto_cas = ('mainca', 'otherca', 'badca') # Add end certs here mainca_end_certs = ('nic','east','west', 'road', 'sunset', 'sunrise','north','south', 'pole','park','beet','carrot', 'usage-server', 'usage-client', 'usage-both', 'nic-noext', 'nic-nourl', 'japan','bigkey', 'key4096', 'notyetvalid','notvalidanymore', 'signedbyother','wrongdnorg', 'unwisechar','spaceincn','hashsha2', 'cnofca','revoked', 'badwest', 'badeast') # Add chain roots here chain_ca_roots = ('east_chain', 'west_chain') # Put special case code for new certs in the following functions create_basic_pluto_cas(basic_pluto_cas) create_mainca_end_certs(mainca_end_certs) create_chained_certs(chain_ca_roots, 3) create_chained_certs(chain_ca_roots, 9, 'long_') create_chained_certs(chain_ca_roots, 10, 'too_long_') create_crlsets() create_ec_certs() def create_nss_pw(): print "creating nss-pw" f = open("nss-pw","w") f.write("foobar") f.close() def main(): outdir = os.path.dirname(sys.argv[0]) cwd = os.getcwd() os.chdir(outdir) global dates reset_files() dates = gen_gmtime_dates() print "format dates being used for this run:" # TODO: print the display GMT times for n, s in dates.iteritems(): print "%s : %s" % (n, s) run_dist_certs() create_nss_pw() os.chdir(cwd) print "finished!" if __name__ == "__main__": main()