Implement RFC 9258 as a server Fixed: 369963041 Change-Id: I30fc886360723658f0354a4ca7ce4e5b2a4c7e2c Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/89547 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Lily Chen <chlily@google.com> Commit-Queue: 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 b2028a4..e0159fe 100644 --- a/crypto/err/ssl.errordata +++ b/crypto/err/ssl.errordata
@@ -132,6 +132,7 @@ SSL,183,NO_REQUIRED_DIGEST SSL,184,NO_SHARED_CIPHER SSL,266,NO_SHARED_GROUP +SSL,332,NO_SUPPORTED_PSK_MODE SSL,280,NO_SUPPORTED_VERSIONS_ENABLED SSL,185,NULL_SSL_CTX SSL,186,NULL_SSL_METHOD_PASSED
diff --git a/crypto/mem_internal.h b/crypto/mem_internal.h index 5cc5e5c..ee003f2 100644 --- a/crypto/mem_internal.h +++ b/crypto/mem_internal.h
@@ -87,7 +87,9 @@ namespace internal { // All types with kAllowUniquePtr set may be used with UniquePtr. Other types -// may be C structs which require a |BORINGSSL_MAKE_DELETER| registration. +// may be C structs which require a |BORINGSSL_MAKE_DELETER| registration. Where +// an internal type cannot be annotated (e.g. an alias of std::variant), use +// |BORINGSSL_MAKE_DELETER(T, Delete)|. template <typename T> struct DeleterImpl<T, std::enable_if_t<T::kAllowUniquePtr>> { static void Free(T *t) { Delete(t); }
diff --git a/gen/crypto/err_data.cc b/gen/crypto/err_data.cc index 4d7c79a..9d8e2d2 100644 --- a/gen/crypto/err_data.cc +++ b/gen/crypto/err_data.cc
@@ -202,51 +202,51 @@ 0x283500f7, 0x28358cc1, 0x2836099a, - 0x2c323438, + 0x2c32344e, 0x2c3293f7, - 0x2c333446, - 0x2c33b458, - 0x2c34346c, - 0x2c34b47e, - 0x2c353499, - 0x2c35b4ab, - 0x2c3634db, + 0x2c33345c, + 0x2c33b46e, + 0x2c343482, + 0x2c34b494, + 0x2c3534af, + 0x2c35b4c1, + 0x2c3634f1, 0x2c36833a, - 0x2c3734e8, - 0x2c37b514, - 0x2c383552, - 0x2c38b569, - 0x2c393587, - 0x2c39b597, - 0x2c3a35a9, - 0x2c3ab5bd, - 0x2c3b35ce, - 0x2c3bb5ed, + 0x2c3734fe, + 0x2c37b52a, + 0x2c383568, + 0x2c38b57f, + 0x2c39359d, + 0x2c39b5ad, + 0x2c3a35bf, + 0x2c3ab5d3, + 0x2c3b35e4, + 0x2c3bb603, 0x2c3c1409, 0x2c3c941f, - 0x2c3d3632, + 0x2c3d3648, 0x2c3d9438, - 0x2c3e365c, - 0x2c3eb66a, - 0x2c3f3682, - 0x2c3fb69a, - 0x2c4036c4, + 0x2c3e3672, + 0x2c3eb680, + 0x2c3f3698, + 0x2c3fb6b0, + 0x2c4036da, 0x2c4092ec, - 0x2c4136d5, - 0x2c41b6e8, + 0x2c4136eb, + 0x2c41b6fe, 0x2c4212b2, - 0x2c42b6f9, + 0x2c42b70f, 0x2c43076d, - 0x2c43b5df, - 0x2c443527, - 0x2c44b6a7, - 0x2c4534be, - 0x2c45b4fa, - 0x2c463577, - 0x2c46b601, - 0x2c473616, - 0x2c47b64f, - 0x2c483539, + 0x2c43b5f5, + 0x2c44353d, + 0x2c44b6bd, + 0x2c4534d4, + 0x2c45b510, + 0x2c46358d, + 0x2c46b617, + 0x2c47362c, + 0x2c47b665, + 0x2c48354f, 0x30320000, 0x30328015, 0x3033001f, @@ -469,74 +469,74 @@ 0x405b25b0, 0x405ba5c1, 0x405c25d4, - 0x405ca613, - 0x405d2620, - 0x405da645, - 0x405e2683, + 0x405ca629, + 0x405d2636, + 0x405da65b, + 0x405e2699, 0x405e8afe, - 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, + 0x405f26e8, + 0x405fa6f5, + 0x40602703, + 0x4060a725, + 0x40612799, + 0x4061a7d1, + 0x406227e8, + 0x4062a7f9, + 0x40632846, + 0x4063a85b, + 0x40642872, + 0x4064a89e, + 0x406528b9, + 0x4065a8d0, + 0x406628e8, + 0x4066a912, + 0x4067293d, + 0x4067a982, + 0x406829ca, + 0x4068a9eb, + 0x40692a1d, + 0x4069aa4b, + 0x406a2a6c, + 0x406aaa8c, + 0x406b2c14, + 0x406bac37, + 0x406c2c4d, + 0x406caf57, + 0x406d2f86, + 0x406dafae, + 0x406e2fdc, + 0x406eb029, + 0x406f3082, + 0x406fb0ba, + 0x407030cd, + 0x4070b0ea, 0x4071084d, - 0x4071b0e6, - 0x407230f9, - 0x4072b12f, - 0x40733147, + 0x4071b0fc, + 0x4072310f, + 0x4072b145, + 0x4073315d, 0x40739621, - 0x4074315b, - 0x4074b175, - 0x40753186, - 0x4075b19a, - 0x407631a8, + 0x40743171, + 0x4074b18b, + 0x4075319c, + 0x4075b1b0, + 0x407631be, 0x407693af, - 0x407731cd, - 0x4077b229, - 0x40783244, - 0x4078b27d, - 0x40793294, - 0x4079b2aa, - 0x407a32d6, - 0x407ab2e9, - 0x407b32fe, - 0x407bb310, - 0x407c3341, - 0x407cb34a, - 0x407d29f0, + 0x407731e3, + 0x4077b23f, + 0x4078325a, + 0x4078b293, + 0x407932aa, + 0x4079b2c0, + 0x407a32ec, + 0x407ab2ff, + 0x407b3314, + 0x407bb326, + 0x407c3357, + 0x407cb360, + 0x407d2a06, 0x407da293, - 0x407e3259, + 0x407e326f, 0x407ea525, 0x407f1e86, 0x407fa069, @@ -544,58 +544,58 @@ 0x40809eae, 0x40812393, 0x4081a170, - 0x40822fb1, + 0x40822fc7, 0x40829c01, 0x40832500, - 0x4083a86d, + 0x4083a883, 0x40841ed2, 0x4084a55d, 0x408525e5, - 0x4085a74a, - 0x40862665, + 0x4085a760, + 0x4086267b, 0x4086a2c8, - 0x40872ff7, - 0x4087a798, + 0x4087300d, + 0x4087a7ae, 0x40881c3f, - 0x4088a97f, + 0x4088a995, 0x40891c8e, 0x40899c1b, - 0x408a2c6f, + 0x408a2c85, 0x408a9a39, - 0x408b3325, - 0x408bb081, - 0x408c25f5, + 0x408b333b, + 0x408bb097, + 0x408c260b, 0x408d1fba, 0x408d9f04, 0x408e20ea, 0x408ea450, - 0x408f2993, - 0x408fa766, - 0x40902948, - 0x4090a637, - 0x40912c57, + 0x408f29a9, + 0x408fa77c, + 0x4090295e, + 0x4090a64d, + 0x40912c6d, 0x40919a71, 0x40921cdb, - 0x4092b032, - 0x40933112, + 0x4092b048, + 0x40933128, 0x4093a2d9, 0x40941ee6, - 0x4094ac88, - 0x409527f4, - 0x4095b2b6, - 0x40962fde, + 0x4094ac9e, + 0x4095280a, + 0x4095b2cc, + 0x40962ff4, 0x4096a21e, 0x40972359, 0x4097a139, 0x40981d3b, - 0x4098a808, - 0x4099304e, + 0x4098a81e, + 0x40993064, 0x4099a47d, 0x409a2416, 0x409a9a55, 0x409b1f40, 0x409b9f6b, - 0x409c320b, + 0x409c3221, 0x409c9f93, 0x409d21da, 0x409da186, @@ -608,48 +608,49 @@ 0x40a121a1, 0x40a1a571, 0x40a222f5, - 0x40a2a6c3, - 0x40a32737, - 0x40a3b1ef, + 0x40a2a6d9, + 0x40a3274d, + 0x40a3b205, 0x40a4233f, 0x40a4a1b8, 0x40a51ec2, 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, + 0x40a625f5, + 0x41f42b3f, + 0x41f92bd1, + 0x41fe2ac4, + 0x41fead7a, + 0x41ff2ea8, + 0x42032b58, + 0x42082b7a, + 0x4208abb6, + 0x42092aa8, + 0x4209abf0, + 0x420a2aff, + 0x420aaadf, + 0x420b2b1f, + 0x420bab98, + 0x420c2ec4, + 0x420cacae, + 0x420d2d61, + 0x420dad98, + 0x42122dcb, + 0x42172e8b, + 0x4217ae0d, + 0x421c2e2f, + 0x421f2dea, + 0x42212f3c, + 0x42262e6e, + 0x422b2f1a, + 0x422bad3c, + 0x422c2efc, + 0x422cacef, + 0x422d2cc8, + 0x422daedb, + 0x422e2d1b, + 0x42302e4a, + 0x4230adb2, + 0x423126ba, 0x44320778, 0x44328787, 0x44330793, @@ -705,71 +706,71 @@ 0x4c419501, 0x4c42166a, 0x4c429449, - 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, + 0x50323721, + 0x5032b730, + 0x5033373b, + 0x5033b74b, + 0x50343764, + 0x5034b77e, + 0x5035378c, + 0x5035b7a2, + 0x503637b4, + 0x5036b7ca, + 0x503737e3, + 0x5037b7f6, + 0x5038380e, + 0x5038b81f, + 0x50393834, + 0x5039b848, + 0x503a3868, + 0x503ab87e, + 0x503b3896, + 0x503bb8a8, + 0x503c38c4, + 0x503cb8db, + 0x503d38f4, + 0x503db90a, + 0x503e3917, + 0x503eb92d, + 0x503f393f, 0x503f83b3, - 0x5040393c, - 0x5040b94c, - 0x50413966, - 0x5041b975, - 0x5042398f, - 0x5042b9ac, - 0x504339bc, - 0x5043b9cc, - 0x504439e9, + 0x50403952, + 0x5040b962, + 0x5041397c, + 0x5041b98b, + 0x504239a5, + 0x5042b9c2, + 0x504339d2, + 0x5043b9e2, + 0x504439ff, 0x50448469, - 0x504539fd, - 0x5045ba1b, - 0x50463a2e, - 0x5046ba44, - 0x50473a56, - 0x5047ba6b, - 0x50483a91, - 0x5048ba9f, - 0x50493ab2, - 0x5049bac7, - 0x504a3add, - 0x504abaed, - 0x504b3b0d, - 0x504bbb20, - 0x504c3b43, - 0x504cbb71, - 0x504d3b9e, - 0x504dbbbb, - 0x504e3bd6, - 0x504ebbf2, - 0x504f3c04, - 0x504fbc1b, - 0x50503c2a, + 0x50453a13, + 0x5045ba31, + 0x50463a44, + 0x5046ba5a, + 0x50473a6c, + 0x5047ba81, + 0x50483aa7, + 0x5048bab5, + 0x50493ac8, + 0x5049badd, + 0x504a3af3, + 0x504abb03, + 0x504b3b23, + 0x504bbb36, + 0x504c3b59, + 0x504cbb87, + 0x504d3bb4, + 0x504dbbd1, + 0x504e3bec, + 0x504ebc08, + 0x504f3c1a, + 0x504fbc31, + 0x50503c40, 0x50508729, - 0x50513c3d, - 0x5051b9db, - 0x50523b83, + 0x50513c53, + 0x5051b9f1, + 0x50523b99, 0x58321011, 0x68320fd3, 0x68328d2b, @@ -814,19 +815,19 @@ 0x7c3212c8, 0x80321514, 0x80328090, - 0x80333407, + 0x8033341d, 0x803380b9, - 0x80343416, - 0x8034b37e, - 0x8035339c, - 0x8035b42a, - 0x803633de, - 0x8036b38d, - 0x803733d0, - 0x8037b36b, - 0x803833f1, - 0x8038b3ad, - 0x803933c2, + 0x8034342c, + 0x8034b394, + 0x803533b2, + 0x8035b440, + 0x803633f4, + 0x8036b3a3, + 0x803733e6, + 0x8037b381, + 0x80383407, + 0x8038b3c3, + 0x803933d8, 0x84320bb0, 0x84328bc9, }; @@ -1305,6 +1306,7 @@ "NO_REQUIRED_DIGEST\0" "NO_SHARED_CIPHER\0" "NO_SHARED_GROUP\0" + "NO_SUPPORTED_PSK_MODE\0" "NO_SUPPORTED_VERSIONS_ENABLED\0" "NULL_SSL_CTX\0" "NULL_SSL_METHOD_PASSED\0"
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index fcfd67d..96e6c2a 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h
@@ -3755,9 +3755,6 @@ // 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); @@ -3892,10 +3889,9 @@ // 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. +// secret. For example, an application may export a PSK from a PAKE connection +// with |SSL_export_keying_material|, and then pass the result to +// |SSL_CREDENTIAL_new_pre_shared_key| to authenticate subsequent connections. // // WARNING: PAKE support in TLS is still experimental and may change as the // standard evolves. See @@ -6820,6 +6816,7 @@ #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_NO_SUPPORTED_PSK_MODE 332 #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 79d89a0..84c80ab 100644 --- a/ssl/extensions.cc +++ b/ssl/extensions.cc
@@ -2066,82 +2066,134 @@ return &hs->pre_shared_keys[selected_identity]; } -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, - const SSL_CLIENT_HELLO *client_hello, CBS *contents) { +std::optional<SSLOfferedPSK> SSLOfferedPSKs::Next() { + if (CBS_len(&identities) == 0) { + return std::nullopt; + } + SSLOfferedPSK psk; + if (!CBS_get_u16_length_prefixed(&identities, &psk.identity) || + CBS_len(&psk.identity) == 0 || + !CBS_get_u32(&identities, &psk.obfuscated_ticket_age) || + !CBS_get_u8_length_prefixed(&binders, &psk.binder) || + CBS_len(&psk.binder) == 0) { + // After a successful parse, this should never happen. + return std::nullopt; + } + return psk; +} + +std::optional<SSLOfferedPSKs> ssl_ext_pre_shared_key_parse_clienthello( + SSL_HANDSHAKE *hs, uint8_t *out_alert, const SSL_CLIENT_HELLO *client_hello, + CBS *contents) { // Verify that the pre_shared_key extension is the last extension in // ClientHello. if (CBS_data(contents) + CBS_len(contents) != client_hello->extensions + client_hello->extensions_len) { OPENSSL_PUT_ERROR(SSL, SSL_R_PRE_SHARED_KEY_MUST_BE_LAST); *out_alert = SSL_AD_ILLEGAL_PARAMETER; - return false; + return std::nullopt; } - // We only process the first PSK identity since we don't support pure PSK. - CBS identities, binders; - if (!CBS_get_u16_length_prefixed(contents, &identities) || // - !CBS_get_u16_length_prefixed(&identities, out_ticket) || // - !CBS_get_u32(&identities, out_obfuscated_ticket_age) || // - !CBS_get_u16_length_prefixed(contents, &binders) || // - CBS_len(&binders) == 0 || // + SSLOfferedPSKs psks; + if (!CBS_get_u16_length_prefixed(contents, &psks.identities) || + !CBS_get_u16_length_prefixed(contents, &psks.binders) || + CBS_len(&psks.identities) == 0 || // + CBS_len(&psks.binders) == 0 || // CBS_len(contents) != 0) { OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); *out_alert = SSL_AD_DECODE_ERROR; - return false; + return std::nullopt; } - *out_binders = binders; - - // Check the syntax of the remaining identities, but do not process them. - size_t num_identities = 1; - while (CBS_len(&identities) != 0) { - CBS unused_ticket; - uint32_t unused_obfuscated_ticket_age; - if (!CBS_get_u16_length_prefixed(&identities, &unused_ticket) || - !CBS_get_u32(&identities, &unused_obfuscated_ticket_age)) { + // Check the syntax of the extension. + SSLOfferedPSKs copy = psks; + while (CBS_len(©.identities) != 0 && CBS_len(©.binders) != 0) { + if (!copy.Next()) { OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); *out_alert = SSL_AD_DECODE_ERROR; - return false; + return std::nullopt; } - - num_identities++; } - // Check the syntax of the binders. The value will be checked later if - // resuming. - size_t num_binders = 0; - while (CBS_len(&binders) != 0) { - CBS binder; - if (!CBS_get_u8_length_prefixed(&binders, &binder)) { - OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); - *out_alert = SSL_AD_DECODE_ERROR; - return false; - } - - num_binders++; - } - - if (num_identities != num_binders) { + // We should have run out of identities and binders at the same time. + if (CBS_len(©.identities) != 0 || + CBS_len(©.binders) != 0) { OPENSSL_PUT_ERROR(SSL, SSL_R_PSK_IDENTITY_BINDER_COUNT_MISMATCH); *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return std::nullopt; + } + + return psks; +} + +bool ssl_verify_psk_binder(SSL_HANDSHAKE *hs, uint8_t *out_alert, + const SSLPreSharedKey &psk, + const SSL_CLIENT_HELLO &client_hello) { + CBS pre_shared_key; + if (!ssl_client_hello_get_extension(&client_hello, &pre_shared_key, + TLSEXT_TYPE_pre_shared_key)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_EXTENSION); + *out_alert = SSL_AD_MISSING_EXTENSION; return false; } - return true; + std::optional<SSLOfferedPSKs> offered_psks = + ssl_ext_pre_shared_key_parse_clienthello(hs, out_alert, &client_hello, + &pre_shared_key); + if (!offered_psks) { + return false; + } + const size_t binders_len = + 2 /* length prefix */ + CBS_len(&offered_psks->binders); + + Span<const uint8_t> identity = ssl_pre_shared_key_identity(psk); + for (uint16_t index = 0; true; index++) { + std::optional<SSLOfferedPSK> offered_psk = offered_psks->Next(); + if (!offered_psk) { + OPENSSL_PUT_ERROR(SSL, SSL_R_PSK_IDENTITY_NOT_FOUND); + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return false; + } + + if (offered_psk->identity != identity) { + continue; + } + + hs->selected_psk_index = index; + uint8_t verify_data[EVP_MAX_MD_SIZE]; + size_t verify_data_len; + if (!tls13_psk_binder( + hs, verify_data, &verify_data_len, psk, hs->transcript, + Span(client_hello.client_hello, client_hello.client_hello_len), + binders_len)) { + *out_alert = SSL_AD_INTERNAL_ERROR; + return false; + } + + bool binder_ok = CBS_len(&offered_psk->binder) == verify_data_len && + CRYPTO_memcmp(CBS_data(&offered_psk->binder), verify_data, + verify_data_len) == 0; + if (CRYPTO_fuzzer_mode_enabled()) { + binder_ok = true; + } + if (!binder_ok) { + OPENSSL_PUT_ERROR(SSL, SSL_R_DIGEST_CHECK_FAILED); + *out_alert = SSL_AD_DECRYPT_ERROR; + return false; + } + return true; + } } bool ssl_ext_pre_shared_key_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) { - if (!hs->ssl->s3->session_reused) { + if (!hs->selected_psk_index.has_value()) { return true; } CBB contents; - if (!CBB_add_u16(out, TLSEXT_TYPE_pre_shared_key) || // - !CBB_add_u16_length_prefixed(out, &contents) || // - // We only consider the first identity for resumption - !CBB_add_u16(&contents, 0) || // + if (!CBB_add_u16(out, TLSEXT_TYPE_pre_shared_key) || + !CBB_add_u16_length_prefixed(out, &contents) || + !CBB_add_u16(&contents, *hs->selected_psk_index) || // !CBB_flush(out)) { return false; } @@ -4540,7 +4592,7 @@ enum ssl_ticket_aead_result_t ssl_process_ticket( SSL_HANDSHAKE *hs, UniquePtr<SSL_SESSION> *out_session, bool *out_renew_ticket, Span<const uint8_t> ticket, - Span<const uint8_t> session_id) { + Span<const uint8_t> session_id, bool save_ticket) { SSL *const ssl = hs->ssl; *out_renew_ticket = false; out_session->reset(); @@ -4625,6 +4677,10 @@ return ssl_ticket_aead_ignore_ticket; } + if (save_ticket && !session->ticket.CopyFrom(ticket)) { + return ssl_ticket_aead_error; + } + // Envoy's tests expect the session to have a session ID that matches the // placeholder used by the client. It's unclear whether this is a good idea, // but we maintain it for now.
diff --git a/ssl/internal.h b/ssl/internal.h index 189a904..0c81546 100644 --- a/ssl/internal.h +++ b/ssl/internal.h
@@ -1232,7 +1232,16 @@ uint16_t protocol, const EVP_MD *hkdf_md); +// tls13_compare_imported_psk_identity returns whether |id| is equal to |cred|'s +// imported identity for the specified target protocol and target KDF. This +// allows matching against PSK identities without deriving imported PSK keys. +bool tls13_compare_imported_psk_identity(Span<const uint8_t> id, + const SSL_CREDENTIAL *cred, + uint16_t protocol, + const EVP_MD *hkdf_md); + using SSLPreSharedKey = std::variant<SSLImportedPSK, UniquePtr<SSL_SESSION>>; +BORINGSSL_MAKE_DELETER(SSLPreSharedKey, Delete) // ssl_pre_shared_key_hash return's |psk|'s hash. const EVP_MD *ssl_pre_shared_key_hash(const SSLPreSharedKey &psk); @@ -1240,6 +1249,9 @@ // ssl_pre_shared_key_identity return's |psk|'s identity. Span<const uint8_t> ssl_pre_shared_key_identity(const SSLPreSharedKey &psk); +// ssl_pre_shared_key_secret return's |psk|'s secret. +Span<const uint8_t> ssl_pre_shared_key_secret(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. @@ -1250,13 +1262,6 @@ 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 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); - // Encrypted ClientHello. @@ -1770,6 +1775,13 @@ // pre_shared_keys are the pre-shared keys to be offered by the client. Vector<SSLPreSharedKey> pre_shared_keys; + // pre_shared_key is the selected pre-shared key on the server. + UniquePtr<SSLPreSharedKey> pre_shared_key; + + // selected_psk_index is the index of the selected pre-shared key on the + // server. + std::optional<uint16_t> selected_psk_index; + // transcript is the current handshake transcript. SSLTranscript transcript; @@ -2164,14 +2176,36 @@ Array<uint8_t> *out_secret, uint8_t *out_alert, CBS *contents); +struct SSLOfferedPSK { + CBS identity, binder; + uint32_t obfuscated_ticket_age; +}; + +struct SSLOfferedPSKs { + CBS identities, binders; + std::optional<SSLOfferedPSK> Next(); +}; + 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, - const SSL_CLIENT_HELLO *client_hello, CBS *contents); +std::optional<SSLOfferedPSKs> ssl_ext_pre_shared_key_parse_clienthello( + SSL_HANDSHAKE *hs, uint8_t *out_alert, const SSL_CLIENT_HELLO *client_hello, + CBS *contents); + +// ssl_verify_psk_binder verifies |client_hello| has a valid binder for |psk|. +// The binder is computed with |client_hello| and |hs|'s transcript, which +// should not have |client_hello| in it. On success, it returns true. Otherwise, +// it returns false and sets |*out_alert| to an alert to send. +// +// This function additionally saves the index where |psk| was found in |hs|. It +// must be called before |ssl_ext_pre_shared_key_add_serverhello|. +bool ssl_verify_psk_binder(SSL_HANDSHAKE *hs, uint8_t *out_alert, + const SSLPreSharedKey &psk, + const SSL_CLIENT_HELLO &client_hello); + bool ssl_ext_pre_shared_key_add_serverhello(SSL_HANDSHAKE *hs, CBB *out); + // ssl_is_sct_list_valid does a shallow parse of the SCT list in |contents| and // returns whether it's valid. bool ssl_is_sct_list_valid(const CBS *contents); @@ -3658,10 +3692,13 @@ // |ssl_ticket_aead_retry|: the ticket could not be immediately decrypted. // Retry later. // |ssl_ticket_aead_error|: an error occurred that is fatal to the connection. +// +// If |save_ticket| is true, |*out_session| will have a copy of the ticket saved +// in its |ticket| field. enum ssl_ticket_aead_result_t ssl_process_ticket( SSL_HANDSHAKE *hs, UniquePtr<SSL_SESSION> *out_session, bool *out_renew_ticket, Span<const uint8_t> ticket, - Span<const uint8_t> session_id); + Span<const uint8_t> session_id, bool save_ticket); // tls1_verify_channel_id processes |msg| as a Channel ID message, and verifies // the signature. If the key is valid, it saves the Channel ID and returns true.
diff --git a/ssl/ssl_session.cc b/ssl/ssl_session.cc index c2a4336..528acea 100644 --- a/ssl/ssl_session.cc +++ b/ssl/ssl_session.cc
@@ -599,7 +599,8 @@ if (tickets_supported && CBS_len(&ticket) != 0) { switch (ssl_process_ticket( hs, &session, &renew_ticket, ticket, - Span(client_hello->session_id, client_hello->session_id_len))) { + Span(client_hello->session_id, client_hello->session_id_len), + /*save_ticket=*/false)) { case ssl_ticket_aead_success: break; case ssl_ticket_aead_ignore_ticket:
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index 1eea175..b23fd15 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go
@@ -401,6 +401,7 @@ HasApplicationSettingsOld bool // whether ALPS old codepoint was negotiated PeerApplicationSettingsOld []byte // the old application settings received from the peer ECHAccepted bool // whether ECH was accepted on this connection + SelectedPSK *Credential // the selected PSK, if any } // ClientAuthType declares the policy the server will follow for @@ -561,7 +562,11 @@ Time func() time.Time // Credential contains the credential to present to the other side of - // the connection. Server configurations must include this field. + // the connection. Server configurations must include this field. We only + // support one credential because, except for PSKs, offered credentials do + // not appear on the wire, and tests already know which credential to + // expect to use. For offering multiple PSKs, use the PSKCredentials + // field. Credential *Credential // RootCAs defines the set of root certificate authorities @@ -697,13 +702,17 @@ RequestChannelID bool // PreSharedKey, if not nil, is the pre-shared key to use with - // the PSK cipher suites. + // TLS 1.2 PSK cipher suites. PreSharedKey []byte // PreSharedKeyIdentity, if not empty, is the identity to use - // with the PSK cipher suites. + // with TLS 1.2 PSK cipher suites. PreSharedKeyIdentity string + // PSKCredentials, if not empty, is a list of TLS 1.3 PSK credentials to + // offer as a client. + PSKCredentials []*Credential + // MaxEarlyDataSize controls the maximum number of bytes that the // server will accept in early data and advertise in a // NewSessionTicketMsg. If 0, no early data will be accepted and @@ -1940,9 +1949,13 @@ // rejected. See RFC 8701. ExpectGREASE bool - // OmitPSKsOnSecondClientHello, if true, causes the client to omit the + // OmitPSKsOnSecondClientHello causes the client to delete the specified + // number of PSKs, from the front, on the second ClientHello. + OmitPSKsOnSecondClientHello int + + // OmitAllPSKsOnSecondClientHello, if true, causes the client to omit the // PSK extension on the second ClientHello. - OmitPSKsOnSecondClientHello bool + OmitAllPSKsOnSecondClientHello bool // OnlyCorruptSecondPSKBinder, if true, causes the options below to // only apply to the second PSK binder. @@ -2416,6 +2429,17 @@ PSKIdentity []byte PSKHash crypto.Hash PSKContext []byte + // ImportTargetPSKHashes, if not empty, causes the PSK to be imported + // with the specified set of target PSK hashes, instead of the default + // set. To test unknown hashes, zero is interpreted as SHA-256 with the + // wrong codepoint. + ImportTargetPSKHashes []crypto.Hash + // ImportTargetPSKProtocol, if non-zero, causes the imported PSK + // identity use the specified value instead of the protocol. + ImportTargetPSKProtocol uint16 + // AppendToImportedPSKIdentity is a byte string that is appended to the + // imported PSK identity. + AppendToImportedPSKIdentity []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/conn.go b/ssl/test/runner/conn.go index 377011c..54bb7b2 100644 --- a/ssl/test/runner/conn.go +++ b/ssl/test/runner/conn.go
@@ -155,6 +155,8 @@ // echAccepted indicates whether ECH was accepted for this connection. echAccepted bool + selectedPSK *Credential + tmp [16]byte } @@ -1925,6 +1927,7 @@ state.HasApplicationSettingsOld = c.hasApplicationSettingsOld state.PeerApplicationSettingsOld = c.peerApplicationSettingsOld state.ECHAccepted = c.echAccepted + state.SelectedPSK = c.selectedPSK } return state
diff --git a/ssl/test/runner/fuzzer_mode.json b/ssl/test/runner/fuzzer_mode.json index f895546..5186df0 100644 --- a/ssl/test/runner/fuzzer_mode.json +++ b/ssl/test/runner/fuzzer_mode.json
@@ -32,6 +32,7 @@ "ShimTicketRewritable*": "Fuzzer mode does not encrypt tickets.", "Resume-Server-*Binder*": "Fuzzer mode does not check binders.", + "PSK-Server-*Binder*": "Fuzzer mode does not check binders.", "SkipEarlyData*": "Trial decryption does not work with the NULL cipher.", "EarlyDataChannelID-OfferBoth-Server-*": "Trial decryption does not work with the NULL cipher.",
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go index 48089cb..aafa6e7 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go
@@ -158,6 +158,24 @@ if session != nil && (session.vers.protocolVersion() >= VersionTLS13 || c.config.Bugs.SendBothTickets) { hs.preSharedKeys = append(hs.preSharedKeys, newClientSessionPSK(session)) } + pskCreds := c.config.PSKCredentials + if c.config.Credential != nil && c.config.Credential.Type == CredentialTypePreSharedKey { + pskCreds = append(slices.Clone(pskCreds), c.config.Credential) + } + for _, cred := range pskCreds { + targetProtocol := version{VersionTLS13} + if c.isDTLS { + targetProtocol = version{VersionDTLS13} + } + if len(cred.ImportTargetPSKHashes) != 0 { + for _, targetHash := range cred.ImportTargetPSKHashes { + hs.preSharedKeys = append(hs.preSharedKeys, importPSK(cred, targetProtocol, targetHash)) + } + } else { + hs.preSharedKeys = append(hs.preSharedKeys, importPSK(cred, targetProtocol, crypto.SHA256)) + hs.preSharedKeys = append(hs.preSharedKeys, importPSK(cred, targetProtocol, crypto.SHA384)) + } + } // Set up ECH parameters. var err error @@ -1139,30 +1157,32 @@ // Resolve PSK and compute the early secret. zeroSecret := hs.finishedHash.zeroSecret() - pskSecret := zeroSecret + var psk *preSharedKey if hs.serverHello.hasPSKIdentity { - // We send at most one PSK identity. - if hs.session == nil || hs.serverHello.pskIdentity != 0 { + if int(hs.serverHello.pskIdentity) >= len(hs.preSharedKeys) { c.sendAlert(alertUnknownPSKIdentity) return errors.New("tls: server sent unknown PSK identity") } - if hs.session.vers != c.vers { + psk = hs.preSharedKeys[hs.serverHello.pskIdentity] + if psk.version != c.vers { c.sendAlert(alertIllegalParameter) - return errors.New("tls: server resumed an invalid session for the protocol version") + return errors.New("tls: server selected an invalid PSK for the protocol version") } - if hs.session.cipherSuite.hash() != hs.suite.hash() { + if psk.hash != hs.suite.hash() { c.sendAlert(alertIllegalParameter) - return errors.New("tls: server resumed an invalid session for the cipher suite") + return errors.New("tls: server selected an invalid PSK for the cipher suite") } - pskSecret = hs.session.secret - c.didResume = true + c.didResume = psk.clientSession != nil + c.selectedPSK = psk.credential + hs.finishedHash.addEntropy(psk.secret) + } else { + hs.finishedHash.addEntropy(zeroSecret) } - hs.finishedHash.addEntropy(pskSecret) sharedSecret := zeroSecret if len(hs.serverHello.pakeMessage) != 0 { - if c.didResume { - return errors.New("server resumed and returned a PAKE extension") + if psk != nil { + return errors.New("server selected PSK and PAKE at the same time") } if hs.pakeContext == nil { return errors.New("server selected a PAKE unexpectedly") @@ -1240,9 +1260,7 @@ 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 { + } else if hs.pakeContext == nil && psk == nil { msg, err := c.readHandshake() if err != nil { return err @@ -1627,14 +1645,18 @@ } // The first ClientHello may have set this due to OnlyCompressSecondClientHelloInner. hello.reorderOuterExtensionsWithoutCompressing = false - if c.config.Bugs.OmitPSKsOnSecondClientHello { + hello.pskIdentities = hello.pskIdentities[c.config.Bugs.OmitPSKsOnSecondClientHello:] + hs.preSharedKeys = hs.preSharedKeys[c.config.Bugs.OmitPSKsOnSecondClientHello:] + if c.config.Bugs.OmitAllPSKsOnSecondClientHello { hello.pskIdentities = nil - hello.pskBinders = nil + hs.preSharedKeys = nil } hello.raw = nil if len(hello.pskIdentities) > 0 { hs.generatePSKBinders(hello, firstHelloBytes, helloRetryRequest.marshal()) + } else { + hello.pskBinders = nil } if outerHello != nil {
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index 82054ac..1acab4a 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go
@@ -625,6 +625,7 @@ } hs.hello.hasPSKIdentity = true hs.hello.pskIdentity = uint16(pskIndex) + c.selectedPSK = config.Credential } if foundKEMode && !config.SessionTicketsDisabled {
diff --git a/ssl/test/runner/prf.go b/ssl/test/runner/prf.go index 9c5354c..3b1a62a 100644 --- a/ssl/test/runner/prf.go +++ b/ssl/test/runner/prf.go
@@ -468,23 +468,27 @@ } type preSharedKey struct { - version version - hash crypto.Hash - identity []byte - secret []byte - binderKey []byte - creationTime time.Time - ticketAgeAdd uint32 + version version + hash crypto.Hash + identity []byte + secret []byte + binderKey []byte + creationTime time.Time + ticketAgeAdd uint32 + clientSession *ClientSessionState + serverSession *sessionState + credential *Credential } func newClientSessionPSK(session *ClientSessionState) *preSharedKey { psk := &preSharedKey{ - version: session.vers, - hash: session.cipherSuite.hash(), - identity: session.sessionTicket, - secret: session.secret, - creationTime: session.ticketCreationTime, - ticketAgeAdd: session.ticketAgeAdd, + version: session.vers, + hash: session.cipherSuite.hash(), + identity: session.sessionTicket, + secret: session.secret, + creationTime: session.ticketCreationTime, + ticketAgeAdd: session.ticketAgeAdd, + clientSession: session, } psk.initBinder(resumptionPSKBinderLabel) return psk @@ -492,12 +496,13 @@ func newServerSessionPSK(ticket []byte, session *sessionState) *preSharedKey { psk := &preSharedKey{ - version: session.vers, - hash: session.cipherSuite.hash(), - identity: ticket, - secret: session.secret, - creationTime: session.ticketCreationTime, - ticketAgeAdd: session.ticketAgeAdd, + version: session.vers, + hash: session.cipherSuite.hash(), + identity: ticket, + secret: session.secret, + creationTime: session.ticketCreationTime, + ticketAgeAdd: session.ticketAgeAdd, + serverSession: session, } psk.initBinder(resumptionPSKBinderLabel) return psk @@ -532,6 +537,10 @@ var targetKDF uint16 switch targetHash { + case 0: + // We treat zero as some unrecognized hash value for testing. + targetKDF = 0x1234 + targetHash = crypto.SHA256 case crypto.SHA256: targetKDF = kdfHKDFWithSHA256 case crypto.SHA384: @@ -540,13 +549,19 @@ panic("unrecogized target HKDF hash") } + targetProtocolValue := targetProtocol.wire + if cred.ImportTargetPSKProtocol != 0 { + targetProtocolValue = cred.ImportTargetPSKProtocol + } + // See RFC 9258, Section 5.1. identity := (&importedPSKIdentity{ externalIdentity: cred.PSKIdentity, context: cred.PSKContext, - targetProtocol: targetProtocol.wire, + targetProtocol: targetProtocolValue, targetKDF: targetKDF, }).marshal() + identity = append(identity, cred.AppendToImportedPSKIdentity...) h := cred.PSKHash.New() h.Write(identity) @@ -561,10 +576,11 @@ ipskx := hkdfExpandLabel(targetProtocol, cred.PSKHash, epskx, derivedPSKLabel, identityHash, targetHash.Size()) psk := &preSharedKey{ - version: targetProtocol, - hash: targetHash, - identity: identity, - secret: ipskx, + version: targetProtocol, + hash: targetHash, + identity: identity, + secret: ipskx, + credential: cred, } psk.initBinder(importedPSKBinderLabel) return psk
diff --git a/ssl/test/runner/psk_tests.go b/ssl/test/runner/psk_tests.go index 36f6de6..ada0a3b 100644 --- a/ssl/test/runner/psk_tests.go +++ b/ssl/test/runner/psk_tests.go
@@ -46,6 +46,13 @@ PSKContext: []byte("context2"), PSKHash: crypto.SHA384, } + pskSHA256Credential2 := Credential{ + Type: CredentialTypePreSharedKey, + PreSharedKey: slices.Repeat([]byte{'I', 'J', 'K', 'L'}, 8), + PSKIdentity: []byte("psk3"), + PSKContext: []byte("context3"), + PSKHash: crypto.SHA256, + } hashToPSK := func(hash crypto.Hash) *Credential { switch hash { @@ -90,6 +97,23 @@ // resumption connections. flags: []string{"-expect-no-peer-cert"}, }) + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-%s-%s-%s", hashToString(pskHash), hashToString(cipherHash), protocol), + config: Config{ + Credential: psk, + MaxVersion: VersionTLS13, + CipherSuites: []uint16{cipher}, + }, + shimCredentials: []*Credential{psk}, + expectations: connectionExpectations{ + selectedPSK: psk, + }, + // Also test that the resulting session can be reused. + resumeSession: true, + resumeExpectations: &connectionExpectations{}, + }) // Test with HelloRetryRequest to ensure the client computes // the second ClientHello's binder correctly, and also accounts @@ -392,5 +416,485 @@ expectedError: ":UNEXPECTED_MESSAGE:", expectedLocalError: "remote error: unexpected message", }) + + // If a server is configured to request client certificates, it should + // still not do so when negotiating a PSK. + testCases = append(testCases, testCase{ + protocol: protocol, + name: fmt.Sprintf("PSK-Server-DoNotRequestClientCertificate-%s", protocol), + testType: serverTest, + config: Config{ + Credential: &pskSHA256Credential, + MaxVersion: VersionTLS13, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + flags: []string{"-require-any-client-certificate"}, + }) + + // The server should notice if the second binder is wrong. + for _, secondBinder := range []bool{false, true} { + binderStr := "FirstBinder" + var defaultCurves []CurveID + if secondBinder { + binderStr = "SecondBinder" + // Force a HelloRetryRequest by predicting an empty curve list. + defaultCurves = []CurveID{} + } + + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: fmt.Sprintf("PSK-Server-BinderWrongLength-%s-%s", binderStr, protocol), + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + DefaultCurves: defaultCurves, + Bugs: ProtocolBugs{ + SendShortPSKBinder: true, + OnlyCorruptSecondPSKBinder: secondBinder, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedLocalError: "remote error: error decrypting message", + expectedError: ":DIGEST_CHECK_FAILED:", + }) + + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: fmt.Sprintf("PSK-Server-NoPSKBinder-%s-%s", binderStr, protocol), + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + DefaultCurves: defaultCurves, + Bugs: ProtocolBugs{ + SendNoPSKBinder: true, + OnlyCorruptSecondPSKBinder: secondBinder, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedLocalError: "remote error: error decoding message", + expectedError: ":DECODE_ERROR:", + }) + + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: fmt.Sprintf("PSK-Server-ExtraPSKBinder-%s-%s", binderStr, protocol), + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + DefaultCurves: defaultCurves, + Bugs: ProtocolBugs{ + SendExtraPSKBinder: true, + OnlyCorruptSecondPSKBinder: secondBinder, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedLocalError: "remote error: illegal parameter", + expectedError: ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:", + }) + + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: fmt.Sprintf("PSK-Server-ExtraIdentityNoBinder-%s-%s", binderStr, protocol), + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + DefaultCurves: defaultCurves, + Bugs: ProtocolBugs{ + ExtraPSKIdentity: true, + OnlyCorruptSecondPSKBinder: secondBinder, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedLocalError: "remote error: illegal parameter", + expectedError: ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:", + }) + + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: fmt.Sprintf("PSK-Server-InvalidPSKBinder-%s-%s", binderStr, protocol), + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + DefaultCurves: defaultCurves, + Bugs: ProtocolBugs{ + SendInvalidPSKBinder: true, + OnlyCorruptSecondPSKBinder: secondBinder, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedLocalError: "remote error: error decrypting message", + expectedError: ":DIGEST_CHECK_FAILED:", + }) + + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: fmt.Sprintf("PSK-Server-PSKBinderFirstExtension-%s-%s", binderStr, protocol), + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + DefaultCurves: defaultCurves, + Bugs: ProtocolBugs{ + PSKBinderFirst: true, + OnlyCorruptSecondPSKBinder: secondBinder, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedLocalError: "remote error: illegal parameter", + expectedError: ":PRE_SHARED_KEY_MUST_BE_LAST:", + }) + } + + // The server can defer configuring PSKs to either the early callback + // or the SSL_CTX_set_cert_cb callback. (-async causes the shim to defer + // installing credentials. -use-early-callback controls which callback + // installs it.) + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-CertCallback-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + flags: []string{"-async", "-expect-selected-credential", "0"}, + expectations: connectionExpectations{ + selectedPSK: &pskSHA256Credential, + }, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-EarlyCallback-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + flags: []string{"-async", "-use-early-callback", "-expect-selected-credential", "0"}, + expectations: connectionExpectations{ + selectedPSK: &pskSHA256Credential, + }, + }) + + // If a server is configured with multiple PSKs, it selects the + // first common one. + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-ConsiderMultiplePSKs-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + PSKCredentials: []*Credential{&pskSHA256Credential, &pskSHA384Credential}, + }, + shimCredentials: []*Credential{&pskSHA256Credential2, &pskSHA384Credential}, + flags: []string{"-expect-selected-credential", "1"}, + expectations: connectionExpectations{ + selectedPSK: &pskSHA384Credential, + }, + }) + + // The client and server have no PSKs in common. + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-NoCommonPSKs-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + PSKCredentials: []*Credential{&pskSHA256Credential, &pskSHA384Credential}, + }, + shimCredentials: []*Credential{&pskSHA256Credential2}, + shouldFail: true, + expectedError: ":PSK_IDENTITY_NOT_FOUND:", + expectedLocalError: "remote error: handshake failure", + }) + + // If the server sends HelloRetryRequest, the client may filter its PSK list + // based on the selected cipher. The server must send its PSK index based + // on the second list, not the first. + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-HRR-UpdateIndex-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + PSKCredentials: []*Credential{&pskSHA256Credential, &pskSHA384Credential}, + DefaultCurves: []CurveID{}, // Trigger HRR + Bugs: ProtocolBugs{ + OmitPSKsOnSecondClientHello: 1, + }, + }, + shimCredentials: []*Credential{&pskSHA384Credential}, + flags: []string{"-expect-selected-credential", "0"}, + expectations: connectionExpectations{ + selectedPSK: &pskSHA384Credential, + }, + }) + + // If the PSK is missing from the second ClientHello, the server should + // reject the connection. + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-HRR-PSKMissing-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + PSKCredentials: []*Credential{&pskSHA256Credential, &pskSHA384Credential}, + DefaultCurves: []CurveID{}, // Trigger HRR + Bugs: ProtocolBugs{ + OmitPSKsOnSecondClientHello: 2, // Delete both SHA-256 and SHA-384 variants. + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedLocalError: "remote error: illegal parameter", + expectedError: ":PSK_IDENTITY_NOT_FOUND:", + }) + + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: fmt.Sprintf("PSK-Server-OmitAllPSKsOnSecondClientHello-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + Credential: &pskSHA256Credential, + DefaultCurves: []CurveID{}, // Trigger HRR + Bugs: ProtocolBugs{ + OmitAllPSKsOnSecondClientHello: true, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedLocalError: "remote error: missing extension", + expectedError: ":MISSING_EXTENSION:", + }) + + // The imported PSK must match exactly, or it is not in common. + extraBytes := pskSHA256Credential + extraBytes.AppendToImportedPSKIdentity = []byte("extra") + wrongHash := pskSHA256Credential + wrongHash.ImportTargetPSKHashes = []crypto.Hash{0} + wrongProtocol := pskSHA256Credential + wrongProtocol.ImportTargetPSKProtocol = 0x1234 + wrongProtocol2 := pskSHA256Credential + wrongProtocol2.ImportTargetPSKProtocol = VersionDTLS13 + wrongContext := pskSHA256Credential + wrongContext.PSKContext = []byte("wrong context") + if protocol == dtls { + wrongProtocol2.ImportTargetPSKProtocol = VersionTLS13 + } + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-IdentityDoesNotMatch-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + PSKCredentials: []*Credential{ + &extraBytes, + &wrongHash, + &wrongProtocol, + &wrongProtocol2, + &wrongContext, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedError: ":PSK_IDENTITY_NOT_FOUND:", + expectedLocalError: "remote error: handshake failure", + }) + + // If multiple PSKs match, the server's order is used. + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-ServerPreferenceOrder-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + PSKCredentials: []*Credential{&pskSHA384Credential, &pskSHA256Credential}, + }, + shimCredentials: []*Credential{&pskSHA256Credential, &pskSHA384Credential}, + flags: []string{"-expect-selected-credential", "0"}, + expectations: connectionExpectations{ + selectedPSK: &pskSHA256Credential, + }, + }) + + // Servers can be configured with both PSKs and certificates, + // in which case they evaluate based on their preference order. + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-PSKOrCert-PSK-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + PSKCredentials: []*Credential{&pskSHA256Credential}, + }, + shimCredentials: []*Credential{ + &pskSHA256Credential, + &rsaCertificate, + }, + // The ClientHello works for both, but the shim should + // pick the PSK. + flags: []string{"-expect-selected-credential", "0"}, + expectations: connectionExpectations{ + selectedPSK: &pskSHA256Credential, + }, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-PSKOrCert-Cert-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + PSKCredentials: []*Credential{&pskSHA384Credential}, // Wrong PSK + }, + shimCredentials: []*Credential{ + &pskSHA256Credential, + &rsaCertificate, + }, + // The ClientHello is not good for the PSK, so the shim + // should pick the certificate. + flags: []string{"-expect-selected-credential", "1"}, + expectations: connectionExpectations{ + peerCertificate: &rsaCertificate, + }, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-CertOrPSK-Cert-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + PSKCredentials: []*Credential{&pskSHA256Credential}, + }, + shimCredentials: []*Credential{ + &rsaCertificate, + &pskSHA256Credential, + }, + // The ClientHello works for both, but the shim should + // pick the certificate. + flags: []string{"-expect-selected-credential", "0"}, + expectations: connectionExpectations{ + peerCertificate: &rsaCertificate, + }, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-CertOrPSK-PSK-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + PSKCredentials: []*Credential{&pskSHA256Credential}, + VerifySignatureAlgorithms: []signatureAlgorithm{}, // No common algs + }, + shimCredentials: []*Credential{ + &rsaCertificate, + &pskSHA256Credential, + }, + // The ClientHello is not good for the certficate, so the + // shim should pick the PSK. + flags: []string{"-expect-selected-credential", "1"}, + expectations: connectionExpectations{ + selectedPSK: &pskSHA256Credential, + }, + }) + + // Clients should import PSKs for each of their supported ciphers. + // If one does not, the server should skip any PSKs that do not + // work with the chosen cipher. + importSHA384Only := pskSHA256Credential + importSHA384Only.ImportTargetPSKHashes = []crypto.Hash{crypto.SHA384} + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-PartialImport-Match-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{TLS_AES_256_GCM_SHA384}, + PSKCredentials: []*Credential{&importSHA384Only}, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + flags: []string{"-expect-selected-credential", "0"}, + expectations: connectionExpectations{ + selectedPSK: &importSHA384Only, + }, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-PartialImport-NoMatch-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{TLS_AES_128_GCM_SHA256}, + PSKCredentials: []*Credential{&importSHA384Only}, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedError: ":PSK_IDENTITY_NOT_FOUND:", + expectedLocalError: "remote error: handshake failure", + }) + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-PartialImport-NoMatch-Fallback-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{TLS_AES_128_GCM_SHA256}, + PSKCredentials: []*Credential{&importSHA384Only}, + }, + shimCredentials: []*Credential{&pskSHA256Credential, &rsaCertificate}, + flags: []string{"-expect-selected-credential", "1"}, + expectations: connectionExpectations{ + peerCertificate: &rsaCertificate, + }, + }) + + // We only implement psk_dhe_ke. If the client does not offer it, PSKs + // are not eligible. + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-MissingPSKMode-NoMatch-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + PSKCredentials: []*Credential{&pskSHA256Credential}, + Bugs: ProtocolBugs{ + SendPSKKeyExchangeModes: []byte{0x1a}, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential}, + shouldFail: true, + expectedError: ":NO_SUPPORTED_PSK_MODE:", + expectedLocalError: "remote error: handshake failure", + }) + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: fmt.Sprintf("PSK-Server-MissingPSKMode-NoMatch-Fallback-%s", protocol), + config: Config{ + MaxVersion: VersionTLS13, + PSKCredentials: []*Credential{&pskSHA256Credential}, + Bugs: ProtocolBugs{ + SendPSKKeyExchangeModes: []byte{0x1a}, + }, + }, + shimCredentials: []*Credential{&pskSHA256Credential, &rsaCertificate}, + flags: []string{"-expect-selected-credential", "1"}, + expectations: connectionExpectations{ + peerCertificate: &rsaCertificate, + }, + }) } }
diff --git a/ssl/test/runner/resumption_tests.go b/ssl/test/runner/resumption_tests.go index ccb3053..efae49d 100644 --- a/ssl/test/runner/resumption_tests.go +++ b/ssl/test/runner/resumption_tests.go
@@ -575,18 +575,18 @@ testCases = append(testCases, testCase{ testType: serverTest, - name: "Resume-Server-OmitPSKsOnSecondClientHello", + name: "Resume-Server-OmitAllPSKsOnSecondClientHello", resumeSession: true, config: Config{ MaxVersion: VersionTLS13, DefaultCurves: []CurveID{}, Bugs: ProtocolBugs{ - OmitPSKsOnSecondClientHello: true, + OmitAllPSKsOnSecondClientHello: true, }, }, shouldFail: true, - expectedLocalError: "remote error: illegal parameter", - expectedError: ":INCONSISTENT_CLIENT_HELLO:", + expectedLocalError: "remote error: missing extension", + expectedError: ":MISSING_EXTENSION:", }) }
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index ebc1f46..99019fa 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go
@@ -453,6 +453,9 @@ // serverNameAck, if not nil, is whether the server should have acknowledged // the server name. This field is only checked in full handshakes. serverNameAck *bool + // selectedPSK, if not nil, is the PSK credential that should have been + // negotiated. + selectedPSK *Credential } type testCase struct { @@ -945,6 +948,13 @@ } } + if expectations.selectedPSK != nil && expectations.selectedPSK != connState.SelectedPSK { + if connState.SelectedPSK == nil { + return errors.New("tls: expected a PSK, but none was selected") + } + return errors.New("tls: connection selected a different PSK from what was expected") + } + if test.exportKeyingMaterial > 0 { actual := make([]byte, test.exportKeyingMaterial) if _, err := io.ReadFull(tlsConn, actual); err != nil {
diff --git a/ssl/tls13_enc.cc b/ssl/tls13_enc.cc index c24cf58..9d28be5 100644 --- a/ssl/tls13_enc.cc +++ b/ssl/tls13_enc.cc
@@ -518,6 +518,14 @@ return std::get<UniquePtr<SSL_SESSION>>(psk)->ticket; } +Span<const uint8_t> ssl_pre_shared_key_secret(const SSLPreSharedKey &psk) { + if (const auto *imported = std::get_if<SSLImportedPSK>(&psk); + imported != nullptr) { + return imported->ipskx; + } + return std::get<UniquePtr<SSL_SESSION>>(psk)->secret; +} + bool tls13_psk_binder(const SSL_HANDSHAKE *hs, Span<uint8_t> out, size_t *out_len, const SSLPreSharedKey &psk, const SSLTranscript &transcript, @@ -590,36 +598,16 @@ return true; } -bool tls13_verify_psk_binder(const SSL_HANDSHAKE *hs, - const SSL_SESSION *session, const SSLMessage &msg, - CBS *binders) { - uint8_t verify_data[EVP_MAX_MD_SIZE]; - size_t verify_data_len; - CBS binder; - // 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, - 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)) { - OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); - return false; +static std::optional<uint16_t> hkdf_md_to_kdf_id(const EVP_MD *hkdf_md) { + // See Section 10 of RFC 9258. + switch (EVP_MD_nid(hkdf_md)) { + case NID_sha256: + return 0x0001; // HKDF_SHA256 + case NID_sha384: + return 0x0002; // HKDF_SHA384 + default: + return std::nullopt; } - - bool binder_ok = - CBS_len(&binder) == verify_data_len && - CRYPTO_memcmp(CBS_data(&binder), verify_data, verify_data_len) == 0; - if (CRYPTO_fuzzer_mode_enabled()) { - binder_ok = true; - } - if (!binder_ok) { - OPENSSL_PUT_ERROR(SSL, SSL_R_DIGEST_CHECK_FAILED); - return false; - } - - return true; } std::optional<SSLImportedPSK> tls13_derive_imported_psk( @@ -627,18 +615,10 @@ 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; + std::optional<uint16_t> target_kdf = hkdf_md_to_kdf_id(hkdf_md); + if (!target_kdf.has_value()) { + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return std::nullopt; } SSLImportedPSK ret; @@ -658,7 +638,7 @@ !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) || + !CBB_add_u16(imported_id.get(), *target_kdf) || !CBBFinishArray(imported_id.get(), &ret.imported_identity)) { return std::nullopt; } @@ -684,6 +664,28 @@ return ret; } +bool tls13_compare_imported_psk_identity(Span<const uint8_t> id, + const SSL_CREDENTIAL *cred, + uint16_t protocol, + const EVP_MD *hkdf_md) { + assert(cred->type == SSLCredentialType::kPreSharedKey); + std::optional<uint16_t> target_kdf = hkdf_md_to_kdf_id(hkdf_md); + if (!target_kdf.has_value()) { + return false; + } + + // See Section 5.1 of RFC 9258. + CBS cbs = id, external_identity, context; + uint16_t found_protocol, found_kdf; + return CBS_get_u16_length_prefixed(&cbs, &external_identity) && + external_identity == Span(cred->epsk_id) && + CBS_get_u16_length_prefixed(&cbs, &context) && + context == Span(cred->epsk_context) && + CBS_get_u16(&cbs, &found_protocol) && found_protocol == protocol && + CBS_get_u16(&cbs, &found_kdf) && found_kdf == *target_kdf && + CBS_len(&cbs) == 0; +} + 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");
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc index c365c04..4b6d840 100644 --- a/ssl/tls13_server.cc +++ b/ssl/tls13_server.cc
@@ -307,15 +307,59 @@ return true; } +static bool check_psk_credential(SSL_HANDSHAKE *hs, const SSL_CREDENTIAL *cred, + const std::optional<SSLOfferedPSKs> &psks) { + assert(cred->type == SSLCredentialType::kPreSharedKey); + SSL *const ssl = hs->ssl; + if (!psks) { + OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_EXTENSION); + return false; + } + if (!hs->accept_psk_mode) { + OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SUPPORTED_PSK_MODE); + return false; + } + + // Look for a matching PSK. + const EVP_MD *md = + ssl_get_handshake_digest(ssl_protocol_version(ssl), hs->new_cipher); + SSLOfferedPSKs copy = *psks; + for (;;) { + std::optional<SSLOfferedPSK> psk = copy.Next(); + if (!psk) { + OPENSSL_PUT_ERROR(SSL, SSL_R_PSK_IDENTITY_NOT_FOUND); + return false; + } + if (tls13_compare_imported_psk_identity(psk->identity, cred, + ssl->s3->version, md)) { + 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. + // the common handshake logic. Resolve the remaining non-resumption + // parameters. First, parse out another copy of the ClientHello and important + // extensions. SSL *const ssl = hs->ssl; SSLMessage msg; SSL_CLIENT_HELLO client_hello; if (!hs->GetClientHello(&msg, &client_hello)) { return ssl_hs_error; } + std::optional<SSLOfferedPSKs> psks; + CBS psk_ext; + uint8_t alert = SSL_AD_DECODE_ERROR; + if (ssl_client_hello_get_extension(&client_hello, &psk_ext, + TLSEXT_TYPE_pre_shared_key)) { + psks = ssl_ext_pre_shared_key_parse_clienthello(hs, &alert, &client_hello, + &psk_ext); + if (!psks) { + ssl_send_alert(ssl, SSL3_AL_FATAL, alert); + return ssl_hs_error; + } + } if (SSL_is_quic(ssl) && client_hello.session_id_len > 0) { OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_COMPATIBILITY_MODE); @@ -331,6 +375,15 @@ Span(client_hello.session_id, client_hello.session_id_len)); } + // Negotiate the cipher suite. This must happen before negotiating PSKs. + hs->new_cipher = choose_tls13_cipher(ssl, &client_hello); + if (hs->new_cipher == nullptr) { + OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER); + ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE); + return ssl_hs_error; + } + + // Select the credential to use. Array<SSL_CREDENTIAL *> creds; if (!ssl_get_full_credential_list(hs, &creds)) { return ssl_hs_error; @@ -340,8 +393,6 @@ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); return ssl_hs_error; } - - // Select the credential to use. for (SSL_CREDENTIAL *cred : creds) { ERR_clear_error(); if (cred->type == SSLCredentialType::kSPAKE2PlusV1Server) { @@ -358,6 +409,22 @@ } break; } + } else if (cred->type == SSLCredentialType::kPreSharedKey) { + if (check_psk_credential(hs, cred, psks)) { + const EVP_MD *md = + ssl_get_handshake_digest(ssl_protocol_version(ssl), hs->new_cipher); + std::optional<SSLImportedPSK> imported = + tls13_derive_imported_psk(hs, cred, ssl->s3->version, md); + if (!imported) { + return ssl_hs_error; + } + hs->credential = UpRef(cred); + hs->pre_shared_key = MakeUnique<SSLPreSharedKey>(*std::move(imported)); + if (hs->pre_shared_key == nullptr) { + return ssl_hs_error; + } + break; + } } else { uint16_t sigalg; if (check_signature_credential(hs, cred, &sigalg)) { @@ -374,17 +441,8 @@ return ssl_hs_error; } - // Negotiate the cipher suite. - hs->new_cipher = choose_tls13_cipher(ssl, &client_hello); - if (hs->new_cipher == nullptr) { - OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER); - ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE); - return ssl_hs_error; - } - // HTTP/2 negotiation depends on the cipher suite, so ALPN negotiation was // deferred. Complete it now. - uint8_t alert = SSL_AD_DECODE_ERROR; if (!ssl_negotiate_alpn(hs, &alert, &client_hello)) { ssl_send_alert(ssl, SSL3_AL_FATAL, alert); return ssl_hs_error; @@ -423,11 +481,9 @@ return ssl_ticket_aead_error; } - CBS ticket, binders; - uint32_t client_ticket_age; - if (!ssl_ext_pre_shared_key_parse_clienthello( - hs, &ticket, &binders, &client_ticket_age, out_alert, client_hello, - &pre_shared_key)) { + std::optional<SSLOfferedPSKs> psks = ssl_ext_pre_shared_key_parse_clienthello( + hs, out_alert, client_hello, &pre_shared_key); + if (!psks) { return ssl_ticket_aead_error; } @@ -442,12 +498,21 @@ return ssl_ticket_aead_ignore_ticket; } - // TLS 1.3 session tickets are renewed separately as part of the - // NewSessionTicket. + // We only consider the first PSK for session resumption. + std::optional<SSLOfferedPSK> psk = psks->Next(); + if (!psk) { + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return ssl_ticket_aead_error; + } + + // Ignore whether the caller asked for TLS 1.2 ticket renewal. TLS 1.3 session + // tickets are renewed separately as part of the NewSessionTicket. Also save + // the ticket so we can find the PSK again on the second ClientHello. bool unused_renew; UniquePtr<SSL_SESSION> session; enum ssl_ticket_aead_result_t ret = - ssl_process_ticket(hs, &session, &unused_renew, ticket, {}); + ssl_process_ticket(hs, &session, &unused_renew, psk->identity, + /*session_id=*/{}, /*save_ticket=*/true); switch (ret) { case ssl_ticket_aead_success: break; @@ -465,7 +530,8 @@ } // Recover the client ticket age and convert to seconds. - client_ticket_age -= session->ticket_age_add; + uint32_t client_ticket_age = + psk->obfuscated_ticket_age - session->ticket_age_add; client_ticket_age /= 1000; OPENSSL_timeval now = ssl_ctx_get_current_time(ssl->ctx.get()); @@ -482,13 +548,6 @@ *out_ticket_age_skew = static_cast<int32_t>(client_ticket_age) - static_cast<int32_t>(server_ticket_age); - - // Check the PSK binder. - if (!tls13_verify_psk_binder(hs, session.get(), msg, &binders)) { - *out_alert = SSL_AD_DECRYPT_ERROR; - return ssl_ticket_aead_error; - } - *out_session = std::move(session); return ssl_ticket_aead_success; } @@ -510,6 +569,10 @@ return true; } +static bool using_certificate(const SSL_HANDSHAKE *hs) { + return !hs->pre_shared_key && !hs->pake_verifier; +} + static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) { SSL *const ssl = hs->ssl; SSLMessage msg; @@ -541,12 +604,15 @@ return ssl_hs_error; } - ssl->s3->session_reused = true; - hs->can_release_private_key = true; - // 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->s3->session_reused = true; + hs->pre_shared_key = MakeUnique<SSLPreSharedKey>(UpRef(session)); + if (hs->pre_shared_key == nullptr) { + return ssl_hs_error; + } break; case ssl_ticket_aead_error: @@ -558,6 +624,8 @@ return ssl_hs_pending_ticket; } + hs->can_release_private_key = !using_certificate(hs); + // Negotiate ALPS now, after ALPN is negotiated and |hs->new_session| is // initialized. if (!ssl_negotiate_alps(hs, &alert, &client_hello)) { @@ -661,13 +729,20 @@ return ssl_hs_error; } + if (hs->pre_shared_key && + !ssl_verify_psk_binder(hs, &alert, *hs->pre_shared_key, client_hello)) { + ssl_send_alert(ssl, SSL3_AL_FATAL, alert); + return ssl_hs_error; + } + size_t hash_len = EVP_MD_size( ssl_get_handshake_digest(ssl_protocol_version(ssl), hs->new_cipher)); // Set up the key schedule and incorporate the PSK into the running secret. - if (!tls13_init_key_schedule(hs, ssl->s3->session_reused - ? Span(hs->new_session->secret) - : Span(kZeroes, hash_len)) || + Span<const uint8_t> psk = hs->pre_shared_key + ? ssl_pre_shared_key_secret(*hs->pre_shared_key) + : Span(kZeroes, hash_len); + if (!tls13_init_key_schedule(hs, psk) || // !ssl_hash_message(hs, msg)) { return ssl_hs_error; } @@ -851,33 +926,15 @@ // // We do, however, check the second PSK binder. This covers the client key // share, in case we ever send half-RTT data (we currently do not). It is also - // a tricky computation, so we enforce the peer handled it correctly. - if (ssl->s3->session_reused) { - CBS pre_shared_key; - if (!ssl_client_hello_get_extension(&client_hello, &pre_shared_key, - TLSEXT_TYPE_pre_shared_key)) { - OPENSSL_PUT_ERROR(SSL, SSL_R_INCONSISTENT_CLIENT_HELLO); - ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); - return ssl_hs_error; - } - - CBS ticket, binders; - uint32_t client_ticket_age; + // a tricky computation, so we enforce the peer handled it correctly. It is + // also necessary to search the PSK list from the second ClientHello because + // PSK indices may have changed (RFC 8446 section 4.1.4). + if (hs->pre_shared_key) { uint8_t alert = SSL_AD_DECODE_ERROR; - if (!ssl_ext_pre_shared_key_parse_clienthello( - hs, &ticket, &binders, &client_ticket_age, &alert, &client_hello, - &pre_shared_key)) { + if (!ssl_verify_psk_binder(hs, &alert, *hs->pre_shared_key, client_hello)) { ssl_send_alert(ssl, SSL3_AL_FATAL, alert); return ssl_hs_error; } - - // Note it is important that we do not obtain a new |SSL_SESSION| from - // |ticket|. We have already selected parameters based on the first - // ClientHello (in the transcript) and must not switch partway through. - if (!tls13_verify_psk_binder(hs, hs->new_session.get(), msg, &binders)) { - ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR); - return ssl_hs_error; - } } // Although a server could HelloRetryRequest with PAKEs to request a cookie, @@ -988,7 +1045,7 @@ return ssl_hs_error; } - if (!ssl->s3->session_reused && !hs->pake_verifier) { + if (using_certificate(hs)) { // Determine whether to request a client certificate. hs->cert_request = !!(hs->config->verify_mode & SSL_VERIFY_PEER); } @@ -1027,7 +1084,7 @@ } // Send the server Certificate message, if necessary. - if (!ssl->s3->session_reused && !hs->pake_verifier) { + if (using_certificate(hs)) { if (!tls13_add_certificate(hs)) { return ssl_hs_error; }