aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeil Horman <[email protected]>2026-05-14 17:39:05 +0200
committerTomas Mraz <[email protected]>2026-10-21 18:44:28 +0200
commit54dacb1710cf0dd00d7c6f2c4c7ee7a19d5028a4 (patch)
tree0d5ad28707551e83eb7b57bc17895f3999801d3a
parentCHANGES.md: formatting fixes (diff)
downloadopenssl-54dacb1710cf0dd00d7c6f2c4c7ee7a19d5028a4.tar.gz
openssl-54dacb1710cf0dd00d7c6f2c4c7ee7a19d5028a4.zip
Cache decoded certs on client in CTX to avoid multiple asn1 parsing
our TLS client path has to do an ASN1 decode on every new connection it establishes. Other TLS stacks (notably rustls) seems to not do this decode, opting instead to to do just enough der parsing to verify the certificate chain. We can't really do that, but we can maybe do some things to speed up this path. Most notably we can cache the received certificate der string in the SSL_CTX along with a previously decoded X509 certificate to avoid the overhead of additional ASN1 parsings in future connections Reviewed-by: Bob Beck <[email protected]> Reviewed-by: Nikola Pajkovsky <[email protected]> Merge-date: Wed Oct 21 16:44:29 2026 Merged-from: https://github.com/openssl/openssl/pull/33085
-rw-r--r--ssl/ssl_lib.c107
-rw-r--r--ssl/ssl_local.h14
-rw-r--r--ssl/statem/statem_clnt.c35
3 files changed, 143 insertions, 13 deletions
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index ff039c71388..36f506dfe5b 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -4432,6 +4432,9 @@ SSL_CTX *SSL_CTX_new_ex(OSSL_LIB_CTX *libctx, const char *propq,
goto err;
if ((ret->tktenc = EVP_CIPHER_fetch(libctx, "AES-256-CBC", propq)) == NULL)
goto err;
+ if ((ret->sha1 = EVP_MD_fetch(libctx, "SHA1", propq)) == NULL)
+ goto err;
+
#if defined(OPENSSL_HAVE_TLS1PRF)
if ((ret->tls1prf = EVP_KDF_fetch(libctx, OSSL_KDF_NAME_TLS1_PRF, propq)) == NULL)
goto err;
@@ -4458,6 +4461,14 @@ SSL_CTX *SSL_CTX_new_ex(OSSL_LIB_CTX *libctx, const char *propq,
ERR_raise(ERR_LIB_SSL, ERR_R_X509_LIB);
goto err;
}
+
+ if (!SSL_CTX_is_server(ret)) {
+ ret->handshake_certs = sk_X509_HS_CACHE_ENT_new(NULL);
+ if (ret->handshake_certs == NULL) {
+ ERR_raise(ERR_LIB_SSL, ERR_R_CRYPTO_LIB);
+ goto err;
+ }
+ }
#ifndef OPENSSL_NO_CT
ret->ctlog_store = CTLOG_STORE_new_ex(libctx, propq);
if (ret->ctlog_store == NULL) {
@@ -4717,6 +4728,13 @@ int SSL_CTX_up_ref(SSL_CTX *ctx)
return ((i > 1) ? 1 : 0);
}
+static void free_hs_cache_ent(X509_HS_CACHE_ENT *entry)
+{
+ X509_free(entry->cert);
+ OPENSSL_free(entry);
+ return;
+}
+
void SSL_CTX_free(SSL_CTX *a)
{
int i;
@@ -4758,6 +4776,7 @@ void SSL_CTX_free(SSL_CTX *a)
EVP_MAC_free(a->hmac);
EVP_MD_free(a->sha256);
EVP_CIPHER_free(a->tktenc);
+ EVP_MD_free(a->sha1);
#ifdef OPENSSL_HAVE_TLS1PRF
EVP_KDF_free(a->tls1prf);
#endif
@@ -4819,6 +4838,7 @@ void SSL_CTX_free(SSL_CTX *a)
OPENSSL_free(a->client_cert_type);
OPENSSL_free(a->server_cert_type);
+ sk_X509_HS_CACHE_ENT_pop_free(a->handshake_certs, free_hs_cache_ent);
CRYPTO_THREAD_lock_free(a->lock);
CRYPTO_FREE_REF(&a->references);
#ifdef TSAN_REQUIRES_LOCKING
@@ -4841,6 +4861,93 @@ void SSL_CTX_free(SSL_CTX *a)
OPENSSL_free(a);
}
+X509 *ssl_ctx_find_handshake_cert(SSL_CTX *sctx, const unsigned char *certbytes,
+ size_t cert_len, unsigned char *sha1_hash)
+{
+ int idx;
+ X509_HS_CACHE_ENT *find = NULL;
+ X509 *ret = NULL;
+ unsigned int len = SHA_DIGEST_LENGTH;
+ int cache_len;
+
+ if (sctx->handshake_certs == NULL)
+ return NULL;
+
+ if (!EVP_Digest(certbytes, cert_len, sha1_hash, &len, sctx->sha1, NULL))
+ return NULL;
+
+ if (len != SHA_DIGEST_LENGTH)
+ return NULL;
+
+ if (!CRYPTO_THREAD_read_lock(sctx->lock))
+ return NULL;
+
+ cache_len = sk_X509_HS_CACHE_ENT_num(sctx->handshake_certs);
+ for (idx = 0; idx < cache_len; idx++) {
+ find = sk_X509_HS_CACHE_ENT_value(sctx->handshake_certs, idx);
+ if (!memcmp(find->sha1_hash, sha1_hash, SHA_DIGEST_LENGTH)) {
+ X509_up_ref(find->cert);
+ ret = find->cert;
+ break;
+ }
+ }
+ CRYPTO_THREAD_unlock(sctx->lock);
+ return ret;
+}
+
+X509 *ssl_ctx_add_handshake_cert(SSL_CTX *sctx, X509 *cert,
+ unsigned char *sha1_hash)
+{
+ X509_HS_CACHE_ENT *tmp, *del;
+ X509 *ret = cert;
+
+ if (sctx->handshake_certs == NULL)
+ return cert;
+
+ /*
+ * Force extension caching on the X509 now, while we still "own" it
+ * exclusively. From this point on the cached cert is treated as
+ * immutable by all readers (they only X509_up_ref it).
+ */
+ if (X509_check_purpose(cert, -1, 0) <= 0)
+ return cert;
+
+ tmp = OPENSSL_malloc(sizeof(X509_HS_CACHE_ENT));
+ if (tmp == NULL)
+ return cert;
+
+ /*
+ * We had to compute the hash of the der string in the find routine, so
+ * we can reuse it here
+ */
+ memcpy(tmp->sha1_hash, sha1_hash, SHA_DIGEST_LENGTH);
+ tmp->cert = cert;
+ X509_up_ref(cert);
+
+ if (!CRYPTO_THREAD_write_lock(sctx->lock)) {
+ free_hs_cache_ent(tmp);
+ return cert;
+ }
+
+ if (sk_X509_HS_CACHE_ENT_num(sctx->handshake_certs) > 64) {
+ /*
+ * Evict the oldest entry (index 0). Deleting index 64 with
+ * num > 64 would remove the most-recently-pushed entry, which
+ * is the opposite of what we want.
+ */
+ del = sk_X509_HS_CACHE_ENT_delete(sctx->handshake_certs, 0);
+ if (del != NULL)
+ free_hs_cache_ent(del);
+ }
+ if (!sk_X509_HS_CACHE_ENT_push(sctx->handshake_certs, tmp)) {
+ free_hs_cache_ent(tmp);
+ ret = cert;
+ }
+
+ CRYPTO_THREAD_unlock(sctx->lock);
+ return ret;
+}
+
void SSL_CTX_set_default_passwd_cb(SSL_CTX *ctx, pem_password_cb *cb)
{
ctx->default_passwd_callback = cb;
diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h
index 6ba7c549118..283065640cb 100644
--- a/ssl/ssl_local.h
+++ b/ssl/ssl_local.h
@@ -866,6 +866,13 @@ typedef struct {
#define OPENSSL_HAVE_TLS1PRF
#endif
+typedef struct x509_hs_cache_entry_st {
+ unsigned char sha1_hash[SHA_DIGEST_LENGTH];
+ X509 *cert;
+} X509_HS_CACHE_ENT;
+
+DEFINE_STACK_OF(X509_HS_CACHE_ENT)
+
struct ssl_ctx_st {
OSSL_LIB_CTX *libctx;
@@ -877,6 +884,7 @@ struct ssl_ctx_st {
STACK_OF(SSL_CIPHER) *tls13_ciphersuites;
struct x509_store_st /* X509_STORE */ *cert_store;
LHASH_OF(SSL_SESSION) *sessions;
+ STACK_OF(X509_HS_CACHE_ENT) *handshake_certs;
EVP_MAC *hmac;
EVP_MD *sha256;
EVP_CIPHER *tktenc;
@@ -976,6 +984,8 @@ struct ssl_ctx_st {
CRYPTO_EX_DATA ex_data;
+ EVP_MD *sha1; /* For SSLv3/TLSv1 'ssl3-sha1' */
+
STACK_OF(X509) *extra_certs;
STACK_OF(SSL_COMP) *comp_methods; /* stack of SSL_COMP, TLSv1 */
@@ -1274,6 +1284,10 @@ struct ssl_ctx_st {
#endif
};
+X509 *ssl_ctx_find_handshake_cert(SSL_CTX *sctx, const unsigned char *certbytes,
+ size_t cert_len, unsigned char *sha1_hash);
+X509 *ssl_ctx_add_handshake_cert(SSL_CTX *sctx, X509 *cert, unsigned char *sha1_hash);
+
typedef struct ossl_quic_tls_callbacks_st {
int (*crypto_send_cb)(SSL *s, const unsigned char *buf, size_t buf_len,
size_t *consumed, void *arg);
diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c
index cc291b1ff8b..c79d914c76a 100644
--- a/ssl/statem/statem_clnt.c
+++ b/ssl/statem/statem_clnt.c
@@ -2510,6 +2510,7 @@ MSG_PROCESS_RETURN tls_process_server_certificate(SSL_CONNECTION *s,
const unsigned char *certstart, *certbytes;
size_t chainidx;
unsigned int context = 0;
+ unsigned char sha1_hash[SHA_DIGEST_LENGTH];
SSL_CTX *sctx = SSL_CONNECTION_GET_CTX(s);
if (s->ext.server_cert_type == TLSEXT_cert_type_rpk)
@@ -2541,21 +2542,29 @@ MSG_PROCESS_RETURN tls_process_server_certificate(SSL_CONNECTION *s,
}
certstart = certbytes;
- x = X509_new_ex(sctx->libctx, sctx->propq);
+ x = ssl_ctx_find_handshake_cert(sctx, certstart, cert_len, sha1_hash);
if (x == NULL) {
- SSLfatal(s, SSL_AD_DECODE_ERROR, ERR_R_ASN1_LIB);
- goto err;
- }
- if (d2i_X509(&x, (const unsigned char **)&certbytes,
- cert_len)
- == NULL) {
- SSLfatal(s, SSL_AD_BAD_CERTIFICATE, ERR_R_ASN1_LIB);
- goto err;
- }
+ x = X509_new_ex(sctx->libctx, sctx->propq);
+ if (x == NULL) {
+ SSLfatal(s, SSL_AD_DECODE_ERROR, ERR_R_ASN1_LIB);
+ goto err;
+ }
+ if (d2i_X509(&x, (const unsigned char **)&certbytes,
+ cert_len)
+ == NULL) {
+ SSLfatal(s, SSL_AD_BAD_CERTIFICATE, ERR_R_ASN1_LIB);
+ goto err;
+ }
- if (certbytes != (certstart + cert_len)) {
- SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_CERT_LENGTH_MISMATCH);
- goto err;
+ if (certbytes != (certstart + cert_len)) {
+ SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_CERT_LENGTH_MISMATCH);
+ goto err;
+ }
+
+ /*
+ * Add the cert to our handshake cache
+ */
+ x = ssl_ctx_add_handshake_cert(sctx, x, sha1_hash);
}
if (SSL_CONNECTION_IS_VERSION13(s)) {