diff options
68 files changed, 3210 insertions, 1935 deletions
diff --git a/Makefile.in b/Makefile.in index 8908ae32..3121c7c4 100644 --- a/Makefile.in +++ b/Makefile.in @@ -45,11 +45,13 @@ SETUP_PY_ROOT = `${PYTHON} -c 'import sys; print "--root " + sys.argv[1] if sys. POW_SO = rpki/POW/_POW.so -RNGS = schemas/relaxng/left-right-schema.rng \ - schemas/relaxng/up-down-schema.rng \ - schemas/relaxng/publication-schema.rng \ +RNGS = schemas/relaxng/left-right.rng \ + schemas/relaxng/up-down.rng \ + schemas/relaxng/publication.rng \ + schemas/relaxng/publication-control.rng \ schemas/relaxng/myrpki.rng \ - schemas/relaxng/router-certificate-schema.rng + schemas/relaxng/router-certificate.rng \ + schemas/relaxng/rrdp.rng SQLS = schemas/sql/rpkid.sql \ schemas/sql/pubd.sql @@ -187,20 +189,26 @@ ${abs_top_srcdir}/rpki/sql_schemas.py: buildtools/make-sql-schemas.py ${SQLS} cd schemas/sql; ${PYTHON} ${abs_top_srcdir}/buildtools/make-sql-schemas.py >$@.tmp mv $@.tmp $@ -schemas/relaxng/left-right-schema.rng: schemas/relaxng/left-right-schema.rnc - ${TRANG} schemas/relaxng/left-right-schema.rnc schemas/relaxng/left-right-schema.rng +schemas/relaxng/left-right.rng: schemas/relaxng/left-right.rnc + ${TRANG} schemas/relaxng/left-right.rnc schemas/relaxng/left-right.rng -schemas/relaxng/up-down-schema.rng: schemas/relaxng/up-down-schema.rnc - ${TRANG} schemas/relaxng/up-down-schema.rnc schemas/relaxng/up-down-schema.rng +schemas/relaxng/up-down.rng: schemas/relaxng/up-down.rnc + ${TRANG} schemas/relaxng/up-down.rnc schemas/relaxng/up-down.rng -schemas/relaxng/publication-schema.rng: schemas/relaxng/publication-schema.rnc - ${TRANG} schemas/relaxng/publication-schema.rnc schemas/relaxng/publication-schema.rng +schemas/relaxng/publication.rng: schemas/relaxng/publication.rnc + ${TRANG} schemas/relaxng/publication.rnc schemas/relaxng/publication.rng + +schemas/relaxng/publication-control.rng: schemas/relaxng/publication-control.rnc + ${TRANG} schemas/relaxng/publication-control.rnc schemas/relaxng/publication-control.rng schemas/relaxng/myrpki.rng: schemas/relaxng/myrpki.rnc ${TRANG} schemas/relaxng/myrpki.rnc schemas/relaxng/myrpki.rng -schemas/relaxng/router-certificate-schema.rng: schemas/relaxng/router-certificate-schema.rnc - ${TRANG} schemas/relaxng/router-certificate-schema.rnc schemas/relaxng/router-certificate-schema.rng +schemas/relaxng/router-certificate.rng: schemas/relaxng/router-certificate.rnc + ${TRANG} schemas/relaxng/router-certificate.rnc schemas/relaxng/router-certificate.rng + +schemas/relaxng/rrdp.rng: schemas/relaxng/rrdp.rnc + ${TRANG} schemas/relaxng/rrdp.rnc schemas/relaxng/rrdp.rng # Eg: PYLINT_FLAGS='--disable=W0311' @@ -208,8 +216,10 @@ lint: { find rpki rp ca -name '*.py' -print; find rp ca -type f -perm -1 -print | xargs grep -El '^#!.+python'; } | \ sort -u | xargs pylint --rcfile ${abs_top_srcdir}/buildtools/pylint.rc ${PYLINT_FLAGS} -tags: Makefile +tags: Makefile .FORCE find rpki rp ca schemas -type f \ \( -name '*.[ch]' -o -name '*.py' -o -name '*.sql' -o -name '*.rnc' \) \ ! -name relaxng.py ! -name sql_schemas.py -print | \ etags - + +.FORCE: diff --git a/ca/rpki-confgen.xml b/ca/rpki-confgen.xml index e0ed273a..5468db50 100644 --- a/ca/rpki-confgen.xml +++ b/ca/rpki-confgen.xml @@ -618,6 +618,15 @@ </doc> </option> + <option name = "pubd-crl" + value = "${myrpki::bpki_servers_directory}/ca.crl"> + <doc> + Where pubd should look for the CRL covering its own BPKI EE + certificate. Don't change this unless you really know what + you are doing. + </doc> + </option> + <option name = "irbe-cert" value = "${myrpki::bpki_servers_directory}/irbe.cer"> <doc> diff --git a/ca/tests/Makefile.in b/ca/tests/Makefile.in index 9796dd2b..618a741e 100644 --- a/ca/tests/Makefile.in +++ b/ca/tests/Makefile.in @@ -3,12 +3,11 @@ PYTHON = @PYTHON@ abs_top_builddir = @abs_top_builddir@ -all: protocol-samples +all: + @true clean: - rm -rf smoketest.dir left-right-protocol-samples publication-protocol-samples yamltest.dir rcynic.xml rcynic-data - -protocol-samples: left-right-protocol-samples/.stamp publication-protocol-samples/.stamp + rm -rf smoketest.dir left-right-protocol-samples publication-protocol-samples publication-control-protocol-samples rrdp-samples yamltest.dir rcynic.xml rcynic-data left-right-protocol-samples/.stamp: left-right-protocol-samples.xml split-protocol-samples.xsl rm -rf left-right-protocol-samples @@ -16,20 +15,44 @@ left-right-protocol-samples/.stamp: left-right-protocol-samples.xml split-protoc xsltproc --param verbose 0 --stringparam dir left-right-protocol-samples split-protocol-samples.xsl left-right-protocol-samples.xml touch $@ +left-right-relaxng: left-right-protocol-samples/.stamp + xmllint --noout --relaxng ../../schemas/relaxng/left-right.rng left-right-protocol-samples/*.xml + publication-protocol-samples/.stamp: publication-protocol-samples.xml split-protocol-samples.xsl rm -rf publication-protocol-samples mkdir publication-protocol-samples xsltproc --param verbose 0 --stringparam dir publication-protocol-samples split-protocol-samples.xsl publication-protocol-samples.xml touch $@ -relaxng: protocol-samples - xmllint --noout --relaxng ../../schemas/relaxng/left-right-schema.rng left-right-protocol-samples/*.xml - xmllint --noout --relaxng ../../schemas/relaxng/up-down-schema.rng up-down-protocol-samples/*.xml - xmllint --noout --relaxng ../../schemas/relaxng/publication-schema.rng publication-protocol-samples/*.xml +publication-relaxng: publication-protocol-samples/.stamp + xmllint --noout --relaxng ../../schemas/relaxng/publication.rng publication-protocol-samples/*.xml + +publication-control-protocol-samples/.stamp: publication-control-protocol-samples.xml split-protocol-samples.xsl + rm -rf publication-control-protocol-samples + mkdir publication-control-protocol-samples + xsltproc --param verbose 0 --stringparam dir publication-control-protocol-samples split-protocol-samples.xsl publication-control-protocol-samples.xml + touch $@ + +publication-control-relaxng: publication-control-protocol-samples/.stamp + xmllint --noout --relaxng ../../schemas/relaxng/publication-control.rng publication-control-protocol-samples/*.xml + +rrdp-samples/.stamp: rrdp-samples.xml split-protocol-samples.xsl + rm -rf rrdp-samples + mkdir rrdp-samples + xsltproc --param verbose 0 --stringparam dir rrdp-samples split-protocol-samples.xsl rrdp-samples.xml + touch $@ + +rrdp-relaxng: rrdp-samples/.stamp + xmllint --noout --relaxng ../../schemas/relaxng/rrdp.rng rrdp-samples/*.xml + +up-down-relaxng: + xmllint --noout --relaxng ../../schemas/relaxng/up-down.rng up-down-protocol-samples/*.xml + +relaxng: up-down-relaxng left-right-relaxng publication-relaxng publication-control-relaxng rrdp-relaxng all-tests:: relaxng -parse-test: protocol-samples +parse-test: left-right-protocol-samples publication-protocol-samples publication-control-protocol-samples ${PYTHON} xml-parse-test.py all-tests:: parse-test diff --git a/ca/tests/publication-control-protocol-samples.xml b/ca/tests/publication-control-protocol-samples.xml new file mode 100644 index 00000000..e094f3f6 --- /dev/null +++ b/ca/tests/publication-control-protocol-samples.xml @@ -0,0 +1,155 @@ +<!-- -*- SGML -*- + - $Id$ + - + - Copyright (C) 2008 American Registry for Internet Numbers ("ARIN") + - + - Permission to use, copy, modify, and distribute this software for any + - purpose with or without fee is hereby granted, provided that the above + - copyright notice and this permission notice appear in all copies. + - + - THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH + - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + - AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, + - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + - PERFORMANCE OF THIS SOFTWARE. + - + - + - This is a collection of sample publication protocol PDU samples + - to use as test cases for the publication protocol RelaxNG schema. + --> + +<completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint> + + <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/"> + <client action="create" client_handle="3" base_uri="rsync://wombat.invalid/"> + <bpki_cert> + MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV + BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN + MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS + b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S + G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G + Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC + DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM + uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP + 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ + diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4 + ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq + hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg + cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4 + XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm + sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH + YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq + 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ== + </bpki_cert> + </client> + </msg> + + <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/"> + <client action="create" client_handle="3"/> + </msg> + + <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/"> + <client action="set" client_handle="3"> + <bpki_glue> + MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV + BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN + MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS + b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S + G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G + Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC + DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM + uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP + 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ + diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4 + ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq + hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg + cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4 + XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm + sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH + YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq + 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ== + </bpki_glue> + </client> + </msg> + + <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/"> + <client action="set" client_handle="3"/> + </msg> + + <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/"> + <client action="get" client_handle="3"/> + </msg> + + <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/"> + <client action="get" client_handle="3" base_uri="rsync://wombat.invalid/"> + <bpki_cert> + MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV + BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN + MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS + b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S + G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G + Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC + DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM + uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP + 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ + diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4 + ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq + hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg + cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4 + XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm + sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH + YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq + 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ== + </bpki_cert> + </client> + </msg> + + <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/"> + <client action="list"/> + </msg> + + <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/"> + <client action="list" client_handle="3"> + <bpki_cert> + MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV + BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN + MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS + b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S + G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G + Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC + DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM + uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP + 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ + diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4 + ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq + hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg + cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4 + XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm + sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH + YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq + 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ== + </bpki_cert> + </client> + </msg> + + <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-control/"> + <client action="destroy" client_handle="3"/> + </msg> + + <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/"> + <client action="destroy" client_handle="3"/> + </msg> + + <!-- === --> + + <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/"> + <report_error error_code="your_hair_is_on_fire">text string</report_error> + </msg> + + <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-control/"> + <report_error error_code="your_hair_is_on_fire"/> + </msg> + +</completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint> diff --git a/ca/tests/publication-protocol-samples.xml b/ca/tests/publication-protocol-samples.xml index 96b095a7..6d0a99a9 100644 --- a/ca/tests/publication-protocol-samples.xml +++ b/ca/tests/publication-protocol-samples.xml @@ -1,370 +1,107 @@ <!-- -*- SGML -*- - - $Id$ + - $Id$ - - - Copyright (C) 2008 American Registry for Internet Numbers ("ARIN") + - Sample PDUs for RPKI publication protocol, from current I-D. - - - Permission to use, copy, modify, and distribute this software for any - - purpose with or without fee is hereby granted, provided that the above - - copyright notice and this permission notice appear in all copies. + - Copyright (c) 2014 IETF Trust and the persons identified as authors + - of the code. All rights reserved. - - - THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH - - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - - AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, - - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE - - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - - PERFORMANCE OF THIS SOFTWARE. + - Redistribution and use in source and binary forms, with or without + - modification, are permitted provided that the following conditions + - are met: - + - * Redistributions of source code must retain the above copyright + - notice, this list of conditions and the following disclaimer. - - - This is a collection of sample publication protocol PDU samples - - to use as test cases for the publication protocol RelaxNG schema. + - * Redistributions in binary form must reproduce the above copyright + - notice, this list of conditions and the following disclaimer in + - the documentation and/or other materials provided with the + - distribution. + - + - * Neither the name of Internet Society, IETF or IETF Trust, nor the + - names of specific contributors, may be used to endorse or promote + - products derived from this software without specific prior written + - permission. + - + - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + - FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + - COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + - BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + - ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + - POSSIBILITY OF SUCH DAMAGE. --> <completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint> - <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="1"> - <config action="set"> - <bpki_crl> - MIIBezBlAgEBMA0GCSqGSIb3DQEBCwUAMCMxITAfBgNVBAMTGFRlc3QgQ2VydGlm - aWNhdGUgcHViZCBUQRcNMDgwNjAyMjE0OTQ1WhcNMDgwNzAyMjE0OTQ1WqAOMAww - CgYDVR0UBAMCAQEwDQYJKoZIhvcNAQELBQADggEBAFWCWgBl4ljVqX/CHo+RpqYt - vmKMnjPVflMXUB7i28RGP4DAq4l7deDU7Q82xEJyE4TXMWDWAV6UG6uUGum0VHWO - cj9ohqyiZUGfOsKg2hbwkETm8sAENOsi1yNdyKGk6jZ16aF5fubxQqZa1pdGCSac - 1/ZYC5sLLhEz3kmz+B9z9mXFVc5TgAh4dN3Gy5ftF8zZAFpDGnS4biCnRVqhGv6R - 0Lh/5xmii+ZU6kNDhbeMsjJg+ZOmtN+wMeHSIbjiy0WuuaZ3k2xSh0C94anrHBZA - vvCRhbazjR0Ef5OMZ5lcllw3uO8IHuoisHKkehy4Y0GySdj98fV+OuiRTH9vt/M= - </bpki_crl> - </config> - </msg> - - <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="1"> - <config action="set"/> - </msg> - - <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="1"> - <config action="get"/> - </msg> - - <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="1"> - <config action="get"> - <bpki_crl> - MIIBezBlAgEBMA0GCSqGSIb3DQEBCwUAMCMxITAfBgNVBAMTGFRlc3QgQ2VydGlm - aWNhdGUgcHViZCBUQRcNMDgwNjAyMjE0OTQ1WhcNMDgwNzAyMjE0OTQ1WqAOMAww - CgYDVR0UBAMCAQEwDQYJKoZIhvcNAQELBQADggEBAFWCWgBl4ljVqX/CHo+RpqYt - vmKMnjPVflMXUB7i28RGP4DAq4l7deDU7Q82xEJyE4TXMWDWAV6UG6uUGum0VHWO - cj9ohqyiZUGfOsKg2hbwkETm8sAENOsi1yNdyKGk6jZ16aF5fubxQqZa1pdGCSac - 1/ZYC5sLLhEz3kmz+B9z9mXFVc5TgAh4dN3Gy5ftF8zZAFpDGnS4biCnRVqhGv6R - 0Lh/5xmii+ZU6kNDhbeMsjJg+ZOmtN+wMeHSIbjiy0WuuaZ3k2xSh0C94anrHBZA - vvCRhbazjR0Ef5OMZ5lcllw3uO8IHuoisHKkehy4Y0GySdj98fV+OuiRTH9vt/M= - </bpki_crl> - </config> - </msg> - - <!-- === --> - - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <client action="create" client_handle="3" base_uri="rsync://wombat.invalid/"> - <bpki_cert> - MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV - BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN - MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS - b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S - G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G - Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC - DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM - uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP - 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ - diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4 - ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq - hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg - cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4 - XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm - sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH - YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq - 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ== - </bpki_cert> - </client> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <client action="create" client_handle="3"/> - </msg> - - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <client action="set" client_handle="3"> - <bpki_glue> - MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV - BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN - MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS - b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S - G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G - Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC - DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM - uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP - 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ - diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4 - ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq - hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg - cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4 - XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm - sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH - YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq - 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ== - </bpki_glue> - </client> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <client action="set" client_handle="3"/> - </msg> - - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <client action="get" client_handle="3"/> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <client action="get" client_handle="3" base_uri="rsync://wombat.invalid/"> - <bpki_cert> - MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV - BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN - MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS - b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S - G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G - Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC - DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM - uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP - 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ - diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4 - ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq - hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg - cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4 - XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm - sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH - YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq - 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ== - </bpki_cert> - </client> - </msg> - - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <client action="list"/> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <client action="list" client_handle="3"> - <bpki_cert> - MIIDGzCCAgOgAwIBAgIJAKi+/+wUhQlxMA0GCSqGSIb3DQEBBQUAMCQxIjAgBgNV - BAMTGVRlc3QgQ2VydGlmaWNhdGUgQm9iIFJvb3QwHhcNMDcwODAxMTk1MzEwWhcN - MDcwODMxMTk1MzEwWjAkMSIwIAYDVQQDExlUZXN0IENlcnRpZmljYXRlIEJvYiBS - b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKYUtJaM5PH5917S - G2ACc7iBYdQO2HYyu8Gb6i9Q2Gxc3cWEX7RTBvgOL79pWf3GIdnoupzMnoZVtY3G - Ux2G/0WkmLui2TCeDhcfXdQ4rcp8J3V/6ESj+yuEPPOG8UN17mUKKgujrch6ZvgC - DO9AyOK/uXu+ABQXTPsn2pVe2EVh3V004ShLi8GKgVdqb/rW/6GTg0Xb/zLT6WWM - uT++6sXTlztJdQYkRamJvKfQDU1naC8mAkGf79Tba0xyBGAUII0GfREY6t4/+NAP - 2Yyb3xNlBqcJoTov0JfNKHZcCZePr79j7LK/hkZxxip+Na9xDpE+oQRV+DRukCRJ - diqg+wIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBTDEsXJe6pjAQD4 - ULlB7+GMDBlimTAfBgNVHSMEGDAWgBTDEsXJe6pjAQD4ULlB7+GMDBlimTANBgkq - hkiG9w0BAQUFAAOCAQEAWWkNcW6S1tKKqtzJsdfhjJiAAPQmOXJskv0ta/8f6Acg - cum1YieNdtT0n96P7CUHOWP8QBb91JzeewR7b6WJLwb1Offs3wNq3kk75pJe89r4 - XY39EZHhMW+Dv0PhIKu2CgD4LeyH1FVTQkF/QObGEmkn+s+HTsuzd1l2VLwcP1Sm - sqep6LAlFj62qqaIJzNeQ9NVkBqtkygnYlBOkaBTHfQTux3jYNpEo8JJB5e/WFdH - YyMNrG2xMOtIC7T4+IOHgT8PgrNhaeDg9ctewj0X8Qi9nI9nXeinicLX8vj6hdEq - 3ORv7RZMJNYqv1HQ3wUE2B7fCPFv7EUwzaCds1kgRQ== - </bpki_cert> - </client> - </msg> - - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <client action="destroy" client_handle="3"/> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <client action="destroy" client_handle="3"/> - </msg> - - <!-- === --> - - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <certificate action="publish" uri="rsync://wombat.invalid/testbed/RIR/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.cer"> - MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQDEyhERjRBODAxN0U2 - NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XDTA4MDUyMjE4MDUxMloXDTA4MDUy - NDE3NTQ1M1owMzExMC8GA1UEAxMoOEZCODIxOEYwNkU1MEFCNzAyQTdEOTZEQzhGMENEQ0Q4 - MjhGN0YxNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMeziKp0k5nP7v6SZoNs - XIMQYRgNtC6Fr/9Xm/1yQHomiPqHUk47rHhGojYiK5AhkrwoYhkH4UjJl2iwklDYczXuaBU3 - F5qrKlZ4aZnjIxdlP7+hktVpeApL6yuJTUAYeC3UIxnLDVdD6phydZ/FOQluffiNDjzteCCv - oyOUatqt8WB+oND6LToHp028g1YUYLHG6mur0dPdcHOVXLSmUDuZ1HDz1nDuYvIVKjB/MpH9 - aW9XeaQ6ZFIlZVPwuuvI2brR+ThH7Gv27GL/o8qFdC300VQfoTZ+rKPGDE8K1cI906BL4kiw - x9z0oiDcE96QCz+B0vsjc9mGaA1jgAxlXWsCAwEAAaOCAhcwggITMB0GA1UdDgQWBBSPuCGP - BuUKtwKn2W3I8M3Ngo9/FzAfBgNVHSMEGDAWgBTfSoAX5mqekXLkYS2M9Mg/I43iozBVBgNV - HR8ETjBMMEqgSKBGhkRyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rlc3RiZWQvUklSLzEvMzBx - QUYtWnFucEZ5NUdFdGpQVElQeU9ONHFNLmNybDBFBggrBgEFBQcBAQQ5MDcwNQYIKwYBBQUH - MAKGKXJzeW5jOi8vbG9jYWxob3N0OjQ0MDAvdGVzdGJlZC9XT01CQVQuY2VyMBgGA1UdIAEB - /wQOMAwwCgYIKwYBBQUHDgIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwgZsG - CCsGAQUFBwELBIGOMIGLMDQGCCsGAQUFBzAFhihyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rl - c3RiZWQvUklSL1IwLzEvMFMGCCsGAQUFBzAKhkdyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rl - c3RiZWQvUklSL1IwLzEvajdnaGp3YmxDcmNDcDlsdHlQRE56WUtQZnhjLm1uZjAaBggrBgEF - BQcBCAEB/wQLMAmgBzAFAgMA/BUwPgYIKwYBBQUHAQcBAf8ELzAtMCsEAgABMCUDAwAKAzAO - AwUAwAACAQMFAcAAAiAwDgMFAsAAAiwDBQDAAAJkMA0GCSqGSIb3DQEBCwUAA4IBAQCEhuH7 - jtI2PJY6+zwv306vmCuXhtu9Lr2mmRw2ZErB8EMcb5xypMrNqMoKeu14K2x4a4RPJkK4yATh - M81FPNRsU5mM0acIRnAPtxjHvPME7PHN2w2nGLASRsZmaa+b8A7SSOxVcFURazENztppsolH - eTpm0cpLItK7mNpudUg1JGuFo94VLf1MnE2EqARG1vTsNhel/SM/UvOArCCOBvf0Gz7kSuup - DSZ7qx+LiDmtEsLdbGNQBiYPbLrDk41PHrxdx28qIj7ejZkRzNFw/3pi8/XK281h8zeHoFVu - 6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbtTdPcXBauY - </certificate> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <certificate action="publish" uri="rsync://wombat.invalid/testbed/RIR/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.cer"/> - </msg> + <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="3"> + <!-- Zero or more PDUs --> + </msg> + + <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3"> + <!-- Zero or more PDUs --> + </msg> + + <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="3"> + <publish uri="rsync://wombat.example/Alice/blCrcCp9ltyPDNzYKPfxc.cer"> + MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQDEyhE + RjRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XDTA4 + MDUyMjE4MDUxMloXDTA4MDUyNDE3NTQ1M1owMzExMC8GA1UEAxMoOEZCODIx + OEYwNkU1MEFCNzAyQTdEOTZEQzhGMENEQ0Q4MjhGN0YxNzCCASIwDQYJKoZI + hvcNAQEBBQADggEPADCCAQoCggEBAMeziKp0k5nP7v6SZoNsXIMQYRgNtC6F + r/9Xm/1yQHomiPqHUk47rHhGojYiK5AhkrwoYhkH4UjJl2iwklDYczXuaBU3 + F5qrKlZ4aZnjIxdlP7+hktVpeApL6yuJTUAYeC3UIxnLDVdD6phydZ/FOQlu + ffiNDjzteCCvoyOUatqt8WB+oND6LToHp028g1YUYLHG6mur0dPdcHOVXLSm + UDuZ1HDz1nDuYvIVKjB/MpH9aW9XeaQ6ZFIlZVPwuuvI2brR+ThH7Gv27GL/ + o8qFdC300VQfoTZ+rKPGDE8K1cI906BL4kiwx9z0oiDcE96QCz+B0vsjc9mG + aA1jgAxlXWsCAwEAAaOCAhcwggITMB0GA1UdDgQWBBSPuCGPBuUKtwKn2W3I + 8M3Ngo9/FzAfBgNVHSMEGDAWgBTfSoAX5mqekXLkYS2M9Mg/I43iozBVBgNV + HR8ETjBMMEqgSKBGhkRyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rlc3RiZWQv + UklSLzEvMzBxQUYtWnFucEZ5NUdFdGpQVElQeU9ONHFNLmNybDBFBggrBgEF + BQcBAQQ5MDcwNQYIKwYBBQUHMAKGKXJzeW5jOi8vbG9jYWxob3N0OjQ0MDAv + dGVzdGJlZC9XT01CQVQuY2VyMBgGA1UdIAEB/wQOMAwwCgYIKwYBBQUHDgIw + DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwgZsGCCsGAQUFBwEL + BIGOMIGLMDQGCCsGAQUFBzAFhihyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rl + c3RiZWQvUklSL1IwLzEvMFMGCCsGAQUFBzAKhkdyc3luYzovL2xvY2FsaG9z + dDo0NDAwL3Rlc3RiZWQvUklSL1IwLzEvajdnaGp3YmxDcmNDcDlsdHlQRE56 + WUtQZnhjLm1uZjAaBggrBgEFBQcBCAEB/wQLMAmgBzAFAgMA/BUwPgYIKwYB + BQUHAQcBAf8ELzAtMCsEAgABMCUDAwAKAzAOAwUAwAACAQMFAcAAAiAwDgMF + AsAAAiwDBQDAAAJkMA0GCSqGSIb3DQEBCwUAA4IBAQCEhuH7jtI2PJY6+zwv + 306vmCuXhtu9Lr2mmRw2ZErB8EMcb5xypMrNqMoKeu14K2x4a4RPJkK4yATh + M81FPNRsU5mM0acIRnAPtxjHvPME7PHN2w2nGLASRsZmaa+b8A7SSOxVcFUR + azENztppsolHeTpm0cpLItK7mNpudUg1JGuFo94VLf1MnE2EqARG1vTsNhel + /SM/UvOArCCOBvf0Gz7kSuupDSZ7qx+LiDmtEsLdbGNQBiYPbLrDk41PHrxd + x28qIj7ejZkRzNFw/3pi8/XK281h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx + 0iwPYdLiDbdWFbtTdPcXBauY + </publish> + </msg> + + <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3"> + <publish uri="rsync://wombat.example/Alice/blCrcCp9ltyPDNzYKPfxc.cer"/> + </msg> + + <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3"> + <report_error error_code="your_hair_is_on_fire"> + Shampooing with sterno again, are we? + </report_error> + </msg> + + <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3"> + <report_error error_code="your_hair_is_on_fire"/> + </msg> + + <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="query" version="3"> + <withdraw uri="rsync://wombat.example/Alice/blCrcCp9ltyPDNzYKPfxc.cer" hash="deadf00d"/> + </msg> + + <msg xmlns="http://www.hactrn.net/uris/rpki/publication-spec/" type="reply" version="3"> + <withdraw uri="rsync://wombat.example/Alice/blCrcCp9ltyPDNzYKPfxc.cer"/> + </msg> - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <certificate action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.cer"/> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <certificate action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.cer"/> - </msg> - - <!-- === --> - - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <crl action="publish" uri="rsync://wombat.invalid/testbed/RIR/1/30qAF-ZqnpFy5GEtjPTIPyON4qM.crl"> - MIIBwzCBrAIBATANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQDEyhERjRBODAxN0U2NkE5RTkx - NzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzFw0wODA1MjIxODA0MTZaFw0wODA1MjIxODA1MTZa - MBQwEgIBAhcNMDgwNTIyMTc1ODQwWqAvMC0wHwYDVR0jBBgwFoAU30qAF+ZqnpFy5GEtjPTI - PyON4qMwCgYDVR0UBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAKkM0Fb/pJpHVHWZyjp4wojH - W2KkvA/DFtBiz3moxocSnkDVP3QI19uVvqdC6nH3hJyFmsAMwULR0f1XU/V4j+X+FqYEl6Nv - p8zAEPIB4r8xbEFs7udRwXRAjkJmOQbv9aomF2i+d7jpTFVJxShZWOgsoGEhIy/aktKQrOIR - c4ZDrXpQwXVj2Y7+cGVfQ4gvnPOdlyLcnNovoegazATvA3EcidBNPWRg7XTCz0LVBEB7JgPd - nNyXRg35HdMEHBl7U9uUQJXP7S02oaQ1ehNDMfaJPgBBpQtAnM1lIzJfevd9+e4ywGsRpxAV - 8wxTXSPd1jwuKtS0kwrgsrQ8Ya85xUE= - </crl> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <crl action="publish" uri="rsync://wombat.invalid/testbed/RIR/1/30qAF-ZqnpFy5GEtjPTIPyON4qM.crl"/> - </msg> - - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <crl action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/1/30qAF-ZqnpFy5GEtjPTIPyON4qM.crl"/> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <crl action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/1/30qAF-ZqnpFy5GEtjPTIPyON4qM.crl"/> - </msg> - - <!-- === --> - - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <manifest action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"> - MIIHCgYJKoZIhvcNAQcCoIIG+zCCBvcCAQMxDTALBglghkgBZQMEAgEwggEeBgsqhkiG9w0B - CRABGqCCAQ0EggEJMIIBBQIBEhgPMjAwODA1MjIxODA1MTVaGA8yMDA4MDUyMjE4MDYxNVoG - CWCGSAFlAwQCATCB0jBEFh9ZbTVUTzRJYnlDb0pNZ3E2R2o4dG41Mng5U0UuY2VyAyEA4L8Z - WMyuhOx+o6kUfsRR++QjSaRaATy4UOeVtjvZVqYwRBYfWnRxbjB3NEVFbU9hclAzQmd1SUY3 - MDhhNTM4LmNlcgMhAGQI1gYJotxWmwzcmpLNFZJ656uWOjcPYANlbNz80xm8MEQWH2xxa1Vx - RHEwMDBESW9ZVjlybXdLTGdrN2F6by5jZXIDIQB7jRAEpkPvc4s4PX9vDvnTifj3BIE145FO - 1ne2kEejVqCCBBEwggQNMIIC9aADAgECAgEFMA0GCSqGSIb3DQEBCwUAMDMxMTAvBgNVBAMT - KDhGQjgyMThGMDZFNTBBQjcwMkE3RDk2REM4RjBDRENEODI4RjdGMTcwHhcNMDgwNTIyMTc1 - NzQ5WhcNMDgwNTI0MTc1NDUzWjAzMTEwLwYDVQQDEyhERkRBMjMyMUJENEVCMDNFQTE1RkUy - N0NGRkRGMEFGRkU1QjBFNjY4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2/Gk - AHW5pDqye0+TvUp7sl0rVgmTfeHpVp18ypxvuovogVJgkjEtBEikfaFU0646wYD6JM6IJFJX - lWLWd7bVmhkWViKuZL0VmT2wpUToNHCLUGUQUVVX8R7oSHFdTArv2AqH+6yt0LmczDH1y2M6 - 2Tgkz9wZ9ryyuPx3VX4PkHzUMlkGFICj1fvyXkcAu8jBaxR9UME1c413TPaMi6lMh1HUmtVN - LJMP5+/SnwEAW/Z3dPClCFIgQXK3nAKPVzAIwADEiqhK7cSchhO7ikI1CVt0XzG4n7oaILc3 - Hq/DAxyiutw5GlkUlKPri2YJzJ3+H4P+TveSa/b02fVA5csm/QIDAQABo4IBKjCCASYwHQYD - VR0OBBYEFN/aIyG9TrA+oV/ifP/fCv/lsOZoMB8GA1UdIwQYMBaAFI+4IY8G5Qq3AqfZbcjw - zc2Cj38XMFgGA1UdHwRRME8wTaBLoEmGR3JzeW5jOi8vbG9jYWxob3N0OjQ0MDAvdGVzdGJl - ZC9SSVIvUjAvMS9qN2doandibENyY0NwOWx0eVBETnpZS1BmeGMuY3JsMGAGCCsGAQUFBwEB - BFQwUjBQBggrBgEFBQcwAoZEcnN5bmM6Ly9sb2NhbGhvc3Q6NDQwMC90ZXN0YmVkL1JJUi8x - L2o3Z2hqd2JsQ3JjQ3A5bHR5UEROellLUGZ4Yy5jZXIwGAYDVR0gAQH/BA4wDDAKBggrBgEF - BQcOAjAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBADpsE9HfgVTgmX1WeJTE - fm87CXuOoGH85RFiAngSt5kR4gYCyadklOZ7Eta+ERUZVu4tcKO6sJOTuHPfVrAvR0VpgH+j - PvXboYWSfwJdi00BC28ScrVM2zarA7B10+J6Oq8tbFlAyVBkrbuPet/axmndBtGWhrBTynGl - nc/5L371Lxy6CrOYqXO0Qx3SrOKaailAe3zTIpHQeACqnPdL00zIBw/hVy/VNaH1wy+FmhAz - TsmsQUrMyovJcu/ry5w0KHlP8BTnqfykikCWR+Lw0VQHmpJGAbtrmsOeIbfLY1zl7A81lDAl - AG/ZH1DUdDOUIXMLHWur+D2rwjp7RL16LHYxggGqMIIBpgIBA4AU39ojIb1OsD6hX+J8/98K - /+Ww5mgwCwYJYIZIAWUDBAIBoGswGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEaMBwGCSqG - SIb3DQEJBTEPFw0wODA1MjIxODA1MTVaMC8GCSqGSIb3DQEJBDEiBCBj/GjEQw3LgKPf5DTz - 8eu1fcp6/cQjqqne6ZqFkF42azANBgkqhkiG9w0BAQEFAASCAQBOY0uHNMwy/o1nFANSgha5 - PZxt8fz+wTrbeomCb+lxqQKq1clcSiQORVGc8NmqC8sS5OR3eTw/3qnK9yPHxz2UQ4hn1pBa - +Zy5veM61qMaXCw6w98EyNcvUfA1AkezAjkabfHQDs3o4Ezh49thXXyRcBoF+O6Lmi+LZbT2 - 4jvfFbaXW9zsb6/DaoDkeHnlk+YYgfSP4wOnkK5uqxtDW8QpMPq3GGdIp0oJDkzEdj7VsWIL - 9JP2mxxL8fTPVUyAPOmURYwYDXqhke2O9eVDiCYhrEfB8/84Rint4Cj8n5aCujnAtqtwxHpD - 0NRYO/V1MjhG+ARy1vRH1Dm0r92RBam3 - </manifest> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <manifest action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"/> - </msg> - - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <manifest action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"/> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <manifest action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"/> - </msg> - - <!-- === --> - - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <roa action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/lqkUqDq000DIoYV9rmwKLgk7azo.roa"> - MIIGmwYJKoZIhvcNAQcCoIIGjDCCBogCAQMxDTALBglghkgBZQMEAgEwKgYLKoZIhvcNAQkQ - ARigGwQZMBcCAgKaMBEwDwQCAAEwCTAHAwUACgMALKCCBJgwggSUMIIDfKADAgECAgEJMA0G - CSqGSIb3DQEBCwUAMDMxMTAvBgNVBAMTKDhGQjgyMThGMDZFNTBBQjcwMkE3RDk2REM4RjBD - RENEODI4RjdGMTcwHhcNMDgwNTIyMTc1ODI0WhcNMDgwNTI0MTc1NDUzWjAzMTEwLwYDVQQD - Eyg5NkE5MTRBODNBQjREMzQwQzhBMTg1N0RBRTZDMEEyRTA5M0I2QjNBMIIBIjANBgkqhkiG - 9w0BAQEFAAOCAQ8AMIIBCgKCAQEApoK50BjW5bcF4gsdaYhndtVADZvQk3RCsvuqDElF6uLi - 9BYQq/NHyDOIMyJtvCmzjdv3Y135n1sNO7YvssqHlt7dMfCQTD5ND1GpFnQLdWP7stWM5AbO - nJV6+PtDITUA/QHOli7Do0YCUgR6G+1QJsMu0DK+TRSzBJ6WP7WIYOBOOg3y/NKc1rkWhS1Q - dcQepbHgQYZHzzpjNDR6+oYVuhuUEWx1P6O4pv/p+tpE0SDua7jBjMywIYHkPQBecf2IX1RU - WNojB9dJlnRx5YUUneP2SvF2MrmdDbclgzwhf6alqD2OjiMuoBOG8yeTKcuhzCMnrFAklbst - 6x3Rnq9BswIDAQABo4IBsTCCAa0wHQYDVR0OBBYEFJapFKg6tNNAyKGFfa5sCi4JO2s6MB8G - A1UdIwQYMBaAFI+4IY8G5Qq3AqfZbcjwzc2Cj38XMFgGA1UdHwRRME8wTaBLoEmGR3JzeW5j - Oi8vbG9jYWxob3N0OjQ0MDAvdGVzdGJlZC9SSVIvUjAvMS9qN2doandibENyY0NwOWx0eVBE - TnpZS1BmeGMuY3JsMGAGCCsGAQUFBwEBBFQwUjBQBggrBgEFBQcwAoZEcnN5bmM6Ly9sb2Nh - bGhvc3Q6NDQwMC90ZXN0YmVkL1JJUi8xL2o3Z2hqd2JsQ3JjQ3A5bHR5UEROellLUGZ4Yy5j - ZXIwGAYDVR0gAQH/BA4wDDAKBggrBgEFBQcOAjAOBgNVHQ8BAf8EBAMCB4AwYwYIKwYBBQUH - AQsEVzBVMFMGCCsGAQUFBzALhkdyc3luYzovL2xvY2FsaG9zdDo0NDAwL3Rlc3RiZWQvUklS - L1IwLzEvbHFrVXFEcTAwMERJb1lWOXJtd0tMZ2s3YXpvLnJvYTAgBggrBgEFBQcBBwEB/wQR - MA8wDQQCAAEwBwMFAAoDACwwDQYJKoZIhvcNAQELBQADggEBAL8iHwsyGOYhhIf3nVuL361y - TOJSP8SR0mtQLHULPl+GkYk+5MRNWtL8ucTXFvniYJtOCXEGGEIO9eDXvkQIXQSz/qbF9URQ - fuf38ghRza257syVhal6UHTgCFYuRIO9CUjcU1vkWUxH05BBIHlYdtlIQbAG/mRsCPCEgSmG - bbQaomGlUOqmJMlKxLLcoAtz2vDrwVotgHyfS5h2mgINFjnlLcNLTci+sfs7/aQAkDYx7K98 - se/ZlMorvGkFNhHoOTcGIrWkYsfkbTygVwWRm278PaB3o4449Kvsg/gb8BZeHXRs68cr5Mcf - jP7Q6jeypjTgDBnwb1yzoJIKWszFuSgxggGqMIIBpgIBA4AUlqkUqDq000DIoYV9rmwKLgk7 - azowCwYJYIZIAWUDBAIBoGswGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEYMBwGCSqGSIb3 - DQEJBTEPFw0wODA1MjIxNzU4MjRaMC8GCSqGSIb3DQEJBDEiBCDCyf9v9Wed515TRp2WwnyM - 1rk6dB///X+aqIym2e9jdTANBgkqhkiG9w0BAQEFAASCAQAFvzrHeRPW+wn4WSyoyBEq0zKS - Cyh5tu1qTR0NHs6Rr/p8Pk81P1HQLND/U+znJZKLWlO2niEHUXPIicPDYchbj8ApH9VxKA+1 - lCWllOzFAsYyZFr3/VNs9pVp2eT4F9eEYBrBVDSNrD72MMTlWm1T5MEXqltTJJOCKzUEX96x - 91iW6A+4erop7S8hpCnxqkTin4bFVreqYcGc4CC4bh+L9pPqJnURcEk7Qeu/WEHQBm38voB4 - S11qRZNrJMQ99oiJR7hXDIBm66HjGqoUL2gPCfpgJEVVnM9pVv2k889z4eTTck2Qj54gga2W - Xkvw4Je420aDx88s9T2+PqXcbZ4g - </roa> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <roa action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/lqkUqDq000DIoYV9rmwKLgk7azo.roa"/> - </msg> - - <msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <roa action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/lqkUqDq000DIoYV9rmwKLgk7azo.roa"/> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <roa action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/lqkUqDq000DIoYV9rmwKLgk7azo.roa"/> - </msg> - - <!-- === --> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <report_error error_code="your_hair_is_on_fire">text string</report_error> - </msg> - - <msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/"> - <report_error error_code="your_hair_is_on_fire"/> - </msg> </completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint> diff --git a/ca/tests/rrdp-samples.xml b/ca/tests/rrdp-samples.xml new file mode 100644 index 00000000..0318b169 --- /dev/null +++ b/ca/tests/rrdp-samples.xml @@ -0,0 +1,88 @@ +<!-- -*- SGML -*- + - $Id$ + - + - This is a collection of sample RRDP PDU samples to use as test + - cases for the RRDP RelaxNG schema. + - + - Need to figure out whose copyright should be on these examples. + - BSD in any case so makes little practical difference, just need to + - be sure we give proper credit. Might be RIPE, might be IETF + - Trust, might be us for derivative work. Slap ours on for the + - moment, fix when we figure this out. + - + - Copyright (C) 2014 Dragon Research Labs ("DRL") + - + - Permission to use, copy, modify, and distribute this software for any + - purpose with or without fee is hereby granted, provided that the above + - copyright notice and this permission notice appear in all copies. + - + - THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH + - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + - AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT, + - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + - PERFORMANCE OF THIS SOFTWARE. + --> + +<completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint> + + <!-- Notification file: lists current snapshots and deltas --> + + <notification version="1" xmlns="http://www.ripe.net/rpki/rrdp" session_id="d9f6dc91-0394-40b9-9663-66aef4bb623a" serial="203"> + <snapshot uri="http://host.example/d9f6dc91-0394-40b9-9663-66aeb623a/snapshot/202.xml" hash="279b79fd8389e20585f26735ee70e0e4d4b8af23bb2e2e611c70e92d2433edea"/> + <delta from="156" to="183" uri="http://host.example/d9f6c91-0394-40b9-9663-66aeb623a/deltas/156/183.xml" hash="a2d56ec180f2dde2a46bf90565932e25829b852a0b43107d5de6e41394c29100"/> + <delta from="183" to="184" uri="http://host.example/d9f6c91-0394-40b9-9663-66aeb623a/deltas/183/184.xml" hash="a2d56ec180f2dde2a46b2e0565932e25829b852a0b43107d5de6e41394c29200"/> + <delta from="184" to="197" uri="http://host.example/d9f6c91-0394-40b9-9663-66aeb623a/deltas/184/197.xml" hash="a2d56ec180f2dde2a46b2e0565932e25829b852a0b43107d5de6e41394c29201"/> + <delta from="197" to="203" uri="http://host.example/d9f6c91-0394-40b9-9663-66aeb623a/deltas/197/203.xml" hash="a2d56ec180f2dde2a4f92e0565932e25829b852a0b43107d5de6e41394c29300"/> + </notification> + + <!-- Snapshot segment: think DNS AXFR --> + + <snapshot version="1" xmlns="http://www.ripe.net/rpki/rrdp" session_id="d9f6dc91-0394-40b9-9663-66aef4bb623a" serial="1"> + <publish uri="http://host.example/foo/bar/cer1.cer"> + MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQD + jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XE + h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbtTdPcXBau + </publish> + <publish uri="http://host.example/foo/bar/cer2.cer"> + MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQD + h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbtTdPcXBau + jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XD + </publish> + <publish uri="http://host.example/foo/bar/cer3.cer"> + MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEwLwYDVQQD + h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbtTdPcXBau + jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFMkEzMB4XD + </publish> + </snapshot> + + <!-- Delta segment: think DNS IXFR --> + + <deltas version="1" xmlns="http://www.ripe.net/rpki/rrdp" session_id="d9f6dc91-0394-40b9-9663-66aef4bb623a" from="0" to="3"> + <delta serial="1"> + <publish uri="http://host.example/foo/bar/cer1.cer"> + MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEw + jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFM + h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbt + </publish> + </delta> + <delta serial="2"> + <withdraw uri="http://host.example/foo/bar/cer1.cer" hash="deadf00d"/> + <publish uri="http://host.example/foo/bar/cer2.cer"> + MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEw + h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbt + jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFM + </publish> + <publish uri="http://host.example/foo/bar/cer3.cer" hash="deadf00d"> + MIIE+jCCA+KgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAzMTEw + h8zeHoFVu6ghRPy5dbOA4akX/KG6b8XIx0iwPYdLiDbdWFbt + jRBODAxN0U2NkE5RTkxNzJFNDYxMkQ4Q0Y0QzgzRjIzOERFM + </publish> + </delta> + <delta serial="3"> + <withdraw uri="http://host.example/foo/bar/cer2.cer" hash="deadf00d"/> + </delta> + </deltas> + +</completely_gratuitous_wrapper_element_to_let_me_run_this_through_xmllint> diff --git a/ca/tests/smoketest.py b/ca/tests/smoketest.py index 32f11cc3..53e65b9f 100644 --- a/ca/tests/smoketest.py +++ b/ca/tests/smoketest.py @@ -47,7 +47,7 @@ import rpki.http import rpki.log import rpki.left_right import rpki.config -import rpki.publication +import rpki.publication_control import rpki.async from rpki.mysql_import import MySQLdb @@ -80,6 +80,7 @@ def allocate_port(): """ Allocate a TCP port number. """ + global base_port p = base_port base_port += 1 @@ -249,16 +250,11 @@ def main(): # the code until final exit is all closures. def start(): - rpki.async.iterator(db.engines, create_rpki_objects, created_rpki_objects) + rpki.async.iterator(db.engines, create_rpki_objects, yaml_loop) def create_rpki_objects(iterator, a): a.create_rpki_objects(iterator) - def created_rpki_objects(): - - # Set pubd's BPKI CRL - set_pubd_crl(yaml_loop) - def yaml_loop(): # This is probably where we should be updating expired BPKI @@ -324,6 +320,7 @@ def cmd_sleep(cb, interval): """ Set an alarm, then wait for it to go off. """ + howlong = rpki.sundial.timedelta.parse(interval) logger.info("Sleeping %r", howlong) rpki.async.timer(cb).set(howlong) @@ -332,6 +329,7 @@ def cmd_shell(cb, *cmd): """ Run a shell command. """ + cmd = " ".join(cmd) status = subprocess.call(cmd, shell = True) logger.info("Shell command returned status %d", status) @@ -341,6 +339,7 @@ def cmd_echo(cb, *words): """ Echo some text to the log. """ + logger.info(" ".join(words)) cb() @@ -498,6 +497,7 @@ class allocation_db(list): """ Print content of the database. """ + for a in self: print a @@ -518,6 +518,7 @@ class allocation(object): """ Initialize one entity and insert it into the database. """ + db.append(self) self.name = yaml["name"] self.parent = parent @@ -554,6 +555,7 @@ class allocation(object): """ Compute the transitive resource closure. """ + resources = self.base for kid in self.kids: resources |= kid.closure() @@ -708,6 +710,7 @@ class allocation(object): """ Set the engine number for this entity. """ + self.irdb_db_name = "irdb%d" % n self.irdb_port = allocate_port() self.rpki_db_name = "rpki%d" % n @@ -717,6 +720,7 @@ class allocation(object): """ Get rpki port to use for this entity. """ + if self.is_hosted: assert self.hosted_by.rpki_port is not None return self.hosted_by.rpki_port @@ -728,6 +732,7 @@ class allocation(object): """ Create BPKI certificates for this entity. """ + logger.info("Constructing BPKI keys and certs for %s", self.name) setup_bpki_cert_chain(name = self.name, ee = ("RPKI", "IRDB", "IRBE"), @@ -741,6 +746,7 @@ class allocation(object): """ Write config files for this entity. """ + logger.info("Writing config files for %s", self.name) assert self.rpki_port is not None d = { "my_name" : self.name, @@ -760,6 +766,7 @@ class allocation(object): """ Set up this entity's IRDB. """ + logger.info("Setting up MySQL for %s", self.name) db = MySQLdb.connect(user = "rpki", db = self.rpki_db_name, passwd = rpki_db_pass, conv = sql_conversions) @@ -794,6 +801,7 @@ class allocation(object): once during setup, then do it again every time we apply a delta to this entity. """ + logger.info("Updating MySQL data for IRDB %s", self.name) db = MySQLdb.connect(user = "irdb", db = self.irdb_db_name, passwd = irdb_db_pass, conv = sql_conversions) @@ -847,6 +855,7 @@ class allocation(object): """ Run daemons for this entity. """ + logger.info("Running daemons for %s", self.name) self.rpkid_process = subprocess.Popen((prog_python, prog_rpkid, "--foreground", "--log-stdout", "--log-level", "debug", "--config", self.name + ".conf") + (("--profile", self.name + ".prof") if args.profile else ())) @@ -856,6 +865,7 @@ class allocation(object): """ Kill daemons for this entity. """ + # pylint: disable=E1103 for proc, name in ((self.rpkid_process, "rpkid"), (self.irdbd_process, "irdbd")): @@ -1005,7 +1015,7 @@ class allocation(object): bsc_handle = "b", generate_keypair = True)) - pubd_pdus.append(rpki.publication.client_elt.make_pdu( + pubd_pdus.append(rpki.publication_control.client_elt.make_pdu( action = "create", client_handle = s.client_handle, base_uri = s.sia_base, @@ -1174,6 +1184,7 @@ def setup_bpki_cert_chain(name, ee = (), ca = ()): """ Build a set of BPKI certificates. """ + s = "exec >/dev/null 2>&1\n" #s = "set -x\n" for kind in ("TA",) + ee + ca: @@ -1201,6 +1212,7 @@ def setup_rootd(rpkid, rootd_yaml): """ Write the config files for rootd. """ + rpkid.cross_certify(rootd_name + "-TA", reverse = True) logger.info("Writing config files for %s", rootd_name) d = { "rootd_name" : rootd_name, @@ -1224,6 +1236,7 @@ def setup_rcynic(): """ Write the config file for rcynic. """ + logger.info("Config file for rcynic") d = { "rcynic_name" : rcynic_name, "rootd_name" : rootd_name, @@ -1236,6 +1249,7 @@ def setup_rsyncd(): """ Write the config file for rsyncd. """ + logger.info("Config file for rsyncd") d = { "rsyncd_name" : rsyncd_name, "rsyncd_port" : rsyncd_port, @@ -1249,6 +1263,7 @@ def setup_publication(pubd_sql): """ Set up publication daemon. """ + logger.info("Configure publication daemon") publication_dir = os.getcwd() + "/publication" assert rootd_sia.startswith("rsync://") @@ -1288,12 +1303,13 @@ def setup_publication(pubd_sql): def call_pubd(pdus, cb): """ - Send a publication message to publication daemon and return the - response. + Send a publication control message to publication daemon and return + the response. """ + logger.info("Calling pubd") - q_msg = rpki.publication.msg.query(*pdus) - q_cms = rpki.publication.cms_msg() + q_msg = rpki.publication_control.msg.query(*pdus) + q_cms = rpki.publication_control.cms_msg() q_der = q_cms.wrap(q_msg, pubd_irbe_key, pubd_irbe_cert) q_url = "http://localhost:%d/control" % pubd_port @@ -1301,13 +1317,13 @@ def call_pubd(pdus, cb): def call_pubd_cb(r_der): global pubd_last_cms_time - r_cms = rpki.publication.cms_msg(DER = r_der) + r_cms = rpki.publication_control.cms_msg(DER = r_der) r_msg = r_cms.unwrap((pubd_ta, pubd_pubd_cert)) pubd_last_cms_time = r_cms.check_replay(pubd_last_cms_time, q_url) logger.debug(r_cms.pretty_print_content()) assert r_msg.is_reply for r_pdu in r_msg: - assert not isinstance(r_pdu, rpki.publication.report_error_elt) + assert not isinstance(r_pdu, rpki.publication_control.report_error_elt) cb(r_msg) def call_pubd_eb(e): @@ -1319,22 +1335,13 @@ def call_pubd(pdus, cb): callback = call_pubd_cb, errback = call_pubd_eb) -def set_pubd_crl(cb): - """ - Whack publication daemon's bpki_crl. This must be configured before - publication daemon starts talking to its clients, and must be - updated whenever we update the CRL. - """ - logger.info("Setting pubd's BPKI CRL") - crl = rpki.x509.CRL(Auto_file = pubd_name + "-TA.crl") - call_pubd([rpki.publication.config_elt.make_pdu(action = "set", bpki_crl = crl)], cb = lambda ignored: cb()) - last_rcynic_run = None def run_rcynic(): """ Run rcynic to see whether what was published makes sense. """ + logger.info("Running rcynic") env = os.environ.copy() env["TZ"] = "" @@ -1350,6 +1357,7 @@ def mangle_sql(filename): """ Mangle an SQL file into a sequence of SQL statements. """ + words = [] f = open(filename) for line in f: @@ -1636,6 +1644,7 @@ sql-database = %(pubd_db_name)s sql-username = %(pubd_db_user)s sql-password = %(pubd_db_pass)s bpki-ta = %(pubd_name)s-TA.cer +pubd-crl = %(pubd_name)s-TA.crl pubd-cert = %(pubd_name)s-PUBD.cer pubd-key = %(pubd_name)s-PUBD.key irbe-cert = %(pubd_name)s-IRBE.cer diff --git a/ca/tests/testpoke.py b/ca/tests/testpoke.py index efa068c9..8a443e0d 100644 --- a/ca/tests/testpoke.py +++ b/ca/tests/testpoke.py @@ -74,9 +74,9 @@ def get_PEM_chain(name, cert = None): if cert is not None: chain.append(cert) if name in yaml_data: - chain.extend([rpki.x509.X509(PEM = x) for x in yaml_data[name]]) + chain.extend(rpki.x509.X509(PEM = x) for x in yaml_data[name]) elif name + "-file" in yaml_data: - chain.extend([rpki.x509.X509(PEM_file = x) for x in yaml_data[name + "-file"]]) + chain.extend(rpki.x509.X509(PEM_file = x) for x in yaml_data[name + "-file"]) return chain def query_up_down(q_pdu): diff --git a/ca/tests/xml-parse-test.py b/ca/tests/xml-parse-test.py index 5ea25492..85f4453e 100644 --- a/ca/tests/xml-parse-test.py +++ b/ca/tests/xml-parse-test.py @@ -28,8 +28,14 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -import glob, lxml.etree, lxml.sax -import rpki.up_down, rpki.left_right, rpki.publication, rpki.relaxng +import glob +import lxml.etree +import lxml.sax +import rpki.up_down +import rpki.left_right +import rpki.publication +import rpki.publication_control +import rpki.relaxng verbose = False @@ -88,17 +94,17 @@ def lr_tester(elt_in, elt_out, msg): def pp_tester(elt_in, elt_out, msg): assert isinstance(msg, rpki.publication.msg) for obj in msg: - if isinstance(obj, rpki.publication.client_elt): + if isinstance(obj, rpki.publication.publish_elt): + pprint(((obj.payload, "Publish object"),)) + if isinstance(obj, rpki.publication.withdraw_elt): + pprint(((None, "Withdraw object"),)) + +def pc_tester(elt_in, elt_out, msg): + assert isinstance(msg, rpki.publication_control.msg) + for obj in msg: + if isinstance(obj, rpki.publication_control.client_elt): pprint(((obj.bpki_cert, "BPKI cert"), (obj.bpki_glue, "BPKI glue"))) - if isinstance(obj, rpki.publication.certificate_elt): - pprint(((obj.payload, "RPKI cert"),)) - if isinstance(obj, rpki.publication.crl_elt): - pprint(((obj.payload, "RPKI CRL"),)) - if isinstance(obj, rpki.publication.manifest_elt): - pprint(((obj.payload, "RPKI manifest"),)) - if isinstance(obj, rpki.publication.roa_elt): - pprint(((obj.payload, "ROA"),)) test(fileglob = "up-down-protocol-samples/*.xml", rng = rpki.relaxng.up_down, @@ -117,3 +123,9 @@ test(fileglob = "publication-protocol-samples/*.xml", sax_handler = rpki.publication.sax_handler, encoding = "us-ascii", tester = pp_tester) + +test(fileglob = "publication-control-protocol-samples/*.xml", + rng = rpki.relaxng.publication_control, + sax_handler = rpki.publication_control.sax_handler, + encoding = "us-ascii", + tester = pc_tester) diff --git a/ca/tests/yamltest.py b/ca/tests/yamltest.py index 2b65dbd2..0d427cf3 100644 --- a/ca/tests/yamltest.py +++ b/ca/tests/yamltest.py @@ -67,6 +67,7 @@ def cleanpath(*names): """ Construct normalized pathnames. """ + return os.path.normpath(os.path.join(*names)) # Pathnames for various things we need @@ -110,6 +111,7 @@ class roa_request(object): """ Parse a ROA request from YAML format. """ + return cls(y.get("asn"), y.get("ipv4"), y.get("ipv6")) @@ -180,6 +182,7 @@ class allocation_db(list): """ Show contents of allocation database. """ + for a in self: a.dump() @@ -210,6 +213,7 @@ class allocation(object): """ Allocate a TCP port. """ + cls.base_port += 1 return cls.base_port @@ -221,6 +225,7 @@ class allocation(object): Allocate an engine number, mostly used to construct MySQL database names. """ + cls.base_engine += 1 return cls.base_engine @@ -275,6 +280,7 @@ class allocation(object): Compute resource closure of this node and its children, to avoid a lot of tedious (and error-prone) duplication in the YAML file. """ + resources = self.base for kid in self.kids: resources |= kid.closure() @@ -285,6 +291,7 @@ class allocation(object): """ Show content of this allocation node. """ + print str(self) def __str__(self): @@ -309,6 +316,7 @@ class allocation(object): """ Is this the root node? """ + return self.parent is None @property @@ -316,6 +324,7 @@ class allocation(object): """ Is this entity hosted? """ + return self.hosted_by is not None @property @@ -323,18 +332,21 @@ class allocation(object): """ Does this entity run a pubd? """ + return self.is_root or not (self.is_hosted or only_one_pubd) def path(self, *names): """ Construct pathnames in this entity's test directory. """ + return cleanpath(test_dir, self.host.name, *names) def csvout(self, fn): """ Open and log a CSV output file. """ + path = self.path(fn) print "Writing", path return rpki.csv_utils.csv_writer(path) @@ -343,6 +355,7 @@ class allocation(object): """ Construct service URL for this node's parent. """ + return "http://localhost:%d/up-down/%s/%s" % (self.parent.host.rpkid_port, self.parent.name, self.name) @@ -351,6 +364,7 @@ class allocation(object): """ Write Autonomous System Numbers CSV file. """ + fn = "%s.asns.csv" % d.name if not args.skip_config: f = self.csvout(fn) @@ -364,6 +378,7 @@ class allocation(object): """ Write prefixes CSV file. """ + fn = "%s.prefixes.csv" % d.name if not args.skip_config: f = self.csvout(fn) @@ -377,6 +392,7 @@ class allocation(object): """ Write ROA CSV file. """ + fn = "%s.roas.csv" % d.name if not args.skip_config: f = self.csvout(fn) @@ -391,6 +407,7 @@ class allocation(object): """ Write Ghostbusters vCard file. """ + if self.ghostbusters: fn = "%s.ghostbusters.vcard" % d.name if not args.skip_config: @@ -409,6 +426,7 @@ class allocation(object): """ Write EE certificates (router certificates, etc). """ + if self.router_certs: fn = "%s.routercerts.xml" % d.name if not args.skip_config: @@ -434,6 +452,7 @@ class allocation(object): """ Walk up tree until we find somebody who runs pubd. """ + s = self while not s.runs_pubd: s = s.parent @@ -444,6 +463,7 @@ class allocation(object): """ Work out what pubd configure_publication_client will call us. """ + path = [] s = self if not args.flat_publication: @@ -539,6 +559,7 @@ class allocation(object): """ Run rpkic for this entity. """ + cmd = [prog_rpkic, "-i", self.name, "-c", self.path("rpki.conf")] if args.profile: cmd.append("--profile") @@ -554,6 +575,7 @@ class allocation(object): Start a Python daemon and return a subprocess.Popen object representing the running daemon. """ + basename = os.path.splitext(os.path.basename(prog))[0] cmd = [prog, "--foreground", "--log-level", "debug", "--log-file", self.path(basename + ".log"), @@ -569,30 +591,35 @@ class allocation(object): """ Run rpkid. """ + return self.run_python_daemon(prog_rpkid) def run_irdbd(self): """ Run irdbd. """ + return self.run_python_daemon(prog_irdbd) def run_pubd(self): """ Run pubd. """ + return self.run_python_daemon(prog_pubd) def run_rootd(self): """ Run rootd. """ + return self.run_python_daemon(prog_rootd) def run_rsyncd(self): """ Run rsyncd. """ + p = subprocess.Popen(("rsync", "--daemon", "--no-detach", "--config", "rsyncd.conf"), cwd = self.path()) print "Running rsyncd for %s: pid %d process %r" % (self.name, p.pid, p) diff --git a/rpki/adns.py b/rpki/adns.py index 968684b5..018bb7cf 100644 --- a/rpki/adns.py +++ b/rpki/adns.py @@ -88,6 +88,7 @@ class dispatcher(asyncore.dispatcher): """ Receive a packet, hand it off to query class callback. """ + wire, from_address = self.recvfrom(self.bufsize) self.cb(self.af, from_address[0], from_address[1], wire) @@ -95,18 +96,21 @@ class dispatcher(asyncore.dispatcher): """ Pass errors to query class errback. """ + self.eb(sys.exc_info()[1]) def handle_connect(self): """ Quietly ignore UDP "connection" events. """ + pass def writable(self): """ We don't need to hear about UDP socket becoming writable. """ + return False @@ -138,6 +142,7 @@ class query(object): query; if we find an answer there, just return it. Otherwise start the network query. """ + if resolver.cache: answer = resolver.cache.get((self.qname, self.qtype, self.qclass)) else: @@ -161,6 +166,7 @@ class query(object): Outer loop. If we haven't got a response yet and still have nameservers to check, start inner loop. Otherwise, we're done. """ + self.timer.cancel() if self.response is None and self.nameservers: self.iterator = rpki.async.iterator(self.nameservers[:], self.loop2, self.done2) @@ -172,6 +178,7 @@ class query(object): Inner loop. Send query to next nameserver in our list, unless we've hit the overall timeout for this query. """ + self.timer.cancel() try: timeout = resolver._compute_timeout(self.start) @@ -191,6 +198,7 @@ class query(object): """ No answer from nameserver, move on to next one (inner loop). """ + self.response = None self.iterator() @@ -200,6 +208,7 @@ class query(object): error, handle as if we've timed out on this nameserver; otherwise, pass error back to caller. """ + self.timer.cancel() if isinstance(e, socket.error): self.response = None @@ -215,6 +224,7 @@ class query(object): we're done, otherwise handle error appropriately and move on to next nameserver. """ + sender = (af, dns.inet.inet_pton(af, from_host)) if from_port != resolver.port or sender not in self.nameservers: return @@ -240,6 +250,7 @@ class query(object): while before starting the cycle again, unless we've hit the timeout threshold for the whole query. """ + if self.response is None and self.nameservers: try: delay = rpki.sundial.timedelta(seconds = min(resolver._compute_timeout(self.start), self.backoff)) @@ -256,6 +267,7 @@ class query(object): """ Shut down our timer and sockets. """ + self.timer.cancel() for s in self.sockets.itervalues(): s.close() @@ -264,6 +276,7 @@ class query(object): """ Something bad happened. Clean up, then pass error back to caller. """ + self.cleanup() self.eb(self, e) @@ -273,6 +286,7 @@ class query(object): pass it back to caller; if we got an error, pass the appropriate exception back to caller. """ + self.cleanup() try: if not self.nameservers: diff --git a/rpki/async.py b/rpki/async.py index b17c31ed..f2abd05d 100644 --- a/rpki/async.py +++ b/rpki/async.py @@ -131,6 +131,7 @@ class timer(object): """ Debug logging. """ + if self.gc_debug: bt = traceback.extract_stack(limit = 3) logger.debug("%s from %s:%d", msg, bt[0][0], bt[0][1]) @@ -140,6 +141,7 @@ class timer(object): Set a timer. Argument can be a datetime, to specify an absolute time, or a timedelta, to specify an offset time. """ + if self.gc_debug: self.trace("Setting %r to %r" % (self, when)) if isinstance(when, rpki.sundial.timedelta): @@ -162,6 +164,7 @@ class timer(object): """ Cancel a timer, if it was set. """ + if self.gc_debug: self.trace("Canceling %r" % self) try: @@ -174,6 +177,7 @@ class timer(object): """ Test whether this timer is currently set. """ + return self in timer_queue def set_handler(self, handler): @@ -184,12 +188,14 @@ class timer(object): bound method to an object in a class representing a network connection). """ + self.handler = handler def set_errback(self, errback): """ Set a timer's errback. Like set_handler(), for errbacks. """ + self.errback = errback @classmethod @@ -202,6 +208,7 @@ class timer(object): called, so that even if new events keep getting scheduled, we'll return to the I/O loop reasonably quickly. """ + now = rpki.sundial.now() while timer_queue and now >= timer_queue[0].when: t = timer_queue.pop(0) @@ -233,6 +240,7 @@ class timer(object): the same units (argh!), and we're not doing anything that hair-triggered, so rounding up is simplest. """ + if not timer_queue: return None now = rpki.sundial.now() @@ -251,6 +259,7 @@ class timer(object): queue content, but this way we can notify subclasses that provide their own cancel() method. """ + while timer_queue: timer_queue.pop(0).cancel() @@ -258,12 +267,14 @@ def _raiseExitNow(signum, frame): """ Signal handler for event_loop(). """ + raise ExitNow def exit_event_loop(): """ Force exit from event_loop(). """ + raise ExitNow def event_defer(handler, delay = rpki.sundial.timedelta(seconds = 0)): @@ -271,6 +282,7 @@ def event_defer(handler, delay = rpki.sundial.timedelta(seconds = 0)): Use a near-term (default: zero interval) timer to schedule an event to run after letting the I/O system have a turn. """ + timer(handler).set(delay) ## @var debug_event_timing @@ -282,6 +294,7 @@ def event_loop(catch_signals = (signal.SIGINT, signal.SIGTERM)): """ Replacement for asyncore.loop(), adding timer and signal support. """ + old_signal_handlers = {} while True: save_sigs = len(old_signal_handlers) == 0 @@ -347,6 +360,7 @@ class sync_wrapper(object): Wrapped code has requested normal termination. Store result, and exit the event loop. """ + self.res = res self.fin = True logger.debug("%r callback with result %r", self, self.res) @@ -357,6 +371,7 @@ class sync_wrapper(object): Wrapped code raised an exception. Store exception data, then exit the event loop. """ + exc_info = sys.exc_info() self.err = exc_info if exc_info[1] is err else err self.fin = True @@ -401,6 +416,7 @@ class gc_summary(object): """ Collect and log GC state for this period, reset timer. """ + logger.debug("gc_summary: Running gc.collect()") gc.collect() logger.debug("gc_summary: Summarizing (threshold %d)", self.threshold) diff --git a/rpki/config.py b/rpki/config.py index f38427c4..0e30982b 100644 --- a/rpki/config.py +++ b/rpki/config.py @@ -141,6 +141,7 @@ class parser(object): 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, "") @@ -151,6 +152,7 @@ class parser(object): """ 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): @@ -165,6 +167,7 @@ class parser(object): """ Get a boolean option, perhaps with a default value. """ + v = self.get(option, default, section) if isinstance(v, str): v = v.lower() @@ -177,12 +180,14 @@ class parser(object): """ 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): diff --git a/rpki/csv_utils.py b/rpki/csv_utils.py index 9ba04a02..9034e96b 100644 --- a/rpki/csv_utils.py +++ b/rpki/csv_utils.py @@ -99,6 +99,7 @@ class csv_writer(object): """ Close this writer. """ + if self.file is not None: self.file.close() self.file = None @@ -109,4 +110,5 @@ class csv_writer(object): """ Fake inheritance from whatever object csv.writer deigns to give us. """ + return getattr(self.writer, attr) diff --git a/rpki/exceptions.py b/rpki/exceptions.py index 504c6f28..86c7fa27 100644 --- a/rpki/exceptions.py +++ b/rpki/exceptions.py @@ -288,6 +288,16 @@ class NoObjectAtURI(RPKI_Exception): No object published at specified URI. """ +class ExistingObjectAtURI(RPKI_Exception): + """ + An object has already been published at specified URI. + """ + +class DifferentObjectAtURI(RPKI_Exception): + """ + An object with a different hash exists at specified URI. + """ + class CMSContentNotSet(RPKI_Exception): """ Inner content of a CMS_object has not been set. If object is known diff --git a/rpki/gui/app/check_expired.py b/rpki/gui/app/check_expired.py index a084af79..2907f071 100644 --- a/rpki/gui/app/check_expired.py +++ b/rpki/gui/app/check_expired.py @@ -41,8 +41,8 @@ def check_cert(handle, p, errs): The displayed object name defaults to the class name, but can be overridden using the `object_name` argument. - """ + t = p.certificate.getNotAfter() if t <= expire_time: e = 'expired' if t <= now else 'will expire' @@ -102,8 +102,8 @@ def check_expire(conf, errs): def check_child_certs(conf, errs): """Fetch the list of published objects from rpkid, and inspect the issued resource certs (uri ending in .cer). - """ + z = Zookeeper(handle=conf.handle) req = list_published_objects_elt.make_pdu(action="list", tag="list_published_objects", @@ -139,8 +139,8 @@ def notify_expired(expire_days=14, from_email=None): expire_days: the number of days ahead of today to warn from_email: set the From: address for the email - """ + global expire_time # so i don't have to pass it around global now diff --git a/rpki/gui/app/forms.py b/rpki/gui/app/forms.py index 5394a804..02561303 100644 --- a/rpki/gui/app/forms.py +++ b/rpki/gui/app/forms.py @@ -52,6 +52,7 @@ class GhostbusterRequestForm(forms.ModelForm): Generate a ModelForm with the subset of parents for the current resource handle. """ + # override default form field parent = forms.ModelChoiceField(queryset=None, required=False, help_text='Specify specific parent, or none for all parents') @@ -86,6 +87,7 @@ class GhostbusterRequestForm(forms.ModelForm): class ImportForm(forms.Form): """Form used for uploading parent/child identity xml files.""" + handle = forms.CharField(required=False, widget=forms.TextInput(attrs={'class': 'xlarge'}), help_text='Optional. Your name for this entity, or blank to accept name in XML') @@ -101,6 +103,7 @@ class ImportRepositoryForm(forms.Form): class ImportClientForm(forms.Form): """Form used for importing publication client requests.""" + xml = forms.FileField(label='XML file') @@ -137,6 +140,7 @@ class UserCreateForm(forms.Form): class UserEditForm(forms.Form): """Form for editing a user.""" + email = forms.CharField() pw = forms.CharField(widget=forms.PasswordInput, label='Password', required=False) @@ -185,8 +189,8 @@ class ROARequest(forms.Form): """Takes an optional `conf` keyword argument specifying the user that is creating the ROAs. It is used for validating that the prefix the user entered is currently allocated to that user. - """ + conf = kwargs.pop('conf', None) kwargs['auto_id'] = False super(ROARequest, self).__init__(*args, **kwargs) @@ -199,8 +203,8 @@ class ROARequest(forms.Form): rpki.resource_set.resource_range_ip object. If there is no mask provided, assume the closest classful mask. - """ + prefix = self.cleaned_data.get('prefix') if '/' not in prefix: p = IPAddress(prefix) @@ -296,7 +300,6 @@ class AddASNForm(forms.Form): Returns a forms.Form subclass which verifies that the entered ASN range does not overlap with a previous allocation to the specified child, and that the ASN range is within the range allocated to the parent. - """ asns = forms.CharField( @@ -335,8 +338,8 @@ class AddNetForm(forms.Form): Returns a forms.Form subclass which validates that the entered address range is within the resources allocated to the parent, and does not overlap with what is already allocated to the specified child. - """ + address_range = forms.CharField( help_text='CIDR or range', widget=forms.TextInput(attrs={'autofocus': 'true'}) @@ -383,7 +386,6 @@ def ChildForm(instance): This is roughly based on the equivalent ModelForm, but uses Form as a base class so that selection boxes for the AS and Prefixes can be edited in a single form. - """ class _wrapped(forms.Form): @@ -401,11 +403,13 @@ def ChildForm(instance): class Empty(forms.Form): """Stub form for views requiring confirmation.""" + pass class ResourceHolderForm(forms.Form): """form for editing ACL on Conf objects.""" + users = forms.ModelMultipleChoiceField( queryset=User.objects.all(), help_text='users allowed to mange this resource holder' @@ -413,7 +417,8 @@ class ResourceHolderForm(forms.Form): class ResourceHolderCreateForm(forms.Form): - """form for creating new resource holdres.""" + """form for creating new resource holders.""" + handle = forms.CharField(max_length=30) parent = forms.ModelChoiceField( required=False, diff --git a/rpki/gui/app/glue.py b/rpki/gui/app/glue.py index 0bf5f942..f17ba5ac 100644 --- a/rpki/gui/app/glue.py +++ b/rpki/gui/app/glue.py @@ -16,7 +16,6 @@ """ This file contains code that interfaces between the django views implementing the portal gui and the rpki.* modules. - """ from __future__ import with_statement @@ -39,6 +38,7 @@ from django.db.transaction import commit_on_success def ghostbuster_to_vcard(gbr): """Convert a GhostbusterRequest object into a vCard object.""" + import vobject vcard = vobject.vCard() @@ -86,7 +86,6 @@ def list_received_resources(log, conf): The semantics are to clear the entire table and populate with the list of certs received. Other models should not reference the table directly with foreign keys. - """ z = Zookeeper(handle=conf.handle) diff --git a/rpki/gui/app/models.py b/rpki/gui/app/models.py index 32a897c7..d6332796 100644 --- a/rpki/gui/app/models.py +++ b/rpki/gui/app/models.py @@ -120,16 +120,16 @@ class Conf(rpki.irdb.models.ResourceHolderCA): def parents(self): """Simulates irdb.models.Parent.objects, but returns app.models.Parent proxy objects. - """ + return Parent.objects.filter(issuer=self) @property def children(self): """Simulates irdb.models.Child.objects, but returns app.models.Child proxy objects. - """ + return Child.objects.filter(issuer=self) @property @@ -148,8 +148,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA): def routes(self): """Return all IPv4 routes covered by RPKI certs issued to this resource holder. - """ + # build a Q filter to select all RouteOrigin objects covered by # prefixes in the resource holder's certificates q = models.Q() @@ -162,8 +162,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA): def routes_v6(self): """Return all IPv6 routes covered by RPKI certs issued to this resource holder. - """ + # build a Q filter to select all RouteOrigin objects covered by # prefixes in the resource holder's certificates q = models.Q() @@ -174,6 +174,7 @@ class Conf(rpki.irdb.models.ResourceHolderCA): def send_alert(self, subject, message, from_email, severity=Alert.INFO): """Store an alert for this resource holder.""" + self.alerts.create(subject=subject, text=message, severity=severity) send_mail( @@ -189,8 +190,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA): Contact emails are extract from any ghostbuster requests, and any linked user accounts. - """ + notify_emails = [gbr.email_address for gbr in self.ghostbusters if gbr.email_address] notify_emails.extend( [acl.user.email for acl in ConfACL.objects.filter(conf=self) if acl.user.email] @@ -209,7 +210,6 @@ class ResourceCert(models.Model): """Represents a resource certificate. This model is used to cache the output of <list_received_resources/>. - """ # Handle to which this cert was issued @@ -237,6 +237,7 @@ class ResourceCert(models.Model): def get_cert_chain(self): """Return a list containing the complete certificate chain for this certificate.""" + cert = self x = [cert] while cert.issuer: @@ -410,7 +411,6 @@ class RouteOriginV6(rpki.gui.routeview.models.RouteOriginV6): class ConfACL(models.Model): """Stores access control for which users are allowed to manage a given resource handle. - """ conf = models.ForeignKey(Conf) diff --git a/rpki/gui/app/range_list.py b/rpki/gui/app/range_list.py index 21fd1f29..5cb4f5e4 100755 --- a/rpki/gui/app/range_list.py +++ b/rpki/gui/app/range_list.py @@ -70,6 +70,7 @@ class RangeList(list): def difference(self, other): """Return a RangeList object which contains ranges in this object which are not in "other".""" + it = iter(other) try: @@ -85,6 +86,7 @@ class RangeList(list): def V(v): """convert the integer value to the appropriate type for this range""" + return x.__class__.datum_type(v) try: diff --git a/rpki/gui/app/views.py b/rpki/gui/app/views.py index 9a1c4cfe..228d5c6c 100644 --- a/rpki/gui/app/views.py +++ b/rpki/gui/app/views.py @@ -71,6 +71,7 @@ def superuser_required(f): def get_conf(user, handle): """return the Conf object for 'handle'. user is a request.user object to use enforce ACLs.""" + if user.is_superuser: qs = models.Conf.objects.all() else: @@ -81,8 +82,8 @@ def get_conf(user, handle): def handle_required(f): """Decorator for view functions which require the user to be logged in and a resource handle selected for the session. - """ + @login_required @tls_required def wrapped_fn(request, *args, **kwargs): @@ -126,8 +127,8 @@ def generic_import(request, queryset, configure, form_class=None, if None (default), the user will be redirected to the detail page for the imported object. Otherwise, the user will be redirected to the specified URL. - """ + conf = get_conf(request.user, request.session['handle']) if form_class is None: form_class = forms.ImportForm @@ -251,6 +252,7 @@ def dashboard(request): @login_required def conf_list(request, **kwargs): """Allow the user to select a handle.""" + log = request.META['wsgi.errors'] next_url = request.GET.get('next', reverse(dashboard)) if request.user.is_superuser: @@ -266,6 +268,7 @@ def conf_list(request, **kwargs): @login_required def conf_select(request): """Change the handle for the current session.""" + if not 'handle' in request.GET: return redirect(conf_list) handle = request.GET['handle'] @@ -288,8 +291,8 @@ def serve_xml(content, basename, ext='xml'): `basename` is the prefix to specify for the XML filename. `csv` is the type (default: xml) - """ + resp = http.HttpResponse(content, mimetype='application/%s' % ext) resp['Content-Disposition'] = 'attachment; filename=%s.%s' % (basename, ext) return resp @@ -298,6 +301,7 @@ def serve_xml(content, basename, ext='xml'): @handle_required def conf_export(request): """Return the identity.xml for the current handle.""" + conf = get_conf(request.user, request.session['handle']) z = Zookeeper(handle=conf.handle) xml = z.generate_identity() @@ -307,6 +311,7 @@ def conf_export(request): @handle_required def export_asns(request): """Export CSV file containing ASN allocations to children.""" + conf = get_conf(request.user, request.session['handle']) s = cStringIO.StringIO() csv_writer = csv.writer(s, delimiter=' ') @@ -342,6 +347,7 @@ def import_asns(request): @handle_required def export_prefixes(request): """Export CSV file containing ASN allocations to children.""" + conf = get_conf(request.user, request.session['handle']) s = cStringIO.StringIO() csv_writer = csv.writer(s, delimiter=' ') @@ -411,6 +417,7 @@ def parent_delete(request, pk): @handle_required def parent_export(request, pk): """Export XML repository request for a given parent.""" + conf = get_conf(request.user, request.session['handle']) parent = get_object_or_404(conf.parents, pk=pk) z = Zookeeper(handle=conf.handle) @@ -474,6 +481,7 @@ def child_detail(request, pk): @handle_required def child_edit(request, pk): """Edit the end validity date for a resource handle's child.""" + log = request.META['wsgi.errors'] conf = get_conf(request.user, request.session['handle']) child = get_object_or_404(conf.children.all(), pk=pk) @@ -505,8 +513,8 @@ def child_response(request, pk): """ Export the XML file containing the output of the configure_child to send back to the client. - """ + conf = get_conf(request.user, request.session['handle']) child = get_object_or_404(models.Child, issuer=conf, pk=pk) z = Zookeeper(handle=conf.handle) @@ -551,7 +559,6 @@ def get_covered_routes(rng, max_prefixlen, asn): A "newstatus" attribute is monkey-patched on the RouteOrigin objects which can be used in the template. "status" remains the current validation status of the object. - """ # find all routes that match or are completed covered by the proposed new roa @@ -591,7 +598,6 @@ def roa_create(request): Doesn't use the generic create_object() form because we need to create both the ROARequest and ROARequestPrefix objects. - """ conf = get_conf(request.user, request.session['handle']) @@ -628,7 +634,6 @@ def roa_create(request): class ROARequestFormSet(BaseFormSet): """There is no way to pass arbitrary keyword arguments to the form constructor, so we have to override BaseFormSet to allow it. - """ def __init__(self, *args, **kwargs): self.conf = kwargs.pop('conf') @@ -666,7 +671,6 @@ def roa_create_multi(request): ?roa=1.1.1.1-2.2.2.2,42 The ASN may optionally be omitted. - """ conf = get_conf(request.user, request.session['handle']) @@ -717,8 +721,8 @@ def roa_create_multi(request): def roa_create_confirm(request): """This function is called when the user confirms the creation of a ROA request. It is responsible for updating the IRDB. - """ + conf = get_conf(request.user, request.session['handle']) log = request.META['wsgi.errors'] if request.method == 'POST': @@ -746,8 +750,8 @@ def roa_create_confirm(request): def roa_create_multi_confirm(request): """This function is called when the user confirms the creation of a ROA request. It is responsible for updating the IRDB. - """ + conf = get_conf(request.user, request.session['handle']) log = request.META['wsgi.errors'] if request.method == 'POST': @@ -778,7 +782,6 @@ def roa_delete(request, pk): Uses a form for double confirmation, displaying how the route validation status may change as a result. - """ conf = get_conf(request.user, request.session['handle']) @@ -835,6 +838,7 @@ def roa_clone(request, pk): @handle_required def roa_import(request): """Import CSV containing ROA declarations.""" + if request.method == 'POST': form = forms.ImportCSVForm(request.POST, request.FILES) if form.is_valid(): @@ -860,6 +864,7 @@ def roa_import(request): @handle_required def roa_export(request): """Export CSV containing ROA declarations.""" + # FIXME: remove when Zookeeper can do this f = cStringIO.StringIO() csv_writer = csv.writer(f, delimiter=' ') @@ -941,8 +946,8 @@ def ghostbuster_edit(request, pk): def refresh(request): """ Query rpkid, update the db, and redirect back to the dashboard. - """ + conf = get_conf(request.user, request.session['handle']) glue.list_received_resources(request.META['wsgi.errors'], conf) return http.HttpResponseRedirect(reverse(dashboard)) @@ -953,8 +958,8 @@ def route_view(request): """ Display a list of global routing table entries which match resources listed in received certificates. - """ + conf = get_conf(request.user, request.session['handle']) count = request.GET.get('count', 25) page = request.GET.get('page', 1) @@ -972,6 +977,7 @@ def route_view(request): def route_detail(request, pk): """Show a list of ROAs that match a given IPv4 route.""" + route = get_object_or_404(models.RouteOrigin, pk=pk) # when running rootd, viewing the 0.0.0.0/0 route will cause a fetch of all # roas, so we paginate here, even though in the general case the number of @@ -989,8 +995,8 @@ def route_suggest(request): """Handles POSTs from the route view and redirects to the ROA creation page based on selected route objects. The form should contain elements of the form "pk-NUM" where NUM is the RouteOrigin object id. - """ + if request.method == 'POST': routes = [] for pk in request.POST.iterkeys(): @@ -1040,6 +1046,7 @@ def repository_delete(request, pk): @handle_required def repository_import(request): """Import XML response file from repository operator.""" + return generic_import(request, models.Repository.objects, Zookeeper.configure_repository, @@ -1094,8 +1101,8 @@ def client_import(request): def client_export(request, pk): """Return the XML file resulting from a configure_publication_client request. - """ + client = get_object_or_404(models.Client, pk=pk) z = Zookeeper() xml = z.generate_repository_response(client) @@ -1107,6 +1114,7 @@ def client_export(request, pk): @superuser_required def resource_holder_list(request): """Display a list of all the RPKI handles managed by this server.""" + return render(request, 'app/resource_holder_list.html', { 'object_list': models.Conf.objects.all() }) @@ -1115,6 +1123,7 @@ def resource_holder_list(request): @superuser_required def resource_holder_edit(request, pk): """Display a list of all the RPKI handles managed by this server.""" + conf = get_object_or_404(models.Conf, pk=pk) if request.method == 'POST': form = forms.ResourceHolderForm(request.POST, request.FILES) @@ -1221,6 +1230,7 @@ def user_create(request): @superuser_required def user_list(request): """Display a list of all the RPKI handles managed by this server.""" + return render(request, 'app/user_list.html', { 'object_list': User.objects.all() }) @@ -1316,6 +1326,7 @@ class AlertDeleteView(DeleteView): @handle_required def alert_clear_all(request): """Clear all alerts associated with the current resource holder.""" + if request.method == 'POST': form = forms.Empty(request.POST, request.FILES) if form.is_valid(): diff --git a/rpki/gui/cacheview/models.py b/rpki/gui/cacheview/models.py index c3ee8421..08acfa2d 100644 --- a/rpki/gui/cacheview/models.py +++ b/rpki/gui/cacheview/models.py @@ -58,6 +58,7 @@ class ValidationLabel(models.Model): Represents a specific error condition defined in the rcynic XML output file. """ + label = models.CharField(max_length=79, db_index=True, unique=True) status = models.CharField(max_length=255) kind = models.PositiveSmallIntegerField(choices=kinds) @@ -70,6 +71,7 @@ class RepositoryObject(models.Model): """ Represents a globally unique RPKI repository object, specified by its URI. """ + uri = models.URLField(unique=True, db_index=True) generations = list(enumerate(('current', 'backup'))) @@ -89,6 +91,7 @@ class SignedObject(models.Model): The signing certificate is ommitted here in order to give a proper value for the 'related_name' attribute. """ + repo = models.ForeignKey(RepositoryObject, related_name='cert', unique=True) # on-disk file modification time @@ -108,6 +111,7 @@ class SignedObject(models.Model): """ convert the local timestamp to UTC and convert to a datetime object """ + return datetime.utcfromtimestamp(self.mtime + time.timezone) def status_id(self): @@ -116,6 +120,7 @@ class SignedObject(models.Model): The selector is chosen based on the current generation only. If there is any bad status, return bad, else if there are any warn status, return warn, else return good. """ + for x in reversed(kinds): if self.repo.statuses.filter(generation=generations_dict['current'], status__kind=x[0]): return x[1] @@ -129,6 +134,7 @@ class Cert(SignedObject): """ Object representing a resource certificate. """ + addresses = models.ManyToManyField(AddressRange, related_name='certs') addresses_v6 = models.ManyToManyField(AddressRangeV6, related_name='certs') asns = models.ManyToManyField(ASRange, related_name='certs') @@ -141,6 +147,7 @@ class Cert(SignedObject): def get_cert_chain(self): """Return a list containing the complete certificate chain for this certificate.""" + cert = self x = [cert] while cert != cert.issuer: @@ -180,6 +187,7 @@ class ROAPrefixV4(ROAPrefix, rpki.gui.models.PrefixV4): @property def routes(self): """return all routes covered by this roa prefix""" + return RouteOrigin.objects.filter(prefix_min__gte=self.prefix_min, prefix_max__lte=self.prefix_max) diff --git a/rpki/gui/cacheview/tests.py b/rpki/gui/cacheview/tests.py index 2247054b..daca07bf 100644 --- a/rpki/gui/cacheview/tests.py +++ b/rpki/gui/cacheview/tests.py @@ -12,6 +12,7 @@ class SimpleTest(TestCase): """ Tests that 1 + 1 always equals 2. """ + self.failUnlessEqual(1 + 1, 2) __test__ = {"doctest": """ diff --git a/rpki/gui/cacheview/util.py b/rpki/gui/cacheview/util.py index 9e8748bf..31ad8b8b 100644 --- a/rpki/gui/cacheview/util.py +++ b/rpki/gui/cacheview/util.py @@ -310,8 +310,8 @@ def fetch_published_objects(): """Query rpkid for all objects published by local users, and look up the current validation status of each object. The validation status is used later to send alerts for objects which have transitioned to invalid. - """ + logger.info('querying for published objects') handles = [conf.handle for conf in Conf.objects.all()] @@ -353,7 +353,6 @@ class Handle(object): def notify_invalid(): """Send email alerts to the addresses registered in ghostbuster records for any invalid objects that were published by users of this system. - """ logger.info('sending notifications for invalid objects') diff --git a/rpki/gui/cacheview/views.py b/rpki/gui/cacheview/views.py index 94870eb2..451c0d1e 100644 --- a/rpki/gui/cacheview/views.py +++ b/rpki/gui/cacheview/views.py @@ -29,6 +29,7 @@ def cert_chain(obj): """ returns an iterator covering all certs from the root cert down to the EE. """ + chain = [obj] while obj != obj.issuer: obj = obj.issuer diff --git a/rpki/gui/decorators.py b/rpki/gui/decorators.py index 69d20c46..ed10f3d9 100644 --- a/rpki/gui/decorators.py +++ b/rpki/gui/decorators.py @@ -20,8 +20,8 @@ from django import http def tls_required(f): """Decorator which returns a 500 error if the connection is not secured with TLS (https). - """ + def _tls_required(request, *args, **kwargs): if not request.is_secure(): return http.HttpResponseServerError( diff --git a/rpki/gui/default_settings.py b/rpki/gui/default_settings.py index 3859247c..e0626965 100644 --- a/rpki/gui/default_settings.py +++ b/rpki/gui/default_settings.py @@ -93,6 +93,7 @@ TIME_ZONE = select_tz() def get_secret_key(): """Retrieve the secret-key value from rpki.conf or generate a random value if it is not present.""" + d = string.letters + string.digits val = ''.join([random.choice(d) for _ in range(50)]) return rpki_config.get('secret-key', val) diff --git a/rpki/gui/models.py b/rpki/gui/models.py index 184383c0..62400d2a 100644 --- a/rpki/gui/models.py +++ b/rpki/gui/models.py @@ -42,8 +42,8 @@ class IPv6AddressField(models.Field): """ Note that we add a custom conversion to encode long values as hex strings in SQL statements. See settings.get_conv() for details. - """ + return value.toBytes() @@ -82,6 +82,7 @@ class Prefix(models.Model): """ Returns the prefix as a rpki.resource_set.resource_range_ip object. """ + return self.range_cls(self.prefix_min, self.prefix_max) @property @@ -96,6 +97,7 @@ class Prefix(models.Model): def __unicode__(self): """This method may be overridden by subclasses. The default implementation calls get_prefix_display(). """ + return self.get_prefix_display() class Meta: diff --git a/rpki/gui/routeview/api.py b/rpki/gui/routeview/api.py index cf699c9a..b4ff297a 100644 --- a/rpki/gui/routeview/api.py +++ b/rpki/gui/routeview/api.py @@ -29,8 +29,8 @@ def route_list(request): By default, only returns up to 10 matching routes, but the client may request a different limit with the 'count=' query string parameter. - """ + hard_limit = 100 if request.method == 'GET' and 'prefix__in' in request.GET: diff --git a/rpki/gui/routeview/util.py b/rpki/gui/routeview/util.py index 54d50f24..a2b515c8 100644 --- a/rpki/gui/routeview/util.py +++ b/rpki/gui/routeview/util.py @@ -179,8 +179,8 @@ def import_routeviews_dump(filename=DEFAULT_URL, filetype='auto'): filename [optional]: the full path to the downloaded file to parse filetype [optional]: 'text' or 'mrt' - """ + start_time = time.time() if filename.startswith('http://'): diff --git a/rpki/gui/script_util.py b/rpki/gui/script_util.py index c3a864fd..46545d83 100644 --- a/rpki/gui/script_util.py +++ b/rpki/gui/script_util.py @@ -28,6 +28,7 @@ def setup(): """ Configure Django enough to use the ORM. """ + cfg = config.parser(section='web_portal') # INSTALLED_APPS doesn't seem necessary so long as you are only accessing # existing tables. diff --git a/rpki/http.py b/rpki/http.py index 546dd310..e41b0080 100644 --- a/rpki/http.py +++ b/rpki/http.py @@ -112,6 +112,7 @@ def supported_address_families(enable_ipv6): IP address families on which servers should listen, and to consider when selecting addresses for client connections. """ + if enable_ipv6 and have_ipv6: return (socket.AF_INET, socket.AF_INET6) else: @@ -121,6 +122,7 @@ def localhost_addrinfo(): """ Return pseudo-getaddrinfo results for localhost. """ + result = [(socket.AF_INET, "127.0.0.1")] if enable_ipv6_clients and have_ipv6: result.append((socket.AF_INET6, "::1")) @@ -144,6 +146,7 @@ class http_message(object): Clean up (some of) the horrible messes that HTTP allows in its headers. """ + if headers is None: headers = () if self.headers is None else self.headers.items() translate_underscore = True @@ -166,6 +169,7 @@ class http_message(object): """ Parse and normalize an incoming HTTP message. """ + self = cls() headers = headers.split("\r\n") self.parse_first_line(*headers.pop(0).split(None, 2)) @@ -180,6 +184,7 @@ class http_message(object): """ Format an outgoing HTTP message. """ + s = self.format_first_line() if self.body is not None: assert isinstance(self.body, str) @@ -198,6 +203,7 @@ class http_message(object): """ Parse HTTP version, raise an exception if we can't. """ + if version[:5] != "HTTP/": raise rpki.exceptions.HTTPBadVersion("Couldn't parse version %s" % version) self.version = tuple(int(i) for i in version[5:].split(".")) @@ -207,6 +213,7 @@ class http_message(object): """ Figure out whether this HTTP message encourages a persistent connection. """ + c = self.headers.get("Connection") if self.version == (1, 1): return c is None or "close" not in c.lower() @@ -233,6 +240,7 @@ class http_request(http_message): """ Parse first line of HTTP request message. """ + self.parse_version(version) self.cmd = cmd self.path = path @@ -242,6 +250,7 @@ class http_request(http_message): Format first line of HTTP request message, and set up the User-Agent header. """ + self.headers.setdefault("User-Agent", self.software_name) return "%s %s HTTP/%d.%d\r\n" % (self.cmd, self.path, self.version[0], self.version[1]) @@ -262,6 +271,7 @@ class http_response(http_message): """ Parse first line of HTTP response message. """ + self.parse_version(version) self.code = int(code) self.reason = reason @@ -271,6 +281,7 @@ class http_response(http_message): Format first line of HTTP response message, and set up Date and Server headers. """ + self.headers.setdefault("Date", time.strftime("%a, %d %b %Y %T GMT")) self.headers.setdefault("Server", self.software_name) return "HTTP/%d.%d %s %s\r\n" % (self.version[0], self.version[1], self.code, self.reason) @@ -319,6 +330,7 @@ class http_stream(asynchat.async_chat): """ (Re)start HTTP message parser, reset timer. """ + assert not self.buffer self.chunk_handler = None self.set_terminator("\r\n\r\n") @@ -330,6 +342,7 @@ class http_stream(asynchat.async_chat): stream's timeout value if we're doing timeouts, otherwise clear it. """ + if self.timeout is not None: self.logger.debug("Setting timeout %s", self.timeout) self.timer.set(self.timeout) @@ -341,6 +354,7 @@ class http_stream(asynchat.async_chat): """ Buffer incoming data from asynchat. """ + self.buffer.append(data) self.update_timeout() @@ -348,6 +362,7 @@ class http_stream(asynchat.async_chat): """ Consume data buffered from asynchat. """ + val = "".join(self.buffer) self.buffer = [] return val @@ -369,6 +384,7 @@ class http_stream(asynchat.async_chat): separate mechanisms (chunked, content-length, TCP close) is going to tell us how to find the end of the message body. """ + self.update_timeout() if self.chunk_handler: self.chunk_handler() @@ -392,6 +408,7 @@ class http_stream(asynchat.async_chat): stream up to read it; otherwise, this is the last chunk, so start the process of exiting the chunk decoder. """ + n = int(self.get_buffer().partition(";")[0], 16) self.logger.debug("Chunk length %s", n) if n: @@ -407,6 +424,7 @@ class http_stream(asynchat.async_chat): body of a chunked message (sic). Save it, and prepare to move on to the next chunk. """ + self.logger.debug("Chunk body") self.msg.body += self.buffer self.buffer = [] @@ -418,6 +436,7 @@ class http_stream(asynchat.async_chat): Consume the CRLF that terminates a chunk, reinitialize chunk decoder to be ready for the next chunk. """ + self.logger.debug("Chunk CRLF") s = self.get_buffer() assert s == "", "%r: Expected chunk CRLF, got '%s'" % (self, s) @@ -428,6 +447,7 @@ class http_stream(asynchat.async_chat): Consume chunk trailer, which should be empty, then (finally!) exit the chunk decoder and hand complete message off to the application. """ + self.logger.debug("Chunk trailer") s = self.get_buffer() assert s == "", "%r: Expected end of chunk trailers, got '%s'" % (self, s) @@ -438,6 +458,7 @@ class http_stream(asynchat.async_chat): """ Hand normal (not chunked) message off to the application. """ + self.msg.body = self.get_buffer() self.handle_message() @@ -447,6 +468,7 @@ class http_stream(asynchat.async_chat): whether it's one we should just pass along, otherwise log a stack trace and close the stream. """ + self.timer.cancel() etype = sys.exc_info()[0] if etype in (SystemExit, rpki.async.ExitNow): @@ -459,6 +481,7 @@ class http_stream(asynchat.async_chat): """ Inactivity timer expired, close connection with prejudice. """ + self.logger.debug("Timeout, closing") self.close() @@ -467,6 +490,7 @@ class http_stream(asynchat.async_chat): Wrapper around asynchat connection close handler, so that we can log the event, cancel timer, and so forth. """ + self.logger.debug("Close event in HTTP stream handler") self.timer.cancel() asynchat.async_chat.handle_close(self) @@ -497,12 +521,14 @@ class http_server(http_stream): Content-Length header (that is: this message will be the last one in this server stream). No special action required. """ + self.handle_message() def find_handler(self, path): """ Helper method to search self.handlers. """ + for s, h in self.handlers: if path.startswith(s): return h @@ -515,6 +541,7 @@ class http_server(http_stream): Content-Type, look for a handler, and if everything looks right, pass the message body, path, and a reply callback to the handler. """ + self.logger.debug("Received request %r", self.msg) if not self.msg.persistent: self.expect_close = True @@ -541,12 +568,14 @@ class http_server(http_stream): """ Send an error response to this request. """ + self.send_message(code = code, reason = reason) def send_reply(self, code, body = None, reason = "OK"): """ Send a reply to this request. """ + self.send_message(code = code, body = body, reason = reason) def send_message(self, code, reason = "OK", body = None): @@ -556,6 +585,7 @@ class http_server(http_stream): listen for next message; otherwise, queue up a close event for this stream so it will shut down once the reply has been sent. """ + self.logger.debug("Sending response %s %s", code, reason) if code >= 400: self.expect_close = True @@ -611,6 +641,7 @@ class http_listener(asyncore.dispatcher): Asyncore says we have an incoming connection, spawn an http_server stream for it and pass along all of our handler data. """ + try: res = self.accept() if res is None: @@ -627,6 +658,7 @@ class http_listener(asyncore.dispatcher): """ Asyncore signaled an error, pass it along or log it. """ + if sys.exc_info()[0] in (SystemExit, rpki.async.ExitNow): raise self.logger.exception("Error in HTTP listener") @@ -662,6 +694,7 @@ class http_client(http_stream): """ Create socket and request a connection. """ + if not use_adns: self.logger.debug("Not using ADNS") self.gotaddrinfo([(socket.AF_INET, self.host)]) @@ -678,12 +711,14 @@ class http_client(http_stream): Handle DNS lookup errors. For now, just whack the connection. Undoubtedly we should do something better with diagnostics here. """ + self.handle_error() def gotaddrinfo(self, addrinfo): """ Got address data from DNS, create socket and request connection. """ + try: self.af, self.address = random.choice(addrinfo) self.logger.debug("Connecting to AF %s host %s port %s addr %s", self.af, self.host, self.port, self.address) @@ -701,6 +736,7 @@ class http_client(http_stream): """ Asyncore says socket has connected. """ + self.logger.debug("Socket connected") self.set_state("idle") assert self.queue.client is self @@ -710,6 +746,7 @@ class http_client(http_stream): """ Set HTTP client connection state. """ + self.logger.debug("State transition %s => %s", self.state, state) self.state = state @@ -720,12 +757,14 @@ class http_client(http_stream): in this server stream). In this case we want to read until we reach the end of the data stream. """ + self.set_terminator(None) def send_request(self, msg): """ Queue up request message and kickstart connection. """ + self.logger.debug("Sending request %r", msg) assert self.state == "idle", "%r: state should be idle, is %s" % (self, self.state) self.set_state("request-sent") @@ -782,6 +821,7 @@ class http_client(http_stream): message now; if we were waiting for the response to a request we sent, signal the error. """ + http_stream.handle_close(self) self.logger.debug("State %s", self.state) if self.get_terminator() is None: @@ -796,6 +836,7 @@ class http_client(http_stream): Connection idle timer has expired. Shut down connection in any case, noisily if we weren't idle. """ + bad = self.state not in ("idle", "closing") if bad: self.logger.warning("Timeout while in state %s", self.state) @@ -813,6 +854,7 @@ class http_client(http_stream): Asyncore says something threw an exception. Log it, then shut down the connection and pass back the exception. """ + eclass, edata = sys.exc_info()[0:2] self.logger.warning("Error on HTTP client connection %s:%s %s %s", self.host, self.port, eclass, edata) http_stream.handle_error(self) @@ -840,6 +882,7 @@ class http_queue(object): """ Append http_request object(s) to this queue. """ + self.logger.debug("Adding requests %r", requests) self.queue.extend(requests) @@ -852,6 +895,7 @@ class http_queue(object): exception, or timeout) for the query currently in progress will call this method when it's time to kick out the next query. """ + try: if self.client is None: self.client = http_client(self, self.hostport) @@ -871,6 +915,7 @@ class http_queue(object): """ Kick out the next query in this queue, if any. """ + if self.queue: self.client.send_request(self.queue[0]) @@ -881,6 +926,7 @@ class http_queue(object): handling of what otherwise would be a nasty set of race conditions. """ + if client_ is self.client: self.logger.debug("Detaching client %r", client_) self.client = None @@ -1032,6 +1078,7 @@ class caller(object): """ Handle CMS-wrapped XML response message. """ + try: r_cms = self.proto.cms_msg(DER = r_der) r_msg = r_cms.unwrap((self.server_ta, self.server_cert)) diff --git a/rpki/ipaddrs.py b/rpki/ipaddrs.py index 68b2d27d..25eefd0d 100644 --- a/rpki/ipaddrs.py +++ b/rpki/ipaddrs.py @@ -61,6 +61,7 @@ class v4addr(long): """ Construct a v4addr object. """ + if isinstance(x, unicode): x = x.encode("ascii") if isinstance(x, str): @@ -72,6 +73,7 @@ class v4addr(long): """ Convert a v4addr object to a raw byte string. """ + return struct.pack("!I", long(self)) @classmethod @@ -79,12 +81,14 @@ class v4addr(long): """ Convert from a raw byte string to a v4addr object. """ + return cls(struct.unpack("!I", x)[0]) def __str__(self): """ Convert a v4addr object to string format. """ + return socket.inet_ntop(socket.AF_INET, self.to_bytes()) class v6addr(long): @@ -101,6 +105,7 @@ class v6addr(long): """ Construct a v6addr object. """ + if isinstance(x, unicode): x = x.encode("ascii") if isinstance(x, str): @@ -112,6 +117,7 @@ class v6addr(long): """ Convert a v6addr object to a raw byte string. """ + return struct.pack("!QQ", long(self) >> 64, long(self) & 0xFFFFFFFFFFFFFFFF) @classmethod @@ -119,6 +125,7 @@ class v6addr(long): """ Convert from a raw byte string to a v6addr object. """ + x = struct.unpack("!QQ", x) return cls((x[0] << 64) | x[1]) @@ -126,12 +133,14 @@ class v6addr(long): """ Convert a v6addr object to string format. """ + return socket.inet_ntop(socket.AF_INET6, self.to_bytes()) def parse(s): """ Parse a string as either an IPv4 or IPv6 address, and return object of appropriate class. """ + if isinstance(s, unicode): s = s.encode("ascii") return v6addr(s) if ":" in s else v4addr(s) diff --git a/rpki/irdb/models.py b/rpki/irdb/models.py index 6fa48c59..c5cf7f15 100644 --- a/rpki/irdb/models.py +++ b/rpki/irdb/models.py @@ -101,6 +101,7 @@ class SundialField(django.db.models.DateTimeField): """ A field type for our customized datetime objects. """ + __metaclass__ = django.db.models.SubfieldBase description = "A datetime type using our customized datetime objects" diff --git a/rpki/irdb/zookeeper.py b/rpki/irdb/zookeeper.py index 1e163a4d..0effe213 100644 --- a/rpki/irdb/zookeeper.py +++ b/rpki/irdb/zookeeper.py @@ -35,6 +35,7 @@ import rpki.left_right import rpki.x509 import rpki.async import rpki.irdb +import rpki.publication_control import django.db.transaction from lxml.etree import (Element, SubElement, ElementTree, @@ -148,7 +149,6 @@ class etree_wrapper(object): """ Wrapper for ETree objects so we can return them as function results without requiring the caller to understand much about them. - """ def __init__(self, e, msg = None, debug = False): @@ -533,13 +533,8 @@ class Zookeeper(object): if self.run_pubd: updates = [] - updates.append( - rpki.publication.config_elt.make_pdu( - action = "set", - bpki_crl = self.server_ca.latest_crl)) - updates.extend( - rpki.publication.client_elt.make_pdu( + rpki.publication_control.client_elt.make_pdu( action = "set", client_handle = client.handle, bpki_cert = client.certificate) @@ -1141,9 +1136,9 @@ class Zookeeper(object): clear_replay_protection = "yes") for ca in rpki.irdb.ResourceHolderCA.objects.all()) if self.run_pubd: - self.call_pubd(rpki.publication.client_elt.make_pdu(action = "set", - client_handle = client.handle, - clear_replay_protection = "yes") + self.call_pubd(rpki.publication_control.client_elt.make_pdu(action = "set", + client_handle = client.handle, + clear_replay_protection = "yes") for client in self.server_ca.clients.all()) @@ -1170,7 +1165,7 @@ class Zookeeper(object): pdus = pdus[0] call_pubd = rpki.async.sync_wrapper(rpki.http.caller( - proto = rpki.publication, + proto = rpki.publication_control, client_key = irbe.private_key, client_cert = irbe.certificate, server_ta = self.server_ca.certificate, @@ -1187,11 +1182,11 @@ class Zookeeper(object): throw exceptions as needed. """ - if any(isinstance(pdu, (rpki.left_right.report_error_elt, rpki.publication.report_error_elt)) for pdu in pdus): + if any(isinstance(pdu, (rpki.left_right.report_error_elt, rpki.publication_control.report_error_elt)) for pdu in pdus): for pdu in pdus: if isinstance(pdu, rpki.left_right.report_error_elt): self.log("rpkid reported failure: %s" % pdu.error_code) - elif isinstance(pdu, rpki.publication.report_error_elt): + elif isinstance(pdu, rpki.publication_control.report_error_elt): self.log("pubd reported failure: %s" % pdu.error_code) else: continue @@ -1527,16 +1522,10 @@ class Zookeeper(object): if not self.run_pubd: return - # Make sure that pubd's BPKI CRL is up to date. - - self.call_pubd(rpki.publication.config_elt.make_pdu( - action = "set", - bpki_crl = self.server_ca.latest_crl)) - # See what pubd already has on file - pubd_reply = self.call_pubd(rpki.publication.client_elt.make_pdu(action = "list")) - client_pdus = dict((x.client_handle, x) for x in pubd_reply if isinstance(x, rpki.publication.client_elt)) + pubd_reply = self.call_pubd(rpki.publication_control.client_elt.make_pdu(action = "list")) + client_pdus = dict((x.client_handle, x) for x in pubd_reply if isinstance(x, rpki.publication_control.client_elt)) pubd_query = [] # Check all clients @@ -1548,7 +1537,7 @@ class Zookeeper(object): if (client_pdu is None or client_pdu.base_uri != client.sia_base or client_pdu.bpki_cert != client.certificate): - pubd_query.append(rpki.publication.client_elt.make_pdu( + pubd_query.append(rpki.publication_control.client_elt.make_pdu( action = "create" if client_pdu is None else "set", client_handle = client.handle, bpki_cert = client.certificate, @@ -1556,7 +1545,7 @@ class Zookeeper(object): # Delete any unknown clients - pubd_query.extend(rpki.publication.client_elt.make_pdu( + pubd_query.extend(rpki.publication_control.client_elt.make_pdu( action = "destroy", client_handle = p) for p in client_pdus) # If we changed anything, ship updates off to pubd diff --git a/rpki/left_right.py b/rpki/left_right.py index 68ead08f..e4cf25fe 100644 --- a/rpki/left_right.py +++ b/rpki/left_right.py @@ -67,6 +67,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name """ Fetch self object to which this object links. """ + return self_elt.sql_fetch(self.gctx, self.self_id) @property @@ -75,12 +76,14 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name """ Return BSC object to which this object links. """ + return bsc_elt.sql_fetch(self.gctx, self.bsc_id) def make_reply_clone_hook(self, r_pdu): """ Set handles when cloning, including _id -> _handle translation. """ + if r_pdu.self_handle is None: r_pdu.self_handle = self.self_handle for tag, elt in self.handles: @@ -97,6 +100,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name """ Find an object based on its handle. """ + return cls.sql_fetch_where1(gctx, cls.element_name + "_handle = %s AND self_id = %s", (handle, self_id)) def serve_fetch_one_maybe(self): @@ -104,6 +108,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name Find the object on which a get, set, or destroy method should operate, or which would conflict with a create method. """ + where = "%s.%s_handle = %%s AND %s.self_id = self.self_id AND self.self_handle = %%s" % ((self.element_name,) * 3) args = (getattr(self, self.element_name + "_handle"), self.self_handle) return self.sql_fetch_where1(self.gctx, where, args, "self") @@ -112,6 +117,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name """ Find the objects on which a list method should operate. """ + where = "%s.self_id = self.self_id and self.self_handle = %%s" % self.element_name return self.sql_fetch_where(self.gctx, where, (self.self_handle,), "self") @@ -124,6 +130,7 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name operations, self is the pre-existing object from SQL and q_pdu is the set request received from the the IRBE. """ + for tag, elt in self.handles: id_name = tag + "_id" if getattr(self, id_name, None) is None: @@ -171,6 +178,7 @@ class self_elt(data_elt): """ Fetch all BSC objects that link to this self object. """ + return bsc_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @property @@ -178,6 +186,7 @@ class self_elt(data_elt): """ Fetch all repository objects that link to this self object. """ + return repository_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @property @@ -185,6 +194,7 @@ class self_elt(data_elt): """ Fetch all parent objects that link to this self object. """ + return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @property @@ -192,6 +202,7 @@ class self_elt(data_elt): """ Fetch all child objects that link to this self object. """ + return child_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @property @@ -199,6 +210,7 @@ class self_elt(data_elt): """ Fetch all ROA objects that link to this self object. """ + return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @property @@ -206,6 +218,7 @@ class self_elt(data_elt): """ Fetch all Ghostbuster record objects that link to this self object. """ + return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @property @@ -213,6 +226,7 @@ class self_elt(data_elt): """ Fetch all EE certificate objects that link to this self object. """ + return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @@ -220,6 +234,7 @@ class self_elt(data_elt): """ Extra server actions for self_elt. """ + actions = [] if q_pdu.rekey: actions.append(self.serve_rekey) @@ -243,6 +258,7 @@ class self_elt(data_elt): """ Handle a left-right rekey action for this self. """ + def loop(iterator, parent): parent.serve_rekey(iterator, eb) rpki.async.iterator(self.parents, loop, cb) @@ -251,6 +267,7 @@ class self_elt(data_elt): """ Handle a left-right revoke action for this self. """ + def loop(iterator, parent): parent.serve_revoke(iterator, eb) rpki.async.iterator(self.parents, loop, cb) @@ -259,6 +276,7 @@ class self_elt(data_elt): """ Handle a left-right reissue action for this self. """ + def loop(iterator, parent): parent.serve_reissue(iterator, eb) rpki.async.iterator(self.parents, loop, cb) @@ -267,6 +285,7 @@ class self_elt(data_elt): """ Handle a left-right revoke_forgotten action for this self. """ + def loop(iterator, parent): parent.serve_revoke_forgotten(iterator, eb) rpki.async.iterator(self.parents, loop, cb) @@ -275,6 +294,7 @@ class self_elt(data_elt): """ Handle a left-right clear_replay_protection action for this self. """ + def loop(iterator, obj): obj.serve_clear_replay_protection(iterator, eb) rpki.async.iterator(self.parents + self.children + self.repositories, loop, cb) @@ -283,6 +303,7 @@ class self_elt(data_elt): """ Extra cleanup actions when destroying a self_elt. """ + def loop(iterator, parent): parent.delete(iterator) rpki.async.iterator(self.parents, loop, cb) @@ -291,45 +312,44 @@ class self_elt(data_elt): def serve_publish_world_now(self, cb, eb): """ Handle a left-right publish_world_now action for this self. - - The publication stuff needs refactoring, right now publication is - interleaved with local operations in a way that forces far too - many bounces through the task system for any complex update. The - whole thing ought to be rewritten to queue up outgoing publication - PDUs and only send them when we're all done or when we need to - force publication at a particular point in a multi-phase operation. - - Once that reorganization has been done, this method should be - rewritten to reuse the low-level publish() methods that each - object will have...but we're not there yet. So, for now, we just - do this via brute force. Think of it as a trial version to see - whether we've identified everything that needs to be republished - for this operation. """ + publisher = rpki.rpkid.publication_queue() + def loop(iterator, parent): - q_msg = rpki.publication.msg.query() + repo = parent.repository for ca in parent.cas: ca_detail = ca.active_ca_detail if ca_detail is not None: - q_msg.append(rpki.publication.crl_elt.make_publish( - ca_detail.crl_uri, ca_detail.latest_crl)) - q_msg.append(rpki.publication.manifest_elt.make_publish( - ca_detail.manifest_uri, ca_detail.latest_manifest)) - q_msg.extend(rpki.publication.certificate_elt.make_publish( - c.uri, c.cert) for c in ca_detail.child_certs) - q_msg.extend(rpki.publication.roa_elt.make_publish( - r.uri, r.roa) for r in ca_detail.roas if r.roa is not None) - q_msg.extend(rpki.publication.ghostbuster_elt.make_publish( - g.uri, g.ghostbuster) for g in ca_detail.ghostbusters) - parent.repository.call_pubd(iterator, eb, q_msg) + publisher.queue( + uri = ca_detail.crl_uri, new_obj = ca_detail.latest_crl, repository = repo) + publisher.queue( + uri = ca_detail.manifest_uri, new_obj = ca_detail.latest_manifest, repository = repo) + for c in ca_detail.child_certs: + publisher.queue( + uri = c.uri, new_obj = c.cert, repository = repo) + for r in ca_detail.roas: + if r.roa is not None: + publisher.queue( + uri = r.uri, new_obj = r.roa, repository = repo) + for g in ca_detail.ghostbusters: + publisher.queue( + uri = g.uri, new_obj = g.ghostbuster, repository = repo) + for c in ca_detail.ee_certificates: + publisher.queue( + uri = c.uri, new_obj = c.cert, repository = repo) + iterator() - rpki.async.iterator(self.parents, loop, cb) + def done(): + publisher.call_pubd(cb, eb) + + rpki.async.iterator(self.parents, loop, done) def serve_run_now(self, cb, eb): """ Handle a left-right run_now action for this self. """ + logger.debug("Forced immediate run of periodic actions for self %s[%d]", self.self_handle, self.self_id) completion = rpki.rpkid_tasks.CompletionHandler(cb) @@ -342,6 +362,7 @@ class self_elt(data_elt): Find the self object upon which a get, set, or destroy action should operate, or which would conflict with a create method. """ + return self.serve_fetch_handle(self.gctx, None, self.self_handle) @classmethod @@ -349,6 +370,7 @@ class self_elt(data_elt): """ Find a self object based on its self_handle. """ + return cls.sql_fetch_where1(gctx, "self_handle = %s", (self_handle,)) def serve_fetch_all(self): @@ -357,6 +379,7 @@ class self_elt(data_elt): This is different from the list action for all other objects, where list only works within a given self_id context. """ + return self.sql_fetch_all(self.gctx) def schedule_cron_tasks(self, completion): @@ -428,6 +451,7 @@ class bsc_elt(data_elt): """ Fetch all repository objects that link to this BSC object. """ + return repository_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,)) @property @@ -435,6 +459,7 @@ class bsc_elt(data_elt): """ Fetch all parent objects that link to this BSC object. """ + return parent_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,)) @property @@ -442,6 +467,7 @@ class bsc_elt(data_elt): """ Fetch all child objects that link to this BSC object. """ + return child_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,)) def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb): @@ -449,6 +475,7 @@ class bsc_elt(data_elt): Extra server actions for bsc_elt -- handle key generation. For now this only allows RSA with SHA-256. """ + if q_pdu.generate_keypair: assert q_pdu.key_type in (None, "rsa") and q_pdu.hash_alg in (None, "sha256") self.private_key_id = rpki.x509.RSA.generate(keylength = q_pdu.key_length or 2048) @@ -492,12 +519,14 @@ class repository_elt(data_elt): """ Fetch all parent objects that link to this repository object. """ + return parent_elt.sql_fetch_where(self.gctx, "repository_id = %s", (self.repository_id,)) def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): """ Extra server actions for repository_elt. """ + actions = [] if q_pdu.clear_replay_protection: actions.append(self.serve_clear_replay_protection) @@ -509,6 +538,7 @@ class repository_elt(data_elt): """ Handle a left-right clear_replay_protection action for this repository. """ + self.last_cms_timestamp = None self.sql_mark_dirty() cb() @@ -518,6 +548,7 @@ class repository_elt(data_elt): """ Default handler for publication response PDUs. """ + pdu.raise_if_error() def call_pubd(self, callback, errback, q_msg, handlers = None): @@ -544,7 +575,7 @@ class repository_elt(data_elt): handlers = {} for q_pdu in q_msg: - logger.info("Sending %s %s to pubd", q_pdu.action, q_pdu.uri) + logger.info("Sending %r to pubd", q_pdu) bsc = self.bsc q_der = rpki.publication.cms_msg().wrap(q_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl) @@ -624,6 +655,7 @@ class parent_elt(data_elt): """ Fetch repository object to which this parent object links. """ + return repository_elt.sql_fetch(self.gctx, self.repository_id) @property @@ -631,12 +663,14 @@ class parent_elt(data_elt): """ Fetch all CA objects that link to this parent object. """ + return rpki.rpkid.ca_obj.sql_fetch_where(self.gctx, "parent_id = %s", (self.parent_id,)) def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): """ Extra server actions for parent_elt. """ + actions = [] if q_pdu.rekey: actions.append(self.serve_rekey) @@ -656,6 +690,7 @@ class parent_elt(data_elt): """ Handle a left-right rekey action for this parent. """ + def loop(iterator, ca): ca.rekey(iterator, eb) rpki.async.iterator(self.cas, loop, cb) @@ -664,6 +699,7 @@ class parent_elt(data_elt): """ Handle a left-right revoke action for this parent. """ + def loop(iterator, ca): ca.revoke(cb = iterator, eb = eb) rpki.async.iterator(self.cas, loop, cb) @@ -672,6 +708,7 @@ class parent_elt(data_elt): """ Handle a left-right reissue action for this parent. """ + def loop(iterator, ca): ca.reissue(cb = iterator, eb = eb) rpki.async.iterator(self.cas, loop, cb) @@ -680,6 +717,7 @@ class parent_elt(data_elt): """ Handle a left-right clear_replay_protection action for this parent. """ + self.last_cms_timestamp = None self.sql_mark_dirty() cb() @@ -860,6 +898,7 @@ class child_elt(data_elt): """ Fetch all child_cert objects that link to this child object. """ + return rpki.rpkid.child_cert_obj.fetch(self.gctx, self, ca_detail, ski, unique) @property @@ -867,6 +906,7 @@ class child_elt(data_elt): """ Fetch all child_cert objects that link to this child object. """ + return self.fetch_child_certs() @property @@ -874,12 +914,14 @@ class child_elt(data_elt): """ Fetch all parent objects that link to self object to which this child object links. """ + return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): """ Extra server actions for child_elt. """ + actions = [] if q_pdu.reissue: actions.append(self.serve_reissue) @@ -893,6 +935,7 @@ class child_elt(data_elt): """ Handle a left-right reissue action for this child. """ + publisher = rpki.rpkid.publication_queue() for child_cert in self.child_certs: child_cert.reissue(child_cert.ca_detail, publisher, force = True) @@ -902,6 +945,7 @@ class child_elt(data_elt): """ Handle a left-right clear_replay_protection action for this child. """ + self.last_cms_timestamp = None self.sql_mark_dirty() cb() @@ -910,6 +954,7 @@ class child_elt(data_elt): """ Fetch the CA corresponding to an up-down class_name. """ + if not class_name.isdigit(): raise rpki.exceptions.BadClassNameSyntax("Bad class name %s" % class_name) ca = rpki.rpkid.ca_obj.sql_fetch(self.gctx, long(class_name)) @@ -926,6 +971,7 @@ class child_elt(data_elt): """ Extra server actions when destroying a child_elt. """ + publisher = rpki.rpkid.publication_queue() for child_cert in self.child_certs: child_cert.revoke(publisher = publisher, @@ -989,6 +1035,7 @@ class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace): Handle <list_resources/> element. This requires special handling due to the data types of some of the attributes. """ + assert name == "list_resources", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) if isinstance(self.valid_until, str): @@ -1005,6 +1052,7 @@ class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace): Generate <list_resources/> element. This requires special handling due to the data types of some of the attributes. """ + elt = self.make_elt() if isinstance(self.valid_until, int): elt.set("valid_until", self.valid_until.toXMLtime()) @@ -1023,6 +1071,7 @@ class list_roa_requests_elt(rpki.xml_utils.base_elt, left_right_namespace): Handle <list_roa_requests/> element. This requires special handling due to the data types of some of the attributes. """ + assert name == "list_roa_requests", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) if self.ipv4 is not None: @@ -1068,6 +1117,7 @@ class list_ee_certificate_requests_elt(rpki.xml_utils.base_elt, left_right_names Handle <list_ee_certificate_requests/> element. This requires special handling due to the data types of some of the attributes. """ + if name not in self.elements: assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) @@ -1086,6 +1136,7 @@ class list_ee_certificate_requests_elt(rpki.xml_utils.base_elt, left_right_names """ Handle <pkcs10/> sub-element. """ + assert len(self.elements) == 1 if name == self.elements[0]: self.pkcs10 = rpki.x509.PKCS10(Base64 = text) @@ -1098,6 +1149,7 @@ class list_ee_certificate_requests_elt(rpki.xml_utils.base_elt, left_right_names Generate <list_ee_certificate_requests/> element. This requires special handling due to the data types of some of the attributes. """ + if isinstance(self.eku, (tuple, list)): self.eku = ",".join(self.eku) elt = self.make_elt() @@ -1128,6 +1180,7 @@ class list_published_objects_elt(rpki.xml_utils.text_elt, left_right_namespace): misnomer here, there's no action attribute and no dispatch, we just dump every published object for the specified <self/> and return. """ + for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents: for ca in parent.cas: ca_detail = ca.active_ca_detail @@ -1148,6 +1201,7 @@ class list_published_objects_elt(rpki.xml_utils.text_elt, left_right_namespace): """ Generate one reply PDU. """ + r_pdu = self.make_pdu(tag = self.tag, self_handle = self.self_handle, uri = uri, child_handle = child_handle) r_pdu.obj = obj.get_Base64() @@ -1172,6 +1226,7 @@ class list_received_resources_elt(rpki.xml_utils.base_elt, left_right_namespace) just dump a bunch of data about every certificate issued to us by one of our parents, then return. """ + for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents: for ca in parent.cas: ca_detail = ca.active_ca_detail @@ -1183,6 +1238,7 @@ class list_received_resources_elt(rpki.xml_utils.base_elt, left_right_namespace) """ Generate one reply PDU. """ + resources = cert.get_3779resources() return self.make_pdu( tag = self.tag, @@ -1216,6 +1272,7 @@ class report_error_elt(rpki.xml_utils.text_elt, left_right_namespace): """ Generate a <report_error/> element from an exception. """ + self = cls() self.self_handle = self_handle self.tag = tag diff --git a/rpki/pubd.py b/rpki/pubd.py index 79315a78..647b0f68 100644 --- a/rpki/pubd.py +++ b/rpki/pubd.py @@ -23,7 +23,9 @@ RPKI publication engine. import os import re +import uuid import time +import socket import logging import argparse import rpki.resource_set @@ -36,10 +38,31 @@ import rpki.exceptions import rpki.relaxng import rpki.log import rpki.publication +import rpki.publication_control import rpki.daemonize +from lxml.etree import Element, SubElement, tostring as ElementToString + logger = logging.getLogger(__name__) + +# Temporary, these should come from the schema or something +rrdp_xmlns = "{http://www.ripe.net/rpki/rrdp}" +rrdp_version = "1" +rrdp_nsmap = { None : rrdp_xmlns[1:-1] } + + +def DERSubElement(elt, name, der, attrib = None, **kwargs): + """ + Convenience wrapper around SubElement for use with Base64 text. + """ + + se = SubElement(elt, name, attrib, **kwargs) + se.text = rpki.x509.base64_with_linebreaks(der) + se.tail = "\n" + return se + + class main(object): """ Main program for pubd. @@ -96,6 +119,7 @@ class main(object): self.irbe_cert = rpki.x509.X509(Auto_update = self.cfg.get("irbe-cert")) self.pubd_cert = rpki.x509.X509(Auto_update = self.cfg.get("pubd-cert")) self.pubd_key = rpki.x509.RSA( Auto_update = self.cfg.get("pubd-key")) + self.pubd_crl = rpki.x509.CRL( Auto_update = self.cfg.get("pubd-crl")) self.http_server_host = self.cfg.get("server-host", "") self.http_server_port = self.cfg.getint("server-port") @@ -104,45 +128,40 @@ class main(object): self.publication_multimodule = self.cfg.getboolean("publication-multimodule", False) + self.rrdp_uri_base = self.cfg.get("rrdp-uri-base", "http://%s/" % socket.getfqdn()) + self.rrdp_expiration_interval = rpki.sundial.timedelta.parse(self.cfg.get("rrdp-expiration-interval", "6h")) + self.rrdp_publication_base = self.cfg.get("rrdp-publication-base", "rrdp-publication/") + + self.session = session_obj.fetch(self) + rpki.http.server( host = self.http_server_host, port = self.http_server_port, handlers = (("/control", self.control_handler), ("/client/", self.client_handler))) - def handler_common(self, query, client, cb, certs, crl = None): - """ - Common PDU handler code. - """ - - def done(r_msg): - reply = rpki.publication.cms_msg().wrap(r_msg, self.pubd_key, self.pubd_cert, crl) - self.sql.sweep() - cb(reply) - - q_cms = rpki.publication.cms_msg(DER = query) - q_msg = q_cms.unwrap(certs) - if client is None: - self.irbe_cms_timestamp = q_cms.check_replay(self.irbe_cms_timestamp, "control") - else: - q_cms.check_replay_sql(client, client.client_handle) - q_msg.serve_top_level(self, client, done) def control_handler(self, query, path, cb): """ Process one PDU from the IRBE. """ - def done(body): - cb(200, body = body) + def done(r_msg): + self.sql.sweep() + cb(code = 200, + body = rpki.publication_control.cms_msg().wrap(r_msg, self.pubd_key, self.pubd_cert)) try: - self.handler_common(query, None, done, (self.bpki_ta, self.irbe_cert)) + q_cms = rpki.publication_control.cms_msg(DER = query) + q_msg = q_cms.unwrap((self.bpki_ta, self.irbe_cert)) + self.irbe_cms_timestamp = q_cms.check_replay(self.irbe_cms_timestamp, "control") + q_msg.serve_top_level(self, done) except (rpki.async.ExitNow, SystemExit): raise except Exception, e: logger.exception("Unhandled exception processing control query, path %r", path) - cb(500, reason = "Unhandled exception %s: %s" % (e.__class__.__name__, e)) + cb(code = 500, reason = "Unhandled exception %s: %s" % (e.__class__.__name__, e)) + client_url_regexp = re.compile("/client/([-A-Z0-9_/]+)$", re.I) @@ -151,23 +170,306 @@ class main(object): Process one PDU from a client. """ - def done(body): - cb(200, body = body) - try: match = self.client_url_regexp.search(path) if match is None: raise rpki.exceptions.BadContactURL("Bad path: %s" % path) client_handle = match.group(1) - client = rpki.publication.client_elt.sql_fetch_where1(self, "client_handle = %s", (client_handle,)) + client = rpki.publication_control.client_elt.sql_fetch_where1(self, "client_handle = %s", (client_handle,)) if client is None: raise rpki.exceptions.ClientNotFound("Could not find client %s" % client_handle) - config = rpki.publication.config_elt.fetch(self) - if config is None or config.bpki_crl is None: - raise rpki.exceptions.CMSCRLNotSet - self.handler_common(query, client, done, (self.bpki_ta, client.bpki_cert, client.bpki_glue), config.bpki_crl) + q_cms = rpki.publication.cms_msg(DER = query) + q_msg = q_cms.unwrap((self.bpki_ta, client.bpki_cert, client.bpki_glue)) + q_cms.check_replay_sql(client, client.client_handle) + if not q_msg.is_query(): + raise rpki.exceptions.BadQuery("Message type is not query") + r_msg = q_msg.__class__.reply() + delta = self.session.new_delta() + failed = False + for q_pdu in q_msg: + try: + if isinstance(q_pdu, rpki.publication.list_elt): + for obj in client.published_objects: + r_pdu = q_pdu.__class__() + r_pdu.tag = q_pdu.tag + r_pdu.uri = obj.uri + r_pdu.hash = obj.hash + r_msg.append(r_pdu) + else: + q_pdu.gctx = self + q_pdu.client = client + q_pdu.client.check_allowed_uri(q_pdu.uri) + q_pdu.serve_action(delta) + r_pdu = q_pdu.__class__() + r_pdu.tag = q_pdu.tag + r_pdu.uri = q_pdu.uri + r_msg.append(r_pdu) + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, e: + if not isinstance(e, rpki.exceptions.NotFound): + logger.exception("Exception processing PDU %r", q_pdu) + r_msg.append(rpki.publication.report_error_elt.from_exception(e, q_pdu.tag)) + delta.sql_delete() + failed = True + # + # Need to check "failed" flag here? + # + delta.activate() + self.sql.sweep() + self.session.generate_snapshot() + self.session.write_snapshot() + self.session.write_deltas() + self.session.write_notification() + cb(code = 200, + body = rpki.publication.cms_msg().wrap(r_msg, self.pubd_key, self.pubd_cert, self.pubd_crl)) except (rpki.async.ExitNow, SystemExit): raise except Exception, e: logger.exception("Unhandled exception processing client query, path %r", path) - cb(500, reason = "Could not process PDU: %s" % e) + cb(code = 500, + reason = "Could not process PDU: %s" % e) + + +class session_obj(rpki.sql.sql_persistent): + """ + An RRDP session. + """ + + sql_template = rpki.sql.template( + "session", + "session_id", + "uuid", + "serial", + "snapshot", + "hash") + + def __repr__(self): + return rpki.log.log_repr(self, self.uuid, self.serial) + + @classmethod + def fetch(cls, gctx): + """ + Fetch the one and only session, creating it if necessary. + """ + + self = cls.sql_fetch(gctx, 1) + if self is None: + self = cls() + self.uuid = str(uuid.uuid4()) + self.serial = 0 + self.snapshot = None + self.hash = None + self.gctx = gctx + self.sql_store() + return self + + @property + def objects(self): + return object_obj.sql_fetch_where(self.gctx, "session_id = %s", (self.session_id,)) + + @property + def deltas(self): + return delta_obj.sql_fetch_where(self.gctx, "session_id = %s", (self.session_id,)) + + def new_delta(self): + return delta_obj.create(self) + + def expire_deltas(self): + for delta in delta_obj.sql_fetch_where(self.gctx, + "session_id = %s AND expires IS NOT NULL AND expires < %s", + (self.session_id, rpki.sundial.now())): + delta.sql_delete() + + def write_rrdp_file(self, fn, text, overwrite = False): + """ + Save RRDP XML to disk. + """ + + if overwrite or not os.path.exists(os.path.join(self.gctx.rrdp_publication_base, fn)): + tn = os.path.join(self.gctx.rrdp_publication_base, fn + ".%s.tmp" % os.getpid()) + if not os.path.isdir(os.path.dirname(tn)): + os.makedirs(os.path.dirname(tn)) + with open(tn, "w") as f: + f.write(text) + os.rename(tn, os.path.join(self.gctx.rrdp_publication_base, fn)) + + def generate_snapshot(self): + """ + Generate an XML snapshot of this session. + """ + + xml = Element(rrdp_xmlns + "snapshot", nsmap = rrdp_nsmap, + version = rrdp_version, + session_id = self.uuid, + serial = str(self.serial)) + xml.text = "\n" + for obj in self.objects: + DERSubElement(xml, rrdp_xmlns + "publish", + der = obj.der, + uri = obj.uri) + rpki.relaxng.rrdp.assertValid(xml) + self.snapshot = ElementToString(xml, pretty_print = True) + self.hash = rpki.x509.sha256(self.snapshot).encode("hex") + self.sql_store() + + def write_snapshot(self): + """ + Write current session snapshot to disk. + """ + + self.write_rrdp_file("%s/snapshot/%s.xml" % (self.uuid, self.serial), self.snapshot) + + def write_deltas(self): + """ + Write any missing deltas to disk. + """ + + for delta in self.deltas: + self.write_rrdp_file(delta.fn, delta.xml) + + def write_notification(self): + """ + Write current notification file to disk. + """ + + xml = Element(rrdp_xmlns + "notification", nsmap = rrdp_nsmap, + version = rrdp_version, + session_id = self.uuid, + serial = str(self.serial)) + SubElement(xml, rrdp_xmlns + "snapshot", + uri = "%s/%s/snapshot/%d.xml" % (self.gctx.rrdp_uri_base, self.uuid, self.serial), + hash = self.hash) + for delta in self.deltas: + se = SubElement(xml, rrdp_xmlns + "delta", + to = str(delta.serial), + uri = "%s/%s" % (self.gctx.rrdp_uri_base, delta.fn), + hash = delta.hash) + se.set("from", str(delta.serial - 1)) + rpki.relaxng.rrdp.assertValid(xml) + self.write_rrdp_file("%s/notification.xml" % self.uuid, + ElementToString(xml, pretty_print = True), + overwrite = True) + + +class delta_obj(rpki.sql.sql_persistent): + """ + An RRDP delta. + """ + + sql_template = rpki.sql.template( + "delta", + "delta_id", + "session_id", + "serial", + "xml", + "hash", + ("expires", rpki.sundial.datetime)) + + @property + @rpki.sql.cache_reference + def session(self): + return session_obj.sql_fetch(self.gctx, self.session_id) + + @property + def fn(self): + return "%s/deltas/%s-%s.xml" % (self.session.uuid, self.serial - 1, self.serial) + + @classmethod + def create(cls, session): + self = cls() + session.serial += 1 + session.sql_mark_dirty() + self.gctx = session.gctx + self.session_id = session.session_id + self.serial = session.serial + self.xml = None + self.hash = None + self.expires = rpki.sundial.now() + self.gctx.rrdp_expiration_interval + self.deltas = Element(rrdp_xmlns + "deltas", nsmap = rrdp_nsmap, + to = str(self.serial), + version = rrdp_version, + session_id = session.uuid) + self.deltas.set("from", str(self.serial - 1)) + SubElement(self.deltas, rrdp_xmlns + "delta", serial = str(self.serial)).text = "\n" + return self + + def activate(self): + rpki.relaxng.rrdp.assertValid(self.deltas) + self.xml = ElementToString(self.deltas, pretty_print = True) + self.hash = rpki.x509.sha256(self.xml).encode("hex") + del self.deltas + self.sql_mark_dirty() + + def publish(self, client, der, uri, hash): + if hash is not None: + self.withdraw(client, uri, hash) + elif object_obj.current_object_at_uri(client, self, uri) is not None: + raise rpki.exceptions.ExistingObjectAtURI("Object already published at %s" % uri) + logger.debug("Publishing %s", uri) + object_obj.create(client, self, der, uri) + se = DERSubElement(self.deltas[0], rrdp_xmlns + "publish", der = der, uri = uri) + if hash is not None: + se.set("hash", hash) + rpki.relaxng.rrdp.assertValid(self.deltas) + + def withdraw(self, client, uri, hash): + obj = object_obj.current_object_at_uri(client, self, uri) + if obj is None: + raise rpki.exceptions.NoObjectAtURI("No object published at %s" % uri) + if obj.hash != hash: + raise rpki.exceptions.DifferentObjectAtURI("Found different object at %s (old %s, new %s)" % (uri, obj.hash, hash)) + logger.debug("Withdrawing %s", uri) + obj.delete(self) + SubElement(self.deltas[0], rrdp_xmlns + "withdraw", uri = uri, hash = hash).tail = "\n" + rpki.relaxng.rrdp.assertValid(self.deltas) + + +class object_obj(rpki.sql.sql_persistent): + """ + A published object. + """ + + sql_template = rpki.sql.template( + "object", + "object_id", + "uri", + "der", + "hash", + "client_id", + "session_id") + + def __repr__(self): + return rpki.log.log_repr(self, self.uri) + + @property + @rpki.sql.cache_reference + def session(self): + return session_obj.sql_fetch(self.gctx, self.session_id) + + @property + @rpki.sql.cache_reference + def client(self): + return rpki.publication_control.client_elt.sql_fetch(self.gctx, self.client_id) + + @classmethod + def create(cls, client, delta, der, uri): + self = cls() + self.gctx = delta.gctx + self.uri = uri + self.der = der + self.hash = rpki.x509.sha256(der).encode("hex") + logger.debug("Computed hash %s for %s", self.hash, self.uri) + self.session_id = delta.session_id + self.client_id = client.client_id + self.sql_mark_dirty() + return self + + def delete(self, delta): + self.sql_mark_deleted() + + @classmethod + def current_object_at_uri(cls, client, delta, uri): + return cls.sql_fetch_where1(client.gctx, + "session_id = %s AND client_id = %s AND uri = %s", + (delta.session_id, client.client_id, uri)) diff --git a/rpki/publication.py b/rpki/publication.py index 5fc7f3dd..8970ff7a 100644 --- a/rpki/publication.py +++ b/rpki/publication.py @@ -1,35 +1,24 @@ # $Id$ # -# Copyright (C) 2009--2012 Internet Systems Consortium ("ISC") -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. -# +# Copyright (C) 2013--2014 Dragon Research Labs ("DRL") +# Portions copyright (C) 2009--2012 Internet Systems Consortium ("ISC") # Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. +# copyright notices and this permission notice appear in all copies. # -# THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. +# THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL +# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL, +# ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """ -RPKI "publication" protocol. +RPKI publication protocol. """ import os @@ -48,224 +37,117 @@ import rpki.log logger = logging.getLogger(__name__) -class publication_namespace(object): - """ - XML namespace parameters for publication protocol. - """ +class publication_namespace(object): xmlns = rpki.relaxng.publication.xmlns nsmap = rpki.relaxng.publication.nsmap -class control_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, publication_namespace): - """ - Virtual class for control channel objects. - """ - def serve_dispatch(self, r_msg, cb, eb): - """ - Action dispatch handler. This needs special handling because we - need to make sure that this PDU arrived via the control channel. - """ - if self.client is not None: - raise rpki.exceptions.BadQuery("Control query received on client channel") - rpki.xml_utils.data_elt.serve_dispatch(self, r_msg, cb, eb) - -class config_elt(control_elt): +class base_publication_elt(rpki.xml_utils.base_elt, publication_namespace): """ - <config/> element. This is a little weird because there should - never be more than one row in the SQL config table, but we have to - put the BPKI CRL somewhere and SQL is the least bad place available. - - So we reuse a lot of the SQL machinery, but we nail config_id at 1, - we don't expose it in the XML protocol, and we only support the get - and set actions. + Base element for publication protocol. Publish and withdraw PDUs subclass this. """ - attributes = ("action", "tag") - element_name = "config" - elements = ("bpki_crl",) + attributes = ("tag", "uri", "hash") - sql_template = rpki.sql.template( - "config", - "config_id", - ("bpki_crl", rpki.x509.CRL)) + tag = None + uri = None + der = None + hash = None + + _payload = None - wired_in_config_id = 1 + def __repr__(self): + return rpki.log.log_repr(self, self.tag, self.uri, self.hash, self.payload) - 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) + @property + def payload(self): + if self._payload is None and self.der is not None: + self._payload = rpki.x509.uri_dispatch(self.uri)(DER = self.der) + return self._payload - def serve_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): + def uri_to_filename(self): """ - Find the config object on which a get or set method should - operate. + Convert a URI to a local filename. """ - return self.sql_fetch(self.gctx, self.config_id) - -class client_elt(control_elt): - """ - <client/> element. - """ - element_name = "client" - attributes = ("action", "tag", "client_handle", "base_uri") - elements = ("bpki_cert", "bpki_glue") - booleans = ("clear_replay_protection",) - - sql_template = rpki.sql.template( - "client", - "client_id", - "client_handle", - "base_uri", - ("bpki_cert", rpki.x509.X509), - ("bpki_glue", rpki.x509.X509), - ("last_cms_timestamp", rpki.sundial.datetime)) - - base_uri = None - bpki_cert = None - bpki_glue = None - last_cms_timestamp = None - - def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Extra server actions for client_elt. - """ - actions = [] - if q_pdu.clear_replay_protection: - actions.append(self.serve_clear_replay_protection) - def loop(iterator, action): - action(iterator, eb) - rpki.async.iterator(actions, loop, cb) - - def serve_clear_replay_protection(self, cb, eb): - """ - Handle a clear_replay_protection action for this client. - """ - self.last_cms_timestamp = None - self.sql_mark_dirty() - cb() + 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 - def serve_fetch_one_maybe(self): + def raise_if_error(self): """ - Find the client object on which a get, set, or destroy method - should operate, or which would conflict with a create method. + No-op, since this is not a <report_error/> PDU. """ - return self.sql_fetch_where1(self.gctx, "client_handle = %s", (self.client_handle,)) - def serve_fetch_all(self): - """ - Find client objects on which a list method should operate. - """ - return self.sql_fetch_all(self.gctx) + pass - def check_allowed_uri(self, uri): - """ - Make sure that a target URI is within this client's allowed URI space. - """ - if not uri.startswith(self.base_uri): - raise rpki.exceptions.ForbiddenURI -class publication_object_elt(rpki.xml_utils.base_elt, publication_namespace): +class publish_elt(base_publication_elt): """ - Virtual class for publishable objects. These have very similar - syntax, differences lie in underlying datatype and methods. XML - methods are a little different from the pattern used for objects - that support the create/set/get/list/destroy actions, but - publishable objects don't go in SQL either so these classes would be - different in any case. + <publish/> element. """ - attributes = ("action", "tag", "client_handle", "uri") - payload_type = None - payload = None + element_name = "publish" def endElement(self, stack, name, text): """ - Handle a publishable element element. + Handle reading of the object to be published """ + assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) if text: - self.payload = self.payload_type(Base64 = text) # pylint: disable=E1102 + self.der = text.decode("base64") stack.pop() def toXML(self): """ Generate XML element for publishable object. """ + elt = self.make_elt() - if self.payload: - elt.text = self.payload.get_Base64() + if self.der is not None: + elt.text = self.der.encode("base64") return elt - def serve_dispatch(self, r_msg, cb, eb): - """ - Action dispatch handler. - """ - # pylint: disable=E0203 - try: - if self.client is None: - raise rpki.exceptions.BadQuery("Client query received on control channel") - dispatch = { "publish" : self.serve_publish, - "withdraw" : self.serve_withdraw } - if self.action not in dispatch: - raise rpki.exceptions.BadQuery("Unexpected query: action %s" % self.action) - self.client.check_allowed_uri(self.uri) - dispatch[self.action]() - r_pdu = self.__class__() - r_pdu.action = self.action - r_pdu.tag = self.tag - r_pdu.uri = self.uri - r_msg.append(r_pdu) - cb() - except rpki.exceptions.NoObjectAtURI, e: - # This can happen when we're cleaning up from a prior mess, so - # we generate a <report_error/> PDU then carry on. - r_msg.append(report_error_elt.from_exception(e, self.tag)) - cb() - - def serve_publish(self): + def serve_action(self, delta): """ Publish an object. """ + logger.info("Publishing %s", self.payload.tracking_data(self.uri)) + delta.publish(self.client, self.der, self.uri, self.hash) 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() + with open(filename_tmp, "wb") as f: + f.write(self.der) os.rename(filename_tmp, filename) - def serve_withdraw(self): + +class withdraw_elt(base_publication_elt): + """ + <withdraw/> element. + """ + + element_name = "withdraw" + + def serve_action(self, delta): """ Withdraw an object, then recursively delete empty directories. """ + logger.info("Withdrawing %s", self.uri) + delta.withdraw(self.client, self.uri, self.hash) filename = self.uri_to_filename() try: os.remove(filename) @@ -284,86 +166,14 @@ class publication_object_elt(rpki.xml_utils.base_elt, publication_namespace): else: dirname = os.path.dirname(dirname) - def uri_to_filename(self): - """ - Convert a URI to a local filename. - """ - if not self.uri.startswith("rsync://"): - raise rpki.exceptions.BadURISyntax(self.uri) - path = self.uri.split("/")[3:] - if not self.gctx.publication_multimodule: - del path[0] - path.insert(0, self.gctx.publication_base.rstrip("/")) - filename = "/".join(path) - if "/../" in filename or filename.endswith("/.."): - raise rpki.exceptions.BadURISyntax(filename) - return filename - - @classmethod - def make_publish(cls, uri, obj, tag = None): - """ - Construct a publication PDU. - """ - assert cls.payload_type is not None and type(obj) is cls.payload_type - return cls.make_pdu(action = "publish", uri = uri, payload = obj, tag = tag) - - @classmethod - def make_withdraw(cls, uri, obj, tag = None): - """ - Construct a withdrawal PDU. - """ - assert cls.payload_type is not None and type(obj) is cls.payload_type - return cls.make_pdu(action = "withdraw", uri = uri, tag = tag) - - def raise_if_error(self): - """ - No-op, since this is not a <report_error/> PDU. - """ - pass - -class certificate_elt(publication_object_elt): - """ - <certificate/> element. - """ - - element_name = "certificate" - payload_type = rpki.x509.X509 - -class crl_elt(publication_object_elt): - """ - <crl/> element. - """ - - element_name = "crl" - payload_type = rpki.x509.CRL - -class manifest_elt(publication_object_elt): - """ - <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 -class ghostbuster_elt(publication_object_elt): +class list_elt(base_publication_elt): """ - <ghostbuster/> element. + <list/> element. """ - element_name = "ghostbuster" - payload_type = rpki.x509.Ghostbuster + pass -publication_object_elt.obj2elt = dict( - (e.payload_type, e) for e in - (certificate_elt, crl_elt, manifest_elt, roa_elt, ghostbuster_elt)) class report_error_elt(rpki.xml_utils.text_elt, publication_namespace): """ @@ -374,13 +184,18 @@ class report_error_elt(rpki.xml_utils.text_elt, publication_namespace): attributes = ("tag", "error_code") text_attribute = "error_text" + error_code = None error_text = None + def __repr__(self): + return rpki.log.log_repr(self, self.error_code, self.error_text) + @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__ @@ -400,12 +215,14 @@ class report_error_elt(rpki.xml_utils.text_elt, publication_namespace): """ Raise exception associated with this <report_error/> PDU. """ + t = rpki.exceptions.__dict__.get(self.error_code) if isinstance(t, type) and issubclass(t, rpki.exceptions.RPKI_Exception): raise t(getattr(self, "text", None)) else: raise rpki.exceptions.BadPublicationReply("Unexpected response from pubd: %s" % self) + class msg(rpki.xml_utils.msg, publication_namespace): """ Publication PDU. @@ -417,38 +234,8 @@ class msg(rpki.xml_utils.msg, publication_namespace): ## @var pdus # Dispatch table of PDUs for this protocol. - pdus = dict((x.element_name, x) for x in - (config_elt, client_elt, certificate_elt, crl_elt, manifest_elt, roa_elt, ghostbuster_elt, report_error_elt)) - - def serve_top_level(self, gctx, client, cb): - """ - Serve one msg PDU. - """ - if not self.is_query(): - raise rpki.exceptions.BadQuery("Message type is not query") - r_msg = self.__class__.reply() - - def loop(iterator, q_pdu): - - def fail(e): - if not isinstance(e, rpki.exceptions.NotFound): - logger.exception("Exception processing PDU %r", q_pdu) - r_msg.append(report_error_elt.from_exception(e, q_pdu.tag)) - cb(r_msg) - - try: - q_pdu.gctx = gctx - q_pdu.client = client - q_pdu.serve_dispatch(r_msg, iterator, fail) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - fail(e) - - def done(): - cb(r_msg) + pdus = dict((x.element_name, x) for x in (publish_elt, withdraw_elt, report_error_elt)) - rpki.async.iterator(self, loop, done) class sax_handler(rpki.xml_utils.sax_handler): """ diff --git a/rpki/publication_control.py b/rpki/publication_control.py new file mode 100644 index 00000000..4ddcd8b2 --- /dev/null +++ b/rpki/publication_control.py @@ -0,0 +1,247 @@ +# $Id$ +# +# Copyright (C) 2009--2012 Internet Systems Consortium ("ISC") +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. +# +# 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. + +""" +RPKI publication control protocol. + +Per IETF SIDR WG discussion, this is now separate from the publication +protocol itself. +""" + +import logging +import rpki.resource_set +import rpki.x509 +import rpki.sql +import rpki.exceptions +import rpki.xml_utils +import rpki.http +import rpki.up_down +import rpki.relaxng +import rpki.sundial +import rpki.log + +logger = logging.getLogger(__name__) + + +class publication_control_namespace(object): + """ + XML namespace parameters for publication control protocol. + """ + + xmlns = rpki.relaxng.publication_control.xmlns + nsmap = rpki.relaxng.publication_control.nsmap + + +class client_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, publication_control_namespace): + """ + <client/> element. + """ + + element_name = "client" + attributes = ("action", "tag", "client_handle", "base_uri") + elements = ("bpki_cert", "bpki_glue") + booleans = ("clear_replay_protection",) + + sql_template = rpki.sql.template( + "client", + "client_id", + "client_handle", + "base_uri", + ("bpki_cert", rpki.x509.X509), + ("bpki_glue", rpki.x509.X509), + ("last_cms_timestamp", rpki.sundial.datetime)) + + base_uri = None + bpki_cert = None + bpki_glue = None + last_cms_timestamp = None + + def __repr__(self): + return rpki.log.log_repr(self, self.client_handle, self.base_uri) + + @property + def objects(self): + return rpki.pubd.object_obj.sql_fetch_where(self.gctx, "client_id = %s", (self.client_id,)) + + @property + def published_object(self): + return rpki.pubd.object_obj.sql_fetch_where(self.gctx, "client_id = %s AND withdrawn_snapshot_id IS NULL", (self.client_id,)) + + def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): + """ + Extra server actions for client_elt. + """ + + actions = [] + if q_pdu.clear_replay_protection: + actions.append(self.serve_clear_replay_protection) + def loop(iterator, action): + action(iterator, eb) + rpki.async.iterator(actions, loop, cb) + + def serve_clear_replay_protection(self, cb, eb): + """ + Handle a clear_replay_protection action for this client. + """ + + self.last_cms_timestamp = None + self.sql_mark_dirty() + cb() + + def serve_fetch_one_maybe(self): + """ + Find the client object on which a get, set, or destroy method + should operate, or which would conflict with a create method. + """ + + return self.sql_fetch_where1(self.gctx, "client_handle = %s", (self.client_handle,)) + + def serve_fetch_all(self): + """ + Find client objects on which a list method should operate. + """ + + return self.sql_fetch_all(self.gctx) + + def check_allowed_uri(self, uri): + """ + Make sure that a target URI is within this client's allowed URI space. + """ + + if not uri.startswith(self.base_uri): + raise rpki.exceptions.ForbiddenURI + + +class report_error_elt(rpki.xml_utils.text_elt, publication_control_namespace): + """ + <report_error/> element. + """ + + element_name = "report_error" + attributes = ("tag", "error_code") + text_attribute = "error_text" + + error_text = None + + @classmethod + def from_exception(cls, e, tag = None): + """ + Generate a <report_error/> element from an exception. + """ + + self = cls() + self.tag = tag + self.error_code = e.__class__.__name__ + self.error_text = str(e) + return self + + def __str__(self): + s = "" + if getattr(self, "tag", None) is not None: + s += "[%s] " % self.tag + s += self.error_code + if getattr(self, "error_text", None) is not None: + s += ": " + self.error_text + return s + + def raise_if_error(self): + """ + Raise exception associated with this <report_error/> PDU. + """ + + t = rpki.exceptions.__dict__.get(self.error_code) + if isinstance(t, type) and issubclass(t, rpki.exceptions.RPKI_Exception): + raise t(getattr(self, "text", None)) + else: + raise rpki.exceptions.BadPublicationReply("Unexpected response from pubd: %s" % self) + + +class msg(rpki.xml_utils.msg, publication_control_namespace): + """ + Publication control PDU. + """ + + ## @var version + # Protocol version + version = int(rpki.relaxng.publication_control.version) + + ## @var pdus + # Dispatch table of PDUs for this protocol. + pdus = dict((x.element_name, x) for x in (client_elt, report_error_elt)) + + def serve_top_level(self, gctx, cb): + """ + Serve one msg PDU. + """ + + if not self.is_query(): + raise rpki.exceptions.BadQuery("Message type is not query") + r_msg = self.__class__.reply() + + def loop(iterator, q_pdu): + + def fail(e): + if not isinstance(e, rpki.exceptions.NotFound): + logger.exception("Exception processing PDU %r", q_pdu) + r_msg.append(report_error_elt.from_exception(e, q_pdu.tag)) + cb(r_msg) + + try: + q_pdu.gctx = gctx + q_pdu.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 control protocol. + """ + + pdu = msg + name = "msg" + version = rpki.relaxng.publication_control.version + + +class cms_msg(rpki.x509.XML_CMS_object): + """ + Class to hold a CMS-signed publication control PDU. + """ + + encoding = "us-ascii" + schema = rpki.relaxng.publication_control + saxify = sax_handler.saxify diff --git a/rpki/rcynic.py b/rpki/rcynic.py index 10ad7516..a36e4a4e 100644 --- a/rpki/rcynic.py +++ b/rpki/rcynic.py @@ -53,6 +53,7 @@ class rcynic_object(object): Print a bunch of object attributes, quietly ignoring any that might be missing. """ + for a in attrs: try: print "%s: %s" % (a.capitalize(), getattr(self, a)) @@ -63,6 +64,7 @@ class rcynic_object(object): """ Print common object attributes. """ + self.show_attrs("filename", "uri", "status", "timestamp") class rcynic_certificate(rcynic_object): @@ -91,6 +93,7 @@ class rcynic_certificate(rcynic_object): """ Print certificate attributes. """ + rcynic_object.show(self) self.show_attrs("notBefore", "notAfter", "aia_uri", "sia_directory_uri", "resources") @@ -128,6 +131,7 @@ class rcynic_roa(rcynic_object): """ Print ROA attributes. """ + rcynic_object.show(self) self.show_attrs("notBefore", "notAfter", "aia_uri", "resources", "asID") if self.prefix_sets: diff --git a/rpki/relaxng.py b/rpki/relaxng.py index 2ca4e39f..a7bc7578 100644 --- a/rpki/relaxng.py +++ b/rpki/relaxng.py @@ -6,7 +6,7 @@ from rpki.relaxng_parser import RelaxNGParser ## Parsed RelaxNG left_right schema left_right = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: left-right-schema.rnc 5845 2014-05-29 22:31:15Z sra $ + $Id: left-right.rnc 5881 2014-07-03 16:55:02Z sra $ RelaxNG schema for RPKI left-right protocol. @@ -1102,7 +1102,7 @@ left_right = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> ## Parsed RelaxNG myrpki schema myrpki = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: myrpki.rnc 5757 2014-04-05 22:42:12Z sra $ + $Id: myrpki.rnc 5876 2014-06-26 19:00:12Z sra $ RelaxNG schema for MyRPKI XML messages. @@ -1481,11 +1481,11 @@ myrpki = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> --> ''') -## @var publication -## Parsed RelaxNG publication schema -publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> +## @var publication_control +## Parsed RelaxNG publication_control schema +publication_control = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: publication-schema.rnc 5845 2014-05-29 22:31:15Z sra $ + $Id: publication-control.rnc 5883 2014-07-03 19:21:31Z sra $ RelaxNG schema for RPKI publication protocol. @@ -1506,7 +1506,7 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --> -<grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> +<grammar ns="http://www.hactrn.net/uris/rpki/publication-control/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> <define name="version"> <value>1</value> </define> @@ -1540,26 +1540,12 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> </start> <!-- PDUs allowed in a query --> <define name="query_elt"> - <choice> - <ref name="config_query"/> - <ref name="client_query"/> - <ref name="certificate_query"/> - <ref name="crl_query"/> - <ref name="manifest_query"/> - <ref name="roa_query"/> - <ref name="ghostbuster_query"/> - </choice> + <ref name="client_query"/> </define> <!-- PDUs allowed in a reply --> <define name="reply_elt"> <choice> - <ref name="config_reply"/> <ref name="client_reply"/> - <ref name="certificate_reply"/> - <ref name="crl_reply"/> - <ref name="manifest_reply"/> - <ref name="roa_reply"/> - <ref name="ghostbuster_reply"/> <ref name="report_error_reply"/> </choice> </define> @@ -1603,60 +1589,7 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> <param name="pattern">[\-_A-Za-z0-9/]+</param> </data> </define> - <!-- - <config/> element (use restricted to repository operator) - config_handle attribute, create, list, and destroy commands omitted deliberately, see code for details - --> - <define name="config_payload"> - <optional> - <element name="bpki_crl"> - <ref name="base64"/> - </element> - </optional> - </define> - <define name="config_query" combine="choice"> - <element name="config"> - <attribute name="action"> - <value>set</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="config_payload"/> - </element> - </define> - <define name="config_reply" combine="choice"> - <element name="config"> - <attribute name="action"> - <value>set</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - </element> - </define> - <define name="config_query" combine="choice"> - <element name="config"> - <attribute name="action"> - <value>get</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - </element> - </define> - <define name="config_reply" combine="choice"> - <element name="config"> - <attribute name="action"> - <value>get</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="config_payload"/> - </element> - </define> - <!-- <client/> element (use restricted to repository operator) --> + <!-- <client/> element --> <define name="client_handle"> <attribute name="client_handle"> <ref name="object_handle"/> @@ -1801,242 +1734,217 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> <ref name="client_handle"/> </element> </define> - <!-- <certificate/> element --> - <define name="certificate_query" combine="choice"> - <element name="certificate"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> + <!-- <report_error/> element --> + <define name="error"> + <data type="token"> + <param name="maxLength">1024</param> + </data> </define> - <define name="certificate_reply" combine="choice"> - <element name="certificate"> - <attribute name="action"> - <value>publish</value> - </attribute> + <define name="report_error_reply"> + <element name="report_error"> <optional> <ref name="tag"/> </optional> - <ref name="uri"/> - </element> - </define> - <define name="certificate_query" combine="choice"> - <element name="certificate"> - <attribute name="action"> - <value>withdraw</value> + <attribute name="error_code"> + <ref name="error"/> </attribute> <optional> - <ref name="tag"/> + <data type="string"> + <param name="maxLength">512000</param> + </data> </optional> - <ref name="uri"/> </element> </define> - <define name="certificate_reply" combine="choice"> - <element name="certificate"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> +</grammar> +<!-- + Local Variables: + indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" + End: +--> +''') + +## @var publication +## Parsed RelaxNG publication schema +publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> +<!-- + $Id: publication.rnc 5896 2014-07-15 19:34:32Z sra $ + + RelaxNG schema for RPKI publication protocol, from current I-D. + + Copyright (c) 2014 IETF Trust and the persons identified as authors + of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Internet Society, IETF or IETF Trust, nor the + names of specific contributors, may be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +--> +<grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> + <!-- This is version 3 of the protocol. --> + <define name="version"> + <value>3</value> </define> - <!-- <crl/> element --> - <define name="crl_query" combine="choice"> - <element name="crl"> - <attribute name="action"> - <value>publish</value> + <!-- Top level PDU is either a query or a reply. --> + <start combine="choice"> + <element name="msg"> + <attribute name="version"> + <ref name="version"/> </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> - </define> - <define name="crl_reply" combine="choice"> - <element name="crl"> - <attribute name="action"> - <value>publish</value> + <attribute name="type"> + <value>query</value> </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> + <zeroOrMore> + <ref name="query_elt"/> + </zeroOrMore> </element> - </define> - <define name="crl_query" combine="choice"> - <element name="crl"> - <attribute name="action"> - <value>withdraw</value> + </start> + <start combine="choice"> + <element name="msg"> + <attribute name="version"> + <ref name="version"/> </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="crl_reply" combine="choice"> - <element name="crl"> - <attribute name="action"> - <value>withdraw</value> + <attribute name="type"> + <value>reply</value> </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> + <zeroOrMore> + <ref name="reply_elt"/> + </zeroOrMore> </element> + </start> + <!-- PDUs allowed in queries and replies. --> + <define name="query_elt"> + <choice> + <ref name="publish_query"/> + <ref name="withdraw_query"/> + <ref name="list_query"/> + </choice> </define> - <!-- <manifest/> element --> - <define name="manifest_query" combine="choice"> - <element name="manifest"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> + <define name="reply_elt"> + <choice> + <ref name="publish_reply"/> + <ref name="withdraw_reply"/> + <ref name="list_reply"/> + <ref name="report_error_reply"/> + </choice> </define> - <define name="manifest_reply" combine="choice"> - <element name="manifest"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> + <!-- Tag attributes for bulk operations. --> + <define name="tag"> + <attribute name="tag"> + <data type="token"> + <param name="maxLength">1024</param> + </data> + </attribute> </define> - <define name="manifest_query" combine="choice"> - <element name="manifest"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> + <!-- Base64 encoded DER stuff. --> + <define name="base64"> + <data type="base64Binary"/> </define> - <define name="manifest_reply" combine="choice"> - <element name="manifest"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> + <!-- Publication URIs. --> + <define name="uri"> + <attribute name="uri"> + <data type="anyURI"> + <param name="maxLength">4096</param> + </data> + </attribute> </define> - <!-- <roa/> element --> - <define name="roa_query" combine="choice"> - <element name="roa"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> + <!-- Digest of objects being withdrawn --> + <define name="hash"> + <attribute name="hash"> + <data type="string"> + <param name="pattern">[0-9a-fA-F]+</param> + </data> + </attribute> </define> - <define name="roa_reply" combine="choice"> - <element name="roa"> - <attribute name="action"> - <value>publish</value> - </attribute> + <!-- Error codes. --> + <define name="error"> + <data type="token"> + <param name="maxLength">1024</param> + </data> + </define> + <!-- <publish/> element --> + <define name="publish_query"> + <element name="publish"> <optional> <ref name="tag"/> </optional> <ref name="uri"/> - </element> - </define> - <define name="roa_query" combine="choice"> - <element name="roa"> - <attribute name="action"> - <value>withdraw</value> - </attribute> <optional> - <ref name="tag"/> + <ref name="hash"/> </optional> - <ref name="uri"/> + <ref name="base64"/> </element> </define> - <define name="roa_reply" combine="choice"> - <element name="roa"> - <attribute name="action"> - <value>withdraw</value> - </attribute> + <define name="publish_reply"> + <element name="publish"> <optional> <ref name="tag"/> </optional> <ref name="uri"/> </element> </define> - <!-- <ghostbuster/> element --> - <define name="ghostbuster_query" combine="choice"> - <element name="ghostbuster"> - <attribute name="action"> - <value>publish</value> - </attribute> + <!-- <withdraw/> element --> + <define name="withdraw_query"> + <element name="withdraw"> <optional> <ref name="tag"/> </optional> <ref name="uri"/> - <ref name="base64"/> + <ref name="hash"/> </element> </define> - <define name="ghostbuster_reply" combine="choice"> - <element name="ghostbuster"> - <attribute name="action"> - <value>publish</value> - </attribute> + <define name="withdraw_reply"> + <element name="withdraw"> <optional> <ref name="tag"/> </optional> <ref name="uri"/> </element> </define> - <define name="ghostbuster_query" combine="choice"> - <element name="ghostbuster"> - <attribute name="action"> - <value>withdraw</value> - </attribute> + <!-- <list/> element --> + <define name="list_query"> + <element name="list"> <optional> <ref name="tag"/> </optional> - <ref name="uri"/> </element> </define> - <define name="ghostbuster_reply" combine="choice"> - <element name="ghostbuster"> - <attribute name="action"> - <value>withdraw</value> - </attribute> + <define name="list_reply"> + <element name="list"> <optional> <ref name="tag"/> </optional> <ref name="uri"/> + <ref name="hash"/> </element> </define> <!-- <report_error/> element --> - <define name="error"> - <data type="token"> - <param name="maxLength">1024</param> - </data> - </define> <define name="report_error_reply"> <element name="report_error"> <optional> @@ -2066,7 +1974,7 @@ publication = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> ## Parsed RelaxNG router_certificate schema router_certificate = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: router-certificate-schema.rnc 5757 2014-04-05 22:42:12Z sra $ + $Id: router-certificate.rnc 5881 2014-07-03 16:55:02Z sra $ RelaxNG schema for BGPSEC router certificate interchange format. @@ -2164,11 +2072,178 @@ router_certificate = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> --> ''') +## @var rrdp +## Parsed RelaxNG rrdp schema +rrdp = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> +<!-- + $Id: rrdp.rnc 5888 2014-07-09 05:39:54Z sra $ + + RelaxNG schema for RPKI Repository Delta Protocol (RRDP). + + Copyright (C) 2014 Dragon Research Labs ("DRL") + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. +--> +<grammar ns="http://www.ripe.net/rpki/rrdp" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> + <define name="version"> + <data type="positiveInteger"> + <param name="maxInclusive">1</param> + </data> + </define> + <define name="serial"> + <data type="nonNegativeInteger"/> + </define> + <define name="uri"> + <data type="anyURI"/> + </define> + <define name="uuid"> + <data type="string"> + <param name="pattern">[\-0-9a-fA-F]+</param> + </data> + </define> + <define name="hash"> + <data type="string"> + <param name="pattern">[0-9a-fA-F]+</param> + </data> + </define> + <define name="base64"> + <data type="base64Binary"/> + </define> + <!-- Notification file: lists current snapshots and deltas --> + <start combine="choice"> + <element name="notification"> + <attribute name="version"> + <ref name="version"/> + </attribute> + <attribute name="session_id"> + <ref name="uuid"/> + </attribute> + <attribute name="serial"> + <ref name="serial"/> + </attribute> + <element name="snapshot"> + <attribute name="uri"> + <ref name="uri"/> + </attribute> + <attribute name="hash"> + <ref name="hash"/> + </attribute> + </element> + <zeroOrMore> + <element name="delta"> + <attribute name="from"> + <ref name="serial"/> + </attribute> + <attribute name="to"> + <ref name="serial"/> + </attribute> + <attribute name="uri"> + <ref name="uri"/> + </attribute> + <attribute name="hash"> + <ref name="hash"/> + </attribute> + </element> + </zeroOrMore> + </element> + </start> + <!-- Snapshot segment: think DNS AXFR. --> + <start combine="choice"> + <element name="snapshot"> + <attribute name="version"> + <ref name="version"/> + </attribute> + <attribute name="session_id"> + <ref name="uuid"/> + </attribute> + <attribute name="serial"> + <ref name="serial"/> + </attribute> + <zeroOrMore> + <element name="publish"> + <attribute name="uri"> + <ref name="uri"/> + </attribute> + <ref name="base64"/> + </element> + </zeroOrMore> + </element> + </start> + <!-- Delta segment: think DNS IXFR. --> + <start combine="choice"> + <element name="deltas"> + <attribute name="version"> + <ref name="version"/> + </attribute> + <attribute name="session_id"> + <ref name="uuid"/> + </attribute> + <attribute name="from"> + <ref name="serial"/> + </attribute> + <attribute name="to"> + <ref name="serial"/> + </attribute> + <oneOrMore> + <element name="delta"> + <attribute name="serial"> + <ref name="serial"/> + </attribute> + <oneOrMore> + <ref name="delta_element"/> + </oneOrMore> + </element> + </oneOrMore> + </element> + </start> + <define name="delta_element" combine="choice"> + <element name="publish"> + <attribute name="uri"> + <ref name="uri"/> + </attribute> + <optional> + <attribute name="hash"> + <ref name="hash"/> + </attribute> + </optional> + <ref name="base64"/> + </element> + </define> + <define name="delta_element" combine="choice"> + <element name="withdraw"> + <attribute name="uri"> + <ref name="uri"/> + </attribute> + <attribute name="hash"> + <ref name="hash"/> + </attribute> + </element> + </define> +</grammar> +<!-- + Local Variables: + indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" + End: +--> +''') + ## @var up_down ## Parsed RelaxNG up_down schema up_down = RelaxNGParser(r'''<?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: up-down-schema.rnc 5757 2014-04-05 22:42:12Z sra $ + $Id: up-down.rnc 5881 2014-07-03 16:55:02Z sra $ RelaxNG schema for the up-down protocol, extracted from RFC 6492. diff --git a/rpki/resource_set.py b/rpki/resource_set.py index fea6ad2d..130bf4e7 100644 --- a/rpki/resource_set.py +++ b/rpki/resource_set.py @@ -86,6 +86,7 @@ class resource_range_as(resource_range): """ Convert a resource_range_as to string format. """ + if self.min == self.max: return str(self.min) else: @@ -96,6 +97,7 @@ class resource_range_as(resource_range): """ Parse ASN resource range from text (eg, XML attributes). """ + r = re_asn_range.match(x) if r: return cls(long(r.group(1)), long(r.group(2))) @@ -107,6 +109,7 @@ class resource_range_as(resource_range): """ Construct ASN range from strings. """ + if b is None: b = a return cls(long(a), long(b)) @@ -133,6 +136,7 @@ class resource_range_ip(resource_range): prefix. Returns prefix length if it can, otherwise raises MustBePrefix exception. """ + mask = self.min ^ self.max if self.min & mask != 0: raise rpki.exceptions.MustBePrefix @@ -154,6 +158,7 @@ class resource_range_ip(resource_range): the logic in one place. This property is useful primarily in context where catching an exception isn't practical. """ + try: self.prefixlen() return True @@ -164,6 +169,7 @@ class resource_range_ip(resource_range): """ Convert a resource_range_ip to string format. """ + try: return str(self.min) + "/" + str(self.prefixlen()) except rpki.exceptions.MustBePrefix: @@ -174,6 +180,7 @@ class resource_range_ip(resource_range): """ Parse IP address range or prefix from text (eg, XML attributes). """ + r = re_address_range.match(x) if r: return cls.from_strings(r.group(1), r.group(2)) @@ -192,6 +199,7 @@ class resource_range_ip(resource_range): """ Construct a resource range corresponding to a prefix. """ + assert isinstance(prefix, rpki.POW.IPAddress) and isinstance(prefixlen, (int, long)) assert prefixlen >= 0 and prefixlen <= prefix.bits, "Nonsensical prefix length: %s" % prefixlen mask = (1 << (prefix.bits - prefixlen)) - 1 @@ -203,6 +211,7 @@ class resource_range_ip(resource_range): Chop up a resource_range_ip into ranges that can be represented as prefixes. """ + try: self.prefixlen() result.append(self) @@ -226,6 +235,7 @@ class resource_range_ip(resource_range): """ Construct IP address range from strings. """ + if b is None: b = a a = rpki.POW.IPAddress(a) @@ -300,6 +310,7 @@ class resource_set(list): """ Initialize a resource_set. """ + list.__init__(self) if isinstance(ini, (int, long)): ini = str(ini) @@ -317,6 +328,7 @@ class resource_set(list): """ Whack this resource_set into canonical form. """ + assert not self.inherit or len(self) == 0 if not self.canonical: self.sort() @@ -339,6 +351,7 @@ class resource_set(list): """ Wrapper around list.append() (q.v.) to reset canonical flag. """ + list.append(self, item) self.canonical = False @@ -346,6 +359,7 @@ class resource_set(list): """ Wrapper around list.extend() (q.v.) to reset canonical flag. """ + list.extend(self, item) self.canonical = False @@ -353,6 +367,7 @@ class resource_set(list): """ Convert a resource_set to string format. """ + if self.inherit: return inherit_token else: @@ -428,6 +443,7 @@ class resource_set(list): """ Set intersection for resource sets. """ + return self._comm(other)[2] __and__ = intersection @@ -436,6 +452,7 @@ class resource_set(list): """ Set difference for resource sets. """ + return self._comm(other)[0] __sub__ = difference @@ -444,6 +461,7 @@ class resource_set(list): """ Set symmetric difference (XOR) for resource sets. """ + com = self._comm(other) return com[0] | com[1] @@ -453,6 +471,7 @@ class resource_set(list): """ Set membership test for resource sets. """ + assert not self.inherit self.canonize() if not self: @@ -479,6 +498,7 @@ class resource_set(list): """ Test whether self is a subset (possibly improper) of other. """ + for i in self: if not other.contains(i): return False @@ -490,6 +510,7 @@ class resource_set(list): """ Test whether self is a superset (possibly improper) of other. """ + return other.issubset(self) __ge__ = issuperset @@ -506,6 +527,7 @@ class resource_set(list): we can't know the answer here. This is also consistent with __nonzero__ which returns True for inherit sets, and False for empty sets. """ + return self.inherit or other.inherit or list.__ne__(self, other) def __eq__(self, other): @@ -516,6 +538,7 @@ class resource_set(list): Tests whether or not this set is empty. Note that sets with the inherit bit set are considered non-empty, despite having zero length. """ + return self.inherit or len(self) @classmethod @@ -553,6 +576,7 @@ class resource_set(list): a backwards compatability wrapper, real functionality is now part of the range classes. """ + return cls.range_type.parse_str(s) class resource_set_as(resource_set): @@ -577,6 +601,7 @@ class resource_set_ip(resource_set): """ Convert from a resource set to a ROA prefix set. """ + prefix_ranges = [] for r in self: r.chop_into_prefixes(prefix_ranges) @@ -632,6 +657,7 @@ class resource_bag(object): """ True iff self is oversized with respect to other. """ + return not self.asn.issubset(other.asn) or \ not self.v4.issubset(other.v4) or \ not self.v6.issubset(other.v6) @@ -640,6 +666,7 @@ class resource_bag(object): """ True iff self is undersized with respect to other. """ + return not other.asn.issubset(self.asn) or \ not other.v4.issubset(self.v4) or \ not other.v6.issubset(self.v6) @@ -650,6 +677,7 @@ class resource_bag(object): Build a resource bag that just inherits everything from its parent. """ + self = cls() self.asn = resource_set_as() self.v4 = resource_set_ipv4() @@ -665,6 +693,7 @@ class resource_bag(object): Parse a comma-separated text string into a resource_bag. Not particularly efficient, fix that if and when it becomes an issue. """ + asns = [] v4s = [] v6s = [] @@ -689,6 +718,7 @@ class resource_bag(object): temporary: in the long run, we should be using rpki.POW.IPAddress rather than long here. """ + asn = inherit_token if resources[0] == "inherit" else [resource_range_as( r[0], r[1]) for r in resources[0] or ()] v4 = inherit_token if resources[1] == "inherit" else [resource_range_ipv4(r[0], r[1]) for r in resources[1] or ()] v6 = inherit_token if resources[2] == "inherit" else [resource_range_ipv6(r[0], r[1]) for r in resources[2] or ()] @@ -700,6 +730,7 @@ class resource_bag(object): """ True iff all resource sets in this bag are empty. """ + return not self.asn and not self.v4 and not self.v6 def __nonzero__(self): @@ -719,6 +750,7 @@ class resource_bag(object): Compute intersection with another resource_bag. valid_until attribute (if any) inherits from self. """ + return self.__class__(self.asn & other.asn, self.v4 & other.v4, self.v6 & other.v6, @@ -731,6 +763,7 @@ class resource_bag(object): Compute union with another resource_bag. valid_until attribute (if any) inherits from self. """ + return self.__class__(self.asn | other.asn, self.v4 | other.v4, self.v6 | other.v6, @@ -743,6 +776,7 @@ class resource_bag(object): Compute difference against another resource_bag. valid_until attribute (if any) inherits from self """ + return self.__class__(self.asn - other.asn, self.v4 - other.v4, self.v6 - other.v6, @@ -755,6 +789,7 @@ class resource_bag(object): Compute symmetric difference against another resource_bag. valid_until attribute (if any) inherits from self """ + return self.__class__(self.asn ^ other.asn, self.v4 ^ other.v4, self.v6 ^ other.v6, @@ -816,6 +851,7 @@ class roa_prefix(object): Initialize a ROA prefix. max_prefixlen is optional and defaults to prefixlen. max_prefixlen must not be smaller than prefixlen. """ + if max_prefixlen is None: max_prefixlen = prefixlen assert max_prefixlen >= prefixlen, "Bad max_prefixlen: %d must not be shorter than %d" % (max_prefixlen, prefixlen) @@ -828,6 +864,7 @@ class roa_prefix(object): Compare two ROA prefix objects. Comparision is based on prefix, prefixlen, and max_prefixlen, in that order. """ + assert self.__class__ is other.__class__ return (cmp(self.prefix, other.prefix) or cmp(self.prefixlen, other.prefixlen) or @@ -837,6 +874,7 @@ class roa_prefix(object): """ Convert a ROA prefix to string format. """ + if self.prefixlen == self.max_prefixlen: return str(self.prefix) + "/" + str(self.prefixlen) else: @@ -848,24 +886,28 @@ class roa_prefix(object): object. This is an irreversable transformation because it loses the max_prefixlen attribute, nothing we can do about that. """ + return self.range_type.make_prefix(self.prefix, self.prefixlen) def min(self): """ Return lowest address covered by prefix. """ + return self.prefix def max(self): """ Return highest address covered by prefix. """ + return self.prefix | ((1 << (self.prefix.bits - self.prefixlen)) - 1) def to_POW_roa_tuple(self): """ Convert a resource_range_ip to rpki.POW.ROA.setPrefixes() format. """ + return self.prefix, self.prefixlen, self.max_prefixlen @classmethod @@ -873,6 +915,7 @@ class roa_prefix(object): """ Parse ROA prefix from text (eg, an XML attribute). """ + r = re_prefix_with_maxlen.match(x) if r: return cls(rpki.POW.IPAddress(r.group(1)), int(r.group(2)), int(r.group(3))) @@ -910,6 +953,7 @@ class roa_prefix_set(list): """ Initialize a ROA prefix set. """ + list.__init__(self) if isinstance(ini, str) and len(ini): self.extend(self.parse_str(s) for s in ini.split(",")) @@ -923,6 +967,7 @@ class roa_prefix_set(list): """ Convert a ROA prefix set to string format. """ + return ",".join(str(x) for x in self) @classmethod @@ -931,6 +976,7 @@ class roa_prefix_set(list): Parse ROA prefix from text (eg, an XML attribute). This method is a backwards compatability shim. """ + return cls.prefix_type.parse_str(s) def to_resource_set(self): @@ -942,6 +988,7 @@ class roa_prefix_set(list): a more efficient way to do this, but start by getting the output right before worrying about making it fast or pretty. """ + r = self.resource_set_type() s = self.resource_set_type() s.append(None) @@ -982,6 +1029,7 @@ class roa_prefix_set(list): """ Convert ROA prefix set to form used by rpki.POW.ROA.setPrefixes(). """ + if self: return tuple(a.to_POW_roa_tuple() for a in self) else: diff --git a/rpki/rootd.py b/rpki/rootd.py index fb445213..579909fd 100644 --- a/rpki/rootd.py +++ b/rpki/rootd.py @@ -101,6 +101,7 @@ class message_pdu(rpki.up_down.message_pdu): """ Log query we're handling. """ + logger.info("Serving %s query", self.type) class sax_handler(rpki.up_down.sax_handler): diff --git a/rpki/rpkid.py b/rpki/rpkid.py index 36ee2ea9..9a9429e5 100644 --- a/rpki/rpkid.py +++ b/rpki/rpkid.py @@ -318,6 +318,7 @@ class main(object): Record that we were still alive when we got here, by resetting keepalive timer. """ + if force or self.cron_timeout is not None: self.cron_timeout = rpki.sundial.now() + self.cron_keepalive @@ -325,6 +326,7 @@ class main(object): """ Add a task to the scheduler task queue, unless it's already queued. """ + if task not in self.task_queue: logger.debug("Adding %r to task queue", task) self.task_queue.append(task) @@ -339,6 +341,7 @@ class main(object): queue (we don't want to run it directly, as that could eventually blow out our call stack). """ + try: self.task_current = self.task_queue.pop(0) except IndexError: @@ -350,6 +353,7 @@ class main(object): """ Run first task on the task queue, unless one is running already. """ + if self.task_current is None: self.task_next() @@ -446,6 +450,7 @@ class ca_obj(rpki.sql.sql_persistent): """ Fetch parent object to which this CA object links. """ + return rpki.left_right.parent_elt.sql_fetch(self.gctx, self.parent_id) @property @@ -453,6 +458,7 @@ class ca_obj(rpki.sql.sql_persistent): """ Fetch all ca_detail objects that link to this CA object. """ + return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s", (self.ca_id,)) @property @@ -460,6 +466,7 @@ class ca_obj(rpki.sql.sql_persistent): """ Fetch the pending ca_details for this CA, if any. """ + return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'pending'", (self.ca_id,)) @property @@ -467,6 +474,7 @@ class ca_obj(rpki.sql.sql_persistent): """ Fetch the active ca_detail for this CA, if any. """ + return ca_detail_obj.sql_fetch_where1(self.gctx, "ca_id = %s AND state = 'active'", (self.ca_id,)) @property @@ -474,6 +482,7 @@ class ca_obj(rpki.sql.sql_persistent): """ Fetch deprecated ca_details for this CA, if any. """ + return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'deprecated'", (self.ca_id,)) @property @@ -481,6 +490,7 @@ class ca_obj(rpki.sql.sql_persistent): """ Fetch active and deprecated ca_details for this CA, if any. """ + return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND (state = 'active' OR state = 'deprecated')", (self.ca_id,)) @property @@ -488,6 +498,7 @@ class ca_obj(rpki.sql.sql_persistent): """ Fetch revoked ca_details for this CA, if any. """ + return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'revoked'", (self.ca_id,)) @property @@ -496,7 +507,7 @@ class ca_obj(rpki.sql.sql_persistent): Fetch ca_details which are candidates for consideration when processing an up-down issue_response PDU. """ - #return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND latest_ca_cert IS NOT NULL AND state != 'revoked'", (self.ca_id,)) + return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state != 'revoked'", (self.ca_id,)) def construct_sia_uri(self, parent, rc): @@ -542,7 +553,8 @@ class ca_obj(rpki.sql.sql_persistent): if rc_cert is None: - logger.warning("SKI %s in resource class %s is in database but missing from list_response to %s from %s, maybe parent certificate went away?", + logger.warning("SKI %s in resource class %s is in database but missing from list_response to %s from %s, " + "maybe parent certificate went away?", ca_detail.public_key.gSKI(), rc.class_name, parent.self.self_handle, parent.parent_handle) publisher = publication_queue() ca_detail.delete(ca = ca_detail.ca, publisher = publisher) @@ -677,6 +689,7 @@ class ca_obj(rpki.sql.sql_persistent): """ Allocate a certificate serial number. """ + self.last_issued_sn += 1 self.sql_mark_dirty() return self.last_issued_sn @@ -685,6 +698,7 @@ class ca_obj(rpki.sql.sql_persistent): """ Allocate a manifest serial number. """ + self.last_manifest_sn += 1 self.sql_mark_dirty() return self.last_manifest_sn @@ -693,6 +707,7 @@ class ca_obj(rpki.sql.sql_persistent): """ Allocate a CRL serial number. """ + self.last_crl_sn += 1 self.sql_mark_dirty() return self.last_crl_sn @@ -783,6 +798,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): """ Extra assertions for SQL decode of a ca_detail_obj. """ + rpki.sql.sql_persistent.sql_decode(self, vals) assert self.public_key is None or self.private_key_id is None or self.public_key.get_DER() == self.private_key_id.get_public_DER() assert self.manifest_public_key is None or self.manifest_private_key_id is None or self.manifest_public_key.get_DER() == self.manifest_private_key_id.get_public_DER() @@ -793,12 +809,14 @@ class ca_detail_obj(rpki.sql.sql_persistent): """ Fetch CA object to which this ca_detail links. """ + return ca_obj.sql_fetch(self.gctx, self.ca_id) def fetch_child_certs(self, child = None, ski = None, unique = False, unpublished = None): """ Fetch all child_cert objects that link to this ca_detail. """ + return rpki.rpkid.child_cert_obj.fetch(self.gctx, child, self, ski, unique, unpublished) @property @@ -806,6 +824,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): """ Fetch all child_cert objects that link to this ca_detail. """ + return self.fetch_child_certs() def unpublished_child_certs(self, when): @@ -813,6 +832,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): Fetch all unpublished child_cert objects linked to this ca_detail with attempted publication dates older than when. """ + return self.fetch_child_certs(unpublished = when) @property @@ -820,6 +840,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): """ Fetch all revoked_cert objects that link to this ca_detail. """ + return revoked_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,)) @property @@ -827,6 +848,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): """ Fetch all ROA objects that link to this ca_detail. """ + return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,)) def unpublished_roas(self, when): @@ -834,6 +856,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): Fetch all unpublished ROA objects linked to this ca_detail with attempted publication dates older than when. """ + return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s AND published IS NOT NULL and published < %s", (self.ca_detail_id, when)) @property @@ -841,27 +864,39 @@ class ca_detail_obj(rpki.sql.sql_persistent): """ Fetch all Ghostbuster objects that link to this ca_detail. """ + return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,)) + def unpublished_ghostbusters(self, when): + """ + Fetch all unpublished Ghostbusters objects linked to this + ca_detail with attempted publication dates older than when. + """ + + return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s AND published IS NOT NULL and published < %s", (self.ca_detail_id, when)) + @property def ee_certificates(self): """ Fetch all EE certificate objects that link to this ca_detail. """ + return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,)) - def unpublished_ghostbusters(self, when): + def unpublished_ee_certificates(self, when): """ - Fetch all unpublished Ghostbusters objects linked to this + Fetch all unpublished EE certificate objects linked to this ca_detail with attempted publication dates older than when. """ - return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s AND published IS NOT NULL and published < %s", (self.ca_detail_id, when)) + + return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s AND published IS NOT NULL and published < %s", (self.ca_detail_id, when)) @property def crl_uri(self): """ Return publication URI for this ca_detail's CRL. """ + return self.ca.sia_uri + self.crl_uri_tail @property @@ -869,6 +904,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): """ Return tail (filename portion) of publication URI for this ca_detail's CRL. """ + return self.public_key.gSKI() + ".crl" @property @@ -876,12 +912,14 @@ class ca_detail_obj(rpki.sql.sql_persistent): """ Return publication URI for this ca_detail's manifest. """ + return self.ca.sia_uri + self.public_key.gSKI() + ".mft" def has_expired(self): """ Return whether this ca_detail's certificate has expired. """ + return self.latest_ca_cert.getNotAfter() <= rpki.sundial.now() def covers(self, target): @@ -933,11 +971,10 @@ class ca_detail_obj(rpki.sql.sql_persistent): repository = ca.parent.repository handler = False if allow_failure else None for child_cert in self.child_certs: - publisher.withdraw(cls = rpki.publication.certificate_elt, - uri = child_cert.uri, - obj = child_cert.cert, - repository = repository, - handler = handler) + publisher.queue(uri = child_cert.uri, + old_obj = child_cert.cert, + repository = repository, + handler = handler) child_cert.sql_mark_deleted() for roa in self.roas: roa.revoke(publisher = publisher, allow_failure = allow_failure, fast = True) @@ -948,21 +985,19 @@ class ca_detail_obj(rpki.sql.sql_persistent): except AttributeError: latest_manifest = None if latest_manifest is not None: - publisher.withdraw(cls = rpki.publication.manifest_elt, - uri = self.manifest_uri, - obj = self.latest_manifest, - repository = repository, - handler = handler) + publisher.queue(uri = self.manifest_uri, + old_obj = self.latest_manifest, + repository = repository, + handler = handler) try: latest_crl = self.latest_crl except AttributeError: latest_crl = None if latest_crl is not None: - publisher.withdraw(cls = rpki.publication.crl_elt, - uri = self.crl_uri, - obj = self.latest_crl, - repository = repository, - handler = handler) + publisher.queue(uri = self.crl_uri, + old_obj = self.latest_crl, + repository = repository, + handler = handler) self.gctx.sql.sweep() for cert in self.revoked_certs: # + self.child_certs logger.debug("Deleting %r", cert) @@ -1172,6 +1207,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): notAfter = resources.valid_until) if child_cert is None: + old_cert = None child_cert = rpki.rpkid.child_cert_obj( gctx = child.gctx, child_id = child.child_id, @@ -1179,6 +1215,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): cert = cert) logger.debug("Created new child_cert %r", child_cert) else: + old_cert = child_cert.cert child_cert.cert = cert del child_cert.ca_detail child_cert.ca_detail_id = self.ca_detail_id @@ -1187,10 +1224,10 @@ class ca_detail_obj(rpki.sql.sql_persistent): child_cert.ski = cert.get_SKI() child_cert.published = rpki.sundial.now() child_cert.sql_store() - publisher.publish( - cls = rpki.publication.certificate_elt, + publisher.queue( uri = child_cert.uri, - obj = child_cert.cert, + old_obj = old_cert, + new_obj = child_cert.cert, repository = ca.parent.repository, handler = child_cert.published_callback) self.generate_manifest(publisher = publisher) @@ -1221,6 +1258,8 @@ class ca_detail_obj(rpki.sql.sql_persistent): certlist.append((revoked_cert.serial, revoked_cert.revoked)) certlist.sort() + old_crl = self.latest_crl + self.latest_crl = rpki.x509.CRL.generate( keypair = self.private_key_id, issuer = self.latest_ca_cert, @@ -1231,10 +1270,10 @@ class ca_detail_obj(rpki.sql.sql_persistent): self.crl_published = rpki.sundial.now() self.sql_mark_dirty() - publisher.publish( - cls = rpki.publication.crl_elt, + publisher.queue( uri = self.crl_uri, - obj = self.latest_crl, + old_obj = old_crl, + new_obj = self.latest_crl, repository = parent.repository, handler = self.crl_published_callback) @@ -1242,6 +1281,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): """ Check result of CRL publication. """ + pdu.raise_if_error() self.crl_published = None self.sql_mark_dirty() @@ -1278,6 +1318,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): objs.extend((e.uri_tail, e.cert) for e in self.ee_certificates) logger.debug("Building manifest object %s", uri) + old_manifest = self.latest_manifest self.latest_manifest = rpki.x509.SignedManifest.build( serial = ca.next_manifest_number(), thisUpdate = now, @@ -1290,16 +1331,17 @@ class ca_detail_obj(rpki.sql.sql_persistent): self.manifest_published = rpki.sundial.now() self.sql_mark_dirty() - publisher.publish(cls = rpki.publication.manifest_elt, - uri = uri, - obj = self.latest_manifest, - repository = parent.repository, - handler = self.manifest_published_callback) + publisher.queue(uri = uri, + old_obj = old_manifest, + new_obj = self.latest_manifest, + repository = parent.repository, + handler = self.manifest_published_callback) def manifest_published_callback(self, pdu): """ Check result of manifest publication. """ + pdu.raise_if_error() self.manifest_published = None self.sql_mark_dirty() @@ -1361,21 +1403,19 @@ class ca_detail_obj(rpki.sql.sql_persistent): self.crl_published is not None and \ self.crl_published < stale: logger.debug("Retrying publication for %s", self.crl_uri) - publisher.publish(cls = rpki.publication.crl_elt, - uri = self.crl_uri, - obj = self.latest_crl, - repository = repository, - handler = self.crl_published_callback) + publisher.queue(uri = self.crl_uri, + new_obj = self.latest_crl, + repository = repository, + handler = self.crl_published_callback) if self.latest_manifest is not None and \ self.manifest_published is not None and \ self.manifest_published < stale: logger.debug("Retrying publication for %s", self.manifest_uri) - publisher.publish(cls = rpki.publication.manifest_elt, - uri = self.manifest_uri, - obj = self.latest_manifest, - repository = repository, - handler = self.manifest_published_callback) + publisher.queue(uri = self.manifest_uri, + new_obj = self.latest_manifest, + repository = repository, + handler = self.manifest_published_callback) if not check_all: return @@ -1385,31 +1425,37 @@ class ca_detail_obj(rpki.sql.sql_persistent): for child_cert in self.unpublished_child_certs(stale): logger.debug("Retrying publication for %s", child_cert) - publisher.publish( - cls = rpki.publication.certificate_elt, + publisher.queue( uri = child_cert.uri, - obj = child_cert.cert, + new_obj = child_cert.cert, repository = repository, handler = child_cert.published_callback) for roa in self.unpublished_roas(stale): logger.debug("Retrying publication for %s", roa) - publisher.publish( - cls = rpki.publication.roa_elt, + publisher.queue( uri = roa.uri, - obj = roa.roa, + new_obj = roa.roa, repository = repository, handler = roa.published_callback) for ghostbuster in self.unpublished_ghostbusters(stale): logger.debug("Retrying publication for %s", ghostbuster) - publisher.publish( - cls = rpki.publication.ghostbuster_elt, + publisher.queue( uri = ghostbuster.uri, - obj = ghostbuster.ghostbuster, + new_obj = ghostbuster.ghostbuster, repository = repository, handler = ghostbuster.published_callback) + for ee_cert in self.unpublished_ee_certificates(stale): + logger.debug("Retrying publication for %s", ee_cert) + publisher.queue( + uri = ee_cert.uri, + new_obj = ee_cert.cert, + repository = repository, + handler = ee_cert.published_callback) + + class child_cert_obj(rpki.sql.sql_persistent): """ Certificate that has been issued to a child. @@ -1436,6 +1482,7 @@ class child_cert_obj(rpki.sql.sql_persistent): """ Initialize a child_cert_obj. """ + rpki.sql.sql_persistent.__init__(self) self.gctx = gctx self.child_id = child_id @@ -1451,6 +1498,7 @@ class child_cert_obj(rpki.sql.sql_persistent): """ Fetch child object to which this child_cert object links. """ + return rpki.left_right.child_elt.sql_fetch(self.gctx, self.child_id) @property @@ -1459,6 +1507,7 @@ class child_cert_obj(rpki.sql.sql_persistent): """ Fetch ca_detail object to which this child_cert object links. """ + return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id) @ca_detail.deleter @@ -1473,6 +1522,7 @@ class child_cert_obj(rpki.sql.sql_persistent): """ Return the tail (filename) portion of the URI for this child_cert. """ + return self.cert.gSKI() + ".cer" @property @@ -1480,6 +1530,7 @@ class child_cert_obj(rpki.sql.sql_persistent): """ Return the publication URI for this child_cert. """ + return self.ca_detail.ca.sia_uri + self.uri_tail def revoke(self, publisher, generate_crl_and_manifest = True): @@ -1491,10 +1542,9 @@ class child_cert_obj(rpki.sql.sql_persistent): ca = ca_detail.ca logger.debug("Revoking %r %r", self, self.uri) revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail) - publisher.withdraw( - cls = rpki.publication.certificate_elt, - uri = self.uri, - obj = self.cert, + publisher.queue( + uri = self.uri, + old_obj = self.cert, repository = ca.parent.repository) self.gctx.sql.sweep() self.sql_delete() @@ -1625,6 +1675,7 @@ class child_cert_obj(rpki.sql.sql_persistent): """ Publication callback: check result and mark published. """ + pdu.raise_if_error() self.published = None self.sql_mark_dirty() @@ -1649,6 +1700,7 @@ class revoked_cert_obj(rpki.sql.sql_persistent): """ Initialize a revoked_cert_obj. """ + rpki.sql.sql_persistent.__init__(self) self.gctx = gctx self.serial = serial @@ -1664,6 +1716,7 @@ class revoked_cert_obj(rpki.sql.sql_persistent): """ Fetch ca_detail object to which this revoked_cert_obj links. """ + return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id) @classmethod @@ -1671,6 +1724,7 @@ class revoked_cert_obj(rpki.sql.sql_persistent): """ Revoke a certificate. """ + return cls( serial = cert.getSerial(), expires = cert.getNotAfter(), @@ -1712,6 +1766,7 @@ class roa_obj(rpki.sql.sql_persistent): """ Fetch ca_detail object to which this roa_obj links. """ + return rpki.rpkid.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id) @ca_detail.deleter @@ -1725,6 +1780,7 @@ class roa_obj(rpki.sql.sql_persistent): """ Extra SQL fetch actions for roa_obj -- handle prefix lists. """ + for version, datatype, attribute in ((4, rpki.resource_set.roa_prefix_set_ipv4, "ipv4"), (6, rpki.resource_set.roa_prefix_set_ipv6, "ipv6")): setattr(self, attribute, datatype.from_sql( @@ -1739,6 +1795,7 @@ class roa_obj(rpki.sql.sql_persistent): """ Extra SQL insert actions for roa_obj -- handle prefix lists. """ + for version, prefix_set in ((4, self.ipv4), (6, self.ipv6)): if prefix_set: self.gctx.sql.executemany( @@ -1753,6 +1810,7 @@ class roa_obj(rpki.sql.sql_persistent): """ Extra SQL delete actions for roa_obj -- handle prefix lists. """ + self.gctx.sql.execute("DELETE FROM roa_prefix WHERE roa_id = %s", (self.roa_id,)) def __repr__(self): @@ -1894,10 +1952,9 @@ class roa_obj(rpki.sql.sql_persistent): self.sql_store() logger.debug("Generating %r URI %s", self, self.uri) - publisher.publish( - cls = rpki.publication.roa_elt, + publisher.queue( uri = self.uri, - obj = self.roa, + new_obj = self.roa, repository = ca.parent.repository, handler = self.published_callback) if not fast: @@ -1942,9 +1999,10 @@ class roa_obj(rpki.sql.sql_persistent): logger.debug("Withdrawing %r %s and revoking its EE cert", self, uri) rpki.rpkid.revoked_cert_obj.revoke(cert = cert, ca_detail = ca_detail) - publisher.withdraw(cls = rpki.publication.roa_elt, uri = uri, obj = roa, - repository = ca_detail.ca.parent.repository, - handler = False if allow_failure else None) + publisher.queue(uri = uri, + old_obj = roa, + repository = ca_detail.ca.parent.repository, + handler = False if allow_failure else None) if not regenerate: self.sql_mark_deleted() @@ -1958,6 +2016,7 @@ class roa_obj(rpki.sql.sql_persistent): """ Reissue ROA associated with this roa_obj. """ + if self.ca_detail is None: self.generate(publisher = publisher, fast = fast) else: @@ -1967,6 +2026,7 @@ class roa_obj(rpki.sql.sql_persistent): """ Return publication URI for a public key. """ + return self.ca_detail.ca.sia_uri + key.gSKI() + ".roa" @property @@ -1974,6 +2034,7 @@ class roa_obj(rpki.sql.sql_persistent): """ Return the publication URI for this roa_obj's ROA. """ + return self.ca_detail.ca.sia_uri + self.uri_tail @property @@ -1982,6 +2043,7 @@ class roa_obj(rpki.sql.sql_persistent): Return the tail (filename portion) of the publication URI for this roa_obj's ROA. """ + return self.cert.gSKI() + ".roa" @@ -2024,6 +2086,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent): """ Fetch self object to which this ghostbuster_obj links. """ + return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id) @property @@ -2032,6 +2095,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent): """ Fetch ca_detail object to which this ghostbuster_obj links. """ + return rpki.rpkid.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id) def __init__(self, gctx = None, self_id = None, ca_detail_id = None, vcard = None): @@ -2097,10 +2161,9 @@ class ghostbuster_obj(rpki.sql.sql_persistent): self.sql_store() logger.debug("Generating Ghostbuster record %r", self.uri) - publisher.publish( - cls = rpki.publication.ghostbuster_elt, + publisher.queue( uri = self.uri, - obj = self.ghostbuster, + new_obj = self.ghostbuster, repository = ca.parent.repository, handler = self.published_callback) if not fast: @@ -2110,6 +2173,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent): """ Check publication result. """ + pdu.raise_if_error() self.published = None self.sql_mark_dirty() @@ -2144,9 +2208,10 @@ class ghostbuster_obj(rpki.sql.sql_persistent): logger.debug("Withdrawing %r %s and revoking its EE cert", self, uri) rpki.rpkid.revoked_cert_obj.revoke(cert = cert, ca_detail = ca_detail) - publisher.withdraw(cls = rpki.publication.ghostbuster_elt, uri = uri, obj = ghostbuster, - repository = ca_detail.ca.parent.repository, - handler = False if allow_failure else None) + publisher.queue(uri = uri, + old_obj = ghostbuster, + repository = ca_detail.ca.parent.repository, + handler = False if allow_failure else None) if not regenerate: self.sql_mark_deleted() @@ -2160,6 +2225,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent): """ Reissue Ghostbuster associated with this ghostbuster_obj. """ + if self.ghostbuster is None: self.generate(publisher = publisher, fast = fast) else: @@ -2169,6 +2235,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent): """ Return publication URI for a public key. """ + return self.ca_detail.ca.sia_uri + key.gSKI() + ".gbr" @property @@ -2176,6 +2243,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent): """ Return the publication URI for this ghostbuster_obj's ghostbuster. """ + return self.ca_detail.ca.sia_uri + self.uri_tail @property @@ -2184,6 +2252,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent): Return the tail (filename portion) of the publication URI for this ghostbuster_obj's ghostbuster. """ + return self.cert.gSKI() + ".gbr" @@ -2221,6 +2290,7 @@ class ee_cert_obj(rpki.sql.sql_persistent): """ Fetch self object to which this ee_cert_obj links. """ + return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id) @property @@ -2229,6 +2299,7 @@ class ee_cert_obj(rpki.sql.sql_persistent): """ Fetch ca_detail object to which this ee_cert_obj links. """ + return rpki.rpkid.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id) @ca_detail.deleter @@ -2246,6 +2317,7 @@ class ee_cert_obj(rpki.sql.sql_persistent): Although, really, one has to ask why we don't just store g(SKI) in rpkid.sql instead of ski.... """ + return base64.urlsafe_b64encode(self.ski).rstrip("=") @gski.setter @@ -2257,6 +2329,7 @@ class ee_cert_obj(rpki.sql.sql_persistent): """ Return the publication URI for this ee_cert_obj. """ + return self.ca_detail.ca.sia_uri + self.uri_tail @property @@ -2265,6 +2338,7 @@ class ee_cert_obj(rpki.sql.sql_persistent): Return the tail (filename portion) of the publication URI for this ee_cert_obj. """ + return self.cert.gSKI() + ".cer" @classmethod @@ -2292,12 +2366,11 @@ class ee_cert_obj(rpki.sql.sql_persistent): ca_detail_id = ca_detail.ca_detail_id, cert = cert) - publisher.publish( - cls = rpki.publication.certificate_elt, - uri = self.uri, - obj = self.cert, + publisher.queue( + uri = self.uri, + new_obj = self.cert, repository = ca.parent.repository, - handler = self.published_callback) + handler = self.published_callback) self.sql_store() @@ -2316,10 +2389,9 @@ class ee_cert_obj(rpki.sql.sql_persistent): ca = ca_detail.ca logger.debug("Revoking %r %r", self, self.uri) revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail) - publisher.withdraw(cls = rpki.publication.certificate_elt, - uri = self.uri, - obj = self.cert, - repository = ca.parent.repository) + publisher.queue(uri = self.uri, + old_obj = self.cert, + repository = ca.parent.repository) self.gctx.sql.sweep() self.sql_delete() if generate_crl_and_manifest: @@ -2401,12 +2473,12 @@ class ee_cert_obj(rpki.sql.sql_persistent): self.sql_mark_dirty() - publisher.publish( - cls = rpki.publication.certificate_elt, - uri = self.uri, - obj = self.cert, + publisher.queue( + uri = self.uri, + old_obj = old_cert, + new_obj = self.cert, repository = ca_detail.ca.parent.repository, - handler = self.published_callback) + handler = self.published_callback) if must_revoke: revoked_cert_obj.revoke(cert = old_cert.cert, ca_detail = old_ca_detail) @@ -2423,6 +2495,7 @@ class ee_cert_obj(rpki.sql.sql_persistent): """ Publication callback: check result and mark published. """ + pdu.raise_if_error() self.published = None self.sql_mark_dirty() @@ -2451,29 +2524,37 @@ class publication_queue(object): if self.replace: self.uris = {} - def _add(self, uri, obj, repository, handler, make_pdu): + def queue(self, uri, repository, handler = None, old_obj = None, new_obj = None): + + assert old_obj is not None or new_obj is not None + assert old_obj is None or isinstance(old_obj, rpki.x509.uri_dispatch(uri)) + assert new_obj is None or isinstance(new_obj, rpki.x509.uri_dispatch(uri)) + rid = id(repository) if rid not in self.repositories: self.repositories[rid] = repository self.msgs[rid] = rpki.publication.msg.query() + if self.replace and uri in self.uris: - logger.debug("Removing publication duplicate <%s %r %r>", - self.uris[uri].action, self.uris[uri].uri, self.uris[uri].payload) + logger.debug("Removing publication duplicate %r", self.uris[uri]) self.msgs[rid].remove(self.uris.pop(uri)) - pdu = make_pdu(uri = uri, obj = obj) + + hash = None if old_obj is None else rpki.x509.sha256(old_obj.get_DER()).encode("hex") + + if new_obj is None: + pdu = rpki.publication.withdraw_elt.make_pdu(uri = uri, hash = hash) + else: + pdu = rpki.publication.publish_elt.make_pdu( uri = uri, hash = hash, der = new_obj.get_DER()) + if handler is not None: self.handlers[id(pdu)] = handler pdu.tag = id(pdu) + self.msgs[rid].append(pdu) + if self.replace: self.uris[uri] = pdu - def publish(self, cls, uri, obj, repository, handler = None): - return self._add( uri, obj, repository, handler, cls.make_publish) - - def withdraw(self, cls, uri, obj, repository, handler = None): - return self._add( uri, obj, repository, handler, cls.make_withdraw) - def call_pubd(self, cb, eb): def loop(iterator, rid): logger.debug("Calling pubd[%r]", self.repositories[rid]) @@ -2488,5 +2569,5 @@ class publication_queue(object): return sum(len(self.msgs[rid]) for rid in self.repositories) def empty(self): - assert (not self.msgs) == (self.size == 0) + assert (not self.msgs) == (self.size == 0), "Assertion failure: not self.msgs: %r, self.size %r" % (not self.msgs, self.size) return not self.msgs diff --git a/rpki/rpkid_tasks.py b/rpki/rpkid_tasks.py index e0bb6904..8f652fa6 100644 --- a/rpki/rpkid_tasks.py +++ b/rpki/rpkid_tasks.py @@ -308,10 +308,9 @@ class UpdateChildrenTask(AbstractTask): self.child.child_handle, child_cert.cert.gSKI(), old_resources.valid_until, irdb_resources.valid_until) child_cert.sql_delete() - self.publisher.withdraw( - cls = rpki.publication.certificate_elt, + self.publisher.queue( uri = child_cert.uri, - obj = child_cert.cert, + old_obj = child_cert.cert, repository = ca.parent.repository) ca_detail.generate_manifest(publisher = self.publisher) diff --git a/rpki/rtr/bgpdump.py b/rpki/rtr/bgpdump.py index fc3ae9df..5ffabc4d 100755 --- a/rpki/rtr/bgpdump.py +++ b/rpki/rtr/bgpdump.py @@ -295,7 +295,7 @@ def bgpdump_server_main(args): rpki.rtr.server.read_current = clock.read_current try: - server = rpki.rtr.server.ServerChannel(logger = logger) + server = rpki.rtr.server.ServerChannel(logger = logger, refresh = args.refresh, retry = args.retry, expire = args.expire) old_serial = server.get_serial() logger.debug("[Starting at serial %d (%s)]", old_serial, old_serial) while clock: diff --git a/rpki/sql.py b/rpki/sql.py index 31ed40ee..9e805ad1 100644 --- a/rpki/sql.py +++ b/rpki/sql.py @@ -118,6 +118,7 @@ class session(object): Clear the SQL object cache. Shouldn't be necessary now that the cache uses weak references, but should be harmless. """ + logger.debug("Clearing SQL cache") self.assert_pristine() self.cache.clear() @@ -126,12 +127,14 @@ class session(object): """ Assert that there are no dirty objects in the cache. """ + assert not self.dirty, "Dirty objects in SQL cache: %s" % self.dirty def sweep(self): """ Write any dirty objects out to SQL. """ + for s in self.dirty.copy(): #if s.sql_cache_debug: logger.debug("Sweeping (%s) %r", "deleting" if s.sql_deleted else "storing", s) @@ -150,6 +153,7 @@ class template(object): """ Build a SQL template. """ + type_map = dict((x[0], x[1]) for x in data_columns if isinstance(x, tuple)) data_columns = tuple(isinstance(x, tuple) and x[0] or x for x in data_columns) columns = (index_column,) + data_columns @@ -220,6 +224,7 @@ class sql_persistent(object): """ Fetch one object from SQL, based on an arbitrary SQL WHERE expression. """ + results = cls.sql_fetch_where(gctx, where, args, also_from) if len(results) == 0: return None @@ -235,6 +240,7 @@ class sql_persistent(object): """ Fetch all objects of this type from SQL. """ + return cls.sql_fetch_where(gctx, None) @classmethod @@ -242,6 +248,7 @@ class sql_persistent(object): """ Fetch objects of this type matching an arbitrary SQL WHERE expression. """ + if where is None: assert args is None and also_from is None if cls.sql_debug: @@ -269,6 +276,7 @@ class sql_persistent(object): """ Initialize one Python object from the result of a SQL query. """ + self = cls() self.gctx = gctx self.sql_decode(dict(zip(cls.sql_template.columns, row))) @@ -281,6 +289,7 @@ class sql_persistent(object): """ Mark this object as needing to be written back to SQL. """ + if self.sql_cache_debug and not self.sql_is_dirty: logger.debug("Marking %r SQL dirty", self) self.gctx.sql.dirty.add(self) @@ -289,6 +298,7 @@ class sql_persistent(object): """ Mark this object as not needing to be written back to SQL. """ + if self.sql_cache_debug and self.sql_is_dirty: logger.debug("Marking %r SQL clean", self) self.gctx.sql.dirty.discard(self) @@ -298,12 +308,14 @@ class sql_persistent(object): """ Query whether this object needs to be written back to SQL. """ + return self in self.gctx.sql.dirty def sql_mark_deleted(self): """ Mark this object as needing to be deleted in SQL. """ + self.sql_deleted = True self.sql_mark_dirty() @@ -311,6 +323,7 @@ class sql_persistent(object): """ Store this object to SQL. """ + args = self.sql_encode() if not self.sql_in_db: if self.sql_debug: @@ -333,6 +346,7 @@ class sql_persistent(object): """ Delete this object from SQL. """ + if self.sql_in_db: id = getattr(self, self.sql_template.index) # pylint: disable=W0622 if self.sql_debug: @@ -352,6 +366,7 @@ class sql_persistent(object): mapping between column names in SQL and attribute names in Python. If you need something fancier, override this. """ + d = dict((a, getattr(self, a, None)) for a in self.sql_template.columns) for i in self.sql_template.map: if d.get(i) is not None: @@ -365,6 +380,7 @@ class sql_persistent(object): between column names in SQL and attribute names in Python. If you need something fancier, override this. """ + for a in self.sql_template.columns: if vals.get(a) is not None and a in self.sql_template.map: setattr(self, a, self.sql_template.map[a].from_sql(vals[a])) @@ -375,18 +391,21 @@ class sql_persistent(object): """ Customization hook. """ + pass def sql_insert_hook(self): """ Customization hook. """ + pass def sql_update_hook(self): """ Customization hook. """ + self.sql_delete_hook() self.sql_insert_hook() @@ -394,6 +413,7 @@ class sql_persistent(object): """ Customization hook. """ + pass diff --git a/rpki/sql_schemas.py b/rpki/sql_schemas.py index 07037970..ad469204 100644 --- a/rpki/sql_schemas.py +++ b/rpki/sql_schemas.py @@ -2,35 +2,24 @@ ## @var rpkid ## SQL schema rpkid -rpkid = '''-- $Id: rpkid.sql 5845 2014-05-29 22:31:15Z sra $ +rpkid = '''-- $Id: rpkid.sql 5881 2014-07-03 16:55:02Z sra $ --- Copyright (C) 2009--2011 Internet Systems Consortium ("ISC") +-- Copyright (C) 2012--2014 Dragon Research Labs ("DRL") +-- Portions copyright (C) 2009--2011 Internet Systems Consortium ("ISC") +-- Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") -- -- Permission to use, copy, modify, and distribute this software for any -- purpose with or without fee is hereby granted, provided that the above --- copyright notice and this permission notice appear in all copies. +-- copyright notices and this permission notice appear in all copies. -- --- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH --- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY --- AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, --- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM --- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE --- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR --- PERFORMANCE OF THIS SOFTWARE. - --- Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") --- --- Permission to use, copy, modify, and distribute this software for any --- purpose with or without fee is hereby granted, provided that the above --- copyright notice and this permission notice appear in all copies. --- --- THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH --- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY --- AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, --- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM --- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE --- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR --- PERFORMANCE OF THIS SOFTWARE. +-- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL +-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL, +-- ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +-- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- SQL objects needed by the RPKI engine (rpkid.py). @@ -256,50 +245,39 @@ CREATE TABLE ee_cert ( ## @var pubd ## SQL schema pubd -pubd = '''-- $Id: pubd.sql 5757 2014-04-05 22:42:12Z sra $ +pubd = '''-- $Id: pubd.sql 5901 2014-07-17 21:57:36Z sra $ --- Copyright (C) 2009--2010 Internet Systems Consortium ("ISC") --- --- Permission to use, copy, modify, and distribute this software for any --- purpose with or without fee is hereby granted, provided that the above --- copyright notice and this permission notice appear in all copies. --- --- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH --- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY --- AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, --- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM --- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE --- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR --- PERFORMANCE OF THIS SOFTWARE. - --- Copyright (C) 2008 American Registry for Internet Numbers ("ARIN") +-- Copyright (C) 2012--2014 Dragon Research Labs ("DRL") +-- Portions copyright (C) 2009--2010 Internet Systems Consortium ("ISC") +-- Portions copyright (C) 2008 American Registry for Internet Numbers ("ARIN") -- -- Permission to use, copy, modify, and distribute this software for any -- purpose with or without fee is hereby granted, provided that the above --- copyright notice and this permission notice appear in all copies. +-- copyright notices and this permission notice appear in all copies. -- --- THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH --- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY --- AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, --- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM --- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE --- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR --- PERFORMANCE OF THIS SOFTWARE. +-- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL +-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL, +-- ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +-- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- SQL objects needed by pubd.py. --- The config table is weird because we're really only using it --- to store one BPKI CRL, but putting this here lets us use a lot of --- existing machinery and the alternatives are whacky in other ways. +-- Old tables that should just be flushed if present at all. -DROP TABLE IF EXISTS client; DROP TABLE IF EXISTS config; +DROP TABLE IF EXISTS snapshot; -CREATE TABLE config ( - config_id SERIAL NOT NULL, - bpki_crl LONGBLOB, - PRIMARY KEY (config_id) -) ENGINE=InnoDB; +-- DROP TABLE commands must be in correct (reverse dependency) order +-- to satisfy FOREIGN KEY constraints. + +DROP TABLE IF EXISTS object; +DROP TABLE IF EXISTS delta; +DROP TABLE IF EXISTS session; +DROP TABLE IF EXISTS client; CREATE TABLE client ( client_id SERIAL NOT NULL, @@ -312,6 +290,43 @@ CREATE TABLE client ( UNIQUE (client_handle) ) ENGINE=InnoDB; +CREATE TABLE session ( + session_id SERIAL NOT NULL, + uuid VARCHAR(36) NOT NULL, + serial BIGINT UNSIGNED NOT NULL, + snapshot TEXT, + hash CHAR(64), + PRIMARY KEY (session_id), + UNIQUE (uuid) +) ENGINE=InnoDB; + +CREATE TABLE delta ( + delta_id SERIAL NOT NULL, + serial BIGINT UNSIGNED NOT NULL, + xml TEXT NOT NULL, + hash CHAR(64) NOT NULL, + expires DATETIME NOT NULL, + session_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (delta_id), + CONSTRAINT delta_session_id + FOREIGN KEY (session_id) REFERENCES session (session_id) ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE object ( + object_id SERIAL NOT NULL, + uri VARCHAR(255) NOT NULL, + der LONGBLOB NOT NULL, + hash CHAR(64) NOT NULL, + client_id BIGINT UNSIGNED NOT NULL, + session_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (object_id), + CONSTRAINT object_client_id + FOREIGN KEY (client_id) REFERENCES client (client_id) ON DELETE CASCADE, + CONSTRAINT object_session_id + FOREIGN KEY (session_id) REFERENCES session (session_id) ON DELETE CASCADE, + UNIQUE (session_id, hash) +) ENGINE=InnoDB; + -- Local Variables: -- indent-tabs-mode: nil -- End: diff --git a/rpki/sundial.py b/rpki/sundial.py index 7be122c8..60037277 100644 --- a/rpki/sundial.py +++ b/rpki/sundial.py @@ -51,6 +51,7 @@ def now(): """ Get current timestamp. """ + return datetime.utcnow() class ParseFailure(Exception): @@ -69,6 +70,7 @@ class datetime(pydatetime.datetime): Convert to seconds from epoch (like time.time()). Conversion method is a bit silly, but avoids time module timezone whackiness. """ + return int(self.strftime("%s")) @classmethod @@ -76,6 +78,7 @@ class datetime(pydatetime.datetime): """ Convert from XML time representation. """ + if x is None: return None else: @@ -85,6 +88,7 @@ class datetime(pydatetime.datetime): """ Convert to XML time representation. """ + return self.strftime("%Y-%m-%dT%H:%M:%SZ") def __str__(self): @@ -96,6 +100,7 @@ class datetime(pydatetime.datetime): Convert a datetime.datetime object into this subclass. This is whacky due to the weird constructors for datetime. """ + return cls.combine(x.date(), x.time()) def to_datetime(self): @@ -104,6 +109,7 @@ class datetime(pydatetime.datetime): shouldn't be necessary, but convincing SQL interfaces to use subclasses of datetime can be hard. """ + return pydatetime.datetime(year = self.year, month = self.month, day = self.day, hour = self.hour, minute = self.minute, second = self.second, microsecond = 0, tzinfo = None) @@ -115,6 +121,7 @@ class datetime(pydatetime.datetime): Convert from the format OpenSSL's command line tool uses into this subclass. May require rewriting if we run into locale problems. """ + if x.startswith("notBefore=") or x.startswith("notAfter="): x = x.partition("=")[2] return cls.strptime(x, "%b %d %H:%M:%S %Y GMT") @@ -124,24 +131,28 @@ class datetime(pydatetime.datetime): """ Convert from SQL storage format. """ + return cls.from_datetime(x) def to_sql(self): """ Convert to SQL storage format. """ + return self.to_datetime() def later(self, other): """ Return the later of two timestamps. """ + return other if other > self else self def earlier(self, other): """ Return the earlier of two timestamps. """ + return other if other < self else self def __add__(self, y): return _cast(pydatetime.datetime.__add__(self, y)) @@ -216,6 +227,7 @@ class timedelta(pydatetime.timedelta): """ Parse text into a timedelta object. """ + if not isinstance(arg, str): return cls(seconds = arg) elif arg.isdigit(): @@ -237,6 +249,7 @@ class timedelta(pydatetime.timedelta): """ Convert a timedelta interval to seconds. """ + return self.days * 24 * 60 * 60 + self.seconds @classmethod @@ -244,6 +257,7 @@ class timedelta(pydatetime.timedelta): """ Convert a datetime.timedelta object into this subclass. """ + return cls(days = x.days, seconds = x.seconds, microseconds = x.microseconds) def __abs__(self): return _cast(pydatetime.timedelta.__abs__(self)) @@ -264,6 +278,7 @@ def _cast(x): """ Cast result of arithmetic operations back into correct subtype. """ + if isinstance(x, pydatetime.datetime): return datetime.from_datetime(x) if isinstance(x, pydatetime.timedelta): diff --git a/rpki/up_down.py b/rpki/up_down.py index 73a0ae99..9fb627a1 100644 --- a/rpki/up_down.py +++ b/rpki/up_down.py @@ -50,6 +50,7 @@ class base_elt(object): Some elements have no attributes and we only care about their text content. """ + pass def endElement(self, stack, name, text): @@ -58,12 +59,14 @@ class base_elt(object): If we don't need to do anything else, just pop the stack. """ + stack.pop() def make_elt(self, name, *attrs): """ Construct a element, copying over a set of attributes. """ + elt = lxml.etree.Element(xmlns + name, nsmap = nsmap) for key in attrs: val = getattr(self, key, None) @@ -75,6 +78,7 @@ class base_elt(object): """ Construct a sub-element with Base64 text content. """ + if value is not None and not value.empty(): lxml.etree.SubElement(elt, xmlns + name, nsmap = nsmap).text = value.get_Base64() @@ -82,12 +86,14 @@ class base_elt(object): """ Default PDU handler to catch unexpected types. """ + raise rpki.exceptions.BadQuery("Unexpected query type %s" % q_msg.type) def check_response(self): """ Placeholder for response checking. """ + pass class multi_uri(list): @@ -99,6 +105,7 @@ class multi_uri(list): """ Initialize a set of URIs, which includes basic some syntax checking. """ + list.__init__(self) if isinstance(ini, (list, tuple)): self[:] = ini @@ -114,12 +121,14 @@ class multi_uri(list): """ Convert a multi_uri back to a string representation. """ + return ",".join(self) def rsync(self): """ Find first rsync://... URI in self. """ + for s in self: if s.startswith("rsync://"): return s @@ -134,6 +143,7 @@ class certificate_elt(base_elt): """ Handle attributes of <certificate/> element. """ + assert name == "certificate", "Unexpected name %s, stack %s" % (name, stack) self.cert_url = multi_uri(attrs["cert_url"]) self.req_resource_set_as = rpki.resource_set.resource_set_as(attrs.get("req_resource_set_as")) @@ -144,6 +154,7 @@ class certificate_elt(base_elt): """ Handle text content of a <certificate/> element. """ + assert name == "certificate", "Unexpected name %s, stack %s" % (name, stack) self.cert = rpki.x509.X509(Base64 = text) stack.pop() @@ -152,6 +163,7 @@ class certificate_elt(base_elt): """ Generate a <certificate/> element. """ + elt = self.make_elt("certificate", "cert_url", "req_resource_set_as", "req_resource_set_ipv4", "req_resource_set_ipv6") elt.text = self.cert.get_Base64() @@ -168,6 +180,7 @@ class class_elt(base_elt): """ Initialize class_elt. """ + base_elt.__init__(self) self.certs = [] @@ -175,6 +188,7 @@ class class_elt(base_elt): """ Handle <class/> elements and their children. """ + if name == "certificate": cert = certificate_elt() self.certs.append(cert) @@ -194,6 +208,7 @@ class class_elt(base_elt): """ Handle <class/> elements and their children. """ + if name == "issuer": self.issuer = rpki.x509.X509(Base64 = text) else: @@ -204,10 +219,11 @@ class class_elt(base_elt): """ Generate a <class/> element. """ + elt = self.make_elt("class", "class_name", "cert_url", "resource_set_as", "resource_set_ipv4", "resource_set_ipv6", "resource_set_notafter", "suggested_sia_head") - elt.extend([i.toXML() for i in self.certs]) + elt.extend(i.toXML() for i in self.certs) self.make_b64elt(elt, "issuer", self.issuer) return elt @@ -215,6 +231,7 @@ class class_elt(base_elt): """ Build a resource_bag from from this <class/> element. """ + return rpki.resource_set.resource_bag(self.resource_set_as, self.resource_set_ipv4, self.resource_set_ipv6, @@ -224,6 +241,7 @@ class class_elt(base_elt): """ Set resources of this class element from a resource_bag. """ + self.resource_set_as = bag.asn self.resource_set_ipv4 = bag.v4 self.resource_set_ipv6 = bag.v6 @@ -235,7 +253,10 @@ class list_pdu(base_elt): """ def toXML(self): - """Generate (empty) payload of "list" PDU.""" + """ + Generate (empty) payload of "list" PDU. + """ + return [] def serve_pdu(self, q_msg, r_msg, child, callback, errback): @@ -282,6 +303,7 @@ class list_pdu(base_elt): """ Send a "list" query to parent. """ + try: logger.info('Sending "list" request to parent %s', parent.parent_handle) parent.query_up_down(cls(), cb, eb) @@ -299,6 +321,7 @@ class class_response_syntax(base_elt): """ Initialize class_response_syntax. """ + base_elt.__init__(self) self.classes = [] @@ -306,6 +329,7 @@ class class_response_syntax(base_elt): """ Handle "list_response" and "issue_response" PDUs. """ + assert name == "class", "Unexpected name %s, stack %s" % (name, stack) c = class_elt() self.classes.append(c) @@ -313,13 +337,17 @@ class class_response_syntax(base_elt): c.startElement(stack, name, attrs) def toXML(self): - """Generate payload of "list_response" and "issue_response" PDUs.""" + """ + Generate payload of "list_response" and "issue_response" PDUs. + """ + return [c.toXML() for c in self.classes] class list_response_pdu(class_response_syntax): """ Up-Down protocol "list_response" PDU. """ + pass class issue_pdu(base_elt): @@ -331,6 +359,7 @@ class issue_pdu(base_elt): """ Handle "issue" PDU. """ + assert name == "request", "Unexpected name %s, stack %s" % (name, stack) self.class_name = attrs["class_name"] self.req_resource_set_as = rpki.resource_set.resource_set_as(attrs.get("req_resource_set_as")) @@ -341,6 +370,7 @@ class issue_pdu(base_elt): """ Handle "issue" PDU. """ + assert name == "request", "Unexpected name %s, stack %s" % (name, stack) self.pkcs10 = rpki.x509.PKCS10(Base64 = text) stack.pop() @@ -349,6 +379,7 @@ class issue_pdu(base_elt): """ Generate payload of "issue" PDU. """ + elt = self.make_elt("request", "class_name", "req_resource_set_as", "req_resource_set_ipv4", "req_resource_set_ipv6") elt.text = self.pkcs10.get_Base64() @@ -432,6 +463,7 @@ class issue_pdu(base_elt): """ Send an "issue" request to parent associated with ca. """ + assert ca_detail is not None and ca_detail.state in ("pending", "active") self = cls() self.class_name = ca.parent_resource_class @@ -453,6 +485,7 @@ class issue_response_pdu(class_response_syntax): Check whether this looks like a reasonable issue_response PDU. XML schema should be tighter for this response. """ + if len(self.classes) != 1 or len(self.classes[0].certs) != 1: raise rpki.exceptions.BadIssueResponse @@ -462,12 +495,18 @@ class revoke_syntax(base_elt): """ def startElement(self, stack, name, attrs): - """Handle "revoke" PDU.""" + """ + Handle "revoke" PDU. + """ + self.class_name = attrs["class_name"] self.ski = attrs["ski"] def toXML(self): - """Generate payload of "revoke" PDU.""" + """ + Generate payload of "revoke" PDU. + """ + return [self.make_elt("key", "class_name", "ski")] class revoke_pdu(revoke_syntax): @@ -479,6 +518,7 @@ class revoke_pdu(revoke_syntax): """ Convert g(SKI) encoding from PDU back to raw SKI. """ + return base64.urlsafe_b64decode(self.ski + "=") def serve_pdu(self, q_msg, r_msg, child, cb, eb): @@ -505,6 +545,7 @@ class revoke_pdu(revoke_syntax): """ Send a "revoke" request for certificate(s) named by gski to parent associated with ca. """ + parent = ca.parent self = cls() self.class_name = ca.parent_resource_class @@ -545,6 +586,7 @@ class error_response_pdu(base_elt): """ Initialize an error_response PDU from an exception object. """ + base_elt.__init__(self) if exception is not None: logger.debug("Constructing up-down error response from exception %s", exception) @@ -570,6 +612,7 @@ class error_response_pdu(base_elt): """ Handle "error_response" PDU. """ + if name == "status": code = int(text) if code not in self.codes: @@ -586,6 +629,7 @@ class error_response_pdu(base_elt): """ Generate payload of "error_response" PDU. """ + assert self.status in self.codes elt = self.make_elt("status") elt.text = str(self.status) @@ -602,6 +646,7 @@ class error_response_pdu(base_elt): Handle an error response. For now, just raise an exception, perhaps figure out something more clever to do later. """ + raise rpki.exceptions.UpstreamError(self.codes[self.status]) class message_pdu(base_elt): @@ -628,6 +673,7 @@ class message_pdu(base_elt): """ Generate payload of message PDU. """ + elt = self.make_elt("message", "version", "sender", "recipient", "type") elt.extend(self.payload.toXML()) return elt @@ -640,6 +686,7 @@ class message_pdu(base_elt): attribute, so after some basic checks we have to instantiate the right class object to handle whatever kind of PDU this is. """ + assert name == "message", "Unexpected name %s, stack %s" % (name, stack) assert self.version == int(attrs["version"]) self.sender = attrs["sender"] @@ -652,6 +699,7 @@ class message_pdu(base_elt): """ Convert a message PDU to a string. """ + return lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "UTF-8") def serve_top_level(self, child, callback): @@ -683,12 +731,14 @@ class message_pdu(base_elt): """ Log query we're handling. Separate method so rootd can override. """ + logger.info("Serving %s query from child %s [sender %s, recipient %s]", self.type, child.child_handle, self.sender, self.recipient) def serve_error(self, exception): """ Generate an error_response message PDU. """ + r_msg = message_pdu() r_msg.sender = self.recipient r_msg.recipient = self.sender @@ -701,6 +751,7 @@ class message_pdu(base_elt): """ Construct one message PDU. """ + assert not cls.type2name[type(payload)].endswith("_response") if sender is None: sender = "tweedledee" diff --git a/rpki/x509.py b/rpki/x509.py index a7e4d17a..44d9484e 100644 --- a/rpki/x509.py +++ b/rpki/x509.py @@ -57,6 +57,7 @@ def base64_with_linebreaks(der): Encode DER (really, anything) as Base64 text, with linebreaks to keep the result (sort of) readable. """ + b = base64.b64encode(der) n = len(b) return "\n" + "\n".join(b[i : min(i + 64, n)] for i in xrange(0, n, 64)) + "\n" @@ -81,6 +82,27 @@ def first_rsync_uri(xia): return uri return None +def sha1(data): + """ + Calculate SHA-1 digest of some data. + Convenience wrapper around rpki.POW.Digest class. + """ + + d = rpki.POW.Digest(rpki.POW.SHA1_DIGEST) + d.update(data) + return d.digest() + +def sha256(data): + """ + Calculate SHA-256 digest of some data. + Convenience wrapper around rpki.POW.Digest class. + """ + + d = rpki.POW.Digest(rpki.POW.SHA256_DIGEST) + d.update(data) + return d.digest() + + class X501DN(object): """ Class to hold an X.501 Distinguished Name. @@ -207,12 +229,14 @@ class DER_object(object): """ Test whether this object is empty. """ + return all(getattr(self, a, None) is None for a in self.formats) def clear(self): """ Make this object empty. """ + for a in self.formats + self.other_clear: setattr(self, a, None) self.filename = None @@ -223,6 +247,7 @@ class DER_object(object): """ Initialize a DER_object. """ + self.clear() if len(kw): self.set(**kw) @@ -271,6 +296,7 @@ class DER_object(object): """ Check for updates to a DER object that auto-updates from a file. """ + if self.filename is None: return try: @@ -301,6 +327,7 @@ class DER_object(object): """ Perform basic checks on a DER object. """ + self.check_auto_update() assert not self.empty() @@ -309,6 +336,7 @@ class DER_object(object): Set the POW value of this object based on a PEM input value. Subclasses may need to override this. """ + assert self.empty() self.POW = self.POW_class.pemRead(pem) @@ -317,6 +345,7 @@ class DER_object(object): Get the DER value of this object. Subclasses may need to override this method. """ + self.check() if self.DER: return self.DER @@ -330,6 +359,7 @@ class DER_object(object): Get the rpki.POW value of this object. Subclasses may need to override this method. """ + self.check() if not self.POW: # pylint: disable=E0203 self.POW = self.POW_class.derRead(self.get_DER()) @@ -339,18 +369,21 @@ class DER_object(object): """ Get the Base64 encoding of the DER value of this object. """ + return base64_with_linebreaks(self.get_DER()) def get_PEM(self): """ Get the PEM representation of this object. """ + return self.get_POW().pemWrite() def __cmp__(self, other): """ Compare two DER-encoded objects. """ + if self is None and other is None: return 0 elif self is None: @@ -367,6 +400,7 @@ class DER_object(object): Return hexadecimal string representation of SKI for this object. Only work for subclasses that implement get_SKI(). """ + ski = self.get_SKI() return ":".join(("%02X" % ord(i) for i in ski)) if ski else "" @@ -375,6 +409,7 @@ class DER_object(object): Calculate g(SKI) for this object. Only work for subclasses that implement get_SKI(). """ + return base64.urlsafe_b64encode(self.get_SKI()).rstrip("=") def hAKI(self): @@ -382,6 +417,7 @@ class DER_object(object): Return hexadecimal string representation of AKI for this object. Only work for subclasses that implement get_AKI(). """ + aki = self.get_AKI() return ":".join(("%02X" % ord(i) for i in aki)) if aki else "" @@ -390,24 +426,28 @@ class DER_object(object): Calculate g(AKI) for this object. Only work for subclasses that implement get_AKI(). """ + return base64.urlsafe_b64encode(self.get_AKI()).rstrip("=") def get_AKI(self): """ Get the AKI extension from this object, if supported. """ + return self.get_POW().getAKI() def get_SKI(self): """ Get the SKI extension from this object, if supported. """ + return self.get_POW().getSKI() def get_EKU(self): """ Get the Extended Key Usage extension from this object, if supported. """ + return self.get_POW().getEKU() def get_SIA(self): @@ -415,6 +455,7 @@ class DER_object(object): Get the SIA extension from this object. Only works for subclasses that support getSIA(). """ + return self.get_POW().getSIA() def get_sia_directory_uri(self): @@ -422,6 +463,7 @@ class DER_object(object): Get SIA directory (id-ad-caRepository) URI from this object. Only works for subclasses that support getSIA(). """ + sia = self.get_POW().getSIA() return None if sia is None else first_rsync_uri(sia[0]) @@ -430,6 +472,7 @@ class DER_object(object): Get SIA manifest (id-ad-rpkiManifest) URI from this object. Only works for subclasses that support getSIA(). """ + sia = self.get_POW().getSIA() return None if sia is None else first_rsync_uri(sia[1]) @@ -438,6 +481,7 @@ class DER_object(object): Get SIA object (id-ad-signedObject) URI from this object. Only works for subclasses that support getSIA(). """ + sia = self.get_POW().getSIA() return None if sia is None else first_rsync_uri(sia[2]) @@ -446,6 +490,7 @@ class DER_object(object): Get the SIA extension from this object. Only works for subclasses that support getAIA(). """ + return self.get_POW().getAIA() def get_aia_uri(self): @@ -453,6 +498,7 @@ class DER_object(object): Get AIA (id-ad-caIssuers) URI from this object. Only works for subclasses that support getAIA(). """ + return first_rsync_uri(self.get_POW().getAIA()) def get_basicConstraints(self): @@ -460,6 +506,7 @@ class DER_object(object): Get the basicConstraints extension from this object. Only works for subclasses that support getExtension(). """ + return self.get_POW().getBasicConstraints() def is_CA(self): @@ -467,6 +514,7 @@ class DER_object(object): Return True if and only if object has the basicConstraints extension and its cA value is true. """ + basicConstraints = self.get_basicConstraints() return basicConstraints is not None and basicConstraints[0] @@ -474,6 +522,7 @@ class DER_object(object): """ Get RFC 3779 resources as rpki.resource_set objects. """ + resources = rpki.resource_set.resource_bag.from_POW_rfc3779(self.get_POW().getRFC3779()) try: resources.valid_until = self.getNotAfter() @@ -486,12 +535,14 @@ class DER_object(object): """ Convert from SQL storage format. """ + return cls(DER = x) def to_sql(self): """ Convert to SQL storage format. """ + return self.get_DER() def dumpasn1(self): @@ -522,11 +573,11 @@ class DER_object(object): provide more information, but should make sure to include at least this information at the start of the tracking line. """ + try: - d = rpki.POW.Digest(rpki.POW.SHA1_DIGEST) - d.update(self.get_DER()) - return "%s %s %s" % (uri, self.creation_timestamp, - "".join(("%02X" % ord(b) for b in d.digest()))) + return "%s %s %s" % (uri, + self.creation_timestamp, + "".join(("%02X" % ord(b) for b in sha1(self.get_DER())))) except: # pylint: disable=W0702 return uri @@ -534,12 +585,14 @@ class DER_object(object): """ Pickling protocol -- pickle the DER encoding. """ + return self.get_DER() def __setstate__(self, state): """ Pickling protocol -- unpickle the DER encoding. """ + self.set(DER = state) class X509(DER_object): @@ -559,48 +612,56 @@ class X509(DER_object): """ Get the issuer of this certificate. """ + return X501DN.from_POW(self.get_POW().getIssuer()) def getSubject(self): """ Get the subject of this certificate. """ + return X501DN.from_POW(self.get_POW().getSubject()) def getNotBefore(self): """ Get the inception time of this certificate. """ + return self.get_POW().getNotBefore() def getNotAfter(self): """ Get the expiration time of this certificate. """ + return self.get_POW().getNotAfter() def getSerial(self): """ Get the serial number of this certificate. """ + return self.get_POW().getSerial() def getPublicKey(self): """ Extract the public key from this certificate. """ + return PublicKey(POW = self.get_POW().getPublicKey()) def get_SKI(self): """ Get the SKI extension from this object. """ + return self.get_POW().getSKI() def expired(self): """ Test whether this certificate has expired. """ + return self.getNotAfter() <= rpki.sundial.now() def issue(self, keypair, subject_key, serial, sia, aia, crldp, notAfter, @@ -743,6 +804,7 @@ class X509(DER_object): """ Issue a BPKI certificate with values taking from an existing certificate. """ + return self.bpki_certify( keypair = keypair, subject_name = source_cert.getSubject(), @@ -759,6 +821,7 @@ class X509(DER_object): """ Issue a self-signed BPKI CA certificate. """ + return cls._bpki_certify( keypair = keypair, issuer_name = subject_name, @@ -775,6 +838,7 @@ class X509(DER_object): """ Issue a normal BPKI certificate. """ + assert keypair.get_public() == self.getPublicKey() return self._bpki_certify( keypair = keypair, @@ -833,6 +897,7 @@ class X509(DER_object): allowed cases. So this method allows X509, None, lists, and tuples, and returns a tuple of X509 objects. """ + if isinstance(chain, cls): chain = (chain,) return tuple(x for x in chain if x is not None) @@ -842,6 +907,7 @@ class X509(DER_object): """ Time at which this object was created. """ + return self.getNotBefore() class PKCS10(DER_object): @@ -869,6 +935,7 @@ class PKCS10(DER_object): """ Get the DER value of this certification request. """ + self.check() if self.DER: return self.DER @@ -881,6 +948,7 @@ class PKCS10(DER_object): """ Get the rpki.POW value of this certification request. """ + self.check() if not self.POW: # pylint: disable=E0203 self.POW = rpki.POW.PKCS10.derRead(self.get_DER()) @@ -890,18 +958,21 @@ class PKCS10(DER_object): """ Extract the subject name from this certification request. """ + return X501DN.from_POW(self.get_POW().getSubject()) def getPublicKey(self): """ Extract the public key from this certification request. """ + return PublicKey(POW = self.get_POW().getPublicKey()) def get_SKI(self): """ Compute SKI for public key from this certification request. """ + return self.getPublicKey().get_SKI() @@ -1150,6 +1221,7 @@ class PrivateKey(DER_object): """ Get the DER value of this keypair. """ + self.check() if self.DER: return self.DER @@ -1162,6 +1234,7 @@ class PrivateKey(DER_object): """ Get the rpki.POW value of this keypair. """ + self.check() if not self.POW: # pylint: disable=E0203 self.POW = rpki.POW.Asymmetric.derReadPrivate(self.get_DER()) @@ -1171,12 +1244,14 @@ class PrivateKey(DER_object): """ Get the PEM representation of this keypair. """ + return self.get_POW().pemWritePrivate() def _set_PEM(self, pem): """ Set the POW value of this keypair from a PEM string. """ + assert self.empty() self.POW = self.POW_class.pemReadPrivate(pem) @@ -1184,18 +1259,21 @@ class PrivateKey(DER_object): """ Get the DER encoding of the public key from this keypair. """ + return self.get_POW().derWritePublic() def get_SKI(self): """ Calculate the SKI of this keypair. """ + return self.get_POW().calculateSKI() def get_public(self): """ Convert the public key of this keypair into a PublicKey object. """ + return PublicKey(DER = self.get_public_DER()) class PublicKey(DER_object): @@ -1209,6 +1287,7 @@ class PublicKey(DER_object): """ Get the DER value of this public key. """ + self.check() if self.DER: return self.DER @@ -1221,6 +1300,7 @@ class PublicKey(DER_object): """ Get the rpki.POW value of this public key. """ + self.check() if not self.POW: # pylint: disable=E0203 self.POW = rpki.POW.Asymmetric.derReadPublic(self.get_DER()) @@ -1230,12 +1310,14 @@ class PublicKey(DER_object): """ Get the PEM representation of this public key. """ + return self.get_POW().pemWritePublic() def _set_PEM(self, pem): """ Set the POW value of this public key from a PEM string. """ + assert self.empty() self.POW = self.POW_class.pemReadPublic(pem) @@ -1243,6 +1325,7 @@ class PublicKey(DER_object): """ Calculate the SKI of this public key. """ + return self.get_POW().calculateSKI() class KeyParams(DER_object): @@ -1266,6 +1349,7 @@ class RSA(PrivateKey): """ Generate a new keypair. """ + if not quiet: logger.debug("Generating new %d-bit RSA key", keylength) if generate_insecure_debug_only_rsa_key is not None: @@ -1348,6 +1432,7 @@ class CMS_object(DER_object): """ Get the DER value of this CMS_object. """ + self.check() if self.DER: return self.DER @@ -1360,6 +1445,7 @@ class CMS_object(DER_object): """ Get the rpki.POW value of this CMS_object. """ + self.check() if not self.POW: # pylint: disable=E0203 self.POW = self.POW_class.derRead(self.get_DER()) @@ -1369,6 +1455,7 @@ class CMS_object(DER_object): """ Extract signingTime from CMS signed attributes. """ + return self.get_POW().signingTime() def verify(self, ta): @@ -1540,6 +1627,7 @@ class CMS_object(DER_object): """ Time at which this object was created. """ + return self.get_signingTime() @@ -1561,6 +1649,7 @@ class Wrapped_CMS_object(CMS_object): """ Get the inner content of this Wrapped_CMS_object. """ + if self.content is None: raise rpki.exceptions.CMSContentNotSet("Inner content of CMS object %r is not set" % self) return self.content @@ -1569,6 +1658,7 @@ class Wrapped_CMS_object(CMS_object): """ Set the (inner) content of this Wrapped_CMS_object, clearing the wrapper. """ + self.clear() self.content = content @@ -1651,12 +1741,14 @@ class SignedManifest(DER_CMS_object): """ Get thisUpdate value from this manifest. """ + return self.get_POW().getThisUpdate() def getNextUpdate(self): """ Get nextUpdate value from this manifest. """ + return self.get_POW().getNextUpdate() @classmethod @@ -1667,9 +1759,7 @@ class SignedManifest(DER_CMS_object): filelist = [] for name, obj in names_and_objs: - d = rpki.POW.Digest(rpki.POW.SHA256_DIGEST) - d.update(obj.get_DER()) - filelist.append((name.rpartition("/")[2], d.digest())) + filelist.append((name.rpartition("/")[2], sha256(obj.get_DER()))) filelist.sort(key = lambda x: x[0]) obj = cls.POW_class() @@ -1697,6 +1787,7 @@ class ROA(DER_CMS_object): """ Build a ROA. """ + ipv4 = ipv4.to_POW_roa_tuple() if ipv4 else None ipv6 = ipv6.to_POW_roa_tuple() if ipv6 else None obj = cls.POW_class() @@ -1712,6 +1803,7 @@ class ROA(DER_CMS_object): Return a string containing data we want to log when tracking how objects move through the RPKI system. """ + msg = DER_CMS_object.tracking_data(self, uri) try: self.extract_if_needed() @@ -1794,6 +1886,7 @@ class XML_CMS_object(Wrapped_CMS_object): """ Encode inner content for signing. """ + return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, @@ -1803,12 +1896,14 @@ class XML_CMS_object(Wrapped_CMS_object): """ Decode XML and set inner content. """ + self.content = lxml.etree.fromstring(xml) def pretty_print_content(self): """ Pretty print XML content of this message. """ + return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, @@ -1818,6 +1913,7 @@ class XML_CMS_object(Wrapped_CMS_object): """ Handle XML RelaxNG schema check. """ + try: self.schema.assertValid(self.get_content()) except lxml.etree.DocumentInvalid: @@ -1830,6 +1926,7 @@ class XML_CMS_object(Wrapped_CMS_object): """ Write DER of current message to disk, for debugging. """ + f = open(prefix + rpki.sundial.now().isoformat() + "Z.cms", "wb") f.write(self.get_DER()) f.close() @@ -1838,6 +1935,7 @@ class XML_CMS_object(Wrapped_CMS_object): """ Wrap an XML PDU in CMS and return its DER encoding. """ + if self.saxify is None: self.set_content(msg) else: @@ -1853,6 +1951,7 @@ class XML_CMS_object(Wrapped_CMS_object): """ Unwrap a CMS-wrapped XML PDU and return Python objects. """ + if self.dump_inbound_cms: self.dump_inbound_cms.dump(self) self.verify(ta) @@ -1869,6 +1968,7 @@ class XML_CMS_object(Wrapped_CMS_object): timestamp. Raises an exception if the recorded timestamp is more recent, otherwise returns the new timestamp. """ + new_timestamp = self.get_signingTime() if timestamp is not None and timestamp > new_timestamp: if context: @@ -1884,6 +1984,7 @@ class XML_CMS_object(Wrapped_CMS_object): "last_cms_timestamp" field of an SQL object and stores the new timestamp back in that same field. """ + obj.last_cms_timestamp = self.check_replay(obj.last_cms_timestamp, *context) obj.sql_mark_dirty() @@ -1913,6 +2014,7 @@ class Ghostbuster(Wrapped_CMS_object): Encode inner content for signing. At the moment we're treating the VCard as an opaque byte string, so no encoding needed here. """ + return self.get_content() def decode(self, vcard): @@ -1920,6 +2022,7 @@ class Ghostbuster(Wrapped_CMS_object): Decode XML and set inner content. At the moment we're treating the VCard as an opaque byte string, so no encoding needed here. """ + self.content = vcard @classmethod @@ -1927,6 +2030,7 @@ class Ghostbuster(Wrapped_CMS_object): """ Build a Ghostbuster record. """ + self = cls() self.set_content(vcard) self.sign(keypair, certs) @@ -1944,6 +2048,7 @@ class CRL(DER_object): """ Get the DER value of this CRL. """ + self.check() if self.DER: return self.DER @@ -1956,6 +2061,7 @@ class CRL(DER_object): """ Get the rpki.POW value of this CRL. """ + self.check() if not self.POW: # pylint: disable=E0203 self.POW = rpki.POW.CRL.derRead(self.get_DER()) @@ -1965,24 +2071,28 @@ class CRL(DER_object): """ Get thisUpdate value from this CRL. """ + return self.get_POW().getThisUpdate() def getNextUpdate(self): """ Get nextUpdate value from this CRL. """ + return self.get_POW().getNextUpdate() def getIssuer(self): """ Get issuer value of this CRL. """ + return X501DN.from_POW(self.get_POW().getIssuer()) def getCRLNumber(self): """ Get CRL Number value for this CRL. """ + return self.get_POW().getCRLNumber() @classmethod @@ -1990,6 +2100,7 @@ class CRL(DER_object): """ Generate a new CRL. """ + crl = rpki.POW.CRL() crl.setVersion(version) crl.setIssuer(issuer.getSubject().get_POW()) @@ -2006,6 +2117,7 @@ class CRL(DER_object): """ Time at which this object was created. """ + return self.getThisUpdate() ## @var uri_dispatch_map @@ -2024,4 +2136,5 @@ def uri_dispatch(uri): """ Return the Python class object corresponding to a given URI. """ + return uri_dispatch_map[os.path.splitext(uri)[1]] diff --git a/rpki/xml_utils.py b/rpki/xml_utils.py index c276ce98..3a7b919a 100644 --- a/rpki/xml_utils.py +++ b/rpki/xml_utils.py @@ -56,6 +56,7 @@ class sax_handler(xml.sax.handler.ContentHandler): """ Initialize SAX handler. """ + xml.sax.handler.ContentHandler.__init__(self) self.text = "" self.stack = [] @@ -64,18 +65,21 @@ class sax_handler(xml.sax.handler.ContentHandler): """ Redirect startElementNS() events to startElement(). """ + return self.startElement(name[1], attrs) def endElementNS(self, name, qname): """ Redirect endElementNS() events to endElement(). """ + return self.endElement(name[1]) def characters(self, content): """ Accumulate a chuck of element content (text). """ + self.text += content def startElement(self, name, attrs): @@ -111,6 +115,7 @@ class sax_handler(xml.sax.handler.ContentHandler): Handle endElement() events. Mostly this means handling any accumulated element text. """ + text = self.text.encode("ascii").strip() self.text = "" self.stack[-1].endElement(self.stack, name, text) @@ -120,6 +125,7 @@ class sax_handler(xml.sax.handler.ContentHandler): """ Create a one-off SAX parser, parse an ETree, return the result. """ + self = cls() lxml.sax.saxify(elt, self) return self.result @@ -128,6 +134,7 @@ class sax_handler(xml.sax.handler.ContentHandler): """ Handle top-level PDU for this protocol. """ + assert name == self.name and attrs["version"] == self.version return self.pdu() @@ -154,6 +161,7 @@ class base_elt(object): """ Default startElement() handler: just process attributes. """ + if name not in self.elements: assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) @@ -162,6 +170,7 @@ class base_elt(object): """ Default endElement() handler: just pop the stack. """ + assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) stack.pop() @@ -169,12 +178,14 @@ class base_elt(object): """ Default toXML() element generator. """ + return self.make_elt() def read_attrs(self, attrs): """ Template-driven attribute reader. """ + for key in self.attributes: val = attrs.get(key, None) if isinstance(val, str) and val.isdigit() and not key.endswith("_handle"): @@ -187,6 +198,7 @@ class base_elt(object): """ XML element constructor. """ + elt = lxml.etree.Element(self.xmlns + self.element_name, nsmap = self.nsmap) for key in self.attributes: val = getattr(self, key, None) @@ -201,6 +213,7 @@ class base_elt(object): """ Constructor for Base64-encoded subelement. """ + if value is not None and not value.empty(): lxml.etree.SubElement(elt, self.xmlns + name, nsmap = self.nsmap).text = value.get_Base64() @@ -208,6 +221,7 @@ class base_elt(object): """ Convert a base_elt object to string format. """ + return lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii") @classmethod @@ -215,6 +229,7 @@ class base_elt(object): """ Generic PDU constructor. """ + self = cls() for k, v in kargs.items(): if isinstance(v, bool): @@ -235,6 +250,7 @@ class text_elt(base_elt): """ Extract text from parsed XML. """ + base_elt.endElement(self, stack, name, text) setattr(self, self.text_attribute, text) @@ -242,6 +258,7 @@ class text_elt(base_elt): """ Insert text into generated XML. """ + elt = self.make_elt() elt.text = getattr(self, self.text_attribute) or None return elt @@ -258,6 +275,7 @@ class data_elt(base_elt): that sub-elements are Base64-encoded using the sql_template mechanism. """ + if name in self.elements: elt_type = self.sql_template.map.get(name) assert elt_type is not None, "Couldn't find element type for %s, stack %s" % (name, stack) @@ -271,6 +289,7 @@ class data_elt(base_elt): Default element generator for SQL-based objects. This assumes that sub-elements are Base64-encoded DER objects. """ + elt = self.make_elt() for i in self.elements: self.make_b64elt(elt, i, getattr(self, i, None)) @@ -280,6 +299,7 @@ class data_elt(base_elt): """ Construct a reply PDU. """ + if r_pdu is None: r_pdu = self.__class__() self.make_reply_clone_hook(r_pdu) @@ -297,6 +317,7 @@ class data_elt(base_elt): """ Overridable hook. """ + pass def serve_fetch_one(self): @@ -304,6 +325,7 @@ class data_elt(base_elt): Find the object on which a get, set, or destroy method should operate. """ + r = self.serve_fetch_one_maybe() if r is None: raise rpki.exceptions.NotFound @@ -313,12 +335,14 @@ class data_elt(base_elt): """ Overridable hook. """ + cb() def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): """ Overridable hook. """ + cb() def serve_create(self, r_msg, cb, eb): @@ -371,6 +395,7 @@ class data_elt(base_elt): """ Handle a get action. """ + r_pdu = self.serve_fetch_one() self.make_reply(r_pdu) r_msg.append(r_pdu) @@ -380,6 +405,7 @@ class data_elt(base_elt): """ Handle a list action for non-self objects. """ + for r_pdu in self.serve_fetch_all(): self.make_reply(r_pdu) r_msg.append(r_pdu) @@ -389,12 +415,14 @@ class data_elt(base_elt): """ Overridable hook. """ + cb() def serve_destroy(self, r_msg, cb, eb): """ Handle a destroy action. """ + def done(): db_pdu.sql_delete() r_msg.append(self.make_reply()) @@ -406,6 +434,7 @@ class data_elt(base_elt): """ Action dispatch handler. """ + dispatch = { "create" : self.serve_create, "set" : self.serve_set, "get" : self.serve_get, @@ -419,6 +448,7 @@ class data_elt(base_elt): """ Uniform handling for unimplemented control operations. """ + unimplemented = [x for x in controls if getattr(self, x, False)] if unimplemented: raise rpki.exceptions.NotImplementedYet("Unimplemented control %s" % ", ".join(unimplemented)) @@ -432,6 +462,7 @@ class msg(list): """ Handle top-level PDU. """ + if name == "msg": assert self.version == int(attrs["version"]) self.type = attrs["type"] @@ -445,6 +476,7 @@ class msg(list): """ Handle top-level PDU. """ + assert name == "msg", "Unexpected name %s, stack %s" % (name, stack) assert len(stack) == 1 stack.pop() @@ -453,14 +485,16 @@ class msg(list): """ Convert msg object to string. """ + return lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii") def toXML(self): """ Generate top-level PDU. """ + elt = lxml.etree.Element(self.xmlns + "msg", nsmap = self.nsmap, version = str(self.version), type = self.type) - elt.extend([i.toXML() for i in self]) + elt.extend(i.toXML() for i in self) return elt @classmethod @@ -468,6 +502,7 @@ class msg(list): """ Create a query PDU. """ + self = cls(args) self.type = "query" return self @@ -477,6 +512,7 @@ class msg(list): """ Create a reply PDU. """ + self = cls(args) self.type = "reply" return self @@ -485,10 +521,12 @@ class msg(list): """ Is this msg a query? """ + return self.type == "query" def is_reply(self): """ Is this msg a reply? """ + return self.type == "reply" diff --git a/schemas/relaxng/left-right-schema.rnc b/schemas/relaxng/left-right.rnc index 201f8ff0..201f8ff0 100644 --- a/schemas/relaxng/left-right-schema.rnc +++ b/schemas/relaxng/left-right.rnc diff --git a/schemas/relaxng/left-right-schema.rng b/schemas/relaxng/left-right.rng index 6e60bd40..31bea740 100644 --- a/schemas/relaxng/left-right-schema.rng +++ b/schemas/relaxng/left-right.rng @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: left-right-schema.rnc 5845 2014-05-29 22:31:15Z sra $ + $Id: left-right.rnc 5881 2014-07-03 16:55:02Z sra $ RelaxNG schema for RPKI left-right protocol. diff --git a/schemas/relaxng/myrpki.rng b/schemas/relaxng/myrpki.rng index 8c7473eb..3beafe8f 100644 --- a/schemas/relaxng/myrpki.rng +++ b/schemas/relaxng/myrpki.rng @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: myrpki.rnc 5757 2014-04-05 22:42:12Z sra $ + $Id: myrpki.rnc 5876 2014-06-26 19:00:12Z sra $ RelaxNG schema for MyRPKI XML messages. diff --git a/schemas/relaxng/publication-schema.rnc b/schemas/relaxng/publication-control.rnc index fdf38c9e..ac59c617 100644 --- a/schemas/relaxng/publication-schema.rnc +++ b/schemas/relaxng/publication-control.rnc @@ -19,7 +19,7 @@ # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -default namespace = "http://www.hactrn.net/uris/rpki/publication-spec/" +default namespace = "http://www.hactrn.net/uris/rpki/publication-control/" version = "1" @@ -32,12 +32,10 @@ start = element msg { } # PDUs allowed in a query -query_elt = ( config_query | client_query | certificate_query | crl_query | - manifest_query | roa_query | ghostbuster_query ) +query_elt = client_query # PDUs allowed in a reply -reply_elt = ( config_reply | client_reply | certificate_reply | crl_reply | - manifest_reply | roa_reply | ghostbuster_reply | report_error_reply ) +reply_elt = ( client_reply | report_error_reply ) # Tag attributes for bulk operations tag = attribute tag { xsd:token {maxLength="1024" } } @@ -58,17 +56,7 @@ uri = attribute uri { uri_t } # hierarchy delimiter. object_handle = xsd:string { maxLength="255" pattern="[\-_A-Za-z0-9/]+" } -# <config/> element (use restricted to repository operator) -# config_handle attribute, create, list, and destroy commands omitted deliberately, see code for details - -config_payload = (element bpki_crl { base64 }?) - -config_query |= element config { attribute action { "set" }, tag?, config_payload } -config_reply |= element config { attribute action { "set" }, tag? } -config_query |= element config { attribute action { "get" }, tag? } -config_reply |= element config { attribute action { "get" }, tag?, config_payload } - -# <client/> element (use restricted to repository operator) +# <client/> element client_handle = attribute client_handle { object_handle } @@ -87,41 +75,6 @@ client_reply |= element client { attribute action { "list" }, tag?, client_ha client_query |= element client { attribute action { "destroy" }, tag?, client_handle } client_reply |= element client { attribute action { "destroy" }, tag?, client_handle } -# <certificate/> element - -certificate_query |= element certificate { attribute action { "publish" }, tag?, uri, base64 } -certificate_reply |= element certificate { attribute action { "publish" }, tag?, uri } -certificate_query |= element certificate { attribute action { "withdraw" }, tag?, uri } -certificate_reply |= element certificate { attribute action { "withdraw" }, tag?, uri } - -# <crl/> element - -crl_query |= element crl { attribute action { "publish" }, tag?, uri, base64 } -crl_reply |= element crl { attribute action { "publish" }, tag?, uri } -crl_query |= element crl { attribute action { "withdraw" }, tag?, uri } -crl_reply |= element crl { attribute action { "withdraw" }, tag?, uri } - -# <manifest/> element - -manifest_query |= element manifest { attribute action { "publish" }, tag?, uri, base64 } -manifest_reply |= element manifest { attribute action { "publish" }, tag?, uri } -manifest_query |= element manifest { attribute action { "withdraw" }, tag?, uri } -manifest_reply |= element manifest { attribute action { "withdraw" }, tag?, uri } - -# <roa/> element - -roa_query |= element roa { attribute action { "publish" }, tag?, uri, base64 } -roa_reply |= element roa { attribute action { "publish" }, tag?, uri } -roa_query |= element roa { attribute action { "withdraw" }, tag?, uri } -roa_reply |= element roa { attribute action { "withdraw" }, tag?, uri } - -# <ghostbuster/> element - -ghostbuster_query |= element ghostbuster { attribute action { "publish" }, tag?, uri, base64 } -ghostbuster_reply |= element ghostbuster { attribute action { "publish" }, tag?, uri } -ghostbuster_query |= element ghostbuster { attribute action { "withdraw" }, tag?, uri } -ghostbuster_reply |= element ghostbuster { attribute action { "withdraw" }, tag?, uri } - # <report_error/> element error = xsd:token { maxLength="1024" } diff --git a/schemas/relaxng/publication-control.rng b/schemas/relaxng/publication-control.rng new file mode 100644 index 00000000..16ec2371 --- /dev/null +++ b/schemas/relaxng/publication-control.rng @@ -0,0 +1,280 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + $Id: publication-control.rnc 5883 2014-07-03 19:21:31Z sra $ + + RelaxNG schema for RPKI publication protocol. + + Copyright (C) 2012- -2014 Dragon Research Labs ("DRL") + Portions copyright (C) 2009- -2011 Internet Systems Consortium ("ISC") + Portions copyright (C) 2007- -2008 American Registry for Internet Numbers ("ARIN") + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notices and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL + WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL, + ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +--> +<grammar ns="http://www.hactrn.net/uris/rpki/publication-control/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> + <define name="version"> + <value>1</value> + </define> + <!-- Top level PDU --> + <start> + <element name="msg"> + <attribute name="version"> + <data type="positiveInteger"> + <param name="maxInclusive">1</param> + </data> + </attribute> + <choice> + <group> + <attribute name="type"> + <value>query</value> + </attribute> + <zeroOrMore> + <ref name="query_elt"/> + </zeroOrMore> + </group> + <group> + <attribute name="type"> + <value>reply</value> + </attribute> + <zeroOrMore> + <ref name="reply_elt"/> + </zeroOrMore> + </group> + </choice> + </element> + </start> + <!-- PDUs allowed in a query --> + <define name="query_elt"> + <ref name="client_query"/> + </define> + <!-- PDUs allowed in a reply --> + <define name="reply_elt"> + <choice> + <ref name="client_reply"/> + <ref name="report_error_reply"/> + </choice> + </define> + <!-- Tag attributes for bulk operations --> + <define name="tag"> + <attribute name="tag"> + <data type="token"> + <param name="maxLength">1024</param> + </data> + </attribute> + </define> + <!-- + Base64 encoded DER stuff + base64 = xsd:base64Binary { maxLength="512000" } + + Sadly, it turns out that CRLs can in fact get longer than this for an active CA. + Remove length limit for now, think about whether to put it back later. + --> + <define name="base64"> + <data type="base64Binary"/> + </define> + <!-- Publication URLs --> + <define name="uri_t"> + <data type="anyURI"> + <param name="maxLength">4096</param> + </data> + </define> + <define name="uri"> + <attribute name="uri"> + <ref name="uri_t"/> + </attribute> + </define> + <!-- + Handles on remote objects (replaces passing raw SQL IDs). NB: + Unlike the up-down protocol, handles in this protocol allow "/" as a + hierarchy delimiter. + --> + <define name="object_handle"> + <data type="string"> + <param name="maxLength">255</param> + <param name="pattern">[\-_A-Za-z0-9/]+</param> + </data> + </define> + <!-- <client/> element --> + <define name="client_handle"> + <attribute name="client_handle"> + <ref name="object_handle"/> + </attribute> + </define> + <define name="client_bool"> + <optional> + <attribute name="clear_replay_protection"> + <value>yes</value> + </attribute> + </optional> + </define> + <define name="client_payload"> + <optional> + <attribute name="base_uri"> + <ref name="uri_t"/> + </attribute> + </optional> + <optional> + <element name="bpki_cert"> + <ref name="base64"/> + </element> + </optional> + <optional> + <element name="bpki_glue"> + <ref name="base64"/> + </element> + </optional> + </define> + <define name="client_query" combine="choice"> + <element name="client"> + <attribute name="action"> + <value>create</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="client_handle"/> + <ref name="client_bool"/> + <ref name="client_payload"/> + </element> + </define> + <define name="client_reply" combine="choice"> + <element name="client"> + <attribute name="action"> + <value>create</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="client_handle"/> + </element> + </define> + <define name="client_query" combine="choice"> + <element name="client"> + <attribute name="action"> + <value>set</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="client_handle"/> + <ref name="client_bool"/> + <ref name="client_payload"/> + </element> + </define> + <define name="client_reply" combine="choice"> + <element name="client"> + <attribute name="action"> + <value>set</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="client_handle"/> + </element> + </define> + <define name="client_query" combine="choice"> + <element name="client"> + <attribute name="action"> + <value>get</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="client_handle"/> + </element> + </define> + <define name="client_reply" combine="choice"> + <element name="client"> + <attribute name="action"> + <value>get</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="client_handle"/> + <ref name="client_payload"/> + </element> + </define> + <define name="client_query" combine="choice"> + <element name="client"> + <attribute name="action"> + <value>list</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + </element> + </define> + <define name="client_reply" combine="choice"> + <element name="client"> + <attribute name="action"> + <value>list</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="client_handle"/> + <ref name="client_payload"/> + </element> + </define> + <define name="client_query" combine="choice"> + <element name="client"> + <attribute name="action"> + <value>destroy</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="client_handle"/> + </element> + </define> + <define name="client_reply" combine="choice"> + <element name="client"> + <attribute name="action"> + <value>destroy</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="client_handle"/> + </element> + </define> + <!-- <report_error/> element --> + <define name="error"> + <data type="token"> + <param name="maxLength">1024</param> + </data> + </define> + <define name="report_error_reply"> + <element name="report_error"> + <optional> + <ref name="tag"/> + </optional> + <attribute name="error_code"> + <ref name="error"/> + </attribute> + <optional> + <data type="string"> + <param name="maxLength">512000</param> + </data> + </optional> + </element> + </define> +</grammar> +<!-- + Local Variables: + indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" + End: +--> diff --git a/schemas/relaxng/publication-schema.rng b/schemas/relaxng/publication-schema.rng deleted file mode 100644 index 72a77ad4..00000000 --- a/schemas/relaxng/publication-schema.rng +++ /dev/null @@ -1,577 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - $Id: publication-schema.rnc 5845 2014-05-29 22:31:15Z sra $ - - RelaxNG schema for RPKI publication protocol. - - Copyright (C) 2012- -2014 Dragon Research Labs ("DRL") - Portions copyright (C) 2009- -2011 Internet Systems Consortium ("ISC") - Portions copyright (C) 2007- -2008 American Registry for Internet Numbers ("ARIN") - - Permission to use, copy, modify, and distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notices and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL - WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL, - ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR - CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS - OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, - NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION - WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---> -<grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> - <define name="version"> - <value>1</value> - </define> - <!-- Top level PDU --> - <start> - <element name="msg"> - <attribute name="version"> - <data type="positiveInteger"> - <param name="maxInclusive">1</param> - </data> - </attribute> - <choice> - <group> - <attribute name="type"> - <value>query</value> - </attribute> - <zeroOrMore> - <ref name="query_elt"/> - </zeroOrMore> - </group> - <group> - <attribute name="type"> - <value>reply</value> - </attribute> - <zeroOrMore> - <ref name="reply_elt"/> - </zeroOrMore> - </group> - </choice> - </element> - </start> - <!-- PDUs allowed in a query --> - <define name="query_elt"> - <choice> - <ref name="config_query"/> - <ref name="client_query"/> - <ref name="certificate_query"/> - <ref name="crl_query"/> - <ref name="manifest_query"/> - <ref name="roa_query"/> - <ref name="ghostbuster_query"/> - </choice> - </define> - <!-- PDUs allowed in a reply --> - <define name="reply_elt"> - <choice> - <ref name="config_reply"/> - <ref name="client_reply"/> - <ref name="certificate_reply"/> - <ref name="crl_reply"/> - <ref name="manifest_reply"/> - <ref name="roa_reply"/> - <ref name="ghostbuster_reply"/> - <ref name="report_error_reply"/> - </choice> - </define> - <!-- Tag attributes for bulk operations --> - <define name="tag"> - <attribute name="tag"> - <data type="token"> - <param name="maxLength">1024</param> - </data> - </attribute> - </define> - <!-- - Base64 encoded DER stuff - base64 = xsd:base64Binary { maxLength="512000" } - - Sadly, it turns out that CRLs can in fact get longer than this for an active CA. - Remove length limit for now, think about whether to put it back later. - --> - <define name="base64"> - <data type="base64Binary"/> - </define> - <!-- Publication URLs --> - <define name="uri_t"> - <data type="anyURI"> - <param name="maxLength">4096</param> - </data> - </define> - <define name="uri"> - <attribute name="uri"> - <ref name="uri_t"/> - </attribute> - </define> - <!-- - Handles on remote objects (replaces passing raw SQL IDs). NB: - Unlike the up-down protocol, handles in this protocol allow "/" as a - hierarchy delimiter. - --> - <define name="object_handle"> - <data type="string"> - <param name="maxLength">255</param> - <param name="pattern">[\-_A-Za-z0-9/]+</param> - </data> - </define> - <!-- - <config/> element (use restricted to repository operator) - config_handle attribute, create, list, and destroy commands omitted deliberately, see code for details - --> - <define name="config_payload"> - <optional> - <element name="bpki_crl"> - <ref name="base64"/> - </element> - </optional> - </define> - <define name="config_query" combine="choice"> - <element name="config"> - <attribute name="action"> - <value>set</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="config_payload"/> - </element> - </define> - <define name="config_reply" combine="choice"> - <element name="config"> - <attribute name="action"> - <value>set</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - </element> - </define> - <define name="config_query" combine="choice"> - <element name="config"> - <attribute name="action"> - <value>get</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - </element> - </define> - <define name="config_reply" combine="choice"> - <element name="config"> - <attribute name="action"> - <value>get</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="config_payload"/> - </element> - </define> - <!-- <client/> element (use restricted to repository operator) --> - <define name="client_handle"> - <attribute name="client_handle"> - <ref name="object_handle"/> - </attribute> - </define> - <define name="client_bool"> - <optional> - <attribute name="clear_replay_protection"> - <value>yes</value> - </attribute> - </optional> - </define> - <define name="client_payload"> - <optional> - <attribute name="base_uri"> - <ref name="uri_t"/> - </attribute> - </optional> - <optional> - <element name="bpki_cert"> - <ref name="base64"/> - </element> - </optional> - <optional> - <element name="bpki_glue"> - <ref name="base64"/> - </element> - </optional> - </define> - <define name="client_query" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>create</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - <ref name="client_bool"/> - <ref name="client_payload"/> - </element> - </define> - <define name="client_reply" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>create</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - </element> - </define> - <define name="client_query" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>set</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - <ref name="client_bool"/> - <ref name="client_payload"/> - </element> - </define> - <define name="client_reply" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>set</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - </element> - </define> - <define name="client_query" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>get</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - </element> - </define> - <define name="client_reply" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>get</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - <ref name="client_payload"/> - </element> - </define> - <define name="client_query" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>list</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - </element> - </define> - <define name="client_reply" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>list</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - <ref name="client_payload"/> - </element> - </define> - <define name="client_query" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>destroy</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - </element> - </define> - <define name="client_reply" combine="choice"> - <element name="client"> - <attribute name="action"> - <value>destroy</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="client_handle"/> - </element> - </define> - <!-- <certificate/> element --> - <define name="certificate_query" combine="choice"> - <element name="certificate"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> - </define> - <define name="certificate_reply" combine="choice"> - <element name="certificate"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="certificate_query" combine="choice"> - <element name="certificate"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="certificate_reply" combine="choice"> - <element name="certificate"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <!-- <crl/> element --> - <define name="crl_query" combine="choice"> - <element name="crl"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> - </define> - <define name="crl_reply" combine="choice"> - <element name="crl"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="crl_query" combine="choice"> - <element name="crl"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="crl_reply" combine="choice"> - <element name="crl"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <!-- <manifest/> element --> - <define name="manifest_query" combine="choice"> - <element name="manifest"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> - </define> - <define name="manifest_reply" combine="choice"> - <element name="manifest"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="manifest_query" combine="choice"> - <element name="manifest"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="manifest_reply" combine="choice"> - <element name="manifest"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <!-- <roa/> element --> - <define name="roa_query" combine="choice"> - <element name="roa"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> - </define> - <define name="roa_reply" combine="choice"> - <element name="roa"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="roa_query" combine="choice"> - <element name="roa"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="roa_reply" combine="choice"> - <element name="roa"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <!-- <ghostbuster/> element --> - <define name="ghostbuster_query" combine="choice"> - <element name="ghostbuster"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - <ref name="base64"/> - </element> - </define> - <define name="ghostbuster_reply" combine="choice"> - <element name="ghostbuster"> - <attribute name="action"> - <value>publish</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="ghostbuster_query" combine="choice"> - <element name="ghostbuster"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <define name="ghostbuster_reply" combine="choice"> - <element name="ghostbuster"> - <attribute name="action"> - <value>withdraw</value> - </attribute> - <optional> - <ref name="tag"/> - </optional> - <ref name="uri"/> - </element> - </define> - <!-- <report_error/> element --> - <define name="error"> - <data type="token"> - <param name="maxLength">1024</param> - </data> - </define> - <define name="report_error_reply"> - <element name="report_error"> - <optional> - <ref name="tag"/> - </optional> - <attribute name="error_code"> - <ref name="error"/> - </attribute> - <optional> - <data type="string"> - <param name="maxLength">512000</param> - </data> - </optional> - </element> - </define> -</grammar> -<!-- - Local Variables: - indent-tabs-mode: nil - comment-start: "# " - comment-start-skip: "#[ \t]*" - End: ---> diff --git a/schemas/relaxng/publication.rnc b/schemas/relaxng/publication.rnc new file mode 100644 index 00000000..f3d1f94e --- /dev/null +++ b/schemas/relaxng/publication.rnc @@ -0,0 +1,111 @@ +# $Id$ +# +# RelaxNG schema for RPKI publication protocol, from current I-D. +# +# Copyright (c) 2014 IETF Trust and the persons identified as authors +# of the code. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# * Neither the name of Internet Society, IETF or IETF Trust, nor the +# names of specific contributors, may be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +default namespace = + "http://www.hactrn.net/uris/rpki/publication-spec/" + +# This is version 3 of the protocol. + +version = "3" + +# Top level PDU is either a query or a reply. + +start |= element msg { + attribute version { version }, + attribute type { "query" }, + query_elt* +} + +start |= element msg { + attribute version { version }, + attribute type { "reply" }, + reply_elt* +} + +# PDUs allowed in queries and replies. + +query_elt = publish_query | withdraw_query | list_query +reply_elt = publish_reply | withdraw_reply | list_reply | report_error_reply + +# Tag attributes for bulk operations. + +tag = attribute tag { xsd:token { maxLength="1024" } } + +# Base64 encoded DER stuff. + +base64 = xsd:base64Binary + +# Publication URIs. + +uri = attribute uri { xsd:anyURI { maxLength="4096" } } + +# Digest of objects being withdrawn + +hash = attribute hash { xsd:string { pattern = "[0-9a-fA-F]+" } } + +# Error codes. + +error = xsd:token { maxLength="1024" } + +# <publish/> element + +publish_query = element publish { tag?, uri, hash?, base64 } +publish_reply = element publish { tag?, uri } + +# <withdraw/> element + +withdraw_query = element withdraw { tag?, uri, hash } +withdraw_reply = element withdraw { tag?, uri } + +# <list/> element + +list_query = element list { tag? } +list_reply = element list { tag?, uri, hash } + +# <report_error/> element + +report_error_reply = element report_error { + tag?, + attribute error_code { error }, + xsd:string { maxLength="512000" }? +} + +# Local Variables: +# indent-tabs-mode: nil +# comment-start: "# " +# comment-start-skip: "#[ \t]*" +# End: diff --git a/schemas/relaxng/publication.rng b/schemas/relaxng/publication.rng new file mode 100644 index 00000000..5e72407e --- /dev/null +++ b/schemas/relaxng/publication.rng @@ -0,0 +1,201 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + $Id: publication.rnc 5896 2014-07-15 19:34:32Z sra $ + + RelaxNG schema for RPKI publication protocol, from current I-D. + + Copyright (c) 2014 IETF Trust and the persons identified as authors + of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Internet Society, IETF or IETF Trust, nor the + names of specific contributors, may be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +--> +<grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> + <!-- This is version 3 of the protocol. --> + <define name="version"> + <value>3</value> + </define> + <!-- Top level PDU is either a query or a reply. --> + <start combine="choice"> + <element name="msg"> + <attribute name="version"> + <ref name="version"/> + </attribute> + <attribute name="type"> + <value>query</value> + </attribute> + <zeroOrMore> + <ref name="query_elt"/> + </zeroOrMore> + </element> + </start> + <start combine="choice"> + <element name="msg"> + <attribute name="version"> + <ref name="version"/> + </attribute> + <attribute name="type"> + <value>reply</value> + </attribute> + <zeroOrMore> + <ref name="reply_elt"/> + </zeroOrMore> + </element> + </start> + <!-- PDUs allowed in queries and replies. --> + <define name="query_elt"> + <choice> + <ref name="publish_query"/> + <ref name="withdraw_query"/> + <ref name="list_query"/> + </choice> + </define> + <define name="reply_elt"> + <choice> + <ref name="publish_reply"/> + <ref name="withdraw_reply"/> + <ref name="list_reply"/> + <ref name="report_error_reply"/> + </choice> + </define> + <!-- Tag attributes for bulk operations. --> + <define name="tag"> + <attribute name="tag"> + <data type="token"> + <param name="maxLength">1024</param> + </data> + </attribute> + </define> + <!-- Base64 encoded DER stuff. --> + <define name="base64"> + <data type="base64Binary"/> + </define> + <!-- Publication URIs. --> + <define name="uri"> + <attribute name="uri"> + <data type="anyURI"> + <param name="maxLength">4096</param> + </data> + </attribute> + </define> + <!-- Digest of objects being withdrawn --> + <define name="hash"> + <attribute name="hash"> + <data type="string"> + <param name="pattern">[0-9a-fA-F]+</param> + </data> + </attribute> + </define> + <!-- Error codes. --> + <define name="error"> + <data type="token"> + <param name="maxLength">1024</param> + </data> + </define> + <!-- <publish/> element --> + <define name="publish_query"> + <element name="publish"> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + <optional> + <ref name="hash"/> + </optional> + <ref name="base64"/> + </element> + </define> + <define name="publish_reply"> + <element name="publish"> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + </element> + </define> + <!-- <withdraw/> element --> + <define name="withdraw_query"> + <element name="withdraw"> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + <ref name="hash"/> + </element> + </define> + <define name="withdraw_reply"> + <element name="withdraw"> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + </element> + </define> + <!-- <list/> element --> + <define name="list_query"> + <element name="list"> + <optional> + <ref name="tag"/> + </optional> + </element> + </define> + <define name="list_reply"> + <element name="list"> + <optional> + <ref name="tag"/> + </optional> + <ref name="uri"/> + <ref name="hash"/> + </element> + </define> + <!-- <report_error/> element --> + <define name="report_error_reply"> + <element name="report_error"> + <optional> + <ref name="tag"/> + </optional> + <attribute name="error_code"> + <ref name="error"/> + </attribute> + <optional> + <data type="string"> + <param name="maxLength">512000</param> + </data> + </optional> + </element> + </define> +</grammar> +<!-- + Local Variables: + indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" + End: +--> diff --git a/schemas/relaxng/router-certificate-schema.rnc b/schemas/relaxng/router-certificate.rnc index 8cc325ce..8cc325ce 100644 --- a/schemas/relaxng/router-certificate-schema.rnc +++ b/schemas/relaxng/router-certificate.rnc diff --git a/schemas/relaxng/router-certificate-schema.rng b/schemas/relaxng/router-certificate.rng index 90b50107..9352ed76 100644 --- a/schemas/relaxng/router-certificate-schema.rng +++ b/schemas/relaxng/router-certificate.rng @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: router-certificate-schema.rnc 5757 2014-04-05 22:42:12Z sra $ + $Id: router-certificate.rnc 5881 2014-07-03 16:55:02Z sra $ RelaxNG schema for BGPSEC router certificate interchange format. diff --git a/schemas/relaxng/rrdp.rnc b/schemas/relaxng/rrdp.rnc new file mode 100644 index 00000000..2829605d --- /dev/null +++ b/schemas/relaxng/rrdp.rnc @@ -0,0 +1,83 @@ +# $Id$ +# +# RelaxNG schema for RPKI Repository Delta Protocol (RRDP). +# +# Copyright (C) 2014 Dragon Research Labs ("DRL") +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +default namespace = "http://www.ripe.net/rpki/rrdp" + +version = xsd:positiveInteger { maxInclusive="1" } +serial = xsd:nonNegativeInteger +uri = xsd:anyURI +uuid = xsd:string { pattern = "[\-0-9a-fA-F]+" } +hash = xsd:string { pattern = "[0-9a-fA-F]+" } +base64 = xsd:base64Binary + +# Notification file: lists current snapshots and deltas + +start |= element notification { + attribute version { version }, + attribute session_id { uuid }, + attribute serial { serial }, + element snapshot { + attribute uri { uri }, + attribute hash { hash } + }, + element delta { + attribute from { serial }, + attribute to { serial }, + attribute uri { uri }, + attribute hash { hash } + }* +} + +# Snapshot segment: think DNS AXFR. + +start |= element snapshot { + attribute version { version }, + attribute session_id { uuid }, + attribute serial { serial }, + element publish { attribute uri { uri }, base64 }* +} + +# Delta segment: think DNS IXFR. + +start |= element deltas { + attribute version { version }, + attribute session_id { uuid }, + attribute from { serial }, + attribute to { serial }, + element delta { + attribute serial { serial }, + delta_element+ + }+ +} + +delta_element |= element publish { + attribute uri { uri }, + attribute hash { hash }?, + base64 +} + +delta_element |= element withdraw { + attribute uri { uri }, + attribute hash { hash } +} + +# Local Variables: +# indent-tabs-mode: nil +# comment-start: "# " +# comment-start-skip: "#[ \t]*" +# End: diff --git a/schemas/relaxng/rrdp.rng b/schemas/relaxng/rrdp.rng new file mode 100644 index 00000000..9bd3a207 --- /dev/null +++ b/schemas/relaxng/rrdp.rng @@ -0,0 +1,163 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + $Id: rrdp.rnc 5888 2014-07-09 05:39:54Z sra $ + + RelaxNG schema for RPKI Repository Delta Protocol (RRDP). + + Copyright (C) 2014 Dragon Research Labs ("DRL") + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. +--> +<grammar ns="http://www.ripe.net/rpki/rrdp" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> + <define name="version"> + <data type="positiveInteger"> + <param name="maxInclusive">1</param> + </data> + </define> + <define name="serial"> + <data type="nonNegativeInteger"/> + </define> + <define name="uri"> + <data type="anyURI"/> + </define> + <define name="uuid"> + <data type="string"> + <param name="pattern">[\-0-9a-fA-F]+</param> + </data> + </define> + <define name="hash"> + <data type="string"> + <param name="pattern">[0-9a-fA-F]+</param> + </data> + </define> + <define name="base64"> + <data type="base64Binary"/> + </define> + <!-- Notification file: lists current snapshots and deltas --> + <start combine="choice"> + <element name="notification"> + <attribute name="version"> + <ref name="version"/> + </attribute> + <attribute name="session_id"> + <ref name="uuid"/> + </attribute> + <attribute name="serial"> + <ref name="serial"/> + </attribute> + <element name="snapshot"> + <attribute name="uri"> + <ref name="uri"/> + </attribute> + <attribute name="hash"> + <ref name="hash"/> + </attribute> + </element> + <zeroOrMore> + <element name="delta"> + <attribute name="from"> + <ref name="serial"/> + </attribute> + <attribute name="to"> + <ref name="serial"/> + </attribute> + <attribute name="uri"> + <ref name="uri"/> + </attribute> + <attribute name="hash"> + <ref name="hash"/> + </attribute> + </element> + </zeroOrMore> + </element> + </start> + <!-- Snapshot segment: think DNS AXFR. --> + <start combine="choice"> + <element name="snapshot"> + <attribute name="version"> + <ref name="version"/> + </attribute> + <attribute name="session_id"> + <ref name="uuid"/> + </attribute> + <attribute name="serial"> + <ref name="serial"/> + </attribute> + <zeroOrMore> + <element name="publish"> + <attribute name="uri"> + <ref name="uri"/> + </attribute> + <ref name="base64"/> + </element> + </zeroOrMore> + </element> + </start> + <!-- Delta segment: think DNS IXFR. --> + <start combine="choice"> + <element name="deltas"> + <attribute name="version"> + <ref name="version"/> + </attribute> + <attribute name="session_id"> + <ref name="uuid"/> + </attribute> + <attribute name="from"> + <ref name="serial"/> + </attribute> + <attribute name="to"> + <ref name="serial"/> + </attribute> + <oneOrMore> + <element name="delta"> + <attribute name="serial"> + <ref name="serial"/> + </attribute> + <oneOrMore> + <ref name="delta_element"/> + </oneOrMore> + </element> + </oneOrMore> + </element> + </start> + <define name="delta_element" combine="choice"> + <element name="publish"> + <attribute name="uri"> + <ref name="uri"/> + </attribute> + <optional> + <attribute name="hash"> + <ref name="hash"/> + </attribute> + </optional> + <ref name="base64"/> + </element> + </define> + <define name="delta_element" combine="choice"> + <element name="withdraw"> + <attribute name="uri"> + <ref name="uri"/> + </attribute> + <attribute name="hash"> + <ref name="hash"/> + </attribute> + </element> + </define> +</grammar> +<!-- + Local Variables: + indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" + End: +--> diff --git a/schemas/relaxng/up-down-schema.rnc b/schemas/relaxng/up-down.rnc index a603b8fe..a603b8fe 100644 --- a/schemas/relaxng/up-down-schema.rnc +++ b/schemas/relaxng/up-down.rnc diff --git a/schemas/relaxng/up-down-schema.rng b/schemas/relaxng/up-down.rng index 89235b7e..a0fc0514 100644 --- a/schemas/relaxng/up-down-schema.rng +++ b/schemas/relaxng/up-down.rng @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: up-down-schema.rnc 5757 2014-04-05 22:42:12Z sra $ + $Id: up-down.rnc 5881 2014-07-03 16:55:02Z sra $ RelaxNG schema for the up-down protocol, extracted from RFC 6492. diff --git a/schemas/sql/pubd.sql b/schemas/sql/pubd.sql index 3a58ec00..de857bf4 100644 --- a/schemas/sql/pubd.sql +++ b/schemas/sql/pubd.sql @@ -1,47 +1,36 @@ -- $Id$ --- Copyright (C) 2009--2010 Internet Systems Consortium ("ISC") +-- Copyright (C) 2012--2014 Dragon Research Labs ("DRL") +-- Portions copyright (C) 2009--2010 Internet Systems Consortium ("ISC") +-- Portions copyright (C) 2008 American Registry for Internet Numbers ("ARIN") -- -- Permission to use, copy, modify, and distribute this software for any -- purpose with or without fee is hereby granted, provided that the above --- copyright notice and this permission notice appear in all copies. +-- copyright notices and this permission notice appear in all copies. -- --- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH --- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY --- AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, --- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM --- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE --- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR --- PERFORMANCE OF THIS SOFTWARE. - --- Copyright (C) 2008 American Registry for Internet Numbers ("ARIN") --- --- Permission to use, copy, modify, and distribute this software for any --- purpose with or without fee is hereby granted, provided that the above --- copyright notice and this permission notice appear in all copies. --- --- THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH --- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY --- AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, --- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM --- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE --- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR --- PERFORMANCE OF THIS SOFTWARE. +-- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL +-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL, +-- ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +-- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- SQL objects needed by pubd.py. --- The config table is weird because we're really only using it --- to store one BPKI CRL, but putting this here lets us use a lot of --- existing machinery and the alternatives are whacky in other ways. +-- Old tables that should just be flushed if present at all. -DROP TABLE IF EXISTS client; DROP TABLE IF EXISTS config; +DROP TABLE IF EXISTS snapshot; -CREATE TABLE config ( - config_id SERIAL NOT NULL, - bpki_crl LONGBLOB, - PRIMARY KEY (config_id) -) ENGINE=InnoDB; +-- DROP TABLE commands must be in correct (reverse dependency) order +-- to satisfy FOREIGN KEY constraints. + +DROP TABLE IF EXISTS object; +DROP TABLE IF EXISTS delta; +DROP TABLE IF EXISTS session; +DROP TABLE IF EXISTS client; CREATE TABLE client ( client_id SERIAL NOT NULL, @@ -54,6 +43,43 @@ CREATE TABLE client ( UNIQUE (client_handle) ) ENGINE=InnoDB; +CREATE TABLE session ( + session_id SERIAL NOT NULL, + uuid VARCHAR(36) NOT NULL, + serial BIGINT UNSIGNED NOT NULL, + snapshot TEXT, + hash CHAR(64), + PRIMARY KEY (session_id), + UNIQUE (uuid) +) ENGINE=InnoDB; + +CREATE TABLE delta ( + delta_id SERIAL NOT NULL, + serial BIGINT UNSIGNED NOT NULL, + xml TEXT NOT NULL, + hash CHAR(64) NOT NULL, + expires DATETIME NOT NULL, + session_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (delta_id), + CONSTRAINT delta_session_id + FOREIGN KEY (session_id) REFERENCES session (session_id) ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE object ( + object_id SERIAL NOT NULL, + uri VARCHAR(255) NOT NULL, + der LONGBLOB NOT NULL, + hash CHAR(64) NOT NULL, + client_id BIGINT UNSIGNED NOT NULL, + session_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (object_id), + CONSTRAINT object_client_id + FOREIGN KEY (client_id) REFERENCES client (client_id) ON DELETE CASCADE, + CONSTRAINT object_session_id + FOREIGN KEY (session_id) REFERENCES session (session_id) ON DELETE CASCADE, + UNIQUE (session_id, hash) +) ENGINE=InnoDB; + -- Local Variables: -- indent-tabs-mode: nil -- End: diff --git a/schemas/sql/rpkid.sql b/schemas/sql/rpkid.sql index ad0c39b0..f3b899ee 100644 --- a/schemas/sql/rpkid.sql +++ b/schemas/sql/rpkid.sql @@ -1,32 +1,21 @@ -- $Id$ --- Copyright (C) 2009--2011 Internet Systems Consortium ("ISC") +-- Copyright (C) 2012--2014 Dragon Research Labs ("DRL") +-- Portions copyright (C) 2009--2011 Internet Systems Consortium ("ISC") +-- Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") -- -- Permission to use, copy, modify, and distribute this software for any -- purpose with or without fee is hereby granted, provided that the above --- copyright notice and this permission notice appear in all copies. +-- copyright notices and this permission notice appear in all copies. -- --- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH --- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY --- AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, --- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM --- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE --- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR --- PERFORMANCE OF THIS SOFTWARE. - --- Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") --- --- Permission to use, copy, modify, and distribute this software for any --- purpose with or without fee is hereby granted, provided that the above --- copyright notice and this permission notice appear in all copies. --- --- THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH --- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY --- AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, --- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM --- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE --- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR --- PERFORMANCE OF THIS SOFTWARE. +-- THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL +-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL, +-- ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +-- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- SQL objects needed by the RPKI engine (rpkid.py). |