diff options
author | Rob Austein <sra@hactrn.net> | 2009-06-09 04:29:20 +0000 |
---|---|---|
committer | Rob Austein <sra@hactrn.net> | 2009-06-09 04:29:20 +0000 |
commit | d689e9ce44ab7d7a07c4195fe6fc28289f7c5654 (patch) | |
tree | 9e0886ef12c8c9711efc440428d42fd133d5f3a5 | |
parent | 51d3efcc2d122ac8d2049dbe3421d8ba80e633f6 (diff) |
Checkpoint
svn path=/rpkid/rpki/https.py; revision=2508
-rw-r--r-- | rpkid/rpki/https.py | 6 | ||||
-rw-r--r-- | rpkid/rpki/rpki_engine.py | 260 | ||||
-rw-r--r-- | rpkid/rpkid.sql | 34 |
3 files changed, 286 insertions, 14 deletions
diff --git a/rpkid/rpki/https.py b/rpkid/rpki/https.py index 2dabfe50..95fe2ad9 100644 --- a/rpkid/rpki/https.py +++ b/rpkid/rpki/https.py @@ -378,8 +378,7 @@ class http_server(http_stream): self.log_cert("server", cert) self.tls.useCertificate(cert.get_POW()) self.tls.useKey(key.get_POW()) - ta = set(dynamic_ta() if dynamic_ta else ta) - ta.discard(None) + ta = rpki.x509.X509.normalize_chain(dynamic_ta() if dynamic_ta else ta) if not ta: raise RuntimeError, "No trust anchor(s) specified, this is unlikely to work" for x in ta: @@ -508,8 +507,7 @@ class http_client(http_stream): self.expect_close = not want_persistent_client self.cert = cert self.key = key - self.ta = set(ta) - self.ta.discard(None) + self.ta = rpki.x509.X509.normalize_chain(ta) def start(self): try: diff --git a/rpkid/rpki/rpki_engine.py b/rpkid/rpki/rpki_engine.py index 7dbd78f5..45c1eff6 100644 --- a/rpkid/rpki/rpki_engine.py +++ b/rpkid/rpki/rpki_engine.py @@ -1045,3 +1045,263 @@ class revoked_cert_obj(rpki.sql.sql_persistent): revoked = rpki.sundial.now(), gctx = ca_detail.gctx, ca_detail_id = ca_detail.ca_detail_id) + +class roa_obj(rpki.sql.sql_persistent): + """ + Route Origin Authorization. + """ + + sql_template = rpki.sql.template( + "roa", + "roa_id", + "ca_detail_id", + "self_id", + "as_number", + ("roa", rpki.x509.ROA), + ("cert", rpki.x509.X509)) + + ca_detail_id = None + cert = None + roa = None + + def sql_fetch_hook(self): + """ + Extra SQL fetch actions for roa_obj -- handle prefix list. + """ + for version, datatype, attribute in ((4, rpki.resource_set.roa_prefix_set_ipv4, "ipv4"), + (6, rpki.resource_set.roa_prefix_set_ipv6, "ipv6")): + setattr(self, attribute, datatype.from_sql( + self.gctx.sql, + """ + SELECT address, prefixlen, max_prefixlen FROM roa_prefix + WHERE roa_id = %s AND version = %s + """, + (self.roa_id, version))) + + def sql_insert_hook(self): + """ + Extra SQL insert actions for roa_obj -- handle address + ranges. + """ + for version, prefix_set in ((4, self.ipv4), (6, self.ipv6)): + if prefix_set: + self.gctx.sql.executemany( + """ + INSERT roa_prefix (roa_id, address, prefixlen, max_prefixlen, version) + VALUES (%s, %s, %s, %s, %s) + """, + ((self.roa_id, x.address, x.prefixlen, x.max_prefixlen, version) + for x in prefix_set)) + + def sql_delete_hook(self): + """ + Extra SQL delete actions for roa_obj -- handle address + ranges. + """ + self.gctx.sql.execute("DELETE FROM roa_prefix WHERE roa_id = %s", (self.roa_id,)) + + def ca_detail(self): + """ + Fetch all ca_detail objects that link to this roa_obj. + """ + return rpki.rpki_engine.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id) + + def update_roa(self, callback): + """ + Bring this roa_obj's ROA up to date if necesssary. + """ + + def lose(e): + rpki.log.error(traceback.format_exc()) + rpki.log.warn("Could not update ROA %r, skipping: %s" % (self, e)) + callback() + return + + if self.roa is None: + self.generate_roa(callback, lose) + return + + ca_detail = self.ca_detail() + + if ca_detail is None or ca_detail.state != "active": + self.regenerate_roa(callback, lose) + return + + regen_margin = rpki.sundial.timedelta(seconds = self.self().regen_margin) + + if rpki.sundial.now() + regen_margin > self.cert.getNotAfter(): + self.regenerate_roa(callback, lose) + return + + ca_resources = ca_detail.latest_ca_cert.get_3779resources() + ee_resources = self.cert.get_3779resources() + + if ee_resources.oversized(ca_resources): + self.regenerate_roa(callback, lose) + return + + v4 = self.ipv4.to_resource_set() if self.ipv4 is not None else rpki.resource_set.resource_set_ipv4() + v6 = self.ipv6.to_resource_set() if self.ipv6 is not None else rpki.resource_set.resource_set_ipv6() + + if ee_resources.v4 != v4 or ee_resources.v6 != v6: + self.regenerate_roa(callback, lose) + return + + callback() + + def generate_roa(self, callback, errback): + """ + Generate a ROA. + + At present this does not support ROAs with multiple signatures + (neither does the current CMS code). + + At present we have no way of performing a direct lookup from a + desired set of resources to a covering certificate, so we have to + search. This could be quite slow if we have a lot of active + ca_detail objects. Punt on the issue for now, revisit if + profiling shows this as a hotspot. + + Once we have the right covering certificate, we generate the ROA + payload, generate a new EE certificate, use the EE certificate to + sign the ROA payload, publish the result, then throw away the + private key for the EE cert, all per the ROA specification. This + implies that generating a lot of ROAs will tend to thrash + /dev/random, but there is not much we can do about that. + """ + + if self.ipv4 is None and self.ipv6 is None: + rpki.log.warn("Can't generate ROA for empty prefix list") + return + + # Ugly and expensive search for covering ca_detail, there has to + # be a better way, but it would require the ability to test for + # resource subsets in SQL. + + v4 = self.ipv4.to_resource_set() if self.ipv4 is not None else rpki.resource_set.resource_set_ipv4() + v6 = self.ipv6.to_resource_set() if self.ipv6 is not None else rpki.resource_set.resource_set_ipv6() + + ca_detail = self.ca_detail() + if ca_detail is None or ca_detail.state != "active": + ca_detail = None + for parent in self.self().parents(): + for ca in parent.cas(): + ca_detail = ca.fetch_active() + if ca_detail is not None: + resources = ca_detail.latest_ca_cert.get_3779resources() + if v4.issubset(resources.v4) and v6.issubset(resources.v6): + break + ca_detail = None + if ca_detail is not None: + break + + if ca_detail is None: + rpki.log.warn("generate_roa() could not find a certificate covering %s %s" % (v4, v6)) + return + + ca = ca_detail.ca() + + resources = rpki.resource_set.resource_bag(v4 = v4, v6 = v6) + + keypair = rpki.x509.RSA.generate() + + self.ca_detail_id = ca_detail.ca_detail_id + + self.cert = ca_detail.issue_ee(ca, resources, keypair.get_RSApublic(), + sia = ((rpki.oids.name2oid["id-ad-signedObject"], + ("uri", self.roa_uri(keypair))),)) + + self.roa = rpki.x509.ROA.build(self.as_number, self.ipv4, self.ipv6, keypair, (self.cert,)) + + self.sql_store() + + repository = ca.parent().repository() + + def one(): + repository.publish(self.cert, self.ee_uri(), two, errback) + + def two(): + ca_detail.generate_manifest(callback, errback) + + repository.publish(self.roa, self.roa_uri(), + one if self.publish_ee_separately else two, + errback) + + def withdraw_roa(self, callback, errback, regenerate = False): + """ + Withdraw ROA associated with this roa_obj. + + In order to preserve make-before-break properties without + duplicating code, this method also handles generating a + replacement ROA when requested. + """ + + ca_detail = self.ca_detail() + ca = ca_detail.ca() + repository = ca.parent().repository() + cert = self.cert + roa = self.roa + roa_uri = self.roa_uri() + ee_uri = self.ee_uri() + + if ca_detail.state != 'active': + self.ca_detail_id = None + + def one(): + rpki.log.debug("Withdrawing ROA and revoking its EE cert") + rpki.rpki_engine.revoked_cert_obj.revoke(cert = cert, ca_detail = ca_detail) + repository.withdraw(roa, roa_uri, + two if self.publish_ee_separately else three, + errback) + + def two(): + repository.withdraw(cert, ee_uri, three, errback) + + def three(): + self.gctx.sql.sweep() + ca_detail.generate_crl(four, errback) + + def four(): + ca_detail.generate_manifest(callback, errback) + + if regenerate: + self.generate_roa(one, errback) + else: + one() + + def regenerate_roa(self, callback, errback): + """ + Reissue ROA associated with this roa_obj. + """ + if self.ca_detail() is None: + self.generate_roa(callback, errback) + else: + self.withdraw_roa(callback, errback, regenerate = True) + + def roa_uri(self, key = None): + """ + Return the publication URI for this roa_obj's ROA. + """ + return self.ca_detail().ca().sia_uri + self.roa_uri_tail(key) + + def roa_uri_tail(self, key = None): + """ + Return the tail (filename portion) of the publication URI for this + roa_obj's ROA. + """ + return (key or self.cert).gSKI() + ".roa" + + def ee_uri_tail(self): + """ + Return the tail (filename) portion of the URI for this roa_obj's + ROA's EE certificate. + """ + return self.cert.gSKI() + ".cer" + + def ee_uri(self): + """ + Return the publication URI for this roa_obj's ROA's EE + certificate. + """ + return self.ca_detail().ca().sia_uri + self.ee_uri_tail() + diff --git a/rpkid/rpkid.sql b/rpkid/rpkid.sql index 8cb2484c..96725b85 100644 --- a/rpkid/rpkid.sql +++ b/rpkid/rpkid.sql @@ -19,6 +19,8 @@ -- DROP TABLE commands must be in correct (reverse dependency) order -- to satisfy FOREIGN KEY constraints. +DROP TABLE IF EXISTS roa_prefix; +DROP TABLE IF EXISTS roa; DROP TABLE IF EXISTS route_origin_prefix; DROP TABLE IF EXISTS route_origin; DROP TABLE IF EXISTS revoked_cert; @@ -43,7 +45,6 @@ CREATE TABLE self ( UNIQUE (self_handle) ) ENGINE=InnoDB; - CREATE TABLE bsc ( bsc_id SERIAL NOT NULL, bsc_handle VARCHAR(255) NOT NULL, @@ -58,7 +59,6 @@ CREATE TABLE bsc ( UNIQUE (self_id, bsc_handle) ) ENGINE=InnoDB; - CREATE TABLE repository ( repository_id SERIAL NOT NULL, repository_handle VARCHAR(255) NOT NULL, @@ -75,7 +75,6 @@ CREATE TABLE repository ( UNIQUE (self_id, repository_handle) ) ENGINE=InnoDB; - CREATE TABLE parent ( parent_id SERIAL NOT NULL, parent_handle VARCHAR(255) NOT NULL, @@ -97,7 +96,6 @@ CREATE TABLE parent ( UNIQUE (self_id, parent_handle) ) ENGINE=InnoDB; - CREATE TABLE ca ( ca_id SERIAL NOT NULL, last_crl_sn BIGINT UNSIGNED NOT NULL, @@ -112,7 +110,6 @@ CREATE TABLE ca ( FOREIGN KEY (parent_id) REFERENCES parent (parent_id) ) ENGINE=InnoDB; - CREATE TABLE ca_detail ( ca_detail_id SERIAL NOT NULL, public_key LONGBLOB, @@ -130,7 +127,6 @@ CREATE TABLE ca_detail ( FOREIGN KEY (ca_id) REFERENCES ca (ca_id) ) ENGINE=InnoDB; - CREATE TABLE child ( child_id SERIAL NOT NULL, child_handle VARCHAR(255) NOT NULL, @@ -144,7 +140,6 @@ CREATE TABLE child ( UNIQUE (self_id, child_handle) ) ENGINE=InnoDB; - CREATE TABLE child_cert ( child_cert_id SERIAL NOT NULL, cert LONGBLOB NOT NULL, @@ -156,7 +151,6 @@ CREATE TABLE child_cert ( FOREIGN KEY (child_id) REFERENCES child (child_id) ) ENGINE=InnoDB; - CREATE TABLE revoked_cert ( revoked_cert_id SERIAL NOT NULL, serial BIGINT UNSIGNED NOT NULL, @@ -167,7 +161,6 @@ CREATE TABLE revoked_cert ( FOREIGN KEY (ca_detail_id) REFERENCES ca_detail (ca_detail_id) ) ENGINE=InnoDB; - CREATE TABLE route_origin ( route_origin_id SERIAL NOT NULL, route_origin_handle VARCHAR(255) NOT NULL, @@ -182,7 +175,6 @@ CREATE TABLE route_origin ( UNIQUE (self_id, route_origin_handle) ) ENGINE=InnoDB; - CREATE TABLE route_origin_prefix ( address VARCHAR(40) NOT NULL, prefixlen TINYINT NOT NULL, @@ -193,6 +185,28 @@ CREATE TABLE route_origin_prefix ( FOREIGN KEY (route_origin_id) REFERENCES route_origin (route_origin_id) ) ENGINE=InnoDB; +CREATE TABLE roa ( + roa_id SERIAL NOT NULL, + as_number DECIMAL(24,0), + cert LONGBLOB, + roa LONGBLOB, + self_id BIGINT UNSIGNED NOT NULL, + ca_detail_id BIGINT UNSIGNED, + PRIMARY KEY (roa_id), + FOREIGN KEY (self_id) REFERENCES self (self_id), + FOREIGN KEY (ca_detail_id) REFERENCES ca_detail (ca_detail_id) +) ENGINE=InnoDB; + +CREATE TABLE roa_prefix ( + address VARCHAR(40) NOT NULL, + prefixlen TINYINT NOT NULL, + max_prefixlen TINYINT NOT NULL, + version TINYINT NOT NULL, + roa_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (roa_id, address, prefixlen, max_prefixlen), + FOREIGN KEY (roa_id) REFERENCES roa (roa_id) +) ENGINE=InnoDB; + -- Local Variables: -- indent-tabs-mode: nil -- End: |