S2N_RESULT s2n_signature_algorithm_select()

in tls/s2n_signature_algorithms.c [177:317]


S2N_RESULT s2n_signature_algorithm_select(struct s2n_connection *conn)
{
    RESULT_ENSURE_REF(conn);
    RESULT_ENSURE_REF(conn->secure);
    struct s2n_cipher_suite *cipher_suite = conn->secure->cipher_suite;
    RESULT_ENSURE_REF(cipher_suite);

    const struct s2n_signature_scheme **chosen_sig_scheme = NULL;
    if (conn->mode == S2N_CLIENT) {
        chosen_sig_scheme = &conn->handshake_params.client_cert_sig_scheme;
    } else {
        chosen_sig_scheme = &conn->handshake_params.server_cert_sig_scheme;
    }

    /* Before TLS1.2, signature algorithms were fixed instead of negotiated */
    if (conn->actual_protocol_version < S2N_TLS12) {
        RESULT_GUARD(s2n_signature_algorithms_get_legacy_default(conn, conn->mode, chosen_sig_scheme));
        return S2N_RESULT_OK;
    }

    const struct s2n_signature_preferences *signature_preferences = NULL;
    RESULT_GUARD_POSIX(s2n_connection_get_signature_preferences(conn, &signature_preferences));
    RESULT_ENSURE_REF(signature_preferences);

    const struct s2n_signature_scheme *fallback_candidate = NULL;

    /* We use local preference order, not peer preference order, so we iterate
     * over the local preferences instead of over the options offered by the peer.
     */
    for (size_t i = 0; i < signature_preferences->count; i++) {
        const struct s2n_signature_scheme *candidate = signature_preferences->signature_schemes[i];

        /* Validates that a signature is valid to choose,
         * including that it's allowed by the current protocol version.
         */
        if (!s2n_signature_scheme_is_valid_for_recv(conn, candidate)) {
            continue;
        }

        if (s2n_is_sig_scheme_valid_for_auth(conn, candidate) != S2N_SUCCESS) {
            continue;
        }

        /* s2n-tls first attempts to choose a signature algorithm offered by the peer.
         * However, if that is not possible, we will attempt to continue the handshake
         * anyway with an algorithm not offered by the peer. This fallback behavior
         * is allowed by the RFC for TLS1.3 servers and partially allowed for TLS1.2
         * servers that don't receive the signature_algorithms extension, but is
         * otherwise an intentional deviation from the RFC.
         *
         * TLS1.3 servers:
         *= https://www.rfc-editor.org/rfc/rfc8446#section-4.4.3
         *# If the CertificateVerify message is sent by a server, the signature
         *# algorithm MUST be one offered in the client's "signature_algorithms"
         *# extension unless no valid certificate chain can be produced without
         *# unsupported algorithms
         *
         * TLS1.3 clients:
         *= https://www.rfc-editor.org/rfc/rfc8446#section-4.4.3
         *= type=exception
         *= reason=Compatibility with hypothetical faulty peers
         *# If sent by a client, the signature algorithm used in the signature
         *# MUST be one of those present in the supported_signature_algorithms
         *# field of the "signature_algorithms" extension in the
         *# CertificateRequest message.
         *
         * TLS1.2 servers:
         *= https://www.rfc-editor.org/rfc/rfc5246#section-7.4.3
         *= type=exception
         *= reason=Compatibility with known faulty peers
         *# If the client has offered the "signature_algorithms" extension, the
         *# signature algorithm and hash algorithm MUST be a pair listed in that
         *# extension.
         *
         * TLS1.2 clients:
         *= https://www.rfc-editor.org/rfc/rfc5246#section-7.4.8
         *= type=exception
         *= reason=Compatibility with hypothetical faulty peers
         *# The hash and signature algorithms used in the signature MUST be
         *# one of those present in the supported_signature_algorithms field
         *# of the CertificateRequest message.
         */
        bool is_peer_supported = s2n_signature_algorithm_is_supported_by_peer(
                conn, candidate->iana_value);
        if (is_peer_supported) {
            *chosen_sig_scheme = candidate;
            return S2N_RESULT_OK;
        }

        /**
         *= https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.4.1
         *# If the client does not send the signature_algorithms extension, the
         *# server MUST do the following:
         *#
         *# -  If the negotiated key exchange algorithm is one of (RSA, DHE_RSA,
         *#    DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had
         *#    sent the value {sha1,rsa}.
         *#
         *# -  If the negotiated key exchange algorithm is one of (DHE_DSS,
         *#    DH_DSS), behave as if the client had sent the value {sha1,dsa}.
         *#
         *# -  If the negotiated key exchange algorithm is one of (ECDH_ECDSA,
         *#    ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}.
         *
         * The default scheme for DSA is not used because s2n-tls does not support DSA certificates.
         *
         * These defaults are only relevant for TLS1.2, since TLS1.3 does not allow SHA1.
         */
        bool is_default = (candidate == &s2n_ecdsa_sha1 || candidate == &s2n_rsa_pkcs1_sha1);

        /* If we ultimately cannot choose any algorithm offered by the peer,
         * we will attempt negotiation with an algorithm not offered by the peer.
         *
         * The TLS1.2 RFC specifies default algorithms for use when no signature_algorithms
         * extension is sent-- see the definition of is_default above.
         *
         * s2n-tls has encountered clients in the wild that support the TLS1.2
         * default algorithms but do not include them in their signature_algorithms
         * extension, likely due to a misreading of the RFC. So s2n-tls attempts
         * to use the TLS1.2 defaults even when the client sends the signature_algorithms
         * extension, and always treats them as the most preferred fallback option.
         *
         * If the TLS1.2 defaults are not possible-- for example, because TLS1.3
         * or the security policy forbids SHA1-- we fallback to our own most
         * preferred algorithm. In most cases a correctly implemented peer will reject
         * this fallback, but the only alternative is to kill the connection here.
         */
        if (is_default) {
            fallback_candidate = candidate;
        } else if (fallback_candidate == NULL) {
            fallback_candidate = candidate;
        }
    }

    if (fallback_candidate) {
        *chosen_sig_scheme = fallback_candidate;
    } else {
        RESULT_BAIL(S2N_ERR_NO_VALID_SIGNATURE_SCHEME);
    }
    return S2N_RESULT_OK;
}