diff options
author | Rob Austein <sra@hactrn.net> | 2008-04-19 22:52:16 +0000 |
---|---|---|
committer | Rob Austein <sra@hactrn.net> | 2008-04-19 22:52:16 +0000 |
commit | 14f2160b5f9a78c02b8b072930040210220c63cb (patch) | |
tree | b253a47e66ede9ba68c2bef296836a052044ded4 | |
parent | bd558a26aad5da955c4b59e446837ecab0618be2 (diff) |
Convert CMS code to something vaguely object-oriented, to simplify
handling of eContentType OIDs, etc. Unifiy some of the redundant XML
processing as method routines.
svn path=/pow/POW-0.7/POW.c; revision=1679
-rw-r--r-- | pow/POW-0.7/POW.c | 24 | ||||
-rwxr-xr-x | rpkid/irbe-cli.py | 56 | ||||
-rw-r--r-- | rpkid/irbe-setup.py | 28 | ||||
-rwxr-xr-x | rpkid/irdbd.py | 12 | ||||
-rwxr-xr-x | rpkid/rootd.py | 18 | ||||
-rw-r--r-- | rpkid/rpki/cms.py | 120 | ||||
-rw-r--r-- | rpkid/rpki/gctx.py | 21 | ||||
-rw-r--r-- | rpkid/rpki/left_right.py | 38 | ||||
-rw-r--r-- | rpkid/rpki/oids.py | 49 | ||||
-rw-r--r-- | rpkid/rpki/x509.py | 152 | ||||
-rwxr-xr-x | rpkid/rpkid.py | 2 | ||||
-rw-r--r-- | rpkid/testbed.py | 14 | ||||
-rw-r--r-- | rpkid/testpoke.py | 18 |
13 files changed, 253 insertions, 299 deletions
diff --git a/pow/POW-0.7/POW.c b/pow/POW-0.7/POW.c index cbe4f8a1..1b3af645 100644 --- a/pow/POW-0.7/POW.c +++ b/pow/POW-0.7/POW.c @@ -148,17 +148,17 @@ #define DER_FORMAT 2 // Object check functions -#define X_X509_Check(op) ((op)->ob_type == &x509type) -#define X_X509_store_Check(op) ((op)->ob_type == &x509_storetype) -#define X_X509_crl_Check(op) ((op)->ob_type == &x509_crltype) +#define X_X509_Check(op) ((op)->ob_type == &x509type) +#define X_X509_store_Check(op) ((op)->ob_type == &x509_storetype) +#define X_X509_crl_Check(op) ((op)->ob_type == &x509_crltype) #define X_X509_revoked_Check(op) ((op)->ob_type == &x509_revokedtype) -#define X_asymmetric_Check(op) ((op)->ob_type == &asymmetrictype) -#define X_symmetric_Check(op) ((op)->ob_type == &symmetrictype) -#define X_digest_Check(op) ((op)->ob_type == &digesttype) -#define X_hmac_Check(op) ((op)->ob_type == &hmactype) -#define X_ssl_Check(op) ((op)->ob_type == &ssltype) -#define X_pkcs7_Check(op) ((op)->ob_type == &pkcs7type) -#define X_cms_Check(op) ((op)->ob_type == &cmstype) +#define X_asymmetric_Check(op) ((op)->ob_type == &asymmetrictype) +#define X_symmetric_Check(op) ((op)->ob_type == &symmetrictype) +#define X_digest_Check(op) ((op)->ob_type == &digesttype) +#define X_hmac_Check(op) ((op)->ob_type == &hmactype) +#define X_ssl_Check(op) ((op)->ob_type == &ssltype) +#define X_pkcs7_Check(op) ((op)->ob_type == &pkcs7type) +#define X_cms_Check(op) ((op)->ob_type == &cmstype) static char pow_module__doc__ [] = "<moduleDescription>\n" @@ -290,9 +290,9 @@ typedef struct { Simple function to install a constant in the module name space. */ static void -install_int_const( PyObject *d, char *name, int value ) +install_int_const( PyObject *d, char *name, long value ) { - PyObject *v = PyInt_FromLong( (long)value ); + PyObject *v = PyInt_FromLong(value); if (!v || PyDict_SetItemString(d, name, v) ) PyErr_Clear(); diff --git a/rpkid/irbe-cli.py b/rpkid/irbe-cli.py index 786d6c98..77c8f870 100755 --- a/rpkid/irbe-cli.py +++ b/rpkid/irbe-cli.py @@ -21,7 +21,7 @@ The query back-channel is handled by a separate program. """ import getopt, sys, lxml.etree, lxml.sax -import rpki.left_right, rpki.relaxng, rpki.cms, rpki.https, rpki.x509, rpki.config, rpki.log +import rpki.left_right, rpki.relaxng, rpki.https, rpki.x509, rpki.config, rpki.log pem_out = None @@ -153,6 +153,14 @@ if not argv: cfg = rpki.config.parser(cfg_file, "irbe-cli") +cms_key = rpki.x509.RSA( Auto_file = cfg.get( "cms-key")) +cms_certs = rpki.x509.X509_chain(Auto_files = cfg.multiget("cms-cert")) +cms_ta = rpki.x509.X509( Auto_file = cfg.get( "cms-ta")) +https_key = rpki.x509.RSA( Auto_file = cfg.get( "https-key")) +https_certs = rpki.x509.X509_chain(Auto_files = cfg.multiget("https-cert")) +https_ta = rpki.x509.X509( Auto_file = cfg.get( "https-ta")) +https_url = cfg.get( "https-url") + q_msg = rpki.left_right.msg() while argv: @@ -163,41 +171,19 @@ while argv: argv = q_pdu.client_getopt(argv[1:]) q_msg.append(q_pdu) -# We don't use rpki.cms.xml_sign() and rpki.cms.xml_verify() because -# we want to display the raw XML. If and when that changes, we clean -# up the following slightly. - q_elt = q_msg.toXML() -q_xml = lxml.etree.tostring(q_elt, pretty_print=True, encoding="us-ascii", xml_declaration=True) -try: - rpki.relaxng.left_right.assertValid(q_elt) -except lxml.etree.DocumentInvalid: - print "Generated query document does not pass schema check:" - print - print q_xml - raise - -q_cms = rpki.cms.sign(q_xml, - rpki.x509.RSA(Auto_file = cfg.get("cms-key")), - rpki.x509.X509_chain(Auto_files = cfg.multiget("cms-cert"))) - -r_cms = rpki.https.client(client_key = rpki.x509.RSA(Auto_file = cfg.get("https-key")), - client_certs = rpki.x509.X509_chain(Auto_files = cfg.multiget("https-cert")), - server_ta = rpki.x509.X509(Auto_file = cfg.get("https-ta")), - url = cfg.get("https-url"), - msg = q_cms) - -r_xml = rpki.cms.verify(r_cms, rpki.x509.X509(Auto_file = cfg.get("cms-ta"))) - -r_elt = lxml.etree.fromstring(r_xml) -try: - rpki.relaxng.left_right.assertValid(r_elt) -except lxml.etree.DocumentInvalid: - print "Received reply document does not pass schema check:" - print r_xml - raise - -print r_xml +q_cms = rpki.x509.left_right_pdu.build(q_elt, cms_key, cms_certs) + +der = rpki.https.client(client_key = https_key, + client_certs = https_certs, + server_ta = https_ta, + url = https_url, + msg = q_cms.get_DER()) + +r_cms = rpki.x509.left_right(DER = der) +r_elt = r_cms.verify(r_cms, cms_ta) + +print r_cms.prettyprint_content() handler = sax_handler() lxml.sax.saxify(r_elt, handler) diff --git a/rpkid/irbe-setup.py b/rpkid/irbe-setup.py index d3170bfa..452aae76 100644 --- a/rpkid/irbe-setup.py +++ b/rpkid/irbe-setup.py @@ -20,7 +20,7 @@ engine for every registrant object in the IRDB. """ import os, MySQLdb, getopt, sys, lxml.etree, lxml.sax -import rpki.left_right, rpki.relaxng, rpki.cms, rpki.https +import rpki.left_right, rpki.relaxng, rpki.https import rpki.x509, rpki.config, rpki.log rpki.log.init("irbe-setup") @@ -48,24 +48,14 @@ def call_rpkid(pdu): pdu.type = "query" msg = rpki.left_right.msg((pdu,)) elt = msg.toXML() - try: - rpki.relaxng.left_right.assertValid(elt) - except lxml.etree.DocumentInvalid: - print lxml.etree.tostring(elt, pretty_print = True, encoding = "us-ascii") - raise - elt = rpki.cms.xml_verify(der = rpki.https.client(client_key = https_key, - client_certs = https_certs, - server_ta = https_ta, - url = https_url, - msg = rpki.cms.xml_sign(elt = elt, - key = cms_key, - certs = cms_certs)), - ta = cms_ta) - try: - rpki.relaxng.left_right.assertValid(elt) - except lxml.etree.DocumentInvalid: - print lxml.etree.tostring(elt, pretty_print = True, encoding = "us-ascii") - raise + cms = rpki.x509.let_right_pdu.build(elt, cms_key, cms_certs) + der = rpki.https.client(client_key = https_key, + client_certs = https_certs, + server_ta = https_ta, + url = https_url, + msg = cms) + cms = rpki.x509.left_right_pdu(DER = der) + elt = cms.verify(cms_ta) msg = rpki.left_right.sax_handler.saxify(elt) pdu = msg[0] assert len(msg) == 1 and pdu.type == "reply" and not isinstance(pdu, rpki.left_right.report_error_elt) diff --git a/rpkid/irdbd.py b/rpkid/irdbd.py index 09b1685e..6570eeb3 100755 --- a/rpkid/irdbd.py +++ b/rpkid/irdbd.py @@ -24,13 +24,13 @@ Default configuration file is irdbd.conf, override with --config option. import sys, os, time, getopt, urlparse, traceback import tlslite.api, MySQLdb, lxml.etree -import rpki.https, rpki.config, rpki.resource_set, rpki.cms, rpki.relaxng -import rpki.exceptions, rpki.left_right, rpki.log +import rpki.https, rpki.config, rpki.resource_set, rpki.relaxng +import rpki.exceptions, rpki.left_right, rpki.log, rpki.x509 def handler(query, path): try: - q_elt = rpki.cms.xml_verify(query, cms_ta) - rpki.relaxng.left_right.assertValid(q_elt) + q_cms = rpki.x509.left_right_pdu(DER = query) + q_elt = q_cms.verify(cms_ta) q_msg = rpki.left_right.sax_handler.saxify(q_elt) if not isinstance(q_msg, rpki.left_right.msg): raise rpki.exceptions.BadQuery, "Unexpected %s PDU" % repr(q_msg) @@ -71,8 +71,8 @@ def handler(query, path): r_msg.append(r_pdu) r_elt = r_msg.toXML() - rpki.relaxng.left_right.assertValid(r_elt) - return 200, rpki.cms.xml_sign(r_elt, cms_key, cms_certs) + r_cms = rpki.x509.left_right_pdu.build(r_elt, cms_key, cms_certs) + return 200, r_cms.get_DER() except Exception, data: rpki.log.error(traceback.format_exc()) diff --git a/rpkid/rootd.py b/rpkid/rootd.py index ba767917..21b1b371 100755 --- a/rpkid/rootd.py +++ b/rpkid/rootd.py @@ -26,7 +26,7 @@ Default configuration file is rootd.conf, override with --config option. import traceback, os, time, getopt, sys, lxml import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509 -import rpki.https, rpki.config, rpki.cms, rpki.exceptions, rpki.relaxng +import rpki.https, rpki.config, rpki.exceptions, rpki.relaxng import rpki.sundial, rpki.log rpki_subject_lifetime = rpki.sundial.timedelta(days = 30) @@ -130,8 +130,8 @@ class sax_handler(rpki.sax_utils.handler): def up_down_handler(query, path): try: - q_elt = rpki.cms.xml_verify(query, cms_ta) - rpki.relaxng.up_down.assertValid(q_elt) + q_cms = rpki.x509.up_down_pdu(DER = query) + q_elt = q_cms.verify(cms_ta) q_msg = sax_handler.saxify(q_elt) except Exception, data: rpki.log.error(traceback.format_exc()) @@ -139,19 +139,15 @@ def up_down_handler(query, path): try: r_msg = q_msg.serve_top_level(None) r_elt = r_msg.toXML() - try: - rpki.relaxng.up_down.assertValid(r_elt) - except lxml.etree.DocumentInvalid: - rpki.log.debug(lxml.etree.tostring(r_elt, pretty_print = True, encoding ="utf-8", xml_declaration = True)) - raise - return 200, rpki.cms.xml_sign(r_elt, cms_key, cms_certs, encoding = "utf-8") + r_cms = rpki.x509.up_down_pdu.build(r_elt, cms_key, cms_certs) + return 200, r_cms.get_DER() except Exception, data: rpki.log.error(traceback.format_exc()) try: r_msg = q_msg.serve_error(data) r_elt = r_msg.toXML() - rpki.relaxng.up_down.assertValid(r_elt) - return 200, rpki.cms.xml_sign(r_elt, cms_key, cms_certs, encoding = "utf-8") + r_cms = rpki.x509.up_down_pdu.build(r_elt, cms_key, cms_certs) + return 200, r_cms.get_DER() except Exception, data: rpki.log.error(traceback.format_exc()) return 500, "Could not process PDU: %s" % data diff --git a/rpkid/rpki/cms.py b/rpkid/rpki/cms.py deleted file mode 100644 index 35d08e8a..00000000 --- a/rpkid/rpki/cms.py +++ /dev/null @@ -1,120 +0,0 @@ -# $Id$ - -# Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -"""CMS routines. I should write a pretty DER_object wrapper around -the POW code and include it in x509.py, haven't gotten to that yet. -""" - -import os, rpki.x509, rpki.exceptions, lxml.etree, rpki.log, POW - -debug = 1 - -id_data = (1, 2, 840, 113549, 1, 7, 1) - -# openssl smime -sign -nodetach -outform DER -signer biz-certs/Alice-EE.cer -# -certfile biz-certs/Alice-CA.cer -inkey biz-certs/Alice-EE.key -# -in THING -out THING.der - -def sign(plaintext, keypair, certs, oid = id_data, no_certs = False): - """Sign plaintext as CMS with specified key and bag of certificates.""" - - cms = POW.CMS() - cms.sign(certs[0].get_POW(), - keypair.get_POW(), - [x.get_POW() for x in certs[1:]], - plaintext, - ".".join(str(i) for i in oid), - no_certs) - der = cms.derWrite() - - if debug >= 2: - print - print "Signed CMS:" - dumpasn1(der) - - return der - -# openssl smime -verify -inform DER -in THING.der -CAfile biz-certs/Alice-Root.cer - -def verify(der, ta): - """Verify the signature of a chunk of CMS. - - Returns the plaintext on success, otherwise raise an exception. - """ - - if debug >= 2: - print - print "Verifying CMS:" - dumpasn1(der) - - cms = POW.derRead(POW.CMS_MESSAGE, der) - - store = POW.X509Store() - - if isinstance(ta, (tuple, list)): - for x in ta: - store.addTrust(x.get_POW()) - else: - store.addTrust(ta.get_POW()) - - try: - return cms.verify(store) - - except: - if debug >= 1: - print "CMS verification failed, dumping inputs:" - print - if isinstance(ta, (tuple, list)): - for x in ta: - print "TA:" - dumpasn1(x.get_DER()) - else: - print "TA:" - dumpasn1(ta.get_DER()) - print - print "CMS:" - dumpasn1(der) - raise rpki.exceptions.CMSVerificationFailed, "CMS verification failed" - -def xml_verify(der, ta): - """Composite routine to verify CMS-wrapped XML.""" - - val = lxml.etree.fromstring(verify(der, ta)) - return val - -def xml_sign(elt, key, certs, encoding = "us-ascii"): - """Composite routine to sign CMS-wrapped XML.""" - - val = sign(lxml.etree.tostring(elt, pretty_print = True, encoding = encoding, xml_declaration = True), - key, certs) - return val - -def dumpasn1(thing): - """Prettyprint an ASN.1 DER object using cryptlib dumpasn1 tool. - Use a temporary file rather than popen4() because dumpasn1 uses - seek() when decoding ASN.1 content nested in OCTET STRING values. - """ - - fn = "dumpasn1.tmp" - try: - f = open(fn, "w") - f.write(thing) - f.close() - f = os.popen("dumpasn1 2>&1 -a " + fn) - print "\n".join(x for x in f.read().splitlines() if x.startswith(" ")) - f.close() - finally: - os.unlink(fn) diff --git a/rpkid/rpki/gctx.py b/rpkid/rpki/gctx.py index d0d3d2c4..d6a572a4 100644 --- a/rpkid/rpki/gctx.py +++ b/rpkid/rpki/gctx.py @@ -21,7 +21,7 @@ the identifier gctx is scattered all through the code at the moment. import traceback, os, time, getopt, sys, MySQLdb, lxml.etree import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509, rpki.sql -import rpki.https, rpki.config, rpki.cms, rpki.exceptions, rpki.relaxng, rpki.log +import rpki.https, rpki.config, rpki.exceptions, rpki.relaxng, rpki.log # This should be wrapped somewhere in rpki.x509 eventually import POW @@ -75,16 +75,15 @@ class global_context(object): q_msg[0].self_id = self_id q_msg[0].child_id = child_id q_elt = q_msg.toXML() - rpki.relaxng.left_right.assertValid(q_elt) - q_cms = rpki.cms.xml_sign(q_elt, self.cms_key, self.cms_certs) - r_cms = rpki.https.client( + q_cms = rpki.x509.left_right_pdu.build(q_elt, self.cms_key, self.cms_certs) + der = rpki.https.client( client_key = self.https_key, client_certs = self.https_certs, server_ta = self.https_ta_irdb, url = self.irdb_url, - msg = q_cms) - r_elt = rpki.cms.xml_verify(r_cms, self.cms_ta_irdb) - rpki.relaxng.left_right.assertValid(r_elt) + msg = q_cms.get_DER()) + r_cms = rpki.x509.left_right_pdu(DER = der) + r_elt = r_cms.verify(self.cms_ta_irdb) r_msg = rpki.left_right.sax_handler.saxify(r_elt) if len(r_msg) == 0 or not isinstance(r_msg[0], rpki.left_right.list_resources_elt) or r_msg[0].type != "reply": raise rpki.exceptions.BadIRDBReply, "Unexpected response to IRDB query: %s" % lxml.etree.tostring(r_msg.toXML(), pretty_print = True, encoding = "us-ascii") @@ -116,13 +115,13 @@ class global_context(object): """Process one left-right PDU.""" rpki.log.trace() try: - q_elt = rpki.cms.xml_verify(query, self.cms_ta_irbe) - rpki.relaxng.left_right.assertValid(q_elt) + q_cms = rpki.x509.left_right_pdu(DER = query) + q_elt = q_cms.verify(self.cms_ta_irbe) q_msg = rpki.left_right.sax_handler.saxify(q_elt) r_msg = q_msg.serve_top_level(self) r_elt = r_msg.toXML() - rpki.relaxng.left_right.assertValid(r_elt) - reply = rpki.cms.xml_sign(r_elt, self.cms_key, self.cms_certs) + r_cms = rpki.x509.left_right_pdu.build(r_elt, self.cms_key, self.cms_certs) + reply = r_cms.get_DER() self.sql_sweep() return 200, reply except lxml.etree.DocumentInvalid: diff --git a/rpkid/rpki/left_right.py b/rpkid/rpki/left_right.py index 5a3ae255..e14ed7cb 100644 --- a/rpkid/rpki/left_right.py +++ b/rpkid/rpki/left_right.py @@ -610,21 +610,16 @@ class parent_elt(data_elt): sender = self.sender_name, recipient = self.recipient_name) q_elt = q_msg.toXML() - try: - rpki.relaxng.up_down.assertValid(q_elt) - except lxml.etree.DocumentInvalid: - rpki.log.error("Message does not pass schema check: " + lxml.etree.tostring(q_elt, pretty_print = True)) - raise - q_cms = rpki.cms.xml_sign(q_elt, bsc.private_key_id, bsc.signing_cert, encoding = "UTF-8") - - r_cms = rpki.https.client(server_ta = self.peer_biz_cert, - client_key = bsc.private_key_id, - client_certs = bsc.signing_cert, - msg = q_cms, - url = self.peer_contact_uri) - - r_elt = rpki.cms.xml_verify(r_cms, self.peer_biz_cert) - rpki.relaxng.up_down.assertValid(r_elt) + q_cms = rpki.x509.up_down_pdu.build(q_elt, bsc.private_key_id, bsc.signing_cert) + + der = rpki.https.client(server_ta = self.peer_biz_cert, + client_key = bsc.private_key_id, + client_certs = bsc.signing_cert, + msg = q_cms.get_DER(), + url = self.peer_contact_uri) + + r_cms = rpki.x509.up_down_pdu(DER = der) + r_elt = r_cms.verify(self.peer_biz_cert) r_msg = rpki.up_down.sax_handler.saxify(r_elt) r_msg.payload.check_response() return r_msg @@ -704,8 +699,8 @@ class child_elt(data_elt): bsc = self.bsc() if bsc is None: raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id - q_elt = rpki.cms.xml_verify(query, self.peer_biz_cert) - rpki.relaxng.up_down.assertValid(q_elt) + q_cms = rpki.x509.up_down_pdu(DER = query) + q_elt = q_cms.verify(self.peer_biz_cert) q_msg = rpki.up_down.sax_handler.saxify(q_elt) q_msg.payload.gctx = self.gctx if enforce_strict_up_down_xml_sender and q_msg.sender != str(self.child_id): @@ -721,13 +716,8 @@ class child_elt(data_elt): # May require refactoring, ignore the issue for now. # r_elt = r_msg.toXML() - try: - rpki.relaxng.up_down.assertValid(r_elt) - except: - rpki.log.debug(lxml.etree.tostring(r_elt, pretty_print = True, encoding = "UTF-8")) - rpki.log.error(traceback.format_exc()) - raise - return rpki.cms.xml_sign(r_elt, bsc.private_key_id, bsc.signing_cert, encoding = "UTF-8") + r_cms = rpki.x509.up_down_pdu.build(r_elt, bsc.private_key_id, bsc.signing_cert) + return r_cms.get_DER() class repository_elt(data_elt): """<repository/> element.""" diff --git a/rpkid/rpki/oids.py b/rpkid/rpki/oids.py index 4e08aef7..a2dbf239 100644 --- a/rpkid/rpki/oids.py +++ b/rpkid/rpki/oids.py @@ -20,27 +20,34 @@ # Mapping table of OIDs to conventional string names. oid2name = { - (1, 2, 840, 113549, 1, 1, 11) : "sha256WithRSAEncryption", - (1, 2, 840, 113549, 1, 1, 12) : "sha384WithRSAEncryption", - (1, 2, 840, 113549, 1, 1, 13) : "sha512WithRSAEncryption", - (1, 3, 6, 1, 5, 5, 7, 1, 1) : "authorityInfoAccess", - (1, 3, 6, 1, 5, 5, 7, 1, 11) : "subjectInfoAccess", - (1, 3, 6, 1, 5, 5, 7, 1, 7) : "sbgp-ipAddrBlock", - (1, 3, 6, 1, 5, 5, 7, 1, 8) : "sbgp-autonomousSysNum", - (1, 3, 6, 1, 5, 5, 7, 14, 2) : "id-cp-ipAddr-asNumber", - (1, 3, 6, 1, 5, 5, 7, 48, 2) : "id-ad-caIssuers", - (1, 3, 6, 1, 5, 5, 7, 48, 5) : "id-ad-caRepository", - (1, 3, 6, 1, 5, 5, 7, 48, 9) : "id-ad-signedObjectRepository", - (1, 3, 6, 1, 5, 5, 7, 48, 10) : "id-ad-rpkiManifest", - (1, 3, 6, 1, 5, 5, 7, 48, 11) : "id-ad-signedObject", - (2, 5, 29, 14) : "subjectKeyIdentifier", - (2, 5, 29, 15) : "keyUsage", - (2, 5, 29, 19) : "basicConstraints", - (2, 5, 29, 20) : "cRLNumber", - (2, 5, 29, 31) : "cRLDistributionPoints", - (2, 5, 29, 32) : "certificatePolicies", - (2, 5, 29, 35) : "authorityKeyIdentifier", - (2, 5, 4, 3) : "commonName", + (1, 2, 840, 113549, 1, 1, 11) : "sha256WithRSAEncryption", + (1, 2, 840, 113549, 1, 1, 12) : "sha384WithRSAEncryption", + (1, 2, 840, 113549, 1, 1, 13) : "sha512WithRSAEncryption", + (1, 2, 840, 113549, 1, 7, 1) : "id-data", + (1, 2, 840, 113549, 1, 9, 16) : "id-smime", + (1, 2, 840, 113549, 1, 9, 16, 1) : "id-ct", + (1, 2, 840, 113549, 1, 9, 16, 1, 24) : "id-ct-routeOriginAttestation", + (1, 2, 840, 113549, 1, 9, 16, 1, 26) : "id-ct-rpkiManifest", + (1, 2, 840, 113549, 1, 9, 16, 1, 28) : "id-ct-xml", + (1, 3, 6, 1, 5, 5, 7, 1, 1) : "authorityInfoAccess", + (1, 3, 6, 1, 5, 5, 7, 1, 11) : "subjectInfoAccess", + (1, 3, 6, 1, 5, 5, 7, 1, 7) : "sbgp-ipAddrBlock", + (1, 3, 6, 1, 5, 5, 7, 1, 8) : "sbgp-autonomousSysNum", + (1, 3, 6, 1, 5, 5, 7, 14, 2) : "id-cp-ipAddr-asNumber", + (1, 3, 6, 1, 5, 5, 7, 48, 10) : "id-ad-rpkiManifest", + (1, 3, 6, 1, 5, 5, 7, 48, 11) : "id-ad-signedObject", + (1, 3, 6, 1, 5, 5, 7, 48, 2) : "id-ad-caIssuers", + (1, 3, 6, 1, 5, 5, 7, 48, 5) : "id-ad-caRepository", + (1, 3, 6, 1, 5, 5, 7, 48, 9) : "id-ad-signedObjectRepository", + (2, 16, 840, 1, 101, 3, 4, 2, 1) : "id-sha256", + (2, 5, 29, 14) : "subjectKeyIdentifier", + (2, 5, 29, 15) : "keyUsage", + (2, 5, 29, 19) : "basicConstraints", + (2, 5, 29, 20) : "cRLNumber", + (2, 5, 29, 31) : "cRLDistributionPoints", + (2, 5, 29, 32) : "certificatePolicies", + (2, 5, 29, 35) : "authorityKeyIdentifier", + (2, 5, 4, 3) : "commonName", } ## @var name2oid diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py index c9ed2864..fa46fb74 100644 --- a/rpkid/rpki/x509.py +++ b/rpkid/rpki/x509.py @@ -26,9 +26,9 @@ bring together the functionality I need in a way that hides at least some of the nasty details. This involves a lot of format conversion. """ -import POW, tlslite.api, POW.pkix, base64, time -import rpki.exceptions, rpki.resource_set, rpki.cms, rpki.oids, rpki.sundial -import rpki.manifest, rpki.roa +import POW, tlslite.api, POW.pkix, base64, lxml.etree, os +import rpki.exceptions, rpki.resource_set, rpki.oids, rpki.sundial +import rpki.manifest, rpki.roa, rpki.relaxng def calculate_SKI(public_key_der): """Calculate the SKI value given the DER representation of a public @@ -208,6 +208,23 @@ class DER_object(object): """Convert to SQL storage format.""" return self.get_DER() + def dumpasn1(self): + """Prettyprint an ASN.1 DER object using cryptlib dumpasn1 tool. + Use a temporary file rather than popen4() because dumpasn1 uses + seek() when decoding ASN.1 content nested in OCTET STRING values. + """ + + fn = "dumpasn1.tmp" + try: + f = open(fn, "wb") + f.write(self.get_DER()) + f.close() + f = os.popen("dumpasn1 2>&1 -a " + fn) + print "\n".join(x for x in f.read().splitlines() if x.startswith(" ")) + f.close() + finally: + os.unlink(fn) + class X509(DER_object): """X.509 certificates. @@ -579,6 +596,15 @@ class RSApublic(DER_object): """Calculate the SKI of this public key.""" return calculate_SKI(self.get_DER()) +def POWify(oid): + """Utility function to convert tuple form of an OID to + the dotted-decimal string form that POW uses. + """ + if isinstance(oid, str): + return POWify(rpki.oids.name2oid[oid]) + else: + return ".".join(str(i) for i in oid) + class CMS_object(DER_object): """Class to hold a CMS-wrapped object. @@ -593,7 +619,10 @@ class CMS_object(DER_object): formats = ("DER",) other_clear = ("content",) + econtent_oid = POWify("id-data") + dump_on_verify_failure = False + def get_DER(self): """Get the DER value of this CMS_object.""" assert not self.empty() @@ -611,20 +640,57 @@ class CMS_object(DER_object): self.clear() self.content = content - def verify_and_store(self, ta, obj): + def verify(self, ta): """Verify CMS wrapper and store inner content.""" - s = rpki.cms.verify(self.get_DER(), ta) - obj.fromString(s) - self.content = obj - def sign(self, keypair, certs): + cms = POW.derRead(POW.CMS_MESSAGE, self.get_DER()) + store = POW.X509Store() + if isinstance(ta, (tuple, list)): + for x in ta: + store.addTrust(x.get_POW()) + else: + store.addTrust(ta.get_POW()) + try: + content = cms.verify(store) + except: + if self.dump_on_verify_failure: + print "CMS verification failed, dumping ASN.1:" + self.dumpasn1() + raise rpki.exceptions.CMSVerificationFailed, "CMS verification failed" + self.decode(content) + return self.get_content() + + def sign(self, keypair, certs, no_certs = False): """Sign and wrap inner content.""" - self.DER = rpki.cms.sign(self.get_content().toString(), keypair, certs) -class SignedManifest(CMS_object): + cms = POW.CMS() + cms.sign(certs[0].get_POW(), + keypair.get_POW(), + [x.get_POW() for x in certs[1:]], + self.encode(), + self.econtent_oid, + no_certs) + self.DER = cms.derWrite() + +class DER_CMS_object(CMS_object): + """Class to hold CMS objects with DER-based content.""" + + def encode(self): + """Encode inner content for signing.""" + return self.get_content().toString() + + def decode(self, der): + """Decode DER and set inner content.""" + obj = self.content_class() + obj.fromString(der) + self.content = obj + +class SignedManifest(DER_CMS_object): """Class to hold a signed manifest.""" pem_converter = PEM_converter("RPKI MANIFEST") + content_class = rpki.manifest.Manifest + econtent_oid = POWify("id-ct-rpkiManifest") def getThisUpdate(self): """Get thisUpdate value from this manifest.""" @@ -634,10 +700,6 @@ class SignedManifest(CMS_object): """Get nextUpdate value from this manifest.""" return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().nextUpdate.get()) - def verify(self, ta): - """Verify this manifest.""" - self.verify_and_store(ta, rpki.manifest.Manifest()) - @classmethod def build(cls, serial, thisUpdate, nextUpdate, names_and_objs, keypair, certs, version = 0): """Build a signed manifest.""" @@ -653,20 +715,18 @@ class SignedManifest(CMS_object): m.manifestNumber.set(serial) m.thisUpdate.set(thisUpdate.toGeneralizedTime()) m.nextUpdate.set(nextUpdate.toGeneralizedTime()) - m.fileHashAlg.set((2, 16, 840, 1, 101, 3, 4, 2, 1)) # id-sha256 + m.fileHashAlg.set(rpki.oids.name2oid["id-sha256"]) m.fileList.set(filelist) self.set_content(m) self.sign(keypair, certs) return self -class ROA(CMS_object): +class ROA(DER_CMS_object): """Class to hold a signed ROA.""" pem_converter = PEM_converter("ROUTE ORIGIN ATTESTATION") - - def verify(self, ta): - """Verify this ROA.""" - self.verify_and_store(ta, rpki.roa.RouteOriginAttestation()) + content_class = rpki.roa.RouteOriginAttestation + econtent_oid = POWify("id-ct-routeOriginAttestation") @classmethod def build(cls, as_number, exact_match, ipv4, ipv6, keypair, certs, version = 0): @@ -681,6 +741,58 @@ class ROA(CMS_object): self.sign(keypair, certs) return self +class XML_CMS_object(CMS_object): + """Class to hold CMS-wrapped XML protocol data.""" + + econtent_oid = POWify("id-ct-xml") + + def encode(self): + """Encode inner content for signing.""" + return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True) + + def decode(self, xml): + """Decode XML and set inner content.""" + self.content = lxml.etree.fromstring(xml) + + def prettyprint_content(self): + """Prettyprint XML content of this message.""" + return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True) + + def schema_check(self): + """Handle XML RelaxNG schema check.""" + try: + self.schema.assertValid(self.get_content()) + except lxml.etree.DocumentInvalid: + rpki.log.error("PDU failed schema check: " + self.prettyprint_content()) + raise + + @classmethod + def build(cls, elt, keypair, certs): + """Build a CMS-wrapped XML PDU.""" + self = cls() + self.set_content(elt) + self.schema_check() + self.sign(keypair, certs) + return self + + def verify(self, ta): + """Wrapper around CMS_object.verify(), adds RelaxNG schema check.""" + CMS_object.verify(self, ta) + self.schema_check() + return self.get_content() + +class left_right_pdu(XML_CMS_object): + """Class to hold a CMS-signed left-right PDU.""" + + encoding = "us-ascii" + schema = rpki.relaxng.left_right + +class up_down_pdu(XML_CMS_object): + """Class to hold a CMS-signed up-down PDU.""" + + encoding = "UTF-8" + schema = rpki.relaxng.up_down + class CRL(DER_object): """Class to hold a Certificate Revocation List.""" diff --git a/rpkid/rpkid.py b/rpkid/rpkid.py index 7ea22a46..c61826d7 100755 --- a/rpkid/rpkid.py +++ b/rpkid/rpkid.py @@ -24,7 +24,7 @@ Default configuration file is rpkid.conf, override with --config option. import traceback, os, time, getopt, sys, MySQLdb, lxml.etree import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509, rpki.sql -import rpki.https, rpki.config, rpki.cms, rpki.exceptions, rpki.relaxng, rpki.log +import rpki.https, rpki.config, rpki.exceptions, rpki.relaxng, rpki.log import rpki.gctx os.environ["TZ"] = "UTC" diff --git a/rpkid/testbed.py b/rpkid/testbed.py index 3534b24f..8c689f04 100644 --- a/rpkid/testbed.py +++ b/rpkid/testbed.py @@ -568,22 +568,18 @@ class allocation(object): pdu.type = "query" elt = rpki.left_right.msg((pdu,)).toXML() rpki.log.debug(lxml.etree.tostring(elt, pretty_print = True, encoding = "us-ascii")) - rpki.relaxng.left_right.assertValid(elt) - cms = rpki.cms.xml_sign( - elt = elt, - key = testbed_key, - certs = testbed_certs) + cms = rpki.x509.left_right_pdu.build(elt, testbed_key, testbed_certs) url = "https://localhost:%d/left-right" % self.rpki_port rpki.log.debug("Attempting to connect to %s" % url) - cms = rpki.https.client( + der = rpki.https.client( client_key = testbed_key, client_certs = testbed_certs, server_ta = self.rpkid_ta, url = url, - msg = cms) - elt = rpki.cms.xml_verify(der = cms, ta = self.rpkid_ta) + msg = cms.get_DER()) + cms = rpki.x509.left_right_pdu(DER = der) + elt = cms.verify(ta = self.rpkid_ta) rpki.log.debug(lxml.etree.tostring(elt, pretty_print = True, encoding = "us-ascii")) - rpki.relaxng.left_right.assertValid(elt) pdu = rpki.left_right.sax_handler.saxify(elt)[0] assert pdu.type == "reply" and not isinstance(pdu, rpki.left_right.report_error_elt) return pdu diff --git a/rpkid/testpoke.py b/rpkid/testpoke.py index b2908bd9..99ee53c0 100644 --- a/rpkid/testpoke.py +++ b/rpkid/testpoke.py @@ -29,7 +29,7 @@ Default configuration file is testpoke.yaml, override with --yaml option. import os, time, getopt, sys, lxml, yaml import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509 -import rpki.https, rpki.config, rpki.cms, rpki.exceptions +import rpki.https, rpki.config, rpki.exceptions import rpki.relaxng, rpki.oids, rpki.log os.environ["TZ"] = "UTC" @@ -90,18 +90,16 @@ def query_up_down(q_pdu): sender = yaml_data["sender-id"], recipient = yaml_data["recipient-id"]) q_elt = q_msg.toXML() - rpki.relaxng.up_down.assertValid(q_elt) - q_cms = rpki.cms.xml_sign(q_elt, cms_key, cms_certs, encoding = "UTF-8") - r_cms = rpki.https.client( + q_cms = rpki.x509.up_down_pdu.build(q_elt, cms_key, cms_certs) + der = rpki.https.client( server_ta = https_ta, client_key = https_key, client_certs = https_certs, - msg = q_cms, - url = yaml_data["posturl"]) - r_xml = rpki.cms.verify(r_cms, cms_ta) - r_elt = lxml.etree.fromstring(r_xml) - rpki.relaxng.up_down.assertValid(r_elt) - return r_xml + msg = q_cms.get_DER(), + url = yaml_data["posturl"]) + r_cms = rpki.x509.up_down_pdu(DER = der) + r_elt = r_cms.verify(cms_ta) + return r_cms.prettyprint_content() def do_list(): print query_up_down(rpki.up_down.list_pdu()) |