Commit fe26a8fc90 for openssl.org
commit fe26a8fc907106bb66aca4fb056b9bb75bf0341e
Author: Tim Perry <pimterry@gmail.com>
Date: Sun Mar 15 17:00:48 2026 +0100
Add CTLOG_STORE_add0_log() to add CTLOGs to a store programmatically
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
MergeDate: Thu Mar 19 20:45:34 2026
(Merged from https://github.com/openssl/openssl/pull/30427)
diff --git a/CHANGES.md b/CHANGES.md
index 90069f94d7..6df6c90a67 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -41,6 +41,10 @@ OpenSSL Releases
*Paul Louvel*
+ * Added `CTLOG_STORE_add0_log()` to add individual CT logs to a `CTLOG_STORE`.
+
+ *Tim Perry*
+
* Dropped `no-ecdsa` and `no-ecdh` options from `Configure` as these options
did not really disable the implementations. Use `no-ec` to disable the
elliptic curve support.
diff --git a/crypto/ct/ct_log.c b/crypto/ct/ct_log.c
index b9990dc9a0..6fad6a6bd4 100644
--- a/crypto/ct/ct_log.c
+++ b/crypto/ct/ct_log.c
@@ -315,6 +315,20 @@ EVP_PKEY *CTLOG_get0_public_key(const CTLOG *log)
return log->public_key;
}
+int CTLOG_STORE_add0_log(CTLOG_STORE *store, CTLOG *log)
+{
+ if (store == NULL || log == NULL) {
+ ERR_raise(ERR_LIB_CT, ERR_R_PASSED_NULL_PARAMETER);
+ return 0;
+ }
+
+ if (!sk_CTLOG_push(store->logs, log)) {
+ ERR_raise(ERR_LIB_CT, ERR_R_CRYPTO_LIB);
+ return 0;
+ }
+ return 1;
+}
+
/*
* Given a log ID, finds the matching log.
* Returns NULL if no match found.
diff --git a/doc/man3/CTLOG_STORE_new.pod b/doc/man3/CTLOG_STORE_new.pod
index 361eda57b1..f342637012 100644
--- a/doc/man3/CTLOG_STORE_new.pod
+++ b/doc/man3/CTLOG_STORE_new.pod
@@ -4,6 +4,7 @@
CTLOG_STORE_new_ex,
CTLOG_STORE_new, CTLOG_STORE_free,
+CTLOG_STORE_add0_log,
CTLOG_STORE_load_default_file, CTLOG_STORE_load_file -
Create and populate a Certificate Transparency log list
@@ -15,14 +16,17 @@ Create and populate a Certificate Transparency log list
CTLOG_STORE *CTLOG_STORE_new(void);
void CTLOG_STORE_free(CTLOG_STORE *store);
+ int CTLOG_STORE_add0_log(CTLOG_STORE *store, CTLOG *log);
+
int CTLOG_STORE_load_default_file(CTLOG_STORE *store);
int CTLOG_STORE_load_file(CTLOG_STORE *store, const char *file);
=head1 DESCRIPTION
A CTLOG_STORE is a container for a list of CTLOGs (Certificate Transparency
-logs). The list can be loaded from one or more files and then searched by LogID
-(see RFC 6962, Section 3.2, for the definition of a LogID).
+logs). The list can be loaded from one or more files, or populated
+programmatically, and then searched by LogID (see RFC 6962, Section 3.2, for
+the definition of a LogID).
CTLOG_STORE_new_ex() creates an empty list of CT logs associated with
the library context I<libctx> and the property query string I<propq>.
@@ -30,8 +34,10 @@ the library context I<libctx> and the property query string I<propq>.
CTLOG_STORE_new() does the same thing as CTLOG_STORE_new_ex() but with
the default library context and property query string.
-The CTLOG_STORE is then populated by CTLOG_STORE_load_default_file() or
-CTLOG_STORE_load_file(). CTLOG_STORE_load_default_file() loads from the default
+The CTLOG_STORE is then populated by CTLOG_STORE_load_default_file(),
+CTLOG_STORE_load_file(), or CTLOG_STORE_add0_log().
+
+CTLOG_STORE_load_default_file() loads from the default
file, which is named F<ct_log_list.cnf> in OPENSSLDIR (see the output of
L<openssl-version(1)>). This can be overridden using an environment variable
named B<CTLOG_FILE>. CTLOG_STORE_load_file() loads from a caller-specified file
@@ -50,6 +56,11 @@ The expected format of the file is:
description = Log 2
key = <base64-encoded DER SubjectPublicKeyInfo here>
+CTLOG_STORE_add0_log() adds a single CTLOG (see L<CTLOG_new(3)>) to the store.
+On success, the store takes ownership of I<log> and the caller must not free
+it. On failure, the caller retains ownership and is responsible for freeing
+I<log>.
+
Once a CTLOG_STORE is no longer required, it should be passed to
CTLOG_STORE_free(). This will delete all of the CTLOGs stored within, along
with the CTLOG_STORE itself. If the argument is NULL, nothing is done.
@@ -62,6 +73,8 @@ invalid if it is missing a "key" or "description" field.
=head1 RETURN VALUES
+B<CTLOG_STORE_add0_log> returns 1 on success, 0 on failure.
+
Both B<CTLOG_STORE_load_default_file> and B<CTLOG_STORE_load_file> return 1 if
all CT logs in the file are successfully parsed and loaded, 0 otherwise.
@@ -73,8 +86,8 @@ L<SSL_CTX_set_ctlog_list_file(3)>
=head1 HISTORY
-CTLOG_STORE_new_ex was added in OpenSSL 3.0. All other functions were
-added in OpenSSL 1.1.0.
+CTLOG_STORE_add0_log was added in OpenSSL 4.1. CTLOG_STORE_new_ex was added in
+OpenSSL 3.0. All other functions were added in OpenSSL 1.1.0.
=head1 COPYRIGHT
diff --git a/include/openssl/ct.h.in b/include/openssl/ct.h.in
index a6103980d4..8c83f53a26 100644
--- a/include/openssl/ct.h.in
+++ b/include/openssl/ct.h.in
@@ -499,6 +499,14 @@ CTLOG_STORE *CTLOG_STORE_new(void);
*/
void CTLOG_STORE_free(CTLOG_STORE *store);
+/*
+ * Adds a CT log to a CTLOG_STORE.
+ * Takes ownership of the CTLOG on success - the caller must not free it after
+ * a successful call. On failure, the caller retains ownership.
+ * Returns 1 on success, 0 on failure.
+ */
+__owur int CTLOG_STORE_add0_log(CTLOG_STORE *store, CTLOG *log);
+
/*
* Finds a CT log in the store based on its log ID.
* Returns the CT log, or NULL if no match is found.
diff --git a/test/ct_test.c b/test/ct_test.c
index 62c7b9490f..51cad3eaae 100644
--- a/test/ct_test.c
+++ b/test/ct_test.c
@@ -508,6 +508,131 @@ static int test_ctlog_from_base64(void)
return 0;
return 1;
}
+
+static int test_ctlog_store_add0_log(void)
+{
+ CTLOG_STORE *store = NULL;
+ CTLOG *log = NULL;
+ const CTLOG *found = NULL;
+ const uint8_t *log_id = NULL;
+ size_t log_id_len = 0;
+ int result = 0;
+ const char pkey_base64[] = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmXg8sUUzwBYaWrRb+V0IopzQ6o3U"
+ "yEJ04r5ZrRXGdpYM8K+hB0pXrGRLI0eeWz+3skXrS0IO83AhA3GpRL6s6w==";
+ const char name[] = "test log";
+
+ if (!TEST_ptr(store = CTLOG_STORE_new()))
+ goto end;
+ if (!TEST_true(CTLOG_new_from_base64(&log, pkey_base64, name)))
+ goto end;
+
+ CTLOG_get0_log_id(log, &log_id, &log_id_len);
+ if (!TEST_size_t_eq(log_id_len, CT_V1_HASHLEN))
+ goto end;
+ if (!TEST_ptr_null(CTLOG_STORE_get0_log_by_id(store, log_id, log_id_len)))
+ goto end;
+
+ if (!TEST_true(CTLOG_STORE_add0_log(store, log)))
+ goto end;
+ log = NULL;
+
+ found = CTLOG_STORE_get0_log_by_id(store, log_id, log_id_len);
+ if (!TEST_ptr(found))
+ goto end;
+ if (!TEST_str_eq(CTLOG_get0_name(found), name))
+ goto end;
+
+ result = 1;
+
+end:
+ CTLOG_STORE_free(store);
+ CTLOG_free(log);
+ return result;
+}
+
+static int test_ctlog_store_add0_log_validates_sct(void)
+{
+ CTLOG_STORE *store = NULL;
+ CTLOG *log = NULL;
+ CT_POLICY_EVAL_CTX *ct_policy_ctx = NULL;
+ X509 *cert = NULL, *issuer = NULL;
+ STACK_OF(SCT) *scts = NULL;
+ const X509_EXTENSION *sct_extension = NULL;
+ int result = 0;
+ const char pkey_base64[] = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmXg8sUUzwBYaWrRb+V0IopzQ6o3U"
+ "yEJ04r5ZrRXGdpYM8K+hB0pXrGRLI0eeWz+3skXrS0IO83AhA3GpRL6s6w==";
+
+ if (!TEST_ptr(store = CTLOG_STORE_new()))
+ goto end;
+ if (!TEST_true(CTLOG_new_from_base64(&log, pkey_base64, "test")))
+ goto end;
+ if (!TEST_true(CTLOG_STORE_add0_log(store, log)))
+ goto end;
+ log = NULL;
+
+ if (!TEST_ptr(ct_policy_ctx = CT_POLICY_EVAL_CTX_new()))
+ goto end;
+ CT_POLICY_EVAL_CTX_set_shared_CTLOG_STORE(ct_policy_ctx, store);
+ CT_POLICY_EVAL_CTX_set_time(ct_policy_ctx, 1580335307000ULL);
+
+ if (!TEST_ptr(cert = load_pem_cert(certs_dir, "embeddedSCTs1.pem")))
+ goto end;
+ if (!TEST_ptr(issuer = load_pem_cert(certs_dir, "embeddedSCTs1_issuer.pem")))
+ goto end;
+ CT_POLICY_EVAL_CTX_set1_cert(ct_policy_ctx, cert);
+ CT_POLICY_EVAL_CTX_set1_issuer(ct_policy_ctx, issuer);
+
+ sct_extension = X509_get_ext(cert,
+ X509_get_ext_by_NID(cert, NID_ct_precert_scts, -1));
+ if (!TEST_ptr(sct_extension))
+ goto end;
+ if (!TEST_ptr(scts = X509V3_EXT_d2i(sct_extension)))
+ goto end;
+ if (!TEST_int_eq(sk_SCT_num(scts), 1))
+ goto end;
+ if (!TEST_int_ge(SCT_LIST_validate(scts, ct_policy_ctx), 0))
+ goto end;
+ if (!TEST_int_eq(SCT_get_validation_status(sk_SCT_value(scts, 0)),
+ SCT_VALIDATION_STATUS_VALID))
+ goto end;
+
+ result = 1;
+
+end:
+ CTLOG_STORE_free(store);
+ CTLOG_free(log);
+ CT_POLICY_EVAL_CTX_free(ct_policy_ctx);
+ X509_free(cert);
+ X509_free(issuer);
+ SCT_LIST_free(scts);
+ return result;
+}
+
+static int test_ctlog_store_add0_log_null(void)
+{
+ CTLOG_STORE *store = NULL;
+ CTLOG *log = NULL;
+ int result = 0;
+ const char pkey_base64[] = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmXg8sUUzwBYaWrRb+V0IopzQ6o3U"
+ "yEJ04r5ZrRXGdpYM8K+hB0pXrGRLI0eeWz+3skXrS0IO83AhA3GpRL6s6w==";
+
+ if (!TEST_ptr(store = CTLOG_STORE_new()))
+ goto end;
+ if (!TEST_false(CTLOG_STORE_add0_log(store, NULL)))
+ goto end;
+
+ if (!TEST_true(CTLOG_new_from_base64(&log, pkey_base64, "test")))
+ goto end;
+ if (!TEST_false(CTLOG_STORE_add0_log(NULL, log)))
+ goto end;
+
+ result = 1;
+
+end:
+ CTLOG_STORE_free(store);
+ CTLOG_free(log);
+ return result;
+}
#endif
int setup_tests(void)
@@ -528,6 +653,9 @@ int setup_tests(void)
ADD_TEST(test_encode_tls_sct);
ADD_TEST(test_default_ct_policy_eval_ctx_time_is_now);
ADD_TEST(test_ctlog_from_base64);
+ ADD_TEST(test_ctlog_store_add0_log);
+ ADD_TEST(test_ctlog_store_add0_log_validates_sct);
+ ADD_TEST(test_ctlog_store_add0_log_null);
#else
printf("No CT support\n");
#endif
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 8cbf49ac77..dc7da949c2 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -5715,3 +5715,4 @@ OPENSSL_sk_set_cmp_thunks ? 4_0_0 EXIST::FUNCTION:
ASN1_BIT_STRING_set1 ? 4_0_0 EXIST::FUNCTION:
OSSL_ESS_check_signing_certs_ex ? 4_0_0 EXIST::FUNCTION:
X509v3_delete_extension ? 4_0_0 EXIST::FUNCTION:
+CTLOG_STORE_add0_log ? 4_0_0 EXIST::FUNCTION:CT