From: Aki Tuomi Date: Tue, 31 Dec 2024 09:57:04 +0000 (+0200) Subject: lib-ssl-iostream: Add ssl_iostream_get_peer_cert_fingerprint() X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a34af61fbd8704b8c5d5eb7b20886249acad509c;p=thirdparty%2Fdovecot%2Fcore.git lib-ssl-iostream: Add ssl_iostream_get_peer_cert_fingerprint() Provides fingerprint of peer certificate and it's public key using the configured hash algorithm in context. --- diff --git a/src/lib-ssl-iostream/iostream-openssl-context.c b/src/lib-ssl-iostream/iostream-openssl-context.c index a6e0ef5296..9f6c77132d 100644 --- a/src/lib-ssl-iostream/iostream-openssl-context.c +++ b/src/lib-ssl-iostream/iostream-openssl-context.c @@ -844,6 +844,15 @@ ssl_iostream_context_init_common(struct ssl_iostream_context *ctx, #ifdef SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER SSL_CTX_set_mode(ctx->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); #endif + if (set->cert_hash_algo != NULL && *set->cert_hash_algo != '\0') { + ctx->pcert_fp_algo = EVP_get_digestbyname(set->cert_hash_algo); + if (ctx->pcert_fp_algo == NULL) { + *error_r = t_strdup_printf("Unsupported hash algorithm '%s'", + set->cert_hash_algo); + return -1; + } + } + if (ssl_proxy_ctx_set_crypto_params(ctx->ssl_ctx, set, error_r) < 0) return -1; diff --git a/src/lib-ssl-iostream/iostream-openssl.c b/src/lib-ssl-iostream/iostream-openssl.c index 56d0edf7e6..dc04eb5c81 100644 --- a/src/lib-ssl-iostream/iostream-openssl.c +++ b/src/lib-ssl-iostream/iostream-openssl.c @@ -2,6 +2,7 @@ #include "lib.h" #include "buffer.h" +#include "hex-binary.h" #include "istream-private.h" #include "ostream-private.h" #include "iostream-openssl.h" @@ -223,6 +224,8 @@ static void openssl_iostream_free(struct ssl_iostream *ssl_io) i_stream_unref(&ssl_io->plain_input); BIO_free(ssl_io->bio_ext); SSL_free(ssl_io->ssl); + i_free(ssl_io->cert_fp); + i_free(ssl_io->pubkey_fp); i_free(ssl_io->ja3_str); i_free(ssl_io->plain_stream_errstr); i_free(ssl_io->last_error); @@ -989,6 +992,81 @@ openssl_iostream_get_channel_binding(struct ssl_iostream *ssl_io, return -1; } +static int +openssl_iostream_get_peer_cert_fingerprint(struct ssl_iostream *ssl_io, + const char **cert_fp_r, + const char **pubkey_fp_r, + const char **error_r) +{ + SSL *ssl = ssl_io->ssl; + struct ssl_iostream_context *ctx = ssl_io->ctx; + const char *cert_fp; + + if (!ssl_io->handshaked || ssl_io->handshake_failed) + return 0; + + /* Use cached result */ + if (ssl_io->cert_fp != NULL) { + *cert_fp_r = ssl_io->cert_fp; + *pubkey_fp_r = ssl_io->pubkey_fp; + return 1; + } + +#ifdef HAVE_SSL_get1_peer_certificate + X509 *cert = SSL_get0_peer_certificate(ssl); +#else + X509 *cert = SSL_get_peer_certificate(ssl); +#endif + if (cert == NULL) + return 0; + + if (ctx->pcert_fp_algo == NULL) { + *error_r = "No hash algorithm configured"; + return -1; + } + + unsigned int fp_len = EVP_MAX_MD_SIZE; + unsigned char result[EVP_MAX_MD_SIZE]; + + if (X509_digest(cert, ctx->pcert_fp_algo, result, &fp_len) == 0) { + *error_r = openssl_iostream_error(); + return -1; + } + + cert_fp = binary_to_hex(result, fp_len); + + fp_len = EVP_MAX_MD_SIZE; + memset(result, 0, EVP_MAX_MD_SIZE); + + /* Apparently X509_pubkey_digest does not work correctly, + so we need to do this the hard way. */ + BIO *bio = BIO_new(BIO_s_null()); + BIO *hash = BIO_new(BIO_f_md()); + BIO_set_md(hash, ctx->pcert_fp_algo); + bio = BIO_push(hash, bio); + + EVP_PKEY *pubkey = X509_get0_pubkey(cert); + int ret = i2d_PUBKEY_bio(bio, pubkey); + + if (ret == 1) + fp_len = BIO_gets(hash, (void*)result, EVP_MAX_MD_SIZE); + else + *error_r = openssl_iostream_error(); + + BIO_free_all(bio); + + if (ret == 0) + return -1; + + ssl_io->cert_fp = i_strdup(cert_fp); + ssl_io->pubkey_fp = i_strdup(binary_to_hex(result, fp_len)); + *cert_fp_r = ssl_io->cert_fp; + *pubkey_fp_r = ssl_io->pubkey_fp; + + return 1; +} + + static const struct iostream_ssl_vfuncs ssl_vfuncs = { .global_init = openssl_iostream_global_init, .context_init_client = openssl_iostream_context_init_client, @@ -1027,6 +1105,7 @@ static const struct iostream_ssl_vfuncs ssl_vfuncs = { .set_application_protocols = openssl_iostream_context_set_application_protocols, .get_channel_binding = openssl_iostream_get_channel_binding, + .get_peer_cert_fingerprint = openssl_iostream_get_peer_cert_fingerprint, }; void ssl_iostream_openssl_init(void) diff --git a/src/lib-ssl-iostream/iostream-openssl.h b/src/lib-ssl-iostream/iostream-openssl.h index eb2b57da65..4524fe8a3b 100644 --- a/src/lib-ssl-iostream/iostream-openssl.h +++ b/src/lib-ssl-iostream/iostream-openssl.h @@ -27,6 +27,9 @@ struct ssl_iostream_context { size_t proto_len; } *protos; + /* Peer certificate fingerprint hash algo */ + const EVP_MD *pcert_fp_algo; + int username_nid; bool client_ctx:1; @@ -54,6 +57,8 @@ struct ssl_iostream { char *last_error; char *plain_stream_errstr; char *ja3_str; + char *cert_fp; + char *pubkey_fp; int plain_stream_errno; ssl_iostream_handshake_callback_t *handshake_callback; diff --git a/src/lib-ssl-iostream/iostream-ssl-private.h b/src/lib-ssl-iostream/iostream-ssl-private.h index 33941481db..29375f4dff 100644 --- a/src/lib-ssl-iostream/iostream-ssl-private.h +++ b/src/lib-ssl-iostream/iostream-ssl-private.h @@ -62,6 +62,11 @@ struct iostream_ssl_vfuncs { int (*get_channel_binding)(struct ssl_iostream *ssl_io, const char *type, const buffer_t **data_r, const char **error_r); + + int (*get_peer_cert_fingerprint)(struct ssl_iostream *ssl_io, + const char **cert_fp_r, + const char **pubkey_fp_r, + const char **error_r); }; void iostream_ssl_module_init(const struct iostream_ssl_vfuncs *vfuncs); diff --git a/src/lib-ssl-iostream/iostream-ssl.c b/src/lib-ssl-iostream/iostream-ssl.c index 52e2996558..e42765bdd9 100644 --- a/src/lib-ssl-iostream/iostream-ssl.c +++ b/src/lib-ssl-iostream/iostream-ssl.c @@ -444,3 +444,12 @@ int ssl_iostream_get_channel_binding(struct ssl_iostream *ssl_io, return ssl_vfuncs->get_channel_binding(ssl_io, type, data_r, error_r); } + +int ssl_iostream_get_peer_cert_fingerprint(struct ssl_iostream *ssl_io, + const char **cert_fp_r, + const char **pubkey_fp_r, + const char **error_r) +{ + return ssl_vfuncs->get_peer_cert_fingerprint(ssl_io, cert_fp_r, + pubkey_fp_r, error_r); +} diff --git a/src/lib-ssl-iostream/iostream-ssl.h b/src/lib-ssl-iostream/iostream-ssl.h index 3327f89b1a..98379ed1d5 100644 --- a/src/lib-ssl-iostream/iostream-ssl.h +++ b/src/lib-ssl-iostream/iostream-ssl.h @@ -63,6 +63,8 @@ struct ssl_iostream_settings { /* Field which contains the username returned by ssl_iostream_get_peer_username() */ const char *cert_username_field; + /* Hashing algorithm for certificate fingerprinting */ + const char *cert_hash_algo; const char *crypto_device; /* List of application protocol names */ @@ -253,6 +255,13 @@ const char *ssl_iostream_get_last_error(struct ssl_iostream *ssl_io); const char *ssl_iostream_get_application_protocol(struct ssl_iostream *ssl_io); +/* Get peer certificate fingerprints, returns 1 on success, 0 if no client cert + was provided and -1 on error. */ +int ssl_iostream_get_peer_cert_fingerprint(struct ssl_iostream *ssl_io, + const char **cert_fp_r, + const char **pubkey_fp_r, + const char **error_r); + void ssl_iostream_context_set_application_protocols(struct ssl_iostream_context *ssl_ctx, const char *const *names);