From: pcarana Date: Wed, 13 Nov 2019 23:47:58 +0000 (-0600) Subject: Support RFC8630, TALs can have comments and HTTPS URIs. X-Git-Tag: v1.2.0~53 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a9853d35b12bb262509821f32f0c89a03089066f;p=thirdparty%2FFORT-validator.git Support RFC8630, TALs can have comments and HTTPS URIs. -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. --- diff --git a/docs/intro-fort.md b/docs/intro-fort.md index 34794c82..b68dfb79 100644 --- a/docs/intro-fort.md +++ b/docs/intro-fort.md @@ -29,7 +29,6 @@ Further information can be found in the subsections below. | [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% | @@ -37,7 +36,7 @@ Further information can be found in the subsections below. | [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) @@ -57,10 +56,6 @@ These constitute the approximate missing 25%. 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 diff --git a/docs/usage.md b/docs/usage.md index be7872eb..1ef9f44a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -165,7 +165,7 @@ Whichever registry serves as root of the tree you want to validate is responsibl 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: diff --git a/man/fort.8 b/man/fort.8 index ee4cc2ca..143ecfb7 100644 --- a/man/fort.8 +++ b/man/fort.8 @@ -174,7 +174,7 @@ utilized by fort as TAL. .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 diff --git a/src/asn1/signed_data.c b/src/asn1/signed_data.c index b40eb31e..1305d950 100644 --- a/src/asn1/signed_data.c +++ b/src/asn1/signed_data.c @@ -100,6 +100,9 @@ handle_sdata_certificate(ANY_t *cert_encoded, struct signed_object_args *args, 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); diff --git a/src/cert_stack.c b/src/cert_stack.c index a532d7f2..deea8acf 100644 --- a/src/cert_stack.c +++ b/src/cert_stack.c @@ -291,9 +291,9 @@ x509stack_push(struct cert_stack *stack, struct rpki_uri *uri, X509 *x509, 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(). */ diff --git a/src/certificate_refs.c b/src/certificate_refs.c index ed8e7826..134d0d92 100644 --- a/src/certificate_refs.c +++ b/src/certificate_refs.c @@ -40,31 +40,6 @@ validate_cdp(struct certificate_refs *refs, struct rpp const *pp) 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) @@ -100,10 +75,6 @@ refs_validate_ca(struct certificate_refs *refs, struct rpp const *pp) 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)); @@ -129,9 +100,5 @@ refs_validate_ee(struct certificate_refs *refs, struct rpp const *pp, if (error) return error; - error = validate_aia(refs); - if (error) - return error; - return validate_signedObject(refs, uri); } diff --git a/src/config.c b/src/config.c index 2f966899..9b236e93 100644 --- a/src/config.c +++ b/src/config.c @@ -38,7 +38,7 @@ struct rpki_config { 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; diff --git a/src/line_file.c b/src/line_file.c index a0f6b68a..c6563e67 100644 --- a/src/line_file.c +++ b/src/line_file.c @@ -81,7 +81,7 @@ lfile_read(struct line_file *lfile, char **result) * - 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: diff --git a/src/object/certificate.c b/src/object/certificate.c index c1dff1e3..7a31d3b6 100644 --- a/src/object/certificate.c +++ b/src/object/certificate.c @@ -216,7 +216,7 @@ check_dup_public_key(bool *duplicated, char const *file, void *arg) 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; @@ -327,7 +327,7 @@ validate_spki(X509_PUBKEY *cert_spki) /* * 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." * @@ -515,7 +515,7 @@ certificate_validate_rfc6487(X509 *cert, enum cert_type type) /* 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; @@ -1723,6 +1723,109 @@ get_certificate_type(X509 *cert, bool is_ta) 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) @@ -1812,6 +1915,12 @@ 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; diff --git a/src/object/certificate.h b/src/object/certificate.h index 758a469d..d5bad771 100644 --- a/src/object/certificate.h +++ b/src/object/certificate.h @@ -53,6 +53,12 @@ int certificate_get_resources(X509 *, struct resources *, enum cert_type); 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_ */ diff --git a/src/object/tal.c b/src/object/tal.c index f35fa489..69eec5be 100644 --- a/src/object/tal.c +++ b/src/object/tal.c @@ -22,6 +22,7 @@ #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" @@ -105,6 +106,23 @@ read_uris(struct line_file *lfile, struct uris *uris) 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); @@ -359,11 +377,10 @@ foreach_uri(struct tal *tal, foreach_uri_cb cb, void *arg) 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) @@ -410,6 +427,26 @@ tal_get_spki(struct tal *tal, unsigned char const **buffer, size_t *len) *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. @@ -426,7 +463,7 @@ handle_tal_uri(struct tal *tal, struct rpki_uri *uri, void *arg) * * 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. */ @@ -446,9 +483,13 @@ handle_tal_uri(struct tal *tal, struct rpki_uri *uri, void *arg) 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); diff --git a/src/object/tal.h b/src/object/tal.h index 12e6669c..19ff0811 100644 --- a/src/object/tal.h +++ b/src/object/tal.h @@ -1,7 +1,7 @@ #ifndef TAL_OBJECT_H_ #define TAL_OBJECT_H_ -/* This is RFC 7730. */ +/* This is RFC 8630. */ #include #include "uri.h" diff --git a/src/uri.c b/src/uri.c index 9a5b1f5d..a8ffd674 100644 --- a/src/uri.c +++ b/src/uri.c @@ -1,10 +1,20 @@ #include "uri.h" +#include #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. * @@ -24,7 +34,7 @@ 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. @@ -53,6 +63,9 @@ struct rpki_uri { char *local; /* "local_len" is never needed right now. */ + /* Type, currently rysnc and https are valid */ + enum rpki_uri_type type; + unsigned int references; }; @@ -178,6 +191,63 @@ succeed: 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. * @@ -185,30 +255,28 @@ succeed: * "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; @@ -228,17 +296,20 @@ g2l(char const *global, size_t global_len, char **result) 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; @@ -253,7 +324,7 @@ uri_create(struct rpki_uri **result, void const *guri, size_t guri_len) return error; } - error = autocomplete_local(uri); + error = autocomplete_local(uri, flags); if (error) { free(uri->global); free(uri); @@ -268,7 +339,15 @@ uri_create(struct rpki_uri **result, void const *guri, size_t guri_len) 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); } /* @@ -290,7 +369,7 @@ uri_create_mft(struct rpki_uri **result, struct rpki_uri *mft, IA5String_t *ia5) return error; } - error = autocomplete_local(uri); + error = autocomplete_local(uri, URI_VALID_RSYNC); if (error) { free(uri->global); free(uri); @@ -345,7 +424,8 @@ uri_create_ad(struct rpki_uri **uri, ACCESS_DESCRIPTION *ad) * 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)); } @@ -411,6 +491,12 @@ uri_is_certificate(struct rpki_uri *uri) 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) { diff --git a/src/uri.h b/src/uri.h index a6e6bdba..d5434180 100644 --- a/src/uri.h +++ b/src/uri.h @@ -7,8 +7,8 @@ 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 *); @@ -26,6 +26,8 @@ size_t uri_get_global_len(struct rpki_uri *); 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_ */