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 0aec98dce2)
This commit is contained in:
Yossi Gottlieb 2020-10-11 17:11:42 +03:00 committed by Oran Agra
parent 28d1fe6718
commit 4147a2202e
6 changed files with 88 additions and 0 deletions

View File

@ -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();

View File

@ -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);
}

View File

@ -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

View File

@ -37,6 +37,7 @@
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/pem.h>
#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

View File

@ -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;

View File

@ -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 "*<misc> Test message*" 0