diff options
author | Rob Austein <sra@hactrn.net> | 2014-04-05 19:24:26 +0000 |
---|---|---|
committer | Rob Austein <sra@hactrn.net> | 2014-04-05 19:24:26 +0000 |
commit | 3e9ffaab9aef186a3c94123bcfc8346aebda026d (patch) | |
tree | 3127d04811c8bf780641314cbd4c7f3e5a286e91 | |
parent | b221ad67e384afbfc8513488325a6e29414e0085 (diff) | |
parent | 5cb86d4686552904bd16affffb902410e2580471 (diff) |
Merge tk671 (router certificate support) back to trunk. See #671.
svn path=/trunk/; revision=5753
50 files changed, 3792 insertions, 1509 deletions
diff --git a/buildtools/debian-skeleton/rpki-ca.postinst b/buildtools/debian-skeleton/rpki-ca.postinst index 08af52e0..dd01d3f8 100644 --- a/buildtools/debian-skeleton/rpki-ca.postinst +++ b/buildtools/debian-skeleton/rpki-ca.postinst @@ -48,7 +48,8 @@ setup_rpki_conf() { } setup_mysql() { - rpki-sql-setup --mysql-defaults /etc/mysql/debian.cnf + rpki-sql-setup --create-if-missing --mysql-defaults /etc/mysql/debian.cnf + rpki-sql-setup --apply-upgrades --verbose } setup_bpki() { diff --git a/buildtools/freebsd-skeleton/rpki-ca/pkg-install b/buildtools/freebsd-skeleton/rpki-ca/pkg-install index 7f2c2e2a..157b3ced 100644 --- a/buildtools/freebsd-skeleton/rpki-ca/pkg-install +++ b/buildtools/freebsd-skeleton/rpki-ca/pkg-install @@ -29,7 +29,8 @@ POST-INSTALL) /usr/bin/install -o root -g wheel -d /usr/local/share/rpki/publication /usr/bin/install -o www -g www -d /usr/local/share/rpki/python-eggs - /usr/local/sbin/rpki-sql-setup + /usr/local/sbin/rpki-sql-setup --create-if-missing + /usr/local/sbin/rpki-sql-setup --apply-upgrades --verbose /usr/local/sbin/rpki-manage syncdb --noinput /usr/local/sbin/rpki-manage migrate app @@ -5062,7 +5062,7 @@ fi if test $build_rp_tools = yes then - ac_config_files="$ac_config_files rcynic/Makefile utils/Makefile utils/find_roa/Makefile utils/hashdir/Makefile utils/print_rpki_manifest/Makefile utils/print_roa/Makefile utils/scan_roas/Makefile utils/uri/Makefile rtr-origin/Makefile" + ac_config_files="$ac_config_files rcynic/Makefile utils/Makefile utils/find_roa/Makefile utils/hashdir/Makefile utils/print_rpki_manifest/Makefile utils/print_roa/Makefile utils/scan_roas/Makefile utils/scan_routercerts/Makefile utils/uri/Makefile rtr-origin/Makefile" fi @@ -5819,6 +5819,7 @@ do "utils/print_rpki_manifest/Makefile") CONFIG_FILES="$CONFIG_FILES utils/print_rpki_manifest/Makefile" ;; "utils/print_roa/Makefile") CONFIG_FILES="$CONFIG_FILES utils/print_roa/Makefile" ;; "utils/scan_roas/Makefile") CONFIG_FILES="$CONFIG_FILES utils/scan_roas/Makefile" ;; + "utils/scan_routercerts/Makefile") CONFIG_FILES="$CONFIG_FILES utils/scan_routercerts/Makefile" ;; "utils/uri/Makefile") CONFIG_FILES="$CONFIG_FILES utils/uri/Makefile" ;; "rtr-origin/Makefile") CONFIG_FILES="$CONFIG_FILES rtr-origin/Makefile" ;; "rpkid/Makefile") CONFIG_FILES="$CONFIG_FILES rpkid/Makefile" ;; diff --git a/configure.ac b/configure.ac index adeb2fa1..4f4aa244 100644 --- a/configure.ac +++ b/configure.ac @@ -825,6 +825,7 @@ then utils/print_rpki_manifest/Makefile utils/print_roa/Makefile utils/scan_roas/Makefile + utils/scan_routercerts/Makefile utils/uri/Makefile rtr-origin/Makefile]) fi diff --git a/rpkid/Makefile.in b/rpkid/Makefile.in index 90c6b7ac..d36a3163 100644 --- a/rpkid/Makefile.in +++ b/rpkid/Makefile.in @@ -55,7 +55,7 @@ build/stamp: .FORCE setup_autoconf.py clean:: rm -rf ${POW_SO} build dist -RNGS = left-right-schema.rng up-down-schema.rng publication-schema.rng myrpki.rng +RNGS = left-right-schema.rng up-down-schema.rng publication-schema.rng myrpki.rng router-certificate-schema.rng rpki/relaxng.py: ${abs_top_srcdir}/buildtools/make-relaxng.py ${RNGS} ${PYTHON} ${abs_top_srcdir}/buildtools/make-relaxng.py ${RNGS} >$@.tmp @@ -73,6 +73,9 @@ publication-schema.rng: publication-schema.rnc myrpki.rng: myrpki.rnc ${TRANG} myrpki.rnc myrpki.rng +router-certificate-schema.rng: router-certificate-schema.rnc + ${TRANG} router-certificate-schema.rnc router-certificate-schema.rng + rpki/sql_schemas.py: ${abs_top_srcdir}/buildtools/make-sql-schemas.py rpkid.sql pubd.sql ${PYTHON} ${abs_top_srcdir}/buildtools/make-sql-schemas.py >$@.tmp mv $@.tmp $@ diff --git a/rpkid/ext/POW.c b/rpkid/ext/POW.c index 05007135..b5d9ccaf 100644 --- a/rpkid/ext/POW.c +++ b/rpkid/ext/POW.c @@ -355,7 +355,7 @@ typedef struct { typedef struct { PyObject_HEAD X509_REQ *pkcs10; - STACK_OF(X509_EXTENSION) *exts; + X509_EXTENSIONS *exts; } pkcs10_object; @@ -403,9 +403,15 @@ typedef struct { goto error; \ } while (0) +#define lose_value_error(_msg_) \ + do { \ + PyErr_SetString(PyExc_ValueError, (_msg_)); \ + goto error; \ + } while (0) + #define lose_openssl_error(_msg_) \ do { \ - set_openssl_exception(OpenSSLErrorObject, (_msg_)); \ + set_openssl_exception(OpenSSLErrorObject, (_msg_), 0); \ goto error; \ } while (0) @@ -417,19 +423,21 @@ typedef struct { #define assert_no_unhandled_openssl_errors() \ do { \ - if (ERR_peek_error()) \ - lose_openssl_error(assert_helper(__LINE__)); \ + if (ERR_peek_error()) { \ + set_openssl_exception(OpenSSLErrorObject, NULL, __LINE__); \ + goto error; \ + } \ } while (0) -static char * -assert_helper(int line) -{ - static const char fmt[] = "Unhandled OpenSSL error at " __FILE__ ":%d!"; - static char msg[sizeof(fmt) + 10]; - - snprintf(msg, sizeof(msg), fmt, line); - return msg; -} +#define POW_assert(_cond_) \ + do { \ + if (!(_cond_)) { \ + (void) PyErr_Format(POWErrorObject, \ + "Assertion %s failed at " __FILE__ ":%d", \ + #_cond_, __LINE__); \ + goto error; \ + } \ + } while (0) /* * Consolidate some tedious EVP-related switch statements. @@ -451,10 +459,19 @@ evp_digest_factory(int digest_type) /* * Raise an exception with data pulled from the OpenSSL error stack. - * Exception value is a tuple with some internal structure. If a - * string error message is supplied, that string is the first element - * of the exception value tuple. Remainder of exception value tuple - * is zero or more tuples, each representing one error from the stack. + * Exception value is a tuple with some internal structure. + * + * If a string error message is supplied, that string is the first + * element of the exception value tuple. + * + * If a non-zero line number is supplied, a string listing this as an + * unhandled exception detected at that line will be the next element + * of the exception value tuple (or the first, if no error message was + * supplied). + * + * Remainder of exception value tuple is zero or more tuples, each + * representing one error from the stack. + * * Each error tuple contains six slots: * - the numeric error code * - string translation of numeric error code ("reason") @@ -465,18 +482,26 @@ evp_digest_factory(int digest_type) */ static void -set_openssl_exception(PyObject *error_class, const char *msg) +set_openssl_exception(PyObject *error_class, const char *msg, const int unhandled_line) { - PyObject *errors; + PyObject *errtuple = NULL; + PyObject *errlist = NULL; unsigned long err; const char *file; int line; - errors = PyList_New(0); + if ((errlist = PyList_New(0)) == NULL) + return; if (msg) { - PyObject *s = Py_BuildValue("s", msg); - (void) PyList_Append(errors, s); + PyObject *s = PyString_FromString(msg); + (void) PyList_Append(errlist, s); + Py_XDECREF(s); + } + + if (unhandled_line) { + PyObject *s = PyString_FromFormat("Unhandled OpenSSL error at " __FILE__ ":%d!", unhandled_line); + (void) PyList_Append(errlist, s); Py_XDECREF(s); } @@ -488,12 +513,15 @@ set_openssl_exception(PyObject *error_class, const char *msg) ERR_func_error_string(err), file, line); - (void) PyList_Append(errors, t); + (void) PyList_Append(errlist, t); Py_XDECREF(t); } - PyErr_SetObject(error_class, PyList_AsTuple(errors)); - Py_XDECREF(errors); + if ((errtuple = PyList_AsTuple(errlist)) != NULL) + PyErr_SetObject(error_class, errtuple); + + Py_XDECREF(errtuple); + Py_XDECREF(errlist); } static X509_NAME * @@ -1048,6 +1076,589 @@ ASN1_OBJECT_to_PyString(const ASN1_OBJECT *oid) /* + * Extension functions. Calling sequence here is a little weird, + * because it turns out that the simplest way to avoid massive + * duplication of code between classes is to work directly with + * X509_EXTENSIONS objects. + */ + +static PyObject * +extension_get_key_usage(X509_EXTENSIONS **exts) +{ + ASN1_BIT_STRING *ext = NULL; + PyObject *result = NULL; + PyObject *token = NULL; + int bit = -1; + + ENTERING(extension_get_key_usage); + + if (!exts) + goto error; + + if ((ext = X509V3_get_d2i(*exts, NID_key_usage, NULL, NULL)) == NULL) + Py_RETURN_NONE; + + if ((result = PyFrozenSet_New(NULL)) == NULL) + goto error; + + for (bit = 0; key_usage_bit_names[bit] != NULL; bit++) { + if (ASN1_BIT_STRING_get_bit(ext, bit) && + ((token = PyString_FromString(key_usage_bit_names[bit])) == NULL || + PySet_Add(result, token) < 0)) + goto error; + Py_XDECREF(token); + token = NULL; + } + + ASN1_BIT_STRING_free(ext); + return result; + + error: + ASN1_BIT_STRING_free(ext); + Py_XDECREF(token); + Py_XDECREF(result); + return NULL; +} + +static PyObject * +extension_set_key_usage(X509_EXTENSIONS **exts, PyObject *args) +{ + ASN1_BIT_STRING *ext = NULL; + PyObject *iterable = NULL; + PyObject *critical = Py_True; + PyObject *iterator = NULL; + PyObject *item = NULL; + const char *token; + int bit = -1; + int ok = 0; + + ENTERING(extension_set_key_usage); + + if (!exts) + goto error; + + if ((ext = ASN1_BIT_STRING_new()) == NULL) + lose_no_memory(); + + if (!PyArg_ParseTuple(args, "O|O", &iterable, &critical) || + (iterator = PyObject_GetIter(iterable)) == NULL) + goto error; + + while ((item = PyIter_Next(iterator)) != NULL) { + + if ((token = PyString_AsString(item)) == NULL) + goto error; + + for (bit = 0; key_usage_bit_names[bit] != NULL; bit++) + if (!strcmp(token, key_usage_bit_names[bit])) + break; + + if (key_usage_bit_names[bit] == NULL) + lose("Unrecognized KeyUsage token"); + + if (!ASN1_BIT_STRING_set_bit(ext, bit, 1)) + lose_no_memory(); + + Py_XDECREF(item); + item = NULL; + } + + if (!X509V3_add1_i2d(exts, NID_key_usage, ext, + PyObject_IsTrue(critical), + X509V3_ADD_REPLACE)) + lose_openssl_error("Couldn't add KeyUsage extension to OpenSSL object"); + + ok = 1; + + error: /* Fall through */ + ASN1_BIT_STRING_free(ext); + Py_XDECREF(iterator); + Py_XDECREF(item); + + if (ok) + Py_RETURN_NONE; + else + return NULL; +} + +static PyObject * +extension_get_basic_constraints(X509_EXTENSIONS **exts) +{ + BASIC_CONSTRAINTS *ext = NULL; + PyObject *result = NULL; + + ENTERING(extension_get_basic_constraints); + + if (!exts) + goto error; + + if ((ext = X509V3_get_d2i(*exts, NID_basic_constraints, NULL, NULL)) == NULL) + Py_RETURN_NONE; + + if (ext->pathlen == NULL) + result = Py_BuildValue("(NO)", PyBool_FromLong(ext->ca), Py_None); + else + result = Py_BuildValue("(Nl)", PyBool_FromLong(ext->ca), ASN1_INTEGER_get(ext->pathlen)); + + error: + BASIC_CONSTRAINTS_free(ext); + return result; +} + +static PyObject * +extension_set_basic_constraints(X509_EXTENSIONS **exts, PyObject *args) +{ + BASIC_CONSTRAINTS *ext = NULL; + PyObject *is_ca = NULL; + PyObject *pathlen_obj = Py_None; + PyObject *critical = Py_True; + long pathlen = -1; + int ok = 0; + + ENTERING(extension_set_basic_constraints); + + if (!exts) + goto error; + + if (!PyArg_ParseTuple(args, "O|OO", &is_ca, &pathlen_obj, &critical)) + goto error; + + if (pathlen_obj != Py_None && (pathlen = PyInt_AsLong(pathlen_obj)) < 0) + lose_type_error("Bad pathLenConstraint value"); + + if ((ext = BASIC_CONSTRAINTS_new()) == NULL) + lose_no_memory(); + + ext->ca = PyObject_IsTrue(is_ca) ? 0xFF : 0; + + if (pathlen_obj != Py_None && + ((ext->pathlen == NULL && (ext->pathlen = ASN1_INTEGER_new()) == NULL) || + !ASN1_INTEGER_set(ext->pathlen, pathlen))) + lose_no_memory(); + + if (!X509V3_add1_i2d(exts, NID_basic_constraints, ext, + PyObject_IsTrue(critical), X509V3_ADD_REPLACE)) + lose_openssl_error("Couldn't add BasicConstraints extension to OpenSSL object"); + + ok = 1; + + error: + BASIC_CONSTRAINTS_free(ext); + + if (ok) + Py_RETURN_NONE; + else + return NULL; +} + +static PyObject * +extension_get_sia(X509_EXTENSIONS **exts) +{ + AUTHORITY_INFO_ACCESS *ext = NULL; + PyObject *result = NULL; + PyObject *result_caRepository = NULL; + PyObject *result_rpkiManifest = NULL; + PyObject *result_signedObject = NULL; + int n_caRepository = 0; + int n_rpkiManifest = 0; + int n_signedObject = 0; + const char *uri; + PyObject *obj; + int i, nid; + + ENTERING(pkcs10_object_get_sia); + + if (!exts) + goto error; + + if ((ext = X509V3_get_d2i(*exts, NID_sinfo_access, NULL, NULL)) == NULL) + Py_RETURN_NONE; + + /* + * Easiest to do this in two passes, first pass just counts URIs. + */ + + for (i = 0; i < sk_ACCESS_DESCRIPTION_num(ext); i++) { + ACCESS_DESCRIPTION *a = sk_ACCESS_DESCRIPTION_value(ext, i); + if (a->location->type != GEN_URI) + continue; + nid = OBJ_obj2nid(a->method); + if (nid == NID_caRepository) { + n_caRepository++; + continue; + } + if (nid == NID_rpkiManifest) { + n_rpkiManifest++; + continue; + } + if (nid == NID_signedObject) { + n_signedObject++; + continue; + } + } + + if (((result_caRepository = PyTuple_New(n_caRepository)) == NULL) || + ((result_rpkiManifest = PyTuple_New(n_rpkiManifest)) == NULL) || + ((result_signedObject = PyTuple_New(n_signedObject)) == NULL)) + goto error; + + n_caRepository = n_rpkiManifest = n_signedObject = 0; + + for (i = 0; i < sk_ACCESS_DESCRIPTION_num(ext); i++) { + ACCESS_DESCRIPTION *a = sk_ACCESS_DESCRIPTION_value(ext, i); + if (a->location->type != GEN_URI) + continue; + nid = OBJ_obj2nid(a->method); + uri = (char *) ASN1_STRING_data(a->location->d.uniformResourceIdentifier); + if (nid == NID_caRepository) { + if ((obj = PyString_FromString(uri)) == NULL) + goto error; + PyTuple_SET_ITEM(result_caRepository, n_caRepository++, obj); + continue; + } + if (nid == NID_rpkiManifest) { + if ((obj = PyString_FromString(uri)) == NULL) + goto error; + PyTuple_SET_ITEM(result_rpkiManifest, n_rpkiManifest++, obj); + continue; + } + if (nid == NID_signedObject) { + if ((obj = PyString_FromString(uri)) == NULL) + goto error; + PyTuple_SET_ITEM(result_signedObject, n_signedObject++, obj); + continue; + } + } + + result = Py_BuildValue("(OOO)", + result_caRepository, + result_rpkiManifest, + result_signedObject); + + error: + AUTHORITY_INFO_ACCESS_free(ext); + Py_XDECREF(result_caRepository); + Py_XDECREF(result_rpkiManifest); + Py_XDECREF(result_signedObject); + return result; +} + +static PyObject * +extension_set_sia(X509_EXTENSIONS **exts, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"caRepository", "rpkiManifest", "signedObject", NULL}; + AUTHORITY_INFO_ACCESS *ext = NULL; + PyObject *caRepository = Py_None; + PyObject *rpkiManifest = Py_None; + PyObject *signedObject = Py_None; + PyObject *iterator = NULL; + ASN1_OBJECT *oid = NULL; + PyObject **pobj = NULL; + PyObject *item = NULL; + ACCESS_DESCRIPTION *a = NULL; + int i, nid = NID_undef, ok = 0; + Py_ssize_t urilen; + char *uri; + + ENTERING(extension_set_sia); + + if (!exts) + goto error; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist, + &caRepository, &rpkiManifest, &signedObject)) + goto error; + + if ((ext = AUTHORITY_INFO_ACCESS_new()) == NULL) + lose_no_memory(); + + /* + * This is going to want refactoring, because it's ugly, because we + * want to reuse code for AIA, and because it'd be nice to support a + * single URI as an abbreviation for a collection containing one URI. + */ + + for (i = 0; i < 3; i++) { + switch (i) { + case 0: pobj = &caRepository; nid = NID_caRepository; break; + case 1: pobj = &rpkiManifest; nid = NID_rpkiManifest; break; + case 2: pobj = &signedObject; nid = NID_signedObject; break; + } + + if (*pobj == Py_None) + continue; + + if ((oid = OBJ_nid2obj(nid)) == NULL) + lose_openssl_error("Couldn't find SIA accessMethod OID"); + + if ((iterator = PyObject_GetIter(*pobj)) == NULL) + goto error; + + while ((item = PyIter_Next(iterator)) != NULL) { + + if (PyString_AsStringAndSize(item, &uri, &urilen) < 0) + goto error; + + if ((a = ACCESS_DESCRIPTION_new()) == NULL || + (a->method = OBJ_dup(oid)) == NULL || + (a->location->d.uniformResourceIdentifier = ASN1_IA5STRING_new()) == NULL || + !ASN1_OCTET_STRING_set(a->location->d.uniformResourceIdentifier, (unsigned char *) uri, urilen)) + lose_no_memory(); + + a->location->type = GEN_URI; + + if (!sk_ACCESS_DESCRIPTION_push(ext, a)) + lose_no_memory(); + + a = NULL; + Py_XDECREF(item); + item = NULL; + } + + Py_XDECREF(iterator); + iterator = NULL; + } + + if (!X509V3_add1_i2d(exts, NID_sinfo_access, ext, 0, X509V3_ADD_REPLACE)) + lose_openssl_error("Couldn't add SIA extension to OpenSSL object"); + + ok = 1; + + error: + AUTHORITY_INFO_ACCESS_free(ext); + ACCESS_DESCRIPTION_free(a); + Py_XDECREF(item); + Py_XDECREF(iterator); + + if (ok) + Py_RETURN_NONE; + else + return NULL; +} + +static PyObject * +extension_get_eku(X509_EXTENSIONS **exts) +{ + EXTENDED_KEY_USAGE *ext = NULL; + PyObject *result = NULL; + PyObject *oid = NULL; + int i; + + ENTERING(extension_get_eku); + + if (!exts) + goto error; + + if ((ext = X509V3_get_d2i(*exts, NID_ext_key_usage, NULL, NULL)) == NULL) + Py_RETURN_NONE; + + if ((result = PyFrozenSet_New(NULL)) == NULL) + goto error; + + for (i = 0; i < sk_ASN1_OBJECT_num(ext); i++) { + if ((oid = ASN1_OBJECT_to_PyString(sk_ASN1_OBJECT_value(ext, i))) == NULL || + PySet_Add(result, oid) < 0) + goto error; + Py_XDECREF(oid); + oid = NULL; + } + + sk_ASN1_OBJECT_pop_free(ext, ASN1_OBJECT_free); + return result; + + error: + sk_ASN1_OBJECT_pop_free(ext, ASN1_OBJECT_free); + Py_XDECREF(oid); + Py_XDECREF(result); + return NULL; +} + +static PyObject * +extension_set_eku(X509_EXTENSIONS **exts, PyObject *args) +{ + EXTENDED_KEY_USAGE *ext = NULL; + PyObject *iterable = NULL; + PyObject *critical = Py_False; + PyObject *iterator = NULL; + PyObject *item = NULL; + ASN1_OBJECT *obj = NULL; + const char *txt; + int ok = 0; + + ENTERING(extension_set_eku); + + if (!exts) + goto error; + + if ((ext = sk_ASN1_OBJECT_new_null()) == NULL) + lose_no_memory(); + + if (!PyArg_ParseTuple(args, "O|O", &iterable, &critical) || + (iterator = PyObject_GetIter(iterable)) == NULL) + goto error; + + while ((item = PyIter_Next(iterator)) != NULL) { + + if ((txt = PyString_AsString(item)) == NULL) + goto error; + + if ((obj = OBJ_txt2obj(txt, 1)) == NULL) + lose("Couldn't parse OID"); + + if (!sk_ASN1_OBJECT_push(ext, obj)) + lose_no_memory(); + + obj = NULL; + Py_XDECREF(item); + item = NULL; + } + + if (sk_ASN1_OBJECT_num(ext) < 1) + lose("Empty ExtendedKeyUsage extension"); + + if (!X509V3_add1_i2d(exts, NID_ext_key_usage, ext, + PyObject_IsTrue(critical), + X509V3_ADD_REPLACE)) + lose_openssl_error("Couldn't add ExtendedKeyUsage extension to OpenSSL object"); + + ok = 1; + + error: /* Fall through */ + sk_ASN1_OBJECT_pop_free(ext, ASN1_OBJECT_free); + Py_XDECREF(item); + Py_XDECREF(iterator); + + if (ok) + Py_RETURN_NONE; + else + return NULL; +} + +static PyObject * +extension_get_ski(X509_EXTENSIONS **exts) +{ + ASN1_OCTET_STRING *ext = NULL; + PyObject *result = NULL; + + ENTERING(extension_get_ski); + + if (!exts) + goto error; + + if ((ext = X509V3_get_d2i(*exts, NID_subject_key_identifier, NULL, NULL)) == NULL) + Py_RETURN_NONE; + + result = Py_BuildValue("s#", ASN1_STRING_data(ext), + (Py_ssize_t) ASN1_STRING_length(ext)); + + error: /* Fall through */ + ASN1_OCTET_STRING_free(ext); + return result; +} + +static PyObject * +extension_set_ski(X509_EXTENSIONS **exts, PyObject *args) +{ + ASN1_OCTET_STRING *ext = NULL; + const unsigned char *buf = NULL; + Py_ssize_t len; + int ok = 0; + + ENTERING(extension_set_ski); + + if (!exts) + goto error; + + if (!PyArg_ParseTuple(args, "s#", &buf, &len)) + goto error; + + if ((ext = ASN1_OCTET_STRING_new()) == NULL || + !ASN1_OCTET_STRING_set(ext, buf, len)) + lose_no_memory(); + + /* + * RFC 5280 says this MUST be non-critical. + */ + + if (!X509V3_add1_i2d(exts, NID_subject_key_identifier, + ext, 0, X509V3_ADD_REPLACE)) + lose_openssl_error("Couldn't add SKI extension to OpenSSL object"); + + ok = 1; + + error: + ASN1_OCTET_STRING_free(ext); + + if (ok) + Py_RETURN_NONE; + else + return NULL; +} + +static PyObject * +extension_get_aki(X509_EXTENSIONS **exts) +{ + AUTHORITY_KEYID *ext = NULL; + PyObject *result = NULL; + + ENTERING(extension_get_aki); + + if (!exts) + goto error; + + if ((ext = X509V3_get_d2i(*exts, NID_authority_key_identifier, NULL, NULL)) == NULL) + Py_RETURN_NONE; + + result = Py_BuildValue("s#", ASN1_STRING_data(ext->keyid), + (Py_ssize_t) ASN1_STRING_length(ext->keyid)); + + error: /* Fall through */ + AUTHORITY_KEYID_free(ext); + return result; +} + +static PyObject * +extension_set_aki(X509_EXTENSIONS **exts, PyObject *args) +{ + AUTHORITY_KEYID *ext = NULL; + const unsigned char *buf = NULL; + Py_ssize_t len; + int ok = 0; + + ENTERING(extension_set_aki); + + assert (exts); + + if (!PyArg_ParseTuple(args, "s#", &buf, &len)) + goto error; + + if ((ext = AUTHORITY_KEYID_new()) == NULL || + (ext->keyid == NULL && (ext->keyid = ASN1_OCTET_STRING_new()) == NULL) || + !ASN1_OCTET_STRING_set(ext->keyid, buf, len)) + lose_no_memory(); + + /* + * RFC 5280 says this MUST be non-critical. + */ + + if (!X509V3_add1_i2d(exts, NID_authority_key_identifier, + ext, 0, X509V3_ADD_REPLACE)) + lose_openssl_error("Couldn't add AKI extension to OpenSSL object"); + + ok = 1; + + error: + AUTHORITY_KEYID_free(ext); + + if (ok) + Py_RETURN_NONE; + else + return NULL; +} + + + +/* * IPAddress object. */ @@ -1716,6 +2327,15 @@ x509_object_der_write(x509_object *self) return result; } +static X509_EXTENSIONS ** +x509_object_extension_helper(x509_object *self) +{ + if (self && self->x509 && self->x509->cert_info) + return &self->x509->cert_info->extensions; + PyErr_SetString(PyExc_ValueError, "Can't find X509_EXTENSIONS in X509 object"); + return NULL; +} + static char x509_object_get_public_key__doc__[] = "Return the public key from this certificate object,\n" "as an Asymmetric object.\n" @@ -2144,15 +2764,7 @@ static char x509_object_get_ski__doc__[] = static PyObject * x509_object_get_ski(x509_object *self) { - ENTERING(x509_object_get_ski); - - (void) X509_check_ca(self->x509); /* Calls x509v3_cache_extensions() */ - - if (self->x509->skid == NULL) - Py_RETURN_NONE; - else - return Py_BuildValue("s#", ASN1_STRING_data(self->x509->skid), - (Py_ssize_t) ASN1_STRING_length(self->x509->skid)); + return extension_get_ski(x509_object_extension_helper(self)); } static char x509_object_set_ski__doc__[] = @@ -2162,37 +2774,7 @@ static char x509_object_set_ski__doc__[] = static PyObject * x509_object_set_ski(x509_object *self, PyObject *args) { - ASN1_OCTET_STRING *ext = NULL; - const unsigned char *buf = NULL; - Py_ssize_t len; - int ok = 0; - - ENTERING(x509_object_set_ski); - - if (!PyArg_ParseTuple(args, "s#", &buf, &len)) - goto error; - - if ((ext = ASN1_OCTET_STRING_new()) == NULL || - !ASN1_OCTET_STRING_set(ext, buf, len)) - lose_no_memory(); - - /* - * RFC 5280 4.2.1.2 says this MUST be non-critical. - */ - - if (!X509_add1_ext_i2d(self->x509, NID_subject_key_identifier, - ext, 0, X509V3_ADD_REPLACE)) - lose_openssl_error("Couldn't add SKI extension to certificate"); - - ok = 1; - - error: - ASN1_OCTET_STRING_free(ext); - - if (ok) - Py_RETURN_NONE; - else - return NULL; + return extension_set_ski(x509_object_extension_helper(self), args); } static char x509_object_get_aki__doc__[] = @@ -2204,15 +2786,7 @@ static char x509_object_get_aki__doc__[] = static PyObject * x509_object_get_aki(x509_object *self) { - ENTERING(x509_object_get_aki); - - (void) X509_check_ca(self->x509); /* Calls x509v3_cache_extensions() */ - - if (self->x509->akid == NULL || self->x509->akid->keyid == NULL) - Py_RETURN_NONE; - else - return Py_BuildValue("s#", ASN1_STRING_data(self->x509->akid->keyid), - (Py_ssize_t) ASN1_STRING_length(self->x509->akid->keyid)); + return extension_get_aki(x509_object_extension_helper(self)); } static char x509_object_set_aki__doc__[] = @@ -2225,38 +2799,7 @@ static char x509_object_set_aki__doc__[] = static PyObject * x509_object_set_aki(x509_object *self, PyObject *args) { - AUTHORITY_KEYID *ext = NULL; - const unsigned char *buf = NULL; - Py_ssize_t len; - int ok = 0; - - ENTERING(x509_object_set_aki); - - if (!PyArg_ParseTuple(args, "s#", &buf, &len)) - goto error; - - if ((ext = AUTHORITY_KEYID_new()) == NULL || - (ext->keyid == NULL && (ext->keyid = ASN1_OCTET_STRING_new()) == NULL) || - !ASN1_OCTET_STRING_set(ext->keyid, buf, len)) - lose_no_memory(); - - /* - * RFC 5280 4.2.1.1 says this MUST be non-critical. - */ - - if (!X509_add1_ext_i2d(self->x509, NID_authority_key_identifier, - ext, 0, X509V3_ADD_REPLACE)) - lose_openssl_error("Couldn't add AKI extension to certificate"); - - ok = 1; - - error: - AUTHORITY_KEYID_free(ext); - - if (ok) - Py_RETURN_NONE; - else - return NULL; + return extension_set_aki(x509_object_extension_helper(self), args); } static char x509_object_get_key_usage__doc__[] = @@ -2268,36 +2811,7 @@ static char x509_object_get_key_usage__doc__[] = static PyObject * x509_object_get_key_usage(x509_object *self) { - ASN1_BIT_STRING *ext = NULL; - PyObject *result = NULL; - PyObject *token = NULL; - int bit = -1; - - ENTERING(x509_object_get_key_usage); - - if ((ext = X509_get_ext_d2i(self->x509, NID_key_usage, NULL, NULL)) == NULL) - Py_RETURN_NONE; - - if ((result = PyFrozenSet_New(NULL)) == NULL) - goto error; - - for (bit = 0; key_usage_bit_names[bit] != NULL; bit++) { - if (ASN1_BIT_STRING_get_bit(ext, bit) && - ((token = PyString_FromString(key_usage_bit_names[bit])) == NULL || - PySet_Add(result, token) < 0)) - goto error; - Py_XDECREF(token); - token = NULL; - } - - ASN1_BIT_STRING_free(ext); - return result; - - error: - ASN1_BIT_STRING_free(ext); - Py_XDECREF(token); - Py_XDECREF(result); - return NULL; + return extension_get_key_usage(x509_object_extension_helper(self)); } static char x509_object_set_key_usage__doc__[] = @@ -2314,59 +2828,36 @@ static char x509_object_set_key_usage__doc__[] = static PyObject * x509_object_set_key_usage(x509_object *self, PyObject *args) { - ASN1_BIT_STRING *ext = NULL; - PyObject *iterable = NULL; - PyObject *critical = Py_True; - PyObject *iterator = NULL; - PyObject *token = NULL; - const char *t; - int bit = -1; - int ok = 0; - - ENTERING(x509_object_set_key_usage); - - if ((ext = ASN1_BIT_STRING_new()) == NULL) - lose_no_memory(); - - if (!PyArg_ParseTuple(args, "O|O", &iterable, &critical) || - (iterator = PyObject_GetIter(iterable)) == NULL) - goto error; - - while ((token = PyIter_Next(iterator)) != NULL) { - - if ((t = PyString_AsString(token)) == NULL) - goto error; - - for (bit = 0; key_usage_bit_names[bit] != NULL; bit++) - if (!strcmp(t, key_usage_bit_names[bit])) - break; - - if (key_usage_bit_names[bit] == NULL) - lose("Unrecognized KeyUsage token"); - - if (!ASN1_BIT_STRING_set_bit(ext, bit, 1)) - lose_no_memory(); - - Py_XDECREF(token); - token = NULL; - } + return extension_set_key_usage(x509_object_extension_helper(self), args); +} - if (!X509_add1_ext_i2d(self->x509, NID_key_usage, ext, - PyObject_IsTrue(critical), - X509V3_ADD_REPLACE)) - lose_openssl_error("Couldn't add KeyUsage extension to certificate"); +static char x509_object_get_eku__doc__[] = + "Return a FrozenSet of object identifiers representing the\n" + "ExtendedKeyUsage settings for this certificate, or None if\n" + "the certificate has no ExtendedKeyUsage extension.\n" + ; - ok = 1; +static PyObject * +x509_object_get_eku(x509_object *self) +{ + return extension_get_eku(x509_object_extension_helper(self)); +} - error: /* Fall through */ - ASN1_BIT_STRING_free(ext); - Py_XDECREF(iterator); - Py_XDECREF(token); +static char x509_object_set_eku__doc__[] = + "Set the ExtendedKeyUsage extension for this certificate.\n" + "\n" + "Argument \"iterable\" should be an iterable object which returns one or more\n" + "object identifiers.\n" + "\n" + "Optional argument \"critical\" is a boolean indicating whether the extension\n" + "should be marked as critical or not. RFC 6487 4.8.5 says this extension\n" + "MUST NOT be marked as non-critical when used, so the default is False.\n" + ; - if (ok) - Py_RETURN_NONE; - else - return NULL; +static PyObject * +x509_object_set_eku(x509_object *self, PyObject *args) +{ + return extension_set_eku(x509_object_extension_helper(self), args); } static char x509_object_get_rfc3779__doc__[] = @@ -2745,21 +3236,7 @@ static char x509_object_get_basic_constraints__doc__[] = static PyObject * x509_object_get_basic_constraints(x509_object *self) { - BASIC_CONSTRAINTS *ext = NULL; - PyObject *result; - - ENTERING(x509_object_get_basic_constraints); - - if ((ext = X509_get_ext_d2i(self->x509, NID_basic_constraints, NULL, NULL)) == NULL) - Py_RETURN_NONE; - - if (ext->pathlen == NULL) - result = Py_BuildValue("(NO)", PyBool_FromLong(ext->ca), Py_None); - else - result = Py_BuildValue("(Nl)", PyBool_FromLong(ext->ca), ASN1_INTEGER_get(ext->pathlen)); - - BASIC_CONSTRAINTS_free(ext); - return result; + return extension_get_basic_constraints(x509_object_extension_helper(self)); } static char x509_object_set_basic_constraints__doc__[] = @@ -2780,44 +3257,7 @@ static char x509_object_set_basic_constraints__doc__[] = static PyObject * x509_object_set_basic_constraints(x509_object *self, PyObject *args) { - BASIC_CONSTRAINTS *ext = NULL; - PyObject *is_ca = NULL; - PyObject *pathlen_obj = Py_None; - PyObject *critical = Py_True; - long pathlen = -1; - int ok = 0; - - ENTERING(x509_object_set_basic_constraints); - - if (!PyArg_ParseTuple(args, "O|OO", &is_ca, &pathlen_obj, &critical)) - goto error; - - if (pathlen_obj != Py_None && (pathlen = PyInt_AsLong(pathlen_obj)) < 0) - lose_type_error("Bad pathLenConstraint value"); - - if ((ext = BASIC_CONSTRAINTS_new()) == NULL) - lose_no_memory(); - - ext->ca = PyObject_IsTrue(is_ca) ? 0xFF : 0; - - if (pathlen_obj != Py_None && - ((ext->pathlen == NULL && (ext->pathlen = ASN1_INTEGER_new()) == NULL) || - !ASN1_INTEGER_set(ext->pathlen, pathlen))) - lose_no_memory(); - - if (!X509_add1_ext_i2d(self->x509, NID_basic_constraints, - ext, PyObject_IsTrue(critical), X509V3_ADD_REPLACE)) - lose_openssl_error("Couldn't add BasicConstraints extension to certificate"); - - ok = 1; - - error: - BASIC_CONSTRAINTS_free(ext); - - if (ok) - Py_RETURN_NONE; - else - return NULL; + return extension_set_basic_constraints(x509_object_extension_helper(self), args); } static char x509_object_get_sia__doc__[] = @@ -2837,90 +3277,7 @@ static char x509_object_get_sia__doc__[] = static PyObject * x509_object_get_sia(x509_object *self) { - AUTHORITY_INFO_ACCESS *ext = NULL; - PyObject *result = NULL; - PyObject *result_caRepository = NULL; - PyObject *result_rpkiManifest = NULL; - PyObject *result_signedObject = NULL; - int n_caRepository = 0; - int n_rpkiManifest = 0; - int n_signedObject = 0; - const char *uri; - PyObject *obj; - int i, nid; - - ENTERING(x509_object_get_sia); - - if ((ext = X509_get_ext_d2i(self->x509, NID_sinfo_access, NULL, NULL)) == NULL) - Py_RETURN_NONE; - - /* - * Easiest to do this in two passes, first pass just counts URIs. - */ - - for (i = 0; i < sk_ACCESS_DESCRIPTION_num(ext); i++) { - ACCESS_DESCRIPTION *a = sk_ACCESS_DESCRIPTION_value(ext, i); - if (a->location->type != GEN_URI) - continue; - nid = OBJ_obj2nid(a->method); - if (nid == NID_caRepository) { - n_caRepository++; - continue; - } - if (nid == NID_rpkiManifest) { - n_rpkiManifest++; - continue; - } - if (nid == NID_signedObject) { - n_signedObject++; - continue; - } - } - - if (((result_caRepository = PyTuple_New(n_caRepository)) == NULL) || - ((result_rpkiManifest = PyTuple_New(n_rpkiManifest)) == NULL) || - ((result_signedObject = PyTuple_New(n_signedObject)) == NULL)) - goto error; - - n_caRepository = n_rpkiManifest = n_signedObject = 0; - - for (i = 0; i < sk_ACCESS_DESCRIPTION_num(ext); i++) { - ACCESS_DESCRIPTION *a = sk_ACCESS_DESCRIPTION_value(ext, i); - if (a->location->type != GEN_URI) - continue; - nid = OBJ_obj2nid(a->method); - uri = (char *) ASN1_STRING_data(a->location->d.uniformResourceIdentifier); - if (nid == NID_caRepository) { - if ((obj = PyString_FromString(uri)) == NULL) - goto error; - PyTuple_SET_ITEM(result_caRepository, n_caRepository++, obj); - continue; - } - if (nid == NID_rpkiManifest) { - if ((obj = PyString_FromString(uri)) == NULL) - goto error; - PyTuple_SET_ITEM(result_rpkiManifest, n_rpkiManifest++, obj); - continue; - } - if (nid == NID_signedObject) { - if ((obj = PyString_FromString(uri)) == NULL) - goto error; - PyTuple_SET_ITEM(result_signedObject, n_signedObject++, obj); - continue; - } - } - - result = Py_BuildValue("(OOO)", - result_caRepository, - result_rpkiManifest, - result_signedObject); - - error: - AUTHORITY_INFO_ACCESS_free(ext); - Py_XDECREF(result_caRepository); - Py_XDECREF(result_rpkiManifest); - Py_XDECREF(result_signedObject); - return result; + return extension_get_sia(x509_object_extension_helper(self)); } static char x509_object_set_sia__doc__[] = @@ -2935,91 +3292,7 @@ static char x509_object_set_sia__doc__[] = static PyObject * x509_object_set_sia(x509_object *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"caRepository", "rpkiManifest", "signedObject", NULL}; - AUTHORITY_INFO_ACCESS *ext = NULL; - PyObject *caRepository = Py_None; - PyObject *rpkiManifest = Py_None; - PyObject *signedObject = Py_None; - PyObject *iterator = NULL; - ASN1_OBJECT *oid = NULL; - PyObject **pobj = NULL; - PyObject *item = NULL; - ACCESS_DESCRIPTION *a = NULL; - int i, nid = NID_undef, ok = 0; - Py_ssize_t urilen; - char *uri; - - ENTERING(x509_object_set_sia); - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist, - &caRepository, &rpkiManifest, &signedObject)) - goto error; - - if ((ext = AUTHORITY_INFO_ACCESS_new()) == NULL) - lose_no_memory(); - - /* - * This is going to want refactoring, because it's ugly, because we - * want to reuse code for AIA, and because it'd be nice to support a - * single URI as an abbreviation for a collection containing one URI. - */ - - for (i = 0; i < 3; i++) { - switch (i) { - case 0: pobj = &caRepository; nid = NID_caRepository; break; - case 1: pobj = &rpkiManifest; nid = NID_rpkiManifest; break; - case 2: pobj = &signedObject; nid = NID_signedObject; break; - } - - if (*pobj == Py_None) - continue; - - if ((oid = OBJ_nid2obj(nid)) == NULL) - lose_openssl_error("Couldn't find SIA accessMethod OID"); - - if ((iterator = PyObject_GetIter(*pobj)) == NULL) - goto error; - - while ((item = PyIter_Next(iterator)) != NULL) { - - if (PyString_AsStringAndSize(item, &uri, &urilen) < 0) - goto error; - - if ((a = ACCESS_DESCRIPTION_new()) == NULL || - (a->method = OBJ_dup(oid)) == NULL || - (a->location->d.uniformResourceIdentifier = ASN1_IA5STRING_new()) == NULL || - !ASN1_OCTET_STRING_set(a->location->d.uniformResourceIdentifier, (unsigned char *) uri, urilen)) - lose_no_memory(); - - a->location->type = GEN_URI; - - if (!sk_ACCESS_DESCRIPTION_push(ext, a)) - lose_no_memory(); - - a = NULL; - Py_XDECREF(item); - item = NULL; - } - - Py_XDECREF(iterator); - iterator = NULL; - } - - if (!X509_add1_ext_i2d(self->x509, NID_sinfo_access, ext, 0, X509V3_ADD_REPLACE)) - lose_openssl_error("Couldn't add SIA extension to certificate"); - - ok = 1; - - error: - AUTHORITY_INFO_ACCESS_free(ext); - ACCESS_DESCRIPTION_free(a); - Py_XDECREF(item); - Py_XDECREF(iterator); - - if (ok) - Py_RETURN_NONE; - else - return NULL; + return extension_set_sia(x509_object_extension_helper(self), args, kwds); } static char x509_object_get_aia__doc__[] = @@ -3464,6 +3737,8 @@ static struct PyMethodDef x509_object_methods[] = { Define_Method(setAKI, x509_object_set_aki, METH_VARARGS), Define_Method(getKeyUsage, x509_object_get_key_usage, METH_NOARGS), Define_Method(setKeyUsage, x509_object_set_key_usage, METH_VARARGS), + Define_Method(getEKU, x509_object_get_eku, METH_NOARGS), + Define_Method(setEKU, x509_object_set_eku, METH_VARARGS), Define_Method(getRFC3779, x509_object_get_rfc3779, METH_NOARGS), Define_Method(setRFC3779, x509_object_set_rfc3779, METH_KEYWORDS), Define_Method(getBasicConstraints, x509_object_get_basic_constraints, METH_NOARGS), @@ -4245,6 +4520,15 @@ crl_object_der_read_file(PyTypeObject *type, PyObject *args) return read_from_file_helper(crl_object_der_read_helper, type, args); } +static X509_EXTENSIONS ** +crl_object_extension_helper(crl_object *self) +{ + if (self && self->crl && self->crl->crl) + return &self->crl->crl->extensions; + PyErr_SetString(PyExc_ValueError, "Can't find X509_EXTENSIONS in CRL object"); + return NULL; +} + static char crl_object_get_version__doc__[] = "return the version number of this CRL.\n" ; @@ -4617,7 +4901,7 @@ crl_object_sign(crl_object *self, PyObject *args) } static char crl_object_verify__doc__[] = - "Verifie this CRL's signature.\n" + "Verify this CRL's signature.\n" "\n" "The check is performed using OpenSSL's X509_CRL_verify() function.\n" "\n" @@ -4700,22 +4984,7 @@ static char crl_object_get_aki__doc__[] = static PyObject * crl_object_get_aki(crl_object *self) { - AUTHORITY_KEYID *ext = X509_CRL_get_ext_d2i(self->crl, NID_authority_key_identifier, NULL, NULL); - int empty = (ext == NULL || ext->keyid == NULL); - PyObject *result = NULL; - - ENTERING(crl_object_get_aki); - - if (!empty) - result = Py_BuildValue("s#", ASN1_STRING_data(ext->keyid), - (Py_ssize_t) ASN1_STRING_length(ext->keyid)); - - AUTHORITY_KEYID_free(ext); - - if (empty) - Py_RETURN_NONE; - else - return result; + return extension_get_aki(crl_object_extension_helper(self)); } static char crl_object_set_aki__doc__[] = @@ -4727,34 +4996,7 @@ static char crl_object_set_aki__doc__[] = static PyObject * crl_object_set_aki(crl_object *self, PyObject *args) { - AUTHORITY_KEYID *ext = NULL; - const unsigned char *buf = NULL; - Py_ssize_t len; - int ok = 0; - - ENTERING(crl_object_set_aki); - - if (!PyArg_ParseTuple(args, "s#", &buf, &len)) - goto error; - - if ((ext = AUTHORITY_KEYID_new()) == NULL || - (ext->keyid = ASN1_OCTET_STRING_new()) == NULL || - !ASN1_OCTET_STRING_set(ext->keyid, buf, len)) - lose_no_memory(); - - if (!X509_CRL_add1_ext_i2d(self->crl, NID_authority_key_identifier, - ext, 0, X509V3_ADD_REPLACE)) - lose_openssl_error("Couldn't add AKI extension to CRL"); - - ok = 1; - - error: - AUTHORITY_KEYID_free(ext); - - if (ok) - Py_RETURN_NONE; - else - return NULL; + return extension_set_aki(crl_object_extension_helper(self), args); } static char crl_object_get_crl_number__doc__[] = @@ -8073,6 +8315,12 @@ pkcs10_object_der_write(pkcs10_object *self) return result; } +static X509_EXTENSIONS ** +pkcs10_object_extension_helper(pkcs10_object *self) +{ + return &self->exts; +} + static char pkcs10_object_get_public_key__doc__[] = "Return the public key from this PKCS#10 request, as an Asymmetric\n" "object.\n" @@ -8315,36 +8563,7 @@ static char pkcs10_object_get_key_usage__doc__[] = static PyObject * pkcs10_object_get_key_usage(pkcs10_object *self) { - ASN1_BIT_STRING *ext = NULL; - PyObject *result = NULL; - PyObject *token = NULL; - int bit = -1; - - ENTERING(pkcs10_object_get_key_usage); - - if ((ext = X509V3_get_d2i(self->exts, NID_key_usage, NULL, NULL)) == NULL) - Py_RETURN_NONE; - - if ((result = PyFrozenSet_New(NULL)) == NULL) - goto error; - - for (bit = 0; key_usage_bit_names[bit] != NULL; bit++) { - if (ASN1_BIT_STRING_get_bit(ext, bit) && - ((token = PyString_FromString(key_usage_bit_names[bit])) == NULL || - PySet_Add(result, token) < 0)) - goto error; - Py_XDECREF(token); - token = NULL; - } - - ASN1_BIT_STRING_free(ext); - return result; - - error: - ASN1_BIT_STRING_free(ext); - Py_XDECREF(token); - Py_XDECREF(result); - return NULL; + return extension_get_key_usage(pkcs10_object_extension_helper(self)); } static char pkcs10_object_set_key_usage__doc__[] = @@ -8361,59 +8580,36 @@ static char pkcs10_object_set_key_usage__doc__[] = static PyObject * pkcs10_object_set_key_usage(pkcs10_object *self, PyObject *args) { - ASN1_BIT_STRING *ext = NULL; - PyObject *iterable = NULL; - PyObject *critical = Py_True; - PyObject *iterator = NULL; - PyObject *token = NULL; - const char *t; - int bit = -1; - int ok = 0; - - ENTERING(pkcs10_object_set_key_usage); - - if ((ext = ASN1_BIT_STRING_new()) == NULL) - lose_no_memory(); - - if (!PyArg_ParseTuple(args, "O|O", &iterable, &critical) || - (iterator = PyObject_GetIter(iterable)) == NULL) - goto error; - - while ((token = PyIter_Next(iterator)) != NULL) { - - if ((t = PyString_AsString(token)) == NULL) - goto error; - - for (bit = 0; key_usage_bit_names[bit] != NULL; bit++) - if (!strcmp(t, key_usage_bit_names[bit])) - break; - - if (key_usage_bit_names[bit] == NULL) - lose("Unrecognized KeyUsage token"); - - if (!ASN1_BIT_STRING_set_bit(ext, bit, 1)) - lose_no_memory(); - - Py_XDECREF(token); - token = NULL; - } + return extension_set_key_usage(pkcs10_object_extension_helper(self), args); +} - if (!X509V3_add1_i2d(&self->exts, NID_key_usage, ext, - PyObject_IsTrue(critical), - X509V3_ADD_REPLACE)) - lose_openssl_error("Couldn't add KeyUsage extension to certificate"); +static char pkcs10_object_get_eku__doc__[] = + "Return a FrozenSet of object identifiers representing the\n" + "ExtendedKeyUsage settings for this PKCS #10 requst, or None if\n" + "the request has no ExtendedKeyUsage extension.\n" + ; - ok = 1; +static PyObject * +pkcs10_object_get_eku(pkcs10_object *self) +{ + return extension_get_eku(pkcs10_object_extension_helper(self)); +} - error: /* Fall through */ - ASN1_BIT_STRING_free(ext); - Py_XDECREF(iterator); - Py_XDECREF(token); +static char pkcs10_object_set_eku__doc__[] = + "Set the ExtendedKeyUsage extension for this PKCS #10 request.\n" + "\n" + "Argument \"iterable\" should be an iterable object which returns one or more\n" + "object identifiers.\n" + "\n" + "Optional argument \"critical\" is a boolean indicating whether the extension\n" + "should be marked as critical or not. RFC 6487 4.8.5 says this extension\n" + "MUST NOT be marked as non-critical when used, so the default is False.\n" + ; - if (ok) - Py_RETURN_NONE; - else - return NULL; +static PyObject * +pkcs10_object_set_eku(pkcs10_object *self, PyObject *args) +{ + return extension_set_eku(pkcs10_object_extension_helper(self), args); } static char pkcs10_object_get_basic_constraints__doc__[] = @@ -8431,21 +8627,7 @@ static char pkcs10_object_get_basic_constraints__doc__[] = static PyObject * pkcs10_object_get_basic_constraints(pkcs10_object *self) { - BASIC_CONSTRAINTS *ext = NULL; - PyObject *result; - - ENTERING(pkcs10_object_get_basic_constraints); - - if ((ext = X509V3_get_d2i(self->exts, NID_basic_constraints, NULL, NULL)) == NULL) - Py_RETURN_NONE; - - if (ext->pathlen == NULL) - result = Py_BuildValue("(NO)", PyBool_FromLong(ext->ca), Py_None); - else - result = Py_BuildValue("(Nl)", PyBool_FromLong(ext->ca), ASN1_INTEGER_get(ext->pathlen)); - - BASIC_CONSTRAINTS_free(ext); - return result; + return extension_get_basic_constraints(pkcs10_object_extension_helper(self)); } static char pkcs10_object_set_basic_constraints__doc__[] = @@ -8467,44 +8649,7 @@ static char pkcs10_object_set_basic_constraints__doc__[] = static PyObject * pkcs10_object_set_basic_constraints(pkcs10_object *self, PyObject *args) { - BASIC_CONSTRAINTS *ext = NULL; - PyObject *is_ca = NULL; - PyObject *pathlen_obj = Py_None; - PyObject *critical = Py_True; - long pathlen = -1; - int ok = 0; - - ENTERING(pkcs10_object_set_basic_constraints); - - if (!PyArg_ParseTuple(args, "O|OO", &is_ca, &pathlen_obj, &critical)) - goto error; - - if (pathlen_obj != Py_None && (pathlen = PyInt_AsLong(pathlen_obj)) < 0) - lose_type_error("Bad pathLenConstraint value"); - - if ((ext = BASIC_CONSTRAINTS_new()) == NULL) - lose_no_memory(); - - ext->ca = PyObject_IsTrue(is_ca) ? 0xFF : 0; - - if (pathlen_obj != Py_None && - ((ext->pathlen == NULL && (ext->pathlen = ASN1_INTEGER_new()) == NULL) || - !ASN1_INTEGER_set(ext->pathlen, pathlen))) - lose_no_memory(); - - if (!X509V3_add1_i2d(&self->exts, NID_basic_constraints, ext, - PyObject_IsTrue(critical), X509V3_ADD_REPLACE)) - lose_openssl_error("Couldn't add BasicConstraints extension to certificate"); - - ok = 1; - - error: - BASIC_CONSTRAINTS_free(ext); - - if (ok) - Py_RETURN_NONE; - else - return NULL; + return extension_set_basic_constraints(pkcs10_object_extension_helper(self), args); } static char pkcs10_object_get_sia__doc__[] = @@ -8521,90 +8666,7 @@ static char pkcs10_object_get_sia__doc__[] = static PyObject * pkcs10_object_get_sia(pkcs10_object *self) { - AUTHORITY_INFO_ACCESS *ext = NULL; - PyObject *result = NULL; - PyObject *result_caRepository = NULL; - PyObject *result_rpkiManifest = NULL; - PyObject *result_signedObject = NULL; - int n_caRepository = 0; - int n_rpkiManifest = 0; - int n_signedObject = 0; - const char *uri; - PyObject *obj; - int i, nid; - - ENTERING(pkcs10_object_get_sia); - - if ((ext = X509V3_get_d2i(self->exts, NID_sinfo_access, NULL, NULL)) == NULL) - Py_RETURN_NONE; - - /* - * Easiest to do this in two passes, first pass just counts URIs. - */ - - for (i = 0; i < sk_ACCESS_DESCRIPTION_num(ext); i++) { - ACCESS_DESCRIPTION *a = sk_ACCESS_DESCRIPTION_value(ext, i); - if (a->location->type != GEN_URI) - continue; - nid = OBJ_obj2nid(a->method); - if (nid == NID_caRepository) { - n_caRepository++; - continue; - } - if (nid == NID_rpkiManifest) { - n_rpkiManifest++; - continue; - } - if (nid == NID_signedObject) { - n_signedObject++; - continue; - } - } - - if (((result_caRepository = PyTuple_New(n_caRepository)) == NULL) || - ((result_rpkiManifest = PyTuple_New(n_rpkiManifest)) == NULL) || - ((result_signedObject = PyTuple_New(n_signedObject)) == NULL)) - goto error; - - n_caRepository = n_rpkiManifest = n_signedObject = 0; - - for (i = 0; i < sk_ACCESS_DESCRIPTION_num(ext); i++) { - ACCESS_DESCRIPTION *a = sk_ACCESS_DESCRIPTION_value(ext, i); - if (a->location->type != GEN_URI) - continue; - nid = OBJ_obj2nid(a->method); - uri = (char *) ASN1_STRING_data(a->location->d.uniformResourceIdentifier); - if (nid == NID_caRepository) { - if ((obj = PyString_FromString(uri)) == NULL) - goto error; - PyTuple_SET_ITEM(result_caRepository, n_caRepository++, obj); - continue; - } - if (nid == NID_rpkiManifest) { - if ((obj = PyString_FromString(uri)) == NULL) - goto error; - PyTuple_SET_ITEM(result_rpkiManifest, n_rpkiManifest++, obj); - continue; - } - if (nid == NID_signedObject) { - if ((obj = PyString_FromString(uri)) == NULL) - goto error; - PyTuple_SET_ITEM(result_signedObject, n_signedObject++, obj); - continue; - } - } - - result = Py_BuildValue("(OOO)", - result_caRepository, - result_rpkiManifest, - result_signedObject); - - error: - AUTHORITY_INFO_ACCESS_free(ext); - Py_XDECREF(result_caRepository); - Py_XDECREF(result_rpkiManifest); - Py_XDECREF(result_signedObject); - return result; + return extension_get_sia(pkcs10_object_extension_helper(self)); } static char pkcs10_object_set_sia__doc__[] = @@ -8619,91 +8681,9 @@ static char pkcs10_object_set_sia__doc__[] = ; static PyObject * -pkcs10_object_set_sia(pkcs10_object *self, PyObject *args) +pkcs10_object_set_sia(pkcs10_object *self, PyObject *args, PyObject *kwds) { - AUTHORITY_INFO_ACCESS *ext = NULL; - PyObject *caRepository = NULL; - PyObject *rpkiManifest = NULL; - PyObject *signedObject = NULL; - PyObject *iterator = NULL; - ASN1_OBJECT *oid = NULL; - PyObject **pobj = NULL; - PyObject *item = NULL; - ACCESS_DESCRIPTION *a = NULL; - int i, nid = NID_undef, ok = 0; - Py_ssize_t urilen; - char *uri; - - ENTERING(pkcs10_object_set_sia); - - if (!PyArg_ParseTuple(args, "OOO", &caRepository, &rpkiManifest, &signedObject)) - goto error; - - if ((ext = AUTHORITY_INFO_ACCESS_new()) == NULL) - lose_no_memory(); - - /* - * This is going to want refactoring, because it's ugly, because we - * want to reuse code for AIA, and because it'd be nice to support a - * single URI as an abbreviation for a sequence containing one URI. - */ - - for (i = 0; i < 3; i++) { - switch (i) { - case 0: pobj = &caRepository; nid = NID_caRepository; break; - case 1: pobj = &rpkiManifest; nid = NID_rpkiManifest; break; - case 2: pobj = &signedObject; nid = NID_signedObject; break; - } - - if (*pobj == Py_None) - continue; - - if ((oid = OBJ_nid2obj(nid)) == NULL) - lose_openssl_error("Couldn't find SIA accessMethod OID"); - - if ((iterator = PyObject_GetIter(*pobj)) == NULL) - goto error; - - while ((item = PyIter_Next(iterator)) != NULL) { - - if (PyString_AsStringAndSize(item, &uri, &urilen) < 0) - goto error; - - if ((a = ACCESS_DESCRIPTION_new()) == NULL || - (a->method = OBJ_dup(oid)) == NULL || - (a->location->d.uniformResourceIdentifier = ASN1_IA5STRING_new()) == NULL || - !ASN1_OCTET_STRING_set(a->location->d.uniformResourceIdentifier, (unsigned char *) uri, urilen)) - lose_no_memory(); - - a->location->type = GEN_URI; - - if (!sk_ACCESS_DESCRIPTION_push(ext, a)) - lose_no_memory(); - - a = NULL; - Py_XDECREF(item); - item = NULL; - } - - Py_XDECREF(iterator); - iterator = NULL; - } - - if (!X509V3_add1_i2d(&self->exts, NID_sinfo_access, ext, 0, X509V3_ADD_REPLACE)) - lose_openssl_error("Couldn't add SIA extension to certificate"); - - ok = 1; - - error: - AUTHORITY_INFO_ACCESS_free(ext); - ACCESS_DESCRIPTION_free(a); - Py_XDECREF(item); - Py_XDECREF(iterator); - - if (ok) - Py_RETURN_NONE; - else - return NULL; + return extension_set_sia(pkcs10_object_extension_helper(self), args, kwds); } static char pkcs10_object_get_signature_algorithm__doc__[] = @@ -8800,10 +8780,12 @@ static struct PyMethodDef pkcs10_object_methods[] = { Define_Method(pprint, pkcs10_object_pprint, METH_NOARGS), Define_Method(getKeyUsage, pkcs10_object_get_key_usage, METH_NOARGS), Define_Method(setKeyUsage, pkcs10_object_set_key_usage, METH_VARARGS), + Define_Method(getEKU, pkcs10_object_get_eku, METH_NOARGS), + Define_Method(setEKU, pkcs10_object_set_eku, METH_VARARGS), Define_Method(getBasicConstraints, pkcs10_object_get_basic_constraints, METH_NOARGS), Define_Method(setBasicConstraints, pkcs10_object_set_basic_constraints, METH_VARARGS), Define_Method(getSIA, pkcs10_object_get_sia, METH_NOARGS), - Define_Method(setSIA, pkcs10_object_set_sia, METH_VARARGS), + Define_Method(setSIA, pkcs10_object_set_sia, METH_KEYWORDS), Define_Method(getSignatureAlgorithm, pkcs10_object_get_signature_algorithm, METH_NOARGS), Define_Method(getExtensionOIDs, pkcs10_object_get_extension_oids, METH_NOARGS), Define_Class_Method(pemRead, pkcs10_object_pem_read, METH_VARARGS), diff --git a/rpkid/left-right-schema.rnc b/rpkid/left-right-schema.rnc index 50b2401e..b46adeb5 100644 --- a/rpkid/left-right-schema.rnc +++ b/rpkid/left-right-schema.rnc @@ -1,37 +1,23 @@ # $Id$ # -# RelaxNG Schema for RPKI left-right protocol. -# -# libxml2 (including xmllint) only groks the XML syntax of RelaxNG, so -# run the compact syntax through trang to get XML syntax. -# -# Copyright (C) 2009-2011 Internet Systems Consortium ("ISC") +# RelaxNG schema for RPKI left-right 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 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. +# 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. default namespace = "http://www.hactrn.net/uris/rpki/left-right-spec/" @@ -51,6 +37,7 @@ query_elt |= child_query query_elt |= repository_query query_elt |= list_roa_requests_query query_elt |= list_ghostbuster_requests_query +query_elt |= list_ee_certificate_requests_query query_elt |= list_resources_query query_elt |= list_published_objects_query query_elt |= list_received_resources_query @@ -64,6 +51,7 @@ reply_elt |= repository_reply reply_elt |= list_resources_reply reply_elt |= list_roa_requests_reply reply_elt |= list_ghostbuster_requests_reply +reply_elt |= list_ee_certificate_requests_reply reply_elt |= list_published_objects_reply reply_elt |= list_received_resources_reply reply_elt |= report_error_reply @@ -89,7 +77,7 @@ base64 = xsd:base64Binary { maxLength="512000" } # in this protocol, so they're turninging into handles. # Length restriction is a MySQL implementation issue. # Handles are case-insensitive (because SQL is, among other reasons). -object_handle = xsd:string { maxLength="255" pattern="[\-_A-Za-z0-9]*" } +object_handle = xsd:string { maxLength="255" pattern="[\-_A-Za-z0-9]+" } # URIs uri = xsd:anyURI { maxLength="4096" } @@ -268,6 +256,25 @@ list_ghostbuster_requests_reply = element list_ghostbuster_requests { xsd:string } +# <list_ee_certificate_requests/> element + +list_ee_certificate_requests_query = element list_ee_certificate_requests { + tag, self_handle +} + +list_ee_certificate_requests_reply = element list_ee_certificate_requests { + tag, self_handle, + attribute gski { xsd:token { minLength="27" maxLength="27" } }, + attribute valid_until { xsd:dateTime { pattern=".*Z" } }, + attribute asn { asn_list }?, + attribute ipv4 { ipv4_list }?, + attribute ipv6 { ipv6_list }?, + attribute cn { xsd:string { maxLength="64" pattern="[\-0-9A-Za-z_ ]+" } }?, + attribute sn { xsd:string { maxLength="64" pattern="[0-9A-Fa-f]+" } }?, + attribute eku { xsd:string { maxLength="512000" pattern="[.,0-9]+" } }?, + element pkcs10 { base64 } +} + # <list_published_objects/> element list_published_objects_query = element list_published_objects { @@ -311,4 +318,6 @@ report_error_reply = element report_error { # Local Variables: # indent-tabs-mode: nil +# comment-start: "# " +# comment-start-skip: "#[ \t]*" # End: diff --git a/rpkid/left-right-schema.rng b/rpkid/left-right-schema.rng index 32188de1..1b2b94b6 100644 --- a/rpkid/left-right-schema.rng +++ b/rpkid/left-right-schema.rng @@ -1,39 +1,25 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: left-right-schema.rnc 4588 2012-07-06 19:43:56Z sra $ + $Id: left-right-schema.rnc 5746 2014-04-04 02:00:06Z sra $ - RelaxNG Schema for RPKI left-right protocol. + RelaxNG schema for RPKI left-right protocol. - libxml2 (including xmllint) only groks the XML syntax of RelaxNG, so - run the compact syntax through trang to get XML syntax. - - Copyright (C) 2009-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. - - 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 notices and this permission notice appear in all copies. - 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. + 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/left-right-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> <!-- Top level PDU --> @@ -87,6 +73,9 @@ <ref name="list_ghostbuster_requests_query"/> </define> <define name="query_elt" combine="choice"> + <ref name="list_ee_certificate_requests_query"/> + </define> + <define name="query_elt" combine="choice"> <ref name="list_resources_query"/> </define> <define name="query_elt" combine="choice"> @@ -121,6 +110,9 @@ <ref name="list_ghostbuster_requests_reply"/> </define> <define name="reply_elt" combine="choice"> + <ref name="list_ee_certificate_requests_reply"/> + </define> + <define name="reply_elt" combine="choice"> <ref name="list_published_objects_reply"/> </define> <define name="reply_elt" combine="choice"> @@ -192,7 +184,7 @@ <define name="object_handle"> <data type="string"> <param name="maxLength">255</param> - <param name="pattern">[\-_A-Za-z0-9]*</param> + <param name="pattern">[\-_A-Za-z0-9]+</param> </data> </define> <!-- URIs --> @@ -929,6 +921,72 @@ <data type="string"/> </element> </define> + <!-- <list_ee_certificate_requests/> element --> + <define name="list_ee_certificate_requests_query"> + <element name="list_ee_certificate_requests"> + <ref name="tag"/> + <ref name="self_handle"/> + </element> + </define> + <define name="list_ee_certificate_requests_reply"> + <element name="list_ee_certificate_requests"> + <ref name="tag"/> + <ref name="self_handle"/> + <attribute name="gski"> + <data type="token"> + <param name="minLength">27</param> + <param name="maxLength">27</param> + </data> + </attribute> + <attribute name="valid_until"> + <data type="dateTime"> + <param name="pattern">.*Z</param> + </data> + </attribute> + <optional> + <attribute name="asn"> + <ref name="asn_list"/> + </attribute> + </optional> + <optional> + <attribute name="ipv4"> + <ref name="ipv4_list"/> + </attribute> + </optional> + <optional> + <attribute name="ipv6"> + <ref name="ipv6_list"/> + </attribute> + </optional> + <optional> + <attribute name="cn"> + <data type="string"> + <param name="maxLength">64</param> + <param name="pattern">[\-0-9A-Za-z_ ]+</param> + </data> + </attribute> + </optional> + <optional> + <attribute name="sn"> + <data type="string"> + <param name="maxLength">64</param> + <param name="pattern">[0-9A-Fa-f]+</param> + </data> + </attribute> + </optional> + <optional> + <attribute name="eku"> + <data type="string"> + <param name="maxLength">512000</param> + <param name="pattern">[.,0-9]+</param> + </data> + </attribute> + </optional> + <element name="pkcs10"> + <ref name="base64"/> + </element> + </element> + </define> <!-- <list_published_objects/> element --> <define name="list_published_objects_query"> <element name="list_published_objects"> @@ -1025,5 +1083,7 @@ <!-- Local Variables: indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" End: --> diff --git a/rpkid/myrpki.rnc b/rpkid/myrpki.rnc index 74603229..156ab0d5 100644 --- a/rpkid/myrpki.rnc +++ b/rpkid/myrpki.rnc @@ -1,6 +1,6 @@ # $Id$ # -# RelaxNG Schema for MyRPKI XML messages. +# RelaxNG schema for MyRPKI XML messages. # # This message protocol is on its way out, as we're in the process of # moving on from the user interface model that produced it, but even @@ -29,13 +29,13 @@ default namespace = "http://www.hactrn.net/uris/rpki/myrpki/" version = "2" base64 = xsd:base64Binary { maxLength="512000" } -object_handle = xsd:string { maxLength="255" pattern="[\-_A-Za-z0-9]*" } -pubd_handle = xsd:string { maxLength="255" pattern="[\-_A-Za-z0-9/]*" } +object_handle = xsd:string { maxLength="255" pattern="[\-_A-Za-z0-9]+" } +pubd_handle = xsd:string { maxLength="255" pattern="[\-_A-Za-z0-9/]+" } uri = xsd:anyURI { maxLength="4096" } asn = xsd:positiveInteger -asn_list = xsd:string { maxLength="512000" pattern="[\-,0-9]*" } -ipv4_list = xsd:string { maxLength="512000" pattern="[\-,0-9/.]*" } -ipv6_list = xsd:string { maxLength="512000" pattern="[\-,0-9/:a-fA-F]*" } +asn_list = xsd:string { maxLength="512000" pattern="[\-,0-9]+" } +ipv4_list = xsd:string { maxLength="512000" pattern="[\-,0-9/.]+" } +ipv6_list = xsd:string { maxLength="512000" pattern="[\-,0-9/:a-fA-F]+" } timestamp = xsd:dateTime { pattern=".*Z" } # Message formate used between configure_resources and @@ -159,4 +159,6 @@ start |= element referral { # Local Variables: # indent-tabs-mode: nil +# comment-start: "# " +# comment-start-skip: "#[ \t]*" # End: diff --git a/rpkid/myrpki.rng b/rpkid/myrpki.rng index a29d4fa1..050c695e 100644 --- a/rpkid/myrpki.rng +++ b/rpkid/myrpki.rng @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: myrpki.rnc 4430 2012-04-17 16:00:14Z sra $ + $Id: myrpki.rnc 5746 2014-04-04 02:00:06Z sra $ - RelaxNG Schema for MyRPKI XML messages. + RelaxNG schema for MyRPKI XML messages. This message protocol is on its way out, as we're in the process of moving on from the user interface model that produced it, but even @@ -38,13 +38,13 @@ <define name="object_handle"> <data type="string"> <param name="maxLength">255</param> - <param name="pattern">[\-_A-Za-z0-9]*</param> + <param name="pattern">[\-_A-Za-z0-9]+</param> </data> </define> <define name="pubd_handle"> <data type="string"> <param name="maxLength">255</param> - <param name="pattern">[\-_A-Za-z0-9/]*</param> + <param name="pattern">[\-_A-Za-z0-9/]+</param> </data> </define> <define name="uri"> @@ -58,19 +58,19 @@ <define name="asn_list"> <data type="string"> <param name="maxLength">512000</param> - <param name="pattern">[\-,0-9]*</param> + <param name="pattern">[\-,0-9]+</param> </data> </define> <define name="ipv4_list"> <data type="string"> <param name="maxLength">512000</param> - <param name="pattern">[\-,0-9/.]*</param> + <param name="pattern">[\-,0-9/.]+</param> </data> </define> <define name="ipv6_list"> <data type="string"> <param name="maxLength">512000</param> - <param name="pattern">[\-,0-9/:a-fA-F]*</param> + <param name="pattern">[\-,0-9/:a-fA-F]+</param> </data> </define> <define name="timestamp"> @@ -373,5 +373,7 @@ <!-- Local Variables: indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" End: --> diff --git a/rpkid/publication-schema.rnc b/rpkid/publication-schema.rnc index 4a4b71c7..4353ae80 100644 --- a/rpkid/publication-schema.rnc +++ b/rpkid/publication-schema.rnc @@ -1,37 +1,23 @@ # $Id$ # -# RelaxNG Schema for RPKI publication protocol. -# -# libxml2 (including xmllint) only groks the XML syntax of RelaxNG, so -# run the compact syntax through trang to get XML syntax. -# -# Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. -# -# Portions copyright (C) 2007-2008 American Registry for Internet Numbers ("ARIN") +# 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 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. +# 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. default namespace = "http://www.hactrn.net/uris/rpki/publication-spec/" @@ -44,10 +30,12 @@ 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 = ( config_query | client_query | certificate_query | crl_query | + manifest_query | roa_query | ghostbuster_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 = ( config_reply | client_reply | certificate_reply | crl_reply | + manifest_reply | roa_reply | ghostbuster_reply | report_error_reply ) # Tag attributes for bulk operations tag = attribute tag { xsd:token {maxLength="1024" } } @@ -66,7 +54,7 @@ uri = attribute uri { uri_t } # Handles on remote objects (replaces passing raw SQL IDs). NB: # Unlike the up-down protocol, handles in this protocol allow "/" as a # hierarchy delimiter. -object_handle = xsd:string { maxLength="255" pattern="[\-_A-Za-z0-9/]*" } +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 @@ -144,4 +132,6 @@ report_error_reply = element report_error { # Local Variables: # indent-tabs-mode: nil +# comment-start: "# " +# comment-start-skip: "#[ \t]*" # End: diff --git a/rpkid/publication-schema.rng b/rpkid/publication-schema.rng index d2cd41b2..4dd90e99 100644 --- a/rpkid/publication-schema.rng +++ b/rpkid/publication-schema.rng @@ -1,39 +1,25 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: publication-schema.rnc 4588 2012-07-06 19:43:56Z sra $ + $Id: publication-schema.rnc 5746 2014-04-04 02:00:06Z sra $ - RelaxNG Schema for RPKI publication protocol. + RelaxNG schema for RPKI publication protocol. - libxml2 (including xmllint) only groks the XML syntax of RelaxNG, so - run the compact syntax through trang to get XML syntax. - - Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - - Permission to use, copy, modify, and distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THIS SOFTWARE. - - Portions copyright (C) 2007-2008 American Registry for Internet Numbers ("ARIN") + 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 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. --> <grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> <!-- Top level PDU --> @@ -126,7 +112,7 @@ <define name="object_handle"> <data type="string"> <param name="maxLength">255</param> - <param name="pattern">[\-_A-Za-z0-9/]*</param> + <param name="pattern">[\-_A-Za-z0-9/]+</param> </data> </define> <!-- @@ -582,5 +568,7 @@ <!-- Local Variables: indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" End: --> diff --git a/rpkid/router-certificate-schema.rnc b/rpkid/router-certificate-schema.rnc new file mode 100644 index 00000000..8cc325ce --- /dev/null +++ b/rpkid/router-certificate-schema.rnc @@ -0,0 +1,61 @@ +# $Id$ +# +# RelaxNG schema for BGPSEC router certificate interchange format. +# +# At least for now, this is a trivial encapsulation of a PKCS #10 +# request, a set (usually containing exactly one member) of autonomous +# system numbers, and a router-id. Be warned that this could change +# radically by the time we have any real operational understanding of +# how these things will be used, this is just our current best guess +# to let us move forward on initial coding. +# +# 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.hactrn.net/uris/rpki/router-certificate/" + +version = "1" +base64 = xsd:base64Binary { maxLength="512000" } +router_id = xsd:unsignedInt +asn_list = xsd:string { maxLength="512000" pattern="[0-9][\-,0-9]*" } +timestamp = xsd:dateTime { pattern=".*Z" } + +# Core payload used in this schema. + +payload = ( + attribute router_id { router_id }, + attribute asn { asn_list }, + attribute valid_until { timestamp }?, + base64 +) + +# We allow two forms, one with a wrapper to allow multiple requests in +# a single file, one without for brevity; the version attribute goes +# in the outermost element in either case. + +start |= element router_certificate_request { + attribute version { version }, + payload +} + +start |= element router_certificate_requests { + attribute version { version }, + element router_certificate_request { payload }* +} + +# Local Variables: +# indent-tabs-mode: nil +# comment-start: "# " +# comment-start-skip: "#[ \t]*" +# End: diff --git a/rpkid/router-certificate-schema.rng b/rpkid/router-certificate-schema.rng new file mode 100644 index 00000000..6b4fb77e --- /dev/null +++ b/rpkid/router-certificate-schema.rng @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + $Id: router-certificate-schema.rnc 5746 2014-04-04 02:00:06Z sra $ + + RelaxNG schema for BGPSEC router certificate interchange format. + + At least for now, this is a trivial encapsulation of a PKCS #10 + request, a set (usually containing exactly one member) of autonomous + system numbers, and a router-id. Be warned that this could change + radically by the time we have any real operational understanding of + how these things will be used, this is just our current best guess + to let us move forward on initial coding. + + 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.hactrn.net/uris/rpki/router-certificate/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> + <define name="version"> + <value>1</value> + </define> + <define name="base64"> + <data type="base64Binary"> + <param name="maxLength">512000</param> + </data> + </define> + <define name="router_id"> + <data type="unsignedInt"/> + </define> + <define name="asn_list"> + <data type="string"> + <param name="maxLength">512000</param> + <param name="pattern">[0-9][\-,0-9]*</param> + </data> + </define> + <define name="timestamp"> + <data type="dateTime"> + <param name="pattern">.*Z</param> + </data> + </define> + <!-- Core payload used in this schema. --> + <define name="payload"> + <attribute name="router_id"> + <ref name="router_id"/> + </attribute> + <attribute name="asn"> + <ref name="asn_list"/> + </attribute> + <optional> + <attribute name="valid_until"> + <ref name="timestamp"/> + </attribute> + </optional> + <ref name="base64"/> + </define> + <!-- + We allow two forms, one with a wrapper to allow multiple requests in + a single file, one without for brevity; the version attribute goes + in the outermost element in either case. + --> + <start combine="choice"> + <element name="router_certificate_request"> + <attribute name="version"> + <ref name="version"/> + </attribute> + <ref name="payload"/> + </element> + </start> + <start combine="choice"> + <element name="router_certificate_requests"> + <attribute name="version"> + <ref name="version"/> + </attribute> + <zeroOrMore> + <element name="router_certificate_request"> + <ref name="payload"/> + </element> + </zeroOrMore> + </element> + </start> +</grammar> +<!-- + Local Variables: + indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" + End: +--> diff --git a/rpkid/rpki-sql-setup b/rpkid/rpki-sql-setup index 1623d542..40a78532 100755 --- a/rpkid/rpki-sql-setup +++ b/rpkid/rpki-sql-setup @@ -2,27 +2,37 @@ # $Id$ # -# Copyright (C) 2009--2013 Internet Systems Consortium ("ISC") -# +# Copyright (C) 2014 Dragon Research Labs ("DRL") +# Portions copyright (C) 2009-2013 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 +# copyright notices and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND DRL AND ISC DISCLAIM ALL +# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL OR +# ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. +import os import sys +import glob import getpass import argparse +import datetime import rpki.config +import rpki.version +import rpki.autoconf import rpki.sql_schemas -from rpki.mysql_import import MySQLdb +from rpki.mysql_import import MySQLdb, _mysql_exceptions + +ER_NO_SUCH_TABLE = 1146 # See mysqld_ername.h + class RootDB(object): """ @@ -58,12 +68,30 @@ class RootDB(object): if self.initialized: self.db.close() + class UserDB(object): """ Class to wrap MySQL access parameters for a particular database. + + NB: The SQL definitions for the upgrade_version table is embedded in + this class rather than being declared in any of the .sql files. + This is deliberate: nothing but the upgrade system should ever touch + this table, and it's simpler to keep everything in one place. + + We have to be careful about SQL commits here, because CREATE TABLE + implies an automatic commit. So presence of the magic table per se + isn't significant, only its content (or lack thereof). """ + upgrade_version_table_schema = """ + CREATE TABLE upgrade_version ( + version TEXT NOT NULL, + updated DATETIME NOT NULL + ) ENGINE=InnoDB + """ + def __init__(self, name): + self.name = name self.database = cfg.get("sql-database", section = name) self.username = cfg.get("sql-username", section = name) self.password = cfg.get("sql-password", section = name) @@ -72,39 +100,104 @@ class UserDB(object): def open(self): self.db = MySQLdb.connect(db = self.database, user = self.username, passwd = self.password) + self.db.autocommit(False) self.cur = self.db.cursor() - def commit(self): - self.db.commit() - def close(self): + if self.cur is not None: + self.cur.close() + self.cur = None if self.db is not None: + self.db.commit() self.db.close() self.db = None - self.cur = None @property def exists_and_accessible(self): try: - db = MySQLdb.connect(db = self.database, user = self.username, passwd = self.password) - db.close() - return True + MySQLdb.connect(db = self.database, user = self.username, passwd = self.password).close() except: return False + else: + return True + @property + def version(self): + try: + self.cur.execute("SELECT version FROM upgrade_version") + v = self.cur.fetchone() + return Version(None if v is None else v[0]) + except _mysql_exceptions.ProgrammingError, e: + if e.args[0] != ER_NO_SUCH_TABLE: + raise + log("Creating upgrade_version table in %s" % self.name) + self.cur.execute(self.upgrade_version_table_schema) + return Version(None) -def read_schema(name): + @version.setter + def version(self, v): + if v > self.version: + self.cur.execute("DELETE FROM upgrade_version") + self.cur.execute("INSERT upgrade_version (version, updated) VALUES (%s, %s)", (v, datetime.datetime.now())) + self.db.commit() + log("Updated %s to %s" % (self.name, v)) + + @property + def schema(self): + lines = [] + for line in getattr(rpki.sql_schemas, self.name, "").splitlines(): + line = " ".join(line.split()) + if line and not line.startswith("--"): + lines.append(line) + return [statement.strip() for statement in " ".join(lines).rstrip(";").split(";") if statement.strip()] + + +class Version(object): """ - Convert an SQL file into a list of SQL statements. + A version number. This is a class in its own right to force the + comparision and string I/O behavior we want. """ - lines = [] - for line in getattr(rpki.sql_schemas, name, "").splitlines(): - line = " ".join(line.split()) - if line and not line.startswith("--"): - lines.append(line) + def __init__(self, v): + if v is None: + v = "0.0" + self.v = tuple(v.lower().split(".")) + + def __str__(self): + return ".".join(self.v) + + def __cmp__(self, other): + return cmp(self.v, other.v) + + +class Upgrade(object): + """ + One upgrade script. Really, just its filename and the Version + object we parse from its filename, we don't need to read the script + itself except when applying it, but we do need to sort all the + available upgrade scripts into version order. + """ + + @classmethod + def load_all(cls, name, dir): + g = os.path.join(dir, "upgrade-%s-to-*.py" % name) + for fn in glob.iglob(g): + yield cls(g, fn) + + def __init__(self, g, fn): + head, sep, tail = g.partition("*") + self.fn = fn + self.version = Version(fn[len(head):-len(tail)]) + + def __cmp__(self, other): + return cmp(self.version, other.version) + + def apply(self, db): + # db is an argument here primarily so the script we exec can get at it + log("Applying %s to %s" % (self.fn, db.name)) + with open(self.fn, "r") as f: + exec f - return [statement.strip() for statement in " ".join(lines).rstrip(";").split(";") if statement.strip()] def do_drop(name): db = UserDB(name) @@ -122,11 +215,11 @@ def do_create(name): (db.password,)) root.db.commit() db.open() - for statement in read_schema(name): + for statement in db.schema: if not statement.upper().startswith("DROP TABLE"): log(statement) db.cur.execute(statement) - db.commit() + db.version = current_version db.close() def do_script_drop(name): @@ -150,6 +243,19 @@ def do_create_if_missing(name): if not db.exists_and_accessible: do_create(name) +def do_apply_upgrades(name): + upgrades = sorted(Upgrade.load_all(name, args.upgrade_scripts)) + if upgrades: + db = UserDB(name) + db.open() + log("Current version of %s is %s" % (db.name, db.version)) + for upgrade in upgrades: + if upgrade.version > db.version: + upgrade.apply(db) + db.version = upgrade.version + db.version = current_version + db.close() + def log(text): if args.verbose: print "#", text @@ -165,6 +271,9 @@ parser.add_argument("-v", "--verbose", action = "store_true", help = "whistle while you work") parser.add_argument("--mysql-defaults", help = "specify MySQL root access credentials via a configuration file") +parser.add_argument("--upgrade-scripts", + default = os.path.join(rpki.autoconf.datarootdir, "rpki", "upgrade-scripts"), + help = "override default location of upgrade scripts") group.add_argument("--create", action = "store_const", dest = "dispatch", const = do_create, help = "create databases and load schemas") @@ -183,18 +292,20 @@ group.add_argument("--fix-grants", group.add_argument("--create-if-missing", action = "store_const", dest = "dispatch", const = do_create_if_missing, help = "create databases and load schemas if they don't exist already") +group.add_argument("--apply-upgrades", + action = "store_const", dest = "dispatch", const = do_apply_upgrades, + help = "apply upgrade scripts to existing databases") parser.set_defaults(dispatch = do_create_if_missing) args = parser.parse_args() -cfg = rpki.config.parser(args.config, "myrpki") -root = RootDB(args.mysql_defaults) try: - if cfg.getboolean("start_irdbd", False): - args.dispatch("irdbd") - if cfg.getboolean("start_rpkid", False): - args.dispatch("rpkid") - if cfg.getboolean("start_pubd", False): - args.dispatch("pubd") + cfg = rpki.config.parser(args.config, "myrpki") + root = RootDB(args.mysql_defaults) + current_version = Version(rpki.version.VERSION) + for name in ("irdbd", "rpkid", "pubd"): + if cfg.getboolean("start_" + name, False): + args.dispatch(name) root.close() except Exception, e: - sys.exit(str(e)) + #sys.exit(str(e)) + raise diff --git a/rpkid/rpki/exceptions.py b/rpkid/rpki/exceptions.py index d390d67b..d8d3774e 100644 --- a/rpkid/rpki/exceptions.py +++ b/rpkid/rpki/exceptions.py @@ -350,3 +350,18 @@ class NullValidityInterval(RPKI_Exception): """ Requested validity interval is null. """ + +class BadX510DN(RPKI_Exception): + """ + X.510 distinguished name does not match profile. + """ + +class BadAutonomousSystemNumber(RPKI_Exception): + """ + Bad AutonomousSystem number. + """ + +class WrongEKU(RPKI_Exception): + """ + Extended Key Usage extension does not match profile. + """ diff --git a/rpkid/rpki/irdb/models.py b/rpkid/rpki/irdb/models.py index c795e21c..1ad9b4e3 100644 --- a/rpkid/rpki/irdb/models.py +++ b/rpkid/rpki/irdb/models.py @@ -401,7 +401,7 @@ class EECertificate(Certificate): self.private_key = rpki.x509.RSA.generate(quiet = True) self.certificate = self.issuer.certify( subject_name = self.subject_name, - subject_key = self.private_key.get_RSApublic(), + subject_key = self.private_key.get_public(), validity_interval = ee_certificate_lifetime, is_ca = False) @@ -451,58 +451,76 @@ class BSC(Certificate): def __unicode__(self): return self.handle -class Child(CrossCertification): - issuer = django.db.models.ForeignKey(ResourceHolderCA, related_name = "children") - name = django.db.models.TextField(null = True, blank = True) +class ResourceSet(django.db.models.Model): valid_until = SundialField() + class Meta: + abstract = True + @property def resource_bag(self): - child_asn = rpki.irdb.ChildASN.objects.raw(""" - SELECT * - FROM irdb_childasn - WHERE child_id = %s - """, [self.id]) - child_net = list(rpki.irdb.ChildNet.objects.raw(""" - SELECT * - FROM irdb_childnet - WHERE child_id = %s - """, [self.id])) + raw_asn, raw_net = self._select_resource_bag() asns = rpki.resource_set.resource_set_as.from_django( - (a.start_as, a.end_as) for a in child_asn) + (a.start_as, a.end_as) for a in raw_asn) ipv4 = rpki.resource_set.resource_set_ipv4.from_django( - (a.start_ip, a.end_ip) for a in child_net if a.version == "IPv4") + (a.start_ip, a.end_ip) for a in raw_net if a.version == "IPv4") ipv6 = rpki.resource_set.resource_set_ipv6.from_django( - (a.start_ip, a.end_ip) for a in child_net if a.version == "IPv6") + (a.start_ip, a.end_ip) for a in raw_net if a.version == "IPv6") return rpki.resource_set.resource_bag( valid_until = self.valid_until, asn = asns, v4 = ipv4, v6 = ipv6) # Writing of .setter method deferred until something needs it. - # This shouldn't be necessary - class Meta: - unique_together = ("issuer", "handle") - -class ChildASN(django.db.models.Model): - child = django.db.models.ForeignKey(Child, related_name = "asns") +class ResourceSetASN(django.db.models.Model): start_as = django.db.models.BigIntegerField() end_as = django.db.models.BigIntegerField() + class Meta: + abstract = True + def as_resource_range(self): return rpki.resource_set.resource_range_as(self.start_as, self.end_as) - class Meta: - unique_together = ("child", "start_as", "end_as") - -class ChildNet(django.db.models.Model): - child = django.db.models.ForeignKey(Child, related_name = "address_ranges") +class ResourceSetNet(django.db.models.Model): start_ip = django.db.models.CharField(max_length = 40) end_ip = django.db.models.CharField(max_length = 40) version = EnumField(choices = ip_version_choices) + class Meta: + abstract = True + def as_resource_range(self): return rpki.resource_set.resource_range_ip.from_strings(self.start_ip, self.end_ip) +class Child(CrossCertification, ResourceSet): + issuer = django.db.models.ForeignKey(ResourceHolderCA, related_name = "children") + name = django.db.models.TextField(null = True, blank = True) + + def _select_resource_bag(self): + child_asn = rpki.irdb.ChildASN.objects.raw(""" + SELECT * + FROM irdb_childasn + WHERE child_id = %s + """, [self.id]) + child_net = list(rpki.irdb.ChildNet.objects.raw(""" + SELECT * + FROM irdb_childnet + WHERE child_id = %s + """, [self.id])) + return child_asn, child_net + + class Meta: + unique_together = ("issuer", "handle") + +class ChildASN(ResourceSetASN): + child = django.db.models.ForeignKey(Child, related_name = "asns") + + class Meta: + unique_together = ("child", "start_as", "end_as") + +class ChildNet(ResourceSetNet): + child = django.db.models.ForeignKey(Child, related_name = "address_ranges") + class Meta: unique_together = ("child", "start_ip", "end_ip", "version") @@ -561,6 +579,42 @@ class GhostbusterRequest(django.db.models.Model): parent = django.db.models.ForeignKey(Parent, related_name = "ghostbuster_requests", null = True) vcard = django.db.models.TextField() +class EECertificateRequest(ResourceSet): + issuer = django.db.models.ForeignKey(ResourceHolderCA, related_name = "ee_certificate_requests") + pkcs10 = PKCS10Field() + gski = django.db.models.CharField(max_length = 27) + cn = django.db.models.CharField(max_length = 64) + sn = django.db.models.CharField(max_length = 64) + eku = django.db.models.TextField(null = True) + + def _select_resource_bag(self): + ee_asn = rpki.irdb.EECertificateRequestASN.objects.raw(""" + SELECT * + FROM irdb_eecertificaterequestasn + WHERE ee_certificate_request_id = %s + """, [self.id]) + ee_net = rpki.irdb.EECertificateRequestNet.objects.raw(""" + SELECT * + FROM irdb_eecertificaterequestnet + WHERE ee_certificate_request_id = %s + """, [self.id]) + return ee_asn, ee_net + + class Meta: + unique_together = ("issuer", "gski") + +class EECertificateRequestASN(ResourceSetASN): + ee_certificate_request = django.db.models.ForeignKey(EECertificateRequest, related_name = "asns") + + class Meta: + unique_together = ("ee_certificate_request", "start_as", "end_as") + +class EECertificateRequestNet(ResourceSetNet): + ee_certificate_request = django.db.models.ForeignKey(EECertificateRequest, related_name = "address_ranges") + + class Meta: + unique_together = ("ee_certificate_request", "start_ip", "end_ip", "version") + class Repository(CrossCertification): issuer = django.db.models.ForeignKey(ResourceHolderCA, related_name = "repositories") client_handle = HandleField() diff --git a/rpkid/rpki/irdb/zookeeper.py b/rpkid/rpki/irdb/zookeeper.py index 2bae0e8b..f99dc9f0 100644 --- a/rpkid/rpki/irdb/zookeeper.py +++ b/rpkid/rpki/irdb/zookeeper.py @@ -55,6 +55,16 @@ myrpki_namespace = "http://www.hactrn.net/uris/rpki/myrpki/" myrpki_version = "2" myrpki_namespaceQName = "{" + myrpki_namespace + "}" +# XML namespace and protocol version for router certificate requests. +# We probably ought to be pulling this sort of thing from the schema, +# with an assertion to make sure that we understand the current +# protocol version number, but just copy what we did for myrpki until +# I'm ready to rewrite the rpki.relaxng code. + +routercert_namespace = "http://www.hactrn.net/uris/rpki/router-certificate/" +routercert_version = "1" +routercert_namespaceQName = "{" + routercert_namespace + "}" + myrpki_section = "myrpki" irdbd_section = "irdbd" rpkid_section = "rpkid" @@ -1588,3 +1598,85 @@ class Zookeeper(object): if rpkid_query: rpkid_reply = self.call_rpkid(rpkid_query) self.check_error_report(rpkid_reply) + + + @django.db.transaction.commit_on_success + def add_ee_certificate_request(self, pkcs10, resources): + """ + Check a PKCS #10 request to see if it complies with the + specification for a RPKI EE certificate; if it does, add an + EECertificateRequest for it to the IRDB. + + Not yet sure what we want for update and delete semantics here, so + for the moment this is straight addition. See methods like + .load_asns() and .load_prefixes() for other strategies. + """ + + pkcs10.check_valid_request_ee() + ee_request = self.resource_ca.ee_certificate_requests.create( + pkcs10 = pkcs10, + gski = pkcs10.gSKI(), + valid_until = resources.valid_until) + for range in resources.asn: + ee_request.asns.create(start_as = str(range.min), end_as = str(range.max)) + for range in resources.v4: + ee_request.address_ranges.create(start_ip = str(range.min), end_ip = str(range.max), version = 4) + for range in resources.v6: + ee_request.address_ranges.create(start_ip = str(range.min), end_ip = str(range.max), version = 6) + + + @django.db.transaction.commit_on_success + def add_router_certificate_request(self, router_certificate_request_xml, valid_until = None): + """ + Read XML file containing one or more router certificate requests, + attempt to add request(s) to IRDB. + + Check each PKCS #10 request to see if it complies with the + specification for a router certificate; if it does, create an EE + certificate request for it along with the ASN resources and + router-ID supplied in the XML. + """ + + xml = ElementTree(file = router_certificate_request_xml).getroot() + rpki.relaxng.router_certificate.assertValid(xml) + + for req in xml.getiterator(routercert_namespaceQName + "router_certificate_request"): + + pkcs10 = rpki.x509.PKCS10(Base64 = req.text) + router_id = long(req.get("router_id")) + asns = rpki.resource_set.resource_set_as(req.get("asn")) + if not valid_until: + valid_until = req.get("valid_until") + + if valid_until and isinstance(valid_until, (str, unicode)): + valid_until = rpki.sundial.datetime.fromXMLtime(valid_until) + + if not valid_until: + valid_until = rpki.sundial.now() + rpki.sundial.timedelta(days = 365) + elif valid_until < rpki.sundial.now(): + raise PastExpiration, "Specified expiration date %s has already passed" % valid_until + + pkcs10.check_valid_request_router() + + cn = "ROUTER-%08x" % asns[0].min + sn = "%08x" % router_id + + ee_request = self.resource_ca.ee_certificate_requests.create( + pkcs10 = pkcs10, + gski = pkcs10.gSKI(), + valid_until = valid_until, + cn = cn, + sn = sn, + eku = rpki.oids.id_kp_bgpsec_router) + + for range in asns: + ee_request.asns.create(start_as = str(range.min), end_as = str(range.max)) + + + @django.db.transaction.commit_on_success + def delete_router_certificate_request(self, gski): + """ + Delete a router certificate request from this RPKI entity. + """ + + self.resource_ca.ee_certificate_requests.get(gski = gski).delete() diff --git a/rpkid/rpki/irdbd.py b/rpkid/rpki/irdbd.py index c27995e7..41739dc4 100644 --- a/rpkid/rpki/irdbd.py +++ b/rpkid/rpki/irdbd.py @@ -39,8 +39,9 @@ import rpki.daemonize class main(object): def handle_list_resources(self, q_pdu, r_msg): - child = rpki.irdb.Child.objects.get(issuer__handle__exact = q_pdu.self_handle, - handle = q_pdu.child_handle) + child = rpki.irdb.Child.objects.get( + issuer__handle__exact = q_pdu.self_handle, + handle = q_pdu.child_handle) resources = child.resource_bag r_pdu = rpki.left_right.list_resources_elt() r_pdu.tag = q_pdu.tag @@ -84,6 +85,23 @@ class main(object): r_pdu.vcard = ghostbuster.vcard r_msg.append(r_pdu) + def handle_list_ee_certificate_requests(self, q_pdu, r_msg): + for ee_req in rpki.irdb.EECertificateRequest.objects.filter(issuer__handle__exact = q_pdu.self_handle): + resources = ee_req.resource_bag + r_pdu = rpki.left_right.list_ee_certificate_requests_elt() + r_pdu.tag = q_pdu.tag + r_pdu.self_handle = q_pdu.self_handle + r_pdu.gski = ee_req.gski + r_pdu.valid_until = ee_req.valid_until.strftime("%Y-%m-%dT%H:%M:%SZ") + r_pdu.asn = resources.asn + r_pdu.ipv4 = resources.v4 + r_pdu.ipv6 = resources.v6 + r_pdu.cn = ee_req.cn + r_pdu.sn = ee_req.sn + r_pdu.eku = ee_req.eku + r_pdu.pkcs10 = ee_req.pkcs10 + r_msg.append(r_pdu) + def handler(self, query, path, cb): try: q_pdu = None @@ -219,9 +237,10 @@ class main(object): self.start_new_transaction = django.db.transaction.commit_manually(django.db.transaction.commit) self.dispatch_vector = { - rpki.left_right.list_resources_elt : self.handle_list_resources, - rpki.left_right.list_roa_requests_elt : self.handle_list_roa_requests, - rpki.left_right.list_ghostbuster_requests_elt : self.handle_list_ghostbuster_requests } + rpki.left_right.list_resources_elt : self.handle_list_resources, + rpki.left_right.list_roa_requests_elt : self.handle_list_roa_requests, + rpki.left_right.list_ghostbuster_requests_elt : self.handle_list_ghostbuster_requests, + rpki.left_right.list_ee_certificate_requests_elt : self.handle_list_ee_certificate_requests} try: self.http_server_host = self.cfg.get("server-host", "") diff --git a/rpkid/rpki/left_right.py b/rpkid/rpki/left_right.py index 9be927f3..2d46cdfa 100644 --- a/rpkid/rpki/left_right.py +++ b/rpkid/rpki/left_right.py @@ -205,6 +205,13 @@ class self_elt(data_elt): """ return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) + @property + def ee_certificates(self): + """ + 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,)) + def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): """ @@ -362,19 +369,33 @@ class self_elt(data_elt): """ if self.cron_tasks is None: - self.cron_tasks = ( - rpki.rpkid_tasks.PollParentTask(self), - rpki.rpkid_tasks.UpdateChildrenTask(self), - rpki.rpkid_tasks.UpdateROAsTask(self), - rpki.rpkid_tasks.UpdateGhostbustersTask(self), - rpki.rpkid_tasks.RegenerateCRLsAndManifestsTask(self), - rpki.rpkid_tasks.CheckFailedPublication(self)) + self.cron_tasks = tuple(task(self) for task in rpki.rpkid_tasks.task_classes) for task in self.cron_tasks: self.gctx.task_add(task) completion.register(task) + def find_covering_ca_details(self, resources): + """ + Return all active ca_detail_objs for this <self/> which cover a + particular set of resources. + + If we expected there to be a large number of ca_detail_objs, we + could add index tables and write fancy SQL query to do this, but + for the expected common case where there are only one or two + active ca_detail_objs per <self/>, it's probably not worth it. In + any case, this is an optimization we can leave for later. + """ + + results = set() + for parent in self.parents: + for ca in parent.cas: + ca_detail = ca.active_ca_detail + if ca_detail is not None and ca_detail.covers(resources): + results.add(ca_detail) + return results + class bsc_elt(data_elt): """ <bsc/> (Business Signing Context) element. @@ -1036,6 +1057,66 @@ class list_ghostbuster_requests_elt(rpki.xml_utils.text_elt, left_right_namespac def __repr__(self): return rpki.log.log_repr(self, self.self_handle, self.parent_handle) +class list_ee_certificate_requests_elt(rpki.xml_utils.base_elt, left_right_namespace): + """ + <list_ee_certificate_requests/> element. + """ + + element_name = "list_ee_certificate_requests" + attributes = ("self_handle", "tag", "gski", "valid_until", "asn", "ipv4", "ipv6", "cn", "sn", "eku") + elements = ("pkcs10",) + + pkcs10 = None + valid_until = None + eku = None + + def __repr__(self): + return rpki.log.log_repr(self, self.self_handle, self.gski, self.cn, self.sn, self.asn, self.ipv4, self.ipv6) + + def startElement(self, stack, name, attrs): + """ + 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) + if isinstance(self.valid_until, str): + self.valid_until = rpki.sundial.datetime.fromXMLtime(self.valid_until) + if self.asn is not None: + self.asn = rpki.resource_set.resource_set_as(self.asn) + if self.ipv4 is not None: + self.ipv4 = rpki.resource_set.resource_set_ipv4(self.ipv4) + if self.ipv6 is not None: + self.ipv6 = rpki.resource_set.resource_set_ipv6(self.ipv6) + if self.eku is not None: + self.eku = self.eku.split(",") + + def endElement(self, stack, name, text): + """ + Handle <pkcs10/> sub-element. + """ + assert len(self.elements) == 1 + if name == self.elements[0]: + self.pkcs10 = rpki.x509.PKCS10(Base64 = text) + else: + assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) + stack.pop() + + def toXML(self): + """ + 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() + for i in self.elements: + self.make_b64elt(elt, i, getattr(self, i, None)) + if isinstance(self.valid_until, int): + elt.set("valid_until", self.valid_until.toXMLtime()) + return elt + class list_published_objects_elt(rpki.xml_utils.text_elt, left_right_namespace): """ <list_published_objects/> element. @@ -1069,6 +1150,8 @@ class list_published_objects_elt(rpki.xml_utils.text_elt, left_right_namespace): for r in ca_detail.roas if r.roa is not None) r_msg.extend(self.make_reply(g.uri, g.ghostbuster) for g in ca_detail.ghostbusters) + r_msg.extend(self.make_reply(c.uri, c.cert) + for c in ca_detail.ee_certificates) cb() def make_reply(self, uri, obj, child_handle = None): @@ -1165,6 +1248,7 @@ class msg(rpki.xml_utils.msg, left_right_namespace): for x in (self_elt, child_elt, parent_elt, bsc_elt, repository_elt, list_resources_elt, list_roa_requests_elt, list_ghostbuster_requests_elt, + list_ee_certificate_requests_elt, list_published_objects_elt, list_received_resources_elt, report_error_elt)) diff --git a/rpkid/rpki/oids.py b/rpkid/rpki/oids.py index 094fa1a2..a97df6a7 100644 --- a/rpkid/rpki/oids.py +++ b/rpkid/rpki/oids.py @@ -1,140 +1,101 @@ # $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. """ OID database. -""" - -## @var oid2name -# Mapping table of OIDs to conventional string names. -oid2name = { - (1, 2, 840, 113549, 1, 1, 11) : "sha256WithRSAEncryption", - (1, 2, 840, 113549, 1, 1, 12) : "sha384WithRSAEncryption", - (1, 2, 840, 113549, 1, 1, 13) : "sha512WithRSAEncryption", - (1, 2, 840, 113549, 1, 7, 1) : "id-data", - (1, 2, 840, 113549, 1, 9, 16) : "id-smime", - (1, 2, 840, 113549, 1, 9, 16, 1) : "id-ct", - (1, 2, 840, 113549, 1, 9, 16, 1, 24) : "id-ct-routeOriginAttestation", - (1, 2, 840, 113549, 1, 9, 16, 1, 26) : "id-ct-rpkiManifest", - (1, 2, 840, 113549, 1, 9, 16, 1, 28) : "id-ct-xml", - (1, 2, 840, 113549, 1, 9, 16, 1, 35) : "id-ct-rpkiGhostbusters", - (1, 3, 6, 1, 5, 5, 7, 1, 1) : "authorityInfoAccess", - (1, 3, 6, 1, 5, 5, 7, 1, 11) : "subjectInfoAccess", - (1, 3, 6, 1, 5, 5, 7, 1, 7) : "sbgp-ipAddrBlock", - (1, 3, 6, 1, 5, 5, 7, 1, 8) : "sbgp-autonomousSysNum", - (1, 3, 6, 1, 5, 5, 7, 14, 2) : "id-cp-ipAddr-asNumber", - (1, 3, 6, 1, 5, 5, 7, 48, 2) : "id-ad-caIssuers", - (1, 3, 6, 1, 5, 5, 7, 48, 5) : "id-ad-caRepository", - (1, 3, 6, 1, 5, 5, 7, 48, 9) : "id-ad-signedObjectRepository", - (1, 3, 6, 1, 5, 5, 7, 48, 10) : "id-ad-rpkiManifest", - (1, 3, 6, 1, 5, 5, 7, 48, 11) : "id-ad-signedObject", - (2, 16, 840, 1, 101, 3, 4, 2, 1) : "id-sha256", - (2, 5, 29, 14) : "subjectKeyIdentifier", - (2, 5, 29, 15) : "keyUsage", - (2, 5, 29, 19) : "basicConstraints", - (2, 5, 29, 20) : "cRLNumber", - (2, 5, 29, 31) : "cRLDistributionPoints", - (2, 5, 29, 32) : "certificatePolicies", - (2, 5, 29, 35) : "authorityKeyIdentifier", - (2, 5, 29, 37) : "extendedKeyUsage", - (2, 5, 4, 3) : "commonName", - (2, 5, 4, 5) : "serialNumber", - (2, 5, 4, 6) : "countryName", - (2, 5, 4, 7) : "localityName", - (2, 5, 4, 8) : "stateOrProvinceName", - (2, 5, 4, 9) : "streetAddress", - (2, 5, 4, 10) : "organizationName", - (2, 5, 4, 11) : "organizationalUnitName", -} +This used to be fairly complicated, with multiple representations and +a collection of conversion functions, but now it is very simple: -## @var name2oid -# Mapping table of string names to OIDs +- We represent OIDs as Python strings, holding the dotted-decimal + form of an OID. Nothing but decimal digits and "." is legal. + This is compatible with the format that rpki.POW uses. -name2oid = dict((v, k) for k, v in oid2name.items()) +- We define symbols in this module whose values are OIDs. -def safe_name2oid(name): - """ - Map name to OID, also parsing numeric (dotted decimal) format. - """ - - try: - return name2oid[name] - except KeyError: - fields = name.split(".") - if all(field.isdigit() for field in fields): - return tuple(int(field) for field in fields) - raise - -def safe_oid2name(oid): - """ - Map OID to name. If we have no mapping, generate numeric (dotted - decimal) format. - """ - - try: - return oid2name[oid] - except KeyError: - return oid2dotted(oid) - -def oid2dotted(oid): - """ - Convert OID to numeric (dotted decimal) format. - """ - - return ".".join(str(field) for field in oid) - -def dotted2oid(dotted): - """ - Convert dotted decimal format to OID tuple. - """ - - fields = dotted.split(".") - if all(field.isdigit() for field in fields): - return tuple(int(field) for field in fields) - raise ValueError("%r is not a dotted decimal OID" % dotted) +That's pretty much it. There's a bit of code at the end which checks +the syntax of the defined strings and provides a pretty-print function +for the rare occasion when we need to print an OID, but other than +that this is just a collection of symbolic names for text strings. +""" -def safe_name2dotted(name): +ecdsa_with_SHA256 = "1.2.840.10045.4.3.2" +sha256WithRSAEncryption = "1.2.840.113549.1.1.11" +sha384WithRSAEncryption = "1.2.840.113549.1.1.12" +sha512WithRSAEncryption = "1.2.840.113549.1.1.13" +id_data = "1.2.840.113549.1.7.1" +id_smime = "1.2.840.113549.1.9.16" +id_ct = "1.2.840.113549.1.9.16.1" +id_ct_routeOriginAttestation = "1.2.840.113549.1.9.16.1.24" +id_ct_rpkiManifest = "1.2.840.113549.1.9.16.1.26" +id_ct_xml = "1.2.840.113549.1.9.16.1.28" +id_ct_rpkiGhostbusters = "1.2.840.113549.1.9.16.1.35" +authorityInfoAccess = "1.3.6.1.5.5.7.1.1" +sbgp_ipAddrBlock = "1.3.6.1.5.5.7.1.7" +sbgp_autonomousSysNum = "1.3.6.1.5.5.7.1.8" +subjectInfoAccess = "1.3.6.1.5.5.7.1.11" +id_kp_bgpsec_router = "1.3.6.1.5.5.7.3.30" +id_cp_ipAddr_asNumber = "1.3.6.1.5.5.7.14.2" +id_ad_caIssuers = "1.3.6.1.5.5.7.48.2" +id_ad_caRepository = "1.3.6.1.5.5.7.48.5" +id_ad_signedObjectRepository = "1.3.6.1.5.5.7.48.9" +id_ad_rpkiManifest = "1.3.6.1.5.5.7.48.10" +id_ad_signedObject = "1.3.6.1.5.5.7.48.11" +commonName = "2.5.4.3" +serialNumber = "2.5.4.5" +countryName = "2.5.4.6" +localityName = "2.5.4.7" +stateOrProvinceName = "2.5.4.8" +streetAddress = "2.5.4.9" +organizationName = "2.5.4.10" +organizationalUnitName = "2.5.4.11" +subjectKeyIdentifier = "2.5.29.14" +keyUsage = "2.5.29.15" +basicConstraints = "2.5.29.19" +cRLNumber = "2.5.29.20" +cRLDistributionPoints = "2.5.29.31" +certificatePolicies = "2.5.29.32" +authorityKeyIdentifier = "2.5.29.35" +extendedKeyUsage = "2.5.29.37" +id_sha256 = "2.16.840.1.101.3.4.2.1" + +# Make sure all symbols exported so far look like OIDs, and build a +# dictionary to use when pretty-printing. + +_oid2name = {} + +for _sym in dir(): + if not _sym.startswith("_"): + _val = globals()[_sym] + if not isinstance(_val, str) or not all(_v.isdigit() for _v in _val.split(".")): + raise ValueError("Bad OID definition: %s = %r" % (_sym, _val)) + _oid2name[_val] = _sym.replace("_", "-") + +del _sym +del _val + +def oid2name(oid): """ - Convert name to dotted decimal format. + Translate an OID into a string suitable for printing. """ - return oid2dotted(safe_name2oid(name)) - -def safe_dotted2name(dotted): - """ - Convert dotted decimal to name if we know one, - otherwise just return dotted. - """ + if not isinstance(oid, (str, unicode)) or not all(o.isdigit() for o in oid.split(".")): + raise ValueError("Parameter does not look like an OID string: " + repr(oid)) - try: - return oid2name[dotted2oid(dotted)] - except KeyError: - return dotted + return _oid2name.get(oid, oid) diff --git a/rpkid/rpki/old_irdbd.py b/rpkid/rpki/old_irdbd.py index 10796711..41060344 100644 --- a/rpkid/rpki/old_irdbd.py +++ b/rpkid/rpki/old_irdbd.py @@ -52,13 +52,18 @@ class main(object): r_pdu.child_handle = q_pdu.child_handle self.cur.execute( - "SELECT registrant_id, valid_until FROM registrant WHERE registry_handle = %s AND registrant_handle = %s", + """ + SELECT registrant_id, valid_until + FROM registrant + WHERE registry_handle = %s AND registrant_handle = %s + """, (q_pdu.self_handle, q_pdu.child_handle)) if self.cur.rowcount != 1: - raise rpki.exceptions.NotInDatabase, \ - "This query should have produced a single exact match, something's messed up (rowcount = %d, self_handle = %s, child_handle = %s)" \ - % (self.cur.rowcount, q_pdu.self_handle, q_pdu.child_handle) + raise rpki.exceptions.NotInDatabase( + "This query should have produced a single exact match, something's messed up" + " (rowcount = %d, self_handle = %s, child_handle = %s)" + % (self.cur.rowcount, q_pdu.self_handle, q_pdu.child_handle)) registrant_id, valid_until = self.cur.fetchone() @@ -66,17 +71,29 @@ class main(object): r_pdu.asn = rpki.resource_set.resource_set_as.from_sql( self.cur, - "SELECT start_as, end_as FROM registrant_asn WHERE registrant_id = %s", + """ + SELECT start_as, end_as + FROM registrant_asn + WHERE registrant_id = %s + """, (registrant_id,)) r_pdu.ipv4 = rpki.resource_set.resource_set_ipv4.from_sql( self.cur, - "SELECT start_ip, end_ip FROM registrant_net WHERE registrant_id = %s AND version = 4", + """ + SELECT start_ip, end_ip + FROM registrant_net + WHERE registrant_id = %s AND version = 4 + """, (registrant_id,)) r_pdu.ipv6 = rpki.resource_set.resource_set_ipv6.from_sql( self.cur, - "SELECT start_ip, end_ip FROM registrant_net WHERE registrant_id = %s AND version = 6", + """ + SELECT start_ip, end_ip + FROM registrant_net + WHERE registrant_id = %s AND version = 6 + """, (registrant_id,)) r_msg.append(r_pdu) @@ -85,7 +102,7 @@ class main(object): def handle_list_roa_requests(self, q_pdu, r_msg): self.cur.execute( - "SELECT roa_request_id, asn FROM roa_request WHERE roa_request_handle = %s", + "SELECT roa_request_id, asn FROM roa_request WHERE self_handle = %s", (q_pdu.self_handle,)) for roa_request_id, asn in self.cur.fetchall(): @@ -97,12 +114,20 @@ class main(object): r_pdu.ipv4 = rpki.resource_set.roa_prefix_set_ipv4.from_sql( self.cur, - "SELECT prefix, prefixlen, max_prefixlen FROM roa_request_prefix WHERE roa_request_id = %s AND version = 4", + """ + SELECT prefix, prefixlen, max_prefixlen + FROM roa_request_prefix + WHERE roa_request_id = %s AND version = 4 + """, (roa_request_id,)) r_pdu.ipv6 = rpki.resource_set.roa_prefix_set_ipv6.from_sql( self.cur, - "SELECT prefix, prefixlen, max_prefixlen FROM roa_request_prefix WHERE roa_request_id = %s AND version = 6", + """ + SELECT prefix, prefixlen, max_prefixlen + FROM roa_request_prefix + WHERE roa_request_id = %s AND version = 6 + """, (roa_request_id,)) r_msg.append(r_pdu) @@ -111,7 +136,11 @@ class main(object): def handle_list_ghostbuster_requests(self, q_pdu, r_msg): self.cur.execute( - "SELECT vcard FROM ghostbuster_request WHERE self_handle = %s AND parent_handle = %s", + """ + SELECT vcard + FROM ghostbuster_request + WHERE self_handle = %s AND parent_handle = %s + """, (q_pdu.self_handle, q_pdu.parent_handle)) vcards = [result[0] for result in self.cur.fetchall()] @@ -119,7 +148,11 @@ class main(object): if not vcards: self.cur.execute( - "SELECT vcard FROM ghostbuster_request WHERE self_handle = %s AND parent_handle IS NULL", + """ + SELECT vcard + FROM ghostbuster_request + WHERE self_handle = %s AND parent_handle IS NULL + """, (q_pdu.self_handle,)) vcards = [result[0] for result in self.cur.fetchall()] @@ -133,11 +166,63 @@ class main(object): r_msg.append(r_pdu) - handle_dispatch = { - rpki.left_right.list_resources_elt : handle_list_resources, - rpki.left_right.list_roa_requests_elt : handle_list_roa_requests, - rpki.left_right.list_ghostbuster_requests_elt : handle_list_ghostbuster_requests} + def handle_list_ee_certificate_requests(self, q_pdu, r_msg): + + self.cur.execute( + """ + SELECT ee_certificate_id, pkcs10, gski, cn, sn, eku, valid_until + FROM ee_certificate + WHERE self_handle = %s + """, + (q_pdu.self_handle,)) + for ee_certificate_id, pkcs10, gski, cn, sn, eku, valid_until in self.cur.fetchall(): + + r_pdu = rpki.left_right.list_ee_certificate_requests_elt() + r_pdu.tag = q_pdu.tag + r_pdu.self_handle = q_pdu.self_handle + r_pdu.valid_until = valid_until.strftime("%Y-%m-%dT%H:%M:%SZ") + r_pdu.pkcs10 = rpki.x509.PKCS10(DER = pkcs10) + r_pdu.gski = gski + r_pdu.cn = cn + r_pdu.sn = sn + r_pdu.eku = eku + + r_pdu.asn = rpki.resource_set.resource_set_as.from_sql( + self.cur, + """ + SELECT start_as, end_as + FROM ee_certificate_asn + WHERE ee_certificate_id = %s + """, + (ee_certificate_id,)) + + r_pdu.ipv4 = rpki.resource_set.resource_set_ipv4.from_sql( + self.cur, + """ + SELECT start_ip, end_ip + FROM ee_certificate_net + WHERE ee_certificate_id = %s AND version = 4 + """, + (ee_certificate_id,)) + + r_pdu.ipv6 = rpki.resource_set.resource_set_ipv6.from_sql( + self.cur, + """ + SELECT start_ip, end_ip + FROM ee_certificate_net + WHERE ee_certificate_id = %s AND version = 6 + """, + (ee_certificate_id,)) + + r_msg.append(r_pdu) + + + handle_dispatch = { + rpki.left_right.list_resources_elt : handle_list_resources, + rpki.left_right.list_roa_requests_elt : handle_list_roa_requests, + rpki.left_right.list_ghostbuster_requests_elt : handle_list_ghostbuster_requests, + rpki.left_right.list_ee_certificate_requests_elt : handle_list_ee_certificate_requests } def handler(self, query, path, cb): try: diff --git a/rpkid/rpki/relaxng.py b/rpkid/rpki/relaxng.py index 962858c7..bc1f57c6 100644 --- a/rpkid/rpki/relaxng.py +++ b/rpkid/rpki/relaxng.py @@ -6,40 +6,26 @@ import lxml.etree ## Parsed RelaxNG left_right schema left_right = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: left-right-schema.rnc 4588 2012-07-06 19:43:56Z sra $ + $Id: left-right-schema.rnc 5746 2014-04-04 02:00:06Z sra $ - RelaxNG Schema for RPKI left-right protocol. + RelaxNG schema for RPKI left-right protocol. - libxml2 (including xmllint) only groks the XML syntax of RelaxNG, so - run the compact syntax through trang to get XML syntax. - - Copyright (C) 2009-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. - - 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. + 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/left-right-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> <!-- Top level PDU --> @@ -93,6 +79,9 @@ left_right = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" en <ref name="list_ghostbuster_requests_query"/> </define> <define name="query_elt" combine="choice"> + <ref name="list_ee_certificate_requests_query"/> + </define> + <define name="query_elt" combine="choice"> <ref name="list_resources_query"/> </define> <define name="query_elt" combine="choice"> @@ -127,6 +116,9 @@ left_right = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" en <ref name="list_ghostbuster_requests_reply"/> </define> <define name="reply_elt" combine="choice"> + <ref name="list_ee_certificate_requests_reply"/> + </define> + <define name="reply_elt" combine="choice"> <ref name="list_published_objects_reply"/> </define> <define name="reply_elt" combine="choice"> @@ -198,7 +190,7 @@ left_right = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" en <define name="object_handle"> <data type="string"> <param name="maxLength">255</param> - <param name="pattern">[\-_A-Za-z0-9]*</param> + <param name="pattern">[\-_A-Za-z0-9]+</param> </data> </define> <!-- URIs --> @@ -935,6 +927,72 @@ left_right = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" en <data type="string"/> </element> </define> + <!-- <list_ee_certificate_requests/> element --> + <define name="list_ee_certificate_requests_query"> + <element name="list_ee_certificate_requests"> + <ref name="tag"/> + <ref name="self_handle"/> + </element> + </define> + <define name="list_ee_certificate_requests_reply"> + <element name="list_ee_certificate_requests"> + <ref name="tag"/> + <ref name="self_handle"/> + <attribute name="gski"> + <data type="token"> + <param name="minLength">27</param> + <param name="maxLength">27</param> + </data> + </attribute> + <attribute name="valid_until"> + <data type="dateTime"> + <param name="pattern">.*Z</param> + </data> + </attribute> + <optional> + <attribute name="asn"> + <ref name="asn_list"/> + </attribute> + </optional> + <optional> + <attribute name="ipv4"> + <ref name="ipv4_list"/> + </attribute> + </optional> + <optional> + <attribute name="ipv6"> + <ref name="ipv6_list"/> + </attribute> + </optional> + <optional> + <attribute name="cn"> + <data type="string"> + <param name="maxLength">64</param> + <param name="pattern">[\-0-9A-Za-z_ ]+</param> + </data> + </attribute> + </optional> + <optional> + <attribute name="sn"> + <data type="string"> + <param name="maxLength">64</param> + <param name="pattern">[0-9A-Fa-f]+</param> + </data> + </attribute> + </optional> + <optional> + <attribute name="eku"> + <data type="string"> + <param name="maxLength">512000</param> + <param name="pattern">[.,0-9]+</param> + </data> + </attribute> + </optional> + <element name="pkcs10"> + <ref name="base64"/> + </element> + </element> + </define> <!-- <list_published_objects/> element --> <define name="list_published_objects_query"> <element name="list_published_objects"> @@ -1031,6 +1089,8 @@ left_right = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" en <!-- Local Variables: indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" End: --> ''')) @@ -1039,13 +1099,42 @@ left_right = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" en ## Parsed RelaxNG up_down schema up_down = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: up-down-schema.rnc 3913 2011-07-01 17:04:18Z sra $ + $Id: up-down-schema.rnc 5748 2014-04-04 16:30:30Z sra $ - RelaxNG Scheme for up-down protocol, extracted from - draft-ietf-sidr-rescerts-provisioning-10.txt. + RelaxNG schema for the up-down protocol, extracted from RFC 6492. - libxml2 (including xmllint) only groks the XML syntax of RelaxNG, so - run the compact syntax through trang to get XML syntax. + Copyright (c) 2012 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.apnic.net/specs/rescerts/up-down/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> <define name="resource_set_as"> @@ -1291,40 +1380,26 @@ up_down = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" encod ## Parsed RelaxNG publication schema publication = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: publication-schema.rnc 4588 2012-07-06 19:43:56Z sra $ - - RelaxNG Schema for RPKI publication protocol. - - libxml2 (including xmllint) only groks the XML syntax of RelaxNG, so - run the compact syntax through trang to get XML syntax. - - Copyright (C) 2009-2010 Internet Systems Consortium ("ISC") - - Permission to use, copy, modify, and distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. + $Id: publication-schema.rnc 5746 2014-04-04 02:00:06Z sra $ - 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. + RelaxNG schema for RPKI publication protocol. - Portions copyright (C) 2007-2008 American Registry for Internet Numbers ("ARIN") + 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 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. --> <grammar ns="http://www.hactrn.net/uris/rpki/publication-spec/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> <!-- Top level PDU --> @@ -1417,7 +1492,7 @@ publication = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" e <define name="object_handle"> <data type="string"> <param name="maxLength">255</param> - <param name="pattern">[\-_A-Za-z0-9/]*</param> + <param name="pattern">[\-_A-Za-z0-9/]+</param> </data> </define> <!-- @@ -1873,6 +1948,8 @@ publication = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" e <!-- Local Variables: indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" End: --> ''')) @@ -1881,9 +1958,9 @@ publication = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" e ## Parsed RelaxNG myrpki schema myrpki = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: myrpki.rnc 4430 2012-04-17 16:00:14Z sra $ + $Id: myrpki.rnc 5746 2014-04-04 02:00:06Z sra $ - RelaxNG Schema for MyRPKI XML messages. + RelaxNG schema for MyRPKI XML messages. This message protocol is on its way out, as we're in the process of moving on from the user interface model that produced it, but even @@ -1919,13 +1996,13 @@ myrpki = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" encodi <define name="object_handle"> <data type="string"> <param name="maxLength">255</param> - <param name="pattern">[\-_A-Za-z0-9]*</param> + <param name="pattern">[\-_A-Za-z0-9]+</param> </data> </define> <define name="pubd_handle"> <data type="string"> <param name="maxLength">255</param> - <param name="pattern">[\-_A-Za-z0-9/]*</param> + <param name="pattern">[\-_A-Za-z0-9/]+</param> </data> </define> <define name="uri"> @@ -1939,19 +2016,19 @@ myrpki = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" encodi <define name="asn_list"> <data type="string"> <param name="maxLength">512000</param> - <param name="pattern">[\-,0-9]*</param> + <param name="pattern">[\-,0-9]+</param> </data> </define> <define name="ipv4_list"> <data type="string"> <param name="maxLength">512000</param> - <param name="pattern">[\-,0-9/.]*</param> + <param name="pattern">[\-,0-9/.]+</param> </data> </define> <define name="ipv6_list"> <data type="string"> <param name="maxLength">512000</param> - <param name="pattern">[\-,0-9/:a-fA-F]*</param> + <param name="pattern">[\-,0-9/:a-fA-F]+</param> </data> </define> <define name="timestamp"> @@ -2254,6 +2331,110 @@ myrpki = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" encodi <!-- Local Variables: indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" + End: +--> +''')) + +## @var router_certificate +## Parsed RelaxNG router_certificate schema +router_certificate = lxml.etree.RelaxNG(lxml.etree.fromstring(r'''<?xml version="1.0" encoding="UTF-8"?> +<!-- + $Id: router-certificate-schema.rnc 5746 2014-04-04 02:00:06Z sra $ + + RelaxNG schema for BGPSEC router certificate interchange format. + + At least for now, this is a trivial encapsulation of a PKCS #10 + request, a set (usually containing exactly one member) of autonomous + system numbers, and a router-id. Be warned that this could change + radically by the time we have any real operational understanding of + how these things will be used, this is just our current best guess + to let us move forward on initial coding. + + 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.hactrn.net/uris/rpki/router-certificate/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> + <define name="version"> + <value>1</value> + </define> + <define name="base64"> + <data type="base64Binary"> + <param name="maxLength">512000</param> + </data> + </define> + <define name="router_id"> + <data type="unsignedInt"/> + </define> + <define name="asn_list"> + <data type="string"> + <param name="maxLength">512000</param> + <param name="pattern">[0-9][\-,0-9]*</param> + </data> + </define> + <define name="timestamp"> + <data type="dateTime"> + <param name="pattern">.*Z</param> + </data> + </define> + <!-- Core payload used in this schema. --> + <define name="payload"> + <attribute name="router_id"> + <ref name="router_id"/> + </attribute> + <attribute name="asn"> + <ref name="asn_list"/> + </attribute> + <optional> + <attribute name="valid_until"> + <ref name="timestamp"/> + </attribute> + </optional> + <ref name="base64"/> + </define> + <!-- + We allow two forms, one with a wrapper to allow multiple requests in + a single file, one without for brevity; the version attribute goes + in the outermost element in either case. + --> + <start combine="choice"> + <element name="router_certificate_request"> + <attribute name="version"> + <ref name="version"/> + </attribute> + <ref name="payload"/> + </element> + </start> + <start combine="choice"> + <element name="router_certificate_requests"> + <attribute name="version"> + <ref name="version"/> + </attribute> + <zeroOrMore> + <element name="router_certificate_request"> + <ref name="payload"/> + </element> + </zeroOrMore> + </element> + </start> +</grammar> +<!-- + Local Variables: + indent-tabs-mode: nil + comment-start: "# " + comment-start-skip: "#[ \t]*" End: --> ''')) diff --git a/rpkid/rpki/resource_set.py b/rpkid/rpki/resource_set.py index f78d37fd..2ec19cab 100644 --- a/rpkid/rpki/resource_set.py +++ b/rpkid/rpki/resource_set.py @@ -30,7 +30,6 @@ We also provide some basic set operations (union, intersection, etc). import re import math -import rpki.oids import rpki.exceptions import rpki.POW diff --git a/rpkid/rpki/rootd.py b/rpkid/rpki/rootd.py index 6723813c..43e84873 100644 --- a/rpkid/rpki/rootd.py +++ b/rpkid/rpki/rootd.py @@ -37,7 +37,6 @@ import rpki.exceptions import rpki.relaxng import rpki.sundial import rpki.log -import rpki.oids import rpki.daemonize rootd = None @@ -50,7 +49,7 @@ class list_pdu(rpki.up_down.list_pdu): class issue_pdu(rpki.up_down.issue_pdu): def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): - self.pkcs10.check_valid_rpki() + self.pkcs10.check_valid_request_ca() r_msg.payload = rpki.up_down.issue_response_pdu() rootd.compose_response(r_msg, self.pkcs10) callback() @@ -230,7 +229,7 @@ class main(object): manifest_keypair = rpki.x509.RSA.generate() manifest_cert = self.rpki_root_cert.issue( keypair = self.rpki_root_key, - subject_key = manifest_keypair.get_RSApublic(), + subject_key = manifest_keypair.get_public(), serial = self.serial_number, sia = (None, None, self.rpki_base_uri + self.rpki_root_manifest), aia = self.rpki_root_cert_uri, diff --git a/rpkid/rpki/rpkic.py b/rpkid/rpki/rpkic.py index 5914dfc1..d5339f5b 100644 --- a/rpkid/rpki/rpkic.py +++ b/rpkid/rpki/rpkic.py @@ -38,7 +38,6 @@ import time import rpki.config import rpki.sundial import rpki.log -import rpki.oids import rpki.http import rpki.resource_set import rpki.relaxng @@ -706,6 +705,54 @@ class main(Cmd): self.zoo.run_rpkid_now() + @parsecmd(argsubparsers, + cmdarg("--valid_until", help = "override default validity interval"), + cmdarg("router_certificate_request_xml", help = "file containing XML router certificate request")) + def do_add_router_certificate_request(self, args): + """ + Load router certificate request(s) into IRDB from XML file. + """ + + self.zoo.add_router_certificate_request(args.router_certificate_request_xml, args.valid_until) + if self.autosync: + self.zoo.run_rpkid_now() + + @parsecmd(argsubparsers, + cmdarg("gski", help = "g(SKI) of router certificate request to delete")) + def do_delete_router_certificate_request(self, args): + """ + Delete a router certificate request from the IRDB. + """ + + try: + self.zoo.delete_router_certificate_request(args.gski) + if self.autosync: + self.zoo.run_rpkid_now() + except rpki.irdb.ResourceHolderCA.DoesNotExist: + print "No such resource holder \"%s\"" % self.zoo.handle + except rpki.irdb.EECertificateRequest.DoesNotExist: + print "No certificate request matching g(SKI) \"%s\"" % args.gski + + def complete_delete_router_certificate_request(self, text, line, begidx, endidx): + return [obj.gski for obj in self.zoo.resource_ca.ee_certificate_requests.all() + if obj.gski and obj.gski.startswith(text)] + + + @parsecmd(argsubparsers) + def do_show_router_certificate_requests(self, args): + """ + Show this entity's router certificate requests. + """ + + for req in self.zoo.resource_ca.ee_certificate_requests.all(): + print "%s %s %s %s" % (req.gski, req.valid_until, req.cn, req.sn) + + + # What about updates? Validity interval, change router-id, change + # ASNs. Not sure what this looks like yet, blunder ahead with the + # core code while mulling over the UI. + + @parsecmd(argsubparsers) def do_synchronize(self, args): """ @@ -817,3 +864,14 @@ class main(Cmd): """ print rpki.version.VERSION + + + @parsecmd(argsubparsers) + def do_list_self_handles(self, args): + """ + List all <self/> handles in this rpkid instance. + """ + + for ca in rpki.irdb.ResourceHolderCA.objects.all(): + print ca.handle + diff --git a/rpkid/rpki/rpkid.py b/rpkid/rpki/rpkid.py index cbb36ada..d6163bee 100644 --- a/rpkid/rpki/rpkid.py +++ b/rpkid/rpki/rpkid.py @@ -27,6 +27,7 @@ import argparse import sys import re import random +import base64 import rpki.resource_set import rpki.up_down import rpki.left_right @@ -116,9 +117,14 @@ class main(object): # Icky hack to let Iain do some testing quickly, should go away # once we sort out whether we can make this change permanent. + # + # OK, the stuff to add router certificate support makes enough + # other changes that we're going to need a migration program in + # any case, so might as well throw the switch here too, or at + # least find out if it (still) works as expected. self.merge_publication_directories = self.cfg.getboolean("merge_publication_directories", - False) + True) self.use_internal_cron = self.cfg.getboolean("use-internal-cron", True) @@ -249,6 +255,18 @@ class main(object): self.irdb_query(callback, errback, *q_pdus) + def irdb_query_ee_certificate_requests(self, self_handle, callback, errback): + """ + Ask IRDB about self's EE certificate requests. + """ + + rpki.log.trace() + + q_pdu = rpki.left_right.list_ee_certificate_requests_elt() + q_pdu.self_handle = self_handle + + self.irdb_query(callback, errback, q_pdu) + def left_right_handler(self, query, path, cb): """ Process one left-right PDU. @@ -522,6 +540,7 @@ class ca_obj(rpki.sql.sql_persistent): sia_uri = self.construct_sia_uri(parent, rc) sia_uri_changed = self.sia_uri != sia_uri if sia_uri_changed: + rpki.log.debug("SIA changed: was %s now %s" % (self.sia_uri, sia_uri)) self.sia_uri = sia_uri self.sql_mark_dirty() @@ -544,6 +563,11 @@ class ca_obj(rpki.sql.sql_persistent): else: + if ca_detail.state == "active" and ca_detail.ca_cert_uri != rc_cert.cert_url.rsync(): + rpki.log.debug("AIA changed: was %s now %s" % (ca_detail.ca_cert_uri, rc_cert.cert_url.rsync())) + ca_detail.ca_cert_uri = rc_cert.cert_url.rsync() + ca_detail.sql_mark_dirty() + if ca_detail.state in ("pending", "active"): if ca_detail.state == "pending": @@ -570,10 +594,12 @@ class ca_obj(rpki.sql.sql_persistent): def done(): if cert_map: - rpki.log.warn("Unknown certificate SKI%s %s in resource class %s in list_response to %s from %s, maybe you want to \"revoke_forgotten\"?" + rpki.log.warn("Unknown certificate SKI%s %s in resource class %s in list_response " + "to %s from %s, maybe you want to \"revoke_forgotten\"?" % ("" if len(cert_map) == 1 else "s", ", ".join(c.cert.gSKI() for c in cert_map.values()), rc.class_name, parent.self.self_handle, parent.parent_handle)) + self.gctx.sql.sweep() self.gctx.checkpoint() cb() @@ -598,7 +624,8 @@ class ca_obj(rpki.sql.sql_persistent): if ca_details: rpki.async.iterator(ca_details, loop, done) else: - rpki.log.warn("Existing resource class %s to %s from %s with no certificates, rekeying" % (rc.class_name, parent.self.self_handle, parent.parent_handle)) + rpki.log.warn("Existing resource class %s to %s from %s with no certificates, rekeying" % + (rc.class_name, parent.self.self_handle, parent.parent_handle)) self.gctx.checkpoint() self.rekey(cb, eb) @@ -748,10 +775,10 @@ class ca_detail_obj(rpki.sql.sql_persistent): "ca_detail", "ca_detail_id", ("private_key_id", rpki.x509.RSA), - ("public_key", rpki.x509.RSApublic), + ("public_key", rpki.x509.PublicKey), ("latest_ca_cert", rpki.x509.X509), ("manifest_private_key_id", rpki.x509.RSA), - ("manifest_public_key", rpki.x509.RSApublic), + ("manifest_public_key", rpki.x509.PublicKey), ("latest_manifest_cert", rpki.x509.X509), ("latest_manifest", rpki.x509.SignedManifest), ("latest_crl", rpki.x509.CRL), @@ -835,6 +862,13 @@ class ca_detail_obj(rpki.sql.sql_persistent): """ return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,)) + @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): """ Fetch all unpublished Ghostbusters objects linked to this @@ -869,6 +903,15 @@ class ca_detail_obj(rpki.sql.sql_persistent): """ return self.latest_ca_cert.getNotAfter() <= rpki.sundial.now() + def covers(self, target): + """ + Test whether this ca-detail covers a given set of resources. + """ + + assert not target.asn.inherit and not target.v4.inherit and not target.v6.inherit + me = self.latest_ca_cert.get_3779resources() + return target.asn <= me.asn and target.v4 <= me.v4 and target.v6 <= me.v6 + def activate(self, ca, cert, uri, callback, errback, predecessor = None): """ Activate this ca_detail. @@ -1082,19 +1125,23 @@ class ca_detail_obj(rpki.sql.sql_persistent): self.state = "pending" self.private_key_id = rpki.x509.RSA.generate() - self.public_key = self.private_key_id.get_RSApublic() + self.public_key = self.private_key_id.get_public() self.manifest_private_key_id = rpki.x509.RSA.generate() - self.manifest_public_key = self.manifest_private_key_id.get_RSApublic() + self.manifest_public_key = self.manifest_private_key_id.get_public() self.sql_store() return self - def issue_ee(self, ca, resources, subject_key, sia): + def issue_ee(self, ca, resources, subject_key, sia, + cn = None, sn = None, notAfter = None, eku = None): """ Issue a new EE certificate. """ + if notAfter is None: + notAfter = self.latest_ca_cert.getNotAfter() + return self.latest_ca_cert.issue( keypair = self.private_key_id, subject_key = subject_key, @@ -1103,8 +1150,11 @@ class ca_detail_obj(rpki.sql.sql_persistent): aia = self.ca_cert_uri, crldp = self.crl_uri, resources = resources, - notAfter = self.latest_ca_cert.getNotAfter(), - is_ca = False) + notAfter = notAfter, + is_ca = False, + cn = cn, + sn = sn, + eku = eku) def generate_manifest_cert(self): """ @@ -1200,8 +1250,12 @@ 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, uri = self.crl_uri, obj = self.latest_crl, repository = parent.repository, - handler = self.crl_published_callback) + publisher.publish( + cls = rpki.publication.crl_elt, + uri = self.crl_uri, + obj = self.latest_crl, + repository = parent.repository, + handler = self.crl_published_callback) def crl_published_callback(self, pdu): """ @@ -1238,6 +1292,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): objs.extend((c.uri_tail, c.cert) for c in self.child_certs) objs.extend((r.uri_tail, r.roa) for r in self.roas if r.roa is not None) objs.extend((g.uri_tail, g.ghostbuster) for g in self.ghostbusters) + objs.extend((e.uri_tail, e.cert) for e in self.ee_certificates) rpki.log.debug("Building manifest object %s" % uri) self.latest_manifest = rpki.x509.SignedManifest.build( @@ -1277,6 +1332,8 @@ class ca_detail_obj(rpki.sql.sql_persistent): roa.regenerate(publisher, fast = True) for ghostbuster in self.ghostbusters: ghostbuster.regenerate(publisher, fast = True) + for ee_certificate in self.ee_certificates: + ee_certificate.reissue(publisher, force = True) for child_cert in self.child_certs: child_cert.reissue(self, publisher, force = True) self.gctx.sql.sweep() @@ -1451,7 +1508,11 @@ class child_cert_obj(rpki.sql.sql_persistent): ca = ca_detail.ca rpki.log.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.withdraw( + cls = rpki.publication.certificate_elt, + uri = self.uri, + obj = self.cert, + repository = ca.parent.repository) self.gctx.sql.sweep() self.sql_delete() if generate_crl_and_manifest: @@ -1473,6 +1534,7 @@ class child_cert_obj(rpki.sql.sql_persistent): old_resources = self.cert.get_3779resources() old_sia = self.cert.get_SIA() + old_aia = self.cert.get_AIA()[0] old_ca_detail = self.ca_detail needed = False @@ -1490,7 +1552,8 @@ class child_cert_obj(rpki.sql.sql_persistent): needed = True if resources.valid_until != old_resources.valid_until: - rpki.log.debug("Validity changed for %r: old %s new %s" % (self, old_resources.valid_until, resources.valid_until)) + rpki.log.debug("Validity changed for %r: old %s new %s" % ( + self, old_resources.valid_until, resources.valid_until)) needed = True if sia != old_sia: @@ -1498,7 +1561,11 @@ class child_cert_obj(rpki.sql.sql_persistent): needed = True if ca_detail != old_ca_detail: - rpki.log.debug("Issuer changed for %r %s: old %r new %r" % (self, self.uri, old_ca_detail, ca_detail)) + rpki.log.debug("Issuer changed for %r: old %r new %r" % (self, old_ca_detail, ca_detail)) + needed = True + + if ca_detail.ca_cert_uri != old_aia: + rpki.log.debug("AIA changed for %r: old %r new %r" % (self, old_aia, ca_detail.ca_cert_uri)) needed = True must_revoke = old_resources.oversized(resources) or old_resources.valid_until > resources.valid_until @@ -1765,6 +1832,10 @@ class roa_obj(rpki.sql.sql_persistent): rpki.log.debug("%r resources do not match EE, regenerating" % self) return self.regenerate(publisher = publisher, fast = fast) + if self.cert.get_AIA()[0] != ca_detail.ca_cert_uri: + rpki.log.debug("%r AIA changed, regenerating" % self) + return self.regenerate(publisher = publisher, fast = fast) + def generate(self, publisher, fast = False): """ Generate a ROA. @@ -1829,7 +1900,7 @@ class roa_obj(rpki.sql.sql_persistent): self.cert = ca_detail.issue_ee( ca = ca, resources = resources, - subject_key = keypair.get_RSApublic(), + subject_key = keypair.get_public(), sia = (None, None, self.uri_from_key(keypair))) self.roa = rpki.x509.ROA.build(self.asn, self.ipv4, self.ipv6, keypair, (self.cert,)) self.published = rpki.sundial.now() @@ -2001,6 +2072,10 @@ class ghostbuster_obj(rpki.sql.sql_persistent): rpki.log.debug("%r past threshold %s, regenerating" % (self, regen_time)) return self.regenerate(publisher = publisher, fast = fast) + if self.cert.get_AIA()[0] != self.ca_detail.ca_cert_uri: + rpki.log.debug("%r AIA changed, regenerating" % self) + return self.regenerate(publisher = publisher, fast = fast) + def generate(self, publisher, fast = False): """ Generate a Ghostbuster record @@ -2024,7 +2099,7 @@ class ghostbuster_obj(rpki.sql.sql_persistent): self.cert = ca_detail.issue_ee( ca = ca, resources = resources, - subject_key = keypair.get_RSApublic(), + subject_key = keypair.get_public(), sia = (None, None, self.uri_from_key(keypair))) self.ghostbuster = rpki.x509.Ghostbuster.build(self.vcard, keypair, (self.cert,)) self.published = rpki.sundial.now() @@ -2121,6 +2196,247 @@ class ghostbuster_obj(rpki.sql.sql_persistent): return self.cert.gSKI() + ".gbr" +class ee_cert_obj(rpki.sql.sql_persistent): + """ + EE certificate (router certificate or generic). + """ + + sql_template = rpki.sql.template( + "ee_cert", + "ee_cert_id", + "self_id", + "ca_detail_id", + "ski", + ("cert", rpki.x509.X509), + ("published", rpki.sundial.datetime)) + + def __repr__(self): + return rpki.log.log_repr(self, self.cert.getSubject(), self.uri) + + def __init__(self, gctx = None, self_id = None, ca_detail_id = None, cert = None): + rpki.sql.sql_persistent.__init__(self) + self.gctx = gctx + self.self_id = self_id + self.ca_detail_id = ca_detail_id + self.cert = cert + self.ski = None if cert is None else cert.get_SKI() + self.published = None + if self_id or ca_detail_id or cert: + self.sql_mark_dirty() + + @property + @rpki.sql.cache_reference + def self(self): + """ + Fetch self object to which this ee_cert_obj links. + """ + return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id) + + @property + @rpki.sql.cache_reference + def ca_detail(self): + """ + 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 + def ca_detail(self): + try: + del self._ca_detail + except AttributeError: + pass + + @property + def gski(self): + """ + Calculate g(SKI), for ease of comparison with XML. + + 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 + def gski(self, val): + self.ski = base64.urlsafe_b64decode(s + ("=" * ((4 - len(s)) % 4))) + + @property + def uri(self): + """ + Return the publication URI for this ee_cert_obj. + """ + return self.ca_detail.ca.sia_uri + self.uri_tail + + @property + def uri_tail(self): + """ + Return the tail (filename portion) of the publication URI for this + ee_cert_obj. + """ + return self.cert.gSKI() + ".cer" + + @classmethod + def create(cls, ca_detail, subject_name, subject_key, resources, publisher, eku = None): + """ + Generate a new certificate and stuff it in a new ee_cert_obj. + """ + + cn, sn = subject_name.extract_cn_and_sn() + ca = ca_detail.ca + + cert = ca_detail.issue_ee( + ca = ca, + subject_key = subject_key, + sia = None, + resources = resources, + notAfter = resources.valid_until, + cn = cn, + sn = sn, + eku = eku) + + self = cls( + gctx = ca_detail.gctx, + self_id = ca.parent.self.self_id, + ca_detail_id = ca_detail.ca_detail_id, + cert = cert) + + publisher.publish( + cls = rpki.publication.certificate_elt, + uri = self.uri, + obj = self.cert, + repository = ca.parent.repository, + handler = self.published_callback) + + self.sql_store() + + ca_detail.generate_manifest(publisher = publisher) + + rpki.log.debug("New ee_cert %r" % self) + + return self + + def revoke(self, publisher, generate_crl_and_manifest = True): + """ + Revoke and withdraw an EE certificate. + """ + + ca_detail = self.ca_detail + ca = ca_detail.ca + rpki.log.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) + self.gctx.sql.sweep() + self.sql_delete() + if generate_crl_and_manifest: + ca_detail.generate_crl(publisher = publisher) + ca_detail.generate_manifest(publisher = publisher) + + def reissue(self, publisher, ca_detail = None, resources = None, force = False): + """ + Reissue an existing EE cert, reusing the public key. If the EE + cert we would generate is identical to the one we already have, we + just return; if we need to reissue, we reuse this ee_cert_obj and + just update its contents, as the publication URI will not have + changed. + """ + + needed = False + + old_cert = self.cert + + old_ca_detail = self.ca_detail + if ca_detail is None: + ca_detail = old_ca_detail + + assert ca_detail.ca is old_ca_detail.ca + + old_resources = old_cert.get_3779resources() + if resources is None: + resources = old_resources + + assert resources.valid_until is not None and old_resources.valid_until is not None + + assert ca_detail.covers(resources) + + if ca_detail != self.ca_detail: + rpki.log.debug("ca_detail changed for %r: old %r new %r" % ( + self, self.ca_detail, ca_detail)) + needed = True + + if ca_detail.ca_cert_uri != old_cert.get_AIA()[0]: + rpki.log.debug("AIA changed for %r: old %s new %s" % ( + self, old_cert.get_AIA()[0], ca_detail.ca_cert_uri)) + needed = True + + if resources.valid_until != old_resources.valid_until: + rpki.log.debug("Validity changed for %r: old %s new %s" % ( + self, old_resources.valid_until, resources.valid_until)) + needed = True + + if resources.asn != old_resources.asn or resources.v4 != old_resources.v4 or resources.v6 != old_resources.v6: + rpki.log.debug("Resources changed for %r: old %s new %s" % ( + self, old_resources, resources)) + needed = True + + must_revoke = (old_resources.oversized(resources) or + old_resources.valid_until > resources.valid_until) + if must_revoke: + rpki.log.debug("Must revoke existing cert(s) for %r" % self) + needed = True + + if not needed and force: + rpki.log.debug("No change needed for %r, forcing reissuance anyway" % self) + needed = True + + if not needed: + rpki.log.debug("No change to %r" % self) + return + + cn, sn = self.cert.getSubject().extract_cn_and_sn() + + self.cert = ca_detail.issue_ee( + ca = ca_detail.ca, + subject_key = self.cert.getPublicKey(), + eku = self.cert.get_EKU(), + sia = None, + resources = resources, + notAfter = resources.valid_until, + cn = cn, + sn = sn) + + self.sql_mark_dirty() + + publisher.publish( + cls = rpki.publication.certificate_elt, + uri = self.uri, + obj = self.cert, + repository = ca_detail.ca.parent.repository, + handler = self.published_callback) + + if must_revoke: + revoked_cert_obj.revoke(cert = old_cert.cert, ca_detail = old_ca_detail) + + self.gctx.sql.sweep() + + if must_revoke: + ca_detail.generate_crl(publisher = publisher) + self.gctx.sql.sweep() + + ca_detail.generate_manifest(publisher = publisher) + + def published_callback(self, pdu): + """ + Publication callback: check result and mark published. + """ + pdu.raise_if_error() + self.published = None + self.sql_mark_dirty() + + class publication_queue(object): """ Utility to simplify publication from within rpkid. diff --git a/rpkid/rpki/rpkid_tasks.py b/rpkid/rpki/rpkid_tasks.py index a1657d97..04e1c0df 100644 --- a/rpkid/rpki/rpkid_tasks.py +++ b/rpkid/rpki/rpkid_tasks.py @@ -1,17 +1,19 @@ # $Id$ # -# Copyright (C) 2012-2013 Internet Systems Consortium ("ISC") +# Copyright (C) 2014 Dragon Research Labs ("DRL") +# Portions copyright (C) 2012--2013 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. +# 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 +# THE SOFTWARE IS PROVIDED "AS IS" AND DRL AND ISC DISCLAIM ALL +# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL OR +# 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. """ @@ -27,6 +29,18 @@ import rpki.sundial import rpki.publication import rpki.exceptions +task_classes = () + +def queue_task(cls): + """ + Class decorator to add a new task class to task_classes. + """ + + global task_classes + task_classes += (cls,) + return cls + + class CompletionHandler(object): """ Track one or more scheduled rpkid tasks and execute a callback when @@ -136,6 +150,7 @@ class AbstractTask(object): pass +@queue_task class PollParentTask(AbstractTask): """ Run the regular client poll cycle with each of this self's @@ -203,6 +218,7 @@ class PollParentTask(AbstractTask): self.parent_iterator() +@queue_task class UpdateChildrenTask(AbstractTask): """ Check for updated IRDB data for all of this self's children and @@ -258,6 +274,8 @@ class UpdateChildrenTask(AbstractTask): if ca_detail.state == "active": old_resources = child_cert.cert.get_3779resources() new_resources = old_resources & irdb_resources & ca_detail.latest_ca_cert.get_3779resources() + old_aia = child_cert.cert.get_AIA()[0] + new_aia = ca_detail.ca_cert_uri if new_resources.empty(): rpki.log.debug("Resources shrank to the null set, " @@ -267,9 +285,11 @@ class UpdateChildrenTask(AbstractTask): ca_detail.generate_crl(publisher = self.publisher) ca_detail.generate_manifest(publisher = self.publisher) - elif old_resources != new_resources or (old_resources.valid_until < self.rsn and - irdb_resources.valid_until > self.now and - old_resources.valid_until != irdb_resources.valid_until): + elif (old_resources != new_resources or + old_aia != new_aia or + (old_resources.valid_until < self.rsn and + irdb_resources.valid_until > self.now and + old_resources.valid_until != irdb_resources.valid_until)): rpki.log.debug("Need to reissue child %s certificate SKI %s" % ( self.child.child_handle, child_cert.cert.gSKI())) @@ -321,6 +341,7 @@ class UpdateChildrenTask(AbstractTask): self.exit() +@queue_task class UpdateROAsTask(AbstractTask): """ Generate or update ROAs for this self. @@ -450,6 +471,7 @@ class UpdateROAsTask(AbstractTask): self.exit() +@queue_task class UpdateGhostbustersTask(AbstractTask): """ Generate or update Ghostbuster records for this self. @@ -547,6 +569,112 @@ class UpdateGhostbustersTask(AbstractTask): rpki.log.warn("Could not fetch Ghostbuster record requests for %s, skipping: %s" % (self.self_handle, e)) self.exit() + +@queue_task +class UpdateEECertificatesTask(AbstractTask): + """ + Generate or update EE certificates for this self. + + Not yet sure what kind of scaling constraints this task might have, + so keeping it simple for initial version, we can optimize later. + """ + + def start(self): + rpki.log.trace() + self.gctx.checkpoint() + rpki.log.debug("Self %s[%d] updating EE certificates" % (self.self_handle, self.self_id)) + + self.gctx.irdb_query_ee_certificate_requests(self.self_handle, + self.got_requests, + self.get_requests_failed) + + def got_requests(self, requests): + + try: + self.gctx.checkpoint() + if self.gctx.sql.dirty: + rpki.log.warn("Unexpected dirty SQL cache, flushing") + self.gctx.sql.sweep() + + publisher = rpki.rpkid.publication_queue() + + existing = dict() + for ee in self.ee_certificates: + gski = ee.gski + if gski not in existing: + existing[gski] = set() + existing[gski].add(ee) + + ca_details = set() + + for req in requests: + ees = existing.pop(req.gski, ()) + resources = rpki.resource_set.resource_bag( + asn = req.asn, + v4 = req.ipv4, + v6 = req.ipv6, + valid_until = req.valid_until) + covering = self.find_covering_ca_details(resources) + ca_details.update(covering) + + for ee in ees: + if ee.ca_detail in covering: + rpki.log.debug("Updating existing EE certificate for %s %s" % (req.gski, resources)) + ee.reissue( + resources = resources, + publisher = publisher) + covering.remove(ee.ca_detail) + else: + rpki.log.debug("Existing EE certificate for %s %s is no longer covered" % (req.gski, resources)) + ee.revoke(publisher = publisher) + + for ca_detail in covering: + rpki.log.debug("No existing EE certificate for %s %s" % (req.gski, resources)) + rpki.rpkid.ee_cert_obj.create( + ca_detail = ca_detail, + subject_name = rpki.x509.X501DN.from_cn(req.cn, req.sn), + subject_key = req.pkcs10.getPublicKey(), + resources = resources, + publisher = publisher, + eku = req.eku or None) + + # Anything left is an orphan + for ees in existing.values(): + for ee in ees: + ca_details.add(ee.ca_detail) + ee.revoke(publisher = publisher) + + self.gctx.sql.sweep() + + for ca_detail in ca_details: + ca_detail.generate_crl(publisher = publisher) + ca_detail.generate_manifest(publisher = publisher) + + self.gctx.sql.sweep() + + self.gctx.checkpoint() + publisher.call_pubd(self.exit, self.publication_failed) + + except (SystemExit, rpki.async.ExitNow): + raise + except Exception, e: + rpki.log.traceback() + rpki.log.warn("Could not update EE certificates for %s, skipping: %s" % (self.self_handle, e)) + self.exit() + + def publication_failed(self, e): + rpki.log.traceback() + rpki.log.warn("Couldn't publish EE certificate updates for %s, skipping: %s" % (self.self_handle, e)) + self.gctx.checkpoint() + self.exit() + + def get_requests_failed(self, e): + rpki.log.traceback() + rpki.log.warn("Could not fetch EE certificate requests for %s, skipping: %s" % (self.self_handle, e)) + self.exit() + + +@queue_task class RegenerateCRLsAndManifestsTask(AbstractTask): """ Generate new CRLs and manifests as necessary for all of this self's @@ -595,6 +723,8 @@ class RegenerateCRLsAndManifestsTask(AbstractTask): self.gctx.checkpoint() self.exit() + +@queue_task class CheckFailedPublication(AbstractTask): """ Periodic check for objects we tried to publish but failed (eg, due diff --git a/rpkid/rpki/sql_schemas.py b/rpkid/rpki/sql_schemas.py index e7c65299..9a82f0ef 100644 --- a/rpkid/rpki/sql_schemas.py +++ b/rpkid/rpki/sql_schemas.py @@ -37,6 +37,7 @@ rpkid = '''-- $Id: rpkid.sql 3745 2011-03-27 00:21:57Z sra $ -- DROP TABLE commands must be in correct (reverse dependency) order -- to satisfy FOREIGN KEY constraints. +DROP TABLE IF EXISTS ee_cert; DROP TABLE IF EXISTS ghostbuster; DROP TABLE IF EXISTS roa_prefix; DROP TABLE IF EXISTS roa; @@ -234,6 +235,20 @@ CREATE TABLE ghostbuster ( FOREIGN KEY (ca_detail_id) REFERENCES ca_detail (ca_detail_id) ON DELETE CASCADE ) ENGINE=InnoDB; +CREATE TABLE ee_cert ( + ee_cert_id SERIAL NOT NULL, + ski BINARY(20) NOT NULL, + cert LONGBLOB NOT NULL, + published DATETIME, + self_id BIGINT UNSIGNED NOT NULL, + ca_detail_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (ee_cert_id), + CONSTRAINT ee_cert_self_id + FOREIGN KEY (self_id) REFERENCES self (self_id) ON DELETE CASCADE, + CONSTRAINT ee_cert_ca_detail_id + FOREIGN KEY (ca_detail_id) REFERENCES ca_detail (ca_detail_id) ON DELETE CASCADE +) ENGINE=InnoDB; + -- Local Variables: -- indent-tabs-mode: nil -- End: diff --git a/rpkid/rpki/up_down.py b/rpkid/rpki/up_down.py index 6d1fa45a..d2ad85d3 100644 --- a/rpkid/rpki/up_down.py +++ b/rpkid/rpki/up_down.py @@ -365,7 +365,7 @@ class issue_pdu(base_elt): raise rpki.exceptions.NotImplementedYet("req_* attributes not implemented yet, sorry") # Check the request - self.pkcs10.check_valid_rpki() + self.pkcs10.check_valid_request_ca() ca = child.ca_from_class_name(self.class_name) ca_detail = ca.active_ca_detail if ca_detail is None: diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py index 5475a452..fb1a5a2b 100644 --- a/rpkid/rpki/x509.py +++ b/rpkid/rpki/x509.py @@ -78,20 +78,6 @@ def first_rsync_uri(xia): return uri return None -def _find_xia_uri(extension, name): - """ - Find a rsync URI in an SIA or AIA extension. - Returns the URI if found, otherwise None. - """ - oid = rpki.oids.name2oid[name] - - # extension may be None if the AIA is not present - if extension: - for method, location in extension: - if method == oid and location[0] == "uri" and location[1].startswith("rsync://"): - return location[1] - return None - class X501DN(object): """ Class to hold an X.501 Distinguished Name. @@ -126,7 +112,7 @@ class X501DN(object): """ def __str__(self): - return "".join("/" + "+".join("%s=%s" % (rpki.oids.safe_dotted2name(a[0]), a[1]) + return "".join("/" + "+".join("%s=%s" % (rpki.oids.oid2name(a[0]), a[1]) for a in rdn) for rdn in self.dn) @@ -145,10 +131,18 @@ class X501DN(object): rpki.log.debug("++ %r %r" % (self, self.dn)) @classmethod - def from_cn(cls, s): - assert isinstance(s, (str, unicode)) + def from_cn(cls, cn, sn = None): + assert isinstance(cn, (str, unicode)) + if isinstance(sn, (int, long)): + sn = "%08X" % sn + elif isinstance(sn, (str, unicode)): + assert all(c in "0123456789abcdefABCDEF" for c in sn) + sn = str(sn) self = cls() - self.dn = (((rpki.oids.safe_name2dotted("commonName"), s),),) + if sn is not None: + self.dn = (((rpki.oids.commonName, cn),), ((rpki.oids.serialNumber, sn),)) + else: + self.dn = (((rpki.oids.commonName, cn),),) return self @classmethod @@ -161,6 +155,28 @@ class X501DN(object): def get_POW(self): return self.dn + def extract_cn_and_sn(self): + cn = None + sn = None + + for rdn in self.dn: + if len(rdn) == 1 and len(rdn[0]) == 2: + oid = rdn[0][0] + val = rdn[0][1] + if oid == rpki.oids.commonName and cn is None: + cn = val + continue + if oid == rpki.oids.serialNumber and sn is None: + sn = val + continue + raise rpki.exceptions.BadX510DN("Bad subject name: %s" % (self.dn,)) + + if cn is None: + raise rpki.exceptions.BadX510DN("Subject name is missing CN: %s" % (self.dn,)) + + return cn, sn + + class DER_object(object): """ Virtual class to hold a generic DER object. @@ -300,14 +316,26 @@ class DER_object(object): def get_DER(self): """ Get the DER value of this object. - - Subclasses will almost certainly override this method. + Subclasses may need to override this method. """ self.check() if self.DER: return self.DER + if self.POW: + self.DER = self.POW.derWrite() + return self.get_DER() raise rpki.exceptions.DERObjectConversionError("No conversion path to DER available") + def get_POW(self): + """ + Get the 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()) + return self.POW + def get_Base64(self): """ Get the Base64 encoding of the DER value of this object. @@ -367,18 +395,22 @@ class DER_object(object): def get_AKI(self): """ - Get the AKI extension from this object. Only works for subclasses - that support getExtension(). + 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. Only works for subclasses - that support getExtension(). + 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): """ Get the SIA extension from this object. Only works for subclasses @@ -524,27 +556,6 @@ class X509(DER_object): POW_class = rpki.POW.X509 - def get_DER(self): - """ - Get the DER value of this certificate. - """ - self.check() - if self.DER: - return self.DER - if self.POW: - self.DER = self.POW.derWrite() - return self.get_DER() - raise rpki.exceptions.DERObjectConversionError("No conversion path to DER available") - - def get_POW(self): - """ - Get the rpki.POW value of this certificate. - """ - self.check() - if not self.POW: # pylint: disable=E0203 - self.POW = rpki.POW.X509.derRead(self.get_DER()) - return self.POW - def getIssuer(self): """ Get the issuer of this certificate. @@ -579,7 +590,7 @@ class X509(DER_object): """ Extract the public key from this certificate. """ - return RSApublic(POW = self.get_POW().getPublicKey()) + return PublicKey(POW = self.get_POW().getPublicKey()) def get_SKI(self): """ @@ -594,13 +605,16 @@ class X509(DER_object): return self.getNotAfter() <= rpki.sundial.now() def issue(self, keypair, subject_key, serial, sia, aia, crldp, notAfter, - cn = None, resources = None, is_ca = True, notBefore = None): + cn = None, resources = None, is_ca = True, notBefore = None, + sn = None, eku = None): """ Issue an RPKI certificate. """ assert aia is not None and crldp is not None + assert eku is None or not is_ca + return self._issue( keypair = keypair, subject_key = subject_key, @@ -611,15 +625,18 @@ class X509(DER_object): notBefore = notBefore, notAfter = notAfter, cn = cn, + sn = sn, resources = resources, is_ca = is_ca, aki = self.get_SKI(), - issuer_name = self.getSubject()) + issuer_name = self.getSubject(), + eku = eku) @classmethod def self_certify(cls, keypair, subject_key, serial, sia, notAfter, - cn = None, resources = None, notBefore = None): + cn = None, resources = None, notBefore = None, + sn = None): """ Generate a self-certified RPKI certificate. """ @@ -639,15 +656,17 @@ class X509(DER_object): notBefore = notBefore, notAfter = notAfter, cn = cn, + sn = sn, resources = resources, is_ca = True, aki = ski, - issuer_name = X501DN.from_cn(cn)) + issuer_name = X501DN.from_cn(cn, sn), + eku = None) @classmethod def _issue(cls, keypair, subject_key, serial, sia, aia, crldp, notAfter, - cn, resources, is_ca, aki, issuer_name, notBefore): + cn, sn, resources, is_ca, aki, issuer_name, notBefore, eku): """ Common code to issue an RPKI certificate. """ @@ -673,13 +692,13 @@ class X509(DER_object): cert.setVersion(2) cert.setSerial(serial) cert.setIssuer(issuer_name.get_POW()) - cert.setSubject(X501DN.from_cn(cn).get_POW()) + cert.setSubject(X501DN.from_cn(cn, sn).get_POW()) cert.setNotBefore(notBefore) cert.setNotAfter(notAfter) cert.setPublicKey(subject_key.get_POW()) cert.setSKI(ski) cert.setAKI(aki) - cert.setCertificatePolicies((POWify_OID("id-cp-ipAddr-asNumber"),)) + cert.setCertificatePolicies((rpki.oids.id_cp_ipAddr_asNumber,)) if crldp is not None: cert.setCRLDP((crldp,)) @@ -712,6 +731,10 @@ class X509(DER_object): ipv6 = ("inherit" if resources.v6.inherit else ((r.min, r.max) for r in resources.v6))) + if eku is not None: + assert not is_ca + cert.setEKU(eku) + cert.sign(keypair.get_POW(), rpki.POW.SHA256_DIGEST) return cls(POW = cert) @@ -741,7 +764,7 @@ class X509(DER_object): keypair = keypair, issuer_name = subject_name, subject_name = subject_name, - subject_key = keypair.get_RSApublic(), + subject_key = keypair.get_public(), serial = serial, now = now, notAfter = notAfter, @@ -753,7 +776,7 @@ class X509(DER_object): """ Issue a normal BPKI certificate. """ - assert keypair.get_RSApublic() == self.getPublicKey() + assert keypair.get_public() == self.getPublicKey() return self._bpki_certify( keypair = keypair, issuer_name = self.getSubject(), @@ -777,7 +800,7 @@ class X509(DER_object): if now is None: now = rpki.sundial.now() - issuer_key = keypair.get_RSApublic() + issuer_key = keypair.get_public() assert (issuer_key == subject_key) == (issuer_name == subject_name) assert is_ca or issuer_name != subject_name @@ -837,10 +860,11 @@ class PKCS10(DER_object): ## @var allowed_extensions # Extensions allowed by RPKI profile. - allowed_extensions = frozenset(rpki.oids.safe_name2dotted(name) - for name in ("basicConstraints", - "keyUsage", - "subjectInfoAccess")) + allowed_extensions = frozenset((rpki.oids.basicConstraints, + rpki.oids.keyUsage, + rpki.oids.subjectInfoAccess, + rpki.oids.extendedKeyUsage)) + def get_DER(self): """ @@ -873,91 +897,189 @@ class PKCS10(DER_object): """ Extract the public key from this certification request. """ - return RSApublic(POW = self.get_POW().getPublicKey()) + 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() - def check_valid_rpki(self): + + def check_valid_request_common(self): """ - Check this certification request to see whether it's a valid - request for an RPKI certificate. This is broken out of the - up-down protocol code because it's somewhat involved and the - up-down code doesn't need to know the details. + Common code for checking this certification requests to see + whether they conform to the RPKI certificate profile. Throws an exception if the request isn't valid, so if this method returns at all, the request is ok. - At the moment, this only allows requests for CA certificates; as a - direct consequence, it also rejects ExtendedKeyUsage, because the - RPKI profile only allows EKU for EE certificates. + You probably don't want to call this directly, as it only performs + the checks that are common to all RPKI certificates. """ if not self.get_POW().verify(): - raise rpki.exceptions.BadPKCS10("Signature check failed") + raise rpki.exceptions.BadPKCS10("PKCS #10 signature check failed") ver = self.get_POW().getVersion() if ver != 0: - raise rpki.exceptions.BadPKCS10("Bad version number %s" % ver) + raise rpki.exceptions.BadPKCS10("PKCS #10 request has bad version number %s" % ver) - alg = rpki.oids.safe_dotted2name(self.get_POW().getSignatureAlgorithm()) + ku = self.get_POW().getKeyUsage() - if alg != "sha256WithRSAEncryption": - raise rpki.exceptions.BadPKCS10("Bad signature algorithm %s" % alg) + if ku is not None and self.expected_ca_keyUsage != ku: + raise rpki.exceptions.BadPKCS10("PKCS #10 keyUsage doesn't match profile: %r" % ku) - bc = self.get_POW().getBasicConstraints() - - if bc is None or not bc[0]: - raise rpki.exceptions.BadPKCS10("Request for EE certificate not allowed here") + forbidden_extensions = self.get_POW().getExtensionOIDs() - self.allowed_extensions - if bc[1] is not None: - raise rpki.exceptions.BadPKCS10("basicConstraints must not specify Path Length") + if forbidden_extensions: + raise rpki.exceptions.BadExtension("Forbidden extension%s in PKCS #10 certificate request: %s" % ( + "" if len(forbidden_extensions) == 1 else "s", + ", ".join(forbidden_extensions))) - ku = self.get_POW().getKeyUsage() - if ku is not None and self.expected_ca_keyUsage != ku: - raise rpki.exceptions.BadPKCS10("keyUsage doesn't match basicConstraints: %r" % ku) + def check_valid_request_ca(self): + """ + Check this certification request to see whether it's a valid + request for an RPKI CA certificate. + + Throws an exception if the request isn't valid, so if this method + returns at all, the request is ok. + """ - if any(oid not in self.allowed_extensions - for oid in self.get_POW().getExtensionOIDs()): - raise rpki.exceptions.BadExtension("Forbidden extension(s) in certificate request") + self.check_valid_request_common() + alg = self.get_POW().getSignatureAlgorithm() + bc = self.get_POW().getBasicConstraints() + eku = self.get_POW().getEKU() sias = self.get_POW().getSIA() + if alg != rpki.oids.sha256WithRSAEncryption: + raise rpki.exceptions.BadPKCS10("PKCS #10 has bad signature algorithm for CA: %s" % alg) + + if bc is None or not bc[0] or bc[1] is not None: + raise rpki.exceptions.BadPKCS10("PKCS #10 CA bad basicConstraints") + + if eku is not None: + raise rpki.exceptions.BadPKCS10("PKCS #10 CA EKU not allowed") + if sias is None: - raise rpki.exceptions.BadPKCS10("Certificate request is missing SIA extension") + raise rpki.exceptions.BadPKCS10("PKCS #10 CA SIA missing") caRepository, rpkiManifest, signedObject = sias if signedObject: - raise rpki.exceptions.BadPKCS10("CA certificate request has SIA id-ad-signedObject") + raise rpki.exceptions.BadPKCS10("PKCS #10 CA SIA must not have id-ad-signedObject") if not caRepository: - raise rpki.exceptions.BadPKCS10("Certificate request is missing SIA id-ad-caRepository") + raise rpki.exceptions.BadPKCS10("PKCS #10 CA SIA must have id-ad-caRepository") if not any(uri.startswith("rsync://") for uri in caRepository): - raise rpki.exceptions.BadPKCS10("Certificate request SIA id-ad-caRepository contains no rsync URIs") + raise rpki.exceptions.BadPKCS10("PKCS #10 CA SIA id-ad-caRepository contains no rsync URIs") + + if any(uri.startswith("rsync://") and not uri.endswith("/") for uri in caRepository): + raise rpki.exceptions.BadPKCS10("PKCS #10 CA SIA id-ad-caRepository does not end with slash") if not rpkiManifest: - raise rpki.exceptions.BadPKCS10("Certificate request is missing SIA id-ad-rpkiManifest") - - if not any(uri.startswith("rsync://") for uri in rpkiManifest): - raise rpki.exceptions.BadPKCS10("Certificate request SIA id-ad-rpkiManifest contains no rsync URIs") + raise rpki.exceptions.BadPKCS10("PKCS #10 CA SIA must have id-ad-rpkiManifest") - if any(uri.startswith("rsync://") and not uri.endswith("/") for uri in caRepository): - raise rpki.exceptions.BadPKCS10("Certificate request SIA id-ad-caRepository does not end with slash") + if not any(uri.startswith("rsync://") for uri in rpkiManifest): + raise rpki.exceptions.BadPKCS10("PKCS #10 CA SIA id-ad-rpkiManifest contains no rsync URIs") if any(uri.startswith("rsync://") and uri.endswith("/") for uri in rpkiManifest): - raise rpki.exceptions.BadPKCS10("Certificate request SIA id-ad-rpkiManifest ends with slash") + raise rpki.exceptions.BadPKCS10("PKCS #10 CA SIA id-ad-rpkiManifest ends with slash") + + + def check_valid_request_ee(self): + """ + Check this certification request to see whether it's a valid + request for an RPKI EE certificate. + + Throws an exception if the request isn't valid, so if this method + returns at all, the request is ok. + + We're a bit less strict here than we are for either CA + certificates or BGPSEC router certificates, because the profile is + less tightly nailed down for unspecified-use RPKI EE certificates. + Future specific purposes may impose tighter constraints. + + Note that this method does NOT apply to so-called "infrastructure" + EE certificates (eg, the EE certificates embedded in manifests and + ROAs); those are constrained fairly tightly, but they're also + generated internally so we don't need to check them as user or + protocol input. + """ + + self.check_valid_request_common() + + alg = self.get_POW().getSignatureAlgorithm() + bc = self.get_POW().getBasicConstraints() + sia = self.get_POW().getSIA() + + caRepository, rpkiManifest, signedObject = sia or (None, None, None) + + if alg not in (rpki.oids.sha256WithRSAEncryption, rpki.oids.ecdsa_with_SHA256): + raise rpki.exceptions.BadPKCS10("PKCS #10 has bad signature algorithm for EE: %s" % alg) + + if bc is not None and (bc[0] or bc[1] is not None): + raise rpki.exceptions.BadPKCS10("PKCS #10 EE has bad basicConstraints") + + if caRepository: + raise rpki.exceptions.BadPKCS10("PKCS #10 EE must not have id-ad-caRepository") + + if rpkiManifest: + raise rpki.exceptions.BadPKCS10("PKCS #10 EE must not have id-ad-rpkiManifest") + + if signedObject and not any(uri.startswith("rsync://") for uri in signedObject): + raise rpki.exceptions.BadPKCS10("PKCS #10 EE SIA id-ad-signedObject contains no rsync URIs") + + + def check_valid_request_router(self): + """ + Check this certification request to see whether it's a valid + request for a BGPSEC router certificate. + + Throws an exception if the request isn't valid, so if this method + returns at all, the request is ok. + + draft-ietf-sidr-bgpsec-pki-profiles 3.2 says follow RFC 6487 3 + except where explicitly overriden, and does not override for SIA. + But draft-ietf-sidr-bgpsec-pki-profiles also says that router + certificates don't get SIA, while RFC 6487 requires SIA. So what + do we do with SIA in PKCS #10 for router certificates? + + For the moment, ignore it, but make sure we don't include it in + the certificate when we get to the code that generates that. + """ + + self.check_valid_request_ee() + + alg = self.get_POW().getSignatureAlgorithm() + eku = self.get_POW().getEKU() + + if alg != rpki.oids.ecdsa_with_SHA256: + raise rpki.exceptions.BadPKCS10("PKCS #10 has bad signature algorithm for router: %s" % alg) + + # Not really clear to me whether PKCS #10 should have EKU or not, so allow + # either, but insist that it be the right one if present. + + if eku is not None and rpki.oids.id_kp_bgpsec_router not in eku: + raise rpki.exceptions.BadPKCS10("PKCS #10 router must have EKU") + @classmethod def create(cls, keypair, exts = None, is_ca = False, - caRepository = None, rpkiManifest = None, signedObject = None): + caRepository = None, rpkiManifest = None, signedObject = None, + cn = None, sn = None, eku = None): """ Create a new request for a given keypair. """ assert exts is None, "Old calling sequence to rpki.x509.PKCS10.create()" - cn = "".join(("%02X" % ord(i) for i in keypair.get_SKI())) + if cn is None: + cn = "".join(("%02X" % ord(i) for i in keypair.get_SKI())) if isinstance(caRepository, str): caRepository = (caRepository,) @@ -970,7 +1092,7 @@ class PKCS10(DER_object): req = rpki.POW.PKCS10() req.setVersion(0) - req.setSubject(X501DN.from_cn(cn).get_POW()) + req.setSubject(X501DN.from_cn(cn, sn).get_POW()) req.setPublicKey(keypair.get_POW()) if is_ca: @@ -980,6 +1102,9 @@ class PKCS10(DER_object): if caRepository or rpkiManifest or signedObject: req.setSIA(caRepository, rpkiManifest, signedObject) + if eku: + req.setEKU(eku) + req.sign(keypair.get_POW(), rpki.POW.SHA256_DIGEST) return cls(POW = req) @@ -1014,9 +1139,10 @@ class insecure_debug_only_rsa_key_generator(object): self.keyno += 1 return v -class RSA(DER_object): + +class PrivateKey(DER_object): """ - Class to hold an RSA key pair. + Class to hold a Public/Private key pair. """ POW_class = rpki.POW.Asymmetric @@ -1055,18 +1181,6 @@ class RSA(DER_object): assert self.empty() self.POW = self.POW_class.pemReadPrivate(pem) - @classmethod - def generate(cls, keylength = 2048, quiet = False): - """ - Generate a new keypair. - """ - if not quiet: - rpki.log.debug("Generating new %d-bit RSA key" % keylength) - if generate_insecure_debug_only_rsa_key is not None: - return cls(POW = generate_insecure_debug_only_rsa_key()) - else: - return cls(POW = rpki.POW.Asymmetric.generateRSA(keylength)) - def get_public_DER(self): """ Get the DER encoding of the public key from this keypair. @@ -1079,15 +1193,15 @@ class RSA(DER_object): """ return self.get_POW().calculateSKI() - def get_RSApublic(self): + def get_public(self): """ - Convert the public key of this keypair into a RSApublic object. + Convert the public key of this keypair into a PublicKey object. """ - return RSApublic(DER = self.get_public_DER()) + return PublicKey(DER = self.get_public_DER()) -class RSApublic(DER_object): +class PublicKey(DER_object): """ - Class to hold an RSA public key. + Class to hold a public key. """ POW_class = rpki.POW.Asymmetric @@ -1132,22 +1246,63 @@ class RSApublic(DER_object): """ return self.get_POW().calculateSKI() -def POWify_OID(oid): +class KeyParams(DER_object): + """ + Wrapper for OpenSSL's asymmetric key parameter classes. + """ + + POW_class = rpki.POW.AsymmetricParams + + @classmethod + def generateEC(cls, curve = rpki.POW.EC_P256_CURVE): + return cls(POW = rpki.POW.AsymmetricParams.generateEC(curve = curve)) + +class RSA(PrivateKey): """ - Utility function to convert tuple form of an OID to the - dotted-decimal string form that rpki.POW uses. + Class to hold an RSA key pair. """ - if isinstance(oid, str): - return POWify_OID(rpki.oids.name2oid[oid]) - else: - return ".".join(str(i) for i in oid) + + @classmethod + def generate(cls, keylength = 2048, quiet = False): + """ + Generate a new keypair. + """ + if not quiet: + rpki.log.debug("Generating new %d-bit RSA key" % keylength) + if generate_insecure_debug_only_rsa_key is not None: + return cls(POW = generate_insecure_debug_only_rsa_key()) + else: + return cls(POW = rpki.POW.Asymmetric.generateRSA(keylength)) + +class ECDSA(PrivateKey): + """ + Class to hold an ECDSA key pair. + """ + + @classmethod + def generate(cls, params = None, quiet = False): + """ + Generate a new keypair. + """ + + if params is None: + if not quiet: + rpki.log.debug("Generating new ECDSA key parameters") + params = KeyParams.generateEC() + + assert isinstance(params, KeyParams) + + if not quiet: + rpki.log.debug("Generating new ECDSA key") + + return cls(POW = rpki.POW.Asymmetric.generateFromParams(params.get_POW())) class CMS_object(DER_object): """ Abstract class to hold a CMS object. """ - econtent_oid = POWify_OID("id-data") + econtent_oid = rpki.oids.id_data POW_class = rpki.POW.CMS ## @var dump_on_verify_failure @@ -1492,7 +1647,7 @@ class SignedManifest(DER_CMS_object): Class to hold a signed manifest. """ - econtent_oid = POWify_OID("id-ct-rpkiManifest") + econtent_oid = rpki.oids.id_ct_rpkiManifest POW_class = rpki.POW.Manifest def getThisUpdate(self): @@ -1525,7 +1680,7 @@ class SignedManifest(DER_CMS_object): obj.setManifestNumber(serial) obj.setThisUpdate(thisUpdate) obj.setNextUpdate(nextUpdate) - obj.setAlgorithm(POWify_OID(rpki.oids.name2oid["id-sha256"])) + obj.setAlgorithm(rpki.oids.id_sha256) obj.addFiles(filelist) self = cls(POW = obj) @@ -1537,7 +1692,7 @@ class ROA(DER_CMS_object): Class to hold a signed ROA. """ - econtent_oid = POWify_OID("id-ct-routeOriginAttestation") + econtent_oid = rpki.oids.id_ct_routeOriginAttestation POW_class = rpki.POW.ROA @classmethod @@ -1614,7 +1769,7 @@ class XML_CMS_object(Wrapped_CMS_object): Class to hold CMS-wrapped XML protocol data. """ - econtent_oid = POWify_OID("id-ct-xml") + econtent_oid = rpki.oids.id_ct_xml ## @var dump_outbound_cms # If set, we write all outbound XML-CMS PDUs to disk, for debugging. @@ -1755,7 +1910,7 @@ class Ghostbuster(Wrapped_CMS_object): managed by the back-end. """ - econtent_oid = POWify_OID("id-ct-rpkiGhostbusters") + econtent_oid = rpki.oids.id_ct_rpkiGhostbusters def encode(self): """ diff --git a/rpkid/rpkid.sql b/rpkid/rpkid.sql index 39603124..a7e3dc0a 100644 --- a/rpkid/rpkid.sql +++ b/rpkid/rpkid.sql @@ -33,6 +33,7 @@ -- DROP TABLE commands must be in correct (reverse dependency) order -- to satisfy FOREIGN KEY constraints. +DROP TABLE IF EXISTS ee_cert; DROP TABLE IF EXISTS ghostbuster; DROP TABLE IF EXISTS roa_prefix; DROP TABLE IF EXISTS roa; @@ -230,6 +231,20 @@ CREATE TABLE ghostbuster ( FOREIGN KEY (ca_detail_id) REFERENCES ca_detail (ca_detail_id) ON DELETE CASCADE ) ENGINE=InnoDB; +CREATE TABLE ee_cert ( + ee_cert_id SERIAL NOT NULL, + ski BINARY(20) NOT NULL, + cert LONGBLOB NOT NULL, + published DATETIME, + self_id BIGINT UNSIGNED NOT NULL, + ca_detail_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (ee_cert_id), + CONSTRAINT ee_cert_self_id + FOREIGN KEY (self_id) REFERENCES self (self_id) ON DELETE CASCADE, + CONSTRAINT ee_cert_ca_detail_id + FOREIGN KEY (ca_detail_id) REFERENCES ca_detail (ca_detail_id) ON DELETE CASCADE +) ENGINE=InnoDB; + -- Local Variables: -- indent-tabs-mode: nil -- End: diff --git a/rpkid/setup.py b/rpkid/setup.py index 39aad552..653d2d31 100644 --- a/rpkid/setup.py +++ b/rpkid/setup.py @@ -88,4 +88,6 @@ setup(name = "rpkitoolkit", (autoconf.datarootdir + "/rpki/media/js", glob("rpki/gui/app/static/js/*")), (autoconf.datarootdir + "/rpki/media/img", - glob("rpki/gui/app/static/img/*"))]) + glob("rpki/gui/app/static/img/*")), + (autoconf.datarootdir + "/rpki/upgrade-scripts", + glob("upgrade-scripts/*"))]) diff --git a/rpkid/tests/old_irdbd.sql b/rpkid/tests/old_irdbd.sql index bf324cd8..e773bb2e 100644 --- a/rpkid/tests/old_irdbd.sql +++ b/rpkid/tests/old_irdbd.sql @@ -42,6 +42,9 @@ DROP TABLE IF EXISTS registrant_net; DROP TABLE IF EXISTS registrant_asn; DROP TABLE IF EXISTS registrant; DROP TABLE IF EXISTS ghostbuster_request; +DROP TABLE IF EXISTS ee_certificate_asn; +DROP TABLE IF EXISTS ee_certificate_net; +DROP TABLE IF EXISTS ee_certificate; CREATE TABLE registrant ( registrant_id SERIAL NOT NULL, @@ -54,29 +57,29 @@ CREATE TABLE registrant ( ) ENGINE=InnoDB; CREATE TABLE registrant_asn ( - registrant_asn_id SERIAL NOT NULL, start_as BIGINT UNSIGNED NOT NULL, end_as BIGINT UNSIGNED NOT NULL, registrant_id BIGINT UNSIGNED NOT NULL, - PRIMARY KEY (registrant_asn_id), + PRIMARY KEY (registrant_id, start_as, end_as), CONSTRAINT registrant_asn_registrant_id - FOREIGN KEY (registrant_id) REFERENCES registrant (registrant_id) ON DELETE CASCADE + FOREIGN KEY (registrant_id) REFERENCES registrant (registrant_id) + ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB; CREATE TABLE registrant_net ( - registrant_net_id SERIAL NOT NULL, start_ip VARCHAR(40) NOT NULL, end_ip VARCHAR(40) NOT NULL, version TINYINT UNSIGNED NOT NULL, registrant_id BIGINT UNSIGNED NOT NULL, - PRIMARY KEY (registrant_net_id), + PRIMARY KEY (registrant_id, version, start_ip, end_ip), CONSTRAINT registrant_net_registrant_id - FOREIGN KEY (registrant_id) REFERENCES registrant (registrant_id) ON DELETE CASCADE + FOREIGN KEY (registrant_id) REFERENCES registrant (registrant_id) + ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB; CREATE TABLE roa_request ( roa_request_id SERIAL NOT NULL, - roa_request_handle VARCHAR(255) NOT NULL, + self_handle VARCHAR(255) NOT NULL, asn BIGINT UNSIGNED NOT NULL, PRIMARY KEY (roa_request_id) ) ENGINE=InnoDB; @@ -89,17 +92,52 @@ CREATE TABLE roa_request_prefix ( roa_request_id BIGINT UNSIGNED NOT NULL, PRIMARY KEY (roa_request_id, prefix, prefixlen, max_prefixlen), CONSTRAINT roa_request_prefix_roa_request_id - FOREIGN KEY (roa_request_id) REFERENCES roa_request (roa_request_id) ON DELETE CASCADE + FOREIGN KEY (roa_request_id) REFERENCES roa_request (roa_request_id) + ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB; CREATE TABLE ghostbuster_request ( ghostbuster_request_id SERIAL NOT NULL, - self_handle VARCHAR(40) NOT NULL, - parent_handle VARCHAR(40), + self_handle VARCHAR(255) NOT NULL, + parent_handle VARCHAR(255), vcard LONGBLOB NOT NULL, PRIMARY KEY (ghostbuster_request_id) ) ENGINE=InnoDB; +CREATE TABLE ee_certificate ( + ee_certificate_id SERIAL NOT NULL, + self_handle VARCHAR(255) NOT NULL, + pkcs10 LONGBLOB NOT NULL, + gski VARCHAR(27) NOT NULL, + cn VARCHAR(64) NOT NULL, + sn VARCHAR(64), + eku TEXT NOT NULL, + valid_until DATETIME NOT NULL, + PRIMARY KEY (ee_certificate_id), + UNIQUE (self_handle, gski) +) ENGINE=InnoDB; + +CREATE TABLE ee_certificate_asn ( + start_as BIGINT UNSIGNED NOT NULL, + end_as BIGINT UNSIGNED NOT NULL, + ee_certificate_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (ee_certificate_id, start_as, end_as), + CONSTRAINT ee_certificate_asn_ee_certificate_id + FOREIGN KEY (ee_certificate_id) REFERENCES ee_certificate (ee_certificate_id) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE ee_certificate_net ( + version TINYINT UNSIGNED NOT NULL, + start_ip VARCHAR(40) NOT NULL, + end_ip VARCHAR(40) NOT NULL, + ee_certificate_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (ee_certificate_id, version, start_ip, end_ip), + CONSTRAINT ee_certificate_net_ee_certificate_id + FOREIGN KEY (ee_certificate_id) REFERENCES ee_certificate (ee_certificate_id) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + -- Local Variables: -- indent-tabs-mode: nil -- End: diff --git a/rpkid/tests/revoke.yaml b/rpkid/tests/revoke.yaml index c006460d..2edb8335 100644 --- a/rpkid/tests/revoke.yaml +++ b/rpkid/tests/revoke.yaml @@ -49,150 +49,372 @@ kids: ipv4: 10.3.0.44/32 --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 rekey: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 + - name: R0 revoke: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - sleep 30 - --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 rekey: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 revoke: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - sleep 30 - --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 rekey: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 revoke: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - sleep 30 - --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 rekey: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 revoke: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - sleep 30 - --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 rekey: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 revoke: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - sleep 30 - --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 rekey: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 revoke: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - sleep 30 - --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 rekey: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 revoke: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - sleep 30 - --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 rekey: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 revoke: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - sleep 30 - --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 rekey: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 revoke: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - sleep 30 - --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 rekey: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - name: R0 revoke: - sleep 10 + --- -- shell sleep 1; dir=rcynic.`date +%s`.data; mkdir $dir; cd rcynic-data; pax -rwl . ../$dir; find . -type f -name '*.cer' | sort | xargs ../../../../utils/uri/uri -s >../${dir%.data}.uris; sleep 1 +- shell sleep 1; + dir=rcynic.`date +%s`.data; mkdir $dir; + cd rcynic-data; + pax -rwl . ../$dir; find . -type f -name '*.cer' | + sort | + xargs ../../../../utils/uri/uri -s + >../${dir%.data}.uris; + sleep 1 - sleep 30 diff --git a/rpkid/tests/smoketest.1.yaml b/rpkid/tests/smoketest.1.yaml index 455e14d6..914aaae4 100644 --- a/rpkid/tests/smoketest.1.yaml +++ b/rpkid/tests/smoketest.1.yaml @@ -40,14 +40,21 @@ kids: roa_request: - asn: 42 ipv4: 192.0.2.32/32 + router_cert: + - router_id: 666 + asn: 42 - name: Bob ipv4: 192.0.2.44-192.0.2.100 ipv4: 10.3.0.0/16 roa_request: - asn: 666 ipv4: 10.3.0.44/32 + --- -- shell set -x; rtr_origin='python ../../../rtr-origin/rtr-origin.py'; $rtr_origin --cronjob rcynic-data/authenticated && $rtr_origin --show +- shell set -x; + rtr_origin='python ../../../rtr-origin/rtr-origin.py'; + $rtr_origin --cronjob rcynic-data/authenticated && + $rtr_origin --show --- - name: R0 rekey: @@ -62,7 +69,10 @@ kids: - asn: 17 ipv4: 10.3.0.1/32, 10.0.0.44/32 --- -- shell set -x; rtr_origin='python ../../../rtr-origin/rtr-origin.py'; $rtr_origin --cronjob rcynic-data/authenticated && $rtr_origin --show +- shell set -x; + rtr_origin='python ../../../rtr-origin/rtr-origin.py'; + $rtr_origin --cronjob rcynic-data/authenticated && + $rtr_origin --show --- - sleep 30 --- diff --git a/rpkid/tests/smoketest.3.yaml b/rpkid/tests/smoketest.3.yaml index f7e4d2a9..e6a10a12 100644 --- a/rpkid/tests/smoketest.3.yaml +++ b/rpkid/tests/smoketest.3.yaml @@ -50,13 +50,20 @@ kids: - asn: 666 ipv4: 10.3.0.0/23 --- -#- shell find publication -type f -name '*.roa' -print -exec ../../../utils/print_roa/print_roa {} \; -#- shell find publication -type f -name '*.mft' -print -exec ../../../utils/print_manifest/print_manifest {} \; +#- shell find publication -type f -name '*.roa' +# -print -exec ../../../utils/print_roa/print_roa {} \; +#- shell find publication -type f -name '*.mft' +# -print -exec ../../../utils/print_manifest/print_manifest {} \; #--- -#- shell find publication -type f -name '*.roa' -print -exec ../../../utils/print_roa/print_roa {} \; -#- shell find publication -type f -name '*.mft' -print -exec ../../../utils/print_manifest/print_manifest {} \; +#- shell find publication -type f -name '*.roa' +# -print -exec ../../../utils/print_roa/print_roa {} \; +#- shell find publication -type f -name '*.mft' +# -print -exec ../../../utils/print_manifest/print_manifest {} \; #--- -- shell set -x; rtr_origin=../../../rtr-origin/rtr-origin; $rtr_origin --cronjob rcynic-data/authenticated && $rtr_origin --show +- shell set -x; + rtr_origin=../../../rtr-origin/rtr-origin; + $rtr_origin --cronjob rcynic-data/authenticated && + $rtr_origin --show --- - name: Alice roa_request_del: @@ -68,4 +75,7 @@ kids: ipv4: 192.0.2.0/30-32,192.0.2.32/32 ipv6: 2002:0a00::/32-128 --- -- shell set -x; rtr_origin=../../../rtr-origin/rtr-origin; $rtr_origin --cronjob rcynic-data/authenticated && $rtr_origin --show +- shell set -x; + rtr_origin=../../../rtr-origin/rtr-origin; + $rtr_origin --cronjob rcynic-data/authenticated && + $rtr_origin --show diff --git a/rpkid/tests/smoketest.7.yaml b/rpkid/tests/smoketest.7.yaml index 84c98a31..fedd2fff 100644 --- a/rpkid/tests/smoketest.7.yaml +++ b/rpkid/tests/smoketest.7.yaml @@ -68,5 +68,10 @@ roa_request: ipv4: 208.91.236.0/22,203.33.196.0/24,203.27.251.0/24,198.80.148.0/24,198.80.131.0/24,157.130.103.144/30,140.222.224.0/24,65.243.171.0/24,63.122.162.212/30,63.116.191.0/24,63.81.136.0/24,17.0.0.0/8,17.128.0.0/9 --- -- shell set -x; find publication -type f -name '*.roa' -print -exec ../../../utils/print_roa/print_roa {} \; -- shell set -x; rtr_origin=../../../rtr-origin/rtr-origin; $rtr_origin --cronjob rcynic-data/authenticated && $rtr_origin --show +- shell set -x; + find publication -type f -name '*.roa' + -print -exec ../../../utils/print_roa/print_roa {} \; + ; + rtr_origin=../../../rtr-origin/rtr-origin; + $rtr_origin --cronjob rcynic-data/authenticated && + $rtr_origin --show diff --git a/rpkid/tests/smoketest.py b/rpkid/tests/smoketest.py index e9135a42..28bedaa4 100644 --- a/rpkid/tests/smoketest.py +++ b/rpkid/tests/smoketest.py @@ -134,6 +134,8 @@ pubd_pubd_cert = None pubd_last_cms_time = None +ecdsa_params = None + class CantRekeyYAMLLeaf(Exception): """ Can't rekey YAML leaf. @@ -228,7 +230,8 @@ def main(): rootd_process = subprocess.Popen((prog_python, prog_rootd, "-d", "-c", rootd_name + ".conf")) rpki.log.info("Starting pubd") - pubd_process = subprocess.Popen((prog_python, prog_pubd, "-d", "-c", pubd_name + ".conf") + (("-p", pubd_name + ".prof") if args.profile else ())) + pubd_process = subprocess.Popen((prog_python, prog_pubd, "-d", "-c", pubd_name + ".conf") + + (("-p", pubd_name + ".prof") if args.profile else ())) rpki.log.info("Starting rsyncd") rsyncd_process = subprocess.Popen((prog_rsyncd, "--daemon", "--no-detach", "--config", rsyncd_name + ".conf")) @@ -248,10 +251,6 @@ def main(): def created_rpki_objects(): - # Setup keys and certs and write YAML files for leaves - for a in db.leaves: - a.setup_yaml_leaf() - # Set pubd's BPKI CRL set_pubd_crl(yaml_loop) @@ -268,10 +267,6 @@ def main(): def run_yaml(): - # Run all YAML clients - for a in db.leaves: - a.run_yaml() - # Run rcynic to check results run_rcynic() @@ -382,6 +377,43 @@ class roa_request(object): def parse(cls, yaml): return cls(yaml.get("asn"), yaml.get("ipv4"), yaml.get("ipv6")) +class router_cert(object): + """ + Representation for a router_cert object. + """ + + _ecparams = None + + @classmethod + def ecparams(cls): + if cls._ecparams is None: + cls._ecparams = rpki.x509.KeyParams.generateEC() + return cls._ecparams + + def __init__(self, asn, router_id): + self.asn = rpki.resource_set.resource_set_as("".join(str(asn).split())) + self.router_id = router_id + self.keypair = rpki.x509.ECDSA.generate(self.ecparams()) + self.pkcs10 = rpki.x509.PKCS10.create(keypair = self.keypair) + self.gski = self.pkcs10.gSKI() + self.cn = "ROUTER-%08x" % self.asn[0].min + self.sn = "%08x" % self.router_id + self.eku = rpki.oids.id_kp_bgpsec_router + + def __eq__(self, other): + return self.asn == other.asn and self.sn == other.sn and self.gski == other.gski + + def __hash__(self): + v6 = tuple(self.v6) if self.v6 is not None else None + return tuple(self.asn).__hash__() + sn.__hash__() + self.gski.__hash__() + + def __str__(self): + return "%s: %s: %s" % (self.asn, self.cn, self.sn, self.gski) + + @classmethod + def parse(cls, yaml): + return cls(yaml.get("asn"), yaml.get("router_id")) + class allocation_db(list): """ Representation of all the entities and allocations in the test @@ -413,7 +445,6 @@ class allocation_db(list): self.root.closure() self.map = dict((a.name, a) for a in self) self.engines = [a for a in self if a.is_engine] - self.leaves = [a for a in self if a.is_leaf] for i, a in enumerate(self.engines): a.set_engine_number(i) for a in self: @@ -493,6 +524,9 @@ class allocation(object): self.base.v4 |= r.v4.to_resource_set() if r.v6: self.base.v6 |= r.v6.to_resource_set() + self.router_certs = [router_cert.parse(y) for y in yaml.get("router_cert", ())] + for r in self.router_certs: + self.base.asn |= r.asn self.hosted_by = yaml.get("hosted_by") self.extra_conf = yaml.get("extra_conf", []) self.hosts = [] @@ -576,6 +610,20 @@ class allocation(object): self.roa_requests.remove(r) cb() + def apply_router_cert_add(self, yaml, cb): + for y in yaml: + r = router_cert.parse(y) + if r not in self.router_certs: + self.router_certs.append(r) + cb() + + def apply_router_cert_del(self, yaml, cb): + for y in yaml: + r = router_cert.parse(y) + if r in self.router_certs: + self.router_certs.remove(r) + cb() + def apply_rekey(self, target, cb): def done(e): @@ -584,14 +632,14 @@ class allocation(object): raise e cb() - if self.is_leaf: - raise CantRekeyYAMLLeaf, "Can't rekey YAML leaf %s, sorry" % self.name - elif target is None: + if target is None: rpki.log.info("Rekeying <self/> %s" % self.name) - self.call_rpkid([rpki.left_right.self_elt.make_pdu(action = "set", self_handle = self.name, rekey = "yes")], cb = done) + self.call_rpkid([rpki.left_right.self_elt.make_pdu( + action = "set", self_handle = self.name, rekey = "yes")], cb = done) else: rpki.log.info("Rekeying <parent/> %s %s" % (self.name, target)) - self.call_rpkid([rpki.left_right.parent_elt.make_pdu(action = "set", self_handle = self.name, parent_handle = target, rekey = "yes")], cb = done) + self.call_rpkid([rpki.left_right.parent_elt.make_pdu( + action = "set", self_handle = self.name, parent_handle = target, rekey = "yes")], cb = done) def apply_revoke(self, target, cb): @@ -601,16 +649,14 @@ class allocation(object): raise e cb() - if self.is_leaf: - rpki.log.info("Attempting to revoke YAML leaf %s" % self.name) - subprocess.check_call((prog_python, prog_poke, "-y", self.name + ".yaml", "-r", "revoke")) - cb() - elif target is None: + if target is None: rpki.log.info("Revoking <self/> %s" % self.name) - self.call_rpkid([rpki.left_right.self_elt.make_pdu(action = "set", self_handle = self.name, revoke = "yes")], cb = done) + self.call_rpkid([rpki.left_right.self_elt.make_pdu( + action = "set", self_handle = self.name, revoke = "yes")], cb = done) else: rpki.log.info("Revoking <parent/> %s %s" % (self.name, target)) - self.call_rpkid([rpki.left_right.parent_elt.make_pdu(action = "set", self_handle = self.name, parent_handle = target, revoke = "yes")], cb = done) + self.call_rpkid([rpki.left_right.parent_elt.make_pdu( + action = "set", self_handle = self.name, parent_handle = target, revoke = "yes")], cb = done) def __str__(self): s = self.name + "\n" @@ -622,10 +668,6 @@ class allocation(object): if self.sia_base: s += " SIA: %s\n" % self.sia_base return s + "Until: %s\n" % self.resources.valid_until - @property - def is_leaf(self): - #return not self.kids and not self.roa_requests - return False @property def is_root(self): @@ -633,7 +675,7 @@ class allocation(object): @property def is_twig(self): - return not self.is_leaf and not self.is_root + return not self.is_root @property def is_hosted(self): @@ -641,7 +683,7 @@ class allocation(object): @property def is_engine(self): - return not self.is_leaf and not self.is_hosted + return not self.is_hosted def set_engine_number(self, n): """ @@ -668,16 +710,13 @@ class allocation(object): Create BPKI certificates for this entity. """ rpki.log.info("Constructing BPKI keys and certs for %s" % self.name) - if self.is_leaf: - setup_bpki_cert_chain(self.name, ee = ("RPKI",)) - else: - setup_bpki_cert_chain(name = self.name, - ee = ("RPKI", "IRDB", "IRBE"), - ca = ("SELF",)) - self.rpkid_ta = rpki.x509.X509(PEM_file = self.name + "-TA.cer") - self.irbe_key = rpki.x509.RSA( PEM_file = self.name + "-IRBE.key") - self.irbe_cert = rpki.x509.X509(PEM_file = self.name + "-IRBE.cer") - self.rpkid_cert = rpki.x509.X509(PEM_file = self.name + "-RPKI.cer") + setup_bpki_cert_chain(name = self.name, + ee = ("RPKI", "IRDB", "IRBE"), + ca = ("SELF",)) + self.rpkid_ta = rpki.x509.X509(PEM_file = self.name + "-TA.cer") + self.irbe_key = rpki.x509.RSA( PEM_file = self.name + "-IRBE.key") + self.irbe_cert = rpki.x509.X509(PEM_file = self.name + "-IRBE.cer") + self.rpkid_cert = rpki.x509.X509(PEM_file = self.name + "-RPKI.cer") def setup_conf_file(self): """ @@ -745,24 +784,44 @@ class allocation(object): cur.execute("DELETE FROM registrant_net") cur.execute("DELETE FROM roa_request_prefix") cur.execute("DELETE FROM roa_request") + cur.execute("DELETE FROM ee_certificate_asn") + cur.execute("DELETE FROM ee_certificate_net") + cur.execute("DELETE FROM ee_certificate") + for s in [self] + self.hosts: for kid in s.kids: - cur.execute("SELECT registrant_id FROM registrant WHERE registrant_handle = %s AND registry_handle = %s", (kid.name, s.name)) + cur.execute("SELECT registrant_id FROM registrant WHERE registrant_handle = %s AND registry_handle = %s", + (kid.name, s.name)) registrant_id = cur.fetchone()[0] for as_range in kid.resources.asn: - cur.execute("INSERT registrant_asn (start_as, end_as, registrant_id) VALUES (%s, %s, %s)", (as_range.min, as_range.max, registrant_id)) + cur.execute("INSERT registrant_asn (start_as, end_as, registrant_id) VALUES (%s, %s, %s)", + (as_range.min, as_range.max, registrant_id)) for v4_range in kid.resources.v4: - cur.execute("INSERT registrant_net (start_ip, end_ip, version, registrant_id) VALUES (%s, %s, 4, %s)", (v4_range.min, v4_range.max, registrant_id)) + cur.execute("INSERT registrant_net (start_ip, end_ip, version, registrant_id) VALUES (%s, %s, 4, %s)", + (v4_range.min, v4_range.max, registrant_id)) for v6_range in kid.resources.v6: - cur.execute("INSERT registrant_net (start_ip, end_ip, version, registrant_id) VALUES (%s, %s, 6, %s)", (v6_range.min, v6_range.max, registrant_id)) - cur.execute("UPDATE registrant SET valid_until = %s WHERE registrant_id = %s", (kid.resources.valid_until, registrant_id)) + cur.execute("INSERT registrant_net (start_ip, end_ip, version, registrant_id) VALUES (%s, %s, 6, %s)", + (v6_range.min, v6_range.max, registrant_id)) + cur.execute("UPDATE registrant SET valid_until = %s WHERE registrant_id = %s", + (kid.resources.valid_until, registrant_id)) for r in s.roa_requests: - cur.execute("INSERT roa_request (roa_request_handle, asn) VALUES (%s, %s)", (s.name, r.asn)) + cur.execute("INSERT roa_request (self_handle, asn) VALUES (%s, %s)", + (s.name, r.asn)) roa_request_id = cur.lastrowid for version, prefix_set in ((4, r.v4), (6, r.v6)): if prefix_set: - cur.executemany("INSERT roa_request_prefix (roa_request_id, prefix, prefixlen, max_prefixlen, version) VALUES (%s, %s, %s, %s, %s)", - ((roa_request_id, x.prefix, x.prefixlen, x.max_prefixlen, version) for x in prefix_set)) + cur.executemany("INSERT roa_request_prefix " + "(roa_request_id, prefix, prefixlen, max_prefixlen, version) " + "VALUES (%s, %s, %s, %s, %s)", + ((roa_request_id, x.prefix, x.prefixlen, x.max_prefixlen, version) + for x in prefix_set)) + for r in s.router_certs: + cur.execute("INSERT ee_certificate (self_handle, pkcs10, gski, cn, sn, eku, valid_until) " + "VALUES (%s, %s, %s, %s, %s, %s, %s)", + (s.name, r.pkcs10.get_DER(), r.gski, r.cn, r.sn, r.eku, s.resources.valid_until)) + ee_certificate_id = cur.lastrowid + cur.executemany("INSERT ee_certificate_asn (ee_certificate_id, start_as, end_as) VALUES (%s, %s, %s)", + ((ee_certificate_id, a.min, a.max) for a in r.asn)) db.close() def run_daemons(self): @@ -770,7 +829,8 @@ class allocation(object): Run daemons for this entity. """ rpki.log.info("Running daemons for %s" % self.name) - self.rpkid_process = subprocess.Popen((prog_python, prog_rpkid, "-d", "-c", self.name + ".conf") + (("-p", self.name + ".prof") if args.profile else ())) + self.rpkid_process = subprocess.Popen((prog_python, prog_rpkid, "-d", "-c", self.name + ".conf") + + (("-p", self.name + ".prof") if args.profile else ())) self.irdbd_process = subprocess.Popen((prog_python, prog_irdbd, "-d", "-c", self.name + ".conf")) def kill_daemons(self): @@ -844,8 +904,6 @@ class allocation(object): if reverse: certifier = certificant certificant = self.name + "-SELF" - elif self.is_leaf: - certifier = self.name + "-TA" else: certifier = self.name + "-SELF" certfile = certifier + "-" + certificant + ".cer" @@ -901,7 +959,7 @@ class allocation(object): #10 requests we get back when we tell rpkid to generate BSC keys. """ - assert not self.is_hosted and not self.is_leaf + assert not self.is_hosted selves = [self] + self.hosts @@ -948,7 +1006,7 @@ class allocation(object): self_handle = s.name, child_handle = k.name, bsc_handle = "b", - bpki_cert = s.cross_certify(k.name + ("-TA" if k.is_leaf else "-SELF")))) + bpki_cert = s.cross_certify(k.name + "-SELF"))) if s.is_root: rootd_cert = s.cross_certify(rootd_name + "-TA") @@ -974,7 +1032,8 @@ class allocation(object): bpki_cms_cert = s.cross_certify(s.parent.name + "-SELF"), sender_name = s.name, recipient_name = s.parent.name, - peer_contact_uri = "http://localhost:%s/up-down/%s/%s" % (s.parent.get_rpki_port(), s.parent.name, s.name))) + peer_contact_uri = "http://localhost:%s/up-down/%s/%s" % (s.parent.get_rpki_port(), + s.parent.name, s.name))) def one(): call_pubd(pubd_pdus, cb = two) @@ -992,7 +1051,8 @@ class allocation(object): b = bsc_dict[s.name] rpki.log.info("Issuing BSC EE cert for %s" % s.name) - cmd = (prog_openssl, "x509", "-req", "-sha256", "-extfile", s.name + "-RPKI.conf", "-extensions", "req_x509_ext", "-days", "30", + cmd = (prog_openssl, "x509", "-req", "-sha256", "-extfile", s.name + "-RPKI.conf", + "-extensions", "req_x509_ext", "-days", "30", "-CA", s.name + "-SELF.cer", "-CAkey", s.name + "-SELF.key", "-CAcreateserial", "-text") signer = subprocess.Popen(cmd, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE) signed = signer.communicate(input = b.pkcs10_request.get_PEM()) @@ -1248,8 +1308,8 @@ def set_pubd_crl(cb): updated whenever we update the CRL. """ rpki.log.info("Setting pubd's BPKI CRL") - call_pubd([rpki.publication.config_elt.make_pdu(action = "set", bpki_crl = rpki.x509.CRL(Auto_file = pubd_name + "-TA.crl"))], - cb = lambda ignored: cb()) + 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 @@ -1314,22 +1374,44 @@ bpki_cert_fmt_2 = '''\ ''' bpki_cert_fmt_3 = '''\ -%(openssl)s req -new -sha256 -key %(name)s-%(kind)s.key -out %(name)s-%(kind)s.req -config %(name)s-%(kind)s.conf && +%(openssl)s req -new \ + -sha256 \ + -key %(name)s-%(kind)s.key \ + -out %(name)s-%(kind)s.req \ + -config %(name)s-%(kind)s.conf && touch %(name)s-%(kind)s.idx && echo >%(name)s-%(kind)s.cnm 01 && ''' bpki_cert_fmt_4 = '''\ -%(openssl)s x509 -req -sha256 -in %(name)s-TA.req -out %(name)s-TA.cer -extfile %(name)s-TA.conf -extensions req_x509_ext -signkey %(name)s-TA.key -days 60 -text \ +%(openssl)s x509 -req -sha256 \ + -in %(name)s-TA.req \ + -out %(name)s-TA.cer \ + -extfile %(name)s-TA.conf \ + -extensions req_x509_ext \ + -signkey %(name)s-TA.key \ + -days 60 -text \ ''' bpki_cert_fmt_5 = ''' && \ -%(openssl)s x509 -req -sha256 -in %(name)s-%(kind)s.req -out %(name)s-%(kind)s.cer -extfile %(name)s-%(kind)s.conf -extensions req_x509_ext -days 30 -text \ - -CA %(name)s-TA.cer -CAkey %(name)s-TA.key -CAcreateserial \ +%(openssl)s x509 -req \ + -sha256 \ + -in %(name)s-%(kind)s.req \ + -out %(name)s-%(kind)s.cer \ + -extfile %(name)s-%(kind)s.conf \ + -extensions req_x509_ext \ + -days 30 \ + -text \ + -CA %(name)s-TA.cer \ + -CAkey %(name)s-TA.key \ + -CAcreateserial \ ''' bpki_cert_fmt_6 = ''' && \ -%(openssl)s ca -batch -gencrl -out %(name)s-%(kind)s.crl -config %(name)s-%(kind)s.conf \ +%(openssl)s ca -batch \ + -gencrl \ + -out %(name)s-%(kind)s.crl \ + -config %(name)s-%(kind)s.conf \ ''' yaml_fmt_1 = '''--- @@ -1467,11 +1549,16 @@ authorityKeyIdentifier = keyid:always basicConstraints = critical,CA:true subjectKeyIdentifier = hash keyUsage = critical,keyCertSign,cRLSign -subjectInfoAccess = 1.3.6.1.5.5.7.48.5;URI:%(rootd_sia)sroot/,1.3.6.1.5.5.7.48.10;URI:%(rootd_sia)sroot/root.mft +subjectInfoAccess = @sia sbgp-autonomousSysNum = critical,AS:0-4294967295 sbgp-ipAddrBlock = critical,IPv4:0.0.0.0/0,IPv6:0::/0 certificatePolicies = critical, @rpki_certificate_policy +[sia] + +1.3.6.1.5.5.7.48.5;URI = %(rootd_sia)sroot/ +1.3.6.1.5.5.7.48.10;URI = %(rootd_sia)sroot/root.mft + [rpki_certificate_policy] policyIdentifier = 1.3.6.1.5.5.7.14.2 @@ -1484,10 +1571,20 @@ rootd_fmt_2 = '''\ rootd_fmt_3 = '''\ echo >%(rootd_name)s.tal %(rootd_sia)sroot.cer && echo >>%(rootd_name)s.tal && -%(openssl)s rsa -pubout -in root.key | awk '!/-----(BEGIN|END)/' >>%(rootd_name)s.tal && -%(openssl)s req -new -sha256 -key root.key -out %(rootd_name)s.req -config %(rootd_name)s.conf -text -extensions req_x509_rpki_ext && -%(openssl)s x509 -req -sha256 -in %(rootd_name)s.req -out root.cer -outform DER -extfile %(rootd_name)s.conf -extensions req_x509_rpki_ext \ - -signkey root.key && +%(openssl)s rsa -pubout -in root.key | +awk '!/-----(BEGIN|END)/' >>%(rootd_name)s.tal && +%(openssl)s req -new -text -sha256 \ + -key root.key \ + -out %(rootd_name)s.req \ + -config %(rootd_name)s.conf \ + -extensions req_x509_rpki_ext && +%(openssl)s x509 -req -sha256 \ + -in %(rootd_name)s.req \ + -out root.cer \ + -outform DER \ + -extfile %(rootd_name)s.conf \ + -extensions req_x509_rpki_ext \ + -signkey root.key && ln -f root.cer %(rsyncd_dir)s ''' diff --git a/rpkid/tests/testpoke.py b/rpkid/tests/testpoke.py index 00dbc300..fd5ab206 100644 --- a/rpkid/tests/testpoke.py +++ b/rpkid/tests/testpoke.py @@ -36,7 +36,6 @@ import rpki.http import rpki.config import rpki.exceptions import rpki.relaxng -import rpki.oids import rpki.log import rpki.async diff --git a/rpkid/tests/yamlconf.py b/rpkid/tests/yamlconf.py index 81698fbf..3c71d3cd 100644 --- a/rpkid/tests/yamlconf.py +++ b/rpkid/tests/yamlconf.py @@ -467,7 +467,7 @@ class allocation(object): root_cert = rpki.x509.X509.self_certify( keypair = root_key, - subject_key = root_key.get_RSApublic(), + subject_key = root_key.get_public(), serial = 1, sia = root_sia, notAfter = rpki.sundial.now() + rpki.sundial.timedelta(days = 365), @@ -481,7 +481,7 @@ class allocation(object): with open(cleanpath(test_dir, "root.tal"), "w") as f: f.write("rsync://%s/root/root.cer\n\n%s" % ( - self.rsync_server, root_key.get_RSApublic().get_Base64())) + self.rsync_server, root_key.get_public().get_Base64())) def mkdir(self, *path): path = self.path(*path) diff --git a/rpkid/tests/yamltest.py b/rpkid/tests/yamltest.py index 08da81f3..5eb3bd2f 100644 --- a/rpkid/tests/yamltest.py +++ b/rpkid/tests/yamltest.py @@ -46,12 +46,14 @@ import sys import yaml import signal import time +import lxml.etree import rpki.resource_set import rpki.sundial import rpki.config import rpki.log import rpki.csv_utils import rpki.x509 +import rpki.relaxng # Nasty regular expressions for parsing config files. Sadly, while # the Python ConfigParser supports writing config files, it does so in @@ -109,6 +111,41 @@ class roa_request(object): """ return cls(y.get("asn"), y.get("ipv4"), y.get("ipv6")) + +class router_cert(object): + """ + Representation for a router_cert object. + """ + + _ecparams = None + + @classmethod + def ecparams(cls): + if cls._ecparams is None: + cls._ecparams = rpki.x509.KeyParams.generateEC() + return cls._ecparams + + def __init__(self, asn, router_id): + self.asn = rpki.resource_set.resource_set_as("".join(str(asn).split())) + self.router_id = router_id + self.keypair = rpki.x509.ECDSA.generate(self.ecparams()) + self.pkcs10 = rpki.x509.PKCS10.create(keypair = self.keypair) + self.gski = self.pkcs10.gSKI() + + def __eq__(self, other): + return self.asn == other.asn and self.router_id == other.router_id and self.gski == other.gski + + def __hash__(self): + v6 = tuple(self.v6) if self.v6 is not None else None + return tuple(self.asn).__hash__() + self.router_id.__hash__() + self.gski.__hash__() + + def __str__(self): + return "%s: %s: %s" % (self.asn, self.router_id, self.gski) + + @classmethod + def parse(cls, yaml): + return cls(yaml.get("asn"), yaml.get("router_id")) + class allocation_db(list): """ Our allocation database. @@ -207,6 +244,7 @@ class allocation(object): if "regen_margin" in yaml: self.regen_margin = rpki.sundial.timedelta.parse(yaml["regen_margin"]).convert_to_seconds() self.roa_requests = [roa_request.parse(y) for y in yaml.get("roa_request", yaml.get("route_origin", ()))] + self.router_certs = [router_cert.parse(y) for y in yaml.get("router_cert", ())] if "ghostbusters" in yaml: self.ghostbusters = yaml.get("ghostbusters") elif "ghostbuster" in yaml: @@ -218,6 +256,8 @@ class allocation(object): self.base.v4 |= r.v4.to_resource_set() if r.v6: self.base.v6 |= r.v6.to_resource_set() + for r in self.router_certs: + self.base.asn |= r.asn self.hosted_by = yaml.get("hosted_by") self.hosts = [] if not self.is_hosted: @@ -365,6 +405,28 @@ class allocation(object): if not args.stop_after_config: self.run_rpkic("load_ghostbuster_requests", fn) + def dump_router_certificates(self): + """ + Write EE certificates (router certificates, etc). + """ + if self.router_certs: + fn = "%s.routercerts.xml" % d.name + if not args.skip_config: + path = self.path(fn) + print "Writing", path + xmlns = "{http://www.hactrn.net/uris/rpki/router-certificate/}" + xml = lxml.etree.Element(xmlns + "router_certificate_requests", version = "1") + for r in self.router_certs: + x = lxml.etree.SubElement(xml, xmlns + "router_certificate_request", + router_id = str(r.router_id), + asn = str(r.asn), + valid_until = str(self.resources.valid_until)) + x.text = r.pkcs10.get_Base64() + rpki.relaxng.router_certificate.assertValid(xml) + lxml.etree.ElementTree(xml).write(path, pretty_print = True) + if not args.stop_after_config: + self.run_rpkic("add_router_certificate_request", fn) + @property def pubd(self): """ @@ -553,7 +615,7 @@ def create_root_certificate(db_root): root_cert = rpki.x509.X509.self_certify( keypair = root_key, - subject_key = root_key.get_RSApublic(), + subject_key = root_key.get_public(), serial = 1, sia = root_sia, notAfter = rpki.sundial.now() + rpki.sundial.timedelta(days = 365), @@ -569,7 +631,7 @@ def create_root_certificate(db_root): f = open(os.path.join(test_dir, "root.tal"), "w") f.write("rsync://localhost:%d/root/root.cer\n\n" % db_root.pubd.rsync_port) - f.write(root_key.get_RSApublic().get_Base64()) + f.write(root_key.get_public().get_Base64()) f.close() @@ -761,6 +823,7 @@ try: d.dump_prefixes() d.dump_roas() d.dump_ghostbusters() + d.dump_router_certificates() # Wait until something terminates. diff --git a/rpkid/up-down-schema.rnc b/rpkid/up-down-schema.rnc index c915d54a..a603b8fe 100644 --- a/rpkid/up-down-schema.rnc +++ b/rpkid/up-down-schema.rnc @@ -1,10 +1,39 @@ # $Id$ # -# RelaxNG Scheme for up-down protocol, extracted from -# draft-ietf-sidr-rescerts-provisioning-10.txt. +# RelaxNG schema for the up-down protocol, extracted from RFC 6492. # -# libxml2 (including xmllint) only groks the XML syntax of RelaxNG, so -# run the compact syntax through trang to get XML syntax. +# Copyright (c) 2012 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.apnic.net/specs/rescerts/up-down/" diff --git a/rpkid/up-down-schema.rng b/rpkid/up-down-schema.rng index f457f1a1..aa956000 100644 --- a/rpkid/up-down-schema.rng +++ b/rpkid/up-down-schema.rng @@ -1,12 +1,41 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - $Id: up-down-schema.rnc 3913 2011-07-01 17:04:18Z sra $ + $Id: up-down-schema.rnc 5748 2014-04-04 16:30:30Z sra $ - RelaxNG Scheme for up-down protocol, extracted from - draft-ietf-sidr-rescerts-provisioning-10.txt. + RelaxNG schema for the up-down protocol, extracted from RFC 6492. - libxml2 (including xmllint) only groks the XML syntax of RelaxNG, so - run the compact syntax through trang to get XML syntax. + Copyright (c) 2012 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.apnic.net/specs/rescerts/up-down/" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> <define name="resource_set_as"> diff --git a/rpkid/upgrade-scripts/upgrade-rpkid-to-0.5709.py b/rpkid/upgrade-scripts/upgrade-rpkid-to-0.5709.py new file mode 100644 index 00000000..aa8e3ec1 --- /dev/null +++ b/rpkid/upgrade-scripts/upgrade-rpkid-to-0.5709.py @@ -0,0 +1,38 @@ +# $Id$ +# +# Copyright (C) 2014 Dragon Research Labs ("DRL") +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +""" +Upgrade RPKI SQL databases to schema expected by 0.5709. + +This code is evaluated in the context of rpki-sql-setup's +do_apply_upgrades() function and has access to its variables. +""" + +db.cur.execute(""" + CREATE TABLE ee_cert ( + ee_cert_id SERIAL NOT NULL, + ski BINARY(20) NOT NULL, + cert LONGBLOB NOT NULL, + published DATETIME, + self_id BIGINT UNSIGNED NOT NULL, + ca_detail_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (ee_cert_id), + CONSTRAINT ee_cert_self_id + FOREIGN KEY (self_id) REFERENCES self (self_id) ON DELETE CASCADE, + CONSTRAINT ee_cert_ca_detail_id + FOREIGN KEY (ca_detail_id) REFERENCES ca_detail (ca_detail_id) ON DELETE CASCADE + ) ENGINE=InnoDB +""") diff --git a/rtr-origin/rtr-origin.py b/rtr-origin/rtr-origin.py index 1d8aeb0f..f37d2ce0 100755 --- a/rtr-origin/rtr-origin.py +++ b/rtr-origin/rtr-origin.py @@ -40,6 +40,8 @@ import traceback import getopt import bisect import random +import base64 + # Debugging only, should be False in production disable_incrementals = False @@ -310,7 +312,8 @@ class pdu_with_serial(pdu): Generate the wire format PDU. """ if self._pdu is None: - self._pdu = self.header_struct.pack(self.version, self.pdu_type, self.nonce, self.header_struct.size, self.serial) + self._pdu = self.header_struct.pack(self.version, self.pdu_type, self.nonce, + self.header_struct.size, self.serial) return self._pdu def got_pdu(self, reader): @@ -538,7 +541,8 @@ class prefix(pdu): def __str__(self): plm = "%s/%s-%s" % (self.prefix, self.prefixlen, self.max_prefixlen) - return "%s %8s %-32s %s" % ("+" if self.announce else "-", self.asn, plm, ":".join(("%02X" % ord(b) for b in self.to_pdu()))) + return "%s %8s %-32s %s" % ("+" if self.announce else "-", self.asn, plm, + ":".join(("%02X" % ord(b) for b in self.to_pdu()))) def show(self): blather("# Class: %s" % self.__class__.__name__) @@ -660,6 +664,86 @@ class ipv6_prefix(prefix): pdu_type = 6 addr_type = v6addr +class router_key(pdu): + """ + Router Key PDU. + """ + + pdu_type = 9 + + header_struct = struct.Struct("!BBBxL20sL") + + @classmethod + def from_text(cls, asnum, gski, key): + """ + Construct a router key from its text form. + """ + + self = cls() + self.asn = long(asnum) + self.ski = base64.urlsafe_b64decode(gski + "=") + self.key = base64.b64decode(key) + self.announce = 1 + self.check() + return self + + def __str__(self): + return "%s %8s %-32s %s" % ("+" if self.announce else "-", self.asn, + base64.urlsafe_b64encode(self.ski).rstrip("="), + ":".join(("%02X" % ord(b) for b in self.to_pdu()))) + + def consume(self, client): + """ + Handle one incoming Router Key PDU + """ + + blather(self) + client.consume_routerkey(self) + + def check(self): + """ + Check attributes to make sure they're within range. + """ + + if self.announce not in (0, 1): + raise CorruptData("Announce value %d is neither zero nor one" % self.announce, pdu = self) + if len(self.ski) != 20: + raise CorruptData("Implausible SKI length %d" % len(self.ski), pdu = self) + pdulen = self.header_struct.size + len(self.key) + if len(self.to_pdu()) != pdulen: + raise CorruptData("Expected %d byte PDU, got %d" % (pdulen, len(self.to_pdu())), pdu = self) + + def to_pdu(self, announce = None): + if announce is not None: + assert announce in (0, 1) + elif self._pdu is not None: + return self._pdu + pdulen = self.header_struct.size + len(self.key) + pdu = (self.header_struct.pack(self.version, + self.pdu_type, + announce if announce is not None else self.announce, + pdulen, + self.ski, + self.asn) + + self.key) + if announce is None: + assert self._pdu is None + self._pdu = pdu + return pdu + + def got_pdu(self, reader): + if not reader.ready(): + return None + header = reader.get(self.header_struct.size) + version, pdu_type, self.announce, length, self.ski, self.asn = self.header_struct.unpack(header) + remaining = length - self.header_struct.size + if remaining <= 0: + raise CorruptData("Got PDU length %d, minimum is %d" % (length, self.header_struct.size + 1), pdu = self) + self.key = reader.get(remaining) + assert header + self.key == self.to_pdu() + return self + + class error_report(pdu): """ Error Report PDU. @@ -736,7 +820,10 @@ class error_report(pdu): if length != self.header_struct.size + self.string_struct.size * 2 + self.pdulen + self.errlen: raise CorruptData("Got PDU length %d, expected %d" % ( length, self.header_struct.size + self.string_struct.size * 2 + self.pdulen + self.errlen)) - assert header + self.to_counted_string(self.errpdu) + self.to_counted_string(self.errmsg.encode("utf8")) == self.to_pdu() + assert (header + + self.to_counted_string(self.errpdu) + + self.to_counted_string(self.errmsg.encode("utf8")) + == self.to_pdu()) return self def serve(self, server): @@ -750,19 +837,19 @@ class error_report(pdu): sys.exit(1) pdu.pdu_map = dict((p.pdu_type, p) for p in (ipv4_prefix, ipv6_prefix, serial_notify, serial_query, reset_query, - cache_response, end_of_data, cache_reset, error_report)) + cache_response, end_of_data, cache_reset, router_key, error_report)) -class prefix_set(list): +class pdu_set(list): """ - Object representing a set of prefixes, that is, one versioned and - (theoretically) consistant set of prefixes extracted from rcynic's - output. + Object representing a set of PDUs, that is, one versioned and + (theoretically) consistant set of prefixes and router keys extracted + from rcynic's output. """ @classmethod def _load_file(cls, filename): """ - Low-level method to read prefix_set from a file. + Low-level method to read pdu_set from a file. """ self = cls() f = open(filename, "rb") @@ -783,28 +870,27 @@ class prefix_set(list): return ((a - b) % (1 << 32)) < (1 << 31) -class axfr_set(prefix_set): +class axfr_set(pdu_set): """ - Object representing a complete set of prefixes, that is, one - versioned and (theoretically) consistant set of prefixes extracted - from rcynic's output, all with the announce field set. + Object representing a complete set of PDUs, that is, one versioned + and (theoretically) consistant set of prefixes and router + certificates extracted from rcynic's output, all with the announce + field set. """ - xargs_count = 500 - @classmethod def parse_rcynic(cls, rcynic_dir): """ - Parse ROAS fetched (and validated!) by rcynic to create a new - axfr_set. We use the scan_roas utility to parse the ASN.1. We - used to parse ROAs internally, but that made this program depend - on all of the complex stuff for building Python extensions, which - is way over the top for a relying party tool. - + Parse ROAS and router certificates fetched (and validated!) by + rcynic to create a new axfr_set. We use the scan_roas and + scan_routercerts utilities to parse the ASN.1, although we may go + back to parsing the files directly using the rpki.POW library code + some day. """ + self = cls() self.serial = timestamp.now() - roa_files = [] + try: p = subprocess.Popen((scan_roas, rcynic_dir), stdout = subprocess.PIPE) for line in p.stdout: @@ -813,6 +899,17 @@ class axfr_set(prefix_set): self.extend(prefix.from_text(asn, addr) for addr in line[2:]) except OSError, e: sys.exit("Could not run %s, check your $PATH variable? (%s)" % (scan_roas, e)) + + try: + p = subprocess.Popen((scan_routercerts, rcynic_dir), stdout = subprocess.PIPE) + for line in p.stdout: + line = line.split() + gski = line[0] + key = line[-1] + self.extend(router_key.from_text(asn, gski, key) for asn in line[1:-1]) + except OSError, e: + sys.exit("Could not run %s, check your $PATH variable? (%s)" % (scan_routercerts, e)) + self.sort() for i in xrange(len(self) - 2, -1, -1): if self[i] == self[i + 1]: @@ -885,7 +982,7 @@ class axfr_set(prefix_set): def save_ixfr(self, other): """ Comparing this axfr_set with an older one and write the resulting - ixfr_set to file with magic filename. Since we store prefix_sets + ixfr_set to file with magic filename. Since we store pdu_sets in sorted order, computing the difference is a trivial linear comparison. """ @@ -965,12 +1062,13 @@ class axfr_set(prefix_set): del self[i] self.serial = pfx.timestamp -class ixfr_set(prefix_set): +class ixfr_set(pdu_set): """ - Object representing an incremental set of prefixes, that is, the + Object representing an incremental set of PDUs, that is, the differences between one versioned and (theoretically) consistant set - of prefixes extracted from rcynic's output and another, with the - announce fields set or cleared as necessary to indicate the changes. + of prefixes and router certificates extracted from rcynic's output + and another, with the announce fields set or cleared as necessary to + indicate the changes. """ @classmethod @@ -1355,6 +1453,19 @@ class client_channel(pdu_channel): prefixlen INTEGER NOT NULL, max_prefixlen INTEGER NOT NULL, UNIQUE (cache_id, asn, prefix, prefixlen, max_prefixlen))''') + + cur.execute(''' + CREATE TABLE routerkey ( + cache_id INTEGER NOT NULL + REFERENCES cache(cache_id) + ON DELETE CASCADE + ON UPDATE CASCADE, + asn INTEGER NOT NULL, + ski TEXT NOT NULL, + key TEXT NOT NULL, + UNIQUE (cache_id, asn, ski), + UNIQUE (cache_id, asn, key))''') + cur.execute("SELECT cache_id, nonce, serial FROM cache WHERE host = ? AND port = ?", (self.host, self.port)) try: @@ -1392,13 +1503,34 @@ class client_channel(pdu_channel): if self.sql: values = (self.cache_id, prefix.asn, str(prefix.prefix), prefix.prefixlen, prefix.max_prefixlen) if prefix.announce: - self.sql.execute("INSERT INTO prefix (cache_id, asn, prefix, prefixlen, max_prefixlen) VALUES (?, ?, ?, ?, ?)", + self.sql.execute("INSERT INTO prefix (cache_id, asn, prefix, prefixlen, max_prefixlen) " + "VALUES (?, ?, ?, ?, ?)", values) else: self.sql.execute("DELETE FROM prefix " "WHERE cache_id = ? AND asn = ? AND prefix = ? AND prefixlen = ? AND max_prefixlen = ?", values) + + def consume_routerkey(self, routerkey): + """ + Handle one Router Key PDU. + """ + + if self.sql: + values = (self.cache_id, routerkey.asn, + base64.urlsafe_b64encode(routerkey.ski).rstrip("="), + base64.b64encode(routerkey.key)) + if routerkey.announce: + self.sql.execute("INSERT INTO routerkey (cache_id, asn, ski, key) " + "VALUES (?, ?, ?, ?)", + values) + else: + self.sql.execute("DELETE FROM routerkey " + "WHERE cache_id = ? AND asn = ? AND (ski = ? OR key = ?)", + values) + + def deliver_pdu(self, pdu): """ Handle received PDU. @@ -2040,6 +2172,17 @@ except NameError: if not os.path.exists(scan_roas): scan_roas = "scan_roas" +# Same thing for scan_routercerts +try: + # Set from autoconf + scan_routercerts = ac_scan_routercerts +except NameError: + # Source directory + scan_routercerts = os.path.normpath(os.path.join(sys.path[0], "..", "utils", + "scan_routercerts", "scan_routercerts")) +if not os.path.exists(scan_routercerts): + scan_routercerts = "scan_routercerts" + force_zero_nonce = False kickme_dir = "sockets" diff --git a/utils/Makefile.in b/utils/Makefile.in index 11c8d17b..c89fdff5 100644 --- a/utils/Makefile.in +++ b/utils/Makefile.in @@ -1,6 +1,6 @@ # $Id$ -SUBDIRS = uri print_rpki_manifest print_roa hashdir find_roa scan_roas +SUBDIRS = uri print_rpki_manifest print_roa hashdir find_roa scan_roas scan_routercerts all clean test distclean install deinstall uninstall:: @for i in ${SUBDIRS}; do echo "Making $@ in $$i"; (cd $$i && ${MAKE} $@); done diff --git a/utils/scan_roas/Makefile.in b/utils/scan_roas/Makefile.in index 3d86532d..7707969c 100644 --- a/utils/scan_roas/Makefile.in +++ b/utils/scan_roas/Makefile.in @@ -39,7 +39,7 @@ ROA_DIR = ${abs_top_builddir}/rpkid/tests/smoketest.dir/publication test: all -date -u +'now: %Y%m%d%H%M%SZ' - if test -d ${ROA_DIR}; then find ${ROA_DIR} -type f -name '*.roa' -print -exec ./${BIN} {} \; ; else :; fi + if test -d ${ROA_DIR}; then ./${BIN} ${ROA_DIR} ; else :; fi install: all if test -d ${DESTDIR}${bindir} ; then :; else ${INSTALL} -d ${DESTDIR}${bindir}; fi diff --git a/utils/scan_routercerts/Makefile.in b/utils/scan_routercerts/Makefile.in new file mode 100644 index 00000000..715d1325 --- /dev/null +++ b/utils/scan_routercerts/Makefile.in @@ -0,0 +1,41 @@ +# $Id$ + +NAME = scan_routercerts + +BIN = ${NAME} + +INSTALL = @INSTALL@ -m 555 + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +datarootdir = @datarootdir@ +datadir = @datadir@ +localstatedir = @localstatedir@ +sharedstatedir = @sharedstatedir@ +sysconfdir = @sysconfdir@ +bindir = @bindir@ +sbindir = @sbindir@ +libexecdir = @libexecdir@ +libdir = @libdir@ + +abs_top_srcdir = @abs_top_srcdir@ +abs_top_builddir = @abs_top_builddir@ + +all clean: + @true + +ROUTERCERT_DIR = ${abs_top_builddir}/rpkid/tests/smoketest.dir/publication + +test: all + -date -u +'now: %Y%m%d%H%M%SZ' + if test -d ${ROUTERCERT_DIR}; then ./${BIN} ; else :; fi + +install: all + if test -d ${DESTDIR}${bindir} ; then :; else ${INSTALL} -d ${DESTDIR}${bindir}; fi + ${INSTALL} ${BIN} ${DESTDIR}${bindir} + +deinstall uninstall: + rm -f ${DESTDIR}${bindir}/${BIN} + +distclean: clean + rm -f Makefile diff --git a/utils/scan_routercerts/scan_routercerts b/utils/scan_routercerts/scan_routercerts new file mode 100755 index 00000000..342fa272 --- /dev/null +++ b/utils/scan_routercerts/scan_routercerts @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# $Id$ +# +# Copyright (C) 2014 Dragon Research Labs ("DRL") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +""" +Scan rcynic validated output looking for router certificates, print +out stuff that the rpki-rtr code cares about. +""" + +# This program represents a weird temporary state, mostly to avoid +# diving into a recursive yak shaving exercise. +# +# Under the old scheme, anything used by the RP code should be either +# C code or pure Python code using just the standard libraries. This +# has gotten silly, but we haven't yet refactored the current packaged +# builds from two packages into three (adding a -libs package). +# +# So, by rights, this program should be a C monstrosity written using +# the OpenSSL C API. I started coding it that way, but it was just +# too painful for something we're probably going to rewrite as a few +# lines of Python once we refactor, but by the same token I didn't +# want to delay router certificate support until the refactoring. +# +# So this program anticipates the new scheme of things, but makes one +# concession to current reality: if it has a problem importing the +# RPKI-specific libraries, it just quietly exits as if everything were +# fine and there simply are no router certificates to report. This +# isn't the right answer in the long run, but will suffice to avoid +# further bald yaks. + +import os +import sys +import base64 + +try: + import rpki.POW + import rpki.oids +except ImportError: + sys.exit(0) + +rcynic_dir = sys.argv[1] + +for root, dirs, files in os.walk(rcynic_dir): + for fn in files: + if not fn.endswith(".cer"): + continue + x = rpki.POW.X509.derReadFile(os.path.join(root, fn)) + + if rpki.oids.id_kp_bgpsec_router not in (x.getEKU() or ()): + continue + + sys.stdout.write(base64.urlsafe_b64encode(x.getSKI()).rstrip("=")) + for min_asn, max_asn in x.getRFC3779()[0]: + for asn in xrange(min_asn, max_asn + 1): + sys.stdout.write(" %s" % asn) + sys.stdout.write(" %s\n" % base64.b64encode(x.getPublicKey().derWritePublic())) |