diff --git a/ssl/internal.h b/ssl/internal.h index 970a86aa..56cbd3d7 100644 --- a/ssl/internal.h +++ b/ssl/internal.h @@ -2488,14 +2488,13 @@ uint32_t ssl_hash_session_id(Span session_id); // SSL_SESSION_parse parses an |SSL_SESSION| from |cbs| and advances |cbs| over // the parsed data. -UniquePtr SSL_SESSION_parse(CBS *cbs, - const SSL_X509_METHOD *x509_method, - CRYPTO_BUFFER_POOL *pool); +OPENSSL_EXPORT UniquePtr SSL_SESSION_parse( + CBS *cbs, const SSL_X509_METHOD *x509_method, CRYPTO_BUFFER_POOL *pool); // ssl_session_serialize writes |in| to |cbb| as if it were serialising a // session for Session-ID resumption. It returns one on success and zero on // error. -int ssl_session_serialize(const SSL_SESSION *in, CBB *cbb); +OPENSSL_EXPORT int ssl_session_serialize(const SSL_SESSION *in, CBB *cbb); // ssl_session_is_context_valid returns one if |session|'s session ID context // matches the one set on |hs| and zero otherwise. diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc index 161d64b3..cae9b053 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc @@ -451,7 +451,10 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume, } } - if (config->is_server && !GetTestState(ssl)->early_callback_called) { + // early_callback_called is updated in the handshaker, so we don't see it + // here. + if (!config->handoff && config->is_server && + !GetTestState(ssl)->early_callback_called) { fprintf(stderr, "early callback not called\n"); return false; } diff --git a/ssl/test/test_state.cc b/ssl/test/test_state.cc index 14bd4a15..83ab6bf0 100644 --- a/ssl/test/test_state.cc +++ b/ssl/test/test_state.cc @@ -19,6 +19,8 @@ #include "../../crypto/internal.h" #include "../internal.h" +using namespace bssl; + static CRYPTO_once_t g_once = CRYPTO_ONCE_INIT; static int g_state_index = 0; // Some code treats the zero time special, so initialize the clock to a @@ -62,19 +64,9 @@ TestState *GetTestState(const SSL *ssl) { return (TestState *)SSL_get_ex_data(ssl, g_state_index); } -bool MoveTestState(SSL *dest, SSL *src) { - TestState *state = GetTestState(src); - if (!SSL_set_ex_data(src, g_state_index, nullptr) || - !SSL_set_ex_data(dest, g_state_index, state)) { - return false; - } - - return true; -} - static void ssl_ctx_add_session(SSL_SESSION *session, void *void_param) { SSL_CTX *ctx = reinterpret_cast(void_param); - bssl::UniquePtr new_session = bssl::SSL_SESSION_dup( + UniquePtr new_session = SSL_SESSION_dup( session, SSL_SESSION_INCLUDE_NONAUTH | SSL_SESSION_INCLUDE_TICKET); if (new_session != nullptr) { SSL_CTX_add_session(ctx, new_session.get()); @@ -84,3 +76,115 @@ static void ssl_ctx_add_session(SSL_SESSION *session, void *void_param) { void CopySessions(SSL_CTX *dst, const SSL_CTX *src) { lh_SSL_SESSION_doall_arg(src->sessions, ssl_ctx_add_session, dst); } + +static void push_session(SSL_SESSION *session, void *arg) { + auto s = reinterpret_cast *>(arg); + s->push_back(session); +} + +bool SerializeContextState(SSL_CTX *ctx, CBB *cbb) { + CBB out, ctx_sessions, ticket_keys; + uint8_t keys[48]; + if (!CBB_add_u24_length_prefixed(cbb, &out) || + !CBB_add_u16(&out, 0 /* version */) || + !SSL_CTX_get_tlsext_ticket_keys(ctx, &keys, sizeof(keys)) || + !CBB_add_u8_length_prefixed(&out, &ticket_keys) || + !CBB_add_bytes(&ticket_keys, keys, sizeof(keys)) || + !CBB_add_asn1(&out, &ctx_sessions, CBS_ASN1_SEQUENCE)) { + return false; + } + std::vector sessions; + lh_SSL_SESSION_doall_arg(ctx->sessions, push_session, &sessions); + for (const auto &sess : sessions) { + if (!ssl_session_serialize(sess, &ctx_sessions)) { + return false; + } + } + return CBB_flush(cbb); +} + +bool DeserializeContextState(CBS *cbs, SSL_CTX *ctx) { + CBS in, sessions, ticket_keys; + uint16_t version; + constexpr uint16_t kVersion = 0; + if (!CBS_get_u24_length_prefixed(cbs, &in) || + !CBS_get_u16(&in, &version) || + version > kVersion || + !CBS_get_u8_length_prefixed(&in, &ticket_keys) || + !SSL_CTX_set_tlsext_ticket_keys(ctx, CBS_data(&ticket_keys), + CBS_len(&ticket_keys)) || + !CBS_get_asn1(&in, &sessions, CBS_ASN1_SEQUENCE)) { + return false; + } + while (CBS_len(&sessions)) { + UniquePtr session = + SSL_SESSION_parse(&sessions, ctx->x509_method, ctx->pool); + if (!session) { + return false; + } + SSL_CTX_add_session(ctx, session.get()); + } + return true; +} + +bool TestState::Serialize(CBB *cbb) const { + CBB out, pending, text; + if (!CBB_add_u24_length_prefixed(cbb, &out) || + !CBB_add_u16(&out, 0 /* version */) || + !CBB_add_u24_length_prefixed(&out, &pending) || + (pending_session && + !ssl_session_serialize(pending_session.get(), &pending)) || + !CBB_add_u16_length_prefixed(&out, &text) || + !CBB_add_bytes( + &text, reinterpret_cast(msg_callback_text.data()), + msg_callback_text.length()) || + !CBB_flush(cbb)) { + return false; + } + return true; +} + +std::unique_ptr TestState::Deserialize(CBS *cbs, SSL_CTX *ctx) { + CBS in, pending_session, text; + std::unique_ptr out_state(new TestState()); + uint16_t version; + constexpr uint16_t kVersion = 0; + if (!CBS_get_u24_length_prefixed(cbs, &in) || + !CBS_get_u16(&in, &version) || + version > kVersion || + !CBS_get_u24_length_prefixed(&in, &pending_session) || + !CBS_get_u16_length_prefixed(&in, &text)) { + return nullptr; + } + if (CBS_len(&pending_session)) { + out_state->pending_session = SSL_SESSION_parse( + &pending_session, ctx->x509_method, ctx->pool); + if (!out_state->pending_session) { + return nullptr; + } + } + out_state->msg_callback_text = std::string( + reinterpret_cast(CBS_data(&text)), CBS_len(&text)); + return out_state; +} + +bool MoveTestState(SSL *dest, SSL *src) { + ScopedCBB out; + Array serialized; + if (!CBB_init(out.get(), 512) || + !SerializeContextState(src->ctx.get(), out.get()) || + !GetTestState(src)->Serialize(out.get()) || + !CBBFinishArray(out.get(), &serialized)) { + return false; + } + CBS in; + CBS_init(&in, serialized.data(), serialized.size()); + if (!DeserializeContextState(&in, dest->ctx.get()) || + !SetTestState(dest, TestState::Deserialize(&in, dest->ctx.get())) || + !GetTestState(dest)) { + return false; + } + GetTestState(dest)->async_bio = GetTestState(src)->async_bio; + GetTestState(src)->async_bio = nullptr; + return true; +} diff --git a/ssl/test/test_state.h b/ssl/test/test_state.h index 3fe2972e..21c78d2c 100644 --- a/ssl/test/test_state.h +++ b/ssl/test/test_state.h @@ -22,6 +22,17 @@ #include struct TestState { + // Serialize writes |pending_session| and |msg_callback_text| to |out|, for + // use in split-handshake tests. We don't try to serialize every bit of test + // state, but serializing |pending_session| is necessary to exercise session + // resumption, and |msg_callback_text| is especially useful. In the general + // case, checks of state updated during the handshake can be skipped when + // |config->handoff|. + bool Serialize(CBB *out) const; + + // Deserialize returns a new |TestState| from data written by |Serialize|. + static std::unique_ptr Deserialize(CBS *cbs, SSL_CTX *ctx); + // async_bio is async BIO which pauses reads and writes. BIO *async_bio = nullptr; // packeted_bio is the packeted BIO which simulates read timeouts. @@ -64,4 +75,12 @@ void AdvanceClock(unsigned seconds); void CopySessions(SSL_CTX *dest, const SSL_CTX *src); +// SerializeContextState writes session material (sessions and ticket keys) from +// |ctx| into |cbb|. +bool SerializeContextState(SSL_CTX *ctx, CBB *cbb); + +// DeserializeContextState updates |out| with material previously serialized by +// SerializeContextState. +bool DeserializeContextState(CBS *in, SSL_CTX *out); + #endif // HEADER_TEST_STATE