diff options
-rw-r--r-- | scripts/manifests.py | 5 | ||||
-rw-r--r-- | scripts/rpki/left_right.py | 10 | ||||
-rw-r--r-- | scripts/rpki/sql.py | 105 | ||||
-rw-r--r-- | scripts/rpki/sundial.py | 8 | ||||
-rw-r--r-- | scripts/rpki/up_down.py | 2 | ||||
-rw-r--r-- | scripts/rpki/x509.py | 4 |
6 files changed, 114 insertions, 20 deletions
diff --git a/scripts/manifests.py b/scripts/manifests.py index cdfce5c7..df8bc2d4 100644 --- a/scripts/manifests.py +++ b/scripts/manifests.py @@ -45,10 +45,13 @@ if test_empty_manifest: else: names_and_objs = [(fn, rpki.x509.X509(Auto_file = fn)) for fn in glob.glob("resource-cert-samples/*.cer")] +now = rpki.sundial.datetime.utcnow() + m = rpki.x509.SignedManifest() m.build( serial = 17, - nextUpdate = rpki.sundial.datetime.utcnow() + rpki.sundial.timedelta(days = 1), + thisUpdate = now, + nextUpdate = now + rpki.sundial.timedelta(days = 1), names_and_objs = names_and_objs, keypair = rpki.x509.RSA(Auto_file = "biz-certs/Alice-EE.key"), certs = rpki.x509.X509_chain(Auto_files = ("biz-certs/Alice-EE.cer", "biz-certs/Alice-CA.cer"))) diff --git a/scripts/rpki/left_right.py b/scripts/rpki/left_right.py index 06ca194f..a8813927 100644 --- a/scripts/rpki/left_right.py +++ b/scripts/rpki/left_right.py @@ -398,6 +398,10 @@ class self_elt(data_elt): self's CAs. Extracting nextUpdate from a manifest is hard at the moment due to implementation silliness, so for now we generate a new manifest whenever we generate a new CRL + + This method also cleans up tombstones left behind by revoked + ca_detail objects, since we're walking through the relevant + portions of the database anyway. """ rpki.log.trace() @@ -406,6 +410,9 @@ class self_elt(data_elt): for parent in self.parents(gctx): repository = parent.repository(gctx) for ca in parent.cas(gctx): + for ca_detail in ca.fetch_revoked(gctx): + if now > ca_detail.latest_crl.getNextUpdate(): + ca_detail.delete(gctx, ca, repository) ca_detail = ca.fetch_active(gctx) if now > ca_detail.latest_crl.getNextUpdate(): ca_detail.generate_crl(gctx) @@ -545,8 +552,7 @@ class parent_elt(data_elt): def serve_revoke(self, gctx): """Handle a left-right revoke action for this parent.""" for ca in self.cas(gctx): - for ca_detail in ca.ca_details(gctx): - ca_detail.revoke(gctx) + ca.revoke(gctx) def serve_reissue(self, gctx): """Handle a left-right reissue action for this parent.""" diff --git a/scripts/rpki/sql.py b/scripts/rpki/sql.py index cdab439f..de18e1f1 100644 --- a/scripts/rpki/sql.py +++ b/scripts/rpki/sql.py @@ -239,10 +239,22 @@ class ca_obj(sql_persistant): """Fetch all ca_detail objects that link to this CA object.""" return ca_detail_obj.sql_fetch_where(gctx, "ca_id = %s", (self.ca_id,)) + def fetch_pending(self, gctx): + """Fetch the pending ca_details for this CA, if any.""" + return ca_detail_obj.sql_fetch_where(gctx, "ca_id = %s AND state = 'pending'", (self.ca_id,)) + def fetch_active(self, gctx): """Fetch the active ca_detail for this CA, if any.""" return ca_detail_obj.sql_fetch_where1(gctx, "ca_id = %s AND state = 'active'", (self.ca_id,)) + def fetch_deprecated(self, gctx): + """Fetch deprecated ca_details for this CA, if any.""" + return ca_detail_obj.sql_fetch_where(gctx, "ca_id = %s AND state = 'deprecated'", (self.ca_id,)) + + def fetch_revoked(self, gctx): + """Fetch revoked ca_details for this CA, if any.""" + return ca_detail_obj.sql_fetch_where(gctx, "ca_id = %s AND state = 'revoked'", (self.ca_id,)) + def construct_sia_uri(self, gctx, parent, rc): """Construct the sia_uri value for this CA given configured information and the parent's up-down protocol list_response PDU. @@ -274,7 +286,7 @@ class ca_obj(sql_persistant): for ca_detail in ca_detail_obj.sql_fetch_where(gctx, "ca_id = %s AND latest_ca_cert IS NOT NULL", (self.ca_id,)): ski = ca_detail.latest_ca_cert.get_SKI() - if ca_detail.state != "deprecated": + if ca_detail.state in ("pending", "active"): current_resources = ca_detail.latest_ca_cert.get_3779resources() if sia_uri_changed or \ ca_detail.latest_ca_cert != cert_map[ski].cert or \ @@ -383,11 +395,27 @@ class ca_obj(sql_persistant): - Destroy old keypair. - Leave final CRL in place until its next CRL time has passed. + """ + parent = self.parent(gctx) + old_detail = self.fetch_active(gctx) + new_detail = ca_detail_obj.create(gctx, self) - """ + # This will need a callback when we go event-driven + issue_response = rpki.up_down.issue_pdu.query(gctx, parent, self, new_detail) + + new_detail.activate( + gctx = gctx, + ca = self, + cert = issue_response.payload.classes[0].certs[0].cert, + uri = issue_response.payload.classes[0].certs[0].cert_url, + predecessor = old_detail) + + def revoke(self, gctx): + """Revoke deprecated ca_detail objects associated with this ca.""" - raise rpki.exceptions.NotImplementedYet + for ca_detail in self.fetch_deprecated(gctx): + ca_detail.revoke(gctx) class ca_detail_obj(sql_persistant): """Internal CA detail object.""" @@ -404,7 +432,6 @@ class ca_detail_obj(sql_persistant): ("latest_manifest", rpki.x509.SignedManifest), ("latest_crl", rpki.x509.CRL), "state", - ("state_timer", rpki.sundial.datetime), "ca_cert_uri", "ca_id") @@ -450,6 +477,8 @@ class ca_detail_obj(sql_persistant): if predecessor is not None: predecessor.state = "deprecated" predecessor.sql_mark_dirty() + for child_cert in predecessor.child_certs(gctx): + child_cert.reissue(gctx, self) def delete(self, gctx, ca, repository): """Delete this ca_detail and all of its associated child_cert objects.""" @@ -464,7 +493,22 @@ class ca_detail_obj(sql_persistant): self.sql_delete(gctx) def revoke(self, gctx): - """Request revocation of all certificates whose SKI matches the key for this ca_detail.""" + """Request revocation of all certificates whose SKI matches the key for this ca_detail. + + Tasks: + + - Request revocation of old keypair by parent. + + - Revoke all child certs issued by the old keypair. + + - Generate a final CRL, signed with the old keypair, listing all + the revoked certs, with a next CRL time after the last cert or + CRL signed by the old keypair will have expired. + + - Destroy old keypair (and manifest keypair). + + - Leave final CRL in place until its next CRL time has passed. + """ # This will need a callback when we go event-driven r_msg = rpki.up_down.revoke_pdu.query(gctx, self) @@ -472,8 +516,29 @@ class ca_detail_obj(sql_persistant): if r_msg.payload.ski != self.latest_ca_cert.gSKI(): raise rpki.exceptions.SKIMismatch - ca = self.ca(gctx) - self.delete(gctx, ca, ca.parent(gctx).repository(gctx)) + nextUpdate = rpki.sundial.datetime.utcnow() + + if self.latest_manifest is not None: + nextUpdate = nextUpdate.later(self.latest_manifest.getNextUpdate()) + + if self.latest_crl is not None: + nextUpdate = nextUpdate.later(self.latest_crl.getNextUpdate()) + + for child_cert in self.chidl_certs(gctx): + nextUpdate = nextUpdate.later(child_cert.cert.getNotAfter()) + child_cert.revoke() + + nextUpdate += rpki.sundial.timedelta(seconds = parent.self(gctx).crl_interval) + + generate_crl(gctx, nextUpdate) + generate_manifest(gctx, nextUpdate) + + self.private_key_id = None + self.manifest_private_key_id = None + self.manifest_public_key = None + self.latest_manifest_cert = None + self.state = "revoked" + self.sql_mark_dirty() def update(self, gctx, parent, ca, rc, sia_uri_changed, old_resources): """Need to get a new certificate for this ca_detail and perhaps @@ -572,7 +637,7 @@ class ca_detail_obj(sql_persistant): return child_cert - def generate_crl(self, gctx): + def generate_crl(self, gctx, nextUpdate = None): """Generate a new CRL for this ca_detail. At the moment this is unconditional, that is, it is up to the caller to decide whether a new CRL is needed. @@ -584,6 +649,9 @@ class ca_detail_obj(sql_persistant): crl_interval = rpki.sundial.timedelta(seconds = parent.self(gctx).crl_interval) now = rpki.sundial.datetime.utcnow() + if nextUpdate is None: + nextUpdate = now + crl_interval + certlist = [] for child_cert in self.child_certs(gctx, revoked = True): if now > child_cert.cert.getNotAfter() + crl_interval: @@ -597,12 +665,12 @@ class ca_detail_obj(sql_persistant): issuer = self.latest_ca_cert, serial = ca.next_crl_number(), thisUpdate = now, - nextUpdate = now + crl_interval, + nextUpdate = nextUpdate, revokedCertificates = certlist) repository.publish(gctx, self.latest_crl, self.crl_uri(ca)) - def generate_manifest(self, gctx): + def generate_manifest(self, gctx, nextUpdate = None): """Generate a new manifest for this ca_detail.""" ca = self.ca(gctx) @@ -611,12 +679,16 @@ class ca_detail_obj(sql_persistant): crl_interval = rpki.sundial.timedelta(seconds = parent.self(gctx).crl_interval) now = rpki.sundial.datetime.utcnow() + if nextUpdate is None: + nextUpdate = now + crl_interval + certs = self.child_certs(gctx) m = rpki.x509.SignedManifest() m.build( serial = ca.next_manifest_number(), - nextUpdate = now + crl_interval, + thisUpdate = now, + nextUpdate = nextUpdate, names_and_objs = [(c.uri_tail(), c.cert) for c in certs], keypair = self.manifest_private_key_id, certs = rpki.x509.X509_chain(self.latest_manifest_cert)) @@ -661,7 +733,7 @@ class child_cert_obj(sql_persistant): self.revoked = rpki.sundial.datetime.utcnow() self.sql_mark_dirty() - def reissue(self, gctx, ca_detail, resources, sia = None): + def reissue(self, gctx, ca_detail, resources = None, sia = None): """Reissue an existing cert, reusing the public key. If the cert we would generate is identical to the one we already have, we just return the one we already have. If we have to revoke the old @@ -675,21 +747,26 @@ class child_cert_obj(sql_persistant): old_resources = self.cert.get_3779resources() old_sia = self.cert.get_SIA() + old_ca_detail = self.ca_detail(gctx) + + if resources is None: + resources = old_resources if sia is None: sia = old_sia assert resources.valid_until is not None and old_resources.valid_until is not None - if resources == old_resources and sia == old_sia: + if resources == old_resources and sia == old_sia and ca_detail == old_ca_detail: return self must_revoke = old_resources.oversized(resources) or old_resources.valid_until > resources.valid_until + new_issuer = ca_detail != old_ca_detail if resources.valid_until != old_resources.valid_until: rpki.log.debug("Validity changed: %s %s" % ( old_resources.valid_until, resources.valid_until)) - if must_revoke: + if must_revoke or new_issuer: child_cert = None else: child_cert = self diff --git a/scripts/rpki/sundial.py b/scripts/rpki/sundial.py index c8e977c6..0703ff22 100644 --- a/scripts/rpki/sundial.py +++ b/scripts/rpki/sundial.py @@ -105,6 +105,14 @@ class datetime(pydatetime.datetime): """Convert to SQL storage format.""" return self + def later(self, other): + """Return the later of two timestamps.""" + return other if other > self else self + + def earlier(self, other): + """Return the earlier of two timestamps.""" + return other if other < self else self + # Alias to simplify imports for callers timedelta = pydatetime.timedelta diff --git a/scripts/rpki/up_down.py b/scripts/rpki/up_down.py index 839e91ee..93b5fbf2 100644 --- a/scripts/rpki/up_down.py +++ b/scripts/rpki/up_down.py @@ -312,7 +312,7 @@ class issue_pdu(base_elt): @classmethod def query(cls, gctx, parent, ca, ca_detail): """Send an "issue" request to parent associated with ca.""" - assert ca_detail is not None and ca_detail.state != "deprecated" + assert ca_detail is not None and ca_detail.state in ("pending", "active") sia = ((rpki.oids.name2oid["id-ad-caRepository"], ("uri", ca.sia_uri)), (rpki.oids.name2oid["id-ad-rpkiManifest"], ("uri", ca_detail.manifest_uri(ca)))) self = cls() diff --git a/scripts/rpki/x509.py b/scripts/rpki/x509.py index d61b8449..4d902351 100644 --- a/scripts/rpki/x509.py +++ b/scripts/rpki/x509.py @@ -623,7 +623,7 @@ class SignedManifest(DER_object): m.fromString(s) self.content = m - def build(self, serial, nextUpdate, names_and_objs, keypair, certs, version = 0): + 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.""" filelist = [] for name, obj in names_and_objs: @@ -634,7 +634,7 @@ class SignedManifest(DER_object): m = rpki.manifest.Manifest() m.version.set(version) m.manifestNumber.set(serial) - m.thisUpdate.set(rpki.sundial.datetime.utcnow().toGeneralizedTime()) + 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.fileList.set(filelist) |