From: Rob Percival Date: Mon, 29 Feb 2016 17:33:02 +0000 (+0000) Subject: CT policy validation X-Git-Tag: OpenSSL_1_1_0-pre4~420 X-Git-Url: https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff_plain;h=7d054e5ab2aeaead14c0c19b808d62221020b0e1 CT policy validation Specifies a callback that will, in the future, be used by the SSL code to decide whether to abort a connection on Certificate Transparency grounds. Reviewed-by: Ben Laurie Reviewed-by: Rich Salz --- diff --git a/crypto/ct/Makefile.in b/crypto/ct/Makefile.in index de122df863..0daec36544 100644 --- a/crypto/ct/Makefile.in +++ b/crypto/ct/Makefile.in @@ -15,10 +15,10 @@ CFLAGS= $(INCLUDES) $(CFLAG) $(SHARED_CFLAG) GENERAL=Makefile LIB=$(TOP)/libcrypto.a -LIBSRC= ct_b64.c ct_err.c ct_log.c ct_oct.c ct_prn.c ct_sct.c ct_sct_ctx.c \ - ct_vfy.c ct_x509v3.c -LIBOBJ= ct_b64.o ct_err.o ct_log.o ct_oct.o ct_prn.o ct_sct.o ct_sct_ctx.o \ - ct_vfy.o ct_x509v3.o +LIBSRC= ct_b64.c ct_err.c ct_log.c ct_oct.c ct_policy.c ct_prn.c ct_sct.c \ + ct_sct_ctx.c ct_vfy.c ct_x509v3.c +LIBOBJ= ct_b64.o ct_err.o ct_log.o ct_oct.o ct_policy.o ct_prn.o ct_sct.o \ + ct_sct_ctx.o ct_vfy.o ct_x509v3.o SRC= $(LIBSRC) diff --git a/crypto/ct/build.info b/crypto/ct/build.info index fbf2495813..3ca0e31608 100644 --- a/crypto/ct/build.info +++ b/crypto/ct/build.info @@ -1,3 +1,3 @@ LIBS=../../libcrypto -SOURCE[../../libcrypto]= ct_b64.c ct_err.c ct_log.c ct_oct.c ct_prn.c ct_sct.c \ - ct_sct_ctx.c ct_vfy.c ct_x509v3.c +SOURCE[../../libcrypto]= ct_b64.c ct_err.c ct_log.c ct_oct.c ct_policy.c \ + ct_prn.c ct_sct.c ct_sct_ctx.c ct_vfy.c ct_x509v3.c diff --git a/crypto/ct/ct_err.c b/crypto/ct/ct_err.c index 6db237b534..c55677c722 100644 --- a/crypto/ct/ct_err.c +++ b/crypto/ct/ct_err.c @@ -77,7 +77,23 @@ static ERR_STRING_DATA CT_str_functs[] = { {ERR_FUNC(CT_F_CTLOG_STORE_LOAD_CTX_NEW), "CTLOG_STORE_LOAD_CTX_new"}, {ERR_FUNC(CT_F_CTLOG_STORE_LOAD_FILE), "CTLOG_STORE_load_file"}, {ERR_FUNC(CT_F_CT_BASE64_DECODE), "CT_base64_decode"}, + {ERR_FUNC(CT_F_CT_POLICY_EVAL_CTX_GET0_CERT), + "CT_POLICY_EVAL_CTX_get0_cert"}, + {ERR_FUNC(CT_F_CT_POLICY_EVAL_CTX_GET0_ISSUER), + "CT_POLICY_EVAL_CTX_get0_issuer"}, + {ERR_FUNC(CT_F_CT_POLICY_EVAL_CTX_GET0_LOG_STORE), + "CT_POLICY_EVAL_CTX_get0_log_store"}, + {ERR_FUNC(CT_F_CT_POLICY_EVAL_CTX_NEW), "CT_POLICY_EVAL_CTX_new"}, + {ERR_FUNC(CT_F_CT_POLICY_EVAL_CTX_SET0_CERT), + "CT_POLICY_EVAL_CTX_set0_cert"}, + {ERR_FUNC(CT_F_CT_POLICY_EVAL_CTX_SET0_ISSUER), + "CT_POLICY_EVAL_CTX_set0_issuer"}, + {ERR_FUNC(CT_F_CT_POLICY_EVAL_CTX_SET0_LOG_STORE), + "CT_POLICY_EVAL_CTX_set0_log_store"}, {ERR_FUNC(CT_F_CT_V1_LOG_ID_FROM_PKEY), "CT_v1_log_id_from_pkey"}, + {ERR_FUNC(CT_F_CT_VERIFY_AT_LEAST_ONE_GOOD_SCT), + "CT_verify_at_least_one_good_sct"}, + {ERR_FUNC(CT_F_CT_VERIFY_NO_BAD_SCTS), "CT_verify_no_bad_scts"}, {ERR_FUNC(CT_F_D2I_SCT_LIST), "d2i_SCT_LIST"}, {ERR_FUNC(CT_F_I2D_SCT_LIST), "i2d_SCT_LIST"}, {ERR_FUNC(CT_F_I2O_SCT), "i2o_SCT"}, @@ -87,6 +103,7 @@ static ERR_STRING_DATA CT_str_functs[] = { {ERR_FUNC(CT_F_O2I_SCT_LIST), "o2i_SCT_LIST"}, {ERR_FUNC(CT_F_O2I_SCT_SIGNATURE), "o2i_SCT_signature"}, {ERR_FUNC(CT_F_SCT_CTX_NEW), "SCT_CTX_new"}, + {ERR_FUNC(CT_F_SCT_LIST_VALIDATE), "SCT_LIST_validate"}, {ERR_FUNC(CT_F_SCT_NEW), "SCT_new"}, {ERR_FUNC(CT_F_SCT_NEW_FROM_BASE64), "SCT_new_from_base64"}, {ERR_FUNC(CT_F_SCT_SET0_LOG_ID), "SCT_set0_log_id"}, @@ -97,6 +114,7 @@ static ERR_STRING_DATA CT_str_functs[] = { {ERR_FUNC(CT_F_SCT_SET_SIGNATURE_NID), "SCT_set_signature_nid"}, {ERR_FUNC(CT_F_SCT_SET_VERSION), "SCT_set_version"}, {ERR_FUNC(CT_F_SCT_SIGNATURE_IS_VALID), "SCT_signature_is_valid"}, + {ERR_FUNC(CT_F_SCT_VALIDATE), "SCT_validate"}, {ERR_FUNC(CT_F_SCT_VERIFY), "SCT_verify"}, {ERR_FUNC(CT_F_SCT_VERIFY_V1), "SCT_verify_v1"}, {0, NULL} @@ -111,12 +129,15 @@ static ERR_STRING_DATA CT_str_reasons[] = { "log conf missing description"}, {ERR_REASON(CT_R_LOG_CONF_MISSING_KEY), "log conf missing key"}, {ERR_REASON(CT_R_LOG_KEY_INVALID), "log key invalid"}, + {ERR_REASON(CT_R_NOT_ENOUGH_SCTS), "not enough scts"}, {ERR_REASON(CT_R_SCT_INVALID), "sct invalid"}, {ERR_REASON(CT_R_SCT_INVALID_SIGNATURE), "sct invalid signature"}, {ERR_REASON(CT_R_SCT_LIST_INVALID), "sct list invalid"}, {ERR_REASON(CT_R_SCT_LOG_ID_MISMATCH), "sct log id mismatch"}, {ERR_REASON(CT_R_SCT_NOT_SET), "sct not set"}, {ERR_REASON(CT_R_SCT_UNSUPPORTED_VERSION), "sct unsupported version"}, + {ERR_REASON(CT_R_SCT_VALIDATION_STATUS_NOT_SET), + "sct validation status not set"}, {ERR_REASON(CT_R_UNRECOGNIZED_SIGNATURE_NID), "unrecognized signature nid"}, {ERR_REASON(CT_R_UNSUPPORTED_ENTRY_TYPE), "unsupported entry type"}, diff --git a/crypto/ct/ct_locl.h b/crypto/ct/ct_locl.h index fce234da78..b82fabc4f0 100644 --- a/crypto/ct/ct_locl.h +++ b/crypto/ct/ct_locl.h @@ -127,6 +127,8 @@ struct sct_st { sct_source_t source; /* The CT log that produced this SCT. */ CTLOG *log; + /* The result of the last attempt to validate this SCT. */ + sct_validation_status_t validation_status; }; /* Miscellaneous data that is useful when verifying an SCT */ @@ -147,6 +149,15 @@ struct sct_ctx_st { size_t prederlen; }; +/* Context when evaluating whether a Certificate Transparency policy is met */ +struct ct_policy_eval_ctx_st { + X509 *cert; + X509 *issuer; + CTLOG_STORE *log_store; + STACK_OF(SCT) *good_scts; + STACK_OF(SCT) *bad_scts; +}; + /* * Creates a new context for verifying an SCT. */ diff --git a/crypto/ct/ct_policy.c b/crypto/ct/ct_policy.c new file mode 100644 index 0000000000..1236411000 --- /dev/null +++ b/crypto/ct/ct_policy.c @@ -0,0 +1,110 @@ +/* +* Implementations of Certificate Transparency SCT policies. +* Written by Rob Percival (robpercival@google.com) for the OpenSSL project. +*/ +/* ==================================================================== +* Copyright (c) 2016 The OpenSSL Project. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. All advertising materials mentioning features or use of this +* software must display the following acknowledgment: +* "This product includes software developed by the OpenSSL Project +* for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" +* +* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to +* endorse or promote products derived from this software without +* prior written permission. For written permission, please contact +* licensing@OpenSSL.org. +* +* 5. Products derived from this software may not be called "OpenSSL" +* nor may "OpenSSL" appear in their names without prior written +* permission of the OpenSSL Project. +* +* 6. Redistributions of any form whatsoever must retain the following +* acknowledgment: +* "This product includes software developed by the OpenSSL Project +* for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" +* +* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY +* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR +* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +* OF THE POSSIBILITY OF SUCH DAMAGE. +* ==================================================================== +*/ + +#ifdef OPENSSL_NO_CT +# error "CT is disabled" +#endif + +#include +#include + +#include "ct_locl.h" + +CT_POLICY_EVAL_CTX *CT_POLICY_EVAL_CTX_new(void) +{ + CT_POLICY_EVAL_CTX *ctx = OPENSSL_zalloc(sizeof(CT_POLICY_EVAL_CTX)); + + if (ctx == NULL) { + CTerr(CT_F_CT_POLICY_EVAL_CTX_NEW, ERR_R_MALLOC_FAILURE); + return NULL; + } + + return ctx; +} + +void CT_POLICY_EVAL_CTX_free(CT_POLICY_EVAL_CTX *ctx) +{ + OPENSSL_free(ctx); +} + +void CT_POLICY_EVAL_CTX_set0_cert(CT_POLICY_EVAL_CTX *ctx, X509 *cert) +{ + ctx->cert = cert; +} + +void CT_POLICY_EVAL_CTX_set0_issuer(CT_POLICY_EVAL_CTX *ctx, X509 *issuer) +{ + ctx->issuer = issuer; +} + +void CT_POLICY_EVAL_CTX_set0_log_store(CT_POLICY_EVAL_CTX *ctx, + CTLOG_STORE *log_store) +{ + ctx->log_store = log_store; +} + +X509* CT_POLICY_EVAL_CTX_get0_cert(CT_POLICY_EVAL_CTX *ctx) +{ + return ctx->cert; +} + +X509* CT_POLICY_EVAL_CTX_get0_issuer(CT_POLICY_EVAL_CTX *ctx) +{ + return ctx->issuer; +} + +CTLOG_STORE *CT_POLICY_EVAL_CTX_get0_log_store(CT_POLICY_EVAL_CTX *ctx) +{ + return ctx->log_store; +} + diff --git a/crypto/ct/ct_sct.c b/crypto/ct/ct_sct.c index e75061d290..35f815235f 100644 --- a/crypto/ct/ct_sct.c +++ b/crypto/ct/ct_sct.c @@ -356,3 +356,94 @@ int SCT_LIST_set0_logs(STACK_OF(SCT) *sct_list, const CTLOG_STORE *ct_logs) return sct_logs_found; } + +sct_validation_status_t SCT_get_validation_status(const SCT *sct) +{ + return sct->validation_status; +} + +int SCT_validate(SCT *sct, const CT_POLICY_EVAL_CTX *ctx) +{ + int is_sct_valid = -1; + SCT_CTX *sctx = NULL; + X509_PUBKEY *pub = NULL, *log_pkey = NULL; + + switch (sct->version) { + case SCT_VERSION_V1: + if (sct->log == NULL) + sct->log = CTLOG_STORE_get0_log_by_id(ctx->log_store, + sct->log_id, + CT_V1_HASHLEN); + break; + default: + sct->validation_status = SCT_VALIDATION_STATUS_UNKNOWN_VERSION; + goto end; + } + + if (sct->log == NULL) { + sct->validation_status = SCT_VALIDATION_STATUS_UNKNOWN_LOG; + goto end; + } + + sctx = SCT_CTX_new(); + if (sctx == NULL) + goto err; + + if (X509_PUBKEY_set(&log_pkey, CTLOG_get0_public_key(sct->log)) != 1) + goto err; + if (SCT_CTX_set1_pubkey(sctx, log_pkey) != 1) + goto err; + + if (SCT_get_log_entry_type(sct) == CT_LOG_ENTRY_TYPE_PRECERT) { + EVP_PKEY *issuer_pkey; + + if (ctx->issuer == NULL) { + sct->validation_status = SCT_VALIDATION_STATUS_UNVERIFIED; + goto end; + } + + issuer_pkey = X509_get_pubkey(ctx->issuer); + + if (X509_PUBKEY_set(&pub, issuer_pkey) != 1) + goto err; + if (SCT_CTX_set1_issuer_pubkey(sctx, pub) != 1) + goto err; + } + + if (SCT_CTX_set1_cert(sctx, ctx->cert, NULL) != 1) + goto err; + + sct->validation_status = SCT_verify(sctx, sct) == 1 ? + SCT_VALIDATION_STATUS_VALID : SCT_VALIDATION_STATUS_INVALID; + +end: + is_sct_valid = sct->validation_status == SCT_VALIDATION_STATUS_VALID; +err: + X509_PUBKEY_free(pub); + X509_PUBKEY_free(log_pkey); + SCT_CTX_free(sctx); + + return is_sct_valid; +} + +int SCT_LIST_validate(const STACK_OF(SCT) *scts, CT_POLICY_EVAL_CTX *ctx) +{ + int are_scts_valid = 1; + int sct_count = scts != NULL ? sk_SCT_num(scts) : 0; + int i; + + for (i = 0; i < sct_count; ++i) { + int is_sct_valid = -1; + SCT *sct = sk_SCT_value(scts, i); + + if (sct == NULL) + continue; + + is_sct_valid = SCT_validate(sct, ctx); + if (is_sct_valid < 0) + return is_sct_valid; + are_scts_valid &= is_sct_valid; + } + + return are_scts_valid; +} diff --git a/crypto/ct/ct_vfy.c b/crypto/ct/ct_vfy.c index 57053120ad..236678363b 100644 --- a/crypto/ct/ct_vfy.c +++ b/crypto/ct/ct_vfy.c @@ -71,6 +71,65 @@ typedef enum sct_signature_type_t { SIGNATURE_TYPE_TREE_HASH } SCT_SIGNATURE_TYPE; +int CT_verify_no_bad_scts(const CT_POLICY_EVAL_CTX *ctx, + const STACK_OF(SCT) *scts, void *arg) +{ + int sct_count = scts != NULL ? sk_SCT_num(scts) : 0; + int i; + + for (i = 0; i < sct_count; ++i) { + SCT *sct = sk_SCT_value(scts, i); + + switch (SCT_get_validation_status(sct)) { + case SCT_VALIDATION_STATUS_INVALID: + return 0; + case SCT_VALIDATION_STATUS_NOT_SET: + CTerr(CT_F_CT_VERIFY_NO_BAD_SCTS, + CT_R_SCT_VALIDATION_STATUS_NOT_SET); + return -1; + default: + /* Ignore other validation statuses. */ + break; + } + } + + return 1; +} + +int CT_verify_at_least_one_good_sct(const CT_POLICY_EVAL_CTX *ctx, + const STACK_OF(SCT) *scts, void *arg) +{ + int sct_count = scts != NULL ? sk_SCT_num(scts) : 0; + int valid_scts = 0; + int i; + + for (i = 0; i < sct_count; ++i) { + SCT *sct = sk_SCT_value(scts, i); + + switch (SCT_get_validation_status(sct)) { + case SCT_VALIDATION_STATUS_VALID: + ++valid_scts; + break; + case SCT_VALIDATION_STATUS_INVALID: + return 0; + case SCT_VALIDATION_STATUS_NOT_SET: + CTerr(CT_F_CT_VERIFY_AT_LEAST_ONE_GOOD_SCT, + CT_R_SCT_VALIDATION_STATUS_NOT_SET); + return -1; + default: + /* Ignore other validation statuses. */ + break; + } + } + + if (valid_scts == 0) { + CTerr(CT_F_CT_VERIFY_AT_LEAST_ONE_GOOD_SCT, CT_R_NOT_ENOUGH_SCTS); + return 0; + } + + return 1; +} + /* * Update encoding for SCT signature verification/generation to supplied * EVP_MD_CTX. diff --git a/include/openssl/ct.h b/include/openssl/ct.h index de130c4678..7ea7ff236c 100644 --- a/include/openssl/ct.h +++ b/include/openssl/ct.h @@ -90,9 +90,62 @@ typedef enum { SCT_SOURCE_OCSP_STAPLED_RESPONSE } sct_source_t; +typedef enum { + SCT_VALIDATION_STATUS_NOT_SET, + SCT_VALIDATION_STATUS_UNKNOWN_LOG, + SCT_VALIDATION_STATUS_VALID, + SCT_VALIDATION_STATUS_INVALID, + SCT_VALIDATION_STATUS_UNVERIFIED, + SCT_VALIDATION_STATUS_UNKNOWN_VERSION +} sct_validation_status_t; + DEFINE_STACK_OF(SCT) DEFINE_STACK_OF(CTLOG) +/****************************************** + * CT policy evaluation context functions * + ******************************************/ + +/* Creates a new, empty policy evaluation context */ +CT_POLICY_EVAL_CTX *CT_POLICY_EVAL_CTX_new(void); + +/* Deletes a policy evaluation context */ +void CT_POLICY_EVAL_CTX_free(CT_POLICY_EVAL_CTX *ctx); + +/* Gets the peer certificate that the SCTs are for */ +X509* CT_POLICY_EVAL_CTX_get0_cert(CT_POLICY_EVAL_CTX *ctx); + +/* Sets the certificate associated with the received SCTs */ +void CT_POLICY_EVAL_CTX_set0_cert(CT_POLICY_EVAL_CTX *ctx, X509 *cert); + +/* Gets the issuer of the aforementioned certificate */ +X509* CT_POLICY_EVAL_CTX_get0_issuer(CT_POLICY_EVAL_CTX *ctx); + +/* Sets the issuer of the certificate associated with the received SCTs */ +void CT_POLICY_EVAL_CTX_set0_issuer(CT_POLICY_EVAL_CTX *ctx, X509 *issuer); + +/* Gets the CT logs that are trusted sources of SCTs */ +CTLOG_STORE *CT_POLICY_EVAL_CTX_get0_log_store(CT_POLICY_EVAL_CTX *ctx); + +/* Sets the log store that is in use */ +void CT_POLICY_EVAL_CTX_set0_log_store(CT_POLICY_EVAL_CTX *ctx, + CTLOG_STORE *log_store); + +/* + * A callback for verifying that the received SCTs are sufficient. + * Expected to return 1 if they are sufficient, otherwise 0. + * May return a negative integer if an error occurs. + * A connection should be aborted if the SCTs are deemed insufficient. + */ +typedef int(*ct_validation_cb)(const CT_POLICY_EVAL_CTX *ctx, + const STACK_OF(SCT) *scts, void *arg); +/* Returns 0 if there are invalid SCTs */ +int CT_verify_no_bad_scts(const CT_POLICY_EVAL_CTX *ctx, + const STACK_OF(SCT) *scts, void *arg); +/* Returns 0 if there are invalid SCTS or fewer than one valid SCT */ +int CT_verify_at_least_one_good_sct(const CT_POLICY_EVAL_CTX *ctx, + const STACK_OF(SCT) *scts, void *arg); + /***************** * SCT functions * *****************/ @@ -304,6 +357,31 @@ int SCT_verify(const SCT_CTX *sctx, const SCT *sct); int SCT_verify_v1(SCT *sct, X509 *cert, X509 *preissuer, X509_PUBKEY *log_pubkey, X509 *issuer_cert); +/* + * Gets the last result of validating this SCT. + * If it has not been validated yet, returns SCT_VALIDATION_STATUS_NOT_SET. + */ +sct_validation_status_t SCT_get_validation_status(const SCT *sct); + +/* + * Validates the given SCT with the provided context. + * Sets the "validation_status" field of the SCT. + * Returns 1 if the SCT is valid and the signature verifies. + * Returns 0 if the SCT is invalid or could not be verified. + * Returns -1 if an error occurs. + */ +int SCT_validate(SCT *sct, const CT_POLICY_EVAL_CTX *ctx); + +/* + * Validates the given list of SCTs with the provided context. + * Populates the "good_scts" and "bad_scts" of the evaluation context. + * Returns 1 if there are no invalid SCTs and all signatures verify. + * Returns 0 if at least one SCT is invalid or could not be verified. + * Returns a negative integer if an error occurs. + */ +int SCT_LIST_validate(const STACK_OF(SCT) *scts, CT_POLICY_EVAL_CTX *ctx); + + /********************************* * SCT parsing and serialisation * *********************************/ @@ -494,7 +572,16 @@ void ERR_load_CT_strings(void); # define CT_F_CTLOG_STORE_LOAD_CTX_NEW 122 # define CT_F_CTLOG_STORE_LOAD_FILE 123 # define CT_F_CT_BASE64_DECODE 124 +# define CT_F_CT_POLICY_EVAL_CTX_GET0_CERT 130 +# define CT_F_CT_POLICY_EVAL_CTX_GET0_ISSUER 131 +# define CT_F_CT_POLICY_EVAL_CTX_GET0_LOG_STORE 132 +# define CT_F_CT_POLICY_EVAL_CTX_NEW 133 +# define CT_F_CT_POLICY_EVAL_CTX_SET0_CERT 134 +# define CT_F_CT_POLICY_EVAL_CTX_SET0_ISSUER 135 +# define CT_F_CT_POLICY_EVAL_CTX_SET0_LOG_STORE 136 # define CT_F_CT_V1_LOG_ID_FROM_PKEY 125 +# define CT_F_CT_VERIFY_AT_LEAST_ONE_GOOD_SCT 137 +# define CT_F_CT_VERIFY_NO_BAD_SCTS 138 # define CT_F_D2I_SCT_LIST 105 # define CT_F_I2D_SCT_LIST 106 # define CT_F_I2O_SCT 107 @@ -504,6 +591,7 @@ void ERR_load_CT_strings(void); # define CT_F_O2I_SCT_LIST 111 # define CT_F_O2I_SCT_SIGNATURE 112 # define CT_F_SCT_CTX_NEW 126 +# define CT_F_SCT_LIST_VALIDATE 139 # define CT_F_SCT_NEW 100 # define CT_F_SCT_NEW_FROM_BASE64 127 # define CT_F_SCT_SET0_LOG_ID 101 @@ -514,6 +602,7 @@ void ERR_load_CT_strings(void); # define CT_F_SCT_SET_SIGNATURE_NID 103 # define CT_F_SCT_SET_VERSION 104 # define CT_F_SCT_SIGNATURE_IS_VALID 113 +# define CT_F_SCT_VALIDATE 140 # define CT_F_SCT_VERIFY 128 # define CT_F_SCT_VERIFY_V1 129 @@ -525,12 +614,14 @@ void ERR_load_CT_strings(void); # define CT_R_LOG_CONF_MISSING_DESCRIPTION 111 # define CT_R_LOG_CONF_MISSING_KEY 112 # define CT_R_LOG_KEY_INVALID 113 +# define CT_R_NOT_ENOUGH_SCTS 116 # define CT_R_SCT_INVALID 104 # define CT_R_SCT_INVALID_SIGNATURE 107 # define CT_R_SCT_LIST_INVALID 105 # define CT_R_SCT_LOG_ID_MISMATCH 114 # define CT_R_SCT_NOT_SET 106 # define CT_R_SCT_UNSUPPORTED_VERSION 115 +# define CT_R_SCT_VALIDATION_STATUS_NOT_SET 117 # define CT_R_UNRECOGNIZED_SIGNATURE_NID 101 # define CT_R_UNSUPPORTED_ENTRY_TYPE 102 # define CT_R_UNSUPPORTED_VERSION 103 diff --git a/include/openssl/ossl_typ.h b/include/openssl/ossl_typ.h index 013296618c..7a8d319989 100644 --- a/include/openssl/ossl_typ.h +++ b/include/openssl/ossl_typ.h @@ -204,6 +204,7 @@ typedef struct sct_st SCT; typedef struct sct_ctx_st SCT_CTX; typedef struct ctlog_st CTLOG; typedef struct ctlog_store_st CTLOG_STORE; +typedef struct ct_policy_eval_ctx_st CT_POLICY_EVAL_CTX; #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L && \ defined(INTMAX_MAX) && defined(UINTMAX_MAX) diff --git a/test/Makefile.in b/test/Makefile.in index 2fb1f49985..7a5dcc1c3c 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -373,7 +373,7 @@ $(ASYNCTEST)$(EXE_EXT): $(ASYNCTEST).o $(DTLSV1LISTENTEST)$(EXE_EXT): $(DTLSV1LISTENTEST).o @target=$(DTLSV1LISTENTEST) $(BUILD_CMD) -$(CTTEST)$(EXE_EXT): $(CTTEST).o testutil.o +$(CTTEST)$(EXE_EXT): $(CTTEST).o $(DLIBCRYPTO) testutil.o @target=$(CTTEST) testutil=testutil.o; $(BUILD_CMD) $(THREADSTEST)$(EXE_EXT): $(THREADSTEST).o $(DLIBCRYPTO) diff --git a/test/certs/embeddedSCTs1_issuer.pem b/test/certs/embeddedSCTs1_issuer.pem new file mode 100644 index 0000000000..1fa449d5a0 --- /dev/null +++ b/test/certs/embeddedSCTs1_issuer.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk +MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX +YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw +MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu +c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf +MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVimhTYhCicRmTbneDIRgcKkATxtB7 +jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP +KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL +svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk +tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG +A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO +MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB +/zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt +OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy +f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP +OwqULg== +-----END CERTIFICATE----- diff --git a/test/certs/embeddedSCTs3_issuer.pem b/test/certs/embeddedSCTs3_issuer.pem new file mode 100644 index 0000000000..f4bc31274b --- /dev/null +++ b/test/certs/embeddedSCTs3_issuer.pem @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIGDjCCA/agAwIBAgIQBqdDgNTr/tQ1taP34Wq92DANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTIwMjEy +MDAwMDAwWhcNMjcwMjExMjM1OTU5WjCBkjELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxODA2BgNVBAMTL0NPTU9ETyBSU0EgRXh0ZW5kZWQg +VmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAlVbeVLTf1QJJe9FbXKKyHo+cK2JMK40SKPMalaPGEP0p3uGf +CzhAk9HvbpUQ/OGQF3cs7nU+e2PsYZJuTzurgElr3wDqAwB/L3XVKC/sVmePgIOj +vdwDmZOLlJFWW6G4ajo/Br0OksxgnP214J9mMF/b5pTwlWqvyIqvgNnmiDkBfBzA +xSr3e5Wg8narbZtyOTDr0VdVAZ1YEZ18bYSPSeidCfw8/QpKdhQhXBZzQCMZdMO6 +WAqmli7eNuWf0MLw4eDBYuPCGEUZUaoXHugjddTI0JYT/8ck0YwLJ66eetw6YWNg +iJctXQUL5Tvrrs46R3N2qPos3cCHF+msMJn4HwIDAQABo4IBaTCCAWUwHwYDVR0j +BBgwFoAUu69+Aj36pvE8hI6t7jiY7NkyMtQwHQYDVR0OBBYEFDna/8ooFIqodBMI +ueQOqdL6fp1pMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMD4G +A1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5j +b21vZG8uY29tL0NQUzBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggr +BgEFBQcBAQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29t +L0NPTU9ET1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz +cC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAERCnUFRK0iIXZebeV4R +AUpSGXtBLMeJPNBy3IX6WK/VJeQT+FhlZ58N/1eLqYVeyqZLsKeyLeCMIs37/3mk +jCuN/gI9JN6pXV/kD0fQ22YlPodHDK4ixVAihNftSlka9pOlk7DgG4HyVsTIEFPk +1Hax0VtpS3ey4E/EhOfUoFDuPPpE/NBXueEoU/1Tzdy5H3pAvTA/2GzS8+cHnx8i +teoiccsq8FZ8/qyo0QYPFBRSTP5kKwxpKrgNUG4+BAe/eiCL+O5lCeHHSQgyPQ0o +fkkdt0rvAucNgBfIXOBhYsvss2B5JdoaZXOcOBCgJjqwyBZ9kzEi7nQLiMBciUEA +KKlHMd99SUWa9eanRRrSjhMQ34Ovmw2tfn6dNVA0BM7pINae253UqNpktNEvWS5e +ojZh1CSggjMziqHRbO9haKPl0latxf1eYusVqHQSTC8xjOnB3xBLAer2VBvNfzu9 +XJ/B288ByvK6YBIhMe2pZLiySVgXbVrXzYxtvp5/4gJYp9vDLVj2dAZqmvZh+fYA +tmnYOosxWd2R5nwnI4fdAw+PKowegwFOAWEMUnNt/AiiuSpm5HZNMaBWm9lTjaK2 +jwLI5jqmBNFI+8NKAnb9L9K8E7bobTQk+p0pisehKxTxlgBzuRPpwLk6R1YCcYAn +pLwltum95OmYdBbxN4SBB7SC +-----END CERTIFICATE----- diff --git a/test/ct_test.c b/test/ct_test.c index ac739ff755..99517a6d9f 100644 --- a/test/ct_test.c +++ b/test/ct_test.c @@ -1,8 +1,7 @@ /* * Tests the Certificate Transparency public and internal APIs. * - * Author: Rob Percival (robpercival@google.com) - * Date: 2016-01-26 + * Author: Rob Percival (robpercival@google.com) * * ==================================================================== * Copyright (c) 2016 The OpenSSL Project. All rights reserved. @@ -72,9 +71,12 @@ typedef struct ct_test_fixture { const char *test_case_name; + /* The CT log store to use during tests */ + CTLOG_STORE* ctlog_store; /* Set the following to test handling of SCTs in X509 certificates */ const char *certificate_file_path; - size_t expected_sct_count; + const char *issuer_file_path; + int expected_sct_count; /* Set the following to test handling of SCTs in TLS format */ const uint8_t *tls_sct; size_t tls_sct_len; @@ -85,6 +87,8 @@ typedef struct ct_test_fixture { * A maximum of |CT_TEST_MAX_FILE_SIZE| bytes will be read of this file. */ const char *sct_text_file_path; + /* Whether to test the validity of the SCT(s) */ + int test_validity; } CT_TEST_FIXTURE; @@ -92,10 +96,25 @@ static CT_TEST_FIXTURE set_up(const char *const test_case_name) { CT_TEST_FIXTURE fixture; int setup_ok = 1; + CTLOG_STORE *ctlog_store = CTLOG_STORE_new(); + + if (ctlog_store == NULL) { + setup_ok = 0; + fprintf(stderr, "Failed to create a new CT log store\n"); + goto end; + } + + if (CTLOG_STORE_load_default_file(ctlog_store) != 1) { + setup_ok = 0; + fprintf(stderr, "Failed to load CT log list\n"); + goto end; + } memset(&fixture, 0, sizeof(fixture)); fixture.test_case_name = test_case_name; + fixture.ctlog_store = ctlog_store; +end: if (!setup_ok) { exit(EXIT_FAILURE); } @@ -104,6 +123,7 @@ static CT_TEST_FIXTURE set_up(const char *const test_case_name) static void tear_down(CT_TEST_FIXTURE fixture) { + CTLOG_STORE_free(fixture.ctlog_store); ERR_print_errors_fp(stderr); } @@ -211,11 +231,13 @@ end: static int execute_cert_test(CT_TEST_FIXTURE fixture) { - int result = 0; - X509 *cert = NULL; + int test_failed = 0; + X509 *cert = NULL, *issuer = NULL; + STACK_OF(SCT) *scts = NULL; SCT *sct = NULL; char expected_sct_text[CT_TEST_MAX_FILE_SIZE]; int sct_text_len = 0; + CT_POLICY_EVAL_CTX *ct_policy_ctx = CT_POLICY_EVAL_CTX_new(); if (fixture.sct_text_file_path != NULL) { sct_text_len = read_text_file( @@ -224,7 +246,7 @@ static int execute_cert_test(CT_TEST_FIXTURE fixture) CT_TEST_MAX_FILE_SIZE - 1); if (sct_text_len < 0) { - result = 1; + test_failed = 1; fprintf(stderr, "Test data file not found: %s\n", fixture.sct_text_file_path); goto end; @@ -233,38 +255,106 @@ static int execute_cert_test(CT_TEST_FIXTURE fixture) expected_sct_text[sct_text_len] = '\0'; } + CT_POLICY_EVAL_CTX_set0_log_store(ct_policy_ctx, fixture.ctlog_store); + if (fixture.certificate_file_path != NULL) { int sct_extension_index; X509_EXTENSION *sct_extension = NULL; cert = load_pem_cert(fixture.certificate_file_path); if (cert == NULL) { - result = 1; + test_failed = 1; fprintf(stderr, "Unable to load certificate: %s\n", fixture.certificate_file_path); goto end; } - sct_extension_index = X509_get_ext_by_NID(cert, NID_ct_precert_scts, -1); + CT_POLICY_EVAL_CTX_set0_cert(ct_policy_ctx, cert); + + if (fixture.issuer_file_path != NULL) { + issuer = load_pem_cert(fixture.issuer_file_path); + + if (issuer == NULL) { + test_failed = 1; + fprintf(stderr, "Unable to load issuer certificate: %s\n", + fixture.issuer_file_path); + goto end; + } + + CT_POLICY_EVAL_CTX_set0_issuer(ct_policy_ctx, issuer); + } + + sct_extension_index = + X509_get_ext_by_NID(cert, NID_ct_precert_scts, -1); sct_extension = X509_get_ext(cert, sct_extension_index); if (fixture.expected_sct_count > 0) { if (sct_extension == NULL) { - result = 1; + test_failed = 1; fprintf(stderr, "SCT extension not found in: %s\n", fixture.certificate_file_path); goto end; } if (fixture.sct_text_file_path) { - result = compare_extension_printout(sct_extension, + test_failed = compare_extension_printout(sct_extension, expected_sct_text); - if (result != 0) + if (test_failed != 0) + goto end; + } + + if (fixture.test_validity) { + int are_scts_validated = 0; + scts = X509V3_EXT_d2i(sct_extension); + SCT_LIST_set_source(scts, SCT_SOURCE_X509V3_EXTENSION); + + are_scts_validated = SCT_LIST_validate(scts, ct_policy_ctx); + if (are_scts_validated < 0) { + fprintf(stderr, "Error verifying SCTs\n"); + test_failed = 1; + } else if (!are_scts_validated) { + int invalid_sct_count = 0; + int valid_sct_count = 0; + int i; + + for (i = 0; i < sk_SCT_num(scts); ++i) { + SCT *sct_i = sk_SCT_value(scts, i); + switch (SCT_get_validation_status(sct_i)) { + case SCT_VALIDATION_STATUS_VALID: + ++valid_sct_count; + break; + case SCT_VALIDATION_STATUS_INVALID: + ++invalid_sct_count; + break; + default: + /* Ignore other validation statuses. */ + break; + } + } + + if (valid_sct_count != fixture.expected_sct_count) { + int unverified_sct_count = sk_SCT_num(scts) - + invalid_sct_count - valid_sct_count; + + fprintf(stderr, + "%d SCTs failed verification\n" + "%d SCTs passed verification (%d expected)\n" + "%d SCTs were unverified\n", + invalid_sct_count, + valid_sct_count, + fixture.expected_sct_count, + unverified_sct_count); + } + test_failed = 1; + } + + if (test_failed != 0) goto end; } } else if (sct_extension != NULL) { - result = 1; - fprintf(stderr, "Expected no SCTs, but found SCT extension in: %s\n", - fixture.certificate_file_path); + test_failed = 1; + fprintf(stderr, + "Expected no SCTs, but found SCT extension in: %s\n", + fixture.certificate_file_path); goto end; } } @@ -274,30 +364,46 @@ static int execute_cert_test(CT_TEST_FIXTURE fixture) unsigned char *tls_sct; size_t tls_sct_len; if (o2i_SCT(&sct, &p, fixture.tls_sct_len) == NULL) { - result = 1; + test_failed = 1; fprintf(stderr, "Failed to decode SCT from TLS format\n"); goto end; } if (fixture.sct_text_file_path) { - result = compare_sct_printout(sct, expected_sct_text); - if (result != 0) + test_failed = compare_sct_printout(sct, expected_sct_text); + if (test_failed != 0) goto end; } tls_sct_len = i2o_SCT(sct, &tls_sct); if (tls_sct_len != fixture.tls_sct_len || memcmp(fixture.tls_sct, tls_sct, tls_sct_len) != 0) { - result = 1; + test_failed = 1; fprintf(stderr, "Failed to encode SCT into TLS format correctly\n"); goto end; } + + if (fixture.test_validity && cert != NULL) { + int is_sct_validated = SCT_validate(sct, ct_policy_ctx); + if (is_sct_validated < 0) { + test_failed = 1; + fprintf(stderr, "Error validating SCT\n"); + goto end; + } else if (!is_sct_validated) { + test_failed = 1; + fprintf(stderr, "SCT failed verification\n"); + goto end; + } + } } end: X509_free(cert); + X509_free(issuer); + SCT_LIST_free(scts); SCT_free(sct); - return result; + CT_POLICY_EVAL_CTX_free(ct_policy_ctx); + return test_failed; } #define SETUP_CT_TEST_FIXTURE() SETUP_TEST_FIXTURE(CT_TEST_FIXTURE, set_up) @@ -307,6 +413,7 @@ static int test_no_scts_in_certificate() { SETUP_CT_TEST_FIXTURE(); fixture.certificate_file_path = "certs/leaf.pem"; + fixture.issuer_file_path = "certs/subinterCA.pem"; fixture.expected_sct_count = 0; EXECUTE_CT_TEST(); } @@ -315,6 +422,7 @@ static int test_one_sct_in_certificate() { SETUP_CT_TEST_FIXTURE(); fixture.certificate_file_path = "certs/embeddedSCTs1.pem"; + fixture.issuer_file_path = "certs/embeddedSCTs1_issuer.pem"; fixture.expected_sct_count = 1; fixture.sct_text_file_path = "certs/embeddedSCTs1.sct"; EXECUTE_CT_TEST(); @@ -324,11 +432,32 @@ static int test_multiple_scts_in_certificate() { SETUP_CT_TEST_FIXTURE(); fixture.certificate_file_path = "certs/embeddedSCTs3.pem"; + fixture.issuer_file_path = "certs/embeddedSCTs3_issuer.pem"; fixture.expected_sct_count = 3; fixture.sct_text_file_path = "certs/embeddedSCTs3.sct"; EXECUTE_CT_TEST(); } +static int test_verify_one_sct() +{ + SETUP_CT_TEST_FIXTURE(); + fixture.certificate_file_path = "certs/embeddedSCTs1.pem"; + fixture.issuer_file_path = "certs/embeddedSCTs1_issuer.pem"; + fixture.expected_sct_count = 1; + fixture.test_validity = 1; + EXECUTE_CT_TEST(); +} + +static int test_verify_multiple_scts() +{ + SETUP_CT_TEST_FIXTURE(); + fixture.certificate_file_path = "certs/embeddedSCTs3.pem"; + fixture.issuer_file_path = "certs/embeddedSCTs3_issuer.pem"; + fixture.expected_sct_count = 3; + fixture.test_validity = 1; + EXECUTE_CT_TEST(); +} + static int test_decode_tls_sct() { SETUP_CT_TEST_FIXTURE(); @@ -384,6 +513,8 @@ int main(int argc, char *argv[]) ADD_TEST(test_no_scts_in_certificate); ADD_TEST(test_one_sct_in_certificate); ADD_TEST(test_multiple_scts_in_certificate); + ADD_TEST(test_verify_one_sct); + ADD_TEST(test_verify_multiple_scts); ADD_TEST(test_decode_tls_sct); ADD_TEST(test_encode_tls_sct); diff --git a/test/recipes/80-test_ct.t b/test/recipes/80-test_ct.t index 616bb17603..54c3e228c2 100644 --- a/test/recipes/80-test_ct.t +++ b/test/recipes/80-test_ct.t @@ -1,6 +1,9 @@ #! /usr/bin/perl +use OpenSSL::Test qw/:DEFAULT srctop_file/; use OpenSSL::Test::Simple; -simple_test("test_ct", "ct_test"); +setup("test_ct"); +$ENV{CTLOG_FILE} = srctop_file("test", "ct", "log_list.conf"); +simple_test("test_ct", "ct_test", "ct"); diff --git a/util/libeay.num b/util/libeay.num index 2484fd2fc1..d49fd74bc0 100755 --- a/util/libeay.num +++ b/util/libeay.num @@ -4788,3 +4788,16 @@ SCT_LIST_set0_logs 5291 1_1_0 EXIST::FUNCTION: CTLOG_STORE_get0_log_by_id 5292 1_1_0 EXIST::FUNCTION: CTLOG_STORE_load_default_file 5293 1_1_0 EXIST::FUNCTION: CTLOG_new 5294 1_1_0 EXIST::FUNCTION: +SCT_LIST_validate 5295 1_1_0 EXIST::FUNCTION: +CT_verify_at_least_one_good_sct 5296 1_1_0 EXIST::FUNCTION: +CT_POLICY_EVAL_CTX_set0_issuer 5297 1_1_0 EXIST::FUNCTION: +SCT_get_validation_status 5298 1_1_0 EXIST::FUNCTION: +CT_POLICY_EVAL_CTX_set0_log_store 5299 1_1_0 EXIST::FUNCTION: +SCT_validate 5300 1_1_0 EXIST::FUNCTION: +CT_POLICY_EVAL_CTX_new 5301 1_1_0 EXIST::FUNCTION: +CT_POLICY_EVAL_CTX_get0_cert 5302 1_1_0 EXIST::FUNCTION: +CT_POLICY_EVAL_CTX_get0_issuer 5303 1_1_0 EXIST::FUNCTION: +CT_POLICY_EVAL_CTX_set0_cert 5304 1_1_0 EXIST::FUNCTION: +CT_POLICY_EVAL_CTX_get0_log_store 5305 1_1_0 EXIST::FUNCTION: +CT_POLICY_EVAL_CTX_free 5306 1_1_0 EXIST::FUNCTION: +CT_verify_no_bad_scts 5307 1_1_0 EXIST::FUNCTION: