diff options
Diffstat (limited to 'rpkid.with_tls/rpki')
24 files changed, 0 insertions, 16471 deletions
diff --git a/rpkid.with_tls/rpki/__doc__.py.in b/rpkid.with_tls/rpki/__doc__.py.in deleted file mode 100644 index 420de455..00000000 --- a/rpkid.with_tls/rpki/__doc__.py.in +++ /dev/null @@ -1,2413 +0,0 @@ -## @file -# @details -# Documentation sourc, expressed as Python comments to make Doxygen happy. -# -# $Id$ -# -# Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. -# -# 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. - -## @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 and -# design documents. -# -# The RPKI Engine is an implementation of the production-side tools -# for generating certificates, CRLs, and ROAs. The -# <a href="http://viewvc.hactrn.net/subvert-rpki.hactrn.net/rcynic/">relying party tools</a> -# are a separate (and much simpler) package. -# -# The Subversion repository for the entire project is available for -# (read-only) anonymous access at http://subvert-rpki.hactrn.net/. -# -# 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 @subpage Overview "Overview of the tools" -# -# @li @subpage Installation "Installation instructions" -# -# @li @subpage Configuration "Configuration instructions" -# -# @li @subpage MySQL-Setup "MySQL setup instructions" -# -# @li The @subpage myrpki "myrpki tool" -# -# @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" -# -# This work was funded from 2006 through 2008 by <a -# href="http://www.arin.net/">ARIN</a>, in collaboration with the -# other Regional Internet Registries. Current work is funded by DHS. - -## @page Overview Overview -# -# @section Terminology Terminology -# -# A few special terms that appear often enough in code or -# documentation that they need explaining. -# -# @todo -# These explanations should be fleshed out properly. -# -# @par IRBE: -# Internet Registry Back End. -# -# @par IRDB: -# Internet Registry Data Base. -# -# @par BPKI: -# Business PKI. -# -# @par RPKI: -# Resource PKI. -# -# -# @section Programs Programs -# -# 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 @ref rpkid "@c rpkid": -# The main RPKI engine daemon. -# -# @li @ref pubd "@c pubd": -# The publication engine daemon. -# -# @li @ref rootd "@c rootd" -# 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 @ref irdbd "@c irdbd": -# A sample implementation of an IR database daemon. -# rpkid calls into this to perform lookups via the -# left-right protocol. -# -# @li @ref smoketest "@c smoketest": -# A test tool for running a collection of rpkid and irdb -# instances under common control, driven by a unified -# test script. -# -# @li @ref yamltest "@c yamltest": -# Another test tool which takes the same input format as -# @c smoketest.py, but with slightly different purpose. -# @c smoketest.py is intended to support regression tests, -# while @c yamltest.py is intended for automated testing -# of something closer to a real operational environment. -# There's a fair amount of code duplication between the -# two, and at some point they will probably be merged -# into a single program that supports both modes of -# operation. -# -# 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 @c yamltest 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 -# (see @ref MySQL-Setup "MySQL Setup"), configuring relationships -# between parents and children and between publication clients and -# repositories (see @ref MyRPKI "The myrpki tool"), starting @c rpkid, -# @c pubd, @c rootd, and @c irdbd, and using the left-right and -# publication control protocols (see @ref MyRPKI "The myrpki tool") to -# set up rpkid's and pubd's internal state. 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. -# -# The core programs are all event-driven, and are (in theory) capable -# of supporting an arbitrary number of hosted RPKI engines to run in a -# single rpkid instance, up to the performance limits of the underlying -# hardware. -# -# At present the daemon programs all run in foreground, that is, the -# daemons themselves make no attempt to put themselves in background. -# The easiest way to run the servers is to run the @c start_servers -# script, which examines your @c myrpki.conf file and starts the -# appropriate servers in background using @c myrpki.conf as the -# configuration file for each server as well. -# -# If you prefer, you can run each server by hand instead of using the -# script, eg, using Bourne shell syntax to run rpkid in background: -# -# @verbatim -# $ python rpkid.py & -# $ echo >rpkid.pid "$!" -# @endverbatim -# -# All of the daemons use syslog by default. You can change this by -# running either the servers themselves or the @c start_servers script -# with the "-d" option. Used as an argument to a server directly, -# "-d" causes that server to log to @c stderr instead of to syslog. -# Used as an argument to @c start_servers, "-d" starts each of the -# servers with "-d" while redirecting @c stderr from each server to a -# separate log file. This is intended primarily for debugging. -# -# Some of the options that the several daemons take are common to all -# daemons. Which daemon they affect depends only on which sections of -# which config files they are in. See -# @ref CommonOptions "Common Options" -# for details. -# -# @subsection rpkid rpkid -# -# 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 @ref Left-Right "left-right protocol", -# followed by dynamic configuration via the left-right protocol. The -# latter stage is handled by the @c myrpki tool. -# -# rpkid stores dynamic data in an SQL database, which must have been -# created for it, as explained in the -# @ref Installation "Installation Guide". -# -# -# @subsection pubd pubd -# -# 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 -# @ref Installation "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; see the -# @ref Configuration "Configuration Guide" -# for details. -# -# -# @subsection rootd rootd -# -# 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; -# see the -# @ref Configuration "Configuration Guide" -# for details. -# -# -# @subsection irdbd irdbd -# -# 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 -# @ref irdbd-sql "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 handles (foreign -# keys into rpkid's database) in the IRDB. See the -# @ref Installation "Installation Guide" -# and the -# @ref MySQL-Setup "MySQL setup instructions" -# for details. -# -# -# @subsection smoketest smoketest -# -# smoketest is a test harness to set up and run a collection of rpkid and -# irdbd instances under scripted control. -# -# Unlike the programs described above, smoketest 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. smoketest 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. -# -# See @ref smoketestconf "smoketest.conf" for what can go into the -# (optional) first configuration file. -# -# See @ref smoketestyaml "smoketest.yaml" for what goes into the -# (required) second configuration file. -# -# -# @subsection yamltest yamltest -# -# yamltest is another test harness to set up and run a collection of -# rpkid and irdbd instances under scripted control. It is similar in -# many ways to @ref smoketest "@c smoketest", and in fact uses the -# same YAML test description language, but its purpose is different: -# @c smoketest runs a particular test scenario through a series of -# changes, then shuts it down; @c yamltest, on the other hand, sets up -# a test network using the same tools that a real user would -# (principally the @c myrpki tool), and leaves the test running -# indefinitely. -# -# @c yamltest grew out of @c smoketest and the two probably should be -# merged back into a single tool which supports both kinds of testing. -# -# -# @section further-reading Further Reading -# -# If you're interested in this package you might also be interested -# in: -# -# @li <a href="http://track-rpki.hactrn.net/browser/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> -# -# -# @section getting-started Getting Started -# -# The first step to bringing up rpkid and friends is installing the code, -# which is described in the @ref Installation "Installation Guide". - -## @page Installation Installation Guide -# -# 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 or 2.6. rpkid et al are mostly self-contained, but do -# require a small number of external packages to run. -# -# <ul> -# <li> -# If your Python installation does not already include the sources -# files needed to compile new Python extension modules, you will -# need to install whatever package does include those source -# files. The need for and name of this package varies from system -# to system. On FreeBSD, the base Python interpreter package -# includes the development sources; on at least some Linux -# distributions, you have to install a separate "python-devel" -# package or something similar. If you get compilation errors -# trying to build the POW code (below) and the error message says -# something about the file "Python.h" being missing, this is -# almost certainly your problem. -# </li> -# -# <li> -# <a href="http://codespeak.net/lxml/">http://codespeak.net/lxml/</a>, -# a Pythonic interface to the Gnome LibXML2 libraries. -# lxml in turn requires the LibXML2 C libraries. -# <ul> -# <li>FreeBSD: /usr/ports/devel/py-lxml</li> -# <li>Fedora: python-lxml.i386</li> -# <li>Ubuntu: python-lxml</li> -# </ul> -# </li> -# -# <li> -# <a href="http://sourceforge.net/projects/mysql-python/">http://sourceforge.net/projects/mysql-python/</a>, -# the Python "db" interface to MySQL. 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> -# <li>Ubuntu: python-mysqldb</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 snapshot of the OpenSSL development -# sources, 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, -# run the configure script, then run "make": -# -# @verbatim -# $ cd $top -# $ ./configure -# $ make -# @endverbatim -# -# This should automatically build everything, in the right order, -# including linking the POW extension module with the OpenSSL library -# to provide RFC 3779 support. If you get errors building POW, see -# the above discussion of Python development sources. -# -# 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 -# to run the core programs, but you will probably want to test it. -# 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>. -# Several of the test programs use PyYAML to parse a YAML -# description of a simulated allocation hierarchy to test. -# <ul> -# <li>FreeBSD: /usr/ports/devel/py-yaml</li> -# <li>Ubuntu: python-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> -# <li>Ubuntu: xsltproc</li> -# </ul> -# </li> -# </ul> -# -# All tests should be run from the rpkid/ directories. -# -# 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/tests/smoketest.setup.sql. The MySQL command line -# client is usually the easiest way to do this, eg: -# -# @verbatim -# $ cd $top/rpkid -# $ mysql -u root -p <tests/smoketest.setup.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> -# <li>Ubuntu: doxygen</li> -# </ul> -# </li> -# -# <li> -# <a href="http://www.mbayer.de/html2text/">http://www.mbayer.de/html2text/</a>. -# The documentation build process uses xsltproc and html2text to dump -# flat text versions of a few critical documentation pages. -# <ul> -# <li>FreeBSD: /usr/ports/textproc/html2text</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. -# <ul> -# <li>FreeBSD: /usr/ports/databases/p5-SQL-Translator</li> -# </ul> -# </li> -# </ul> -# -# Once you've finished with installation, the next thing you should -# read is the @ref Configuration "Configuration Guide". - -## @page Configuration Configuration Guide -# -# This section describes the configuration file syntax and settings. -# -# Each of the programs that make up the RPKI tookit can potentially -# take its own configuration file, but for most uses this is -# unnecessarily complicated. The recommended approach is to use a -# single configuration file, and to put all of the parameters that a -# normal user might need to change into a single section of that -# configuration file, then reference these common settings from the -# program-specific sections of the configuration file via macro -# expansion. The configuration file parser supports a limited version -# of the macro facility used in OpenSSL's configuration parser. An -# expression such as @verbatim foo = ${bar::baz} @endverbatim sets foo -# to the value of the @c baz variable from section @c bar. The section -# name @c ENV is special: it refers to environment variables. -# -# @section myrpkiconf myrpki.conf -# -# The default name for the shared configuration file is @c myrpki.conf. -# Unless you really know what you're doing, you should start by -# copying the @c $top/myrpki.conf from the @c rpkid/examples directory and -# modifying it, as the sample configuration file already includes all -# the additional settings necessary to use the simplified configuration. -# -# @dontinclude myrpki.conf -# @skipline [myrpki] -# -# The @c [myrpki] section of @c myrpki.conf contains all the -# parameters that you really need to configure. -# -# @skip # -# @until = -# -# Every resource-holding or server-operating entity needs a "handle", -# which is just an identifier by which the entity calls itself. -# Handles do not need to be globally unique, but should be chosen with -# an eye towards debugging operational problems: it's best if you use -# a handle that your parents and children will recognize as being you. -# -# @skip # -# @until bpki/servers -# -# The myrpki tool requires filenames for several input data files, the -# "business PKI" databases used to secure CMS and TLS communications, -# and the XML intermediate format that it uses. Rather than -# hardwiring the names into the code, they're configured here. You -# can change the names if you must, but the defaults should be fine in -# most cases. -# -# @skip # -# @until irdbd_server_port -# -# If you're hosting RPKI service for others, or are self-hosting, you -# want this on. If somebody else is running rpkid on your behalf and -# you're just shipping them your @c myrpki.xml file, you can turn this -# off. -# -# If you're running @c rpkid at all, you'll need to set at least the -# @c rpkid_server_host parameter here. You may be able to use the -# default port numbers, or may need to pick different ones. Unless -# you plan to run @c irdbd on a different machine from @c rpkid, you -# should leave @c irdbd_server_host alone. -# -# @skip # -# @until pubd_contact_info -# -# The myrpki tool will attempt to negotiate publication service for -# you with whatever publication service your parent is using, if you -# let it, so in most cases you should not need to run @c pubd unless -# you need to issue certificates for private IP address space or -# private Autononmous System Numbers. -# -# If you do run @c pubd, you will need to set @c pubd_server_host. -# You may also need to set @c pubd_server_port, and you should provide -# something helpful as contact information in @c pubd_contact_info if -# you plan to offer publication service to your RPKI children, so that -# grandchildren (or descendents even further down the tree) who -# receive referrals to your service will know how to contact you. -# -# @skip # -# @until rootd_server_port -# -# You shouldn't run rootd unless you're the root of an RPKI tree. Who -# gets to be the root of the public RPKI tree is a political issue -# outside the scope of this document. For everybody else, the only -# reason for running @c rootd (other than test purposes) would be to -# support certification of private IP addresses and ASNs. The core -# tools can do this without any problem, but the simplified -# configuration mechanism does not (yet) make this easy to do. -# -# @skip # -# @until publication_rsync_server -# -# These parameters control the mapping between the rsync URIs -# presented by @c rsyncd and the local filesystem on the machine where -# @c pubd and @c rsyncd run. Any changes here must also be reflected -# as changes in @c rsyncd.conf. In most cases you should not change -# the value of @c publication_rsync_module from the default; since -# pubd can't (and should not) rewrite @c rsyncd.conf, it's best to use -# a static rsync module name here and let @c pubd do its work -# underneath that name. In most cases @c publication_rsync_server -# should be the same as @c publication_rsync_server, which is what the -# macro invocation in the default setting does. @c -# publication_base_directory, like other pathnames in @c myrpki.conf, -# can be either a relative or absolute pathname; if relative, it's -# interpreted with respect to the directory in which the programs in -# question were started. In this specific case, it's probably better -# to use an absolute pathname, since this pathname must also appear in -# @c rsyncd.conf. -# -# @skip # -# @until pubd_sql_password -# -# These settings control how @c rpkid, @c irdbd, and @c pubd talk to -# the MySQL server. At minimum, each daemon needs its own database; -# in the simplest configuration, the username and password can be -# shared, which is what the macro references in the default -# configuration does. If for some reason you need to set different -# usernames and passwords for different daemons, you can do so by -# changing the daemon-specific variables. -# -# @skip # -# @until = openssl -# -# The @c myrpki tool uses the @c openssl command line tool for most of -# its BPKI operations, for two reasons: -# -# @li To avoid duplicating CA-management functionality already -# provided by the command line tool, and -# -# @li To ease portability of the @c myrpki tool, so that a "hosted" -# resource holder can use it without needing to install entire toolkit. -# -# The @c myrpki tool's use of OpenSSL does not require exotic features -# like RFC 3779 support, but it does require a version of the tool -# recent enough to support CMS and the @c -ss_cert argument to the @c -# ca command. Depending on the platform on which you are running this -# code, you may or may not have a system copy of the @c openssl tool -# installed that meets these criteria; if not, the @c openssl binary -# built when you compile the toolkit will suffice. This parameter -# allows you to tell @c myrpki where to find the binary, if necessary; -# the default just uses the system search path. -# -# @section otherconf Other configuration files and options -# -# In most cases the simplified configuration in the @c [myrpki] -# section of @c myrpki.conf should suffice, but in case you need to -# tinker, here are details on the the rest of the configuration -# options. In most cases the default name of the configuration file -# for a program is the name of the program followed by @c ".conf", and -# the section name is also named for the program, so that you can -# combine sections into a single configuration file as shown with @c -# myrpki.conf. -# -# @li @subpage CommonOptions "Common configuration options" -# -# @li @subpage rpkidconf "rpkid configuration" -# -# @li @subpage irdbdconf "irdbd configuration" -# -# @li @subpage pubdconf "pubd configuration" -# -# @li @subpage rootdconf "rootd configuration" -# -# @li @subpage smoketestconf "configuration of the smoketest test harness" -# -# @li @subpage smoketestyaml "test description language for the smoketest test harness" -# -# Once you've finished with configuration, the next thing you should -# read is the @ref MySQL-Setup "MySQL setup instructions". - -## @page MySQL-Setup MySQL Setup -# -# You need to install MySQL and set up the relevant databases before -# starting @c rpkid, @c irdbd, or @c pubd. -# -# See the @ref Installation "Installation Guide" for details on where -# to download MySQL and find documentation on installing it. -# -# See the @ref Configuration "Configuration Guide" for details on the -# configuration file settings the daemons will use to find and -# authenticate themselves to their respective databases. -# -# Before you can (usefully) start any of the daemons, you will need to -# set up the MySQL databases they use. You can do this by hand, or -# you can use the @c sql-setup.py script, which prompts you for your -# MySQL root password then attempts to do everything else -# automatically using values from myrpki.conf. -# -# Using the script is simple: -# -# @verbatim -# $ python sql-setup.py -# Please enter your MySQL root password: -# @endverbatim -# -# The script should tell you what databases it creates. You can use -# the -v option if you want to see more details about what it's doing. -# -# If you'd prefer to do the SQL setup manually, perhaps because you -# have valuable data in other MySQL databases and you don't want to -# trust some random setup script with your MySQL root password, you'll -# need to use the MySQL command line tool, as follows: -# -# @verbatim -# $ mysql -u root -p -# -# mysql> CREATE DATABASE irdb_database; -# mysql> GRANT all ON irdb_database.* TO irdb_user@localhost IDENTIFIED BY 'irdb_password'; -# mysql> USE irdb_database; -# mysql> SOURCE $top/rpkid/irdbd.sql; -# mysql> CREATE DATABASE rpki_database; -# mysql> GRANT all ON rpki_database.* TO rpki_user@localhost IDENTIFIED BY 'rpki_password'; -# mysql> USE rpki_database; -# mysql> SOURCE $top/rpkid/rpkid.sql; -# mysql> COMMIT; -# mysql> quit -# @endverbatim -# -# where @c irdb_database, @c irdb_user, @c irdb_password, @c -# rpki_database, @c rpki_user, and @c rpki_password match the values -# you used in your configuration file. -# -# If you are running pubd and are doing manual SQL setup, you'll also -# have to do: -# -# @verbatim -# $ mysql -u root -p -# mysql> CREATE DATABASE pubd_database; -# mysql> GRANT all ON pubd_database.* TO pubd_user@localhost IDENTIFIED BY 'pubd_password'; -# mysql> USE pubd_database; -# mysql> SOURCE $top/rpkid/pubd.sql; -# mysql> COMMIT; -# mysql> quit -# @endverbatim -# -# where @c pubd_database, @c pubd_user @c pubd_password match the -# values you used in your configuration file. -# -# Once you've finished configuring MySQL, the next thing you should -# read is the instructions for the @ref MyRPKI "myrpki tool". - - -## @page MyRPKI The myrpki tool -# -# The design of rpkid and friends assumes that certain tasks can be -# thrown over the wall to the registry's back end operation. This was -# a deliberate design decision to allow rpkid et al to remain -# independent of existing database schema, business PKIs, and so forth -# that a registry might already have. All very nice, but it leaves -# someone who just wants to test the tools or who has no existing back -# end with a fairly large programming project. The @c myrpki tool -# attempts to fill that gap. -# -# @c myrpki is a basic implementation of what a registry back end -# would need to use rpkid and friends. @c myrpki does not use every -# available option in the other programs, nor is it necessarily as -# efficient as possible. Large registries will almost certainly want -# to roll their own tools, perhaps using these as a starting point. -# Nevertheless, we hope that @c myrpki will at least provide a useful -# example, and may be adaquate for simple use. -# -# @c myrpki is (currently) implemented as a single command line Python -# program. It has a number of commands, most of which are used for -# initial setup, some of which are used on an ongoing basis. @c -# myrpki can be run either in an interactive mode or by passing a -# single command on the command line when starting the program; the -# former mode is intended to be somewhat human-friendly, the latter -# mode is useful in scripting, cron jobs, and automated testing. -# -# @c myrpki use has two distinct phases: setup and data maintenance. -# The setup phase is primarily about constructing the "business PKI" -# (BPKI) certificates that the daemons use to authenticate CMS and -# HTTPS messages and obtaining the service URLs needed to configure -# the daemons. The data maintenance phase is about configuring local -# data into the daemons. -# -# @c myrpki uses the OpenSSL command line tool for almost all -# operations on keys and certificates; the one exception to this is -# the comamnd which talks directly to the daemons, as this command -# uses the same communication libraries as the daemons themselves do. -# The intent behind using the OpenSSL command line tool for everything -# else is to allow all the other commands to be run without requiring -# all the auxiliary packages upon which the daemons depend; this can -# be useful, eg, if one wants to run the back-end on a laptop while -# running the daemons on a server, in which case one might prefer not -# to have to install a bunch of unnecessary packages on the laptop. -# -# During setup phase @c myrpki generates and processes small XML -# messages which it expects the user to ship to and from its parents, -# children, etc via some out-of-band means (email, perhaps with PGP -# signatures, USB stick, we really don't care). During data -# maintenance phase, @c myrpki does something similar with another XML -# file, to allow hosting of RPKI services; in the degenerate case -# where an entity is just self-hosting (ie, is running the daemons for -# itself, and only for itself), this latter XML file need not be sent -# anywhere. -# -# The basic idea here is that a user who has resources maintains a set -# of .csv files containing a text representation of the data needed by -# the back-end, along with a configuration file containing other -# parameters. The intent is that these be very simple files that are -# easy to generate either by hand or as a dump from relational -# database, spreadsheet, awk script, whatever works in your -# environment. Given these files, the user then runs @c myrpki to -# extract the relevant information and encode everything about its -# back end state into an XML file, which can then be shipped to the -# appropriate other party. -# -# Many of the @c myrpki commands which process XML input write out a -# new XML file, either in place or as an entirely new file; in -# general, these files need to be sent back to the party that sent the -# original file. Think of all this as a very slow packet-based -# communication channel, where each XML file is a single packet. In -# setup phase, there's generally a single round-trip per setup -# conversation; in the data maintenance phase, the same XML file keeps -# bouncing back and forth between hosted entity and hosting entity. -# -# Note that, as certificates and CRLs have expiration and nextUpdate -# values, a low-level cycle of updates passing between resource holder -# and rpkid operator will be necessary as a part of steady state -# operation. [The current version of these tools does not yet -# regenerate these expiring objects, but fixing this will be a -# relatively minor matter.] -# -# The third important kind of file in this system is the -# @ref Configuration "configuration file" -# for @c myrpki. This contains a number of sections, some of which -# are for myrpki, others of which are for the OpenSSL command line -# tool, still others of which are for the various RPKI daemon -# programs. The examples/ subdirectory contains a commented version -# of the configuration file that explains the various parameters. -# -# The .csv files read by myrpki are (now) misnamed: formerly, they -# used the "excel-tab" format from the Python csv library, but early -# users kept trying to make the colums line up, which didn't do what -# the users expected. So now these files are just -# whitespace-delimted, such as a program like "awk" would understand. -# -# Keep reading, and don't panic. -# -# The default configuration file name for @c myrpki is -# @ref Configuration "@c myrpki.conf". -# You can change this using the "-c" option when invoking myrpki, or -# by setting the environment variable MYRPKI_CONF. -# -# See examples/*.csv for commented examples of the several CSV files. -# Note that the comments themselves are not legal CSV, they're just -# present to make it easier to understand the examples. -# -# @section myrpkioverview myrpki overview -# -# Which process you need to follow depends on whether you are running -# rpkid yourself or will be hosted by somebody else. We call the first -# case "self-hosted", because the software treats running rpkid to -# handle resources that you yourself hold as if you are an rpkid -# operator who is hosting an entity that happens to be yourself. -# -# "$top" in the following refers to wherever you put the -# subvert-rpki.hactrn.net code. Once we have autoconf and "make -# install" targets, this will be some system directory or another; for -# now, it's wherever you checked out a copy of the code from the -# subversion repository or unpacked a tarball of the code. -# -# Most of the setup process looks the same for any resource holder, -# regardless of whether they are self-hosting or not. The differences -# come in the data maintenence phase. -# -# The steps needed during setup phase are: -# -# @li Write a configuration file (copy $top/rpkid/examples/myrpki.conf -# and edit as needed). You need to configure the @c [myrpki] section; -# in theory, the rest of the file should be ok as it is, at least for -# simple use. You also need to create (either by hand or by dumping -# from a database, spreadsheet, whatever) the CSV files describing -# prefixes and ASNs you want to allocate to your children and ROAs -# you want created. -# -# @li Initialization ("initialize" command). This creates the local BPKI -# and other data structures that can be constructed just based on -# local data such as the config file. Other than some internal data -# structures, the main output of this step is the "identity.xml" file, -# which is used as input to later stages. -# -# In theory it should be safe to run the "initialize" command more -# than once, in practice this has not (yet) been tested. -# -# @li Send (email, USB stick, carrier pigeon) identity.xml to each of your -# parents. This tells each of your parents what you call yourself, -# and supplies each parent with a trust anchor for your -# resource-holding BPKI. -# -# @li Each of your parents runs the "configure_child" command, giving -# the identity.xml you supplied as input. This registers your -# data with the parent, including BPKI cross-registration, and -# generates a return message containing your parent's BPKI trust -# anchors, a service URL for contacting your parent via the -# "up-down" protocol, and (usually) either an offer of publication -# service (if your parent operates a repository) or a referral -# from your parent to whatever publication service your parent -# does use. Referrals include a CMS-signed authorization token -# that the repository operator can use to determine that your -# parent has given you permission to home underneath your parent -# in the publication tree. -# -# @li Each of your parents sends (...) back the response XML file -# generated by the "configure_child" command. -# -# @li You feed the response message you just got into myrpki using the -# "configure_parent" command. This registers the parent's -# information in your database, including BPKI -# cross-certification, and processes the repository offer or -# referral to generate a publication request message. -# -# @li You send (...) the publication request message to the -# repository. The @c contact_info element in the request message -# should (in theory) provide some clue as to where you should send -# this. -# -# @li The repository operator processes your request using myrpki's -# "configure_publication_client" command. This registers your -# information, including BPKI cross-certification, and generates a -# response message containing the repository's BPKI trust anchor -# and service URL. -# -# @li Repository operator sends (...) the publication confirmation message -# back to you. -# -# @li You process the publication confirmation message using myrpki's -# "configure_repository" command. -# -# At this point you should, in theory, have established relationships, -# exchanged trust anchors, and obtained service URLs from all of your -# parents and repositories. The last setup step is establishing a -# relationship with your RPKI service host, if you're not self-hosted, -# but as this is really just the first message of an ongoing exchange -# with your host, it's handled by the data maintenance commands. -# -# The two commands used in data maintenence phase are -# "configure_resources" and "configure_daemons". The first is used by -# the resource holder, the second is used by the host. In the -# self-hosted case, it is not necessary to run "configure_resources" at -# all, myrpki will run it for you automatically. -# -# @section myrpkihosted Hosted case -# -# The basic steps involved in getting started for a resource holder who -# is being hosted by somebody else are: -# -# @li Run through steps listed in -# @ref myrpkioverview "the myrpki overview section". -# -# @li Run the configure_resources command to generate myrpki.xml. -# -# @li Send myrpki.xml to the rpkid operator who will be hosting you. -# -# @li Wait for your rpkid operator to ship you back an updated XML -# file containing a PKCS #10 certificate request for the BPKI -# signing context (BSC) created by rpkid. -# -# @li Run configure_resources again with the XML file you just -# received, to issue the BSC certificate and update the XML file -# again to contain the newly issued BSC certificate. -# -# @li Send the updated XML file back to your rpkid operator. -# -# At this point you're done with initial setup. You will need to run -# configure_resources again whenever you make any changes to your -# configuration file or CSV files. -# -# @warning Once myrpki knows how to update -# BPKI CRLs, you will also need to run configure_resources periodically -# to keep your BPKI CRLs up to date. -# -# Any time you run configure_resources myrpki, you should send the -# updated XML file to your rpkid operator, who should send you a -# further updated XML file in response. -# -# @section myrpkiselfhosted Self-hosted case -# -# The first few steps involved in getting started for a self-hosted -# resource holder (that is, a resource holder that runs its own copy -# of rpkid) are the same as in the @ref myrpkihosted "hosted case" -# above; after that the process diverges. -# -# The [current] steps are: -# -# @li Follow the basic installation instructions in -# @ref Installation "the Installation Guide" to build the -# RFC-3779-aware OpenSSL code and associated Python extension -# module. -# -# @li Run through steps listed in -# @ref myrpkioverview "the myrpki overview section". -# -# @li Set up the MySQL databases that rpkid et al will use. The -# package includes a tool to do this for you, you can use that or -# do the job by hand. See -# @ref MySQL-Setup "MySQL database setup" -# for details. -# -# @li If you are running your own publication repository (that is, if -# you are running pubd), you will also need to set up an rsyncd -# server or configure your existing one to serve pubd's output. -# There's a sample configuration file in -# $top/rpkid/examples/rsyncd.conf, but you may need to do -# something more complicated if you are already running rsyncd for -# other purposes. See the rsync(1) and rsyncd.conf(5) manual -# pages for more details. -# -# @li Start the daemons. You can use $top/rpkid/start-servers.py to -# do this, or write your own script. If you intend to run pubd, -# you should make sure that the directory you specified as -# publication_base_directory exists and is writable by the userid -# that will be running pubd, and should also make sure to start -# rsyncd. -# -# @li Run myrpki's configure_daemons command, twice, with no -# arguments. You need to run the command twice because myrpki has -# to ask rpkid to create a keypair and generate a certification -# request for the BSC. The first pass does this, the second -# processes the certification request, issues the BSC, and loads -# the result into rpkid. [Yes, we could automate this somehow, if -# necessary.] -# -# At this point, if everything went well, rpkid should be up, -# configured, and starting to obtain resource certificates from its -# parents, generate CRLs and manifests, and so forth. At this point you -# should go figure out how to use the relying party tool, rcynic: see -# $top/rcynic/README if you haven't already done so. -# -# If and when you change your CSV files, you should run -# configure_daemons again to feed the changes into the daemons. -# -# @section myrpkihosting Hosting case -# -# If you are running rpkid not just for your own resources but also to -# host other resource holders (see @ref myrpkihosted "hosted case" -# above), your setup will be almost the same as in the self-hosted -# case (see @ref myrpkiselfhosted "self-hosted case", above), with one -# procedural change: you will need to tell @c configure_daemons to -# process the XML files produced by the resource holders you are -# hosting. You do this by specifying the names of all those XML files -# on as arguments to the @c configure_daemons command. So, if you are -# hosting two friends, Alice and Bob, then, everywhere the -# instructions for the self-hosted case say to run @c -# configure_daemons with no arguments, you will instead run it with -# the names of Alice's and Bob's XML files as arguments. -# -# Note that @c configure_daemons sometimes modifies these XML files, -# in which case it will write them back to the same filenames. While -# it is possible to figure out the set of circumstances in which this -# will happen (at present, only when @c myrpki has to ask @c rpkid to -# create a new BSC keypair and PKCS #10 certificate request), it may -# be easiest just to ship back an updated copy of the XML file after -# every you run @c configure_daemons. -# -# @section myrpkipurehosting "Pure" hosting case -# -# In general we assume that anybody who bothers to run @c rpkid is -# also a resource holder, but the software does not insist on this. -# -# @todo -# Er, well, rpkid doesn't, but myrpki now does -- "pure" hosting was an -# unused feature that fell by the wayside while simplifying the user -# interface. It would be relatively straightforward to add it back if -# we ever need it for anything, but the mechanism it used to use no -# longer exists -- the old [myirbe] section of the config file has been -# collapsed into the [myrpki] section, so testing for existance of the -# [myrpki] section no longer works. So we'll need an explicit -# configuration option, no big deal, just not worth chasing now. -# -# A (perhaps) plausible use for this capability would be if you are an -# rpkid-running resource holder who wants for some reason to keep the -# resource-holding side of your operation completely separate from the -# rpkid-running side of your operation. This is essentially the -# pure-hosting model, just with an internal hosted entity within a -# different part of your own organization. -# -# @section myrpkitroubleshooting Troubleshooting -# -# If you run into trouble setting up this package, the first thing to do -# is categorize the kind of trouble you are having. If you've gotten -# far enough to be running the daemons, check their log files. If -# you're seeing Python exceptions, read the error messages. If you're -# getting TLS errors, check to make sure that you're using all the right -# BPKI certificates and service contact URLs. -# -# TLS configuration errors are, unfortunately, notoriously difficult to -# debug, because connection failures due to misconfiguration happen -# early, deep in the guts of the OpenSSL TLS code, where there isn't -# enough application context available to provide useful error messages. -# -# If you've completed the steps above, everything appears to have gone -# OK, but nothing seems to be happening, the first thing to do is -# check the logs to confirm that nothing is actively broken. @c -# rpkid's log should include messages telling you when it starts and -# finishes its internal "cron" cycle. It can take several cron cycles -# for resources to work their way down from your parent into a full -# set of certificates and ROAs, so have a little patience. @c rpkid's -# log should also include messages showing every time it contacts its -# parent(s) or attempts to publish anything. -# -# @c rcynic in fully verbose mode provides a fairly detailed -# explanation of what it's doing and why objects that fail have -# failed. -# -# You can use @c rsync (sic) to examine the contents of a publication -# repository one directory at a time, without attempting validation, -# by running rsync with just the URI of the directory on its command -# line: -# -# @verbatim -# $ rsync rsync://rpki.example.org/where/ever/ -# @endverbatim -# -# @section myrpkiknownissues Known Issues -# -# The lxml package provides a Python interface to the Gnome libxml2 -# and libxslt C libraries. This code has been quite stable for -# several years, but initial testing with lxml compiled and linked -# against a newer version of libxml2 ran into problems (specifically, -# gratuitous RelaxNG schema validation failures). libxml2 2.7.3 -# worked; libxml2 2.7.5 did not work on the test machine in question. -# Reverting to libxml2 2.7.3 fixed the problem. Rewriting the two -# lines of Python code that were triggering the lxml bug appears to -# have solved the problem, so the code now works properly with libxml -# 2.7.5, but if you start seeing weird XML validation failures, it -# might be another variation of this lxml bug. -# -# An earlier version of this code ran into problems with what appears to -# be an implementation restriction in the the GNU linker ("ld") on -# 64-bit hardware, resulting in obscure build failures. The workaround -# for this required use of shared libraries and is somewhat less -# portable than the original code, but without it the code simply would -# not build in 64-bit environments with the GNU tools. The current -# workaround appears to behave properly, but the workaround requires -# that the pathname to the RFC-3779-aware OpenSSL shared libraries be -# built into the _POW.so Python extension module. At the moment, in the -# absence of "make install" targets for the Python code and libraries, -# this means the build directory; eventually, once we're using autoconf -# and installation targets, this will be the installation directory. If -# necessary, you can override this by setting the LD_LIBRARY_PATH -# environment variable, see the ld.so man page for details. This is a -# relatively minor variation on the usual build issues for shared -# libraries, it's just annoying because shared libraries should not be -# needed here and would not be if not for this GNU linker issue. - -## @page CommonOptions Common Configuration Options -# -# Some of the options that the several daemons take are common to all -# daemons. Which daemon they affect depends only on which sections of -# which config files they are in. -# -# The first group of options are debugging flags, which can be set to -# "true" or "false". If not specified, default values will be chosen -# (generally false). -# -# @par @c debug_http: -# Enable verbose http debug logging. -# -# @par @c debug_tls_certs: -# Enable verbose logging about tls certs. -# -# @par @c want_persistent_client: -# Enable http 1.1 persistence, client side. -# -# @par @c want_persistent_server: -# Enable http 1.1 persistence, server side. -# -# @par @c debug_cms_certs: -# Enable verbose logging about cms certs. -# -# @par @c sql_debug: -# Enable verbose logging about sql operations. -# -# @par @c gc_debug: -# Enable scary garbage collector debugging. -# -# @par @c timer_debug: -# Enable verbose logging of timer system. -# -# There are also a few options that allow you to save CMS messages for -# audit or debugging. The save format is a simple MIME encoding in a -# Maildir-format mailbox. The current options are very crude, at some -# point we may provide finer grain controls. -# -# @par @c dump_outbound_cms: -# Dump messages we send to this mailbox. -# -# @par @c dump_inbound_cms: -# Dump messages we receive to this mailbox. - -## @page rpkidconf rpkid.conf -# -# rpkid's 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: -# -# @par @c startup-message: -# String to %log on startup, useful when -# debugging a collection of rpkid instances at -# once. -# -# @par @c sql-username: -# Username to hand to MySQL when connecting to -# rpkid's database. -# -# @par @c sql-database: -# MySQL's database name for rpkid's database. -# -# @par @c sql-password: -# Password to hand to MySQL when connecting to -# rpkid's database. -# -# @par @c bpki-ta: -# Name of file containing BPKI trust anchor. -# All BPKI certificate verification within rpkid -# traces back to this trust anchor. -# -# @par @c rpkid-cert: -# Name of file containing rpkid's own BPKI EE -# certificate. -# -# @par @c rpkid-key: -# Name of file containing RSA key corresponding -# to rpkid-cert. -# -# @par @c irbe-cert: -# Name of file containing BPKI certificate used -# by IRBE when talking to rpkid. -# -# @par @c irdb-cert: -# Name of file containing BPKI certificate used -# by irdbd. -# -# @par @c irdb-url: -# Service URL for irdbd. Must be a %https:// URL. -# -# @par @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. -# -# @par @c server-port: -# TCP port on which to listen for HTTPS -# connections. - -## @page pubdconf pubd.conf -# -# pubd's 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: -# -# @par @c sql-username: -# Username to hand to MySQL when connecting to -# pubd's database. -# -# @par @c sql-database: -# MySQL's database name for pubd's database. -# -# @par @c sql-password: -# Password to hand to MySQL when connecting to -# pubd's database. -# -# @par @c bpki-ta: -# Name of file containing master BPKI trust -# anchor for pubd. All BPKI validation in pubd -# traces back to this trust anchor. -# -# @par @c irbe-cert: -# Name of file containing BPKI certificate used -# by IRBE when talking to pubd. -# -# @par @c pubd-cert: -# Name of file containing BPKI certificate used -# by pubd. -# -# @par @c pubd-key: -# Name of file containing RSA key corresponding -# to @c pubd-cert. -# -# @par @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. -# -# @par @c server-port: -# TCP port on which to listen for HTTPS -# connections. -# -# @par @c publication-base: -# Path to base of filesystem tree where pubd -# should store publishable objects. Default is -# "publication/". - -## @page rootdconf rootd.conf -# -# rootd's 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: -# -# @par @c bpki-ta: -# Name of file containing BPKI trust anchor. All -# BPKI certificate validation in rootd traces -# back to this trust anchor. -# -# @par @c rootd-bpki-cert: -# Name of file containing rootd's own BPKI -# certificate. -# -# @par @c rootd-bpki-key: -# Name of file containing RSA key corresponding to -# rootd-bpki-cert. -# -# @par @c rootd-bpki-crl: -# Name of file containing BPKI CRL that would -# cover rootd-bpki-cert had it been revoked. -# -# @par @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). -# -# @par @c server-host: -# Hostname or IP address on which to listen for -# HTTPS connections. Default is localhost. -# -# @par @c server-port: -# TCP port on which to listen for HTTPS -# connections. -# -# @par @c rpki-root-key: -# Name of file containing RSA key to use in -# signing resource certificates. -# -# @par @c rpki-root-cert: -# Name of file containing self-signed root -# resource certificate corresponding to -# rpki-root-key. -# -# @par @c rpki-root-dir: -# Name of directory where rootd should write -# RPKI subject certificate, manifest, and CRL. -# -# @par @c rpki-subject-cert: -# Name of file that rootd should use to save the -# one and only certificate it issues. -# Default is "Subroot.cer". -# -# @par @c rpki-root-crl: -# Name of file to which rootd should save its -# RPKI CRL. Default is "Root.crl". -# -# @par @c rpki-root-manifest: -# Name of file to which rootd should save its -# RPKI manifest. Default is "Root.mnf". -# -# @par @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. - -## @page irdbdconf irdbd.conf -# -# 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: -# -# @par @c startup-message: -# String to %log on startup, useful when -# debugging a collection of irdbd instances at -# once. -# -# @par @c sql-username: -# Username to hand to MySQL when connecting to -# irdbd's database. -# -# @par @c sql-database: -# MySQL's database name for irdbd's database. -# -# @par @c sql-password: -# Password to hand to MySQL when connecting to -# irdbd's database. -# -# @par @c bpki-ta: -# Name of file containing BPKI trust anchor. All -# BPKI certificate validation in irdbd traces -# back to this trust anchor. -# -# @par @c irdbd-cert: -# Name of file containing irdbd's own BPKI -# certificate. -# -# @par @c irdbd-key: -# Name of file containing RSA key corresponding -# to irdbd-cert. -# -# @par @c rpkid-cert: -# Name of file containing certificate used the -# one and only by rpkid instance authorized to -# contact this irdbd instance. -# -# @par @c https-url: -# Service URL for irdbd. Must be a %https:// URL. - -## @page smoketestconf smoketest.conf -# -# All of the options in smoketest's (optional) configuration file are -# overrides for wired-in default values. In almost all cases the -# defaults will suffice. There are a ridiculous number of options, -# most of which noone will ever need, see the code for details. The -# default name for this configuration file is smoketest.conf, run -# smoketest with "-c filename" to change it. - -## @page smoketestyaml smoketest.yaml -# -# smoketest's second configuration file is named smoketest.yaml by -# default, run smoketest 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". -# -# Here's an example of current usage: -# -# @verbatim -# name: Alice -# valid_for: 2d -# sia_base: "rsync://alice.example/rpki/" -# kids: -# - name: Bob -# kids: -# - name: Carol -# ipv4: 192.0.2.1-192.0.2.33 -# asn: 64533 -# --- -# - name: Carol -# valid_add: 10 -# --- -# - name: Carol -# add_as: 33 -# valid_add: 2d -# --- -# - name: Carol -# valid_sub: 2d -# --- -# - name: Carol -# valid_for: 10d -# @endverbatim -# -# This specifies an initial layout consisting of an RPKI engine named -# "Alice", with one child "Bob", which in turn has one child "Carol". -# Carol 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 Carol's resources and makes no other -# modifications. The second subsequent document grants Carol additional -# resources and adds another two days to the validity interval for -# Carol's resources. The next document subtracts two days from the -# validity interval for Carol's resources. The final document sets the -# validity interval for Carol's resources to ten days. -# -# Operators in subsequent (update) documents: -# -# @par @c add_as: -# Add ASN resources. -# -# @par @c add_v4: -# Add IPv4 resources. -# -# @par @c add_v6: -# Add IPv6 resources. -# -# @par @c sub_as: -# Subtract ASN resources. -# -# @par @c sub_v4: -# Subtract IPv4 resources. -# -# @par @c sub_v6: -# Subtract IPv6 resources. -# -# @par @c valid_until: -# Set an absolute expiration date. -# -# @par @c valid_for: -# Set a relative expiration date. -# -# @par @c valid_add: -# Add to validity interval. -# -# @par @c valid_sub: -# Subtract from validity interval. -# -# @par @c sleep [interval]: -# Sleep for specified interval, or until smoketest receives a SIGALRM signal. -# -# @par @c shell cmd...: -# Pass rest of line verbatim to /bin/sh and block until the shell returns. -# -# 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". - - -## @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 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_handle" 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_handle attribute, which must be specified -# for the "create", "set", "get", and "destroy" actions. -# -# Payload data which can be configured in a @c <self/> %object: -# -# @par @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. -# -# @par @c crl_interval (attribute): -# Positive integer representing the planned lifetime of an RPKI CRL -# for this @c <self/>, measured in seconds. -# -# @par @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. This parameter also controls -# how long before the nextUpdate time of CRL or manifest the CRL -# or manifest should be updated. -# -# @par @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. -# -# @par @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: -# -# @par @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. -# -# @par @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. -# -# @par @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. -# -# @par @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. -# -# @par @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_handle, which must be specified for the -# "create", "get", "set", and "destroy" actions. Every @c <bsc/> also has a self_handle -# 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: -# -# @par @c signing_cert (element): -# BPKI certificate to use when generating a signature. -# -# @par @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: -# -# @par @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": -# -# @par @c key_type: -# Type of BPKI keypair to generate. "rsa" is both the default and, -# at the moment, the only allowed value. -# -# @par @c hash_alg: -# Cryptographic hash algorithm to use with this keypair. "sha256" -# is both the default and, at the moment, the only allowed value. -# -# @par @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_handle, which must be specified for -# the "create", "get", "set", and "destroy" actions. Every @c <parent/> also has a -# self_handle attribute which indicates the @c <self/> %object with which this -# @c <parent/> %object is associated, a bsc_handle attribute indicating the @c <bsc/> -# %object to be used when signing messages sent to this parent, and a -# repository_handle 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: -# -# @par @c peer_contact_uri (attribute): -# HTTPS URI used to contact this parent. -# -# @par @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. -# -# @par @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. -# -# @par @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. -# -# @par @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. -# -# @par @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. -# -# @par @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. -# -# @par @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: -# -# @par @c rekey: -# This is like the rekey command in the @c <self/> %object, but limited -# to RPKI CAs under this parent. -# -# @par @c reissue: -# This is like the reissue command in the @c <self/> %object, but limited -# to RPKI CAs under this parent. -# -# @par @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 child_handle, which must be specified for the -# "create", "get", "set", and "destroy" actions. Every @c <child/> also has a -# self_handle 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: -# -# @par @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. -# -# @par @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: -# -# @par @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_handle, which must be -# specified for the "create", "get", "set", and "destroy" actions. Every -# @c <repository/> also has a self_handle 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: -# -# @par @c peer_contact_uri (attribute): -# HTTPS URI used to contact this repository. -# -# @par @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. -# -# @par @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. -# -# @par @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. -# -# @par @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 -# -# This section is out-of-date. The @c <route_origin/> %object -# has been replaced by the @c <list_roa_requests/> IRDB query, -# but the documentation for that hasn't been written yet. -# -# 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: -# -# @par @c asn (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. -# -# @par @c ipv4 (attribute): -# %List of IPv4 prefix and maxLength values, see below for format. -# -# @par @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: -# -# @par @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_handle" attribute naming -# the @c <self/> that is making the request and also a @c "child_handle" -# 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_handle, and @c child_handle copied -# from the request: -# -# @par @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. -# -# @par @c asn: -# A %list of autonomous sequence numbers, expressed as a -# comma-separated sequence of decimal integers with no whitespace. -# -# @par @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. -# -# @par @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_handle" 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 @ref Left-Right "left-right protocol": 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 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: -# -# @par @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_handle" -# attribute, which is used in responses and must be specified in "create", "set", -# "get", or "destroy" actions. -# -# Payload data which can be configured in a <client/> %object: -# -# @par @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. -# -# @par @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. -# -# @par @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 -# -# @image html @abs_top_builddir@/rpkid/doc/rpkid.png "Diagram of rpkid.sql" -# @image latex @abs_top_builddir@/rpkid/doc/rpkid.eps "Diagram of rpkid.sql" height=\textheight -# -# @verbinclude rpkid.sql - -## @page pubd-sql pubd SQL Schema -# -# @image html @abs_top_builddir@/rpkid/doc/pubd.png "Diagram of pubd.sql" -# @image latex @abs_top_builddir@/rpkid/doc/pubd.eps "Diagram of pubd.sql" width=\textwidth -# -# @verbinclude @abs_top_builddir@/rpkid/pubd.sql - -## @page irdbd-sql irdbd SQL Schema -# -# @image html @abs_top_builddir@/rpkid/doc/irdbd.png "Diagram of irdbd.sql" -# @image latex @abs_top_builddir@/rpkid/doc/irdbd.eps "Diagram of irdbd.sql" width=\textwidth -# -# @verbinclude @abs_top_builddir@/rpkid/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 "B" (Business) in "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. -# -# For the most part an ordinary user of this package need not worry -# about the details explained here, as the -# @ref MyRPKI "myrpki tool" -# takes care of all of this. However, users who want to understand -# what's going on behind the scenes or who have needs too complex for -# the myrpki tool to handle might want to understand the underlying -# model. -# -# 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": -# -# @image html @abs_top_builddir@/rpkid/doc/rpkid-bpki.png -# @image latex @abs_top_builddir@/rpkid/doc/rpkid-bpki.eps width=\textwidth -# -# 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; 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": -# -# @image html @abs_top_builddir@/rpkid/doc/pubd-bpki.png -# @image latex @abs_top_builddir@/rpkid/doc/pubd-bpki.eps width=\textwidth -# -# 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: -# mode:python -# compile-command: "cd ../.. && ./config.status && cd rpkid && make docs" -# End: diff --git a/rpkid.with_tls/rpki/__init__.py b/rpkid.with_tls/rpki/__init__.py deleted file mode 100644 index 9e090f63..00000000 --- a/rpkid.with_tls/rpki/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# This file exists to tell Python that this the content of this -# directory constitute a Python package. diff --git a/rpkid.with_tls/rpki/adns.py b/rpkid.with_tls/rpki/adns.py deleted file mode 100644 index e634a30d..00000000 --- a/rpkid.with_tls/rpki/adns.py +++ /dev/null @@ -1,374 +0,0 @@ -""" -Basic asynchronous DNS code, using asyncore and Bob Halley's excellent -dnspython package. - -$Id$ - -Copyright (C) 2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -Portions copyright (C) 2003-2007, 2009, 2010 Nominum, Inc. - -Permission to use, copy, modify, and distribute this software and its -documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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 asyncore, socket, time, sys -import rpki.async, rpki.sundial, rpki.log - -try: - import dns.resolver, dns.rdatatype, dns.rdataclass, dns.name, dns.message - import dns.inet, dns.exception, dns.query, dns.rcode, dns.ipv4, dns.ipv6 -except ImportError: - if __name__ == "__main__": - sys.exit("DNSPython not available, skipping rpki.adns unit test") - else: - raise - -## @var resolver -# Resolver object, shared by everything using this module - -resolver = dns.resolver.Resolver() -if resolver.cache is None: - resolver.cache = dns.resolver.Cache() - -## @var nameservers -# Nameservers from resolver.nameservers converted to (af, address) -# pairs. The latter turns out to be a more useful form for us to use -# internally, because it simplifies the checks we need to make upon -# packet receiption. - -nameservers = [] - -for ns in resolver.nameservers: - try: - nameservers.append((socket.AF_INET, dns.ipv4.inet_aton(ns))) - continue - except: - pass - try: - nameservers.append((socket.AF_INET6, dns.ipv6.inet_aton(ns))) - continue - except: - pass - rpki.log.error("Couldn't parse nameserver address %r" % ns) - -class dispatcher(asyncore.dispatcher): - """ - Basic UDP socket reader for use with asyncore. - """ - - def __init__(self, cb, eb, af, bufsize = 65535): - asyncore.dispatcher.__init__(self) - self.cb = cb - self.eb = eb - self.af = af - self.bufsize = bufsize - self.create_socket(af, socket.SOCK_DGRAM) - - def handle_read(self): - """ - Receive a packet, hand it off to query class callback. - """ - wire, from_address = self.recvfrom(self.bufsize) - self.cb(self.af, from_address[0], from_address[1], wire) - - def handle_error(self): - """ - Pass errors to query class errback. - """ - self.eb(sys.exc_info()[1]) - - def handle_connect(self): - """ - Quietly ignore UDP "connection" events. - """ - pass - - def writable(self): - """ - We don't need to hear about UDP socket becoming writable. - """ - return False - - -class query(object): - """ - Simplified (no search paths) asynchronous adaptation of - dns.resolver.Resolver.query() (q.v.). - """ - - def __init__(self, cb, eb, qname, qtype = dns.rdatatype.A, qclass = dns.rdataclass.IN): - if isinstance(qname, (str, unicode)): - qname = dns.name.from_text(qname) - if isinstance(qtype, str): - qtype = dns.rdatatype.from_text(qtype) - if isinstance(qclass, str): - qclass = dns.rdataclass.from_text(qclass) - assert qname.is_absolute() - self.cb = cb - self.eb = eb - self.qname = qname - self.qtype = qtype - self.qclass = qclass - self.start = time.time() - rpki.async.defer(self.go) - - def go(self): - """ - Start running the query. Check our cache before doing network - query; if we find an answer there, just return it. Otherwise - start the network query. - """ - if resolver.cache: - answer = resolver.cache.get((self.qname, self.qtype, self.qclass)) - else: - answer = None - if answer: - self.cb(self, answer) - else: - self.timer = rpki.async.timer() - self.sockets = {} - self.request = dns.message.make_query(self.qname, self.qtype, self.qclass) - if resolver.keyname is not None: - self.request.use_tsig(resolver.keyring, resolver.keyname, resolver.keyalgorithm) - self.request.use_edns(resolver.edns, resolver.ednsflags, resolver.payload) - self.response = None - self.backoff = 0.10 - self.nameservers = nameservers[:] - self.loop1() - - def loop1(self): - """ - Outer loop. If we haven't got a response yet and still have - nameservers to check, start inner loop. Otherwise, we're done. - """ - self.timer.cancel() - if self.response is None and self.nameservers: - self.iterator = rpki.async.iterator(self.nameservers[:], self.loop2, self.done2) - else: - self.done1() - - def loop2(self, iterator, nameserver): - """ - Inner loop. Send query to next nameserver in our list, unless - we've hit the overall timeout for this query. - """ - self.timer.cancel() - try: - timeout = resolver._compute_timeout(self.start) - except dns.resolver.Timeout, e: - self.lose(e) - else: - af, addr = nameserver - if af not in self.sockets: - self.sockets[af] = dispatcher(self.socket_cb, self.socket_eb, af) - self.sockets[af].sendto(self.request.to_wire(), - (dns.inet.inet_ntop(af, addr), resolver.port)) - self.timer.set_handler(self.socket_timeout) - self.timer.set_errback(self.socket_eb) - self.timer.set(rpki.sundial.timedelta(seconds = timeout)) - - def socket_timeout(self): - """ - No answer from nameserver, move on to next one (inner loop). - """ - self.response = None - self.iterator() - - def socket_eb(self, e): - """ - UDP socket signaled error. If it really is some kind of socket - error, handle as if we've timed out on this nameserver; otherwise, - pass error back to caller. - """ - self.timer.cancel() - if isinstance(e, socket.error): - self.response = None - self.iterator() - else: - self.lose(e) - - def socket_cb(self, af, from_host, from_port, wire): - """ - Received a packet that might be a DNS message. If it doesn't look - like it came from one of our nameservers, just drop it and leave - the timer running. Otherwise, try parsing it: if it's an answer, - we're done, otherwise handle error appropriately and move on to - next nameserver. - """ - sender = (af, dns.inet.inet_pton(af, from_host)) - if from_port != resolver.port or sender not in self.nameservers: - return - self.timer.cancel() - try: - self.response = dns.message.from_wire(wire, keyring = self.request.keyring, request_mac = self.request.mac, one_rr_per_rrset = False) - except dns.exception.FormError: - self.nameservers.remove(sender) - else: - rcode = self.response.rcode() - if rcode in (dns.rcode.NOERROR, dns.rcode.NXDOMAIN): - self.done1() - return - if rcode != dns.rcode.SERVFAIL: - self.nameservers.remove(sender) - self.response = None - self.iterator() - - def done2(self): - """ - Done with inner loop. If we still haven't got an answer and - haven't (yet?) eliminated all of our nameservers, wait a little - while before starting the cycle again, unless we've hit the - timeout threshold for the whole query. - """ - if self.response is None and self.nameservers: - try: - delay = rpki.sundial.timedelta(seconds = min(resolver._compute_timeout(self.start), self.backoff)) - self.backoff *= 2 - self.timer.set_handler(self.loop1) - self.timer.set_errback(self.lose) - self.timer.set(delay) - except dns.resolver.Timeout, e: - self.lose(e) - else: - self.loop1() - - def cleanup(self): - """ - Shut down our timer and sockets. - """ - self.timer.cancel() - for s in self.sockets.itervalues(): - s.close() - - def lose(self, e): - """ - Something bad happened. Clean up, then pass error back to caller. - """ - self.cleanup() - self.eb(self, e) - - def done1(self): - """ - Done with outer loop. If we got a useful answer, cache it, then - pass it back to caller; if we got an error, pass the appropriate - exception back to caller. - """ - self.cleanup() - try: - if not self.nameservers: - raise dns.resolver.NoNameservers - if self.response.rcode() == dns.rcode.NXDOMAIN: - raise dns.resolver.NXDOMAIN - answer = dns.resolver.Answer(self.qname, self.qtype, self.qclass, self.response) - if resolver.cache: - resolver.cache.put((self.qname, self.qtype, self.qclass), answer) - self.cb(self, answer) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - self.lose(e) - -class getaddrinfo(object): - - typemap = { dns.rdatatype.A : socket.AF_INET, - dns.rdatatype.AAAA : socket.AF_INET6 } - - def __init__(self, cb, eb, host, address_families = typemap.values()): - self.cb = cb - self.eb = eb - self.host = host - self.result = [] - self.queries = [query(self.done, self.lose, host, qtype) - for qtype in self.typemap - if self.typemap[qtype] in address_families] - - def done(self, q, answer): - if answer is not None: - for a in answer: - self.result.append((self.typemap[a.rdtype], a.address)) - self.queries.remove(q) - if not self.queries: - self.cb(self.result) - - def lose(self, q, e): - if isinstance(e, dns.resolver.NoAnswer): - self.done(q, None) - else: - for q in self.queries: - q.cleanup() - self.eb(e) - -if __name__ == "__main__": - - rpki.log.use_syslog = False - print "Some adns tests may take a minute or two, please be patient" - - class test_getaddrinfo(object): - - def __init__(self, qname): - self.qname = qname - getaddrinfo(self.done, self.lose, qname) - - def done(self, result): - print "getaddrinfo(%s) returned: %s" % ( - self.qname, - ", ".join(str(r) for r in result)) - - def lose(self, e): - print "getaddrinfo(%s) failed: %r" % (self.qname, e) - - class test_query(object): - - def __init__(self, qname, qtype = dns.rdatatype.A, qclass = dns.rdataclass.IN): - self.qname = qname - self.qtype = qtype - self.qclass = qclass - query(self.done, self.lose, qname, qtype = qtype, qclass = qclass) - - def done(self, q, result): - print "query(%s, %s, %s) returned: %s" % ( - self.qname, - dns.rdatatype.to_text(self.qtype), - dns.rdataclass.to_text(self.qclass), - ", ".join(str(r) for r in result)) - - def lose(self, q, e): - print "getaddrinfo(%s, %s, %s) failed: %r" % ( - self.qname, - dns.rdatatype.to_text(self.qtype), - dns.rdataclass.to_text(self.qclass), - e) - - if True: - for qtype in (dns.rdatatype.A, dns.rdatatype.AAAA, dns.rdatatype.HINFO): - test_query("subvert-rpki.hactrn.net", qtype) - test_query("nonexistant.rpki.net") - test_query("subvert-rpki.hactrn.net", qclass = dns.rdataclass.CH) - - for host in ("subvert-rpki.hactrn.net", "nonexistant.rpki.net"): - test_getaddrinfo(host) - - rpki.async.event_loop() diff --git a/rpkid.with_tls/rpki/async.py b/rpkid.with_tls/rpki/async.py deleted file mode 100644 index e0f9f84c..00000000 --- a/rpkid.with_tls/rpki/async.py +++ /dev/null @@ -1,411 +0,0 @@ -""" -Utilities for event-driven programming. - -$Id$ - -Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -""" - -import asyncore, signal, traceback, gc, sys -import rpki.log, rpki.sundial - -ExitNow = asyncore.ExitNow - -class iterator(object): - """ - Iteration construct for event-driven code. Takes three - arguments: - - - Some kind of iterable object - - - A callback to call on each item in the iteration - - - A callback to call after the iteration terminates. - - The item callback receives two arguments: the callable iterator - object and the current value of the iteration. It should call the - iterator (or arrange for the iterator to be called) when it is time - to continue to the next item in the iteration. - - The termination callback receives no arguments. - """ - - def __init__(self, iterable, item_callback, done_callback, unwind_stack = True): - self.item_callback = item_callback - self.done_callback = done_callback - self.caller_file, self.caller_line, self.caller_function = traceback.extract_stack(limit = 2)[0][0:3] - self.unwind_stack = unwind_stack - try: - self.iterator = iter(iterable) - except (ExitNow, SystemExit): - raise - except: - rpki.log.debug("Problem constructing iterator for %r" % (iterable,)) - raise - self.doit() - - def __repr__(self): - return ("<%s created at %s:%s %s at 0x%x>" % - (self.__class__.__name__, - self.caller_file, self.caller_line, self.caller_function, id(self))) - - def __call__(self): - if self.unwind_stack: - defer(self.doit) - else: - self.doit() - - def doit(self): - """ - Implement the iterator protocol: attempt to call the item handler - with the next iteration value, call the termination handler if the - iterator signaled StopIteration. - """ - try: - self.item_callback(self, self.iterator.next()) - except StopIteration: - if self.done_callback is not None: - self.done_callback() - -class timer(object): - """ - Timer construct for event-driven code. It can be used in either of two ways: - - - As a virtual class, in which case the subclass should provide a - handler() method to receive the wakup event when the timer expires; or - - - By setting an explicit handler callback, either via the - constructor or the set_handler() method. - - Subclassing is probably more Pythonic, but setting an explict - handler turns out to be very convenient when combined with bound - methods to other objects. - """ - - ## @var gc_debug - # Verbose chatter about timers states and garbage collection. - gc_debug = False - - ## @var run_debug - # Verbose chatter about timers being run. - run_debug = False - - ## @var queue - # Timer queue, shared by all timer instances (there can be only one queue). - queue = [] - - def __init__(self, handler = None, errback = None): - if handler is not None: - self.set_handler(handler) - if errback is not None: - self.set_errback(errback) - self.when = None - if self.gc_debug: - self.trace("Creating %r" % self) - - def trace(self, msg): - """ - Debug logging. - """ - if self.gc_debug: - bt = traceback.extract_stack(limit = 3) - rpki.log.debug("%s from %s:%d" % (msg, bt[0][0], bt[0][1])) - - def set(self, when): - """ - Set a timer. Argument can be a datetime, to specify an absolute - time, or a timedelta, to specify an offset time. - """ - if self.gc_debug: - self.trace("Setting %r to %r" % (self, when)) - if isinstance(when, rpki.sundial.timedelta): - self.when = rpki.sundial.now() + when - else: - self.when = when - assert isinstance(self.when, rpki.sundial.datetime), "%r: Expecting a datetime, got %r" % (self, self.when) - if self not in self.queue: - self.queue.append(self) - self.queue.sort(key = lambda x: x.when) - - def __cmp__(self, other): - return cmp(id(self), id(other)) - - if gc_debug: - def __del__(self): - rpki.log.debug("Deleting %r" % self) - - def cancel(self): - """ - Cancel a timer, if it was set. - """ - if self.gc_debug: - self.trace("Canceling %r" % self) - try: - while True: - self.queue.remove(self) - except ValueError: - pass - - def is_set(self): - """Test whether this timer is currently set.""" - return self in self.queue - - def handler(self): - """ - Handle a timer that has expired. This must either be overriden by - a subclass or set dynamically by set_handler(). - """ - raise NotImplementedError - - def set_handler(self, handler): - """ - Set timer's expiration handler. This is an alternative to - subclassing the timer class, and may be easier to use when - integrating timers into other classes (eg, the handler can be a - bound method to an object in a class representing a network - connection). - """ - self.handler = handler - - def errback(self, e): - """ - Error callback. May be overridden, or set with set_errback(). - """ - rpki.log.error("Unhandled exception from timer: %s" % e) - rpki.log.traceback() - - def set_errback(self, errback): - """Set a timer's errback. Like set_handler(), for errbacks.""" - self.errback = errback - - @classmethod - def runq(cls): - """ - Run the timer queue: for each timer whose call time has passed, - pull the timer off the queue and call its handler() method. - """ - while cls.queue and rpki.sundial.now() >= cls.queue[0].when: - t = cls.queue.pop(0) - if cls.run_debug: - rpki.log.debug("Running %r" % t) - try: - t.handler() - except (ExitNow, SystemExit): - raise - except Exception, e: - t.errback(e) - - def __repr__(self): - return "<%s %r %r at 0x%x>" % (self.__class__.__name__, self.when, self.handler, id(self)) - - @classmethod - def seconds_until_wakeup(cls): - """ - Calculate delay until next timer expires, or None if no timers are - set and we should wait indefinitely. Rounds up to avoid spinning - in select() or poll(). We could calculate fractional seconds in - the right units instead, but select() and poll() don't even take - the same units (argh!), and we're not doing anything that - hair-triggered, so rounding up is simplest. - """ - if not cls.queue: - return None - now = rpki.sundial.now() - if now >= cls.queue[0].when: - return 0 - delay = cls.queue[0].when - now - seconds = delay.convert_to_seconds() - if delay.microseconds: - seconds += 1 - return seconds - - @classmethod - def clear(cls): - """ - Cancel every timer on the queue. We could just throw away the - queue content, but this way we can notify subclasses that provide - their own cancel() method. - """ - while cls.queue: - cls.queue.pop(0).cancel() - -## @var deferred_queue -# List to hold deferred actions. We used to do this with the timer -# queue, but that appears to confuse the garbage collector, and is -# overengineering for simple deferred actions in any case. - -deferred_queue = [] - -def defer(thunk): - """ - Defer an action until the next pass through the event loop. - """ - deferred_queue.append(thunk) - -def run_deferred(): - """ - Run deferred actions. - """ - while deferred_queue: - try: - deferred_queue.pop(0)() - except (ExitNow, SystemExit): - raise - except Exception, e: - rpki.log.error("Unhandled exception from deferred action: %s" % e) - rpki.log.traceback() - -def _raiseExitNow(signum, frame): - """Signal handler for event_loop().""" - raise ExitNow - -def event_loop(catch_signals = (signal.SIGINT, signal.SIGTERM)): - """ - Replacement for asyncore.loop(), adding timer and signal support. - """ - while True: - old_signal_handlers = {} - try: - for sig in catch_signals: - old_signal_handlers[sig] = signal.signal(sig, _raiseExitNow) - while asyncore.socket_map or deferred_queue or timer.queue: - run_deferred() - asyncore.poll(timer.seconds_until_wakeup(), asyncore.socket_map) - run_deferred() - timer.runq() - if timer.gc_debug: - gc.collect() - if gc.garbage: - for i in gc.garbage: - rpki.log.debug("GC-cycle %r" % i) - del gc.garbage[:] - except ExitNow: - break - except SystemExit: - raise - except Exception, e: - rpki.log.error("event_loop() exited with exception %r, this is not supposed to happen, restarting" % e) - else: - break - finally: - for sig in old_signal_handlers: - signal.signal(sig, old_signal_handlers[sig]) - -class sync_wrapper(object): - """ - Synchronous wrapper around asynchronous functions. Running in - asynchronous mode at all times makes sense for event-driven daemons, - but is kind of tedious for simple scripts, hence this wrapper. - - The wrapped function should take at least two arguments: a callback - function and an errback function. If any arguments are passed to - the wrapper, they will be passed as additional arguments to the - wrapped function. - """ - - res = None - err = None - - def __init__(self, func): - self.func = func - - def cb(self, res = None): - """ - Wrapped code has requested normal termination. Store result, and - exit the event loop. - """ - self.res = res - raise ExitNow - - def eb(self, err): - """ - Wrapped code raised an exception. Store exception data, then exit - the event loop. - """ - exc_info = sys.exc_info() - self.err = exc_info if exc_info[1] is err else err - raise ExitNow - - def __call__(self, *args, **kwargs): - - def thunk(): - """ - Deferred action to call the wrapped code once event system is - running. - """ - try: - self.func(self.cb, self.eb, *args, **kwargs) - except ExitNow: - raise - except Exception, e: - self.eb(e) - - defer(thunk) - event_loop() - if self.err is not None: - if isinstance(self.err, tuple): - raise self.err[0], self.err[1], self.err[2] - else: - raise self.err - else: - return self.res - -def exit_event_loop(): - """Force exit from event_loop().""" - raise ExitNow - -class gc_summary(object): - """ - Periodic summary of GC state, for tracking down memory bloat. - """ - - def __init__(self, interval, threshold = 0): - if isinstance(interval, (int, long)): - interval = rpki.sundial.timedelta(seconds = interval) - self.interval = interval - self.threshold = threshold - self.timer = timer(handler = self.handler) - self.timer.set(self.interval) - - def handler(self): - """ - Collect and log GC state for this period, reset timer. - """ - rpki.log.debug("gc_summary: Running gc.collect()") - gc.collect() - rpki.log.debug("gc_summary: Summarizing (threshold %d)" % self.threshold) - total = {} - tuples = {} - for g in gc.get_objects(): - k = type(g).__name__ - total[k] = total.get(k, 0) + 1 - if isinstance(g, tuple): - k = ", ".join(type(x).__name__ for x in g) - tuples[k] = tuples.get(k, 0) + 1 - rpki.log.debug("gc_summary: Sorting result") - total = total.items() - total.sort(reverse = True, key = lambda x: x[1]) - tuples = tuples.items() - tuples.sort(reverse = True, key = lambda x: x[1]) - rpki.log.debug("gc_summary: Object type counts in descending order") - for name, count in total: - if count > self.threshold: - rpki.log.debug("gc_summary: %8d %s" % (count, name)) - rpki.log.debug("gc_summary: Tuple content type signature counts in descending order") - for types, count in tuples: - if count > self.threshold: - rpki.log.debug("gc_summary: %8d (%s)" % (count, types)) - rpki.log.debug("gc_summary: Scheduling next cycle") - self.timer.set(self.interval) diff --git a/rpkid.with_tls/rpki/cli.py b/rpkid.with_tls/rpki/cli.py deleted file mode 100644 index d31f7204..00000000 --- a/rpkid.with_tls/rpki/cli.py +++ /dev/null @@ -1,176 +0,0 @@ -""" -Utilities for writing command line tools. - -$Id$ - -Copyright (C) 2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -""" - -import cmd, glob, os.path, traceback - -try: - import readline - have_readline = True -except ImportError: - have_readline = False - -class Cmd(cmd.Cmd): - """ - Customized subclass of Python cmd module. - """ - - emptyline_repeats_last_command = False - - EOF_exits_command_loop = True - - identchars = cmd.IDENTCHARS + "/-." - - histfile = None - - def __init__(self, argv = None): - cmd.Cmd.__init__(self) - if argv: - self.onecmd(" ".join(argv)) - else: - self.cmdloop_with_history() - - def onecmd(self, line): - """ - Wrap error handling around cmd.Cmd.onecmd(). Might want to do - something kinder than showing a traceback, eventually. - """ - try: - return cmd.Cmd.onecmd(self, line) - except SystemExit: - raise - except: - traceback.print_exc() - - def do_EOF(self, arg): - """ - Exit program. - """ - if self.EOF_exits_command_loop and self.prompt: - print - return self.EOF_exits_command_loop - - def do_exit(self, arg): - """ - Exit program. - """ - return True - - do_quit = do_exit - - def emptyline(self): - """ - Handle an empty line. cmd module default is to repeat the last - command, which I find to be violation of the principal of least - astonishment, so my preference is that an empty line does nothing. - """ - if self.emptyline_repeats_last_command: - cmd.Cmd.emptyline(self) - - def filename_complete(self, text, line, begidx, endidx): - """ - Filename completion handler, with hack to restore what I consider - the normal (bash-like) behavior when one hits the completion key - and there's only one match. - """ - result = glob.glob(text + "*") - if len(result) == 1: - path = result.pop() - if os.path.isdir(path) or (os.path.islink(path) and os.path.isdir(os.path.join(path, "."))): - result.append(path + os.path.sep) - else: - result.append(path + " ") - return result - - def completenames(self, text, *ignored): - """ - Command name completion handler, with hack to restore what I - consider the normal (bash-like) behavior when one hits the - completion key and there's only one match. - """ - result = set(cmd.Cmd.completenames(self, text, *ignored)) - if len(result) == 1: - result.add(result.pop() + " ") - return list(result) - - def help_help(self): - """ - Type "help [topic]" for help on a command, - or just "help" for a list of commands. - """ - self.stdout.write(self.help_help.__doc__ + "\n") - - def complete_help(self, *args): - """ - Better completion function for help command arguments. - """ - text = args[0] - names = self.get_names() - result = [] - for prefix in ("do_", "help_"): - result.extend(s[len(prefix):] for s in names if s.startswith(prefix + text)) - return result - - if have_readline: - - def cmdloop_with_history(self): - """ - Better command loop, with history file and tweaked readline - completion delimiters. - """ - old_completer_delims = readline.get_completer_delims() - if self.histfile is not None: - try: - readline.read_history_file(self.histfile) - except IOError: - pass - try: - readline.set_completer_delims("".join(set(old_completer_delims) - set(self.identchars))) - self.cmdloop() - finally: - if self.histfile is not None and readline.get_current_history_length(): - readline.write_history_file(self.histfile) - readline.set_completer_delims(old_completer_delims) - - else: - - cmdloop_with_history = cmd.Cmd.cmdloop - - - -def yes_or_no(prompt, default = None, require_full_word = False): - """ - Ask a yes-or-no question. - """ - prompt = prompt.rstrip() + _yes_or_no_prompts[default] - while True: - answer = raw_input(prompt).strip().lower() - if not answer and default is not None: - return default - if answer == "yes" or (not require_full_word and answer.startswith("y")): - return True - if answer == "no" or (not require_full_word and answer.startswith("n")): - return False - print 'Please answer "yes" or "no"' - -_yes_or_no_prompts = { - True : ' ("yes" or "no" ["yes"]) ', - False : ' ("yes" or "no" ["no"]) ', - None : ' ("yes" or "no") ' } - diff --git a/rpkid.with_tls/rpki/config.py b/rpkid.with_tls/rpki/config.py deleted file mode 100644 index 87554a53..00000000 --- a/rpkid.with_tls/rpki/config.py +++ /dev/null @@ -1,229 +0,0 @@ -""" -Configuration file parsing utilities, layered on top of stock Python -ConfigParser module. - -$Id$ - -Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -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 ConfigParser, os, re - -class parser(object): - """ - Extensions to stock Python ConfigParser: - - Read config file and set default section while initializing parser object. - - Support for OpenSSL-style subscripted options and a limited form of - OpenSSL-style indirect variable references (${section::option}). - - get-methods with default values and default section name. - """ - - def __init__(self, filename, section = None, allow_missing = False): - """ - Initialize this parser. - """ - - self.filename = filename - self.cfg = ConfigParser.RawConfigParser() - try: - self.cfg.readfp(open(filename), filename) - except IOError: - if not allow_missing: - raise - self.default_section = section - - def has_section(self, section): - """ - Test whether a section exists. - """ - - return self.cfg.has_section(section) - - def has_option(self, option, section = None): - """ - Test whether an option exists. - """ - - if section is None: - section = self.default_section - return self.cfg.has_option(section, option) - - 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.cfg.has_option(section, option): - matches.append((-1, self.get(option, section = section))) - for key, value in self.cfg.items(section): - s = key.rsplit(".", 1) - if len(s) == 2 and s[0] == option and s[1].isdigit(): - matches.append((int(s[1]), self.get(option, section = section))) - matches.sort() - return [match[1] for match in matches] - - _regexp = re.compile("\\${(.*?)::(.*?)}") - - def _repl(self, m): - """ - Replacement function for indirect variable substitution. - This is intended for use with re.subn(). - """ - section, option = m.group(1, 2) - if section == "ENV": - return os.getenv(option, "") - else: - return self.cfg.get(section, option) - - 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 not None and not self.cfg.has_option(section, option): - return default - val = self.cfg.get(section, option) - while True: - val, modified = self._regexp.subn(self._repl, val, 1) - if not modified: - return val - - def getboolean(self, option, default = None, section = None): - """ - Get a boolean option, perhaps with a default value. - """ - v = self.get(option, default, section) - if isinstance(v, str): - v = v.lower() - if v not in self.cfg._boolean_states: - raise ValueError, "Not a boolean: %s" % v - v = self.cfg._boolean_states[v] - return v - - def getint(self, option, default = None, section = None): - """ - Get an integer option, perhaps with a default value. - """ - return int(self.get(option, default, section)) - - def getlong(self, option, default = None, section = None): - """ - Get a long integer option, perhaps with a default value. - """ - return long(self.get(option, default, section)) - - def set_global_flags(self): - """ - Consolidated control for all the little global control flags - scattered through the libraries. This isn't a particularly good - place for this function to live, but it has to live somewhere and - making it a method of the config parser from which it gets all of - its data is less silly than the available alternatives. - """ - - import rpki.https, rpki.x509, rpki.sql, rpki.async - - try: - rpki.https.debug_http = self.getboolean("debug_http") - except ConfigParser.NoOptionError: - pass - - try: - rpki.https.debug_tls_certs = self.getboolean("debug_tls_certs") - except ConfigParser.NoOptionError: - pass - - try: - rpki.https.want_persistent_client = self.getboolean("want_persistent_client") - except ConfigParser.NoOptionError: - pass - - try: - rpki.https.want_persistent_server = self.getboolean("want_persistent_server") - except ConfigParser.NoOptionError: - pass - - try: - rpki.https.use_adns = self.getboolean("use_adns") - except ConfigParser.NoOptionError: - pass - - try: - rpki.https.enable_ipv6_clients = self.getboolean("enable_ipv6_clients") - except ConfigParser.NoOptionError: - pass - - try: - rpki.https.enable_ipv6_servers = self.getboolean("enable_ipv6_servers") - except ConfigParser.NoOptionError: - pass - - try: - rpki.x509.CMS_object.debug_cms_certs = self.getboolean("debug_cms_certs") - except ConfigParser.NoOptionError: - pass - - try: - rpki.sql.sql_persistent.sql_debug = self.getboolean("sql_debug") - except ConfigParser.NoOptionError: - pass - - try: - rpki.async.timer.gc_debug = self.getboolean("gc_debug") - except ConfigParser.NoOptionError: - pass - - try: - rpki.async.timer.run_debug = self.getboolean("timer_debug") - except ConfigParser.NoOptionError: - pass - - try: - rpki.x509.XML_CMS_object.dump_outbound_cms = rpki.x509.DeadDrop(self.get("dump_outbound_cms")) - except ConfigParser.NoOptionError: - pass - - try: - rpki.x509.XML_CMS_object.dump_inbound_cms = rpki.x509.DeadDrop(self.get("dump_inbound_cms")) - except ConfigParser.NoOptionError: - pass - - try: - rpki.async.gc_summary(self.getint("gc_summary"), self.getint("gc_summary_threshold", 0)) - except ConfigParser.NoOptionError: - pass diff --git a/rpkid.with_tls/rpki/exceptions.py b/rpkid.with_tls/rpki/exceptions.py deleted file mode 100644 index 8e77beab..00000000 --- a/rpkid.with_tls/rpki/exceptions.py +++ /dev/null @@ -1,328 +0,0 @@ -""" -Exception definitions for RPKI modules. - -$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. -""" - -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 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. - """ - -class BadClientURL(RPKI_Exception): - """ - URL given to HTTPS client does not match profile. - """ - -class ClientNotFound(RPKI_Exception): - """ - Could not find specified client in database. - """ - -class BadExtension(RPKI_Exception): - """ - Forbidden X.509 extension. - """ - -class ForbiddenURI(RPKI_Exception): - """ - Forbidden URI, does not start with correct base URI. - """ - -class HTTPSClientAborted(RPKI_Exception): - """ - HTTPS client connection closed while in request-sent state. - """ - -class BadPublicationReply(RPKI_Exception): - """ - Unexpected reply to publication query. - """ - -class DuplicateObject(RPKI_Exception): - """ - Attempt to create an object that already exists. - """ - -class EmptyROAPrefixList(RPKI_Exception): - """ - Can't create ROA with an empty prefix list. - """ - -class NoCoveringCertForROA(RPKI_Exception): - """ - Couldn't find a covering certificate to generate ROA. - """ - -class BSCNotReady(RPKI_Exception): - """ - BSC not yet in a usable state, signing_cert not set. - """ - -class HTTPSUnexpectedState(RPKI_Exception): - """ - HTTPS event occurred in an unexpected state. - """ - -class HTTPSBadVersion(RPKI_Exception): - """ - HTTPS couldn't parse HTTP version. - """ - -class HandleTranslationError(RPKI_Exception): - """ - Internal error translating protocol handle -> SQL id. - """ - -class NoObjectAtURI(RPKI_Exception): - """ - No object published at specified URI. - """ - -class CMSContentNotSet(RPKI_Exception): - """ - Inner content of a CMS_object has not been set. If object is known - to be valid, the .extract() method should be able to set the - content; otherwise, only the .verify() method (which checks - signatures) is safe. - """ - -class HTTPTimeout(RPKI_Exception): - """ - HTTP connection timed out. - """ - -class BadIPResource(RPKI_Exception): - """ - Parse failure for alleged IP resource string. - """ - -class BadROAPrefix(RPKI_Exception): - """ - Parse failure for alleged ROA prefix string. - """ - -class CommandParseFailure(RPKI_Exception): - """ - Failed to parse command line. - """ diff --git a/rpkid.with_tls/rpki/https.py b/rpkid.with_tls/rpki/https.py deleted file mode 100644 index 8592b578..00000000 --- a/rpkid.with_tls/rpki/https.py +++ /dev/null @@ -1,1245 +0,0 @@ -""" -HTTPS utilities, both client and server. - -$Id$ - -Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -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 time, socket, asyncore, asynchat, urlparse, sys, random -import rpki.async, rpki.sundial, rpki.x509, rpki.exceptions, rpki.log -import POW - -## @var rpki_content_type -# HTTP content type used for all RPKI messages. -rpki_content_type = "application/x-rpki" - -## @var debug_http -# Verbose chatter about HTTP streams. -debug_http = False - -## @var debug_tls_certs -# Verbose chatter about TLS certificates. -debug_tls_certs = False - -## @var want_persistent_client -# Whether we want persistent HTTP client streams, when server also supports them. -want_persistent_client = False - -## @var want_persistent_server -# Whether we want persistent HTTP server streams, when client also supports them. -want_persistent_server = False - -## @var default_client_timeout -# Default HTTP client connection timeout. -default_client_timeout = rpki.sundial.timedelta(minutes = 15) - -## @var default_server_timeout -# Default HTTP server connection timeouts. Given our druthers, we'd -# prefer that the client close the connection, as this avoids the -# problem of client starting to reuse connection just as server closes -# it, so this should be longer than the client timeout. -default_server_timeout = rpki.sundial.timedelta(minutes = 20) - -## @var default_http_version -# Preferred HTTP version. -default_http_version = (1, 0) - -## @var default_tcp_port -# Default port for clients and servers that don't specify one. -default_tcp_port = 443 - -## @var enable_ipv6_servers -# Whether to enable IPv6 listeners. Enabled by default, as it should -# be harmless. Has no effect if kernel doesn't support IPv6. -enable_ipv6_servers = True - -## @var enable_ipv6_clients -# Whether to consider IPv6 addresses when making connections. -# Disabled by default, as IPv6 connectivity is still a bad joke in -# far too much of the world. -enable_ipv6_clients = False - -## @var use_adns -# Whether to use rpki.adns code. This is still experimental, so it's -# not (yet) enabled by default. -use_adns = False - -## @var have_ipv6 -# Whether the current machine claims to support IPv6. Note that just -# because the kernel supports it doesn't mean that the machine has -# usable IPv6 connectivity. I don't know of a simple portable way to -# probe for connectivity at runtime (the old test of "can you ping -# SRI-NIC.ARPA?" seems a bit dated...). Don't set this, it's set -# automatically by probing using the socket() system call at runtime. -try: - socket.socket(socket.AF_INET6).close() - socket.IPPROTO_IPV6 - socket.IPV6_V6ONLY -except: - have_ipv6 = False -else: - have_ipv6 = True - -def supported_address_families(enable_ipv6): - """ - IP address families on which servers should listen, and to consider - when selecting addresses for client connections. - """ - if enable_ipv6 and have_ipv6: - return (socket.AF_INET, socket.AF_INET6) - else: - return (socket.AF_INET,) - -def localhost_addrinfo(): - """ - Return pseudo-getaddrinfo results for localhost. - """ - result = [(socket.AF_INET, "127.0.0.1")] - if enable_ipv6_clients and have_ipv6: - result.append((socket.AF_INET6, "::1")) - return result - -class http_message(object): - """ - Virtual class representing of one HTTP message. - """ - - software_name = "ISC RPKI library" - - def __init__(self, version = None, body = None, headers = None): - self.version = version - self.body = body - self.headers = headers - self.normalize_headers() - - def normalize_headers(self, headers = None): - """ - Clean up (some of) the horrible messes that HTTP allows in its - headers. - """ - if headers is None: - headers = () if self.headers is None else self.headers.items() - translate_underscore = True - else: - translate_underscore = False - result = {} - for k, v in headers: - if translate_underscore: - k = k.replace("_", "-") - k = "-".join(s.capitalize() for s in k.split("-")) - v = v.strip() - if k in result: - result[k] += ", " + v - else: - result[k] = v - self.headers = result - - @classmethod - def parse_from_wire(cls, headers): - """ - Parse and normalize an incoming HTTP message. - """ - self = cls() - headers = headers.split("\r\n") - self.parse_first_line(*headers.pop(0).split(None, 2)) - for i in xrange(len(headers) - 2, -1, -1): - if headers[i + 1][0].isspace(): - headers[i] += headers[i + 1] - del headers[i + 1] - self.normalize_headers([h.split(":", 1) for h in headers]) - return self - - def format(self): - """ - Format an outgoing HTTP message. - """ - s = self.format_first_line() - if self.body is not None: - assert isinstance(self.body, str) - self.headers["Content-Length"] = len(self.body) - for kv in self.headers.iteritems(): - s += "%s: %s\r\n" % kv - s += "\r\n" - if self.body is not None: - s += self.body - return s - - def __str__(self): - return self.format() - - def parse_version(self, version): - """ - Parse HTTP version, raise an exception if we can't. - """ - if version[:5] != "HTTP/": - raise rpki.exceptions.HTTPSBadVersion, "Couldn't parse version %s" % version - self.version = tuple(int(i) for i in version[5:].split(".")) - - def persistent(self): - """ - Figure out whether this HTTP message encourages a persistent connection. - """ - c = self.headers.get("Connection") - if self.version == (1, 1): - return c is None or "close" not in c.lower() - elif self.version == (1, 0): - return c is not None and "keep-alive" in c.lower() - else: - return False - -class http_request(http_message): - """ - HTTP request message. - """ - - def __init__(self, cmd = None, path = None, version = default_http_version, body = None, callback = None, errback = None, **headers): - assert cmd == "POST" or body is None - http_message.__init__(self, version = version, body = body, headers = headers) - self.cmd = cmd - self.path = path - self.callback = callback - self.errback = errback - self.retried = False - - def parse_first_line(self, cmd, path, version): - """ - Parse first line of HTTP request message. - """ - self.parse_version(version) - self.cmd = cmd - self.path = path - - def format_first_line(self): - """ - Format first line of HTTP request message, and set up the - User-Agent header. - """ - self.headers.setdefault("User-Agent", self.software_name) - return "%s %s HTTP/%d.%d\r\n" % (self.cmd, self.path, self.version[0], self.version[1]) - -class http_response(http_message): - """ - HTTP response message. - """ - - def __init__(self, code = None, reason = None, version = default_http_version, body = None, **headers): - http_message.__init__(self, version = version, body = body, headers = headers) - self.code = code - self.reason = reason - - def parse_first_line(self, version, code, reason): - """ - Parse first line of HTTP response message. - """ - self.parse_version(version) - self.code = int(code) - self.reason = reason - - def format_first_line(self): - """ - Format first line of HTTP response message, and set up Date and - Server headers. - """ - self.headers.setdefault("Date", time.strftime("%a, %d %b %Y %T GMT")) - self.headers.setdefault("Server", self.software_name) - return "HTTP/%d.%d %s %s\r\n" % (self.version[0], self.version[1], self.code, self.reason) - -def log_method(self, msg, logger = rpki.log.debug): - """ - Logging method used in several different classes. - """ - assert isinstance(logger, rpki.log.logger) - if debug_http or logger is not rpki.log.debug: - logger("%r: %s" % (self, msg)) - -class http_stream(asynchat.async_chat): - """ - Virtual class representing an HTTP message stream. - """ - - log = log_method - tls = None - retry_read = None - retry_write = None - - def __init__(self, sock = None): - asynchat.async_chat.__init__(self, sock) - self.buffer = [] - self.timer = rpki.async.timer(self.handle_timeout) - self.restart() - - def restart(self): - """ - (Re)start HTTP message parser, reset timer. - """ - assert not self.buffer - self.chunk_handler = None - self.set_terminator("\r\n\r\n") - self.update_timeout() - - def update_timeout(self): - """ - Put this stream's timer in known good state: set it to the - stream's timeout value if we're doing timeouts, otherwise clear - it. - """ - if self.timeout is not None: - self.log("Setting timeout %r" % self.timeout) - self.timer.set(self.timeout) - else: - self.log("Clearing timeout") - self.timer.cancel() - - def collect_incoming_data(self, data): - """ - Buffer incoming data from asynchat. - """ - self.buffer.append(data) - self.update_timeout() - - def get_buffer(self): - """ - Consume data buffered from asynchat. - """ - val = "".join(self.buffer) - self.buffer = [] - return val - - def found_terminator(self): - """ - Asynchat reported that it found whatever terminator we set, so - figure out what to do next. This can be messy, because we can be - in any of several different states: - - @li We might be handling chunked HTTP, in which case we have to - initialize the chunk decoder; - - @li We might have found the end of the message body, in which case - we can (finally) process it; or - - @li We might have just gotten to the end of the message headers, - in which case we have to parse them to figure out which of three - separate mechanisms (chunked, content-length, TCP close) is going - to tell us how to find the end of the message body. - """ - self.update_timeout() - if self.chunk_handler: - self.chunk_handler() - elif not isinstance(self.get_terminator(), str): - self.handle_body() - else: - self.msg = self.parse_type.parse_from_wire(self.get_buffer()) - if self.msg.version == (1, 1) and "chunked" in self.msg.headers.get("Transfer-Encoding", "").lower(): - self.msg.body = [] - self.chunk_handler = self.chunk_header - self.set_terminator("\r\n") - elif "Content-Length" in self.msg.headers: - self.set_terminator(int(self.msg.headers["Content-Length"])) - else: - self.handle_no_content_length() - - def chunk_header(self): - """ - Asynchat just handed us what should be the header of one chunk of - a chunked encoding stream. If this chunk has a body, set the - stream up to read it; otherwise, this is the last chunk, so start - the process of exiting the chunk decoder. - """ - n = int(self.get_buffer().partition(";")[0], 16) - self.log("Chunk length %s" % n) - if n: - self.chunk_handler = self.chunk_body - self.set_terminator(n) - else: - self.msg.body = "".join(self.msg.body) - self.chunk_handler = self.chunk_discard_trailer - - def chunk_body(self): - """ - Asynchat just handed us what should be the body of a chunk of the - body of a chunked message (sic). Save it, and prepare to move on - to the next chunk. - """ - self.log("Chunk body") - self.msg.body += self.buffer - self.buffer = [] - self.chunk_handler = self.chunk_discard_crlf - self.set_terminator("\r\n") - - def chunk_discard_crlf(self): - """ - Consume the CRLF that terminates a chunk, reinitialize chunk - decoder to be ready for the next chunk. - """ - self.log("Chunk CRLF") - s = self.get_buffer() - assert s == "", "%r: Expected chunk CRLF, got '%s'" % (self, s) - self.chunk_handler = self.chunk_header - - def chunk_discard_trailer(self): - """ - Consume chunk trailer, which should be empty, then (finally!) exit - the chunk decoder and hand complete message off to the application. - """ - self.log("Chunk trailer") - s = self.get_buffer() - assert s == "", "%r: Expected end of chunk trailers, got '%s'" % (self, s) - self.chunk_handler = None - self.handle_message() - - def handle_body(self): - """ - Hand normal (not chunked) message off to the application. - """ - self.msg.body = self.get_buffer() - self.handle_message() - - def handle_error(self): - """ - Asynchat (or asyncore, or somebody) raised an exception. See - whether it's one we should just pass along, otherwise log a stack - trace and close the stream. - """ - etype = sys.exc_info()[0] - if etype in (SystemExit, rpki.async.ExitNow): - self.log("Caught %s, propagating" % etype.__name__) - raise - self.log("Error in HTTP stream handler", rpki.log.warn) - rpki.log.traceback() - if etype not in (rpki.exceptions.HTTPSClientAborted,): - self.log("Closing due to error", rpki.log.warn) - self.close(force = True) - - def handle_timeout(self): - """ - Inactivity timer expired, close connection with prejudice. - """ - self.log("Timeout, closing") - self.close(force = True) - - def handle_close(self): - """ - Wrapper around asynchat connection close handler, so that we can - log the event. - """ - self.log("Close event in HTTP stream handler") - asynchat.async_chat.handle_close(self) - - def send(self, data): - """ - TLS replacement for normal asyncore .send() method. Throw an - exception if TLS hasn't been started or if TLS I/O was already in - progress, otherwise hand off to the TLS code. - """ - assert self.retry_read is None and self.retry_write is None, "%r: TLS I/O already in progress, r %r w %r" % (self, self.retry_read, self.retry_write) - assert self.tls is not None - return self.tls.write(data) - - def recv(self, buffer_size): - """ - TLS replacement for normal asyncore .recv() method. Throw an - exception if TLS hasn't been started or if TLS I/O was already in - progress, otherwise hand off to the TLS code. - """ - assert self.retry_read is None and self.retry_write is None, "%r: TLS I/O already in progress, r %r w %r" % (self, self.retry_read, self.retry_write) - assert self.tls is not None - return self.tls.read(buffer_size) - - def readable(self): - """ - TLS replacement for normal asynchat .readable() method. A TLS - connection that's blocked waiting for TLS write is considered not - readable even if the underlying socket is. - """ - return self.retry_read is not None or (self.retry_write is None and asynchat.async_chat.readable(self)) - - def writeable(self): - """ - TLS replacement for normal asynchat .writeable() method. A TLS - connection that's blocked waiting for TLS read is considered not - writeable even if the underlying socket is. - """ - return self.retry_write is not None or (self.retry_read is None and asynchat.async_chat.writeable(self)) - - def handle_read(self): - """ - Asyncore says socket is readable. Make sure there's no TLS write - already in progress, retry previous read operation if we had one - that was waiting for more input, otherwise try to read some data, - and handle all the weird OpenSSL exceptions that the TLS code - throws. - """ - assert self.retry_write is None, "%r: TLS I/O already in progress, w %r" % (self, self.retry_write) - if self.retry_read is not None: - thunk = self.retry_read - self.retry_read = None - self.log("Retrying TLS read %r" % thunk) - thunk() - else: - try: - asynchat.async_chat.handle_read(self) - except POW.WantReadError: - self.retry_read = self.handle_read - except POW.WantWriteError: - self.retry_write = self.handle_read - except POW.ZeroReturnError: - self.log("ZeroReturn in handle_read()") - self.handle_close() - except POW.SSLUnexpectedEOFError: - self.log("SSLUnexpectedEOF in handle_read()", rpki.log.warn) - self.handle_error() - - def handle_write(self): - """ - Asyncore says socket is writeable. Make sure there's no TLS read - already in progress, retry previous write operation if we had one - that was blocked on the socket, otherwise try to write some data. - Handling all the weird OpenSSL exceptions that TLS throws is our - caller's problem. - """ - # This used to be an assertion, but apparently this can happen - # without anything really being wrong, as a sort of race - # condition, due to select() having signaled that a socket was - # both readable and writable. I think. - # - if self.retry_read is not None: - self.log("TLS I/O already in progress, r %r" % self.retry_read) - return - if self.retry_write is not None: - thunk = self.retry_write - self.retry_write = None - thunk() - self.log("Retrying TLS write %r" % thunk) - else: - asynchat.async_chat.handle_write(self) - - def initiate_send(self): - """ - Initiate a write operation. This is just a wrapper around the - asynchat method, to handle all the whacky TLS exceptions. - """ - assert self.retry_read is None and self.retry_write is None, "%r: TLS I/O already in progress, r %r w %r" % (self, self.retry_read, self.retry_write) - try: - asynchat.async_chat.initiate_send(self) - except POW.WantReadError: - self.retry_read = self.initiate_send - except POW.WantWriteError: - self.retry_write = self.initiate_send - except POW.ZeroReturnError: - self.log("ZeroReturn in initiate_send()") - self.handle_close() - except POW.SSLUnexpectedEOFError: - self.log("SSLUnexpectedEOF in initiate_send()", rpki.log.warn) - self.handle_error() - - def close(self, force = False): - """ - Close the stream. - - Graceful shutdown of a TLS connection requires multiple calls to - the underlying TLS code. If the connection should be closed right - now without waiting (perhaps because it's already dead and we're - just cleaning up), call with force = True. - """ - self.log("Close requested") - assert force or (self.retry_read is None and self.retry_write is None), "%r: TLS I/O already in progress, r %r w %r" % (self, self.retry_read, self.retry_write) - if self.tls is not None: - try: - if self.retry_read is None and self.retry_write is None: - ret = self.tls.shutdown() - else: - ret = None - self.log("tls.shutdown() returned %s, force_shutdown %s" % (ret, force)) - if ret or force: - self.tls = None - except POW.WantReadError: - self.retry_read = self.close - except POW.WantWriteError: - self.retry_write = self.close - except POW.SSLError, e: - self.log("tls.shutdown() threw %s, shutting down anyway" % e) - self.tls = None - if self.tls is None: - self.log("TLS layer is done, closing socket") - self.timer.cancel() - self.timer.set_handler(None) - try: - asynchat.async_chat.close(self) - except AttributeError: - if getattr(self, "socket", None) is not None: - raise - - def log_cert(self, tag, x): - """ - Log HTTPS certificates, if certificate debugging is enabled. - """ - if debug_tls_certs: - rpki.log.debug("%r: HTTPS %s cert %r issuer %s [%s] subject %s [%s]" % (self, tag, x, x.getIssuer(), x.hAKI(), x.getSubject(), x.hSKI())) - -class http_server(http_stream): - """ - HTTP(S) server stream. - """ - - ## @var parse_type - # Stream parser should look for incoming HTTP request messages. - parse_type = http_request - - ## @var timeout - # Use the default server timeout value set in the module header. - timeout = default_server_timeout - - def __init__(self, sock, handlers, cert = None, key = None, ta = (), dynamic_ta = None): - self.log("Starting") - self.handlers = handlers - http_stream.__init__(self, sock = sock) - self.expect_close = not want_persistent_server - - self.log("cert %r key %r ta %r dynamic_ta %r" % (cert, key, ta, dynamic_ta)) - - self.tls = POW.Ssl(POW.TLSV1_SERVER_METHOD) - self.log_cert("server", cert) - self.tls.useCertificate(cert.get_POW()) - self.tls.useKey(key.get_POW()) - ta = rpki.x509.X509.normalize_chain(dynamic_ta() if dynamic_ta else ta) - assert ta - for x in ta: - self.log_cert("trusted", x) - self.tls.addTrust(x.get_POW()) - self.tls.setVerifyMode(POW.SSL_VERIFY_PEER | POW.SSL_VERIFY_FAIL_IF_NO_PEER_CERT) - - self.tls.setFd(self.fileno()) - self.tls_accept() - - def tls_accept(self): - """ - Set up TLS for server side connection, handling all the whacky - OpenSSL exceptions from TLS. - - SSLErrorSSLError exceptions are particularly nasty, because all - too often they indicate a certificate lookup failure deep within - the guts of OpenSSL's TLS connection setup logic. Extracting - anything resembling a Python data structure from a handler called - that deep inside the OpenSSL TLS library, while theoretically - possible, runs a high risk of triggering some kind of memory leak - or corruption. So, for now, we just get back a long text string, - which we break up and log but don't attempt to process further. - """ - try: - self.tls.accept() - except POW.WantReadError: - self.retry_read = self.tls_accept - except POW.WantWriteError: - self.retry_write = self.tls_accept - except POW.SSLUnexpectedEOFError: - self.close(force = True) # nagios/sysmond probe, just close - except POW.SSLErrorSSLError, e: - if "\n" in e: - for line in str(e).splitlines(): - rpki.log.warn(line) - raise POW.SSLErrorSSLError, "TLS certificate problem, most likely" - else: - raise - - def handle_no_content_length(self): - """ - Handle an incoming message that used neither chunking nor a - Content-Length header (that is: this message will be the last one - in this server stream). No special action required. - """ - self.handle_message() - - def find_handler(self, path): - """ - Helper method to search self.handlers. - """ - for s, h in self.handlers: - if path.startswith(s): - return h - return None - - def handle_message(self): - """ - TLS and HTTP layers managed to deliver a complete HTTP request to - us, figure out what to do with it. Check the command and - Content-Type, look for a handler, and if everything looks right, - pass the message body, path, and a reply callback to the handler. - """ - self.log("Received request %s %s" % (self.msg.cmd, self.msg.path)) - if not self.msg.persistent(): - self.expect_close = True - handler = self.find_handler(self.msg.path) - error = None - if self.msg.cmd != "POST": - error = 501, "No handler for method %s" % self.msg.cmd - elif self.msg.headers["Content-Type"] != rpki_content_type: - error = 415, "No handler for Content-Type %s" % self.headers["Content-Type"] - elif handler is None: - error = 404, "No handler for URL %s" % self.msg.path - if error is None: - try: - handler(self.msg.body, self.msg.path, self.send_reply) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - rpki.log.traceback() - self.send_error(500, "Unhandled exception %s" % e) - else: - self.send_error(code = error[0], reason = error[1]) - - def send_error(self, code, reason): - """ - Send an error response to this request. - """ - self.send_message(code = code, reason = reason) - - def send_reply(self, code, body): - """ - Send a reply to this request. - """ - self.send_message(code = code, body = body) - - def send_message(self, code, reason = "OK", body = None): - """ - Queue up reply message. If both parties agree that connection is - persistant, and if no error occurred, restart this stream to - listen for next message; otherwise, queue up a close event for - this stream so it will shut down once the reply has been sent. - """ - self.log("Sending response %s %s" % (code, reason)) - if code >= 400: - self.expect_close = True - msg = http_response(code = code, reason = reason, body = body, - Content_Type = rpki_content_type, - Connection = "Close" if self.expect_close else "Keep-Alive") - self.push(msg.format()) - if self.expect_close: - self.log("Closing") - self.timer.cancel() - self.close_when_done() - else: - self.log("Listening for next message") - self.restart() - -class http_listener(asyncore.dispatcher): - """ - Listener for incoming HTTP(S) connections. - """ - - log = log_method - - def __init__(self, handlers, addrinfo, cert = None, key = None, ta = None, dynamic_ta = None): - self.log("Listener cert %r key %r ta %r dynamic_ta %r" % (cert, key, ta, dynamic_ta)) - asyncore.dispatcher.__init__(self) - self.handlers = handlers - self.cert = cert - self.key = key - self.ta = ta - self.dynamic_ta = dynamic_ta - try: - af, socktype, proto, canonname, sockaddr = addrinfo - self.create_socket(af, socktype) - self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - try: - self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - except AttributeError: - pass - if have_ipv6 and af == socket.AF_INET6: - self.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) - self.bind(sockaddr) - self.listen(5) - except: - self.log("Couldn't set up HTTP listener", rpki.log.warn) - rpki.log.traceback() - self.close() - self.log("Listening on %r, handlers %r" % (sockaddr, handlers)) - - def handle_accept(self): - """ - Asyncore says we have an incoming connection, spawn an http_server - stream for it and pass along all of our handler and TLS data. - """ - self.log("Accepting connection") - try: - s, client = self.accept() - self.log("Accepting connection from %r" % (client,)) - http_server(sock = s, handlers = self.handlers, cert = self.cert, key = self.key, ta = self.ta, dynamic_ta = self.dynamic_ta) - except (rpki.async.ExitNow, SystemExit): - raise - except: - self.handle_error() - - def handle_error(self): - """ - Asyncore signaled an error, pass it along or log it. - """ - if sys.exc_info()[0] in (SystemExit, rpki.async.ExitNow): - raise - self.log("Error in HTTP listener", rpki.log.warn) - rpki.log.traceback() - -class http_client(http_stream): - """ - HTTP(S) client stream. - """ - - ## @var parse_type - # Stream parser should look for incoming HTTP response messages. - parse_type = http_response - - ## @var timeout - # Use the default client timeout value set in the module header. - timeout = default_client_timeout - - def __init__(self, queue, hostport, cert = None, key = None, ta = ()): - self.log("Creating new connection to %r" % (hostport,)) - self.log("cert %r key %r ta %r" % (cert, key, ta)) - http_stream.__init__(self) - self.queue = queue - self.host = hostport[0] - self.port = hostport[1] - self.state = "opening" - self.expect_close = not want_persistent_client - self.cert = cert - self.key = key - self.ta = rpki.x509.X509.normalize_chain(ta) - - def start(self): - """ - Create socket and request a connection. - """ - if not use_adns: - self.gotaddrinfo([(socket.AF_INET, self.host)]) - elif self.host == "localhost": - self.gotaddrinfo(localhost_addrinfo()) - else: - import rpki.adns # This should move to start of file once we've decided to inflict it on all users - rpki.adns.getaddrinfo(self.gotaddrinfo, self.dns_error, self.host, supported_address_families(enable_ipv6_clients)) - - def dns_error(self, e): - """ - Handle DNS lookup errors. For now, just whack the connection. - Undoubtedly we should do something better with diagnostics here. - """ - self.handle_error() - - def gotaddrinfo(self, addrinfo): - """ - Got address data from DNS, create socket and request connection. - """ - try: - self.af, self.addr = random.choice(addrinfo) - self.create_socket(self.af, socket.SOCK_STREAM) - self.connect((self.addr, self.port)) - except (rpki.async.ExitNow, SystemExit): - raise - except: - self.handle_error() - - def handle_connect(self): - """ - Asyncore says socket has connected, configure TLS junk. - """ - self.log("Socket connected") - self.tls = POW.Ssl(POW.TLSV1_CLIENT_METHOD) - self.log_cert("client", self.cert) - self.tls.useCertificate(self.cert.get_POW()) - self.tls.useKey(self.key.get_POW()) - assert self.ta - for x in self.ta: - self.log_cert("trusted", x) - self.tls.addTrust(x.get_POW()) - self.tls.setVerifyMode(POW.SSL_VERIFY_PEER | POW.SSL_VERIFY_FAIL_IF_NO_PEER_CERT) - self.tls.setFd(self.fileno()) - self.tls_connect() - - def tls_connect(self): - """ - Initialize client side of TLS. - """ - try: - self.tls.connect() - except POW.WantReadError: - self.retry_read = self.tls_connect - except POW.WantWriteError: - self.retry_write = self.tls_connect - else: - self.log("TLS connected") - self.set_state("idle") - self.queue.send_request() - - def set_state(self, state): - """ - Set HTTP client connection state. - """ - self.log("State transition %s => %s" % (self.state, state)) - self.state = state - - def handle_no_content_length(self): - """ - Handle response message that used neither chunking nor a - Content-Length header (that is: this message will be the last one - in this server stream). In this case we want to read until we - reach the end of the data stream. - """ - self.set_terminator(None) - - def send_request(self, msg): - """ - Queue up request message and kickstart connection. - """ - self.log("Sending request %r" % msg) - assert self.state == "idle", "%r: state should be idle, is %s" % (self, self.state) - self.set_state("request-sent") - msg.headers["Connection"] = "Close" if self.expect_close else "Keep-Alive" - self.push(msg.format()) - self.restart() - - def handle_message(self): - """ - Handle incoming HTTP response message. Make sure we're in a state - where we expect to see such a message (and allow the mysterious - empty messages that Apache sends during connection close, no idea - what that is supposed to be about). If everybody agrees that the - connection should stay open, put it into an idle state; otherwise, - arrange for the stream to shut down. - """ - - self.log("Message received, state %s" % self.state) - - if not self.msg.persistent(): - self.expect_close = True - - if self.state != "request-sent": - if self.state == "closing": - assert not self.msg.body - self.log("Ignoring empty response received while closing") - return - raise rpki.exceptions.HTTPSUnexpectedState, "%r received message while in unexpected state %s" % (self, self.state) - - if self.expect_close: - self.log("Closing") - self.set_state("closing") - self.queue.detach(self) - self.close_when_done() - else: - self.log("Idling") - self.set_state("idle") - self.update_timeout() - - if self.msg.code != 200: - raise rpki.exceptions.HTTPRequestFailed, "HTTPS request failed with status %s, reason %s, response %s" % (self.msg.code, self.msg.reason, self.msg.body) - self.queue.return_result(self.msg) - - def handle_close(self): - """ - Asyncore signaled connection close. If we were waiting for that - to find the end of a response message, process the resulting - message now; if we were waiting for the response to a request we - sent, signal the error. - """ - http_stream.handle_close(self) - self.log("State %s" % self.state) - self.queue.detach(self) - if self.get_terminator() is None: - self.handle_body() - elif self.state == "request-sent": - raise rpki.exceptions.HTTPSClientAborted, "HTTPS request aborted by close event" - - def handle_timeout(self): - """ - Connection idle timer has expired. Shut down connection in any - case, noisily if we weren't idle. - """ - if self.state != "idle": - self.log("Timeout while in state %s" % self.state, rpki.log.warn) - http_stream.handle_timeout(self) - self.queue.detach(self) - if self.state != "idle": - try: - raise rpki.exceptions.HTTPTimeout - except rpki.exceptions.HTTPTimeout, e: - self.queue.return_result(e) - - def handle_error(self): - """ - Asyncore says something threw an exception. Log it, then shut - down the connection and pass back the exception. - """ - eclass, edata = sys.exc_info()[0:2] - self.log("Error on HTTP client connection %s:%s: %s %s" % (self.host, self.port, eclass, edata), rpki.log.warn) - http_stream.handle_error(self) - self.queue.detach(self) - self.queue.return_result(edata) - -class http_queue(object): - """ - Queue of pending HTTP requests for a single destination. This class - is very tightly coupled to http_client; http_client handles the HTTP - stream itself, this class provides a slightly higher-level API. - """ - - log = log_method - - def __init__(self, hostport, cert = None, key = None, ta = ()): - self.log("Creating queue for %r" % (hostport,)) - self.log("cert %r key %r ta %r" % (cert, key, ta)) - self.hostport = hostport - self.client = None - self.queue = [] - self.cert = cert - self.key = key - self.ta = ta - - def request(self, *requests): - """ - Append http_request object(s) to this queue. - """ - self.log("Adding requests %r" % requests) - self.queue.extend(requests) - - def restart(self): - """ - Send next request for this queue, if we can. This may involve - starting a new http_client stream, reusing an existing idle - stream, or just ignoring this request if there's an active client - stream already; in the last case, handling of the response (or - exception, or timeout) for the query currently in progress will - call this method when it's time to kick out the next query. - """ - try: - if self.client is None: - self.client = http_client(self, self.hostport, cert = self.cert, key = self.key, ta = self.ta) - self.log("Attached client %r" % self.client) - self.client.start() - elif self.client.state == "idle": - self.log("Sending request to existing client %r" % self.client) - self.send_request() - else: - self.log("Client %r exists in state %r" % (self.client, self.client.state)) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - self.return_result(e) - - def send_request(self): - """ - Kick out the next query in this queue, if any. - """ - if self.queue: - self.client.send_request(self.queue[0]) - - def detach(self, client_): - """ - Detatch a client from this queue. Silently ignores attempting to - detach a client that is not attached to this queue, to simplify - handling of what otherwise would be a nasty set of race - conditions. - """ - if client_ is self.client: - self.log("Detaching client %r" % client_) - self.client = None - - def return_result(self, result): - """ - Client stream has returned a result, which we need to pass along - to the original caller. Result may be either an HTTP response - message or an exception. In either case, once we're done - processing this result, kick off next message in the queue, if any. - """ - - try: - req = self.queue.pop(0) - except IndexError: - self.log("No caller, this should not happen. Dropping result %r" % result) - return - - self.log("Dequeuing request %r" % req) - - try: - if isinstance(result, http_response): - self.log("Returning result %r to caller" % result) - req.callback(result.body) - else: - assert isinstance(result, Exception) - self.log("Returning exception %r to caller: %s" % (result, result), rpki.log.warn) - req.errback(result) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - self.log("Unhandled exception %r from callback: %s" % (e, e), rpki.log.warn) - #rpki.log.traceback() - req.errback(e) - - self.log("Queue: %r" % self.queue) - - if self.queue: - self.restart() - -## @var client_queues -# Map of (host, port) tuples to http_queue objects. -client_queues = {} - -def client(msg, client_key, client_cert, server_ta, url, callback, errback): - """ - Open client HTTPS connection, send a message, set up callbacks to - handle response. - """ - - u = urlparse.urlparse(url) - - if (u.scheme not in ("", "https") or - u.username is not None or - u.password is not None or - u.params != "" or - u.query != "" or - u.fragment != ""): - raise rpki.exceptions.BadClientURL, "Unusable URL %s" % url - - if debug_http: - rpki.log.debug("Contacting %s" % url) - - request = http_request( - cmd = "POST", - path = u.path, - body = msg, - callback = callback, - errback = errback, - Host = u.hostname, - Content_Type = rpki_content_type) - - hostport = (u.hostname or "localhost", u.port or default_tcp_port) - - if debug_http: - rpki.log.debug("Created request %r for %r" % (request, hostport)) - if not isinstance(server_ta, (tuple, list)): - server_ta = (server_ta,) - if hostport not in client_queues: - client_queues[hostport] = http_queue(hostport, cert = client_cert, key = client_key, ta = server_ta) - client_queues[hostport].request(request) - - # Defer connection attempt until after we've had time to process any - # pending I/O events, in case connections have closed. - - if debug_http: - rpki.log.debug("Scheduling connection startup for %r" % request) - rpki.async.defer(client_queues[hostport].restart) - -def server(handlers, server_key, server_cert, port, host = "", client_ta = (), dynamic_https_trust_anchor = None): - """ - Run an HTTPS server and wait (forever) for connections. - """ - - if not isinstance(handlers, (tuple, list)): - handlers = (("/", handlers),) - - if not isinstance(client_ta, (tuple, list)): - client_ta = (client_ta,) - - # Yes, this is sick. So is getaddrinfo() returning duplicate - # records, which RedHat has the gall to claim is a feature. - ai = [] - for af in supported_address_families(enable_ipv6_servers): - try: - if host: - h = host - elif have_ipv6 and af == socket.AF_INET6: - h = "::" - else: - h = "0.0.0.0" - for a in socket.getaddrinfo(h, port, af, socket.SOCK_STREAM): - if a not in ai: - ai.append(a) - except socket.gaierror: - pass - - for a in ai: - http_listener(addrinfo = a, handlers = handlers, cert = server_cert, key = server_key, ta = client_ta, dynamic_ta = dynamic_https_trust_anchor) - - rpki.async.event_loop() - -def build_https_ta_cache(certs): - """ - Package up a collection of certificates into a form suitable for use - as a dynamic HTTPS trust anchor set. Precise format of this - collection is an internal conspiracy within the rpki.https module; - at one point it was a POW.X509Store object, at the moment it's a - Python set, what it will be tomorow is nobody else's business. - """ - - return set(certs) - -class caller(object): - """ - Handle client-side mechanics for protocols based on HTTPS, CMS, and - rpki.xml_utils. Calling sequence is intended to nest within - rpki.async.sync_wrapper. - """ - - debug = False - - def __init__(self, proto, client_key, client_cert, server_ta, server_cert, url, debug = None): - self.proto = proto - self.client_key = client_key - self.client_cert = client_cert - self.server_ta = server_ta - self.server_cert = server_cert - self.url = url - if debug is not None: - self.debug = debug - - def __call__(self, cb, eb, *pdus): - - def done(r_der): - """ - Handle CMS-wrapped XML response message. - """ - r_cms = self.proto.cms_msg(DER = r_der) - r_msg = r_cms.unwrap((self.server_ta, self.server_cert)) - if self.debug: - print "<!-- Reply -->" - print r_cms.pretty_print_content() - cb(r_msg) - - q_msg = self.proto.msg.query(*pdus) - q_cms = self.proto.cms_msg() - q_der = q_cms.wrap(q_msg, self.client_key, self.client_cert) - if self.debug: - print "<!-- Query -->" - print q_cms.pretty_print_content() - - client( - client_key = self.client_key, - client_cert = self.client_cert, - server_ta = self.server_ta, - url = self.url, - msg = q_der, - callback = done, - errback = eb) diff --git a/rpkid.with_tls/rpki/ipaddrs.py b/rpkid.with_tls/rpki/ipaddrs.py deleted file mode 100644 index 58185cc1..00000000 --- a/rpkid.with_tls/rpki/ipaddrs.py +++ /dev/null @@ -1,114 +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()) - -def parse(s): - """ - Parse a string as either an IPv4 or IPv6 address, and return object of appropriate class. - """ - if isinstance(s, unicode): - s = s.encode("ascii") - return v6addr(s) if ":" in s else v4addr(s) diff --git a/rpkid.with_tls/rpki/left_right.py b/rpkid.with_tls/rpki/left_right.py deleted file mode 100644 index 8d2bf0ad..00000000 --- a/rpkid.with_tls/rpki/left_right.py +++ /dev/null @@ -1,1180 +0,0 @@ -""" -RPKI "left-right" protocol. - -$Id$ - -Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -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 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, rpki.async - -# 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_persistent, left_right_namespace): - """ - Virtual class for top-level left-right protocol data elements. - """ - - handles = () - - self_id = None - self_handle = None - - def self(self): - """Fetch self object to which this object links.""" - return self_elt.sql_fetch(self.gctx, self.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 handles when cloning, including _id -> _handle translation. - """ - if r_pdu.self_handle is None: - r_pdu.self_handle = self.self_handle - for tag, elt in self.handles: - id_name = tag + "_id" - handle_name = tag + "_handle" - if getattr(r_pdu, handle_name, None) is None: - try: - setattr(r_pdu, handle_name, getattr(elt.sql_fetch(self.gctx, getattr(r_pdu, id_name)), handle_name)) - except AttributeError: - continue - - @classmethod - def serve_fetch_handle(cls, gctx, self_id, handle): - """ - Find an object based on its handle. - """ - return cls.sql_fetch_where1(gctx, cls.element_name + "_handle = %s AND self_id = %s", (handle, self_id)) - - def serve_fetch_one_maybe(self): - """ - Find the object on which a get, set, or destroy method should - operate, or which would conflict with a create method. - """ - where = "%s.%s_handle = %%s AND %s.self_id = self.self_id AND self.self_handle = %%s" % ((self.element_name,) * 3) - args = (getattr(self, self.element_name + "_handle"), self.self_handle) - return self.sql_fetch_where1(self.gctx, where, args, "self") - - def serve_fetch_all(self): - """ - Find the objects on which a list method should operate. - """ - where = "%s.self_id = self.self_id and self.self_handle = %%s" % self.element_name - return self.sql_fetch_where(self.gctx, where, (self.self_handle,), "self") - - def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Hook to do _handle => _id translation before saving. - - self is always the object to be saved to SQL. For create - operations, self and q_pdu are be the same object; for set - operations, self is the pre-existing object from SQL and q_pdu is - the set request received from the the IRBE. - """ - for tag, elt in self.handles: - id_name = tag + "_id" - if getattr(self, id_name, None) is None: - x = elt.serve_fetch_handle(self.gctx, self.self_id, getattr(q_pdu, tag + "_handle")) - if x is None: - raise rpki.exceptions.HandleTranslationError, "Could not translate %r %s_handle" % (self, tag) - setattr(self, id_name, getattr(x, id_name)) - cb() - -class self_elt(data_elt): - """ - <self/> element. - """ - - element_name = "self" - attributes = ("action", "tag", "self_handle", "crl_interval", "regen_margin") - elements = ("bpki_cert", "bpki_glue") - booleans = ("rekey", "reissue", "revoke", "run_now", "publish_world_now", "revoke_forgotten") - - sql_template = rpki.sql.template("self", "self_id", "self_handle", - "use_hsm", "crl_interval", "regen_margin", - ("bpki_cert", rpki.x509.X509), ("bpki_glue", rpki.x509.X509)) - handles = () - - 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 roas(self): - """Fetch all ROA objects that link to this self object.""" - return rpki.rpki_engine.roa_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Extra server actions for self_elt. - """ - rpki.log.trace() - self.unimplemented_control("reissue") - actions = [] - if q_pdu.rekey: - actions.append(self.serve_rekey) - if q_pdu.revoke: - actions.append(self.serve_revoke) - if q_pdu.revoke_forgotten: - actions.append(self.serve_revoke_forgotten) - if q_pdu.publish_world_now: - actions.append(self.serve_publish_world_now) - if q_pdu.run_now: - actions.append(self.serve_run_now) - def loop(iterator, action): - action(iterator, eb) - rpki.async.iterator(actions, loop, cb) - - def serve_rekey(self, cb, eb): - """ - Handle a left-right rekey action for this self. - """ - rpki.log.trace() - def loop(iterator, parent): - parent.serve_rekey(iterator, eb) - rpki.async.iterator(self.parents(), loop, cb) - - def serve_revoke(self, cb, eb): - """ - Handle a left-right revoke action for this self. - """ - rpki.log.trace() - def loop(iterator, parent): - parent.serve_revoke(iterator, eb) - rpki.async.iterator(self.parents(), loop, cb) - - def serve_revoke_forgotten(self, cb, eb): - """ - Handle a left-right revoke_forgotten action for this self. - """ - rpki.log.trace() - def loop(iterator, parent): - parent.serve_revoke_forgotten(iterator, eb) - rpki.async.iterator(self.parents(), loop, cb) - - def serve_publish_world_now(self, cb, eb): - """ - Handle a left-right publish_world_now action for this self. - - The publication stuff needs refactoring, right now publication is - interleaved with local operations in a way that forces far too - many bounces through the task system for any complex update. The - whole thing ought to be rewritten to queue up outgoing publication - PDUs and only send them when we're all done or when we need to - force publication at a particular point in a multi-phase operation. - - Once that reorganization has been done, this method should be - rewritten to reuse the low-level publish() methods that each - object will have...but we're not there yet. So, for now, we just - do this via brute force. Think of it as a trial version to see - whether we've identified everything that needs to be republished - for this operation. - """ - - def loop(iterator, parent): - q_msg = rpki.publication.msg.query() - for ca in parent.cas(): - ca_detail = ca.fetch_active() - if ca_detail is not None: - q_msg.append(rpki.publication.crl_elt.make_publish(ca_detail.crl_uri(ca), ca_detail.latest_crl)) - q_msg.append(rpki.publication.manifest_elt.make_publish(ca_detail.manifest_uri(ca), ca_detail.latest_manifest)) - q_msg.extend(rpki.publication.certificate_elt.make_publish(c.uri(ca), c.cert) for c in ca_detail.child_certs()) - q_msg.extend(rpki.publication.roa_elt.make_publish(r.uri(), r.roa) for r in ca_detail.roas() if r.roa is not None) - parent.repository().call_pubd(iterator, eb, q_msg) - - rpki.async.iterator(self.parents(), loop, cb) - - def serve_run_now(self, cb, eb): - """ - Handle a left-right run_now action for this self. - """ - rpki.log.debug("Forced immediate run of periodic actions for self %s[%d]" % (self.self_handle, self.self_id)) - self.cron(cb) - - def serve_fetch_one_maybe(self): - """ - Find the self object upon which a get, set, or destroy action - should operate, or which would conflict with a create method. - """ - return self.serve_fetch_handle(self.gctx, None, self.self_handle) - - @classmethod - def serve_fetch_handle(cls, gctx, self_id, self_handle): - """ - Find a self object based on its self_handle. - """ - return cls.sql_fetch_where1(gctx, "self_handle = %s", self_handle) - - def serve_fetch_all(self): - """ - 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 cron(self, cb): - """ - Periodic tasks. - """ - - def one(): - self.gctx.checkpoint() - rpki.log.debug("Self %s[%d] polling parents" % (self.self_handle, self.self_id)) - self.client_poll(two) - - def two(): - self.gctx.checkpoint() - rpki.log.debug("Self %s[%d] updating children" % (self.self_handle, self.self_id)) - self.update_children(three) - - def three(): - self.gctx.checkpoint() - rpki.log.debug("Self %s[%d] updating ROAs" % (self.self_handle, self.self_id)) - self.update_roas(four) - - def four(): - self.gctx.checkpoint() - rpki.log.debug("Self %s[%d] regenerating CRLs and manifests" % (self.self_handle, self.self_id)) - self.regenerate_crls_and_manifests(cb) - - one() - - - def client_poll(self, callback): - """ - Run the regular client poll cycle with each of this self's parents - in turn. - """ - - rpki.log.trace() - - def parent_loop(parent_iterator, parent): - - def got_list(r_msg): - ca_map = dict((ca.parent_resource_class, ca) for ca in parent.cas()) - self.gctx.checkpoint() - - def class_loop(class_iterator, rc): - - def class_update_failed(e): - rpki.log.traceback() - rpki.log.warn("Couldn't update class, skipping: %s" % e) - class_iterator() - - def class_create_failed(e): - rpki.log.traceback() - rpki.log.warn("Couldn't create class, skipping: %s" % e) - class_iterator() - - self.gctx.checkpoint() - 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, class_iterator, class_update_failed) - else: - rpki.rpki_engine.ca_obj.create(parent, rc, class_iterator, class_create_failed) - - def class_done(): - - def ca_loop(iterator, ca): - self.gctx.checkpoint() - ca.delete(parent, iterator) - - def ca_done(): - self.gctx.checkpoint() - self.gctx.sql.sweep() - parent_iterator() - - rpki.async.iterator(ca_map.values(), ca_loop, ca_done) - - rpki.async.iterator(r_msg.payload.classes, class_loop, class_done) - - def list_failed(e): - rpki.log.traceback() - rpki.log.warn("Couldn't get resource class list from parent %r, skipping: %s" % (parent, e)) - parent_iterator() - - rpki.up_down.list_pdu.query(parent, got_list, list_failed) - - rpki.async.iterator(self.parents(), parent_loop, callback) - - - def update_children(self, cb): - """ - 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) - publisher = rpki.rpki_engine.publication_queue() - - def loop(iterator, child): - - def lose(e): - rpki.log.traceback() - rpki.log.warn("Couldn't update child %r, skipping: %s" % (child, e)) - iterator() - - def got_resources(irdb_resources): - try: - for child_cert in child_certs: - ca_detail = child_cert.ca_detail() - ca = ca_detail.ca() - if ca_detail.state == "active": - old_resources = child_cert.cert.get_3779resources() - new_resources = irdb_resources.intersection(old_resources).intersection(ca_detail.latest_ca_cert.get_3779resources()) - - if new_resources.empty(): - rpki.log.debug("Resources shrank to the null set, revoking and withdrawing child certificate SKI %s" % child_cert.cert.gSKI()) - child_cert.revoke(publisher = publisher) - ca_detail.generate_crl(publisher = publisher) - ca_detail.generate_manifest(publisher = publisher) - - elif 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, - publisher = publisher) - - 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)) - child_cert.sql_delete() - publisher.withdraw(cls = rpki.publication.certificate_elt, uri = child_cert.uri(ca), obj = child_cert.cert, repository = ca.parent().repository()) - ca_detail.generate_manifest(publisher = publisher) - - except (SystemExit, rpki.async.ExitNow): - raise - except Exception, e: - self.gctx.checkpoint() - lose(e) - else: - self.gctx.checkpoint() - iterator() - - self.gctx.checkpoint() - child_certs = child.child_certs() - if child_certs: - self.gctx.irdb_query_child_resources(child.self().self_handle, child.child_handle, got_resources, lose) - else: - iterator() - - def done(): - def lose(e): - rpki.log.traceback() - rpki.log.warn("Couldn't publish for %s, skipping: %s" % (self.self_handle, e)) - self.gctx.checkpoint() - cb() - self.gctx.checkpoint() - publisher.call_pubd(cb, lose) - - rpki.async.iterator(self.children(), loop, done) - - - def regenerate_crls_and_manifests(self, cb): - """ - 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() - regen_margin = rpki.sundial.timedelta(seconds = self.regen_margin) - publisher = rpki.rpki_engine.publication_queue() - - for parent in self.parents(): - for ca in parent.cas(): - try: - for ca_detail in ca.fetch_revoked(): - if now > ca_detail.latest_crl.getNextUpdate(): - ca_detail.delete(ca = ca, publisher = publisher) - ca_detail = ca.fetch_active() - if ca_detail is not None and now + regen_margin> ca_detail.latest_crl.getNextUpdate(): - ca_detail.generate_crl(publisher = publisher) - ca_detail.generate_manifest(publisher = publisher) - except (SystemExit, rpki.async.ExitNow): - raise - except Exception, e: - rpki.log.traceback() - rpki.log.warn("Couldn't regenerate CRLs and manifests for CA %r, skipping: %s" % (ca, e)) - - def lose(e): - rpki.log.traceback() - rpki.log.warn("Couldn't publish updated CRLs and manifests for self %r, skipping: %s" % (self.self_handle, e)) - self.gctx.checkpoint() - cb() - - self.gctx.checkpoint() - publisher.call_pubd(cb, lose) - - - def update_roas(self, cb): - """ - Generate or update ROAs for this self. - """ - - def got_roa_requests(roa_requests): - - self.gctx.checkpoint() - - if self.gctx.sql.dirty: - rpki.log.warn("Unexpected dirty SQL cache, flushing") - self.gctx.sql.sweep() - - roas = {} - orphans = [] - for roa in self.roas(): - k = (roa.asn, str(roa.ipv4), str(roa.ipv6)) - if k not in roas: - roas[k] = roa - elif (roa.roa is not None and roa.cert is not None and roa.ca_detail() is not None and roa.ca_detail().state == "active" and - (roas[k].roa is None or roas[k].cert is None or roas[k].ca_detail() is None or roas[k].ca_detail().state != "active")): - orphans.append(roas[k]) - roas[k] = roa - else: - orphans.append(roa) - - publisher = rpki.rpki_engine.publication_queue() - ca_details = set() - - seen = set() - for roa_request in roa_requests: - try: - k = (roa_request.asn, str(roa_request.ipv4), str(roa_request.ipv6)) - if k in seen: - rpki.log.warn("Skipping duplicate ROA request %r for %r" % (k, roa_request)) - continue - seen.add(k) - roa = roas.pop(k, None) - if roa is None: - roa = rpki.rpki_engine.roa_obj(self.gctx, self.self_id, roa_request.asn, roa_request.ipv4, roa_request.ipv6) - rpki.log.debug("Couldn't find existing ROA matching %r, created %r" % (k, roa)) - else: - rpki.log.debug("Found existing ROA %r matching %r" % (roa, k)) - roa.update(publisher = publisher, fast = True) - ca_details.add(roa.ca_detail()) - except (SystemExit, rpki.async.ExitNow): - raise - except Exception, e: - if not isinstance(e, rpki.exceptions.NoCoveringCertForROA): - rpki.log.traceback() - rpki.log.warn("Could not update ROA %r, %r, skipping: %s" % (roa_request, roa, e)) - - orphans.extend(roas.itervalues()) - for roa in orphans: - try: - ca_details.add(roa.ca_detail()) - roa.revoke(publisher = publisher, fast = True) - except (SystemExit, rpki.async.ExitNow): - raise - except Exception, e: - rpki.log.traceback() - rpki.log.warn("Could not revoke ROA %r: %s" % (roa, e)) - - for ca_detail in ca_details: - ca_detail.generate_crl(publisher = publisher) - ca_detail.generate_manifest(publisher = publisher) - - self.gctx.sql.sweep() - - def publication_failed(e): - rpki.log.traceback() - rpki.log.warn("Couldn't publish for %s, skipping: %s" % (self.self_handle, e)) - self.gctx.checkpoint() - cb() - - self.gctx.checkpoint() - publisher.call_pubd(cb, publication_failed) - - def roa_requests_failed(e): - rpki.log.traceback() - rpki.log.warn("Could not fetch ROA requests for %s, skipping: %s" % (self.self_handle, e)) - cb() - - self.gctx.checkpoint() - self.gctx.irdb_query_roa_requests(self.self_handle, got_roa_requests, roa_requests_failed) - -class bsc_elt(data_elt): - """ - <bsc/> (Business Signing Context) element. - """ - - element_name = "bsc" - attributes = ("action", "tag", "self_handle", "bsc_handle", "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", "bsc_handle", - "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)) - handles = (("self", self_elt),) - - 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, cb, eb): - """ - 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 - data_elt.serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb) - -class repository_elt(data_elt): - """ - <repository/> element. - """ - - element_name = "repository" - attributes = ("action", "tag", "self_handle", "repository_handle", "bsc_handle", "peer_contact_uri") - elements = ("bpki_cert", "bpki_glue") - - sql_template = rpki.sql.template("repository", "repository_id", "repository_handle", - "self_id", "bsc_id", "peer_contact_uri", - ("bpki_cert", rpki.x509.X509), ("bpki_glue", rpki.x509.X509)) - handles = (("self", self_elt), ("bsc", bsc_elt)) - - bpki_cert = None - bpki_glue = None - - 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 default_pubd_handler(pdu): - """ - Default handler for publication response PDUs. - """ - pdu.raise_if_error() - - def call_pubd(self, callback, errback, q_msg, handlers = None): - """ - Send a message to publication daemon and return the response. - - As a convenience, attempting to send an empty message returns - immediate success without sending anything. - - Handlers is a dict of handler functions to process the response - PDUs. If the tag value in the response PDU appears in the dict, - the associated handler is called to process the PDU. If no tag - matches, default_pubd_handler() is called. A handler value of - False suppresses calling of the default handler. - """ - - try: - rpki.log.trace() - - self.gctx.sql.sweep() - - if not q_msg: - return callback() - - if handlers is None: - handlers = {} - - for q_pdu in q_msg: - rpki.log.info("Sending <%s %r %r> to pubd" % (q_pdu.action, q_pdu.uri, q_pdu.payload)) - - bsc = self.bsc() - q_der = 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_cert, self.bpki_glue) - - def done(r_der): - try: - r_msg = rpki.publication.cms_msg(DER = r_der).unwrap(bpki_ta_path) - for r_pdu in r_msg: - handler = handlers.get(r_pdu.tag, self.default_pubd_handler) - if handler: - handler(r_pdu) - if len(q_msg) != len(r_msg): - raise rpki.exceptions.BadPublicationReply, "Wrong number of response PDUs from pubd: sent %r, got %r" % (q_msg, r_msg) - callback() - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - errback(e) - - 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_der, - callback = done, - errback = errback) - - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - errback(e) - -class parent_elt(data_elt): - """ - <parent/> element. - """ - - element_name = "parent" - attributes = ("action", "tag", "self_handle", "parent_handle", "bsc_handle", "repository_handle", - "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", "revoke_forgotten") - - sql_template = rpki.sql.template("parent", "parent_id", "parent_handle", - "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") - handles = (("self", self_elt), ("bsc", bsc_elt), ("repository", repository_elt)) - - 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, cb, eb): - """ - Extra server actions for parent_elt. - """ - self.unimplemented_control("reissue") - actions = [] - if q_pdu.rekey: - actions.append(self.serve_rekey) - if q_pdu.revoke: - actions.append(self.serve_revoke) - if q_pdu.revoke_forgotten: - actions.append(self.serve_revoke_forgotten) - def loop(iterator, action): - action(iterator, eb) - rpki.async.iterator(actions, loop, cb) - - def serve_rekey(self, cb, eb): - """ - Handle a left-right rekey action for this parent. - """ - def loop(iterator, ca): - ca.rekey(iterator, eb) - rpki.async.iterator(self.cas(), loop, cb) - - def serve_revoke(self, cb, eb): - """ - Handle a left-right revoke action for this parent. - """ - def loop(iterator, ca): - ca.revoke(cb = iterator, eb = eb) - rpki.async.iterator(self.cas(), loop, cb) - - def serve_revoke_forgotten(self, cb, eb): - """ - Handle a left-right revoke_forgotten action for this parent. - - This is a bit fiddly: we have to compare the result of an up-down - list query with what we have locally and identify the SKIs of any - certificates that have gone missing. This should never happen in - ordinary operation, but can arise if we have somehow lost a - private key, in which case there is nothing more we can do with - the issued cert, so we have to clear it. As this really is not - supposed to happen, we don't clear it automatically, instead we - require an explicit trigger. - """ - - def got_list(r_msg): - - ca_map = dict((ca.parent_resource_class, ca) for ca in self.cas()) - - def rc_loop(rc_iterator, rc): - - if rc.class_name in ca_map: - - def ski_loop(ski_iterator, ski): - rpki.log.warn("Revoking certificates missing from our database, class %r, SKI %s" % (rc.class_name, ski)) - rpki.up_down.revoke_pdu.query(ca, ski, lambda x: ski_iterator(), eb) - - ca = ca_map[rc.class_name] - skis_parent_knows_about = set(c.cert.gSKI() for c in rc.certs) - skis_ca_knows_about = set(ca_detail.latest_ca_cert.gSKI() for ca_detail in ca.fetch_issue_response_candidates()) - skis_only_parent_knows_about = skis_parent_knows_about - skis_ca_knows_about - rpki.async.iterator(skis_only_parent_knows_about, ski_loop, rc_iterator) - - else: - rc_iterator() - - rpki.async.iterator(r_msg.payload.classes, rc_loop, cb) - - rpki.up_down.list_pdu.query(self, got_list, eb) - - - def query_up_down(self, q_pdu, cb, eb): - """ - Client code for sending one up-down query PDU to this parent. - """ - - rpki.log.trace() - - bsc = self.bsc() - if bsc is None: - raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id - - if bsc.signing_cert is None: - raise rpki.exceptions.BSCNotReady, "BSC %r[%s] is not yet usable" % (bsc.bsc_handle, bsc.bsc_id) - - q_msg = rpki.up_down.message_pdu.make_query( - payload = q_pdu, - sender = self.sender_name, - recipient = self.recipient_name) - - q_der = rpki.up_down.cms_msg().wrap(q_msg, bsc.private_key_id, - bsc.signing_cert, - bsc.signing_cert_crl) - - def unwrap(r_der): - try: - r_msg = rpki.up_down.cms_msg(DER = r_der).unwrap((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() - except (SystemExit, rpki.async.ExitNow): - raise - except Exception, e: - eb(e) - else: - cb(r_msg) - - 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_der, - url = self.peer_contact_uri, - callback = unwrap, - errback = eb) - -class child_elt(data_elt): - """ - <child/> element. - """ - - element_name = "child" - attributes = ("action", "tag", "self_handle", "child_handle", "bsc_handle") - elements = ("bpki_cert", "bpki_glue") - booleans = ("reissue", ) - - sql_template = rpki.sql.template("child", "child_id", "child_handle", - "self_id", "bsc_id", - ("bpki_cert", rpki.x509.X509), - ("bpki_glue", rpki.x509.X509)) - - handles = (("self", self_elt), ("bsc", bsc_elt)) - - 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, cb, eb): - """ - 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 - cb() - - def serve_destroy_hook(self, cb, eb): - """ - Extra server actions when destroying a child_elt. - """ - publisher = rpki.rpki_engine.publication_queue() - for child_cert in self.child_certs(): - child_cert.revoke(publisher = publisher, - generate_crl_and_manifest = True) - publisher.call_pubd(cb, eb) - - 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, callback): - """ - 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(DER = query).unwrap((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 - - def done(r_msg): - # - # 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. - # - reply = rpki.up_down.cms_msg().wrap(r_msg, bsc.private_key_id, - bsc.signing_cert, bsc.signing_cert_crl) - callback(reply) - - try: - q_msg.serve_top_level(self, done) - except (rpki.async.ExitNow, SystemExit): - raise - except rpki.exceptions.NoActiveCA, data: - done(q_msg.serve_error(data)) - except Exception, data: - rpki.log.traceback() - done(q_msg.serve_error(data)) - -class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace): - """ - <list_resources/> element. - """ - - element_name = "list_resources" - attributes = ("self_handle", "tag", "child_handle", "valid_until", "asn", "ipv4", "ipv6") - 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 list_roa_requests_elt(rpki.xml_utils.base_elt, left_right_namespace): - """ - <list_roa_requests/> element. - """ - - element_name = "list_roa_requests" - attributes = ("self_handle", "tag", "asn", "ipv4", "ipv6") - - def startElement(self, stack, name, attrs): - """ - Handle <list_roa_requests/> element. This requires special handling - due to the data types of some of the attributes. - """ - assert name == "list_roa_requests", "Unexpected name %s, stack %s" % (name, stack) - self.read_attrs(attrs) - if self.ipv4 is not None: - 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) - -class list_published_objects_elt(rpki.xml_utils.text_elt, left_right_namespace): - """ - <list_published_objects/> element. - """ - - element_name = "list_published_objects" - attributes = ("self_handle", "tag", "uri") - text_attribute = "obj" - - obj = None - - def serve_dispatch(self, r_msg, cb, eb): - """ - Handle a <list_published_objects/> query. The method name is a - misnomer here, there's no action attribute and no dispatch, we - just dump every published object for the specified <self/> and return. - """ - for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents(): - for ca in parent.cas(): - ca_detail = ca.fetch_active() - if ca_detail is not None: - r_msg.append(self.make_reply(ca_detail.crl_uri(ca), ca_detail.latest_crl)) - r_msg.append(self.make_reply(ca_detail.manifest_uri(ca), ca_detail.latest_manifest)) - r_msg.extend(self.make_reply(c.uri(ca), c.cert) for c in ca_detail.child_certs()) - r_msg.extend(self.make_reply(r.uri(), r.roa) for r in ca_detail.roas() if r.roa is not None) - cb() - - def make_reply(self, uri, obj): - """ - Generate one reply PDU. - """ - r_pdu = self.make_pdu(tag = self.tag, self_handle = self.self_handle, uri = uri) - r_pdu.obj = obj.get_Base64() - return r_pdu - -class list_received_resources_elt(rpki.xml_utils.base_elt, left_right_namespace): - """ - <list_received_resources/> element. - """ - - element_name = "list_received_resources" - attributes = ("self_handle", "tag", "parent_handle", - "notBefore", "notAfter", "uri", "sia_uri", "aia_uri", "asn", "ipv4", "ipv6") - - def serve_dispatch(self, r_msg, cb, eb): - """ - Handle a <list_received_resources/> query. The method name is a - misnomer here, there's no action attribute and no dispatch, we - just dump a bunch of data about every certificate issued to us by - one of our parents, then return. - """ - for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents(): - for ca in parent.cas(): - ca_detail = ca.fetch_active() - if ca_detail is not None and ca_detail.latest_ca_cert is not None: - r_msg.append(self.make_reply(parent.parent_handle, ca_detail.ca_cert_uri, ca_detail.latest_ca_cert)) - cb() - - def make_reply(self, parent_handle, uri, cert): - """ - Generate one reply PDU. - """ - resources = cert.get_3779resources() - return self.make_pdu( - tag = self.tag, - self_handle = self.self_handle, - parent_handle = parent_handle, - notBefore = str(cert.getNotBefore()), - notAfter = str(cert.getNotAfter()), - uri = uri, - sia_uri = cert.get_sia_directory_uri(), - aia_uri = cert.get_aia_uri(), - asn = resources.asn, - ipv4 = resources.v4, - ipv6 = resources.v6) - -class report_error_elt(rpki.xml_utils.text_elt, left_right_namespace): - """ - <report_error/> element. - """ - - element_name = "report_error" - attributes = ("tag", "self_handle", "error_code") - text_attribute = "error_text" - - error_text = None - - @classmethod - def from_exception(cls, e, self_handle = None, tag = None): - """ - Generate a <report_error/> element from an exception. - """ - self = cls() - self.self_handle = self_handle - self.tag = tag - self.error_code = e.__class__.__name__ - self.error_text = str(e) - 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, - list_resources_elt, list_roa_requests_elt, - list_published_objects_elt, list_received_resources_elt, - report_error_elt)) - - def serve_top_level(self, gctx, cb): - """ - Serve one msg PDU. - """ - - r_msg = self.__class__.reply() - - def loop(iterator, q_pdu): - - def fail(e): - if not isinstance(e, rpki.exceptions.NotFound): - rpki.log.traceback() - r_msg.append(report_error_elt.from_exception(e, self_handle = q_pdu.self_handle, tag = q_pdu.tag)) - cb(r_msg) - - try: - q_pdu.gctx = gctx - q_pdu.serve_dispatch(r_msg, iterator, fail) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - fail(e) - - def done(): - cb(r_msg) - - rpki.async.iterator(self, loop, done) - -class sax_handler(rpki.xml_utils.sax_handler): - """ - 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.with_tls/rpki/log.py b/rpkid.with_tls/rpki/log.py deleted file mode 100644 index 8fbf6789..00000000 --- a/rpkid.with_tls/rpki/log.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -Logging facilities for RPKI libraries. - -$Id$ - -Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -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 syslog, sys, os, time -import traceback as tb - -## @var enable_trace -# Whether call tracing is enabled. - -enable_trace = False - -## @var use_syslog -# Whether to use syslog - -use_syslog = True - -tag = "" -pid = 0 - -def init(ident = "rpki", flags = syslog.LOG_PID, facility = syslog.LOG_DAEMON): - """ - Initialize logging system. - """ - - if use_syslog: - return syslog.openlog(ident, flags, facility) - else: - global tag, pid - tag = ident - pid = os.getpid() - -def set_trace(enable): - """ - Enable or disable call tracing. - """ - - global enable_trace - enable_trace = enable - -class logger(object): - """ - Closure for logging. - """ - - def __init__(self, priority): - self.priority = priority - - def __call__(self, message): - if use_syslog: - return syslog.syslog(self.priority, message) - else: - sys.stderr.write("%s %s[%d]: %s\n" % (time.strftime("%F %T"), tag, pid, 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 = tb.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])) - -def traceback(): - """ - Consolidated backtrace facility with a bit of extra info. - """ - - assert sys.exc_info() != (None, None, None), "rpki.log.traceback() called without valid trace on stack, this is a programming error" - bt = tb.extract_stack(limit = 3) - error("Exception caught in %s() at %s:%d called from %s:%d" % (bt[1][2], bt[1][0], bt[1][1], bt[0][0], bt[0][1])) - bt = tb.format_exc() - assert bt is not None, "Apparently I'm still not using the right test for null backtrace" - for line in bt.splitlines(): - warn(line) diff --git a/rpkid.with_tls/rpki/manifest.py b/rpkid.with_tls/rpki/manifest.py deleted file mode 100644 index 8581f3a0..00000000 --- a/rpkid.with_tls/rpki/manifest.py +++ /dev/null @@ -1,54 +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.with_tls/rpki/myrpki.py b/rpkid.with_tls/rpki/myrpki.py deleted file mode 100644 index c1812b5c..00000000 --- a/rpkid.with_tls/rpki/myrpki.py +++ /dev/null @@ -1,1901 +0,0 @@ -""" -This (oversized) module used to be an (oversized) program. -Refactoring in progress, some doc still needs updating. - - -This program is now the merger of three different tools: the old -myrpki.py script, the old myirbe.py script, and the newer setup.py CLI -tool. As such, it is still in need of some cleanup, but the need to -provide a saner user interface is more urgent than internal code -prettiness at the moment. In the long run, 90% of the code in this -file probably ought to move to well-designed library modules. - -Overall goal here is to build up the configuration necessary to run -rpkid and friends, by reading a config file, a collection of .CSV -files, and the results of a few out-of-band XML setup messages -exchanged with one's parents, children, and so forth. - -The config file is in an OpenSSL-compatible format, the CSV files are -simple tab-delimited text. The XML files are all generated by this -program, either the local instance or an instance being run by another -player in the system; the mechanism used to exchange these setup -messages is outside the scope of this program, feel free to use -PGP-signed mail, a web interface (not provided), USB stick, carrier -pigeons, whatever works. - -With one exception, the commands in this program avoid using any -third-party Python code other than the rpki libraries themselves; with -the same one exception, all OpenSSL work is done with the OpenSSL -command line tool (the one built as a side effect of building rcynic -will do, if your platform has no system copy or the system copy is too -old). This is all done in an attempt to make the code more portable, -so one can run most of the RPKI back end software on a laptop or -whatever. The one exception is the configure_daemons command, which -must, of necessity, use the same communication libraries as the -daemons with which it is conversing. So that one command will not -work if the correct Python modules are not available. - - -$Id$ - -Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -""" - -from __future__ import with_statement - -import subprocess, csv, re, os, getopt, sys, base64, time, glob, copy, warnings -import rpki.config, rpki.cli, rpki.sundial, rpki.log, rpki.oids - -try: - from lxml.etree import (Element, SubElement, ElementTree, - fromstring as ElementFromString, - tostring as ElementToString) -except ImportError: - from xml.etree.ElementTree import (Element, SubElement, ElementTree, - fromstring as ElementFromString, - tostring as ElementToString) - - - -# Our XML namespace and protocol version. - -namespace = "http://www.hactrn.net/uris/rpki/myrpki/" -version = "2" -namespaceQName = "{" + namespace + "}" - -# Whether to include incomplete entries when rendering to XML. - -allow_incomplete = False - -# Whether to whine about incomplete entries while rendering to XML. - -whine = False - -class comma_set(set): - """ - Minor customization of set(), to provide a print syntax. - """ - - def __str__(self): - return ",".join(self) - -class EntityDB(object): - """ - Wrapper for entitydb path lookups. Hmm, maybe some or all of the - entitydb glob stuff should end up here too? Later. - """ - - def __init__(self, cfg): - self.dir = cfg.get("entitydb_dir", "entitydb") - - def __call__(self, *args): - return os.path.join(self.dir, *args) - - def iterate(self, *args): - return glob.iglob(os.path.join(self.dir, *args)) - -class roa_request(object): - """ - Representation of a ROA request. - """ - - v4re = re.compile("^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]+(-[0-9]+)?$", re.I) - v6re = re.compile("^([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4}/[0-9]+(-[0-9]+)?$", re.I) - - def __init__(self, asn, group): - self.asn = asn - self.group = group - self.v4 = comma_set() - self.v6 = comma_set() - - def __repr__(self): - s = "<%s asn %s group %s" % (self.__class__.__name__, self.asn, self.group) - if self.v4: - s += " v4 %s" % self.v4 - if self.v6: - s += " v6 %s" % self.v6 - return s + ">" - - def add(self, prefix): - """ - Add one prefix to this ROA request. - """ - if self.v4re.match(prefix): - self.v4.add(prefix) - elif self.v6re.match(prefix): - self.v6.add(prefix) - else: - raise RuntimeError, "Bad prefix syntax: %r" % (prefix,) - - def xml(self, e): - """ - Generate XML element represeting representing this ROA request. - """ - e = SubElement(e, "roa_request", - asn = self.asn, - v4 = str(self.v4), - v6 = str(self.v6)) - e.tail = "\n" - -class roa_requests(dict): - """ - Database of ROA requests. - """ - - def add(self, asn, group, prefix): - """ - Add one <ASN, group, prefix> set to ROA request database. - """ - key = (asn, group) - if key not in self: - self[key] = roa_request(asn, group) - self[key].add(prefix) - - def xml(self, e): - """ - Render ROA requests as XML elements. - """ - for r in self.itervalues(): - r.xml(e) - - @classmethod - def from_csv(cls, roa_csv_file): - """ - Parse ROA requests from CSV file. - """ - self = cls() - # format: p/n-m asn group - for pnm, asn, group in csv_reader(roa_csv_file, columns = 3): - self.add(asn = asn, group = group, prefix = pnm) - return self - -class child(object): - """ - Representation of one child entity. - """ - - v4re = re.compile("^(([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]+)|(([0-9]{1,3}\.){3}[0-9]{1,3}-([0-9]{1,3}\.){3}[0-9]{1,3})$", re.I) - v6re = re.compile("^(([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4}/[0-9]+)|(([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4}-([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4})$", re.I) - - def __init__(self, handle): - self.handle = handle - self.asns = comma_set() - self.v4 = comma_set() - self.v6 = comma_set() - self.validity = None - self.bpki_certificate = None - - def __repr__(self): - s = "<%s %s" % (self.__class__.__name__, self.handle) - if self.asns: - s += " asn %s" % self.asns - if self.v4: - s += " v4 %s" % self.v4 - if self.v6: - s += " v6 %s" % self.v6 - if self.validity: - s += " valid %s" % self.validity - if self.bpki_certificate: - s += " cert %s" % self.bpki_certificate - return s + ">" - - def add(self, prefix = None, asn = None, validity = None, bpki_certificate = None): - """ - Add prefix, autonomous system number, validity date, or BPKI - certificate for this child. - """ - if prefix is not None: - if self.v4re.match(prefix): - self.v4.add(prefix) - elif self.v6re.match(prefix): - self.v6.add(prefix) - else: - raise RuntimeError, "Bad prefix syntax: %r" % (prefix,) - if asn is not None: - self.asns.add(asn) - if validity is not None: - self.validity = validity - if bpki_certificate is not None: - self.bpki_certificate = bpki_certificate - - def xml(self, e): - """ - Render this child as an XML element. - """ - complete = self.bpki_certificate and self.validity - if whine and not complete: - print "Incomplete child entry %s" % self - if complete or allow_incomplete: - e = SubElement(e, "child", - handle = self.handle, - valid_until = self.validity, - asns = str(self.asns), - v4 = str(self.v4), - v6 = str(self.v6)) - e.tail = "\n" - if self.bpki_certificate: - PEMElement(e, "bpki_certificate", self.bpki_certificate) - -class children(dict): - """ - Database of children. - """ - - def add(self, handle, prefix = None, asn = None, validity = None, bpki_certificate = None): - """ - Add resources to a child, creating the child object if necessary. - """ - if handle not in self: - self[handle] = child(handle) - self[handle].add(prefix = prefix, asn = asn, validity = validity, bpki_certificate = bpki_certificate) - - def xml(self, e): - """ - Render children database to XML. - """ - for c in self.itervalues(): - c.xml(e) - - @classmethod - def from_csv(cls, prefix_csv_file, asn_csv_file, fxcert, entitydb): - """ - Parse child resources, certificates, and validity dates from CSV files. - """ - self = cls() - for f in entitydb.iterate("children", "*.xml"): - c = etree_read(f) - self.add(handle = os.path.splitext(os.path.split(f)[-1])[0], - validity = c.get("valid_until"), - bpki_certificate = fxcert(c.findtext("bpki_child_ta"))) - # childname p/n - for handle, pn in csv_reader(prefix_csv_file, columns = 2): - self.add(handle = handle, prefix = pn) - # childname asn - for handle, asn in csv_reader(asn_csv_file, columns = 2): - self.add(handle = handle, asn = asn) - return self - -class parent(object): - """ - Representation of one parent entity. - """ - - def __init__(self, handle): - self.handle = handle - self.service_uri = None - self.bpki_cms_certificate = None - self.bpki_https_certificate = None - self.myhandle = None - self.sia_base = None - - def __repr__(self): - s = "<%s %s" % (self.__class__.__name__, self.handle) - if self.myhandle: - s += " myhandle %s" % self.myhandle - if self.service_uri: - s += " uri %s" % self.service_uri - if self.sia_base: - s += " sia %s" % self.sia_base - if self.bpki_cms_certificate: - s += " cms %s" % self.bpki_cms_certificate - if self.bpki_https_certificate: - s += " https %s" % self.bpki_https_certificate - return s + ">" - - def add(self, service_uri = None, - bpki_cms_certificate = None, - bpki_https_certificate = None, - myhandle = None, - sia_base = None): - """ - Add service URI or BPKI certificates to this parent object. - """ - if service_uri is not None: - self.service_uri = service_uri - if bpki_cms_certificate is not None: - self.bpki_cms_certificate = bpki_cms_certificate - if bpki_https_certificate is not None: - self.bpki_https_certificate = bpki_https_certificate - if myhandle is not None: - self.myhandle = myhandle - if sia_base is not None: - self.sia_base = sia_base - - def xml(self, e): - """ - Render this parent object to XML. - """ - complete = self.bpki_cms_certificate and self.bpki_https_certificate and self.myhandle and self.service_uri and self.sia_base - if whine and not complete: - print "Incomplete parent entry %s" % self - if complete or allow_incomplete: - e = SubElement(e, "parent", - handle = self.handle, - myhandle = self.myhandle, - service_uri = self.service_uri, - sia_base = self.sia_base) - e.tail = "\n" - if self.bpki_cms_certificate: - PEMElement(e, "bpki_cms_certificate", self.bpki_cms_certificate) - if self.bpki_https_certificate: - PEMElement(e, "bpki_https_certificate", self.bpki_https_certificate) - -class parents(dict): - """ - Database of parent objects. - """ - - def add(self, handle, - service_uri = None, - bpki_cms_certificate = None, - bpki_https_certificate = None, - myhandle = None, - sia_base = None): - """ - Add service URI or certificates to parent object, creating it if necessary. - """ - if handle not in self: - self[handle] = parent(handle) - self[handle].add(service_uri = service_uri, - bpki_cms_certificate = bpki_cms_certificate, - bpki_https_certificate = bpki_https_certificate, - myhandle = myhandle, - sia_base = sia_base) - - def xml(self, e): - for c in self.itervalues(): - c.xml(e) - - @classmethod - def from_csv(cls, fxcert, entitydb): - """ - Parse parent data from entitydb. - """ - self = cls() - for f in entitydb.iterate("parents", "*.xml"): - h = os.path.splitext(os.path.split(f)[-1])[0] - p = etree_read(f) - r = etree_read(f.replace(os.path.sep + "parents" + os.path.sep, - os.path.sep + "repositories" + os.path.sep)) - assert r.get("type") == "confirmed" - self.add(handle = h, - service_uri = p.get("service_uri"), - bpki_cms_certificate = fxcert(p.findtext("bpki_resource_ta")), - bpki_https_certificate = fxcert(p.findtext("bpki_server_ta")), - myhandle = p.get("child_handle"), - sia_base = r.get("sia_base")) - return self - -class repository(object): - """ - Representation of one repository entity. - """ - - def __init__(self, handle): - self.handle = handle - self.service_uri = None - self.bpki_certificate = None - - def __repr__(self): - s = "<%s %s" % (self.__class__.__name__, self.handle) - if self.service_uri: - s += " uri %s" % self.service_uri - if self.bpki_certificate: - s += " cert %s" % self.bpki_certificate - return s + ">" - - def add(self, service_uri = None, bpki_certificate = None): - """ - Add service URI or BPKI certificates to this repository object. - """ - if service_uri is not None: - self.service_uri = service_uri - if bpki_certificate is not None: - self.bpki_certificate = bpki_certificate - - def xml(self, e): - """ - Render this repository object to XML. - """ - complete = self.bpki_certificate and self.service_uri - if whine and not complete: - print "Incomplete repository entry %s" % self - if complete or allow_incomplete: - e = SubElement(e, "repository", - handle = self.handle, - service_uri = self.service_uri) - e.tail = "\n" - if self.bpki_certificate: - PEMElement(e, "bpki_certificate", self.bpki_certificate) - -class repositories(dict): - """ - Database of repository objects. - """ - - def add(self, handle, - service_uri = None, - bpki_certificate = None): - """ - Add service URI or certificate to repository object, creating it if necessary. - """ - if handle not in self: - self[handle] = repository(handle) - self[handle].add(service_uri = service_uri, - bpki_certificate = bpki_certificate) - - def xml(self, e): - for c in self.itervalues(): - c.xml(e) - - @classmethod - def from_csv(cls, fxcert, entitydb): - """ - Parse repository data from entitydb. - """ - self = cls() - for f in entitydb.iterate("repositories", "*.xml"): - h = os.path.splitext(os.path.split(f)[-1])[0] - r = etree_read(f) - assert r.get("type") == "confirmed" - self.add(handle = h, - service_uri = r.get("service_uri"), - bpki_certificate = fxcert(r.findtext("bpki_server_ta"))) - return self - -class csv_reader(object): - """ - Reader for tab-delimited text that's (slightly) friendlier than the - stock Python csv module (which isn't intended for direct use by - humans anyway, and neither was this package originally, but that - seems to be the way that it has evolved...). - - Columns parameter specifies how many columns users of the reader - expect to see; lines with fewer columns will be padded with None - values. - - Original API design for this class courtesy of Warren Kumari, but - don't blame him if you don't like what I did with his ideas. - """ - - def __init__(self, filename, columns = None, min_columns = None, comment_characters = "#;"): - assert columns is None or isinstance(columns, int) - assert min_columns is None or isinstance(min_columns, int) - if columns is not None and min_columns is None: - min_columns = columns - self.filename = filename - self.columns = columns - self.min_columns = min_columns - self.comment_characters = comment_characters - self.file = open(filename, "r") - - def __iter__(self): - line_number = 0 - for line in self.file: - line_number += 1 - line = line.strip() - if not line or line[0] in self.comment_characters: - continue - fields = line.split() - if self.min_columns is not None and len(fields) < self.min_columns: - raise RuntimeError, "%s:%d: Not enough columns in line %r" % (self.filename, line_number, line) - if self.columns is not None and len(fields) > self.columns: - raise RuntimeError, "%s:%d: Too many columns in line %r" % (self.filename, line_number, line) - if self.columns is not None and len(fields) < self.columns: - fields += tuple(None for i in xrange(self.columns - len(fields))) - yield fields - -class csv_writer(object): - """ - Writer object for tab delimited text. We just use the stock CSV - module in excel-tab mode for this. - - If "renmwo" is set (default), the file will be written to - a temporary name and renamed to the real filename after closing. - """ - - def __init__(self, filename, renmwo = True): - self.filename = filename - self.renmwo = "%s.%d.~rnnmwo~" % (filename, os.getpid()) if renmwo else filename - self.file = open(self.renmwo, "w") - self.writer = csv.writer(self.file, dialect = csv.get_dialect("excel-tab")) - - def close(self): - """ - Close this writer. - """ - if self.file is not None: - self.file.close() - self.file = None - if self.filename != self.renmwo: - os.rename(self.renmwo, self.filename) - - def __getattr__(self, attr): - """ - Fake inheritance from whatever object csv.writer deigns to give us. - """ - return getattr(self.writer, attr) - -def PEMElement(e, tag, filename, **kwargs): - """ - Create an XML element containing Base64 encoded data taken from a - PEM file. - """ - lines = open(filename).readlines() - while lines: - if lines.pop(0).startswith("-----BEGIN "): - break - while lines: - if lines.pop(-1).startswith("-----END "): - break - if e.text is None: - e.text = "\n" - se = SubElement(e, tag, **kwargs) - se.text = "\n" + "".join(lines) - se.tail = "\n" - return se - -class CA(object): - """ - Representation of one certification authority. - """ - - # Mapping of path restriction values we use to OpenSSL config file - # section names. - - path_restriction = { 0 : "ca_x509_ext_xcert0", - 1 : "ca_x509_ext_xcert1" } - - def __init__(self, cfg_file, dir): - self.cfg = cfg_file - self.dir = dir - self.cer = dir + "/ca.cer" - self.key = dir + "/ca.key" - self.req = dir + "/ca.req" - self.crl = dir + "/ca.crl" - self.index = dir + "/index" - self.serial = dir + "/serial" - self.crlnum = dir + "/crl_number" - - cfg = rpki.config.parser(cfg_file, "myrpki") - self.openssl = cfg.get("openssl", "openssl") - - self.env = { "PATH" : os.environ["PATH"], - "BPKI_DIRECTORY" : dir, - "RANDFILE" : ".OpenSSL.whines.unless.I.set.this", - "OPENSSL_CONF" : cfg_file } - - def run_openssl(self, *cmd, **kwargs): - """ - Run an OpenSSL command, suppresses stderr unless OpenSSL returns - failure, and returns stdout. - """ - stdin = kwargs.pop("stdin", None) - env = self.env.copy() - env.update(kwargs) - cmd = (self.openssl,) + cmd - p = subprocess.Popen(cmd, env = env, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE) - stdout, stderr = p.communicate(stdin) - if p.wait() != 0: - sys.stderr.write("OpenSSL command failed: " + stderr + "\n") - raise subprocess.CalledProcessError(returncode = p.returncode, cmd = cmd) - return stdout - - def run_ca(self, *args): - """ - Run OpenSSL "ca" command with common initial arguments. - """ - self.run_openssl("ca", "-batch", "-config", self.cfg, *args) - - def run_req(self, key_file, req_file, log_key = sys.stdout): - """ - Run OpenSSL "genrsa" and "req" commands. - """ - if not os.path.exists(key_file): - if log_key: - log_key.write("Generating 2048-bit RSA key %s\n" % os.path.realpath(key_file)) - self.run_openssl("genrsa", "-out", key_file, "2048") - if not os.path.exists(req_file): - self.run_openssl("req", "-new", "-sha256", "-config", self.cfg, "-key", key_file, "-out", req_file) - - def run_dgst(self, input, algorithm = "md5"): - """ - Run OpenSSL "dgst" command, return cleaned-up result. - """ - hash = self.run_openssl("dgst", "-" + algorithm, stdin = input) - # - # Twits just couldn't leave well enough alone, grr. - hash = "".join(hash.split()) - if hash.startswith("(stdin)="): - hash = hash[len("(stdin)="):] - return hash - - @staticmethod - def touch_file(filename, content = None): - """ - Create dumb little text files expected by OpenSSL "ca" utility. - """ - if not os.path.exists(filename): - f = open(filename, "w") - if content is not None: - f.write(content) - f.close() - - def setup(self, ca_name): - """ - Set up this CA. ca_name is an X.509 distinguished name in - /tag=val/tag=val format. - """ - - modified = False - - if not os.path.exists(self.dir): - os.makedirs(self.dir) - self.touch_file(self.index) - self.touch_file(self.serial, "01\n") - self.touch_file(self.crlnum, "01\n") - - self.run_req(key_file = self.key, req_file = self.req) - - if not os.path.exists(self.cer): - modified = True - self.run_ca("-selfsign", "-extensions", "ca_x509_ext_ca", "-subj", ca_name, "-in", self.req, "-out", self.cer) - - if not os.path.exists(self.crl): - modified = True - self.run_ca("-gencrl", "-out", self.crl) - - return modified - - def ee(self, ee_name, base_name): - """ - Issue an end-enity certificate. - """ - key_file = "%s/%s.key" % (self.dir, base_name) - req_file = "%s/%s.req" % (self.dir, base_name) - cer_file = "%s/%s.cer" % (self.dir, base_name) - self.run_req(key_file = key_file, req_file = req_file) - if not os.path.exists(cer_file): - self.run_ca("-extensions", "ca_x509_ext_ee", "-subj", ee_name, "-in", req_file, "-out", cer_file) - return True - else: - return False - - def cms_xml_sign(self, ee_name, base_name, elt): - """ - Sign an XML object with CMS, return Base64 text. - """ - self.ee(ee_name, base_name) - return base64.b64encode(self.run_openssl( - "cms", "-sign", "-binary", "-outform", "DER", - "-keyid", "-md", "sha256", "-nodetach", "-nosmimecap", - "-econtent_type", ".".join(str(i) for i in rpki.oids.name2oid["id-ct-xml"]), - "-inkey", "%s/%s.key" % (self.dir, base_name), - "-signer", "%s/%s.cer" % (self.dir, base_name), - stdin = ElementToString(etree_pre_write(elt)))) - - def cms_xml_verify(self, b64, ca): - """ - Attempt to verify and extract XML from a Base64-encoded signed CMS - object. CA is the filename of a certificate that we expect to be - the issuer of the EE certificate bundled with the CMS, and must - previously have been cross-certified under our trust anchor. - """ - # In theory, we should be able to use the -certfile parameter to - # pass in the CA certificate, but in practice, I have never gotten - # this to work, either with the command line tool or in the - # OpenSSL C API. Dunno why. Passing both TA and CA via -CAfile - # does work, so we do that, using a temporary file, sigh. - CAfile = os.path.join(self.dir, "temp.%s.pem" % os.getpid()) - try: - f = open(CAfile, "w") - f.write(open(self.cer).read()) - f.write(open(ca).read()) - f.close() - return etree_post_read(ElementFromString(self.run_openssl( - "cms", "-verify", "-inform", "DER", "-CAfile", CAfile, - stdin = base64.b64decode(b64)))) - finally: - if os.path.exists(CAfile): - os.unlink(CAfile) - - def bsc(self, pkcs10): - """ - Issue BSC certificiate, if we have a PKCS #10 request for it. - """ - - if pkcs10 is None: - return None, None - - pkcs10 = base64.b64decode(pkcs10) - - hash = self.run_dgst(pkcs10) - - req_file = "%s/bsc.%s.req" % (self.dir, hash) - cer_file = "%s/bsc.%s.cer" % (self.dir, hash) - - if not os.path.exists(cer_file): - self.run_openssl("req", "-inform", "DER", "-out", req_file, stdin = pkcs10) - self.run_ca("-extensions", "ca_x509_ext_ee", "-in", req_file, "-out", cer_file) - - return req_file, cer_file - - def fxcert(self, b64, filename = None, path_restriction = 0): - """ - Write PEM certificate to file, then cross-certify. - """ - fn = os.path.join(self.dir, filename or "temp.%s.cer" % os.getpid()) - try: - self.run_openssl("x509", "-inform", "DER", "-out", fn, - stdin = base64.b64decode(b64)) - return self.xcert(fn, path_restriction) - finally: - if not filename and os.path.exists(fn): - os.unlink(fn) - - def xcert_filename(self, cert): - """ - Generate filename for a cross-certification. - - Extracts public key and subject name from PEM file and hash it so - we can use the result as a tag for cross-certifying this cert. - """ - - if cert and os.path.exists(cert): - return "%s/xcert.%s.cer" % (self.dir, self.run_dgst(self.run_openssl( - "x509", "-noout", "-pubkey", "-subject", "-in", cert)).strip()) - else: - return None - - def xcert(self, cert, path_restriction = 0): - """ - Cross-certify a certificate represented as a PEM file, if we - haven't already. This only works for self-signed certs, due to - limitations of the OpenSSL command line tool, but that suffices - for our purposes. - """ - - xcert = self.xcert_filename(cert) - if not os.path.exists(xcert): - self.run_ca("-ss_cert", cert, "-out", xcert, "-extensions", self.path_restriction[path_restriction]) - return xcert - - def xcert_revoke(self, cert): - """ - Revoke a cross-certification and regenerate CRL. - """ - - xcert = self.xcert_filename(cert) - if xcert: - self.run_ca("-revoke", xcert) - self.run_ca("-gencrl", "-out", self.crl) - -def etree_validate(e): - # This is a kludge, schema should be loaded as module or configured - # in .conf, but it will do as a temporary debugging hack. - schema = os.getenv("MYRPKI_RNG") - if schema: - try: - import lxml.etree - except ImportError: - return - try: - lxml.etree.RelaxNG(file = schema).assertValid(e) - except lxml.etree.RelaxNGParseError: - return - except lxml.etree.DocumentInvalid: - print lxml.etree.tostring(e, pretty_print = True) - raise - -def etree_write(e, filename, verbose = False, validate = True, msg = None): - """ - Write out an etree to a file, safely. - - I still miss SYSCAL(RENMWO). - """ - filename = os.path.realpath(filename) - tempname = filename - if not filename.startswith("/dev/"): - tempname += ".tmp" - if verbose or msg: - print "Writing", filename - if msg: - print msg - e = etree_pre_write(e, validate) - ElementTree(e).write(tempname) - if tempname != filename: - os.rename(tempname, filename) - -def etree_pre_write(e, validate = True): - """ - Do the namespace frobbing needed on write; broken out of - etree_write() because also needed with ElementToString(). - """ - e = copy.deepcopy(e) - e.set("version", version) - for i in e.getiterator(): - if i.tag[0] != "{": - i.tag = namespaceQName + i.tag - assert i.tag.startswith(namespaceQName) - if validate: - etree_validate(e) - return e - -def etree_read(filename, verbose = False, validate = True): - """ - Read an etree from a file, verifying then stripping XML namespace - cruft. - """ - if verbose: - print "Reading", filename - e = ElementTree(file = filename).getroot() - return etree_post_read(e, validate) - -def etree_post_read(e, validate = True): - """ - Do the namespace frobbing needed on read; broken out of etree_read() - beause also needed by ElementFromString(). - """ - if validate: - etree_validate(e) - for i in e.getiterator(): - if i.tag.startswith(namespaceQName): - i.tag = i.tag[len(namespaceQName):] - else: - raise RuntimeError, "XML tag %r is not in namespace %r" % (i.tag, namespace) - return e - -def b64_equal(thing1, thing2): - """ - Compare two Base64-encoded values for equality. - """ - return "".join(thing1.split()) == "".join(thing2.split()) - - - -class main(rpki.cli.Cmd): - - prompt = "myrpki> " - - completedefault = rpki.cli.Cmd.filename_complete - - show_xml = False - - def __init__(self): - os.environ["TZ"] = "UTC" - time.tzset() - - rpki.log.use_syslog = False - - self.cfg_file = os.getenv("MYRPKI_CONF", "myrpki.conf") - - opts, argv = getopt.getopt(sys.argv[1:], "c:h?", ["config=", "help"]) - for o, a in opts: - if o in ("-c", "--config"): - self.cfg_file = a - elif o in ("-h", "--help", "-?"): - argv = ["help"] - - if not argv or argv[0] != "help": - rpki.log.init("myrpki") - self.read_config() - - rpki.cli.Cmd.__init__(self, argv) - - - def help_overview(self): - """ - Show program __doc__ string. Perhaps there's some clever way to - do this using the textwrap module, but for now something simple - and crude will suffice. - """ - for line in __doc__.splitlines(True): - self.stdout.write(" " * 4 + line) - self.stdout.write("\n") - - def entitydb_complete(self, prefix, text, line, begidx, endidx): - """ - Completion helper for entitydb filenames. - """ - names = [] - for name in self.entitydb.iterate(prefix, "*.xml"): - name = os.path.splitext(os.path.basename(name))[0] - if name.startswith(text): - names.append(name) - return names - - def read_config(self): - - self.cfg = rpki.config.parser(self.cfg_file, "myrpki") - - self.histfile = self.cfg.get("history_file", ".myrpki_history") - self.handle = self.cfg.get("handle") - self.run_rpkid = self.cfg.getboolean("run_rpkid") - self.run_pubd = self.cfg.getboolean("run_pubd") - self.run_rootd = self.cfg.getboolean("run_rootd") - self.entitydb = EntityDB(self.cfg) - - if self.run_rootd and (not self.run_pubd or not self.run_rpkid): - raise RuntimeError, "Can't run rootd unless also running rpkid and pubd" - - self.bpki_resources = CA(self.cfg_file, self.cfg.get("bpki_resources_directory")) - if self.run_rpkid or self.run_pubd or self.run_rootd: - self.bpki_servers = CA(self.cfg_file, self.cfg.get("bpki_servers_directory")) - else: - self.bpki_servers = None - - self.pubd_contact_info = self.cfg.get("pubd_contact_info", "") - - self.rsync_module = self.cfg.get("publication_rsync_module") - self.rsync_server = self.cfg.get("publication_rsync_server") - - - def do_initialize(self, arg): - """ - Initialize an RPKI installation. This command reads the - configuration file, creates the BPKI and EntityDB directories, - generates the initial BPKI certificates, and creates an XML file - describing the resource-holding aspect of this RPKI installation. - """ - - if arg: - raise RuntimeError, "This command takes no arguments" - - self.bpki_resources.setup(self.cfg.get("bpki_resources_ta_dn", - "/CN=%s BPKI Resource Trust Anchor" % self.handle)) - if self.run_rpkid or self.run_pubd or self.run_rootd: - self.bpki_servers.setup(self.cfg.get("bpki_servers_ta_dn", - "/CN=%s BPKI Server Trust Anchor" % self.handle)) - - # Create entitydb directories. - - for i in ("parents", "children", "repositories", "pubclients"): - d = self.entitydb(i) - if not os.path.exists(d): - os.makedirs(d) - - if self.run_rpkid or self.run_pubd or self.run_rootd: - - if self.run_rpkid: - self.bpki_servers.ee(self.cfg.get("bpki_rpkid_ee_dn", - "/CN=%s rpkid server certificate" % self.handle), "rpkid") - self.bpki_servers.ee(self.cfg.get("bpki_irdbd_ee_dn", - "/CN=%s irdbd server certificate" % self.handle), "irdbd") - if self.run_pubd: - self.bpki_servers.ee(self.cfg.get("bpki_pubd_ee_dn", - "/CN=%s pubd server certificate" % self.handle), "pubd") - if self.run_rpkid or self.run_pubd: - self.bpki_servers.ee(self.cfg.get("bpki_irbe_ee_dn", - "/CN=%s irbe client certificate" % self.handle), "irbe") - if self.run_rootd: - self.bpki_servers.ee(self.cfg.get("bpki_rootd_ee_dn", - "/CN=%s rootd server certificate" % self.handle), "rootd") - - # Build the identity.xml file. Need to check for existing file so we don't - # overwrite? Worry about that later. - - e = Element("identity", handle = self.handle) - PEMElement(e, "bpki_ta", self.bpki_resources.cer) - etree_write(e, self.entitydb("identity.xml"), - msg = None if self.run_rootd else 'This is the "identity" file you will need to send to your parent') - - # If we're running rootd, construct a fake parent to go with it, - # and cross-certify in both directions so we can talk to rootd. - - if self.run_rootd: - - e = Element("parent", parent_handle = self.handle, child_handle = self.handle, - service_uri = "https://localhost:%s/" % self.cfg.get("rootd_server_port"), - valid_until = str(rpki.sundial.now() + rpki.sundial.timedelta(days = 365))) - PEMElement(e, "bpki_resource_ta", self.bpki_servers.cer) - PEMElement(e, "bpki_server_ta", self.bpki_servers.cer) - PEMElement(e, "bpki_child_ta", self.bpki_resources.cer) - SubElement(e, "repository", type = "offer") - etree_write(e, self.entitydb("parents", "%s.xml" % self.handle)) - - self.bpki_resources.xcert(self.bpki_servers.cer) - - rootd_child_fn = self.cfg.get("child-bpki-cert", None, "rootd") - if not os.path.exists(rootd_child_fn): - os.link(self.bpki_servers.xcert(self.bpki_resources.cer), rootd_child_fn) - - repo_file_name = self.entitydb("repositories", "%s.xml" % self.handle) - - try: - want_offer = etree_read(repo_file_name).get("type") != "confirmed" - except IOError: - want_offer = True - - if want_offer: - e = Element("repository", type = "offer", handle = self.handle, parent_handle = self.handle) - PEMElement(e, "bpki_client_ta", self.bpki_resources.cer) - etree_write(e, repo_file_name, - msg = 'This is the "repository offer" file for you to use if you want to publish in your own repository') - - def do_update_bpki(self, arg): - """ - Update BPKI certificates. Assumes an existing RPKI installation. - - Basic plan here is to reissue all BPKI certificates we can, right - now. In the long run we might want to be more clever about only - touching ones that need maintenance, but this will do for a start. - - Most likely this should be run under cron. - """ - - if self.bpki_servers: - bpkis = (self.bpki_resources, self.bpki_servers) - else: - bpkis = (self.bpki_resources,) - - for bpki in bpkis: - for cer in glob.iglob("%s/*.cer" % bpki.dir): - key = cer[0:-4] + ".key" - req = cer[0:-4] + ".req" - if os.path.exists(key): - print "Regenerating BPKI PKCS #10", req - bpki.run_openssl("x509", "-x509toreq", "-in", cer, "-out", req, "-signkey", key) - print "Clearing BPKI certificate", cer - os.unlink(cer) - if cer == bpki.cer: - assert req == bpki.req - print "Regenerating certificate", cer - bpki.run_ca("-selfsign", "-extensions", "ca_x509_ext_ca", "-in", req, "-out", cer) - - print "Regenerating CRLs" - for bpki in bpkis: - bpki.run_ca("-gencrl", "-out", bpki.crl) - - self.do_initialize(None) - if self.run_rpkid or self.run_pubd or self.run_rootd: - self.do_configure_daemons(arg) - else: - self.do_configure_resources(None) - - - def do_configure_child(self, arg): - """ - Configure a new child of this RPKI entity, given the child's XML - identity file as an input. This command extracts the child's data - from the XML, cross-certifies the child's resource-holding BPKI - certificate, and generates an XML file describing the relationship - between the child and this parent, including this parent's BPKI - data and up-down protocol service URI. - """ - - child_handle = None - - opts, argv = getopt.getopt(arg.split(), "", ["child_handle="]) - for o, a in opts: - if o == "--child_handle": - child_handle = a - - if len(argv) != 1: - raise RuntimeError, "Need to specify filename for child.xml" - - c = etree_read(argv[0]) - - if child_handle is None: - child_handle = c.get("handle") - - try: - e = etree_read(self.cfg.get("xml_filename")) - service_uri_base = e.get("service_uri") - server_ta = e.findtext("bpki_server_ta") - except IOError: - service_uri_base = None - server_ta = None - - if not service_uri_base and self.run_rpkid: - service_uri_base = "https://%s:%s/up-down/%s" % (self.cfg.get("rpkid_server_host"), - self.cfg.get("rpkid_server_port"), - self.handle) - if not service_uri_base or not server_ta: - print "Sorry, you can't set up children of a hosted config that itself has not yet been set up" - return - - print "Child calls itself %r, we call it %r" % (c.get("handle"), child_handle) - - if self.run_rpkid or self.run_pubd or self.run_rootd: - self.bpki_servers.fxcert(c.findtext("bpki_ta")) - - e = Element("parent", parent_handle = self.handle, child_handle = child_handle, - service_uri = "%s/%s" % (service_uri_base, child_handle), - valid_until = str(rpki.sundial.now() + rpki.sundial.timedelta(days = 365))) - - PEMElement(e, "bpki_resource_ta", self.bpki_resources.cer) - if self.run_rpkid or self.run_pubd or self.run_rootd: - PEMElement(e, "bpki_server_ta", self.bpki_servers.cer) - else: - assert server_ta is not None - SubElement(e, "bpki_server_ta").text = server_ta - SubElement(e, "bpki_child_ta").text = c.findtext("bpki_ta") - - try: - repo = None - for f in self.entitydb.iterate("repositories", "*.xml"): - r = etree_read(f) - if r.get("type") == "confirmed": - if repo is not None: - raise RuntimeError, "Too many repositories, I don't know what to do, not giving referral" - repo_handle = os.path.splitext(os.path.split(f)[-1])[0] - repo = r - if repo is None: - raise RuntimeError, "Couldn't find any usable repositories, not giving referral" - - if repo_handle == self.handle: - SubElement(e, "repository", type = "offer") - else: - proposed_sia_base = repo.get("sia_base") + child_handle + "/" - r = Element("referral", authorized_sia_base = proposed_sia_base) - r.text = c.findtext("bpki_ta") - auth = self.bpki_resources.cms_xml_sign( - "/CN=%s Publication Referral" % self.handle, "referral", r) - r = SubElement(e, "repository", type = "referral") - SubElement(r, "authorization", referrer = repo.get("client_handle")).text = auth - SubElement(r, "contact_info").text = repo.findtext("contact_info") - - except RuntimeError, err: - print err - - etree_write(e, self.entitydb("children", "%s.xml" % child_handle), - msg = "Send this file back to the child you just configured") - - - def do_delete_child(self, arg): - """ - Delete a child of this RPKI entity. - - This should check that the XML file it's deleting really is a - child, but doesn't, yet. - """ - - try: - os.unlink(self.entitydb("children", "%s.xml" % arg)) - except OSError: - print "No such child \"%s\"" % arg - - def complete_delete_child(self, *args): - return self.entitydb_complete("children", *args) - - - def do_configure_parent(self, arg): - """ - Configure a new parent of this RPKI entity, given the output of - the parent's configure_child command as input. This command reads - the parent's response XML, extracts the parent's BPKI and service - URI information, cross-certifies the parent's BPKI data into this - entity's BPKI, and checks for offers or referrals of publication - service. If a publication offer or referral is present, we - generate a request-for-service message to that repository, in case - the user wants to avail herself of the referral or offer. - """ - - parent_handle = None - - opts, argv = getopt.getopt(arg.split(), "", ["parent_handle="]) - for o, a in opts: - if o == "--parent_handle": - parent_handle = a - - if len(argv) != 1: - raise RuntimeError, "Need to specify filename for parent.xml on command line" - - p = etree_read(argv[0]) - - if parent_handle is None: - parent_handle = p.get("parent_handle") - - print "Parent calls itself %r, we call it %r" % (p.get("parent_handle"), parent_handle) - print "Parent calls us %r" % p.get("child_handle") - - self.bpki_resources.fxcert(p.findtext("bpki_resource_ta")) - self.bpki_resources.fxcert(p.findtext("bpki_server_ta")) - - etree_write(p, self.entitydb("parents", "%s.xml" % parent_handle)) - - r = p.find("repository") - - if r is not None and r.get("type") in ("offer", "referral"): - r.set("handle", self.handle) - r.set("parent_handle", parent_handle) - PEMElement(r, "bpki_client_ta", self.bpki_resources.cer) - etree_write(r, self.entitydb("repositories", "%s.xml" % parent_handle), - msg = 'This is the "repository %s" file to send to the repository operator' % r.get("type")) - else: - print "Couldn't find repository offer or referral" - - - def do_delete_parent(self, arg): - """ - Delete a parent of this RPKI entity. - - This should check that the XML file it's deleting really is a - parent, but doesn't, yet. - """ - - try: - os.unlink(self.entitydb("parents", "%s.xml" % arg)) - except OSError: - print "No such parent \"%s\"" % arg - - def complete_delete_parent(self, *args): - return self.entitydb_complete("parents", *args) - - - def do_configure_publication_client(self, arg): - """ - Configure publication server to know about a new client, given the - client's request-for-service message as input. This command reads - the client's request for service, cross-certifies the client's - BPKI data, and generates a response message containing the - repository's BPKI data and service URI. - """ - - sia_base = None - - opts, argv = getopt.getopt(arg.split(), "", ["sia_base="]) - for o, a in opts: - if o == "--sia_base": - sia_base = a - - if len(argv) != 1: - raise RuntimeError, "Need to specify filename for client.xml" - - client = etree_read(argv[0]) - - if sia_base is None: - - auth = client.find("authorization") - if auth is not None: - print "Found <authorization/> element, this looks like a referral" - referrer = etree_read(self.entitydb("pubclients", "%s.xml" % auth.get("referrer").replace("/","."))) - referrer = self.bpki_servers.fxcert(referrer.findtext("bpki_client_ta")) - referral = self.bpki_servers.cms_xml_verify(auth.text, referrer) - if not b64_equal(referral.text, client.findtext("bpki_client_ta")): - raise RuntimeError, "Referral trust anchor does not match" - sia_base = referral.get("authorized_sia_base") - - elif client.get("parent_handle") == self.handle: - print "Client claims to be our child, checking" - client_ta = client.findtext("bpki_client_ta") - assert client_ta - for child in self.entitydb.iterate("children", "*.xml"): - c = etree_read(child) - if b64_equal(c.findtext("bpki_child_ta"), client_ta): - sia_base = "rsync://%s/%s/%s/%s/" % (self.rsync_server, self.rsync_module, - self.handle, client.get("handle")) - break - - # If we still haven't figured out what to do with this client, it - # gets a top-level tree of its own, no attempt at nesting. - - if sia_base is None: - print "Don't know where to nest this client, defaulting to top-level" - sia_base = "rsync://%s/%s/%s/" % (self.rsync_server, self.rsync_module, client.get("handle")) - - assert sia_base.startswith("rsync://") - - client_handle = "/".join(sia_base.rstrip("/").split("/")[4:]) - - parent_handle = client.get("parent_handle") - - print "Client calls itself %r, we call it %r" % (client.get("handle"), client_handle) - print "Client says its parent handle is %r" % parent_handle - - self.bpki_servers.fxcert(client.findtext("bpki_client_ta")) - - e = Element("repository", type = "confirmed", - client_handle = client_handle, - parent_handle = parent_handle, - sia_base = sia_base, - service_uri = "https://%s:%s/client/%s" % (self.cfg.get("pubd_server_host"), - self.cfg.get("pubd_server_port"), - client_handle)) - - PEMElement(e, "bpki_server_ta", self.bpki_servers.cer) - SubElement(e, "bpki_client_ta").text = client.findtext("bpki_client_ta") - SubElement(e, "contact_info").text = self.pubd_contact_info - etree_write(e, self.entitydb("pubclients", "%s.xml" % client_handle.replace("/", ".")), - msg = "Send this file back to the publication client you just configured") - - - def do_delete_publication_client(self, arg): - """ - Delete a publication client of this RPKI entity. - - This should check that the XML file it's deleting really is a - client, but doesn't, yet. - """ - - try: - os.unlink(self.entitydb("pubclients", "%s.xml" % arg)) - except OSError: - print "No such client \"%s\"" % arg - - def complete_delete_publication_client(self, *args): - return self.entitydb_complete("pubclients", *args) - - - def do_configure_repository(self, arg): - """ - Configure a publication repository for this RPKI entity, given the - repository's response to our request-for-service message as input. - This command reads the repository's response, extracts and - cross-certifies the BPKI data and service URI, and links the - repository data with the corresponding parent data in our local - database. - """ - - argv = arg.split() - - if len(argv) != 1: - raise RuntimeError, "Need to specify filename for repository.xml on command line" - - r = etree_read(argv[0]) - - parent_handle = r.get("parent_handle") - - print "Repository calls us %r" % (r.get("client_handle")) - print "Repository response associated with parent_handle %r" % parent_handle - - etree_write(r, self.entitydb("repositories", "%s.xml" % parent_handle)) - - - def do_delete_repository(self, arg): - """ - Delete a repository of this RPKI entity. - - This should check that the XML file it's deleting really is a - repository, but doesn't, yet. - """ - - try: - os.unlink(self.entitydb("repositories", "%s.xml" % arg)) - except OSError: - print "No such repository \"%s\"" % arg - - def complete_delete_repository(self, *args): - return self.entitydb_complete("repositories", *args) - - - - - def configure_resources_main(self, msg = None): - """ - Main program of old myrpki.py script. This remains separate - because it's called from more than one place. - """ - - roa_csv_file = self.cfg.get("roa_csv") - prefix_csv_file = self.cfg.get("prefix_csv") - asn_csv_file = self.cfg.get("asn_csv") - - # This probably should become an argument instead of (or in - # addition to a default from?) a config file option. - xml_filename = self.cfg.get("xml_filename") - - try: - e = etree_read(xml_filename) - bsc_req, bsc_cer = self.bpki_resources.bsc(e.findtext("bpki_bsc_pkcs10")) - service_uri = e.get("service_uri") - server_ta = e.findtext("bpki_server_ta") - except IOError: - bsc_req, bsc_cer = None, None - service_uri = None - server_ta = None - - e = Element("myrpki", handle = self.handle) - - if service_uri: - e.set("service_uri", service_uri) - - roa_requests.from_csv(roa_csv_file).xml(e) - - children.from_csv( - prefix_csv_file = prefix_csv_file, - asn_csv_file = asn_csv_file, - fxcert = self.bpki_resources.fxcert, - entitydb = self.entitydb).xml(e) - - parents.from_csv( fxcert = self.bpki_resources.fxcert, entitydb = self.entitydb).xml(e) - repositories.from_csv(fxcert = self.bpki_resources.fxcert, entitydb = self.entitydb).xml(e) - - PEMElement(e, "bpki_ca_certificate", self.bpki_resources.cer) - PEMElement(e, "bpki_crl", self.bpki_resources.crl) - - if bsc_cer: - PEMElement(e, "bpki_bsc_certificate", bsc_cer) - - if bsc_req: - PEMElement(e, "bpki_bsc_pkcs10", bsc_req) - - if server_ta: - SubElement(e, "bpki_server_ta").text = server_ta - - etree_write(e, xml_filename, msg = msg) - - - def do_configure_resources(self, arg): - """ - Read CSV files and all the descriptions of parents and children - that we've built up, package the result up as a single XML file to - be shipped to a hosting rpkid. - """ - - if arg: - raise RuntimeError, "Unexpected argument %r" % arg - self.configure_resources_main(msg = "Send this file to the rpkid operator who is hosting you") - - - - def do_configure_daemons(self, arg): - """ - Configure RPKI daemons with the data built up by the other - commands in this program. - - The basic model here is that each entity with resources to certify - runs the myrpki tool, but not all of them necessarily run their - own RPKI engines. The entities that do run RPKI engines get data - from the entities they host via the XML files output by the - configure_resources command. Those XML files are the input to - this command, which uses them to do all the work of configuring - daemons, populating SQL databases, and so forth. A few operations - (eg, BSC construction) generate data which has to be shipped back - to the resource holder, which we do by updating the same XML file. - - In essence, the XML files are a sneakernet (or email, or carrier - pigeon) communication channel between the resource holders and the - RPKI engine operators. - - As a convenience, for the normal case where the RPKI engine - operator is itself a resource holder, this command in effect runs - the configure_resources command automatically to process the RPKI - engine operator's own resources. - - Note that, due to the back and forth nature of some of these - operations, it may take several cycles for data structures to stablize - and everything to reach a steady state. This is normal. - """ - - argv = arg.split() - - try: - import rpki.https, rpki.resource_set, rpki.relaxng, rpki.exceptions - import rpki.left_right, rpki.x509, rpki.async - if hasattr(warnings, "catch_warnings"): - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - import MySQLdb - else: - import MySQLdb - - except ImportError, e: - print "Sorry, you appear to be missing some of the Python modules needed to run this command" - print "[Error: %r]" % e - - def findbase64(tree, name, b64type = rpki.x509.X509): - x = tree.findtext(name) - return b64type(Base64 = x) if x else None - - # We can use a single BSC for everything -- except BSC key - # rollovers. Drive off that bridge when we get to it. - - bsc_handle = "bsc" - - self.cfg.set_global_flags() - - # Default values for CRL parameters are low, for testing. Not - # quite as low as they once were, too much expired CRL whining. - - self_crl_interval = self.cfg.getint("self_crl_interval", 2 * 60 * 60) - self_regen_margin = self.cfg.getint("self_regen_margin", self_crl_interval / 4) - pubd_base = "https://%s:%s/" % (self.cfg.get("pubd_server_host"), self.cfg.get("pubd_server_port")) - rpkid_base = "https://%s:%s/" % (self.cfg.get("rpkid_server_host"), self.cfg.get("rpkid_server_port")) - - # Wrappers to simplify calling rpkid and pubd. - - call_rpkid = rpki.async.sync_wrapper(rpki.https.caller( - proto = rpki.left_right, - client_key = rpki.x509.RSA( PEM_file = self.bpki_servers.dir + "/irbe.key"), - client_cert = rpki.x509.X509(PEM_file = self.bpki_servers.dir + "/irbe.cer"), - server_ta = rpki.x509.X509(PEM_file = self.bpki_servers.cer), - server_cert = rpki.x509.X509(PEM_file = self.bpki_servers.dir + "/rpkid.cer"), - url = rpkid_base + "left-right", - debug = self.show_xml)) - - if self.run_pubd: - - call_pubd = rpki.async.sync_wrapper(rpki.https.caller( - proto = rpki.publication, - client_key = rpki.x509.RSA( PEM_file = self.bpki_servers.dir + "/irbe.key"), - client_cert = rpki.x509.X509(PEM_file = self.bpki_servers.dir + "/irbe.cer"), - server_ta = rpki.x509.X509(PEM_file = self.bpki_servers.cer), - server_cert = rpki.x509.X509(PEM_file = self.bpki_servers.dir + "/pubd.cer"), - url = pubd_base + "control", - debug = self.show_xml)) - - # Make sure that pubd's BPKI CRL is up to date. - - call_pubd(rpki.publication.config_elt.make_pdu( - action = "set", - bpki_crl = rpki.x509.CRL(PEM_file = self.bpki_servers.crl))) - - irdbd_cfg = rpki.config.parser(self.cfg.get("irdbd_conf", self.cfg_file), "irdbd") - - db = MySQLdb.connect(user = irdbd_cfg.get("sql-username"), - db = irdbd_cfg.get("sql-database"), - passwd = irdbd_cfg.get("sql-password")) - - cur = db.cursor() - - xmlfiles = [] - - # If [myrpki] section includes an "xml_filename" setting, run - # myrpki.py internally, as a convenience, and include its output at - # the head of our list of XML files to process. - - my_xmlfile = self.cfg.get("xml_filename", "") - if my_xmlfile: - self.configure_resources_main() - xmlfiles.append(my_xmlfile) - else: - my_xmlfile = None - - # Add any other XML files specified on the command line - - xmlfiles.extend(argv) - - for xmlfile in xmlfiles: - - # Parse XML file and validate it against our scheme - - tree = etree_read(xmlfile, validate = True) - - handle = tree.get("handle") - - # Update IRDB with parsed resource and roa-request data. - - cur.execute( - """ - DELETE - FROM roa_request_prefix - USING roa_request, roa_request_prefix - WHERE roa_request.roa_request_id = roa_request_prefix.roa_request_id AND roa_request.roa_request_handle = %s - """, (handle,)) - - cur.execute("DELETE FROM roa_request WHERE roa_request.roa_request_handle = %s", (handle,)) - - for x in tree.getiterator("roa_request"): - cur.execute("INSERT roa_request (roa_request_handle, asn) VALUES (%s, %s)", (handle, x.get("asn"))) - roa_request_id = cur.lastrowid - for version, prefix_set in ((4, rpki.resource_set.roa_prefix_set_ipv4(x.get("v4"))), (6, rpki.resource_set.roa_prefix_set_ipv6(x.get("v6")))): - if prefix_set: - cur.executemany("INSERT roa_request_prefix (roa_request_id, prefix, prefixlen, max_prefixlen, version) VALUES (%s, %s, %s, %s, %s)", - ((roa_request_id, p.prefix, p.prefixlen, p.max_prefixlen, version) for p in prefix_set)) - - cur.execute( - """ - DELETE - FROM registrant_asn - USING registrant, registrant_asn - WHERE registrant.registrant_id = registrant_asn.registrant_id AND registrant.registry_handle = %s - """ , (handle,)) - - cur.execute( - """ - DELETE FROM registrant_net USING registrant, registrant_net - WHERE registrant.registrant_id = registrant_net.registrant_id AND registrant.registry_handle = %s - """ , (handle,)) - - cur.execute("DELETE FROM registrant WHERE registrant.registry_handle = %s" , (handle,)) - - for x in tree.getiterator("child"): - child_handle = x.get("handle") - asns = rpki.resource_set.resource_set_as(x.get("asns")) - ipv4 = rpki.resource_set.resource_set_ipv4(x.get("v4")) - ipv6 = rpki.resource_set.resource_set_ipv6(x.get("v6")) - - cur.execute("INSERT registrant (registrant_handle, registry_handle, registrant_name, valid_until) VALUES (%s, %s, %s, %s)", - (child_handle, handle, child_handle, rpki.sundial.datetime.fromXMLtime(x.get("valid_until")).to_sql())) - child_id = cur.lastrowid - if asns: - cur.executemany("INSERT registrant_asn (start_as, end_as, registrant_id) VALUES (%s, %s, %s)", - ((a.min, a.max, child_id) for a in asns)) - if ipv4: - cur.executemany("INSERT registrant_net (start_ip, end_ip, version, registrant_id) VALUES (%s, %s, 4, %s)", - ((a.min, a.max, child_id) for a in ipv4)) - if ipv6: - cur.executemany("INSERT registrant_net (start_ip, end_ip, version, registrant_id) VALUES (%s, %s, 6, %s)", - ((a.min, a.max, child_id) for a in ipv6)) - - db.commit() - - # Check for certificates before attempting anything else - - hosted_cacert = findbase64(tree, "bpki_ca_certificate") - if not hosted_cacert: - print "Nothing else I can do without a trust anchor for the entity I'm hosting." - continue - - rpkid_xcert = rpki.x509.X509(PEM_file = self.bpki_servers.fxcert(b64 = hosted_cacert.get_Base64(), - #filename = handle + ".cacert.cer", - path_restriction = 1)) - - # See what rpkid and pubd already have on file for this entity. - - if self.run_pubd: - client_pdus = dict((x.client_handle, x) - for x in call_pubd(rpki.publication.client_elt.make_pdu(action = "list")) - if isinstance(x, rpki.publication.client_elt)) - - rpkid_reply = call_rpkid( - rpki.left_right.self_elt.make_pdu( action = "get", tag = "self", self_handle = handle), - rpki.left_right.bsc_elt.make_pdu( action = "list", tag = "bsc", self_handle = handle), - rpki.left_right.repository_elt.make_pdu(action = "list", tag = "repository", self_handle = handle), - rpki.left_right.parent_elt.make_pdu( action = "list", tag = "parent", self_handle = handle), - rpki.left_right.child_elt.make_pdu( action = "list", tag = "child", self_handle = handle)) - - self_pdu = rpkid_reply[0] - bsc_pdus = dict((x.bsc_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.bsc_elt)) - repository_pdus = dict((x.repository_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.repository_elt)) - parent_pdus = dict((x.parent_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.parent_elt)) - child_pdus = dict((x.child_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.child_elt)) - - pubd_query = [] - rpkid_query = [] - - # There should be exactly one <self/> object per hosted entity, by definition - - if (isinstance(self_pdu, rpki.left_right.report_error_elt) or - self_pdu.crl_interval != self_crl_interval or - self_pdu.regen_margin != self_regen_margin or - self_pdu.bpki_cert != rpkid_xcert): - rpkid_query.append(rpki.left_right.self_elt.make_pdu( - action = "create" if isinstance(self_pdu, rpki.left_right.report_error_elt) else "set", - tag = "self", - self_handle = handle, - bpki_cert = rpkid_xcert, - crl_interval = self_crl_interval, - regen_margin = self_regen_margin)) - - # In general we only need one <bsc/> per <self/>. BSC objects are a - # little unusual in that the PKCS #10 subelement is generated by rpkid - # in response to generate_keypair, so there's more of a separation - # between create and set than with other objects. - - bsc_cert = findbase64(tree, "bpki_bsc_certificate") - bsc_crl = findbase64(tree, "bpki_crl", rpki.x509.CRL) - - bsc_pdu = bsc_pdus.pop(bsc_handle, None) - - if bsc_pdu is None: - rpkid_query.append(rpki.left_right.bsc_elt.make_pdu( - action = "create", - tag = "bsc", - self_handle = handle, - bsc_handle = bsc_handle, - generate_keypair = "yes")) - elif bsc_pdu.signing_cert != bsc_cert or bsc_pdu.signing_cert_crl != bsc_crl: - rpkid_query.append(rpki.left_right.bsc_elt.make_pdu( - action = "set", - tag = "bsc", - self_handle = handle, - bsc_handle = bsc_handle, - signing_cert = bsc_cert, - signing_cert_crl = bsc_crl)) - - rpkid_query.extend(rpki.left_right.bsc_elt.make_pdu( - action = "destroy", self_handle = handle, bsc_handle = b) for b in bsc_pdus) - - bsc_req = None - - if bsc_pdu and bsc_pdu.pkcs10_request: - bsc_req = bsc_pdu.pkcs10_request - - # At present we need one <repository/> per <parent/>, not because - # rpkid requires that, but because pubd does. pubd probably should - # be fixed to support a single client allowed to update multiple - # trees, but for the moment the easiest way forward is just to - # enforce a 1:1 mapping between <parent/> and <repository/> objects - - for repository in tree.getiterator("repository"): - - repository_handle = repository.get("handle") - repository_pdu = repository_pdus.pop(repository_handle, None) - repository_uri = repository.get("service_uri") - repository_cert = findbase64(repository, "bpki_certificate") - - if (repository_pdu is None or - repository_pdu.bsc_handle != bsc_handle or - repository_pdu.peer_contact_uri != repository_uri or - repository_pdu.bpki_cert != repository_cert): - rpkid_query.append(rpki.left_right.repository_elt.make_pdu( - action = "create" if repository_pdu is None else "set", - tag = repository_handle, - self_handle = handle, - repository_handle = repository_handle, - bsc_handle = bsc_handle, - peer_contact_uri = repository_uri, - bpki_cert = repository_cert)) - - rpkid_query.extend(rpki.left_right.repository_elt.make_pdu( - action = "destroy", self_handle = handle, repository_handle = r) for r in repository_pdus) - - # <parent/> setup code currently assumes 1:1 mapping between - # <repository/> and <parent/>, and further assumes that the handles - # for an associated pair are the identical (that is: - # parent.repository_handle == parent.parent_handle). - - for parent in tree.getiterator("parent"): - - parent_handle = parent.get("handle") - parent_pdu = parent_pdus.pop(parent_handle, None) - parent_uri = parent.get("service_uri") - parent_myhandle = parent.get("myhandle") - parent_sia_base = parent.get("sia_base") - parent_cms_cert = findbase64(parent, "bpki_cms_certificate") - parent_https_cert = findbase64(parent, "bpki_https_certificate") - - if (parent_pdu is None or - parent_pdu.bsc_handle != bsc_handle or - parent_pdu.repository_handle != parent_handle or - parent_pdu.peer_contact_uri != parent_uri or - parent_pdu.sia_base != parent_sia_base or - parent_pdu.sender_name != parent_myhandle or - parent_pdu.recipient_name != parent_handle or - parent_pdu.bpki_cms_cert != parent_cms_cert or - parent_pdu.bpki_https_cert != parent_https_cert): - rpkid_query.append(rpki.left_right.parent_elt.make_pdu( - action = "create" if parent_pdu is None else "set", - tag = parent_handle, - self_handle = handle, - parent_handle = parent_handle, - bsc_handle = bsc_handle, - repository_handle = parent_handle, - peer_contact_uri = parent_uri, - sia_base = parent_sia_base, - sender_name = parent_myhandle, - recipient_name = parent_handle, - bpki_cms_cert = parent_cms_cert, - bpki_https_cert = parent_https_cert)) - - rpkid_query.extend(rpki.left_right.parent_elt.make_pdu( - action = "destroy", self_handle = handle, parent_handle = p) for p in parent_pdus) - - # Children are simpler than parents, because they call us, so no URL - # to construct and figuring out what certificate to use is their - # problem, not ours. - - for child in tree.getiterator("child"): - - child_handle = child.get("handle") - child_pdu = child_pdus.pop(child_handle, None) - child_cert = findbase64(child, "bpki_certificate") - - if (child_pdu is None or - child_pdu.bsc_handle != bsc_handle or - child_pdu.bpki_cert != child_cert): - rpkid_query.append(rpki.left_right.child_elt.make_pdu( - action = "create" if child_pdu is None else "set", - tag = child_handle, - self_handle = handle, - child_handle = child_handle, - bsc_handle = bsc_handle, - bpki_cert = child_cert)) - - rpkid_query.extend(rpki.left_right.child_elt.make_pdu( - action = "destroy", self_handle = handle, child_handle = c) for c in child_pdus) - - # Publication setup. - - if self.run_pubd: - - for f in self.entitydb.iterate("pubclients", "*.xml"): - c = etree_read(f) - - client_handle = c.get("client_handle") - client_base_uri = c.get("sia_base") - client_bpki_cert = rpki.x509.X509(PEM_file = self.bpki_servers.fxcert(c.findtext("bpki_client_ta"))) - client_pdu = client_pdus.pop(client_handle, None) - - if (client_pdu is None or - client_pdu.base_uri != client_base_uri or - client_pdu.bpki_cert != client_bpki_cert): - pubd_query.append(rpki.publication.client_elt.make_pdu( - action = "create" if client_pdu is None else "set", - client_handle = client_handle, - bpki_cert = client_bpki_cert, - base_uri = client_base_uri)) - - pubd_query.extend(rpki.publication.client_elt.make_pdu( - action = "destroy", client_handle = p) for p in client_pdus) - - # If we changed anything, ship updates off to daemons - - failed = False - - if rpkid_query: - rpkid_reply = call_rpkid(*rpkid_query) - bsc_pdus = dict((x.bsc_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.bsc_elt)) - if bsc_handle in bsc_pdus and bsc_pdus[bsc_handle].pkcs10_request: - bsc_req = bsc_pdus[bsc_handle].pkcs10_request - for r in rpkid_reply: - if isinstance(r, rpki.left_right.report_error_elt): - failed = True - print "rpkid reported failure:", r.error_code - if r.error_text: - print r.error_text - - if failed: - raise RuntimeError - - if pubd_query: - assert self.run_pubd - pubd_reply = call_pubd(*pubd_query) - for r in pubd_reply: - if isinstance(r, rpki.publication.report_error_elt): - failed = True - print "pubd reported failure:", r.error_code - if r.error_text: - print r.error_text - - if failed: - raise RuntimeError - - # Rewrite XML. - - e = tree.find("bpki_bsc_pkcs10") - if e is not None: - tree.remove(e) - if bsc_req is not None: - SubElement(tree, "bpki_bsc_pkcs10").text = bsc_req.get_Base64() - - tree.set("service_uri", rpkid_base + "up-down/" + handle) - - e = tree.find("bpki_server_ta") - if e is not None: - tree.remove(e) - PEMElement(tree, "bpki_server_ta", self.bpki_resources.cer) - - etree_write(tree, xmlfile, validate = True, - msg = None if xmlfile is my_xmlfile else 'Send this file back to the hosted entity ("%s")' % handle) - - db.close() - - # Run event loop again to give TLS connections a chance to shut down cleanly. - # Might need to add a timeout here, dunno yet. - - rpki.async.event_loop() diff --git a/rpkid.with_tls/rpki/oids.py b/rpkid.with_tls/rpki/oids.py deleted file mode 100644 index 90e486b7..00000000 --- a/rpkid.with_tls/rpki/oids.py +++ /dev/null @@ -1,58 +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.with_tls/rpki/publication.py b/rpkid.with_tls/rpki/publication.py deleted file mode 100644 index 1af4be10..00000000 --- a/rpkid.with_tls/rpki/publication.py +++ /dev/null @@ -1,422 +0,0 @@ -""" -RPKI "publication" protocol. - -$Id$ - -Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -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 base64, os, errno -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_persistent, publication_namespace): - """ - Virtual class for control channel objects. - """ - - def serve_dispatch(self, r_msg, cb, eb): - """ - Action dispatch handler. This needs special handling because we - need to make sure that this PDU arrived via the control channel. - """ - if self.client is not None: - raise rpki.exceptions.BadQuery, "Control query received on client channel" - rpki.xml_utils.data_elt.serve_dispatch(self, r_msg, cb, eb) - -class config_elt(control_elt): - """ - <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, cb, eb): - """ - Handle a set action. This requires special handling because - config doesn't support the create method. - """ - if self.sql_fetch(self.gctx, self.config_id) is None: - control_elt.serve_create(self, r_msg, cb, eb) - else: - control_elt.serve_set(self, r_msg, cb, eb) - - def serve_fetch_one_maybe(self): - """ - Find the config object on which a get or set method should - operate. - """ - return self.sql_fetch(self.gctx, self.config_id) - -class client_elt(control_elt): - """ - <client/> element. - """ - - element_name = "client" - attributes = ("action", "tag", "client_handle", "base_uri") - elements = ("bpki_cert", "bpki_glue") - - sql_template = rpki.sql.template("client", "client_id", "client_handle", "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, cb, eb): - """ - Extra server actions for client_elt. - """ - if self.clear_https_ta_cache: - self.gctx.clear_https_ta_cache() - self.clear_https_ta_cache = False - cb() - - def serve_fetch_one_maybe(self): - """ - Find the client object on which a get, set, or destroy method - should operate, or which would conflict with a create method. - """ - return self.sql_fetch_where1(self.gctx, "client_handle = %s", self.client_handle) - - def serve_fetch_all(self): - """Find client objects on which a list method should operate.""" - return self.sql_fetch_all(self.gctx) - - def check_allowed_uri(self, uri): - 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_handle", "uri") - payload_type = None - 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 = self.payload.get_Base64() - return elt - - def serve_dispatch(self, r_msg, cb, eb): - """ - Action dispatch handler. - """ - try: - if self.client is None: - raise rpki.exceptions.BadQuery, "Client query received on control channel" - dispatch = { "publish" : self.serve_publish, - "withdraw" : self.serve_withdraw } - if self.action not in dispatch: - raise rpki.exceptions.BadQuery, "Unexpected query: action %s" % self.action - self.client.check_allowed_uri(self.uri) - dispatch[self.action]() - r_pdu = self.__class__() - r_pdu.action = self.action - r_pdu.tag = self.tag - r_pdu.uri = self.uri - r_msg.append(r_pdu) - cb() - except rpki.exceptions.NoObjectAtURI, e: - # This can happen when we're cleaning up from a prior mess, so - # we generate a <report_error/> PDU then carry on. - r_msg.append(report_error_elt.from_exception(e, self.tag)) - cb() - - def serve_publish(self): - """ - Publish an object. - """ - rpki.log.info("Publishing %r as %r" % (self.payload, self.uri)) - filename = self.uri_to_filename() - filename_tmp = filename + ".tmp" - dirname = os.path.dirname(filename) - if not os.path.isdir(dirname): - os.makedirs(dirname) - f = open(filename_tmp, "wb") - f.write(self.payload.get_DER()) - f.close() - os.rename(filename_tmp, filename) - - def serve_withdraw(self): - """ - Withdraw an object. - """ - rpki.log.info("Withdrawing %r" % (self.uri,)) - filename = self.uri_to_filename() - try: - os.remove(filename) - except OSError, e: - if e.errno == errno.ENOENT: - raise rpki.exceptions.NoObjectAtURI, "No object published at %r" % self.uri - else: - raise - - def uri_to_filename(self): - """ - Convert a URI to a local filename. - """ - if not self.uri.startswith("rsync://"): - raise rpki.exceptions.BadURISyntax, self.uri - path = self.uri.split("/")[3:] - if not self.gctx.publication_multimodule: - del path[0] - path.insert(0, self.gctx.publication_base.rstrip("/")) - filename = "/".join(path) - if "/../" in filename or filename.endswith("/.."): - raise rpki.exceptions.BadURISyntax, filename - return filename - - @classmethod - def make_publish(cls, uri, obj, tag = None): - """ - Construct a publication PDU. - """ - assert cls.payload_type is not None and type(obj) is cls.payload_type - return cls.make_pdu(action = "publish", uri = uri, payload = obj, tag = tag) - - @classmethod - def make_withdraw(cls, uri, obj, tag = None): - """ - Construct a withdrawal PDU. - """ - assert cls.payload_type is not None and type(obj) is cls.payload_type - return cls.make_pdu(action = "withdraw", uri = uri, tag = tag) - - def raise_if_error(self): - """ - No-op, since this is not a <report_error/> PDU. - """ - pass - -class certificate_elt(publication_object_elt): - """ - <certificate/> element. - """ - - element_name = "certificate" - payload_type = rpki.x509.X509 - -class crl_elt(publication_object_elt): - """ - <crl/> element. - """ - - element_name = "crl" - payload_type = rpki.x509.CRL - -class manifest_elt(publication_object_elt): - """ - <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 - -publication_object_elt.obj2elt = dict((e.payload_type, e) for e in (certificate_elt, crl_elt, manifest_elt, roa_elt)) - -class report_error_elt(rpki.xml_utils.text_elt, publication_namespace): - """ - <report_error/> element. - """ - - element_name = "report_error" - attributes = ("tag", "error_code") - text_attribute = "error_text" - - error_text = None - - @classmethod - def from_exception(cls, e, tag = None): - """ - Generate a <report_error/> element from an exception. - """ - self = cls() - self.tag = tag - self.error_code = e.__class__.__name__ - self.error_text = str(e) - return self - - def __str__(self): - s = "" - if getattr(self, "tag", None) is not None: - s += "[%s] " % self.tag - s += self.error_code - if getattr(self, "error_text", None) is not None: - s += ": " + self.error_text - return s - - def raise_if_error(self): - """ - Raise exception associated with this <report_error/> PDU. - """ - t = rpki.exceptions.__dict__.get(self.error_code) - if isinstance(t, type) and issubclass(t, rpki.exceptions.RPKI_Exception): - raise t, getattr(self, "text", None) - else: - raise rpki.exceptions.BadPublicationReply, "Unexpected response from pubd: %s" % self - -class msg(rpki.xml_utils.msg, publication_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, cb): - """ - Serve one msg PDU. - """ - if not self.is_query(): - raise rpki.exceptions.BadQuery, "Message type is not query" - r_msg = self.__class__.reply() - - def loop(iterator, q_pdu): - - def fail(e): - if not isinstance(e, rpki.exceptions.NotFound): - rpki.log.traceback() - r_msg.append(report_error_elt.from_exception(e, q_pdu.tag)) - cb(r_msg) - - try: - q_pdu.gctx = gctx - q_pdu.client = client - q_pdu.serve_dispatch(r_msg, iterator, fail) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - fail(e) - - def done(): - cb(r_msg) - - rpki.async.iterator(self, loop, done) - -class sax_handler(rpki.xml_utils.sax_handler): - """ - 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.with_tls/rpki/relaxng.py b/rpkid.with_tls/rpki/relaxng.py deleted file mode 100644 index b16a5a7d..00000000 --- a/rpkid.with_tls/rpki/relaxng.py +++ /dev/null @@ -1,1783 +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 3302 2010-06-29 01:51:45Z 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. - - Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - - Permission to use, copy, modify, and distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THIS SOFTWARE. - - 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. ---> -<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="list_roa_requests_query"/> - </define> - <define name="query_elt" combine="choice"> - <ref name="list_resources_query"/> - </define> - <define name="query_elt" combine="choice"> - <ref name="list_published_objects_query"/> - </define> - <define name="query_elt" combine="choice"> - <ref name="list_received_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="list_resources_reply"/> - </define> - <define name="reply_elt" combine="choice"> - <ref name="list_roa_requests_reply"/> - </define> - <define name="reply_elt" combine="choice"> - <ref name="list_published_objects_reply"/> - </define> - <define name="reply_elt" combine="choice"> - <ref name="list_received_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 - sql_id = xsd:nonNegativeInteger - --> - <!-- - ...except that fields containing SQL primary indicies don't belong - in this protocol, so they're turninging into handles. - Length restriction is a MySQL implementation issue. - Handles are case-insensitive (because SQL is, among other reasons). - --> - <define name="object_handle"> - <data type="string"> - <param name="maxLength">255</param> - <param name="pattern">[\-_A-Za-z0-9]*</param> - </data> - </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> - <optional> - <attribute name="revoke_forgotten"> - <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_handle"> - <attribute name="self_handle"> - <ref name="object_handle"/> - </attribute> - </define> - <define name="self_query" combine="choice"> - <element name="self"> - <ref name="ctl_create"/> - <ref name="self_handle"/> - <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_handle"/> - </element> - </define> - <define name="self_query" combine="choice"> - <element name="self"> - <ref name="ctl_set"/> - <ref name="self_handle"/> - <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_handle"/> - </element> - </define> - <define name="self_query" combine="choice"> - <element name="self"> - <ref name="ctl_get"/> - <ref name="self_handle"/> - </element> - </define> - <define name="self_reply" combine="choice"> - <element name="self"> - <ref name="ctl_get"/> - <ref name="self_handle"/> - <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_handle"/> - <ref name="self_payload"/> - </element> - </define> - <define name="self_query" combine="choice"> - <element name="self"> - <ref name="ctl_destroy"/> - <ref name="self_handle"/> - </element> - </define> - <define name="self_reply" combine="choice"> - <element name="self"> - <ref name="ctl_destroy"/> - <ref name="self_handle"/> - </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_handle"> - <attribute name="bsc_handle"> - <ref name="object_handle"/> - </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_handle"/> - <ref name="bsc_handle"/> - <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_handle"/> - <ref name="bsc_handle"/> - <ref name="bsc_pkcs10"/> - </element> - </define> - <define name="bsc_query" combine="choice"> - <element name="bsc"> - <ref name="ctl_set"/> - <ref name="self_handle"/> - <ref name="bsc_handle"/> - <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_handle"/> - <ref name="bsc_handle"/> - <ref name="bsc_pkcs10"/> - </element> - </define> - <define name="bsc_query" combine="choice"> - <element name="bsc"> - <ref name="ctl_get"/> - <ref name="self_handle"/> - <ref name="bsc_handle"/> - </element> - </define> - <define name="bsc_reply" combine="choice"> - <element name="bsc"> - <ref name="ctl_get"/> - <ref name="self_handle"/> - <ref name="bsc_handle"/> - <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_handle"/> - </element> - </define> - <define name="bsc_reply" combine="choice"> - <element name="bsc"> - <ref name="ctl_list"/> - <ref name="self_handle"/> - <ref name="bsc_handle"/> - <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_handle"/> - <ref name="bsc_handle"/> - </element> - </define> - <define name="bsc_reply" combine="choice"> - <element name="bsc"> - <ref name="ctl_destroy"/> - <ref name="self_handle"/> - <ref name="bsc_handle"/> - </element> - </define> - <!-- <parent/> element --> - <define name="parent_handle"> - <attribute name="parent_handle"> - <ref name="object_handle"/> - </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> - <optional> - <attribute name="revoke_forgotten"> - <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_handle"/> - </optional> - <optional> - <ref name="repository_handle"/> - </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_handle"/> - <ref name="parent_handle"/> - <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_handle"/> - <ref name="parent_handle"/> - </element> - </define> - <define name="parent_query" combine="choice"> - <element name="parent"> - <ref name="ctl_set"/> - <ref name="self_handle"/> - <ref name="parent_handle"/> - <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_handle"/> - <ref name="parent_handle"/> - </element> - </define> - <define name="parent_query" combine="choice"> - <element name="parent"> - <ref name="ctl_get"/> - <ref name="self_handle"/> - <ref name="parent_handle"/> - </element> - </define> - <define name="parent_reply" combine="choice"> - <element name="parent"> - <ref name="ctl_get"/> - <ref name="self_handle"/> - <ref name="parent_handle"/> - <ref name="parent_payload"/> - </element> - </define> - <define name="parent_query" combine="choice"> - <element name="parent"> - <ref name="ctl_list"/> - <ref name="self_handle"/> - </element> - </define> - <define name="parent_reply" combine="choice"> - <element name="parent"> - <ref name="ctl_list"/> - <ref name="self_handle"/> - <ref name="parent_handle"/> - <ref name="parent_payload"/> - </element> - </define> - <define name="parent_query" combine="choice"> - <element name="parent"> - <ref name="ctl_destroy"/> - <ref name="self_handle"/> - <ref name="parent_handle"/> - </element> - </define> - <define name="parent_reply" combine="choice"> - <element name="parent"> - <ref name="ctl_destroy"/> - <ref name="self_handle"/> - <ref name="parent_handle"/> - </element> - </define> - <!-- <child/> element --> - <define name="child_handle"> - <attribute name="child_handle"> - <ref name="object_handle"/> - </attribute> - </define> - <define name="child_bool"> - <optional> - <attribute name="reissue"> - <value>yes</value> - </attribute> - </optional> - </define> - <define name="child_payload"> - <optional> - <ref name="bsc_handle"/> - </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_handle"/> - <ref name="child_handle"/> - <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_handle"/> - <ref name="child_handle"/> - </element> - </define> - <define name="child_query" combine="choice"> - <element name="child"> - <ref name="ctl_set"/> - <ref name="self_handle"/> - <ref name="child_handle"/> - <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_handle"/> - <ref name="child_handle"/> - </element> - </define> - <define name="child_query" combine="choice"> - <element name="child"> - <ref name="ctl_get"/> - <ref name="self_handle"/> - <ref name="child_handle"/> - </element> - </define> - <define name="child_reply" combine="choice"> - <element name="child"> - <ref name="ctl_get"/> - <ref name="self_handle"/> - <ref name="child_handle"/> - <ref name="child_payload"/> - </element> - </define> - <define name="child_query" combine="choice"> - <element name="child"> - <ref name="ctl_list"/> - <ref name="self_handle"/> - </element> - </define> - <define name="child_reply" combine="choice"> - <element name="child"> - <ref name="ctl_list"/> - <ref name="self_handle"/> - <ref name="child_handle"/> - <ref name="child_payload"/> - </element> - </define> - <define name="child_query" combine="choice"> - <element name="child"> - <ref name="ctl_destroy"/> - <ref name="self_handle"/> - <ref name="child_handle"/> - </element> - </define> - <define name="child_reply" combine="choice"> - <element name="child"> - <ref name="ctl_destroy"/> - <ref name="self_handle"/> - <ref name="child_handle"/> - </element> - </define> - <!-- <repository/> element --> - <define name="repository_handle"> - <attribute name="repository_handle"> - <ref name="object_handle"/> - </attribute> - </define> - <define name="repository_payload"> - <optional> - <attribute name="peer_contact_uri"> - <ref name="uri"/> - </attribute> - </optional> - <optional> - <ref name="bsc_handle"/> - </optional> - <optional> - <element name="bpki_cert"> - <ref name="base64"/> - </element> - </optional> - <optional> - <element name="bpki_glue"> - <ref name="base64"/> - </element> - </optional> - </define> - <define name="repository_query" combine="choice"> - <element name="repository"> - <ref name="ctl_create"/> - <ref name="self_handle"/> - <ref name="repository_handle"/> - <ref name="repository_payload"/> - </element> - </define> - <define name="repository_reply" combine="choice"> - <element name="repository"> - <ref name="ctl_create"/> - <ref name="self_handle"/> - <ref name="repository_handle"/> - </element> - </define> - <define name="repository_query" combine="choice"> - <element name="repository"> - <ref name="ctl_set"/> - <ref name="self_handle"/> - <ref name="repository_handle"/> - <ref name="repository_payload"/> - </element> - </define> - <define name="repository_reply" combine="choice"> - <element name="repository"> - <ref name="ctl_set"/> - <ref name="self_handle"/> - <ref name="repository_handle"/> - </element> - </define> - <define name="repository_query" combine="choice"> - <element name="repository"> - <ref name="ctl_get"/> - <ref name="self_handle"/> - <ref name="repository_handle"/> - </element> - </define> - <define name="repository_reply" combine="choice"> - <element name="repository"> - <ref name="ctl_get"/> - <ref name="self_handle"/> - <ref name="repository_handle"/> - <ref name="repository_payload"/> - </element> - </define> - <define name="repository_query" combine="choice"> - <element name="repository"> - <ref name="ctl_list"/> - <ref name="self_handle"/> - </element> - </define> - <define name="repository_reply" combine="choice"> - <element name="repository"> - <ref name="ctl_list"/> - <ref name="self_handle"/> - <ref name="repository_handle"/> - <ref name="repository_payload"/> - </element> - </define> - <define name="repository_query" combine="choice"> - <element name="repository"> - <ref name="ctl_destroy"/> - <ref name="self_handle"/> - <ref name="repository_handle"/> - </element> - </define> - <define name="repository_reply" combine="choice"> - <element name="repository"> - <ref name="ctl_destroy"/> - <ref name="self_handle"/> - <ref name="repository_handle"/> - </element> - </define> - <!-- <list_resources/> element --> - <define name="list_resources_query"> - <element name="list_resources"> - <ref name="tag"/> - <ref name="self_handle"/> - <ref name="child_handle"/> - </element> - </define> - <define name="list_resources_reply"> - <element name="list_resources"> - <ref name="tag"/> - <ref name="self_handle"/> - <ref name="child_handle"/> - <attribute name="valid_until"> - <data type="dateTime"> - <param name="pattern">.*Z</param> - </data> - </attribute> - <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> - <!-- <list_roa_requests/> element --> - <define name="list_roa_requests_query"> - <element name="list_roa_requests"> - <ref name="tag"/> - <ref name="self_handle"/> - </element> - </define> - <define name="list_roa_requests_reply"> - <element name="list_roa_requests"> - <ref name="tag"/> - <ref name="self_handle"/> - <attribute name="asn"> - <data type="positiveInteger"/> - </attribute> - <optional> - <attribute name="ipv4"> - <ref name="ipv4_list"/> - </attribute> - </optional> - <optional> - <attribute name="ipv6"> - <ref name="ipv6_list"/> - </attribute> - </optional> - </element> - </define> - <!-- <list_published_objects/> element --> - <define name="list_published_objects_query"> - <element name="list_published_objects"> - <ref name="tag"/> - <ref name="self_handle"/> - </element> - </define> - <define name="list_published_objects_reply"> - <element name="list_published_objects"> - <ref name="tag"/> - <ref name="self_handle"/> - <attribute name="uri"> - <ref name="uri"/> - </attribute> - <ref name="base64"/> - </element> - </define> - <!-- <list_received_resources/> element --> - <define name="list_received_resources_query"> - <element name="list_received_resources"> - <ref name="tag"/> - <ref name="self_handle"/> - </element> - </define> - <define name="list_received_resources_reply"> - <element name="list_received_resources"> - <ref name="tag"/> - <ref name="self_handle"/> - <ref name="parent_handle"/> - <attribute name="notBefore"> - <data type="dateTime"> - <param name="pattern">.*Z</param> - </data> - </attribute> - <attribute name="notAfter"> - <data type="dateTime"> - <param name="pattern">.*Z</param> - </data> - </attribute> - <attribute name="uri"> - <ref name="uri"/> - </attribute> - <attribute name="sia_uri"> - <ref name="uri"/> - </attribute> - <attribute name="aia_uri"> - <ref name="uri"/> - </attribute> - <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"/> - <optional> - <ref name="self_handle"/> - </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: ---> -''')) - -## @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 2839 2009-10-27 18:53:00Z 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 3434 2010-08-31 00:31:55Z 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. - - Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - - Permission to use, copy, modify, and distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THIS SOFTWARE. - - 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. ---> -<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 - base64 = xsd:base64Binary { maxLength="512000" } - - Sadly, it turns out that CRLs can in fact get longer than this for an active CA. - Remove length limit for now, think about whether to put it back later. - --> - <define name="base64"> - <data type="base64Binary"/> - </define> - <!-- Publication URLs --> - <define name="uri_t"> - <data type="anyURI"> - <param name="maxLength">4096</param> - </data> - </define> - <define name="uri"> - <attribute name="uri"> - <ref name="uri_t"/> - </attribute> - </define> - <!-- - Handles on remote objects (replaces passing raw SQL IDs). NB: - Unlike the up-down protocol, handles in this protocol allow "/" as a - hierarchy delimiter. - --> - <define name="object_handle"> - <data type="string"> - <param name="maxLength">255</param> - <param name="pattern">[\-_A-Za-z0-9/]*</param> - </data> - </define> - <!-- - <config/> element (use restricted to repository operator) - config_handle attribute, create, list, and destroy commands omitted deliberately, see code for details - --> - <define name="config_payload"> - <optional> - <element name="bpki_crl"> - <ref name="base64"/> - </element> - </optional> - </define> - <define name="config_query" combine="choice"> - <element name="config"> - <attribute name="action"> - <value>set</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="config_payload"/> - </element> - </define> - <define name="config_reply" combine="choice"> - <element name="config"> - <attribute name="action"> - <value>set</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - </element> - </define> - <define name="config_query" combine="choice"> - <element name="config"> - <attribute name="action"> - <value>get</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - </element> - </define> - <define name="config_reply" combine="choice"> - <element name="config"> - <attribute name="action"> - <value>get</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="config_payload"/> - </element> - </define> - <!-- <client/> element (use restricted to repository operator) --> - <define name="client_handle"> - <attribute name="client_handle"> - <ref name="object_handle"/> - </attribute> - </define> - <define name="client_payload"> - <optional> - <attribute name="base_uri"> - <ref name="uri_t"/> - </attribute> - </optional> - <optional> - <element name="bpki_cert"> - <ref name="base64"/> - </element> - </optional> - <optional> - <element name="bpki_glue"> - <ref name="base64"/> - </element> - </optional> - </define> - <define name="client_query" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>create</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - <ref name="client_payload"/> - </element> - </define> - <define name="client_reply" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>create</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - </element> - </define> - <define name="client_query" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>set</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - <ref name="client_payload"/> - </element> - </define> - <define name="client_reply" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>set</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - </element> - </define> - <define name="client_query" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>get</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - </element> - </define> - <define name="client_reply" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>get</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - <ref name="client_payload"/> - </element> - </define> - <define name="client_query" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>list</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - </element> - </define> - <define name="client_reply" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>list</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - <ref name="client_payload"/> - </element> - </define> - <define name="client_query" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>destroy</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - </element> - </define> - <define name="client_reply" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>destroy</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - </element> - </define> - <!-- <certificate/> element --> - <define name="certificate_query" combine="choice"> - <element name="certificate"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> - </define> - <define name="certificate_reply" combine="choice"> - <element name="certificate"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="certificate_query" combine="choice"> - <element name="certificate"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="certificate_reply" combine="choice"> - <element name="certificate"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <!-- <crl/> element --> - <define name="crl_query" combine="choice"> - <element name="crl"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> - </define> - <define name="crl_reply" combine="choice"> - <element name="crl"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="crl_query" combine="choice"> - <element name="crl"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="crl_reply" combine="choice"> - <element name="crl"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <!-- <manifest/> element --> - <define name="manifest_query" combine="choice"> - <element name="manifest"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> - </define> - <define name="manifest_reply" combine="choice"> - <element name="manifest"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="manifest_query" combine="choice"> - <element name="manifest"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="manifest_reply" combine="choice"> - <element name="manifest"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <!-- <roa/> element --> - <define name="roa_query" combine="choice"> - <element name="roa"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> - </define> - <define name="roa_reply" combine="choice"> - <element name="roa"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="roa_query" combine="choice"> - <element name="roa"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="roa_reply" combine="choice"> - <element name="roa"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <!-- <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.with_tls/rpki/resource_set.py b/rpkid.with_tls/rpki/resource_set.py deleted file mode 100644 index 9addab49..00000000 --- a/rpkid.with_tls/rpki/resource_set.py +++ /dev/null @@ -1,1107 +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) 2009-2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -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 re, math -import rpki.ipaddrs, rpki.oids, rpki.exceptions - -## @var inherit_token -# Token used to indicate inheritance in read and print syntax. - -inherit_token = "<inherit>" - -re_asn_range = re.compile("^([0-9]+)-([0-9]+)$") -re_address_range = re.compile("^([0-9:.a-fA-F]+)-([0-9:.a-fA-F]+)$") -re_prefix_with_maxlen = re.compile("^([0-9:.a-fA-F]+)/([0-9]+)-([0-9]+)$") -re_prefix = re.compile("^([0-9:.a-fA-F]+)/([0-9]+)$") - -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.__class__ is max.__class__, "Type mismatch, %r doesn't match %r" % (min.__class__, max.__class__) - assert min <= max, "Mis-ordered range: %s before %s" % (min, max) - self.min = min - self.max = max - - def __cmp__(self, other): - """ - Compare two resource_range objects. - """ - assert self.__class__ is other.__class__, "Type mismatch, comparing %r with %r" % (self.__class__, other.__class__) - return cmp(self.min, other.min) or cmp(self.max, other.max) - -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)) - - @classmethod - def parse_str(cls, x): - """ - Parse ASN resource range from text (eg, XML attributes). - """ - r = re_asn_range.match(x) - if r: - return cls(long(r.group(1)), long(r.group(2))) - else: - return cls(long(x), long(x)) - - @classmethod - def from_strings(cls, a, b = None): - """ - Construct ASN range from strings. - """ - if b is None: - b = a - return cls(long(a), long(b)) - -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. Returns prefix length if it can, otherwise raises - MustBePrefix exception. - """ - mask = self.min ^ self.max - if self.min & mask != 0: - raise rpki.exceptions.MustBePrefix - prefixlen = self.datum_type.bits - while mask & 1: - prefixlen -= 1 - mask >>= 1 - if mask: - raise rpki.exceptions.MustBePrefix - return prefixlen - - # Backwards compatability, will go away at some point - _prefixlen = prefixlen - - def __str__(self): - """ - Convert a resource_range_ip to string format. - """ - try: - return str(self.min) + "/" + str(self.prefixlen()) - except rpki.exceptions.MustBePrefix: - return str(self.min) + "-" + str(self.max) - - def to_rfc3779_tuple(self): - """ - Convert a resource_range_ip to tuple format for RFC 3779 ASN.1 - encoding. - """ - try: - return ("addressPrefix", _long2bs(self.min, self.datum_type.bits, - prefixlen = self.prefixlen())) - except rpki.exceptions.MustBePrefix: - return ("addressRange", (_long2bs(self.min, self.datum_type.bits, strip = 0), - _long2bs(self.max, self.datum_type.bits, strip = 1))) - - @classmethod - def parse_str(cls, x): - """ - Parse IP address range or prefix from text (eg, XML attributes). - """ - r = re_address_range.match(x) - if r: - return cls(cls.datum_type(r.group(1)), cls.datum_type(r.group(2))) - r = re_prefix.match(x) - if r: - return cls.make_prefix(cls.datum_type(r.group(1)), int(r.group(2))) - raise rpki.exceptions.BadIPResource, 'Bad IP resource "%s"' % (x) - - @classmethod - def make_prefix(cls, prefix, prefixlen): - """ - Construct a resource range corresponding to a prefix. - """ - assert isinstance(prefix, 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 (prefix & mask) == 0, "Resource not in canonical form: %s/%s" % (prefix, prefixlen) - return cls(cls.datum_type(prefix), cls.datum_type(prefix | mask)) - - def chop_into_prefixes(self, result): - """ - Chop up a resource_range_ip into ranges that can be represented as - prefixes. - """ - try: - self.prefixlen() - result.append(self) - except rpki.exceptions.MustBePrefix: - min = self.min - max = self.max - while max >= min: - bits = int(math.log(max - min + 1, 2)) - while True: - mask = ~(~0 << bits) - assert min + mask <= max - if min & mask == 0: - break - assert bits > 0 - bits -= 1 - result.append(self.make_prefix(min, self.datum_type.bits - bits)) - min = self.datum_type(min + mask + 1) - - @classmethod - def from_strings(cls, a, b = None): - """ - Construct IP address range from strings. - """ - if b is None: - b = a - a = rpki.ipaddrs.parse(a) - b = rpki.ipaddrs.parse(b) - if a.__class__ is not b.__class__: - raise TypeError - if cls is resource_range_ip: - if isinstance(a, rpki.ipaddrs.v4addr): - return resource_range_ipv4(a, b) - if isinstance(a, rpki.ipaddrs.v6addr): - return resource_range_ipv6(a, b) - elif isinstance(a, cls.datum_type): - return cls(a, b) - raise TypeError - -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, 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 - - ## @var canonical - # Whether this resource_set is currently in canonical form. - - canonical = False - - def __init__(self, ini = None): - """ - Initialize a resource_set. - """ - list.__init__(self) - if isinstance(ini, (int, long)): - ini = str(ini) - if ini is 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 (isinstance(ini, str) and ini == ""), "Unexpected initializer: %s" % str(ini) - self.canonize() - - def canonize(self): - """ - Whack this resource_set into canonical form. - """ - assert not self.inherit or not self - if not self.canonical: - 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]) - self.canonical = True - - def append(self, item): - """ - Wrapper around list.append() (q.v.) to reset canonical flag. - """ - list.append(self, item) - self.canonical = False - - def extend(self, item): - """ - Wrapper around list.extend() (q.v.) to reset canonical flag. - """ - list.extend(self, item) - self.canonical = False - - 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 %r %r" % (type(self), type(other)) - set1 = type(self)(self) # clone and whack into canonical form - set2 = type(other)(other) # ditto - 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: %r %r" % (type(self), type(other)) - set1 = type(self)(self) # clone and whack into canonical form - set2 = type(other)(other) # ditto - 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)) - while set1 and set1[0].max <= max: - assert set1[0].min >= min - del set1[0] - while set2 and set2[0].max <= max: - assert set2[0].min >= min - del set2[0] - 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 - self.canonize() - if not self: - return False - if type(item) is type(self[0]): - min = item.min - max = item.max - else: - min = item - max = item - lo = 0 - hi = len(self) - while lo < hi: - mid = (lo + hi) / 2 - if self[mid].max < max: - lo = mid + 1 - else: - hi = mid - return lo < len(self) and self[lo].min <= min and self[lo].max >= max - - 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()]) - - @classmethod - def parse_str(cls, s): - """ - Parse resource set from text string (eg, XML attributes). This is - a backwards compatability wrapper, real functionality is now part - of the range classes. - """ - return cls.range_type.parse_str(s) - -class resource_set_as(resource_set): - """ - Autonomous System Number resource set. - """ - - ## @var range_type - # Type of range underlying this type of resource_set. - - range_type = resource_range_as - - 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. - """ - self.canonize() - 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_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_roa_prefix_set(self): - """ - Convert from a resource set to a ROA prefix set. - """ - prefix_ranges = [] - for r in self: - r.chop_into_prefixes(prefix_ranges) - return self.roa_prefix_set_type([ - self.roa_prefix_set_type.prefix_type(r.min, r.prefixlen()) - for r in prefix_ranges]) - - def to_rfc3779_tuple(self): - """ - Convert IP resource set into tuple format used by RFC 3779 ASN.1 - encoder. - """ - self.canonize() - 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): - """ - 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 prefix - # The prefix itself, an IP address with bits beyond the prefix - # length zeroed. - - ## @var prefixlen - # (Minimum) prefix length. - - ## @var max_prefixlen - # Maxmimum prefix length. - - def __init__(self, prefix, 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.prefix = prefix - self.prefixlen = prefixlen - self.max_prefixlen = max_prefixlen - - def __cmp__(self, other): - """ - Compare two ROA prefix objects. Comparision is based on prefix, - prefixlen, and max_prefixlen, in that order. - """ - assert self.__class__ is other.__class__ - return (cmp(self.prefix, other.prefix) or - cmp(self.prefixlen, other.prefixlen) or - cmp(self.max_prefixlen, other.max_prefixlen)) - - def __str__(self): - """ - Convert a ROA prefix to string format. - """ - if self.prefixlen == self.max_prefixlen: - return str(self.prefix) + "/" + str(self.prefixlen) - else: - return str(self.prefix) + "/" + 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.prefix, self.prefixlen) - - def min(self): - """ - Return lowest address covered by prefix. - """ - return self.prefix - - def max(self): - """ - Return highest address covered by prefix. - """ - t = self.range_type.datum_type - return t(self.prefix | ((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.prefix, self.range_type.datum_type.bits, prefixlen = self.prefixlen), - None if self.prefixlen == self.max_prefixlen else self.max_prefixlen) - - @classmethod - def parse_str(cls, x): - """ - Parse ROA prefix from text (eg, an XML attribute). - """ - r = re_prefix_with_maxlen.match(x) - if r: - return cls(cls.range_type.datum_type(r.group(1)), int(r.group(2)), int(r.group(3))) - r = re_prefix.match(x) - if r: - return cls(cls.range_type.datum_type(r.group(1)), int(r.group(2))) - raise rpki.exceptions.BadROAPrefix, 'Bad ROA prefix "%s"' % (x) - -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. - """ - list.__init__(self) - 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() - - def __str__(self): - """ - Convert a ROA prefix set to string format. - """ - return ",".join(str(x) for x in self) - - @classmethod - def parse_str(cls, s): - """ - Parse ROA prefix from text (eg, an XML attribute). - This method is a backwards compatability shim. - """ - return cls.prefix_type.parse_str(s) - - def to_resource_set(self): - """ - Convert a ROA prefix set to a resource set. This is an - irreversable transformation. We have to compute a union here - because ROA prefix sets can include overlaps, while RFC 3779 - resource sets cannot. This is ugly, and there is almost certainly - a more efficient way to do this, but start by getting the output - right before worrying about making it fast or pretty. - """ - r = self.resource_set_type() - s = self.resource_set_type() - s.append(None) - for p in self: - s[0] = p.to_resource_range() - r = r.union(s) - return r - - @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 (prefix, - 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 - -# Fix back link from resource_set to roa_prefix -resource_set_ipv4.roa_prefix_set_type = roa_prefix_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 - -# Fix back link from resource_set to roa_prefix -resource_set_ipv6.roa_prefix_set_type = roa_prefix_set_ipv6 - -# Test suite for set operations. - -if __name__ == "__main__": - - def testprefix(v): - return " (%s)" % v.to_roa_prefix_set() if isinstance(v, resource_set_ip) else "" - - 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, testprefix(r1) - print "y: ", r2, testprefix(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, testprefix(v1) - v1 = r1.difference(r2) - v2 = r2.difference(r1) - print "x-y:", v1, testprefix(v1) - print "y-x:", v2, testprefix(v2) - v1 = r1.symmetric_difference(r2) - v2 = r2.symmetric_difference(r1) - assert v1 == v2 - print "x^y:", v1, testprefix(v1) - v1 = r1.intersection(r2) - v2 = r2.intersection(r1) - assert v1 == v2 - print "x&y:", v1, testprefix(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()) - - def test3(t, s1, s2): - test1(t, s1, s2) - r1 = t(s1).to_roa_prefix_set() - r2 = t(s2).to_roa_prefix_set() - print "x: ", r1 - print "y: ", r2 - print "x>y:", (r1 > r2) - print "x<y:", (r1 < r2) - test1(t.roa_prefix_set_type.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 - test1(resource_set_ipv4, "10.0.0.0/24", "10.0.0.0/32,10.0.0.2/32,10.0.0.4/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 - test2(roa_prefix_set_ipv6, "2002:0a00:002c::1/128", "2002:0a00:002c::2/128") - print - test2(roa_prefix_set_ipv6, "2002:0a00:002c::1/128", "2002:0a00:002c::7/128") - print - test2(roa_prefix_set_ipv6, "2002:0a00:002c::1/128", "2002:0a00:002c::/120") - print - test2(roa_prefix_set_ipv6, "2002:0a00:002c::1/128", "2002:0a00:002c::/120-128") - print - test3(resource_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.3.0.0/24,10.0.0.77/32") - print - test3(resource_set_ipv6, "2002:0a00:002c::1/128", "2002:0a00:002c::2/128") - print - test3(resource_set_ipv6, "2002:0a00:002c::1/128", "2002:0a00:002c::/120") diff --git a/rpkid.with_tls/rpki/roa.py b/rpkid.with_tls/rpki/roa.py deleted file mode 100644 index 9f7c1849..00000000 --- a/rpkid.with_tls/rpki/roa.py +++ /dev/null @@ -1,76 +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.with_tls/rpki/rpki_engine.py b/rpkid.with_tls/rpki/rpki_engine.py deleted file mode 100644 index 46523814..00000000 --- a/rpkid.with_tls/rpki/rpki_engine.py +++ /dev/null @@ -1,1447 +0,0 @@ -""" -Global context for rpkid. - -$Id$ - -Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -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 lxml.etree, re, random -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, rpki.async - -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_update = cfg.get("bpki-ta")) - self.irdb_cert = rpki.x509.X509(Auto_update = cfg.get("irdb-cert")) - self.irbe_cert = rpki.x509.X509(Auto_update = cfg.get("irbe-cert")) - self.rpkid_cert = rpki.x509.X509(Auto_update = cfg.get("rpkid-cert")) - self.rpkid_key = rpki.x509.RSA( Auto_update = cfg.get("rpkid-key")) - - self.irdb_url = cfg.get("irdb-url") - - self.https_server_host = cfg.get("server-host", "") - self.https_server_port = cfg.getint("server-port", 4433) - - self.publication_kludge_base = cfg.get("publication-kludge-base", "publication/") - - self.use_internal_cron = cfg.getboolean("use-internal-cron", True) - - self.initial_delay = random.randint(cfg.getint("initial-delay-min", 10), - cfg.getint("initial-delay-max", 120)) - - # Should be much longer in production - self.cron_period = rpki.sundial.timedelta(seconds = cfg.getint("cron-period", 120)) - self.cron_keepalive = rpki.sundial.timedelta(seconds = cfg.getint("cron-keepalive", 0)) - if not self.cron_keepalive: - self.cron_keepalive = self.cron_period * 4 - self.cron_timeout = None - - def start_cron(self): - """ - Start clock for rpkid's internal cron process. - """ - - if self.use_internal_cron: - self.cron_timer = rpki.async.timer(handler = self.cron) - when = rpki.sundial.now() + rpki.sundial.timedelta(seconds = self.initial_delay) - rpki.log.debug("Scheduling initial cron pass at %s" % when) - self.cron_timer.set(when) - else: - rpki.log.debug("Not using internal clock, start_cron() call ignored") - - def irdb_query(self, q_pdu, callback, errback, expected_pdu_count = None): - """ - Perform an IRDB callback query. - """ - - rpki.log.trace() - - q_msg = rpki.left_right.msg.query() - q_msg.append(q_pdu) - q_der = rpki.left_right.cms_msg().wrap(q_msg, self.rpkid_key, self.rpkid_cert) - - def unwrap(r_der): - r_cms = rpki.left_right.cms_msg(DER = r_der) - r_msg = r_cms.unwrap((self.bpki_ta, self.irdb_cert)) - if not r_msg.is_reply() or not all(type(r_pdu) is type(q_pdu) for r_pdu in r_msg): - raise rpki.exceptions.BadIRDBReply, "Unexpected response to IRDB query: %s" % r_cms.pretty_print_content() - if expected_pdu_count is not None and len(r_msg) != expected_pdu_count: - assert isinstance(expected_pdu_count, (int, long)) - raise rpki.exceptions.BadIRDBReply, "Expected exactly %d PDU%s from IRDB: %s" ( - expected_pdu_count, "" if expected_pdu_count == 1 else "s", r_cms.pretty_print_content()) - callback(r_msg) - - 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_der, - callback = unwrap, - errback = errback) - - def irdb_query_child_resources(self, self_handle, child_handle, callback, errback): - """ - Ask IRDB about a child's resources. - """ - - rpki.log.trace() - - q_pdu = rpki.left_right.list_resources_elt() - q_pdu.self_handle = self_handle - q_pdu.child_handle = child_handle - - def done(r_msg): - callback(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)) - - self.irdb_query(q_pdu, done, errback, expected_pdu_count = 1) - - def irdb_query_roa_requests(self, self_handle, callback, errback): - """ - Ask IRDB about self's ROA requests. - """ - - rpki.log.trace() - - q_pdu = rpki.left_right.list_roa_requests_elt() - q_pdu.self_handle = self_handle - - self.irdb_query(q_pdu, callback, errback) - - def left_right_handler(self, query, path, cb): - """ - Process one left-right PDU. - """ - - rpki.log.trace() - - def done(r_msg): - reply = rpki.left_right.cms_msg().wrap(r_msg, self.rpkid_key, self.rpkid_cert) - self.sql.sweep() - cb(200, reply) - - try: - self.sql.ping() - q_msg = rpki.left_right.cms_msg(DER = query).unwrap((self.bpki_ta, self.irbe_cert)) - if not q_msg.is_query(): - raise rpki.exceptions.BadQuery, "Message type is not query" - q_msg.serve_top_level(self, done) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, data: - rpki.log.traceback() - cb(500, "Unhandled exception %s" % data) - - up_down_url_regexp = re.compile("/up-down/([-A-Z0-9_]+)/([-A-Z0-9_]+)$", re.I) - - def up_down_handler(self, query, path, cb): - """ - Process one up-down PDU. - """ - - rpki.log.trace() - - def done(reply): - self.sql.sweep() - cb(200, reply) - - try: - self.sql.ping() - match = self.up_down_url_regexp.search(path) - if match is None: - raise rpki.exceptions.BadContactURL, "Bad path: %s" % path - self_handle, child_handle = match.groups() - child = rpki.left_right.child_elt.sql_fetch_where1(self, "self.self_handle = %s AND child.child_handle = %s AND child.self_id = self.self_id", - (self_handle, child_handle), "self") - if child is None: - raise rpki.exceptions.ChildNotFound, "Could not find child %s" % child_handle - child.serve_up_down(query, done) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, data: - rpki.log.traceback() - cb(400, "Could not process PDU: %s" % data) - - def checkpoint(self): - """ - Record that we were still alive when we got here, by resetting - keepalive timer. - """ - self.cron_timeout = rpki.sundial.now() + self.cron_keepalive - - def cron(self, cb = None): - """ - Periodic tasks. - """ - - rpki.log.trace() - self.sql.ping() - - now = rpki.sundial.now() - - assert self.use_internal_cron or self.cron_timeout is None - - if self.use_internal_cron: - - if self.cron_timeout and self.cron_timeout < now: - rpki.log.warn("cron keepalive threshold %s has expired, breaking lock" % self.cron_timeout) - self.cron_timeout = None - - when = now + self.cron_period - rpki.log.debug("Scheduling next cron run at %s" % when) - self.cron_timer.set(when) - - if self.cron_timeout: - rpki.log.warn("cron already running, keepalive will expire at %s" % self.cron_timeout) - return - - self.checkpoint() - - def loop(iterator, s): - self.checkpoint() - s.cron(iterator) - - def done(): - self.sql.sweep() - self.cron_timeout = None - rpki.log.info("Finished cron run started at %s" % now) - if not self.use_internal_cron: - cb() - - def lose(e): - self.cron_timeout = None - if self.use_internal_cron: - rpki.log.traceback() - else: - raise - - try: - rpki.async.iterator(rpki.left_right.self_elt.sql_fetch_all(self), loop, done) - - except (rpki.async.ExitNow, SystemExit): - self.cron_timeout = None - raise - - except Exception, e: - lose(e) - - def cronjob_handler(self, query, path, cb): - """ - External trigger for periodic tasks. This is somewhat obsolete - now that we have internal timers, but the test framework still - uses it. - """ - - if self.use_internal_cron: - cb(500, "Running cron internally") - else: - self.cron(lambda: cb(200, "OK")) - - ## @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_persistent): - """ - 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 fetch_issue_response_candidates(self): - """ - Fetch ca_details which are candidates for consideration when - processing an up-down issue_response PDU. - """ - #return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND latest_ca_cert IS NOT NULL AND state != 'revoked'", (self.ca_id,)) - return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state != 'revoked'", (self.ca_id,)) - - def construct_sia_uri(self, parent, rc): - """ - Construct the sia_uri value for this CA given configured - information and the parent's up-down protocol list_response PDU. - """ - - 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 - if 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, cb, eb): - """ - 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) - - def loop(iterator, ca_detail): - - self.gctx.checkpoint() - - rc_cert = cert_map.pop(ca_detail.public_key.get_SKI(), None) - - if rc_cert is None: - - rpki.log.warn("Certificate in database missing from list_response, class %r, SKI %s, maybe parent certificate went away?" - % (rc.class_name, ca_detail.public_key.gSKI())) - publisher = publication_queue() - ca_detail.delete(ca = ca_detail.ca(), publisher = publisher) - return publisher.call_pubd(iterator, eb) - - else: - - if ca_detail.state in ("pending", "active"): - - if ca_detail.state == "pending": - current_resources = rpki.resource_set.resource_bag() - else: - current_resources = ca_detail.latest_ca_cert.get_3779resources() - - if (ca_detail.state == "pending" or - sia_uri_changed or - ca_detail.latest_ca_cert != rc_cert.cert or - current_resources.undersized(rc_resources) or - current_resources.oversized(rc_resources)): - return ca_detail.update( - parent = parent, - ca = self, - rc = rc, - sia_uri_changed = sia_uri_changed, - old_resources = current_resources, - callback = iterator, - errback = eb) - - iterator() - - def done(): - if cert_map: - rpki.log.warn("Certificates in list_response missing from our database, class %r, SKIs %s" - % (rc.class_name, ", ".join(c.cert.gSKI() for c in cert_map.values()))) - self.gctx.checkpoint() - cb() - - ca_details = self.fetch_issue_response_candidates() - - if True: - for x in cert_map.itervalues(): - rpki.log.debug("Parent thinks I have %r %s" % (x, x.cert.gSKI())) - for x in ca_details: - if x.latest_ca_cert is not None: - rpki.log.debug("I think I have %r %s" % (x, x.latest_ca_cert.gSKI())) - - if ca_details: - rpki.async.iterator(ca_details, loop, done) - else: - rpki.log.warn("Existing certificate class %r with no certificates, rekeying" % rc.class_name) - self.gctx.checkpoint() - self.rekey(cb, eb) - - @classmethod - def create(cls, parent, rc, cb, eb): - """ - 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) - - def done(issue_response): - ca_detail.activate( - ca = self, - cert = issue_response.payload.classes[0].certs[0].cert, - uri = issue_response.payload.classes[0].certs[0].cert_url, - callback = cb, - errback = eb) - - rpki.up_down.issue_pdu.query(parent, self, ca_detail, done, eb) - - def delete(self, parent, callback): - """ - 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. - """ - - def lose(e): - rpki.log.traceback() - rpki.log.warn("Could not delete CA %r, skipping: %s" % (self, e)) - callback() - - def done(): - self.sql_delete() - callback() - - publisher = publication_queue() - for ca_detail in self.ca_details(): - ca_detail.delete(ca = self, publisher = publisher) - publisher.call_pubd(done, lose) - - 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, cb, eb): - """ - 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) - - def done(issue_response): - 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, - callback = cb, - errback = eb) - - rpki.up_down.issue_pdu.query(parent, self, new_detail, done, eb) - - def revoke(self, cb, eb): - """ - Revoke deprecated ca_detail objects associated with this ca. - """ - - rpki.log.trace() - - def loop(iterator, ca_detail): - ca_detail.revoke(cb = iterator, eb = eb) - - rpki.async.iterator(self.fetch_deprecated(), loop, cb) - -class ca_detail_obj(rpki.sql.sql_persistent): - """ - 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), - ("crl_published", rpki.sundial.datetime), - ("manifest_published", rpki.sundial.datetime), - "state", - "ca_cert_uri", - "ca_id") - - crl_published = None - manifest_published = None - latest_ca_cert = None - - def sql_decode(self, vals): - """ - Extra assertions for SQL decode of a ca_detail_obj. - """ - rpki.sql.sql_persistent.sql_decode(self, vals) - assert self.public_key is None or self.private_key_id is None or self.public_key.get_DER() == self.private_key_id.get_public_DER() - assert self.manifest_public_key is None or self.manifest_private_key_id is None or self.manifest_public_key.get_DER() == self.manifest_private_key_id.get_public_DER() - - 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 roas(self): - """Fetch all ROA objects that link to this ca_detail.""" - return rpki.rpki_engine.roa_obj.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, callback, errback, predecessor = None): - """ - Activate this ca_detail. - """ - - publisher = publication_queue() - - self.latest_ca_cert = cert - self.ca_cert_uri = uri.rsync() - self.generate_manifest_cert(ca) - self.state = "active" - self.generate_crl(publisher = publisher) - self.generate_manifest(publisher = publisher) - 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(ca_detail = self, publisher = publisher) - for roa in predecessor.roas(): - roa.regenerate(publisher = publisher) - - publisher.call_pubd(callback, errback) - - def delete(self, ca, publisher, allow_failure = False): - """ - Delete this ca_detail and all of the certs it issued. - - If allow_failure is true, we clean up as much as we can but don't - raise an exception. - """ - - repository = ca.parent().repository() - for child_cert in self.child_certs(): - publisher.withdraw(cls = rpki.publication.certificate_elt, uri = child_cert.uri(ca), obj = child_cert.cert, repository = repository, - handler = False if allow_failure else None) - for roa in self.roas(): - roa.revoke(publisher = publisher, allow_failure = allow_failure) - try: - latest_manifest = self.latest_manifest - except AttributeError: - latest_manifest = None - if latest_manifest is not None: - publisher.withdraw(cls = rpki.publication.manifest_elt, uri = self.manifest_uri(ca), obj = self.latest_manifest, repository = repository, - handler = False if allow_failure else None) - try: - latest_crl = self.latest_crl - except AttributeError: - latest_crl = None - if latest_crl is not None: - publisher.withdraw(cls = rpki.publication.crl_elt, uri = self.crl_uri(ca), obj = self.latest_crl, repository = repository, - handler = False if allow_failure else None) - for cert in self.child_certs() + self.revoked_certs(): - cert.sql_delete() - self.sql_delete() - - def revoke(self, cb, eb): - """ - 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. - - - Generate a corresponding final manifest. - - - Destroy old keypairs. - - - Leave final CRL and manifest in place until their nextupdate - time has passed. - """ - - ca = self.ca() - parent = ca.parent() - - def parent_revoked(r_msg): - - if r_msg.payload.ski != self.latest_ca_cert.gSKI(): - raise rpki.exceptions.SKIMismatch - - crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval) - - nextUpdate = rpki.sundial.now() - - if self.latest_manifest is not None: - try: - self.latest_manifest.get_content() - except rpki.exceptions.CMSContentNotSet: - self.latest_manifest.extract() - nextUpdate = nextUpdate.later(self.latest_manifest.getNextUpdate()) - - if self.latest_crl is not None: - nextUpdate = nextUpdate.later(self.latest_crl.getNextUpdate()) - - publisher = publication_queue() - - for child_cert in self.child_certs(): - nextUpdate = nextUpdate.later(child_cert.cert.getNotAfter()) - child_cert.revoke(publisher = publisher) - - nextUpdate += crl_interval - self.generate_crl(publisher = publisher, nextUpdate = nextUpdate) - self.generate_manifest(publisher = publisher, nextUpdate = 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() - publisher.call_pubd(cb, eb) - - rpki.up_down.revoke_pdu.query(ca, self.latest_ca_cert.gSKI(), parent_revoked, eb) - - def update(self, parent, ca, rc, sia_uri_changed, old_resources, callback, errback): - """ - Need to get a new certificate for this ca_detail and perhaps frob - children of this ca_detail. - """ - - def issued(issue_response): - self.latest_ca_cert = issue_response.payload.classes[0].certs[0].cert - new_resources = self.latest_ca_cert.get_3779resources() - publisher = publication_queue() - - 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), - publisher = publisher) - - publisher.call_pubd(callback, errback) - - rpki.up_down.issue_pdu.query(parent, ca, self, issued, errback) - - @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(rpki.resource_set.inherit_token), - v4 = rpki.resource_set.resource_set_ipv4(rpki.resource_set.inherit_token), - v6 = rpki.resource_set.resource_set_ipv6(rpki.resource_set.inherit_token)) - - self.latest_manifest_cert = self.issue_ee(ca, resources, self.manifest_public_key) - - def issue(self, ca, child, subject_key, sia, resources, publisher, 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 %r" % child_cert) - else: - child_cert.cert = cert - rpki.log.debug("Reusing existing child_cert %r" % child_cert) - - child_cert.ski = cert.get_SKI() - child_cert.published = rpki.sundial.now() - child_cert.sql_store() - publisher.publish(cls = rpki.publication.certificate_elt, uri = child_cert.uri(ca), obj = child_cert.cert, repository = ca.parent().repository(), - handler = child_cert.published_callback) - self.generate_manifest(publisher = publisher) - return child_cert - - def generate_crl(self, publisher, 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() - 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) - - self.crl_published = rpki.sundial.now() - self.sql_mark_dirty() - publisher.publish(cls = rpki.publication.crl_elt, uri = self.crl_uri(ca), obj = self.latest_crl, repository = parent.repository(), - handler = self.crl_published_callback) - - def crl_published_callback(self, pdu): - """ - Check result of CRL publication. - """ - pdu.raise_if_error() - self.crl_published = None - self.sql_mark_dirty() - - def generate_manifest(self, publisher, nextUpdate = None): - """ - Generate a new manifest for this ca_detail. - """ - - ca = self.ca() - parent = ca.parent() - crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval) - now = rpki.sundial.now() - - if nextUpdate is None: - nextUpdate = now + crl_interval - - if self.latest_manifest_cert is None or self.latest_manifest_cert.getNotAfter() < nextUpdate: - self.generate_manifest_cert(ca) - - objs = [(self.crl_uri_tail(), self.latest_crl)] - objs.extend((c.uri_tail(), c.cert) for c in self.child_certs()) - objs.extend((r.uri_tail(), r.roa) for r in self.roas() if r.roa is not None) - - self.latest_manifest = rpki.x509.SignedManifest.build( - serial = ca.next_manifest_number(), - thisUpdate = now, - nextUpdate = nextUpdate, - names_and_objs = objs, - keypair = self.manifest_private_key_id, - certs = self.latest_manifest_cert) - - - self.manifest_published = rpki.sundial.now() - self.sql_mark_dirty() - publisher.publish(cls = rpki.publication.manifest_elt, uri = self.manifest_uri(ca), obj = self.latest_manifest, repository = parent.repository(), - handler = self.manifest_published_callback) - - def manifest_published_callback(self, pdu): - """ - Check result of manifest publication. - """ - pdu.raise_if_error() - self.manifest_published = None - self.sql_mark_dirty() - - -class child_cert_obj(rpki.sql.sql_persistent): - """ - 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", - ("published", rpki.sundial.datetime)) - - def __init__(self, gctx = None, child_id = None, ca_detail_id = None, cert = None): - """ - Initialize a child_cert_obj. - """ - rpki.sql.sql_persistent.__init__(self) - self.gctx = gctx - self.child_id = child_id - self.ca_detail_id = ca_detail_id - self.cert = cert - self.published = None - 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, publisher, generate_crl_and_manifest = False): - """ - Revoke a child cert. - """ - - ca_detail = self.ca_detail() - ca = ca_detail.ca() - rpki.log.debug("Revoking %r %r" % (self, self.uri(ca))) - revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail) - publisher.withdraw(cls = rpki.publication.certificate_elt, uri = self.uri(ca), obj = self.cert, repository = ca.parent().repository()) - self.gctx.sql.sweep() - self.sql_delete() - if generate_crl_and_manifest: - ca_detail.generate_crl(publisher = publisher) - ca_detail.generate_manifest(publisher = publisher) - - def reissue(self, ca_detail, publisher, resources = None, sia = None): - """ - Reissue an existing child cert, reusing the public key. If the - child 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 child cert 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: - rpki.log.debug("No change to %r" % self) - return self - - must_revoke = old_resources.oversized(resources) or old_resources.valid_until > resources.valid_until - new_issuer = ca_detail != old_ca_detail - - rpki.log.debug("Reissuing %r, must_revoke %s, new_issuer %s" % (self, must_revoke, new_issuer)) - - 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: - for x in child.child_certs(ca_detail = ca_detail, ski = self.ski): - rpki.log.debug("Revoking child_cert %r" % x) - x.revoke(publisher = publisher) - ca_detail.generate_crl(publisher = publisher) - ca_detail.generate_manifest(publisher = publisher) - - child_cert = ca_detail.issue( - ca = ca, - child = child, - subject_key = self.cert.getPublicKey(), - sia = sia, - resources = resources, - child_cert = None if must_revoke or new_issuer else self, - publisher = publisher) - - rpki.log.debug("New child_cert %r uri %s" % (child_cert, child_cert.uri(ca))) - - 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) - - def published_callback(self, pdu): - """ - Publication callback: check result and mark published. - """ - pdu.raise_if_error() - self.published = None - self.sql_mark_dirty() - -class revoked_cert_obj(rpki.sql.sql_persistent): - """ - 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.""" - rpki.sql.sql_persistent.__init__(self) - 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) - -class roa_obj(rpki.sql.sql_persistent): - """ - Route Origin Authorization. - """ - - sql_template = rpki.sql.template( - "roa", - "roa_id", - "ca_detail_id", - "self_id", - "asn", - ("roa", rpki.x509.ROA), - ("cert", rpki.x509.X509), - ("published", rpki.sundial.datetime)) - - ca_detail_id = None - cert = None - roa = None - published = None - - def self(self): - """ - Fetch self object to which this roa_obj links. - """ - return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id) - - def ca_detail(self): - """ - Fetch ca_detail object to which this roa_obj links. - """ - return rpki.rpki_engine.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id) - - def sql_fetch_hook(self): - """ - Extra SQL fetch actions for roa_obj -- handle prefix lists. - """ - for version, datatype, attribute in ((4, rpki.resource_set.roa_prefix_set_ipv4, "ipv4"), - (6, rpki.resource_set.roa_prefix_set_ipv6, "ipv6")): - setattr(self, attribute, datatype.from_sql( - self.gctx.sql, - """ - SELECT prefix, prefixlen, max_prefixlen FROM roa_prefix - WHERE roa_id = %s AND version = %s - """, - (self.roa_id, version))) - - def sql_insert_hook(self): - """ - Extra SQL insert actions for roa_obj -- handle prefix lists. - """ - for version, prefix_set in ((4, self.ipv4), (6, self.ipv6)): - if prefix_set: - self.gctx.sql.executemany( - """ - INSERT roa_prefix (roa_id, prefix, prefixlen, max_prefixlen, version) - VALUES (%s, %s, %s, %s, %s) - """, - ((self.roa_id, x.prefix, x.prefixlen, x.max_prefixlen, version) - for x in prefix_set)) - - def sql_delete_hook(self): - """ - Extra SQL delete actions for roa_obj -- handle prefix lists. - """ - self.gctx.sql.execute("DELETE FROM roa_prefix WHERE roa_id = %s", (self.roa_id,)) - - def __init__(self, gctx = None, self_id = None, asn = None, ipv4 = None, ipv6 = None): - rpki.sql.sql_persistent.__init__(self) - self.gctx = gctx - self.self_id = self_id - self.asn = asn - self.ipv4 = ipv4 - self.ipv6 = ipv6 - - # Defer marking new ROA as dirty until .generate() has a chance to - # finish setup, otherwise we get SQL consistency errors. - # - #if self_id or asn or ipv4 or ipv6: self.sql_mark_dirty() - - def update(self, publisher, fast = False): - """ - Bring this roa_obj's ROA up to date if necesssary. - """ - - 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() - - me = "<%s %s>" % (self.asn, ("%s,%s" % (v4, v6)).strip(",")) - - if self.roa is None: - rpki.log.debug("ROA doesn't exist, generating %s" % me) - return self.generate(publisher = publisher, fast = fast) - - ca_detail = self.ca_detail() - - if ca_detail is None: - rpki.log.debug("ROA has no associated ca_detail, generating %s" % me) - return self.generate(publisher = publisher, fast = fast) - - if ca_detail.state != "active": - rpki.log.debug("ROA's associated ca_detail not active (state %r), regenerating %s" % (ca_detail.state, me)) - return self.regenerate(publisher = publisher, fast = fast) - - regen_time = self.cert.getNotAfter() - rpki.sundial.timedelta(seconds = self.self().regen_margin) - - if rpki.sundial.now() > regen_time: - rpki.log.debug("ROA past threshold %s, regenerating %s" % (regen_time, me)) - return self.regenerate(publisher = publisher, fast = fast) - - ca_resources = ca_detail.latest_ca_cert.get_3779resources() - ee_resources = self.cert.get_3779resources() - - if ee_resources.oversized(ca_resources): - rpki.log.debug("ROA oversized with respect to CA, regenerating %s" % me) - return self.regenerate(publisher = publisher, fast = fast) - - if ee_resources.v4 != v4 or ee_resources.v6 != v6: - rpki.log.debug("ROA resources do not match EE, regenerating %s" % me) - return self.regenerate(publisher = publisher, fast = fast) - - def generate(self, publisher, fast = False): - """ - Generate a ROA. - - 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 fast is set, we leave generating the new manifest for our - caller to handle, presumably at the end of a bulk operation. - """ - - if self.ipv4 is None and self.ipv6 is None: - raise rpki.exceptions.EmptyROAPrefixList - - # 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: - raise rpki.exceptions.NoCoveringCertForROA, "generate() could not find a certificate covering %s %s" % (v4, v6) - - ca = ca_detail.ca() - resources = rpki.resource_set.resource_bag(v4 = v4, v6 = v6) - keypair = rpki.x509.RSA.generate() - - self.ca_detail_id = ca_detail.ca_detail_id - self.cert = ca_detail.issue_ee( - ca = ca, - resources = resources, - subject_key = keypair.get_RSApublic(), - sia = ((rpki.oids.name2oid["id-ad-signedObject"], ("uri", self.uri(keypair))),)) - self.roa = rpki.x509.ROA.build(self.asn, self.ipv4, self.ipv6, keypair, (self.cert,)) - self.published = rpki.sundial.now() - self.sql_store() - - rpki.log.debug("Generating ROA %r" % self.uri()) - publisher.publish(cls = rpki.publication.roa_elt, uri = self.uri(), obj = self.roa, repository = ca.parent().repository(), handler = self.published_callback) - if not fast: - ca_detail.generate_manifest(publisher = publisher) - - def published_callback(self, pdu): - """ - Check publication result. - """ - pdu.raise_if_error() - self.published = None - self.sql_mark_dirty() - - def revoke(self, publisher, regenerate = False, allow_failure = False, fast = False): - """ - Withdraw ROA associated with this roa_obj. - - In order to preserve make-before-break properties without - duplicating code, this method also handles generating a - replacement ROA when requested. - - If allow_failure is set, failing to withdraw the ROA will not be - considered an error. - - If fast is set, SQL actions will be deferred, on the assumption - that our caller will handle regenerating CRL and manifest and - flushing the SQL cache. - """ - - ca_detail = self.ca_detail() - cert = self.cert - roa = self.roa - uri = self.uri() - - if ca_detail.state != 'active': - self.ca_detail_id = None - - if regenerate: - self.generate(publisher = publisher, fast = fast) - - rpki.log.debug("Withdrawing ROA %r and revoking its EE cert" % uri) - rpki.rpki_engine.revoked_cert_obj.revoke(cert = cert, ca_detail = ca_detail) - publisher.withdraw(cls = rpki.publication.roa_elt, uri = uri, obj = roa, repository = ca_detail.ca().parent().repository(), - handler = False if allow_failure else None) - self.sql_mark_deleted() - if not fast: - ca_detail.generate_crl(publisher = publisher) - ca_detail.generate_manifest(publisher = publisher) - self.gctx.sql.sweep() - - def regenerate(self, publisher, fast = False): - """ - Reissue ROA associated with this roa_obj. - """ - if self.ca_detail() is None: - self.generate(publisher = publisher, fast = fast) - else: - self.revoke(publisher = publisher, regenerate = True, fast = fast) - - def uri(self, key = None): - """ - Return the publication URI for this roa_obj's ROA. - """ - return self.ca_detail().ca().sia_uri + self.uri_tail(key) - - def uri_tail(self, key = None): - """ - Return the tail (filename portion) of the publication URI for this - roa_obj's ROA. - """ - return (key or self.cert).gSKI() + ".roa" - - -class publication_queue(object): - """ - Utility to simplify publication from within rpkid. - - General idea here is to accumulate a collection of objects to be - published, in one or more repositories, each potentially with its - own completion callback. Eventually we want to publish everything - we've accumulated, at which point we need to iterate over the - collection and do repository.call_pubd() for each repository. - """ - - replace = True - - def __init__(self): - self.repositories = {} - self.msgs = {} - self.handlers = {} - if self.replace: - self.uris = {} - - def _add(self, uri, obj, repository, handler, make_pdu): - rid = id(repository) - if rid not in self.repositories: - self.repositories[rid] = repository - self.msgs[rid] = rpki.publication.msg.query() - if self.replace and uri in self.uris: - rpki.log.debug("Removing publication duplicate <%s %r %r>" % (self.uris[uri].action, self.uris[uri].uri, self.uris[uri].payload)) - self.msgs[rid].remove(self.uris.pop(uri)) - pdu = make_pdu(uri = uri, obj = obj) - if handler is not None: - self.handlers[id(pdu)] = handler - pdu.tag = id(pdu) - self.msgs[rid].append(pdu) - if self.replace: - self.uris[uri] = pdu - - def publish(self, cls, uri, obj, repository, handler = None): - return self._add( uri, obj, repository, handler, cls.make_publish) - - def withdraw(self, cls, uri, obj, repository, handler = None): - return self._add( uri, obj, repository, handler, cls.make_withdraw) - - def call_pubd(self, cb, eb): - def loop(iterator, rid): - self.repositories[rid].call_pubd(iterator, eb, self.msgs[rid], self.handlers) - rpki.async.iterator(self.repositories, loop, cb) diff --git a/rpkid.with_tls/rpki/sql.py b/rpkid.with_tls/rpki/sql.py deleted file mode 100644 index 88ede288..00000000 --- a/rpkid.with_tls/rpki/sql.py +++ /dev/null @@ -1,352 +0,0 @@ -""" -SQL interface code. - -$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. -""" - -from __future__ import with_statement - -import warnings - -# Silence warning while loading MySQLdb in Python 2.6, sigh -if hasattr(warnings, "catch_warnings"): - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - import MySQLdb -else: - import MySQLdb - -import _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() - self.db.autocommit(True) - - 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 %r" % 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("%s.%s" % (table_name, c) for c in 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_persistent(object): - """ - Mixin for persistent 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 %r" % 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, also_from = None): - """ - Fetch one object from SQL, based on an arbitrary SQL WHERE expression. - """ - results = cls.sql_fetch_where(gctx, where, args, also_from) - if len(results) == 0: - return None - elif len(results) == 1: - return results[0] - else: - raise rpki.exceptions.DBConsistancyError, \ - "Database contained multiple matches for %s where %s: %r" % \ - (cls.__name__, where % tuple(repr(a) for a in args), results) - - @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, also_from = None): - """ - Fetch objects of this type matching an arbitrary SQL WHERE expression. - """ - if where is None: - assert args is None and also_from is None - if cls.sql_debug: - rpki.log.debug("sql_fetch_where(%r)" % cls.sql_template.select) - gctx.sql.execute(cls.sql_template.select) - else: - query = cls.sql_template.select - if also_from is not None: - query += "," + also_from - query += " WHERE " + where - if cls.sql_debug: - rpki.log.debug("sql_fetch_where(%r, %r)" % (query, 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 - self.sql_mark_dirty() - - 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(%r, %r)" % (self.sql_template.insert, 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(%r, %r)" % (self.sql_template.update, 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) - if self.sql_debug: - rpki.log.debug("sql_fetch_delete(%r, %r)" % (self.sql_template.delete, id)) - self.sql_delete_hook() - self.gctx.sql.execute(self.sql_template.delete, id) - 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.with_tls/rpki/sundial.py b/rpkid.with_tls/rpki/sundial.py deleted file mode 100644 index e8c92d5a..00000000 --- a/rpkid.with_tls/rpki/sundial.py +++ /dev/null @@ -1,287 +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. - -List of arithmetic methods that require result casting was derived by -inspection of the datetime module, to wit: - - >>> import datetime - >>> for t in (datetime.datetime, datetime.timedelta): - ... for k in t.__dict__.keys(): - ... if k.startswith("__"): - ... print "%s.%s()" % (t.__name__, k) - -$Id$ - -Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -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 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.""" - x = str(x) - return cls.fromGeneralizedTime(("19" if x[0] >= "5" else "20") + x) - - 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()) - - @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 - - def __add__(self, y): return _cast(pydatetime.datetime.__add__(self, y)) - def __radd__(self, y): return _cast(pydatetime.datetime.__radd__(self, y)) - def __rsub__(self, y): return _cast(pydatetime.datetime.__rsub__(self, y)) - def __sub__(self, y): return _cast(pydatetime.datetime.__sub__(self, y)) - -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 "uY vW wD xH yM zS" where u, v, w, x, y, and z - are integers and Y, W, D, H, M, and S indicate years, weeks, 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". - - There is no "months" format, because the definition of a month is too - fuzzy to be useful (what day is six months from August 30th?) - - Similarly, the "years" conversion may produce surprising results, as - "one year" in conventional English does not refer to a fixed interval - but rather a fixed (and in some cases undefined) offset within the - Gregorian calendar (what day is one year from February 29th?) 1Y as - implemented by this code refers to a specific number of seconds. - If you mean 365 days or 52 weeks, say that instead. - """ - - ## @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<years>\\d+)Y)?", - "(?:(?P<weeks>\\d+)W)?", - "(?:(?P<days>\\d+)D)?", - "(?:(?P<hours>\\d+)H)?", - "(?:(?P<minutes>\\d+)M)?", - "(?:(?P<seconds>\\d+)S)?", - "$")), - re.I) - - ## @var years_to_seconds - # Conversion factor from years to seconds (value furnished by the - # "units" program). - - years_to_seconds = 31556926 - - @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)) - d = match.groupdict("0") - for k, v in d.iteritems(): - d[k] = int(v) - d["days"] += d.pop("weeks") * 7 - d["seconds"] += d.pop("years") * cls.years_to_seconds - return cls(**d) - else: - raise RuntimeError, "Couldn't parse timedelta %r" % (arg,) - - def convert_to_seconds(self): - """Convert a timedelta interval to seconds.""" - return self.days * 24 * 60 * 60 + self.seconds - - @classmethod - def fromtimedelta(cls, x): - """Convert a datetime.timedelta object into this subclass.""" - return cls(days = x.days, seconds = x.seconds, microseconds = x.microseconds) - - def __abs__(self): return _cast(pydatetime.timedelta.__abs__(self)) - def __add__(self, x): return _cast(pydatetime.timedelta.__add__(self, x)) - def __div__(self, x): return _cast(pydatetime.timedelta.__div__(self, x)) - def __floordiv__(self, x): return _cast(pydatetime.timedelta.__floordiv__(self, x)) - def __mul__(self, x): return _cast(pydatetime.timedelta.__mul__(self, x)) - def __neg__(self): return _cast(pydatetime.timedelta.__neg__(self)) - def __pos__(self): return _cast(pydatetime.timedelta.__pos__(self)) - def __radd__(self, x): return _cast(pydatetime.timedelta.__radd__(self, x)) - def __rdiv__(self, x): return _cast(pydatetime.timedelta.__rdiv__(self, x)) - def __rfloordiv__(self, x): return _cast(pydatetime.timedelta.__rfloordiv__(self, x)) - def __rmul__(self, x): return _cast(pydatetime.timedelta.__rmul__(self, x)) - def __rsub__(self, x): return _cast(pydatetime.timedelta.__rsub__(self, x)) - def __sub__(self, x): return _cast(pydatetime.timedelta.__sub__(self, x)) - -def _cast(x): - """ - Cast result of arithmetic operations back into correct subtype. - """ - if isinstance(x, pydatetime.datetime): - return datetime.fromdatetime(x) - if isinstance(x, pydatetime.timedelta): - return timedelta.fromtimedelta(x) - return x - -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")) - test(now() + timedelta.parse(" 3d 5s ")) - test(now() + timedelta.parse("1y3d5h")) diff --git a/rpkid.with_tls/rpki/up_down.py b/rpkid.with_tls/rpki/up_down.py deleted file mode 100644 index 225c0bd4..00000000 --- a/rpkid.with_tls/rpki/up_down.py +++ /dev/null @@ -1,689 +0,0 @@ -""" -RPKI "up-down" protocol. - -$Id$ - -Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -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 base64, lxml.etree -import rpki.resource_set, rpki.x509, rpki.exceptions, rpki.log -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): - """ - Construct a sub-element with Base64 text content. - """ - if value is not None and not value.empty(): - lxml.etree.SubElement(elt, "{%s}%s" % (xmlns, name), nsmap=nsmap).text = value.get_Base64() - - def serve_pdu(self, q_msg, r_msg, child, callback, errback): - """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. - """ - list.__init__(self) - if isinstance(ini, (list, tuple)): - self[:] = ini - elif isinstance(ini, str): - self[:] = ini.split(",") - for s in self: - if s.strip() != s or "://" not in s: - 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.""" - base_elt.__init__(self) - 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]) - self.make_b64elt(elt, "issuer", self.issuer) - 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, callback, errback): - """ - Serve one "list" PDU. - """ - - def handle(irdb_resources): - - r_msg.payload = list_response_pdu() - - 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) - callback() - - self.gctx.irdb_query_child_resources(child.self().self_handle, child.child_handle, handle, errback) - - @classmethod - def query(cls, parent, cb, eb): - """ - Send a "list" query to parent. - """ - try: - rpki.log.info('Sending "list" request to parent %s' % parent.parent_handle) - parent.query_up_down(cls(), cb, eb) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - eb(e) - -class class_response_syntax(base_elt): - """ - Syntax for Up-Down protocol "list_response" and "issue_response" PDUs. - """ - - def __init__(self): - """ - Initialize class_response_syntax. - """ - base_elt.__init__(self) - 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, callback, errback): - """ - 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 %r" % self.class_name - - # Check current cert, if any - - def got_resources(irdb_resources): - - 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 - - publisher = rpki.rpki_engine.publication_queue() - - if child_cert is None: - child_cert = ca_detail.issue( - ca = ca, - child = child, - subject_key = req_key, - sia = req_sia, - resources = resources, - publisher = publisher) - else: - child_cert = child_cert.reissue( - ca_detail = ca_detail, - sia = req_sia, - resources = resources, - publisher = publisher) - - def done(): - 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) - callback() - - self.gctx.sql.sweep() - assert child_cert and child_cert.sql_in_db - publisher.call_pubd(done, errback) - - self.gctx.irdb_query_child_resources(child.self().self_handle, child.child_handle, got_resources, errback) - - @classmethod - def query(cls, parent, ca, ca_detail, callback, errback): - """ - 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) - rpki.log.info('Sending "issue" request to parent %s' % parent.parent_handle) - parent.query_up_down(self, callback, errback) - -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, cb, eb): - """ - Serve one revoke request PDU. - """ - - def done(): - r_msg.payload = revoke_response_pdu() - r_msg.payload.class_name = self.class_name - r_msg.payload.ski = self.ski - cb() - - ca = child.ca_from_class_name(self.class_name) - publisher = rpki.rpki_engine.publication_queue() - for ca_detail in ca.ca_details(): - for child_cert in child.child_certs(ca_detail = ca_detail, ski = self.get_SKI()): - child_cert.revoke(publisher = publisher) - self.gctx.sql.sweep() - publisher.call_pubd(done, eb) - - @classmethod - def query(cls, ca, gski, cb, eb): - """ - Send a "revoke" request for certificate(s) named by gski to parent associated with ca. - """ - parent = ca.parent() - self = cls() - self.class_name = ca.parent_resource_class - self.ski = gski - rpki.log.info('Sending "revoke" request for SKI %s to parent %s' % (gski, parent.parent_handle)) - parent.query_up_down(self, cb, eb) - -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 = { - rpki.exceptions.NoActiveCA : 1202 } - - def __init__(self, exception = None): - """ - Initialize an error_response PDU from an exception object. - """ - base_elt.__init__(self) - if exception is not None: - self.status = self.exceptions.get(type(exception), 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, callback): - """ - Serve one message request PDU. - """ - - r_msg = message_pdu() - r_msg.sender = self.recipient - r_msg.recipient = self.sender - - def done(): - r_msg.type = self.type2name[type(r_msg.payload)] - callback(r_msg) - - def lose(e): - rpki.log.traceback() - callback(self.serve_error(e)) - - try: - self.log_query(child) - self.payload.serve_pdu(self, r_msg, child, done, lose) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - lose(e) - - def log_query(self, child): - """ - Log query we're handling. Separate method so rootd can override. - """ - rpki.log.info("Serving %s query from child %s [sender %s, recipient %s]" % (self.type, child.child_handle, self.sender, self.recipient)) - - def serve_error(self, exception): - """ - Generate an error_response message PDU. - """ - r_msg = message_pdu() - r_msg.sender = self.recipient - r_msg.recipient = self.sender - 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.with_tls/rpki/x509.py b/rpkid.with_tls/rpki/x509.py deleted file mode 100644 index 07b127b1..00000000 --- a/rpkid.with_tls/rpki/x509.py +++ /dev/null @@ -1,1242 +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-2010 Internet Systems Consortium ("ISC") - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - - -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, POW.pkix, base64, lxml.etree, os, subprocess, sys -import email.mime.application, email.utils, mailbox, time -import rpki.exceptions, rpki.resource_set, rpki.oids, rpki.sundial -import rpki.manifest, rpki.roa, rpki.log, rpki.async - -def base64_with_linebreaks(der): - """ - Encode DER (really, anything) as Base64 text, with linebreaks to - keep the result (sort of) readable. - """ - b = base64.b64encode(der) - n = len(b) - return "\n" + "\n".join(b[i : min(i + 64, n)] for i in xrange(0, n, 64)) + "\n" - -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. - """ - return self.b + base64_with_linebreaks(der) + self.e + "\n" - -def _find_xia_uri(extension, name): - """ - Find a rsync URI in an SIA or AIA extension. - Returns the URI if found, otherwise None. - """ - oid = rpki.oids.name2oid[name] - - for method, location in extension: - if method == oid and location[0] == "uri" and location[1].startswith("rsync://"): - return location[1] - return None - -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. - """ - return all(getattr(self, a, None) is None for a in self.formats) - - def clear(self): - """ - Make this object empty. - """ - for a in self.formats + self.other_clear: - setattr(self, a, None) - self.filename = None - self.timestamp = 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 == "Auto_update": - self.filename = kw[name] - self.check_auto_update() - 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 %r" % (kw,) - - def check_auto_update(self): - """ - Check for updates to a DER object that auto-updates from a file. - """ - if self.filename is None: - return - filename = self.filename - timestamp = os.stat(self.filename).st_mtime - if self.timestamp is None or self.timestamp < timestamp: - rpki.log.debug("Updating %s, timestamp %s" % (filename, rpki.sundial.datetime.fromtimestamp(timestamp))) - f = open(filename, "rb") - value = f.read() - f.close() - if self.pem_converter.looks_like_PEM(value): - value = self.pem_converter.to_DER(value) - self.clear() - self.DER = value - self.filename = filename - self.timestamp = timestamp - - def check(self): - """ - Perform basic checks on a DER object. - """ - assert not self.empty() - self.check_auto_update() - - def get_DER(self): - """ - Get the DER value of this object. - - Subclasses will almost certainly override this method. - """ - self.check() - 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_with_linebreaks(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. - """ - if self is None and other is None: - return 0 - elif self is None: - return -1 - elif other is None: - return 1 - else: - 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_sia_directory_uri(self): - """ - Get SIA directory (id-ad-caRepository) URI from this object. - Only works for subclasses that support getExtension(). - """ - return _find_xia_uri(self.get_SIA(), "id-ad-caRepository") - - def get_sia_manifest_uri(self): - """ - Get SIA manifest (id-ad-rpkiManifest) URI from this object. - Only works for subclasses that support getExtension(). - """ - return _find_xia_uri(self.get_SIA(), "id-ad-rpkiManifest") - - 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["authorityInfoAccess"]) or ((), 0, None))[2] - - def get_aia_uri(self): - """ - Get AIA (id-ad-caIssuers) URI from this object. - Only works for subclasses that support getExtension(). - """ - return _find_xia_uri(self.get_AIA(), "id-ad-caIssuers") - - 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.%d.tmp" % os.getpid() - try: - f = open(fn, "wb") - f.write(self.get_DER()) - f.close() - p = subprocess.Popen(("dumpasn1", "-a", fn), stdout = subprocess.PIPE, stderr = subprocess.STDOUT) - ret = "\n".join(x for x in p.communicate()[0].splitlines() if x.startswith(" ")) - 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") - pem_converter = PEM_converter("CERTIFICATE") - - def get_DER(self): - """ - Get the DER value of this certificate. - """ - self.check() - 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 certificate. - """ - self.check() - 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. - """ - self.check() - if not self.POWpkix: - cert = POW.pkix.Certificate() - cert.fromString(self.get_DER()) - self.POWpkix = cert - return self.POWpkix - - 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) - - def cross_certify(self, keypair, source_cert, serial, notAfter, now = None, pathLenConstraint = 0): - """ - Issue a certificate with values taking from an existing certificate. - This is used to construct some kinds oF BPKI certificates. - """ - - if now is None: - now = rpki.sundial.now() - - assert isinstance(pathLenConstraint, int) and pathLenConstraint >= 0 - - cert = POW.pkix.Certificate() - cert.setVersion(2) - cert.setSerial(serial) - cert.setIssuer(self.get_POWpkix().getSubject()) - cert.setSubject(source_cert.get_POWpkix().getSubject()) - cert.setNotBefore(now.toASN1tuple()) - cert.setNotAfter(notAfter.toASN1tuple()) - cert.tbs.subjectPublicKeyInfo.set( - source_cert.get_POWpkix().tbs.subjectPublicKeyInfo.get()) - cert.setExtensions(( - (rpki.oids.name2oid["subjectKeyIdentifier" ], False, source_cert.get_SKI()), - (rpki.oids.name2oid["authorityKeyIdentifier"], False, (self.get_SKI(), (), None)), - (rpki.oids.name2oid["basicConstraints" ], True, (1, 0)))) - 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. - """ - self.check() - 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. - """ - self.check() - 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: %r" % 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") - pem_converter = PEM_converter("RSA PRIVATE KEY") - - def get_DER(self): - """ - Get the DER value of this keypair. - """ - self.check() - 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. - """ - self.check() - if not self.POW: - self.POW = POW.derRead(POW.RSA_PRIVATE_KEY, self.get_DER()) - return self.POW - - @classmethod - def generate(cls, keylength = 2048): - """ - Generate a new keypair. - """ - rpki.log.debug("Generating new %d-bit RSA key" % keylength) - 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. - """ - self.check() - 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. - """ - self.check() - 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. - """ - self.check() - 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. - """ - self.check() - 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. - """ - if self.content is None: - raise rpki.exceptions.CMSContentNotSet, "Inner content of CMS object %r is not set" % self - 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 get_signingTime(self): - """ - Extract signingTime from CMS signed attributes. - """ - return rpki.sundial.datetime.fromGeneralizedTime(self.get_POW().signingTime()) - - def verify(self, ta): - """ - Verify CMS wrapper and store inner content. - """ - - try: - cms = self.get_POW() - except (rpki.async.ExitNow, SystemExit): - raise - except: - if self.print_on_der_error: - rpki.log.debug("Problem parsing DER CMS message, might not really be DER: %r" % 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 SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI())) - for c in crls: - rpki.log.debug("Received CMS CRL issuer %r" % (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 SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI())) - 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 SKI %s" % (trusted_ee.getIssuer(), trusted_ee.getSubject(), trusted_ee.hSKI())) - if certs and (len(certs) > 1 or certs[0].getSubject() != trusted_ee.getSubject() or certs[0].getPublicKey() != trusted_ee.getPublicKey()): - 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 (rpki.async.ExitNow, SystemExit): - raise - except: - if self.dump_on_verify_failure: - if True: - dbg = self.dumpasn1() - else: - dbg = cms.pprint() - sys.stderr.write("CMS verification failed, dumping ASN.1 (%d octets):\n%s\n" % (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 (rpki.async.ExitNow, SystemExit): - raise - 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,) - - if self.debug_cms_certs: - rpki.log.debug("Signing with cert issuer %s subject %s SKI %s" % (cert.getIssuer(), cert.getSubject(), cert.hSKI())) - for i, c in enumerate(certs): - rpki.log.debug("Additional cert %d issuer %s subject %s SKI %s" % (i, c.getIssuer(), c.getSubject(), c.hSKI())) - - 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, asn, ipv4, ipv6, keypair, certs, version = 0): - """ - Build a ROA. - """ - try: - self = cls() - r = rpki.roa.RouteOriginAttestation() - r.version.set(version) - r.asID.set(asn) - r.ipAddrBlocks.set((a.to_roa_tuple() for a in (ipv4, ipv6) if a)) - self.set_content(r) - self.sign(keypair, certs) - return self - except POW.pkix.DerError, e: - rpki.log.debug("Encoding error while generating ROA %r: %s" % (self, e)) - rpki.log.debug("ROA inner content: %r" % (r.get(),)) - raise - -class DeadDrop(object): - """ - Dead-drop utility for storing copies of CMS messages for debugging or - audit. At the moment this uses Maildir mailbox format, as it has - approximately the right properties and a number of useful tools for - manipulating it already exist. - """ - - def __init__(self, name): - self.maildir = mailbox.Maildir(name, factory = None, create = True) - self.pid = os.getpid() - - def dump(self, obj): - now = time.time() - msg = email.mime.application.MIMEApplication(obj.get_DER(), "x-rpki") - msg["Date"] = email.utils.formatdate(now) - msg["Subject"] = "Process %s dump of %r" % (self.pid, obj) - msg["Message-ID"] = email.utils.make_msgid() - msg["X-RPKI-PID"] = str(self.pid) - msg["X-RPKI-Object"] = repr(obj) - msg["X-RPKI-Timestamp"] = "%f" % now - self.maildir.add(msg) - -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. - # If set, value should be a DeadDrop object. - - dump_outbound_cms = None - - ## @var dump_inbound_cms - # If set, we write all inbound XML-CMS PDUs to disk, for debugging. - # If set, value should be a DeadDrop object. - - 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") - for line in self.pretty_print_content().splitlines(): - rpki.log.warn(line) - 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() - - def wrap(self, msg, keypair, certs, crls = None): - """ - Wrap an XML PDU in CMS and return its DER encoding. - """ - rpki.log.trace() - self.set_content(msg.toXML()) - self.schema_check() - self.sign(keypair, certs, crls) - if self.dump_outbound_cms: - self.dump_outbound_cms.dump(self) - return self.get_DER() - - def unwrap(self, ta): - """ - Unwrap a CMS-wrapped XML PDU and return Python objects. - """ - if self.dump_inbound_cms: - self.dump_inbound_cms.dump(self) - self.verify(ta) - self.schema_check() - return self.saxify(self.get_content()) - -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. - """ - self.check() - 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. - """ - self.check() - 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. - """ - self.check() - 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"): - """ - Generate a new CRL. - """ - 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.with_tls/rpki/xml_utils.py b/rpkid.with_tls/rpki/xml_utils.py deleted file mode 100644 index 4933cda9..00000000 --- a/rpkid.with_tls/rpki/xml_utils.py +++ /dev/null @@ -1,470 +0,0 @@ -""" -XML utilities. - -$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 xml.sax, lxml.sax, lxml.etree, base64 -import rpki.exceptions - -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. - """ - xml.sax.handler.ContentHandler.__init__(self) - 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() and not key.endswith("_handle"): - 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): - """ - Constructor for Base64-encoded subelement. - """ - if value is not None and not value.empty(): - lxml.etree.SubElement(elt, "{%s}%s" % (self.xmlns, name), nsmap = self.nsmap).text = value.get_Base64() - - 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 text_elt(base_elt): - """ - Virtual base class for XML message elements that contain text. - """ - - ## @var text_attribute - # Name of the class attribute that holds the text value. - text_attribute = None - - def endElement(self, stack, name, text): - """ - Extract text from parsed XML. - """ - base_elt.endElement(self, stack, name, text) - setattr(self, self.text_attribute, text) - - def toXML(self): - """ - Insert text into generated XML. - """ - elt = self.make_elt() - elt.text = getattr(self, self.text_attribute) or None - return elt - -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: - self.make_b64elt(elt, i, getattr(self, i, None)) - 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) - handle_name = self.element_name + "_handle" - setattr(r_pdu, handle_name, getattr(self, handle_name, None)) - else: - self.make_reply_clone_hook(r_pdu) - 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_fetch_one(self): - """ - Find the object on which a get, set, or destroy method should - operate. - """ - r = self.serve_fetch_one_maybe() - if r is None: - raise rpki.exceptions.NotFound - return r - - def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb): - """Overridable hook.""" - cb() - - def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): - """Overridable hook.""" - cb() - - def serve_create(self, r_msg, cb, eb): - """ - Handle a create action. - """ - - r_pdu = self.make_reply() - - def one(): - self.sql_store() - setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index)) - self.serve_post_save_hook(self, r_pdu, two, eb) - - def two(): - r_msg.append(r_pdu) - cb() - - oops = self.serve_fetch_one_maybe() - if oops is not None: - raise rpki.exceptions.DuplicateObject, "Object already exists: %r[%r] %r[%r]" % (self, getattr(self, self.element_name + "_handle"), - oops, getattr(oops, oops.element_name + "_handle")) - - self.serve_pre_save_hook(self, r_pdu, one, eb) - - def serve_set(self, r_msg, cb, eb): - """ - 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, None) - if v is not None: - setattr(db_pdu, a, v) - db_pdu.sql_mark_dirty() - - def one(): - db_pdu.sql_store() - db_pdu.serve_post_save_hook(self, r_pdu, two, eb) - - def two(): - r_msg.append(r_pdu) - cb() - - db_pdu.serve_pre_save_hook(self, r_pdu, one, eb) - - def serve_get(self, r_msg, cb, eb): - """ - Handle a get action. - """ - r_pdu = self.serve_fetch_one() - self.make_reply(r_pdu) - r_msg.append(r_pdu) - cb() - - def serve_list(self, r_msg, cb, eb): - """ - 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) - cb() - - def serve_destroy_hook(self, cb, eb): - """ - Overridable hook. - """ - cb() - - def serve_destroy(self, r_msg, cb, eb): - """ - Handle a destroy action. - """ - def done(): - db_pdu.sql_delete() - r_msg.append(self.make_reply()) - cb() - db_pdu = self.serve_fetch_one() - db_pdu.serve_destroy_hook(done, eb) - - def serve_dispatch(self, r_msg, cb, eb): - """ - 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, cb, eb) - - def unimplemented_control(self, *controls): - """ - Uniform handling for unimplemented control operations. - """ - unimplemented = [x for x in controls if getattr(self, x, False)] - if unimplemented: - raise rpki.exceptions.NotImplementedYet, "Unimplemented control %s" % ", ".join(unimplemented) - -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 - - @classmethod - def query(cls, *args): - """Create a query PDU.""" - self = cls(args) - self.type = "query" - return self - - @classmethod - def reply(cls, *args): - """Create a reply PDU.""" - self = cls(args) - self.type = "reply" - return self - - def is_query(self): - """Is this msg a query?""" - return self.type == "query" - - def is_reply(self): - """Is this msg a reply?""" - return self.type == "reply" |