From 9aa15e72a71efa9287e3104af33b8cdf96307fa8 Mon Sep 17 00:00:00 2001 From: Georg Richter Date: Thu, 31 Aug 2023 08:21:13 +0200 Subject: [PATCH] TLS fingerprint Beside SHA1 fingerprint hash, Connector/C now also supports SHA224 (OpenSSL and GnuTLS only), SHA256, SHA384 and SHA512 fingerprint hashes. --- CMakeLists.txt | 2 +- include/ma_crypt.h | 24 ++------- include/ma_hash.h | 22 ++++++++ include/ma_tls.h | 6 ++- libmariadb/CMakeLists.txt | 4 ++ libmariadb/ma_tls.c | 87 +++++++++++++++++++++++-------- libmariadb/secure/gnutls.c | 29 ++++++++++- libmariadb/secure/gnutls_crypt.c | 2 - libmariadb/secure/ma_schannel.h | 4 +- libmariadb/secure/openssl.c | 47 +++++++++++++---- libmariadb/secure/openssl_crypt.c | 2 - libmariadb/secure/schannel.c | 27 ++++++++-- 12 files changed, 191 insertions(+), 65 deletions(-) create mode 100644 include/ma_hash.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e7b50d2..cc44428f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -382,7 +382,7 @@ CONFIGURE_FILE(${CC_SOURCE_DIR}/include/mariadb_version.h.in INCLUDE_DIRECTORIES(${CC_BINARY_DIR}/include) IF(WIN32) - SET(SYSTEM_LIBS ws2_32 advapi32 kernel32 shlwapi crypt32 ${LIBZ}) + SET(SYSTEM_LIBS ws2_32 advapi32 kernel32 shlwapi crypt32 bcrypt ${LIBZ}) ELSE() SET(SYSTEM_LIBS ${SYSTEM_LIBS} ${LIBPTHREAD} ${CMAKE_DL_LIBS} ${LIBM}) IF(ICONV_EXTERNAL) diff --git a/include/ma_crypt.h b/include/ma_crypt.h index 367488fa..b4d2a09f 100644 --- a/include/ma_crypt.h +++ b/include/ma_crypt.h @@ -17,34 +17,20 @@ 51 Franklin St., Fifth Floor, Boston, MA 02110, USA */ -#ifndef _ma_hash_h_ -#define _ma_hash_h_ +#ifndef _ma_crypt_h_ +#define _ma_crypt_h_ +#include #include #include /*! Hash algorithms */ -#define MA_HASH_MD5 1 -#define MA_HASH_SHA1 2 -#define MA_HASH_SHA224 3 -#define MA_HASH_SHA256 4 -#define MA_HASH_SHA384 5 -#define MA_HASH_SHA512 6 #define MA_HASH_RIPEMD160 7 #define MA_HASH_MAX 8 /*! Hash digest sizes */ -#define MA_MD5_HASH_SIZE 16 -#define MA_SHA1_HASH_SIZE 20 -#define MA_SHA224_HASH_SIZE 28 -#define MA_SHA256_HASH_SIZE 32 -#define MA_SHA384_HASH_SIZE 48 -#define MA_SHA512_HASH_SIZE 64 #define MA_RIPEMD160_HASH_SIZE 20 -#define MA_MAX_HASH_SIZE 64 -/** \typedef MRL hash context */ - #if defined(HAVE_WINCRYPT) typedef void MA_HASH_CTX; #elif defined(HAVE_OPENSSL) @@ -123,8 +109,6 @@ static inline size_t ma_hash_digest_size(unsigned int hash_alg) return MA_SHA384_HASH_SIZE; case MA_HASH_SHA512: return MA_SHA512_HASH_SIZE; - case MA_HASH_RIPEMD160: - return MA_RIPEMD160_HASH_SIZE; default: return 0; } @@ -152,4 +136,4 @@ static inline void ma_hash(unsigned int algorithm, ma_hash_free(ctx); } -#endif /* _ma_hash_h_ */ +#endif /* _ma_crypt_h_ */ diff --git a/include/ma_hash.h b/include/ma_hash.h new file mode 100644 index 00000000..1a40d48a --- /dev/null +++ b/include/ma_hash.h @@ -0,0 +1,22 @@ +#ifndef _ma_hash_h_ +#define _ma_hash_h_ + +/*! Hash algorithms */ +#define MA_HASH_MD5 1 +#define MA_HASH_SHA1 2 +#define MA_HASH_SHA224 3 +#define MA_HASH_SHA256 4 +#define MA_HASH_SHA384 5 +#define MA_HASH_SHA512 6 + +/*! Hash digest sizes */ +#define MA_MD5_HASH_SIZE 16 +#define MA_SHA1_HASH_SIZE 20 +#define MA_SHA224_HASH_SIZE 28 +#define MA_SHA256_HASH_SIZE 32 +#define MA_SHA384_HASH_SIZE 48 +#define MA_SHA512_HASH_SIZE 64 + +#define MA_MAX_HASH_SIZE 64 + +#endif diff --git a/include/ma_tls.h b/include/ma_tls.h index ec8bc239..616124c0 100644 --- a/include/ma_tls.h +++ b/include/ma_tls.h @@ -1,6 +1,8 @@ #ifndef _ma_tls_h_ #define _ma_tls_h_ +#include + enum enum_pvio_tls_type { SSL_TYPE_DEFAULT=0, #ifdef _WIN32 @@ -128,12 +130,14 @@ const char *ma_tls_get_cipher(MARIADB_TLS *ssl); returns SHA1 finger print of server certificate Parameter: MARIADB_TLS MariaDB SSL container + hash_type hash_type as defined in ma_hash.h fp buffer for fingerprint fp_len buffer length + Returns: actual size of finger print */ -unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int fp_len); +unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, uint hash_type, char *fp, unsigned int fp_len); /* ma_tls_get_protocol_version returns protocol version number in use diff --git a/libmariadb/CMakeLists.txt b/libmariadb/CMakeLists.txt index 43ed67b4..852be8dc 100644 --- a/libmariadb/CMakeLists.txt +++ b/libmariadb/CMakeLists.txt @@ -344,6 +344,10 @@ IF(WIN32) ${CC_SOURCE_DIR}/win-iconv/win_iconv.c win32_errmsg.c win32_errmsg.h) + IF(WITH_SSL STREQUAL "SCHANNEL") + SET(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} + secure/win_crypt.c) + ENDIF() ELSE() IF(ICONV_INCLUDE_DIR) INCLUDE_DIRECTORIES(BEFORE ${ICONV_INCLUDE_DIR}) diff --git a/libmariadb/ma_tls.c b/libmariadb/ma_tls.c index 3f48ad8b..76c23c11 100644 --- a/libmariadb/ma_tls.c +++ b/libmariadb/ma_tls.c @@ -41,12 +41,15 @@ #include #include #include +#include #ifdef HAVE_NONBLOCK #include #include #endif +#define MAX_FINGERPRINT_LEN 128; + /* Errors should be handled via pvio callback function */ my_bool ma_tls_initialized= FALSE; unsigned int mariadb_deinitialize_ssl= 1; @@ -141,36 +144,74 @@ static signed char ma_hex2int(char c) return -1; } -static my_bool ma_pvio_tls_compare_fp(const char *cert_fp, - unsigned int cert_fp_len, - const char *fp, unsigned int fp_len) +#ifndef EVP_MAX_MD_SIZE +#define EVP_MAX_MD_SIZE 64 +#endif + +static my_bool ma_pvio_tls_compare_fp(MARIADB_TLS *ctls, + const char *cert_fp, + unsigned int cert_fp_len +) { - char *p= (char *)fp, - *c; + const char fp[EVP_MAX_MD_SIZE]; + unsigned int fp_len= EVP_MAX_MD_SIZE; + unsigned int hash_type; - /* check length */ - if (cert_fp_len != 20) + char *p, *c; + uint hash_len; + + /* check length without colons */ + if (strchr(cert_fp, ':')) + hash_len= (uint)((strlen(cert_fp) + 1) / 3) * 2; + else + hash_len= (uint)strlen(cert_fp); + + /* check hash size */ + switch (hash_len) { +#ifndef DISABLE_WEAK_HASH + case MA_SHA1_HASH_SIZE * 2: + hash_type = MA_HASH_SHA1; + break; +#endif + case MA_SHA224_HASH_SIZE * 2: + hash_type = MA_HASH_SHA224; + break; + case MA_SHA256_HASH_SIZE * 2: + hash_type = MA_HASH_SHA256; + break; + case MA_SHA384_HASH_SIZE * 2: + hash_type = MA_HASH_SHA384; + break; + case MA_SHA512_HASH_SIZE * 2: + hash_type = MA_HASH_SHA512; + break; + default: + { + MYSQL* mysql = ctls->pvio->mysql; + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Unknown or invalid fingerprint hash size detected"); + return 1; + } + } + + if (!ma_tls_get_finger_print(ctls, hash_type, (char *)fp, fp_len)) return 1; - /* We support two formats: - 2 digits hex numbers, separated by colons (length=59) - 20 * 2 digits hex numbers without separators (length = 40) - */ - if (fp_len != (strchr(fp, ':') ? 59 : 40)) - return 1; + p= (char *)cert_fp; + c = (char *)fp; - for(c= (char *)cert_fp; c < cert_fp + cert_fp_len; c++) + for (p = (char*)cert_fp; p < cert_fp + cert_fp_len; c++, p += 2) { signed char d1, d2; if (*p == ':') p++; - if (p - fp > (int)fp_len -1) + if (p - cert_fp > (int)fp_len - 1) return 1; - if ((d1 = ma_hex2int(*p)) == - 1 || - (d2 = ma_hex2int(*(p+1))) == -1 || - (char)(d1 * 16 + d2) != *c) + if ((d1 = ma_hex2int(*p)) == -1 || + (d2 = ma_hex2int(*(p + 1))) == -1 || + (char)(d1 * 16 + d2) != *c) return 1; - p+= 2; } return 0; } @@ -184,10 +225,10 @@ my_bool ma_pvio_tls_check_fp(MARIADB_TLS *ctls, const char *fp, const char *fp_l cert_fp= (char *)malloc(cert_fp_len); - if ((cert_fp_len= ma_tls_get_finger_print(ctls, cert_fp, cert_fp_len)) < 1) - goto end; if (fp) - rc= ma_pvio_tls_compare_fp(cert_fp, cert_fp_len, fp, (unsigned int)strlen(fp)); + { + rc = ma_pvio_tls_compare_fp(ctls, fp, (uint)strlen(fp)); + } else if (fp_list) { MA_FILE *fp; @@ -205,7 +246,7 @@ my_bool ma_pvio_tls_check_fp(MARIADB_TLS *ctls, const char *fp, const char *fp_l if (pos) *pos= '\0'; - if (!ma_pvio_tls_compare_fp(cert_fp, cert_fp_len, buff, (unsigned int)strlen(buff))) + if (!ma_pvio_tls_compare_fp(ctls, cert_fp, cert_fp_len)) { /* finger print is valid: close file and exit */ ma_close(fp); diff --git a/libmariadb/secure/gnutls.c b/libmariadb/secure/gnutls.c index 4782be62..55aca26b 100644 --- a/libmariadb/secure/gnutls.c +++ b/libmariadb/secure/gnutls.c @@ -1391,18 +1391,43 @@ static int my_verify_callback(gnutls_session_t ssl) return 0; } -unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int len) +unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, uint hash_type, char *fp, unsigned int len) { MYSQL *mysql; size_t fp_len= len; const gnutls_datum_t *cert_list; unsigned int cert_list_size; + gnutls_digest_algorithm_t hash_alg; if (!ctls || !ctls->ssl) return 0; mysql= (MYSQL *)gnutls_session_get_ptr(ctls->ssl); + switch (hash_type) + { + case MA_HASH_SHA1: + hash_alg = GNUTLS_DIG_SHA1; + break; + case MA_HASH_SHA224: + hash_alg = GNUTLS_DIG_SHA224; + break; + case MA_HASH_SHA256: + hash_alg = GNUTLS_DIG_SHA256; + break; + case MA_HASH_SHA384: + hash_alg = GNUTLS_DIG_SHA384; + break; + case MA_HASH_SHA512: + hash_alg = GNUTLS_DIG_SHA512; + break; + default: + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Cannot detect hash algorithm for fingerprint verification"); + return 0; + } + cert_list = gnutls_certificate_get_peers (ctls->ssl, &cert_list_size); if (cert_list == NULL) { @@ -1412,7 +1437,7 @@ unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int l return 0; } - if (gnutls_fingerprint(GNUTLS_DIG_SHA1, &cert_list[0], fp, &fp_len) == 0) + if (gnutls_fingerprint(hash_alg, &cert_list[0], fp, &fp_len) == 0) return fp_len; else { diff --git a/libmariadb/secure/gnutls_crypt.c b/libmariadb/secure/gnutls_crypt.c index a669e88e..222e5585 100644 --- a/libmariadb/secure/gnutls_crypt.c +++ b/libmariadb/secure/gnutls_crypt.c @@ -34,8 +34,6 @@ static gnutls_digest_algorithm_t ma_hash_get_algorithm(unsigned int alg) return GNUTLS_DIG_SHA384; case MA_HASH_SHA512: return GNUTLS_DIG_SHA512; - case MA_HASH_RIPEMD160: - return GNUTLS_DIG_RMD160; default: return GNUTLS_DIG_UNKNOWN; } diff --git a/libmariadb/secure/ma_schannel.h b/libmariadb/secure/ma_schannel.h index af7fc602..562dae1f 100644 --- a/libmariadb/secure/ma_schannel.h +++ b/libmariadb/secure/ma_schannel.h @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -35,6 +36,7 @@ #include +#include #include #undef SECURITY_WIN32 @@ -57,7 +59,7 @@ struct st_schannel { DWORD IoBufferSize; SecPkgContext_StreamSizes Sizes; CtxtHandle hCtxt; - + BCRYPT_ALG_HANDLE HashProv[MA_MAX_HASH_SIZE]; /* Cached data from the last read/decrypt call.*/ SecBuffer extraBuf; /* encrypted data read from server. */ SecBuffer dataBuf; /* decrypted but still unread data from server.*/ diff --git a/libmariadb/secure/openssl.c b/libmariadb/secure/openssl.c index 2a272504..53061312 100644 --- a/libmariadb/secure/openssl.c +++ b/libmariadb/secure/openssl.c @@ -29,6 +29,7 @@ #include /* error reporting */ #include #include +#include #if defined(_WIN32) && !defined(_OPENSSL_Applink) && defined(HAVE_OPENSSL_APPLINK_C) #include @@ -729,17 +730,50 @@ const char *ma_tls_get_cipher(MARIADB_TLS *ctls) return SSL_get_cipher_name(ctls->ssl); } -unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int len) +unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, uint hash_type, char *fp, unsigned int len) { X509 *cert= NULL; MYSQL *mysql; unsigned int fp_len; + const EVP_MD *hash_alg; if (!ctls || !ctls->ssl) return 0; - mysql= SSL_get_app_data(ctls->ssl); + mysql = SSL_get_app_data(ctls->ssl); + if (len < EVP_MAX_MD_SIZE) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Finger print buffer too small"); + return 0; + } + + switch (hash_type) + { + case MA_HASH_SHA1: + hash_alg = EVP_sha1(); + break; + case MA_HASH_SHA224: + hash_alg = EVP_sha224(); + break; + case MA_HASH_SHA256: + hash_alg = EVP_sha256(); + break; + case MA_HASH_SHA384: + hash_alg = EVP_sha384(); + break; + case MA_HASH_SHA512: + hash_alg = EVP_sha512(); + break; + default: + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Cannot detect hash algorithm for fingerprint verification"); + return 0; + } + if (!(cert= SSL_get_peer_certificate(ctls->ssl))) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, @@ -748,14 +782,7 @@ unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int l goto end; } - if (len < EVP_MAX_MD_SIZE) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - "Finger print buffer too small"); - goto end; - } - if (!X509_digest(cert, EVP_sha1(), (unsigned char *)fp, &fp_len)) + if (!X509_digest(cert, hash_alg, (unsigned char *)fp, &fp_len)) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), diff --git a/libmariadb/secure/openssl_crypt.c b/libmariadb/secure/openssl_crypt.c index faf755c9..fd3538ba 100644 --- a/libmariadb/secure/openssl_crypt.c +++ b/libmariadb/secure/openssl_crypt.c @@ -36,8 +36,6 @@ static const EVP_MD *ma_hash_get_algorithm(unsigned int alg) return EVP_sha384(); case MA_HASH_SHA512: return EVP_sha512(); - case MA_HASH_RIPEMD160: - return EVP_ripemd160(); default: return NULL; } diff --git a/libmariadb/secure/schannel.c b/libmariadb/secure/schannel.c index 8069af53..e9231efb 100644 --- a/libmariadb/secure/schannel.c +++ b/libmariadb/secure/schannel.c @@ -20,6 +20,9 @@ #include "ma_schannel.h" #include "schannel_certs.h" #include +#include +#include +#include extern my_bool ma_tls_initialized; char tls_library_version[] = "Schannel"; @@ -550,15 +553,33 @@ const char *ma_tls_get_cipher(MARIADB_TLS *ctls) return cipher_name(&CipherInfo); } -unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int len) +unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, uint hash_type, char *fp, unsigned int len) { + MA_HASH_CTX* hash_ctx; + SC_CTX *sctx= (SC_CTX *)ctls->ssl; PCCERT_CONTEXT pRemoteCertContext = NULL; + int rc= 0; + + if (hash_type == MA_HASH_SHA224) + { + MYSQL *mysql = ctls->pvio->mysql; + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "SHA224 hash for fingerprint verification is not supported in Schannel"); + return 0; + } + if (QueryContextAttributes(&sctx->hCtxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext) != SEC_E_OK) return 0; - CertGetCertificateContextProperty(pRemoteCertContext, CERT_HASH_PROP_ID, fp, (DWORD *)&len); + + hash_ctx = ma_hash_new(hash_type); + ma_hash_input(hash_ctx, pRemoteCertContext->pbCertEncoded, pRemoteCertContext->cbCertEncoded); + ma_hash_result(hash_ctx, fp); + ma_hash_free(hash_ctx); + CertFreeCertificateContext(pRemoteCertContext); - return len; + return (uint)ma_hash_digest_size(hash_type); } void ma_tls_set_connection(MYSQL *mysql __attribute__((unused)))