Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3324381

Browse files
committed
bpo-18233: Review of SSLSocket.get_peer_cert_chain and SSLSocket.get_verified_chain
1 parent a7c641d commit 3324381

File tree

6 files changed

+201
-107
lines changed

6 files changed

+201
-107
lines changed

Doc/library/ssl.rst

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,20 +1259,39 @@ SSL sockets also have the following additional methods and attributes:
12591259
.. versionchanged:: 3.9
12601260
IPv6 address strings no longer have a trailing new line.
12611261

1262-
.. method:: SSLSocket.getpeercertchain(binary_form=False, validate=True)
1262+
.. method:: SSLSocket.get_peer_cert_chain(binary_form=False)
12631263

1264-
Returns certificate chain for the peer. If no chain is provided, returns
1265-
None. Otherwise returns a tuple of dicts containing information about the
1266-
certificates. The chain starts with the leaf certificate and ends with the
1267-
root certificate. If called on the client side, the leaf certificate is the
1268-
peer's certificate.
1264+
Returns an **unverified** certificate chain for the peer. If no chain is
1265+
provided, returns :const:`None`. Otherwise returns a tuple of dicts
1266+
containing information about the certificates. The chain starts with the
1267+
leaf certificate and ends with the root certificate. Return :const:`None`
1268+
if the session is resumed as peers do not send certificates.
12691269

1270-
If the optional argument *binary_form* is True, return a list of *binary_form*-encoded copies
1271-
of the certificates.
1272-
If the optional argument *validate* is False, return the peer's cert chain
1273-
without any validation and without the root CA cert.");
1270+
If the ``binary_form`` parameter is :const:`True`, and a chain is available,
1271+
this method returns a tuple with each element corresponding to the
1272+
DER-encoded form of the entire certificate as a sequence of bytes.
12741273

1275-
.. versionadded:: 3.9
1274+
.. versionadded:: 3.9
1275+
1276+
.. warning::
1277+
This is not a verified chain. See :meth:`ssl.SSLSocket.get_verified_chain`.
1278+
1279+
.. method:: SSLSocket.get_verified_chain(binary_form=False)
1280+
1281+
Returns a verified certificate chain for the peer. If no chain is provided,
1282+
returns :const:`None`. Otherwise returns a tuple of dicts containing
1283+
information about the certificates. The chain starts with the leaf
1284+
certificate and ends with the root certificate. Return :const:`None` if the
1285+
session is resumed as peers do not send certificates.
1286+
1287+
If the ``binary_form`` parameter is :const:`True`, and a chain is available,
1288+
this method returns a tuple with each element corresponding to the
1289+
DER-encoded form of the entire certificate as a sequence of bytes.
1290+
1291+
.. versionadded:: 3.9
1292+
1293+
.. note::
1294+
This features requires OpenSSL 1.1.0 or newer.
12761295

12771296
.. method:: SSLSocket.cipher()
12781297

Lib/ssl.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -905,12 +905,20 @@ def getpeercert(self, binary_form=False):
905905
"""
906906
return self._sslobj.getpeercert(binary_form)
907907

908-
def getpeercertchain(self, binary_form=False, validate=True):
909-
""""Returns the certificate chain of the SSL connection
910-
as tuple of dicts.
908+
def get_peer_cert_chain(self, binary_form=False):
909+
""""Returns the certificate chain of the SSL connection as a tuple of
910+
dicts. It is *not* a verified chain.
911911
912-
Return None if no chain is provieded."""
913-
return self._sslobj.getpeercertchain(binary_form, validate)
912+
Return ``None`` if no chain is provided."""
913+
return self._sslobj.get_peer_cert_chain(binary_form)
914+
915+
if hasattr(_ssl._SSLSocket, 'get_verified_chain'):
916+
def get_verified_chain(self, binary_form=False):
917+
""""Returns the verified certificate chain of the SSL connection as a
918+
tuple of dicts.
919+
920+
Return ``None`` if no chain is provided."""
921+
return self._sslobj.get_verified_chain(binary_form)
914922

915923
def selected_npn_protocol(self):
916924
"""Return the currently selected NPN protocol as a string, or ``None``
@@ -1131,10 +1139,17 @@ def getpeercert(self, binary_form=False):
11311139
return self._sslobj.getpeercert(binary_form)
11321140

11331141
@_sslcopydoc
1134-
def getpeercertchain(self, binary_form=False, validate=True):
1142+
def get_peer_cert_chain(self, binary_form=False):
11351143
self._checkClosed()
11361144
self._check_connected()
1137-
return self._sslobj.getpeercertchain(binary_form, validate)
1145+
return self._sslobj.get_peer_cert_chain(binary_form)
1146+
1147+
if hasattr(_ssl._SSLSocket, 'get_verified_chain'):
1148+
@_sslcopydoc
1149+
def get_verified_chain(self, binary_form=False):
1150+
self._checkClosed()
1151+
self._check_connected()
1152+
return self._sslobj.get_verified_chain(binary_form)
11381153

11391154
@_sslcopydoc
11401155
def selected_npn_protocol(self):

Lib/test/test_ssl.py

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2160,7 +2160,29 @@ def test_get_ca_certs_capath(self):
21602160
self.assertTrue(cert)
21612161
self.assertEqual(len(ctx.get_ca_certs()), 1)
21622162

2163-
def test_getpeercertchain(self):
2163+
def test_get_peer_cert_chain(self):
2164+
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
2165+
ctx.verify_mode = ssl.CERT_REQUIRED
2166+
ctx.load_verify_locations(capath=CAPATH)
2167+
with ctx.wrap_socket(socket.socket(socket.AF_INET)) as s:
2168+
s.connect(self.server_addr)
2169+
try:
2170+
peer_cert = s.getpeercert()
2171+
peer_cert_bin = s.getpeercert(True)
2172+
chain_no_validate = s.get_peer_cert_chain()
2173+
chain_bin_no_validate = s.get_peer_cert_chain(True)
2174+
finally:
2175+
self.assertTrue(peer_cert)
2176+
self.assertTrue(peer_cert_bin)
2177+
2178+
# ca cert
2179+
ca_certs = ctx.get_ca_certs()
2180+
self.assertEqual(len(ca_certs), 1)
2181+
2182+
self.assertEqual(chain_no_validate, (peer_cert,))
2183+
self.assertEqual(chain_bin_no_validate, (peer_cert_bin,))
2184+
2185+
def test_get_verified_chain(self):
21642186
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
21652187
ctx.verify_mode = ssl.CERT_REQUIRED
21662188
ctx.load_verify_locations(capath=CAPATH)
@@ -2170,17 +2192,10 @@ def test_getpeercertchain(self):
21702192
peer_cert = s.getpeercert()
21712193
peer_cert_bin = s.getpeercert(True)
21722194
if IS_OPENSSL_1_1_0:
2173-
chain = s.getpeercertchain()
2174-
chain_bin = s.getpeercertchain(True)
2195+
chain = s.get_verified_chain()
2196+
chain_bin = s.get_verified_chain(True)
21752197
else:
2176-
self.assertRaisesRegex(
2177-
Exception, r'only supported by OpenSSL 1\.1\.0',
2178-
s.getpeercertchain)
2179-
self.assertRaisesRegex(
2180-
Exception, r'only supported by OpenSSL 1\.1\.0',
2181-
s.getpeercertchain, True)
2182-
chain_no_validate = s.getpeercertchain(validate=False)
2183-
chain_bin_no_validate = s.getpeercertchain(True, False)
2198+
self.assertFalse(hasattr(s, 'get_verified_chain'))
21842199
finally:
21852200
self.assertTrue(peer_cert)
21862201
self.assertTrue(peer_cert_bin)
@@ -2197,8 +2212,6 @@ def test_getpeercertchain(self):
21972212
if IS_OPENSSL_1_1_0:
21982213
self.assertEqual(chain, (peer_cert, test_get_ca_certsert))
21992214
self.assertEqual(chain_bin, (peer_cert_bin, ca_cert_bin))
2200-
self.assertEqual(chain_no_validate, (peer_cert,))
2201-
self.assertEqual(chain_bin_no_validate, (peer_cert_bin,))
22022215

22032216
@needs_sni
22042217
def test_context_setget(self):
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Add :meth:`ssl.SSLSocket.getpeercertchain` for accessing the certificate chain of SSL connections.
1+
Add :meth:`ssl.SSLSocket.get_peer_cert_chain` and :meth:`ssl.SSLSocket.get_verified_chain` for accessing the certificate chain of SSL connections.

Modules/_ssl.c

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2096,64 +2096,17 @@ _ssl__SSLSocket_cipher_impl(PySSLSocket *self)
20962096
return cipher_to_tuple(current);
20972097
}
20982098

2099-
/*[clinic input]
2100-
_ssl._SSLSocket.getpeercertchain
2101-
der as binary_mode: bool = False
2102-
validate: bool = True
2103-
[clinic start generated code]*/
2104-
21052099
static PyObject *
2106-
_ssl__SSLSocket_getpeercertchain_impl(PySSLSocket *self, int binary_mode,
2107-
int validate)
2108-
/*[clinic end generated code: output=8094e6d78d27eb9a input=f4dcd181d0d163eb]*/
2100+
chain_to_pyobject(STACK_OF(X509) *peer_chain, int binary_mode)
21092101
{
21102102
int len, i;
21112103
PyObject *retval = NULL, *ci=NULL;
2112-
STACK_OF(X509) *peer_chain; /* reference */
2113-
2114-
assert((self->ctx != NULL) && (self->ctx->ctx != NULL));
2115-
if (self->ssl == NULL) {
2116-
Py_RETURN_NONE;
2117-
}
2118-
2119-
/* The peer just transmits the intermediate cert chain EXCLUDING the root
2120-
* CA certificate as this side is suppose to have a copy of the root
2121-
* certificate for verification. */
2122-
if (validate) {
2123-
#ifdef OPENSSL_VERSION_1_1
2124-
peer_chain = SSL_get0_verified_chain(self->ssl);
2125-
long ret = SSL_get_verify_result(self->ssl);
2126-
if (ret != X509_V_OK) {
2127-
#ifdef SSL_R_CERTIFICATE_VERIFY_FAILED
2128-
long e = ERR_PACK(ERR_LIB_SSL, 0, SSL_R_CERTIFICATE_VERIFY_FAILED);
2129-
#else
2130-
long e = ERR_PACK(ERR_LIB_SSL, 0, 134);
2131-
#endif
2132-
fill_and_set_sslerror(self, PySSLCertVerificationErrorObject, PY_SSL_ERROR_SSL, NULL, __LINE__, e);
2133-
return NULL;
2134-
}
2135-
#else
2136-
PyErr_SetString(
2137-
PyExc_Exception,
2138-
"Getting verified certificate chains with SSL_get0_verified_chain"
2139-
" is only supported by OpenSSL 1.1.0 and later");
2140-
return NULL;
2141-
#endif
2142-
} else {
2143-
peer_chain = SSL_get_peer_cert_chain(self->ssl);
2144-
if (self->socket_type == PY_SSL_SERVER) {
2145-
X509 *peer_cert = SSL_get_peer_certificate(self->ssl);
2146-
if (peer_cert != NULL)
2147-
sk_X509_insert(peer_chain, peer_cert, 0);
2148-
}
2149-
}
21502104

21512105
if (peer_chain == NULL) {
21522106
Py_RETURN_NONE;
21532107
}
21542108

21552109
len = sk_X509_num(peer_chain);
2156-
21572110
if ((retval = PyTuple_New(len)) == NULL) {
21582111
return NULL;
21592112
}
@@ -2168,15 +2121,70 @@ _ssl__SSLSocket_getpeercertchain_impl(PySSLSocket *self, int binary_mode,
21682121

21692122
if (ci == NULL) {
21702123
Py_CLEAR(retval);
2171-
goto end;
2124+
break;
21722125
}
21732126
PyTuple_SET_ITEM(retval, i, ci);
21742127
}
21752128

2176-
end:
21772129
return retval;
21782130
}
21792131

2132+
/*[clinic input]
2133+
_ssl._SSLSocket.get_peer_cert_chain
2134+
der as binary_mode: bool = False
2135+
[clinic start generated code]*/
2136+
2137+
static PyObject *
2138+
_ssl__SSLSocket_get_peer_cert_chain_impl(PySSLSocket *self, int binary_mode)
2139+
/*[clinic end generated code: output=2fc1e5dce85798ba input=3dd8f43730febaa9]*/
2140+
{
2141+
STACK_OF(X509) *peer_chain; /* reference */
2142+
2143+
assert((self->ctx != NULL) && (self->ctx->ctx != NULL));
2144+
if (self->ssl == NULL)
2145+
Py_RETURN_NONE;
2146+
2147+
peer_chain = SSL_get_peer_cert_chain(self->ssl);
2148+
/* In OpenSSL only the client side includes the peer certificate.
2149+
* Manually add it if required it to be more consistent. */
2150+
if (self->socket_type == PY_SSL_SERVER) {
2151+
X509 *peer_cert = SSL_get_peer_certificate(self->ssl);
2152+
if (peer_cert != NULL) {
2153+
if (peer_chain == NULL)
2154+
peer_chain = sk_X509_new_null();
2155+
sk_X509_insert(peer_chain, peer_cert, 0);
2156+
}
2157+
}
2158+
return chain_to_pyobject(peer_chain, binary_mode);
2159+
}
2160+
2161+
#ifdef OPENSSL_VERSION_1_1
2162+
/*[clinic input]
2163+
_ssl._SSLSocket.get_verified_chain
2164+
der as binary_mode: bool = False
2165+
[clinic start generated code]*/
2166+
2167+
static PyObject *
2168+
_ssl__SSLSocket_get_verified_chain_impl(PySSLSocket *self, int binary_mode)
2169+
/*[clinic end generated code: output=6e07b709feaeb291 input=8f51efb220ed687f]*/
2170+
{
2171+
STACK_OF(X509) *peer_chain; /* reference */
2172+
2173+
assert((self->ctx != NULL) && (self->ctx->ctx != NULL));
2174+
if (self->ssl == NULL)
2175+
Py_RETURN_NONE;
2176+
2177+
peer_chain = SSL_get0_verified_chain(self->ssl);
2178+
long ret = SSL_get_verify_result(self->ssl);
2179+
if (ret != X509_V_OK) {
2180+
long e = ERR_PACK(ERR_LIB_SSL, 0, SSL_R_CERTIFICATE_VERIFY_FAILED);
2181+
fill_and_set_sslerror(self, PySSLCertVerificationErrorObject, PY_SSL_ERROR_SSL, NULL, __LINE__, e);
2182+
return NULL;
2183+
}
2184+
return chain_to_pyobject(peer_chain, binary_mode);
2185+
}
2186+
#endif
2187+
21802188
/*[clinic input]
21812189
_ssl._SSLSocket.version
21822190
[clinic start generated code]*/
@@ -3081,7 +3089,10 @@ static PyMethodDef PySSLMethods[] = {
30813089
_SSL__SSLSOCKET_GET_CHANNEL_BINDING_METHODDEF
30823090
_SSL__SSLSOCKET_CIPHER_METHODDEF
30833091
_SSL__SSLSOCKET_SHARED_CIPHERS_METHODDEF
3084-
_SSL__SSLSOCKET_GETPEERCERTCHAIN_METHODDEF
3092+
_SSL__SSLSOCKET_GET_PEER_CERT_CHAIN_METHODDEF
3093+
#ifdef OPENSSL_VERSION_1_1
3094+
_SSL__SSLSOCKET_GET_VERIFIED_CHAIN_METHODDEF
3095+
#endif
30853096
_SSL__SSLSOCKET_VERSION_METHODDEF
30863097
_SSL__SSLSOCKET_SELECTED_NPN_PROTOCOL_METHODDEF
30873098
_SSL__SSLSOCKET_SELECTED_ALPN_PROTOCOL_METHODDEF

0 commit comments

Comments
 (0)