summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnder Juaristi <a@juaristi.eus>2023-05-12 20:36:39 +0200
committerAnder Juaristi <a@juaristi.eus>2023-05-12 20:39:32 +0200
commit24cb993e5c2c7f5c463794db6e265a40549b238a (patch)
treebc24a6b0db87d8e93f239b14d56c5f2eba202a07
parent0263dec937e02ab71fd9cb158fa69acf7c374481 (diff)
downloadgnutls-aj-certificate-transparency.tar.gz
Initial import [ci-skip]aj-certificate-transparency
Signed-off-by: Ander Juaristi <a@juaristi.eus>
-rw-r--r--lib/Makefile.am3
-rw-r--r--lib/ct.c354
-rw-r--r--lib/includes/Makefile.am2
-rw-r--r--lib/includes/gnutls/ct.h60
4 files changed, 417 insertions, 2 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 6d4e8d225a..34481d4211 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -83,7 +83,8 @@ COBJECTS = range.c record.c compress.c debug.c cipher.c gthreads.h handshake-tls
cert-session.c handshake-checks.c dtls-sw.c dh-primes.c openpgp_compat.c \
crypto-selftests.c crypto-selftests-pk.c secrets.c extv.c extv.h \
hello_ext_lib.c hello_ext_lib.h ocsp-api.c stek.c cert-cred-rawpk.c \
- iov.c iov.h system/ktls.c system/ktls.h pathbuf.c pathbuf.h
+ iov.c iov.h system/ktls.c system/ktls.h pathbuf.c pathbuf.h \
+ ct.c
if ENABLE_GOST
COBJECTS += vko.c
diff --git a/lib/ct.c b/lib/ct.c
new file mode 100644
index 0000000000..98822925c3
--- /dev/null
+++ b/lib/ct.c
@@ -0,0 +1,354 @@
+#include "gnutls_int.h"
+#include <gnutls/ct.h>
+#include <gnutls/crypto.h>
+#include <nettle/base64.h>
+#include <gnutls/x509-ext.h>
+
+#define SCT_V1_LOGID_SIZE 32
+
+struct ct_log_st {
+ char *name;
+ char *description;
+ gnutls_pubkey_t public_key;
+ uint8_t id[SCT_V1_LOGID_SIZE];
+ time_t not_before, not_after;
+};
+
+struct gnutls_ct_logs_st {
+ struct ct_log_st *logs;
+ size_t size;
+};
+
+static int _gnutls_ct_add_log(struct ct_log_st *log,
+ struct ct_log_st **logs, size_t *size)
+{
+ struct ct_log_st *new_logs;
+
+ new_logs =
+ _gnutls_reallocarray(*logs, *size + 1, sizeof(struct ct_log_st));
+ if (new_logs == NULL)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+ memcpy(&new_logs[*size], log, sizeof(struct ct_log_st));
+ (*size)++;
+ *logs = new_logs;
+
+ return 0;
+}
+
+static int _gnutls_init_log(struct ct_log_st *log,
+ const char *name, const char *description,
+ const gnutls_datum_t *pubkey_data)
+{
+ int retval;
+
+ if (name && (log->name = strdup(name)) == NULL) {
+ gnutls_assert();
+ retval = GNUTLS_E_MEMORY_ERROR;
+ goto bail;
+ }
+ if (description && (log->description = strdup(description)) == NULL) {
+ gnutls_assert();
+ retval = GNUTLS_E_MEMORY_ERROR;
+ goto bail;
+ }
+
+ if ((retval = gnutls_pubkey_init(&log->public_key)) < 0) {
+ gnutls_assert();
+ goto bail;
+ }
+
+ if ((retval = gnutls_pubkey_import(log->public_key,
+ pubkey_data, GNUTLS_X509_FMT_DER)) < 0) {
+ gnutls_assert();
+ goto bail;
+ }
+
+ return 0;
+
+bail:
+ if (log->name)
+ gnutls_free(log->name);
+ if (log->description)
+ gnutls_free(log->description);
+ return gnutls_assert_val(retval);
+}
+
+static void _gnutls_free_log(struct ct_log_st *log)
+{
+ if (log->name)
+ gnutls_free(log->name);
+ if (log->description)
+ gnutls_free(log->description);
+ gnutls_pubkey_deinit(log->public_key);
+
+}
+
+static int decode_base64(const gnutls_datum_t *src, gnutls_datum_t *dst)
+{
+ struct base64_decode_ctx ctx;
+
+ base64_decode_init(&ctx);
+
+ dst->data = gnutls_malloc(BASE64_DECODE_LENGTH(src->size));
+ if (!dst->data)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+ if (base64_decode_update(&ctx, &dst->size, dst->data, src->size, src->data) != 1) {
+ gnutls_assert();
+ goto bail;
+ }
+
+ if (base64_decode_final(&ctx) != 1) {
+ gnutls_assert();
+ goto bail;
+ }
+
+ return 0;
+
+bail:
+ gnutls_free(dst->data);
+ dst->data = NULL;
+ return GNUTLS_E_INVALID_UTF8_STRING;
+}
+
+static struct ct_log_st *_gnutls_lookup_log_by_id(const struct gnutls_ct_logs_st *logs, const uint8_t *log_id)
+{
+ struct ct_log_st *cur_log;
+
+ for (unsigned i = 0; i < logs->size; i++) {
+ cur_log = &logs->logs[i];
+ if (memcmp(cur_log->id, log_id, SCT_V1_LOGID_SIZE) == 0)
+ return cur_log;
+ }
+
+ return NULL;
+}
+
+static int generate_precert(const gnutls_x509_crt_t crt, gnutls_datum_t *out)
+{
+ int retval;
+ size_t output_data_size = 0;
+
+ retval = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_DER,
+ NULL, &output_data_size);
+ if (retval != GNUTLS_E_SHORT_MEMORY_BUFFER || output_data_size == 0)
+ return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
+
+ if ((out->data = gnutls_malloc(output_data_size)) == NULL)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+ out->size = output_data_size;
+
+ if ((retval = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_DER,
+ out->data, (size_t *) &output_data_size)) < 0)
+ return gnutls_assert_val(retval);
+
+ return retval;
+}
+
+static int _gnutls_update_hash_with_precert(gnutls_hash_hd_t md, uint64_t timestamp,
+ const uint8_t *crtder, uint32_t crtder_len,
+ const gnutls_datum_t *issuer_key_hash,
+ const uint8_t *extensions, size_t extensions_len)
+{
+ int retval;
+ uint8_t three_bytes[3] = { 0x00, 0x00, 0x01 };
+ uint8_t u16_be[sizeof(uint16_t)], u32_be[sizeof(uint32_t)], u64_be[sizeof(uint64_t)];
+
+ if ((retval = gnutls_hash(md, three_bytes, 2)) < 0)
+ return gnutls_assert_val(retval);
+
+ _gnutls_write_uint64(timestamp, u64_be);
+ if ((retval = gnutls_hash(md, timestamp, sizeof(timestamp))) < 0)
+ return gnutls_assert_val(retval);
+
+ if ((retval = gnutls_hash(md, &three_bytes[1], 2)) < 0)
+ return gnutls_assert_val(retval);
+
+ /* Issuer key hash */
+ if ((retval = gnutls_hash(md, issuer_key_hash->data, issuer_key_hash->size)) < 0)
+ return gnutls_assert_val(retval);
+
+ /* Certificate in DER encoding, with length prefix */
+ _gnutls_write_uint24(crtder_len, u32_be);
+ if ((retval = gnutls_hash(md, &u32_be[1], 3)) < 0)
+ return gnutls_assert_val(retval);
+ if ((retval = gnutls_hash(md, crtder, crtder_len)) < 0)
+ return gnutls_assert_val(retval);
+
+ /* Extensions, with length prefix */
+ /* TODO complete this */
+
+ return 0;
+}
+
+static int _gnutls_ct_sct_verify_precert(const gnutls_datum_t *precert_digest, const gnutls_datum_t *signature,
+ gnutls_sign_algorithm_t algo, const struct ct_log_st *log,
+ gnutls_time_func time_func)
+{
+ time_t curtime;
+
+ if (time_func)
+ curtime = time_func(NULL);
+
+ if (time_func && log->not_before > curtime)
+ return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+ if (time_func && log->not_after < curtime)
+ return gnutls_assert_val(GNUTLS_E_EXPIRED);
+
+ /* if (gnutls_pubkey_verify_data2(&log.public_key, algo, 0, precert, signature) < 0) */
+ /* return gnutls_assert_val(GNUTLS_E_PK_SIG_VERIFY_FAILED); */
+ if (gnutls_pubkey_verify_hash2(log->public_key, algo, 0, precert_digest, signature) < 0)
+ return gnutls_assert_val(GNUTLS_E_PK_SIG_VERIFY_FAILED);
+
+ return 0;
+}
+
+int gnutls_ct_logs_init(gnutls_ct_logs_t * logs)
+{
+ if (!logs)
+ return GNUTLS_E_INVALID_REQUEST;
+
+ *logs = gnutls_malloc(sizeof(struct gnutls_ct_logs_st));
+ if (!*logs)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+ (*logs)->logs = NULL;
+ (*logs)->size = 0;
+
+ return 0;
+}
+
+void gnutls_ct_logs_deinit(gnutls_ct_logs_t logs)
+{
+ if (logs) {
+ if (logs->size > 0) {
+ for (unsigned i = 0; i < logs->size; i++)
+ _gnutls_free_log(&logs->logs[i]);
+ }
+ gnutls_free(logs->logs);
+ gnutls_free(logs);
+ }
+}
+
+int gnutls_ct_sct_validate(const gnutls_x509_ct_scts_t scts, unsigned idx,
+ const gnutls_ct_logs_t logs,
+ gnutls_x509_crt_t crt, gnutls_time_func time_func)
+{
+ int retval;
+ time_t timestamp;
+ gnutls_sign_algorithm_t sigalg;
+ gnutls_datum_t logid, signature;
+ gnutls_hash_hd_t md;
+ struct ct_log_st *log;
+ uint8_t signed_data_digest[gnutls_hash_get_len(GNUTLS_DIG_SHA256)];
+ gnutls_datum_t signed_data_digest_datum = {
+ .data = signed_data_digest,
+ .size = gnutls_hash_get_len(GNUTLS_DIG_SHA256)
+ };
+ gnutls_datum_t ikey_hash, crtder = {
+ .data = NULL,
+ .size = 0
+ };
+
+ if ((retval = gnutls_x509_ct_sct_get(scts, idx,
+ &timestamp, &logid, &sigalg, &signature)) < 0) {
+ gnutls_assert();
+ return retval;
+ }
+
+ if (logid.size != SCT_V1_LOGID_SIZE)
+ return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
+
+ /* Lookup the log */
+ if ((log = _gnutls_lookup_log_by_id(logs, logid.data)) == NULL)
+ return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS);
+
+ if ((retval = gnutls_hash_init(&md, GNUTLS_DIG_SHA256)) < 0) {
+ gnutls_assert();
+ goto bail;
+ }
+
+ /* Compute preCert as DER */
+ if ((retval = generate_precert(crt, &crtder)) < 0) {
+ gnutls_assert();
+ goto bail;
+ }
+
+ if ((retval = _gnutls_update_hash_with_precert(md, timestamp,
+ crtder.data, crtder.size, &ikey_hash,
+ NULL, 0)) < 0) {
+ gnutls_assert();
+ goto bail;
+ }
+
+ /* gnutls_hash_output(md, signed_data_digest); */
+ gnutls_hash_deinit(md, signed_data_digest);
+
+ /* if ((retval = gnutls_pubkey_verify_hash2(log->public_key, log->sign_algo, 0, */
+ /* &signed_data_digest_datum, &signature)) < 0) */
+ /* return gnutls_assert_val(retval); */
+
+ return _gnutls_ct_sct_verify_precert(&signed_data_digest_datum, &signature, sigalg, log, time_func);
+
+bail:
+ gnutls_hash_deinit(md, NULL);
+ _gnutls_free_datum(&crtder);
+ return retval;
+}
+
+int gnutls_ct_add_log(gnutls_ct_logs_t logs,
+ const char *name, const char *description,
+ const gnutls_datum_t *key,
+ time_t not_before, time_t not_after,
+ unsigned flags)
+{
+ int retval;
+ struct ct_log_st ct_log;
+ gnutls_datum_t realkey;
+ bool realkey_must_be_freed = 0;
+
+ if (!logs || !key || !key->data || !key->size)
+ return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+
+ memset(&ct_log, 0, sizeof(struct ct_log_st));
+
+ /*
+ * If key is in base64, decode it first.
+ * This will allocate new memory that must be freed after public key import.
+ */
+ if (flags == GNUTLS_CT_KEY_AS_BASE64) {
+ if ((retval = decode_base64(key, &realkey)) < 0) {
+ gnutls_assert();
+ goto bail;
+ }
+
+ key = &realkey;
+ realkey_must_be_freed = 1;
+ }
+
+ if ((retval = _gnutls_init_log(&ct_log, name, description, key)) < 0) {
+ gnutls_assert();
+ goto bail;
+ }
+
+ /* Pre-compute key's SHA-256 hash for faster lookup later */
+ if ((retval = gnutls_hash_fast(GNUTLS_DIG_SHA256,
+ key->data, key->size, ct_log.id)) < 0) {
+ gnutls_assert();
+ goto bail;
+ }
+
+ if ((retval = _gnutls_ct_add_log(&ct_log, &logs->logs, &logs->size)) < 0) {
+ gnutls_assert();
+ goto bail;
+ }
+
+ retval = 0;
+
+bail:
+ if (realkey_must_be_freed)
+ _gnutls_free_datum(&realkey);
+ return retval;
+}
diff --git a/lib/includes/Makefile.am b/lib/includes/Makefile.am
index 42bec1c35e..78b02e618f 100644
--- a/lib/includes/Makefile.am
+++ b/lib/includes/Makefile.am
@@ -22,7 +22,7 @@ nobase_include_HEADERS = gnutls/x509.h gnutls/pkcs12.h gnutls/compat.h \
gnutls/openpgp.h gnutls/crypto.h gnutls/pkcs11.h \
gnutls/abstract.h gnutls/dtls.h gnutls/ocsp.h gnutls/tpm.h \
gnutls/x509-ext.h gnutls/self-test.h gnutls/system-keys.h \
- gnutls/urls.h gnutls/pkcs7.h gnutls/socket.h
+ gnutls/urls.h gnutls/pkcs7.h gnutls/socket.h gnutls/ct.h
if ENABLE_CXX
nobase_include_HEADERS += gnutls/gnutlsxx.h
diff --git a/lib/includes/gnutls/ct.h b/lib/includes/gnutls/ct.h
new file mode 100644
index 0000000000..f3a9dfd281
--- /dev/null
+++ b/lib/includes/gnutls/ct.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 Free Software Foundation, Inc.
+ *
+ * Author: Ander Juaristi
+ *
+ * This file is part of GnuTLS.
+ *
+ * The GnuTLS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>
+ *
+ */
+
+/* This file contains the types and prototypes for the X.509
+ * certificate and CRL handling functions.
+ */
+/* This file contains the types and prototypes for handling
+ * the Certificate Transparency (CT) v2.0 structures defined in RFC 9162
+ */
+
+#ifndef GNUTLS_CT_H
+# define GNUTLS_CT_H
+
+# include <gnutls/gnutls.h>
+# include <gnutls/x509-ext.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct gnutls_ct_logs_st *gnutls_ct_logs_t;
+
+int gnutls_ct_logs_init(gnutls_ct_logs_t * logs);
+void gnutls_ct_logs_deinit(gnutls_ct_logs_t logs);
+
+#define GNUTLS_CT_KEY_AS_DER 0
+#define GNUTLS_CT_KEY_AS_BASE64 1
+int gnutls_ct_add_log(gnutls_ct_logs_t logs,
+ const char * name,
+ const char * description,
+ const gnutls_datum_t * key,
+ time_t not_before, time_t not_after,
+ unsigned flags);
+int gnutls_ct_sct_validate(const gnutls_x509_ct_scts_t scts, unsigned idx,
+ const gnutls_ct_logs_t logs,
+ gnutls_x509_crt_t crt, gnutls_time_func time_func);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GNUTLS_CT_H */