Implement RFC 9258 as a client Follow-up CLs will: - Address an API edge case around a PSK-or-certs connection that sees a CertificateRequest and aims to decline to send a certificate. - Add server support Bug: 369963041 Change-Id: Ieef6bd3d2808fb5615dc8aaf66d7c3045440493d Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/89475 Reviewed-by: Lily Chen <chlily@google.com> Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata index 01c4ca6..b2028a4 100644 --- a/crypto/err/ssl.errordata +++ b/crypto/err/ssl.errordata
@@ -94,6 +94,7 @@ SSL,159,INVALID_MESSAGE SSL,320,INVALID_OUTER_EXTENSION SSL,251,INVALID_OUTER_RECORD_TYPE +SSL,331,INVALID_PSK_FOR_CONNECTION SSL,269,INVALID_SCT_LIST SSL,295,INVALID_SIGNATURE_ALGORITHM SSL,324,INVALID_SPAKE2PLUSV1_VALUE
diff --git a/gen/crypto/err_data.cc b/gen/crypto/err_data.cc index 1445792..4d7c79a 100644 --- a/gen/crypto/err_data.cc +++ b/gen/crypto/err_data.cc
@@ -202,51 +202,51 @@ 0x283500f7, 0x28358cc1, 0x2836099a, - 0x2c32341d, + 0x2c323438, 0x2c3293f7, - 0x2c33342b, - 0x2c33b43d, - 0x2c343451, - 0x2c34b463, - 0x2c35347e, - 0x2c35b490, - 0x2c3634c0, + 0x2c333446, + 0x2c33b458, + 0x2c34346c, + 0x2c34b47e, + 0x2c353499, + 0x2c35b4ab, + 0x2c3634db, 0x2c36833a, - 0x2c3734cd, - 0x2c37b4f9, - 0x2c383537, - 0x2c38b54e, - 0x2c39356c, - 0x2c39b57c, - 0x2c3a358e, - 0x2c3ab5a2, - 0x2c3b35b3, - 0x2c3bb5d2, + 0x2c3734e8, + 0x2c37b514, + 0x2c383552, + 0x2c38b569, + 0x2c393587, + 0x2c39b597, + 0x2c3a35a9, + 0x2c3ab5bd, + 0x2c3b35ce, + 0x2c3bb5ed, 0x2c3c1409, 0x2c3c941f, - 0x2c3d3617, + 0x2c3d3632, 0x2c3d9438, - 0x2c3e3641, - 0x2c3eb64f, - 0x2c3f3667, - 0x2c3fb67f, - 0x2c4036a9, + 0x2c3e365c, + 0x2c3eb66a, + 0x2c3f3682, + 0x2c3fb69a, + 0x2c4036c4, 0x2c4092ec, - 0x2c4136ba, - 0x2c41b6cd, + 0x2c4136d5, + 0x2c41b6e8, 0x2c4212b2, - 0x2c42b6de, + 0x2c42b6f9, 0x2c43076d, - 0x2c43b5c4, - 0x2c44350c, - 0x2c44b68c, - 0x2c4534a3, - 0x2c45b4df, - 0x2c46355c, - 0x2c46b5e6, - 0x2c4735fb, - 0x2c47b634, - 0x2c48351e, + 0x2c43b5df, + 0x2c443527, + 0x2c44b6a7, + 0x2c4534be, + 0x2c45b4fa, + 0x2c463577, + 0x2c46b601, + 0x2c473616, + 0x2c47b64f, + 0x2c483539, 0x30320000, 0x30328015, 0x3033001f, @@ -446,156 +446,156 @@ 0x404ea122, 0x404f21f5, 0x404fa26b, - 0x405022f5, - 0x4050a309, - 0x40512356, - 0x40522366, - 0x4052a38a, - 0x405323a2, - 0x4053a3b5, - 0x405423ca, - 0x4054a3ed, - 0x40552418, - 0x4055a455, - 0x4056247a, - 0x4056a493, - 0x405724ab, - 0x4057a4be, - 0x405824d3, - 0x4058a4fa, - 0x40592529, - 0x4059a569, - 0x405aa57d, - 0x405b2595, - 0x405ba5a6, - 0x405c25b9, - 0x405ca5f8, - 0x405d2605, - 0x405da62a, - 0x405e2668, + 0x40502310, + 0x4050a324, + 0x40512371, + 0x40522381, + 0x4052a3a5, + 0x405323bd, + 0x4053a3d0, + 0x405423e5, + 0x4054a408, + 0x40552433, + 0x4055a470, + 0x40562495, + 0x4056a4ae, + 0x405724c6, + 0x4057a4d9, + 0x405824ee, + 0x4058a515, + 0x40592544, + 0x4059a584, + 0x405aa598, + 0x405b25b0, + 0x405ba5c1, + 0x405c25d4, + 0x405ca613, + 0x405d2620, + 0x405da645, + 0x405e2683, 0x405e8afe, - 0x405f26b7, - 0x405fa6c4, - 0x406026d2, - 0x4060a6f4, - 0x40612768, - 0x4061a7a0, - 0x406227b7, - 0x4062a7c8, - 0x40632815, - 0x4063a82a, - 0x40642841, - 0x4064a86d, - 0x40652888, - 0x4065a89f, - 0x406628b7, - 0x4066a8e1, - 0x4067290c, - 0x4067a951, - 0x40682999, - 0x4068a9ba, - 0x406929ec, - 0x4069aa1a, - 0x406a2a3b, - 0x406aaa5b, - 0x406b2be3, - 0x406bac06, - 0x406c2c1c, - 0x406caf26, - 0x406d2f55, - 0x406daf7d, - 0x406e2fab, - 0x406eaff8, - 0x406f3051, - 0x406fb089, - 0x4070309c, - 0x4070b0b9, + 0x405f26d2, + 0x405fa6df, + 0x406026ed, + 0x4060a70f, + 0x40612783, + 0x4061a7bb, + 0x406227d2, + 0x4062a7e3, + 0x40632830, + 0x4063a845, + 0x4064285c, + 0x4064a888, + 0x406528a3, + 0x4065a8ba, + 0x406628d2, + 0x4066a8fc, + 0x40672927, + 0x4067a96c, + 0x406829b4, + 0x4068a9d5, + 0x40692a07, + 0x4069aa35, + 0x406a2a56, + 0x406aaa76, + 0x406b2bfe, + 0x406bac21, + 0x406c2c37, + 0x406caf41, + 0x406d2f70, + 0x406daf98, + 0x406e2fc6, + 0x406eb013, + 0x406f306c, + 0x406fb0a4, + 0x407030b7, + 0x4070b0d4, 0x4071084d, - 0x4071b0cb, - 0x407230de, - 0x4072b114, - 0x4073312c, + 0x4071b0e6, + 0x407230f9, + 0x4072b12f, + 0x40733147, 0x40739621, - 0x40743140, - 0x4074b15a, - 0x4075316b, - 0x4075b17f, - 0x4076318d, + 0x4074315b, + 0x4074b175, + 0x40753186, + 0x4075b19a, + 0x407631a8, 0x407693af, - 0x407731b2, - 0x4077b20e, - 0x40783229, - 0x4078b262, - 0x40793279, - 0x4079b28f, - 0x407a32bb, - 0x407ab2ce, - 0x407b32e3, - 0x407bb2f5, - 0x407c3326, - 0x407cb32f, - 0x407d29d5, + 0x407731cd, + 0x4077b229, + 0x40783244, + 0x4078b27d, + 0x40793294, + 0x4079b2aa, + 0x407a32d6, + 0x407ab2e9, + 0x407b32fe, + 0x407bb310, + 0x407c3341, + 0x407cb34a, + 0x407d29f0, 0x407da293, - 0x407e323e, - 0x407ea50a, + 0x407e3259, + 0x407ea525, 0x407f1e86, 0x407fa069, 0x40802205, 0x40809eae, - 0x40812378, + 0x40812393, 0x4081a170, - 0x40822f96, + 0x40822fb1, 0x40829c01, - 0x408324e5, - 0x4083a852, + 0x40832500, + 0x4083a86d, 0x40841ed2, - 0x4084a542, - 0x408525ca, - 0x4085a72f, - 0x4086264a, - 0x4086a2ad, - 0x40872fdc, - 0x4087a77d, + 0x4084a55d, + 0x408525e5, + 0x4085a74a, + 0x40862665, + 0x4086a2c8, + 0x40872ff7, + 0x4087a798, 0x40881c3f, - 0x4088a964, + 0x4088a97f, 0x40891c8e, 0x40899c1b, - 0x408a2c54, + 0x408a2c6f, 0x408a9a39, - 0x408b330a, - 0x408bb066, - 0x408c25da, + 0x408b3325, + 0x408bb081, + 0x408c25f5, 0x408d1fba, 0x408d9f04, 0x408e20ea, - 0x408ea435, - 0x408f2978, - 0x408fa74b, - 0x4090292d, - 0x4090a61c, - 0x40912c3c, + 0x408ea450, + 0x408f2993, + 0x408fa766, + 0x40902948, + 0x4090a637, + 0x40912c57, 0x40919a71, 0x40921cdb, - 0x4092b017, - 0x409330f7, - 0x4093a2be, + 0x4092b032, + 0x40933112, + 0x4093a2d9, 0x40941ee6, - 0x4094ac6d, - 0x409527d9, - 0x4095b29b, - 0x40962fc3, + 0x4094ac88, + 0x409527f4, + 0x4095b2b6, + 0x40962fde, 0x4096a21e, - 0x4097233e, + 0x40972359, 0x4097a139, 0x40981d3b, - 0x4098a7ed, - 0x40993033, - 0x4099a462, - 0x409a23fb, + 0x4098a808, + 0x4099304e, + 0x4099a47d, + 0x409a2416, 0x409a9a55, 0x409b1f40, 0x409b9f6b, - 0x409c31f0, + 0x409c320b, 0x409c9f93, 0x409d21da, 0x409da186, @@ -606,49 +606,50 @@ 0x40a0227b, 0x40a0a153, 0x40a121a1, - 0x40a1a556, - 0x40a222da, - 0x40a2a6a8, - 0x40a3271c, - 0x40a3b1d4, - 0x40a42324, + 0x40a1a571, + 0x40a222f5, + 0x40a2a6c3, + 0x40a32737, + 0x40a3b1ef, + 0x40a4233f, 0x40a4a1b8, 0x40a51ec2, - 0x41f42b0e, - 0x41f92ba0, - 0x41fe2a93, - 0x41fead49, - 0x41ff2e77, - 0x42032b27, - 0x42082b49, - 0x4208ab85, - 0x42092a77, - 0x4209abbf, - 0x420a2ace, - 0x420aaaae, - 0x420b2aee, - 0x420bab67, - 0x420c2e93, - 0x420cac7d, - 0x420d2d30, - 0x420dad67, - 0x42122d9a, - 0x42172e5a, - 0x4217addc, - 0x421c2dfe, - 0x421f2db9, - 0x42212f0b, - 0x42262e3d, - 0x422b2ee9, - 0x422bad0b, - 0x422c2ecb, - 0x422cacbe, - 0x422d2c97, - 0x422daeaa, - 0x422e2cea, - 0x42302e19, - 0x4230ad81, - 0x42312689, + 0x40a5a2ad, + 0x41f42b29, + 0x41f92bbb, + 0x41fe2aae, + 0x41fead64, + 0x41ff2e92, + 0x42032b42, + 0x42082b64, + 0x4208aba0, + 0x42092a92, + 0x4209abda, + 0x420a2ae9, + 0x420aaac9, + 0x420b2b09, + 0x420bab82, + 0x420c2eae, + 0x420cac98, + 0x420d2d4b, + 0x420dad82, + 0x42122db5, + 0x42172e75, + 0x4217adf7, + 0x421c2e19, + 0x421f2dd4, + 0x42212f26, + 0x42262e58, + 0x422b2f04, + 0x422bad26, + 0x422c2ee6, + 0x422cacd9, + 0x422d2cb2, + 0x422daec5, + 0x422e2d05, + 0x42302e34, + 0x4230ad9c, + 0x423126a4, 0x44320778, 0x44328787, 0x44330793, @@ -704,71 +705,71 @@ 0x4c419501, 0x4c42166a, 0x4c429449, - 0x503236f0, - 0x5032b6ff, - 0x5033370a, - 0x5033b71a, - 0x50343733, - 0x5034b74d, - 0x5035375b, - 0x5035b771, - 0x50363783, - 0x5036b799, - 0x503737b2, - 0x5037b7c5, - 0x503837dd, - 0x5038b7ee, - 0x50393803, - 0x5039b817, - 0x503a3837, - 0x503ab84d, - 0x503b3865, - 0x503bb877, - 0x503c3893, - 0x503cb8aa, - 0x503d38c3, - 0x503db8d9, - 0x503e38e6, - 0x503eb8fc, - 0x503f390e, + 0x5032370b, + 0x5032b71a, + 0x50333725, + 0x5033b735, + 0x5034374e, + 0x5034b768, + 0x50353776, + 0x5035b78c, + 0x5036379e, + 0x5036b7b4, + 0x503737cd, + 0x5037b7e0, + 0x503837f8, + 0x5038b809, + 0x5039381e, + 0x5039b832, + 0x503a3852, + 0x503ab868, + 0x503b3880, + 0x503bb892, + 0x503c38ae, + 0x503cb8c5, + 0x503d38de, + 0x503db8f4, + 0x503e3901, + 0x503eb917, + 0x503f3929, 0x503f83b3, - 0x50403921, - 0x5040b931, - 0x5041394b, - 0x5041b95a, - 0x50423974, - 0x5042b991, - 0x504339a1, - 0x5043b9b1, - 0x504439ce, + 0x5040393c, + 0x5040b94c, + 0x50413966, + 0x5041b975, + 0x5042398f, + 0x5042b9ac, + 0x504339bc, + 0x5043b9cc, + 0x504439e9, 0x50448469, - 0x504539e2, - 0x5045ba00, - 0x50463a13, - 0x5046ba29, - 0x50473a3b, - 0x5047ba50, - 0x50483a76, - 0x5048ba84, - 0x50493a97, - 0x5049baac, - 0x504a3ac2, - 0x504abad2, - 0x504b3af2, - 0x504bbb05, - 0x504c3b28, - 0x504cbb56, - 0x504d3b83, - 0x504dbba0, - 0x504e3bbb, - 0x504ebbd7, - 0x504f3be9, - 0x504fbc00, - 0x50503c0f, + 0x504539fd, + 0x5045ba1b, + 0x50463a2e, + 0x5046ba44, + 0x50473a56, + 0x5047ba6b, + 0x50483a91, + 0x5048ba9f, + 0x50493ab2, + 0x5049bac7, + 0x504a3add, + 0x504abaed, + 0x504b3b0d, + 0x504bbb20, + 0x504c3b43, + 0x504cbb71, + 0x504d3b9e, + 0x504dbbbb, + 0x504e3bd6, + 0x504ebbf2, + 0x504f3c04, + 0x504fbc1b, + 0x50503c2a, 0x50508729, - 0x50513c22, - 0x5051b9c0, - 0x50523b68, + 0x50513c3d, + 0x5051b9db, + 0x50523b83, 0x58321011, 0x68320fd3, 0x68328d2b, @@ -813,19 +814,19 @@ 0x7c3212c8, 0x80321514, 0x80328090, - 0x803333ec, + 0x80333407, 0x803380b9, - 0x803433fb, - 0x8034b363, - 0x80353381, - 0x8035b40f, - 0x803633c3, - 0x8036b372, - 0x803733b5, - 0x8037b350, - 0x803833d6, - 0x8038b392, - 0x803933a7, + 0x80343416, + 0x8034b37e, + 0x8035339c, + 0x8035b42a, + 0x803633de, + 0x8036b38d, + 0x803733d0, + 0x8037b36b, + 0x803833f1, + 0x8038b3ad, + 0x803933c2, 0x84320bb0, 0x84328bc9, }; @@ -1266,6 +1267,7 @@ "INVALID_MESSAGE\0" "INVALID_OUTER_EXTENSION\0" "INVALID_OUTER_RECORD_TYPE\0" + "INVALID_PSK_FOR_CONNECTION\0" "INVALID_SCT_LIST\0" "INVALID_SIGNATURE_ALGORITHM\0" "INVALID_SPAKE2PLUSV1_VALUE\0"
diff --git a/include/openssl/prefix_symbols.h b/include/openssl/prefix_symbols.h index bf94685..86bf926 100644 --- a/include/openssl/prefix_symbols.h +++ b/include/openssl/prefix_symbols.h
@@ -1837,6 +1837,7 @@ #pragma redefine_extname SSL_CREDENTIAL_get_ex_new_index BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_get_ex_new_index)) #pragma redefine_extname SSL_CREDENTIAL_is_complete BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_is_complete)) #pragma redefine_extname SSL_CREDENTIAL_new_delegated BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_new_delegated)) +#pragma redefine_extname SSL_CREDENTIAL_new_pre_shared_key BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_new_pre_shared_key)) #pragma redefine_extname SSL_CREDENTIAL_new_spake2plusv1_client BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_new_spake2plusv1_client)) #pragma redefine_extname SSL_CREDENTIAL_new_spake2plusv1_server BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_new_spake2plusv1_server)) #pragma redefine_extname SSL_CREDENTIAL_new_x509 BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_new_x509)) @@ -4898,6 +4899,7 @@ #define SSL_CREDENTIAL_get_ex_new_index BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_get_ex_new_index) #define SSL_CREDENTIAL_is_complete BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_is_complete) #define SSL_CREDENTIAL_new_delegated BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_new_delegated) +#define SSL_CREDENTIAL_new_pre_shared_key BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_new_pre_shared_key) #define SSL_CREDENTIAL_new_spake2plusv1_client BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_new_spake2plusv1_client) #define SSL_CREDENTIAL_new_spake2plusv1_server BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_new_spake2plusv1_server) #define SSL_CREDENTIAL_new_x509 BORINGSSL_ADD_PREFIX(SSL_CREDENTIAL_new_x509)
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 430273e..fcfd67d 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h
@@ -3703,11 +3703,71 @@ SSL *ssl); -// Pre-shared keys. +// TLS 1.3 pre-shared keys. // -// Connections may be configured with PSK (Pre-Shared Key) cipher suites. These -// authenticate using out-of-band pre-shared keys rather than certificates. See -// RFC 4279. +// TLS 1.3 connections can be authenticated using external pre-shared keys +// (PSKs), as described in RFC 9258. These are represented in BoringSSL with +// |SSL_CREDENTIAL| objects. +// +// BoringSSL only implements the PSK importer interface from RFC 9258. The +// underlying protocol- and cipher-specific TLS 1.3 PSK mechanism is not exposed +// directly. + +// SSL_CREDENTIAL_new_pre_shared_key returns a newly-allocated |SSL_CREDENTIAL| +// representing an external pre-shared key, as described in RFC 9258, or NULL on +// error. |key| is the base key, |id| is the external identity, and |md| is the +// hash function. The result may be added to the credential list with +// |SSL_CTX_add1_credential| or |SSL_add1_credential|. |context| is the context +// string to use when importing to TLS. +// +// Callers can configure the credential list with multiple PSKs, or a mix of +// PSKs and other credentials, in some preference order. Due to protocol +// differences, clients and servers evaluate PSKs in the credential list +// differently: +// +// - As a server, PSK credentials behave similarly to other credentials. Callers +// can configure them dynamically in the certificate callbacks (see +// |SSL_CTX_set_select_certificate_cb| and |SSL_CTX_set_cert_cb|). After those +// callbacks run, BoringSSL will select a credential to use from the list. +// +// - As a client, PSK credentials are offered in the ClientHello and selected by +// the server. This means all PSK credentials must be configured before +// starting the handshake, and the order between PSK and non-PSK credentials +// will be ignored. PSK credentials cannot be configured dynamically in +// callbacks such as |SSL_CTX_set_cert_cb|. +// +// A single connection may be configured to accept only certificate-based +// handshakes, only PSK-based handshakes, or both. The server credential +// determines the kind of handshake, so this is implicitly controlled by the +// credential list as a server: +// +// - If the credential list only contains PSKs, BoringSSL will only consider +// PSK-based handshakes. +// +// - If the credential list contains PSKs and certificates, BoringSSL will +// consider both, depending on the client. +// +// As a client, if any PSK credentials are configured, BoringSSL will only +// accept PSK-based handshakes by default. Setting the verify mode to +// |SSL_VERIFY_PEER| (see |SSL_CTX_set_verify| and |SSL_CTX_set_custom_verify|) +// overrides this behavior and causes BoringSSL to accept either. +// +// In both clients and servers, if a caller configures one or more PSK +// credentials, and calls no certificate-related functions, the connection will +// only accept one of those PSKs. +// +// TODO(crbug.com/369963041): These credentials are currently only implemented +// as a client. Implement these as a server as well. +OPENSSL_EXPORT SSL_CREDENTIAL *SSL_CREDENTIAL_new_pre_shared_key( + const uint8_t *key, size_t key_len, const uint8_t *id, size_t id_len, + const EVP_MD *md, const uint8_t *context, size_t context_len); + + +// TLS 1.2 pre-shared keys. +// +// TLS 1.2 connections may be configured with PSK (Pre-Shared Key) cipher +// suites. These authenticate using out-of-band pre-shared keys rather than +// certificates. See RFC 4279. // // This implementation uses NUL-terminated C strings for identities and identity // hints, so values with a NUL character are not supported. (RFC 4279 does not @@ -6759,6 +6819,7 @@ #define SSL_R_INVALID_TRUST_ANCHOR_LIST 328 #define SSL_R_INVALID_CERTIFICATE_PROPERTY_LIST 329 #define SSL_R_DUPLICATE_GROUP 330 +#define SSL_R_INVALID_PSK_FOR_CONNECTION 331 #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
diff --git a/ssl/extensions.cc b/ssl/extensions.cc index d9683e9..79d89a0 100644 --- a/ssl/extensions.cc +++ b/ssl/extensions.cc
@@ -21,6 +21,7 @@ #include <algorithm> #include <optional> +#include <variant> #include <utility> #include <openssl/aead.h> @@ -1878,40 +1879,68 @@ // // https://tools.ietf.org/html/rfc8446#section-4.2.11 -static bool should_offer_psk(const SSL_HANDSHAKE *hs, - ssl_client_hello_type_t type) { +bool ssl_setup_pre_shared_keys(SSL_HANDSHAKE *hs) { const SSL *const ssl = hs->ssl; - if (hs->max_version < TLS1_3_VERSION || ssl->session == nullptr || - ssl_session_get_type(ssl->session.get()) != - SSLSessionType::kPreSharedKey || - // TODO(https://crbug.com/boringssl/275): Should we synthesize a - // placeholder PSK, at least when we offer early data? Otherwise - // ClientHelloOuter will contain an early_data extension without a - // pre_shared_key extension and potentially break the recovery flow. - type == ssl_client_hello_outer) { + if (hs->max_version < TLS1_3_VERSION) { + return true; + } + + if (SSL_SESSION *session = ssl->session.get(); + session != nullptr && + ssl_session_get_type(session) == SSLSessionType::kPreSharedKey && + !hs->pre_shared_keys.Push(UpRef(session))) { return false; } - // Per RFC 8446 section 4.1.4, skip offering the session if the selected - // cipher in HelloRetryRequest does not match. This avoids performing the - // transcript hash transformation for multiple hashes. - if (ssl->s3->used_hello_retry_request && - ssl->session->cipher->algorithm_prf != hs->new_cipher->algorithm_prf) { - return false; + const uint16_t epsk_protocol = + SSL_is_dtls(ssl) ? DTLS1_3_VERSION : TLS1_3_VERSION; +#if defined(TLS1_4_VERSION) +#error "More imported PSKs may be needed when TLS 1.4 exists" +#endif + for (const auto &cred : hs->config->cert->credentials) { + if (cred->type != SSLCredentialType::kPreSharedKey) { + continue; + } + // We need SHA-256 and SHA-384 variants to cover supported cipher suites. + for (const EVP_MD *md : {EVP_sha256(), EVP_sha384()}) { + std::optional<SSLImportedPSK> psk = + tls13_derive_imported_psk(hs, cred.get(), epsk_protocol, md); + if (!psk || !hs->pre_shared_keys.Push(*std::move(psk))) { + return false; + } + } } return true; } +static bool should_offer_psk(const SSL_HANDSHAKE *hs, + ssl_client_hello_type_t type) { + // TODO(https://crbug.com/boringssl/275): Should we synthesize a placeholder + // PSK, at least when we offer early data? Otherwise ClientHelloOuter will + // contain an early_data extension without a pre_shared_key extension and + // potentially break the recovery flow. + return hs->max_version >= TLS1_3_VERSION && !hs->pre_shared_keys.empty() && + type != ssl_client_hello_outer; +} + static size_t ext_pre_shared_key_clienthello_length( const SSL_HANDSHAKE *hs, ssl_client_hello_type_t type) { - const SSL *const ssl = hs->ssl; if (!should_offer_psk(hs, type)) { return 0; } - size_t binder_len = EVP_MD_size(ssl_session_get_digest(ssl->session.get())); - return 15 + ssl->session->ticket.size() + binder_len; + // extension value, extension length, identities length, binders length. + size_t ret = 2 + 2 + 2 + 2; + for (const auto &psk : hs->pre_shared_keys) { + // identity + ret += 2 + ssl_pre_shared_key_identity(psk).size(); + // obfuscated_ticket_age + ret += 4; + // binder + ret += 1 + EVP_MD_size(ssl_pre_shared_key_hash(psk)); + } + return ret; } // ext_pre_shared_key_add_clienthello writes a pre_shared_key extension to @@ -1936,30 +1965,61 @@ return CBB_flush(out_client_hello); } - OPENSSL_timeval now = ssl_ctx_get_current_time(ssl->ctx.get()); - uint32_t ticket_age = 1000 * (now.tv_sec - ssl->session->time); - uint32_t obfuscated_ticket_age = ticket_age + ssl->session->ticket_age_add; - - const size_t one_binder_len = - EVP_MD_size(ssl_session_get_digest(ssl->session.get())); const size_t len_before = CBB_len(out_extensions); - CBB contents, identity, ticket, binders, binder; + CBB contents, identities, identity, binders, binder; if (!CBB_add_u16(out_extensions, TLSEXT_TYPE_pre_shared_key) || !CBB_add_u16_length_prefixed(out_extensions, &contents) || - !CBB_add_u16_length_prefixed(&contents, &identity) || - !CBB_add_u16_length_prefixed(&identity, &ticket) || - !CBB_add_bytes(&ticket, ssl->session->ticket.data(), - ssl->session->ticket.size()) || - !CBB_add_u32(&identity, obfuscated_ticket_age) || - !CBB_add_u16_length_prefixed(&contents, &binders) || - !CBB_add_u8_length_prefixed(&binders, &binder) || - // Fill in a placeholder zero binder of the appropriate length. It will be - // computed and filled in later after length prefixes are computed. - !CBB_add_zeros(&binder, one_binder_len) || // - !CBB_flush(out_extensions)) { + !CBB_add_u16_length_prefixed(&contents, &identities)) { return false; } - // Sample the length of the PSK extension. + + // Fill in the PSK identities. + for (const auto &psk : hs->pre_shared_keys) { + if (const auto *imported = std::get_if<SSLImportedPSK>(&psk); + imported != nullptr) { + if (!CBB_add_u16_length_prefixed(&identities, &identity) || + !CBB_add_bytes(&identity, imported->imported_identity.data(), + imported->imported_identity.size()) || + !CBB_add_u32(&identities, 0 /* obfuscated_ticket_age */)) { + return false; + } + } else { + const SSL_SESSION *session = std::get<UniquePtr<SSL_SESSION>>(psk).get(); + // At most one PSK will be a session. + assert(session == ssl->session.get()); + OPENSSL_timeval now = ssl_ctx_get_current_time(ssl->ctx.get()); + uint32_t ticket_age = 1000 * (now.tv_sec - session->time); + uint32_t obfuscated_ticket_age = ticket_age + session->ticket_age_add; + if (!CBB_add_u16_length_prefixed(&identities, &identity) || + !CBB_add_bytes(&identity, session->ticket.data(), + session->ticket.size()) || + !CBB_add_u32(&identities, obfuscated_ticket_age)) { + return false; + } + } + } + + // Fill in placeholder zero binders of the appropriate length. They will be + // computed and filled in later after length prefixes are computed. + if (!CBB_add_u16_length_prefixed(&contents, &binders)) { + return false; + } + for (const auto &psk : hs->pre_shared_keys) { + size_t binder_len = EVP_MD_size(ssl_pre_shared_key_hash(psk)); + if (!CBB_add_u8_length_prefixed(&binders, &binder) || + !CBB_add_zeros(&binder, binder_len)) { + return false; + } + } + if (!CBB_flush(&binders)) { + return false; + } + const size_t binders_len = CBB_len(&binders); + + // Finish the PSK extension and save the length. + if (!CBB_flush(out_extensions)) { + return false; + } *out_psk_len = CBB_len(out_extensions) - len_before; // Fill in |out_extensions|'s length prefix. @@ -1967,41 +2027,43 @@ return false; } - // Fill in the binder. + // Fill in binders. + const auto client_hello = CBBAsSpan(out_client_hello); + auto remaining_binders_out = client_hello.last(binders_len); const auto &transcript = type == ssl_client_hello_inner ? hs->inner_transcript : hs->transcript; - auto client_hello = CBBAsSpan(out_client_hello); - auto binder_out = client_hello.last(one_binder_len); - size_t binders_len = 3 + one_binder_len; // u16 and u8 length prefix - size_t actual_binder_len; - if (!tls13_psk_binder(hs, binder_out, &actual_binder_len, ssl->session.get(), - transcript, client_hello, binders_len)) { - return false; + for (const auto &psk : hs->pre_shared_keys) { + // Skip length prefix. + remaining_binders_out = remaining_binders_out.subspan(1); + size_t one_binder_len; + if (!tls13_psk_binder(hs, remaining_binders_out, &one_binder_len, psk, + transcript, client_hello, + 2 /* length */ + binders_len)) { + return false; + } + remaining_binders_out = remaining_binders_out.subspan(one_binder_len); } - assert(actual_binder_len == one_binder_len); - + assert(remaining_binders_out.empty()); return true; } -bool ssl_ext_pre_shared_key_parse_serverhello(SSL_HANDSHAKE *hs, - uint8_t *out_alert, - CBS *contents) { - uint16_t psk_id; - if (!CBS_get_u16(contents, &psk_id) || // +const SSLPreSharedKey *ssl_ext_pre_shared_key_parse_serverhello( + SSL_HANDSHAKE *hs, uint8_t *out_alert, CBS *contents) { + uint16_t selected_identity; + if (!CBS_get_u16(contents, &selected_identity) || // CBS_len(contents) != 0) { OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); *out_alert = SSL_AD_DECODE_ERROR; - return false; + return nullptr; } - // We only advertise one PSK identity, so the only legal index is zero. - if (psk_id != 0) { + if (selected_identity >= hs->pre_shared_keys.size()) { OPENSSL_PUT_ERROR(SSL, SSL_R_PSK_IDENTITY_NOT_FOUND); *out_alert = SSL_AD_UNKNOWN_PSK_IDENTITY; - return false; + return nullptr; } - return true; + return &hs->pre_shared_keys[selected_identity]; } bool ssl_ext_pre_shared_key_parse_clienthello(
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc index e8ccacc..23dc602 100644 --- a/ssl/handshake_client.cc +++ b/ssl/handshake_client.cc
@@ -328,8 +328,26 @@ } bool ssl_accepts_server_certificate_auth(const SSL_HANDSHAKE *hs) { - // In PAKE mode, the server must respond with a PAKE. - return hs->pake_prover == nullptr; + assert(!hs->ssl->server); + // In PAKE mode, the server must respond with a PAKE. We do not support + // accepting both PAKE and certificates together. + if (hs->pake_prover != nullptr) { + return false; + } + + // If the caller has explicitly asked for certificate verification, it is + // willing to accept a certificate, even if it has non-certificate + // credentials, such as a PSK. + if (hs->config->verify_mode != SSL_VERIFY_NONE) { + return true; + } + + // Otherwise, we fall back to default behavior: if there is at least one + // non-certificate credential configured, assume by default that the caller is + // only willing to use that non-certificate mode. + return std::none_of(hs->config->cert->credentials.begin(), + hs->config->cert->credentials.end(), + [](const auto &cred) { return !cred->UsesPrivateKey(); }); } static enum ssl_hs_wait_t do_start_connect(SSL_HANDSHAKE *hs) { @@ -423,7 +441,8 @@ hs->early_data_offered = true; } - if (!ssl_setup_key_shares(hs, /*override_group_id=*/0) || + if (!ssl_setup_pre_shared_keys(hs) || + !ssl_setup_key_shares(hs, /*override_group_id=*/0) || !ssl_setup_extension_permutation(hs) || !ssl_encrypt_client_hello(hs, Span(ech_enc, ech_enc_len)) || !ssl_add_client_hello(hs)) { @@ -658,6 +677,7 @@ // Clear some TLS 1.3 state that no longer needs to be retained. hs->key_shares.clear(); + hs->pre_shared_keys.clear(); ssl_done_writing_client_hello(hs); // TLS 1.2 handshakes cannot accept ECH. @@ -1297,6 +1317,8 @@ return ssl_hs_error; } + // TODO(crbug.com/369963041): It is currently impossible for a client that + // accepts PSKs or server certs to then decline to send a client cert. if (creds.empty()) { // If there were no credentials, proceed without a client certificate. In // this case, the handshake buffer may be released early.
diff --git a/ssl/internal.h b/ssl/internal.h index ccdb9e9..189a904 100644 --- a/ssl/internal.h +++ b/ssl/internal.h
@@ -31,6 +31,7 @@ #include <string_view> #include <type_traits> #include <utility> +#include <variant> #include <openssl/aead.h> #include <openssl/curve25519.h> @@ -1212,19 +1213,46 @@ bool tls13_derive_session_psk(SSL_SESSION *session, Span<const uint8_t> nonce, bool is_dtls); -// tls13_psk_binder calculates PSK binder value for |session| over -// |transcript| and |client_hello|. On success, it writes the result to |out|, -// sets |*out_len| to the length, and returns true. Otherwise, it returns false. +struct SSLImportedPSK { + static constexpr bool kAllowUniquePtr = true; + UniquePtr<SSL_CREDENTIAL> credential; + Array<uint8_t> imported_identity; + InplaceVector<uint8_t, SSL_MAX_MD_SIZE> ipskx; + uint16_t protocol = 0; + const EVP_MD *md = nullptr; +}; + +// tls13_derive_imported_psk computes the imported PSK value for |cred|, which +// must be an PSK credential, for use with a target KDF of HKDF with |hkdf_md|. +// |protocol| should be the wire version (i.e. |TLS1_3_VERSION| or +// |DTLS1_3_VERSION|) of the target protocol. It returns the imported PSK on +// success and std::nullopt on error. +std::optional<SSLImportedPSK> tls13_derive_imported_psk(const SSL_HANDSHAKE *hs, + SSL_CREDENTIAL *cred, + uint16_t protocol, + const EVP_MD *hkdf_md); + +using SSLPreSharedKey = std::variant<SSLImportedPSK, UniquePtr<SSL_SESSION>>; + +// ssl_pre_shared_key_hash return's |psk|'s hash. +const EVP_MD *ssl_pre_shared_key_hash(const SSLPreSharedKey &psk); + +// ssl_pre_shared_key_identity return's |psk|'s identity. +Span<const uint8_t> ssl_pre_shared_key_identity(const SSLPreSharedKey &psk); + +// tls13_psk_binder calculates the PSK binder value for |psk| over |transcript| +// and |client_hello|. On success, it writes the result to |out|, sets +// |*out_len| to the length, and returns true. Otherwise, it returns false. // |binders_len| must be the length of the binders field, covering all binders // and the overall length prefix, in |client_hello|. bool tls13_psk_binder(const SSL_HANDSHAKE *hs, Span<uint8_t> out, - size_t *out_len, const SSL_SESSION *session, + size_t *out_len, const SSLPreSharedKey &psk, const SSLTranscript &transcript, Span<const uint8_t> client_hello, size_t binders_len); // tls13_verify_psk_binder verifies that the handshake transcript, truncated up -// to the binders has a valid signature using the value of |session|'s -// resumption secret. It returns true on success, and false on failure. +// to the binders has a valid binder for |session|. It returns true on success, +// and false on failure. bool tls13_verify_psk_binder(const SSL_HANDSHAKE *hs, const SSL_SESSION *session, const SSLMessage &msg, CBS *binders); @@ -1365,6 +1393,7 @@ kDelegated, kSPAKE2PlusV1Client, kSPAKE2PlusV1Server, + kPreSharedKey, }; BSSL_NAMESPACE_END @@ -1468,6 +1497,13 @@ bssl::Array<uint8_t> registration_record; // client-only mutable std::atomic<uint32_t> pake_limit; + // External-PSK-specific information. epskx is the HKDF-Extract-ed value, from + // Section 5.1 of RFC 9258. + bssl::Array<uint8_t> epskx; + bssl::Array<uint8_t> epsk_id; + const EVP_MD *epsk_md = nullptr; + bssl::Array<uint8_t> epsk_context; + // Checks whether there are still permitted PAKE attempts remaining, without // changing the counter. bool HasPAKEAttempts() const; @@ -1731,6 +1767,9 @@ // members of this vector must be non-null. InplaceVector<UniquePtr<SSLKeyShare>, kNumNamedGroups> key_shares; + // pre_shared_keys are the pre-shared keys to be offered by the client. + Vector<SSLPreSharedKey> pre_shared_keys; + // transcript is the current handshake transcript. SSLTranscript transcript; @@ -2084,6 +2123,10 @@ // for |hs|, if applicable. It returns true on success and false on error. bool ssl_setup_extension_permutation(SSL_HANDSHAKE *hs); +// ssl_setup_pre_shared_keys computes the offered client PSKs and saves them in +// |hs|. It returns true on success and false on failure. +bool ssl_setup_pre_shared_keys(SSL_HANDSHAKE *hs); + // ssl_setup_key_shares computes client key shares and saves them in |hs|. It // returns true on success and false on failure. In order of precedence: // @@ -2121,9 +2164,8 @@ 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); +const SSLPreSharedKey *ssl_ext_pre_shared_key_parse_serverhello( + SSL_HANDSHAKE *hs, uint8_t *out_alert, CBS *contents); bool ssl_ext_pre_shared_key_parse_clienthello( SSL_HANDSHAKE *hs, CBS *out_ticket, CBS *out_binders, uint32_t *out_obfuscated_ticket_age, uint8_t *out_alert,
diff --git a/ssl/ssl_credential.cc b/ssl/ssl_credential.cc index 49cef15..36ee339 100644 --- a/ssl/ssl_credential.cc +++ b/ssl/ssl_credential.cc
@@ -16,6 +16,7 @@ #include <assert.h> +#include <openssl/hkdf.h> #include <openssl/span.h> #include "../crypto/internal.h" @@ -164,6 +165,7 @@ return true; case SSLCredentialType::kSPAKE2PlusV1Client: case SSLCredentialType::kSPAKE2PlusV1Server: + case SSLCredentialType::kPreSharedKey: return false; } abort(); @@ -176,6 +178,7 @@ return true; case SSLCredentialType::kSPAKE2PlusV1Client: case SSLCredentialType::kSPAKE2PlusV1Server: + case SSLCredentialType::kPreSharedKey: return false; } abort(); @@ -330,6 +333,31 @@ return New<SSL_CREDENTIAL>(SSLCredentialType::kX509); } +SSL_CREDENTIAL *SSL_CREDENTIAL_new_pre_shared_key( + const uint8_t *key, size_t key_len, const uint8_t *id, size_t id_len, + const EVP_MD *md, const uint8_t *context, size_t context_len) { + if (id_len == 0) { + OPENSSL_PUT_ERROR(SSL, SSL_R_PSK_IDENTITY_NOT_FOUND); + return nullptr; + } + + auto cred = MakeUnique<SSL_CREDENTIAL>(SSLCredentialType::kPreSharedKey); + size_t epskx_len; + if (cred == nullptr || + // Precompute epskx, to avoid recomputing it on every use of the + // credential. + !cred->epskx.InitForOverwrite(EVP_MD_size(md)) || + !HKDF_extract(cred->epskx.data(), &epskx_len, md, key, key_len, + /*salt=*/nullptr, /*salt_len=*/0) || + !cred->epsk_id.CopyFrom(Span(id, id_len)) || + !cred->epsk_context.CopyFrom(Span(context, context_len))) { + return nullptr; + } + BSSL_CHECK(epskx_len == cred->epskx.size()); + cred->epsk_md = md; + return cred.release(); +} + SSL_CREDENTIAL *SSL_CREDENTIAL_new_delegated() { return New<SSL_CREDENTIAL>(SSLCredentialType::kDelegated); }
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc index a6ed9aa..4e306a1 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc
@@ -433,6 +433,12 @@ CredentialConfigType::kSPAKE2PlusV1; } +static bool IsTLS13PSK(const SSL *ssl) { + int idx = GetTestState(ssl)->selected_credential; + return idx >= 0 && GetTestConfig(ssl)->credentials[idx].type == + CredentialConfigType::kPreSharedKey; +} + // CheckHandshakeProperties checks, immediately after |ssl| completes its // initial handshake (or False Starts), whether all the properties are // consistent with the test configuration and invariants. @@ -663,14 +669,10 @@ return false; } - if (!config->psk.empty()) { + if (config->expect_no_peer_cert || !config->psk.empty() || IsTLS13PSK(ssl) || + IsPAKE(ssl)) { if (SSL_get_peer_cert_chain(ssl) != nullptr) { - 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"); + fprintf(stderr, "Received unexpected peer certificate.\n"); return false; } } else if (!config->is_server || config->require_any_client_certificate) {
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index b7f14c7..1eea175 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go
@@ -367,6 +367,12 @@ // Temporary value; pre RFC. const spakeID uint16 = 0x7d96 +// KDF identifiers (RFC 9258) +const ( + kdfHKDFWithSHA256 uint16 = 0x0001 + kdfHKDFWithSHA384 uint16 = 0x0002 +) + // ConnectionState records basic TLS details about the connection. type ConnectionState struct { Version uint16 // TLS version used by the connection (e.g. VersionTLS12) @@ -1714,13 +1720,9 @@ // resumption. NegotiatePSKResumption bool - // AlwaysSelectPSKIdentity, if true, causes the server in TLS 1.3 to - // always acknowledge a session, regardless of one was offered. - AlwaysSelectPSKIdentity bool - - // SelectPSKIdentityOnResume, if non-zero, causes the server to select - // the specified PSK identity index rather than the actual value. - SelectPSKIdentityOnResume uint16 + // AlwaysSelectPSKIdentity, if not nil, causes the server in TLS 1.3 to + // select the specified PSK identity index. + AlwaysSelectPSKIdentity *uint16 // ExtraPSKIdentity, if true, causes the client to send an extra PSK // identity. @@ -2356,6 +2358,7 @@ CredentialTypeX509 CredentialType = iota CredentialTypeDelegated CredentialTypeSPAKE2PlusV1 + CredentialTypePreSharedKey ) // A Credential is a certificate chain and private key that a TLS endpoint may @@ -2408,6 +2411,11 @@ // OverridePAKECodepoint, if non-zero, causes the runner to send the // specified value instead of the actual PAKE codepoint. OverridePAKECodepoint uint16 + // The following fields are used for PSK credentials. + PreSharedKey []byte + PSKIdentity []byte + PSKHash crypto.Hash + PSKContext []byte // TrustAnchorID, if not empty, is the trust anchor ID for the issuer // of the certificate chain. TrustAnchorID []byte
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go index f5fae05..12cb52b 100644 --- a/ssl/test/runner/handshake_messages.go +++ b/ssl/test/runner/handshake_messages.go
@@ -2968,3 +2968,20 @@ func (*endOfEarlyDataMsg) unmarshal(data []byte) bool { return len(data) == 4 } + +type importedPSKIdentity struct { + externalIdentity []byte + context []byte + targetProtocol uint16 + targetKDF uint16 +} + +func (m *importedPSKIdentity) marshal() []byte { + // See RFC 9258, Section 5.1. + b := cryptobyte.NewBuilder(nil) + addUint16LengthPrefixedBytes(b, m.externalIdentity) + addUint16LengthPrefixedBytes(b, m.context) + b.AddUint16(m.targetProtocol) + b.AddUint16(m.targetKDF) + return b.BytesOrPanic() +}
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index df1d380..82054ac 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go
@@ -605,6 +605,28 @@ var psk *preSharedKey foundKEMode := bytes.IndexByte(pskKEModes, pskDHEKEMode) >= 0 + + // If using PSKs, require that we are able to use it. (We may override the + // choice with session resumption later.) + if config.Credential.Type == CredentialTypePreSharedKey { + if !foundKEMode { + return errors.New("tls: no common PSK key exchange mode") + } + psk = importPSK(config.Credential, c.vers, hs.suite.hash()) + pskIndex := slices.IndexFunc(pskIdentities, func(id pskIdentity) bool { + return bytes.Equal(id.ticket, psk.identity) + }) + if pskIndex < 0 { + return errors.New("tls: client did not offer expected PSK") + } + binderToVerify := hs.clientHello.pskBinders[pskIndex] + if err := verifyPSKBinder(psk, hs.clientHello, binderToVerify, []byte{}, []byte{}); err != nil { + return err + } + hs.hello.hasPSKIdentity = true + hs.hello.pskIdentity = uint16(pskIndex) + } + if foundKEMode && !config.SessionTicketsDisabled { for i, pskIdentity := range pskIdentities { // TODO(svaldez): Check the obfuscatedTicketAge before accepting 0-RTT. @@ -638,19 +660,11 @@ hs.sessionState = sessionState hs.hello.hasPSKIdentity = true hs.hello.pskIdentity = uint16(i) - if config.Bugs.SelectPSKIdentityOnResume != 0 { - hs.hello.pskIdentity = config.Bugs.SelectPSKIdentityOnResume - } c.didResume = true break } } - if config.Bugs.AlwaysSelectPSKIdentity { - hs.hello.hasPSKIdentity = true - hs.hello.pskIdentity = 0 - } - // Resolve PSK and compute the early secret. if psk != nil { hs.finishedHash.addEntropy(psk.secret) @@ -664,7 +678,7 @@ // Decide whether to use key_share. hs.hello.hasKeyShare = config.Credential.Type != CredentialTypeSPAKE2PlusV1 && config.Bugs.UnsolicitedPAKE == 0 - if hs.sessionState != nil && config.Bugs.NegotiatePSKResumption { + if psk != nil && config.Bugs.NegotiatePSKResumption { hs.hello.hasKeyShare = false } if config.Bugs.MissingKeyShare { @@ -946,6 +960,11 @@ } } + if config.Bugs.AlwaysSelectPSKIdentity != nil { + hs.hello.hasPSKIdentity = true + hs.hello.pskIdentity = *config.Bugs.AlwaysSelectPSKIdentity + } + if config.Bugs.SendEarlyDataExtension { encryptedExtensions.extensions.hasEarlyData = true } @@ -1100,7 +1119,7 @@ } var requestClientCert bool - if (hs.sessionState == nil && hs.cert.Type != CredentialTypeSPAKE2PlusV1) || config.Bugs.AlwaysSendCertificateRequest { + if (psk == nil && hs.cert.Type != CredentialTypeSPAKE2PlusV1) || config.Bugs.AlwaysSendCertificateRequest { requestClientCert = config.ClientAuth >= RequestClientCert } if requestClientCert { @@ -1127,7 +1146,7 @@ c.writeRecord(recordTypeHandshake, certReq.marshal()) } - if (hs.sessionState == nil && hs.cert.Type != CredentialTypeSPAKE2PlusV1) || config.Bugs.AlwaysSendCertificate { + if (psk == nil && hs.cert.Type != CredentialTypeSPAKE2PlusV1) || config.Bugs.AlwaysSendCertificate { useCert := hs.cert if config.Bugs.UseCertificateCredential != nil { useCert = config.Bugs.UseCertificateCredential
diff --git a/ssl/test/runner/pake_tests.go b/ssl/test/runner/pake_tests.go index a2b89bd..794ebf7 100644 --- a/ssl/test/runner/pake_tests.go +++ b/ssl/test/runner/pake_tests.go
@@ -511,7 +511,7 @@ ExpectNoTLS13PSK: true, // Respond with an unsolicited PSK extension in ServerHello, to // check that the client rejects it. - AlwaysSelectPSKIdentity: true, + AlwaysSelectPSKIdentity: ptrTo(uint16(0)), }, }, resumeShimCredentials: []*Credential{&spakeCredential},
diff --git a/ssl/test/runner/prf.go b/ssl/test/runner/prf.go index 8217e97..9c5354c 100644 --- a/ssl/test/runner/prf.go +++ b/ssl/test/runner/prf.go
@@ -382,8 +382,8 @@ } var ( - externalPSKBinderLabel = []byte("ext binder") resumptionPSKBinderLabel = []byte("res binder") + importedPSKBinderLabel = []byte("imp binder") earlyTrafficLabel = []byte("c e traffic") clientHandshakeTrafficLabel = []byte("c hs traffic") serverHandshakeTrafficLabel = []byte("s hs traffic") @@ -398,6 +398,8 @@ echAcceptConfirmationLabel = []byte("ech accept confirmation") echAcceptConfirmationHRRLabel = []byte("hrr ech accept confirmation") + + derivedPSKLabel = []byte("derived psk") ) // deriveSecret implements TLS 1.3's Derive-Secret function, as defined in @@ -522,3 +524,48 @@ hash := suite.hash() return hkdfExpandLabel(vers, hash, masterSecret, resumptionPSKLabel, nonce, hash.Size()) } + +func importPSK(cred *Credential, targetProtocol version, targetHash crypto.Hash) *preSharedKey { + if cred.Type != CredentialTypePreSharedKey { + panic("not a PSK credential") + } + + var targetKDF uint16 + switch targetHash { + case crypto.SHA256: + targetKDF = kdfHKDFWithSHA256 + case crypto.SHA384: + targetKDF = kdfHKDFWithSHA384 + default: + panic("unrecogized target HKDF hash") + } + + // See RFC 9258, Section 5.1. + identity := (&importedPSKIdentity{ + externalIdentity: cred.PSKIdentity, + context: cred.PSKContext, + targetProtocol: targetProtocol.wire, + targetKDF: targetKDF, + }).marshal() + + h := cred.PSKHash.New() + h.Write(identity) + identityHash := h.Sum(nil) + + epskx, err := hkdf.Extract(cred.PSKHash.New, cred.PreSharedKey, make([]byte, cred.PSKHash.Size())) + if err != nil { + panic(err) + } + + // HKDF-Expand-Label in the ipskx calculation uses the label for the target protocol. + ipskx := hkdfExpandLabel(targetProtocol, cred.PSKHash, epskx, derivedPSKLabel, identityHash, targetHash.Size()) + + psk := &preSharedKey{ + version: targetProtocol, + hash: targetHash, + identity: identity, + secret: ipskx, + } + psk.initBinder(importedPSKBinderLabel) + return psk +}
diff --git a/ssl/test/runner/psk_tests.go b/ssl/test/runner/psk_tests.go new file mode 100644 index 0000000..d00cf86 --- /dev/null +++ b/ssl/test/runner/psk_tests.go
@@ -0,0 +1,365 @@ +// Copyright 2026 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runner + +import ( + "crypto" + "fmt" + "slices" +) + +func hashToString(hash crypto.Hash) string { + switch hash { + case crypto.SHA256: + return "SHA256" + case crypto.SHA384: + return "SHA384" + default: + panic(fmt.Sprintf("unknown hash %d", hash)) + } +} + +func addPSKTests() { + pskSHA256Credential := Credential{ + Type: CredentialTypePreSharedKey, + PreSharedKey: slices.Repeat([]byte{'A', 'B', 'C', 'D'}, 8), + PSKIdentity: []byte("psk1"), + PSKContext: []byte("context1"), + PSKHash: crypto.SHA256, + } + pskSHA384Credential := Credential{ + Type: CredentialTypePreSharedKey, + PreSharedKey: slices.Repeat([]byte{'E', 'F', 'G', 'H'}, 12), + PSKIdentity: []byte("psk2"), + PSKContext: []byte("context2"), + PSKHash: crypto.SHA384, + } + + hashToPSK := func(hash crypto.Hash) *Credential { + switch hash { + case crypto.SHA256: + return &pskSHA256Credential + case crypto.SHA384: + return &pskSHA384Credential + default: + panic(fmt.Sprintf("unknown hash %d", hash)) + } + } + hashToCipher := func(hash crypto.Hash) uint16 { + switch hash { + case crypto.SHA256: + return TLS_AES_128_GCM_SHA256 + case crypto.SHA384: + return TLS_AES_256_GCM_SHA384 + default: + panic(fmt.Sprintf("unknown hash %d", hash)) + } + } + + for _, protocol := range []protocol{tls, dtls, quic} { + // Test that SHA-256 and SHA-384 PSKs can be used with SHA-256 and + // SHA-384 ciphers. + for _, pskHash := range []crypto.Hash{crypto.SHA256, crypto.SHA384} { + psk := hashToPSK(pskHash) + for _, cipherHash := range []crypto.Hash{crypto.SHA256, crypto.SHA384} { + cipher := hashToCipher(cipherHash) + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-%s-%s-%s", hashToString(pskHash), hashToString(cipherHash), protocol), + config: Config{ + Credential: psk, + MaxVersion: VersionTLS13, + CipherSuites: []uint16{cipher}, + }, + shimCredentials: []*Credential{psk}, + // Also test that the resulting session can be reused. + resumeSession: true, + // Override the default behavior of expecting a peer certificate on + // resumption connections. + flags: []string{"-expect-no-peer-cert"}, + }) + + // Test with HelloRetryRequest to ensure the client computes + // the second ClientHello's binder correctly, and also accounts + // for the PSK list getting smaller once the cipher is known. + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-HRR-%s-%s-%s", hashToString(pskHash), hashToString(cipherHash), protocol), + config: Config{ + Credential: psk, + MaxVersion: VersionTLS13, + CipherSuites: []uint16{cipher}, + Bugs: ProtocolBugs{ + SendHelloRetryRequestCookie: []byte("cookie"), + }, + }, + shimCredentials: []*Credential{psk}, + // Also test that the resulting session can be reused. + resumeSession: true, + // Override the default behavior of expecting a peer certificate on + // resumption connections. + flags: []string{"-expect-no-peer-cert"}, + }) + } + } + + // If the client is configured to offer multiple PSKs, it should accept + // either from the server. + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-AcceptFirst-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + }, + shimCredentials: []*Credential{&pskSHA256Credential, &pskSHA384Credential}, + flags: []string{"-expect-selected-credential", "0"}, + }) + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-AcceptSecond-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA384Credential, + }, + shimCredentials: []*Credential{&pskSHA256Credential, &pskSHA384Credential}, + flags: []string{"-expect-selected-credential", "1"}, + }) + + // If the client is configured (on the second connection) with both PSKs and + // a session, the PSK is still usable. + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-DeclineSession-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + }, + resumeConfig: &Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + SessionTicketsDisabled: true, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + resumeSession: true, + expectResumeRejected: true, + // The runner will not provision a ticket on the second connection. + flags: []string{"-on-resume-expect-no-session"}, + }) + + // The client should reject out-of-bounds PSK indices. + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-OutOfBoundsIndex-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + Bugs: ProtocolBugs{ + // The shim will import two PSKs from the credential, so + // only indices 0 and 1 are valid, + AlwaysSelectPSKIdentity: ptrTo(uint16(2)), + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedError: ":PSK_IDENTITY_NOT_FOUND:", + }) + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-OutOfBoundsIndex-HRR-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + Bugs: ProtocolBugs{ + SendHelloRetryRequestCookie: []byte("cookie"), + // The shim will import two PSKs from the credential, but + // then prune them in the second ClientHello, so only index + // 0 is valid. + AlwaysSelectPSKIdentity: ptrTo(uint16(1)), + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedError: ":PSK_IDENTITY_NOT_FOUND:", + }) + + // The client should reject psk_ke connections. We require psk_dhe_ke. + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-NoKeyShare-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + Bugs: ProtocolBugs{ + NegotiatePSKResumption: true, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedError: ":MISSING_KEY_SHARE:", + }) + + // By default, if the client configures PSKs, it should reject server + // responses that do use certificates, including TLS 1.2. + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-PSKRequired-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS13, + Credential: &rsaCertificate, + Bugs: ProtocolBugs{ + // Ignore the client's lack of signature_algorithms. + IgnorePeerSignatureAlgorithmPreferences: true, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedError: ":MISSING_EXTENSION:", + // The shim sends an alert, but alerts immediately after TLS 1.3 + // ServerHello have an encryption mismatch. + }) + if protocol != quic { + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-PSKRequired-TLS12-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS12, + Credential: &rsaCertificate, + Bugs: ProtocolBugs{ + // Ignore the client's lack of signature_algorithms. + IgnorePeerSignatureAlgorithmPreferences: true, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedError: ":UNSUPPORTED_PROTOCOL:", + expectedLocalError: "remote error: protocol version not supported", + }) + } + + // The client can be configured to accept certificates or PSKs. In this + // case, even TLS 1.2 certificates are acceptable. + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-PSKOrCert-PSK-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + flags: []string{"-verify-peer"}, + }) + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-PSKOrCert-Cert-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS13, + Credential: &rsaCertificate, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + flags: []string{"-verify-peer"}, + }) + if protocol != quic { + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-PSKOrCert-Cert-TLS12-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS12, + Credential: &rsaCertificate, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + flags: []string{"-verify-peer"}, + }) + } + + // When a client is configured with PSKs or certificates, it can even send + // client certificates, configured from the credential list. + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-PSKOrCert-CertRequest-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS13, + Credential: &rsaCertificate, + ClientAuth: RequireAnyClientCert, + }, + shimCredentials: []*Credential{&pskSHA256Credential, &rsaCertificate}, + flags: []string{"-verify-peer"}, + }) + if protocol != quic { + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-PSKOrCert-CertRequest-TLS12-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS12, + Credential: &rsaCertificate, + ClientAuth: RequireAnyClientCert, + }, + shimCredentials: []*Credential{&pskSHA256Credential, &rsaCertificate}, + flags: []string{"-verify-peer"}, + }) + } + + // The client should reject CertificateRequest messages on PSK connections. + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-UnexpectedCertificateRequest-%s", protocol), + testType: clientTest, + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + ClientAuth: RequireAnyClientCert, + Bugs: ProtocolBugs{ + AlwaysSendCertificateRequest: true, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedError: ":UNEXPECTED_MESSAGE:", + expectedLocalError: "remote error: unexpected message", + }) + + // The client should reject Certificate messages on PSK connections. + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Client-UnexpectedCertificate-%s", protocol), + config: Config{ + Credential: &pskSHA256Credential, + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + AlwaysSendCertificate: true, + UseCertificateCredential: &rsaCertificate, + // Ignore the client's lack of signature_algorithms. + IgnorePeerSignatureAlgorithmPreferences: true, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedError: ":UNEXPECTED_MESSAGE:", + expectedLocalError: "remote error: unexpected message", + }) + } +}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index 786aba7..ebc1f46 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go
@@ -1432,6 +1432,8 @@ flags = append(flags, prefix+"-new-delegated-credential") case CredentialTypeSPAKE2PlusV1: flags = append(flags, prefix+"-new-spake2plusv1-credential") + case CredentialTypePreSharedKey: + flags = append(flags, prefix+"-new-psk-credential") default: panic(fmt.Errorf("unknown credential type %d", cred.Type)) } @@ -1467,6 +1469,19 @@ if cred.WrongPAKERole { flags = append(flags, prefix+"-wrong-pake-role") } + handleBase64Field("psk-importer-key", cred.PreSharedKey) + handleBase64Field("psk-importer-identity", cred.PSKIdentity) + handleBase64Field("psk-importer-context", cred.PSKContext) + switch cred.PSKHash { + case 0: + break + case crypto.SHA256: + flags = append(flags, prefix+"-psk-importer-sha256") + case crypto.SHA384: + flags = append(flags, prefix+"-psk-importer-sha384") + default: + panic(fmt.Sprintf("unknown PSK hash %s", cred.PSKHash)) + } handleBase64Field("trust-anchor-id", cred.TrustAnchorID) return flags } @@ -2244,6 +2259,7 @@ addKeyUpdateTests() addPAKETests() addTrustAnchorTests() + addPSKTests() toAppend, err := convertToSplitHandshakeTests(testCases) if err != nil {
diff --git a/ssl/test/runner/tls13_tests.go b/ssl/test/runner/tls13_tests.go index e4af165..5853d64 100644 --- a/ssl/test/runner/tls13_tests.go +++ b/ssl/test/runner/tls13_tests.go
@@ -1220,7 +1220,7 @@ config: Config{ MaxVersion: VersionTLS13, Bugs: ProtocolBugs{ - AlwaysSelectPSKIdentity: true, + AlwaysSelectPSKIdentity: ptrTo(uint16(0)), }, }, shouldFail: true, @@ -1231,8 +1231,11 @@ name: "InvalidPSKIdentity-TLS13", config: Config{ MaxVersion: VersionTLS13, + }, + resumeConfig: &Config{ + MaxVersion: VersionTLS13, Bugs: ProtocolBugs{ - SelectPSKIdentityOnResume: 1, + AlwaysSelectPSKIdentity: ptrTo(uint16(1)), }, }, resumeSession: true,
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index 90ffee1..b99985b 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc
@@ -492,6 +492,7 @@ IntFlag("-expect-cipher", &TestConfig::expect_cipher), StringFlag("-expect-peer-cert-file", &TestConfig::expect_peer_cert_file), + BoolFlag("-expect-no-peer-cert", &TestConfig::expect_no_peer_cert), IntFlag("-resumption-delay", &TestConfig::resumption_delay), BoolFlag("-retain-only-sha256-client-cert", &TestConfig::retain_only_sha256_client_cert), @@ -574,6 +575,8 @@ CredentialConfigType::kDelegated), NewCredentialFlag("-new-spake2plusv1-credential", CredentialConfigType::kSPAKE2PlusV1), + NewCredentialFlag("-new-psk-credential", + CredentialConfigType::kPreSharedKey), CredentialFlagWithDefault( StringFlag("-cert-file", &TestConfig::cert_file), StringFlag("-cert-file", &CredentialConfig::cert_file)), @@ -605,6 +608,15 @@ Base64Flag("-pake-password", &CredentialConfig::pake_password)), CredentialFlag( BoolFlag("-wrong-pake-role", &CredentialConfig::wrong_pake_role)), + CredentialFlag(Base64Flag("-psk-importer-key", &CredentialConfig::psk)), + CredentialFlag(Base64Flag("-psk-importer-identity", + &CredentialConfig::psk_identity)), + CredentialFlag(Base64Flag("-psk-importer-context", + &CredentialConfig::psk_context)), + CredentialFlag(SetValueFlag("-psk-importer-sha256", + &CredentialConfig::psk_hash, EVP_sha256())), + CredentialFlag(SetValueFlag("-psk-importer-sha384", + &CredentialConfig::psk_hash, EVP_sha384())), CredentialFlag( Base64Flag("-trust-anchor-id", &CredentialConfig::trust_anchor_id)), IntFlag("-private-key-delay-ms", &TestConfig::private_key_delay_ms), @@ -1487,6 +1499,12 @@ } break; } + case CredentialConfigType::kPreSharedKey: + cred.reset(SSL_CREDENTIAL_new_pre_shared_key( + cred_config.psk.data(), cred_config.psk.size(), + cred_config.psk_identity.data(), cred_config.psk_identity.size(), + cred_config.psk_hash, cred_config.psk_context.data(), + cred_config.psk_context.size())); } if (cred == nullptr) { return nullptr;
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h index 440a6a4..164232f 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h
@@ -29,6 +29,7 @@ kX509, kDelegated, kSPAKE2PlusV1, + kPreSharedKey, }; struct CredentialConfig { @@ -46,6 +47,10 @@ std::vector<uint8_t> pake_password; std::vector<uint8_t> trust_anchor_id; bool wrong_pake_role = false; + std::vector<uint8_t> psk; + std::vector<uint8_t> psk_identity; + std::vector<uint8_t> psk_context; + const EVP_MD *psk_hash; }; struct TestConfig { @@ -188,6 +193,7 @@ uint16_t expect_cipher_aes = 0; uint16_t expect_cipher_no_aes = 0; uint16_t expect_cipher = 0; + bool expect_no_peer_cert = false; std::string expect_peer_cert_file; int resumption_delay = 0; bool retain_only_sha256_client_cert = false;
diff --git a/ssl/tls13_client.cc b/ssl/tls13_client.cc index 0027861..59dc387 100644 --- a/ssl/tls13_client.cc +++ b/ssl/tls13_client.cc
@@ -335,6 +335,14 @@ return ssl_hs_error; } + // Per RFC 8446 section 4.1.4, skip any PSKs whose hash does not match the + // selected cipher. This avoids performing the transcript hash transformation + // for multiple hashes. + const EVP_MD *cipher_md = SSL_CIPHER_get_handshake_digest(cipher); + hs->pre_shared_keys.EraseIf([=](const SSLPreSharedKey &psk) { + return ssl_pre_shared_key_hash(psk) != cipher_md; + }); + // HelloRetryRequest should be the end of the flight. if (ssl->method->has_unprocessed_handshake_data(ssl)) { ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE); @@ -373,6 +381,56 @@ return ssl_hs_flush; } +static bool check_session(const SSL_HANDSHAKE *hs, uint8_t *out_alert, + const SSL_SESSION *session) { + const SSL *const ssl = hs->ssl; + if (session->ssl_version != ssl->s3->version) { + OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_VERSION_NOT_RETURNED); + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return false; + } + + if (session->cipher->algorithm_prf != hs->new_cipher->algorithm_prf) { + OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_PRF_HASH_MISMATCH); + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return false; + } + + if (!ssl_session_is_context_valid(hs, session)) { + // This is actually a client application bug. + OPENSSL_PUT_ERROR(SSL, SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT); + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return false; + } + return true; +} + +static bool check_imported_psk(const SSL_HANDSHAKE *hs, uint8_t *out_alert, + const SSLImportedPSK &imported) { + const SSL *const ssl = hs->ssl; + const EVP_MD *md = + ssl_get_handshake_digest(ssl_protocol_version(ssl), hs->new_cipher); + if (imported.md != md || imported.protocol != ssl->s3->version) { + OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_PSK_FOR_CONNECTION); + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return false; + } + return true; +} + +static bool using_certificate(const SSL_HANDSHAKE *hs) { + const SSL *const ssl = hs->ssl; + // Resumption is not a certificate-based handshake. + if (ssl->s3->session_reused) { + return false; + } + // Non-private-key credentials imply a non-certificate handshake (PSK, etc.). + if (hs->credential != nullptr && !hs->credential->UsesPrivateKey()) { + return false; + } + return true; +} + static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) { SSL *const ssl = hs->ssl; SSLMessage msg; @@ -425,12 +483,9 @@ OPENSSL_memcpy(ssl->s3->server_random, CBS_data(&server_hello.random), SSL3_RANDOM_SIZE); - // When offering ECH, |ssl->session| is only offered in ClientHelloInner. + // When offering ECH, pre-shared keys are only offered in ClientHelloInner. const bool pre_shared_key_allowed = - ssl->session != nullptr && - ssl_session_get_type(ssl->session.get()) == - SSLSessionType::kPreSharedKey && - ssl->s3->ech_status != ssl_ech_rejected; + !hs->pre_shared_keys.empty() && ssl->s3->ech_status != ssl_ech_rejected; SSLExtension key_share(TLSEXT_TYPE_key_share, !hs->key_shares.empty()), pake_share(TLSEXT_TYPE_pake, hs->pake_prover != nullptr), pre_shared_key(TLSEXT_TYPE_pre_shared_key, pre_shared_key_allowed), @@ -495,61 +550,65 @@ // PAKE handshake (!key_share.present && pake_share.present && !pre_shared_key.present)); + // Determine the PSK used. + const EVP_MD *cipher_md = + ssl_get_handshake_digest(ssl_protocol_version(ssl), hs->new_cipher); + Span<const uint8_t> psk_secret = Span(kZeroes, EVP_MD_size(cipher_md)); alert = SSL_AD_DECODE_ERROR; if (pre_shared_key.present) { - if (!ssl_ext_pre_shared_key_parse_serverhello(hs, &alert, - &pre_shared_key.data)) { + const SSLPreSharedKey *psk = ssl_ext_pre_shared_key_parse_serverhello( + hs, &alert, &pre_shared_key.data); + if (psk == nullptr) { ssl_send_alert(ssl, SSL3_AL_FATAL, alert); return ssl_hs_error; } - if (ssl->session->ssl_version != ssl->s3->version) { - OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_VERSION_NOT_RETURNED); - ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); - return ssl_hs_error; - } + if (const auto *imported = std::get_if<SSLImportedPSK>(psk); + imported != nullptr) { + if (!check_imported_psk(hs, &alert, *imported)) { + ssl_send_alert(ssl, SSL3_AL_FATAL, alert); + return ssl_hs_error; + } - if (ssl->session->cipher->algorithm_prf != hs->new_cipher->algorithm_prf) { - OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_PRF_HASH_MISMATCH); - ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); - return ssl_hs_error; - } + psk_secret = imported->ipskx; + hs->credential = UpRef(imported->credential); + if (!ssl_get_new_session(hs)) { + ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); + return ssl_hs_error; + } + } else { + const SSL_SESSION *session = std::get<UniquePtr<SSL_SESSION>>(*psk).get(); + assert(session == ssl->session.get()); + if (!check_session(hs, &alert, session)) { + ssl_send_alert(ssl, SSL3_AL_FATAL, alert); + return ssl_hs_error; + } - if (!ssl_session_is_context_valid(hs, ssl->session.get())) { - // This is actually a client application bug. - OPENSSL_PUT_ERROR(SSL, - SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT); - ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); - return ssl_hs_error; - } + psk_secret = session->secret; + ssl->s3->session_reused = true; + // Only authentication information carries over in TLS 1.3. + hs->new_session = SSL_SESSION_dup(session, SSL_SESSION_DUP_AUTH_ONLY); + if (!hs->new_session) { + ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); + return ssl_hs_error; + } + ssl_set_session(ssl, nullptr); - ssl->s3->session_reused = true; - hs->can_release_private_key = true; - // Only authentication information carries over in TLS 1.3. - hs->new_session = - SSL_SESSION_dup(ssl->session.get(), SSL_SESSION_DUP_AUTH_ONLY); - if (!hs->new_session) { - ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); - return ssl_hs_error; + // Resumption incorporates fresh key material, so refresh the timeout. + ssl_session_renew_timeout(ssl, hs->new_session.get(), + ssl->session_ctx->session_psk_dhe_timeout); } - ssl_set_session(ssl, nullptr); - - // Resumption incorporates fresh key material, so refresh the timeout. - ssl_session_renew_timeout(ssl, hs->new_session.get(), - ssl->session_ctx->session_psk_dhe_timeout); } else if (!ssl_get_new_session(hs)) { ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); return ssl_hs_error; } hs->new_session->cipher = hs->new_cipher; + hs->can_release_private_key = !using_certificate(hs); + assert(!using_certificate(hs) || ssl_accepts_server_certificate_auth(hs)); // Set up the key schedule and incorporate the PSK into the running secret. - size_t hash_len = EVP_MD_size( - ssl_get_handshake_digest(ssl_protocol_version(ssl), hs->new_cipher)); - if (!tls13_init_key_schedule(hs, ssl->s3->session_reused - ? Span(hs->new_session->secret) - : Span(kZeroes, hash_len))) { + if (!tls13_init_key_schedule(hs, psk_secret)) { return ssl_hs_error; } @@ -686,9 +745,10 @@ static enum ssl_hs_wait_t do_read_certificate_request(SSL_HANDSHAKE *hs) { SSL *const ssl = hs->ssl; - // CertificateRequest may only be sent in non-resumption handshakes. - if (ssl->s3->session_reused) { - if (ssl->ctx->reverify_on_resume && !ssl->s3->early_data_accepted) { + // CertificateRequest may only be sent in certificate-based handshakes. + if (!using_certificate(hs)) { + if (ssl->s3->session_reused && ssl->ctx->reverify_on_resume && + !ssl->s3->early_data_accepted) { hs->tls13_state = state_server_certificate_reverify; return ssl_hs_ok; } @@ -696,11 +756,6 @@ 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; @@ -952,6 +1007,8 @@ return ssl_hs_error; } + // TODO(crbug.com/369963041): It is currently impossible for a client that + // accepts PSKs or server certs to then decline to send a client cert. if (!creds.empty()) { // Select the credential to use. for (SSL_CREDENTIAL *cred : creds) {
diff --git a/ssl/tls13_enc.cc b/ssl/tls13_enc.cc index 2c82fad..c24cf58 100644 --- a/ssl/tls13_enc.cc +++ b/ssl/tls13_enc.cc
@@ -30,6 +30,7 @@ #include <openssl/hmac.h> #include <openssl/mem.h> +#include "../crypto/bytestring/internal.h" #include "../crypto/fipsmodule/tls/internal.h" #include "../crypto/internal.h" #include "internal.h" @@ -501,13 +502,40 @@ hash, SSL_is_dtls(ssl)); } -static const char kTLS13LabelPSKBinder[] = "res binder"; +const EVP_MD *ssl_pre_shared_key_hash(const SSLPreSharedKey &psk) { + if (const auto *imported = std::get_if<SSLImportedPSK>(&psk); + imported != nullptr) { + return imported->md; + } + return ssl_session_get_digest(std::get<UniquePtr<SSL_SESSION>>(psk).get()); +} + +Span<const uint8_t> ssl_pre_shared_key_identity(const SSLPreSharedKey &psk) { + if (const auto *imported = std::get_if<SSLImportedPSK>(&psk); + imported != nullptr) { + return imported->imported_identity; + } + return std::get<UniquePtr<SSL_SESSION>>(psk)->ticket; +} bool tls13_psk_binder(const SSL_HANDSHAKE *hs, Span<uint8_t> out, - size_t *out_len, const SSL_SESSION *session, + size_t *out_len, const SSLPreSharedKey &psk, const SSLTranscript &transcript, Span<const uint8_t> client_hello, size_t binders_len) { - const EVP_MD *digest = ssl_session_get_digest(session); + const EVP_MD *digest; + Span<const uint8_t> secret; + std::string_view label; + if (const auto *imported = std::get_if<SSLImportedPSK>(&psk); + imported != nullptr) { + digest = imported->md; + secret = imported->ipskx; + label = "imp binder"; + } else { + const SSL_SESSION *session = std::get<UniquePtr<SSL_SESSION>>(psk).get(); + digest = ssl_session_get_digest(session); + secret = session->secret; + label = "res binder"; + } // Compute the binder key. // @@ -521,13 +549,11 @@ auto binder_key = Span(binder_key_buf, EVP_MD_size(digest)); if (!EVP_Digest(nullptr, 0, binder_context, &binder_context_len, digest, nullptr) || - !HKDF_extract(early_secret, &early_secret_len, digest, - session->secret.data(), session->secret.size(), nullptr, - 0) || + !HKDF_extract(early_secret, &early_secret_len, digest, secret.data(), + secret.size(), nullptr, 0) || !hkdf_expand_label( - binder_key, digest, Span(early_secret, early_secret_len), - kTLS13LabelPSKBinder, Span(binder_context, binder_context_len), - SSL_is_dtls(hs->ssl))) { + binder_key, digest, Span(early_secret, early_secret_len), label, + Span(binder_context, binder_context_len), SSL_is_dtls(hs->ssl))) { return false; } @@ -573,7 +599,8 @@ // The binders are computed over |msg| with |binders| and its u16 length // prefix removed. The caller is assumed to have parsed |msg|, extracted // |binders|, and verified the PSK extension is last. - if (!tls13_psk_binder(hs, verify_data, &verify_data_len, session, + if (!tls13_psk_binder(hs, verify_data, &verify_data_len, + UpRef(const_cast<SSL_SESSION *>(session)), hs->transcript, msg.body, 2 + CBS_len(binders)) || // We only consider the first PSK, so compare against the first binder. !CBS_get_u8_length_prefixed(binders, &binder)) { @@ -595,6 +622,68 @@ return true; } +std::optional<SSLImportedPSK> tls13_derive_imported_psk( + const SSL_HANDSHAKE *hs, SSL_CREDENTIAL *cred, uint16_t protocol, + const EVP_MD *hkdf_md) { + assert(cred->type == SSLCredentialType::kPreSharedKey); + + // See Section 10 of RFC 9258. + uint16_t target_kdf; + switch (EVP_MD_nid(hkdf_md)) { + case NID_sha256: + target_kdf = 0x0001; // HKDF_SHA256 + break; + case NID_sha384: + target_kdf = 0x0002; // HKDF_SHA384 + break; + default: + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return std::nullopt; + } + + SSLImportedPSK ret; + ret.credential = UpRef(cred); + ret.protocol = protocol; + ret.md = hkdf_md; + + // See Section 5.1 of RFC 9258. + ScopedCBB imported_id; + CBB external_identity, context; + if (!CBB_init(imported_id.get(), 2 + cred->epsk_id.size() + 2 + + cred->epsk_context.size() + 2 + 2) || + !CBB_add_u16_length_prefixed(imported_id.get(), &external_identity) || + !CBB_add_bytes(&external_identity, cred->epsk_id.data(), + cred->epsk_id.size()) || + !CBB_add_u16_length_prefixed(imported_id.get(), &context) || + !CBB_add_bytes(&context, cred->epsk_context.data(), + cred->epsk_context.size()) || + !CBB_add_u16(imported_id.get(), protocol) || + !CBB_add_u16(imported_id.get(), target_kdf) || + !CBBFinishArray(imported_id.get(), &ret.imported_identity)) { + return std::nullopt; + } + + ScopedEVP_MD_CTX imported_id_ctx; + InplaceVector<uint8_t, EVP_MAX_MD_SIZE> imported_id_hash; + imported_id_hash.ResizeForOverwrite(EVP_MD_size(cred->epsk_md)); + unsigned imported_id_hash_len; + if (!EVP_Digest(ret.imported_identity.data(), ret.imported_identity.size(), + imported_id_hash.data(), &imported_id_hash_len, cred->epsk_md, + nullptr)) { + return std::nullopt; + } + assert(imported_id_hash.size() == imported_id_hash_len); + + ret.ipskx.ResizeForOverwrite(EVP_MD_size(hkdf_md)); + if (!hkdf_expand_label(Span(ret.ipskx), cred->epsk_md, cred->epskx, + "derived psk", imported_id_hash, + SSL_is_dtls(hs->ssl))) { + return std::nullopt; + } + + return ret; +} + size_t ssl_ech_confirmation_signal_hello_offset(const SSL *ssl) { static_assert(ECH_CONFIRMATION_SIGNAL_LEN < SSL3_RANDOM_SIZE, "the confirmation signal is a suffix of the random");