This GREASEs cipher suites, groups, and extensions. For now, we'll always place them in a hard-coded position. We can experiment with more interesting strategies later. If we add new ciphers and curves, presumably we prefer them over current ones, so place GREASE values at the front. This prevents implementations from parsing only the first value and ignoring the rest. Add two new extensions, one empty and one non-empty. Place the empty one in front (IBM WebSphere can't handle trailing empty extensions) and the non-empty one at the end. Change-Id: If2e009936bc298cedf2a7a593ce7d5d5ddbb841a Reviewed-on: https://boringssl-review.googlesource.com/11241 Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: Adam Langley <agl@google.com> CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>kris/onging/CECPQ3_patch15
@@ -3101,6 +3101,10 @@ OPENSSL_EXPORT const SSL_CIPHER *SSL_get_pending_cipher(const SSL *ssl); | |||||
OPENSSL_EXPORT void SSL_CTX_set_retain_only_sha256_of_client_certs(SSL_CTX *ctx, | OPENSSL_EXPORT void SSL_CTX_set_retain_only_sha256_of_client_certs(SSL_CTX *ctx, | ||||
int enable); | int enable); | ||||
/* SSL_CTX_set_grease_enabled configures whether client sockets on |ctx| should | |||||
* enable GREASE. See draft-davidben-tls-grease-01. */ | |||||
OPENSSL_EXPORT void SSL_CTX_set_grease_enabled(SSL_CTX *ctx, int enabled); | |||||
/* Deprecated functions. */ | /* Deprecated functions. */ | ||||
@@ -3992,11 +3996,15 @@ struct ssl_ctx_st { | |||||
/* If true, a client will request certificate timestamps. */ | /* If true, a client will request certificate timestamps. */ | ||||
unsigned signed_cert_timestamps_enabled:1; | unsigned signed_cert_timestamps_enabled:1; | ||||
/* tlsext_channel_id_enabled is copied from the |SSL_CTX|. For a server, | |||||
* means that we'll accept Channel IDs from clients. For a client, means that | |||||
* we'll advertise support. */ | |||||
/* tlsext_channel_id_enabled is one if Channel ID is enabled and zero | |||||
* otherwise. For a server, means that we'll accept Channel IDs from clients. | |||||
* For a client, means that we'll advertise support. */ | |||||
unsigned tlsext_channel_id_enabled:1; | unsigned tlsext_channel_id_enabled:1; | ||||
/* grease_enabled is one if draft-davidben-tls-grease-01 is enabled and zero | |||||
* otherwise. */ | |||||
unsigned grease_enabled:1; | |||||
/* extra_certs is a dummy value included for compatibility. | /* extra_certs is a dummy value included for compatibility. | ||||
* TODO(agl): remove once node.js no longer references this. */ | * TODO(agl): remove once node.js no longer references this. */ | ||||
STACK_OF(X509)* extra_certs; | STACK_OF(X509)* extra_certs; | ||||
@@ -579,6 +579,18 @@ end: | |||||
return ret; | return ret; | ||||
} | } | ||||
uint16_t ssl_get_grease_value(const SSL *ssl, enum ssl_grease_index_t index) { | |||||
/* Use the client_random for entropy. This both avoids calling |RAND_bytes| on | |||||
* a single byte repeatedly and ensures the values are deterministic. This | |||||
* allows the same ClientHello be sent twice for a HelloRetryRequest or the | |||||
* same group be advertised in both supported_groups and key_shares. */ | |||||
uint16_t ret = ssl->s3->client_random[index]; | |||||
/* This generates a random value of the form 0xωaωa, for all 0 ≤ ω < 16. */ | |||||
ret = (ret & 0xf0) | 0x0a; | |||||
ret |= ret << 8; | |||||
return ret; | |||||
} | |||||
static int ssl_write_client_cipher_list(SSL *ssl, CBB *out, | static int ssl_write_client_cipher_list(SSL *ssl, CBB *out, | ||||
uint16_t min_version, | uint16_t min_version, | ||||
uint16_t max_version) { | uint16_t max_version) { | ||||
@@ -590,6 +602,12 @@ static int ssl_write_client_cipher_list(SSL *ssl, CBB *out, | |||||
return 0; | return 0; | ||||
} | } | ||||
/* Add a fake cipher suite. See draft-davidben-tls-grease-01. */ | |||||
if (ssl->ctx->grease_enabled && | |||||
!CBB_add_u16(&child, ssl_get_grease_value(ssl, ssl_grease_cipher))) { | |||||
return 0; | |||||
} | |||||
STACK_OF(SSL_CIPHER) *ciphers = SSL_get_ciphers(ssl); | STACK_OF(SSL_CIPHER) *ciphers = SSL_get_ciphers(ssl); | ||||
int any_enabled = 0; | int any_enabled = 0; | ||||
@@ -1014,6 +1014,22 @@ int ssl_client_cipher_list_contains_cipher( | |||||
const struct ssl_early_callback_ctx *client_hello, uint16_t id); | const struct ssl_early_callback_ctx *client_hello, uint16_t id); | ||||
/* GREASE. */ | |||||
enum ssl_grease_index_t { | |||||
ssl_grease_cipher = 0, | |||||
ssl_grease_group, | |||||
ssl_grease_extension1, | |||||
ssl_grease_extension2, | |||||
}; | |||||
/* ssl_get_grease_value returns a GREASE value for |ssl|. For a given | |||||
* connection, the values for each index will be deterministic. This allows the | |||||
* same ClientHello be sent twice for a HelloRetryRequest or the same group be | |||||
* advertised in both supported_groups and key_shares. */ | |||||
uint16_t ssl_get_grease_value(const SSL *ssl, enum ssl_grease_index_t index); | |||||
/* Underdocumented functions. | /* Underdocumented functions. | ||||
* | * | ||||
* Functions below here haven't been touched up and may be underdocumented. */ | * Functions below here haven't been touched up and may be underdocumented. */ | ||||
@@ -2884,6 +2884,10 @@ void SSL_CTX_set_retain_only_sha256_of_client_certs(SSL_CTX *ctx, int enabled) { | |||||
ctx->retain_only_sha256_of_client_certs = !!enabled; | ctx->retain_only_sha256_of_client_certs = !!enabled; | ||||
} | } | ||||
void SSL_CTX_set_grease_enabled(SSL_CTX *ctx, int enabled) { | |||||
ctx->grease_enabled = !!enabled; | |||||
} | |||||
int SSL_clear(SSL *ssl) { | int SSL_clear(SSL *ssl) { | ||||
if (ssl->method == NULL) { | if (ssl->method == NULL) { | ||||
OPENSSL_PUT_ERROR(SSL, SSL_R_NO_METHOD_SPECIFIED); | OPENSSL_PUT_ERROR(SSL, SSL_R_NO_METHOD_SPECIFIED); | ||||
@@ -2106,6 +2106,15 @@ static int ext_key_share_add_clienthello(SSL *ssl, CBB *out) { | |||||
group_id = ssl->s3->hs->retry_group; | group_id = ssl->s3->hs->retry_group; | ||||
} else { | } else { | ||||
/* Add a fake group. See draft-davidben-tls-grease-01. */ | |||||
if (ssl->ctx->grease_enabled && | |||||
(!CBB_add_u16(&kse_bytes, | |||||
ssl_get_grease_value(ssl, ssl_grease_group)) || | |||||
!CBB_add_u16(&kse_bytes, 1 /* length */) || | |||||
!CBB_add_u8(&kse_bytes, 0 /* one byte key share */))) { | |||||
return 0; | |||||
} | |||||
/* Predict the most preferred group. */ | /* Predict the most preferred group. */ | ||||
const uint16_t *groups; | const uint16_t *groups; | ||||
size_t groups_len; | size_t groups_len; | ||||
@@ -2293,6 +2302,13 @@ static int ext_supported_groups_add_clienthello(SSL *ssl, CBB *out) { | |||||
return 0; | return 0; | ||||
} | } | ||||
/* Add a fake group. See draft-davidben-tls-grease-01. */ | |||||
if (ssl->ctx->grease_enabled && | |||||
!CBB_add_u16(&groups_bytes, | |||||
ssl_get_grease_value(ssl, ssl_grease_group))) { | |||||
return 0; | |||||
} | |||||
const uint16_t *groups; | const uint16_t *groups; | ||||
size_t groups_len; | size_t groups_len; | ||||
tls1_get_grouplist(ssl, 0, &groups, &groups_len); | tls1_get_grouplist(ssl, 0, &groups, &groups_len); | ||||
@@ -2546,6 +2562,16 @@ int ssl_add_clienthello_tlsext(SSL *ssl, CBB *out, size_t header_len) { | |||||
} | } | ||||
} | } | ||||
uint16_t grease_ext1 = 0; | |||||
if (ssl->ctx->grease_enabled) { | |||||
/* Add a fake empty extension. See draft-davidben-tls-grease-01. */ | |||||
grease_ext1 = ssl_get_grease_value(ssl, ssl_grease_extension1); | |||||
if (!CBB_add_u16(&extensions, grease_ext1) || | |||||
!CBB_add_u16(&extensions, 0 /* zero length */)) { | |||||
goto err; | |||||
} | |||||
} | |||||
for (size_t i = 0; i < kNumExtensions; i++) { | for (size_t i = 0; i < kNumExtensions; i++) { | ||||
const size_t len_before = CBB_len(&extensions); | const size_t len_before = CBB_len(&extensions); | ||||
if (!kExtensions[i].add_clienthello(ssl, &extensions)) { | if (!kExtensions[i].add_clienthello(ssl, &extensions)) { | ||||
@@ -2563,6 +2589,24 @@ int ssl_add_clienthello_tlsext(SSL *ssl, CBB *out, size_t header_len) { | |||||
goto err; | goto err; | ||||
} | } | ||||
if (ssl->ctx->grease_enabled) { | |||||
/* Add a fake non-empty extension. See draft-davidben-tls-grease-01. */ | |||||
uint16_t grease_ext2 = ssl_get_grease_value(ssl, ssl_grease_extension2); | |||||
/* The two fake extensions must not have the same value. GREASE values are | |||||
* of the form 0x1a1a, 0x2a2a, 0x3a3a, etc., so XOR to generate a different | |||||
* one. */ | |||||
if (grease_ext1 == grease_ext2) { | |||||
grease_ext2 ^= 0x1010; | |||||
} | |||||
if (!CBB_add_u16(&extensions, grease_ext2) || | |||||
!CBB_add_u16(&extensions, 1 /* one byte length */) || | |||||
!CBB_add_u8(&extensions, 0 /* single zero byte as contents */)) { | |||||
goto err; | |||||
} | |||||
} | |||||
if (!SSL_is_dtls(ssl)) { | if (!SSL_is_dtls(ssl)) { | ||||
header_len += 2 + CBB_len(&extensions); | header_len += 2 + CBB_len(&extensions); | ||||
if (header_len > 0xff && header_len < 0x200) { | if (header_len > 0xff && header_len < 0x200) { | ||||
@@ -942,6 +942,10 @@ static bssl::UniquePtr<SSL_CTX> SetupCtx(const TestConfig *config) { | |||||
SSL_CTX_set_client_CA_list(ssl_ctx.get(), nullptr); | SSL_CTX_set_client_CA_list(ssl_ctx.get(), nullptr); | ||||
} | } | ||||
if (config->enable_grease) { | |||||
SSL_CTX_set_grease_enabled(ssl_ctx.get(), 1); | |||||
} | |||||
return ssl_ctx; | return ssl_ctx; | ||||
} | } | ||||
@@ -1084,6 +1084,10 @@ type ProtocolBugs struct { | |||||
// InvalidChannelIDSignature, if true, causes the client to generate an | // InvalidChannelIDSignature, if true, causes the client to generate an | ||||
// invalid Channel ID signature. | // invalid Channel ID signature. | ||||
InvalidChannelIDSignature bool | InvalidChannelIDSignature bool | ||||
// ExpectGREASE, if true, causes the server to reject a ClientHello | |||||
// unless it contains GREASE values. See draft-davidben-tls-grease-01. | |||||
ExpectGREASE bool | |||||
} | } | ||||
func (c *Config) serverInit() { | func (c *Config) serverInit() { | ||||
@@ -159,6 +159,7 @@ type clientHelloMsg struct { | |||||
srtpMasterKeyIdentifier string | srtpMasterKeyIdentifier string | ||||
sctListSupported bool | sctListSupported bool | ||||
customExtension string | customExtension string | ||||
hasGREASEExtension bool | |||||
} | } | ||||
func (m *clientHelloMsg) equal(i interface{}) bool { | func (m *clientHelloMsg) equal(i interface{}) bool { | ||||
@@ -199,7 +200,8 @@ func (m *clientHelloMsg) equal(i interface{}) bool { | |||||
eqUint16s(m.srtpProtectionProfiles, m1.srtpProtectionProfiles) && | eqUint16s(m.srtpProtectionProfiles, m1.srtpProtectionProfiles) && | ||||
m.srtpMasterKeyIdentifier == m1.srtpMasterKeyIdentifier && | m.srtpMasterKeyIdentifier == m1.srtpMasterKeyIdentifier && | ||||
m.sctListSupported == m1.sctListSupported && | m.sctListSupported == m1.sctListSupported && | ||||
m.customExtension == m1.customExtension | |||||
m.customExtension == m1.customExtension && | |||||
m.hasGREASEExtension == m1.hasGREASEExtension | |||||
} | } | ||||
func (m *clientHelloMsg) marshal() []byte { | func (m *clientHelloMsg) marshal() []byte { | ||||
@@ -705,6 +707,10 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { | |||||
m.customExtension = string(data[:length]) | m.customExtension = string(data[:length]) | ||||
} | } | ||||
data = data[length:] | data = data[length:] | ||||
if isGREASEValue(extension) { | |||||
m.hasGREASEExtension = true | |||||
} | |||||
} | } | ||||
return true | return true | ||||
@@ -246,11 +246,13 @@ func (hs *serverHandshakeState) readClientHello() error { | |||||
} | } | ||||
c.haveVers = true | c.haveVers = true | ||||
var scsvFound bool | |||||
var scsvFound, greaseFound bool | |||||
for _, cipherSuite := range hs.clientHello.cipherSuites { | for _, cipherSuite := range hs.clientHello.cipherSuites { | ||||
if cipherSuite == fallbackSCSV { | if cipherSuite == fallbackSCSV { | ||||
scsvFound = true | scsvFound = true | ||||
break | |||||
} | |||||
if isGREASEValue(cipherSuite) { | |||||
greaseFound = true | |||||
} | } | ||||
} | } | ||||
@@ -260,6 +262,36 @@ func (hs *serverHandshakeState) readClientHello() error { | |||||
return errors.New("tls: fallback SCSV found when not expected") | return errors.New("tls: fallback SCSV found when not expected") | ||||
} | } | ||||
if !greaseFound && config.Bugs.ExpectGREASE { | |||||
return errors.New("tls: no GREASE cipher suite value found") | |||||
} | |||||
greaseFound = false | |||||
for _, curve := range hs.clientHello.supportedCurves { | |||||
if isGREASEValue(uint16(curve)) { | |||||
greaseFound = true | |||||
break | |||||
} | |||||
} | |||||
if !greaseFound && config.Bugs.ExpectGREASE { | |||||
return errors.New("tls: no GREASE curve value found") | |||||
} | |||||
if len(hs.clientHello.keyShares) > 0 { | |||||
greaseFound = false | |||||
for _, keyShare := range hs.clientHello.keyShares { | |||||
if isGREASEValue(uint16(keyShare.group)) { | |||||
greaseFound = true | |||||
break | |||||
} | |||||
} | |||||
if !greaseFound && config.Bugs.ExpectGREASE { | |||||
return errors.New("tls: no GREASE curve value found") | |||||
} | |||||
} | |||||
if config.Bugs.IgnorePeerSignatureAlgorithmPreferences { | if config.Bugs.IgnorePeerSignatureAlgorithmPreferences { | ||||
hs.clientHello.signatureAlgorithms = config.signSignatureAlgorithms() | hs.clientHello.signatureAlgorithms = config.signSignatureAlgorithms() | ||||
} | } | ||||
@@ -1002,6 +1034,10 @@ func (hs *serverHandshakeState) processClientExtensions(serverExtensions *server | |||||
serverExtensions.ticketSupported = true | serverExtensions.ticketSupported = true | ||||
} | } | ||||
if !hs.clientHello.hasGREASEExtension && config.Bugs.ExpectGREASE { | |||||
return errors.New("tls: no GREASE extension found") | |||||
} | |||||
return nil | return nil | ||||
} | } | ||||
@@ -1673,3 +1709,7 @@ func isTLS12Cipher(id uint16) bool { | |||||
// Unknown cipher. | // Unknown cipher. | ||||
return false | return false | ||||
} | } | ||||
func isGREASEValue(val uint16) bool { | |||||
return val&0x0f0f == 0x0a0a && val&0xff == val >> 8 | |||||
} |
@@ -2309,6 +2309,26 @@ func addBasicTests() { | |||||
expectedError: ":INVALID_COMPRESSION_LIST:", | expectedError: ":INVALID_COMPRESSION_LIST:", | ||||
expectedLocalError: "remote error: illegal parameter", | expectedLocalError: "remote error: illegal parameter", | ||||
}, | }, | ||||
{ | |||||
name: "GREASE-TLS12", | |||||
config: Config{ | |||||
MaxVersion: VersionTLS12, | |||||
Bugs: ProtocolBugs{ | |||||
ExpectGREASE: true, | |||||
}, | |||||
}, | |||||
flags: []string{"-enable-grease"}, | |||||
}, | |||||
{ | |||||
name: "GREASE-TLS13", | |||||
config: Config{ | |||||
MaxVersion: VersionTLS13, | |||||
Bugs: ProtocolBugs{ | |||||
ExpectGREASE: true, | |||||
}, | |||||
}, | |||||
flags: []string{"-enable-grease"}, | |||||
}, | |||||
} | } | ||||
testCases = append(testCases, basicTests...) | testCases = append(testCases, basicTests...) | ||||
} | } | ||||
@@ -105,6 +105,7 @@ const Flag<bool> kBoolFlags[] = { | |||||
{ "-use-null-client-ca-list", &TestConfig::use_null_client_ca_list }, | { "-use-null-client-ca-list", &TestConfig::use_null_client_ca_list }, | ||||
{ "-send-alert", &TestConfig::send_alert }, | { "-send-alert", &TestConfig::send_alert }, | ||||
{ "-peek-then-read", &TestConfig::peek_then_read }, | { "-peek-then-read", &TestConfig::peek_then_read }, | ||||
{ "-enable-grease", &TestConfig::enable_grease }, | |||||
}; | }; | ||||
const Flag<std::string> kStringFlags[] = { | const Flag<std::string> kStringFlags[] = { | ||||
@@ -113,6 +113,7 @@ struct TestConfig { | |||||
bool use_null_client_ca_list = false; | bool use_null_client_ca_list = false; | ||||
bool send_alert = false; | bool send_alert = false; | ||||
bool peek_then_read = false; | bool peek_then_read = false; | ||||
bool enable_grease = false; | |||||
}; | }; | ||||
bool ParseConfig(int argc, char **argv, TestConfig *out_config); | bool ParseConfig(int argc, char **argv, TestConfig *out_config); | ||||
@@ -88,6 +88,10 @@ static const struct argument kArguments[] = { | |||||
"A STARTTLS mini-protocol to run before the TLS handshake. Supported" | "A STARTTLS mini-protocol to run before the TLS handshake. Supported" | ||||
" values: 'smtp'", | " values: 'smtp'", | ||||
}, | }, | ||||
{ | |||||
"-grease", kBooleanArgument, | |||||
"Enable GREASE", | |||||
}, | |||||
{ | { | ||||
"", kOptionalArgument, "", | "", kOptionalArgument, "", | ||||
}, | }, | ||||
@@ -269,6 +273,10 @@ bool Client(const std::vector<std::string> &args) { | |||||
SSL_CTX_sess_set_new_cb(ctx.get(), NewSessionCallback); | SSL_CTX_sess_set_new_cb(ctx.get(), NewSessionCallback); | ||||
} | } | ||||
if (args_map.count("-grease") != 0) { | |||||
SSL_CTX_set_grease_enabled(ctx.get(), 1); | |||||
} | |||||
int sock = -1; | int sock = -1; | ||||
if (!Connect(&sock, args_map["-connect"])) { | if (!Connect(&sock, args_map["-connect"])) { | ||||
return false; | return false; | ||||