hx509: preserve received certificate encodings

Keep the original DER encoding around when decoding certificates so hx509_cert_binary() does not rewrite certificates unnecessarily.

Add regression coverage for direct certificate binary output and keyless stores.
This commit is contained in:
Roland C. Dowdeswell
2026-06-16 12:03:50 +01:00
committed by Nico Williams
parent d67a2c36c4
commit 5ac91f2dc9
3 changed files with 221 additions and 2 deletions
+4 -1
View File
@@ -169,12 +169,15 @@ fuzz_jose_SOURCES = fuzz_jose.c
fuzz_jose_LDADD = libhx509.la $(LIB_roken) $(top_builddir)/lib/base/libheimbase.la
fuzz_jose.$(OBJEXT): $(HX509_PROTOS) $(nodist_include_HEADERS)
test_expr_LDADD = libhx509.la $(LIB_roken) $(top_builddir)/lib/asn1/libasn1.la
test_cert_binary_CPPFLAGS = $(AM_CPPFLAGS) -DSRCDIR=\"$(srcdir)\"
test_cert_binary_LDADD = libhx509.la $(LIB_roken) $(top_builddir)/lib/asn1/libasn1.la
TESTS = $(SCRIPT_TESTS) $(PROGRAM_TESTS)
PROGRAM_TESTS = \
test_name \
test_expr
test_expr \
test_cert_binary
SCRIPT_TESTS = \
test_ca \
+38 -1
View File
@@ -78,6 +78,7 @@ struct hx509_cert_data {
unsigned int ref;
char *friendlyname;
Certificate *data;
heim_octet_string encoding;
hx509_private_key private_key;
struct _hx509_cert_attrs attrs;
hx509_name basename;
@@ -635,6 +636,8 @@ cert_init(hx509_context context, heim_error_t *error)
cert->release = NULL;
cert->ctx = NULL;
cert->data= NULL;
cert->encoding.data = NULL;
cert->encoding.length = 0;
return cert;
}
@@ -693,7 +696,23 @@ hx509_cert_copy_no_private_key(hx509_context context,
hx509_cert src,
heim_error_t *error)
{
return hx509_cert_init(context, src->data, error);
hx509_cert cert;
int ret;
cert = hx509_cert_init(context, src->data, error);
if (cert == NULL)
return NULL;
ret = der_copy_octet_string(&src->encoding, &cert->encoding);
if (ret) {
hx509_cert_free(cert);
if (error)
*error = heim_error_create_enomem();
errno = ret;
return NULL;
}
return cert;
}
/**
@@ -747,6 +766,7 @@ hx509_cert_init_data(hx509_context context,
heim_error_t *error)
{
hx509_cert cert;
heim_octet_string os;
Certificate t;
size_t size;
int ret;
@@ -769,6 +789,19 @@ hx509_cert_init_data(hx509_context context,
cert = hx509_cert_init(context, &t, error);
free_Certificate(&t);
if (cert == NULL)
return NULL;
os.length = len;
os.data = rk_UNCONST(ptr);
ret = der_copy_octet_string(&os, &cert->encoding);
if (ret) {
hx509_cert_free(cert);
if (error)
*error = heim_error_create_enomem();
errno = ret;
return NULL;
}
return cert;
}
@@ -824,6 +857,7 @@ hx509_cert_free(hx509_cert cert)
if (cert->data)
free_Certificate(cert->data);
free(cert->data);
der_free_octet_string(&cert->encoding);
for (i = 0; i < cert->attrs.len; i++) {
der_free_octet_string(&cert->attrs.val[i]->data);
@@ -4057,6 +4091,9 @@ hx509_cert_binary(hx509_context context, hx509_cert c, heim_octet_string *os)
os->data = NULL;
os->length = 0;
if (c->encoding.data != NULL)
return der_copy_octet_string(&c->encoding, os);
ASN1_MALLOC_ENCODE(Certificate, os->data, os->length,
_hx509_get_cert(c), &size, ret);
if (ret) {
+179
View File
@@ -0,0 +1,179 @@
#include "hx_locl.h"
#include <err.h>
#ifndef SRCDIR
#define SRCDIR "."
#endif
struct pem_cert {
heim_octet_string der;
int found;
};
static int
pem_cert_reader(hx509_context context, const char *type,
const hx509_pem_header *headers,
const void *data, size_t length, void *ctx)
{
struct pem_cert *pem = ctx;
(void)context;
(void)headers;
if (strcmp(type, "CERTIFICATE") != 0)
return 0;
if (pem->found)
return 0;
pem->der.data = malloc(length);
if (pem->der.data == NULL)
return ENOMEM;
memcpy(pem->der.data, data, length);
pem->der.length = length;
pem->found = 1;
return 0;
}
static void
read_cert_der(hx509_context context, const char *path, heim_octet_string *der)
{
struct pem_cert pem;
FILE *f;
int ret;
memset(&pem, 0, sizeof(pem));
f = fopen(path, "r");
if (f == NULL)
err(1, "fopen: %s", path);
ret = hx509_pem_read(context, f, pem_cert_reader, &pem);
fclose(f);
if (ret)
hx509_err(context, 1, ret, "hx509_pem_read: %s", path);
if (!pem.found)
errx(1, "did not find a certificate in %s", path);
*der = pem.der;
}
static void
compare_octet_string(const char *what,
const heim_octet_string *expected,
const heim_octet_string *actual)
{
if (actual->length != expected->length)
errx(1, "%s length changed from %lu to %lu",
what, (unsigned long)expected->length,
(unsigned long)actual->length);
if (memcmp(actual->data, expected->data, expected->length) != 0)
errx(1, "%s data changed", what);
}
static void
check_cert_binary(hx509_context context, const heim_octet_string *der)
{
heim_octet_string os;
hx509_cert cert;
int ret;
memset(&os, 0, sizeof(os));
cert = hx509_cert_init_data(context, der->data, der->length, NULL);
if (cert == NULL)
err(1, "hx509_cert_init_data");
ret = hx509_cert_binary(context, cert, &os);
if (ret)
hx509_err(context, 1, ret, "hx509_cert_binary");
compare_octet_string("certificate encoding", der, &os);
der_free_octet_string(&os);
hx509_cert_free(cert);
}
static char *
make_store_name(void)
{
char *store_name = NULL;
if (asprintf(&store_name, "FILE:%s/data/test.crt,%s/data/test.key",
SRCDIR, SRCDIR) == -1 || store_name == NULL)
err(1, "asprintf");
return store_name;
}
static void
check_keyless_store_cert_encoding(hx509_context context,
const heim_octet_string *der)
{
heim_octet_string os;
hx509_certs certs = NULL;
hx509_cert cert = NULL;
char *store_name;
int ret;
memset(&os, 0, sizeof(os));
ret = hx509_certs_init(context, "MEMORY:test-keyless-certs",
HX509_CERTS_NO_PRIVATE_KEYS, NULL, &certs);
if (ret)
hx509_err(context, 1, ret, "hx509_certs_init");
store_name = make_store_name();
ret = hx509_certs_append(context, certs, NULL, store_name);
free(store_name);
if (ret)
hx509_err(context, 1, ret, "hx509_certs_append");
ret = hx509_get_one_cert(context, certs, &cert);
if (ret)
hx509_err(context, 1, ret, "hx509_get_one_cert");
if (hx509_cert_have_private_key(cert))
errx(1, "HX509_CERTS_NO_PRIVATE_KEYS store kept a private key");
ret = hx509_cert_binary(context, cert, &os);
if (ret)
hx509_err(context, 1, ret, "hx509_cert_binary on keyless cert");
compare_octet_string("keyless store certificate encoding", der, &os);
der_free_octet_string(&os);
hx509_cert_free(cert);
hx509_certs_free(&certs);
}
int
main(int argc, char **argv)
{
heim_octet_string der;
hx509_context context;
char *cert_path = NULL;
int ret;
(void)argc;
(void)argv;
memset(&der, 0, sizeof(der));
ret = hx509_context_init(&context);
if (ret)
errx(1, "hx509_context_init failed with %d", ret);
if (asprintf(&cert_path, "%s/data/test.crt", SRCDIR) == -1 ||
cert_path == NULL)
err(1, "asprintf");
read_cert_der(context, cert_path, &der);
free(cert_path);
check_cert_binary(context, &der);
check_keyless_store_cert_encoding(context, &der);
der_free_octet_string(&der);
hx509_context_free(&context);
return 0;
}