Skip to content

Commit cf76174

Browse files
[3.7] bpo-37428: Don't set PHA verify flag on client side (GH-14421) (GH-14493)
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 <christian@python.org> https://bugs.python.org/issue37428 (cherry picked from commit f0f5930) Co-authored-by: Christian Heimes <christian@python.org> https://bugs.python.org/issue37428
1 parent e2e41cd commit cf76174

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
@@ -4437,6 +4437,37 @@ def test_pha_not_tls13(self):
44374437
s.write(b'PHA')
44384438
self.assertIn(b'WRONG_SSL_VERSION', s.recv(1024))
44394439

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

44414472
def test_main(verbose=False):
44424473
if support.verbose:
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
@@ -931,6 +931,26 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
931931
SSL_set_mode(self->ssl,
932932
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_AUTO_RETRY);
933933

934+
#ifdef TLS1_3_VERSION
935+
if (sslctx->post_handshake_auth == 1) {
936+
if (socket_type == PY_SSL_SERVER) {
937+
/* bpo-37428: OpenSSL does not ignore SSL_VERIFY_POST_HANDSHAKE.
938+
* Set SSL_VERIFY_POST_HANDSHAKE flag only for server sockets and
939+
* only in combination with SSL_VERIFY_PEER flag. */
940+
int mode = SSL_get_verify_mode(self->ssl);
941+
if (mode & SSL_VERIFY_PEER) {
942+
int (*verify_cb)(int, X509_STORE_CTX *) = NULL;
943+
verify_cb = SSL_get_verify_callback(self->ssl);
944+
mode |= SSL_VERIFY_POST_HANDSHAKE;
945+
SSL_set_verify(self->ssl, mode, verify_cb);
946+
}
947+
} else {
948+
/* client socket */
949+
SSL_set_post_handshake_auth(self->ssl, 1);
950+
}
951+
}
952+
#endif
953+
934954
if (server_hostname != NULL) {
935955
if (_ssl_configure_hostname(self, server_hostname) < 0) {
936956
Py_DECREF(self);
@@ -2928,10 +2948,10 @@ _set_verify_mode(PySSLContext *self, enum py_ssl_cert_requirements n)
29282948
"invalid value for verify_mode");
29292949
return -1;
29302950
}
2931-
#ifdef TLS1_3_VERSION
2932-
if (self->post_handshake_auth)
2933-
mode |= SSL_VERIFY_POST_HANDSHAKE;
2934-
#endif
2951+
2952+
/* bpo-37428: newPySSLSocket() sets SSL_VERIFY_POST_HANDSHAKE flag for
2953+
* server sockets and SSL_set_post_handshake_auth() for client. */
2954+
29352955
/* keep current verify cb */
29362956
verify_cb = SSL_CTX_get_verify_callback(self->ctx);
29372957
SSL_CTX_set_verify(self->ctx, mode, verify_cb);
@@ -3628,8 +3648,6 @@ get_post_handshake_auth(PySSLContext *self, void *c) {
36283648
#if TLS1_3_VERSION
36293649
static int
36303650
set_post_handshake_auth(PySSLContext *self, PyObject *arg, void *c) {
3631-
int (*verify_cb)(int, X509_STORE_CTX *) = NULL;
3632-
int mode = SSL_CTX_get_verify_mode(self->ctx);
36333651
if (arg == NULL) {
36343652
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
36353653
return -1;
@@ -3641,17 +3659,8 @@ set_post_handshake_auth(PySSLContext *self, PyObject *arg, void *c) {
36413659
}
36423660
self->post_handshake_auth = pha;
36433661

3644-
/* client-side socket setting, ignored by server-side */
3645-
SSL_CTX_set_post_handshake_auth(self->ctx, pha);
3646-
3647-
/* server-side socket setting, ignored by client-side */
3648-
verify_cb = SSL_CTX_get_verify_callback(self->ctx);
3649-
if (pha) {
3650-
mode |= SSL_VERIFY_POST_HANDSHAKE;
3651-
} else {
3652-
mode ^= SSL_VERIFY_POST_HANDSHAKE;
3653-
}
3654-
SSL_CTX_set_verify(self->ctx, mode, verify_cb);
3662+
/* bpo-37428: newPySSLSocket() sets SSL_VERIFY_POST_HANDSHAKE flag for
3663+
* server sockets and SSL_set_post_handshake_auth() for client. */
36553664

36563665
return 0;
36573666
}

0 commit comments

Comments
 (0)