From a4fec0377a11b3ec66e333d8446a5db87568319f Mon Sep 17 00:00:00 2001 From: Will Childs-Klein Date: Tue, 14 Jan 2025 11:24:58 -0500 Subject: [PATCH] Allow TLS PSK without server certificate (#2083) While we currently support TLS [pre-shared keys (PSKs)][1] for TLS 1.2, our python integration tests have shown that we don't currently support TLS 1.2 PSKs on the server side unless a certificate has been configured. PSKs can be used for both secret establishment and authentication, so there likely exist legitimate use-cases for establishing PSK connections without certificates. CPython's integration tests [expect this][2] behavior. This gap appears to be incidental, not intentional. Unrelated [OCSP work][3] introduced a requirement that a valid certificate public key is loaded on the server before we sort out handshake parameters. If that load fails, the hanshake is aborted before PSK can be negotiated for secret establishment. To fix this, we simply check whether the server has a PSK callback enabled, and allow the certificate loading to fail if so. We also handle the potential nullity of the public key. This PR only applies to TLSv1.2 PSK. TLSv1.3 moved PSK negotiation earlier in the handshake to client/server hello's, so does utilize this code path. Currently, we only support TLSv1.3 PSK for session resumption, _not "pure" PSK used for initial shared secret establishment. I have [a branch][4] where I'm implementing this, but it's non-trivial. We won't be able to delete the TLSv1.3 PSK python test patch until we implement "pure" TLSv1.3 PSK. [1]: https://datatracker.ietf.org/doc/html/rfc4279 [2]: https://github.com/python/cpython/blob/aeb9b65aa26444529e4adc7d6e5b0d3dd9889ec2/Lib/test/test_ssl.py#L4395 [3]: https://github.com/aws/aws-lc/pull/1120/files#diff-2743f6406343b4e3d671a71af35cf44a36ad77cef1b6cfbc02f25dc732915ee9R790 [4]: https://github.com/WillChilds-Klein/aws-lc/tree/tls13-pure-psk --- ssl/handshake_server.cc | 12 +++++++----- ssl/ssl_privkey.cc | 1 + .../python_patch/main/aws-lc-cpython.patch | 17 ----------------- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc index be9dc6b8d6..55e47f49e4 100644 --- a/ssl/handshake_server.cc +++ b/ssl/handshake_server.cc @@ -793,11 +793,13 @@ static enum ssl_hs_wait_t do_select_certificate(SSL_HANDSHAKE *hs) { } } - // Load |hs->local_pubkey| from the cert prematurely. The certificate could be - // subject to change once we negotiate signature algorithms later. If it - // changes to another leaf certificate the server and client has support for, - // we reload it. - if (!ssl_handshake_load_local_pubkey(hs)) { + // Load |hs->local_pubkey| from the cert (if present) prematurely. The + // certificate could be subject to change once we negotiate signature + // algorithms later. If it changes to another leaf certificate the server and + // client has support for, we reload it. The public key may only be absent if + // PSK is enabled on the server, as indicated by presense of a callback. + if (!ssl_handshake_load_local_pubkey(hs) && + !(hs->local_pubkey == nullptr && hs->config->psk_server_callback)) { return ssl_hs_error; } diff --git a/ssl/ssl_privkey.cc b/ssl/ssl_privkey.cc index 6806e54a3c..f329f82948 100644 --- a/ssl/ssl_privkey.cc +++ b/ssl/ssl_privkey.cc @@ -385,6 +385,7 @@ static bool ssl_public_key_rsa_pss_check(EVP_PKEY *pubkey, uint16_t sigalg) { static bool tls12_pkey_supports_cipher_auth(SSL_HANDSHAKE *hs, const EVP_PKEY *key) { + GUARD_PTR(key); SSL *const ssl = hs->ssl; // We may have a private key that supports the signature algorithm, but we // need to verify that the negotiated cipher allows it. This behavior is only diff --git a/tests/ci/integration/python_patch/main/aws-lc-cpython.patch b/tests/ci/integration/python_patch/main/aws-lc-cpython.patch index b59d0f09dd..73b5dfce02 100644 --- a/tests/ci/integration/python_patch/main/aws-lc-cpython.patch +++ b/tests/ci/integration/python_patch/main/aws-lc-cpython.patch @@ -27,23 +27,6 @@ index 0e50d09..f4b7b3c 100644 def test_dh_params(self): # Check we can get a connection with ephemeral Diffie-Hellman client_context, server_context, hostname = testing_context() -@@ -4364,14 +4366,14 @@ def test_session_handling(self): - def test_psk(self): - psk = bytes.fromhex('deadbeef') - -- client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) -+ client_context, server_context, _ = testing_context() -+ - client_context.check_hostname = False - client_context.verify_mode = ssl.CERT_NONE - client_context.maximum_version = ssl.TLSVersion.TLSv1_2 - client_context.set_ciphers('PSK') - client_context.set_psk_client_callback(lambda hint: (None, psk)) - -- server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) - server_context.maximum_version = ssl.TLSVersion.TLSv1_2 - server_context.set_ciphers('PSK') - server_context.set_psk_server_callback(lambda identity: psk) @@ -4443,14 +4445,14 @@ def server_callback(identity): self.assertEqual(identity, client_identity) return psk