From 10823e2de05e34a32c16ae14772d070ba92f5625 Mon Sep 17 00:00:00 2001 From: Alberto Leiva Popper Date: Thu, 15 Nov 2018 14:11:47 -0600 Subject: [PATCH] Add actual certificate tree validation and other misc tweaks The tweaks are 1. Unified error message printing. Probably not the final version. 2. Add validation state object, meant to be passed around everywhere. Prevents global variables. 3. Add a sketch of the CRL code. WIP. --- src/Makefile.am | 5 +- src/asn1/content_info.c | 13 +- src/asn1/content_info.h | 5 +- src/asn1/decode.c | 36 +++--- src/asn1/decode.h | 13 +- src/asn1/oid.c | 4 +- src/asn1/oid.h | 3 +- src/asn1/signed_data.c | 97 ++++++++------ src/asn1/signed_data.h | 6 +- src/common.c | 48 ++----- src/common.h | 61 +-------- src/file.c | 25 ++-- src/file.h | 5 +- src/line_file.c | 4 +- src/log.c | 192 ++++++++++++++++++++++++++++ src/log.h | 17 +++ src/{main2.c => main.c} | 37 ++++-- src/object/certificate.c | 82 ++++++------ src/object/certificate.h | 5 +- src/object/crl.c | 164 ++++++++++++++++++++++++ src/object/crl.h | 10 ++ src/object/manifest.c | 30 +++-- src/object/manifest.h | 3 +- src/object/roa.c | 33 +++-- src/object/roa.h | 3 +- src/object/signed_object.c | 31 +++-- src/object/signed_object.h | 4 +- src/state.c | 255 +++++++++++++++++++++++++++++++++++++ src/state.h | 18 +++ 29 files changed, 919 insertions(+), 290 deletions(-) create mode 100644 src/log.c create mode 100644 src/log.h rename src/{main2.c => main.c} (67%) create mode 100644 src/object/crl.c create mode 100644 src/object/crl.h create mode 100644 src/state.c create mode 100644 src/state.h diff --git a/src/Makefile.am b/src/Makefile.am index c3b4faf2..92a6c2fa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,12 +4,14 @@ LDFLAGS_DEBUG = -rdynamic bin_PROGRAMS = rpki_validator -rpki_validator_SOURCES = main2.c +rpki_validator_SOURCES = main.c rpki_validator_SOURCES += common.h common.c 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 += state.h state.c rpki_validator_SOURCES += asn1/content_info.h asn1/content_info.c rpki_validator_SOURCES += asn1/decode.h asn1/decode.c @@ -17,6 +19,7 @@ rpki_validator_SOURCES += asn1/oid.h asn1/oid.c rpki_validator_SOURCES += asn1/signed_data.h asn1/signed_data.c rpki_validator_SOURCES += object/certificate.h object/certificate.c +rpki_validator_SOURCES += object/crl.h object/crl.c rpki_validator_SOURCES += object/manifest.h object/manifest.c rpki_validator_SOURCES += object/roa.h object/roa.c rpki_validator_SOURCES += object/signed_object.h object/signed_object.c diff --git a/src/asn1/content_info.c b/src/asn1/content_info.c index a9de4350..d363d789 100644 --- a/src/asn1/content_info.c +++ b/src/asn1/content_info.c @@ -1,6 +1,5 @@ #include "content_info.h" -#include #include #include #include "file.h" @@ -29,12 +28,13 @@ validate(struct ContentInfo *info) } static int -decode(struct file_contents *fc, struct ContentInfo **result) +decode(struct validation *state, struct file_contents *fc, + struct ContentInfo **result) { struct ContentInfo *cinfo; int error; - error = asn1_decode_fc(fc, &asn_DEF_ContentInfo, (void **) &cinfo); + error = asn1_decode_fc(state, fc, &asn_DEF_ContentInfo, (void **) &cinfo); if (error) return error; @@ -49,16 +49,17 @@ decode(struct file_contents *fc, struct ContentInfo **result) } int -content_info_load(const char *file_name, struct ContentInfo **result) +content_info_load(struct validation *state, const char *file_name, + struct ContentInfo **result) { struct file_contents fc; int error; - error = file_load(file_name, &fc); + error = file_load(state, file_name, &fc); if (error) return error; - error = decode(&fc, result); + error = decode(state, &fc, result); file_free(&fc); return error; diff --git a/src/asn1/content_info.h b/src/asn1/content_info.h index 87c7e619..b8f91d9d 100644 --- a/src/asn1/content_info.h +++ b/src/asn1/content_info.h @@ -4,8 +4,9 @@ /* Some wrappers for libcmscodec's ContentInfo. */ #include +#include "state.h" -int content_info_load(const char *file_name, struct ContentInfo **result); -void content_info_free(struct ContentInfo *info); +int content_info_load(struct validation *, const char *, struct ContentInfo **); +void content_info_free(struct ContentInfo *); #endif /* SRC_CONTENT_INFO_H_ */ diff --git a/src/asn1/decode.c b/src/asn1/decode.c index 0e6c2a93..4abd49ba 100644 --- a/src/asn1/decode.c +++ b/src/asn1/decode.c @@ -1,11 +1,12 @@ #include "decode.h" -#include #include #include "common.h" +#include "log.h" static int -validate(asn_TYPE_descriptor_t const *descriptor, void *result) +validate(struct validation *state, asn_TYPE_descriptor_t const *descriptor, + void *result) { char error_msg[256]; size_t error_msg_size; @@ -16,7 +17,7 @@ validate(asn_TYPE_descriptor_t const *descriptor, void *result) error = asn_check_constraints(descriptor, result, error_msg, &error_msg_size); if (error == -1) { - warnx("Error validating ASN.1 object: %s", error_msg); + pr_err(state, "Error validating ASN.1 object: %s", error_msg); return -EINVAL; } @@ -24,7 +25,7 @@ validate(asn_TYPE_descriptor_t const *descriptor, void *result) } int -asn1_decode(const void *buffer, size_t buffer_size, +asn1_decode(struct validation *state, const void *buffer, size_t buffer_size, asn_TYPE_descriptor_t const *descriptor, void **result) { asn_dec_rval_t rval; @@ -35,13 +36,13 @@ asn1_decode(const void *buffer, size_t buffer_size, rval = ber_decode(0, descriptor, result, buffer, buffer_size); if (rval.code != RC_OK) { /* TODO if rval.code == RC_WMORE (1), more work is needed */ - warnx("Error decoding ASN.1 object: %d", rval.code); + pr_err(state, "Error decoding ASN.1 object: %d", rval.code); /* Must free partial object according to API contracts. */ ASN_STRUCT_FREE(*descriptor, *result); return -EINVAL; } - error = validate(descriptor, *result); + error = validate(state, descriptor, *result); if (error) { ASN_STRUCT_FREE(*descriptor, *result); return error; @@ -51,25 +52,24 @@ asn1_decode(const void *buffer, size_t buffer_size, } int -asn1_decode_any(ANY_t *any, - asn_TYPE_descriptor_t const *descriptor, - void **result) +asn1_decode_any(struct validation *state, ANY_t *any, + asn_TYPE_descriptor_t const *descriptor, void **result) { - return asn1_decode(any->buf, any->size, descriptor, result); + return asn1_decode(state, any->buf, any->size, descriptor, result); } int -asn1_decode_octet_string(OCTET_STRING_t *string, - asn_TYPE_descriptor_t const *descriptor, - void **result) +asn1_decode_octet_string(struct validation *state, OCTET_STRING_t *string, + asn_TYPE_descriptor_t const *descriptor, void **result) { - return asn1_decode(string->buf, string->size, descriptor, result); + return asn1_decode(state, string->buf, string->size, descriptor, + result); } int -asn1_decode_fc(struct file_contents *fc, - asn_TYPE_descriptor_t const *descriptor, - void **result) +asn1_decode_fc(struct validation *state, struct file_contents *fc, + asn_TYPE_descriptor_t const *descriptor, void **result) { - return asn1_decode(fc->buffer, fc->buffer_size, descriptor, result); + return asn1_decode(state, fc->buffer, fc->buffer_size, descriptor, + result); } diff --git a/src/asn1/decode.h b/src/asn1/decode.h index e20f878d..7c68ed5a 100644 --- a/src/asn1/decode.h +++ b/src/asn1/decode.h @@ -4,12 +4,15 @@ #include #include #include "file.h" +#include "state.h" -int asn1_decode(const void *, size_t, asn_TYPE_descriptor_t const *, void **); -int asn1_decode_any(ANY_t *, asn_TYPE_descriptor_t const *, void **); -int asn1_decode_octet_string(OCTET_STRING_t *, asn_TYPE_descriptor_t const *, - void **); -int asn1_decode_fc(struct file_contents *, asn_TYPE_descriptor_t const *, +int asn1_decode(struct validation *, const void *, size_t, + asn_TYPE_descriptor_t const *, void **); +int asn1_decode_any(struct validation *, ANY_t *, asn_TYPE_descriptor_t const *, void **); +int asn1_decode_octet_string(struct validation *, OCTET_STRING_t *, + asn_TYPE_descriptor_t const *, void **); +int asn1_decode_fc(struct validation *, struct file_contents *, + asn_TYPE_descriptor_t const *, void **); #endif /* SRC_ASN1_DECODE_H_ */ diff --git a/src/asn1/oid.c b/src/asn1/oid.c index 6089ba93..031ce113 100644 --- a/src/asn1/oid.c +++ b/src/asn1/oid.c @@ -51,12 +51,12 @@ oid2arcs(OBJECT_IDENTIFIER_t *oid, struct oid_arcs *result) /* Callers must free @result. */ int -any2arcs(ANY_t *any, struct oid_arcs *result) +any2arcs(struct validation *state, ANY_t *any, struct oid_arcs *result) { OBJECT_IDENTIFIER_t *oid; int error; - error = asn1_decode_any(any, &asn_DEF_OBJECT_IDENTIFIER, + error = asn1_decode_any(state, any, &asn_DEF_OBJECT_IDENTIFIER, (void **) &oid); if (error) return error; diff --git a/src/asn1/oid.h b/src/asn1/oid.h index 8dbc630b..d31b3b4c 100644 --- a/src/asn1/oid.h +++ b/src/asn1/oid.h @@ -4,6 +4,7 @@ #include #include #include "common.h" +#include "state.h" /* These objects are expected to live on the stack. */ struct oid_arcs { @@ -38,7 +39,7 @@ typedef asn_oid_arc_t OID[]; #define OID_SHA512 { 2, 16, 840, 1, 101, 3, 4, 2, 3 } int oid2arcs(OBJECT_IDENTIFIER_t *, struct oid_arcs *); -int any2arcs(ANY_t *, struct oid_arcs *); +int any2arcs(struct validation *, ANY_t *, struct oid_arcs *); bool arcs_equal(struct oid_arcs const *, struct oid_arcs const *); /* Use ARCS_EQUAL_OID() instead. */ diff --git a/src/asn1/signed_data.c b/src/asn1/signed_data.c index 9d20ef90..731084a0 100644 --- a/src/asn1/signed_data.c +++ b/src/asn1/signed_data.c @@ -1,8 +1,8 @@ #include "signed_data.h" -#include #include #include +#include "log.h" #include "oid.h" #include "asn1/decode.h" @@ -41,8 +41,8 @@ is_digest_algorithm(AlgorithmIdentifier_t *aid, bool *result) } static int -validate_content_type_attribute(CMSAttributeValue_t *value, - EncapsulatedContentInfo_t *eci) +validate_content_type_attribute(struct validation *state, + CMSAttributeValue_t *value, EncapsulatedContentInfo_t *eci) { struct oid_arcs attrValue_arcs; struct oid_arcs EncapContentInfo_arcs; @@ -50,7 +50,7 @@ validate_content_type_attribute(CMSAttributeValue_t *value, /* rfc6488#section-3.1.h */ - error = any2arcs(value, &attrValue_arcs); + error = any2arcs(state, value, &attrValue_arcs); if (error) return error; @@ -61,7 +61,8 @@ validate_content_type_attribute(CMSAttributeValue_t *value, } if (!arcs_equal(&attrValue_arcs, &EncapContentInfo_arcs)) { - warnx("The eContentType in the EncapsulatedContentInfo does not match the attrValues in the content-type attribute."); + pr_err(state, + "The eContentType in the EncapsulatedContentInfo does not match the attrValues in the content-type attribute."); error = -EINVAL; } @@ -71,13 +72,14 @@ validate_content_type_attribute(CMSAttributeValue_t *value, } static int -validate_message_digest_attribute(CMSAttributeValue_t *value) +validate_message_digest_attribute(struct validation *state, CMSAttributeValue_t *value) { return 0; /* TODO need the content being signed */ } static int -validate_signed_attrs(struct SignerInfo *sinfo, EncapsulatedContentInfo_t *eci) +validate_signed_attrs(struct validation *state, struct SignerInfo *sinfo, + EncapsulatedContentInfo_t *eci) { struct CMSAttribute *attr; struct CMSAttribute__attrValues *attrs; @@ -90,25 +92,28 @@ validate_signed_attrs(struct SignerInfo *sinfo, EncapsulatedContentInfo_t *eci) int error; if (sinfo->signedAttrs == NULL) { - warnx("The SignerInfo's signedAttrs field is NULL."); + pr_err(state, "The SignerInfo's signedAttrs field is NULL."); return -EINVAL; } for (i = 0; i < sinfo->signedAttrs->list.count; i++) { attr = sinfo->signedAttrs->list.array[i]; if (attr == NULL) { - warnx("SignedAttrs array element %u is NULL.", i); + pr_err(state, "SignedAttrs array element %u is NULL.", + i); continue; } attrs = &attr->attrValues; if (attrs->list.count != 1) { - warnx("signedAttrs's attribute set size (%d) is different than 1.", + pr_err(state, 0, + "signedAttrs's attribute set size (%d) is different than 1.", attr->attrValues.list.count); return -EINVAL; } if (attrs->list.array == NULL || attrs->list.array[0] == NULL) { - warnx("Programming error: Array size is 1 but array itself is NULL."); + pr_err(state, + "Programming error: Array size is 1 but array itself is NULL."); return -EINVAL; } @@ -118,23 +123,25 @@ validate_signed_attrs(struct SignerInfo *sinfo, EncapsulatedContentInfo_t *eci) if (ARCS_EQUAL_OIDS(&attrType, oid_cta)) { if (content_type_found) { - warnx("Multiple ContentTypes found."); + pr_err(state, "Multiple ContentTypes found."); goto illegal_attrType; } - error = validate_content_type_attribute(attr->attrValues.list.array[0], eci); + error = validate_content_type_attribute(state, + attr->attrValues.list.array[0], eci); content_type_found = true; } else if (ARCS_EQUAL_OIDS(&attrType, oid_mda)) { if (message_digest_found) { - warnx("Multiple MessageDigests found."); + pr_err(state, "Multiple MessageDigests found."); goto illegal_attrType; } - error = validate_message_digest_attribute(attr->attrValues.list.array[0]); + error = validate_message_digest_attribute(state, + attr->attrValues.list.array[0]); message_digest_found = true; } else if (ARCS_EQUAL_OIDS(&attrType, oid_sta)) { if (signing_time_found) { - warnx("Multiple SigningTimes found."); + pr_err(state, "Multiple SigningTimes found."); goto illegal_attrType; } error = 0; /* No validations needed for now. */ @@ -142,7 +149,7 @@ validate_signed_attrs(struct SignerInfo *sinfo, EncapsulatedContentInfo_t *eci) } else if (ARCS_EQUAL_OIDS(&attrType, oid_bst)) { if (binary_signing_time_found) { - warnx("Multiple BinarySigningTimes found."); + pr_err(state, "Multiple BinarySigningTimes found."); goto illegal_attrType; } error = 0; /* No validations needed for now. */ @@ -150,7 +157,7 @@ validate_signed_attrs(struct SignerInfo *sinfo, EncapsulatedContentInfo_t *eci) } else { /* rfc6488#section-3.1.g */ - warnx("Illegal attrType OID in SignerInfo."); + pr_err(state, "Illegal attrType OID in SignerInfo."); goto illegal_attrType; } @@ -162,11 +169,11 @@ validate_signed_attrs(struct SignerInfo *sinfo, EncapsulatedContentInfo_t *eci) /* rfc6488#section-3.1.f */ if (!content_type_found) { - warnx("SignerInfo lacks a ContentType attribute."); + pr_err(state, "SignerInfo lacks a ContentType attribute."); return -EINVAL; } if (!message_digest_found) { - warnx("SignerInfo lacks a MessageDigest attribute."); + pr_err(state, "SignerInfo lacks a MessageDigest attribute."); return -EINVAL; } @@ -178,7 +185,7 @@ illegal_attrType: } static int -validate(struct SignedData *sdata) +validate(struct validation *state, struct SignedData *sdata) { struct SignerInfo *sinfo; bool is_digest; @@ -186,7 +193,8 @@ validate(struct SignedData *sdata) /* rfc6488#section-2.1 */ if (sdata->signerInfos.list.count != 1) { - warnx("The SignedData's SignerInfo set is supposed to have only one element. (%d given.)", + pr_err(state, + "The SignedData's SignerInfo set is supposed to have only one element. (%d given.)", sdata->signerInfos.list.count); return -EINVAL; } @@ -194,7 +202,8 @@ validate(struct SignedData *sdata) /* rfc6488#section-2.1.1 */ /* rfc6488#section-3.1.b */ if (sdata->version != 3) { - warnx("The SignedData version is only allowed to be 3. (Was %ld.)", + pr_err(state, + "The SignedData version is only allowed to be 3. (Was %ld.)", sdata->version); return -EINVAL; } @@ -202,7 +211,8 @@ validate(struct SignedData *sdata) /* rfc6488#section-2.1.2 */ /* rfc6488#section-3.1.j 1/2 */ if (sdata->digestAlgorithms.list.count != 1) { - warnx("The SignedData's digestAlgorithms set is supposed to have only one element. (%d given.)", + pr_err(state, + "The SignedData's digestAlgorithms set is supposed to have only one element. (%d given.)", sdata->digestAlgorithms.list.count); return -EINVAL; } @@ -218,7 +228,8 @@ validate(struct SignedData *sdata) if (error) return error; if (!is_digest) { - warnx("The SignedData's digestAlgorithm OID is not listed in RFC 5754."); + pr_err(state, + "The SignedData's digestAlgorithm OID is not listed in RFC 5754."); return -EINVAL; } @@ -228,12 +239,13 @@ validate(struct SignedData *sdata) /* rfc6488#section-2.1.4 */ /* rfc6488#section-3.1.c TODO missing half of the requirement. */ if (sdata->certificates == NULL) { - warnx("The SignedData does not contain certificates."); + pr_err(state, "The SignedData does not contain certificates."); return -EINVAL; } if (sdata->certificates->list.count != 1) { - warnx("The SignedData contains %d certificates, one expected.", + pr_err(state, + "The SignedData contains %d certificates, one expected.", sdata->certificates->list.count); return -EINVAL; } @@ -241,7 +253,7 @@ validate(struct SignedData *sdata) /* rfc6488#section-2.1.5 */ /* rfc6488#section-3.1.d */ if (sdata->crls != NULL && sdata->crls->list.count > 0) { - warnx("The SignedData contains at least one crls."); + pr_err(state, "The SignedData contains at least one crls."); return -EINVAL; } @@ -249,11 +261,12 @@ validate(struct SignedData *sdata) /* rfc6488#section-3.1.e */ sinfo = sdata->signerInfos.list.array[0]; if (sinfo == NULL) { - warnx("The SignerInfo object is NULL."); + pr_err(state, "The SignerInfo object is NULL."); return -EINVAL; } if (sinfo->version != 3) { - warnx("The SignerInfo version is only allowed to be 3. (Was %ld.)", + pr_err(state, + "The SignerInfo version is only allowed to be 3. (Was %ld.)", sinfo->version); return -EINVAL; } @@ -270,12 +283,13 @@ validate(struct SignedData *sdata) if (error) return error; if (!is_digest) { - warnx("The SignerInfo digestAlgorithm OID is not listed in RFC 5754."); + pr_err(state, + "The SignerInfo digestAlgorithm OID is not listed in RFC 5754."); return -EINVAL; } /* rfc6488#section-2.1.6.4 */ - error = validate_signed_attrs(sinfo, &sdata->encapContentInfo); + error = validate_signed_attrs(state, sinfo, &sdata->encapContentInfo); if (error) return error; @@ -303,7 +317,7 @@ validate(struct SignedData *sdata) /* rfc6488#section-2.1.6.7 */ /* rfc6488#section-3.1.i */ if (sinfo->unsignedAttrs != NULL && sinfo->unsignedAttrs->list.count > 0) { - warnx("SignerInfo has at least one unsignedAttr."); + pr_err(state, "SignerInfo has at least one unsignedAttr."); return -EINVAL; } @@ -315,17 +329,19 @@ validate(struct SignedData *sdata) } int -signed_data_decode(ANY_t *coded, struct SignedData **result) +signed_data_decode(struct validation *state, ANY_t *coded, + struct SignedData **result) { struct SignedData *sdata; int error; /* rfc6488#section-3.1.l TODO this is BER, not guaranteed to be DER. */ - error = asn1_decode_any(coded, &asn_DEF_SignedData, (void **) &sdata); + error = asn1_decode_any(state, coded, &asn_DEF_SignedData, + (void **) &sdata); if (error) return error; - error = validate(sdata); + error = validate(state, sdata); if (error) { signed_data_free(sdata); return error; @@ -343,7 +359,8 @@ signed_data_free(struct SignedData *sdata) /* Caller must free *@result. */ int -get_content_type_attr(struct SignedData *sdata, OBJECT_IDENTIFIER_t **result) +get_content_type_attr(struct validation *state, struct SignedData *sdata, + OBJECT_IDENTIFIER_t **result) { struct SignedAttributes *signedAttrs; struct CMSAttribute *attr; @@ -377,8 +394,10 @@ get_content_type_attr(struct SignedData *sdata, OBJECT_IDENTIFIER_t **result) return -EINVAL; if (attr->attrValues.list.array[0] == NULL) return -EINVAL; - return asn1_decode_any(attr->attrValues.list.array[0], - &asn_DEF_OBJECT_IDENTIFIER, (void **) result); + return asn1_decode_any(state, + attr->attrValues.list.array[0], + &asn_DEF_OBJECT_IDENTIFIER, + (void **) result); } } diff --git a/src/asn1/signed_data.h b/src/asn1/signed_data.h index f35d2c6b..6d21361e 100644 --- a/src/asn1/signed_data.h +++ b/src/asn1/signed_data.h @@ -4,10 +4,12 @@ /* Some wrappers for libcmscodec's SignedData. */ #include +#include "state.h" -int signed_data_decode(ANY_t *, struct SignedData **); +int signed_data_decode(struct validation *, ANY_t *, struct SignedData **); void signed_data_free(struct SignedData *); -int get_content_type_attr(struct SignedData *, OBJECT_IDENTIFIER_t **); +int get_content_type_attr(struct validation *, struct SignedData *, + OBJECT_IDENTIFIER_t **); #endif /* SRC_SIGNED_DATA_H_ */ diff --git a/src/common.c b/src/common.c index 49387819..9dbd2e62 100644 --- a/src/common.c +++ b/src/common.c @@ -2,42 +2,14 @@ #include #include - -#define INDENT_MAX 10 -static unsigned int indent; +#include +#include +#include "log.h" char const *repository; int NID_rpkiManifest; int NID_rpkiNotify; -void -pr_indent(void) -{ - unsigned int __indent = indent; - unsigned int i; - -// if (__indent > INDENT_MAX) -// __indent = INDENT_MAX; - - for (i = 0; i < __indent; i++) - printf(" "); -} - -void -pr_add_indent(void) -{ - indent++; -} - -void -pr_rm_indent(void) -{ - if (indent > 0) - indent--; - else - warnx("Programming error: Too many pr_sub_indent()s."); -} - bool file_has_extension(char const *file_name, char const *extension) { @@ -55,7 +27,7 @@ file_has_extension(char const *file_name, char const *extension) * You need to free the result once you're done. */ int -uri_g2l(char const *guri, char **result) +uri_g2l(struct validation *state, char const *guri, char **result) { char const *const PREFIX = "rsync://"; char *luri; @@ -66,7 +38,13 @@ uri_g2l(char const *guri, char **result) prefix_len = strlen(PREFIX); if (strncmp(PREFIX, guri, prefix_len) != 0) { - warnx("Global URI %s does not begin with '%s'.", guri, PREFIX); + if (state == NULL) { + warnx("Global URI %s does not begin with '%s'.", guri, + PREFIX); + } else { + pr_err(state, "Global URI %s does not begin with '%s'.", + guri, PREFIX); + } return -EINVAL; } @@ -91,14 +69,14 @@ uri_g2l(char const *guri, char **result) } int -gn2uri(GENERAL_NAME *gn, char const **uri) +gn2uri(struct validation *state, GENERAL_NAME *gn, char const **uri) { ASN1_STRING *asn_string; int type; asn_string = GENERAL_NAME_get0_value(gn, &type); if (type != GEN_URI) { - pr_debug("Unknown GENERAL_NAME type: %d", type); + pr_debug(state, "Unknown GENERAL_NAME type: %d", type); return -ENOTSUPPORTED; } diff --git a/src/common.h b/src/common.h index d37cb306..409eef95 100644 --- a/src/common.h +++ b/src/common.h @@ -2,9 +2,8 @@ #define SRC_RTR_COMMON_H_ #include -#include -#include #include +#include "state.h" /* "I think that this is not supposed to be implemented." */ #define ENOTSUPPORTED 3172 @@ -17,62 +16,8 @@ extern int NID_rpkiNotify; #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0])) -#define warnxerror0(error, msg) \ - warnx(msg ": %s", strerror(error)) -#define warnxerrno0(msg) \ - warnxerror0(errno, msg) -#define warnxerror(error, msg, ...) \ - warnx(msg ": %s", ##__VA_ARGS__, strerror(error)) -#define warnxerrno(msg, ...) \ - warnxerror(errno, msg, ##__VA_ARGS__) - -#ifdef DEBUG - -#define pr_debug(msg, ...) { \ - printf("DBG: "); \ - pr_indent(); \ - printf(msg "\n", ##__VA_ARGS__); \ -} -#define pr_debug_add(msg, ...) { \ - pr_debug(msg, ##__VA_ARGS__); \ - pr_add_indent(); \ -} -#define pr_debug_rm(msg, ...) { \ - pr_rm_indent(); \ - pr_debug(msg, ##__VA_ARGS__); \ -} - -#define pr_debug0(msg) { \ - printf("DBG: "); \ - pr_indent(); \ - printf(msg "\n"); \ -} -#define pr_debug0_add(msg) { \ - pr_debug0(msg); \ - pr_add_indent(); \ -} -#define pr_debug0_rm(msg) { \ - pr_rm_indent(); \ - pr_debug0(msg); \ -} - -#else /* #ifdef DEBUG */ - -#define pr_debug(msg, ...) -#define pr_debug_add(msg, ...) -#define pr_debug_rm(msg, ...) -#define pr_debug0(msg) -#define pr_debug0_add(msg) -#define pr_debug0_rm(msg) - -#endif /* #ifdef DEBUG */ - -void pr_indent(void); -void pr_add_indent(void); -void pr_rm_indent(void); - bool file_has_extension(char const *, char const *); -int uri_g2l(char const *guri, char **result); -int gn2uri(GENERAL_NAME *, char const **); +int uri_g2l(struct validation *, char const *, char **); +int gn2uri(struct validation *, GENERAL_NAME *, char const **); #endif /* SRC_RTR_COMMON_H_ */ diff --git a/src/file.c b/src/file.c index 3f3047b8..310b010b 100644 --- a/src/file.c +++ b/src/file.c @@ -1,10 +1,10 @@ #include "file.h" -#include #include #include #include #include "common.h" +#include "log.h" /* * Will also rewind the file as a side effect. @@ -21,7 +21,8 @@ get_file_size(FILE *file, long int *size) } int -file_load(const char *file_name, struct file_contents *fc) +file_load(struct validation *state, const char *file_name, + struct file_contents *fc) { FILE *file; long int file_size; @@ -30,14 +31,15 @@ file_load(const char *file_name, struct file_contents *fc) file = fopen(file_name, "rb"); if (file == NULL) { - warnxerrno("Could not open file '%s'", file_name); - return errno; + return pr_errno(state, errno, "Could not open file '%s'", + file_name); } /* TODO if @file is a directory, this returns a very large integer. */ error = get_file_size(file, &file_size); if (error) { - warnxerror0(error, "Could not compute file size"); + pr_errno(state, error, "Could not compute the file size of %s", + file_name); fclose(file); return error; } @@ -45,7 +47,7 @@ file_load(const char *file_name, struct file_contents *fc) fc->buffer_size = file_size; fc->buffer = malloc(fc->buffer_size); if (fc->buffer == NULL) { - warnx("Out of memory."); + pr_err(state, "Out of memory."); fclose(file); return -ENOMEM; } @@ -59,8 +61,9 @@ file_load(const char *file_name, struct file_contents *fc) * code. It literally doesn't say how to obtain the * error code. */ - warnx("File read error. The errcode is presumably %d. (%s)", - error, strerror(error)); + pr_errno(state, error, + "File reading error. Error message (apparently)", + file_name); free(fc->buffer); fclose(file); return error; @@ -70,9 +73,9 @@ file_load(const char *file_name, struct file_contents *fc) * As far as I can tell from the man page, feof() cannot return * less bytes that requested like read() does. */ - warnx("Likely programming error: fread() < file size"); - warnx("fr:%zu bs:%zu EOF:%d", fread_result, fc->buffer_size, - feof(file)); + pr_err(state, "Likely programming error: fread() < file size"); + pr_err(state, "fr:%zu bs:%zu EOF:%d", fread_result, + fc->buffer_size, feof(file)); free(fc->buffer); fclose(file); return -EINVAL; diff --git a/src/file.h b/src/file.h index 3ee0aa8c..3e412390 100644 --- a/src/file.h +++ b/src/file.h @@ -2,6 +2,7 @@ #define SRC_FILE_H_ #include +#include "state.h" /* * The entire contents of the file, loaded into a buffer. @@ -13,7 +14,7 @@ struct file_contents { size_t buffer_size; }; -int file_load(const char *file_name, struct file_contents *fc); -void file_free(struct file_contents *fc); +int file_load(struct validation *, const char *, struct file_contents *); +void file_free(struct file_contents *); #endif /* SRC_FILE_H_ */ diff --git a/src/line_file.c b/src/line_file.c index e723c1e9..ea2aee7a 100644 --- a/src/line_file.c +++ b/src/line_file.c @@ -106,7 +106,7 @@ lfile_read(struct line_file *lfile, char **result) if (feof(lfile->file)) return 0; warnx("Supposedly unreachable code reached. ferror:%d feof:%d", - ferror(lfile->file), feof(lfile->file)); + ferror(lfile->file), feof(lfile->file)); return -EINVAL; } @@ -120,7 +120,7 @@ lfile_read(struct line_file *lfile, char **result) for (i = 0; i < len; i++) { if (string[i] == '\0') { warnx("File '%s' has an illegal null character in its body. Please remove it.", - lfile_name(lfile)); + lfile_name(lfile)); free(string); return -EINVAL; } diff --git a/src/log.c b/src/log.c new file mode 100644 index 00000000..7919d8d6 --- /dev/null +++ b/src/log.c @@ -0,0 +1,192 @@ +#include "log.h" + +#include +#include +#include + +#ifdef DEBUG + +#define INDENT_MAX 10 +static unsigned int indent; + +static void +pr_indent(BIO *stream) +{ + unsigned int __indent = indent; + unsigned int i; + +// if (__indent > INDENT_MAX) +// __indent = INDENT_MAX; + + for (i = 0; i < __indent; i++) + BIO_printf(stream, " "); +} + +static void +pr_add_indent(void) +{ + indent++; +} + +static void +pr_rm_indent(void) +{ + if (indent > 0) + indent--; + else + fprintf(stderr, "Programming error: Too many pr_rm_indent()s.\n"); +} + +static void +print_debug_prefix(BIO *bio) +{ + BIO_printf(bio, "DBG: "); + pr_indent(bio); +} + +#endif + +void +pr_debug(struct validation *state, const char *format, ...) +{ +#ifdef DEBUG + BIO *bio = validation_stdout(state); + va_list args; + + print_debug_prefix(bio); + + va_start(args, format); + BIO_vprintf(bio, format, args); + va_end(args); + BIO_printf(bio, "\n"); +#endif +} + +void +pr_debug_add(struct validation *state, const char *format, ...) +{ +#ifdef DEBUG + BIO *bio = validation_stdout(state); + va_list args; + + print_debug_prefix(bio); + + va_start(args, format); + BIO_vprintf(bio, format, args); + va_end(args); + BIO_printf(bio, "\n"); + + pr_add_indent(); +#endif +} + +void +pr_debug_rm(struct validation *state, const char *format, ...) +{ +#ifdef DEBUG + BIO *bio = validation_stdout(state); + va_list args; + + pr_rm_indent(); + + print_debug_prefix(bio); + + va_start(args, format); + BIO_vprintf(bio, format, args); + va_end(args); + BIO_printf(bio, "\n"); +#endif +} + +/** + * Always appends a newline at the end. + */ +void +pr_err(struct validation *state, const char *format, ...) +{ + BIO *bio = validation_stderr(state); + va_list args; + + va_start(args, format); + BIO_vprintf(bio, format, args); + va_end(args); + BIO_printf(bio, "\n"); +} + +/** + * @error fulfills two functions, both of which apply only if it's nonzero: + * + * - @error's corresponding generic error message will be appended to the print. + * - @error's value will be returned. This is for the sake of error code + * propagation. + * + * If @error is zero, no error message will be appended, and the function will + * return -EINVAL. (I acknowledge that this looks convoluted at first glance. + * The purpose is to ensure that this function will propagate an error code even + * if there is no error code originally.) + * + * Always appends a newline at the end. + */ +int +pr_errno(struct validation *state, int error, const char *format, ...) +{ + BIO *bio = validation_stderr(state); + va_list args; + + va_start(args, format); + BIO_vprintf(bio, format, args); + va_end(args); + + if (error) { + BIO_printf(bio, ": %s", strerror(error)); + } else { + /* We should assume that there WAS an error; go generic. */ + error = -EINVAL; + } + + BIO_printf(bio, "\n"); + + return error; +} + +/** + * This is like pr_err() and pr_errno(), except meant to log an error made + * during a libcrypto routine. + * + * This differs from usual printf-like functions: + * + * - It returns the last error code libcrypto threw, not bytes written. + * - It prints a newline. + * - Also prints the cryptolib's error message after a colon. + * (So don't include periods at the end of @format.) + * + * Always appends a newline at the end. + */ +int +crypto_err(struct validation *state, const char *format, ...) +{ + BIO *bio = validation_stderr(state); + va_list args; + int error; + + error = ERR_GET_REASON(ERR_peek_last_error()); + + va_start(args, format); + BIO_vprintf(bio, format, args); + va_end(args); + + if (error) { + BIO_printf(bio, ": "); + /* + * Reminder: This clears the error queue. + * BTW: The string format is pretty ugly. Maybe override this. + */ + ERR_print_errors(bio); + } else { + /* We should assume that there WAS an error; go generic. */ + error = -EINVAL; + } + + BIO_printf(bio, "\n"); + return error; +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 00000000..66970546 --- /dev/null +++ b/src/log.h @@ -0,0 +1,17 @@ +#ifndef SRC_LOG_H_ +#define SRC_LOG_H_ + +#include "state.h" + +void pr_debug(struct validation *, const char *, ...); +void pr_debug_add(struct validation *, const char *, ...); +void pr_debug_rm(struct validation *, const char *, ...); + +void pr_err(struct validation *, const char *, ...); +int pr_errno(struct validation *, int, const char *, ...); +int crypto_err(struct validation *, const char *, ...); + +#define PR_DEBUG(msg) \ + printf("%s:%d (%s()): " msg "\n", __FILE__, __LINE__, __func__) + +#endif /* SRC_LOG_H_ */ diff --git a/src/main2.c b/src/main.c similarity index 67% rename from src/main2.c rename to src/main.c index c3e1a2c3..467b742b 100644 --- a/src/main2.c +++ b/src/main.c @@ -5,6 +5,7 @@ #include "common.h" #include "debug.h" +#include "log.h" #include "object/certificate.h" #include "object/manifest.h" #include "object/tal.h" @@ -19,12 +20,12 @@ add_rpki_oids(void) NID_rpkiManifest = OBJ_create("1.3.6.1.5.5.7.48.10", "id-ad-rpkiManifest (RFC 6487)", "Resource Public Key Infrastructure (RPKI) manifest access method"); - pr_debug("rpkiManifest registered. Its nid is %d.", NID_rpkiManifest); + printf("rpkiManifest registered. Its nid is %d.\n", NID_rpkiManifest); NID_rpkiNotify = OBJ_create("1.3.6.1.5.5.7.48.13", "id-ad-rpkiNotify (RFC 8182)", /* TODO */ "Blah blah"); - pr_debug("rpkiNotify registered. Its nid is %d.", NID_rpkiNotify); + printf("rpkiNotify registered. Its nid is %d.\n", NID_rpkiNotify); } /** @@ -34,27 +35,35 @@ add_rpki_oids(void) static int handle_tal_uri(char const *uri) { + struct validation *state; char *cert_file; int error; - pr_debug_add("TAL URI %s {", uri); + error = uri_g2l(NULL, uri, &cert_file); + if (error) + return error; + + error = validation_create(&state, cert_file); + if (error) + goto end1; + + pr_debug_add(state, "TAL URI %s {", uri); if (!is_certificate(uri)) { - warnx("TAL file does not seem to point to a certificate."); - warnx("(Expected .cer file, got '%s')", uri); + pr_err(state, + "TAL file does not point to a certificate. (Expected .cer, got '%s')", + uri); error = -ENOTSUPPORTED; - goto end; + goto end2; } - error = uri_g2l(uri, &cert_file); - if (error) - goto end; + error = certificate_handle_extensions(state, validation_peek(state)); - error = handle_certificate(cert_file); +end2: + pr_debug_rm(state, "}"); + validation_destroy(state); +end1: free(cert_file); - -end: - pr_debug0_rm("}"); return error; } @@ -64,6 +73,8 @@ main(int argc, char **argv) struct tal *tal; int error; + print_stack_trace_on_segfault(); + if (argc < 3) { warnx("Repository path as first argument and TAL file as second argument, please."); return -EINVAL; diff --git a/src/object/certificate.c b/src/object/certificate.c index a09ee6e1..48be0316 100644 --- a/src/object/certificate.c +++ b/src/object/certificate.c @@ -1,12 +1,12 @@ #include "certificate.h" -#include #include #include #include #include #include "common.h" +#include "log.h" #include "manifest.h" #include "asn1/decode.h" @@ -22,37 +22,33 @@ bool is_certificate(char const *file_name) return file_has_extension(file_name, "cer"); } -static X509 * -load_certificate(BIO *bio_err, const char *file) +X509 * +certificate_load(struct validation *state, const char *file) { X509 *cert = NULL; BIO *bio; bio = BIO_new(BIO_s_file()); if (bio == NULL) { - BIO_printf(bio_err, "BIO_new(BIO_s_file()) returned NULL.\n"); - ERR_print_errors(bio_err); + crypto_err(state, "BIO_new(BIO_s_file()) returned NULL"); goto end; } if (BIO_read_filename(bio, file) <= 0) { - BIO_printf(bio_err, "Error reading certificate '%s'.\n", file); - ERR_print_errors(bio_err); + crypto_err(state, "Error reading certificate '%s'", file); goto end; } cert = d2i_X509_bio(bio, NULL); - if (cert == NULL) { - BIO_printf(bio_err, "Error parsing certificate '%s'.\n", file); - ERR_print_errors(bio_err); - } + if (cert == NULL) + crypto_err(state, "Error parsing certificate '%s'", file); end: BIO_free(bio); return cert; } -static int -handle_extensions(X509 *cert) +int +certificate_handle_extensions(struct validation *state, X509 *cert) { SIGNATURE_INFO_ACCESS *sia; ACCESS_DESCRIPTION *ad; @@ -63,22 +59,23 @@ handle_extensions(X509 *cert) int error; sia = X509_get_ext_d2i(cert, NID_sinfo_access, &error, NULL); - if (!sia) { + if (sia == NULL) { switch (error) { case -1: - warnx("The certificate lacks an SIA extension."); + pr_err(state, "Certificate lacks an SIA extension."); return -ESRCH; case -2: - warnx("The certificate has more than one SIA extension."); + pr_err(state, "Certificate has more than one SIA extension."); return -EINVAL; default: - warnx("X509_get_ext_d2i() returned unknown error code %d.", + pr_err(state, + "X509_get_ext_d2i() returned unknown error code %d.", error); return -EINVAL; } } - pr_debug0_add("SIA {"); + pr_debug_add(state, "SIA {"); error = 0; for (i = 0; i < sk_ACCESS_DESCRIPTION_num(sia); i++) { @@ -86,70 +83,63 @@ handle_extensions(X509 *cert) nid = OBJ_obj2nid(ad->method); if (nid == NID_rpkiManifest) { - error = gn2uri(ad->location, &uri); + error = gn2uri(state, ad->location, &uri); if (error) goto end; - error = uri_g2l(uri, &luri); + error = uri_g2l(state, uri, &luri); if (error) goto end; - error = handle_manifest(luri); + error = handle_manifest(state, luri); free(luri); if (error) goto end; } else if (nid == NID_rpkiNotify) { /* TODO Another fucking RFC... */ - pr_debug0("Unimplemented thingy: rpkiNotify"); + pr_debug(state, "Unimplemented thingy: rpkiNotify"); } else if (nid == NID_caRepository) { - error = gn2uri(ad->location, &uri); + error = gn2uri(state, ad->location, &uri); if (error) goto end; /* TODO no idea what to do with this. */ - pr_debug("CA Repository URI: %s", uri); + pr_debug(state, "CA Repository URI: %s", uri); } else { - pr_debug("Unknown NID: %d", nid); + pr_debug(state, "Unknown NID: %d", nid); goto end; } } end: AUTHORITY_INFO_ACCESS_free(sia); - pr_debug0_rm("}"); + pr_debug_rm(state, "}"); return error; } int -handle_certificate(char const *file) +certificate_handle(struct validation *state, char const *file) { X509 *certificate; - BIO *bio_err; int error; - pr_debug0_add("Certificate {"); - - bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); - if (bio_err == NULL) { - warnx("Failed to initialise bio_err."); - /* TODO get the right one through the ERR_* functions. */ - error = -ENOMEM; - goto abort1; - } + pr_debug_add(state, "Certificate {"); - certificate = load_certificate(bio_err, file); - if (!certificate) { + certificate = certificate_load(state, file); + if (certificate == NULL) { /* TODO get the right one through the ERR_* functions. */ error = -EINVAL; - goto abort2; + goto end; } - error = handle_extensions(certificate); - X509_free(certificate); + error = validation_push(state, certificate); + if (error) + goto end; + + error = certificate_handle_extensions(state, certificate); + validation_pop(state); -abort2: - BIO_free_all(bio_err); -abort1: - pr_debug0_rm("}"); +end: + pr_debug_rm(state, "}"); return error; } diff --git a/src/object/certificate.h b/src/object/certificate.h index 9e89fc24..fc75c83c 100644 --- a/src/object/certificate.h +++ b/src/object/certificate.h @@ -2,8 +2,11 @@ #define SRC_OBJECT_CERTIFICATE_H_ #include +#include "state.h" bool is_certificate(char const *); -int handle_certificate(char const *); +X509 *certificate_load(struct validation *, const char *); +int certificate_handle(struct validation *, char const *); +int certificate_handle_extensions(struct validation *, X509 *); #endif /* SRC_OBJECT_CERTIFICATE_H_ */ diff --git a/src/object/crl.c b/src/object/crl.c new file mode 100644 index 00000000..a86c30c1 --- /dev/null +++ b/src/object/crl.c @@ -0,0 +1,164 @@ +#include "crl.h" + +#include +#include +#include +#include + +#include "common.h" +#include "log.h" +#include "manifest.h" +#include "asn1/decode.h" + +bool is_crl(char const *file_name) +{ + return file_has_extension(file_name, "crl"); +} + +static X509_CRL * +load_crl(struct validation *state, const char *file) +{ + X509_CRL *crl = NULL; + BIO *bio; + + bio = BIO_new(BIO_s_file()); + if (bio == NULL) { + crypto_err(state, "BIO_new(BIO_s_file()) returned NULL"); + goto end; + } + if (BIO_read_filename(bio, file) <= 0) { + crypto_err(state, "Error reading CRL '%s'", file); + goto end; + } + + crl = d2i_X509_CRL_bio(bio, NULL); + if (crl == NULL) + crypto_err(state, "Error parsing CRL '%s'", file); + +end: + BIO_free(bio); + return crl; +} + +static int +handle_authority_key_identifier(struct validation *state, X509_EXTENSION *ext) +{ + /* TODO */ + pr_debug(state, "Unimplemented still: Authority Key Identifier"); + /* AUTHORITY_KEYID *aki = X509V3_EXT_d2i(ext); */ + /* AUTHORITY_KEYID_free(aki); */ + return 0; +} + +static int +handle_revoked(struct validation *state, X509_REVOKED *revoked) +{ + const ASN1_INTEGER *serialNumber; + const ASN1_TIME *revocationDate; + + serialNumber = X509_REVOKED_get0_serialNumber(revoked); + revocationDate = X509_REVOKED_get0_revocationDate(revoked); + + if (serialNumber == NULL) { + pr_err(state, "Revoked entry's serial number is NULL."); + return -EINVAL; + } + if (revocationDate == NULL) { + pr_err(state, "Revoked entry's revocation date is NULL."); + return -EINVAL; + } + if (X509_REVOKED_get0_extensions(revoked) != NULL) { + pr_err(state, "Revoked entry's extension list is not NULL."); + return -EINVAL; + } + + pr_debug(state, "Revoked:%ld", ASN1_INTEGER_get(serialNumber)); +// ASN1_TIME_print(bio_err, revocationDate); +// printf("\n"); + return 0; +} + +static int +handle_revoked_list(struct validation *state, X509_CRL *crl) +{ + STACK_OF(X509_REVOKED) *list; + unsigned int i; + int error; + + list = X509_CRL_get_REVOKED(crl); + for (i = 0; i < sk_X509_REVOKED_num(list); i++) { + error = handle_revoked(state, sk_X509_REVOKED_value(list, i)); + if (error) + return error; + } + + return 0; +} + +static int +handle_crl_number(struct validation *state, X509_EXTENSION *ext) +{ + pr_debug(state, "Unimplemented still: CRL Number"); /* TODO */ + return 0; +} + +static int +handle_extensions(struct validation *state, X509_CRL *crl) +{ + const STACK_OF(X509_EXTENSION) *exts; + X509_EXTENSION *ext; + unsigned int i; + int nid; + int error; + + exts = X509_CRL_get0_extensions(crl); + for (i = 0; i < sk_X509_EXTENSION_num(exts); i++) { + ext = sk_X509_EXTENSION_value(exts, i); + nid = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); + + switch (nid) { + case NID_authority_key_identifier: + error = handle_authority_key_identifier(state, ext); + break; + case NID_crl_number: + error = handle_crl_number(state, ext); + break; + default: + pr_err(state, "CRL has illegal extension: NID %d", nid); + return -EINVAL; + } + + if (error) + return error; + } + + return 0; +} + +int +handle_crl(struct validation *state, char const *file) +{ + X509_CRL *crl; + int error; + + pr_debug_add(state, "CRL {"); + + crl = load_crl(state, file); + if (!crl) { + /* TODO get the right one through the ERR_* functions. */ + error = -EINVAL; + goto abort2; + } + + error = handle_revoked_list(state, crl); + if (error) + goto abort3; + + error = handle_extensions(state, crl); + +abort3: + X509_CRL_free(crl); +abort2: + pr_debug_rm(state, "}"); + return error; +} diff --git a/src/object/crl.h b/src/object/crl.h new file mode 100644 index 00000000..b1a99bf3 --- /dev/null +++ b/src/object/crl.h @@ -0,0 +1,10 @@ +#ifndef SRC_OBJECT_CRL_H_ +#define SRC_OBJECT_CRL_H_ + +#include +#include "state.h" + +bool is_crl(char const *); +int handle_crl(struct validation *, char const *); + +#endif /* SRC_OBJECT_CRL_H_ */ diff --git a/src/object/manifest.c b/src/object/manifest.c index 8bc79101..87b90748 100644 --- a/src/object/manifest.c +++ b/src/object/manifest.c @@ -4,11 +4,14 @@ #include #include "common.h" +#include "log.h" #include "asn1/oid.h" #include "object/certificate.h" +#include "object/crl.h" #include "object/roa.h" #include "object/signed_object.h" +/* TODO not being called right now. */ bool is_manifest(char const *file_name) { @@ -153,39 +156,44 @@ succeed: } static int -handle_file(char const *mft, IA5String_t *string) +handle_file(struct validation *state, char const *mft, IA5String_t *string) { char *luri; int error; /* TODO Treating string->buf as a C string is probably not correct. */ - pr_debug_add("File %s {", string->buf); +// pr_debug_add(state, "File %s {", string->buf); error = get_relative_file(mft, (char const *) string->buf, &luri); if (error) goto end; + pr_debug_add(state, "File %s {", luri); + if (is_certificate(luri)) - error = handle_certificate(luri); + error = certificate_handle(state, luri); + else if (is_crl(luri)) + error = handle_crl(state, luri); else if (is_roa(luri)) - error = handle_roa(luri); + error = handle_roa(state, luri); else - pr_debug0("Unhandled file type."); + pr_debug(state, "Unhandled file type."); free(luri); end: - pr_debug0_rm("}"); + pr_debug_rm(state, "}"); return error; } static int -__handle_manifest(char const *mft, struct Manifest *manifest) +__handle_manifest(struct validation *state, char const *mft, + struct Manifest *manifest) { int i; int error; for (i = 0; i < manifest->fileList.list.count; i++) { - error = handle_file(mft, + error = handle_file(state, mft, &manifest->fileList.list.array[i]->file); if (error) return error; @@ -195,21 +203,21 @@ __handle_manifest(char const *mft, struct Manifest *manifest) } int -handle_manifest(char const *file_path) +handle_manifest(struct validation *state, char const *file_path) { static OID oid = OID_MANIFEST; struct oid_arcs arcs = OID2ARCS(oid); struct Manifest *manifest; int error; - error = signed_object_decode(file_path, &asn_DEF_Manifest, &arcs, + error = signed_object_decode(state, file_path, &asn_DEF_Manifest, &arcs, (void **) &manifest); if (error) return error; error = validate_manifest(manifest); if (!error) - error = __handle_manifest(file_path, manifest); + error = __handle_manifest(state, file_path, manifest); ASN_STRUCT_FREE(asn_DEF_Manifest, manifest); return error; diff --git a/src/object/manifest.h b/src/object/manifest.h index 00156fa4..e957c4c2 100644 --- a/src/object/manifest.h +++ b/src/object/manifest.h @@ -2,8 +2,9 @@ #define SRC_OBJECT_MANIFEST_H_ #include +#include "state.h" bool is_manifest(char const *); -int handle_manifest(char const *); +int handle_manifest(struct validation *, char const *); #endif /* SRC_OBJECT_MANIFEST_H_ */ diff --git a/src/object/roa.c b/src/object/roa.c index c2a4a7fe..9c6e5b13 100644 --- a/src/object/roa.c +++ b/src/object/roa.c @@ -1,11 +1,11 @@ #include "object/roa.h" -#include #include #include #include #include "common.h" +#include "log.h" #include "asn1/oid.h" #include "object/signed_object.h" @@ -15,11 +15,11 @@ bool is_roa(char const *file_name) } static int -validate_roa(struct RouteOriginAttestation *roa) +validate_roa(struct validation *state, struct RouteOriginAttestation *roa) { /* rfc6482#section-3.1 */ if (roa->version != 0) { - warnx("ROA's version (%ld) is not zero.", roa->version); + pr_err(state, "ROA's version (%ld) is nonzero.", roa->version); return -EINVAL; } @@ -31,7 +31,8 @@ validate_roa(struct RouteOriginAttestation *roa) } static int -print_addr(long asn, uint8_t family, struct ROAIPAddress *roa_addr) +print_addr(struct validation *state, long asn, uint8_t family, + struct ROAIPAddress *roa_addr) { union { struct in6_addr ip6; @@ -43,7 +44,6 @@ print_addr(long asn, uint8_t family, struct ROAIPAddress *roa_addr) } str; int prefix_len; const char *str2; - int error; switch (family) { case 1: @@ -53,7 +53,7 @@ print_addr(long asn, uint8_t family, struct ROAIPAddress *roa_addr) family = AF_INET6; break; default: - warnx("Unknown family value: %u", family); + pr_err(state, "Unknown family value: %u", family); return -EINVAL; } @@ -68,11 +68,8 @@ print_addr(long asn, uint8_t family, struct ROAIPAddress *roa_addr) memset(&addr, 0, sizeof(addr)); memcpy(&addr, roa_addr->address.buf, roa_addr->address.size); str2 = inet_ntop(family, &addr, str.ip6, sizeof(str)); - if (str2 == NULL) { - error = errno; - warnxerrno0("Could not parse IP address"); - return error; - } + if (str2 == NULL) + return pr_errno(state, errno, "Cannot parse IP address"); prefix_len = 8 * roa_addr->address.size - roa_addr->address.bits_unused; @@ -88,7 +85,7 @@ print_addr(long asn, uint8_t family, struct ROAIPAddress *roa_addr) } static int -__handle_roa(struct RouteOriginAttestation *roa) +__handle_roa(struct validation *state, struct RouteOriginAttestation *roa) { struct ROAIPAddressFamily *block; int b; @@ -111,7 +108,7 @@ __handle_roa(struct RouteOriginAttestation *roa) if (block->addresses.list.array == NULL) return -EINVAL; for (a = 0; a < block->addresses.list.count; a++) { - error = print_addr(roa->asID, + error = print_addr(state, roa->asID, block->addressFamily.buf[1], block->addresses.list.array[a]); if (error) @@ -122,21 +119,21 @@ __handle_roa(struct RouteOriginAttestation *roa) return 0; } -int handle_roa(char const *file) +int handle_roa(struct validation *state, char const *file) { static OID oid = OID_ROA; struct oid_arcs arcs = OID2ARCS(oid); struct RouteOriginAttestation *roa; int error; - error = signed_object_decode(file, &asn_DEF_RouteOriginAttestation, - &arcs, (void **) &roa); + error = signed_object_decode(state, file, + &asn_DEF_RouteOriginAttestation, &arcs, (void **) &roa); if (error) return error; - error = validate_roa(roa); + error = validate_roa(state, roa); if (!error) - error = __handle_roa(roa); + error = __handle_roa(state, roa); ASN_STRUCT_FREE(asn_DEF_RouteOriginAttestation, roa); return error; diff --git a/src/object/roa.h b/src/object/roa.h index 9795c397..a2efda47 100644 --- a/src/object/roa.h +++ b/src/object/roa.h @@ -2,8 +2,9 @@ #define SRC_OBJECT_ROA_H_ #include +#include "state.h" bool is_roa(char const *); -int handle_roa(char const *); +int handle_roa(struct validation *, char const *); #endif /* SRC_OBJECT_ROA_H_ */ diff --git a/src/object/signed_object.c b/src/object/signed_object.c index 3fe3062d..d162e767 100644 --- a/src/object/signed_object.c +++ b/src/object/signed_object.c @@ -1,13 +1,14 @@ #include "signed_object.h" -#include #include +#include "log.h" #include "asn1/content_info.h" #include "asn1/decode.h" #include "asn1/signed_data.h" static int -validate_eContentType(struct SignedData *sdata, +validate_eContentType(struct validation *state, + struct SignedData *sdata, asn_TYPE_descriptor_t const *descriptor, struct oid_arcs const *oid) { @@ -21,7 +22,8 @@ validate_eContentType(struct SignedData *sdata, equals = arcs_equal(&arcs, oid); free_arcs(&arcs); if (!equals) { - warnx("SignedObject's encapContentInfo lacks the OID of a %s.", + pr_err(state, + "SignedObject's encapContentInfo lacks the OID of a %s.", descriptor->name); return -EINVAL; } @@ -30,7 +32,8 @@ validate_eContentType(struct SignedData *sdata, } static int -validate_content_type(struct SignedData *sdata, +validate_content_type(struct validation *state, + struct SignedData *sdata, asn_TYPE_descriptor_t const *descriptor, struct oid_arcs const *oid) { @@ -39,7 +42,7 @@ validate_content_type(struct SignedData *sdata, bool equals; int error; - error = get_content_type_attr(sdata, &ctype); + error = get_content_type_attr(state, sdata, &ctype); if (error) return error; error = oid2arcs(ctype, &arcs); @@ -49,7 +52,8 @@ validate_content_type(struct SignedData *sdata, equals = arcs_equal(&arcs, oid); free_arcs(&arcs); if (!equals) { - warnx("SignedObject's content type attribute lacks the OID of a %s.", + pr_err(state, + "SignedObject's content type attribute lacks the OID of a %s.", descriptor->name); return -EINVAL; } @@ -58,7 +62,8 @@ validate_content_type(struct SignedData *sdata, } int -signed_object_decode(char const *file, +signed_object_decode(struct validation *state, + char const *file, asn_TYPE_descriptor_t const *descriptor, struct oid_arcs const *oid, void **result) @@ -67,29 +72,29 @@ signed_object_decode(char const *file, struct SignedData *sdata; int error; - error = content_info_load(file, &cinfo); + error = content_info_load(state, file, &cinfo); if (error) goto end1; - error = signed_data_decode(&cinfo->content, &sdata); + error = signed_data_decode(state, &cinfo->content, &sdata); if (error) goto end2; /* rfc6482#section-2 */ /* rfc6486#section-4.1 */ /* rfc6486#section-4.4.1 */ - error = validate_eContentType(sdata, descriptor, oid); + error = validate_eContentType(state, sdata, descriptor, oid); if (error) goto end3; /* rfc6482#section-2 */ /* rfc6486#section-4.3 */ - error = validate_content_type(sdata, descriptor, oid); + error = validate_content_type(state, sdata, descriptor, oid); if (error) goto end3; - error = asn1_decode_octet_string(sdata->encapContentInfo.eContent, - descriptor, result); + error = asn1_decode_octet_string(state, + sdata->encapContentInfo.eContent, descriptor, result); end3: signed_data_free(sdata); end2: content_info_free(cinfo); diff --git a/src/object/signed_object.h b/src/object/signed_object.h index 9311d1f6..7bc49088 100644 --- a/src/object/signed_object.h +++ b/src/object/signed_object.h @@ -3,7 +3,7 @@ #include "asn1/oid.h" -int signed_object_decode(char const *, asn_TYPE_descriptor_t const *, - struct oid_arcs const *, void **); +int signed_object_decode(struct validation *, char const *, + asn_TYPE_descriptor_t const *, struct oid_arcs const *, void **); #endif /* SRC_OBJECT_SIGNED_OBJECT_H_ */ diff --git a/src/state.c b/src/state.c new file mode 100644 index 00000000..3485db56 --- /dev/null +++ b/src/state.c @@ -0,0 +1,255 @@ +#include "state.h" + +#include +#include +#include +#include +#include "common.h" +#include "log.h" +#include "object/certificate.h" + +/** + * The current state of the validation cycle. + * + * It is one of the core objects in this project. Every time a trust anchor + * triggers a validation cycle, the validator creates one of these objects and + * uses it to traverse the tree and keep track of validated data. + */ +struct validation { + /** + * Encapsulated standard output. + * Needed because the crypto library won't write to stdout directly. + */ + BIO *out; + /** + * Encapsulated standard error. + * Needed because the crypto library won't write to stderr directly. + */ + BIO *err; + + /** https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_load_locations.html */ + X509_STORE *store; + + /** Certificates we've already validated. */ + STACK_OF(X509) *trusted; +}; + +/* + * It appears that this function is called by LibreSSL whenever it finds an + * error while validating. + * It is expected to return "okay" status: Nonzero if the error should be + * ignored, zero if the error is grounds to abort the validation. + * + * Note to myself: During my tests, this function was called in + * X509_verify_cert(ctx) -> check_chain_extensions(0, ctx), + * and then twice again in + * X509_verify_cert(ctx) -> internal_verify(1, ctx). + * + * Regarding the ok argument: I'm not 100% sure that I get it; I don't + * understand why this function would be called with ok = 1. + * http://openssl.cs.utah.edu/docs/crypto/X509_STORE_CTX_set_verify_cb.html + * The logic I implemented is the same as the second example: Always ignore the + * error that's troubling the library, otherwise try to be as unintrusive as + * possible. + */ +static int +cb(int ok, X509_STORE_CTX *ctx) +{ + int error; + + /* + * We need to handle two new critical extensions (IP Resources and ASN + * Resources), so unknown critical extensions are fine as far as + * LibreSSL is concerned. + * Unfortunately, LibreSSL has no way of telling us *which* is the + * unknown critical extension, but since RPKI defines its own set of + * valid extensions, we'll have to figure it out later anyway. + */ + error = X509_STORE_CTX_get_error(ctx); + return (error == X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION) ? 1 : ok; +} + +static int +init_trusted(struct validation *result, char *root) +{ + X509 *cert; + int ok; + int error; + + cert = certificate_load(result, root); + if (cert == NULL) + return -EINVAL; + + result->trusted = sk_X509_new_null(); + if (result->trusted == NULL) { + error = -EINVAL; + goto abort1; + } + + ok = sk_X509_push(result->trusted, cert); + if (ok <= 0) { + error = crypto_err(result, + "Could not add certificate to trusted stack: %d", ok); + goto abort2; + } + + return 0; + +abort2: + sk_X509_free(result->trusted); +abort1: + X509_free(cert); + return error; +} + +int +validation_create(struct validation **out, char *root) +{ + struct validation *result; + int error = -ENOMEM; + + result = malloc(sizeof(struct validation)); + if (!result) + return -ENOMEM; + + result->out = BIO_new_fp(stdout, BIO_NOCLOSE); + if (result->out == NULL) { + fprintf(stderr, "Failed to initialise standard output's BIO.\n"); + goto abort1; + } + result->err = BIO_new_fp(stderr, BIO_NOCLOSE); + if (result->out == NULL) { + fprintf(stderr, "Failed to initialise standard error's BIO.\n"); + goto abort2; + } + + result->store = X509_STORE_new(); + if (!result->store) { + error = crypto_err(result, "X509_STORE_new() returned NULL"); + goto abort3; + } + + X509_STORE_set_verify_cb(result->store, cb); + + error = init_trusted(result, root); + if (error) + goto abort4; + + *out = result; + return 0; + +abort4: + X509_STORE_free(result->store); +abort3: + BIO_free_all(result->err); +abort2: + BIO_free_all(result->out); +abort1: + free(result); + return error; +} + +void +validation_destroy(struct validation *state) +{ + int cert_num; + + /* + * Only the certificate created during validation_create() should + * remain. + */ + cert_num = sk_X509_num(state->trusted); + if (cert_num != 1) { + pr_err(state, "Error: validation state has %d certificates. (1 expected)", + cert_num); + } + + sk_X509_pop_free(state->trusted, X509_free); + X509_STORE_free(state->store); + BIO_free_all(state->err); + BIO_free_all(state->out); + free(state); +} + +/** + * "Swallows" @cert; do not delete it. + */ +int +validation_push(struct validation *state, X509 *cert) +{ + /* + * TODO + * The only difference between -CAfile and -trusted, as it seems, is + * that -CAfile consults the default file location, while -trusted does + * not. As far as I can tell, this means that we absolutely need to use + * -trusted. + * So, just in case, enable -no-CAfile and -no-CApath. + */ + + X509_STORE_CTX *ctx; + int ok; + + ctx = X509_STORE_CTX_new(); + if (ctx == NULL) { + crypto_err(state, "X509_STORE_CTX_new() returned NULL"); + goto end1; + } + + /* Returns 0 or 1 , all callers test ! only. */ + ok = X509_STORE_CTX_init(ctx, state->store, cert, NULL); + if (!ok) { + crypto_err(state, "X509_STORE_CTX_init() returned %d", ok); + goto end2; + } + + X509_STORE_CTX_trusted_stack(ctx, state->trusted); + + /* Can return negative codes, all callers do <= 0. */ + ok = X509_verify_cert(ctx); + if (ok <= 0) { + crypto_err(state, "Certificate validation failed: %d", ok); + goto end2; + } + + /* Returns number of stack elements or 0 */ + ok = sk_X509_push(state->trusted, cert); + if (ok <= 0) { + crypto_err(state, + "Could not add certificate to trusted stack: %d", ok); + goto end2; + } + + X509_STORE_CTX_free(ctx); + return 0; + +end2: + X509_STORE_CTX_free(ctx); +end1: + X509_free(cert); + return -EINVAL; +} + +void +validation_pop(struct validation *state) +{ + X509 *cert = sk_X509_pop(state->trusted); + X509_free(cert); +} + +X509 * +validation_peek(struct validation *state) +{ + return sk_X509_value(state->trusted, sk_X509_num(state->trusted) - 1); +} + +BIO * +validation_stdout(struct validation *state) +{ + return state->out; +} + +BIO * +validation_stderr(struct validation *state) +{ + return state->err; +} diff --git a/src/state.h b/src/state.h new file mode 100644 index 00000000..52218b5a --- /dev/null +++ b/src/state.h @@ -0,0 +1,18 @@ +#ifndef SRC_STATE_H_ +#define SRC_STATE_H_ + +#include + +struct validation; + +int validation_create(struct validation **, char *); +void validation_destroy(struct validation *); + +int validation_push(struct validation *, X509 *); +void validation_pop(struct validation *); +X509 *validation_peek(struct validation *); + +BIO *validation_stdout(struct validation *); +BIO *validation_stderr(struct validation *); + +#endif /* SRC_STATE_H_ */ -- 2.47.3