-Remove all references to RFC 7730 (docs and source comments), now obsolete.
-Indicate full RFC 8630 compliance at docs.
-Implement full validation of AIA (RFC 6487 section 4.8.7), since HTTPS URIs loaded from a TAL can cause the current validation to fail.
-The AIA validation function is now exposed, so that CAs and EEs can do it when the current certificate is being validated (and already loaded at heap).
-Allow to create uris that start with 'https://', let uri.c ready to validate https and/or rsync uris.
-Parse comments and https URIs from a tal file, comments are ignored. Whenever and https URI is found and utilized, the file is downloaded using the previously commited HTTP module.
| [6493](https://tools.ietf.org/html/rfc6493) (Ghostbusters) | 100% |
| [6810](https://tools.ietf.org/html/rfc6810) (RTR Version 0) | 100% |
| [7318](https://tools.ietf.org/html/rfc7318) (Policy Qualifiers) | 100% |
-| [7730](https://tools.ietf.org/html/rfc7730) (TALs) | 100% |
| [7935](https://tools.ietf.org/html/rfc7935) (RPKI algorithms) | 100% |
| [8182](https://tools.ietf.org/html/rfc8182) (RRDP) | 0% |
| [8209](https://tools.ietf.org/html/rfc8209) (BGPSec Certificates) | 100% |
| [8360](https://tools.ietf.org/html/rfc8360) (Validation Reconsidered) | 100% |
| [8416](https://tools.ietf.org/html/rfc8416) (SLURM) | 100% |
| [8608](https://tools.ietf.org/html/rfc8608) (BGPsec algorithms) | 100% |
-| [8630](https://tools.ietf.org/html/rfc8630) (TALs with HTTPS URIs) | 0% |
+| [8630](https://tools.ietf.org/html/rfc8630) (TALs with HTTPS URIs) | 100% |
### RFC 6350 (vCard)
RRDP is a protocol intended to replace RSYNC in the RPKI. Fort only implements RSYNC, currently.
-### RFC 8630 (TALs with HTTPS URIs)
-
-This RFC is relatively new (published in August 2019) and obsoletes the currently implemented [RFC 7730](https://tools.ietf.org/html/rfc7730).
-
## TO-DO
- Reach 100% RFC compliance
If you are paranoid, however, you'd be advised to get your own TALs.
-The TAL file format has been standardized in [RFC 7730](https://tools.ietf.org/html/rfc7730). It is a text file that contains a list of URLs (which serve as alternate access methods for the TA), followed by a blank line, followed by the Base64-encoded public key of the TA.
+The TAL file format has been standardized in [RFC 8630](https://tools.ietf.org/html/rfc8630). It is a text file that contains zero or more comments (each comment must start with the character "#" and end with a line break), a list of URLs (which serve as alternate access methods for the TA), followed by a blank line, followed by the Base64-encoded public key of the TA.
Just for completeness sake, here's an example on what a typical TAL looks like:
.P
The TAL ("Trust Anchor Locator") is a text file that lists a few URLs which can
be used to access the "Trust Anchor" (the root of a particular RPKI tree) and
-its public key. (See RFC 7730.)
+its public key. (See RFC 8630.)
.RE
.P
goto end2;
error = certificate_validate_extensions_ee(cert, sid, &args->refs,
&policy);
+ if (error)
+ goto end2;
+ error = certificate_validate_aia(args->refs.caIssuers, cert);
if (error)
goto end2;
error = certificate_validate_signature(cert, signedData, signature);
goto end5;
/*
- * rfc7730#section-2.2
- * "The INR extension(s) of this trust anchor MUST contain a non-empty
- * set of number resources."
+ * rfc8630#section-2.3
+ * "The INR extension(s) of this TA MUST contain a non-empty set of
+ * number resources."
* The "It MUST NOT use the "inherit" form of the INR extension(s)"
* part is already handled in certificate_get_resources().
*/
return 0;
}
-static int
-validate_aia(struct certificate_refs *refs)
-{
- struct validation *state;
- struct rpki_uri *parent;
-
- if (refs->caIssuers == NULL)
- pr_crit("Certificate's AIA was not recorded.");
-
- state = state_retrieve();
- if (state == NULL)
- return -EINVAL;
- parent = x509stack_peek_uri(validation_certstack(state));
- if (parent == NULL)
- pr_crit("CA certificate has no parent.");
-
- if (!uri_equals(refs->caIssuers, parent)) {
- return pr_err("Certificate's AIA ('%s') does not match parent's URI ('%s').",
- uri_get_printable(refs->caIssuers),
- uri_get_printable(parent));
- }
-
- return 0;
-}
-
static int
validate_signedObject(struct certificate_refs *refs,
struct rpki_uri *signedObject_uri)
if (error)
return error;
- error = validate_aia(refs);
- if (error)
- return error;
-
if (refs->signedObject != NULL)
pr_crit("CA summary has a signedObject ('%s').",
uri_get_printable(refs->signedObject));
if (error)
return error;
- error = validate_aia(refs);
- if (error)
- return error;
-
return validate_signedObject(refs, uri);
}
enum sync_strategy sync_strategy;
/**
* Handle TAL URIs in random order?
- * (https://tools.ietf.org/html/rfc7730#section-3, last
+ * (https://tools.ietf.org/html/rfc8630#section-3, last
* paragraphs)
*/
bool shuffle_tal_uris;
* - The string WILL be NULL-terminated, but the NULL chara will not be
* included in the returned length. BUT IT'S THERE. Don't worry about
* writing past the allocated space on the last line.
- * - Newline is `\n` according to POSIX, which is good, because RFC 7730
+ * - Newline is `\n` according to POSIX, which is good, because RFC 8630
* agrees. You will have to worry about `\r`, though.
*
* Also, the Linux man page claims the following:
return error;
/* Check if it's '.cer', otherwise treat as a signed object */
- if (uri_has_extension(uri, ".cer")) {
+ if (uri_is_certificate(uri)) {
error = certificate_load(uri, &rcvd_cert);
if (error)
goto free_uri;
/*
* We have a problem at this point:
*
- * RFC 7730 says "The public key used to verify the trust anchor MUST be
+ * RFC 8630 says "The public key used to verify the trust anchor MUST be
* the same as the subjectPublicKeyInfo in the CA certificate and in the
* TAL."
*
/* libcrypto already does this. */
/* rfc6487#section-4.7 */
- /* Fragment of rfc7730#section-2.2 */
+ /* Fragment of rfc8630#section-2.3 */
error = validate_public_key(cert, type);
if (error)
return error;
return EE;
}
+/*
+ * It does some of the things from validate_issuer(), but we can not wait for
+ * such validation, since at this point the RSYNC URI at AIA extension must be
+ * verified to comply with rfc6487#section-4.8.7
+ */
+static int
+force_aia_validation(struct rpki_uri *caIssuers, void *arg)
+{
+ X509 *son = arg;
+ X509 *parent;
+ struct rfc5280_name *son_name;
+ struct rfc5280_name *parent_name;
+ int error;
+
+ pr_debug("AIA's URI didn't matched parent URI, doing AIA RSYNC");
+
+ /* RSYNC is still the prefered access mechanism */
+ error = download_files(caIssuers, false, false);
+ if (error)
+ return error;
+
+ error = certificate_load(caIssuers, &parent);
+ if (error)
+ return error;
+
+ error = x509_name_decode(X509_get_subject_name(parent), "subject",
+ &parent_name);
+ if (error)
+ goto free_parent;
+
+ error = x509_name_decode(X509_get_issuer_name(son), "issuer",
+ &son_name);
+ if (error)
+ goto free_parent_name;
+
+ if (x509_name_equals(parent_name, son_name))
+ error = 0; /* Everything its ok */
+ else
+ error = pr_err("Certificate subject from AIA ('%s') isn't issuer of this certificate.",
+ uri_get_printable(caIssuers));
+
+ x509_name_put(son_name);
+free_parent_name:
+ x509_name_put(parent_name);
+free_parent:
+ X509_free(parent);
+ return error;
+}
+
+int
+certificate_validate_aia(struct rpki_uri *caIssuers, X509 *cert)
+{
+ struct validation *state;
+ struct rpki_uri *parent;
+
+ if (caIssuers == NULL)
+ pr_crit("Certificate's AIA was not recorded.");
+
+ state = state_retrieve();
+ if (state == NULL)
+ return -EINVAL;
+ parent = x509stack_peek_uri(validation_certstack(state));
+ if (parent == NULL)
+ pr_crit("Certificate has no parent.");
+
+ /*
+ * There are two possible issues here, specifically at first level root
+ * certificate's childs:
+ *
+ * - Considering that the root certificate can be published at one or
+ * more rsync or HTTPS URIs (RFC 8630), the validation is done
+ * considering the first valid downloaded certificate URI from the
+ * list of URIs; so, that URI doesn't necessarily matches AIA. And
+ * this issue is more likely to happen if the 'shuffle-uris' flag
+ * is active an a TAL has more than one rsync/HTTPS uri.
+ *
+ * - If the TAL has only one URI, and such URI is HTTPS, the root
+ * certificate will be located at a distinct point that what it's
+ * expected, so this might be an error if such certificate (root
+ * certificate) isn't published at an rsync repository. See RFC 6487
+ * section-4.8.7:
+ *
+ * "The preferred URI access mechanisms is "rsync", and an rsync URI
+ * [RFC5781] MUST be specified with an accessMethod value of
+ * id-ad-caIssuers. The URI MUST reference the point of publication
+ * of the certificate where this Issuer is the subject (the issuer's
+ * immediate superior certificate)."
+ *
+ * November 2019: this isn't a problem, all five RIRs are using one URI
+ * at their TALs, that matches AIA from the first level root certificate
+ * childs. Anyways, we'll try to consult the subject at IETF.
+ */
+ if (uri_equals(caIssuers, parent))
+ return 0;
+
+ /*
+ * URI didn't match, try to match the immediate superior subject with
+ * the current issuer. This will force an RSYNC of AIA's URI, load
+ * the certificate and do the comparison.
+ */
+ return force_aia_validation(caIssuers, cert);
+}
+
/** Boilerplate code for CA certificate validation and recursive traversal. */
int
certificate_traverse(struct rpp *rpp_parent, struct rpki_uri *cert_uri)
if (error)
goto revert_cert;
+ if (!IS_TA) {
+ error = certificate_validate_aia(refs.caIssuers, cert);
+ if (error)
+ goto revert_uris;
+ }
+
error = refs_validate_ca(&refs, rpp_parent);
if (error)
goto revert_uris;
int certificate_validate_extensions_ee(X509 *, OCTET_STRING_t *,
struct certificate_refs *, enum rpki_policy *);
+/*
+ * Specific validation of AIA (rfc6487#section-4.8.7) extension, public so that
+ * CAs and EEs can access it.
+ */
+int certificate_validate_aia(struct rpki_uri *, X509 *);
+
int certificate_traverse(struct rpp *, struct rpki_uri *);
#endif /* SRC_OBJECT_CERTIFICATE_H_ */
#include "thread_var.h"
#include "validation_handler.h"
#include "crypto/base64.h"
+#include "http/http.h"
#include "object/certificate.h"
#include "rsync/rsync.h"
#include "rtr/db/vrps.h"
if (strcmp(uri, "") == 0) {
free(uri);
return pr_err("There's no URI in the first line of the TAL.");
+ } else if (strncmp(uri, "#", 1) == 0) {
+ /* More comments expected, or an URI */
+ do {
+ free(uri); /* Ignore the comment */
+ error = lfile_read(lfile, &uri);
+ if (error)
+ return error;
+ if (uri == NULL)
+ return pr_err("TAL file ended prematurely. (Expected more comments or an URI list.)");
+ if (strcmp(uri, "") == 0) {
+ free(uri);
+ return pr_err("TAL file comments syntax error. (Expected more comments or an URI list.)");
+ }
+ /* Not a comment, probably the URI(s) */
+ if (strncmp(uri, "#", 1) != 0)
+ break;
+ } while (true);
}
error = uris_add(uris, uri);
int error;
for (i = 0; i < tal->uris.count; i++) {
- error = uri_create_str(&uri, tal->uris.array[i],
+ error = uri_create_mixed_str(&uri, tal->uris.array[i],
strlen(tal->uris.array[i]));
- if (error == ENOTRSYNC) {
- /* Log level should probably be INFO. */
- pr_debug("TAL has non-RSYNC URI; ignoring.");
+ if (error == ENOTSUPPORTED) {
+ pr_info("TAL has non-RSYNC/HTTPS URI; ignoring.");
continue;
}
if (error)
*len = tal->spki_len;
}
+static size_t
+write_http_cer(unsigned char *content, size_t size, size_t nmemb, void *arg)
+{
+ FILE *fd = arg;
+ size_t read = size * nmemb;
+ size_t written;
+
+ written = fwrite(content, size, nmemb, fd);
+ if (written != nmemb)
+ return -EINVAL;
+
+ return read;
+}
+
+static int
+handle_https_uri(struct rpki_uri *uri)
+{
+ return http_download_file(uri, write_http_cer);
+}
+
/**
* Performs the whole validation walkthrough on uri @uri, which is assumed to
* have been extracted from a TAL.
*
* A "soft error" is "the connection to the preferred URI fails, or the
* retrieved CA certificate public key does not match the TAL public
- * key." (RFC 7730)
+ * key." (RFC 8630)
*
* A "hard error" is any other error.
*/
if (error)
return ENSURE_NEGATIVE(error);
- error = download_files(uri, true, false);
+ if (uri_is_rsync(uri))
+ error = download_files(uri, true, false);
+ else
+ error = handle_https_uri(uri);
+
if (error) {
- pr_warn("TAL '%s' could not be RSYNC'd.",
+ pr_warn("TAL '%s' could not be downloaded.",
uri_get_printable(uri));
validation_destroy(state);
return ENSURE_NEGATIVE(error);
#ifndef TAL_OBJECT_H_
#define TAL_OBJECT_H_
-/* This is RFC 7730. */
+/* This is RFC 8630. */
#include <stddef.h>
#include "uri.h"
#include "uri.h"
+#include <errno.h>
#include "common.h"
#include "config.h"
#include "log.h"
#include "str.h"
+#define URI_VALID_RSYNC 0x01
+#define URI_VALID_HTTPS 0x02
+
+/* Expected URI types */
+enum rpki_uri_type {
+ URI_RSYNC,
+ URI_HTTPS,
+};
+
/**
* All rpki_uris are guaranteed to be RSYNC URLs right now.
*
struct rpki_uri {
/**
* "Global URI".
- * The one that always starts with "rsync://".
+ * The one that always starts with "rsync://" or "https://".
*
* These things are IA5-encoded, which means you're not bound to get
* non-ASCII characters.
char *local;
/* "local_len" is never needed right now. */
+ /* Type, currently rysnc and https are valid */
+ enum rpki_uri_type type;
+
unsigned int references;
};
return 0;
}
+static int
+validate_uri_begin(char const *uri_pfx, const size_t uri_pfx_len,
+ char const *global, size_t global_len, size_t *size, int error)
+{
+ if (global_len < uri_pfx_len
+ || strncmp(uri_pfx, global, uri_pfx_len) != 0) {
+ if (!error)
+ return -EINVAL;
+ pr_err("Global URI '%s' does not begin with '%s'.",
+ global, uri_pfx);
+ return error;
+ }
+
+ (*size) = uri_pfx_len;
+ return 0;
+}
+
+static int
+validate_gprefix(char const *global, size_t global_len, uint8_t flags,
+ size_t *size, enum rpki_uri_type *type)
+{
+ static char const *const PFX_RSYNC = "rsync://";
+ static char const *const PFX_HTTPS = "https://";
+ size_t const PFX_RSYNC_LEN = strlen(PFX_RSYNC);
+ size_t const PFX_HTTPS_LEN = strlen(PFX_HTTPS);
+ int error;
+
+ if (flags == URI_VALID_RSYNC) {
+ (*type) = URI_RSYNC;
+ return validate_uri_begin(PFX_RSYNC, PFX_RSYNC_LEN, global,
+ global_len, size, ENOTRSYNC);
+ }
+ if (flags == URI_VALID_HTTPS) {
+ (*type) = URI_HTTPS;
+ return validate_uri_begin(PFX_HTTPS, PFX_HTTPS_LEN, global,
+ global_len, size, ENOTHTTPS);
+ }
+ if (flags != (URI_VALID_RSYNC & URI_VALID_HTTPS))
+ pr_crit("Unknown URI flag");
+
+ /* It has both flags */
+ error = validate_uri_begin(PFX_RSYNC, PFX_RSYNC_LEN, global, global_len,
+ size, 0);
+ if (!error) {
+ (*type) = URI_RSYNC;
+ return 0;
+ }
+ error = validate_uri_begin(PFX_HTTPS, PFX_HTTPS_LEN, global, global_len,
+ size, 0);
+ if (error)
+ return ENOTSUPPORTED;
+
+ /* @size was already set */
+ (*type) = URI_HTTPS;
+ return 0;
+}
+
/**
* Initializes @uri->local by converting @uri->global.
*
* "rsync://rpki.ripe.net/repo/manifest.mft", initializes @uri->local as
* "/tmp/rpki/rpki.ripe.net/repo/manifest.mft".
*
- * By contract, if @guri is not RSYNC, this will return ENOTRSYNC.
+ * By contract, if @guri is not RSYNC nor HTTPS, this will return ENOTRSYNC.
* This often should not be treated as an error; please handle gracefully.
*/
static int
-g2l(char const *global, size_t global_len, char **result)
+g2l(char const *global, size_t global_len, uint8_t flags, char **result,
+ enum rpki_uri_type *result_type)
{
- static char const *const PREFIX = "rsync://";
char const *repository;
char *local;
size_t prefix_len;
size_t repository_len;
size_t extra_slash;
size_t offset;
+ enum rpki_uri_type type;
+ int error;
repository = config_get_local_repository();
repository_len = strlen(repository);
- prefix_len = strlen(PREFIX);
- if (global_len < prefix_len
- || strncmp(PREFIX, global, prefix_len) != 0) {
- pr_err("Global URI '%s' does not begin with '%s'.",
- global, PREFIX);
- return ENOTRSYNC; /* Not an error, so not negative */
- }
+ error = validate_gprefix(global, global_len, flags, &prefix_len, &type);
+ if (error)
+ return error;
global += prefix_len;
global_len -= prefix_len;
local[offset] = '\0';
*result = local;
+ (*result_type) = type;
return 0;
}
static int
-autocomplete_local(struct rpki_uri *uri)
+autocomplete_local(struct rpki_uri *uri, uint8_t flags)
{
- return g2l(uri->global, uri->global_len, &uri->local);
+ return g2l(uri->global, uri->global_len, flags, &uri->local,
+ &uri->type);
}
-int
-uri_create(struct rpki_uri **result, void const *guri, size_t guri_len)
+static int
+uri_create(struct rpki_uri **result, uint8_t flags, void const *guri,
+ size_t guri_len)
{
struct rpki_uri *uri;
int error;
return error;
}
- error = autocomplete_local(uri);
+ error = autocomplete_local(uri, flags);
if (error) {
free(uri->global);
free(uri);
int
uri_create_str(struct rpki_uri **uri, char const *guri, size_t guri_len)
{
- return uri_create(uri, guri, guri_len);
+ return uri_create(uri, URI_VALID_RSYNC, guri, guri_len);
+}
+
+/* A URI that can be rsync or https */
+int
+uri_create_mixed_str(struct rpki_uri **uri, char const *guri, size_t guri_len)
+{
+ return uri_create(uri, URI_VALID_RSYNC & URI_VALID_HTTPS, guri,
+ guri_len);
}
/*
return error;
}
- error = autocomplete_local(uri);
+ error = autocomplete_local(uri, URI_VALID_RSYNC);
if (error) {
free(uri->global);
free(uri);
* directory our g2l version of @asn1_string should contain.
* But ask the testers to keep an eye on it anyway.
*/
- return uri_create(uri, ASN1_STRING_get0_data(asn1_string),
+ return uri_create(uri, URI_VALID_RSYNC,
+ ASN1_STRING_get0_data(asn1_string),
ASN1_STRING_length(asn1_string));
}
return uri_has_extension(uri, ".cer");
}
+bool
+uri_is_rsync(struct rpki_uri *uri)
+{
+ return uri->type == URI_RSYNC;
+}
+
static char const *
get_filename(char const *file_path)
{
struct rpki_uri;
-int uri_create(struct rpki_uri **, void const *, size_t);
int uri_create_str(struct rpki_uri **, char const *, size_t);
+int uri_create_mixed_str(struct rpki_uri **, char const *, size_t);
int uri_create_mft(struct rpki_uri **, struct rpki_uri *, IA5String_t *);
int uri_create_ad(struct rpki_uri **, ACCESS_DESCRIPTION *);
bool uri_equals(struct rpki_uri *, struct rpki_uri *);
bool uri_has_extension(struct rpki_uri *, char const *);
bool uri_is_certificate(struct rpki_uri *);
+bool uri_is_rsync(struct rpki_uri *);
+
char const *uri_get_printable(struct rpki_uri *);
#endif /* SRC_URI_H_ */