diff --git a/src/redisx-tls.c b/src/redisx-tls.c index 1074be9..c166d05 100644 --- a/src/redisx-tls.c +++ b/src/redisx-tls.c @@ -14,6 +14,7 @@ #include "redisx-priv.h" #if WITH_TLS +# include // For DH parameters # include #endif @@ -41,6 +42,59 @@ void rDestroyClientTLS(ClientPrivate *cp) { } } +/** + * Loads parameters from a file for DH-based ciphers. + * + * Based on https://github.com/openssl/openssl/blob/master/apps/dhparam.c + * + * @param ctx The SSL context for which to set DH parameters + * @param filename The DH parameter filename (in PEM format) + * @return X_SUCCESS (0) if successful, or else an error code <0. + * + * @sa rConnectTLSClient() + */ + static int rSetSHParamsFromFile(SSL_CTX *ctx, const char *filename) { + static const char *fn = "rSetSHParamsFromFile"; + + BIO *in; + OSSL_DECODER_CTX *dctx = NULL; + EVP_PKEY *pkey = NULL; + int status = X_SUCCESS; + + in = BIO_new_file(filename, "rb"); + if(!in) return x_error(0, errno, fn, "Could not open DH parameters: %s", filename); + + dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "PEM", NULL, NULL, OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, NULL, NULL); + if(!dctx) { + status = x_error(X_FAILURE, errno, fn, "Failed to create decoder context"); + goto cleanup; // @suppress("Goto statement used") + } + + if(!OSSL_DECODER_from_bio(dctx, in)) { + status = x_error(X_FAILURE, errno, fn, "Could not decode DH parameters from: %s", filename); + goto cleanup; // @suppress("Goto statement used") + } + + if (!EVP_PKEY_is_a(pkey, "DH")) { + status = x_error(X_FAILURE, errno, fn, "Invalid DH parameters in: %s", filename); + goto cleanup; // @suppress("Goto statement used") + } + + // The call references the key, does nor copy it. Therefore we should not free it + // if successful. The set key is freed then later when the context is freed. + if(!SSL_CTX_set0_tmp_dh_pkey(ctx, pkey)) { + EVP_PKEY_free(pkey); + status = x_error(X_FAILURE, errno, fn, "Failed to set DH parameters"); + } + + cleanup: + + if(dctx) OSSL_DECODER_CTX_free(dctx); + BIO_free(in); + + return status; +} + /** * Connects a client using the specified TLS configuration. * @@ -111,13 +165,19 @@ int rConnectTLSClient(ClientPrivate *cp, const TLSConfig *tls) { goto abort; // @suppress("Goto statement used") } +#if OPENSSL_VERSION_NUMBER >= 0x1010100f + // Since OpenSSL version 1.1.1 if(tls->cipher_suites) if(!SSL_CTX_set_ciphersuites(cp->ctx, tls->cipher_suites)) { x_error(0, errno, fn, "Failed to set ciphers= suites %s", tls->ciphers); goto abort; // @suppress("Goto statement used") } +#endif - if(tls->dh_params) if(!SSL_CTX_set_tmp_dh(cp->ctx, tls->dh_params)) { - x_error(0, errno, fn, "Failed to set DH-based cypher parameters from: %s", tls->dh_params); + if(tls->dh_params) { + if(rSetSHParamsFromFile(cp->ctx, tls->dh_params) != X_SUCCESS) goto abort; // @suppress("Goto statement used") + } + else if(!SSL_CTX_set_dh_auto(cp->ctx, 1)) { + x_error(0, errno, fn, "Failed to set automatic DH-based cypher parameters"); goto abort; // @suppress("Goto statement used") } @@ -282,26 +342,26 @@ int redisxSetMutualTLS(Redis *redis, const char *cert_file, const char *key_file int redisxSetTLSCiphers(Redis *redis, const char *cipher_list) { static const char *fn = "redisxSetTLSCiphers"; - #if WITH_TLS - RedisPrivate *p; - TLSConfig *tls; +#if WITH_TLS + RedisPrivate *p; + TLSConfig *tls; - prop_error(fn, rConfigLock(redis)); + prop_error(fn, rConfigLock(redis)); - p = (RedisPrivate *) redis->priv; - tls = &p->config.tls; + p = (RedisPrivate *) redis->priv; + tls = &p->config.tls; - tls->ciphers = xStringCopyOf(cipher_list); + tls->ciphers = xStringCopyOf(cipher_list); - rConfigUnlock(redis); + rConfigUnlock(redis); - return X_SUCCESS; - #else - (void) redis; - (void) ca_file; + return X_SUCCESS; +#else + (void) redis; + (void) cipher_list; - return x_error(X_FAILURE, ENOSYS, fn, "RedisX was built without TLS support"); - #endif + return x_error(X_FAILURE, ENOSYS, fn, "RedisX was built without TLS support"); +#endif } /** @@ -318,26 +378,34 @@ int redisxSetTLSCiphers(Redis *redis, const char *cipher_list) { int redisxSetTLSCipherSuites(Redis *redis, const char *list) { static const char *fn = "redisxSetTLSCiphers"; - #if WITH_TLS - RedisPrivate *p; - TLSConfig *tls; +#if WITH_TLS +# if OPENSSL_VERSION_NUMBER >= 0x1010100f + // Since OpenSSL version 1.1.1 + RedisPrivate *p; + TLSConfig *tls; + + prop_error(fn, rConfigLock(redis)); - prop_error(fn, rConfigLock(redis)); + p = (RedisPrivate *) redis->priv; + tls = &p->config.tls; - p = (RedisPrivate *) redis->priv; - tls = &p->config.tls; + tls->cipher_suites = xStringCopyOf(list); - tls->cipher_suites = xStringCopyOf(list); + rConfigUnlock(redis); - rConfigUnlock(redis); + return X_SUCCESS; +# else + (void) redis; + (void) list; - return X_SUCCESS; - #else - (void) redis; - (void) ca_file; + return x_error(X_FAILURE, ENOSYS, fn, "Needs OpenSSL >= 1.1.1 (for TLSv1.3+)"); +# endif // OpenSSL >= 1.1.1 +#else + (void) redis; + (void) list; - return x_error(X_FAILURE, ENOSYS, fn, "RedisX was built without TLS support"); - #endif + return x_error(X_FAILURE, ENOSYS, fn, "RedisX was built without TLS support"); +#endif } /** @@ -389,26 +457,26 @@ int redisxSetDHCipherParams(Redis *redis, const char *dh_params_file) { int redisxSetTLSServerName(Redis *redis, const char *host) { static const char *fn = "redisxSetDHCipherParams"; - #if WITH_TLS - RedisPrivate *p; - TLSConfig *tls; +#if WITH_TLS + RedisPrivate *p; + TLSConfig *tls; - prop_error(fn, rConfigLock(redis)); + prop_error(fn, rConfigLock(redis)); - p = (RedisPrivate *) redis->priv; - tls = &p->config.tls; + p = (RedisPrivate *) redis->priv; + tls = &p->config.tls; - tls->hostname = xStringCopyOf(host); + tls->hostname = xStringCopyOf(host); - rConfigUnlock(redis); + rConfigUnlock(redis); - return X_SUCCESS; - #else - (void) redis; - (void) host; + return X_SUCCESS; +#else + (void) redis; + (void) host; - return x_error(X_FAILURE, ENOSYS, fn, "RedisX was built without TLS support"); - #endif + return x_error(X_FAILURE, ENOSYS, fn, "RedisX was built without TLS support"); +#endif } /** @@ -423,24 +491,23 @@ int redisxSetTLSServerName(Redis *redis, const char *host) { int redisxSetTLSSkipVerify(Redis *redis, boolean value) { static const char *fn = "redisxTLSSkipVerify"; - #if WITH_TLS - RedisPrivate *p; - TLSConfig *tls; +#if WITH_TLS + RedisPrivate *p; + TLSConfig *tls; - prop_error(fn, rConfigLock(redis)); + prop_error(fn, rConfigLock(redis)); - p = (RedisPrivate *) redis->priv; - tls = &p->config.tls; + p = (RedisPrivate *) redis->priv; + tls = &p->config.tls; - tls->skip_verify = (value != 0); + tls->skip_verify = (value != 0); - rConfigUnlock(redis); + rConfigUnlock(redis); - return X_SUCCESS; - #else - (void) redis; - (void) host; + return X_SUCCESS; +#else + (void) redis; - return x_error(X_FAILURE, ENOSYS, fn, "RedisX was built without TLS support"); - #endif + return x_error(X_FAILURE, ENOSYS, fn, "RedisX was built without TLS support"); +#endif }