]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-ssl-iostream: Add ssl_iostream_get_peer_cert_fingerprint()
authorAki Tuomi <aki.tuomi@open-xchange.com>
Tue, 31 Dec 2024 09:57:04 +0000 (11:57 +0200)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Mon, 26 May 2025 05:39:13 +0000 (05:39 +0000)
Provides fingerprint of peer certificate and it's public key
using the configured hash algorithm in context.

src/lib-ssl-iostream/iostream-openssl-context.c
src/lib-ssl-iostream/iostream-openssl.c
src/lib-ssl-iostream/iostream-openssl.h
src/lib-ssl-iostream/iostream-ssl-private.h
src/lib-ssl-iostream/iostream-ssl.c
src/lib-ssl-iostream/iostream-ssl.h

index a6e0ef5296ab7cdd99da598011132bc421b494d3..9f6c77132de57566054b04feec42b565b316eb01 100644 (file)
@@ -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;
 
index 56d0edf7e6186fff6dd18a847bb9989751bde99c..dc04eb5c81151d4be148e1668dd27db133724615 100644 (file)
@@ -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)
index eb2b57da65d3aa7d3cbcbe5aaa24db8998e2c891..4524fe8a3b4db05e0a8355853de36e528278fb3f 100644 (file)
@@ -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;
index 33941481dbd35abcfe648e2f88e2aaf9e093e73c..29375f4dff5711c13ede869724289a1f807f37ef 100644 (file)
@@ -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);
index 52e2996558089d2ea951ea151b346ed25c2d8772..e42765bdd9236a9678457cd07e5f023a615f2624 100644 (file)
@@ -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);
+}
index 3327f89b1a14b2cdae2069b949baa4aab208c013..98379ed1d588fbd8040b661e82f9217f24072951 100644 (file)
@@ -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);