From: Alberto Leiva Popper Date: Tue, 15 Jan 2019 15:42:17 +0000 (-0600) Subject: Implement some postponed requirements X-Git-Tag: v0.0.2~112 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0caaf77a1fc4c2a38c07ca5b1902063a4e5868aa;p=thirdparty%2FFORT-validator.git Implement some postponed requirements At this point I'm filling in blanks and TODOs rather than focus on a particular feature. Mainly, this commit adds - Validate certificate extensions - Improve the main loop so it stops on the TAL's first successful URI --- diff --git a/src/Makefile.am b/src/Makefile.am index 323151f2..5b4c7104 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,6 +12,7 @@ rpki_validator_SOURCES += debug.h debug.c rpki_validator_SOURCES += file.h file.c rpki_validator_SOURCES += line_file.h line_file.c rpki_validator_SOURCES += log.h log.c +rpki_validator_SOURCES += random.h random.c rpki_validator_SOURCES += resource.h resource.c rpki_validator_SOURCES += sorted_array.h sorted_array.c rpki_validator_SOURCES += state.h state.c diff --git a/src/asn1/signed_data.c b/src/asn1/signed_data.c index d1a8497e..25650b29 100644 --- a/src/asn1/signed_data.c +++ b/src/asn1/signed_data.c @@ -33,9 +33,25 @@ is_digest_algorithm(AlgorithmIdentifier_t *id, char *what) return 0; } +static int +get_sid(struct SignerInfo *sinfo, OCTET_STRING_t **result) +{ + switch (sinfo->sid.present) { + case SignerIdentifier_PR_subjectKeyIdentifier: + *result = &sinfo->sid.choice.subjectKeyIdentifier; + return 0; + case SignerIdentifier_PR_issuerAndSerialNumber: + return pr_err("Signer Info's sid is an IssuerAndSerialNumber, not a SubjectKeyIdentifier."); + case SignerIdentifier_PR_NOTHING: + break; + } + + return pr_err("Signer Info's sid is not a SubjectKeyIdentifier."); +} + static int handle_sdata_certificate(ANY_t *any, STACK_OF(X509_CRL) *crls, - struct resources *res) + struct resources *res, OCTET_STRING_t *sid) { const unsigned char *tmp; X509 *cert; @@ -71,7 +87,7 @@ handle_sdata_certificate(ANY_t *any, STACK_OF(X509_CRL) *crls, goto end2; } - error = certificate_traverse_ee(cert); + error = certificate_traverse_ee(cert, sid); end2: X509_free(cert); @@ -225,6 +241,7 @@ validate(struct SignedData *sdata, STACK_OF(X509_CRL) *crls, struct resources *res) { struct SignerInfo *sinfo; + OCTET_STRING_t *sid = NULL; int error; /* rfc6488#section-2.1 */ @@ -259,23 +276,13 @@ validate(struct SignedData *sdata, STACK_OF(X509_CRL) *crls, if (error) return error; - /* section-2.1.3 */ + /* rfc6488#section-2.1.3 */ /* Specific sub-validations will be performed later by calling code. */ - /* rfc6488#section-2.1.4 */ - /* rfc6488#section-3.1.c 1/2 */ - if (sdata->certificates == NULL) - return pr_err("The SignedData does not contain certificates."); - - if (sdata->certificates->list.count != 1) { - return pr_err("The SignedData contains %d certificates, one expected.", - sdata->certificates->list.count); - } - - error = handle_sdata_certificate(sdata->certificates->list.array[0], - crls, res); - if (error) - return error; + /* + * We will validate the certificate later, because we need the sid + * first. + */ /* rfc6488#section-2.1.5 */ /* rfc6488#section-3.1.d */ @@ -294,9 +301,25 @@ validate(struct SignedData *sdata, STACK_OF(X509_CRL) *crls, /* rfc6488#section-2.1.6.2 */ /* rfc6488#section-3.1.c 2/2 */ - /* - * TODO need the "EE certificate carried in the CMS certificates field." - */ + /* (Most of this requirement is in handle_ski_ee().) */ + error = get_sid(sinfo, &sid); + if (error) + return error; + + /* rfc6488#section-2.1.4 */ + /* rfc6488#section-3.1.c 1/2 */ + if (sdata->certificates == NULL) + return pr_err("The SignedData does not contain certificates."); + + if (sdata->certificates->list.count != 1) { + return pr_err("The SignedData contains %d certificates, one expected.", + sdata->certificates->list.count); + } + + error = handle_sdata_certificate(sdata->certificates->list.array[0], + crls, res, sid); + if (error) + return error; /* rfc6488#section-2.1.6.3 */ /* rfc6488#section-3.1.j 2/2 */ diff --git a/src/common.c b/src/common.c index f2ac0812..cbef2d0e 100644 --- a/src/common.c +++ b/src/common.c @@ -30,6 +30,10 @@ file_has_extension(char const *filename, size_t filename_len, char const *ext) * * You need to free the result once you're done. * This function does not assume that @guri is null-terminated. + * + * By contract, if @guri is not RSYNC, this will return ENOTRSYNC. + * This often should not be treated as an error; please handle gracefully. + * TODO open call hirarchy. */ int uri_g2l(char const *guri, size_t guri_len, char **result) @@ -42,8 +46,10 @@ uri_g2l(char const *guri, size_t guri_len, char **result) prefix_len = strlen(PREFIX); - if (guri_len < prefix_len || strncmp(PREFIX, guri, prefix_len) != 0) - return pr_err("Global URI does not begin with '%s'.", PREFIX); + if (guri_len < prefix_len || strncmp(PREFIX, guri, prefix_len) != 0) { + pr_err("Global URI does not begin with '%s'.", PREFIX); + return ENOTRSYNC; /* Not really an error, so not negative */ + } guri += prefix_len; guri_len -= prefix_len; diff --git a/src/common.h b/src/common.h index f18345ba..4962d5c2 100644 --- a/src/common.h +++ b/src/common.h @@ -8,6 +8,13 @@ #define ENOTSUPPORTED 3172 /* "I haven't implemented this yet." */ #define ENOTIMPLEMENTED 3173 +/* + * "URI was not RSYNC; ignore it." + * Not really an error. The RFCs usually declare URI lists; usually only one of + * them is required to be RSYNC and the others should be skipped (until we + * start supporting them.) + */ +#define ENOTRSYNC 3174 extern char const *repository; extern size_t repository_len; diff --git a/src/main.c b/src/main.c index c0457c1d..72e95f95 100644 --- a/src/main.c +++ b/src/main.c @@ -43,7 +43,7 @@ handle_tal_certificate(char *uri) error = certificate_validate_rfc6487(cert, true); if (error) goto revert; - error = certificate_traverse_ca(cert, NULL); + error = certificate_traverse_ta(cert, NULL); revert: X509_free(cert); @@ -59,6 +59,22 @@ end: static int handle_tal_uri(struct tal *tal, char const *guri) { + /* + * Because of the way the foreach iterates, this function must return + * + * - 0 on soft errors. + * - `> 0` on URI handled successfully. + * - `< 0` on hard errors. + * + * 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) + * + * A "hard error" is any other error. + * + * TODO this will probably need an update after the merge. + */ + struct validation *state; char *luri; int error; @@ -66,26 +82,42 @@ handle_tal_uri(struct tal *tal, char const *guri) /* TODO this probably needs the state... */ error = download_files(guri); if (error) - return error; + return 0; error = validation_prepare(&state, tal); if (error) - return error; + return -abs(error); pr_debug_add("TAL URI %s {", guri); if (!is_certificate(guri)) { pr_err("TAL file does not point to a certificate. (Expected .cer, got '%s')", guri); - error = -ENOTSUPPORTED; + error = -EINVAL; goto end; } error = uri_g2l(guri, strlen(guri), &luri); - if (error) - return error; + if (error) { + error = -abs(error); + goto end; + } error = handle_tal_certificate(luri); + if (error) { + switch (validation_pubkey_state(state)) { + case PKS_INVALID: + error = 0; + break; + case PKS_VALID: + case PKS_UNTESTED: + error = -abs(error); + break; + } + } else { + error = 1; + } + free(luri); end: @@ -100,6 +132,7 @@ main(int argc, char **argv) struct tal *tal; int error; bool is_rsync_active = true; + bool shuffle_uris = false; print_stack_trace_on_segfault(); @@ -107,6 +140,8 @@ main(int argc, char **argv) return pr_err("Repository path as first argument and TAL file as second argument, please."); if (argc >= 4) is_rsync_active = false; + if (argc >= 5) + shuffle_uris = true; /* TODO lol fix this */ error = hash_init(); if (error) @@ -125,7 +160,10 @@ main(int argc, char **argv) error = tal_load(argv[2], &tal); if (!error) { + if (shuffle_uris) + tal_shuffle_uris(tal); error = foreach_uri(tal, handle_tal_uri); + error = (error >= 0) ? 0 : error; tal_destroy(tal); } diff --git a/src/object/certificate.c b/src/object/certificate.c index 9016b0d0..da744fe7 100644 --- a/src/object/certificate.c +++ b/src/object/certificate.c @@ -20,6 +20,80 @@ */ typedef AUTHORITY_INFO_ACCESS SIGNATURE_INFO_ACCESS; +struct extension_metadata { + char *name; + int nid; + bool critical; +}; + +static const struct extension_metadata BC = { + "Basic Constraints", + NID_basic_constraints, + true, +}; +static const struct extension_metadata SKI = { + "Subject Key Identifier", + NID_subject_key_identifier, + false, +}; +static const struct extension_metadata AKI = { + "Authority Key Identifier", + NID_authority_key_identifier, + false, +}; +static const struct extension_metadata KU = { + "Key Usage", + NID_key_usage, + true, +}; +static const struct extension_metadata CDP = { + "CRL Distribution Points", + NID_crl_distribution_points, + false, +}; +static const struct extension_metadata AIA = { + "Authority Information Access", + NID_info_access, + false, +}; +static const struct extension_metadata SIA = { + "Subject Information Access", + NID_sinfo_access , + false, +}; +static const struct extension_metadata CP = { + "Certificate Policies", + NID_certificate_policies, + true, +}; +static const struct extension_metadata IR = { + "IP Resources", + NID_sbgp_ipAddrBlock, + true, +}; +static const struct extension_metadata AR = { + "AS Resources", + NID_sbgp_autonomousSysNum, + true, +}; + +struct extension_handler { + struct extension_metadata const *meta; + bool mandatory; + int (*cb)(X509_EXTENSION *, void *); + void *arg; + + void (*free)(void *); + + /* For internal use */ + bool found; +}; + +struct sia_arguments { + X509 *cert; + STACK_OF(X509_CRL) *crls; +}; + bool is_certificate(char const *file_name) { return file_has_extension(file_name, strlen(file_name), ".cer"); @@ -132,31 +206,37 @@ validate_spki(const unsigned char *cert_spk, int cert_spk_len) error = asn1_decode(_tal_spki, _tal_spki_len, &asn_DEF_SubjectPublicKeyInfo, (void **) &tal_spki); if (error) - return error; + goto fail1; /* Algorithm Identifier */ error = oid2arcs(&tal_spki->algorithm.algorithm, &tal_alg_arcs); if (error) - goto fail; + goto fail2; if (!ARCS_EQUAL_OIDS(&tal_alg_arcs, oid_rsa)) { error = pr_err("TAL's public key format is not RSA PKCS#1 v1.5 with SHA-256."); - goto fail; + goto fail3; } /* SPK */ if (tal_spki->subjectPublicKey.size != cert_spk_len) - goto not_equal; + goto fail4; if (memcmp(tal_spki->subjectPublicKey.buf, cert_spk, cert_spk_len) != 0) - goto not_equal; + goto fail4; + free_arcs(&tal_alg_arcs); ASN_STRUCT_FREE(asn_DEF_SubjectPublicKeyInfo, tal_spki); + validation_pubkey_valid(state); return 0; -not_equal: +fail4: error = pr_err("TAL's public key is different than the root certificate's public key."); -fail: +fail3: + free_arcs(&tal_alg_arcs); +fail2: ASN_STRUCT_FREE(asn_DEF_SubjectPublicKeyInfo, tal_spki); +fail1: + validation_pubkey_invalid(state); return error; } @@ -214,13 +294,6 @@ validate_public_key(X509 *cert, bool is_root) return 0; } -static int -validate_extensions(X509 *cert) -{ - /* TODO (field) */ - return 0; -} - int certificate_validate_rfc6487(X509 *cert, bool is_root) { @@ -279,7 +352,8 @@ certificate_validate_rfc6487(X509 *cert, bool is_root) if (error) return error; - return validate_extensions(cert); + /* We'll validate extensions later. */ + return 0; } int @@ -383,6 +457,10 @@ abort: /* * "GENERAL_NAME, global to local" * Result has to be freed. + * + * If this function returns ENOTRSYNC, it means that @name was not an RSYNC URI. + * This often should not be treated as an error; please handle gracefully. + * TODO open call hierarchy. */ static int gn_g2l(GENERAL_NAME *name, char **luri) @@ -404,15 +482,11 @@ gn_g2l(GENERAL_NAME *name, char **luri) * Does it imply that the GeneralName CHOICE is constrained to type * "uniformResourceIdentifier"? I guess so, though I don't see anything * stopping a few of the other types from also being capable of storing - * URIs. Then again, DER is all about unique serialized representation. + * URIs. * * Also, nobody seems to be using the other types, and handling them * would be a titanic pain in the ass. So this is what I'm committing * to. - * - * I know that this is the logical conclusion; it's just that I know - * that at some point in the future I'm going find myself bewilderingly - * staring at this if again. */ if (type != GEN_URI) { pr_err("Unknown GENERAL_NAME type: %d", type); @@ -557,6 +631,24 @@ certificate_get_resources(X509 *cert, struct resources *resources) return 0; } +static int +cannot_decode(struct extension_metadata const *meta) +{ + return pr_err("Extension '%s' seems to be malformed. Cannot decode.", + meta->name); +} + +static bool +is_rsync(ASN1_IA5STRING *uri) +{ + static char const *const PREFIX = "rsync://"; + size_t prefix_len = strlen(PREFIX); + + return (uri->length >= prefix_len) + ? (strncmp((char *) uri->data, PREFIX, strlen(PREFIX)) == 0) + : false; +} + static int handle_rpkiManifest(ACCESS_DESCRIPTION *ad, STACK_OF(X509_CRL) *crls) { @@ -606,28 +698,299 @@ handle_signedObject(ACCESS_DESCRIPTION *ad) return error; } -int -certificate_traverse_ca(X509 *cert, STACK_OF(X509_CRL) *crls) +static int +handle_bc(X509_EXTENSION *ext, void *arg) +{ + BASIC_CONSTRAINTS *bc; + int error; + + bc = X509V3_EXT_d2i(ext); + if (bc == NULL) + return cannot_decode(&BC); + + /* + * 'The issuer determines whether the "cA" boolean is set.' + * ................................. Uh-huh. So nothing then. + * Well, libcrypto should do the RFC 5280 thing with it anyway. + */ + + error = (bc->pathlen == NULL) + ? 0 + : pr_err("%s extension contains a Path Length Constraint.", BC.name); + + BASIC_CONSTRAINTS_free(bc); + return error; +} + +static int +handle_ski(X509_EXTENSION *ext, void *arg) +{ + X509_PUBKEY *pubkey; + const unsigned char *spk; + int spk_len; + int ok; + + /* + * "Applications are not required to verify that key identifiers match + * when performing certification path validation." + * (rfc5280#section-4.2.1.2) + * I think "match" refers to the "parent's SKI must match the + * children's AKI" requirement, not "The SKI must match the SHA-1 of the + * SPK" requirement. + * So I guess we're only supposed to check the SHA-1. + */ + + pubkey = X509_get_X509_PUBKEY((X509 *) arg); + if (pubkey == NULL) { + crypto_err("X509_get_X509_PUBKEY() returned NULL"); + return -EINVAL; + } + + ok = X509_PUBKEY_get0_param(NULL, &spk, &spk_len, NULL, pubkey); + if (!ok) { + crypto_err("X509_PUBKEY_get0_param() returned %d", ok); + return -EINVAL; + } + + /* Get the SHA-1 of spk */ + /* TODO (certext) ... */ + + /* Decode ext */ + + /* Compare the SHA and the decoded ext */ + + /* Free the decoded ext */ + + return 0; +} + +static int +handle_ski_ee(X509_EXTENSION *ext, void *arg) +{ + ASN1_OCTET_STRING *ski; + OCTET_STRING_t *sid = arg; + int error = 0; + + ski = X509V3_EXT_d2i(ext); + if (ski == NULL) + return cannot_decode(&SKI); + + /* rfc6488#section-2.1.6.2 */ + /* rfc6488#section-3.1.c 2/2 */ + if (ski->length != sid->size + || memcmp(ski->data, sid->buf, sid->size) != 0) { + error = pr_err("The EE certificate's subjectKeyIdentifier does not equal the Signed Object's sid."); + } + + ASN1_OCTET_STRING_free(ski); + return error; +} + +static int +handle_aki_ta(X509_EXTENSION *ext, void *arg) +{ + return 0; /* TODO (certext) implement. */ +} + +static int +handle_aki(X509_EXTENSION *ext, void *arg) +{ + AUTHORITY_KEYID *aki; + int error = 0; + + aki = X509V3_EXT_d2i(ext); + if (aki == NULL) + return cannot_decode(&AKI); + + if (aki->issuer != NULL) { + error = pr_err("%s extension contains an authorityCertIssuer.", + AKI.name); + goto end; + } + if (aki->serial != NULL) { + error = pr_err("%s extension contains an authorityCertSerialNumber.", + AKI.name); + goto end; + } + + /* TODO (certext) stuff */ + +end: + AUTHORITY_KEYID_free(aki); + return error; +} + +static int +handle_ku(X509_EXTENSION *ext, unsigned char byte1) { + /* + * About the key usage string: At time of writing, it's 9 bits long. + * But zeroized rightmost bits can be omitted. + * This implementation assumes that the ninth bit should always be zero. + */ + + ASN1_BIT_STRING *ku; + unsigned char data[2]; + int error = 0; + + ku = X509V3_EXT_d2i(ext); + if (ku == NULL) + return cannot_decode(&KU); + + if (ku->length == 0) { + error = pr_err("%s bit string has no enabled bits.", KU.name); + goto end; + } + + memset(data, 0, sizeof(data)); + memcpy(data, ku->data, ku->length); + + if (ku->data[0] != byte1) { + error = pr_err("Illegal key usage flag string: %u%u%u%u%u%u%u%u%u", + !!(ku->data[0] & 0x80), !!(ku->data[0] & 0x40), + !!(ku->data[0] & 0x20), !!(ku->data[0] & 0x10), + !!(ku->data[0] & 0x08), !!(ku->data[0] & 0x04), + !!(ku->data[0] & 0x02), !!(ku->data[0] & 0x01), + !!(ku->data[1] & 0x80)); + goto end; + } + +end: + ASN1_BIT_STRING_free(ku); + return error; +} + +static int +handle_ku_ca(X509_EXTENSION *ext, void *arg) +{ + return handle_ku(ext, 0x06); +} + +static int +handle_ku_ee(X509_EXTENSION *ext, void *arg) +{ + return handle_ku(ext, 0x80); +} + +static int +handle_cdp(X509_EXTENSION *ext, void *arg) +{ + STACK_OF(DIST_POINT) *crldp = X509V3_EXT_d2i(ext); + DIST_POINT *dp; + GENERAL_NAMES *names; + GENERAL_NAME *name; + int i; + int error = 0; + char *error_msg; + + crldp = X509V3_EXT_d2i(ext); + if (crldp == NULL) + return cannot_decode(&CDP); + + if (sk_DIST_POINT_num(crldp) != 1) { + error = pr_err("The %s extension has %u distribution points. (1 expected)", + CDP.name, sk_DIST_POINT_num(crldp)); + goto end; + } + + dp = sk_DIST_POINT_value(crldp, 0); + + if (dp->CRLissuer != NULL) { + error_msg = "has a CRLIssuer field"; + goto dist_point_error; + } + if (dp->reasons != NULL) { + error_msg = "has a Reasons field"; + goto dist_point_error; + } + + if (dp->distpoint == NULL) { + error_msg = "lacks a distributionPoint field"; + goto dist_point_error; + } + + /* Bleargh. There's no enum. 0 is fullname, 1 is relativename. */ + switch (dp->distpoint->type) { + case 0: + break; + case 1: + error_msg = "has a relative name"; + goto dist_point_error; + default: + error_msg = "has an unknown type of name"; + goto dist_point_error; + } + + names = dp->distpoint->name.fullname; + for (i = 0; i < sk_GENERAL_NAME_num(names); i++) { + name = sk_GENERAL_NAME_value(names, i); + if (name->type == GEN_URI && is_rsync(name->d.uniformResourceIdentifier)) { + /* + * TODO (certext) check the URI matches what we rsync'd. + * Also indent properly. + */ + error = 0; + goto end; + } + } + + error_msg = "lacks an RSYNC URI"; + +dist_point_error: + error = pr_err("The %s extension's distribution point %s.", CDP.name, + error_msg); + +end: + sk_DIST_POINT_pop_free(crldp, DIST_POINT_free); + return error; +} + +static int +handle_aia(X509_EXTENSION *ext, void *arg) +{ + AUTHORITY_INFO_ACCESS *aia; + ACCESS_DESCRIPTION *ad; + int i; + + aia = X509V3_EXT_d2i(ext); + if (aia == NULL) + return cannot_decode(&AIA); + + for (i = 0; i < sk_ACCESS_DESCRIPTION_num(aia); i++) { + ad = sk_ACCESS_DESCRIPTION_value(aia, i); + if (OBJ_obj2nid(ad->method) == NID_ad_ca_issuers) { + /* + * TODO (certext) check the URI matches what we rsync'd. + */ + } + } + + AUTHORITY_INFO_ACCESS_free(aia); + return 0; +} + +static int +handle_sia_ca(X509_EXTENSION *ext, void *arg) +{ + struct sia_arguments *args = arg; struct validation *state; SIGNATURE_INFO_ACCESS *sia; ACCESS_DESCRIPTION *ad; + bool rsync_found = false; bool manifest_found = false; int i; int error; - sia = X509_get_ext_d2i(cert, NID_sinfo_access, NULL, NULL); - if (sia == NULL) { - pr_err("Certificate lacks a Subject Information Access extension."); - return -ESRCH; - } + sia = X509V3_EXT_d2i(ext); + if (sia == NULL) + return cannot_decode(&SIA); state = state_retrieve(); if (state == NULL) { error = -EINVAL; goto end2; } - error = validation_push_cert(state, cert); + error = validation_push_cert(state, args->cert, false); if (error) goto end2; @@ -636,16 +999,26 @@ certificate_traverse_ca(X509 *cert, STACK_OF(X509_CRL) *crls) ad = sk_ACCESS_DESCRIPTION_value(sia, i); if (OBJ_obj2nid(ad->method) == NID_caRepository) { error = handle_caRepository(ad); + if (error == ENOTRSYNC) + continue; if (error) goto end1; + rsync_found = true; + break; } } + if (!rsync_found) { + pr_err("SIA extension lacks an RSYNC URI caRepository."); + error = -ESRCH; + goto end1; + } + /* validate */ for (i = 0; i < sk_ACCESS_DESCRIPTION_num(sia); i++) { ad = sk_ACCESS_DESCRIPTION_value(sia, i); if (OBJ_obj2nid(ad->method) == NID_rpkiManifest) { - error = handle_rpkiManifest(ad, crls); + error = handle_rpkiManifest(ad, args->crls); if (error) goto end1; manifest_found = true; @@ -654,7 +1027,7 @@ certificate_traverse_ca(X509 *cert, STACK_OF(X509_CRL) *crls) /* rfc6481#section-2 */ if (!manifest_found) { - pr_err("Repository publication point seems to have no manifest."); + pr_err("SIA extension lacks an rpkiManifest access description."); error = -ESRCH; } @@ -665,19 +1038,17 @@ end2: return error; } -int -certificate_traverse_ee(X509 *cert) +static int +handle_sia_ee(X509_EXTENSION *ext, void *arg) { SIGNATURE_INFO_ACCESS *sia; ACCESS_DESCRIPTION *ad; int i; - int error; + int error = 0; - sia = X509_get_ext_d2i(cert, NID_sinfo_access, NULL, NULL); - if (sia == NULL) { - pr_err("Certificate lacks a Subject Information Access extension."); - return -ESRCH; - } + sia = X509V3_EXT_d2i(ext); + if (sia == NULL) + return cannot_decode(&SIA); for (i = 0; i < sk_ACCESS_DESCRIPTION_num(sia); i++) { ad = sk_ACCESS_DESCRIPTION_value(sia, i); @@ -697,3 +1068,153 @@ end: AUTHORITY_INFO_ACCESS_free(sia); return error; } + +static int +handle_cp(X509_EXTENSION *ext, void *arg) +{ + return 0; /* TODO (certext) Implement */ +} + +static int +handle_ir(X509_EXTENSION *ext, void *arg) +{ + return 0; /* Handled in certificate_get_resources(). */ +} + +static int +handle_ar(X509_EXTENSION *ext, void *arg) +{ + return 0; /* Handled in certificate_get_resources(). */ +} + +static int +handle_extension(struct extension_handler *handlers, X509_EXTENSION *ext) +{ + struct extension_handler *handler; + int nid; + + nid = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); + + for (handler = handlers; handler->meta != NULL; handler++) { + if (handler->meta->nid == nid) { + if (handler->found) + goto dupe; + handler->found = true; + + if (handler->meta->critical) { + if (!X509_EXTENSION_get_critical(ext)) + goto not_critical; + } else { + if (X509_EXTENSION_get_critical(ext)) + goto critical; + } + + return handler->cb(ext, handler->arg); + } + } + + if (!X509_EXTENSION_get_critical(ext)) + return 0; /* Unknown and not critical; ignore it. */ + + return pr_err("Certificate has unknown extension. (Extension NID: %d)", + nid); +dupe: + return pr_err("Certificate has more than one '%s' extension.", + handler->meta->name); +not_critical: + return pr_err("Extension '%s' is supposed to be marked critical.", + handler->meta->name); +critical: + return pr_err("Extension '%s' is not supposed to be marked critical.", + handler->meta->name); +} + +static int +handle_cert_extensions(struct extension_handler *handlers, X509 *cert) +{ + struct extension_handler *handler; + int e; + int error; + + for (e = 0; e < X509_get_ext_count(cert); e++) { + error = handle_extension(handlers, X509_get_ext(cert, e)); + if (error) + return error; + } + + for (handler = handlers; handler->meta != NULL; handler++) { + if (handler->mandatory && !handler->found) + return pr_err("Certificate is missing the '%s' extension.", + handler->meta->name); + } + + return 0; +} + +int +certificate_traverse_ta(X509 *cert, STACK_OF(X509_CRL) *crls) +{ + struct sia_arguments sia_args; + struct extension_handler handlers[] = { + /* ext reqd handler arg */ + { &BC, true, handle_bc, }, + { &SKI, true, handle_ski, &cert }, + { &AKI, false, handle_aki_ta, }, + { &KU, true, handle_ku_ca, }, + { &SIA, true, handle_sia_ca, &sia_args }, + { &CP, true, handle_cp, }, + { &IR, false, handle_ir, }, + { &AR, false, handle_ar, }, + { NULL }, + }; + + sia_args.cert = cert; + sia_args.crls = crls; + + return handle_cert_extensions(handlers, cert); +} + +int +certificate_traverse_ca(X509 *cert, STACK_OF(X509_CRL) *crls) +{ + struct sia_arguments sia_args; + struct extension_handler handlers[] = { + /* ext reqd handler arg */ + { &BC, true, handle_bc, }, + { &SKI, true, handle_ski, &cert }, + { &AKI, true, handle_aki, }, + { &KU, true, handle_ku_ca, }, + { &CDP, true, handle_cdp, }, + { &AIA, true, handle_aia, }, + { &SIA, true, handle_sia_ca, &sia_args }, + { &CP, true, handle_cp, }, + { &IR, false, handle_ir, }, + { &AR, false, handle_ar, }, + { NULL }, + }; + + sia_args.cert = cert; + sia_args.crls = crls; + + return handle_cert_extensions(handlers, cert); +} + +int +certificate_traverse_ee(X509 *cert, OCTET_STRING_t *sid) +{ + struct extension_handler handlers[] = { + /* ext reqd handler arg */ + { &SKI, true, handle_ski_ee, sid }, + { &AKI, true, handle_aki, }, + { &KU, true, handle_ku_ee, }, + { &CDP, true, handle_cdp, }, + { &AIA, true, handle_aia, }, + { &SIA, true, handle_sia_ee, }, + { &CP, true, handle_cp, }, + { &IR, false, handle_ir, }, + { &AR, false, handle_ar, }, + { NULL }, + }; + + return handle_cert_extensions(handlers, cert); +} diff --git a/src/object/certificate.h b/src/object/certificate.h index 53066ed6..cae7e107 100644 --- a/src/object/certificate.h +++ b/src/object/certificate.h @@ -23,8 +23,14 @@ int certificate_validate_rfc6487(X509 *, bool); * Returns the IP and AS resources declared in the respective extensions. */ int certificate_get_resources(X509 *, struct resources *); + +/** + * Handles the SIA extension, Trust Anchor style. + * (ie. Recursively walks through the certificate's children.) + */ +int certificate_traverse_ta(X509 *, STACK_OF(X509_CRL) *); /** - * Handles the SIA extension, CA style. + * Handles the SIA extension, (intermediate) CA style. * (ie. Recursively walks through the certificate's children.) */ int certificate_traverse_ca(X509 *, STACK_OF(X509_CRL) *); @@ -33,6 +39,6 @@ int certificate_traverse_ca(X509 *, STACK_OF(X509_CRL) *); * (Doesn't actually "traverse" anything. The name is just for the sake of * mirroring.) */ -int certificate_traverse_ee(X509 *); +int certificate_traverse_ee(X509 *, OCTET_STRING_t *); #endif /* SRC_OBJECT_CERTIFICATE_H_ */ diff --git a/src/object/tal.c b/src/object/tal.c index ffc49631..5a8f1aa1 100644 --- a/src/object/tal.c +++ b/src/object/tal.c @@ -1,101 +1,98 @@ +#define _GNU_SOURCE + #include "tal.h" #include #include #include #include -#include #include #include #include "line_file.h" #include "log.h" +#include "random.h" #include "crypto/base64.h" -struct uri { - char *string; - SLIST_ENTRY(uri) next; +struct uris { + char **array; /* This is an array of string pointers. */ + unsigned int count; + unsigned int size; }; -SLIST_HEAD(uri_list, uri); - struct tal { - struct uri_list uris; + struct uris uris; unsigned char *spki; /* Decoded; not base64. */ size_t spki_len; }; -static void -uri_destroy(struct uri *uri) +static int +uris_init(struct uris *uris) { - free(uri->string); - free(uri); + uris->count = 0; + uris->size = 4; /* Most TALs only define one. */ + uris->array = malloc(uris->size * sizeof(char *)); + return (uris->array != NULL) ? 0 : -ENOMEM; } static void -uris_destroy(struct uri_list *uris) +uris_destroy(struct uris *uris) { - struct uri *uri; - - while (!SLIST_EMPTY(uris)) { - uri = SLIST_FIRST(uris); - SLIST_REMOVE_HEAD(uris, next); - uri_destroy(uri); - } + unsigned int i; + for (i = 0; i < uris->count; i++) + free(uris->array[i]); + free(uris->array); } static int -read_uri(struct line_file *lfile, struct uri **result) +uris_add(struct uris *uris, char *uri) { - struct uri *uri; - int err; - - uri = malloc(sizeof(struct uri)); - if (uri == NULL) - return pr_enomem(); - - err = lfile_read(lfile, &uri->string); - if (err) { - free(uri); - return err; + char **tmp; + + if (uris->count + 1 >= uris->size) { + uris->size *= 2; + tmp = realloc(uris->array, uris->size * sizeof(char *)); + if (tmp == NULL) + return pr_enomem(); + uris->array = tmp; } - *result = uri; + uris->array[uris->count++] = uri; return 0; } static int -read_uris(struct line_file *lfile, struct uri_list *uris) +read_uris(struct line_file *lfile, struct uris *uris) { - struct uri *previous, *uri; - int err; + char *uri; + int error; - err = read_uri(lfile, &uri); - if (err) - return err; + error = lfile_read(lfile, &uri); + if (error) + return error; - if (strcmp(uri->string, "") == 0) { - uri_destroy(uri); - return pr_err("TAL file %s contains no URIs", - lfile_name(lfile)); + if (strcmp(uri, "") == 0) { + free(uri); + return pr_err("TAL file contains no URIs"); } - SLIST_INIT(uris); - SLIST_INSERT_HEAD(uris, uri, next); + error = uris_add(uris, uri); + if (error) + return error; do { - previous = uri; - - err = read_uri(lfile, &uri); - if (err) - return err; + error = lfile_read(lfile, &uri); + if (error) + return error; - if (strcmp(uri->string, "") == 0) { - uri_destroy(uri); + if (strcmp(uri, "") == 0) { + free(uri); return 0; /* Happy path */ } - SLIST_INSERT_AFTER(previous, uri, next); + error = uris_add(uris, uri); + if (error) + return error; } while (true); } @@ -146,36 +143,42 @@ tal_load(const char *file_name, struct tal **result) { struct line_file *lfile; struct tal *tal; - int err; + int error; - err = lfile_open(file_name, &lfile); - if (err) - return err; + error = lfile_open(file_name, &lfile); + if (error) + goto fail4; tal = malloc(sizeof(struct tal)); if (tal == NULL) { - lfile_close(lfile); - return -ENOMEM; + error = -ENOMEM; + goto fail3; } - err = read_uris(lfile, &tal->uris); - if (err) { - free(tal); - lfile_close(lfile); - return err; - } + error = uris_init(&tal->uris); + if (error) + goto fail2; - err = read_spki(lfile, tal); - if (err) { - uris_destroy(&tal->uris); - free(tal); - lfile_close(lfile); - return err; - } + error = read_uris(lfile, &tal->uris); + if (error) + goto fail1; + + error = read_spki(lfile, tal); + if (error) + goto fail1; lfile_close(lfile); *result = tal; return 0; + +fail1: + uris_destroy(&tal->uris); +fail2: + free(tal); +fail3: + lfile_close(lfile); +fail4: + return error; } void tal_destroy(struct tal *tal) @@ -191,11 +194,11 @@ void tal_destroy(struct tal *tal) int foreach_uri(struct tal *tal, foreach_uri_cb cb) { - struct uri *cursor; + unsigned int i; int error; - SLIST_FOREACH(cursor, &tal->uris, next) { - error = cb(tal, cursor->string); + for (i = 0; i < tal->uris.count; i++) { + error = cb(tal, tal->uris.array[i]); if (error) return error; } @@ -203,6 +206,25 @@ foreach_uri(struct tal *tal, foreach_uri_cb cb) return 0; } +void +tal_shuffle_uris(struct tal *tal) +{ + char **array = tal->uris.array; + unsigned int count = tal->uris.count; + char *tmp; + long random_index; + unsigned int i; + + random_init(); + + for (i = 0; i < count; i++) { + tmp = array[i]; + random_index = random_at_most(count - 1 - i) + i; + array[i] = array[random_index]; + array[random_index] = tmp; + } +} + void tal_get_spki(struct tal *tal, unsigned char const **buffer, size_t *len) { diff --git a/src/object/tal.h b/src/object/tal.h index d20a9025..6be3099e 100644 --- a/src/object/tal.h +++ b/src/object/tal.h @@ -12,6 +12,7 @@ void tal_destroy(struct tal *); typedef int (*foreach_uri_cb)(struct tal *, char const *); int foreach_uri(struct tal *, foreach_uri_cb); +void tal_shuffle_uris(struct tal *); void tal_get_spki(struct tal *, unsigned char const **, size_t *); diff --git a/src/random.c b/src/random.c new file mode 100644 index 00000000..291ee37c --- /dev/null +++ b/src/random.c @@ -0,0 +1,38 @@ +#include "random.h" +#include +#include + +void +random_init(void) +{ + /* + * time() has second precision, which is fine. + * I don't think that anyone will legitimately need to run this program + * more than once a second. + */ + srandom(time(NULL)); +} + +/** + * Assumes 0 <= max <= RAND_MAX + * Returns in the closed interval [0, max] + * + * Source: https://stackoverflow.com/questions/2509679 + */ +long random_at_most(long max) +{ + /* max <= RAND_MAX < ULONG_MAX, so this is okay. */ + unsigned long num_bins = (unsigned long) max + 1; + unsigned long num_rand = (unsigned long) RAND_MAX + 1; + unsigned long bin_size = num_rand / num_bins; + unsigned long defect = num_rand % num_bins; + long x; + + do { + x = random(); + /* This is carefully written not to overflow */ + } while (num_rand - defect <= (unsigned long) x); + + /* Truncated division is intentional */ + return x / bin_size; +} diff --git a/src/random.h b/src/random.h new file mode 100644 index 00000000..1e837ce2 --- /dev/null +++ b/src/random.h @@ -0,0 +1,7 @@ +#ifndef SRC_RANDOM_H_ +#define SRC_RANDOM_H_ + +void random_init(void); +long random_at_most(long max); + +#endif /* SRC_RANDOM_H_ */ diff --git a/src/resource.c b/src/resource.c index 6b84a5dd..0e343016 100644 --- a/src/resource.c +++ b/src/resource.c @@ -139,7 +139,7 @@ inherit_aors(struct resources *resources, int family) struct resources *parent; parent = get_parent_resources(); - if (!parent) + if (parent == NULL) return pr_err("Certificate inherits IP resources, but parent does not define any resources."); switch (family) { @@ -400,7 +400,7 @@ inherit_asiors(struct resources *resources) struct resources *parent; parent = get_parent_resources(); - if (!parent) + if (parent == NULL) return pr_err("Certificate inherits ASN resources, but parent does not define any resources."); if (resources->asns != NULL) @@ -498,6 +498,14 @@ resources_add_asn(struct resources *resources, struct ASIdentifiers *ids) return pr_err("Unknown ASIdentifierChoice: %d", ids->asnum->present); } +bool +resources_empty(struct resources *res) +{ + return rasn_empty(res->asns) + && res4_empty(res->ip4s) + && res6_empty(res->ip6s); +} + bool resources_contains_asn(struct resources *res, ASId_t asn) { diff --git a/src/resource.h b/src/resource.h index f70c9549..d4888606 100644 --- a/src/resource.h +++ b/src/resource.h @@ -16,6 +16,7 @@ void resources_destroy(struct resources *); int resources_add_ip(struct resources *, struct IPAddressFamily *); int resources_add_asn(struct resources *, struct ASIdentifiers *); +bool resources_empty(struct resources *); bool resources_contains_asn(struct resources *, ASId_t); bool resources_contains_ipv4(struct resources *, struct ipv4_prefix *); bool resources_contains_ipv6(struct resources *, struct ipv6_prefix *); diff --git a/src/resource/asn.c b/src/resource/asn.c index 1dd436a8..11581ab9 100644 --- a/src/resource/asn.c +++ b/src/resource/asn.c @@ -59,6 +59,12 @@ rasn_add(struct resources_asn *asns, ASId_t min, ASId_t max) return sarray_add((struct sorted_array *) asns, &n); } +bool +rasn_empty(struct resources_asn *asns) +{ + return sarray_empty((struct sorted_array *) asns); +} + bool rasn_contains(struct resources_asn *asns, ASId_t min, ASId_t max) { diff --git a/src/resource/asn.h b/src/resource/asn.h index 0d812bf2..855372a3 100644 --- a/src/resource/asn.h +++ b/src/resource/asn.h @@ -11,6 +11,7 @@ void rasn_get(struct resources_asn *); void rasn_put(struct resources_asn *); int rasn_add(struct resources_asn *, ASId_t, ASId_t); +bool rasn_empty(struct resources_asn *); bool rasn_contains(struct resources_asn *, ASId_t, ASId_t); #endif /* SRC_RESOURCE_ASN_H_ */ diff --git a/src/resource/ip4.c b/src/resource/ip4.c index 4612b635..140f0961 100644 --- a/src/resource/ip4.c +++ b/src/resource/ip4.c @@ -77,6 +77,11 @@ res4_add_range(struct resources_ipv4 *ips, struct ipv4_range *range) return sarray_add((struct sorted_array *) ips, &n); } +bool res4_empty(struct resources_ipv4 *ips) +{ + return sarray_empty((struct sorted_array *) ips); +} + bool res4_contains_prefix(struct resources_ipv4 *ips, struct ipv4_prefix *prefix) { diff --git a/src/resource/ip4.h b/src/resource/ip4.h index 8abf0b27..bf4db9b4 100644 --- a/src/resource/ip4.h +++ b/src/resource/ip4.h @@ -12,6 +12,7 @@ void res4_put(struct resources_ipv4 *); int res4_add_prefix(struct resources_ipv4 *, struct ipv4_prefix *); int res4_add_range(struct resources_ipv4 *, struct ipv4_range *); +bool res4_empty(struct resources_ipv4 *); bool res4_contains_prefix(struct resources_ipv4 *, struct ipv4_prefix *); bool res4_contains_range(struct resources_ipv4 *, struct ipv4_range *); diff --git a/src/resource/ip6.c b/src/resource/ip6.c index 42346554..dccf661c 100644 --- a/src/resource/ip6.c +++ b/src/resource/ip6.c @@ -93,6 +93,12 @@ res6_add_range(struct resources_ipv6 *ips, struct ipv6_range *range) return sarray_add((struct sorted_array *) ips, range); } +bool +res6_empty(struct resources_ipv6 *ips) +{ + return sarray_empty((struct sorted_array *) ips); +} + bool res6_contains_prefix(struct resources_ipv6 *ips, struct ipv6_prefix *prefix) { diff --git a/src/resource/ip6.h b/src/resource/ip6.h index 74aa40ce..6e78c805 100644 --- a/src/resource/ip6.h +++ b/src/resource/ip6.h @@ -12,6 +12,7 @@ void res6_put(struct resources_ipv6 *); int res6_add_prefix(struct resources_ipv6 *ps, struct ipv6_prefix *); int res6_add_range(struct resources_ipv6 *, struct ipv6_range *); +bool res6_empty(struct resources_ipv6 *ips); bool res6_contains_prefix(struct resources_ipv6 *, struct ipv6_prefix *); bool res6_contains_range(struct resources_ipv6 *, struct ipv6_range *); diff --git a/src/sorted_array.c b/src/sorted_array.c index 6c535593..8ee83c4a 100644 --- a/src/sorted_array.c +++ b/src/sorted_array.c @@ -126,6 +126,12 @@ sarray_add(struct sorted_array *sarray, void *element) return 0; } +bool +sarray_empty(struct sorted_array *sarray) +{ + return (sarray == NULL) || (sarray->count == 0); +} + bool sarray_contains(struct sorted_array *sarray, void *elem) { diff --git a/src/sorted_array.h b/src/sorted_array.h index 92281f02..4198b5f6 100644 --- a/src/sorted_array.h +++ b/src/sorted_array.h @@ -39,6 +39,7 @@ void sarray_put(struct sorted_array *); #define EINTERSECTION 7900 int sarray_add(struct sorted_array *, void *); +bool sarray_empty(struct sorted_array *); bool sarray_contains(struct sorted_array *, void *); char const *sarray_err2str(int); diff --git a/src/state.c b/src/state.c index 3dff9e60..e2205f97 100644 --- a/src/state.c +++ b/src/state.c @@ -33,6 +33,9 @@ struct validation { * seemingly not intended to be used outside of its library.) */ struct restack *rsrcs; + + /* Did the TAL's public key match the root certificate's public key? */ + enum pubkey_state pubkey_state; }; /* @@ -107,6 +110,8 @@ validation_prepare(struct validation **out, struct tal *tal) goto abort3; } + result->pubkey_state = PKS_UNTESTED; + *out = result; return 0; @@ -160,8 +165,23 @@ validation_resources(struct validation *state) return state->rsrcs; } +void validation_pubkey_valid(struct validation *state) +{ + state->pubkey_state = PKS_VALID; +} + +void validation_pubkey_invalid(struct validation *state) +{ + state->pubkey_state = PKS_INVALID; +} + +enum pubkey_state validation_pubkey_state(struct validation *state) +{ + return state->pubkey_state; +} + int -validation_push_cert(struct validation *state, X509 *cert) +validation_push_cert(struct validation *state, X509 *cert, bool is_ta) { struct resources *resources; int ok; @@ -175,6 +195,16 @@ validation_push_cert(struct validation *state, X509 *cert) if (error) goto fail; + /* + * rfc7730#section-2.2 + * "The INR extension(s) of this trust anchor 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(). + */ + if (is_ta && resources_empty(resources)) + return pr_err("Trust Anchor certificate does not define any number resources."); + ok = sk_X509_push(state->trusted, cert); if (ok <= 0) { error = crypto_err( diff --git a/src/state.h b/src/state.h index 9b5ac89f..0172ead3 100644 --- a/src/state.h +++ b/src/state.h @@ -15,7 +15,17 @@ X509_STORE *validation_store(struct validation *); STACK_OF(X509) *validation_certs(struct validation *); struct restack *validation_resources(struct validation *); -int validation_push_cert(struct validation *, X509 *); +enum pubkey_state { + PKS_VALID, + PKS_INVALID, + PKS_UNTESTED, +}; + +void validation_pubkey_valid(struct validation *); +void validation_pubkey_invalid(struct validation *); +enum pubkey_state validation_pubkey_state(struct validation *); + +int validation_push_cert(struct validation *, X509 *, bool); int validation_pop_cert(struct validation *); struct resources *validation_peek_resource(struct validation *);