Use SSLCipherPreferenceList for TLS 1.3 ciphers This change is a refactor to use a SSLCipherPreferenceList to hold the TLS 1.3 cipher preference list in SSL_CTX and SSL_CONFIG. In SSL_CTX_new, it is populated with one of two default lists depending on whether we have EVP hardware. The default lists are ordered lists with equipreference groups layered on top (for servers). SSL_new copies the list from the SSL_CTX. This CL also rewrites the logic for TLS 1.3 cipher suite selection for SSL compliance policies to express it in terms of SSLCipherPreferenceList. This paves the way for a future CL which will add a general purpose API to configure the list. Bug: 545123692 Change-Id: Id57d698c288f603225706cd53b00cc696a6a6964 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/101167 Reviewed-by: David Benjamin <davidben@google.com> Commit-Queue: Lily Chen <chlily@google.com>
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc index c0ec883..62d5a0b 100644 --- a/ssl/handshake_client.cc +++ b/ssl/handshake_client.cc
@@ -83,14 +83,6 @@ } } -static bool ssl_add_tls13_cipher(CBB *cbb, uint16_t cipher_id, - ssl_compliance_policy_t policy) { - if (ssl_tls13_cipher_meets_policy(cipher_id, policy)) { - return CBB_add_u16(cbb, cipher_id); - } - return true; -} - static bool ssl_write_client_cipher_list(const SSL_HANDSHAKE *hs, CBB *out, ssl_client_hello_type_t type) { const SSLImpl *const ssl = hs->ssl; @@ -108,35 +100,10 @@ return false; } - // Add TLS 1.3 ciphers. Order ChaCha20-Poly1305 relative to AES-GCM based on - // hardware support. + // Add TLS 1.3 ciphers in configured preference order. if (hs->max_version >= TLS1_3_VERSION) { - static const uint16_t kCiphersNoAESHardware[] = { - SSL_CIPHER_CHACHA20_POLY1305_SHA256, - SSL_CIPHER_AES_128_GCM_SHA256, - SSL_CIPHER_AES_256_GCM_SHA384, - }; - static const uint16_t kCiphersAESHardware[] = { - SSL_CIPHER_AES_128_GCM_SHA256, - SSL_CIPHER_AES_256_GCM_SHA384, - SSL_CIPHER_CHACHA20_POLY1305_SHA256, - }; - static const uint16_t kCiphersCNSA[] = { - SSL_CIPHER_AES_256_GCM_SHA384, - SSL_CIPHER_AES_128_GCM_SHA256, - SSL_CIPHER_CHACHA20_POLY1305_SHA256, - }; - - const bssl::Span<const uint16_t> ciphers = - ssl->config->compliance_policy == ssl_compliance_policy_cnsa_202407 - ? bssl::Span<const uint16_t>(kCiphersCNSA) - : (EVP_has_aes_hardware() - ? bssl::Span<const uint16_t>(kCiphersAESHardware) - : bssl::Span<const uint16_t>(kCiphersNoAESHardware)); - - for (auto cipher : ciphers) { - if (!ssl_add_tls13_cipher(&child, cipher, - ssl->config->compliance_policy)) { + for (const SSL_CIPHER *cipher : hs->config->tls13_cipher_list.ciphers()) { + if (!CBB_add_u16(&child, SSL_CIPHER_get_protocol_id(cipher))) { return false; } }
diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc index 0b4f1a4..d47af74 100644 --- a/ssl/handshake_server.cc +++ b/ssl/handshake_server.cc
@@ -127,95 +127,6 @@ return true; } -static UniquePtr<STACK_OF(SSL_CIPHER)> ssl_parse_client_cipher_list( - const SSL_CLIENT_HELLO *client_hello) { - CBS cipher_suites; - CBS_init(&cipher_suites, client_hello->cipher_suites, - client_hello->cipher_suites_len); - - UniquePtr<STACK_OF(SSL_CIPHER)> sk(sk_SSL_CIPHER_new_null()); - if (!sk) { - return nullptr; - } - - while (CBS_len(&cipher_suites) > 0) { - uint16_t cipher_suite; - - if (!CBS_get_u16(&cipher_suites, &cipher_suite)) { - OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST); - return nullptr; - } - - const SSL_CIPHER *c = SSL_get_cipher_by_value(cipher_suite); - if (c != nullptr && !sk_SSL_CIPHER_push(sk.get(), c)) { - return nullptr; - } - } - - return sk; -} - -static const SSL_CIPHER *choose_cipher(SSL_HANDSHAKE *hs, - const STACK_OF(SSL_CIPHER) *client_pref, - uint32_t mask_k, uint32_t mask_a) { - SSLImpl *const ssl = hs->ssl; - const STACK_OF(SSL_CIPHER) *prio, *allow; - // in_group_flags will either be empty, or will contain an array of bytes - // which indicate equal-preference groups in the `prio` stack. See the - // comment about `in_group_flags` in the `SSLCipherPreferenceList` - // struct. - Span<const bool> in_group_flags; - // best_index contains the index of the best matching cipher suite found so - // far, indexed into `allow`. If `best_index` is `SIZE_MAX`, no matching - // cipher suite has been found yet. - size_t best_index = SIZE_MAX; - - const SSLCipherPreferenceList *server_pref = - hs->config->cipher_list ? hs->config->cipher_list.get() - : ssl->ctx->cipher_list.get(); - if (ssl->options & SSL_OP_CIPHER_SERVER_PREFERENCE) { - prio = server_pref->ciphers(); - in_group_flags = server_pref->in_group_flags(); - allow = client_pref; - } else { - prio = client_pref; - in_group_flags = Span<const bool>(); - allow = server_pref->ciphers(); - } - - for (size_t i = 0; i < sk_SSL_CIPHER_num(prio); i++) { - const SSL_CIPHER *c = sk_SSL_CIPHER_value(prio, i); - const bool in_group = !in_group_flags.empty() && in_group_flags[i]; - - size_t cipher_index; - if ( // Check if the cipher is supported for the current version. - SSL_CIPHER_get_min_version(c) <= ssl_protocol_version(ssl) && // - ssl_protocol_version(ssl) <= SSL_CIPHER_get_max_version(c) && // - // Check the cipher is supported for the server configuration. - (c->algorithm_mkey & mask_k) && // - (c->algorithm_auth & mask_a) && // - // Check the cipher is in the `allow` list. - sk_SSL_CIPHER_find(allow, &cipher_index, c)) { - // Within a group, `allow`'s preference order applies. - if (best_index == SIZE_MAX || best_index > cipher_index) { - best_index = cipher_index; - } - } - - // We are about to leave a (possibly singleton) group, but we found a match - // in it, so that's our answer. - if (!in_group && best_index != SIZE_MAX) { - return sk_SSL_CIPHER_value(allow, best_index); - } - } - - // The final cipher suite must end a group, so, if we found a match, we must - // have returned early above. - assert(best_index == SIZE_MAX); - OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER); - return nullptr; -} - struct TLS12ServerParams { bool ok() const { return cipher != nullptr; } @@ -226,7 +137,7 @@ static TLS12ServerParams choose_params(SSL_HANDSHAKE *hs, const SSLCredential *cred, Span<const uint8_t> allowed_cert_types, - const STACK_OF(SSL_CIPHER) *client_pref, + const CBS *client_cipher_list, bool has_ecdhe_group) { // Determine the usable cipher suites. uint32_t mask_k = 0, mask_a = 0; @@ -279,7 +190,14 @@ } TLS12ServerParams params; - params.cipher = choose_cipher(hs, client_pref, mask_k, mask_a); + const SSLCipherPreferenceList *server_cipher_pref = + hs->config->cipher_list ? hs->config->cipher_list.get() + : hs->ssl->ctx->cipher_list.get(); + bool prioritize_client_pref = + (hs->ssl->options & SSL_OP_CIPHER_SERVER_PREFERENCE) == 0; + params.cipher = server_cipher_pref->ChooseCipher( + client_cipher_list, prioritize_client_pref, ssl_protocol_version(hs->ssl), + mask_k, mask_a); if (params.cipher == nullptr || (cred != nullptr && !ssl_credential_matches_requested_issuers(hs, cred))) { @@ -750,11 +668,9 @@ // TODO(davidben): In the course of picking these, we also pick the ECDHE // group and signature algorithm. It would be tidier if we saved that decision // and avoided redoing it later. - UniquePtr<STACK_OF(SSL_CIPHER)> client_pref = - ssl_parse_client_cipher_list(&client_hello); - if (client_pref == nullptr) { - return ssl_hs_error; - } + CBS client_cipher_list; + CBS_init(&client_cipher_list, client_hello.cipher_suites, + client_hello.cipher_suites_len); Array<SSLCredential *> creds; if (!ssl_get_full_credential_list(hs, &creds)) { return ssl_hs_error; @@ -770,12 +686,12 @@ if (creds.empty()) { // The caller may have configured no credentials, but set a PSK callback. params = choose_params(hs, /*cred=*/nullptr, *allowed_cert_types, - client_pref.get(), has_ecdhe_group); + &client_cipher_list, has_ecdhe_group); } else { // Select the first credential which works. for (SSLCredential *cred : creds) { ERR_clear_error(); - params = choose_params(hs, cred, *allowed_cert_types, client_pref.get(), + params = choose_params(hs, cred, *allowed_cert_types, &client_cipher_list, has_ecdhe_group); if (params.ok()) { hs->credential = UpRef(cred);
diff --git a/ssl/internal.h b/ssl/internal.h index 65bd0b3..60ee51b 100644 --- a/ssl/internal.h +++ b/ssl/internal.h
@@ -294,9 +294,15 @@ SSLCipherPreferenceList() = default; ~SSLCipherPreferenceList() = default; - // Initializes a list with the specified ciphers and flags. + // Initializes a list with the specified ciphers and flags. Calling Init on a + // previously initialized list discards the previous contents. bool Init(UniquePtr<STACK_OF(SSL_CIPHER)> ciphers, Array<bool> in_group_flags); + // Same as above, but takes a list of cipher protocol IDs. + bool Init(Span<const uint16_t> cipher_ids, Span<const bool> in_group_flags); + + // Reset clears any contents previously set by `Init`. + void Reset(); // Makes `this` a deep copy of another (already initialized) instance. bool CopyFrom(const SSLCipherPreferenceList &); @@ -304,13 +310,33 @@ // Removes `cipher` from the preference list. void Remove(const SSL_CIPHER *cipher); + // Contains returns whether a cipher whose protocol ID is `cipher_id` appears + // in the list. + bool Contains(uint16_t cipher_id) const; + + // ChooseCipher implements the logic for a server to select the most-preferred + // cipher satisfying the constraints that is listed in both `*this` and the + // client's preference list in `client_cipher_list`, which contains an ordered + // list of 2-byte cipher suite protocol IDs. The returned cipher must be + // supported for the SSL protocol `version`, and must match the key exchange + // algorithm mask `mask_k` and the server authentication mask `mask_a`. If + // `prioritize_client_pref` is true, then the client's preference list is + // prioritized over the list in `*this`. Otherwise, the server's preference + // list (`*this`) is prioritized. This function returns a pointer to the + // most-preferred shared cipher, or nullptr if no shared cipher was found. + const SSL_CIPHER *ChooseCipher(const CBS *client_cipher_list, + bool prioritize_client_pref, uint16_t version, + uint32_t mask_k, uint32_t mask_a) const; + size_t size() const { return sk_SSL_CIPHER_num(ciphers_.get()); } const STACK_OF(SSL_CIPHER) *ciphers() const { return ciphers_.get(); } - STACK_OF(SSL_CIPHER) *ciphers() { return ciphers_.get(); } - Span<const bool> in_group_flags() const { return in_group_flags_; } + // TODO(crbug.com/550501994): Remove the non-const overload. This may result + // in callers mutating the internal state in an inconsistent way. + STACK_OF(SSL_CIPHER) *ciphers() { return ciphers_.get(); } + private: // SSL_CIPHERs are maintained in a stack so they are easily accessible in the // form required for `SSL{_CTX}_get_ciphers`. @@ -336,15 +362,24 @@ const EVP_MD *ssl_get_handshake_digest(uint16_t version, const SSL_CIPHER *cipher); -// ssl_create_cipher_list evaluates `rule_str`. It sets `*out_cipher_list` to a -// newly-allocated `SSLCipherPreferenceList` containing the result. It returns -// true on success and false on failure. If `strict` is true, nonsense will be -// rejected. If false, nonsense will be silently ignored. An empty result is -// considered an error regardless of `strict`. The resulting list will be -// ordered based on having support for AES in hardware or not. +// ssl_create_cipher_list evaluates `rule_str` to create the TLS 1.2 cipher +// list. It sets `*out_cipher_list` to a newly-allocated +// `SSLCipherPreferenceList` containing the result. It returns true on success +// and false on failure. If `strict` is true, nonsense will be rejected. If +// false, nonsense will be silently ignored. An empty result is considered an +// error regardless of `strict`. The resulting list will be ordered based on +// having support for AES in hardware or not. bool ssl_create_cipher_list(UniquePtr<SSLCipherPreferenceList> *out_cipher_list, const char *rule_str, bool strict); +// ssl_create_default_tls13_cipher_list populates the default TLS 1.3 cipher +// list, clearing any previous contents in `*out_cipher_list` and replacing them +// with the result. It returns true on success and false on failure. The +// resulting list will be ordered based on having support for AES in hardware or +// not. +bool ssl_create_default_tls13_cipher_list( + SSLCipherPreferenceList *out_cipher_list); + // ssl_cipher_auth_mask_for_key returns the mask of cipher `algorithm_auth` // values suitable for use with `key` in TLS 1.2 and below. `sign_ok` indicates // whether `key` may be used for signing. @@ -366,19 +401,6 @@ // it returns zero. size_t ssl_cipher_get_record_split_len(const SSL_CIPHER *cipher); -// ssl_choose_tls13_cipher returns an `SSL_CIPHER` corresponding with the best -// available from `cipher_suites` compatible with `version` and `policy`. It -// returns NULL if there isn't a compatible cipher. `has_aes_hw` indicates if -// the choice should be made as if support for AES in hardware is available. -const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, bool has_aes_hw, - uint16_t version, - enum ssl_compliance_policy_t policy); - -// ssl_tls13_cipher_meets_policy returns true if `cipher_id` is acceptable given -// `policy`. -bool ssl_tls13_cipher_meets_policy(uint16_t cipher_id, - enum ssl_compliance_policy_t policy); - // ssl_cipher_is_deprecated returns true if `cipher` is deprecated. bool ssl_cipher_is_deprecated(const SSL_CIPHER *cipher); @@ -3437,7 +3459,10 @@ X509_VERIFY_PARAM *param = nullptr; // crypto - UniquePtr<SSLCipherPreferenceList> cipher_list; + UniquePtr<SSLCipherPreferenceList> cipher_list; // for TLS 1.2 ciphers. + + // Inherited from `SSL_CTX`. + SSLCipherPreferenceList tls13_cipher_list; // This is used to hold the local certificate used (i.e. the server // certificate for a server or the client certificate for a client). @@ -3973,7 +3998,8 @@ // quic_method is the method table corresponding to the QUIC hooks. const SSL_QUIC_METHOD *quic_method = nullptr; - UniquePtr<SSLCipherPreferenceList> cipher_list; + UniquePtr<SSLCipherPreferenceList> cipher_list; // for TLS 1.2 ciphers. + SSLCipherPreferenceList tls13_cipher_list; X509_STORE *cert_store = nullptr; LHASH_OF(SSL_SESSION) *sessions = nullptr;
diff --git a/ssl/s3_both.cc b/ssl/s3_both.cc index d885da9..10e0ad2 100644 --- a/ssl/s3_both.cc +++ b/ssl/s3_both.cc
@@ -552,138 +552,4 @@ } } -namespace { - -class CipherScorer { - public: - using Score = int; - static constexpr Score kMinScore = 0; - - virtual ~CipherScorer() = default; - - virtual Score Evaluate(const SSL_CIPHER *cipher) const = 0; -}; - -// AesHwCipherScorer scores cipher suites based on whether AES is supported in -// hardware. -class AesHwCipherScorer : public CipherScorer { - public: - explicit AesHwCipherScorer(bool has_aes_hw) : aes_is_fine_(has_aes_hw) {} - - virtual ~AesHwCipherScorer() override = default; - - Score Evaluate(const SSL_CIPHER *a) const override { - return - // Something is always preferable to nothing. - 1 + - // Either AES is fine, or else ChaCha20 is preferred. - ((aes_is_fine_ || a->algorithm_enc == SSL_CHACHA20POLY1305) ? 1 : 0); - } - - private: - const bool aes_is_fine_; -}; - -// CNsaCipherScorer prefers AES-256-GCM over AES-128-GCM over anything else. -class CNsaCipherScorer : public CipherScorer { - public: - virtual ~CNsaCipherScorer() override = default; - - Score Evaluate(const SSL_CIPHER *a) const override { - if (a->protocol_id == SSL_CIPHER_AES_256_GCM_SHA384) { - return 3; - } else if (a->protocol_id == SSL_CIPHER_AES_128_GCM_SHA256) { - return 2; - } - return 1; - } -}; - -} // namespace - -bool ssl_tls13_cipher_meets_policy(uint16_t cipher_id, - enum ssl_compliance_policy_t policy) { - switch (policy) { - case ssl_compliance_policy_none: - case ssl_compliance_policy_cnsa_202407: - return true; - - case ssl_compliance_policy_fips_202205: - switch (cipher_id) { - case SSL_CIPHER_AES_128_GCM_SHA256: - case SSL_CIPHER_AES_256_GCM_SHA384: - return true; - case SSL_CIPHER_CHACHA20_POLY1305_SHA256: - return false; - default: - assert(false); - return false; - } - - case ssl_compliance_policy_wpa3_192_202304: - case ssl_compliance_policy_cnsa1_202603: - case ssl_compliance_policy_cnsa2_202603: - switch (cipher_id) { - case SSL_CIPHER_AES_256_GCM_SHA384: - return true; - case SSL_CIPHER_AES_128_GCM_SHA256: - case SSL_CIPHER_CHACHA20_POLY1305_SHA256: - return false; - default: - assert(false); - return false; - } - } - - assert(false); - return false; -} - -const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, bool has_aes_hw, - uint16_t version, - enum ssl_compliance_policy_t policy) { - if (CBS_len(&cipher_suites) % 2 != 0) { - return nullptr; - } - - const SSL_CIPHER *best = nullptr; - AesHwCipherScorer aes_hw_scorer(has_aes_hw); - CNsaCipherScorer cnsa_scorer; - CipherScorer *const scorer = - (policy == ssl_compliance_policy_cnsa_202407) - ? static_cast<CipherScorer *>(&cnsa_scorer) - : static_cast<CipherScorer *>(&aes_hw_scorer); - CipherScorer::Score best_score = CipherScorer::kMinScore; - - while (CBS_len(&cipher_suites) > 0) { - uint16_t cipher_suite; - if (!CBS_get_u16(&cipher_suites, &cipher_suite)) { - return nullptr; - } - - // Limit to TLS 1.3 ciphers we know about. - const SSL_CIPHER *candidate = SSL_get_cipher_by_value(cipher_suite); - if (candidate == nullptr || - SSL_CIPHER_get_min_version(candidate) > version || - SSL_CIPHER_get_max_version(candidate) < version) { - continue; - } - - if (!ssl_tls13_cipher_meets_policy(SSL_CIPHER_get_protocol_id(candidate), - policy)) { - continue; - } - - const CipherScorer::Score candidate_score = scorer->Evaluate(candidate); - // `candidate_score` must be larger to displace the current choice. That way - // the client's order controls between ciphers with an equal score. - if (candidate_score > best_score) { - best = candidate; - best_score = candidate_score; - } - } - - return best; -} - BSSL_NAMESPACE_END
diff --git a/ssl/ssl_cipher.cc b/ssl/ssl_cipher.cc index 4b7a78b..c36c194 100644 --- a/ssl/ssl_cipher.cc +++ b/ssl/ssl_cipher.cc
@@ -20,6 +20,7 @@ #include <string.h> #include <iterator> +#include <optional> #include <openssl/aead.h> #include <openssl/err.h> @@ -605,18 +606,81 @@ *head = curr; } +// Helper to iterate over a client cipher list and find a given cipher protocol +// ID. Returns the index of the cipher, if found in `cipher_list`, or returns +// std::nullopt if not found. +static std::optional<size_t> FindProtocolID(CBS *cipher_list, + uint16_t cipher_id) { + assert(CBS_len(cipher_list) % 2 == 0); + if (CBS_len(cipher_list) == 0) { + return std::nullopt; + } + size_t cur_index = 0; + while (CBS_len(cipher_list) > 0) { + uint16_t cipher_suite; + if (!CBS_get_u16(cipher_list, &cipher_suite)) { + return std::nullopt; + } + if (cipher_suite == cipher_id) { + return cur_index; + } + ++cur_index; + } + return std::nullopt; +} + bool SSLCipherPreferenceList::Init(UniquePtr<STACK_OF(SSL_CIPHER)> ciphers, Array<bool> in_group_flags) { if (sk_SSL_CIPHER_num(ciphers.get()) != in_group_flags.size()) { OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); return false; } + // The last element has no next element to be in a group with. + if (!in_group_flags.empty() && in_group_flags.back()) { + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return false; + } ciphers_ = std::move(ciphers); in_group_flags_ = std::move(in_group_flags); return true; } +bool SSLCipherPreferenceList::Init(Span<const uint16_t> cipher_ids, + Span<const bool> in_group_flags) { + if (cipher_ids.size() != in_group_flags.size()) { + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return false; + } + + UniquePtr<STACK_OF(SSL_CIPHER)> ciphers(sk_SSL_CIPHER_new_null()); + if (!ciphers) { + return false; + } + for (uint16_t cipher_id : cipher_ids) { + const SSL_CIPHER *cipher = SSL_get_cipher_by_value(cipher_id); + if (cipher == nullptr) { + OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CIPHER_TYPE); + return false; + } + if (!sk_SSL_CIPHER_push(ciphers.get(), cipher)) { + return false; + } + } + + Array<bool> flags; + if (!flags.CopyFrom(in_group_flags)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return false; + } + return Init(std::move(ciphers), std::move(flags)); +} + +void SSLCipherPreferenceList::Reset() { + sk_SSL_CIPHER_zero(ciphers_.get()); + in_group_flags_.Reset(); +} + bool SSLCipherPreferenceList::CopyFrom(const SSLCipherPreferenceList &other) { UniquePtr<STACK_OF(SSL_CIPHER)> other_ciphers( sk_SSL_CIPHER_dup(other.ciphers())); @@ -645,6 +709,76 @@ in_group_flags_.Shrink(size()); } +bool SSLCipherPreferenceList::Contains(uint16_t cipher_id) const { + for (const SSL_CIPHER *cipher : ciphers_.get()) { + if (cipher->protocol_id == cipher_id) { + return true; + } + } + return false; +} + +const SSL_CIPHER *SSLCipherPreferenceList::ChooseCipher( + const CBS *client_cipher_list, bool prioritize_client_pref, + uint16_t version, uint32_t mask_k, uint32_t mask_a) const { + if (CBS_len(client_cipher_list) % 2 != 0) { + OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST); + return nullptr; + } + + // Index of the best matching cipher suite found so far, indexed into + // `client_cipher_list`. + std::optional<size_t> best_index = std::nullopt; + const SSL_CIPHER *best_cipher = nullptr; + + // Iterate over our list (the server preference list) and check for each + // cipher in the client's list. + for (size_t i = 0; i < size(); ++i) { + const SSL_CIPHER *const c = sk_SSL_CIPHER_value(ciphers_.get(), i); + bool in_group = in_group_flags_[i]; + // If prioritizing the client preference list, treat all of the server's + // allowed ciphers as a single equipreference group so that the client's + // preferences dictate the choice. + if (prioritize_client_pref) { + in_group = (i < size() - 1); + } + + if (version >= SSL_CIPHER_get_min_version(c) && + version <= SSL_CIPHER_get_max_version(c) && + (c->algorithm_mkey & mask_k) != 0 && + (c->algorithm_auth & mask_a) != 0) { + CBS copy = *client_cipher_list; + std::optional<size_t> client_list_index = + FindProtocolID(©, c->protocol_id); + // Within a group, the client's preference order applies. + if (client_list_index.has_value() && + (!best_index.has_value() || *best_index > *client_list_index)) { + best_index = *client_list_index; + best_cipher = c; + } + } + + // Always evaluate a whole equipreference group. + if (in_group) { + continue; + } + // We are about to leave a (possibly singleton) group. If we have a match, + // return it because we will only see less-preferred ciphers if we keep + // going. + if (best_index.has_value()) { + assert(best_cipher != nullptr); + return best_cipher; + } + } + + // The final cipher suite must end a group, so, if we found a match, we must + // have returned early above. + assert(!best_index.has_value()); + assert(best_cipher == nullptr); + OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER); + return nullptr; +} + bool ssl_cipher_is_deprecated(const SSL_CIPHER *cipher) { return cipher->protocol_id == SSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 || @@ -1144,6 +1278,47 @@ return true; } +bool ssl_create_default_tls13_cipher_list( + SSLCipherPreferenceList *out_cipher_list) { + // If we have AES hardware: + // For a client: AES-128 > AES-256 > ChaCha20. + // For a server: (AES-128 | AES-256 | ChaCha20), i.e. defer to client + // preference. + static const uint16_t kCiphersAESHardware[] = { + SSL_CIPHER_AES_128_GCM_SHA256, + SSL_CIPHER_AES_256_GCM_SHA384, + SSL_CIPHER_CHACHA20_POLY1305_SHA256, + }; + static const bool kInGroupFlagsAESHardware[] = { + true, + true, + false, + }; + // If we do not have AES hardware: + // For a client: ChaCha20 > AES-128 > AES-256. + // For a server: ChaCha20 > (AES-128 | AES-256). + static const uint16_t kCiphersNoAESHardware[] = { + SSL_CIPHER_CHACHA20_POLY1305_SHA256, + SSL_CIPHER_AES_128_GCM_SHA256, + SSL_CIPHER_AES_256_GCM_SHA384, + }; + static const bool kInGroupFlagsNoAESHardware[] = { + false, + true, + false, + }; + + Span<const uint16_t> ciphers = EVP_has_aes_hardware() + ? Span(kCiphersAESHardware) + : Span(kCiphersNoAESHardware); + Span<const bool> in_group_flags = EVP_has_aes_hardware() + ? Span(kInGroupFlagsAESHardware) + : Span(kInGroupFlagsNoAESHardware); + + out_cipher_list->Reset(); + return out_cipher_list->Init(ciphers, in_group_flags); +} + uint32_t ssl_cipher_auth_mask_for_key(const EVP_PKEY *key, bool sign_ok) { switch (EVP_PKEY_id(key)) { case EVP_PKEY_RSA:
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc index ff885c9..b7d9980 100644 --- a/ssl/ssl_lib.cc +++ b/ssl/ssl_lib.cc
@@ -426,6 +426,7 @@ } if (!SSL_CTX_set_strict_cipher_list(ret.get(), SSL_DEFAULT_CIPHER_LIST) || + !ssl_create_default_tls13_cipher_list(&ret->tls13_cipher_list) || // Lock the SSL_CTX to the specified version, for compatibility with // legacy uses of SSL_METHOD. !SSL_CTX_set_max_proto_version(ret.get(), method->version) || @@ -514,6 +515,10 @@ ssl->config->permute_extensions = ctx_impl->permute_extensions; ssl->config->compliance_policy = ctx_impl->compliance_policy; + if (!ssl->config->tls13_cipher_list.CopyFrom(ctx_impl->tls13_cipher_list)) { + return nullptr; + } + if (!ssl->config->supported_group_list.CopyFrom( ctx_impl->supported_group_list) || !ssl->config->supported_group_list_flags.CopyFrom( @@ -3532,6 +3537,15 @@ "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:" "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"; +static const uint16_t kTLS13Ciphers[] = { + SSL_CIPHER_AES_128_GCM_SHA256, + SSL_CIPHER_AES_256_GCM_SHA384, +}; +static const bool kTLS13CiphersInGroup[] = { + true, + false, +}; + static int Configure(SSLContext *ctx) { ctx->compliance_policy = ssl_compliance_policy_fips_202205; @@ -3548,6 +3562,8 @@ // Encrypt-then-MAC extension is required for all CBC cipher suites and so // it's easier to drop them. SSL_CTX_set_strict_cipher_list(ctx, kTLS12Ciphers) && + ctx->tls13_cipher_list.Init(Span(kTLS13Ciphers), + Span(kTLS13CiphersInGroup)) && SSL_CTX_set1_group_ids(ctx, kGroups, std::size(kGroups)) && SSL_CTX_set_signing_algorithm_prefs(ctx, kSigAlgs, std::size(kSigAlgs)) && SSL_CTX_set_verify_algorithm_prefs(ctx, kSigAlgs, std::size(kSigAlgs)); @@ -3560,6 +3576,8 @@ return SSL_set_min_proto_version(ssl, TLS1_2_VERSION) && SSL_set_max_proto_version(ssl, TLS1_3_VERSION) && SSL_set_strict_cipher_list(ssl, kTLS12Ciphers) && + ssl->config->tls13_cipher_list.Init(Span(kTLS13Ciphers), + Span(kTLS13CiphersInGroup)) && SSL_set1_group_ids(ssl, kGroups, std::size(kGroups)) && SSL_set_signing_algorithm_prefs(ssl, kSigAlgs, std::size(kSigAlgs)) && SSL_set_verify_algorithm_prefs(ssl, kSigAlgs, std::size(kSigAlgs)); @@ -3585,6 +3603,13 @@ "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:" "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"; +static const uint16_t kTLS13Ciphers[] = { + SSL_CIPHER_AES_256_GCM_SHA384, +}; +static const bool kTLS13CiphersInGroup[] = { + false, +}; + static int Configure(SSLContext *ctx) { ctx->compliance_policy = ssl_compliance_policy_wpa3_192_202304; @@ -3592,6 +3617,8 @@ SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) && SSL_CTX_set_strict_cipher_list(ctx, kTLS12Ciphers) && SSL_CTX_set1_group_ids(ctx, kGroups, std::size(kGroups)) && + ctx->tls13_cipher_list.Init(Span(kTLS13Ciphers), + Span(kTLS13CiphersInGroup)) && SSL_CTX_set_signing_algorithm_prefs(ctx, kSigAlgs, std::size(kSigAlgs)) && SSL_CTX_set_verify_algorithm_prefs(ctx, kSigAlgs, std::size(kSigAlgs)); @@ -3603,6 +3630,8 @@ return SSL_set_min_proto_version(ssl, TLS1_2_VERSION) && SSL_set_max_proto_version(ssl, TLS1_3_VERSION) && SSL_set_strict_cipher_list(ssl, kTLS12Ciphers) && + ssl->config->tls13_cipher_list.Init(Span(kTLS13Ciphers), + Span(kTLS13CiphersInGroup)) && SSL_set1_group_ids(ssl, kGroups, std::size(kGroups)) && SSL_set_signing_algorithm_prefs(ssl, kSigAlgs, std::size(kSigAlgs)) && SSL_set_verify_algorithm_prefs(ssl, kSigAlgs, std::size(kSigAlgs)); @@ -3612,14 +3641,27 @@ namespace cnsa202407 { +static const uint16_t kTLS13Ciphers[] = { + SSL_CIPHER_AES_256_GCM_SHA384, + SSL_CIPHER_AES_128_GCM_SHA256, + SSL_CIPHER_CHACHA20_POLY1305_SHA256, +}; +static const bool kTLS13CiphersInGroup[] = { + false, + false, + false, +}; + static int Configure(SSLContext *ctx) { ctx->compliance_policy = ssl_compliance_policy_cnsa_202407; - return 1; + return ctx->tls13_cipher_list.Init(Span(kTLS13Ciphers), + Span(kTLS13CiphersInGroup)); } static int Configure(SSLImpl *ssl) { ssl->config->compliance_policy = ssl_compliance_policy_cnsa_202407; - return 1; + return ssl->config->tls13_cipher_list.Init(Span(kTLS13Ciphers), + Span(kTLS13CiphersInGroup)); } } // namespace cnsa202407 @@ -3644,12 +3686,21 @@ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:" "TLS_RSA_WITH_AES_256_GCM_SHA384"; +static const uint16_t kTLS13Ciphers[] = { + SSL_CIPHER_AES_256_GCM_SHA384, +}; +static const bool kTLS13CiphersInGroup[] = { + false, +}; + static int Configure(SSLContext *ctx) { ctx->compliance_policy = ssl_compliance_policy_cnsa1_202603; return SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) && SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) && SSL_CTX_set_strict_cipher_list(ctx, kTLS12Ciphers) && + ctx->tls13_cipher_list.Init(Span(kTLS13Ciphers), + Span(kTLS13CiphersInGroup)) && SSL_CTX_set1_group_ids(ctx, kGroups, std::size(kGroups)) && SSL_CTX_set_options(ctx, kOptions) && SSL_CTX_set_signing_algorithm_prefs(ctx, kSigAlgs, @@ -3663,6 +3714,8 @@ return SSL_set_min_proto_version(ssl, TLS1_2_VERSION) && SSL_set_max_proto_version(ssl, TLS1_3_VERSION) && SSL_set_strict_cipher_list(ssl, kTLS12Ciphers) && + ssl->config->tls13_cipher_list.Init(Span(kTLS13Ciphers), + Span(kTLS13CiphersInGroup)) && SSL_set1_group_ids(ssl, kGroups, std::size(kGroups)) && SSL_set_options(ssl, kOptions) && SSL_set_signing_algorithm_prefs(ssl, kSigAlgs, std::size(kSigAlgs)) && @@ -3683,11 +3736,20 @@ SSL_SIGN_RSA_PKCS1_SHA384, }; +static const uint16_t kTLS13Ciphers[] = { + SSL_CIPHER_AES_256_GCM_SHA384, +}; +static const bool kTLS13CiphersInGroup[] = { + false, +}; + static int Configure(SSLContext *ctx) { ctx->compliance_policy = ssl_compliance_policy_cnsa2_202603; return SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION) && SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) && + ctx->tls13_cipher_list.Init(Span(kTLS13Ciphers), + Span(kTLS13CiphersInGroup)) && SSL_CTX_set1_group_ids(ctx, kGroups, std::size(kGroups)) && SSL_CTX_set_signing_algorithm_prefs(ctx, kSigAlgs, std::size(kSigAlgs)) && @@ -3699,6 +3761,8 @@ return SSL_set_min_proto_version(ssl, TLS1_3_VERSION) && SSL_set_max_proto_version(ssl, TLS1_3_VERSION) && + ssl->config->tls13_cipher_list.Init(Span(kTLS13Ciphers), + Span(kTLS13CiphersInGroup)) && SSL_set1_group_ids(ssl, kGroups, std::size(kGroups)) && SSL_set_signing_algorithm_prefs(ssl, kSigAlgs, std::size(kSigAlgs)) && SSL_set_verify_algorithm_prefs(ssl, kSigAlgs, std::size(kSigAlgs));
diff --git a/ssl/test/runner/cipher_suite_tests.go b/ssl/test/runner/cipher_suite_tests.go index 4880d37..9f35445 100644 --- a/ssl/test/runner/cipher_suite_tests.go +++ b/ssl/test/runner/cipher_suite_tests.go
@@ -283,6 +283,22 @@ }) testCases = append(testCases, testCase{ + name: "UnsupportedCipherSuite-TLS13", + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{TLS_AES_128_GCM_SHA256}, + Bugs: ProtocolBugs{ + IgnorePeerCipherPreferences: true, + }, + }, + // This compliance policy causes the client to advertise only + // TLS_AES_256_GCM_SHA384. + flags: []string{"-wpa-202304"}, + shouldFail: true, + expectedError: ":WRONG_CIPHER_RETURNED:", + }) + + testCases = append(testCases, testCase{ name: "ServerHelloBogusCipher", config: Config{ MaxVersion: VersionTLS12,
diff --git a/ssl/tls13_client.cc b/ssl/tls13_client.cc index f134ebb..f0caf83 100644 --- a/ssl/tls13_client.cc +++ b/ssl/tls13_client.cc
@@ -204,15 +204,12 @@ return ssl_hs_error; } - // The cipher suite must be one we offered. We currently offer all supported - // TLS 1.3 ciphers unless policy controls limited it. So we check the version - // and that it's ok per policy. + // The cipher suite must be one we offered, and supported for the version. const SSL_CIPHER *cipher = SSL_get_cipher_by_value(server_hello.cipher_suite); if (cipher == nullptr || SSL_CIPHER_get_min_version(cipher) > ssl_protocol_version(ssl) || SSL_CIPHER_get_max_version(cipher) < ssl_protocol_version(ssl) || - !ssl_tls13_cipher_meets_policy(SSL_CIPHER_get_protocol_id(cipher), - ssl->config->compliance_policy)) { + !ssl->config->tls13_cipher_list.Contains(server_hello.cipher_suite)) { OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED); ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); return ssl_hs_error;
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc index 6d2031f..8119c3b 100644 --- a/ssl/tls13_server.cc +++ b/ssl/tls13_server.cc
@@ -136,18 +136,6 @@ return 1; } -static const SSL_CIPHER *choose_tls13_cipher( - const SSLImpl *ssl, const SSL_CLIENT_HELLO *client_hello) { - CBS cipher_suites; - CBS_init(&cipher_suites, client_hello->cipher_suites, - client_hello->cipher_suites_len); - - const uint16_t version = ssl_protocol_version(ssl); - - return ssl_choose_tls13_cipher(cipher_suites, EVP_has_aes_hardware(), version, - ssl->config->compliance_policy); -} - static bool add_new_session_tickets(SSL_HANDSHAKE *hs, bool *out_sent_tickets) { SSLImpl *const ssl = hs->ssl; if ( // If the client doesn't accept resumption with PSK_DHE_KE, don't send a @@ -388,7 +376,12 @@ } // Negotiate the cipher suite. This must happen before negotiating PSKs. - hs->new_cipher = choose_tls13_cipher(ssl, &client_hello); + CBS client_cipher_list; + CBS_init(&client_cipher_list, client_hello.cipher_suites, + client_hello.cipher_suites_len); + hs->new_cipher = ssl->config->tls13_cipher_list.ChooseCipher( + &client_cipher_list, /*prioritize_client_pref=*/false, + ssl_protocol_version(ssl), SSL_kGENERIC, SSL_aGENERIC); if (hs->new_cipher == nullptr) { OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER); ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);