diff --git a/apps/s_client.c b/apps/s_client.c index b180dbc1cb..b2f10c82fc 100644 --- a/apps/s_client.c +++ b/apps/s_client.c @@ -666,7 +666,7 @@ typedef enum OPTION_choice { OPT_S_ENUM, OPT_FALLBACKSCSV, OPT_NOCMDS, OPT_PROXY, OPT_DANE_TLSA_DOMAIN, #ifndef OPENSSL_NO_CT - OPT_NOCT, OPT_REQUESTCT, OPT_REQUIRECT, OPT_CTLOG_FILE, + OPT_CT, OPT_NOCT, OPT_CTLOG_FILE, #endif OPT_DANE_TLSA_RRDATA } OPTION_CHOICE; @@ -831,9 +831,8 @@ OPTIONS s_client_options[] = { "Specify engine to be used for client certificate operations"}, #endif #ifndef OPENSSL_NO_CT + {"ct", OPT_CT, '-', "Request and parse SCTs (also enables OCSP stapling)"}, {"noct", OPT_NOCT, '-', "Do not request or parse SCTs (default)"}, - {"requestct", OPT_REQUESTCT, '-', "Request SCTs (enables OCSP stapling)"}, - {"requirect", OPT_REQUIRECT, '-', "Require at least 1 SCT (enables OCSP stapling)"}, {"ctlogfile", OPT_CTLOG_FILE, '<', "CT log list CONF file"}, #endif {NULL} @@ -935,7 +934,7 @@ int s_client_main(int argc, char **argv) #endif #ifndef OPENSSL_NO_CT char *ctlog_file = NULL; - ct_validation_cb ct_validation = NULL; + int ct_validation = 0; #endif int min_version = 0, max_version = 0; @@ -1335,13 +1334,10 @@ int s_client_main(int argc, char **argv) break; #ifndef OPENSSL_NO_CT case OPT_NOCT: - ct_validation = NULL; + ct_validation = 0; break; - case OPT_REQUESTCT: - ct_validation = CT_verify_no_bad_scts; - break; - case OPT_REQUIRECT: - ct_validation = CT_verify_at_least_one_good_sct; + case OPT_CT: + ct_validation = 1; break; case OPT_CTLOG_FILE: ctlog_file = opt_arg(); @@ -1684,13 +1680,15 @@ int s_client_main(int argc, char **argv) SSL_CTX_set_info_callback(ctx, apps_ssl_info_callback); #ifndef OPENSSL_NO_CT - if (!SSL_CTX_set_ct_validation_callback(ctx, ct_validation, NULL)) { + /* Enable SCT processing, without early connection termination */ + if (ct_validation && + !SSL_CTX_enable_ct(ctx, SSL_CT_VALIDATION_PERMISSIVE)) { ERR_print_errors(bio_err); goto end; } if (!ctx_set_ctlog_list_file(ctx, ctlog_file)) { - if (ct_validation != NULL) { + if (ct_validation) { ERR_print_errors(bio_err); goto end; } @@ -2570,7 +2568,6 @@ static void print_stuff(BIO *bio, SSL *s, int full) #endif unsigned char *exportedkeymat; #ifndef OPENSSL_NO_CT - const STACK_OF(SCT) *scts; const SSL_CTX *ctx = SSL_get_SSL_CTX(s); #endif @@ -2626,21 +2623,35 @@ static void print_stuff(BIO *bio, SSL *s, int full) ssl_print_tmp_key(bio, s); #ifndef OPENSSL_NO_CT - scts = SSL_get0_peer_scts(s); - BIO_printf(bio, "---\nSCTs present (%i)\n", - scts != NULL ? sk_SCT_num(scts) : 0); + /* + * When the SSL session is anonymous, or resumed via an abbreviated + * handshake, no SCTs are provided as part of the handshake. While in + * a resumed session SCTs may be present in the session's certificate, + * no callbacks are invoked to revalidate these, and in any case that + * set of SCTs may be incomplete. Thus it makes little sense to + * attempt to display SCTs from a resumed session's certificate, and of + * course none are associated with an anonymous peer. + */ + if (peer != NULL && !SSL_session_reused(s) && SSL_ct_is_enabled(s)) { + const STACK_OF(SCT) *scts = SSL_get0_peer_scts(s); + int sct_count = scts != NULL ? sk_SCT_num(scts) : 0; - if (SSL_get_ct_validation_callback(s) == NULL) { - BIO_printf(bio, "Warning: CT validation is disabled, so not all " - "SCTs may be displayed. Re-run with \"-requestct\".\n"); - } + BIO_printf(bio, "---\nSCTs present (%i)\n", sct_count); + if (sct_count > 0) { + const CTLOG_STORE *log_store = SSL_CTX_get0_ctlog_store(ctx); - if (scts != NULL && sk_SCT_num(scts) > 0) { - const CTLOG_STORE *log_store = SSL_CTX_get0_ctlog_store(ctx); + BIO_printf(bio, "---\n"); + for (i = 0; i < sct_count; ++i) { + SCT *sct = sk_SCT_value(scts, i); - BIO_printf(bio, "---\n"); - SCT_LIST_print(scts, bio, 0, "\n---\n", log_store); - BIO_printf(bio, "\n"); + BIO_printf(bio, "SCT validation status: %s\n", + SCT_validation_status_string(sct)); + SCT_print(sct, bio, 0, log_store); + if (i < sct_count - 1) + BIO_printf(bio, "\n---\n"); + } + BIO_printf(bio, "\n"); + } } #endif diff --git a/crypto/ct/ct_oct.c b/crypto/ct/ct_oct.c index 620edab038..ece353bdac 100644 --- a/crypto/ct/ct_oct.c +++ b/crypto/ct/ct_oct.c @@ -135,10 +135,14 @@ SCT *o2i_SCT(SCT **psct, const unsigned char **in, size_t len) if (sct->version == SCT_VERSION_V1) { int sig_len; size_t len2; - /* - * Fixed-length header: struct { (1 byte) Version sct_version; (32 - * bytes) log_id id; (8 bytes) uint64 timestamp; (2 bytes + ?) - * CtExtensions extensions; + /*- + * Fixed-length header: + * struct { + * Version sct_version; (1 byte) + * log_id id; (32 bytes) + * uint64 timestamp; (8 bytes) + * CtExtensions extensions; (2 bytes + ?) + * } */ if (len < 43) { CTerr(CT_F_O2I_SCT, CT_R_SCT_INVALID); diff --git a/crypto/ct/ct_prn.c b/crypto/ct/ct_prn.c index 0d9d0197d5..5004ae0b94 100644 --- a/crypto/ct/ct_prn.c +++ b/crypto/ct/ct_prn.c @@ -96,6 +96,26 @@ static void timestamp_print(uint64_t timestamp, BIO *out) ASN1_GENERALIZEDTIME_free(gen); } +const char *SCT_validation_status_string(const SCT *sct) +{ + + switch (SCT_get_validation_status(sct)) { + case SCT_VALIDATION_STATUS_NOT_SET: + return "not set"; + case SCT_VALIDATION_STATUS_UNKNOWN_VERSION: + return "unknown version"; + case SCT_VALIDATION_STATUS_UNKNOWN_LOG: + return "unknown log"; + case SCT_VALIDATION_STATUS_UNVERIFIED: + return "unverified"; + case SCT_VALIDATION_STATUS_INVALID: + return "invalid"; + case SCT_VALIDATION_STATUS_VALID: + return "valid"; + } + return "unknown status"; +} + void SCT_print(const SCT *sct, BIO *out, int indent, const CTLOG_STORE *log_store) { @@ -143,9 +163,10 @@ void SCT_print(const SCT *sct, BIO *out, int indent, void SCT_LIST_print(const STACK_OF(SCT) *sct_list, BIO *out, int indent, const char *separator, const CTLOG_STORE *log_store) { + int sct_count = sk_SCT_num(sct_list); int i; - for (i = 0; i < sk_SCT_num(sct_list); ++i) { + for (i = 0; i < sct_count; ++i) { SCT *sct = sk_SCT_value(sct_list, i); SCT_print(sct, out, indent, log_store); diff --git a/crypto/ct/ct_sct.c b/crypto/ct/ct_sct.c index 9eefa0caf0..1fc7456129 100644 --- a/crypto/ct/ct_sct.c +++ b/crypto/ct/ct_sct.c @@ -334,17 +334,22 @@ int SCT_validate(SCT *sct, const CT_POLICY_EVAL_CTX *ctx) X509_PUBKEY *pub = NULL, *log_pkey = NULL; const CTLOG *log; + /* + * With an unrecognized SCT version we don't know what such an SCT means, + * let alone validate one. So we return validation failure (0). + */ if (sct->version != SCT_VERSION_V1) { sct->validation_status = SCT_VALIDATION_STATUS_UNKNOWN_VERSION; - goto end; + return 0; } log = CTLOG_STORE_get0_log_by_id(ctx->log_store, sct->log_id, sct->log_id_len); + /* Similarly, an SCT from an unknown log also cannot be validated. */ if (log == NULL) { sct->validation_status = SCT_VALIDATION_STATUS_UNKNOWN_LOG; - goto end; + return 0; } sctx = SCT_CTX_new(); @@ -372,10 +377,28 @@ int SCT_validate(SCT *sct, const CT_POLICY_EVAL_CTX *ctx) goto err; } + /* + * XXX: Potential for optimization. This repeats some idempotent heavy + * lifting on the certificate for each candidate SCT, and appears to not + * use any information in the SCT itself, only the certificate is + * processed. So it may make more sense to to do this just once, perhaps + * associated with the shared (by all SCTs) policy eval ctx. + * + * XXX: Failure here is global (SCT independent) and represents either an + * issue with the certificate (e.g. duplicate extensions) or an out of + * memory condition. When the certificate is incompatible with CT, we just + * mark the SCTs invalid, rather than report a failure to determine the + * validation status. That way, callbacks that want to do "soft" SCT + * processing will not abort handshakes with false positive internal + * errors. Since the function does not distinguish between certificate + * issues (peer's fault) and internal problems (out fault) the safe thing + * to do is to report a validation failure and let the callback or + * application decide what to do. + */ if (SCT_CTX_set1_cert(sctx, ctx->cert, NULL) != 1) - goto err; - - sct->validation_status = SCT_verify(sctx, sct) == 1 ? + sct->validation_status = SCT_VALIDATION_STATUS_UNVERIFIED; + else + sct->validation_status = SCT_verify(sctx, sct) == 1 ? SCT_VALIDATION_STATUS_VALID : SCT_VALIDATION_STATUS_INVALID; end: diff --git a/crypto/ct/ct_vfy.c b/crypto/ct/ct_vfy.c index 9895231d1b..71c0361126 100644 --- a/crypto/ct/ct_vfy.c +++ b/crypto/ct/ct_vfy.c @@ -71,65 +71,6 @@ 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/doc/apps/s_client.pod b/doc/apps/s_client.pod index 881fbcfefe..e06af14ec9 100644 --- a/doc/apps/s_client.pod +++ b/doc/apps/s_client.pod @@ -95,7 +95,7 @@ B B [B<-serverinfo types>] [B<-status>] [B<-nextprotoneg protocols>] -[B<-noct|requestct|requirect>] +[B<-ct|noct>] [B<-ctlogfile>] =head1 DESCRIPTION @@ -464,14 +464,12 @@ Empty list of protocols is treated specially and will cause the client to advertise support for the TLS extension but disconnect just after receiving ServerHello with a list of server supported protocols. -=item B<-noct|requestct|requirect> +=item B<-ct|noct> -Use one of these three options to control whether Certificate Transparency (CT) -is disabled (-noct), enabled but not enforced (-requestct), or enabled and -enforced (-requirect). If CT is enabled, signed certificate timestamps (SCTs) -will be requested from the server and invalid SCTs will cause the connection to -be aborted. If CT is enforced, at least one valid SCT from a recognised CT log -(see B<-ctlogfile>) will be required or the connection will be aborted. +Use one of these two options to control whether Certificate Transparency (CT) +is enabled (B<-ct>) or disabled (B<-noct>). +If CT is enabled, signed certificate timestamps (SCTs) will be requested from +the server and reported at handshake completion. Enabling CT also enables OCSP stapling, as this is one possible delivery method for SCTs. diff --git a/doc/ssl/SSL_CTX_set_ct_validation_callback.pod b/doc/ssl/SSL_CTX_set_ct_validation_callback.pod index 167a044536..ec51c75eb4 100644 --- a/doc/ssl/SSL_CTX_set_ct_validation_callback.pod +++ b/doc/ssl/SSL_CTX_set_ct_validation_callback.pod @@ -2,39 +2,92 @@ =head1 NAME +SSL_ct_enable, SSL_CTX_ct_enable, SSL_ct_disable, SSL_CTX_ct_disable, SSL_set_ct_validation_callback, SSL_CTX_set_ct_validation_callback, -SSL_get_ct_validation_callback, SSL_CTX_get_ct_validation_callback - +SSL_ct_is_enabled, SSL_CTX_ct_is_enabled - control Certificate Transparency policy =head1 SYNOPSIS #include - int SSL_set_ct_validation_callback(SSL *s, ct_validation_cb callback, void *arg); - int SSL_CTX_set_ct_validation_callback(SSL_CTX *ctx, ct_validation_cb callback, void *arg); - ct_validation_cb SSL_get_ct_validation_callback(const SSL *s); - ct_validation_cb SSL_CTX_get_ct_validation_callback(const SSL_CTX *ctx); + int SSL_ct_enable(SSL *s, int validation_mode); + int SSL_CTX_ct_enable(SSL_CTX *ctx, int validation_mode); + int SSL_set_ct_validation_callback(SSL *s, ssl_ct_validation_cb callback, + void *arg); + int SSL_CTX_set_ct_validation_callback(SSL_CTX *ctx, + ssl_ct_validation_cb callback, + void *arg); + void SSL_ct_disable(SSL *s); + void SSL_CTX_ct_disable(SSL_CTX *ctx); + int SSL_ct_is_enabled(const SSL *s); + int SSL_CTX_ct_is_enabled(const SSL_CTX *ctx); =head1 DESCRIPTION -SSL_set_ct_validation_callback() and SSL_CTX_set_ct_validation_callback() set -the function that is called when Certificate Transparency validation needs to -occur. It is the responsibility of this function to examine the signed -certificate timestamps (SCTs) that are passed to it and determine whether they -are sufficient to allow the connection to continue. If they are, the function -must return 1, otherwise it must return 0. +SSL_ct_enable() and SSL_CTX_ct_enable() enable the processing of signed +certificate timestamps (SCTs) either for a given SSL connection or for all +connections that share the given SSL context, respectively. +This is accomplished by setting a built-in CT validation callback. +The behaviour of the callback is determined by the B argument, +which can be either of B or +B as described below. -An arbitrary piece of user data, B, can be passed in when setting the -callback. This will be passed to the callback whenever it is invoked. Ownership -of this userdata remains with the caller. +If B is equal to B, then the +handshake continues regardless of the validation status of any SCTs. +The application can inspect the validation status of the SCTs at handshake +completion. +Note that with session resumption there will not be any SCTs presented during +the handshake. +Therefore, in applications that delay SCT policy enforcement until after +handshake completion, SCT checks should only be performed when the session is +not reused. +See L. + +If B is equal to B, then in a full +TLS handshake with the verification mode set to B, if the peer +presents no valid SCTs the handshake will be aborted. +See L. + +SSL_set_ct_validation_callback() and SSL_CTX_set_ct_validation_callback() +register a custom callback that may implement a different policy than either of +the above. +This callback can examine the peer's SCTs and determine whether they are +sufficient to allow the connection to continue. +The TLS handshake is aborted if the verification mode is not B +and the callback returns a non-positive result. + +An arbitrary callback context argument, B, can be passed in when setting +the callback. +This will be passed to the callback whenever it is invoked. +Ownership of this context remains with the caller. If no callback is set, SCTs will not be requested and Certificate Transparency validation will not occur. +No callback will be invoked when the peer presents no certificate, e.g. by +employing an anonymous (aNULL) ciphersuite. +In that case the handshake continues as it would had no callback been +requested. +Callbacks are also not invoked when the peer certificate chain is invalid or +validated via DANE-TA(2) or DANE-EE(3) TLSA records which use a private X.509 +PKI, or no X.509 PKI at all, respectively. +Clients that require SCTs are expected to not have enabled any aNULL ciphers +nor to have specified server verification via DANE-TA(2) or DANE-EE(3) TLSA +records. + +SSL_ct_disable() and SSL_CTX_ct_disable() turn off CT processing, whether +enabled via the built-in or the custom callbacks, by setting a NULL callback. +These may be implemented as macros. + +SSL_ct_is_enabled() and SSL_CTX_ct_is_enabled() return 1 if CT processing is +enabled via either SSL_ct_enable() or a non-null custom callback, and 0 +otherwise. + =head1 NOTES -If a callback is set, OCSP stapling will be enabled. This is because one -possible source of SCTs is the OCSP response from a server. +When SCT processing is enabled, OCSP stapling will be enabled. This is because +one possible source of SCTs is the OCSP response from a server. =head1 RESTRICTIONS @@ -42,24 +95,26 @@ Certificate Transparency validation cannot be enabled and so a callback cannot be set if a custom client extension handler has been registered to handle SCT extensions (B). -If an SCT callback is enabled, a handshake may fail if the peer does -not provide a certificate, which can happen when using opportunistic -encryption with anonymous (B) cipher-suites enabled on both ends. -SCTs should only be used when the application requires an authenticated -connection, and wishes to perform additional validation on that identity. - =head1 RETURN VALUES -SSL_CTX_set_ct_validation_callback() and SSL_set_ct_validation_callback() -return 1 if the B is successfully set. They return 0 if an error -occurs, e.g. a custom client extension handler has been setup to handle SCTs. +SSL_ct_enable(), SSL_CTX_ct_enable(), SSL_CTX_set_ct_validation_callback() and +SSL_set_ct_validation_callback() return 1 if the B is successfully +set. +They return 0 if an error occurs, e.g. a custom client extension handler has +been setup to handle SCTs. -SSL_CTX_get_ct_validation_callback() and SSL_get_ct_validation_callback() -return the current callback, or NULL if no callback is set. +SSL_ct_disable() and SSL_CTX_ct_disable() do not return a result. + +SSL_CTX_ct_is_enabled() and SSL_ct_is_enabled() return a 1 if a non-null CT +validation callback is set, or 0 if no callback (or equivalently a NULL +callback) is set. =head1 SEE ALSO L, -L +L, +L, +L, +L =cut diff --git a/doc/ssl/SSL_CTX_set_ctlog_list_file.pod b/doc/ssl/SSL_CTX_set_ctlog_list_file.pod index 9ef15adb90..9e5798f04c 100644 --- a/doc/ssl/SSL_CTX_set_ctlog_list_file.pod +++ b/doc/ssl/SSL_CTX_set_ctlog_list_file.pod @@ -49,6 +49,6 @@ the case of an error, the log list may have been partially loaded. =head1 SEE ALSO L, -L +L =cut diff --git a/include/openssl/ct.h b/include/openssl/ct.h index 0da3125d17..9b0ce2f119 100644 --- a/include/openssl/ct.h +++ b/include/openssl/ct.h @@ -130,21 +130,6 @@ const CTLOG_STORE *CT_POLICY_EVAL_CTX_get0_log_store(const CT_POLICY_EVAL_CTX *c 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 * *****************/ @@ -298,6 +283,11 @@ sct_source_t SCT_get_source(const SCT *sct); */ __owur int SCT_set_source(SCT *sct, sct_source_t source); +/* + * Returns a text string describing the validation status of |sct|. + */ +const char *SCT_validation_status_string(const SCT *sct); + /* * Pretty-prints an |sct| to |out|. * It will be indented by the number of spaces specified by |indent|. diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index ea47cb3da3..0b103f495d 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -1898,6 +1898,15 @@ int DTLSv1_listen(SSL *s, BIO_ADDR *client); # ifndef OPENSSL_NO_CT +/* + * 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(*ssl_ct_validation_cb)(const CT_POLICY_EVAL_CTX *ctx, + const STACK_OF(SCT) *scts, void *arg); + /* * Sets a |callback| that is invoked upon receipt of ServerHelloDone to validate * the received SCTs. @@ -1910,18 +1919,42 @@ int DTLSv1_listen(SSL *s, BIO_ADDR *client); * NOTE: A side-effect of setting a CT callback is that an OCSP stapled response * will be requested. */ -__owur int SSL_set_ct_validation_callback(SSL *s, - ct_validation_cb callback, - void *arg); -__owur int SSL_CTX_set_ct_validation_callback(SSL_CTX *ctx, - ct_validation_cb callback, - void *arg); +int SSL_set_ct_validation_callback(SSL *s, ssl_ct_validation_cb callback, + void *arg); +int SSL_CTX_set_ct_validation_callback(SSL_CTX *ctx, + ssl_ct_validation_cb callback, + void *arg); +#define SSL_disable_ct(s) \ + ((void) SSL_set_validation_callback((s), NULL, NULL)) +#define SSL_CTX_disable_ct(ctx) \ + ((void) SSL_CTX_set_validation_callback((ctx), NULL, NULL)) + /* - * Gets the callback being used to validate SCTs. - * This will return NULL if SCTs are neither being requested nor validated. + * The validation type enumerates the available behaviours of the built-in SSL + * CT validation callback selected via SSL_enable_ct() and SSL_CTX_enable_ct(). + * The underlying callback is a static function in libssl. */ -__owur ct_validation_cb SSL_get_ct_validation_callback(const SSL *s); -__owur ct_validation_cb SSL_CTX_get_ct_validation_callback(const SSL_CTX *ctx); +enum { + SSL_CT_VALIDATION_PERMISSIVE = 0, + SSL_CT_VALIDATION_STRICT +}; + +/* + * Enable CT by setting up a callback that implements one of the built-in + * validation variants. The SSL_CT_VALIDATION_PERMISSIVE variant always + * continues the handshake, the application can make appropriate decisions at + * handshake completion. The SSL_CT_VALIDATION_STRICT variant requires at + * least one valid SCT, or else handshake termination will be requested. The + * handshake may continue anyway if SSL_VERIFY_NONE is in effect. + */ +int SSL_enable_ct(SSL *s, int validation_mode); +int SSL_CTX_enable_ct(SSL_CTX *ctx, int validation_mode); + +/* + * Report whether a non-NULL callback is enabled. + */ +int SSL_ct_is_enabled(const SSL *s); +int SSL_CTX_ct_is_enabled(const SSL_CTX *ctx); /* Gets the SCTs received from a connection */ const STACK_OF(SCT) *SSL_get0_peer_scts(SSL *s); diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 6875f384b1..5a6e6a7060 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -4039,10 +4039,32 @@ err: return NULL; } -int SSL_set_ct_validation_callback(SSL *s, ct_validation_cb callback, void *arg) +static int ct_permissive(const CT_POLICY_EVAL_CTX *ctx, + const STACK_OF(SCT) *scts, void *unused_arg) { - int ret = 0; + return 1; +} +static int ct_strict(const CT_POLICY_EVAL_CTX *ctx, + const STACK_OF(SCT) *scts, void *unused_arg) +{ + int count = scts != NULL ? sk_SCT_num(scts) : 0; + int i; + + for (i = 0; i < count; ++i) { + SCT *sct = sk_SCT_value(scts, i); + int status = SCT_get_validation_status(sct); + + if (status == SCT_VALIDATION_STATUS_VALID) + return 1; + } + SSLerr(SSL_F_CT_STRICT, SSL_R_NO_VALID_SCTS); + return 0; +} + +int SSL_set_ct_validation_callback(SSL *s, ssl_ct_validation_cb callback, + void *arg) +{ /* * 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. @@ -4051,28 +4073,25 @@ int SSL_set_ct_validation_callback(SSL *s, ct_validation_cb callback, void *arg) TLSEXT_TYPE_signed_certificate_timestamp)) { SSLerr(SSL_F_SSL_SET_CT_VALIDATION_CALLBACK, SSL_R_CUSTOM_EXT_HANDLER_ALREADY_INSTALLED); - goto err; + return 0; + } + + 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)) + return 0; } 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; + return 1; } -int SSL_CTX_set_ct_validation_callback(SSL_CTX *ctx, ct_validation_cb callback, +int SSL_CTX_set_ct_validation_callback(SSL_CTX *ctx, + ssl_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. @@ -4081,59 +4100,90 @@ int SSL_CTX_set_ct_validation_callback(SSL_CTX *ctx, ct_validation_cb callback, TLSEXT_TYPE_signed_certificate_timestamp)) { SSLerr(SSL_F_SSL_CTX_SET_CT_VALIDATION_CALLBACK, SSL_R_CUSTOM_EXT_HANDLER_ALREADY_INSTALLED); - goto err; + return 0; } ctx->ct_validation_callback = callback; ctx->ct_validation_callback_arg = arg; - ret = 1; -err: - return ret; + return 1; } -ct_validation_cb SSL_get_ct_validation_callback(const SSL *s) +int SSL_ct_is_enabled(const SSL *s) { - return s->ct_validation_callback; + return s->ct_validation_callback != NULL; } -ct_validation_cb SSL_CTX_get_ct_validation_callback(const SSL_CTX *ctx) +int SSL_CTX_ct_is_enabled(const SSL_CTX *ctx) { - return ctx->ct_validation_callback; + return ctx->ct_validation_callback != NULL; } int ssl_validate_ct(SSL *s) { int ret = 0; X509 *cert = s->session != NULL ? s->session->peer : NULL; - X509 *issuer = NULL; + X509 *issuer; + struct dane_st *dane = &s->dane; 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) + /* + * If no callback is set, the peer is anonymous, or its chain is invalid, + * skip SCT validation - just return success. Applications that continue + * handshakes without certificates, with unverified chains, or pinned leaf + * certificates are outside the scope of the WebPKI and CT. + * + * The above exclusions notwithstanding the vast majority of peers will + * have rather ordinary certificate chains validated by typical + * applications that perform certificate verification and therefore will + * process SCTs when enabled. + */ + if (s->ct_validation_callback == NULL || cert == NULL || + s->verify_result != X509_V_OK || + s->verified_chain == NULL || + sk_X509_num(s->verified_chain) <= 1) return 1; - if (cert == NULL) { - SSLerr(SSL_F_SSL_VALIDATE_CT, SSL_R_NO_CERTIFICATE_ASSIGNED); - goto end; + /* + * CT not applicable for chains validated via DANE-TA(2) or DANE-EE(3) + * trust-anchors. See https://tools.ietf.org/html/rfc7671#section-4.2 + */ + if (DANETLS_ENABLED(dane) && dane->mtlsa != NULL) { + switch (dane->mtlsa->usage) { + case DANETLS_USAGE_DANE_TA: + case DANETLS_USAGE_DANE_EE: + return 1; + } } - 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; } + issuer = sk_X509_value(s->verified_chain, 1); 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) { + /* + * This function returns success (> 0) only when all the SCTs are valid, 0 + * when some are invalid, and < 0 on various internal errors (out of + * memory, etc.). Having some, or even all, invalid SCTs is not sufficient + * reason to abort the handshake, that decision is up to the callback. + * Therefore, we error out only in the unexpected case that the return + * value is negative. + * + * XXX: One might well argue that the return value of this function is an + * unforunate design choice. Its job is only to determine the validation + * status of each of the provided SCTs. So long as it correctly separates + * the wheat from the chaff it should return success. Failure in this case + * ought to correspond to an inability to carry out its duties. + */ + if (SCT_LIST_validate(scts, ctx) < 0) { SSLerr(SSL_F_SSL_VALIDATE_CT, SSL_R_SCT_VERIFICATION_FAILED); goto end; } @@ -4147,6 +4197,32 @@ end: return ret; } +int SSL_CTX_enable_ct(SSL_CTX *ctx, int validation_mode) +{ + switch (validation_mode) { + default: + SSLerr(SSL_F_SSL_CTX_ENABLE_CT, SSL_R_INVALID_CT_VALIDATION_TYPE); + return 0; + case SSL_CT_VALIDATION_PERMISSIVE: + return SSL_CTX_set_ct_validation_callback(ctx, ct_permissive, NULL); + case SSL_CT_VALIDATION_STRICT: + return SSL_CTX_set_ct_validation_callback(ctx, ct_strict, NULL); + } +} + +int SSL_enable_ct(SSL *s, int validation_mode) +{ + switch (validation_mode) { + default: + SSLerr(SSL_F_SSL_ENABLE_CT, SSL_R_INVALID_CT_VALIDATION_TYPE); + return 0; + case SSL_CT_VALIDATION_PERMISSIVE: + return SSL_set_ct_validation_callback(s, ct_permissive, NULL); + case SSL_CT_VALIDATION_STRICT: + return SSL_set_ct_validation_callback(s, ct_strict, NULL); + } +} + int SSL_CTX_set_default_ctlog_list_file(SSL_CTX *ctx) { return CTLOG_STORE_load_default_file(ctx->ctlog_store); diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h index 4a2b52d19e..8c8876c88e 100644 --- a/ssl/ssl_locl.h +++ b/ssl/ssl_locl.h @@ -816,7 +816,7 @@ struct ssl_ctx_st { * Validates that the SCTs (Signed Certificate Timestamps) are sufficient. * If they are not, the connection should be aborted. */ - ct_validation_cb ct_validation_callback; + ssl_ct_validation_cb ct_validation_callback; void *ct_validation_callback_arg; # endif @@ -1123,7 +1123,7 @@ struct ssl_st { * Validates that the SCTs (Signed Certificate Timestamps) are sufficient. * If they are not, the connection should be aborted. */ - ct_validation_cb ct_validation_callback; + ssl_ct_validation_cb ct_validation_callback; /* User-supplied argument tha tis passed to the ct_validation_callback */ void *ct_validation_callback_arg; /* diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c index 19ea227e6a..fe1cde69e1 100644 --- a/ssl/statem/statem_clnt.c +++ b/ssl/statem/statem_clnt.c @@ -2067,7 +2067,8 @@ MSG_PROCESS_RETURN tls_process_server_done(SSL *s, PACKET *pkt) #ifndef OPENSSL_NO_CT if (s->ct_validation_callback != NULL) { - if (!ssl_validate_ct(s)) { + /* Note we validate the SCTs whether or not we abort on error */ + if (!ssl_validate_ct(s) && (s->verify_mode & SSL_VERIFY_PEER)) { ssl3_send_alert(s, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE); return MSG_PROCESS_ERROR; } diff --git a/ssl/t1_ext.c b/ssl/t1_ext.c index 7940cfc2bf..e9933976cf 100644 --- a/ssl/t1_ext.c +++ b/ssl/t1_ext.c @@ -260,12 +260,6 @@ int SSL_CTX_add_client_custom_ext(SSL_CTX *ctx, unsigned int ext_type, custom_ext_parse_cb parse_cb, void *parse_arg) { - int ret = custom_ext_meth_add(&ctx->cert->cli_ext, ext_type, add_cb, - free_cb, add_arg, parse_cb, parse_arg); - - if (ret != 1) - goto end; - #ifndef OPENSSL_NO_CT /* * We don't want applications registering callbacks for SCT extensions @@ -273,12 +267,11 @@ int SSL_CTX_add_client_custom_ext(SSL_CTX *ctx, unsigned int ext_type, * these two things may not play well together. */ if (ext_type == TLSEXT_TYPE_signed_certificate_timestamp && - SSL_CTX_get_ct_validation_callback(ctx) != NULL) { - ret = 0; - } + SSL_CTX_ct_is_enabled(ctx)) + return 0; #endif -end: - return ret; + return custom_ext_meth_add(&ctx->cert->cli_ext, ext_type, add_cb, + free_cb, add_arg, parse_cb, parse_arg); } int SSL_CTX_add_server_custom_ext(SSL_CTX *ctx, unsigned int ext_type, diff --git a/test/ct_test.c b/test/ct_test.c index 5446f9d0da..bdd5b84806 100644 --- a/test/ct_test.c +++ b/test/ct_test.c @@ -402,6 +402,17 @@ static int execute_cert_test(CT_TEST_FIXTURE fixture) goto end; } + if (fixture.test_validity && cert != NULL) { + int is_sct_validated = SCT_validate(sct, ct_policy_ctx); + if (is_sct_validated < 0) { + fprintf(stderr, "Error validating SCT\n"); + goto end; + } else if (!is_sct_validated) { + fprintf(stderr, "SCT failed verification\n"); + goto end; + } + } + if (fixture.sct_text_file && compare_sct_printout(sct, expected_sct_text)) { goto end; @@ -413,17 +424,6 @@ static int execute_cert_test(CT_TEST_FIXTURE fixture) 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) { - fprintf(stderr, "Error validating SCT\n"); - goto end; - } else if (!is_sct_validated) { - fprintf(stderr, "SCT failed verification\n"); - goto end; - } - } } success = 1; diff --git a/test/recipes/80-test_ssl_old.t b/test/recipes/80-test_ssl_old.t index 855e7c66f4..13fcfbe6df 100644 --- a/test/recipes/80-test_ssl_old.t +++ b/test/recipes/80-test_ssl_old.t @@ -811,20 +811,21 @@ sub testssl { plan tests => 3; SKIP: { - skip "Certificate Transparency is not supported by this OpenSSL build", 3 - if $no_ct; - skip "TLSv1.0 is not supported by this OpenSSL build", 3 - if $no_tls1; + skip "Certificate Transparency is not supported by this OpenSSL build", 3 + if $no_ct; + skip "TLSv1.0 is not supported by this OpenSSL build", 3 + if $no_tls1; - $ENV{CTLOG_FILE} = srctop_file("test", "ct", "log_list.conf"); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-noct"]))); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-requestct"]))); - # No SCTs provided, so this should fail. - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-requirect", - "-should_negotiate", "fail-client"]))); - } + $ENV{CTLOG_FILE} = srctop_file("test", "ct", "log_list.conf"); + my @ca = qw(-CAfile certCA.ss); + ok(run(test([@ssltest, @ca, "-bio_pair", "-tls1", "-noct"]))); + # No SCTs provided, so this should fail. + ok(run(test([@ssltest, @ca, "-bio_pair", "-tls1", "-ct", + "-should_negotiate", "fail-client"]))); + # No SCTs provided, unverified chains still succeed. + ok(run(test([@ssltest, "-bio_pair", "-tls1", "-ct"]))); + } }; - } sub testsslproxy { diff --git a/test/ssltest_old.c b/test/ssltest_old.c index 8018b3bd16..e3f8d774cb 100644 --- a/test/ssltest_old.c +++ b/test/ssltest_old.c @@ -1113,7 +1113,7 @@ int main(int argc, char *argv[]) * Disable CT validation by default, because it will interfere with * anything using custom extension handlers to deal with SCT extensions. */ - ct_validation_cb ct_validation = NULL; + int ct_validation = 0; #endif SSL_CONF_CTX *s_cctx = NULL, *c_cctx = NULL, *s_cctx2 = NULL; STACK_OF(OPENSSL_STRING) *conf_args = NULL; @@ -1300,13 +1300,10 @@ int main(int argc, char *argv[]) } #ifndef OPENSSL_NO_CT else if (strcmp(*argv, "-noct") == 0) { - ct_validation = NULL; + ct_validation = 0; } - else if (strcmp(*argv, "-requestct") == 0) { - ct_validation = CT_verify_no_bad_scts; - } - else if (strcmp(*argv, "-requirect") == 0) { - ct_validation = CT_verify_at_least_one_good_sct; + else if (strcmp(*argv, "-ct") == 0) { + ct_validation = 1; } #endif #ifndef OPENSSL_NO_COMP @@ -1633,7 +1630,8 @@ int main(int argc, char *argv[]) } #ifndef OPENSSL_NO_CT - if (!SSL_CTX_set_ct_validation_callback(c_ctx, ct_validation, NULL)) { + if (ct_validation && + !SSL_CTX_enable_ct(c_ctx, SSL_CT_VALIDATION_STRICT)) { ERR_print_errors(bio_err); goto end; }