From 852a2ebe6eff6f53cdede602f2e6e490e03621ea Mon Sep 17 00:00:00 2001 From: Zhi Guan Date: Wed, 10 Jun 2026 17:25:11 +0800 Subject: [PATCH] Add SCT verification --- CMakeLists.txt | 1 + include/gmssl/sct.h | 166 ++++++++++++++++++ src/sct.c | 408 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 575 insertions(+) create mode 100644 include/gmssl/sct.h create mode 100644 src/sct.c diff --git a/CMakeLists.txt b/CMakeLists.txt index c71e2d02..00c1bf05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -561,6 +561,7 @@ if (ENABLE_TLS) src/tls_ext.c src/tls_psk.c src/tls_sni.c + src/sct.c src/tls_sct.c src/tls_ocsp.c src/tls_cookie.c diff --git a/include/gmssl/sct.h b/include/gmssl/sct.h new file mode 100644 index 00000000..8401e359 --- /dev/null +++ b/include/gmssl/sct.h @@ -0,0 +1,166 @@ +/* + * Copyright 2014-2026 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + + +#ifndef GMSSL_SCT_H +#define GMSSL_SCT_H + + +#include +#include +#include +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* +Certificate Transparency (RFC 6962) uses TLS-style presentation language for +Signed Certificate Timestamp (SCT) objects. When SCTs are carried in an X.509 +or OCSP extension, the extension value is an ASN.1 OCTET STRING whose contents +are the TLS-serialized SignedCertificateTimestampList. + +id-ct OBJECT IDENTIFIER ::= { 1 3 6 1 4 1 11129 2 4 } + +id-ct-precertificate-scts OBJECT IDENTIFIER ::= { id-ct 2 } + +ExtnValue contents ::= + SignedCertificateTimestampList +*/ + + +enum { + SCT_version_v1 = 0, +}; + +enum { + SCT_signature_type_certificate_timestamp = 0, + SCT_signature_type_tree_hash = 1, +}; + +enum { + SCT_log_entry_type_x509_entry = 0, + SCT_log_entry_type_precert_entry = 1, +}; + +#define SCT_LOG_ID_SIZE 32 +#define SCT_ISSUER_KEY_HASH_SIZE 32 +#define SCT_MAX_SIGNED_DATA_SIZE 65536 + +/* +struct { + Version sct_version; + SignatureType signature_type = certificate_timestamp; + uint64 timestamp; + LogEntryType entry_type; + select(entry_type) { + case x509_entry: ASN.1Cert; + case precert_entry: PreCert; + } signed_entry; + CtExtensions extensions; +} digitally_signed; + +ASN.1Cert ::= opaque <1..2^24-1>; + +PreCert ::= struct { + opaque issuer_key_hash[32]; + TBSCertificate tbs_certificate; +} + +TBSCertificate ::= opaque <1..2^24-1>; + +CtExtensions ::= opaque <0..2^16-1>; +*/ +int sct_signed_data_to_bytes(int version, uint64_t timestamp, int entry_type, + const uint8_t issuer_key_hash[SCT_ISSUER_KEY_HASH_SIZE], + const uint8_t *entry, size_t entry_len, + const uint8_t *exts, size_t extslen, + uint8_t **out, size_t *outlen); +int sct_signed_data_construct(const uint8_t *sct, size_t sct_len, + int entry_type, const uint8_t issuer_key_hash[SCT_ISSUER_KEY_HASH_SIZE], + const uint8_t *entry, size_t entry_len, + uint8_t **out, size_t *outlen); + +/* +DigitallySigned ::= struct { + uint16 sig_algorithm; + opaque signature<0..2^16-1>; +} +*/ +int signed_certificate_timestamp_signature_to_bytes( + int sig_alg, const uint8_t *sig, size_t siglen, + uint8_t **out, size_t *outlen); +int signed_certificate_timestamp_signature_from_bytes( + int *sig_alg, const uint8_t **sig, size_t *siglen, + const uint8_t **in, size_t *inlen); +int signed_certificate_timestamp_signature_print(FILE *fp, int fmt, int ind, + const char *label, const uint8_t *d, size_t dlen); + + +/* +SignedCertificateTimestamp ::= struct { + Version sct_version; + LogID id; + uint64 timestamp; + CtExtensions extensions; + DigitallySigned signature; +} + +Version ::= enum { v1(0), (255) } + +LogID ::= opaque key_id[32]; + +CtExtensions ::= opaque <0..2^16-1>; +*/ +int signed_certificate_timestamp_to_bytes(int version, + const uint8_t log_id[SCT_LOG_ID_SIZE], uint64_t timestamp, + const uint8_t *exts, size_t extslen, + int sig_alg, const uint8_t *sig, size_t siglen, + uint8_t **out, size_t *outlen); +int signed_certificate_timestamp_from_bytes(int *version, + const uint8_t **log_id, uint64_t *timestamp, + const uint8_t **exts, size_t *extslen, + int *sig_alg, const uint8_t **sig, size_t *siglen, + const uint8_t **in, size_t *inlen); +int signed_certificate_timestamp_print(FILE *fp, int fmt, int ind, + const char *label, const uint8_t *d, size_t dlen); +int signed_certificate_timestamp_verify(const uint8_t *sct, size_t sct_len, + const uint8_t *signed_data, size_t signed_data_len, + X509_KEY *key, const DIGEST *digest); + + +/* +在验证sct_list的时候,我们需要提供一组公钥的信息,包括X509_KEY, Key_hash, URL , description 这三个是最重要的了 +*/ + + +typedef struct { + X509_KEY log_key; + uint8_t log_id[32]; + const char *log_name; + const char *log_url; + const char *log_dns_domain; +} CT_LOG_INFO; + +int sct_list_verify(const uint8_t *sct_list, size_t sct_list_len, + int entry_type, const uint8_t issuer_key_hash[SCT_ISSUER_KEY_HASH_SIZE], + const uint8_t *entry, size_t entry_len, + const CT_LOG_INFO *ct_logs, size_t ct_logs_cnt, + size_t at_least); + + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/sct.c b/src/sct.c new file mode 100644 index 00000000..190df334 --- /dev/null +++ b/src/sct.c @@ -0,0 +1,408 @@ +/* + * Copyright 2014-2026 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +int sct_signed_data_to_bytes(int version, uint64_t timestamp, int entry_type, + const uint8_t issuer_key_hash[SCT_ISSUER_KEY_HASH_SIZE], + const uint8_t *entry, size_t entry_len, + const uint8_t *exts, size_t extslen, + uint8_t **out, size_t *outlen) +{ + if (version < 0 || version > 0xff + || !entry || !entry_len || entry_len > 0xffffff + || extslen > 0xffff || (extslen && !exts) || !outlen) { + error_print(); + return -1; + } + switch (entry_type) { + case SCT_log_entry_type_x509_entry: + break; + case SCT_log_entry_type_precert_entry: + if (!issuer_key_hash) { + error_print(); + return -1; + } + break; + default: + error_print(); + return -1; + } + + tls_uint8_to_bytes((uint8_t)version, out, outlen); + tls_uint8_to_bytes(SCT_signature_type_certificate_timestamp, out, outlen); + tls_uint64_to_bytes(timestamp, out, outlen); + tls_uint16_to_bytes((uint16_t)entry_type, out, outlen); + switch (entry_type) { + case SCT_log_entry_type_x509_entry: + tls_uint24array_to_bytes(entry, entry_len, out, outlen); + break; + case SCT_log_entry_type_precert_entry: + tls_array_to_bytes(issuer_key_hash, SCT_ISSUER_KEY_HASH_SIZE, + out, outlen); + tls_uint24array_to_bytes(entry, entry_len, out, outlen); + break; + } + tls_uint16array_to_bytes(exts, extslen, out, outlen); + return 1; +} + +int sct_signed_data_construct(const uint8_t *sct, size_t sct_len, + int entry_type, const uint8_t issuer_key_hash[SCT_ISSUER_KEY_HASH_SIZE], + const uint8_t *entry, size_t entry_len, + uint8_t **out, size_t *outlen) +{ + int version; + const uint8_t *log_id; + uint64_t timestamp; + const uint8_t *exts; + size_t extslen; + int sig_alg; + const uint8_t *sig; + size_t siglen; + + if (!sct || !sct_len || !entry || !entry_len || !outlen) { + error_print(); + return -1; + } + if (signed_certificate_timestamp_from_bytes(&version, &log_id, ×tamp, + &exts, &extslen, &sig_alg, &sig, &siglen, &sct, &sct_len) != 1 + || sct_len) { + error_print(); + return -1; + } + if (sct_signed_data_to_bytes(version, timestamp, entry_type, + issuer_key_hash, entry, entry_len, exts, extslen, + out, outlen) != 1) { + error_print(); + return -1; + } + (void)log_id; + (void)sig_alg; + (void)sig; + (void)siglen; + return 1; +} + +int signed_certificate_timestamp_signature_to_bytes( + int sig_alg, const uint8_t *sig, size_t siglen, + uint8_t **out, size_t *outlen) +{ + if (tls_signature_scheme_algorithm_oid(sig_alg) == OID_undef + || siglen > 0xffff + || (siglen && !sig) || !outlen) { + error_print(); + return -1; + } + tls_uint16_to_bytes((uint16_t)sig_alg, out, outlen); + tls_uint16array_to_bytes(sig, siglen, out, outlen); + return 1; +} + +int signed_certificate_timestamp_signature_from_bytes( + int *sig_alg, const uint8_t **sig, size_t *siglen, + const uint8_t **in, size_t *inlen) +{ + uint16_t scheme; + + if (!sig_alg || !sig || !siglen || !in || !(*in) || !inlen) { + error_print(); + return -1; + } + if (tls_uint16_from_bytes(&scheme, in, inlen) != 1 + || tls_uint16array_from_bytes(sig, siglen, in, inlen) != 1) { + error_print(); + return -1; + } + *sig_alg = scheme; + return 1; +} + +int signed_certificate_timestamp_signature_print(FILE *fp, int fmt, int ind, + const char *label, const uint8_t *d, size_t dlen) +{ + int sig_alg; + const char *sig_alg_name; + const uint8_t *sig; + size_t siglen; + + if (!fp || !label || !d) { + error_print(); + return -1; + } + if (signed_certificate_timestamp_signature_from_bytes(&sig_alg, &sig, + &siglen, &d, &dlen) != 1 || dlen) { + error_print(); + return -1; + } + + sig_alg_name = tls_signature_scheme_name(sig_alg); + format_print(fp, fmt, ind, "%s\n", label); + format_print(fp, fmt, ind + 4, "sig_algorithm: %s (%04x)\n", + sig_alg_name ? sig_alg_name : "unknown", sig_alg); + format_bytes(fp, fmt, ind + 4, "signature", sig, siglen); + return 1; +} + +int signed_certificate_timestamp_to_bytes(int version, + const uint8_t log_id[SCT_LOG_ID_SIZE], uint64_t timestamp, + const uint8_t *exts, size_t extslen, + int sig_alg, const uint8_t *sig, size_t siglen, + uint8_t **out, size_t *outlen) +{ + if (version < 0 || version > 0xff || !log_id || extslen > 0xffff + || (extslen && !exts) || siglen > 0xffff || (siglen && !sig) + || tls_signature_scheme_algorithm_oid(sig_alg) == OID_undef + || !outlen) { + error_print(); + return -1; + } + tls_uint8_to_bytes((uint8_t)version, out, outlen); + tls_array_to_bytes(log_id, SCT_LOG_ID_SIZE, out, outlen); + tls_uint64_to_bytes(timestamp, out, outlen); + tls_uint16array_to_bytes(exts, extslen, out, outlen); + if (signed_certificate_timestamp_signature_to_bytes(sig_alg, sig, siglen, + out, outlen) != 1) { + error_print(); + return -1; + } + return 1; +} + +int signed_certificate_timestamp_from_bytes(int *version, + const uint8_t **log_id, uint64_t *timestamp, + const uint8_t **exts, size_t *extslen, + int *sig_alg, const uint8_t **sig, size_t *siglen, + const uint8_t **in, size_t *inlen) +{ + uint8_t ver; + + if (!version || !log_id || !timestamp || !exts || !extslen + || !sig_alg || !sig || !siglen || !in || !(*in) || !inlen) { + error_print(); + return -1; + } + if (tls_uint8_from_bytes(&ver, in, inlen) != 1 + || tls_array_from_bytes(log_id, SCT_LOG_ID_SIZE, in, inlen) != 1 + || tls_uint64_from_bytes(timestamp, in, inlen) != 1 + || tls_uint16array_from_bytes(exts, extslen, in, inlen) != 1 + || signed_certificate_timestamp_signature_from_bytes(sig_alg, sig, + siglen, in, inlen) != 1) { + error_print(); + return -1; + } + *version = ver; + return 1; +} + +int signed_certificate_timestamp_print(FILE *fp, int fmt, int ind, + const char *label, const uint8_t *d, size_t dlen) +{ + int version; + const uint8_t *log_id; + uint64_t timestamp; + const uint8_t *exts; + size_t extslen; + int sig_alg; + const char *sig_alg_name; + const uint8_t *sig; + size_t siglen; + + if (!fp || !label || !d) { + error_print(); + return -1; + } + if (signed_certificate_timestamp_from_bytes(&version, &log_id, ×tamp, + &exts, &extslen, &sig_alg, &sig, &siglen, &d, &dlen) != 1 + || dlen) { + error_print(); + return -1; + } + + sig_alg_name = tls_signature_scheme_name(sig_alg); + format_print(fp, fmt, ind, "%s\n", label); + format_print(fp, fmt, ind + 4, "version: %d\n", version); + format_bytes(fp, fmt, ind + 4, "log_id", log_id, SCT_LOG_ID_SIZE); + format_print(fp, fmt, ind + 4, "timestamp: %" PRIu64 "\n", timestamp); + format_bytes(fp, fmt, ind + 4, "extensions", exts, extslen); + format_print(fp, fmt, ind + 4, "sig_algorithm: %s (%04x)\n", + sig_alg_name ? sig_alg_name : "unknown", sig_alg); + format_bytes(fp, fmt, ind + 4, "signature", sig, siglen); + return 1; +} + +int signed_certificate_timestamp_verify(const uint8_t *sct, size_t sct_len, + const uint8_t *signed_data, size_t signed_data_len, + X509_KEY *key, const DIGEST *digest) +{ + int version; + const uint8_t *log_id; + uint64_t timestamp; + const uint8_t *exts; + size_t extslen; + int sig_alg; + int sig_alg_oid; + const uint8_t *sig; + size_t siglen; + uint8_t dgst[DIGEST_MAX_SIZE]; + size_t dgstlen; + X509_SIGN_CTX verify_ctx; + + if (!sct || !sct_len || !signed_data || !signed_data_len + || !key || !digest) { + error_print(); + return -1; + } + if (signed_certificate_timestamp_from_bytes(&version, &log_id, ×tamp, + &exts, &extslen, &sig_alg, &sig, &siglen, &sct, &sct_len) != 1 + || sct_len) { + error_print(); + return -1; + } + if (version != SCT_version_v1) { + error_print(); + return -1; + } + + if (x509_public_key_digest_ex(key, digest, dgst, &dgstlen) != 1 + || dgstlen != SCT_LOG_ID_SIZE) { + error_print(); + return -1; + } + if (memcmp(log_id, dgst, SCT_LOG_ID_SIZE) != 0) { + error_print(); + return -1; + } + + sig_alg_oid = tls_signature_scheme_algorithm_oid(sig_alg); + if (sig_alg_oid == OID_undef) { + error_print(); + return -1; + } + if (x509_verify_init(&verify_ctx, key, NULL, 0, sig, siglen) != 1 + || verify_ctx.sign_algor != sig_alg_oid + || x509_verify_update(&verify_ctx, signed_data, signed_data_len) != 1 + || x509_verify_finish(&verify_ctx) != 1) { + error_print(); + return -1; + } + return 1; +} + +int sct_list_verify(const uint8_t *sct_list, size_t sct_list_len, + int entry_type, const uint8_t issuer_key_hash[SCT_ISSUER_KEY_HASH_SIZE], + const uint8_t *entry, size_t entry_len, + const CT_LOG_INFO *ct_logs, size_t ct_logs_cnt, + size_t at_least) +{ + const uint8_t *serialized_scts; + size_t serialized_scts_len; + size_t success_count = 0; + size_t i; + + if (!sct_list || !sct_list_len || !entry || !entry_len + || !ct_logs || !ct_logs_cnt || !at_least + || at_least > ct_logs_cnt) { + error_print(); + return -1; + } + if (tls_uint16array_from_bytes(&serialized_scts, &serialized_scts_len, + &sct_list, &sct_list_len) != 1 || sct_list_len) { + error_print(); + return -1; + } + + for (i = 0; i < ct_logs_cnt; i++) { + const uint8_t *scts = serialized_scts; + size_t scts_len = serialized_scts_len; + + while (scts_len) { + const uint8_t *sct; + size_t sct_len; + int version; + const uint8_t *log_id; + uint64_t timestamp; + const uint8_t *exts; + size_t extslen; + int sig_alg; + int sig_alg_oid; + const uint8_t *sig; + size_t siglen; + uint8_t signed_data[SCT_MAX_SIGNED_DATA_SIZE]; + uint8_t *p = signed_data; + size_t signed_data_len = 0; + X509_SIGN_CTX verify_ctx; + + if (tls_uint16array_from_bytes(&sct, &sct_len, + &scts, &scts_len) != 1) { + error_print(); + return -1; + } + if (signed_certificate_timestamp_from_bytes(&version, &log_id, + ×tamp, &exts, &extslen, &sig_alg, &sig, &siglen, + &sct, &sct_len) != 1 || sct_len) { + error_print(); + return -1; + } + if (version != SCT_version_v1 + || memcmp(log_id, ct_logs[i].log_id, SCT_LOG_ID_SIZE) != 0) { + continue; + } + + sig_alg_oid = tls_signature_scheme_algorithm_oid(sig_alg); + if (sig_alg_oid == OID_undef) { + continue; + } + if (sct_signed_data_to_bytes(version, timestamp, entry_type, + issuer_key_hash, entry, entry_len, exts, extslen, + NULL, &signed_data_len) != 1) { + error_print(); + return -1; + } + if (signed_data_len > sizeof(signed_data)) { + error_print(); + return -1; + } + signed_data_len = 0; + if (sct_signed_data_to_bytes(version, timestamp, entry_type, + issuer_key_hash, entry, entry_len, exts, extslen, + &p, &signed_data_len) != 1) { + error_print(); + return -1; + } + + if (x509_verify_init(&verify_ctx, &ct_logs[i].log_key, + NULL, 0, sig, siglen) == 1 + && verify_ctx.sign_algor == sig_alg_oid + && x509_verify_update(&verify_ctx, + signed_data, signed_data_len) == 1 + && x509_verify_finish(&verify_ctx) == 1) { + success_count++; + break; + } + } + if (success_count >= at_least) { + return 1; + } + } + + //error_print(); + return 0; +}