From: Alberto Leiva Popper Date: Sun, 21 Apr 2024 19:27:25 +0000 (-0600) Subject: Add certificates to --mode=print X-Git-Tag: 1.6.2~36 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9cfbc58426456d5e3ae150a6414253b6e0147c04;p=thirdparty%2FFORT-validator.git Add certificates to --mode=print This includes .cer files, as well as "certificates" signed object fields. Known caveat: The .SignedData.certificates[*].tbsCertificate.extensions are pretty ugly still. Progress for #122. --- diff --git a/src/alloc.c b/src/alloc.c index 00a1b09c..e6fd2bd0 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -60,3 +60,15 @@ pstrdup(const char *s) return result; } + +char * +pstrndup(const char *s, size_t n) +{ + char *result; + + result = strndup(s, n); + if (result == NULL) + enomem_panic(); + + return result; +} diff --git a/src/alloc.h b/src/alloc.h index b9e34e5a..2f4fd58b 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -20,5 +20,7 @@ void *prealloc(void *ptr, size_t size); /* strdup(), but panic on allocation failure. */ char *pstrdup(char const *s); +/* strndup(), but panic on allocation failure. */ +char *pstrndup(const char *s, size_t n); #endif /* SRC_ALLOC_H_ */ diff --git a/src/asn1/asn1c/Certificate.c b/src/asn1/asn1c/Certificate.c new file mode 100644 index 00000000..42158543 --- /dev/null +++ b/src/asn1/asn1c/Certificate.c @@ -0,0 +1,385 @@ +#include "asn1/asn1c/Certificate.h" + +#include +#include +#include "alloc.h" + +/* Swallows @bio. */ +static json_t * +bio2json(BIO *bio) +{ + BUF_MEM *buffer; + json_t *json; + + json = (BIO_get_mem_ptr(bio, &buffer) > 0) + ? json_stringn(buffer->data, buffer->length) + : NULL; + + BIO_free_all(bio); + return json; +} + +/* Swallows @bio. */ +static char * +bio2str(BIO *bio) +{ + BUF_MEM *buffer; + char *str; + + str = (BIO_get_mem_ptr(bio, &buffer) > 0) + ? pstrndup(buffer->data, buffer->length) + : NULL; + + BIO_free_all(bio); + return str; +} + +static json_t * +asn1int2json(ASN1_INTEGER const *asn1int) +{ + BIGNUM *bignum; + char *str; + json_t *json; + + if (asn1int == NULL) + return NULL; + + bignum = ASN1_INTEGER_to_BN(asn1int, NULL); + str = BN_bn2hex(bignum); + + json = json_string(str); + + OPENSSL_free(str); + BN_free(bignum); + + return json; +} + +static json_t * +name2json(X509_NAME const *name) +{ + json_t *root; + json_t *child; + int i; + + root = json_object(); + if (root == NULL) + return NULL; + + for (i = 0; i < X509_NAME_entry_count(name); i++) { + X509_NAME_ENTRY *entry; + int nid; + const ASN1_STRING *data; + + entry = X509_NAME_get_entry(name, i); + nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(entry)); + + data = X509_NAME_ENTRY_get_data(entry); + if (data == NULL) + goto fail; + child = json_stringn((char *)data->data, data->length); + + if (json_object_set_new(root, OBJ_nid2ln(nid), child) < 0) + goto fail; + } + + return root; + +fail: json_decref(root); + return NULL; +} + +static json_t * +asn1time2json(ASN1_TIME const *time) +{ + BIO *bio = BIO_new(BIO_s_mem()); + if (bio == NULL) + return NULL; + + if (!ASN1_TIME_print_ex(bio, time, ASN1_DTFLGS_ISO8601)) { + BIO_free_all(bio); + return NULL; + } + + return bio2json(bio); +} + +static json_t * +validity2json(X509 *x) +{ + json_t *root; + + root = json_object(); + if (root == NULL) + return NULL; + + if (json_object_set_new(root, "notBefore", asn1time2json(X509_get0_notBefore(x))) < 0) + goto fail; + if (json_object_set_new(root, "notAfter", asn1time2json(X509_get0_notAfter(x))) < 0) + goto fail; + + return root; + +fail: json_decref(root); + return NULL; +} + +static json_t * +pk2json(X509 const *x) +{ + json_t *root; + ASN1_OBJECT *xpoid; + EVP_PKEY *pkey; + BIO *bio; + + root = json_object(); + if (root == NULL) + return NULL; + + /* algorithm */ + if (!X509_PUBKEY_get0_param(&xpoid, NULL, NULL, NULL, X509_get_X509_PUBKEY(x))) + goto fail; + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) + goto fail; + if (i2a_ASN1_OBJECT(bio, xpoid) <= 0) { + BIO_free_all(bio); + goto fail; + } + if (json_object_set_new(root, "algorithm", bio2json(bio))) + goto fail; + + /* Actual pk */ + pkey = X509_get0_pubkey(x); + if (pkey == NULL) + goto fail; + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) + goto fail; + if (PEM_write_bio_PUBKEY(bio, pkey) <= 0) { + BIO_free_all(bio); + goto fail; + } + if (json_object_set_new(root, "subjectPublicKey", bio2json(bio))) + goto fail; + + return root; + +fail: json_decref(root); + return NULL; +} + +static json_t * +bitstr2json(ASN1_BIT_STRING const *bitstr) +{ + BIO *bio; + unsigned char *data; + int length; + int i; + + if (bitstr == NULL) + return json_null(); + + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) + return NULL; + + data = bitstr->data; + length = bitstr->length; + + for (i = 0; i < length; i++) { + if (BIO_printf(bio, "%02x", data[i]) <= 0) { + BIO_free_all(bio); + return NULL; + } + } + + return bio2json(bio); +} + +static json_t * +iuid2json(X509 const *x) +{ + const ASN1_BIT_STRING *iuid; + X509_get0_uids(x, &iuid, NULL); + return bitstr2json(iuid); +} + +static json_t * +suid2json(X509 const *x) +{ + const ASN1_BIT_STRING *suid; + X509_get0_uids(x, NULL, &suid); + return bitstr2json(suid); +} + +static json_t * +exts2json(const STACK_OF(X509_EXTENSION) *exts) +{ + json_t *root; + BIO *bio; + int i; + + if (sk_X509_EXTENSION_num(exts) <= 0) + return json_null(); + + root = json_object(); + if (root == NULL) + return NULL; + + for (i = 0; i < sk_X509_EXTENSION_num(exts); i++) { + json_t *node; + X509_EXTENSION *ex; + + ex = sk_X509_EXTENSION_value(exts, i); + + /* Get the extension name */ + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) + goto fail; + if (i2a_ASN1_OBJECT(bio, X509_EXTENSION_get_object(ex)) <= 0) { + BIO_free_all(bio); + goto fail; + } + + /* Create node, add to parent */ + node = json_object(); + if (node == NULL) { + BIO_free_all(bio); + goto fail; + } + if (json_object_set_new(root, bio2str(bio), node) < 0) + goto fail; + + /* Child 1: Critical */ + if (json_object_set_new(node, "critical", X509_EXTENSION_get_critical(ex) ? json_true() : json_false()) < 0) + goto fail; + + /* Child 2: Value */ + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) + goto fail; + /* TODO Those flags are kinda interesting */ + if (!X509V3_EXT_print(bio, ex, 0, 0)) { + BIO_free_all(bio); + goto fail; + } + if (json_object_set_new(node, "value", bio2json(bio)) < 0) + goto fail; + } + + return root; + +fail: json_decref(root); + return NULL; +} + +static json_t * +tbsCert2json(X509 *x) +{ + json_t *tbsCert; + + tbsCert = json_object(); + if (tbsCert == NULL) + return NULL; + + if (json_object_set_new(tbsCert, "version", json_integer(X509_get_version(x))) < 0) + goto fail; + if (json_object_set_new(tbsCert, "serialNumber", asn1int2json(X509_get0_serialNumber(x))) < 0) + goto fail; + if (json_object_set_new(tbsCert, "signature", json_string(OBJ_nid2ln(X509_get_signature_nid(x)))) < 0) + goto fail; + if (json_object_set_new(tbsCert, "issuer", name2json(X509_get_issuer_name(x))) < 0) + goto fail; + if (json_object_set_new(tbsCert, "validity", validity2json(x)) < 0) + goto fail; + if (json_object_set_new(tbsCert, "subject", name2json(X509_get_subject_name(x))) < 0) + goto fail; + if (json_object_set_new(tbsCert, "subjectPublicKeyInfo", pk2json(x)) < 0) + goto fail; + if (json_object_set_new(tbsCert, "issuerUniqueID", iuid2json(x)) < 0) + goto fail; + if (json_object_set_new(tbsCert, "subjectUniqueID", suid2json(x)) < 0) + goto fail; + if (json_object_set_new(tbsCert, "extensions", exts2json(X509_get0_extensions(x))) < 0) + goto fail; + + return tbsCert; + +fail: + json_decref(tbsCert); + return NULL; +} + +static json_t * +sigAlgorithm2json(X509 *cert) +{ + const X509_ALGOR *palg; + const ASN1_OBJECT *paobj; + + X509_get0_signature(NULL, &palg, cert); + X509_ALGOR_get0(&paobj, NULL, NULL, palg); + + return json_string(OBJ_nid2ln(OBJ_obj2nid(paobj))); +} + +static json_t * +sigValue2json(X509 *cert) +{ + const ASN1_BIT_STRING *signature; + X509_get0_signature(&signature, NULL, cert); + return bitstr2json(signature); +} + +static json_t * +x509_to_json(X509 *x) +{ + json_t *root; + + root = json_object(); + if (root == NULL) + return NULL; + + if (json_object_set_new(root, "tbsCertificate", tbsCert2json(x)) < 0) + goto fail; + if (json_object_set_new(root, "signatureAlgorithm", sigAlgorithm2json(x)) < 0) + goto fail; + if (json_object_set_new(root, "signatureValue", sigValue2json(x)) < 0) + goto fail; + + return root; + +fail: + json_decref(root); + return NULL; +} + +json_t * +Certificate_encode_json(ANY_t *ber) +{ + const unsigned char *tmp; + X509 *cert; + + /* + * "If the call is successful *in is incremented to the byte following + * the parsed data." + * (https://www.openssl.org/docs/man1.0.2/crypto/d2i_X509_fp.html) + * We don't want @ber->buf modified, so use a dummy pointer. + */ + tmp = (const unsigned char *) ber->buf; + + cert = d2i_X509(NULL, &tmp, ber->size); + if (cert == NULL) + return NULL; + + json_t *root = x509_to_json(cert); + if (root == NULL) + goto fail; + + X509_free(cert); + return root; + +fail: json_decref(root); + X509_free(cert); + return NULL; +} diff --git a/src/asn1/asn1c/Certificate.h b/src/asn1/asn1c/Certificate.h new file mode 100644 index 00000000..d2dccf45 --- /dev/null +++ b/src/asn1/asn1c/Certificate.h @@ -0,0 +1,9 @@ +#ifndef SRC_ASN1_ASN1C_CERTIFICATE_H_ +#define SRC_ASN1_ASN1C_CERTIFICATE_H_ + +#include +#include "asn1/asn1c/ANY.h" + +json_t *Certificate_encode_json(ANY_t *ber); + +#endif /* SRC_ASN1_ASN1C_CERTIFICATE_H_ */ diff --git a/src/asn1/asn1c/CertificateSet.c b/src/asn1/asn1c/CertificateSet.c index 92659187..dcdc88ba 100644 --- a/src/asn1/asn1c/CertificateSet.c +++ b/src/asn1/asn1c/CertificateSet.c @@ -6,6 +6,49 @@ */ #include "asn1/asn1c/CertificateSet.h" +#include "asn1/asn1c/Certificate.h" + +static json_t * +CertificateSet_encode_json(const struct asn_TYPE_descriptor_s *td, + const void *sptr) +{ + json_t *result; + const asn_anonymous_set_ *list; + int i; + + if (!sptr) + return json_null(); + + result = json_array(); + if (result == NULL) + return NULL; + + list = _A_CSET_FROM_VOID(sptr); + + for (i = 0; i < list->count; i++) { + json_t *node = Certificate_encode_json(list->array[i]); + if (node == NULL) + goto fail; + if (json_array_append_new(result, node) < 0) + goto fail; + } + + return result; + +fail: json_decref(result); + return NULL; +} + +asn_TYPE_operation_t asn_OP_CertificateSet = { + SET_OF_free, + SET_OF_print, + SET_OF_compare, + SET_OF_decode_ber, + SET_OF_encode_der, + CertificateSet_encode_json, + SET_OF_encode_xer, + 0 /* Use generic outmost tag fetcher */ +}; asn_TYPE_member_t asn_MBR_CertificateSet_1[] = { { ATF_ANY_TYPE | ATF_POINTER, 0, 0, @@ -29,7 +72,7 @@ asn_SET_OF_specifics_t asn_SPC_CertificateSet_specs_1 = { asn_TYPE_descriptor_t asn_DEF_CertificateSet = { "CertificateSet", "CertificateSet", - &asn_OP_SET_OF, + &asn_OP_CertificateSet, asn_DEF_CertificateSet_tags_1, sizeof(asn_DEF_CertificateSet_tags_1) /sizeof(asn_DEF_CertificateSet_tags_1[0]), /* 1 */ diff --git a/src/asn1/asn1c/GeneralizedTime.c b/src/asn1/asn1c/GeneralizedTime.c index 5d66e77a..2de0f8f0 100644 --- a/src/asn1/asn1c/GeneralizedTime.c +++ b/src/asn1/asn1c/GeneralizedTime.c @@ -207,7 +207,7 @@ asn_tm2str(struct tm *tm, char *str) int ret; ret = snprintf(str, ASN_TM_STR_MAXLEN, - "%04d-%02d-%02dT%02d:%02d:%02dZ", + "%04d-%02d-%02d %02d:%02d:%02dZ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); assert(ret == ASN_TM_STR_MAXLEN - 1); diff --git a/src/asn1/asn1c/Makefile.include b/src/asn1/asn1c/Makefile.include index f9faea9d..245ec2e2 100644 --- a/src/asn1/asn1c/Makefile.include +++ b/src/asn1/asn1c/Makefile.include @@ -47,6 +47,7 @@ ASN_MODULE_SRCS= \ asn1/asn1c/RevocationInfoChoices.c \ asn1/asn1c/RevocationInfoChoice.c \ asn1/asn1c/OtherRevocationInfoFormat.c \ + asn1/asn1c/Certificate.c \ asn1/asn1c/CertificateSet.c \ asn1/asn1c/IssuerAndSerialNumber.c \ asn1/asn1c/CMSVersion.c \ @@ -110,6 +111,7 @@ ASN_MODULE_HDRS= \ asn1/asn1c/RevocationInfoChoices.h \ asn1/asn1c/RevocationInfoChoice.h \ asn1/asn1c/OtherRevocationInfoFormat.h \ + asn1/asn1c/Certificate.h \ asn1/asn1c/CertificateSet.h \ asn1/asn1c/IssuerAndSerialNumber.h \ asn1/asn1c/CMSVersion.h \ diff --git a/src/asn1/asn1c/SignedAttributes.c b/src/asn1/asn1c/SignedAttributes.c index dc33197a..a9d3d526 100644 --- a/src/asn1/asn1c/SignedAttributes.c +++ b/src/asn1/asn1c/SignedAttributes.c @@ -13,7 +13,6 @@ SignedAttributes_encode_json(const struct asn_TYPE_descriptor_s *td, { json_t *result; const asn_anonymous_set_ *list; - asn_TYPE_descriptor_t *type; int i; if (!sptr) @@ -24,7 +23,7 @@ SignedAttributes_encode_json(const struct asn_TYPE_descriptor_s *td, return NULL; list = _A_CSET_FROM_VOID(sptr); - type = &asn_DEF_CMSAttribute; + td = &asn_DEF_CMSAttribute; for (i = 0; i < list->count; i++) { CMSAttribute_t *attr; @@ -33,7 +32,7 @@ SignedAttributes_encode_json(const struct asn_TYPE_descriptor_s *td, char const *key; attr = list->array[i]; - node = type->op->json_encoder(type, attr); + node = td->op->json_encoder(td, attr); if (node == NULL) goto fail; diff --git a/src/common.c b/src/common.c index a0e9ec01..d7a40198 100644 --- a/src/common.c +++ b/src/common.c @@ -14,6 +14,20 @@ str_starts_with(char const *str, char const *prefix) return strncmp(str, prefix, strlen(prefix)) == 0; } +bool +str_ends_with(char const *str, char const *suffix) +{ + size_t str_len; + size_t suffix_len; + + str_len = strlen(str); + suffix_len = strlen(suffix); + if (str_len < suffix_len) + return false; + + return strncmp(str + str_len - suffix_len, suffix, suffix_len) == 0; +} + void panic_on_fail(int error, char const *function_name) { diff --git a/src/common.h b/src/common.h index be66e4d8..01c3a788 100644 --- a/src/common.h +++ b/src/common.h @@ -26,6 +26,7 @@ #define ARRAY_LEN(array) (sizeof(array) / sizeof((array)[0])) bool str_starts_with(char const *, char const *); +bool str_ends_with(char const *, char const *); void panic_on_fail(int, char const *); diff --git a/src/object/certificate.c b/src/object/certificate.c index e3194d48..46ab2100 100644 --- a/src/object/certificate.c +++ b/src/object/certificate.c @@ -463,6 +463,7 @@ validate_public_key(X509 *cert, enum cert_type type) if (pubkey == NULL) return val_crypto_err("X509_get_X509_PUBKEY() returned NULL"); + /* TODO not validating @alg? */ ok = X509_PUBKEY_get0_param(&alg, NULL, NULL, &pa, pubkey); if (!ok) return val_crypto_err("X509_PUBKEY_get0_param() returned %d", ok); diff --git a/src/print_file.c b/src/print_file.c index 4999e832..7344fc01 100644 --- a/src/print_file.c +++ b/src/print_file.c @@ -1,9 +1,12 @@ #include "print_file.h" #include +#include "common.h" #include "config.h" +#include "file.h" #include "log.h" #include "asn1/content_info.h" +#include "asn1/asn1c/Certificate.h" int print_file(void) @@ -14,18 +17,35 @@ print_file(void) int error; filename = config_get_payload(); -// if (str_starts_with(filename, "rsync://")) { -// -// } else { + if (str_ends_with(filename, ".cer")) { + struct file_contents fc; + ANY_t any; + + error = file_load(filename, &fc); + if (error) + return error; + + memset(&any, 0, sizeof(any)); + any.buf = fc.buffer; + any.size = fc.buffer_size; + + json = Certificate_encode_json(&any); + + file_free(&fc); + + } else { error = content_info_load(filename, &ci); if (error) return error; -// } - json = json_encode(&asn_DEF_ContentInfo, ci); + json = json_encode(&asn_DEF_ContentInfo, ci); + + ASN_STRUCT_FREE(asn_DEF_ContentInfo, ci); + } + if (json == NULL) { pr_op_err("Error parsing object."); - goto end; + return error; } errno = 0; @@ -38,6 +58,6 @@ print_file(void) } json_decref(json); -end: ASN_STRUCT_FREE(asn_DEF_ContentInfo, ci); + printf("\n"); return error; } diff --git a/src/types/uri.c b/src/types/uri.c index cc7fb74e..85ac41e5 100644 --- a/src/types/uri.c +++ b/src/types/uri.c @@ -518,15 +518,7 @@ uri_equals(struct rpki_uri *u1, struct rpki_uri *u2) bool uri_has_extension(struct rpki_uri *uri, char const *ext) { - size_t ext_len; - int cmp; - - ext_len = strlen(ext); - if (uri->global_len < ext_len) - return false; - - cmp = strncmp(uri->global + uri->global_len - ext_len, ext, ext_len); - return cmp == 0; + return str_ends_with(uri->global, ext); } bool