From 784b20d33070a8450b23d846a0d936a356646739 Mon Sep 17 00:00:00 2001 From: Rob Austein Date: Sat, 23 Apr 2016 15:03:37 +0000 Subject: Internal root sort of working, but only sort of. It's skipping the worker CA and going straight from the root to certifying children, which is wrong. However...this is far enough along that we can now remove all the rootd glorp, which is a worthwhile simplification in its own right, so checkpoint here, remove rootd glorp, then figure out what's wrong with the internal certificate hierarchy. rcynic does validate the current output, given a manually constructed TAL, even if the current output isn't quite what it should be. So we should also be able to sort out the new TAL generation code now. Yes, checking in a version that works for the wrong reasons is weird, but the current sort-of-broken state lets us confirm that the lower levels of the tree are still correct as we go, which would be much harder if the poor thing just sat there and whimpered until we had the new internal CA code completely finished. svn path=/branches/tk705/; revision=6376 --- ca/tests/smoketest.py | 6 +- ca/tests/yamlconf.py | 12 +- ca/tests/yamltest.py | 12 +- rp/config/rpki-generate-root-certificate | 6 +- rpki/irdb/migrations/0002_root.py | 31 ++-- rpki/irdb/models.py | 19 +-- rpki/irdb/zookeeper.py | 80 ++++----- rpki/resource_set.py | 8 + rpki/rootd.py | 11 +- rpki/rpkid.py | 8 +- rpki/rpkid_tasks.py | 16 +- rpki/rpkidb/models.py | 276 ++++++++++++++++++++++++++++--- rpki/sundial.py | 2 +- 13 files changed, 361 insertions(+), 126 deletions(-) diff --git a/ca/tests/smoketest.py b/ca/tests/smoketest.py index 9d82c640..6479883e 100644 --- a/ca/tests/smoketest.py +++ b/ca/tests/smoketest.py @@ -537,9 +537,9 @@ class allocation(object): if valid_until is None and "valid_for" in yaml: valid_until = rpki.sundial.now() + rpki.sundial.timedelta.parse(yaml["valid_for"]) self.base = rpki.resource_set.resource_bag( - asn = rpki.resource_set.resource_set_as(yaml.get("asn")), - v4 = rpki.resource_set.resource_set_ipv4(yaml.get("ipv4")), - v6 = rpki.resource_set.resource_set_ipv6(yaml.get("ipv6")), + asn = str(yaml.get("asn", "")), + v4 = yaml.get("ipv4"), + v6 = yaml.get("ipv6"), valid_until = valid_until) self.sia_base = yaml.get("sia_base") if "crl_interval" in yaml: diff --git a/ca/tests/yamlconf.py b/ca/tests/yamlconf.py index 52c4da26..08827acd 100644 --- a/ca/tests/yamlconf.py +++ b/ca/tests/yamlconf.py @@ -218,9 +218,9 @@ class allocation(object): if valid_until is None and "valid_for" in y: valid_until = rpki.sundial.now() + rpki.sundial.timedelta.parse(y["valid_for"]) self.base = rpki.resource_set.resource_bag( - asn = rpki.resource_set.resource_set_as(y.get("asn")), - v4 = rpki.resource_set.resource_set_ipv4(y.get("ipv4")), - v6 = rpki.resource_set.resource_set_ipv6(y.get("ipv6")), + asn = str(y.get("asn", "")), + v4 = y.get("ipv4"), + v6 = y.get("ipv6"), valid_until = valid_until) if "crl_interval" in y: self.crl_interval = rpki.sundial.timedelta.parse(y["crl_interval"]).convert_to_seconds() @@ -514,9 +514,9 @@ class allocation(object): assert self.is_root and not self.is_hosted root_resources = rpki.resource_set.resource_bag( - asn = rpki.resource_set.resource_set_as("0-4294967295"), - v4 = rpki.resource_set.resource_set_ipv4("0.0.0.0/0"), - v6 = rpki.resource_set.resource_set_ipv6("::/0")) + asn = "0-4294967295", + v4 = "0.0.0.0/0", + v6 = "::/0") root_key = rpki.x509.RSA.generate(quiet = True) diff --git a/ca/tests/yamltest.py b/ca/tests/yamltest.py index 38b5bdac..d413df5c 100755 --- a/ca/tests/yamltest.py +++ b/ca/tests/yamltest.py @@ -244,9 +244,9 @@ class allocation(object): if valid_until is None and "valid_for" in yaml: valid_until = rpki.sundial.now() + rpki.sundial.timedelta.parse(yaml["valid_for"]) self.base = rpki.resource_set.resource_bag( - asn = rpki.resource_set.resource_set_as(yaml.get("asn")), - v4 = rpki.resource_set.resource_set_ipv4(yaml.get("ipv4")), - v6 = rpki.resource_set.resource_set_ipv6(yaml.get("ipv6")), + asn = str(yaml.get("asn", "")), + v4 = yaml.get("ipv4"), + v6 = yaml.get("ipv6"), valid_until = valid_until) if "crl_interval" in yaml: self.crl_interval = rpki.sundial.timedelta.parse(yaml["crl_interval"]).convert_to_seconds() @@ -788,9 +788,9 @@ def create_root_certificate(db_root): print "Creating rootd RPKI root certificate" root_resources = rpki.resource_set.resource_bag( - asn = rpki.resource_set.resource_set_as("0-4294967295"), - v4 = rpki.resource_set.resource_set_ipv4("0.0.0.0/0"), - v6 = rpki.resource_set.resource_set_ipv6("::/0")) + asn = "0-4294967295", + v4 = "0.0.0.0/0", + v6 = "::/0") root_key = rpki.x509.RSA.generate(quiet = True) diff --git a/rp/config/rpki-generate-root-certificate b/rp/config/rpki-generate-root-certificate index d4ee08fd..10b8b194 100755 --- a/rp/config/rpki-generate-root-certificate +++ b/rp/config/rpki-generate-root-certificate @@ -37,9 +37,9 @@ cfg.argparser.add_argument("--tal", help = "TAL file", default = args = cfg.argparser.parse_args() resources = rpki.resource_set.resource_bag( - asn = rpki.resource_set.resource_set_as(args.asns), - v4 = rpki.resource_set.resource_set_ipv4(args.ipv4), - v6 = rpki.resource_set.resource_set_ipv6(args.ipv6)) + asn = args.asns, + v4 = args.ipv4, + v6 = args.ipv6) keypair = rpki.x509.RSA.generate(quiet = True) diff --git a/rpki/irdb/migrations/0002_root.py b/rpki/irdb/migrations/0002_root.py index 73c08dde..6bdc060e 100644 --- a/rpki/irdb/migrations/0002_root.py +++ b/rpki/irdb/migrations/0002_root.py @@ -2,8 +2,6 @@ from __future__ import unicode_literals from django.db import migrations, models -import rpki.irdb.models -import rpki.fields class Migration(migrations.Migration): @@ -13,22 +11,19 @@ class Migration(migrations.Migration): ] operations = [ - migrations.CreateModel( - name='Root', - fields=[ - ('turtle_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='irdb.Turtle')), - ('certificate', rpki.fields.CertificateField()), - ('handle', rpki.irdb.models.HandleField(max_length=120)), - ('ta', rpki.fields.CertificateField()), - ('asn_resources', models.TextField()), - ('ipv4_resources', models.TextField()), - ('ipv6_resources', models.TextField()), - ('issuer', models.OneToOneField(related_name='root', to='irdb.ResourceHolderCA')), - ], - bases=('irdb.turtle', models.Model), + migrations.AddField( + model_name='parent', + name='asn_resources', + field=models.TextField(blank=True), ), - migrations.AlterUniqueTogether( - name='root', - unique_together=set([('issuer', 'handle')]), + migrations.AddField( + model_name='parent', + name='ipv4_resources', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='parent', + name='ipv6_resources', + field=models.TextField(blank=True), ), ] diff --git a/rpki/irdb/models.py b/rpki/irdb/models.py index dc3723d8..ab81aa84 100644 --- a/rpki/irdb/models.py +++ b/rpki/irdb/models.py @@ -452,27 +452,14 @@ class Parent(CrossCertification, Turtle): repository_type = EnumField(choices = ("none", "offer", "referral")) referrer = HandleField(null = True, blank = True) referral_authorization = SignedReferralField(null = True, blank = True) + asn_resources = django.db.models.TextField(blank = True) # root only + ipv4_resources = django.db.models.TextField(blank = True) # root only + ipv6_resources = django.db.models.TextField(blank = True) # root only # This shouldn't be necessary class Meta: unique_together = ("issuer", "handle") -class Root(CrossCertification, Turtle): - # - # This is sort of a cross between a Rootd and a Parent with extra - # fields for the root resources. As with Parent, the private key - # comes from a BSC rather than from a server EE cert as with - # Rootd, so this looks looks to us like a cross certification (of - # ourself). We may want to revisit this. - # - issuer = django.db.models.OneToOneField(ResourceHolderCA, related_name = "root") - asn_resources = django.db.models.TextField() - ipv4_resources = django.db.models.TextField() - ipv6_resources = django.db.models.TextField() - - class Meta: - unique_together = ("issuer", "handle") - class ROARequest(django.db.models.Model): issuer = django.db.models.ForeignKey(ResourceHolderCA, related_name = "roa_requests") asn = django.db.models.BigIntegerField() diff --git a/rpki/irdb/zookeeper.py b/rpki/irdb/zookeeper.py index 7446e7c7..1f6fb6c2 100644 --- a/rpki/irdb/zookeeper.py +++ b/rpki/irdb/zookeeper.py @@ -368,34 +368,23 @@ class Zookeeper(object): def configure_root(self, handle, resources): # XXX This should be some other exception, not an assertion - assert self.run_rpkid and self.run_pubd and self.run_rootd + assert self.run_rpkid and self.run_pubd - rpki.irdb.models.Rootd.objects.get_or_certify( - issuer = self.resource_ca, - service_uri = "http://localhost:%s/" % self.cfg.get("rootd_server_port", - section = myrpki_section)) + if not handle: + handle = self.handle - rpki.irdb.models.Root.objects.get_or_certify( - handle = handle or self.handle, - issuer = self.resource_ca, - ta = self.resource_ca.certificate, - asn_resources = str(resources.asn), - ipv4_resources = str(resources.v4), - ipv6_resources = str(resources.v6)) + parent = rpki.irdb.models.Parent.objects.get_or_certify( + issuer = self.resource_ca, + handle = handle, + parent_handle = handle, + child_handle = handle, + ta = self.resource_ca.certificate, + repository_type = "none", + asn_resources = str(resources.asn), + ipv4_resources = str(resources.v4), + ipv6_resources = str(resources.v6))[0] - return self.generate_root_repository_offer() - - - def generate_root_repository_offer(self): - """ - Generate repository offer for rootd. Split out of - configure_rootd() because that's easier for the GUI. - """ - - e = Element(tag_oob_publisher_request, nsmap = oob_nsmap, version = oob_version, - publisher_handle = self.handle) - B64Element(e, tag_oob_publisher_bpki_ta, self.resource_ca.certificate) - return etree_wrapper(e, msg = 'This is the "repository offer" file for you to use if you want to publish in your own repository') + return self.generate_repository_request(parent) def write_bpki_files(self): @@ -1341,7 +1330,9 @@ class Zookeeper(object): # might make a case for a day instead, but we've been running with # six hours for a while now and haven't seen a lot of whining. - tenant_crl_interval = self.cfg.getint("tenant_crl_interval", 6 * 60 * 60, section = myrpki_section) + tenant_crl_interval = self.cfg.getint("tenant_crl_interval", + 6 * 60 * 60, + section = myrpki_section) # regen_margin now just controls how long before RPKI certificate # expiration we should regenerate; it used to control the interval @@ -1353,7 +1344,9 @@ class Zookeeper(object): # that this will regenerate certificates just *before* the # companion cron job warns of impending doom. - tenant_regen_margin = self.cfg.getint("tenant_regen_margin", 14 * 24 * 60 * 60 + 2 * 60, section = myrpki_section) + tenant_regen_margin = self.cfg.getint("tenant_regen_margin", + 14 * 24 * 60 * 60 + 2 * 60, + section = myrpki_section) # See what rpkid already has on file for this entity. @@ -1390,7 +1383,8 @@ class Zookeeper(object): if (tenant_pdu is None or tenant_pdu.get("crl_interval") != str(tenant_crl_interval) or tenant_pdu.get("regen_margin") != str(tenant_regen_margin) or - tenant_pdu.findtext(rpki.left_right.tag_bpki_cert, "").decode("base64") != tenant_cert.certificate.get_DER()): + tenant_pdu.findtext(rpki.left_right.tag_bpki_cert, + "").decode("base64") != tenant_cert.certificate.get_DER()): q_pdu = SubElement(q_msg, rpki.left_right.tag_tenant, action = "create" if tenant_pdu is None else "set", tag = "tenant", @@ -1422,7 +1416,8 @@ class Zookeeper(object): # can finish setting up the BSC before anything tries to use it. if len(q_msg) > 0: - SubElement(q_msg, rpki.left_right.tag_bsc, action = "list", tag = "bsc", tenant_handle = ca.handle) + SubElement(q_msg, rpki.left_right.tag_bsc, + action = "list", tag = "bsc", tenant_handle = ca.handle) r_msg = self.call_rpkid(q_msg) bsc_pdus = dict((r_pdu.get("bsc_handle"), r_pdu) for r_pdu in r_msg.getiterator(rpki.left_right.tag_bsc) @@ -1439,8 +1434,10 @@ class Zookeeper(object): handle = bsc_handle, pkcs10 = rpki.x509.PKCS10(Base64 = bsc_pkcs10.text))[0] - if (bsc_pdu.findtext(rpki.left_right.tag_signing_cert, "").decode("base64") != bsc.certificate.get_DER() or - bsc_pdu.findtext(rpki.left_right.tag_signing_cert_crl, "").decode("base64") != ca.latest_crl.get_DER()): + if (bsc_pdu.findtext(rpki.left_right.tag_signing_cert, + "").decode("base64") != bsc.certificate.get_DER() or + bsc_pdu.findtext(rpki.left_right.tag_signing_cert_crl, + "").decode("base64") != ca.latest_crl.get_DER()): q_pdu = SubElement(q_msg, rpki.left_right.tag_bsc, action = "set", tag = "bsc", @@ -1463,7 +1460,8 @@ class Zookeeper(object): repository_pdu.get("bsc_handle") != bsc_handle or repository_pdu.get("peer_contact_uri") != repository.service_uri or repository_pdu.get("rrdp_notification_uri") != repository.rrdp_notification_uri or - repository_pdu.findtext(rpki.left_right.tag_bpki_cert, "").decode("base64") != repository.certificate.get_DER()): + repository_pdu.findtext(rpki.left_right.tag_bpki_cert, + "").decode("base64") != repository.certificate.get_DER()): q_pdu = SubElement(q_msg, rpki.left_right.tag_repository, action = "create" if repository_pdu is None else "set", tag = repository.handle, @@ -1473,7 +1471,8 @@ class Zookeeper(object): peer_contact_uri = repository.service_uri) if repository.rrdp_notification_uri: q_pdu.set("rrdp_notification_uri", repository.rrdp_notification_uri) - SubElement(q_pdu, rpki.left_right.tag_bpki_cert).text = repository.certificate.get_Base64() + SubElement(q_pdu, + rpki.left_right.tag_bpki_cert).text = repository.certificate.get_Base64() for repository_handle in repository_pdus: SubElement(q_msg, rpki.left_right.tag_repository, action = "destroy", @@ -1499,7 +1498,11 @@ class Zookeeper(object): parent_pdu.get("sia_base") != parent.repository.sia_base or parent_pdu.get("sender_name") != parent.child_handle or parent_pdu.get("recipient_name") != parent.parent_handle or - parent_pdu.findtext(rpki.left_right.tag_bpki_cert, "").decode("base64") != parent.certificate.get_DER()): + parent_pdu.get("root_asn_resources", "") != parent.asn_resources or + parent_pdu.get("root_ipv4_resources", "") != parent.ipv4_resources or + parent_pdu.get("root_ipv6_resources", "") != parent.ipv6_resources or + parent_pdu.findtext(rpki.left_right.tag_bpki_cert, + "").decode("base64") != parent.certificate.get_DER()): q_pdu = SubElement(q_msg, rpki.left_right.tag_parent, action = "create" if parent_pdu is None else "set", tag = parent.handle, @@ -1510,14 +1513,17 @@ class Zookeeper(object): peer_contact_uri = parent.service_uri, sia_base = parent.repository.sia_base, sender_name = parent.child_handle, - recipient_name = parent.parent_handle) - SubElement(q_pdu, rpki.left_right.tag_bpki_cert).text = parent.certificate.get_Base64() + recipient_name = parent.parent_handle, + root_asn_resources = parent.asn_resources, + root_ipv4_resources = parent.ipv4_resources, + root_ipv6_resources = parent.ipv6_resources) + SubElement(q_pdu, + rpki.left_right.tag_bpki_cert).text = parent.certificate.get_Base64() except rpki.irdb.models.Repository.DoesNotExist: pass try: - parent_pdu = parent_pdus.pop(ca.handle, None) if (parent_pdu is None or diff --git a/rpki/resource_set.py b/rpki/resource_set.py index 319e2677..055076dd 100644 --- a/rpki/resource_set.py +++ b/rpki/resource_set.py @@ -644,6 +644,14 @@ class resource_bag(object): # Expiration date of resources, for setting certificate notAfter field. def __init__(self, asn = None, v4 = None, v6 = None, valid_until = None): + if isinstance(asn, (str, unicode)): + asn = resource_set_as(asn) + if isinstance(v4, (str, unicode)): + v4 = resource_set_ipv4(v4) + if isinstance(v6, (str, unicode)): + v6 = resource_set_ipv6(v6) + if isinstance(valid_until, (str, unicode)): + valid_until = rpki.sundial.datetime.fromXMLtime(valid_until) self.asn = asn or resource_set_as() self.v4 = v4 or resource_set_ipv4() self.v6 = v6 or resource_set_ipv6() diff --git a/rpki/rootd.py b/rpki/rootd.py index 08259a9a..70669345 100644 --- a/rpki/rootd.py +++ b/rpki/rootd.py @@ -349,16 +349,19 @@ class main(object): q_msg = q_cms.unwrap((self.bpki_ta, self.child_bpki_cert)) q_type = q_msg.get("type") logger.info("Serving %s query", q_type) - 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: self.rpkid_cms_timestamp = q_cms.check_replay(self.rpkid_cms_timestamp, request.path) getattr(self, "handle_" + q_type)(q_msg, r_msg) except Exception, e: logger.exception("Exception processing up-down %s message", q_type) rpki.up_down.generate_error_response_from_exception(r_msg, e, q_type) - request.send_cms_response(rpki.up_down.cms_msg().wrap(r_msg, self.rootd_bpki_key, self.rootd_bpki_cert, - self.rootd_bpki_crl if self.include_bpki_crl else None)) + request.send_cms_response(rpki.up_down.cms_msg().wrap( + r_msg, self.rootd_bpki_key, self.rootd_bpki_cert, + self.rootd_bpki_crl if self.include_bpki_crl else None)) except Exception, e: logger.exception("Unhandled exception processing up-down message") request.send_error(500, "Unhandled exception %s: %s" % (e.__class__.__name__, e)) diff --git a/rpki/rpkid.py b/rpki/rpkid.py index bafad8a9..48645396 100644 --- a/rpki/rpkid.py +++ b/rpki/rpkid.py @@ -371,10 +371,10 @@ class main(object): if len(r_msg) != len(q_msg): raise rpki.exceptions.BadIRDBReply("Expected IRDB response to be same length as query: %s" % r_msg.pretty_print_content()) - bags = [rpki.resource_set.resource_bag(asn = rpki.resource_set.resource_set_as(r_pdu.get("asn")), - v4 = rpki.resource_set.resource_set_ipv4(r_pdu.get("ipv4")), - v6 = rpki.resource_set.resource_set_ipv6(r_pdu.get("ipv6")), - valid_until = rpki.sundial.datetime.fromXMLtime(r_pdu.get("valid_until"))) + bags = [rpki.resource_set.resource_bag(asn = r_pdu.get("asn"), + v4 = r_pdu.get("ipv4"), + v6 = r_pdu.get("ipv6"), + valid_until = r_pdu.get("valid_until")) for r_pdu in r_msg] raise tornado.gen.Return(bags) diff --git a/rpki/rpkid_tasks.py b/rpki/rpkid_tasks.py index e101f1d1..a2545f90 100644 --- a/rpki/rpkid_tasks.py +++ b/rpki/rpkid_tasks.py @@ -209,10 +209,10 @@ class PollParentTask(AbstractTask): ca.sia_uri = sia_uri 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")) + asn = rc.get("resource_set_as"), + v4 = rc.get("resource_set_ipv4"), + v6 = rc.get("resource_set_ipv6"), + valid_until = rc.get("resource_set_notafter")) cert_map = {} @@ -554,10 +554,10 @@ class UpdateEECertificatesTask(AbstractTask): ees = existing.pop(gski, ()) resources = rpki.resource_set.resource_bag( - asn = rpki.resource_set.resource_set_as(r_pdu.get("asn")), - v4 = rpki.resource_set.resource_set_ipv4(r_pdu.get("ipv4")), - v6 = rpki.resource_set.resource_set_ipv6(r_pdu.get("ipv6")), - valid_until = rpki.sundial.datetime.fromXMLtime(r_pdu.get("valid_until"))) + asn = r_pdu.get("asn"), + v4 = r_pdu.get("ipv4"), + v6 = r_pdu.get("ipv6"), + valid_until = r_pdu.get("valid_until")) covering = self.tenant.find_covering_ca_details(resources) ca_details.update(covering) diff --git a/rpki/rpkidb/models.py b/rpki/rpkidb/models.py index d21d7e2e..7cfed2e5 100644 --- a/rpki/rpkidb/models.py +++ b/rpki/rpkidb/models.py @@ -16,6 +16,7 @@ import tornado.httpserver from django.db import models import rpki.left_right +import rpki.sundial from rpki.fields import (EnumField, SundialField, CertificateField, RSAPrivateKeyField, @@ -515,7 +516,7 @@ class Repository(models.Model): body = rpki.publication.cms_msg().wrap(q_msg, self.bsc.private_key_id, self.bsc.signing_cert, self.bsc.signing_cert_crl), headers = { "Content-Type" : rpki.publication.content_type }, - connect_timeout = rpkid.http_client_timeout, + connect_timeout = rpkid.http_client_timeout, request_timeout = rpkid.http_client_timeout) http_response = yield rpkid.http_fetch(http_request) if http_response.headers.get("Content-Type") not in rpki.publication.allowed_content_types: @@ -593,8 +594,8 @@ class Parent(Turtle): @property def rpki_root_cert(self): try: - ca_detail = self.root.cas.ca_details.get(state = "active") - except (Root.DoesNotExist, CA.DoesNotExist, CADetail.DoesNotExist): + ca_detail = CADetail.objects.get(ca__turtle__root__parent = self, state = "active") + except (Root.DoesNotExist, CADetail.DoesNotExist): return None else: return ca_detail.latest_ca_cert @@ -610,10 +611,9 @@ class Parent(Turtle): def root_asn_resources(self, value): try: self.root.asn_resources = value - self.root.save() except Root.DoesNotExist: if value: - Root.objects.create(parent = self, asn_resources = value) + self.root = Root(asn_resources = value) @property def root_ipv4_resources(self): @@ -626,10 +626,9 @@ class Parent(Turtle): def root_ipv4_resources(self, value): try: self.root.ipv4_resources = value - self.root.save() except Root.DoesNotExist: if value: - Root.objects.create(parent = self, ipv4_resources = value) + self.root = Root(ipv4_resources = value) @property def root_ipv6_resources(self): @@ -642,10 +641,9 @@ class Parent(Turtle): def root_ipv6_resources(self, value): try: self.root.ipv6_resources = value - self.root.save() except Root.DoesNotExist: if value: - Root.objects.create(parent = self, ipv6_resources = value) + self.root = Root(ipv6_resources = value) @tornado.gen.coroutine def xml_pre_delete_hook(self, rpkid): @@ -655,6 +653,13 @@ class Parent(Turtle): @tornado.gen.coroutine def xml_post_save_hook(self, rpkid, q_pdu): trace_call_chain() + try: + self.root.tenant = self.tenant + self.root.parent = self + self.root.repository = self.repository + self.root.save() + except Root.DoesNotExist: + pass if q_pdu.get("clear_replay_protection"): self.clear_replay_protection() futures = [] @@ -765,8 +770,9 @@ class Parent(Turtle): 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) + return Element(rpki.up_down.tag_message, nsmap = rpki.up_down.nsmap, + version = rpki.up_down.version, type = query_type, + sender = self.sender_name, recipient = self.recipient_name) @tornado.gen.coroutine @@ -806,6 +812,9 @@ class Parent(Turtle): @tornado.gen.coroutine def query_up_down(self, rpkid, q_msg): trace_call_chain() + if Root.objects.filter(parent = self).exists(): + r_msg = yield self.query_up_down_root(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: @@ -814,20 +823,231 @@ class Parent(Turtle): url = self.peer_contact_uri, method = "POST", body = rpki.up_down.cms_msg().wrap(q_msg, self.bsc.private_key_id, - self.bsc.signing_cert, self.bsc.signing_cert_crl), + self.bsc.signing_cert, + self.bsc.signing_cert_crl), headers = { "Content-Type" : rpki.up_down.content_type }, - connect_timeout = rpkid.http_client_timeout, + connect_timeout = rpkid.http_client_timeout, request_timeout = rpkid.http_client_timeout) http_response = yield rpkid.http_fetch(http_request) if http_response.headers.get("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, http_response.headers.get("Content-Type"))) r_cms = rpki.up_down.cms_msg(DER = http_response.body) - r_msg = r_cms.unwrap((rpkid.bpki_ta, self.tenant.bpki_cert, self.tenant.bpki_glue, self.bpki_cert, self.bpki_glue)) + 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) + # Extracting TALs from a root is a little tricky. If we're + # willing to have rpkic know how rpkid constructs things, + # rpkic can piece it together from the sia_base value it got + # back in the OOB protocol (which, hmm, is probably + # rsync-only, may need to revisit that) and knowing that the + # root certificate will be in the top-directory of that tree + # with a g(SKI).cer filename which it can calculate from the + # certificate. rpkic probably needs to know some of this + # anyway if it's going to supply a https:// URI in the TAL. + + @tornado.gen.coroutine + def query_up_down_root(self, rpkid, q_msg): + """ + Internal RPKI root, divered from the normal up_down client. + + While it looks a bit silly, the simplest way to drop this in + without rewriting all of the up-down client code is to + implement a minimal version of the server side of the up-down + protocol here, XML and all. This has the additional advantage + of using a well-defined protocol, one with a formal schema, + even. Yes, there's a bit of XML overhead, but we'd be paying + that in any case for an external root, so it's just a minor + optimization we've chosen not to take. + + We do skip the CMS wrapper, though, since this is all internal + not just to a single Tenant but to a single Parent. + """ + + trace_call_chain() + publisher = rpki.rpkid.publication_queue(rpkid = rpkid) + + r_msg = Element(rpki.up_down.tag_message, + nsmap = rpki.up_down.nsmap, + version = rpki.up_down.version, + sender = self.recipient_name, + recipient = self.sender_name) + + # CA.parent_resource_class, CA.sia_uri + # CADetail.private_key_id, CADetail.public_key, + # CADetail.latest_ca_cert, CADetail.state, CADetail.ca_cert_uri + # + # CA.sia_uri + gski + ".cer" == CADetail.ca_cert_uri + + if q_msg.get("type") == "revoke": + r_msg.set("type", "revoke_response") + try: + parent_ca_detail = CADetail.objects.get( + ca__turtle = self, + state__in = ("active", "deprecated"), + ca__parent_resource_class = q_msg[0].get("class_name"), + ca_cert_uri__endswith = q_msg[0].get("ski") + ".cer") + old_cert = parent_ca_detail.latest_ca_cert.certificate + old_uri = parent_ca_detail.ca_cert_uri + root_ca_detail = CADetail.objects.get( + ca__turtle = self.root, + state = "active") + RevokedCert.revoke(cert = old_cert, ca_detail = root_ca_detail) + publisher.queue(uri = old_uri, old_obj = old_cert, repository = self.repository) + root_ca_detail.generate_crl_and_manifest(publisher = publisher) + yield publisher.call_pubd() + SubElement(r_msg, rpki.up_down.tag_key, + class_name = q_msg[0].get("class_name"), + ski = q_msg[0].get("ski")) + except CA.DoesNotExist: + r_msg.set("type", "error_response") + SubElement(r_msg, rpki.up_down.tag_status).text = "1301" + except CADetail.DoesNotExist: + r_msg.set("type", "error_response") + SubElement(r_msg, rpki.up_down.tag_status).text = "1302" + except: + r_msg.set("type", "error_response") + SubElement(r_msg, rpki.up_down.tag_status).text = "2001" + raise tornado.gen.Return(r_msg) + + # I can think of no sane reason why we would ever have more + # than one root CA here, so code for one. + + try: + root_ca = self.root.cas.get() + + except CA.DoesNotExist: + root_ca = CA.objects.create( + turtle = self.root, + parent_resource_class = "root", + sia_uri = self.sia_base + "root/") + logger.debug("%r query_up_down_root() created new internal root CA %r", self, root_ca) + + try: + root_ca_detail = root_ca.ca_details.get(state = "active") + + except CADetail.DoesNotExist: + root_ca_detail = root_ca.create_detail() + logger.debug("%r query_up_down_root() created internal root CADetail %r", + self, root_ca_detail) + sia = (root_ca.sia_uri, root_ca_detail.manifest_uri, + None, self.repository.rrdp_notification_uri) + notAfter = rpki.sundial.now() + rpki.sundial.timedelta.parse( + rpkid.cfg.get("rpki-root-certificate-lifetime", "1y")) + bag = rpki.resource_set.resource_bag( + asn = self.root.asn_resources, + v4 = self.root.ipv4_resources, + v6 = self.root.ipv6_resources, + valid_until = notAfter) + root_cert = rpki.x509.X509.self_certify( + keypair = root_ca_detail.private_key_id, + subject_key = root_ca_detail.public_key, + serial = root_ca.next_serial_number(), + sia = sia, + notAfter = notAfter, + resources = bag) + root_uri = self.sia_base + root_cert.gSKI() + ".cer" + publisher.queue( + uri = root_uri, + new_obj = root_cert, + repository = self.repository) + yield publisher.call_pubd() + yield root_ca_detail.activate( + rpkid = rpkid, + ca = root_ca, + cert = root_cert, + uri = root_uri) + logger.debug("%r query_up_down_root() activated internal root CADetail %r", + self, root_ca_detail) + + try: + bag = root_ca_detail.latest_ca_cert.get_3779resources() + rc = SubElement( + r_msg, rpki.up_down.tag_class, + class_name = root_ca.parent_resource_class, + cert_url = root_ca.ca_cert_uri, + resource_set_as = str(bag.asn), + resource_set_ipv4 = str(bag.v4), + resource_set_ipv6 = str(bag.v6), + resource_set_notafter = str(bag.valid_until)) + + if q_msg.get("type") == rpki.up_down.tag_list: + r_msg.set("type", "list_response") + for parent_ca_detail in CADetail.objects.filter( + ca__turtle = self, + state__in = ("active", "deprecated"), + ca__parent_resource_class = root_ca.parent_resource_class): + c = SubElement(rc, rpki.up_down.tag_certificate, + cert_url = parent_ca_detail.ca.ca_cert_uri) + c.text = parent_ca_detail.latest_ca_cert.get_Base64() + + else: + assert q_msg.get("type") == "issue" + assert q_msg[0].get("class_name") == root_ca.parent_resource_class + r_msg.set("type", "issue_response") + pkcs10 = rpki.x509.PKCS10(Base64 = q_msg[0].text) + pkcs10_key = pkcs10.getPublicKey() + pkcs10_sia = pkcs10.get_SIA() + + # There are other reasons why we might want to regenerate, but + # for the moment just focus on changed PKCS #10 (key, SIA), assume + # something in rpkid_tasks will handle the rest. + try: + parent_ca_detail = CADetail.objects.get( + ca__turtle = self, + ca__parent_resource_class = root_ca.parent_resource_class, + state = "active") + parent_cert = parent_ca_detail.latest_ca_cert + need_to_issue = (parent_ca_detail.public_key != pkcs10_key or + parent_cert.get_SIA() != pkcs10_sia) + except CADetail.DoesNotExist: + parent_ca_detail = None + need_to_issue = True + + parent_uri = self.sia_base + "root"/ + pkcs10_key.gSKI() + ".cer" + + if need_to_issue: + logger.debug("%r query_up_down_root() issuing new worker CADetail, old %r", + self, parent_ca_detail) + parent_cert = root_ca_detail.latest_ca_cert.certificate.issue( + keypair = root_ca_detail.private_key_id, + subject_key = pkcs10.key, + serial = root_ca.next_serial_number(), + sia = pkcs10_sia, + notAfter = bag.valid_until, + resources = bag) + publisher.queue( + uri = parent_uri, + new_obj = parent_cert, + repository = self.repository) + nextUpdate = rpki.sundial.now() + rpki.sundial.timedelta( + seconds = self.tenant.crl_interval) + root_ca_detail.generate_crl_and_manifest( + publisher = publisher, + nextUpdate = nextUpdate) + yield publisher.call_pubd() + logger.debug("%r query_up_down_root() issued new worker cert %r", + self, parent_cert) + + SubElement(rc, rpki.up_down.tag_certificate, + cert_url = parent_uri).text = parent_cert.get_Base64() + + SubElement(rc, rpki.up_down.tag_issuer).text = root_ca_detail.latest_ca_cert.get_Base64() + raise tornado.gen.Return(r_msg) + + except tornado.gen.Return: + raise + + except: + del r_msg[:] + r_msg.set("type", "error_response") + SubElement(r_msg, rpki.up_down.tag_status).text = "2001" + + raise tornado.gen.Return(r_msg) def construct_sia_uri(self, rc): """ @@ -851,12 +1071,18 @@ class Root(Turtle): ipv6_resources = models.TextField() parent = models.OneToOneField(Parent) + def __repr__(self): + try: + return "".format(self.parent) + except: + return "" + class CA(models.Model): last_crl_manifest_number = models.BigIntegerField(default = 1) 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_resource_class = models.TextField(null = True) # Not sure this should allow NULL turtle = models.ForeignKey(Turtle, related_name = "cas") # So it turns out that there's always a 1:1 mapping between the @@ -1349,12 +1575,18 @@ class CADetail(models.Model): nextUpdate = nextUpdate, revokedCertificates = certlist) + # XXX + logger.debug("%r Generating manifest, child_certs_all(): %r", self, self.child_certs.all()) + 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()) + # XXX + logger.debug("%r Generating manifest, objs: %r", self, objs) + self.latest_manifest = rpki.x509.SignedManifest.build( serial = crl_manifest_number, thisUpdate = now, @@ -1597,18 +1829,22 @@ class Child(models.Model): 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. + # 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")): + 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__turtle__tenant = self.tenant, state = "active", - ca__parent_resource_class = class_name) + ca_detail = CADetail.objects.get(ca__turtle__tenant = self.tenant, + ca__parent_resource_class = class_name, + state = "active") - irdb_resources = yield rpkid.irdb_query_child_resources(self.tenant.tenant_handle, self.child_handle) + 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" % ( diff --git a/rpki/sundial.py b/rpki/sundial.py index 1556d0bd..b788940d 100644 --- a/rpki/sundial.py +++ b/rpki/sundial.py @@ -235,7 +235,7 @@ class timedelta(pydatetime.timedelta): Parse text into a timedelta object. """ - if not isinstance(arg, str): + if not isinstance(arg, (str, unicode)): return cls(seconds = arg) elif arg.isdigit(): return cls(seconds = int(arg)) -- cgit v1.2.3