blob: 7ab5054592f1d73f3250587f9a2ee8cf8aad9c7c [file] [log] [blame]
/* Copyright (c) 2014, Google Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <algorithm>
#include <limits>
#include <string>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include <openssl/aead.h>
#include <openssl/base64.h>
#include <openssl/bytestring.h>
#include <openssl/bio.h>
#include <openssl/cipher.h>
#include <openssl/crypto.h>
#include <openssl/curve25519.h>
#include <openssl/err.h>
#include <openssl/hmac.h>
#include <openssl/hpke.h>
#include <openssl/pem.h>
#include <openssl/sha.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/x509.h>
#include "internal.h"
#include "../crypto/internal.h"
#include "../crypto/test/test_util.h"
#if defined(OPENSSL_WINDOWS)
// Windows defines struct timeval in winsock2.h.
OPENSSL_MSVC_PRAGMA(warning(push, 3))
#include <winsock2.h>
OPENSSL_MSVC_PRAGMA(warning(pop))
#else
#include <sys/time.h>
#endif
#if defined(OPENSSL_THREADS)
#include <thread>
#endif
BSSL_NAMESPACE_BEGIN
namespace {
#define TRACED_CALL(code) \
do { \
SCOPED_TRACE("<- called from here"); \
code; \
if (::testing::Test::HasFatalFailure()) { \
return; \
} \
} while (false)
struct VersionParam {
uint16_t version;
enum { is_tls, is_dtls } ssl_method;
const char name[8];
};
static const size_t kTicketKeyLen = 48;
static const VersionParam kAllVersions[] = {
{TLS1_VERSION, VersionParam::is_tls, "TLS1"},
{TLS1_1_VERSION, VersionParam::is_tls, "TLS1_1"},
{TLS1_2_VERSION, VersionParam::is_tls, "TLS1_2"},
{TLS1_3_VERSION, VersionParam::is_tls, "TLS1_3"},
{DTLS1_VERSION, VersionParam::is_dtls, "DTLS1"},
{DTLS1_2_VERSION, VersionParam::is_dtls, "DTLS1_2"},
};
struct ExpectedCipher {
unsigned long id;
int in_group_flag;
};
struct CipherTest {
// The rule string to apply.
const char *rule;
// The list of expected ciphers, in order.
std::vector<ExpectedCipher> expected;
// True if this cipher list should fail in strict mode.
bool strict_fail;
};
struct CurveTest {
// The rule string to apply.
const char *rule;
// The list of expected curves, in order.
std::vector<uint16_t> expected;
};
template <typename T>
class UnownedSSLExData {
public:
UnownedSSLExData() {
index_ = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
}
T *Get(const SSL *ssl) {
return index_ < 0 ? nullptr
: static_cast<T *>(SSL_get_ex_data(ssl, index_));
}
bool Set(SSL *ssl, T *t) {
return index_ >= 0 && SSL_set_ex_data(ssl, index_, t);
}
private:
int index_;
};
static const CipherTest kCipherTests[] = {
// Selecting individual ciphers should work.
{
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
false,
},
// + reorders selected ciphers to the end, keeping their relative order.
{
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"+aRSA",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
false,
},
// ! banishes ciphers from future selections.
{
"!aRSA:"
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
},
false,
},
// Multiple masks can be ANDed in a single rule.
{
"kRSA+AESGCM+AES128",
{
{TLS1_CK_RSA_WITH_AES_128_GCM_SHA256, 0},
},
false,
},
// - removes selected ciphers, but preserves their order for future
// selections. Select AES_128_GCM, but order the key exchanges RSA,
// ECDHE_RSA.
{
"ALL:-kECDHE:"
"-kRSA:-ALL:"
"AESGCM+AES128+aRSA",
{
{TLS1_CK_RSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
false,
},
// Unknown selectors are no-ops, except in strict mode.
{
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"BOGUS1",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
true,
},
// Unknown selectors are no-ops, except in strict mode.
{
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"-BOGUS2:+BOGUS3:!BOGUS4",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
true,
},
// Square brackets specify equi-preference groups.
{
"[ECDHE-ECDSA-CHACHA20-POLY1305|ECDHE-ECDSA-AES128-GCM-SHA256]:"
"[ECDHE-RSA-CHACHA20-POLY1305]:"
"ECDHE-RSA-AES128-GCM-SHA256",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 1},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
false,
},
// Standard names may be used instead of OpenSSL names.
{
"[TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256|"
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256]:"
"[TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256]:"
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 1},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
false,
},
// @STRENGTH performs a stable strength-sort of the selected ciphers and
// only the selected ciphers.
{
// To simplify things, banish all but {ECDHE_RSA,RSA} x
// {CHACHA20,AES_256_CBC,AES_128_CBC} x SHA1.
"!AESGCM:!3DES:"
// Order some ciphers backwards by strength.
"ALL:-CHACHA20:-AES256:-AES128:-ALL:"
// Select ECDHE ones and sort them by strength. Ties should resolve
// based on the order above.
"kECDHE:@STRENGTH:-ALL:"
// Now bring back everything uses RSA. ECDHE_RSA should be first, sorted
// by strength. Then RSA, backwards by strength.
"aRSA",
{
{TLS1_CK_ECDHE_RSA_WITH_AES_256_CBC_SHA, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_CBC_SHA, 0},
{TLS1_CK_RSA_WITH_AES_128_SHA, 0},
{TLS1_CK_RSA_WITH_AES_256_SHA, 0},
},
false,
},
// Additional masks after @STRENGTH get silently discarded.
//
// TODO(davidben): Make this an error. If not silently discarded, they get
// interpreted as + opcodes which are very different.
{
"ECDHE-RSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES256-GCM-SHA384:"
"@STRENGTH+AES256",
{
{TLS1_CK_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
false,
},
{
"ECDHE-RSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES256-GCM-SHA384:"
"@STRENGTH+AES256:"
"ECDHE-RSA-CHACHA20-POLY1305",
{
{TLS1_CK_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
},
false,
},
// Exact ciphers may not be used in multi-part rules; they are treated
// as unknown aliases.
{
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"!ECDHE-RSA-AES128-GCM-SHA256+RSA:"
"!ECDSA+ECDHE-ECDSA-AES128-GCM-SHA256",
{
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
true,
},
// SSLv3 matches everything that existed before TLS 1.2.
{
"AES128-SHA:ECDHE-RSA-AES128-GCM-SHA256:!SSLv3",
{
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
false,
},
// TLSv1.2 matches everything added in TLS 1.2.
{
"AES128-SHA:ECDHE-RSA-AES128-GCM-SHA256:!TLSv1.2",
{
{TLS1_CK_RSA_WITH_AES_128_SHA, 0},
},
false,
},
// The two directives have no intersection. But each component is valid, so
// even in strict mode it is accepted.
{
"AES128-SHA:ECDHE-RSA-AES128-GCM-SHA256:!TLSv1.2+SSLv3",
{
{TLS1_CK_RSA_WITH_AES_128_SHA, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
false,
},
// Spaces, semi-colons and commas are separators.
{
"AES128-SHA: ECDHE-RSA-AES128-GCM-SHA256 AES256-SHA ,ECDHE-ECDSA-AES128-GCM-SHA256 ; AES128-GCM-SHA256",
{
{TLS1_CK_RSA_WITH_AES_128_SHA, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_RSA_WITH_AES_256_SHA, 0},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_RSA_WITH_AES_128_GCM_SHA256, 0},
},
// …but not in strict mode.
true,
},
};
static const char *kBadRules[] = {
// Invalid brackets.
"[ECDHE-RSA-CHACHA20-POLY1305|ECDHE-RSA-AES128-GCM-SHA256",
"RSA]",
"[[RSA]]",
// Operators inside brackets.
"[+RSA]",
// Unknown directive.
"@BOGUS",
// Empty cipher lists error at SSL_CTX_set_cipher_list.
"",
"BOGUS",
// COMPLEMENTOFDEFAULT is empty.
"COMPLEMENTOFDEFAULT",
// Invalid command.
"?BAR",
// Special operators are not allowed if groups are used.
"[ECDHE-RSA-CHACHA20-POLY1305|ECDHE-RSA-AES128-GCM-SHA256]:+FOO",
"[ECDHE-RSA-CHACHA20-POLY1305|ECDHE-RSA-AES128-GCM-SHA256]:!FOO",
"[ECDHE-RSA-CHACHA20-POLY1305|ECDHE-RSA-AES128-GCM-SHA256]:-FOO",
"[ECDHE-RSA-CHACHA20-POLY1305|ECDHE-RSA-AES128-GCM-SHA256]:@STRENGTH",
// Opcode supplied, but missing selector.
"+",
// Spaces are forbidden in equal-preference groups.
"[AES128-SHA | AES128-SHA256]",
};
static const char *kMustNotIncludeNull[] = {
"ALL",
"DEFAULT",
"HIGH",
"FIPS",
"SHA",
"SHA1",
"RSA",
"SSLv3",
"TLSv1",
"TLSv1.2",
};
static const CurveTest kCurveTests[] = {
{
"P-256",
{ SSL_CURVE_SECP256R1 },
},
{
"P-256:CECPQ2",
{ SSL_CURVE_SECP256R1, SSL_CURVE_CECPQ2 },
},
{
"P-256:P-384:P-521:X25519",
{
SSL_CURVE_SECP256R1,
SSL_CURVE_SECP384R1,
SSL_CURVE_SECP521R1,
SSL_CURVE_X25519,
},
},
{
"prime256v1:secp384r1:secp521r1:x25519",
{
SSL_CURVE_SECP256R1,
SSL_CURVE_SECP384R1,
SSL_CURVE_SECP521R1,
SSL_CURVE_X25519,
},
},
};
static const char *kBadCurvesLists[] = {
"",
":",
"::",
"P-256::X25519",
"RSA:P-256",
"P-256:RSA",
"X25519:P-256:",
":X25519:P-256",
};
static std::string CipherListToString(SSL_CTX *ctx) {
bool in_group = false;
std::string ret;
const STACK_OF(SSL_CIPHER) *ciphers = SSL_CTX_get_ciphers(ctx);
for (size_t i = 0; i < sk_SSL_CIPHER_num(ciphers); i++) {
const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(ciphers, i);
if (!in_group && SSL_CTX_cipher_in_group(ctx, i)) {
ret += "\t[\n";
in_group = true;
}
ret += "\t";
if (in_group) {
ret += " ";
}
ret += SSL_CIPHER_get_name(cipher);
ret += "\n";
if (in_group && !SSL_CTX_cipher_in_group(ctx, i)) {
ret += "\t]\n";
in_group = false;
}
}
return ret;
}
static bool CipherListsEqual(SSL_CTX *ctx,
const std::vector<ExpectedCipher> &expected) {
const STACK_OF(SSL_CIPHER) *ciphers = SSL_CTX_get_ciphers(ctx);
if (sk_SSL_CIPHER_num(ciphers) != expected.size()) {
return false;
}
for (size_t i = 0; i < expected.size(); i++) {
const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(ciphers, i);
if (expected[i].id != SSL_CIPHER_get_id(cipher) ||
expected[i].in_group_flag != !!SSL_CTX_cipher_in_group(ctx, i)) {
return false;
}
}
return true;
}
TEST(GrowableArrayTest, Resize) {
GrowableArray<size_t> array;
ASSERT_TRUE(array.empty());
EXPECT_EQ(array.size(), 0u);
ASSERT_TRUE(array.Push(42));
ASSERT_TRUE(!array.empty());
EXPECT_EQ(array.size(), 1u);
// Force a resize operation to occur
for (size_t i = 0; i < 16; i++) {
ASSERT_TRUE(array.Push(i + 1));
}
EXPECT_EQ(array.size(), 17u);
// Verify that expected values are still contained in array
for (size_t i = 0; i < array.size(); i++) {
EXPECT_EQ(array[i], i == 0 ? 42 : i);
}
}
TEST(GrowableArrayTest, MoveConstructor) {
GrowableArray<size_t> array;
for (size_t i = 0; i < 100; i++) {
ASSERT_TRUE(array.Push(i));
}
GrowableArray<size_t> array_moved(std::move(array));
for (size_t i = 0; i < 100; i++) {
EXPECT_EQ(array_moved[i], i);
}
}
TEST(GrowableArrayTest, GrowableArrayContainingGrowableArrays) {
// Representative example of a struct that contains a GrowableArray.
struct TagAndArray {
size_t tag;
GrowableArray<size_t> array;
};
GrowableArray<TagAndArray> array;
for (size_t i = 0; i < 100; i++) {
TagAndArray elem;
elem.tag = i;
for (size_t j = 0; j < i; j++) {
ASSERT_TRUE(elem.array.Push(j));
}
ASSERT_TRUE(array.Push(std::move(elem)));
}
EXPECT_EQ(array.size(), static_cast<size_t>(100));
GrowableArray<TagAndArray> array_moved(std::move(array));
EXPECT_EQ(array_moved.size(), static_cast<size_t>(100));
size_t count = 0;
for (const TagAndArray &elem : array_moved) {
// Test the square bracket operator returns the same value as iteration.
EXPECT_EQ(&elem, &array_moved[count]);
EXPECT_EQ(elem.tag, count);
EXPECT_EQ(elem.array.size(), count);
for (size_t j = 0; j < count; j++) {
EXPECT_EQ(elem.array[j], j);
}
count++;
}
}
TEST(SSLTest, CipherRules) {
for (const CipherTest &t : kCipherTests) {
SCOPED_TRACE(t.rule);
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
// Test lax mode.
ASSERT_TRUE(SSL_CTX_set_cipher_list(ctx.get(), t.rule));
EXPECT_TRUE(CipherListsEqual(ctx.get(), t.expected))
<< "Cipher rule evaluated to:\n"
<< CipherListToString(ctx.get());
// Test strict mode.
if (t.strict_fail) {
EXPECT_FALSE(SSL_CTX_set_strict_cipher_list(ctx.get(), t.rule));
} else {
ASSERT_TRUE(SSL_CTX_set_strict_cipher_list(ctx.get(), t.rule));
EXPECT_TRUE(CipherListsEqual(ctx.get(), t.expected))
<< "Cipher rule evaluated to:\n"
<< CipherListToString(ctx.get());
}
}
for (const char *rule : kBadRules) {
SCOPED_TRACE(rule);
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
EXPECT_FALSE(SSL_CTX_set_cipher_list(ctx.get(), rule));
ERR_clear_error();
}
for (const char *rule : kMustNotIncludeNull) {
SCOPED_TRACE(rule);
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
ASSERT_TRUE(SSL_CTX_set_strict_cipher_list(ctx.get(), rule));
for (const SSL_CIPHER *cipher : SSL_CTX_get_ciphers(ctx.get())) {
EXPECT_NE(NID_undef, SSL_CIPHER_get_cipher_nid(cipher));
}
}
}
TEST(SSLTest, CurveRules) {
for (const CurveTest &t : kCurveTests) {
SCOPED_TRACE(t.rule);
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
ASSERT_TRUE(SSL_CTX_set1_curves_list(ctx.get(), t.rule));
ASSERT_EQ(t.expected.size(), ctx->supported_group_list.size());
for (size_t i = 0; i < t.expected.size(); i++) {
EXPECT_EQ(t.expected[i], ctx->supported_group_list[i]);
}
}
for (const char *rule : kBadCurvesLists) {
SCOPED_TRACE(rule);
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
EXPECT_FALSE(SSL_CTX_set1_curves_list(ctx.get(), rule));
ERR_clear_error();
}
}
// kOpenSSLSession is a serialized SSL_SESSION.
static const char kOpenSSLSession[] =
"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"
"i4gv7Y5oliyntgMBAQA=";
// kCustomSession is a custom serialized SSL_SESSION generated by
// filling in missing fields from |kOpenSSLSession|. This includes
// providing |peer_sha256|, so |peer| is not serialized.
static const char kCustomSession[] =
"MIIBZAIBAQICAwMEAsAvBCAG5Q1ndq4Yfmbeo1zwLkNRKmCXGdNgWvGT3cskV0yQ"
"kAQwJlrlzkAWBOWiLj/jJ76D7l+UXoizP2KI2C7I2FccqMmIfFmmkUy32nIJ0mZH"
"IWoJoQYCBFRDO46iBAICASykAwQBAqUDAgEUqAcEBXdvcmxkqQUCAwGJwKqBpwSB"
"pBwUQvoeOk0Kg36SYTcLEkXqKwOBfF9vE4KX0NxeLwjcDTpsuh3qXEaZ992r1N38"
"VDcyS6P7I6HBYN9BsNHM362zZnY27GpTw+Kwd751CLoXFPoaMOe57dbBpXoro6Pd"
"3BTbf/Tzr88K06yEOTDKPNj3+inbMaVigtK4PLyPq+Topyzvx9USFgRvyuoxn0Hg"
"b+R0A3j6SLRuyOdAi4gv7Y5oliynrSIEIAYGBgYGBgYGBgYGBgYGBgYGBgYGBgYG"
"BgYGBgYGBgYGrgMEAQevAwQBBLADBAEF";
// kBoringSSLSession is a serialized SSL_SESSION generated from bssl client.
static const char kBoringSSLSession[] =
"MIIRwQIBAQICAwMEAsAvBCDdoGxGK26mR+8lM0uq6+k9xYuxPnwAjpcF9n0Yli9R"
"kQQwbyshfWhdi5XQ1++7n2L1qqrcVlmHBPpr6yknT/u4pUrpQB5FZ7vqvNn8MdHf"
"9rWgoQYCBFXgs7uiBAICHCCjggR6MIIEdjCCA16gAwIBAgIIf+yfD7Y6UicwDQYJ"
"KoZIhvcNAQELBQAwSTELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMx"
"JTAjBgNVBAMTHEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzIwHhcNMTUwODEy"
"MTQ1MzE1WhcNMTUxMTEwMDAwMDAwWjBoMQswCQYDVQQGEwJVUzETMBEGA1UECAwK"
"Q2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzETMBEGA1UECgwKR29v"
"Z2xlIEluYzEXMBUGA1UEAwwOd3d3Lmdvb2dsZS5jb20wggEiMA0GCSqGSIb3DQEB"
"AQUAA4IBDwAwggEKAoIBAQC0MeG5YGQ0t+IeJeoneP/PrhEaieibeKYkbKVLNZpo"
"PLuBinvhkXZo3DC133NpCBpy6ZktBwamqyixAyuk/NU6OjgXqwwxfQ7di1AInLIU"
"792c7hFyNXSUCG7At8Ifi3YwBX9Ba6u/1d6rWTGZJrdCq3QU11RkKYyTq2KT5mce"
"Tv9iGKqSkSTlp8puy/9SZ/3DbU3U+BuqCFqeSlz7zjwFmk35acdCilpJlVDDN5C/"
"RCh8/UKc8PaL+cxlt531qoTENvYrflBno14YEZlCBZsPiFeUSILpKEj3Ccwhy0eL"
"EucWQ72YZU8mUzXBoXGn0zA0crFl5ci/2sTBBGZsylNBAgMBAAGjggFBMIIBPTAd"
"BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGQYDVR0RBBIwEIIOd3d3Lmdv"
"b2dsZS5jb20waAYIKwYBBQUHAQEEXDBaMCsGCCsGAQUFBzAChh9odHRwOi8vcGtp"
"Lmdvb2dsZS5jb20vR0lBRzIuY3J0MCsGCCsGAQUFBzABhh9odHRwOi8vY2xpZW50"
"czEuZ29vZ2xlLmNvbS9vY3NwMB0GA1UdDgQWBBS/bzHxcE73Q4j3slC4BLbMtLjG"
"GjAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFErdBhYbvPZotXb1gba7Yhq6WoEv"
"MBcGA1UdIAQQMA4wDAYKKwYBBAHWeQIFATAwBgNVHR8EKTAnMCWgI6Ahhh9odHRw"
"Oi8vcGtpLmdvb2dsZS5jb20vR0lBRzIuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAb"
"qdWPZEHk0X7iKPCTHL6S3w6q1eR67goxZGFSM1lk1hjwyu7XcLJuvALVV9uY3ovE"
"kQZSHwT+pyOPWQhsSjO+1GyjvCvK/CAwiUmBX+bQRGaqHsRcio7xSbdVcajQ3bXd"
"X+s0WdbOpn6MStKAiBVloPlSxEI8pxY6x/BBCnTIk/+DMB17uZlOjG3vbAnkDkP+"
"n0OTucD9sHV7EVj9XUxi51nOfNBCN/s7lpUjDS/NJ4k3iwOtbCPswiot8vLO779a"
"f07vR03r349Iz/KTzk95rlFtX0IU+KYNxFNsanIXZ+C9FYGRXkwhHcvFb4qMUB1y"
"TTlM80jBMOwyjZXmjRAhpAIEAKUDAgEUqQUCAwGJwKqBpwSBpOgebbmn9NRUtMWH"
"+eJpqA5JLMFSMCChOsvKey3toBaCNGU7HfAEiiXNuuAdCBoK262BjQc2YYfqFzqH"
"zuppopXCvhohx7j/tnCNZIMgLYt/O9SXK2RYI5z8FhCCHvB4CbD5G0LGl5EFP27s"
"Jb6S3aTTYPkQe8yZSlxevg6NDwmTogLO9F7UUkaYmVcMQhzssEE2ZRYNwSOU6KjE"
"0Yj+8fAiBtbQriIEIN2L8ZlpaVrdN5KFNdvcmOxJu81P8q53X55xQyGTnGWwsgMC"
"ARezggvvMIIEdjCCA16gAwIBAgIIf+yfD7Y6UicwDQYJKoZIhvcNAQELBQAwSTEL"
"MAkGA1UEBhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2ds"
"ZSBJbnRlcm5ldCBBdXRob3JpdHkgRzIwHhcNMTUwODEyMTQ1MzE1WhcNMTUxMTEw"
"MDAwMDAwWjBoMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG"
"A1UEBwwNTW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEXMBUGA1UE"
"AwwOd3d3Lmdvb2dsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB"
"AQC0MeG5YGQ0t+IeJeoneP/PrhEaieibeKYkbKVLNZpoPLuBinvhkXZo3DC133Np"
"CBpy6ZktBwamqyixAyuk/NU6OjgXqwwxfQ7di1AInLIU792c7hFyNXSUCG7At8If"
"i3YwBX9Ba6u/1d6rWTGZJrdCq3QU11RkKYyTq2KT5mceTv9iGKqSkSTlp8puy/9S"
"Z/3DbU3U+BuqCFqeSlz7zjwFmk35acdCilpJlVDDN5C/RCh8/UKc8PaL+cxlt531"
"qoTENvYrflBno14YEZlCBZsPiFeUSILpKEj3Ccwhy0eLEucWQ72YZU8mUzXBoXGn"
"0zA0crFl5ci/2sTBBGZsylNBAgMBAAGjggFBMIIBPTAdBgNVHSUEFjAUBggrBgEF"
"BQcDAQYIKwYBBQUHAwIwGQYDVR0RBBIwEIIOd3d3Lmdvb2dsZS5jb20waAYIKwYB"
"BQUHAQEEXDBaMCsGCCsGAQUFBzAChh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lB"
"RzIuY3J0MCsGCCsGAQUFBzABhh9odHRwOi8vY2xpZW50czEuZ29vZ2xlLmNvbS9v"
"Y3NwMB0GA1UdDgQWBBS/bzHxcE73Q4j3slC4BLbMtLjGGjAMBgNVHRMBAf8EAjAA"
"MB8GA1UdIwQYMBaAFErdBhYbvPZotXb1gba7Yhq6WoEvMBcGA1UdIAQQMA4wDAYK"
"KwYBBAHWeQIFATAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vcGtpLmdvb2dsZS5j"
"b20vR0lBRzIuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAbqdWPZEHk0X7iKPCTHL6S"
"3w6q1eR67goxZGFSM1lk1hjwyu7XcLJuvALVV9uY3ovEkQZSHwT+pyOPWQhsSjO+"
"1GyjvCvK/CAwiUmBX+bQRGaqHsRcio7xSbdVcajQ3bXdX+s0WdbOpn6MStKAiBVl"
"oPlSxEI8pxY6x/BBCnTIk/+DMB17uZlOjG3vbAnkDkP+n0OTucD9sHV7EVj9XUxi"
"51nOfNBCN/s7lpUjDS/NJ4k3iwOtbCPswiot8vLO779af07vR03r349Iz/KTzk95"
"rlFtX0IU+KYNxFNsanIXZ+C9FYGRXkwhHcvFb4qMUB1yTTlM80jBMOwyjZXmjRAh"
"MIID8DCCAtigAwIBAgIDAjqDMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlVT"
"MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i"
"YWwgQ0EwHhcNMTMwNDA1MTUxNTU2WhcNMTYxMjMxMjM1OTU5WjBJMQswCQYDVQQG"
"EwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy"
"bmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB"
"AJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP"
"VaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv"
"h8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE"
"ahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ"
"EASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC"
"DTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB5zCB5DAfBgNVHSMEGDAWgBTAephojYn7"
"qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wDgYD"
"VR0PAQH/BAQDAgEGMC4GCCsGAQUFBwEBBCIwIDAeBggrBgEFBQcwAYYSaHR0cDov"
"L2cuc3ltY2QuY29tMBIGA1UdEwEB/wQIMAYBAf8CAQAwNQYDVR0fBC4wLDAqoCig"
"JoYkaHR0cDovL2cuc3ltY2IuY29tL2NybHMvZ3RnbG9iYWwuY3JsMBcGA1UdIAQQ"
"MA4wDAYKKwYBBAHWeQIFATANBgkqhkiG9w0BAQsFAAOCAQEAqvqpIM1qZ4PtXtR+"
"3h3Ef+AlBgDFJPupyC1tft6dgmUsgWM0Zj7pUsIItMsv91+ZOmqcUHqFBYx90SpI"
"hNMJbHzCzTWf84LuUt5oX+QAihcglvcpjZpNy6jehsgNb1aHA30DP9z6eX0hGfnI"
"Oi9RdozHQZJxjyXON/hKTAAj78Q1EK7gI4BzfE00LshukNYQHpmEcxpw8u1VDu4X"
"Bupn7jLrLN1nBz/2i8Jw3lsA5rsb0zYaImxssDVCbJAJPZPpZAkiDoUGn8JzIdPm"
"X4DkjYUiOnMDsWCOrmji9D6X52ASCWg23jrW4kOVWzeBkoEfu43XrVJkFleW2V40"
"fsg12DCCA30wggLmoAMCAQICAxK75jANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQG"
"EwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUg"
"Q2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTAyMDUyMTA0MDAwMFoXDTE4MDgyMTA0"
"MDAwMFowQjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xGzAZ"
"BgNVBAMTEkdlb1RydXN0IEdsb2JhbCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP"
"ADCCAQoCggEBANrMGGMw/fQXIxpWflvfPGw45HG3eJHUvKHYTPioQ7YD6U0hBwiI"
"2lgvZjkpvQV4i5046AW3an5xpObEYKaw74DkiSgPniXW7YPzraaRx5jJQhg1FJ2t"
"mEaSLk/K8YdDwRaVVy1Q74ktgHpXrfLuX2vSAI25FPgUFTXZwEaje3LIkb/JVSvN"
"0Jc+nCZkzN/Ogxlxyk7m1NV7qRnNVd7I7NJeOFPlXE+MLf5QIzb8ZubLjqQ5GQC3"
"lQI5kQsO/jgu0R0FmvZNPm8PBx2vLB6PYDni+jZTEznUXiYr2z2oFL0y6xgDKFIE"
"ceWrMz3hOLsHNoRinHnqFjD0X8Ar6HFr5PkCAwEAAaOB8DCB7TAfBgNVHSMEGDAW"
"gBRI5mj5K9KylddH2CMgEE8zmJCf1DAdBgNVHQ4EFgQUwHqYaI2J+6sFZAwRfap9"
"ZbjKzE4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMw"
"MTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9zZWN1cmVjYS5j"
"cmwwTgYDVR0gBEcwRTBDBgRVHSAAMDswOQYIKwYBBQUHAgEWLWh0dHBzOi8vd3d3"
"Lmdlb3RydXN0LmNvbS9yZXNvdXJjZXMvcmVwb3NpdG9yeTANBgkqhkiG9w0BAQUF"
"AAOBgQB24RJuTksWEoYwBrKBCM/wCMfHcX5m7sLt1Dsf//DwyE7WQziwuTB9GNBV"
"g6JqyzYRnOhIZqNtf7gT1Ef+i1pcc/yu2RsyGTirlzQUqpbS66McFAhJtrvlke+D"
"NusdVm/K2rxzY5Dkf3s+Iss9B+1fOHSc4wNQTqGvmO5h8oQ/Eg==";
// kBadSessionExtraField is a custom serialized SSL_SESSION generated by replacing
// the final (optional) element of |kCustomSession| with tag number 99.
static const char kBadSessionExtraField[] =
"MIIBdgIBAQICAwMEAsAvBCAG5Q1ndq4Yfmbeo1zwLkNRKmCXGdNgWvGT3cskV0yQ"
"kAQwJlrlzkAWBOWiLj/jJ76D7l+UXoizP2KI2C7I2FccqMmIfFmmkUy32nIJ0mZH"
"IWoJoQYCBFRDO46iBAICASykAwQBAqUDAgEUphAEDnd3dy5nb29nbGUuY29tqAcE"
"BXdvcmxkqQUCAwGJwKqBpwSBpBwUQvoeOk0Kg36SYTcLEkXqKwOBfF9vE4KX0Nxe"
"LwjcDTpsuh3qXEaZ992r1N38VDcyS6P7I6HBYN9BsNHM362zZnY27GpTw+Kwd751"
"CLoXFPoaMOe57dbBpXoro6Pd3BTbf/Tzr88K06yEOTDKPNj3+inbMaVigtK4PLyP"
"q+Topyzvx9USFgRvyuoxn0Hgb+R0A3j6SLRuyOdAi4gv7Y5oliynrSIEIAYGBgYG"
"BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGrgMEAQevAwQBBOMDBAEF";
// kBadSessionVersion is a custom serialized SSL_SESSION generated by replacing
// the version of |kCustomSession| with 2.
static const char kBadSessionVersion[] =
"MIIBdgIBAgICAwMEAsAvBCAG5Q1ndq4Yfmbeo1zwLkNRKmCXGdNgWvGT3cskV0yQ"
"kAQwJlrlzkAWBOWiLj/jJ76D7l+UXoizP2KI2C7I2FccqMmIfFmmkUy32nIJ0mZH"
"IWoJoQYCBFRDO46iBAICASykAwQBAqUDAgEUphAEDnd3dy5nb29nbGUuY29tqAcE"
"BXdvcmxkqQUCAwGJwKqBpwSBpBwUQvoeOk0Kg36SYTcLEkXqKwOBfF9vE4KX0Nxe"
"LwjcDTpsuh3qXEaZ992r1N38VDcyS6P7I6HBYN9BsNHM362zZnY27GpTw+Kwd751"
"CLoXFPoaMOe57dbBpXoro6Pd3BTbf/Tzr88K06yEOTDKPNj3+inbMaVigtK4PLyP"
"q+Topyzvx9USFgRvyuoxn0Hgb+R0A3j6SLRuyOdAi4gv7Y5oliynrSIEIAYGBgYG"
"BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGrgMEAQevAwQBBLADBAEF";
// kBadSessionTrailingData is a custom serialized SSL_SESSION with trailing data
// appended.
static const char kBadSessionTrailingData[] =
"MIIBdgIBAQICAwMEAsAvBCAG5Q1ndq4Yfmbeo1zwLkNRKmCXGdNgWvGT3cskV0yQ"
"kAQwJlrlzkAWBOWiLj/jJ76D7l+UXoizP2KI2C7I2FccqMmIfFmmkUy32nIJ0mZH"
"IWoJoQYCBFRDO46iBAICASykAwQBAqUDAgEUphAEDnd3dy5nb29nbGUuY29tqAcE"
"BXdvcmxkqQUCAwGJwKqBpwSBpBwUQvoeOk0Kg36SYTcLEkXqKwOBfF9vE4KX0Nxe"
"LwjcDTpsuh3qXEaZ992r1N38VDcyS6P7I6HBYN9BsNHM362zZnY27GpTw+Kwd751"
"CLoXFPoaMOe57dbBpXoro6Pd3BTbf/Tzr88K06yEOTDKPNj3+inbMaVigtK4PLyP"
"q+Topyzvx9USFgRvyuoxn0Hgb+R0A3j6SLRuyOdAi4gv7Y5oliynrSIEIAYGBgYG"
"BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGrgMEAQevAwQBBLADBAEFAAAA";
static bool DecodeBase64(std::vector<uint8_t> *out, const char *in) {
size_t len;
if (!EVP_DecodedLength(&len, strlen(in))) {
fprintf(stderr, "EVP_DecodedLength failed\n");
return false;
}
out->resize(len);
if (!EVP_DecodeBase64(out->data(), &len, len, (const uint8_t *)in,
strlen(in))) {
fprintf(stderr, "EVP_DecodeBase64 failed\n");
return false;
}
out->resize(len);
return true;
}
TEST(SSLTest, SessionEncoding) {
for (const char *input_b64 : {
kOpenSSLSession,
kCustomSession,
kBoringSSLSession,
}) {
SCOPED_TRACE(std::string(input_b64));
// Decode the input.
std::vector<uint8_t> input;
ASSERT_TRUE(DecodeBase64(&input, input_b64));
// Verify the SSL_SESSION decodes.
bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ssl_ctx);
bssl::UniquePtr<SSL_SESSION> session(
SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get()));
ASSERT_TRUE(session) << "SSL_SESSION_from_bytes failed";
// Verify the SSL_SESSION encoding round-trips.
size_t encoded_len;
bssl::UniquePtr<uint8_t> encoded;
uint8_t *encoded_raw;
ASSERT_TRUE(SSL_SESSION_to_bytes(session.get(), &encoded_raw, &encoded_len))
<< "SSL_SESSION_to_bytes failed";
encoded.reset(encoded_raw);
EXPECT_EQ(Bytes(encoded.get(), encoded_len), Bytes(input))
<< "SSL_SESSION_to_bytes did not round-trip";
// Verify the SSL_SESSION also decodes with the legacy API.
const uint8_t *cptr = input.data();
session.reset(d2i_SSL_SESSION(NULL, &cptr, input.size()));
ASSERT_TRUE(session) << "d2i_SSL_SESSION failed";
EXPECT_EQ(cptr, input.data() + input.size());
// Verify the SSL_SESSION encoding round-trips via the legacy API.
int len = i2d_SSL_SESSION(session.get(), NULL);
ASSERT_GT(len, 0) << "i2d_SSL_SESSION failed";
ASSERT_EQ(static_cast<size_t>(len), input.size())
<< "i2d_SSL_SESSION(NULL) returned invalid length";
encoded.reset((uint8_t *)OPENSSL_malloc(input.size()));
ASSERT_TRUE(encoded);
uint8_t *ptr = encoded.get();
len = i2d_SSL_SESSION(session.get(), &ptr);
ASSERT_GT(len, 0) << "i2d_SSL_SESSION failed";
ASSERT_EQ(static_cast<size_t>(len), input.size())
<< "i2d_SSL_SESSION(NULL) returned invalid length";
ASSERT_EQ(ptr, encoded.get() + input.size())
<< "i2d_SSL_SESSION did not advance ptr correctly";
EXPECT_EQ(Bytes(encoded.get(), encoded_len), Bytes(input))
<< "SSL_SESSION_to_bytes did not round-trip";
}
for (const char *input_b64 : {
kBadSessionExtraField,
kBadSessionVersion,
kBadSessionTrailingData,
}) {
SCOPED_TRACE(std::string(input_b64));
std::vector<uint8_t> input;
ASSERT_TRUE(DecodeBase64(&input, input_b64));
// Verify that the SSL_SESSION fails to decode.
bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ssl_ctx);
bssl::UniquePtr<SSL_SESSION> session(
SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get()));
EXPECT_FALSE(session) << "SSL_SESSION_from_bytes unexpectedly succeeded";
ERR_clear_error();
}
}
static void ExpectDefaultVersion(uint16_t min_version, uint16_t max_version,
const SSL_METHOD *(*method)(void)) {
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(method()));
ASSERT_TRUE(ctx);
EXPECT_EQ(min_version, SSL_CTX_get_min_proto_version(ctx.get()));
EXPECT_EQ(max_version, SSL_CTX_get_max_proto_version(ctx.get()));
}
TEST(SSLTest, DefaultVersion) {
ExpectDefaultVersion(TLS1_VERSION, TLS1_3_VERSION, &TLS_method);
ExpectDefaultVersion(TLS1_VERSION, TLS1_VERSION, &TLSv1_method);
ExpectDefaultVersion(TLS1_1_VERSION, TLS1_1_VERSION, &TLSv1_1_method);
ExpectDefaultVersion(TLS1_2_VERSION, TLS1_2_VERSION, &TLSv1_2_method);
ExpectDefaultVersion(DTLS1_VERSION, DTLS1_2_VERSION, &DTLS_method);
ExpectDefaultVersion(DTLS1_VERSION, DTLS1_VERSION, &DTLSv1_method);
ExpectDefaultVersion(DTLS1_2_VERSION, DTLS1_2_VERSION, &DTLSv1_2_method);
}
TEST(SSLTest, CipherProperties) {
static const struct {
int id;
const char *standard_name;
int cipher_nid;
int digest_nid;
int kx_nid;
int auth_nid;
int prf_nid;
} kTests[] = {
{
SSL3_CK_RSA_DES_192_CBC3_SHA,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
NID_des_ede3_cbc,
NID_sha1,
NID_kx_rsa,
NID_auth_rsa,
NID_md5_sha1,
},
{
TLS1_CK_RSA_WITH_AES_128_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA",
NID_aes_128_cbc,
NID_sha1,
NID_kx_rsa,
NID_auth_rsa,
NID_md5_sha1,
},
{
TLS1_CK_PSK_WITH_AES_256_CBC_SHA,
"TLS_PSK_WITH_AES_256_CBC_SHA",
NID_aes_256_cbc,
NID_sha1,
NID_kx_psk,
NID_auth_psk,
NID_md5_sha1,
},
{
TLS1_CK_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
NID_aes_128_cbc,
NID_sha1,
NID_kx_ecdhe,
NID_auth_rsa,
NID_md5_sha1,
},
{
TLS1_CK_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
NID_aes_256_cbc,
NID_sha1,
NID_kx_ecdhe,
NID_auth_rsa,
NID_md5_sha1,
},
{
TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
NID_aes_128_gcm,
NID_undef,
NID_kx_ecdhe,
NID_auth_rsa,
NID_sha256,
},
{
TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
NID_aes_128_gcm,
NID_undef,
NID_kx_ecdhe,
NID_auth_ecdsa,
NID_sha256,
},
{
TLS1_CK_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
NID_aes_256_gcm,
NID_undef,
NID_kx_ecdhe,
NID_auth_ecdsa,
NID_sha384,
},
{
TLS1_CK_ECDHE_PSK_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA",
NID_aes_128_cbc,
NID_sha1,
NID_kx_ecdhe,
NID_auth_psk,
NID_md5_sha1,
},
{
TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
NID_chacha20_poly1305,
NID_undef,
NID_kx_ecdhe,
NID_auth_rsa,
NID_sha256,
},
{
TLS1_CK_AES_256_GCM_SHA384,
"TLS_AES_256_GCM_SHA384",
NID_aes_256_gcm,
NID_undef,
NID_kx_any,
NID_auth_any,
NID_sha384,
},
{
TLS1_CK_AES_128_GCM_SHA256,
"TLS_AES_128_GCM_SHA256",
NID_aes_128_gcm,
NID_undef,
NID_kx_any,
NID_auth_any,
NID_sha256,
},
{
TLS1_CK_CHACHA20_POLY1305_SHA256,
"TLS_CHACHA20_POLY1305_SHA256",
NID_chacha20_poly1305,
NID_undef,
NID_kx_any,
NID_auth_any,
NID_sha256,
},
};
for (const auto &t : kTests) {
SCOPED_TRACE(t.standard_name);
const SSL_CIPHER *cipher = SSL_get_cipher_by_value(t.id & 0xffff);
ASSERT_TRUE(cipher);
EXPECT_STREQ(t.standard_name, SSL_CIPHER_standard_name(cipher));
bssl::UniquePtr<char> rfc_name(SSL_CIPHER_get_rfc_name(cipher));
ASSERT_TRUE(rfc_name);
EXPECT_STREQ(t.standard_name, rfc_name.get());
EXPECT_EQ(t.cipher_nid, SSL_CIPHER_get_cipher_nid(cipher));
EXPECT_EQ(t.digest_nid, SSL_CIPHER_get_digest_nid(cipher));
EXPECT_EQ(t.kx_nid, SSL_CIPHER_get_kx_nid(cipher));
EXPECT_EQ(t.auth_nid, SSL_CIPHER_get_auth_nid(cipher));
EXPECT_EQ(t.prf_nid, SSL_CIPHER_get_prf_nid(cipher));
}
}
// CreateSessionWithTicket returns a sample |SSL_SESSION| with the specified
// version and ticket length or nullptr on failure.
static bssl::UniquePtr<SSL_SESSION> CreateSessionWithTicket(uint16_t version,
size_t ticket_len) {
std::vector<uint8_t> der;
if (!DecodeBase64(&der, kOpenSSLSession)) {
return nullptr;
}
bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
if (!ssl_ctx) {
return nullptr;
}
// Use a garbage ticket.
std::vector<uint8_t> ticket(ticket_len, 'a');
bssl::UniquePtr<SSL_SESSION> session(
SSL_SESSION_from_bytes(der.data(), der.size(), ssl_ctx.get()));
if (!session ||
!SSL_SESSION_set_protocol_version(session.get(), version) ||
!SSL_SESSION_set_ticket(session.get(), ticket.data(), ticket.size())) {
return nullptr;
}
// Fix up the timeout.
#if defined(BORINGSSL_UNSAFE_DETERMINISTIC_MODE)
SSL_SESSION_set_time(session.get(), 1234);
#else
SSL_SESSION_set_time(session.get(), time(nullptr));
#endif
return session;
}
static bool GetClientHello(SSL *ssl, std::vector<uint8_t> *out) {
bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
if (!bio) {
return false;
}
// Do not configure a reading BIO, but record what's written to a memory BIO.
BIO_up_ref(bio.get());
SSL_set_bio(ssl, nullptr /* rbio */, bio.get());
int ret = SSL_connect(ssl);
if (ret > 0) {
// SSL_connect should fail without a BIO to write to.
return false;
}
ERR_clear_error();
const uint8_t *client_hello;
size_t client_hello_len;
if (!BIO_mem_contents(bio.get(), &client_hello, &client_hello_len)) {
return false;
}
*out = std::vector<uint8_t>(client_hello, client_hello + client_hello_len);
return true;
}
// GetClientHelloLen creates a client SSL connection with the specified version
// and ticket length. It returns the length of the ClientHello, not including
// the record header, on success and zero on error.
static size_t GetClientHelloLen(uint16_t max_version, uint16_t session_version,
size_t ticket_len) {
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
bssl::UniquePtr<SSL_SESSION> session =
CreateSessionWithTicket(session_version, ticket_len);
if (!ctx || !session) {
return 0;
}
// Set a one-element cipher list so the baseline ClientHello is unpadded.
bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
if (!ssl || !SSL_set_session(ssl.get(), session.get()) ||
!SSL_set_strict_cipher_list(ssl.get(), "ECDHE-RSA-AES128-GCM-SHA256") ||
!SSL_set_max_proto_version(ssl.get(), max_version)) {
return 0;
}
std::vector<uint8_t> client_hello;
if (!GetClientHello(ssl.get(), &client_hello) ||
client_hello.size() <= SSL3_RT_HEADER_LENGTH) {
return 0;
}
return client_hello.size() - SSL3_RT_HEADER_LENGTH;
}
TEST(SSLTest, Padding) {
struct PaddingVersions {
uint16_t max_version, session_version;
};
static const PaddingVersions kPaddingVersions[] = {
// Test the padding extension at TLS 1.2.
{TLS1_2_VERSION, TLS1_2_VERSION},
// Test the padding extension at TLS 1.3 with a TLS 1.2 session, so there
// will be no PSK binder after the padding extension.
{TLS1_3_VERSION, TLS1_2_VERSION},
// Test the padding extension at TLS 1.3 with a TLS 1.3 session, so there
// will be a PSK binder after the padding extension.
{TLS1_3_VERSION, TLS1_3_VERSION},
};
struct PaddingTest {
size_t input_len, padded_len;
};
static const PaddingTest kPaddingTests[] = {
// ClientHellos of length below 0x100 do not require padding.
{0xfe, 0xfe},
{0xff, 0xff},
// ClientHellos of length 0x100 through 0x1fb are padded up to 0x200.
{0x100, 0x200},
{0x123, 0x200},
{0x1fb, 0x200},
// ClientHellos of length 0x1fc through 0x1ff get padded beyond 0x200. The
// padding extension takes a minimum of four bytes plus one required
// content
// byte. (To work around yet more server bugs, we avoid empty final
// extensions.)
{0x1fc, 0x201},
{0x1fd, 0x202},
{0x1fe, 0x203},
{0x1ff, 0x204},
// Finally, larger ClientHellos need no padding.
{0x200, 0x200},
{0x201, 0x201},
};
for (const PaddingVersions &versions : kPaddingVersions) {
SCOPED_TRACE(versions.max_version);
SCOPED_TRACE(versions.session_version);
// Sample a baseline length.
size_t base_len =
GetClientHelloLen(versions.max_version, versions.session_version, 1);
ASSERT_NE(base_len, 0u) << "Baseline length could not be sampled";
for (const PaddingTest &test : kPaddingTests) {
SCOPED_TRACE(test.input_len);
ASSERT_LE(base_len, test.input_len) << "Baseline ClientHello too long";
size_t padded_len =
GetClientHelloLen(versions.max_version, versions.session_version,
1 + test.input_len - base_len);
EXPECT_EQ(padded_len, test.padded_len)
<< "ClientHello was not padded to expected length";
}
}
}
static bssl::UniquePtr<X509> CertFromPEM(const char *pem) {
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem, strlen(pem)));
if (!bio) {
return nullptr;
}
return bssl::UniquePtr<X509>(
PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
}
static bssl::UniquePtr<EVP_PKEY> KeyFromPEM(const char *pem) {
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem, strlen(pem)));
if (!bio) {
return nullptr;
}
return bssl::UniquePtr<EVP_PKEY>(
PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr));
}
static bssl::UniquePtr<X509> GetTestCertificate() {
static const char kCertPEM[] =
"-----BEGIN CERTIFICATE-----\n"
"MIICWDCCAcGgAwIBAgIJAPuwTC6rEJsMMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV\n"
"BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n"
"aWRnaXRzIFB0eSBMdGQwHhcNMTQwNDIzMjA1MDQwWhcNMTcwNDIyMjA1MDQwWjBF\n"
"MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n"
"ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n"
"gQDYK8imMuRi/03z0K1Zi0WnvfFHvwlYeyK9Na6XJYaUoIDAtB92kWdGMdAQhLci\n"
"HnAjkXLI6W15OoV3gA/ElRZ1xUpxTMhjP6PyY5wqT5r6y8FxbiiFKKAnHmUcrgfV\n"
"W28tQ+0rkLGMryRtrukXOgXBv7gcrmU7G1jC2a7WqmeI8QIDAQABo1AwTjAdBgNV\n"
"HQ4EFgQUi3XVrMsIvg4fZbf6Vr5sp3Xaha8wHwYDVR0jBBgwFoAUi3XVrMsIvg4f\n"
"Zbf6Vr5sp3Xaha8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQA76Hht\n"
"ldY9avcTGSwbwoiuIqv0jTL1fHFnzy3RHMLDh+Lpvolc5DSrSJHCP5WuK0eeJXhr\n"
"T5oQpHL9z/cCDLAKCKRa4uV0fhEdOWBqyR9p8y5jJtye72t6CuFUV5iqcpF4BH4f\n"
"j2VNHwsSrJwkD4QUGlUtH7vwnQmyCFxZMmWAJg==\n"
"-----END CERTIFICATE-----\n";
return CertFromPEM(kCertPEM);
}
static bssl::UniquePtr<EVP_PKEY> GetTestKey() {
static const char kKeyPEM[] =
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIICXgIBAAKBgQDYK8imMuRi/03z0K1Zi0WnvfFHvwlYeyK9Na6XJYaUoIDAtB92\n"
"kWdGMdAQhLciHnAjkXLI6W15OoV3gA/ElRZ1xUpxTMhjP6PyY5wqT5r6y8FxbiiF\n"
"KKAnHmUcrgfVW28tQ+0rkLGMryRtrukXOgXBv7gcrmU7G1jC2a7WqmeI8QIDAQAB\n"
"AoGBAIBy09Fd4DOq/Ijp8HeKuCMKTHqTW1xGHshLQ6jwVV2vWZIn9aIgmDsvkjCe\n"
"i6ssZvnbjVcwzSoByhjN8ZCf/i15HECWDFFh6gt0P5z0MnChwzZmvatV/FXCT0j+\n"
"WmGNB/gkehKjGXLLcjTb6dRYVJSCZhVuOLLcbWIV10gggJQBAkEA8S8sGe4ezyyZ\n"
"m4e9r95g6s43kPqtj5rewTsUxt+2n4eVodD+ZUlCULWVNAFLkYRTBCASlSrm9Xhj\n"
"QpmWAHJUkQJBAOVzQdFUaewLtdOJoPCtpYoY1zd22eae8TQEmpGOR11L6kbxLQsk\n"
"aMly/DOnOaa82tqAGTdqDEZgSNmCeKKknmECQAvpnY8GUOVAubGR6c+W90iBuQLj\n"
"LtFp/9ihd2w/PoDwrHZaoUYVcT4VSfJQog/k7kjE4MYXYWL8eEKg3WTWQNECQQDk\n"
"104Wi91Umd1PzF0ijd2jXOERJU1wEKe6XLkYYNHWQAe5l4J4MWj9OdxFXAxIuuR/\n"
"tfDwbqkta4xcux67//khAkEAvvRXLHTaa6VFzTaiiO8SaFsHV3lQyXOtMrBpB5jd\n"
"moZWgjHvB2W9Ckn7sDqsPB+U2tyX0joDdQEyuiMECDY8oQ==\n"
"-----END RSA PRIVATE KEY-----\n";
return KeyFromPEM(kKeyPEM);
}
static bssl::UniquePtr<SSL_CTX> CreateContextWithTestCertificate(
const SSL_METHOD *method) {
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
bssl::UniquePtr<X509> cert = GetTestCertificate();
bssl::UniquePtr<EVP_PKEY> key = GetTestKey();
if (!ctx || !cert || !key ||
!SSL_CTX_use_certificate(ctx.get(), cert.get()) ||
!SSL_CTX_use_PrivateKey(ctx.get(), key.get())) {
return nullptr;
}
return ctx;
}
static bssl::UniquePtr<X509> GetECDSATestCertificate() {
static const char kCertPEM[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIBzzCCAXagAwIBAgIJANlMBNpJfb/rMAkGByqGSM49BAEwRTELMAkGA1UEBhMC\n"
"QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp\n"
"dHMgUHR5IEx0ZDAeFw0xNDA0MjMyMzIxNTdaFw0xNDA1MjMyMzIxNTdaMEUxCzAJ\n"
"BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l\n"
"dCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATmK2ni\n"
"v2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI2xYa\n"
"HPUdfvGULUvPciLBo1AwTjAdBgNVHQ4EFgQUq4TSrKuV8IJOFngHVVdf5CaNgtEw\n"
"HwYDVR0jBBgwFoAUq4TSrKuV8IJOFngHVVdf5CaNgtEwDAYDVR0TBAUwAwEB/zAJ\n"
"BgcqhkjOPQQBA0gAMEUCIQDyoDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13E\n"
"BwIgfB55FGohg/B6dGh5XxSZmmi08cueFV7mHzJSYV51yRQ=\n"
"-----END CERTIFICATE-----\n";
return CertFromPEM(kCertPEM);
}
static bssl::UniquePtr<EVP_PKEY> GetECDSATestKey() {
static const char kKeyPEM[] =
"-----BEGIN PRIVATE KEY-----\n"
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgBw8IcnrUoEqc3VnJ\n"
"TYlodwi1b8ldMHcO6NHJzgqLtGqhRANCAATmK2niv2Wfl74vHg2UikzVl2u3qR4N\n"
"Rvvdqakendy6WgHn1peoChj5w8SjHlbifINI2xYaHPUdfvGULUvPciLB\n"
"-----END PRIVATE KEY-----\n";
return KeyFromPEM(kKeyPEM);
}
static bssl::UniquePtr<CRYPTO_BUFFER> BufferFromPEM(const char *pem) {
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem, strlen(pem)));
char *name, *header;
uint8_t *data;
long data_len;
if (!PEM_read_bio(bio.get(), &name, &header, &data,
&data_len)) {
return nullptr;
}
OPENSSL_free(name);
OPENSSL_free(header);
auto ret = bssl::UniquePtr<CRYPTO_BUFFER>(
CRYPTO_BUFFER_new(data, data_len, nullptr));
OPENSSL_free(data);
return ret;
}
static bssl::UniquePtr<CRYPTO_BUFFER> GetChainTestCertificateBuffer() {
static const char kCertPEM[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIC0jCCAbqgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEQiBD\n"
"QTAeFw0xNjAyMjgyMDI3MDNaFw0yNjAyMjUyMDI3MDNaMBgxFjAUBgNVBAMMDUNs\n"
"aWVudCBDZXJ0IEEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRvaz8\n"
"CC/cshpCafJo4jLkHEoBqDLhdgFelJoAiQUyIqyWl2O7YHPnpJH+TgR7oelzNzt/\n"
"kLRcH89M/TszB6zqyLTC4aqmvzKL0peD/jL2LWBucR0WXIvjA3zoRuF/x86+rYH3\n"
"tHb+xs2PSs8EGL/Ev+ss+qTzTGEn26fuGNHkNw6tOwPpc+o8+wUtzf/kAthamo+c\n"
"IDs2rQ+lP7+aLZTLeU/q4gcLutlzcK5imex5xy2jPkweq48kijK0kIzl1cPlA5d1\n"
"z7C8jU50Pj9X9sQDJTN32j7UYRisJeeYQF8GaaN8SbrDI6zHgKzrRLyxDt/KQa9V\n"
"iLeXANgZi+Xx9KgfAgMBAAGjLzAtMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYI\n"
"KwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBFEVbmYl+2RtNw\n"
"rDftRDF1v2QUbcN2ouSnQDHxeDQdSgasLzT3ui8iYu0Rw2WWcZ0DV5e0ztGPhWq7\n"
"AO0B120aFRMOY+4+bzu9Q2FFkQqc7/fKTvTDzIJI5wrMnFvUfzzvxh3OHWMYSs/w\n"
"giq33hTKeHEq6Jyk3btCny0Ycecyc3yGXH10sizUfiHlhviCkDuESk8mFDwDDzqW\n"
"ZF0IipzFbEDHoIxLlm3GQxpiLoEV4k8KYJp3R5KBLFyxM6UGPz8h72mIPCJp2RuK\n"
"MYgF91UDvVzvnYm6TfseM2+ewKirC00GOrZ7rEcFvtxnKSqYf4ckqfNdSU1Y+RRC\n"
"1ngWZ7Ih\n"
"-----END CERTIFICATE-----\n";
return BufferFromPEM(kCertPEM);
}
static bssl::UniquePtr<X509> X509FromBuffer(
bssl::UniquePtr<CRYPTO_BUFFER> buffer) {
if (!buffer) {
return nullptr;
}
const uint8_t *derp = CRYPTO_BUFFER_data(buffer.get());
return bssl::UniquePtr<X509>(
d2i_X509(NULL, &derp, CRYPTO_BUFFER_len(buffer.get())));
}
static bssl::UniquePtr<X509> GetChainTestCertificate() {
return X509FromBuffer(GetChainTestCertificateBuffer());
}
static bssl::UniquePtr<CRYPTO_BUFFER> GetChainTestIntermediateBuffer() {
static const char kCertPEM[] =
"-----BEGIN CERTIFICATE-----\n"
"MIICwjCCAaqgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJQyBS\n"
"b290IENBMB4XDTE2MDIyODIwMjcwM1oXDTI2MDIyNTIwMjcwM1owDzENMAsGA1UE\n"
"AwwEQiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALsSCYmDip2D\n"
"GkjFxw7ykz26JSjELkl6ArlYjFJ3aT/SCh8qbS4gln7RH8CPBd78oFdfhIKQrwtZ\n"
"3/q21ykD9BAS3qHe2YdcJfm8/kWAy5DvXk6NXU4qX334KofBAEpgdA/igEFq1P1l\n"
"HAuIfZCpMRfT+i5WohVsGi8f/NgpRvVaMONLNfgw57mz1lbtFeBEISmX0kbsuJxF\n"
"Qj/Bwhi5/0HAEXG8e7zN4cEx0yPRvmOATRdVb/8dW2pwOHRJq9R5M0NUkIsTSnL7\n"
"6N/z8hRAHMsV3IudC5Yd7GXW1AGu9a+iKU+Q4xcZCoj0DC99tL4VKujrV1kAeqsM\n"
"cz5/dKzi6+cCAwEAAaMjMCEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n"
"AQYwDQYJKoZIhvcNAQELBQADggEBAIIeZiEeNhWWQ8Y4D+AGDwqUUeG8NjCbKrXQ\n"
"BlHg5wZ8xftFaiP1Dp/UAezmx2LNazdmuwrYB8lm3FVTyaPDTKEGIPS4wJKHgqH1\n"
"QPDhqNm85ey7TEtI9oYjsNim/Rb+iGkIAMXaxt58SzxbjvP0kMr1JfJIZbic9vye\n"
"NwIspMFIpP3FB8ywyu0T0hWtCQgL4J47nigCHpOu58deP88fS/Nyz/fyGVWOZ76b\n"
"WhWwgM3P3X95fQ3d7oFPR/bVh0YV+Cf861INwplokXgXQ3/TCQ+HNXeAMWn3JLWv\n"
"XFwk8owk9dq/kQGdndGgy3KTEW4ctPX5GNhf3LJ9Q7dLji4ReQ4=\n"
"-----END CERTIFICATE-----\n";
return BufferFromPEM(kCertPEM);
}
static bssl::UniquePtr<X509> GetChainTestIntermediate() {
return X509FromBuffer(GetChainTestIntermediateBuffer());
}
static bssl::UniquePtr<EVP_PKEY> GetChainTestKey() {
static const char kKeyPEM[] =
"-----BEGIN PRIVATE KEY-----\n"
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRvaz8CC/cshpC\n"
"afJo4jLkHEoBqDLhdgFelJoAiQUyIqyWl2O7YHPnpJH+TgR7oelzNzt/kLRcH89M\n"
"/TszB6zqyLTC4aqmvzKL0peD/jL2LWBucR0WXIvjA3zoRuF/x86+rYH3tHb+xs2P\n"
"Ss8EGL/Ev+ss+qTzTGEn26fuGNHkNw6tOwPpc+o8+wUtzf/kAthamo+cIDs2rQ+l\n"
"P7+aLZTLeU/q4gcLutlzcK5imex5xy2jPkweq48kijK0kIzl1cPlA5d1z7C8jU50\n"
"Pj9X9sQDJTN32j7UYRisJeeYQF8GaaN8SbrDI6zHgKzrRLyxDt/KQa9ViLeXANgZ\n"
"i+Xx9KgfAgMBAAECggEBAK0VjSJzkyPaamcyTVSWjo7GdaBGcK60lk657RjR+lK0\n"
"YJ7pkej4oM2hdsVZFsP8Cs4E33nXLa/0pDsRov/qrp0WQm2skwqGMC1I/bZ0WRPk\n"
"wHaDrBBfESWnJDX/AGpVtlyOjPmgmK6J2usMPihQUDkKdAYrVWJePrMIxt1q6BMe\n"
"iczs3qriMmtY3bUc4UyUwJ5fhDLjshHvfuIpYQyI6EXZM6dZksn9LylXJnigY6QJ\n"
"HxOYO0BDwOsZ8yQ8J8afLk88i0GizEkgE1z3REtQUwgWfxr1WV/ud+T6/ZhSAgH9\n"
"042mQvSFZnIUSEsmCvjhWuAunfxHKCTcAoYISWfzWpkCgYEA7gpf3HHU5Tn+CgUn\n"
"1X5uGpG3DmcMgfeGgs2r2f/IIg/5Ac1dfYILiybL1tN9zbyLCJfcbFpWBc9hJL6f\n"
"CPc5hUiwWFJqBJewxQkC1Ae/HakHbip+IZ+Jr0842O4BAArvixk4Lb7/N2Ct9sTE\n"
"NJO6RtK9lbEZ5uK61DglHy8CS2UCgYEA4ZC1o36kPAMQBggajgnucb2yuUEelk0f\n"
"AEr+GI32MGE+93xMr7rAhBoqLg4AITyIfEnOSQ5HwagnIHonBbv1LV/Gf9ursx8Z\n"
"YOGbvT8zzzC+SU1bkDzdjAYnFQVGIjMtKOBJ3K07++ypwX1fr4QsQ8uKL8WSOWwt\n"
"Z3Bym6XiZzMCgYADnhy+2OwHX85AkLt+PyGlPbmuelpyTzS4IDAQbBa6jcuW/2wA\n"
"UE2km75VUXmD+u2R/9zVuLm99NzhFhSMqlUxdV1YukfqMfP5yp1EY6m/5aW7QuIP\n"
"2MDa7TVL9rIFMiVZ09RKvbBbQxjhuzPQKL6X/PPspnhiTefQ+dl2k9xREQKBgHDS\n"
"fMfGNEeAEKezrfSVqxphE9/tXms3L+ZpnCaT+yu/uEr5dTIAawKoQ6i9f/sf1/Sy\n"
"xedsqR+IB+oKrzIDDWMgoJybN4pkZ8E5lzhVQIjFjKgFdWLzzqyW9z1gYfABQPlN\n"
"FiS20WX0vgP1vcKAjdNrHzc9zyHBpgQzDmAj3NZZAoGBAI8vKCKdH7w3aL5CNkZQ\n"
"2buIeWNA2HZazVwAGG5F2TU/LmXfRKnG6dX5bkU+AkBZh56jNZy//hfFSewJB4Kk\n"
"buB7ERSdaNbO21zXt9FEA3+z0RfMd/Zv2vlIWOSB5nzl/7UKti3sribK6s9ZVLfi\n"
"SxpiPQ8d/hmSGwn4ksrWUsJD\n"
"-----END PRIVATE KEY-----\n";
return KeyFromPEM(kKeyPEM);
}
static bool CompleteHandshakes(SSL *client, SSL *server) {
// Drive both their handshakes to completion.
for (;;) {
int client_ret = SSL_do_handshake(client);
int client_err = SSL_get_error(client, client_ret);
if (client_err != SSL_ERROR_NONE &&
client_err != SSL_ERROR_WANT_READ &&
client_err != SSL_ERROR_WANT_WRITE &&
client_err != SSL_ERROR_PENDING_TICKET) {
fprintf(stderr, "Client error: %s\n", SSL_error_description(client_err));
return false;
}
int server_ret = SSL_do_handshake(server);
int server_err = SSL_get_error(server, server_ret);
if (server_err != SSL_ERROR_NONE &&
server_err != SSL_ERROR_WANT_READ &&
server_err != SSL_ERROR_WANT_WRITE &&
server_err != SSL_ERROR_PENDING_TICKET) {
fprintf(stderr, "Server error: %s\n", SSL_error_description(server_err));
return false;
}
if (client_ret == 1 && server_ret == 1) {
break;
}
}
return true;
}
static bool FlushNewSessionTickets(SSL *client, SSL *server) {
// NewSessionTickets are deferred on the server to |SSL_write|, and clients do
// not pick them up until |SSL_read|.
for (;;) {
int server_ret = SSL_write(server, nullptr, 0);
int server_err = SSL_get_error(server, server_ret);
// The server may either succeed (|server_ret| is zero) or block on write
// (|server_ret| is -1 and |server_err| is |SSL_ERROR_WANT_WRITE|).
if (server_ret > 0 ||
(server_ret < 0 && server_err != SSL_ERROR_WANT_WRITE)) {
fprintf(stderr, "Unexpected server result: %d %d\n", server_ret,
server_err);
return false;
}
int client_ret = SSL_read(client, nullptr, 0);
int client_err = SSL_get_error(client, client_ret);
// The client must always block on read.
if (client_ret != -1 || client_err != SSL_ERROR_WANT_READ) {
fprintf(stderr, "Unexpected client result: %d %d\n", client_ret,
client_err);
return false;
}
// The server flushed everything it had to write.
if (server_ret == 0) {
return true;
}
}
}
// CreateClientAndServer creates a client and server |SSL| objects whose |BIO|s
// are paired with each other. It does not run the handshake. The caller is
// expected to configure the objects and drive the handshake as needed.
static bool CreateClientAndServer(bssl::UniquePtr<SSL> *out_client,
bssl::UniquePtr<SSL> *out_server,
SSL_CTX *client_ctx, SSL_CTX *server_ctx) {
bssl::UniquePtr<SSL> client(SSL_new(client_ctx)), server(SSL_new(server_ctx));
if (!client || !server) {
return false;
}
SSL_set_connect_state(client.get());
SSL_set_accept_state(server.get());
BIO *bio1, *bio2;
if (!BIO_new_bio_pair(&bio1, 0, &bio2, 0)) {
return false;
}
// SSL_set_bio takes ownership.
SSL_set_bio(client.get(), bio1, bio1);
SSL_set_bio(server.get(), bio2, bio2);
*out_client = std::move(client);
*out_server = std::move(server);
return true;
}
struct ClientConfig {
SSL_SESSION *session = nullptr;
std::string servername;
bool early_data = false;
};
static bool ConnectClientAndServer(bssl::UniquePtr<SSL> *out_client,
bssl::UniquePtr<SSL> *out_server,
SSL_CTX *client_ctx, SSL_CTX *server_ctx,
const ClientConfig &config = ClientConfig(),
bool shed_handshake_config = true) {
bssl::UniquePtr<SSL> client, server;
if (!CreateClientAndServer(&client, &server, client_ctx, server_ctx)) {
return false;
}
if (config.early_data) {
SSL_set_early_data_enabled(client.get(), 1);
}
if (config.session) {
SSL_set_session(client.get(), config.session);
}
if (!config.servername.empty() &&
!SSL_set_tlsext_host_name(client.get(), config.servername.c_str())) {
return false;
}
SSL_set_shed_handshake_config(client.get(), shed_handshake_config);
SSL_set_shed_handshake_config(server.get(), shed_handshake_config);
if (!CompleteHandshakes(client.get(), server.get())) {
return false;
}
*out_client = std::move(client);
*out_server = std::move(server);
return true;
}
static bssl::UniquePtr<SSL_SESSION> g_last_session;
static int SaveLastSession(SSL *ssl, SSL_SESSION *session) {
// Save the most recent session.
g_last_session.reset(session);
return 1;
}
static bssl::UniquePtr<SSL_SESSION> CreateClientSession(
SSL_CTX *client_ctx, SSL_CTX *server_ctx,
const ClientConfig &config = ClientConfig()) {
g_last_session = nullptr;
SSL_CTX_sess_set_new_cb(client_ctx, SaveLastSession);
// Connect client and server to get a session.
bssl::UniquePtr<SSL> client, server;
if (!ConnectClientAndServer(&client, &server, client_ctx, server_ctx,
config) ||
!FlushNewSessionTickets(client.get(), server.get())) {
fprintf(stderr, "Failed to connect client and server.\n");
return nullptr;
}
SSL_CTX_sess_set_new_cb(client_ctx, nullptr);
if (!g_last_session) {
fprintf(stderr, "Client did not receive a session.\n");
return nullptr;
}
return std::move(g_last_session);
}
// Test that |SSL_get_client_CA_list| echoes back the configured parameter even
// before configuring as a server.
TEST(SSLTest, ClientCAList) {
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
ASSERT_TRUE(ssl);
bssl::UniquePtr<X509_NAME> name(X509_NAME_new());
ASSERT_TRUE(name);
bssl::UniquePtr<X509_NAME> name_dup(X509_NAME_dup(name.get()));
ASSERT_TRUE(name_dup);
bssl::UniquePtr<STACK_OF(X509_NAME)> stack(sk_X509_NAME_new_null());
ASSERT_TRUE(stack);
ASSERT_TRUE(PushToStack(stack.get(), std::move(name_dup)));
// |SSL_set_client_CA_list| takes ownership.
SSL_set_client_CA_list(ssl.get(), stack.release());
STACK_OF(X509_NAME) *result = SSL_get_client_CA_list(ssl.get());
ASSERT_TRUE(result);
ASSERT_EQ(1u, sk_X509_NAME_num(result));
EXPECT_EQ(0, X509_NAME_cmp(sk_X509_NAME_value(result, 0), name.get()));
}
TEST(SSLTest, AddClientCA) {
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
ASSERT_TRUE(ssl);
bssl::UniquePtr<X509> cert1 = GetTestCertificate();
bssl::UniquePtr<X509> cert2 = GetChainTestCertificate();
ASSERT_TRUE(cert1 && cert2);
X509_NAME *name1 = X509_get_subject_name(cert1.get());
X509_NAME *name2 = X509_get_subject_name(cert2.get());
EXPECT_EQ(0u, sk_X509_NAME_num(SSL_get_client_CA_list(ssl.get())));
ASSERT_TRUE(SSL_add_client_CA(ssl.get(), cert1.get()));
ASSERT_TRUE(SSL_add_client_CA(ssl.get(), cert2.get()));
STACK_OF(X509_NAME) *list = SSL_get_client_CA_list(ssl.get());
ASSERT_EQ(2u, sk_X509_NAME_num(list));
EXPECT_EQ(0, X509_NAME_cmp(sk_X509_NAME_value(list, 0), name1));
EXPECT_EQ(0, X509_NAME_cmp(sk_X509_NAME_value(list, 1), name2));
ASSERT_TRUE(SSL_add_client_CA(ssl.get(), cert1.get()));
list = SSL_get_client_CA_list(ssl.get());
ASSERT_EQ(3u, sk_X509_NAME_num(list));
EXPECT_EQ(0, X509_NAME_cmp(sk_X509_NAME_value(list, 0), name1));
EXPECT_EQ(0, X509_NAME_cmp(sk_X509_NAME_value(list, 1), name2));
EXPECT_EQ(0, X509_NAME_cmp(sk_X509_NAME_value(list, 2), name1));
}
struct ECHConfigParams {
uint16_t version = TLSEXT_TYPE_encrypted_client_hello;
uint16_t config_id = 1;
std::string public_name = "example.com";
const EVP_HPKE_KEY *key = nullptr;
// kem_id, if zero, takes its value from |key|.
uint16_t kem_id = 0;
// public_key, if empty takes its value from |key|.
std::vector<uint8_t> public_key;
size_t max_name_len = 16;
// cipher_suites is a list of code points which should contain pairs of KDF
// and AEAD IDs.
std::vector<uint16_t> cipher_suites = {EVP_HPKE_HKDF_SHA256,
EVP_HPKE_AES_128_GCM};
std::vector<uint8_t> extensions;
};
// MakeECHConfig serializes an ECHConfig from |params| and writes it to
// |*out|.
bool MakeECHConfig(std::vector<uint8_t> *out,
const ECHConfigParams &params) {
uint16_t kem_id = params.kem_id == 0
? EVP_HPKE_KEM_id(EVP_HPKE_KEY_kem(params.key))
: params.kem_id;
std::vector<uint8_t> public_key = params.public_key;
if (public_key.empty()) {
public_key.resize(EVP_HPKE_MAX_PUBLIC_KEY_LENGTH);
size_t len;
if (!EVP_HPKE_KEY_public_key(params.key, public_key.data(), &len,
public_key.size())) {
return false;
}
public_key.resize(len);
}
bssl::ScopedCBB cbb;
CBB contents, child;
if (!CBB_init(cbb.get(), 64) ||
!CBB_add_u16(cbb.get(), params.version) ||
!CBB_add_u16_length_prefixed(cbb.get(), &contents) ||
!CBB_add_u8(&contents, params.config_id) ||
!CBB_add_u16(&contents, kem_id) ||
!CBB_add_u16_length_prefixed(&contents, &child) ||
!CBB_add_bytes(&child, public_key.data(), public_key.size()) ||
!CBB_add_u16_length_prefixed(&contents, &child)) {
return false;
}
for (uint16_t cipher_suite : params.cipher_suites) {
if (!CBB_add_u16(&child, cipher_suite)) {
return false;
}
}
if (!CBB_add_u8(&contents, params.max_name_len) ||
!CBB_add_u8_length_prefixed(&contents, &child) ||
!CBB_add_bytes(
&child, reinterpret_cast<const uint8_t *>(params.public_name.data()),
params.public_name.size()) ||
!CBB_add_u16_length_prefixed(&contents, &child) ||
!CBB_add_bytes(&child, params.extensions.data(),
params.extensions.size()) ||
!CBB_flush(cbb.get())) {
return false;
}
out->assign(CBB_data(cbb.get()), CBB_data(cbb.get()) + CBB_len(cbb.get()));
return true;
}
static bssl::UniquePtr<SSL_ECH_KEYS> MakeTestECHKeys(uint8_t config_id = 1) {
bssl::ScopedEVP_HPKE_KEY key;
uint8_t *ech_config;
size_t ech_config_len;
if (!EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256()) ||
!SSL_marshal_ech_config(&ech_config, &ech_config_len, config_id,
key.get(), "public.example", 16)) {
return nullptr;
}
bssl::UniquePtr<uint8_t> free_ech_config(ech_config);
// Install a non-retry config.
bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
if (!keys || !SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, ech_config,
ech_config_len, key.get())) {
return nullptr;
}
return keys;
}
static bool InstallECHConfigList(SSL *client, const SSL_ECH_KEYS *keys) {
uint8_t *ech_config_list;
size_t ech_config_list_len;
if (!SSL_ECH_KEYS_marshal_retry_configs(keys, &ech_config_list,
&ech_config_list_len)) {
return false;
}
bssl::UniquePtr<uint8_t> free_ech_config_list(ech_config_list);
return SSL_set1_ech_config_list(client, ech_config_list, ech_config_list_len);
}
// Test that |SSL_marshal_ech_config| and |SSL_ECH_KEYS_marshal_retry_configs|
// output values as expected.
TEST(SSLTest, MarshalECHConfig) {
static const uint8_t kPrivateKey[X25519_PRIVATE_KEY_LEN] = {
0xbc, 0xb5, 0x51, 0x29, 0x31, 0x10, 0x30, 0xc9, 0xed, 0x26, 0xde,
0xd4, 0xb3, 0xdf, 0x3a, 0xce, 0x06, 0x8a, 0xee, 0x17, 0xab, 0xce,
0xd7, 0xdb, 0xf3, 0x11, 0xe5, 0xa8, 0xf3, 0xb1, 0x8e, 0x24};
bssl::ScopedEVP_HPKE_KEY key;
ASSERT_TRUE(EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(),
kPrivateKey, sizeof(kPrivateKey)));
static const uint8_t kECHConfig[] = {
// version
0xfe, 0x0d,
// length
0x00, 0x41,
// contents.config_id
0x01,
// contents.kem_id
0x00, 0x20,
// contents.public_key
0x00, 0x20, 0xa6, 0x9a, 0x41, 0x48, 0x5d, 0x32, 0x96, 0xa4, 0xe0, 0xc3,
0x6a, 0xee, 0xf6, 0x63, 0x0f, 0x59, 0x32, 0x6f, 0xdc, 0xff, 0x81, 0x29,
0x59, 0xa5, 0x85, 0xd3, 0x9b, 0x3b, 0xde, 0x98, 0x55, 0x5c,
// contents.cipher_suites
0x00, 0x08, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03,
// contents.maximum_name_length
0x10,
// contents.public_name
0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d,
0x70, 0x6c, 0x65,
// contents.extensions
0x00, 0x00};
uint8_t *ech_config;
size_t ech_config_len;
ASSERT_TRUE(SSL_marshal_ech_config(&ech_config, &ech_config_len,
/*config_id=*/1, key.get(),
"public.example", 16));
bssl::UniquePtr<uint8_t> free_ech_config(ech_config);
EXPECT_EQ(Bytes(kECHConfig), Bytes(ech_config, ech_config_len));
// Generate a second ECHConfig.
bssl::ScopedEVP_HPKE_KEY key2;
ASSERT_TRUE(EVP_HPKE_KEY_generate(key2.get(), EVP_hpke_x25519_hkdf_sha256()));
uint8_t *ech_config2;
size_t ech_config2_len;
ASSERT_TRUE(SSL_marshal_ech_config(&ech_config2, &ech_config2_len,
/*config_id=*/2, key2.get(),
"public.example", 16));
bssl::UniquePtr<uint8_t> free_ech_config2(ech_config2);
// Install both ECHConfigs in an |SSL_ECH_KEYS|.
bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
ASSERT_TRUE(keys);
ASSERT_TRUE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, ech_config,
ech_config_len, key.get()));
ASSERT_TRUE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, ech_config2,
ech_config2_len, key2.get()));
// The ECHConfigList should be correctly serialized.
uint8_t *ech_config_list;
size_t ech_config_list_len;
ASSERT_TRUE(SSL_ECH_KEYS_marshal_retry_configs(keys.get(), &ech_config_list,
&ech_config_list_len));
bssl::UniquePtr<uint8_t> free_ech_config_list(ech_config_list);
// ECHConfigList is just the concatenation with a length prefix.
size_t len = ech_config_len + ech_config2_len;
std::vector<uint8_t> expected = {uint8_t(len >> 8), uint8_t(len)};
expected.insert(expected.end(), ech_config, ech_config + ech_config_len);
expected.insert(expected.end(), ech_config2, ech_config2 + ech_config2_len);
EXPECT_EQ(Bytes(expected), Bytes(ech_config_list, ech_config_list_len));
}
TEST(SSLTest, ECHHasDuplicateConfigID) {
const struct {
std::vector<uint8_t> ids;
bool has_duplicate;
} kTests[] = {
{{}, false},
{{1}, false},
{{1, 2, 3, 255}, false},
{{1, 2, 3, 1}, true},
};
for (const auto &test : kTests) {
bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
ASSERT_TRUE(keys);
for (const uint8_t id : test.ids) {
bssl::ScopedEVP_HPKE_KEY key;
ASSERT_TRUE(
EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256()));
uint8_t *ech_config;
size_t ech_config_len;
ASSERT_TRUE(SSL_marshal_ech_config(&ech_config, &ech_config_len, id,
key.get(), "public.example", 16));
bssl::UniquePtr<uint8_t> free_ech_config(ech_config);
ASSERT_TRUE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config, ech_config_len, key.get()));
}
EXPECT_EQ(test.has_duplicate ? 1 : 0,
SSL_ECH_KEYS_has_duplicate_config_id(keys.get()));
}
}
// Test that |SSL_ECH_KEYS_add| checks consistency between the public and
// private key.
TEST(SSLTest, ECHKeyConsistency) {
bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
ASSERT_TRUE(keys);
bssl::ScopedEVP_HPKE_KEY key;
ASSERT_TRUE(EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256()));
uint8_t public_key[EVP_HPKE_MAX_PUBLIC_KEY_LENGTH];
size_t public_key_len;
ASSERT_TRUE(EVP_HPKE_KEY_public_key(key.get(), public_key, &public_key_len,
sizeof(public_key)));
// Adding an ECHConfig with the matching public key succeeds.
ECHConfigParams params;
params.key = key.get();
std::vector<uint8_t> ech_config;
ASSERT_TRUE(MakeECHConfig(&ech_config, params));
EXPECT_TRUE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(),
key.get()));
// Adding an ECHConfig with the wrong public key is an error.
bssl::ScopedEVP_HPKE_KEY wrong_key;
ASSERT_TRUE(
EVP_HPKE_KEY_generate(wrong_key.get(), EVP_hpke_x25519_hkdf_sha256()));
EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(),
wrong_key.get()));
// Adding an ECHConfig with a truncated public key is an error.
ECHConfigParams truncated;
truncated.key = key.get();
truncated.public_key.assign(public_key, public_key + public_key_len - 1);
ASSERT_TRUE(MakeECHConfig(&ech_config, truncated));
EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(), key.get()));
// Adding an ECHConfig with the right public key, but wrong KEM ID, is an
// error.
ECHConfigParams wrong_kem;
wrong_kem.key = key.get();
wrong_kem.kem_id = 0x0010; // DHKEM(P-256, HKDF-SHA256)
ASSERT_TRUE(MakeECHConfig(&ech_config, wrong_kem));
EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(),
key.get()));
}
// Test that |SSL_CTX_set1_ech_keys| fails when the config list
// has no retry configs.
TEST(SSLTest, ECHServerConfigsWithoutRetryConfigs) {
bssl::ScopedEVP_HPKE_KEY key;
ASSERT_TRUE(EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256()));
uint8_t *ech_config;
size_t ech_config_len;
ASSERT_TRUE(SSL_marshal_ech_config(&ech_config, &ech_config_len,
/*config_id=*/1, key.get(),
"public.example", 16));
bssl::UniquePtr<uint8_t> free_ech_config(ech_config);
// Install a non-retry config.
bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
ASSERT_TRUE(keys);
ASSERT_TRUE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/0, ech_config,
ech_config_len, key.get()));
// |keys| has no retry configs.
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
EXPECT_FALSE(SSL_CTX_set1_ech_keys(ctx.get(), keys.get()));
// Add the same ECHConfig to the list, but this time mark it as a retry
// config.
ASSERT_TRUE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, ech_config,
ech_config_len, key.get()));
EXPECT_TRUE(SSL_CTX_set1_ech_keys(ctx.get(), keys.get()));
}
// Test that the server APIs reject ECHConfigs with unsupported features.
TEST(SSLTest, UnsupportedECHConfig) {
bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
ASSERT_TRUE(keys);
bssl::ScopedEVP_HPKE_KEY key;
ASSERT_TRUE(EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256()));
// Unsupported versions are rejected.
ECHConfigParams unsupported_version;
unsupported_version.version = 0xffff;
unsupported_version.key = key.get();
std::vector<uint8_t> ech_config;
ASSERT_TRUE(MakeECHConfig(&ech_config, unsupported_version));
EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(),
key.get()));
// Unsupported cipher suites are rejected. (We only support HKDF-SHA256.)
ECHConfigParams unsupported_kdf;
unsupported_kdf.key = key.get();
unsupported_kdf.cipher_suites = {0x002 /* HKDF-SHA384 */,
EVP_HPKE_AES_128_GCM};
ASSERT_TRUE(MakeECHConfig(&ech_config, unsupported_kdf));
EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(),
key.get()));
ECHConfigParams unsupported_aead;
unsupported_aead.key = key.get();
unsupported_aead.cipher_suites = {EVP_HPKE_HKDF_SHA256, 0xffff};
ASSERT_TRUE(MakeECHConfig(&ech_config, unsupported_aead));
EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(),
key.get()));
// Unsupported extensions are rejected.
ECHConfigParams extensions;
extensions.key = key.get();
extensions.extensions = {0x00, 0x01, 0x00, 0x00};
ASSERT_TRUE(MakeECHConfig(&ech_config, extensions));
EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(),
key.get()));
// Invalid public names are rejected.
ECHConfigParams invalid_public_name;
invalid_public_name.key = key.get();
invalid_public_name.public_name = "dns_names_have_no_underscores.example";
ASSERT_TRUE(MakeECHConfig(&ech_config, invalid_public_name));
EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(),
key.get()));
}
// Test that |SSL_get_client_random| reports the correct value on both client
// and server in ECH. The client sends two different random values. When ECH is
// accepted, we should report the inner one.
TEST(SSLTest, ECHClientRandomsMatch) {
bssl::UniquePtr<SSL_CTX> server_ctx =
CreateContextWithTestCertificate(TLS_method());
bssl::UniquePtr<SSL_ECH_KEYS> keys = MakeTestECHKeys();
ASSERT_TRUE(keys);
ASSERT_TRUE(SSL_CTX_set1_ech_keys(server_ctx.get(), keys.get()));
bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(client_ctx);
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(CreateClientAndServer(&client, &server, client_ctx.get(),
server_ctx.get()));
ASSERT_TRUE(InstallECHConfigList(client.get(), keys.get()));
ASSERT_TRUE(CompleteHandshakes(client.get(), server.get()));
EXPECT_TRUE(SSL_ech_accepted(client.get()));
EXPECT_TRUE(SSL_ech_accepted(server.get()));
// An ECH server will fairly naturally record the inner ClientHello random,
// but an ECH client may forget to update the random once ClientHelloInner is
// selected.
uint8_t client_random1[SSL3_RANDOM_SIZE];
uint8_t client_random2[SSL3_RANDOM_SIZE];
ASSERT_EQ(sizeof(client_random1),
SSL_get_client_random(client.get(), client_random1,
sizeof(client_random1)));
ASSERT_EQ(sizeof(client_random2),
SSL_get_client_random(server.get(), client_random2,
sizeof(client_random2)));
EXPECT_EQ(Bytes(client_random1), Bytes(client_random2));
}
// GetECHLength sets |*out_client_hello_len| and |*out_ech_len| to the lengths
// of the ClientHello and ECH extension, respectively, when a client created
// from |ctx| constructs a ClientHello with name |name| and an ECHConfig with
// maximum name length |max_name_len|.
static bool GetECHLength(SSL_CTX *ctx, size_t *out_client_hello_len,
size_t *out_ech_len, size_t max_name_len,
const char *name) {
bssl::ScopedEVP_HPKE_KEY key;
uint8_t *ech_config;
size_t ech_config_len;
if (!EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256()) ||
!SSL_marshal_ech_config(&ech_config, &ech_config_len,
/*config_id=*/1, key.get(), "public.example",
max_name_len)) {
return false;
}
bssl::UniquePtr<uint8_t> free_ech_config(ech_config);
bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
if (!keys || !SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, ech_config,
ech_config_len, key.get())) {
return false;
}
bssl::UniquePtr<SSL> ssl(SSL_new(ctx));
if (!ssl || !InstallECHConfigList(ssl.get(), keys.get()) ||
(name != nullptr && !SSL_set_tlsext_host_name(ssl.get(), name))) {
return false;
}
SSL_set_connect_state(ssl.get());
std::vector<uint8_t> client_hello;
SSL_CLIENT_HELLO parsed;
const uint8_t *unused;
if (!GetClientHello(ssl.get(), &client_hello) ||
!ssl_client_hello_init(
ssl.get(), &parsed,
// Skip record and handshake headers. This assumes the ClientHello
// fits in one record.
MakeConstSpan(client_hello)
.subspan(SSL3_RT_HEADER_LENGTH + SSL3_HM_HEADER_LENGTH)) ||
!SSL_early_callback_ctx_extension_get(
&parsed, TLSEXT_TYPE_encrypted_client_hello, &unused, out_ech_len)) {
return false;
}
*out_client_hello_len = client_hello.size();
return true;
}
TEST(SSLTest, ECHPadding) {
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
// Sample lengths with max_name_len = 128 as baseline.
size_t client_hello_len_baseline, ech_len_baseline;
ASSERT_TRUE(GetECHLength(ctx.get(), &client_hello_len_baseline,
&ech_len_baseline, 128, "example.com"));
// Check that all name lengths under the server's maximum look the same.
for (size_t name_len : {1, 2, 32, 64, 127, 128}) {
SCOPED_TRACE(name_len);
size_t client_hello_len, ech_len;
ASSERT_TRUE(GetECHLength(ctx.get(), &client_hello_len, &ech_len, 128,
std::string(name_len, 'a').c_str()));
EXPECT_EQ(client_hello_len, client_hello_len_baseline);
EXPECT_EQ(ech_len, ech_len_baseline);
}
// When sending no SNI, we must still pad as if we are sending one.
size_t client_hello_len, ech_len;
ASSERT_TRUE(
GetECHLength(ctx.get(), &client_hello_len, &ech_len, 128, nullptr));
EXPECT_EQ(client_hello_len, client_hello_len_baseline);
EXPECT_EQ(ech_len, ech_len_baseline);
// Name lengths above the maximum do not get named-based padding, but the
// overall input is padded to a multiple of 32.
size_t client_hello_len_baseline2, ech_len_baseline2;
ASSERT_TRUE(GetECHLength(ctx.get(), &client_hello_len_baseline2,
&ech_len_baseline2, 128,
std::string(128 + 32, 'a').c_str()));
EXPECT_EQ(ech_len_baseline2, ech_len_baseline + 32);
// The ClientHello lengths may match if we are still under the threshold for
// padding extension.
EXPECT_GE(client_hello_len_baseline2, client_hello_len_baseline);
for (size_t name_len = 128 + 1; name_len < 128 + 32; name_len++) {
SCOPED_TRACE(name_len);
ASSERT_TRUE(GetECHLength(ctx.get(), &client_hello_len, &ech_len, 128,
std::string(name_len, 'a').c_str()));
EXPECT_TRUE(ech_len == ech_len_baseline || ech_len == ech_len_baseline2)
<< ech_len;
EXPECT_TRUE(client_hello_len == client_hello_len_baseline ||
client_hello_len == client_hello_len_baseline2)
<< client_hello_len;
}
}
TEST(SSLTest, ECHPublicName) {
auto str_to_span = [](const char *str) -> Span<const uint8_t> {
return MakeConstSpan(reinterpret_cast<const uint8_t *>(str), strlen(str));
};
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("")));
EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("example.com")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span(".example.com")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("example.com.")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("example..com")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("www.-example.com")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("www.example-.com")));
EXPECT_FALSE(
ssl_is_valid_ech_public_name(str_to_span("no_underscores.example")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(
str_to_span("invalid_chars.\x01.example")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(
str_to_span("invalid_chars.\xff.example")));
static const uint8_t kWithNUL[] = {'t', 'e', 's', 't', 0};
EXPECT_FALSE(ssl_is_valid_ech_public_name(kWithNUL));
// Test an LDH label with every character and the maximum length.
EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span(
"abcdefhijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span(
"abcdefhijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ-01234567899")));
// Inputs with trailing numeric components are rejected.
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("127.0.0.1")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("example.1")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("example.01")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("example.0x01")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("example.0X01")));
// Leading zeros and values that overflow |uint32_t| are still rejected.
EXPECT_FALSE(ssl_is_valid_ech_public_name(
str_to_span("example.123456789000000000000000")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(
str_to_span("example.012345678900000000000000")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(
str_to_span("example.0x123456789abcdefABCDEF0")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(
str_to_span("example.0x0123456789abcdefABCDEF")));
// Adding a non-digit or non-hex character makes it a valid DNS name again.
// Single-component numbers are rejected.
EXPECT_TRUE(ssl_is_valid_ech_public_name(
str_to_span("example.1234567890a")));
EXPECT_TRUE(ssl_is_valid_ech_public_name(
str_to_span("example.01234567890a")));
EXPECT_TRUE(ssl_is_valid_ech_public_name(
str_to_span("example.0x123456789abcdefg")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("1")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("01")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0x01")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0X01")));
// Numbers with trailing dots are rejected. (They are already rejected by the
// LDH label rules, but the WHATWG URL parser additionally rejects them.)
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("1.")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("01.")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0x01.")));
EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0X01.")));
}
// When using the built-in verifier, test that |SSL_get0_ech_name_override| is
// applied automatically.
TEST(SSLTest, ECHBuiltinVerifier) {
// These test certificates generated with the following Go program.
/* clang-format off
func main() {
notBefore := time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
notAfter := time.Date(2099, time.January, 1, 0, 0, 0, 0, time.UTC)
rootKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
rootTemplate := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "Test CA"},
NotBefore: notBefore,
NotAfter: notAfter,
BasicConstraintsValid: true,
IsCA: true,
}
rootDER, _ := x509.CreateCertificate(rand.Reader, rootTemplate, rootTemplate, &rootKey.PublicKey, rootKey)
root, _ := x509.ParseCertificate(rootDER)
pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: rootDER})
leafKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
leafKeyDER, _ := x509.MarshalPKCS8PrivateKey(leafKey)
pem.Encode(os.Stdout, &pem.Block{Type: "PRIVATE KEY", Bytes: leafKeyDER})
for i, name := range []string{"public.example", "secret.example"} {
leafTemplate := &x509.Certificate{
SerialNumber: big.NewInt(int64(i) + 2),
Subject: pkix.Name{CommonName: name},
NotBefore: notBefore,
NotAfter: notAfter,
BasicConstraintsValid: true,
DNSNames: []string{name},
}
leafDER, _ := x509.CreateCertificate(rand.Reader, leafTemplate, root, &leafKey.PublicKey, rootKey)
pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: leafDER})
}
}
clang-format on */
bssl::UniquePtr<X509> root = CertFromPEM(R"(
-----BEGIN CERTIFICATE-----
MIIBRzCB7aADAgECAgEBMAoGCCqGSM49BAMCMBIxEDAOBgNVBAMTB1Rlc3QgQ0Ew
IBcNMDAwMTAxMDAwMDAwWhgPMjA5OTAxMDEwMDAwMDBaMBIxEDAOBgNVBAMTB1Rl
c3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT5JUjrI1DAxSpEl88UkmJw
tAJqxo/YrSFo9V3MkcNkfTixi5p6MUtO8DazhEgekBcd2+tBAWtl7dy0qpvTqx92
ozIwMDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTw6ftkexAI6o4r5FntJIfL
GU5F4zAKBggqhkjOPQQDAgNJADBGAiEAiiNowddQeHZaZFIygwe6RW5/WG4sUXWC
dkyl9CQzRaYCIQCFS1EvwZbZtMny27fYm1eeYciY0TkJTEi34H1KwyzzIA==
-----END CERTIFICATE-----
)");
ASSERT_TRUE(root);
bssl::UniquePtr<EVP_PKEY> leaf_key = KeyFromPEM(R"(
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgj5WKHwHnziiyPauf
7QukxTwtTyGZkk8qNdms4puJfxqhRANCAARNrkhxabALDlJrHtvkuDwvCWUF/oVC
hr6PDITHi1lDlJzvVT4aXBH87sH2n2UV5zpx13NHkq1bIC8eRT8eOIe0
-----END PRIVATE KEY-----
)");
ASSERT_TRUE(leaf_key);
bssl::UniquePtr<X509> leaf_public = CertFromPEM(R"(
-----BEGIN CERTIFICATE-----
MIIBaDCCAQ6gAwIBAgIBAjAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdUZXN0IENB
MCAXDTAwMDEwMTAwMDAwMFoYDzIwOTkwMTAxMDAwMDAwWjAZMRcwFQYDVQQDEw5w
dWJsaWMuZXhhbXBsZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABE2uSHFpsAsO
Umse2+S4PC8JZQX+hUKGvo8MhMeLWUOUnO9VPhpcEfzuwfafZRXnOnHXc0eSrVsg
Lx5FPx44h7SjTDBKMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU8On7ZHsQCOqO
K+RZ7SSHyxlOReMwGQYDVR0RBBIwEIIOcHVibGljLmV4YW1wbGUwCgYIKoZIzj0E
AwIDSAAwRQIhANqZRhDR/+QL05hsWXMYEwaiHifd9iakKoFEhKFchcF3AiBRAeXw
wRGGT6+iPmTYM6N5/IDyAb5B9Ke38O6lLEsUwA==
-----END CERTIFICATE-----
)");
ASSERT_TRUE(leaf_public);
bssl::UniquePtr<X509> leaf_secret = CertFromPEM(R"(
-----BEGIN CERTIFICATE-----
MIIBaTCCAQ6gAwIBAgIBAzAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdUZXN0IENB
MCAXDTAwMDEwMTAwMDAwMFoYDzIwOTkwMTAxMDAwMDAwWjAZMRcwFQYDVQQDEw5z
ZWNyZXQuZXhhbXBsZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABE2uSHFpsAsO
Umse2+S4PC8JZQX+hUKGvo8MhMeLWUOUnO9VPhpcEfzuwfafZRXnOnHXc0eSrVsg
Lx5FPx44h7SjTDBKMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU8On7ZHsQCOqO
K+RZ7SSHyxlOReMwGQYDVR0RBBIwEIIOc2VjcmV0LmV4YW1wbGUwCgYIKoZIzj0E
AwIDSQAwRgIhAPQdIz1xCFkc9WuSkxOxJDpywZiEp9SnKcxJ9nwrlRp3AiEA+O3+
XRqE7XFhHL+7TNC2a9OOAjQsEF137YPWo+rhgko=
-----END CERTIFICATE-----
)");
ASSERT_TRUE(leaf_secret);
// Use different config IDs so that fuzzer mode, which breaks trial
// decryption, will observe the key mismatch.
bssl::UniquePtr<SSL_ECH_KEYS> keys = MakeTestECHKeys(/*config_id=*/1);
ASSERT_TRUE(keys);
bssl::UniquePtr<SSL_ECH_KEYS> wrong_keys = MakeTestECHKeys(/*config_id=*/2);
ASSERT_TRUE(wrong_keys);
bssl::UniquePtr<SSL_CTX> server_ctx =
CreateContextWithTestCertificate(TLS_method());
ASSERT_TRUE(server_ctx);
bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(client_ctx);
// Configure the client to verify certificates and expect the secret name.
// This is the name the client is trying to connect to. If ECH is rejected,
// BoringSSL will internally override this setting with the public name.
bssl::UniquePtr<X509_STORE> store(X509_STORE_new());
ASSERT_TRUE(store);
ASSERT_TRUE(X509_STORE_add_cert(store.get(), root.get()));
SSL_CTX_set_cert_store(client_ctx.get(), store.release());
SSL_CTX_set_verify(client_ctx.get(), SSL_VERIFY_PEER, nullptr);
static const char kSecretName[] = "secret.example";
ASSERT_TRUE(X509_VERIFY_PARAM_set1_host(SSL_CTX_get0_param(client_ctx.get()),
kSecretName, strlen(kSecretName)));
// For simplicity, we only run through a pair of representative scenarios here
// and rely on runner.go to verify that |SSL_get0_ech_name_override| behaves
// correctly.
for (bool accept_ech : {false, true}) {
SCOPED_TRACE(accept_ech);
for (bool use_leaf_secret : {false, true}) {
SCOPED_TRACE(use_leaf_secret);
// The server will reject ECH when configured with the wrong keys.
ASSERT_TRUE(SSL_CTX_set1_ech_keys(
server_ctx.get(), accept_ech ? keys.get() : wrong_keys.get()));
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(CreateClientAndServer(&client, &server, client_ctx.get(),
server_ctx.get()));
ASSERT_TRUE(InstallECHConfigList(client.get(), keys.get()));
// Configure the server with the selected certificate.
ASSERT_TRUE(SSL_use_certificate(server.get(), use_leaf_secret
? leaf_secret.get()
: leaf_public.get()));
ASSERT_TRUE(SSL_use_PrivateKey(server.get(), leaf_key.get()));
// The handshake may fail due to name mismatch or ECH reject. We check
// |SSL_get_verify_result| to confirm the handshake got far enough.
CompleteHandshakes(client.get(), server.get());
EXPECT_EQ(accept_ech == use_leaf_secret ? X509_V_OK
: X509_V_ERR_HOSTNAME_MISMATCH,
SSL_get_verify_result(client.get()));
}
}
}
#if defined(OPENSSL_THREADS)
// Test that the server ECH config can be swapped out while the |SSL_CTX| is
// in use on other threads. This test is intended to be run with TSan.
TEST(SSLTest, ECHThreads) {
// Generate a pair of ECHConfigs.
bssl::ScopedEVP_HPKE_KEY key1;
ASSERT_TRUE(EVP_HPKE_KEY_generate(key1.get(), EVP_hpke_x25519_hkdf_sha256()));
uint8_t *ech_config1;
size_t ech_config1_len;
ASSERT_TRUE(SSL_marshal_ech_config(&ech_config1, &ech_config1_len,
/*config_id=*/1, key1.get(),
"public.example", 16));
bssl::UniquePtr<uint8_t> free_ech_config1(ech_config1);
bssl::ScopedEVP_HPKE_KEY key2;
ASSERT_TRUE(EVP_HPKE_KEY_generate(key2.get(), EVP_hpke_x25519_hkdf_sha256()));
uint8_t *ech_config2;
size_t ech_config2_len;
ASSERT_TRUE(SSL_marshal_ech_config(&ech_config2, &ech_config2_len,
/*config_id=*/2, key2.get(),
"public.example", 16));
bssl::UniquePtr<uint8_t> free_ech_config2(ech_config2);
// |keys1| contains the first config. |keys12| contains both.
bssl::UniquePtr<SSL_ECH_KEYS> keys1(SSL_ECH_KEYS_new());
ASSERT_TRUE(keys1);
ASSERT_TRUE(SSL_ECH_KEYS_add(keys1.get(), /*is_retry_config=*/1, ech_config1,
ech_config1_len, key1.get()));
bssl::UniquePtr<SSL_ECH_KEYS> keys12(SSL_ECH_KEYS_new());
ASSERT_TRUE(keys12);
ASSERT_TRUE(SSL_ECH_KEYS_add(keys12.get(), /*is_retry_config=*/1, ech_config2,
ech_config2_len, key2.get()));
ASSERT_TRUE(SSL_ECH_KEYS_add(keys12.get(), /*is_retry_config=*/0, ech_config1,
ech_config1_len, key1.get()));
bssl::UniquePtr<SSL_CTX> server_ctx =
CreateContextWithTestCertificate(TLS_method());
ASSERT_TRUE(SSL_CTX_set1_ech_keys(server_ctx.get(), keys1.get()));
bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(client_ctx);
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(CreateClientAndServer(&client, &server, client_ctx.get(),
server_ctx.get()));
ASSERT_TRUE(InstallECHConfigList(client.get(), keys1.get()));
// In parallel, complete the connection and reconfigure the ECHConfig. Note
// |keys12| supports all the keys in |keys1|, so the handshake should complete
// the same whichever the server uses.
std::vector<std::thread> threads;
threads.emplace_back([&] {
ASSERT_TRUE(CompleteHandshakes(client.get(), server.get()));
EXPECT_TRUE(SSL_ech_accepted(client.get()));
EXPECT_TRUE(SSL_ech_accepted(server.get()));
});
threads.emplace_back([&] {
EXPECT_TRUE(SSL_CTX_set1_ech_keys(server_ctx.get(), keys12.get()));
});
for (auto &thread : threads) {
thread.join();
}
}
#endif // OPENSSL_THREADS
static void AppendSession(SSL_SESSION *session, void *arg) {
std::vector<SSL_SESSION*> *out =
reinterpret_cast<std::vector<SSL_SESSION*>*>(arg);
out->push_back(session);
}
// CacheEquals returns true if |ctx|'s session cache consists of |expected|, in
// order.
static bool CacheEquals(SSL_CTX *ctx,
const std::vector<SSL_SESSION*> &expected) {
// Check the linked list.
SSL_SESSION *ptr = ctx->session_cache_head;
for (SSL_SESSION *session : expected) {
if (ptr != session) {
return false;
}
// TODO(davidben): This is an absurd way to denote the end of the list.
if (ptr->next ==
reinterpret_cast<SSL_SESSION *>(&ctx->session_cache_tail)) {
ptr = nullptr;
} else {
ptr = ptr->next;
}
}
if (ptr != nullptr) {
return false;
}
// Check the hash table.
std::vector<SSL_SESSION*> actual, expected_copy;
lh_SSL_SESSION_doall_arg(ctx->sessions, AppendSession, &actual);
expected_copy = expected;
std::sort(actual.begin(), actual.end());
std::sort(expected_copy.begin(), expected_copy.end());
return actual == expected_copy;
}
static bssl::UniquePtr<SSL_SESSION> CreateTestSession(uint32_t number) {
bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
if (!ssl_ctx) {
return nullptr;
}
bssl::UniquePtr<SSL_SESSION> ret(SSL_SESSION_new(ssl_ctx.get()));
if (!ret) {
return nullptr;
}
uint8_t id[SSL3_SSL_SESSION_ID_LENGTH] = {0};
OPENSSL_memcpy(id, &number, sizeof(number));
if (!SSL_SESSION_set1_id(ret.get(), id, sizeof(id))) {
return nullptr;
}
return ret;
}
// Test that the internal session cache behaves as expected.
TEST(SSLTest, InternalSessionCache) {
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
// Prepare 10 test sessions.
std::vector<bssl::UniquePtr<SSL_SESSION>> sessions;
for (int i = 0; i < 10; i++) {
bssl::UniquePtr<SSL_SESSION> session = CreateTestSession(i);
ASSERT_TRUE(session);
sessions.push_back(std::move(session));
}
SSL_CTX_sess_set_cache_size(ctx.get(), 5);
// Insert all the test sessions.
for (const auto &session : sessions) {
ASSERT_TRUE(SSL_CTX_add_session(ctx.get(), session.get()));
}
// Only the last five should be in the list.
ASSERT_TRUE(CacheEquals(
ctx.get(), {sessions[9].get(), sessions[8].get(), sessions[7].get(),
sessions[6].get(), sessions[5].get()}));
// Inserting an element already in the cache should fail and leave the cache
// unchanged.
ASSERT_FALSE(SSL_CTX_add_session(ctx.get(), sessions[7].get()));
ASSERT_TRUE(CacheEquals(
ctx.get(), {sessions[9].get(), sessions[8].get(), sessions[7].get(),
sessions[6].get(), sessions[5].get()}));
// Although collisions should be impossible (256-bit session IDs), the cache
// must handle them gracefully.
bssl::UniquePtr<SSL_SESSION> collision(CreateTestSession(7));
ASSERT_TRUE(collision);
ASSERT_TRUE(SSL_CTX_add_session(ctx.get(), collision.get()));
ASSERT_TRUE(CacheEquals(
ctx.get(), {collision.get(), sessions[9].get(), sessions[8].get(),
sessions[6].get(), sessions[5].get()}));
// Removing sessions behaves correctly.
ASSERT_TRUE(SSL_CTX_remove_session(ctx.get(), sessions[6].get()));
ASSERT_TRUE(CacheEquals(ctx.get(), {collision.get(), sessions[9].get(),
sessions[8].get(), sessions[5].get()}));
// Removing sessions requires an exact match.
ASSERT_FALSE(SSL_CTX_remove_session(ctx.get(), sessions[0].get()));
ASSERT_FALSE(SSL_CTX_remove_session(ctx.get(), sessions[7].get()));
// The cache remains unchanged.
ASSERT_TRUE(CacheEquals(ctx.get(), {collision.get(), sessions[9].get(),
sessions[8].get(), sessions[5].get()}));
}
static uint16_t EpochFromSequence(uint64_t seq) {
return static_cast<uint16_t>(seq >> 48);
}
static const uint8_t kTestName[] = {
0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65,
0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49,
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67,
0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64,
};
// SSLVersionTest executes its test cases under all available protocol versions.
// Test cases call |Connect| to create a connection using context objects with
// the protocol version fixed to the current version under test.
class SSLVersionTest : public ::testing::TestWithParam<VersionParam> {
protected:
SSLVersionTest() : cert_(GetTestCertificate()), key_(GetTestKey()) {}
void SetUp() { ResetContexts(); }
bssl::UniquePtr<SSL_CTX> CreateContext() const {
const SSL_METHOD *method = is_dtls() ? DTLS_method() : TLS_method();
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(method));
if (!ctx || !SSL_CTX_set_min_proto_version(ctx.get(), version()) ||
!SSL_CTX_set_max_proto_version(ctx.get(), version())) {
return nullptr;
}
return ctx;
}
void ResetContexts() {
ASSERT_TRUE(cert_);
ASSERT_TRUE(key_);
client_ctx_ = CreateContext();
ASSERT_TRUE(client_ctx_);
server_ctx_ = CreateContext();
ASSERT_TRUE(server_ctx_);
// Set up a server cert. Client certs can be set up explicitly.
ASSERT_TRUE(UseCertAndKey(server_ctx_.get()));
}
bool UseCertAndKey(SSL_CTX *ctx) const {
return SSL_CTX_use_certificate(ctx, cert_.get()) &&
SSL_CTX_use_PrivateKey(ctx, key_.get());
}
bool Connect(const ClientConfig &config = ClientConfig()) {
return ConnectClientAndServer(&client_, &server_, client_ctx_.get(),
server_ctx_.get(), config,
shed_handshake_config_);
}
uint16_t version() const { return GetParam().version; }
bool is_dtls() const {
return GetParam().ssl_method == VersionParam::is_dtls;
}
bool shed_handshake_config_ = true;
bssl::UniquePtr<SSL> client_, server_;
bssl::UniquePtr<SSL_CTX> server_ctx_, client_ctx_;
bssl::UniquePtr<X509> cert_;
bssl::UniquePtr<EVP_PKEY> key_;
};
INSTANTIATE_TEST_SUITE_P(WithVersion, SSLVersionTest,
testing::ValuesIn(kAllVersions),
[](const testing::TestParamInfo<VersionParam> &i) {
return i.param.name;
});
TEST_P(SSLVersionTest, SequenceNumber) {
ASSERT_TRUE(Connect());
// Drain any post-handshake messages to ensure there are no unread records
// on either end.
ASSERT_TRUE(FlushNewSessionTickets(client_.get(), server_.get()));
uint64_t client_read_seq = SSL_get_read_sequence(client_.get());
uint64_t client_write_seq = SSL_get_write_sequence(client_.get());
uint64_t server_read_seq = SSL_get_read_sequence(server_.get());
uint64_t server_write_seq = SSL_get_write_sequence(server_.get());
if (is_dtls()) {
// Both client and server must be at epoch 1.
EXPECT_EQ(EpochFromSequence(client_read_seq), 1);
EXPECT_EQ(EpochFromSequence(client_write_seq), 1);
EXPECT_EQ(EpochFromSequence(server_read_seq), 1);
EXPECT_EQ(EpochFromSequence(server_write_seq), 1);
// The next record to be written should exceed the largest received.
EXPECT_GT(client_write_seq, server_read_seq);
EXPECT_GT(server_write_seq, client_read_seq);
} else {
// The next record to be written should equal the next to be received.
EXPECT_EQ(client_write_seq, server_read_seq);
EXPECT_EQ(server_write_seq, client_read_seq);
}
// Send a record from client to server.
uint8_t byte = 0;
EXPECT_EQ(SSL_write(client_.get(), &byte, 1), 1);
EXPECT_EQ(SSL_read(server_.get(), &byte, 1), 1);
// The client write and server read sequence numbers should have
// incremented.
EXPECT_EQ(client_write_seq + 1, SSL_get_write_sequence(client_.get()));
EXPECT_EQ(server_read_seq + 1, SSL_get_read_sequence(server_.get()));
}
TEST_P(SSLVersionTest, OneSidedShutdown) {
// SSL_shutdown is a no-op in DTLS.
if (is_dtls()) {
return;
}
ASSERT_TRUE(Connect());
// Shut down half the connection. |SSL_shutdown| will return 0 to signal only
// one side has shut down.
ASSERT_EQ(SSL_shutdown(client_.get()), 0);
// Reading from the server should consume the EOF.
uint8_t byte;
ASSERT_EQ(SSL_read(server_.get(), &byte, 1), 0);
ASSERT_EQ(SSL_get_error(server_.get(), 0), SSL_ERROR_ZERO_RETURN);
// However, the server may continue to write data and then shut down the
// connection.
byte = 42;
ASSERT_EQ(SSL_write(server_.get(), &byte, 1), 1);
ASSERT_EQ(SSL_read(client_.get(), &byte, 1), 1);
ASSERT_EQ(byte, 42);
// The server may then shutdown the connection.
EXPECT_EQ(SSL_shutdown(server_.get()), 1);
EXPECT_EQ(SSL_shutdown(client_.get()), 1);
}
// Test that, after calling |SSL_shutdown|, |SSL_write| fails.
TEST_P(SSLVersionTest, WriteAfterShutdown) {
ASSERT_TRUE(Connect());
for (SSL *ssl : {client_.get(), server_.get()}) {
SCOPED_TRACE(SSL_is_server(ssl) ? "server" : "client");
bssl::UniquePtr<BIO> mem(BIO_new(BIO_s_mem()));
ASSERT_TRUE(mem);
SSL_set0_wbio(ssl, bssl::UpRef(mem).release());
// Shut down half the connection. |SSL_shutdown| will return 0 to signal
// only one side has shut down.