Implement draft-vvv-tls-alps-01.

(Original CL by svaldez, reworked by davidben.)

Change-Id: I8570808fa5e96a1c9e6e03c4877039a22e73254f
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/42404
Reviewed-by: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
chromium-5359
Steven Valdez 4 years ago committed by CQ bot account: commit-bot@chromium.org
parent e580e9ded3
commit 51607f1fe1
  1. 2
      crypto/err/ssl.errordata
  2. 51
      include/openssl/ssl.h
  3. 4
      include/openssl/tls1.h
  4. 35
      ssl/handoff.cc
  5. 37
      ssl/internal.h
  6. 43
      ssl/ssl_asn1.cc
  7. 30
      ssl/ssl_lib.cc
  8. 19
      ssl/ssl_session.cc
  9. 4
      ssl/ssl_test.cc
  10. 227
      ssl/t1_lib.cc
  11. 29
      ssl/test/bssl_shim.cc
  12. 69
      ssl/test/runner/common.go
  13. 59
      ssl/test/runner/conn.go
  14. 1
      ssl/test/runner/fuzzer_mode.json
  15. 50
      ssl/test/runner/handshake_client.go
  16. 98
      ssl/test/runner/handshake_messages.go
  17. 54
      ssl/test/runner/handshake_server.go
  18. 500
      ssl/test/runner/runner.go
  19. 72
      ssl/test/runner/ticket.go
  20. 62
      ssl/test/test_config.cc
  21. 3
      ssl/test/test_config.h
  22. 64
      ssl/tls13_client.cc
  23. 163
      ssl/tls13_server.cc

@ -1,4 +1,5 @@
SSL,277,ALPN_MISMATCH_ON_EARLY_DATA
SSL,309,ALPS_MISMATCH_ON_EARLY_DATA
SSL,281,APPLICATION_DATA_INSTEAD_OF_HANDSHAKE
SSL,291,APPLICATION_DATA_ON_SHUTDOWN
SSL,100,APP_DATA_IN_HANDSHAKE
@ -94,6 +95,7 @@ SSL,166,MISSING_TMP_DH_KEY
SSL,167,MISSING_TMP_ECDH_KEY
SSL,168,MIXED_SPECIAL_OPERATOR_WITH_GROUPS
SSL,169,MTU_TOO_SMALL
SSL,308,NEGOTIATED_ALPS_WITHOUT_ALPN
SSL,170,NEGOTIATED_BOTH_NPN_AND_ALPN
SSL,285,NEGOTIATED_TB_WITHOUT_EMS_OR_RI
SSL,171,NESTED_GROUP

@ -2776,6 +2776,51 @@ OPENSSL_EXPORT void SSL_CTX_set_allow_unknown_alpn_protos(SSL_CTX *ctx,
int enabled);
// Application-layer protocol settings
//
// The ALPS extension (draft-vvv-tls-alps) allows exchanging application-layer
// settings in the TLS handshake for applications negotiated with ALPN. Note
// that, when ALPS is negotiated, the client and server each advertise their own
// settings, so there are functions to both configure setting to send and query
// received settings.
// SSL_add_application_settings configures |ssl| to enable ALPS with ALPN
// protocol |proto|, sending an ALPS value of |settings|. It returns one on
// success and zero on error. If |proto| is negotiated via ALPN and the peer
// supports ALPS, |settings| will be sent to the peer. The peer's ALPS value can
// be retrieved with |SSL_get0_peer_application_settings|.
//
// On the client, this function should be called before the handshake, once for
// each supported ALPN protocol which uses ALPS. |proto| must be included in the
// client's ALPN configuration (see |SSL_CTX_set_alpn_protos| and
// |SSL_set_alpn_protos|). On the server, ALPS can be preconfigured for each
// protocol as in the client, or configuration can be deferred to the ALPN
// callback (see |SSL_CTX_set_alpn_select_cb|), in which case only the selected
// protocol needs to be configured.
//
// ALPS can be independently configured from 0-RTT, however changes in protocol
// settings will fallback to 1-RTT to negotiate the new value, so it is
// recommended for |settings| to be relatively stable.
OPENSSL_EXPORT int SSL_add_application_settings(SSL *ssl, const uint8_t *proto,
size_t proto_len,
const uint8_t *settings,
size_t settings_len);
// SSL_get0_peer_application_settings sets |*out_data| and |*out_len| to a
// buffer containing the peer's ALPS value, or the empty string if ALPS was not
// negotiated. Note an empty string could also indicate the peer sent an empty
// settings value. Use |SSL_has_application_settings| to check if ALPS was
// negotiated. The output buffer is owned by |ssl| and is valid until the next
// time |ssl| is modified.
OPENSSL_EXPORT void SSL_get0_peer_application_settings(const SSL *ssl,
const uint8_t **out_data,
size_t *out_len);
// SSL_has_application_settings returns one if ALPS was negotiated on this
// connection and zero otherwise.
OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl);
// Certificate compression.
//
// Certificates in TLS 1.3 can be compressed[1]. BoringSSL supports this as both
@ -3493,8 +3538,10 @@ enum ssl_early_data_reason_t BORINGSSL_ENUM_INT {
ssl_early_data_ticket_age_skew = 12,
// QUIC parameters differ between this connection and the original.
ssl_early_data_quic_parameter_mismatch = 13,
// The application settings did not match the session.
ssl_early_data_alps_mismatch = 14,
// The value of the largest entry.
ssl_early_data_reason_max_value = ssl_early_data_quic_parameter_mismatch,
ssl_early_data_reason_max_value = ssl_early_data_alps_mismatch,
};
// SSL_get_early_data_reason returns details why 0-RTT was accepted or rejected
@ -5217,6 +5264,8 @@ BSSL_NAMESPACE_END
#define SSL_R_QUIC_TRANSPORT_PARAMETERS_MISCONFIGURED 305
#define SSL_R_UNEXPECTED_COMPATIBILITY_MODE 306
#define SSL_R_MISSING_ALPN 307
#define SSL_R_NEGOTIATED_ALPS_WITHOUT_ALPN 308
#define SSL_R_ALPS_MISMATCH_ON_EARLY_DATA 309
#define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000
#define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010
#define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020

@ -235,6 +235,10 @@ extern "C" {
// ExtensionType value from draft-ietf-tls-subcerts.
#define TLSEXT_TYPE_delegated_credential 0x22
// ExtensionType value from draft-vvv-tls-alps. This is not an IANA defined
// extension number.
#define TLSEXT_TYPE_application_settings 17513
// ExtensionType value from RFC6962
#define TLSEXT_TYPE_certificate_timestamp 18

@ -24,6 +24,8 @@ BSSL_NAMESPACE_BEGIN
constexpr int kHandoffVersion = 0;
constexpr int kHandbackVersion = 0;
static const unsigned kHandoffTagALPS = CBS_ASN1_CONTEXT_SPECIFIC | 0;
// early_data_t represents the state of early data in a more compact way than
// the 3 bits used by the implementation.
enum early_data_t {
@ -57,6 +59,16 @@ static bool serialize_features(CBB *out) {
return false;
}
}
// ALPS is a draft protocol and may change over time. The handoff structure
// contains a [0] IMPLICIT OCTET STRING OPTIONAL, containing a list of u16
// ALPS versions that the binary supports. For now we name them by codepoint.
// Once ALPS is finalized and past the support horizon, this field can be
// removed.
CBB alps;
if (!CBB_add_asn1(out, &alps, kHandoffTagALPS) ||
!CBB_add_u16(&alps, TLSEXT_TYPE_application_settings)) {
return false;
}
return CBB_flush(out);
}
@ -189,6 +201,29 @@ static bool apply_remote_features(SSL *ssl, CBS *in) {
new_configured_curves.Shrink(idx);
ssl->config->supported_group_list = std::move(new_configured_curves);
CBS alps;
CBS_init(&alps, nullptr, 0);
if (!CBS_get_optional_asn1(in, &alps, /*out_present=*/nullptr,
kHandoffTagALPS)) {
return false;
}
bool supports_alps = false;
while (CBS_len(&alps) != 0) {
uint16_t id;
if (!CBS_get_u16(&alps, &id)) {
return false;
}
// For now, we only support one ALPS code point, so we only need to extract
// a boolean signal from the feature list.
if (id == TLSEXT_TYPE_application_settings) {
supports_alps = true;
break;
}
}
if (!supports_alps) {
ssl->config->alps_configs.clear();
}
return true;
}

@ -389,6 +389,11 @@ class GrowableArray {
T *end() { return array_.data() + size_; }
const T *cend() const { return array_.data() + size_; }
void clear() {
size_ = 0;
array_.Reset();
}
// Push adds |elem| at the end of the internal array, growing if necessary. It
// returns false when allocation fails.
bool Push(T elem) {
@ -1482,6 +1487,7 @@ enum tls13_server_hs_state_t {
state13_send_half_rtt_ticket,
state13_read_second_client_flight,
state13_process_end_of_early_data,
state13_read_client_encrypted_extensions,
state13_read_client_certificate,
state13_read_client_certificate_verify,
state13_read_channel_id,
@ -1918,6 +1924,12 @@ bool ssl_is_alpn_protocol_allowed(const SSL_HANDSHAKE *hs,
bool ssl_negotiate_alpn(SSL_HANDSHAKE *hs, uint8_t *out_alert,
const SSL_CLIENT_HELLO *client_hello);
// ssl_negotiate_alps negotiates the ALPS extension, if applicable. It returns
// true on successful negotiation or if nothing was negotiated. It returns false
// and sets |*out_alert| to an alert on error.
bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert,
const SSL_CLIENT_HELLO *client_hello);
struct SSL_EXTENSION_TYPE {
uint16_t type;
bool *out_present;
@ -2624,6 +2636,12 @@ struct DTLS1_STATE {
unsigned timeout_duration_ms = 0;
};
// An ALPSConfig is a pair of ALPN protocol and settings value to use with ALPS.
struct ALPSConfig {
Array<uint8_t> protocol;
Array<uint8_t> settings;
};
// SSL_CONFIG contains configuration bits that can be shed after the handshake
// completes. Objects of this type are not shared; they are unique to a
// particular |SSL|.
@ -2690,6 +2708,10 @@ struct SSL_CONFIG {
// format.
Array<uint8_t> alpn_client_proto_list;
// alps_configs contains the list of supported protocols to use with ALPS,
// along with their corresponding ALPS values.
GrowableArray<ALPSConfig> alps_configs;
// Contains a list of supported Token Binding key parameters.
Array<uint8_t> token_binding_params;
@ -3543,9 +3565,18 @@ struct ssl_session_st {
// early_alpn is the ALPN protocol from the initial handshake. This is only
// stored for TLS 1.3 and above in order to enforce ALPN matching for 0-RTT
// resumptions.
// resumptions. For the current connection's ALPN protocol, see
// |alpn_selected| on |SSL3_STATE|.
bssl::Array<uint8_t> early_alpn;
// local_application_settings, if |has_application_settings| is true, is the
// local ALPS value for this connection.
bssl::Array<uint8_t> local_application_settings;
// peer_application_settings, if |has_application_settings| is true, is the
// peer ALPS value for this connection.
bssl::Array<uint8_t> peer_application_settings;
// extended_master_secret is whether the master secret in this session was
// generated using EMS and thus isn't vulnerable to the Triple Handshake
// attack.
@ -3566,6 +3597,10 @@ struct ssl_session_st {
// is_quic indicates whether this session was created using QUIC.
bool is_quic : 1;
// has_application_settings indicates whether ALPS was negotiated in this
// session.
bool has_application_settings : 1;
// quic_early_data_context is used to determine whether early data must be
// rejected when performing a QUIC handshake.
bssl::Array<uint8_t> quic_early_data_context;

@ -131,6 +131,10 @@ BSSL_NAMESPACE_BEGIN
// earlyALPN [26] OCTET STRING OPTIONAL,
// isQuic [27] BOOLEAN OPTIONAL,
// quicEarlyDataHash [28] OCTET STRING OPTIONAL,
// localALPS [29] OCTET STRING OPTIONAL,
// peerALPS [30] OCTET STRING OPTIONAL,
// -- Either both or none of localALPS and peerALPS must be present. If both
// -- are present, earlyALPN must be present and non-empty.
// }
//
// Note: historically this serialization has included other optional
@ -194,6 +198,10 @@ static const unsigned kIsQuicTag =
CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 27;
static const unsigned kQuicEarlyDataContextTag =
CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 28;
static const unsigned kLocalALPSTag =
CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 29;
static const unsigned kPeerALPSTag =
CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 30;
static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, CBB *cbb,
int for_ticket) {
@ -411,6 +419,19 @@ static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, CBB *cbb,
}
}
if (in->has_application_settings) {
if (!CBB_add_asn1(&session, &child, kLocalALPSTag) ||
!CBB_add_asn1_octet_string(&child,
in->local_application_settings.data(),
in->local_application_settings.size()) ||
!CBB_add_asn1(&session, &child, kPeerALPSTag) ||
!CBB_add_asn1_octet_string(&child, in->peer_application_settings.data(),
in->peer_application_settings.size())) {
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
return 0;
}
}
return CBB_flush(cbb);
}
@ -753,13 +774,33 @@ UniquePtr<SSL_SESSION> SSL_SESSION_parse(CBS *cbs,
!CBS_get_optional_asn1_bool(&session, &is_quic, kIsQuicTag,
/*default_value=*/false) ||
!SSL_SESSION_parse_octet_string(&session, &ret->quic_early_data_context,
kQuicEarlyDataContextTag) ||
kQuicEarlyDataContextTag)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
return nullptr;
}
CBS settings;
int has_local_alps, has_peer_alps;
if (!CBS_get_optional_asn1_octet_string(&session, &settings, &has_local_alps,
kLocalALPSTag) ||
!ret->local_application_settings.CopyFrom(settings) ||
!CBS_get_optional_asn1_octet_string(&session, &settings, &has_peer_alps,
kPeerALPSTag) ||
!ret->peer_application_settings.CopyFrom(settings) ||
CBS_len(&session) != 0) {
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
return nullptr;
}
ret->is_quic = is_quic;
// The two ALPS values and ALPN must be consistent.
if (has_local_alps != has_peer_alps ||
(has_local_alps && ret->early_alpn.empty())) {
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
return nullptr;
}
ret->has_application_settings = has_local_alps;
if (!x509_method->session_cache_objects(ret.get())) {
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
return nullptr;

@ -2241,6 +2241,36 @@ void SSL_CTX_set_allow_unknown_alpn_protos(SSL_CTX *ctx, int enabled) {
ctx->allow_unknown_alpn_protos = !!enabled;
}
int SSL_add_application_settings(SSL *ssl, const uint8_t *proto,
size_t proto_len, const uint8_t *settings,
size_t settings_len) {
if (!ssl->config) {
return 0;
}
ALPSConfig config;
if (!config.protocol.CopyFrom(MakeConstSpan(proto, proto_len)) ||
!config.settings.CopyFrom(MakeConstSpan(settings, settings_len)) ||
!ssl->config->alps_configs.Push(std::move(config))) {
return 0;
}
return 1;
}
void SSL_get0_peer_application_settings(const SSL *ssl,
const uint8_t **out_data,
size_t *out_len) {
const SSL_SESSION *session = SSL_get_session(ssl);
Span<const uint8_t> settings =
session ? session->peer_application_settings : Span<const uint8_t>();
*out_data = settings.data();
*out_len = settings.size();
}
int SSL_has_application_settings(const SSL *ssl) {
const SSL_SESSION *session = SSL_get_session(ssl);
return session && session->has_application_settings;
}
int SSL_CTX_add_cert_compression_alg(SSL_CTX *ctx, uint16_t alg_id,
ssl_cert_compression_func_t compress,
ssl_cert_decompression_func_t decompress) {

@ -264,13 +264,15 @@ UniquePtr<SSL_SESSION> SSL_SESSION_dup(SSL_SESSION *session, int dup_flags) {
new_session->ticket_age_add = session->ticket_age_add;
new_session->ticket_max_early_data = session->ticket_max_early_data;
new_session->extended_master_secret = session->extended_master_secret;
if (!new_session->early_alpn.CopyFrom(session->early_alpn)) {
return nullptr;
}
if (!new_session->quic_early_data_context.CopyFrom(
session->quic_early_data_context)) {
new_session->has_application_settings = session->has_application_settings;
if (!new_session->early_alpn.CopyFrom(session->early_alpn) ||
!new_session->quic_early_data_context.CopyFrom(
session->quic_early_data_context) ||
!new_session->local_application_settings.CopyFrom(
session->local_application_settings) ||
!new_session->peer_application_settings.CopyFrom(
session->peer_application_settings)) {
return nullptr;
}
}
@ -864,7 +866,8 @@ ssl_session_st::ssl_session_st(const SSL_X509_METHOD *method)
not_resumable(false),
ticket_age_add_valid(false),
is_server(false),
is_quic(false) {
is_quic(false),
has_application_settings(false) {
CRYPTO_new_ex_data(&ex_data);
time = ::time(nullptr);
}

@ -754,7 +754,7 @@ static const char kBoringSSLSession[] =
"NusdVm/K2rxzY5Dkf3s+Iss9B+1fOHSc4wNQTqGvmO5h8oQ/Eg==";
// kBadSessionExtraField is a custom serialized SSL_SESSION generated by replacing
// the final (optional) element of |kCustomSession| with tag number 30.
// the final (optional) element of |kCustomSession| with tag number 99.
static const char kBadSessionExtraField[] =
"MIIBdgIBAQICAwMEAsAvBCAG5Q1ndq4Yfmbeo1zwLkNRKmCXGdNgWvGT3cskV0yQ"
"kAQwJlrlzkAWBOWiLj/jJ76D7l+UXoizP2KI2C7I2FccqMmIfFmmkUy32nIJ0mZH"
@ -763,7 +763,7 @@ static const char kBadSessionExtraField[] =
"LwjcDTpsuh3qXEaZ992r1N38VDcyS6P7I6HBYN9BsNHM362zZnY27GpTw+Kwd751"
"CLoXFPoaMOe57dbBpXoro6Pd3BTbf/Tzr88K06yEOTDKPNj3+inbMaVigtK4PLyP"
"q+Topyzvx9USFgRvyuoxn0Hgb+R0A3j6SLRuyOdAi4gv7Y5oliynrSIEIAYGBgYG"
"BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGrgMEAQevAwQBBL4DBAEF";
"BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGrgMEAQevAwQBBOMDBAEF";
// kBadSessionVersion is a custom serialized SSL_SESSION generated by replacing
// the version of |kCustomSession| with 2.

@ -125,13 +125,14 @@
#include <openssl/nid.h>
#include <openssl/rand.h>
#include "internal.h"
#include "../crypto/internal.h"
#include "internal.h"
BSSL_NAMESPACE_BEGIN
static bool ssl_check_clienthello_tlsext(SSL_HANDSHAKE *hs);
static bool ssl_check_serverhello_tlsext(SSL_HANDSHAKE *hs);
static int compare_uint16_t(const void *p1, const void *p2) {
uint16_t u1 = *((const uint16_t *)p1);
@ -512,7 +513,7 @@ struct tls_extension {
};
static bool forbid_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
CBS *contents) {
CBS *contents) {
if (contents != NULL) {
// Servers MUST NOT send this extension.
*out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
@ -524,7 +525,7 @@ static bool forbid_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
}
static bool ignore_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
CBS *contents) {
CBS *contents) {
// This extension from the client is handled elsewhere.
return true;
}
@ -1380,7 +1381,6 @@ bool ssl_negotiate_alpn(SSL_HANDSHAKE *hs, uint8_t *out_alert,
CBS protocol_name_list_copy = protocol_name_list;
while (CBS_len(&protocol_name_list_copy) > 0) {
CBS protocol_name;
if (!CBS_get_u8_length_prefixed(&protocol_name_list_copy, &protocol_name) ||
// Empty protocol names are forbidden.
CBS_len(&protocol_name) == 0) {
@ -1946,6 +1946,21 @@ static bool ext_psk_key_exchange_modes_parse_clienthello(SSL_HANDSHAKE *hs,
//
// https://tools.ietf.org/html/rfc8446#section-4.2.10
// ssl_get_local_application_settings looks up the configured ALPS value for
// |protocol|. If found, it sets |*out_settings| to the value and returns true.
// Otherwise, it returns false.
static bool ssl_get_local_application_settings(
const SSL_HANDSHAKE *hs, Span<const uint8_t> *out_settings,
Span<const uint8_t> protocol) {
for (const ALPSConfig &config : hs->config->alps_configs) {
if (protocol == config.protocol) {
*out_settings = config.settings;
return true;
}
}
return false;
}
static bool ext_early_data_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
SSL *const ssl = hs->ssl;
// The second ClientHello never offers early data, and we must have already
@ -1978,13 +1993,22 @@ static bool ext_early_data_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
return true;
}
// In case ALPN preferences changed since this session was established, avoid
// reporting a confusing value in |SSL_get0_alpn_selected| and sending early
// data we know will be rejected.
if (!ssl->session->early_alpn.empty() &&
!ssl_is_alpn_protocol_allowed(hs, ssl->session->early_alpn)) {
ssl->s3->early_data_reason = ssl_early_data_alpn_mismatch;
return true;
if (!ssl->session->early_alpn.empty()) {
if (!ssl_is_alpn_protocol_allowed(hs, ssl->session->early_alpn)) {
// Avoid reporting a confusing value in |SSL_get0_alpn_selected|.
ssl->s3->early_data_reason = ssl_early_data_alpn_mismatch;
return true;
}
Span<const uint8_t> settings;
bool has_alps = ssl_get_local_application_settings(
hs, &settings, ssl->session->early_alpn);
if (has_alps != ssl->session->has_application_settings ||
settings != ssl->session->local_application_settings) {
// 0-RTT carries ALPS over, so we only offer it when the value matches.
ssl->s3->early_data_reason = ssl_early_data_alps_mismatch;
return true;
}
}
// |early_data_reason| will be filled in later when the server responds.
@ -2797,6 +2821,144 @@ static bool cert_compression_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
return true;
}
// Application-level Protocol Settings
//
// https://tools.ietf.org/html/draft-vvv-tls-alps-01
static bool ext_alps_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
SSL *const ssl = hs->ssl;
if (// ALPS requires TLS 1.3.
hs->max_version < TLS1_3_VERSION ||
// Do not offer ALPS without ALPN.
hs->config->alpn_client_proto_list.empty() ||
// Do not offer ALPS if not configured.
hs->config->alps_configs.empty() ||
// Do not offer ALPS on renegotiation handshakes.
ssl->s3->initial_handshake_complete) {
return true;
}
CBB contents, proto_list, proto;
if (!CBB_add_u16(out, TLSEXT_TYPE_application_settings) ||
!CBB_add_u16_length_prefixed(out, &contents) ||
!CBB_add_u16_length_prefixed(&contents, &proto_list)) {
return false;
}
for (const ALPSConfig &config : hs->config->alps_configs) {
if (!CBB_add_u8_length_prefixed(&proto_list, &proto) ||
!CBB_add_bytes(&proto, config.protocol.data(),
config.protocol.size())) {
return false;
}
}
return CBB_flush(out);
}
static bool ext_alps_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
CBS *contents) {
SSL *const ssl = hs->ssl;
if (contents == nullptr) {
return true;
}
assert(!ssl->s3->initial_handshake_complete);
assert(!hs->config->alpn_client_proto_list.empty());
assert(!hs->config->alps_configs.empty());
// ALPS requires TLS 1.3.
if (ssl_protocol_version(ssl) < TLS1_3_VERSION) {
*out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
return false;
}
// Note extension callbacks may run in any order, so we defer checking
// consistency with ALPN to |ssl_check_serverhello_tlsext|.
if (!hs->new_session->peer_application_settings.CopyFrom(*contents)) {
*out_alert = SSL_AD_INTERNAL_ERROR;
return false;
}
hs->new_session->has_application_settings = true;
return true;
}
static bool ext_alps_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
SSL *const ssl = hs->ssl;
// If early data is accepted, we omit the ALPS extension. It is implicitly
// carried over from the previous connection.
if (hs->new_session == nullptr ||
!hs->new_session->has_application_settings ||
ssl->s3->early_data_accepted) {
return true;
}
CBB contents;
if (!CBB_add_u16(out, TLSEXT_TYPE_application_settings) ||
!CBB_add_u16_length_prefixed(out, &contents) ||
!CBB_add_bytes(&contents,
hs->new_session->local_application_settings.data(),
hs->new_session->local_application_settings.size()) ||
!CBB_flush(out)) {
return false;
}
return true;
}
bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert,
const SSL_CLIENT_HELLO *client_hello) {
SSL *const ssl = hs->ssl;
if (ssl->s3->alpn_selected.empty()) {
return true;
}
// If we negotiate ALPN over TLS 1.3, try to negotiate ALPS.
CBS alps_contents;
Span<const uint8_t> settings;
if (ssl_protocol_version(ssl) >= TLS1_3_VERSION &&
ssl_get_local_application_settings(hs, &settings,
ssl->s3->alpn_selected) &&
ssl_client_hello_get_extension(client_hello, &alps_contents,
TLSEXT_TYPE_application_settings)) {
// Check if the client supports ALPS with the selected ALPN.
bool found = false;
CBS alps_list;
if (!CBS_get_u16_length_prefixed(&alps_contents, &alps_list) ||
CBS_len(&alps_contents) != 0 ||
CBS_len(&alps_list) == 0) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
*out_alert = SSL_AD_DECODE_ERROR;
return false;
}
while (CBS_len(&alps_list) > 0) {
CBS protocol_name;
if (!CBS_get_u8_length_prefixed(&alps_list, &protocol_name) ||
// Empty protocol names are forbidden.
CBS_len(&protocol_name) == 0) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
*out_alert = SSL_AD_DECODE_ERROR;
return false;
}
if (protocol_name == MakeConstSpan(ssl->s3->alpn_selected)) {
found = true;
}
}
// Negotiate ALPS if both client also supports ALPS for this protocol.
if (found) {
hs->new_session->has_application_settings = true;
if (!hs->new_session->local_application_settings.CopyFrom(settings)) {
*out_alert = SSL_AD_INTERNAL_ERROR;
return false;
}
}
}
return true;
}
// kExtensions contains all the supported extensions.
static const struct tls_extension kExtensions[] = {
@ -2978,6 +3140,15 @@ static const struct tls_extension kExtensions[] = {
ext_delegated_credential_parse_clienthello,
dont_add_serverhello,
},
{
TLSEXT_TYPE_application_settings,
NULL,
ext_alps_add_clienthello,
ext_alps_parse_serverhello,
// ALPS is negotiated late in |ssl_negotiate_alpn|.
ignore_parse_clienthello,
ext_alps_add_serverhello,
},
};
#define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
@ -3370,6 +3541,36 @@ static bool ssl_check_clienthello_tlsext(SSL_HANDSHAKE *hs) {
}
}
static bool ssl_check_serverhello_tlsext(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
// ALPS and ALPN have a dependency between each other, so we defer checking
// consistency to after the callbacks run.
if (hs->new_session != nullptr && hs->new_session->has_application_settings) {
// ALPN must be negotiated.
if (ssl->s3->alpn_selected.empty()) {
OPENSSL_PUT_ERROR(SSL, SSL_R_NEGOTIATED_ALPS_WITHOUT_ALPN);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
return false;
}
// The negotiated protocol must be one of the ones we advertised for ALPS.
Span<const uint8_t> settings;
if (!ssl_get_local_application_settings(hs, &settings,
ssl->s3->alpn_selected)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_ALPN_PROTOCOL);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
return false;
}
if (!hs->new_session->local_application_settings.CopyFrom(settings)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return false;
}
}
return true;
}
bool ssl_parse_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs) {
SSL *const ssl = hs->ssl;
int alert = SSL_AD_DECODE_ERROR;
@ -3378,6 +3579,10 @@ bool ssl_parse_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs) {
return false;
}
if (!ssl_check_serverhello_tlsext(hs)) {
return false;
}
return true;
}

@ -397,6 +397,11 @@ static bool CheckAuthProperties(SSL *ssl, bool is_resume,
}
static const char *EarlyDataReasonToString(ssl_early_data_reason_t reason) {
if (reason > ssl_early_data_reason_max_value) {
fprintf(stderr, "ssl_early_data_reason_max_value is out of date.\n");
abort();
}
switch (reason) {
case ssl_early_data_unknown:
return "unknown";
@ -426,8 +431,12 @@ static const char *EarlyDataReasonToString(ssl_early_data_reason_t reason) {
return "ticket_age_skew";
case ssl_early_data_quic_parameter_mismatch:
return "quic_parameter_mismatch";
case ssl_early_data_alps_mismatch:
return "alps_mismatch";
}
fprintf(stderr, "Unknown ssl_early_data_reason_t value %d.\n",
static_cast<int>(reason));
abort();
}
@ -538,6 +547,26 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume,
return false;
}
if (SSL_has_application_settings(ssl) !=
(config->expect_peer_application_settings ? 1 : 0)) {
fprintf(stderr,
"connection %s application settings, but expected the opposite\n",
SSL_has_application_settings(ssl) ? "has" : "does not have");
return false;
}
std::string expect_settings = config->expect_peer_application_settings
? *config->expect_peer_application_settings
: "";
const uint8_t *peer_settings;
size_t peer_settings_len;
SSL_get0_peer_application_settings(ssl, &peer_settings, &peer_settings_len);
if (expect_settings !=
std::string(reinterpret_cast<const char *>(peer_settings),
peer_settings_len)) {
fprintf(stderr, "peer application settings mismatch\n");
return false;
}
if (!config->expect_quic_transport_params.empty() && expect_handshake_done) {
const uint8_t *peer_params;
size_t peer_params_len;

@ -121,6 +121,7 @@ const (
extensionKeyShare uint16 = 51
extensionCustom uint16 = 1234 // not IANA assigned
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
extensionApplicationSettings uint16 = 17513 // not IANA assigned
extensionRenegotiationInfo uint16 = 0xff01
extensionQUICTransportParams uint16 = 0xffa5 // draft-ietf-quic-tls-13
extensionChannelID uint16 = 30032 // not IANA assigned
@ -260,6 +261,8 @@ type ConnectionState struct {
PeerSignatureAlgorithm signatureAlgorithm // algorithm used by the peer in the handshake
CurveID CurveID // the curve used in ECDHE
QUICTransportParams []byte // the QUIC transport params received from the peer
HasApplicationSettings bool // whether ALPS was negotiated
PeerApplicationSettings []byte // application settings received from the peer
}
// ClientAuthType declares the policy the server will follow for
@ -277,22 +280,25 @@ const (
// ClientSessionState contains the state needed by clients to resume TLS
// sessions.
type ClientSessionState struct {
sessionId []uint8 // Session ID supplied by the server. nil if the session has a ticket.
sessionTicket []uint8 // Encrypted ticket used for session resumption with server
vers uint16 // SSL/TLS version negotiated for the session
wireVersion uint16 // Wire SSL/TLS version negotiated for the session
cipherSuite uint16 // Ciphersuite negotiated for the session
masterSecret []byte // MasterSecret generated by client on a full handshake
handshakeHash []byte // Handshake hash for Channel ID purposes.
serverCertificates []*x509.Certificate // Certificate chain presented by the server
extendedMasterSecret bool // Whether an extended master secret was used to generate the session
sctList []byte
ocspResponse []byte
earlyALPN string
ticketCreationTime time.Time
ticketExpiration time.Time
ticketAgeAdd uint32
maxEarlyDataSize uint32
sessionId []uint8 // Session ID supplied by the server. nil if the session has a ticket.
sessionTicket []uint8 // Encrypted ticket used for session resumption with server
vers uint16 // SSL/TLS version negotiated for the session
wireVersion uint16 // Wire SSL/TLS version negotiated for the session
cipherSuite uint16 // Ciphersuite negotiated for the session
masterSecret []byte // MasterSecret generated by client on a full handshake
handshakeHash []byte // Handshake hash for Channel ID purposes.
serverCertificates []*x509.Certificate // Certificate chain presented by the server
extendedMasterSecret bool // Whether an extended master secret was used to generate the session
sctList []byte
ocspResponse []byte
earlyALPN string
ticketCreationTime time.Time
ticketExpiration time.Time
ticketAgeAdd uint32
maxEarlyDataSize uint32
hasApplicationSettings bool
localApplicationSettings []byte
peerApplicationSettings []byte
}
// ClientSessionCache is a cache of ClientSessionState objects that can be used
@ -367,6 +373,10 @@ type Config struct {
// NextProtos is a list of supported, application level protocols.
NextProtos []string
// ApplicationSettings is a set of application settings to use which each
// application protocol.
ApplicationSettings map[string][]byte
// ServerName is used to verify the hostname on the returned
// certificates unless InsecureSkipVerify is given. It is also included
// in the client's handshake to support virtual hosting.
@ -792,6 +802,33 @@ type ProtocolBugs struct {
// return.
ALPNProtocol *string
// AlwaysNegotiateApplicationSettings, if true, causes the server to
// negotiate ALPS for a protocol even if the client did not support it or
// the version is wrong.
AlwaysNegotiateApplicationSettings bool
// SendApplicationSettingsWithEarlyData, if true, causes the client and
// server to send the application_settings extension with early data,
// rather than letting them implicitly carry over.
SendApplicationSettingsWithEarlyData bool
// AlwaysSendClientEncryptedExtension, if true, causes the client to always
// send a, possibly empty, client EncryptedExtensions message.
AlwaysSendClientEncryptedExtensions bool
// OmitClientEncryptedExtensions, if true, causes the client to omit the
// client EncryptedExtensions message.
OmitClientEncryptedExtensions bool
// OmitClientApplicationSettings, if true, causes the client to omit the
// application_settings extension but still send EncryptedExtensions.
OmitClientApplicationSettings bool
// SendExtraClientEncryptedExtension, if true, causes the client to
// include an unsolicited extension in the client EncryptedExtensions
// message.
SendExtraClientEncryptedExtension bool
// AcceptAnySession causes the server to resume sessions regardless of
// the version associated with the session or cipher suite. It also
// causes the server to look in both TLS 1.2 and 1.3 extensions to

@ -73,6 +73,9 @@ type Conn struct {
clientProtocolFallback bool
usedALPN bool
localApplicationSettings, peerApplicationSettings []byte
hasApplicationSettings bool
// verify_data values for the renegotiation extension.
clientVerify []byte
serverVerify []byte
@ -1390,7 +1393,11 @@ func (c *Conn) readHandshake() (interface{}, error) {
isDTLS: c.isDTLS,
}
case typeEncryptedExtensions:
m = new(encryptedExtensionsMsg)
if c.isClient {
m = new(encryptedExtensionsMsg)
} else {
m = new(clientEncryptedExtensionsMsg)
}
case typeCertificate:
m = &certificateMsg{
hasRequestContext: c.vers >= VersionTLS13,
@ -1608,19 +1615,22 @@ func (c *Conn) processTLS13NewSessionTicket(newSessionTicket *newSessionTicketMs
}
session := &ClientSessionState{
sessionTicket: newSessionTicket.ticket,
vers: c.vers,
wireVersion: c.wireVersion,
cipherSuite: cipherSuite.id,
masterSecret: c.resumptionSecret,
serverCertificates: c.peerCertificates,
sctList: c.sctList,
ocspResponse: c.ocspResponse,
ticketCreationTime: c.config.time(),
ticketExpiration: c.config.time().Add(time.Duration(newSessionTicket.ticketLifetime) * time.Second),
ticketAgeAdd: newSessionTicket.ticketAgeAdd,
maxEarlyDataSize: newSessionTicket.maxEarlyDataSize,
earlyALPN: c.clientProtocol,
sessionTicket: newSessionTicket.ticket,
vers: c.vers,
wireVersion: c.wireVersion,
cipherSuite: cipherSuite.id,
masterSecret: c.resumptionSecret,
serverCertificates: c.peerCertificates,
sctList: c.sctList,
ocspResponse: c.ocspResponse,
ticketCreationTime: c.config.time(),
ticketExpiration: c.config.time().Add(time.Duration(newSessionTicket.ticketLifetime) * time.Second),
ticketAgeAdd: newSessionTicket.ticketAgeAdd,
maxEarlyDataSize: newSessionTicket.maxEarlyDataSize,
earlyALPN: c.clientProtocol,
hasApplicationSettings: c.hasApplicationSettings,
localApplicationSettings: c.localApplicationSettings,
peerApplicationSettings: c.peerApplicationSettings,
}
session.masterSecret = deriveSessionPSK(cipherSuite, c.wireVersion, c.resumptionSecret, newSessionTicket.ticketNonce)
@ -1883,6 +1893,8 @@ func (c *Conn) ConnectionState() ConnectionState {
state.PeerSignatureAlgorithm = c.peerSignatureAlgorithm
state.CurveID = c.curveID
state.QUICTransportParams = c.quicTransportParams
state.HasApplicationSettings = c.hasApplicationSettings
state.PeerApplicationSettings = c.peerApplicationSettings
}
return state
@ -2007,14 +2019,17 @@ func (c *Conn) SendNewSessionTicket(nonce []byte) error {
}
state := sessionState{
vers: c.vers,
cipherSuite: c.cipherSuite.id,
masterSecret: deriveSessionPSK(c.cipherSuite, c.wireVersion, c.resumptionSecret, nonce),
certificates: peerCertificatesRaw,
ticketCreationTime: c.config.time(),
ticketExpiration: c.config.time().Add(time.Duration(m.ticketLifetime) * time.Second),
ticketAgeAdd: uint32(addBuffer[3])<<24 | uint32(addBuffer[2])<<16 | uint32(addBuffer[1])<<8 | uint32(addBuffer[0]),
earlyALPN: []byte(c.clientProtocol),
vers: c.vers,
cipherSuite: c.cipherSuite.id,
masterSecret: deriveSessionPSK(c.cipherSuite, c.wireVersion, c.resumptionSecret, nonce),
certificates: peerCertificatesRaw,
ticketCreationTime: c.config.time(),
ticketExpiration: c.config.time().Add(time.Duration(m.ticketLifetime) * time.Second),
ticketAgeAdd: uint32(addBuffer[3])<<24 | uint32(addBuffer[2])<<16 | uint32(addBuffer[1])<<8 | uint32(addBuffer[0]),
earlyALPN: []byte(c.clientProtocol),
hasApplicationSettings: c.hasApplicationSettings,
localApplicationSettings: c.localApplicationSettings,
peerApplicationSettings: c.peerApplicationSettings,
}
if !c.config.Bugs.SendEmptySessionTicket {

@ -47,6 +47,7 @@
"CustomExtensions-Server-EarlyDataOffered": "Trial decryption does not work with the NULL cipher.",
"*-TicketAgeSkew-*-Reject*": "Trial decryption does not work with the NULL cipher.",
"*EarlyDataRejected*": "Trial decryption does not work with the NULL cipher.",
"ALPS-EarlyData-Mismatch-*": "Trial decryption does not work with the NULL cipher.",
"Renegotiate-Client-BadExt*": "Fuzzer mode does not check renegotiation_info.",

@ -207,6 +207,10 @@ func (c *Conn) clientHandshake() error {
hello.secureRenegotiation = nil
}
for protocol, _ := range c.config.ApplicationSettings {
hello.alpsProtocols = append(hello.alpsProtocols, protocol)
}
var keyShares map[CurveID]ecdhCurve
if maxVersion >= VersionTLS13 {
keyShares = make(map[CurveID]ecdhCurve)
@ -1127,6 +1131,26 @@ func (hs *clientHandshakeState) doTLS13Handshake() error {
c.useOutTrafficSecret(c.wireVersion, hs.suite, clientHandshakeTrafficSecret)
// The client EncryptedExtensions message is sent if some extension uses it.
// (Currently only ALPS does.)
hasEncryptedExtensions := c.config.Bugs.AlwaysSendClientEncryptedExtensions
clientEncryptedExtensions := new(clientEncryptedExtensionsMsg)
if encryptedExtensions.extensions.hasApplicationSettings || (c.config.Bugs.SendApplicationSettingsWithEarlyData && c.hasApplicationSettings) {
hasEncryptedExtensions = true
if !c.config.Bugs.OmitClientApplicationSettings {
clientEncryptedExtensions.hasApplicationSettings = true
clientEncryptedExtensions.applicationSettings = c.localApplicationSettings
}
}
if c.config.Bugs.SendExtraClientEncryptedExtension {
hasEncryptedExtensions = true
clientEncryptedExtensions.customExtension = []byte{0}
}
if hasEncryptedExtensions && !c.config.Bugs.OmitClientEncryptedExtensions {
hs.writeClientHash(clientEncryptedExtensions.marshal())
c.writeRecord(recordTypeHandshake, clientEncryptedExtensions.marshal())
}
if certReq != nil && !c.config.Bugs.SkipClientCertificate {
certMsg := &certificateMsg{
hasRequestContext: true,
@ -1695,6 +1719,8 @@ func (hs *clientHandshakeState) processServerExtensions(serverExtensions *server
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: server accepted early data when not expected")
}
} else if serverExtensions.hasEarlyData {
return errors.New("tls: server accepted early data when not resuming")
}
if len(serverExtensions.quicTransportParams) > 0 {
@ -1705,6 +1731,30 @@ func (hs *clientHandshakeState) processServerExtensions(serverExtensions *server
c.quicTransportParams = serverExtensions.quicTransportParams
}
if serverExtensions.hasApplicationSettings {
if c.vers < VersionTLS13 {
return errors.New("tls: server sent application settings at invalid version")
}
if serverExtensions.hasEarlyData {
return errors.New("tls: server sent application settings with 0-RTT")
}
if !serverHasALPN {
return errors.New("tls: server sent application settings without ALPN")
}
settings, ok := c.config.ApplicationSettings[serverExtensions.alpnProtocol]
if !ok {
return errors.New("tls: server sent application settings for invalid protocol")
}
c.hasApplicationSettings = true
c.localApplicationSettings = settings
c.peerApplicationSettings = serverExtensions.applicationSettings
} else if serverExtensions.hasEarlyData {
// 0-RTT connections inherit application settings from the session.
c.hasApplicationSettings = hs.session.hasApplicationSettings
c.localApplicationSettings = hs.session.localApplicationSettings
c.peerApplicationSettings = hs.session.peerApplicationSettings
}
return nil
}

@ -295,6 +295,7 @@ type clientHelloMsg struct {
pad int
compressedCertAlgs []uint16
delegatedCredentials bool
alpsProtocols []string
prefixExtensions []uint16
}
@ -574,6 +575,17 @@ func (m *clientHelloMsg) marshal() []byte {
body: body.finish(),
})
}
if len(m.alpsProtocols) > 0 {
body := newByteBuilder()
protocolNameList := body.addU16LengthPrefixed()
for _, s := range m.alpsProtocols {
protocolNameList.addU8LengthPrefixed().addBytes([]byte(s))
}
extensions = append(extensions, extension{
id: extensionApplicationSettings,
body: body.finish(),
})
}
// The PSK extension must be last. See https://tools.ietf.org/html/rfc8446#section-4.2.11
if len(m.pskIdentities) > 0 {
@ -731,6 +743,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
m.extendedMasterSecret = false
m.customExtension = ""
m.delegatedCredentials = false
m.alpsProtocols = nil
if len(reader) == 0 {
// ClientHello is optionally followed by extension data
@ -889,7 +902,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
}
for len(protocols) > 0 {
var protocol []byte
if !protocols.readU8LengthPrefixedBytes(&protocol) {
if !protocols.readU8LengthPrefixedBytes(&protocol) || len(protocol) == 0 {
return false
}
m.alpnProtocols = append(m.alpnProtocols, string(protocol))
@ -966,6 +979,18 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
return false
}
m.delegatedCredentials = true
case extensionApplicationSettings:
var protocols byteReader
if !body.readU16LengthPrefixed(&protocols) || len(body) != 0 {
return false
}
for len(protocols) > 0 {
var protocol []byte
if !protocols.readU8LengthPrefixedBytes(&protocol) || len(protocol) == 0 {
return false
}
m.alpsProtocols = append(m.alpsProtocols, string(protocol))
}
}
if isGREASEValue(extension) {
@ -1233,6 +1258,8 @@ type serverExtensions struct {
supportedCurves []CurveID
quicTransportParams []byte
serverNameAck bool
applicationSettings []byte
hasApplicationSettings bool
}
func (m *serverExtensions) marshal(extensions *byteBuilder) {
@ -1367,6 +1394,10 @@ func (m *serverExtensions) marshal(extensions *byteBuilder) {
extensions.addU16(extensionServerName)
extensions.addU16(0) // zero length
}
if m.hasApplicationSettings {
extensions.addU16(extensionApplicationSettings)
extensions.addU16LengthPrefixed().addBytes(m.applicationSettings)
}
}
func (m *serverExtensions) unmarshal(data byteReader, version uint16) bool {
@ -1475,6 +1506,9 @@ func (m *serverExtensions) unmarshal(data byteReader, version uint16) bool {
return false
}
m.hasEarlyData = true
case extensionApplicationSettings:
m.hasApplicationSettings = true
m.applicationSettings = body
default:
// Unknown extensions are illegal from the server.
return false
@ -1484,6 +1518,68 @@ func (m *serverExtensions) unmarshal(data byteReader, version uint16) bool {
return true
}
type clientEncryptedExtensionsMsg struct {
raw []byte
applicationSettings []byte
hasApplicationSettings bool
customExtension []byte
}
func (m *clientEncryptedExtensionsMsg) marshal() (x []byte) {
if m.raw != nil {
return m.raw
}
builder := newByteBuilder()
builder.addU8(typeEncryptedExtensions)
body := builder.addU24LengthPrefixed()
extensions := body.addU16LengthPrefixed()
if m.hasApplicationSettings {
extensions.addU16(extensionApplicationSettings)
extensions.addU16LengthPrefixed().addBytes(m.applicationSettings)
}
if len(m.customExtension) > 0 {
extensions.addU16(extensionCustom)
extensions.addU16LengthPrefixed().addBytes(m.customExtension)
}
m.raw = builder.finish()
return m.raw
}
func (m *clientEncryptedExtensionsMsg) unmarshal(data []byte) bool {
m.raw = data
reader := byteReader(data[4:])
var extensions byteReader
if !reader.readU16LengthPrefixed(&extensions) ||
len(reader) != 0 {
return false
}
if !checkDuplicateExtensions(extensions) {
return false
}
for len(extensions) > 0 {
var extension uint16
var body byteReader
if !extensions.readU16(&extension) ||
!extensions.readU16LengthPrefixed(&body) {
return false
}
switch extension {
case extensionApplicationSettings:
m.hasApplicationSettings = true
m.applicationSettings = body
default:
// Unknown extensions are illegal in EncryptedExtensions.
return false
}
}
return true
}
type helloRetryRequestMsg struct {
raw []byte
vers uint16

@ -718,7 +718,10 @@ ResendHelloRetryRequest:
// Decide whether or not to accept early data.
if !sendHelloRetryRequest && hs.clientHello.hasEarlyData {
if !config.Bugs.AlwaysRejectEarlyData && hs.sessionState != nil {
if hs.sessionState.cipherSuite == hs.suite.id && c.clientProtocol == string(hs.sessionState.earlyALPN) {
if hs.sessionState.cipherSuite == hs.suite.id &&
c.clientProtocol == string(hs.sessionState.earlyALPN) &&
c.hasApplicationSettings == hs.sessionState.hasApplicationSettings &&
bytes.Equal(c.localApplicationSettings, hs.sessionState.localApplicationSettings) {
encryptedExtensions.extensions.hasEarlyData = true
}
if config.Bugs.AlwaysAcceptEarlyData {
@ -729,6 +732,12 @@ ResendHelloRetryRequest:
earlyTrafficSecret := hs.finishedHash.deriveSecret(earlyTrafficLabel)
c.earlyExporterSecret = hs.finishedHash.deriveSecret(earlyExporterLabel)
// Applications are implicit with early data.
if !config.Bugs.SendApplicationSettingsWithEarlyData {
encryptedExtensions.extensions.hasApplicationSettings = false
encryptedExtensions.extensions.applicationSettings = nil
}
sessionCipher := cipherSuiteFromID(hs.sessionState.cipherSuite)
if err := c.useInTrafficSecret(c.wireVersion, sessionCipher, earlyTrafficSecret); err != nil {
return err
@ -1050,6 +1059,29 @@ ResendHelloRetryRequest:
return err
}
// If we sent an ALPS extension, the client must respond with one.
if encryptedExtensions.extensions.hasApplicationSettings {
msg, err := c.readHandshake()
if err != nil {
return err
}
clientEncryptedExtensions, ok := msg.(*clientEncryptedExtensionsMsg)
if !ok {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(clientEncryptedExtensions, msg)
}
hs.writeClientHash(clientEncryptedExtensions.marshal())
if !clientEncryptedExtensions.hasApplicationSettings {
c.sendAlert(alertMissingExtension)
return errors.New("tls: client didn't provide application settings")
}
c.peerApplicationSettings = clientEncryptedExtensions.applicationSettings
} else if encryptedExtensions.extensions.hasEarlyData {
// 0-RTT sessions carry application settings over.
c.peerApplicationSettings = hs.sessionState.peerApplicationSettings
}
// If we requested a client certificate, then the client must send a
// certificate message, even if it's empty.
if config.ClientAuth >= RequestClientCert {
@ -1367,6 +1399,26 @@ func (hs *serverHandshakeState) processClientExtensions(serverExtensions *server
c.clientProtocol = selectedProto
c.usedALPN = true
}
var alpsAllowed bool
if c.vers >= VersionTLS13 {
for _, proto := range hs.clientHello.alpsProtocols {
if proto == c.clientProtocol {
alpsAllowed = true
break
}
}
}
if c.config.Bugs.AlwaysNegotiateApplicationSettings {
alpsAllowed = true
}
if settings, ok := c.config.ApplicationSettings[c.clientProtocol]; ok && alpsAllowed {
c.hasApplicationSettings = true
c.localApplicationSettings = settings
// Note these fields may later be cleared we accept 0-RTT.
serverExtensions.hasApplicationSettings = true
serverExtensions.applicationSettings = settings
}
}
if len(c.config.Bugs.SendALPN) > 0 {

@ -533,6 +533,9 @@ type connectionExpectations struct {
// quicTransportParams contains the QUIC transport parameters that are to be
// sent by the peer.
quicTransportParams []byte
// peerApplicationSettings are the expected application settings for the
// connection. If nil, no application settings are expected.
peerApplicationSettings []byte
}
type testCase struct {
@ -894,6 +897,17 @@ func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool, tr
}
}
if expectations.peerApplicationSettings != nil {
if !connState.HasApplicationSettings {
return errors.New("application settings should have been negotiated")
}
if !bytes.Equal(connState.PeerApplicationSettings, expectations.peerApplicationSettings) {
return fmt.Errorf("peer application settings mismatch: got %q, wanted %q", connState.PeerApplicationSettings, expectations.peerApplicationSettings)
}
} else if connState.HasApplicationSettings {
return errors.New("application settings unexpectedly negotiated")
}
if p := connState.SRTPProtectionProfile; p != expectations.srtpProtectionProfile {
return fmt.Errorf("SRTP profile mismatch: got %d, wanted %d", p, expectations.srtpProtectionProfile)
}
@ -6973,6 +6987,492 @@ func addExtensionTests() {
})
}
// Test ALPS.
if ver.version >= VersionTLS13 {
// Test that client and server can negotiate ALPS, including
// different values on resumption.
testCases = append(testCases, testCase{
testType: clientTest,
name: "ALPS-Basic-Client-" + ver.name,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
},
resumeConfig: &Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
},
resumeSession: true,
expectations: connectionExpectations{
peerApplicationSettings: []byte("shim1"),
},
resumeExpectations: &connectionExpectations{
peerApplicationSettings: []byte("shim2"),
},
flags: []string{
"-advertise-alpn", "\x05proto",
"-expect-alpn", "proto",
"-on-initial-application-settings", "proto,shim1",
"-on-initial-expect-peer-application-settings", "runner1",
"-on-resume-application-settings", "proto,shim2",
"-on-resume-expect-peer-application-settings", "runner2",
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ALPS-Basic-Server-" + ver.name,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
},
resumeConfig: &Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
},
resumeSession: true,
expectations: connectionExpectations{
peerApplicationSettings: []byte("shim1"),
},
resumeExpectations: &connectionExpectations{
peerApplicationSettings: []byte("shim2"),
},
flags: []string{
"-select-alpn", "proto",
"-on-initial-application-settings", "proto,shim1",
"-on-initial-expect-peer-application-settings", "runner1",
"-on-resume-application-settings", "proto,shim2",
"-on-resume-expect-peer-application-settings", "runner2",
},
})
// Test the client and server correctly handle empty settings.
testCases = append(testCases, testCase{
testType: clientTest,
name: "ALPS-Empty-Client-" + ver.name,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte{}},
},
resumeSession: true,
expectations: connectionExpectations{
peerApplicationSettings: []byte{},
},
flags: []string{
"-advertise-alpn", "\x05proto",
"-expect-alpn", "proto",
"-application-settings", "proto,",
"-expect-peer-application-settings", "",
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ALPS-Empty-Server-" + ver.name,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte{}},
},
resumeSession: true,
expectations: connectionExpectations{
peerApplicationSettings: []byte{},
},
flags: []string{
"-select-alpn", "proto",
"-application-settings", "proto,",
"-expect-peer-application-settings", "",
},
})
// Test the client rejects application settings from the server on
// protocols it doesn't have them.
testCases = append(testCases, testCase{
testType: clientTest,
name: "ALPS-UnsupportedProtocol-Client-" + ver.name,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto1"},
ApplicationSettings: map[string][]byte{"proto1": []byte("runner")},
Bugs: ProtocolBugs{
AlwaysNegotiateApplicationSettings: true,
},
},
// The client supports ALPS with "proto2", but not "proto1".
flags: []string{
"-advertise-alpn", "\x06proto1\x06proto2",
"-application-settings", "proto2,shim",
"-expect-alpn", "proto1",
},
// The server sends ALPS with "proto1", which is invalid.
shouldFail: true,
expectedError: ":INVALID_ALPN_PROTOCOL:",
expectedLocalError: "remote error: illegal parameter",
})
// Test the server declines ALPS if it doesn't support it for the
// specified protocol.
testCases = append(testCases, testCase{
testType: serverTest,
name: "ALPS-UnsupportedProtocol-Server-" + ver.name,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto1"},
ApplicationSettings: map[string][]byte{"proto1": []byte("runner")},
},
// The server supports ALPS with "proto2", but not "proto1".
flags: []string{
"-select-alpn", "proto1",
"-application-settings", "proto2,shim",
},
})
// Test that the server rejects a missing application_settings extension.
testCases = append(testCases, testCase{
testType: serverTest,
name: "ALPS-OmitClientApplicationSettings-" + ver.name,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
Bugs: ProtocolBugs{
OmitClientApplicationSettings: true,
},
},
flags: []string{
"-select-alpn", "proto",
"-application-settings", "proto,shim",
},
// The runner is a client, so it only processes the shim's alert
// after checking connection state.
expectations: connectionExpectations{
peerApplicationSettings: []byte("shim"),
},
shouldFail: true,
expectedError: ":MISSING_EXTENSION:",
expectedLocalError: "remote error: missing extension",
})
// Test that the server rejects a missing EncryptedExtensions message.
testCases = append(testCases, testCase{
testType: serverTest,
name: "ALPS-OmitClientEncryptedExtensions-" + ver.name,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
Bugs: ProtocolBugs{
OmitClientEncryptedExtensions: true,
},
},
flags: []string{
"-select-alpn", "proto",
"-application-settings", "proto,shim",
},
// The runner is a client, so it only processes the shim's alert
// after checking connection state.
expectations: connectionExpectations{
peerApplicationSettings: []byte("shim"),
},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
expectedLocalError: "remote error: unexpected message",
})
// Test that the server rejects an unexpected EncryptedExtensions message.
testCases = append(testCases, testCase{
testType: serverTest,
name: "UnexpectedClientEncryptedExtensions-" + ver.name,
config: Config{
MaxVersion: ver.version,
Bugs: ProtocolBugs{
AlwaysSendClientEncryptedExtensions: true,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
expectedLocalError: "remote error: unexpected message",
})
// Test that the server rejects an unexpected extension in an
// expected EncryptedExtensions message.
testCases = append(testCases, testCase{
testType: serverTest,
name: "ExtraClientEncryptedExtension-" + ver.name,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
Bugs: ProtocolBugs{
SendExtraClientEncryptedExtension: true,
},
},
flags: []string{
"-select-alpn", "proto",
"-application-settings", "proto,shim",
},
// The runner is a client, so it only processes the shim's alert
// after checking connection state.
expectations: connectionExpectations{
peerApplicationSettings: []byte("shim"),
},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
expectedLocalError: "remote error: unsupported extension",
})
// Test that ALPS is carried over on 0-RTT.
for _, empty := range []bool{false, true} {
suffix := ver.name
runnerSettings := "runner"
shimSettings := "shim"
if empty {
suffix = "Empty-" + ver.name
runnerSettings = ""
shimSettings = ""
}
testCases = append(testCases, testCase{
testType: clientTest,
name: "ALPS-EarlyData-Client-" + suffix,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
},
resumeSession: true,
earlyData: true,
flags: []string{
"-advertise-alpn", "\x05proto",
"-expect-alpn", "proto",
"-application-settings", "proto," + shimSettings,
"-expect-peer-application-settings", runnerSettings,
},
expectations: connectionExpectations{
peerApplicationSettings: []byte(shimSettings),
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ALPS-EarlyData-Server-" + suffix,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
},
resumeSession: true,
earlyData: true,
flags: []string{
"-select-alpn", "proto",
"-application-settings", "proto," + shimSettings,
"-expect-peer-application-settings", runnerSettings,
},
expectations: connectionExpectations{
peerApplicationSettings: []byte(shimSettings),
},
})
// Sending application settings in 0-RTT handshakes is forbidden.
testCases = append(testCases, testCase{
testType: clientTest,
name: "ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Client-" + suffix,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
Bugs: ProtocolBugs{
SendApplicationSettingsWithEarlyData: true,
},
},
resumeSession: true,
earlyData: true,
flags: []string{
"-advertise-alpn", "\x05proto",
"-expect-alpn", "proto",
"-application-settings", "proto," + shimSettings,
"-expect-peer-application-settings", runnerSettings,
},
expectations: connectionExpectations{
peerApplicationSettings: []byte(shimSettings),
},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
expectedLocalError: "remote error: illegal parameter",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Server-" + suffix,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
Bugs: ProtocolBugs{
SendApplicationSettingsWithEarlyData: true,
},
},
resumeSession: true,
earlyData: true,
flags: []string{
"-select-alpn", "proto",
"-application-settings", "proto," + shimSettings,
"-expect-peer-application-settings", runnerSettings,
},
expectations: connectionExpectations{
peerApplicationSettings: []byte(shimSettings),
},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
expectedLocalError: "remote error: unexpected message",
})
}
// Test that the client and server each decline early data if local
// ALPS preferences has changed for the current connection.
alpsMismatchTests := []struct {
name string
initialSettings, resumeSettings []byte
}{
{"DifferentValues", []byte("settings1"), []byte("settings2")},
{"OnOff", []byte("settings"), nil},
{"OffOn", nil, []byte("settings")},
// The empty settings value should not be mistaken for ALPS not
// being negotiated.
{"OnEmpty", []byte("settings"), []byte{}},
{"EmptyOn", []byte{}, []byte("settings")},
{"EmptyOff", []byte{}, nil},
{"OffEmpty", nil, []byte{}},
}
for _, test := range alpsMismatchTests {
flags := []string{"-on-resume-expect-early-data-reason", "alps_mismatch"}
if test.initialSettings != nil {
flags = append(flags, "-on-initial-application-settings", "proto,"+string(test.initialSettings))
flags = append(flags, "-on-initial-expect-peer-application-settings", "runner")
}
if test.resumeSettings != nil {
flags = append(flags, "-on-resume-application-settings", "proto,"+string(test.resumeSettings))
flags = append(flags, "-on-resume-expect-peer-application-settings", "runner")
}
// The client should not offer early data.
testCases = append(testCases, testCase{
testType: clientTest,
name: fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Client-%s", test.name, ver.name),
config: Config{
MaxVersion: ver.version,
MaxEarlyDataSize: 16384,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
},
resumeSession: true,
flags: append([]string{
"-enable-early-data",
"-expect-ticket-supports-early-data",
"-expect-no-offer-early-data",
"-advertise-alpn", "\x05proto",
"-expect-alpn", "proto",
}, flags...),
expectations: connectionExpectations{
peerApplicationSettings: test.initialSettings,
},
resumeExpectations: &connectionExpectations{
peerApplicationSettings: test.resumeSettings,
},
})
// The server should reject early data.
testCases = append(testCases, testCase{
testType: serverTest,
name: fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Server-%s", test.name, ver.name),
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
},
resumeSession: true,
earlyData: true,
expectEarlyDataRejected: true,
flags: append([]string{
"-select-alpn", "proto",
}, flags...),
expectations: connectionExpectations{
peerApplicationSettings: test.initialSettings,
},
resumeExpectations: &connectionExpectations{
peerApplicationSettings: test.resumeSettings,
},
})
}
} else {
// Test the client rejects the ALPS extension if the server
// negotiated TLS 1.2 or below.
testCases = append(testCases, testCase{
testType: clientTest,
name: "ALPS-Reject-Client-" + ver.name,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"foo"},
ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
Bugs: ProtocolBugs{
AlwaysNegotiateApplicationSettings: true,
},
},
flags: []string{
"-advertise-alpn", "\x03foo",
"-expect-alpn", "foo",
"-application-settings", "foo,shim",
},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
expectedLocalError: "remote error: unsupported extension",
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "ALPS-Reject-Client-Resume-" + ver.name,
config: Config{
MaxVersion: ver.version,
},
resumeConfig: &Config{
MaxVersion: ver.version,
NextProtos: []string{"foo"},
ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
Bugs: ProtocolBugs{
AlwaysNegotiateApplicationSettings: true,
},
},
resumeSession: true,
flags: []string{
"-on-resume-advertise-alpn", "\x03foo",
"-on-resume-expect-alpn", "foo",
"-on-resume-application-settings", "foo,shim",
},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
expectedLocalError: "remote error: unsupported extension",
})
// Test the server declines ALPS if it negotiates TLS 1.2 or below.
testCases = append(testCases, testCase{
testType: serverTest,
name: "ALPS-Decline-Server-" + ver.name,
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"foo"},
ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
},
// Test both TLS 1.2 full and resumption handshakes.
resumeSession: true,
flags: []string{
"-select-alpn", "foo",
"-application-settings", "foo,shim",
},
// If not specified, runner and shim both implicitly expect ALPS
// is not negotiated.
})
}
// Test Token Binding.
const maxTokenBindingVersion = 16

@ -18,17 +18,20 @@ import (
// sessionState contains the information that is serialized into a session
// ticket in order to later resume a connection.
type sessionState struct {
vers uint16
cipherSuite uint16
masterSecret []byte
handshakeHash []byte
certificates [][]byte
extendedMasterSecret bool
earlyALPN []byte
ticketCreationTime time.Time
ticketExpiration time.Time
ticketFlags uint32
ticketAgeAdd uint32
vers uint16
cipherSuite uint16
masterSecret []byte
handshakeHash []byte
certificates [][]byte
extendedMasterSecret bool
earlyALPN []byte
ticketCreationTime time.Time
ticketExpiration time.Time
ticketFlags uint32
ticketAgeAdd uint32
hasApplicationSettings bool
localApplicationSettings []byte
peerApplicationSettings []byte
}
func (s *sessionState) marshal() []byte {
@ -61,9 +64,33 @@ func (s *sessionState) marshal() []byte {
earlyALPN := msg.addU16LengthPrefixed()
earlyALPN.addBytes(s.earlyALPN)
if s.hasApplicationSettings {
msg.addU8(1)
msg.addU16LengthPrefixed().addBytes(s.localApplicationSettings)
msg.addU16LengthPrefixed().addBytes(s.peerApplicationSettings)
} else {
msg.addU8(0)
}
return msg.finish()
}
func readBool(reader *byteReader, out *bool) bool {
var value uint8
if !reader.readU8(&value) {
return false
}
if value == 0 {
*out = false
return true
}
if value == 1 {
*out = true
return true
}
return false
}
func (s *sessionState) unmarshal(data []byte) bool {
reader := byteReader(data)
var numCerts uint16
@ -82,15 +109,7 @@ func (s *sessionState) unmarshal(data []byte) bool {
}
}
var extendedMasterSecret uint8
if !reader.readU8(&extendedMasterSecret) {
return false
}
if extendedMasterSecret == 0 {
s.extendedMasterSecret = false
} else if extendedMasterSecret == 1 {
s.extendedMasterSecret = true
} else {
if !readBool(&reader, &s.extendedMasterSecret) {
return false
}
@ -107,7 +126,18 @@ func (s *sessionState) unmarshal(data []byte) bool {
}
if !reader.readU16LengthPrefixedBytes(&s.earlyALPN) ||
len(reader) > 0 {
!readBool(&reader, &s.hasApplicationSettings) {
return false
}
if s.hasApplicationSettings {
if !reader.readU16LengthPrefixedBytes(&s.localApplicationSettings) ||
!reader.readU16LengthPrefixedBytes(&s.peerApplicationSettings) {
return false
}
}
if len(reader) > 0 {
return false
}

@ -185,6 +185,13 @@ const Flag<std::string> kStringFlags[] = {
{"-expect-early-data-reason", &TestConfig::expect_early_data_reason},
};
// TODO(davidben): When we can depend on C++17 or Abseil, switch this to
// std::optional or absl::optional.
const Flag<std::unique_ptr<std::string>> kOptionalStringFlags[] = {
{"-expect-peer-application-settings",
&TestConfig::expect_peer_application_settings},
};
const Flag<std::string> kBase64Flags[] = {
{"-expect-certificate-types", &TestConfig::expect_certificate_types},
{"-expect-channel-id", &TestConfig::expect_channel_id},
@ -231,6 +238,11 @@ const Flag<std::vector<int>> kIntVectorFlags[] = {
{"-curves", &TestConfig::curves},
};
const Flag<std::vector<std::pair<std::string, std::string>>>
kStringPairVectorFlags[] = {
{"-application-settings", &TestConfig::application_settings},
};
bool ParseFlag(char *flag, int argc, char **argv, int *i,
bool skip, TestConfig *out_config) {
bool *bool_field = FindField(out_config, kBoolFlags, flag);
@ -254,6 +266,20 @@ bool ParseFlag(char *flag, int argc, char **argv, int *i,
return true;
}
std::unique_ptr<std::string> *optional_string_field =
FindField(out_config, kOptionalStringFlags, flag);
if (optional_string_field != NULL) {
*i = *i + 1;
if (*i >= argc) {
fprintf(stderr, "Missing parameter.\n");
return false;
}
if (!skip) {
optional_string_field->reset(new std::string(argv[*i]));
}
return true;
}
std::string *base64_field = FindField(out_config, kBase64Flags, flag);
if (base64_field != NULL) {
*i = *i + 1;
@ -309,6 +335,28 @@ bool ParseFlag(char *flag, int argc, char **argv, int *i,
return true;
}
std::vector<std::pair<std::string, std::string>> *string_pair_vector_field =
FindField(out_config, kStringPairVectorFlags, flag);
if (string_pair_vector_field) {
*i = *i + 1;
if (*i >= argc) {
fprintf(stderr, "Missing parameter.\n");
return false;
}
const char *comma = strchr(argv[*i], ',');
if (!comma) {
fprintf(stderr,
"Parameter should be a pair of comma-separated strings.\n");
return false;
}
// Each instance of the flag adds to the list.
if (!skip) {
string_pair_vector_field->push_back(std::make_pair(
std::string(argv[*i], comma - argv[*i]), std::string(comma + 1)));
}
return true;
}
fprintf(stderr, "Unknown argument: %s.\n", flag);
return false;
}
@ -1555,10 +1603,20 @@ bssl::UniquePtr<SSL> TestConfig::NewSSL(
return nullptr;
}
if (!advertise_alpn.empty() &&
SSL_set_alpn_protos(ssl.get(), (const uint8_t *)advertise_alpn.data(),
advertise_alpn.size()) != 0) {
SSL_set_alpn_protos(
ssl.get(), reinterpret_cast<const uint8_t *>(advertise_alpn.data()),
advertise_alpn.size()) != 0) {
return nullptr;
}
for (const auto &pair : application_settings) {
if (!SSL_add_application_settings(
ssl.get(), reinterpret_cast<const uint8_t *>(pair.first.data()),
pair.first.size(),
reinterpret_cast<const uint8_t *>(pair.second.data()),
pair.second.size())) {
return nullptr;
}
}
if (!psk.empty()) {
SSL_set_psk_client_callback(ssl.get(), PskClientCallback);
SSL_set_psk_server_callback(ssl.get(), PskServerCallback);

@ -16,6 +16,7 @@
#define HEADER_TEST_CONFIG
#include <string>
#include <utility>
#include <vector>
#include <openssl/base.h>
@ -67,6 +68,8 @@ struct TestConfig {
std::string select_alpn;
bool decline_alpn = false;
bool select_empty_alpn = false;
std::vector<std::pair<std::string, std::string>> application_settings;
std::unique_ptr<std::string> expect_peer_application_settings;
std::string quic_transport_params;
std::string expect_quic_transport_params;
bool expect_session_miss = false;

@ -44,6 +44,7 @@ enum client_hs_state_t {
state_server_certificate_reverify,
state_read_server_finished,
state_send_end_of_early_data,
state_send_client_encrypted_extensions,
state_send_client_certificate,
state_send_client_certificate_verify,
state_complete_second_flight,
@ -487,12 +488,6 @@ static enum ssl_hs_wait_t do_read_encrypted_extensions(SSL_HANDSHAKE *hs) {
return ssl_hs_error;
}
// Store the negotiated ALPN in the session.
if (!hs->new_session->early_alpn.CopyFrom(ssl->s3->alpn_selected)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_hs_error;
}
if (ssl->s3->early_data_accepted) {
if (hs->early_session->cipher != hs->new_session->cipher) {
OPENSSL_PUT_ERROR(SSL, SSL_R_CIPHER_MISMATCH_ON_EARLY_DATA);
@ -505,11 +500,29 @@ static enum ssl_hs_wait_t do_read_encrypted_extensions(SSL_HANDSHAKE *hs) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
return ssl_hs_error;
}
if (ssl->s3->channel_id_valid || ssl->s3->token_binding_negotiated) {
// Channel ID and Token Binding are incompatible with 0-RTT. The ALPS
// extension should be negotiated implicitly.
if (ssl->s3->channel_id_valid || ssl->s3->token_binding_negotiated ||
hs->new_session->has_application_settings) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION_ON_EARLY_DATA);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
return ssl_hs_error;
}
hs->new_session->has_application_settings =
hs->early_session->has_application_settings;
if (!hs->new_session->local_application_settings.CopyFrom(
hs->early_session->local_application_settings) ||
!hs->new_session->peer_application_settings.CopyFrom(
hs->early_session->peer_application_settings)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_hs_error;
}
}
// Store the negotiated ALPN in the session.
if (!hs->new_session->early_alpn.CopyFrom(ssl->s3->alpn_selected)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_hs_error;
}
if (!ssl_hash_message(hs, msg)) {
@ -626,8 +639,7 @@ static enum ssl_hs_wait_t do_read_server_certificate(SSL_HANDSHAKE *hs) {
return ssl_hs_ok;
}
static enum ssl_hs_wait_t do_read_server_certificate_verify(
SSL_HANDSHAKE *hs) {
static enum ssl_hs_wait_t do_read_server_certificate_verify(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
SSLMessage msg;
if (!ssl->method->get_message(ssl, &msg)) {
@ -654,8 +666,7 @@ static enum ssl_hs_wait_t do_read_server_certificate_verify(
return ssl_hs_ok;
}
static enum ssl_hs_wait_t do_server_certificate_reverify(
SSL_HANDSHAKE *hs) {
static enum ssl_hs_wait_t do_server_certificate_reverify(SSL_HANDSHAKE *hs) {
switch (ssl_reverify_peer_cert(hs, /*send_alert=*/true)) {
case ssl_verify_ok:
break;
@ -718,6 +729,32 @@ static enum ssl_hs_wait_t do_send_end_of_early_data(SSL_HANDSHAKE *hs) {
}
}
hs->tls13_state = state_send_client_encrypted_extensions;
return ssl_hs_ok;
}
static enum ssl_hs_wait_t do_send_client_encrypted_extensions(
SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
// For now, only one extension uses client EncryptedExtensions. This function
// may be generalized if others use it in the future.
if (hs->new_session->has_application_settings &&
!ssl->s3->early_data_accepted) {
ScopedCBB cbb;
CBB body, extensions, extension;
if (!ssl->method->init_message(ssl, cbb.get(), &body,
SSL3_MT_ENCRYPTED_EXTENSIONS) ||
!CBB_add_u16_length_prefixed(&body, &extensions) ||
!CBB_add_u16(&extensions, TLSEXT_TYPE_application_settings) ||
!CBB_add_u16_length_prefixed(&extensions, &extension) ||
!CBB_add_bytes(&extension,
hs->new_session->local_application_settings.data(),
hs->new_session->local_application_settings.size()) ||
!ssl_add_message_cbb(ssl, cbb.get())) {
return ssl_hs_error;
}
}
hs->tls13_state = state_send_client_certificate;
return ssl_hs_ok;
}
@ -860,6 +897,9 @@ enum ssl_hs_wait_t tls13_client_handshake(SSL_HANDSHAKE *hs) {
case state_send_client_certificate:
ret = do_send_client_certificate(hs);
break;
case state_send_client_encrypted_extensions:
ret = do_send_client_encrypted_extensions(hs);
break;
case state_send_client_certificate_verify:
ret = do_send_client_certificate_verify(hs);
break;
@ -907,6 +947,8 @@ const char *tls13_client_handshake_state(SSL_HANDSHAKE *hs) {
return "TLS 1.3 client read_server_finished";
case state_send_end_of_early_data:
return "TLS 1.3 client send_end_of_early_data";
case state_send_client_encrypted_extensions:
return "TLS 1.3 client send_client_encrypted_extensions";
case state_send_client_certificate:
return "TLS 1.3 client send_client_certificate";
case state_send_client_certificate_verify:

@ -354,13 +354,6 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
&offered_ticket, msg, &client_hello)) {
case ssl_ticket_aead_ignore_ticket:
assert(!session);
if (!ssl->enable_early_data) {
ssl->s3->early_data_reason = ssl_early_data_disabled;
} else if (!offered_ticket) {
ssl->s3->early_data_reason = ssl_early_data_no_session_offered;
} else {
ssl->s3->early_data_reason = ssl_early_data_session_not_resumed;
}
if (!ssl_get_new_session(hs, 1 /* server */)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_hs_error;
@ -377,35 +370,6 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
return ssl_hs_error;
}
// |ssl_session_is_resumable| forbids cross-cipher resumptions even if the
// PRF hashes match.
assert(hs->new_cipher == session->cipher);
if (!ssl->enable_early_data) {
ssl->s3->early_data_reason = ssl_early_data_disabled;
} else if (session->ticket_max_early_data == 0) {
ssl->s3->early_data_reason = ssl_early_data_unsupported_for_session;
} else if (!hs->early_data_offered) {
ssl->s3->early_data_reason = ssl_early_data_peer_declined;
} else if (ssl->s3->channel_id_valid) {
// Channel ID is incompatible with 0-RTT.
ssl->s3->early_data_reason = ssl_early_data_channel_id;
} else if (ssl->s3->token_binding_negotiated) {
// Token Binding is incompatible with 0-RTT.
ssl->s3->early_data_reason = ssl_early_data_token_binding;
} else if (MakeConstSpan(ssl->s3->alpn_selected) != session->early_alpn) {
// The negotiated ALPN must match the one in the ticket.
ssl->s3->early_data_reason = ssl_early_data_alpn_mismatch;
} else if (ssl->s3->ticket_age_skew < -kMaxTicketAgeSkewSeconds ||
kMaxTicketAgeSkewSeconds < ssl->s3->ticket_age_skew) {
ssl->s3->early_data_reason = ssl_early_data_ticket_age_skew;
} else if (!quic_ticket_compatible(session.get(), hs->config)) {
ssl->s3->early_data_reason = ssl_early_data_quic_parameter_mismatch;
} else {
ssl->s3->early_data_reason = ssl_early_data_accepted;
ssl->s3->early_data_accepted = true;
}
ssl->s3->session_reused = true;
// Resumption incorporates fresh key material, so refresh the timeout.
@ -422,15 +386,74 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
return ssl_hs_pending_ticket;
}
// Negotiate ALPS now, after ALPN is negotiated and |hs->new_session| is
// initialized.
if (!ssl_negotiate_alps(hs, &alert, &client_hello)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
return ssl_hs_error;
}
// Determine if we're negotiating 0-RTT.
if (!ssl->enable_early_data) {
ssl->s3->early_data_reason = ssl_early_data_disabled;
} else if (!offered_ticket) {
ssl->s3->early_data_reason = ssl_early_data_no_session_offered;
} else if (!session) {
ssl->s3->early_data_reason = ssl_early_data_session_not_resumed;
} else if (session->ticket_max_early_data == 0) {
ssl->s3->early_data_reason = ssl_early_data_unsupported_for_session;
} else if (!hs->early_data_offered) {
ssl->s3->early_data_reason = ssl_early_data_peer_declined;
} else if (ssl->s3->channel_id_valid) {
// Channel ID is incompatible with 0-RTT.
ssl->s3->early_data_reason = ssl_early_data_channel_id;
} else if (ssl->s3->token_binding_negotiated) {
// Token Binding is incompatible with 0-RTT.
ssl->s3->early_data_reason = ssl_early_data_token_binding;
} else if (MakeConstSpan(ssl->s3->alpn_selected) != session->early_alpn) {
// The negotiated ALPN must match the one in the ticket.
ssl->s3->early_data_reason = ssl_early_data_alpn_mismatch;
} else if (hs->new_session->has_application_settings !=
session->has_application_settings ||
MakeConstSpan(hs->new_session->local_application_settings) !=
session->local_application_settings) {
ssl->s3->early_data_reason = ssl_early_data_alps_mismatch;
} else if (ssl->s3->ticket_age_skew < -kMaxTicketAgeSkewSeconds ||
kMaxTicketAgeSkewSeconds < ssl->s3->ticket_age_skew) {
ssl->s3->early_data_reason = ssl_early_data_ticket_age_skew;
} else if (!quic_ticket_compatible(session.get(), hs->config)) {
ssl->s3->early_data_reason = ssl_early_data_quic_parameter_mismatch;
} else {
// |ssl_session_is_resumable| forbids cross-cipher resumptions even if the
// PRF hashes match.
assert(hs->new_cipher == session->cipher);
ssl->s3->early_data_reason = ssl_early_data_accepted;
ssl->s3->early_data_accepted = true;
}
// Record connection properties in the new session.
hs->new_session->cipher = hs->new_cipher;
// Store the initial negotiated ALPN in the session.
// Store the ALPN and ALPS values in the session for 0-RTT. Note the peer
// applications settings are not generally known until client
// EncryptedExtensions.
if (!hs->new_session->early_alpn.CopyFrom(ssl->s3->alpn_selected)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_hs_error;
}
// The peer applications settings are usually received later, in
// EncryptedExtensions. But, in 0-RTT handshakes, we carry over the
// values from |session|. Do this now, before |session| is discarded.
if (ssl->s3->early_data_accepted &&
hs->new_session->has_application_settings &&
!hs->new_session->peer_application_settings.CopyFrom(
session->peer_application_settings)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_hs_error;
}
// Copy the QUIC early data context to the session.
if (ssl->enable_early_data && ssl->quic_method) {
if (!hs->new_session->quic_early_data_context.CopyFrom(
@ -854,6 +877,64 @@ static enum ssl_hs_wait_t do_process_end_of_early_data(SSL_HANDSHAKE *hs) {
hs->client_handshake_secret())) {
return ssl_hs_error;
}
hs->tls13_state = state13_read_client_encrypted_extensions;
return ssl_hs_ok;
}
static enum ssl_hs_wait_t do_read_client_encrypted_extensions(
SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
// For now, only one extension uses client EncryptedExtensions. This function
// may be generalized if others use it in the future.
if (hs->new_session->has_application_settings &&
!ssl->s3->early_data_accepted) {
SSLMessage msg;
if (!ssl->method->get_message(ssl, &msg)) {
return ssl_hs_read_message;
}
if (!ssl_check_message_type(ssl, msg, SSL3_MT_ENCRYPTED_EXTENSIONS)) {
return ssl_hs_error;
}
CBS body = msg.body, extensions;
if (!CBS_get_u16_length_prefixed(&body, &extensions) ||
CBS_len(&body) != 0) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
return ssl_hs_error;
}
// Parse out the extensions.
bool have_application_settings = false;
CBS application_settings;
SSL_EXTENSION_TYPE ext_types[] = {{TLSEXT_TYPE_application_settings,
&have_application_settings,
&application_settings}};
uint8_t alert = SSL_AD_DECODE_ERROR;
if (!ssl_parse_extensions(&extensions, &alert, ext_types,
/*ignore_unknown=*/false)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
return ssl_hs_error;
}
if (!have_application_settings) {
OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_EXTENSION);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_MISSING_EXTENSION);
return ssl_hs_error;
}
// Note that, if 0-RTT was accepted, these values will already have been
// initialized earlier.
if (!hs->new_session->peer_application_settings.CopyFrom(
application_settings) ||
!ssl_hash_message(hs, msg)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_hs_error;
}
ssl->method->next_message(ssl);
}
hs->tls13_state = state13_read_client_certificate;
return ssl_hs_ok;
}
@ -892,8 +973,7 @@ static enum ssl_hs_wait_t do_read_client_certificate(SSL_HANDSHAKE *hs) {
return ssl_hs_ok;
}
static enum ssl_hs_wait_t do_read_client_certificate_verify(
SSL_HANDSHAKE *hs) {
static enum ssl_hs_wait_t do_read_client_certificate_verify(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) {
// Skip this state.
@ -1037,6 +1117,9 @@ enum ssl_hs_wait_t tls13_server_handshake(SSL_HANDSHAKE *hs) {
case state13_process_end_of_early_data:
ret = do_process_end_of_early_data(hs);
break;
case state13_read_client_encrypted_extensions:
ret = do_read_client_encrypted_extensions(hs);
break;
case state13_read_client_certificate:
ret = do_read_client_certificate(hs);
break;
@ -1093,6 +1176,8 @@ const char *tls13_server_handshake_state(SSL_HANDSHAKE *hs) {
return "TLS 1.3 server read_second_client_flight";
case state13_process_end_of_early_data:
return "TLS 1.3 server process_end_of_early_data";
case state13_read_client_encrypted_extensions:
return "TLS 1.3 server read_client_encrypted_extensions";
case state13_read_client_certificate:
return "TLS 1.3 server read_client_certificate";
case state13_read_client_certificate_verify:

Loading…
Cancel
Save