Serialize SSL configuration in handoff and check it on application.

A split SSL handshake may involve 2 binaries, potentially built at
different versions: call them the "handoff/handback" binary and the
"handshake" binary.  We would like to guarantee that the
handoff/handback binary does not make any promises that the handshake
binary cannot keep.

As a start, this commit serializes |kCiphers| to the handoff message.
When the handoff message is applied to an |SSL|, any configured
ciphers not listed in the handoff message will be removed, in order to
prevent them from being negotiated.

Subsequent commits will apply the same approach to other lists of features.

Change-Id: Idf6dbeadb750c076ab0509c09b9d3f22eb162b9c
Reviewed-on: https://boringssl-review.googlesource.com/c/29264
Reviewed-by: Matt Braithwaite <mab@google.com>
This commit is contained in:
Matthew Braithwaite 2018-07-10 16:27:22 -07:00 committed by Matt Braithwaite
parent e62bf02a13
commit d2ed382e64
4 changed files with 134 additions and 7 deletions

View File

@ -24,6 +24,22 @@ BSSL_NAMESPACE_BEGIN
constexpr int kHandoffVersion = 0;
constexpr int kHandbackVersion = 0;
// serialize_features adds a description of features supported by this binary to
// |out|. Returns true on success and false on error.
static bool serialize_features(CBB *out) {
CBB ciphers;
if (!CBB_add_asn1(out, &ciphers, CBS_ASN1_OCTETSTRING)) {
return false;
}
Span<const SSL_CIPHER> all_ciphers = AllCiphers();
for (const SSL_CIPHER& cipher : all_ciphers) {
if (!CBB_add_u16(&ciphers, static_cast<uint16_t>(cipher.id))) {
return false;
}
}
return CBB_flush(out);
}
bool SSL_serialize_handoff(const SSL *ssl, CBB *out) {
const SSL3_STATE *const s3 = ssl->s3;
if (!ssl->server ||
@ -40,6 +56,7 @@ bool SSL_serialize_handoff(const SSL *ssl, CBB *out) {
!CBB_add_asn1_octet_string(&seq,
reinterpret_cast<uint8_t *>(s3->hs_buf->data),
s3->hs_buf->length) ||
!serialize_features(&seq) ||
!CBB_flush(out)) {
return false;
}
@ -59,6 +76,53 @@ bool SSL_decline_handoff(SSL *ssl) {
return true;
}
// apply_remote_features reads a list of supported features from |in| and
// (possibly) reconfigures |ssl| to disallow the negotation of features whose
// support has not been indicated. (This prevents the the handshake from
// committing to features that are not supported on the handoff/handback side.)
static bool apply_remote_features(SSL *ssl, CBS *in) {
CBS ciphers;
if (!CBS_get_asn1(in, &ciphers, CBS_ASN1_OCTETSTRING)) {
return false;
}
bssl::UniquePtr<STACK_OF(SSL_CIPHER)> supported(sk_SSL_CIPHER_new_null());
while (CBS_len(&ciphers)) {
uint16_t id;
if (!CBS_get_u16(&ciphers, &id)) {
return false;
}
const SSL_CIPHER *cipher = SSL_get_cipher_by_value(id);
if (!cipher) {
continue;
}
if (!sk_SSL_CIPHER_push(supported.get(), cipher)) {
return false;
}
}
STACK_OF(SSL_CIPHER) *configured =
ssl->config->cipher_list ? ssl->config->cipher_list->ciphers.get()
: ssl->ctx->cipher_list->ciphers.get();
bssl::UniquePtr<STACK_OF(SSL_CIPHER)> unsupported(sk_SSL_CIPHER_new_null());
for (const SSL_CIPHER *configured_cipher : configured) {
if (sk_SSL_CIPHER_find(supported.get(), nullptr, configured_cipher)) {
continue;
}
if (!sk_SSL_CIPHER_push(unsupported.get(), configured_cipher)) {
return false;
}
}
if (sk_SSL_CIPHER_num(unsupported.get()) && !ssl->config->cipher_list) {
ssl->config->cipher_list = bssl::MakeUnique<SSLCipherPreferenceList>();
if (!ssl->config->cipher_list->Init(*ssl->ctx->cipher_list)) {
return false;
}
}
for (const SSL_CIPHER *unsupported_cipher : unsupported.get()) {
ssl->config->cipher_list->Remove(unsupported_cipher);
}
return sk_SSL_CIPHER_num(SSL_get_ciphers(ssl)) > 0;
}
bool SSL_apply_handoff(SSL *ssl, Span<const uint8_t> handoff) {
if (ssl->method->is_dtls) {
return false;
@ -74,7 +138,8 @@ bool SSL_apply_handoff(SSL *ssl, Span<const uint8_t> handoff) {
CBS transcript, hs_buf;
if (!CBS_get_asn1(&seq, &transcript, CBS_ASN1_OCTETSTRING) ||
!CBS_get_asn1(&seq, &hs_buf, CBS_ASN1_OCTETSTRING)) {
!CBS_get_asn1(&seq, &hs_buf, CBS_ASN1_OCTETSTRING) ||
!apply_remote_features(ssl, &seq)) {
return false;
}

View File

@ -509,11 +509,17 @@ struct SSLCipherPreferenceList {
bool Init(UniquePtr<STACK_OF(SSL_CIPHER)> ciphers,
Span<const bool> in_group_flags);
bool Init(const SSLCipherPreferenceList &);
void Remove(const SSL_CIPHER *cipher);
UniquePtr<STACK_OF(SSL_CIPHER)> ciphers;
bool *in_group_flags = nullptr;
};
// AllCiphers returns an array of all supported ciphers, sorted by id.
Span<const SSL_CIPHER> AllCiphers();
// ssl_cipher_get_evp_aead sets |*out_aead| to point to the correct EVP_AEAD
// object for |cipher| protocol version |version|. It sets |*out_mac_secret_len|
// and |*out_fixed_iv_len| to the MAC key length and fixed IV length,

View File

@ -156,7 +156,6 @@
BSSL_NAMESPACE_BEGIN
// kCiphers is an array of all supported ciphers, sorted by id.
static constexpr SSL_CIPHER kCiphers[] = {
// The RSA ciphers
// Cipher 02
@ -464,7 +463,9 @@ static constexpr SSL_CIPHER kCiphers[] = {
};
static const size_t kCiphersLen = OPENSSL_ARRAY_SIZE(kCiphers);
Span<const SSL_CIPHER> AllCiphers() {
return MakeConstSpan(kCiphers, OPENSSL_ARRAY_SIZE(kCiphers));
}
#define CIPHER_ADD 1
#define CIPHER_KILL 2
@ -707,7 +708,7 @@ static bool ssl_cipher_collect_ciphers(Array<CIPHER_ORDER> *out_co_list,
CIPHER_ORDER **out_head,
CIPHER_ORDER **out_tail) {
Array<CIPHER_ORDER> co_list;
if (!co_list.Init(kCiphersLen)) {
if (!co_list.Init(OPENSSL_ARRAY_SIZE(kCiphers))) {
return false;
}
@ -772,6 +773,31 @@ bool SSLCipherPreferenceList::Init(UniquePtr<STACK_OF(SSL_CIPHER)> ciphers_arg,
return true;
}
bool SSLCipherPreferenceList::Init(const SSLCipherPreferenceList& other) {
size_t size = sk_SSL_CIPHER_num(other.ciphers.get());
Span<const bool> other_flags(other.in_group_flags, size);
UniquePtr<STACK_OF(SSL_CIPHER)> other_ciphers(sk_SSL_CIPHER_dup(
other.ciphers.get()));
if (!other_ciphers) {
return false;
}
return Init(std::move(other_ciphers), other_flags);
}
void SSLCipherPreferenceList::Remove(const SSL_CIPHER *cipher) {
size_t index;
if (!sk_SSL_CIPHER_find(ciphers.get(), &index, cipher)) {
return;
}
if (!in_group_flags[index] /* last element of group */ && index > 0) {
in_group_flags[index-1] = false;
}
for (size_t i = index; i < sk_SSL_CIPHER_num(ciphers.get()) - 1; ++i) {
in_group_flags[i] = in_group_flags[i+1];
}
sk_SSL_CIPHER_delete(ciphers.get(), index);
}
// ssl_cipher_apply_rule applies the rule type |rule| to ciphers matching its
// parameters in the linked list from |*head_p| to |*tail_p|. It writes the new
// head and tail of the list to |*head_p| and |*tail_p|, respectively.
@ -1051,7 +1077,7 @@ static bool ssl_cipher_process_rulestr(const char *rule_str,
// Look for a matching exact cipher. These aren't allowed in multipart
// rules.
if (!multi && ch != '+') {
for (j = 0; j < kCiphersLen; j++) {
for (j = 0; j < OPENSSL_ARRAY_SIZE(kCiphers); j++) {
const SSL_CIPHER *cipher = &kCiphers[j];
if (rule_equals(cipher->name, buf, buf_len) ||
rule_equals(cipher->standard_name, buf, buf_len)) {
@ -1217,7 +1243,7 @@ bool ssl_create_cipher_list(UniquePtr<SSLCipherPreferenceList> *out_cipher_list,
UniquePtr<STACK_OF(SSL_CIPHER)> cipherstack(sk_SSL_CIPHER_new_null());
Array<bool> in_group_flags;
if (cipherstack == nullptr ||
!in_group_flags.Init(kCiphersLen)) {
!in_group_flags.Init(OPENSSL_ARRAY_SIZE(kCiphers))) {
return false;
}
@ -1345,7 +1371,8 @@ const SSL_CIPHER *SSL_get_cipher_by_value(uint16_t value) {
c.id = 0x03000000L | value;
return reinterpret_cast<const SSL_CIPHER *>(bsearch(
&c, kCiphers, kCiphersLen, sizeof(SSL_CIPHER), ssl_cipher_id_cmp));
&c, kCiphers, OPENSSL_ARRAY_SIZE(kCiphers), sizeof(SSL_CIPHER),
ssl_cipher_id_cmp));
}
uint32_t SSL_CIPHER_get_id(const SSL_CIPHER *cipher) { return cipher->id; }

View File

@ -4278,6 +4278,35 @@ TEST(SSLTest, SigAlgsList) {
}
}
TEST(SSLTest, ApplyHandoffRemovesUnsupportedCiphers) {
bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
bssl::UniquePtr<SSL> server(SSL_new(server_ctx.get()));
// handoff is a handoff message that has been artificially modified to pretend
// that only cipher 0x0A is supported. When it is applied to |server|, all
// ciphers but that one should be removed.
uint8_t handoff[] = {
0x30, 0x81, 0x8e, 0x02, 0x01, 0x00, 0x04, 0x00, 0x04, 0x81, 0x82, 0x01,
0x00, 0x00, 0x7e, 0x03, 0x03, 0x77, 0x62, 0x00, 0x9a, 0x13, 0x48, 0x23,
0x46, 0x11, 0x6c, 0x0b, 0x1c, 0x91, 0x4e, 0xbc, 0x1c, 0xff, 0x54, 0xb9,
0xe6, 0x3f, 0xa8, 0x8d, 0x49, 0x37, 0x7a, 0x9e, 0xbf, 0x36, 0xd5, 0x08,
0x24, 0x00, 0x00, 0x1e, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x0a, 0xc0, 0x14,
0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
0x00, 0x37, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x17, 0x00, 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, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x0a, 0x00,
0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x04, 0x02, 0x00,
0x0a,
};
EXPECT_EQ(20u, sk_SSL_CIPHER_num(SSL_get_ciphers(server.get())));
ASSERT_TRUE(
SSL_apply_handoff(server.get(), {handoff, OPENSSL_ARRAY_SIZE(handoff)}));
EXPECT_EQ(1u, sk_SSL_CIPHER_num(SSL_get_ciphers(server.get())));
}
TEST_P(SSLVersionTest, VerifyBeforeCertRequest) {
// Configure the server to request client certificates.
SSL_CTX_set_custom_verify(