aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xrtr-origin/rtr-origin.py206
-rw-r--r--utils/Makefile.in2
-rw-r--r--utils/scan_roas/Makefile.in2
-rw-r--r--utils/scan_routercerts/Makefile.in41
-rwxr-xr-xutils/scan_routercerts/scan_routercerts69
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()))