diff options
Diffstat (limited to 'src/ssl/s3_both.cc')
-rw-r--r-- | src/ssl/s3_both.cc | 70 |
1 files changed, 70 insertions, 0 deletions
diff --git a/src/ssl/s3_both.cc b/src/ssl/s3_both.cc index 27e9454f..842ec676 100644 --- a/src/ssl/s3_both.cc +++ b/src/ssl/s3_both.cc @@ -116,6 +116,8 @@ #include <limits.h> #include <string.h> +#include <tuple> + #include <openssl/buf.h> #include <openssl/bytestring.h> #include <openssl/err.h> @@ -652,4 +654,72 @@ void ssl3_next_message(SSL *ssl) { } } +// CipherScorer produces a "score" for each possible cipher suite offered by +// the client. +class CipherScorer { + public: + CipherScorer(uint16_t group_id) + : aes_is_fine_(EVP_has_aes_hardware()), + security_128_is_fine_(group_id != SSL_CURVE_CECPQ2 && + group_id != SSL_CURVE_CECPQ2b) {} + + typedef std::tuple<bool, bool, bool> Score; + + // MinScore returns a |Score| that will compare less than the score of all + // cipher suites. + Score MinScore() const { + return Score(false, false, false); + } + + Score Evaluate(const SSL_CIPHER *a) const { + return Score( + // Something is always preferable to nothing. + true, + // Either 128-bit is fine, or 256-bit is preferred. + security_128_is_fine_ || a->algorithm_enc != SSL_AES128GCM, + // Either AES is fine, or else ChaCha20 is preferred. + aes_is_fine_ || a->algorithm_enc == SSL_CHACHA20POLY1305); + } + + private: + const bool aes_is_fine_; + const bool security_128_is_fine_; +}; + +const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, uint16_t version, + uint16_t group_id) { + if (CBS_len(&cipher_suites) % 2 != 0) { + return nullptr; + } + + const SSL_CIPHER *best = nullptr; + CipherScorer scorer(group_id); + CipherScorer::Score best_score = scorer.MinScore(); + + 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; + } + + 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 |