diff options
-rw-r--r-- | docs/rpki-db-schema.sql | 2 | ||||
-rw-r--r-- | rpkid/rpki/left_right.py | 28 | ||||
-rw-r--r-- | rpkid/rpki/sql.py | 10 | ||||
-rw-r--r-- | rpkid/rpki/x509.py | 73 | ||||
-rw-r--r-- | rpkid/testbed.py | 14 |
5 files changed, 81 insertions, 46 deletions
diff --git a/docs/rpki-db-schema.sql b/docs/rpki-db-schema.sql index 6a436e80..87cb552b 100644 --- a/docs/rpki-db-schema.sql +++ b/docs/rpki-db-schema.sql @@ -166,7 +166,7 @@ DROP TABLE IF EXISTS route_origin; CREATE TABLE route_origin ( route_origin_id SERIAL NOT NULL, as_number DECIMAL(24,0), - exact_match BOOLEAN NOT NULL, + exact_match BOOLEAN, cert LONGBLOB, roa LONGBLOB, self_id BIGINT unsigned NOT NULL, diff --git a/rpkid/rpki/left_right.py b/rpkid/rpki/left_right.py index 3803d5f4..17f92ddd 100644 --- a/rpkid/rpki/left_right.py +++ b/rpkid/rpki/left_right.py @@ -91,6 +91,8 @@ class data_elt(base_elt, rpki.sql.sql_persistant): """Generic left-right PDU constructor.""" self = cls() for k,v in kargs.items(): + if isinstance(v, bool): + v = 1 if v else 0 setattr(self, k, v) return self @@ -820,8 +822,9 @@ class route_origin_elt(data_elt): attributes = ("action", "type", "tag", "self_id", "route_origin_id", "as_number", "exact_match", "ipv4", "ipv6") booleans = ("suppress_publication",) - sql_template = rpki.sql.template("route_origin", "route_origin_id", "self_id", "as_number", "exact_match", - "ca_detail_id", "roa", + sql_template = rpki.sql.template("route_origin", "route_origin_id", "ca_detail_id", + "self_id", "as_number", "exact_match", + ("roa", rpki.x509.ROA), ("cert", rpki.x509.X509)) ca_detail_id = None @@ -856,11 +859,6 @@ class route_origin_elt(data_elt): """Fetch all ca_detail objects that link to this route_origin object.""" return rpki.sql.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id) - def serve_pre_save_hook(self, q_pdu, r_pdu): - """Extra server actions for route_origin_elt -- normalize exact_match.""" - if self.exact_match is None: - self.exact_match = False - def serve_post_save_hook(self, q_pdu, r_pdu): """Extra server actions for route_origin_elt.""" self.unimplemented_control("suppress_publication") @@ -905,6 +903,10 @@ class route_origin_elt(data_elt): /dev/random, but there is not much we can do about that. """ + if self.exact_match is None: + rpki.log.warn("Can't generate ROA with undefined exactMatch") + return + if self.ipv4 is None and self.ipv6 is None: rpki.log.warn("Can't generate ROA for empty address list") return @@ -934,19 +936,13 @@ class route_origin_elt(data_elt): resources = rpki.resource_set.resource_bag(v4 = self.ipv4, v6 = self.ipv6) - payload = rpki.roa.RouteOriginAttestation() - payload.version.set(0) - payload.asID.set(self.as_number) - payload.exactMatch.set(self.exact_match) - payload.ipAddrBlocks.set((a.to_roa_tuple() for a in (self.ipv4, self.ipv6) if a)) - keypair = rpki.x509.RSA() keypair.generate() sia = ((rpki.oids.name2oid["id-ad-signedObject"], ("uri", self.roa_uri(ca, keypair))),) self.cert = ca_detail.issue_ee(ca, resources, keypair.get_RSApublic(), sia = sia) - self.roa = rpki.cms.sign(payload.toString(), keypair, (self.cert,)) + self.roa = rpki.x509.ROA.build(self.as_number, self.exact_match, self.ipv4, self.ipv6, keypair, (self.cert,)) self.ca_detail_id = ca_detail.ca_detail_id self.sql_store() @@ -957,8 +953,6 @@ class route_origin_elt(data_elt): ca_detail.generate_manifest() - raise rpki.exceptions.NotImplementedYet - def roa_uri(self, ca, key = None): """Return the publication URI for this route_origin's ROA.""" return ca.sia_uri + (key or self.cert).gSKI() + ".roa" @@ -969,7 +963,7 @@ class route_origin_elt(data_elt): def ee_uri(self, ca): """Return the publication URI for this route_origin's ROA's EE certificate.""" - return ca.sia_uri + self.uri_tail() + return ca.sia_uri + self.ee_uri_tail() class list_resources_elt(base_elt): """<list_resources/> element.""" diff --git a/rpkid/rpki/sql.py b/rpkid/rpki/sql.py index dad37606..f9f58e4f 100644 --- a/rpkid/rpki/sql.py +++ b/rpkid/rpki/sql.py @@ -449,7 +449,8 @@ class ca_detail_obj(sql_persistant): for child_cert in predecessor.child_certs(): child_cert.reissue(self) for route_origin in predecessor.route_origins(): - raise rpki.exceptions.NotImplementedYet, "Don't (yet) know how to reissue ROAs" + if route_origin.roa: + raise rpki.exceptions.NotImplementedYet, "Don't (yet) know how to reissue ROAs" def delete(self, ca, repository): """Delete this ca_detail and all of the certs it issued.""" @@ -460,7 +461,8 @@ class ca_detail_obj(sql_persistant): for revoked__cert in self.revoked_certs(): revoked_cert.sql_delete() for route_origin in self.route_origins(): - raise rpki.exceptions.NotImplementedYet, "Don't (yet) know how to withdraw ROAs" + if route_origin.roa: + raise rpki.exceptions.NotImplementedYet, "Don't (yet) know how to withdraw ROAs" repository.withdraw(self.latest_manifest, self.manifest_uri(ca)) repository.withdraw(self.latest_crl, self.crl_uri()) self.sql_delete() @@ -669,15 +671,13 @@ class ca_detail_obj(sql_persistant): certs = [(c.uri_tail(), c.cert) for c in self.child_certs()] + \ [(r.ee_uri_tail(), r.cert) for r in self.route_origins() if r.cert is not None] - m = rpki.x509.SignedManifest() - m.build( + self.latest_manifest = rpki.x509.SignedManifest.build( serial = ca.next_manifest_number(), thisUpdate = now, nextUpdate = nextUpdate, names_and_objs = certs, keypair = self.manifest_private_key_id, certs = rpki.x509.X509_chain(self.latest_manifest_cert)) - self.latest_manifest = m repository.publish(self.latest_manifest, self.manifest_uri(ca)) diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py index 3636ece0..c9ed2864 100644 --- a/rpkid/rpki/x509.py +++ b/rpkid/rpki/x509.py @@ -27,7 +27,8 @@ 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.manifest, rpki.cms, rpki.oids, rpki.sundial +import rpki.exceptions, rpki.resource_set, rpki.cms, rpki.oids, rpki.sundial +import rpki.manifest, rpki.roa def calculate_SKI(public_key_der): """Calculate the SKI value given the DER representation of a public @@ -578,39 +579,53 @@ class RSApublic(DER_object): """Calculate the SKI of this public key.""" return calculate_SKI(self.get_DER()) -class SignedManifest(DER_object): - """Class to hold a signed manifest. +class CMS_object(DER_object): + """Class to hold a CMS-wrapped object. - Signed manifests are a little different from the other DER_object + CMS-wrapped objects are a little different from the other DER_object types because the signed object is CMS wrapping inner content that's also ASN.1, and due to our current minimal support for CMS we can't just handle this as a pretty composite object. So, for now anyway, - this SignedManifest object refers to the outer CMS wrapped manifest - so that the usual DER and PEM operations do the obvious things, and - the inner content is handle via separate methods using rpki.manifest. + a CMS_object is the outer CMS wrapped object so that the usual DER + and PEM operations do the obvious things, and the inner content is + handle via separate methods. """ formats = ("DER",) other_clear = ("content",) - pem_converter = PEM_converter("RPKI MANIFEST") def get_DER(self): - """Get the DER value of this manifest.""" + """Get the DER value of this CMS_object.""" assert not self.empty() if self.DER: return self.DER raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available" def get_content(self): - """Get the inner content of this manifest.""" + """Get the inner content of this CMS_object.""" assert self.content is not None return self.content def set_content(self, content): - """Set the (inner) content of this manifest, clearing the wrapper.""" + """Set the (inner) content of this CMS_object, clearing the wrapper.""" self.clear() self.content = content + def verify_and_store(self, ta, obj): + """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): + """Sign and wrap inner content.""" + self.DER = rpki.cms.sign(self.get_content().toString(), keypair, certs) + +class SignedManifest(CMS_object): + """Class to hold a signed manifest.""" + + pem_converter = PEM_converter("RPKI MANIFEST") + def getThisUpdate(self): """Get thisUpdate value from this manifest.""" return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().thisUpdate.get()) @@ -621,13 +636,12 @@ class SignedManifest(DER_object): def verify(self, ta): """Verify this manifest.""" - m = rpki.manifest.Manifest() - s = rpki.cms.verify(self.get_DER(), ta) - m.fromString(s) - self.content = m + self.verify_and_store(ta, rpki.manifest.Manifest()) - def build(self, serial, thisUpdate, nextUpdate, names_and_objs, keypair, certs, version = 0): - """Build the inner content of this manifest and sign it with CMS.""" + @classmethod + def build(cls, serial, thisUpdate, nextUpdate, names_and_objs, keypair, certs, version = 0): + """Build a signed manifest.""" + self = cls() filelist = [] for name, obj in names_and_objs: d = POW.Digest(POW.SHA256_DIGEST) @@ -642,7 +656,30 @@ class SignedManifest(DER_object): m.fileHashAlg.set((2, 16, 840, 1, 101, 3, 4, 2, 1)) # id-sha256 m.fileList.set(filelist) self.set_content(m) - self.DER = rpki.cms.sign(m.toString(), keypair, certs) + self.sign(keypair, certs) + return self + +class ROA(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()) + + @classmethod + def build(cls, as_number, exact_match, ipv4, ipv6, keypair, certs, version = 0): + """Build a ROA.""" + self = cls() + r = rpki.roa.RouteOriginAttestation() + r.version.set(version) + r.asID.set(as_number) + r.exactMatch.set(exact_match) + r.ipAddrBlocks.set((a.to_roa_tuple() for a in (ipv4, ipv6) if a)) + self.set_content(r) + self.sign(keypair, certs) + return self class CRL(DER_object): """Class to hold a Certificate Revocation List.""" diff --git a/rpkid/testbed.py b/rpkid/testbed.py index 5d1a8e7e..eebafe87 100644 --- a/rpkid/testbed.py +++ b/rpkid/testbed.py @@ -302,10 +302,11 @@ cmds = { "sleep" : cmd_sleep, class route_origin(object): """Representation for a route_origin object.""" - def __init__(self, asn, ipv4, ipv6): + def __init__(self, asn, ipv4, ipv6, exact_match): self.asn = asn self.v4 = rpki.resource_set.resource_set_ipv4("".join(ipv4.split())) if ipv4 else None self.v6 = rpki.resource_set.resource_set_ipv6("".join(ipv6.split())) if ipv6 else None + self.exact_match = exact_match def __eq__(self, other): return self.asn == other.asn and self.v4 == other.v4 and self.v6 == other.v6 @@ -323,7 +324,7 @@ class route_origin(object): @classmethod def parse(cls, yaml): - return cls(yaml.get("asn"), yaml.get("ipv4"), yaml.get("ipv6")) + return cls(yaml.get("asn"), yaml.get("ipv4"), yaml.get("ipv6"), yaml.get("exact_match", False)) class allocation_db(list): """Representation of all the entities and allocations in the test system. @@ -559,8 +560,8 @@ class allocation(object): rpki.log.info("Calling rpkid for %s" % self.name) pdu.type = "query" elt = rpki.left_right.msg((pdu,)).toXML() - rpki.relaxng.left_right.assertValid(elt) 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, @@ -574,8 +575,8 @@ class allocation(object): url = url, msg = cms) elt = rpki.cms.xml_verify(der = cms, ta = self.rpkid_ta) - rpki.relaxng.left_right.assertValid(elt) 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 @@ -641,7 +642,10 @@ class allocation(object): rpki.log.info("Creating rpkid route_origin objects for %s" % self.name) for ro in self.route_origins: ro.route_origin_id = self.call_rpkid(rpki.left_right.route_origin_elt.make_pdu( - action = "create", self_id = self.self_id, as_number = ro.asn, ipv4 = ro.v4, ipv6 = ro.v6)).route_origin_id + action = "create", self_id = self.self_id, as_number = ro.asn, + exact_match = ro.exact_match, ipv4 = ro.v4, ipv6 = ro.v6)).route_origin_id + +# exact_match = 1 if ro.exact_match else 0 def write_leaf_yaml(self): """Write YAML scripts for leaf nodes. Only supports list requests |