aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.in36
-rw-r--r--buildtools/build-freebsd-ports.py71
-rw-r--r--buildtools/freebsd-skeleton/rpki-ca/Makefile2
-rw-r--r--buildtools/freebsd-skeleton/rpki-rp/Makefile2
-rw-r--r--ca/rpki-confgen.xml158
-rwxr-xr-xca/rpkigui-apache-conf-gen64
-rw-r--r--ca/tests/Makefile.in41
-rw-r--r--ca/tests/publication-control-protocol-samples.xml155
-rw-r--r--ca/tests/publication-protocol-samples.xml451
-rw-r--r--ca/tests/rrdp-samples.xml88
-rw-r--r--ca/tests/smoketest.py244
-rwxr-xr-xca/tests/test-rrdp.py109
-rw-r--r--ca/tests/testpoke.py4
-rw-r--r--ca/tests/xml-parse-test.py34
-rw-r--r--ca/tests/yamlconf.py6
-rw-r--r--ca/tests/yamltest.py185
-rw-r--r--h/rpki/sk_roa.h2
-rwxr-xr-xpotpourri/rrdp-fetch.py68
-rwxr-xr-xpotpourri/rrdp-test-tool142
-rw-r--r--rpki/adns.py14
-rw-r--r--rpki/async.py16
-rw-r--r--rpki/csv_utils.py2
-rw-r--r--rpki/exceptions.py10
-rw-r--r--rpki/gui/app/check_expired.py6
-rw-r--r--rpki/gui/app/forms.py17
-rw-r--r--rpki/gui/app/glue.py3
-rw-r--r--rpki/gui/app/models.py14
-rwxr-xr-xrpki/gui/app/range_list.py2
-rw-r--r--rpki/gui/app/views.py41
-rw-r--r--rpki/gui/cacheview/models.py8
-rw-r--r--rpki/gui/cacheview/tests.py1
-rw-r--r--rpki/gui/cacheview/util.py3
-rw-r--r--rpki/gui/cacheview/views.py1
-rw-r--r--rpki/gui/decorators.py1
-rw-r--r--rpki/gui/models.py4
-rw-r--r--rpki/gui/routeview/api.py2
-rw-r--r--rpki/gui/routeview/util.py2
-rw-r--r--rpki/http.py47
-rw-r--r--rpki/http_simple.py85
-rw-r--r--rpki/ipaddrs.py9
-rw-r--r--rpki/irdb/zookeeper.py52
-rw-r--r--rpki/irdbd.py18
-rw-r--r--rpki/left_right.py113
-rw-r--r--rpki/log.py5
-rw-r--r--rpki/old_irdbd.py25
-rw-r--r--rpki/pubd.py480
-rw-r--r--rpki/publication.py408
-rw-r--r--rpki/publication_control.py227
-rw-r--r--rpki/rcynic.py4
-rw-r--r--rpki/relaxng.py583
-rw-r--r--rpki/resource_set.py48
-rw-r--r--rpki/rootd.py260
-rw-r--r--rpki/rpkid.py271
-rw-r--r--rpki/rpkid_tasks.py5
-rwxr-xr-xrpki/rtr/bgpdump.py2
-rw-r--r--rpki/sql.py51
-rw-r--r--rpki/sql_schemas.py129
-rw-r--r--rpki/sundial.py15
-rw-r--r--rpki/up_down.py79
-rw-r--r--rpki/x509.py135
-rw-r--r--rpki/xml_utils.py54
-rw-r--r--schemas/relaxng/left-right.rnc (renamed from schemas/relaxng/left-right-schema.rnc)0
-rw-r--r--schemas/relaxng/left-right.rng (renamed from schemas/relaxng/left-right-schema.rng)2
-rw-r--r--schemas/relaxng/myrpki.rng2
-rw-r--r--schemas/relaxng/publication-control.rnc (renamed from schemas/relaxng/publication-schema.rnc)55
-rw-r--r--schemas/relaxng/publication-control.rng280
-rw-r--r--schemas/relaxng/publication-schema.rng577
-rw-r--r--schemas/relaxng/publication.rnc111
-rw-r--r--schemas/relaxng/publication.rng201
-rw-r--r--schemas/relaxng/router-certificate.rnc (renamed from schemas/relaxng/router-certificate-schema.rnc)0
-rw-r--r--schemas/relaxng/router-certificate.rng (renamed from schemas/relaxng/router-certificate-schema.rng)2
-rw-r--r--schemas/relaxng/rrdp.rnc83
-rw-r--r--schemas/relaxng/rrdp.rng163
-rw-r--r--schemas/relaxng/up-down.rnc (renamed from schemas/relaxng/up-down-schema.rnc)0
-rw-r--r--schemas/relaxng/up-down.rng (renamed from schemas/relaxng/up-down-schema.rng)2
-rw-r--r--schemas/sql/pubd.sql90
-rw-r--r--schemas/sql/rpkid.sql35
77 files changed, 4312 insertions, 2405 deletions
diff --git a/Makefile.in b/Makefile.in
index 8908ae32..3121c7c4 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -45,11 +45,13 @@ SETUP_PY_ROOT = `${PYTHON} -c 'import sys; print "--root " + sys.argv[1] if sys.
POW_SO = rpki/POW/_POW.so
-RNGS = schemas/relaxng/left-right-schema.rng \
- schemas/relaxng/up-down-schema.rng \
- schemas/relaxng/publication-schema.rng \
+RNGS = schemas/relaxng/left-right.rng \
+ schemas/relaxng/up-down.rng \
+ schemas/relaxng/publication.rng \
+ schemas/relaxng/publication-control.rng \
schemas/relaxng/myrpki.rng \
- schemas/relaxng/router-certificate-schema.rng
+ schemas/relaxng/router-certificate.rng \
+ schemas/relaxng/rrdp.rng
SQLS = schemas/sql/rpkid.sql \
schemas/sql/pubd.sql
@@ -187,20 +189,26 @@ ${abs_top_srcdir}/rpki/sql_schemas.py: buildtools/make-sql-schemas.py ${SQLS}
cd schemas/sql; ${PYTHON} ${abs_top_srcdir}/buildtools/make-sql-schemas.py >$@.tmp
mv $@.tmp $@
-schemas/relaxng/left-right-schema.rng: schemas/relaxng/left-right-schema.rnc
- ${TRANG} schemas/relaxng/left-right-schema.rnc schemas/relaxng/left-right-schema.rng
+schemas/relaxng/left-right.rng: schemas/relaxng/left-right.rnc
+ ${TRANG} schemas/relaxng/left-right.rnc schemas/relaxng/left-right.rng
-schemas/relaxng/up-down-schema.rng: schemas/relaxng/up-down-schema.rnc
- ${TRANG} schemas/relaxng/up-down-schema.rnc schemas/relaxng/up-down-schema.rng
+schemas/relaxng/up-down.rng: schemas/relaxng/up-down.rnc
+ ${TRANG} schemas/relaxng/up-down.rnc schemas/relaxng/up-down.rng
-schemas/relaxng/publication-schema.rng: schemas/relaxng/publication-schema.rnc
- ${TRANG} schemas/relaxng/publication-schema.rnc schemas/relaxng/publication-schema.rng
+schemas/relaxng/publication.rng: schemas/relaxng/publication.rnc
+ ${TRANG} schemas/relaxng/publication.rnc schemas/relaxng/publication.rng
+
+schemas/relaxng/publication-control.rng: schemas/relaxng/publication-control.rnc
+ ${TRANG} schemas/relaxng/publication-control.rnc schemas/relaxng/publication-control.rng
schemas/relaxng/myrpki.rng: schemas/relaxng/myrpki.rnc
${TRANG} schemas/relaxng/myrpki.rnc schemas/relaxng/myrpki.rng
-schemas/relaxng/router-certificate-schema.rng: schemas/relaxng/router-certificate-schema.rnc
- ${TRANG} schemas/relaxng/router-certificate-schema.rnc schemas/relaxng/router-certificate-schema.rng
+schemas/relaxng/router-certificate.rng: schemas/relaxng/router-certificate.rnc
+ ${TRANG} schemas/relaxng/router-certificate.rnc schemas/relaxng/router-certificate.rng
+
+schemas/relaxng/rrdp.rng: schemas/relaxng/rrdp.rnc
+ ${TRANG} schemas/relaxng/rrdp.rnc schemas/relaxng/rrdp.rng
# Eg: PYLINT_FLAGS='--disable=W0311'
@@ -208,8 +216,10 @@ lint:
{ find rpki rp ca -name '*.py' -print; find rp ca -type f -perm -1 -print | xargs grep -El '^#!.+python'; } | \
sort -u | xargs pylint --rcfile ${abs_top_srcdir}/buildtools/pylint.rc ${PYLINT_FLAGS}
-tags: Makefile
+tags: Makefile .FORCE
find rpki rp ca schemas -type f \
\( -name '*.[ch]' -o -name '*.py' -o -name '*.sql' -o -name '*.rnc' \) \
! -name relaxng.py ! -name sql_schemas.py -print | \
etags -
+
+.FORCE:
diff --git a/buildtools/build-freebsd-ports.py b/buildtools/build-freebsd-ports.py
index c422f02f..c653e5cf 100644
--- a/buildtools/build-freebsd-ports.py
+++ b/buildtools/build-freebsd-ports.py
@@ -23,36 +23,40 @@ This is a script because we need to generate package lists and update
version numbers in the Makefiles.
"""
-import sys
import os
import re
-import subprocess
-import errno
+import sys
import glob
+import errno
import shutil
import argparse
+import subprocess
def check_dir(s):
if not os.path.isdir(s):
raise argparse.ArgumentTypeError("%r is not a directory" % s)
return s
-parser = argparse.ArgumentParser(description = __doc__)
-parser.add_argument("--allow-dirty", action = "store_true",
- help = "don't insist on pristine subversion checkout")
+parser = argparse.ArgumentParser(description = __doc__,
+ formatter_class = argparse.ArgumentDefaultsHelpFormatter)
+parser.add_argument("--local-dist", action = "store_true",
+ help = "generate local distribution from subversion working tree (implies --make-package)")
parser.add_argument("--make-package", action = "store_true",
help = "build binary package")
parser.add_argument("--no-clean", action = "store_true",
help = "don't clean port after staging etc (implies --no-tarball)")
parser.add_argument("--no-tarball", action = "store_true",
help = "don't create tarball of generated port")
+parser.add_argument("--portsdir", type = os.path.abspath,
+ default = os.path.abspath("freebsd-ports"),
+ help = "where to build FreeBSD port trees")
parser.add_argument("svndir", metavar = "subversion-working-directory", type = check_dir,
help = "directory containing subversion working tree")
args = parser.parse_args()
svnversion = subprocess.check_output(("svnversion", "-c", args.svndir)).strip().split(":")[-1]
-if args.allow_dirty:
+if args.local_dist:
svnversion = svnversion.translate(None, "M")
if not svnversion.isdigit():
@@ -66,53 +70,56 @@ if branch != "trunk" and (branch[:2] != "tk" or not branch[2:].isdigit()):
version = "0." + svnversion
tarname = "rpki-%s-r%s" % (branch, svnversion)
tarball = tarname + ".tar.xz"
-url = "http://download.rpki.net/" + tarball
-
-portsdir = os.path.abspath("freebsd-ports")
-portsdir_old = portsdir + ".old"
-# Could perhaps use distutils.sysconfig.get_python_lib() instead of
-# this regexp hack, but would be just as complicated in its own way,
-# so just go with this for the moment.
-
-py_lib = re.compile(r"^lib/python\d+\.\d+")
-py_sitelib = re.compile(r"^lib/python\d+\.\d+/site-packages")
+portsdir_old = args.portsdir + ".old"
if os.path.isdir(portsdir_old):
shutil.rmtree(portsdir_old)
-if os.path.isdir(portsdir):
- os.rename(portsdir, portsdir_old)
+if os.path.isdir(args.portsdir):
+ os.rename(args.portsdir, portsdir_old)
-shutil.copytree(os.path.join(args.svndir, "buildtools", "freebsd-skeleton"), portsdir)
+shutil.copytree(os.path.join(args.svndir, "buildtools", "freebsd-skeleton"), args.portsdir)
-if os.path.exists(os.path.join(portsdir_old, tarball)):
- os.link(os.path.join(portsdir_old, tarball), os.path.join(portsdir, tarball))
+if args.local_dist:
+ subprocess.check_call(("svn", "export", args.svndir, os.path.join(args.portsdir, tarname)))
+ subprocess.check_call(("tar", "cJvvf", tarball, tarname), cwd = args.portsdir)
+ shutil.rmtree(os.path.join(args.portsdir, tarname))
+elif os.path.exists(os.path.join(portsdir_old, tarball)):
+ os.link(os.path.join(portsdir_old, tarball), os.path.join(args.portsdir, tarball))
elif os.path.exists(os.path.join("/usr/ports/distfiles", tarball)):
- shutil.copy(os.path.join("/usr/ports/distfiles", tarball), os.path.join(portsdir, tarball))
+ shutil.copy(os.path.join("/usr/ports/distfiles", tarball), os.path.join(args.portsdir, tarball))
if os.path.isdir(portsdir_old):
shutil.rmtree(portsdir_old)
-if args.make_package:
- pkgdir = os.path.join(portsdir, "packages")
+if args.make_package or args.local_dist:
+ pkgdir = os.path.join(args.portsdir, "packages")
os.mkdir(pkgdir)
-formatdict = dict(SVNVERSION = svnversion, SVNBRANCH = branch)
+py_lib = re.compile(r"^lib/python\d+\.\d+")
+py_sitelib = re.compile(r"^lib/python\d+\.\d+/site-packages")
+
+if args.local_dist:
+ master_site = "file://" + args.portsdir + "/"
+else:
+ master_site = "http://download.rpki.net/"
+
+formatdict = dict(SVNVERSION = svnversion, SVNBRANCH = branch, MASTER_SITE = master_site)
keepdirs = ("usr", "etc", "bin", "var", "lib", "libexec", "sbin", "share", "etc/rc.d", "%%PYTHON_SITELIBDIR%%")
for port in ("rpki-rp", "rpki-ca"):
- base = os.path.join(portsdir, port)
+ base = os.path.join(args.portsdir, port)
stage = os.path.join(base, "work", "stage")
- fn = os.path.join(portsdir, port, "Makefile")
+ fn = os.path.join(args.portsdir, port, "Makefile")
with open(fn, "r") as f:
template = f.read()
with open(fn, "w") as f:
f.write(template % formatdict)
- subprocess.check_call(("make", "makesum", "stage", "DISTDIR=" + portsdir), cwd = base)
+ subprocess.check_call(("make", "makesum", "stage", "DISTDIR=" + args.portsdir), cwd = base)
with open(os.path.join(base, "pkg-plist"), "w") as f:
usr_local = None
@@ -135,11 +142,11 @@ for port in ("rpki-rp", "rpki-ca"):
if dn and dn not in keepdirs and not py_lib.match(dn):
f.write("@dirrm %s\n" % dn)
- if args.make_package:
- subprocess.check_call(("make", "clean", "package", "PKGREPOSITORY=" + pkgdir), cwd = base)
+ if args.make_package or args.local_dist:
+ subprocess.check_call(("make", "clean", "package", "DISTDIR=" + args.portsdir, "PKGREPOSITORY=" + pkgdir), cwd = base)
if not args.no_clean:
subprocess.check_call(("make", "clean"), cwd = base)
if not args.no_tarball and not args.no_clean:
- subprocess.check_call(("tar", "czf", "%s-port.tgz" % port, port), cwd = portsdir)
+ subprocess.check_call(("tar", "czf", "%s-port.tgz" % port, port), cwd = args.portsdir)
diff --git a/buildtools/freebsd-skeleton/rpki-ca/Makefile b/buildtools/freebsd-skeleton/rpki-ca/Makefile
index cf095aa1..fbde22fe 100644
--- a/buildtools/freebsd-skeleton/rpki-ca/Makefile
+++ b/buildtools/freebsd-skeleton/rpki-ca/Makefile
@@ -1,7 +1,7 @@
PORTNAME= rpki-ca
PORTVERSION= 0.%(SVNVERSION)s
CATEGORIES= net
-MASTER_SITES= http://download.rpki.net/
+MASTER_SITES= %(MASTER_SITE)s
DISTFILES= rpki-%(SVNBRANCH)s-r%(SVNVERSION)s.tar.xz
WRKSRC= ${WRKDIR}/rpki-%(SVNBRANCH)s-r%(SVNVERSION)s
MAINTAINER= sra@hactrn.net
diff --git a/buildtools/freebsd-skeleton/rpki-rp/Makefile b/buildtools/freebsd-skeleton/rpki-rp/Makefile
index adae6ef0..0c1fdd91 100644
--- a/buildtools/freebsd-skeleton/rpki-rp/Makefile
+++ b/buildtools/freebsd-skeleton/rpki-rp/Makefile
@@ -1,7 +1,7 @@
PORTNAME= rpki-rp
PORTVERSION= 0.%(SVNVERSION)s
CATEGORIES= net
-MASTER_SITES= http://download.rpki.net/
+MASTER_SITES= %(MASTER_SITE)s
DISTFILES= rpki-%(SVNBRANCH)s-r%(SVNVERSION)s.tar.xz
WRKSRC= ${WRKDIR}/rpki-%(SVNBRANCH)s-r%(SVNVERSION)s
MAINTAINER= sra@hactrn.net
diff --git a/ca/rpki-confgen.xml b/ca/rpki-confgen.xml
index a29ad8cd..b3e50823 100644
--- a/ca/rpki-confgen.xml
+++ b/ca/rpki-confgen.xml
@@ -186,17 +186,17 @@
</doc>
</option>
- <option name = "publication_root_cert_directory"
- value = "${myrpki::publication_base_directory}.root">
+ <option name = "rrdp_publication_base_directory"
+ value = "${autoconf::datarootdir}/rpki/rrdp-publication">
<doc>
- Root of local directory tree where rootd (sigh) should write out
- published data. This is just like publication_base_directory, but
- rootd is too dumb to use pubd and needs its own directory in
- which to write one certificate, one CRL, and one manifest.
- Neither rootd nor rsyncd much cares //where// you tell them to put
- this stuff, the important thing is that the rsync URIs in
- generated certificates match up with the published objects so that
- relying parties can find and verify rootd's published outputs.
+ Root of local directory tree where pubd should write out RRDP
+ files. You need to configure this, and the configuration
+ should match up with the directory where you point the web
+ server (usually Apache) that serves the RRDP files. Neither
+ pubd nor Apache much cares //where// you tell it to put this
+ stuff, the important thing is that all the URIs match up so
+ that relying parties can find and verify rpkid's published
+ outputs.
</doc>
</option>
@@ -209,15 +209,6 @@
</doc>
</option>
- <option name = "publication_root_module"
- value = "root">
- <doc>
- rsyncd module name corresponding to publication_root_cert_directory.
- This has to match the module you configured into `rsyncd.conf`.
- Leave this alone unless you have some need to change it.
- </doc>
- </option>
-
<option name = "publication_rsync_server"
value = "${myrpki::pubd_server_host}">
<doc>
@@ -577,6 +568,20 @@
</doc>
</option>
+ <option name = "rrdp-publication-base"
+ value = "${myrpki::rrdp_publication_base_directory}">
+ <doc>
+ Root of local directory tree where pubd should write out RRDP
+ files. You need to configure this, and the configuration
+ should match up with the directory where you point the web
+ server (usually Apache) that serves the RRDP files. Neither
+ pubd nor Apache much cares //where// you tell it to put this
+ stuff, the important thing is that all the URIs match up so
+ that relying parties can find and verify rpkid's published
+ outputs.
+ </doc>
+ </option>
+
<option name = "server-host"
value = "${myrpki::pubd_server_host}">
<doc>
@@ -618,6 +623,15 @@
</doc>
</option>
+ <option name = "pubd-crl"
+ value = "${myrpki::bpki_servers_directory}/ca.crl">
+ <doc>
+ Where pubd should look for the CRL covering its own BPKI EE
+ certificate. Don't change this unless you really know what
+ you are doing.
+ </doc>
+ </option>
+
<option name = "irbe-cert"
value = "${myrpki::bpki_servers_directory}/irbe.cer">
<doc>
@@ -638,10 +652,9 @@
</doc>
<doc>
- Ok, if that wasn't enough to scare you off: rootd is a mess, and
- needs to be rewritten, or, better, merged into rpkid. It
- doesn't use the publication protocol, and it requires far too
- many configuration parameters.
+ Ok, if that wasn't enough to scare you off: rootd is a mess,
+ needs to be rewritten, or, better, merged into rpkid, and
+ requires far too many configuration parameters.
</doc>
<doc>
@@ -712,6 +725,13 @@
</doc>
</option>
+ <option name = "pubd-bpki-cert">
+ <doc>
+ BPKI certificate for pubd. Don't set this unless you really
+ know what you are doing.
+ </doc>
+ </option>
+
<option name = "server-host"
value = "${myrpki::rootd_server_host}">
<doc>
@@ -726,46 +746,75 @@
</doc>
</option>
- <option name = "rpki-root-dir"
- value = "${myrpki::publication_base_directory}">
+ <option name = "rpki_data_dir"
+ value = "${myrpki::bpki_servers_directory}">
<doc>
- Where rootd should write its output. Yes, rootd should be using
- pubd instead of publishing directly, but it doesn't. This
- needs to match pubd's configuration.
+ Directory where rootd should store its RPKI data files. This
+ is only used to construct other variables, rootd itself
+ doesn't read it.
</doc>
</option>
- <option name = "rpki-base-uri"
- value = "rsync://${myrpki::publication_rsync_server}/${myrpki::publication_rsync_module}/">
+ <option name = "rpki_base_uri"
+ value = "rsync://${myrpki::publication_rsync_server}/${myrpki::publication_rsync_module}/${myrpki::handle}-root/root">
<doc>
- rsync URI corresponding to directory containing rootd's outputs.
+ rsync URI corresponding to directory containing rootd's
+ outputs. This is only used to construct other variables,
+ rootd itself doesn't read it.
</doc>
</option>
<option name = "rpki-root-cert-uri"
- value = "rsync://${myrpki::publication_rsync_server}/${myrpki::publication_root_module}/root.cer">
+ value = "${rootd::rpki_base_uri}.cer">
<doc>
rsync URI for rootd's root (self-signed) RPKI certificate.
</doc>
</option>
- <option name = "rpki-root-key"
- value = "${myrpki::bpki_servers_directory}/root.key">
+ <option name = "rpki-root-cert-file"
+ value = "${rootd::rpki_data_dir}/root.cer">
+ <doc>
+ Filename of rootd's root RPKI certificate.
+ </doc>
+ </option>
+
+ <option name = "rpki-root-key-file"
+ value = "${rootd::rpki_data_dir}/root.key">
<doc>
Private key corresponding to rootd's root RPKI certificate.
</doc>
</option>
- <option name = "rpki-root-cert"
- value = "${myrpki::publication_root_cert_directory}/root.cer">
+ <option name = "rpki-root-crl-uri"
+ value = "${rootd::rpki_base_uri}/root.crl">
+ <doc>
+ URI of the CRL for rootd's root RPKI certificate.
+ </doc>
+ </option>
+
+ <option name = "rpki-root-crl-file"
+ value = "${rootd::rpki_data_dir}/root.crl">
+ <doc>
+ Filename of the CRL for rootd's root RPKI certificate.
+ </doc>
+ </option>
+
+ <option name = "rpki-root-manifest-uri"
+ value = "${rootd::rpki_base_uri}/root.mft">
+ <doc>
+ URI of the manifest for rootd's root RPKI certificate.
+ </doc>
+ </option>
+
+ <option name = "rpki-root-manifest-file"
+ value = "${rootd::rpki_data_dir}/root.mft">
<doc>
- Filename (as opposed to rsync URI) of rootd's root RPKI
- certificate.
+ Filename of the manifest for rootd's root RPKI certificate.
</doc>
</option>
- <option name = "rpki-subject-pkcs10"
- value = "${myrpki::bpki_servers_directory}/rootd.subject.pkcs10">
+ <option name = "rpki-subject-pkcs10-file"
+ value = "${rootd::rpki_data_dir}/subject.pkcs10">
<doc>
Where rootd should stash a copy of the PKCS #10 request it gets
from its one (and only) child
@@ -779,35 +828,32 @@
</doc>
</option>
- <option name = "rpki-root-crl"
- value = "root.crl">
+ <option name = "rpki-class-name"
+ value = "${myrpki::handle}">
<doc>
- Filename (relative to rootd-base-uri and rpki-root-dir) of the CRL
- for rootd's root RPKI certificate.
+ Up-down protocol class name for RPKI certificate rootd issues to its
+ one (and only) child.
</doc>
</option>
- <option name = "rpki-root-manifest"
- value = "root.mft">
+ <option name = "rpki-subject-cert-uri"
+ value = "${rootd::rpki_base_uri}/${myrpki::handle}.cer">
<doc>
- Filename (relative to rootd-base-uri and rpki-root-dir) of the
- manifest for rootd's root RPKI certificate.
+ URI of the one (and only) RPKI certificate rootd issues.
</doc>
</option>
- <option name = "rpki-class-name"
- value = "${myrpki::handle}">
+ <option name = "rpki-subject-cert-file"
+ value = "${rootd::rpki_data_dir}/${myrpki::handle}.cer">
<doc>
- Up-down protocol class name for RPKI certificate rootd issues to its
- one (and only) child.
+ Filename of the one (and only) RPKI certificate rootd issues.
</doc>
</option>
- <option name = "rpki-subject-cert"
- value = "${myrpki::handle}.cer">
+ <option name = "pubd-contact-uri"
+ value = "http://${myrpki::pubd_server_host}:${myrpki::pubd_server_port}/client/${myrpki::handle}-root">
<doc>
- Filename (relative to rootd-base-uri and rpki-root-dir) of the one
- (and only) RPKI certificate rootd issues.
+ URI at which rootd should contact pubd for service.
</doc>
</option>
diff --git a/ca/rpkigui-apache-conf-gen b/ca/rpkigui-apache-conf-gen
index 0f56342f..8ab8819a 100755
--- a/ca/rpkigui-apache-conf-gen
+++ b/ca/rpkigui-apache-conf-gen
@@ -29,6 +29,50 @@ import rpki.autoconf
fqdn = socket.getfqdn()
vhost_template = """\
+
+#
+# Stuff that should be visible with both HTTP and HTTPS is (now)
+# outside the vhost block (see if this works properly...).
+#
+
+#
+# Allow access to the directory where rcynic-html writes
+# its output files.
+#
+<Directory %(RCYNIC_HTML_DIR)s>
+%(allow)s
+</Directory>
+
+#
+# Add alias pointing to rcynic-html's output files.
+#
+# If for some reason you need to change this, be careful to leave
+# the trailing slash off the URL, otherwise /rcynic will be
+# swallowed by the WSGIScriptAlias
+#
+Alias /rcynic %(RCYNIC_HTML_DIR)s/
+
+#
+# Allow access to the directory where pubd writes RRDP files.
+#
+<Directory %(datarootdir)s/rpki/rrdp-publication/>
+%(allow)s
+</Directory>
+
+#
+# Add alias pointing to pubd's RRD output files.
+#
+Alias /rrdp %(datarootdir)s/rpki/rrdp-publication/
+
+#
+# RRDP "notification" file needs a short expiration: this is
+# a critical part of how RRDP interacts with HTTP caching.
+#
+<LocationMatch ^/rrdp/updates[.]xml$>
+ ExpiresActive on
+ ExpiresDefault "access plus 5 minutes"
+</LocationMatch>
+
#
# By default, this configuration assumes that you use name-based
# virtual hosting. If that's not what you want, you may need
@@ -78,23 +122,6 @@ vhost_template = """\
Alias /site_media/ %(datarootdir)s/rpki/media/
#
- # Allow access to the directory where rcynic-html writes
- # its output files.
- #
- <Directory %(RCYNIC_HTML_DIR)s>
-%(allow)s
- </Directory>
-
- #
- # Add alias pointing to rcynic-html's output files.
- #
- # If for some reason you need to change this, be careful to leave
- # the trailing slash off the URL, otherwise /rcynic will be
- # swallowed by the WSGIScriptAlias
- #
- Alias /rcynic %(RCYNIC_HTML_DIR)s/
-
- #
# Redirect to the GUI dashboard when someone hits the bare vhost.
#
RedirectMatch ^/$ /rpki/
@@ -102,7 +129,7 @@ vhost_template = """\
#
# Enable HTTPS
#
- SSLEngine on
+ SSLEngine on
#
# Specify HTTPS server certificate and key files for this virtual host.
@@ -402,6 +429,7 @@ class Debian(Platform):
def enable(self):
self.run("a2enmod", "ssl")
+ self.run("a2enmod", "expires")
self.run("a2ensite", "rpki")
#
# In light of BREACH and CRIME attacks, mod_deflate is looking
diff --git a/ca/tests/Makefile.in b/ca/tests/Makefile.in
index 9796dd2b..618a741e 100644
--- a/ca/tests/Makefile.in
+++ b/ca/tests/Makefile.in
@@ -3,12 +3,11 @@
PYTHON = @PYTHON@
abs_top_builddir = @abs_top_builddir@
-all: protocol-samples
+all:
+ @true
clean:
- rm -rf smoketest.dir left-right-protocol-samples publication-protocol-samples yamltest.dir rcynic.xml rcynic-data
-
-protocol-samples: left-right-protocol-samples/.stamp publication-protocol-samples/.stamp
+ rm -rf smoketest.dir left-right-protocol-samples publication-protocol-samples publication-control-protocol-samples rrdp-samples yamltest.dir rcynic.xml rcynic-data
left-right-protocol-samples/.stamp: left-right-protocol-samples.xml split-protocol-samples.xsl
rm -rf left-right-protocol-samples
@@ -16,20 +15,44 @@ left-right-protocol-samples/.stamp: left-right-protocol-samples.xml split-protoc
xsltproc --param verbose 0 --stringparam dir left-right-protocol-samples split-protocol-samples.xsl left-right-protocol-samples.xml
touch $@
+left-right-relaxng: left-right-protocol-samples/.stamp
+ xmllint --noout --relaxng ../../schemas/relaxng/left-right.rng left-right-protocol-samples/*.xml
+
publication-protocol-samples/.stamp: publication-protocol-samples.xml split-protocol-samples.xsl
rm -rf publication-protocol-samples
mkdir publication-protocol-samples
xsltproc --param verbose 0 --stringparam dir publication-protocol-samples split-protocol-samples.xsl publication-protocol-samples.xml
touch $@
-relaxng: protocol-samples
- xmllint --noout --relaxng ../../schemas/relaxng/left-right-schema.rng left-right-protocol-samples/*.xml
- xmllint --noout --relaxng ../../schemas/relaxng/up-down-schema.rng up-down-protocol-samples/*.xml
- xmllint --noout --relaxng ../../schemas/relaxng/publication-schema.rng publication-protocol-samples/*.xml
+publication-relaxng: publication-protocol-samples/.stamp
+ xmllint --noout --relaxng ../../schemas/relaxng/publication.rng publication-protocol-samples/*.xml
+
+publication-control-protocol-samples/.stamp: publication-control-protocol-samples.xml split-protocol-samples.xsl
+ rm -rf publication-control-protocol-samples
+ mkdir publication-control-protocol-samples
+ xsltproc --param verbose 0 --stringparam dir publication-control-protocol-samples split-protocol-samples.xsl publication-control-protocol-samples.xml
+ touch $@
+
+publication-control-relaxng: publication-control-protocol-samples/.stamp
+ xmllint --noout --relaxng ../../schemas/relaxng/publication-control.rng publication-control-protocol-samples/*.xml
+
+rrdp-samples/.stamp: rrdp-samples.xml split-protocol-samples.xsl
+ rm -rf rrdp-samples
+ mkdir rrdp-samples
+ xsltproc --param verbose 0 --stringparam dir rrdp-samples split-protocol-samples.xsl rrdp-samples.xml
+ touch $@
+
+rrdp-relaxng: rrdp-samples/.stamp
+ xmllint --noout --relaxng ../../schemas/relaxng/rrdp.rng rrdp-samples/*.xml
+
+up-down-relaxng:
+ xmllint --noout --relaxng ../../schemas/relaxng/up-down.rng up-down-protocol-samples/*.xml
+
+relaxng: up-down-relaxng left-right-relaxng publication-relaxng publication-control-relaxng rrdp-relaxng
all-tests:: relaxng
-parse-test: protocol-samples
+parse-test: left-right-protocol-samples publication-protocol-samples publication-control-protocol-samples
${PYTHON} xml-parse-test.py
all-tests:: parse-test
diff --git a/ca/tests/publication-control-protocol-samples.xml b/ca/tests/publication-control-protocol-samples.xml
new file mode 100644
index 00000000..e094f3f6
--- /dev/null
+++ b/ca/tests/publication-control-protocol-samples.xml
@@ -0,0 +1,155 @@
+<!-- -*- SGML -*-
+ - $Id$
+ -
+ - Copyright (C) 2008 American Registry for Internet Numbers ("ARIN")
+ -
+ - Permission to use, copy, modify, and 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 ARIN DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS. IN NO EVENT SHALL ARIN 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.
+ -
+ -
+ - This is a collection of sample publication protocol PDU samples
+ - to use as test cases for the publication protocol RelaxNG schema.
+ -->
+
+<completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint>
+
+ <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="create" client_handle="3" base_uri="rsync://wombat.invalid/">
+ <bpki_cert>
+ MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
+ BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
+ MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
+ b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
+ G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
+ Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
+ DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
+ uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
+ 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
+ diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
+ ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
+ hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
+ cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
+ XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
+ sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
+ YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
+ 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
+ </bpki_cert>
+ </client>
+ </msg>
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="create" client_handle="3"/>
+ </msg>
+
+ <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="set" client_handle="3">
+ <bpki_glue>
+ MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
+ BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
+ MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
+ b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
+ G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
+ Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
+ DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
+ uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
+ 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
+ diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
+ ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
+ hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
+ cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
+ XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
+ sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
+ YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
+ 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
+ </bpki_glue>
+ </client>
+ </msg>
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="set" client_handle="3"/>
+ </msg>
+
+ <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="get" client_handle="3"/>
+ </msg>
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="get" client_handle="3" base_uri="rsync://wombat.invalid/">
+ <bpki_cert>
+ MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
+ BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
+ MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
+ b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
+ G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
+ Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
+ DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
+ uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
+ 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
+ diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
+ ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
+ hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
+ cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
+ XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
+ sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
+ YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
+ 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
+ </bpki_cert>
+ </client>
+ </msg>
+
+ <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="list"/>
+ </msg>
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="list" client_handle="3">
+ <bpki_cert>
+ MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
+ BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
+ MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
+ b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
+ G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
+ Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
+ DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
+ uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
+ 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
+ diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
+ ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
+ hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
+ cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
+ XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
+ sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
+ YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
+ 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
+ </bpki_cert>
+ </client>
+ </msg>
+
+ <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="destroy" client_handle="3"/>
+ </msg>
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <client action="destroy" client_handle="3"/>
+ </msg>
+
+ <!-- === -->
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <report_error error_code="your_hair_is_on_fire">text string</report_error>
+ </msg>
+
+ <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/">
+ <report_error error_code="your_hair_is_on_fire"/>
+ </msg>
+
+</completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint>
diff --git a/ca/tests/publication-protocol-samples.xml b/ca/tests/publication-protocol-samples.xml
index 96b095a7..6d0a99a9 100644
--- a/ca/tests/publication-protocol-samples.xml
+++ b/ca/tests/publication-protocol-samples.xml
@@ -1,370 +1,107 @@
<!-- -*- SGML -*-
- - $Id$
+ - $Id$
-
- - Copyright (C) 2008 American Registry for Internet Numbers ("ARIN")
+ - Sample PDUs for RPKI publication protocol, from current I-D.
-
- - Permission to use, copy, modify, and 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.
+ - Copyright (c) 2014 IETF Trust and the persons identified as authors
+ - of the code. All rights reserved.
-
- - THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
- - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- - AND FITNESS. IN NO EVENT SHALL ARIN 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.
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
-
+ - * Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
-
- - This is a collection of sample publication protocol PDU samples
- - to use as test cases for the publication protocol RelaxNG schema.
+ - * Redistributions in binary form must reproduce the above copyright
+ - notice, this list of conditions and the following disclaimer in
+ - the documentation and/or other materials provided with the
+ - distribution.
+ -
+ - * Neither the name of Internet Society, IETF or IETF Trust, nor the
+ - names of specific contributors, may be used to endorse or promote
+ - products derived from this software without specific prior written
+ - permission.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ - FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ - COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ - BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ - ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ - POSSIBILITY OF SUCH DAMAGE.
-->
<completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint>
- <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="1">
- <config action="set">
- <bpki_crl>
- MIIBezBlAgEBMA0GCSqGSIb3DQEBCwUAMCMxITAfBgNVBAMTGFRlc3QgQ2VydGlm
- aWNhdGUgcHViZCBUQRcNMDgwNjAyMjE0OTQ1WhcNMDgwNzAyMjE0OTQ1WqAOMAww
- CgYDVR0UBAMCAQEwDQYJKoZIhvcNAQELBQADggEBAFWCWgBl4ljVqX/CHo+RpqYt
- vmKMnjPVflMXUB7i28RGP4DAq4l7deDU7Q82xEJyE4TXMWDWAV6UG6uUGum0VHWO
- cj9ohqyiZUGfOsKg2hbwkETm8sAENOsi1yNdyKGk6jZ16aF5fubxQqZa1pdGCSac
- 1/ZYC5sLLhEz3kmz+B9z9mXFVc5TgAh4dN3Gy5ftF8zZAFpDGnS4biCnRVqhGv6R
- 0Lh/5xmii+ZU6kNDhbeMsjJg+ZOmtN+wMeHSIbjiy0WuuaZ3k2xSh0C94anrHBZA
- vvCRhbazjR0Ef5OMZ5lcllw3uO8IHuoisHKkehy4Y0GySdj98fV+OuiRTH9vt/M=
- </bpki_crl>
- </config>
- </msg>
-
- <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="1">
- <config action="set"/>
- </msg>
-
- <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="1">
- <config action="get"/>
- </msg>
-
- <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="1">
- <config action="get">
- <bpki_crl>
- MIIBezBlAgEBMA0GCSqGSIb3DQEBCwUAMCMxITAfBgNVBAMTGFRlc3QgQ2VydGlm
- aWNhdGUgcHViZCBUQRcNMDgwNjAyMjE0OTQ1WhcNMDgwNzAyMjE0OTQ1WqAOMAww
- CgYDVR0UBAMCAQEwDQYJKoZIhvcNAQELBQADggEBAFWCWgBl4ljVqX/CHo+RpqYt
- vmKMnjPVflMXUB7i28RGP4DAq4l7deDU7Q82xEJyE4TXMWDWAV6UG6uUGum0VHWO
- cj9ohqyiZUGfOsKg2hbwkETm8sAENOsi1yNdyKGk6jZ16aF5fubxQqZa1pdGCSac
- 1/ZYC5sLLhEz3kmz+B9z9mXFVc5TgAh4dN3Gy5ftF8zZAFpDGnS4biCnRVqhGv6R
- 0Lh/5xmii+ZU6kNDhbeMsjJg+ZOmtN+wMeHSIbjiy0WuuaZ3k2xSh0C94anrHBZA
- vvCRhbazjR0Ef5OMZ5lcllw3uO8IHuoisHKkehy4Y0GySdj98fV+OuiRTH9vt/M=
- </bpki_crl>
- </config>
- </msg>
-
- <!-- === -->
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="create" client_handle="3" base_uri="rsync://wombat.invalid/">
- <bpki_cert>
- MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
- BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
- MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
- b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
- G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
- Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
- DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
- uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
- 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
- diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
- ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
- hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
- cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
- XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
- sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
- YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
- 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
- </bpki_cert>
- </client>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="create" client_handle="3"/>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="set" client_handle="3">
- <bpki_glue>
- MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
- BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
- MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
- b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
- G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
- Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
- DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
- uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
- 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
- diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
- ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
- hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
- cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
- XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
- sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
- YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
- 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
- </bpki_glue>
- </client>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="set" client_handle="3"/>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="get" client_handle="3"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="get" client_handle="3" base_uri="rsync://wombat.invalid/">
- <bpki_cert>
- MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
- BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
- MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
- b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
- G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
- Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
- DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
- uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
- 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
- diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
- ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
- hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
- cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
- XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
- sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
- YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
- 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
- </bpki_cert>
- </client>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="list"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="list" client_handle="3">
- <bpki_cert>
- MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV
- BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN
- MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS
- b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S
- G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G
- Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC
- DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM
- uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP
- 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ
- diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4
- ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq
- hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg
- cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4
- XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm
- sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH
- YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq
- 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ==
- </bpki_cert>
- </client>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="destroy" client_handle="3"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <client action="destroy" client_handle="3"/>
- </msg>
-
- <!-- === -->
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <certificate action="publish" uri="rsync://wombat.invalid/testbed/RIR/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.cer">
- MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQDEyhERjRBODAxN0U2
- NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XDTA4MDUyMjE4MDUxMloXDTA4MDUy
- NDE3NTQ1M1owMzExMC8GA1UEAxMoOEZCODIxOEYwNkU1MEFCNzAyQTdEOTZEQzhGMENEQ0Q4
- MjhGN0YxNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMeziKp0k5nP7v6SZoNs
- XIMQYRgNtC6Fr/9Xm/1yQHomiPqHUk47rHhGojYiK5AhkrwoYhkH4UjJl2iwklDYczXuaBU3
- F5qrKlZ4aZnjIxdlP7+hktVpeApL6yuJTUAYeC3UIxnLDVdD6phydZ/FOQluffiNDjzteCCv
- oyOUatqt8WB+oND6LToHp028g1YUYLHG6mur0dPdcHOVXLSmUDuZ1HDz1nDuYvIVKjB/MpH9
- aW9XeaQ6ZFIlZVPwuuvI2brR+ThH7Gv27GL/o8qFdC300VQfoTZ+rKPGDE8K1cI906BL4kiw
- x9z0oiDcE96QCz+B0vsjc9mGaA1jgAxlXWsCAwEAAaOCAhcwggITMB0GA1UdDgQWBBSPuCGP
- BuUKtwKn2W3I8M3Ngo9/FzAfBgNVHSMEGDAWgBTfSoAX5mqekXLkYS2M9Mg/I43iozBVBgNV
- HR8ETjBMMEqgSKBGhkRyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rlc3RiZWQvUklSLzEvMzBx
- QUYtWnFucEZ5NUdFdGpQVElQeU9ONHFNLmNybDBFBggrBgEFBQcBAQQ5MDcwNQYIKwYBBQUH
- MAKGKXJzeW5jOi8vbG9jYWxob3N0OjQ0MDAvdGVzdGJlZC9XT01CQVQuY2VyMBgGA1UdIAEB
- /wQOMAwwCgYIKwYBBQUHDgIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwgZsG
- CCsGAQUFBwELBIGOMIGLMDQGCCsGAQUFBzAFhihyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rl
- c3RiZWQvUklSL1IwLzEvMFMGCCsGAQUFBzAKhkdyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rl
- c3RiZWQvUklSL1IwLzEvajdnaGp3YmxDcmNDcDlsdHlQRE56WUtQZnhjLm1uZjAaBggrBgEF
- BQcBCAEB/wQLMAmgBzAFAgMA/BUwPgYIKwYBBQUHAQcBAf8ELzAtMCsEAgABMCUDAwAKAzAO
- AwUAwAACAQMFAcAAAiAwDgMFAsAAAiwDBQDAAAJkMA0GCSqGSIb3DQEBCwUAA4IBAQCEhuH7
- jtI2PJY6+zwv306vmCuXhtu9Lr2mmRw2ZErB8EMcb5xypMrNqMoKeu14K2x4a4RPJkK4yATh
- M81FPNRsU5mM0acIRnAPtxjHvPME7PHN2w2nGLASRsZmaa+b8A7SSOxVcFURazENztppsolH
- eTpm0cpLItK7mNpudUg1JGuFo94VLf1MnE2EqARG1vTsNhel/SM/UvOArCCOBvf0Gz7kSuup
- DSZ7qx+LiDmtEsLdbGNQBiYPbLrDk41PHrxdx28qIj7ejZkRzNFw/3pi8/XK281h8zeHoFVu
- 6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbtTdPcXBauY
- </certificate>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <certificate action="publish" uri="rsync://wombat.invalid/testbed/RIR/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.cer"/>
- </msg>
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="3">
+ <!-- Zero or more PDUs -->
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3">
+ <!-- Zero or more PDUs -->
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="3">
+ <publish uri="rsync://wombat.example/Alice/blCrcCp9ltyPDNzYKPfxc.cer">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQDEyhE
+ RjRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XDTA4
+ MDUyMjE4MDUxMloXDTA4MDUyNDE3NTQ1M1owMzExMC8GA1UEAxMoOEZCODIx
+ OEYwNkU1MEFCNzAyQTdEOTZEQzhGMENEQ0Q4MjhGN0YxNzCCASIwDQYJKoZI
+ hvcNAQEBBQADggEPADCCAQoCggEBAMeziKp0k5nP7v6SZoNsXIMQYRgNtC6F
+ r/9Xm/1yQHomiPqHUk47rHhGojYiK5AhkrwoYhkH4UjJl2iwklDYczXuaBU3
+ F5qrKlZ4aZnjIxdlP7+hktVpeApL6yuJTUAYeC3UIxnLDVdD6phydZ/FOQlu
+ ffiNDjzteCCvoyOUatqt8WB+oND6LToHp028g1YUYLHG6mur0dPdcHOVXLSm
+ UDuZ1HDz1nDuYvIVKjB/MpH9aW9XeaQ6ZFIlZVPwuuvI2brR+ThH7Gv27GL/
+ o8qFdC300VQfoTZ+rKPGDE8K1cI906BL4kiwx9z0oiDcE96QCz+B0vsjc9mG
+ aA1jgAxlXWsCAwEAAaOCAhcwggITMB0GA1UdDgQWBBSPuCGPBuUKtwKn2W3I
+ 8M3Ngo9/FzAfBgNVHSMEGDAWgBTfSoAX5mqekXLkYS2M9Mg/I43iozBVBgNV
+ HR8ETjBMMEqgSKBGhkRyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rlc3RiZWQv
+ UklSLzEvMzBxQUYtWnFucEZ5NUdFdGpQVElQeU9ONHFNLmNybDBFBggrBgEF
+ BQcBAQQ5MDcwNQYIKwYBBQUHMAKGKXJzeW5jOi8vbG9jYWxob3N0OjQ0MDAv
+ dGVzdGJlZC9XT01CQVQuY2VyMBgGA1UdIAEB/wQOMAwwCgYIKwYBBQUHDgIw
+ DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwgZsGCCsGAQUFBwEL
+ BIGOMIGLMDQGCCsGAQUFBzAFhihyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rl
+ c3RiZWQvUklSL1IwLzEvMFMGCCsGAQUFBzAKhkdyc3luYzovL2xvY2FsaG9z
+ dDo0NDAwL3Rlc3RiZWQvUklSL1IwLzEvajdnaGp3YmxDcmNDcDlsdHlQRE56
+ WUtQZnhjLm1uZjAaBggrBgEFBQcBCAEB/wQLMAmgBzAFAgMA/BUwPgYIKwYB
+ BQUHAQcBAf8ELzAtMCsEAgABMCUDAwAKAzAOAwUAwAACAQMFAcAAAiAwDgMF
+ AsAAAiwDBQDAAAJkMA0GCSqGSIb3DQEBCwUAA4IBAQCEhuH7jtI2PJY6+zwv
+ 306vmCuXhtu9Lr2mmRw2ZErB8EMcb5xypMrNqMoKeu14K2x4a4RPJkK4yATh
+ M81FPNRsU5mM0acIRnAPtxjHvPME7PHN2w2nGLASRsZmaa+b8A7SSOxVcFUR
+ azENztppsolHeTpm0cpLItK7mNpudUg1JGuFo94VLf1MnE2EqARG1vTsNhel
+ /SM/UvOArCCOBvf0Gz7kSuupDSZ7qx+LiDmtEsLdbGNQBiYPbLrDk41PHrxd
+ x28qIj7ejZkRzNFw/3pi8/XK281h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx
+ 0iwPYdLiDbdWFbtTdPcXBauY
+ </publish>
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3">
+ <publish uri="rsync://wombat.example/Alice/blCrcCp9ltyPDNzYKPfxc.cer"/>
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3">
+ <report_error error_code="your_hair_is_on_fire">
+ Shampooing with sterno again, are we?
+ </report_error>
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3">
+ <report_error error_code="your_hair_is_on_fire"/>
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="3">
+ <withdraw uri="rsync://wombat.example/Alice/blCrcCp9ltyPDNzYKPfxc.cer" hash="deadf00d"/>
+ </msg>
+
+ <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3">
+ <withdraw uri="rsync://wombat.example/Alice/blCrcCp9ltyPDNzYKPfxc.cer"/>
+ </msg>
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <certificate action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.cer"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <certificate action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.cer"/>
- </msg>
-
- <!-- === -->
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <crl action="publish" uri="rsync://wombat.invalid/testbed/RIR/1/30qAF-ZqnpFy5GEtjPTIPyON4qM.crl">
- MIIBwzCBrAIBATANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQDEyhERjRBODAxN0U2NkE5RTkx
- NzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzFw0wODA1MjIxODA0MTZaFw0wODA1MjIxODA1MTZa
- MBQwEgIBAhcNMDgwNTIyMTc1ODQwWqAvMC0wHwYDVR0jBBgwFoAU30qAF+ZqnpFy5GEtjPTI
- PyON4qMwCgYDVR0UBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAKkM0Fb/pJpHVHWZyjp4wojH
- W2KkvA/DFtBiz3moxocSnkDVP3QI19uVvqdC6nH3hJyFmsAMwULR0f1XU/V4j+X+FqYEl6Nv
- p8zAEPIB4r8xbEFs7udRwXRAjkJmOQbv9aomF2i+d7jpTFVJxShZWOgsoGEhIy/aktKQrOIR
- c4ZDrXpQwXVj2Y7+cGVfQ4gvnPOdlyLcnNovoegazATvA3EcidBNPWRg7XTCz0LVBEB7JgPd
- nNyXRg35HdMEHBl7U9uUQJXP7S02oaQ1ehNDMfaJPgBBpQtAnM1lIzJfevd9+e4ywGsRpxAV
- 8wxTXSPd1jwuKtS0kwrgsrQ8Ya85xUE=
- </crl>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <crl action="publish" uri="rsync://wombat.invalid/testbed/RIR/1/30qAF-ZqnpFy5GEtjPTIPyON4qM.crl"/>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <crl action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/1/30qAF-ZqnpFy5GEtjPTIPyON4qM.crl"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <crl action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/1/30qAF-ZqnpFy5GEtjPTIPyON4qM.crl"/>
- </msg>
-
- <!-- === -->
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <manifest action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft">
- MIIHCgYJKoZIhvcNAQcCoIIG+zCCBvcCAQMxDTALBglghkgBZQMEAgEwggEeBgsqhkiG9w0B
- CRABGqCCAQ0EggEJMIIBBQIBEhgPMjAwODA1MjIxODA1MTVaGA8yMDA4MDUyMjE4MDYxNVoG
- CWCGSAFlAwQCATCB0jBEFh9ZbTVUTzRJYnlDb0pNZ3E2R2o4dG41Mng5U0UuY2VyAyEA4L8Z
- WMyuhOx+o6kUfsRR++QjSaRaATy4UOeVtjvZVqYwRBYfWnRxbjB3NEVFbU9hclAzQmd1SUY3
- MDhhNTM4LmNlcgMhAGQI1gYJotxWmwzcmpLNFZJ656uWOjcPYANlbNz80xm8MEQWH2xxa1Vx
- RHEwMDBESW9ZVjlybXdLTGdrN2F6by5jZXIDIQB7jRAEpkPvc4s4PX9vDvnTifj3BIE145FO
- 1ne2kEejVqCCBBEwggQNMIIC9aADAgECAgEFMA0GCSqGSIb3DQEBCwUAMDMxMTAvBgNVBAMT
- KDhGQjgyMThGMDZFNTBBQjcwMkE3RDk2REM4RjBDRENEODI4RjdGMTcwHhcNMDgwNTIyMTc1
- NzQ5WhcNMDgwNTI0MTc1NDUzWjAzMTEwLwYDVQQDEyhERkRBMjMyMUJENEVCMDNFQTE1RkUy
- N0NGRkRGMEFGRkU1QjBFNjY4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2/Gk
- AHW5pDqye0+TvUp7sl0rVgmTfeHpVp18ypxvuovogVJgkjEtBEikfaFU0646wYD6JM6IJFJX
- lWLWd7bVmhkWViKuZL0VmT2wpUToNHCLUGUQUVVX8R7oSHFdTArv2AqH+6yt0LmczDH1y2M6
- 2Tgkz9wZ9ryyuPx3VX4PkHzUMlkGFICj1fvyXkcAu8jBaxR9UME1c413TPaMi6lMh1HUmtVN
- LJMP5+/SnwEAW/Z3dPClCFIgQXK3nAKPVzAIwADEiqhK7cSchhO7ikI1CVt0XzG4n7oaILc3
- Hq/DAxyiutw5GlkUlKPri2YJzJ3+H4P+TveSa/b02fVA5csm/QIDAQABo4IBKjCCASYwHQYD
- VR0OBBYEFN/aIyG9TrA+oV/ifP/fCv/lsOZoMB8GA1UdIwQYMBaAFI+4IY8G5Qq3AqfZbcjw
- zc2Cj38XMFgGA1UdHwRRME8wTaBLoEmGR3JzeW5jOi8vbG9jYWxob3N0OjQ0MDAvdGVzdGJl
- ZC9SSVIvUjAvMS9qN2doandibENyY0NwOWx0eVBETnpZS1BmeGMuY3JsMGAGCCsGAQUFBwEB
- BFQwUjBQBggrBgEFBQcwAoZEcnN5bmM6Ly9sb2NhbGhvc3Q6NDQwMC90ZXN0YmVkL1JJUi8x
- L2o3Z2hqd2JsQ3JjQ3A5bHR5UEROellLUGZ4Yy5jZXIwGAYDVR0gAQH/BA4wDDAKBggrBgEF
- BQcOAjAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBADpsE9HfgVTgmX1WeJTE
- fm87CXuOoGH85RFiAngSt5kR4gYCyadklOZ7Eta+ERUZVu4tcKO6sJOTuHPfVrAvR0VpgH+j
- PvXboYWSfwJdi00BC28ScrVM2zarA7B10+J6Oq8tbFlAyVBkrbuPet/axmndBtGWhrBTynGl
- nc/5L371Lxy6CrOYqXO0Qx3SrOKaailAe3zTIpHQeACqnPdL00zIBw/hVy/VNaH1wy+FmhAz
- TsmsQUrMyovJcu/ry5w0KHlP8BTnqfykikCWR+Lw0VQHmpJGAbtrmsOeIbfLY1zl7A81lDAl
- AG/ZH1DUdDOUIXMLHWur+D2rwjp7RL16LHYxggGqMIIBpgIBA4AU39ojIb1OsD6hX+J8/98K
- /+Ww5mgwCwYJYIZIAWUDBAIBoGswGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEaMBwGCSqG
- SIb3DQEJBTEPFw0wODA1MjIxODA1MTVaMC8GCSqGSIb3DQEJBDEiBCBj/GjEQw3LgKPf5DTz
- 8eu1fcp6/cQjqqne6ZqFkF42azANBgkqhkiG9w0BAQEFAASCAQBOY0uHNMwy/o1nFANSgha5
- PZxt8fz+wTrbeomCb+lxqQKq1clcSiQORVGc8NmqC8sS5OR3eTw/3qnK9yPHxz2UQ4hn1pBa
- +Zy5veM61qMaXCw6w98EyNcvUfA1AkezAjkabfHQDs3o4Ezh49thXXyRcBoF+O6Lmi+LZbT2
- 4jvfFbaXW9zsb6/DaoDkeHnlk+YYgfSP4wOnkK5uqxtDW8QpMPq3GGdIp0oJDkzEdj7VsWIL
- 9JP2mxxL8fTPVUyAPOmURYwYDXqhke2O9eVDiCYhrEfB8/84Rint4Cj8n5aCujnAtqtwxHpD
- 0NRYO/V1MjhG+ARy1vRH1Dm0r92RBam3
- </manifest>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <manifest action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"/>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <manifest action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <manifest action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"/>
- </msg>
-
- <!-- === -->
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <roa action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/lqkUqDq000DIoYV9rmwKLgk7azo.roa">
- MIIGmwYJKoZIhvcNAQcCoIIGjDCCBogCAQMxDTALBglghkgBZQMEAgEwKgYLKoZIhvcNAQkQ
- ARigGwQZMBcCAgKaMBEwDwQCAAEwCTAHAwUACgMALKCCBJgwggSUMIIDfKADAgECAgEJMA0G
- CSqGSIb3DQEBCwUAMDMxMTAvBgNVBAMTKDhGQjgyMThGMDZFNTBBQjcwMkE3RDk2REM4RjBD
- RENEODI4RjdGMTcwHhcNMDgwNTIyMTc1ODI0WhcNMDgwNTI0MTc1NDUzWjAzMTEwLwYDVQQD
- Eyg5NkE5MTRBODNBQjREMzQwQzhBMTg1N0RBRTZDMEEyRTA5M0I2QjNBMIIBIjANBgkqhkiG
- 9w0BAQEFAAOCAQ8AMIIBCgKCAQEApoK50BjW5bcF4gsdaYhndtVADZvQk3RCsvuqDElF6uLi
- 9BYQq/NHyDOIMyJtvCmzjdv3Y135n1sNO7YvssqHlt7dMfCQTD5ND1GpFnQLdWP7stWM5AbO
- nJV6+PtDITUA/QHOli7Do0YCUgR6G+1QJsMu0DK+TRSzBJ6WP7WIYOBOOg3y/NKc1rkWhS1Q
- dcQepbHgQYZHzzpjNDR6+oYVuhuUEWx1P6O4pv/p+tpE0SDua7jBjMywIYHkPQBecf2IX1RU
- WNojB9dJlnRx5YUUneP2SvF2MrmdDbclgzwhf6alqD2OjiMuoBOG8yeTKcuhzCMnrFAklbst
- 6x3Rnq9BswIDAQABo4IBsTCCAa0wHQYDVR0OBBYEFJapFKg6tNNAyKGFfa5sCi4JO2s6MB8G
- A1UdIwQYMBaAFI+4IY8G5Qq3AqfZbcjwzc2Cj38XMFgGA1UdHwRRME8wTaBLoEmGR3JzeW5j
- Oi8vbG9jYWxob3N0OjQ0MDAvdGVzdGJlZC9SSVIvUjAvMS9qN2doandibENyY0NwOWx0eVBE
- TnpZS1BmeGMuY3JsMGAGCCsGAQUFBwEBBFQwUjBQBggrBgEFBQcwAoZEcnN5bmM6Ly9sb2Nh
- bGhvc3Q6NDQwMC90ZXN0YmVkL1JJUi8xL2o3Z2hqd2JsQ3JjQ3A5bHR5UEROellLUGZ4Yy5j
- ZXIwGAYDVR0gAQH/BA4wDDAKBggrBgEFBQcOAjAOBgNVHQ8BAf8EBAMCB4AwYwYIKwYBBQUH
- AQsEVzBVMFMGCCsGAQUFBzALhkdyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rlc3RiZWQvUklS
- L1IwLzEvbHFrVXFEcTAwMERJb1lWOXJtd0tMZ2s3YXpvLnJvYTAgBggrBgEFBQcBBwEB/wQR
- MA8wDQQCAAEwBwMFAAoDACwwDQYJKoZIhvcNAQELBQADggEBAL8iHwsyGOYhhIf3nVuL361y
- TOJSP8SR0mtQLHULPl+GkYk+5MRNWtL8ucTXFvniYJtOCXEGGEIO9eDXvkQIXQSz/qbF9URQ
- fuf38ghRza257syVhal6UHTgCFYuRIO9CUjcU1vkWUxH05BBIHlYdtlIQbAG/mRsCPCEgSmG
- bbQaomGlUOqmJMlKxLLcoAtz2vDrwVotgHyfS5h2mgINFjnlLcNLTci+sfs7/aQAkDYx7K98
- se/ZlMorvGkFNhHoOTcGIrWkYsfkbTygVwWRm278PaB3o4449Kvsg/gb8BZeHXRs68cr5Mcf
- jP7Q6jeypjTgDBnwb1yzoJIKWszFuSgxggGqMIIBpgIBA4AUlqkUqDq000DIoYV9rmwKLgk7
- azowCwYJYIZIAWUDBAIBoGswGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEYMBwGCSqGSIb3
- DQEJBTEPFw0wODA1MjIxNzU4MjRaMC8GCSqGSIb3DQEJBDEiBCDCyf9v9Wed515TRp2WwnyM
- 1rk6dB///X+aqIym2e9jdTANBgkqhkiG9w0BAQEFAASCAQAFvzrHeRPW+wn4WSyoyBEq0zKS
- Cyh5tu1qTR0NHs6Rr/p8Pk81P1HQLND/U+znJZKLWlO2niEHUXPIicPDYchbj8ApH9VxKA+1
- lCWllOzFAsYyZFr3/VNs9pVp2eT4F9eEYBrBVDSNrD72MMTlWm1T5MEXqltTJJOCKzUEX96x
- 91iW6A+4erop7S8hpCnxqkTin4bFVreqYcGc4CC4bh+L9pPqJnURcEk7Qeu/WEHQBm38voB4
- S11qRZNrJMQ99oiJR7hXDIBm66HjGqoUL2gPCfpgJEVVnM9pVv2k889z4eTTck2Qj54gga2W
- Xkvw4Je420aDx88s9T2+PqXcbZ4g
- </roa>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <roa action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/lqkUqDq000DIoYV9rmwKLgk7azo.roa"/>
- </msg>
-
- <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <roa action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/lqkUqDq000DIoYV9rmwKLgk7azo.roa"/>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <roa action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/lqkUqDq000DIoYV9rmwKLgk7azo.roa"/>
- </msg>
-
- <!-- === -->
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <report_error error_code="your_hair_is_on_fire">text string</report_error>
- </msg>
-
- <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <report_error error_code="your_hair_is_on_fire"/>
- </msg>
</completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint>
diff --git a/ca/tests/rrdp-samples.xml b/ca/tests/rrdp-samples.xml
new file mode 100644
index 00000000..0318b169
--- /dev/null
+++ b/ca/tests/rrdp-samples.xml
@@ -0,0 +1,88 @@
+<!-- -*- SGML -*-
+ - $Id$
+ -
+ - This is a collection of sample RRDP PDU samples to use as test
+ - cases for the RRDP RelaxNG schema.
+ -
+ - Need to figure out whose copyright should be on these examples.
+ - BSD in any case so makes little practical difference, just need to
+ - be sure we give proper credit. Might be RIPE, might be IETF
+ - Trust, might be us for derivative work. Slap ours on for the
+ - moment, fix when we figure this out.
+ -
+ - Copyright (C) 2014 Dragon Research Labs ("DRL")
+ -
+ - Permission to use, copy, modify, and 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.
+ -->
+
+<completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint>
+
+ <!-- Notification file: lists current snapshots and deltas -->
+
+ <notification version="1" xmlns="http://www.ripe.net/rpki/rrdp" session_id="d9f6dc91-0394-40b9-9663-66aef4bb623a" serial="203">
+ <snapshot uri="http://host.example/d9f6dc91-0394-40b9-9663-66aeb623a/snapshot/202.xml" hash="279b79fd8389e20585f26735ee70e0e4d4b8af23bb2e2e611c70e92d2433edea"/>
+ <delta from="156" to="183" uri="http://host.example/d9f6c91-0394-40b9-9663-66aeb623a/deltas/156/183.xml" hash="a2d56ec180f2dde2a46bf90565932e25829b852a0b43107d5de6e41394c29100"/>
+ <delta from="183" to="184" uri="http://host.example/d9f6c91-0394-40b9-9663-66aeb623a/deltas/183/184.xml" hash="a2d56ec180f2dde2a46b2e0565932e25829b852a0b43107d5de6e41394c29200"/>
+ <delta from="184" to="197" uri="http://host.example/d9f6c91-0394-40b9-9663-66aeb623a/deltas/184/197.xml" hash="a2d56ec180f2dde2a46b2e0565932e25829b852a0b43107d5de6e41394c29201"/>
+ <delta from="197" to="203" uri="http://host.example/d9f6c91-0394-40b9-9663-66aeb623a/deltas/197/203.xml" hash="a2d56ec180f2dde2a4f92e0565932e25829b852a0b43107d5de6e41394c29300"/>
+ </notification>
+
+ <!-- Snapshot segment: think DNS AXFR -->
+
+ <snapshot version="1" xmlns="http://www.ripe.net/rpki/rrdp" session_id="d9f6dc91-0394-40b9-9663-66aef4bb623a" serial="1">
+ <publish uri="http://host.example/foo/bar/cer1.cer">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQD
+ jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XE
+ h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbtTdPcXBau
+ </publish>
+ <publish uri="http://host.example/foo/bar/cer2.cer">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQD
+ h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbtTdPcXBau
+ jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XD
+ </publish>
+ <publish uri="http://host.example/foo/bar/cer3.cer">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQD
+ h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbtTdPcXBau
+ jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XD
+ </publish>
+ </snapshot>
+
+ <!-- Delta segment: think DNS IXFR -->
+
+ <deltas version="1" xmlns="http://www.ripe.net/rpki/rrdp" session_id="d9f6dc91-0394-40b9-9663-66aef4bb623a" from="0" to="3">
+ <delta serial="1">
+ <publish uri="http://host.example/foo/bar/cer1.cer">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEw
+ jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFM
+ h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbt
+ </publish>
+ </delta>
+ <delta serial="2">
+ <withdraw uri="http://host.example/foo/bar/cer1.cer" hash="deadf00d"/>
+ <publish uri="http://host.example/foo/bar/cer2.cer">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEw
+ h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbt
+ jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFM
+ </publish>
+ <publish uri="http://host.example/foo/bar/cer3.cer" hash="deadf00d">
+ MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEw
+ h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbt
+ jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFM
+ </publish>
+ </delta>
+ <delta serial="3">
+ <withdraw uri="http://host.example/foo/bar/cer2.cer" hash="deadf00d"/>
+ </delta>
+ </deltas>
+
+</completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint>
diff --git a/ca/tests/smoketest.py b/ca/tests/smoketest.py
index 99e2304f..bf949a97 100644
--- a/ca/tests/smoketest.py
+++ b/ca/tests/smoketest.py
@@ -47,7 +47,7 @@ import rpki.http
import rpki.log
import rpki.left_right
import rpki.config
-import rpki.publication
+import rpki.publication_control
import rpki.async
from rpki.mysql_import import MySQLdb
@@ -80,6 +80,7 @@ def allocate_port():
"""
Allocate a TCP port number.
"""
+
global base_port
p = base_port
base_port += 1
@@ -220,7 +221,7 @@ def main():
a.setup_bpki_certs()
setup_publication(pubd_sql)
- setup_rootd(db.root, y.get("rootd", {}))
+ setup_rootd(db.root, y.get("rootd", {}), db)
setup_rsyncd()
setup_rcynic()
@@ -251,15 +252,17 @@ def main():
# the code until final exit is all closures.
def start():
- rpki.async.iterator(db.engines, create_rpki_objects, created_rpki_objects)
+ rpki.async.iterator(db.engines, create_rpki_objects, create_pubd_objects)
def create_rpki_objects(iterator, a):
a.create_rpki_objects(iterator)
- def created_rpki_objects():
-
- # Set pubd's BPKI CRL
- set_pubd_crl(yaml_loop)
+ def create_pubd_objects():
+ call_pubd([rpki.publication_control.client_elt.make_pdu(action = "create",
+ client_handle = db.root.client_handle + "-" + rootd_name,
+ base_uri = rootd_sia,
+ bpki_cert = cross_certify(rootd_name + "-TA", pubd_name + "-TA"))],
+ cb = lambda ignored: yaml_loop())
def yaml_loop():
@@ -326,6 +329,7 @@ def cmd_sleep(cb, interval):
"""
Set an alarm, then wait for it to go off.
"""
+
howlong = rpki.sundial.timedelta.parse(interval)
logger.info("Sleeping %r", howlong)
rpki.async.timer(cb).set(howlong)
@@ -334,6 +338,7 @@ def cmd_shell(cb, *cmd):
"""
Run a shell command.
"""
+
cmd = " ".join(cmd)
status = subprocess.call(cmd, shell = True)
logger.info("Shell command returned status %d", status)
@@ -343,6 +348,7 @@ def cmd_echo(cb, *words):
"""
Echo some text to the log.
"""
+
logger.info(" ".join(words))
cb()
@@ -500,6 +506,7 @@ class allocation_db(list):
"""
Print content of the database.
"""
+
for a in self:
print a
@@ -520,6 +527,7 @@ class allocation(object):
"""
Initialize one entity and insert it into the database.
"""
+
db.append(self)
self.name = yaml["name"]
self.parent = parent
@@ -556,6 +564,7 @@ class allocation(object):
"""
Compute the transitive resource closure.
"""
+
resources = self.base
for kid in self.kids:
resources |= kid.closure()
@@ -710,6 +719,7 @@ class allocation(object):
"""
Set the engine number for this entity.
"""
+
self.irdb_db_name = "irdb%d" % n
self.irdb_port = allocate_port()
self.rpki_db_name = "rpki%d" % n
@@ -719,6 +729,7 @@ class allocation(object):
"""
Get rpki port to use for this entity.
"""
+
if self.is_hosted:
assert self.hosted_by.rpki_port is not None
return self.hosted_by.rpki_port
@@ -730,6 +741,7 @@ class allocation(object):
"""
Create BPKI certificates for this entity.
"""
+
logger.info("Constructing BPKI keys and certs for %s", self.name)
setup_bpki_cert_chain(name = self.name,
ee = ("RPKI", "IRDB", "IRBE"),
@@ -743,15 +755,16 @@ class allocation(object):
"""
Write config files for this entity.
"""
+
logger.info("Writing config files for %s", self.name)
assert self.rpki_port is not None
- d = { "my_name" : self.name,
- "irdb_db_name" : self.irdb_db_name,
- "irdb_db_pass" : irdb_db_pass,
- "irdb_port" : self.irdb_port,
- "rpki_db_name" : self.rpki_db_name,
- "rpki_db_pass" : rpki_db_pass,
- "rpki_port" : self.rpki_port }
+ d = dict(my_name = self.name,
+ irdb_db_name = self.irdb_db_name,
+ irdb_db_pass = irdb_db_pass,
+ irdb_port = self.irdb_port,
+ rpki_db_name = self.rpki_db_name,
+ rpki_db_pass = rpki_db_pass,
+ rpki_port = self.rpki_port)
f = open(self.name + ".conf", "w")
f.write(conf_fmt_1 % d)
for line in self.extra_conf:
@@ -762,6 +775,7 @@ class allocation(object):
"""
Set up this entity's IRDB.
"""
+
logger.info("Setting up MySQL for %s", self.name)
db = MySQLdb.connect(user = "rpki", db = self.rpki_db_name, passwd = rpki_db_pass,
conv = sql_conversions)
@@ -796,6 +810,7 @@ class allocation(object):
once during setup, then do it again every time we apply a delta to
this entity.
"""
+
logger.info("Updating MySQL data for IRDB %s", self.name)
db = MySQLdb.connect(user = "irdb", db = self.irdb_db_name, passwd = irdb_db_pass,
conv = sql_conversions)
@@ -849,6 +864,7 @@ class allocation(object):
"""
Run daemons for this entity.
"""
+
logger.info("Running daemons for %s", self.name)
env = dict(os.environ, RPKI_CONF = self.name + ".conf")
self.rpkid_process = subprocess.Popen((prog_python, prog_rpkid, "--foreground", "--log-stdout", "--log-level", "debug") +
@@ -861,6 +877,7 @@ class allocation(object):
"""
Kill daemons for this entity.
"""
+
# pylint: disable=E1103
for proc, name in ((self.rpkid_process, "rpkid"),
(self.irdbd_process, "irdbd")):
@@ -930,45 +947,7 @@ class allocation(object):
certificant = self.name + "-SELF"
else:
certifier = self.name + "-SELF"
- certfile = certifier + "-" + certificant + ".cer"
-
- logger.info("Cross certifying %s into %s's BPKI (%s)", certificant, certifier, certfile)
-
- child = rpki.x509.X509(Auto_file = certificant + ".cer")
- parent = rpki.x509.X509(Auto_file = certifier + ".cer")
- keypair = rpki.x509.RSA(Auto_file = certifier + ".key")
- serial_file = certifier + ".srl"
-
- now = rpki.sundial.now()
- notAfter = now + rpki.sundial.timedelta(days = 30)
-
- try:
- f = open(serial_file, "r")
- serial = f.read()
- f.close()
- serial = int(serial.splitlines()[0], 16)
- except IOError:
- serial = 1
-
- x = parent.bpki_cross_certify(
- keypair = keypair,
- source_cert = child,
- serial = serial,
- notAfter = notAfter,
- now = now)
-
- f = open(serial_file, "w")
- f.write("%02x\n" % (serial + 1))
- f.close()
-
- f = open(certfile, "w")
- f.write(x.get_PEM())
- f.close()
-
- logger.debug("Cross certified %s:", certfile)
- logger.debug(" Issuer %s [%s]", x.getIssuer(), x.hAKI())
- logger.debug(" Subject %s [%s]", x.getSubject(), x.hSKI())
- return x
+ return cross_certify(certificant, certifier)
def create_rpki_objects(self, cb):
"""
@@ -987,13 +966,11 @@ class allocation(object):
selves = [self] + self.hosts
- for i, s in enumerate(selves):
- logger.info("Creating RPKI objects for [%d] %s", i, s.name)
-
rpkid_pdus = []
pubd_pdus = []
- for s in selves:
+ for i, s in enumerate(selves):
+ logger.info("Creating RPKI objects for [%d] %s", i, s.name)
rpkid_pdus.append(rpki.left_right.self_elt.make_pdu(
action = "create",
@@ -1010,7 +987,7 @@ class allocation(object):
bsc_handle = "b",
generate_keypair = True))
- pubd_pdus.append(rpki.publication.client_elt.make_pdu(
+ pubd_pdus.append(rpki.publication_control.client_elt.make_pdu(
action = "create",
client_handle = s.client_handle,
base_uri = s.sia_base,
@@ -1179,20 +1156,22 @@ def setup_bpki_cert_chain(name, ee = (), ca = ()):
"""
Build a set of BPKI certificates.
"""
+
s = "exec >/dev/null 2>&1\n"
#s = "set -x\n"
for kind in ("TA",) + ee + ca:
- d = { "name" : name,
- "kind" : kind,
- "ca" : "false" if kind in ee else "true",
- "openssl" : prog_openssl }
+ d = dict(name = name,
+ kind = kind,
+ ca = "false" if kind in ee else "true",
+ openssl = prog_openssl)
f = open("%(name)s-%(kind)s.conf" % d, "w")
f.write(bpki_cert_fmt_1 % d)
f.close()
if not os.path.exists("%(name)s-%(kind)s.key" % d):
s += bpki_cert_fmt_2 % d
s += bpki_cert_fmt_3 % d
- d = { "name" : name, "openssl" : prog_openssl }
+ d = dict(name = name,
+ openssl = prog_openssl)
s += bpki_cert_fmt_4 % d
for kind in ee + ca:
d["kind"] = kind
@@ -1202,19 +1181,24 @@ def setup_bpki_cert_chain(name, ee = (), ca = ()):
s += bpki_cert_fmt_6 % d
subprocess.check_call(s, shell = True)
-def setup_rootd(rpkid, rootd_yaml):
+def setup_rootd(rpkid, rootd_yaml, db):
"""
Write the config files for rootd.
"""
+
rpkid.cross_certify(rootd_name + "-TA", reverse = True)
+ cross_certify(pubd_name + "-TA", rootd_name + "-TA")
logger.info("Writing config files for %s", rootd_name)
- d = { "rootd_name" : rootd_name,
- "rootd_port" : rootd_port,
- "rpkid_name" : rpkid.name,
- "rootd_sia" : rootd_sia,
- "rsyncd_dir" : rsyncd_dir,
- "openssl" : prog_openssl,
- "lifetime" : rootd_yaml.get("lifetime", "30d") }
+ d = dict(rootd_name = rootd_name,
+ rootd_port = rootd_port,
+ rpkid_name = rpkid.name,
+ pubd_name = pubd_name,
+ rootd_sia = rootd_sia,
+ rsyncd_dir = rsyncd_dir,
+ openssl = prog_openssl,
+ lifetime = rootd_yaml.get("lifetime", "30d"),
+ pubd_port = pubd_port,
+ rootd_handle = db.root.client_handle + "-" + rootd_name)
f = open(rootd_name + ".conf", "w")
f.write(rootd_fmt_1 % d)
f.close()
@@ -1229,10 +1213,11 @@ def setup_rcynic():
"""
Write the config file for rcynic.
"""
+
logger.info("Config file for rcynic")
- d = { "rcynic_name" : rcynic_name,
- "rootd_name" : rootd_name,
- "rootd_sia" : rootd_sia }
+ d = dict(rcynic_name = rcynic_name,
+ rootd_name = rootd_name,
+ rootd_sia = rootd_sia)
f = open(rcynic_name + ".conf", "w")
f.write(rcynic_fmt_1 % d)
f.close()
@@ -1241,11 +1226,12 @@ def setup_rsyncd():
"""
Write the config file for rsyncd.
"""
+
logger.info("Config file for rsyncd")
- d = { "rsyncd_name" : rsyncd_name,
- "rsyncd_port" : rsyncd_port,
- "rsyncd_module" : rsyncd_module,
- "rsyncd_dir" : rsyncd_dir }
+ d = dict(rsyncd_name = rsyncd_name,
+ rsyncd_port = rsyncd_port,
+ rsyncd_module = rsyncd_module,
+ rsyncd_dir = rsyncd_dir)
f = open(rsyncd_name + ".conf", "w")
f.write(rsyncd_fmt_1 % d)
f.close()
@@ -1254,6 +1240,7 @@ def setup_publication(pubd_sql):
"""
Set up publication daemon.
"""
+
logger.info("Configure publication daemon")
publication_dir = os.getcwd() + "/publication"
assert rootd_sia.startswith("rsync://")
@@ -1273,12 +1260,12 @@ def setup_publication(pubd_sql):
if "DROP TABLE IF EXISTS" not in sql.upper():
raise
db.close()
- d = { "pubd_name" : pubd_name,
- "pubd_port" : pubd_port,
- "pubd_db_name" : pubd_db_name,
- "pubd_db_user" : pubd_db_user,
- "pubd_db_pass" : pubd_db_pass,
- "pubd_dir" : rsyncd_dir }
+ d = dict(pubd_name = pubd_name,
+ pubd_port = pubd_port,
+ pubd_db_name = pubd_db_name,
+ pubd_db_user = pubd_db_user,
+ pubd_db_pass = pubd_db_pass,
+ pubd_dir = rsyncd_dir)
f = open(pubd_name + ".conf", "w")
f.write(pubd_fmt_1 % d)
f.close()
@@ -1293,12 +1280,13 @@ def setup_publication(pubd_sql):
def call_pubd(pdus, cb):
"""
- Send a publication message to publication daemon and return the
- response.
+ Send a publication control message to publication daemon and return
+ the response.
"""
+
logger.info("Calling pubd")
- q_msg = rpki.publication.msg.query(*pdus)
- q_cms = rpki.publication.cms_msg()
+ q_msg = rpki.publication_control.msg.query(*pdus)
+ q_cms = rpki.publication_control.cms_msg()
q_der = q_cms.wrap(q_msg, pubd_irbe_key, pubd_irbe_cert)
q_url = "http://localhost:%d/control" % pubd_port
@@ -1306,13 +1294,13 @@ def call_pubd(pdus, cb):
def call_pubd_cb(r_der):
global pubd_last_cms_time
- r_cms = rpki.publication.cms_msg(DER = r_der)
+ r_cms = rpki.publication_control.cms_msg(DER = r_der)
r_msg = r_cms.unwrap((pubd_ta, pubd_pubd_cert))
pubd_last_cms_time = r_cms.check_replay(pubd_last_cms_time, q_url)
logger.debug(r_cms.pretty_print_content())
assert r_msg.is_reply
for r_pdu in r_msg:
- assert not isinstance(r_pdu, rpki.publication.report_error_elt)
+ r_pdu.raise_if_error()
cb(r_msg)
def call_pubd_eb(e):
@@ -1324,15 +1312,47 @@ def call_pubd(pdus, cb):
callback = call_pubd_cb,
errback = call_pubd_eb)
-def set_pubd_crl(cb):
+
+def cross_certify(certificant, certifier):
"""
- Whack publication daemon's bpki_crl. This must be configured before
- publication daemon starts talking to its clients, and must be
- updated whenever we update the CRL.
+ Cross-certify and return the resulting certificate.
"""
- logger.info("Setting pubd's BPKI CRL")
- crl = rpki.x509.CRL(Auto_file = pubd_name + "-TA.crl")
- call_pubd([rpki.publication.config_elt.make_pdu(action = "set", bpki_crl = crl)], cb = lambda ignored: cb())
+
+ certfile = certifier + "-" + certificant + ".cer"
+
+ logger.info("Cross certifying %s into %s's BPKI (%s)", certificant, certifier, certfile)
+
+ child = rpki.x509.X509(Auto_file = certificant + ".cer")
+ parent = rpki.x509.X509(Auto_file = certifier + ".cer")
+ keypair = rpki.x509.RSA(Auto_file = certifier + ".key")
+ serial_file = certifier + ".srl"
+
+ now = rpki.sundial.now()
+ notAfter = now + rpki.sundial.timedelta(days = 30)
+
+ try:
+ with open(serial_file, "r") as f:
+ serial = int(f.read().splitlines()[0], 16)
+ except IOError:
+ serial = 1
+
+ x = parent.bpki_cross_certify(
+ keypair = keypair,
+ source_cert = child,
+ serial = serial,
+ notAfter = notAfter,
+ now = now)
+
+ with open(serial_file, "w") as f:
+ f.write("%02x\n" % (serial + 1))
+
+ with open(certfile, "w") as f:
+ f.write(x.get_PEM())
+
+ logger.debug("Cross certified %s:", certfile)
+ logger.debug(" Issuer %s [%s]", x.getIssuer(), x.hAKI())
+ logger.debug(" Subject %s [%s]", x.getSubject(), x.hSKI())
+ return x
last_rcynic_run = None
@@ -1340,6 +1360,7 @@ def run_rcynic():
"""
Run rcynic to see whether what was published makes sense.
"""
+
logger.info("Running rcynic")
env = os.environ.copy()
env["TZ"] = ""
@@ -1355,6 +1376,7 @@ def mangle_sql(filename):
"""
Mangle an SQL file into a sequence of SQL statements.
"""
+
words = []
f = open(filename)
for line in f:
@@ -1530,24 +1552,28 @@ rootd-bpki-cert = %(rootd_name)s-RPKI.cer
rootd-bpki-key = %(rootd_name)s-RPKI.key
rootd-bpki-crl = %(rootd_name)s-TA.crl
child-bpki-cert = %(rootd_name)s-TA-%(rpkid_name)s-SELF.cer
+pubd-bpki-cert = %(rootd_name)s-TA-%(pubd_name)s-TA.cer
server-port = %(rootd_port)s
-rpki-root-dir = %(rsyncd_dir)sroot
-rpki-base-uri = %(rootd_sia)sroot/
-rpki-root-cert-uri = %(rootd_sia)sroot.cer
+rpki-class-name = trunk
-rpki-root-key = root.key
-rpki-root-cert = root.cer
+pubd-contact-uri = http://localhost:%(pubd_port)d/client/%(rootd_handle)s
-rpki-subject-pkcs10 = %(rootd_name)s.subject.pkcs10
+rpki-root-cert-file = root.cer
+rpki-root-cert-uri = %(rootd_sia)sroot.cer
+rpki-root-key-file = root.key
+
+rpki-subject-cert-file = trunk.cer
+rpki-subject-cert-uri = %(rootd_sia)sroot/trunk.cer
+rpki-subject-pkcs10-file= trunk.p10
rpki-subject-lifetime = %(lifetime)s
-rpki-root-crl = root.crl
-rpki-root-manifest = root.mft
+rpki-root-crl-file = root.crl
+rpki-root-crl-uri = %(rootd_sia)sroot/root.crl
-rpki-class-name = trunk
-rpki-subject-cert = trunk.cer
+rpki-root-manifest-file = root.mft
+rpki-root-manifest-uri = %(rootd_sia)sroot/root.mft
include-bpki-crl = yes
enable_tracebacks = yes
@@ -1607,8 +1633,7 @@ awk '!/-----(BEGIN|END)/' >>%(rootd_name)s.tal &&
-outform DER \
-extfile %(rootd_name)s.conf \
-extensions req_x509_rpki_ext \
- -signkey root.key &&
-ln -f root.cer %(rsyncd_dir)s
+ -signkey root.key
'''
rcynic_fmt_1 = '''\
@@ -1641,6 +1666,7 @@ sql-database = %(pubd_db_name)s
sql-username = %(pubd_db_user)s
sql-password = %(pubd_db_pass)s
bpki-ta = %(pubd_name)s-TA.cer
+pubd-crl = %(pubd_name)s-TA.crl
pubd-cert = %(pubd_name)s-PUBD.cer
pubd-key = %(pubd_name)s-PUBD.key
irbe-cert = %(pubd_name)s-IRBE.cer
diff --git a/ca/tests/test-rrdp.py b/ca/tests/test-rrdp.py
new file mode 100755
index 00000000..98918bad
--- /dev/null
+++ b/ca/tests/test-rrdp.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+# $Id$
+#
+# Copyright (C) 2013 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and 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.
+
+"""
+Proof-of-concept test driver for RRDP code. Still fairly kludgy in places.
+"""
+
+import os
+import sys
+import glob
+import time
+import textwrap
+import argparse
+import subprocess
+
+parser = argparse.ArgumentParser(description = __doc__)
+parser.add_argument("--use-smoketest", action = "store_true")
+parser.add_argument("--yaml-file", default = "smoketest.2.yaml")
+parser.add_argument("--delay", type = int, default = 300)
+parser.add_argument("--exhaustive", action = "store_true")
+parser.add_argument("--skip-daemons", action = "store_true")
+args = parser.parse_args()
+
+def log(msg):
+ sys.stdout.write(msg + "\n")
+ sys.stdout.flush()
+
+def run(*argv):
+ log("Running: " + " ".join(argv))
+ subprocess.check_call(argv)
+
+def dataglob(pattern):
+ return glob.iglob(os.path.join(("smoketest.dir" if args.use_smoketest else "yamltest.dir/RIR"), pattern))
+
+def snapshot_to_serial(fn):
+ return int(os.path.splitext(os.path.basename(fn))[0])
+
+def delta_to_serial(fn):
+ return int(os.path.splitext(os.path.basename(fn))[0].split("-")[1])
+
+top = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "..", ".."))
+
+rrdp_test_tool = os.path.join(top, "potpourri/rrdp-test-tool")
+rcynic = os.path.join(top, "rp/rcynic/rcynic")
+rcynic_text = os.path.join(top, "rp/rcynic/rcynic-text")
+
+with open("rcynic-rrdp.conf", "w") as f:
+ f.write(textwrap.dedent('''# Automatically generated for RRDP tests, do not edit.
+ [rcynic]
+ xml-summary = rcynic.xml
+ jitter = 0
+ use-links = yes
+ use-syslog = no
+ use-stderr = yes
+ log-level = log_debug
+ run-rsync = no
+ '''))
+ if args.use_smoketest:
+ f.write("trust-anchor = smoketest.dir/root.cer\n")
+ else:
+ f.write("trust-anchor = yamltest.dir/RIR/publication/RIR-root/root.cer\n")
+
+if args.skip_daemons:
+ log("--skip-daemons specified, so running neither smoketest nor yamltest")
+elif args.use_smoketest:
+ run("python", "smoketest.py", args.yaml_file)
+else:
+ run("python", "sql-cleaner.py")
+ argv = ("python", "yamltest.py", args.yaml_file)
+ log("Running: " + " ".join(argv))
+ yamltest = subprocess.Popen(argv)
+ log("Sleeping %s" % args.delay)
+ time.sleep(args.delay)
+ yamltest.terminate()
+
+snapshots = dict((snapshot_to_serial(fn), fn) for fn in dataglob("rrdp-publication/*/snapshot/*.xml"))
+deltas = dict((delta_to_serial(fn), fn) for fn in dataglob("rrdp-publication/*/deltas/*.xml"))
+
+for snapshot in sorted(snapshots):
+
+ time.sleep(1)
+ run("rm", "-rf", "rcynic-data")
+ run(rrdp_test_tool, snapshots[snapshot])
+ run(rcynic, "-c", "rcynic-rrdp.conf")
+ run(rcynic_text, "rcynic.xml")
+
+ for delta in sorted(deltas):
+ if delta > snapshot:
+ time.sleep(1)
+ run(rrdp_test_tool, deltas[delta])
+ run(rcynic, "-c", "rcynic-rrdp.conf")
+ run(rcynic_text, "rcynic.xml")
+
+ if not args.exhaustive:
+ break
diff --git a/ca/tests/testpoke.py b/ca/tests/testpoke.py
index efa068c9..8a443e0d 100644
--- a/ca/tests/testpoke.py
+++ b/ca/tests/testpoke.py
@@ -74,9 +74,9 @@ def get_PEM_chain(name, cert = None):
if cert is not None:
chain.append(cert)
if name in yaml_data:
- chain.extend([rpki.x509.X509(PEM = x) for x in yaml_data[name]])
+ chain.extend(rpki.x509.X509(PEM = x) for x in yaml_data[name])
elif name + "-file" in yaml_data:
- chain.extend([rpki.x509.X509(PEM_file = x) for x in yaml_data[name + "-file"]])
+ chain.extend(rpki.x509.X509(PEM_file = x) for x in yaml_data[name + "-file"])
return chain
def query_up_down(q_pdu):
diff --git a/ca/tests/xml-parse-test.py b/ca/tests/xml-parse-test.py
index 5ea25492..85f4453e 100644
--- a/ca/tests/xml-parse-test.py
+++ b/ca/tests/xml-parse-test.py
@@ -28,8 +28,14 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
-import glob, lxml.etree, lxml.sax
-import rpki.up_down, rpki.left_right, rpki.publication, rpki.relaxng
+import glob
+import lxml.etree
+import lxml.sax
+import rpki.up_down
+import rpki.left_right
+import rpki.publication
+import rpki.publication_control
+import rpki.relaxng
verbose = False
@@ -88,17 +94,17 @@ def lr_tester(elt_in, elt_out, msg):
def pp_tester(elt_in, elt_out, msg):
assert isinstance(msg, rpki.publication.msg)
for obj in msg:
- if isinstance(obj, rpki.publication.client_elt):
+ if isinstance(obj, rpki.publication.publish_elt):
+ pprint(((obj.payload, "Publish object"),))
+ if isinstance(obj, rpki.publication.withdraw_elt):
+ pprint(((None, "Withdraw object"),))
+
+def pc_tester(elt_in, elt_out, msg):
+ assert isinstance(msg, rpki.publication_control.msg)
+ for obj in msg:
+ if isinstance(obj, rpki.publication_control.client_elt):
pprint(((obj.bpki_cert, "BPKI cert"),
(obj.bpki_glue, "BPKI glue")))
- if isinstance(obj, rpki.publication.certificate_elt):
- pprint(((obj.payload, "RPKI cert"),))
- if isinstance(obj, rpki.publication.crl_elt):
- pprint(((obj.payload, "RPKI CRL"),))
- if isinstance(obj, rpki.publication.manifest_elt):
- pprint(((obj.payload, "RPKI manifest"),))
- if isinstance(obj, rpki.publication.roa_elt):
- pprint(((obj.payload, "ROA"),))
test(fileglob = "up-down-protocol-samples/*.xml",
rng = rpki.relaxng.up_down,
@@ -117,3 +123,9 @@ test(fileglob = "publication-protocol-samples/*.xml",
sax_handler = rpki.publication.sax_handler,
encoding = "us-ascii",
tester = pp_tester)
+
+test(fileglob = "publication-control-protocol-samples/*.xml",
+ rng = rpki.relaxng.publication_control,
+ sax_handler = rpki.publication_control.sax_handler,
+ encoding = "us-ascii",
+ tester = pc_tester)
diff --git a/ca/tests/yamlconf.py b/ca/tests/yamlconf.py
index c54c0c65..bb82ef74 100644
--- a/ca/tests/yamlconf.py
+++ b/ca/tests/yamlconf.py
@@ -532,15 +532,15 @@ class allocation(object):
notAfter = rpki.sundial.now() + rpki.sundial.timedelta(days = 365),
resources = root_resources)
- with open(self.path("publication.root", "root.cer"), "wb") as f:
+ with open(self.path("root.cer"), "wb") as f:
f.write(root_cert.get_DER())
with open(self.path("root.key"), "wb") as f:
f.write(root_key.get_DER())
with open(cleanpath(test_dir, "root.tal"), "w") as f:
- f.write("rsync://%s/root/root.cer\n\n%s" % (
- self.rsync_server, root_key.get_public().get_Base64()))
+ f.write("rsync://%s/root/root.cer\n\n" % self.rsync_server)
+ f.write(root_key.get_public().get_Base64())
def mkdir(self, *path):
path = self.path(*path)
diff --git a/ca/tests/yamltest.py b/ca/tests/yamltest.py
index bc78c090..84355e59 100644
--- a/ca/tests/yamltest.py
+++ b/ca/tests/yamltest.py
@@ -68,6 +68,7 @@ def cleanpath(*names):
"""
Construct normalized pathnames.
"""
+
return os.path.normpath(os.path.join(*names))
# Pathnames for various things we need
@@ -112,6 +113,7 @@ class roa_request(object):
"""
Parse a ROA request from YAML format.
"""
+
return cls(y.get("asn"), y.get("ipv4"), y.get("ipv6"))
@@ -182,6 +184,7 @@ class allocation_db(list):
"""
Show contents of allocation database.
"""
+
for a in self:
a.dump()
@@ -212,6 +215,7 @@ class allocation(object):
"""
Allocate a TCP port.
"""
+
cls.base_port += 1
return cls.base_port
@@ -223,6 +227,7 @@ class allocation(object):
Allocate an engine number, mostly used to construct MySQL database
names.
"""
+
cls.base_engine += 1
return cls.base_engine
@@ -277,6 +282,7 @@ class allocation(object):
Compute resource closure of this node and its children, to avoid a
lot of tedious (and error-prone) duplication in the YAML file.
"""
+
resources = self.base
for kid in self.kids:
resources |= kid.closure()
@@ -287,6 +293,7 @@ class allocation(object):
"""
Show content of this allocation node.
"""
+
print str(self)
def __str__(self):
@@ -311,6 +318,7 @@ class allocation(object):
"""
Is this the root node?
"""
+
return self.parent is None
@property
@@ -318,6 +326,7 @@ class allocation(object):
"""
Is this entity hosted?
"""
+
return self.hosted_by is not None
@property
@@ -325,18 +334,21 @@ class allocation(object):
"""
Does this entity run a pubd?
"""
+
return self.is_root or not (self.is_hosted or only_one_pubd)
def path(self, *names):
"""
Construct pathnames in this entity's test directory.
"""
+
return cleanpath(test_dir, self.host.name, *names)
def csvout(self, fn):
"""
Open and log a CSV output file.
"""
+
path = self.path(fn)
print "Writing", path
return rpki.csv_utils.csv_writer(path)
@@ -345,6 +357,7 @@ class allocation(object):
"""
Construct service URL for this node's parent.
"""
+
return "http://localhost:%d/up-down/%s/%s" % (self.parent.host.rpkid_port,
self.parent.name,
self.name)
@@ -353,12 +366,12 @@ class allocation(object):
"""
Write Autonomous System Numbers CSV file.
"""
+
fn = "%s.asns.csv" % d.name
if not args.skip_config:
- f = self.csvout(fn)
- for k in self.kids:
- f.writerows((k.name, a) for a in k.resources.asn)
- f.close()
+ with self.csvout(fn) as f:
+ for k in self.kids:
+ f.writerows((k.name, a) for a in k.resources.asn)
if not args.stop_after_config:
self.run_rpkic("load_asns", fn)
@@ -366,12 +379,12 @@ class allocation(object):
"""
Write prefixes CSV file.
"""
+
fn = "%s.prefixes.csv" % d.name
if not args.skip_config:
- f = self.csvout(fn)
- for k in self.kids:
- f.writerows((k.name, p) for p in (k.resources.v4 + k.resources.v6))
- f.close()
+ with self.csvout(fn) as f:
+ for k in self.kids:
+ f.writerows((k.name, p) for p in (k.resources.v4 + k.resources.v6))
if not args.stop_after_config:
self.run_rpkic("load_prefixes", fn)
@@ -379,13 +392,13 @@ class allocation(object):
"""
Write ROA CSV file.
"""
+
fn = "%s.roas.csv" % d.name
if not args.skip_config:
- f = self.csvout(fn)
- for g1, r in enumerate(self.roa_requests):
- f.writerows((p, r.asn, "G%08d%08d" % (g1, g2))
- for g2, p in enumerate((r.v4 + r.v6 if r.v4 and r.v6 else r.v4 or r.v6 or ())))
- f.close()
+ with self.csvout(fn) as f:
+ for g1, r in enumerate(self.roa_requests):
+ f.writerows((p, r.asn, "G%08d%08d" % (g1, g2))
+ for g2, p in enumerate((r.v4 + r.v6 if r.v4 and r.v6 else r.v4 or r.v6 or ())))
if not args.stop_after_config:
self.run_rpkic("load_roa_requests", fn)
@@ -393,17 +406,14 @@ class allocation(object):
"""
Write Ghostbusters vCard file.
"""
+
if self.ghostbusters:
fn = "%s.ghostbusters.vcard" % d.name
if not args.skip_config:
path = self.path(fn)
print "Writing", path
- f = open(path, "w")
- for i, g in enumerate(self.ghostbusters):
- if i:
- f.write("\n")
- f.write(g)
- f.close()
+ with open(path, "w") as f:
+ f.write("\n".join(self.ghostbusters))
if not args.stop_after_config:
self.run_rpkic("load_ghostbuster_requests", fn)
@@ -411,6 +421,7 @@ class allocation(object):
"""
Write EE certificates (router certificates, etc).
"""
+
if self.router_certs:
fn = "%s.routercerts.xml" % d.name
if not args.skip_config:
@@ -436,6 +447,7 @@ class allocation(object):
"""
Walk up tree until we find somebody who runs pubd.
"""
+
s = self
while not s.runs_pubd:
s = s.parent
@@ -446,6 +458,7 @@ class allocation(object):
"""
Work out what pubd configure_publication_client will call us.
"""
+
path = []
s = self
if not args.flat_publication:
@@ -465,46 +478,45 @@ class allocation(object):
"""
r = dict(
- handle = self.name,
- run_rpkid = str(not self.is_hosted),
- run_pubd = str(self.runs_pubd),
- run_rootd = str(self.is_root),
- irdbd_sql_database = "irdb%d" % self.engine,
- irdbd_sql_username = "irdb",
- rpkid_sql_database = "rpki%d" % self.engine,
- rpkid_sql_username = "rpki",
- rpkid_server_host = "localhost",
- rpkid_server_port = str(self.rpkid_port),
- irdbd_server_host = "localhost",
- irdbd_server_port = str(self.irdbd_port),
- rootd_server_port = str(self.rootd_port),
- pubd_sql_database = "pubd%d" % self.engine,
- pubd_sql_username = "pubd",
- pubd_server_host = "localhost",
- pubd_server_port = str(self.pubd.pubd_port),
- publication_rsync_server = "localhost:%s" % self.pubd.rsync_port,
- bpki_servers_directory = self.path(),
- publication_base_directory = self.path("publication"),
- shared_sql_password = "fnord")
+ handle = self.name,
+ run_rpkid = str(not self.is_hosted),
+ run_pubd = str(self.runs_pubd),
+ run_rootd = str(self.is_root),
+ irdbd_sql_database = "irdb%d" % self.engine,
+ irdbd_sql_username = "irdb",
+ rpkid_sql_database = "rpki%d" % self.engine,
+ rpkid_sql_username = "rpki",
+ rpkid_server_host = "localhost",
+ rpkid_server_port = str(self.rpkid_port),
+ irdbd_server_host = "localhost",
+ irdbd_server_port = str(self.irdbd_port),
+ rootd_server_port = str(self.rootd_port),
+ pubd_sql_database = "pubd%d" % self.engine,
+ pubd_sql_username = "pubd",
+ pubd_server_host = "localhost",
+ pubd_server_port = str(self.pubd.pubd_port),
+ publication_rsync_server = "localhost:%s" % self.pubd.rsync_port,
+ bpki_servers_directory = self.path(),
+ publication_base_directory = self.path("publication"),
+ rrdp_publication_base_directory = self.path("rrdp-publication"),
+ shared_sql_password = "fnord")
r.update(config_overrides)
- f = open(self.path("rpki.conf"), "w")
- f.write("# Automatically generated, do not edit\n")
- print "Writing", f.name
-
- section = None
- for line in open(cleanpath(ca_dir, "examples/rpki.conf")):
- m = section_regexp.match(line)
- if m:
- section = m.group(1)
- m = variable_regexp.match(line)
- option = m.group(1) if m and section == "myrpki" else None
- if option and option in r:
- line = "%s = %s\n" % (option, r[option])
- f.write(line)
+ with open(self.path("rpki.conf"), "w") as f:
+ f.write("# Automatically generated, do not edit\n")
+ print "Writing", f.name
- f.close()
+ section = None
+ for line in open(cleanpath(ca_dir, "examples/rpki.conf")):
+ m = section_regexp.match(line)
+ if m:
+ section = m.group(1)
+ m = variable_regexp.match(line)
+ option = m.group(1) if m and section == "myrpki" else None
+ if option and option in r:
+ line = "%s = %s\n" % (option, r[option])
+ f.write(line)
def dump_rsyncd(self):
"""
@@ -512,25 +524,24 @@ class allocation(object):
"""
if self.runs_pubd:
- f = open(self.path("rsyncd.conf"), "w")
- print "Writing", f.name
- f.writelines(s + "\n" for s in
- ("# Automatically generated, do not edit",
- "port = %d" % self.rsync_port,
- "address = localhost",
- "[rpki]",
- "log file = rsyncd.log",
- "read only = yes",
- "use chroot = no",
- "path = %s" % self.path("publication"),
- "comment = RPKI test",
- "[root]",
- "log file = rsyncd_root.log",
- "read only = yes",
- "use chroot = no",
- "path = %s" % self.path("publication.root"),
- "comment = RPKI test root"))
- f.close()
+ with open(self.path("rsyncd.conf"), "w") as f:
+ print "Writing", f.name
+ f.writelines(s + "\n" for s in
+ ("# Automatically generated, do not edit",
+ "port = %d" % self.rsync_port,
+ "address = localhost",
+ "[rpki]",
+ "log file = rsyncd.log",
+ "read only = yes",
+ "use chroot = no",
+ "path = %s" % self.path("publication"),
+ "comment = RPKI test",
+ "[root]",
+ "log file = rsyncd_root.log",
+ "read only = yes",
+ "use chroot = no",
+ "path = %s" % self.path("publication.root"),
+ "comment = RPKI test root"))
@classmethod
def next_rpkic_counter(cls):
@@ -599,30 +610,35 @@ class allocation(object):
"""
Run rpkid.
"""
+
return self.run_python_daemon(prog_rpkid)
def run_irdbd(self):
"""
Run irdbd.
"""
+
return self.run_python_daemon(prog_irdbd)
def run_pubd(self):
"""
Run pubd.
"""
+
return self.run_python_daemon(prog_pubd)
def run_rootd(self):
"""
Run rootd.
"""
+
return self.run_python_daemon(prog_rootd)
def run_rsyncd(self):
"""
Run rsyncd.
"""
+
p = subprocess.Popen(("rsync", "--daemon", "--no-detach", "--config", "rsyncd.conf"),
cwd = self.path())
print "Running rsyncd for %s: pid %d process %r" % (self.name, p.pid, p)
@@ -657,9 +673,9 @@ def create_root_certificate(db_root):
root_key = rpki.x509.RSA.generate(quiet = True)
- root_uri = "rsync://localhost:%d/rpki/" % db_root.pubd.rsync_port
+ root_uri = "rsync://localhost:%d/rpki/%s-root/root" % (db_root.pubd.rsync_port, db_root.name)
- root_sia = (root_uri, root_uri + "root.mft", None)
+ root_sia = (root_uri + "/", root_uri + "/root.mft", None)
root_cert = rpki.x509.X509.self_certify(
keypair = root_key,
@@ -669,18 +685,15 @@ def create_root_certificate(db_root):
notAfter = rpki.sundial.now() + rpki.sundial.timedelta(days = 365),
resources = root_resources)
- f = open(db_root.path("publication.root/root.cer"), "wb")
- f.write(root_cert.get_DER())
- f.close()
+ with open(db_root.path("root.cer"), "wb") as f:
+ f.write(root_cert.get_DER())
- f = open(db_root.path("root.key"), "wb")
- f.write(root_key.get_DER())
- f.close()
+ with open(db_root.path("root.key"), "wb") as f:
+ f.write(root_key.get_DER())
- f = open(os.path.join(test_dir, "root.tal"), "w")
- f.write("rsync://localhost:%d/root/root.cer\n\n" % db_root.pubd.rsync_port)
- f.write(root_key.get_public().get_Base64())
- f.close()
+ with open(os.path.join(test_dir, "root.tal"), "w") as f:
+ f.write(root_uri + ".cer\n\n")
+ f.write(root_key.get_public().get_Base64())
logger = logging.getLogger(__name__)
diff --git a/h/rpki/sk_roa.h b/h/rpki/sk_roa.h
index 13036955..7423f8ff 100644
--- a/h/rpki/sk_roa.h
+++ b/h/rpki/sk_roa.h
@@ -1,6 +1,6 @@
/*
* Automatically generated, do not edit.
- * Generator $Id: defstack.py 4878 2012-11-15 22:13:53Z sra $
+ * Generator $Id: defstack.py 5784 2014-04-10 22:56:47Z sra $
*/
#ifndef __RPKI_ROA_H__DEFSTACK_H__
diff --git a/potpourri/rrdp-fetch.py b/potpourri/rrdp-fetch.py
new file mode 100755
index 00000000..aa5b762b
--- /dev/null
+++ b/potpourri/rrdp-fetch.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+# $Id$
+#
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and 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.
+
+"""
+Fetch an RRDP notifcation file and follow all the links. Should be
+merged into rrdp-test-tool eventually, but one thing at a time.
+"""
+
+from urllib2 import urlopen
+from lxml.etree import ElementTree, XML
+from socket import getfqdn
+from rpki.x509 import sha256
+from rpki.relaxng import rrdp
+from urlparse import urlparse
+from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
+
+class BadHash(Exception):
+ "Calculated hash value doesn't match expected hash value."
+
+def fetch(elt):
+ uri = elt.get("uri")
+ hash = elt.get("hash")
+ print "Fetching", uri
+
+ text = urlopen(uri).read()
+ h = sha256(text).encode("hex")
+ if h != hash:
+ raise BadHash("Bad hash for %s: expected %s got %s" % (uri, hash, h))
+
+ xml = XML(text)
+ rrdp.schema.assertValid(xml)
+
+ u = urlparse(uri)
+ fn = u.netloc + u.path
+
+ return elt, xml, fn
+
+parser = ArgumentParser(description = __doc__, formatter_class = ArgumentDefaultsHelpFormatter)
+parser.add_argument("uri", nargs = "?",
+ default = "http://" + getfqdn() + "/rrdp/updates.xml",
+ help = "RRDP notification file to fetch")
+args = parser.parse_args()
+
+updates = ElementTree(file = urlopen(args.uri))
+rrdp.schema.assertValid(updates)
+
+snapshot = fetch(updates.find(rrdp.xmlns + "snapshot"))
+
+deltas = [fetch(elt) for elt in updates.findall(rrdp.xmlns + "delta")]
+
+print updates
+print snapshot
+for delta in deltas:
+ print delta
diff --git a/potpourri/rrdp-test-tool b/potpourri/rrdp-test-tool
new file mode 100755
index 00000000..b4dc65da
--- /dev/null
+++ b/potpourri/rrdp-test-tool
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+# $Id$
+#
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and 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.
+
+"""
+Test tool for prototype RRDP implementation. Eventually some of this
+code will likely be refactored into more user-friendly form, but for
+the moment this just does whatever insane thing I need to do this week
+for testing.
+"""
+
+import rpki.relaxng
+import rpki.x509
+import lxml.etree
+import argparse
+import os
+
+class Tags(object):
+ def __init__(self, *tags):
+ for tag in tags:
+ setattr(self, tag, rpki.relaxng.rrdp.xmlns + tag)
+
+tags = Tags("notification", "deltas", "delta", "snapshot", "publish", "withdraw")
+
+class main(object):
+
+ def __init__(self):
+ parser = argparse.ArgumentParser(description = __doc__)
+ parser.add_argument("--rcynic-tree", default = "rcynic-data/unauthenticated",
+ help = "directory tree in which to write extracted RPKI objects")
+ parser.add_argument("--serial-filename",
+ help = "file name in which to store RRDP serial number")
+ parser.add_argument("rrdp_file", nargs = "+",
+ help = "RRDP snapshot or deltas file")
+ self.args = parser.parse_args()
+ if not os.path.isdir(self.args.rcynic_tree):
+ os.makedirs(self.args.rcynic_tree)
+ for rrdp_file in self.args.rrdp_file:
+ xml = lxml.etree.ElementTree(file = rrdp_file).getroot()
+ rpki.relaxng.rrdp.assertValid(xml)
+ getattr(self, xml.tag[len(rpki.relaxng.rrdp.xmlns):])(xml)
+
+ @property
+ def serial_filename(self):
+ return self.args.serial_filename or os.path.join(self.args.rcynic_tree, "serial")
+
+ def get_serial(self):
+ with open(self.serial_filename, "r") as f:
+ return f.read().strip()
+
+ def set_serial(self, value):
+ with open(self.serial_filename, "w") as f:
+ f.write("%s\n" % value)
+
+ def notification(self, xml):
+ print "Notification version %s session %s serial %s" % (
+ xml.get("version"), xml.get("session_id"), xml.get("serial"))
+ assert xml[0].tag == tags.snapshot
+ print " Snapshot URI %s hash %s" % (
+ xml[0].get("uri"), xml[0].get("hash"))
+ for i, elt in enumerate(xml.iterchildren(tags.delta)):
+ print " Delta %3d from %6s to %6s URI %s hash %s" % (
+ i, elt.get("from"), elt.get("to"), elt.get("uri"), elt.get("hash"))
+
+ def uri_to_filename(self, uri):
+ assert uri.startswith("rsync://")
+ return os.path.join(self.args.rcynic_tree, uri[len("rsync://"):])
+
+ def add_obj(self, uri, obj):
+ fn = self.uri_to_filename(uri)
+ dn = os.path.dirname(fn)
+ if not os.path.isdir(dn):
+ os.makedirs(dn)
+ with open(fn, "wb") as f:
+ f.write(obj)
+
+ def del_obj(self, uri, hash):
+ fn = self.uri_to_filename(uri)
+ with open(fn, "rb") as f:
+ if hash != rpki.x509.sha256(f.read()).encode("hex"):
+ raise RuntimeError("Hash mismatch for URI %s" % uri)
+ os.unlink(fn)
+ dn = os.path.dirname(fn)
+ while True:
+ try:
+ os.rmdir(dn)
+ except OSError:
+ break
+ else:
+ dn = os.path.dirname(dn)
+
+ def snapshot(self, xml):
+ print "Unpacking snapshot version %s session %s serial %6s" % (
+ xml.get("version"), xml.get("session_id"), xml.get("serial"))
+ for elt in xml.iterchildren(tags.publish):
+ print " ", elt.get("uri")
+ self.add_obj(elt.get("uri"), elt.text.decode("base64"))
+ self.set_serial(xml.get("serial"))
+
+ def deltas(self, xml):
+ cur = int(self.get_serial())
+ old = int(xml.get("from"))
+ new = int(xml.get("to"))
+ print "Unpacking deltas version %s session %s from %s to %s" % (
+ xml.get("version"), xml.get("session_id"), old, new)
+ if cur != old:
+ raise RuntimeError("Can't apply deltas: current %s old %s new %s" % (cur, old, new))
+ for i, delta in enumerate(xml.iterchildren(tags.delta)):
+ serial = int(delta.get("serial"))
+ print " Delta %3d serial %d" % (i, serial)
+ if cur != serial - 1:
+ raise RuntimeError("Can't apply delta: current %s delta serial %s" % (cur, serial))
+ for j, elt in enumerate(delta.iterchildren(tags.withdraw)):
+ uri = elt.get("uri")
+ hash = elt.get("hash")
+ print " %3d withdraw URI %s hash %s" % (j, uri, hash)
+ self.del_obj(uri, hash)
+ for j, elt in enumerate(delta.iterchildren(tags.publish)):
+ uri = elt.get("uri")
+ hash = elt.get("hash", None)
+ print " %3d publish URI %s hash %s" % (j, uri, hash)
+ if hash is not None:
+ self.del_obj(uri, hash)
+ self.add_obj(elt.get("uri"), elt.text.decode("base64"))
+ cur += 1
+ self.set_serial(cur)
+
+if __name__ == "__main__":
+ main()
diff --git a/rpki/adns.py b/rpki/adns.py
index 968684b5..018bb7cf 100644
--- a/rpki/adns.py
+++ b/rpki/adns.py
@@ -88,6 +88,7 @@ class dispatcher(asyncore.dispatcher):
"""
Receive a packet, hand it off to query class callback.
"""
+
wire, from_address = self.recvfrom(self.bufsize)
self.cb(self.af, from_address[0], from_address[1], wire)
@@ -95,18 +96,21 @@ class dispatcher(asyncore.dispatcher):
"""
Pass errors to query class errback.
"""
+
self.eb(sys.exc_info()[1])
def handle_connect(self):
"""
Quietly ignore UDP "connection" events.
"""
+
pass
def writable(self):
"""
We don't need to hear about UDP socket becoming writable.
"""
+
return False
@@ -138,6 +142,7 @@ class query(object):
query; if we find an answer there, just return it. Otherwise
start the network query.
"""
+
if resolver.cache:
answer = resolver.cache.get((self.qname, self.qtype, self.qclass))
else:
@@ -161,6 +166,7 @@ class query(object):
Outer loop. If we haven't got a response yet and still have
nameservers to check, start inner loop. Otherwise, we're done.
"""
+
self.timer.cancel()
if self.response is None and self.nameservers:
self.iterator = rpki.async.iterator(self.nameservers[:], self.loop2, self.done2)
@@ -172,6 +178,7 @@ class query(object):
Inner loop. Send query to next nameserver in our list, unless
we've hit the overall timeout for this query.
"""
+
self.timer.cancel()
try:
timeout = resolver._compute_timeout(self.start)
@@ -191,6 +198,7 @@ class query(object):
"""
No answer from nameserver, move on to next one (inner loop).
"""
+
self.response = None
self.iterator()
@@ -200,6 +208,7 @@ class query(object):
error, handle as if we've timed out on this nameserver; otherwise,
pass error back to caller.
"""
+
self.timer.cancel()
if isinstance(e, socket.error):
self.response = None
@@ -215,6 +224,7 @@ class query(object):
we're done, otherwise handle error appropriately and move on to
next nameserver.
"""
+
sender = (af, dns.inet.inet_pton(af, from_host))
if from_port != resolver.port or sender not in self.nameservers:
return
@@ -240,6 +250,7 @@ class query(object):
while before starting the cycle again, unless we've hit the
timeout threshold for the whole query.
"""
+
if self.response is None and self.nameservers:
try:
delay = rpki.sundial.timedelta(seconds = min(resolver._compute_timeout(self.start), self.backoff))
@@ -256,6 +267,7 @@ class query(object):
"""
Shut down our timer and sockets.
"""
+
self.timer.cancel()
for s in self.sockets.itervalues():
s.close()
@@ -264,6 +276,7 @@ class query(object):
"""
Something bad happened. Clean up, then pass error back to caller.
"""
+
self.cleanup()
self.eb(self, e)
@@ -273,6 +286,7 @@ class query(object):
pass it back to caller; if we got an error, pass the appropriate
exception back to caller.
"""
+
self.cleanup()
try:
if not self.nameservers:
diff --git a/rpki/async.py b/rpki/async.py
index b17c31ed..f2abd05d 100644
--- a/rpki/async.py
+++ b/rpki/async.py
@@ -131,6 +131,7 @@ class timer(object):
"""
Debug logging.
"""
+
if self.gc_debug:
bt = traceback.extract_stack(limit = 3)
logger.debug("%s from %s:%d", msg, bt[0][0], bt[0][1])
@@ -140,6 +141,7 @@ class timer(object):
Set a timer. Argument can be a datetime, to specify an absolute
time, or a timedelta, to specify an offset time.
"""
+
if self.gc_debug:
self.trace("Setting %r to %r" % (self, when))
if isinstance(when, rpki.sundial.timedelta):
@@ -162,6 +164,7 @@ class timer(object):
"""
Cancel a timer, if it was set.
"""
+
if self.gc_debug:
self.trace("Canceling %r" % self)
try:
@@ -174,6 +177,7 @@ class timer(object):
"""
Test whether this timer is currently set.
"""
+
return self in timer_queue
def set_handler(self, handler):
@@ -184,12 +188,14 @@ class timer(object):
bound method to an object in a class representing a network
connection).
"""
+
self.handler = handler
def set_errback(self, errback):
"""
Set a timer's errback. Like set_handler(), for errbacks.
"""
+
self.errback = errback
@classmethod
@@ -202,6 +208,7 @@ class timer(object):
called, so that even if new events keep getting scheduled, we'll
return to the I/O loop reasonably quickly.
"""
+
now = rpki.sundial.now()
while timer_queue and now >= timer_queue[0].when:
t = timer_queue.pop(0)
@@ -233,6 +240,7 @@ class timer(object):
the same units (argh!), and we're not doing anything that
hair-triggered, so rounding up is simplest.
"""
+
if not timer_queue:
return None
now = rpki.sundial.now()
@@ -251,6 +259,7 @@ class timer(object):
queue content, but this way we can notify subclasses that provide
their own cancel() method.
"""
+
while timer_queue:
timer_queue.pop(0).cancel()
@@ -258,12 +267,14 @@ def _raiseExitNow(signum, frame):
"""
Signal handler for event_loop().
"""
+
raise ExitNow
def exit_event_loop():
"""
Force exit from event_loop().
"""
+
raise ExitNow
def event_defer(handler, delay = rpki.sundial.timedelta(seconds = 0)):
@@ -271,6 +282,7 @@ def event_defer(handler, delay = rpki.sundial.timedelta(seconds = 0)):
Use a near-term (default: zero interval) timer to schedule an event
to run after letting the I/O system have a turn.
"""
+
timer(handler).set(delay)
## @var debug_event_timing
@@ -282,6 +294,7 @@ def event_loop(catch_signals = (signal.SIGINT, signal.SIGTERM)):
"""
Replacement for asyncore.loop(), adding timer and signal support.
"""
+
old_signal_handlers = {}
while True:
save_sigs = len(old_signal_handlers) == 0
@@ -347,6 +360,7 @@ class sync_wrapper(object):
Wrapped code has requested normal termination. Store result, and
exit the event loop.
"""
+
self.res = res
self.fin = True
logger.debug("%r callback with result %r", self, self.res)
@@ -357,6 +371,7 @@ class sync_wrapper(object):
Wrapped code raised an exception. Store exception data, then exit
the event loop.
"""
+
exc_info = sys.exc_info()
self.err = exc_info if exc_info[1] is err else err
self.fin = True
@@ -401,6 +416,7 @@ class gc_summary(object):
"""
Collect and log GC state for this period, reset timer.
"""
+
logger.debug("gc_summary: Running gc.collect()")
gc.collect()
logger.debug("gc_summary: Summarizing (threshold %d)", self.threshold)
diff --git a/rpki/csv_utils.py b/rpki/csv_utils.py
index 9ba04a02..9034e96b 100644
--- a/rpki/csv_utils.py
+++ b/rpki/csv_utils.py
@@ -99,6 +99,7 @@ class csv_writer(object):
"""
Close this writer.
"""
+
if self.file is not None:
self.file.close()
self.file = None
@@ -109,4 +110,5 @@ class csv_writer(object):
"""
Fake inheritance from whatever object csv.writer deigns to give us.
"""
+
return getattr(self.writer, attr)
diff --git a/rpki/exceptions.py b/rpki/exceptions.py
index 504c6f28..86c7fa27 100644
--- a/rpki/exceptions.py
+++ b/rpki/exceptions.py
@@ -288,6 +288,16 @@ class NoObjectAtURI(RPKI_Exception):
No object published at specified URI.
"""
+class ExistingObjectAtURI(RPKI_Exception):
+ """
+ An object has already been published at specified URI.
+ """
+
+class DifferentObjectAtURI(RPKI_Exception):
+ """
+ An object with a different hash exists at specified URI.
+ """
+
class CMSContentNotSet(RPKI_Exception):
"""
Inner content of a CMS_object has not been set. If object is known
diff --git a/rpki/gui/app/check_expired.py b/rpki/gui/app/check_expired.py
index a084af79..2907f071 100644
--- a/rpki/gui/app/check_expired.py
+++ b/rpki/gui/app/check_expired.py
@@ -41,8 +41,8 @@ def check_cert(handle, p, errs):
The displayed object name defaults to the class name, but can be overridden
using the `object_name` argument.
-
"""
+
t = p.certificate.getNotAfter()
if t <= expire_time:
e = 'expired' if t <= now else 'will expire'
@@ -102,8 +102,8 @@ def check_expire(conf, errs):
def check_child_certs(conf, errs):
"""Fetch the list of published objects from rpkid, and inspect the issued
resource certs (uri ending in .cer).
-
"""
+
z = Zookeeper(handle=conf.handle)
req = list_published_objects_elt.make_pdu(action="list",
tag="list_published_objects",
@@ -139,8 +139,8 @@ def notify_expired(expire_days=14, from_email=None):
expire_days: the number of days ahead of today to warn
from_email: set the From: address for the email
-
"""
+
global expire_time # so i don't have to pass it around
global now
diff --git a/rpki/gui/app/forms.py b/rpki/gui/app/forms.py
index 5394a804..02561303 100644
--- a/rpki/gui/app/forms.py
+++ b/rpki/gui/app/forms.py
@@ -52,6 +52,7 @@ class GhostbusterRequestForm(forms.ModelForm):
Generate a ModelForm with the subset of parents for the current
resource handle.
"""
+
# override default form field
parent = forms.ModelChoiceField(queryset=None, required=False,
help_text='Specify specific parent, or none for all parents')
@@ -86,6 +87,7 @@ class GhostbusterRequestForm(forms.ModelForm):
class ImportForm(forms.Form):
"""Form used for uploading parent/child identity xml files."""
+
handle = forms.CharField(required=False,
widget=forms.TextInput(attrs={'class': 'xlarge'}),
help_text='Optional. Your name for this entity, or blank to accept name in XML')
@@ -101,6 +103,7 @@ class ImportRepositoryForm(forms.Form):
class ImportClientForm(forms.Form):
"""Form used for importing publication client requests."""
+
xml = forms.FileField(label='XML file')
@@ -137,6 +140,7 @@ class UserCreateForm(forms.Form):
class UserEditForm(forms.Form):
"""Form for editing a user."""
+
email = forms.CharField()
pw = forms.CharField(widget=forms.PasswordInput, label='Password',
required=False)
@@ -185,8 +189,8 @@ class ROARequest(forms.Form):
"""Takes an optional `conf` keyword argument specifying the user that
is creating the ROAs. It is used for validating that the prefix the
user entered is currently allocated to that user.
-
"""
+
conf = kwargs.pop('conf', None)
kwargs['auto_id'] = False
super(ROARequest, self).__init__(*args, **kwargs)
@@ -199,8 +203,8 @@ class ROARequest(forms.Form):
rpki.resource_set.resource_range_ip object.
If there is no mask provided, assume the closest classful mask.
-
"""
+
prefix = self.cleaned_data.get('prefix')
if '/' not in prefix:
p = IPAddress(prefix)
@@ -296,7 +300,6 @@ class AddASNForm(forms.Form):
Returns a forms.Form subclass which verifies that the entered ASN range
does not overlap with a previous allocation to the specified child, and
that the ASN range is within the range allocated to the parent.
-
"""
asns = forms.CharField(
@@ -335,8 +338,8 @@ class AddNetForm(forms.Form):
Returns a forms.Form subclass which validates that the entered address
range is within the resources allocated to the parent, and does not overlap
with what is already allocated to the specified child.
-
"""
+
address_range = forms.CharField(
help_text='CIDR or range',
widget=forms.TextInput(attrs={'autofocus': 'true'})
@@ -383,7 +386,6 @@ def ChildForm(instance):
This is roughly based on the equivalent ModelForm, but uses Form as a base
class so that selection boxes for the AS and Prefixes can be edited in a
single form.
-
"""
class _wrapped(forms.Form):
@@ -401,11 +403,13 @@ def ChildForm(instance):
class Empty(forms.Form):
"""Stub form for views requiring confirmation."""
+
pass
class ResourceHolderForm(forms.Form):
"""form for editing ACL on Conf objects."""
+
users = forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
help_text='users allowed to mange this resource holder'
@@ -413,7 +417,8 @@ class ResourceHolderForm(forms.Form):
class ResourceHolderCreateForm(forms.Form):
- """form for creating new resource holdres."""
+ """form for creating new resource holders."""
+
handle = forms.CharField(max_length=30)
parent = forms.ModelChoiceField(
required=False,
diff --git a/rpki/gui/app/glue.py b/rpki/gui/app/glue.py
index 0bf5f942..f17ba5ac 100644
--- a/rpki/gui/app/glue.py
+++ b/rpki/gui/app/glue.py
@@ -16,7 +16,6 @@
"""
This file contains code that interfaces between the django views implementing
the portal gui and the rpki.* modules.
-
"""
from __future__ import with_statement
@@ -39,6 +38,7 @@ from django.db.transaction import commit_on_success
def ghostbuster_to_vcard(gbr):
"""Convert a GhostbusterRequest object into a vCard object."""
+
import vobject
vcard = vobject.vCard()
@@ -86,7 +86,6 @@ def list_received_resources(log, conf):
The semantics are to clear the entire table and populate with the list of
certs received. Other models should not reference the table directly with
foreign keys.
-
"""
z = Zookeeper(handle=conf.handle)
diff --git a/rpki/gui/app/models.py b/rpki/gui/app/models.py
index 32a897c7..d6332796 100644
--- a/rpki/gui/app/models.py
+++ b/rpki/gui/app/models.py
@@ -120,16 +120,16 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
def parents(self):
"""Simulates irdb.models.Parent.objects, but returns app.models.Parent
proxy objects.
-
"""
+
return Parent.objects.filter(issuer=self)
@property
def children(self):
"""Simulates irdb.models.Child.objects, but returns app.models.Child
proxy objects.
-
"""
+
return Child.objects.filter(issuer=self)
@property
@@ -148,8 +148,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
def routes(self):
"""Return all IPv4 routes covered by RPKI certs issued to this resource
holder.
-
"""
+
# build a Q filter to select all RouteOrigin objects covered by
# prefixes in the resource holder's certificates
q = models.Q()
@@ -162,8 +162,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
def routes_v6(self):
"""Return all IPv6 routes covered by RPKI certs issued to this resource
holder.
-
"""
+
# build a Q filter to select all RouteOrigin objects covered by
# prefixes in the resource holder's certificates
q = models.Q()
@@ -174,6 +174,7 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
def send_alert(self, subject, message, from_email, severity=Alert.INFO):
"""Store an alert for this resource holder."""
+
self.alerts.create(subject=subject, text=message, severity=severity)
send_mail(
@@ -189,8 +190,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
Contact emails are extract from any ghostbuster requests, and any
linked user accounts.
-
"""
+
notify_emails = [gbr.email_address for gbr in self.ghostbusters if gbr.email_address]
notify_emails.extend(
[acl.user.email for acl in ConfACL.objects.filter(conf=self) if acl.user.email]
@@ -209,7 +210,6 @@ class ResourceCert(models.Model):
"""Represents a resource certificate.
This model is used to cache the output of <list_received_resources/>.
-
"""
# Handle to which this cert was issued
@@ -237,6 +237,7 @@ class ResourceCert(models.Model):
def get_cert_chain(self):
"""Return a list containing the complete certificate chain for this
certificate."""
+
cert = self
x = [cert]
while cert.issuer:
@@ -410,7 +411,6 @@ class RouteOriginV6(rpki.gui.routeview.models.RouteOriginV6):
class ConfACL(models.Model):
"""Stores access control for which users are allowed to manage a given
resource handle.
-
"""
conf = models.ForeignKey(Conf)
diff --git a/rpki/gui/app/range_list.py b/rpki/gui/app/range_list.py
index 21fd1f29..5cb4f5e4 100755
--- a/rpki/gui/app/range_list.py
+++ b/rpki/gui/app/range_list.py
@@ -70,6 +70,7 @@ class RangeList(list):
def difference(self, other):
"""Return a RangeList object which contains ranges in this object which
are not in "other"."""
+
it = iter(other)
try:
@@ -85,6 +86,7 @@ class RangeList(list):
def V(v):
"""convert the integer value to the appropriate type for this
range"""
+
return x.__class__.datum_type(v)
try:
diff --git a/rpki/gui/app/views.py b/rpki/gui/app/views.py
index 9a1c4cfe..228d5c6c 100644
--- a/rpki/gui/app/views.py
+++ b/rpki/gui/app/views.py
@@ -71,6 +71,7 @@ def superuser_required(f):
def get_conf(user, handle):
"""return the Conf object for 'handle'.
user is a request.user object to use enforce ACLs."""
+
if user.is_superuser:
qs = models.Conf.objects.all()
else:
@@ -81,8 +82,8 @@ def get_conf(user, handle):
def handle_required(f):
"""Decorator for view functions which require the user to be logged in and
a resource handle selected for the session.
-
"""
+
@login_required
@tls_required
def wrapped_fn(request, *args, **kwargs):
@@ -126,8 +127,8 @@ def generic_import(request, queryset, configure, form_class=None,
if None (default), the user will be redirected to the detail page for
the imported object. Otherwise, the user will be redirected to the
specified URL.
-
"""
+
conf = get_conf(request.user, request.session['handle'])
if form_class is None:
form_class = forms.ImportForm
@@ -251,6 +252,7 @@ def dashboard(request):
@login_required
def conf_list(request, **kwargs):
"""Allow the user to select a handle."""
+
log = request.META['wsgi.errors']
next_url = request.GET.get('next', reverse(dashboard))
if request.user.is_superuser:
@@ -266,6 +268,7 @@ def conf_list(request, **kwargs):
@login_required
def conf_select(request):
"""Change the handle for the current session."""
+
if not 'handle' in request.GET:
return redirect(conf_list)
handle = request.GET['handle']
@@ -288,8 +291,8 @@ def serve_xml(content, basename, ext='xml'):
`basename` is the prefix to specify for the XML filename.
`csv` is the type (default: xml)
-
"""
+
resp = http.HttpResponse(content, mimetype='application/%s' % ext)
resp['Content-Disposition'] = 'attachment; filename=%s.%s' % (basename, ext)
return resp
@@ -298,6 +301,7 @@ def serve_xml(content, basename, ext='xml'):
@handle_required
def conf_export(request):
"""Return the identity.xml for the current handle."""
+
conf = get_conf(request.user, request.session['handle'])
z = Zookeeper(handle=conf.handle)
xml = z.generate_identity()
@@ -307,6 +311,7 @@ def conf_export(request):
@handle_required
def export_asns(request):
"""Export CSV file containing ASN allocations to children."""
+
conf = get_conf(request.user, request.session['handle'])
s = cStringIO.StringIO()
csv_writer = csv.writer(s, delimiter=' ')
@@ -342,6 +347,7 @@ def import_asns(request):
@handle_required
def export_prefixes(request):
"""Export CSV file containing ASN allocations to children."""
+
conf = get_conf(request.user, request.session['handle'])
s = cStringIO.StringIO()
csv_writer = csv.writer(s, delimiter=' ')
@@ -411,6 +417,7 @@ def parent_delete(request, pk):
@handle_required
def parent_export(request, pk):
"""Export XML repository request for a given parent."""
+
conf = get_conf(request.user, request.session['handle'])
parent = get_object_or_404(conf.parents, pk=pk)
z = Zookeeper(handle=conf.handle)
@@ -474,6 +481,7 @@ def child_detail(request, pk):
@handle_required
def child_edit(request, pk):
"""Edit the end validity date for a resource handle's child."""
+
log = request.META['wsgi.errors']
conf = get_conf(request.user, request.session['handle'])
child = get_object_or_404(conf.children.all(), pk=pk)
@@ -505,8 +513,8 @@ def child_response(request, pk):
"""
Export the XML file containing the output of the configure_child
to send back to the client.
-
"""
+
conf = get_conf(request.user, request.session['handle'])
child = get_object_or_404(models.Child, issuer=conf, pk=pk)
z = Zookeeper(handle=conf.handle)
@@ -551,7 +559,6 @@ def get_covered_routes(rng, max_prefixlen, asn):
A "newstatus" attribute is monkey-patched on the RouteOrigin objects which
can be used in the template. "status" remains the current validation
status of the object.
-
"""
# find all routes that match or are completed covered by the proposed new roa
@@ -591,7 +598,6 @@ def roa_create(request):
Doesn't use the generic create_object() form because we need to
create both the ROARequest and ROARequestPrefix objects.
-
"""
conf = get_conf(request.user, request.session['handle'])
@@ -628,7 +634,6 @@ def roa_create(request):
class ROARequestFormSet(BaseFormSet):
"""There is no way to pass arbitrary keyword arguments to the form
constructor, so we have to override BaseFormSet to allow it.
-
"""
def __init__(self, *args, **kwargs):
self.conf = kwargs.pop('conf')
@@ -666,7 +671,6 @@ def roa_create_multi(request):
?roa=1.1.1.1-2.2.2.2,42
The ASN may optionally be omitted.
-
"""
conf = get_conf(request.user, request.session['handle'])
@@ -717,8 +721,8 @@ def roa_create_multi(request):
def roa_create_confirm(request):
"""This function is called when the user confirms the creation of a ROA
request. It is responsible for updating the IRDB.
-
"""
+
conf = get_conf(request.user, request.session['handle'])
log = request.META['wsgi.errors']
if request.method == 'POST':
@@ -746,8 +750,8 @@ def roa_create_confirm(request):
def roa_create_multi_confirm(request):
"""This function is called when the user confirms the creation of a ROA
request. It is responsible for updating the IRDB.
-
"""
+
conf = get_conf(request.user, request.session['handle'])
log = request.META['wsgi.errors']
if request.method == 'POST':
@@ -778,7 +782,6 @@ def roa_delete(request, pk):
Uses a form for double confirmation, displaying how the route
validation status may change as a result.
-
"""
conf = get_conf(request.user, request.session['handle'])
@@ -835,6 +838,7 @@ def roa_clone(request, pk):
@handle_required
def roa_import(request):
"""Import CSV containing ROA declarations."""
+
if request.method == 'POST':
form = forms.ImportCSVForm(request.POST, request.FILES)
if form.is_valid():
@@ -860,6 +864,7 @@ def roa_import(request):
@handle_required
def roa_export(request):
"""Export CSV containing ROA declarations."""
+
# FIXME: remove when Zookeeper can do this
f = cStringIO.StringIO()
csv_writer = csv.writer(f, delimiter=' ')
@@ -941,8 +946,8 @@ def ghostbuster_edit(request, pk):
def refresh(request):
"""
Query rpkid, update the db, and redirect back to the dashboard.
-
"""
+
conf = get_conf(request.user, request.session['handle'])
glue.list_received_resources(request.META['wsgi.errors'], conf)
return http.HttpResponseRedirect(reverse(dashboard))
@@ -953,8 +958,8 @@ def route_view(request):
"""
Display a list of global routing table entries which match resources
listed in received certificates.
-
"""
+
conf = get_conf(request.user, request.session['handle'])
count = request.GET.get('count', 25)
page = request.GET.get('page', 1)
@@ -972,6 +977,7 @@ def route_view(request):
def route_detail(request, pk):
"""Show a list of ROAs that match a given IPv4 route."""
+
route = get_object_or_404(models.RouteOrigin, pk=pk)
# when running rootd, viewing the 0.0.0.0/0 route will cause a fetch of all
# roas, so we paginate here, even though in the general case the number of
@@ -989,8 +995,8 @@ def route_suggest(request):
"""Handles POSTs from the route view and redirects to the ROA creation
page based on selected route objects. The form should contain elements of
the form "pk-NUM" where NUM is the RouteOrigin object id.
-
"""
+
if request.method == 'POST':
routes = []
for pk in request.POST.iterkeys():
@@ -1040,6 +1046,7 @@ def repository_delete(request, pk):
@handle_required
def repository_import(request):
"""Import XML response file from repository operator."""
+
return generic_import(request,
models.Repository.objects,
Zookeeper.configure_repository,
@@ -1094,8 +1101,8 @@ def client_import(request):
def client_export(request, pk):
"""Return the XML file resulting from a configure_publication_client
request.
-
"""
+
client = get_object_or_404(models.Client, pk=pk)
z = Zookeeper()
xml = z.generate_repository_response(client)
@@ -1107,6 +1114,7 @@ def client_export(request, pk):
@superuser_required
def resource_holder_list(request):
"""Display a list of all the RPKI handles managed by this server."""
+
return render(request, 'app/resource_holder_list.html', {
'object_list': models.Conf.objects.all()
})
@@ -1115,6 +1123,7 @@ def resource_holder_list(request):
@superuser_required
def resource_holder_edit(request, pk):
"""Display a list of all the RPKI handles managed by this server."""
+
conf = get_object_or_404(models.Conf, pk=pk)
if request.method == 'POST':
form = forms.ResourceHolderForm(request.POST, request.FILES)
@@ -1221,6 +1230,7 @@ def user_create(request):
@superuser_required
def user_list(request):
"""Display a list of all the RPKI handles managed by this server."""
+
return render(request, 'app/user_list.html', {
'object_list': User.objects.all()
})
@@ -1316,6 +1326,7 @@ class AlertDeleteView(DeleteView):
@handle_required
def alert_clear_all(request):
"""Clear all alerts associated with the current resource holder."""
+
if request.method == 'POST':
form = forms.Empty(request.POST, request.FILES)
if form.is_valid():
diff --git a/rpki/gui/cacheview/models.py b/rpki/gui/cacheview/models.py
index c3ee8421..08acfa2d 100644
--- a/rpki/gui/cacheview/models.py
+++ b/rpki/gui/cacheview/models.py
@@ -58,6 +58,7 @@ class ValidationLabel(models.Model):
Represents a specific error condition defined in the rcynic XML
output file.
"""
+
label = models.CharField(max_length=79, db_index=True, unique=True)
status = models.CharField(max_length=255)
kind = models.PositiveSmallIntegerField(choices=kinds)
@@ -70,6 +71,7 @@ class RepositoryObject(models.Model):
"""
Represents a globally unique RPKI repository object, specified by its URI.
"""
+
uri = models.URLField(unique=True, db_index=True)
generations = list(enumerate(('current', 'backup')))
@@ -89,6 +91,7 @@ class SignedObject(models.Model):
The signing certificate is ommitted here in order to give a proper
value for the 'related_name' attribute.
"""
+
repo = models.ForeignKey(RepositoryObject, related_name='cert', unique=True)
# on-disk file modification time
@@ -108,6 +111,7 @@ class SignedObject(models.Model):
"""
convert the local timestamp to UTC and convert to a datetime object
"""
+
return datetime.utcfromtimestamp(self.mtime + time.timezone)
def status_id(self):
@@ -116,6 +120,7 @@ class SignedObject(models.Model):
The selector is chosen based on the current generation only. If there is any bad status,
return bad, else if there are any warn status, return warn, else return good.
"""
+
for x in reversed(kinds):
if self.repo.statuses.filter(generation=generations_dict['current'], status__kind=x[0]):
return x[1]
@@ -129,6 +134,7 @@ class Cert(SignedObject):
"""
Object representing a resource certificate.
"""
+
addresses = models.ManyToManyField(AddressRange, related_name='certs')
addresses_v6 = models.ManyToManyField(AddressRangeV6, related_name='certs')
asns = models.ManyToManyField(ASRange, related_name='certs')
@@ -141,6 +147,7 @@ class Cert(SignedObject):
def get_cert_chain(self):
"""Return a list containing the complete certificate chain for this
certificate."""
+
cert = self
x = [cert]
while cert != cert.issuer:
@@ -180,6 +187,7 @@ class ROAPrefixV4(ROAPrefix, rpki.gui.models.PrefixV4):
@property
def routes(self):
"""return all routes covered by this roa prefix"""
+
return RouteOrigin.objects.filter(prefix_min__gte=self.prefix_min,
prefix_max__lte=self.prefix_max)
diff --git a/rpki/gui/cacheview/tests.py b/rpki/gui/cacheview/tests.py
index 2247054b..daca07bf 100644
--- a/rpki/gui/cacheview/tests.py
+++ b/rpki/gui/cacheview/tests.py
@@ -12,6 +12,7 @@ class SimpleTest(TestCase):
"""
Tests that 1 + 1 always equals 2.
"""
+
self.failUnlessEqual(1 + 1, 2)
__test__ = {"doctest": """
diff --git a/rpki/gui/cacheview/util.py b/rpki/gui/cacheview/util.py
index 9e8748bf..31ad8b8b 100644
--- a/rpki/gui/cacheview/util.py
+++ b/rpki/gui/cacheview/util.py
@@ -310,8 +310,8 @@ def fetch_published_objects():
"""Query rpkid for all objects published by local users, and look up the
current validation status of each object. The validation status is used
later to send alerts for objects which have transitioned to invalid.
-
"""
+
logger.info('querying for published objects')
handles = [conf.handle for conf in Conf.objects.all()]
@@ -353,7 +353,6 @@ class Handle(object):
def notify_invalid():
"""Send email alerts to the addresses registered in ghostbuster records for
any invalid objects that were published by users of this system.
-
"""
logger.info('sending notifications for invalid objects')
diff --git a/rpki/gui/cacheview/views.py b/rpki/gui/cacheview/views.py
index 94870eb2..451c0d1e 100644
--- a/rpki/gui/cacheview/views.py
+++ b/rpki/gui/cacheview/views.py
@@ -29,6 +29,7 @@ def cert_chain(obj):
"""
returns an iterator covering all certs from the root cert down to the EE.
"""
+
chain = [obj]
while obj != obj.issuer:
obj = obj.issuer
diff --git a/rpki/gui/decorators.py b/rpki/gui/decorators.py
index 75efeae0..d197acff 100644
--- a/rpki/gui/decorators.py
+++ b/rpki/gui/decorators.py
@@ -28,6 +28,7 @@ def tls_required(f):
Decorator which returns a 500 error if the connection is not
secured with TLS (https).
"""
+ """
def _tls_required(request, *args, **kwargs):
if not request.is_secure() and not _allow_plain_http_for_testing:
diff --git a/rpki/gui/models.py b/rpki/gui/models.py
index 184383c0..62400d2a 100644
--- a/rpki/gui/models.py
+++ b/rpki/gui/models.py
@@ -42,8 +42,8 @@ class IPv6AddressField(models.Field):
"""
Note that we add a custom conversion to encode long values as hex
strings in SQL statements. See settings.get_conv() for details.
-
"""
+
return value.toBytes()
@@ -82,6 +82,7 @@ class Prefix(models.Model):
"""
Returns the prefix as a rpki.resource_set.resource_range_ip object.
"""
+
return self.range_cls(self.prefix_min, self.prefix_max)
@property
@@ -96,6 +97,7 @@ class Prefix(models.Model):
def __unicode__(self):
"""This method may be overridden by subclasses. The default
implementation calls get_prefix_display(). """
+
return self.get_prefix_display()
class Meta:
diff --git a/rpki/gui/routeview/api.py b/rpki/gui/routeview/api.py
index cf699c9a..b4ff297a 100644
--- a/rpki/gui/routeview/api.py
+++ b/rpki/gui/routeview/api.py
@@ -29,8 +29,8 @@ def route_list(request):
By default, only returns up to 10 matching routes, but the client may
request a different limit with the 'count=' query string parameter.
-
"""
+
hard_limit = 100
if request.method == 'GET' and 'prefix__in' in request.GET:
diff --git a/rpki/gui/routeview/util.py b/rpki/gui/routeview/util.py
index 54d50f24..a2b515c8 100644
--- a/rpki/gui/routeview/util.py
+++ b/rpki/gui/routeview/util.py
@@ -179,8 +179,8 @@ def import_routeviews_dump(filename=DEFAULT_URL, filetype='auto'):
filename [optional]: the full path to the downloaded file to parse
filetype [optional]: 'text' or 'mrt'
-
"""
+
start_time = time.time()
if filename.startswith('http://'):
diff --git a/rpki/http.py b/rpki/http.py
index 546dd310..e41b0080 100644
--- a/rpki/http.py
+++ b/rpki/http.py
@@ -112,6 +112,7 @@ def supported_address_families(enable_ipv6):
IP address families on which servers should listen, and to consider
when selecting addresses for client connections.
"""
+
if enable_ipv6 and have_ipv6:
return (socket.AF_INET, socket.AF_INET6)
else:
@@ -121,6 +122,7 @@ def localhost_addrinfo():
"""
Return pseudo-getaddrinfo results for localhost.
"""
+
result = [(socket.AF_INET, "127.0.0.1")]
if enable_ipv6_clients and have_ipv6:
result.append((socket.AF_INET6, "::1"))
@@ -144,6 +146,7 @@ class http_message(object):
Clean up (some of) the horrible messes that HTTP allows in its
headers.
"""
+
if headers is None:
headers = () if self.headers is None else self.headers.items()
translate_underscore = True
@@ -166,6 +169,7 @@ class http_message(object):
"""
Parse and normalize an incoming HTTP message.
"""
+
self = cls()
headers = headers.split("\r\n")
self.parse_first_line(*headers.pop(0).split(None, 2))
@@ -180,6 +184,7 @@ class http_message(object):
"""
Format an outgoing HTTP message.
"""
+
s = self.format_first_line()
if self.body is not None:
assert isinstance(self.body, str)
@@ -198,6 +203,7 @@ class http_message(object):
"""
Parse HTTP version, raise an exception if we can't.
"""
+
if version[:5] != "HTTP/":
raise rpki.exceptions.HTTPBadVersion("Couldn't parse version %s" % version)
self.version = tuple(int(i) for i in version[5:].split("."))
@@ -207,6 +213,7 @@ class http_message(object):
"""
Figure out whether this HTTP message encourages a persistent connection.
"""
+
c = self.headers.get("Connection")
if self.version == (1, 1):
return c is None or "close" not in c.lower()
@@ -233,6 +240,7 @@ class http_request(http_message):
"""
Parse first line of HTTP request message.
"""
+
self.parse_version(version)
self.cmd = cmd
self.path = path
@@ -242,6 +250,7 @@ class http_request(http_message):
Format first line of HTTP request message, and set up the
User-Agent header.
"""
+
self.headers.setdefault("User-Agent", self.software_name)
return "%s %s HTTP/%d.%d\r\n" % (self.cmd, self.path, self.version[0], self.version[1])
@@ -262,6 +271,7 @@ class http_response(http_message):
"""
Parse first line of HTTP response message.
"""
+
self.parse_version(version)
self.code = int(code)
self.reason = reason
@@ -271,6 +281,7 @@ class http_response(http_message):
Format first line of HTTP response message, and set up Date and
Server headers.
"""
+
self.headers.setdefault("Date", time.strftime("%a, %d %b %Y %T GMT"))
self.headers.setdefault("Server", self.software_name)
return "HTTP/%d.%d %s %s\r\n" % (self.version[0], self.version[1], self.code, self.reason)
@@ -319,6 +330,7 @@ class http_stream(asynchat.async_chat):
"""
(Re)start HTTP message parser, reset timer.
"""
+
assert not self.buffer
self.chunk_handler = None
self.set_terminator("\r\n\r\n")
@@ -330,6 +342,7 @@ class http_stream(asynchat.async_chat):
stream's timeout value if we're doing timeouts, otherwise clear
it.
"""
+
if self.timeout is not None:
self.logger.debug("Setting timeout %s", self.timeout)
self.timer.set(self.timeout)
@@ -341,6 +354,7 @@ class http_stream(asynchat.async_chat):
"""
Buffer incoming data from asynchat.
"""
+
self.buffer.append(data)
self.update_timeout()
@@ -348,6 +362,7 @@ class http_stream(asynchat.async_chat):
"""
Consume data buffered from asynchat.
"""
+
val = "".join(self.buffer)
self.buffer = []
return val
@@ -369,6 +384,7 @@ class http_stream(asynchat.async_chat):
separate mechanisms (chunked, content-length, TCP close) is going
to tell us how to find the end of the message body.
"""
+
self.update_timeout()
if self.chunk_handler:
self.chunk_handler()
@@ -392,6 +408,7 @@ class http_stream(asynchat.async_chat):
stream up to read it; otherwise, this is the last chunk, so start
the process of exiting the chunk decoder.
"""
+
n = int(self.get_buffer().partition(";")[0], 16)
self.logger.debug("Chunk length %s", n)
if n:
@@ -407,6 +424,7 @@ class http_stream(asynchat.async_chat):
body of a chunked message (sic). Save it, and prepare to move on
to the next chunk.
"""
+
self.logger.debug("Chunk body")
self.msg.body += self.buffer
self.buffer = []
@@ -418,6 +436,7 @@ class http_stream(asynchat.async_chat):
Consume the CRLF that terminates a chunk, reinitialize chunk
decoder to be ready for the next chunk.
"""
+
self.logger.debug("Chunk CRLF")
s = self.get_buffer()
assert s == "", "%r: Expected chunk CRLF, got '%s'" % (self, s)
@@ -428,6 +447,7 @@ class http_stream(asynchat.async_chat):
Consume chunk trailer, which should be empty, then (finally!) exit
the chunk decoder and hand complete message off to the application.
"""
+
self.logger.debug("Chunk trailer")
s = self.get_buffer()
assert s == "", "%r: Expected end of chunk trailers, got '%s'" % (self, s)
@@ -438,6 +458,7 @@ class http_stream(asynchat.async_chat):
"""
Hand normal (not chunked) message off to the application.
"""
+
self.msg.body = self.get_buffer()
self.handle_message()
@@ -447,6 +468,7 @@ class http_stream(asynchat.async_chat):
whether it's one we should just pass along, otherwise log a stack
trace and close the stream.
"""
+
self.timer.cancel()
etype = sys.exc_info()[0]
if etype in (SystemExit, rpki.async.ExitNow):
@@ -459,6 +481,7 @@ class http_stream(asynchat.async_chat):
"""
Inactivity timer expired, close connection with prejudice.
"""
+
self.logger.debug("Timeout, closing")
self.close()
@@ -467,6 +490,7 @@ class http_stream(asynchat.async_chat):
Wrapper around asynchat connection close handler, so that we can
log the event, cancel timer, and so forth.
"""
+
self.logger.debug("Close event in HTTP stream handler")
self.timer.cancel()
asynchat.async_chat.handle_close(self)
@@ -497,12 +521,14 @@ class http_server(http_stream):
Content-Length header (that is: this message will be the last one
in this server stream). No special action required.
"""
+
self.handle_message()
def find_handler(self, path):
"""
Helper method to search self.handlers.
"""
+
for s, h in self.handlers:
if path.startswith(s):
return h
@@ -515,6 +541,7 @@ class http_server(http_stream):
Content-Type, look for a handler, and if everything looks right,
pass the message body, path, and a reply callback to the handler.
"""
+
self.logger.debug("Received request %r", self.msg)
if not self.msg.persistent:
self.expect_close = True
@@ -541,12 +568,14 @@ class http_server(http_stream):
"""
Send an error response to this request.
"""
+
self.send_message(code = code, reason = reason)
def send_reply(self, code, body = None, reason = "OK"):
"""
Send a reply to this request.
"""
+
self.send_message(code = code, body = body, reason = reason)
def send_message(self, code, reason = "OK", body = None):
@@ -556,6 +585,7 @@ class http_server(http_stream):
listen for next message; otherwise, queue up a close event for
this stream so it will shut down once the reply has been sent.
"""
+
self.logger.debug("Sending response %s %s", code, reason)
if code >= 400:
self.expect_close = True
@@ -611,6 +641,7 @@ class http_listener(asyncore.dispatcher):
Asyncore says we have an incoming connection, spawn an http_server
stream for it and pass along all of our handler data.
"""
+
try:
res = self.accept()
if res is None:
@@ -627,6 +658,7 @@ class http_listener(asyncore.dispatcher):
"""
Asyncore signaled an error, pass it along or log it.
"""
+
if sys.exc_info()[0] in (SystemExit, rpki.async.ExitNow):
raise
self.logger.exception("Error in HTTP listener")
@@ -662,6 +694,7 @@ class http_client(http_stream):
"""
Create socket and request a connection.
"""
+
if not use_adns:
self.logger.debug("Not using ADNS")
self.gotaddrinfo([(socket.AF_INET, self.host)])
@@ -678,12 +711,14 @@ class http_client(http_stream):
Handle DNS lookup errors. For now, just whack the connection.
Undoubtedly we should do something better with diagnostics here.
"""
+
self.handle_error()
def gotaddrinfo(self, addrinfo):
"""
Got address data from DNS, create socket and request connection.
"""
+
try:
self.af, self.address = random.choice(addrinfo)
self.logger.debug("Connecting to AF %s host %s port %s addr %s", self.af, self.host, self.port, self.address)
@@ -701,6 +736,7 @@ class http_client(http_stream):
"""
Asyncore says socket has connected.
"""
+
self.logger.debug("Socket connected")
self.set_state("idle")
assert self.queue.client is self
@@ -710,6 +746,7 @@ class http_client(http_stream):
"""
Set HTTP client connection state.
"""
+
self.logger.debug("State transition %s => %s", self.state, state)
self.state = state
@@ -720,12 +757,14 @@ class http_client(http_stream):
in this server stream). In this case we want to read until we
reach the end of the data stream.
"""
+
self.set_terminator(None)
def send_request(self, msg):
"""
Queue up request message and kickstart connection.
"""
+
self.logger.debug("Sending request %r", msg)
assert self.state == "idle", "%r: state should be idle, is %s" % (self, self.state)
self.set_state("request-sent")
@@ -782,6 +821,7 @@ class http_client(http_stream):
message now; if we were waiting for the response to a request we
sent, signal the error.
"""
+
http_stream.handle_close(self)
self.logger.debug("State %s", self.state)
if self.get_terminator() is None:
@@ -796,6 +836,7 @@ class http_client(http_stream):
Connection idle timer has expired. Shut down connection in any
case, noisily if we weren't idle.
"""
+
bad = self.state not in ("idle", "closing")
if bad:
self.logger.warning("Timeout while in state %s", self.state)
@@ -813,6 +854,7 @@ class http_client(http_stream):
Asyncore says something threw an exception. Log it, then shut
down the connection and pass back the exception.
"""
+
eclass, edata = sys.exc_info()[0:2]
self.logger.warning("Error on HTTP client connection %s:%s %s %s", self.host, self.port, eclass, edata)
http_stream.handle_error(self)
@@ -840,6 +882,7 @@ class http_queue(object):
"""
Append http_request object(s) to this queue.
"""
+
self.logger.debug("Adding requests %r", requests)
self.queue.extend(requests)
@@ -852,6 +895,7 @@ class http_queue(object):
exception, or timeout) for the query currently in progress will
call this method when it's time to kick out the next query.
"""
+
try:
if self.client is None:
self.client = http_client(self, self.hostport)
@@ -871,6 +915,7 @@ class http_queue(object):
"""
Kick out the next query in this queue, if any.
"""
+
if self.queue:
self.client.send_request(self.queue[0])
@@ -881,6 +926,7 @@ class http_queue(object):
handling of what otherwise would be a nasty set of race
conditions.
"""
+
if client_ is self.client:
self.logger.debug("Detaching client %r", client_)
self.client = None
@@ -1032,6 +1078,7 @@ class caller(object):
"""
Handle CMS-wrapped XML response message.
"""
+
try:
r_cms = self.proto.cms_msg(DER = r_der)
r_msg = r_cms.unwrap((self.server_ta, self.server_cert))
diff --git a/rpki/http_simple.py b/rpki/http_simple.py
new file mode 100644
index 00000000..8f609e46
--- /dev/null
+++ b/rpki/http_simple.py
@@ -0,0 +1,85 @@
+# $Id$
+
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and 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.
+
+"""
+HTTP using Python standard libraries, for RPKI programs that don't
+need the full-blown rpki.http asynchronous code.
+"""
+
+import logging
+import BaseHTTPServer
+
+logger = logging.getLogger(__name__)
+
+
+rpki_content_type = "application/x-rpki"
+
+
+class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ """
+ HTTP request handler simple RPKI servers.
+ """
+
+ def do_POST(self):
+ try:
+ content_type = self.headers.get("Content-Type")
+ content_length = self.headers.get("Content-Length")
+ if content_type != rpki_content_type:
+ self.send_error(415, "No handler for Content-Type %s" % content_type)
+ else:
+ for prefix, handler in self.rpki_handlers:
+ if self.path.startswith(prefix):
+ handler(self, (self.rfile.read()
+ if content_length is None else
+ self.rfile.read(int(content_length))))
+ break
+ else:
+ self.send_error(404, "No handler for path %s" % self.path)
+ except Exception, e:
+ logger.exception("Unhandled exception")
+ self.send_error(501, "Unhandled exception")
+
+ def send_cms_response(self, der):
+ self.send_response(200)
+ self.send_header("Content-Type", rpki_content_type)
+ self.send_header("Content-Length", str(len(der)))
+ self.end_headers()
+ self.wfile.write(der)
+
+ def log_message(self, *args):
+ logger.info(*args, extra = dict(context = "%s:%s" % self.client_address))
+
+ def send_error(self, code, message = None):
+ # BaseHTTPRequestHandler.send_error() generates HTML error messages,
+ # which we don't want, so we override the method to suppress this.
+ self.send_response(code, message)
+ self.send_header("Content-Type", rpki_content_type)
+ self.send_header("Connection", "close")
+ self.end_headers()
+
+
+def server(handlers, port, host = ""):
+ """
+ Run an HTTP server and wait (forever) for connections.
+ """
+
+ if not isinstance(handlers, (tuple, list)):
+ handlers = (("/", handlers),)
+
+ class RequestHandler(HTTPRequestHandler):
+ rpki_handlers = handlers
+
+ BaseHTTPServer.HTTPServer((host, port), RequestHandler).serve_forever()
diff --git a/rpki/ipaddrs.py b/rpki/ipaddrs.py
index 68b2d27d..25eefd0d 100644
--- a/rpki/ipaddrs.py
+++ b/rpki/ipaddrs.py
@@ -61,6 +61,7 @@ class v4addr(long):
"""
Construct a v4addr object.
"""
+
if isinstance(x, unicode):
x = x.encode("ascii")
if isinstance(x, str):
@@ -72,6 +73,7 @@ class v4addr(long):
"""
Convert a v4addr object to a raw byte string.
"""
+
return struct.pack("!I", long(self))
@classmethod
@@ -79,12 +81,14 @@ class v4addr(long):
"""
Convert from a raw byte string to a v4addr object.
"""
+
return cls(struct.unpack("!I", x)[0])
def __str__(self):
"""
Convert a v4addr object to string format.
"""
+
return socket.inet_ntop(socket.AF_INET, self.to_bytes())
class v6addr(long):
@@ -101,6 +105,7 @@ class v6addr(long):
"""
Construct a v6addr object.
"""
+
if isinstance(x, unicode):
x = x.encode("ascii")
if isinstance(x, str):
@@ -112,6 +117,7 @@ class v6addr(long):
"""
Convert a v6addr object to a raw byte string.
"""
+
return struct.pack("!QQ", long(self) >> 64, long(self) & 0xFFFFFFFFFFFFFFFF)
@classmethod
@@ -119,6 +125,7 @@ class v6addr(long):
"""
Convert from a raw byte string to a v6addr object.
"""
+
x = struct.unpack("!QQ", x)
return cls((x[0] << 64) | x[1])
@@ -126,12 +133,14 @@ class v6addr(long):
"""
Convert a v6addr object to string format.
"""
+
return socket.inet_ntop(socket.AF_INET6, self.to_bytes())
def parse(s):
"""
Parse a string as either an IPv4 or IPv6 address, and return object of appropriate class.
"""
+
if isinstance(s, unicode):
s = s.encode("ascii")
return v6addr(s) if ":" in s else v4addr(s)
diff --git a/rpki/irdb/zookeeper.py b/rpki/irdb/zookeeper.py
index 1e163a4d..c9f7d78e 100644
--- a/rpki/irdb/zookeeper.py
+++ b/rpki/irdb/zookeeper.py
@@ -35,6 +35,7 @@ import rpki.left_right
import rpki.x509
import rpki.async
import rpki.irdb
+import rpki.publication_control
import django.db.transaction
from lxml.etree import (Element, SubElement, ElementTree,
@@ -148,7 +149,6 @@ class etree_wrapper(object):
"""
Wrapper for ETree objects so we can return them as function results
without requiring the caller to understand much about them.
-
"""
def __init__(self, e, msg = None, debug = False):
@@ -533,13 +533,8 @@ class Zookeeper(object):
if self.run_pubd:
updates = []
- updates.append(
- rpki.publication.config_elt.make_pdu(
- action = "set",
- bpki_crl = self.server_ca.latest_crl))
-
updates.extend(
- rpki.publication.client_elt.make_pdu(
+ rpki.publication_control.client_elt.make_pdu(
action = "set",
client_handle = client.handle,
bpki_cert = client.certificate)
@@ -1141,9 +1136,9 @@ class Zookeeper(object):
clear_replay_protection = "yes")
for ca in rpki.irdb.ResourceHolderCA.objects.all())
if self.run_pubd:
- self.call_pubd(rpki.publication.client_elt.make_pdu(action = "set",
- client_handle = client.handle,
- clear_replay_protection = "yes")
+ self.call_pubd(rpki.publication_control.client_elt.make_pdu(action = "set",
+ client_handle = client.handle,
+ clear_replay_protection = "yes")
for client in self.server_ca.clients.all())
@@ -1170,7 +1165,7 @@ class Zookeeper(object):
pdus = pdus[0]
call_pubd = rpki.async.sync_wrapper(rpki.http.caller(
- proto = rpki.publication,
+ proto = rpki.publication_control,
client_key = irbe.private_key,
client_cert = irbe.certificate,
server_ta = self.server_ca.certificate,
@@ -1187,11 +1182,11 @@ class Zookeeper(object):
throw exceptions as needed.
"""
- if any(isinstance(pdu, (rpki.left_right.report_error_elt, rpki.publication.report_error_elt)) for pdu in pdus):
+ if any(isinstance(pdu, (rpki.left_right.report_error_elt, rpki.publication_control.report_error_elt)) for pdu in pdus):
for pdu in pdus:
if isinstance(pdu, rpki.left_right.report_error_elt):
self.log("rpkid reported failure: %s" % pdu.error_code)
- elif isinstance(pdu, rpki.publication.report_error_elt):
+ elif isinstance(pdu, rpki.publication_control.report_error_elt):
self.log("pubd reported failure: %s" % pdu.error_code)
else:
continue
@@ -1527,16 +1522,10 @@ class Zookeeper(object):
if not self.run_pubd:
return
- # Make sure that pubd's BPKI CRL is up to date.
-
- self.call_pubd(rpki.publication.config_elt.make_pdu(
- action = "set",
- bpki_crl = self.server_ca.latest_crl))
-
# See what pubd already has on file
- pubd_reply = self.call_pubd(rpki.publication.client_elt.make_pdu(action = "list"))
- client_pdus = dict((x.client_handle, x) for x in pubd_reply if isinstance(x, rpki.publication.client_elt))
+ pubd_reply = self.call_pubd(rpki.publication_control.client_elt.make_pdu(action = "list"))
+ client_pdus = dict((x.client_handle, x) for x in pubd_reply if isinstance(x, rpki.publication_control.client_elt))
pubd_query = []
# Check all clients
@@ -1548,15 +1537,32 @@ class Zookeeper(object):
if (client_pdu is None or
client_pdu.base_uri != client.sia_base or
client_pdu.bpki_cert != client.certificate):
- pubd_query.append(rpki.publication.client_elt.make_pdu(
+ pubd_query.append(rpki.publication_control.client_elt.make_pdu(
action = "create" if client_pdu is None else "set",
client_handle = client.handle,
bpki_cert = client.certificate,
base_uri = client.sia_base))
+ # rootd instances are also a weird sort of client
+
+ for rootd in rpki.irdb.Rootd.objects.all():
+
+ client_handle = rootd.issuer.handle + "-root"
+ client_pdu = client_pdus.pop(client_handle, None)
+ sia_base = "rsync://%s/%s/%s/" % (self.rsync_server, self.rsync_module, client_handle)
+
+ if (client_pdu is None or
+ client_pdu.base_uri != sia_base or
+ client_pdu.bpki_cert != rootd.issuer.certificate):
+ pubd_query.append(rpki.publication_control.client_elt.make_pdu(
+ action = "create" if client_pdu is None else "set",
+ client_handle = client_handle,
+ bpki_cert = rootd.issuer.certificate,
+ base_uri = sia_base))
+
# Delete any unknown clients
- pubd_query.extend(rpki.publication.client_elt.make_pdu(
+ pubd_query.extend(rpki.publication_control.client_elt.make_pdu(
action = "destroy", client_handle = p) for p in client_pdus)
# If we changed anything, ship updates off to pubd
diff --git a/rpki/irdbd.py b/rpki/irdbd.py
index 64460c30..2b697cc8 100644
--- a/rpki/irdbd.py
+++ b/rpki/irdbd.py
@@ -26,7 +26,7 @@ import time
import logging
import argparse
import urlparse
-import rpki.http
+import rpki.http_simple
import rpki.config
import rpki.resource_set
import rpki.relaxng
@@ -104,7 +104,7 @@ class main(object):
r_pdu.pkcs10 = ee_req.pkcs10
r_msg.append(r_pdu)
- def handler(self, query, path, cb):
+ def handler(self, request, q_der):
try:
q_pdu = None
r_msg = rpki.left_right.msg.reply()
@@ -114,15 +114,13 @@ class main(object):
serverCA = rpki.irdb.ServerCA.objects.get()
rpkid = serverCA.ee_certificates.get(purpose = "rpkid")
try:
- q_cms = rpki.left_right.cms_msg(DER = query)
+ q_cms = rpki.left_right.cms_msg(DER = q_der)
q_msg = q_cms.unwrap((serverCA.certificate, rpkid.certificate))
- self.cms_timestamp = q_cms.check_replay(self.cms_timestamp, path)
+ self.cms_timestamp = q_cms.check_replay(self.cms_timestamp, request.path)
if not isinstance(q_msg, rpki.left_right.msg) or not q_msg.is_query():
raise rpki.exceptions.BadQuery("Unexpected %r PDU" % q_msg)
for q_pdu in q_msg:
self.dispatch(q_pdu, r_msg)
- except (rpki.async.ExitNow, SystemExit):
- raise
except Exception, e:
logger.exception("Exception while handling HTTP request")
if q_pdu is None:
@@ -130,12 +128,10 @@ class main(object):
else:
r_msg.append(rpki.left_right.report_error_elt.from_exception(e, q_pdu.self_handle, q_pdu.tag))
irdbd = serverCA.ee_certificates.get(purpose = "irdbd")
- cb(200, body = rpki.left_right.cms_msg().wrap(r_msg, irdbd.private_key, irdbd.certificate))
- except (rpki.async.ExitNow, SystemExit):
- raise
+ request.send_cms_response(rpki.left_right.cms_msg().wrap(r_msg, irdbd.private_key, irdbd.certificate))
except Exception, e:
logger.exception("Unhandled exception while processing HTTP request")
- cb(500, reason = "Unhandled exception %s: %s" % (e.__class__.__name__, e))
+ request.send_error(500, "Unhandled exception %s: %s" % (e.__class__.__name__, e))
def dispatch(self, q_pdu, r_msg):
try:
@@ -240,7 +236,7 @@ class main(object):
self.cms_timestamp = None
- rpki.http.server(
+ rpki.http_simple.server(
host = self.http_server_host,
port = self.http_server_port,
handlers = self.handler)
diff --git a/rpki/left_right.py b/rpki/left_right.py
index 68ead08f..e4cf25fe 100644
--- a/rpki/left_right.py
+++ b/rpki/left_right.py
@@ -67,6 +67,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name
"""
Fetch self object to which this object links.
"""
+
return self_elt.sql_fetch(self.gctx, self.self_id)
@property
@@ -75,12 +76,14 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name
"""
Return BSC object to which this object links.
"""
+
return bsc_elt.sql_fetch(self.gctx, self.bsc_id)
def make_reply_clone_hook(self, r_pdu):
"""
Set handles when cloning, including _id -> _handle translation.
"""
+
if r_pdu.self_handle is None:
r_pdu.self_handle = self.self_handle
for tag, elt in self.handles:
@@ -97,6 +100,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name
"""
Find an object based on its handle.
"""
+
return cls.sql_fetch_where1(gctx, cls.element_name + "_handle = %s AND self_id = %s", (handle, self_id))
def serve_fetch_one_maybe(self):
@@ -104,6 +108,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name
Find the object on which a get, set, or destroy method should
operate, or which would conflict with a create method.
"""
+
where = "%s.%s_handle = %%s AND %s.self_id = self.self_id AND self.self_handle = %%s" % ((self.element_name,) * 3)
args = (getattr(self, self.element_name + "_handle"), self.self_handle)
return self.sql_fetch_where1(self.gctx, where, args, "self")
@@ -112,6 +117,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name
"""
Find the objects on which a list method should operate.
"""
+
where = "%s.self_id = self.self_id and self.self_handle = %%s" % self.element_name
return self.sql_fetch_where(self.gctx, where, (self.self_handle,), "self")
@@ -124,6 +130,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name
operations, self is the pre-existing object from SQL and q_pdu is
the set request received from the the IRBE.
"""
+
for tag, elt in self.handles:
id_name = tag + "_id"
if getattr(self, id_name, None) is None:
@@ -171,6 +178,7 @@ class self_elt(data_elt):
"""
Fetch all BSC objects that link to this self object.
"""
+
return bsc_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@property
@@ -178,6 +186,7 @@ class self_elt(data_elt):
"""
Fetch all repository objects that link to this self object.
"""
+
return repository_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@property
@@ -185,6 +194,7 @@ class self_elt(data_elt):
"""
Fetch all parent objects that link to this self object.
"""
+
return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@property
@@ -192,6 +202,7 @@ class self_elt(data_elt):
"""
Fetch all child objects that link to this self object.
"""
+
return child_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@property
@@ -199,6 +210,7 @@ class self_elt(data_elt):
"""
Fetch all ROA objects that link to this self object.
"""
+
return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@property
@@ -206,6 +218,7 @@ class self_elt(data_elt):
"""
Fetch all Ghostbuster record objects that link to this self object.
"""
+
return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@property
@@ -213,6 +226,7 @@ class self_elt(data_elt):
"""
Fetch all EE certificate objects that link to this self object.
"""
+
return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
@@ -220,6 +234,7 @@ class self_elt(data_elt):
"""
Extra server actions for self_elt.
"""
+
actions = []
if q_pdu.rekey:
actions.append(self.serve_rekey)
@@ -243,6 +258,7 @@ class self_elt(data_elt):
"""
Handle a left-right rekey action for this self.
"""
+
def loop(iterator, parent):
parent.serve_rekey(iterator, eb)
rpki.async.iterator(self.parents, loop, cb)
@@ -251,6 +267,7 @@ class self_elt(data_elt):
"""
Handle a left-right revoke action for this self.
"""
+
def loop(iterator, parent):
parent.serve_revoke(iterator, eb)
rpki.async.iterator(self.parents, loop, cb)
@@ -259,6 +276,7 @@ class self_elt(data_elt):
"""
Handle a left-right reissue action for this self.
"""
+
def loop(iterator, parent):
parent.serve_reissue(iterator, eb)
rpki.async.iterator(self.parents, loop, cb)
@@ -267,6 +285,7 @@ class self_elt(data_elt):
"""
Handle a left-right revoke_forgotten action for this self.
"""
+
def loop(iterator, parent):
parent.serve_revoke_forgotten(iterator, eb)
rpki.async.iterator(self.parents, loop, cb)
@@ -275,6 +294,7 @@ class self_elt(data_elt):
"""
Handle a left-right clear_replay_protection action for this self.
"""
+
def loop(iterator, obj):
obj.serve_clear_replay_protection(iterator, eb)
rpki.async.iterator(self.parents + self.children + self.repositories, loop, cb)
@@ -283,6 +303,7 @@ class self_elt(data_elt):
"""
Extra cleanup actions when destroying a self_elt.
"""
+
def loop(iterator, parent):
parent.delete(iterator)
rpki.async.iterator(self.parents, loop, cb)
@@ -291,45 +312,44 @@ class self_elt(data_elt):
def serve_publish_world_now(self, cb, eb):
"""
Handle a left-right publish_world_now action for this self.
-
- The publication stuff needs refactoring, right now publication is
- interleaved with local operations in a way that forces far too
- many bounces through the task system for any complex update. The
- whole thing ought to be rewritten to queue up outgoing publication
- PDUs and only send them when we're all done or when we need to
- force publication at a particular point in a multi-phase operation.
-
- Once that reorganization has been done, this method should be
- rewritten to reuse the low-level publish() methods that each
- object will have...but we're not there yet. So, for now, we just
- do this via brute force. Think of it as a trial version to see
- whether we've identified everything that needs to be republished
- for this operation.
"""
+ publisher = rpki.rpkid.publication_queue()
+
def loop(iterator, parent):
- q_msg = rpki.publication.msg.query()
+ repo = parent.repository
for ca in parent.cas:
ca_detail = ca.active_ca_detail
if ca_detail is not None:
- q_msg.append(rpki.publication.crl_elt.make_publish(
- ca_detail.crl_uri, ca_detail.latest_crl))
- q_msg.append(rpki.publication.manifest_elt.make_publish(
- ca_detail.manifest_uri, ca_detail.latest_manifest))
- q_msg.extend(rpki.publication.certificate_elt.make_publish(
- c.uri, c.cert) for c in ca_detail.child_certs)
- q_msg.extend(rpki.publication.roa_elt.make_publish(
- r.uri, r.roa) for r in ca_detail.roas if r.roa is not None)
- q_msg.extend(rpki.publication.ghostbuster_elt.make_publish(
- g.uri, g.ghostbuster) for g in ca_detail.ghostbusters)
- parent.repository.call_pubd(iterator, eb, q_msg)
+ publisher.queue(
+ uri = ca_detail.crl_uri, new_obj = ca_detail.latest_crl, repository = repo)
+ publisher.queue(
+ uri = ca_detail.manifest_uri, new_obj = ca_detail.latest_manifest, repository = repo)
+ for c in ca_detail.child_certs:
+ publisher.queue(
+ uri = c.uri, new_obj = c.cert, repository = repo)
+ for r in ca_detail.roas:
+ if r.roa is not None:
+ publisher.queue(
+ uri = r.uri, new_obj = r.roa, repository = repo)
+ for g in ca_detail.ghostbusters:
+ publisher.queue(
+ uri = g.uri, new_obj = g.ghostbuster, repository = repo)
+ for c in ca_detail.ee_certificates:
+ publisher.queue(
+ uri = c.uri, new_obj = c.cert, repository = repo)
+ iterator()
- rpki.async.iterator(self.parents, loop, cb)
+ def done():
+ publisher.call_pubd(cb, eb)
+
+ rpki.async.iterator(self.parents, loop, done)
def serve_run_now(self, cb, eb):
"""
Handle a left-right run_now action for this self.
"""
+
logger.debug("Forced immediate run of periodic actions for self %s[%d]",
self.self_handle, self.self_id)
completion = rpki.rpkid_tasks.CompletionHandler(cb)
@@ -342,6 +362,7 @@ class self_elt(data_elt):
Find the self object upon which a get, set, or destroy action
should operate, or which would conflict with a create method.
"""
+
return self.serve_fetch_handle(self.gctx, None, self.self_handle)
@classmethod
@@ -349,6 +370,7 @@ class self_elt(data_elt):
"""
Find a self object based on its self_handle.
"""
+
return cls.sql_fetch_where1(gctx, "self_handle = %s", (self_handle,))
def serve_fetch_all(self):
@@ -357,6 +379,7 @@ class self_elt(data_elt):
This is different from the list action for all other objects,
where list only works within a given self_id context.
"""
+
return self.sql_fetch_all(self.gctx)
def schedule_cron_tasks(self, completion):
@@ -428,6 +451,7 @@ class bsc_elt(data_elt):
"""
Fetch all repository objects that link to this BSC object.
"""
+
return repository_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
@property
@@ -435,6 +459,7 @@ class bsc_elt(data_elt):
"""
Fetch all parent objects that link to this BSC object.
"""
+
return parent_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
@property
@@ -442,6 +467,7 @@ class bsc_elt(data_elt):
"""
Fetch all child objects that link to this BSC object.
"""
+
return child_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb):
@@ -449,6 +475,7 @@ class bsc_elt(data_elt):
Extra server actions for bsc_elt -- handle key generation. For
now this only allows RSA with SHA-256.
"""
+
if q_pdu.generate_keypair:
assert q_pdu.key_type in (None, "rsa") and q_pdu.hash_alg in (None, "sha256")
self.private_key_id = rpki.x509.RSA.generate(keylength = q_pdu.key_length or 2048)
@@ -492,12 +519,14 @@ class repository_elt(data_elt):
"""
Fetch all parent objects that link to this repository object.
"""
+
return parent_elt.sql_fetch_where(self.gctx, "repository_id = %s", (self.repository_id,))
def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
"""
Extra server actions for repository_elt.
"""
+
actions = []
if q_pdu.clear_replay_protection:
actions.append(self.serve_clear_replay_protection)
@@ -509,6 +538,7 @@ class repository_elt(data_elt):
"""
Handle a left-right clear_replay_protection action for this repository.
"""
+
self.last_cms_timestamp = None
self.sql_mark_dirty()
cb()
@@ -518,6 +548,7 @@ class repository_elt(data_elt):
"""
Default handler for publication response PDUs.
"""
+
pdu.raise_if_error()
def call_pubd(self, callback, errback, q_msg, handlers = None):
@@ -544,7 +575,7 @@ class repository_elt(data_elt):
handlers = {}
for q_pdu in q_msg:
- logger.info("Sending %s %s to pubd", q_pdu.action, q_pdu.uri)
+ logger.info("Sending %r to pubd", q_pdu)
bsc = self.bsc
q_der = rpki.publication.cms_msg().wrap(q_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl)
@@ -624,6 +655,7 @@ class parent_elt(data_elt):
"""
Fetch repository object to which this parent object links.
"""
+
return repository_elt.sql_fetch(self.gctx, self.repository_id)
@property
@@ -631,12 +663,14 @@ class parent_elt(data_elt):
"""
Fetch all CA objects that link to this parent object.
"""
+
return rpki.rpkid.ca_obj.sql_fetch_where(self.gctx, "parent_id = %s", (self.parent_id,))
def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
"""
Extra server actions for parent_elt.
"""
+
actions = []
if q_pdu.rekey:
actions.append(self.serve_rekey)
@@ -656,6 +690,7 @@ class parent_elt(data_elt):
"""
Handle a left-right rekey action for this parent.
"""
+
def loop(iterator, ca):
ca.rekey(iterator, eb)
rpki.async.iterator(self.cas, loop, cb)
@@ -664,6 +699,7 @@ class parent_elt(data_elt):
"""
Handle a left-right revoke action for this parent.
"""
+
def loop(iterator, ca):
ca.revoke(cb = iterator, eb = eb)
rpki.async.iterator(self.cas, loop, cb)
@@ -672,6 +708,7 @@ class parent_elt(data_elt):
"""
Handle a left-right reissue action for this parent.
"""
+
def loop(iterator, ca):
ca.reissue(cb = iterator, eb = eb)
rpki.async.iterator(self.cas, loop, cb)
@@ -680,6 +717,7 @@ class parent_elt(data_elt):
"""
Handle a left-right clear_replay_protection action for this parent.
"""
+
self.last_cms_timestamp = None
self.sql_mark_dirty()
cb()
@@ -860,6 +898,7 @@ class child_elt(data_elt):
"""
Fetch all child_cert objects that link to this child object.
"""
+
return rpki.rpkid.child_cert_obj.fetch(self.gctx, self, ca_detail, ski, unique)
@property
@@ -867,6 +906,7 @@ class child_elt(data_elt):
"""
Fetch all child_cert objects that link to this child object.
"""
+
return self.fetch_child_certs()
@property
@@ -874,12 +914,14 @@ class child_elt(data_elt):
"""
Fetch all parent objects that link to self object to which this child object links.
"""
+
return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
"""
Extra server actions for child_elt.
"""
+
actions = []
if q_pdu.reissue:
actions.append(self.serve_reissue)
@@ -893,6 +935,7 @@ class child_elt(data_elt):
"""
Handle a left-right reissue action for this child.
"""
+
publisher = rpki.rpkid.publication_queue()
for child_cert in self.child_certs:
child_cert.reissue(child_cert.ca_detail, publisher, force = True)
@@ -902,6 +945,7 @@ class child_elt(data_elt):
"""
Handle a left-right clear_replay_protection action for this child.
"""
+
self.last_cms_timestamp = None
self.sql_mark_dirty()
cb()
@@ -910,6 +954,7 @@ class child_elt(data_elt):
"""
Fetch the CA corresponding to an up-down class_name.
"""
+
if not class_name.isdigit():
raise rpki.exceptions.BadClassNameSyntax("Bad class name %s" % class_name)
ca = rpki.rpkid.ca_obj.sql_fetch(self.gctx, long(class_name))
@@ -926,6 +971,7 @@ class child_elt(data_elt):
"""
Extra server actions when destroying a child_elt.
"""
+
publisher = rpki.rpkid.publication_queue()
for child_cert in self.child_certs:
child_cert.revoke(publisher = publisher,
@@ -989,6 +1035,7 @@ class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace):
Handle <list_resources/> element. This requires special handling
due to the data types of some of the attributes.
"""
+
assert name == "list_resources", "Unexpected name %s, stack %s" % (name, stack)
self.read_attrs(attrs)
if isinstance(self.valid_until, str):
@@ -1005,6 +1052,7 @@ class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace):
Generate <list_resources/> element. This requires special
handling due to the data types of some of the attributes.
"""
+
elt = self.make_elt()
if isinstance(self.valid_until, int):
elt.set("valid_until", self.valid_until.toXMLtime())
@@ -1023,6 +1071,7 @@ class list_roa_requests_elt(rpki.xml_utils.base_elt, left_right_namespace):
Handle <list_roa_requests/> element. This requires special handling
due to the data types of some of the attributes.
"""
+
assert name == "list_roa_requests", "Unexpected name %s, stack %s" % (name, stack)
self.read_attrs(attrs)
if self.ipv4 is not None:
@@ -1068,6 +1117,7 @@ class list_ee_certificate_requests_elt(rpki.xml_utils.base_elt, left_right_names
Handle <list_ee_certificate_requests/> element. This requires special
handling due to the data types of some of the attributes.
"""
+
if name not in self.elements:
assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
self.read_attrs(attrs)
@@ -1086,6 +1136,7 @@ class list_ee_certificate_requests_elt(rpki.xml_utils.base_elt, left_right_names
"""
Handle <pkcs10/> sub-element.
"""
+
assert len(self.elements) == 1
if name == self.elements[0]:
self.pkcs10 = rpki.x509.PKCS10(Base64 = text)
@@ -1098,6 +1149,7 @@ class list_ee_certificate_requests_elt(rpki.xml_utils.base_elt, left_right_names
Generate <list_ee_certificate_requests/> element. This requires special
handling due to the data types of some of the attributes.
"""
+
if isinstance(self.eku, (tuple, list)):
self.eku = ",".join(self.eku)
elt = self.make_elt()
@@ -1128,6 +1180,7 @@ class list_published_objects_elt(rpki.xml_utils.text_elt, left_right_namespace):
misnomer here, there's no action attribute and no dispatch, we
just dump every published object for the specified <self/> and return.
"""
+
for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents:
for ca in parent.cas:
ca_detail = ca.active_ca_detail
@@ -1148,6 +1201,7 @@ class list_published_objects_elt(rpki.xml_utils.text_elt, left_right_namespace):
"""
Generate one reply PDU.
"""
+
r_pdu = self.make_pdu(tag = self.tag, self_handle = self.self_handle,
uri = uri, child_handle = child_handle)
r_pdu.obj = obj.get_Base64()
@@ -1172,6 +1226,7 @@ class list_received_resources_elt(rpki.xml_utils.base_elt, left_right_namespace)
just dump a bunch of data about every certificate issued to us by
one of our parents, then return.
"""
+
for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents:
for ca in parent.cas:
ca_detail = ca.active_ca_detail
@@ -1183,6 +1238,7 @@ class list_received_resources_elt(rpki.xml_utils.base_elt, left_right_namespace)
"""
Generate one reply PDU.
"""
+
resources = cert.get_3779resources()
return self.make_pdu(
tag = self.tag,
@@ -1216,6 +1272,7 @@ class report_error_elt(rpki.xml_utils.text_elt, left_right_namespace):
"""
Generate a <report_error/> element from an exception.
"""
+
self = cls()
self.self_handle = self_handle
self.tag = tag
diff --git a/rpki/log.py b/rpki/log.py
index 2abb3b2c..4fe2a808 100644
--- a/rpki/log.py
+++ b/rpki/log.py
@@ -96,7 +96,10 @@ class Formatter(object):
yield time.strftime("%Y-%m-%d %H:%M:%S ", time.gmtime(record.created))
yield "%s[%d]: " % (self.ident, record.process)
try:
- yield repr(record.context) + " "
+ if isinstance(record.context, (str, unicode)):
+ yield record.context + " "
+ else:
+ yield repr(record.context) + " "
except AttributeError:
pass
yield record.getMessage()
diff --git a/rpki/old_irdbd.py b/rpki/old_irdbd.py
index c5ce2278..3fd476f2 100644
--- a/rpki/old_irdbd.py
+++ b/rpki/old_irdbd.py
@@ -30,7 +30,7 @@ import time
import logging
import argparse
import urlparse
-import rpki.http
+import rpki.http_simple
import rpki.config
import rpki.resource_set
import rpki.relaxng
@@ -226,7 +226,7 @@ class main(object):
rpki.left_right.list_ghostbuster_requests_elt : handle_list_ghostbuster_requests,
rpki.left_right.list_ee_certificate_requests_elt : handle_list_ee_certificate_requests }
- def handler(self, query, path, cb):
+ def handler(self, request, q_der):
try:
self.db.ping(True)
@@ -235,7 +235,7 @@ class main(object):
try:
- q_msg = rpki.left_right.cms_msg(DER = query).unwrap((self.bpki_ta, self.rpkid_cert))
+ q_msg = rpki.left_right.cms_msg(DER = q_der).unwrap((self.bpki_ta, self.rpkid_cert))
if not isinstance(q_msg, rpki.left_right.msg) or not q_msg.is_query():
raise rpki.exceptions.BadQuery("Unexpected %r PDU" % q_msg)
@@ -251,28 +251,19 @@ class main(object):
else:
h(self, q_pdu, r_msg)
- except (rpki.async.ExitNow, SystemExit):
- raise
-
except Exception, e:
logger.exception("Exception serving PDU %r", q_pdu)
r_msg.append(rpki.left_right.report_error_elt.from_exception(e, q_pdu.self_handle, q_pdu.tag))
- except (rpki.async.ExitNow, SystemExit):
- raise
-
except Exception, e:
logger.exception("Exception decoding query")
r_msg.append(rpki.left_right.report_error_elt.from_exception(e))
- cb(200, body = rpki.left_right.cms_msg().wrap(r_msg, self.irdbd_key, self.irdbd_cert))
-
- except (rpki.async.ExitNow, SystemExit):
- raise
+ request.send_cms_response(rpki.left_right.cms_msg().wrap(r_msg, self.irdbd_key, self.irdbd_cert))
except Exception, e:
logger.exception("Unhandled exception, returning HTTP failure")
- cb(500, reason = "Unhandled exception %s: %s" % (e.__class__.__name__, e))
+ request.send_error(500, "Unhandled exception %s: %s" % (e.__class__.__name__, e))
def __init__(self):
@@ -317,6 +308,6 @@ class main(object):
u.query == "" and \
u.fragment == ""
- rpki.http.server(host = u.hostname or "localhost",
- port = u.port or 443,
- handlers = ((u.path, self.handler),))
+ rpki.http_simple.server(host = u.hostname or "localhost",
+ port = u.port or 443,
+ handlers = ((u.path, self.handler),))
diff --git a/rpki/pubd.py b/rpki/pubd.py
index cb9da32c..fe7987d1 100644
--- a/rpki/pubd.py
+++ b/rpki/pubd.py
@@ -23,23 +23,63 @@ RPKI publication engine.
import os
import re
+import uuid
import time
+import socket
import logging
import argparse
+
import rpki.resource_set
import rpki.up_down
import rpki.x509
import rpki.sql
-import rpki.http
import rpki.config
import rpki.exceptions
import rpki.relaxng
import rpki.log
import rpki.publication
+import rpki.publication_control
import rpki.daemonize
+import rpki.http_simple
+
+from lxml.etree import Element, SubElement, tostring as ElementToString
logger = logging.getLogger(__name__)
+
+rrdp_xmlns = rpki.relaxng.rrdp.xmlns
+rrdp_nsmap = rpki.relaxng.rrdp.nsmap
+rrdp_version = "1"
+
+pub_xmlns = rpki.relaxng.publication.xmlns
+pub_nsmap = rpki.relaxng.publication.nsmap
+pub_version = rpki.relaxng.publication.version
+
+rrdp_tag_delta = rrdp_xmlns + "delta"
+rrdp_tag_deltas = rrdp_xmlns + "deltas"
+rrdp_tag_notification = rrdp_xmlns + "notification"
+rrdp_tag_publish = rrdp_xmlns + "publish"
+rrdp_tag_snapshot = rrdp_xmlns + "snapshot"
+rrdp_tag_withdraw = rrdp_xmlns + "withdraw"
+
+pub_tag_msg = pub_xmlns + "msg"
+pub_tag_list = pub_xmlns + "list"
+pub_tag_publish = pub_xmlns + "publish"
+pub_tag_withdraw = pub_xmlns + "withdraw"
+pub_tag_report_error = pub_xmlns + "report_error"
+
+
+def DERSubElement(elt, name, der, attrib = None, **kwargs):
+ """
+ Convenience wrapper around SubElement for use with Base64 text.
+ """
+
+ se = SubElement(elt, name, attrib, **kwargs)
+ se.text = rpki.x509.base64_with_linebreaks(der)
+ se.tail = "\n"
+ return se
+
+
class main(object):
"""
Main program for pubd.
@@ -88,84 +128,440 @@ class main(object):
if self.profile:
logger.info("Running in profile mode with output to %s", self.profile)
- self.sql = rpki.sql.session(self.cfg)
+ self.sql = rpki.sql.session(self.cfg, autocommit = False)
self.bpki_ta = rpki.x509.X509(Auto_update = self.cfg.get("bpki-ta"))
self.irbe_cert = rpki.x509.X509(Auto_update = self.cfg.get("irbe-cert"))
self.pubd_cert = rpki.x509.X509(Auto_update = self.cfg.get("pubd-cert"))
self.pubd_key = rpki.x509.RSA( Auto_update = self.cfg.get("pubd-key"))
+ self.pubd_crl = rpki.x509.CRL( Auto_update = self.cfg.get("pubd-crl"))
self.http_server_host = self.cfg.get("server-host", "")
self.http_server_port = self.cfg.getint("server-port")
self.publication_base = self.cfg.get("publication-base", "publication/")
- self.publication_multimodule = self.cfg.getboolean("publication-multimodule", False)
+ self.rrdp_uri_base = self.cfg.get("rrdp-uri-base",
+ "http://%s/rrdp/" % socket.getfqdn())
+ self.rrdp_expiration_interval = rpki.sundial.timedelta.parse(self.cfg.get("rrdp-expiration-interval", "6h"))
+ self.rrdp_publication_base = self.cfg.get("rrdp-publication-base",
+ "rrdp-publication/")
+
+ self.session = session_obj.fetch(self)
- rpki.http.server(
+ rpki.http_simple.server(
host = self.http_server_host,
port = self.http_server_port,
handlers = (("/control", self.control_handler),
("/client/", self.client_handler)))
- def handler_common(self, query, client, cb, certs, crl = None):
- """
- Common PDU handler code.
- """
- def done(r_msg):
- reply = rpki.publication.cms_msg().wrap(r_msg, self.pubd_key, self.pubd_cert, crl)
- self.sql.sweep()
- cb(reply)
+ def rrdp_filename_to_uri(self, fn):
+ return "%s/%s" % (self.rrdp_uri_base.rstrip("/"), fn)
- q_cms = rpki.publication.cms_msg(DER = query)
- q_msg = q_cms.unwrap(certs)
- if client is None:
- self.irbe_cms_timestamp = q_cms.check_replay(self.irbe_cms_timestamp, "control")
- else:
- q_cms.check_replay_sql(client, client.client_handle)
- q_msg.serve_top_level(self, client, done)
- def control_handler(self, query, path, cb):
+ def control_handler(self, request, q_der):
"""
Process one PDU from the IRBE.
"""
- def done(body):
- cb(200, body = body)
+ # This is still structured with callbacks as if it were
+ # asynchronous, because a lot of the grunt work is done by code in
+ # rpki.xml_utils. If and when we get around to re-writing the
+ # left-right and publication-control protocols to use lxml.etree
+ # directly, most of the rpki.xml_utils code will go away, but for
+ # the moment it's simplest to preserve the weird calling sequences.
+
+ def done(r_msg):
+ self.sql.commit()
+ request.send_cms_response(rpki.publication_control.cms_msg().wrap(r_msg, self.pubd_key, self.pubd_cert))
+ self.sql.commit()
try:
- self.handler_common(query, None, done, (self.bpki_ta, self.irbe_cert))
- except (rpki.async.ExitNow, SystemExit):
- raise
+ q_cms = rpki.publication_control.cms_msg(DER = q_der)
+ q_msg = q_cms.unwrap((self.bpki_ta, self.irbe_cert))
+ self.irbe_cms_timestamp = q_cms.check_replay(self.irbe_cms_timestamp, "control")
+ q_msg.serve_top_level(self, done)
except Exception, e:
- logger.exception("Unhandled exception processing control query, path %r", path)
- cb(500, reason = "Unhandled exception %s: %s" % (e.__class__.__name__, e))
+ logger.exception("Unhandled exception processing control query, path %r", request.path)
+ request.send_error(500, "Unhandled exception %s: %s" % (e.__class__.__name__, e))
+
client_url_regexp = re.compile("/client/([-A-Z0-9_/]+)$", re.I)
- def client_handler(self, query, path, cb):
+ def client_handler(self, request, q_der):
"""
Process one PDU from a client.
"""
- def done(body):
- cb(200, body = body)
-
try:
- match = self.client_url_regexp.search(path)
+ match = self.client_url_regexp.search(request.path)
if match is None:
- raise rpki.exceptions.BadContactURL("Bad path: %s" % path)
+ raise rpki.exceptions.BadContactURL("Bad path: %s" % request.path)
client_handle = match.group(1)
- client = rpki.publication.client_elt.sql_fetch_where1(self, "client_handle = %s", (client_handle,))
+ client = rpki.publication_control.client_elt.sql_fetch_where1(self, "client_handle = %s", (client_handle,))
if client is None:
raise rpki.exceptions.ClientNotFound("Could not find client %s" % client_handle)
- config = rpki.publication.config_elt.fetch(self)
- if config is None or config.bpki_crl is None:
- raise rpki.exceptions.CMSCRLNotSet
- self.handler_common(query, client, done, (self.bpki_ta, client.bpki_cert, client.bpki_glue), config.bpki_crl)
- except (rpki.async.ExitNow, SystemExit):
- raise
+ q_cms = rpki.publication.cms_msg_no_sax(DER = q_der)
+ q_msg = q_cms.unwrap((self.bpki_ta, client.bpki_cert, client.bpki_glue))
+ q_cms.check_replay_sql(client, client.client_handle)
+ self.sql.commit() # commit the replay timestamp
+ if q_msg.get("type") != "query":
+ raise rpki.exceptions.BadQuery("Message type is %s, expected query" % q_msg.get("type"))
+ r_msg = Element(pub_tag_msg, nsmap = pub_nsmap, type = "reply", version = pub_version)
+ delta = None
+ failed = False
+ for q_pdu in q_msg:
+ try:
+ if q_pdu.tag == pub_tag_list:
+ for obj in client.objects:
+ r_pdu = SubElement(r_msg, q_pdu.tag, uri = obj.uri, hash = obj.hash)
+ if q_pdu.get("tag") is not None:
+ r_pdu.set("tag", q_pdu.get("tag"))
+ else:
+ assert q_pdu.tag in (pub_tag_publish, pub_tag_withdraw)
+ if delta is None:
+ delta = self.session.new_delta()
+ client.check_allowed_uri(q_pdu.get("uri"))
+ if q_pdu.tag == pub_tag_publish:
+ der = q_pdu.text.decode("base64")
+ logger.info("Publishing %s", rpki.x509.uri_dispatch(q_pdu.get("uri"))(DER = der).tracking_data(q_pdu.get("uri")))
+ delta.publish(client, der, q_pdu.get("uri"), q_pdu.get("hash"))
+ else:
+ logger.info("Withdrawing %s", q_pdu.get("uri"))
+ delta.withdraw(client, q_pdu.get("uri"), q_pdu.get("hash"))
+ r_pdu = SubElement(r_msg, q_pdu.tag, uri = q_pdu.get("uri"))
+ if q_pdu.get("tag") is not None:
+ r_pdu.set("tag", q_pdu.get("tag"))
+ except Exception, e:
+ logger.exception("Exception processing PDU %r", q_pdu)
+ r_pdu = SubElement(r_msg, pub_tag_report_error, error_code = e.__class__.__name__)
+ r_pdu.text = str(e)
+ if q_pdu.get("tag") is not None:
+ r_pdu.set("tag", q_pdu.get("tag"))
+ failed = True
+
+ if failed:
+ self.sql.rollback()
+
+ elif delta is not None:
+ delta.activate()
+ self.sql.sweep()
+ self.session.generate_snapshot()
+ self.session.expire_deltas()
+ self.sql.commit()
+ self.session.synchronize_rrdp_files()
+ delta.update_rsync_files()
+
+ request.send_cms_response(rpki.publication.cms_msg_no_sax().wrap(r_msg, self.pubd_key, self.pubd_cert, self.pubd_crl))
+
+
except Exception, e:
- logger.exception("Unhandled exception processing client query, path %r", path)
- cb(500, reason = "Could not process PDU: %s" % e)
+ logger.exception("Unhandled exception processing client query, path %r", request.path)
+ self.sql.rollback()
+ request.send_error(500, "Could not process PDU: %s" % e)
+
+
+ def uri_to_filename(self, uri):
+ """
+ Convert a URI to a local filename.
+ """
+
+ if not uri.startswith("rsync://"):
+ raise rpki.exceptions.BadURISyntax(uri)
+ path = uri.split("/")[4:]
+ path.insert(0, self.publication_base.rstrip("/"))
+ filename = "/".join(path)
+ if "/../" in filename or filename.endswith("/.."):
+ raise rpki.exceptions.BadURISyntax(filename)
+ return filename
+
+
+class session_obj(rpki.sql.sql_persistent):
+ """
+ An RRDP session.
+ """
+
+ sql_template = rpki.sql.template(
+ "session",
+ "session_id",
+ "uuid",
+ "serial",
+ "snapshot",
+ "hash")
+
+ def __repr__(self):
+ return rpki.log.log_repr(self, self.uuid, self.serial)
+
+ @classmethod
+ def fetch(cls, gctx):
+ """
+ Fetch the one and only session, creating it if necessary.
+ """
+
+ self = cls.sql_fetch(gctx, 1)
+ if self is None:
+ self = cls()
+ self.uuid = str(uuid.uuid4())
+ self.serial = 0
+ self.snapshot = None
+ self.hash = None
+ self.gctx = gctx
+ self.sql_store()
+ return self
+
+ @property
+ def objects(self):
+ return object_obj.sql_fetch_where(self.gctx, "session_id = %s", (self.session_id,))
+
+ @property
+ def deltas(self):
+ return delta_obj.sql_fetch_where(self.gctx, "session_id = %s", (self.session_id,))
+
+ def new_delta(self):
+ return delta_obj.create(self)
+
+ def expire_deltas(self):
+ for delta in delta_obj.sql_fetch_where(self.gctx,
+ "session_id = %s AND expires IS NOT NULL AND expires < %s",
+ (self.session_id, rpki.sundial.now())):
+ delta.sql_mark_deleted()
+
+ def write_rrdp_file(self, fn, text, overwrite = False):
+ """
+ Save RRDP XML to disk.
+ """
+
+ if overwrite or not os.path.exists(os.path.join(self.gctx.rrdp_publication_base, fn)):
+ tn = os.path.join(self.gctx.rrdp_publication_base, fn + ".%s.tmp" % os.getpid())
+ if not os.path.isdir(os.path.dirname(tn)):
+ os.makedirs(os.path.dirname(tn))
+ with open(tn, "w") as f:
+ f.write(text)
+ os.rename(tn, os.path.join(self.gctx.rrdp_publication_base, fn))
+
+ def generate_snapshot(self):
+ """
+ Generate an XML snapshot of this session.
+ """
+
+ xml = Element(rrdp_tag_snapshot, nsmap = rrdp_nsmap,
+ version = rrdp_version,
+ session_id = self.uuid,
+ serial = str(self.serial))
+ xml.text = "\n"
+ for obj in self.objects:
+ DERSubElement(xml, rrdp_tag_publish,
+ der = obj.der,
+ uri = obj.uri)
+ rpki.relaxng.rrdp.assertValid(xml)
+ self.snapshot = ElementToString(xml, pretty_print = True)
+ self.hash = rpki.x509.sha256(self.snapshot).encode("hex")
+ self.sql_store()
+
+ @property
+ def snapshot_fn(self):
+ return "%s/snapshot/%s.xml" % (self.uuid, self.serial)
+
+ @property
+ def notification_fn(self):
+ return "updates.xml"
+
+ def synchronize_rrdp_files(self):
+ """
+ Write current RRDP files to disk, clean up old files and directories.
+ """
+
+ current_filenames = set()
+
+ for delta in self.deltas:
+ self.write_rrdp_file(delta.fn, delta.xml)
+ current_filenames.add(delta.fn)
+
+ self.write_rrdp_file(self.snapshot_fn, self.snapshot)
+ current_filenames.add(self.snapshot_fn)
+
+ xml = Element(rrdp_tag_notification, nsmap = rrdp_nsmap,
+ version = rrdp_version,
+ session_id = self.uuid,
+ serial = str(self.serial))
+ SubElement(xml, rrdp_tag_snapshot,
+ uri = self.gctx.rrdp_filename_to_uri(self.snapshot_fn),
+ hash = self.hash)
+ for delta in self.deltas:
+ se = SubElement(xml, rrdp_tag_delta,
+ to = str(delta.serial),
+ uri = self.gctx.rrdp_filename_to_uri(delta.fn),
+ hash = delta.hash)
+ se.set("from", str(delta.serial - 1))
+ rpki.relaxng.rrdp.assertValid(xml)
+ self.write_rrdp_file(self.notification_fn,
+ ElementToString(xml, pretty_print = True),
+ overwrite = True)
+ current_filenames.add(self.notification_fn)
+
+ for root, dirs, files in os.walk(self.gctx.rrdp_publication_base, topdown = False):
+ for fn in files:
+ fn = os.path.join(root, fn)
+ if fn[len(self.gctx.rrdp_publication_base):].lstrip("/") not in current_filenames:
+ os.remove(fn)
+ for dn in dirs:
+ try:
+ os.rmdir(os.path.join(root, dn))
+ except OSError:
+ pass
+
+
+class delta_obj(rpki.sql.sql_persistent):
+ """
+ An RRDP delta.
+ """
+
+ sql_template = rpki.sql.template(
+ "delta",
+ "delta_id",
+ "session_id",
+ "serial",
+ "xml",
+ "hash",
+ ("expires", rpki.sundial.datetime))
+
+ @property
+ @rpki.sql.cache_reference
+ def session(self):
+ return session_obj.sql_fetch(self.gctx, self.session_id)
+
+ @property
+ def fn(self):
+ return "%s/deltas/%s-%s.xml" % (self.session.uuid, self.serial - 1, self.serial)
+
+ @classmethod
+ def create(cls, session):
+ self = cls()
+ self.gctx = session.gctx
+ self.session_id = session.session_id
+ self.serial = session.serial + 1
+ self.xml = None
+ self.hash = None
+ self.expires = rpki.sundial.now() + self.gctx.rrdp_expiration_interval
+ self.deltas = Element(rrdp_tag_deltas, nsmap = rrdp_nsmap,
+ to = str(self.serial),
+ version = rrdp_version,
+ session_id = session.uuid)
+ self.deltas.set("from", str(self.serial - 1))
+ SubElement(self.deltas, rrdp_tag_delta, serial = str(self.serial)).text = "\n"
+ return self
+
+ def activate(self):
+ rpki.relaxng.rrdp.assertValid(self.deltas)
+ self.xml = ElementToString(self.deltas, pretty_print = True)
+ self.hash = rpki.x509.sha256(self.xml).encode("hex")
+ self.sql_mark_dirty()
+ self.session.serial += 1
+ self.session.sql_mark_dirty()
+
+ def publish(self, client, der, uri, hash):
+ obj = object_obj.current_object_at_uri(client, self, uri)
+ if obj is not None:
+ if obj.hash == hash:
+ obj.delete(self)
+ elif hash is None:
+ raise rpki.exceptions.ExistingObjectAtURI("Object already published at %s" % uri)
+ else:
+ raise rpki.exceptions.DifferentObjectAtURI("Found different object at %s (old %s, new %s)" % (uri, obj.hash, hash))
+ logger.debug("Publishing %s", uri)
+ object_obj.create(client, self, der, uri)
+ se = DERSubElement(self.deltas[0], rrdp_tag_publish, der = der, uri = uri)
+ if hash is not None:
+ se.set("hash", hash)
+ rpki.relaxng.rrdp.assertValid(self.deltas)
+
+ def withdraw(self, client, uri, hash):
+ obj = object_obj.current_object_at_uri(client, self, uri)
+ if obj is None:
+ raise rpki.exceptions.NoObjectAtURI("No object published at %s" % uri)
+ if obj.hash != hash:
+ raise rpki.exceptions.DifferentObjectAtURI("Found different object at %s (old %s, new %s)" % (uri, obj.hash, hash))
+ logger.debug("Withdrawing %s", uri)
+ obj.delete(self)
+ SubElement(self.deltas[0], rrdp_tag_withdraw, uri = uri, hash = hash).tail = "\n"
+ rpki.relaxng.rrdp.assertValid(self.deltas)
+
+ def update_rsync_files(self):
+ min_path_len = len(self.gctx.publication_base.rstrip("/"))
+ for pdu in self.deltas[0]:
+ assert pdu.tag in (rrdp_tag_publish, rrdp_tag_withdraw)
+ fn = self.gctx.uri_to_filename(pdu.get("uri"))
+ if pdu.tag == rrdp_tag_publish:
+ tn = fn + ".tmp"
+ dn = os.path.dirname(fn)
+ if not os.path.isdir(dn):
+ os.makedirs(dn)
+ with open(tn, "wb") as f:
+ f.write(pdu.text.decode("base64"))
+ os.rename(tn, fn)
+ else:
+ try:
+ os.remove(fn)
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ dn = os.path.dirname(fn)
+ while len(dn) > min_path_len:
+ try:
+ os.rmdir(dn)
+ except OSError:
+ break
+ else:
+ dn = os.path.dirname(dn)
+ del self.deltas
+
+
+class object_obj(rpki.sql.sql_persistent):
+ """
+ A published object.
+ """
+
+ sql_template = rpki.sql.template(
+ "object",
+ "object_id",
+ "uri",
+ "der",
+ "hash",
+ "client_id",
+ "session_id")
+
+ def __repr__(self):
+ return rpki.log.log_repr(self, self.uri)
+
+ @property
+ @rpki.sql.cache_reference
+ def session(self):
+ return session_obj.sql_fetch(self.gctx, self.session_id)
+
+ @property
+ @rpki.sql.cache_reference
+ def client(self):
+ return rpki.publication_control.client_elt.sql_fetch(self.gctx, self.client_id)
+
+ @classmethod
+ def create(cls, client, delta, der, uri):
+ self = cls()
+ self.gctx = delta.gctx
+ self.uri = uri
+ self.der = der
+ self.hash = rpki.x509.sha256(der).encode("hex")
+ logger.debug("Computed hash %s for %s", self.hash, self.uri)
+ self.session_id = delta.session_id
+ self.client_id = client.client_id
+ self.sql_mark_dirty()
+ return self
+
+ def delete(self, delta):
+ self.sql_mark_deleted()
+
+ @classmethod
+ def current_object_at_uri(cls, client, delta, uri):
+ return cls.sql_fetch_where1(client.gctx,
+ "session_id = %s AND client_id = %s AND uri = %s",
+ (delta.session_id, client.client_id, uri))
diff --git a/rpki/publication.py b/rpki/publication.py
index 5fc7f3dd..ca7f7792 100644
--- a/rpki/publication.py
+++ b/rpki/publication.py
@@ -1,35 +1,24 @@
# $Id$
#
-# Copyright (C) 2009--2012 Internet Systems Consortium ("ISC")
-#
-# Permission to use, copy, modify, and 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 ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC 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.
-#
+# Copyright (C) 2013--2014 Dragon Research Labs ("DRL")
+# Portions copyright (C) 2009--2012 Internet Systems Consortium ("ISC")
# Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
#
# Permission to use, copy, modify, and 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.
+# copyright notices and this permission notice appear in all copies.
#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ARIN 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.
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+# ISC, OR ARIN 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.
"""
-RPKI "publication" protocol.
+RPKI publication protocol.
"""
import os
@@ -48,322 +37,86 @@ import rpki.log
logger = logging.getLogger(__name__)
-class publication_namespace(object):
- """
- XML namespace parameters for publication protocol.
- """
+class publication_namespace(object):
xmlns = rpki.relaxng.publication.xmlns
nsmap = rpki.relaxng.publication.nsmap
-class control_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, publication_namespace):
- """
- Virtual class for control channel objects.
- """
- def serve_dispatch(self, r_msg, cb, eb):
- """
- Action dispatch handler. This needs special handling because we
- need to make sure that this PDU arrived via the control channel.
- """
- if self.client is not None:
- raise rpki.exceptions.BadQuery("Control query received on client channel")
- rpki.xml_utils.data_elt.serve_dispatch(self, r_msg, cb, eb)
-
-class config_elt(control_elt):
+class base_publication_elt(rpki.xml_utils.base_elt, publication_namespace):
"""
- <config/> element. This is a little weird because there should
- never be more than one row in the SQL config table, but we have to
- put the BPKI CRL somewhere and SQL is the least bad place available.
-
- So we reuse a lot of the SQL machinery, but we nail config_id at 1,
- we don't expose it in the XML protocol, and we only support the get
- and set actions.
+ Base element for publication protocol. Publish and withdraw PDUs subclass this.
"""
- attributes = ("action", "tag")
- element_name = "config"
- elements = ("bpki_crl",)
+ attributes = ("tag", "uri", "hash")
- sql_template = rpki.sql.template(
- "config",
- "config_id",
- ("bpki_crl", rpki.x509.CRL))
+ tag = None
+ uri = None
+ der = None
+ hash = None
+
+ _payload = None
- wired_in_config_id = 1
-
- def startElement(self, stack, name, attrs):
- """
- StartElement() handler for config object. This requires special
- handling because of the weird way we treat config_id.
- """
- control_elt.startElement(self, stack, name, attrs)
- self.config_id = self.wired_in_config_id
+ def __repr__(self):
+ return rpki.log.log_repr(self, self.tag, self.uri, self.hash, self.payload)
- @classmethod
- def fetch(cls, gctx):
- """
- Fetch the config object from SQL. This requires special handling
- because of the weird way we treat config_id.
- """
- return cls.sql_fetch(gctx, cls.wired_in_config_id)
-
- def serve_set(self, r_msg, cb, eb):
- """
- Handle a set action. This requires special handling because
- config doesn't support the create method.
- """
- if self.sql_fetch(self.gctx, self.config_id) is None:
- control_elt.serve_create(self, r_msg, cb, eb)
- else:
- control_elt.serve_set(self, r_msg, cb, eb)
+ @property
+ def payload(self):
+ if self._payload is None and self.der is not None:
+ self._payload = rpki.x509.uri_dispatch(self.uri)(DER = self.der)
+ return self._payload
- def serve_fetch_one_maybe(self):
- """
- Find the config object on which a get or set method should
- operate.
- """
- return self.sql_fetch(self.gctx, self.config_id)
-
-class client_elt(control_elt):
- """
- <client/> element.
- """
-
- element_name = "client"
- attributes = ("action", "tag", "client_handle", "base_uri")
- elements = ("bpki_cert", "bpki_glue")
- booleans = ("clear_replay_protection",)
-
- sql_template = rpki.sql.template(
- "client",
- "client_id",
- "client_handle",
- "base_uri",
- ("bpki_cert", rpki.x509.X509),
- ("bpki_glue", rpki.x509.X509),
- ("last_cms_timestamp", rpki.sundial.datetime))
-
- base_uri = None
- bpki_cert = None
- bpki_glue = None
- last_cms_timestamp = None
-
- def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
- """
- Extra server actions for client_elt.
- """
- actions = []
- if q_pdu.clear_replay_protection:
- actions.append(self.serve_clear_replay_protection)
- def loop(iterator, action):
- action(iterator, eb)
- rpki.async.iterator(actions, loop, cb)
-
- def serve_clear_replay_protection(self, cb, eb):
- """
- Handle a clear_replay_protection action for this client.
- """
- self.last_cms_timestamp = None
- self.sql_mark_dirty()
- cb()
-
- def serve_fetch_one_maybe(self):
+ def raise_if_error(self):
"""
- Find the client object on which a get, set, or destroy method
- should operate, or which would conflict with a create method.
+ No-op unless this is a <report_error/> PDU.
"""
- return self.sql_fetch_where1(self.gctx, "client_handle = %s", (self.client_handle,))
- def serve_fetch_all(self):
- """
- Find client objects on which a list method should operate.
- """
- return self.sql_fetch_all(self.gctx)
+ pass
- def check_allowed_uri(self, uri):
- """
- Make sure that a target URI is within this client's allowed URI space.
- """
- if not uri.startswith(self.base_uri):
- raise rpki.exceptions.ForbiddenURI
-class publication_object_elt(rpki.xml_utils.base_elt, publication_namespace):
+class publish_elt(base_publication_elt):
"""
- Virtual class for publishable objects. These have very similar
- syntax, differences lie in underlying datatype and methods. XML
- methods are a little different from the pattern used for objects
- that support the create/set/get/list/destroy actions, but
- publishable objects don't go in SQL either so these classes would be
- different in any case.
+ <publish/> element.
"""
- attributes = ("action", "tag", "client_handle", "uri")
- payload_type = None
- payload = None
+ element_name = "publish"
def endElement(self, stack, name, text):
"""
- Handle a publishable element element.
+ Handle reading of the object to be published
"""
+
assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
if text:
- self.payload = self.payload_type(Base64 = text) # pylint: disable=E1102
+ self.der = text.decode("base64")
stack.pop()
def toXML(self):
"""
Generate XML element for publishable object.
"""
+
elt = self.make_elt()
- if self.payload:
- elt.text = self.payload.get_Base64()
+ if self.der is not None:
+ elt.text = self.der.encode("base64")
return elt
- def serve_dispatch(self, r_msg, cb, eb):
- """
- Action dispatch handler.
- """
- # pylint: disable=E0203
- try:
- if self.client is None:
- raise rpki.exceptions.BadQuery("Client query received on control channel")
- dispatch = { "publish" : self.serve_publish,
- "withdraw" : self.serve_withdraw }
- if self.action not in dispatch:
- raise rpki.exceptions.BadQuery("Unexpected query: action %s" % self.action)
- self.client.check_allowed_uri(self.uri)
- dispatch[self.action]()
- r_pdu = self.__class__()
- r_pdu.action = self.action
- r_pdu.tag = self.tag
- r_pdu.uri = self.uri
- r_msg.append(r_pdu)
- cb()
- except rpki.exceptions.NoObjectAtURI, e:
- # This can happen when we're cleaning up from a prior mess, so
- # we generate a <report_error/> PDU then carry on.
- r_msg.append(report_error_elt.from_exception(e, self.tag))
- cb()
-
- def serve_publish(self):
- """
- Publish an object.
- """
- logger.info("Publishing %s", self.payload.tracking_data(self.uri))
- filename = self.uri_to_filename()
- filename_tmp = filename + ".tmp"
- dirname = os.path.dirname(filename)
- if not os.path.isdir(dirname):
- os.makedirs(dirname)
- f = open(filename_tmp, "wb")
- f.write(self.payload.get_DER())
- f.close()
- os.rename(filename_tmp, filename)
-
- def serve_withdraw(self):
- """
- Withdraw an object, then recursively delete empty directories.
- """
- logger.info("Withdrawing %s", self.uri)
- filename = self.uri_to_filename()
- try:
- os.remove(filename)
- except OSError, e:
- if e.errno == errno.ENOENT:
- raise rpki.exceptions.NoObjectAtURI("No object published at %s" % self.uri)
- else:
- raise
- min_path_len = len(self.gctx.publication_base.rstrip("/"))
- dirname = os.path.dirname(filename)
- while len(dirname) > min_path_len:
- try:
- os.rmdir(dirname)
- except OSError:
- break
- else:
- dirname = os.path.dirname(dirname)
-
- def uri_to_filename(self):
- """
- Convert a URI to a local filename.
- """
- if not self.uri.startswith("rsync://"):
- raise rpki.exceptions.BadURISyntax(self.uri)
- path = self.uri.split("/")[3:]
- if not self.gctx.publication_multimodule:
- del path[0]
- path.insert(0, self.gctx.publication_base.rstrip("/"))
- filename = "/".join(path)
- if "/../" in filename or filename.endswith("/.."):
- raise rpki.exceptions.BadURISyntax(filename)
- return filename
-
- @classmethod
- def make_publish(cls, uri, obj, tag = None):
- """
- Construct a publication PDU.
- """
- assert cls.payload_type is not None and type(obj) is cls.payload_type
- return cls.make_pdu(action = "publish", uri = uri, payload = obj, tag = tag)
-
- @classmethod
- def make_withdraw(cls, uri, obj, tag = None):
- """
- Construct a withdrawal PDU.
- """
- assert cls.payload_type is not None and type(obj) is cls.payload_type
- return cls.make_pdu(action = "withdraw", uri = uri, tag = tag)
-
- def raise_if_error(self):
- """
- No-op, since this is not a <report_error/> PDU.
- """
- pass
-
-class certificate_elt(publication_object_elt):
- """
- <certificate/> element.
- """
-
- element_name = "certificate"
- payload_type = rpki.x509.X509
-
-class crl_elt(publication_object_elt):
- """
- <crl/> element.
- """
-
- element_name = "crl"
- payload_type = rpki.x509.CRL
-class manifest_elt(publication_object_elt):
+class withdraw_elt(base_publication_elt):
"""
- <manifest/> element.
+ <withdraw/> element.
"""
- element_name = "manifest"
- payload_type = rpki.x509.SignedManifest
-
-class roa_elt(publication_object_elt):
- """
- <roa/> element.
- """
+ element_name = "withdraw"
- element_name = "roa"
- payload_type = rpki.x509.ROA
-class ghostbuster_elt(publication_object_elt):
+class list_elt(base_publication_elt):
"""
- <ghostbuster/> element.
+ <list/> element.
"""
- element_name = "ghostbuster"
- payload_type = rpki.x509.Ghostbuster
+ element_name = "list"
-publication_object_elt.obj2elt = dict(
- (e.payload_type, e) for e in
- (certificate_elt, crl_elt, manifest_elt, roa_elt, ghostbuster_elt))
class report_error_elt(rpki.xml_utils.text_elt, publication_namespace):
"""
@@ -374,18 +127,11 @@ class report_error_elt(rpki.xml_utils.text_elt, publication_namespace):
attributes = ("tag", "error_code")
text_attribute = "error_text"
+ error_code = None
error_text = None
- @classmethod
- def from_exception(cls, e, tag = None):
- """
- Generate a <report_error/> element from an exception.
- """
- self = cls()
- self.tag = tag
- self.error_code = e.__class__.__name__
- self.error_text = str(e)
- return self
+ def __repr__(self):
+ return rpki.log.log_repr(self, self.error_code, self.error_text)
def __str__(self):
s = ""
@@ -400,11 +146,15 @@ class report_error_elt(rpki.xml_utils.text_elt, publication_namespace):
"""
Raise exception associated with this <report_error/> PDU.
"""
- t = rpki.exceptions.__dict__.get(self.error_code)
- if isinstance(t, type) and issubclass(t, rpki.exceptions.RPKI_Exception):
- raise t(getattr(self, "text", None))
- else:
- raise rpki.exceptions.BadPublicationReply("Unexpected response from pubd: %s" % self)
+
+ try:
+ e = getattr(rpki.exceptions, self.error_code)
+ if issubclass(e, rpki.exceptions.RPKI_Exception):
+ raise e(getattr(self, "text", None))
+ except (TypeError, AttributeError):
+ pass
+ raise rpki.exceptions.BadPublicationReply("Unexpected response from pubd: %s" % self)
+
class msg(rpki.xml_utils.msg, publication_namespace):
"""
@@ -417,38 +167,8 @@ class msg(rpki.xml_utils.msg, publication_namespace):
## @var pdus
# Dispatch table of PDUs for this protocol.
- pdus = dict((x.element_name, x) for x in
- (config_elt, client_elt, certificate_elt, crl_elt, manifest_elt, roa_elt, ghostbuster_elt, report_error_elt))
+ pdus = dict((x.element_name, x) for x in (publish_elt, withdraw_elt, list_elt, report_error_elt))
- def serve_top_level(self, gctx, client, cb):
- """
- Serve one msg PDU.
- """
- if not self.is_query():
- raise rpki.exceptions.BadQuery("Message type is not query")
- r_msg = self.__class__.reply()
-
- def loop(iterator, q_pdu):
-
- def fail(e):
- if not isinstance(e, rpki.exceptions.NotFound):
- logger.exception("Exception processing PDU %r", q_pdu)
- r_msg.append(report_error_elt.from_exception(e, q_pdu.tag))
- cb(r_msg)
-
- try:
- q_pdu.gctx = gctx
- q_pdu.client = client
- q_pdu.serve_dispatch(r_msg, iterator, fail)
- except (rpki.async.ExitNow, SystemExit):
- raise
- except Exception, e:
- fail(e)
-
- def done():
- cb(r_msg)
-
- rpki.async.iterator(self, loop, done)
class sax_handler(rpki.xml_utils.sax_handler):
"""
@@ -468,3 +188,11 @@ class cms_msg(rpki.x509.XML_CMS_object):
encoding = "us-ascii"
schema = rpki.relaxng.publication
saxify = sax_handler.saxify
+
+class cms_msg_no_sax(cms_msg):
+ """
+ Transition kludge: varient of cms_msg (q.v.) with SAX parsing disabled.
+ If and when we ditch SAX entirely, this will become cms_msg.
+ """
+
+ saxify = None
diff --git a/rpki/publication_control.py b/rpki/publication_control.py
new file mode 100644
index 00000000..eae96ccc
--- /dev/null
+++ b/rpki/publication_control.py
@@ -0,0 +1,227 @@
+# $Id$
+#
+# Copyright (C) 2013--2014 Dragon Research Labs ("DRL")
+# Portions copyright (C) 2009--2012 Internet Systems Consortium ("ISC")
+# Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notices and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+# ISC, OR ARIN 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.
+
+"""
+RPKI publication control protocol.
+
+Per IETF SIDR WG discussion, this is now separate from the publication
+protocol itself.
+"""
+
+import logging
+import rpki.resource_set
+import rpki.x509
+import rpki.sql
+import rpki.exceptions
+import rpki.xml_utils
+import rpki.http
+import rpki.up_down
+import rpki.relaxng
+import rpki.sundial
+import rpki.log
+
+logger = logging.getLogger(__name__)
+
+
+class publication_control_namespace(object):
+ """
+ XML namespace parameters for publication control protocol.
+ """
+
+ xmlns = rpki.relaxng.publication_control.xmlns
+ nsmap = rpki.relaxng.publication_control.nsmap
+
+
+class client_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, publication_control_namespace):
+ """
+ <client/> element.
+ """
+
+ element_name = "client"
+ attributes = ("action", "tag", "client_handle", "base_uri")
+ elements = ("bpki_cert", "bpki_glue")
+ booleans = ("clear_replay_protection",)
+
+ sql_template = rpki.sql.template(
+ "client",
+ "client_id",
+ "client_handle",
+ "base_uri",
+ ("bpki_cert", rpki.x509.X509),
+ ("bpki_glue", rpki.x509.X509),
+ ("last_cms_timestamp", rpki.sundial.datetime))
+
+ base_uri = None
+ bpki_cert = None
+ bpki_glue = None
+ last_cms_timestamp = None
+
+ def __repr__(self):
+ return rpki.log.log_repr(self, self.client_handle, self.base_uri)
+
+ @property
+ def objects(self):
+ return rpki.pubd.object_obj.sql_fetch_where(self.gctx, "client_id = %s", (self.client_id,))
+
+ def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
+ """
+ Extra server actions for client_elt.
+ """
+
+ if q_pdu.clear_replay_protection:
+ self.last_cms_timestamp = None
+ self.sql_mark_dirty()
+ cb()
+
+ def serve_fetch_one_maybe(self):
+ """
+ Find the client object on which a get, set, or destroy method
+ should operate, or which would conflict with a create method.
+ """
+
+ return self.sql_fetch_where1(self.gctx, "client_handle = %s", (self.client_handle,))
+
+ def serve_fetch_all(self):
+ """
+ Find client objects on which a list method should operate.
+ """
+
+ return self.sql_fetch_all(self.gctx)
+
+ def check_allowed_uri(self, uri):
+ """
+ Make sure that a target URI is within this client's allowed URI space.
+ """
+
+ if not uri.startswith(self.base_uri):
+ raise rpki.exceptions.ForbiddenURI
+
+ def raise_if_error(self):
+ """
+ No-op, because this isn't a <report_error/> PDU.
+ """
+
+ pass
+
+
+class report_error_elt(rpki.xml_utils.text_elt, publication_control_namespace):
+ """
+ <report_error/> element.
+ """
+
+ element_name = "report_error"
+ attributes = ("tag", "error_code")
+ text_attribute = "error_text"
+
+ error_text = None
+
+ @classmethod
+ def from_exception(cls, e, tag = None):
+ """
+ Generate a <report_error/> element from an exception.
+ """
+
+ self = cls()
+ self.tag = tag
+ self.error_code = e.__class__.__name__
+ self.error_text = str(e)
+ return self
+
+ def __str__(self):
+ s = ""
+ if getattr(self, "tag", None) is not None:
+ s += "[%s] " % self.tag
+ s += self.error_code
+ if getattr(self, "error_text", None) is not None:
+ s += ": " + self.error_text
+ return s
+
+ def raise_if_error(self):
+ """
+ Raise exception associated with this <report_error/> PDU.
+ """
+
+ t = rpki.exceptions.__dict__.get(self.error_code)
+ if isinstance(t, type) and issubclass(t, rpki.exceptions.RPKI_Exception):
+ raise t(getattr(self, "text", None))
+ else:
+ raise rpki.exceptions.BadPublicationReply("Unexpected response from pubd: %s" % self)
+
+
+class msg(rpki.xml_utils.msg, publication_control_namespace):
+ """
+ Publication control PDU.
+ """
+
+ ## @var version
+ # Protocol version
+ version = int(rpki.relaxng.publication_control.version)
+
+ ## @var pdus
+ # Dispatch table of PDUs for this protocol.
+ pdus = dict((x.element_name, x) for x in (client_elt, report_error_elt))
+
+ def serve_top_level(self, gctx, cb):
+ """
+ Serve one msg PDU.
+ """
+
+ if not self.is_query():
+ raise rpki.exceptions.BadQuery("Message type is not query")
+ r_msg = self.__class__.reply()
+
+ for q_pdu in self:
+
+ def next():
+ # Relic of asynch I/O structure, clean up eventually
+ pass
+
+ def fail(e):
+ if not isinstance(e, rpki.exceptions.NotFound):
+ logger.exception("Exception processing PDU %r", q_pdu)
+ r_msg.append(report_error_elt.from_exception(e, q_pdu.tag))
+ return cb(r_msg)
+
+ try:
+ q_pdu.gctx = gctx
+ q_pdu.serve_dispatch(r_msg, next, fail)
+ except Exception, e:
+ return fail(e)
+
+ return cb(r_msg)
+
+
+class sax_handler(rpki.xml_utils.sax_handler):
+ """
+ SAX handler for publication control protocol.
+ """
+
+ pdu = msg
+ name = "msg"
+ version = rpki.relaxng.publication_control.version
+
+
+class cms_msg(rpki.x509.XML_CMS_object):
+ """
+ Class to hold a CMS-signed publication control PDU.
+ """
+
+ encoding = "us-ascii"
+ schema = rpki.relaxng.publication_control
+ saxify = sax_handler.saxify
diff --git a/rpki/rcynic.py b/rpki/rcynic.py
index 10ad7516..a36e4a4e 100644
--- a/rpki/rcynic.py
+++ b/rpki/rcynic.py
@@ -53,6 +53,7 @@ class rcynic_object(object):
Print a bunch of object attributes, quietly ignoring any that
might be missing.
"""
+
for a in attrs:
try:
print "%s: %s" % (a.capitalize(), getattr(self, a))
@@ -63,6 +64,7 @@ class rcynic_object(object):
"""
Print common object attributes.
"""
+
self.show_attrs("filename", "uri", "status", "timestamp")
class rcynic_certificate(rcynic_object):
@@ -91,6 +93,7 @@ class rcynic_certificate(rcynic_object):
"""
Print certificate attributes.
"""
+
rcynic_object.show(self)
self.show_attrs("notBefore", "notAfter", "aia_uri", "sia_directory_uri", "resources")
@@ -128,6 +131,7 @@ class rcynic_roa(rcynic_object):
"""
Print ROA attributes.
"""
+
rcynic_object.show(self)
self.show_attrs("notBefore", "notAfter", "aia_uri", "resources", "asID")
if self.prefix_sets:
diff --git a/rpki/relaxng.py b/rpki/relaxng.py
index e43384e7..71e8ade4 100644
--- a/rpki/relaxng.py
+++ b/rpki/relaxng.py
@@ -6,7 +6,7 @@ from rpki.relaxng_parser import RelaxNGParser
## Parsed RelaxNG left_right schema
left_right = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: left-right-schema.rnc 5902 2014-07-18 16:37:04Z sra $
+ $Id: left-right.rnc 5903 2014-07-18 17:08:13Z sra $
RelaxNG schema for RPKI left-right protocol.
@@ -1102,7 +1102,7 @@ left_right = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
## Parsed RelaxNG myrpki schema
myrpki = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: myrpki.rnc 5757 2014-04-05 22:42:12Z sra $
+ $Id: myrpki.rnc 5876 2014-06-26 19:00:12Z sra $
RelaxNG schema for MyRPKI XML messages.
@@ -1481,11 +1481,11 @@ myrpki = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
-->
''')
-## @var publication
-## Parsed RelaxNG publication schema
-publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
+## @var publication_control
+## Parsed RelaxNG publication_control schema
+publication_control = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: publication-schema.rnc 5902 2014-07-18 16:37:04Z sra $
+ $Id: publication-control.rnc 5903 2014-07-18 17:08:13Z sra $
RelaxNG schema for RPKI publication protocol.
@@ -1506,7 +1506,7 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-->
-<grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+<grammar ns="http://www.hactrn.net/uris/rpki/publication-control/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<define name="version">
<value>1</value>
</define>
@@ -1540,26 +1540,12 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
</start>
<!-- PDUs allowed in a query -->
<define name="query_elt">
- <choice>
- <ref name="config_query"/>
- <ref name="client_query"/>
- <ref name="certificate_query"/>
- <ref name="crl_query"/>
- <ref name="manifest_query"/>
- <ref name="roa_query"/>
- <ref name="ghostbuster_query"/>
- </choice>
+ <ref name="client_query"/>
</define>
<!-- PDUs allowed in a reply -->
<define name="reply_elt">
<choice>
- <ref name="config_reply"/>
<ref name="client_reply"/>
- <ref name="certificate_reply"/>
- <ref name="crl_reply"/>
- <ref name="manifest_reply"/>
- <ref name="roa_reply"/>
- <ref name="ghostbuster_reply"/>
<ref name="report_error_reply"/>
</choice>
</define>
@@ -1603,60 +1589,7 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<param name="pattern">[\-_A-Za-z0-9/]+</param>
</data>
</define>
- <!--
- <config/> element (use restricted to repository operator)
- config_handle attribute, create, list, and destroy commands omitted deliberately, see code for details
- -->
- <define name="config_payload">
- <optional>
- <element name="bpki_crl">
- <ref name="base64"/>
- </element>
- </optional>
- </define>
- <define name="config_query" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>set</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="config_payload"/>
- </element>
- </define>
- <define name="config_reply" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>set</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- </element>
- </define>
- <define name="config_query" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>get</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- </element>
- </define>
- <define name="config_reply" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>get</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="config_payload"/>
- </element>
- </define>
- <!-- <client/> element (use restricted to repository operator) -->
+ <!-- <client/> element -->
<define name="client_handle">
<attribute name="client_handle">
<ref name="object_handle"/>
@@ -1801,242 +1734,217 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<ref name="client_handle"/>
</element>
</define>
- <!-- <certificate/> element -->
- <define name="certificate_query" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
+ <!-- <report_error/> element -->
+ <define name="error">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
</define>
- <define name="certificate_reply" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>publish</value>
- </attribute>
+ <define name="report_error_reply">
+ <element name="report_error">
<optional>
<ref name="tag"/>
</optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="certificate_query" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>withdraw</value>
+ <attribute name="error_code">
+ <ref name="error"/>
</attribute>
<optional>
- <ref name="tag"/>
+ <data type="string">
+ <param name="maxLength">512000</param>
+ </data>
</optional>
- <ref name="uri"/>
</element>
</define>
- <define name="certificate_reply" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
+</grammar>
+<!--
+ Local Variables:
+ indent-tabs-mode: nil
+ comment-start: "# "
+ comment-start-skip: "#[ \t]*"
+ End:
+-->
+''')
+
+## @var publication
+## Parsed RelaxNG publication schema
+publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ $Id: publication.rnc 5896 2014-07-15 19:34:32Z sra $
+
+ RelaxNG schema for RPKI publication protocol, from current I-D.
+
+ Copyright (c) 2014 IETF Trust and the persons identified as authors
+ of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Internet Society, IETF or IETF Trust, nor the
+ names of specific contributors, may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+-->
+<grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+ <!-- This is version 3 of the protocol. -->
+ <define name="version">
+ <value>3</value>
</define>
- <!-- <crl/> element -->
- <define name="crl_query" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>publish</value>
+ <!-- Top level PDU is either a query or a reply. -->
+ <start combine="choice">
+ <element name="msg">
+ <attribute name="version">
+ <ref name="version"/>
</attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
- </define>
- <define name="crl_reply" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>publish</value>
+ <attribute name="type">
+ <value>query</value>
</attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
+ <zeroOrMore>
+ <ref name="query_elt"/>
+ </zeroOrMore>
</element>
- </define>
- <define name="crl_query" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>withdraw</value>
+ </start>
+ <start combine="choice">
+ <element name="msg">
+ <attribute name="version">
+ <ref name="version"/>
</attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="crl_reply" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>withdraw</value>
+ <attribute name="type">
+ <value>reply</value>
</attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
+ <zeroOrMore>
+ <ref name="reply_elt"/>
+ </zeroOrMore>
</element>
+ </start>
+ <!-- PDUs allowed in queries and replies. -->
+ <define name="query_elt">
+ <choice>
+ <ref name="publish_query"/>
+ <ref name="withdraw_query"/>
+ <ref name="list_query"/>
+ </choice>
</define>
- <!-- <manifest/> element -->
- <define name="manifest_query" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
+ <define name="reply_elt">
+ <choice>
+ <ref name="publish_reply"/>
+ <ref name="withdraw_reply"/>
+ <ref name="list_reply"/>
+ <ref name="report_error_reply"/>
+ </choice>
</define>
- <define name="manifest_reply" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
+ <!-- Tag attributes for bulk operations. -->
+ <define name="tag">
+ <attribute name="tag">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
+ </attribute>
</define>
- <define name="manifest_query" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
+ <!-- Base64 encoded DER stuff. -->
+ <define name="base64">
+ <data type="base64Binary"/>
</define>
- <define name="manifest_reply" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
+ <!-- Publication URIs. -->
+ <define name="uri">
+ <attribute name="uri">
+ <data type="anyURI">
+ <param name="maxLength">4096</param>
+ </data>
+ </attribute>
</define>
- <!-- <roa/> element -->
- <define name="roa_query" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
+ <!-- Digest of objects being withdrawn -->
+ <define name="hash">
+ <attribute name="hash">
+ <data type="string">
+ <param name="pattern">[0-9a-fA-F]+</param>
+ </data>
+ </attribute>
</define>
- <define name="roa_reply" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>publish</value>
- </attribute>
+ <!-- Error codes. -->
+ <define name="error">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
+ </define>
+ <!-- <publish/> element -->
+ <define name="publish_query">
+ <element name="publish">
<optional>
<ref name="tag"/>
</optional>
<ref name="uri"/>
- </element>
- </define>
- <define name="roa_query" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
<optional>
- <ref name="tag"/>
+ <ref name="hash"/>
</optional>
- <ref name="uri"/>
+ <ref name="base64"/>
</element>
</define>
- <define name="roa_reply" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
+ <define name="publish_reply">
+ <element name="publish">
<optional>
<ref name="tag"/>
</optional>
<ref name="uri"/>
</element>
</define>
- <!-- <ghostbuster/> element -->
- <define name="ghostbuster_query" combine="choice">
- <element name="ghostbuster">
- <attribute name="action">
- <value>publish</value>
- </attribute>
+ <!-- <withdraw/> element -->
+ <define name="withdraw_query">
+ <element name="withdraw">
<optional>
<ref name="tag"/>
</optional>
<ref name="uri"/>
- <ref name="base64"/>
+ <ref name="hash"/>
</element>
</define>
- <define name="ghostbuster_reply" combine="choice">
- <element name="ghostbuster">
- <attribute name="action">
- <value>publish</value>
- </attribute>
+ <define name="withdraw_reply">
+ <element name="withdraw">
<optional>
<ref name="tag"/>
</optional>
<ref name="uri"/>
</element>
</define>
- <define name="ghostbuster_query" combine="choice">
- <element name="ghostbuster">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
+ <!-- <list/> element -->
+ <define name="list_query">
+ <element name="list">
<optional>
<ref name="tag"/>
</optional>
- <ref name="uri"/>
</element>
</define>
- <define name="ghostbuster_reply" combine="choice">
- <element name="ghostbuster">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
+ <define name="list_reply">
+ <element name="list">
<optional>
<ref name="tag"/>
</optional>
<ref name="uri"/>
+ <ref name="hash"/>
</element>
</define>
<!-- <report_error/> element -->
- <define name="error">
- <data type="token">
- <param name="maxLength">1024</param>
- </data>
- </define>
<define name="report_error_reply">
<element name="report_error">
<optional>
@@ -2066,7 +1974,7 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
## Parsed RelaxNG router_certificate schema
router_certificate = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: router-certificate-schema.rnc 5757 2014-04-05 22:42:12Z sra $
+ $Id: router-certificate.rnc 5881 2014-07-03 16:55:02Z sra $
RelaxNG schema for BGPSEC router certificate interchange format.
@@ -2164,11 +2072,178 @@ router_certificate = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
-->
''')
+## @var rrdp
+## Parsed RelaxNG rrdp schema
+rrdp = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ $Id: rrdp.rnc 5888 2014-07-09 05:39:54Z sra $
+
+ RelaxNG schema for RPKI Repository Delta Protocol (RRDP).
+
+ Copyright (C) 2014 Dragon Research Labs ("DRL")
+
+ Permission to use, copy, modify, and 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.
+-->
+<grammar ns="http://www.ripe.net/rpki/rrdp" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+ <define name="version">
+ <data type="positiveInteger">
+ <param name="maxInclusive">1</param>
+ </data>
+ </define>
+ <define name="serial">
+ <data type="nonNegativeInteger"/>
+ </define>
+ <define name="uri">
+ <data type="anyURI"/>
+ </define>
+ <define name="uuid">
+ <data type="string">
+ <param name="pattern">[\-0-9a-fA-F]+</param>
+ </data>
+ </define>
+ <define name="hash">
+ <data type="string">
+ <param name="pattern">[0-9a-fA-F]+</param>
+ </data>
+ </define>
+ <define name="base64">
+ <data type="base64Binary"/>
+ </define>
+ <!-- Notification file: lists current snapshots and deltas -->
+ <start combine="choice">
+ <element name="notification">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="session_id">
+ <ref name="uuid"/>
+ </attribute>
+ <attribute name="serial">
+ <ref name="serial"/>
+ </attribute>
+ <element name="snapshot">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </element>
+ <zeroOrMore>
+ <element name="delta">
+ <attribute name="from">
+ <ref name="serial"/>
+ </attribute>
+ <attribute name="to">
+ <ref name="serial"/>
+ </attribute>
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </element>
+ </zeroOrMore>
+ </element>
+ </start>
+ <!-- Snapshot segment: think DNS AXFR. -->
+ <start combine="choice">
+ <element name="snapshot">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="session_id">
+ <ref name="uuid"/>
+ </attribute>
+ <attribute name="serial">
+ <ref name="serial"/>
+ </attribute>
+ <zeroOrMore>
+ <element name="publish">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <ref name="base64"/>
+ </element>
+ </zeroOrMore>
+ </element>
+ </start>
+ <!-- Delta segment: think DNS IXFR. -->
+ <start combine="choice">
+ <element name="deltas">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="session_id">
+ <ref name="uuid"/>
+ </attribute>
+ <attribute name="from">
+ <ref name="serial"/>
+ </attribute>
+ <attribute name="to">
+ <ref name="serial"/>
+ </attribute>
+ <oneOrMore>
+ <element name="delta">
+ <attribute name="serial">
+ <ref name="serial"/>
+ </attribute>
+ <oneOrMore>
+ <ref name="delta_element"/>
+ </oneOrMore>
+ </element>
+ </oneOrMore>
+ </element>
+ </start>
+ <define name="delta_element" combine="choice">
+ <element name="publish">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <optional>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </optional>
+ <ref name="base64"/>
+ </element>
+ </define>
+ <define name="delta_element" combine="choice">
+ <element name="withdraw">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </element>
+ </define>
+</grammar>
+<!--
+ Local Variables:
+ indent-tabs-mode: nil
+ comment-start: "# "
+ comment-start-skip: "#[ \t]*"
+ End:
+-->
+''')
+
## @var up_down
## Parsed RelaxNG up_down schema
up_down = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: up-down-schema.rnc 5757 2014-04-05 22:42:12Z sra $
+ $Id: up-down.rnc 5881 2014-07-03 16:55:02Z sra $
RelaxNG schema for the up-down protocol, extracted from RFC 6492.
diff --git a/rpki/resource_set.py b/rpki/resource_set.py
index fea6ad2d..130bf4e7 100644
--- a/rpki/resource_set.py
+++ b/rpki/resource_set.py
@@ -86,6 +86,7 @@ class resource_range_as(resource_range):
"""
Convert a resource_range_as to string format.
"""
+
if self.min == self.max:
return str(self.min)
else:
@@ -96,6 +97,7 @@ class resource_range_as(resource_range):
"""
Parse ASN resource range from text (eg, XML attributes).
"""
+
r = re_asn_range.match(x)
if r:
return cls(long(r.group(1)), long(r.group(2)))
@@ -107,6 +109,7 @@ class resource_range_as(resource_range):
"""
Construct ASN range from strings.
"""
+
if b is None:
b = a
return cls(long(a), long(b))
@@ -133,6 +136,7 @@ class resource_range_ip(resource_range):
prefix. Returns prefix length if it can, otherwise raises
MustBePrefix exception.
"""
+
mask = self.min ^ self.max
if self.min & mask != 0:
raise rpki.exceptions.MustBePrefix
@@ -154,6 +158,7 @@ class resource_range_ip(resource_range):
the logic in one place. This property is useful primarily in
context where catching an exception isn't practical.
"""
+
try:
self.prefixlen()
return True
@@ -164,6 +169,7 @@ class resource_range_ip(resource_range):
"""
Convert a resource_range_ip to string format.
"""
+
try:
return str(self.min) + "/" + str(self.prefixlen())
except rpki.exceptions.MustBePrefix:
@@ -174,6 +180,7 @@ class resource_range_ip(resource_range):
"""
Parse IP address range or prefix from text (eg, XML attributes).
"""
+
r = re_address_range.match(x)
if r:
return cls.from_strings(r.group(1), r.group(2))
@@ -192,6 +199,7 @@ class resource_range_ip(resource_range):
"""
Construct a resource range corresponding to a prefix.
"""
+
assert isinstance(prefix, rpki.POW.IPAddress) and isinstance(prefixlen, (int, long))
assert prefixlen >= 0 and prefixlen <= prefix.bits, "Nonsensical prefix length: %s" % prefixlen
mask = (1 << (prefix.bits - prefixlen)) - 1
@@ -203,6 +211,7 @@ class resource_range_ip(resource_range):
Chop up a resource_range_ip into ranges that can be represented as
prefixes.
"""
+
try:
self.prefixlen()
result.append(self)
@@ -226,6 +235,7 @@ class resource_range_ip(resource_range):
"""
Construct IP address range from strings.
"""
+
if b is None:
b = a
a = rpki.POW.IPAddress(a)
@@ -300,6 +310,7 @@ class resource_set(list):
"""
Initialize a resource_set.
"""
+
list.__init__(self)
if isinstance(ini, (int, long)):
ini = str(ini)
@@ -317,6 +328,7 @@ class resource_set(list):
"""
Whack this resource_set into canonical form.
"""
+
assert not self.inherit or len(self) == 0
if not self.canonical:
self.sort()
@@ -339,6 +351,7 @@ class resource_set(list):
"""
Wrapper around list.append() (q.v.) to reset canonical flag.
"""
+
list.append(self, item)
self.canonical = False
@@ -346,6 +359,7 @@ class resource_set(list):
"""
Wrapper around list.extend() (q.v.) to reset canonical flag.
"""
+
list.extend(self, item)
self.canonical = False
@@ -353,6 +367,7 @@ class resource_set(list):
"""
Convert a resource_set to string format.
"""
+
if self.inherit:
return inherit_token
else:
@@ -428,6 +443,7 @@ class resource_set(list):
"""
Set intersection for resource sets.
"""
+
return self._comm(other)[2]
__and__ = intersection
@@ -436,6 +452,7 @@ class resource_set(list):
"""
Set difference for resource sets.
"""
+
return self._comm(other)[0]
__sub__ = difference
@@ -444,6 +461,7 @@ class resource_set(list):
"""
Set symmetric difference (XOR) for resource sets.
"""
+
com = self._comm(other)
return com[0] | com[1]
@@ -453,6 +471,7 @@ class resource_set(list):
"""
Set membership test for resource sets.
"""
+
assert not self.inherit
self.canonize()
if not self:
@@ -479,6 +498,7 @@ class resource_set(list):
"""
Test whether self is a subset (possibly improper) of other.
"""
+
for i in self:
if not other.contains(i):
return False
@@ -490,6 +510,7 @@ class resource_set(list):
"""
Test whether self is a superset (possibly improper) of other.
"""
+
return other.issubset(self)
__ge__ = issuperset
@@ -506,6 +527,7 @@ class resource_set(list):
we can't know the answer here. This is also consistent with __nonzero__
which returns True for inherit sets, and False for empty sets.
"""
+
return self.inherit or other.inherit or list.__ne__(self, other)
def __eq__(self, other):
@@ -516,6 +538,7 @@ class resource_set(list):
Tests whether or not this set is empty. Note that sets with the inherit
bit set are considered non-empty, despite having zero length.
"""
+
return self.inherit or len(self)
@classmethod
@@ -553,6 +576,7 @@ class resource_set(list):
a backwards compatability wrapper, real functionality is now part
of the range classes.
"""
+
return cls.range_type.parse_str(s)
class resource_set_as(resource_set):
@@ -577,6 +601,7 @@ class resource_set_ip(resource_set):
"""
Convert from a resource set to a ROA prefix set.
"""
+
prefix_ranges = []
for r in self:
r.chop_into_prefixes(prefix_ranges)
@@ -632,6 +657,7 @@ class resource_bag(object):
"""
True iff self is oversized with respect to other.
"""
+
return not self.asn.issubset(other.asn) or \
not self.v4.issubset(other.v4) or \
not self.v6.issubset(other.v6)
@@ -640,6 +666,7 @@ class resource_bag(object):
"""
True iff self is undersized with respect to other.
"""
+
return not other.asn.issubset(self.asn) or \
not other.v4.issubset(self.v4) or \
not other.v6.issubset(self.v6)
@@ -650,6 +677,7 @@ class resource_bag(object):
Build a resource bag that just inherits everything from its
parent.
"""
+
self = cls()
self.asn = resource_set_as()
self.v4 = resource_set_ipv4()
@@ -665,6 +693,7 @@ class resource_bag(object):
Parse a comma-separated text string into a resource_bag. Not
particularly efficient, fix that if and when it becomes an issue.
"""
+
asns = []
v4s = []
v6s = []
@@ -689,6 +718,7 @@ class resource_bag(object):
temporary: in the long run, we should be using rpki.POW.IPAddress
rather than long here.
"""
+
asn = inherit_token if resources[0] == "inherit" else [resource_range_as( r[0], r[1]) for r in resources[0] or ()]
v4 = inherit_token if resources[1] == "inherit" else [resource_range_ipv4(r[0], r[1]) for r in resources[1] or ()]
v6 = inherit_token if resources[2] == "inherit" else [resource_range_ipv6(r[0], r[1]) for r in resources[2] or ()]
@@ -700,6 +730,7 @@ class resource_bag(object):
"""
True iff all resource sets in this bag are empty.
"""
+
return not self.asn and not self.v4 and not self.v6
def __nonzero__(self):
@@ -719,6 +750,7 @@ class resource_bag(object):
Compute intersection with another resource_bag. valid_until
attribute (if any) inherits from self.
"""
+
return self.__class__(self.asn & other.asn,
self.v4 & other.v4,
self.v6 & other.v6,
@@ -731,6 +763,7 @@ class resource_bag(object):
Compute union with another resource_bag. valid_until attribute
(if any) inherits from self.
"""
+
return self.__class__(self.asn | other.asn,
self.v4 | other.v4,
self.v6 | other.v6,
@@ -743,6 +776,7 @@ class resource_bag(object):
Compute difference against another resource_bag. valid_until
attribute (if any) inherits from self
"""
+
return self.__class__(self.asn - other.asn,
self.v4 - other.v4,
self.v6 - other.v6,
@@ -755,6 +789,7 @@ class resource_bag(object):
Compute symmetric difference against another resource_bag.
valid_until attribute (if any) inherits from self
"""
+
return self.__class__(self.asn ^ other.asn,
self.v4 ^ other.v4,
self.v6 ^ other.v6,
@@ -816,6 +851,7 @@ class roa_prefix(object):
Initialize a ROA prefix. max_prefixlen is optional and defaults
to prefixlen. max_prefixlen must not be smaller than prefixlen.
"""
+
if max_prefixlen is None:
max_prefixlen = prefixlen
assert max_prefixlen >= prefixlen, "Bad max_prefixlen: %d must not be shorter than %d" % (max_prefixlen, prefixlen)
@@ -828,6 +864,7 @@ class roa_prefix(object):
Compare two ROA prefix objects. Comparision is based on prefix,
prefixlen, and max_prefixlen, in that order.
"""
+
assert self.__class__ is other.__class__
return (cmp(self.prefix, other.prefix) or
cmp(self.prefixlen, other.prefixlen) or
@@ -837,6 +874,7 @@ class roa_prefix(object):
"""
Convert a ROA prefix to string format.
"""
+
if self.prefixlen == self.max_prefixlen:
return str(self.prefix) + "/" + str(self.prefixlen)
else:
@@ -848,24 +886,28 @@ class roa_prefix(object):
object. This is an irreversable transformation because it loses
the max_prefixlen attribute, nothing we can do about that.
"""
+
return self.range_type.make_prefix(self.prefix, self.prefixlen)
def min(self):
"""
Return lowest address covered by prefix.
"""
+
return self.prefix
def max(self):
"""
Return highest address covered by prefix.
"""
+
return self.prefix | ((1 << (self.prefix.bits - self.prefixlen)) - 1)
def to_POW_roa_tuple(self):
"""
Convert a resource_range_ip to rpki.POW.ROA.setPrefixes() format.
"""
+
return self.prefix, self.prefixlen, self.max_prefixlen
@classmethod
@@ -873,6 +915,7 @@ class roa_prefix(object):
"""
Parse ROA prefix from text (eg, an XML attribute).
"""
+
r = re_prefix_with_maxlen.match(x)
if r:
return cls(rpki.POW.IPAddress(r.group(1)), int(r.group(2)), int(r.group(3)))
@@ -910,6 +953,7 @@ class roa_prefix_set(list):
"""
Initialize a ROA prefix set.
"""
+
list.__init__(self)
if isinstance(ini, str) and len(ini):
self.extend(self.parse_str(s) for s in ini.split(","))
@@ -923,6 +967,7 @@ class roa_prefix_set(list):
"""
Convert a ROA prefix set to string format.
"""
+
return ",".join(str(x) for x in self)
@classmethod
@@ -931,6 +976,7 @@ class roa_prefix_set(list):
Parse ROA prefix from text (eg, an XML attribute).
This method is a backwards compatability shim.
"""
+
return cls.prefix_type.parse_str(s)
def to_resource_set(self):
@@ -942,6 +988,7 @@ class roa_prefix_set(list):
a more efficient way to do this, but start by getting the output
right before worrying about making it fast or pretty.
"""
+
r = self.resource_set_type()
s = self.resource_set_type()
s.append(None)
@@ -982,6 +1029,7 @@ class roa_prefix_set(list):
"""
Convert ROA prefix set to form used by rpki.POW.ROA.setPrefixes().
"""
+
if self:
return tuple(a.to_POW_roa_tuple() for a in self)
else:
diff --git a/rpki/rootd.py b/rpki/rootd.py
index e912a846..6b6aa0fa 100644
--- a/rpki/rootd.py
+++ b/rpki/rootd.py
@@ -46,15 +46,13 @@ rootd = None
class list_pdu(rpki.up_down.list_pdu):
def serve_pdu(self, q_msg, r_msg, ignored, callback, errback):
r_msg.payload = rpki.up_down.list_response_pdu()
- rootd.compose_response(r_msg)
- callback()
+ rootd.compose_response(r_msg, callback, errback)
class issue_pdu(rpki.up_down.issue_pdu):
def serve_pdu(self, q_msg, r_msg, ignored, callback, errback):
self.pkcs10.check_valid_request_ca()
r_msg.payload = rpki.up_down.issue_response_pdu()
- rootd.compose_response(r_msg, self.pkcs10)
- callback()
+ rootd.compose_response(r_msg, callback, errback, self.pkcs10)
class revoke_pdu(rpki.up_down.revoke_pdu):
def serve_pdu(self, q_msg, r_msg, ignored, callback, errback):
@@ -68,14 +66,15 @@ class revoke_pdu(rpki.up_down.revoke_pdu):
raise rpki.exceptions.NotInDatabase
logger.debug("Revoking certificate %s", self.ski)
now = rpki.sundial.now()
+ pubd_msg = rpki.publication.msg.query()
rootd.revoke_subject_cert(now)
rootd.del_subject_cert()
rootd.del_subject_pkcs10()
- rootd.generate_crl_and_manifest(now)
r_msg.payload = rpki.up_down.revoke_response_pdu()
r_msg.payload.class_name = self.class_name
r_msg.payload.ski = self.ski
- callback()
+ rootd.generate_crl_and_manifest(now, pubd_msg)
+ rootd.publish(callback, errback, pubd_msg)
class error_response_pdu(rpki.up_down.error_response_pdu):
exceptions = rpki.up_down.error_response_pdu.exceptions.copy()
@@ -84,23 +83,18 @@ class error_response_pdu(rpki.up_down.error_response_pdu):
class message_pdu(rpki.up_down.message_pdu):
- name2type = {
- "list" : list_pdu,
- "list_response" : rpki.up_down.list_response_pdu,
- "issue" : issue_pdu,
- "issue_response" : rpki.up_down.issue_response_pdu,
- "revoke" : revoke_pdu,
- "revoke_response" : rpki.up_down.revoke_response_pdu,
- "error_response" : error_response_pdu }
+ name2type = dict(
+ rpki.up_down.message_pdu.name2type,
+ list = list_pdu,
+ issue = issue_pdu,
+ revoke = revoke_pdu,
+ error_response = error_response_pdu)
- type2name = dict((v, k) for k, v in name2type.items())
+ type2name = dict((v, k) for k, v in name2type.iteritems())
error_pdu_type = error_response_pdu
def log_query(self, child):
- """
- Log query we're handling.
- """
logger.info("Serving %s query", self.type)
class sax_handler(rpki.up_down.sax_handler):
@@ -111,34 +105,30 @@ class cms_msg(rpki.up_down.cms_msg):
class main(object):
- def get_root_cert(self):
- logger.debug("Read root cert %s", self.rpki_root_cert_file)
- self.rpki_root_cert = rpki.x509.X509(Auto_file = self.rpki_root_cert_file)
def root_newer_than_subject(self):
- return os.stat(self.rpki_root_cert_file).st_mtime > \
- os.stat(os.path.join(self.rpki_root_dir, self.rpki_subject_cert)).st_mtime
+ return self.rpki_root_cert.mtime > os.stat(self.rpki_subject_cert_file).st_mtime
+
def get_subject_cert(self):
- filename = os.path.join(self.rpki_root_dir, self.rpki_subject_cert)
try:
- x = rpki.x509.X509(Auto_file = filename)
- logger.debug("Read subject cert %s", filename)
+ x = rpki.x509.X509(Auto_file = self.rpki_subject_cert_file)
+ logger.debug("Read subject cert %s", self.rpki_subject_cert_file)
return x
except IOError:
return None
+
def set_subject_cert(self, cert):
- filename = os.path.join(self.rpki_root_dir, self.rpki_subject_cert)
- logger.debug("Writing subject cert %s, SKI %s", filename, cert.hSKI())
- f = open(filename, "wb")
- f.write(cert.get_DER())
- f.close()
+ logger.debug("Writing subject cert %s, SKI %s", self.rpki_subject_cert_file, cert.hSKI())
+ with open(self.rpki_subject_cert_file, "wb") as f:
+ f.write(cert.get_DER())
+
def del_subject_cert(self):
- filename = os.path.join(self.rpki_root_dir, self.rpki_subject_cert)
- logger.debug("Deleting subject cert %s", filename)
- os.remove(filename)
+ logger.debug("Deleting subject cert %s", self.rpki_subject_cert_file)
+ os.remove(self.rpki_subject_cert_file)
+
def get_subject_pkcs10(self):
try:
@@ -148,11 +138,12 @@ class main(object):
except IOError:
return None
+
def set_subject_pkcs10(self, pkcs10):
logger.debug("Writing subject PKCS #10 %s", self.rpki_subject_pkcs10)
- f = open(self.rpki_subject_pkcs10, "wb")
- f.write(pkcs10.get_DER())
- f.close()
+ with open(self.rpki_subject_pkcs10, "wb") as f:
+ f.write(pkcs10.get_DER())
+
def del_subject_pkcs10(self):
logger.debug("Deleting subject PKCS #10 %s", self.rpki_subject_pkcs10)
@@ -161,9 +152,11 @@ class main(object):
except OSError:
pass
+
def issue_subject_cert_maybe(self, new_pkcs10):
now = rpki.sundial.now()
subject_cert = self.get_subject_cert()
+ hash = None if subject_cert is None else rpki.x509.sha256(subject_cert.get_DER()).encode("hex")
old_pkcs10 = self.get_subject_pkcs10()
if new_pkcs10 is not None and new_pkcs10 != old_pkcs10:
self.set_subject_pkcs10(new_pkcs10)
@@ -179,17 +172,16 @@ class main(object):
logger.debug("Root certificate has changed, regenerating subject")
self.revoke_subject_cert(now)
subject_cert = None
- self.get_root_cert()
if subject_cert is not None:
- return subject_cert
+ return subject_cert, None
pkcs10 = old_pkcs10 if new_pkcs10 is None else new_pkcs10
if pkcs10 is None:
logger.debug("No PKCS #10 request, can't generate subject certificate yet")
- return None
+ return None, None
resources = self.rpki_root_cert.get_3779resources()
notAfter = now + self.rpki_subject_lifetime
logger.info("Generating subject cert %s with resources %s, expires %s",
- self.rpki_base_uri + self.rpki_subject_cert, resources, notAfter)
+ self.rpki_subject_cert_uri, resources, notAfter)
req_key = pkcs10.getPublicKey()
req_sia = pkcs10.get_SIA()
self.next_serial_number()
@@ -199,15 +191,21 @@ class main(object):
serial = self.serial_number,
sia = req_sia,
aia = self.rpki_root_cert_uri,
- crldp = self.rpki_base_uri + self.rpki_root_crl,
+ crldp = self.rpki_root_crl_uri,
resources = resources,
notBefore = now,
notAfter = notAfter)
self.set_subject_cert(subject_cert)
- self.generate_crl_and_manifest(now)
- return subject_cert
+ pubd_msg = rpki.publication.msg.query()
+ pubd_msg.append(rpki.publication.publish_elt.make_pdu(
+ uri = self.rpki_subject_cert_uri,
+ hash = hash,
+ der = subject_cert.get_DER()))
+ self.generate_crl_and_manifest(now, pubd_msg)
+ return subject_cert, pubd_msg
+
- def generate_crl_and_manifest(self, now):
+ def generate_crl_and_manifest(self, now, pubd_msg):
subject_cert = self.get_subject_cert()
self.next_serial_number()
self.next_crl_number()
@@ -220,23 +218,26 @@ class main(object):
thisUpdate = now,
nextUpdate = now + self.rpki_subject_regen,
revokedCertificates = self.revoked)
- fn = os.path.join(self.rpki_root_dir, self.rpki_root_crl)
- logger.debug("Writing CRL %s", fn)
- f = open(fn, "wb")
- f.write(crl.get_DER())
- f.close()
- manifest_content = [(self.rpki_root_crl, crl)]
+ hash = self.read_hash_maybe(self.rpki_root_crl_file)
+ logger.debug("Writing CRL %s", self.rpki_root_crl_file)
+ with open(self.rpki_root_crl_file, "wb") as f:
+ f.write(crl.get_DER())
+ pubd_msg.append(rpki.publication.publish_elt.make_pdu(
+ uri = self.rpki_root_crl_uri,
+ hash = hash,
+ der = crl.get_DER()))
+ manifest_content = [(os.path.basename(self.rpki_root_crl_uri), crl)]
if subject_cert is not None:
- manifest_content.append((self.rpki_subject_cert, subject_cert))
+ manifest_content.append((os.path.basename(self.rpki_subject_cert_uri), subject_cert))
manifest_resources = rpki.resource_set.resource_bag.from_inheritance()
manifest_keypair = rpki.x509.RSA.generate()
manifest_cert = self.rpki_root_cert.issue(
keypair = self.rpki_root_key,
subject_key = manifest_keypair.get_public(),
serial = self.serial_number,
- sia = (None, None, self.rpki_base_uri + self.rpki_root_manifest),
+ sia = (None, None, self.rpki_root_manifest_uri),
aia = self.rpki_root_cert_uri,
- crldp = self.rpki_base_uri + self.rpki_root_crl,
+ crldp = self.rpki_root_crl_uri,
resources = manifest_resources,
notBefore = now,
notAfter = now + self.rpki_subject_lifetime,
@@ -248,17 +249,42 @@ class main(object):
names_and_objs = manifest_content,
keypair = manifest_keypair,
certs = manifest_cert)
- fn = os.path.join(self.rpki_root_dir, self.rpki_root_manifest)
- logger.debug("Writing manifest %s", fn)
- f = open(fn, "wb")
- f.write(manifest.get_DER())
- f.close()
+ hash = self.read_hash_maybe(self.rpki_root_manifest_file)
+ logger.debug("Writing manifest %s", self.rpki_root_manifest_file)
+ with open(self.rpki_root_manifest_file, "wb") as f:
+ f.write(manifest.get_DER())
+ pubd_msg.append(rpki.publication.publish_elt.make_pdu(
+ uri = self.rpki_root_manifest_uri,
+ hash = hash,
+ der = manifest.get_DER()))
+ hash = rpki.x509.sha256(self.rpki_root_cert.get_DER()).encode("hex")
+ if hash != self.rpki_root_cert_hash:
+ pubd_msg.append(rpki.publication.publish_elt.make_pdu(
+ uri = self.rpki_root_cert_uri,
+ hash = self.rpki_root_cert_hash,
+ der = self.rpki_root_cert.get_DER()))
+ self.rpki_root_cert_hash = hash
+
+
+ @staticmethod
+ def read_hash_maybe(fn):
+ """
+ Return hash of an existing object, or None.
+ """
+
+ try:
+ with open(fn, "rb") as f:
+ return rpki.x509.sha256(f.read()).encode("hex")
+ except IOError:
+ return None
+
def revoke_subject_cert(self, now):
self.revoked.append((self.get_subject_cert().getSerial(), now))
- def compose_response(self, r_msg, pkcs10 = None):
- subject_cert = self.issue_subject_cert_maybe(pkcs10)
+
+ def compose_response(self, r_msg, callback, errback, pkcs10 = None):
+ subject_cert, pubd_msg = self.issue_subject_cert_maybe(pkcs10)
rc = rpki.up_down.class_elt()
rc.class_name = self.rpki_class_name
rc.cert_url = rpki.up_down.multi_uri(self.rpki_root_cert_uri)
@@ -267,14 +293,78 @@ class main(object):
r_msg.payload.classes.append(rc)
if subject_cert is not None:
rc.certs.append(rpki.up_down.certificate_elt())
- rc.certs[0].cert_url = rpki.up_down.multi_uri(self.rpki_base_uri + self.rpki_subject_cert)
+ rc.certs[0].cert_url = rpki.up_down.multi_uri(self.rpki_subject_cert_uri)
rc.certs[0].cert = subject_cert
+ self.publish(callback, errback, pubd_msg)
+
+
+ def publish(self, callback, errback, q_msg):
+
+ def done(r_msg):
+ if len(q_msg) != len(r_msg):
+ raise rpki.exceptions.BadPublicationReply("Wrong number of response PDUs from pubd: sent %r, got %r" % (q_msg, r_msg))
+ callback()
+
+ def fix_hashes(r_msg):
+ published_hash = dict((r_pdu.uri, r_pdu.hash) for r_pdu in r_msg)
+ for q_pdu in q_msg:
+ if q_pdu.hash is None and published_hash.get(q_pdu.uri) is not None:
+ logger.debug("Updating hash of %r to %s from previously published data", q_pdu, published_hash[q_pdu.uri])
+ q_pdu.hash = published_hash[q_pdu.uri]
+ self.call_pubd(done, errback, q_msg)
+
+ if not q_msg:
+ callback()
+ elif all(q_pdu.hash is not None for q_pdu in q_msg):
+ self.call_pubd(done, errback, q_msg)
+ else:
+ logger.debug("Some publication PDUs are missing hashes, checking...")
+ self.call_pubd(fix_hashes, errback, rpki.publication.msg.query(rpki.publication.list_elt()))
+
+
+ def call_pubd(self, callback, errback, q_msg):
+
+ try:
+ if not q_msg:
+ return callback(())
+
+ for q_pdu in q_msg:
+ logger.info("Sending %r to pubd", q_pdu)
+
+ q_der = rpki.publication.cms_msg().wrap(q_msg, self.rootd_bpki_key, self.rootd_bpki_cert, self.rootd_bpki_crl)
+
+ def done(r_der):
+ try:
+ logger.debug("Received response from pubd")
+ r_cms = rpki.publication.cms_msg(DER = r_der)
+ r_msg = r_cms.unwrap((self.bpki_ta, self.pubd_bpki_cert))
+ self.pubd_cms_timestamp = r_cms.check_replay(self.pubd_cms_timestamp, self.pubd_contact_uri)
+ for r_pdu in r_msg:
+ r_pdu.raise_if_error()
+ callback(r_msg)
+ except (rpki.async.ExitNow, SystemExit):
+ raise
+ except Exception, e:
+ errback(e)
+
+ logger.debug("Sending request to pubd")
+ rpki.http.client(
+ url = self.pubd_contact_uri,
+ msg = q_der,
+ callback = done,
+ errback = errback)
+
+ except (rpki.async.ExitNow, SystemExit):
+ raise
+ except Exception, e:
+ errback(e)
+
def up_down_handler(self, query, path, cb):
try:
q_cms = cms_msg(DER = query)
q_msg = q_cms.unwrap((self.bpki_ta, self.child_bpki_cert))
- self.cms_timestamp = q_cms.check_replay(self.cms_timestamp, path)
+ self.rpkid_cms_timestamp = q_cms.check_replay(self.rpkid_cms_timestamp, path)
except (rpki.async.ExitNow, SystemExit):
raise
except Exception, e:
@@ -304,7 +394,7 @@ class main(object):
def next_crl_number(self):
if self.crl_number is None:
try:
- crl = rpki.x509.CRL(DER_file = os.path.join(self.rpki_root_dir, self.rpki_root_crl))
+ crl = rpki.x509.CRL(DER_file = self.rpki_root_crl_file)
self.crl_number = crl.getCRLNumber()
except: # pylint: disable=W0702
self.crl_number = 0
@@ -328,11 +418,11 @@ class main(object):
global rootd
rootd = self # Gross, but simpler than what we'd have to do otherwise
- self.rpki_root_cert = None
self.serial_number = None
self.crl_number = None
self.revoked = []
- self.cms_timestamp = None
+ self.rpkid_cms_timestamp = None
+ self.pubd_cms_timestamp = None
os.environ["TZ"] = "UTC"
time.tzset()
@@ -359,28 +449,38 @@ class main(object):
self.rootd_bpki_crl = rpki.x509.CRL( Auto_update = self.cfg.get("rootd-bpki-crl"))
self.child_bpki_cert = rpki.x509.X509(Auto_update = self.cfg.get("child-bpki-cert"))
+ if self.cfg.has_option("pubd-bpki-cert"):
+ self.pubd_bpki_cert = rpki.x509.X509(Auto_update = self.cfg.get("pubd-bpki-cert"))
+ else:
+ self.pubd_bpki_cert = None
+
self.http_server_host = self.cfg.get("server-host", "")
self.http_server_port = self.cfg.getint("server-port")
- self.rpki_class_name = self.cfg.get("rpki-class-name", "wombat")
+ self.rpki_class_name = self.cfg.get("rpki-class-name")
- self.rpki_root_dir = self.cfg.get("rpki-root-dir")
- self.rpki_base_uri = self.cfg.get("rpki-base-uri", "rsync://" + self.rpki_class_name + ".invalid/")
+ self.rpki_root_key = rpki.x509.RSA( Auto_update = self.cfg.get("rpki-root-key-file"))
+ self.rpki_root_cert = rpki.x509.X509(Auto_update = self.cfg.get("rpki-root-cert-file"))
+ self.rpki_root_cert_uri = self.cfg.get("rpki-root-cert-uri")
+ self.rpki_root_cert_hash = None
- self.rpki_root_key = rpki.x509.RSA(Auto_update = self.cfg.get("rpki-root-key"))
- self.rpki_root_cert_file = self.cfg.get("rpki-root-cert")
- self.rpki_root_cert_uri = self.cfg.get("rpki-root-cert-uri", self.rpki_base_uri + "root.cer")
+ self.rpki_root_manifest_file = self.cfg.get("rpki-root-manifest-file")
+ self.rpki_root_manifest_uri = self.cfg.get("rpki-root-manifest-uri")
- self.rpki_root_manifest = self.cfg.get("rpki-root-manifest", "root.mft")
- self.rpki_root_crl = self.cfg.get("rpki-root-crl", "root.crl")
- self.rpki_subject_cert = self.cfg.get("rpki-subject-cert", "child.cer")
- self.rpki_subject_pkcs10 = self.cfg.get("rpki-subject-pkcs10", "child.pkcs10")
+ self.rpki_root_crl_file = self.cfg.get("rpki-root-crl-file")
+ self.rpki_root_crl_uri = self.cfg.get("rpki-root-crl-uri")
+ self.rpki_subject_cert_file = self.cfg.get("rpki-subject-cert-file")
+ self.rpki_subject_cert_uri = self.cfg.get("rpki-subject-cert-uri")
+ self.rpki_subject_pkcs10 = self.cfg.get("rpki-subject-pkcs10-file")
self.rpki_subject_lifetime = rpki.sundial.timedelta.parse(self.cfg.get("rpki-subject-lifetime", "8w"))
- self.rpki_subject_regen = rpki.sundial.timedelta.parse(self.cfg.get("rpki-subject-regen", self.rpki_subject_lifetime.convert_to_seconds() / 2))
+ self.rpki_subject_regen = rpki.sundial.timedelta.parse(self.cfg.get("rpki-subject-regen",
+ self.rpki_subject_lifetime.convert_to_seconds() / 2))
self.include_bpki_crl = self.cfg.getboolean("include-bpki-crl", False)
- rpki.http.server(host = self.http_server_host,
- port = self.http_server_port,
- handlers = self.up_down_handler)
+ self.pubd_contact_uri = self.cfg.get("pubd-contact-uri")
+
+ rpki.http.server(host = self.http_server_host,
+ port = self.http_server_port,
+ handlers = self.up_down_handler)
diff --git a/rpki/rpkid.py b/rpki/rpkid.py
index 13be2fd2..e9a05abb 100644
--- a/rpki/rpkid.py
+++ b/rpki/rpkid.py
@@ -316,6 +316,7 @@ class main(object):
Record that we were still alive when we got here, by resetting
keepalive timer.
"""
+
if force or self.cron_timeout is not None:
self.cron_timeout = rpki.sundial.now() + self.cron_keepalive
@@ -323,6 +324,7 @@ class main(object):
"""
Add a task to the scheduler task queue, unless it's already queued.
"""
+
if task not in self.task_queue:
logger.debug("Adding %r to task queue", task)
self.task_queue.append(task)
@@ -337,6 +339,7 @@ class main(object):
queue (we don't want to run it directly, as that could eventually
blow out our call stack).
"""
+
try:
self.task_current = self.task_queue.pop(0)
except IndexError:
@@ -348,6 +351,7 @@ class main(object):
"""
Run first task on the task queue, unless one is running already.
"""
+
if self.task_current is None:
self.task_next()
@@ -444,6 +448,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Fetch parent object to which this CA object links.
"""
+
return rpki.left_right.parent_elt.sql_fetch(self.gctx, self.parent_id)
@property
@@ -451,6 +456,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Fetch all ca_detail objects that link to this CA object.
"""
+
return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s", (self.ca_id,))
@property
@@ -458,6 +464,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Fetch the pending ca_details for this CA, if any.
"""
+
return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'pending'", (self.ca_id,))
@property
@@ -465,6 +472,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Fetch the active ca_detail for this CA, if any.
"""
+
return ca_detail_obj.sql_fetch_where1(self.gctx, "ca_id = %s AND state = 'active'", (self.ca_id,))
@property
@@ -472,6 +480,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Fetch deprecated ca_details for this CA, if any.
"""
+
return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'deprecated'", (self.ca_id,))
@property
@@ -479,6 +488,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Fetch active and deprecated ca_details for this CA, if any.
"""
+
return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND (state = 'active' OR state = 'deprecated')", (self.ca_id,))
@property
@@ -486,6 +496,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Fetch revoked ca_details for this CA, if any.
"""
+
return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'revoked'", (self.ca_id,))
@property
@@ -494,7 +505,7 @@ class ca_obj(rpki.sql.sql_persistent):
Fetch ca_details which are candidates for consideration when
processing an up-down issue_response PDU.
"""
- #return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND latest_ca_cert IS NOT NULL AND state != 'revoked'", (self.ca_id,))
+
return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state != 'revoked'", (self.ca_id,))
def construct_sia_uri(self, parent, rc):
@@ -540,7 +551,8 @@ class ca_obj(rpki.sql.sql_persistent):
if rc_cert is None:
- logger.warning("SKI %s in resource class %s is in database but missing from list_response to %s from %s, maybe parent certificate went away?",
+ logger.warning("SKI %s in resource class %s is in database but missing from list_response to %s from %s, "
+ "maybe parent certificate went away?",
ca_detail.public_key.gSKI(), rc.class_name, parent.self.self_handle, parent.parent_handle)
publisher = publication_queue()
ca_detail.delete(ca = ca_detail.ca, publisher = publisher)
@@ -675,6 +687,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Allocate a certificate serial number.
"""
+
self.last_issued_sn += 1
self.sql_mark_dirty()
return self.last_issued_sn
@@ -683,6 +696,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Allocate a manifest serial number.
"""
+
self.last_manifest_sn += 1
self.sql_mark_dirty()
return self.last_manifest_sn
@@ -691,6 +705,7 @@ class ca_obj(rpki.sql.sql_persistent):
"""
Allocate a CRL serial number.
"""
+
self.last_crl_sn += 1
self.sql_mark_dirty()
return self.last_crl_sn
@@ -781,6 +796,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Extra assertions for SQL decode of a ca_detail_obj.
"""
+
rpki.sql.sql_persistent.sql_decode(self, vals)
assert self.public_key is None or self.private_key_id is None or self.public_key.get_DER() == self.private_key_id.get_public_DER()
assert self.manifest_public_key is None or self.manifest_private_key_id is None or self.manifest_public_key.get_DER() == self.manifest_private_key_id.get_public_DER()
@@ -791,12 +807,14 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Fetch CA object to which this ca_detail links.
"""
+
return ca_obj.sql_fetch(self.gctx, self.ca_id)
def fetch_child_certs(self, child = None, ski = None, unique = False, unpublished = None):
"""
Fetch all child_cert objects that link to this ca_detail.
"""
+
return rpki.rpkid.child_cert_obj.fetch(self.gctx, child, self, ski, unique, unpublished)
@property
@@ -804,6 +822,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Fetch all child_cert objects that link to this ca_detail.
"""
+
return self.fetch_child_certs()
def unpublished_child_certs(self, when):
@@ -811,6 +830,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
Fetch all unpublished child_cert objects linked to this ca_detail
with attempted publication dates older than when.
"""
+
return self.fetch_child_certs(unpublished = when)
@property
@@ -818,6 +838,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Fetch all revoked_cert objects that link to this ca_detail.
"""
+
return revoked_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
@property
@@ -825,6 +846,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Fetch all ROA objects that link to this ca_detail.
"""
+
return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
def unpublished_roas(self, when):
@@ -832,6 +854,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
Fetch all unpublished ROA objects linked to this ca_detail with
attempted publication dates older than when.
"""
+
return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s AND published IS NOT NULL and published < %s", (self.ca_detail_id, when))
@property
@@ -839,27 +862,39 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Fetch all Ghostbuster objects that link to this ca_detail.
"""
+
return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
+ def unpublished_ghostbusters(self, when):
+ """
+ Fetch all unpublished Ghostbusters objects linked to this
+ ca_detail with attempted publication dates older than when.
+ """
+
+ return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s AND published IS NOT NULL and published < %s", (self.ca_detail_id, when))
+
@property
def ee_certificates(self):
"""
Fetch all EE certificate objects that link to this ca_detail.
"""
+
return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
- def unpublished_ghostbusters(self, when):
+ def unpublished_ee_certificates(self, when):
"""
- Fetch all unpublished Ghostbusters objects linked to this
+ Fetch all unpublished EE certificate objects linked to this
ca_detail with attempted publication dates older than when.
"""
- return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s AND published IS NOT NULL and published < %s", (self.ca_detail_id, when))
+
+ return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s AND published IS NOT NULL and published < %s", (self.ca_detail_id, when))
@property
def crl_uri(self):
"""
Return publication URI for this ca_detail's CRL.
"""
+
return self.ca.sia_uri + self.crl_uri_tail
@property
@@ -867,6 +902,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Return tail (filename portion) of publication URI for this ca_detail's CRL.
"""
+
return self.public_key.gSKI() + ".crl"
@property
@@ -874,12 +910,14 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Return publication URI for this ca_detail's manifest.
"""
+
return self.ca.sia_uri + self.public_key.gSKI() + ".mft"
def has_expired(self):
"""
Return whether this ca_detail's certificate has expired.
"""
+
return self.latest_ca_cert.getNotAfter() <= rpki.sundial.now()
def covers(self, target):
@@ -931,11 +969,10 @@ class ca_detail_obj(rpki.sql.sql_persistent):
repository = ca.parent.repository
handler = False if allow_failure else None
for child_cert in self.child_certs:
- publisher.withdraw(cls = rpki.publication.certificate_elt,
- uri = child_cert.uri,
- obj = child_cert.cert,
- repository = repository,
- handler = handler)
+ publisher.queue(uri = child_cert.uri,
+ old_obj = child_cert.cert,
+ repository = repository,
+ handler = handler)
child_cert.sql_mark_deleted()
for roa in self.roas:
roa.revoke(publisher = publisher, allow_failure = allow_failure, fast = True)
@@ -946,21 +983,19 @@ class ca_detail_obj(rpki.sql.sql_persistent):
except AttributeError:
latest_manifest = None
if latest_manifest is not None:
- publisher.withdraw(cls = rpki.publication.manifest_elt,
- uri = self.manifest_uri,
- obj = self.latest_manifest,
- repository = repository,
- handler = handler)
+ publisher.queue(uri = self.manifest_uri,
+ old_obj = self.latest_manifest,
+ repository = repository,
+ handler = handler)
try:
latest_crl = self.latest_crl
except AttributeError:
latest_crl = None
if latest_crl is not None:
- publisher.withdraw(cls = rpki.publication.crl_elt,
- uri = self.crl_uri,
- obj = self.latest_crl,
- repository = repository,
- handler = handler)
+ publisher.queue(uri = self.crl_uri,
+ old_obj = self.latest_crl,
+ repository = repository,
+ handler = handler)
self.gctx.sql.sweep()
for cert in self.revoked_certs: # + self.child_certs
logger.debug("Deleting %r", cert)
@@ -1170,6 +1205,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
notAfter = resources.valid_until)
if child_cert is None:
+ old_cert = None
child_cert = rpki.rpkid.child_cert_obj(
gctx = child.gctx,
child_id = child.child_id,
@@ -1177,6 +1213,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
cert = cert)
logger.debug("Created new child_cert %r", child_cert)
else:
+ old_cert = child_cert.cert
child_cert.cert = cert
del child_cert.ca_detail
child_cert.ca_detail_id = self.ca_detail_id
@@ -1185,10 +1222,10 @@ class ca_detail_obj(rpki.sql.sql_persistent):
child_cert.ski = cert.get_SKI()
child_cert.published = rpki.sundial.now()
child_cert.sql_store()
- publisher.publish(
- cls = rpki.publication.certificate_elt,
+ publisher.queue(
uri = child_cert.uri,
- obj = child_cert.cert,
+ old_obj = old_cert,
+ new_obj = child_cert.cert,
repository = ca.parent.repository,
handler = child_cert.published_callback)
self.generate_manifest(publisher = publisher)
@@ -1219,6 +1256,8 @@ class ca_detail_obj(rpki.sql.sql_persistent):
certlist.append((revoked_cert.serial, revoked_cert.revoked))
certlist.sort()
+ old_crl = self.latest_crl
+
self.latest_crl = rpki.x509.CRL.generate(
keypair = self.private_key_id,
issuer = self.latest_ca_cert,
@@ -1229,10 +1268,10 @@ class ca_detail_obj(rpki.sql.sql_persistent):
self.crl_published = rpki.sundial.now()
self.sql_mark_dirty()
- publisher.publish(
- cls = rpki.publication.crl_elt,
+ publisher.queue(
uri = self.crl_uri,
- obj = self.latest_crl,
+ old_obj = old_crl,
+ new_obj = self.latest_crl,
repository = parent.repository,
handler = self.crl_published_callback)
@@ -1240,6 +1279,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Check result of CRL publication.
"""
+
pdu.raise_if_error()
self.crl_published = None
self.sql_mark_dirty()
@@ -1276,6 +1316,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
objs.extend((e.uri_tail, e.cert) for e in self.ee_certificates)
logger.debug("Building manifest object %s", uri)
+ old_manifest = self.latest_manifest
self.latest_manifest = rpki.x509.SignedManifest.build(
serial = ca.next_manifest_number(),
thisUpdate = now,
@@ -1288,16 +1329,17 @@ class ca_detail_obj(rpki.sql.sql_persistent):
self.manifest_published = rpki.sundial.now()
self.sql_mark_dirty()
- publisher.publish(cls = rpki.publication.manifest_elt,
- uri = uri,
- obj = self.latest_manifest,
- repository = parent.repository,
- handler = self.manifest_published_callback)
+ publisher.queue(uri = uri,
+ old_obj = old_manifest,
+ new_obj = self.latest_manifest,
+ repository = parent.repository,
+ handler = self.manifest_published_callback)
def manifest_published_callback(self, pdu):
"""
Check result of manifest publication.
"""
+
pdu.raise_if_error()
self.manifest_published = None
self.sql_mark_dirty()
@@ -1359,21 +1401,19 @@ class ca_detail_obj(rpki.sql.sql_persistent):
self.crl_published is not None and \
self.crl_published < stale:
logger.debug("Retrying publication for %s", self.crl_uri)
- publisher.publish(cls = rpki.publication.crl_elt,
- uri = self.crl_uri,
- obj = self.latest_crl,
- repository = repository,
- handler = self.crl_published_callback)
+ publisher.queue(uri = self.crl_uri,
+ new_obj = self.latest_crl,
+ repository = repository,
+ handler = self.crl_published_callback)
if self.latest_manifest is not None and \
self.manifest_published is not None and \
self.manifest_published < stale:
logger.debug("Retrying publication for %s", self.manifest_uri)
- publisher.publish(cls = rpki.publication.manifest_elt,
- uri = self.manifest_uri,
- obj = self.latest_manifest,
- repository = repository,
- handler = self.manifest_published_callback)
+ publisher.queue(uri = self.manifest_uri,
+ new_obj = self.latest_manifest,
+ repository = repository,
+ handler = self.manifest_published_callback)
if not check_all:
return
@@ -1383,31 +1423,37 @@ class ca_detail_obj(rpki.sql.sql_persistent):
for child_cert in self.unpublished_child_certs(stale):
logger.debug("Retrying publication for %s", child_cert)
- publisher.publish(
- cls = rpki.publication.certificate_elt,
+ publisher.queue(
uri = child_cert.uri,
- obj = child_cert.cert,
+ new_obj = child_cert.cert,
repository = repository,
handler = child_cert.published_callback)
for roa in self.unpublished_roas(stale):
logger.debug("Retrying publication for %s", roa)
- publisher.publish(
- cls = rpki.publication.roa_elt,
+ publisher.queue(
uri = roa.uri,
- obj = roa.roa,
+ new_obj = roa.roa,
repository = repository,
handler = roa.published_callback)
for ghostbuster in self.unpublished_ghostbusters(stale):
logger.debug("Retrying publication for %s", ghostbuster)
- publisher.publish(
- cls = rpki.publication.ghostbuster_elt,
+ publisher.queue(
uri = ghostbuster.uri,
- obj = ghostbuster.ghostbuster,
+ new_obj = ghostbuster.ghostbuster,
repository = repository,
handler = ghostbuster.published_callback)
+ for ee_cert in self.unpublished_ee_certificates(stale):
+ logger.debug("Retrying publication for %s", ee_cert)
+ publisher.queue(
+ uri = ee_cert.uri,
+ new_obj = ee_cert.cert,
+ repository = repository,
+ handler = ee_cert.published_callback)
+
+
class child_cert_obj(rpki.sql.sql_persistent):
"""
Certificate that has been issued to a child.
@@ -1434,6 +1480,7 @@ class child_cert_obj(rpki.sql.sql_persistent):
"""
Initialize a child_cert_obj.
"""
+
rpki.sql.sql_persistent.__init__(self)
self.gctx = gctx
self.child_id = child_id
@@ -1449,6 +1496,7 @@ class child_cert_obj(rpki.sql.sql_persistent):
"""
Fetch child object to which this child_cert object links.
"""
+
return rpki.left_right.child_elt.sql_fetch(self.gctx, self.child_id)
@property
@@ -1457,6 +1505,7 @@ class child_cert_obj(rpki.sql.sql_persistent):
"""
Fetch ca_detail object to which this child_cert object links.
"""
+
return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
@ca_detail.deleter
@@ -1471,6 +1520,7 @@ class child_cert_obj(rpki.sql.sql_persistent):
"""
Return the tail (filename) portion of the URI for this child_cert.
"""
+
return self.cert.gSKI() + ".cer"
@property
@@ -1478,6 +1528,7 @@ class child_cert_obj(rpki.sql.sql_persistent):
"""
Return the publication URI for this child_cert.
"""
+
return self.ca_detail.ca.sia_uri + self.uri_tail
def revoke(self, publisher, generate_crl_and_manifest = True):
@@ -1489,10 +1540,9 @@ class child_cert_obj(rpki.sql.sql_persistent):
ca = ca_detail.ca
logger.debug("Revoking %r %r", self, self.uri)
revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
- publisher.withdraw(
- cls = rpki.publication.certificate_elt,
- uri = self.uri,
- obj = self.cert,
+ publisher.queue(
+ uri = self.uri,
+ old_obj = self.cert,
repository = ca.parent.repository)
self.gctx.sql.sweep()
self.sql_delete()
@@ -1623,6 +1673,7 @@ class child_cert_obj(rpki.sql.sql_persistent):
"""
Publication callback: check result and mark published.
"""
+
pdu.raise_if_error()
self.published = None
self.sql_mark_dirty()
@@ -1647,6 +1698,7 @@ class revoked_cert_obj(rpki.sql.sql_persistent):
"""
Initialize a revoked_cert_obj.
"""
+
rpki.sql.sql_persistent.__init__(self)
self.gctx = gctx
self.serial = serial
@@ -1662,6 +1714,7 @@ class revoked_cert_obj(rpki.sql.sql_persistent):
"""
Fetch ca_detail object to which this revoked_cert_obj links.
"""
+
return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
@classmethod
@@ -1669,6 +1722,7 @@ class revoked_cert_obj(rpki.sql.sql_persistent):
"""
Revoke a certificate.
"""
+
return cls(
serial = cert.getSerial(),
expires = cert.getNotAfter(),
@@ -1710,6 +1764,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Fetch ca_detail object to which this roa_obj links.
"""
+
return rpki.rpkid.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
@ca_detail.deleter
@@ -1723,6 +1778,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Extra SQL fetch actions for roa_obj -- handle prefix lists.
"""
+
for version, datatype, attribute in ((4, rpki.resource_set.roa_prefix_set_ipv4, "ipv4"),
(6, rpki.resource_set.roa_prefix_set_ipv6, "ipv6")):
setattr(self, attribute, datatype.from_sql(
@@ -1737,6 +1793,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Extra SQL insert actions for roa_obj -- handle prefix lists.
"""
+
for version, prefix_set in ((4, self.ipv4), (6, self.ipv6)):
if prefix_set:
self.gctx.sql.executemany(
@@ -1751,6 +1808,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Extra SQL delete actions for roa_obj -- handle prefix lists.
"""
+
self.gctx.sql.execute("DELETE FROM roa_prefix WHERE roa_id = %s", (self.roa_id,))
def __repr__(self):
@@ -1892,10 +1950,9 @@ class roa_obj(rpki.sql.sql_persistent):
self.sql_store()
logger.debug("Generating %r URI %s", self, self.uri)
- publisher.publish(
- cls = rpki.publication.roa_elt,
+ publisher.queue(
uri = self.uri,
- obj = self.roa,
+ new_obj = self.roa,
repository = ca.parent.repository,
handler = self.published_callback)
if not fast:
@@ -1940,9 +1997,10 @@ class roa_obj(rpki.sql.sql_persistent):
logger.debug("Withdrawing %r %s and revoking its EE cert", self, uri)
rpki.rpkid.revoked_cert_obj.revoke(cert = cert, ca_detail = ca_detail)
- publisher.withdraw(cls = rpki.publication.roa_elt, uri = uri, obj = roa,
- repository = ca_detail.ca.parent.repository,
- handler = False if allow_failure else None)
+ publisher.queue(uri = uri,
+ old_obj = roa,
+ repository = ca_detail.ca.parent.repository,
+ handler = False if allow_failure else None)
if not regenerate:
self.sql_mark_deleted()
@@ -1956,6 +2014,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Reissue ROA associated with this roa_obj.
"""
+
if self.ca_detail is None:
self.generate(publisher = publisher, fast = fast)
else:
@@ -1965,6 +2024,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Return publication URI for a public key.
"""
+
return self.ca_detail.ca.sia_uri + key.gSKI() + ".roa"
@property
@@ -1972,6 +2032,7 @@ class roa_obj(rpki.sql.sql_persistent):
"""
Return the publication URI for this roa_obj's ROA.
"""
+
return self.ca_detail.ca.sia_uri + self.uri_tail
@property
@@ -1980,6 +2041,7 @@ class roa_obj(rpki.sql.sql_persistent):
Return the tail (filename portion) of the publication URI for this
roa_obj's ROA.
"""
+
return self.cert.gSKI() + ".roa"
@@ -2022,6 +2084,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
"""
Fetch self object to which this ghostbuster_obj links.
"""
+
return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id)
@property
@@ -2030,6 +2093,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
"""
Fetch ca_detail object to which this ghostbuster_obj links.
"""
+
return rpki.rpkid.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
def __init__(self, gctx = None, self_id = None, ca_detail_id = None, vcard = None):
@@ -2095,10 +2159,9 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
self.sql_store()
logger.debug("Generating Ghostbuster record %r", self.uri)
- publisher.publish(
- cls = rpki.publication.ghostbuster_elt,
+ publisher.queue(
uri = self.uri,
- obj = self.ghostbuster,
+ new_obj = self.ghostbuster,
repository = ca.parent.repository,
handler = self.published_callback)
if not fast:
@@ -2108,6 +2171,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
"""
Check publication result.
"""
+
pdu.raise_if_error()
self.published = None
self.sql_mark_dirty()
@@ -2142,9 +2206,10 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
logger.debug("Withdrawing %r %s and revoking its EE cert", self, uri)
rpki.rpkid.revoked_cert_obj.revoke(cert = cert, ca_detail = ca_detail)
- publisher.withdraw(cls = rpki.publication.ghostbuster_elt, uri = uri, obj = ghostbuster,
- repository = ca_detail.ca.parent.repository,
- handler = False if allow_failure else None)
+ publisher.queue(uri = uri,
+ old_obj = ghostbuster,
+ repository = ca_detail.ca.parent.repository,
+ handler = False if allow_failure else None)
if not regenerate:
self.sql_mark_deleted()
@@ -2158,6 +2223,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
"""
Reissue Ghostbuster associated with this ghostbuster_obj.
"""
+
if self.ghostbuster is None:
self.generate(publisher = publisher, fast = fast)
else:
@@ -2167,6 +2233,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
"""
Return publication URI for a public key.
"""
+
return self.ca_detail.ca.sia_uri + key.gSKI() + ".gbr"
@property
@@ -2174,6 +2241,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
"""
Return the publication URI for this ghostbuster_obj's ghostbuster.
"""
+
return self.ca_detail.ca.sia_uri + self.uri_tail
@property
@@ -2182,6 +2250,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
Return the tail (filename portion) of the publication URI for this
ghostbuster_obj's ghostbuster.
"""
+
return self.cert.gSKI() + ".gbr"
@@ -2219,6 +2288,7 @@ class ee_cert_obj(rpki.sql.sql_persistent):
"""
Fetch self object to which this ee_cert_obj links.
"""
+
return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id)
@property
@@ -2227,6 +2297,7 @@ class ee_cert_obj(rpki.sql.sql_persistent):
"""
Fetch ca_detail object to which this ee_cert_obj links.
"""
+
return rpki.rpkid.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
@ca_detail.deleter
@@ -2244,6 +2315,7 @@ class ee_cert_obj(rpki.sql.sql_persistent):
Although, really, one has to ask why we don't just store g(SKI)
in rpkid.sql instead of ski....
"""
+
return base64.urlsafe_b64encode(self.ski).rstrip("=")
@gski.setter
@@ -2255,6 +2327,7 @@ class ee_cert_obj(rpki.sql.sql_persistent):
"""
Return the publication URI for this ee_cert_obj.
"""
+
return self.ca_detail.ca.sia_uri + self.uri_tail
@property
@@ -2263,6 +2336,7 @@ class ee_cert_obj(rpki.sql.sql_persistent):
Return the tail (filename portion) of the publication URI for this
ee_cert_obj.
"""
+
return self.cert.gSKI() + ".cer"
@classmethod
@@ -2290,12 +2364,11 @@ class ee_cert_obj(rpki.sql.sql_persistent):
ca_detail_id = ca_detail.ca_detail_id,
cert = cert)
- publisher.publish(
- cls = rpki.publication.certificate_elt,
- uri = self.uri,
- obj = self.cert,
+ publisher.queue(
+ uri = self.uri,
+ new_obj = self.cert,
repository = ca.parent.repository,
- handler = self.published_callback)
+ handler = self.published_callback)
self.sql_store()
@@ -2314,10 +2387,9 @@ class ee_cert_obj(rpki.sql.sql_persistent):
ca = ca_detail.ca
logger.debug("Revoking %r %r", self, self.uri)
revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
- publisher.withdraw(cls = rpki.publication.certificate_elt,
- uri = self.uri,
- obj = self.cert,
- repository = ca.parent.repository)
+ publisher.queue(uri = self.uri,
+ old_obj = self.cert,
+ repository = ca.parent.repository)
self.gctx.sql.sweep()
self.sql_delete()
if generate_crl_and_manifest:
@@ -2399,12 +2471,12 @@ class ee_cert_obj(rpki.sql.sql_persistent):
self.sql_mark_dirty()
- publisher.publish(
- cls = rpki.publication.certificate_elt,
- uri = self.uri,
- obj = self.cert,
+ publisher.queue(
+ uri = self.uri,
+ old_obj = old_cert,
+ new_obj = self.cert,
repository = ca_detail.ca.parent.repository,
- handler = self.published_callback)
+ handler = self.published_callback)
if must_revoke:
revoked_cert_obj.revoke(cert = old_cert.cert, ca_detail = old_ca_detail)
@@ -2421,6 +2493,7 @@ class ee_cert_obj(rpki.sql.sql_persistent):
"""
Publication callback: check result and mark published.
"""
+
pdu.raise_if_error()
self.published = None
self.sql_mark_dirty()
@@ -2449,29 +2522,43 @@ class publication_queue(object):
if self.replace:
self.uris = {}
- def _add(self, uri, obj, repository, handler, make_pdu):
+ def queue(self, uri, repository, handler = None, old_obj = None, new_obj = None):
+
+ assert old_obj is not None or new_obj is not None
+ assert old_obj is None or isinstance(old_obj, rpki.x509.uri_dispatch(uri))
+ assert new_obj is None or isinstance(new_obj, rpki.x509.uri_dispatch(uri))
+
+ logger.debug("Queuing publication action: uri %s, old %r, new %r", uri, old_obj, new_obj)
+
rid = id(repository)
if rid not in self.repositories:
self.repositories[rid] = repository
self.msgs[rid] = rpki.publication.msg.query()
+
if self.replace and uri in self.uris:
- logger.debug("Removing publication duplicate <%s %r %r>",
- self.uris[uri].action, self.uris[uri].uri, self.uris[uri].payload)
- self.msgs[rid].remove(self.uris.pop(uri))
- pdu = make_pdu(uri = uri, obj = obj)
+ logger.debug("Removing publication duplicate %r", self.uris[uri])
+ old_pdu = self.uris.pop(uri)
+ self.msgs[rid].remove(old_pdu)
+ hash = old_pdu.hash
+ elif old_obj is None:
+ hash = None
+ else:
+ hash = rpki.x509.sha256(old_obj.get_DER()).encode("hex")
+
+ if new_obj is None:
+ pdu = rpki.publication.withdraw_elt.make_pdu(uri = uri, hash = hash)
+ else:
+ pdu = rpki.publication.publish_elt.make_pdu( uri = uri, hash = hash, der = new_obj.get_DER())
+
if handler is not None:
self.handlers[id(pdu)] = handler
pdu.tag = id(pdu)
+
self.msgs[rid].append(pdu)
+
if self.replace:
self.uris[uri] = pdu
- def publish(self, cls, uri, obj, repository, handler = None):
- return self._add( uri, obj, repository, handler, cls.make_publish)
-
- def withdraw(self, cls, uri, obj, repository, handler = None):
- return self._add( uri, obj, repository, handler, cls.make_withdraw)
-
def call_pubd(self, cb, eb):
def loop(iterator, rid):
logger.debug("Calling pubd[%r]", self.repositories[rid])
@@ -2486,5 +2573,5 @@ class publication_queue(object):
return sum(len(self.msgs[rid]) for rid in self.repositories)
def empty(self):
- assert (not self.msgs) == (self.size == 0)
+ assert (not self.msgs) == (self.size == 0), "Assertion failure: not self.msgs: %r, self.size %r" % (not self.msgs, self.size)
return not self.msgs
diff --git a/rpki/rpkid_tasks.py b/rpki/rpkid_tasks.py
index 58b4bcfe..959d4223 100644
--- a/rpki/rpkid_tasks.py
+++ b/rpki/rpkid_tasks.py
@@ -310,10 +310,9 @@ class UpdateChildrenTask(AbstractTask):
self.child.child_handle, child_cert.cert.gSKI(),
old_resources.valid_until, irdb_resources.valid_until)
child_cert.sql_delete()
- self.publisher.withdraw(
- cls = rpki.publication.certificate_elt,
+ self.publisher.queue(
uri = child_cert.uri,
- obj = child_cert.cert,
+ old_obj = child_cert.cert,
repository = ca.parent.repository)
ca_detail.generate_manifest(publisher = self.publisher)
diff --git a/rpki/rtr/bgpdump.py b/rpki/rtr/bgpdump.py
index fc3ae9df..5ffabc4d 100755
--- a/rpki/rtr/bgpdump.py
+++ b/rpki/rtr/bgpdump.py
@@ -295,7 +295,7 @@ def bgpdump_server_main(args):
rpki.rtr.server.read_current = clock.read_current
try:
- server = rpki.rtr.server.ServerChannel(logger = logger)
+ server = rpki.rtr.server.ServerChannel(logger = logger, refresh = args.refresh, retry = args.retry, expire = args.expire)
old_serial = server.get_serial()
logger.debug("[Starting at serial %d (%s)]", old_serial, old_serial)
while clock:
diff --git a/rpki/sql.py b/rpki/sql.py
index 31ed40ee..55e6f7cb 100644
--- a/rpki/sql.py
+++ b/rpki/sql.py
@@ -56,11 +56,12 @@ class session(object):
ping_threshold = rpki.sundial.timedelta(seconds = 60)
- def __init__(self, cfg):
+ def __init__(self, cfg, autocommit = True):
self.username = cfg.get("sql-username")
self.database = cfg.get("sql-database")
self.password = cfg.get("sql-password")
+ self.autocommit = autocommit
self.conv = MySQLdb.converters.conversions.copy()
self.conv.update({
@@ -78,7 +79,7 @@ class session(object):
passwd = self.password,
conv = self.conv)
self.cur = self.db.cursor()
- self.db.autocommit(True)
+ self.db.autocommit(self.autocommit)
self.timestamp = rpki.sundial.now()
def close(self):
@@ -113,11 +114,37 @@ class session(object):
def lastrowid(self):
return self.cur.lastrowid
+ def commit(self):
+ """
+ Sweep cache, then commit SQL.
+ """
+
+ self.sweep()
+ logger.debug("Executing SQL COMMIT")
+ self.db.commit()
+
+ def rollback(self):
+ """
+ SQL rollback, then clear cache and dirty cache.
+
+ NB: We have no way of clearing other references to cached objects,
+ so if you call this method you MUST forget any state that might
+ cause you to retain such references. This is probably tricky, and
+ is itself a good argument for switching to something like the
+ Django ORM's @commit_on_success semantics, but we do what we can.
+ """
+
+ logger.debug("Executing SQL ROLLBACK, discarding SQL cache and dirty set")
+ self.db.rollback()
+ self.dirty.clear()
+ self.cache.clear()
+
def cache_clear(self):
"""
Clear the SQL object cache. Shouldn't be necessary now that the
cache uses weak references, but should be harmless.
"""
+
logger.debug("Clearing SQL cache")
self.assert_pristine()
self.cache.clear()
@@ -126,14 +153,15 @@ class session(object):
"""
Assert that there are no dirty objects in the cache.
"""
+
assert not self.dirty, "Dirty objects in SQL cache: %s" % self.dirty
def sweep(self):
"""
Write any dirty objects out to SQL.
"""
+
for s in self.dirty.copy():
- #if s.sql_cache_debug:
logger.debug("Sweeping (%s) %r", "deleting" if s.sql_deleted else "storing", s)
if s.sql_deleted:
s.sql_delete()
@@ -150,6 +178,7 @@ class template(object):
"""
Build a SQL template.
"""
+
type_map = dict((x[0], x[1]) for x in data_columns if isinstance(x, tuple))
data_columns = tuple(isinstance(x, tuple) and x[0] or x for x in data_columns)
columns = (index_column,) + data_columns
@@ -220,6 +249,7 @@ class sql_persistent(object):
"""
Fetch one object from SQL, based on an arbitrary SQL WHERE expression.
"""
+
results = cls.sql_fetch_where(gctx, where, args, also_from)
if len(results) == 0:
return None
@@ -235,6 +265,7 @@ class sql_persistent(object):
"""
Fetch all objects of this type from SQL.
"""
+
return cls.sql_fetch_where(gctx, None)
@classmethod
@@ -242,6 +273,7 @@ class sql_persistent(object):
"""
Fetch objects of this type matching an arbitrary SQL WHERE expression.
"""
+
if where is None:
assert args is None and also_from is None
if cls.sql_debug:
@@ -269,6 +301,7 @@ class sql_persistent(object):
"""
Initialize one Python object from the result of a SQL query.
"""
+
self = cls()
self.gctx = gctx
self.sql_decode(dict(zip(cls.sql_template.columns, row)))
@@ -281,6 +314,7 @@ class sql_persistent(object):
"""
Mark this object as needing to be written back to SQL.
"""
+
if self.sql_cache_debug and not self.sql_is_dirty:
logger.debug("Marking %r SQL dirty", self)
self.gctx.sql.dirty.add(self)
@@ -289,6 +323,7 @@ class sql_persistent(object):
"""
Mark this object as not needing to be written back to SQL.
"""
+
if self.sql_cache_debug and self.sql_is_dirty:
logger.debug("Marking %r SQL clean", self)
self.gctx.sql.dirty.discard(self)
@@ -298,12 +333,14 @@ class sql_persistent(object):
"""
Query whether this object needs to be written back to SQL.
"""
+
return self in self.gctx.sql.dirty
def sql_mark_deleted(self):
"""
Mark this object as needing to be deleted in SQL.
"""
+
self.sql_deleted = True
self.sql_mark_dirty()
@@ -311,6 +348,7 @@ class sql_persistent(object):
"""
Store this object to SQL.
"""
+
args = self.sql_encode()
if not self.sql_in_db:
if self.sql_debug:
@@ -333,6 +371,7 @@ class sql_persistent(object):
"""
Delete this object from SQL.
"""
+
if self.sql_in_db:
id = getattr(self, self.sql_template.index) # pylint: disable=W0622
if self.sql_debug:
@@ -352,6 +391,7 @@ class sql_persistent(object):
mapping between column names in SQL and attribute names in Python.
If you need something fancier, override this.
"""
+
d = dict((a, getattr(self, a, None)) for a in self.sql_template.columns)
for i in self.sql_template.map:
if d.get(i) is not None:
@@ -365,6 +405,7 @@ class sql_persistent(object):
between column names in SQL and attribute names in Python. If you
need something fancier, override this.
"""
+
for a in self.sql_template.columns:
if vals.get(a) is not None and a in self.sql_template.map:
setattr(self, a, self.sql_template.map[a].from_sql(vals[a]))
@@ -375,18 +416,21 @@ class sql_persistent(object):
"""
Customization hook.
"""
+
pass
def sql_insert_hook(self):
"""
Customization hook.
"""
+
pass
def sql_update_hook(self):
"""
Customization hook.
"""
+
self.sql_delete_hook()
self.sql_insert_hook()
@@ -394,6 +438,7 @@ class sql_persistent(object):
"""
Customization hook.
"""
+
pass
diff --git a/rpki/sql_schemas.py b/rpki/sql_schemas.py
index 07037970..fc262f12 100644
--- a/rpki/sql_schemas.py
+++ b/rpki/sql_schemas.py
@@ -2,35 +2,24 @@
## @var rpkid
## SQL schema rpkid
-rpkid = '''-- $Id: rpkid.sql 5845 2014-05-29 22:31:15Z sra $
+rpkid = '''-- $Id: rpkid.sql 5881 2014-07-03 16:55:02Z sra $
--- Copyright (C) 2009--2011 Internet Systems Consortium ("ISC")
+-- Copyright (C) 2012--2014 Dragon Research Labs ("DRL")
+-- Portions copyright (C) 2009--2011 Internet Systems Consortium ("ISC")
+-- Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
--
-- Permission to use, copy, modify, and 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.
+-- copyright notices and this permission notice appear in all copies.
--
--- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ISC 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.
-
--- Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
---
--- Permission to use, copy, modify, and 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 ARIN DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ARIN 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.
+-- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+-- ISC, OR ARIN 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.
-- SQL objects needed by the RPKI engine (rpkid.py).
@@ -256,50 +245,39 @@ CREATE TABLE ee_cert (
## @var pubd
## SQL schema pubd
-pubd = '''-- $Id: pubd.sql 5757 2014-04-05 22:42:12Z sra $
+pubd = '''-- $Id: pubd.sql 5914 2014-08-06 22:52:28Z sra $
--- Copyright (C) 2009--2010 Internet Systems Consortium ("ISC")
---
--- Permission to use, copy, modify, and 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 ISC DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ISC 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.
-
--- Copyright (C) 2008 American Registry for Internet Numbers ("ARIN")
+-- Copyright (C) 2012--2014 Dragon Research Labs ("DRL")
+-- Portions copyright (C) 2009--2010 Internet Systems Consortium ("ISC")
+-- Portions copyright (C) 2008 American Registry for Internet Numbers ("ARIN")
--
-- Permission to use, copy, modify, and 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.
+-- copyright notices and this permission notice appear in all copies.
--
--- THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ARIN 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.
+-- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+-- ISC, OR ARIN 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.
-- SQL objects needed by pubd.py.
--- The config table is weird because we're really only using it
--- to store one BPKI CRL, but putting this here lets us use a lot of
--- existing machinery and the alternatives are whacky in other ways.
+-- Old tables that should just be flushed if present at all.
-DROP TABLE IF EXISTS client;
DROP TABLE IF EXISTS config;
+DROP TABLE IF EXISTS snapshot;
-CREATE TABLE config (
- config_id SERIAL NOT NULL,
- bpki_crl LONGBLOB,
- PRIMARY KEY (config_id)
-) ENGINE=InnoDB;
+-- DROP TABLE commands must be in correct (reverse dependency) order
+-- to satisfy FOREIGN KEY constraints.
+
+DROP TABLE IF EXISTS object;
+DROP TABLE IF EXISTS delta;
+DROP TABLE IF EXISTS session;
+DROP TABLE IF EXISTS client;
CREATE TABLE client (
client_id SERIAL NOT NULL,
@@ -312,6 +290,43 @@ CREATE TABLE client (
UNIQUE (client_handle)
) ENGINE=InnoDB;
+CREATE TABLE session (
+ session_id SERIAL NOT NULL,
+ uuid VARCHAR(36) NOT NULL,
+ serial BIGINT UNSIGNED NOT NULL,
+ snapshot LONGTEXT,
+ hash CHAR(64),
+ PRIMARY KEY (session_id),
+ UNIQUE (uuid)
+) ENGINE=InnoDB;
+
+CREATE TABLE delta (
+ delta_id SERIAL NOT NULL,
+ serial BIGINT UNSIGNED NOT NULL,
+ xml LONGTEXT NOT NULL,
+ hash CHAR(64) NOT NULL,
+ expires DATETIME NOT NULL,
+ session_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (delta_id),
+ CONSTRAINT delta_session_id
+ FOREIGN KEY (session_id) REFERENCES session (session_id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+CREATE TABLE object (
+ object_id SERIAL NOT NULL,
+ uri VARCHAR(255) NOT NULL,
+ der LONGBLOB NOT NULL,
+ hash CHAR(64) NOT NULL,
+ client_id BIGINT UNSIGNED NOT NULL,
+ session_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (object_id),
+ CONSTRAINT object_client_id
+ FOREIGN KEY (client_id) REFERENCES client (client_id) ON DELETE CASCADE,
+ CONSTRAINT object_session_id
+ FOREIGN KEY (session_id) REFERENCES session (session_id) ON DELETE CASCADE,
+ UNIQUE (session_id, hash)
+) ENGINE=InnoDB;
+
-- Local Variables:
-- indent-tabs-mode: nil
-- End:
diff --git a/rpki/sundial.py b/rpki/sundial.py
index 7be122c8..60037277 100644
--- a/rpki/sundial.py
+++ b/rpki/sundial.py
@@ -51,6 +51,7 @@ def now():
"""
Get current timestamp.
"""
+
return datetime.utcnow()
class ParseFailure(Exception):
@@ -69,6 +70,7 @@ class datetime(pydatetime.datetime):
Convert to seconds from epoch (like time.time()). Conversion
method is a bit silly, but avoids time module timezone whackiness.
"""
+
return int(self.strftime("%s"))
@classmethod
@@ -76,6 +78,7 @@ class datetime(pydatetime.datetime):
"""
Convert from XML time representation.
"""
+
if x is None:
return None
else:
@@ -85,6 +88,7 @@ class datetime(pydatetime.datetime):
"""
Convert to XML time representation.
"""
+
return self.strftime("%Y-%m-%dT%H:%M:%SZ")
def __str__(self):
@@ -96,6 +100,7 @@ class datetime(pydatetime.datetime):
Convert a datetime.datetime object into this subclass. This is
whacky due to the weird constructors for datetime.
"""
+
return cls.combine(x.date(), x.time())
def to_datetime(self):
@@ -104,6 +109,7 @@ class datetime(pydatetime.datetime):
shouldn't be necessary, but convincing SQL interfaces to use
subclasses of datetime can be hard.
"""
+
return pydatetime.datetime(year = self.year, month = self.month, day = self.day,
hour = self.hour, minute = self.minute, second = self.second,
microsecond = 0, tzinfo = None)
@@ -115,6 +121,7 @@ class datetime(pydatetime.datetime):
Convert from the format OpenSSL's command line tool uses into this
subclass. May require rewriting if we run into locale problems.
"""
+
if x.startswith("notBefore=") or x.startswith("notAfter="):
x = x.partition("=")[2]
return cls.strptime(x, "%b %d %H:%M:%S %Y GMT")
@@ -124,24 +131,28 @@ class datetime(pydatetime.datetime):
"""
Convert from SQL storage format.
"""
+
return cls.from_datetime(x)
def to_sql(self):
"""
Convert to SQL storage format.
"""
+
return self.to_datetime()
def later(self, other):
"""
Return the later of two timestamps.
"""
+
return other if other > self else self
def earlier(self, other):
"""
Return the earlier of two timestamps.
"""
+
return other if other < self else self
def __add__(self, y): return _cast(pydatetime.datetime.__add__(self, y))
@@ -216,6 +227,7 @@ class timedelta(pydatetime.timedelta):
"""
Parse text into a timedelta object.
"""
+
if not isinstance(arg, str):
return cls(seconds = arg)
elif arg.isdigit():
@@ -237,6 +249,7 @@ class timedelta(pydatetime.timedelta):
"""
Convert a timedelta interval to seconds.
"""
+
return self.days * 24 * 60 * 60 + self.seconds
@classmethod
@@ -244,6 +257,7 @@ class timedelta(pydatetime.timedelta):
"""
Convert a datetime.timedelta object into this subclass.
"""
+
return cls(days = x.days, seconds = x.seconds, microseconds = x.microseconds)
def __abs__(self): return _cast(pydatetime.timedelta.__abs__(self))
@@ -264,6 +278,7 @@ def _cast(x):
"""
Cast result of arithmetic operations back into correct subtype.
"""
+
if isinstance(x, pydatetime.datetime):
return datetime.from_datetime(x)
if isinstance(x, pydatetime.timedelta):
diff --git a/rpki/up_down.py b/rpki/up_down.py
index 73a0ae99..a145ed28 100644
--- a/rpki/up_down.py
+++ b/rpki/up_down.py
@@ -50,6 +50,7 @@ class base_elt(object):
Some elements have no attributes and we only care about their
text content.
"""
+
pass
def endElement(self, stack, name, text):
@@ -58,12 +59,14 @@ class base_elt(object):
If we don't need to do anything else, just pop the stack.
"""
+
stack.pop()
def make_elt(self, name, *attrs):
"""
Construct a element, copying over a set of attributes.
"""
+
elt = lxml.etree.Element(xmlns + name, nsmap = nsmap)
for key in attrs:
val = getattr(self, key, None)
@@ -75,6 +78,7 @@ class base_elt(object):
"""
Construct a sub-element with Base64 text content.
"""
+
if value is not None and not value.empty():
lxml.etree.SubElement(elt, xmlns + name, nsmap = nsmap).text = value.get_Base64()
@@ -82,12 +86,14 @@ class base_elt(object):
"""
Default PDU handler to catch unexpected types.
"""
+
raise rpki.exceptions.BadQuery("Unexpected query type %s" % q_msg.type)
def check_response(self):
"""
Placeholder for response checking.
"""
+
pass
class multi_uri(list):
@@ -99,6 +105,7 @@ class multi_uri(list):
"""
Initialize a set of URIs, which includes basic some syntax checking.
"""
+
list.__init__(self)
if isinstance(ini, (list, tuple)):
self[:] = ini
@@ -114,12 +121,14 @@ class multi_uri(list):
"""
Convert a multi_uri back to a string representation.
"""
+
return ",".join(self)
def rsync(self):
"""
Find first rsync://... URI in self.
"""
+
for s in self:
if s.startswith("rsync://"):
return s
@@ -134,6 +143,7 @@ class certificate_elt(base_elt):
"""
Handle attributes of <certificate/> element.
"""
+
assert name == "certificate", "Unexpected name %s, stack %s" % (name, stack)
self.cert_url = multi_uri(attrs["cert_url"])
self.req_resource_set_as = rpki.resource_set.resource_set_as(attrs.get("req_resource_set_as"))
@@ -144,6 +154,7 @@ class certificate_elt(base_elt):
"""
Handle text content of a <certificate/> element.
"""
+
assert name == "certificate", "Unexpected name %s, stack %s" % (name, stack)
self.cert = rpki.x509.X509(Base64 = text)
stack.pop()
@@ -152,6 +163,7 @@ class certificate_elt(base_elt):
"""
Generate a <certificate/> element.
"""
+
elt = self.make_elt("certificate", "cert_url",
"req_resource_set_as", "req_resource_set_ipv4", "req_resource_set_ipv6")
elt.text = self.cert.get_Base64()
@@ -168,6 +180,7 @@ class class_elt(base_elt):
"""
Initialize class_elt.
"""
+
base_elt.__init__(self)
self.certs = []
@@ -175,6 +188,7 @@ class class_elt(base_elt):
"""
Handle <class/> elements and their children.
"""
+
if name == "certificate":
cert = certificate_elt()
self.certs.append(cert)
@@ -194,6 +208,7 @@ class class_elt(base_elt):
"""
Handle <class/> elements and their children.
"""
+
if name == "issuer":
self.issuer = rpki.x509.X509(Base64 = text)
else:
@@ -204,10 +219,11 @@ class class_elt(base_elt):
"""
Generate a <class/> element.
"""
+
elt = self.make_elt("class", "class_name", "cert_url", "resource_set_as",
"resource_set_ipv4", "resource_set_ipv6",
"resource_set_notafter", "suggested_sia_head")
- elt.extend([i.toXML() for i in self.certs])
+ elt.extend(i.toXML() for i in self.certs)
self.make_b64elt(elt, "issuer", self.issuer)
return elt
@@ -215,6 +231,7 @@ class class_elt(base_elt):
"""
Build a resource_bag from from this <class/> element.
"""
+
return rpki.resource_set.resource_bag(self.resource_set_as,
self.resource_set_ipv4,
self.resource_set_ipv6,
@@ -224,6 +241,7 @@ class class_elt(base_elt):
"""
Set resources of this class element from a resource_bag.
"""
+
self.resource_set_as = bag.asn
self.resource_set_ipv4 = bag.v4
self.resource_set_ipv6 = bag.v6
@@ -235,7 +253,10 @@ class list_pdu(base_elt):
"""
def toXML(self):
- """Generate (empty) payload of "list" PDU."""
+ """
+ Generate (empty) payload of "list" PDU.
+ """
+
return []
def serve_pdu(self, q_msg, r_msg, child, callback, errback):
@@ -282,6 +303,7 @@ class list_pdu(base_elt):
"""
Send a "list" query to parent.
"""
+
try:
logger.info('Sending "list" request to parent %s', parent.parent_handle)
parent.query_up_down(cls(), cb, eb)
@@ -299,6 +321,7 @@ class class_response_syntax(base_elt):
"""
Initialize class_response_syntax.
"""
+
base_elt.__init__(self)
self.classes = []
@@ -306,6 +329,7 @@ class class_response_syntax(base_elt):
"""
Handle "list_response" and "issue_response" PDUs.
"""
+
assert name == "class", "Unexpected name %s, stack %s" % (name, stack)
c = class_elt()
self.classes.append(c)
@@ -313,13 +337,17 @@ class class_response_syntax(base_elt):
c.startElement(stack, name, attrs)
def toXML(self):
- """Generate payload of "list_response" and "issue_response" PDUs."""
+ """
+ Generate payload of "list_response" and "issue_response" PDUs.
+ """
+
return [c.toXML() for c in self.classes]
class list_response_pdu(class_response_syntax):
"""
Up-Down protocol "list_response" PDU.
"""
+
pass
class issue_pdu(base_elt):
@@ -331,6 +359,7 @@ class issue_pdu(base_elt):
"""
Handle "issue" PDU.
"""
+
assert name == "request", "Unexpected name %s, stack %s" % (name, stack)
self.class_name = attrs["class_name"]
self.req_resource_set_as = rpki.resource_set.resource_set_as(attrs.get("req_resource_set_as"))
@@ -341,6 +370,7 @@ class issue_pdu(base_elt):
"""
Handle "issue" PDU.
"""
+
assert name == "request", "Unexpected name %s, stack %s" % (name, stack)
self.pkcs10 = rpki.x509.PKCS10(Base64 = text)
stack.pop()
@@ -349,6 +379,7 @@ class issue_pdu(base_elt):
"""
Generate payload of "issue" PDU.
"""
+
elt = self.make_elt("request", "class_name", "req_resource_set_as",
"req_resource_set_ipv4", "req_resource_set_ipv6")
elt.text = self.pkcs10.get_Base64()
@@ -432,6 +463,7 @@ class issue_pdu(base_elt):
"""
Send an "issue" request to parent associated with ca.
"""
+
assert ca_detail is not None and ca_detail.state in ("pending", "active")
self = cls()
self.class_name = ca.parent_resource_class
@@ -453,6 +485,7 @@ class issue_response_pdu(class_response_syntax):
Check whether this looks like a reasonable issue_response PDU.
XML schema should be tighter for this response.
"""
+
if len(self.classes) != 1 or len(self.classes[0].certs) != 1:
raise rpki.exceptions.BadIssueResponse
@@ -462,12 +495,18 @@ class revoke_syntax(base_elt):
"""
def startElement(self, stack, name, attrs):
- """Handle "revoke" PDU."""
+ """
+ Handle "revoke" PDU.
+ """
+
self.class_name = attrs["class_name"]
self.ski = attrs["ski"]
def toXML(self):
- """Generate payload of "revoke" PDU."""
+ """
+ Generate payload of "revoke" PDU.
+ """
+
return [self.make_elt("key", "class_name", "ski")]
class revoke_pdu(revoke_syntax):
@@ -479,6 +518,7 @@ class revoke_pdu(revoke_syntax):
"""
Convert g(SKI) encoding from PDU back to raw SKI.
"""
+
return base64.urlsafe_b64decode(self.ski + "=")
def serve_pdu(self, q_msg, r_msg, child, cb, eb):
@@ -505,6 +545,7 @@ class revoke_pdu(revoke_syntax):
"""
Send a "revoke" request for certificate(s) named by gski to parent associated with ca.
"""
+
parent = ca.parent
self = cls()
self.class_name = ca.parent_resource_class
@@ -545,6 +586,7 @@ class error_response_pdu(base_elt):
"""
Initialize an error_response PDU from an exception object.
"""
+
base_elt.__init__(self)
if exception is not None:
logger.debug("Constructing up-down error response from exception %s", exception)
@@ -570,6 +612,7 @@ class error_response_pdu(base_elt):
"""
Handle "error_response" PDU.
"""
+
if name == "status":
code = int(text)
if code not in self.codes:
@@ -586,6 +629,7 @@ class error_response_pdu(base_elt):
"""
Generate payload of "error_response" PDU.
"""
+
assert self.status in self.codes
elt = self.make_elt("status")
elt.text = str(self.status)
@@ -602,6 +646,7 @@ class error_response_pdu(base_elt):
Handle an error response. For now, just raise an exception,
perhaps figure out something more clever to do later.
"""
+
raise rpki.exceptions.UpstreamError(self.codes[self.status])
class message_pdu(base_elt):
@@ -611,16 +656,16 @@ class message_pdu(base_elt):
version = 1
- name2type = {
- "list" : list_pdu,
- "list_response" : list_response_pdu,
- "issue" : issue_pdu,
- "issue_response" : issue_response_pdu,
- "revoke" : revoke_pdu,
- "revoke_response" : revoke_response_pdu,
- "error_response" : error_response_pdu }
+ name2type = dict(
+ list = list_pdu,
+ list_response = list_response_pdu,
+ issue = issue_pdu,
+ issue_response = issue_response_pdu,
+ revoke = revoke_pdu,
+ revoke_response = revoke_response_pdu,
+ error_response = error_response_pdu)
- type2name = dict((v, k) for k, v in name2type.items())
+ type2name = dict((v, k) for k, v in name2type.iteritems())
error_pdu_type = error_response_pdu
@@ -628,6 +673,7 @@ class message_pdu(base_elt):
"""
Generate payload of message PDU.
"""
+
elt = self.make_elt("message", "version", "sender", "recipient", "type")
elt.extend(self.payload.toXML())
return elt
@@ -640,6 +686,7 @@ class message_pdu(base_elt):
attribute, so after some basic checks we have to instantiate the
right class object to handle whatever kind of PDU this is.
"""
+
assert name == "message", "Unexpected name %s, stack %s" % (name, stack)
assert self.version == int(attrs["version"])
self.sender = attrs["sender"]
@@ -652,6 +699,7 @@ class message_pdu(base_elt):
"""
Convert a message PDU to a string.
"""
+
return lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "UTF-8")
def serve_top_level(self, child, callback):
@@ -683,12 +731,14 @@ class message_pdu(base_elt):
"""
Log query we're handling. Separate method so rootd can override.
"""
+
logger.info("Serving %s query from child %s [sender %s, recipient %s]", self.type, child.child_handle, self.sender, self.recipient)
def serve_error(self, exception):
"""
Generate an error_response message PDU.
"""
+
r_msg = message_pdu()
r_msg.sender = self.recipient
r_msg.recipient = self.sender
@@ -701,6 +751,7 @@ class message_pdu(base_elt):
"""
Construct one message PDU.
"""
+
assert not cls.type2name[type(payload)].endswith("_response")
if sender is None:
sender = "tweedledee"
diff --git a/rpki/x509.py b/rpki/x509.py
index a7e4d17a..da949305 100644
--- a/rpki/x509.py
+++ b/rpki/x509.py
@@ -57,6 +57,7 @@ def base64_with_linebreaks(der):
Encode DER (really, anything) as Base64 text, with linebreaks to
keep the result (sort of) readable.
"""
+
b = base64.b64encode(der)
n = len(b)
return "\n" + "\n".join(b[i : min(i + 64, n)] for i in xrange(0, n, 64)) + "\n"
@@ -81,6 +82,27 @@ def first_rsync_uri(xia):
return uri
return None
+def sha1(data):
+ """
+ Calculate SHA-1 digest of some data.
+ Convenience wrapper around rpki.POW.Digest class.
+ """
+
+ d = rpki.POW.Digest(rpki.POW.SHA1_DIGEST)
+ d.update(data)
+ return d.digest()
+
+def sha256(data):
+ """
+ Calculate SHA-256 digest of some data.
+ Convenience wrapper around rpki.POW.Digest class.
+ """
+
+ d = rpki.POW.Digest(rpki.POW.SHA256_DIGEST)
+ d.update(data)
+ return d.digest()
+
+
class X501DN(object):
"""
Class to hold an X.501 Distinguished Name.
@@ -207,12 +229,14 @@ class DER_object(object):
"""
Test whether this object is empty.
"""
+
return all(getattr(self, a, None) is None for a in self.formats)
def clear(self):
"""
Make this object empty.
"""
+
for a in self.formats + self.other_clear:
setattr(self, a, None)
self.filename = None
@@ -223,6 +247,7 @@ class DER_object(object):
"""
Initialize a DER_object.
"""
+
self.clear()
if len(kw):
self.set(**kw)
@@ -271,6 +296,7 @@ class DER_object(object):
"""
Check for updates to a DER object that auto-updates from a file.
"""
+
if self.filename is None:
return
try:
@@ -297,10 +323,19 @@ class DER_object(object):
else:
self.lastfail = None
+ @property
+ def mtime(self):
+ """
+ Retrieve os.stat().st_mtime for auto-update files.
+ """
+
+ return os.stat(self.filename).st_mtime
+
def check(self):
"""
Perform basic checks on a DER object.
"""
+
self.check_auto_update()
assert not self.empty()
@@ -309,6 +344,7 @@ class DER_object(object):
Set the POW value of this object based on a PEM input value.
Subclasses may need to override this.
"""
+
assert self.empty()
self.POW = self.POW_class.pemRead(pem)
@@ -317,6 +353,7 @@ class DER_object(object):
Get the DER value of this object.
Subclasses may need to override this method.
"""
+
self.check()
if self.DER:
return self.DER
@@ -330,6 +367,7 @@ class DER_object(object):
Get the rpki.POW value of this object.
Subclasses may need to override this method.
"""
+
self.check()
if not self.POW: # pylint: disable=E0203
self.POW = self.POW_class.derRead(self.get_DER())
@@ -339,18 +377,21 @@ class DER_object(object):
"""
Get the Base64 encoding of the DER value of this object.
"""
+
return base64_with_linebreaks(self.get_DER())
def get_PEM(self):
"""
Get the PEM representation of this object.
"""
+
return self.get_POW().pemWrite()
def __cmp__(self, other):
"""
Compare two DER-encoded objects.
"""
+
if self is None and other is None:
return 0
elif self is None:
@@ -367,6 +408,7 @@ class DER_object(object):
Return hexadecimal string representation of SKI for this object.
Only work for subclasses that implement get_SKI().
"""
+
ski = self.get_SKI()
return ":".join(("%02X" % ord(i) for i in ski)) if ski else ""
@@ -375,6 +417,7 @@ class DER_object(object):
Calculate g(SKI) for this object. Only work for subclasses
that implement get_SKI().
"""
+
return base64.urlsafe_b64encode(self.get_SKI()).rstrip("=")
def hAKI(self):
@@ -382,6 +425,7 @@ class DER_object(object):
Return hexadecimal string representation of AKI for this
object. Only work for subclasses that implement get_AKI().
"""
+
aki = self.get_AKI()
return ":".join(("%02X" % ord(i) for i in aki)) if aki else ""
@@ -390,24 +434,28 @@ class DER_object(object):
Calculate g(AKI) for this object. Only work for subclasses
that implement get_AKI().
"""
+
return base64.urlsafe_b64encode(self.get_AKI()).rstrip("=")
def get_AKI(self):
"""
Get the AKI extension from this object, if supported.
"""
+
return self.get_POW().getAKI()
def get_SKI(self):
"""
Get the SKI extension from this object, if supported.
"""
+
return self.get_POW().getSKI()
def get_EKU(self):
"""
Get the Extended Key Usage extension from this object, if supported.
"""
+
return self.get_POW().getEKU()
def get_SIA(self):
@@ -415,6 +463,7 @@ class DER_object(object):
Get the SIA extension from this object. Only works for subclasses
that support getSIA().
"""
+
return self.get_POW().getSIA()
def get_sia_directory_uri(self):
@@ -422,6 +471,7 @@ class DER_object(object):
Get SIA directory (id-ad-caRepository) URI from this object.
Only works for subclasses that support getSIA().
"""
+
sia = self.get_POW().getSIA()
return None if sia is None else first_rsync_uri(sia[0])
@@ -430,6 +480,7 @@ class DER_object(object):
Get SIA manifest (id-ad-rpkiManifest) URI from this object.
Only works for subclasses that support getSIA().
"""
+
sia = self.get_POW().getSIA()
return None if sia is None else first_rsync_uri(sia[1])
@@ -438,6 +489,7 @@ class DER_object(object):
Get SIA object (id-ad-signedObject) URI from this object.
Only works for subclasses that support getSIA().
"""
+
sia = self.get_POW().getSIA()
return None if sia is None else first_rsync_uri(sia[2])
@@ -446,6 +498,7 @@ class DER_object(object):
Get the SIA extension from this object. Only works for subclasses
that support getAIA().
"""
+
return self.get_POW().getAIA()
def get_aia_uri(self):
@@ -453,6 +506,7 @@ class DER_object(object):
Get AIA (id-ad-caIssuers) URI from this object.
Only works for subclasses that support getAIA().
"""
+
return first_rsync_uri(self.get_POW().getAIA())
def get_basicConstraints(self):
@@ -460,6 +514,7 @@ class DER_object(object):
Get the basicConstraints extension from this object. Only works
for subclasses that support getExtension().
"""
+
return self.get_POW().getBasicConstraints()
def is_CA(self):
@@ -467,6 +522,7 @@ class DER_object(object):
Return True if and only if object has the basicConstraints
extension and its cA value is true.
"""
+
basicConstraints = self.get_basicConstraints()
return basicConstraints is not None and basicConstraints[0]
@@ -474,6 +530,7 @@ class DER_object(object):
"""
Get RFC 3779 resources as rpki.resource_set objects.
"""
+
resources = rpki.resource_set.resource_bag.from_POW_rfc3779(self.get_POW().getRFC3779())
try:
resources.valid_until = self.getNotAfter()
@@ -486,12 +543,14 @@ class DER_object(object):
"""
Convert from SQL storage format.
"""
+
return cls(DER = x)
def to_sql(self):
"""
Convert to SQL storage format.
"""
+
return self.get_DER()
def dumpasn1(self):
@@ -522,11 +581,11 @@ class DER_object(object):
provide more information, but should make sure to include at least
this information at the start of the tracking line.
"""
+
try:
- d = rpki.POW.Digest(rpki.POW.SHA1_DIGEST)
- d.update(self.get_DER())
- return "%s %s %s" % (uri, self.creation_timestamp,
- "".join(("%02X" % ord(b) for b in d.digest())))
+ return "%s %s %s" % (uri,
+ self.creation_timestamp,
+ "".join(("%02X" % ord(b) for b in sha1(self.get_DER()))))
except: # pylint: disable=W0702
return uri
@@ -534,12 +593,14 @@ class DER_object(object):
"""
Pickling protocol -- pickle the DER encoding.
"""
+
return self.get_DER()
def __setstate__(self, state):
"""
Pickling protocol -- unpickle the DER encoding.
"""
+
self.set(DER = state)
class X509(DER_object):
@@ -559,48 +620,56 @@ class X509(DER_object):
"""
Get the issuer of this certificate.
"""
+
return X501DN.from_POW(self.get_POW().getIssuer())
def getSubject(self):
"""
Get the subject of this certificate.
"""
+
return X501DN.from_POW(self.get_POW().getSubject())
def getNotBefore(self):
"""
Get the inception time of this certificate.
"""
+
return self.get_POW().getNotBefore()
def getNotAfter(self):
"""
Get the expiration time of this certificate.
"""
+
return self.get_POW().getNotAfter()
def getSerial(self):
"""
Get the serial number of this certificate.
"""
+
return self.get_POW().getSerial()
def getPublicKey(self):
"""
Extract the public key from this certificate.
"""
+
return PublicKey(POW = self.get_POW().getPublicKey())
def get_SKI(self):
"""
Get the SKI extension from this object.
"""
+
return self.get_POW().getSKI()
def expired(self):
"""
Test whether this certificate has expired.
"""
+
return self.getNotAfter() <= rpki.sundial.now()
def issue(self, keypair, subject_key, serial, sia, aia, crldp, notAfter,
@@ -743,6 +812,7 @@ class X509(DER_object):
"""
Issue a BPKI certificate with values taking from an existing certificate.
"""
+
return self.bpki_certify(
keypair = keypair,
subject_name = source_cert.getSubject(),
@@ -759,6 +829,7 @@ class X509(DER_object):
"""
Issue a self-signed BPKI CA certificate.
"""
+
return cls._bpki_certify(
keypair = keypair,
issuer_name = subject_name,
@@ -775,6 +846,7 @@ class X509(DER_object):
"""
Issue a normal BPKI certificate.
"""
+
assert keypair.get_public() == self.getPublicKey()
return self._bpki_certify(
keypair = keypair,
@@ -833,6 +905,7 @@ class X509(DER_object):
allowed cases. So this method allows X509, None, lists, and
tuples, and returns a tuple of X509 objects.
"""
+
if isinstance(chain, cls):
chain = (chain,)
return tuple(x for x in chain if x is not None)
@@ -842,6 +915,7 @@ class X509(DER_object):
"""
Time at which this object was created.
"""
+
return self.getNotBefore()
class PKCS10(DER_object):
@@ -869,6 +943,7 @@ class PKCS10(DER_object):
"""
Get the DER value of this certification request.
"""
+
self.check()
if self.DER:
return self.DER
@@ -881,6 +956,7 @@ class PKCS10(DER_object):
"""
Get the rpki.POW value of this certification request.
"""
+
self.check()
if not self.POW: # pylint: disable=E0203
self.POW = rpki.POW.PKCS10.derRead(self.get_DER())
@@ -890,18 +966,21 @@ class PKCS10(DER_object):
"""
Extract the subject name from this certification request.
"""
+
return X501DN.from_POW(self.get_POW().getSubject())
def getPublicKey(self):
"""
Extract the public key from this certification request.
"""
+
return PublicKey(POW = self.get_POW().getPublicKey())
def get_SKI(self):
"""
Compute SKI for public key from this certification request.
"""
+
return self.getPublicKey().get_SKI()
@@ -1150,6 +1229,7 @@ class PrivateKey(DER_object):
"""
Get the DER value of this keypair.
"""
+
self.check()
if self.DER:
return self.DER
@@ -1162,6 +1242,7 @@ class PrivateKey(DER_object):
"""
Get the rpki.POW value of this keypair.
"""
+
self.check()
if not self.POW: # pylint: disable=E0203
self.POW = rpki.POW.Asymmetric.derReadPrivate(self.get_DER())
@@ -1171,12 +1252,14 @@ class PrivateKey(DER_object):
"""
Get the PEM representation of this keypair.
"""
+
return self.get_POW().pemWritePrivate()
def _set_PEM(self, pem):
"""
Set the POW value of this keypair from a PEM string.
"""
+
assert self.empty()
self.POW = self.POW_class.pemReadPrivate(pem)
@@ -1184,18 +1267,21 @@ class PrivateKey(DER_object):
"""
Get the DER encoding of the public key from this keypair.
"""
+
return self.get_POW().derWritePublic()
def get_SKI(self):
"""
Calculate the SKI of this keypair.
"""
+
return self.get_POW().calculateSKI()
def get_public(self):
"""
Convert the public key of this keypair into a PublicKey object.
"""
+
return PublicKey(DER = self.get_public_DER())
class PublicKey(DER_object):
@@ -1209,6 +1295,7 @@ class PublicKey(DER_object):
"""
Get the DER value of this public key.
"""
+
self.check()
if self.DER:
return self.DER
@@ -1221,6 +1308,7 @@ class PublicKey(DER_object):
"""
Get the rpki.POW value of this public key.
"""
+
self.check()
if not self.POW: # pylint: disable=E0203
self.POW = rpki.POW.Asymmetric.derReadPublic(self.get_DER())
@@ -1230,12 +1318,14 @@ class PublicKey(DER_object):
"""
Get the PEM representation of this public key.
"""
+
return self.get_POW().pemWritePublic()
def _set_PEM(self, pem):
"""
Set the POW value of this public key from a PEM string.
"""
+
assert self.empty()
self.POW = self.POW_class.pemReadPublic(pem)
@@ -1243,6 +1333,7 @@ class PublicKey(DER_object):
"""
Calculate the SKI of this public key.
"""
+
return self.get_POW().calculateSKI()
class KeyParams(DER_object):
@@ -1266,6 +1357,7 @@ class RSA(PrivateKey):
"""
Generate a new keypair.
"""
+
if not quiet:
logger.debug("Generating new %d-bit RSA key", keylength)
if generate_insecure_debug_only_rsa_key is not None:
@@ -1348,6 +1440,7 @@ class CMS_object(DER_object):
"""
Get the DER value of this CMS_object.
"""
+
self.check()
if self.DER:
return self.DER
@@ -1360,6 +1453,7 @@ class CMS_object(DER_object):
"""
Get the rpki.POW value of this CMS_object.
"""
+
self.check()
if not self.POW: # pylint: disable=E0203
self.POW = self.POW_class.derRead(self.get_DER())
@@ -1369,6 +1463,7 @@ class CMS_object(DER_object):
"""
Extract signingTime from CMS signed attributes.
"""
+
return self.get_POW().signingTime()
def verify(self, ta):
@@ -1540,6 +1635,7 @@ class CMS_object(DER_object):
"""
Time at which this object was created.
"""
+
return self.get_signingTime()
@@ -1561,6 +1657,7 @@ class Wrapped_CMS_object(CMS_object):
"""
Get the inner content of this Wrapped_CMS_object.
"""
+
if self.content is None:
raise rpki.exceptions.CMSContentNotSet("Inner content of CMS object %r is not set" % self)
return self.content
@@ -1569,6 +1666,7 @@ class Wrapped_CMS_object(CMS_object):
"""
Set the (inner) content of this Wrapped_CMS_object, clearing the wrapper.
"""
+
self.clear()
self.content = content
@@ -1651,12 +1749,14 @@ class SignedManifest(DER_CMS_object):
"""
Get thisUpdate value from this manifest.
"""
+
return self.get_POW().getThisUpdate()
def getNextUpdate(self):
"""
Get nextUpdate value from this manifest.
"""
+
return self.get_POW().getNextUpdate()
@classmethod
@@ -1667,9 +1767,7 @@ class SignedManifest(DER_CMS_object):
filelist = []
for name, obj in names_and_objs:
- d = rpki.POW.Digest(rpki.POW.SHA256_DIGEST)
- d.update(obj.get_DER())
- filelist.append((name.rpartition("/")[2], d.digest()))
+ filelist.append((name.rpartition("/")[2], sha256(obj.get_DER())))
filelist.sort(key = lambda x: x[0])
obj = cls.POW_class()
@@ -1697,6 +1795,7 @@ class ROA(DER_CMS_object):
"""
Build a ROA.
"""
+
ipv4 = ipv4.to_POW_roa_tuple() if ipv4 else None
ipv6 = ipv6.to_POW_roa_tuple() if ipv6 else None
obj = cls.POW_class()
@@ -1712,6 +1811,7 @@ class ROA(DER_CMS_object):
Return a string containing data we want to log when tracking how
objects move through the RPKI system.
"""
+
msg = DER_CMS_object.tracking_data(self, uri)
try:
self.extract_if_needed()
@@ -1794,6 +1894,7 @@ class XML_CMS_object(Wrapped_CMS_object):
"""
Encode inner content for signing.
"""
+
return lxml.etree.tostring(self.get_content(),
pretty_print = True,
encoding = self.encoding,
@@ -1803,12 +1904,14 @@ class XML_CMS_object(Wrapped_CMS_object):
"""
Decode XML and set inner content.
"""
+
self.content = lxml.etree.fromstring(xml)
def pretty_print_content(self):
"""
Pretty print XML content of this message.
"""
+
return lxml.etree.tostring(self.get_content(),
pretty_print = True,
encoding = self.encoding,
@@ -1818,6 +1921,7 @@ class XML_CMS_object(Wrapped_CMS_object):
"""
Handle XML RelaxNG schema check.
"""
+
try:
self.schema.assertValid(self.get_content())
except lxml.etree.DocumentInvalid:
@@ -1830,6 +1934,7 @@ class XML_CMS_object(Wrapped_CMS_object):
"""
Write DER of current message to disk, for debugging.
"""
+
f = open(prefix + rpki.sundial.now().isoformat() + "Z.cms", "wb")
f.write(self.get_DER())
f.close()
@@ -1838,6 +1943,7 @@ class XML_CMS_object(Wrapped_CMS_object):
"""
Wrap an XML PDU in CMS and return its DER encoding.
"""
+
if self.saxify is None:
self.set_content(msg)
else:
@@ -1853,6 +1959,7 @@ class XML_CMS_object(Wrapped_CMS_object):
"""
Unwrap a CMS-wrapped XML PDU and return Python objects.
"""
+
if self.dump_inbound_cms:
self.dump_inbound_cms.dump(self)
self.verify(ta)
@@ -1869,6 +1976,7 @@ class XML_CMS_object(Wrapped_CMS_object):
timestamp. Raises an exception if the recorded timestamp is more
recent, otherwise returns the new timestamp.
"""
+
new_timestamp = self.get_signingTime()
if timestamp is not None and timestamp > new_timestamp:
if context:
@@ -1884,6 +1992,7 @@ class XML_CMS_object(Wrapped_CMS_object):
"last_cms_timestamp" field of an SQL object and stores the new
timestamp back in that same field.
"""
+
obj.last_cms_timestamp = self.check_replay(obj.last_cms_timestamp, *context)
obj.sql_mark_dirty()
@@ -1913,6 +2022,7 @@ class Ghostbuster(Wrapped_CMS_object):
Encode inner content for signing. At the moment we're treating
the VCard as an opaque byte string, so no encoding needed here.
"""
+
return self.get_content()
def decode(self, vcard):
@@ -1920,6 +2030,7 @@ class Ghostbuster(Wrapped_CMS_object):
Decode XML and set inner content. At the moment we're treating
the VCard as an opaque byte string, so no encoding needed here.
"""
+
self.content = vcard
@classmethod
@@ -1927,6 +2038,7 @@ class Ghostbuster(Wrapped_CMS_object):
"""
Build a Ghostbuster record.
"""
+
self = cls()
self.set_content(vcard)
self.sign(keypair, certs)
@@ -1944,6 +2056,7 @@ class CRL(DER_object):
"""
Get the DER value of this CRL.
"""
+
self.check()
if self.DER:
return self.DER
@@ -1956,6 +2069,7 @@ class CRL(DER_object):
"""
Get the rpki.POW value of this CRL.
"""
+
self.check()
if not self.POW: # pylint: disable=E0203
self.POW = rpki.POW.CRL.derRead(self.get_DER())
@@ -1965,24 +2079,28 @@ class CRL(DER_object):
"""
Get thisUpdate value from this CRL.
"""
+
return self.get_POW().getThisUpdate()
def getNextUpdate(self):
"""
Get nextUpdate value from this CRL.
"""
+
return self.get_POW().getNextUpdate()
def getIssuer(self):
"""
Get issuer value of this CRL.
"""
+
return X501DN.from_POW(self.get_POW().getIssuer())
def getCRLNumber(self):
"""
Get CRL Number value for this CRL.
"""
+
return self.get_POW().getCRLNumber()
@classmethod
@@ -1990,6 +2108,7 @@ class CRL(DER_object):
"""
Generate a new CRL.
"""
+
crl = rpki.POW.CRL()
crl.setVersion(version)
crl.setIssuer(issuer.getSubject().get_POW())
@@ -2006,6 +2125,7 @@ class CRL(DER_object):
"""
Time at which this object was created.
"""
+
return self.getThisUpdate()
## @var uri_dispatch_map
@@ -2024,4 +2144,5 @@ def uri_dispatch(uri):
"""
Return the Python class object corresponding to a given URI.
"""
+
return uri_dispatch_map[os.path.splitext(uri)[1]]
diff --git a/rpki/xml_utils.py b/rpki/xml_utils.py
index c276ce98..9b443d0b 100644
--- a/rpki/xml_utils.py
+++ b/rpki/xml_utils.py
@@ -32,11 +32,15 @@
XML utilities.
"""
+import logging
import xml.sax
import lxml.sax
import lxml.etree
import rpki.exceptions
+logger = logging.getLogger(__name__)
+
+
class sax_handler(xml.sax.handler.ContentHandler):
"""
SAX handler for RPKI protocols.
@@ -56,6 +60,7 @@ class sax_handler(xml.sax.handler.ContentHandler):
"""
Initialize SAX handler.
"""
+
xml.sax.handler.ContentHandler.__init__(self)
self.text = ""
self.stack = []
@@ -64,18 +69,21 @@ class sax_handler(xml.sax.handler.ContentHandler):
"""
Redirect startElementNS() events to startElement().
"""
+
return self.startElement(name[1], attrs)
def endElementNS(self, name, qname):
"""
Redirect endElementNS() events to endElement().
"""
+
return self.endElement(name[1])
def characters(self, content):
"""
Accumulate a chuck of element content (text).
"""
+
self.text += content
def startElement(self, name, attrs):
@@ -111,6 +119,7 @@ class sax_handler(xml.sax.handler.ContentHandler):
Handle endElement() events. Mostly this means handling any
accumulated element text.
"""
+
text = self.text.encode("ascii").strip()
self.text = ""
self.stack[-1].endElement(self.stack, name, text)
@@ -120,6 +129,7 @@ class sax_handler(xml.sax.handler.ContentHandler):
"""
Create a one-off SAX parser, parse an ETree, return the result.
"""
+
self = cls()
lxml.sax.saxify(elt, self)
return self.result
@@ -128,6 +138,7 @@ class sax_handler(xml.sax.handler.ContentHandler):
"""
Handle top-level PDU for this protocol.
"""
+
assert name == self.name and attrs["version"] == self.version
return self.pdu()
@@ -154,6 +165,7 @@ class base_elt(object):
"""
Default startElement() handler: just process attributes.
"""
+
if name not in self.elements:
assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
self.read_attrs(attrs)
@@ -162,6 +174,7 @@ class base_elt(object):
"""
Default endElement() handler: just pop the stack.
"""
+
assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
stack.pop()
@@ -169,12 +182,14 @@ class base_elt(object):
"""
Default toXML() element generator.
"""
+
return self.make_elt()
def read_attrs(self, attrs):
"""
Template-driven attribute reader.
"""
+
for key in self.attributes:
val = attrs.get(key, None)
if isinstance(val, str) and val.isdigit() and not key.endswith("_handle"):
@@ -187,6 +202,7 @@ class base_elt(object):
"""
XML element constructor.
"""
+
elt = lxml.etree.Element(self.xmlns + self.element_name, nsmap = self.nsmap)
for key in self.attributes:
val = getattr(self, key, None)
@@ -201,6 +217,7 @@ class base_elt(object):
"""
Constructor for Base64-encoded subelement.
"""
+
if value is not None and not value.empty():
lxml.etree.SubElement(elt, self.xmlns + name, nsmap = self.nsmap).text = value.get_Base64()
@@ -208,6 +225,7 @@ class base_elt(object):
"""
Convert a base_elt object to string format.
"""
+
return lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
@classmethod
@@ -215,6 +233,7 @@ class base_elt(object):
"""
Generic PDU constructor.
"""
+
self = cls()
for k, v in kargs.items():
if isinstance(v, bool):
@@ -235,6 +254,7 @@ class text_elt(base_elt):
"""
Extract text from parsed XML.
"""
+
base_elt.endElement(self, stack, name, text)
setattr(self, self.text_attribute, text)
@@ -242,6 +262,7 @@ class text_elt(base_elt):
"""
Insert text into generated XML.
"""
+
elt = self.make_elt()
elt.text = getattr(self, self.text_attribute) or None
return elt
@@ -258,6 +279,7 @@ class data_elt(base_elt):
that sub-elements are Base64-encoded using the sql_template
mechanism.
"""
+
if name in self.elements:
elt_type = self.sql_template.map.get(name)
assert elt_type is not None, "Couldn't find element type for %s, stack %s" % (name, stack)
@@ -271,6 +293,7 @@ class data_elt(base_elt):
Default element generator for SQL-based objects. This assumes
that sub-elements are Base64-encoded DER objects.
"""
+
elt = self.make_elt()
for i in self.elements:
self.make_b64elt(elt, i, getattr(self, i, None))
@@ -280,6 +303,7 @@ class data_elt(base_elt):
"""
Construct a reply PDU.
"""
+
if r_pdu is None:
r_pdu = self.__class__()
self.make_reply_clone_hook(r_pdu)
@@ -297,6 +321,7 @@ class data_elt(base_elt):
"""
Overridable hook.
"""
+
pass
def serve_fetch_one(self):
@@ -304,6 +329,7 @@ class data_elt(base_elt):
Find the object on which a get, set, or destroy method should
operate.
"""
+
r = self.serve_fetch_one_maybe()
if r is None:
raise rpki.exceptions.NotFound
@@ -313,12 +339,14 @@ class data_elt(base_elt):
"""
Overridable hook.
"""
+
cb()
def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
"""
Overridable hook.
"""
+
cb()
def serve_create(self, r_msg, cb, eb):
@@ -371,6 +399,7 @@ class data_elt(base_elt):
"""
Handle a get action.
"""
+
r_pdu = self.serve_fetch_one()
self.make_reply(r_pdu)
r_msg.append(r_pdu)
@@ -380,6 +409,7 @@ class data_elt(base_elt):
"""
Handle a list action for non-self objects.
"""
+
for r_pdu in self.serve_fetch_all():
self.make_reply(r_pdu)
r_msg.append(r_pdu)
@@ -389,12 +419,14 @@ class data_elt(base_elt):
"""
Overridable hook.
"""
+
cb()
def serve_destroy(self, r_msg, cb, eb):
"""
Handle a destroy action.
"""
+
def done():
db_pdu.sql_delete()
r_msg.append(self.make_reply())
@@ -406,19 +438,17 @@ class data_elt(base_elt):
"""
Action dispatch handler.
"""
- dispatch = { "create" : self.serve_create,
- "set" : self.serve_set,
- "get" : self.serve_get,
- "list" : self.serve_list,
- "destroy" : self.serve_destroy }
- if self.action not in dispatch:
+
+ method = getattr(self, "serve_" + self.action, None)
+ if method is None:
raise rpki.exceptions.BadQuery("Unexpected query: action %s" % self.action)
- dispatch[self.action](r_msg, cb, eb)
+ method(r_msg, cb, eb)
def unimplemented_control(self, *controls):
"""
Uniform handling for unimplemented control operations.
"""
+
unimplemented = [x for x in controls if getattr(self, x, False)]
if unimplemented:
raise rpki.exceptions.NotImplementedYet("Unimplemented control %s" % ", ".join(unimplemented))
@@ -432,6 +462,7 @@ class msg(list):
"""
Handle top-level PDU.
"""
+
if name == "msg":
assert self.version == int(attrs["version"])
self.type = attrs["type"]
@@ -445,6 +476,7 @@ class msg(list):
"""
Handle top-level PDU.
"""
+
assert name == "msg", "Unexpected name %s, stack %s" % (name, stack)
assert len(stack) == 1
stack.pop()
@@ -453,14 +485,16 @@ class msg(list):
"""
Convert msg object to string.
"""
+
return lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
def toXML(self):
"""
Generate top-level PDU.
"""
+
elt = lxml.etree.Element(self.xmlns + "msg", nsmap = self.nsmap, version = str(self.version), type = self.type)
- elt.extend([i.toXML() for i in self])
+ elt.extend(i.toXML() for i in self)
return elt
@classmethod
@@ -468,6 +502,7 @@ class msg(list):
"""
Create a query PDU.
"""
+
self = cls(args)
self.type = "query"
return self
@@ -477,6 +512,7 @@ class msg(list):
"""
Create a reply PDU.
"""
+
self = cls(args)
self.type = "reply"
return self
@@ -485,10 +521,12 @@ class msg(list):
"""
Is this msg a query?
"""
+
return self.type == "query"
def is_reply(self):
"""
Is this msg a reply?
"""
+
return self.type == "reply"
diff --git a/schemas/relaxng/left-right-schema.rnc b/schemas/relaxng/left-right.rnc
index 201f8ff0..201f8ff0 100644
--- a/schemas/relaxng/left-right-schema.rnc
+++ b/schemas/relaxng/left-right.rnc
diff --git a/schemas/relaxng/left-right-schema.rng b/schemas/relaxng/left-right.rng
index c5596a2f..15dd2fa1 100644
--- a/schemas/relaxng/left-right-schema.rng
+++ b/schemas/relaxng/left-right.rng
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: left-right-schema.rnc 5902 2014-07-18 16:37:04Z sra $
+ $Id: left-right.rnc 5903 2014-07-18 17:08:13Z sra $
RelaxNG schema for RPKI left-right protocol.
diff --git a/schemas/relaxng/myrpki.rng b/schemas/relaxng/myrpki.rng
index 8c7473eb..3beafe8f 100644
--- a/schemas/relaxng/myrpki.rng
+++ b/schemas/relaxng/myrpki.rng
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: myrpki.rnc 5757 2014-04-05 22:42:12Z sra $
+ $Id: myrpki.rnc 5876 2014-06-26 19:00:12Z sra $
RelaxNG schema for MyRPKI XML messages.
diff --git a/schemas/relaxng/publication-schema.rnc b/schemas/relaxng/publication-control.rnc
index fdf38c9e..ac59c617 100644
--- a/schemas/relaxng/publication-schema.rnc
+++ b/schemas/relaxng/publication-control.rnc
@@ -19,7 +19,7 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-default namespace = "http://www.hactrn.net/uris/rpki/publication-spec/"
+default namespace = "http://www.hactrn.net/uris/rpki/publication-control/"
version = "1"
@@ -32,12 +32,10 @@ start = element msg {
}
# PDUs allowed in a query
-query_elt = ( config_query | client_query | certificate_query | crl_query |
- manifest_query | roa_query | ghostbuster_query )
+query_elt = client_query
# PDUs allowed in a reply
-reply_elt = ( config_reply | client_reply | certificate_reply | crl_reply |
- manifest_reply | roa_reply | ghostbuster_reply | report_error_reply )
+reply_elt = ( client_reply | report_error_reply )
# Tag attributes for bulk operations
tag = attribute tag { xsd:token {maxLength="1024" } }
@@ -58,17 +56,7 @@ uri = attribute uri { uri_t }
# hierarchy delimiter.
object_handle = xsd:string { maxLength="255" pattern="[\-_A-Za-z0-9/]+" }
-# <config/> element (use restricted to repository operator)
-# config_handle attribute, create, list, and destroy commands omitted deliberately, see code for details
-
-config_payload = (element bpki_crl { base64 }?)
-
-config_query |= element config { attribute action { "set" }, tag?, config_payload }
-config_reply |= element config { attribute action { "set" }, tag? }
-config_query |= element config { attribute action { "get" }, tag? }
-config_reply |= element config { attribute action { "get" }, tag?, config_payload }
-
-# <client/> element (use restricted to repository operator)
+# <client/> element
client_handle = attribute client_handle { object_handle }
@@ -87,41 +75,6 @@ client_reply |= element client { attribute action { "list" }, tag?, client_ha
client_query |= element client { attribute action { "destroy" }, tag?, client_handle }
client_reply |= element client { attribute action { "destroy" }, tag?, client_handle }
-# <certificate/> element
-
-certificate_query |= element certificate { attribute action { "publish" }, tag?, uri, base64 }
-certificate_reply |= element certificate { attribute action { "publish" }, tag?, uri }
-certificate_query |= element certificate { attribute action { "withdraw" }, tag?, uri }
-certificate_reply |= element certificate { attribute action { "withdraw" }, tag?, uri }
-
-# <crl/> element
-
-crl_query |= element crl { attribute action { "publish" }, tag?, uri, base64 }
-crl_reply |= element crl { attribute action { "publish" }, tag?, uri }
-crl_query |= element crl { attribute action { "withdraw" }, tag?, uri }
-crl_reply |= element crl { attribute action { "withdraw" }, tag?, uri }
-
-# <manifest/> element
-
-manifest_query |= element manifest { attribute action { "publish" }, tag?, uri, base64 }
-manifest_reply |= element manifest { attribute action { "publish" }, tag?, uri }
-manifest_query |= element manifest { attribute action { "withdraw" }, tag?, uri }
-manifest_reply |= element manifest { attribute action { "withdraw" }, tag?, uri }
-
-# <roa/> element
-
-roa_query |= element roa { attribute action { "publish" }, tag?, uri, base64 }
-roa_reply |= element roa { attribute action { "publish" }, tag?, uri }
-roa_query |= element roa { attribute action { "withdraw" }, tag?, uri }
-roa_reply |= element roa { attribute action { "withdraw" }, tag?, uri }
-
-# <ghostbuster/> element
-
-ghostbuster_query |= element ghostbuster { attribute action { "publish" }, tag?, uri, base64 }
-ghostbuster_reply |= element ghostbuster { attribute action { "publish" }, tag?, uri }
-ghostbuster_query |= element ghostbuster { attribute action { "withdraw" }, tag?, uri }
-ghostbuster_reply |= element ghostbuster { attribute action { "withdraw" }, tag?, uri }
-
# <report_error/> element
error = xsd:token { maxLength="1024" }
diff --git a/schemas/relaxng/publication-control.rng b/schemas/relaxng/publication-control.rng
new file mode 100644
index 00000000..606deb53
--- /dev/null
+++ b/schemas/relaxng/publication-control.rng
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ $Id: publication-control.rnc 5903 2014-07-18 17:08:13Z sra $
+
+ RelaxNG schema for RPKI publication protocol.
+
+ Copyright (C) 2012- -2014 Dragon Research Labs ("DRL")
+ Portions copyright (C) 2009- -2011 Internet Systems Consortium ("ISC")
+ Portions copyright (C) 2007- -2008 American Registry for Internet Numbers ("ARIN")
+
+ Permission to use, copy, modify, and distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notices and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+ ISC, OR ARIN 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.
+-->
+<grammar ns="http://www.hactrn.net/uris/rpki/publication-control/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+ <define name="version">
+ <value>1</value>
+ </define>
+ <!-- Top level PDU -->
+ <start>
+ <element name="msg">
+ <attribute name="version">
+ <data type="positiveInteger">
+ <param name="maxInclusive">1</param>
+ </data>
+ </attribute>
+ <choice>
+ <group>
+ <attribute name="type">
+ <value>query</value>
+ </attribute>
+ <zeroOrMore>
+ <ref name="query_elt"/>
+ </zeroOrMore>
+ </group>
+ <group>
+ <attribute name="type">
+ <value>reply</value>
+ </attribute>
+ <zeroOrMore>
+ <ref name="reply_elt"/>
+ </zeroOrMore>
+ </group>
+ </choice>
+ </element>
+ </start>
+ <!-- PDUs allowed in a query -->
+ <define name="query_elt">
+ <ref name="client_query"/>
+ </define>
+ <!-- PDUs allowed in a reply -->
+ <define name="reply_elt">
+ <choice>
+ <ref name="client_reply"/>
+ <ref name="report_error_reply"/>
+ </choice>
+ </define>
+ <!-- Tag attributes for bulk operations -->
+ <define name="tag">
+ <attribute name="tag">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
+ </attribute>
+ </define>
+ <!--
+ Base64 encoded DER stuff
+ base64 = xsd:base64Binary { maxLength="512000" }
+
+ Sadly, it turns out that CRLs can in fact get longer than this for an active CA.
+ Remove length limit for now, think about whether to put it back later.
+ -->
+ <define name="base64">
+ <data type="base64Binary"/>
+ </define>
+ <!-- Publication URLs -->
+ <define name="uri_t">
+ <data type="anyURI">
+ <param name="maxLength">4096</param>
+ </data>
+ </define>
+ <define name="uri">
+ <attribute name="uri">
+ <ref name="uri_t"/>
+ </attribute>
+ </define>
+ <!--
+ Handles on remote objects (replaces passing raw SQL IDs). NB:
+ Unlike the up-down protocol, handles in this protocol allow "/" as a
+ hierarchy delimiter.
+ -->
+ <define name="object_handle">
+ <data type="string">
+ <param name="maxLength">255</param>
+ <param name="pattern">[\-_A-Za-z0-9/]+</param>
+ </data>
+ </define>
+ <!-- <client/> element -->
+ <define name="client_handle">
+ <attribute name="client_handle">
+ <ref name="object_handle"/>
+ </attribute>
+ </define>
+ <define name="client_bool">
+ <optional>
+ <attribute name="clear_replay_protection">
+ <value>yes</value>
+ </attribute>
+ </optional>
+ </define>
+ <define name="client_payload">
+ <optional>
+ <attribute name="base_uri">
+ <ref name="uri_t"/>
+ </attribute>
+ </optional>
+ <optional>
+ <element name="bpki_cert">
+ <ref name="base64"/>
+ </element>
+ </optional>
+ <optional>
+ <element name="bpki_glue">
+ <ref name="base64"/>
+ </element>
+ </optional>
+ </define>
+ <define name="client_query" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>create</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ <ref name="client_bool"/>
+ <ref name="client_payload"/>
+ </element>
+ </define>
+ <define name="client_reply" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>create</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ </element>
+ </define>
+ <define name="client_query" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>set</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ <ref name="client_bool"/>
+ <ref name="client_payload"/>
+ </element>
+ </define>
+ <define name="client_reply" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>set</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ </element>
+ </define>
+ <define name="client_query" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>get</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ </element>
+ </define>
+ <define name="client_reply" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>get</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ <ref name="client_payload"/>
+ </element>
+ </define>
+ <define name="client_query" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>list</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ </element>
+ </define>
+ <define name="client_reply" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>list</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ <ref name="client_payload"/>
+ </element>
+ </define>
+ <define name="client_query" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>destroy</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ </element>
+ </define>
+ <define name="client_reply" combine="choice">
+ <element name="client">
+ <attribute name="action">
+ <value>destroy</value>
+ </attribute>
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="client_handle"/>
+ </element>
+ </define>
+ <!-- <report_error/> element -->
+ <define name="error">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
+ </define>
+ <define name="report_error_reply">
+ <element name="report_error">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <attribute name="error_code">
+ <ref name="error"/>
+ </attribute>
+ <optional>
+ <data type="string">
+ <param name="maxLength">512000</param>
+ </data>
+ </optional>
+ </element>
+ </define>
+</grammar>
+<!--
+ Local Variables:
+ indent-tabs-mode: nil
+ comment-start: "# "
+ comment-start-skip: "#[ \t]*"
+ End:
+-->
diff --git a/schemas/relaxng/publication-schema.rng b/schemas/relaxng/publication-schema.rng
deleted file mode 100644
index 482fa477..00000000
--- a/schemas/relaxng/publication-schema.rng
+++ /dev/null
@@ -1,577 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- $Id: publication-schema.rnc 5902 2014-07-18 16:37:04Z sra $
-
- RelaxNG schema for RPKI publication protocol.
-
- Copyright (C) 2012- -2014 Dragon Research Labs ("DRL")
- Portions copyright (C) 2009- -2011 Internet Systems Consortium ("ISC")
- Portions copyright (C) 2007- -2008 American Registry for Internet Numbers ("ARIN")
-
- Permission to use, copy, modify, and distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notices and this permission notice appear in all copies.
-
- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
- ISC, OR ARIN 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.
--->
-<grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
- <define name="version">
- <value>1</value>
- </define>
- <!-- Top level PDU -->
- <start>
- <element name="msg">
- <attribute name="version">
- <data type="positiveInteger">
- <param name="maxInclusive">1</param>
- </data>
- </attribute>
- <choice>
- <group>
- <attribute name="type">
- <value>query</value>
- </attribute>
- <zeroOrMore>
- <ref name="query_elt"/>
- </zeroOrMore>
- </group>
- <group>
- <attribute name="type">
- <value>reply</value>
- </attribute>
- <zeroOrMore>
- <ref name="reply_elt"/>
- </zeroOrMore>
- </group>
- </choice>
- </element>
- </start>
- <!-- PDUs allowed in a query -->
- <define name="query_elt">
- <choice>
- <ref name="config_query"/>
- <ref name="client_query"/>
- <ref name="certificate_query"/>
- <ref name="crl_query"/>
- <ref name="manifest_query"/>
- <ref name="roa_query"/>
- <ref name="ghostbuster_query"/>
- </choice>
- </define>
- <!-- PDUs allowed in a reply -->
- <define name="reply_elt">
- <choice>
- <ref name="config_reply"/>
- <ref name="client_reply"/>
- <ref name="certificate_reply"/>
- <ref name="crl_reply"/>
- <ref name="manifest_reply"/>
- <ref name="roa_reply"/>
- <ref name="ghostbuster_reply"/>
- <ref name="report_error_reply"/>
- </choice>
- </define>
- <!-- Tag attributes for bulk operations -->
- <define name="tag">
- <attribute name="tag">
- <data type="token">
- <param name="maxLength">1024</param>
- </data>
- </attribute>
- </define>
- <!--
- Base64 encoded DER stuff
- base64 = xsd:base64Binary { maxLength="512000" }
-
- Sadly, it turns out that CRLs can in fact get longer than this for an active CA.
- Remove length limit for now, think about whether to put it back later.
- -->
- <define name="base64">
- <data type="base64Binary"/>
- </define>
- <!-- Publication URLs -->
- <define name="uri_t">
- <data type="anyURI">
- <param name="maxLength">4096</param>
- </data>
- </define>
- <define name="uri">
- <attribute name="uri">
- <ref name="uri_t"/>
- </attribute>
- </define>
- <!--
- Handles on remote objects (replaces passing raw SQL IDs). NB:
- Unlike the up-down protocol, handles in this protocol allow "/" as a
- hierarchy delimiter.
- -->
- <define name="object_handle">
- <data type="string">
- <param name="maxLength">255</param>
- <param name="pattern">[\-_A-Za-z0-9/]+</param>
- </data>
- </define>
- <!--
- <config/> element (use restricted to repository operator)
- config_handle attribute, create, list, and destroy commands omitted deliberately, see code for details
- -->
- <define name="config_payload">
- <optional>
- <element name="bpki_crl">
- <ref name="base64"/>
- </element>
- </optional>
- </define>
- <define name="config_query" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>set</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="config_payload"/>
- </element>
- </define>
- <define name="config_reply" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>set</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- </element>
- </define>
- <define name="config_query" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>get</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- </element>
- </define>
- <define name="config_reply" combine="choice">
- <element name="config">
- <attribute name="action">
- <value>get</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="config_payload"/>
- </element>
- </define>
- <!-- <client/> element (use restricted to repository operator) -->
- <define name="client_handle">
- <attribute name="client_handle">
- <ref name="object_handle"/>
- </attribute>
- </define>
- <define name="client_bool">
- <optional>
- <attribute name="clear_replay_protection">
- <value>yes</value>
- </attribute>
- </optional>
- </define>
- <define name="client_payload">
- <optional>
- <attribute name="base_uri">
- <ref name="uri_t"/>
- </attribute>
- </optional>
- <optional>
- <element name="bpki_cert">
- <ref name="base64"/>
- </element>
- </optional>
- <optional>
- <element name="bpki_glue">
- <ref name="base64"/>
- </element>
- </optional>
- </define>
- <define name="client_query" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>create</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- <ref name="client_bool"/>
- <ref name="client_payload"/>
- </element>
- </define>
- <define name="client_reply" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>create</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- </element>
- </define>
- <define name="client_query" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>set</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- <ref name="client_bool"/>
- <ref name="client_payload"/>
- </element>
- </define>
- <define name="client_reply" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>set</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- </element>
- </define>
- <define name="client_query" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>get</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- </element>
- </define>
- <define name="client_reply" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>get</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- <ref name="client_payload"/>
- </element>
- </define>
- <define name="client_query" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>list</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- </element>
- </define>
- <define name="client_reply" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>list</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- <ref name="client_payload"/>
- </element>
- </define>
- <define name="client_query" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>destroy</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- </element>
- </define>
- <define name="client_reply" combine="choice">
- <element name="client">
- <attribute name="action">
- <value>destroy</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="client_handle"/>
- </element>
- </define>
- <!-- <certificate/> element -->
- <define name="certificate_query" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
- </define>
- <define name="certificate_reply" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="certificate_query" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="certificate_reply" combine="choice">
- <element name="certificate">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <!-- <crl/> element -->
- <define name="crl_query" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
- </define>
- <define name="crl_reply" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="crl_query" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="crl_reply" combine="choice">
- <element name="crl">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <!-- <manifest/> element -->
- <define name="manifest_query" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
- </define>
- <define name="manifest_reply" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="manifest_query" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="manifest_reply" combine="choice">
- <element name="manifest">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <!-- <roa/> element -->
- <define name="roa_query" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
- </define>
- <define name="roa_reply" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="roa_query" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="roa_reply" combine="choice">
- <element name="roa">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <!-- <ghostbuster/> element -->
- <define name="ghostbuster_query" combine="choice">
- <element name="ghostbuster">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- <ref name="base64"/>
- </element>
- </define>
- <define name="ghostbuster_reply" combine="choice">
- <element name="ghostbuster">
- <attribute name="action">
- <value>publish</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="ghostbuster_query" combine="choice">
- <element name="ghostbuster">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <define name="ghostbuster_reply" combine="choice">
- <element name="ghostbuster">
- <attribute name="action">
- <value>withdraw</value>
- </attribute>
- <optional>
- <ref name="tag"/>
- </optional>
- <ref name="uri"/>
- </element>
- </define>
- <!-- <report_error/> element -->
- <define name="error">
- <data type="token">
- <param name="maxLength">1024</param>
- </data>
- </define>
- <define name="report_error_reply">
- <element name="report_error">
- <optional>
- <ref name="tag"/>
- </optional>
- <attribute name="error_code">
- <ref name="error"/>
- </attribute>
- <optional>
- <data type="string">
- <param name="maxLength">512000</param>
- </data>
- </optional>
- </element>
- </define>
-</grammar>
-<!--
- Local Variables:
- indent-tabs-mode: nil
- comment-start: "# "
- comment-start-skip: "#[ \t]*"
- End:
--->
diff --git a/schemas/relaxng/publication.rnc b/schemas/relaxng/publication.rnc
new file mode 100644
index 00000000..f3d1f94e
--- /dev/null
+++ b/schemas/relaxng/publication.rnc
@@ -0,0 +1,111 @@
+# $Id$
+#
+# RelaxNG schema for RPKI publication protocol, from current I-D.
+#
+# Copyright (c) 2014 IETF Trust and the persons identified as authors
+# of the code. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# * Neither the name of Internet Society, IETF or IETF Trust, nor the
+# names of specific contributors, may be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+default namespace =
+ "http://www.hactrn.net/uris/rpki/publication-spec/"
+
+# This is version 3 of the protocol.
+
+version = "3"
+
+# Top level PDU is either a query or a reply.
+
+start |= element msg {
+ attribute version { version },
+ attribute type { "query" },
+ query_elt*
+}
+
+start |= element msg {
+ attribute version { version },
+ attribute type { "reply" },
+ reply_elt*
+}
+
+# PDUs allowed in queries and replies.
+
+query_elt = publish_query | withdraw_query | list_query
+reply_elt = publish_reply | withdraw_reply | list_reply | report_error_reply
+
+# Tag attributes for bulk operations.
+
+tag = attribute tag { xsd:token { maxLength="1024" } }
+
+# Base64 encoded DER stuff.
+
+base64 = xsd:base64Binary
+
+# Publication URIs.
+
+uri = attribute uri { xsd:anyURI { maxLength="4096" } }
+
+# Digest of objects being withdrawn
+
+hash = attribute hash { xsd:string { pattern = "[0-9a-fA-F]+" } }
+
+# Error codes.
+
+error = xsd:token { maxLength="1024" }
+
+# <publish/> element
+
+publish_query = element publish { tag?, uri, hash?, base64 }
+publish_reply = element publish { tag?, uri }
+
+# <withdraw/> element
+
+withdraw_query = element withdraw { tag?, uri, hash }
+withdraw_reply = element withdraw { tag?, uri }
+
+# <list/> element
+
+list_query = element list { tag? }
+list_reply = element list { tag?, uri, hash }
+
+# <report_error/> element
+
+report_error_reply = element report_error {
+ tag?,
+ attribute error_code { error },
+ xsd:string { maxLength="512000" }?
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# comment-start: "# "
+# comment-start-skip: "#[ \t]*"
+# End:
diff --git a/schemas/relaxng/publication.rng b/schemas/relaxng/publication.rng
new file mode 100644
index 00000000..5e72407e
--- /dev/null
+++ b/schemas/relaxng/publication.rng
@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ $Id: publication.rnc 5896 2014-07-15 19:34:32Z sra $
+
+ RelaxNG schema for RPKI publication protocol, from current I-D.
+
+ Copyright (c) 2014 IETF Trust and the persons identified as authors
+ of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Internet Society, IETF or IETF Trust, nor the
+ names of specific contributors, may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+-->
+<grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+ <!-- This is version 3 of the protocol. -->
+ <define name="version">
+ <value>3</value>
+ </define>
+ <!-- Top level PDU is either a query or a reply. -->
+ <start combine="choice">
+ <element name="msg">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="type">
+ <value>query</value>
+ </attribute>
+ <zeroOrMore>
+ <ref name="query_elt"/>
+ </zeroOrMore>
+ </element>
+ </start>
+ <start combine="choice">
+ <element name="msg">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="type">
+ <value>reply</value>
+ </attribute>
+ <zeroOrMore>
+ <ref name="reply_elt"/>
+ </zeroOrMore>
+ </element>
+ </start>
+ <!-- PDUs allowed in queries and replies. -->
+ <define name="query_elt">
+ <choice>
+ <ref name="publish_query"/>
+ <ref name="withdraw_query"/>
+ <ref name="list_query"/>
+ </choice>
+ </define>
+ <define name="reply_elt">
+ <choice>
+ <ref name="publish_reply"/>
+ <ref name="withdraw_reply"/>
+ <ref name="list_reply"/>
+ <ref name="report_error_reply"/>
+ </choice>
+ </define>
+ <!-- Tag attributes for bulk operations. -->
+ <define name="tag">
+ <attribute name="tag">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
+ </attribute>
+ </define>
+ <!-- Base64 encoded DER stuff. -->
+ <define name="base64">
+ <data type="base64Binary"/>
+ </define>
+ <!-- Publication URIs. -->
+ <define name="uri">
+ <attribute name="uri">
+ <data type="anyURI">
+ <param name="maxLength">4096</param>
+ </data>
+ </attribute>
+ </define>
+ <!-- Digest of objects being withdrawn -->
+ <define name="hash">
+ <attribute name="hash">
+ <data type="string">
+ <param name="pattern">[0-9a-fA-F]+</param>
+ </data>
+ </attribute>
+ </define>
+ <!-- Error codes. -->
+ <define name="error">
+ <data type="token">
+ <param name="maxLength">1024</param>
+ </data>
+ </define>
+ <!-- <publish/> element -->
+ <define name="publish_query">
+ <element name="publish">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="uri"/>
+ <optional>
+ <ref name="hash"/>
+ </optional>
+ <ref name="base64"/>
+ </element>
+ </define>
+ <define name="publish_reply">
+ <element name="publish">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="uri"/>
+ </element>
+ </define>
+ <!-- <withdraw/> element -->
+ <define name="withdraw_query">
+ <element name="withdraw">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="uri"/>
+ <ref name="hash"/>
+ </element>
+ </define>
+ <define name="withdraw_reply">
+ <element name="withdraw">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="uri"/>
+ </element>
+ </define>
+ <!-- <list/> element -->
+ <define name="list_query">
+ <element name="list">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ </element>
+ </define>
+ <define name="list_reply">
+ <element name="list">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <ref name="uri"/>
+ <ref name="hash"/>
+ </element>
+ </define>
+ <!-- <report_error/> element -->
+ <define name="report_error_reply">
+ <element name="report_error">
+ <optional>
+ <ref name="tag"/>
+ </optional>
+ <attribute name="error_code">
+ <ref name="error"/>
+ </attribute>
+ <optional>
+ <data type="string">
+ <param name="maxLength">512000</param>
+ </data>
+ </optional>
+ </element>
+ </define>
+</grammar>
+<!--
+ Local Variables:
+ indent-tabs-mode: nil
+ comment-start: "# "
+ comment-start-skip: "#[ \t]*"
+ End:
+-->
diff --git a/schemas/relaxng/router-certificate-schema.rnc b/schemas/relaxng/router-certificate.rnc
index 8cc325ce..8cc325ce 100644
--- a/schemas/relaxng/router-certificate-schema.rnc
+++ b/schemas/relaxng/router-certificate.rnc
diff --git a/schemas/relaxng/router-certificate-schema.rng b/schemas/relaxng/router-certificate.rng
index 90b50107..9352ed76 100644
--- a/schemas/relaxng/router-certificate-schema.rng
+++ b/schemas/relaxng/router-certificate.rng
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: router-certificate-schema.rnc 5757 2014-04-05 22:42:12Z sra $
+ $Id: router-certificate.rnc 5881 2014-07-03 16:55:02Z sra $
RelaxNG schema for BGPSEC router certificate interchange format.
diff --git a/schemas/relaxng/rrdp.rnc b/schemas/relaxng/rrdp.rnc
new file mode 100644
index 00000000..2829605d
--- /dev/null
+++ b/schemas/relaxng/rrdp.rnc
@@ -0,0 +1,83 @@
+# $Id$
+#
+# RelaxNG schema for RPKI Repository Delta Protocol (RRDP).
+#
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and 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.
+
+default namespace = "http://www.ripe.net/rpki/rrdp"
+
+version = xsd:positiveInteger { maxInclusive="1" }
+serial = xsd:nonNegativeInteger
+uri = xsd:anyURI
+uuid = xsd:string { pattern = "[\-0-9a-fA-F]+" }
+hash = xsd:string { pattern = "[0-9a-fA-F]+" }
+base64 = xsd:base64Binary
+
+# Notification file: lists current snapshots and deltas
+
+start |= element notification {
+ attribute version { version },
+ attribute session_id { uuid },
+ attribute serial { serial },
+ element snapshot {
+ attribute uri { uri },
+ attribute hash { hash }
+ },
+ element delta {
+ attribute from { serial },
+ attribute to { serial },
+ attribute uri { uri },
+ attribute hash { hash }
+ }*
+}
+
+# Snapshot segment: think DNS AXFR.
+
+start |= element snapshot {
+ attribute version { version },
+ attribute session_id { uuid },
+ attribute serial { serial },
+ element publish { attribute uri { uri }, base64 }*
+}
+
+# Delta segment: think DNS IXFR.
+
+start |= element deltas {
+ attribute version { version },
+ attribute session_id { uuid },
+ attribute from { serial },
+ attribute to { serial },
+ element delta {
+ attribute serial { serial },
+ delta_element+
+ }+
+}
+
+delta_element |= element publish {
+ attribute uri { uri },
+ attribute hash { hash }?,
+ base64
+}
+
+delta_element |= element withdraw {
+ attribute uri { uri },
+ attribute hash { hash }
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# comment-start: "# "
+# comment-start-skip: "#[ \t]*"
+# End:
diff --git a/schemas/relaxng/rrdp.rng b/schemas/relaxng/rrdp.rng
new file mode 100644
index 00000000..9bd3a207
--- /dev/null
+++ b/schemas/relaxng/rrdp.rng
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ $Id: rrdp.rnc 5888 2014-07-09 05:39:54Z sra $
+
+ RelaxNG schema for RPKI Repository Delta Protocol (RRDP).
+
+ Copyright (C) 2014 Dragon Research Labs ("DRL")
+
+ Permission to use, copy, modify, and 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.
+-->
+<grammar ns="http://www.ripe.net/rpki/rrdp" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+ <define name="version">
+ <data type="positiveInteger">
+ <param name="maxInclusive">1</param>
+ </data>
+ </define>
+ <define name="serial">
+ <data type="nonNegativeInteger"/>
+ </define>
+ <define name="uri">
+ <data type="anyURI"/>
+ </define>
+ <define name="uuid">
+ <data type="string">
+ <param name="pattern">[\-0-9a-fA-F]+</param>
+ </data>
+ </define>
+ <define name="hash">
+ <data type="string">
+ <param name="pattern">[0-9a-fA-F]+</param>
+ </data>
+ </define>
+ <define name="base64">
+ <data type="base64Binary"/>
+ </define>
+ <!-- Notification file: lists current snapshots and deltas -->
+ <start combine="choice">
+ <element name="notification">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="session_id">
+ <ref name="uuid"/>
+ </attribute>
+ <attribute name="serial">
+ <ref name="serial"/>
+ </attribute>
+ <element name="snapshot">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </element>
+ <zeroOrMore>
+ <element name="delta">
+ <attribute name="from">
+ <ref name="serial"/>
+ </attribute>
+ <attribute name="to">
+ <ref name="serial"/>
+ </attribute>
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </element>
+ </zeroOrMore>
+ </element>
+ </start>
+ <!-- Snapshot segment: think DNS AXFR. -->
+ <start combine="choice">
+ <element name="snapshot">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="session_id">
+ <ref name="uuid"/>
+ </attribute>
+ <attribute name="serial">
+ <ref name="serial"/>
+ </attribute>
+ <zeroOrMore>
+ <element name="publish">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <ref name="base64"/>
+ </element>
+ </zeroOrMore>
+ </element>
+ </start>
+ <!-- Delta segment: think DNS IXFR. -->
+ <start combine="choice">
+ <element name="deltas">
+ <attribute name="version">
+ <ref name="version"/>
+ </attribute>
+ <attribute name="session_id">
+ <ref name="uuid"/>
+ </attribute>
+ <attribute name="from">
+ <ref name="serial"/>
+ </attribute>
+ <attribute name="to">
+ <ref name="serial"/>
+ </attribute>
+ <oneOrMore>
+ <element name="delta">
+ <attribute name="serial">
+ <ref name="serial"/>
+ </attribute>
+ <oneOrMore>
+ <ref name="delta_element"/>
+ </oneOrMore>
+ </element>
+ </oneOrMore>
+ </element>
+ </start>
+ <define name="delta_element" combine="choice">
+ <element name="publish">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <optional>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </optional>
+ <ref name="base64"/>
+ </element>
+ </define>
+ <define name="delta_element" combine="choice">
+ <element name="withdraw">
+ <attribute name="uri">
+ <ref name="uri"/>
+ </attribute>
+ <attribute name="hash">
+ <ref name="hash"/>
+ </attribute>
+ </element>
+ </define>
+</grammar>
+<!--
+ Local Variables:
+ indent-tabs-mode: nil
+ comment-start: "# "
+ comment-start-skip: "#[ \t]*"
+ End:
+-->
diff --git a/schemas/relaxng/up-down-schema.rnc b/schemas/relaxng/up-down.rnc
index a603b8fe..a603b8fe 100644
--- a/schemas/relaxng/up-down-schema.rnc
+++ b/schemas/relaxng/up-down.rnc
diff --git a/schemas/relaxng/up-down-schema.rng b/schemas/relaxng/up-down.rng
index 89235b7e..a0fc0514 100644
--- a/schemas/relaxng/up-down-schema.rng
+++ b/schemas/relaxng/up-down.rng
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- $Id: up-down-schema.rnc 5757 2014-04-05 22:42:12Z sra $
+ $Id: up-down.rnc 5881 2014-07-03 16:55:02Z sra $
RelaxNG schema for the up-down protocol, extracted from RFC 6492.
diff --git a/schemas/sql/pubd.sql b/schemas/sql/pubd.sql
index 3a58ec00..2a0e2851 100644
--- a/schemas/sql/pubd.sql
+++ b/schemas/sql/pubd.sql
@@ -1,47 +1,36 @@
-- $Id$
--- Copyright (C) 2009--2010 Internet Systems Consortium ("ISC")
+-- Copyright (C) 2012--2014 Dragon Research Labs ("DRL")
+-- Portions copyright (C) 2009--2010 Internet Systems Consortium ("ISC")
+-- Portions copyright (C) 2008 American Registry for Internet Numbers ("ARIN")
--
-- Permission to use, copy, modify, and 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.
+-- copyright notices and this permission notice appear in all copies.
--
--- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ISC 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.
-
--- Copyright (C) 2008 American Registry for Internet Numbers ("ARIN")
---
--- Permission to use, copy, modify, and 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 ARIN DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ARIN 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.
+-- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+-- ISC, OR ARIN 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.
-- SQL objects needed by pubd.py.
--- The config table is weird because we're really only using it
--- to store one BPKI CRL, but putting this here lets us use a lot of
--- existing machinery and the alternatives are whacky in other ways.
+-- Old tables that should just be flushed if present at all.
-DROP TABLE IF EXISTS client;
DROP TABLE IF EXISTS config;
+DROP TABLE IF EXISTS snapshot;
-CREATE TABLE config (
- config_id SERIAL NOT NULL,
- bpki_crl LONGBLOB,
- PRIMARY KEY (config_id)
-) ENGINE=InnoDB;
+-- DROP TABLE commands must be in correct (reverse dependency) order
+-- to satisfy FOREIGN KEY constraints.
+
+DROP TABLE IF EXISTS object;
+DROP TABLE IF EXISTS delta;
+DROP TABLE IF EXISTS session;
+DROP TABLE IF EXISTS client;
CREATE TABLE client (
client_id SERIAL NOT NULL,
@@ -54,6 +43,43 @@ CREATE TABLE client (
UNIQUE (client_handle)
) ENGINE=InnoDB;
+CREATE TABLE session (
+ session_id SERIAL NOT NULL,
+ uuid VARCHAR(36) NOT NULL,
+ serial BIGINT UNSIGNED NOT NULL,
+ snapshot LONGTEXT,
+ hash CHAR(64),
+ PRIMARY KEY (session_id),
+ UNIQUE (uuid)
+) ENGINE=InnoDB;
+
+CREATE TABLE delta (
+ delta_id SERIAL NOT NULL,
+ serial BIGINT UNSIGNED NOT NULL,
+ xml LONGTEXT NOT NULL,
+ hash CHAR(64) NOT NULL,
+ expires DATETIME NOT NULL,
+ session_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (delta_id),
+ CONSTRAINT delta_session_id
+ FOREIGN KEY (session_id) REFERENCES session (session_id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+CREATE TABLE object (
+ object_id SERIAL NOT NULL,
+ uri VARCHAR(255) NOT NULL,
+ der LONGBLOB NOT NULL,
+ hash CHAR(64) NOT NULL,
+ client_id BIGINT UNSIGNED NOT NULL,
+ session_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (object_id),
+ CONSTRAINT object_client_id
+ FOREIGN KEY (client_id) REFERENCES client (client_id) ON DELETE CASCADE,
+ CONSTRAINT object_session_id
+ FOREIGN KEY (session_id) REFERENCES session (session_id) ON DELETE CASCADE,
+ UNIQUE (session_id, hash)
+) ENGINE=InnoDB;
+
-- Local Variables:
-- indent-tabs-mode: nil
-- End:
diff --git a/schemas/sql/rpkid.sql b/schemas/sql/rpkid.sql
index ad0c39b0..f3b899ee 100644
--- a/schemas/sql/rpkid.sql
+++ b/schemas/sql/rpkid.sql
@@ -1,32 +1,21 @@
-- $Id$
--- Copyright (C) 2009--2011 Internet Systems Consortium ("ISC")
+-- Copyright (C) 2012--2014 Dragon Research Labs ("DRL")
+-- Portions copyright (C) 2009--2011 Internet Systems Consortium ("ISC")
+-- Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
--
-- Permission to use, copy, modify, and 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.
+-- copyright notices and this permission notice appear in all copies.
--
--- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ISC 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.
-
--- Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
---
--- Permission to use, copy, modify, and 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 ARIN DISCLAIMS ALL WARRANTIES WITH
--- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
--- AND FITNESS. IN NO EVENT SHALL ARIN 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.
+-- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+-- ISC, OR ARIN 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.
-- SQL objects needed by the RPKI engine (rpkid.py).