From: Alberto Leiva Popper Date: Wed, 20 Feb 2019 19:12:46 +0000 (-0600) Subject: Implement RFC 6493 (Ghostbusters record) X-Git-Tag: v0.0.2~84 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=cdf3da85bc12f53048d673960edf360df296cdc9;p=thirdparty%2FFORT-validator.git Implement RFC 6493 (Ghostbusters record) Issue: I implemented 6493, but not 6350. Only the 6493-defined vCard requirements are checked. --- diff --git a/src/Makefile.am b/src/Makefile.am index 41a34421..09fa0613 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -40,10 +40,13 @@ rpki_validator_SOURCES += crypto/hash.h crypto/hash.c rpki_validator_SOURCES += object/certificate.h object/certificate.c rpki_validator_SOURCES += object/crl.h object/crl.c +rpki_validator_SOURCES += object/ghostbusters.h object/ghostbusters.c rpki_validator_SOURCES += object/manifest.h object/manifest.c +rpki_validator_SOURCES += object/name.h object/name.c rpki_validator_SOURCES += object/roa.h object/roa.c rpki_validator_SOURCES += object/signed_object.h object/signed_object.c rpki_validator_SOURCES += object/tal.h object/tal.c +rpki_validator_SOURCES += object/vcard.h object/vcard.c rpki_validator_SOURCES += resource/ip4.h resource/ip4.c rpki_validator_SOURCES += resource/ip6.h resource/ip6.c diff --git a/src/asn1/oid.h b/src/asn1/oid.h index da9e9984..8e984968 100644 --- a/src/asn1/oid.h +++ b/src/asn1/oid.h @@ -8,11 +8,16 @@ /* These objects are expected to live on the stack. */ struct oid_arcs { + char const *name; asn_oid_arc_t *arcs; size_t count; }; -#define OID2ARCS(oid) { .arcs = oid, .count = ARRAY_LEN(oid) } +#define OID2ARCS(_name, oid) { \ + .name = _name, \ + .arcs = oid, \ + .count = ARRAY_LEN(oid), \ +} void free_arcs(struct oid_arcs *); @@ -32,6 +37,7 @@ typedef asn_oid_arc_t OID[]; #define OID_ROA { 1, 2, 840, 113549, 1, 9, 16, 1, 24 } #define OID_MANIFEST { 1, 2, 840, 113549, 1, 9, 16, 1, 26 } +#define OID_GHOSTBUSTERS { 1, 2, 840, 113549, 1, 9, 16, 1, 35 } #define OID_RSA { 1, 2, 840, 113549, 1, 1, 1 } #define OID_SHA256 { 2, 16, 840, 1, 101, 3, 4, 2, 1 } diff --git a/src/common.c b/src/common.c index ab2be7ea..80e09460 100644 --- a/src/common.c +++ b/src/common.c @@ -1,9 +1,7 @@ #include "common.h" -#include #include #include "log.h" -#include "thread_var.h" /** * Does not assume that @string is NULL-terminated. @@ -31,136 +29,3 @@ ia5s2string(ASN1_IA5STRING *ia5, char **result) ? pr_err("CRL URI IA5String has unused bits.") : string_clone(ia5->data, ia5->length, result); } - -/** - * Only prints error message if the result is not 0 nor -ESRCH. - */ -int -x509_name_decode(X509_NAME *name, int nid, char **_result) -{ - char *result; - int len1, len2; - - len1 = X509_NAME_get_text_by_NID(name, nid, NULL, 0); - if (len1 < 0) - return -ESRCH; - - if (_result == NULL) - return 0; - - result = calloc(len1 + 1, sizeof(char)); - if (result == NULL) - return pr_enomem(); - - len2 = X509_NAME_get_text_by_NID(name, nid, result, len1 + 1); - if (len1 != len2) { - free(result); - return pr_err("Likely programming error: X509_NAME_get_text_by_NID() returned inconsistent lengths: %d,%d", - len1, len2); - } - - *_result = result; - return 0; -} - -struct rfc5280_names { - char *commonName; - char *serialNumber; -}; - -static int -get_names(X509_NAME *name, char const *what, struct rfc5280_names *result) -{ - int error; - - error = x509_name_decode(name, NID_commonName, &result->commonName); - if (error == -ESRCH) { - return pr_err("The '%s' name lacks a commonName attribute.", - what); - } - if (error) - return error; - - error = x509_name_decode(name, NID_serialNumber, &result->serialNumber); - if (error == -ESRCH) { - result->serialNumber = NULL; - return 0; - } - if (error) { - free(result->commonName); - return error; - } - - return 0; -} - -/** - * Also checks NULL. - * - * Does assume that @str1 and @str2 are NULL-terminated. - */ -static bool str_equals(char const *str1, char const *str2) -{ - if (str1 == str2) - return true; - if (str1 == NULL || str2 == NULL) - return false; - return strcmp(str1, str2) == 0; -} - -int -validate_issuer_name(char const *container, X509_NAME *issuer) -{ - struct validation *state; - X509 *parent; - struct rfc5280_names parent_subject = { 0 }; - struct rfc5280_names child_issuer = { 0 }; - int error; - - /* - * Not sure whether "the CRL issuer is the CA" means that the issuer - * name should equal the parent's subject name or not, because that's - * very much not what rfc6487#section-4.4 is asking us to check. - * But let's check it anyway. - */ - - state = state_retrieve(); - if (state == NULL) - return -EINVAL; - parent = validation_peek_cert(state); - if (parent == NULL) { - return pr_err("%s appears to have no parent certificate.", - container); - } - - error = get_names(X509_get_subject_name(parent), "subject", - &parent_subject); - if (error) - return error; - error = get_names(issuer, "issuer", &child_issuer); - if (error) - goto end2; - - if (strcmp(parent_subject.commonName, child_issuer.commonName) != 0) { - error = pr_err("%s's issuer commonName ('%s') does not equal issuer certificate's commonName ('%s').", - container, parent_subject.commonName, - child_issuer.commonName); - goto end1; - } - - if (!str_equals(parent_subject.serialNumber, - child_issuer.serialNumber)) { - error = pr_err("%s's issuer serialNumber ('%s') does not equal issuer certificate's serialNumber ('%s').", - container, parent_subject.serialNumber, - child_issuer.serialNumber); - goto end1; - } - -end1: - free(child_issuer.commonName); - free(child_issuer.serialNumber); -end2: - free(parent_subject.commonName); - free(parent_subject.serialNumber); - return error; -} diff --git a/src/common.h b/src/common.h index e109d437..e78d09fd 100644 --- a/src/common.h +++ b/src/common.h @@ -1,8 +1,8 @@ #ifndef SRC_RTR_COMMON_H_ #define SRC_RTR_COMMON_H_ -#include -#include +#include +#include /* "I think that this is not supposed to be implemented." */ #define ENOTSUPPORTED 3172 @@ -21,7 +21,4 @@ int string_clone(void const *, size_t, char **); int ia5s2string(ASN1_IA5STRING *, char **); -int x509_name_decode(X509_NAME *, int, char **); -int validate_issuer_name(char const *, X509_NAME *); - #endif /* SRC_RTR_COMMON_H_ */ diff --git a/src/log.c b/src/log.c index 4c2954c1..54fb1e7f 100644 --- a/src/log.c +++ b/src/log.c @@ -66,7 +66,9 @@ pr_debug_prefix(void) void pr_debug_suffix(void) { - fprintf(STDOUT, "%s\n", COLOR_RESET); + if (config_get_color_output()) + fprintf(STDOUT, "%s", COLOR_RESET); + fprintf(STDOUT, "\n"); } void diff --git a/src/object/certificate.c b/src/object/certificate.c index dd53abe8..be9209ed 100644 --- a/src/object/certificate.c +++ b/src/object/certificate.c @@ -16,6 +16,7 @@ #include "asn1/decode.h" #include "asn1/oid.h" #include "crypto/hash.h" +#include "object/name.h" #include "rsync/rsync.h" /* Just to prevent some line breaking. */ diff --git a/src/object/crl.c b/src/object/crl.c index 059eaa14..10808ad9 100644 --- a/src/object/crl.c +++ b/src/object/crl.c @@ -2,10 +2,10 @@ #include #include "algorithm.h" -#include "common.h" #include "extension.h" #include "log.h" #include "thread_var.h" +#include "object/name.h" static int __crl_load(struct rpki_uri const *uri, X509_CRL **result) diff --git a/src/object/ghostbusters.c b/src/object/ghostbusters.c new file mode 100644 index 00000000..83fb7ae6 --- /dev/null +++ b/src/object/ghostbusters.c @@ -0,0 +1,44 @@ +#include "object/ghostbusters.h" + +#include "log.h" +#include "thread_var.h" +#include "asn1/oid.h" +#include "asn1/signed_data.h" +#include "object/signed_object.h" +#include "vcard.h" + +static int +handle_vcard(OCTET_STRING_t *vcard, void *arg) +{ + return handle_ghostbusters_vcard(vcard); +} + +int +handle_ghostbusters(struct rpki_uri const *uri, struct rpp *pp, + STACK_OF(X509_CRL) *crls) +{ + static OID oid = OID_GHOSTBUSTERS; + struct oid_arcs arcs = OID2ARCS("ghostbusters", oid); + struct signed_object_args sobj_args; + int error; + + pr_debug_add("Ghostbusters %s {", uri->global); + fnstack_push(uri->global); + + error = signed_object_args_init(&sobj_args, uri, crls); + if (error) + goto end1; + + error = signed_object_decode(&sobj_args, &arcs, handle_vcard, NULL); + if (error) + goto end2; + + error = refs_validate_ee(&sobj_args.refs, pp, sobj_args.uri); + +end2: + signed_object_args_cleanup(&sobj_args); +end1: + pr_debug_rm("}"); + fnstack_pop(); + return error; +} diff --git a/src/object/ghostbusters.h b/src/object/ghostbusters.h new file mode 100644 index 00000000..d5610cb7 --- /dev/null +++ b/src/object/ghostbusters.h @@ -0,0 +1,11 @@ +#ifndef SRC_OBJECT_GHOSTBUSTERS_H_ +#define SRC_OBJECT_GHOSTBUSTERS_H_ + +#include +#include "uri.h" +#include "rpp.h" + +int handle_ghostbusters(struct rpki_uri const *, struct rpp *, + STACK_OF(X509_CRL) *); + +#endif /* SRC_OBJECT_GHOSTBUSTERS_H_ */ diff --git a/src/object/manifest.c b/src/object/manifest.c index 94cacb86..534a32ca 100644 --- a/src/object/manifest.c +++ b/src/object/manifest.c @@ -6,6 +6,7 @@ #include "log.h" #include "thread_var.h" +#include "asn1/decode.h" #include "asn1/oid.h" #include "crypto/hash.h" #include "object/certificate.h" @@ -18,6 +19,12 @@ struct manifest { char const *file_path; }; +static int +manifest_decode(OCTET_STRING_t *string, void *arg) +{ + return asn1_decode_octet_string(string, &asn_DEF_Manifest, arg); +} + static int validate_dates(GeneralizedTime_t *this, GeneralizedTime_t *next) { @@ -146,6 +153,8 @@ __handle_manifest(struct manifest *mft, struct rpp **pp) error = rpp_add_roa(*pp, &uri); else if (uri_has_extension(&uri, ".crl")) error = rpp_add_crl(*pp, &uri); + else if (uri_has_extension(&uri, ".gbr")) + error = rpp_add_ghostbusters(*pp, &uri); else uri_cleanup(&uri); /* ignore it. */ @@ -171,7 +180,7 @@ handle_manifest(struct rpki_uri const *uri, STACK_OF(X509_CRL) *crls, struct rpp **pp) { static OID oid = OID_MANIFEST; - struct oid_arcs arcs = OID2ARCS(oid); + struct oid_arcs arcs = OID2ARCS("manifest", oid); struct signed_object_args sobj_args; struct manifest mft; int error; @@ -184,8 +193,8 @@ handle_manifest(struct rpki_uri const *uri, STACK_OF(X509_CRL) *crls, goto end1; mft.file_path = uri->global; - error = signed_object_decode(&sobj_args, &asn_DEF_Manifest, &arcs, - (void **) &mft.obj); + error = signed_object_decode(&sobj_args, &arcs, + manifest_decode, &mft.obj); if (error) goto end2; diff --git a/src/object/name.c b/src/object/name.c new file mode 100644 index 00000000..2dae6893 --- /dev/null +++ b/src/object/name.c @@ -0,0 +1,141 @@ +#include "object/name.h" + +#include +#include +#include + +#include "log.h" +#include "thread_var.h" + +/** + * Only prints error message if the result is not 0 nor -ESRCH. + */ +int +x509_name_decode(X509_NAME *name, int nid, char **_result) +{ + char *result; + int len1, len2; + + len1 = X509_NAME_get_text_by_NID(name, nid, NULL, 0); + if (len1 < 0) + return -ESRCH; + + if (_result == NULL) + return 0; + + result = calloc(len1 + 1, sizeof(char)); + if (result == NULL) + return pr_enomem(); + + len2 = X509_NAME_get_text_by_NID(name, nid, result, len1 + 1); + if (len1 != len2) { + free(result); + return pr_err("Likely programming error: X509_NAME_get_text_by_NID() returned inconsistent lengths: %d,%d", + len1, len2); + } + + *_result = result; + return 0; +} + +struct rfc5280_names { + char *commonName; + char *serialNumber; +}; + +static int +get_names(X509_NAME *name, char const *what, struct rfc5280_names *result) +{ + int error; + + error = x509_name_decode(name, NID_commonName, &result->commonName); + if (error == -ESRCH) { + return pr_err("The '%s' name lacks a commonName attribute.", + what); + } + if (error) + return error; + + error = x509_name_decode(name, NID_serialNumber, &result->serialNumber); + if (error == -ESRCH) { + result->serialNumber = NULL; + return 0; + } + if (error) { + free(result->commonName); + return error; + } + + return 0; +} + +/** + * Also checks NULL. + * + * Does assume that @str1 and @str2 are NULL-terminated. + */ +static bool str_equals(char const *str1, char const *str2) +{ + if (str1 == str2) + return true; + if (str1 == NULL || str2 == NULL) + return false; + return strcmp(str1, str2) == 0; +} + +int +validate_issuer_name(char const *container, X509_NAME *issuer) +{ + struct validation *state; + X509 *parent; + struct rfc5280_names parent_subject = { 0 }; + struct rfc5280_names child_issuer = { 0 }; + int error; + + /* + * Not sure whether "the CRL issuer is the CA" means that the issuer + * name should equal the parent's subject name or not, because that's + * very much not what rfc6487#section-4.4 is asking us to check. + * But let's check it anyway. + */ + + state = state_retrieve(); + if (state == NULL) + return -EINVAL; + parent = validation_peek_cert(state); + if (parent == NULL) { + return pr_err("%s appears to have no parent certificate.", + container); + } + + error = get_names(X509_get_subject_name(parent), "subject", + &parent_subject); + if (error) + return error; + error = get_names(issuer, "issuer", &child_issuer); + if (error) + goto end2; + + if (strcmp(parent_subject.commonName, child_issuer.commonName) != 0) { + error = pr_err("%s's issuer commonName ('%s') does not equal issuer certificate's commonName ('%s').", + container, parent_subject.commonName, + child_issuer.commonName); + goto end1; + } + + if (!str_equals(parent_subject.serialNumber, + child_issuer.serialNumber)) { + error = pr_err("%s's issuer serialNumber ('%s') does not equal issuer certificate's serialNumber ('%s').", + container, parent_subject.serialNumber, + child_issuer.serialNumber); + goto end1; + } + +end1: + free(child_issuer.commonName); + free(child_issuer.serialNumber); +end2: + free(parent_subject.commonName); + free(parent_subject.serialNumber); + return error; +} diff --git a/src/object/name.h b/src/object/name.h new file mode 100644 index 00000000..fb0fcf83 --- /dev/null +++ b/src/object/name.h @@ -0,0 +1,9 @@ +#ifndef SRC_OBJECT_NAME_H_ +#define SRC_OBJECT_NAME_H_ + +#include + +int x509_name_decode(X509_NAME *, int, char **); +int validate_issuer_name(char const *, X509_NAME *); + +#endif /* SRC_OBJECT_NAME_H_ */ diff --git a/src/object/roa.c b/src/object/roa.c index 9948f5a2..c4339880 100644 --- a/src/object/roa.c +++ b/src/object/roa.c @@ -6,9 +6,17 @@ #include "log.h" #include "thread_var.h" +#include "asn1/decode.h" #include "asn1/oid.h" #include "object/signed_object.h" +static int +roa_decode(OCTET_STRING_t *string, void *arg) +{ + return asn1_decode_octet_string(string, &asn_DEF_RouteOriginAttestation, + arg); +} + static int print_addr4(struct resources *parent, unsigned long asn, struct ROAIPAddress *roa_addr) @@ -196,7 +204,7 @@ handle_roa(struct rpki_uri const *uri, struct rpp *pp, STACK_OF(X509_CRL) *crls) { static OID oid = OID_ROA; - struct oid_arcs arcs = OID2ARCS(oid); + struct oid_arcs arcs = OID2ARCS("roa", oid); struct signed_object_args sobj_args; struct RouteOriginAttestation *roa; int error; @@ -208,8 +216,7 @@ handle_roa(struct rpki_uri const *uri, struct rpp *pp, if (error) goto end1; - error = signed_object_decode(&sobj_args, - &asn_DEF_RouteOriginAttestation, &arcs, (void **) &roa); + error = signed_object_decode(&sobj_args, &arcs, roa_decode, &roa); if (error) goto end2; diff --git a/src/object/signed_object.c b/src/object/signed_object.c index 644c6be1..e7949d7a 100644 --- a/src/object/signed_object.c +++ b/src/object/signed_object.c @@ -3,12 +3,9 @@ #include #include "log.h" #include "asn1/content_info.h" -#include "asn1/decode.h" static int -validate_eContentType(struct SignedData *sdata, - asn_TYPE_descriptor_t const *descriptor, - struct oid_arcs const *oid) +validate_eContentType(struct SignedData *sdata, struct oid_arcs const *oid) { struct oid_arcs arcs; bool equals; @@ -20,17 +17,15 @@ validate_eContentType(struct SignedData *sdata, equals = arcs_equal(&arcs, oid); free_arcs(&arcs); if (!equals) { - return pr_err("SignedObject's encapContentInfo lacks the OID of a %s.", - descriptor->name); + return pr_err("The OID of the SignedObject's encapContentInfo is not '%s'.", + oid->name); } return 0; } static int -validate_content_type(struct SignedData *sdata, - asn_TYPE_descriptor_t const *descriptor, - struct oid_arcs const *oid) +validate_content_type(struct SignedData *sdata, struct oid_arcs const *oid) { OBJECT_IDENTIFIER_t *ctype; struct oid_arcs arcs; @@ -47,8 +42,8 @@ validate_content_type(struct SignedData *sdata, equals = arcs_equal(&arcs, oid); free_arcs(&arcs); if (!equals) { - return pr_err("SignedObject's content type attribute lacks the OID of a %s.", - descriptor->name); + return pr_err("The OID of the SignedObject's content type attribute is not '%s'.", + oid->name); } return 0; @@ -56,9 +51,9 @@ validate_content_type(struct SignedData *sdata, int signed_object_decode(struct signed_object_args *args, - asn_TYPE_descriptor_t const *descriptor, struct oid_arcs const *oid, - void **result) + signed_object_cb cb, + void *cb_arg) { struct ContentInfo *cinfo; struct SignedData *sdata; @@ -75,18 +70,17 @@ signed_object_decode(struct signed_object_args *args, /* rfc6482#section-2 */ /* rfc6486#section-4.1 */ /* rfc6486#section-4.4.1 */ - error = validate_eContentType(sdata, descriptor, oid); + error = validate_eContentType(sdata, oid); if (error) goto end3; /* rfc6482#section-2 */ /* rfc6486#section-4.3 */ - error = validate_content_type(sdata, descriptor, oid); + error = validate_content_type(sdata, oid); if (error) goto end3; - error = asn1_decode_octet_string(sdata->encapContentInfo.eContent, - descriptor, result); + error = cb(sdata->encapContentInfo.eContent, cb_arg); 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 6898a637..41dc5586 100644 --- a/src/object/signed_object.h +++ b/src/object/signed_object.h @@ -5,7 +5,9 @@ #include "asn1/oid.h" #include "asn1/signed_data.h" -int signed_object_decode(struct signed_object_args *args, - asn_TYPE_descriptor_t const *, struct oid_arcs const *, void **); +typedef int (*signed_object_cb)(OCTET_STRING_t *, void *); + +int signed_object_decode(struct signed_object_args *, struct oid_arcs const *, + signed_object_cb, void *); #endif /* SRC_OBJECT_SIGNED_OBJECT_H_ */ diff --git a/src/object/vcard.c b/src/object/vcard.c new file mode 100644 index 00000000..e20a4461 --- /dev/null +++ b/src/object/vcard.c @@ -0,0 +1,277 @@ +#include "vcard.h" + +#include +#include + +#include "log.h" + +/** + * Reminder: UTF-8 strings are **not** C strings. + * They can contain null characters, which do not terminate them. + * DO NOT use standard string operations on them. (Except for the `n` ones). + */ +struct utf8_string { + uint8_t *val; + /** Number of bytes in @val. */ + size_t len; + /** Actual allocated size of @val. */ + size_t size; +}; + +struct vcard_line { + /** + * This is a copy of the actual vCard value. + * It does not include the newline and has folding characters removed. + */ + struct utf8_string str; + size_t octet_string_offset; +}; + +enum string_analysis { + SA_COPY_CHARA, + SA_LINE_ENDED, + SA_SKIP_THREE_CHARAS, + SA_ERROR, +}; + +/** + * Returns a pointer to the character at the right of string[pos], if it exists. + * + * Assumes that string->len > 0. + */ +static uint8_t * +next_chara(struct utf8_string *string, size_t pos) +{ + return (pos < string->len - 1) ? (string->val + pos + 1) : NULL; +} + +/** + * Assumes that pos < string_len and that string_len > 0. + */ +static enum string_analysis +analyze_pos(struct utf8_string *string, size_t pos) +{ + /* + * Special cases: + * + * 1. \r\n: Normal line/property end + * 2. \r\n\w (where '\w' is space or tab): Folded line + * + * Each vCard line must end with \r\n. + */ + + uint8_t *next1; + uint8_t *next2; + + if (string->val[pos] != '\r') + return SA_COPY_CHARA; /* Typical path */ + + /* At this point, we have a \r. */ + + next1 = next_chara(string, pos); + if (next1 == NULL) { + pr_err("vCard's final newline is incomplete ('\\r')."); + return SA_ERROR; + } + if (*next1 != '\n') + return SA_COPY_CHARA; /* Random stray \r; no problem for now. */ + + /* At this point, we have a \r\n. */ + + next2 = next_chara(string, pos + 1); + if (next2 == NULL) + return SA_LINE_ENDED; /* \r\n */ + if (*next2 == ' ' || *next2 == '\t') + return SA_SKIP_THREE_CHARAS; /* Folded line */ + + return SA_LINE_ENDED; /* \r\n */ +} + +static int +double_line_size(struct vcard_line *line) +{ + uint8_t *tmp; + + line->str.size *= 2; + tmp = realloc(line->str.val, line->str.size); + if (tmp == NULL) + return pr_enomem(); + line->str.val = tmp; + + return 0; +} + +static int +add_chara(struct vcard_line *line, uint8_t chara, bool inc_str_len) +{ + int error; + + if (line->str.len + 1 == line->str.size) { + error = double_line_size(line); + if (error) + return error; + } + + line->str.val[line->str.len] = chara; + if (inc_str_len) + line->str.len++; + + return 0; +} + +/** + * Will remove the newline (\r\n). Just assume that there is a valid newline at + * the end. + * The result will not be a C string, but will still have a null character at + * the end (not accounted by line->str.len), in case you want to print it. + */ +static int +line_next(struct vcard_line *line, OCTET_STRING_t *string8) +{ + struct utf8_string string; + size_t string_pos; + int error; + + if (string8->size == line->octet_string_offset) + return pr_err("vCard ends prematurely. (Expected an END line)"); + + string.val = string8->buf + line->octet_string_offset; + string.len = string8->size - line->octet_string_offset; + string.size = 0; + line->str.len = 0; + + for (string_pos = 0; string_pos < string.len; string_pos++) { + switch (analyze_pos(&string, string_pos)) { + case SA_COPY_CHARA: + error = add_chara(line, string.val[string_pos], true); + if (error) + return error; + break; + + case SA_LINE_ENDED: + line->octet_string_offset += string_pos + 2; + return add_chara(line, 0, false); + + case SA_SKIP_THREE_CHARAS: + string_pos += 2; + break; + + case SA_ERROR: + return -EINVAL; + } + } + + return pr_err("vCard line does not end with a \\r\\n-style newline."); +} + +static int +line_validate(struct vcard_line *line, char const *expected) +{ + size_t expected_len = strlen(expected); + + if (line->str.len != expected_len) + goto fail; + + /* + * RFC 6350: + * "Property names and parameter names are case-insensitive" + * "Parameter values that are not explicitly defined as being + * case-sensitive are case-insensitive." + */ + if (strncasecmp((char *) line->str.val, expected, expected_len) != 0) + goto fail; + + return 0; + +fail: + return pr_err("Expected vCard property '%s', got '%s'.", + expected, line->str.val); +} + +/** + * @tag must contain the colon. This simplifies the code. + */ +static int +line_starts_with(struct vcard_line *line, char const *tag) +{ + /* RFC6350: "Property names and parameter names are case-insensitive" */ + return strncasecmp((char *) line->str.val, tag, strlen(tag)) == 0; +} + +static int +__handle_ghostbusters_vcard(OCTET_STRING_t *vcard, struct vcard_line *line) +{ + bool fn_found = false; + bool useful_found = false; + int error; + + error = line_next(line, vcard); + if (error) + return error; + error = line_validate(line, "BEGIN:VCARD"); + if (error) + return error; + + error = line_next(line, vcard); + if (error) + return error; + error = line_validate(line, "VERSION:4.0"); + if (error) + return error; + + do { + error = line_next(line, vcard); + if (error) + return error; + + if (line_starts_with(line, "FN:")) { + fn_found = true; + + } else if (line_starts_with(line, "ORG:") + || line_starts_with(line, "ADR:") + || line_starts_with(line, "TEL:") + || line_starts_with(line, "EMAIL:")) { + useful_found = true; + + } else if (line_starts_with(line, "END:")) { + break; + + } else { + return pr_err("Unexpected vCard line: '%s'", + line->str.val); + } + + } while (true); + + error = line_validate(line, "END:VCARD"); + if (error) + return error; + if (vcard->size != line->octet_string_offset) + return pr_err("vCard has content after the END tag."); + + if (!fn_found) + return pr_err("vCard lacks the 'FN' property."); + if (!useful_found) + return pr_err("vCard lacks the 'ORG', 'ADR', 'TEL' and/or 'EMAIL' properties."); + + return 0; +} + +int +handle_ghostbusters_vcard(OCTET_STRING_t *vcard) +{ + struct vcard_line line; + int error; + + line.str.size = 81; /* Okay default, assuming there is no folding. */ + line.str.len = 0; + line.str.val = malloc(line.str.size); + if (line.str.val == NULL) + return pr_enomem(); + line.octet_string_offset = 0; + + error = __handle_ghostbusters_vcard(vcard, &line); + + free(line.str.val); + return error; +} diff --git a/src/object/vcard.h b/src/object/vcard.h new file mode 100644 index 00000000..b25fc0cf --- /dev/null +++ b/src/object/vcard.h @@ -0,0 +1,8 @@ +#ifndef SRC_OBJECT_VCARD_H_ +#define SRC_OBJECT_VCARD_H_ + +#include + +int handle_ghostbusters_vcard(OCTET_STRING_t *); + +#endif /* SRC_OBJECT_VCARD_H_ */ diff --git a/src/rpp.c b/src/rpp.c index ca769c27..b7992f83 100644 --- a/src/rpp.c +++ b/src/rpp.c @@ -7,6 +7,7 @@ #include "uri.h" #include "object/certificate.h" #include "object/crl.h" +#include "object/ghostbusters.h" #include "object/roa.h" ARRAY_LIST(uris, struct rpki_uri) @@ -21,6 +22,8 @@ struct rpp { /* The Manifest is not needed for now. */ struct uris roas; /* Route Origin Attestations */ + + struct uris ghostbusters; }; struct rpp * @@ -37,9 +40,13 @@ rpp_create(void) result->crl_set = false; if (uris_init(&result->roas) != 0) goto fail3; + if (uris_init(&result->ghostbusters) != 0) + goto fail4; return result; +fail4: + uris_cleanup(&result->roas, uri_cleanup); fail3: uris_cleanup(&result->certs, uri_cleanup); fail2: @@ -53,6 +60,7 @@ rpp_destroy(struct rpp *pp) { uris_cleanup(&pp->certs, uri_cleanup); uris_cleanup(&pp->roas, uri_cleanup); + uris_cleanup(&pp->ghostbusters, uri_cleanup); free(pp); } @@ -68,6 +76,12 @@ rpp_add_roa(struct rpp *pp, struct rpki_uri *uri) return uris_add(&pp->roas, uri); } +int +rpp_add_ghostbusters(struct rpp *pp, struct rpki_uri *uri) +{ + return uris_add(&pp->ghostbusters, uri); +} + int rpp_add_crl(struct rpp *pp, struct rpki_uri *uri) { @@ -132,14 +146,16 @@ rpp_traverse(struct rpp *pp) goto end; /* Use CRL stack to validate certificates, and also traverse them. */ - ARRAYLIST_FOREACH(&pp->certs, uri) { + ARRAYLIST_FOREACH(&pp->certs, uri) certificate_traverse(pp, uri, crls, false); - } /* Use valid address ranges to print ROAs that match them. */ ARRAYLIST_FOREACH(&pp->roas, uri) handle_roa(uri, pp, crls); + ARRAYLIST_FOREACH(&pp->ghostbusters, uri) + handle_ghostbusters(uri, pp, crls); + end: sk_X509_CRL_pop_free(crls, X509_CRL_free); return error; diff --git a/src/rpp.h b/src/rpp.h index 6b687f5a..7d17dbd1 100644 --- a/src/rpp.h +++ b/src/rpp.h @@ -11,6 +11,7 @@ void rpp_destroy(struct rpp *); int rpp_add_cert(struct rpp *, struct rpki_uri *); int rpp_add_crl(struct rpp *, struct rpki_uri *); int rpp_add_roa(struct rpp *, struct rpki_uri *); +int rpp_add_ghostbusters(struct rpp *, struct rpki_uri *); struct rpki_uri const *rpp_get_crl(struct rpp const *); diff --git a/src/rsync/rsync.c b/src/rsync/rsync.c index 552ffc09..30433b63 100644 --- a/src/rsync/rsync.c +++ b/src/rsync/rsync.c @@ -25,8 +25,6 @@ static struct uri_list *rsync_uris; static char const *const RSYNC_PREFIX = "rsync://"; static bool rsync_enabled; -//static const char *rsync_command[] = {"rsync", "--recursive", "--delete", "--times", NULL}; - int rsync_init(bool is_rsync_enable) { diff --git a/src/state.c b/src/state.c index 7ee41837..07ba2032 100644 --- a/src/state.c +++ b/src/state.c @@ -373,10 +373,18 @@ validation_store_subject(struct validation *state, char *subject) if (cert == NULL) return 0; /* The TA lacks siblings, so subject is unique. */ - ARRAYLIST_FOREACH(&cert->subjects, cursor) - if (strcmp(*cursor, subject) == 0) - return pr_err("Subject name '%s' is not unique.", + ARRAYLIST_FOREACH(&cert->subjects, cursor) { + if (strcmp(*cursor, subject) == 0) { + /* + * I had to downgrade this because lots of people are + * breaking this rule. + * TODO (next iteration) make a framework so the user + * can choose the severity of each error. + */ + return pr_warn("Subject name '%s' is not unique.", subject); + } + } duplicate = strdup(subject); if (duplicate == NULL) diff --git a/test/Makefile.am b/test/Makefile.am index b4c72bff..5bb2c4a5 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -8,35 +8,45 @@ AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src -DUNIT_TESTING ${CHECK_CFLAGS} MY_LDADD = ${CHECK_LIBS} - -check_PROGRAMS = address.test line_file.test rsync.test tal.test +BASIC_MODULES = ../src/config.c ../src/config.h +BASIC_MODULES += ../src/log.c ../src/log.h +BASIC_MODULES += impersonator.c + +check_PROGRAMS = address.test +check_PROGRAMS += vcard.test +check_PROGRAMS += line_file.test +check_PROGRAMS += rsync.test +check_PROGRAMS += tal.test TESTS = ${check_PROGRAMS} -address_test_SOURCES = ../src/address.h -address_test_SOURCES += ../src/log.c ../src/log.h +address_test_SOURCES = ${BASIC_MODULES} +address_test_SOURCES += ../src/address.h address_test_SOURCES += address_test.c address_test_LDADD = ${MY_LDADD} -line_file_test_SOURCES = ../src/file.c ../src/file.h ../src/log.c ../src/log.h -line_file_test_SOURCES += ../src/common.c ../src/common.h -line_file_test_SOURCES += line_file_test.c ../src/line_file.c ../src/line_file.h +vcard_test_SOURCES = ${BASIC_MODULES} +vcard_test_SOURCES += vcard_test.c +vcard_test_LDADD = ${MY_LDADD} + +line_file_test_SOURCES = ${BASIC_MODULES} +line_file_test_SOURCES += ../src/file.c ../src/file.h +line_file_test_SOURCES += ../src/line_file.c ../src/line_file.h +line_file_test_SOURCES += line_file_test.c line_file_test_LDADD = ${MY_LDADD} -tal_test_SOURCES = ../src/file.c ../src/file.h ../src/log.c ../src/log.h -tal_test_SOURCES += ../src/config.c ../src/config.h -tal_test_SOURCES += ../src/toml_handler.c ../src/toml_handler.h +rsync_test_SOURCES = ${BASIC_MODULES} +rsync_test_SOURCES += ../src/common.c ../src/common.h +rsync_test_SOURCES += ../src/uri.c ../src/uri.h +rsync_test_SOURCES += rsync_test.c +rsync_test_LDADD = ${MY_LDADD} + +tal_test_SOURCES = ${BASIC_MODULES} +tal_test_SOURCES += ../src/file.c ../src/file.h tal_test_SOURCES += ../src/common.c ../src/common.h tal_test_SOURCES += ../src/crypto/base64.c ../src/crypto/base64.h tal_test_SOURCES += ../src/random.c ../src/random.h tal_test_SOURCES += ../src/uri.c ../src/uri.h -tal_test_SOURCES += tal_test.c ../src/line_file.c ../src/line_file.h -tal_test_CFLAGS = ${AM_CFLAGS} ${GLIB_CFLAGS} -tal_test_LDADD = ${MY_LDADD} ${GLIB_LIBS} - -rsync_test_SOURCES = ../src/file.c ../src/file.h ../src/log.c ../src/log.h -rsync_test_SOURCES += ../src/config.c ../src/config.h -rsync_test_SOURCES += ../src/toml_handler.c ../src/toml_handler.h -rsync_test_SOURCES += ../src/uri.c ../src/uri.h -rsync_test_SOURCES += ../src/common.c ../src/common.h -rsync_test_SOURCES += rsync_test.c -rsync_test_LDADD = ${MY_LDADD} +tal_test_SOURCES += ../src/line_file.c ../src/line_file.h +tal_test_SOURCES += tal_test.c +tal_test_CFLAGS = ${AM_CFLAGS} +tal_test_LDADD = ${MY_LDADD} diff --git a/test/impersonator.c b/test/impersonator.c new file mode 100644 index 00000000..3ae99a65 --- /dev/null +++ b/test/impersonator.c @@ -0,0 +1,38 @@ +#include + +/** + * Some core functions, as linked from unit testing code. + */ + +static char addr_buffer1[INET6_ADDRSTRLEN]; +static char addr_buffer2[INET6_ADDRSTRLEN]; + +char const * +v4addr2str(struct in_addr *addr) +{ + return inet_ntop(AF_INET, addr, addr_buffer1, sizeof(addr_buffer1)); +} + +char const * +v4addr2str2(struct in_addr *addr) +{ + return inet_ntop(AF_INET, addr, addr_buffer2, sizeof(addr_buffer2)); +} + +char const * +v6addr2str(struct in6_addr *addr) +{ + return inet_ntop(AF_INET6, addr, addr_buffer1, sizeof(addr_buffer1)); +} + +char const * +v6addr2str2(struct in6_addr *addr) +{ + return inet_ntop(AF_INET6, addr, addr_buffer2, sizeof(addr_buffer2)); +} + +int +set_config_from_file(char *config_file) +{ + return 0; +} diff --git a/test/line_file_test.c b/test/line_file_test.c index ed8004a7..51290de2 100644 --- a/test/line_file_test.c +++ b/test/line_file_test.c @@ -83,7 +83,7 @@ START_TEST(file_line_null_chara) } END_TEST -Suite *lfile_read_suite(void) +Suite *ghostbusters_suite(void) { Suite *suite; TCase *core, *limits, *errors; @@ -110,7 +110,7 @@ int main(void) SRunner *runner; int tests_failed; - suite = lfile_read_suite(); + suite = ghostbusters_suite(); runner = srunner_create(suite); srunner_run_all(runner, CK_NORMAL); diff --git a/test/rsync_test.c b/test/rsync_test.c index 77d6373f..b0f455ed 100644 --- a/test/rsync_test.c +++ b/test/rsync_test.c @@ -105,7 +105,7 @@ test_get_path(char *test, char *expected) error = get_path_only(string, strlen(string), rsync_prefix_len, &result); if (error) { -// free(string); + free(string); return; } diff --git a/test/vcard_test.c b/test/vcard_test.c new file mode 100644 index 00000000..6747d666 --- /dev/null +++ b/test/vcard_test.c @@ -0,0 +1,127 @@ +#include "object/vcard.c" + +#include + +#define VC_BEGIN "BEGIN:VCARD\r\n" +#define VC_VERSION "VERSION:4.0\r\n" +#define VC_FN "FN:name\r\n" +#define VC_ORG "ORG:organization\r\n" +#define VC_ADR "ADR:address\r\n" +#define VC_TEL "TEL:12345678\r\n" +#define VC_EMAIL "EMAIL:e@ma.il\r\n" +#define VC_END "END:VCARD\r\n" + +#define INIT_STR8(name, str) do { \ + printf("- %s:\n", name); \ + str8.buf = (uint8_t *) (str); \ + str8.size = strlen(str); \ +} while (0) + +START_TEST(vcard_normal) +{ + OCTET_STRING_t str8; + + INIT_STR8( + "Minimal", + VC_BEGIN VC_VERSION VC_FN VC_ORG VC_END + ); + ck_assert_int_eq(0, handle_ghostbusters_vcard(&str8)); + + INIT_STR8( + "Full", + VC_BEGIN VC_VERSION VC_FN VC_ORG VC_ADR VC_TEL VC_EMAIL VC_END + ); + ck_assert_int_eq(0, handle_ghostbusters_vcard(&str8)); + + INIT_STR8( + "Missing locator", + VC_BEGIN VC_VERSION VC_FN VC_END + ); + ck_assert_int_eq(-EINVAL, handle_ghostbusters_vcard(&str8)); + + INIT_STR8( + "Missing name", + VC_BEGIN VC_VERSION VC_ORG VC_END + ); + ck_assert_int_eq(-EINVAL, handle_ghostbusters_vcard(&str8)); + + INIT_STR8( + "Unknown property", + VC_BEGIN VC_VERSION VC_FN VC_ORG "POTATO:potato\r\n" VC_END + ); + ck_assert_int_eq(-EINVAL, handle_ghostbusters_vcard(&str8)); + + INIT_STR8( + "No newline", + VC_BEGIN VC_VERSION "FN:name" VC_ORG VC_END + ); + ck_assert_int_eq(-EINVAL, handle_ghostbusters_vcard(&str8)); + + INIT_STR8( + "\\r newline", + VC_BEGIN VC_VERSION "FN:name\r" VC_ORG VC_END + ); + ck_assert_int_eq(-EINVAL, handle_ghostbusters_vcard(&str8)); + + INIT_STR8( + "\\n newline", + VC_BEGIN VC_VERSION "FN:name\n" VC_ORG VC_END + ); + ck_assert_int_eq(-EINVAL, handle_ghostbusters_vcard(&str8)); + + INIT_STR8( + "Last line has no valid newline", + VC_BEGIN VC_VERSION VC_FN VC_ORG "END:VCARD" + ); + ck_assert_int_eq(-EINVAL, handle_ghostbusters_vcard(&str8)); + + INIT_STR8( + "Stray null character (in non-constant)", + VC_BEGIN VC_VERSION "FN:n\0ame\r\n" VC_ORG VC_END + ); + str8.size += strlen(" ame\r\n" VC_ORG VC_END); + ck_assert_int_eq(0, handle_ghostbusters_vcard(&str8)); + + INIT_STR8( + "Stray null character (in constant)", + VC_BEGIN "VERSION:4.\00\r\n" VC_FN VC_ORG VC_END + ); + str8.size += strlen(" 0\r\n" VC_FN VC_ORG VC_END); + ck_assert_int_eq(-EINVAL, handle_ghostbusters_vcard(&str8)); + + INIT_STR8( + "Garbage after END", + VC_BEGIN VC_VERSION VC_FN VC_ORG VC_END VC_EMAIL + ); + ck_assert_int_eq(-EINVAL, handle_ghostbusters_vcard(&str8)); +} +END_TEST + +Suite *ghostbusters_suite(void) +{ + Suite *suite; + TCase *hgv; + + hgv = tcase_create("handle_ghostbusters_vcard()"); + tcase_add_test(hgv, vcard_normal); + + suite = suite_create("vCard"); + suite_add_tcase(suite, hgv); + return suite; +} + +int main(void) +{ + Suite *suite; + SRunner *runner; + int tests_failed; + + suite = ghostbusters_suite(); + + runner = srunner_create(suite); + srunner_run_all(runner, CK_NORMAL); + tests_failed = srunner_ntests_failed(runner); + srunner_free(runner); + + return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +}