Commit 43341433 authored by Viktor Dukhovni's avatar Viktor Dukhovni
Browse files

Suppress CT callback as appropriate



Suppress CT callbacks with aNULL or PSK ciphersuites that involve
no certificates.  Ditto when the certificate chain is validated via
DANE-TA(2) or DANE-EE(3) TLSA records.  Also skip SCT processing
when the chain is fails verification.

Move and consolidate CT callbacks from libcrypto to libssl.  We
also simplify the interface to SSL_{,CTX_}_enable_ct() which can
specify either a permissive mode that just collects information or
a strict mode that requires at least one valid SCT or else asks to
abort the connection.

Simplified SCT processing and options in s_client(1) which now has
just a simple pair of "-noct" vs. "-ct" options, the latter enables
the permissive callback so that we can complete the handshake and
report all relevant information.  When printing SCTs, print the
validation status if set and not valid.

Signed-off-by: default avatarRob Percival <robpercival@google.com>
Reviewed-by: default avatarEmilia Käsper <emilia@openssl.org>
parent c636c1c4
Loading
Loading
Loading
Loading
+39 −28
Original line number Diff line number Diff line
@@ -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,22 +2623,36 @@ 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");
        }
        /*
         * 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 (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");
            SCT_LIST_print(scts, bio, 0, "\n---\n", log_store);
                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

        BIO_printf(bio,
+8 −4
Original line number Diff line number Diff line
@@ -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);
+22 −1
Original line number Diff line number Diff line
@@ -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);
+28 −5
Original line number Diff line number Diff line
@@ -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,9 +377,27 @@ 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_VALIDATION_STATUS_UNVERIFIED;
    else
        sct->validation_status = SCT_verify(sctx, sct) == 1 ?
            SCT_VALIDATION_STATUS_VALID : SCT_VALIDATION_STATUS_INVALID;

+0 −59
Original line number Diff line number Diff line
@@ -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.
Loading