Adds CT validation to SSL connections
Disabled by default, but can be enabled by setting the ct_validation_callback on a SSL or SSL_CTX. Reviewed-by: Ben Laurie <ben@openssl.org> Reviewed-by: Rich Salz <rsalz@openssl.org>
This commit is contained in:
295
ssl/ssl_lib.c
295
ssl/ssl_lib.c
@@ -159,6 +159,9 @@
|
||||
# include <openssl/engine.h>
|
||||
#endif
|
||||
#include <openssl/async.h>
|
||||
#ifndef OPENSSL_NO_CT
|
||||
# include <openssl/ct.h>
|
||||
#endif
|
||||
|
||||
const char SSL_version_str[] = OPENSSL_VERSION_TEXT;
|
||||
|
||||
@@ -740,6 +743,12 @@ SSL *SSL_new(SSL_CTX *ctx)
|
||||
|
||||
s->job = NULL;
|
||||
|
||||
#ifndef OPENSSL_NO_CT
|
||||
if (!SSL_set_ct_validation_callback(s, ctx->ct_validation_callback,
|
||||
ctx->ct_validation_callback_arg))
|
||||
goto err;
|
||||
#endif
|
||||
|
||||
return (s);
|
||||
err:
|
||||
SSL_free(s);
|
||||
@@ -1041,6 +1050,10 @@ void SSL_free(SSL *s)
|
||||
#endif /* OPENSSL_NO_EC */
|
||||
sk_X509_EXTENSION_pop_free(s->tlsext_ocsp_exts, X509_EXTENSION_free);
|
||||
sk_OCSP_RESPID_pop_free(s->tlsext_ocsp_ids, OCSP_RESPID_free);
|
||||
#ifndef OPENSSL_NO_CT
|
||||
SCT_LIST_free(s->scts);
|
||||
OPENSSL_free(s->tlsext_scts);
|
||||
#endif
|
||||
OPENSSL_free(s->tlsext_ocsp_resp);
|
||||
OPENSSL_free(s->alpn_client_proto_list);
|
||||
|
||||
@@ -2321,7 +2334,11 @@ SSL_CTX *SSL_CTX_new(const SSL_METHOD *meth)
|
||||
ret->cert_store = X509_STORE_new();
|
||||
if (ret->cert_store == NULL)
|
||||
goto err;
|
||||
|
||||
#ifndef OPENSSL_NO_CT
|
||||
ret->ctlog_store = CTLOG_STORE_new();
|
||||
if (ret->ctlog_store == NULL)
|
||||
goto err;
|
||||
#endif
|
||||
if (!ssl_create_cipher_list(ret->method,
|
||||
&ret->cipher_list, &ret->cipher_list_by_id,
|
||||
SSL_DEFAULT_CIPHER_LIST, ret->cert)
|
||||
@@ -2439,6 +2456,9 @@ void SSL_CTX_free(SSL_CTX *a)
|
||||
CRYPTO_free_ex_data(CRYPTO_EX_INDEX_SSL_CTX, a, &a->ex_data);
|
||||
lh_SSL_SESSION_free(a->sessions);
|
||||
X509_STORE_free(a->cert_store);
|
||||
#ifndef OPENSSL_NO_CT
|
||||
CTLOG_STORE_free(a->ctlog_store);
|
||||
#endif
|
||||
sk_SSL_CIPHER_free(a->cipher_list);
|
||||
sk_SSL_CIPHER_free(a->cipher_list_by_id);
|
||||
ssl_cert_free(a->cert);
|
||||
@@ -3796,3 +3816,276 @@ STACK_OF(X509) *SSL_get0_verified_chain(const SSL *s)
|
||||
}
|
||||
|
||||
IMPLEMENT_OBJ_BSEARCH_GLOBAL_CMP_FN(SSL_CIPHER, SSL_CIPHER, ssl_cipher_id);
|
||||
|
||||
#ifndef OPENSSL_NO_CT
|
||||
|
||||
/*
|
||||
* Moves SCTs from the |src| stack to the |dst| stack.
|
||||
* The source of each SCT will be set to |origin|.
|
||||
* If |dst| points to a NULL pointer, a new stack will be created and owned by
|
||||
* the caller.
|
||||
* Returns the number of SCTs moved, or a negative integer if an error occurs.
|
||||
*/
|
||||
static int ct_move_scts(STACK_OF(SCT) **dst, STACK_OF(SCT) *src, sct_source_t origin)
|
||||
{
|
||||
int scts_moved = 0;
|
||||
SCT *sct = NULL;
|
||||
|
||||
if (*dst == NULL) {
|
||||
*dst = sk_SCT_new_null();
|
||||
if (*dst == NULL) {
|
||||
SSLerr(SSL_F_CT_MOVE_SCTS, ERR_R_MALLOC_FAILURE);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
while ((sct = sk_SCT_pop(src)) != NULL) {
|
||||
if (SCT_set_source(sct, origin) != 1)
|
||||
goto err;
|
||||
|
||||
if (sk_SCT_push(*dst, sct) <= 0)
|
||||
goto err;
|
||||
scts_moved += 1;
|
||||
}
|
||||
|
||||
return scts_moved;
|
||||
err:
|
||||
if (sct != NULL)
|
||||
sk_SCT_push(src, sct); /* Put the SCT back */
|
||||
return scts_moved;
|
||||
}
|
||||
|
||||
/*
|
||||
* Look for data collected during ServerHello and parse if found.
|
||||
* Return 1 on success, 0 on failure.
|
||||
*/
|
||||
static int ct_extract_tls_extension_scts(SSL *s)
|
||||
{
|
||||
int scts_extracted = 0;
|
||||
|
||||
if (s->tlsext_scts != NULL) {
|
||||
const unsigned char *p = s->tlsext_scts;
|
||||
STACK_OF(SCT) *scts = o2i_SCT_LIST(NULL, &p, s->tlsext_scts_len);
|
||||
|
||||
scts_extracted = ct_move_scts(&s->scts, scts, SCT_SOURCE_TLS_EXTENSION);
|
||||
|
||||
SCT_LIST_free(scts);
|
||||
}
|
||||
|
||||
return scts_extracted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks for an OCSP response and then attempts to extract any SCTs found if it
|
||||
* contains an SCT X509 extension. They will be stored in |s->scts|.
|
||||
* Returns:
|
||||
* - The number of SCTs extracted, assuming an OCSP response exists.
|
||||
* - 0 if no OCSP response exists or it contains no SCTs.
|
||||
* - A negative integer if an error occurs.
|
||||
*/
|
||||
static int ct_extract_ocsp_response_scts(SSL *s)
|
||||
{
|
||||
int scts_extracted = 0;
|
||||
const unsigned char *p;
|
||||
OCSP_BASICRESP *br = NULL;
|
||||
OCSP_RESPONSE *rsp = NULL;
|
||||
STACK_OF(SCT) *scts = NULL;
|
||||
int i;
|
||||
|
||||
if (s->tlsext_ocsp_resp == NULL || s->tlsext_ocsp_resplen == 0)
|
||||
goto err;
|
||||
|
||||
p = s->tlsext_ocsp_resp;
|
||||
rsp = d2i_OCSP_RESPONSE(NULL, &p, s->tlsext_ocsp_resplen);
|
||||
if (rsp == NULL)
|
||||
goto err;
|
||||
|
||||
br = OCSP_response_get1_basic(rsp);
|
||||
if (br == NULL)
|
||||
goto err;
|
||||
|
||||
for (i = 0; i < OCSP_resp_count(br); ++i) {
|
||||
OCSP_SINGLERESP *single = OCSP_resp_get0(br, i);
|
||||
|
||||
if (single == NULL)
|
||||
continue;
|
||||
|
||||
scts = OCSP_SINGLERESP_get1_ext_d2i(single, NID_ct_cert_scts, NULL, NULL);
|
||||
scts_extracted = ct_move_scts(&s->scts, scts,
|
||||
SCT_SOURCE_OCSP_STAPLED_RESPONSE);
|
||||
if (scts_extracted < 0)
|
||||
goto err;
|
||||
}
|
||||
err:
|
||||
SCT_LIST_free(scts);
|
||||
OCSP_BASICRESP_free(br);
|
||||
OCSP_RESPONSE_free(rsp);
|
||||
return scts_extracted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to extract SCTs from the peer certificate.
|
||||
* Return the number of SCTs extracted, or a negative integer if an error
|
||||
* occurs.
|
||||
*/
|
||||
static int ct_extract_x509v3_extension_scts(SSL *s)
|
||||
{
|
||||
int scts_extracted = 0;
|
||||
X509 *cert = SSL_get_peer_certificate(s);
|
||||
|
||||
if (cert != NULL) {
|
||||
STACK_OF(SCT) *scts =
|
||||
X509_get_ext_d2i(cert, NID_ct_precert_scts, NULL, NULL);
|
||||
|
||||
scts_extracted =
|
||||
ct_move_scts(&s->scts, scts, SCT_SOURCE_X509V3_EXTENSION);
|
||||
|
||||
SCT_LIST_free(scts);
|
||||
}
|
||||
|
||||
return scts_extracted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to find all received SCTs by checking TLS extensions, the OCSP
|
||||
* response (if it exists) and X509v3 extensions in the certificate.
|
||||
* Returns NULL if an error occurs.
|
||||
*/
|
||||
const STACK_OF(SCT) *SSL_get0_peer_scts(SSL *s)
|
||||
{
|
||||
if (!s->scts_parsed) {
|
||||
if (ct_extract_tls_extension_scts(s) < 0 ||
|
||||
ct_extract_ocsp_response_scts(s) < 0 ||
|
||||
ct_extract_x509v3_extension_scts(s) < 0)
|
||||
goto err;
|
||||
|
||||
s->scts_parsed = 1;
|
||||
}
|
||||
return s->scts;
|
||||
err:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int SSL_set_ct_validation_callback(SSL *s, ct_validation_cb callback, void *arg)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Since code exists that uses the custom extension handler for CT, look
|
||||
* for this and throw an error if they have already registered to use CT.
|
||||
*/
|
||||
if (callback != NULL && SSL_CTX_has_client_custom_ext(s->ctx,
|
||||
TLSEXT_TYPE_signed_certificate_timestamp)) {
|
||||
SSLerr(SSL_F_SSL_SET_CT_VALIDATION_CALLBACK,
|
||||
SSL_R_CUSTOM_EXT_HANDLER_ALREADY_INSTALLED);
|
||||
goto err;
|
||||
}
|
||||
|
||||
s->ct_validation_callback = callback;
|
||||
s->ct_validation_callback_arg = arg;
|
||||
|
||||
if (callback != NULL) {
|
||||
/* If we are validating CT, then we MUST accept SCTs served via OCSP */
|
||||
if (!SSL_set_tlsext_status_type(s, TLSEXT_STATUSTYPE_ocsp))
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = 1;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SSL_CTX_set_ct_validation_callback(SSL_CTX *ctx, ct_validation_cb callback,
|
||||
void *arg)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Since code exists that uses the custom extension handler for CT, look for
|
||||
* this and throw an error if they have already registered to use CT.
|
||||
*/
|
||||
if (callback != NULL && SSL_CTX_has_client_custom_ext(ctx,
|
||||
TLSEXT_TYPE_signed_certificate_timestamp)) {
|
||||
SSLerr(SSL_F_SSL_CTX_SET_CT_VALIDATION_CALLBACK,
|
||||
SSL_R_CUSTOM_EXT_HANDLER_ALREADY_INSTALLED);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ctx->ct_validation_callback = callback;
|
||||
ctx->ct_validation_callback_arg = arg;
|
||||
ret = 1;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
ct_validation_cb SSL_get_ct_validation_callback(const SSL *s)
|
||||
{
|
||||
return s->ct_validation_callback;
|
||||
}
|
||||
|
||||
ct_validation_cb SSL_CTX_get_ct_validation_callback(const SSL_CTX *ctx)
|
||||
{
|
||||
return ctx->ct_validation_callback;
|
||||
}
|
||||
|
||||
int SSL_validate_ct(SSL *s)
|
||||
{
|
||||
int ret = 0;
|
||||
X509 *cert = SSL_get_peer_certificate(s);
|
||||
X509 *issuer = NULL;
|
||||
CT_POLICY_EVAL_CTX *ctx = NULL;
|
||||
const STACK_OF(SCT) *scts;
|
||||
|
||||
/* If no callback is set, attempt no validation - just return success */
|
||||
if (s->ct_validation_callback == NULL)
|
||||
return 1;
|
||||
|
||||
if (cert == NULL) {
|
||||
SSLerr(SSL_F_SSL_VALIDATE_CT, SSL_R_NO_CERTIFICATE_ASSIGNED);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (s->verified_chain != NULL && sk_X509_num(s->verified_chain) > 1)
|
||||
issuer = sk_X509_value(s->verified_chain, 1);
|
||||
|
||||
ctx = CT_POLICY_EVAL_CTX_new();
|
||||
if (ctx == NULL) {
|
||||
SSLerr(SSL_F_SSL_VALIDATE_CT, ERR_R_MALLOC_FAILURE);
|
||||
goto end;
|
||||
}
|
||||
|
||||
CT_POLICY_EVAL_CTX_set0_cert(ctx, cert);
|
||||
CT_POLICY_EVAL_CTX_set0_issuer(ctx, issuer);
|
||||
CT_POLICY_EVAL_CTX_set0_log_store(ctx, s->ctx->ctlog_store);
|
||||
|
||||
scts = SSL_get0_peer_scts(s);
|
||||
|
||||
if (SCT_LIST_validate(scts, ctx) != 1) {
|
||||
SSLerr(SSL_F_SSL_VALIDATE_CT, SSL_R_SCT_VERIFICATION_FAILED);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = s->ct_validation_callback(ctx, scts, s->ct_validation_callback_arg);
|
||||
if (ret < 0)
|
||||
ret = 0; /* This function returns 0 on failure */
|
||||
|
||||
end:
|
||||
CT_POLICY_EVAL_CTX_free(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SSL_CTX_set_default_ctlog_list_file(SSL_CTX *ctx)
|
||||
{
|
||||
int ret = CTLOG_STORE_load_default_file(ctx->ctlog_store);
|
||||
|
||||
/* Clear any errors if the default file does not exist */
|
||||
ERR_clear_error();
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SSL_CTX_set_ctlog_list_file(SSL_CTX *ctx, const char *path)
|
||||
{
|
||||
return CTLOG_STORE_load_file(ctx->ctlog_store, path);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user