aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/s_client.c67
-rw-r--r--crypto/ct/ct_oct.c12
-rw-r--r--crypto/ct/ct_prn.c23
-rw-r--r--crypto/ct/ct_sct.c33
-rw-r--r--crypto/ct/ct_vfy.c59
-rw-r--r--doc/apps/s_client.pod14
-rw-r--r--doc/ssl/SSL_CTX_set_ct_validation_callback.pod113
-rw-r--r--doc/ssl/SSL_CTX_set_ctlog_list_file.pod2
-rw-r--r--include/openssl/ct.h20
-rw-r--r--include/openssl/ssl.h53
-rw-r--r--ssl/ssl_lib.c138
-rw-r--r--ssl/ssl_locl.h4
-rw-r--r--ssl/statem/statem_clnt.c3
-rw-r--r--ssl/t1_ext.c15
-rw-r--r--test/ct_test.c22
-rw-r--r--test/recipes/80-test_ssl_old.t27
-rw-r--r--test/ssltest_old.c14
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);
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;
-
- $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"])));
- }
+ 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");
+ 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;
}