Implement SPAKE2+ and its integration in TLS 1.3

This change adds an implementation of SPAKE2+ using the P-256,
SHA256, HKDF-SHA256, and HMAC-SHA256 configuration, as specified
in RFC9383. It also integrates this algorithm into the TLS 1.3
handshake following the I-D specification available at
https://chris-wood.github.io/draft-bmw-tls-pake13/draft-bmw-tls-pake13.html

Change-Id: Ifc81ba974ddef014ea9dcbc7380ecf4db909225c
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/72427
Reviewed-by: Adam Langley <agl@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
This commit is contained in:
Chris Wood
2025-01-29 13:12:39 -08:00
committed by Boringssl LUCI CQ
parent 817ab07ebb
commit 2b19cd39ba
21 changed files with 3002 additions and 429 deletions
+5
View File
@@ -94,6 +94,7 @@ SSL,320,INVALID_OUTER_EXTENSION
SSL,251,INVALID_OUTER_RECORD_TYPE
SSL,269,INVALID_SCT_LIST
SSL,295,INVALID_SIGNATURE_ALGORITHM
SSL,324,INVALID_SPAKE2PLUSV1_VALUE
SSL,160,INVALID_SSL_SESSION
SSL,161,INVALID_TICKET_KEYS_LENGTH
SSL,302,KEY_USAGE_BIT_INCORRECT
@@ -135,10 +136,13 @@ SSL,187,OLD_SESSION_CIPHER_NOT_RETURNED
SSL,268,OLD_SESSION_PRF_HASH_MISMATCH
SSL,188,OLD_SESSION_VERSION_NOT_RETURNED
SSL,189,OUTPUT_ALIASES_INPUT
SSL,1122,PAKE_AND_KEY_SHARE_NOT_ALLOWED
SSL,325,PAKE_EXHAUSTED
SSL,190,PARSE_TLSEXT
SSL,191,PATH_TOO_LONG
SSL,192,PEER_DID_NOT_RETURN_A_CERTIFICATE
SSL,193,PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE
SSL,326,PEER_PAKE_MISMATCH
SSL,267,PRE_SHARED_KEY_MUST_BE_LAST
SSL,287,PRIVATE_KEY_OPERATION_FAILED
SSL,194,PROTOCOL_IS_SHUTDOWN
@@ -238,6 +242,7 @@ SSL,235,UNKNOWN_STATE
SSL,236,UNSAFE_LEGACY_RENEGOTIATION_DISABLED
SSL,237,UNSUPPORTED_CIPHER
SSL,238,UNSUPPORTED_COMPRESSION_ALGORITHM
SSL,327,UNSUPPORTED_CREDENTIAL_LIST
SSL,312,UNSUPPORTED_ECH_SERVER_CONFIG
SSL,239,UNSUPPORTED_ELLIPTIC_CURVE
SSL,240,UNSUPPORTED_PROTOCOL
+276 -266
View File
@@ -198,51 +198,51 @@ const uint32_t kOpenSSLReasonValues[] = {
0x283500f7,
0x28358c81,
0x2836099a,
0x2c323305,
0x2c32337d,
0x2c3293a3,
0x2c333313,
0x2c33b325,
0x2c343339,
0x2c34b34b,
0x2c353366,
0x2c35b378,
0x2c3633a8,
0x2c33338b,
0x2c33b39d,
0x2c3433b1,
0x2c34b3c3,
0x2c3533de,
0x2c35b3f0,
0x2c363420,
0x2c36833a,
0x2c3733b5,
0x2c37b3e1,
0x2c38341f,
0x2c38b436,
0x2c393454,
0x2c39b464,
0x2c3a3476,
0x2c3ab48a,
0x2c3b349b,
0x2c3bb4ba,
0x2c37342d,
0x2c37b459,
0x2c383497,
0x2c38b4ae,
0x2c3934cc,
0x2c39b4dc,
0x2c3a34ee,
0x2c3ab502,
0x2c3b3513,
0x2c3bb532,
0x2c3c13b5,
0x2c3c93cb,
0x2c3d34ff,
0x2c3d3577,
0x2c3d93e4,
0x2c3e3529,
0x2c3eb537,
0x2c3f354f,
0x2c3fb567,
0x2c403591,
0x2c3e35a1,
0x2c3eb5af,
0x2c3f35c7,
0x2c3fb5df,
0x2c403609,
0x2c409298,
0x2c4135a2,
0x2c41b5b5,
0x2c41361a,
0x2c41b62d,
0x2c42125e,
0x2c42b5c6,
0x2c42b63e,
0x2c43076d,
0x2c43b4ac,
0x2c4433f4,
0x2c44b574,
0x2c45338b,
0x2c45b3c7,
0x2c463444,
0x2c46b4ce,
0x2c4734e3,
0x2c47b51c,
0x2c483406,
0x2c43b524,
0x2c44346c,
0x2c44b5ec,
0x2c453403,
0x2c45b43f,
0x2c4634bc,
0x2c46b546,
0x2c47355b,
0x2c47b594,
0x2c48347e,
0x30320000,
0x30328015,
0x3033001f,
@@ -442,156 +442,156 @@ const uint32_t kOpenSSLReasonValues[] = {
0x404ea0be,
0x404f216f,
0x404fa1e5,
0x40502254,
0x4050a268,
0x4051229b,
0x405222ab,
0x4052a2cf,
0x405322e7,
0x4053a2fa,
0x4054230f,
0x4054a332,
0x4055235d,
0x4055a39a,
0x405623bf,
0x4056a3d8,
0x405723f0,
0x4057a403,
0x40582418,
0x4058a43f,
0x4059246e,
0x4059a4ae,
0x405aa4c2,
0x405b24da,
0x405ba4eb,
0x405c24fe,
0x405ca53d,
0x405d254a,
0x405da56f,
0x405e25ad,
0x4050226f,
0x4050a283,
0x405122b6,
0x405222c6,
0x4052a2ea,
0x40532302,
0x4053a315,
0x4054232a,
0x4054a34d,
0x40552378,
0x4055a3b5,
0x405623da,
0x4056a3f3,
0x4057240b,
0x4057a41e,
0x40582433,
0x4058a45a,
0x40592489,
0x4059a4c9,
0x405aa4dd,
0x405b24f5,
0x405ba506,
0x405c2519,
0x405ca558,
0x405d2565,
0x405da58a,
0x405e25c8,
0x405e8afe,
0x405f25ce,
0x405fa5db,
0x406025e9,
0x4060a60b,
0x4061266c,
0x4061a6a4,
0x406226bb,
0x4062a6cc,
0x40632719,
0x4063a72e,
0x40642745,
0x4064a771,
0x4065278c,
0x4065a7a3,
0x406627bb,
0x4066a7e5,
0x40672810,
0x4067a855,
0x4068289d,
0x4068a8be,
0x406928f0,
0x4069a91e,
0x406a293f,
0x406aa95f,
0x406b2ae7,
0x406bab0a,
0x406c2b20,
0x406cae2a,
0x406d2e59,
0x406dae81,
0x406e2eaf,
0x406eaefc,
0x406f2f55,
0x406faf8d,
0x40702fa0,
0x4070afbd,
0x405f2617,
0x405fa624,
0x40602632,
0x4060a654,
0x406126c8,
0x4061a700,
0x40622717,
0x4062a728,
0x40632775,
0x4063a78a,
0x406427a1,
0x4064a7cd,
0x406527e8,
0x4065a7ff,
0x40662817,
0x4066a841,
0x4067286c,
0x4067a8b1,
0x406828f9,
0x4068a91a,
0x4069294c,
0x4069a97a,
0x406a299b,
0x406aa9bb,
0x406b2b43,
0x406bab66,
0x406c2b7c,
0x406cae86,
0x406d2eb5,
0x406daedd,
0x406e2f0b,
0x406eaf58,
0x406f2fb1,
0x406fafe9,
0x40702ffc,
0x4070b019,
0x4071084d,
0x4071afcf,
0x40722fe2,
0x4072b018,
0x40733030,
0x4071b02b,
0x4072303e,
0x4072b074,
0x4073308c,
0x407395cd,
0x40743044,
0x4074b05e,
0x4075306f,
0x4075b083,
0x40763091,
0x407430a0,
0x4074b0ba,
0x407530cb,
0x4075b0df,
0x407630ed,
0x4076935b,
0x407730b6,
0x4077b0f6,
0x40783111,
0x4078b14a,
0x40793161,
0x4079b177,
0x407a31a3,
0x407ab1b6,
0x407b31cb,
0x407bb1dd,
0x407c320e,
0x407cb217,
0x407d28d9,
0x40773112,
0x4077b16e,
0x40783189,
0x4078b1c2,
0x407931d9,
0x4079b1ef,
0x407a321b,
0x407ab22e,
0x407b3243,
0x407bb255,
0x407c3286,
0x407cb28f,
0x407d2935,
0x407da20d,
0x407e3126,
0x407ea44f,
0x407e319e,
0x407ea46a,
0x407f1e32,
0x407fa005,
0x4080217f,
0x40809e5a,
0x408122bd,
0x408122d8,
0x4081a10c,
0x40822e9a,
0x40822ef6,
0x40829bad,
0x4083242a,
0x4083a756,
0x40832445,
0x4083a7b2,
0x40841e6e,
0x4084a487,
0x4085250f,
0x4085a633,
0x4086258f,
0x4084a4a2,
0x4085252a,
0x4085a68f,
0x408625aa,
0x4086a227,
0x40872ee0,
0x4087a681,
0x40872f3c,
0x4087a6dd,
0x40881beb,
0x4088a868,
0x4088a8c4,
0x40891c3a,
0x40899bc7,
0x408a2b58,
0x408a2bb4,
0x408a99e5,
0x408b31f2,
0x408baf6a,
0x408c251f,
0x408b326a,
0x408bafc6,
0x408c253a,
0x408d1f56,
0x408d9ea0,
0x408e2086,
0x408ea37a,
0x408f287c,
0x408fa64f,
0x40902831,
0x4090a561,
0x40912b40,
0x408ea395,
0x408f28d8,
0x408fa6ab,
0x4090288d,
0x4090a57c,
0x40912b9c,
0x40919a1d,
0x40921c87,
0x4092af1b,
0x40932ffb,
0x4092af77,
0x40933057,
0x4093a238,
0x40941e82,
0x4094ab71,
0x409526dd,
0x4095b183,
0x40962ec7,
0x4094abcd,
0x40952739,
0x4095b1fb,
0x40962f23,
0x4096a198,
0x40972283,
0x4097229e,
0x4097a0d5,
0x40981ce7,
0x4098a6f1,
0x40992f37,
0x4099a3a7,
0x409a2340,
0x4098a74d,
0x40992f93,
0x4099a3c2,
0x409a235b,
0x409a9a01,
0x409b1edc,
0x409b9f07,
0x409c30d8,
0x409c3150,
0x409c9f2f,
0x409d2154,
0x409da122,
@@ -602,41 +602,46 @@ const uint32_t kOpenSSLReasonValues[] = {
0x40a021f5,
0x40a0a0ef,
0x40a1213d,
0x40a1a49b,
0x41f42a12,
0x41f92aa4,
0x41fe2997,
0x41feac4d,
0x41ff2d7b,
0x42032a2b,
0x42082a4d,
0x4208aa89,
0x4209297b,
0x4209aac3,
0x420a29d2,
0x420aa9b2,
0x420b29f2,
0x420baa6b,
0x420c2d97,
0x420cab81,
0x420d2c34,
0x420dac6b,
0x42122c9e,
0x42172d5e,
0x4217ace0,
0x421c2d02,
0x421f2cbd,
0x42212e0f,
0x42262d41,
0x422b2ded,
0x422bac0f,
0x422c2dcf,
0x422cabc2,
0x422d2b9b,
0x422dadae,
0x422e2bee,
0x42302d1d,
0x4230ac85,
0x40a1a4b6,
0x40a22254,
0x40a2a608,
0x40a3267c,
0x40a3b134,
0x41f42a6e,
0x41f92b00,
0x41fe29f3,
0x41feaca9,
0x41ff2dd7,
0x42032a87,
0x42082aa9,
0x4208aae5,
0x420929d7,
0x4209ab1f,
0x420a2a2e,
0x420aaa0e,
0x420b2a4e,
0x420baac7,
0x420c2df3,
0x420cabdd,
0x420d2c90,
0x420dacc7,
0x42122cfa,
0x42172dba,
0x4217ad3c,
0x421c2d5e,
0x421f2d19,
0x42212e6b,
0x42262d9d,
0x422b2e49,
0x422bac6b,
0x422c2e2b,
0x422cac1e,
0x422d2bf7,
0x422dae0a,
0x422e2c4a,
0x42302d79,
0x4230ace1,
0x423125e9,
0x44320778,
0x44328787,
0x44330793,
@@ -692,71 +697,71 @@ const uint32_t kOpenSSLReasonValues[] = {
0x4c4194ad,
0x4c421616,
0x4c4293f5,
0x503235d8,
0x5032b5e7,
0x503335f2,
0x5033b602,
0x5034361b,
0x5034b635,
0x50353643,
0x5035b659,
0x5036366b,
0x5036b681,
0x5037369a,
0x5037b6ad,
0x503836c5,
0x5038b6d6,
0x503936eb,
0x5039b6ff,
0x503a371f,
0x503ab735,
0x503b374d,
0x503bb75f,
0x503c377b,
0x503cb792,
0x503d37ab,
0x503db7c1,
0x503e37ce,
0x503eb7e4,
0x503f37f6,
0x50323650,
0x5032b65f,
0x5033366a,
0x5033b67a,
0x50343693,
0x5034b6ad,
0x503536bb,
0x5035b6d1,
0x503636e3,
0x5036b6f9,
0x50373712,
0x5037b725,
0x5038373d,
0x5038b74e,
0x50393763,
0x5039b777,
0x503a3797,
0x503ab7ad,
0x503b37c5,
0x503bb7d7,
0x503c37f3,
0x503cb80a,
0x503d3823,
0x503db839,
0x503e3846,
0x503eb85c,
0x503f386e,
0x503f83b3,
0x50403809,
0x5040b819,
0x50413833,
0x5041b842,
0x5042385c,
0x5042b879,
0x50433889,
0x5043b899,
0x504438b6,
0x50403881,
0x5040b891,
0x504138ab,
0x5041b8ba,
0x504238d4,
0x5042b8f1,
0x50433901,
0x5043b911,
0x5044392e,
0x50448469,
0x504538ca,
0x5045b8e8,
0x504638fb,
0x5046b911,
0x50473923,
0x5047b938,
0x5048395e,
0x5048b96c,
0x5049397f,
0x5049b994,
0x504a39aa,
0x504ab9ba,
0x504b39da,
0x504bb9ed,
0x504c3a10,
0x504cba3e,
0x504d3a6b,
0x504dba88,
0x504e3aa3,
0x504ebabf,
0x504f3ad1,
0x504fbae8,
0x50503af7,
0x50453942,
0x5045b960,
0x50463973,
0x5046b989,
0x5047399b,
0x5047b9b0,
0x504839d6,
0x5048b9e4,
0x504939f7,
0x5049ba0c,
0x504a3a22,
0x504aba32,
0x504b3a52,
0x504bba65,
0x504c3a88,
0x504cbab6,
0x504d3ae3,
0x504dbb00,
0x504e3b1b,
0x504ebb37,
0x504f3b49,
0x504fbb60,
0x50503b6f,
0x50508729,
0x50513b0a,
0x5051b8a8,
0x50523a50,
0x50513b82,
0x5051b920,
0x50523ac8,
0x58320fd1,
0x68320f93,
0x68328ceb,
@@ -801,19 +806,19 @@ const uint32_t kOpenSSLReasonValues[] = {
0x7c321274,
0x803214c0,
0x80328090,
0x803332d4,
0x8033334c,
0x803380b9,
0x803432e3,
0x8034b24b,
0x80353269,
0x8035b2f7,
0x803632ab,
0x8036b25a,
0x8037329d,
0x8037b238,
0x803832be,
0x8038b27a,
0x8039328f,
0x8034335b,
0x8034b2c3,
0x803532e1,
0x8035b36f,
0x80363323,
0x8036b2d2,
0x80373315,
0x8037b2b0,
0x80383336,
0x8038b2f2,
0x80393307,
};
extern const size_t kOpenSSLReasonValuesLen;
@@ -1249,6 +1254,7 @@ const char kOpenSSLReasonStringData[] =
"INVALID_OUTER_RECORD_TYPE\0"
"INVALID_SCT_LIST\0"
"INVALID_SIGNATURE_ALGORITHM\0"
"INVALID_SPAKE2PLUSV1_VALUE\0"
"INVALID_SSL_SESSION\0"
"INVALID_TICKET_KEYS_LENGTH\0"
"KEY_USAGE_BIT_INCORRECT\0"
@@ -1289,10 +1295,13 @@ const char kOpenSSLReasonStringData[] =
"OLD_SESSION_CIPHER_NOT_RETURNED\0"
"OLD_SESSION_PRF_HASH_MISMATCH\0"
"OLD_SESSION_VERSION_NOT_RETURNED\0"
"PAKE_AND_KEY_SHARE_NOT_ALLOWED\0"
"PAKE_EXHAUSTED\0"
"PARSE_TLSEXT\0"
"PATH_TOO_LONG\0"
"PEER_DID_NOT_RETURN_A_CERTIFICATE\0"
"PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE\0"
"PEER_PAKE_MISMATCH\0"
"PRE_SHARED_KEY_MUST_BE_LAST\0"
"PRIVATE_KEY_OPERATION_FAILED\0"
"PROTOCOL_IS_SHUTDOWN\0"
@@ -1389,6 +1398,7 @@ const char kOpenSSLReasonStringData[] =
"UNKNOWN_STATE\0"
"UNSAFE_LEGACY_RENEGOTIATION_DISABLED\0"
"UNSUPPORTED_COMPRESSION_ALGORITHM\0"
"UNSUPPORTED_CREDENTIAL_LIST\0"
"UNSUPPORTED_ECH_SERVER_CONFIG\0"
"UNSUPPORTED_ELLIPTIC_CURVE\0"
"UNSUPPORTED_PROTOCOL\0"
+120 -1
View File
@@ -3492,6 +3492,120 @@ OPENSSL_EXPORT int SSL_CREDENTIAL_set1_delegated_credential(
SSL_CREDENTIAL *cred, CRYPTO_BUFFER *dc);
// Password Authenticated Key Exchange (PAKE).
//
// Password Authenticated Key Exchange protocols allow client and server to
// mutually authenticate one another using knowledge of a password or other
// low-entropy secret. While the TLS 1.3 pre-shared key (PSK) mechanism can
// authenticate a high-entropy secret, it cannot be used with low-entropy
// secrets as the PSK binder values can be used to mount a dictionary attack on
// a low-entropy PSK. Using TLS 1.3 with a PAKE limits an attacker to confirming
// one password guess per handshake attempt.
//
// WARNING: The PAKE mode in TLS is not a general-purpose authentication scheme.
// As the underlying secret is still low-entropy, callers must limit brute force
// attacks across multiple connections, especially in multi-connection protocols
// such as HTTP. The |error_limit| and |rate_limit| parameters in the functions
// below may be used to implement this, provided the same |SSL_CREDENTIAL|
// object is used across connections. Applications using multiple connections
// should use the PAKE credential only once to authenticate a high-entropy
// secret, e.g. exporting a PSK from |SSL_export_keying_material|, and use the
// high-entropy secret for subsequent connections.
//
// TODO(crbug.com/369963041): Implement RFC 9258 so one can actually do that.
//
// WARNING: PAKE support in TLS is still experimental and may change as the
// standard evolves. See
// https://chris-wood.github.io/draft-bmw-tls-pake13/draft-bmw-tls-pake13.html
//
// Currently, only the SPAKE2PLUS_V1 named PAKE algorithm is implemented; see
// https://chris-wood.github.io/draft-bmw-tls-pake13/draft-bmw-tls-pake13.html#section-8.1.
// SSL_PAKE_SPAKE2PLUSV1 is the codepoint for SPAKE2PLUS_V1. See
// https://chris-wood.github.io/draft-bmw-tls-pake13/draft-bmw-tls-pake13.html#name-named-pake-registry.
#define SSL_PAKE_SPAKE2PLUSV1 0x7d96
// SSL_spake2plusv1_register computes the values that the client (w0,
// w1) and server (w0, registration_record) require to run SPAKE2+. These values
// can be used when calling |SSL_CREDENTIAL_new_spake2plusv1_client| and
// |SSL_CREDENTIAL_new_spake2plusv1_server|. The client and server identities
// must match the values passed to those functions.
//
// Returns one on success and zero on error.
OPENSSL_EXPORT int SSL_spake2plusv1_register(
uint8_t out_w0[32], uint8_t out_w1[32], uint8_t out_registration_record[65],
const uint8_t *password, size_t password_len,
const uint8_t *client_identity, size_t client_identity_len,
const uint8_t *server_identity, size_t server_identity_len);
// SSL_CREDENTIAL_new_spake2plusv1_client creates a new |SSL_CREDENTIAL| that
// authenticates using SPAKE2+. It is to be used with a TLS client.
//
// The |context|, |client_identity|, and |server_identity| fields serve to
// identity the SPAKE2+ settings and both sides of a connection must agree on
// these values. If |context| is |NULL|, a default value will be used.
//
// |error_limit| is the number of failed handshakes allowed on the credential.
// After the limit is reached, using the credential will fail. Ideally this
// value is set to 1. Setting it to a higher value allows an attacker to have
// that many attempts at guessing the password using this |SSL_CREDENTIAL|.
// (Assuming that multiple TLS connections are allowed.)
//
// |w0| and |w1| come from calling |SSL_spake2plusv1_register|.
//
// Unlike most |SSL_CREDENTIAL|s, PAKE client credentials must be the only
// credential configured on the connection. BoringSSL does not currently support
// configuring multiple PAKE credentials as a client, or configuring a mix of
// PAKE and non-PAKE credentials. Once a PAKE credential is configured, the
// connection will require the server to authenticate with the same secret, so a
// successful connection then implies that the server supported the PAKE and
// knew the password.
OPENSSL_EXPORT SSL_CREDENTIAL *SSL_CREDENTIAL_new_spake2plusv1_client(
const uint8_t *context, size_t context_len, const uint8_t *client_identity,
size_t client_identity_len, const uint8_t *server_identity,
size_t server_identity_len, uint32_t error_limit, const uint8_t *w0,
size_t w0_len, const uint8_t *w1, size_t w1_len);
// SSL_CREDENTIAL_new_spake2plusv1_server creates a new |SSL_CREDENTIAL| that
// authenticates using SPAKE2+. It is to be used with a TLS server.
//
// The |context|, |client_identity|, and |server_identity| fields serve to
// identity the SPAKE2+ settings and both sides of a connection must agree on
// these values. If |context| is |NULL|, a default value will be used.
//
// |rate_limit| is the number of failed or unfinished handshakes allowed on the
// credential. After the limit is reached, using the credential will fail.
// Ideally this value is set to 1. Setting it to a higher value allows an
// attacker to have that many attempts at guessing the password using this
// |SSL_CREDENTIAL|. (Assuming that multiple TLS connections are allowed.)
//
// WARNING: |rate_limit| differs from the client's |error_limit| parameter.
// Server PAKE credentials must temporarily deduct incomplete handshakes from
// the limit, until the peer completes the handshake correctly. Thus
// applications use that multiple connections in parallel may need a higher
// limit, and thus higher attacker exposure, to avoid failures. Such
// applications should instead use one PAKE-based connection to established a
// high-entropy secret (e.g. with |SSL_export_keying_material|) instead of
// repeating the PAKE exchange for each connection.
//
// |w0| and |registration_record| come from calling |SSL_spake2plusv1_register|,
// which may be computed externally so that the server does not know the
// password, or a password-equivalent secret.
//
// A server wishing to support a PAKE should install one of these credentials.
// It is also possible to install certificate-based credentials, in which case
// both PAKE and non-PAKE clients can be supported. However, if only a PAKE
// credential is installed then the server knows that any successfully-connected
// clients also knows the password. Otherwise, the server must be careful to
// inspect the credential used for a connection before assuming that.
OPENSSL_EXPORT SSL_CREDENTIAL *SSL_CREDENTIAL_new_spake2plusv1_server(
const uint8_t *context, size_t context_len, const uint8_t *client_identity,
size_t client_identity_len, const uint8_t *server_identity,
size_t server_identity_len, uint32_t rate_limit, const uint8_t *w0,
size_t w0_len, const uint8_t *registration_record,
size_t registration_record_len);
// QUIC integration.
//
// QUIC acts as an underlying transport for the TLS 1.3 handshake. The following
@@ -5545,7 +5659,7 @@ enum ssl_compliance_policy_t BORINGSSL_ENUM_INT {
// other than by the supported signature algorithms. But WPA3's "192-bit"
// mode requires at least P-384 or 3072-bit along the chain. The caller must
// enforce this themselves on the verified chain using functions such as
// `X509_STORE_CTX_get0_chain`.
// |X509_STORE_CTX_get0_chain|.
//
// Note that this setting is less secure than the default. The
// implementation risks of using a more obscure primitive like P-384
@@ -6068,6 +6182,10 @@ BSSL_NAMESPACE_END
#define SSL_R_INCONSISTENT_ECH_NEGOTIATION 321
#define SSL_R_INVALID_ALPS_CODEPOINT 322
#define SSL_R_NO_MATCHING_ISSUER 323
#define SSL_R_INVALID_SPAKE2PLUSV1_VALUE 324
#define SSL_R_PAKE_EXHAUSTED 325
#define SSL_R_PEER_PAKE_MISMATCH 326
#define SSL_R_UNSUPPORTED_CREDENTIAL_LIST 327
#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
@@ -6102,5 +6220,6 @@ BSSL_NAMESPACE_END
#define SSL_R_TLSV1_ALERT_CERTIFICATE_REQUIRED 1116
#define SSL_R_TLSV1_ALERT_NO_APPLICATION_PROTOCOL 1120
#define SSL_R_TLSV1_ALERT_ECH_REQUIRED 1121
#define SSL_R_PAKE_AND_KEY_SHARE_NOT_ALLOWED 1122
#endif // OPENSSL_HEADER_SSL_H
+12 -8
View File
@@ -14,7 +14,7 @@
#include <openssl/base.h>
#ifdef __cplusplus
#ifdef __cplusplus
extern "C" {
#endif
@@ -114,6 +114,10 @@ extern "C" {
#define TLSEXT_TYPE_encrypted_client_hello 0xfe0d
#define TLSEXT_TYPE_ech_outer_extensions 0xfd00
// ExtensionType values from draft-bmw-tls-pake13. This is not an IANA defined
// extension number.
#define TLSEXT_TYPE_pake 0x8a3b
// ExtensionType value from RFC 6962
#define TLSEXT_TYPE_certificate_timestamp 18
@@ -153,14 +157,14 @@ extern "C" {
#define TLSEXT_MAXLEN_host_name 255
// PSK ciphersuites from 4279
#define TLS1_CK_PSK_WITH_RC4_128_SHA 0x0300008A
#define TLS1_CK_PSK_WITH_3DES_EDE_CBC_SHA 0x0300008B
#define TLS1_CK_PSK_WITH_AES_128_CBC_SHA 0x0300008C
#define TLS1_CK_PSK_WITH_AES_256_CBC_SHA 0x0300008D
#define TLS1_CK_PSK_WITH_RC4_128_SHA 0x0300008A
#define TLS1_CK_PSK_WITH_3DES_EDE_CBC_SHA 0x0300008B
#define TLS1_CK_PSK_WITH_AES_128_CBC_SHA 0x0300008C
#define TLS1_CK_PSK_WITH_AES_256_CBC_SHA 0x0300008D
// PSK ciphersuites from RFC 5489
#define TLS1_CK_ECDHE_PSK_WITH_AES_128_CBC_SHA 0x0300C035
#define TLS1_CK_ECDHE_PSK_WITH_AES_256_CBC_SHA 0x0300C036
#define TLS1_CK_ECDHE_PSK_WITH_AES_128_CBC_SHA 0x0300C035
#define TLS1_CK_ECDHE_PSK_WITH_AES_256_CBC_SHA 0x0300C036
// Additional TLS ciphersuites from expired Internet Draft
// draft-ietf-tls-56-bit-ciphersuites-01.txt
@@ -518,7 +522,7 @@ extern "C" {
#define TLS_MD_MAX_CONST_SIZE 20
#ifdef __cplusplus
#ifdef __cplusplus
} // extern C
#endif
+277 -3
View File
@@ -31,6 +31,7 @@
#include <openssl/rand.h>
#include "../crypto/internal.h"
#include "../crypto/spake2plus/internal.h"
#include "internal.h"
@@ -917,6 +918,10 @@ static bool ext_sigalgs_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
if (hs->max_version < TLS1_2_VERSION) {
return true;
}
// In PAKE mode, signature_algorithms is not used.
if (hs->pake_prover != nullptr) {
return true;
}
CBB contents, sigalgs_cbb;
if (!CBB_add_u16(out_compressible, TLSEXT_TYPE_signature_algorithms) ||
@@ -1969,6 +1974,11 @@ static bool ext_psk_key_exchange_modes_add_clienthello(
if (hs->max_version < TLS1_3_VERSION) {
return true;
}
// We do not support resumption with PAKEs, so do not offer any PSK key
// exchange modes, to signal the server not to send a ticket.
if (hs->pake_prover != nullptr) {
return true;
}
CBB contents, ke_modes;
if (!CBB_add_u16(out_compressible, TLSEXT_TYPE_psk_key_exchange_modes) ||
@@ -2116,7 +2126,9 @@ bool ssl_setup_key_shares(SSL_HANDSHAKE *hs, uint16_t override_group_id) {
hs->key_shares[1].reset();
hs->key_share_bytes.Reset();
if (hs->max_version < TLS1_3_VERSION) {
// If offering a PAKE, do not set up key shares. We do not currently support
// clients offering both PAKE and non-PAKE modes, including resumption.
if (hs->max_version < TLS1_3_VERSION || hs->pake_prover) {
return true;
}
@@ -2181,7 +2193,9 @@ bool ssl_setup_key_shares(SSL_HANDSHAKE *hs, uint16_t override_group_id) {
static bool ext_key_share_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
CBB *out_compressible,
ssl_client_hello_type_t type) {
if (hs->max_version < TLS1_3_VERSION) {
// If offering a PAKE, do not set up key shares. We do not currently support
// clients offering both PAKE and non-PAKE modes, including resumption.
if (hs->max_version < TLS1_3_VERSION || hs->pake_prover) {
return true;
}
@@ -2202,6 +2216,14 @@ static bool ext_key_share_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
bool ssl_ext_key_share_parse_serverhello(SSL_HANDSHAKE *hs,
Array<uint8_t> *out_secret,
uint8_t *out_alert, CBS *contents) {
if (hs->key_shares[0] == nullptr) {
// If we did not offer key shares, the extension should have been rejected
// as unsolicited.
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
*out_alert = SSL_AD_INTERNAL_ERROR;
return false;
}
CBS ciphertext;
uint16_t group_id;
if (!CBS_get_u16(contents, &group_id) ||
@@ -2237,7 +2259,8 @@ bool ssl_ext_key_share_parse_clienthello(SSL_HANDSHAKE *hs, bool *out_found,
Span<const uint8_t> *out_peer_key,
uint8_t *out_alert,
const SSL_CLIENT_HELLO *client_hello) {
// We only support connections that include an ECDHE key exchange.
// We only support connections that include an ECDHE key exchange, or use a
// PAKE.
CBS contents;
if (!ssl_client_hello_get_extension(client_hello, &contents,
TLSEXT_TYPE_key_share)) {
@@ -2286,7 +2309,30 @@ bool ssl_ext_key_share_parse_clienthello(SSL_HANDSHAKE *hs, bool *out_found,
return true;
}
bool ssl_ext_pake_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
if (hs->pake_share_bytes.empty()) {
return true;
}
CBB pake_ext, pake_msg;
if (!CBB_add_u16(out, TLSEXT_TYPE_pake) ||
!CBB_add_u16_length_prefixed(out, &pake_ext) ||
!CBB_add_u16(&pake_ext, SSL_PAKE_SPAKE2PLUSV1) ||
!CBB_add_u16_length_prefixed(&pake_ext, &pake_msg) ||
!CBB_add_bytes(&pake_msg, hs->pake_share_bytes.data(),
hs->pake_share_bytes.size()) ||
!CBB_flush(out)) {
return false;
}
return true;
}
bool ssl_ext_key_share_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
if (hs->pake_verifier) {
// We don't add the key share extension if a PAKE is offered.
return true;
}
CBB entry, ciphertext;
if (!CBB_add_u16(out, TLSEXT_TYPE_key_share) ||
!CBB_add_u16_length_prefixed(out, &entry) ||
@@ -2378,6 +2424,11 @@ static bool ext_supported_groups_add_clienthello(const SSL_HANDSHAKE *hs,
CBB *out_compressible,
ssl_client_hello_type_t type) {
const SSL *const ssl = hs->ssl;
// In PAKE mode, supported_groups and key_share are not used.
if (hs->pake_prover != nullptr) {
return true;
}
CBB contents, groups_bytes;
if (!CBB_add_u16(out_compressible, TLSEXT_TYPE_supported_groups) ||
!CBB_add_u16_length_prefixed(out_compressible, &contents) ||
@@ -2818,6 +2869,220 @@ static bool cert_compression_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
return true;
}
// PAKEs
//
// See
// https://chris-wood.github.io/draft-bmw-tls-pake13/draft-bmw-tls-pake13.html
bool ssl_setup_pake_shares(SSL_HANDSHAKE *hs) {
hs->pake_share_bytes.Reset();
if (hs->max_version < TLS1_3_VERSION) {
return true;
}
Array<SSL_CREDENTIAL *> creds;
if (!ssl_get_credential_list(hs, &creds)) {
return false;
}
if (std::none_of(creds.begin(), creds.end(), [](SSL_CREDENTIAL *cred) {
return cred->type == SSLCredentialType::kSPAKE2PlusV1Client;
})) {
// If there were no configured PAKE credentials, proceed without filling
// in the PAKE extension.
return true;
}
// We currently do not support multiple PAKE credentials, or a mix of PAKE and
// non-PAKE credentials.
if (creds.size() != 1u) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_CREDENTIAL_LIST);
return false;
}
SSL_CREDENTIAL *cred = creds[0];
assert(cred->type == SSLCredentialType::kSPAKE2PlusV1Client);
hs->pake_prover = MakeUnique<spake2plus::Prover>();
uint8_t prover_share[spake2plus::kShareSize];
if (hs->pake_prover == nullptr ||
!hs->pake_prover->Init(cred->pake_context, cred->client_identity,
cred->server_identity, cred->password_verifier_w0,
cred->password_verifier_w1) ||
!hs->pake_prover->GenerateShare(prover_share)) {
return false;
}
hs->credential = UpRef(cred);
bssl::ScopedCBB cbb;
CBB shares, client_identity, server_identity, pake_message;
if (!CBB_init(cbb.get(), 64) ||
!CBB_add_u16_length_prefixed(cbb.get(), &client_identity) ||
!CBB_add_bytes(&client_identity, cred->client_identity.data(),
cred->client_identity.size()) ||
!CBB_add_u16_length_prefixed(cbb.get(), &server_identity) ||
!CBB_add_bytes(&server_identity, cred->server_identity.data(),
cred->server_identity.size()) ||
!CBB_add_u16_length_prefixed(cbb.get(), &shares) ||
!CBB_add_u16(&shares, SSL_PAKE_SPAKE2PLUSV1) ||
!CBB_add_u16_length_prefixed(&shares, &pake_message) ||
!CBB_add_bytes(&pake_message, prover_share, sizeof(prover_share))) {
return false;
}
return CBBFinishArray(cbb.get(), &hs->pake_share_bytes);
}
static bool ext_pake_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
CBB *out_compressible,
ssl_client_hello_type_t type) {
if (hs->pake_share_bytes.empty()) {
return true;
}
CBB pake_share_bytes;
if (!CBB_add_u16(out_compressible, TLSEXT_TYPE_pake) ||
!CBB_add_u16_length_prefixed(out_compressible, &pake_share_bytes) ||
!CBB_add_bytes(&pake_share_bytes, hs->pake_share_bytes.data(),
hs->pake_share_bytes.size()) ||
!CBB_flush(out_compressible)) {
return false;
}
return true;
}
bool ssl_ext_pake_parse_serverhello(SSL_HANDSHAKE *hs,
Array<uint8_t> *out_secret,
uint8_t *out_alert, CBS *contents) {
*out_alert = SSL_AD_DECODE_ERROR;
if (!hs->pake_prover) {
// If we did not offer a PAKE, the extension should have been rejected as
// unsolicited.
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
*out_alert = SSL_AD_INTERNAL_ERROR;
return false;
}
CBS pake_msg;
uint16_t named_pake;
if (!CBS_get_u16(contents, &named_pake) ||
!CBS_get_u16_length_prefixed(contents, &pake_msg) ||
CBS_len(contents) != 0 || //
named_pake != SSL_PAKE_SPAKE2PLUSV1) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
return false;
}
// Check that the server's PAKE share consists of the right number of
// bytes for a PAKE share and a key confirmation message.
if (CBS_len(&pake_msg) != spake2plus::kShareSize + spake2plus::kConfirmSize) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
*out_alert = SSL_AD_ILLEGAL_PARAMETER;
return false;
}
Span<const uint8_t> pake_msg_span = pake_msg;
// Releasing the result of |ComputeConfirmation| lets the client confirm one
// PAKE guess. If all failures are used up, no more guesses are allowed.
if (!hs->credential->HasPAKEAttempts()) {
OPENSSL_PUT_ERROR(SSL, SSL_R_PAKE_EXHAUSTED);
*out_alert = SSL_AD_INTERNAL_ERROR;
return false;
}
uint8_t prover_confirm[spake2plus::kConfirmSize];
uint8_t prover_secret[spake2plus::kSecretSize];
if (!hs->pake_prover->ComputeConfirmation(
prover_confirm, prover_secret,
pake_msg_span.subspan(0, spake2plus::kShareSize),
pake_msg_span.subspan(spake2plus::kShareSize))) {
// Record a failure before releasing the answer to the client.
hs->credential->ClaimPAKEAttempt();
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
*out_alert = SSL_AD_ILLEGAL_PARAMETER;
return false;
}
Array<uint8_t> secret;
if (!secret.CopyFrom(prover_secret)) {
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
*out_alert = SSL_AD_INTERNAL_ERROR;
return false;
}
*out_secret = std::move(secret);
return true;
}
static bool ext_pake_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
CBS *contents) {
if (contents == nullptr) {
return true;
}
// struct {
// opaque client_identity<0..2^16-1>;
// opaque server_identity<0..2^16-1>;
// PAKEShare client_shares<0..2^16-1>;
// } PAKEClientHello;
//
// struct {
// NamedPAKE named_pake;
// opaque pake_message<1..2^16-1>;
// } PAKEShare;
*out_alert = SSL_AD_DECODE_ERROR;
CBS client_identity, server_identity, shares;
if (!CBS_get_u16_length_prefixed(contents, &client_identity) ||
!CBS_get_u16_length_prefixed(contents, &server_identity) ||
!CBS_get_u16_length_prefixed(contents, &shares) ||
CBS_len(contents) != 0) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
return false;
}
uint16_t last_named_pake = 0;
for (size_t i = 0; CBS_len(&shares) > 0; i++) {
uint16_t pake_id;
CBS message;
if (!CBS_get_u16(&shares, &pake_id) ||
!CBS_get_u16_length_prefixed(&shares, &message)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
return false;
}
// PAKEs must be sent in strictly monotonic order.
if (i > 0 && last_named_pake >= pake_id) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
return false;
}
last_named_pake = pake_id;
// We only support one PAKE.
if (pake_id != SSL_PAKE_SPAKE2PLUSV1) {
continue;
}
// Save the PAKE share for the handshake logic to pick up later.
// TODO(crbug.com/391393404): It would be nice if the callback did not have
// to copy this.
hs->pake_share = MakeUnique<SSLPAKEShare>();
if (hs->pake_share == nullptr ||
!hs->pake_share->client_identity.CopyFrom(client_identity) ||
!hs->pake_share->server_identity.CopyFrom(server_identity) ||
!hs->pake_share->pake_message.CopyFrom(message)) {
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
return false;
}
hs->pake_share->named_pake = pake_id;
}
return true;
}
// Application-level Protocol Settings
//
// https://tools.ietf.org/html/draft-vvv-tls-alps-01
@@ -3221,6 +3486,15 @@ static const struct tls_extension kExtensions[] = {
ext_certificate_authorities_parse_clienthello,
dont_add_serverhello,
},
{
TLSEXT_TYPE_pake,
ext_pake_add_clienthello,
// This extension is unencrypted and so adding and parsing it from the
// ServerHello is handled elsewhere.
forbid_parse_serverhello,
ext_pake_parse_clienthello,
dont_add_serverhello,
},
};
#define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
+17
View File
@@ -332,6 +332,7 @@ void ssl_done_writing_client_hello(SSL_HANDSHAKE *hs) {
hs->ech_client_outer.Reset();
hs->cookie.Reset();
hs->key_share_bytes.Reset();
hs->pake_share_bytes.Reset();
}
static enum ssl_hs_wait_t do_start_connect(SSL_HANDSHAKE *hs) {
@@ -363,6 +364,10 @@ static enum ssl_hs_wait_t do_start_connect(SSL_HANDSHAKE *hs) {
hs->max_version >= TLS1_2_VERSION ? TLS1_2_VERSION : hs->max_version;
}
if (!ssl_setup_pake_shares(hs)) {
return ssl_hs_error;
}
// If the configured session has expired or is not usable, drop it. We also do
// not offer sessions on renegotiation.
SSLSessionType session_type = SSLSessionType::kNotResumable;
@@ -379,6 +384,10 @@ static enum ssl_hs_wait_t do_start_connect(SSL_HANDSHAKE *hs) {
// Don't offer TLS 1.2 tickets if disabled.
(session_type == SSLSessionType::kTicket &&
(SSL_get_options(ssl) & SSL_OP_NO_TICKET)) ||
// Don't offer sessions and PAKEs at the same time. We do not currently
// support resumption with PAKEs. (Offering both together would need
// more logic to conditionally send the key_share extension.)
hs->pake_prover != nullptr ||
!ssl_session_is_time_valid(ssl, ssl->session.get()) ||
SSL_is_quic(ssl) != int{ssl->session->is_quic} ||
ssl->s3->initial_handshake_complete) {
@@ -642,6 +651,14 @@ static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) {
return ssl_hs_ok;
}
// If this client is configured to use a PAKE, then the server must support
// TLS 1.3.
if (hs->pake_prover) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_PROTOCOL_VERSION);
return ssl_hs_error;
}
// Clear some TLS 1.3 state that no longer needs to be retained.
hs->key_shares[0].reset();
hs->key_shares[1].reset();
+54
View File
@@ -17,6 +17,7 @@
#include <stdlib.h>
#include <algorithm>
#include <atomic>
#include <bitset>
#include <initializer_list>
#include <limits>
@@ -38,6 +39,7 @@
#include "../crypto/err/internal.h"
#include "../crypto/internal.h"
#include "../crypto/lhash/internal.h"
#include "../crypto/spake2plus/internal.h"
#if defined(OPENSSL_WINDOWS)
@@ -1818,6 +1820,8 @@ bool ssl_encrypt_client_hello(SSL_HANDSHAKE *hs, Span<const uint8_t> enc);
enum class SSLCredentialType {
kX509,
kDelegated,
kSPAKE2PlusV1Client,
kSPAKE2PlusV1Server,
};
BSSL_NAMESPACE_END
@@ -1912,6 +1916,27 @@ struct ssl_credential_st : public bssl::RefCounted<ssl_credential_st> {
// OCSP response to be sent to the client, if requested.
bssl::UniquePtr<CRYPTO_BUFFER> ocsp_response;
// SPAKE2+-specific information.
bssl::Array<uint8_t> pake_context;
bssl::Array<uint8_t> client_identity;
bssl::Array<uint8_t> server_identity;
bssl::Array<uint8_t> password_verifier_w0;
bssl::Array<uint8_t> password_verifier_w1; // server-only
bssl::Array<uint8_t> registration_record; // client-only
mutable std::atomic<uint32_t> pake_limit;
// Checks whether there are still permitted PAKE attempts remaining, without
// changing the counter.
bool HasPAKEAttempts() const;
// Atomically decrement |pake_limit|. Return true if successful and false if
// |pake_limit| is already zero.
bool ClaimPAKEAttempt() const;
// Atomically increment |pake_limit|. This must be paired with a
// |ClaimPAKEAttempt| call.
void RestorePAKEAttempt() const;
CRYPTO_EX_DATA ex_data;
// must_match_issuer is a flag indicating that this credential should be
@@ -2062,6 +2087,14 @@ struct SSL_HANDSHAKE_HINTS {
bool ignore_ticket = false;
};
struct SSLPAKEShare {
static constexpr bool kAllowUniquePtr = true;
uint16_t named_pake;
Array<uint8_t> client_identity;
Array<uint8_t> server_identity;
Array<uint8_t> pake_message;
};
struct SSL_HANDSHAKE {
explicit SSL_HANDSHAKE(SSL *ssl);
~SSL_HANDSHAKE();
@@ -2392,6 +2425,18 @@ struct SSL_HANDSHAKE {
// grease_seed is the entropy for GREASE values.
uint8_t grease_seed[ssl_grease_last_index + 1] = {0};
// pake_share is the PAKE message received over the wire, if any.
UniquePtr<SSLPAKEShare> pake_share;
// pake_share_bytes are the bytes of the PAKEShare to send, if any.
Array<uint8_t> pake_share_bytes;
// pake_prover is the PAKE context for a client.
UniquePtr<spake2plus::Prover> pake_prover;
// pake_verifier is the PAKE context for a server.
UniquePtr<spake2plus::Verifier> pake_verifier;
};
// kMaxTickets is the maximum number of tickets to send immediately after the
@@ -2464,6 +2509,10 @@ bool ssl_setup_extension_permutation(SSL_HANDSHAKE *hs);
// a single key share of the specified group.
bool ssl_setup_key_shares(SSL_HANDSHAKE *hs, uint16_t override_group_id);
// ssl_setup_pake_shares computes the client PAKE shares and saves them in |hs|.
// It returns true on success and false on failure.
bool ssl_setup_pake_shares(SSL_HANDSHAKE *hs);
bool ssl_ext_key_share_parse_serverhello(SSL_HANDSHAKE *hs,
Array<uint8_t> *out_secret,
uint8_t *out_alert, CBS *contents);
@@ -2471,8 +2520,13 @@ bool ssl_ext_key_share_parse_clienthello(SSL_HANDSHAKE *hs, bool *out_found,
Span<const uint8_t> *out_peer_key,
uint8_t *out_alert,
const SSL_CLIENT_HELLO *client_hello);
bool ssl_ext_pake_add_serverhello(SSL_HANDSHAKE *hs, CBB *out);
bool ssl_ext_key_share_add_serverhello(SSL_HANDSHAKE *hs, CBB *out);
bool ssl_ext_pake_parse_serverhello(SSL_HANDSHAKE *hs,
Array<uint8_t> *out_secret,
uint8_t *out_alert, CBS *contents);
bool ssl_ext_pre_shared_key_parse_serverhello(SSL_HANDSHAKE *hs,
uint8_t *out_alert,
CBS *contents);
+134 -6
View File
@@ -19,6 +19,7 @@
#include <openssl/span.h>
#include "../crypto/internal.h"
#include "../crypto/spake2plus/internal.h"
#include "internal.h"
@@ -141,15 +142,27 @@ void ssl_credential_st::ClearCertAndKey() {
}
bool ssl_credential_st::UsesX509() const {
// Currently, all credential types use X.509. However, we may add other
// certificate types in the future. Add the checks in the setters now, so we
// don't forget.
return true;
switch (type) {
case SSLCredentialType::kX509:
case SSLCredentialType::kDelegated:
return true;
case SSLCredentialType::kSPAKE2PlusV1Client:
case SSLCredentialType::kSPAKE2PlusV1Server:
return false;
}
abort();
}
bool ssl_credential_st::UsesPrivateKey() const {
// Currently, all credential types use private keys. However, we may add PSK
return true;
switch (type) {
case SSLCredentialType::kX509:
case SSLCredentialType::kDelegated:
return true;
case SSLCredentialType::kSPAKE2PlusV1Client:
case SSLCredentialType::kSPAKE2PlusV1Server:
return false;
}
abort();
}
bool ssl_credential_st::IsComplete() const {
@@ -258,6 +271,30 @@ bool ssl_credential_st::ChainContainsIssuer(
return false;
}
bool ssl_credential_st::HasPAKEAttempts() const {
return pake_limit.load() != 0;
}
bool ssl_credential_st::ClaimPAKEAttempt() const {
uint32_t current = pake_limit.load(std::memory_order_relaxed);
for (;;) {
if (current == 0) {
return false;
}
if (pake_limit.compare_exchange_weak(current, current - 1)) {
break;
}
}
return true;
}
void ssl_credential_st::RestorePAKEAttempt() const {
// This should not overflow because it will only be paired with
// ClaimPAKEAttempt.
pake_limit.fetch_add(1);
}
bool ssl_credential_st::AppendIntermediateCert(UniquePtr<CRYPTO_BUFFER> cert) {
if (!UsesX509()) {
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
@@ -425,6 +462,97 @@ int SSL_CREDENTIAL_set1_signed_cert_timestamp_list(SSL_CREDENTIAL *cred,
return 1;
}
int SSL_spake2plusv1_register(uint8_t out_w0[32], uint8_t out_w1[32],
uint8_t out_registration_record[65],
const uint8_t *password, size_t password_len,
const uint8_t *client_identity,
size_t client_identity_len,
const uint8_t *server_identity,
size_t server_identity_len) {
return spake2plus::Register(
Span(out_w0, 32), Span(out_w1, 32), Span(out_registration_record, 65),
Span(password, password_len), Span(client_identity, client_identity_len),
Span(server_identity, server_identity_len));
}
static UniquePtr<SSL_CREDENTIAL> ssl_credential_new_spake2plusv1(
SSLCredentialType type, Span<const uint8_t> context,
Span<const uint8_t> client_identity, Span<const uint8_t> server_identity,
uint32_t limit) {
assert(type == SSLCredentialType::kSPAKE2PlusV1Client ||
type == SSLCredentialType::kSPAKE2PlusV1Server);
auto cred = MakeUnique<SSL_CREDENTIAL>(type);
if (cred == nullptr) {
return nullptr;
}
if (!cred->pake_context.CopyFrom(context) ||
!cred->client_identity.CopyFrom(client_identity) ||
!cred->server_identity.CopyFrom(server_identity)) {
return nullptr;
}
cred->pake_limit.store(limit);
return cred;
}
SSL_CREDENTIAL *SSL_CREDENTIAL_new_spake2plusv1_client(
const uint8_t *context, size_t context_len, const uint8_t *client_identity,
size_t client_identity_len, const uint8_t *server_identity,
size_t server_identity_len, uint32_t error_limit, const uint8_t *w0,
size_t w0_len, const uint8_t *w1, size_t w1_len) {
if (w0_len != spake2plus::kVerifierSize ||
w1_len != spake2plus::kVerifierSize ||
(context == nullptr && context_len != 0)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SPAKE2PLUSV1_VALUE);
return nullptr;
}
UniquePtr<SSL_CREDENTIAL> cred = ssl_credential_new_spake2plusv1(
SSLCredentialType::kSPAKE2PlusV1Client, Span(context, context_len),
Span(client_identity, client_identity_len),
Span(server_identity, server_identity_len), error_limit);
if (!cred) {
return nullptr;
}
if (!cred->password_verifier_w0.CopyFrom(Span(w0, w0_len)) ||
!cred->password_verifier_w1.CopyFrom(Span(w1, w1_len))) {
return nullptr;
}
return cred.release();
}
SSL_CREDENTIAL *SSL_CREDENTIAL_new_spake2plusv1_server(
const uint8_t *context, size_t context_len, const uint8_t *client_identity,
size_t client_identity_len, const uint8_t *server_identity,
size_t server_identity_len, uint32_t rate_limit, const uint8_t *w0,
size_t w0_len, const uint8_t *registration_record,
size_t registration_record_len) {
if (w0_len != spake2plus::kVerifierSize ||
registration_record_len != spake2plus::kRegistrationRecordSize ||
(context == nullptr && context_len != 0)) {
return nullptr;
}
UniquePtr<SSL_CREDENTIAL> cred = ssl_credential_new_spake2plusv1(
SSLCredentialType::kSPAKE2PlusV1Server, Span(context, context_len),
Span(client_identity, client_identity_len),
Span(server_identity, server_identity_len), rate_limit);
if (!cred) {
return nullptr;
}
if (!cred->password_verifier_w0.CopyFrom(Span(w0, w0_len)) ||
!cred->registration_record.CopyFrom(
Span(registration_record, registration_record_len))) {
return nullptr;
}
return cred.release();
}
int SSL_CTX_add1_credential(SSL_CTX *ctx, SSL_CREDENTIAL *cred) {
if (!cred->IsComplete()) {
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+291 -1
View File
@@ -3780,7 +3780,6 @@ TEST_P(SSLVersionTest, DefaultTicketKeyRotation) {
true /* changed */));
// Verify ticket resumption actually works.
bssl::UniquePtr<SSL> client, server;
bssl::UniquePtr<SSL_SESSION> session =
CreateClientSession(client_ctx_.get(), server_ctx_.get());
ASSERT_TRUE(session);
@@ -9857,5 +9856,296 @@ TEST(SSLTest, SetGetCompliancePolicy) {
}
}
class SSLPAKETest : public testing::Test {
public:
static Span<const uint8_t> pake_context() {
return StringAsBytes("test context");
}
static Span<const uint8_t> client_identity() {
return StringAsBytes("client");
}
static Span<const uint8_t> server_identity() {
return StringAsBytes("client");
}
static UniquePtr<SSL_CTX> NewClientContext(std::string_view password,
uint32_t attempts) {
auto reg = Register(password);
if (!reg) {
return nullptr;
}
UniquePtr<SSL_CREDENTIAL> cred(SSL_CREDENTIAL_new_spake2plusv1_client(
pake_context().data(), pake_context().size(), client_identity().data(),
client_identity().size(), server_identity().data(),
server_identity().size(), attempts, reg->pw_verifier_w0,
sizeof(reg->pw_verifier_w0), reg->pw_verifier_w1,
sizeof(reg->pw_verifier_w1)));
if (cred == nullptr) {
return nullptr;
}
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
if (ctx == nullptr || !SSL_CTX_add1_credential(ctx.get(), cred.get())) {
return nullptr;
}
return ctx;
}
static UniquePtr<SSL_CTX> NewServerContext(std::string_view password,
uint32_t attempts) {
auto reg = Register(password);
if (!reg) {
return nullptr;
}
UniquePtr<SSL_CREDENTIAL> cred(SSL_CREDENTIAL_new_spake2plusv1_server(
pake_context().data(), pake_context().size(), client_identity().data(),
client_identity().size(), server_identity().data(),
server_identity().size(), attempts, reg->pw_verifier_w0,
sizeof(reg->pw_verifier_w0), reg->registration_record,
sizeof(reg->registration_record)));
if (cred == nullptr) {
return nullptr;
}
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
if (ctx == nullptr || !SSL_CTX_add1_credential(ctx.get(), cred.get())) {
return nullptr;
}
return ctx;
}
private:
struct PAKERegistration {
uint8_t pw_verifier_w0[32];
uint8_t pw_verifier_w1[32];
uint8_t registration_record[65];
};
static std::optional<PAKERegistration> Register(std::string_view password) {
auto password_bytes = StringAsBytes(password);
PAKERegistration ret;
if (!SSL_spake2plusv1_register(
ret.pw_verifier_w0, ret.pw_verifier_w1, ret.registration_record,
password_bytes.data(), password_bytes.size(),
client_identity().data(), client_identity().size(),
server_identity().data(), server_identity().size())) {
return std::nullopt;
}
return ret;
}
};
TEST_F(SSLPAKETest, SPAKE2PLUS) {
UniquePtr<SSL_CTX> client_ctx = NewClientContext("password", 1);
ASSERT_TRUE(client_ctx);
UniquePtr<SSL_CTX> server_ctx = NewServerContext("password", 1);
ASSERT_TRUE(server_ctx);
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
server_ctx.get()));
}
TEST_F(SSLPAKETest, ClientLimit) {
static constexpr uint32_t kLimit = 5;
static constexpr uint32_t kUnlimited = UINT32_MAX;
UniquePtr<SSL_CTX> client_ctx = NewClientContext("password", kLimit);
ASSERT_TRUE(client_ctx);
UniquePtr<SSL_CTX> server_ctx_good = NewServerContext("password", kUnlimited);
ASSERT_TRUE(server_ctx_good);
UniquePtr<SSL_CTX> server_ctx_bad = NewServerContext("wrong", kUnlimited);
ASSERT_TRUE(server_ctx_bad);
// The client sees confirmV before revealing a password confirmation, so
// neither successful nor unfinished handshakes contribute to the limit.
bssl::UniquePtr<SSL> client, server;
for (uint32_t i = 0; i < kLimit * 2; i++) {
// Unfinished handshake.
ASSERT_TRUE(CreateClientAndServer(&client, &server, client_ctx.get(),
server_ctx_good.get()));
ASSERT_EQ(SSL_do_handshake(client.get()), -1); // Write ClientHello.
ASSERT_EQ(SSL_get_error(client.get(), -1), SSL_ERROR_WANT_READ);
// Successful handshake.
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
server_ctx_good.get()));
}
// After kLimit - 1 password mismatches, the credential still functions.
for (uint32_t i = 0; i < kLimit - 1; i++) {
ASSERT_FALSE(ConnectClientAndServer(&client, &server, client_ctx.get(),
server_ctx_bad.get()));
}
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
server_ctx_good.get()));
// But after one more password mismatch...
ASSERT_FALSE(ConnectClientAndServer(&client, &server, client_ctx.get(),
server_ctx_bad.get()));
// ...the client should refuse to use the credential at all.
ASSERT_FALSE(ConnectClientAndServer(&client, &server, client_ctx.get(),
server_ctx_good.get()));
ASSERT_TRUE(ErrorEquals(ERR_get_error(), ERR_LIB_SSL, SSL_R_PAKE_EXHAUSTED));
}
TEST_F(SSLPAKETest, ServerLimit) {
static constexpr uint32_t kLimit = 5;
static constexpr uint32_t kUnlimited = UINT32_MAX;
UniquePtr<SSL_CTX> server_ctx = NewServerContext("password", kLimit);
ASSERT_TRUE(server_ctx);
UniquePtr<SSL_CTX> client_ctx_good = NewClientContext("password", kUnlimited);
ASSERT_TRUE(client_ctx_good);
UniquePtr<SSL_CTX> client_ctx_bad = NewClientContext("wrong", kUnlimited);
ASSERT_TRUE(client_ctx_bad);
// Successful handshakes do not (indefinitely) contribute to the limit. If the
// server sees one good handshake at a time, the limit does not impact it.
bssl::UniquePtr<SSL> client, server;
for (uint32_t i = 0; i < kLimit * 2; i++) {
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_good.get(),
server_ctx.get()));
}
// The server sends confirmV before confirming the client knew the password,
// so any handshake in between ClientHello and ServerHello counts towards the
// limit.
struct ClientServerPair {
bssl::UniquePtr<SSL> client, server;
};
std::vector<ClientServerPair> pending;
auto handshake_up_to_serverhello = [](ClientServerPair *pair) {
// Send ClientHello.
ASSERT_EQ(SSL_do_handshake(pair->client.get()), -1);
ASSERT_EQ(SSL_get_error(pair->client.get(), -1), SSL_ERROR_WANT_READ);
// Send ServerHello..Finished.
ASSERT_EQ(SSL_do_handshake(pair->server.get()), -1);
ASSERT_EQ(SSL_get_error(pair->server.get(), -1), SSL_ERROR_WANT_READ);
};
// First, go just under the limit.
for (uint32_t i = 0; i < kLimit - 1; i++) {
ClientServerPair pair;
ASSERT_TRUE(CreateClientAndServer(&pair.client, &pair.server,
client_ctx_good.get(), server_ctx.get()));
ASSERT_NO_FATAL_FAILURE(handshake_up_to_serverhello(&pair));
pending.push_back(std::move(pair));
}
// The server can still complete a handshake.
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_good.get(),
server_ctx.get()));
// Start one more unfinished handshake.
ClientServerPair pair;
ASSERT_TRUE(CreateClientAndServer(&pair.client, &pair.server,
client_ctx_good.get(), server_ctx.get()));
ASSERT_NO_FATAL_FAILURE(handshake_up_to_serverhello(&pair));
pending.push_back(std::move(pair));
// The credential is at its limit.
ASSERT_FALSE(ConnectClientAndServer(&client, &server, client_ctx_good.get(),
server_ctx.get()));
ASSERT_TRUE(ErrorEquals(ERR_get_error(), ERR_LIB_SSL, SSL_R_PAKE_EXHAUSTED));
// Complete some of the handshakes. As they complete, the server learns that
// the client had the correct guess, so the connections no longer count
// towards the brute force limit.
static constexpr uint32_t kRemainingLimit = kLimit / 2;
for (uint32_t i = 0; i < kRemainingLimit; i++) {
ASSERT_TRUE(CompleteHandshakes(pending.back().client.get(),
pending.back().server.get()));
pending.pop_back();
}
// The server can complete a handshake now that some of the limit has been
// released.
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_good.get(),
server_ctx.get()));
// Failed handshakes consume the limit. First consume all but one of the newly
// released limit.
for (uint32_t i = 0; i < kRemainingLimit - 1; i++) {
ASSERT_FALSE(ConnectClientAndServer(&client, &server, client_ctx_bad.get(),
server_ctx.get()));
}
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_good.get(),
server_ctx.get()));
// Consume the last of the limit.
ASSERT_FALSE(ConnectClientAndServer(&client, &server, client_ctx_bad.get(),
server_ctx.get()));
// The credential is disabled again.
ASSERT_FALSE(ConnectClientAndServer(&client, &server, client_ctx_good.get(),
server_ctx.get()));
ASSERT_TRUE(ErrorEquals(ERR_get_error(), ERR_LIB_SSL, SSL_R_PAKE_EXHAUSTED));
// The unfinished handshakes continue to count toward the limit even if they
// are destroyed.
pending.clear();
ASSERT_FALSE(ConnectClientAndServer(&client, &server, client_ctx_good.get(),
server_ctx.get()));
ASSERT_TRUE(ErrorEquals(ERR_get_error(), ERR_LIB_SSL, SSL_R_PAKE_EXHAUSTED));
}
#if defined(OPENSSL_THREADS)
// The PAKE limit mechanism should be thread-safe.
TEST_F(SSLPAKETest, ClientThreads) {
static constexpr uint32_t kLimit = 5;
static constexpr uint32_t kUnlimited = UINT32_MAX;
static constexpr int kThreads = 10;
UniquePtr<SSL_CTX> client_ctx = NewClientContext("password", kLimit);
ASSERT_TRUE(client_ctx);
UniquePtr<SSL_CTX> server_ctx_good = NewServerContext("password", kUnlimited);
ASSERT_TRUE(server_ctx_good);
UniquePtr<SSL_CTX> server_ctx_bad = NewServerContext("wrong", kUnlimited);
ASSERT_TRUE(server_ctx_bad);
auto connect = [&](SSL_CTX *server_ctx) {
bssl::UniquePtr<SSL> client, server;
ConnectClientAndServer(&client, &server, client_ctx.get(), server_ctx);
};
std::vector<std::thread> threads;
for (int i = 0; i < kThreads; i++) {
threads.emplace_back([&] { connect(server_ctx_good.get()); });
threads.emplace_back([&] { connect(server_ctx_bad.get()); });
}
for (auto &thread : threads) {
thread.join();
}
}
TEST_F(SSLPAKETest, ServerThreads) {
static constexpr uint32_t kLimit = 5;
static constexpr uint32_t kUnlimited = UINT32_MAX;
static constexpr int kThreads = 10;
UniquePtr<SSL_CTX> server_ctx = NewServerContext("password", kLimit);
ASSERT_TRUE(server_ctx);
UniquePtr<SSL_CTX> client_ctx_good = NewClientContext("password", kUnlimited);
ASSERT_TRUE(client_ctx_good);
UniquePtr<SSL_CTX> client_ctx_bad = NewClientContext("wrong", kUnlimited);
ASSERT_TRUE(client_ctx_bad);
auto connect = [&](SSL_CTX *client_ctx) {
bssl::UniquePtr<SSL> client, server;
ConnectClientAndServer(&client, &server, client_ctx, server_ctx.get());
};
std::vector<std::thread> threads;
for (int i = 0; i < kThreads; i++) {
threads.emplace_back([&] { connect(client_ctx_good.get()); });
threads.emplace_back([&] { connect(client_ctx_bad.get()); });
}
for (auto &thread : threads) {
thread.join();
}
}
#endif // OPENSSL_THREADS
} // namespace
BSSL_NAMESPACE_END
+12 -1
View File
@@ -425,6 +425,12 @@ static bool CheckAuthProperties(SSL *ssl, bool is_resume,
return true;
}
static bool IsPAKE(const SSL *ssl) {
int idx = GetTestState(ssl)->selected_credential;
return idx >= 0 && GetTestConfig(ssl)->credentials[idx].type ==
CredentialConfigType::kSPAKE2PlusV1;
}
// CheckHandshakeProperties checks, immediately after |ssl| completes its
// initial handshake (or False Starts), whether all the properties are
// consistent with the test configuration and invariants.
@@ -660,6 +666,11 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume,
fprintf(stderr, "Received peer certificate on a PSK cipher.\n");
return false;
}
} else if (IsPAKE(ssl)) {
if (SSL_get_peer_cert_chain(ssl) != nullptr) {
fprintf(stderr, "Received peer certificate on a PAKE handshake.\n");
return false;
}
} else if (!config->is_server || config->require_any_client_certificate) {
if (SSL_get_peer_cert_chain(ssl) == nullptr) {
fprintf(stderr, "Received no peer certificate but expected one.\n");
@@ -1257,7 +1268,7 @@ static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session,
if (GetProtocolVersion(ssl) >= TLS1_3_VERSION && !config->is_server) {
bool expect_new_session =
!config->expect_no_session && !config->shim_shuts_down;
!config->expect_no_session && !config->shim_shuts_down && !IsPAKE(ssl);
if (expect_new_session != test_state->got_new_session) {
fprintf(stderr,
"new session was%s cached, but we expected the opposite\n",
+52 -1
View File
@@ -133,6 +133,7 @@ const (
extensionRenegotiationInfo uint16 = 0xff01
extensionQUICTransportParamsLegacy uint16 = 0xffa5 // draft-ietf-quic-tls-32 and earlier
extensionChannelID uint16 = 30032 // not IANA assigned
extensionPAKE uint16 = 35387 // not IANA assigned
extensionDuplicate uint16 = 0xffff // not IANA assigned
extensionEncryptedClientHello uint16 = 0xfe0d // not IANA assigned
extensionECHOuterExtensions uint16 = 0xfd00 // not IANA assigned
@@ -264,6 +265,9 @@ const (
// draft-ietf-tls-esni-13, sections 7.2 and 7.2.1.
const echAcceptConfirmationLength = 8
// Temporary value; pre RFC.
const spakeID uint16 = 0x7d96
// ConnectionState records basic TLS details about the connection.
type ConnectionState struct {
Version uint16 // TLS version used by the connection (e.g. VersionTLS12)
@@ -1711,7 +1715,8 @@ type ProtocolBugs struct {
SecondHelloRetryRequest bool
// SendHelloRetryRequestCurve, if non-zero, causes the server to send
// the specified curve in a HelloRetryRequest.
// the specified curve in a HelloRetryRequest, even if the client did
// not offer key shares at all.
SendHelloRetryRequestCurve CurveID
// SendHelloRetryRequestCipherSuite, if non-zero, causes the server to send
@@ -1778,6 +1783,12 @@ type ProtocolBugs struct {
// TLS 1.3, even in handshakes where it is not allowed, such as resumption.
AlwaysSendCertificate bool
// UseCertificateCredential, if not nil, is the credential to use as a
// server for TLS 1.3 Certificate and CertificateVerify messages. This may
// be used with AlwaysSendCertificate to authenticate with a certificate
// alongside some non-certificate credential.
UseCertificateCredential *Credential
// SendSNIWarningAlert, if true, causes the server to send an
// unrecognized_name alert before the ServerHello.
SendSNIWarningAlert bool
@@ -2023,6 +2034,32 @@ type ProtocolBugs struct {
// AllowEpochOverflow allows DTLS epoch numbers to wrap around.
AllowEpochOverflow bool
// SendPAKEInHelloRetryRequest causes the server to send a HelloRetryRequest
// message containing a PAKE extension.
SendPAKEInHelloRetryRequest bool
// UnsolicitedPAKE, if non-zero, causes a ServerHello to contain a PAKE
// response of the specified algorithm, even if the client didn't request it.
UnsolicitedPAKE uint16
// OfferExtraPAKEs, if not empty, is a list of additional PAKE algorithms to
// offer as a client. They cannot be negotiated and should be used in tests
// where the server is expected to ignore them.
OfferExtraPAKEs []uint16
// OfferExtraPAKEClientID and OfferExtraPAKEServerID are the PAKE client and
// server IDs to send with OfferExtraPAKEs. These may be left unset if
// configured with a real PAKE credential.
OfferExtraPAKEClientID []byte
OfferExtraPAKEServerID []byte
// TruncatePAKEMessage, if true, causes PAKE messages to be truncated.
TruncatePAKEMessage bool
// CheckClientHello is called on the initial ClientHello received from the
// peer, to implement extra checks.
CheckClientHello func(*clientHelloMsg) error
}
func (c *Config) serverInit() {
@@ -2194,6 +2231,7 @@ type CredentialType int
const (
CredentialTypeX509 CredentialType = iota
CredentialTypeDelegated
CredentialTypeSPAKE2PlusV1
)
// A Credential is a certificate chain and private key that a TLS endpoint may
@@ -2233,6 +2271,19 @@ type Credential struct {
// SignSignatureAlgorithms, if not nil, overrides the default set of
// supported signature algorithms to sign with.
SignSignatureAlgorithms []signatureAlgorithm
// The following fields are used for PAKE credentials. For simplicity,
// we specify the password directly and expect the shim and runner to
// compute the client- and server-specific halves as needed.
PAKEContext []byte
PAKEClientID []byte
PAKEServerID []byte
PAKEPassword []byte
// WrongPAKERole, if set, causes the shim to be configured with a
// credential of the wrong role.
WrongPAKERole bool
// OverridePAKECodepoint, if non-zero, causes the runner to send the
// specified value instead of the actual PAKE codepoint.
OverridePAKECodepoint uint16
}
func (c *Credential) WithSignatureAlgorithms(sigAlgs ...signatureAlgorithm) *Credential {
+85 -20
View File
@@ -22,6 +22,7 @@ import (
"time"
"boringssl.googlesource.com/boringssl/ssl/test/runner/hpke"
"boringssl.googlesource.com/boringssl/ssl/test/runner/spake2plus"
"golang.org/x/crypto/cryptobyte"
)
@@ -40,6 +41,7 @@ type clientHandshakeState struct {
session *ClientSessionState
finishedBytes []byte
peerPublicKey crypto.PublicKey
pakeContext *spake2plus.Context
}
func mapClientHelloVersion(vers uint16, isDTLS bool) uint16 {
@@ -673,6 +675,41 @@ func (hs *clientHandshakeState) createClientHello(innerHello *clientHelloMsg, ec
}
}
for _, id := range c.config.Bugs.OfferExtraPAKEs {
hello.pakeClientID = c.config.Bugs.OfferExtraPAKEClientID
hello.pakeServerID = c.config.Bugs.OfferExtraPAKEServerID
hello.pakeShares = append(hello.pakeShares, pakeShare{id: id, msg: []byte{1}})
}
if cred := c.config.Credential; cred != nil && cred.Type == CredentialTypeSPAKE2PlusV1 {
if maxVersion < VersionTLS13 {
panic("The PAKE extension is only supported in TLS 1.3")
}
w0, w1, _, err := spake2plus.Register(cred.PAKEPassword, cred.PAKEClientID, cred.PAKEServerID)
if err != nil {
return nil, err
}
hs.pakeContext, err = spake2plus.NewProver(cred.PAKEContext, cred.PAKEClientID, cred.PAKEServerID, w0, w1)
if err != nil {
return nil, err
}
share, err := hs.pakeContext.GenerateProverShare()
if err != nil {
return nil, err
}
if c.config.Bugs.TruncatePAKEMessage {
share = share[:len(share)-1]
}
hello.pakeClientID = cred.PAKEClientID
hello.pakeServerID = cred.PAKEServerID
id := spakeID
if cred.OverridePAKECodepoint != 0 {
id = cred.OverridePAKECodepoint
}
hello.pakeShares = append(hello.pakeShares, pakeShare{id: id, msg: share})
hello.hasKeyShares = false
hello.keyShares = nil
}
possibleCipherSuites := c.config.cipherSuites()
hello.cipherSuites = make([]uint16, 0, len(possibleCipherSuites))
@@ -1086,8 +1123,9 @@ func (hs *clientHandshakeState) doTLS13Handshake(msg any) error {
return fmt.Errorf("tls: server sent non-matching cipher suite %04x vs %04x", hs.suite.id, hs.serverHello.cipherSuite)
}
// ServerHello must be consistent with HelloRetryRequest, if any.
if haveHelloRetryRequest {
if helloRetryRequest.hasSelectedGroup && helloRetryRequest.selectedGroup != hs.serverHello.keyShare.group {
if helloRetryRequest.hasSelectedGroup && (!hs.serverHello.hasKeyShare || helloRetryRequest.selectedGroup != hs.serverHello.keyShare.group) {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: ServerHello parameters did not match HelloRetryRequest")
}
@@ -1123,29 +1161,55 @@ func (hs *clientHandshakeState) doTLS13Handshake(msg any) error {
}
hs.finishedHash.addEntropy(pskSecret)
if !hs.serverHello.hasKeyShare {
c.sendAlert(alertUnsupportedExtension)
return errors.New("tls: server omitted KeyShare on resumption.")
}
// Resolve ECDHE and compute the handshake secret.
ecdheSecret := zeroSecret
if !c.config.Bugs.MissingKeyShare && !c.config.Bugs.SecondClientHelloMissingKeyShare {
kem, ok := hs.keyShares[hs.serverHello.keyShare.group]
if !ok {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: server selected an unsupported group")
sharedSecret := zeroSecret
if len(hs.serverHello.pakeMessage) != 0 {
if c.didResume {
return errors.New("server resumed and returned a PAKE extension")
}
if hs.pakeContext == nil {
return errors.New("server selected a PAKE unexpectedly")
}
if hs.serverHello.pakeID != spakeID {
return errors.New("server selected an unknown PAKE")
}
if expected := 65 + 32; len(hs.serverHello.pakeMessage) != expected {
return fmt.Errorf("wrong length SPAKE2+ message, got %d, want %d", len(hs.serverHello.pakeMessage), expected)
}
if hs.serverHello.hasKeyShare || hs.serverHello.hasPSKIdentity {
return errors.New("server included invalid extension with PAKE extension")
}
c.curveID = hs.serverHello.keyShare.group
var err error
ecdheSecret, err = kem.decap(c.config, hs.serverHello.keyShare.keyExchange)
if err != nil {
return err
if _, sharedSecret, err = hs.pakeContext.ComputeProverConfirmation(hs.serverHello.pakeMessage[:65], hs.serverHello.pakeMessage[65:]); err != nil {
return fmt.Errorf("while computing SPAKE2+ confirmation: %w", err)
}
} else if hs.pakeContext != nil {
return errors.New("server didn't respond with PAKE message")
} else {
if !hs.serverHello.hasKeyShare {
c.sendAlert(alertUnsupportedExtension)
return errors.New("tls: server omitted KeyShare on resumption.")
}
// Resolve ECDHE and compute the handshake secret.
if !c.config.Bugs.MissingKeyShare && !c.config.Bugs.SecondClientHelloMissingKeyShare {
kem, ok := hs.keyShares[hs.serverHello.keyShare.group]
if !ok {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: server selected an unsupported group")
}
c.curveID = hs.serverHello.keyShare.group
var err error
sharedSecret, err = kem.decap(c.config, hs.serverHello.keyShare.keyExchange)
if err != nil {
return err
}
}
}
hs.finishedHash.nextSecret()
hs.finishedHash.addEntropy(ecdheSecret)
hs.finishedHash.addEntropy(sharedSecret)
hs.writeServerHash(hs.serverHello.marshal())
// Derive handshake traffic keys and switch read key to handshake
@@ -1178,6 +1242,8 @@ func (hs *clientHandshakeState) doTLS13Handshake(msg any) error {
c.peerCertificates = hs.session.serverCertificates
c.sctList = hs.session.sctList
c.ocspResponse = hs.session.ocspResponse
} else if hs.pakeContext != nil {
// The PAKE authenticates the connection.
} else {
msg, err := c.readHandshake()
if err != nil {
@@ -1867,8 +1933,7 @@ func (hs *clientHandshakeState) verifyCertificates(certMsg *certificateMsg) erro
func (hs *clientHandshakeState) establishKeys() error {
c := hs.c
clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
keysFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.hello.random, hs.serverHello.random, hs.suite.macLen, hs.suite.keyLen, hs.suite.ivLen(c.vers))
clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV := keysFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.hello.random, hs.serverHello.random, hs.suite.macLen, hs.suite.keyLen, hs.suite.ivLen(c.vers))
var clientCipher, serverCipher any
var clientHash, serverHash macFunction
if hs.suite.cipher != nil {
+72 -8
View File
@@ -145,6 +145,11 @@ type echClientOuter struct {
payload []byte
}
type pakeShare struct {
id uint16
msg []byte
}
type clientHelloMsg struct {
raw []byte
isDTLS bool
@@ -197,6 +202,9 @@ type clientHelloMsg struct {
delegatedCredential []signatureAlgorithm
alpsProtocols []string
alpsProtocolsOld []string
pakeClientID []byte
pakeServerID []byte
pakeShares []pakeShare
outerExtensions []uint16
reorderOuterExtensionsWithoutCompressing bool
prefixExtensions []uint16
@@ -537,7 +545,21 @@ func (m *clientHelloMsg) marshalBody(hello *cryptobyte.Builder, typ clientHelloT
body: body.BytesOrPanic(),
})
}
if len(m.pakeShares) > 0 {
body := cryptobyte.NewBuilder(nil)
addUint16LengthPrefixedBytes(body, m.pakeClientID)
addUint16LengthPrefixedBytes(body, m.pakeServerID)
body.AddUint16LengthPrefixed(func(shares *cryptobyte.Builder) {
for _, share := range m.pakeShares {
shares.AddUint16(share.id)
addUint16LengthPrefixedBytes(shares, share.msg)
}
})
extensions = append(extensions, extension{
id: extensionPAKE,
body: body.BytesOrPanic(),
})
}
// The PSK extension must be last. See https://tools.ietf.org/html/rfc8446#section-4.2.11
if len(m.pskIdentities) > 0 {
pskExtension := cryptobyte.NewBuilder(nil)
@@ -759,6 +781,9 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
m.delegatedCredential = nil
m.alpsProtocols = nil
m.alpsProtocolsOld = nil
m.pakeClientID = nil
m.pakeServerID = nil
m.pakeShares = nil
if len(reader) == 0 {
// ClientHello is optionally followed by extension data
@@ -1057,6 +1082,25 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
}
m.alpsProtocolsOld = append(m.alpsProtocolsOld, string(protocol))
}
case extensionPAKE:
var clientId, serverId, shares cryptobyte.String
if !body.ReadUint16LengthPrefixed(&clientId) ||
!body.ReadUint16LengthPrefixed(&serverId) ||
!body.ReadUint16LengthPrefixed(&shares) ||
len(body) != 0 {
return false
}
for len(shares) > 0 {
var id uint16
var msg cryptobyte.String
if !shares.ReadUint16(&id) ||
!shares.ReadUint16LengthPrefixed(&msg) {
return false
}
m.pakeClientID = []byte(clientId)
m.pakeServerID = []byte(serverId)
m.pakeShares = append(m.pakeShares, pakeShare{id: id, msg: msg})
}
}
if isGREASEValue(extension) {
@@ -1209,6 +1253,8 @@ type serverHelloMsg struct {
omitExtensions bool
emptyExtensions bool
extensions serverExtensions
pakeID uint16
pakeMessage []byte
}
func (m *serverHelloMsg) marshal() []byte {
@@ -1266,6 +1312,13 @@ func (m *serverHelloMsg) marshal() []byte {
extensions.AddUint16(m.vers)
}
}
if len(m.pakeMessage) != 0 {
extensions.AddUint16(extensionPAKE)
extensions.AddUint16LengthPrefixed(func(share *cryptobyte.Builder) {
share.AddUint16(m.pakeID)
addUint16LengthPrefixedBytes(share, m.pakeMessage)
})
}
if len(m.customExtension) > 0 {
extensions.AddUint16(extensionCustom)
addUint16LengthPrefixedBytes(extensions, []byte(m.customExtension))
@@ -1376,6 +1429,10 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool {
m.hasPSKIdentity = true
case extensionSupportedVersions:
// Parsed above.
case extensionPAKE:
if !body.ReadUint16(&m.pakeID) || !readUint16LengthPrefixedBytes(&body, &m.pakeMessage) {
return false
}
default:
// Only allow the 3 extensions that are sent in
// the clear in TLS 1.3.
@@ -1630,11 +1687,11 @@ func (m *serverExtensions) unmarshal(data cryptobyte.String, version uint16) boo
return false
}
case extensionALPN:
var protocols, protocol cryptobyte.String
if !body.ReadUint16LengthPrefixed(&protocols) ||
var pakes, protocol cryptobyte.String
if !body.ReadUint16LengthPrefixed(&pakes) ||
len(body) != 0 ||
!protocols.ReadUint8LengthPrefixed(&protocol) ||
len(protocols) != 0 {
!pakes.ReadUint8LengthPrefixed(&protocol) ||
len(pakes) != 0 {
return false
}
m.alpnProtocol = string(protocol)
@@ -1815,6 +1872,8 @@ type helloRetryRequestMsg struct {
echConfirmation []byte
echConfirmationOffset int
duplicateExtensions bool
pakeID uint16
pakeMessage []byte
}
func (m *helloRetryRequestMsg) marshal() []byte {
@@ -1850,6 +1909,13 @@ func (m *helloRetryRequestMsg) marshal() []byte {
extensions.AddUint16(2) // length
extensions.AddUint16(uint16(m.selectedGroup))
}
if len(m.pakeMessage) != 0 {
extensions.AddUint16(extensionPAKE)
extensions.AddUint16LengthPrefixed(func(share *cryptobyte.Builder) {
share.AddUint16(m.pakeID)
addUint16LengthPrefixedBytes(share, m.pakeMessage)
})
}
// m.cookie may be a non-nil empty slice for empty cookie tests.
if m.cookie != nil {
extensions.AddUint16(extensionCookie)
@@ -2000,7 +2066,6 @@ func (m *certificateMsg) marshal() (x []byte) {
}
}
})
})
m.raw = certMsg.BytesOrPanic()
@@ -2708,8 +2773,7 @@ func (m *channelIDMsg) unmarshal(data []byte) bool {
return true
}
type helloRequestMsg struct {
}
type helloRequestMsg struct{}
func (*helloRequestMsg) marshal() []byte {
return []byte{typeHelloRequest, 0, 0, 0}
+76 -15
View File
@@ -21,6 +21,7 @@ import (
"time"
"boringssl.googlesource.com/boringssl/ssl/test/runner/hpke"
"boringssl.googlesource.com/boringssl/ssl/test/runner/spake2plus"
"golang.org/x/crypto/cryptobyte"
)
@@ -171,6 +172,11 @@ func (hs *serverHandshakeState) readClientHello() error {
if err != nil {
return err
}
if config.Bugs.CheckClientHello != nil {
if err = config.Bugs.CheckClientHello(hs.clientHello); err != nil {
return err
}
}
if size := config.Bugs.RequireClientHelloSize; size != 0 && len(hs.clientHello.raw) != size {
return fmt.Errorf("tls: ClientHello record size is %d, but expected %d", len(hs.clientHello.raw), size)
}
@@ -672,7 +678,8 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
return errors.New("tls: early data extension received in DTLS")
}
hs.hello.hasKeyShare = true
// Decide whether to use key_share.
hs.hello.hasKeyShare = config.Credential.Type != CredentialTypeSPAKE2PlusV1 && config.Bugs.UnsolicitedPAKE == 0
if hs.sessionState != nil && config.Bugs.NegotiatePSKResumption {
hs.hello.hasKeyShare = false
}
@@ -680,7 +687,8 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
hs.hello.hasKeyShare = false
}
var sendHelloRetryRequest bool
// Decide whether a HelloRetryRequest is needed.
sendHelloRetryRequest := config.Bugs.AlwaysSendHelloRetryRequest
cipherSuite := hs.suite.id
if config.Bugs.SendHelloRetryRequestCipherSuite != 0 {
cipherSuite = config.Bugs.SendHelloRetryRequestCipherSuite
@@ -694,10 +702,6 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
duplicateExtensions: config.Bugs.DuplicateHelloRetryRequestExtensions,
}
if config.Bugs.AlwaysSendHelloRetryRequest {
sendHelloRetryRequest = true
}
if config.Bugs.SendHelloRetryRequestCookie != nil {
sendHelloRetryRequest = true
helloRetryRequest.cookie = config.Bugs.SendHelloRetryRequestCookie
@@ -755,6 +759,12 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
sendHelloRetryRequest = false
}
if config.Bugs.SendPAKEInHelloRetryRequest {
helloRetryRequest.pakeID = spakeID
helloRetryRequest.pakeMessage = []byte{1}
sendHelloRetryRequest = true
}
if sendHelloRetryRequest {
hs.finishedHash.UpdateForHelloRetryRequest()
@@ -1015,6 +1025,51 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
keyExchange: ciphertext,
}
}
} else if hs.cert.Type == CredentialTypeSPAKE2PlusV1 {
if len(hs.clientHello.pakeShares) == 0 {
return errors.New("tls: client not configured with PAKE")
}
if !bytes.Equal(hs.clientHello.pakeClientID, hs.cert.PAKEClientID) ||
!bytes.Equal(hs.clientHello.pakeServerID, hs.cert.PAKEServerID) {
return fmt.Errorf("tls: client configured with different PAKE identities: got (%x, %x), wanted (%x, %x)", hs.clientHello.pakeClientID, hs.clientHello.pakeServerID, hs.cert.PAKEClientID, hs.cert.PAKEServerID)
}
var pakeMessage []byte
for _, pake := range hs.clientHello.pakeShares {
if pake.id == spakeID {
pakeMessage = pake.msg
}
}
if pakeMessage == nil {
return errors.New("tls: client does not support SPAKE2+")
}
w0, _, registrationRecord, err := spake2plus.Register(hs.cert.PAKEPassword, hs.cert.PAKEClientID, hs.cert.PAKEServerID)
if err != nil {
return err
}
pake, err := spake2plus.NewVerifier(hs.cert.PAKEContext, hs.cert.PAKEClientID, hs.cert.PAKEServerID, w0, registrationRecord)
if err != nil {
return err
}
share, confirm, sharedSecret, err := pake.ProcessProverShare(pakeMessage)
if err != nil {
c.sendAlert(alertHandshakeFailure)
return fmt.Errorf("while processing SPAKE2+ prover share: %w", err)
}
hs.finishedHash.nextSecret()
hs.finishedHash.addEntropy(sharedSecret)
hs.hello.pakeID = spakeID
if hs.cert.OverridePAKECodepoint != 0 {
hs.hello.pakeID = hs.cert.OverridePAKECodepoint
}
hs.hello.pakeMessage = slices.Concat(share, confirm)
if c.config.Bugs.TruncatePAKEMessage {
hs.hello.pakeMessage = hs.hello.pakeMessage[:len(hs.hello.pakeMessage)-1]
}
} else if config.Bugs.UnsolicitedPAKE != 0 {
hs.finishedHash.nextSecret()
hs.finishedHash.addEntropy(hs.finishedHash.zeroSecret())
hs.hello.pakeID = config.Bugs.UnsolicitedPAKE
hs.hello.pakeMessage = []byte{1}
} else {
hs.finishedHash.nextSecret()
hs.finishedHash.addEntropy(hs.finishedHash.zeroSecret())
@@ -1068,7 +1123,10 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
c.writeRecord(recordTypeHandshake, encryptedExtensions.marshal())
}
requestClientCert := config.ClientAuth >= RequestClientCert && (hs.sessionState == nil || config.Bugs.AlwaysSendCertificateRequest)
var requestClientCert bool
if (hs.sessionState == nil && hs.cert.Type != CredentialTypeSPAKE2PlusV1) || config.Bugs.AlwaysSendCertificateRequest {
requestClientCert = config.ClientAuth >= RequestClientCert
}
if requestClientCert {
// Request a client certificate
certReq := &certificateRequestMsg{
@@ -1094,21 +1152,25 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
c.writeRecord(recordTypeHandshake, certReq.marshal())
}
if hs.sessionState == nil || config.Bugs.AlwaysSendCertificate {
if (hs.sessionState == nil && hs.cert.Type != CredentialTypeSPAKE2PlusV1) || config.Bugs.AlwaysSendCertificate {
useCert := hs.cert
if config.Bugs.UseCertificateCredential != nil {
useCert = config.Bugs.UseCertificateCredential
}
certMsg := &certificateMsg{
hasRequestContext: true,
}
if !config.Bugs.EmptyCertificateList {
for i, certData := range hs.cert.Certificate {
for i, certData := range useCert.Certificate {
cert := certificateEntry{
data: certData,
}
if i == 0 {
if hs.clientHello.ocspStapling && !c.config.Bugs.NoOCSPStapling {
cert.ocspResponse = hs.cert.OCSPStaple
cert.ocspResponse = useCert.OCSPStaple
}
if hs.clientHello.sctListSupported && !c.config.Bugs.NoSignedCertificateTimestamps {
cert.sctList = hs.cert.SignedCertificateTimestampList
cert.sctList = useCert.SignedCertificateTimestampList
}
cert.duplicateExtensions = config.Bugs.SendDuplicateCertExtensions
cert.extraExtension = config.Bugs.SendExtensionOnCertificate
@@ -1172,13 +1234,13 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
// Determine the hash to sign.
var err error
certVerify.signatureAlgorithm, err = selectSignatureAlgorithm(c.isClient, c.vers, hs.cert, config, hs.clientHello.signatureAlgorithms)
certVerify.signatureAlgorithm, err = selectSignatureAlgorithm(c.isClient, c.vers, useCert, config, hs.clientHello.signatureAlgorithms)
if err != nil {
c.sendAlert(alertInternalError)
return err
}
privKey := hs.cert.PrivateKey
privKey := useCert.PrivateKey
input := hs.finishedHash.certificateVerifyInput(serverCertificateVerifyContextTLS13)
certVerify.signature, err = signMessage(c.isClient, c.vers, privKey, c.config, certVerify.signatureAlgorithm, input)
if err != nil {
@@ -2042,8 +2104,7 @@ func (hs *serverHandshakeState) doFullHandshake() error {
func (hs *serverHandshakeState) establishKeys() error {
c := hs.c
clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
keysFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.clientHello.random, hs.hello.random, hs.suite.macLen, hs.suite.keyLen, hs.suite.ivLen(c.vers))
clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV := keysFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.clientHello.random, hs.hello.random, hs.suite.macLen, hs.suite.keyLen, hs.suite.ivLen(c.vers))
var clientCipher, serverCipher any
var clientHash, serverHash macFunction
+546 -29
View File
@@ -205,13 +205,17 @@ func initKeys() {
var channelIDBytes []byte
var testOCSPResponse = []byte{1, 2, 3, 4}
var testOCSPResponse2 = []byte{5, 6, 7, 8}
var testSCTList = []byte{0, 6, 0, 4, 5, 6, 7, 8}
var testSCTList2 = []byte{0, 6, 0, 4, 1, 2, 3, 4}
var (
testOCSPResponse = []byte{1, 2, 3, 4}
testOCSPResponse2 = []byte{5, 6, 7, 8}
testSCTList = []byte{0, 6, 0, 4, 5, 6, 7, 8}
testSCTList2 = []byte{0, 6, 0, 4, 1, 2, 3, 4}
)
var testOCSPExtension = append([]byte{byte(extensionStatusRequest) >> 8, byte(extensionStatusRequest), 0, 8, statusTypeOCSP, 0, 0, 4}, testOCSPResponse...)
var testSCTExtension = append([]byte{byte(extensionSignedCertificateTimestamp) >> 8, byte(extensionSignedCertificateTimestamp), 0, byte(len(testSCTList))}, testSCTList...)
var (
testOCSPExtension = append([]byte{byte(extensionStatusRequest) >> 8, byte(extensionStatusRequest), 0, 8, statusTypeOCSP, 0, 0, 4}, testOCSPResponse...)
testSCTExtension = append([]byte{byte(extensionSignedCertificateTimestamp) >> 8, byte(extensionSignedCertificateTimestamp), 0, byte(len(testSCTList))}, testSCTList...)
)
var (
rsaCertificate Credential
@@ -682,7 +686,8 @@ type testCase struct {
// shimCredentials is a list of credentials which should be configured at
// the shim. It differs from shimCertificate only in whether the old or
// new APIs are used.
shimCredentials []*Credential
shimCredentials []*Credential
resumeShimCredentials []*Credential
}
var testCases []testCase
@@ -1071,7 +1076,7 @@ func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool, tr
// If readWithUnfinishedWrite is set, the shim prefix will be
// available later.
if shimPrefix != "" && !test.readWithUnfinishedWrite {
var buf = make([]byte, len(shimPrefix))
buf := make([]byte, len(shimPrefix))
_, err := io.ReadFull(tlsConn, buf)
if err != nil {
return err
@@ -1165,7 +1170,7 @@ func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool, tr
// Consume the shim prefix if needed.
if shimPrefix != "" {
var buf = make([]byte, len(shimPrefix))
buf := make([]byte, len(shimPrefix))
_, err := io.ReadFull(tlsConn, buf)
if err != nil {
return err
@@ -1464,6 +1469,8 @@ func appendCredentialFlags(flags []string, cred *Credential, prefix string, newC
flags = append(flags, prefix+"-new-x509-credential")
case CredentialTypeDelegated:
flags = append(flags, prefix+"-new-delegated-credential")
case CredentialTypeSPAKE2PlusV1:
flags = append(flags, prefix+"-new-spake2plusv1-credential")
default:
panic(fmt.Errorf("unknown credential type %d", cred.Type))
}
@@ -1471,23 +1478,30 @@ func appendCredentialFlags(flags []string, cred *Credential, prefix string, newC
panic("default credential must be X.509")
}
handleBase64Field := func(flag string, value []byte) {
if len(value) != 0 {
flags = append(flags, fmt.Sprintf("%s-%s", prefix, flag), base64FlagValue(value))
}
}
if len(cred.ChainPath) != 0 {
flags = append(flags, prefix+"-cert-file", cred.ChainPath)
}
if len(cred.KeyPath) != 0 {
flags = append(flags, prefix+"-key-file", cred.KeyPath)
}
if len(cred.OCSPStaple) != 0 {
flags = append(flags, prefix+"-ocsp-response", base64FlagValue(cred.OCSPStaple))
}
if len(cred.SignedCertificateTimestampList) != 0 {
flags = append(flags, prefix+"-signed-cert-timestamps", base64FlagValue(cred.SignedCertificateTimestampList))
}
handleBase64Field("ocsp-response", cred.OCSPStaple)
handleBase64Field("signed-cert-timestamps", cred.SignedCertificateTimestampList)
for _, sigAlg := range cred.SignatureAlgorithms {
flags = append(flags, prefix+"-signing-prefs", strconv.Itoa(int(sigAlg)))
}
if len(cred.DelegatedCredential) != 0 {
flags = append(flags, prefix+"-delegated-credential", base64FlagValue(cred.DelegatedCredential))
handleBase64Field("delegated-credential", cred.DelegatedCredential)
handleBase64Field("pake-context", cred.PAKEContext)
handleBase64Field("pake-client-id", cred.PAKEClientID)
handleBase64Field("pake-server-id", cred.PAKEServerID)
handleBase64Field("pake-password", cred.PAKEPassword)
if cred.WrongPAKERole {
flags = append(flags, prefix+"-wrong-pake-role")
}
return flags
}
@@ -1511,7 +1525,7 @@ func runTest(dispatcher *shimDispatcher, statusChan chan statusMsg, test *testCa
// Configure the default credential.
shimCertificate := test.shimCertificate
if shimCertificate == nil && len(test.shimCredentials) == 0 && test.testType == serverTest && len(test.config.PreSharedKey) == 0 {
if shimCertificate == nil && len(test.shimCredentials) == 0 && len(test.resumeShimCredentials) == 0 && test.testType == serverTest && len(test.config.PreSharedKey) == 0 {
shimCertificate = &rsaCertificate
}
if shimCertificate != nil {
@@ -1529,6 +1543,9 @@ func runTest(dispatcher *shimDispatcher, statusChan chan statusMsg, test *testCa
for _, cred := range test.shimCredentials {
flags = appendCredentialFlags(flags, cred, "", true)
}
for _, cred := range test.resumeShimCredentials {
flags = appendCredentialFlags(flags, cred, "-on-resume", true)
}
if test.protocol == dtls {
flags = append(flags, "-dtls")
@@ -2009,6 +2026,7 @@ NextTest:
if test.protocol != tls ||
test.testType != serverTest ||
len(test.shimCredentials) != 0 ||
len(test.resumeShimCredentials) != 0 ||
strings.Contains(test.name, "ECH-Server") ||
test.skipSplitHandshake {
continue
@@ -4572,7 +4590,7 @@ func addCBCPaddingTests() {
}
func addCBCSplittingTests() {
var cbcCiphers = []struct {
cbcCiphers := []struct {
name string
cipher uint16
}{
@@ -4892,7 +4910,6 @@ func addClientAuthTests() {
"-use-client-ca-list", "<EMPTY>",
},
})
}
func addExtendedMasterSecretTests() {
@@ -7802,7 +7819,7 @@ func addExtensionTests() {
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte{}},
ApplicationSettings: map[string][]byte{"proto": {}},
ALPSUseNewCodepoint: alpsCodePoint,
},
resumeSession: true,
@@ -7822,7 +7839,7 @@ func addExtensionTests() {
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte{}},
ApplicationSettings: map[string][]byte{"proto": {}},
ALPSUseNewCodepoint: alpsCodePoint,
},
resumeSession: true,
@@ -7887,7 +7904,7 @@ func addExtensionTests() {
config: Config{
MaxVersion: ver.version,
NextProtos: []string{"proto"},
ApplicationSettings: map[string][]byte{"proto": []byte{}},
ApplicationSettings: map[string][]byte{"proto": {}},
Bugs: bugs,
ALPSUseNewCodepoint: alpsCodePoint,
},
@@ -10467,8 +10484,10 @@ var testSignatureAlgorithms = []struct {
{"ECDSA", 0, &ecdsaP256Certificate, CurveP256},
}
const fakeSigAlg1 signatureAlgorithm = 0x2a01
const fakeSigAlg2 signatureAlgorithm = 0xff01
const (
fakeSigAlg1 signatureAlgorithm = 0x2a01
fakeSigAlg2 signatureAlgorithm = 0xff01
)
func addSignatureAlgorithmTests() {
// Not all ciphers involve a signature. Advertise a list which gives all
@@ -13026,7 +13045,6 @@ func addExportTrafficSecretsTests() {
{"AEAD-AES128-GCM-SHA256", TLS_AES_128_GCM_SHA256},
{"AEAD-AES256-GCM-SHA384", TLS_AES_256_GCM_SHA384},
} {
testCases = append(testCases, testCase{
name: "ExportTrafficSecrets-" + cipherSuite.name,
config: Config{
@@ -18288,7 +18306,7 @@ func addJDK11WorkaroundTests() {
flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
})
var clientHelloTests = []struct {
clientHelloTests := []struct {
clientHello []byte
isJDK11 bool
}{
@@ -18596,7 +18614,8 @@ var echCiphers = []echCipher{
{
name: "HKDF-SHA256-AES-256-GCM",
cipher: HPKECipherSuite{KDF: hpke.HKDFSHA256, AEAD: hpke.AES256GCM},
}, {
},
{
name: "HKDF-SHA256-ChaCha20-Poly1305",
cipher: HPKECipherSuite{KDF: hpke.HKDFSHA256, AEAD: hpke.ChaCha20Poly1305},
},
@@ -18790,7 +18809,8 @@ func addEncryptedClientHelloTests() {
flags: []string{
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
"-ech-server-key", base64FlagValue(echConfig.Key),
"-ech-is-retry-config", "1"},
"-ech-is-retry-config", "1",
},
shouldFail: true,
expectedLocalError: "remote error: illegal parameter",
expectedError: ":INVALID_CLIENT_HELLO_INNER:",
@@ -22885,6 +22905,502 @@ func addKeyUpdateTests() {
})
}
func addPAKETests() {
spakeCredential := Credential{
Type: CredentialTypeSPAKE2PlusV1,
PAKEContext: []byte("context"),
PAKEClientID: []byte("client"),
PAKEServerID: []byte("server"),
PAKEPassword: []byte("password"),
}
spakeWrongClientID := spakeCredential
spakeWrongClientID.PAKEClientID = []byte("wrong")
spakeWrongServerID := spakeCredential
spakeWrongServerID.PAKEServerID = []byte("wrong")
spakeWrongPassword := spakeCredential
spakeWrongPassword.PAKEPassword = []byte("wrong")
spakeWrongRole := spakeCredential
spakeWrongRole.WrongPAKERole = true
spakeWrongCodepoint := spakeCredential
spakeWrongCodepoint.OverridePAKECodepoint = 1234
testCases = append(testCases, testCase{
name: "PAKE-No-Server-Support",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
},
shouldFail: true,
expectedError: ":MISSING_KEY_SHARE:",
})
testCases = append(testCases, testCase{
name: "PAKE-Server",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
// We do not currently support resumption with PAKE, so PAKE
// servers should not issue session tickets.
ExpectNoNewSessionTicket: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
})
testCases = append(testCases, testCase{
// Send a ClientHello with the wrong PAKE client ID.
name: "PAKE-Server-WrongClientID",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeWrongClientID,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":PEER_PAKE_MISMATCH:",
expectedLocalError: "remote error: handshake failure",
})
testCases = append(testCases, testCase{
// Send a ClientHello with the wrong PAKE server ID.
name: "PAKE-Server-WrongServerID",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeWrongServerID,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":PEER_PAKE_MISMATCH:",
expectedLocalError: "remote error: handshake failure",
})
testCases = append(testCases, testCase{
// Send a ClientHello with the wrong PAKE codepoint.
name: "PAKE-Server-WrongCodepoint",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeWrongCodepoint,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":PEER_PAKE_MISMATCH:",
expectedLocalError: "remote error: handshake failure",
})
testCases = append(testCases, testCase{
// A server configured with a mix of PAKE and non-PAKE
// credentials will select the first that matches what the
// client offered. In doing so, it should skip unsupported
// PAKE algorithms.
name: "PAKE-Server-MultiplePAKEs",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
OfferExtraPAKEs: []uint16{1, 2, 3, 4, 5},
},
},
shimCredentials: []*Credential{&spakeWrongClientID, &spakeWrongServerID, &spakeWrongRole, &spakeCredential, &rsaCertificate},
flags: []string{"-expect-selected-credential", "3"},
})
testCases = append(testCases, testCase{
// A server configured with a certificate credential before a
// PAKE credential will consider the certificate credential first.
name: "PAKE-Server-CertificateBeforePAKE",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
// Pretend to offer a matching PAKE share, but expect the
// shim to select the credential first and negotiate a
// normal handshake.
OfferExtraPAKEClientID: spakeCredential.PAKEClientID,
OfferExtraPAKEServerID: spakeCredential.PAKEServerID,
OfferExtraPAKEs: []uint16{spakeID},
},
},
shimCredentials: []*Credential{&rsaCertificate, &spakeCredential},
flags: []string{"-expect-selected-credential", "0"},
})
testCases = append(testCases, testCase{
// A server configured with just a PAKE credential should reject normal
// clients.
name: "PAKE-Server-NormalClient",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":PEER_PAKE_MISMATCH:",
expectedLocalError: "remote error: handshake failure",
})
testCases = append(testCases, testCase{
// ... and TLS 1.2 clients.
name: "PAKE-Server-NormalTLS12Client",
testType: serverTest,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":NO_SHARED_CIPHER:",
expectedLocalError: "remote error: handshake failure",
})
testCases = append(testCases, testCase{
// ... but you can configure a server with both PAKE and certificate-based
// SSL_CREDENTIALs and that works.
name: "PAKE-ServerWithCertsToo-NormalClient",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
},
shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
flags: []string{"-expect-selected-credential", "1"},
})
testCases = append(testCases, testCase{
// ... and for older clients.
name: "PAKE-ServerWithCertsToo-NormalTLS12Client",
testType: serverTest,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
flags: []string{"-expect-selected-credential", "1"},
})
testCases = append(testCases, testCase{
name: "PAKE-Client",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
CheckClientHello: func(c *clientHelloMsg) error {
// PAKE connections don't use the key_share / supported_groups mechanism.
if c.hasKeyShares {
return errors.New("unexpected key_share extension")
}
if len(c.supportedCurves) != 0 {
return errors.New("unexpected supported_groups extension")
}
// PAKE connections don't use signature algorithms.
if len(c.signatureAlgorithms) != 0 {
return errors.New("unexpected signature_algorithms extension")
}
// We don't support resumption with PAKEs.
if len(c.pskKEModes) != 0 {
return errors.New("unexpected psk_key_exchange_modes extension")
}
return nil
},
},
},
shimCredentials: []*Credential{&spakeCredential},
})
testCases = append(testCases, testCase{
// Although there is no reason to request new key shares, the PAKE
// client should handle cookie requests.
name: "PAKE-Client-HRRCookie",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
SendHelloRetryRequestCookie: []byte("cookie"),
},
},
shimCredentials: []*Credential{&spakeCredential},
})
testCases = append(testCases, testCase{
// A PAKE client will not offer key shares, so the client should
// reject a HelloRetryRequest requesting a different key share.
name: "PAKE-Client-HRRKeyShare",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
SendHelloRetryRequestCurve: CurveX25519,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
expectedLocalError: "remote error: unsupported extension",
})
testCases = append(testCases, testCase{
// A server cannot reply with an HRR asking for a PAKE if the client didn't
// offer a PAKE in the ClientHello.
name: "PAKE-NormalClient-PAKEInHRR",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
AlwaysSendHelloRetryRequest: true,
SendPAKEInHelloRetryRequest: true,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
})
testCases = append(testCases, testCase{
// A PAKE client should not accept an empty ServerHello.
name: "PAKE-Client-EmptyServerHello",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
// Trigger an empty ServerHello by making a normal server skip
// the key_share extension.
MissingKeyShare: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":MISSING_EXTENSION:",
})
testCases = append(testCases, testCase{
// A PAKE client should not accept a key_share ServerHello.
name: "PAKE-Client-KeyShareServerHello",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
// Trigger a key_share ServerHello by making a normal server
// skip the HelloRetryRequest it would otherwise send in
// response to the shim's key_share-less ClientHello.
SkipHelloRetryRequest: true,
// Ignore the client's lack of supported_groups.
IgnorePeerCurvePreferences: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
})
testCases = append(testCases, testCase{
// A PAKE client should not accept a TLS 1.2 ServerHello.
name: "PAKE-Client-TLS12ServerHello",
testType: clientTest,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
})
testCases = append(testCases, testCase{
// A server cannot send the PAKE extension to a non-PAKE client.
name: "PAKE-NormalClient-UnsolicitedPAKEInServerHello",
testType: clientTest,
config: Config{
Bugs: ProtocolBugs{
UnsolicitedPAKE: spakeID,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
})
testCases = append(testCases, testCase{
// A server cannot reply with a PAKE that the client did not offer.
name: "PAKE-Client-WrongPAKEInServerHello",
testType: clientTest,
config: Config{
Bugs: ProtocolBugs{
UnsolicitedPAKE: 1234,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
testCases = append(testCases, testCase{
name: "PAKE-Extension-Duplicate",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
OfferExtraPAKEClientID: []byte("client"),
OfferExtraPAKEServerID: []byte("server"),
OfferExtraPAKEs: []uint16{1234, 1234},
},
},
shouldFail: true,
expectedError: ":ERROR_PARSING_EXTENSION:",
})
testCases = append(testCases, testCase{
// If the client sees a server with a wrong password, it should
// reject the confirmV value in the ServerHello.
name: "PAKE-Client-WrongPassword",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeWrongPassword,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
testCases = append(testCases, testCase{
name: "PAKE-Client-Truncate",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
TruncatePAKEMessage: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
testCases = append(testCases, testCase{
name: "PAKE-Server-Truncate",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
TruncatePAKEMessage: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
expectedLocalError: "remote error: illegal parameter",
})
testCases = append(testCases, testCase{
// Servers may not send CertificateRequest in a PAKE handshake.
name: "PAKE-Client-UnexpectedCertificateRequest",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
ClientAuth: RequireAnyClientCert,
Bugs: ProtocolBugs{
AlwaysSendCertificateRequest: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
expectedLocalError: "remote error: unexpected message",
})
testCases = append(testCases, testCase{
// Servers may not send Certificate in a PAKE handshake.
name: "PAKE-Client-UnexpectedCertificate",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
AlwaysSendCertificate: true,
UseCertificateCredential: &rsaCertificate,
// Ignore the client's lack of signature_algorithms.
IgnorePeerSignatureAlgorithmPreferences: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
expectedLocalError: "remote error: unexpected message",
})
testCases = append(testCases, testCase{
// If a server is configured to request client certificates, it should
// still not do so when negotiating a PAKE.
name: "PAKE-Server-DoNotRequestClientCertificate",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
},
shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
flags: []string{"-require-any-client-certificate"},
})
testCases = append(testCases, testCase{
// Clients should ignore server PAKE credentials.
name: "PAKE-Client-WrongRole",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
},
shimCredentials: []*Credential{&spakeWrongRole},
shouldFail: true,
// The shim will send a non-PAKE ClientHello.
expectedLocalError: "tls: client not configured with PAKE",
})
testCases = append(testCases, testCase{
// Servers should ignore client PAKE credentials.
name: "PAKE-Server-WrongRole",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
},
shimCredentials: []*Credential{&spakeWrongRole},
shouldFail: true,
// The shim will fail the handshake because it has no usable credentials
// available.
expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
expectedLocalError: "remote error: handshake failure",
})
testCases = append(testCases, testCase{
// On the client, we only support a single PAKE credential.
name: "PAKE-Client-MultiplePAKEs",
testType: clientTest,
shimCredentials: []*Credential{&spakeCredential, &spakeWrongPassword},
shouldFail: true,
expectedError: ":UNSUPPORTED_CREDENTIAL_LIST:",
})
testCases = append(testCases, testCase{
// On the client, we only support a single PAKE credential.
name: "PAKE-Client-PAKEAndCertificate",
testType: clientTest,
shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
shouldFail: true,
expectedError: ":UNSUPPORTED_CREDENTIAL_LIST:",
})
testCases = append(testCases, testCase{
// We currently do not support resumption with PAKE. Even if configured
// with a session, the client should not offer the session with PAKEs.
name: "PAKE-Client-NoResume",
testType: clientTest,
// Make two connections. For the first connection, just establish a
// session without PAKE, to pick up a session.
config: Config{
Credential: &rsaCertificate,
},
// For the second connection, use SPAKE.
resumeSession: true,
resumeConfig: &Config{
Credential: &spakeCredential,
Bugs: ProtocolBugs{
// Check that the ClientHello does not offer a session, even
// though one was configured.
ExpectNoTLS13PSK: true,
// Respond with an unsolicted PSK extension in ServerHello, to
// check that the client rejects it.
AlwaysSelectPSKIdentity: true,
},
},
resumeShimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
})
}
func worker(dispatcher *shimDispatcher, statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {
defer wg.Done()
@@ -23137,6 +23653,7 @@ func main() {
addCompliancePolicyTests()
addCertificateSelectionTests()
addKeyUpdateTests()
addPAKETests()
toAppend, err := convertToSplitHandshakeTests(testCases)
if err != nil {
+439
View File
@@ -0,0 +1,439 @@
// Copyright 2025 The BoringSSL Authors
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// Package spake2plus implements RFC 9383 for testing.
package spake2plus
import (
"bytes"
"crypto/elliptic"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"errors"
"io"
"math/big"
"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/scrypt"
)
const (
verifierSize = 32 // size of w0, w1 in bytes, for P-256
registrationRecordSize = 65 // uncompressed P-256 point size
shareSize = 65
confirmSize = 32
keySize = 32
pbkdfOutputSize = 80
)
type Role int
const (
RoleProver Role = iota
RoleVerifier
)
type state int
const (
stateInit state = iota
stateShareGenerated
stateKeyGenerated
)
type Context struct {
IDProver []byte
IDVerifier []byte
Role Role
curve elliptic.Curve
context []byte
w0 *big.Int
w1 *big.Int
Lx, Ly *big.Int // L point
Mx, My *big.Int // M point
Nx, Ny *big.Int // N point
Xx, Xy *big.Int // X point
Yx, Yy *big.Int // Y point
Zx, Zy *big.Int // Z point
Vx, Vy *big.Int // V point
x *big.Int // ephemeral scalar for prover
y *big.Int // ephemeral scalar for verifier
share []byte
confirm []byte
state state
}
// Hardcoded M and N from the RFC (uncompressed)
var kM = []byte{
0x04, 0x88, 0x6e, 0x2f, 0x97, 0xac, 0xe4, 0x6e, 0x55, 0xba, 0x9d,
0xd7, 0x24, 0x25, 0x79, 0xf2, 0x99, 0x3b, 0x64, 0xe1, 0x6e, 0xf3,
0xdc, 0xab, 0x95, 0xaf, 0xd4, 0x97, 0x33, 0x3d, 0x8f, 0xa1, 0x2f,
0x5f, 0xf3, 0x55, 0x16, 0x3e, 0x43, 0xce, 0x22, 0x4e, 0x0b, 0x0e,
0x65, 0xff, 0x02, 0xac, 0x8e, 0x5c, 0x7b, 0xe0, 0x94, 0x19, 0xc7,
0x85, 0xe0, 0xca, 0x54, 0x7d, 0x55, 0xa1, 0x2e, 0x2d, 0x20,
}
var kN = []byte{
0x04, 0xd8, 0xbb, 0xd6, 0xc6, 0x39, 0xc6, 0x29, 0x37, 0xb0, 0x4d,
0x99, 0x7f, 0x38, 0xc3, 0x77, 0x07, 0x19, 0xc6, 0x29, 0xd7, 0x01,
0x4d, 0x49, 0xa2, 0x4b, 0x4f, 0x98, 0xba, 0xa1, 0x29, 0x2b, 0x49,
0x07, 0xd6, 0x0a, 0xa6, 0xbf, 0xad, 0xe4, 0x50, 0x08, 0xa6, 0x36,
0x33, 0x7f, 0x51, 0x68, 0xc6, 0x4d, 0x9b, 0xd3, 0x60, 0x34, 0x80,
0x8c, 0xd5, 0x64, 0x49, 0x0b, 0x1e, 0x65, 0x6e, 0xdb, 0xe7,
}
func Register(
pw []byte,
idProver []byte,
idVerifier []byte,
) (pwVerifierW0 []byte, pwVerifierW1 []byte, registrationRecord []byte, err error) {
mhfBuf := new(bytes.Buffer)
if err := binary.Write(mhfBuf, binary.LittleEndian, uint64(len(pw))); err != nil {
return nil, nil, nil, err
}
mhfBuf.Write(pw)
if err := binary.Write(mhfBuf, binary.LittleEndian, uint64(len(idProver))); err != nil {
return nil, nil, nil, err
}
mhfBuf.Write(idProver)
if err := binary.Write(mhfBuf, binary.LittleEndian, uint64(len(idVerifier))); err != nil {
return nil, nil, nil, err
}
mhfBuf.Write(idVerifier)
key, err := scrypt.Key(mhfBuf.Bytes(), nil, 32768, 8, 1, pbkdfOutputSize)
if err != nil {
return nil, nil, nil, err
}
curve := elliptic.P256()
N := curve.Params().N
w0 := new(big.Int).SetBytes(key[:pbkdfOutputSize/2])
w0.Mod(w0, N)
w1 := new(big.Int).SetBytes(key[pbkdfOutputSize/2:])
w1.Mod(w1, N)
pwVerifierW0 = make([]byte, verifierSize)
pwVerifierW1 = make([]byte, verifierSize)
copy(pwVerifierW0, w0.Bytes())
copy(pwVerifierW1, w1.Bytes())
Lx, Ly := curve.ScalarBaseMult(w1.Bytes())
L := elliptic.Marshal(curve, Lx, Ly)
registrationRecord = make([]byte, registrationRecordSize)
copy(registrationRecord, L)
return pwVerifierW0, pwVerifierW1, registrationRecord, nil
}
func newContext(
role Role,
context []byte,
idProver []byte,
idVerifier []byte,
pwVerifierW0 []byte,
pwVerifierW1 []byte,
registrationRecord []byte,
x *big.Int,
y *big.Int,
) (*Context, error) {
curve := elliptic.P256()
Mx, My := elliptic.Unmarshal(curve, kM)
if Mx == nil {
return nil, errors.New("invalid M point")
}
Nx, Ny := elliptic.Unmarshal(curve, kN)
if Nx == nil {
return nil, errors.New("invalid N point")
}
ctx := &Context{
Role: role,
curve: curve,
context: append([]byte(nil), context...),
IDProver: append([]byte(nil), idProver...),
IDVerifier: append([]byte(nil), idVerifier...),
Mx: Mx,
My: My,
Nx: Nx,
Ny: Ny,
state: stateInit,
}
N := curve.Params().N
if role == RoleProver {
if pwVerifierW0 == nil || pwVerifierW1 == nil || y != nil {
return nil, errors.New("invalid parameters for prover")
}
ctx.w0 = new(big.Int).SetBytes(pwVerifierW0)
ctx.w1 = new(big.Int).SetBytes(pwVerifierW1)
ctx.w0.Mod(ctx.w0, N)
ctx.w1.Mod(ctx.w1, N)
if x == nil {
xRand, err := randFieldElement(curve)
if err != nil {
return nil, err
}
ctx.x = xRand
} else {
ctx.x = new(big.Int).Set(x)
}
} else {
// Verifier
if pwVerifierW0 == nil || registrationRecord == nil || x != nil {
return nil, errors.New("invalid parameters for verifier")
}
ctx.w0 = new(big.Int).SetBytes(pwVerifierW0)
ctx.w0.Mod(ctx.w0, N)
// Load L
Lx, Ly := elliptic.Unmarshal(curve, registrationRecord)
if Lx == nil {
return nil, errors.New("invalid L point")
}
ctx.Lx, ctx.Ly = Lx, Ly
if y == nil {
yRand, err := randFieldElement(curve)
if err != nil {
return nil, err
}
ctx.y = yRand
} else {
ctx.y = new(big.Int).Set(y)
}
}
return ctx, nil
}
func randFieldElement(curve elliptic.Curve) (*big.Int, error) {
params := curve.Params()
b := make([]byte, (params.BitSize+7)/8)
var k *big.Int
for {
if _, err := rand.Read(b); err != nil {
return nil, err
}
k = new(big.Int).SetBytes(b)
if k.Sign() != 0 && k.Cmp(params.N) < 0 {
break
}
}
return k, nil
}
func NewProver(
context []byte, idProver []byte, idVerifier []byte,
pwVerifierW0 []byte, pwVerifierW1 []byte,
) (*Context, error) {
return newContext(RoleProver, context, idProver, idVerifier,
pwVerifierW0, pwVerifierW1, nil, nil, nil)
}
func NewVerifier(
context []byte, idProver []byte, idVerifier []byte,
pwVerifierW0 []byte, registrationRecord []byte,
) (*Context, error) {
return newContext(RoleVerifier, context, idProver, idVerifier,
pwVerifierW0, nil, registrationRecord, nil, nil)
}
func (ctx *Context) GenerateProverShare() (share []byte, err error) {
if ctx.Role != RoleProver {
return nil, errors.New("invalid state for prover share generation")
}
if ctx.state != stateInit {
return ctx.share, nil
}
curve := ctx.curve
// l = x * G
lx, ly := curve.ScalarBaseMult(ctx.x.Bytes())
// r = w0 * M
rx, ry := curve.ScalarMult(ctx.Mx, ctx.My, ctx.w0.Bytes())
// X = l + r
Xx, Xy := curve.Add(lx, ly, rx, ry)
ctx.Xx, ctx.Xy = Xx, Xy
share = elliptic.Marshal(curve, Xx, Xy)
ctx.share = append([]byte(nil), share...)
ctx.state = stateShareGenerated
return share, nil
}
func updateWithLengthPrefix(h io.Writer, data []byte) {
var lenLe [8]byte
binary.LittleEndian.PutUint64(lenLe[:], uint64(len(data)))
h.Write(lenLe[:])
h.Write(data)
}
func computeTranscriptAndConfirmation(
ctx *Context,
shareP, shareV []byte,
) (proverConfirm, verifierConfirm, sharedSecret []byte, err error) {
curve := ctx.curve
Z := elliptic.Marshal(curve, ctx.Zx, ctx.Zy)
V := elliptic.Marshal(curve, ctx.Vx, ctx.Vy)
h := sha256.New()
updateWithLengthPrefix(h, ctx.context)
updateWithLengthPrefix(h, ctx.IDProver)
updateWithLengthPrefix(h, ctx.IDVerifier)
updateWithLengthPrefix(h, kM)
updateWithLengthPrefix(h, kN)
updateWithLengthPrefix(h, shareP)
updateWithLengthPrefix(h, shareV)
updateWithLengthPrefix(h, Z)
updateWithLengthPrefix(h, V)
updateWithLengthPrefix(h, ctx.w0.Bytes())
K_main := h.Sum(nil)
confirmationStr := []byte("ConfirmationKeys")
keys := doHKDF(K_main, confirmationStr, keySize*2)
secretInfoStr := []byte("SharedKey")
sharedSecret = doHKDF(K_main, secretInfoStr, keySize)
// Prover confirmation = HMAC(keys[:32], shareV)
macP := hmac.New(sha256.New, keys[:keySize])
macP.Write(shareV)
proverConfirm = macP.Sum(nil)
// Verifier confirmation = HMAC(keys[32:], shareP)
macV := hmac.New(sha256.New, keys[keySize:])
macV.Write(shareP)
verifierConfirm = macV.Sum(nil)
return
}
func doHKDF(ikm, info []byte, size int) []byte {
h := hkdf.New(sha256.New, ikm, nil, info)
out := make([]byte, size)
h.Read(out)
return out
}
func (ctx *Context) ProcessProverShare(
proverShare []byte,
) (verifierShare []byte, verifierConfirm []byte, sharedSecret []byte, err error) {
if ctx.Role != RoleVerifier || ctx.state != stateInit || len(proverShare) != shareSize {
return nil, nil, nil, errors.New("invalid state or share")
}
curve := ctx.curve
// Y = y*G + w0*N
lx, ly := curve.ScalarBaseMult(ctx.y.Bytes())
rx, ry := curve.ScalarMult(ctx.Nx, ctx.Ny, ctx.w0.Bytes())
Yx, Yy := curve.Add(lx, ly, rx, ry)
ctx.Yx, ctx.Yy = Yx, Yy
verifierShare = elliptic.Marshal(curve, Yx, Yy)
if px, py := elliptic.Unmarshal(curve, proverShare); px == nil {
return nil, nil, nil, errors.New("invalid prover share")
} else {
ctx.Xx, ctx.Xy = px, py
}
// T = X - w0*M
mx, my := curve.ScalarMult(ctx.Mx, ctx.My, ctx.w0.Bytes())
mx, my = mx, new(big.Int).Neg(my)
my.Mod(my, curve.Params().P)
Tx, Ty := curve.Add(ctx.Xx, ctx.Xy, mx, my)
// Z = (y)*T
Zx, Zy := curve.ScalarMult(Tx, Ty, ctx.y.Bytes())
ctx.Zx, ctx.Zy = Zx, Zy
// V = (y)*L
Vx, Vy := curve.ScalarMult(ctx.Lx, ctx.Ly, ctx.y.Bytes())
ctx.Vx, ctx.Vy = Vx, Vy
proverConfirm, verifierConfirm, sharedSecret, err := computeTranscriptAndConfirmation(ctx, proverShare, verifierShare)
if err != nil {
return nil, nil, nil, err
}
ctx.confirm = proverConfirm
ctx.state = stateKeyGenerated
return verifierShare, verifierConfirm, sharedSecret, nil
}
// computeProverConfirmation (Prover side)
func (ctx *Context) ComputeProverConfirmation(
verifierShare []byte,
claimedVerifierConfirm []byte,
) (proverConfirm []byte, sharedSecret []byte, err error) {
if ctx.Role != RoleProver || ctx.state != stateShareGenerated || len(verifierShare) != shareSize || len(claimedVerifierConfirm) != confirmSize {
return nil, nil, errors.New("invalid state or input")
}
curve := ctx.curve
vx, vy := elliptic.Unmarshal(curve, verifierShare)
if vx == nil {
return nil, nil, errors.New("invalid verifier share")
}
ctx.Yx, ctx.Yy = vx, vy
// T = Y - w0*N
nx, ny := curve.ScalarMult(ctx.Nx, ctx.Ny, ctx.w0.Bytes())
ny.Neg(ny)
ny.Mod(ny, curve.Params().P)
Tx, Ty := curve.Add(ctx.Yx, ctx.Yy, nx, ny)
// Z = x*T
Zx, Zy := curve.ScalarMult(Tx, Ty, ctx.x.Bytes())
ctx.Zx, ctx.Zy = Zx, Zy
// V = w1*T
Vx, Vy := curve.ScalarMult(Tx, Ty, ctx.w1.Bytes())
ctx.Vx, ctx.Vy = Vx, Vy
// Compute transcript
proverConfirm, verifierConfirm, sharedSecret, err := computeTranscriptAndConfirmation(ctx, ctx.share, verifierShare)
if err != nil {
return nil, nil, err
}
// Check verifier confirm
if !hmac.Equal(verifierConfirm, claimedVerifierConfirm) {
return nil, nil, errors.New("verifier confirmation mismatch")
}
ctx.state = stateKeyGenerated
return proverConfirm, sharedSecret, nil
}
// VerifyProverConfirmation (Verifier side)
func (ctx *Context) VerifyProverConfirmation(proverConfirm []byte) error {
if ctx.Role != RoleVerifier || ctx.state != stateKeyGenerated || len(proverConfirm) != confirmSize {
return errors.New("invalid state or input")
}
if !hmac.Equal(ctx.confirm, proverConfirm) {
return errors.New("prover confirmation mismatch")
}
return nil
}
@@ -0,0 +1,266 @@
// Copyright 2025 The BoringSSL Authors
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package spake2plus
import (
"bytes"
"encoding/hex"
"math/big"
"testing"
)
func hexToBytes(h string) []byte {
b, err := hex.DecodeString(h)
if err != nil {
panic(err)
}
return b
}
func TestSPAKE2PlusBasicRoundTrip(t *testing.T) {
pw := []byte("password")
context := []byte("SPAKE2+-P256-SHA256-HKDF-SHA256-HMAC-SHA256 Test Vectors")
idProver := []byte("client")
idVerifier := []byte("server")
pwVerifierW0, pwVerifierW1, registrationRecord, err := Register(
pw, idProver, idVerifier,
)
if err != nil {
t.Fatalf("Registration failed: %v", err)
}
prover, err := NewProver(
context, idProver, idVerifier,
pwVerifierW0, pwVerifierW1,
)
if err != nil {
t.Fatalf("Prover context creation failed: %v", err)
}
verifier, err := NewVerifier(
context, idProver, idVerifier,
pwVerifierW0, registrationRecord,
)
if err != nil {
t.Fatalf("Verifier context creation failed: %v", err)
}
proverShare, err := prover.GenerateProverShare()
if err != nil {
t.Fatalf("Prover share generation failed: %v", err)
}
verifierShare, verifierConfirm, verifierSecret, err := verifier.ProcessProverShare(proverShare)
if err != nil {
t.Fatalf("Verifier failed to process prover's share: %v", err)
}
proverConfirm, proverSecret, err := prover.ComputeProverConfirmation(verifierShare, verifierConfirm)
if err != nil {
t.Fatalf("Prover failed to compute confirmation: %v", err)
}
if err := verifier.VerifyProverConfirmation(proverConfirm); err != nil {
t.Fatalf("Verifier failed to verify prover confirmation: %v", err)
}
if !bytes.Equal(proverSecret, verifierSecret) {
t.Fatal("Shared secrets do not match")
}
}
func TestSPAKE2PlusTestVectors(t *testing.T) {
// Test Vectors from RFC 9383 Appendix C
context := []byte("SPAKE2+-P256-SHA256-HKDF-SHA256-HMAC-SHA256 Test Vectors")
idProver := []byte("client")
idVerifier := []byte("server")
w0_str := "bb8e1bbcf3c48f62c08db243652ae55d3e5586053fca77102994f23ad95491b3"
w1_str := "7e945f34d78785b8a3ef44d0df5a1a97d6b3b460409a345ca7830387a74b1dba"
L_str := "04eb7c9db3d9a9eb1f8adab81b5794c1f13ae3e225efbe91ea487425854c7fc00f00bfedcbd09b2400142d40a14f2064ef31dfaa903b91d1faea7093d835966efd"
x_str := "d1232c8e8693d02368976c174e2088851b8365d0d79a9eee709c6a05a2fad539"
y_str := "717a72348a182085109c8d3917d6c43d59b224dc6a7fc4f0483232fa6516d8b3"
share_p_str := "04ef3bd051bf78a2234ec0df197f7828060fe9856503579bb1733009042c15c0c1de127727f418b5966afadfdd95a6e4591d171056b333dab97a79c7193e341727"
share_v_str := "04c0f65da0d11927bdf5d560c69e1d7d939a05b0e88291887d679fcadea75810fb5cc1ca7494db39e82ff2f50665255d76173e09986ab46742c798a9a68437b048"
confirm_p_str := "926cc713504b9b4d76c9162ded04b5493e89109f6d89462cd33adc46fda27527"
confirm_v_str := "9747bcc4f8fe9f63defee53ac9b07876d907d55047e6ff2def2e7529089d3e68"
secret_str := "0c5f8ccd1413423a54f6c1fb26ff01534a87f893779c6e68666d772bfd91f3e7"
w0 := hexToBytes(w0_str)
w1 := hexToBytes(w1_str)
L := hexToBytes(L_str)
x := hexToBytes(x_str)
y := hexToBytes(y_str)
prover, err := newContext(
RoleProver, context, idProver, idVerifier,
w0, w1, nil, bytesToBigInt(x), nil,
)
if err != nil {
t.Fatalf("failed to create prover: %v", err)
}
verifier, err := newContext(
RoleVerifier, context, idProver, idVerifier,
w0, nil, L, nil, bytesToBigInt(y),
)
if err != nil {
t.Fatalf("failed to create verifier: %v", err)
}
proverShare, err := prover.GenerateProverShare()
if err != nil {
t.Fatalf("failed to generate prover share: %v", err)
}
expectedShareP := hexToBytes(share_p_str)
if !bytes.Equal(proverShare, expectedShareP) {
t.Fatalf("prover share mismatch:\n got %x\nwant %x", proverShare, expectedShareP)
}
vShare, vConfirm, vSecret, err := verifier.ProcessProverShare(proverShare)
if err != nil {
t.Fatalf("verifier failed to process prover share: %v", err)
}
expectedShareV := hexToBytes(share_v_str)
if !bytes.Equal(vShare, expectedShareV) {
t.Fatalf("verifier share mismatch:\n got %x\nwant %x", vShare, expectedShareV)
}
expectedConfirmV := hexToBytes(confirm_v_str)
if !bytes.Equal(vConfirm, expectedConfirmV) {
t.Fatalf("verifier confirm mismatch:\n got %x\nwant %x", vConfirm, expectedConfirmV)
}
pConfirm, pSecret, err := prover.ComputeProverConfirmation(vShare, vConfirm)
if err != nil {
t.Fatalf("prover failed to compute confirmation: %v", err)
}
expectedConfirmP := hexToBytes(confirm_p_str)
if !bytes.Equal(pConfirm, expectedConfirmP) {
t.Fatalf("prover confirm mismatch:\n got %x\nwant %x", pConfirm, expectedConfirmP)
}
if err := verifier.VerifyProverConfirmation(pConfirm); err != nil {
t.Fatalf("verifier failed to verify prover confirmation: %v", err)
}
if !bytes.Equal(pSecret, vSecret) {
t.Fatal("shared secrets do not match")
}
expectedSecret := hexToBytes(secret_str)
if !bytes.Equal(expectedSecret, vSecret) {
t.Fatalf("shared secret mismatch:\n got %x\nwant %x", vSecret, expectedSecret)
}
}
func TestSPAKE2PlusMultipleRuns(t *testing.T) {
pw := []byte("password")
context := []byte("Repeated test")
idProver := []byte("client")
idVerifier := []byte("server")
for i := 0; i < 5; i++ {
pwVerifierW0, pwVerifierW1, registrationRecord, err := Register(
pw, idProver, idVerifier)
if err != nil {
t.Fatalf("registration failed: %v", err)
}
prover, err := NewProver(context, idProver, idVerifier, pwVerifierW0, pwVerifierW1)
if err != nil {
t.Fatalf("prover context creation failed: %v", err)
}
verifier, err := NewVerifier(context, idProver, idVerifier, pwVerifierW0, registrationRecord)
if err != nil {
t.Fatalf("verifier context creation failed: %v", err)
}
proverShare, err := prover.GenerateProverShare()
if err != nil {
t.Fatalf("prover share gen failed: %v", err)
}
vShare, vConfirm, vSecret, err := verifier.ProcessProverShare(proverShare)
if err != nil {
t.Fatalf("verifier process share failed: %v", err)
}
pConfirm, pSecret, err := prover.ComputeProverConfirmation(vShare, vConfirm)
if err != nil {
t.Fatalf("prover compute confirm failed: %v", err)
}
if err := verifier.VerifyProverConfirmation(pConfirm); err != nil {
t.Fatalf("verifier confirm failed: %v", err)
}
if !bytes.Equal(pSecret, vSecret) {
t.Fatalf("shared secrets differ")
}
}
}
func TestSPAKE2PlusWrongPassword(t *testing.T) {
correctPw := []byte("password")
wrongPw := []byte("wrongpassword")
context := []byte("Wrong password test")
idProver := []byte("client")
idVerifier := []byte("server")
// Register with the correct password
correctW0, _, registrationRecord, err := Register(
correctPw, idProver, idVerifier)
if err != nil {
t.Fatalf("registration failed: %v", err)
}
// Register with the wrong password
wrongW0, wrongW1, _, err := Register(
wrongPw, idProver, idVerifier)
if err != nil {
t.Fatalf("registration failed: %v", err)
}
// Create prover with wrong password verifiers
prover, err := NewProver(context, idProver, idVerifier, wrongW0, wrongW1)
if err != nil {
t.Fatalf("prover context creation failed: %v", err)
}
// Create verifier with correct password verifiers
verifier, err := NewVerifier(context, idProver, idVerifier, correctW0, registrationRecord)
if err != nil {
t.Fatalf("verifier context creation failed: %v", err)
}
proverShare, err := prover.GenerateProverShare()
if err != nil {
t.Fatalf("prover share gen failed: %v", err)
}
vShare, vConfirm, _, err := verifier.ProcessProverShare(proverShare)
if err != nil {
t.Fatalf("verifier process share failed: %v", err)
}
_, _, err = prover.ComputeProverConfirmation(vShare, vConfirm)
if err == nil {
t.Fatalf("expected error computing confirmation, got nil")
}
}
func bytesToBigInt(b []byte) *big.Int {
if len(b) == 0 {
return nil
}
return new(big.Int).SetBytes(b)
}
+60 -21
View File
@@ -509,6 +509,8 @@ const Flag<TestConfig> *FindFlag(const char *name) {
NewCredentialFlag("-new-x509-credential", CredentialConfigType::kX509),
NewCredentialFlag("-new-delegated-credential",
CredentialConfigType::kDelegated),
NewCredentialFlag("-new-spake2plusv1-credential",
CredentialConfigType::kSPAKE2PlusV1),
CredentialFlagWithDefault(
StringFlag("-cert-file", &TestConfig::cert_file),
StringFlag("-cert-file", &CredentialConfig::cert_file)),
@@ -528,6 +530,16 @@ const Flag<TestConfig> *FindFlag(const char *name) {
&TestConfig::signed_cert_timestamps),
Base64Flag("-signed-cert-timestamps",
&CredentialConfig::signed_cert_timestamps)),
CredentialFlag(
Base64Flag("-pake-context", &CredentialConfig::pake_context)),
CredentialFlag(
Base64Flag("-pake-client-id", &CredentialConfig::pake_client_id)),
CredentialFlag(
Base64Flag("-pake-server-id", &CredentialConfig::pake_server_id)),
CredentialFlag(
Base64Flag("-pake-password", &CredentialConfig::pake_password)),
CredentialFlag(
BoolFlag("-wrong-pake-role", &CredentialConfig::wrong_pake_role)),
IntFlag("-private-key-delay-ms", &TestConfig::private_key_delay_ms),
};
std::sort(ret.begin(), ret.end(), FlagNameComparator{});
@@ -555,10 +567,8 @@ bool RemovePrefix(const char **str, const char *prefix) {
} // namespace
bool ParseConfig(int argc, char **argv, bool is_shim,
TestConfig *out_initial,
TestConfig *out_resume,
TestConfig *out_retry) {
bool ParseConfig(int argc, char **argv, bool is_shim, TestConfig *out_initial,
TestConfig *out_resume, TestConfig *out_retry) {
for (int i = 0; i < argc; i++) {
bool skip = false;
const char *arg = argv[i];
@@ -672,7 +682,7 @@ struct CredentialInfo {
static void CredentialInfoExDataFree(void *parent, void *ptr,
CRYPTO_EX_DATA *ad, int index, long argl,
void *argp) {
delete static_cast<CredentialInfo*>(ptr);
delete static_cast<CredentialInfo *>(ptr);
}
static int CredentialInfoExDataIndex() {
@@ -1336,6 +1346,43 @@ static bssl::UniquePtr<SSL_CREDENTIAL> CredentialFromConfig(
case CredentialConfigType::kDelegated:
cred.reset(SSL_CREDENTIAL_new_delegated());
break;
case CredentialConfigType::kSPAKE2PlusV1: {
uint8_t pw_verifier_w0[32];
uint8_t pw_verifier_w1[32];
uint8_t registration_record[65];
if (!SSL_spake2plusv1_register(pw_verifier_w0, pw_verifier_w1,
registration_record,
cred_config.pake_password.data(),
cred_config.pake_password.size(),
cred_config.pake_client_id.data(),
cred_config.pake_client_id.size(),
cred_config.pake_server_id.data(),
cred_config.pake_server_id.size())) {
return nullptr;
}
bool is_server =
cred_config.wrong_pake_role ? !config.is_server : config.is_server;
if (is_server) {
cred.reset(SSL_CREDENTIAL_new_spake2plusv1_server(
cred_config.pake_context.data(), cred_config.pake_context.size(),
cred_config.pake_client_id.data(),
cred_config.pake_client_id.size(),
cred_config.pake_server_id.data(),
cred_config.pake_server_id.size(),
/*attempts=*/1, pw_verifier_w0, sizeof(pw_verifier_w0),
registration_record, sizeof(registration_record)));
} else {
cred.reset(SSL_CREDENTIAL_new_spake2plusv1_client(
cred_config.pake_context.data(), cred_config.pake_context.size(),
cred_config.pake_client_id.data(),
cred_config.pake_client_id.size(),
cred_config.pake_server_id.data(),
cred_config.pake_server_id.size(),
/*attempts=*/1, pw_verifier_w0, sizeof(pw_verifier_w0),
pw_verifier_w1, sizeof(pw_verifier_w1)));
}
break;
}
}
if (cred == nullptr) {
return nullptr;
@@ -1700,16 +1747,13 @@ static enum ssl_select_cert_result_t SelectCertificateCallback(
// Invoke the rewind before we sanity check SNI because we will
// end up calling the select_cert_cb twice with two different SNIs.
if (SSL_ech_accepted(ssl) && config->fail_early_callback_ech_rewind) {
return ssl_select_cert_disable_ech;
return ssl_select_cert_disable_ech;
}
const char *server_name =
SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
const char *server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (config->expect_no_server_name && server_name != nullptr) {
fprintf(stderr,
"Expected no server name but got %s.\n",
server_name);
fprintf(stderr, "Expected no server name but got %s.\n", server_name);
return ssl_select_cert_error;
}
@@ -1797,11 +1841,8 @@ static int SendQuicAlert(SSL *ssl, enum ssl_encryption_level_t level,
}
static const SSL_QUIC_METHOD g_quic_method = {
SetQuicReadSecret,
SetQuicWriteSecret,
AddQuicHandshakeData,
FlushQuicFlight,
SendQuicAlert,
SetQuicReadSecret, SetQuicWriteSecret, AddQuicHandshakeData,
FlushQuicFlight, SendQuicAlert,
};
static bool MaybeInstallCertCompressionAlg(
@@ -2219,7 +2260,7 @@ bssl::UniquePtr<SSL> TestConfig::NewSSL(
return nullptr;
}
if (wpa_202304 && !SSL_set_compliance_policy(
ssl.get(), ssl_compliance_policy_wpa3_192_202304)) {
ssl.get(), ssl_compliance_policy_wpa3_192_202304)) {
fprintf(stderr, "SSL_set_compliance_policy failed\n");
return nullptr;
}
@@ -2314,8 +2355,7 @@ bssl::UniquePtr<SSL> TestConfig::NewSSL(
ssl.get(), SSL_is_dtls(ssl.get()) ? DTLS1_VERSION : TLS1_VERSION)) {
return nullptr;
}
if (min_version != 0 &&
!SSL_set_min_proto_version(ssl.get(), min_version)) {
if (min_version != 0 && !SSL_set_min_proto_version(ssl.get(), min_version)) {
return nullptr;
}
// TODO(crbug.com/42290594): Remove this once DTLS 1.3 is enabled by default.
@@ -2323,8 +2363,7 @@ bssl::UniquePtr<SSL> TestConfig::NewSSL(
!SSL_set_max_proto_version(ssl.get(), DTLS1_3_VERSION)) {
return nullptr;
}
if (max_version != 0 &&
!SSL_set_max_proto_version(ssl.get(), max_version)) {
if (max_version != 0 && !SSL_set_max_proto_version(ssl.get(), max_version)) {
return nullptr;
}
if (mtu != 0) {
+11 -2
View File
@@ -25,7 +25,11 @@
#include "test_state.h"
enum class CredentialConfigType { kX509, kDelegated };
enum class CredentialConfigType {
kX509,
kDelegated,
kSPAKE2PlusV1,
};
struct CredentialConfig {
CredentialConfigType type;
@@ -35,6 +39,11 @@ struct CredentialConfig {
std::vector<uint8_t> delegated_credential;
std::vector<uint8_t> ocsp_response;
std::vector<uint8_t> signed_cert_timestamps;
std::vector<uint8_t> pake_context;
std::vector<uint8_t> pake_client_id;
std::vector<uint8_t> pake_server_id;
std::vector<uint8_t> pake_password;
bool wrong_pake_role = false;
};
struct TestConfig {
@@ -225,7 +234,7 @@ struct TestConfig {
std::vector<CredentialConfig> credentials;
int private_key_delay_ms = 0;
std::vector<const char*> handshaker_args;
std::vector<const char *> handshaker_args;
bssl::UniquePtr<SSL_CTX> SetupCtx(SSL_CTX *old_ctx) const;
+69 -20
View File
@@ -251,7 +251,8 @@ static enum ssl_hs_wait_t do_read_hello_retry_request(SSL_HANDSHAKE *hs) {
// The ECH extension, if present, was already parsed by
// |check_ech_confirmation|.
SSLExtension cookie(TLSEXT_TYPE_cookie), key_share(TLSEXT_TYPE_key_share),
SSLExtension cookie(TLSEXT_TYPE_cookie),
key_share(TLSEXT_TYPE_key_share, !hs->key_share_bytes.empty()),
supported_versions(TLSEXT_TYPE_supported_versions),
ech_unused(TLSEXT_TYPE_encrypted_client_hello,
hs->selected_ech_config || hs->config->ech_grease_enabled);
@@ -284,6 +285,10 @@ static enum ssl_hs_wait_t do_read_hello_retry_request(SSL_HANDSHAKE *hs) {
}
if (key_share.present) {
// If offering PAKE, we won't send key_share extensions, in which case we
// would have rejected key_share from the peer.
assert(!hs->pake_prover);
uint16_t group_id;
if (!CBS_get_u16(&key_share.data, &group_id) ||
CBS_len(&key_share.data) != 0) {
@@ -421,12 +426,14 @@ static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) {
ssl_session_get_type(ssl->session.get()) ==
SSLSessionType::kPreSharedKey &&
ssl->s3->ech_status != ssl_ech_rejected;
SSLExtension key_share(TLSEXT_TYPE_key_share),
SSLExtension key_share(TLSEXT_TYPE_key_share, hs->key_shares[0] != nullptr),
pake_share(TLSEXT_TYPE_pake, hs->pake_prover != nullptr),
pre_shared_key(TLSEXT_TYPE_pre_shared_key, pre_shared_key_allowed),
supported_versions(TLSEXT_TYPE_supported_versions);
if (!ssl_parse_extensions(&server_hello.extensions, &alert,
{&key_share, &pre_shared_key, &supported_versions},
/*ignore_unknown=*/false)) {
if (!ssl_parse_extensions(
&server_hello.extensions, &alert,
{&key_share, &pre_shared_key, &supported_versions, &pake_share},
/*ignore_unknown=*/false)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
return ssl_hs_error;
}
@@ -442,6 +449,39 @@ static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) {
return ssl_hs_error;
}
// The combination of ServerHello extensions determines the kind of handshake
// that the server selected. Check for invalid combinations.
// pake replaces key_share and may not be used with pre_shared_key.
if (pake_share.present && (key_share.present || pre_shared_key.present)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
return ssl_hs_error;
}
// In PAKE mode, we require a PAKE handshake and do not support resumption.
if (hs->pake_prover != nullptr && !pake_share.present) {
OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_EXTENSION);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_MISSING_EXTENSION);
return ssl_hs_error;
}
// In non-PAKE modes, we require per-connection forward secrecy and do not
// support psk_ke.
if (hs->pake_prover == nullptr && !key_share.present) {
OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_KEY_SHARE);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_MISSING_EXTENSION);
return ssl_hs_error;
}
// The above imples only one of three handshake forms will be allowed. The
// checks for unsolicited extensions ensure the server did not select
// something we cannot respond to.
assert(
// Full handshake
(key_share.present && !pake_share.present && !pre_shared_key.present) ||
// PSK/resumption handshake
(key_share.present && !pake_share.present && pre_shared_key.present) ||
// PAKE handshake
(!key_share.present && pake_share.present && !pre_shared_key.present));
alert = SSL_AD_DECODE_ERROR;
if (pre_shared_key.present) {
if (!ssl_ext_pre_shared_key_parse_serverhello(hs, &alert,
@@ -500,24 +540,29 @@ static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) {
return ssl_hs_error;
}
if (!key_share.present) {
// We do not support psk_ke and thus always require a key share.
OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_KEY_SHARE);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_MISSING_EXTENSION);
return ssl_hs_error;
}
// Resolve ECDHE and incorporate it into the secret.
Array<uint8_t> dhe_secret;
// Resolve ECDHE or PAKE and incorporate it into the secret.
Array<uint8_t> shared_secret;
alert = SSL_AD_DECODE_ERROR;
if (!ssl_ext_key_share_parse_serverhello(hs, &dhe_secret, &alert,
&key_share.data)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
if (key_share.present) {
if (!ssl_ext_key_share_parse_serverhello(hs, &shared_secret, &alert,
&key_share.data)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
return ssl_hs_error;
}
} else if (pake_share.present) {
if (!ssl_ext_pake_parse_serverhello(hs, &shared_secret, &alert,
&pake_share.data)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
return ssl_hs_error;
}
} else {
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_hs_error;
}
if (!tls13_advance_key_schedule(hs, dhe_secret) || //
!ssl_hash_message(hs, msg) || //
if (!tls13_advance_key_schedule(hs, shared_secret) || //
!ssl_hash_message(hs, msg) || //
!tls13_derive_handshake_secrets(hs)) {
return ssl_hs_error;
}
@@ -638,6 +683,11 @@ static enum ssl_hs_wait_t do_read_certificate_request(SSL_HANDSHAKE *hs) {
return ssl_hs_ok;
}
if (hs->pake_prover) {
hs->tls13_state = state_read_server_finished;
return ssl_hs_ok;
}
SSLMessage msg;
if (!ssl->method->get_message(ssl, &msg)) {
return ssl_hs_read_message;
@@ -649,7 +699,6 @@ static enum ssl_hs_wait_t do_read_certificate_request(SSL_HANDSHAKE *hs) {
return ssl_hs_ok;
}
SSLExtension sigalgs(TLSEXT_TYPE_signature_algorithms),
ca(TLSEXT_TYPE_certificate_authorities);
CBS body = msg.body, context, extensions, supported_signature_algorithms;
+128 -27
View File
@@ -43,6 +43,30 @@ static const uint8_t kZeroes[EVP_MAX_MD_SIZE] = {0};
// See RFC 8446, section 8.3.
static const int32_t kMaxTicketAgeSkewSeconds = 60;
static bool resolve_pake_secret(SSL_HANDSHAKE *hs) {
uint8_t verifier_share[spake2plus::kShareSize];
uint8_t verifier_confirm[spake2plus::kConfirmSize];
uint8_t shared_secret[spake2plus::kSecretSize];
if (!hs->pake_verifier->ProcessProverShare(verifier_share, verifier_confirm,
shared_secret,
hs->pake_share->pake_message)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
ssl_send_alert(hs->ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
return false;
}
bssl::ScopedCBB cbb;
if (!CBB_init(cbb.get(), sizeof(verifier_share) + sizeof(verifier_confirm)) ||
!CBB_add_bytes(cbb.get(), verifier_share, sizeof(verifier_share)) ||
!CBB_add_bytes(cbb.get(), verifier_confirm, sizeof(verifier_confirm)) ||
!CBBFinishArray(cbb.get(), &hs->pake_share_bytes)) {
return false;
}
return tls13_advance_key_schedule(
hs, MakeConstSpan(shared_secret, sizeof(shared_secret)));
}
static bool resolve_ecdhe_secret(SSL_HANDSHAKE *hs,
const SSL_CLIENT_HELLO *client_hello) {
SSL *const ssl = hs->ssl;
@@ -131,7 +155,10 @@ static bool add_new_session_tickets(SSL_HANDSHAKE *hs, bool *out_sent_tickets) {
!hs->accept_psk_mode ||
// We only implement stateless resumption in TLS 1.3, so skip sending
// tickets if disabled.
(SSL_get_options(ssl) & SSL_OP_NO_TICKET)) {
(SSL_get_options(ssl) & SSL_OP_NO_TICKET) ||
// Don't send tickets for PAKE connections. We don't support resumption
// with PAKEs.
hs->pake_verifier != nullptr) {
*out_sent_tickets = false;
return true;
}
@@ -220,8 +247,9 @@ static bool add_new_session_tickets(SSL_HANDSHAKE *hs, bool *out_sent_tickets) {
return true;
}
static bool check_credential(SSL_HANDSHAKE *hs, const SSL_CREDENTIAL *cred,
uint16_t *out_sigalg) {
static bool check_signature_credential(SSL_HANDSHAKE *hs,
const SSL_CREDENTIAL *cred,
uint16_t *out_sigalg) {
switch (cred->type) {
case SSLCredentialType::kX509:
break;
@@ -234,9 +262,12 @@ static bool check_credential(SSL_HANDSHAKE *hs, const SSL_CREDENTIAL *cred,
return false;
}
break;
default:
OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
return false;
}
// All currently supported credentials require a signature. If |cred| is a
// If we reach here then the credential requires a signature. If |cred| is a
// delegated credential, this also checks that the peer supports delegated
// credentials and matched |dc_cert_verify_algorithm|.
if (!tls1_choose_signature_algorithm(hs, cred, out_sigalg)) {
@@ -247,6 +278,21 @@ static bool check_credential(SSL_HANDSHAKE *hs, const SSL_CREDENTIAL *cred,
return ssl_credential_matches_requested_issuers(hs, cred);
}
static bool check_pake_credential(SSL_HANDSHAKE *hs,
const SSL_CREDENTIAL *cred) {
assert(cred->type == SSLCredentialType::kSPAKE2PlusV1Server);
// Look for a client PAKE share that matches |cred|.
if (hs->pake_share == nullptr ||
hs->pake_share->named_pake != SSL_PAKE_SPAKE2PLUSV1 ||
hs->pake_share->client_identity != Span(cred->client_identity) ||
hs->pake_share->server_identity != Span(cred->server_identity)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_PAKE_MISMATCH);
return false;
}
return true;
}
static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
// At this point, most ClientHello extensions have already been processed by
// the common handshake logic. Resolve the remaining non-PSK parameters.
@@ -284,11 +330,27 @@ static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
// Select the credential to use.
for (SSL_CREDENTIAL *cred : creds) {
ERR_clear_error();
uint16_t sigalg;
if (check_credential(hs, cred, &sigalg)) {
hs->credential = UpRef(cred);
hs->signature_algorithm = sigalg;
break;
if (cred->type == SSLCredentialType::kSPAKE2PlusV1Server) {
if (check_pake_credential(hs, cred)) {
hs->credential = UpRef(cred);
hs->pake_verifier = MakeUnique<spake2plus::Verifier>();
if (hs->pake_verifier == nullptr ||
!hs->pake_verifier->Init(cred->pake_context, cred->client_identity,
cred->server_identity,
cred->password_verifier_w0,
cred->registration_record)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_hs_error;
}
break;
}
} else {
uint16_t sigalg;
if (check_signature_credential(hs, cred, &sigalg)) {
hs->credential = UpRef(cred);
hs->signature_algorithm = sigalg;
break;
}
}
}
if (hs->credential == nullptr) {
@@ -360,6 +422,12 @@ static enum ssl_ticket_aead_result_t select_session(
return ssl_ticket_aead_ignore_ticket;
}
// We do not currently support resumption with PAKEs.
if (hs->credential != nullptr &&
hs->credential->type == SSLCredentialType::kSPAKE2PlusV1Server) {
return ssl_ticket_aead_ignore_ticket;
}
// TLS 1.3 session tickets are renewed separately as part of the
// NewSessionTicket.
bool unused_renew;
@@ -485,19 +553,24 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
// Record connection properties in the new session.
hs->new_session->cipher = hs->new_cipher;
if (!tls1_get_shared_group(hs, &hs->new_session->group_id)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_GROUP);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
return ssl_hs_error;
}
// Determine if we need HelloRetryRequest.
bool found_key_share;
if (!ssl_ext_key_share_parse_clienthello(hs, &found_key_share,
/*out_key_share=*/nullptr, &alert,
&client_hello)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
return ssl_hs_error;
// If using key shares, resolve the supported group and determine if we need
// HelloRetryRequest.
bool need_hrr = false;
if (hs->pake_verifier == nullptr) {
if (!tls1_get_shared_group(hs, &hs->new_session->group_id)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_GROUP);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
return ssl_hs_error;
}
bool found_key_share;
if (!ssl_ext_key_share_parse_clienthello(hs, &found_key_share,
/*out_key_share=*/nullptr, &alert,
&client_hello)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
return ssl_hs_error;
}
need_hrr = !found_key_share;
}
// Determine if we're negotiating 0-RTT.
@@ -527,7 +600,7 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
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 if (!found_key_share) {
} else if (need_hrr) {
ssl->s3->early_data_reason = ssl_early_data_hello_retry_request;
} else {
// |ssl_session_is_resumable| forbids cross-cipher resumptions even if the
@@ -593,7 +666,7 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
ssl->s3->skip_early_data = true;
}
if (!found_key_share) {
if (need_hrr) {
ssl->method->next_message(ssl);
if (!hs->transcript.UpdateForHelloRetryRequest()) {
return ssl_hs_error;
@@ -602,8 +675,22 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
return ssl_hs_ok;
}
if (!resolve_ecdhe_secret(hs, &client_hello)) {
return ssl_hs_error;
if (hs->pake_verifier) {
assert(!ssl->s3->session_reused);
// Revealing the PAKE share (notably confirmV) allows the client to confirm
// one PAKE guess, so we must deduct from the brute force limit.
if (!hs->credential->ClaimPAKEAttempt()) {
OPENSSL_PUT_ERROR(SSL, SSL_R_PAKE_EXHAUSTED);
ssl_send_alert(hs->ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_hs_error;
}
if (!resolve_pake_secret(hs)) {
return ssl_hs_error;
}
} else {
if (!resolve_ecdhe_secret(hs, &client_hello)) {
return ssl_hs_error;
}
}
ssl->method->next_message(ssl);
@@ -618,6 +705,9 @@ static enum ssl_hs_wait_t do_send_hello_retry_request(SSL_HANDSHAKE *hs) {
return ssl_hs_hints_ready;
}
// Although a server could HelloRetryRequest with PAKEs to request a cookie,
// we never do so.
assert(hs->pake_verifier == nullptr);
ScopedCBB cbb;
CBB body, session_id, extensions;
if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_SERVER_HELLO) ||
@@ -775,6 +865,9 @@ static enum ssl_hs_wait_t do_read_second_client_hello(SSL_HANDSHAKE *hs) {
}
}
// Although a server could HelloRetryRequest with PAKEs to request a cookie,
// we never do so.
assert(hs->pake_verifier == nullptr);
if (!resolve_ecdhe_secret(hs, &client_hello)) {
return ssl_hs_error;
}
@@ -832,6 +925,7 @@ static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) {
!CBB_add_u8(&body, 0) ||
!CBB_add_u16_length_prefixed(&body, &extensions) ||
!ssl_ext_pre_shared_key_add_serverhello(hs, &extensions) ||
!ssl_ext_pake_add_serverhello(hs, &extensions) ||
!ssl_ext_key_share_add_serverhello(hs, &extensions) ||
!ssl_ext_supported_versions_add_serverhello(hs, &extensions) ||
!ssl->method->finish_message(ssl, cbb.get(), &server_hello)) {
@@ -882,7 +976,7 @@ static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) {
return ssl_hs_error;
}
if (!ssl->s3->session_reused) {
if (!ssl->s3->session_reused && !hs->pake_verifier) {
// Determine whether to request a client certificate.
hs->cert_request = !!(hs->config->verify_mode & SSL_VERIFY_PEER);
// Only request a certificate if Channel ID isn't negotiated.
@@ -926,7 +1020,7 @@ static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) {
}
// Send the server Certificate message, if necessary.
if (!ssl->s3->session_reused) {
if (!ssl->s3->session_reused && !hs->pake_verifier) {
if (!tls13_add_certificate(hs)) {
return ssl_hs_error;
}
@@ -1274,6 +1368,13 @@ static enum ssl_hs_wait_t do_read_client_finished(SSL_HANDSHAKE *hs) {
hs->tls13_state = state13_done;
}
if (hs->credential != nullptr &&
hs->credential->type == SSLCredentialType::kSPAKE2PlusV1Server) {
// The client has now confirmed that it does know the correct password, so
// this connection no longer counts towards the brute force limit.
hs->credential->RestorePAKEAttempt();
}
ssl->method->next_message(ssl);
if (SSL_is_dtls(ssl)) {
ssl->method->schedule_ack(ssl);