diff options
-rw-r--r-- | ca/tests/smoketest.py | 6 | ||||
-rw-r--r-- | ca/tests/yamlconf.py | 12 | ||||
-rwxr-xr-x | ca/tests/yamltest.py | 12 | ||||
-rwxr-xr-x | rp/config/rpki-generate-root-certificate | 6 | ||||
-rw-r--r-- | rpki/irdb/migrations/0002_root.py | 31 | ||||
-rw-r--r-- | rpki/irdb/models.py | 19 | ||||
-rw-r--r-- | rpki/irdb/zookeeper.py | 80 | ||||
-rw-r--r-- | rpki/resource_set.py | 8 | ||||
-rw-r--r-- | rpki/rootd.py | 11 | ||||
-rw-r--r-- | rpki/rpkid.py | 8 | ||||
-rw-r--r-- | rpki/rpkid_tasks.py | 16 | ||||
-rw-r--r-- | rpki/rpkidb/models.py | 276 | ||||
-rw-r--r-- | 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 "<Root: {!r}>".format(self.parent) + except: + return "<Root: Root object>" + 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)) |