aboutsummaryrefslogtreecommitdiff
path: root/rpki/rpkidb/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'rpki/rpkidb/models.py')
-rw-r--r--rpki/rpkidb/models.py4060
1 files changed, 2030 insertions, 2030 deletions
diff --git a/rpki/rpkidb/models.py b/rpki/rpkidb/models.py
index 1a293360..ab16a176 100644
--- a/rpki/rpkidb/models.py
+++ b/rpki/rpkidb/models.py
@@ -5,7 +5,6 @@ Django ORM models for rpkid.
from __future__ import unicode_literals
import logging
-import base64
import tornado.gen
import tornado.web
@@ -35,189 +34,189 @@ logger = logging.getLogger(__name__)
# very simple change given migrations.
class XMLTemplate(object):
- """
- Encapsulate all the voodoo for transcoding between lxml and ORM.
- """
-
- # Type map to simplify declaration of Base64 sub-elements.
-
- element_type = dict(bpki_cert = rpki.x509.X509,
- bpki_glue = rpki.x509.X509,
- pkcs10_request = rpki.x509.PKCS10,
- signing_cert = rpki.x509.X509,
- signing_cert_crl = rpki.x509.CRL)
-
-
- def __init__(self, name, attributes = (), booleans = (), elements = (), readonly = (), handles = ()):
- self.name = name
- self.handles = handles
- self.attributes = attributes
- self.booleans = booleans
- self.elements = elements
- self.readonly = readonly
-
-
- def encode(self, obj, q_pdu, r_msg):
- """
- Encode an ORM object as XML.
"""
-
- r_pdu = SubElement(r_msg, rpki.left_right.xmlns + self.name, nsmap = rpki.left_right.nsmap, action = q_pdu.get("action"))
- if self.name != "tenant":
- r_pdu.set("tenant_handle", obj.tenant.tenant_handle)
- r_pdu.set(self.name + "_handle", getattr(obj, self.name + "_handle"))
- if q_pdu.get("tag"):
- r_pdu.set("tag", q_pdu.get("tag"))
- for h in self.handles:
- k = h.xml_template.name
- v = getattr(obj, k)
- if v is not None:
- r_pdu.set(k + "_handle", getattr(v, k + "_handle"))
- for k in self.attributes:
- v = getattr(obj, k)
- if v is not None:
- r_pdu.set(k, str(v))
- for k in self.booleans:
- if getattr(obj, k):
- r_pdu.set(k, "yes")
- for k in self.elements + self.readonly:
- v = getattr(obj, k)
- if v is not None and not v.empty():
- SubElement(r_pdu, rpki.left_right.xmlns + k).text = v.get_Base64()
- logger.debug("XMLTemplate.encode(): %s", ElementToString(r_pdu))
-
-
- def acknowledge(self, obj, q_pdu, r_msg):
- """
- Add an acknowledgement PDU in response to a create, set, or
- destroy action.
-
- This includes a bit of special-case code for BSC objects which has
- to go somewhere; we could handle it via some kind method of
- call-out to the BSC model, but it's not worth building a general
- mechanism for one case, so we do it inline and have done.
- """
-
- assert q_pdu.tag == rpki.left_right.xmlns + self.name
- action = q_pdu.get("action")
- r_pdu = SubElement(r_msg, rpki.left_right.xmlns + self.name, nsmap = rpki.left_right.nsmap, action = action)
- if self.name != "tenant":
- r_pdu.set("tenant_handle", obj.tenant.tenant_handle)
- r_pdu.set(self.name + "_handle", getattr(obj, self.name + "_handle"))
- if q_pdu.get("tag"):
- r_pdu.set("tag", q_pdu.get("tag"))
- if self.name == "bsc" and action != "destroy" and obj.pkcs10_request is not None:
- assert not obj.pkcs10_request.empty()
- SubElement(r_pdu, rpki.left_right.xmlns + "pkcs10_request").text = obj.pkcs10_request.get_Base64()
- logger.debug("XMLTemplate.acknowledge(): %s", ElementToString(r_pdu))
-
-
- def decode(self, obj, q_pdu):
- """
- Decode XML into an ORM object.
- """
-
- logger.debug("XMLTemplate.decode(): %r %s", obj, ElementToString(q_pdu))
- assert q_pdu.tag == rpki.left_right.xmlns + self.name
- for h in self.handles:
- k = h.xml_template.name
- v = q_pdu.get(k + "_handle")
- if v is not None:
- setattr(obj, k, h.objects.get(**{k + "_handle" : v, "tenant" : obj.tenant}))
- for k in self.attributes:
- v = q_pdu.get(k)
- if v is not None:
- v.encode("ascii")
- if v.isdigit():
- v = long(v)
- setattr(obj, k, v)
- for k in self.booleans:
- v = q_pdu.get(k)
- if v is not None:
- setattr(obj, k, v == "yes")
- for k in self.elements:
- v = q_pdu.findtext(rpki.left_right.xmlns + k)
- if v and v.strip():
- setattr(obj, k, self.element_type[k](Base64 = v))
+ Encapsulate all the voodoo for transcoding between lxml and ORM.
+ """
+
+ # Type map to simplify declaration of Base64 sub-elements.
+
+ element_type = dict(bpki_cert = rpki.x509.X509,
+ bpki_glue = rpki.x509.X509,
+ pkcs10_request = rpki.x509.PKCS10,
+ signing_cert = rpki.x509.X509,
+ signing_cert_crl = rpki.x509.CRL)
+
+
+ def __init__(self, name, attributes = (), booleans = (), elements = (), readonly = (), handles = ()):
+ self.name = name
+ self.handles = handles
+ self.attributes = attributes
+ self.booleans = booleans
+ self.elements = elements
+ self.readonly = readonly
+
+
+ def encode(self, obj, q_pdu, r_msg):
+ """
+ Encode an ORM object as XML.
+ """
+
+ r_pdu = SubElement(r_msg, rpki.left_right.xmlns + self.name, nsmap = rpki.left_right.nsmap, action = q_pdu.get("action"))
+ if self.name != "tenant":
+ r_pdu.set("tenant_handle", obj.tenant.tenant_handle)
+ r_pdu.set(self.name + "_handle", getattr(obj, self.name + "_handle"))
+ if q_pdu.get("tag"):
+ r_pdu.set("tag", q_pdu.get("tag"))
+ for h in self.handles:
+ k = h.xml_template.name
+ v = getattr(obj, k)
+ if v is not None:
+ r_pdu.set(k + "_handle", getattr(v, k + "_handle"))
+ for k in self.attributes:
+ v = getattr(obj, k)
+ if v is not None:
+ r_pdu.set(k, str(v))
+ for k in self.booleans:
+ if getattr(obj, k):
+ r_pdu.set(k, "yes")
+ for k in self.elements + self.readonly:
+ v = getattr(obj, k)
+ if v is not None and not v.empty():
+ SubElement(r_pdu, rpki.left_right.xmlns + k).text = v.get_Base64()
+ logger.debug("XMLTemplate.encode(): %s", ElementToString(r_pdu))
+
+
+ def acknowledge(self, obj, q_pdu, r_msg):
+ """
+ Add an acknowledgement PDU in response to a create, set, or
+ destroy action.
+
+ This includes a bit of special-case code for BSC objects which has
+ to go somewhere; we could handle it via some kind method of
+ call-out to the BSC model, but it's not worth building a general
+ mechanism for one case, so we do it inline and have done.
+ """
+
+ assert q_pdu.tag == rpki.left_right.xmlns + self.name
+ action = q_pdu.get("action")
+ r_pdu = SubElement(r_msg, rpki.left_right.xmlns + self.name, nsmap = rpki.left_right.nsmap, action = action)
+ if self.name != "tenant":
+ r_pdu.set("tenant_handle", obj.tenant.tenant_handle)
+ r_pdu.set(self.name + "_handle", getattr(obj, self.name + "_handle"))
+ if q_pdu.get("tag"):
+ r_pdu.set("tag", q_pdu.get("tag"))
+ if self.name == "bsc" and action != "destroy" and obj.pkcs10_request is not None:
+ assert not obj.pkcs10_request.empty()
+ SubElement(r_pdu, rpki.left_right.xmlns + "pkcs10_request").text = obj.pkcs10_request.get_Base64()
+ logger.debug("XMLTemplate.acknowledge(): %s", ElementToString(r_pdu))
+
+
+ def decode(self, obj, q_pdu):
+ """
+ Decode XML into an ORM object.
+ """
+
+ logger.debug("XMLTemplate.decode(): %r %s", obj, ElementToString(q_pdu))
+ assert q_pdu.tag == rpki.left_right.xmlns + self.name
+ for h in self.handles:
+ k = h.xml_template.name
+ v = q_pdu.get(k + "_handle")
+ if v is not None:
+ setattr(obj, k, h.objects.get(**{k + "_handle" : v, "tenant" : obj.tenant}))
+ for k in self.attributes:
+ v = q_pdu.get(k)
+ if v is not None:
+ v.encode("ascii")
+ if v.isdigit():
+ v = long(v)
+ setattr(obj, k, v)
+ for k in self.booleans:
+ v = q_pdu.get(k)
+ if v is not None:
+ setattr(obj, k, v == "yes")
+ for k in self.elements:
+ v = q_pdu.findtext(rpki.left_right.xmlns + k)
+ if v and v.strip():
+ setattr(obj, k, self.element_type[k](Base64 = v))
class XMLManager(models.Manager): # pylint: disable=W0232
- """
- Add a few methods which locate or create an object or objects
- corresponding to the handles in an XML element, as appropriate.
-
- This assumes that models which use it have an "xml_template"
- class attribute holding an XMLTemplate object (above).
- """
-
- def xml_get_or_create(self, xml):
- name = self.model.xml_template.name
- action = xml.get("action")
- assert xml.tag == rpki.left_right.xmlns + name and action in ("create", "set")
- d = { name + "_handle" : xml.get(name + "_handle") }
- if name != "tenant" and action != "create":
- d["tenant__tenant_handle"] = xml.get("tenant_handle")
- logger.debug("XMLManager.xml_get_or_create(): name %s action %s filter %r", name, action, d)
- result = self.model(**d) if action == "create" else self.get(**d)
- if name != "tenant" and action == "create":
- result.tenant = Tenant.objects.get(tenant_handle = xml.get("tenant_handle"))
- logger.debug("XMLManager.xml_get_or_create(): name %s action %s filter %r result %r", name, action, d, result)
- return result
-
- def xml_list(self, xml):
- name = self.model.xml_template.name
- action = xml.get("action")
- assert xml.tag == rpki.left_right.xmlns + name and action in ("get", "list")
- d = {}
- if action == "get":
- d[name + "_handle"] = xml.get(name + "_handle")
- if name != "tenant":
- d["tenant__tenant_handle"] = xml.get("tenant_handle")
- logger.debug("XMLManager.xml_list(): name %s action %s filter %r", name, action, d)
- result = self.filter(**d) if d else self.all()
- logger.debug("XMLManager.xml_list(): name %s action %s filter %r result %r", name, action, d, result)
- return result
-
- def xml_get_for_delete(self, xml):
- name = self.model.xml_template.name
- action = xml.get("action")
- assert xml.tag == rpki.left_right.xmlns + name and action == "destroy"
- d = { name + "_handle" : xml.get(name + "_handle") }
- if name != "tenant":
- d["tenant__tenant_handle"] = xml.get("tenant_handle")
- logger.debug("XMLManager.xml_get_for_delete(): name %s action %s filter %r", name, action, d)
- result = self.get(**d)
- logger.debug("XMLManager.xml_get_for_delete(): name %s action %s filter %r result %r", name, action, d, result)
- return result
+ """
+ Add a few methods which locate or create an object or objects
+ corresponding to the handles in an XML element, as appropriate.
+
+ This assumes that models which use it have an "xml_template"
+ class attribute holding an XMLTemplate object (above).
+ """
+
+ def xml_get_or_create(self, xml):
+ name = self.model.xml_template.name
+ action = xml.get("action")
+ assert xml.tag == rpki.left_right.xmlns + name and action in ("create", "set")
+ d = { name + "_handle" : xml.get(name + "_handle") }
+ if name != "tenant" and action != "create":
+ d["tenant__tenant_handle"] = xml.get("tenant_handle")
+ logger.debug("XMLManager.xml_get_or_create(): name %s action %s filter %r", name, action, d)
+ result = self.model(**d) if action == "create" else self.get(**d)
+ if name != "tenant" and action == "create":
+ result.tenant = Tenant.objects.get(tenant_handle = xml.get("tenant_handle"))
+ logger.debug("XMLManager.xml_get_or_create(): name %s action %s filter %r result %r", name, action, d, result)
+ return result
+
+ def xml_list(self, xml):
+ name = self.model.xml_template.name
+ action = xml.get("action")
+ assert xml.tag == rpki.left_right.xmlns + name and action in ("get", "list")
+ d = {}
+ if action == "get":
+ d[name + "_handle"] = xml.get(name + "_handle")
+ if name != "tenant":
+ d["tenant__tenant_handle"] = xml.get("tenant_handle")
+ logger.debug("XMLManager.xml_list(): name %s action %s filter %r", name, action, d)
+ result = self.filter(**d) if d else self.all()
+ logger.debug("XMLManager.xml_list(): name %s action %s filter %r result %r", name, action, d, result)
+ return result
+
+ def xml_get_for_delete(self, xml):
+ name = self.model.xml_template.name
+ action = xml.get("action")
+ assert xml.tag == rpki.left_right.xmlns + name and action == "destroy"
+ d = { name + "_handle" : xml.get(name + "_handle") }
+ if name != "tenant":
+ d["tenant__tenant_handle"] = xml.get("tenant_handle")
+ logger.debug("XMLManager.xml_get_for_delete(): name %s action %s filter %r", name, action, d)
+ result = self.get(**d)
+ logger.debug("XMLManager.xml_get_for_delete(): name %s action %s filter %r result %r", name, action, d, result)
+ return result
def xml_hooks(cls):
- """
- Class decorator to add default XML hooks.
- """
+ """
+ Class decorator to add default XML hooks.
+ """
- # Maybe inheritance from an abstract model would work here. Then
- # again, maybe we could use this decorator to do something prettier
- # for the XMLTemplate setup. Whatever. Gussie up later.
+ # Maybe inheritance from an abstract model would work here. Then
+ # again, maybe we could use this decorator to do something prettier
+ # for the XMLTemplate setup. Whatever. Gussie up later.
- def default_xml_pre_save_hook(self, q_pdu):
- logger.debug("default_xml_pre_save_hook()")
+ def default_xml_pre_save_hook(self, q_pdu):
+ logger.debug("default_xml_pre_save_hook()")
- @tornado.gen.coroutine
- def default_xml_post_save_hook(self, rpkid, q_pdu):
- logger.debug("default_xml_post_save_hook()")
+ @tornado.gen.coroutine
+ def default_xml_post_save_hook(self, rpkid, q_pdu):
+ logger.debug("default_xml_post_save_hook()")
- @tornado.gen.coroutine
- def default_xml_pre_delete_hook(self, rpkid):
- logger.debug("default_xml_pre_delete_hook()")
+ @tornado.gen.coroutine
+ def default_xml_pre_delete_hook(self, rpkid):
+ logger.debug("default_xml_pre_delete_hook()")
- for name, method in (("xml_pre_save_hook", default_xml_pre_save_hook),
- ("xml_post_save_hook", default_xml_post_save_hook),
- ("xml_pre_delete_hook", default_xml_pre_delete_hook)):
- if not hasattr(cls, name):
- setattr(cls, name, method)
+ for name, method in (("xml_pre_save_hook", default_xml_pre_save_hook),
+ ("xml_post_save_hook", default_xml_post_save_hook),
+ ("xml_pre_delete_hook", default_xml_pre_delete_hook)):
+ if not hasattr(cls, name):
+ setattr(cls, name, method)
- return cls
+ return cls
# Models.
@@ -227,2128 +226,2129 @@ def xml_hooks(cls):
@xml_hooks
class Tenant(models.Model):
- tenant_handle = models.SlugField(max_length = 255)
- use_hsm = models.BooleanField(default = False)
- crl_interval = models.BigIntegerField(null = True)
- regen_margin = models.BigIntegerField(null = True)
- bpki_cert = CertificateField(null = True)
- bpki_glue = CertificateField(null = True)
- objects = XMLManager()
-
- xml_template = XMLTemplate(
- name = "tenant",
- attributes = ("crl_interval", "regen_margin"),
- booleans = ("use_hsm",),
- elements = ("bpki_cert", "bpki_glue"))
-
- @tornado.gen.coroutine
- def xml_pre_delete_hook(self, rpkid):
- yield [parent.destroy() for parent in self.parents.all()]
-
- @tornado.gen.coroutine
- def xml_post_save_hook(self, rpkid, q_pdu):
- rekey = q_pdu.get("rekey")
- revoke = q_pdu.get("revoke")
- reissue = q_pdu.get("reissue")
- revoke_forgotten = q_pdu.get("revoke_forgotten")
-
- if q_pdu.get("clear_replay_protection"):
- for parent in self.parents.all():
- parent.clear_replay_protection()
- for child in self.children.all():
- child.clear_replay_protection()
- for repository in self.repositories.all():
- repository.clear_replay_protection()
-
- futures = []
-
- if rekey or revoke or reissue or revoke_forgotten:
- for parent in self.parents.all():
- if rekey:
- futures.append(parent.serve_rekey(rpkid))
- if revoke:
- futures.append(parent.serve_revoke(rpkid))
- if reissue:
- futures.append(parent.serve_reissue(rpkid))
- if revoke_forgotten:
- futures.append(parent.serve_revoke_forgotten(rpkid))
-
- if q_pdu.get("publish_world_now"):
- futures.append(self.serve_publish_world_now(rpkid))
- if q_pdu.get("run_now"):
- futures.append(self.serve_run_now(rpkid))
-
- yield futures
-
-
- @tornado.gen.coroutine
- def serve_publish_world_now(self, rpkid):
- publisher = rpki.rpkid.publication_queue(rpkid)
- repositories = set()
- objects = dict()
-
- for parent in self.parents.all():
-
- repository = parent.repository
- if repository.peer_contact_uri in repositories:
- continue
- repositories.add(repository.peer_contact_uri)
- q_msg = Element(rpki.publication.tag_msg, nsmap = rpki.publication.nsmap,
- type = "query", version = rpki.publication.version)
- SubElement(q_msg, rpki.publication.tag_list, tag = "list")
-
- r_msg = yield repository.call_pubd(rpkid, q_msg, length_check = False)
-
- for r_pdu in r_msg:
- assert r_pdu.tag == rpki.publication.tag_list
- if r_pdu.get("uri") in objects:
- logger.warning("pubd reported multiple published copies of URI %r, this makes no sense, blundering onwards", r_pdu.get("uri"))
- else:
- objects[r_pdu.get("uri")] = (r_pdu.get("hash"), repository)
-
- def reconcile(uri, obj, repository):
- h, r = objects.pop(uri, (None, None))
- if h is not None:
- assert r == repository
- publisher.queue(uri = uri, new_obj = obj, old_hash = h, repository = repository)
-
- for ca_detail in CADetail.objects.filter(ca__parent__tenant = self, state = "active"):
- repository = ca_detail.ca.parent.repository
- reconcile(uri = ca_detail.crl_uri, obj = ca_detail.latest_crl, repository = repository)
- reconcile(uri = ca_detail.manifest_uri, obj = ca_detail.latest_manifest, repository = repository)
- for c in ca_detail.child_certs.all():
- reconcile(uri = c.uri, obj = c.cert, repository = repository)
- for r in ca_detail.roas.filter(roa__isnull = False):
- reconcile(uri = r.uri, obj = r.roa, repository = repository)
- for g in ca_detail.ghostbusters.all():
- reconcile(uri = g.uri, obj = g.ghostbuster, repository = repository)
- for c in ca_detail.ee_certificates.all():
- reconcile(uri = c.uri, obj = c.cert, repository = repository)
- for u in objects:
- h, r = objects[u]
- publisher.queue(uri = u, old_hash = h, repository = r)
-
- yield publisher.call_pubd()
-
-
- @tornado.gen.coroutine
- def serve_run_now(self, rpkid):
- logger.debug("Forced immediate run of periodic actions for tenant %s[%r]", self.tenant_handle, self)
- tasks = self.cron_tasks(rpkid)
- rpkid.task_add(tasks)
- futures = [task.wait() for task in tasks]
- rpkid.task_run()
- yield futures
-
-
- def cron_tasks(self, rpkid):
- try:
- return self._cron_tasks
- except AttributeError:
- self._cron_tasks = tuple(task(rpkid, self) for task in rpki.rpkid_tasks.task_classes)
- return self._cron_tasks
-
-
- def find_covering_ca_details(self, resources):
- """
- Return all active CADetails for this <tenant/> which cover a
- particular set of resources.
-
- If we expected there to be a large number of CADetails, we
- could add index tables and write fancy SQL query to do this, but
- for the expected common case where there are only one or two
- active CADetails per <tenant/>, it's probably not worth it. In
- any case, this is an optimization we can leave for later.
- """
-
- return set(ca_detail
- for ca_detail in CADetail.objects.filter(ca__parent__tenant = self, state = "active")
- if ca_detail.covers(resources))
-
-
-@xml_hooks
-class BSC(models.Model):
- bsc_handle = models.SlugField(max_length = 255)
- private_key_id = RSAPrivateKeyField()
- pkcs10_request = PKCS10Field()
- hash_alg = EnumField(choices = ("sha256",), default = "sha256")
- signing_cert = CertificateField(null = True)
- signing_cert_crl = CRLField(null = True)
- tenant = models.ForeignKey(Tenant, related_name = "bscs")
- objects = XMLManager()
-
- class Meta: # pylint: disable=C1001,W0232
- unique_together = ("tenant", "bsc_handle")
-
- xml_template = XMLTemplate(
- name = "bsc",
- elements = ("signing_cert", "signing_cert_crl"),
- readonly = ("pkcs10_request",))
-
-
- def xml_pre_save_hook(self, q_pdu):
- # Handle key generation, only supports RSA with SHA-256 for now.
- if q_pdu.get("generate_keypair"):
- assert q_pdu.get("key_type") in (None, "rsa") and q_pdu.get("hash_alg") in (None, "sha256")
- self.private_key_id = rpki.x509.RSA.generate(keylength = int(q_pdu.get("key_length", 2048)))
- self.pkcs10_request = rpki.x509.PKCS10.create(keypair = self.private_key_id)
-
-
-@xml_hooks
-class Repository(models.Model):
- repository_handle = models.SlugField(max_length = 255)
- peer_contact_uri = models.TextField(null = True)
- rrdp_notification_uri = models.TextField(null = True)
- bpki_cert = CertificateField(null = True)
- bpki_glue = CertificateField(null = True)
- last_cms_timestamp = SundialField(null = True)
- bsc = models.ForeignKey(BSC, related_name = "repositories")
- tenant = models.ForeignKey(Tenant, related_name = "repositories")
- objects = XMLManager()
-
- class Meta: # pylint: disable=C1001,W0232
- unique_together = ("tenant", "repository_handle")
+ tenant_handle = models.SlugField(max_length = 255)
+ use_hsm = models.BooleanField(default = False)
+ crl_interval = models.BigIntegerField(null = True)
+ regen_margin = models.BigIntegerField(null = True)
+ bpki_cert = CertificateField(null = True)
+ bpki_glue = CertificateField(null = True)
+ objects = XMLManager()
+
+ xml_template = XMLTemplate(
+ name = "tenant",
+ attributes = ("crl_interval", "regen_margin"),
+ booleans = ("use_hsm",),
+ elements = ("bpki_cert", "bpki_glue"))
+
+ @tornado.gen.coroutine
+ def xml_pre_delete_hook(self, rpkid):
+ yield [parent.destroy() for parent in self.parents.all()]
+
+ @tornado.gen.coroutine
+ def xml_post_save_hook(self, rpkid, q_pdu):
+ rekey = q_pdu.get("rekey")
+ revoke = q_pdu.get("revoke")
+ reissue = q_pdu.get("reissue")
+ revoke_forgotten = q_pdu.get("revoke_forgotten")
+
+ if q_pdu.get("clear_replay_protection"):
+ for parent in self.parents.all():
+ parent.clear_replay_protection()
+ for child in self.children.all():
+ child.clear_replay_protection()
+ for repository in self.repositories.all():
+ repository.clear_replay_protection()
+
+ futures = []
+
+ if rekey or revoke or reissue or revoke_forgotten:
+ for parent in self.parents.all():
+ if rekey:
+ futures.append(parent.serve_rekey(rpkid))
+ if revoke:
+ futures.append(parent.serve_revoke(rpkid))
+ if reissue:
+ futures.append(parent.serve_reissue(rpkid))
+ if revoke_forgotten:
+ futures.append(parent.serve_revoke_forgotten(rpkid))
+
+ if q_pdu.get("publish_world_now"):
+ futures.append(self.serve_publish_world_now(rpkid))
+ if q_pdu.get("run_now"):
+ futures.append(self.serve_run_now(rpkid))
+
+ yield futures
+
+
+ @tornado.gen.coroutine
+ def serve_publish_world_now(self, rpkid):
+ publisher = rpki.rpkid.publication_queue(rpkid)
+ repositories = set()
+ objects = dict()
+
+ for parent in self.parents.all():
+
+ repository = parent.repository
+ if repository.peer_contact_uri in repositories:
+ continue
+ repositories.add(repository.peer_contact_uri)
+ q_msg = Element(rpki.publication.tag_msg, nsmap = rpki.publication.nsmap,
+ type = "query", version = rpki.publication.version)
+ SubElement(q_msg, rpki.publication.tag_list, tag = "list")
+
+ r_msg = yield repository.call_pubd(rpkid, q_msg, length_check = False)
+
+ for r_pdu in r_msg:
+ assert r_pdu.tag == rpki.publication.tag_list
+ if r_pdu.get("uri") in objects:
+ logger.warning("pubd reported multiple published copies of URI %r, this makes no sense, blundering onwards", r_pdu.get("uri"))
+ else:
+ objects[r_pdu.get("uri")] = (r_pdu.get("hash"), repository)
+
+ def reconcile(uri, obj, repository):
+ h, r = objects.pop(uri, (None, None))
+ if h is not None:
+ assert r == repository
+ publisher.queue(uri = uri, new_obj = obj, old_hash = h, repository = repository)
+
+ for ca_detail in CADetail.objects.filter(ca__parent__tenant = self, state = "active"):
+ repository = ca_detail.ca.parent.repository
+ reconcile(uri = ca_detail.crl_uri, obj = ca_detail.latest_crl, repository = repository)
+ reconcile(uri = ca_detail.manifest_uri, obj = ca_detail.latest_manifest, repository = repository)
+ for c in ca_detail.child_certs.all():
+ reconcile(uri = c.uri, obj = c.cert, repository = repository)
+ for r in ca_detail.roas.filter(roa__isnull = False):
+ reconcile(uri = r.uri, obj = r.roa, repository = repository)
+ for g in ca_detail.ghostbusters.all():
+ reconcile(uri = g.uri, obj = g.ghostbuster, repository = repository)
+ for c in ca_detail.ee_certificates.all():
+ reconcile(uri = c.uri, obj = c.cert, repository = repository)
+ for u in objects:
+ h, r = objects[u]
+ publisher.queue(uri = u, old_hash = h, repository = r)
- xml_template = XMLTemplate(
- name = "repository",
- handles = (BSC,),
- attributes = ("peer_contact_uri", "rrdp_notification_uri"),
- elements = ("bpki_cert", "bpki_glue"))
+ yield publisher.call_pubd()
- @tornado.gen.coroutine
- def xml_post_save_hook(self, rpkid, q_pdu):
- if q_pdu.get("clear_replay_protection"):
- self.clear_replay_protection()
+ @tornado.gen.coroutine
+ def serve_run_now(self, rpkid):
+ logger.debug("Forced immediate run of periodic actions for tenant %s[%r]", self.tenant_handle, self)
+ tasks = self.cron_tasks(rpkid)
+ rpkid.task_add(tasks)
+ futures = [task.wait() for task in tasks]
+ rpkid.task_run()
+ yield futures
- def clear_replay_protection(self):
- self.last_cms_timestamp = None
- self.save()
+ def cron_tasks(self, rpkid):
+ try:
+ return self._cron_tasks
+ except AttributeError:
+ self._cron_tasks = tuple(task(rpkid, self) for task in rpki.rpkid_tasks.task_classes)
+ return self._cron_tasks
- @tornado.gen.coroutine
- def call_pubd(self, rpkid, q_msg, handlers = {}, length_check = True): # pylint: disable=W0102
- """
- Send a message to publication daemon and return the response.
+ def find_covering_ca_details(self, resources):
+ """
+ Return all active CADetails for this <tenant/> which cover a
+ particular set of resources.
- As a convenience, attempting to send an empty message returns
- immediate success without sending anything.
+ If we expected there to be a large number of CADetails, we
+ could add index tables and write fancy SQL query to do this, but
+ for the expected common case where there are only one or two
+ active CADetails per <tenant/>, it's probably not worth it. In
+ any case, this is an optimization we can leave for later.
+ """
- handlers is a dict of handler functions to process the response
- PDUs. If the tag value in the response PDU appears in the dict,
- the associated handler is called to process the PDU. If no tag
- matches, a default handler is called to check for errors; a
- handler value of False suppresses calling of the default handler.
- """
+ return set(ca_detail
+ for ca_detail in CADetail.objects.filter(ca__parent__tenant = self, state = "active")
+ if ca_detail.covers(resources))
- if len(q_msg) == 0:
- return
- for q_pdu in q_msg:
- logger.info("Sending %r to pubd", q_pdu)
+@xml_hooks
+class BSC(models.Model):
+ bsc_handle = models.SlugField(max_length = 255)
+ private_key_id = RSAPrivateKeyField()
+ pkcs10_request = PKCS10Field()
+ hash_alg = EnumField(choices = ("sha256",), default = "sha256")
+ signing_cert = CertificateField(null = True)
+ signing_cert_crl = CRLField(null = True)
+ tenant = models.ForeignKey(Tenant, related_name = "bscs")
+ objects = XMLManager()
- q_der = rpki.publication.cms_msg().wrap(q_msg, self.bsc.private_key_id, self.bsc.signing_cert, self.bsc.signing_cert_crl)
+ class Meta: # pylint: disable=C1001,W0232
+ unique_together = ("tenant", "bsc_handle")
- http_request = tornado.httpclient.HTTPRequest(
- url = self.peer_contact_uri,
- method = "POST",
- body = q_der,
- headers = { "Content-Type" : rpki.publication.content_type })
+ xml_template = XMLTemplate(
+ name = "bsc",
+ elements = ("signing_cert", "signing_cert_crl"),
+ readonly = ("pkcs10_request",))
- http_response = yield rpkid.http_fetch(http_request)
- # Tornado already checked http_response.code for us
+ def xml_pre_save_hook(self, q_pdu):
+ # Handle key generation, only supports RSA with SHA-256 for now.
+ if q_pdu.get("generate_keypair"):
+ assert q_pdu.get("key_type") in (None, "rsa") and q_pdu.get("hash_alg") in (None, "sha256")
+ self.private_key_id = rpki.x509.RSA.generate(keylength = int(q_pdu.get("key_length", 2048)))
+ self.pkcs10_request = rpki.x509.PKCS10.create(keypair = self.private_key_id)
- content_type = http_response.headers.get("Content-Type")
- if content_type not in rpki.publication.allowed_content_types:
- raise rpki.exceptions.BadContentType("HTTP Content-Type %r, expected %r" % (rpki.publication.content_type, content_type))
+@xml_hooks
+class Repository(models.Model):
+ repository_handle = models.SlugField(max_length = 255)
+ peer_contact_uri = models.TextField(null = True)
+ rrdp_notification_uri = models.TextField(null = True)
+ bpki_cert = CertificateField(null = True)
+ bpki_glue = CertificateField(null = True)
+ last_cms_timestamp = SundialField(null = True)
+ bsc = models.ForeignKey(BSC, related_name = "repositories")
+ tenant = models.ForeignKey(Tenant, related_name = "repositories")
+ objects = XMLManager()
- r_der = http_response.body
- r_cms = rpki.publication.cms_msg(DER = r_der)
- r_msg = r_cms.unwrap((rpkid.bpki_ta, self.tenant.bpki_cert, self.tenant.bpki_glue, self.bpki_cert, self.bpki_glue))
- r_cms.check_replay_sql(self, self.peer_contact_uri)
+ class Meta: # pylint: disable=C1001,W0232
+ unique_together = ("tenant", "repository_handle")
- for r_pdu in r_msg:
- handler = handlers.get(r_pdu.get("tag"), rpki.publication.raise_if_error)
- if handler:
- logger.debug("Calling pubd handler %r", handler)
- handler(r_pdu)
+ xml_template = XMLTemplate(
+ name = "repository",
+ handles = (BSC,),
+ attributes = ("peer_contact_uri", "rrdp_notification_uri"),
+ elements = ("bpki_cert", "bpki_glue"))
- if length_check and len(q_msg) != len(r_msg):
- raise rpki.exceptions.BadPublicationReply("Wrong number of response PDUs from pubd: sent %r, got %r" % (q_msg, r_msg))
- raise tornado.gen.Return(r_msg)
+ @tornado.gen.coroutine
+ def xml_post_save_hook(self, rpkid, q_pdu):
+ if q_pdu.get("clear_replay_protection"):
+ self.clear_replay_protection()
-@xml_hooks
-class Parent(models.Model):
- parent_handle = models.SlugField(max_length = 255)
- bpki_cert = CertificateField(null = True)
- bpki_glue = CertificateField(null = True)
- peer_contact_uri = models.TextField(null = True)
- sia_base = models.TextField(null = True)
- sender_name = models.TextField(null = True)
- recipient_name = models.TextField(null = True)
- last_cms_timestamp = SundialField(null = True)
- tenant = models.ForeignKey(Tenant, related_name = "parents")
- bsc = models.ForeignKey(BSC, related_name = "parents")
- repository = models.ForeignKey(Repository, related_name = "parents")
- objects = XMLManager()
-
- class Meta: # pylint: disable=C1001,W0232
- unique_together = ("tenant", "parent_handle")
-
- xml_template = XMLTemplate(
- name = "parent",
- handles = (BSC, Repository),
- attributes = ("peer_contact_uri", "sia_base", "sender_name", "recipient_name"),
- elements = ("bpki_cert", "bpki_glue"))
-
-
- @tornado.gen.coroutine
- def xml_pre_delete_hook(self, rpkid):
- yield self.destroy(rpkid, delete_parent = False)
-
- @tornado.gen.coroutine
- def xml_post_save_hook(self, rpkid, q_pdu):
- if q_pdu.get("clear_replay_protection"):
- self.clear_replay_protection()
- futures = []
- if q_pdu.get("rekey"):
- futures.append(self.serve_rekey(rpkid))
- if q_pdu.get("revoke"):
- futures.append(self.serve_revoke(rpkid))
- if q_pdu.get("reissue"):
- futures.append(self.serve_reissue(rpkid))
- if q_pdu.get("revoke_forgotten"):
- futures.append(self.serve_revoke_forgotten(rpkid))
- yield futures
-
- @tornado.gen.coroutine
- def serve_rekey(self, rpkid):
- yield [ca.rekey() for ca in self.cas.all()]
-
- @tornado.gen.coroutine
- def serve_revoke(self, rpkid):
- yield [ca.revoke() for ca in self.cas.all()]
-
- @tornado.gen.coroutine
- def serve_reissue(self, rpkid):
- yield [ca.reissue() for ca in self.cas.all()]
-
- def clear_replay_protection(self):
- self.last_cms_timestamp = None
- self.save()
-
-
- @tornado.gen.coroutine
- def get_skis(self, rpkid):
- """
- Fetch SKIs that this parent thinks we have. In theory this should
- agree with our own database, but in practice stuff can happen, so
- sometimes we need to know what our parent thinks.
+ def clear_replay_protection(self):
+ self.last_cms_timestamp = None
+ self.save()
- Result is a dictionary with the resource class name as key and a
- set of SKIs as value.
- This, like everything else dealing with SKIs in the up-down
- protocol, is mis-named: we're really dealing with g(SKI) values,
- not raw SKI values. Sorry.
- """
+ @tornado.gen.coroutine
+ def call_pubd(self, rpkid, q_msg, handlers = {}, length_check = True): # pylint: disable=W0102
+ """
+ Send a message to publication daemon and return the response.
- r_msg = yield self.up_down_list_query(rpkid = rpkid)
+ As a convenience, attempting to send an empty message returns
+ immediate success without sending anything.
- ski_map = {}
+ handlers is a dict of handler functions to process the response
+ PDUs. If the tag value in the response PDU appears in the dict,
+ the associated handler is called to process the PDU. If no tag
+ matches, a default handler is called to check for errors; a
+ handler value of False suppresses calling of the default handler.
+ """
- for rc in r_msg.getiterator(rpki.up_down.tag_class):
- skis = set()
- for c in rc.getiterator(rpki.up_down.tag_certificate):
- skis.add(rpki.x509.X509(Base64 = c.text).gSKI())
- ski_map[rc.get("class_name")] = skis
+ if len(q_msg) == 0:
+ return
- raise tornado.gen.Return(ski_map)
+ for q_pdu in q_msg:
+ logger.info("Sending %r to pubd", q_pdu)
+ q_der = rpki.publication.cms_msg().wrap(q_msg, self.bsc.private_key_id, self.bsc.signing_cert, self.bsc.signing_cert_crl)
- @tornado.gen.coroutine
- def revoke_skis(self, rpkid, rc_name, skis_to_revoke):
- """
- Revoke a set of SKIs within a particular resource class.
- """
+ http_request = tornado.httpclient.HTTPRequest(
+ url = self.peer_contact_uri,
+ method = "POST",
+ body = q_der,
+ headers = { "Content-Type" : rpki.publication.content_type })
- for ski in skis_to_revoke:
- logger.debug("Asking parent %r to revoke class %r, g(SKI) %s", self, rc_name, ski)
- yield self.up_down_revoke_query(rpkid = rpkid, class_name = rc_name, ski = ski)
+ http_response = yield rpkid.http_fetch(http_request)
+ # Tornado already checked http_response.code for us
- @tornado.gen.coroutine
- def serve_revoke_forgotten(self, rpkid):
- """
- Handle a left-right revoke_forgotten action for this parent.
-
- This is a bit fiddly: we have to compare the result of an up-down
- list query with what we have locally and identify the SKIs of any
- certificates that have gone missing. This should never happen in
- ordinary operation, but can arise if we have somehow lost a
- private key, in which case there is nothing more we can do with
- the issued cert, so we have to clear it. As this really is not
- supposed to happen, we don't clear it automatically, instead we
- require an explicit trigger.
- """
+ content_type = http_response.headers.get("Content-Type")
- skis_from_parent = yield self.get_skis(rpkid)
- for rc_name, skis_to_revoke in skis_from_parent.iteritems():
- for ca_detail in CADetail.objects.filter(ca__parent = self).exclude(state = "revoked"):
- skis_to_revoke.discard(ca_detail.latest_ca_cert.gSKI())
- yield self.revoke_skis(rpkid, rc_name, skis_to_revoke)
+ if content_type not in rpki.publication.allowed_content_types:
+ raise rpki.exceptions.BadContentType("HTTP Content-Type %r, expected %r" % (rpki.publication.content_type, content_type))
+ r_der = http_response.body
+ r_cms = rpki.publication.cms_msg(DER = r_der)
+ r_msg = r_cms.unwrap((rpkid.bpki_ta, self.tenant.bpki_cert, self.tenant.bpki_glue, self.bpki_cert, self.bpki_glue))
+ r_cms.check_replay_sql(self, self.peer_contact_uri)
- @tornado.gen.coroutine
- def destroy(self, rpkid, delete_parent = True):
- """
- Delete all the CA stuff under this parent, and perhaps the parent
- itself.
- """
+ for r_pdu in r_msg:
+ handler = handlers.get(r_pdu.get("tag"), rpki.publication.raise_if_error)
+ if handler:
+ logger.debug("Calling pubd handler %r", handler)
+ handler(r_pdu)
- yield [ca.destroy(self) for ca in self.cas()]
- yield self.serve_revoke_forgotten(rpkid)
- if delete_parent:
- self.delete()
+ if length_check and len(q_msg) != len(r_msg):
+ raise rpki.exceptions.BadPublicationReply("Wrong number of response PDUs from pubd: sent %r, got %r" % (q_msg, r_msg))
+ raise tornado.gen.Return(r_msg)
- def _compose_up_down_query(self, query_type):
- return Element(rpki.up_down.tag_message, nsmap = rpki.up_down.nsmap, version = rpki.up_down.version,
- sender = self.sender_name, recipient = self.recipient_name, type = query_type)
+
+@xml_hooks
+class Parent(models.Model):
+ parent_handle = models.SlugField(max_length = 255)
+ bpki_cert = CertificateField(null = True)
+ bpki_glue = CertificateField(null = True)
+ peer_contact_uri = models.TextField(null = True)
+ sia_base = models.TextField(null = True)
+ sender_name = models.TextField(null = True)
+ recipient_name = models.TextField(null = True)
+ last_cms_timestamp = SundialField(null = True)
+ tenant = models.ForeignKey(Tenant, related_name = "parents")
+ bsc = models.ForeignKey(BSC, related_name = "parents")
+ repository = models.ForeignKey(Repository, related_name = "parents")
+ objects = XMLManager()
+
+ class Meta: # pylint: disable=C1001,W0232
+ unique_together = ("tenant", "parent_handle")
+
+ xml_template = XMLTemplate(
+ name = "parent",
+ handles = (BSC, Repository),
+ attributes = ("peer_contact_uri", "sia_base", "sender_name", "recipient_name"),
+ elements = ("bpki_cert", "bpki_glue"))
+
+
+ @tornado.gen.coroutine
+ def xml_pre_delete_hook(self, rpkid):
+ yield self.destroy(rpkid, delete_parent = False)
+
+ @tornado.gen.coroutine
+ def xml_post_save_hook(self, rpkid, q_pdu):
+ if q_pdu.get("clear_replay_protection"):
+ self.clear_replay_protection()
+ futures = []
+ if q_pdu.get("rekey"):
+ futures.append(self.serve_rekey(rpkid))
+ if q_pdu.get("revoke"):
+ futures.append(self.serve_revoke(rpkid))
+ if q_pdu.get("reissue"):
+ futures.append(self.serve_reissue(rpkid))
+ if q_pdu.get("revoke_forgotten"):
+ futures.append(self.serve_revoke_forgotten(rpkid))
+ yield futures
+
+ @tornado.gen.coroutine
+ def serve_rekey(self, rpkid):
+ yield [ca.rekey() for ca in self.cas.all()]
+
+ @tornado.gen.coroutine
+ def serve_revoke(self, rpkid):
+ yield [ca.revoke() for ca in self.cas.all()]
+
+ @tornado.gen.coroutine
+ def serve_reissue(self, rpkid):
+ yield [ca.reissue() for ca in self.cas.all()]
+
+ def clear_replay_protection(self):
+ self.last_cms_timestamp = None
+ self.save()
+
+
+ @tornado.gen.coroutine
+ def get_skis(self, rpkid):
+ """
+ Fetch SKIs that this parent thinks we have. In theory this should
+ agree with our own database, but in practice stuff can happen, so
+ sometimes we need to know what our parent thinks.
+
+ Result is a dictionary with the resource class name as key and a
+ set of SKIs as value.
+
+ This, like everything else dealing with SKIs in the up-down
+ protocol, is mis-named: we're really dealing with g(SKI) values,
+ not raw SKI values. Sorry.
+ """
+
+ r_msg = yield self.up_down_list_query(rpkid = rpkid)
+
+ ski_map = {}
+
+ for rc in r_msg.getiterator(rpki.up_down.tag_class):
+ skis = set()
+ for c in rc.getiterator(rpki.up_down.tag_certificate):
+ skis.add(rpki.x509.X509(Base64 = c.text).gSKI())
+ ski_map[rc.get("class_name")] = skis
+
+ raise tornado.gen.Return(ski_map)
+
+
+ @tornado.gen.coroutine
+ def revoke_skis(self, rpkid, rc_name, skis_to_revoke):
+ """
+ Revoke a set of SKIs within a particular resource class.
+ """
+
+ for ski in skis_to_revoke:
+ logger.debug("Asking parent %r to revoke class %r, g(SKI) %s", self, rc_name, ski)
+ yield self.up_down_revoke_query(rpkid = rpkid, class_name = rc_name, ski = ski)
+
+
+ @tornado.gen.coroutine
+ def serve_revoke_forgotten(self, rpkid):
+ """
+ Handle a left-right revoke_forgotten action for this parent.
+
+ This is a bit fiddly: we have to compare the result of an up-down
+ list query with what we have locally and identify the SKIs of any
+ certificates that have gone missing. This should never happen in
+ ordinary operation, but can arise if we have somehow lost a
+ private key, in which case there is nothing more we can do with
+ the issued cert, so we have to clear it. As this really is not
+ supposed to happen, we don't clear it automatically, instead we
+ require an explicit trigger.
+ """
+
+ skis_from_parent = yield self.get_skis(rpkid)
+ for rc_name, skis_to_revoke in skis_from_parent.iteritems():
+ for ca_detail in CADetail.objects.filter(ca__parent = self).exclude(state = "revoked"):
+ skis_to_revoke.discard(ca_detail.latest_ca_cert.gSKI())
+ yield self.revoke_skis(rpkid, rc_name, skis_to_revoke)
+
+
+ @tornado.gen.coroutine
+ def destroy(self, rpkid, delete_parent = True):
+ """
+ Delete all the CA stuff under this parent, and perhaps the parent
+ itself.
+ """
+
+ yield [ca.destroy(self) for ca in self.cas()]
+ yield self.serve_revoke_forgotten(rpkid)
+ if delete_parent:
+ self.delete()
- @tornado.gen.coroutine
- def up_down_list_query(self, rpkid):
- q_msg = self._compose_up_down_query("list")
- r_msg = yield self.query_up_down(rpkid, q_msg)
- raise tornado.gen.Return(r_msg)
+ def _compose_up_down_query(self, query_type):
+ return Element(rpki.up_down.tag_message, nsmap = rpki.up_down.nsmap, version = rpki.up_down.version,
+ sender = self.sender_name, recipient = self.recipient_name, type = query_type)
- @tornado.gen.coroutine
- def up_down_issue_query(self, rpkid, ca, ca_detail):
- logger.debug("Parent.up_down_issue_query(): caRepository %r rpkiManifest %r rpkiNotify %r",
- ca.sia_uri, ca_detail.manifest_uri, ca.parent.repository.rrdp_notification_uri)
- pkcs10 = rpki.x509.PKCS10.create(
- keypair = ca_detail.private_key_id,
- is_ca = True,
- caRepository = ca.sia_uri,
- rpkiManifest = ca_detail.manifest_uri,
- rpkiNotify = ca.parent.repository.rrdp_notification_uri)
- q_msg = self._compose_up_down_query("issue")
- q_pdu = SubElement(q_msg, rpki.up_down.tag_request, class_name = ca.parent_resource_class)
- q_pdu.text = pkcs10.get_Base64()
- r_msg = yield self.query_up_down(rpkid, q_msg)
- raise tornado.gen.Return(r_msg)
+ @tornado.gen.coroutine
+ def up_down_list_query(self, rpkid):
+ q_msg = self._compose_up_down_query("list")
+ r_msg = yield self.query_up_down(rpkid, q_msg)
+ raise tornado.gen.Return(r_msg)
- @tornado.gen.coroutine
- def up_down_revoke_query(self, rpkid, class_name, ski):
- q_msg = self._compose_up_down_query("revoke")
- SubElement(q_msg, rpki.up_down.tag_key, class_name = class_name, ski = ski)
- r_msg = yield self.query_up_down(rpkid, q_msg)
- raise tornado.gen.Return(r_msg)
+ @tornado.gen.coroutine
+ def up_down_issue_query(self, rpkid, ca, ca_detail):
+ logger.debug("Parent.up_down_issue_query(): caRepository %r rpkiManifest %r rpkiNotify %r",
+ ca.sia_uri, ca_detail.manifest_uri, ca.parent.repository.rrdp_notification_uri)
+ pkcs10 = rpki.x509.PKCS10.create(
+ keypair = ca_detail.private_key_id,
+ is_ca = True,
+ caRepository = ca.sia_uri,
+ rpkiManifest = ca_detail.manifest_uri,
+ rpkiNotify = ca.parent.repository.rrdp_notification_uri)
+ q_msg = self._compose_up_down_query("issue")
+ q_pdu = SubElement(q_msg, rpki.up_down.tag_request, class_name = ca.parent_resource_class)
+ q_pdu.text = pkcs10.get_Base64()
+ r_msg = yield self.query_up_down(rpkid, q_msg)
+ raise tornado.gen.Return(r_msg)
- @tornado.gen.coroutine
- def query_up_down(self, rpkid, q_msg):
+ @tornado.gen.coroutine
+ def up_down_revoke_query(self, rpkid, class_name, ski):
+ q_msg = self._compose_up_down_query("revoke")
+ SubElement(q_msg, rpki.up_down.tag_key, class_name = class_name, ski = ski)
+ r_msg = yield self.query_up_down(rpkid, q_msg)
+ raise tornado.gen.Return(r_msg)
- if self.bsc is None:
- raise rpki.exceptions.BSCNotFound("Could not find BSC")
- if self.bsc.signing_cert is None:
- raise rpki.exceptions.BSCNotReady("BSC %r is not yet usable" % self.bsc.bsc_handle)
+ @tornado.gen.coroutine
+ def query_up_down(self, rpkid, q_msg):
- q_der = rpki.up_down.cms_msg().wrap(q_msg, self.bsc.private_key_id, self.bsc.signing_cert, self.bsc.signing_cert_crl)
+ if self.bsc is None:
+ raise rpki.exceptions.BSCNotFound("Could not find BSC")
- http_request = tornado.httpclient.HTTPRequest(
- url = self.peer_contact_uri,
- method = "POST",
- body = q_der,
- headers = { "Content-Type" : rpki.up_down.content_type })
+ if self.bsc.signing_cert is None:
+ raise rpki.exceptions.BSCNotReady("BSC %r is not yet usable" % self.bsc.bsc_handle)
- http_response = yield rpkid.http_fetch(http_request)
+ q_der = rpki.up_down.cms_msg().wrap(q_msg, self.bsc.private_key_id, self.bsc.signing_cert, self.bsc.signing_cert_crl)
- # Tornado already checked http_response.code for us
+ http_request = tornado.httpclient.HTTPRequest(
+ url = self.peer_contact_uri,
+ method = "POST",
+ body = q_der,
+ headers = { "Content-Type" : rpki.up_down.content_type })
- content_type = http_response.headers.get("Content-Type")
+ http_response = yield rpkid.http_fetch(http_request)
+
+ # Tornado already checked http_response.code for us
- if content_type not in rpki.up_down.allowed_content_types:
- raise rpki.exceptions.BadContentType("HTTP Content-Type %r, expected %r" % (rpki.up_down.content_type, content_type))
+ content_type = http_response.headers.get("Content-Type")
- r_der = http_response.body
- r_cms = rpki.up_down.cms_msg(DER = r_der)
- r_msg = r_cms.unwrap((rpkid.bpki_ta, self.tenant.bpki_cert, self.tenant.bpki_glue, self.bpki_cert, self.bpki_glue))
- r_cms.check_replay_sql(self, self.peer_contact_uri)
- rpki.up_down.check_response(r_msg, q_msg.get("type"))
+ if content_type not in rpki.up_down.allowed_content_types:
+ raise rpki.exceptions.BadContentType("HTTP Content-Type %r, expected %r" % (rpki.up_down.content_type, content_type))
- raise tornado.gen.Return(r_msg)
+ r_der = http_response.body
+ r_cms = rpki.up_down.cms_msg(DER = r_der)
+ r_msg = r_cms.unwrap((rpkid.bpki_ta, self.tenant.bpki_cert, self.tenant.bpki_glue, self.bpki_cert, self.bpki_glue))
+ r_cms.check_replay_sql(self, self.peer_contact_uri)
+ rpki.up_down.check_response(r_msg, q_msg.get("type"))
+ raise tornado.gen.Return(r_msg)
- def construct_sia_uri(self, rc):
- """
- Construct the sia_uri value for a CA under this parent given
- configured information and the parent's up-down protocol
- list_response PDU.
- """
- sia_uri = rc.get("suggested_sia_head", "")
- if not sia_uri.startswith("rsync://") or not sia_uri.startswith(self.sia_base):
- sia_uri = self.sia_base
- if not sia_uri.endswith("/"):
- raise rpki.exceptions.BadURISyntax("SIA URI must end with a slash: %s" % sia_uri)
- return sia_uri
+ def construct_sia_uri(self, rc):
+ """
+ Construct the sia_uri value for a CA under this parent given
+ configured information and the parent's up-down protocol
+ list_response PDU.
+ """
+
+ sia_uri = rc.get("suggested_sia_head", "")
+ if not sia_uri.startswith("rsync://") or not sia_uri.startswith(self.sia_base):
+ sia_uri = self.sia_base
+ if not sia_uri.endswith("/"):
+ raise rpki.exceptions.BadURISyntax("SIA URI must end with a slash: %s" % sia_uri)
+ return sia_uri
class CA(models.Model):
- last_crl_sn = models.BigIntegerField(default = 1)
- last_manifest_sn = models.BigIntegerField(default = 1)
- next_manifest_update = SundialField(null = True)
- next_crl_update = SundialField(null = True)
- last_issued_sn = models.BigIntegerField(default = 1)
- sia_uri = models.TextField(null = True)
- parent_resource_class = models.TextField(null = True) # Not sure this should allow NULL
- parent = models.ForeignKey(Parent, related_name = "cas")
-
- # So it turns out that there's always a 1:1 mapping between the
- # class_name we receive from our parent and the class_name we issue
- # to our children: in spite of the obfuscated way that we used to
- # handle class names, we never actually added a way for the back-end
- # to create new classes. Not clear we want to encourage this, but
- # if we wanted to support it, simple approach would probably be an
- # optional class_name attribute in the left-right <list_resources/>
- # response; if not present, we'd use parent's class_name as now,
- # otherwise we'd use the supplied class_name.
-
- # ca_obj has a zillion properties encoding various specialized
- # ca_detail queries. ORM query syntax probably renders this OBE,
- # but need to translate in existing code.
- #
- #def pending_ca_details(self): return self.ca_details.filter(state = "pending")
- #def active_ca_detail(self): return self.ca_details.get(state = "active")
- #def deprecated_ca_details(self): return self.ca_details.filter(state = "deprecated")
- #def active_or_deprecated_ca_details(self): return self.ca_details.filter(state__in = ("active", "deprecated"))
- #def revoked_ca_details(self): return self.ca_details.filter(state = "revoked")
- #def issue_response_candidate_ca_details(self): return self.ca_details.exclude(state = "revoked")
-
-
- @tornado.gen.coroutine
- def check_for_updates(self, rpkid, parent, rc):
- """
- Parent has signaled continued existance of a resource class we
- already knew about, so we need to check for an updated
- certificate, changes in resource coverage, revocation and reissue
- with the same key, etc.
- """
-
- logger.debug("check_for_updates()")
- sia_uri = parent.construct_sia_uri(rc)
- sia_uri_changed = self.sia_uri != sia_uri
-
- if sia_uri_changed:
- logger.debug("SIA changed: was %s now %s", self.sia_uri, sia_uri)
- self.sia_uri = sia_uri
+ last_crl_sn = models.BigIntegerField(default = 1)
+ last_manifest_sn = models.BigIntegerField(default = 1)
+ next_manifest_update = SundialField(null = True)
+ next_crl_update = SundialField(null = True)
+ last_issued_sn = models.BigIntegerField(default = 1)
+ sia_uri = models.TextField(null = True)
+ parent_resource_class = models.TextField(null = True) # Not sure this should allow NULL
+ parent = models.ForeignKey(Parent, related_name = "cas")
+
+ # So it turns out that there's always a 1:1 mapping between the
+ # class_name we receive from our parent and the class_name we issue
+ # to our children: in spite of the obfuscated way that we used to
+ # handle class names, we never actually added a way for the back-end
+ # to create new classes. Not clear we want to encourage this, but
+ # if we wanted to support it, simple approach would probably be an
+ # optional class_name attribute in the left-right <list_resources/>
+ # response; if not present, we'd use parent's class_name as now,
+ # otherwise we'd use the supplied class_name.
+
+ # ca_obj has a zillion properties encoding various specialized
+ # ca_detail queries. ORM query syntax probably renders this OBE,
+ # but need to translate in existing code.
+ #
+ #def pending_ca_details(self): return self.ca_details.filter(state = "pending")
+ #def active_ca_detail(self): return self.ca_details.get(state = "active")
+ #def deprecated_ca_details(self): return self.ca_details.filter(state = "deprecated")
+ #def active_or_deprecated_ca_details(self): return self.ca_details.filter(state__in = ("active", "deprecated"))
+ #def revoked_ca_details(self): return self.ca_details.filter(state = "revoked")
+ #def issue_response_candidate_ca_details(self): return self.ca_details.exclude(state = "revoked")
+
+
+ @tornado.gen.coroutine
+ def check_for_updates(self, rpkid, parent, rc):
+ """
+ Parent has signaled continued existance of a resource class we
+ already knew about, so we need to check for an updated
+ certificate, changes in resource coverage, revocation and reissue
+ with the same key, etc.
+ """
+
+ logger.debug("check_for_updates()")
+ sia_uri = parent.construct_sia_uri(rc)
+ sia_uri_changed = self.sia_uri != sia_uri
+
+ if sia_uri_changed:
+ logger.debug("SIA changed: was %s now %s", self.sia_uri, sia_uri)
+ self.sia_uri = sia_uri
+
+ class_name = rc.get("class_name")
+
+ rc_resources = rpki.resource_set.resource_bag(
+ rc.get("resource_set_as"),
+ rc.get("resource_set_ipv4"),
+ rc.get("resource_set_ipv6"),
+ rc.get("resource_set_notafter"))
+
+ cert_map = {}
+
+ for c in rc.getiterator(rpki.up_down.tag_certificate):
+ x = rpki.x509.X509(Base64 = c.text)
+ u = rpki.up_down.multi_uri(c.get("cert_url")).rsync()
+ cert_map[x.gSKI()] = (x, u)
+
+ ca_details = self.ca_details.exclude(state = "revoked")
+
+ if not ca_details:
+ logger.warning("Existing resource class %s to %s from %s with no certificates, rekeying",
+ class_name, parent.tenant.tenant_handle, parent.parent_handle)
+ yield self.rekey(rpkid)
+ return
+
+ for ca_detail in ca_details:
+
+ rc_cert, rc_cert_uri = cert_map.pop(ca_detail.public_key.gSKI(), (None, None))
+
+ if rc_cert is None:
+ logger.warning("g(SKI) %s in resource class %s is in database but missing from list_response to %s from %s, "
+ "maybe parent certificate went away?",
+ ca_detail.public_key.gSKI(), class_name, parent.tenant.tenant_handle, parent.parent_handle)
+ publisher = rpki.rpkid.publication_queue(rpkid)
+ ca_detail.destroy(ca = ca_detail.ca, publisher = publisher)
+ yield publisher.call_pubd()
+ continue
+
+ if ca_detail.state == "active" and ca_detail.ca_cert_uri != rc_cert_uri:
+ logger.debug("AIA changed: was %s now %s", ca_detail.ca_cert_uri, rc_cert_uri)
+ ca_detail.ca_cert_uri = rc_cert_uri
+ ca_detail.save()
+
+ if ca_detail.state not in ("pending", "active"):
+ continue
+
+ if ca_detail.state == "pending":
+ current_resources = rpki.resource_set.resource_bag()
+ else:
+ current_resources = ca_detail.latest_ca_cert.get_3779resources()
+
+ if (ca_detail.state == "pending" or
+ sia_uri_changed or
+ ca_detail.latest_ca_cert != rc_cert or
+ ca_detail.latest_ca_cert.getNotAfter() != rc_resources.valid_until or
+ current_resources.undersized(rc_resources) or
+ current_resources.oversized(rc_resources)):
+
+ yield ca_detail.update(
+ rpkid = rpkid,
+ parent = parent,
+ ca = self,
+ rc = rc,
+ sia_uri_changed = sia_uri_changed,
+ old_resources = current_resources)
+
+ if cert_map:
+ logger.warning("Unknown certificate g(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(cert_map), class_name, parent.tenant.tenant_handle, parent.parent_handle)
+
+
+ # Called from exactly one place, in rpki.rpkid_tasks.PollParentTask.class_loop().
+ # Might want to refactor.
+
+ @classmethod
+ @tornado.gen.coroutine
+ def create(cls, rpkid, parent, rc):
+ """
+ Parent has signaled existance of a new resource class, so we need
+ to create and set up a corresponding CA object.
+ """
- class_name = rc.get("class_name")
+ self = cls.objects.create(parent = parent,
+ parent_resource_class = rc.get("class_name"),
+ sia_uri = parent.construct_sia_uri(rc))
+
+ ca_detail = CADetail.create(self)
+
+ logger.debug("Sending issue request to %r from %r", parent, self.create)
- rc_resources = rpki.resource_set.resource_bag(
- rc.get("resource_set_as"),
- rc.get("resource_set_ipv4"),
- rc.get("resource_set_ipv6"),
- rc.get("resource_set_notafter"))
+ r_msg = yield parent.up_down_issue_query(rpkid = rpkid, ca = self, ca_detail = ca_detail)
- cert_map = {}
+ c = r_msg[0][0]
- for c in rc.getiterator(rpki.up_down.tag_certificate):
- x = rpki.x509.X509(Base64 = c.text)
- u = rpki.up_down.multi_uri(c.get("cert_url")).rsync()
- cert_map[x.gSKI()] = (x, u)
+ logger.debug("CA %r received certificate %s", self, c.get("cert_url"))
- ca_details = self.ca_details.exclude(state = "revoked")
+ yield ca_detail.activate(
+ rpkid = rpkid,
+ ca = self,
+ cert = rpki.x509.X509(Base64 = c.text),
+ uri = c.get("cert_url"))
- if not ca_details:
- logger.warning("Existing resource class %s to %s from %s with no certificates, rekeying",
- class_name, parent.tenant.tenant_handle, parent.parent_handle)
- yield self.rekey(rpkid)
- return
- for ca_detail in ca_details:
+ @tornado.gen.coroutine
+ def destroy(self, rpkid, parent):
+ """
+ The list of current resource classes received from parent does not
+ include the class corresponding to this CA, so we need to delete
+ it (and its little dog too...).
- rc_cert, rc_cert_uri = cert_map.pop(ca_detail.public_key.gSKI(), (None, None))
+ All certs published by this CA are now invalid, so need to
+ withdraw them, the CRL, and the manifest from the repository,
+ delete all child_cert and ca_detail records associated with this
+ CA, then finally delete this CA itself.
+ """
- if rc_cert is None:
- logger.warning("g(SKI) %s in resource class %s is in database but missing from list_response to %s from %s, "
- "maybe parent certificate went away?",
- ca_detail.public_key.gSKI(), class_name, parent.tenant.tenant_handle, parent.parent_handle)
publisher = rpki.rpkid.publication_queue(rpkid)
- ca_detail.destroy(ca = ca_detail.ca, publisher = publisher)
- yield publisher.call_pubd()
- continue
-
- if ca_detail.state == "active" and ca_detail.ca_cert_uri != rc_cert_uri:
- logger.debug("AIA changed: was %s now %s", ca_detail.ca_cert_uri, rc_cert_uri)
- ca_detail.ca_cert_uri = rc_cert_uri
- ca_detail.save()
-
- if ca_detail.state not in ("pending", "active"):
- continue
-
- if ca_detail.state == "pending":
- current_resources = rpki.resource_set.resource_bag()
- else:
- current_resources = ca_detail.latest_ca_cert.get_3779resources()
-
- if (ca_detail.state == "pending" or
- sia_uri_changed or
- ca_detail.latest_ca_cert != rc_cert or
- ca_detail.latest_ca_cert.getNotAfter() != rc_resources.valid_until or
- current_resources.undersized(rc_resources) or
- current_resources.oversized(rc_resources)):
-
- yield ca_detail.update(
- rpkid = rpkid,
- parent = parent,
- ca = self,
- rc = rc,
- sia_uri_changed = sia_uri_changed,
- old_resources = current_resources)
-
- if cert_map:
- logger.warning("Unknown certificate g(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(cert_map), class_name, parent.tenant.tenant_handle, parent.parent_handle)
-
-
- # Called from exactly one place, in rpki.rpkid_tasks.PollParentTask.class_loop().
- # Might want to refactor.
-
- @classmethod
- @tornado.gen.coroutine
- def create(cls, rpkid, parent, rc):
- """
- Parent has signaled existance of a new resource class, so we need
- to create and set up a corresponding CA object.
- """
- self = cls.objects.create(parent = parent,
- parent_resource_class = rc.get("class_name"),
- sia_uri = parent.construct_sia_uri(rc))
+ for ca_detail in self.ca_details.all():
+ ca_detail.destroy(ca = self, publisher = publisher, allow_failure = True)
- ca_detail = CADetail.create(self)
+ try:
+ yield publisher.call_pubd()
- logger.debug("Sending issue request to %r from %r", parent, self.create)
+ except:
+ logger.exception("Could not delete CA %r, skipping", self)
- r_msg = yield parent.up_down_issue_query(rpkid = rpkid, ca = self, ca_detail = ca_detail)
+ else:
+ logger.debug("Deleting %r", self)
+ self.delete()
- c = r_msg[0][0]
- logger.debug("CA %r received certificate %s", self, c.get("cert_url"))
+ def next_serial_number(self):
+ """
+ Allocate a certificate serial number.
+ """
- yield ca_detail.activate(
- rpkid = rpkid,
- ca = self,
- cert = rpki.x509.X509(Base64 = c.text),
- uri = c.get("cert_url"))
+ self.last_issued_sn += 1
+ self.save()
+ return self.last_issued_sn
- @tornado.gen.coroutine
- def destroy(self, rpkid, parent):
- """
- The list of current resource classes received from parent does not
- include the class corresponding to this CA, so we need to delete
- it (and its little dog too...).
-
- All certs published by this CA are now invalid, so need to
- withdraw them, the CRL, and the manifest from the repository,
- delete all child_cert and ca_detail records associated with this
- CA, then finally delete this CA itself.
- """
+ def next_manifest_number(self):
+ """
+ Allocate a manifest serial number.
+ """
- publisher = rpki.rpkid.publication_queue(rpkid)
+ self.last_manifest_sn += 1
+ self.save()
+ return self.last_manifest_sn
- for ca_detail in self.ca_details.all():
- ca_detail.destroy(ca = self, publisher = publisher, allow_failure = True)
- try:
- yield publisher.call_pubd()
+ def next_crl_number(self):
+ """
+ Allocate a CRL serial number.
+ """
- except:
- logger.exception("Could not delete CA %r, skipping", self)
+ self.last_crl_sn += 1
+ self.save()
+ return self.last_crl_sn
- else:
- logger.debug("Deleting %r", self)
- self.delete()
+ @tornado.gen.coroutine
+ def rekey(self, rpkid):
+ """
+ Initiate a rekey operation for this CA. Generate a new keypair.
+ Request cert from parent using new keypair. Mark result as our
+ active ca_detail. Reissue all child certs issued by this CA using
+ the new ca_detail.
+ """
- def next_serial_number(self):
- """
- Allocate a certificate serial number.
- """
+ try:
+ old_detail = self.ca_details.get(state = "active")
+ except CADetail.DoesNotExist:
+ old_detail = None
- self.last_issued_sn += 1
- self.save()
- return self.last_issued_sn
+ new_detail = CADetail.create(ca = self) # sic: class method, not manager function (for now, anyway)
+ logger.debug("Sending issue request to %r from %r", self.parent, self.rekey)
- def next_manifest_number(self):
- """
- Allocate a manifest serial number.
- """
+ r_msg = yield self.parent.up_down_issue_query(rpkid = rpkid, ca = self, ca_detail = new_detail)
- self.last_manifest_sn += 1
- self.save()
- return self.last_manifest_sn
+ c = r_msg[0][0]
+ logger.debug("CA %r received certificate %s", self, c.get("cert_url"))
- def next_crl_number(self):
- """
- Allocate a CRL serial number.
- """
+ yield new_detail.activate(
+ rpkid = rpkid,
+ ca = self,
+ cert = rpki.x509.X509(Base64 = c.text),
+ uri = c.get("cert_url"),
+ predecessor = old_detail)
- self.last_crl_sn += 1
- self.save()
- return self.last_crl_sn
+ @tornado.gen.coroutine
+ def revoke(self, revoke_all = False):
+ """
+ Revoke deprecated ca_detail objects associated with this CA, or
+ all ca_details associated with this CA if revoke_all is set.
+ """
- @tornado.gen.coroutine
- def rekey(self, rpkid):
- """
- Initiate a rekey operation for this CA. Generate a new keypair.
- Request cert from parent using new keypair. Mark result as our
- active ca_detail. Reissue all child certs issued by this CA using
- the new ca_detail.
- """
+ if revoke_all:
+ ca_details = self.ca_details.all()
+ else:
+ ca_details = self.ca_details.filter(state = "deprecated")
- try:
- old_detail = self.ca_details.get(state = "active")
- except CADetail.DoesNotExist:
- old_detail = None
+ yield [ca_detail.revoke() for ca_detail in ca_details]
- new_detail = CADetail.create(ca = self) # sic: class method, not manager function (for now, anyway)
- logger.debug("Sending issue request to %r from %r", self.parent, self.rekey)
+ @tornado.gen.coroutine
+ def reissue(self):
+ """
+ Reissue all current certificates issued by this CA.
+ """
- r_msg = yield self.parent.up_down_issue_query(rpkid = rpkid, ca = self, ca_detail = new_detail)
+ ca_detail = self.ca_details.get(state = "active")
+ if ca_detail:
+ yield ca_detail.reissue()
- c = r_msg[0][0]
- logger.debug("CA %r received certificate %s", self, c.get("cert_url"))
+class CADetail(models.Model):
+ public_key = PublicKeyField(null = True)
+ private_key_id = RSAPrivateKeyField(null = True)
+ latest_crl = CRLField(null = True)
+ crl_published = SundialField(null = True)
+ latest_ca_cert = CertificateField(null = True)
+ manifest_private_key_id = RSAPrivateKeyField(null = True)
+ manifest_public_key = PublicKeyField(null = True)
+ latest_manifest_cert = CertificateField(null = True)
+ latest_manifest = ManifestField(null = True)
+ manifest_published = SundialField(null = True)
+ state = EnumField(choices = ("pending", "active", "deprecated", "revoked"))
+ ca_cert_uri = models.TextField(null = True)
+ ca = models.ForeignKey(CA, related_name = "ca_details")
- yield new_detail.activate(
- rpkid = rpkid,
- ca = self,
- cert = rpki.x509.X509(Base64 = c.text),
- uri = c.get("cert_url"),
- predecessor = old_detail)
+ # Like the old ca_obj class, the old ca_detail_obj class had ten
+ # zillion properties and methods encapsulating SQL queries.
+ # Translate as we go.
- @tornado.gen.coroutine
- def revoke(self, revoke_all = False):
- """
- Revoke deprecated ca_detail objects associated with this CA, or
- all ca_details associated with this CA if revoke_all is set.
- """
- if revoke_all:
- ca_details = self.ca_details.all()
- else:
- ca_details = self.ca_details.filter(state = "deprecated")
+ @property
+ def crl_uri(self):
+ """
+ Return publication URI for this ca_detail's CRL.
+ """
- yield [ca_detail.revoke() for ca_detail in ca_details]
+ return self.ca.sia_uri + self.crl_uri_tail
- @tornado.gen.coroutine
- def reissue(self):
- """
- Reissue all current certificates issued by this CA.
- """
+ @property
+ def crl_uri_tail(self):
+ """
+ Return tail (filename portion) of publication URI for this ca_detail's CRL.
+ """
- ca_detail = self.ca_details.get(state = "active")
- if ca_detail:
- yield ca_detail.reissue()
+ return self.public_key.gSKI() + ".crl"
-class CADetail(models.Model):
- public_key = PublicKeyField(null = True)
- private_key_id = RSAPrivateKeyField(null = True)
- latest_crl = CRLField(null = True)
- crl_published = SundialField(null = True)
- latest_ca_cert = CertificateField(null = True)
- manifest_private_key_id = RSAPrivateKeyField(null = True)
- manifest_public_key = PublicKeyField(null = True)
- latest_manifest_cert = CertificateField(null = True)
- latest_manifest = ManifestField(null = True)
- manifest_published = SundialField(null = True)
- state = EnumField(choices = ("pending", "active", "deprecated", "revoked"))
- ca_cert_uri = models.TextField(null = True)
- ca = models.ForeignKey(CA, related_name = "ca_details")
-
-
- # Like the old ca_obj class, the old ca_detail_obj class had ten
- # zillion properties and methods encapsulating SQL queries.
- # Translate as we go.
-
-
- @property
- def crl_uri(self):
- """
- Return publication URI for this ca_detail's CRL.
- """
+ @property
+ def manifest_uri(self):
+ """
+ Return publication URI for this ca_detail's manifest.
+ """
- return self.ca.sia_uri + self.crl_uri_tail
+ return self.ca.sia_uri + self.public_key.gSKI() + ".mft"
- @property
- def crl_uri_tail(self):
- """
- Return tail (filename portion) of publication URI for this ca_detail's CRL.
- """
+ def has_expired(self):
+ """
+ Return whether this ca_detail's certificate has expired.
+ """
- return self.public_key.gSKI() + ".crl"
+ return self.latest_ca_cert.getNotAfter() <= rpki.sundial.now()
- @property
- def manifest_uri(self):
- """
- Return publication URI for this ca_detail's manifest.
- """
+ def covers(self, target):
+ """
+ Test whether this ca-detail covers a given set of resources.
+ """
- return self.ca.sia_uri + self.public_key.gSKI() + ".mft"
+ 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 has_expired(self):
- """
- Return whether this ca_detail's certificate has expired.
- """
+ @tornado.gen.coroutine
+ def activate(self, rpkid, ca, cert, uri, predecessor = None):
+ """
+ Activate this ca_detail.
+ """
- return self.latest_ca_cert.getNotAfter() <= rpki.sundial.now()
+ publisher = rpki.rpkid.publication_queue(rpkid)
+ self.latest_ca_cert = cert
+ self.ca_cert_uri = uri
+ self.generate_manifest_cert()
+ self.state = "active"
+ self.generate_crl(publisher = publisher)
+ self.generate_manifest(publisher = publisher)
+ self.save()
+
+ if predecessor is not None:
+ predecessor.state = "deprecated"
+ predecessor.save()
+ for child_cert in predecessor.child_certs.all():
+ child_cert.reissue(ca_detail = self, publisher = publisher)
+ for roa in predecessor.roas.all():
+ roa.regenerate(publisher = publisher)
+ for ghostbuster in predecessor.ghostbusters.all():
+ ghostbuster.regenerate(publisher = publisher)
+ predecessor.generate_crl(publisher = publisher)
+ predecessor.generate_manifest(publisher = publisher)
+ yield publisher.call_pubd()
- 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 destroy(self, ca, publisher, allow_failure = False):
+ """
+ Delete this ca_detail and all of the certs it issued.
+ If allow_failure is true, we clean up as much as we can but don't
+ raise an exception.
+ """
- @tornado.gen.coroutine
- def activate(self, rpkid, ca, cert, uri, predecessor = None):
- """
- Activate this ca_detail.
- """
+ repository = ca.parent.repository
+ handler = False if allow_failure else None
+ for child_cert in self.child_certs.all():
+ publisher.queue(uri = child_cert.uri, old_obj = child_cert.cert, repository = repository, handler = handler)
+ child_cert.delete()
+ for roa in self.roas.all():
+ roa.revoke(publisher = publisher, allow_failure = allow_failure, fast = True)
+ for ghostbuster in self.ghostbusters.all():
+ ghostbuster.revoke(publisher = publisher, allow_failure = allow_failure, fast = True)
+ if self.latest_manifest is not None:
+ publisher.queue(uri = self.manifest_uri, old_obj = self.latest_manifest, repository = repository, handler = handler)
+ if self.latest_crl is not None:
+ publisher.queue(uri = self.crl_uri, old_obj = self.latest_crl, repository = repository, handler = handler)
+ for cert in self.revoked_certs.all(): # + self.child_certs.all()
+ logger.debug("Deleting %r", cert)
+ cert.delete()
+ logger.debug("Deleting %r", self)
+ self.delete()
- publisher = rpki.rpkid.publication_queue(rpkid)
- self.latest_ca_cert = cert
- self.ca_cert_uri = uri
- self.generate_manifest_cert()
- self.state = "active"
- self.generate_crl(publisher = publisher)
- self.generate_manifest(publisher = publisher)
- self.save()
-
- if predecessor is not None:
- predecessor.state = "deprecated"
- predecessor.save()
- for child_cert in predecessor.child_certs.all():
- child_cert.reissue(ca_detail = self, publisher = publisher)
- for roa in predecessor.roas.all():
- roa.regenerate(publisher = publisher)
- for ghostbuster in predecessor.ghostbusters.all():
- ghostbuster.regenerate(publisher = publisher)
- predecessor.generate_crl(publisher = publisher)
- predecessor.generate_manifest(publisher = publisher)
-
- yield publisher.call_pubd()
-
-
- def destroy(self, ca, publisher, allow_failure = False):
- """
- Delete this ca_detail and all of the certs it issued.
- If allow_failure is true, we clean up as much as we can but don't
- raise an exception.
- """
+ @tornado.gen.coroutine
+ def revoke(self, rpkid):
+ """
+ Request revocation of all certificates whose g(SKI) matches the key
+ for this ca_detail.
- repository = ca.parent.repository
- handler = False if allow_failure else None
- for child_cert in self.child_certs.all():
- publisher.queue(uri = child_cert.uri, old_obj = child_cert.cert, repository = repository, handler = handler)
- child_cert.delete()
- for roa in self.roas.all():
- roa.revoke(publisher = publisher, allow_failure = allow_failure, fast = True)
- for ghostbuster in self.ghostbusters.all():
- ghostbuster.revoke(publisher = publisher, allow_failure = allow_failure, fast = True)
- if self.latest_manifest is not None:
- publisher.queue(uri = self.manifest_uri, old_obj = self.latest_manifest, repository = repository, handler = handler)
- if self.latest_crl is not None:
- publisher.queue(uri = self.crl_uri, old_obj = self.latest_crl, repository = repository, handler = handler)
- for cert in self.revoked_certs.all(): # + self.child_certs.all()
- logger.debug("Deleting %r", cert)
- cert.delete()
- logger.debug("Deleting %r", self)
- self.delete()
-
-
- @tornado.gen.coroutine
- def revoke(self, rpkid):
- """
- Request revocation of all certificates whose g(SKI) matches the key
- for this ca_detail.
+ Tasks:
- Tasks:
+ - Request revocation of old keypair by parent.
- - Request revocation of old keypair by parent.
+ - Revoke all child certs issued by the old keypair.
- - 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.
- - 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.
+ - Generate a corresponding final manifest.
- - Generate a corresponding final manifest.
+ - Destroy old keypairs.
- - Destroy old keypairs.
+ - Leave final CRL and manifest in place until their nextupdate
+ time has passed.
+ """
- - Leave final CRL and manifest in place until their nextupdate
- time has passed.
- """
+ gski = self.latest_ca_cert.gSKI()
- gski = self.latest_ca_cert.gSKI()
+ logger.debug("Asking parent to revoke CA certificate matching g(SKI) = %s", gski)
- logger.debug("Asking parent to revoke CA certificate matching g(SKI) = %s", gski)
+ r_msg = yield self.ca.parent.up_down_revoke_query(rpkid = rpkid, class_name = self.ca.parent_resource_class, ski = gski)
- r_msg = yield self.ca.parent.up_down_revoke_query(rpkid = rpkid, class_name = self.ca.parent_resource_class, ski = gski)
+ if r_msg[0].get("class_name") != self.ca.parent_resource_class:
+ raise rpki.exceptions.ResourceClassMismatch
- if r_msg[0].get("class_name") != self.ca.parent_resource_class:
- raise rpki.exceptions.ResourceClassMismatch
+ if r_msg[0].get("ski") != gski:
+ raise rpki.exceptions.SKIMismatch
- if r_msg[0].get("ski") != gski:
- raise rpki.exceptions.SKIMismatch
+ logger.debug("Parent revoked g(SKI) %s, starting cleanup", gski)
- logger.debug("Parent revoked g(SKI) %s, starting cleanup", gski)
+ crl_interval = rpki.sundial.timedelta(seconds = self.ca.parent.tenant.crl_interval)
- crl_interval = rpki.sundial.timedelta(seconds = self.ca.parent.tenant.crl_interval)
+ nextUpdate = rpki.sundial.now()
- nextUpdate = rpki.sundial.now()
+ if self.latest_manifest is not None:
+ self.latest_manifest.extract_if_needed()
+ nextUpdate = nextUpdate.later(self.latest_manifest.getNextUpdate())
- if self.latest_manifest is not None:
- self.latest_manifest.extract_if_needed()
- nextUpdate = nextUpdate.later(self.latest_manifest.getNextUpdate())
+ if self.latest_crl is not None:
+ nextUpdate = nextUpdate.later(self.latest_crl.getNextUpdate())
- if self.latest_crl is not None:
- nextUpdate = nextUpdate.later(self.latest_crl.getNextUpdate())
+ publisher = rpki.rpkid.publication_queue(rpkid)
- publisher = rpki.rpkid.publication_queue(rpkid)
+ for child_cert in self.child_certs.all():
+ nextUpdate = nextUpdate.later(child_cert.cert.getNotAfter())
+ child_cert.revoke(publisher = publisher)
- for child_cert in self.child_certs.all():
- nextUpdate = nextUpdate.later(child_cert.cert.getNotAfter())
- child_cert.revoke(publisher = publisher)
+ for roa in self.roas.all():
+ nextUpdate = nextUpdate.later(roa.cert.getNotAfter())
+ roa.revoke(publisher = publisher)
- for roa in self.roas.all():
- nextUpdate = nextUpdate.later(roa.cert.getNotAfter())
- roa.revoke(publisher = publisher)
+ for ghostbuster in self.ghostbusters.all():
+ nextUpdate = nextUpdate.later(ghostbuster.cert.getNotAfter())
+ ghostbuster.revoke(publisher = publisher)
- for ghostbuster in self.ghostbusters.all():
- nextUpdate = nextUpdate.later(ghostbuster.cert.getNotAfter())
- ghostbuster.revoke(publisher = publisher)
+ nextUpdate += crl_interval
- nextUpdate += crl_interval
+ self.generate_crl(publisher = publisher, nextUpdate = nextUpdate)
+ self.generate_manifest(publisher = publisher, nextUpdate = 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.save()
- self.generate_crl(publisher = publisher, nextUpdate = nextUpdate)
- self.generate_manifest(publisher = publisher, nextUpdate = 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.save()
+ yield publisher.call_pubd()
- yield publisher.call_pubd()
+ @tornado.gen.coroutine
+ def update(self, rpkid, parent, ca, rc, sia_uri_changed, old_resources):
+ """
+ Need to get a new certificate for this ca_detail and perhaps frob
+ children of this ca_detail.
+ """
- @tornado.gen.coroutine
- def update(self, rpkid, parent, ca, rc, sia_uri_changed, old_resources):
- """
- Need to get a new certificate for this ca_detail and perhaps frob
- children of this ca_detail.
- """
+ logger.debug("Sending issue request to %r from %r", parent, self.update)
- logger.debug("Sending issue request to %r from %r", parent, self.update)
+ r_msg = yield parent.up_down_issue_query(rpkid = rpkid, ca = ca, ca_detail = self)
- r_msg = yield parent.up_down_issue_query(rpkid = rpkid, ca = ca, ca_detail = self)
+ c = r_msg[0][0]
- c = r_msg[0][0]
+ cert = rpki.x509.X509(Base64 = c.text)
+ cert_url = c.get("cert_url")
- cert = rpki.x509.X509(Base64 = c.text)
- cert_url = c.get("cert_url")
+ logger.debug("CA %r received certificate %s", self, cert_url)
- logger.debug("CA %r received certificate %s", self, cert_url)
+ if self.state == "pending":
+ yield self.activate(rpkid = rpkid, ca = ca, cert = cert, uri = cert_url)
+ return
- if self.state == "pending":
- yield self.activate(rpkid = rpkid, ca = ca, cert = cert, uri = cert_url)
- return
+ validity_changed = self.latest_ca_cert is None or self.latest_ca_cert.getNotAfter() != cert.getNotAfter()
- validity_changed = self.latest_ca_cert is None or self.latest_ca_cert.getNotAfter() != cert.getNotAfter()
+ publisher = rpki.rpkid.publication_queue(rpkid)
- publisher = rpki.rpkid.publication_queue(rpkid)
+ if self.latest_ca_cert != cert:
+ self.latest_ca_cert = cert
+ self.save()
+ self.generate_manifest_cert()
+ self.generate_crl(publisher = publisher)
+ self.generate_manifest(publisher = publisher)
- if self.latest_ca_cert != cert:
- self.latest_ca_cert = cert
- self.save()
- self.generate_manifest_cert()
- self.generate_crl(publisher = publisher)
- self.generate_manifest(publisher = publisher)
+ new_resources = self.latest_ca_cert.get_3779resources()
- new_resources = self.latest_ca_cert.get_3779resources()
+ if sia_uri_changed or old_resources.oversized(new_resources):
+ for child_cert in self.child_certs.all():
+ child_resources = child_cert.cert.get_3779resources()
+ if sia_uri_changed or child_resources.oversized(new_resources):
+ child_cert.reissue(ca_detail = self, resources = child_resources & new_resources, publisher = publisher)
- if sia_uri_changed or old_resources.oversized(new_resources):
- for child_cert in self.child_certs.all():
- child_resources = child_cert.cert.get_3779resources()
- if sia_uri_changed or child_resources.oversized(new_resources):
- child_cert.reissue(ca_detail = self, resources = child_resources & new_resources, publisher = publisher)
+ if sia_uri_changed or validity_changed or old_resources.oversized(new_resources):
+ for roa in self.roas.all():
+ roa.update(publisher = publisher, fast = True)
- if sia_uri_changed or validity_changed or old_resources.oversized(new_resources):
- for roa in self.roas.all():
- roa.update(publisher = publisher, fast = True)
+ if sia_uri_changed or validity_changed:
+ for ghostbuster in self.ghostbusters.all():
+ ghostbuster.update(publisher = publisher, fast = True)
- if sia_uri_changed or validity_changed:
- for ghostbuster in self.ghostbusters.all():
- ghostbuster.update(publisher = publisher, fast = True)
+ yield publisher.call_pubd()
- yield publisher.call_pubd()
+ @classmethod
+ def create(cls, ca):
+ """
+ Create a new ca_detail object for a specified CA.
+ """
+
+ cer_keypair = rpki.x509.RSA.generate()
+ mft_keypair = rpki.x509.RSA.generate()
+ return cls.objects.create(
+ ca = ca,
+ state = "pending",
+ private_key_id = cer_keypair,
+ public_key = cer_keypair.get_public(),
+ manifest_private_key_id = mft_keypair,
+ manifest_public_key = mft_keypair.get_public())
+
+
+ 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,
+ serial = ca.next_serial_number(),
+ sia = sia,
+ aia = self.ca_cert_uri,
+ crldp = self.crl_uri,
+ resources = resources,
+ notAfter = notAfter,
+ is_ca = False,
+ cn = cn,
+ sn = sn,
+ eku = eku)
+
+
+ def generate_manifest_cert(self):
+ """
+ Generate a new manifest certificate for this ca_detail.
+ """
+
+ resources = rpki.resource_set.resource_bag.from_inheritance()
+ self.latest_manifest_cert = self.issue_ee(
+ ca = self.ca,
+ resources = resources,
+ subject_key = self.manifest_public_key,
+ sia = (None, None, self.manifest_uri, self.ca.parent.repository.rrdp_notification_uri))
+
+
+ def issue(self, ca, child, subject_key, sia, resources, publisher, child_cert = None):
+ """
+ Issue a new certificate to a child. Optional child_cert argument
+ specifies an existing child_cert object to update in place; if not
+ specified, we create a new one. Returns the child_cert object
+ containing the newly issued cert.
+ """
+
+ self.check_failed_publication(publisher)
+ cert = self.latest_ca_cert.issue(
+ keypair = self.private_key_id,
+ subject_key = subject_key,
+ serial = ca.next_serial_number(),
+ aia = self.ca_cert_uri,
+ crldp = self.crl_uri,
+ sia = sia,
+ resources = resources,
+ notAfter = resources.valid_until)
+ if child_cert is None:
+ old_cert = None
+ child_cert = ChildCert(child = child, ca_detail = self, cert = cert)
+ logger.debug("Created new child_cert %r", child_cert)
+ else:
+ old_cert = child_cert.cert
+ child_cert.cert = cert
+ child_cert.ca_detail = self
+ logger.debug("Reusing existing child_cert %r", child_cert)
+ child_cert.gski = cert.gSKI()
+ child_cert.published = rpki.sundial.now()
+ child_cert.save()
+ publisher.queue(
+ uri = child_cert.uri,
+ old_obj = old_cert,
+ new_obj = child_cert.cert,
+ repository = ca.parent.repository,
+ handler = child_cert.published_callback)
+ self.generate_manifest(publisher = publisher)
+ return child_cert
+
+
+ def generate_crl(self, publisher, 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.
+ """
+
+ self.check_failed_publication(publisher)
+ crl_interval = rpki.sundial.timedelta(seconds = self.ca.parent.tenant.crl_interval)
+ now = rpki.sundial.now()
+ if nextUpdate is None:
+ nextUpdate = now + crl_interval
+ certlist = []
+ for revoked_cert in self.revoked_certs.all():
+ if now > revoked_cert.expires + crl_interval:
+ revoked_cert.delete()
+ else:
+ certlist.append((revoked_cert.serial, revoked_cert.revoked))
+ certlist.sort()
+ old_crl = self.latest_crl
+ self.latest_crl = rpki.x509.CRL.generate(
+ keypair = self.private_key_id,
+ issuer = self.latest_ca_cert,
+ serial = self.ca.next_crl_number(),
+ thisUpdate = now,
+ nextUpdate = nextUpdate,
+ revokedCertificates = certlist)
+ self.crl_published = now
+ self.save()
+ publisher.queue(
+ uri = self.crl_uri,
+ old_obj = old_crl,
+ new_obj = self.latest_crl,
+ repository = self.ca.parent.repository,
+ handler = self.crl_published_callback)
+
+
+ def crl_published_callback(self, pdu):
+ """
+ Check result of CRL publication.
+ """
+
+ rpki.publication.raise_if_error(pdu)
+ self.crl_published = None
+ self.save()
+
+
+ def generate_manifest(self, publisher, nextUpdate = None):
+ """
+ Generate a new manifest for this ca_detail.
+ """
+
+ self.check_failed_publication(publisher)
+
+ crl_interval = rpki.sundial.timedelta(seconds = self.ca.parent.tenant.crl_interval)
+ now = rpki.sundial.now()
+ uri = self.manifest_uri
+ if nextUpdate is None:
+ nextUpdate = now + crl_interval
+ if (self.latest_manifest_cert is None or
+ (self.latest_manifest_cert.getNotAfter() < nextUpdate and
+ self.latest_manifest_cert.getNotAfter() < self.latest_ca_cert.getNotAfter())):
+ logger.debug("Generating EE certificate for %s", uri)
+ self.generate_manifest_cert()
+ logger.debug("Latest CA cert notAfter %s, new %s EE notAfter %s",
+ self.latest_ca_cert.getNotAfter(), uri, self.latest_manifest_cert.getNotAfter())
+ logger.debug("Constructing manifest object list for %s", uri)
+ objs = [(self.crl_uri_tail, self.latest_crl)]
+ objs.extend((c.uri_tail, c.cert) for c in self.child_certs.all())
+ objs.extend((r.uri_tail, r.roa) for r in self.roas.filter(roa__isnull = False))
+ objs.extend((g.uri_tail, g.ghostbuster) for g in self.ghostbusters.all())
+ objs.extend((e.uri_tail, e.cert) for e in self.ee_certificates.all())
+ logger.debug("Building manifest object %s", uri)
+ old_manifest = self.latest_manifest
+ self.latest_manifest = rpki.x509.SignedManifest.build(
+ serial = self.ca.next_manifest_number(),
+ thisUpdate = now,
+ nextUpdate = nextUpdate,
+ names_and_objs = objs,
+ keypair = self.manifest_private_key_id,
+ certs = self.latest_manifest_cert)
+ logger.debug("Manifest generation took %s", rpki.sundial.now() - now)
+ self.manifest_published = now
+ self.save()
+ publisher.queue(
+ uri = uri,
+ old_obj = old_manifest,
+ new_obj = self.latest_manifest,
+ repository = self.ca.parent.repository,
+ handler = self.manifest_published_callback)
+
+
+ def manifest_published_callback(self, pdu):
+ """
+ Check result of manifest publication.
+ """
+
+ rpki.publication.raise_if_error(pdu)
+ self.manifest_published = None
+ self.save()
+
+
+ @tornado.gen.coroutine
+ def reissue(self, rpkid):
+ """
+ Reissue all current certificates issued by this ca_detail.
+ """
- @classmethod
- def create(cls, ca):
- """
- Create a new ca_detail object for a specified CA.
- """
+ publisher = rpki.rpkid.publication_queue(rpkid)
+ self.check_failed_publication(publisher)
+ for roa in self.roas.all():
+ roa.regenerate(publisher, fast = True)
+ for ghostbuster in self.ghostbusters.all():
+ ghostbuster.regenerate(publisher, fast = True)
+ for ee_certificate in self.ee_certificates.all():
+ ee_certificate.reissue(publisher, force = True)
+ for child_cert in self.child_certs.all():
+ child_cert.reissue(self, publisher, force = True)
+ self.generate_manifest_cert()
+ self.save()
+ self.generate_crl(publisher = publisher)
+ self.generate_manifest(publisher = publisher)
+ self.save()
+ yield publisher.call_pubd()
- cer_keypair = rpki.x509.RSA.generate()
- mft_keypair = rpki.x509.RSA.generate()
- return cls.objects.create(
- ca = ca,
- state = "pending",
- private_key_id = cer_keypair,
- public_key = cer_keypair.get_public(),
- manifest_private_key_id = mft_keypair,
- manifest_public_key = mft_keypair.get_public())
+ def check_failed_publication(self, publisher, check_all = True):
+ """
+ Check for failed publication of objects issued by this ca_detail.
+
+ All publishable objects have timestamp fields recording time of
+ last attempted publication, and callback methods which clear these
+ timestamps once publication has succeeded. Our task here is to
+ look for objects issued by this ca_detail which have timestamps
+ set (indicating that they have not been published) and for which
+ the timestamps are not very recent (for some definition of very
+ recent -- intent is to allow a bit of slack in case pubd is just
+ being slow). In such cases, we want to retry publication.
+
+ As an optimization, we can probably skip checking other products
+ if manifest and CRL have been published, thus saving ourselves
+ several complex SQL queries. Not sure yet whether this
+ optimization is worthwhile.
+
+ For the moment we check everything without optimization, because
+ it simplifies testing.
+
+ For the moment our definition of staleness is hardwired; this
+ should become configurable.
+ """
+
+ logger.debug("Checking for failed publication for %r", self)
+
+ stale = rpki.sundial.now() - rpki.sundial.timedelta(seconds = 60)
+ repository = self.ca.parent.repository
+ if self.latest_crl is not None and self.crl_published is not None and self.crl_published < stale:
+ logger.debug("Retrying publication for %s", self.crl_uri)
+ publisher.queue(uri = self.crl_uri,
+ new_obj = self.latest_crl,
+ repository = repository,
+ handler = self.crl_published_callback)
+ if self.latest_manifest is not None and self.manifest_published is not None and self.manifest_published < stale:
+ logger.debug("Retrying publication for %s", self.manifest_uri)
+ publisher.queue(uri = self.manifest_uri,
+ new_obj = self.latest_manifest,
+ repository = repository,
+ handler = self.manifest_published_callback)
+ if not check_all:
+ return
+ for child_cert in self.child_certs.filter(published__isnull = False, published__lt = stale):
+ logger.debug("Retrying publication for %s", child_cert)
+ publisher.queue(
+ uri = child_cert.uri,
+ new_obj = child_cert.cert,
+ repository = repository,
+ handler = child_cert.published_callback)
+ for roa in self.roas.filter(published__isnull = False, published__lt = stale):
+ logger.debug("Retrying publication for %s", roa)
+ publisher.queue(
+ uri = roa.uri,
+ new_obj = roa.roa,
+ repository = repository,
+ handler = roa.published_callback)
+ for ghostbuster in self.ghostbusters.filter(published__isnull = False, published__lt = stale):
+ logger.debug("Retrying publication for %s", ghostbuster)
+ publisher.queue(
+ uri = ghostbuster.uri,
+ new_obj = ghostbuster.ghostbuster,
+ repository = repository,
+ handler = ghostbuster.published_callback)
+ for ee_cert in self.ee_certificates.filter(published__isnull = False, published__lt = stale):
+ logger.debug("Retrying publication for %s", ee_cert)
+ publisher.queue(
+ uri = ee_cert.uri,
+ new_obj = ee_cert.cert,
+ repository = repository,
+ handler = ee_cert.published_callback)
- 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,
- serial = ca.next_serial_number(),
- sia = sia,
- aia = self.ca_cert_uri,
- crldp = self.crl_uri,
- resources = resources,
- notAfter = notAfter,
- is_ca = False,
- cn = cn,
- sn = sn,
- eku = eku)
-
-
- def generate_manifest_cert(self):
- """
- Generate a new manifest certificate for this ca_detail.
- """
+@xml_hooks
+class Child(models.Model):
+ child_handle = models.SlugField(max_length = 255)
+ bpki_cert = CertificateField(null = True)
+ bpki_glue = CertificateField(null = True)
+ last_cms_timestamp = SundialField(null = True)
+ tenant = models.ForeignKey(Tenant, related_name = "children")
+ bsc = models.ForeignKey(BSC, related_name = "children")
+ objects = XMLManager()
- resources = rpki.resource_set.resource_bag.from_inheritance()
- self.latest_manifest_cert = self.issue_ee(
- ca = self.ca,
- resources = resources,
- subject_key = self.manifest_public_key,
- sia = (None, None, self.manifest_uri, self.ca.parent.repository.rrdp_notification_uri))
+ class Meta: # pylint: disable=C1001,W0232
+ unique_together = ("tenant", "child_handle")
+ xml_template = XMLTemplate(
+ name = "child",
+ handles = (BSC,),
+ elements = ("bpki_cert", "bpki_glue"))
- def issue(self, ca, child, subject_key, sia, resources, publisher, child_cert = None):
- """
- Issue a new certificate to a child. Optional child_cert argument
- specifies an existing child_cert object to update in place; if not
- specified, we create a new one. Returns the child_cert object
- containing the newly issued cert.
- """
- self.check_failed_publication(publisher)
- cert = self.latest_ca_cert.issue(
- keypair = self.private_key_id,
- subject_key = subject_key,
- serial = ca.next_serial_number(),
- aia = self.ca_cert_uri,
- crldp = self.crl_uri,
- sia = sia,
- resources = resources,
- notAfter = resources.valid_until)
- if child_cert is None:
- old_cert = None
- child_cert = ChildCert(child = child, ca_detail = self, cert = cert)
- logger.debug("Created new child_cert %r", child_cert)
- else:
- old_cert = child_cert.cert
- child_cert.cert = cert
- child_cert.ca_detail = self
- logger.debug("Reusing existing child_cert %r", child_cert)
- child_cert.gski = cert.gSKI()
- child_cert.published = rpki.sundial.now()
- child_cert.save()
- publisher.queue(
- uri = child_cert.uri,
- old_obj = old_cert,
- new_obj = child_cert.cert,
- repository = ca.parent.repository,
- handler = child_cert.published_callback)
- self.generate_manifest(publisher = publisher)
- return child_cert
-
-
- def generate_crl(self, publisher, 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.
- """
+ @tornado.gen.coroutine
+ def xml_pre_delete_hook(self, rpkid):
+ publisher = rpki.rpkid.publication_queue(rpkid)
+ for child_cert in self.child_certs.all():
+ child_cert.revoke(publisher = publisher, generate_crl_and_manifest = True)
+ yield publisher.call_pubd()
- self.check_failed_publication(publisher)
- crl_interval = rpki.sundial.timedelta(seconds = self.ca.parent.tenant.crl_interval)
- now = rpki.sundial.now()
- if nextUpdate is None:
- nextUpdate = now + crl_interval
- certlist = []
- for revoked_cert in self.revoked_certs.all():
- if now > revoked_cert.expires + crl_interval:
- revoked_cert.delete()
- else:
- certlist.append((revoked_cert.serial, revoked_cert.revoked))
- certlist.sort()
- old_crl = self.latest_crl
- self.latest_crl = rpki.x509.CRL.generate(
- keypair = self.private_key_id,
- issuer = self.latest_ca_cert,
- serial = self.ca.next_crl_number(),
- thisUpdate = now,
- nextUpdate = nextUpdate,
- revokedCertificates = certlist)
- self.crl_published = now
- self.save()
- publisher.queue(
- uri = self.crl_uri,
- old_obj = old_crl,
- new_obj = self.latest_crl,
- repository = self.ca.parent.repository,
- handler = self.crl_published_callback)
-
-
- def crl_published_callback(self, pdu):
- """
- Check result of CRL publication.
- """
- rpki.publication.raise_if_error(pdu)
- self.crl_published = None
- self.save()
+ @tornado.gen.coroutine
+ def xml_post_save_hook(self, rpkid, q_pdu):
+ if q_pdu.get("clear_replay_protection"):
+ self.clear_replay_protection()
+ if q_pdu.get("reissue"):
+ yield self.serve_reissue(rpkid)
- def generate_manifest(self, publisher, nextUpdate = None):
- """
- Generate a new manifest for this ca_detail.
- """
+ def serve_reissue(self, rpkid):
+ publisher = rpki.rpkid.publication_queue(rpkid)
+ for child_cert in self.child_certs.all():
+ child_cert.reissue(child_cert.ca_detail, publisher, force = True)
+ yield publisher.call_pubd()
- self.check_failed_publication(publisher)
-
- crl_interval = rpki.sundial.timedelta(seconds = self.ca.parent.tenant.crl_interval)
- now = rpki.sundial.now()
- uri = self.manifest_uri
- if nextUpdate is None:
- nextUpdate = now + crl_interval
- if (self.latest_manifest_cert is None or
- (self.latest_manifest_cert.getNotAfter() < nextUpdate and
- self.latest_manifest_cert.getNotAfter() < self.latest_ca_cert.getNotAfter())):
- logger.debug("Generating EE certificate for %s", uri)
- self.generate_manifest_cert()
- logger.debug("Latest CA cert notAfter %s, new %s EE notAfter %s",
- self.latest_ca_cert.getNotAfter(), uri, self.latest_manifest_cert.getNotAfter())
- logger.debug("Constructing manifest object list for %s", uri)
- objs = [(self.crl_uri_tail, self.latest_crl)]
- objs.extend((c.uri_tail, c.cert) for c in self.child_certs.all())
- objs.extend((r.uri_tail, r.roa) for r in self.roas.filter(roa__isnull = False))
- objs.extend((g.uri_tail, g.ghostbuster) for g in self.ghostbusters.all())
- objs.extend((e.uri_tail, e.cert) for e in self.ee_certificates.all())
- logger.debug("Building manifest object %s", uri)
- old_manifest = self.latest_manifest
- self.latest_manifest = rpki.x509.SignedManifest.build(
- serial = self.ca.next_manifest_number(),
- thisUpdate = now,
- nextUpdate = nextUpdate,
- names_and_objs = objs,
- keypair = self.manifest_private_key_id,
- certs = self.latest_manifest_cert)
- logger.debug("Manifest generation took %s", rpki.sundial.now() - now)
- self.manifest_published = now
- self.save()
- publisher.queue(uri = uri,
- old_obj = old_manifest,
- new_obj = self.latest_manifest,
- repository = self.ca.parent.repository,
- handler = self.manifest_published_callback)
-
-
- def manifest_published_callback(self, pdu):
- """
- Check result of manifest publication.
- """
- rpki.publication.raise_if_error(pdu)
- self.manifest_published = None
- self.save()
+ def clear_replay_protection(self):
+ self.last_cms_timestamp = None
+ self.save()
- @tornado.gen.coroutine
- def reissue(self, rpkid):
- """
- Reissue all current certificates issued by this ca_detail.
- """
+ @tornado.gen.coroutine
+ def up_down_handle_list(self, rpkid, q_msg, r_msg):
- publisher = rpki.rpkid.publication_queue(rpkid)
- self.check_failed_publication(publisher)
- for roa in self.roas.all():
- roa.regenerate(publisher, fast = True)
- for ghostbuster in self.ghostbusters.all():
- ghostbuster.regenerate(publisher, fast = True)
- for ee_certificate in self.ee_certificates.all():
- ee_certificate.reissue(publisher, force = True)
- for child_cert in self.child_certs.all():
- child_cert.reissue(self, publisher, force = True)
- self.generate_manifest_cert()
- self.save()
- self.generate_crl(publisher = publisher)
- self.generate_manifest(publisher = publisher)
- self.save()
- yield publisher.call_pubd()
-
-
- def check_failed_publication(self, publisher, check_all = True):
- """
- Check for failed publication of objects issued by this ca_detail.
-
- All publishable objects have timestamp fields recording time of
- last attempted publication, and callback methods which clear these
- timestamps once publication has succeeded. Our task here is to
- look for objects issued by this ca_detail which have timestamps
- set (indicating that they have not been published) and for which
- the timestamps are not very recent (for some definition of very
- recent -- intent is to allow a bit of slack in case pubd is just
- being slow). In such cases, we want to retry publication.
-
- As an optimization, we can probably skip checking other products
- if manifest and CRL have been published, thus saving ourselves
- several complex SQL queries. Not sure yet whether this
- optimization is worthwhile.
-
- For the moment we check everything without optimization, because
- it simplifies testing.
-
- For the moment our definition of staleness is hardwired; this
- should become configurable.
- """
+ irdb_resources = yield rpkid.irdb_query_child_resources(self.tenant.tenant_handle, self.child_handle)
- logger.debug("Checking for failed publication for %r", self)
-
- stale = rpki.sundial.now() - rpki.sundial.timedelta(seconds = 60)
- repository = self.ca.parent.repository
- if self.latest_crl is not None and self.crl_published is not None and self.crl_published < stale:
- logger.debug("Retrying publication for %s", self.crl_uri)
- publisher.queue(uri = self.crl_uri,
- new_obj = self.latest_crl,
- repository = repository,
- handler = self.crl_published_callback)
- if self.latest_manifest is not None and self.manifest_published is not None and self.manifest_published < stale:
- logger.debug("Retrying publication for %s", self.manifest_uri)
- publisher.queue(uri = self.manifest_uri,
- new_obj = self.latest_manifest,
- repository = repository,
- handler = self.manifest_published_callback)
- if not check_all:
- return
- for child_cert in self.child_certs.filter(published__isnull = False, published__lt = stale):
- logger.debug("Retrying publication for %s", child_cert)
- publisher.queue(
- uri = child_cert.uri,
- new_obj = child_cert.cert,
- repository = repository,
- handler = child_cert.published_callback)
- for roa in self.roas.filter(published__isnull = False, published__lt = stale):
- logger.debug("Retrying publication for %s", roa)
- publisher.queue(
- uri = roa.uri,
- new_obj = roa.roa,
- repository = repository,
- handler = roa.published_callback)
- for ghostbuster in self.ghostbusters.filter(published__isnull = False, published__lt = stale):
- logger.debug("Retrying publication for %s", ghostbuster)
- publisher.queue(
- uri = ghostbuster.uri,
- new_obj = ghostbuster.ghostbuster,
- repository = repository,
- handler = ghostbuster.published_callback)
- for ee_cert in self.ee_certificates.filter(published__isnull = False, published__lt = stale):
- logger.debug("Retrying publication for %s", ee_cert)
- publisher.queue(
- uri = ee_cert.uri,
- new_obj = ee_cert.cert,
- repository = repository,
- handler = ee_cert.published_callback)
+ if irdb_resources.valid_until < rpki.sundial.now():
+ logger.debug("Child %s's resources expired %s", self.child_handle, irdb_resources.valid_until)
+ else:
-@xml_hooks
-class Child(models.Model):
- child_handle = models.SlugField(max_length = 255)
- bpki_cert = CertificateField(null = True)
- bpki_glue = CertificateField(null = True)
- last_cms_timestamp = SundialField(null = True)
- tenant = models.ForeignKey(Tenant, related_name = "children")
- bsc = models.ForeignKey(BSC, related_name = "children")
- objects = XMLManager()
+ for ca_detail in CADetail.objects.filter(ca__parent__tenant = self.tenant, state = "active"):
+ resources = ca_detail.latest_ca_cert.get_3779resources() & irdb_resources
- class Meta: # pylint: disable=C1001,W0232
- unique_together = ("tenant", "child_handle")
+ if resources.empty():
+ logger.debug("No overlap between received resources and what child %s should get ([%s], [%s])",
+ self.child_handle, ca_detail.latest_ca_cert.get_3779resources(), irdb_resources)
+ continue
- xml_template = XMLTemplate(
- name = "child",
- handles = (BSC,),
- elements = ("bpki_cert", "bpki_glue"))
+ rc = SubElement(r_msg, rpki.up_down.tag_class,
+ class_name = ca_detail.ca.parent_resource_class,
+ cert_url = ca_detail.ca_cert_uri,
+ resource_set_as = str(resources.asn),
+ resource_set_ipv4 = str(resources.v4),
+ resource_set_ipv6 = str(resources.v6),
+ resource_set_notafter = str(resources.valid_until))
+ for child_cert in self.child_certs.filter(ca_detail = ca_detail):
+ c = SubElement(rc, rpki.up_down.tag_certificate, cert_url = child_cert.uri)
+ c.text = child_cert.cert.get_Base64()
+ SubElement(rc, rpki.up_down.tag_issuer).text = ca_detail.latest_ca_cert.get_Base64()
- @tornado.gen.coroutine
- def xml_pre_delete_hook(self, rpkid):
- publisher = rpki.rpkid.publication_queue(rpkid)
- for child_cert in self.child_certs.all():
- child_cert.revoke(publisher = publisher, generate_crl_and_manifest = True)
- yield publisher.call_pubd()
+ @tornado.gen.coroutine
+ def up_down_handle_issue(self, rpkid, q_msg, r_msg):
- @tornado.gen.coroutine
- def xml_post_save_hook(self, rpkid, q_pdu):
- if q_pdu.get("clear_replay_protection"):
- self.clear_replay_protection()
- if q_pdu.get("reissue"):
- yield self.serve_reissue(rpkid)
+ req = q_msg[0]
+ assert req.tag == rpki.up_down.tag_request
+ # Subsetting not yet implemented, this is the one place where we have to handle it, by reporting that we're lame.
- def serve_reissue(self, rpkid):
- publisher = rpki.rpkid.publication_queue(rpkid)
- for child_cert in self.child_certs.all():
- child_cert.reissue(child_cert.ca_detail, publisher, force = True)
- yield publisher.call_pubd()
+ if any(req.get(a) for a in ("req_resource_set_as", "req_resource_set_ipv4", "req_resource_set_ipv6")):
+ raise rpki.exceptions.NotImplementedYet("req_* attributes not implemented yet, sorry")
+ class_name = req.get("class_name")
+ pkcs10 = rpki.x509.PKCS10(Base64 = req.text)
+ pkcs10.check_valid_request_ca()
+ ca_detail = CADetail.objects.get(ca__parent__tenant = self.tenant, state = "active",
+ ca__parent_resource_class = class_name)
- def clear_replay_protection(self):
- self.last_cms_timestamp = None
- self.save()
+ irdb_resources = yield rpkid.irdb_query_child_resources(self.tenant.tenant_handle, self.child_handle)
+ if irdb_resources.valid_until < rpki.sundial.now():
+ raise rpki.exceptions.IRDBExpired("IRDB entry for child %s expired %s" % (
+ self.child_handle, irdb_resources.valid_until))
- @tornado.gen.coroutine
- def up_down_handle_list(self, rpkid, q_msg, r_msg):
+ resources = irdb_resources & ca_detail.latest_ca_cert.get_3779resources()
+ resources.valid_until = irdb_resources.valid_until
+ req_key = pkcs10.getPublicKey()
+ req_sia = pkcs10.get_SIA()
- irdb_resources = yield rpkid.irdb_query_child_resources(self.tenant.tenant_handle, self.child_handle)
+ # Generate new cert or regenerate old one if necessary
- if irdb_resources.valid_until < rpki.sundial.now():
- logger.debug("Child %s's resources expired %s", self.child_handle, irdb_resources.valid_until)
+ publisher = rpki.rpkid.publication_queue(rpkid)
- else:
+ try:
+ child_cert = self.child_certs.get(ca_detail = ca_detail, gski = req_key.gSKI())
- for ca_detail in CADetail.objects.filter(ca__parent__tenant = self.tenant, state = "active"):
- resources = ca_detail.latest_ca_cert.get_3779resources() & irdb_resources
+ except ChildCert.DoesNotExist:
+ child_cert = ca_detail.issue(
+ ca = ca_detail.ca,
+ child = self,
+ subject_key = req_key,
+ sia = req_sia,
+ resources = resources,
+ publisher = publisher)
- if resources.empty():
- logger.debug("No overlap between received resources and what child %s should get ([%s], [%s])",
- self.child_handle, ca_detail.latest_ca_cert.get_3779resources(), irdb_resources)
- continue
+ else:
+ child_cert = child_cert.reissue(
+ ca_detail = ca_detail,
+ sia = req_sia,
+ resources = resources,
+ publisher = publisher)
+
+ yield publisher.call_pubd()
rc = SubElement(r_msg, rpki.up_down.tag_class,
- class_name = ca_detail.ca.parent_resource_class,
+ class_name = class_name,
cert_url = ca_detail.ca_cert_uri,
resource_set_as = str(resources.asn),
resource_set_ipv4 = str(resources.v4),
resource_set_ipv6 = str(resources.v6),
resource_set_notafter = str(resources.valid_until))
-
- for child_cert in self.child_certs.filter(ca_detail = ca_detail):
- c = SubElement(rc, rpki.up_down.tag_certificate, cert_url = child_cert.uri)
- c.text = child_cert.cert.get_Base64()
+ c = SubElement(rc, rpki.up_down.tag_certificate, cert_url = child_cert.uri)
+ c.text = child_cert.cert.get_Base64()
SubElement(rc, rpki.up_down.tag_issuer).text = ca_detail.latest_ca_cert.get_Base64()
- @tornado.gen.coroutine
- def up_down_handle_issue(self, rpkid, q_msg, r_msg):
-
- req = q_msg[0]
- assert req.tag == rpki.up_down.tag_request
-
- # Subsetting not yet implemented, this is the one place where we have to handle it, by reporting that we're lame.
-
- if any(req.get(a) for a in ("req_resource_set_as", "req_resource_set_ipv4", "req_resource_set_ipv6")):
- raise rpki.exceptions.NotImplementedYet("req_* attributes not implemented yet, sorry")
-
- class_name = req.get("class_name")
- pkcs10 = rpki.x509.PKCS10(Base64 = req.text)
- pkcs10.check_valid_request_ca()
- ca_detail = CADetail.objects.get(ca__parent__tenant = self.tenant, state = "active",
- ca__parent_resource_class = class_name)
-
- irdb_resources = yield rpkid.irdb_query_child_resources(self.tenant.tenant_handle, self.child_handle)
-
- if irdb_resources.valid_until < rpki.sundial.now():
- raise rpki.exceptions.IRDBExpired("IRDB entry for child %s expired %s" % (
- self.child_handle, irdb_resources.valid_until))
-
- resources = irdb_resources & ca_detail.latest_ca_cert.get_3779resources()
- resources.valid_until = irdb_resources.valid_until
- req_key = pkcs10.getPublicKey()
- req_sia = pkcs10.get_SIA()
-
- # Generate new cert or regenerate old one if necessary
-
- publisher = rpki.rpkid.publication_queue(rpkid)
-
- try:
- child_cert = self.child_certs.get(ca_detail = ca_detail, gski = req_key.gSKI())
-
- except ChildCert.DoesNotExist:
- child_cert = ca_detail.issue(
- ca = ca_detail.ca,
- child = self,
- subject_key = req_key,
- sia = req_sia,
- resources = resources,
- publisher = publisher)
-
- else:
- child_cert = child_cert.reissue(
- ca_detail = ca_detail,
- sia = req_sia,
- resources = resources,
- publisher = publisher)
-
- yield publisher.call_pubd()
-
- rc = SubElement(r_msg, rpki.up_down.tag_class,
- class_name = class_name,
- cert_url = ca_detail.ca_cert_uri,
- resource_set_as = str(resources.asn),
- resource_set_ipv4 = str(resources.v4),
- resource_set_ipv6 = str(resources.v6),
- resource_set_notafter = str(resources.valid_until))
- c = SubElement(rc, rpki.up_down.tag_certificate, cert_url = child_cert.uri)
- c.text = child_cert.cert.get_Base64()
- SubElement(rc, rpki.up_down.tag_issuer).text = ca_detail.latest_ca_cert.get_Base64()
-
+ @tornado.gen.coroutine
+ def up_down_handle_revoke(self, rpkid, q_msg, r_msg):
+ key = q_msg[0]
+ assert key.tag == rpki.up_down.tag_key
+ class_name = key.get("class_name")
+ publisher = rpki.rpkid.publication_queue(rpkid)
+ for child_cert in ChildCert.objects.filter(ca_detail__ca__parent__tenant = self.tenant,
+ ca_detail__ca__parent_resource_class = class_name,
+ gski = key.get("ski")):
+ child_cert.revoke(publisher = publisher)
+ yield publisher.call_pubd()
+ SubElement(r_msg, key.tag, class_name = class_name, ski = key.get("ski"))
- @tornado.gen.coroutine
- def up_down_handle_revoke(self, rpkid, q_msg, r_msg):
- key = q_msg[0]
- assert key.tag == rpki.up_down.tag_key
- class_name = key.get("class_name")
- publisher = rpki.rpkid.publication_queue(rpkid)
- for child_cert in ChildCert.objects.filter(ca_detail__ca__parent__tenant = self.tenant,
- ca_detail__ca__parent_resource_class = class_name,
- gski = key.get("ski")):
- child_cert.revoke(publisher = publisher)
- yield publisher.call_pubd()
- SubElement(r_msg, key.tag, class_name = class_name, ski = key.get("ski"))
+ @tornado.gen.coroutine
+ def serve_up_down(self, rpkid, q_der):
+ """
+ Outer layer of server handling for one up-down PDU from this child.
+ """
- @tornado.gen.coroutine
- def serve_up_down(self, rpkid, q_der):
- """
- Outer layer of server handling for one up-down PDU from this child.
- """
+ if self.bsc is None:
+ raise rpki.exceptions.BSCNotFound("Could not find BSC")
- if self.bsc is None:
- raise rpki.exceptions.BSCNotFound("Could not find BSC")
+ q_cms = rpki.up_down.cms_msg(DER = q_der)
+ q_msg = q_cms.unwrap((rpkid.bpki_ta, self.tenant.bpki_cert, self.tenant.bpki_glue, self.bpki_cert, self.bpki_glue))
+ q_cms.check_replay_sql(self, "child", self.child_handle)
+ q_type = q_msg.get("type")
- q_cms = rpki.up_down.cms_msg(DER = q_der)
- q_msg = q_cms.unwrap((rpkid.bpki_ta, self.tenant.bpki_cert, self.tenant.bpki_glue, self.bpki_cert, self.bpki_glue))
- q_cms.check_replay_sql(self, "child", self.child_handle)
- q_type = q_msg.get("type")
+ logger.info("Serving %s query from child %s [sender %s, recipient %s]",
+ q_type, self.child_handle, q_msg.get("sender"), q_msg.get("recipient"))
- logger.info("Serving %s query from child %s [sender %s, recipient %s]",
- q_type, self.child_handle, q_msg.get("sender"), q_msg.get("recipient"))
+ if rpki.up_down.enforce_strict_up_down_xml_sender and q_msg.get("sender") != self.child_handle:
+ raise rpki.exceptions.BadSender("Unexpected XML sender %s" % q_msg.get("sender"))
- if rpki.up_down.enforce_strict_up_down_xml_sender and q_msg.get("sender") != self.child_handle:
- raise rpki.exceptions.BadSender("Unexpected XML sender %s" % q_msg.get("sender"))
+ r_msg = Element(rpki.up_down.tag_message, nsmap = rpki.up_down.nsmap, version = rpki.up_down.version,
+ sender = q_msg.get("recipient"), recipient = q_msg.get("sender"), type = q_type + "_response")
- r_msg = Element(rpki.up_down.tag_message, nsmap = rpki.up_down.nsmap, version = rpki.up_down.version,
- sender = q_msg.get("recipient"), recipient = q_msg.get("sender"), type = q_type + "_response")
+ try:
+ yield getattr(self, "up_down_handle_" + q_type)(rpkid, q_msg, r_msg)
- try:
- yield getattr(self, "up_down_handle_" + q_type)(rpkid, q_msg, r_msg)
+ except Exception, e:
+ logger.exception("Unhandled exception serving child %r", self)
+ rpki.up_down.generate_error_response_from_exception(r_msg, e, q_type)
- except Exception, e:
- logger.exception("Unhandled exception serving child %r", self)
- rpki.up_down.generate_error_response_from_exception(r_msg, e, q_type)
-
- r_der = rpki.up_down.cms_msg().wrap(r_msg, self.bsc.private_key_id, self.bsc.signing_cert, self.bsc.signing_cert_crl)
- raise tornado.gen.Return(r_der)
+ r_der = rpki.up_down.cms_msg().wrap(r_msg, self.bsc.private_key_id, self.bsc.signing_cert, self.bsc.signing_cert_crl)
+ raise tornado.gen.Return(r_der)
class ChildCert(models.Model):
- cert = CertificateField()
- published = SundialField(null = True)
- gski = models.CharField(max_length = 27) # Assumes SHA-1 -- SHA-256 would be 43, SHA-512 would be 86, etc.
- child = models.ForeignKey(Child, related_name = "child_certs")
- ca_detail = models.ForeignKey(CADetail, related_name = "child_certs")
-
-
- @property
- def uri_tail(self):
- """
- Return the tail (filename) portion of the URI for this child_cert.
- """
-
- return self.gski + ".cer"
-
-
- @property
- def uri(self):
- """
- Return the publication URI for this child_cert.
- """
-
- return self.ca_detail.ca.sia_uri + self.uri_tail
-
-
- def revoke(self, publisher, generate_crl_and_manifest = True):
- """
- Revoke a child cert.
- """
-
- ca_detail = self.ca_detail
- logger.debug("Revoking %r %r", self, self.uri)
- RevokedCert.revoke(cert = self.cert, ca_detail = ca_detail)
- publisher.queue(uri = self.uri, old_obj = self.cert, repository = ca_detail.ca.parent.repository)
- self.delete()
- if generate_crl_and_manifest:
- ca_detail.generate_crl(publisher = publisher)
- ca_detail.generate_manifest(publisher = publisher)
-
-
- def reissue(self, ca_detail, publisher, resources = None, sia = None, force = False):
- """
- Reissue an existing child cert, reusing the public key. If the
- child 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 child cert when generating the new one, we have to
- generate a new child_cert_obj, so calling code that needs the
- updated child_cert_obj must use the return value from this method.
- """
-
- ca = ca_detail.ca
- child = self.child
- 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
- 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.asn != old_resources.asn or resources.v4 != old_resources.v4 or resources.v6 != old_resources.v6:
- logger.debug("Resources changed for %r: old %s new %s", self, old_resources, resources)
- needed = True
- if resources.valid_until != old_resources.valid_until:
- logger.debug("Validity changed for %r: old %s new %s",
- self, old_resources.valid_until, resources.valid_until)
- needed = True
- if sia != old_sia:
- logger.debug("SIA changed for %r: old %r new %r", self, old_sia, sia)
- needed = True
- if ca_detail != old_ca_detail:
- logger.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:
- logger.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
- if must_revoke:
- logger.debug("Must revoke any existing cert(s) for %r", self)
- needed = True
- if not needed and force:
- logger.debug("No change needed for %r, forcing reissuance anyway", self)
- needed = True
- if not needed:
- logger.debug("No change to %r", self)
- return self
- if must_revoke:
- for x in child.child_certs.filter(ca_detail = ca_detail, gski = self.gski):
- logger.debug("Revoking child_cert %r", x)
- x.revoke(publisher = publisher)
- ca_detail.generate_crl(publisher = publisher)
- ca_detail.generate_manifest(publisher = publisher)
- child_cert = ca_detail.issue(
- ca = ca,
- child = child,
- subject_key = self.cert.getPublicKey(),
- sia = sia,
- resources = resources,
- child_cert = None if must_revoke else self,
- publisher = publisher)
- logger.debug("New child_cert %r uri %s", child_cert, child_cert.uri)
- return child_cert
-
-
- def published_callback(self, pdu):
- """
- Publication callback: check result and mark published.
- """
-
- rpki.publication.raise_if_error(pdu)
- self.published = None
- self.save()
+ cert = CertificateField()
+ published = SundialField(null = True)
+ gski = models.CharField(max_length = 27) # Assumes SHA-1 -- SHA-256 would be 43, SHA-512 would be 86, etc.
+ child = models.ForeignKey(Child, related_name = "child_certs")
+ ca_detail = models.ForeignKey(CADetail, related_name = "child_certs")
+
+
+ @property
+ def uri_tail(self):
+ """
+ Return the tail (filename) portion of the URI for this child_cert.
+ """
+
+ return self.gski + ".cer"
+
+
+ @property
+ def uri(self):
+ """
+ Return the publication URI for this child_cert.
+ """
+
+ return self.ca_detail.ca.sia_uri + self.uri_tail
+
+
+ def revoke(self, publisher, generate_crl_and_manifest = True):
+ """
+ Revoke a child cert.
+ """
+
+ ca_detail = self.ca_detail
+ logger.debug("Revoking %r %r", self, self.uri)
+ RevokedCert.revoke(cert = self.cert, ca_detail = ca_detail)
+ publisher.queue(uri = self.uri, old_obj = self.cert, repository = ca_detail.ca.parent.repository)
+ self.delete()
+ if generate_crl_and_manifest:
+ ca_detail.generate_crl(publisher = publisher)
+ ca_detail.generate_manifest(publisher = publisher)
+
+
+ def reissue(self, ca_detail, publisher, resources = None, sia = None, force = False):
+ """
+ Reissue an existing child cert, reusing the public key. If the
+ child 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 child cert when generating the new one, we have to
+ generate a new child_cert_obj, so calling code that needs the
+ updated child_cert_obj must use the return value from this method.
+ """
+
+ ca = ca_detail.ca
+ child = self.child
+ 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
+ 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.asn != old_resources.asn or resources.v4 != old_resources.v4 or resources.v6 != old_resources.v6:
+ logger.debug("Resources changed for %r: old %s new %s", self, old_resources, resources)
+ needed = True
+ if resources.valid_until != old_resources.valid_until:
+ logger.debug("Validity changed for %r: old %s new %s",
+ self, old_resources.valid_until, resources.valid_until)
+ needed = True
+ if sia != old_sia:
+ logger.debug("SIA changed for %r: old %r new %r", self, old_sia, sia)
+ needed = True
+ if ca_detail != old_ca_detail:
+ logger.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:
+ logger.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
+ if must_revoke:
+ logger.debug("Must revoke any existing cert(s) for %r", self)
+ needed = True
+ if not needed and force:
+ logger.debug("No change needed for %r, forcing reissuance anyway", self)
+ needed = True
+ if not needed:
+ logger.debug("No change to %r", self)
+ return self
+ if must_revoke:
+ for x in child.child_certs.filter(ca_detail = ca_detail, gski = self.gski):
+ logger.debug("Revoking child_cert %r", x)
+ x.revoke(publisher = publisher)
+ ca_detail.generate_crl(publisher = publisher)
+ ca_detail.generate_manifest(publisher = publisher)
+ child_cert = ca_detail.issue(
+ ca = ca,
+ child = child,
+ subject_key = self.cert.getPublicKey(),
+ sia = sia,
+ resources = resources,
+ child_cert = None if must_revoke else self,
+ publisher = publisher)
+ logger.debug("New child_cert %r uri %s", child_cert, child_cert.uri)
+ return child_cert
+
+
+ def published_callback(self, pdu):
+ """
+ Publication callback: check result and mark published.
+ """
+
+ rpki.publication.raise_if_error(pdu)
+ self.published = None
+ self.save()
class EECertificate(models.Model):
- gski = models.CharField(max_length = 27) # Assumes SHA-1 -- SHA-256 would be 43, SHA-512 would be 86, etc.
- cert = CertificateField()
- published = SundialField(null = True)
- tenant = models.ForeignKey(Tenant, related_name = "ee_certificates")
- ca_detail = models.ForeignKey(CADetail, related_name = "ee_certificates")
-
-
- @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.gski + ".cer"
-
-
- @classmethod
- def create(cls, ca_detail, subject_name, subject_key, resources, publisher, eku = None):
- """
- Generate a new EE certificate.
- """
-
- # The low-level X.509 code really ought to supply the singleton
- # tuple wrapper when handed a string, but that yak will need to
- # wait until another day for its shave.
-
- cn, sn = subject_name.extract_cn_and_sn()
- sia = (None, None,
- (ca_detail.ca.sia_uri + subject_key.gSKI() + ".cer",),
- (ca_detail.ca.parent.repository.rrdp_notification_uri,))
- cert = ca_detail.issue_ee(
- ca = ca_detail.ca,
- subject_key = subject_key,
- sia = sia,
- resources = resources,
- notAfter = resources.valid_until,
- cn = cn,
- sn = sn,
- eku = eku)
- self = cls(tenant = ca_detail.ca.parent.tenant, ca_detail = ca_detail, cert = cert, gski = subject_key.gSKI())
- publisher.queue(
- uri = self.uri,
- new_obj = self.cert,
- repository = ca_detail.ca.parent.repository,
- handler = self.published_callback)
- self.save()
- ca_detail.generate_manifest(publisher = publisher)
- logger.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
- logger.debug("Revoking %r %r", self, self.uri)
- RevokedCert.revoke(cert = self.cert, ca_detail = ca_detail)
- publisher.queue(uri = self.uri, old_obj = self.cert, repository = ca_detail.ca.parent.repository)
- self.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:
- logger.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]:
- logger.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:
- logger.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:
- logger.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:
- logger.debug("Must revoke existing cert(s) for %r", self)
- needed = True
- if not needed and force:
- logger.debug("No change needed for %r, forcing reissuance anyway", self)
- needed = True
- if not needed:
- logger.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, None, self.uri, ca_detail.ca.parent.repository.rrdp_notification_uri),
- resources = resources,
- notAfter = resources.valid_until,
- cn = cn,
- sn = sn)
- self.save()
- publisher.queue(
- uri = self.uri,
- old_obj = old_cert,
- new_obj = self.cert,
- repository = ca_detail.ca.parent.repository,
- handler = self.published_callback)
- if must_revoke:
- RevokedCert.revoke(cert = old_cert.cert, ca_detail = old_ca_detail)
- ca_detail.generate_crl(publisher = publisher)
- ca_detail.generate_manifest(publisher = publisher)
-
-
- def published_callback(self, pdu):
- """
- Publication callback: check result and mark published.
- """
-
- rpki.publication.raise_if_error(pdu)
- self.published = None
- self.save()
+ gski = models.CharField(max_length = 27) # Assumes SHA-1 -- SHA-256 would be 43, SHA-512 would be 86, etc.
+ cert = CertificateField()
+ published = SundialField(null = True)
+ tenant = models.ForeignKey(Tenant, related_name = "ee_certificates")
+ ca_detail = models.ForeignKey(CADetail, related_name = "ee_certificates")
+
+
+ @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.gski + ".cer"
+
+
+ @classmethod
+ def create(cls, ca_detail, subject_name, subject_key, resources, publisher, eku = None):
+ """
+ Generate a new EE certificate.
+ """
+
+ # The low-level X.509 code really ought to supply the singleton
+ # tuple wrapper when handed a string, but that yak will need to
+ # wait until another day for its shave.
+
+ cn, sn = subject_name.extract_cn_and_sn()
+ sia = (None, None,
+ (ca_detail.ca.sia_uri + subject_key.gSKI() + ".cer",),
+ (ca_detail.ca.parent.repository.rrdp_notification_uri,))
+ cert = ca_detail.issue_ee(
+ ca = ca_detail.ca,
+ subject_key = subject_key,
+ sia = sia,
+ resources = resources,
+ notAfter = resources.valid_until,
+ cn = cn,
+ sn = sn,
+ eku = eku)
+ self = cls(tenant = ca_detail.ca.parent.tenant, ca_detail = ca_detail, cert = cert, gski = subject_key.gSKI())
+ publisher.queue(
+ uri = self.uri,
+ new_obj = self.cert,
+ repository = ca_detail.ca.parent.repository,
+ handler = self.published_callback)
+ self.save()
+ ca_detail.generate_manifest(publisher = publisher)
+ logger.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
+ logger.debug("Revoking %r %r", self, self.uri)
+ RevokedCert.revoke(cert = self.cert, ca_detail = ca_detail)
+ publisher.queue(uri = self.uri, old_obj = self.cert, repository = ca_detail.ca.parent.repository)
+ self.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:
+ logger.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]:
+ logger.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:
+ logger.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:
+ logger.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:
+ logger.debug("Must revoke existing cert(s) for %r", self)
+ needed = True
+ if not needed and force:
+ logger.debug("No change needed for %r, forcing reissuance anyway", self)
+ needed = True
+ if not needed:
+ logger.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, None, self.uri, ca_detail.ca.parent.repository.rrdp_notification_uri),
+ resources = resources,
+ notAfter = resources.valid_until,
+ cn = cn,
+ sn = sn)
+ self.save()
+ publisher.queue(
+ uri = self.uri,
+ old_obj = old_cert,
+ new_obj = self.cert,
+ repository = ca_detail.ca.parent.repository,
+ handler = self.published_callback)
+ if must_revoke:
+ RevokedCert.revoke(cert = old_cert.cert, ca_detail = old_ca_detail)
+ ca_detail.generate_crl(publisher = publisher)
+ ca_detail.generate_manifest(publisher = publisher)
+
+
+ def published_callback(self, pdu):
+ """
+ Publication callback: check result and mark published.
+ """
+
+ rpki.publication.raise_if_error(pdu)
+ self.published = None
+ self.save()
class Ghostbuster(models.Model):
- vcard = models.TextField()
- cert = CertificateField()
- ghostbuster = GhostbusterField()
- published = SundialField(null = True)
- tenant = models.ForeignKey(Tenant, related_name = "ghostbusters")
- ca_detail = models.ForeignKey(CADetail, related_name = "ghostbusters")
-
-
- def update(self, publisher, fast = False):
- """
- Bring this ghostbuster_obj up to date if necesssary.
- """
-
- if self.ghostbuster is None:
- logger.debug("Ghostbuster record doesn't exist, generating")
- return self.generate(publisher = publisher, fast = fast)
-
- now = rpki.sundial.now()
- regen_time = self.cert.getNotAfter() - rpki.sundial.timedelta(seconds = self.tenant.regen_margin)
-
- if now > regen_time and self.cert.getNotAfter() < self.ca_detail.latest_ca_cert.getNotAfter():
- logger.debug("%r past threshold %s, regenerating", self, regen_time)
- return self.regenerate(publisher = publisher, fast = fast)
-
- if now > regen_time:
- logger.warning("%r is past threshold %s but so is issuer %r, can't regenerate", self, regen_time, self.ca_detail)
-
- if self.cert.get_AIA()[0] != self.ca_detail.ca_cert_uri:
- logger.debug("%r AIA changed, regenerating", self)
- return self.regenerate(publisher = publisher, fast = fast)
-
-
- def generate(self, publisher, fast = False):
- """
- Generate a Ghostbuster record
-
- Once we have the right covering certificate, we generate the
- ghostbuster payload, generate a new EE certificate, use the EE
- certificate to sign the ghostbuster payload, publish the result,
- then throw away the private key for the EE cert. This is modeled
- after the way we handle ROAs.
-
- If fast is set, we leave generating the new manifest for our
- caller to handle, presumably at the end of a bulk operation.
- """
-
- resources = rpki.resource_set.resource_bag.from_inheritance()
- keypair = rpki.x509.RSA.generate()
- self.cert = self.ca_detail.issue_ee(
- ca = self.ca_detail.ca,
- resources = resources,
- subject_key = keypair.get_public(),
- sia = (None, None, self.uri_from_key(keypair), self.ca_detail.ca.parent.repository.rrdp_notification_uri))
- self.ghostbuster = rpki.x509.Ghostbuster.build(self.vcard, keypair, (self.cert,))
- self.published = rpki.sundial.now()
- self.save()
- logger.debug("Generating Ghostbuster record %r", self.uri)
- publisher.queue(
- uri = self.uri,
- new_obj = self.ghostbuster,
- repository = self.ca_detail.ca.parent.repository,
- handler = self.published_callback)
- if not fast:
- self.ca_detail.generate_manifest(publisher = publisher)
-
-
- def published_callback(self, pdu):
- """
- Check publication result.
- """
-
- rpki.publication.raise_if_error(pdu)
- self.published = None
- self.save()
-
-
- def revoke(self, publisher, regenerate = False, allow_failure = False, fast = False):
- """
- Withdraw Ghostbuster associated with this ghostbuster_obj.
-
- In order to preserve make-before-break properties without
- duplicating code, this method also handles generating a
- replacement ghostbuster when requested.
-
- If allow_failure is set, failing to withdraw the ghostbuster will not be
- considered an error.
-
- If fast is set, SQL actions will be deferred, on the assumption
- that our caller will handle regenerating CRL and manifest and
- flushing the SQL cache.
- """
-
- ca_detail = self.ca_detail
- logger.debug("%s %r, ca_detail %r state is %s",
- "Regenerating" if regenerate else "Not regenerating",
- self, ca_detail, ca_detail.state)
- if regenerate:
- self.generate(publisher = publisher, fast = fast)
- logger.debug("Withdrawing %r %s and revoking its EE cert", self, self.uri)
- RevokedCert.revoke(cert = self.cert, ca_detail = ca_detail)
- publisher.queue(uri = self.uri,
- old_obj = self.ghostbuster,
- repository = ca_detail.ca.parent.repository,
- handler = False if allow_failure else None)
- if not regenerate:
- self.delete()
- if not fast:
- ca_detail.generate_crl(publisher = publisher)
- ca_detail.generate_manifest(publisher = publisher)
-
-
- def regenerate(self, publisher, fast = False):
- """
- Reissue Ghostbuster associated with this ghostbuster_obj.
- """
+ vcard = models.TextField()
+ cert = CertificateField()
+ ghostbuster = GhostbusterField()
+ published = SundialField(null = True)
+ tenant = models.ForeignKey(Tenant, related_name = "ghostbusters")
+ ca_detail = models.ForeignKey(CADetail, related_name = "ghostbusters")
+
+
+ def update(self, publisher, fast = False):
+ """
+ Bring this ghostbuster_obj up to date if necesssary.
+ """
+
+ if self.ghostbuster is None:
+ logger.debug("Ghostbuster record doesn't exist, generating")
+ return self.generate(publisher = publisher, fast = fast)
+
+ now = rpki.sundial.now()
+ regen_time = self.cert.getNotAfter() - rpki.sundial.timedelta(seconds = self.tenant.regen_margin)
+
+ if now > regen_time and self.cert.getNotAfter() < self.ca_detail.latest_ca_cert.getNotAfter():
+ logger.debug("%r past threshold %s, regenerating", self, regen_time)
+ return self.regenerate(publisher = publisher, fast = fast)
+
+ if now > regen_time:
+ logger.warning("%r is past threshold %s but so is issuer %r, can't regenerate", self, regen_time, self.ca_detail)
+
+ if self.cert.get_AIA()[0] != self.ca_detail.ca_cert_uri:
+ logger.debug("%r AIA changed, regenerating", self)
+ return self.regenerate(publisher = publisher, fast = fast)
+
+
+ def generate(self, publisher, fast = False):
+ """
+ Generate a Ghostbuster record
+
+ Once we have the right covering certificate, we generate the
+ ghostbuster payload, generate a new EE certificate, use the EE
+ certificate to sign the ghostbuster payload, publish the result,
+ then throw away the private key for the EE cert. This is modeled
+ after the way we handle ROAs.
+
+ If fast is set, we leave generating the new manifest for our
+ caller to handle, presumably at the end of a bulk operation.
+ """
+
+ resources = rpki.resource_set.resource_bag.from_inheritance()
+ keypair = rpki.x509.RSA.generate()
+ self.cert = self.ca_detail.issue_ee(
+ ca = self.ca_detail.ca,
+ resources = resources,
+ subject_key = keypair.get_public(),
+ sia = (None, None, self.uri_from_key(keypair), self.ca_detail.ca.parent.repository.rrdp_notification_uri))
+ self.ghostbuster = rpki.x509.Ghostbuster.build(self.vcard, keypair, (self.cert,))
+ self.published = rpki.sundial.now()
+ self.save()
+ logger.debug("Generating Ghostbuster record %r", self.uri)
+ publisher.queue(
+ uri = self.uri,
+ new_obj = self.ghostbuster,
+ repository = self.ca_detail.ca.parent.repository,
+ handler = self.published_callback)
+ if not fast:
+ self.ca_detail.generate_manifest(publisher = publisher)
+
+
+ def published_callback(self, pdu):
+ """
+ Check publication result.
+ """
+
+ rpki.publication.raise_if_error(pdu)
+ self.published = None
+ self.save()
+
+
+ def revoke(self, publisher, regenerate = False, allow_failure = False, fast = False):
+ """
+ Withdraw Ghostbuster associated with this ghostbuster_obj.
+
+ In order to preserve make-before-break properties without
+ duplicating code, this method also handles generating a
+ replacement ghostbuster when requested.
+
+ If allow_failure is set, failing to withdraw the ghostbuster will not be
+ considered an error.
+
+ If fast is set, SQL actions will be deferred, on the assumption
+ that our caller will handle regenerating CRL and manifest and
+ flushing the SQL cache.
+ """
+
+ ca_detail = self.ca_detail
+ logger.debug("%s %r, ca_detail %r state is %s",
+ "Regenerating" if regenerate else "Not regenerating",
+ self, ca_detail, ca_detail.state)
+ if regenerate:
+ self.generate(publisher = publisher, fast = fast)
+ logger.debug("Withdrawing %r %s and revoking its EE cert", self, self.uri)
+ RevokedCert.revoke(cert = self.cert, ca_detail = ca_detail)
+ publisher.queue(uri = self.uri,
+ old_obj = self.ghostbuster,
+ repository = ca_detail.ca.parent.repository,
+ handler = False if allow_failure else None)
+ if not regenerate:
+ self.delete()
+ if not fast:
+ ca_detail.generate_crl(publisher = publisher)
+ ca_detail.generate_manifest(publisher = publisher)
+
+
+ def regenerate(self, publisher, fast = False):
+ """
+ Reissue Ghostbuster associated with this ghostbuster_obj.
+ """
+
+ if self.ghostbuster is None:
+ self.generate(publisher = publisher, fast = fast)
+ else:
+ self.revoke(publisher = publisher, regenerate = True, fast = fast)
- if self.ghostbuster is None:
- self.generate(publisher = publisher, fast = fast)
- else:
- self.revoke(publisher = publisher, regenerate = True, fast = fast)
+ def uri_from_key(self, key):
+ """
+ Return publication URI for a public key.
+ """
- def uri_from_key(self, key):
- """
- Return publication URI for a public key.
- """
+ return self.ca_detail.ca.sia_uri + key.gSKI() + ".gbr"
- return self.ca_detail.ca.sia_uri + key.gSKI() + ".gbr"
+ @property
+ def uri(self):
+ """
+ Return the publication URI for this ghostbuster_obj's ghostbuster.
+ """
- @property
- def uri(self):
- """
- Return the publication URI for this ghostbuster_obj's ghostbuster.
- """
+ return self.ca_detail.ca.sia_uri + self.uri_tail
- 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
+ ghostbuster_obj's ghostbuster.
+ """
- @property
- def uri_tail(self):
- """
- Return the tail (filename portion) of the publication URI for this
- ghostbuster_obj's ghostbuster.
- """
-
- return self.cert.gSKI() + ".gbr"
+ return self.cert.gSKI() + ".gbr"
class RevokedCert(models.Model):
- serial = models.BigIntegerField()
- revoked = SundialField()
- expires = SundialField()
- ca_detail = models.ForeignKey(CADetail, related_name = "revoked_certs")
+ serial = models.BigIntegerField()
+ revoked = SundialField()
+ expires = SundialField()
+ ca_detail = models.ForeignKey(CADetail, related_name = "revoked_certs")
- @classmethod
- def revoke(cls, cert, ca_detail):
- """
- Revoke a certificate.
- """
+ @classmethod
+ def revoke(cls, cert, ca_detail):
+ """
+ Revoke a certificate.
+ """
- return cls.objects.create(
- serial = cert.getSerial(),
- expires = cert.getNotAfter(),
- revoked = rpki.sundial.now(),
- ca_detail = ca_detail)
+ return cls.objects.create(
+ serial = cert.getSerial(),
+ expires = cert.getNotAfter(),
+ revoked = rpki.sundial.now(),
+ ca_detail = ca_detail)
class ROA(models.Model):
- asn = models.BigIntegerField()
- ipv4 = models.TextField(null = True)
- ipv6 = models.TextField(null = True)
- cert = CertificateField()
- roa = ROAField()
- published = SundialField(null = True)
- tenant = models.ForeignKey(Tenant, related_name = "roas")
- ca_detail = models.ForeignKey(CADetail, related_name = "roas")
-
-
- def update(self, publisher, fast = False):
- """
- Bring ROA up to date if necesssary.
- """
-
- if self.roa is None:
- logger.debug("%r doesn't exist, generating", self)
- return self.generate(publisher = publisher, fast = fast)
-
- if self.ca_detail is None:
- logger.debug("%r has no associated ca_detail, generating", self)
- return self.generate(publisher = publisher, fast = fast)
-
- if self.ca_detail.state != "active":
- logger.debug("ca_detail associated with %r not active (state %s), regenerating", self, self.ca_detail.state)
- return self.regenerate(publisher = publisher, fast = fast)
-
- now = rpki.sundial.now()
- regen_time = self.cert.getNotAfter() - rpki.sundial.timedelta(seconds = self.tenant.regen_margin)
-
- if now > regen_time and self.cert.getNotAfter() < self.ca_detail.latest_ca_cert.getNotAfter():
- logger.debug("%r past threshold %s, regenerating", self, regen_time)
- return self.regenerate(publisher = publisher, fast = fast)
-
- if now > regen_time:
- logger.warning("%r is past threshold %s but so is issuer %r, can't regenerate", self, regen_time, self.ca_detail)
-
- ca_resources = self.ca_detail.latest_ca_cert.get_3779resources()
- ee_resources = self.cert.get_3779resources()
-
- if ee_resources.oversized(ca_resources):
- logger.debug("%r oversized with respect to CA, regenerating", self)
- return self.regenerate(publisher = publisher, fast = fast)
-
- v4 = rpki.resource_set.resource_set_ipv4(self.ipv4)
- v6 = rpki.resource_set.resource_set_ipv6(self.ipv6)
-
- if ee_resources.v4 != v4 or ee_resources.v6 != v6:
- logger.debug("%r resources do not match EE, regenerating", self)
- return self.regenerate(publisher = publisher, fast = fast)
-
- if self.cert.get_AIA()[0] != self.ca_detail.ca_cert_uri:
- logger.debug("%r AIA changed, regenerating", self)
- return self.regenerate(publisher = publisher, fast = fast)
-
-
- def generate(self, publisher, fast = False):
- """
- Generate a ROA.
-
- 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 fast is set, we leave generating the new manifest for our
- caller to handle, presumably at the end of a bulk operation.
- """
-
- if self.ipv4 is None and self.ipv6 is None:
- raise rpki.exceptions.EmptyROAPrefixList
-
- v4 = rpki.resource_set.resource_set_ipv4(self.ipv4)
- v6 = rpki.resource_set.resource_set_ipv6(self.ipv6)
-
- # http://stackoverflow.com/questions/26270042/how-do-you-catch-this-exception
- # "Django is amazing when its not terrifying."
- try:
- ca_detail = self.ca_detail
- except CADetail.DoesNotExist:
- ca_detail = None
-
- if ca_detail is not None and ca_detail.state == "active" and not ca_detail.has_expired():
- logger.debug("Keeping old ca_detail %r for ROA %r", ca_detail, self)
- else:
- logger.debug("Searching for new ca_detail for ROA %r", self)
- for ca_detail in CADetail.objects.filter(ca__parent__tenant = self.tenant, state = "active"):
- resources = ca_detail.latest_ca_cert.get_3779resources()
- if not ca_detail.has_expired() and v4.issubset(resources.v4) and v6.issubset(resources.v6):
- logger.debug("Using new ca_detail %r for ROA %r", ca_detail, self)
- self.ca_detail = ca_detail
- break
- else:
- raise rpki.exceptions.NoCoveringCertForROA("Could not find a certificate covering %r" % self)
-
- resources = rpki.resource_set.resource_bag(v4 = v4, v6 = v6)
- keypair = rpki.x509.RSA.generate()
-
- self.cert = self.ca_detail.issue_ee(
- ca = self.ca_detail.ca,
- resources = resources,
- subject_key = keypair.get_public(),
- sia = (None, None, self.uri_from_key(keypair), self.ca_detail.ca.parent.repository.rrdp_notification_uri))
- self.roa = rpki.x509.ROA.build(self.asn,
- rpki.resource_set.roa_prefix_set_ipv4(self.ipv4),
- rpki.resource_set.roa_prefix_set_ipv6(self.ipv6),
- keypair,
- (self.cert,))
- self.published = rpki.sundial.now()
- self.save()
-
- logger.debug("Generating %r URI %s", self, self.uri)
- publisher.queue(uri = self.uri, new_obj = self.roa,
- repository = self.ca_detail.ca.parent.repository,
- handler = self.published_callback)
- if not fast:
- self.ca_detail.generate_manifest(publisher = publisher)
-
-
- def published_callback(self, pdu):
- """
- Check publication result.
- """
-
- rpki.publication.raise_if_error(pdu)
- self.published = None
- self.save()
-
-
- def revoke(self, publisher, regenerate = False, allow_failure = False, fast = 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.
-
- If allow_failure is set, failing to withdraw the ROA will not be
- considered an error.
-
- If fast is set, SQL actions will be deferred, on the assumption
- that our caller will handle regenerating CRL and manifest and
- flushing the SQL cache.
- """
-
- ca_detail = self.ca_detail
- logger.debug("%s %r, ca_detail %r state is %s",
- "Regenerating" if regenerate else "Not regenerating",
- self, ca_detail, ca_detail.state)
- if regenerate:
- self.generate(publisher = publisher, fast = fast)
- logger.debug("Withdrawing %r %s and revoking its EE cert", self, self.uri)
- RevokedCert.revoke(cert = self.cert, ca_detail = ca_detail)
- publisher.queue(uri = self.uri, old_obj = self.roa,
- repository = ca_detail.ca.parent.repository,
- handler = False if allow_failure else None)
- if not regenerate:
- self.delete()
- if not fast:
- ca_detail.generate_crl(publisher = publisher)
- ca_detail.generate_manifest(publisher = publisher)
-
-
- def regenerate(self, publisher, fast = False):
- """
- Reissue ROA associated with this roa_obj.
- """
+ asn = models.BigIntegerField()
+ ipv4 = models.TextField(null = True)
+ ipv6 = models.TextField(null = True)
+ cert = CertificateField()
+ roa = ROAField()
+ published = SundialField(null = True)
+ tenant = models.ForeignKey(Tenant, related_name = "roas")
+ ca_detail = models.ForeignKey(CADetail, related_name = "roas")
+
+
+ def update(self, publisher, fast = False):
+ """
+ Bring ROA up to date if necesssary.
+ """
+
+ if self.roa is None:
+ logger.debug("%r doesn't exist, generating", self)
+ return self.generate(publisher = publisher, fast = fast)
+
+ if self.ca_detail is None:
+ logger.debug("%r has no associated ca_detail, generating", self)
+ return self.generate(publisher = publisher, fast = fast)
+
+ if self.ca_detail.state != "active":
+ logger.debug("ca_detail associated with %r not active (state %s), regenerating", self, self.ca_detail.state)
+ return self.regenerate(publisher = publisher, fast = fast)
+
+ now = rpki.sundial.now()
+ regen_time = self.cert.getNotAfter() - rpki.sundial.timedelta(seconds = self.tenant.regen_margin)
+
+ if now > regen_time and self.cert.getNotAfter() < self.ca_detail.latest_ca_cert.getNotAfter():
+ logger.debug("%r past threshold %s, regenerating", self, regen_time)
+ return self.regenerate(publisher = publisher, fast = fast)
+
+ if now > regen_time:
+ logger.warning("%r is past threshold %s but so is issuer %r, can't regenerate", self, regen_time, self.ca_detail)
+
+ ca_resources = self.ca_detail.latest_ca_cert.get_3779resources()
+ ee_resources = self.cert.get_3779resources()
+
+ if ee_resources.oversized(ca_resources):
+ logger.debug("%r oversized with respect to CA, regenerating", self)
+ return self.regenerate(publisher = publisher, fast = fast)
+
+ v4 = rpki.resource_set.resource_set_ipv4(self.ipv4)
+ v6 = rpki.resource_set.resource_set_ipv6(self.ipv6)
+
+ if ee_resources.v4 != v4 or ee_resources.v6 != v6:
+ logger.debug("%r resources do not match EE, regenerating", self)
+ return self.regenerate(publisher = publisher, fast = fast)
+
+ if self.cert.get_AIA()[0] != self.ca_detail.ca_cert_uri:
+ logger.debug("%r AIA changed, regenerating", self)
+ return self.regenerate(publisher = publisher, fast = fast)
+
+
+ def generate(self, publisher, fast = False):
+ """
+ Generate a ROA.
+
+ 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 fast is set, we leave generating the new manifest for our
+ caller to handle, presumably at the end of a bulk operation.
+ """
+
+ if self.ipv4 is None and self.ipv6 is None:
+ raise rpki.exceptions.EmptyROAPrefixList
+
+ v4 = rpki.resource_set.resource_set_ipv4(self.ipv4)
+ v6 = rpki.resource_set.resource_set_ipv6(self.ipv6)
+
+ # http://stackoverflow.com/questions/26270042/how-do-you-catch-this-exception
+ # "Django is amazing when its not terrifying."
+ try:
+ ca_detail = self.ca_detail
+ except CADetail.DoesNotExist:
+ ca_detail = None
+
+ if ca_detail is not None and ca_detail.state == "active" and not ca_detail.has_expired():
+ logger.debug("Keeping old ca_detail %r for ROA %r", ca_detail, self)
+ else:
+ logger.debug("Searching for new ca_detail for ROA %r", self)
+ for ca_detail in CADetail.objects.filter(ca__parent__tenant = self.tenant, state = "active"):
+ resources = ca_detail.latest_ca_cert.get_3779resources()
+ if not ca_detail.has_expired() and v4.issubset(resources.v4) and v6.issubset(resources.v6):
+ logger.debug("Using new ca_detail %r for ROA %r", ca_detail, self)
+ self.ca_detail = ca_detail
+ break
+ else:
+ raise rpki.exceptions.NoCoveringCertForROA("Could not find a certificate covering %r" % self)
+
+ resources = rpki.resource_set.resource_bag(v4 = v4, v6 = v6)
+ keypair = rpki.x509.RSA.generate()
+
+ self.cert = self.ca_detail.issue_ee(
+ ca = self.ca_detail.ca,
+ resources = resources,
+ subject_key = keypair.get_public(),
+ sia = (None, None, self.uri_from_key(keypair), self.ca_detail.ca.parent.repository.rrdp_notification_uri))
+ self.roa = rpki.x509.ROA.build(self.asn,
+ rpki.resource_set.roa_prefix_set_ipv4(self.ipv4),
+ rpki.resource_set.roa_prefix_set_ipv6(self.ipv6),
+ keypair,
+ (self.cert,))
+ self.published = rpki.sundial.now()
+ self.save()
+
+ logger.debug("Generating %r URI %s", self, self.uri)
+ publisher.queue(uri = self.uri, new_obj = self.roa,
+ repository = self.ca_detail.ca.parent.repository,
+ handler = self.published_callback)
+ if not fast:
+ self.ca_detail.generate_manifest(publisher = publisher)
+
+
+ def published_callback(self, pdu):
+ """
+ Check publication result.
+ """
+
+ rpki.publication.raise_if_error(pdu)
+ self.published = None
+ self.save()
+
+
+ def revoke(self, publisher, regenerate = False, allow_failure = False, fast = 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.
+
+ If allow_failure is set, failing to withdraw the ROA will not be
+ considered an error.
+
+ If fast is set, SQL actions will be deferred, on the assumption
+ that our caller will handle regenerating CRL and manifest and
+ flushing the SQL cache.
+ """
+
+ ca_detail = self.ca_detail
+ logger.debug("%s %r, ca_detail %r state is %s",
+ "Regenerating" if regenerate else "Not regenerating",
+ self, ca_detail, ca_detail.state)
+ if regenerate:
+ self.generate(publisher = publisher, fast = fast)
+ logger.debug("Withdrawing %r %s and revoking its EE cert", self, self.uri)
+ RevokedCert.revoke(cert = self.cert, ca_detail = ca_detail)
+ publisher.queue(uri = self.uri, old_obj = self.roa,
+ repository = ca_detail.ca.parent.repository,
+ handler = False if allow_failure else None)
+ if not regenerate:
+ self.delete()
+ if not fast:
+ ca_detail.generate_crl(publisher = publisher)
+ ca_detail.generate_manifest(publisher = publisher)
+
+
+ def regenerate(self, publisher, fast = False):
+ """
+ Reissue ROA associated with this roa_obj.
+ """
+
+ if self.ca_detail is None:
+ self.generate(publisher = publisher, fast = fast)
+ else:
+ self.revoke(publisher = publisher, regenerate = True, fast = fast)
- if self.ca_detail is None:
- self.generate(publisher = publisher, fast = fast)
- else:
- self.revoke(publisher = publisher, regenerate = True, fast = fast)
+ def uri_from_key(self, key):
+ """
+ Return publication URI for a public key.
+ """
- def uri_from_key(self, key):
- """
- Return publication URI for a public key.
- """
+ return self.ca_detail.ca.sia_uri + key.gSKI() + ".roa"
- return self.ca_detail.ca.sia_uri + key.gSKI() + ".roa"
+ @property
+ def uri(self):
+ """
+ Return the publication URI for this roa_obj's ROA.
+ """
- @property
- def uri(self):
- """
- Return the publication URI for this roa_obj's ROA.
- """
-
- return self.ca_detail.ca.sia_uri + self.uri_tail
+ 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
- roa_obj's ROA.
- """
+ @property
+ def uri_tail(self):
+ """
+ Return the tail (filename portion) of the publication URI for this
+ roa_obj's ROA.
+ """
- return self.cert.gSKI() + ".roa"
+ return self.cert.gSKI() + ".roa"