diff options
author | Rob Austein <sra@hactrn.net> | 2009-08-28 19:58:08 +0000 |
---|---|---|
committer | Rob Austein <sra@hactrn.net> | 2009-08-28 19:58:08 +0000 |
commit | 9f4bc84a8be52b5fc838f2bf52d37bd8d6ecd112 (patch) | |
tree | 9d5aca21ff0dc1a92210e785a783225f55dd1ee4 /rpkid.stable/rpki | |
parent | 65dd0c1eebf86559508ac24a8200fa4194dec8c0 (diff) |
Clean up old "stable" branch that is now badly out of date
svn path=/rpkid.stable; revision=2714
Diffstat (limited to 'rpkid.stable/rpki')
-rw-r--r-- | rpkid.stable/rpki/__init__.py | 1992 | ||||
-rw-r--r-- | rpkid.stable/rpki/config.py | 56 | ||||
-rw-r--r-- | rpkid.stable/rpki/exceptions.py | 135 | ||||
-rw-r--r-- | rpkid.stable/rpki/https.py | 291 | ||||
-rw-r--r-- | rpkid.stable/rpki/ipaddrs.py | 103 | ||||
-rw-r--r-- | rpkid.stable/rpki/left_right.py | 833 | ||||
-rw-r--r-- | rpkid.stable/rpki/log.py | 58 | ||||
-rw-r--r-- | rpkid.stable/rpki/manifest.py | 53 | ||||
-rw-r--r-- | rpkid.stable/rpki/oids.py | 57 | ||||
-rw-r--r-- | rpkid.stable/rpki/publication.py | 282 | ||||
-rw-r--r-- | rpkid.stable/rpki/relaxng.py | 1699 | ||||
-rw-r--r-- | rpkid.stable/rpki/resource_set.py | 795 | ||||
-rw-r--r-- | rpkid.stable/rpki/roa.py | 75 | ||||
-rw-r--r-- | rpkid.stable/rpki/rpki_engine.py | 819 | ||||
-rw-r--r-- | rpkid.stable/rpki/sql.py | 295 | ||||
-rw-r--r-- | rpkid.stable/rpki/sundial.py | 198 | ||||
-rw-r--r-- | rpkid.stable/rpki/up_down.py | 535 | ||||
-rw-r--r-- | rpkid.stable/rpki/x509.py | 995 | ||||
-rw-r--r-- | rpkid.stable/rpki/xml_utils.py | 317 |
19 files changed, 0 insertions, 9588 deletions
diff --git a/rpkid.stable/rpki/__init__.py b/rpkid.stable/rpki/__init__.py deleted file mode 100644 index 1ac55ca3..00000000 --- a/rpkid.stable/rpki/__init__.py +++ /dev/null @@ -1,1992 +0,0 @@ -# $Id$ - -# 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. - -# This file exists to tell Python that this the content of this -# directory constitute a Python package. Since we're not doing -# anything exotic, this file doesn't need to contain any code, but -# since its existance defines the package, it's as sensible a place as -# any to put the Doxygen mainpage. - -# The "usage" text for irbe_cli in the OPERATIONS section is generated -# automatically by running the program with its --help command. -# Should do the same with the other programs. Don't yet have a sane -# way to automate options in config files, though. Would be nice. - -## @mainpage RPKI Engine Reference Manual -# -# This collection of Python modules implements a prototype of the -# RPKI Engine. This is a work in progress. -# -# See http://viewvc.hactrn.net/subvert-rpki.hactrn.net/ for code, -# design documents, a text mirror of portions of APNIC's Wiki, etc. -# -# The documentation you're reading is generated automatically by -# Doxygen from comments and documentation in -# <a href="http://viewvc.hactrn.net/subvert-rpki.hactrn.net/rpkid/rpki/">the code</a>. -# -# Besides the automatically-generated code documentation, this manual -# also includes documentation of the overall package: -# -# @li The @subpage Installation "installation instructions" -# @li The @subpage Operation "operation instructions" -# @li A description of the @subpage Left-right "left-right protocol" -# @li A description of the @subpage Publication "publication protocol" -# @li A description of the @subpage bpki-model "BPKI model" -# used to secure the up-down, left-right, and %publication protocols -# @li A description of the several @subpage sql-schemas "SQL database schemas" -# @li Some suggestions for @subpage further-reading "further reading" -# -# This work has been funded by <a -# href="http://www.arin.net/">ARIN</a>, in collaboration with the -# other Regional Internet Registries. - -## @page further-reading Further Reading -# -# If you're interested in this package you might also be interested -# in: -# -# @li <a href="http://viewvc.hactrn.net/subvert-rpki.hactrn.net/rcynic/">The rcynic validation tool</a> -# @li <a href="http://www.hactrn.net/opaque/rcynic.html">A live sample of rcynic's summary output</a> -# @li <a href="http://mirin.apnic.net/resourcecerts/wiki/">APNIC's Wiki</a> -# @li <a href="http://mirin.apnic.net/trac/">APNIC's project Trac instance</a> - -## @page Installation Installation Guide -# -# Preliminary installation instructions for rpkid et al. These are the -# production-side RPKI tools, for Internet Registries (RIRs, LIRs, etc). -# See the "rcynic" program for relying party tools. -# -# rpkid is a set of Python modules supporting generation and maintenance -# of resource certificates. Most of the code is in the rpkid/rpki/ -# directory. rpkid itself is a relatively small program that calls the -# library modules. There are several other programs that make use of -# the same libraries, as well as a collection of test programs. -# -# At present the package is intended to be run out of its build -# directory. Setting up proper installation in a system area using the -# Python distutils package would likely not be very hard but has not yet -# been done. -# -# Note that initial development of this code has been on FreeBSD, so -# installation will probably be easiest on FreeBSD. -# -# Before attempting to build the package, you need to install any -# missing prerequisites. Note that the Python code requires Python -# version 2.5. rpkid et al are mostly self-contained, but do require -# a small number of external packages to run. -# -# <ul> -# <li> -# <a href="http://codespeak.net/lxml/">http://codespeak.net/lxml/</a>. -# lxml in turn requires the Gnome LibXML2 C libraries. -# <ul> -# <li>FreeBSD: /usr/ports/devel/py-lxml</li> -# <li>Fedora: python-lxml.i386</li> -# </ul> -# </li> -# <li> -# <a href="http://sourceforge.net/projects/mysql-python/">http://sourceforge.net/projects/mysql-python/</a>. -# MySQLdb in turn requires MySQL client and server. rpkid et al have -# been tested with MySQL 5.0 and 5.1. -# <ul> -# <li>FreeBSD: /usr/ports/databases/py-MySQLdb</li> -# <li>Fedora: MySQL-python.i386</li> -# </ul> -# </li> -# <li> -# <a href="http://trevp.net/tlslite/">http://trevp.net/tlslite/</a>. -# TLSLite pulls in other crypto packages. -# <ul> -# <li>FreeBSD: /usr/ports/security/py-tlslite</li> -# </ul> -# </li> -# </ul> -# -# rpkid et al also make heavy use of a modified copy of the Python -# OpenSSL Wrappers (POW) package, but this copy has enough modifications -# and additions that it's included in the subversion tree. -# -# The next step is to build the OpenSSL and POW binaries. At present -# the OpenSSL code is just a copy of the stock OpenSSL 0.9.8g release, -# compiled with special options to enable RFC 3779 support that ISC -# wrote under previous contract to ARIN. The POW (Python OpenSSL -# Wrapper) library is an extended copy of the stock POW release. -# -# To build these, cd to the top-level directory in the distribution and -# type "make". -# -# @verbatim -# $ cd $top -# $ make -# @endverbatim -# -# This should automatically build everything, in the right order, -# including staticly linking the POW extension module with the OpenSSL -# library to provide RFC 3779 support. -# -# You will also need a MySQL installation. This code was developed -# using MySQL 5.1 and has been tested with MySQL 5.0 and 5.1. -# -# The architecture is intended to support hardware signing modules -# (HSMs), but the code to support them has not been written. -# -# At this point, you should have all the necessary software installed. -# You will probably want to test it. All tests should be run from the -# rpkid/ directory. The test suite requires a few more external -# packages, only one of which is Python code. -# -# <ul> -# <li> -# <a href="http://pyyaml.org/">http://pyyaml.org/</a>. -# testpoke.py (an up-down protocol command line test client) and -# testbed.py (a test harness) use PyYAML. -# <ul> -# <li>FreeBSD: /usr/ports/devel/py-yaml</li> -# </ul> -# </li> -# <li> -# <a href="http://xmlsoft.org/XSLT/">http://xmlsoft.org/XSLT/</a>. -# Some of the test code uses xsltproc, from the Gnome LibXSLT -# package. -# <ul> -# <li>FreeBSD: /usr/ports/textproc/libxslt</li> -# </ul> -# </li> -# <li> -# <a href="http://w3m.sourceforge.net/">http://w3m.sourceforge.net/</a>. -# testbed.py uses w3m to display the summary output from rcynic. -# Nothing terrible will happen if w3m isn't available, testbed.py -# will just complain about it being missing and won't display -# rcynic's output. -# <ul> -# <li>FreeBSD: /usr/ports/www/w3m</li> -# </ul> -# </li> -# </ul> -# -# Some of the tests require MySQL databases to store their data. To set -# up all the databases that the tests will need, run the SQL commands in -# rpkid/testbed.sql. The MySQL command line client is usually the -# easiest way to do this, eg: -# -# @verbatim -# $ cd $top/rpkid -# $ mysql -u root -p <testbed.sql -# @endverbatim -# -# To run the tests, run "make all-tests": -# -# @verbatim -# $ cd $top/rpkid -# $ make all-tests -# @endverbatim -# -# If nothing explodes, your installation is probably ok. Any Python -# backtraces in the output indicate a problem. -# -# There's a last set of tools that only developers should need, as -# they're only used when modifying schemas or regenerating the -# documentation. These tools are listed here for completeness. -# -# <ul> -# <li> -# <a href="http://www.doxygen.org/">http://www.doxygen.org/</a>. -# Doxygen in turn pulls in several other tools, notably Graphviz, -# pdfLaTeX, and Ghostscript. -# <ul> -# <li>FreeBSD: /usr/ports/devel/doxygen</li> -# </ul> -# </li> -# <li> -# <a href="http://lynx.isc.org/current/">http://lynx.isc.org/current/</a>. -# The documentation build process uses xsltproc and Lynx to dump -# flat text versions of a few critical documentation pages. -# <ul> -# <li>FreeBSD: /usr/ports/www/lynx</li> -# </ul> -# </li> -# <li> -# <a href="http://www.thaiopensource.com/relaxng/trang.html">http://www.thaiopensource.com/relaxng/trang.html</a>. -# Trang is used to convert RelaxNG schemas from the human-readable -# "compact" form to the XML form that LibXML2 understands. Trang in -# turn requires Java. -# <ul> -# <li>FreeBSD: /usr/ports/textproc/trang</li> -# </ul> -# </li> -# <li> -# <a href="http://search.cpan.org/dist/SQL-Translator/">http://search.cpan.org/dist/SQL-Translator/</a>. -# SQL-Translator, also known as "SQL Fairy", includes code to parse -# an SQL schema and dump a description of it as Graphviz input. -# SQL Fairy in turn requires Perl. -# </li> -# </ul> - -## @page Operation Operation Guide -# -# Preliminary operation instructions for rpkid et al. These are the -# production-side RPKI tools, for Internet Registries (RIRs, LIRs, etc). -# See rcynic/README for relying party tools. -# -# @warning -# rpkid is still in development, and the code changes more often than -# the hand-maintained portions of this documentation. The following -# text was reasonably accurate at the time it was written but may be -# obsolete by the time you read it. -# -# At present the package is intended to be run out of the @c rpkid/ -# directory. -# -# In addition to the library routines in the @c rpkid/rpki/ directory, -# the package includes the following programs: -# -# @li @c rpkid.py: -# The main RPKI engine daemon. -# -# @li @c pubd.py: -# The publication engine daemon. -# -# @li @c rootd.py: -# A separate daemon for handling the root of an RPKI -# certificate tree. This is essentially a stripped down -# version of rpkid with no SQL database, no left-right -# protocol implementation, and only the parent side of -# the up-down protocol. It's separate because the root -# is a special case in several ways and it was simpler -# to keep the special cases out of the main daemon. -# -# @li @c irdbd.py: -# A sample implementation of an IR database daemon. -# rpkid calls into this to perform lookups via the -# left-right protocol. -# -# @li @c irbe_cli.py: -# A command-line client for the left-right control -# protocol. -# -# @li @c cross_certify.py: -# A BPKI cross-certification tool. -# -# @li @c irbe-setup.py: -# An example of a script to set up the mappings between -# the IRDB and rpkid's own database, using the -# left-right control protocol. -# -# @li @c cronjob.py: -# A trivial HTTP client used to drive rpkid cron events. -# -# @li @c testbed.py: -# A test tool for running a collection of rpkid and irdb -# instances under common control, driven by a unified -# test script. -# -# @li @c testpoke.py: -# A simple client for the up-down protocol, mostly -# compatable with APNIC's rpki_poke.pl tool. -# -# Most of these programs take configuration files in a common format -# similar to that used by the OpenSSL command line tool. The test -# programs also take input in YAML format to drive the tests. Runs of -# the testbed.py test tool will generate a fairly complete set -# configuration files which may be useful as examples. -# -# Basic operation consists of creating the appropriate MySQL databases, -# starting rpkid, pubd, rootd, and irdbd, using the left-right control -# protocol to set up rpkid's internal state, and setting up a cron job -# to invoke rpkid's cron action at regular intervals. All other -# operations should occur either as a result of cron events or as a -# result of incoming left-right and up-down protocol requests. -# -# Note that the full event-driven model for rpkid hasn't yet been -# implemented. The design is intended to allow an arbitrary number of -# hosted RPKI engines to run in a single rpkid instance, but without the -# event-driven tasking model one must set up a separate rpkid instance -# for each hosted RPKI engine. -# -# At present the daemon programs all run in foreground, that is, if one -# wants them to run in background one must do so manually, eg, using -# Bourne shell syntax: -# -# @verbatim -# $ python whatever.py & -# $ echo >whatever.pid "$!" -# @endverbatim -# -# All of the daemons use syslog. At present they all set LOG_PERROR, so -# all logging also goes to stderr. -# -# -# @section rpkid rpkid.py -# -# rpkid is the main RPKI engine daemon. Configuration of rpkid is a -# two step process: a %config file to bootstrap rpkid to the point -# where it can speak using the @link Left-right left-right protocol, -# @endlink followed by dynamic configuration via the left-right -# protocol. In production use the latter stage would be handled by -# the IRBE stub; for test and develoment purposes it's handled by the -# irbe_cli.py command line interface or by the testbed.py test -# framework. -# -# rpkid stores dynamic data in an SQL database, which must have been -# created for it, as explained in the @link Installation installation -# guide. @endlink -# -# The default %config file is rpkid.conf, start rpkid with "-c filename" -# to choose a different %config file. All options are in the section -# "[rpkid]". Certificates, keys, and trust anchors may be in either DER -# or PEM format. -# -# %Config file options: -# -# @li @c startup-message: -# String to %log on startup, useful when -# debugging a collection of rpkid instances at -# once. -# -# @li @c sql-username: -# Username to hand to MySQL when connecting to -# rpkid's database. -# -# @li @c sql-database: -# MySQL's database name for rpkid's database. -# -# @li @c sql-password: -# Password to hand to MySQL when connecting to -# rpkid's database. -# -# @li @c bpki-ta: -# Name of file containing BPKI trust anchor. -# All BPKI certificate verification within rpkid -# traces back to this trust anchor. -# -# @li @c rpkid-cert: -# Name of file containing rpkid's own BPKI EE -# certificate. -# -# @li @c rpkid-key: -# Name of file containing RSA key corresponding -# to rpkid-cert. -# -# @li @c irbe-cert: -# Name of file containing BPKI certificate used -# by IRBE when talking to rpkid. -# -# @li @c irdb-cert: -# Name of file containing BPKI certificate used -# by irdbd. -# -# @li @c irdb-url: -# Service URL for irdbd. Must be a %https:// URL. -# -# @li @c server-host: -# Hostname or IP address on which to listen for -# HTTPS connections. Current default is -# INADDR_ANY (IPv4 0.0.0.0); this will need to -# be hacked to support IPv6 for production. -# -# @li @c server-port: -# TCP port on which to listen for HTTPS -# connections. -# -# -# @section pubd pubd.py -# -# pubd is the publication daemon. It implements the server side of -# the publication protocol, and is used by rpkid to publish the -# certificates and other objects that rpkid generates. -# -# pubd is separate from rpkid for two reasons: -# -# @li The hosting model allows entities which choose to run their own -# copies of rpkid to publish their output under a common -# publication point. In general, encouraging shared publication -# services where practical is a good thing for relying parties, -# as it will speed up rcynic synchronization time. -# -# @li The publication server has to run on (or at least close to) the -# publication point itself, which in turn must be on a publically -# reachable server to be useful. rpkid, on the other hand, need -# only be reachable by the IRBE and its children in the RPKI tree. -# rpkid is a much more complex piece of software than pubd, so in -# some situations it might make sense to wrap tighter firewall -# constraints around rpkid than would be practical if rpkid and -# pubd were a single program. -# -# pubd stores dynamic data in an SQL database, which must have been -# created for it, as explained in the installation guide. pubd also -# stores the published objects themselves as disk files in a -# configurable location which should correspond to an appropriate -# module definition in rsync.conf. -# -# The default %config file is pubd.conf, start pubd with "-c -# filename" to choose a different %config file. ALl options are in -# the section "[pubd]". Certifiates, keys, and trust anchors may be -# either DER or PEM format. -# -# %Config file options: -# -# @li @c sql-username: -# Username to hand to MySQL when connecting to -# pubd's database. -# -# @li @c sql-database: -# MySQL's database name for pubd's database. -# -# @li @c sql-password: -# Password to hand to MySQL when connecting to -# pubd's database. -# -# @li @c bpki-ta: -# Name of file containing master BPKI trust -# anchor for pubd. All BPKI validation in pubd -# traces back to this trust anchor. -# -# @li @c irbe-cert: -# Name of file containing BPKI certificate used -# by IRBE when talking to pubd. -# -# @li @c pubd-cert: -# Name of file containing BPKI certificate used -# by pubd. -# -# @li @c pubd-key: -# Name of file containing RSA key corresponding -# to @c pubd-cert. -# -# @li @c server-host: -# Hostname or IP address on which to listen for -# HTTPS connections. Current default is -# INADDR_ANY (IPv4 0.0.0.0); this will need to -# be hacked to support IPv6 for production. -# -# @li @c server-port: -# TCP port on which to listen for HTTPS -# connections. -# -# @li @c publication-base: -# Path to base of filesystem tree where pubd -# should store publishable objects. Default is -# "publication/". -# -# -# @section rootd rootd.py -# -# rootd is a stripped down implmenetation of (only) the server side of -# the up-down protocol. It's a separate program because the root -# certificate of an RPKI certificate tree requires special handling and -# may also require a special handling policy. rootd is a simple -# implementation intended for test use, it's not suitable for use in a -# production system. All configuration comes via the %config file. -# -# The default %config file is rootd.conf, start rootd with "-c filename" -# to choose a different %config file. All options are in the section -# "[rootd]". Certificates, keys, and trust anchors may be in either DER -# or PEM format. -# -# %Config file options: -# -# @li @c bpki-ta: -# Name of file containing BPKI trust anchor. All -# BPKI certificate validation in rootd traces -# back to this trust anchor. -# -# @li @c rootd-bpki-cert: -# Name of file containing rootd's own BPKI -# certificate. -# -# @li @c rootd-bpki-key: -# Name of file containing RSA key corresponding to -# rootd-bpki-cert. -# -# @li @c rootd-bpki-crl: -# Name of file containing BPKI CRL that would -# cover rootd-bpki-cert had it been revoked. -# -# @li @c child-bpki-cert: -# Name of file containing BPKI certificate for -# rootd's one and only child (RPKI engine to -# which rootd issues an RPKI certificate). -# -# @li @c server-host: -# Hostname or IP address on which to listen for -# HTTPS connections. Default is localhost. -# -# @li @c server-port: -# TCP port on which to listen for HTTPS -# connections. -# -# @li @c rpki-root-key: -# Name of file containing RSA key to use in -# signing resource certificates. -# -# @li @c rpki-root-cert: -# Name of file containing self-signed root -# resource certificate corresponding to -# rpki-root-key. -# -# @li @c rpki-root-dir: -# Name of directory where rootd should write -# RPKI subject certificate, manifest, and CRL. -# -# @li @c rpki-subject-cert: -# Name of file that rootd should use to save the -# one and only certificate it issues. -# Default is "Subroot.cer". -# -# @li @c rpki-root-crl: -# Name of file to which rootd should save its -# RPKI CRL. Default is "Root.crl". -# -# @li @c rpki-root-manifest: -# Name of file to which rootd should save its -# RPKI manifest. Default is "Root.mnf". -# -# @li @c rpki-subject-pkcs10: -# Name of file that rootd should use when saving -# a copy of the received PKCS #10 request for a -# resource certificate. This is only used for -# debugging. Default is not to save the PKCS -# #10 request. -# -# -# @section irdbd irdbd.py -# -# irdbd is a sample implemntation of the server side of the IRDB -# callback subset of the left-right protocol. In production use this -# service is a function of the IRBE stub; irdbd may be suitable for -# production use in simple cases, but an IR with a complex IRDB may need -# to extend or rewrite irdbd. -# -# irdbd requires a pre-populated database to represent the IR's -# customers. irdbd expects this database to use the SQL schema defined -# in rpkid/irdbd.sql. Once this database has been populated, the -# IRBE stub needs to create the appropriate objects in rpkid's database -# via the control subset of the left-right protocol, and store the -# linkage IDs (foreign keys into rpkid's database, basicly) in the -# IRDB. The irbe-setup.py program shows an example of how to do this. -# -# irdbd's default %config file is irdbd.conf, start irdbd with "-c -# filename" to choose a different %config file. All options are in the -# section "[irdbd]". Certificates, keys, and trust anchors may be in -# either DER or PEM format. -# -# %Config file options: -# -# @li @c startup-message: -# String to %log on startup, useful when -# debugging a collection of irdbd instances at -# once. -# -# @li @c sql-username: -# Username to hand to MySQL when connecting to -# irdbd's database. -# -# @li @c sql-database: -# MySQL's database name for irdbd's database. -# -# @li @c sql-password: -# Password to hand to MySQL when connecting to -# irdbd's database. -# -# @li @c bpki-ta: -# Name of file containing BPKI trust anchor. All -# BPKI certificate validation in irdbd traces -# back to this trust anchor. -# -# @li @c irdbd-cert: -# Name of file containing irdbd's own BPKI -# certificate. -# -# @li @c irdbd-key: -# Name of file containing RSA key corresponding -# to irdbd-cert. -# -# @li @c rpkid-cert: -# Name of file containing certificate used the -# one and only by rpkid instance authorized to -# contact this irdbd instance. -# -# @li @c https-url: -# Service URL for irdbd. Must be a %https:// URL. -# -# -# @section irdbd_cli irbe_cli.py -# -# irbe_cli is a simple command line client for the control subsets of -# the @link Left-right left-right @endlink and @link Publication -# publication @endlink protocols. In production use this -# functionality would be part of the IRBE stub. -# -# Basic configuration of irbe_cli is handled via a %config file. The -# specific action or actions to be performed are specified on the -# command line, and map closely to the protocols themselves. -# -# At present the user is assumed to be able to read the (XML) -# left-right and publication protocol messages, and with one -# exception, irdbd-cli makes no attempt to interpret the responses -# other than to check for signature and syntax errors. The one -# exception is that, if the @c --pem_out option is specified on the -# command line, any PKCS \#10 requests received from rpkid will be -# written in PEM format to that file; this makes it easier to hand -# these requests off to the business PKI (BPKI in order to issue signing -# certs corresponding to newly generated business keys. -# -# @verbinclude irbe_cli.usage -# -# Global options (@c --config, @c --help, @c --pem_out) come first, -# then zero or more commands (@c parent, @c repository, @c self, @c -# child, @c route_origin, @c bsc, @c config, @c client), each followed -# by its own set of options. The commands map to elements in the -# protocols, and the command-specific options map to attributes or -# subelements for those commands. -# -# @c --tag is an optional arbitrary tag (think IMAP) to simplify -# matching up replies with batched queries. -# -# @c --*_id options refer to the primary keys of previously created -# objects. -# -# The remaining options are specific to the particular commands, and -# follow directly from the protocol specifications. -# -# A trailing "=" in the above option summary indicates that an option -# takes a value, eg, "--action create" or "--action=create". Options -# without a trailing "=" correspond to boolean control attributes. -# -# The default %config file for irbe_cli is irbe_cli.conf, start -# irbe_cli with "-c filename" (or "--config filename") to choose a -# different %config file. All options are in the section -# "[irbe_cli]". Certificates, keys, and trust anchors may be in -# either DER or PEM format. -# -# %Config file options: -# -# @li @c rpkid-bpki-ta: -# Name of file containing BPKI trust anchor to -# use when authenticating messages from rpkid. -# -# @li @c rpkid-irbe-cert: -# Name of file containing BPKI certificate -# irbe_cli should use when talking to rpkid. -# -# @li @c rpkid-irbe-key: -# Name of file containing RSA key corresponding to -# rpkid-irbe-cert. -# -# @li @c rpkid-cert: -# Name of file containing rpkid's BPKI certificate. -# -# @li @c rpkid-url: -# Service URL for rpkid. Must be a %https:// URL. -# -# @li @c pubd-bpki-ta: -# Name of file containing BPKI trust anchor to -# use when authenticating messages from pubd. -# -# @li @c pubd-irbe-cert: -# Name of file containing BPKI certificate -# irbe_cli should use when talking to pubd. -# -# @li @c pubd-irbe-key: -# Name of file containing RSA key corresponding to -# pubd-irbe-cert. -# -# @li @c pubd-cert: -# Name of file containing pubd's BPKI certificate. -# -# @li @c pubd-url: -# Service URL for pubd. Must be a %https:// URL. -# -# -# -# @section cross_certify cross_certify.py -# -# cross_certify.py is a small tool to extract certain fields from an -# existing X.509 certificate and generate issue a new certificate that -# can be used as part of a cross-certification chain. cross_certify -# doesn't take a config file, all of its arguments are specified on -# the command line. -# -# @verbatim -# python cross_certify.py { -i | --in } input_cert -# { -c | --ca } issuing_cert -# { -k | --key } issuing_cert_key -# { -s | --serial } serial_filename -# [ { -h | --help } ] -# [ { -o | --out } filename ] -# [ { -l | --lifetime } timedelta ] -# @endverbatim -# -# -# @section irbe_setup irbe-setup.py config file -# -# @warning -# irbe-setup is old code, not currently used, kept in case it is -# useful at some later date. It may not work properly or at all. If -# you don't understand what it does, you don't need it. You have been -# warned. -# -# The default %config file is irbe.conf, start rpkid with "-c filename" -# to choose a different %config file. Most options are in the section -# "[irbe_cli]", but a few are in the section "[irdbd]". Certificates, -# keys, and trust anchors may be in either DER or PEM format. -# -# Options in the "[irbe_cli]" section: -# -# @li @c bpki-ta: -# Name of file containing BPKI trust anchor. -# -# @li @c irbe-cert: -# Name of file containing BPKI certificate -# irbe-setup should use. -# -# @li @c irbe-key: -# Name of file containing RSA key corresponding -# to irbe-cert. -# -# @li @c rpkid-cert: -# Name of file containing rpkid's BPKI -# certificate. -# -# @li @c https-url: -# Service URL for rpkid. Must be a %https:// URL. -# -# Options in the "[irdbd]" section: -# -# @li @c sql-username: -# Username to hand to MySQL when connecting to -# irdbd's database. -# -# @li @c sql-database: -# MySQL's database name for irdbd's database. -# -# @li @c sql-password: -# Password to hand to MySQL when connecting to -# irdbd's database. -# -# -# @section cronjob cronjob.py -# -# This is a trivial program to trigger a cron run within rpkid. Once -# rpkid has been converted to the planned event-driven model, this -# function will be handled internally, but for now it has to be -# triggered by an external program. For pseudo-production use one would -# run this program under the system cron daemon. For scripted testing -# it happens to be useful to be able to control when cron cycles occur, -# so at the current stage of code development use of an external trigger -# is a useful feature. -# -# The default %config file is cronjob.conf, start cronjob with "-c -# filename" to choose a different %config file. All options are in the -# section "[cronjob]". Certificates, keys, and trust anchors may be in -# either DER or PEM format. -# -# %Config file options: -# -# @li @c bpki-ta: -# Name of file containing BPKI trust anchor. -# -# @li @c irbe-cert: -# Name of file containing cronjob.py's BPKI -# certificate. -# -# @li @c https-key: -# Name of file containing RSA key corresponding -# to irbe-cert. -# -# @li @c rpkid-cert: -# Name of file containing rpkid's BPKI certificate. -# -# @li @c https-url: -# Service URL for rpkid. Must be a %https:// URL. -# -# -# @section testbed testbed.py: -# -# testbed is a test harness to set up and run a collection of rpkid and -# irdbd instances under scripted control. testbed is a very recent -# addition to the toolset and is still evolving rapidly. -# -# Unlike the programs described above, testbed takes two configuration -# files in different languages. The first configuration file uses the -# same syntax as the above configuration files but is completely -# optional. The second configuration file is the test script, which is -# encoded using the YAML serialization language (see -# http://www.yaml.org/ for more information on YAML). The YAML script -# is not optional, as it describes the test layout. testbed is designed -# to support running a fairly wide set of test configurations as canned -# scripts without writing any new control code. The intent is to make -# it possible to write meaningful regression tests. -# -# All of the options in in the first (optional) configuration file are -# just overrides for wired-in default values. In most cases the -# defaults will suffice, and the set of options is still in flux, so -# only a few of the options are described here. The default name for -# this configuration file is testbed.conf, run testbed with "-c -# filename" to change it. -# -# testbed.conf options: -# -# @li @c testbed_dir: -# Working directory into which testbed should write the -# (many) files it generates. Default is "testbed.dir". -# -# @li @c irdb_db_pass: -# MySQL password for the "irdb" user. Default is -# "fnord". You may want to override this. -# -# @li @c rpki_db_pass: -# MySQL password for the "rpki" user. Default is -# "fnord". You may want to override this. -# -# @li @c rootd_sia: -# rsync URI naming a (perhaps fictious) directory to use -# as the id-ad-caRepository SIA value in the generated -# root resource certificate. Default is -# "rsync://wombat.invalid/". You may want to override -# this if you intend to run an rsync server and test -# against the generated results using rcynic. This -# default will likely change if and when testbed learns -# how to run rcynic itself as part of the test suite. -# -# The second configuration file is named testbed.yaml by default, run -# testbed with "-y filename" to change it. The YAML file contains -# multiple YAML "documents". The first document describes the initial -# test layout and resource allocations, subsequent documents describe -# modifications to the initial allocations and other parameters. -# Resources listed in the initial layout are aggregated automatically, -# so that a node in the resource hierarchy automatically receives the -# resources it needs to issue whatever its children are listed as -# holding. Actions in the subsequent documents are modifications to the -# current resource set, modifications to validity dates or other -# non-resource parameters, or special commands like "sleep". The -# details are still evolving, but here's an example of current usage: -# -# @verbatim -# name: RIR -# valid_for: 2d -# sia_base: "rsync://wombat.invalid/" -# kids: -# - name: LIR0 -# kids: -# - name: Alice -# ipv4: 192.0.2.1-192.0.2.33 -# asn: 64533 -# --- -# - name: Alice -# valid_add: 10 -# --- -# - name: Alice -# add_as: 33 -# valid_add: 2d -# --- -# - name: Alice -# valid_sub: 2d -# --- -# - name: Alice -# valid_for: 10d -# @endverbatim -# -# This specifies an initial layout consisting of an RPKI engine named -# "RIR", with one child "LIR0", which in turn has one child "Alice". -# Alice has a set of assigned resources, and all resources in the system -# are initially set to be valid for two days from the time at which the -# test is started. The first subsequent document adds ten seconds to -# the validity interval for Alice's resources and makes no other -# modifications. The second subsequent document grants Alice additional -# resources and adds another two days to the validity interval for -# Alice's resources. The next document subtracts two days from the -# validity interval for Alice's resources. The final document sets the -# validity interval for Alice's resources to ten days. -# -# Operators in subsequent (update) documents: -# -# @li @c add_as, @c add_v4, @c add_v6: -# These add ASN, IPv4, or IPv6 resources, respectively. -# -# @li @c sub_as, @c sub_v4, @c sub_v6: -# These subtract resources. -# -# @li @c valid_until: -# Set an absolute expiration date. -# -# @li @c valid_for: -# Set a relative expiration date. -# -# @li @c valid_add, @c valid_sub: -# Add to or subtract from validity interval. -# -# @li @c sleep [interval]: -# Sleep for specified interval, or until testbed receives a SIGALRM signal. -# -# Absolute timestamps should be in the form shown (UTC timestamp format -# as used in XML). -# -# Intervals (@c valid_add, @c valid_sub, @c valid_for, @c sleep) are either -# integers, in which case they're interpreted as seconds, or are a -# string of the form "wD xH yM zS" where w, x, y, and z are integers and -# D, H, M, and S indicate days, hours, minutes, and seconds. In the -# latter case all of the fields are optional, but at least one must be -# specified. For example, "3D4H" means "three days plus four hours". -# -# -# @section testpoke testpoke.py -# -# This is a command-line client for the up-down protocol. Unlike all of -# the above programs, testpoke does not accept a %config file in -# OpenSSL-compatable format at all. Instead, it is configured -# exclusively by a YAML script. testpoke's design was constrained by a -# desire to have it be compatable with APNIC's rpki_poke.pl tool, so -# that the two tools could use a common configuration language to -# simplify scripted testing. There are minor variations due to slightly -# different feature sets, but YAML files intended for one program will -# usually work with the other. -# -# README for APNIC's tool describing the input language can be found at -# <a href="http://mirin.apnic.net/svn/rpki_engine/branches/gary-poker/client/poke/README"> -# http://mirin.apnic.net/svn/rpki_engine/branches/gary-poker/client/poke/README</a>. -# -# testpoke.py takes a simplified command line and uses only one YAML -# input file. -# -# @verbatim -# Usage: python testpoke.py [ { -y | --yaml } configfile ] -# [ { -r | --request } requestname ] -# [ { -h | --help } ] -# @endverbatim -# -# Default configuration file is testpoke.yaml, override with --yaml -# option. -# -# The --request option specifies the specific command within the YAML -# file to execute. -# -# Sample configuration file: -# -# @verbatim -# --- -# # Sample YAML configuration file for testpoke.py -# -# version: 1 -# posturl: https://localhost:4433/up-down/1 -# recipient-id: wombat -# sender-id: "1" -# -# cms-cert-file: biz-certs/Frank-EE.cer -# cms-key-file: biz-certs/Frank-EE.key -# cms-ca-cert-file: biz-certs/Bob-Root.cer -# cms-cert-chain-file: [ biz-certs/Frank-CA.cer ] -# -# ssl-cert-file: biz-certs/Frank-EE.cer -# ssl-key-file: biz-certs/Frank-EE.key -# ssl-ca-cert-file: biz-certs/Bob-Root.cer -# -# requests: -# list: -# type: list -# issue: -# type: issue -# class: 1 -# sia: [ "rsync://bandicoot.invalid/some/where/" ] -# revoke: -# type: revoke -# class: 1 -# ski: "CB5K6APY-4KcGAW9jaK_cVPXKX0" -# @endverbatim -# -# testpoke adds one extension to the language described in APNIC's -# README: the cms-cert-chain-* and ssl-cert-chain-* options, which allow -# one to specify a chain of intermediate certificates to be presented in -# the CMS or TLS protocol. APNIC's initial implementation required -# direct knowledge of the issuing certificate (ie, it supported a -# maximum chain length of one); subsequent APNIC code changes have -# probably relaxed this restriction, and with luck APNIC has copied -# testpoke's syntax to express chains of intermediate certificates. - -## @page Left-right Left-right protocol -# -# The left-right protocol is really two separate client/server -# protocols over separate channels between the RPKI engine and the IR -# back end (IRBE). The IRBE is the client for one of the -# subprotocols, the RPKI engine is the client for the other. -# -# @section Terminology -# -# @li @em IRBE: Internet Registry Back End -# -# @li @em IRDB: Internet Registry Data Base -# -# @li @em BPKI: Business PKI -# -# @li @em RPKI: Resource PKI -# -# @section Operations initiated by the IRBE -# -# This part of the protcol uses a kind of message-passing. Each %object -# that the RPKI engine knows about takes five messages: "create", "set", -# "get", "list", and "destroy". Actions which are not just data -# operations on %objects are handled via an SNMP-like mechanism, as if -# they were fields to be set. For example, to generate a keypair one -# "sets" the "generate-keypair" field of a BSC %object, even though there -# is no such field in the %object itself as stored in SQL. This is a bit -# of a kludge, but the reason for doing it as if these were variables -# being set is to allow composite operations such as creating a BSC, -# populating all of its data fields, and generating a keypair, all as a -# single operation. With this model, that's trivial, otherwise it's at -# least two round trips. -# -# Fields can be set in either "create" or "set" operations, the -# difference just being whether the %object already exists. A "get" -# operation returns all visible fields of the %object. A "list" -# operation returns a %list containing what "get" would have returned on -# each of those %objects. -# -# Left-right protocol %objects are encoded as signed CMS messages -# containing XML as eContent and using an eContentType OID of @c id-ct-xml -# (1.2.840.113549.1.9.16.1.28). These CMS messages are in turn passed -# as the data for HTTPS POST operations, with an HTTP content type of -# "application/x-rpki" for both the POST data and the response data. -# -# All operations allow an optional "tag" attribute which can be any -# alphanumeric token. The main purpose of the tag attribute is to allow -# batching of multiple requests into a single PDU. -# -# @subsection self_obj <self/> object -# -# A @c <self/> %object represents one virtual RPKI engine. In simple cases -# where the RPKI engine operator operates the engine only on their own -# behalf, there will only be one @c <self/> %object, representing the engine -# operator's organization, but in environments where the engine operator -# hosts other entities, there will be one @c @c <self/> %object per hosted -# entity (probably including the engine operator's own organization, -# considered as a hosted customer of itself). -# -# Some of the RPKI engine's configured parameters and data are shared by -# all hosted entities, but most are tied to a specific @c <self/> %object. -# Data which are shared by all hosted entities are referred to as -# "per-engine" data, data which are specific to a particular @c <self/> -# %object are "per-self" data. -# -# Since all other RPKI engine %objects refer to a @c <self/> %object via a -# "self_id" value, one must create a @c <self/> %object before one can -# usefully configure any other left-right protocol %objects. -# -# Every @c <self/> %object has a self_id attribute, which must be specified -# for the "set", "get", and "destroy" actions. -# -# Payload data which can be configured in a @c <self/> %object: -# -# @li @c use_hsm (attribute): -# Whether to use a Hardware Signing Module. At present this option -# has no effect, as the implementation does not yet support HSMs. -# -# @li @c crl_interval (attribute): -# Positive integer representing the planned lifetime of an RPKI CRL -# for this @c <self/>, measured in seconds. -# -# @li @c regen_margin (attribute): -# Positive integer representing how long before expiration of an -# RPKI certificiate a new one should be generated, measured in -# seconds. At present this only affects the one-off EE certificates -# associated with ROAs. -# -# @li @c bpki_cert (element): -# BPKI CA certificate for this @c <self/>. This is used as part of the -# certificate chain when validating incoming TLS and CMS messages, -# and should be the issuer of cross-certification BPKI certificates -# used in @c <repository/>, @c <parent/>, and @c <child/> %objects. If the -# bpki_glue certificate is in use (below), the bpki_cert certificate -# should be issued by the bpki_glue certificate; otherwise, the -# bpki_cert certificate should be issued by the per-engine bpki_ta -# certificate. -# -# @li @c bpki_glue (element): -# Another BPKI CA certificate for this @c <self/>, usually not needed. -# Certain pathological cross-certification cases require a -# two-certificate chain due to issuer name conflicts. If used, the -# bpki_glue certificate should be the issuer of the bpki_cert -# certificate and should be issued by the per-engine bpki_ta -# certificate; if not needed, the bpki_glue certificate should be -# left unset. -# -# Control attributes that can be set to "yes" to force actions: -# -# @li @c rekey: -# Start a key rollover for every RPKI CA associated with every -# @c <parent/> %object associated with this @c <self/> %object. This is the -# first phase of a key rollover operation. -# -# @li @c revoke: -# Revoke any remaining certificates for any expired key associated -# with any RPKI CA for any @c <parent/> %object associated with this -# @c <self/> %object. This is the second (cleanup) phase for a key -# rollover operation; it's separate from the first phase to leave -# time for new RPKI certificates to propegate and be installed. -# -# @li @c reissue: -# Not implemented, may be removed from protocol. Original theory -# was that this operation would force reissuance of any %object with -# a changed key, but as that happens automatically as part of the -# key rollover mechanism this operation seems unnecessary. -# -# @li @c run_now: -# Force immediate processing for all tasks associated with this -# @c <self/> %object that would ordinarily be performed under cron. Not -# currently implemented. -# -# @li @c publish_world_now: -# Force (re)publication of every publishable %object for this @c <self/> -# %object. Not currently implemented. Intended to aid in recovery -# if RPKI engine and publication engine somehow get out of sync. -# -# -# @subsection bsc_obj <bsc/> object -# -# The @c <bsc/> ("business signing context") %object represents all the BPKI -# data needed to sign outgoing CMS or HTTPS messages. Various other -# %objects include pointers to a @c <bsc/> %object. Whether a particular -# @c <self/> uses only one @c <bsc/> or multiple is a configuration decision -# based on external requirements: the RPKI engine code doesn't care, it -# just cares that, for any %object representing a relationship for which -# it must sign messages, there be a @c <bsc/> %object that it can use to -# produce that signature. -# -# Every @c <bsc/> %object has a bsc_id, which must be specified for the -# "get", "set", and "destroy" actions. Every @c <bsc/> also has a self_id -# attribute which indicates the @c <self/> %object with which this @c <bsc/> -# %object is associated. -# -# Payload data which can be configured in a @c <isc/> %object: -# -# @li @c signing_cert (element): -# BPKI certificate to use when generating a signature. -# -# @li @c signing_cert_crl (element): -# CRL which would %list signing_cert if it had been revoked. -# -# Control attributes that can be set to "yes" to force actions: -# -# @li @c generate_keypair: -# Generate a new BPKI keypair and return a PKCS #10 certificate -# request. The resulting certificate, once issued, should be -# configured as this @c <bsc/> %object's signing_cert. -# -# Additional attributes which may be specified when specifying -# "generate_keypair": -# -# @li @c key_type: -# Type of BPKI keypair to generate. "rsa" is both the default and, -# at the moment, the only allowed value. -# -# @li @c hash_alg: -# Cryptographic hash algorithm to use with this keypair. "sha256" -# is both the default and, at the moment, the only allowed value. -# -# @li @c key_length: -# Length in bits of the keypair to be generated. "2048" is both the -# default and, at the moment, the only allowed value. -# -# Replies to "create" and "set" actions that specify "generate-keypair" -# include a <bsc_pkcs10/> element, as do replies to "get" and "list" -# actions for a @c <bsc/> %object for which a "generate-keypair" command has -# been issued. The RPKI engine stores the PKCS #10 request, which -# allows the IRBE to reuse the request if and when it needs to reissue -# the corresponding BPKI signing certificate. -# -# @subsection parent_obj <parent/> object -# -# The @c <parent/> %object represents the RPKI engine's view of a particular -# parent of the current @c <self/> %object in the up-down protocol. Due to -# the way that the resource hierarchy works, a given @c <self/> may obtain -# resources from multiple parents, but it will always have at least one; -# in the case of IANA or an RIR, the parent RPKI engine may be a trivial -# stub. -# -# Every @c <parent/> %object has a parent_id, which must be specified for -# the "get", "set", and "destroy" actions. Every @c <parent/> also has a -# self_id attribute which indicates the @c <self/> %object with which this -# @c <parent/> %object is associated, a bsc_id attribute indicating the @c <bsc/> -# %object to be used when signing messages sent to this parent, and a -# repository_id indicating the @c <repository/> %object to be used when -# publishing issued by the certificate issued by this parent. -# -# Payload data which can be configured in a @c <parent/> %object: -# -# @li @c peer_contact_uri (attribute): -# HTTPS URI used to contact this parent. -# -# @li @c sia_base (attribute): -# The leading portion of an rsync URI that the RPKI engine should -# use when composing the publication URI for %objects issued by the -# RPKI certificate issued by this parent. -# -# @li @c sender_name (attribute): -# Sender name to use in the up-down protocol when talking to this -# parent. The RPKI engine doesn't really care what this value is, -# but other implementations of the up-down protocol do care. -# -# @li @c recipient_name (attribute): -# Recipient name to use in the up-down protocol when talking to this -# parent. The RPKI engine doesn't really care what this value is, -# but other implementations of the up-down protocol do care. -# -# @li @c bpki_cms_cert (element): -# BPKI CMS CA certificate for this @c <parent/>. This is used as part -# of the certificate chain when validating incoming CMS messages If -# the bpki_cms_glue certificate is in use (below), the bpki_cms_cert -# certificate should be issued by the bpki_cms_glue certificate; -# otherwise, the bpki_cms_cert certificate should be issued by the -# bpki_cert certificate in the @c <self/> %object. -# -# @li @c bpki_cms_glue (element): -# Another BPKI CMS CA certificate for this @c <parent/>, usually not -# needed. Certain pathological cross-certification cases require a -# two-certificate chain due to issuer name conflicts. If used, the -# bpki_cms_glue certificate should be the issuer of the -# bpki_cms_cert certificate and should be issued by the bpki_cert -# certificate in the @c <self/> %object; if not needed, the -# bpki_cms_glue certificate should be left unset. -# -# @li @c bpki_https_cert (element): -# BPKI HTTPS CA certificate for this @c <parent/>. This is like the -# bpki_cms_cert %object, only used for validating incoming TLS -# messages rather than CMS. -# -# @li @c bpki_cms_glue (element): -# Another BPKI HTTPS CA certificate for this @c <parent/>, usually not -# needed. This is like the bpki_cms_glue certificate, only used for -# validating incoming TLS messages rather than CMS. -# -# Control attributes that can be set to "yes" to force actions: -# -# @li @c rekey: -# This is like the rekey command in the @c <self/> %object, but limited -# to RPKI CAs under this parent. -# -# @li @c reissue: -# This is like the reissue command in the @c <self/> %object, but limited -# to RPKI CAs under this parent. -# -# @li @c revoke: -# This is like the revoke command in the @c <self/> %object, but limited -# to RPKI CAs under this parent. -# -# @subsection child_obj <child/> object -# -# The @c <child/> %object represents the RPKI engine's view of particular -# child of the current @c <self/> in the up-down protocol. -# -# Every @c <child/> %object has a parent_id, which must be specified for the -# "get", "set", and "destroy" actions. Every @c <child/> also has a -# self_id attribute which indicates the @c <self/> %object with which this -# @c <child/> %object is associated. -# -# Payload data which can be configured in a @c <child/> %object: -# -# @li @c bpki_cert (element): -# BPKI CA certificate for this @c <child/>. This is used as part of -# the certificate chain when validating incoming TLS and CMS -# messages. If the bpki_glue certificate is in use (below), the -# bpki_cert certificate should be issued by the bpki_glue -# certificate; otherwise, the bpki_cert certificate should be issued -# by the bpki_cert certificate in the @c <self/> %object. -# -# @li @c bpki_glue (element): -# Another BPKI CA certificate for this @c <child/>, usually not needed. -# Certain pathological cross-certification cases require a -# two-certificate chain due to issuer name conflicts. If used, the -# bpki_glue certificate should be the issuer of the bpki_cert -# certificate and should be issued by the bpki_cert certificate in -# the @c <self/> %object; if not needed, the bpki_glue certificate -# should be left unset. -# -# Control attributes that can be set to "yes" to force actions: -# -# @li @c reissue: -# Not implemented, may be removed from protocol. -# -# @subsection repository_obj <repository/> object -# -# The @c <repository/> %object represents the RPKI engine's view of a -# particular publication repository used by the current @c <self/> %object. -# -# Every @c <repository/> %object has a repository_id, which must be -# specified for the "get", "set", and "destroy" actions. Every -# @c <repository/> also has a self_id attribute which indicates the @c <self/> -# %object with which this @c <repository/> %object is associated. -# -# Payload data which can be configured in a @c <repository/> %object: -# -# @li @c peer_contact_uri (attribute): -# HTTPS URI used to contact this repository. -# -# @li @c bpki_cms_cert (element): -# BPKI CMS CA certificate for this @c <repository/>. This is used as part -# of the certificate chain when validating incoming CMS messages If -# the bpki_cms_glue certificate is in use (below), the bpki_cms_cert -# certificate should be issued by the bpki_cms_glue certificate; -# otherwise, the bpki_cms_cert certificate should be issued by the -# bpki_cert certificate in the @c <self/> %object. -# -# @li @c bpki_cms_glue (element): -# Another BPKI CMS CA certificate for this @c <repository/>, usually not -# needed. Certain pathological cross-certification cases require a -# two-certificate chain due to issuer name conflicts. If used, the -# bpki_cms_glue certificate should be the issuer of the -# bpki_cms_cert certificate and should be issued by the bpki_cert -# certificate in the @c <self/> %object; if not needed, the -# bpki_cms_glue certificate should be left unset. -# -# @li @c bpki_https_cert (element): -# BPKI HTTPS CA certificate for this @c <repository/>. This is like the -# bpki_cms_cert %object, only used for validating incoming TLS -# messages rather than CMS. -# -# @li @c bpki_cms_glue (element): -# Another BPKI HTTPS CA certificate for this @c <repository/>, usually not -# needed. This is like the bpki_cms_glue certificate, only used for -# validating incoming TLS messages rather than CMS. -# -# At present there are no control attributes for @c <repository/> %objects. -# -# @subsection route_origin_obj <route_origin/> object -# -# The @c <route_origin/> %object is a kind of prototype for a ROA. It -# contains all the information needed to generate a ROA once the RPKI -# engine obtains the appropriate RPKI certificates from its parent(s). -# -# Note that a @c <route_origin/> %object represents a ROA to be generated on -# behalf of @c <self/>, not on behalf of a @c <child/>. Thus, a hosted entity -# that has no children but which does need to generate ROAs would be -# represented by a hosted @c <self/> with no @c <child/> %objects but one or -# more @c <route_origin/> %objects. While lumping ROA generation in with -# the other RPKI engine activities may seem a little odd at first, it's -# a natural consequence of the design requirement that the RPKI daemon -# never transmit private keys across the network in any form; given this -# requirement, the RPKI engine that holds the private keys for an RPKI -# certificate must also be the engine which generates any ROAs that -# derive from that RPKI certificate. -# -# The precise content of the @c <route_origin/> has changed over time as -# the underlying ROA specification has changed. The current -# implementation as of this writing matches what we expect to see in -# draft-ietf-sidr-roa-format-03, once it is issued. In particular, note -# that the exactMatch boolean from the -02 draft has been replaced by -# the prefix and maxLength encoding used in the -03 draft. -# -# Payload data which can be configured in a @c <route_origin/> %object: -# -# @li @c as_number (attribute): -# Autonomous System Number (ASN) to place in the generated ROA. A -# single ROA can only grant authorization to a single ASN; multiple -# ASNs require multiple ROAs, thus multiple @c <route_origin/> %objects. -# -# @li @c ipv4 (attribute): -# %List of IPv4 prefix and maxLength values, see below for format. -# -# @li @c ipv6 (attribute): -# %List of IPv6 prefix and maxLength values, see below for format. -# -# Control attributes that can be set to "yes" to force actions: -# -# @li @c suppress_publication: -# Not implemented, may be removed from protocol. -# -# The lists of IPv4 and IPv6 prefix and maxLength values are represented -# as comma-separated text strings, with no whitespace permitted. Each -# entry in such a string represents a single prefix/maxLength pair. -# -# ABNF for these address lists: -# -# @verbatim -# -# <ROAIPAddress> ::= <address> "/" <prefixlen> [ "-" <max_prefixlen> ] -# ; Where <max_prefixlen> defaults to the same -# ; value as <prefixlen>. -# -# <ROAIPAddressList> ::= <ROAIPAddress> *( "," <ROAIPAddress> ) -# -# @endverbatim -# -# For example, @c "10.0.1.0/24-32,10.0.2.0/24", which is a shorthand -# form of @c "10.0.1.0/24-32,10.0.2.0/24-24". -# -# @section irdb_queries Operations initiated by the RPKI engine -# -# The left-right protocol also includes queries from the RPKI engine -# back to the IRDB. These queries do not follow the message-passing -# pattern used in the IRBE-initiated part of the protocol. Instead, -# there's a single query back to the IRDB, with a corresponding -# response. The CMS and HTTPS encoding are the same as in the rest of -# the protocol, but the BPKI certificates will be different as the -# back-queries and responses form a separate communication channel. -# -# @subsection list_resources_msg <list_resources/> messages -# -# The @c <list_resources/> query and response allow the RPKI engine to ask -# the IRDB for information about resources assigned to a particular -# child. The query must include both a @c "self_id" attribute naming -# the @c <self/> that is making the request and also a @c "child_id" -# attribute naming the child that is the subject of the query. The -# query and response also allow an optional @c "tag" attribute of the -# same form used elsewhere in this protocol, to allow batching. -# -# A @c <list_resources/> response includes the following attributes, along -# with the @c tag (if specified), @c self_id, and @c child_id copied -# from the request: -# -# @li @c valid_until: -# A timestamp indicating the date and time at which certificates -# generated by the RPKI engine for these data should expire. The -# timestamp is expressed as an XML @c xsd:dateTime, must be -# expressed in UTC, and must carry the "Z" suffix indicating UTC. -# -# @li @c subject_name: -# An optional text string naming the child. Not currently used. -# -# @li @c asn: -# A %list of autonomous sequence numbers, expressed as a -# comma-separated sequence of decimal integers with no whitespace. -# -# @li @c ipv4: -# A %list of IPv4 address prefixes and ranges, expressed as a -# comma-separated %list of prefixes and ranges with no whitespace. -# See below for format details. -# -# @li @c ipv6: -# A %list of IPv6 address prefixes and ranges, expressed as a -# comma-separated %list of prefixes and ranges with no whitespace. -# See below for format details. -# -# Entries in a %list of address prefixes and ranges can be either -# prefixes, which are written in the usual address/prefixlen notation, -# or ranges, which are expressed as a pair of addresses denoting the -# beginning and end of the range, written in ascending order separated -# by a single "-" character. This format is superficially similar to -# the format used for prefix and maxLength values in the @c <route_origin/> -# %object, but the semantics differ: note in particular that -# @c <route_origin/> %objects don't allow ranges, while @c <list_resources/> -# messages don't allow a maxLength specification. -# -# @section left_right_error_handling Error handling -# -# Error in this protocol are handled at two levels. -# -# Since all messages in this protocol are conveyed over HTTPS -# connections, basic errors are indicated via the HTTP response code. -# 4xx and 5xx responses indicate that something bad happened. Errors -# that make it impossible to decode a query or encode a response are -# handled in this way. -# -# Where possible, errors will result in a @c <report_error/> message which -# takes the place of the expected protocol response message. -# @c <report_error/> messages are CMS-signed XML messages like the rest of -# this protocol, and thus can be archived to provide an audit trail. -# -# @c <report_error/> messages only appear in replies, never in queries. -# The @c <report_error/> message can appear on either the "forward" (IRBE -# as client of RPKI engine) or "back" (RPKI engine as client of IRDB) -# communication channel. -# -# The @c <report_error/> message includes an optional @c "tag" attribute to -# assist in matching the error with a particular query when using -# batching, and also includes a @c "self_id" attribute indicating the -# @c <self/> that issued the error. -# -# The error itself is conveyed in the @c error_code (attribute). The -# value of this attribute is a token indicating the specific error that -# occurred. At present this will be the name of a Python exception; the -# production version of this protocol will nail down the allowed error -# tokens here, probably in the RelaxNG schema. -# -# The body of the @c <report_error/> element itself is an optional text -# string; if present, this is debugging information. At present this -# capabilty is not used, debugging information goes to syslog. - -## @page Publication Publication protocol -# -# The %publication protocol is really two separate client/server -# protocols, between different parties. The first is a configuration -# protocol for an IRBE to use to configure a %publication engine, -# the second is the interface by which authorized clients request -# %publication of specific objects. -# -# Much of the architecture of the %publication protocol is borrowed -# from the @link Left-right left-right protocol: @endlink like the -# left-right protocol, the %publication protocol uses CMS-wrapped XML -# over HTTPS with the same eContentType OID and the same HTTPS -# content-type, and the overall style of the XML messages is very -# similar to the left-right protocol. All operations allow an -# optional "tag" attribute to allow batching. -# -# The %publication engine operates a single HTTPS server which serves -# both of these subprotocols. The two subprotocols share a single -# server port, but use distinct URLs to allow demultiplexing. -# -# @section Terminology -# -# @li @em IRBE: Internet Registry Back End -# -# @li @em IRDB: Internet Registry Data Base -# -# @li @em BPKI: Business PKI -# -# @li @em RPKI: Resource PKI -# -# @section Publication-control Publication control subprotocol -# -# The control subprotocol reuses the message-passing design of the -# left-right protocol. Configured objects support the "create", "set", -# "get", "list", and "destroy" actions, or a subset thereof when the -# full set of actions doesn't make sense. -# -# @subsection config_obj <config/> object -# -# The <config/> %object allows configuration of data that apply to the -# entire %publication server rather than a particular client. -# -# There is exactly one <config/> %object in the %publication server, and -# it only supports the "set" and "get" actions -- it cannot be created -# or destroyed. -# -# Payload data which can be configured in a <config/> %object: -# -# @li @c bpki_crl (element): -# This is the BPKI CRL used by the %publication server when -# signing the CMS wrapper on responses in the %publication -# subprotocol. As the CRL must be updated at regular intervals, -# it's not practical to restart the %publication server when the -# BPKI CRL needs to be updated. The BPKI model doesn't require -# use of a BPKI CRL between the IRBE and the %publication server, -# so we can use the %publication control subprotocol to update the -# BPKI CRL. -# -# @subsection client_obj <client/> object -# -# The <client/> %object represents one client authorized to use the -# %publication server. -# -# The <client/> %object supports the full set of "create", "set", "get", -# "list", and "destroy" actions. Each client has a "client_id" -# attribute, which is used in responses and must be specified in "set", -# "get", or "destroy" actions. -# -# Payload data which can be configured in a <client/> %object: -# -# @li @c base_uri (attribute): -# This is the base URI below which this client is allowed to publish -# data. The %publication server may impose additional constraints in -# the case of a child publishing beneath its parent. -# -# @li @c bpki_cert (element): -# BPKI CA certificate for this <client/>. This is used as part of -# the certificate chain when validating incoming TLS and CMS -# messages. If the bpki_glue certificate is in use (below), the -# bpki_cert certificate should be issued by the bpki_glue -# certificate; otherwise, the bpki_cert certificate should be issued -# by the %publication engine's bpki_ta certificate. -# -# @li @c bpki_glue (element): -# Another BPKI CA certificate for this <client/>, usually not -# needed. Certain pathological cross-certification cases require a -# two-certificate chain due to issuer name conflicts. If used, the -# bpki_glue certificate should be the issuer of the bpki_cert -# certificate and should be issued by the %publication engine's -# bpki_ta certificate; if not needed, the bpki_glue certificate -# should be left unset. -# -# @section Publication-publication Publication subprotocol -# -# The %publication subprotocol is structured somewhat differently from -# the %publication control protocol. Objects in the %publication -# subprotocol represent objects to be published or objects to be -# withdrawn from %publication. Each kind of %object supports two actions: -# "publish" and "withdraw". In each case the XML element representing -# hte %object to be published or withdrawn has a "uri" attribute which -# contains the %publication URI. For "publish" actions, the XML element -# body contains the DER %object to be published, encoded in Base64; for -# "withdraw" actions, the XML element body is empty. -# -# In theory, the detailed access control for each kind of %object might -# be different. In practice, as of this writing, access control for all -# objects is a simple check that the client's @c "base_uri" is a leading -# substring of the %publication URI. Details of why access control might -# need to become more complicated are discussed in a later section. -# -# @subsection certificate_obj <certificate/> object -# -# The <certificate/> %object represents an RPKI certificate to be -# published or withdrawn. -# -# @subsection crl_obj <crl/> object -# -# The <crl/> %object represents an RPKI CRL to be published or withdrawn. -# -# @subsection manifest_obj <manifest/> object -# -# The <manifest/> %object represents an RPKI %publication %manifest to be -# published or withdrawn. -# -# Note that part of the reason for the batching support in the -# %publication protocol is because @em every %publication or withdrawal -# action requires a new %manifest, thus every %publication or withdrawal -# action will involve at least two objects. -# -# @subsection roa_obj <roa/> object -# -# The <roa/> %object represents a ROA to be published or withdrawn. -# -# @section publication_error_handling Error handling -# -# Error in this protocol are handled at two levels. -# -# Since all messages in this protocol are conveyed over HTTPS -# connections, basic errors are indicated via the HTTP response code. -# 4xx and 5xx responses indicate that something bad happened. Errors -# that make it impossible to decode a query or encode a response are -# handled in this way. -# -# Where possible, errors will result in a <report_error/> message which -# takes the place of the expected protocol response message. -# <report_error/> messages are CMS-signed XML messages like the rest of -# this protocol, and thus can be archived to provide an audit trail. -# -# <report_error/> messages only appear in replies, never in -# queries. The <report_error/> message can appear in both the -# control and publication subprotocols. -# -# The <report_error/> message includes an optional @c "tag" attribute to -# assist in matching the error with a particular query when using -# batching. -# -# The error itself is conveyed in the @c error_code (attribute). The -# value of this attribute is a token indicating the specific error that -# occurred. At present this will be the name of a Python exception; the -# production version of this protocol will nail down the allowed error -# tokens here, probably in the RelaxNG schema. -# -# The body of the <report_error/> element itself is an optional text -# string; if present, this is debugging information. At present this -# capabilty is not used, debugging information goes to syslog. -# -# @section publication_access_control Additional access control considerations. -# -# As detailed above, the %publication protocol is trivially simple. This -# glosses over two bits of potential complexity: -# -# @li In the case where parent and child are sharing a repository, we'd -# like to nest child under parent, because testing has demonstrated -# that even on relatively slow hardware the delays involved in -# setting up separate rsync connections tend to dominate -# synchronization time for relying parties. -# -# @li The repository operator might also want to do some checks to -# assure itself that what it's about to allow the RPKI engine to -# publish is not dangerous toxic waste. -# -# The up-down protocol includes a mechanism by which a parent can -# suggest a %publication URI to each of its children. The children are -# not required to accept this hint, and the children must make separate -# arrangements with the repository operator (who might or might not be -# the same as the entity that hosts the children's RPKI engine -# operations) to use the suggested %publication point, but if everything -# works out, this allows children to nest cleanly under their parents -# %publication points, which helps reduce synchronization time for -# relying parties. -# -# In this case, one could argue that the %publication server is -# responsible for preventing one of its clients (the child in the above -# description) from stomping on data published by another of its clients -# (the parent in the above description). This goes beyond the basic -# access check and requires the %publication server to determine whether -# the parent has given its consent for the child to publish under the -# parent. Since the RPKI certificate profile requires the child's -# %publication point to be indicated in an SIA extension in a certificate -# issued by the parent to the child, the %publication engine can infer -# this permission from the parent's issuance of a certificate to the -# child. Since, by definition, the parent also uses this %publication -# server, this is an easy check, as the %publication server should -# already have the parent's certificate available by the time it needs -# to check the child's certificate. -# -# The previous paragraph only covers a "publish" action for a -# <certificate/> %object. For "publish" actions on other -# objects, the %publication server would need to trace permission back -# to the certificate issued by the parent; for "withdraw" actions, -# the %publication server would have to perform the same checks it -# would perform for a "publish" action, using the current published -# data before withdrawing it. The latter in turn implies an ordering -# constraint on "withdraw" actions in order to preserve the data -# necessary for these access control decisions; as this may prove -# impractical, the %publication server may probably need to make -# periodic sweeps over its published data looking for orphaned -# objects, but that's probably a good idea anyway. -# -# Note that, in this %publication model, any agreement that the -# repository makes to publish the RPKI engine's output is conditional -# upon the %object to be published passing whatever access control checks -# the %publication server imposes. - -## @page sql-schemas SQL database schemas -# -# @li @subpage rpkid-sql "rpkid database schema" -# @li @subpage pubd-sql "pubd database schema" -# @li @subpage irdbd-sql "irdbd database schema" - -## @page rpkid-sql rpkid SQL schema -# -# @dotfile rpkid.dot "Diagram of rpkid.sql" -# -# @verbinclude rpkid.sql - -## @page pubd-sql pubd SQL Schema -# -# @dotfile pubd.dot "Diagram of pubd.sql" -# -# @verbinclude pubd.sql - -## @page irdbd-sql irdbd SQL Schema -# -# @dotfile irdbd.dot "Diagram of irdbd.sql" -# -# @verbinclude irdbd.sql - -## @page bpki-model BPKI model -# -# The "business PKI" (BPKI) is the PKI used to authenticate -# communication on the up-down, left-right, and %publication protocols. -# BPKI certificates are @em not resource PKI (RPKI) certificates. The -# BPKI is a separate PKI that represents relationships between the -# various entities involved in the production side of the RPKI system. -# In most cases the BPKI tree will follow existing business -# relationships, hence the name "BPKI". -# -# Setup of the BPKI is handled by the back end; for the most part, -# rpkid and pubd just use the result. The one place where the engines -# are directly involved in creation of new BPKI certificates is in the -# production of end-entity certificates for use by the engines. -# -# There are a few design principals that underly the chosen BPKI model: -# @li Each engine should rely on a single BPKI trust anchor which is -# controlled by the back end entity that runs the engine; all -# other trust material should be cross-certified into the engine's -# BPKI tree. -# @li Private keys must never transit the network. -# @li Except for end entity certificates, the engine should only have -# access to the BPKI certificates; in particular, the private key -# for the BPKI trust anchor should not be accessible to the engine. -# @li The number of BPKI keys and certificates that the engine has to -# manage should be no larger than is necessary. -# -# rpkid's hosting model adds an additional constraint: rpkid's BPKI -# trust anchor belongs to the entity operating rpkid, but the entities -# hosted by rpkid should have control of their own BPKI private keys. -# This implies the need for an additional layer of BPKI certificate -# hierarchy within rpkid. -# -# Here is a simplified picture of what the BPKI might look like for an -# rpkid operator that hosts two entities, "Alice" and "Ellen": -# -# @dot -# // Color code: -# // Black: Hosting entity -# // Blue: Hosted entity -# // Red: Cross-certified peer -# // -# // Shape code: -# // Octagon: TA -# // Diamond: CA -# // Record: EE -# -# digraph bpki_rpkid { -# splines = true; -# size = "14,14"; -# node [ fontname = Times, fontsize = 9 ]; -# -# // Hosting entity -# node [ color = black, shape = record ]; -# TA [ shape = octagon, label = "BPKI TA" ]; -# rpkid [ label = "rpkid|{HTTPS server|HTTPS left-right client|CMS left-right}" ]; -# irdbd [ label = "irdbd|{HTTPS left-right server|CMS left-right}" ]; -# irbe [ label = "IRBE|{HTTPS left-right client|CMS left-right}" ]; -# -# // Hosted entities -# node [ color = blue, fontcolor = blue ]; -# Alice_CA [ shape = diamond ]; -# Alice_EE [ label = "Alice\nBSC EE|{HTTPS up-down client|CMS up-down}" ]; -# Ellen_CA [ shape = diamond ]; -# Ellen_EE [ label = "Ellen\nBSC EE|{HTTPS up-down client|CMS up-down}" ]; -# -# // Peers -# node [ color = red, fontcolor = red, shape = diamond ]; -# Bob_CA; -# Carol_CA; -# Dave_CA; -# Frank_CA; -# Ginny_CA; -# Harry_CA; -# node [ shape = record ]; -# Bob_EE [ label = "Bob\nEE|{HTTPS up-down|CMS up-down}" ]; -# Carol_EE [ label = "Carol\nEE|{HTTPS up-down|CMS up-down}" ]; -# Dave_EE [ label = "Dave\nEE|{HTTPS up-down|CMS up-down}" ]; -# Frank_EE [ label = "Frank\nEE|{HTTPS up-down|CMS up-down}" ]; -# Ginny_EE [ label = "Ginny\nEE|{HTTPS up-down|CMS up-down}" ]; -# Harry_EE [ label = "Bob\nEE|{HTTPS up-down|CMS up-down}" ]; -# -# edge [ color = black, style = solid ]; -# TA -> Alice_CA; -# TA -> Ellen_CA; -# -# edge [ color = black, style = dotted ]; -# TA -> rpkid; -# TA -> irdbd; -# TA -> irbe; -# -# edge [ color = blue, style = solid ]; -# Alice_CA -> Bob_CA; -# Alice_CA -> Carol_CA; -# Alice_CA -> Dave_CA; -# Ellen_CA -> Frank_CA; -# Ellen_CA -> Ginny_CA; -# Ellen_CA -> Harry_CA; -# -# edge [ color = blue, style = dotted ]; -# Alice_CA -> Alice_EE; -# Ellen_CA -> Ellen_EE; -# -# edge [ color = red, style = solid ]; -# Bob_CA -> Bob_EE; -# Carol_CA -> Carol_EE; -# Dave_CA -> Dave_EE; -# Frank_CA -> Frank_EE; -# Ginny_CA -> Ginny_EE; -# Harry_CA -> Harry_EE; -# } -# @enddot -# -# Black objects belong to the hosting entity, blue objects belong to -# the hosted entities, red objects are cross-certified objects from -# the hosted entities' peers. The arrows indicate certificate -# issuance: solid arrows are the ones that rpkid will care about -# during certificate validation, dotted arrows show the origin of the -# EE certificates that rpkid uses to sign CMS and TLS messages. -# -# There's one nasty bit where the model had to bend to fit the current -# state of the underlying protocols: it's not possible to use exactly -# the same BPKI keys and certificates for HTTPS and CMS. The reason -# for this is simple: each hosted entity has its own BPKI, as does the -# hosting entity, but the HTTPS listener is shared. The only ways to -# avoid sharing the HTTPS server certificate would be to use separate -# listeners for each hosted entity, which scales poorly, or to rely on -# the TLS "Server Name Indication" extension (RFC 4366 3.1) which is -# not yet widely implemented. -# -# The certificate tree looks complicated, but the set of certificates -# needed to build any particular validation chain is obvious, again -# excepting the HTTPS server case, where the client certificate is the -# first hint that the engine has of the client's identity, so the -# server must be prepared to accept any current client certificate. -# -# Detailed instructions on how to build a BPKI are beyond the scope of -# this document, but one can handle simple cases using the OpenSSL -# command line tool and cross_certify.py; the latter is a tool -# designed specifically for the purpose of generating the -# cross-certification certificates needed to splice foreign trust -# material into a BPKI tree. -# -# The BPKI tree for a pubd instance is similar to to the BPKI tree for -# an rpkid instance, but is a bit simpler, as pubd does not provide -# hosting in the same sense that rpkid does: pubd is a relatively -# simple server that publishes objects as instructed by its clients. -# -# Here's a simplified picture of what the BPKI might look like for a -# pubd operator that serves two clients, "Alice" and "Bob": -# -# @dot -# // Color code: -# // Black: Operating entity -# // Red: Cross-certified client -# // -# // Shape code: -# // Octagon: TA -# // Diamond: CA -# // Record: EE -# -# digraph bpki_pubd { -# splines = true; -# size = "14,14"; -# node [ fontname = Times, fontsize = 9 ]; -# -# // Operating entity -# node [ color = black, fontcolor = black, shape = record ]; -# TA [ shape = octagon, label = "BPKI TA" ]; -# pubd [ label = "pubd|{HTTPS server|CMS}" ]; -# ctl [ label = "Control|{HTTPS client|CMS}" ]; -# -# // Clients -# node [ color = red, fontcolor = red, shape = diamond ]; -# Alice_CA; -# Bob_CA; -# node [ color = red, fontcolor = red, shape = record ]; -# Alice_EE [ label = "Alice\nEE|{HTTPS client|CMS}" ]; -# Bob_EE [ label = "Bob\nEE|{HTTPS client|CMS}" ]; -# -# edge [ color = black, style = dotted ]; -# TA -> pubd; -# TA -> ctl; -# -# edge [ color = black, style = solid ]; -# TA -> Alice_CA; -# TA -> Bob_CA; -# -# edge [ color = red, style = solid ]; -# Alice_CA -> Alice_EE; -# Bob_CA -> Bob_EE; -# } -# @enddot -# -# While it is likely that RIRs (at least) will operate both rpkid and -# pubd instances, the two functions are conceptually separate. As far -# as pubd is concerned, it doesn't matter who operates the rpkid -# instance: pubd just has clients, each of which has trust material -# that has been cross-certified into pubd's BPKI. Similarly, rpkid -# doesn't really care who operates a pubd instance that it's been -# configured to use, it just treats that pubd as a foreign BPKI whose -# trust material has to be cross-certified into its own BPKI. Cross -# certification itself is done by the back end operator, using -# cross_certify or some equivalent tool; the resulting BPKI -# certificates are configured into rpkid and pubd via the left-right -# protocol and the control subprotocol of the publication protocol, -# respectively. -# -# Because the BPKI tree is almost entirely controlled by the operating -# entity, CRLs are not necessary for most of the BPKI. The one -# exception to this is the EE certificates issued under the -# cross-certification points. These EE certificates are generated by -# the peer, not the local operator, and thus require CRLs. Because of -# this, both rpkid and pubd require regular updates of certain BPKI -# CRLs, again via the left-right and publication control protocols. -# -# Because the left-right protocol and the publication control -# subprotocol are used to configure BPKI certificates and CRLs, they -# cannot themselves use certificates and CRLs configured in this way. -# This is why the configuration files for rpkid and pubd require -# static configuration of the left-right and publication control -# certificates. - -# Local Variables: -# compile-command: "cd .. && make doc" -# End: diff --git a/rpkid.stable/rpki/config.py b/rpkid.stable/rpki/config.py deleted file mode 100644 index df856311..00000000 --- a/rpkid.stable/rpki/config.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Configuration file parsing utilities, layered on top of stock -Python ConfigParser module. - -$Id$ - -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. -""" - -import ConfigParser - -class parser(ConfigParser.RawConfigParser): - - def __init__(self, file = None, section = None): - """Initialize this parser.""" - ConfigParser.RawConfigParser.__init__(self) - if file: - self.read(file) - self.default_section = section - - def multiget(self, option, section = None): - """Parse OpenSSL-style foo.0, foo.1, ... subscripted options. - - Returns a list of values matching the specified option name. - """ - matches = [] - if section is None: - section = self.default_section - if self.has_option(section, option): - matches.append((-1, self.get(option, section = section))) - for key, value in self.items(section): - s = key.rsplit(".", 1) - if len(s) == 2 and s[0] == option and s[1].isdigit(): - matches.append((int(s[1]), value)) - matches.sort() - return [match[1] for match in matches] - - def get(self, option, default = None, section = None): - """Get an option, perhaps with a default value.""" - if section is None: - section = self.default_section - if default is None or self.has_option(section, option): - return ConfigParser.RawConfigParser.get(self, section, option) - else: - return default diff --git a/rpkid.stable/rpki/exceptions.py b/rpkid.stable/rpki/exceptions.py deleted file mode 100644 index 393700e6..00000000 --- a/rpkid.stable/rpki/exceptions.py +++ /dev/null @@ -1,135 +0,0 @@ -"""Exception definitions for RPKI modules. - -$Id$ - -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. -""" - -class RPKI_Exception(Exception): - """Base class for RPKI exceptions.""" - -class NotInDatabase(RPKI_Exception): - """Lookup failed for an object expected to be in the database.""" - -class BadURISyntax(RPKI_Exception): - """Illegal syntax for a URI.""" - -class BadStatusCode(RPKI_Exception): - """Unrecognized protocol status code.""" - -class BadQuery(RPKI_Exception): - """Unexpected protocol query.""" - -class DBConsistancyError(RPKI_Exception): - """Found multiple matches for a database query that shouldn't ever return that.""" - -class CMSVerificationFailed(RPKI_Exception): - """Verification of a CMS message failed.""" - -class HTTPRequestFailed(RPKI_Exception): - """HTTP request failed.""" - -class DERObjectConversionError(RPKI_Exception): - """Error trying to convert a DER-based object from one representation to another.""" - -class NotACertificateChain(RPKI_Exception): - """Certificates don't form a proper chain.""" - -class BadContactURL(RPKI_Exception): - """Error trying to parse up-down protocol contact URL.""" - -class BadClassNameSyntax(RPKI_Exception): - """Illegal syntax for a class_name.""" - -class BadIssueResponse(RPKI_Exception): - """issue_response PDU with wrong number of classes or certificates.""" - -class NotImplementedYet(RPKI_Exception): - """Internal error -- not implemented yet.""" - -class BadPKCS10(RPKI_Exception): - """Bad PKCS #10 object.""" - -class UpstreamError(RPKI_Exception): - """Received an error from upstream.""" - -class ChildNotFound(RPKI_Exception): - """Could not find specified child in database.""" - -class BSCNotFound(RPKI_Exception): - """Could not find specified BSC in database.""" - -class BadSender(RPKI_Exception): - """Unexpected XML sender value.""" - -class ClassNameMismatch(RPKI_Exception): - """class_name does not match child context.""" - -class ClassNameUnknown(RPKI_Exception): - """Unknown class_name.""" - -class SKIMismatch(RPKI_Exception): - """SKI value in response does not match request.""" - -class SubprocessError(RPKI_Exception): - """Subprocess returned unexpected error.""" - -class BadIRDBReply(RPKI_Exception): - """Unexpected reply to IRDB query.""" - -class NotFound(RPKI_Exception): - """Object not found in database.""" - -class MustBePrefix(RPKI_Exception): - """Resource range cannot be expressed as a prefix.""" - -class TLSValidationError(RPKI_Exception): - """TLS certificate validation error.""" - -class MultipleTLSEECert(TLSValidationError): - """Received more than one TLS EE certificate.""" - -class ReceivedTLSCACert(TLSValidationError): - """Received CA certificate via TLS.""" - -class WrongEContentType(RPKI_Exception): - """Received wrong CMS eContentType.""" - -class EmptyPEM(RPKI_Exception): - """Couldn't find PEM block to convert.""" - -class UnexpectedCMSCerts(RPKI_Exception): - """Received CMS certs when not expecting any.""" - -class UnexpectedCMSCRLs(RPKI_Exception): - """Received CMS CRLs when not expecting any.""" - -class MissingCMSEEcert(RPKI_Exception): - """Didn't receive CMS EE cert when expecting one.""" - -class MissingCMSCRL(RPKI_Exception): - """Didn't receive CMS CRL when expecting one.""" - -class UnparsableCMSDER(RPKI_Exception): - """Alleged CMS DER wasn't parsable.""" - -class CMSCRLNotSet(RPKI_Exception): - """CMS CRL has not been configured.""" - -class ServerShuttingDown(RPKI_Exception): - """Server is shutting down.""" - -class NoActiveCA(RPKI_Exception): - """No active ca_detail for specified class.""" diff --git a/rpkid.stable/rpki/https.py b/rpkid.stable/rpki/https.py deleted file mode 100644 index a0443c01..00000000 --- a/rpkid.stable/rpki/https.py +++ /dev/null @@ -1,291 +0,0 @@ -"""HTTPS utilities, both client and server. - -At the moment this only knows how to use the PEM certs in my -subversion repository; generalizing it would not be hard, but the more -general version should use SQL anyway. - -$Id$ - -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. -""" - -import httplib, BaseHTTPServer, tlslite.api, glob, traceback, urlparse, socket, signal -import rpki.x509, rpki.exceptions, rpki.log - -# This should be wrapped somewhere in rpki.x509 eventually -import POW - -# Do not set this to True for production use! -disable_tls_certificate_validation_exceptions = False - -# Chatter about TLS certificates -debug_tls_certs = False - -rpki_content_type = "application/x-rpki" - -def tlslite_certChain(x509): - """Utility function to construct tlslite certChains.""" - if isinstance(x509, rpki.x509.X509): - return tlslite.api.X509CertChain([x509.get_tlslite()]) - else: - return tlslite.api.X509CertChain([x.get_tlslite() for x in x509]) - -def build_https_ta_cache(certs): - """Build a dynamic TLS trust anchor cache.""" - - store = POW.X509Store() - for x in certs: - if rpki.https.debug_tls_certs: - rpki.log.debug("HTTPS dynamic trusted cert issuer %s [%s] subject %s [%s]" % (x.getIssuer(), x.hAKI(), x.getSubject(), x.hSKI())) - store.addTrust(x.get_POW()) - return store - -class Checker(tlslite.api.Checker): - """Derived class to handle X.509 client certificate checking.""" - - ## @var refuse_tls_ca_certs - # Raise an exception upon receiving CA certificates via TLS rather - # than just quietly ignoring them. - - refuse_tls_ca_certs = False - - ## @var pem_dump_tls_certs - # Vile debugging hack - - pem_dump_tls_certs = False - - def __init__(self, trust_anchor = None, dynamic_https_trust_anchor = None): - """Initialize our modified certificate checker.""" - - self.dynamic_https_trust_anchor = dynamic_https_trust_anchor - - if dynamic_https_trust_anchor is not None: - return - - self.x509store = POW.X509Store() - - trust_anchor = rpki.x509.X509.normalize_chain(trust_anchor) - assert trust_anchor - - for x in trust_anchor: - if debug_tls_certs: - rpki.log.debug("HTTPS trusted cert issuer %s [%s] subject %s [%s]" % (x.getIssuer(), x.hAKI(), x.getSubject(), x.hSKI())) - self.x509store.addTrust(x.get_POW()) - if self.pem_dump_tls_certs: - print x.get_PEM() - - def x509store_thunk(self): - if self.dynamic_https_trust_anchor is not None: - return self.dynamic_https_trust_anchor() - else: - return self.x509store - - def __call__(self, tlsConnection): - """POW/OpenSSL-based certificate checker. - - Given our BPKI model, we're only interested in the TLS EE - certificates. - """ - - if tlsConnection._client: - chain = tlsConnection.session.serverCertChain - peer = "server" - else: - chain = tlsConnection.session.clientCertChain - peer = "client" - - chain = [rpki.x509.X509(tlslite = chain.x509List[i]) for i in range(chain.getNumCerts())] - - ee = None - - for x in chain: - - if debug_tls_certs: - rpki.log.debug("Received %s TLS %s cert issuer %s [%s] subject %s [%s]" - % (peer, "CA" if x.is_CA() else "EE", x.getIssuer(), x.hAKI(), x.getSubject(), x.hSKI())) - if self.pem_dump_tls_certs: - print x.get_PEM() - - if x.is_CA(): - if self.refuse_tls_ca_certs: - raise rpki.exceptions.ReceivedTLSCACert - continue - - if ee is not None: - raise rpki.exceptions.MultipleTLSEECert, chain - ee = x - - result = self.x509store_thunk().verifyDetailed(ee.get_POW()) - if not result[0]: - rpki.log.debug("TLS certificate validation result %s" % repr(result)) - if disable_tls_certificate_validation_exceptions: - rpki.log.warn("DANGER WILL ROBINSON! IGNORING TLS VALIDATION FAILURE!") - else: - raise rpki.exceptions.TLSValidationError - -class httpsClient(tlslite.api.HTTPTLSConnection): - """Derived class to let us replace the default Checker.""" - - def __init__(self, host, port = None, - client_cert = None, client_key = None, - server_ta = None, settings = None): - """Create a new httpsClient.""" - - tlslite.api.HTTPTLSConnection.__init__( - self, host = host, port = port, settings = settings, - certChain = client_cert, privateKey = client_key) - - self.checker = Checker(trust_anchor = server_ta) - -def client(msg, client_key, client_cert, server_ta, url, timeout = 300): - """Open client HTTPS connection, send a message, wait for response. - - This function wraps most of what one needs to do to send a message - over HTTPS and get a response. The certificate checking isn't quite - up to snuff; it's better than with the other packages I've found, - but doesn't appear to handle subjectAltName extensions (sigh). - """ - - u = urlparse.urlparse(url) - - assert u.scheme in ("", "https") and \ - u.username is None and \ - u.password is None and \ - u.params == "" and \ - u.query == "" and \ - u.fragment == "" - - rpki.log.debug("Contacting %s" % url) - - if debug_tls_certs: - for cert in (client_cert,) if isinstance(client_cert, rpki.x509.X509) else client_cert: - rpki.log.debug("Sending client TLS cert issuer %s subject %s" % (cert.getIssuer(), cert.getSubject())) - - # We could add a "settings = foo" argument to the following call to - # pass in a tlslite.HandshakeSettings object that would let us - # insist on, eg, particular SSL/TLS versions. - - httpc = httpsClient(host = u.hostname or "localhost", - port = u.port or 443, - client_key = client_key.get_tlslite(), - client_cert = tlslite_certChain(client_cert), - server_ta = server_ta) - httpc.connect() - httpc.sock.settimeout(timeout) - httpc.request("POST", u.path, msg, {"Content-Type" : rpki_content_type}) - response = httpc.getresponse() - if response.status == httplib.OK: - return response.read() - else: - r = response.read() - raise rpki.exceptions.HTTPRequestFailed, \ - "HTTP request failed with status %s, response %s" % (response.status, r) - -class requestHandler(BaseHTTPServer.BaseHTTPRequestHandler): - """Derived type to supply POST handler and override logging.""" - - rpki_handlers = None # Subclass must bind - - def rpki_find_handler(self): - """Helper method to search self.rpki_handlers.""" - for s,h in self.rpki_handlers: - if self.path.startswith(s): - return h - return None - - def do_POST(self): - """POST handler.""" - try: - handler = self.rpki_find_handler() - if self.headers["Content-Type"] != rpki_content_type: - rcode, rtext = 415, "Received Content-Type %s, expected %s" \ - % (self.headers["Content-Type"], rpki_content_type) - elif handler is None: - rcode, rtext = 404, "No handler found for URL " + self.path - else: - rcode, rtext = handler(query = self.rfile.read(int(self.headers["Content-Length"])), - path = self.path) - except Exception, edata: - rpki.log.error(traceback.format_exc()) - rcode, rtext = 500, "Unhandled exception %s" % edata - self.send_response(rcode) - self.send_header("Content-Type", rpki_content_type) - self.end_headers() - self.wfile.write(rtext) - - def log_message(self, format, *args): - """Redirect HTTP server logging into our own logging system.""" - if args: - rpki.log.info(format % args) - else: - rpki.log.info(format) - -class httpsServer(tlslite.api.TLSSocketServerMixIn, BaseHTTPServer.HTTPServer): - """Derived type to handle TLS aspects of HTTPS.""" - - rpki_sessionCache = None - rpki_server_key = None - rpki_server_cert = None - rpki_checker = None - - def handshake(self, tlsConnection): - """TLS handshake handler.""" - assert self.rpki_server_cert is not None - assert self.rpki_server_key is not None - assert self.rpki_sessionCache is not None - - try: - # - # We could add a "settings = foo" argument to the following call - # to pass in a tlslite.HandshakeSettings object that would let - # us insist on, eg, particular SSL/TLS versions. - # - tlsConnection.handshakeServer(certChain = self.rpki_server_cert, - privateKey = self.rpki_server_key, - sessionCache = self.rpki_sessionCache, - checker = self.rpki_checker, - reqCert = True) - tlsConnection.ignoreAbruptClose = True - return True - except (tlslite.api.TLSError, rpki.exceptions.TLSValidationError), error: - rpki.log.warn("TLS handshake failure: " + str(error)) - return False - -def server(handlers, server_key, server_cert, port = 4433, host ="", client_ta = None, dynamic_https_trust_anchor = None, catch_signals = (signal.SIGINT, signal.SIGTERM)): - """Run an HTTPS server and wait (forever) for connections.""" - - if not isinstance(handlers, (tuple, list)): - handlers = (("/", handlers),) - - class boundRequestHandler(requestHandler): - rpki_handlers = handlers - - httpd = httpsServer((host, port), boundRequestHandler) - - httpd.rpki_server_key = server_key.get_tlslite() - httpd.rpki_server_cert = tlslite_certChain(server_cert) - httpd.rpki_sessionCache = tlslite.api.SessionCache() - httpd.rpki_checker = Checker(trust_anchor = client_ta, dynamic_https_trust_anchor = dynamic_https_trust_anchor) - - try: - def raiseServerShuttingDown(signum, frame): - raise rpki.exceptions.ServerShuttingDown - old_signal_handlers = tuple((sig, signal.signal(sig, raiseServerShuttingDown)) for sig in catch_signals) - httpd.serve_forever() - except rpki.exceptions.ServerShuttingDown: - pass - finally: - for sig,handler in old_signal_handlers: - signal.signal(sig, handler) diff --git a/rpkid.stable/rpki/ipaddrs.py b/rpkid.stable/rpki/ipaddrs.py deleted file mode 100644 index db6a5891..00000000 --- a/rpkid.stable/rpki/ipaddrs.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Classes to represent IP addresses. - -Given some of the other operations we need to perform on them, it's -most convenient to represent IP addresses as Python "long" values. -The classes in this module just wrap suitable read/write syntax around -the underlying "long" type. - -These classes also supply a "bits" attribute for use by other code -built on these classes; for the most part, IPv6 addresses really are -just IPv4 addresses with more bits, so we supply the number of bits -once, here, thus avoiding a lot of duplicate code elsewhere. - -$Id$ - - -Copyright (C) 2009 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. - - -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. - -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. -""" - -import socket, struct - -class v4addr(long): - """IPv4 address. - - Derived from long, but supports IPv4 print syntax. - """ - - bits = 32 - - def __new__(cls, x): - """Construct a v4addr object.""" - if isinstance(x, str): - return cls.from_bytes(socket.inet_pton(socket.AF_INET, ".".join(str(int(i)) for i in x.split(".")))) - else: - return long.__new__(cls, x) - - def to_bytes(self): - """Convert a v4addr object to a raw byte string.""" - return struct.pack("!I", long(self)) - - @classmethod - def from_bytes(cls, x): - """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): - """IPv6 address. - - Derived from long, but supports IPv6 print syntax. - """ - - bits = 128 - - def __new__(cls, x): - """Construct a v6addr object.""" - if isinstance(x, str): - return cls.from_bytes(socket.inet_pton(socket.AF_INET6, x)) - else: - return long.__new__(cls, x) - - def to_bytes(self): - """Convert a v6addr object to a raw byte string.""" - return struct.pack("!QQ", long(self) >> 64, long(self) & 0xFFFFFFFFFFFFFFFF) - - @classmethod - def from_bytes(cls, x): - """Convert from a raw byte string to a v6addr object.""" - x = struct.unpack("!QQ", x) - return cls((x[0] << 64) | x[1]) - - def __str__(self): - """Convert a v6addr object to string format.""" - return socket.inet_ntop(socket.AF_INET6, self.to_bytes()) diff --git a/rpkid.stable/rpki/left_right.py b/rpkid.stable/rpki/left_right.py deleted file mode 100644 index 82bf93f4..00000000 --- a/rpkid.stable/rpki/left_right.py +++ /dev/null @@ -1,833 +0,0 @@ -"""RPKI "left-right" protocol. - -$Id$ - -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. -""" - -import base64, lxml.etree, time, traceback, os -import rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.xml_utils -import rpki.https, rpki.up_down, rpki.relaxng, rpki.sundial, rpki.log, rpki.roa -import rpki.publication - -# Enforce strict checking of XML "sender" field in up-down protocol -enforce_strict_up_down_xml_sender = False - -class left_right_namespace(object): - """XML namespace parameters for left-right protocol.""" - - xmlns = "http://www.hactrn.net/uris/rpki/left-right-spec/" - nsmap = { None : xmlns } - -class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistant, left_right_namespace): - """Virtual class for top-level left-right protocol data elements.""" - - def self(this): - """Fetch self object to which this object links.""" - return self_elt.sql_fetch(this.gctx, this.self_id) - - def bsc(self): - """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 self_id when cloning.""" - r_pdu.self_id = self.self_id - - def serve_fetch_one(self): - """Find the object on which a get, set, or destroy method should - operate. - """ - where = self.sql_template.index + " = %s AND self_id = %s" - args = (getattr(self, self.sql_template.index), self.self_id) - r = self.sql_fetch_where1(self.gctx, where, args) - if r is None: - raise rpki.exceptions.NotFound, "Lookup failed where " + (where % args) - return r - - def serve_fetch_all(self): - """Find the objects on which a list method should operate.""" - return self.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - 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) - -class self_elt(data_elt): - """<self/> element.""" - - element_name = "self" - attributes = ("action", "tag", "self_id", "crl_interval", "regen_margin") - elements = ("bpki_cert", "bpki_glue") - booleans = ("rekey", "reissue", "revoke", "run_now", "publish_world_now") - - sql_template = rpki.sql.template("self", "self_id", "use_hsm", "crl_interval", "regen_margin", - ("bpki_cert", rpki.x509.X509), ("bpki_glue", rpki.x509.X509)) - - self_id = None - use_hsm = False - crl_interval = None - regen_margin = None - bpki_cert = None - bpki_glue = None - - def bscs(self): - """Fetch all BSC objects that link to this self object.""" - return bsc_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - def repositories(self): - """Fetch all repository objects that link to this self object.""" - return repository_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - def parents(self): - """Fetch all parent objects that link to this self object.""" - return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - def children(self): - """Fetch all child objects that link to this self object.""" - return child_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - def route_origins(self): - """Fetch all route_origin objects that link to this self object.""" - return route_origin_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - def serve_post_save_hook(self, q_pdu, r_pdu): - """Extra server actions for self_elt.""" - rpki.log.trace() - if q_pdu.rekey: - self.serve_rekey() - if q_pdu.revoke: - self.serve_revoke() - self.unimplemented_control("reissue", "run_now", "publish_world_now") - - def serve_rekey(self): - """Handle a left-right rekey action for this self.""" - rpki.log.trace() - for parent in self.parents(): - parent.serve_rekey() - - def serve_revoke(self): - """Handle a left-right revoke action for this self.""" - rpki.log.trace() - for parent in self.parents(): - parent.serve_revoke() - - def serve_fetch_one(self): - """Find the self object upon which a get, set, or destroy action - should operate. - """ - r = self.sql_fetch(self.gctx, self.self_id) - if r is None: - raise rpki.exceptions.NotFound - return r - - def serve_fetch_all(self): - """Find the self objects upon which a list action should operate. - 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 client_poll(self): - """Run the regular client poll cycle with each of this self's parents in turn.""" - - rpki.log.trace() - - for parent in self.parents(): - - # This will need a callback when we go event-driven - r_msg = rpki.up_down.list_pdu.query(parent) - - ca_map = dict((ca.parent_resource_class, ca) for ca in parent.cas()) - for rc in r_msg.payload.classes: - if rc.class_name in ca_map: - ca = ca_map[rc.class_name] - del ca_map[rc.class_name] - ca.check_for_updates(parent, rc) - else: - rpki.rpki_engine.ca_obj.create(parent, rc) - for ca in ca_map.values(): - ca.delete(parent) # CA not listed by parent - self.gctx.sql.sweep() - - def update_children(self): - """Check for updated IRDB data for all of this self's children and - issue new certs as necessary. Must handle changes both in - resources and in expiration date. - """ - - rpki.log.trace() - - now = rpki.sundial.now() - - rsn = now + rpki.sundial.timedelta(seconds = self.regen_margin) - - for child in self.children(): - child_certs = child.child_certs() - if not child_certs: - continue - - # This will require a callback when we go event-driven - irdb_resources = self.gctx.irdb_query(child.self_id, child.child_id) - - for child_cert in child_certs: - ca_detail = child_cert.ca_detail() - if ca_detail.state != "active": - continue - old_resources = child_cert.cert.get_3779resources() - new_resources = irdb_resources.intersection(old_resources) - if old_resources != new_resources or (old_resources.valid_until < rsn and irdb_resources.valid_until > now): - rpki.log.debug("Need to reissue child certificate SKI %s" % child_cert.cert.gSKI()) - child_cert.reissue( - ca_detail = ca_detail, - resources = new_resources) - elif old_resources.valid_until < now: - rpki.log.debug("Child certificate SKI %s has expired: cert.valid_until %s, irdb.valid_until %s" - % (child_cert.cert.gSKI(), old_resources.valid_until, irdb_resources.valid_until)) - ca = ca_detail.ca() - parent = ca.parent() - repository = parent.repository() - child_cert.sql_delete() - ca_detail.generate_manifest() - repository.withdraw(child_cert.cert, child_cert.uri(ca)) - - def regenerate_crls_and_manifests(self): - """Generate new CRLs and manifests as necessary for all of this - self's CAs. Extracting nextUpdate from a manifest is hard at the - moment due to implementation silliness, so for now we generate a - new manifest whenever we generate a new CRL - - This method also cleans up tombstones left behind by revoked - ca_detail objects, since we're walking through the relevant - portions of the database anyway. - """ - - rpki.log.trace() - - now = rpki.sundial.now() - for parent in self.parents(): - repository = parent.repository() - for ca in parent.cas(): - for ca_detail in ca.fetch_revoked(): - if now > ca_detail.latest_crl.getNextUpdate(): - ca_detail.delete(ca, repository) - ca_detail = ca.fetch_active() - if ca_detail is not None and now > ca_detail.latest_crl.getNextUpdate(): - ca_detail.generate_crl() - ca_detail.generate_manifest() - - def update_roas(self): - """Generate or update ROAs for this self's route_origin objects.""" - - for route_origin in self.route_origins(): - route_origin.update_roa() - -class bsc_elt(data_elt): - """<bsc/> (Business Signing Context) element.""" - - element_name = "bsc" - attributes = ("action", "tag", "self_id", "bsc_id", "key_type", "hash_alg", "key_length") - elements = ("signing_cert", "signing_cert_crl", "pkcs10_request") - booleans = ("generate_keypair",) - - sql_template = rpki.sql.template("bsc", "bsc_id", "self_id", "hash_alg", - ("private_key_id", rpki.x509.RSA), - ("pkcs10_request", rpki.x509.PKCS10), - ("signing_cert", rpki.x509.X509), - ("signing_cert_crl", rpki.x509.CRL)) - - private_key_id = None - pkcs10_request = None - signing_cert = None - signing_cert_crl = None - - def repositories(self): - """Fetch all repository objects that link to this BSC object.""" - return repository_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,)) - - def parents(self): - """Fetch all parent objects that link to this BSC object.""" - return parent_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,)) - - def children(self): - """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): - """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) - self.pkcs10_request = rpki.x509.PKCS10.create(self.private_key_id) - r_pdu.pkcs10_request = self.pkcs10_request - -class parent_elt(data_elt): - """<parent/> element.""" - - element_name = "parent" - attributes = ("action", "tag", "self_id", "parent_id", "bsc_id", "repository_id", - "peer_contact_uri", "sia_base", "sender_name", "recipient_name") - elements = ("bpki_cms_cert", "bpki_cms_glue", "bpki_https_cert", "bpki_https_glue") - booleans = ("rekey", "reissue", "revoke") - - sql_template = rpki.sql.template("parent", "parent_id", "self_id", "bsc_id", "repository_id", - ("bpki_cms_cert", rpki.x509.X509), ("bpki_cms_glue", rpki.x509.X509), - ("bpki_https_cert", rpki.x509.X509), ("bpki_https_glue", rpki.x509.X509), - "peer_contact_uri", "sia_base", "sender_name", "recipient_name") - - bpki_cms_cert = None - bpki_cms_glue = None - bpki_https_cert = None - bpki_https_glue = None - - def repository(self): - """Fetch repository object to which this parent object links.""" - return repository_elt.sql_fetch(self.gctx, self.repository_id) - - def cas(self): - """Fetch all CA objects that link to this parent object.""" - return rpki.rpki_engine.ca_obj.sql_fetch_where(self.gctx, "parent_id = %s", (self.parent_id,)) - - def serve_post_save_hook(self, q_pdu, r_pdu): - """Extra server actions for parent_elt.""" - if q_pdu.rekey: - self.serve_rekey() - if q_pdu.revoke: - self.serve_revoke() - self.unimplemented_control("reissue") - - def serve_rekey(self): - """Handle a left-right rekey action for this parent.""" - for ca in self.cas(): - ca.rekey() - - def serve_revoke(self): - """Handle a left-right revoke action for this parent.""" - for ca in self.cas(): - ca.revoke() - - def query_up_down(self, q_pdu): - """Client code for sending one up-down query PDU to this parent. - - I haven't figured out yet whether this method should do something - clever like dispatching via a method in the response PDU payload, - or just hand back the whole response to the caller. In the long - run this will have to become event driven with a context object - that has methods of its own, but as this method is common code for - several different queries and I don't yet know what the response - processing looks like, it's too soon to tell what will make sense. - - For now, keep this dead simple lock step, rewrite it later. - """ - - rpki.log.trace() - - bsc = self.bsc() - if bsc is None: - raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id - - q_msg = rpki.up_down.message_pdu.make_query( - payload = q_pdu, - sender = self.sender_name, - recipient = self.recipient_name) - - q_cms = rpki.up_down.cms_msg.wrap(q_msg, bsc.private_key_id, - bsc.signing_cert, - bsc.signing_cert_crl) - - der = rpki.https.client(server_ta = (self.gctx.bpki_ta, - self.self().bpki_cert, self.self().bpki_glue, - self.bpki_https_cert, self.bpki_https_glue), - client_key = bsc.private_key_id, - client_cert = bsc.signing_cert, - msg = q_cms, - url = self.peer_contact_uri) - - r_msg = rpki.up_down.cms_msg.unwrap(der, (self.gctx.bpki_ta, - self.self().bpki_cert, self.self().bpki_glue, - self.bpki_cms_cert, self.bpki_cms_glue)) - - r_msg.payload.check_response() - return r_msg - - -class child_elt(data_elt): - """<child/> element.""" - - element_name = "child" - attributes = ("action", "tag", "self_id", "child_id", "bsc_id") - elements = ("bpki_cert", "bpki_glue") - booleans = ("reissue", ) - - sql_template = rpki.sql.template("child", "child_id", "self_id", "bsc_id", - ("bpki_cert", rpki.x509.X509), - ("bpki_glue", rpki.x509.X509)) - - bpki_cert = None - bpki_glue = None - clear_https_ta_cache = False - - def child_certs(self, ca_detail = None, ski = None, unique = False): - """Fetch all child_cert objects that link to this child object.""" - return rpki.rpki_engine.child_cert_obj.fetch(self.gctx, self, ca_detail, ski, unique) - - def parents(self): - """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 ca_from_class_name(self, class_name): - """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.rpki_engine.ca_obj.sql_fetch(self.gctx, long(class_name)) - if ca is None: - raise rpki.exceptions.ClassNameUnknown, "Unknown class name %s" % class_name - parent = ca.parent() - if self.self_id != parent.self_id: - raise rpki.exceptions.ClassNameMismatch, "Class name mismatch: child.self_id = %d, parent.self_id = %d" % (self.self_id, parent.self_id) - return ca - - def serve_post_save_hook(self, q_pdu, r_pdu): - """Extra server actions for child_elt.""" - self.unimplemented_control("reissue") - if self.clear_https_ta_cache: - self.gctx.clear_https_ta_cache() - self.clear_https_ta_cache = False - - def endElement(self, stack, name, text): - """Handle subelements of <child/> element. These require special - handling because modifying them invalidates the HTTPS trust anchor - cache. - """ - rpki.xml_utils.data_elt.endElement(self, stack, name, text) - if name in self.elements: - self.clear_https_ta_cache = True - - def serve_up_down(self, query): - """Outer layer of server handling for one up-down PDU from this child.""" - - rpki.log.trace() - - bsc = self.bsc() - if bsc is None: - raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id - q_msg = rpki.up_down.cms_msg.unwrap(query, (self.gctx.bpki_ta, - self.self().bpki_cert, self.self().bpki_glue, - self.bpki_cert, self.bpki_glue)) - q_msg.payload.gctx = self.gctx - if enforce_strict_up_down_xml_sender and q_msg.sender != str(self.child_id): - raise rpki.exceptions.BadSender, "Unexpected XML sender %s" % q_msg.sender - try: - r_msg = q_msg.serve_top_level(self) - except Exception, data: - rpki.log.error(traceback.format_exc()) - r_msg = q_msg.serve_error(data) - # - # Exceptions from this point on are problematic, as we have no - # sane way of reporting errors in the error reporting mechanism. - # May require refactoring, ignore the issue for now. - # - r_cms = rpki.up_down.cms_msg.wrap(r_msg, bsc.private_key_id, - bsc.signing_cert, bsc.signing_cert_crl) - return r_cms - -class repository_elt(data_elt): - """<repository/> element.""" - - element_name = "repository" - attributes = ("action", "tag", "self_id", "repository_id", "bsc_id", "peer_contact_uri") - elements = ("bpki_cms_cert", "bpki_cms_glue", "bpki_https_cert", "bpki_https_glue") - - sql_template = rpki.sql.template("repository", "repository_id", "self_id", "bsc_id", "peer_contact_uri", - ("bpki_cms_cert", rpki.x509.X509), ("bpki_cms_glue", rpki.x509.X509), - ("bpki_https_cert", rpki.x509.X509), ("bpki_https_glue", rpki.x509.X509)) - - bpki_cms_cert = None - bpki_cms_glue = None - bpki_https_cert = None - bpki_https_glue = None - - use_pubd = True - - def parents(self): - """Fetch all parent objects that link to this repository object.""" - return parent_elt.sql_fetch_where(self.gctx, "repository_id = %s", (self.repository_id,)) - - @staticmethod - def uri_to_filename(base, uri): - """Convert a URI to a filename. [TEMPORARY]""" - if not uri.startswith("rsync://"): - raise rpki.exceptions.BadURISyntax - filename = base + uri[len("rsync://"):] - if filename.find("//") >= 0 or filename.find("/../") >= 0 or filename.endswith("/.."): - raise rpki.exceptions.BadURISyntax - return filename - - @classmethod - def object_write(cls, base, uri, obj): - """Write an object to disk. [TEMPORARY]""" - rpki.log.trace() - filename = cls.uri_to_filename(base, uri) - dirname = os.path.dirname(filename) - if not os.path.isdir(dirname): - os.makedirs(dirname) - f = open(filename, "wb") - f.write(obj.get_DER()) - f.close() - - @classmethod - def object_delete(cls, base, uri): - """Delete an object from disk. [TEMPORARY]""" - rpki.log.trace() - os.remove(cls.uri_to_filename(base, uri)) - - def call_pubd(self, *pdus): - """Send a message to publication daemon and return the response.""" - rpki.log.trace() - bsc = self.bsc() - q_msg = rpki.publication.msg(pdus) - q_msg.type = "query" - q_cms = rpki.publication.cms_msg.wrap(q_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl) - bpki_ta_path = (self.gctx.bpki_ta, self.self().bpki_cert, self.self().bpki_glue, self.bpki_https_cert, self.bpki_https_glue) - r_cms = rpki.https.client( - client_key = bsc.private_key_id, - client_cert = bsc.signing_cert, - server_ta = bpki_ta_path, - url = self.peer_contact_uri, - msg = q_cms) - r_msg = rpki.publication.cms_msg.unwrap(r_cms, bpki_ta_path) - assert len(r_msg) == 1 - return r_msg[0] - - def publish(self, obj, uri): - """Placeholder for publication operation. [TEMPORARY]""" - rpki.log.trace() - rpki.log.info("Publishing %s as %s" % (repr(obj), repr(uri))) - if self.use_pubd: - self.call_pubd(rpki.publication.obj2elt[type(obj)].make_pdu(action = "publish", uri = uri, payload = obj)) - else: - self.object_write(self.gctx.publication_kludge_base, uri, obj) - - def withdraw(self, obj, uri): - """Placeholder for publication withdrawal operation. [TEMPORARY]""" - rpki.log.trace() - rpki.log.info("Withdrawing %s from at %s" % (repr(obj), repr(uri))) - if self.use_pubd: - self.call_pubd(rpki.publication.obj2elt[type(obj)].make_pdu(action = "withdraw", uri = uri)) - else: - self.object_delete(self.gctx.publication_kludge_base, uri) - -class route_origin_elt(data_elt): - """<route_origin/> element.""" - - element_name = "route_origin" - attributes = ("action", "tag", "self_id", "route_origin_id", "as_number", "ipv4", "ipv6") - booleans = ("suppress_publication",) - - sql_template = rpki.sql.template("route_origin", "route_origin_id", "ca_detail_id", - "self_id", "as_number", - ("roa", rpki.x509.ROA), - ("cert", rpki.x509.X509)) - - ca_detail_id = None - cert = None - roa = None - - ## @var publish_ee_separately - # Whether to publish the ROA EE certificate separately from the ROA. - publish_ee_separately = False - - def sql_fetch_hook(self): - """Extra SQL fetch actions for route_origin_elt -- handle prefix list.""" - self.ipv4 = rpki.resource_set.roa_prefix_set_ipv4.from_sql( - self.gctx.sql, - """ - SELECT address, prefixlen, max_prefixlen FROM route_origin_prefix - WHERE route_origin_id = %s AND address NOT LIKE '%:%' - """, (self.route_origin_id,)) - self.ipv6 = rpki.resource_set.roa_prefix_set_ipv6.from_sql( - self.gctx.sql, - """ - SELECT address, prefixlen, max_prefixlen FROM route_origin_prefix - WHERE route_origin_id = %s AND address LIKE '%:%' - """, (self.route_origin_id,)) - - def sql_insert_hook(self): - """Extra SQL insert actions for route_origin_elt -- handle address ranges.""" - if self.ipv4 or self.ipv6: - self.gctx.sql.executemany(""" - INSERT route_origin_prefix (route_origin_id, address, prefixlen, max_prefixlen) - VALUES (%s, %s, %s, %s)""", - ((self.route_origin_id, x.address, x.prefixlen, x.max_prefixlen) - for x in (self.ipv4 or []) + (self.ipv6 or []))) - - def sql_delete_hook(self): - """Extra SQL delete actions for route_origin_elt -- handle address ranges.""" - self.gctx.sql.execute("DELETE FROM route_origin_prefix WHERE route_origin_id = %s", (self.route_origin_id,)) - - def ca_detail(self): - """Fetch all ca_detail objects that link to this route_origin object.""" - return rpki.rpki_engine.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id) - - def serve_post_save_hook(self, q_pdu, r_pdu): - """Extra server actions for route_origin_elt.""" - self.unimplemented_control("suppress_publication") - - def startElement(self, stack, name, attrs): - """Handle <route_origin/> element. This requires special - processing due to the data types of some of the attributes. - """ - assert name == "route_origin", "Unexpected name %s, stack %s" % (name, stack) - self.read_attrs(attrs) - if self.as_number is not None: - self.as_number = long(self.as_number) - if self.ipv4 is not None: - self.ipv4 = rpki.resource_set.roa_prefix_set_ipv4(self.ipv4) - if self.ipv6 is not None: - self.ipv6 = rpki.resource_set.roa_prefix_set_ipv6(self.ipv6) - - def update_roa(self): - """Bring this route_origin's ROA up to date if necesssary.""" - - if self.roa is None: - return self.generate_roa() - - ca_detail = self.ca_detail() - - if ca_detail is None or ca_detail.state != "active": - return self.regenerate_roa() - - regen_margin = rpki.sundial.timedelta(seconds = self.self().regen_margin) - - if rpki.sundial.now() + regen_margin > self.cert.getNotAfter(): - return self.regenerate_roa() - - ca_resources = ca_detail.latest_ca_cert.get_3779resources() - ee_resources = self.cert.get_3779resources() - - if ee_resources.oversized(ca_resources): - return self.regenerate_roa() - - v4 = self.ipv4.to_resource_set() if self.ipv4 is not None else rpki.resource_set.resource_set_ipv4() - v6 = self.ipv6.to_resource_set() if self.ipv6 is not None else rpki.resource_set.resource_set_ipv6() - - if ee_resources.v4 != v4 or ee_resources.v6 != v6: - return self.regenerate_roa() - - def generate_roa(self): - """Generate a ROA based on this <route_origin/> object. - - At present this does not support ROAs with multiple signatures - (neither does the current CMS code). - - At present we have no way of performing a direct lookup from a - desired set of resources to a covering certificate, so we have to - search. This could be quite slow if we have a lot of active - ca_detail objects. Punt on the issue for now, revisit if - profiling shows this as a hotspot. - - Once we have the right covering certificate, we generate the ROA - payload, generate a new EE certificate, use the EE certificate to - sign the ROA payload, publish the result, then throw away the - private key for the EE cert, all per the ROA specification. This - implies that generating a lot of ROAs will tend to thrash - /dev/random, but there is not much we can do about that. - """ - - if self.ipv4 is None and self.ipv6 is None: - rpki.log.warn("Can't generate ROA for empty prefix list") - return - - # Ugly and expensive search for covering ca_detail, there has to - # be a better way, but it would require the ability to test for - # resource subsets in SQL. - - v4 = self.ipv4.to_resource_set() if self.ipv4 is not None else rpki.resource_set.resource_set_ipv4() - v6 = self.ipv6.to_resource_set() if self.ipv6 is not None else rpki.resource_set.resource_set_ipv6() - - ca_detail = self.ca_detail() - if ca_detail is None or ca_detail.state != "active": - ca_detail = None - for parent in self.self().parents(): - for ca in parent.cas(): - ca_detail = ca.fetch_active() - if ca_detail is not None: - resources = ca_detail.latest_ca_cert.get_3779resources() - if v4.issubset(resources.v4) and v6.issubset(resources.v6): - break - ca_detail = None - if ca_detail is not None: - break - - if ca_detail is None: - rpki.log.warn("generate_roa() could not find a certificate covering %s %s" % (v4, v6)) - return - - ca = ca_detail.ca() - - resources = rpki.resource_set.resource_bag(v4 = v4, v6 = v6) - - keypair = rpki.x509.RSA.generate() - - sia = ((rpki.oids.name2oid["id-ad-signedObject"], ("uri", self.roa_uri(ca, keypair))),) - - self.cert = ca_detail.issue_ee(ca, resources, keypair.get_RSApublic(), sia = sia) - self.roa = rpki.x509.ROA.build(self.as_number, self.ipv4, self.ipv6, keypair, (self.cert,)) - self.ca_detail_id = ca_detail.ca_detail_id - self.sql_store() - - repository = ca.parent().repository() - repository.publish(self.roa, self.roa_uri(ca)) - if self.publish_ee_separately: - repository.publish(self.cert, self.ee_uri(ca)) - ca_detail.generate_manifest() - - def withdraw_roa(self, regenerate = False): - """Withdraw ROA associated with this route_origin. - - In order to preserve make-before-break properties without - duplicating code, this method also handles generating a - replacement ROA when requested. - """ - - ca_detail = self.ca_detail() - ca = ca_detail.ca() - repository = ca.parent().repository() - cert = self.cert - roa = self.roa - roa_uri = self.roa_uri(ca) - ee_uri = self.ee_uri(ca) - - if ca_detail.state != 'active': - self.ca_detail_id = None - if regenerate: - self.generate_roa() - - rpki.log.debug("Withdrawing ROA and revoking its EE cert") - rpki.rpki_engine.revoked_cert_obj.revoke(cert = cert, ca_detail = ca_detail) - repository.withdraw(roa, roa_uri) - if self.publish_ee_separately: - repository.withdraw(cert, ee_uri) - self.gctx.sql.sweep() - ca_detail.generate_crl() - ca_detail.generate_manifest() - - def regenerate_roa(self): - """Reissue ROA associated with this route_origin.""" - if self.ca_detail() is None: - self.generate_roa() - else: - self.withdraw_roa(regenerate = True) - - def roa_uri(self, ca, key = None): - """Return the publication URI for this route_origin's ROA.""" - return ca.sia_uri + self.roa_uri_tail(key) - - def roa_uri_tail(self, key = None): - """Return the tail (filename portion) of the publication URI for this route_origin's ROA.""" - return (key or self.cert).gSKI() + ".roa" - - def ee_uri_tail(self): - """Return the tail (filename) portion of the URI for this route_origin's ROA's EE certificate.""" - return self.cert.gSKI() + ".cer" - - def ee_uri(self, ca): - """Return the publication URI for this route_origin's ROA's EE certificate.""" - return ca.sia_uri + self.ee_uri_tail() - -class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace): - """<list_resources/> element.""" - - element_name = "list_resources" - attributes = ("self_id", "tag", "child_id", "valid_until", "asn", "ipv4", "ipv6", "subject_name") - valid_until = None - - def startElement(self, stack, name, attrs): - """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): - self.valid_until = rpki.sundial.datetime.fromXMLtime(self.valid_until) - if self.asn is not None: - self.asn = rpki.resource_set.resource_set_as(self.asn) - if self.ipv4 is not None: - self.ipv4 = rpki.resource_set.resource_set_ipv4(self.ipv4) - if self.ipv6 is not None: - self.ipv6 = rpki.resource_set.resource_set_ipv6(self.ipv6) - - def toXML(self): - """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()) - return elt - -class report_error_elt(rpki.xml_utils.base_elt, left_right_namespace): - """<report_error/> element.""" - - element_name = "report_error" - attributes = ("tag", "self_id", "error_code") - - @classmethod - def from_exception(cls, exc, self_id = None): - """Generate a <report_error/> element from an exception.""" - self = cls() - self.self_id = self_id - self.error_code = exc.__class__.__name__ - return self - -class msg(rpki.xml_utils.msg, left_right_namespace): - """Left-right PDU.""" - - ## @var version - # Protocol version - version = 1 - - ## @var pdus - # Dispatch table of PDUs for this protocol. - pdus = dict((x.element_name, x) - for x in (self_elt, child_elt, parent_elt, bsc_elt, repository_elt, - route_origin_elt, list_resources_elt, report_error_elt)) - - def serve_top_level(self, gctx): - """Serve one msg PDU.""" - r_msg = self.__class__() - r_msg.type = "reply" - for q_pdu in self: - q_pdu.gctx = gctx - q_pdu.serve_dispatch(r_msg) - return r_msg - -class sax_handler(rpki.xml_utils.sax_handler): - """SAX handler for Left-Right protocol.""" - - pdu = msg - name = "msg" - version = "1" - -class cms_msg(rpki.x509.XML_CMS_object): - """Class to hold a CMS-signed left-right PDU.""" - - encoding = "us-ascii" - schema = rpki.relaxng.left_right - saxify = sax_handler.saxify diff --git a/rpkid.stable/rpki/log.py b/rpkid.stable/rpki/log.py deleted file mode 100644 index efaba10c..00000000 --- a/rpkid.stable/rpki/log.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Logging facilities for RPKI libraries. - -$Id$ - -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. -""" - -import syslog, traceback - -## @var enable_trace -# Whether call tracing is enabled. - -enable_trace = False - -def init(ident = "rpki", flags = syslog.LOG_PID | syslog.LOG_PERROR, facility = syslog.LOG_DAEMON): - """Initialize logging system.""" - - return syslog.openlog(ident, flags, facility) - -def set_trace(trace): - """Enable or disable call tracing.""" - - global enable_trace - enable_trace = trace - -class logger(object): - """Closure for logging.""" - - def __init__(self, priority): - self.priority = priority - - def __call__(self, message): - return syslog.syslog(self.priority, message) - -error = logger(syslog.LOG_ERR) -warn = logger(syslog.LOG_WARNING) -note = logger(syslog.LOG_NOTICE) -info = logger(syslog.LOG_INFO) -debug = logger(syslog.LOG_DEBUG) - -def trace(): - """Execution trace -- where are we now, and whence came we here?""" - - if enable_trace: - bt = traceback.extract_stack(limit = 3) - return debug("[%s() at %s:%d from %s:%d]" % (bt[1][2], bt[1][0], bt[1][1], bt[0][0], bt[0][1])) diff --git a/rpkid.stable/rpki/manifest.py b/rpkid.stable/rpki/manifest.py deleted file mode 100644 index 97b1cc71..00000000 --- a/rpkid.stable/rpki/manifest.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Signed manifests. This is just the ASN.1 encoder, the rest is in -rpki.x509 with the rest of the DER_object code. - -Note that rpki.x509.SignedManifest implements the signed manifest; -the structures here are just the payload of the CMS eContent field. - -$Id$ - -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. -""" - -from POW._der import * - -class FileAndHash(Sequence): - def __init__(self, optional=0, default=''): - self.file = IA5String() - self.hash = AltBitString() - contents = [ self.file, self.hash ] - Sequence.__init__(self, contents, optional, default) - -class FilesAndHashes(SequenceOf): - def __init__(self, optional=0, default=''): - SequenceOf.__init__(self, FileAndHash, optional, default) - -class Manifest(Sequence): - def __init__(self, optional=0, default=''): - self.version = Integer() - self.explicitVersion = Explicit(CLASS_CONTEXT, FORM_CONSTRUCTED, 0, self.version, 0, 'oAMCAQA=') - self.manifestNumber = Integer() - self.thisUpdate = GeneralizedTime() - self.nextUpdate = GeneralizedTime() - self.fileHashAlg = Oid() - self.fileList = FilesAndHashes() - - contents = [ self.explicitVersion, - self.manifestNumber, - self.thisUpdate, - self.nextUpdate, - self.fileHashAlg, - self.fileList ] - Sequence.__init__(self, contents, optional, default) diff --git a/rpkid.stable/rpki/oids.py b/rpkid.stable/rpki/oids.py deleted file mode 100644 index 5824ad17..00000000 --- a/rpkid.stable/rpki/oids.py +++ /dev/null @@ -1,57 +0,0 @@ -"""OID database. - -$Id$ - -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. -""" - -## @var oid2name -# Mapping table of OIDs to conventional string names. - -oid2name = { - (1, 2, 840, 113549, 1, 1, 11) : "sha256WithRSAEncryption", - (1, 2, 840, 113549, 1, 1, 12) : "sha384WithRSAEncryption", - (1, 2, 840, 113549, 1, 1, 13) : "sha512WithRSAEncryption", - (1, 2, 840, 113549, 1, 7, 1) : "id-data", - (1, 2, 840, 113549, 1, 9, 16) : "id-smime", - (1, 2, 840, 113549, 1, 9, 16, 1) : "id-ct", - (1, 2, 840, 113549, 1, 9, 16, 1, 24) : "id-ct-routeOriginAttestation", - (1, 2, 840, 113549, 1, 9, 16, 1, 26) : "id-ct-rpkiManifest", - (1, 2, 840, 113549, 1, 9, 16, 1, 28) : "id-ct-xml", - (1, 3, 6, 1, 5, 5, 7, 1, 1) : "authorityInfoAccess", - (1, 3, 6, 1, 5, 5, 7, 1, 11) : "subjectInfoAccess", - (1, 3, 6, 1, 5, 5, 7, 1, 7) : "sbgp-ipAddrBlock", - (1, 3, 6, 1, 5, 5, 7, 1, 8) : "sbgp-autonomousSysNum", - (1, 3, 6, 1, 5, 5, 7, 14, 2) : "id-cp-ipAddr-asNumber", - (1, 3, 6, 1, 5, 5, 7, 48, 2) : "id-ad-caIssuers", - (1, 3, 6, 1, 5, 5, 7, 48, 5) : "id-ad-caRepository", - (1, 3, 6, 1, 5, 5, 7, 48, 9) : "id-ad-signedObjectRepository", - (1, 3, 6, 1, 5, 5, 7, 48, 10) : "id-ad-rpkiManifest", - (1, 3, 6, 1, 5, 5, 7, 48, 11) : "id-ad-signedObject", - (2, 16, 840, 1, 101, 3, 4, 2, 1) : "id-sha256", - (2, 5, 29, 14) : "subjectKeyIdentifier", - (2, 5, 29, 15) : "keyUsage", - (2, 5, 29, 19) : "basicConstraints", - (2, 5, 29, 20) : "cRLNumber", - (2, 5, 29, 31) : "cRLDistributionPoints", - (2, 5, 29, 32) : "certificatePolicies", - (2, 5, 29, 35) : "authorityKeyIdentifier", - (2, 5, 4, 3) : "commonName", -} - -## @var name2oid -# Mapping table of string names to OIDs - -name2oid = dict((v,k) for k,v in oid2name.items()) diff --git a/rpkid.stable/rpki/publication.py b/rpkid.stable/rpki/publication.py deleted file mode 100644 index fe52b631..00000000 --- a/rpkid.stable/rpki/publication.py +++ /dev/null @@ -1,282 +0,0 @@ -"""RPKI "publication" protocol. - -$Id$ - -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. -""" - -import base64, lxml.etree, time, traceback, os -import rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.xml_utils -import rpki.https, rpki.up_down, rpki.relaxng, rpki.sundial, rpki.log, rpki.roa - -class publication_namespace(object): - """XML namespace parameters for publication protocol.""" - - xmlns = "http://www.hactrn.net/uris/rpki/publication-spec/" - nsmap = { None : xmlns } - -class control_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistant, publication_namespace): - """Virtual class for control channel objects.""" - - def serve_dispatch(self, r_msg, client): - """Action dispatch handler. This needs special handling because - we need to make sure that this PDU arrived via the control channel. - """ - if client is not None: - raise rpki.exceptions.BadQuery, "Control query received on client channel" - rpki.xml_utils.data_elt.serve_dispatch(self, r_msg) - -class config_elt(control_elt): - """<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. - """ - - attributes = ("action", "tag") - element_name = "config" - elements = ("bpki_crl",) - - sql_template = rpki.sql.template("config", "config_id", ("bpki_crl", rpki.x509.CRL)) - - 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 - - @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): - """Handle a set action. This requires special handling because - config we don't support the create method. - """ - if self.sql_fetch(self.gctx, self.config_id) is None: - control_elt.serve_create(self, r_msg) - else: - control_elt.serve_set(self, r_msg) - - def serve_fetch_one(self): - """Find the config object on which a get or set method should - operate. - """ - r = self.sql_fetch(self.gctx, self.config_id) - if r is None: - raise rpki.exceptions.NotFound - return r - -class client_elt(control_elt): - """<client/> element.""" - - element_name = "client" - attributes = ("action", "tag", "client_id", "base_uri") - elements = ("bpki_cert", "bpki_glue") - - sql_template = rpki.sql.template("client", "client_id", "base_uri", ("bpki_cert", rpki.x509.X509), ("bpki_glue", rpki.x509.X509)) - - base_uri = None - bpki_cert = None - bpki_glue = None - - clear_https_ta_cache = False - - def endElement(self, stack, name, text): - """Handle subelements of <client/> element. These require special - handling because modifying them invalidates the HTTPS trust anchor - cache. - """ - control_elt.endElement(self, stack, name, text) - if name in self.elements: - self.clear_https_ta_cache = True - - def serve_post_save_hook(self, q_pdu, r_pdu): - """Extra server actions for client_elt.""" - if self.clear_https_ta_cache: - self.gctx.clear_https_ta_cache() - self.clear_https_ta_cache = False - - def serve_fetch_one(self): - """Find the client object on which a get, set, or destroy method - should operate. - """ - r = self.sql_fetch(self.gctx, self.client_id) - if r is None: - raise rpki.exceptions.NotFound - return r - - 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): - if not uri.startswith(self.base_uri): - raise rpki.exceptions.ForbiddenURI - -class publication_object_elt(rpki.xml_utils.base_elt, publication_namespace): - """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. - """ - - attributes = ("action", "tag", "client_id", "uri") - payload = None - - def endElement(self, stack, name, text): - """Handle a publishable element element.""" - assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) - if text: - self.payload = self.payload_type(Base64 = text) - stack.pop() - - def toXML(self): - """Generate XML element for publishable object.""" - elt = self.make_elt() - if self.payload: - elt.text = base64.b64encode(self.payload.get_DER()) - return elt - - def serve_dispatch(self, r_msg, client): - """Action dispatch handler.""" - if 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 - 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) - - def serve_publish(self): - """Publish an object.""" - rpki.log.info("Publishing %s as %s" % (repr(self.payload), repr(self.uri))) - filename = self.uri_to_filename() - dirname = os.path.dirname(filename) - if not os.path.isdir(dirname): - os.makedirs(dirname) - f = open(filename, "wb") - f.write(self.payload.get_DER()) - f.close() - - def serve_withdraw(self): - """Withdraw an object.""" - rpki.log.info("Withdrawing %s" % repr(self.uri)) - os.remove(self.uri_to_filename()) - - def uri_to_filename(self): - """Convert a URI to a local filename.""" - if not self.uri.startswith("rsync://"): - raise rpki.exceptions.BadURISyntax - filename = self.gctx.publication_base + self.uri[len("rsync://"):] - if filename.find("//") >= 0 or filename.find("/../") >= 0 or filename.endswith("/.."): - raise rpki.exceptions.BadURISyntax - return filename - -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): - """<manifest/> element.""" - - element_name = "manifest" - payload_type = rpki.x509.SignedManifest - -class roa_elt(publication_object_elt): - """<roa/> element.""" - - element_name = "roa" - payload_type = rpki.x509.ROA - -## @var obj2elt -# Map of data types to publication element wrapper types - -obj2elt = dict((e.payload_type, e) for e in (certificate_elt, crl_elt, manifest_elt, roa_elt)) - -class report_error_elt(rpki.xml_utils.base_elt, publication_namespace): - """<report_error/> element.""" - - element_name = "report_error" - attributes = ("tag", "error_code") - - @classmethod - def from_exception(cls, exc): - """Generate a <report_error/> element from an exception.""" - self = cls() - self.error_code = exc.__class__.__name__ - return self - -class msg(rpki.xml_utils.msg, publication_namespace): - """Publication PDU.""" - - ## @var version - # Protocol version - version = 1 - - ## @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, report_error_elt)) - - def serve_top_level(self, gctx, client): - """Serve one msg PDU.""" - if self.type != "query": - raise rpki.exceptions.BadQuery, "Message type is not query" - r_msg = self.__class__() - r_msg.type = "reply" - for q_pdu in self: - q_pdu.gctx = gctx - q_pdu.serve_dispatch(r_msg, client) - return r_msg - -class sax_handler(rpki.xml_utils.sax_handler): - """SAX handler for publication protocol.""" - - pdu = msg - name = "msg" - version = "1" - -class cms_msg(rpki.x509.XML_CMS_object): - """Class to hold a CMS-signed publication PDU.""" - - encoding = "us-ascii" - schema = rpki.relaxng.publication - saxify = sax_handler.saxify diff --git a/rpkid.stable/rpki/relaxng.py b/rpkid.stable/rpki/relaxng.py deleted file mode 100644 index c66d965a..00000000 --- a/rpkid.stable/rpki/relaxng.py +++ /dev/null @@ -1,1699 +0,0 @@ -# Automatically generated, do not edit. - -import lxml.etree - -## @var left_right -## Parsed RelaxNG left_right schema -left_right = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" encoding="UTF-8"?> -<!-- - $Id: left-right-schema.rnc 1835 2008-06-02 23:43:01Z sra $ - - RelaxNG Schema for RPKI left-right protocol. - - libxml2 (including xmllint) only groks the XML syntax of RelaxNG, so - run the compact syntax through trang to get XML syntax. ---> -<grammar ns="http://www.hactrn.net/uris/rpki/left-right-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> - <!-- 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" combine="choice"> - <ref name="self_query"/> - </define> - <define name="query_elt" combine="choice"> - <ref name="bsc_query"/> - </define> - <define name="query_elt" combine="choice"> - <ref name="parent_query"/> - </define> - <define name="query_elt" combine="choice"> - <ref name="child_query"/> - </define> - <define name="query_elt" combine="choice"> - <ref name="repository_query"/> - </define> - <define name="query_elt" combine="choice"> - <ref name="route_origin_query"/> - </define> - <define name="query_elt" combine="choice"> - <ref name="list_resources_query"/> - </define> - <!-- PDUs allowed in a reply --> - <define name="reply_elt" combine="choice"> - <ref name="self_reply"/> - </define> - <define name="reply_elt" combine="choice"> - <ref name="bsc_reply"/> - </define> - <define name="reply_elt" combine="choice"> - <ref name="parent_reply"/> - </define> - <define name="reply_elt" combine="choice"> - <ref name="child_reply"/> - </define> - <define name="reply_elt" combine="choice"> - <ref name="repository_reply"/> - </define> - <define name="reply_elt" combine="choice"> - <ref name="route_origin_reply"/> - </define> - <define name="reply_elt" combine="choice"> - <ref name="list_resources_reply"/> - </define> - <define name="reply_elt" combine="choice"> - <ref name="report_error_reply"/> - </define> - <!-- Tag attributes for bulk operations --> - <define name="tag"> - <optional> - <attribute name="tag"> - <data type="token"> - <param name="maxLength">1024</param> - </data> - </attribute> - </optional> - </define> - <!-- - Combinations of action and type attributes used in later definitions. - The same patterns repeat in most of the elements in this protocol. - --> - <define name="ctl_create"> - <attribute name="action"> - <value>create</value> - </attribute> - <ref name="tag"/> - </define> - <define name="ctl_set"> - <attribute name="action"> - <value>set</value> - </attribute> - <ref name="tag"/> - </define> - <define name="ctl_get"> - <attribute name="action"> - <value>get</value> - </attribute> - <ref name="tag"/> - </define> - <define name="ctl_list"> - <attribute name="action"> - <value>list</value> - </attribute> - <ref name="tag"/> - </define> - <define name="ctl_destroy"> - <attribute name="action"> - <value>destroy</value> - </attribute> - <ref name="tag"/> - </define> - <!-- Base64 encoded DER stuff --> - <define name="base64"> - <data type="base64Binary"> - <param name="maxLength">512000</param> - </data> - </define> - <!-- Base definition for all fields that are really just SQL primary indices --> - <define name="sql_id"> - <data type="nonNegativeInteger"/> - </define> - <!-- URIs --> - <define name="uri"> - <data type="anyURI"> - <param name="maxLength">4096</param> - </data> - </define> - <!-- Name fields imported from up-down protocol --> - <define name="up_down_name"> - <data type="token"> - <param name="maxLength">1024</param> - </data> - </define> - <!-- Resource lists --> - <define name="asn_list"> - <data type="string"> - <param name="maxLength">512000</param> - <param name="pattern">[\-,0-9]*</param> - </data> - </define> - <define name="ipv4_list"> - <data type="string"> - <param name="maxLength">512000</param> - <param name="pattern">[\-,0-9/.]*</param> - </data> - </define> - <define name="ipv6_list"> - <data type="string"> - <param name="maxLength">512000</param> - <param name="pattern">[\-,0-9/:a-fA-F]*</param> - </data> - </define> - <!-- <self/> element --> - <define name="self_bool"> - <optional> - <attribute name="rekey"> - <value>yes</value> - </attribute> - </optional> - <optional> - <attribute name="reissue"> - <value>yes</value> - </attribute> - </optional> - <optional> - <attribute name="revoke"> - <value>yes</value> - </attribute> - </optional> - <optional> - <attribute name="run_now"> - <value>yes</value> - </attribute> - </optional> - <optional> - <attribute name="publish_world_now"> - <value>yes</value> - </attribute> - </optional> - </define> - <define name="self_payload"> - <optional> - <attribute name="use_hsm"> - <choice> - <value>yes</value> - <value>no</value> - </choice> - </attribute> - </optional> - <optional> - <attribute name="crl_interval"> - <data type="positiveInteger"/> - </attribute> - </optional> - <optional> - <attribute name="regen_margin"> - <data type="positiveInteger"/> - </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="self_id"> - <attribute name="self_id"> - <ref name="sql_id"/> - </attribute> - </define> - <define name="self_query" combine="choice"> - <element name="self"> - <ref name="ctl_create"/> - <ref name="self_bool"/> - <ref name="self_payload"/> - </element> - </define> - <define name="self_reply" combine="choice"> - <element name="self"> - <ref name="ctl_create"/> - <ref name="self_id"/> - </element> - </define> - <define name="self_query" combine="choice"> - <element name="self"> - <ref name="ctl_set"/> - <ref name="self_id"/> - <ref name="self_bool"/> - <ref name="self_payload"/> - </element> - </define> - <define name="self_reply" combine="choice"> - <element name="self"> - <ref name="ctl_set"/> - <ref name="self_id"/> - </element> - </define> - <define name="self_query" combine="choice"> - <element name="self"> - <ref name="ctl_get"/> - <ref name="self_id"/> - </element> - </define> - <define name="self_reply" combine="choice"> - <element name="self"> - <ref name="ctl_get"/> - <ref name="self_id"/> - <ref name="self_payload"/> - </element> - </define> - <define name="self_query" combine="choice"> - <element name="self"> - <ref name="ctl_list"/> - </element> - </define> - <define name="self_reply" combine="choice"> - <element name="self"> - <ref name="ctl_list"/> - <ref name="self_id"/> - <ref name="self_payload"/> - </element> - </define> - <define name="self_query" combine="choice"> - <element name="self"> - <ref name="ctl_destroy"/> - <ref name="self_id"/> - </element> - </define> - <define name="self_reply" combine="choice"> - <element name="self"> - <ref name="ctl_destroy"/> - <ref name="self_id"/> - </element> - </define> - <!-- <bsc/> element. Key parameters hardwired for now. --> - <define name="bsc_bool"> - <optional> - <attribute name="generate_keypair"> - <value>yes</value> - </attribute> - <optional> - <attribute name="key_type"> - <value>rsa</value> - </attribute> - </optional> - <optional> - <attribute name="hash_alg"> - <value>sha256</value> - </attribute> - </optional> - <optional> - <attribute name="key_length"> - <value>2048</value> - </attribute> - </optional> - </optional> - </define> - <define name="bsc_id"> - <attribute name="bsc_id"> - <ref name="sql_id"/> - </attribute> - </define> - <define name="bsc_payload"> - <optional> - <element name="signing_cert"> - <ref name="base64"/> - </element> - </optional> - <optional> - <element name="signing_cert_crl"> - <ref name="base64"/> - </element> - </optional> - </define> - <define name="bsc_pkcs10"> - <optional> - <element name="pkcs10_request"> - <ref name="base64"/> - </element> - </optional> - </define> - <define name="bsc_query" combine="choice"> - <element name="bsc"> - <ref name="ctl_create"/> - <ref name="self_id"/> - <ref name="bsc_bool"/> - <ref name="bsc_payload"/> - </element> - </define> - <define name="bsc_reply" combine="choice"> - <element name="bsc"> - <ref name="ctl_create"/> - <ref name="self_id"/> - <ref name="bsc_id"/> - <ref name="bsc_pkcs10"/> - </element> - </define> - <define name="bsc_query" combine="choice"> - <element name="bsc"> - <ref name="ctl_set"/> - <ref name="self_id"/> - <ref name="bsc_id"/> - <ref name="bsc_bool"/> - <ref name="bsc_payload"/> - </element> - </define> - <define name="bsc_reply" combine="choice"> - <element name="bsc"> - <ref name="ctl_set"/> - <ref name="self_id"/> - <ref name="bsc_id"/> - <ref name="bsc_pkcs10"/> - </element> - </define> - <define name="bsc_query" combine="choice"> - <element name="bsc"> - <ref name="ctl_get"/> - <ref name="self_id"/> - <ref name="bsc_id"/> - </element> - </define> - <define name="bsc_reply" combine="choice"> - <element name="bsc"> - <ref name="ctl_get"/> - <ref name="self_id"/> - <ref name="bsc_id"/> - <ref name="bsc_payload"/> - <ref name="bsc_pkcs10"/> - </element> - </define> - <define name="bsc_query" combine="choice"> - <element name="bsc"> - <ref name="ctl_list"/> - <ref name="self_id"/> - </element> - </define> - <define name="bsc_reply" combine="choice"> - <element name="bsc"> - <ref name="ctl_list"/> - <ref name="self_id"/> - <ref name="bsc_id"/> - <ref name="bsc_payload"/> - <ref name="bsc_pkcs10"/> - </element> - </define> - <define name="bsc_query" combine="choice"> - <element name="bsc"> - <ref name="ctl_destroy"/> - <ref name="self_id"/> - <ref name="bsc_id"/> - </element> - </define> - <define name="bsc_reply" combine="choice"> - <element name="bsc"> - <ref name="ctl_destroy"/> - <ref name="self_id"/> - <ref name="bsc_id"/> - </element> - </define> - <!-- <parent/> element --> - <define name="parent_id"> - <attribute name="parent_id"> - <ref name="sql_id"/> - </attribute> - </define> - <define name="parent_bool"> - <optional> - <attribute name="rekey"> - <value>yes</value> - </attribute> - </optional> - <optional> - <attribute name="reissue"> - <value>yes</value> - </attribute> - </optional> - <optional> - <attribute name="revoke"> - <value>yes</value> - </attribute> - </optional> - </define> - <define name="parent_payload"> - <optional> - <attribute name="peer_contact_uri"> - <ref name="uri"/> - </attribute> - </optional> - <optional> - <attribute name="sia_base"> - <ref name="uri"/> - </attribute> - </optional> - <optional> - <ref name="bsc_id"/> - </optional> - <optional> - <ref name="repository_id"/> - </optional> - <optional> - <attribute name="sender_name"> - <ref name="up_down_name"/> - </attribute> - </optional> - <optional> - <attribute name="recipient_name"> - <ref name="up_down_name"/> - </attribute> - </optional> - <optional> - <element name="bpki_cms_cert"> - <ref name="base64"/> - </element> - </optional> - <optional> - <element name="bpki_cms_glue"> - <ref name="base64"/> - </element> - </optional> - <optional> - <element name="bpki_https_cert"> - <ref name="base64"/> - </element> - </optional> - <optional> - <element name="bpki_https_glue"> - <ref name="base64"/> - </element> - </optional> - </define> - <define name="parent_query" combine="choice"> - <element name="parent"> - <ref name="ctl_create"/> - <ref name="self_id"/> - <ref name="parent_bool"/> - <ref name="parent_payload"/> - </element> - </define> - <define name="parent_reply" combine="choice"> - <element name="parent"> - <ref name="ctl_create"/> - <ref name="self_id"/> - <ref name="parent_id"/> - </element> - </define> - <define name="parent_query" combine="choice"> - <element name="parent"> - <ref name="ctl_set"/> - <ref name="self_id"/> - <ref name="parent_id"/> - <ref name="parent_bool"/> - <ref name="parent_payload"/> - </element> - </define> - <define name="parent_reply" combine="choice"> - <element name="parent"> - <ref name="ctl_set"/> - <ref name="self_id"/> - <ref name="parent_id"/> - </element> - </define> - <define name="parent_query" combine="choice"> - <element name="parent"> - <ref name="ctl_get"/> - <ref name="self_id"/> - <ref name="parent_id"/> - </element> - </define> - <define name="parent_reply" combine="choice"> - <element name="parent"> - <ref name="ctl_get"/> - <ref name="self_id"/> - <ref name="parent_id"/> - <ref name="parent_payload"/> - </element> - </define> - <define name="parent_query" combine="choice"> - <element name="parent"> - <ref name="ctl_list"/> - <ref name="self_id"/> - </element> - </define> - <define name="parent_reply" combine="choice"> - <element name="parent"> - <ref name="ctl_list"/> - <ref name="self_id"/> - <ref name="parent_id"/> - <ref name="parent_payload"/> - </element> - </define> - <define name="parent_query" combine="choice"> - <element name="parent"> - <ref name="ctl_destroy"/> - <ref name="self_id"/> - <ref name="parent_id"/> - </element> - </define> - <define name="parent_reply" combine="choice"> - <element name="parent"> - <ref name="ctl_destroy"/> - <ref name="self_id"/> - <ref name="parent_id"/> - </element> - </define> - <!-- <child/> element --> - <define name="child_id"> - <attribute name="child_id"> - <ref name="sql_id"/> - </attribute> - </define> - <define name="child_bool"> - <optional> - <attribute name="reissue"> - <value>yes</value> - </attribute> - </optional> - </define> - <define name="child_payload"> - <optional> - <ref name="bsc_id"/> - </optional> - <optional> - <element name="bpki_cert"> - <ref name="base64"/> - </element> - </optional> - <optional> - <element name="bpki_glue"> - <ref name="base64"/> - </element> - </optional> - </define> - <define name="child_query" combine="choice"> - <element name="child"> - <ref name="ctl_create"/> - <ref name="self_id"/> - <ref name="child_bool"/> - <ref name="child_payload"/> - </element> - </define> - <define name="child_reply" combine="choice"> - <element name="child"> - <ref name="ctl_create"/> - <ref name="self_id"/> - <ref name="child_id"/> - </element> - </define> - <define name="child_query" combine="choice"> - <element name="child"> - <ref name="ctl_set"/> - <ref name="self_id"/> - <ref name="child_id"/> - <ref name="child_bool"/> - <ref name="child_payload"/> - </element> - </define> - <define name="child_reply" combine="choice"> - <element name="child"> - <ref name="ctl_set"/> - <ref name="self_id"/> - <ref name="child_id"/> - </element> - </define> - <define name="child_query" combine="choice"> - <element name="child"> - <ref name="ctl_get"/> - <ref name="self_id"/> - <ref name="child_id"/> - </element> - </define> - <define name="child_reply" combine="choice"> - <element name="child"> - <ref name="ctl_get"/> - <ref name="self_id"/> - <ref name="child_id"/> - <ref name="child_payload"/> - </element> - </define> - <define name="child_query" combine="choice"> - <element name="child"> - <ref name="ctl_list"/> - <ref name="self_id"/> - </element> - </define> - <define name="child_reply" combine="choice"> - <element name="child"> - <ref name="ctl_list"/> - <ref name="self_id"/> - <ref name="child_id"/> - <ref name="child_payload"/> - </element> - </define> - <define name="child_query" combine="choice"> - <element name="child"> - <ref name="ctl_destroy"/> - <ref name="self_id"/> - <ref name="child_id"/> - </element> - </define> - <define name="child_reply" combine="choice"> - <element name="child"> - <ref name="ctl_destroy"/> - <ref name="self_id"/> - <ref name="child_id"/> - </element> - </define> - <!-- <repository/> element --> - <define name="repository_id"> - <attribute name="repository_id"> - <ref name="sql_id"/> - </attribute> - </define> - <define name="repository_payload"> - <optional> - <attribute name="peer_contact_uri"> - <ref name="uri"/> - </attribute> - </optional> - <optional> - <ref name="bsc_id"/> - </optional> - <optional> - <element name="bpki_cms_cert"> - <ref name="base64"/> - </element> - </optional> - <optional> - <element name="bpki_cms_glue"> - <ref name="base64"/> - </element> - </optional> - <optional> - <element name="bpki_https_cert"> - <ref name="base64"/> - </element> - </optional> - <optional> - <element name="bpki_https_glue"> - <ref name="base64"/> - </element> - </optional> - </define> - <define name="repository_query" combine="choice"> - <element name="repository"> - <ref name="ctl_create"/> - <ref name="self_id"/> - <ref name="repository_payload"/> - </element> - </define> - <define name="repository_reply" combine="choice"> - <element name="repository"> - <ref name="ctl_create"/> - <ref name="self_id"/> - <ref name="repository_id"/> - </element> - </define> - <define name="repository_query" combine="choice"> - <element name="repository"> - <ref name="ctl_set"/> - <ref name="self_id"/> - <ref name="repository_id"/> - <ref name="repository_payload"/> - </element> - </define> - <define name="repository_reply" combine="choice"> - <element name="repository"> - <ref name="ctl_set"/> - <ref name="self_id"/> - <ref name="repository_id"/> - </element> - </define> - <define name="repository_query" combine="choice"> - <element name="repository"> - <ref name="ctl_get"/> - <ref name="self_id"/> - <ref name="repository_id"/> - </element> - </define> - <define name="repository_reply" combine="choice"> - <element name="repository"> - <ref name="ctl_get"/> - <ref name="self_id"/> - <ref name="repository_id"/> - <ref name="repository_payload"/> - </element> - </define> - <define name="repository_query" combine="choice"> - <element name="repository"> - <ref name="ctl_list"/> - <ref name="self_id"/> - </element> - </define> - <define name="repository_reply" combine="choice"> - <element name="repository"> - <ref name="ctl_list"/> - <ref name="self_id"/> - <ref name="repository_id"/> - <ref name="repository_payload"/> - </element> - </define> - <define name="repository_query" combine="choice"> - <element name="repository"> - <ref name="ctl_destroy"/> - <ref name="self_id"/> - <ref name="repository_id"/> - </element> - </define> - <define name="repository_reply" combine="choice"> - <element name="repository"> - <ref name="ctl_destroy"/> - <ref name="self_id"/> - <ref name="repository_id"/> - </element> - </define> - <!-- <route_origin/> element --> - <define name="route_origin_id"> - <attribute name="route_origin_id"> - <ref name="sql_id"/> - </attribute> - </define> - <define name="route_origin_bool"> - <optional> - <attribute name="suppress_publication"> - <value>yes</value> - </attribute> - </optional> - </define> - <define name="route_origin_payload"> - <optional> - <attribute name="as_number"> - <data type="positiveInteger"/> - </attribute> - </optional> - <optional> - <attribute name="ipv4"> - <ref name="ipv4_list"/> - </attribute> - </optional> - <optional> - <attribute name="ipv6"> - <ref name="ipv6_list"/> - </attribute> - </optional> - </define> - <define name="route_origin_query" combine="choice"> - <element name="route_origin"> - <ref name="ctl_create"/> - <ref name="self_id"/> - <ref name="route_origin_bool"/> - <ref name="route_origin_payload"/> - </element> - </define> - <define name="route_origin_reply" combine="choice"> - <element name="route_origin"> - <ref name="ctl_create"/> - <ref name="self_id"/> - <ref name="route_origin_id"/> - </element> - </define> - <define name="route_origin_query" combine="choice"> - <element name="route_origin"> - <ref name="ctl_set"/> - <ref name="self_id"/> - <ref name="route_origin_id"/> - <ref name="route_origin_bool"/> - <ref name="route_origin_payload"/> - </element> - </define> - <define name="route_origin_reply" combine="choice"> - <element name="route_origin"> - <ref name="ctl_set"/> - <ref name="self_id"/> - <ref name="route_origin_id"/> - </element> - </define> - <define name="route_origin_query" combine="choice"> - <element name="route_origin"> - <ref name="ctl_get"/> - <ref name="self_id"/> - <ref name="route_origin_id"/> - </element> - </define> - <define name="route_origin_reply" combine="choice"> - <element name="route_origin"> - <ref name="ctl_get"/> - <ref name="self_id"/> - <ref name="route_origin_id"/> - <ref name="route_origin_payload"/> - </element> - </define> - <define name="route_origin_query" combine="choice"> - <element name="route_origin"> - <ref name="ctl_list"/> - <ref name="self_id"/> - </element> - </define> - <define name="route_origin_reply" combine="choice"> - <element name="route_origin"> - <ref name="ctl_list"/> - <ref name="self_id"/> - <ref name="route_origin_id"/> - <ref name="route_origin_payload"/> - </element> - </define> - <define name="route_origin_query" combine="choice"> - <element name="route_origin"> - <ref name="ctl_destroy"/> - <ref name="self_id"/> - <ref name="route_origin_id"/> - </element> - </define> - <define name="route_origin_reply" combine="choice"> - <element name="route_origin"> - <ref name="ctl_destroy"/> - <ref name="self_id"/> - <ref name="route_origin_id"/> - </element> - </define> - <!-- <list_resources/> element --> - <define name="list_resources_query"> - <element name="list_resources"> - <ref name="tag"/> - <ref name="self_id"/> - <ref name="child_id"/> - </element> - </define> - <define name="list_resources_reply"> - <element name="list_resources"> - <ref name="tag"/> - <ref name="self_id"/> - <ref name="child_id"/> - <attribute name="valid_until"> - <data type="dateTime"> - <param name="pattern">.*Z</param> - </data> - </attribute> - <optional> - <attribute name="subject_name"> - <data type="token"> - <param name="maxLength">1024</param> - </data> - </attribute> - </optional> - <optional> - <attribute name="asn"> - <ref name="asn_list"/> - </attribute> - </optional> - <optional> - <attribute name="ipv4"> - <ref name="ipv4_list"/> - </attribute> - </optional> - <optional> - <attribute name="ipv6"> - <ref name="ipv6_list"/> - </attribute> - </optional> - </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"> - <ref name="tag"/> - <ref name="self_id"/> - <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 - End: ---> -''')) - -## @var up_down -## Parsed RelaxNG up_down schema -up_down = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" encoding="UTF-8"?> -<!-- - $Id: up-down-schema.rnc 1798 2008-05-17 08:21:50Z sra $ - - RelaxNG Scheme for up-down protocol, extracted from APNIC Wiki. - - libxml2 (including xmllint) only groks the XML syntax of RelaxNG, so - run the compact syntax through trang to get XML syntax. ---> -<grammar ns="http://www.apnic.net/specs/rescerts/up-down/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> - <start> - <element name="message"> - <attribute name="version"> - <data type="positiveInteger"> - <param name="maxInclusive">1</param> - </data> - </attribute> - <attribute name="sender"> - <data type="token"> - <param name="maxLength">1024</param> - </data> - </attribute> - <attribute name="recipient"> - <data type="token"> - <param name="maxLength">1024</param> - </data> - </attribute> - <ref name="payload"/> - </element> - </start> - <define name="payload" combine="choice"> - <attribute name="type"> - <value>list</value> - </attribute> - <ref name="list_request"/> - </define> - <define name="payload" combine="choice"> - <attribute name="type"> - <value>list_response</value> - </attribute> - <ref name="list_response"/> - </define> - <define name="payload" combine="choice"> - <attribute name="type"> - <value>issue</value> - </attribute> - <ref name="issue_request"/> - </define> - <define name="payload" combine="choice"> - <attribute name="type"> - <value>issue_response</value> - </attribute> - <ref name="issue_response"/> - </define> - <define name="payload" combine="choice"> - <attribute name="type"> - <value>revoke</value> - </attribute> - <ref name="revoke_request"/> - </define> - <define name="payload" combine="choice"> - <attribute name="type"> - <value>revoke_response</value> - </attribute> - <ref name="revoke_response"/> - </define> - <define name="payload" combine="choice"> - <attribute name="type"> - <value>error_response</value> - </attribute> - <ref name="error_response"/> - </define> - <define name="list_request"> - <empty/> - </define> - <define name="list_response"> - <zeroOrMore> - <ref name="class"/> - </zeroOrMore> - </define> - <define name="class"> - <element name="class"> - <attribute name="class_name"> - <data type="token"> - <param name="maxLength">1024</param> - </data> - </attribute> - <attribute name="cert_url"> - <data type="string"> - <param name="maxLength">4096</param> - </data> - </attribute> - <attribute name="resource_set_as"> - <data type="string"> - <param name="maxLength">512000</param> - <param name="pattern">[\-,0-9]*</param> - </data> - </attribute> - <attribute name="resource_set_ipv4"> - <data type="string"> - <param name="maxLength">512000</param> - <param name="pattern">[\-,/.0-9]*</param> - </data> - </attribute> - <attribute name="resource_set_ipv6"> - <data type="string"> - <param name="maxLength">512000</param> - <param name="pattern">[\-,/:0-9a-fA-F]*</param> - </data> - </attribute> - <optional> - <attribute name="resource_set_notafter"> - <data type="dateTime"> - <param name="pattern">.*Z</param> - </data> - </attribute> - </optional> - <optional> - <attribute name="suggested_sia_head"> - <data type="anyURI"> - <param name="maxLength">1024</param> - <param name="pattern">rsync://.+</param> - </data> - </attribute> - </optional> - <zeroOrMore> - <element name="certificate"> - <attribute name="cert_url"> - <data type="string"> - <param name="maxLength">4096</param> - </data> - </attribute> - <optional> - <attribute name="req_resource_set_as"> - <data type="string"> - <param name="maxLength">512000</param> - <param name="pattern">[\-,0-9]*</param> - </data> - </attribute> - </optional> - <optional> - <attribute name="req_resource_set_ipv4"> - <data type="string"> - <param name="maxLength">512000</param> - <param name="pattern">[\-,/.0-9]*</param> - </data> - </attribute> - </optional> - <optional> - <attribute name="req_resource_set_ipv6"> - <data type="string"> - <param name="maxLength">512000</param> - <param name="pattern">[\-,/:0-9a-fA-F]*</param> - </data> - </attribute> - </optional> - <data type="base64Binary"> - <param name="maxLength">512000</param> - </data> - </element> - </zeroOrMore> - <element name="issuer"> - <data type="base64Binary"> - <param name="maxLength">512000</param> - </data> - </element> - </element> - </define> - <define name="issue_request"> - <element name="request"> - <attribute name="class_name"> - <data type="token"> - <param name="maxLength">1024</param> - </data> - </attribute> - <optional> - <attribute name="req_resource_set_as"> - <data type="string"> - <param name="maxLength">512000</param> - <param name="pattern">[\-,0-9]*</param> - </data> - </attribute> - </optional> - <optional> - <attribute name="req_resource_set_ipv4"> - <data type="string"> - <param name="maxLength">512000</param> - <param name="pattern">[\-,/.0-9]*</param> - </data> - </attribute> - </optional> - <optional> - <attribute name="req_resource_set_ipv6"> - <data type="string"> - <param name="maxLength">512000</param> - <param name="pattern">[\-,/:0-9a-fA-F]*</param> - </data> - </attribute> - </optional> - <data type="base64Binary"> - <param name="maxLength">512000</param> - </data> - </element> - </define> - <define name="issue_response"> - <ref name="class"/> - </define> - <define name="revoke_request"> - <ref name="revocation"/> - </define> - <define name="revoke_response"> - <ref name="revocation"/> - </define> - <define name="revocation"> - <element name="key"> - <attribute name="class_name"> - <data type="token"> - <param name="maxLength">1024</param> - </data> - </attribute> - <attribute name="ski"> - <data type="token"> - <param name="maxLength">1024</param> - </data> - </attribute> - </element> - </define> - <define name="error_response"> - <element name="status"> - <data type="positiveInteger"> - <param name="maxInclusive">999999999999999</param> - </data> - </element> - <optional> - <element name="description"> - <attribute name="xml:lang"> - <data type="language"/> - </attribute> - <data type="string"> - <param name="maxLength">1024</param> - </data> - </element> - </optional> - </define> -</grammar> -<!-- - Local Variables: - indent-tabs-mode: nil - End: ---> -''')) - -## @var publication -## Parsed RelaxNG publication schema -publication = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" encoding="UTF-8"?> -<!-- - $Id: publication-schema.rnc 1837 2008-06-02 23:47:19Z sra $ - - RelaxNG Schema for RPKI publication protocol. - - libxml2 (including xmllint) only groks the XML syntax of RelaxNG, so - run the compact syntax through trang to get XML syntax. ---> -<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"> - <!-- 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"/> - </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="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"> - <param name="maxLength">512000</param> - </data> - </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> - <!-- - <config/> element (use restricted to repository operator) - config_id 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_id"> - <attribute name="client_id"> - <data type="nonNegativeInteger"/> - </attribute> - </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_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_id"/> - </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_id"/> - <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_id"/> - </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_id"/> - </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_id"/> - <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_id"/> - <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_id"/> - </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_id"/> - </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> - <!-- <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 - End: ---> -''')) diff --git a/rpkid.stable/rpki/resource_set.py b/rpkid.stable/rpki/resource_set.py deleted file mode 100644 index baaccfc4..00000000 --- a/rpkid.stable/rpki/resource_set.py +++ /dev/null @@ -1,795 +0,0 @@ -"""Classes dealing with sets of resources. - -The basic mechanics of a resource set are the same for any of the -resources we handle (ASNs, IPv4 addresses, or IPv6 addresses), so we -can provide the same operations on any of them, even though the -underlying details vary. - -We also provide some basic set operations (union, intersection, etc). - -$Id$ - -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. -""" - -import re -import rpki.ipaddrs, rpki.oids, rpki.exceptions - -## @var inherit_token -# Token used to indicate inheritance in read and print syntax. - -inherit_token = "<inherit>" - -class resource_range(object): - """Generic resource range type. Assumes underlying type is some - kind of integer. - - This is a virtual class. You probably don't want to use this type - directly. - """ - - def __init__(self, min, max): - """Initialize and sanity check a resource_range.""" - assert min <= max, "Mis-ordered range: %s before %s" % (str(min), str(max)) - self.min = min - self.max = max - - def __cmp__(self, other): - """Compare two resource_range objects.""" - assert self.__class__ is other.__class__ - c = self.min - other.min - if c == 0: c = self.max - other.max - if c < 0: c = -1 - if c > 0: c = 1 - return c - -class resource_range_as(resource_range): - """Range of Autonomous System Numbers. - - Denotes a single ASN by a range whose min and max values are identical. - """ - - ## @var datum_type - # Type of underlying data (min and max). - - datum_type = long - - def __str__(self): - """Convert a resource_range_as to string format.""" - if self.min == self.max: - return str(self.min) - else: - return str(self.min) + "-" + str(self.max) - - def to_rfc3779_tuple(self): - """Convert a resource_range_as to tuple format for RFC 3779 ASN.1 encoding.""" - if self.min == self.max: - return ("id", self.min) - else: - return ("range", (self.min, self.max)) - -class resource_range_ip(resource_range): - """Range of (generic) IP addresses. - - Prefixes are converted to ranges on input, and ranges that can be - represented as prefixes are written as prefixes on output. - - This is a virtual class. You probably don't want to use it directly. - """ - - def _prefixlen(self): - """Determine whether a resource_range_ip can be expressed as a prefix.""" - mask = self.min ^ self.max - if self.min & mask != 0: - return -1 - prefixlen = self.datum_type.bits - while mask & 1: - prefixlen -= 1 - mask >>= 1 - if mask: - return -1 - else: - return prefixlen - - def __str__(self): - """Convert a resource_range_ip to string format.""" - prefixlen = self._prefixlen() - if prefixlen < 0: - return str(self.min) + "-" + str(self.max) - else: - return str(self.min) + "/" + str(prefixlen) - - def to_rfc3779_tuple(self): - """Convert a resource_range_ip to tuple format for RFC 3779 ASN.1 encoding.""" - prefixlen = self._prefixlen() - if prefixlen < 0: - return ("addressRange", (_long2bs(self.min, self.datum_type.bits, strip = 0), - _long2bs(self.max, self.datum_type.bits, strip = 1))) - else: - return ("addressPrefix", _long2bs(self.min, self.datum_type.bits, prefixlen = prefixlen)) - - @classmethod - def make_prefix(cls, address, prefixlen): - """Construct a resource range corresponding to a prefix.""" - assert isinstance(address, cls.datum_type) and isinstance(prefixlen, (int, long)) - assert prefixlen >= 0 and prefixlen <= cls.datum_type.bits, "Nonsensical prefix length: %s" % prefixlen - mask = (1 << (cls.datum_type.bits - prefixlen)) - 1 - assert (address & mask) == 0, "Resource not in canonical form: %s/%s" % (address, prefixlen) - return cls(cls.datum_type(address), cls.datum_type(address | mask)) - -class resource_range_ipv4(resource_range_ip): - """Range of IPv4 addresses.""" - - ## @var datum_type - # Type of underlying data (min and max). - - datum_type = rpki.ipaddrs.v4addr - -class resource_range_ipv6(resource_range_ip): - """Range of IPv6 addresses.""" - - ## @var datum_type - # Type of underlying data (min and max). - - datum_type = rpki.ipaddrs.v6addr - -def _rsplit(rset, that): - """Utility function to split a resource range into two resource ranges.""" - this = rset.pop(0) - cell_type = type(this.min) - assert type(this) is type(that) and type(this.max) is cell_type and \ - type(that.min) is cell_type and type(that.max) is cell_type - if this.min < that.min: - rset.insert(0, type(this)(this.min, cell_type(that.min - 1))) - rset.insert(1, type(this)(that.min, this.max)) - else: - assert this.max > that.max - rset.insert(0, type(this)(this.min, that.max)) - rset.insert(1, type(this)(cell_type(that.max + 1), this.max)) - -class resource_set(list): - """Generic resource set. - This is a list subclass containing resource ranges. - - This is a virtual class. You probably don't want to use it - directly. - """ - - ## @var inherit - # Boolean indicating whether this resource_set uses RFC 3779 inheritance. - - inherit = False - - def __init__(self, ini = None): - """Initialize a resource_set.""" - if isinstance(ini, (int, long)): - ini = str(ini) - if ini == inherit_token: - self.inherit = True - elif isinstance(ini, str) and len(ini): - self.extend(self.parse_str(s) for s in ini.split(",")) - elif isinstance(ini, tuple): - self.parse_rfc3779_tuple(ini) - elif isinstance(ini, list): - self.extend(ini) - else: - assert ini is None or ini == "", "Unexpected initializer: %s" % str(ini) - assert not self.inherit or not self - self.sort() - for i in xrange(len(self) - 2, -1, -1): - if self[i].max + 1 == self[i+1].min: - self[i] = type(self[i])(self[i].min, self[i+1].max) - self.pop(i + 1) - if __debug__: - for i in xrange(0, len(self) - 1): - assert self[i].max < self[i+1].min, "Resource overlap: %s %s" % (self[i], self[i+1]) - - def __str__(self): - """Convert a resource_set to string format.""" - if self.inherit: - return inherit_token - else: - return ",".join(str(x) for x in self) - - def _comm(self, other): - """Like comm(1), sort of. - - Returns a tuple of three resource sets: resources only in self, - resources only in other, and resources in both. Used (not very - efficiently) as the basis for most set operations on resource - sets. - """ - assert not self.inherit - assert type(self) is type(other), "Type mismatch %s %s" % (repr(type(self)), repr(type(other))) - set1 = self[:] - set2 = other[:] - only1, only2, both = [], [], [] - while set1 or set2: - if set1 and (not set2 or set1[0].max < set2[0].min): - only1.append(set1.pop(0)) - elif set2 and (not set1 or set2[0].max < set1[0].min): - only2.append(set2.pop(0)) - elif set1[0].min < set2[0].min: - _rsplit(set1, set2[0]) - elif set2[0].min < set1[0].min: - _rsplit(set2, set1[0]) - elif set1[0].max < set2[0].max: - _rsplit(set2, set1[0]) - elif set2[0].max < set1[0].max: - _rsplit(set1, set2[0]) - else: - assert set1[0].min == set2[0].min and set1[0].max == set2[0].max - both.append(set1.pop(0)) - set2.pop(0) - return type(self)(only1), type(self)(only2), type(self)(both) - - def union(self, other): - """Set union for resource sets.""" - assert not self.inherit - assert type(self) is type(other), "Type mismatch: %s %s" % (repr(type(self)), repr(type(other))) - set1 = self[:] - set2 = other[:] - result = [] - while set1 or set2: - if set1 and (not set2 or set1[0].max < set2[0].min): - result.append(set1.pop(0)) - elif set2 and (not set1 or set2[0].max < set1[0].min): - result.append(set2.pop(0)) - else: - this = set1.pop(0) - that = set2.pop(0) - assert type(this) is type(that) - if this.min < that.min: min = this.min - else: min = that.min - if this.max > that.max: max = this.max - else: max = that.max - result.append(type(this)(min, max)) - return type(self)(result) - - def intersection(self, other): - """Set intersection for resource sets.""" - return self._comm(other)[2] - - def difference(self, other): - """Set difference for resource sets.""" - return self._comm(other)[0] - - def symmetric_difference(self, other): - """Set symmetric difference (XOR) for resource sets.""" - com = self._comm(other) - return com[0].union(com[1]) - - def contains(self, item): - """Set membership test for resource sets.""" - assert not self.inherit - for i in self: - if isinstance(item, type(i)) and i.min <= item.min and i.max >= item.max: - return True - elif isinstance(item, type(i.min)) and i.min <= item and i.max >= item: - return True - else: - assert isinstance(item, (type(i), type(i.min))) - return False - - def issubset(self, other): - """Test whether self is a subset (possibly improper) of other.""" - for i in self: - if not other.contains(i): - return False - return True - - def issuperset(self, other): - """Test whether self is a superset (possibly improper) of other.""" - return other.issubset(self) - - @classmethod - def from_sql(cls, sql, query, args = None): - """Create resource set from an SQL query. - - sql is an object that supports execute() and fetchall() methods - like a DB API 2.0 cursor object. - - query is an SQL query that returns a sequence of (min, max) pairs. - """ - - sql.execute(query, args) - return cls(ini = [cls.range_type(cls.range_type.datum_type(b), - cls.range_type.datum_type(e)) - for (b,e) in sql.fetchall()]) - -class resource_set_as(resource_set): - """ASN resource set.""" - - ## @var range_type - # Type of range underlying this type of resource_set. - - range_type = resource_range_as - - def parse_str(self, x): - """Parse ASN resource sets from text (eg, XML attributes).""" - r = re.match("^([0-9]+)-([0-9]+)$", x) - if r: - return resource_range_as(long(r.group(1)), long(r.group(2))) - else: - return resource_range_as(long(x), long(x)) - - def parse_rfc3779_tuple(self, x): - """Parse ASN resource from tuple format generated by RFC 3779 ASN.1 decoder.""" - if x[0] == "asIdsOrRanges": - for aor in x[1]: - if aor[0] == "range": - min = aor[1][0] - max = aor[1][1] - else: - min = aor[1] - max = min - self.append(resource_range_as(min, max)) - else: - assert x[0] == "inherit" - self.inherit = True - - def to_rfc3779_tuple(self): - """Convert ASN resource set into tuple format used for RFC 3779 ASN.1 encoding.""" - if self: - return ("asIdsOrRanges", tuple(a.to_rfc3779_tuple() for a in self)) - elif self.inherit: - return ("inherit", "") - else: - return None - -class resource_set_ip(resource_set): - """(Generic) IP address resource set. - - This is a virtual class. You probably don't want to use it - directly. - """ - - def parse_str(self, x): - """Parse IP address resource sets from text (eg, XML attributes).""" - r = re.match("^([0-9:.a-fA-F]+)-([0-9:.a-fA-F]+)$", x) - if r: - return self.range_type(self.range_type.datum_type(r.group(1)), self.range_type.datum_type(r.group(2))) - r = re.match("^([0-9:.a-fA-F]+)/([0-9]+)$", x) - if r: - return self.range_type.make_prefix(self.range_type.datum_type(r.group(1)), int(r.group(2))) - raise RuntimeError, 'Bad IP resource "%s"' % (x) - - def parse_rfc3779_tuple(self, x): - """Parse IP address resource sets from tuple format generated by RFC 3779 ASN.1 decoder.""" - if x[0] == "addressesOrRanges": - for aor in x[1]: - if aor[0] == "addressRange": - min = _bs2long(aor[1][0], self.range_type.datum_type.bits, 0) - max = _bs2long(aor[1][1], self.range_type.datum_type.bits, 1) - else: - min = _bs2long(aor[1], self.range_type.datum_type.bits, 0) - max = _bs2long(aor[1], self.range_type.datum_type.bits, 1) - self.append(self.range_type(self.range_type.datum_type(min), self.range_type.datum_type(max))) - else: - assert x[0] == "inherit" - self.inherit = True - - def to_rfc3779_tuple(self): - """Convert IP resource set into tuple format used by RFC 3779 ASN.1 encoder.""" - if self: - return (self.afi, ("addressesOrRanges", tuple(a.to_rfc3779_tuple() for a in self))) - elif self.inherit: - return (self.afi, ("inherit", "")) - else: - return None - -class resource_set_ipv4(resource_set_ip): - """IPv4 address resource set.""" - - ## @var range_type - # Type of range underlying this type of resource_set. - - range_type = resource_range_ipv4 - - ## @var afi - # Address Family Identifier value for IPv4. - - afi = "\x00\x01" - -class resource_set_ipv6(resource_set_ip): - """IPv6 address resource set.""" - - ## @var range_type - # Type of range underlying this type of resource_set. - - range_type = resource_range_ipv6 - - ## @var afi - # Address Family Identifier value for IPv6. - - afi = "\x00\x02" - -def _bs2long(bs, addrlen, fill): - """Utility function to convert a bitstring (POW.pkix tuple - representation) into a Python long. - """ - x = 0L - for y in bs: - x = (x << 1) | y - for y in xrange(addrlen - len(bs)): - x = (x << 1) | fill - return x - -def _long2bs(number, addrlen, prefixlen = None, strip = None): - """Utility function to convert a Python long into a POW.pkix tuple - bitstring. This is a bit complicated because it supports the - fiendishly compact encoding used in RFC 3779. - """ - assert prefixlen is None or strip is None - bs = [] - while number: - bs.append(int(number & 1)) - number >>= 1 - if addrlen > len(bs): - bs.extend((0 for i in xrange(addrlen - len(bs)))) - bs.reverse() - if prefixlen is not None: - return tuple(bs[0:prefixlen]) - if strip is not None: - while bs and bs[-1] == strip: - bs.pop() - return tuple(bs) - -class resource_bag(object): - """Container to simplify passing around the usual triple of ASN, - IPv4, and IPv6 resource sets. - """ - - ## @var asn - # Set of Autonomous System Number resources. - - ## @var v4 - # Set of IPv4 resources. - - ## @var v6 - # Set of IPv6 resources. - - ## @var valid_until - # Expiration date of resources, for setting certificate notAfter field. - - def __init__(self, asn = None, v4 = None, v6 = None, valid_until = None): - self.asn = asn or resource_set_as() - self.v4 = v4 or resource_set_ipv4() - self.v6 = v6 or resource_set_ipv6() - self.valid_until = valid_until - - def oversized(self, other): - """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) - - def undersized(self, other): - """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) - - @classmethod - def from_rfc3779_tuples(cls, exts): - """Build a resource_bag from intermediate form generated by RFC 3779 ASN.1 decoder.""" - asn = None - v4 = None - v6 = None - for x in exts: - if x[0] == rpki.oids.name2oid["sbgp-autonomousSysNum"]: # - assert len(x[2]) == 1 or x[2][1] is None, "RDI not implemented: %s" % (str(x)) - assert asn is None - asn = resource_set_as(x[2][0]) - if x[0] == rpki.oids.name2oid["sbgp-ipAddrBlock"]: - for fam in x[2]: - if fam[0] == resource_set_ipv4.afi: - assert v4 is None - v4 = resource_set_ipv4(fam[1]) - if fam[0] == resource_set_ipv6.afi: - assert v6 is None - v6 = resource_set_ipv6(fam[1]) - return cls(asn, v4, v6) - - def empty(self): - """Return True iff all resource sets in this bag are empty.""" - return not self.asn and not self.v4 and not self.v6 - - def __eq__(self, other): - return self.asn == other.asn and \ - self.v4 == other.v4 and \ - self.v6 == other.v6 and \ - self.valid_until == other.valid_until - - def __ne__(self, other): - return not (self == other) - - def intersection(self, other): - """Compute intersection with another resource_bag. - valid_until attribute (if any) inherits from self. - """ - return self.__class__(self.asn.intersection(other.asn), - self.v4.intersection(other.v4), - self.v6.intersection(other.v6), - self.valid_until) - - def union(self, other): - """Compute union with another resource_bag. - valid_until attribute (if any) inherits from self. - """ - return self.__class__(self.asn.union(other.asn), - self.v4.union(other.v4), - self.v6.union(other.v6), - self.valid_until) - - def __str__(self): - s = "" - if self.asn: - s += "ASN: %s" % self.asn - if self.v4: - if s: - s += ", " - s += "V4: %s" % self.v4 - if self.v6: - if s: - s += ", " - s += "V6: %s" % self.v6 - return s - -# Sadly, there are enough differences between RFC 3779 and the data -# structures in the latest proposed ROA format that we can't just use -# the RFC 3779 code for ROAs. So we need a separate set of classes -# that are similar in concept but different in detail, with conversion -# functions. Such is life. I suppose it might be possible to do this -# with multiple inheritance, but that's probably more bother than it's -# worth. - -class roa_prefix(object): - """ROA prefix. This is similar to the resource_range_ip class, but - differs in that it only represents prefixes, never ranges, and - includes the maximum prefix length as an additional value. - - This is a virtual class, you probably don't want to use it directly. - """ - - ## @var address - # Address portion of prefix. - - ## @var prefixlen - # (Minimum) prefix length. - - ## @var max_prefixlen - # Maxmimum prefix length. - - def __init__(self, address, prefixlen, max_prefixlen = None): - """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) - self.address = address - self.prefixlen = prefixlen - self.max_prefixlen = max_prefixlen - - def __cmp__(self, other): - """Compare two ROA prefix objects. Comparision is based on - address, prefixlen, and max_prefixlen, in that order. - """ - assert self.__class__ is other.__class__ - c = self.address - other.address - if c == 0: c = self.prefixlen - other.prefixlen - if c == 0: c = self.max_prefixlen - other.max_prefixlen - if c < 0: c = -1 - if c > 0: c = 1 - return c - - def __str__(self): - """Convert a ROA prefix to string format.""" - if self.prefixlen == self.max_prefixlen: - return str(self.address) + "/" + str(self.prefixlen) - else: - return str(self.address) + "/" + str(self.prefixlen) + "-" + str(self.max_prefixlen) - - def to_resource_range(self): - """Convert this ROA prefix to the equivilent resource_range_ip - 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.address, self.prefixlen) - - def min(self): - """Return lowest address covered by prefix.""" - return self.address - - def max(self): - """Return highest address covered by prefix.""" - t = self.range_type.datum_type - return t(self.address | ((1 << (t.bits - self.prefixlen)) - 1)) - - def to_roa_tuple(self): - """Convert a resource_range_ip to tuple format for ROA ASN.1 encoding.""" - return (_long2bs(self.address, self.range_type.datum_type.bits, prefixlen = self.prefixlen), - None if self.prefixlen == self.max_prefixlen else self.max_prefixlen) - -class roa_prefix_ipv4(roa_prefix): - """IPv4 ROA prefix.""" - - ## @var range_type - # Type of corresponding resource_range_ip. - - range_type = resource_range_ipv4 - -class roa_prefix_ipv6(roa_prefix): - """IPv6 ROA prefix.""" - - ## @var range_type - # Type of corresponding resource_range_ip. - - range_type = resource_range_ipv6 - -class roa_prefix_set(list): - """Set of ROA prefixes, analogous to the resource_set_ip class.""" - - def __init__(self, ini = None): - """Initialize a ROA prefix set.""" - if isinstance(ini, str) and len(ini): - self.extend(self.parse_str(s) for s in ini.split(",")) - elif isinstance(ini, (list, tuple)): - self.extend(ini) - else: - assert ini is None or ini == "", "Unexpected initializer: %s" % str(ini) - self.sort() - if __debug__: - for i in xrange(0, len(self) - 1): - assert self[i].max() < self[i+1].min(), "Prefix overlap: %s %s" % (self[i], self[i+1]) - - def __str__(self): - """Convert a ROA prefix set to string format.""" - return ",".join(str(x) for x in self) - - def parse_str(self, x): - """Parse ROA prefix from text (eg, an XML attribute).""" - r = re.match("^([0-9:.a-fA-F]+)/([0-9]+)-([0-9]+)$", x) - if r: - return self.prefix_type(self.prefix_type.range_type.datum_type(r.group(1)), int(r.group(2)), int(r.group(3))) - r = re.match("^([0-9:.a-fA-F]+)/([0-9]+)$", x) - if r: - return self.prefix_type(self.prefix_type.range_type.datum_type(r.group(1)), int(r.group(2))) - raise RuntimeError, 'Bad ROA prefix "%s"' % (x) - - def to_resource_set(self): - """Convert a ROA prefix set to a resource set. This is an - irreversable transformation. - """ - return self.resource_set_type([p.to_resource_range() for p in self]) - - @classmethod - def from_sql(cls, sql, query, args = None): - """Create ROA prefix set from an SQL query. - - sql is an object that supports execute() and fetchall() methods - like a DB API 2.0 cursor object. - - query is an SQL query that returns a sequence of (address, - prefixlen, max_prefixlen) triples. - """ - - sql.execute(query, args) - return cls([cls.prefix_type(cls.prefix_type.range_type.datum_type(x), int(y), int(z)) - for (x,y,z) in sql.fetchall()]) - - def to_roa_tuple(self): - """Convert ROA prefix set into tuple format used by ROA ASN.1 encoder. - This is a variation on the format used in RFC 3779.""" - if self: - return (self.resource_set_type.afi, tuple(a.to_roa_tuple() for a in self)) - else: - return None - -class roa_prefix_set_ipv4(roa_prefix_set): - """Set of IPv4 ROA prefixes.""" - - ## @var prefix_type - # Type of underlying roa_prefix. - - prefix_type = roa_prefix_ipv4 - - ## @var resource_set_type - # Type of corresponding resource_set_ip class. - - resource_set_type = resource_set_ipv4 - -class roa_prefix_set_ipv6(roa_prefix_set): - """Set of IPv6 ROA prefixes.""" - - ## @var prefix_type - # Type of underlying roa_prefix. - - prefix_type = roa_prefix_ipv6 - - ## @var resource_set_type - # Type of corresponding resource_set_ip class. - - resource_set_type = resource_set_ipv6 - -# Test suite for set operations. - -if __name__ == "__main__": - - def test1(t, s1, s2): - if isinstance(s1, str) and isinstance(s2, str): - print "x: ", s1 - print "y: ", s2 - r1 = t(s1) - r2 = t(s2) - print "x: ", r1 - print "y: ", r2 - v1 = r1._comm(r2) - v2 = r2._comm(r1) - assert v1[0] == v2[1] and v1[1] == v2[0] and v1[2] == v2[2] - for i in r1: assert r1.contains(i) and r1.contains(i.min) and r1.contains(i.max) - for i in r2: assert r2.contains(i) and r2.contains(i.min) and r2.contains(i.max) - for i in v1[0]: assert r1.contains(i) and not r2.contains(i) - for i in v1[1]: assert not r1.contains(i) and r2.contains(i) - for i in v1[2]: assert r1.contains(i) and r2.contains(i) - v1 = r1.union(r2) - v2 = r2.union(r1) - assert v1 == v2 - print "x|y:", v1 - v1 = r1.difference(r2) - v2 = r2.difference(r1) - print "x-y:", v1 - print "y-x:", v2 - v1 = r1.symmetric_difference(r2) - v2 = r2.symmetric_difference(r1) - assert v1 == v2 - print "x^y:", v1 - v1 = r1.intersection(r2) - v2 = r2.intersection(r1) - assert v1 == v2 - print "x&y:", v1 - - def test2(t, s1, s2): - print "x: ", s1 - print "y: ", s2 - r1 = t(s1) - r2 = t(s2) - print "x: ", r1 - print "y: ", r2 - print "x>y:", (r1 > r2) - print "x<y:", (r1 < r2) - test1(t.resource_set_type, r1.to_resource_set(), r2.to_resource_set()) - - print - print "Testing set operations on resource sets" - print - test1(resource_set_as, "1,2,3,4,5,6,11,12,13,14,15", "1,2,3,4,5,6,111,121,131,141,151") - print - test1(resource_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.3.0.0/24,10.0.0.77/32") - print - test1(resource_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.0.0.0/24") - print - test1(resource_set_ipv4, "10.0.0.0/24", "10.3.0.0/24,10.0.0.77/32") - print - print "Testing set operations on ROA prefixes" - print - test2(roa_prefix_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.3.0.0/24,10.0.0.77/32") - print - test2(roa_prefix_set_ipv4, "10.0.0.0/24-32,10.6.0.0/24-32", "10.3.0.0/24,10.0.0.0/16-32") - print - test2(roa_prefix_set_ipv4, "10.3.0.0/24-24,10.0.0.0/16-32", "10.3.0.0/24,10.0.0.0/16-32") - print diff --git a/rpkid.stable/rpki/roa.py b/rpkid.stable/rpki/roa.py deleted file mode 100644 index ab178db0..00000000 --- a/rpkid.stable/rpki/roa.py +++ /dev/null @@ -1,75 +0,0 @@ -"""ROA (Route Origin Authorization). - -At the moment this is just the ASN.1 encoder. - -This corresponds to draft-ietf-sidr-roa-format, which is a work in -progress, so this may need updating later. - -$Id$ - -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. - -draft-ietf-sidr-roa-format-03 2.1.3.2 specifies: - - RouteOriginAttestation ::= SEQUENCE { - version [0] INTEGER DEFAULT 0, - asID ASID, - ipAddrBlocks SEQUENCE OF ROAIPAddressFamily } - - ASID ::= INTEGER - - ROAIPAddressFamily ::= SEQUENCE { - addressFamily OCTET STRING (SIZE (2..3)), - addresses SEQUENCE OF ROAIPAddress } - - ROAIPAddress ::= SEQUENCE { - address IPAddress, - maxLength INTEGER OPTIONAL } - - IPAddress ::= BIT STRING -""" - -from POW._der import * - -class ROAIPAddress(Sequence): - def __init__(self, optional=0, default=''): - self.address = BitString() - self.maxLength = Integer(1) - contents = [ self.address, self.maxLength ] - Sequence.__init__(self, contents, optional, default) - -class ROAIPAddresses(SequenceOf): - def __init__(self, optional=0, default=''): - SequenceOf.__init__(self, ROAIPAddress, optional, default) - -class ROAIPAddressFamily(Sequence): - def __init__(self, optional=0, default=''): - self.addressFamily = OctetString() - self.addresses = ROAIPAddresses() - contents = [ self.addressFamily, self.addresses ] - Sequence.__init__(self, contents, optional, default) - -class ROAIPAddressFamilies(SequenceOf): - def __init__(self, optional=0, default=''): - SequenceOf.__init__(self, ROAIPAddressFamily, optional, default) - -class RouteOriginAttestation(Sequence): - def __init__(self, optional=0, default=''): - self.version = Integer() - self.explicitVersion = Explicit(CLASS_CONTEXT, FORM_CONSTRUCTED, 0, self.version, 0, 'oAMCAQA=') - self.asID = Integer() - self.ipAddrBlocks = ROAIPAddressFamilies() - contents = [ self.explicitVersion, self.asID, self.ipAddrBlocks ] - Sequence.__init__(self, contents, optional, default) diff --git a/rpkid.stable/rpki/rpki_engine.py b/rpkid.stable/rpki/rpki_engine.py deleted file mode 100644 index a49121c1..00000000 --- a/rpkid.stable/rpki/rpki_engine.py +++ /dev/null @@ -1,819 +0,0 @@ -"""Global context for rpkid. - -$Id$ - -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. -""" - -import traceback, os, time, getopt, sys, MySQLdb, lxml.etree -import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509, rpki.sql -import rpki.https, rpki.config, rpki.exceptions, rpki.relaxng, rpki.log - -class rpkid_context(object): - """A container for various global rpkid parameters.""" - - def __init__(self, cfg): - - self.sql = rpki.sql.session(cfg) - - self.bpki_ta = rpki.x509.X509(Auto_file = cfg.get("bpki-ta")) - self.irdb_cert = rpki.x509.X509(Auto_file = cfg.get("irdb-cert")) - self.irbe_cert = rpki.x509.X509(Auto_file = cfg.get("irbe-cert")) - self.rpkid_cert = rpki.x509.X509(Auto_file = cfg.get("rpkid-cert")) - self.rpkid_key = rpki.x509.RSA( Auto_file = cfg.get("rpkid-key")) - - self.irdb_url = cfg.get("irdb-url") - - self.https_server_host = cfg.get("server-host", "") - self.https_server_port = int(cfg.get("server-port", "4433")) - - self.publication_kludge_base = cfg.get("publication-kludge-base", "publication/") - - def irdb_query(self, self_id, child_id = None): - """Perform an IRDB callback query. In the long run this should not - be a blocking routine, it should instead issue a query and set up a - handler to receive the response. For the moment, though, we are - doing simple lock step and damn the torpedos. Not yet doing - anything useful with subject name. Most likely this function should - really be wrapped up in a class that carries both the query result - and also the intermediate state needed for the event-driven code - that this function will need to become. - """ - - rpki.log.trace() - - q_msg = rpki.left_right.msg() - q_msg.type = "query" - q_msg.append(rpki.left_right.list_resources_elt()) - q_msg[0].self_id = self_id - q_msg[0].child_id = child_id - q_cms = rpki.left_right.cms_msg.wrap(q_msg, self.rpkid_key, self.rpkid_cert) - der = rpki.https.client( - server_ta = (self.bpki_ta, self.irdb_cert), - client_key = self.rpkid_key, - client_cert = self.rpkid_cert, - url = self.irdb_url, - msg = q_cms) - r_msg = rpki.left_right.cms_msg.unwrap(der, (self.bpki_ta, self.irdb_cert)) - if len(r_msg) == 0 or not isinstance(r_msg[0], rpki.left_right.list_resources_elt) or r_msg.type != "reply": - raise rpki.exceptions.BadIRDBReply, "Unexpected response to IRDB query: %s" % lxml.etree.tostring(r_msg.toXML(), pretty_print = True, encoding = "us-ascii") - return rpki.resource_set.resource_bag( - asn = r_msg[0].asn, - v4 = r_msg[0].ipv4, - v6 = r_msg[0].ipv6, - valid_until = r_msg[0].valid_until) - - def left_right_handler(self, query, path): - """Process one left-right PDU.""" - rpki.log.trace() - try: - self.sql.ping() - q_msg = rpki.left_right.cms_msg.unwrap(query, (self.bpki_ta, self.irbe_cert)) - if q_msg.type != "query": - raise rpki.exceptions.BadQuery, "Message type is not query" - r_msg = q_msg.serve_top_level(self) - reply = rpki.left_right.cms_msg.wrap(r_msg, self.rpkid_key, self.rpkid_cert) - self.sql.sweep() - return 200, reply - except Exception, data: - rpki.log.error(traceback.format_exc()) - return 500, "Unhandled exception %s" % data - - def up_down_handler(self, query, path): - """Process one up-down PDU.""" - rpki.log.trace() - try: - self.sql.ping() - child_id = path.partition("/up-down/")[2] - if not child_id.isdigit(): - raise rpki.exceptions.BadContactURL, "Bad path: %s" % path - child = rpki.left_right.child_elt.sql_fetch(self, long(child_id)) - if child is None: - raise rpki.exceptions.ChildNotFound, "Could not find child %s" % child_id - reply = child.serve_up_down(query) - self.sql.sweep() - return 200, reply - except Exception, data: - rpki.log.error(traceback.format_exc()) - return 400, "Could not process PDU: %s" % data - - def cronjob_handler(self, query, path): - """Periodic tasks. As simple as possible for now, may need to break - this up into separate handlers later. - """ - - rpki.log.trace() - try: - self.sql.ping() - for s in rpki.left_right.self_elt.sql_fetch_all(self): - rpki.log.debug("Self %s polling parents" % s.self_id) - s.client_poll() - rpki.log.debug("Self %s updating children" % s.self_id) - s.update_children() - rpki.log.debug("Self %s updating ROAs" % s.self_id) - s.update_roas() - rpki.log.debug("Self %s regenerating CRLs and manifests" % s.self_id) - s.regenerate_crls_and_manifests() - self.sql.sweep() - return 200, "OK" - except Exception, data: - rpki.log.error(traceback.format_exc()) - return 500, "Unhandled exception %s" % data - - ## @var https_ta_cache - # HTTPS trust anchor cache, to avoid regenerating it for every TLS connection. - https_ta_cache = None - - def clear_https_ta_cache(self): - """Clear dynamic TLS trust anchors.""" - - if self.https_ta_cache is not None: - rpki.log.debug("Clearing HTTPS trusted cert cache") - self.https_ta_cache = None - - def build_https_ta_cache(self): - """Build dynamic TLS trust anchors.""" - - if self.https_ta_cache is None: - - selves = rpki.left_right.self_elt.sql_fetch_all(self) - children = rpki.left_right.child_elt.sql_fetch_all(self) - - self.https_ta_cache = rpki.https.build_https_ta_cache( - [c.bpki_cert for c in children if c.bpki_cert is not None] + - [c.bpki_glue for c in children if c.bpki_glue is not None] + - [s.bpki_cert for s in selves if s.bpki_cert is not None] + - [s.bpki_glue for s in selves if s.bpki_glue is not None] + - [self.irbe_cert, self.irdb_cert, self.bpki_ta]) - - return self.https_ta_cache - - -class ca_obj(rpki.sql.sql_persistant): - """Internal CA object.""" - - sql_template = rpki.sql.template( - "ca", - "ca_id", - "last_crl_sn", - ("next_crl_update", rpki.sundial.datetime), - "last_issued_sn", "last_manifest_sn", - ("next_manifest_update", rpki.sundial.datetime), - "sia_uri", "parent_id", "parent_resource_class") - - last_crl_sn = 0 - last_issued_sn = 0 - last_manifest_sn = 0 - - def parent(self): - """Fetch parent object to which this CA object links.""" - return rpki.left_right.parent_elt.sql_fetch(self.gctx, self.parent_id) - - def ca_details(self): - """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,)) - - def fetch_pending(self): - """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,)) - - def fetch_active(self): - """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,)) - - def fetch_deprecated(self): - """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,)) - - def fetch_revoked(self): - """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,)) - - def construct_sia_uri(self, parent, rc): - """Construct the sia_uri value for this CA given configured - information and the parent's up-down protocol list_response PDU. - """ - - repository = parent.repository() - sia_uri = rc.suggested_sia_head and rc.suggested_sia_head.rsync() - if not sia_uri or not sia_uri.startswith(parent.sia_base): - sia_uri = parent.sia_base - elif not sia_uri.endswith("/"): - raise rpki.exceptions.BadURISyntax, "SIA URI must end with a slash: %s" % sia_uri - return sia_uri + str(self.ca_id) + "/" - - def check_for_updates(self, parent, rc): - """Parent has signaled continued existance of a resource class we - already knew about, so we need to check for an updated - certificate, changes in resource coverage, revocation and reissue - with the same key, etc. - """ - - sia_uri = self.construct_sia_uri(parent, rc) - sia_uri_changed = self.sia_uri != sia_uri - if sia_uri_changed: - self.sia_uri = sia_uri - self.sql_mark_dirty() - - rc_resources = rc.to_resource_bag() - cert_map = dict((c.cert.get_SKI(), c) for c in rc.certs) - - for ca_detail in ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND latest_ca_cert IS NOT NULL AND state != 'revoked'", (self.ca_id,)): - - ski = ca_detail.latest_ca_cert.get_SKI() - - if ski not in cert_map: - rpki.log.warn("Certificate in database missing from list_response, class %s, SKI %s, maybe parent certificate went away?" - % (repr(rc.class_name), ca_detail.latest_ca_cert.gSKI())) - ca_detail.delete(self, parent.repository()) - continue - - if ca_detail.state in ("pending", "active"): - current_resources = ca_detail.latest_ca_cert.get_3779resources() - if sia_uri_changed or \ - ca_detail.latest_ca_cert != cert_map[ski].cert or \ - current_resources.undersized(rc_resources) or \ - current_resources.oversized(rc_resources): - ca_detail.update( - parent = parent, - ca = self, - rc = rc, - sia_uri_changed = sia_uri_changed, - old_resources = current_resources) - - del cert_map[ski] - - if cert_map: - rpki.log.warn("Certificates in list_response missing from our database, class %s, SKIs %s" - % (repr(rc.class_name), ", ".join(c.cert.gSKI() for c in cert_map.values()))) - - @classmethod - def create(cls, parent, rc): - """Parent has signaled existance of a new resource class, so we - need to create and set up a corresponding CA object. - """ - - self = cls() - self.gctx = parent.gctx - self.parent_id = parent.parent_id - self.parent_resource_class = rc.class_name - self.sql_store() - self.sia_uri = self.construct_sia_uri(parent, rc) - ca_detail = ca_detail_obj.create(self) - - # This will need a callback when we go event-driven - issue_response = rpki.up_down.issue_pdu.query(parent, self, ca_detail) - - ca_detail.activate( - ca = self, - cert = issue_response.payload.classes[0].certs[0].cert, - uri = issue_response.payload.classes[0].certs[0].cert_url) - - def delete(self, parent): - """The list of current resource classes received from parent does - not include the class corresponding to this CA, so we need to - delete it (and its little dog too...). - - All certs published by this CA are now invalid, so need to - withdraw them, the CRL, and the manifest from the repository, - delete all child_cert and ca_detail records associated with this - CA, then finally delete this CA itself. - """ - - repository = parent.repository() - for ca_detail in self.ca_details(): - ca_detail.delete(self, repository) - self.sql_delete() - - def next_serial_number(self): - """Allocate a certificate serial number.""" - self.last_issued_sn += 1 - self.sql_mark_dirty() - return self.last_issued_sn - - def next_manifest_number(self): - """Allocate a manifest serial number.""" - self.last_manifest_sn += 1 - self.sql_mark_dirty() - return self.last_manifest_sn - - def next_crl_number(self): - """Allocate a CRL serial number.""" - self.last_crl_sn += 1 - self.sql_mark_dirty() - return self.last_crl_sn - - def rekey(self): - """Initiate a rekey operation for this ca. Generate a new - keypair. Request cert from parent using new keypair. Mark result - as our active ca_detail. Reissue all child certs issued by this - ca using the new ca_detail. - """ - - rpki.log.trace() - - parent = self.parent() - old_detail = self.fetch_active() - new_detail = ca_detail_obj.create(self) - - # This will need a callback when we go event-driven - issue_response = rpki.up_down.issue_pdu.query(parent, self, new_detail) - - new_detail.activate( - ca = self, - cert = issue_response.payload.classes[0].certs[0].cert, - uri = issue_response.payload.classes[0].certs[0].cert_url, - predecessor = old_detail) - - def revoke(self): - """Revoke deprecated ca_detail objects associated with this ca.""" - - rpki.log.trace() - - for ca_detail in self.fetch_deprecated(): - ca_detail.revoke() - -class ca_detail_obj(rpki.sql.sql_persistant): - """Internal CA detail object.""" - - sql_template = rpki.sql.template( - "ca_detail", - "ca_detail_id", - ("private_key_id", rpki.x509.RSA), - ("public_key", rpki.x509.RSApublic), - ("latest_ca_cert", rpki.x509.X509), - ("manifest_private_key_id", rpki.x509.RSA), - ("manifest_public_key", rpki.x509.RSApublic), - ("latest_manifest_cert", rpki.x509.X509), - ("latest_manifest", rpki.x509.SignedManifest), - ("latest_crl", rpki.x509.CRL), - "state", - "ca_cert_uri", - "ca_id") - - def sql_decode(self, vals): - """Extra assertions for SQL decode of a ca_detail_obj.""" - rpki.sql.sql_persistant.sql_decode(self, vals) - assert (self.public_key is None and 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 and self.manifest_private_key_id is None) or \ - self.manifest_public_key.get_DER() == self.manifest_private_key_id.get_public_DER() - - def ca(self): - """Fetch CA object to which this ca_detail links.""" - return ca_obj.sql_fetch(self.gctx, self.ca_id) - - def child_certs(self, child = None, ski = None, unique = False): - """Fetch all child_cert objects that link to this ca_detail.""" - return rpki.rpki_engine.child_cert_obj.fetch(self.gctx, child, self, ski, unique) - - def revoked_certs(self): - """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,)) - - def route_origins(self): - """Fetch all route_origin objects that link to this ca_detail.""" - return rpki.left_right.route_origin_elt.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,)) - - def crl_uri(self, ca): - """Return publication URI for this ca_detail's CRL.""" - return ca.sia_uri + self.crl_uri_tail() - - def crl_uri_tail(self): - """Return tail (filename portion) of publication URI for this ca_detail's CRL.""" - return self.public_key.gSKI() + ".crl" - - def manifest_uri(self, ca): - """Return publication URI for this ca_detail's manifest.""" - return ca.sia_uri + self.public_key.gSKI() + ".mnf" - - def activate(self, ca, cert, uri, predecessor = None): - """Activate this ca_detail.""" - - self.latest_ca_cert = cert - self.ca_cert_uri = uri.rsync() - self.generate_manifest_cert(ca) - self.generate_crl() - self.generate_manifest() - self.state = "active" - self.sql_mark_dirty() - - if predecessor is not None: - predecessor.state = "deprecated" - predecessor.sql_mark_dirty() - for child_cert in predecessor.child_certs(): - child_cert.reissue(self) - for route_origin in predecessor.route_origins(): - route_origin.regenerate_roa() - - def delete(self, ca, repository): - """Delete this ca_detail and all of the certs it issued.""" - - for child_cert in self.child_certs(): - repository.withdraw(child_cert.cert, child_cert.uri(ca)) - child_cert.sql_delete() - for revoked_cert in self.revoked_certs(): - revoked_cert.sql_delete() - for route_origin in self.route_origins(): - route_origin.withdraw_roa() - repository.withdraw(self.latest_manifest, self.manifest_uri(ca)) - repository.withdraw(self.latest_crl, self.crl_uri(ca)) - self.sql_delete() - - def revoke(self): - """Request revocation of all certificates whose SKI matches the key for this ca_detail. - - Tasks: - - - Request revocation of old keypair by parent. - - - Revoke all child certs issued by the old keypair. - - - Generate a final CRL, signed with the old keypair, listing all - the revoked certs, with a next CRL time after the last cert or - CRL signed by the old keypair will have expired. - - - Destroy old keypair (and manifest keypair). - - - Leave final CRL in place until its next CRL time has passed. - """ - - # This will need a callback when we go event-driven - r_msg = rpki.up_down.revoke_pdu.query(self) - - if r_msg.payload.ski != self.latest_ca_cert.gSKI(): - raise rpki.exceptions.SKIMismatch - - ca = self.ca() - parent = ca.parent() - crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval) - - nextUpdate = rpki.sundial.now() - - if self.latest_manifest is not None: - nextUpdate = nextUpdate.later(self.latest_manifest.getNextUpdate()) - - if self.latest_crl is not None: - nextUpdate = nextUpdate.later(self.latest_crl.getNextUpdate()) - - for child_cert in self.child_certs(): - nextUpdate = nextUpdate.later(child_cert.cert.getNotAfter()) - child_cert.revoke() - - nextUpdate += crl_interval - - self.generate_crl(nextUpdate) - self.generate_manifest(nextUpdate) - - self.private_key_id = None - self.manifest_private_key_id = None - self.manifest_public_key = None - self.latest_manifest_cert = None - self.state = "revoked" - self.sql_mark_dirty() - - def update(self, parent, ca, rc, sia_uri_changed, old_resources): - """Need to get a new certificate for this ca_detail and perhaps - frob children of this ca_detail. - """ - - # This will need a callback when we go event-driven - issue_response = rpki.up_down.issue_pdu.query(parent, ca, self) - - self.latest_ca_cert = issue_response.payload.classes[0].certs[0].cert - new_resources = self.latest_ca_cert.get_3779resources() - - if sia_uri_changed or old_resources.oversized(new_resources): - for child_cert in self.child_certs(): - child_resources = child_cert.cert.get_3779resources() - if sia_uri_changed or child_resources.oversized(new_resources): - child_cert.reissue( - ca_detail = self, - resources = child_resources.intersection(new_resources)) - - @classmethod - def create(cls, ca): - """Create a new ca_detail object for a specified CA.""" - self = cls() - self.gctx = ca.gctx - self.ca_id = ca.ca_id - self.state = "pending" - - self.private_key_id = rpki.x509.RSA.generate() - self.public_key = self.private_key_id.get_RSApublic() - - self.manifest_private_key_id = rpki.x509.RSA.generate() - self.manifest_public_key = self.manifest_private_key_id.get_RSApublic() - - self.sql_store() - return self - - def issue_ee(self, ca, resources, subject_key, sia = None): - """Issue a new EE certificate.""" - - return self.latest_ca_cert.issue( - keypair = self.private_key_id, - subject_key = subject_key, - serial = ca.next_serial_number(), - sia = sia, - aia = self.ca_cert_uri, - crldp = self.crl_uri(ca), - resources = resources, - notAfter = self.latest_ca_cert.getNotAfter(), - is_ca = False) - - - def generate_manifest_cert(self, ca): - """Generate a new manifest certificate for this ca_detail.""" - - resources = rpki.resource_set.resource_bag( - asn = rpki.resource_set.resource_set_as("<inherit>"), - v4 = rpki.resource_set.resource_set_ipv4("<inherit>"), - v6 = rpki.resource_set.resource_set_ipv6("<inherit>")) - - self.latest_manifest_cert = self.issue_ee(ca, resources, self.manifest_public_key) - - def issue(self, ca, child, subject_key, sia, resources, child_cert = None): - """Issue a new certificate to a child. Optional child_cert - argument specifies an existing child_cert object to update in - place; if not specified, we create a new one. Returns the - child_cert object containing the newly issued cert. - """ - - assert child_cert is None or (child_cert.child_id == child.child_id and - child_cert.ca_detail_id == self.ca_detail_id) - - cert = self.latest_ca_cert.issue( - keypair = self.private_key_id, - subject_key = subject_key, - serial = ca.next_serial_number(), - aia = self.ca_cert_uri, - crldp = self.crl_uri(ca), - sia = sia, - resources = resources, - notAfter = resources.valid_until) - - if child_cert is None: - child_cert = rpki.rpki_engine.child_cert_obj( - gctx = child.gctx, - child_id = child.child_id, - ca_detail_id = self.ca_detail_id, - cert = cert) - rpki.log.debug("Created new child_cert %s" % repr(child_cert)) - else: - child_cert.cert = cert - rpki.log.debug("Reusing existing child_cert %s" % repr(child_cert)) - - child_cert.ski = cert.get_SKI() - - child_cert.sql_store() - - ca.parent().repository().publish(child_cert.cert, child_cert.uri(ca)) - - self.generate_manifest() - - return child_cert - - def generate_crl(self, nextUpdate = None): - """Generate a new CRL for this ca_detail. At the moment this is - unconditional, that is, it is up to the caller to decide whether a - new CRL is needed. - """ - - ca = self.ca() - parent = ca.parent() - repository = parent.repository() - crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval) - now = rpki.sundial.now() - - if nextUpdate is None: - nextUpdate = now + crl_interval - - certlist = [] - for revoked_cert in self.revoked_certs(): - if now > revoked_cert.expires + crl_interval: - revoked_cert.sql_delete() - else: - certlist.append((revoked_cert.serial, revoked_cert.revoked.toASN1tuple(), ())) - certlist.sort() - - self.latest_crl = rpki.x509.CRL.generate( - keypair = self.private_key_id, - issuer = self.latest_ca_cert, - serial = ca.next_crl_number(), - thisUpdate = now, - nextUpdate = nextUpdate, - revokedCertificates = certlist) - - repository.publish(self.latest_crl, self.crl_uri(ca)) - - def generate_manifest(self, nextUpdate = None): - """Generate a new manifest for this ca_detail.""" - - ca = self.ca() - parent = ca.parent() - repository = parent.repository() - crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval) - now = rpki.sundial.now() - - if nextUpdate is None: - nextUpdate = now + crl_interval - - route_origins = [r for r in self.route_origins() if r.cert is not None and r.roa is not None] - - if self.latest_manifest_cert is None or self.latest_manifest_cert.getNotAfter() < nextUpdate: - self.generate_manifest_cert(ca) - - certs = [(c.uri_tail(), c.cert) for c in self.child_certs()] + \ - [(r.roa_uri_tail(), r.roa) for r in route_origins] + \ - [(r.ee_uri_tail(), r.cert) for r in route_origins] + \ - [(self.crl_uri_tail(), self.latest_crl)] - - self.latest_manifest = rpki.x509.SignedManifest.build( - serial = ca.next_manifest_number(), - thisUpdate = now, - nextUpdate = nextUpdate, - names_and_objs = certs, - keypair = self.manifest_private_key_id, - certs = self.latest_manifest_cert) - - repository.publish(self.latest_manifest, self.manifest_uri(ca)) - -class child_cert_obj(rpki.sql.sql_persistant): - """Certificate that has been issued to a child.""" - - sql_template = rpki.sql.template( - "child_cert", - "child_cert_id", - ("cert", rpki.x509.X509), - "child_id", - "ca_detail_id", - "ski") - - def __init__(self, gctx = None, child_id = None, ca_detail_id = None, cert = None): - """Initialize a child_cert_obj.""" - self.gctx = gctx - self.child_id = child_id - self.ca_detail_id = ca_detail_id - self.cert = cert - if child_id or ca_detail_id or cert: - self.sql_mark_dirty() - - def child(self): - """Fetch child object to which this child_cert object links.""" - return rpki.left_right.child_elt.sql_fetch(self.gctx, self.child_id) - - def ca_detail(self): - """Fetch ca_detail object to which this child_cert object links.""" - return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id) - - def uri_tail(self): - """Return the tail (filename) portion of the URI for this child_cert.""" - return self.cert.gSKI() + ".cer" - - def uri(self, ca): - """Return the publication URI for this child_cert.""" - return ca.sia_uri + self.uri_tail() - - def revoke(self): - """Revoke a child cert.""" - rpki.log.debug("Revoking %s" % repr(self)) - ca_detail = self.ca_detail() - ca = ca_detail.ca() - revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail) - repository = ca.parent().repository() - repository.withdraw(self.cert, self.uri(ca)) - self.gctx.sql.sweep() - self.sql_delete() - - def reissue(self, ca_detail, resources = None, sia = None): - """Reissue an existing cert, reusing the public key. If the cert - we would generate is identical to the one we already have, we just - return the one we already have. If we have to revoke the old - certificate when generating the new one, we have to generate a new - child_cert_obj, so calling code that needs the updated - child_cert_obj must use the return value from this method. - """ - - ca = ca_detail.ca() - child = self.child() - - old_resources = self.cert.get_3779resources() - old_sia = self.cert.get_SIA() - old_ca_detail = self.ca_detail() - - if resources is None: - resources = old_resources - - if sia is None: - sia = old_sia - - assert resources.valid_until is not None and old_resources.valid_until is not None - - if resources == old_resources and sia == old_sia and ca_detail == old_ca_detail: - return self - - must_revoke = old_resources.oversized(resources) or old_resources.valid_until > resources.valid_until - new_issuer = ca_detail != old_ca_detail - - if resources.valid_until != old_resources.valid_until: - rpki.log.debug("Validity changed: %s %s" % ( old_resources.valid_until, resources.valid_until)) - - if must_revoke or new_issuer: - child_cert = None - else: - child_cert = self - - child_cert = ca_detail.issue( - ca = ca, - child = child, - subject_key = self.cert.getPublicKey(), - sia = sia, - resources = resources, - child_cert = child_cert) - - if must_revoke: - for cert in child.child_certs(ca_detail = ca_detail, ski = self.ski): - if cert is not child_cert: - cert.revoke() - - return child_cert - - @classmethod - def fetch(cls, gctx = None, child = None, ca_detail = None, ski = None, unique = False): - """Fetch all child_cert objects matching a particular set of - parameters. This is a wrapper to consolidate various queries that - would otherwise be inline SQL WHERE expressions. In most cases - code calls this indirectly, through methods in other classes. - """ - - args = [] - where = [] - - if child: - where.append("child_id = %s") - args.append(child.child_id) - - if ca_detail: - where.append("ca_detail_id = %s") - args.append(ca_detail.ca_detail_id) - - if ski: - where.append("ski = %s") - args.append(ski) - - where = " AND ".join(where) - - gctx = gctx or (child and child.gctx) or (ca_detail and ca_detail.gctx) or None - - if unique: - return cls.sql_fetch_where1(gctx, where, args) - else: - return cls.sql_fetch_where(gctx, where, args) - -class revoked_cert_obj(rpki.sql.sql_persistant): - """Tombstone for a revoked certificate.""" - - sql_template = rpki.sql.template( - "revoked_cert", - "revoked_cert_id", - "serial", - "ca_detail_id", - ("revoked", rpki.sundial.datetime), - ("expires", rpki.sundial.datetime)) - - def __init__(self, gctx = None, serial = None, revoked = None, expires = None, ca_detail_id = None): - """Initialize a revoked_cert_obj.""" - self.gctx = gctx - self.serial = serial - self.revoked = revoked - self.expires = expires - self.ca_detail_id = ca_detail_id - if serial or revoked or expires or ca_detail_id: - self.sql_mark_dirty() - - def ca_detail(self): - """Fetch ca_detail object to which this revoked_cert_obj links.""" - return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id) - - @classmethod - def revoke(cls, cert, ca_detail): - """Revoke a certificate.""" - return cls( - serial = cert.getSerial(), - expires = cert.getNotAfter(), - revoked = rpki.sundial.now(), - gctx = ca_detail.gctx, - ca_detail_id = ca_detail.ca_detail_id) diff --git a/rpkid.stable/rpki/sql.py b/rpkid.stable/rpki/sql.py deleted file mode 100644 index e9284539..00000000 --- a/rpkid.stable/rpki/sql.py +++ /dev/null @@ -1,295 +0,0 @@ -"""SQL interface code. - -$Id$ - -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. -""" - -import MySQLdb, time, warnings, _mysql_exceptions -import rpki.x509, rpki.resource_set, rpki.sundial, rpki.log - -class session(object): - """SQL session layer.""" - - _exceptions_enabled = False - - def __init__(self, cfg): - - if not self._exceptions_enabled: - warnings.simplefilter("error", _mysql_exceptions.Warning) - self.__class__._exceptions_enabled = True - - self.username = cfg.get("sql-username") - self.database = cfg.get("sql-database") - self.password = cfg.get("sql-password") - - self.cache = {} - self.dirty = set() - - self.connect() - - def connect(self): - self.db = MySQLdb.connect(user = self.username, db = self.database, passwd = self.password) - self.cur = self.db.cursor() - - def close(self): - if self.cur: - self.cur.close() - self.cur = None - if self.db: - self.db.close() - self.db = None - - def ping(self): - return self.db.ping(True) - - def _wrap_execute(self, func, query, args): - try: - return func(query, args) - except _mysql_exceptions.MySQLError: - if self.dirty: - rpki.log.warn("MySQL exception with dirty objects in SQL cache!") - raise - - def execute(self, query, args = None): - return self._wrap_execute(self.cur.execute, query, args) - - def executemany(self, query, args): - return self._wrap_execute(self.cur.executemany, query, args) - - def fetchall(self): - return self.cur.fetchall() - - def lastrowid(self): - return self.cur.lastrowid - - def cache_clear(self): - """Clear the object cache.""" - self.cache.clear() - - def assert_pristine(self): - """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(): - rpki.log.debug("Sweeping %s" % repr(s)) - if s.sql_deleted: - s.sql_delete() - else: - s.sql_store() - self.assert_pristine() - -class template(object): - """SQL template generator.""" - def __init__(self, table_name, index_column, *data_columns): - """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 - self.table = table_name - self.index = index_column - self.columns = columns - self.map = type_map - self.select = "SELECT %s FROM %s" % (", ".join(columns), table_name) - self.insert = "INSERT %s (%s) VALUES (%s)" % (table_name, ", ".join(data_columns), - ", ".join("%(" + s + ")s" for s in data_columns)) - self.update = "UPDATE %s SET %s WHERE %s = %%(%s)s" % \ - (table_name, ", ".join(s + " = %(" + s + ")s" for s in data_columns), - index_column, index_column) - self.delete = "DELETE FROM %s WHERE %s = %%s" % (table_name, index_column) - -class sql_persistant(object): - """Mixin for persistant class that needs to be stored in SQL. - """ - - ## @var sql_in_db - # Whether this object is already in SQL or not. - - sql_in_db = False - - ## @var sql_deleted - # Whether our cached copy of this object has been deleted. - - sql_deleted = False - - ## @var sql_debug - # Enable logging of SQL actions - - sql_debug = False - - @classmethod - def sql_fetch(cls, gctx, id): - """Fetch one object from SQL, based on its primary key. - - Since in this one case we know that the primary index is also the - cache key, we check for a cache hit directly in the hope of - bypassing the SQL lookup entirely. - - This method is usually called via a one-line class-specific - wrapper. As a convenience, we also accept an id of None, and just - return None in this case. - """ - - if id is None: - return None - assert isinstance(id, (int, long)), "id should be an integer, was %s" % repr(type(id)) - key = (cls, id) - if key in gctx.sql.cache: - return gctx.sql.cache[key] - else: - return cls.sql_fetch_where1(gctx, "%s = %%s" % cls.sql_template.index, (id,)) - - @classmethod - def sql_fetch_where1(cls, gctx, where, args = None): - """Fetch one object from SQL, based on an arbitrary SQL WHERE expression.""" - results = cls.sql_fetch_where(gctx, where, args) - if len(results) == 0: - return None - elif len(results) == 1: - return results[0] - else: - raise rpki.exceptions.DBConsistancyError, \ - "Database contained multiple matches for %s where %s" % \ - (cls.__name__, where % tuple(repr(a) for a in args)) - - @classmethod - def sql_fetch_all(cls, gctx): - """Fetch all objects of this type from SQL.""" - return cls.sql_fetch_where(gctx, None) - - @classmethod - def sql_fetch_where(cls, gctx, where, args = None): - """Fetch objects of this type matching an arbitrary SQL WHERE expression.""" - if where is None: - assert args is None - if cls.sql_debug: - rpki.log.debug("sql_fetch_where(%s)" % repr(cls.sql_template.select)) - gctx.sql.execute(cls.sql_template.select) - else: - query = cls.sql_template.select + " WHERE " + where - if cls.sql_debug: - rpki.log.debug("sql_fetch_where(%s, %s)" % (repr(query), repr(args))) - gctx.sql.execute(query, args) - results = [] - for row in gctx.sql.fetchall(): - key = (cls, row[0]) - if key in gctx.sql.cache: - results.append(gctx.sql.cache[key]) - else: - results.append(cls.sql_init(gctx, row, key)) - return results - - @classmethod - def sql_init(cls, gctx, row, key): - """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))) - gctx.sql.cache[key] = self - self.sql_in_db = True - self.sql_fetch_hook() - return self - - def sql_mark_dirty(self): - """Mark this object as needing to be written back to SQL.""" - self.gctx.sql.dirty.add(self) - - def sql_mark_clean(self): - """Mark this object as not needing to be written back to SQL.""" - self.gctx.sql.dirty.discard(self) - - def sql_is_dirty(self): - """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 - - def sql_store(self): - """Store this object to SQL.""" - args = self.sql_encode() - if not self.sql_in_db: - if self.sql_debug: - rpki.log.debug("sql_fetch_store(%s, %s)" % (repr(self.sql_template.insert), repr(args))) - self.gctx.sql.execute(self.sql_template.insert, args) - setattr(self, self.sql_template.index, self.gctx.sql.lastrowid()) - self.gctx.sql.cache[(self.__class__, self.gctx.sql.lastrowid())] = self - self.sql_insert_hook() - else: - if self.sql_debug: - rpki.log.debug("sql_fetch_store(%s, %s)" % (repr(self.sql_template.update), repr(args))) - self.gctx.sql.execute(self.sql_template.update, args) - self.sql_update_hook() - key = (self.__class__, getattr(self, self.sql_template.index)) - assert key in self.gctx.sql.cache and self.gctx.sql.cache[key] == self - self.sql_mark_clean() - self.sql_in_db = True - - def sql_delete(self): - """Delete this object from SQL.""" - if self.sql_in_db: - id = getattr(self, self.sql_template.index) - self.gctx.sql.execute(self.sql_template.delete, id) - self.sql_delete_hook() - key = (self.__class__, id) - if self.gctx.sql.cache.get(key) == self: - del self.gctx.sql.cache[key] - self.sql_in_db = False - self.sql_mark_clean() - - def sql_encode(self): - """Convert object attributes into a dict for use with canned SQL - queries. This is a default version that assumes a one-to-one - 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: - d[i] = self.sql_template.map[i].to_sql(d[i]) - return d - - def sql_decode(self, vals): - """Initialize an object with values returned by self.sql_fetch(). - This is a default version that assumes a one-to-one mapping - 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])) - else: - setattr(self, a, vals[a]) - - def sql_fetch_hook(self): - """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() - - def sql_delete_hook(self): - """Customization hook.""" - pass - diff --git a/rpkid.stable/rpki/sundial.py b/rpkid.stable/rpki/sundial.py deleted file mode 100644 index 1d7ff8bf..00000000 --- a/rpkid.stable/rpki/sundial.py +++ /dev/null @@ -1,198 +0,0 @@ -"""Unified RPKI date/time handling, based on the standard Python datetime module. - -Module name chosen to sidestep a nightmare of import-related errors -that occur with the more obvious module names. - -$Id$ - -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. -""" - -import datetime as pydatetime -import re - -def now(): - """Get current timestamp.""" - return datetime.utcnow() - -class datetime(pydatetime.datetime): - """RPKI extensions to standard datetime.datetime class. All work - here is in UTC, so we use naive datetime objects. - """ - - def totimestamp(self): - """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 - def fromUTCTime(cls, x): - """Convert from ASN.1 UTCTime.""" - return cls.strptime(x, "%y%m%d%H%M%SZ") - - def toUTCTime(self): - """Convert to ASN.1 UTCTime.""" - return self.strftime("%y%m%d%H%M%SZ") - - @classmethod - def fromGeneralizedTime(cls, x): - """Convert from ASN.1 GeneralizedTime.""" - return cls.strptime(x, "%Y%m%d%H%M%SZ") - - def toGeneralizedTime(self): - """Convert to ASN.1 GeneralizedTime.""" - return self.strftime("%Y%m%d%H%M%SZ") - - @classmethod - def fromASN1tuple(cls, x): - """Convert from ASN.1 tuple representation.""" - assert isinstance(x, tuple) and len(x) == 2 and x[0] in ("utcTime", "generalTime") - if x[0] == "utcTime": - return cls.fromUTCTime(x[1]) - else: - return cls.fromGeneralizedTime(x[1]) - - ## @var PKIX_threshhold - # Threshold specified in RFC 3280 for switchover from UTCTime to GeneralizedTime. - - PKIX_threshhold = pydatetime.datetime(2050, 1, 1) - - def toASN1tuple(self): - """Convert to ASN.1 tuple representation.""" - if self < self.PKIX_threshhold: - return "utcTime", self.toUTCTime() - else: - return "generalTime", self.toGeneralizedTime() - - @classmethod - def fromXMLtime(cls, x): - """Convert from XML time representation.""" - if x is None: - return None - else: - return cls.strptime(x, "%Y-%m-%dT%H:%M:%SZ") - - def toXMLtime(self): - """Convert to XML time representation.""" - return self.strftime("%Y-%m-%dT%H:%M:%SZ") - - def __str__(self): - return self.toXMLtime() - - @classmethod - def fromdatetime(cls, x): - """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 __add__(self, other): - """Force correct class for timedelta results.""" - return self.fromdatetime(pydatetime.datetime.__add__(self, other)) - - def __sub__(self, other): - """Force correct class for timedelta results.""" - return self.fromdatetime(pydatetime.datetime.__sub__(self, other)) - - @classmethod - def from_sql(cls, x): - """Convert from SQL storage format.""" - return cls.fromdatetime(x) - - def to_sql(self): - """Convert to SQL storage format. - - There's something whacky going on in the MySQLdb module, it throws - range errors when storing a derived type into a DATETIME column. - Investigate some day, but for now brute force this by copying the - relevant fields into a datetime.datetime for MySQLdb's - consumption. - - """ - 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) - - 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 - -class timedelta(pydatetime.timedelta): - """Timedelta with text parsing. This accepts two input formats: - - - A simple integer, indicating a number of seconds. - - - A string of the form "wD xH yM zS" where w, x, y, and z are integers - and D, H, M, and S indicate days, hours, minutes, and seconds. - All of the fields are optional, but at least one must be specified. - Eg, "3D4H" means "three days plus four hours". - """ - - ## @var regexp - # Hideously ugly regular expression to parse the complex text form. - # Tags are intended for use with re.MatchObject.groupdict() and map - # directly to the keywords expected by the timedelta constructor. - - regexp = re.compile("\\s*".join(("^", - "(?:(?P<days>\\d+)D)?", - "(?:(?P<hours>\\d+)H)?", - "(?:(?P<minutes>\\d+)M)?", - "(?:(?P<seconds>\\d+)S)?", - "$")), - re.I) - - @classmethod - def parse(cls, arg): - """Parse text into a timedelta object.""" - if not isinstance(arg, str): - return cls(seconds = arg) - elif arg.isdigit(): - return cls(seconds = int(arg)) - else: - match = cls.regexp.match(arg) - if match: - return cls(**dict((k, int(v)) for (k, v) in match.groupdict().items() if v is not None)) - else: - raise RuntimeError, "Couldn't parse timedelta %s" % repr(arg) - - - def convert_to_seconds(self): - """Convert a timedelta interval to seconds.""" - return self.days * 24 * 60 * 60 + self.seconds - -if __name__ == "__main__": - - def test(t): - print - print "str: ", t - print "repr: ", repr(t) - print "seconds since epoch:", t.strftime("%s") - print "UTCTime: ", t.toUTCTime() - print "GeneralizedTime: ", t.toGeneralizedTime() - print "ASN1tuple: ", t.toASN1tuple() - print "XMLtime: ", t.toXMLtime() - print - - print - print "Testing time conversion routines" - test(now()) - test(now() + timedelta(days = 30)) - test(now() + timedelta.parse("3d5s")) - timedelta.parse(" 3d 5s ") diff --git a/rpkid.stable/rpki/up_down.py b/rpkid.stable/rpki/up_down.py deleted file mode 100644 index 30085390..00000000 --- a/rpkid.stable/rpki/up_down.py +++ /dev/null @@ -1,535 +0,0 @@ -"""RPKI "up-down" protocol. - -$Id$ - -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. -""" - -import base64, lxml.etree, time -import rpki.resource_set, rpki.x509, rpki.exceptions -import rpki.xml_utils, rpki.relaxng - -xmlns="http://www.apnic.net/specs/rescerts/up-down/" - -nsmap = { None : xmlns } - -class base_elt(object): - """Generic PDU object. - - Virtual class, just provides some default methods. - """ - - def startElement(self, stack, name, attrs): - """Ignore startElement() if there's no specific handler. - - Some elements have no attributes and we only care about their - text content. - """ - pass - - def endElement(self, stack, name, text): - """Ignore endElement() if there's no specific handler. - - 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("{%s}%s" % (xmlns, name), nsmap=nsmap) - for key in attrs: - val = getattr(self, key, None) - if val is not None: - elt.set(key, str(val)) - return elt - - def make_b64elt(self, elt, name, value=None): - """Construct a sub-element with Base64 text content.""" - if value is None: - value = getattr(self, name, None) - if value is not None: - lxml.etree.SubElement(elt, "{%s}%s" % (xmlns, name), nsmap=nsmap).text = base64.b64encode(value) - - def serve_pdu(self, q_msg, r_msg, child): - """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): - """Container for a set of URIs.""" - - def __init__(self, ini): - """Initialize a set of URIs, which includes basic some syntax checking.""" - if isinstance(ini, (list, tuple)): - self[:] = ini - elif isinstance(ini, str): - self[:] = ini.split(",") - for s in self: - if s.strip() != s or s.find("://") < 0: - raise rpki.exceptions.BadURISyntax, "Bad URI \"%s\"" % s - else: - raise TypeError - - def __str__(self): - """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 - return None - -class certificate_elt(base_elt): - """Up-Down protocol representation of an issued certificate.""" - - def startElement(self, stack, name, attrs): - """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")) - self.req_resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs.get("req_resource_set_ipv4")) - self.req_resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs.get("req_resource_set_ipv6")) - - def endElement(self, stack, name, text): - """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() - - def toXML(self): - """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() - return elt - -class class_elt(base_elt): - """Up-Down protocol representation of a resource class.""" - - issuer = None - - def __init__(self): - """Initialize class_elt.""" - self.certs = [] - - def startElement(self, stack, name, attrs): - """Handle <class/> elements and their children.""" - if name == "certificate": - cert = certificate_elt() - self.certs.append(cert) - stack.append(cert) - cert.startElement(stack, name, attrs) - elif name != "issuer": - assert name == "class", "Unexpected name %s, stack %s" % (name, stack) - self.class_name = attrs["class_name"] - self.cert_url = multi_uri(attrs["cert_url"]) - self.suggested_sia_head = attrs.get("suggested_sia_head") - self.resource_set_as = rpki.resource_set.resource_set_as(attrs["resource_set_as"]) - self.resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs["resource_set_ipv4"]) - self.resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs["resource_set_ipv6"]) - self.resource_set_notafter = rpki.sundial.datetime.fromXMLtime(attrs.get("resource_set_notafter")) - - def endElement(self, stack, name, text): - """Handle <class/> elements and their children.""" - if name == "issuer": - self.issuer = rpki.x509.X509(Base64=text) - else: - assert name == "class", "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - - def toXML(self): - """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]) - if self.issuer is not None: - self.make_b64elt(elt, "issuer", self.issuer.get_DER()) - return elt - - def to_resource_bag(self): - """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, - self.resource_set_notafter) - - def from_resource_bag(self, bag): - """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 - self.resource_set_notafter = bag.valid_until - -class list_pdu(base_elt): - """Up-Down protocol "list" PDU.""" - - def toXML(self): - """Generate (empty) payload of "list" PDU.""" - return [] - - def serve_pdu(self, q_msg, r_msg, child): - """Serve one "list" PDU.""" - r_msg.payload = list_response_pdu() - - # This will require a callback when we go event-driven - irdb_resources = self.gctx.irdb_query(child.self_id, child.child_id) - - for parent in child.parents(): - for ca in parent.cas(): - ca_detail = ca.fetch_active() - if not ca_detail: - continue - resources = ca_detail.latest_ca_cert.get_3779resources().intersection(irdb_resources) - if resources.empty(): - continue - rc = class_elt() - rc.class_name = str(ca.ca_id) - rc.cert_url = multi_uri(ca_detail.ca_cert_uri) - rc.from_resource_bag(resources) - for child_cert in child.child_certs(ca_detail = ca_detail): - c = certificate_elt() - c.cert_url = multi_uri(child_cert.uri(ca)) - c.cert = child_cert.cert - rc.certs.append(c) - rc.issuer = ca_detail.latest_ca_cert - r_msg.payload.classes.append(rc) - - @classmethod - def query(cls, parent): - """Send a "list" query to parent.""" - return parent.query_up_down(cls()) - -class class_response_syntax(base_elt): - """Syntax for Up-Down protocol "list_response" and "issue_response" PDUs.""" - - def __init__(self): - """Initialize class_response_syntax.""" - self.classes = [] - - def startElement(self, stack, name, attrs): - """Handle "list_response" and "issue_response" PDUs.""" - assert name == "class", "Unexpected name %s, stack %s" % (name, stack) - c = class_elt() - self.classes.append(c) - stack.append(c) - c.startElement(stack, name, attrs) - - def toXML(self): - """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): - """Up-Down protocol "issue" PDU.""" - - def startElement(self, stack, name, attrs): - """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")) - self.req_resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs.get("req_resource_set_ipv4")) - self.req_resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs.get("req_resource_set_ipv6")) - - def endElement(self, stack, name, text): - """Handle "issue" PDU.""" - assert name == "request", "Unexpected name %s, stack %s" % (name, stack) - self.pkcs10 = rpki.x509.PKCS10(Base64=text) - stack.pop() - - def toXML(self): - """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() - return [elt] - - def serve_pdu(self, q_msg, r_msg, child): - """Serve one issue request PDU.""" - - # Subsetting not yet implemented, this is the one place where we - # have to handle it, by reporting that we're lame. - - if self.req_resource_set_as or \ - self.req_resource_set_ipv4 or \ - self.req_resource_set_ipv6: - raise rpki.exceptions.NotImplementedYet, "req_* attributes not implemented yet, sorry" - - # Check the request - self.pkcs10.check_valid_rpki() - ca = child.ca_from_class_name(self.class_name) - ca_detail = ca.fetch_active() - if ca_detail is None: - raise rpki.exceptions.NoActiveCA, "No active CA for class %s" % repr(self.class_name) - - # Check current cert, if any - - # This will require a callback when we go event-driven - irdb_resources = self.gctx.irdb_query(child.self_id, child.child_id) - - resources = irdb_resources.intersection(ca_detail.latest_ca_cert.get_3779resources()) - req_key = self.pkcs10.getPublicKey() - req_sia = self.pkcs10.get_SIA() - child_cert = child.child_certs(ca_detail = ca_detail, ski = req_key.get_SKI(), unique = True) - - # Generate new cert or regenerate old one if necessary - - if child_cert is None: - child_cert = ca_detail.issue( - ca = ca, - child = child, - subject_key = req_key, - sia = req_sia, - resources = resources) - else: - child_cert = child_cert.reissue( - ca_detail = ca_detail, - sia = req_sia, - resources = resources) - - # Save anything we modified and generate response - self.gctx.sql.sweep() - assert child_cert and child_cert.sql_in_db - c = certificate_elt() - c.cert_url = multi_uri(child_cert.uri(ca)) - c.cert = child_cert.cert - rc = class_elt() - rc.class_name = self.class_name - rc.cert_url = multi_uri(ca_detail.ca_cert_uri) - rc.from_resource_bag(resources) - rc.certs.append(c) - rc.issuer = ca_detail.latest_ca_cert - r_msg.payload = issue_response_pdu() - r_msg.payload.classes.append(rc) - - @classmethod - def query(cls, parent, ca, ca_detail): - """Send an "issue" request to parent associated with ca.""" - assert ca_detail is not None and ca_detail.state in ("pending", "active") - sia = ((rpki.oids.name2oid["id-ad-caRepository"], ("uri", ca.sia_uri)), - (rpki.oids.name2oid["id-ad-rpkiManifest"], ("uri", ca_detail.manifest_uri(ca)))) - self = cls() - self.class_name = ca.parent_resource_class - self.pkcs10 = rpki.x509.PKCS10.create_ca(ca_detail.private_key_id, sia) - return parent.query_up_down(self) - -class issue_response_pdu(class_response_syntax): - """Up-Down protocol "issue_response" PDU.""" - - def check_response(self): - """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 - -class revoke_syntax(base_elt): - """Syntax for Up-Down protocol "revoke" and "revoke_response" PDUs.""" - - def startElement(self, stack, name, attrs): - """Handle "revoke" PDU.""" - self.class_name = attrs["class_name"] - self.ski = attrs["ski"] - - def toXML(self): - """Generate payload of "revoke" PDU.""" - return [self.make_elt("key", "class_name", "ski")] - -class revoke_pdu(revoke_syntax): - """Up-Down protocol "revoke" PDU.""" - - def get_SKI(self): - """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): - """Serve one revoke request PDU.""" - for ca_detail in child.ca_from_class_name(self.class_name).ca_details(): - for child_cert in child.child_certs(ca_detail = ca_detail, ski = self.get_SKI()): - child_cert.revoke() - self.gctx.sql.sweep() - r_msg.payload = revoke_response_pdu() - r_msg.payload.class_name = self.class_name - r_msg.payload.ski = self.ski - - @classmethod - def query(cls, ca_detail): - """Send a "revoke" request to parent associated with ca_detail.""" - ca = ca_detail.ca() - parent = ca.parent() - self = cls() - self.class_name = ca.parent_resource_class - self.ski = ca_detail.latest_ca_cert.gSKI() - return parent.query_up_down(self) - -class revoke_response_pdu(revoke_syntax): - """Up-Down protocol "revoke_response" PDU.""" - - pass - -class error_response_pdu(base_elt): - """Up-Down protocol "error_response" PDU.""" - - codes = { - 1101 : "Already processing request", - 1102 : "Version number error", - 1103 : "Unrecognised request type", - 1201 : "Request - no such resource class", - 1202 : "Request - no resources allocated in resource class", - 1203 : "Request - badly formed certificate request", - 1301 : "Revoke - no such resource class", - 1302 : "Revoke - no such key", - 2001 : "Internal Server Error - Request not performed" } - - exceptions = {} - - def __init__(self, exception = None): - """Initialize an error_response PDU from an exception object.""" - if exception is not None: - if exception in self.exceptions: - self.status = exceptions[exception] - else: - self.status = 2001 - self.description = str(exception) - - def endElement(self, stack, name, text): - """Handle "error_response" PDU.""" - if name == "status": - code = int(text) - if code not in self.codes: - raise rpki.exceptions.BadStatusCode, "%s is not a known status code" % code - self.status = code - elif name == "description": - self.description = text - else: - assert name == "message", "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - stack[-1].endElement(stack, name, text) - - def toXML(self): - """Generate payload of "error_response" PDU.""" - assert self.status in self.codes - elt = self.make_elt("status") - elt.text = str(self.status) - payload = [elt] - if self.description: - elt = self.make_elt("description") - elt.text = str(self.description) - elt.set("{http://www.w3.org/XML/1998/namespace}lang", "en-US") - payload.append(elt) - return payload - - def check_response(self): - """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): - """Up-Down protocol message wrapper PDU.""" - - 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 } - - type2name = dict((v,k) for k,v in name2type.items()) - - def toXML(self): - """Generate payload of message PDU.""" - elt = self.make_elt("message", "version", "sender", "recipient", "type") - elt.extend(self.payload.toXML()) - return elt - - def startElement(self, stack, name, attrs): - """Handle message PDU. - - Payload of the <message/> element varies depending on the "type" - 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"] - self.recipient = attrs["recipient"] - self.type = attrs["type"] - self.payload = self.name2type[attrs["type"]]() - stack.append(self.payload) - - def __str__(self): - """Convert a message PDU to a string.""" - lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "UTF-8") - - def serve_top_level(self, child): - """Serve one message request PDU.""" - r_msg = message_pdu() - r_msg.sender = self.recipient - r_msg.recipient = self.sender - self.payload.serve_pdu(self, r_msg, child) - r_msg.type = self.type2name[type(r_msg.payload)] - return r_msg - - 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 - r_msg.payload = error_response_pdu(exception) - r_msg.type = self.type2name[type(r_msg.payload)] - return r_msg - - @classmethod - def make_query(cls, payload, sender, recipient): - """Construct one message PDU.""" - assert not cls.type2name[type(payload)].endswith("_response") - if sender is None: - sender = "tweedledee" - if recipient is None: - recipient = "tweedledum" - self = cls() - self.sender = sender - self.recipient = recipient - self.payload = payload - self.type = self.type2name[type(payload)] - return self - -class sax_handler(rpki.xml_utils.sax_handler): - """SAX handler for Up-Down protocol.""" - - pdu = message_pdu - name = "message" - version = "1" - -class cms_msg(rpki.x509.XML_CMS_object): - """Class to hold a CMS-signed up-down PDU.""" - - encoding = "UTF-8" - schema = rpki.relaxng.up_down - saxify = sax_handler.saxify diff --git a/rpkid.stable/rpki/x509.py b/rpkid.stable/rpki/x509.py deleted file mode 100644 index b167560c..00000000 --- a/rpkid.stable/rpki/x509.py +++ /dev/null @@ -1,995 +0,0 @@ -"""One X.509 implementation to rule them all... - -...and in the darkness hide the twisty maze of partially overlapping -X.509 support packages in Python. - -There are several existing packages, none of which do quite what I -need, due to age, lack of documentation, specialization, or lack of -foresight on somebody's part (perhaps mine). This module attempts to -bring together the functionality I need in a way that hides at least -some of the nasty details. This involves a lot of format conversion. - -$Id$ - - -Copyright (C) 2009 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. - - -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. - -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. -""" - -import POW, tlslite.api, POW.pkix, base64, lxml.etree, os -import rpki.exceptions, rpki.resource_set, rpki.oids, rpki.sundial -import rpki.manifest, rpki.roa, rpki.log - -def calculate_SKI(public_key_der): - """Calculate the SKI value given the DER representation of a public - key, which requires first peeling the ASN.1 wrapper off the key. - """ - k = POW.pkix.SubjectPublicKeyInfo() - k.fromString(public_key_der) - d = POW.Digest(POW.SHA1_DIGEST) - d.update(k.subjectPublicKey.get()) - return d.digest() - -class PEM_converter(object): - """Convert between DER and PEM encodings for various kinds of ASN.1 data.""" - - def __init__(self, kind): # "CERTIFICATE", "RSA PRIVATE KEY", ... - """Initialize PEM_converter.""" - self.b = "-----BEGIN %s-----" % kind - self.e = "-----END %s-----" % kind - - def looks_like_PEM(self, text): - """Guess whether text looks like a PEM encoding.""" - b = text.find(self.b) - return b >= 0 and text.find(self.e) > b + len(self.b) - - def to_DER(self, pem): - """Convert from PEM to DER.""" - lines = [line.strip() for line in pem.splitlines(0)] - while lines and lines.pop(0) != self.b: - pass - while lines and lines.pop(-1) != self.e: - pass - if not lines: - raise rpki.exceptions.EmptyPEM, "Could not find PEM in:\n%s" % pem - return base64.b64decode("".join(lines)) - - def to_PEM(self, der): - """Convert from DER to PEM.""" - b64 = base64.b64encode(der) - pem = self.b + "\n" - while len(b64) > 64: - pem += b64[0:64] + "\n" - b64 = b64[64:] - return pem + b64 + "\n" + self.e + "\n" - -class DER_object(object): - """Virtual class to hold a generic DER object.""" - - ## Formats supported in this object - formats = ("DER",) - - ## PEM converter for this object - pem_converter = None - - ## Other attributes that self.clear() should whack - other_clear = () - - ## @var DER - ## DER value of this object - - def empty(self): - """Test whether this object is empty.""" - for a in self.formats: - if getattr(self, a, None) is not None: - return False - return True - - def clear(self): - """Make this object empty.""" - for a in self.formats + self.other_clear: - setattr(self, a, None) - - def __init__(self, **kw): - """Initialize a DER_object.""" - self.clear() - if len(kw): - self.set(**kw) - - def set(self, **kw): - """Set this object by setting one of its known formats. - - This method only allows one to set one format at a time. - Subsequent calls will clear the object first. The point of all - this is to let the object's internal converters handle mustering - the object into whatever format you need at the moment. - """ - if len(kw) == 1: - name = kw.keys()[0] - if name in self.formats: - self.clear() - setattr(self, name, kw[name]) - return - if name == "PEM": - self.clear() - self.DER = self.pem_converter.to_DER(kw[name]) - return - if name == "Base64": - self.clear() - self.DER = base64.b64decode(kw[name]) - return - if name in ("PEM_file", "DER_file", "Auto_file"): - f = open(kw[name], "rb") - value = f.read() - f.close() - if name == "PEM_file" or (name == "Auto_file" and self.pem_converter.looks_like_PEM(value)): - value = self.pem_converter.to_DER(value) - self.clear() - self.DER = value - return - raise rpki.exceptions.DERObjectConversionError, "Can't honor conversion request %s" % repr(kw) - - def get_DER(self): - """Get the DER value of this object. - - Subclasses will almost certainly override this method. - """ - assert not self.empty() - if self.DER: - return self.DER - raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available" - - def get_Base64(self): - """Get the Base64 encoding of the DER value of this object.""" - return base64.b64encode(self.get_DER()) - - def get_PEM(self): - """Get the PEM representation of this object.""" - return self.pem_converter.to_PEM(self.get_DER()) - - def __cmp__(self, other): - """Compare two DER-encoded objects.""" - return cmp(self.get_DER(), other.get_DER()) - - def hSKI(self): - """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 "" - - def gSKI(self): - """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): - """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 "" - - def gAKI(self): - """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. Only works for subclasses that support getExtension().""" - aki = (self.get_POWpkix().getExtension(rpki.oids.name2oid["authorityKeyIdentifier"]) or ((), 0, None))[2] - return aki[0] if isinstance(aki, tuple) else aki - - def get_SKI(self): - """Get the SKI extension from this object. Only works for subclasses that support getExtension().""" - return (self.get_POWpkix().getExtension(rpki.oids.name2oid["subjectKeyIdentifier"]) or ((), 0, None))[2] - - def get_SIA(self): - """Get the SIA extension from this object. Only works for subclasses that support getExtension().""" - return (self.get_POWpkix().getExtension(rpki.oids.name2oid["subjectInfoAccess"]) or ((), 0, None))[2] - - def get_AIA(self): - """Get the SIA extension from this object. Only works for subclasses that support getExtension().""" - return (self.get_POWpkix().getExtension(rpki.oids.name2oid["subjectInfoAccess"]) or ((), 0, None))[2] - - def get_basicConstraints(self): - """Get the basicConstraints extension from this object. Only works for subclasses that support getExtension().""" - return (self.get_POWpkix().getExtension(rpki.oids.name2oid["basicConstraints"]) or ((), 0, None))[2] - - def is_CA(self): - """Return True if and only if object has the basicConstraints extension and its cA value is true.""" - basicConstraints = self.get_basicConstraints() - return basicConstraints and basicConstraints[0] != 0 - - def get_3779resources(self): - """Get RFC 3779 resources as rpki.resource_set objects. - Only works for subclasses that support getExtensions(). - """ - resources = rpki.resource_set.resource_bag.from_rfc3779_tuples(self.get_POWpkix().getExtensions()) - try: - resources.valid_until = self.getNotAfter() - except AttributeError: - pass - return resources - - @classmethod - def from_sql(cls, x): - """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): - """Pretty print an ASN.1 DER object using cryptlib dumpasn1 tool. - Use a temporary file rather than popen4() because dumpasn1 uses - seek() when decoding ASN.1 content nested in OCTET STRING values. - """ - - ret = None - fn = "dumpasn1.tmp" - try: - f = open(fn, "wb") - f.write(self.get_DER()) - f.close() - f = os.popen("dumpasn1 2>&1 -a " + fn) - ret = "\n".join(x for x in f.read().splitlines() if x.startswith(" ")) - f.close() - finally: - os.unlink(fn) - return ret - -class X509(DER_object): - """X.509 certificates. - - This class is designed to hold all the different representations of - X.509 certs we're using and convert between them. X.509 support in - Python a nasty maze of half-cooked stuff (except perhaps for - cryptlib, which is just different). Users of this module should not - have to care about this implementation nightmare. - """ - - formats = ("DER", "POW", "POWpkix", "tlslite") - pem_converter = PEM_converter("CERTIFICATE") - - def get_DER(self): - """Get the DER value of this certificate.""" - assert not self.empty() - if self.DER: - return self.DER - if self.POW: - self.DER = self.POW.derWrite() - return self.get_DER() - if self.POWpkix: - self.DER = self.POWpkix.toString() - return self.get_DER() - if self.tlslite: - der = self.tlslite.writeBytes() - if not isinstance(der, str): # Apparently sometimes tlslite strings aren't strings, - der = der.tostring() # then again somtimes they are. Isn't that special? - self.DER = der - return self.get_DER() - raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available" - - def get_POW(self): - """Get the POW value of this certificate.""" - assert not self.empty() - if not self.POW: - self.POW = POW.derRead(POW.X509_CERTIFICATE, self.get_DER()) - return self.POW - - def get_POWpkix(self): - """Get the POW.pkix value of this certificate.""" - assert not self.empty() - if not self.POWpkix: - cert = POW.pkix.Certificate() - cert.fromString(self.get_DER()) - self.POWpkix = cert - return self.POWpkix - - def get_tlslite(self): - """Get the tlslite value of this certificate.""" - assert not self.empty() - if not self.tlslite: - cert = tlslite.api.X509() - cert.parseBinary(self.get_DER()) - self.tlslite = cert - return self.tlslite - - def getIssuer(self): - """Get the issuer of this certificate.""" - return self.get_POW().getIssuer() - - def getSubject(self): - """Get the subject of this certificate.""" - return self.get_POW().getSubject() - - def getNotBefore(self): - """Get the inception time of this certificate.""" - return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().tbs.validity.notBefore.get()) - - def getNotAfter(self): - """Get the expiration time of this certificate.""" - return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().tbs.validity.notAfter.get()) - - 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 RSApublic(DER = self.get_POWpkix().tbs.subjectPublicKeyInfo.toString()) - - 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, - cn = None, resources = None, is_ca = True): - """Issue a certificate.""" - - now = rpki.sundial.now() - aki = self.get_SKI() - ski = subject_key.get_SKI() - - if cn is None: - cn = "".join(("%02X" % ord(i) for i in ski)) - - # if notAfter is None: notAfter = now + rpki.sundial.timedelta(days = 30) - - cert = POW.pkix.Certificate() - cert.setVersion(2) - cert.setSerial(serial) - cert.setIssuer(self.get_POWpkix().getSubject()) - cert.setSubject((((rpki.oids.name2oid["commonName"], ("printableString", cn)),),)) - cert.setNotBefore(now.toASN1tuple()) - cert.setNotAfter(notAfter.toASN1tuple()) - cert.tbs.subjectPublicKeyInfo.fromString(subject_key.get_DER()) - - exts = [ ["subjectKeyIdentifier", False, ski], - ["authorityKeyIdentifier", False, (aki, (), None)], - ["cRLDistributionPoints", False, ((("fullName", (("uri", crldp),)), None, ()),)], - ["authorityInfoAccess", False, ((rpki.oids.name2oid["id-ad-caIssuers"], ("uri", aia)),)], - ["certificatePolicies", True, ((rpki.oids.name2oid["id-cp-ipAddr-asNumber"], ()),)] ] - - if is_ca: - exts.append(["basicConstraints", True, (1, None)]) - exts.append(["keyUsage", True, (0, 0, 0, 0, 0, 1, 1)]) - else: - exts.append(["keyUsage", True, (1,)]) - - if sia is not None: - exts.append(["subjectInfoAccess", False, sia]) - else: - assert not is_ca - - if resources is not None and resources.asn: - exts.append(["sbgp-autonomousSysNum", True, (resources.asn.to_rfc3779_tuple(), None)]) - - if resources is not None and (resources.v4 or resources.v6): - exts.append(["sbgp-ipAddrBlock", True, [x for x in (resources.v4.to_rfc3779_tuple(), resources.v6.to_rfc3779_tuple()) if x is not None]]) - - for x in exts: - x[0] = rpki.oids.name2oid[x[0]] - cert.setExtensions(exts) - - cert.sign(keypair.get_POW(), POW.SHA256_DIGEST) - - return X509(POWpkix = cert) - - @classmethod - def normalize_chain(cls, chain): - """Normalize a chain of certificates into a tuple of X509 objects. - Given all the glue certificates needed for BPKI cross - certification, it's easiest to allow sloppy arguments to the HTTPS - and CMS validation methods and provide a single method that - normalizes the 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) - -class PKCS10(DER_object): - """Class to hold a PKCS #10 request.""" - - formats = ("DER", "POWpkix") - pem_converter = PEM_converter("CERTIFICATE REQUEST") - - def get_DER(self): - """Get the DER value of this certification request.""" - assert not self.empty() - if self.DER: - return self.DER - if self.POWpkix: - self.DER = self.POWpkix.toString() - return self.get_DER() - raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available" - - def get_POWpkix(self): - """Get the POW.pkix value of this certification request.""" - assert not self.empty() - if not self.POWpkix: - req = POW.pkix.CertificationRequest() - req.fromString(self.get_DER()) - self.POWpkix = req - return self.POWpkix - - def getPublicKey(self): - """Extract the public key from this certification request.""" - return RSApublic(DER = self.get_POWpkix().certificationRequestInfo.subjectPublicKeyInfo.toString()) - - def check_valid_rpki(self): - """Check this certification request to see whether it's a valid - request for an RPKI certificate. This is broken out of the - up-down protocol code because it's somewhat involved and the - up-down code doesn't need to know the details. - - Throws an exception if the request isn't valid, so if this method - returns at all, the request is ok. - """ - - if not self.get_POWpkix().verify(): - raise rpki.exceptions.BadPKCS10, "Signature check failed" - - if self.get_POWpkix().certificationRequestInfo.version.get() != 0: - raise rpki.exceptions.BadPKCS10, \ - "Bad version number %s" % self.get_POWpkix().certificationRequestInfo.version - - if rpki.oids.oid2name.get(self.get_POWpkix().signatureAlgorithm.algorithm.get()) \ - not in ("sha256WithRSAEncryption", "sha384WithRSAEncryption", "sha512WithRSAEncryption"): - raise rpki.exceptions.BadPKCS10, "Bad signature algorithm %s" % self.get_POWpkix().signatureAlgorithm - - exts = self.get_POWpkix().getExtensions() - for oid, critical, value in exts: - if rpki.oids.oid2name.get(oid) not in ("basicConstraints", "keyUsage", "subjectInfoAccess"): - raise rpki.exceptions.BadExtension, "Forbidden extension %s" % oid - req_exts = dict((rpki.oids.oid2name[oid], value) for (oid, critical, value) in exts) - - if "basicConstraints" not in req_exts or not req_exts["basicConstraints"][0]: - raise rpki.exceptions.BadPKCS10, "request for EE cert not allowed here" - - if req_exts["basicConstraints"][1] is not None: - raise rpki.exceptions.BadPKCS10, "basicConstraints must not specify Path Length" - - if "keyUsage" in req_exts and (not req_exts["keyUsage"][5] or not req_exts["keyUsage"][6]): - raise rpki.exceptions.BadPKCS10, "keyUsage doesn't match basicConstraints" - - for method, location in req_exts.get("subjectInfoAccess", ()): - if rpki.oids.oid2name.get(method) == "id-ad-caRepository" and \ - (location[0] != "uri" or (location[1].startswith("rsync://") and not location[1].endswith("/"))): - raise rpki.exceptions.BadPKCS10, "Certificate request includes bad SIA component: %s" % repr(location) - - # This one is an implementation restriction. I don't yet - # understand what the spec is telling me to do in this case. - assert "subjectInfoAccess" in req_exts, "Can't (yet) handle PKCS #10 without an SIA extension" - - @classmethod - def create_ca(cls, keypair, sia = None): - """Create a new request for a given keypair, including given SIA value.""" - exts = [["basicConstraints", True, (1, None)], - ["keyUsage", True, (0, 0, 0, 0, 0, 1, 1)]] - if sia is not None: - exts.append(["subjectInfoAccess", False, sia]) - for x in exts: - x[0] = rpki.oids.name2oid[x[0]] - return cls.create(keypair, exts) - - @classmethod - def create(cls, keypair, exts = None): - """Create a new request for a given keypair, including given extensions.""" - cn = "".join(("%02X" % ord(i) for i in keypair.get_SKI())) - req = POW.pkix.CertificationRequest() - req.certificationRequestInfo.version.set(0) - req.certificationRequestInfo.subject.set((((rpki.oids.name2oid["commonName"], - ("printableString", cn)),),)) - if exts is not None: - req.setExtensions(exts) - req.sign(keypair.get_POW(), POW.SHA256_DIGEST) - return cls(POWpkix = req) - -class RSA(DER_object): - """Class to hold an RSA key pair.""" - - formats = ("DER", "POW", "tlslite") - pem_converter = PEM_converter("RSA PRIVATE KEY") - - def get_DER(self): - """Get the DER value of this keypair.""" - assert not self.empty() - if self.DER: - return self.DER - if self.POW: - self.DER = self.POW.derWrite(POW.RSA_PRIVATE_KEY) - return self.get_DER() - raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available" - - def get_POW(self): - """Get the POW value of this keypair.""" - assert not self.empty() - if not self.POW: - self.POW = POW.derRead(POW.RSA_PRIVATE_KEY, self.get_DER()) - return self.POW - - def get_tlslite(self): - """Get the tlslite value of this keypair.""" - assert not self.empty() - if not self.tlslite: - self.tlslite = tlslite.api.parsePEMKey(self.get_PEM(), private=True) - return self.tlslite - - @classmethod - def generate(cls, keylength = 2048): - """Generate a new keypair.""" - return cls(POW = POW.Asymmetric(POW.RSA_CIPHER, keylength)) - - def get_public_DER(self): - """Get the DER encoding of the public key from this keypair.""" - return self.get_POW().derWrite(POW.RSA_PUBLIC_KEY) - - def get_SKI(self): - """Calculate the SKI of this keypair.""" - return calculate_SKI(self.get_public_DER()) - - def get_RSApublic(self): - """Convert the public key of this keypair into a RSApublic object.""" - return RSApublic(DER = self.get_public_DER()) - -class RSApublic(DER_object): - """Class to hold an RSA public key.""" - - formats = ("DER", "POW") - pem_converter = PEM_converter("RSA PUBLIC KEY") - - def get_DER(self): - """Get the DER value of this public key.""" - assert not self.empty() - if self.DER: - return self.DER - if self.POW: - self.DER = self.POW.derWrite(POW.RSA_PUBLIC_KEY) - return self.get_DER() - raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available" - - def get_POW(self): - """Get the POW value of this public key.""" - assert not self.empty() - if not self.POW: - self.POW = POW.derRead(POW.RSA_PUBLIC_KEY, self.get_DER()) - return self.POW - - def get_SKI(self): - """Calculate the SKI of this public key.""" - return calculate_SKI(self.get_DER()) - -def POWify_OID(oid): - """Utility function to convert tuple form of an OID to - the dotted-decimal string form that POW uses. - """ - if isinstance(oid, str): - return POWify_OID(rpki.oids.name2oid[oid]) - else: - return ".".join(str(i) for i in oid) - -class CMS_object(DER_object): - """Class to hold a CMS-wrapped object. - - CMS-wrapped objects are a little different from the other DER_object - types because the signed object is CMS wrapping inner content that's - also ASN.1, and due to our current minimal support for CMS we can't - just handle this as a pretty composite object. So, for now anyway, - a CMS_object is the outer CMS wrapped object so that the usual DER - and PEM operations do the obvious things, and the inner content is - handle via separate methods. - """ - - formats = ("DER", "POW") - other_clear = ("content",) - econtent_oid = POWify_OID("id-data") - pem_converter = PEM_converter("CMS") - - ## @var dump_on_verify_failure - # Set this to True to get dumpasn1 dumps of ASN.1 on CMS verify failures. - - dump_on_verify_failure = True - - ## @var debug_cms_certs - # Set this to True to log a lot of chatter about CMS certificates. - - debug_cms_certs = False - - ## @var require_crls - # Set this to False to make CMS CRLs optional in the cases where we - # would otherwise require them. Some day this option should go away - # and CRLs should be uncondtionally mandatory in such cases. - - require_crls = False - - ## @var print_on_der_error - # Set this to True to log alleged DER when we have trouble parsing - # it, in case it's really a Perl backtrace or something. - - print_on_der_error = True - - def get_DER(self): - """Get the DER value of this CMS_object.""" - assert not self.empty() - if self.DER: - return self.DER - if self.POW: - self.DER = self.POW.derWrite() - return self.get_DER() - raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available" - - def get_POW(self): - """Get the POW value of this CMS_object.""" - assert not self.empty() - if not self.POW: - self.POW = POW.derRead(POW.CMS_MESSAGE, self.get_DER()) - return self.POW - - def get_content(self): - """Get the inner content of this CMS_object.""" - assert self.content is not None - return self.content - - def set_content(self, content): - """Set the (inner) content of this CMS_object, clearing the wrapper.""" - self.clear() - self.content = content - - def verify(self, ta): - """Verify CMS wrapper and store inner content.""" - - try: - cms = self.get_POW() - except: - if self.print_on_der_error: - rpki.log.debug("Problem parsing DER CMS message, might not really be DER: %s" - % repr(self.get_DER())) - raise rpki.exceptions.UnparsableCMSDER - - if cms.eContentType() != self.econtent_oid: - raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid) - - certs = [X509(POW = x) for x in cms.certs()] - crls = [CRL(POW = c) for c in cms.crls()] - - if self.debug_cms_certs: - for x in certs: - rpki.log.debug("Received CMS cert issuer %s subject %s" % (x.getIssuer(), x.getSubject())) - for c in crls: - rpki.log.debug("Received CMS CRL issuer %s" % repr(c.getIssuer())) - - store = POW.X509Store() - - trusted_ee = None - - for x in X509.normalize_chain(ta): - if self.debug_cms_certs: - rpki.log.debug("CMS trusted cert issuer %s subject %s" % (x.getIssuer(), x.getSubject())) - if not x.is_CA(): - assert trusted_ee is None, "Can't have two EE certs in the same validation chain" - trusted_ee = x - store.addTrust(x.get_POW()) - - if trusted_ee: - if self.debug_cms_certs: - rpki.log.debug("Trusted CMS EE cert issuer %s subject %s" % (trusted_ee.getIssuer(), trusted_ee.getSubject())) - if certs and (len(certs) > 1 or certs[0] != trusted_ee): - raise rpki.exceptions.UnexpectedCMSCerts, certs - if crls: - raise rpki.exceptions.UnexpectedCMSCRLs, crls - else: - if not certs: - raise rpki.exceptions.MissingCMSEEcert, certs - if len(certs) > 1 or certs[0].is_CA(): - raise rpki.exceptions.UnexpectedCMSCerts, certs - if not crls: - if self.require_crls: - raise rpki.exceptions.MissingCMSCRL, crls - else: - rpki.log.warn("MISSING CMS CRL! Ignoring per self.require_crls setting") - if len(crls) > 1: - raise rpki.exceptions.UnexpectedCMSCRLs, crls - - try: - content = cms.verify(store) - except: - if self.dump_on_verify_failure: - if True: - dbg = self.dumpasn1() - else: - dbg = cms.pprint() - print "CMS verification failed, dumping ASN.1 (%d octets):\n%s" % (len(self.get_DER()), dbg) - raise rpki.exceptions.CMSVerificationFailed, "CMS verification failed" - - self.decode(content) - return self.get_content() - - def extract(self): - """Extract and store inner content from CMS wrapper without - verifying the CMS. - - DANGER WILL ROBINSON!!! - - Do not use this method on unvalidated data. Use the verify() - method instead. - - If you don't understand this warning, don't use this method. - """ - - try: - cms = self.get_POW() - except: - raise rpki.exceptions.UnparsableCMSDER - - if cms.eContentType() != self.econtent_oid: - raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid) - - content = cms.verify(POW.X509Store(), None, POW.CMS_NOCRL | POW.CMS_NO_SIGNER_CERT_VERIFY | POW.CMS_NO_ATTR_VERIFY | POW.CMS_NO_CONTENT_VERIFY) - - self.decode(content) - return self.get_content() - - def sign(self, keypair, certs, crls = None, no_certs = False): - """Sign and wrap inner content.""" - - rpki.log.trace() - - if isinstance(certs, X509): - cert = certs - certs = () - else: - cert = certs[0] - certs = certs[1:] - - if crls is None: - crls = () - elif isinstance(crls, CRL): - crls = (crls,) - - cms = POW.CMS() - - cms.sign(cert.get_POW(), - keypair.get_POW(), - self.encode(), - [x.get_POW() for x in certs], - [c.get_POW() for c in crls], - self.econtent_oid, - POW.CMS_NOCERTS if no_certs else 0) - - self.POW = cms - -class DER_CMS_object(CMS_object): - """Class to hold CMS objects with DER-based content.""" - - def encode(self): - """Encode inner content for signing.""" - return self.get_content().toString() - - def decode(self, der): - """Decode DER and set inner content.""" - obj = self.content_class() - obj.fromString(der) - self.content = obj - -class SignedManifest(DER_CMS_object): - """Class to hold a signed manifest.""" - - pem_converter = PEM_converter("RPKI MANIFEST") - content_class = rpki.manifest.Manifest - econtent_oid = POWify_OID("id-ct-rpkiManifest") - - def getThisUpdate(self): - """Get thisUpdate value from this manifest.""" - return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().thisUpdate.get()) - - def getNextUpdate(self): - """Get nextUpdate value from this manifest.""" - return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().nextUpdate.get()) - - @classmethod - def build(cls, serial, thisUpdate, nextUpdate, names_and_objs, keypair, certs, version = 0): - """Build a signed manifest.""" - self = cls() - filelist = [] - for name, obj in names_and_objs: - d = POW.Digest(POW.SHA256_DIGEST) - d.update(obj.get_DER()) - filelist.append((name.rpartition("/")[2], d.digest())) - filelist.sort(key = lambda x: x[0]) - m = rpki.manifest.Manifest() - m.version.set(version) - m.manifestNumber.set(serial) - m.thisUpdate.set(thisUpdate.toGeneralizedTime()) - m.nextUpdate.set(nextUpdate.toGeneralizedTime()) - m.fileHashAlg.set(rpki.oids.name2oid["id-sha256"]) - m.fileList.set(filelist) - self.set_content(m) - self.sign(keypair, certs) - return self - -class ROA(DER_CMS_object): - """Class to hold a signed ROA.""" - - pem_converter = PEM_converter("ROUTE ORIGIN ATTESTATION") - content_class = rpki.roa.RouteOriginAttestation - econtent_oid = POWify_OID("id-ct-routeOriginAttestation") - - @classmethod - def build(cls, as_number, ipv4, ipv6, keypair, certs, version = 0): - """Build a ROA.""" - self = cls() - r = rpki.roa.RouteOriginAttestation() - r.version.set(version) - r.asID.set(as_number) - r.ipAddrBlocks.set((a.to_roa_tuple() for a in (ipv4, ipv6) if a)) - self.set_content(r) - self.sign(keypair, certs) - return self - -class XML_CMS_object(CMS_object): - """Class to hold CMS-wrapped XML protocol data.""" - - econtent_oid = POWify_OID("id-ct-xml") - - ## @var dump_outbound_cms - # If set, we write all outbound XML-CMS PDUs to disk, for debugging. - # Value of this variable is prefix portion of filename, tail will - # be a timestamp. - - dump_outbound_cms = None - - ## @var dump_outbound_cms - # If set, we write all inbound XML-CMS PDUs to disk, for debugging. - # Value of this variable is prefix portion of filename, tail will - # be a timestamp. - - dump_inbound_cms = None - - def encode(self): - """Encode inner content for signing.""" - return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True) - - def decode(self, xml): - """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, xml_declaration = True) - - def schema_check(self): - """Handle XML RelaxNG schema check.""" - try: - self.schema.assertValid(self.get_content()) - except lxml.etree.DocumentInvalid: - rpki.log.error("PDU failed schema check: " + self.pretty_print_content()) - raise - - def dump_to_disk(self, prefix): - """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() - - @classmethod - def wrap(cls, msg, keypair, certs, crls = None, pretty_print = False): - """Build a CMS-wrapped XML PDU and return its DER encoding.""" - rpki.log.trace() - self = cls() - self.set_content(msg.toXML()) - self.schema_check() - self.sign(keypair, certs, crls) - if self.dump_outbound_cms: - self.dump_to_disk(self.dump_outbound_cms) - if pretty_print: - return self.get_DER(), self.pretty_print_content() - else: - return self.get_DER() - - @classmethod - def unwrap(cls, der, ta, pretty_print = False): - """Unwrap a CMS-wrapped XML PDU and return Python objects.""" - self = cls(DER = der) - if self.dump_inbound_cms: - self.dump_to_disk(self.dump_inbound_cms) - self.verify(ta) - self.schema_check() - msg = self.saxify(self.get_content()) - if pretty_print: - return msg, self.pretty_print_content() - else: - return msg - -class CRL(DER_object): - """Class to hold a Certificate Revocation List.""" - - formats = ("DER", "POW", "POWpkix") - pem_converter = PEM_converter("X509 CRL") - - def get_DER(self): - """Get the DER value of this CRL.""" - assert not self.empty() - if self.DER: - return self.DER - if self.POW: - self.DER = self.POW.derWrite() - return self.get_DER() - if self.POWpkix: - self.DER = self.POWpkix.toString() - return self.get_DER() - raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available" - - def get_POW(self): - """Get the POW value of this CRL.""" - assert not self.empty() - if not self.POW: - self.POW = POW.derRead(POW.X509_CRL, self.get_DER()) - return self.POW - - def get_POWpkix(self): - """Get the POW.pkix value of this CRL.""" - assert not self.empty() - if not self.POWpkix: - crl = POW.pkix.CertificateList() - crl.fromString(self.get_DER()) - self.POWpkix = crl - return self.POWpkix - - def getThisUpdate(self): - """Get thisUpdate value from this CRL.""" - return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getThisUpdate()) - - def getNextUpdate(self): - """Get nextUpdate value from this CRL.""" - return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getNextUpdate()) - - def getIssuer(self): - """Get issuer value of this CRL.""" - return self.get_POW().getIssuer() - - @classmethod - def generate(cls, keypair, issuer, serial, thisUpdate, nextUpdate, revokedCertificates, version = 1, digestType = "sha256WithRSAEncryption"): - crl = POW.pkix.CertificateList() - crl.setVersion(version) - crl.setIssuer(issuer.get_POWpkix().getSubject()) - crl.setThisUpdate(thisUpdate.toASN1tuple()) - crl.setNextUpdate(nextUpdate.toASN1tuple()) - if revokedCertificates: - crl.setRevokedCertificates(revokedCertificates) - crl.setExtensions( - ((rpki.oids.name2oid["authorityKeyIdentifier"], False, (issuer.get_SKI(), (), None)), - (rpki.oids.name2oid["cRLNumber"], False, serial))) - crl.sign(keypair.get_POW(), digestType) - return cls(POWpkix = crl) diff --git a/rpkid.stable/rpki/xml_utils.py b/rpkid.stable/rpki/xml_utils.py deleted file mode 100644 index eda8aa85..00000000 --- a/rpkid.stable/rpki/xml_utils.py +++ /dev/null @@ -1,317 +0,0 @@ -"""XML utilities. - -$Id$ - -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. -""" - -import xml.sax, lxml.sax, lxml.etree, base64 - -class sax_handler(xml.sax.handler.ContentHandler): - """SAX handler for RPKI protocols. - - This class provides some basic amenities for parsing protocol XML of - the kind we use in the RPKI protocols, including whacking all the - protocol element text into US-ASCII, simplifying accumulation of - text fields, and hiding some of the fun relating to XML namespaces. - - General assumption: by the time this parsing code gets invoked, the - XML has already passed RelaxNG validation, so we only have to check - for errors that the schema can't catch, and we don't have to play as - many XML namespace games. - """ - - def __init__(self): - """Initialize SAX handler.""" - self.text = "" - self.stack = [] - - def startElementNS(self, name, qname, attrs): - """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): - """Handle startElement() events. - - We maintain a stack of nested elements under construction so that - we can feed events directly to the current element rather than - having to pass them through all the nesting elements. - - If the stack is empty, this event is for the outermost element, so - we call a virtual method to create the corresponding object and - that's the object we'll be returning as our final result. - """ - a = dict() - for k,v in attrs.items(): - if isinstance(k, tuple): - if k == ("http://www.w3.org/XML/1998/namespace", "lang"): - k = "xml:lang" - else: - assert k[0] is None - k = k[1] - a[k.encode("ascii")] = v.encode("ascii") - if len(self.stack) == 0: - assert not hasattr(self, "result") - self.result = self.create_top_level(name, a) - self.stack.append(self.result) - self.stack[-1].startElement(self.stack, name, a) - - def endElement(self, name): - """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) - - @classmethod - def saxify(cls, elt): - """Create a one-off SAX parser, parse an ETree, return the result. - """ - self = cls() - lxml.sax.saxify(elt, self) - return self.result - - def create_top_level(self, name, attrs): - """Handle top-level PDU for this protocol.""" - assert name == self.name and attrs["version"] == self.version - return self.pdu() - -class base_elt(object): - """Virtual base class for XML message elements. The left-right and - publication protocols use this. At least for now, the up-down - protocol does not, due to different design assumptions. - """ - - ## @var attributes - # XML attributes for this element. - attributes = () - - ## @var elements - # XML elements contained by this element. - elements = () - - ## @var booleans - # Boolean attributes (value "yes" or "no") for this element. - booleans = () - - def startElement(self, stack, name, attrs): - """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) - - def endElement(self, stack, name, text): - """Default endElement() handler: just pop the stack.""" - assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - - def toXML(self): - """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(): - val = long(val) - setattr(self, key, val) - for key in self.booleans: - setattr(self, key, attrs.get(key, False)) - - def make_elt(self): - """XML element constructor.""" - elt = lxml.etree.Element("{%s}%s" % (self.xmlns, self.element_name), nsmap = self.nsmap) - for key in self.attributes: - val = getattr(self, key, None) - if val is not None: - elt.set(key, str(val)) - for key in self.booleans: - if getattr(self, key, False): - elt.set(key, "yes") - return elt - - def make_b64elt(self, elt, name, value = None): - """Constructor for Base64-encoded subelement.""" - if value is None: - value = getattr(self, name, None) - if value is not None: - lxml.etree.SubElement(elt, "{%s}%s" % (self.xmlns, name), nsmap = self.nsmap).text = base64.b64encode(value) - - def __str__(self): - """Convert a base_elt object to string format.""" - lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii") - - @classmethod - def make_pdu(cls, **kargs): - """Generic PDU constructor.""" - self = cls() - for k,v in kargs.items(): - if isinstance(v, bool): - v = 1 if v else 0 - setattr(self, k, v) - return self - -class data_elt(base_elt): - """Virtual base class for PDUs that map to SQL objects. These - objects all implement the create/set/get/list/destroy action - attribute. - """ - - def endElement(self, stack, name, text): - """Default endElement handler for SQL-based objects. This assumes - 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) - setattr(self, name, elt_type(Base64 = text)) - else: - assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - - def toXML(self): - """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: - x = getattr(self, i, None) - if x and not x.empty(): - self.make_b64elt(elt, i, x.get_DER()) - return elt - - def make_reply(self, r_pdu = None): - """Construct a reply PDU.""" - if r_pdu is None: - r_pdu = self.__class__() - self.make_reply_clone_hook(r_pdu) - setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index)) - else: - for b in r_pdu.booleans: - setattr(r_pdu, b, False) - r_pdu.action = self.action - r_pdu.tag = self.tag - return r_pdu - - def make_reply_clone_hook(self, r_pdu): - """Overridable hook.""" - pass - - def serve_pre_save_hook(self, q_pdu, r_pdu): - """Overridable hook.""" - pass - - def serve_post_save_hook(self, q_pdu, r_pdu): - """Overridable hook.""" - pass - - def serve_create(self, r_msg): - """Handle a create action.""" - r_pdu = self.make_reply() - self.serve_pre_save_hook(self, r_pdu) - self.sql_store() - setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index)) - self.serve_post_save_hook(self, r_pdu) - r_msg.append(r_pdu) - - def serve_set(self, r_msg): - """Handle a set action.""" - db_pdu = self.serve_fetch_one() - r_pdu = self.make_reply() - for a in db_pdu.sql_template.columns[1:]: - v = getattr(self, a) - if v is not None: - setattr(db_pdu, a, v) - db_pdu.sql_mark_dirty() - db_pdu.serve_pre_save_hook(self, r_pdu) - db_pdu.sql_store() - db_pdu.serve_post_save_hook(self, r_pdu) - r_msg.append(r_pdu) - - def serve_get(self, r_msg): - """Handle a get action.""" - r_pdu = self.serve_fetch_one() - self.make_reply(r_pdu) - r_msg.append(r_pdu) - - def serve_list(self, r_msg): - """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) - - def serve_destroy(self, r_msg): - """Handle a destroy action.""" - db_pdu = self.serve_fetch_one() - db_pdu.sql_delete() - r_msg.append(self.make_reply()) - - def serve_dispatch(self, r_msg): - """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: - raise rpki.exceptions.BadQuery, "Unexpected query: action %s" % self.action - dispatch[self.action](r_msg) - - 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) - -class msg(list): - """Generic top-level PDU.""" - - def startElement(self, stack, name, attrs): - """Handle top-level PDU.""" - if name == "msg": - assert self.version == int(attrs["version"]) - self.type = attrs["type"] - else: - elt = self.pdus[name]() - self.append(elt) - stack.append(elt) - elt.startElement(stack, name, attrs) - - def endElement(self, stack, name, text): - """Handle top-level PDU.""" - assert name == "msg", "Unexpected name %s, stack %s" % (name, stack) - assert len(stack) == 1 - stack.pop() - - def __str__(self): - """Convert msg object to string.""" - lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii") - - def toXML(self): - """Generate top-level PDU.""" - elt = lxml.etree.Element("{%s}msg" % (self.xmlns), nsmap = self.nsmap, version = str(self.version), type = self.type) - elt.extend([i.toXML() for i in self]) - return elt |