sewenew/redis-plus-plus

error connecting with SSL

adobeturchenko opened this issue · 9 comments

I'm trying to connect to SSL Redis server and getting connection error.
(I tried to connect to the same server using redis-cli with hostname and a password and it works fine)

ConnectionOptions opts;
opts.host = <some_hostname>;
opts.port = 6380;
opts.tls.enabled = true;
opts.password = <some_password>;

Error I got is not very informative:
redis error: Failed to initialize TLS connection: SSL_connect failed: (null)

After modifying hiredis it gave me something better:
redis error: Failed to initialize TLS connection: SSL_connect failed: certificate verify failed

It looks like the code (SSL_get_verify_result(ssl) == 20) refers to:
20 X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: unable to get local issuer certificate
the issuer certificate could not be found: this occurs if the issuer certificate of an untrusted certificate cannot be found.

I'm likely doing something wrong. But what? (again, redis-cli works fine)

Since you enabled ConnectionOptions::tls::enable, you need to specify other TLS options, e.g. certification file, key file. Please check the doc for detail.

I tried to connect to the same server using redis-cli with hostname and a password and it works fine

Did you connect Redis with TLS enabled? If you did, you should set TLS options with command line arguments. For example:

./src/redis-cli --tls \
        --cert ./tests/tls/redis.crt \
        --key ./tests/tls/redis.key \
        --cacert ./tests/tls/ca.crt

You can use these command line arguments to set ConnectionOptions::tls

Regards

Actually I connected in redis-cli without specifying certificates:
redis-cli --tls -h < some_hostname > -p 6380
and after authentication was able to set/get values.

Also jedis (Java client) connects and all tests pass without certificates on the same host.
Redis++ documentation states that those certificates are optional:
opts.tls.enabled = true; // Required. false by default.
opts.tls.cert = "/path/to/client/certificate"; // Optional
opts.tls.key = "/path/to/private/key/file"; // Optional
opts.tls.cacert = "/path/to/CA/certificate/file"; // Optional
opts.tls.sni = "server-name-indication"; // Optional

I think I've figured it out...
redis-cli has an option to skip certificate verification:
SSL_CTX_set_verify(ssl_ctx, config.skip_cert_verify ? SSL_VERIFY_NONE : SSL_VERIFY_PEER, NULL);
I don't see such option in redis-plus-plus but maybe I need to look again.

@adobeturchenko Thanks for pointing it out!

It seems that this is a new feature for redis-cli, and it has not been supported by hiredis:

    SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL);

So redis-plus-plus cannot skip the verification so far.

Sorry for that. I'll keep an eye on this hiredis' progress on this problem, and if hiredis fixes it, I'll make redis-plus-plus supports this feature.

Regards

After another round of research I've found actual missing code in hiredis:

diff --git a/ssl.c b/ssl.c
index c856bbc..726706a 100644
--- a/ssl.c
+++ b/ssl.c
@@ -273,6 +273,11 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
             if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
             goto error;
         }
+    } else {
+        if (!SSL_CTX_set_default_verify_paths(ctx->ssl_ctx)) {
+            printf( "!!!!   Failed to use default CA paths\n");
+            goto error;
+        }
     }
 
     if (cert_filename) {

According to OpenSSL:
SSL_CTX_set_default_verify_paths() specifies that the default locations from which CA certificates are loaded should be used. There is one default directory and one default file. The default CA certificates directory is called "certs" in the default OpenSSL directory.

It can be fixed in hiredis or maybe in redis++ similar to how redis-cli is doing it (they also have dependency on hiredis) - construct SSL object inside redis++ and pass it with redisInitiateSSL() (redis++ uses redisInitiateSSLWithContext() and relies on hiredis to deal with SSL as I understand).

I think it's better to make hiredis to support it, so that redis-plus-plus can be consistent with it.

I've created a pull request to hiredis: #927. When it's done, I'll port it to redis-plus-plus.

Regards

Since hiredis already supports skipping certificate verification, I added this support for redis-plus-plus. In order to use this feature, you need to install hiredis v1.1.0 or above with TSL support.

ConnectionOptions opts;
opts.host = "127.0.0.1";
opts.port = 6379;
opts.tls.enabled = true;
opts.tls.verify_mode = REDIS_SSL_VERIFY_NONE;   // DO NOT verify certificate.
auto r = Redis(opts);
std::cout << r.ping() << std::endl;

Regards