diff options
-rwxr-xr-x | rtr-origin/rtr-origin.py | 206 | ||||
-rw-r--r-- | utils/Makefile.in | 2 | ||||
-rw-r--r-- | utils/scan_roas/Makefile.in | 2 | ||||
-rw-r--r-- | utils/scan_routercerts/Makefile.in | 41 | ||||
-rwxr-xr-x | utils/scan_routercerts/scan_routercerts | 69 |
5 files changed, 290 insertions, 30 deletions
diff --git a/rtr-origin/rtr-origin.py b/rtr-origin/rtr-origin.py index 1d8aeb0f..11132288 100755 --- a/rtr-origin/rtr-origin.py +++ b/rtr-origin/rtr-origin.py @@ -40,6 +40,8 @@ import traceback import getopt import bisect import random +import base64 + # Debugging only, should be False in production disable_incrementals = False @@ -310,7 +312,8 @@ class pdu_with_serial(pdu): Generate the wire format PDU. """ if self._pdu is None: - self._pdu = self.header_struct.pack(self.version, self.pdu_type, self.nonce, self.header_struct.size, self.serial) + self._pdu = self.header_struct.pack(self.version, self.pdu_type, self.nonce, + self.header_struct.size, self.serial) return self._pdu def got_pdu(self, reader): @@ -538,7 +541,8 @@ class prefix(pdu): def __str__(self): plm = "%s/%s-%s" % (self.prefix, self.prefixlen, self.max_prefixlen) - return "%s %8s %-32s %s" % ("+" if self.announce else "-", self.asn, plm, ":".join(("%02X" % ord(b) for b in self.to_pdu()))) + return "%s %8s %-32s %s" % ("+" if self.announce else "-", self.asn, plm, + ":".join(("%02X" % ord(b) for b in self.to_pdu()))) def show(self): blather("# Class: %s" % self.__class__.__name__) @@ -660,6 +664,86 @@ class ipv6_prefix(prefix): pdu_type = 6 addr_type = v6addr +class router_key(pdu): + """ + Router Key PDU. + """ + + pdu_type = 9 + + header_struct = struct.Struct("!BBBxL20sL") + + @classmethod + def from_text(cls, asnum, gski, key): + """ + Construct a router key from its text form. + """ + + self = cls() + self.asn = long(asnum) + self.ski = base64.urlsafe_b64decode(gski + "=") + self.key = base64.b64decode(key) + self.announce = 1 + self.check() + return self + + def __str__(self): + return "%s %8s %-32s %s" % ("+" if self.announce else "-", self.asn, + base64.urlsafe_b64encode(self.ski).rstrip("="), + ":".join(("%02X" % ord(b) for b in self.to_pdu()))) + + def consume(self, client): + """ + Handle one incoming Router Key PDU + """ + + blather(self) + client.consume_routerkey(self) + + def check(self): + """ + Check attributes to make sure they're within range. + """ + + if self.announce not in (0, 1): + raise CorruptData("Announce value %d is neither zero nor one" % self.announce, pdu = self) + if len(self.ski) != 20: + raise CorruptData("Implausible SKI length %d" % len(self.ski), pdu = self) + pdulen = self.header_struct.size + len(self.key) + if len(self.to_pdu()) != pdulen: + raise CorruptData("Expected %d byte PDU, got %d" % (pdulen, len(self.to_pdu())), pdu = self) + + def to_pdu(self, announce = None): + if announce is not None: + assert announce in (0, 1) + elif self._pdu is not None: + return self._pdu + pdulen = self.header_struct.size + len(self.key) + pdu = (self.header_struct.pack(self.version, + self.pdu_type, + announce if announce is not None else self.announce, + pdulen, + self.ski, + self.asn) + + self.key) + if announce is None: + assert self._pdu is None + self._pdu = pdu + return pdu + + def got_pdu(self, reader): + if not reader.ready(): + return None + header = reader.get(self.header_struct.size) + version, pdu_type, self.announce, length, self.ski, self.asn = self.header_struct.unpack(header) + remaining = length - self.header_struct.size + if remaining <= 0: + raise CorruptData("Got PDU length %d, minimum is %d" % (length, self.header_struct.size + 1), pdu = self) + self.key = reader.get(remaining) + assert header + self.key == self.to_pdu() + return self + + class error_report(pdu): """ Error Report PDU. @@ -736,7 +820,10 @@ class error_report(pdu): if length != self.header_struct.size + self.string_struct.size * 2 + self.pdulen + self.errlen: raise CorruptData("Got PDU length %d, expected %d" % ( length, self.header_struct.size + self.string_struct.size * 2 + self.pdulen + self.errlen)) - assert header + self.to_counted_string(self.errpdu) + self.to_counted_string(self.errmsg.encode("utf8")) == self.to_pdu() + assert (header + + self.to_counted_string(self.errpdu) + + self.to_counted_string(self.errmsg.encode("utf8")) + == self.to_pdu()) return self def serve(self, server): @@ -750,19 +837,19 @@ class error_report(pdu): sys.exit(1) pdu.pdu_map = dict((p.pdu_type, p) for p in (ipv4_prefix, ipv6_prefix, serial_notify, serial_query, reset_query, - cache_response, end_of_data, cache_reset, error_report)) + cache_response, end_of_data, cache_reset, router_key, error_report)) -class prefix_set(list): +class pdu_set(list): """ - Object representing a set of prefixes, that is, one versioned and - (theoretically) consistant set of prefixes extracted from rcynic's - output. + Object representing a set of PDUs, that is, one versioned and + (theoretically) consistant set of prefixes and router keys extracted + from rcynic's output. """ @classmethod def _load_file(cls, filename): """ - Low-level method to read prefix_set from a file. + Low-level method to read pdu_set from a file. """ self = cls() f = open(filename, "rb") @@ -783,28 +870,27 @@ class prefix_set(list): return ((a - b) % (1 << 32)) < (1 << 31) -class axfr_set(prefix_set): +class axfr_set(pdu_set): """ - Object representing a complete set of prefixes, that is, one - versioned and (theoretically) consistant set of prefixes extracted - from rcynic's output, all with the announce field set. + Object representing a complete set of PDUs, that is, one versioned + and (theoretically) consistant set of prefixes and router + certificates extracted from rcynic's output, all with the announce + field set. """ - xargs_count = 500 - @classmethod def parse_rcynic(cls, rcynic_dir): """ - Parse ROAS fetched (and validated!) by rcynic to create a new - axfr_set. We use the scan_roas utility to parse the ASN.1. We - used to parse ROAs internally, but that made this program depend - on all of the complex stuff for building Python extensions, which - is way over the top for a relying party tool. - + Parse ROAS and router certificates fetched (and validated!) by + rcynic to create a new axfr_set. We use the scan_roas and + scan_routercerts utilities to parse the ASN.1, although we may go + back to parsing the files directly using the rpki.POW library code + some day. """ + self = cls() self.serial = timestamp.now() - roa_files = [] + try: p = subprocess.Popen((scan_roas, rcynic_dir), stdout = subprocess.PIPE) for line in p.stdout: @@ -813,6 +899,24 @@ class axfr_set(prefix_set): self.extend(prefix.from_text(asn, addr) for addr in line[2:]) except OSError, e: sys.exit("Could not run %s, check your $PATH variable? (%s)" % (scan_roas, e)) + + try: + p = subprocess.Popen((scan_routercerts, rcynic_dir), stdout = subprocess.PIPE) + for line in p.stdout: + line = line.split() + gski = line[0] + key = line[-1] + + # XXX + if False: + print "++ Got: ", line + print "++ g(SKI):", gski + print "++ key: ", key + + self.extend(router_key.from_text(asn, gski, key) for asn in line[1:-1]) + except OSError, e: + sys.exit("Could not run %s, check your $PATH variable? (%s)" % (scan_routercerts, e)) + self.sort() for i in xrange(len(self) - 2, -1, -1): if self[i] == self[i + 1]: @@ -885,7 +989,7 @@ class axfr_set(prefix_set): def save_ixfr(self, other): """ Comparing this axfr_set with an older one and write the resulting - ixfr_set to file with magic filename. Since we store prefix_sets + ixfr_set to file with magic filename. Since we store pdu_sets in sorted order, computing the difference is a trivial linear comparison. """ @@ -965,12 +1069,13 @@ class axfr_set(prefix_set): del self[i] self.serial = pfx.timestamp -class ixfr_set(prefix_set): +class ixfr_set(pdu_set): """ - Object representing an incremental set of prefixes, that is, the + Object representing an incremental set of PDUs, that is, the differences between one versioned and (theoretically) consistant set - of prefixes extracted from rcynic's output and another, with the - announce fields set or cleared as necessary to indicate the changes. + of prefixes and router certificates extracted from rcynic's output + and another, with the announce fields set or cleared as necessary to + indicate the changes. """ @classmethod @@ -1355,6 +1460,19 @@ class client_channel(pdu_channel): prefixlen INTEGER NOT NULL, max_prefixlen INTEGER NOT NULL, UNIQUE (cache_id, asn, prefix, prefixlen, max_prefixlen))''') + + cur.execute(''' + CREATE TABLE routerkey ( + cache_id INTEGER NOT NULL + REFERENCES cache(cache_id) + ON DELETE CASCADE + ON UPDATE CASCADE, + asn INTEGER NOT NULL, + ski TEXT NOT NULL, + key TEXT NOT NULL, + UNIQUE (cache_id, asn, ski), + UNIQUE (cache_id, asn, key))''') + cur.execute("SELECT cache_id, nonce, serial FROM cache WHERE host = ? AND port = ?", (self.host, self.port)) try: @@ -1392,13 +1510,34 @@ class client_channel(pdu_channel): if self.sql: values = (self.cache_id, prefix.asn, str(prefix.prefix), prefix.prefixlen, prefix.max_prefixlen) if prefix.announce: - self.sql.execute("INSERT INTO prefix (cache_id, asn, prefix, prefixlen, max_prefixlen) VALUES (?, ?, ?, ?, ?)", + self.sql.execute("INSERT INTO prefix (cache_id, asn, prefix, prefixlen, max_prefixlen) " + "VALUES (?, ?, ?, ?, ?)", values) else: self.sql.execute("DELETE FROM prefix " "WHERE cache_id = ? AND asn = ? AND prefix = ? AND prefixlen = ? AND max_prefixlen = ?", values) + + def consume_routerkey(self, routerkey): + """ + Handle one Router Key PDU. + """ + + if self.sql: + values = (self.cache_id, routerkey.asn, + base64.urlsafe_b64encode(routerkey.ski).rstrip("="), + base64.b64encode(routerkey.key)) + if routerkey.announce: + self.sql.execute("INSERT INTO routerkey (cache_id, asn, ski, key) " + "VALUES (?, ?, ?, ?)", + values) + else: + self.sql.execute("DELETE FROM routerkey " + "WHERE cache_id = ? AND asn = ? AND (ski = ? OR key = ?)", + values) + + def deliver_pdu(self, pdu): """ Handle received PDU. @@ -2040,6 +2179,17 @@ except NameError: if not os.path.exists(scan_roas): scan_roas = "scan_roas" +# Same thing for scan_routercerts +try: + # Set from autoconf + scan_routercerts = ac_scan_routercerts +except NameError: + # Source directory + scan_routercerts = os.path.normpath(os.path.join(sys.path[0], "..", "utils", + "scan_routercerts", "scan_routercerts")) +if not os.path.exists(scan_routercerts): + scan_routercerts = "scan_routercerts" + force_zero_nonce = False kickme_dir = "sockets" diff --git a/utils/Makefile.in b/utils/Makefile.in index 11c8d17b..c89fdff5 100644 --- a/utils/Makefile.in +++ b/utils/Makefile.in @@ -1,6 +1,6 @@ # $Id$ -SUBDIRS = uri print_rpki_manifest print_roa hashdir find_roa scan_roas +SUBDIRS = uri print_rpki_manifest print_roa hashdir find_roa scan_roas scan_routercerts all clean test distclean install deinstall uninstall:: @for i in ${SUBDIRS}; do echo "Making $@ in $$i"; (cd $$i && ${MAKE} $@); done diff --git a/utils/scan_roas/Makefile.in b/utils/scan_roas/Makefile.in index 3d86532d..7707969c 100644 --- a/utils/scan_roas/Makefile.in +++ b/utils/scan_roas/Makefile.in @@ -39,7 +39,7 @@ ROA_DIR = ${abs_top_builddir}/rpkid/tests/smoketest.dir/publication test: all -date -u +'now: %Y%m%d%H%M%SZ' - if test -d ${ROA_DIR}; then find ${ROA_DIR} -type f -name '*.roa' -print -exec ./${BIN} {} \; ; else :; fi + if test -d ${ROA_DIR}; then ./${BIN} ${ROA_DIR} ; else :; fi install: all if test -d ${DESTDIR}${bindir} ; then :; else ${INSTALL} -d ${DESTDIR}${bindir}; fi diff --git a/utils/scan_routercerts/Makefile.in b/utils/scan_routercerts/Makefile.in new file mode 100644 index 00000000..715d1325 --- /dev/null +++ b/utils/scan_routercerts/Makefile.in @@ -0,0 +1,41 @@ +# $Id$ + +NAME = scan_routercerts + +BIN = ${NAME} + +INSTALL = @INSTALL@ -m 555 + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +datarootdir = @datarootdir@ +datadir = @datadir@ +localstatedir = @localstatedir@ +sharedstatedir = @sharedstatedir@ +sysconfdir = @sysconfdir@ +bindir = @bindir@ +sbindir = @sbindir@ +libexecdir = @libexecdir@ +libdir = @libdir@ + +abs_top_srcdir = @abs_top_srcdir@ +abs_top_builddir = @abs_top_builddir@ + +all clean: + @true + +ROUTERCERT_DIR = ${abs_top_builddir}/rpkid/tests/smoketest.dir/publication + +test: all + -date -u +'now: %Y%m%d%H%M%SZ' + if test -d ${ROUTERCERT_DIR}; then ./${BIN} ; else :; fi + +install: all + if test -d ${DESTDIR}${bindir} ; then :; else ${INSTALL} -d ${DESTDIR}${bindir}; fi + ${INSTALL} ${BIN} ${DESTDIR}${bindir} + +deinstall uninstall: + rm -f ${DESTDIR}${bindir}/${BIN} + +distclean: clean + rm -f Makefile diff --git a/utils/scan_routercerts/scan_routercerts b/utils/scan_routercerts/scan_routercerts new file mode 100755 index 00000000..342fa272 --- /dev/null +++ b/utils/scan_routercerts/scan_routercerts @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# $Id$ +# +# Copyright (C) 2014 Dragon Research Labs ("DRL") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +""" +Scan rcynic validated output looking for router certificates, print +out stuff that the rpki-rtr code cares about. +""" + +# This program represents a weird temporary state, mostly to avoid +# diving into a recursive yak shaving exercise. +# +# Under the old scheme, anything used by the RP code should be either +# C code or pure Python code using just the standard libraries. This +# has gotten silly, but we haven't yet refactored the current packaged +# builds from two packages into three (adding a -libs package). +# +# So, by rights, this program should be a C monstrosity written using +# the OpenSSL C API. I started coding it that way, but it was just +# too painful for something we're probably going to rewrite as a few +# lines of Python once we refactor, but by the same token I didn't +# want to delay router certificate support until the refactoring. +# +# So this program anticipates the new scheme of things, but makes one +# concession to current reality: if it has a problem importing the +# RPKI-specific libraries, it just quietly exits as if everything were +# fine and there simply are no router certificates to report. This +# isn't the right answer in the long run, but will suffice to avoid +# further bald yaks. + +import os +import sys +import base64 + +try: + import rpki.POW + import rpki.oids +except ImportError: + sys.exit(0) + +rcynic_dir = sys.argv[1] + +for root, dirs, files in os.walk(rcynic_dir): + for fn in files: + if not fn.endswith(".cer"): + continue + x = rpki.POW.X509.derReadFile(os.path.join(root, fn)) + + if rpki.oids.id_kp_bgpsec_router not in (x.getEKU() or ()): + continue + + sys.stdout.write(base64.urlsafe_b64encode(x.getSKI()).rstrip("=")) + for min_asn, max_asn in x.getRFC3779()[0]: + for asn in xrange(min_asn, max_asn + 1): + sys.stdout.write(" %s" % asn) + sys.stdout.write(" %s\n" % base64.b64encode(x.getPublicKey().derWritePublic())) |