aboutsummaryrefslogtreecommitdiff
path: root/rpkid/rpki/rpkid.py
diff options
context:
space:
mode:
Diffstat (limited to 'rpkid/rpki/rpkid.py')
-rw-r--r--rpkid/rpki/rpkid.py350
1 files changed, 333 insertions, 17 deletions
diff --git a/rpkid/rpki/rpkid.py b/rpkid/rpki/rpkid.py
index cbb36ada..d6163bee 100644
--- a/rpkid/rpki/rpkid.py
+++ b/rpkid/rpki/rpkid.py
@@ -27,6 +27,7 @@ import argparse
import sys
import re
import random
+import base64
import rpki.resource_set
import rpki.up_down
import rpki.left_right
@@ -116,9 +117,14 @@ class main(object):
# Icky hack to let Iain do some testing quickly, should go away
# once we sort out whether we can make this change permanent.
+ #
+ # OK, the stuff to add router certificate support makes enough
+ # other changes that we're going to need a migration program in
+ # any case, so might as well throw the switch here too, or at
+ # least find out if it (still) works as expected.
self.merge_publication_directories = self.cfg.getboolean("merge_publication_directories",
- False)
+ True)
self.use_internal_cron = self.cfg.getboolean("use-internal-cron", True)
@@ -249,6 +255,18 @@ class main(object):
self.irdb_query(callback, errback, *q_pdus)
+ def irdb_query_ee_certificate_requests(self, self_handle, callback, errback):
+ """
+ Ask IRDB about self's EE certificate requests.
+ """
+
+ rpki.log.trace()
+
+ q_pdu = rpki.left_right.list_ee_certificate_requests_elt()
+ q_pdu.self_handle = self_handle
+
+ self.irdb_query(callback, errback, q_pdu)
+
def left_right_handler(self, query, path, cb):
"""
Process one left-right PDU.
@@ -522,6 +540,7 @@ class ca_obj(rpki.sql.sql_persistent):
sia_uri = self.construct_sia_uri(parent, rc)
sia_uri_changed = self.sia_uri != sia_uri
if sia_uri_changed:
+ rpki.log.debug("SIA changed: was %s now %s" % (self.sia_uri, sia_uri))
self.sia_uri = sia_uri
self.sql_mark_dirty()
@@ -544,6 +563,11 @@ class ca_obj(rpki.sql.sql_persistent):
else:
+ if ca_detail.state == "active" and ca_detail.ca_cert_uri != rc_cert.cert_url.rsync():
+ rpki.log.debug("AIA changed: was %s now %s" % (ca_detail.ca_cert_uri, rc_cert.cert_url.rsync()))
+ ca_detail.ca_cert_uri = rc_cert.cert_url.rsync()
+ ca_detail.sql_mark_dirty()
+
if ca_detail.state in ("pending", "active"):
if ca_detail.state == "pending":
@@ -570,10 +594,12 @@ class ca_obj(rpki.sql.sql_persistent):
def done():
if cert_map:
- rpki.log.warn("Unknown certificate SKI%s %s in resource class %s in list_response to %s from %s, maybe you want to \"revoke_forgotten\"?"
+ rpki.log.warn("Unknown certificate SKI%s %s in resource class %s in list_response "
+ "to %s from %s, maybe you want to \"revoke_forgotten\"?"
% ("" if len(cert_map) == 1 else "s",
", ".join(c.cert.gSKI() for c in cert_map.values()),
rc.class_name, parent.self.self_handle, parent.parent_handle))
+ self.gctx.sql.sweep()
self.gctx.checkpoint()
cb()
@@ -598,7 +624,8 @@ class ca_obj(rpki.sql.sql_persistent):
if ca_details:
rpki.async.iterator(ca_details, loop, done)
else:
- rpki.log.warn("Existing resource class %s to %s from %s with no certificates, rekeying" % (rc.class_name, parent.self.self_handle, parent.parent_handle))
+ rpki.log.warn("Existing resource class %s to %s from %s with no certificates, rekeying" %
+ (rc.class_name, parent.self.self_handle, parent.parent_handle))
self.gctx.checkpoint()
self.rekey(cb, eb)
@@ -748,10 +775,10 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"ca_detail",
"ca_detail_id",
("private_key_id", rpki.x509.RSA),
- ("public_key", rpki.x509.RSApublic),
+ ("public_key", rpki.x509.PublicKey),
("latest_ca_cert", rpki.x509.X509),
("manifest_private_key_id", rpki.x509.RSA),
- ("manifest_public_key", rpki.x509.RSApublic),
+ ("manifest_public_key", rpki.x509.PublicKey),
("latest_manifest_cert", rpki.x509.X509),
("latest_manifest", rpki.x509.SignedManifest),
("latest_crl", rpki.x509.CRL),
@@ -835,6 +862,13 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
+ @property
+ def ee_certificates(self):
+ """
+ Fetch all EE certificate objects that link to this ca_detail.
+ """
+ return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
+
def unpublished_ghostbusters(self, when):
"""
Fetch all unpublished Ghostbusters objects linked to this
@@ -869,6 +903,15 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
return self.latest_ca_cert.getNotAfter() <= rpki.sundial.now()
+ def covers(self, target):
+ """
+ Test whether this ca-detail covers a given set of resources.
+ """
+
+ assert not target.asn.inherit and not target.v4.inherit and not target.v6.inherit
+ me = self.latest_ca_cert.get_3779resources()
+ return target.asn <= me.asn and target.v4 <= me.v4 and target.v6 <= me.v6
+
def activate(self, ca, cert, uri, callback, errback, predecessor = None):
"""
Activate this ca_detail.
@@ -1082,19 +1125,23 @@ class ca_detail_obj(rpki.sql.sql_persistent):
self.state = "pending"
self.private_key_id = rpki.x509.RSA.generate()
- self.public_key = self.private_key_id.get_RSApublic()
+ self.public_key = self.private_key_id.get_public()
self.manifest_private_key_id = rpki.x509.RSA.generate()
- self.manifest_public_key = self.manifest_private_key_id.get_RSApublic()
+ self.manifest_public_key = self.manifest_private_key_id.get_public()
self.sql_store()
return self
- def issue_ee(self, ca, resources, subject_key, sia):
+ def issue_ee(self, ca, resources, subject_key, sia,
+ cn = None, sn = None, notAfter = None, eku = None):
"""
Issue a new EE certificate.
"""
+ if notAfter is None:
+ notAfter = self.latest_ca_cert.getNotAfter()
+
return self.latest_ca_cert.issue(
keypair = self.private_key_id,
subject_key = subject_key,
@@ -1103,8 +1150,11 @@ class ca_detail_obj(rpki.sql.sql_persistent):
aia = self.ca_cert_uri,
crldp = self.crl_uri,
resources = resources,
- notAfter = self.latest_ca_cert.getNotAfter(),
- is_ca = False)
+ notAfter = notAfter,
+ is_ca = False,
+ cn = cn,
+ sn = sn,
+ eku = eku)
def generate_manifest_cert(self):
"""
@@ -1200,8 +1250,12 @@ class ca_detail_obj(rpki.sql.sql_persistent):
self.crl_published = rpki.sundial.now()
self.sql_mark_dirty()
- publisher.publish(cls = rpki.publication.crl_elt, uri = self.crl_uri, obj = self.latest_crl, repository = parent.repository,
- handler = self.crl_published_callback)
+ publisher.publish(
+ cls = rpki.publication.crl_elt,
+ uri = self.crl_uri,
+ obj = self.latest_crl,
+ repository = parent.repository,
+ handler = self.crl_published_callback)
def crl_published_callback(self, pdu):
"""
@@ -1238,6 +1292,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
objs.extend((c.uri_tail, c.cert) for c in self.child_certs)
objs.extend((r.uri_tail, r.roa) for r in self.roas if r.roa is not None)
objs.extend((g.uri_tail, g.ghostbuster) for g in self.ghostbusters)
+ objs.extend((e.uri_tail, e.cert) for e in self.ee_certificates)
rpki.log.debug("Building manifest object %s" % uri)
self.latest_manifest = rpki.x509.SignedManifest.build(
@@ -1277,6 +1332,8 @@ class ca_detail_obj(rpki.sql.sql_persistent):
roa.regenerate(publisher, fast = True)
for ghostbuster in self.ghostbusters:
ghostbuster.regenerate(publisher, fast = True)
+ for ee_certificate in self.ee_certificates:
+ ee_certificate.reissue(publisher, force = True)
for child_cert in self.child_certs:
child_cert.reissue(self, publisher, force = True)
self.gctx.sql.sweep()
@@ -1451,7 +1508,11 @@ class child_cert_obj(rpki.sql.sql_persistent):
ca = ca_detail.ca
rpki.log.debug("Revoking %r %r" % (self, self.uri))
revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
- publisher.withdraw(cls = rpki.publication.certificate_elt, uri = self.uri, obj = self.cert, repository = ca.parent.repository)
+ publisher.withdraw(
+ cls = rpki.publication.certificate_elt,
+ uri = self.uri,
+ obj = self.cert,
+ repository = ca.parent.repository)
self.gctx.sql.sweep()
self.sql_delete()
if generate_crl_and_manifest:
@@ -1473,6 +1534,7 @@ class child_cert_obj(rpki.sql.sql_persistent):
old_resources = self.cert.get_3779resources()
old_sia = self.cert.get_SIA()
+ old_aia = self.cert.get_AIA()[0]
old_ca_detail = self.ca_detail
needed = False
@@ -1490,7 +1552,8 @@ class child_cert_obj(rpki.sql.sql_persistent):
needed = True
if resources.valid_until != old_resources.valid_until:
- rpki.log.debug("Validity changed for %r: old %s new %s" % (self, old_resources.valid_until, resources.valid_until))
+ rpki.log.debug("Validity changed for %r: old %s new %s" % (
+ self, old_resources.valid_until, resources.valid_until))
needed = True
if sia != old_sia:
@@ -1498,7 +1561,11 @@ class child_cert_obj(rpki.sql.sql_persistent):
needed = True
if ca_detail != old_ca_detail:
- rpki.log.debug("Issuer changed for %r %s: old %r new %r" % (self, self.uri, old_ca_detail, ca_detail))
+ rpki.log.debug("Issuer changed for %r: old %r new %r" % (self, old_ca_detail, ca_detail))
+ needed = True
+
+ if ca_detail.ca_cert_uri != old_aia:
+ rpki.log.debug("AIA changed for %r: old %r new %r" % (self, old_aia, ca_detail.ca_cert_uri))
needed = True
must_revoke = old_resources.oversized(resources) or old_resources.valid_until > resources.valid_until
@@ -1765,6 +1832,10 @@ class roa_obj(rpki.sql.sql_persistent):
rpki.log.debug("%r resources do not match EE, regenerating" % self)
return self.regenerate(publisher = publisher, fast = fast)
+ if self.cert.get_AIA()[0] != ca_detail.ca_cert_uri:
+ rpki.log.debug("%r AIA changed, regenerating" % self)
+ return self.regenerate(publisher = publisher, fast = fast)
+
def generate(self, publisher, fast = False):
"""
Generate a ROA.
@@ -1829,7 +1900,7 @@ class roa_obj(rpki.sql.sql_persistent):
self.cert = ca_detail.issue_ee(
ca = ca,
resources = resources,
- subject_key = keypair.get_RSApublic(),
+ subject_key = keypair.get_public(),
sia = (None, None, self.uri_from_key(keypair)))
self.roa = rpki.x509.ROA.build(self.asn, self.ipv4, self.ipv6, keypair, (self.cert,))
self.published = rpki.sundial.now()
@@ -2001,6 +2072,10 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
rpki.log.debug("%r past threshold %s, regenerating" % (self, regen_time))
return self.regenerate(publisher = publisher, fast = fast)
+ if self.cert.get_AIA()[0] != self.ca_detail.ca_cert_uri:
+ rpki.log.debug("%r AIA changed, regenerating" % self)
+ return self.regenerate(publisher = publisher, fast = fast)
+
def generate(self, publisher, fast = False):
"""
Generate a Ghostbuster record
@@ -2024,7 +2099,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
self.cert = ca_detail.issue_ee(
ca = ca,
resources = resources,
- subject_key = keypair.get_RSApublic(),
+ subject_key = keypair.get_public(),
sia = (None, None, self.uri_from_key(keypair)))
self.ghostbuster = rpki.x509.Ghostbuster.build(self.vcard, keypair, (self.cert,))
self.published = rpki.sundial.now()
@@ -2121,6 +2196,247 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
return self.cert.gSKI() + ".gbr"
+class ee_cert_obj(rpki.sql.sql_persistent):
+ """
+ EE certificate (router certificate or generic).
+ """
+
+ sql_template = rpki.sql.template(
+ "ee_cert",
+ "ee_cert_id",
+ "self_id",
+ "ca_detail_id",
+ "ski",
+ ("cert", rpki.x509.X509),
+ ("published", rpki.sundial.datetime))
+
+ def __repr__(self):
+ return rpki.log.log_repr(self, self.cert.getSubject(), self.uri)
+
+ def __init__(self, gctx = None, self_id = None, ca_detail_id = None, cert = None):
+ rpki.sql.sql_persistent.__init__(self)
+ self.gctx = gctx
+ self.self_id = self_id
+ self.ca_detail_id = ca_detail_id
+ self.cert = cert
+ self.ski = None if cert is None else cert.get_SKI()
+ self.published = None
+ if self_id or ca_detail_id or cert:
+ self.sql_mark_dirty()
+
+ @property
+ @rpki.sql.cache_reference
+ def self(self):
+ """
+ Fetch self object to which this ee_cert_obj links.
+ """
+ return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id)
+
+ @property
+ @rpki.sql.cache_reference
+ def ca_detail(self):
+ """
+ Fetch ca_detail object to which this ee_cert_obj links.
+ """
+ return rpki.rpkid.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
+
+ @ca_detail.deleter
+ def ca_detail(self):
+ try:
+ del self._ca_detail
+ except AttributeError:
+ pass
+
+ @property
+ def gski(self):
+ """
+ Calculate g(SKI), for ease of comparison with XML.
+
+ Although, really, one has to ask why we don't just store g(SKI)
+ in rpkid.sql instead of ski....
+ """
+ return base64.urlsafe_b64encode(self.ski).rstrip("=")
+
+ @gski.setter
+ def gski(self, val):
+ self.ski = base64.urlsafe_b64decode(s + ("=" * ((4 - len(s)) % 4)))
+
+ @property
+ def uri(self):
+ """
+ Return the publication URI for this ee_cert_obj.
+ """
+ return self.ca_detail.ca.sia_uri + self.uri_tail
+
+ @property
+ def uri_tail(self):
+ """
+ Return the tail (filename portion) of the publication URI for this
+ ee_cert_obj.
+ """
+ return self.cert.gSKI() + ".cer"
+
+ @classmethod
+ def create(cls, ca_detail, subject_name, subject_key, resources, publisher, eku = None):
+ """
+ Generate a new certificate and stuff it in a new ee_cert_obj.
+ """
+
+ cn, sn = subject_name.extract_cn_and_sn()
+ ca = ca_detail.ca
+
+ cert = ca_detail.issue_ee(
+ ca = ca,
+ subject_key = subject_key,
+ sia = None,
+ resources = resources,
+ notAfter = resources.valid_until,
+ cn = cn,
+ sn = sn,
+ eku = eku)
+
+ self = cls(
+ gctx = ca_detail.gctx,
+ self_id = ca.parent.self.self_id,
+ ca_detail_id = ca_detail.ca_detail_id,
+ cert = cert)
+
+ publisher.publish(
+ cls = rpki.publication.certificate_elt,
+ uri = self.uri,
+ obj = self.cert,
+ repository = ca.parent.repository,
+ handler = self.published_callback)
+
+ self.sql_store()
+
+ ca_detail.generate_manifest(publisher = publisher)
+
+ rpki.log.debug("New ee_cert %r" % self)
+
+ return self
+
+ def revoke(self, publisher, generate_crl_and_manifest = True):
+ """
+ Revoke and withdraw an EE certificate.
+ """
+
+ ca_detail = self.ca_detail
+ ca = ca_detail.ca
+ rpki.log.debug("Revoking %r %r" % (self, self.uri))
+ revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
+ publisher.withdraw(cls = rpki.publication.certificate_elt,
+ uri = self.uri,
+ obj = self.cert,
+ repository = ca.parent.repository)
+ self.gctx.sql.sweep()
+ self.sql_delete()
+ if generate_crl_and_manifest:
+ ca_detail.generate_crl(publisher = publisher)
+ ca_detail.generate_manifest(publisher = publisher)
+
+ def reissue(self, publisher, ca_detail = None, resources = None, force = False):
+ """
+ Reissue an existing EE cert, reusing the public key. If the EE
+ cert we would generate is identical to the one we already have, we
+ just return; if we need to reissue, we reuse this ee_cert_obj and
+ just update its contents, as the publication URI will not have
+ changed.
+ """
+
+ needed = False
+
+ old_cert = self.cert
+
+ old_ca_detail = self.ca_detail
+ if ca_detail is None:
+ ca_detail = old_ca_detail
+
+ assert ca_detail.ca is old_ca_detail.ca
+
+ old_resources = old_cert.get_3779resources()
+ if resources is None:
+ resources = old_resources
+
+ assert resources.valid_until is not None and old_resources.valid_until is not None
+
+ assert ca_detail.covers(resources)
+
+ if ca_detail != self.ca_detail:
+ rpki.log.debug("ca_detail changed for %r: old %r new %r" % (
+ self, self.ca_detail, ca_detail))
+ needed = True
+
+ if ca_detail.ca_cert_uri != old_cert.get_AIA()[0]:
+ rpki.log.debug("AIA changed for %r: old %s new %s" % (
+ self, old_cert.get_AIA()[0], ca_detail.ca_cert_uri))
+ needed = True
+
+ if resources.valid_until != old_resources.valid_until:
+ rpki.log.debug("Validity changed for %r: old %s new %s" % (
+ self, old_resources.valid_until, resources.valid_until))
+ needed = True
+
+ if resources.asn != old_resources.asn or resources.v4 != old_resources.v4 or resources.v6 != old_resources.v6:
+ rpki.log.debug("Resources changed for %r: old %s new %s" % (
+ self, old_resources, resources))
+ needed = True
+
+ must_revoke = (old_resources.oversized(resources) or
+ old_resources.valid_until > resources.valid_until)
+ if must_revoke:
+ rpki.log.debug("Must revoke existing cert(s) for %r" % self)
+ needed = True
+
+ if not needed and force:
+ rpki.log.debug("No change needed for %r, forcing reissuance anyway" % self)
+ needed = True
+
+ if not needed:
+ rpki.log.debug("No change to %r" % self)
+ return
+
+ cn, sn = self.cert.getSubject().extract_cn_and_sn()
+
+ self.cert = ca_detail.issue_ee(
+ ca = ca_detail.ca,
+ subject_key = self.cert.getPublicKey(),
+ eku = self.cert.get_EKU(),
+ sia = None,
+ resources = resources,
+ notAfter = resources.valid_until,
+ cn = cn,
+ sn = sn)
+
+ self.sql_mark_dirty()
+
+ publisher.publish(
+ cls = rpki.publication.certificate_elt,
+ uri = self.uri,
+ obj = self.cert,
+ repository = ca_detail.ca.parent.repository,
+ handler = self.published_callback)
+
+ if must_revoke:
+ revoked_cert_obj.revoke(cert = old_cert.cert, ca_detail = old_ca_detail)
+
+ self.gctx.sql.sweep()
+
+ if must_revoke:
+ ca_detail.generate_crl(publisher = publisher)
+ self.gctx.sql.sweep()
+
+ ca_detail.generate_manifest(publisher = publisher)
+
+ def published_callback(self, pdu):
+ """
+ Publication callback: check result and mark published.
+ """
+ pdu.raise_if_error()
+ self.published = None
+ self.sql_mark_dirty()
+
+
class publication_queue(object):
"""
Utility to simplify publication from within rpkid.