]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Add certificates to --mode=print
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Sun, 21 Apr 2024 19:27:25 +0000 (13:27 -0600)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Sun, 21 Apr 2024 19:34:22 +0000 (13:34 -0600)
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.

13 files changed:
src/alloc.c
src/alloc.h
src/asn1/asn1c/Certificate.c [new file with mode: 0644]
src/asn1/asn1c/Certificate.h [new file with mode: 0644]
src/asn1/asn1c/CertificateSet.c
src/asn1/asn1c/GeneralizedTime.c
src/asn1/asn1c/Makefile.include
src/asn1/asn1c/SignedAttributes.c
src/common.c
src/common.h
src/object/certificate.c
src/print_file.c
src/types/uri.c

index 00a1b09cb2ffbb23192b54f818ce12708e145f85..e6fd2bd0985ae99e17a7a27d1a47cb3e88a93916 100644 (file)
@@ -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;
+}
index b9e34e5a55c6b94c64e9640e986c9f7b1fe571d0..2f4fd58bfbdae6dbb0b727d5789864da6ba719cf 100644 (file)
@@ -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 (file)
index 0000000..4215854
--- /dev/null
@@ -0,0 +1,385 @@
+#include "asn1/asn1c/Certificate.h"
+
+#include <openssl/x509v3.h>
+#include <openssl/pem.h>
+#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 (file)
index 0000000..d2dccf4
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef SRC_ASN1_ASN1C_CERTIFICATE_H_
+#define SRC_ASN1_ASN1C_CERTIFICATE_H_
+
+#include <jansson.h>
+#include "asn1/asn1c/ANY.h"
+
+json_t *Certificate_encode_json(ANY_t *ber);
+
+#endif /* SRC_ASN1_ASN1C_CERTIFICATE_H_ */
index 92659187d40ebea84207f1cd2ff70b12c64b7d39..dcdc88ba72a43616d087b31e8aa054d63241e88f 100644 (file)
@@ -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 */
index 5d66e77a95f7842e0bf6b671ec9747fac77a967c..2de0f8f0ca0a26f85e0956754520f1a2f29b5a31 100644 (file)
@@ -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);
index f9faea9d62a2431f03ea39e127a10a3b1faecbfa..245ec2e2400a28e3547d4b5d2debc3b76fd99339 100644 (file)
@@ -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 \
index dc33197ab397ae70de6ffd7a10ef5e66cefb0bfb..a9d3d5264e0ca69a83fd1fa7b5c92ddfea8ef038 100644 (file)
@@ -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;
 
index a0e9ec012cf722c4659299daab873b8fd0a27d07..d7a4019804a40abf8f87fa3f0efe7484f552069a 100644 (file)
@@ -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)
 {
index be66e4d87ca5976365489ab1804c0bcfac045406..01c3a788731faff2b2622c92e591630c7b2ca30f 100644 (file)
@@ -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 *);
 
index e3194d4889e56ed58672db367db6d6b6e04ab691..46ab21004aa5f2c04c3ca768c96b962cdbccd667 100644 (file)
@@ -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);
index 4999e83226c734248df94ac17134f2d22a889301..7344fc01b0fbc4b13fed4d4a36babd9e004f0b66 100644 (file)
@@ -1,9 +1,12 @@
 #include "print_file.h"
 
 #include <errno.h>
+#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;
 }
index cc7fb74e8a8121d078ec0572582d150358c78e6f..85ac41e585746c01219a85efc0af99282cd88cbb 100644 (file)
@@ -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