diff options
-rw-r--r-- | apps/s_client.c | 67 | ||||
-rw-r--r-- | crypto/ct/ct_oct.c | 12 | ||||
-rw-r--r-- | crypto/ct/ct_prn.c | 23 | ||||
-rw-r--r-- | crypto/ct/ct_sct.c | 33 | ||||
-rw-r--r-- | crypto/ct/ct_vfy.c | 59 | ||||
-rw-r--r-- | doc/apps/s_client.pod | 14 | ||||
-rw-r--r-- | doc/ssl/SSL_CTX_set_ct_validation_callback.pod | 113 | ||||
-rw-r--r-- | doc/ssl/SSL_CTX_set_ctlog_list_file.pod | 2 | ||||
-rw-r--r-- | include/openssl/ct.h | 20 | ||||
-rw-r--r-- | include/openssl/ssl.h | 53 | ||||
-rw-r--r-- | ssl/ssl_lib.c | 138 | ||||
-rw-r--r-- | ssl/ssl_locl.h | 4 | ||||
-rw-r--r-- | ssl/statem/statem_clnt.c | 3 | ||||
-rw-r--r-- | ssl/t1_ext.c | 15 | ||||
-rw-r--r-- | test/ct_test.c | 22 | ||||
-rw-r--r-- | test/recipes/80-test_ssl_old.t | 27 | ||||
-rw-r--r-- | test/ssltest_old.c | 14 |
17 files changed, 382 insertions, 237 deletions
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); - - 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"); - } - - if (scts != NULL && sk_SCT_num(scts) > 0) { - const CTLOG_STORE *log_store = SSL_CTX_get0_ctlog_store(ctx); - - BIO_printf(bio, "---\n"); - SCT_LIST_print(scts, bio, 0, "\n---\n", log_store); - BIO_printf(bio, "\n"); + /* + * 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; + + BIO_printf(bio, "---\nSCTs present (%i)\n", sct_count); + if (sct_count > 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, "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<openssl> B<s_client> [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 <openssl/ssl.h> - 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. - -An arbitrary piece of user data, B<arg>, 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. +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<validation_mode> argument, +which can be either of B<SSL_CT_VALIDATION_PERMISSIVE> or +B<SSL_CT_VALIDATION_STRICT> as described below. + +If B<validation_mode> is equal to B<SSL_CT_VALIDATION_PERMISSIVE>, 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<SSL_session_reused(3)>. + +If B<validation_mode> is equal to B<SSL_CT_VALIDATION_STRICT>, then in a full +TLS handshake with the verification mode set to B<SSL_VERIFY_PEER>, if the peer +presents no valid SCTs the handshake will be aborted. +See L<SSL_set_verify(3)>. + +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<SSL_VERIFY_NONE> +and the callback returns a non-positive result. + +An arbitrary callback context argument, B<arg>, 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<TLSEXT_TYPE_signed_certificate_timestamp>). -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<aNULL>) 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<callback> 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<callback> 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_disable() and SSL_CTX_ct_disable() do not return a result. -SSL_CTX_get_ct_validation_callback() and SSL_get_ct_validation_callback() -return the current callback, or NULL if no callback is set. +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<ssl(3)>, -L<ct_validation_cb(3)> +L<SSL_session_reused(3)>, +L<SSL_set_verify(3)>, +L<SSL_CTX_set_verify(3)>, +L<ssl_ct_validation_cb(3)> =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<ssl(3)>, -L<ct_validation_cb(3)> +L<ssl_ct_validation_cb(3)> =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 * *****************/ @@ -299,6 +284,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|. * If |logs| is not NULL, it will be used to lookup the CT log that the SCT came 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 @@ -1899,6 +1899,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. * If the callback returns a non-positive result, the connection is terminated. @@ -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)) + +/* + * 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. + */ +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); + /* - * Gets the callback being used to validate SCTs. - * This will return NULL if SCTs are neither being requested nor validated. + * Report whether a non-NULL callback is enabled. */ -__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); +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; } - 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; + return 0; } - ret = 1; -err: - return ret; + s->ct_validation_callback = callback; + s->ct_validation_callback_arg = arg; + + 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); < |