blob: f939bfe0a295930cbd82f8f1c7cb9cb02aac3088 [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/bio.h>
#include <openssl/cipher.h>
#include <openssl/crypto.h>
#include <openssl/curve25519.h>
#include <openssl/err.h>
#include <openssl/hmac.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);
}
// 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));
}
// kECHConfig contains a serialized ECHConfig value.
static const uint8_t kECHConfig[] = {
// version
0xfe, 0x0a,
// length
0x00, 0x43,
// contents.config_id
0x42,
// 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
0x00, 0x10,
// contents.public_name
0x00, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65,
// contents.extensions
0x00, 0x00};
// kECHPublicKey is the public key encoded in |kECHConfig|.
static const uint8_t kECHPublicKey[X25519_PUBLIC_VALUE_LEN] = {
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};
// kECHPrivateKey is the X25519 private key corresponding to |kECHPublicKey|.
static const uint8_t kECHPrivateKey[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};
// MakeECHConfig serializes an ECHConfig and writes it to |*out| with the
// specified parameters. |cipher_suites| is a list of code points which should
// contain pairs of KDF and AEAD IDs.
bool MakeECHConfig(std::vector<uint8_t> *out, uint8_t config_id,
uint16_t kem_id, Span<const uint8_t> public_key,
Span<const uint16_t> cipher_suites,
Span<const uint8_t> extensions) {
bssl::ScopedCBB cbb;
CBB contents, child;
static const char kPublicName[] = "example.com";
if (!CBB_init(cbb.get(), 64) ||
!CBB_add_u16(cbb.get(), TLSEXT_TYPE_encrypted_client_hello) ||
!CBB_add_u16_length_prefixed(cbb.get(), &contents) ||
!CBB_add_u8(&contents, 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 : cipher_suites) {
if (!CBB_add_u16(&child, cipher_suite)) {
return false;
}
}
if (!CBB_add_u16(&contents, strlen(kPublicName)) || // maximum_name_length
!CBB_add_u16_length_prefixed(&contents, &child) ||
!CBB_add_bytes(&child, reinterpret_cast<const uint8_t *>(kPublicName),
strlen(kPublicName)) ||
!CBB_add_u16_length_prefixed(&contents, &child) ||
!CBB_add_bytes(&child, extensions.data(), extensions.size()) ||
!CBB_flush(cbb.get())) {
return false;
}
out->assign(CBB_data(cbb.get()), CBB_data(cbb.get()) + CBB_len(cbb.get()));
return true;
}
TEST(SSLTest, ECHKeys) {
// kWrongPrivateKey is an unrelated, but valid X25519 private key.
const uint8_t kWrongPrivateKey[X25519_PRIVATE_KEY_LEN] = {
0xbb, 0xfe, 0x08, 0xf7, 0x31, 0xde, 0x9c, 0x8a, 0xf2, 0x06, 0x4a,
0x18, 0xd7, 0x8b, 0x79, 0x31, 0xe2, 0x53, 0xdd, 0x63, 0x8f, 0x58,
0x42, 0xda, 0x21, 0x0e, 0x61, 0x97, 0x29, 0xcc, 0x17, 0x71};
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
ASSERT_TRUE(keys);
// Adding an ECHConfig with the wrong private key is an error.
ASSERT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, kECHConfig,
sizeof(kECHConfig), kWrongPrivateKey,
sizeof(kWrongPrivateKey)));
uint32_t err = ERR_get_error();
EXPECT_EQ(ERR_LIB_SSL, ERR_GET_LIB(err));
EXPECT_EQ(SSL_R_ECH_SERVER_CONFIG_AND_PRIVATE_KEY_MISMATCH,
ERR_GET_REASON(err));
ERR_clear_error();
// Adding an ECHConfig with the matching private key succeeds.
ASSERT_TRUE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, kECHConfig,
sizeof(kECHConfig), kECHPrivateKey,
sizeof(kECHPrivateKey)));
ASSERT_TRUE(SSL_CTX_set1_ech_keys(ctx.get(), keys.get()));
// Build a new config list and replace the old one on |ctx|.
bssl::UniquePtr<SSL_ECH_KEYS> next_keys(SSL_ECH_KEYS_new());
ASSERT_TRUE(SSL_ECH_KEYS_add(next_keys.get(), /*is_retry_config=*/1,
kECHConfig, sizeof(kECHConfig), kECHPrivateKey,
sizeof(kECHPrivateKey)));
ASSERT_TRUE(SSL_CTX_set1_ech_keys(ctx.get(), next_keys.get()));
}
TEST(SSLTest, ECHServerConfigListTruncatedPublicKey) {
std::vector<uint8_t> ech_config;
ASSERT_TRUE(MakeECHConfig(
&ech_config, 0x42, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
MakeConstSpan(kECHPublicKey, sizeof(kECHPublicKey) - 1),
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM},
/*extensions=*/{}));
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
ASSERT_TRUE(keys);
ASSERT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(),
kECHPrivateKey, sizeof(kECHPrivateKey)));
uint32_t err = ERR_peek_error();
EXPECT_EQ(ERR_LIB_SSL, ERR_GET_LIB(err));
EXPECT_EQ(SSL_R_ECH_SERVER_CONFIG_AND_PRIVATE_KEY_MISMATCH,
ERR_GET_REASON(err));
ERR_clear_error();
}
// Test that |SSL_CTX_set1_ech_keys| fails when the config list
// has no retry configs.
TEST(SSLTest, ECHServerConfigsWithoutRetryConfigs) {
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
ASSERT_TRUE(keys);
// Adding an ECHConfig with the matching private key succeeds.
ASSERT_TRUE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/0, kECHConfig,
sizeof(kECHConfig), kECHPrivateKey,
sizeof(kECHPrivateKey)));
ASSERT_FALSE(SSL_CTX_set1_ech_keys(ctx.get(), keys.get()));
uint32_t err = ERR_peek_error();
EXPECT_EQ(ERR_LIB_SSL, ERR_GET_LIB(err));
EXPECT_EQ(SSL_R_ECH_SERVER_WOULD_HAVE_NO_RETRY_CONFIGS, ERR_GET_REASON(err));
ERR_clear_error();
// 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, kECHConfig,
sizeof(kECHConfig), kECHPrivateKey,
sizeof(kECHPrivateKey)));
ASSERT_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);
// Unsupported versions are rejected.
static const uint8_t kUnsupportedVersion[] = {0xff, 0xff, 0x00, 0x00};
EXPECT_FALSE(SSL_ECH_KEYS_add(
keys.get(), /*is_retry_config=*/1, kUnsupportedVersion,
sizeof(kUnsupportedVersion), kECHPrivateKey, sizeof(kECHPrivateKey)));
// Unsupported cipher suites are rejected. (We only support HKDF-SHA256.)
std::vector<uint8_t> ech_config;
ASSERT_TRUE(MakeECHConfig(
&ech_config, 0x42, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, kECHPublicKey,
std::vector<uint16_t>{0x002 /* HKDF-SHA384 */, EVP_HPKE_AES_128_GCM},
/*extensions=*/{}));
EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(),
kECHPrivateKey, sizeof(kECHPrivateKey)));
// Unsupported KEMs are rejected.
static const uint8_t kP256PublicKey[] = {
0x04, 0xe6, 0x2b, 0x69, 0xe2, 0xbf, 0x65, 0x9f, 0x97, 0xbe, 0x2f,
0x1e, 0x0d, 0x94, 0x8a, 0x4c, 0xd5, 0x97, 0x6b, 0xb7, 0xa9, 0x1e,
0x0d, 0x46, 0xfb, 0xdd, 0xa9, 0xa9, 0x1e, 0x9d, 0xdc, 0xba, 0x5a,
0x01, 0xe7, 0xd6, 0x97, 0xa8, 0x0a, 0x18, 0xf9, 0xc3, 0xc4, 0xa3,
0x1e, 0x56, 0xe2, 0x7c, 0x83, 0x48, 0xdb, 0x16, 0x1a, 0x1c, 0xf5,
0x1d, 0x7e, 0xf1, 0x94, 0x2d, 0x4b, 0xcf, 0x72, 0x22, 0xc1};
static const uint8_t kP256PrivateKey[] = {
0x07, 0x0f, 0x08, 0x72, 0x7a, 0xd4, 0xa0, 0x4a, 0x9c, 0xdd, 0x59,
0xc9, 0x4d, 0x89, 0x68, 0x77, 0x08, 0xb5, 0x6f, 0xc9, 0x5d, 0x30,
0x77, 0x0e, 0xe8, 0xd1, 0xc9, 0xce, 0x0a, 0x8b, 0xb4, 0x6a};
ASSERT_TRUE(MakeECHConfig(
&ech_config, 0x42, 0x0010 /* DHKEM(P-256, HKDF-SHA256) */, kP256PublicKey,
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM},
/*extensions=*/{}));
EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(),
kP256PrivateKey, sizeof(kP256PrivateKey)));
// Unsupported extensions are rejected.
static const uint8_t kExtensions[] = {0x00, 0x01, 0x00, 0x00};
ASSERT_TRUE(MakeECHConfig(
&ech_config, 0x42, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, kECHPublicKey,
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM},
kExtensions));
EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1,
ech_config.data(), ech_config.size(),
kECHPrivateKey, sizeof(kECHPrivateKey)));
}
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,
};
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;
}
// 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(SSLTest, SessionDuplication) {
bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
bssl::UniquePtr<SSL_CTX> server_ctx =
CreateContextWithTestCertificate(TLS_method());
ASSERT_TRUE(client_ctx);
ASSERT_TRUE(server_ctx);
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
server_ctx.get()));
SSL_SESSION *session0 = SSL_get_session(client.get());
bssl::UniquePtr<SSL_SESSION> session1 =
bssl::SSL_SESSION_dup(session0, SSL_SESSION_DUP_ALL);
ASSERT_TRUE(session1);
session1->not_resumable = false;
uint8_t *s0_bytes, *s1_bytes;
size_t s0_len, s1_len;
ASSERT_TRUE(SSL_SESSION_to_bytes(session0, &s0_bytes, &s0_len));
bssl::UniquePtr<uint8_t> free_s0(s0_bytes);
ASSERT_TRUE(SSL_SESSION_to_bytes(session1.get(), &s1_bytes, &s1_len));
bssl::UniquePtr<uint8_t> free_s1(s1_bytes);
EXPECT_EQ(Bytes(s0_bytes, s0_len), Bytes(s1_bytes, s1_len));
}
static void ExpectFDs(const SSL *ssl, int rfd, int wfd) {
EXPECT_EQ(rfd, SSL_get_fd(ssl));
EXPECT_EQ(rfd, SSL_get_rfd(ssl));
EXPECT_EQ(wfd, SSL_get_wfd(ssl));
// The wrapper BIOs are always equal when fds are equal, even if set
// individually.
if (rfd == wfd) {
EXPECT_EQ(SSL_get_rbio(ssl), SSL_get_wbio(ssl));
}
}
TEST(SSLTest, SetFD) {
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
// Test setting different read and write FDs.
bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
ASSERT_TRUE(ssl);
EXPECT_TRUE(SSL_set_rfd(ssl.get(), 1));
EXPECT_TRUE(SSL_set_wfd(ssl.get(), 2));
ExpectFDs(ssl.get(), 1, 2);
// Test setting the same FD.
ssl.reset(SSL_new(ctx.get()));
ASSERT_TRUE(ssl);
EXPECT_TRUE(SSL_set_fd(ssl.get(), 1));
ExpectFDs(ssl.get(), 1, 1);
// Test setting the same FD one side at a time.
ssl.reset(SSL_new(ctx.get()));
ASSERT_TRUE(ssl);
EXPECT_TRUE(SSL_set_rfd(ssl.get(), 1));
EXPECT_TRUE(SSL_set_wfd(ssl.get(), 1));
ExpectFDs(ssl.get(), 1, 1);
// Test setting the same FD in the other order.
ssl.reset(SSL_new(ctx.get()));
ASSERT_TRUE(ssl);
EXPECT_TRUE(SSL_set_wfd(ssl.get(), 1));
EXPECT_TRUE(SSL_set_rfd(ssl.get(), 1));
ExpectFDs(ssl.get(), 1, 1);
// Test changing the read FD partway through.
ssl.reset(SSL_new(ctx.get()));
ASSERT_TRUE(ssl);
EXPECT_TRUE(SSL_set_fd(ssl.get(), 1));
EXPECT_TRUE(SSL_set_rfd(ssl.get(), 2));
ExpectFDs(ssl.get(), 2, 1);
// Test changing the write FD partway through.
ssl.reset(SSL_new(ctx.get()));
ASSERT_TRUE(ssl);
EXPECT_TRUE(SSL_set_fd(ssl.get(), 1));
EXPECT_TRUE(SSL_set_wfd(ssl.get(), 2));
ExpectFDs(ssl.get(), 1, 2);
// Test a no-op change to the read FD partway through.
ssl.reset(SSL_new(ctx.get()));
ASSERT_TRUE(ssl);
EXPECT_TRUE(SSL_set_fd(ssl.get(), 1));
EXPECT_TRUE(SSL_set_rfd(ssl.get(), 1));
ExpectFDs(ssl.get(), 1, 1);
// Test a no-op change to the write FD partway through.
ssl.reset(SSL_new(ctx.get()));
ASSERT_TRUE(ssl);
EXPECT_TRUE(SSL_set_fd(ssl.get(), 1));
EXPECT_TRUE(SSL_set_wfd(ssl.get(), 1));
ExpectFDs(ssl.get(), 1, 1);
// ASan builds will implicitly test that the internal |BIO| reference-counting
// is correct.
}
TEST(SSLTest, SetBIO) {
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
bssl::UniquePtr<BIO> bio1(BIO_new(BIO_s_mem())), bio2(BIO_new(BIO_s_mem())),
bio3(BIO_new(BIO_s_mem()));
ASSERT_TRUE(ssl);
ASSERT_TRUE(bio1);
ASSERT_TRUE(bio2);
ASSERT_TRUE(bio3);
// SSL_set_bio takes one reference when the parameters are the same.
BIO_up_ref(bio1.get());
SSL_set_bio(ssl.get(), bio1.get(), bio1.get());
// Repeating the call does nothing.
SSL_set_bio(ssl.get(), bio1.get(), bio1.get());
// It takes one reference each when the parameters are different.
BIO_up_ref(bio2.get());
BIO_up_ref(bio3.get());
SSL_set_bio(ssl.get(), bio2.get(), bio3.get());
// Repeating the call does nothing.
SSL_set_bio(ssl.get(), bio2.get(), bio3.get());
// It takes one reference when changing only wbio.
BIO_up_ref(bio1.get());
SSL_set_bio(ssl.get(), bio2.get(), bio1.get());
// It takes one reference when changing only rbio and the two are different.
BIO_up_ref(bio3.get());
SSL_set_bio(ssl.get(), bio3.get(), bio1.get());
// If setting wbio to rbio, it takes no additional references.
SSL_set_bio(ssl.get(), bio3.get(), bio3.get());
// From there, wbio may be switched to something else.
BIO_up_ref(bio1.get());
SSL_set_bio(ssl.get(), bio3.get(), bio1.get());
// If setting rbio to wbio, it takes no additional references.
SSL_set_bio(ssl.get(), bio1.get(), bio1.get());
// From there, rbio may be switched to something else, but, for historical
// reasons, it takes a reference to both parameters.
BIO_up_ref(bio1.get());
BIO_up_ref(bio2.get());
SSL_set_bio(ssl.get(), bio2.get(), bio1.get());
// ASAN builds will implicitly test that the internal |BIO| reference-counting
// is correct.
}
static int VerifySucceed(X509_STORE_CTX *store_ctx, void *arg) { return 1; }
TEST_P(SSLVersionTest, GetPeerCertificate) {
ASSERT_TRUE(UseCertAndKey(client_ctx_.get()));
// Configure both client and server to accept any certificate.
SSL_CTX_set_verify(client_ctx_.get(),
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
nullptr);
SSL_CTX_set_cert_verify_callback(client_ctx_.get(), VerifySucceed, NULL);
SSL_CTX_set_verify(server_ctx_.get(),
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
nullptr);
SSL_CTX_set_cert_verify_callback(server_ctx_.get(), VerifySucceed, NULL);
ASSERT_TRUE(Connect());
// Client and server should both see the leaf certificate.
bssl::UniquePtr<X509> peer(SSL_get_peer_certificate(server_.get()));
ASSERT_TRUE(peer);
ASSERT_EQ(X509_cmp(cert_.get(), peer.get()), 0);
peer.reset(SSL_get_peer_certificate(client_.get()));
ASSERT_TRUE(peer);
ASSERT_EQ(X509_cmp(cert_.get(), peer.get()), 0);
// However, for historical reasons, the X509 chain includes the leaf on the
// client, but does not on the server.
EXPECT_EQ(sk_X509_num(SSL_get_peer_cert_chain(client_.get())), 1u);
EXPECT_EQ(sk_CRYPTO_BUFFER_num(SSL_get0_peer_certificates(client_.get())),
1u);
EXPECT_EQ(sk_X509_num(SSL_get_peer_cert_chain(server_.get())), 0u);
EXPECT_EQ(sk_CRYPTO_BUFFER_num(SSL_get0_peer_certificates(server_.get())),
1u);
}
TEST_P(SSLVersionTest, NoPeerCertificate) {
SSL_CTX_set_verify(server_ctx_.get(), SSL_VERIFY_PEER, nullptr);
SSL_CTX_set_cert_verify_callback(server_ctx_.get(), VerifySucceed, NULL);
SSL_CTX_set_cert_verify_callback(client_ctx_.get(), VerifySucceed, NULL);
ASSERT_TRUE(Connect());
// Server should not see a peer certificate.
bssl::UniquePtr<X509> peer(SSL_get_peer_certificate(server_.get()));
ASSERT_FALSE(peer);
ASSERT_FALSE(SSL_get0_peer_certificates(server_.get()));
}
TEST_P(SSLVersionTest, RetainOnlySHA256OfCerts) {
uint8_t *cert_der = NULL;
int cert_der_len = i2d_X509(cert_.get(), &cert_der);
ASSERT_GE(cert_der_len, 0);
bssl::UniquePtr<uint8_t> free_cert_der(cert_der);
uint8_t cert_sha256[SHA256_DIGEST_LENGTH];
SHA256(cert_der, cert_der_len, cert_sha256);
ASSERT_TRUE(UseCertAndKey(client_ctx_.get()));
// Configure both client and server to accept any certificate, but the
// server must retain only the SHA-256 of the peer.
SSL_CTX_set_verify(client_ctx_.get(),
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
nullptr);
SSL_CTX_set_verify(server_ctx_.get(),
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
nullptr);
SSL_CTX_set_cert_verify_callback(client_ctx_.get(), VerifySucceed, NULL);
SSL_CTX_set_cert_verify_callback(server_ctx_.get(), VerifySucceed, NULL);
SSL_CTX_set_retain_only_sha256_of_client_certs(server_ctx_.get(), 1);
ASSERT_TRUE(Connect());
// The peer certificate has been dropped.
bssl::UniquePtr<X509> peer(SSL_get_peer_certificate(server_.get()));
EXPECT_FALSE(peer);
SSL_SESSION *session = SSL_get_session(server_.get());
EXPECT_TRUE(SSL_SESSION_has_peer_sha256(session));
const uint8_t *peer_sha256;
size_t peer_sha256_len;
SSL_SESSION_get0_peer_sha256(session, &peer_sha256, &peer_sha256_len);
EXPECT_EQ(Bytes(cert_sha256), Bytes(peer_sha256, peer_sha256_len));
}
// Tests that our ClientHellos do not change unexpectedly. These are purely
// change detection tests. If they fail as part of an intentional ClientHello
// change, update the test vector.
TEST(SSLTest, ClientHello) {
struct {
uint16_t max_version;
std::vector<uint8_t> expected;
} kTests[] = {
{TLS1_VERSION,
{0x16, 0x03, 0x01, 0x00, 0x5a, 0x01, 0x00, 0x00, 0x56, 0x03, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xc0, 0x09,
0xc0, 0x13, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a,
0x01, 0x00, 0x00, 0x1f, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01,
0x00, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00,
0x18, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00}},
{TLS1_1_VERSION,
{0x16, 0x03, 0x01, 0x00, 0x5a, 0x01, 0x00, 0x00, 0x56, 0x03, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xc0, 0x09,
0xc0, 0x13, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a,
0x01, 0x00, 0x00, 0x1f, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01,
0x00, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00,
0x18, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00}},
{TLS1_2_VERSION,
{0x16, 0x03, 0x01, 0x00, 0x82, 0x01, 0x00, 0x00, 0x7e, 0x03, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xcc, 0xa9,
0xcc, 0xa8, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xc0, 0x09,
0xc0, 0x13, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f,
0x00, 0x35, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x37, 0x00, 0x17, 0x00, 0x00,
0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00,
0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00,
0x23, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 0x08,
0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06,
0x01, 0x02, 0x01}},
// TODO(davidben): Add a change detector for TLS 1.3 once the spec and our
// implementation has settled enough that it won't change.
};
for (const auto &t : kTests) {
SCOPED_TRACE(t.max_version);
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
ASSERT_TRUE(ctx);
// Our default cipher list varies by CPU capabilities, so manually place the
// ChaCha20 ciphers in front.
const char *cipher_list = "CHACHA20:ALL";
ASSERT_TRUE(SSL_CTX_set_max_proto_version(ctx.get(), t.max_version));
ASSERT_TRUE(SSL_CTX_set_strict_cipher_list(ctx.get(), cipher_list));
bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
ASSERT_TRUE(ssl);
std::vector<uint8_t> client_hello;
ASSERT_TRUE(GetClientHello(ssl.get(), &client_hello));
// Zero the client_random.
constexpr size_t kRandomOffset = 1 + 2 + 2 + // record header
1 + 3 + // handshake message header
2; // client_version
ASSERT_GE(client_hello.size(), kRandomOffset + SSL3_RANDOM_SIZE);
OPENSSL_memset(client_hello.data() + kRandomOffset, 0, SSL3_RANDOM_SIZE);
if (client_hello != t.expected) {
ADD_FAILURE() << "ClientHellos did not match.";
// Print the value manually so it is easier to update the test vector.
for (size_t i = 0; i < client_hello.size(); i += 12) {
printf(" %c", i == 0 ? '{' : ' ');
for (size_t j = i; j < client_hello.size() && j < i + 12; j++) {
if (j > i) {
printf(" ");
}
printf("0x%02x", client_hello[j]);
if (j < client_hello.size() - 1) {
printf(",");
}
}
if (i + 12 >= client_hello.size()) {
printf("}},");
}
printf("\n");
}
}
}
}
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);
}
static void ExpectSessionReused(SSL_CTX *client_ctx, SSL_CTX *server_ctx,
SSL_SESSION *session, bool want_reused) {
bssl::UniquePtr<SSL> client, server;
ClientConfig config;
config.session = session;
EXPECT_TRUE(
ConnectClientAndServer(&client, &server, client_ctx, server_ctx, config));
EXPECT_EQ(SSL_session_reused(client.get()), SSL_session_reused(server.get()));
bool was_reused = !!SSL_session_reused(client.get());
EXPECT_EQ(was_reused, want_reused);
}
static bssl::UniquePtr<SSL_SESSION> ExpectSessionRenewed(SSL_CTX *client_ctx,
SSL_CTX *server_ctx,
SSL_SESSION *session) {
g_last_session = nullptr;
SSL_CTX_sess_set_new_cb(client_ctx, SaveLastSession);
bssl::UniquePtr<SSL> client, server;
ClientConfig config;
config.session = session;
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;
}
if (SSL_session_reused(client.get()) != SSL_session_reused(server.get())) {
fprintf(stderr, "Client and server were inconsistent.\n");
return nullptr;
}
if (!SSL_session_reused(client.get())) {
fprintf(stderr, "Session was not reused.\n");
return nullptr;
}
SSL_CTX_sess_set_new_cb(client_ctx, nullptr);
if (!g_last_session) {
fprintf(stderr, "Client did not receive a renewed session.\n");
return nullptr;
}
return std::move(g_last_session);
}
static void ExpectTicketKeyChanged(SSL_CTX *ctx, uint8_t *inout_key,
bool changed) {
uint8_t new_key[kTicketKeyLen];
// May return 0, 1 or 48.
ASSERT_EQ(SSL_CTX_get_tlsext_ticket_keys(ctx, new_key, kTicketKeyLen), 1);
if (changed) {
ASSERT_NE(Bytes(inout_key, kTicketKeyLen), Bytes(new_key));
} else {
ASSERT_EQ(Bytes(inout_key, kTicketKeyLen), Bytes(new_key));
}
OPENSSL_memcpy(inout_key, new_key, kTicketKeyLen);
}
static int SwitchSessionIDContextSNI(SSL *ssl, int *out_alert, void *arg) {
static const uint8_t kContext[] = {3};
if (!SSL_set_session_id_context(ssl, kContext, sizeof(kContext))) {
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
return SSL_TLSEXT_ERR_OK;
}
TEST_P(SSLVersionTest, SessionIDContext) {
static const uint8_t kContext1[] = {1};
static const uint8_t kContext2[] = {2};
ASSERT_TRUE(SSL_CTX_set_session_id_context(server_ctx_.get(), kContext1,
sizeof(kContext1)));
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
bssl::UniquePtr<SSL_SESSION> session =
CreateClientSession(client_ctx_.get(), server_ctx_.get());
ASSERT_TRUE(session);
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
session.get(),
true /* expect session reused */));
// Change the session ID context.
ASSERT_TRUE(SSL_CTX_set_session_id_context(server_ctx_.get(), kContext2,
sizeof(kContext2)));
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
session.get(),
false /* expect session not reused */));
// Change the session ID context back and install an SNI callback to switch
// it.
ASSERT_TRUE(SSL_CTX_set_session_id_context(server_ctx_.get(), kContext1,
sizeof(kContext1)));
SSL_CTX_set_tlsext_servername_callback(server_ctx_.get(),
SwitchSessionIDContextSNI);
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
session.get(),
false /* expect session not reused */));
// Switch the session ID context with the early callback instead.
SSL_CTX_set_tlsext_servername_callback(server_ctx_.get(), nullptr);
SSL_CTX_set_select_certificate_cb(
server_ctx_.get(),
[](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
static const uint8_t kContext[] = {3};
if (!SSL_set_session_id_context(client_hello->ssl, kContext,
sizeof(kContext))) {
return ssl_select_cert_error;
}
return ssl_select_cert_success;
});
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
session.get(),
false /* expect session not reused */));
}
static timeval g_current_time;
static void CurrentTimeCallback(const SSL *ssl, timeval *out_clock) {
*out_clock = g_current_time;
}
static void FrozenTimeCallback(const SSL *ssl, timeval *out_clock) {
out_clock->tv_sec = 1000;
out_clock->tv_usec = 0;
}
static int RenewTicketCallback(SSL *ssl, uint8_t *key_name, uint8_t *iv,
EVP_CIPHER_CTX *ctx, HMAC_CTX *hmac_ctx,
int encrypt) {
static const uint8_t kZeros[16] = {0};
if (encrypt) {
OPENSSL_memcpy(key_name, kZeros, sizeof(kZeros));
RAND_bytes(iv, 16);
} else if (OPENSSL_memcmp(key_name, kZeros, 16) != 0) {
return 0;
}
if (!HMAC_Init_ex(hmac_ctx, kZeros, sizeof(kZeros), EVP_sha256(), NULL) ||
!EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, kZeros, iv, encrypt)) {
return -1;
}
// Returning two from the callback in decrypt mode renews the
// session in TLS 1.2 and below.
return encrypt ? 1 : 2;
}
static bool GetServerTicketTime(long *out, const SSL_SESSION *session) {
const uint8_t *ticket;
size_t ticket_len;
SSL_SESSION_get0_ticket(session, &ticket, &ticket_len);
if (ticket_len < 16 + 16 + SHA256_DIGEST_LENGTH) {
return false;
}
const uint8_t *ciphertext = ticket + 16 + 16;
size_t len = ticket_len - 16 - 16 - SHA256_DIGEST_LENGTH;
std::unique_ptr<uint8_t[]> plaintext(new uint8_t[len]);
#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
// Fuzzer-mode tickets are unencrypted.
OPENSSL_memcpy(plaintext.get(), ciphertext, len);
#else
static const uint8_t kZeros[16] = {0};
const uint8_t *iv = ticket + 16;
bssl::ScopedEVP_CIPHER_CTX ctx;
int len1, len2;
if (!EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_cbc(), nullptr, kZeros, iv) ||
!EVP_DecryptUpdate(ctx.get(), plaintext.get(), &len1, ciphertext, len) ||
!EVP_DecryptFinal_ex(ctx.get(), plaintext.get() + len1, &len2)) {
return false;
}
len = static_cast<size_t>(len1 + len2);
#endif
bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
if (!ssl_ctx) {
return false;
}
bssl::UniquePtr<SSL_SESSION> server_session(
SSL_SESSION_from_bytes(plaintext.get(), len, ssl_ctx.get()));
if (!server_session) {
return false;
}
*out = SSL_SESSION_get_time(server_session.get());
return true;
}
TEST_P(SSLVersionTest, SessionTimeout) {
for (bool server_test : {false, true}) {
SCOPED_TRACE(server_test);
ResetContexts();
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
static const time_t kStartTime = 1000;
g_current_time.tv_sec = kStartTime;
// We are willing to use a longer lifetime for TLS 1.3 sessions as
// resumptions still perform ECDHE.
const time_t timeout = version() == TLS1_3_VERSION
? SSL_DEFAULT_SESSION_PSK_DHE_TIMEOUT
: SSL_DEFAULT_SESSION_TIMEOUT;
// Both client and server must enforce session timeouts. We configure the
// other side with a frozen clock so it never expires tickets.
if (server_test) {
SSL_CTX_set_current_time_cb(client_ctx_.get(), FrozenTimeCallback);
SSL_CTX_set_current_time_cb(server_ctx_.get(), CurrentTimeCallback);
} else {
SSL_CTX_set_current_time_cb(client_ctx_.get(), CurrentTimeCallback);
SSL_CTX_set_current_time_cb(server_ctx_.get(), FrozenTimeCallback);
}
// Configure a ticket callback which renews tickets.
SSL_CTX_set_tlsext_ticket_key_cb(server_ctx_.get(), RenewTicketCallback);
bssl::UniquePtr<SSL_SESSION> session =
CreateClientSession(client_ctx_.get(), server_ctx_.get());
ASSERT_TRUE(session);
// Advance the clock just behind the timeout.
g_current_time.tv_sec += timeout - 1;
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
session.get(),
true /* expect session reused */));
// Advance the clock one more second.
g_current_time.tv_sec++;
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
session.get(),
false /* expect session not reused */));
// Rewind the clock to before the session was minted.
g_current_time.tv_sec = kStartTime - 1;
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
session.get(),
false /* expect session not reused */));
// Renew the session 10 seconds before expiration.
time_t new_start_time = kStartTime + timeout - 10;
g_current_time.tv_sec = new_start_time;
bssl::UniquePtr<SSL_SESSION> new_session = ExpectSessionRenewed(
client_ctx_.get(), server_ctx_.get(), session.get());
ASSERT_TRUE(new_session);
// This new session is not the same object as before.
EXPECT_NE(session.get(), new_session.get());
// Check the sessions have timestamps measured from issuance.
long session_time = 0;
if (server_test) {
ASSERT_TRUE(GetServerTicketTime(&session_time, new_session.get()));
} else {
session_time = SSL_SESSION_get_time(new_session.get());
}
ASSERT_EQ(session_time, g_current_time.tv_sec);
if (version() == TLS1_3_VERSION) {
// Renewal incorporates fresh key material in TLS 1.3, so we extend the
// lifetime TLS 1.3.
g_current_time.tv_sec = new_start_time + timeout - 1;
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
new_session.get(),
true /* expect session reused */));
// The new session expires after the new timeout.
g_current_time.tv_sec = new_start_time + timeout + 1;
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
new_session.get(),
false /* expect session ot reused */));
// Renew the session until it begins just past the auth timeout.
time_t auth_end_time = kStartTime + SSL_DEFAULT_SESSION_AUTH_TIMEOUT;
while (new_start_time < auth_end_time - 1000) {
// Get as close as possible to target start time.
new_start_time =
std::min(auth_end_time - 1000, new_start_time + timeout - 1);
g_current_time.tv_sec = new_start_time;
new_session = ExpectSessionRenewed(client_ctx_.get(), server_ctx_.get(),
new_session.get());
ASSERT_TRUE(new_session);
}
// Now the session's lifetime is bound by the auth timeout.
g_current_time.tv_sec = auth_end_time - 1;
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
new_session.get(),
true /* expect session reused */));
g_current_time.tv_sec = auth_end_time + 1;
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
new_session.get(),
false /* expect session ot reused */));
} else {
// The new session is usable just before the old expiration.
g_current_time.tv_sec = kStartTime + timeout - 1;
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
new_session.get(),
true /* expect session reused */));
// Renewal does not extend the lifetime, so it is not usable beyond the
// old expiration.
g_current_time.tv_sec = kStartTime + timeout + 1;
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
new_session.get(),
false /* expect session not reused */));
}
}
}
TEST_P(SSLVersionTest, DefaultTicketKeyInitialization) {
static const uint8_t kZeroKey[kTicketKeyLen] = {};
uint8_t ticket_key[kTicketKeyLen];
ASSERT_EQ(1, SSL_CTX_get_tlsext_ticket_keys(server_ctx_.get(), ticket_key,
kTicketKeyLen));
ASSERT_NE(0, OPENSSL_memcmp(ticket_key, kZeroKey, kTicketKeyLen));
}
TEST_P(SSLVersionTest, DefaultTicketKeyRotation) {
static const time_t kStartTime = 1001;
g_current_time.tv_sec = kStartTime;
// We use session reuse as a proxy for ticket decryption success, hence
// disable session timeouts.
SSL_CTX_set_timeout(server_ctx_.get(), std::numeric_limits<uint32_t>::max());
SSL_CTX_set_session_psk_dhe_timeout(server_ctx_.get(),
std::numeric_limits<uint32_t>::max());
SSL_CTX_set_current_time_cb(client_ctx_.get(), FrozenTimeCallback);
SSL_CTX_set_current_time_cb(server_ctx_.get(), CurrentTimeCallback);
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_OFF);
// Initialize ticket_key with the current key and check that it was
// initialized to something, not all zeros.
uint8_t ticket_key[kTicketKeyLen] = {0};
TRACED_CALL(ExpectTicketKeyChanged(server_ctx_.get(), ticket_key,
true /* changed */));
// Verify ticket resumption actually works.
bssl::UniquePtr<SSL> client, server;
bssl::UniquePtr<SSL_SESSION> session =
CreateClientSession(client_ctx_.get(), server_ctx_.get());
ASSERT_TRUE(session);
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
session.get(), true /* reused */));
// Advance time to just before key rotation.
g_current_time.tv_sec += SSL_DEFAULT_TICKET_KEY_ROTATION_INTERVAL - 1;
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
session.get(), true /* reused */));
TRACED_CALL(ExpectTicketKeyChanged(server_ctx_.get(), ticket_key,
false /* NOT changed */));
// Force key rotation.
g_current_time.tv_sec += 1;
bssl::UniquePtr<SSL_SESSION> new_session =
CreateClientSession(client_ctx_.get(), server_ctx_<