diff options
-rw-r--r-- | myrpki/Makefile | 7 | ||||
-rw-r--r-- | myrpki/README | 54 | ||||
-rw-r--r-- | myrpki/myrpki.conf | 54 | ||||
-rw-r--r-- | myrpki/myrpki.py | 183 | ||||
-rw-r--r-- | myrpki/relatives.conf | 19 |
5 files changed, 243 insertions, 74 deletions
diff --git a/myrpki/Makefile b/myrpki/Makefile index f86c00c3..004b35df 100644 --- a/myrpki/Makefile +++ b/myrpki/Makefile @@ -1,5 +1,6 @@ # $Id$ +all:: relatives all:: myrpki.xml all:: lint #all:: parse @@ -21,12 +22,12 @@ load: myrpki.xml myrpki.rng python myirbe.py clean: - rm -f *.xml *.pem + rm -rf *.xml *.pem bpki relatives: mom.pem dad.pem bro.pem sis.pem -mom.pem dad.pem bro.pem sis.pem: - openssl req -new -sha256 -x509 -verbose -config myrpki.conf -extensions req_x509_ext -newkey rsa:2048 -nodes -keyout /dev/null -subj CN=$@ -out $@ +mom.pem dad.pem bro.pem sis.pem: relatives.conf + CN=$@ openssl req -new -sha256 -x509 -verbose -config relatives.conf -extensions req_x509_ext -newkey rsa:2048 -nodes -keyout /dev/null -out $@ format: myrpki.xml xmllint --format myrpki.xml diff --git a/myrpki/README b/myrpki/README new file mode 100644 index 00000000..5161b376 --- /dev/null +++ b/myrpki/README @@ -0,0 +1,54 @@ +$Id$ + +testbed.py creates so freaking many BPKI certificates that even I can't +keep track of what they're all for anymore. So try starting over. + +Hosted (myrpki) entity needs: + +- self-signed bpki root (doesn't really need to be self-signed, nobody + else will care, but self-signed is simplest for our purposes). this + is what we've been calling the "self" cert in testbed.py. + +- BSC EE issued by self-signed root. + +- cross-certs of every foreign entity (parent, child, or pubd): these + are ca certs with pathLenConstraint 0. input for this cross-cert is + self-signed (or whatever) from foreign entity, output is + pathLenConstraint 0 ca cert issued by myrpki entity's own + self-signed root. + +Hosting rpkid needs: + +- self-signed bpki root + +- bsc ees for rpkid, irdbd, irbe_cli, etc + +- for each hosted entity (including self-hosting): + + - cross-cert of hosted entity's root, issued by rpkid root, ca cert + perhaps with pathLenConstraint 1 + + In theory that's all that's required, everything else is handled + through the hosted entity's cert chain. + +pubd needs: + +- self signed root (might share with rpkid but let's keep it separate + conceptually) + +- bsc ees for pubd and irbe_cli + +- for each client entity of pubd: + + - cross-cert of client entity's self cert (pathLenConstraint 0). + + This should allow pubd to verify clients' bsc ee certs without + getting into transitive ca relationships. + +rootd (when applicable at all) needs: + +- self signed root + +- bsc ee for talking up-down (server) with one and only child + +- cross-cert (pathLenConstraint 0) of one and only child's self cert. diff --git a/myrpki/myrpki.conf b/myrpki/myrpki.conf index 54d63e04..4f68784b 100644 --- a/myrpki/myrpki.conf +++ b/myrpki/myrpki.conf @@ -13,38 +13,72 @@ children_csv = children.csv parents_csv = parents.csv prefix_csv = prefixes.csv asn_csv = asns.csv -bpki_ca_certificate = bpki-ca-cert.pem -bpki_ca_key = bpki-ca-key.pem -bpki_ee_certificate = bpki-ee-cert.pem -bpki_ee_pkcs10 = bpki-ee-pkcs10.pem -bpki_crl = bpki-crl.pem -bpki_index = bpki-ca-index.idx + +bpki_ca_dir = bpki +bpki_serial = bpki/serial.txt +bpki_crl_number = bpki/crl_number.txt +bpki_ca_certificate = bpki/ca-cert.pem +bpki_ca_key = bpki/ca-key.pem +bpki_ee_certificate = bpki/bsc-ee-cert.pem +bpki_ee_pkcs10 = bpki/bsc-ee-pkcs10.pem +bpki_crl = bpki/crl.pem +bpki_index = bpki/index.txt output_filename = myrpki.xml relaxng_schema = myrpki.rng +[constants] +digest = sha256 +key_length = 2048 + [req] -default_bits = 2048 -default_md = sha256 +default_bits = ${constants::key_length} +default_md = ${constants::digest} distinguished_name = req_dn x509_extensions = req_x509_ext prompt = no [req_dn] -CN = wombat +CN = ${myrpki::handle} [req_x509_ext] basicConstraints = critical,CA:true subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always +[ca_ee_x509_ext] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always + +[ca_ca_x509_ext] +basicConstraints = critical,CA:true,pathlen:0 +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always + [ca] default_ca = ca_default [ca_default] database = ${myrpki::bpki_index} +new_certs_dir = ${myrpki::bpki_ca_dir} certificate = ${myrpki::bpki_ca_certificate} private_key = ${myrpki::bpki_ca_key} +default_days = 365 default_crl_days = 365 -default_md = sha256 +default_md = ${constants::digest} +policy = ca_dn_policy_allow_any_dn +unique_subject = no +serial = ${myrpki::bpki_serial} +crlnumber = ${myrpki::bpki_crl_number} + +[ca_dn_policy_allow_any_dn] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional +givenName = optional +surname = optional diff --git a/myrpki/myrpki.py b/myrpki/myrpki.py index afded205..7a360fa3 100644 --- a/myrpki/myrpki.py +++ b/myrpki/myrpki.py @@ -27,7 +27,7 @@ import subprocess, csv, re, os, getopt, sys, ConfigParser from xml.etree.ElementTree import Element, SubElement, ElementTree -namespace = "http://www.hactrn.net/uris/rpki/myrpki/" +namespace = "http://www.hactrn.net/uris/rpki/myrpki/" class comma_set(set): @@ -53,7 +53,7 @@ class roa_request(object): elif self.v6re.match(prefix): self.v6.add(prefix) else: - raise RuntimeError, 'Bad prefix syntax: "%s"' % prefix + raise RuntimeError, "Bad prefix syntax: %r" % (prefix,) def xml(self, e): return SubElement(e, "roa_request", @@ -103,7 +103,7 @@ class child(object): elif self.v6re.match(prefix): self.v6.add(prefix) else: - raise RuntimeError, 'Bad prefix syntax: "%s"' % prefix + raise RuntimeError, "Bad prefix syntax: %r" % (prefix,) if asn is not None: self.asns.add(asn) if validity is not None: @@ -134,11 +134,11 @@ class children(dict): c.xml(e) @classmethod - def from_csv(cls, children_csv_file, prefix_csv_file, asn_csv_file): + def from_csv(cls, children_csv_file, prefix_csv_file, asn_csv_file, cfg_file, bpki_dir): self = cls() # childname date pemfile for handle, date, pemfile in csv_open(children_csv_file): - self.add(handle = handle, validity = date, ta = pemfile) + self.add(handle = handle, validity = date, ta = xcert(pemfile, bpki_dir, cfg_file)) # childname p/n for handle, pn in csv_open(prefix_csv_file): self.add(handle = handle, prefix = pn) @@ -183,27 +183,64 @@ class parents(dict): c.xml(e) @classmethod - def from_csv(cls, parents_csv_file): + def from_csv(cls, parents_csv_file, cfg_file, bpki_dir): self = cls() # parentname uri pemfile for handle, uri, pemfile in csv_open(parents_csv_file): - self.add(handle = handle, uri = uri, ta = pemfile) + self.add(handle = handle, uri = uri, ta = xcert(pemfile, bpki_dir, cfg_file)) return self def csv_open(filename, delimiter = "\t", dialect = None): return csv.reader(open(filename, "rb"), dialect = dialect, delimiter = delimiter) +def xcert(pemfile, bpki_dir, cfg_file): + + if not pemfile: + return None + if not os.path.exists(pemfile): + raise RuntimeError, "PEM file %r does not exist" % (pemfile,) + + # Extract public key and subject name from PEM file and hash it so + # we can use the result as a tag for cross-certifying this cert. + + p1 = subprocess.Popen(("openssl", "x509", "-noout", "-pubkey", "-subject", "-in", pemfile), stdout = subprocess.PIPE) + p2 = subprocess.Popen(("openssl", "dgst", "-md5"), stdin = p1.stdout, stdout = subprocess.PIPE) + xcertfile = "%s/%s.xcert.pem" % (bpki_dir, p2.communicate()[0].strip()) + if p1.wait() != 0 or p2.wait() != 0: + raise RuntimeError, "Couldn't generate cross-certification tag for %r" % pemfile + + # Cross-certify the pemfile we were given, if we haven't already. + # This only works for self-signed certs, due to limitations of the + # OpenSSL command line tool. + + if not os.path.exists(xcertfile): + subprocess.check_call(("openssl", "ca", "-verbose", "-notext", "-batch", + "-config", cfg_file, + "-ss_cert", pemfile, + "-out", xcertfile, + "-extensions", "ca_ca_x509_ext")) + + # This should probably change to be the file content, coordinate with PEMElement() + return xcertfile + def PEMElement(e, tag, filename): e = SubElement(e, tag) e.text = "".join(p.strip() for p in open(filename).readlines()[1:-1]) -def bpki_ca(e, bpki_ca_key_file, bpki_ca_cert_file, bpki_crl_file, bpki_index_file, cfg_file): +def bpki_setup(bpki_ca_key_file, bpki_ca_cert_file, bpki_crl_file, bpki_index_file, cfg_file, + bpki_dir, bpki_serial_file, bpki_crl_number_file, bpki_ee_req_file, bpki_ee_cert_file): + + # Create our BPKI database directory + if not os.path.exists(bpki_dir): + os.makedirs(bpki_dir) + # Create our trust anchor key if not os.path.exists(bpki_ca_key_file): subprocess.check_call(("openssl", "genrsa", "-out", bpki_ca_key_file, "2048")) + # Create our self-signed trust anchor if not os.path.exists(bpki_ca_cert_file): subprocess.check_call(("openssl", "req", "-new", "-sha256", "-x509", "-verbose", "-config", cfg_file, @@ -211,32 +248,39 @@ def bpki_ca(e, bpki_ca_key_file, bpki_ca_cert_file, bpki_crl_file, bpki_index_fi "-key", bpki_ca_key_file, "-out", bpki_ca_cert_file)) - if not os.path.exists(bpki_crl_file): + # Create empty index file for "openssl ca" + if not os.path.exists(bpki_index_file): + f = open(bpki_index_file, "w") + f.close() + + # Create serial number file for "openssl ca" + if not os.path.exists(bpki_serial_file): + f = open(bpki_serial_file, "w") + f.write("01\n") + f.close() - if not os.path.exists(bpki_index_file): - open(bpki_index_file, "w").close() + # Create CRL number file for "openssl ca" + if not os.path.exists(bpki_crl_number_file): + f = open(bpki_crl_number_file, "w") + f.write("01\n") + f.close() - subprocess.check_call(("openssl", "ca", "-batch", "-verbose", "-gencrl", + # Create CRL + if not os.path.exists(bpki_crl_file): + subprocess.check_call(("openssl", "ca", "-batch", "-verbose", "-batch", "-notext", + "-gencrl", + "-config", cfg_file, "-out", bpki_crl_file, "-config", cfg_file)) - PEMElement(e, "bpki_ca_certificate", bpki_ca_cert_file) - PEMElement(e, "bpki_crl", bpki_crl_file) - -def bpki_ee(e, bpki_ee_req_file, bpki_ee_cert_file, bpki_ca_cert_file, bpki_ca_key_file): - - if os.path.exists(bpki_ee_req_file): - - if not os.path.exists(bpki_ee_cert_file): - subprocess.check_call(("openssl", "x509", "-req", "-sha256", "-days", "360", - "-CA", bpki_ca_cert_file, - "-CAkey", bpki_ca_key_file, - "-in", bpki_ee_req_file, - "-out", bpki_ee_cert_file, - "-CAcreateserial")) + # Create BSC EE cert + if os.path.exists(bpki_ee_req_file) and not os.path.exists(bpki_ee_cert_file): + subprocess.check_call(("openssl", "ca", "-verbose", "-batch", "-notext", + "-config", cfg_file, + "-extensions", "ca_ee_x509_ext", + "-in", bpki_ee_req_file, + "-out", bpki_ee_cert_file)) - PEMElement(e, "bpki_ee_certificate", bpki_ee_cert_file) - def extract_resources(): pass @@ -253,45 +297,62 @@ def main(): elif o in ("-c", "--config"): cfg_file = a if argv: - raise RuntimeError, "Unexpected arguments %s" % argv + raise RuntimeError, "Unexpected arguments %r" % (argv,) cfg = ConfigParser.RawConfigParser() cfg.read(cfg_file) - my_handle = cfg.get(myrpki_section, "handle") - roa_csv_file = cfg.get(myrpki_section, "roa_csv") - children_csv_file = cfg.get(myrpki_section, "children_csv") - parents_csv_file = cfg.get(myrpki_section, "parents_csv") - prefix_csv_file = cfg.get(myrpki_section, "prefix_csv") - asn_csv_file = cfg.get(myrpki_section, "asn_csv") - bpki_ca_cert_file = cfg.get(myrpki_section, "bpki_ca_certificate") - bpki_ca_key_file = cfg.get(myrpki_section, "bpki_ca_key") - bpki_ee_cert_file = cfg.get(myrpki_section, "bpki_ee_certificate") - bpki_ee_req_file = cfg.get(myrpki_section, "bpki_ee_pkcs10") - bpki_crl_file = cfg.get(myrpki_section, "bpki_crl") - bpki_index_file = cfg.get(myrpki_section, "bpki_index") - output_filename = cfg.get(myrpki_section, "output_filename") - relaxng_schema = cfg.get(myrpki_section, "relaxng_schema") - - roas = roa_requests.from_csv(roa_csv_file) - kids = children.from_csv(children_csv_file, prefix_csv_file, asn_csv_file) - rents = parents.from_csv(parents_csv_file) + my_handle = cfg.get(myrpki_section, "handle") + roa_csv_file = cfg.get(myrpki_section, "roa_csv") + children_csv_file = cfg.get(myrpki_section, "children_csv") + parents_csv_file = cfg.get(myrpki_section, "parents_csv") + prefix_csv_file = cfg.get(myrpki_section, "prefix_csv") + asn_csv_file = cfg.get(myrpki_section, "asn_csv") + bpki_dir = cfg.get(myrpki_section, "bpki_ca_dir") + bpki_ca_cert_file = cfg.get(myrpki_section, "bpki_ca_certificate") + bpki_ca_key_file = cfg.get(myrpki_section, "bpki_ca_key") + bpki_ee_cert_file = cfg.get(myrpki_section, "bpki_ee_certificate") + bpki_ee_req_file = cfg.get(myrpki_section, "bpki_ee_pkcs10") + bpki_crl_file = cfg.get(myrpki_section, "bpki_crl") + bpki_index_file = cfg.get(myrpki_section, "bpki_index") + bpki_serial_file = cfg.get(myrpki_section, "bpki_serial") + bpki_crl_number_file = cfg.get(myrpki_section, "bpki_crl_number") + output_filename = cfg.get(myrpki_section, "output_filename") + relaxng_schema = cfg.get(myrpki_section, "relaxng_schema") + + bpki_setup( + bpki_ca_cert_file = bpki_ca_cert_file, + bpki_ca_key_file = bpki_ca_key_file, + bpki_crl_file = bpki_crl_file, + bpki_dir = bpki_dir, + bpki_ee_cert_file = bpki_ee_cert_file, + bpki_ee_req_file = bpki_ee_req_file, + bpki_index_file = bpki_index_file, + bpki_serial_file = bpki_serial_file, + bpki_crl_number_file = bpki_crl_number_file, + cfg_file = cfg_file) e = Element("myrpki", xmlns = namespace, version = "1", handle = my_handle) - roas.xml(e) - kids.xml(e) - rents.xml(e) - bpki_ca(e, - bpki_ca_key_file = bpki_ca_key_file, - bpki_ca_cert_file = bpki_ca_cert_file, - bpki_crl_file = bpki_crl_file, - bpki_index_file = bpki_index_file, - cfg_file = cfg_file) - bpki_ee(e, - bpki_ee_req_file = bpki_ee_req_file, - bpki_ee_cert_file = bpki_ee_cert_file, - bpki_ca_cert_file = bpki_ca_cert_file, - bpki_ca_key_file = bpki_ca_key_file) + + roa_requests.from_csv(roa_csv_file).xml(e) + + children.from_csv( + children_csv_file = children_csv_file, + prefix_csv_file = prefix_csv_file, + asn_csv_file = asn_csv_file, + cfg_file = cfg_file, + bpki_dir = bpki_dir).xml(e) + + parents.from_csv( + parents_csv_file = parents_csv_file, + cfg_file = cfg_file, + bpki_dir = bpki_dir).xml(e) + + PEMElement(e, "bpki_ca_certificate", bpki_ca_cert_file) + PEMElement(e, "bpki_crl", bpki_crl_file) + + if os.path.exists(bpki_ee_cert_file): + PEMElement(e, "bpki_ee_certificate", bpki_ee_cert_file) ElementTree(e).write(output_filename + ".tmp") os.rename(output_filename + ".tmp", output_filename) diff --git a/myrpki/relatives.conf b/myrpki/relatives.conf new file mode 100644 index 00000000..8209a4ee --- /dev/null +++ b/myrpki/relatives.conf @@ -0,0 +1,19 @@ +# $Id$ +# +# Config file for self-signed test BPKI certificates. +# Not for production use. + +[req] +default_bits = 2048 +default_md = sha256 +distinguished_name = req_dn +x509_extensions = req_x509_ext +prompt = no + +[req_x509_ext] +basicConstraints = critical,CA:true +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always + +[req_dn] +CN = ${ENV::CN} |