Skip to content

Commit

Permalink
Add SSL cert validation callback
Browse files Browse the repository at this point in the history
  • Loading branch information
Chuck Kasabula committed Dec 11, 2024
1 parent ecdff33 commit 9818f3d
Show file tree
Hide file tree
Showing 8 changed files with 428 additions and 12 deletions.
152 changes: 152 additions & 0 deletions src/cert.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright 2015-2021 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "natsp.h"

#include "mem.h"
#include "cert.h"

#if defined(NATS_HAS_TLS)
natsStatus
natsCert_create(natsCert **newCert, X509 *x509Cert)
{
natsCert *cert = NULL;
const ASN1_TIME* notBefore;
const ASN1_TIME* notAfter;

if (x509Cert == NULL)
return nats_setDefaultError(NATS_INVALID_ARG);

cert = NATS_CALLOC(1, sizeof(natsCert));
if (cert == NULL)
return nats_setDefaultError(NATS_NO_MEMORY);

cert->subjectName = X509_NAME_oneline(X509_get_subject_name(x509Cert), NULL, 0);
cert->issuerName = X509_NAME_oneline(X509_get_issuer_name(x509Cert), NULL, 0);

notBefore = X509_get0_notBefore(x509Cert);
ASN1_TIME_to_tm(notBefore, &cert->tmNotBefore);
notAfter = X509_get0_notAfter(x509Cert);
ASN1_TIME_to_tm(notAfter, &cert->tmNotAfter);

*newCert = cert;

return NATS_OK;
}

void
natsCert_free(natsCert* cert)
{
if (cert == NULL)
return;

OPENSSL_free((char *)cert->subjectName);
OPENSSL_free((char*)cert->issuerName);

NATS_FREE(cert);
}

static natsStatus
_createChainElement(natsCertChain **newElement, X509 *x509Cert)
{
natsCertChain *element = NULL;
natsStatus s = NATS_OK;
natsCert *cert = NULL;

s = natsCert_create(&cert, x509Cert);
if (s != NATS_OK)
return s;

element = NATS_CALLOC(1, sizeof(natsCertChain));
if (element == NULL)
return nats_setDefaultError(NATS_NO_MEMORY);

element->cert = cert;
element->next = NULL;

*newElement = element;

return NATS_OK;
}

static natsStatus
_appendChainElement(natsCertChain **chain, X509 *x509Cert)
{
natsStatus s = NATS_OK;
natsCertChain *newElement = NULL;
natsCertChain *temp = NULL;

s = _createChainElement(&newElement, x509Cert);
if (s != NATS_OK)
return s;

if (*chain == NULL)
{
*chain = newElement;
return NATS_OK;
}

temp = *chain;
while (temp->next != NULL)
{
temp = temp->next;
}
temp->next = newElement;
return NATS_OK;
}


natsStatus
natsCertChain_create(natsCertChain **newChain, STACK_OF(X509) *x509Chain)
{
natsCertChain *chain = NULL;
int numElements;
X509 *x509Cert;
natsStatus s = NATS_OK;

if (x509Chain == NULL)
return nats_setDefaultError(NATS_INVALID_ARG);

numElements = sk_X509_num(x509Chain);
for (int i = 0; i < numElements; i++)
{
x509Cert = sk_X509_value(x509Chain, i);
s = _appendChainElement(&chain, x509Cert);
if (s != NATS_OK)
{
natsCertChain_free(chain);
return s;
}
}

*newChain = chain;
return NATS_OK;
}

void
natsCertChain_free(natsCertChain *chain)
{
natsCertChain *temp;

if (chain == NULL)
return;

while (chain != NULL)
{
temp = chain;
chain = chain->next;
natsCert_free(temp->cert);
NATS_FREE(temp);
}
}
#endif // NATS_HAS_TLS
47 changes: 47 additions & 0 deletions src/cert.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2015-2024 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef CERT_H_
#define CERT_H_

#include "status.h"

struct __natsCert
{
const char *subjectName;
const char *issuerName;
struct tm tmNotBefore;
struct tm tmNotAfter;
};

struct __natsCertChainElement
{
natsCert* cert;
struct __natsCertChainElement *next;
};

#if defined(NATS_HAS_TLS)
natsStatus
natsCert_create(natsCert **newCert, X509 *x509Cert);

void
natsCert_free(natsCert *cert);

natsStatus
natsCertChain_create(natsCertChain **newChain, STACK_OF(X509) *x509Chain);

void
natsCertChain_free(natsCertChain *chain);
#endif // NATS_HAS_TLS

#endif // CERT_H_
44 changes: 37 additions & 7 deletions src/conn.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "crypto.h"
#include "js.h"
#include "glib/glib.h"
#include "cert.h"

#define DEFAULT_SCRATCH_SIZE (512)
#define MAX_INFO_MESSAGE_SIZE (32768)
Expand Down Expand Up @@ -620,10 +621,15 @@ static int
_collectSSLErr(int preverifyOk, X509_STORE_CTX* ctx)
{
SSL *ssl = NULL;
X509 *cert = X509_STORE_CTX_get_current_cert(ctx);
int depth = X509_STORE_CTX_get_error_depth(ctx);
int err = X509_STORE_CTX_get_error(ctx);
natsConnection *nc = NULL;
X509 *x509Cert = X509_STORE_CTX_get_current_cert(ctx);
int depth = X509_STORE_CTX_get_error_depth(ctx);
int err = X509_STORE_CTX_get_error(ctx);
natsConnection *nc = NULL;
natsCert *cert = NULL;
bool certValid;
STACK_OF(X509) *x509Chain;
natsStatus s = NATS_OK;
natsCertChain *chain = NULL;

// Retrieve the SSL object, then our connection...
ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
Expand All @@ -633,11 +639,33 @@ _collectSSLErr(int preverifyOk, X509_STORE_CTX* ctx)
if (nc->opts->sslCtx->skipVerify)
return 1;

if (nc->opts->sslCtx->certificateValidationCB != NULL)
{
s = natsCert_create(&cert, x509Cert);
if (s == NATS_OK)
{
x509Chain = SSL_get_peer_cert_chain(ssl);
if (x509Chain != NULL)
{
s = natsCertChain_create(&chain, x509Chain);
}

if (s == NATS_OK)
{
certValid = nc->opts->sslCtx->certificateValidationCB(preverifyOk, cert, chain);
natsCert_free(cert);
natsCertChain_free(chain);
nc->opts->sslCtx->certificateValidationResult = certValid;
return certValid ? 1 : 0;
}
}
}

if (!preverifyOk)
{
char certName[256]= {0};

X509_NAME_oneline(X509_get_subject_name(cert), certName, sizeof(certName));
X509_NAME_oneline(X509_get_subject_name(x509Cert), certName, sizeof(certName));

if (err == X509_V_ERR_HOSTNAME_MISMATCH)
{
Expand All @@ -649,7 +677,7 @@ _collectSSLErr(int preverifyOk, X509_STORE_CTX* ctx)
{
char issuer[256] = {0};

X509_NAME_oneline(X509_get_issuer_name(cert), issuer, sizeof(issuer));
X509_NAME_oneline(X509_get_issuer_name(x509Cert), issuer, sizeof(issuer));

snprintf_truncate(nc->errStr, sizeof(nc->errStr), "%d:%s:depth=%d:cert=%s:issuer=%s",
err, X509_verify_cert_error_string(err), depth,
Expand Down Expand Up @@ -704,6 +732,8 @@ _makeTLSConn(natsConnection *nc)
}
if (s == NATS_OK)
{
nc->opts->sslCtx->certificateValidationResult = false;

if (nc->opts->sslCtx->skipVerify)
{
SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
Expand Down Expand Up @@ -746,7 +776,7 @@ _makeTLSConn(natsConnection *nc)
s = nats_setError(NATS_SSL_ERROR, "unable to set SNI extension for hostname '%s'", nc->cur->url->host);
}
#endif
if ((s == NATS_OK) && (SSL_do_handshake(ssl) != 1))
if ((s == NATS_OK) && (SSL_do_handshake(ssl) != 1) && !nc->opts->sslCtx->certificateValidationResult)
{
s = nats_setError(NATS_SSL_ERROR,
"SSL handshake error: %s",
Expand Down
37 changes: 37 additions & 0 deletions src/nats.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,18 @@ typedef struct __natsMsg natsMsg;
*/
typedef struct __natsOptions natsOptions;

/** \brief A wrapper for an X509 certificate.
*
* A #natsCert represents an X509 certificate.
*/
typedef struct __natsCert natsCert;

/** \brief A wrapper for an X509 certificate chain.
*
* A #natsCertChain represents an X509 certificate chain.
*/
typedef struct __natsCertChainElement natsCertChain;

/** \brief Unique subject often used for point-to-point communication.
*
* This can be used as the reply for a request. Inboxes are meant to be
Expand Down Expand Up @@ -2447,6 +2459,31 @@ natsOptions_SetName(natsOptions *opts, const char *name);
NATS_EXTERN natsStatus
natsOptions_SetSecure(natsOptions *opts, bool secure);

/** \brief Callback used to verify the SSL certificate
*
* When set, this callback will be invoked to allow for custom SSL certificate validation.
*
* @see natsOptions_SetCertificateValidationCB()
* @see https://docs.openssl.org/master/man3/SSL_CTX_set_verify/
*
* @param preverify_ok indicates, whether the verification of the certificate in question was passed (preverify_ok=1) or not (preverify_ok=0)
* @param cert is a pointer to an object containing information about the certificate
* @param chain is a pointer to an object containing information about the certificate chain
*
* @return a boolean value that determines whether the specified certificate is accepted for authentication.
*/
typedef bool (*CertificateValidationCB)(int preverify_ok, natsCert *cert, natsCertChain *chain);

/** \brief Sets the certificate validation callback.
*
* Sets a callback used to verify the SSL certificate.
*
* @param opts the pointer to the #natsOptions object.
* @param verifyCallback the callback to invoke to fetch the user JWT.
*/
NATS_EXTERN natsStatus
natsOptions_SetCertificateValidationCB(natsOptions *opts, CertificateValidationCB certificateValidationCB);

/** \brief Performs TLS handshake first.
*
* If the server is not configured to require the client to perform
Expand Down
12 changes: 7 additions & 5 deletions src/natsp.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,13 @@ typedef struct __natsServerInfo

typedef struct __natsSSLCtx
{
natsMutex *lock;
int refs;
SSL_CTX *ctx;
char *expectedHostname;
bool skipVerify;
natsMutex *lock;
int refs;
SSL_CTX *ctx;
char *expectedHostname;
bool skipVerify;
bool certificateValidationResult;
CertificateValidationCB certificateValidationCB;

} natsSSLCtx;

Expand Down
24 changes: 24 additions & 0 deletions src/opts.c
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,24 @@ natsOptions_SetSecure(natsOptions *opts, bool secure)
return NATS_UPDATE_ERR_STACK(s);
}

natsStatus
natsOptions_SetCertificateValidationCB(natsOptions *opts, CertificateValidationCB certificateValidationCB)
{
natsStatus s = NATS_OK;

LOCK_AND_CHECK_OPTIONS(opts, 0);

s = natsOptions_SetSecure(opts, true);
if (s == NATS_OK)
{
opts->sslCtx->certificateValidationCB = certificateValidationCB;
}

UNLOCK_OPTS(opts);

return NATS_UPDATE_ERR_STACK(s);
}

natsStatus
natsOptions_TLSHandshakeFirst(natsOptions *opts)
{
Expand Down Expand Up @@ -708,6 +726,12 @@ natsOptions_SetSecure(natsOptions *opts, bool secure)
return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR);
}

natsStatus
natsOptions_SetCertificateValidationCB(natsOptions *opts, CertificateValidationCB certificateValidationCB)
{
return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR);
}

natsStatus
natsOptions_TLSHandshakeFirst(natsOptions *opts)
{
Expand Down
Loading

0 comments on commit 9818f3d

Please sign in to comment.