Skip to content

Commit f0f5930

Browse files
tiranmiss-islington
authored andcommitted
bpo-37428: Don't set PHA verify flag on client side (GH-14421)
SSLContext.post_handshake_auth = True no longer sets SSL_VERIFY_POST_HANDSHAKE verify flag for client connections. Although the option is documented as ignored for clients, OpenSSL implicitly enables cert chain validation when the flag is set. Signed-off-by: Christian Heimes <[email protected]> https://bugs.python.org/issue37428
1 parent 12b436e commit f0f5930

File tree

3 files changed

+61
-17
lines changed

3 files changed

+61
-17
lines changed

Lib/test/test_ssl.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4434,6 +4434,37 @@ def test_pha_not_tls13(self):
44344434
s.write(b'PHA')
44354435
self.assertIn(b'WRONG_SSL_VERSION', s.recv(1024))
44364436

4437+
def test_bpo37428_pha_cert_none(self):
4438+
# verify that post_handshake_auth does not implicitly enable cert
4439+
# validation.
4440+
hostname = SIGNED_CERTFILE_HOSTNAME
4441+
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
4442+
client_context.post_handshake_auth = True
4443+
client_context.load_cert_chain(SIGNED_CERTFILE)
4444+
# no cert validation and CA on client side
4445+
client_context.check_hostname = False
4446+
client_context.verify_mode = ssl.CERT_NONE
4447+
4448+
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
4449+
server_context.load_cert_chain(SIGNED_CERTFILE)
4450+
server_context.load_verify_locations(SIGNING_CA)
4451+
server_context.post_handshake_auth = True
4452+
server_context.verify_mode = ssl.CERT_REQUIRED
4453+
4454+
server = ThreadedEchoServer(context=server_context, chatty=False)
4455+
with server:
4456+
with client_context.wrap_socket(socket.socket(),
4457+
server_hostname=hostname) as s:
4458+
s.connect((HOST, server.port))
4459+
s.write(b'HASCERT')
4460+
self.assertEqual(s.recv(1024), b'FALSE\n')
4461+
s.write(b'PHA')
4462+
self.assertEqual(s.recv(1024), b'OK\n')
4463+
s.write(b'HASCERT')
4464+
self.assertEqual(s.recv(1024), b'TRUE\n')
4465+
# server cert has not been validated
4466+
self.assertEqual(s.getpeercert(), {})
4467+
44374468

44384469
HAS_KEYLOG = hasattr(ssl.SSLContext, 'keylog_filename')
44394470
requires_keylog = unittest.skipUnless(
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
SSLContext.post_handshake_auth = True no longer sets
2+
SSL_VERIFY_POST_HANDSHAKE verify flag for client connections. Although the
3+
option is documented as ignored for clients, OpenSSL implicitly enables cert
4+
chain validation when the flag is set.

Modules/_ssl.c

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,26 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
963963
SSL_set_mode(self->ssl,
964964
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_AUTO_RETRY);
965965

966+
#ifdef TLS1_3_VERSION
967+
if (sslctx->post_handshake_auth == 1) {
968+
if (socket_type == PY_SSL_SERVER) {
969+
/* bpo-37428: OpenSSL does not ignore SSL_VERIFY_POST_HANDSHAKE.
970+
* Set SSL_VERIFY_POST_HANDSHAKE flag only for server sockets and
971+
* only in combination with SSL_VERIFY_PEER flag. */
972+
int mode = SSL_get_verify_mode(self->ssl);
973+
if (mode & SSL_VERIFY_PEER) {
974+
int (*verify_cb)(int, X509_STORE_CTX *) = NULL;
975+
verify_cb = SSL_get_verify_callback(self->ssl);
976+
mode |= SSL_VERIFY_POST_HANDSHAKE;
977+
SSL_set_verify(self->ssl, mode, verify_cb);
978+
}
979+
} else {
980+
/* client socket */
981+
SSL_set_post_handshake_auth(self->ssl, 1);
982+
}
983+
}
984+
#endif
985+
966986
if (server_hostname != NULL) {
967987
if (_ssl_configure_hostname(self, server_hostname) < 0) {
968988
Py_DECREF(self);
@@ -2986,10 +3006,10 @@ _set_verify_mode(PySSLContext *self, enum py_ssl_cert_requirements n)
29863006
"invalid value for verify_mode");
29873007
return -1;
29883008
}
2989-
#ifdef TLS1_3_VERSION
2990-
if (self->post_handshake_auth)
2991-
mode |= SSL_VERIFY_POST_HANDSHAKE;
2992-
#endif
3009+
3010+
/* bpo-37428: newPySSLSocket() sets SSL_VERIFY_POST_HANDSHAKE flag for
3011+
* server sockets and SSL_set_post_handshake_auth() for client. */
3012+
29933013
/* keep current verify cb */
29943014
verify_cb = SSL_CTX_get_verify_callback(self->ctx);
29953015
SSL_CTX_set_verify(self->ctx, mode, verify_cb);
@@ -3735,8 +3755,6 @@ get_post_handshake_auth(PySSLContext *self, void *c) {
37353755
#if TLS1_3_VERSION
37363756
static int
37373757
set_post_handshake_auth(PySSLContext *self, PyObject *arg, void *c) {
3738-
int (*verify_cb)(int, X509_STORE_CTX *) = NULL;
3739-
int mode = SSL_CTX_get_verify_mode(self->ctx);
37403758
if (arg == NULL) {
37413759
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
37423760
return -1;
@@ -3748,17 +3766,8 @@ set_post_handshake_auth(PySSLContext *self, PyObject *arg, void *c) {
37483766
}
37493767
self->post_handshake_auth = pha;
37503768

3751-
/* client-side socket setting, ignored by server-side */
3752-
SSL_CTX_set_post_handshake_auth(self->ctx, pha);
3753-
3754-
/* server-side socket setting, ignored by client-side */
3755-
verify_cb = SSL_CTX_get_verify_callback(self->ctx);
3756-
if (pha) {
3757-
mode |= SSL_VERIFY_POST_HANDSHAKE;
3758-
} else {
3759-
mode ^= SSL_VERIFY_POST_HANDSHAKE;
3760-
}
3761-
SSL_CTX_set_verify(self->ctx, mode, verify_cb);
3769+
/* bpo-37428: newPySSLSocket() sets SSL_VERIFY_POST_HANDSHAKE flag for
3770+
* server sockets and SSL_set_post_handshake_auth() for client. */
37623771

37633772
return 0;
37643773
}

0 commit comments

Comments
 (0)