Add experimental handshake hints API.

See go/handshake-hints (internal).

CL originally by Bin Wu <wub@google.com>. I just reworked the tests and
tidied it up a bit. This is the start of a replacement for the split
handshakes API. For now, only TLS 1.3 is supported. It starts with an
initial set of hints, but we can add more later. (In particular, we
should probably apply the remote handshaker's extension order to avoid
needing to capability protect such changes.)

Change-Id: I7b6a6dfaa84c6c6e3436d2a4026c3652b8a79f0f
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/46535
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
grpc-202302
David Benjamin 4 years ago committed by CQ bot account: commit-bot@chromium.org
parent 666f2ab65e
commit b571e77773
  1. 1
      crypto/err/ssl.errordata
  2. 102
      include/openssl/ssl.h
  3. 243
      ssl/handoff.cc
  4. 5
      ssl/handshake.cc
  5. 6
      ssl/handshake_server.cc
  6. 33
      ssl/internal.h
  7. 3
      ssl/s3_both.cc
  8. 3
      ssl/ssl_lib.cc
  9. 31
      ssl/t1_lib.cc
  10. 36
      ssl/test/bssl_shim.cc
  11. 9
      ssl/test/fuzzer.h
  12. 3
      ssl/test/fuzzer_tags.h
  13. 263
      ssl/test/handshake_util.cc
  14. 17
      ssl/test/handshake_util.h
  15. 115
      ssl/test/handshaker.cc
  16. 279
      ssl/test/runner/runner.go
  17. 21
      ssl/test/settings_writer.cc
  18. 4
      ssl/test/settings_writer.h
  19. 142
      ssl/test/test_config.cc
  20. 4
      ssl/test/test_config.h
  21. 4
      ssl/test/test_state.h
  22. 30
      ssl/tls13_both.cc
  23. 39
      ssl/tls13_server.cc
  24. 6
      ssl/tls_method.cc

@ -43,6 +43,7 @@ SSL,131,CLIENTHELLO_PARSE_FAILED
SSL,132,CLIENTHELLO_TLSEXT
SSL,133,CONNECTION_REJECTED
SSL,134,CONNECTION_TYPE_NOT_SET
SSL,316,COULD_NOT_PARSE_HINTS
SSL,135,CUSTOM_EXTENSION_ERROR
SSL,136,DATA_LENGTH_TOO_LONG
SSL,137,DECODE_ERROR

@ -567,6 +567,11 @@ OPENSSL_EXPORT int SSL_get_error(const SSL *ssl, int ret_code);
// See also |ssl_renegotiate_explicit|.
#define SSL_ERROR_WANT_RENEGOTIATE 19
// SSL_ERROR_HANDSHAKE_HINTS_READY indicates the handshake has progressed enough
// for |SSL_serialize_handshake_hints| to be called. See also
// |SSL_request_handshake_hints|.
#define SSL_ERROR_HANDSHAKE_HINTS_READY 20
// SSL_error_description returns a string representation of |err|, where |err|
// is one of the |SSL_ERROR_*| constants returned by |SSL_get_error|, or NULL
// if the value is unrecognized.
@ -3793,6 +3798,101 @@ OPENSSL_EXPORT uint64_t SSL_get_read_sequence(const SSL *ssl);
OPENSSL_EXPORT uint64_t SSL_get_write_sequence(const SSL *ssl);
// Handshake hints.
//
// *** EXPERIMENTAL — DO NOT USE WITHOUT CHECKING ***
//
// Some server deployments make asynchronous RPC calls in both ClientHello
// dispatch and private key operations. In TLS handshakes where the private key
// operation occurs in the first round-trip, this results in two consecutive RPC
// round-trips. Handshake hints allow the RPC service to predicte a signature.
// If correctly predicted, this can skip the second RPC call.
//
// First, the server installs a certificate selection callback (see
// |SSL_CTX_set_select_certificate_cb|). When that is called, it performs the
// RPC as before, but includes the ClientHello and a capabilities string from
// |SSL_serialize_capabilities|.
//
// Next, the RPC service creates its own |SSL| object, applies the results of
// certificate selection, calls |SSL_request_handshake_hints|, and runs the
// handshake. If this successfully computes handshake hints (see
// |SSL_serialize_handshake_hints|), the RPC server should send the hints
// alongside any certificate selection results.
//
// Finally, the server calls |SSL_set_handshake_hints| and applies any
// configuration from the RPC server. It then completes the handshake as before.
// If the hints apply, BoringSSL will use the predicted signature and skip the
// private key callbacks. Otherwise, BoringSSL will call private key callbacks
// to generate a signature as before.
//
// Callers should synchronize configuration across the two services.
// Configuration mismatches and some cases of version skew are not fatal, but
// may result in the hints not applying. Additionally, some handshake flows use
// the private key in later round-trips, such as TLS 1.3 HelloRetryRequest. In
// those cases, BoringSSL will not predict a signature as there is no benefit.
// Callers must allow for handshakes to complete without a predicted signature.
//
// For now, only TLS 1.3 is hinted. TLS 1.2 will work, but the hints will be
// empty.
// SSL_serialize_capabilities writes an opaque byte string to |out| describing
// some of |ssl|'s capabilities. It returns one on success and zero on error.
//
// This string is used by BoringSSL internally to reduce the impact of version
// skew.
OPENSSL_EXPORT int SSL_serialize_capabilities(const SSL *ssl, CBB *out);
// SSL_request_handshake_hints configures |ssl| to generate a handshake hint for
// |client_hello|. It returns one on success and zero on error. |client_hello|
// should contain a serialized ClientHello structure, from the |client_hello|
// and |client_hello_len| fields of the |SSL_CLIENT_HELLO| structure.
// |capabilities| should contain the output of |SSL_serialize_capabilities|.
//
// When configured, |ssl| will perform no I/O (so there is no need to configure
// |BIO|s). For QUIC, the caller should still configure an |SSL_QUIC_METHOD|,
// but the callbacks themselves will never be called and may be left NULL or
// report failure. |SSL_provide_quic_data| also should not be called.
//
// If hint generation is successful, |SSL_do_handshake| will stop the handshake
// early with |SSL_get_error| returning |SSL_ERROR_HANDSHAKE_HINTS_READY|. At
// this point, the caller should run |SSL_serialize_handshake_hints| to extract
// the resulting hints.
//
// Hint generation may fail if, e.g., |ssl| was unable to process the
// ClientHello. Callers should then complete the certificate selection RPC and
// continue the original handshake with no hint. It will likely fail, but this
// reports the correct alert to the client and is more robust in case of
// mismatch.
OPENSSL_EXPORT int SSL_request_handshake_hints(SSL *ssl,
const uint8_t *client_hello,
size_t client_hello_len,
const uint8_t *capabilities,
size_t capabilities_len);
// SSL_serialize_handshake_hints writes an opaque byte string to |out|
// containing the handshake hints computed by |out|. It returns one on success
// and zero on error. This function should only be called if
// |SSL_request_handshake_hints| was configured and the handshake terminated
// with |SSL_ERROR_HANDSHAKE_HINTS_READY|.
//
// This string may be passed to |SSL_set_handshake_hints| on another |SSL| to
// avoid an extra signature call.
OPENSSL_EXPORT int SSL_serialize_handshake_hints(const SSL *ssl, CBB *out);
// SSL_set_handshake_hints configures |ssl| to use |hints| as handshake hints.
// It returns one on success and zero on error. The handshake will then continue
// as before, but apply predicted values from |hints| where applicable.
//
// Hints may contain connection and session secrets, so they must not leak and
// must come from a source trusted to terminate the connection. However, they
// will not change |ssl|'s configuration. The caller is responsible for
// serializing and applying options from the RPC server as needed. This ensures
// |ssl|'s behavior is self-consistent and consistent with the caller's local
// decisions.
OPENSSL_EXPORT int SSL_set_handshake_hints(SSL *ssl, const uint8_t *hints,
size_t hints_len);
// Obscure functions.
// SSL_CTX_set_msg_callback installs |cb| as the message callback for |ctx|.
@ -5148,6 +5248,7 @@ OPENSSL_EXPORT bool SSL_get_traffic_secrets(
const SSL *ssl, Span<const uint8_t> *out_read_traffic_secret,
Span<const uint8_t> *out_write_traffic_secret);
BSSL_NAMESPACE_END
} // extern C++
@ -5371,6 +5472,7 @@ BSSL_NAMESPACE_END
#define SSL_R_ECH_SERVER_WOULD_HAVE_NO_RETRY_CONFIGS 313
#define SSL_R_INVALID_CLIENT_HELLO_INNER 314
#define SSL_R_INVALID_ALPN_PROTOCOL_LIST 315
#define SSL_R_COULD_NOT_PARSE_HINTS 316
#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

@ -15,6 +15,7 @@
#include <openssl/ssl.h>
#include <openssl/bytestring.h>
#include <openssl/err.h>
#include "internal.h"
@ -708,3 +709,245 @@ bool SSL_apply_handback(SSL *ssl, Span<const uint8_t> handback) {
}
BSSL_NAMESPACE_END
using namespace bssl;
int SSL_serialize_capabilities(const SSL *ssl, CBB *out) {
CBB seq;
if (!CBB_add_asn1(out, &seq, CBS_ASN1_SEQUENCE) ||
!serialize_features(&seq) || //
!CBB_flush(out)) {
return 0;
}
return 1;
}
int SSL_request_handshake_hints(SSL *ssl, const uint8_t *client_hello,
size_t client_hello_len,
const uint8_t *capabilities,
size_t capabilities_len) {
if (SSL_is_dtls(ssl)) {
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
return 0;
}
CBS cbs, seq;
CBS_init(&cbs, capabilities, capabilities_len);
UniquePtr<SSL_HANDSHAKE_HINTS> hints = MakeUnique<SSL_HANDSHAKE_HINTS>();
if (hints == nullptr ||
!CBS_get_asn1(&cbs, &seq, CBS_ASN1_SEQUENCE) ||
!apply_remote_features(ssl, &seq)) {
return 0;
}
SSL3_STATE *const s3 = ssl->s3;
s3->v2_hello_done = true;
s3->has_message = true;
Array<uint8_t> client_hello_msg;
ScopedCBB client_hello_cbb;
CBB client_hello_body;
if (!ssl->method->init_message(ssl, client_hello_cbb.get(),
&client_hello_body, SSL3_MT_CLIENT_HELLO) ||
!CBB_add_bytes(&client_hello_body, client_hello, client_hello_len) ||
!ssl->method->finish_message(ssl, client_hello_cbb.get(),
&client_hello_msg)) {
return 0;
}
s3->hs_buf.reset(BUF_MEM_new());
if (!s3->hs_buf || !BUF_MEM_append(s3->hs_buf.get(), client_hello_msg.data(),
client_hello_msg.size())) {
return 0;
}
s3->hs->hints_requested = true;
s3->hs->hints = std::move(hints);
return 1;
}
// |SSL_HANDSHAKE_HINTS| is serialized as the following ASN.1 structure. We use
// implicit tagging to make it a little more compact.
//
// HandshakeHints ::= SEQUENCE {
// serverRandom [0] IMPLICIT OCTET STRING OPTIONAL,
// keyShareHint [1] IMPLICIT KeyShareHint OPTIONAL,
// signatureHint [2] IMPLICIT SignatureHint OPTIONAL,
// -- At most one of decryptedPSKHint or ignorePSKHint may be present. It
// -- corresponds to the first entry in pre_shared_keys. TLS 1.2 session
// -- tickets will use a separate hint, to ensure the caller does not mix
// -- them up.
// decryptedPSKHint [3] IMPLICIT OCTET STRING OPTIONAL,
// ignorePSKHint [4] IMPLICIT NULL OPTIONAL,
// }
//
// KeyShareHint ::= SEQUENCE {
// groupId INTEGER,
// publicKey OCTET STRING,
// secret OCTET STRING,
// }
//
// SignatureHint ::= SEQUENCE {
// algorithm INTEGER,
// input OCTET STRING,
// subjectPublicKeyInfo OCTET STRING,
// signature OCTET STRING,
// }
// HandshakeHints tags.
static const unsigned kServerRandomTag = CBS_ASN1_CONTEXT_SPECIFIC | 0;
static const unsigned kKeyShareHintTag =
CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 1;
static const unsigned kSignatureHintTag =
CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 2;
static const unsigned kDecryptedPSKTag = CBS_ASN1_CONTEXT_SPECIFIC | 3;
static const unsigned kIgnorePSKTag = CBS_ASN1_CONTEXT_SPECIFIC | 4;
int SSL_serialize_handshake_hints(const SSL *ssl, CBB *out) {
const SSL_HANDSHAKE *hs = ssl->s3->hs.get();
if (!ssl->server || !hs->hints_requested) {
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
return 0;
}
const SSL_HANDSHAKE_HINTS *hints = hs->hints.get();
CBB seq, server_random, key_share_hint, signature_hint, decrypted_psk,
ignore_psk;
if (!CBB_add_asn1(out, &seq, CBS_ASN1_SEQUENCE)) {
return 0;
}
if (!hints->server_random.empty()) {
if (!CBB_add_asn1(&seq, &server_random, kServerRandomTag) ||
!CBB_add_bytes(&server_random, hints->server_random.data(),
hints->server_random.size())) {
return 0;
}
}
if (hints->key_share_group_id != 0 && !hints->key_share_public_key.empty() &&
!hints->key_share_secret.empty()) {
if (!CBB_add_asn1(&seq, &key_share_hint, kKeyShareHintTag) ||
!CBB_add_asn1_uint64(&key_share_hint, hints->key_share_group_id) ||
!CBB_add_asn1_octet_string(&key_share_hint,
hints->key_share_public_key.data(),
hints->key_share_public_key.size()) ||
!CBB_add_asn1_octet_string(&key_share_hint,
hints->key_share_secret.data(),
hints->key_share_secret.size())) {
return 0;
}
}
if (hints->signature_algorithm != 0 && !hints->signature_input.empty() &&
!hints->signature.empty()) {
if (!CBB_add_asn1(&seq, &signature_hint, kSignatureHintTag) ||
!CBB_add_asn1_uint64(&signature_hint, hints->signature_algorithm) ||
!CBB_add_asn1_octet_string(&signature_hint,
hints->signature_input.data(),
hints->signature_input.size()) ||
!CBB_add_asn1_octet_string(&signature_hint,
hints->signature_spki.data(),
hints->signature_spki.size()) ||
!CBB_add_asn1_octet_string(&signature_hint, hints->signature.data(),
hints->signature.size())) {
return 0;
}
}
if (!hints->decrypted_psk.empty()) {
if (!CBB_add_asn1(&seq, &decrypted_psk, kDecryptedPSKTag) ||
!CBB_add_bytes(&decrypted_psk, hints->decrypted_psk.data(),
hints->decrypted_psk.size())) {
return 0;
}
}
if (hints->ignore_psk && //
!CBB_add_asn1(&seq, &ignore_psk, kIgnorePSKTag)) {
return 0;
}
return CBB_flush(out);
}
int SSL_set_handshake_hints(SSL *ssl, const uint8_t *hints, size_t hints_len) {
if (SSL_is_dtls(ssl)) {
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
return 0;
}
UniquePtr<SSL_HANDSHAKE_HINTS> hints_obj = MakeUnique<SSL_HANDSHAKE_HINTS>();
if (hints_obj == nullptr) {
return 0;
}
CBS cbs, seq, server_random, key_share, signature_hint, ticket, ignore_psk;
int has_server_random, has_key_share, has_signature_hint, has_ticket,
has_ignore_psk;
CBS_init(&cbs, hints, hints_len);
if (!CBS_get_asn1(&cbs, &seq, CBS_ASN1_SEQUENCE) ||
!CBS_get_optional_asn1(&seq, &server_random, &has_server_random,
kServerRandomTag) ||
!CBS_get_optional_asn1(&seq, &key_share, &has_key_share,
kKeyShareHintTag) ||
!CBS_get_optional_asn1(&seq, &signature_hint, &has_signature_hint,
kSignatureHintTag) ||
!CBS_get_optional_asn1(&seq, &ticket, &has_ticket, kDecryptedPSKTag) ||
!CBS_get_optional_asn1(&seq, &ignore_psk, &has_ignore_psk,
kIgnorePSKTag)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_COULD_NOT_PARSE_HINTS);
return 0;
}
if (has_server_random && !hints_obj->server_random.CopyFrom(server_random)) {
return 0;
}
if (has_key_share) {
uint64_t group_id;
CBS public_key, secret;
if (!CBS_get_asn1_uint64(&key_share, &group_id) || //
group_id == 0 || group_id > 0xffff ||
!CBS_get_asn1(&key_share, &public_key, CBS_ASN1_OCTETSTRING) ||
!hints_obj->key_share_public_key.CopyFrom(public_key) ||
!CBS_get_asn1(&key_share, &secret, CBS_ASN1_OCTETSTRING) ||
!hints_obj->key_share_secret.CopyFrom(secret)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_COULD_NOT_PARSE_HINTS);
return 0;
}
hints_obj->key_share_group_id = static_cast<uint16_t>(group_id);
}
if (has_signature_hint) {
uint64_t sig_alg;
CBS input, spki, signature;
if (!CBS_get_asn1_uint64(&signature_hint, &sig_alg) || //
sig_alg == 0 || sig_alg > 0xffff ||
!CBS_get_asn1(&signature_hint, &input, CBS_ASN1_OCTETSTRING) ||
!hints_obj->signature_input.CopyFrom(input) ||
!CBS_get_asn1(&signature_hint, &spki, CBS_ASN1_OCTETSTRING) ||
!hints_obj->signature_spki.CopyFrom(spki) ||
!CBS_get_asn1(&signature_hint, &signature, CBS_ASN1_OCTETSTRING) ||
!hints_obj->signature.CopyFrom(signature)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_COULD_NOT_PARSE_HINTS);
return 0;
}
hints_obj->signature_algorithm = static_cast<uint16_t>(sig_alg);
}
if (has_ticket && !hints_obj->decrypted_psk.CopyFrom(ticket)) {
return 0;
}
if (has_ignore_psk) {
if (CBS_len(&ignore_psk) != 0) {
return 0;
}
hints_obj->ignore_psk = true;
}
ssl->s3->hs->hints = std::move(hints_obj);
return 1;
}

@ -149,6 +149,7 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg)
pending_private_key_op(false),
grease_seeded(false),
handback(false),
hints_requested(false),
cert_compression_negotiated(false),
apply_jdk11_workaround(false) {
assert(ssl);
@ -713,6 +714,10 @@ int ssl_run_handshake(SSL_HANDSHAKE *hs, bool *out_early_return) {
hs->wait = ssl_hs_ok;
return 1;
case ssl_hs_hints_ready:
ssl->s3->rwstate = SSL_ERROR_HANDSHAKE_HINTS_READY;
return -1;
case ssl_hs_ok:
break;
}

@ -830,6 +830,12 @@ static enum ssl_hs_wait_t do_select_certificate(SSL_HANDSHAKE *hs) {
// or below.
assert(!hs->ech_accept);
// TODO(davidben): Also compute hints for TLS 1.2. When doing so, update the
// check in bssl_shim.cc to test this.
if (hs->hints_requested) {
return ssl_hs_hints_ready;
}
ssl->s3->early_data_reason = ssl_early_data_protocol_version;
SSLMessage msg_unused;

@ -1577,6 +1577,7 @@ enum ssl_hs_wait_t {
ssl_hs_read_end_of_early_data,
ssl_hs_read_change_cipher_spec,
ssl_hs_certificate_verify,
ssl_hs_hints_ready,
};
enum ssl_grease_index_t {
@ -1644,6 +1645,26 @@ enum handback_t {
handback_max_value = handback_tls13,
};
// SSL_HANDSHAKE_HINTS contains handshake hints for a connection. See
// |SSL_request_handshake_hints| and related functions.
struct SSL_HANDSHAKE_HINTS {
static constexpr bool kAllowUniquePtr = true;
Array<uint8_t> server_random;
uint16_t key_share_group_id = 0;
Array<uint8_t> key_share_public_key;
Array<uint8_t> key_share_secret;
uint16_t signature_algorithm = 0;
Array<uint8_t> signature_input;
Array<uint8_t> signature_spki;
Array<uint8_t> signature;
Array<uint8_t> decrypted_psk;
bool ignore_psk = false;
};
struct SSL_HANDSHAKE {
explicit SSL_HANDSHAKE(SSL *ssl);
~SSL_HANDSHAKE();
@ -1842,6 +1863,13 @@ struct SSL_HANDSHAKE {
// key_block is the record-layer key block for TLS 1.2 and earlier.
Array<uint8_t> key_block;
// hints contains the handshake hints for this connection. If
// |hints_requested| is true, this field is non-null and contains the pending
// hints to filled as the predicted handshake progresses. Otherwise, this
// field, if non-null, contains hints configured by the caller and will
// influence the handshake on match.
UniquePtr<SSL_HANDSHAKE_HINTS> hints;
// ech_accept, on the server, indicates whether the server should overwrite
// part of ServerHello.random with the ECH accept_confirmation value.
bool ech_accept : 1;
@ -1931,6 +1959,11 @@ struct SSL_HANDSHAKE {
// |SSL_apply_handoff|.
bool handback : 1;
// hints_requested indicates the caller has requested handshake hints. Only
// the first round-trip of the handshake will complete, after which the
// |hints| structure can be serialized.
bool hints_requested : 1;
// cert_compression_negotiated is true iff |cert_compression_alg_id| is valid.
bool cert_compression_negotiated : 1;

@ -251,7 +251,8 @@ bool tls_flush_pending_hs_data(SSL *ssl) {
MakeConstSpan(reinterpret_cast<const uint8_t *>(pending_hs_data->data),
pending_hs_data->length);
if (ssl->quic_method) {
if (!ssl->quic_method->add_handshake_data(ssl, ssl->s3->write_level,
if ((ssl->s3->hs == nullptr || !ssl->s3->hs->hints_requested) &&
!ssl->quic_method->add_handshake_data(ssl, ssl->s3->write_level,
data.data(), data.size())) {
OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR);
return false;

@ -1378,6 +1378,7 @@ int SSL_get_error(const SSL *ssl, int ret_code) {
case SSL_ERROR_EARLY_DATA_REJECTED:
case SSL_ERROR_WANT_CERTIFICATE_VERIFY:
case SSL_ERROR_WANT_RENEGOTIATE:
case SSL_ERROR_HANDSHAKE_HINTS_READY:
return ssl->s3->rwstate;
case SSL_ERROR_WANT_READ: {
@ -1463,6 +1464,8 @@ const char *SSL_error_description(int err) {
return "HANDOFF";
case SSL_ERROR_HANDBACK:
return "HANDBACK";
case SSL_ERROR_HANDSHAKE_HINTS_READY:
return "HANDSHAKE_HINTS_READY";
default:
return nullptr;
}

@ -4059,6 +4059,7 @@ enum ssl_ticket_aead_result_t ssl_process_ticket(
SSL_HANDSHAKE *hs, UniquePtr<SSL_SESSION> *out_session,
bool *out_renew_ticket, Span<const uint8_t> ticket,
Span<const uint8_t> session_id) {
SSL *const ssl = hs->ssl;
*out_renew_ticket = false;
out_session->reset();
@ -4067,9 +4068,21 @@ enum ssl_ticket_aead_result_t ssl_process_ticket(
return ssl_ticket_aead_ignore_ticket;
}
// Tickets in TLS 1.3 are tied into pre-shared keys (PSKs), unlike in TLS 1.2
// where that concept doesn't exist. The |decrypted_psk| and |ignore_psk|
// hints only apply to PSKs. We check the version to determine which this is.
const bool is_psk = ssl_protocol_version(ssl) >= TLS1_3_VERSION;
Array<uint8_t> plaintext;
enum ssl_ticket_aead_result_t result;
if (hs->ssl->session_ctx->ticket_aead_method != NULL) {
SSL_HANDSHAKE_HINTS *const hints = hs->hints.get();
if (is_psk && hints && !hs->hints_requested &&
!hints->decrypted_psk.empty()) {
result = plaintext.CopyFrom(hints->decrypted_psk) ? ssl_ticket_aead_success
: ssl_ticket_aead_error;
} else if (is_psk && hints && !hs->hints_requested && hints->ignore_psk) {
result = ssl_ticket_aead_ignore_ticket;
} else if (ssl->session_ctx->ticket_aead_method != NULL) {
result = ssl_decrypt_ticket_with_method(hs, &plaintext, out_renew_ticket,
ticket);
} else {
@ -4078,9 +4091,8 @@ enum ssl_ticket_aead_result_t ssl_process_ticket(
// length should be well under the minimum size for the session material and
// HMAC.
if (ticket.size() < SSL_TICKET_KEY_NAME_LEN + EVP_MAX_IV_LENGTH) {
return ssl_ticket_aead_ignore_ticket;
}
if (hs->ssl->session_ctx->ticket_key_cb != NULL) {
result = ssl_ticket_aead_ignore_ticket;
} else if (ssl->session_ctx->ticket_key_cb != NULL) {
result =
ssl_decrypt_ticket_with_cb(hs, &plaintext, out_renew_ticket, ticket);
} else {
@ -4088,13 +4100,22 @@ enum ssl_ticket_aead_result_t ssl_process_ticket(
}
}
if (is_psk && hints && hs->hints_requested) {
if (result == ssl_ticket_aead_ignore_ticket) {
hints->ignore_psk = true;
} else if (result == ssl_ticket_aead_success &&
!hints->decrypted_psk.CopyFrom(plaintext)) {
return ssl_ticket_aead_error;
}
}
if (result != ssl_ticket_aead_success) {
return result;
}
// Decode the session.
UniquePtr<SSL_SESSION> session(SSL_SESSION_from_bytes(
plaintext.data(), plaintext.size(), hs->ssl->ctx.get()));
plaintext.data(), plaintext.size(), ssl->ctx.get()));
if (!session) {
ERR_clear_error(); // Don't leave an error on the queue.
return ssl_ticket_aead_ignore_ticket;

@ -671,6 +671,27 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume,
SSL_used_hello_retry_request(ssl) ? "" : "no ");
return false;
}
// Test that handshake hints correctly skipped the expected operations.
//
// TODO(davidben): Add support for TLS 1.2 hints and remove the version check.
// Also add a check for the session cache lookup.
if (config->handshake_hints && !config->allow_hint_mismatch &&
SSL_version(ssl) == TLS1_3_VERSION) {
const TestState *state = GetTestState(ssl);
if (!SSL_used_hello_retry_request(ssl) && state->used_private_key) {
fprintf(
stderr,
"Performed private key operation, but hint should have skipped it\n");
return false;
}
if (state->ticket_decrypt_done) {
fprintf(stderr,
"Performed ticket decryption, but hint should have skipped it\n");
return false;
}
}
return true;
}
@ -697,6 +718,17 @@ static bool DoConnection(bssl::UniquePtr<SSL_SESSION> *out_session,
} else {
SSL_set_connect_state(ssl.get());
}
if (config->handshake_hints) {
#if defined(HANDSHAKER_SUPPORTED)
GetTestState(ssl.get())->get_handshake_hints_cb =
[&](const SSL_CLIENT_HELLO *client_hello) {
return GetHandshakeHint(ssl.get(), writer, is_resume, client_hello);
};
#else
fprintf(stderr, "The external handshaker can only be used on Linux\n");
return false;
#endif
}
int sock = Connect(config->port);
if (sock == -1) {
@ -1180,8 +1212,8 @@ int main(int argc, char **argv) {
CRYPTO_library_init();
TestConfig initial_config, resume_config, retry_config;
if (!ParseConfig(argc - 1, argv + 1, &initial_config, &resume_config,
&retry_config)) {
if (!ParseConfig(argc - 1, argv + 1, /*is_shim=*/true, &initial_config,
&resume_config, &retry_config)) {
return Usage(argv[0]);
}

@ -515,6 +515,15 @@ class TLSFuzzer {
break;
}
case kHintsTag: {
CBS hints;
if (!CBS_get_u24_length_prefixed(cbs, &hints)) {
return nullptr;
}
SSL_set_handshake_hints(ssl.get(), CBS_data(&hints), CBS_len(&hints));
break;
}
default:
return nullptr;
}

@ -45,4 +45,7 @@ static const uint16_t kHandoffTag = 3;
// kHandbackTag is followed by te output of |SSL_serialize_handback|.
static const uint16_t kHandbackTag = 4;
// kHintsTag is followed by the output of |SSL_serialize_handshake_hints|.
static const uint16_t kHintsTag = 5;
#endif // HEADER_SSL_TEST_FUZZER_TAGS

@ -34,6 +34,7 @@
#include "test_config.h"
#include "test_state.h"
#include <openssl/bytestring.h>
#include <openssl/ssl.h>
using namespace bssl;
@ -232,7 +233,7 @@ static bool Proxy(BIO *socket, bool async, int control, int rfd, int wfd) {
return false;
}
switch (msg) {
case kControlMsgHandback:
case kControlMsgDone:
return true;
case kControlMsgError:
return false;
@ -304,22 +305,68 @@ static bool Proxy(BIO *socket, bool async, int control, int rfd, int wfd) {
class ScopedFD {
public:
ScopedFD() : fd_(-1) {}
explicit ScopedFD(int fd) : fd_(fd) {}
~ScopedFD() { Close(); }
ScopedFD(const ScopedFD &) = delete;
ScopedFD &operator=(const ScopedFD &) = delete;
~ScopedFD() { Reset(); }
void Close() {
ScopedFD(ScopedFD &&other) { *this = std::move(other); }
ScopedFD &operator=(ScopedFD &&other) {
Reset(other.fd_);
other.fd_ = -1;
return *this;
}
int fd() const { return fd_; }
void Reset(int fd = -1) {
if (fd_ >= 0) {
close(fd_);
}
fd_ = -1;
fd_ = fd;
}
private:
int fd_;
};
class ScopedProcess {
public:
ScopedProcess() : pid_(-1) {}
~ScopedProcess() { Reset(); }
ScopedProcess(ScopedProcess &&other) { *this = std::move(other); }
ScopedProcess &operator=(ScopedProcess &&other) {
Reset(other.pid_);
other.pid_ = -1;
return *this;
}
pid_t pid() const { return pid_; }
void Reset(pid_t pid = -1) {
if (pid_ >= 0) {
kill(pid_, SIGTERM);
int unused;
Wait(&unused);
}
pid_ = pid;
}
bool Wait(int *out_status) {
if (pid_ < 0) {
return false;
}
if (waitpid_eintr(pid_, out_status, 0) != pid_) {
return false;
}
pid_ = -1;
return true;
}
private:
pid_t pid_;
};
class FileActionsDestroyer {
public:
explicit FileActionsDestroyer(posix_spawn_file_actions_t *actions)
@ -332,11 +379,9 @@ class FileActionsDestroyer {
posix_spawn_file_actions_t *actions_;
};
// RunHandshaker forks and execs the handshaker binary, handing off |input|,
// and, after proxying some amount of handshake traffic, handing back |out|.
static bool RunHandshaker(BIO *bio, const TestConfig *config, bool is_resume,
Span<const uint8_t> input,
std::vector<uint8_t> *out) {
static bool StartHandshaker(ScopedProcess *out, ScopedFD *out_control,
const TestConfig *config, bool is_resume,
posix_spawn_file_actions_t *actions) {
if (config->handshaker_path.empty()) {
fprintf(stderr, "no -handshaker-path specified\n");
return false;
@ -347,13 +392,62 @@ static bool RunHandshaker(BIO *bio, const TestConfig *config, bool is_resume,
return false;
}
std::vector<const char *> args;
args.push_back(config->handshaker_path.c_str());
static const char kResumeFlag[] = "-handshaker-resume";
if (is_resume) {
args.push_back(kResumeFlag);
}
// config->argv omits argv[0].
for (int j = 0; j < config->argc; ++j) {
args.push_back(config->argv[j]);
}
args.push_back(nullptr);
// A datagram socket guarantees that writes are all-or-nothing.
int control[2];
if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, control) != 0) {
perror("socketpair");
return false;
}
ScopedFD control0_closer(control[0]), control1_closer(control[1]);
ScopedFD scoped_control0(control[0]), scoped_control1(control[1]);
assert(control[1] != kFdHandshakerToProxy);
assert(control[1] != kFdProxyToHandshaker);
if (posix_spawn_file_actions_addclose(actions, control[0]) != 0) {
return false;
}
if (control[1] != kFdControl &&
posix_spawn_file_actions_adddup2(actions, control[1], kFdControl) != 0) {
return false;
}
fflush(stdout);
fflush(stderr);
// MSan doesn't know that |posix_spawn| initializes its output, so initialize
// it to -1.
pid_t pid = -1;
if (posix_spawn(&pid, args[0], actions, nullptr,
const_cast<char *const *>(args.data()), environ) != 0) {
return false;
}
out->Reset(pid);
*out_control = std::move(scoped_control0);
return true;
}
// RunHandshaker forks and execs the handshaker binary, handing off |input|,
// and, after proxying some amount of handshake traffic, handing back |out|.
static bool RunHandshaker(BIO *bio, const TestConfig *config, bool is_resume,
Span<const uint8_t> input,
std::vector<uint8_t> *out) {
posix_spawn_file_actions_t actions;
if (posix_spawn_file_actions_init(&actions) != 0) {
return false;
}
FileActionsDestroyer actions_destroyer(&actions);
int rfd[2], wfd[2];
// We use pipes, rather than some other mechanism, for their buffers. During
@ -378,38 +472,15 @@ static bool RunHandshaker(BIO *bio, const TestConfig *config, bool is_resume,
}
ScopedFD wfd0_closer(wfd[0]), wfd1_closer(wfd[1]);
fflush(stdout);
fflush(stderr);
std::vector<const char *> args;
args.push_back(config->handshaker_path.c_str());
static const char kResumeFlag[] = "-handshaker-resume";
if (is_resume) {
args.push_back(kResumeFlag);
}
// config->argv omits argv[0].
for (int j = 0; j < config->argc; ++j) {
args.push_back(config->argv[j]);
}
args.push_back(nullptr);
posix_spawn_file_actions_t actions;
if (posix_spawn_file_actions_init(&actions) != 0) {
return false;
}
FileActionsDestroyer actions_destroyer(&actions);
if (posix_spawn_file_actions_addclose(&actions, control[0]) != 0 ||
posix_spawn_file_actions_addclose(&actions, rfd[1]) != 0 ||
posix_spawn_file_actions_addclose(&actions, wfd[0]) != 0) {
return false;
}
assert(kFdControl != rfd[0]);
assert(kFdControl != wfd[1]);
if (control[1] != kFdControl &&
posix_spawn_file_actions_adddup2(&actions, control[1], kFdControl) != 0) {
assert(kFdHandshakerToProxy != rfd[0]);
assert(kFdProxyToHandshaker != wfd[1]);
if (posix_spawn_file_actions_addclose(&actions, rfd[1]) != 0 ||
posix_spawn_file_actions_addclose(&actions, wfd[0]) != 0) {
return false;
}
assert(kFdProxyToHandshaker != wfd[1]);
if (rfd[0] != kFdProxyToHandshaker &&
posix_spawn_file_actions_adddup2(&actions, rfd[0],
kFdProxyToHandshaker) != 0) {
@ -421,25 +492,22 @@ static bool RunHandshaker(BIO *bio, const TestConfig *config, bool is_resume,
return false;
}
// MSan doesn't know that |posix_spawn| initializes its output, so initialize
// it to -1.
pid_t handshaker_pid = -1;
if (posix_spawn(&handshaker_pid, args[0], &actions, nullptr,
const_cast<char *const *>(args.data()), environ) != 0) {
ScopedProcess handshaker;
ScopedFD control;
if (!StartHandshaker(&handshaker, &control, config, is_resume, &actions)) {
return false;
}
control1_closer.Close();
rfd0_closer.Close();
wfd1_closer.Close();
rfd0_closer.Reset();
wfd1_closer.Reset();
if (write_eintr(control[0], input.data(), input.size()) == -1) {
if (write_eintr(control.fd(), input.data(), input.size()) == -1) {
perror("write");
return false;
}
bool ok = Proxy(bio, config->async, control[0], rfd[1], wfd[0]);
bool ok = Proxy(bio, config->async, control.fd(), rfd[1], wfd[0]);
int wstatus;
if (waitpid_eintr(handshaker_pid, &wstatus, 0) != handshaker_pid) {
if (!handshaker.Wait(&wstatus)) {
perror("waitpid");
return false;
}
@ -453,7 +521,7 @@ static bool RunHandshaker(BIO *bio, const TestConfig *config, bool is_resume,
constexpr size_t kBufSize = 1024 * 1024;
std::vector<uint8_t> buf(kBufSize);
ssize_t len = read_eintr(control[0], buf.data(), buf.size());
ssize_t len = read_eintr(control.fd(), buf.data(), buf.size());
if (len == -1) {
perror("read");
return false;
@ -463,6 +531,66 @@ static bool RunHandshaker(BIO *bio, const TestConfig *config, bool is_resume,
return true;
}
static bool RequestHandshakeHint(const TestConfig *config, bool is_resume,
Span<const uint8_t> input, bool *out_has_hints,
std::vector<uint8_t> *out_hints) {
posix_spawn_file_actions_t actions;
if (posix_spawn_file_actions_init(&actions) != 0) {
return false;
}
FileActionsDestroyer actions_destroyer(&actions);
ScopedProcess handshaker;
ScopedFD control;
if (!StartHandshaker(&handshaker, &control, config, is_resume, &actions)) {
return false;
}
if (write_eintr(control.fd(), input.data(), input.size()) == -1) {
perror("write");
return false;
}
char msg;
if (read_eintr(control.fd(), &msg, 1) != 1) {
perror("read");
return false;
}
switch (msg) {
case kControlMsgDone: {
constexpr size_t kBufSize = 1024 * 1024;
out_hints->resize(kBufSize);
ssize_t len =
read_eintr(control.fd(), out_hints->data(), out_hints->size());
if (len == -1) {
perror("read");
return false;
}
out_hints->resize(len);
*out_has_hints = true;
break;
}
case kControlMsgError:
*out_has_hints = false;
break;
default:
fprintf(stderr, "Unknown control message from handshaker: %c\n", msg);
return false;
}
int wstatus;
if (!handshaker.Wait(&wstatus)) {
perror("waitpid");
return false;
}
if (wstatus) {
fprintf(stderr, "handshaker exited irregularly\n");
return false;
}
return true;
}
// PrepareHandoff accepts the |ClientHello| from |ssl| and serializes state to
// be passed to the handshaker. The serialized state includes both the SSL
// handoff, as well test-related state.
@ -543,4 +671,35 @@ bool DoSplitHandshake(UniquePtr<SSL> *ssl, SettingsWriter *writer,
return true;
}
bool GetHandshakeHint(SSL *ssl, SettingsWriter *writer, bool is_resume,
const SSL_CLIENT_HELLO *client_hello) {
ScopedCBB input;
CBB child;
if (!CBB_init(input.get(), client_hello->client_hello_len + 256) ||
!CBB_add_u24_length_prefixed(input.get(), &child) ||
!CBB_add_bytes(&child, client_hello->client_hello,
client_hello->client_hello_len) ||
!CBB_add_u24_length_prefixed(input.get(), &child) ||
!SSL_serialize_capabilities(ssl, &child) || //
!CBB_flush(input.get())) {
return false;
}
bool has_hints;
std::vector<uint8_t> hints;
if (!RequestHandshakeHint(
GetTestConfig(ssl), is_resume,
MakeConstSpan(CBB_data(input.get()), CBB_len(input.get())),
&has_hints, &hints)) {
return false;
}
if (has_hints &&
(!writer->WriteHints(hints) ||
!SSL_set_handshake_hints(ssl, hints.data(), hints.size()))) {
return false;
}
return true;
}
#endif // defined(HANDSHAKER_SUPPORTED)

@ -44,17 +44,24 @@ int CheckIdempotentError(const char *name, SSL *ssl, std::function<int()> func);
bool DoSplitHandshake(bssl::UniquePtr<SSL> *ssl, SettingsWriter *writer,
bool is_resume);
// GetHandshakeHint requests a handshake hint from the handshaker process and
// configures the result on |ssl|. It returns true on success and false on
// error.
bool GetHandshakeHint(SSL *ssl, SettingsWriter *writer, bool is_resume,
const SSL_CLIENT_HELLO *client_hello);
// The protocol between the proxy and the handshaker is defined by these
// single-character prefixes.
// single-character prefixes. |kControlMsgDone| uses 'H' for compatibility with
// older binaries.
constexpr char kControlMsgWantRead = 'R'; // Handshaker wants data
constexpr char kControlMsgWriteCompleted = 'W'; // Proxy has sent data
constexpr char kControlMsgHandback = 'H'; // Proxy should resume control
constexpr char kControlMsgDone = 'H'; // Proxy should resume control
constexpr char kControlMsgError = 'E'; // Handshaker hit an error
// The protocol between the proxy and handshaker uses these file descriptors.
constexpr int kFdControl = 3; // Bi-directional dgram socket.
constexpr int kFdProxyToHandshaker = 4; // Uni-directional pipe.
constexpr int kFdHandshakerToProxy = 5; // Uni-directional pipe.
constexpr int kFdControl = 20; // Bi-directional dgram socket.
constexpr int kFdProxyToHandshaker = 21; // Uni-directional pipe.
constexpr int kFdHandshakerToProxy = 22; // Uni-directional pipe.
#endif // HANDSHAKER_SUPPORTED
#endif // HEADER_TEST_HANDSHAKE

@ -12,11 +12,14 @@
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <memory>
#include <openssl/bytestring.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
@ -55,7 +58,11 @@ bool Handshaker(const TestConfig *config, int rfd, int wfd,
if (!ctx) {
return false;
}
UniquePtr<SSL> ssl = config->NewSSL(ctx.get(), nullptr, nullptr);
UniquePtr<SSL> ssl =
config->NewSSL(ctx.get(), /*session=*/nullptr, /*test_state=*/nullptr);
if (!ssl) {
return false;
}
// Set |O_NONBLOCK| in order to break out of the loop when we hit
// |SSL_ERROR_WANT_READ|, so that we can send |kControlMsgWantRead| to the
@ -100,6 +107,8 @@ bool Handshaker(const TestConfig *config, int rfd, int wfd,
}
}
if (!HandbackReady(ssl.get(), ret)) {
fprintf(stderr, "Handshaker: %s\n",
SSL_error_description(SSL_get_error(ssl.get(), ret)));
ERR_print_errors_fp(stderr);
return false;
}
@ -115,7 +124,7 @@ bool Handshaker(const TestConfig *config, int rfd, int wfd,
return false;
}
char msg = kControlMsgHandback;
char msg = kControlMsgDone;
if (write_eintr(control, &msg, 1) == -1 ||
write_eintr(control, CBB_data(output.get()), CBB_len(output.get())) ==
-1) {
@ -125,6 +134,84 @@ bool Handshaker(const TestConfig *config, int rfd, int wfd,
return true;
}
bool GenerateHandshakeHint(const TestConfig *config,
bssl::Span<const uint8_t> request, int control) {
// The handshake hint contains the ClientHello and the capabilities string.
CBS cbs = request;
CBS client_hello, capabilities;
if (!CBS_get_u24_length_prefixed(&cbs, &client_hello) ||
!CBS_get_u24_length_prefixed(&cbs, &capabilities) || //
CBS_len(&cbs) != 0) {
fprintf(stderr, "Handshaker: Could not parse hint request\n");
return false;
}
UniquePtr<SSL_CTX> ctx = config->SetupCtx(/*old_ctx=*/nullptr);
if (!ctx) {
return false;
}
UniquePtr<SSL> ssl =
config->NewSSL(ctx.get(), /*session=*/nullptr,
std::unique_ptr<TestState>(new TestState));
if (!ssl) {
return false;
}
// TODO(davidben): When split handshakes is replaced, move this into |NewSSL|.
assert(config->is_server);
SSL_set_accept_state(ssl.get());
if (!SSL_request_handshake_hints(
ssl.get(), CBS_data(&client_hello), CBS_len(&client_hello),
CBS_data(&capabilities), CBS_len(&capabilities))) {
fprintf(stderr, "Handshaker: SSL_request_handshake_hints failed\n");
return false;
}
int ret = 0;
do {
ret = CheckIdempotentError("SSL_do_handshake", ssl.get(),
[&] { return SSL_do_handshake(ssl.get()); });
} while (RetryAsync(ssl.get(), ret));
if (ret > 0) {
fprintf(stderr, "Handshaker: handshake unexpectedly succeeded.\n");
return false;
}
if (SSL_get_error(ssl.get(), ret) != SSL_ERROR_HANDSHAKE_HINTS_READY) {
// Errors here may be expected if the test is testing a failing case. The
// shim should continue executing without a hint, so we report an error
// "successfully". This allows the shim to distinguish this from the other
// unexpected error cases.
//
// We intentionally avoid printing the error in this case, to avoid mixing
// up test expectations with errors from the shim.
char msg = kControlMsgError;
if (write_eintr(control, &msg, 1) == -1) {
return false;
}
return true;
}
bssl::ScopedCBB hints;
if (!CBB_init(hints.get(), 256) ||
!SSL_serialize_handshake_hints(ssl.get(), hints.get())) {
fprintf(stderr, "Handshaker: failed to serialize handshake hints\n");
return false;
}
char msg = kControlMsgDone;
if (write_eintr(control, &msg, 1) == -1 ||
write_eintr(control, CBB_data(hints.get()), CBB_len(hints.get())) == -1) {
perror("write");
return false;
}
return true;
}
int SignalError() {
const char msg = kControlMsgError;
if (write_eintr(kFdControl, &msg, 1) != 1) {
@ -137,12 +224,12 @@ int SignalError() {
int main(int argc, char **argv) {
TestConfig initial_config, resume_config, retry_config;
if (!ParseConfig(argc - 1, argv + 1, &initial_config, &resume_config,
&retry_config)) {
if (!ParseConfig(argc - 1, argv + 1, /*is_shim=*/false, &initial_config,
&resume_config, &retry_config)) {
return SignalError();
}
const TestConfig *config = initial_config.handshaker_resume
? &resume_config : &initial_config;
const TestConfig *config =
initial_config.handshaker_resume ? &resume_config : &initial_config;
#if defined(BORINGSSL_UNSAFE_DETERMINISTIC_MODE)
if (initial_config.handshaker_resume) {
// If the PRNG returns exactly the same values when trying to resume then a
@ -157,15 +244,23 @@ int main(int argc, char **argv) {
// read() will return the entire message in one go, because it's a datagram
// socket.
constexpr size_t kBufSize = 1024 * 1024;
std::vector<uint8_t> handoff(kBufSize);
ssize_t len = read_eintr(kFdControl, handoff.data(), handoff.size());
std::vector<uint8_t> request(kBufSize);
ssize_t len = read_eintr(kFdControl, request.data(), request.size());
if (len == -1) {
perror("read");
return 2;
}
if (!Handshaker(config, kFdProxyToHandshaker, kFdHandshakerToProxy, handoff,
kFdControl)) {
request.resize(static_cast<size_t>(len));
if (config->handshake_hints) {
if (!GenerateHandshakeHint(config, request, kFdControl)) {
return SignalError();
}
} else {
if (!Handshaker(config, kFdProxyToHandshaker, kFdHandshakerToProxy,
request, kFdControl)) {
return SignalError();
}
}
return 0;
}

@ -63,6 +63,7 @@ var (
pipe = flag.Bool("pipe", false, "If true, print status output suitable for piping into another program.")
testToRun = flag.String("test", "", "Semicolon-separated patterns of tests to run, or empty to run all tests")
skipTest = flag.String("skip", "", "Semicolon-separated patterns of tests to skip")
allowHintMismatch = flag.String("allow-hint-mismatch", "", "Semicolon-separated patterns of tests where hints may mismatch")
numWorkersFlag = flag.Int("num-workers", runtime.NumCPU(), "The number of workers to run in parallel.")
shimPath = flag.String("shim-path", "../../../build/ssl/test/bssl_shim", "The location of the shim binary.")
handshakerPath = flag.String("handshaker-path", "../../../build/ssl/test/handshaker", "The location of the handshaker binary.")
@ -698,6 +699,9 @@ type testCase struct {
// should retry for early rejection. In a server test, this is whether the
// test expects the shim to reject early data.
expectEarlyDataRejected bool
// skipSplitHandshake, if true, will skip the generation of a split
// handshake copy of the test.
skipSplitHandshake bool
}
var testCases []testCase
@ -1835,12 +1839,12 @@ func bigFromHex(hex string) *big.Int {
return ret
}
func convertToSplitHandshakeTests(tests []testCase) (splitHandshakeTests []testCase) {
func convertToSplitHandshakeTests(tests []testCase) (splitHandshakeTests []testCase, err error) {
var stdout bytes.Buffer
shim := exec.Command(*shimPath, "-is-handshaker-supported")
shim.Stdout = &stdout
if err := shim.Run(); err != nil {
panic(err)
return nil, err
}
switch strings.TrimSpace(string(stdout.Bytes())) {
@ -1849,14 +1853,20 @@ func convertToSplitHandshakeTests(tests []testCase) (splitHandshakeTests []testC
case "Yes":
break
default:
panic("Unknown output from shim: 0x" + hex.EncodeToString(stdout.Bytes()))
return nil, fmt.Errorf("unknown output from shim: %q", stdout.Bytes())
}
var allowHintMismatchPattern []string
if len(*allowHintMismatch) > 0 {
allowHintMismatchPattern = strings.Split(*allowHintMismatch, ";")
}
NextTest:
for _, test := range tests {
if test.protocol != tls ||
test.testType != serverTest ||
strings.Contains(test.name, "DelegatedCredentials") {
strings.Contains(test.name, "DelegatedCredentials") ||
test.skipSplitHandshake {
continue
}
@ -1868,14 +1878,40 @@ NextTest:
shTest := test
shTest.name += "-Split"
shTest.flags = make([]string, len(test.flags), len(test.flags)+1)
shTest.flags = make([]string, len(test.flags), len(test.flags)+3)
copy(shTest.flags, test.flags)
shTest.flags = append(shTest.flags, "-handoff", "-handshaker-path", *handshakerPath)
splitHandshakeTests = append(splitHandshakeTests, shTest)
}
return splitHandshakeTests
for _, test := range tests {
if test.protocol == dtls ||
test.testType != serverTest {
continue
}
var matched bool
if len(allowHintMismatchPattern) > 0 {
matched, err = match(allowHintMismatchPattern, nil, test.name)
if err != nil {
return nil, fmt.Errorf("error matching pattern: %s", err)
}
}
shTest := test
shTest.name += "-Hints"
shTest.flags = make([]string, len(test.flags), len(test.flags)+3)
copy(shTest.flags, test.flags)
shTest.flags = append(shTest.flags, "-handshake-hints", "-handshaker-path", *handshakerPath)
if matched {
shTest.flags = append(shTest.flags, "-allow-hint-mismatch")
}
splitHandshakeTests = append(splitHandshakeTests, shTest)
}
return splitHandshakeTests, nil
}
func addBasicTests() {
@ -17275,6 +17311,229 @@ func addEncryptedClientHelloTests() {
}
}
func addHintMismatchTests() {
// Each of these tests skips split handshakes because split handshakes does
// not handle a mismatch between shim and handshaker. Handshake hints,
// however, are designed to tolerate the mismatch.
//
// Note also these tests do not specify -handshake-hints directly. Instead,
// we define normal tests, that run even without a handshaker, and rely on
// convertToSplitHandshakeTests to generate a handshaker hints variant. This
// avoids repeating the -is-handshaker-supported and -handshaker-path logic.
// (While not useful, the tests will still pass without a handshaker.)
for _, protocol := range []protocol{tls, quic} {
// If the signing payload is different, the handshake still completes
// successfully. Different ALPN preferences will trigger a mismatch.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-SignatureInput",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
NextProtos: []string{"foo", "bar"},
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-select-alpn", "foo",
"-on-handshaker-select-alpn", "bar",
},
expectations: connectionExpectations{
nextProto: "foo",
nextProtoType: alpn,
},
})
// The shim and handshaker may have different curve preferences.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-KeyShare",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
// Send both curves in the key share list, to avoid getting
// mixed up with HelloRetryRequest.
DefaultCurves: []CurveID{CurveX25519, CurveP256},
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-curves", strconv.Itoa(int(CurveX25519)),
"-on-handshaker-curves", strconv.Itoa(int(CurveP256)),
},
expectations: connectionExpectations{
curveID: CurveX25519,
},
})
// If the handshaker does HelloRetryRequest, it will omit most hints.
// The shim should still work.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-HandshakerHelloRetryRequest",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
DefaultCurves: []CurveID{CurveX25519},
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-curves", strconv.Itoa(int(CurveX25519)),
"-on-handshaker-curves", strconv.Itoa(int(CurveP256)),
},
expectations: connectionExpectations{
curveID: CurveX25519,
},
})
// If the shim does HelloRetryRequest, the hints from the handshaker
// will be ignored. This is not reported as a mismatch because hints
// would not have helped the shim anyway.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-ShimHelloRetryRequest",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
DefaultCurves: []CurveID{CurveX25519},
},
flags: []string{
"-on-shim-curves", strconv.Itoa(int(CurveP256)),
"-on-handshaker-curves", strconv.Itoa(int(CurveX25519)),
},
expectations: connectionExpectations{
curveID: CurveP256,
},
})
// The shim and handshaker may have different signature algorithm
// preferences.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-SignatureAlgorithm",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
VerifySignatureAlgorithms: []signatureAlgorithm{
signatureRSAPSSWithSHA256,
signatureRSAPSSWithSHA384,
},
},
flags: []string{
"-allow-hint-mismatch",
"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
"-key-file", path.Join(*resourceDir, rsaKeyFile),
"-on-shim-signing-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA256)),
"-on-handshaker-signing-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA384)),
},
expectations: connectionExpectations{
peerSignatureAlgorithm: signatureRSAPSSWithSHA256,
},
})
// The shim and handshaker may disagree on whether resumption is allowed.
// We run the first connection with tickets enabled, so the client is
// issued a ticket, then disable tickets on the second connection.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-NoTickets1",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
},
flags: []string{
"-on-resume-allow-hint-mismatch",
"-on-shim-on-resume-no-ticket",
},
resumeSession: true,
expectResumeRejected: true,
})
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-NoTickets2",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
},
flags: []string{
"-on-resume-allow-hint-mismatch",
"-on-handshaker-on-resume-no-ticket",
},
resumeSession: true,
})
// The shim and handshaker may disagree on whether to request a client
// certificate.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-CertificateRequest",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
Certificates: []Certificate{rsaCertificate},
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-require-any-client-certificate",
},
})
// The shim and handshaker may negotiate different versions altogether.
if protocol != quic {
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-Version1",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS13,
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-max-version", strconv.Itoa(VersionTLS12),
"-on-handshaker-max-version", strconv.Itoa(VersionTLS13),
},
expectations: connectionExpectations{
version: VersionTLS12,
},
})
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-Version2",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS13,
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-max-version", strconv.Itoa(VersionTLS13),
"-on-handshaker-max-version", strconv.Itoa(VersionTLS12),
},
expectations: connectionExpectations{
version: VersionTLS13,
},
})
}
}
}
func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {
defer wg.Done()
@ -17472,8 +17731,14 @@ func main() {
addJDK11WorkaroundTests()
addDelegatedCredentialTests()
addEncryptedClientHelloTests()
addHintMismatchTests()
testCases = append(testCases, convertToSplitHandshakeTests(testCases)...)
toAppend, err := convertToSplitHandshakeTests(testCases)
if err != nil {
fmt.Fprintf(os.Stderr, "Error making split handshake tests: %s", err)
os.Exit(1)
}
testCases = append(testCases, toAppend...)
var wg sync.WaitGroup

@ -85,29 +85,26 @@ bool SettingsWriter::Commit() {
}
bool SettingsWriter::WriteHandoff(bssl::Span<const uint8_t> handoff) {
if (path_.empty()) {
return true;
return WriteData(kHandoffTag, handoff);
}
CBB child;
if (!CBB_add_u16(cbb_.get(), kHandoffTag) ||
!CBB_add_u24_length_prefixed(cbb_.get(), &child) ||
!CBB_add_bytes(&child, handoff.data(), handoff.size()) ||
!CBB_flush(cbb_.get())) {
return false;
bool SettingsWriter::WriteHandback(bssl::Span<const uint8_t> handback) {
return WriteData(kHandbackTag, handback);
}
return true;
bool SettingsWriter::WriteHints(bssl::Span<const uint8_t> hints) {
return WriteData(kHintsTag, hints);
}
bool SettingsWriter::WriteHandback(bssl::Span<const uint8_t> handback) {
bool SettingsWriter::WriteData(uint16_t tag, bssl::Span<const uint8_t> data) {
if (path_.empty()) {
return true;
}
CBB child;
if (!CBB_add_u16(cbb_.get(), kHandbackTag) ||
if (!CBB_add_u16(cbb_.get(), tag) ||
!CBB_add_u24_length_prefixed(cbb_.get(), &child) ||
!CBB_add_bytes(&child, handback.data(), handback.size()) ||
!CBB_add_bytes(&child, data.data(), data.size()) ||
!CBB_flush(cbb_.get())) {
return false;
}

@ -34,10 +34,12 @@ struct SettingsWriter {
bool Commit();
bool WriteHandoff(bssl::Span<const uint8_t> handoff);
bool WriteHandback(bssl::Span<const uint8_t> handback);
bool WriteHints(bssl::Span<const uint8_t> hints);
private:
bool WriteData(uint16_t tag, bssl::Span<const uint8_t> data);
std::string path_;
bssl::ScopedCBB cbb_;
};

@ -27,6 +27,8 @@
#include "../../crypto/internal.h"
#include "../internal.h"
#include "handshake_util.h"
#include "mock_quic_transport.h"
#include "test_state.h"
namespace {
@ -134,6 +136,8 @@ const Flag<bool> kBoolFlags[] = {
{"-allow-false-start-without-alpn",
&TestConfig::allow_false_start_without_alpn},
{"-handoff", &TestConfig::handoff},
{"-handshake-hints", &TestConfig::handshake_hints},
{"-allow-hint-mismatch", &TestConfig::allow_hint_mismatch},
{"-use-ocsp-callback", &TestConfig::use_ocsp_callback},
{"-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback},
{"-decline-ocsp-callback", &TestConfig::decline_ocsp_callback},
@ -268,7 +272,7 @@ bool DecodeBase64(std::string *out, const std::string &in) {
return true;
}
bool ParseFlag(char *flag, int argc, char **argv, int *i,
bool ParseFlag(const char *flag, int argc, char **argv, int *i,
bool skip, TestConfig *out_config) {
bool *bool_field = FindField(out_config, kBoolFlags, flag);
if (bool_field != NULL) {
@ -398,13 +402,21 @@ bool ParseFlag(char *flag, int argc, char **argv, int *i,
return false;
}
const char kInit[] = "-on-initial";
const char kResume[] = "-on-resume";
const char kRetry[] = "-on-retry";
// RemovePrefix checks if |*str| begins with |prefix| + "-". If so, it advances
// |*str| past |prefix| (but not past the "-") and returns true. Otherwise, it
// returns false and leaves |*str| unmodified.
bool RemovePrefix(const char **str, const char *prefix) {
size_t prefix_len = strlen(prefix);
if (strncmp(*str, prefix, strlen(prefix)) == 0 && (*str)[prefix_len] == '-') {
*str += strlen(prefix);
return true;
}
return false;
}
} // namespace
bool ParseConfig(int argc, char **argv,
bool ParseConfig(int argc, char **argv, bool is_shim,
TestConfig *out_initial,
TestConfig *out_resume,
TestConfig *out_retry) {
@ -412,21 +424,36 @@ bool ParseConfig(int argc, char **argv,
out_initial->argv = out_resume->argv = out_retry->argv = argv;
for (int i = 0; i < argc; i++) {
bool skip = false;
char *flag = argv[i];
if (strncmp(flag, kInit, strlen(kInit)) == 0) {
if (!ParseFlag(flag + strlen(kInit), argc, argv, &i, skip, out_initial)) {
const char *flag = argv[i];
// -on-shim and -on-handshaker prefixes enable flags only on the shim or
// handshaker.
if (RemovePrefix(&flag, "-on-shim")) {
if (!is_shim) {
skip = true;
}
} else if (RemovePrefix(&flag, "-on-handshaker")) {
if (is_shim) {
skip = true;
}
}
// The following prefixes allow different configurations for each of the
// initial, resumption, and 0-RTT retry handshakes.
if (RemovePrefix(&flag, "-on-initial")) {
if (!ParseFlag(flag, argc, argv, &i, skip, out_initial)) {
return false;
}
} else if (strncmp(flag, kResume, strlen(kResume)) == 0) {
if (!ParseFlag(flag + strlen(kResume), argc, argv, &i, skip,
out_resume)) {
} else if (RemovePrefix(&flag, "-on-resume")) {
if (!ParseFlag(flag, argc, argv, &i, skip, out_resume)) {
return false;
}
} else if (strncmp(flag, kRetry, strlen(kRetry)) == 0) {
if (!ParseFlag(flag + strlen(kRetry), argc, argv, &i, skip, out_retry)) {
} else if (RemovePrefix(&flag, "-on-retry")) {
if (!ParseFlag(flag, argc, argv, &i, skip, out_retry)) {
return false;
}
} else {
// Unprefixed flags apply to all three.
int i_init = i;
int i_resume = i;
if (!ParseFlag(flag, argc, argv, &i_init, skip, out_initial) ||
@ -1052,10 +1079,15 @@ static int ClientCertCallback(SSL *ssl, X509 **out_x509, EVP_PKEY **out_pkey) {
return 1;
}
static ssl_private_key_result_t AsyncPrivateKeyComplete(SSL *ssl, uint8_t *out,
size_t *out_len,
size_t max_out);
static ssl_private_key_result_t AsyncPrivateKeySign(
SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
uint16_t signature_algorithm, const uint8_t *in, size_t in_len) {
TestState *test_state = GetTestState(ssl);
test_state->used_private_key = true;
if (!test_state->private_key_result.empty()) {
fprintf(stderr, "AsyncPrivateKeySign called with operation pending.\n");
abort();
@ -1096,8 +1128,7 @@ static ssl_private_key_result_t AsyncPrivateKeySign(
}
test_state->private_key_result.resize(len);
// The signature will be released asynchronously in |AsyncPrivateKeyComplete|.
return ssl_private_key_retry;
return AsyncPrivateKeyComplete(ssl, out, out_len, max_out);
}
static ssl_private_key_result_t AsyncPrivateKeyDecrypt(SSL *ssl, uint8_t *out,
@ -1106,6 +1137,7 @@ static ssl_private_key_result_t AsyncPrivateKeyDecrypt(SSL *ssl, uint8_t *out,
const uint8_t *in,
size_t in_len) {
TestState *test_state = GetTestState(ssl);
test_state->used_private_key = true;
if (!test_state->private_key_result.empty()) {
fprintf(stderr, "AsyncPrivateKeyDecrypt called with operation pending.\n");
abort();
@ -1124,8 +1156,7 @@ static ssl_private_key_result_t AsyncPrivateKeyDecrypt(SSL *ssl, uint8_t *out,
test_state->private_key_result.resize(*out_len);
// The decryption will be released asynchronously in |AsyncPrivateComplete|.
return ssl_private_key_retry;
return AsyncPrivateKeyComplete(ssl, out, out_len, max_out);
}
static ssl_private_key_result_t AsyncPrivateKeyComplete(SSL *ssl, uint8_t *out,
@ -1138,9 +1169,9 @@ static ssl_private_key_result_t AsyncPrivateKeyComplete(SSL *ssl, uint8_t *out,
abort();
}
if (test_state->private_key_retries < 2) {
if (GetTestConfig(ssl)->async && test_state->private_key_retries < 2) {
// Only return the decryption on the second attempt, to test both incomplete
// |decrypt| and |decrypt_complete|.
// |sign|/|decrypt| and |complete|.
return ssl_private_key_retry;
}
@ -1174,7 +1205,10 @@ static bool InstallCertificate(SSL *ssl) {
if (pkey) {
TestState *test_state = GetTestState(ssl);
const TestConfig *config = GetTestConfig(ssl);
if (config->async) {
if (config->async || config->handshake_hints) {
// Install a custom private key if testing asynchronous callbacks, or if
// testing handshake hints. In the handshake hints case, we wish to check
// that hints only mismatch when allowed.
test_state->private_key = std::move(pkey);
SSL_set_private_key_method(ssl, &g_async_private_key_method);
} else if (!SSL_use_PrivateKey(ssl, pkey.get())) {
@ -1195,12 +1229,14 @@ static bool InstallCertificate(SSL *ssl) {
static enum ssl_select_cert_result_t SelectCertificateCallback(
const SSL_CLIENT_HELLO *client_hello) {
const TestConfig *config = GetTestConfig(client_hello->ssl);
GetTestState(client_hello->ssl)->early_callback_called = true;
SSL *ssl = client_hello->ssl;
const TestConfig *config = GetTestConfig(ssl);
TestState *test_state = GetTestState(ssl);
test_state->early_callback_called = true;
if (!config->expect_server_name.empty()) {
const char *server_name =
SSL_get_servername(client_hello->ssl, TLSEXT_NAMETYPE_host_name);
SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (server_name == nullptr ||
std::string(server_name) != config->expect_server_name) {
fprintf(stderr,
@ -1214,48 +1250,73 @@ static enum ssl_select_cert_result_t SelectCertificateCallback(
return ssl_select_cert_error;
}
// Install the certificate in the early callback.
if (config->use_early_callback) {
bool early_callback_ready =
GetTestState(client_hello->ssl)->early_callback_ready;
if (config->async && !early_callback_ready) {
// Install the certificate asynchronously.
// Simulate some asynchronous work in the early callback.
if ((config->use_early_callback || test_state->get_handshake_hints_cb) &&
config->async && !test_state->early_callback_ready) {
return ssl_select_cert_retry;
}
if (!InstallCertificate(client_hello->ssl)) {
if (test_state->get_handshake_hints_cb &&
!test_state->get_handshake_hints_cb(client_hello)) {
return ssl_select_cert_error;
}
if (config->use_early_callback && !InstallCertificate(ssl)) {
return ssl_select_cert_error;
}
return ssl_select_cert_success;
}
static int SetQuicReadSecret(SSL *ssl, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher, const uint8_t *secret,
size_t secret_len) {
return GetTestState(ssl)->quic_transport->SetReadSecret(level, cipher, secret,
secret_len);
MockQuicTransport *quic_transport = GetTestState(ssl)->quic_transport.get();
if (quic_transport == nullptr) {
fprintf(stderr, "No QUIC transport.\n");
return 0;
}
return quic_transport->SetReadSecret(level, cipher, secret, secret_len);
}
static int SetQuicWriteSecret(SSL *ssl, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher, const uint8_t *secret,
size_t secret_len) {
return GetTestState(ssl)->quic_transport->SetWriteSecret(level, cipher,
secret, secret_len);
MockQuicTransport *quic_transport = GetTestState(ssl)->quic_transport.get();
if (quic_transport == nullptr) {
fprintf(stderr, "No QUIC transport.\n");
return 0;
}
return quic_transport->SetWriteSecret(level, cipher, secret, secret_len);
}
static int AddQuicHandshakeData(SSL *ssl, enum ssl_encryption_level_t level,
const uint8_t *data, size_t len) {
return GetTestState(ssl)->quic_transport->WriteHandshakeData(level, data,
len);
MockQuicTransport *quic_transport = GetTestState(ssl)->quic_transport.get();
if (quic_transport == nullptr) {
fprintf(stderr, "No QUIC transport.\n");
return 0;
}
return quic_transport->WriteHandshakeData(level, data, len);
}
static int FlushQuicFlight(SSL *ssl) {
return GetTestState(ssl)->quic_transport->Flush();
MockQuicTransport *quic_transport = GetTestState(ssl)->quic_transport.get();
if (quic_transport == nullptr) {
fprintf(stderr, "No QUIC transport.\n");
return 0;
}
return quic_transport->Flush();
}
static int SendQuicAlert(SSL *ssl, enum ssl_encryption_level_t level,
uint8_t alert) {
return GetTestState(ssl)->quic_transport->SendAlert(level, alert);
MockQuicTransport *quic_transport = GetTestState(ssl)->quic_transport.get();
if (quic_transport == nullptr) {
fprintf(stderr, "No QUIC transport.\n");
return 0;
}
return quic_transport->SendAlert(level, alert);
}
static const SSL_QUIC_METHOD g_quic_method = {
@ -1320,7 +1381,10 @@ bssl::UniquePtr<SSL_CTX> TestConfig::SetupCtx(SSL_CTX *old_ctx) const {
SSL_CTX_set_info_callback(ssl_ctx.get(), InfoCallback);
SSL_CTX_sess_set_new_cb(ssl_ctx.get(), NewSessionCallback);
if (use_ticket_callback) {
if (use_ticket_callback || handshake_hints) {
// If using handshake hints, always enable the ticket callback, so we can
// check that hints only mismatch when allowed. The ticket callback also
// uses a constant key, which simplifies the test.
SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx.get(), TicketKeyCallback);
}

@ -167,6 +167,8 @@ struct TestConfig {
std::string expect_msg_callback;
bool allow_false_start_without_alpn = false;
bool handoff = false;
bool handshake_hints = false;
bool allow_hint_mismatch = false;
bool use_ocsp_callback = false;
bool set_ocsp_in_callback = false;
bool decline_ocsp_callback = false;
@ -198,7 +200,7 @@ struct TestConfig {
std::unique_ptr<TestState> test_state) const;
};
bool ParseConfig(int argc, char **argv, TestConfig *out_initial,
bool ParseConfig(int argc, char **argv, bool is_shim, TestConfig *out_initial,
TestConfig *out_resume, TestConfig *out_retry);
bool SetTestConfig(SSL *ssl, const TestConfig *config);

@ -17,6 +17,7 @@
#include <openssl/base.h>
#include <functional>
#include <memory>
#include <string>
#include <vector>
@ -48,6 +49,8 @@ struct TestState {
bool handshake_done = false;
// private_key is the underlying private key used when testing custom keys.
bssl::UniquePtr<EVP_PKEY> private_key;
// When private key methods are used, whether the private key was used.
bool used_private_key = false;
std::vector<uint8_t> private_key_result;
// private_key_retries is the number of times an asynchronous private key
// operation has been retried.
@ -64,6 +67,7 @@ struct TestState {
// completion. This tests that the callback is not called again after this.
bool cert_verified = false;
int explicit_renegotiates = 0;
std::function<bool(const SSL_CLIENT_HELLO*)> get_handshake_hints_cb;
};
bool SetTestState(SSL *ssl, std::unique_ptr<TestState> state);

@ -580,11 +580,41 @@ enum ssl_private_key_result_t tls13_add_certificate_verify(SSL_HANDSHAKE *hs) {
return ssl_private_key_failure;
}
SSL_HANDSHAKE_HINTS *const hints = hs->hints.get();
Array<uint8_t> spki;
if (hints) {
ScopedCBB spki_cbb;
if (!CBB_init(spki_cbb.get(), 64) ||
!EVP_marshal_public_key(spki_cbb.get(), hs->local_pubkey.get()) ||
!CBBFinishArray(spki_cbb.get(), &spki)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_private_key_failure;
}
}
if (hints && !hs->hints_requested &&
signature_algorithm == hints->signature_algorithm &&
MakeConstSpan(msg) == hints->signature_input &&
MakeConstSpan(spki) == hints->signature_spki &&
!hints->signature.empty() && hints->signature.size() <= max_sig_len) {
// Signature algorithm and input both match. Reuse the signature from hints.
sig_len = hints->signature.size();
OPENSSL_memcpy(sig, hints->signature.data(), sig_len);
} else {
enum ssl_private_key_result_t sign_result = ssl_private_key_sign(
hs, sig, &sig_len, max_sig_len, signature_algorithm, msg);
if (sign_result != ssl_private_key_success) {
return sign_result;
}
if (hints && hs->hints_requested) {
hints->signature_algorithm = signature_algorithm;
hints->signature_input = std::move(msg);
hints->signature_spki = std::move(spki);
if (!hints->signature.CopyFrom(MakeSpan(sig, sig_len))) {
return ssl_private_key_failure;
}
}
}
if (!CBB_did_write(&child, sig_len) ||
!ssl_add_message_cbb(ssl, cbb.get())) {

@ -63,15 +63,34 @@ static bool resolve_ecdhe_secret(SSL_HANDSHAKE *hs,
}
Array<uint8_t> secret;
SSL_HANDSHAKE_HINTS *const hints = hs->hints.get();
if (hints && !hs->hints_requested && hints->key_share_group_id == group_id &&
!hints->key_share_secret.empty()) {
// Copy DH secret from hints.
if (!hs->ecdh_public_key.CopyFrom(hints->key_share_public_key) ||
!secret.CopyFrom(hints->key_share_secret)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return false;
}
} else {
ScopedCBB public_key;
UniquePtr<SSLKeyShare> key_share = SSLKeyShare::Create(group_id);
if (!key_share ||
if (!key_share || //
!CBB_init(public_key.get(), 32) ||
!key_share->Accept(public_key.get(), &secret, &alert, peer_key) ||
!CBBFinishArray(public_key.get(), &hs->ecdh_public_key)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
return false;
}
if (hints && hs->hints_requested) {
hints->key_share_group_id = group_id;
if (!hints->key_share_public_key.CopyFrom(hs->ecdh_public_key) ||
!hints->key_share_secret.CopyFrom(secret)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return false;
}
}
}
return tls13_advance_key_schedule(hs, secret);
}
@ -538,6 +557,9 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
static enum ssl_hs_wait_t do_send_hello_retry_request(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
if (hs->hints_requested) {
return ssl_hs_hints_ready;
}
ScopedCBB cbb;
CBB body, session_id, extensions;
@ -734,7 +756,18 @@ static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
Span<uint8_t> random(ssl->s3->server_random);
SSL_HANDSHAKE_HINTS *const hints = hs->hints.get();
if (hints && !hs->hints_requested &&
hints->server_random.size() == random.size()) {
OPENSSL_memcpy(random.data(), hints->server_random.data(), random.size());
} else {
RAND_bytes(random.data(), random.size());
if (hints && hs->hints_requested &&
!hints->server_random.CopyFrom(random)) {
return ssl_hs_error;
}
}
assert(!hs->ech_accept || hs->ech_is_inner_present);
@ -864,6 +897,10 @@ static enum ssl_hs_wait_t do_send_server_certificate_verify(SSL_HANDSHAKE *hs) {
static enum ssl_hs_wait_t do_send_server_finished(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
if (hs->hints_requested) {
return ssl_hs_hints_ready;
}
if (!tls13_add_finished(hs) ||
// Update the secret to the master secret and derive traffic keys.
!tls13_advance_key_schedule(

@ -93,7 +93,8 @@ static bool tls_set_read_state(SSL *ssl, ssl_encryption_level_t level,
}
if (ssl->quic_method != nullptr) {
if (!ssl->quic_method->set_read_secret(ssl, level, aead_ctx->cipher(),
if ((ssl->s3->hs == nullptr || !ssl->s3->hs->hints_requested) &&
!ssl->quic_method->set_read_secret(ssl, level, aead_ctx->cipher(),
secret_for_quic.data(),
secret_for_quic.size())) {
return false;
@ -121,7 +122,8 @@ static bool tls_set_write_state(SSL *ssl, ssl_encryption_level_t level,
}
if (ssl->quic_method != nullptr) {
if (!ssl->quic_method->set_write_secret(ssl, level, aead_ctx->cipher(),
if ((ssl->s3->hs == nullptr || !ssl->s3->hs->hints_requested) &&
!ssl->quic_method->set_write_secret(ssl, level, aead_ctx->cipher(),
secret_for_quic.data(),
secret_for_quic.size())) {
return false;

Loading…
Cancel
Save