From 4147a2202e3b504b4609b701c43a1488f44f6da7 Mon Sep 17 00:00:00 2001 From: Yossi Gottlieb Date: Sun, 11 Oct 2020 17:11:42 +0300 Subject: [PATCH] Module API: Add RM_GetClientCertificate(). (#7866) This API function makes it possible to retrieve the X.509 certificate used by clients to authenticate TLS connections. (cherry picked from commit 0aec98dce2acbd280ad8ff4feac631e8afa833b1) --- src/connection.h | 1 + src/module.c | 26 ++++++++++++++++++++++++++ src/redismodule.h | 2 ++ src/tls.c | 30 ++++++++++++++++++++++++++++++ tests/modules/misc.c | 19 +++++++++++++++++++ tests/unit/moduleapi/misc.tcl | 10 ++++++++++ 6 files changed, 88 insertions(+) diff --git a/src/connection.h b/src/connection.h index e00d2ea17..03281a3d9 100644 --- a/src/connection.h +++ b/src/connection.h @@ -230,6 +230,7 @@ int connSockName(connection *conn, char *ip, size_t ip_len, int *port); const char *connGetInfo(connection *conn, char *buf, size_t buf_len); /* Helpers for tls special considerations */ +sds connTLSGetPeerCert(connection *conn); int tlsHasPendingData(); int tlsProcessPendingData(); diff --git a/src/module.c b/src/module.c index 25a2f543d..8d873c322 100644 --- a/src/module.c +++ b/src/module.c @@ -5736,6 +5736,31 @@ int RM_DeauthenticateAndCloseClient(RedisModuleCtx *ctx, uint64_t client_id) { return REDISMODULE_OK; } +/* Return the X.509 client-side certificate used by the client to authenticate + * this connection. + * + * The return value is an allocated RedisModuleString that is a X.509 certificate + * encoded in PEM (Base64) format. It should be freed (or auto-freed) by the caller. + * + * A NULL value is returned in the following conditions: + * + * - Connection ID does not exist + * - Connection is not a TLS connection + * - Connection is a TLS connection but no client ceritifcate was used + */ +RedisModuleString *RM_GetClientCertificate(RedisModuleCtx *ctx, uint64_t client_id) { + client *c = lookupClientByID(client_id); + if (c == NULL) return NULL; + + sds cert = connTLSGetPeerCert(c->conn); + if (!cert) return NULL; + + RedisModuleString *s = createObject(OBJ_STRING, cert); + if (ctx != NULL) autoMemoryAdd(ctx, REDISMODULE_AM_STRING, s); + + return s; +} + /* -------------------------------------------------------------------------- * Modules Dictionary API * @@ -8194,5 +8219,6 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(DeauthenticateAndCloseClient); REGISTER_API(AuthenticateClientWithACLUser); REGISTER_API(AuthenticateClientWithUser); + REGISTER_API(GetClientCertificate); REGISTER_API(GetCommandKeys); } diff --git a/src/redismodule.h b/src/redismodule.h index e0bb19fc2..9a0ee470f 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -725,6 +725,7 @@ REDISMODULE_API int (*RedisModule_SetModuleUserACL)(RedisModuleUser *user, const REDISMODULE_API int (*RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_GetClientCertificate)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR; REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, const char *cmdname, RedisModuleString **argv, int argc, int *num_keys) REDISMODULE_ATTR; #endif @@ -968,6 +969,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(DeauthenticateAndCloseClient); REDISMODULE_GET_API(AuthenticateClientWithACLUser); REDISMODULE_GET_API(AuthenticateClientWithUser); + REDISMODULE_GET_API(GetClientCertificate); REDISMODULE_GET_API(GetCommandKeys); #endif diff --git a/src/tls.c b/src/tls.c index 0a9e07895..25580a4f1 100644 --- a/src/tls.c +++ b/src/tls.c @@ -37,6 +37,7 @@ #include #include #include +#include #define REDIS_TLS_PROTO_TLSv1 (1<<0) #define REDIS_TLS_PROTO_TLSv1_1 (1<<1) @@ -868,6 +869,30 @@ int tlsProcessPendingData() { return processed; } +/* Fetch the peer certificate used for authentication on the specified + * connection and return it as a PEM-encoded sds. + */ +sds connTLSGetPeerCert(connection *conn_) { + tls_connection *conn = (tls_connection *) conn_; + if (conn_->type->get_type(conn_) != CONN_TYPE_TLS || !conn->ssl) return NULL; + + X509 *cert = SSL_get_peer_certificate(conn->ssl); + if (!cert) return NULL; + + BIO *bio = BIO_new(BIO_s_mem()); + if (bio == NULL || !PEM_write_bio_X509(bio, cert)) { + if (bio != NULL) BIO_free(bio); + return NULL; + } + + const char *bio_ptr; + long long bio_len = BIO_get_mem_data(bio, &bio_ptr); + sds cert_pem = sdsnewlen(bio_ptr, bio_len); + BIO_free(bio); + + return cert_pem; +} + #else /* USE_OPENSSL */ void tlsInit(void) { @@ -897,4 +922,9 @@ int tlsProcessPendingData() { return 0; } +sds connTLSGetPeerCert(connection *conn_) { + (void) conn_; + return NULL; +} + #endif diff --git a/tests/modules/misc.c b/tests/modules/misc.c index a43bb84ac..621b92b2d 100644 --- a/tests/modules/misc.c +++ b/tests/modules/misc.c @@ -195,6 +195,23 @@ int test_setlfu(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) return REDISMODULE_OK; } +int test_getclientcert(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) +{ + (void) argv; + (void) argc; + + RedisModuleString *cert = RedisModule_GetClientCertificate(ctx, + RedisModule_GetClientId(ctx)); + if (!cert) { + RedisModule_ReplyWithNull(ctx); + } else { + RedisModule_ReplyWithString(ctx, cert); + RedisModule_FreeString(ctx, cert); + } + + return REDISMODULE_OK; +} + int test_clientinfo(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { (void) argv; @@ -283,6 +300,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"test.clientinfo", test_clientinfo,"",0,0,0) == REDISMODULE_ERR) return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"test.getclientcert", test_getclientcert,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"test.log_tsctx", test_log_tsctx,"",0,0,0) == REDISMODULE_ERR) return REDISMODULE_ERR; diff --git a/tests/unit/moduleapi/misc.tcl b/tests/unit/moduleapi/misc.tcl index 2c9ac4fd0..e825f33ff 100644 --- a/tests/unit/moduleapi/misc.tcl +++ b/tests/unit/moduleapi/misc.tcl @@ -87,6 +87,16 @@ start_server {tags {"modules"}} { assert { [dict get $info flags] == "${ssl_flag}::tracking::" } } + test {test module getclientcert api} { + set cert [r test.getclientcert] + + if {$::tls} { + assert {$cert != ""} + } else { + assert {$cert == ""} + } + } + test {test detached thread safe cnotext} { r test.log_tsctx "info" "Test message" verify_log_message 0 "* Test message*" 0