mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Issue #8550: Add first class SSLContext
objects to the ssl module.
This commit is contained in:
parent
8eac60d9af
commit
152efa2ae2
7 changed files with 864 additions and 283 deletions
|
@ -36,6 +36,11 @@ additional :meth:`read` and :meth:`write` methods, along with a method,
|
||||||
connection, and a method, :meth:`cipher`, to retrieve the cipher being used for
|
connection, and a method, :meth:`cipher`, to retrieve the cipher being used for
|
||||||
the secure connection.
|
the secure connection.
|
||||||
|
|
||||||
|
For more sophisticated applications, the :class:`ssl.SSLContext` class
|
||||||
|
helps manage settings and certificates, which can then be inherited
|
||||||
|
by SSL sockets created through the :meth:`SSLContext.wrap_socket` method.
|
||||||
|
|
||||||
|
|
||||||
Functions, Constants, and Exceptions
|
Functions, Constants, and Exceptions
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
|
@ -64,19 +69,6 @@ Functions, Constants, and Exceptions
|
||||||
connection. See the discussion of :ref:`ssl-certificates` for more
|
connection. See the discussion of :ref:`ssl-certificates` for more
|
||||||
information on how the certificate is stored in the ``certfile``.
|
information on how the certificate is stored in the ``certfile``.
|
||||||
|
|
||||||
Often the private key is stored in the same file as the certificate; in this
|
|
||||||
case, only the ``certfile`` parameter need be passed. If the private key is
|
|
||||||
stored in a separate file, both parameters must be used. If the private key
|
|
||||||
is stored in the ``certfile``, it should come before the first certificate in
|
|
||||||
the certificate chain::
|
|
||||||
|
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
... (private key in base64 encoding) ...
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
... (certificate in base64 PEM encoding) ...
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
|
||||||
The parameter ``server_side`` is a boolean which identifies whether
|
The parameter ``server_side`` is a boolean which identifies whether
|
||||||
server-side or client-side behavior is desired from this socket.
|
server-side or client-side behavior is desired from this socket.
|
||||||
|
|
||||||
|
@ -208,24 +200,36 @@ Functions, Constants, and Exceptions
|
||||||
|
|
||||||
.. data:: CERT_NONE
|
.. data:: CERT_NONE
|
||||||
|
|
||||||
Value to pass to the ``cert_reqs`` parameter to :func:`sslobject` when no
|
Possible value for :attr:`SSLContext.verify_mode`, or the ``cert_reqs``
|
||||||
certificates will be required or validated from the other side of the socket
|
parameter to :func:`wrap_socket`. In this mode (the default), no
|
||||||
connection.
|
certificates will be required from the other side of the socket connection.
|
||||||
|
If a certificate is received from the other end, no attempt to validate it
|
||||||
|
is made.
|
||||||
|
|
||||||
|
See the discussion of :ref:`ssl-security` below.
|
||||||
|
|
||||||
.. data:: CERT_OPTIONAL
|
.. data:: CERT_OPTIONAL
|
||||||
|
|
||||||
Value to pass to the ``cert_reqs`` parameter to :func:`sslobject` when no
|
Possible value for :attr:`SSLContext.verify_mode`, or the ``cert_reqs``
|
||||||
certificates will be required from the other side of the socket connection,
|
parameter to :func:`wrap_socket`. In this mode no certificates will be
|
||||||
but if they are provided, will be validated. Note that use of this setting
|
required from the other side of the socket connection; but if they
|
||||||
requires a valid certificate validation file also be passed as a value of the
|
are provided, validation will be attempted and an :class:`SSLError`
|
||||||
``ca_certs`` parameter.
|
will be raised on failure.
|
||||||
|
|
||||||
|
Use of this setting requires a valid set of CA certificates to
|
||||||
|
be passed, either to :meth:`SSLContext.load_verify_locations` or as a
|
||||||
|
value of the ``ca_certs`` parameter to :func:`wrap_socket`.
|
||||||
|
|
||||||
.. data:: CERT_REQUIRED
|
.. data:: CERT_REQUIRED
|
||||||
|
|
||||||
Value to pass to the ``cert_reqs`` parameter to :func:`sslobject` when
|
Possible value for :attr:`SSLContext.verify_mode`, or the ``cert_reqs``
|
||||||
certificates will be required from the other side of the socket connection.
|
parameter to :func:`wrap_socket`. In this mode, certificates are
|
||||||
Note that use of this setting requires a valid certificate validation file
|
required from the other side of the socket connection; an :class:`SSLError`
|
||||||
also be passed as a value of the ``ca_certs`` parameter.
|
will be raised if no certificate is provided, or if its validation fails.
|
||||||
|
|
||||||
|
Use of this setting requires a valid set of CA certificates to
|
||||||
|
be passed, either to :meth:`SSLContext.load_verify_locations` or as a
|
||||||
|
value of the ``ca_certs`` parameter to :func:`wrap_socket`.
|
||||||
|
|
||||||
.. data:: PROTOCOL_SSLv2
|
.. data:: PROTOCOL_SSLv2
|
||||||
|
|
||||||
|
@ -284,8 +288,8 @@ Functions, Constants, and Exceptions
|
||||||
.. versionadded:: 3.2
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
|
|
||||||
SSLSocket Objects
|
SSL Sockets
|
||||||
-----------------
|
-----------
|
||||||
|
|
||||||
.. method:: SSLSocket.read(nbytes=1024, buffer=None)
|
.. method:: SSLSocket.read(nbytes=1024, buffer=None)
|
||||||
|
|
||||||
|
@ -371,6 +375,83 @@ SSLSocket Objects
|
||||||
returned socket should always be used for further communication with the
|
returned socket should always be used for further communication with the
|
||||||
other side of the connection, rather than the original socket.
|
other side of the connection, rather than the original socket.
|
||||||
|
|
||||||
|
|
||||||
|
SSL Contexts
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. class:: SSLContext(protocol)
|
||||||
|
|
||||||
|
An object holding various data longer-lived than single SSL connections,
|
||||||
|
such as SSL configuration options, certificate(s) and private key(s).
|
||||||
|
You must pass *protocol* which must be one of the ``PROTOCOL_*`` constants
|
||||||
|
defined in this module. :data:`PROTOCOL_SSLv23` is recommended for
|
||||||
|
maximum interoperability.
|
||||||
|
|
||||||
|
:class:`SSLContext` objects have the following methods and attributes:
|
||||||
|
|
||||||
|
.. method:: SSLContext.load_cert_chain(certfile, keyfile=None)
|
||||||
|
|
||||||
|
Load a private key and the corresponding certificate. The *certfile*
|
||||||
|
string must be the path to a single file in PEM format containing the
|
||||||
|
certificate as well as any number of CA certificates needed to establish
|
||||||
|
the certificate's authenticity. The *keyfile* string, if present, must
|
||||||
|
point to a file containing the private key in. Otherwise the private
|
||||||
|
key will be taken from *certfile* as well. See the discussion of
|
||||||
|
:ref:`ssl-certificates` for more information on how the certificate
|
||||||
|
is stored in the *certfile*.
|
||||||
|
|
||||||
|
An :class:`SSLError` is raised if the private key doesn't
|
||||||
|
match with the certificate.
|
||||||
|
|
||||||
|
.. method:: SSLContext.load_verify_locations(cafile=None, capath=None)
|
||||||
|
|
||||||
|
Load a set of "certification authority" (CA) certificates used to validate
|
||||||
|
other peers' certificates when :data:`verify_mode` is other than
|
||||||
|
:data:`CERT_NONE`. At least one of *cafile* or *capath* must be specified.
|
||||||
|
|
||||||
|
The *cafile* string, if present, is the path to a file of concatenated
|
||||||
|
CA certificates in PEM format. See the discussion of
|
||||||
|
:ref:`ssl-certificates` for more information about how to arrange the
|
||||||
|
certificates in this file.
|
||||||
|
|
||||||
|
The *capath* string, if present, is
|
||||||
|
the path to a directory containing several CA certificates in PEM format,
|
||||||
|
following an `OpenSSL specific layout
|
||||||
|
<http://www.openssl.org/docs/ssl/SSL_CTX_load_verify_locations.html>`_.
|
||||||
|
|
||||||
|
.. method:: SSLContext.set_ciphers(ciphers)
|
||||||
|
|
||||||
|
Set the available ciphers for sockets created with this context.
|
||||||
|
It should be a string in the `OpenSSL cipher list format
|
||||||
|
<http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT>`_.
|
||||||
|
If no cipher can be selected (because compile-time options or other
|
||||||
|
configuration forbids use of all the specified ciphers), an
|
||||||
|
:class:`SSLError` will be raised.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will
|
||||||
|
give the currently selected cipher.
|
||||||
|
|
||||||
|
.. method:: SSLContext.wrap_socket(sock, server_side=False, do_handshake_on_connect=True, suppress_ragged_eofs=True)
|
||||||
|
|
||||||
|
Wrap an existing Python socket *sock* and return an :class:`SSLSocket`
|
||||||
|
object. The SSL socket is tied to the context, its settings and
|
||||||
|
certificates. The parameters *server_side*, *do_handshake_on_connect*
|
||||||
|
and *suppress_ragged_eofs* have the same meaning as in the top-level
|
||||||
|
:func:`wrap_socket` function.
|
||||||
|
|
||||||
|
.. attribute:: SSLContext.protocol
|
||||||
|
|
||||||
|
The protocol version chosen when constructing the context. This attribute
|
||||||
|
is read-only.
|
||||||
|
|
||||||
|
.. attribute:: SSLContext.verify_mode
|
||||||
|
|
||||||
|
Whether to try to verify other peers' certificates and how to behave
|
||||||
|
if verification fails. This attribute must be one of
|
||||||
|
:data:`CERT_NONE`, :data:`CERT_OPTIONAL` or :data:`CERT_REQUIRED`.
|
||||||
|
|
||||||
|
|
||||||
.. index:: single: certificates
|
.. index:: single: certificates
|
||||||
|
|
||||||
.. index:: single: X509 certificate
|
.. index:: single: X509 certificate
|
||||||
|
@ -416,6 +497,9 @@ and a footer line::
|
||||||
... (certificate in base64 PEM encoding) ...
|
... (certificate in base64 PEM encoding) ...
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Certificate chains
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The Python files which contain certificates can contain a sequence of
|
The Python files which contain certificates can contain a sequence of
|
||||||
certificates, sometimes called a *certificate chain*. This chain should start
|
certificates, sometimes called a *certificate chain*. This chain should start
|
||||||
with the specific certificate for the principal who "is" the client or server,
|
with the specific certificate for the principal who "is" the client or server,
|
||||||
|
@ -439,6 +523,9 @@ certification authority's certificate::
|
||||||
... (the root certificate for the CA's issuer)...
|
... (the root certificate for the CA's issuer)...
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
CA certificates
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
If you are going to require validation of the other side of the connection's
|
If you are going to require validation of the other side of the connection's
|
||||||
certificate, you need to provide a "CA certs" file, filled with the certificate
|
certificate, you need to provide a "CA certs" file, filled with the certificate
|
||||||
chains for each issuer you are willing to trust. Again, this file just contains
|
chains for each issuer you are willing to trust. Again, this file just contains
|
||||||
|
@ -458,6 +545,25 @@ peer is supposed to furnish the other certificates necessary to chain from its
|
||||||
certificate to a root certificate. See :rfc:`4158` for more discussion of the
|
certificate to a root certificate. See :rfc:`4158` for more discussion of the
|
||||||
way in which certification chains can be built.
|
way in which certification chains can be built.
|
||||||
|
|
||||||
|
Combined key and certificate
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Often the private key is stored in the same file as the certificate; in this
|
||||||
|
case, only the ``certfile`` parameter to :meth:`SSLContext.load_cert_chain`
|
||||||
|
and :func:`wrap_socket` needs to be passed. If the private key is stored
|
||||||
|
with the certificate, it should come before the first certificate in
|
||||||
|
the certificate chain::
|
||||||
|
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
... (private key in base64 encoding) ...
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
... (certificate in base64 PEM encoding) ...
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Self-signed certificates
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
If you are going to create a server that provides SSL-encrypted connection
|
If you are going to create a server that provides SSL-encrypted connection
|
||||||
services, you will need to acquire a certificate for that service. There are
|
services, you will need to acquire a certificate for that service. There are
|
||||||
many ways of acquiring appropriate certificates, such as buying one from a
|
many ways of acquiring appropriate certificates, such as buying one from a
|
||||||
|
@ -530,8 +636,7 @@ certificate, sends some bytes, and reads part of the response::
|
||||||
print(pprint.pformat(ssl_sock.getpeercert()))
|
print(pprint.pformat(ssl_sock.getpeercert()))
|
||||||
|
|
||||||
# Set a simple HTTP request -- use http.client in actual code.
|
# Set a simple HTTP request -- use http.client in actual code.
|
||||||
ssl_sock.write("""GET / HTTP/1.0\r
|
ssl_sock.write(b"GET / HTTP/1.0\r\nHost: www.verisign.com\r\n\r\n")
|
||||||
Host: www.verisign.com\r\n\r\n""")
|
|
||||||
|
|
||||||
# Read a chunk of data. Will not necessarily
|
# Read a chunk of data. Will not necessarily
|
||||||
# read all the data returned by the server.
|
# read all the data returned by the server.
|
||||||
|
@ -561,39 +666,91 @@ this::
|
||||||
|
|
||||||
which is a fairly poorly-formed ``subject`` field.
|
which is a fairly poorly-formed ``subject`` field.
|
||||||
|
|
||||||
|
This other example first creates an SSL context, instructs it to verify
|
||||||
|
certificates sent by peers, and feeds it a set of recognized certificate
|
||||||
|
authorities (CA)::
|
||||||
|
|
||||||
|
>>> context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||||
|
>>> context.verify_mode = ssl.CERT_OPTIONAL
|
||||||
|
>>> context.load_verify_locations("/etc/ssl/certs/ca-bundle.crt")
|
||||||
|
|
||||||
|
(it is assumed your operating system places a bundle of all CA certificates
|
||||||
|
in ``/etc/ssl/certs/ca-bundle.crt``; if not, you'll get an error and have
|
||||||
|
to adjust the location)
|
||||||
|
|
||||||
|
When you use the context to connect to a server, :const:`CERT_OPTIONAL`
|
||||||
|
validates the server certificate: it ensures that the server certificate
|
||||||
|
was signed with one of the CA certificates, and checks the signature for
|
||||||
|
correctness::
|
||||||
|
|
||||||
|
>>> conn = context.wrap_socket(socket.socket(socket.AF_INET))
|
||||||
|
>>> conn.connect(("linuxfr.org", 443))
|
||||||
|
|
||||||
|
You should then fetch the certificate and check its fields for conformity.
|
||||||
|
Here, the ``commonName`` field in the ``subject`` matches the desired HTTPS
|
||||||
|
host ``linuxfr.org``::
|
||||||
|
|
||||||
|
>>> pprint.pprint(conn.getpeercert())
|
||||||
|
{'notAfter': 'Jun 26 21:41:46 2011 GMT',
|
||||||
|
'subject': ((('commonName', 'linuxfr.org'),),),
|
||||||
|
'subjectAltName': (('DNS', 'linuxfr.org'), ('othername', '<unsupported>'))}
|
||||||
|
|
||||||
|
Now that you are assured of its authenticity, you can proceed to talk with
|
||||||
|
the server::
|
||||||
|
|
||||||
|
>>> conn.write(b"HEAD / HTTP/1.0\r\nHost: linuxfr.org\r\n\r\n")
|
||||||
|
38
|
||||||
|
>>> pprint.pprint(conn.read().split(b"\r\n"))
|
||||||
|
[b'HTTP/1.1 302 Found',
|
||||||
|
b'Date: Sun, 16 May 2010 13:43:28 GMT',
|
||||||
|
b'Server: Apache/2.2',
|
||||||
|
b'Location: https://linuxfr.org/pub/',
|
||||||
|
b'Vary: Accept-Encoding',
|
||||||
|
b'Connection: close',
|
||||||
|
b'Content-Type: text/html; charset=iso-8859-1',
|
||||||
|
b'',
|
||||||
|
b'']
|
||||||
|
|
||||||
|
|
||||||
|
See the discussion of :ref:`ssl-security` below.
|
||||||
|
|
||||||
|
|
||||||
Server-side operation
|
Server-side operation
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
For server operation, typically you'd need to have a server certificate, and
|
For server operation, typically you'll need to have a server certificate, and
|
||||||
private key, each in a file. You'd open a socket, bind it to a port, call
|
private key, each in a file. You'll first create a context holding the key
|
||||||
:meth:`listen` on it, then start waiting for clients to connect::
|
and the certificate, so that clients can check your authenticity. Then
|
||||||
|
you'll open a socket, bind it to a port, call :meth:`listen` on it, and start
|
||||||
|
waiting for clients to connect::
|
||||||
|
|
||||||
import socket, ssl
|
import socket, ssl
|
||||||
|
|
||||||
|
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
||||||
|
context.load_cert_chain(certfile="mycertfile", keyfile="mykeyfile")
|
||||||
|
|
||||||
bindsocket = socket.socket()
|
bindsocket = socket.socket()
|
||||||
bindsocket.bind(('myaddr.mydomain.com', 10023))
|
bindsocket.bind(('myaddr.mydomain.com', 10023))
|
||||||
bindsocket.listen(5)
|
bindsocket.listen(5)
|
||||||
|
|
||||||
When one did, you'd call :meth:`accept` on the socket to get the new socket from
|
When a client connects, you'll call :meth:`accept` on the socket to get the
|
||||||
the other end, and use :func:`wrap_socket` to create a server-side SSL context
|
new socket from the other end, and use the context's :meth:`SSLContext.wrap_socket`
|
||||||
for it::
|
method to create a server-side SSL socket for the connection::
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
newsocket, fromaddr = bindsocket.accept()
|
newsocket, fromaddr = bindsocket.accept()
|
||||||
connstream = ssl.wrap_socket(newsocket,
|
connstream = context.wrap_socket(newsocket, server_side=True)
|
||||||
server_side=True,
|
try:
|
||||||
certfile="mycertfile",
|
deal_with_client(connstream)
|
||||||
keyfile="mykeyfile",
|
finally:
|
||||||
ssl_version=ssl.PROTOCOL_TLSv1)
|
connstream.close()
|
||||||
deal_with_client(connstream)
|
|
||||||
|
|
||||||
Then you'd read data from the ``connstream`` and do something with it till you
|
Then you'll read data from the ``connstream`` and do something with it till you
|
||||||
are finished with the client (or the client is finished with you)::
|
are finished with the client (or the client is finished with you)::
|
||||||
|
|
||||||
def deal_with_client(connstream):
|
def deal_with_client(connstream):
|
||||||
|
|
||||||
data = connstream.read()
|
data = connstream.read()
|
||||||
# null data means the client is finished with us
|
# empty data means the client is finished with us
|
||||||
while data:
|
while data:
|
||||||
if not do_something(connstream, data):
|
if not do_something(connstream, data):
|
||||||
# we'll assume do_something returns False
|
# we'll assume do_something returns False
|
||||||
|
@ -601,9 +758,41 @@ are finished with the client (or the client is finished with you)::
|
||||||
break
|
break
|
||||||
data = connstream.read()
|
data = connstream.read()
|
||||||
# finished with client
|
# finished with client
|
||||||
connstream.close()
|
|
||||||
|
|
||||||
And go back to listening for new client connections.
|
And go back to listening for new client connections (of course, a real server
|
||||||
|
would probably handle each client connection in a separate thread, or put
|
||||||
|
the sockets in non-blocking mode and use an event loop).
|
||||||
|
|
||||||
|
|
||||||
|
.. _ssl-security:
|
||||||
|
|
||||||
|
Security considerations
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Verifying certificates
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
:const:`CERT_NONE` is the default. Since it does not authenticate the other
|
||||||
|
peer, it can be insecure, especially in client mode where most of time you
|
||||||
|
would like to ensure the authenticity of the server you're talking to.
|
||||||
|
Therefore, when in client mode, it is highly recommended to use
|
||||||
|
:const:`CERT_REQUIRED`. However, it is in itself not sufficient; you also
|
||||||
|
have to check that the server certificate (obtained with
|
||||||
|
:meth:`SSLSocket.getpeercert`) matches the desired service. The exact way
|
||||||
|
of doing so depends on the higher-level protocol used; for example, with
|
||||||
|
HTTPS, you'll check that the host name in the URL matches either the
|
||||||
|
``commonName`` field in the ``subjectName``, or one of the ``DNS`` fields
|
||||||
|
in the ``subjectAltName``.
|
||||||
|
|
||||||
|
In server mode, if you want to authenticate your clients using the SSL layer
|
||||||
|
(rather than using a higher-level authentication mechanism), you'll also have
|
||||||
|
to specify :const:`CERT_REQUIRED` and similarly check the client certificate.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
In client mode, :const:`CERT_OPTIONAL` and :const:`CERT_REQUIRED` are
|
||||||
|
equivalent unless anonymous ciphers are enabled (they are disabled
|
||||||
|
by default).
|
||||||
|
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
71
Lib/ssl.py
71
Lib/ssl.py
|
@ -59,7 +59,7 @@ import textwrap
|
||||||
import _ssl # if we can't import it, let the error propagate
|
import _ssl # if we can't import it, let the error propagate
|
||||||
|
|
||||||
from _ssl import OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_INFO, OPENSSL_VERSION
|
from _ssl import OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_INFO, OPENSSL_VERSION
|
||||||
from _ssl import SSLError
|
from _ssl import _SSLContext, SSLError
|
||||||
from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
|
from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
|
||||||
from _ssl import (PROTOCOL_SSLv2, PROTOCOL_SSLv3, PROTOCOL_SSLv23,
|
from _ssl import (PROTOCOL_SSLv2, PROTOCOL_SSLv3, PROTOCOL_SSLv23,
|
||||||
PROTOCOL_TLSv1)
|
PROTOCOL_TLSv1)
|
||||||
|
@ -84,8 +84,29 @@ import base64 # for DER-to-PEM translation
|
||||||
import traceback
|
import traceback
|
||||||
import errno
|
import errno
|
||||||
|
|
||||||
class SSLSocket(socket):
|
|
||||||
|
|
||||||
|
class SSLContext(_SSLContext):
|
||||||
|
"""An SSLContext holds various SSL-related configuration options and
|
||||||
|
data, such as certificates and possibly a private key."""
|
||||||
|
|
||||||
|
__slots__ = ('protocol',)
|
||||||
|
|
||||||
|
def __new__(cls, protocol, *args, **kwargs):
|
||||||
|
return _SSLContext.__new__(cls, protocol)
|
||||||
|
|
||||||
|
def __init__(self, protocol):
|
||||||
|
self.protocol = protocol
|
||||||
|
|
||||||
|
def wrap_socket(self, sock, server_side=False,
|
||||||
|
do_handshake_on_connect=True,
|
||||||
|
suppress_ragged_eofs=True):
|
||||||
|
return SSLSocket(sock=sock, server_side=server_side,
|
||||||
|
do_handshake_on_connect=do_handshake_on_connect,
|
||||||
|
suppress_ragged_eofs=suppress_ragged_eofs,
|
||||||
|
_context=self)
|
||||||
|
|
||||||
|
|
||||||
|
class SSLSocket(socket):
|
||||||
"""This class implements a subtype of socket.socket that wraps
|
"""This class implements a subtype of socket.socket that wraps
|
||||||
the underlying OS socket in an SSL context when necessary, and
|
the underlying OS socket in an SSL context when necessary, and
|
||||||
provides read and write methods over that channel."""
|
provides read and write methods over that channel."""
|
||||||
|
@ -95,8 +116,31 @@ class SSLSocket(socket):
|
||||||
ssl_version=PROTOCOL_SSLv23, ca_certs=None,
|
ssl_version=PROTOCOL_SSLv23, ca_certs=None,
|
||||||
do_handshake_on_connect=True,
|
do_handshake_on_connect=True,
|
||||||
family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None,
|
family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None,
|
||||||
suppress_ragged_eofs=True, ciphers=None):
|
suppress_ragged_eofs=True, ciphers=None,
|
||||||
|
_context=None):
|
||||||
|
|
||||||
|
if _context:
|
||||||
|
self.context = _context
|
||||||
|
else:
|
||||||
|
if certfile and not keyfile:
|
||||||
|
keyfile = certfile
|
||||||
|
self.context = SSLContext(ssl_version)
|
||||||
|
self.context.verify_mode = cert_reqs
|
||||||
|
if ca_certs:
|
||||||
|
self.context.load_verify_locations(ca_certs)
|
||||||
|
if certfile:
|
||||||
|
self.context.load_cert_chain(certfile, keyfile)
|
||||||
|
if ciphers:
|
||||||
|
self.context.set_ciphers(ciphers)
|
||||||
|
self.keyfile = keyfile
|
||||||
|
self.certfile = certfile
|
||||||
|
self.cert_reqs = cert_reqs
|
||||||
|
self.ssl_version = ssl_version
|
||||||
|
self.ca_certs = ca_certs
|
||||||
|
self.ciphers = ciphers
|
||||||
|
|
||||||
|
self.do_handshake_on_connect = do_handshake_on_connect
|
||||||
|
self.suppress_ragged_eofs = suppress_ragged_eofs
|
||||||
connected = False
|
connected = False
|
||||||
if sock is not None:
|
if sock is not None:
|
||||||
socket.__init__(self,
|
socket.__init__(self,
|
||||||
|
@ -119,18 +163,12 @@ class SSLSocket(socket):
|
||||||
else:
|
else:
|
||||||
socket.__init__(self, family=family, type=type, proto=proto)
|
socket.__init__(self, family=family, type=type, proto=proto)
|
||||||
|
|
||||||
if certfile and not keyfile:
|
|
||||||
keyfile = certfile
|
|
||||||
|
|
||||||
self._closed = False
|
self._closed = False
|
||||||
self._sslobj = None
|
self._sslobj = None
|
||||||
if connected:
|
if connected:
|
||||||
# create the SSL object
|
# create the SSL object
|
||||||
try:
|
try:
|
||||||
self._sslobj = _ssl.sslwrap(self, server_side,
|
self._sslobj = self.context._wrap_socket(self, server_side)
|
||||||
keyfile, certfile,
|
|
||||||
cert_reqs, ssl_version, ca_certs,
|
|
||||||
ciphers)
|
|
||||||
if do_handshake_on_connect:
|
if do_handshake_on_connect:
|
||||||
timeout = self.gettimeout()
|
timeout = self.gettimeout()
|
||||||
if timeout == 0.0:
|
if timeout == 0.0:
|
||||||
|
@ -142,15 +180,6 @@ class SSLSocket(socket):
|
||||||
self.close()
|
self.close()
|
||||||
raise x
|
raise x
|
||||||
|
|
||||||
self.keyfile = keyfile
|
|
||||||
self.certfile = certfile
|
|
||||||
self.cert_reqs = cert_reqs
|
|
||||||
self.ssl_version = ssl_version
|
|
||||||
self.ca_certs = ca_certs
|
|
||||||
self.ciphers = ciphers
|
|
||||||
self.do_handshake_on_connect = do_handshake_on_connect
|
|
||||||
self.suppress_ragged_eofs = suppress_ragged_eofs
|
|
||||||
|
|
||||||
def dup(self):
|
def dup(self):
|
||||||
raise NotImplemented("Can't dup() %s instances" %
|
raise NotImplemented("Can't dup() %s instances" %
|
||||||
self.__class__.__name__)
|
self.__class__.__name__)
|
||||||
|
@ -331,9 +360,7 @@ class SSLSocket(socket):
|
||||||
if self._sslobj:
|
if self._sslobj:
|
||||||
raise ValueError("attempt to connect already-connected SSLSocket!")
|
raise ValueError("attempt to connect already-connected SSLSocket!")
|
||||||
socket.connect(self, addr)
|
socket.connect(self, addr)
|
||||||
self._sslobj = _ssl.sslwrap(self, False, self.keyfile, self.certfile,
|
self._sslobj = self.context._wrap_socket(self, False)
|
||||||
self.cert_reqs, self.ssl_version,
|
|
||||||
self.ca_certs, self.ciphers)
|
|
||||||
try:
|
try:
|
||||||
if self.do_handshake_on_connect:
|
if self.do_handshake_on_connect:
|
||||||
self.do_handshake()
|
self.do_handshake()
|
||||||
|
|
14
Lib/test/capath/6e88d7b8.0
Normal file
14
Lib/test/capath/6e88d7b8.0
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICLDCCAdYCAQAwDQYJKoZIhvcNAQEEBQAwgaAxCzAJBgNVBAYTAlBUMRMwEQYD
|
||||||
|
VQQIEwpRdWVlbnNsYW5kMQ8wDQYDVQQHEwZMaXNib2ExFzAVBgNVBAoTDk5ldXJv
|
||||||
|
bmlvLCBMZGEuMRgwFgYDVQQLEw9EZXNlbnZvbHZpbWVudG8xGzAZBgNVBAMTEmJy
|
||||||
|
dXR1cy5uZXVyb25pby5wdDEbMBkGCSqGSIb3DQEJARYMc2FtcG9AaWtpLmZpMB4X
|
||||||
|
DTk2MDkwNTAzNDI0M1oXDTk2MTAwNTAzNDI0M1owgaAxCzAJBgNVBAYTAlBUMRMw
|
||||||
|
EQYDVQQIEwpRdWVlbnNsYW5kMQ8wDQYDVQQHEwZMaXNib2ExFzAVBgNVBAoTDk5l
|
||||||
|
dXJvbmlvLCBMZGEuMRgwFgYDVQQLEw9EZXNlbnZvbHZpbWVudG8xGzAZBgNVBAMT
|
||||||
|
EmJydXR1cy5uZXVyb25pby5wdDEbMBkGCSqGSIb3DQEJARYMc2FtcG9AaWtpLmZp
|
||||||
|
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL7+aty3S1iBA/+yxjxv4q1MUTd1kjNw
|
||||||
|
L4lYKbpzzlmC5beaQXeQ2RmGMTXU+mDvuqItjVHOK3DvPK7lTcSGftUCAwEAATAN
|
||||||
|
BgkqhkiG9w0BAQQFAANBAFqPEKFjk6T6CKTHvaQeEAsX0/8YHPHqH/9AnhSjrwuX
|
||||||
|
9EBc0n6bVGhN7XaXd6sJ7dym9sbsWxb+pJdurnkxjx4=
|
||||||
|
-----END CERTIFICATE-----
|
41
Lib/test/capath/99d0fa06.0
Normal file
41
Lib/test/capath/99d0fa06.0
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
|
||||||
|
IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
|
||||||
|
IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
|
||||||
|
Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO
|
||||||
|
BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi
|
||||||
|
MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ
|
||||||
|
ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
|
||||||
|
CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ
|
||||||
|
8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6
|
||||||
|
zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y
|
||||||
|
fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7
|
||||||
|
w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc
|
||||||
|
G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k
|
||||||
|
epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q
|
||||||
|
laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ
|
||||||
|
QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU
|
||||||
|
fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826
|
||||||
|
YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w
|
||||||
|
ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY
|
||||||
|
gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe
|
||||||
|
MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0
|
||||||
|
IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy
|
||||||
|
dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw
|
||||||
|
czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0
|
||||||
|
dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl
|
||||||
|
aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC
|
||||||
|
AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg
|
||||||
|
b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB
|
||||||
|
ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc
|
||||||
|
nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg
|
||||||
|
18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c
|
||||||
|
gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl
|
||||||
|
Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY
|
||||||
|
sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T
|
||||||
|
SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF
|
||||||
|
CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum
|
||||||
|
GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk
|
||||||
|
zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW
|
||||||
|
omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -10,6 +10,7 @@ import gc
|
||||||
import os
|
import os
|
||||||
import errno
|
import errno
|
||||||
import pprint
|
import pprint
|
||||||
|
import tempfile
|
||||||
import urllib.parse, urllib.request
|
import urllib.parse, urllib.request
|
||||||
import traceback
|
import traceback
|
||||||
import asyncore
|
import asyncore
|
||||||
|
@ -25,8 +26,30 @@ except ImportError:
|
||||||
skip_expected = True
|
skip_expected = True
|
||||||
|
|
||||||
HOST = support.HOST
|
HOST = support.HOST
|
||||||
CERTFILE = None
|
PROTOCOLS = [
|
||||||
SVN_PYTHON_ORG_ROOT_CERT = None
|
ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_SSLv3,
|
||||||
|
ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1
|
||||||
|
]
|
||||||
|
|
||||||
|
data_file = lambda name: os.path.join(os.path.dirname(__file__), name)
|
||||||
|
fsencode = lambda name: name.encode(sys.getfilesystemencoding(), "surrogateescape")
|
||||||
|
|
||||||
|
CERTFILE = data_file("keycert.pem")
|
||||||
|
BYTES_CERTFILE = fsencode(CERTFILE)
|
||||||
|
ONLYCERT = data_file("ssl_cert.pem")
|
||||||
|
ONLYKEY = data_file("ssl_key.pem")
|
||||||
|
BYTES_ONLYCERT = fsencode(ONLYCERT)
|
||||||
|
BYTES_ONLYKEY = fsencode(ONLYKEY)
|
||||||
|
CAPATH = data_file("capath")
|
||||||
|
BYTES_CAPATH = fsencode(CAPATH)
|
||||||
|
|
||||||
|
SVN_PYTHON_ORG_ROOT_CERT = data_file("https_svn_python_org_root.pem")
|
||||||
|
|
||||||
|
EMPTYCERT = data_file("nullcert.pem")
|
||||||
|
BADCERT = data_file("badcert.pem")
|
||||||
|
WRONGCERT = data_file("XXXnonexisting.pem")
|
||||||
|
BADKEY = data_file("badkey.pem")
|
||||||
|
|
||||||
|
|
||||||
def handle_error(prefix):
|
def handle_error(prefix):
|
||||||
exc_format = ' '.join(traceback.format_exception(*sys.exc_info()))
|
exc_format = ' '.join(traceback.format_exception(*sys.exc_info()))
|
||||||
|
@ -34,7 +57,7 @@ def handle_error(prefix):
|
||||||
sys.stdout.write(prefix + exc_format)
|
sys.stdout.write(prefix + exc_format)
|
||||||
|
|
||||||
|
|
||||||
class BasicTests(unittest.TestCase):
|
class BasicSocketTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_constants(self):
|
def test_constants(self):
|
||||||
ssl.PROTOCOL_SSLv2
|
ssl.PROTOCOL_SSLv2
|
||||||
|
@ -116,11 +139,10 @@ class BasicTests(unittest.TestCase):
|
||||||
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
||||||
cert_reqs=ssl.CERT_NONE, ciphers="DEFAULT")
|
cert_reqs=ssl.CERT_NONE, ciphers="DEFAULT")
|
||||||
s.connect(remote)
|
s.connect(remote)
|
||||||
# Error checking occurs when connecting, because the SSL context
|
# Error checking can happen at instantiation or when connecting
|
||||||
# isn't created before.
|
|
||||||
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
|
||||||
cert_reqs=ssl.CERT_NONE, ciphers="^$:,;?*'dorothyx")
|
|
||||||
with self.assertRaisesRegexp(ssl.SSLError, "No cipher can be selected"):
|
with self.assertRaisesRegexp(ssl.SSLError, "No cipher can be selected"):
|
||||||
|
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
||||||
|
cert_reqs=ssl.CERT_NONE, ciphers="^$:,;?*'dorothyx")
|
||||||
s.connect(remote)
|
s.connect(remote)
|
||||||
|
|
||||||
@support.cpython_only
|
@support.cpython_only
|
||||||
|
@ -143,26 +165,103 @@ class BasicTests(unittest.TestCase):
|
||||||
self.assertEqual(timeout, ss.gettimeout())
|
self.assertEqual(timeout, ss.gettimeout())
|
||||||
|
|
||||||
|
|
||||||
|
class ContextTests(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_constructor(self):
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv2)
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv3)
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
||||||
|
self.assertRaises(TypeError, ssl.SSLContext)
|
||||||
|
self.assertRaises(ValueError, ssl.SSLContext, -1)
|
||||||
|
self.assertRaises(ValueError, ssl.SSLContext, 42)
|
||||||
|
|
||||||
|
def test_protocol(self):
|
||||||
|
for proto in PROTOCOLS:
|
||||||
|
ctx = ssl.SSLContext(proto)
|
||||||
|
self.assertEqual(ctx.protocol, proto)
|
||||||
|
|
||||||
|
def test_ciphers(self):
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
||||||
|
ctx.set_ciphers("ALL")
|
||||||
|
ctx.set_ciphers("DEFAULT")
|
||||||
|
with self.assertRaisesRegexp(ssl.SSLError, "No cipher can be selected"):
|
||||||
|
ctx.set_ciphers("^$:,;?*'dorothyx")
|
||||||
|
|
||||||
|
def test_verify(self):
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
||||||
|
# Default value
|
||||||
|
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
|
||||||
|
ctx.verify_mode = ssl.CERT_OPTIONAL
|
||||||
|
self.assertEqual(ctx.verify_mode, ssl.CERT_OPTIONAL)
|
||||||
|
ctx.verify_mode = ssl.CERT_REQUIRED
|
||||||
|
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
ctx.verify_mode = None
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
ctx.verify_mode = 42
|
||||||
|
|
||||||
|
def test_load_cert_chain(self):
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
||||||
|
# Combined key and cert in a single file
|
||||||
|
ctx.load_cert_chain(CERTFILE)
|
||||||
|
ctx.load_cert_chain(CERTFILE, keyfile=CERTFILE)
|
||||||
|
self.assertRaises(TypeError, ctx.load_cert_chain, keyfile=CERTFILE)
|
||||||
|
with self.assertRaisesRegexp(ssl.SSLError, "system lib"):
|
||||||
|
ctx.load_cert_chain(WRONGCERT)
|
||||||
|
with self.assertRaisesRegexp(ssl.SSLError, "PEM lib"):
|
||||||
|
ctx.load_cert_chain(BADCERT)
|
||||||
|
with self.assertRaisesRegexp(ssl.SSLError, "PEM lib"):
|
||||||
|
ctx.load_cert_chain(EMPTYCERT)
|
||||||
|
# Separate key and cert
|
||||||
|
ctx.load_cert_chain(ONLYCERT, ONLYKEY)
|
||||||
|
ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY)
|
||||||
|
ctx.load_cert_chain(certfile=BYTES_ONLYCERT, keyfile=BYTES_ONLYKEY)
|
||||||
|
with self.assertRaisesRegexp(ssl.SSLError, "PEM lib"):
|
||||||
|
ctx.load_cert_chain(ONLYCERT)
|
||||||
|
with self.assertRaisesRegexp(ssl.SSLError, "PEM lib"):
|
||||||
|
ctx.load_cert_chain(ONLYKEY)
|
||||||
|
with self.assertRaisesRegexp(ssl.SSLError, "PEM lib"):
|
||||||
|
ctx.load_cert_chain(certfile=ONLYKEY, keyfile=ONLYCERT)
|
||||||
|
# Mismatching key and cert
|
||||||
|
with self.assertRaisesRegexp(ssl.SSLError, "key values mismatch"):
|
||||||
|
ctx.load_cert_chain(CERTFILE, ONLYKEY)
|
||||||
|
|
||||||
|
def test_load_verify_locations(self):
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
||||||
|
ctx.load_verify_locations(CERTFILE)
|
||||||
|
ctx.load_verify_locations(cafile=CERTFILE, capath=None)
|
||||||
|
ctx.load_verify_locations(BYTES_CERTFILE)
|
||||||
|
ctx.load_verify_locations(cafile=BYTES_CERTFILE, capath=None)
|
||||||
|
self.assertRaises(TypeError, ctx.load_verify_locations)
|
||||||
|
self.assertRaises(TypeError, ctx.load_verify_locations, None, None)
|
||||||
|
with self.assertRaisesRegexp(ssl.SSLError, "system lib"):
|
||||||
|
ctx.load_verify_locations(WRONGCERT)
|
||||||
|
with self.assertRaisesRegexp(ssl.SSLError, "PEM lib"):
|
||||||
|
ctx.load_verify_locations(BADCERT)
|
||||||
|
ctx.load_verify_locations(CERTFILE, CAPATH)
|
||||||
|
ctx.load_verify_locations(CERTFILE, capath=BYTES_CAPATH)
|
||||||
|
|
||||||
|
|
||||||
class NetworkedTests(unittest.TestCase):
|
class NetworkedTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_connect(self):
|
def test_connect(self):
|
||||||
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
||||||
cert_reqs=ssl.CERT_NONE)
|
cert_reqs=ssl.CERT_NONE)
|
||||||
s.connect(("svn.python.org", 443))
|
try:
|
||||||
c = s.getpeercert()
|
s.connect(("svn.python.org", 443))
|
||||||
if c:
|
self.assertEqual({}, s.getpeercert())
|
||||||
self.fail("Peer cert %s shouldn't be here!")
|
finally:
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
# this should fail because we have no verification certs
|
# this should fail because we have no verification certs
|
||||||
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
||||||
cert_reqs=ssl.CERT_REQUIRED)
|
cert_reqs=ssl.CERT_REQUIRED)
|
||||||
try:
|
self.assertRaisesRegexp(ssl.SSLError, "certificate verify failed",
|
||||||
s.connect(("svn.python.org", 443))
|
s.connect, ("svn.python.org", 443))
|
||||||
except ssl.SSLError:
|
s.close()
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
s.close()
|
|
||||||
|
|
||||||
# this should succeed because we specify the root cert
|
# this should succeed because we specify the root cert
|
||||||
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
||||||
|
@ -170,6 +269,56 @@ class NetworkedTests(unittest.TestCase):
|
||||||
ca_certs=SVN_PYTHON_ORG_ROOT_CERT)
|
ca_certs=SVN_PYTHON_ORG_ROOT_CERT)
|
||||||
try:
|
try:
|
||||||
s.connect(("svn.python.org", 443))
|
s.connect(("svn.python.org", 443))
|
||||||
|
self.assertTrue(s.getpeercert())
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
def test_connect_with_context(self):
|
||||||
|
# Same as test_connect, but with a separately created context
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||||
|
s = ctx.wrap_socket(socket.socket(socket.AF_INET))
|
||||||
|
s.connect(("svn.python.org", 443))
|
||||||
|
try:
|
||||||
|
self.assertEqual({}, s.getpeercert())
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
# This should fail because we have no verification certs
|
||||||
|
ctx.verify_mode = ssl.CERT_REQUIRED
|
||||||
|
s = ctx.wrap_socket(socket.socket(socket.AF_INET))
|
||||||
|
self.assertRaisesRegexp(ssl.SSLError, "certificate verify failed",
|
||||||
|
s.connect, ("svn.python.org", 443))
|
||||||
|
s.close()
|
||||||
|
# This should succeed because we specify the root cert
|
||||||
|
ctx.load_verify_locations(SVN_PYTHON_ORG_ROOT_CERT)
|
||||||
|
s = ctx.wrap_socket(socket.socket(socket.AF_INET))
|
||||||
|
s.connect(("svn.python.org", 443))
|
||||||
|
try:
|
||||||
|
cert = s.getpeercert()
|
||||||
|
self.assertTrue(cert)
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
def test_connect_capath(self):
|
||||||
|
# Verify server certificates using the `capath` argument
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||||
|
ctx.verify_mode = ssl.CERT_REQUIRED
|
||||||
|
ctx.load_verify_locations(capath=CAPATH)
|
||||||
|
s = ctx.wrap_socket(socket.socket(socket.AF_INET))
|
||||||
|
s.connect(("svn.python.org", 443))
|
||||||
|
try:
|
||||||
|
cert = s.getpeercert()
|
||||||
|
self.assertTrue(cert)
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
# Same with a bytes `capath` argument
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||||
|
ctx.verify_mode = ssl.CERT_REQUIRED
|
||||||
|
ctx.load_verify_locations(capath=BYTES_CAPATH)
|
||||||
|
s = ctx.wrap_socket(socket.socket(socket.AF_INET))
|
||||||
|
s.connect(("svn.python.org", 443))
|
||||||
|
try:
|
||||||
|
cert = s.getpeercert()
|
||||||
|
self.assertTrue(cert)
|
||||||
finally:
|
finally:
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
|
@ -1227,18 +1376,14 @@ def test_main(verbose=False):
|
||||||
if skip_expected:
|
if skip_expected:
|
||||||
raise unittest.SkipTest("No SSL support")
|
raise unittest.SkipTest("No SSL support")
|
||||||
|
|
||||||
global CERTFILE, SVN_PYTHON_ORG_ROOT_CERT
|
for filename in [
|
||||||
CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir,
|
CERTFILE, SVN_PYTHON_ORG_ROOT_CERT, BYTES_CERTFILE,
|
||||||
"keycert.pem")
|
ONLYCERT, ONLYKEY, BYTES_ONLYCERT, BYTES_ONLYKEY,
|
||||||
SVN_PYTHON_ORG_ROOT_CERT = os.path.join(
|
BADCERT, BADKEY, EMPTYCERT]:
|
||||||
os.path.dirname(__file__) or os.curdir,
|
if not os.path.exists(filename):
|
||||||
"https_svn_python_org_root.pem")
|
raise support.TestFailed("Can't read certificate file %r" % filename)
|
||||||
|
|
||||||
if (not os.path.exists(CERTFILE) or
|
tests = [ContextTests, BasicSocketTests]
|
||||||
not os.path.exists(SVN_PYTHON_ORG_ROOT_CERT)):
|
|
||||||
raise support.TestFailed("Can't read certificate files!")
|
|
||||||
|
|
||||||
tests = [BasicTests]
|
|
||||||
|
|
||||||
if support.is_resource_enabled('network'):
|
if support.is_resource_enabled('network'):
|
||||||
tests.append(NetworkedTests)
|
tests.append(NetworkedTests)
|
||||||
|
|
|
@ -363,6 +363,8 @@ C-API
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #8550: Add first class ``SSLContext`` objects to the ssl module.
|
||||||
|
|
||||||
- Issue #8681: Make the zlib module's error messages more informative when
|
- Issue #8681: Make the zlib module's error messages more informative when
|
||||||
the zlib itself doesn't give any detailed explanation.
|
the zlib itself doesn't give any detailed explanation.
|
||||||
|
|
||||||
|
|
535
Modules/_ssl.c
535
Modules/_ssl.c
|
@ -115,23 +115,29 @@ static unsigned int _ssl_locks_count = 0;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
PyObject *Socket; /* weakref to socket on which we're layered */
|
SSL_CTX *ctx;
|
||||||
SSL_CTX* ctx;
|
} PySSLContext;
|
||||||
SSL* ssl;
|
|
||||||
X509* peer_cert;
|
|
||||||
int shutdown_seen_zero;
|
|
||||||
|
|
||||||
} PySSLObject;
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
PyObject *Socket; /* weakref to socket on which we're layered */
|
||||||
|
SSL *ssl;
|
||||||
|
X509 *peer_cert;
|
||||||
|
int shutdown_seen_zero;
|
||||||
|
} PySSLSocket;
|
||||||
|
|
||||||
static PyTypeObject PySSL_Type;
|
static PyTypeObject PySSLContext_Type;
|
||||||
static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args);
|
static PyTypeObject PySSLSocket_Type;
|
||||||
static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args);
|
|
||||||
|
static PyObject *PySSL_SSLwrite(PySSLSocket *self, PyObject *args);
|
||||||
|
static PyObject *PySSL_SSLread(PySSLSocket *self, PyObject *args);
|
||||||
static int check_socket_and_wait_for_timeout(PySocketSockObject *s,
|
static int check_socket_and_wait_for_timeout(PySocketSockObject *s,
|
||||||
int writing);
|
int writing);
|
||||||
static PyObject *PySSL_peercert(PySSLObject *self, PyObject *args);
|
static PyObject *PySSL_peercert(PySSLSocket *self, PyObject *args);
|
||||||
static PyObject *PySSL_cipher(PySSLObject *self);
|
static PyObject *PySSL_cipher(PySSLSocket *self);
|
||||||
|
|
||||||
#define PySSLObject_Check(v) (Py_TYPE(v) == &PySSL_Type)
|
#define PySSLContext_Check(v) (Py_TYPE(v) == &PySSLContext_Type)
|
||||||
|
#define PySSLSocket_Check(v) (Py_TYPE(v) == &PySSLSocket_Type)
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SOCKET_IS_NONBLOCKING,
|
SOCKET_IS_NONBLOCKING,
|
||||||
|
@ -154,7 +160,7 @@ typedef enum {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
PySSL_SetError(PySSLObject *obj, int ret, char *filename, int lineno)
|
PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
|
||||||
{
|
{
|
||||||
PyObject *v;
|
PyObject *v;
|
||||||
char buf[2048];
|
char buf[2048];
|
||||||
|
@ -258,126 +264,28 @@ _setSSLError (char *errstr, int errcode, char *filename, int lineno) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PySSLObject *
|
static PySSLSocket *
|
||||||
newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file,
|
newPySSLSocket(SSL_CTX *ctx, PySocketSockObject *sock,
|
||||||
enum py_ssl_server_or_client socket_type,
|
enum py_ssl_server_or_client socket_type)
|
||||||
enum py_ssl_cert_requirements certreq,
|
|
||||||
enum py_ssl_version proto_version,
|
|
||||||
char *cacerts_file, char *ciphers)
|
|
||||||
{
|
{
|
||||||
PySSLObject *self;
|
PySSLSocket *self;
|
||||||
char *errstr = NULL;
|
|
||||||
int ret;
|
|
||||||
int verification_mode;
|
|
||||||
|
|
||||||
self = PyObject_New(PySSLObject, &PySSL_Type); /* Create new object */
|
self = PyObject_New(PySSLSocket, &PySSLSocket_Type);
|
||||||
if (self == NULL)
|
if (self == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
self->peer_cert = NULL;
|
self->peer_cert = NULL;
|
||||||
self->ssl = NULL;
|
self->ssl = NULL;
|
||||||
self->ctx = NULL;
|
|
||||||
self->Socket = NULL;
|
self->Socket = NULL;
|
||||||
|
|
||||||
/* Make sure the SSL error state is initialized */
|
/* Make sure the SSL error state is initialized */
|
||||||
(void) ERR_get_state();
|
(void) ERR_get_state();
|
||||||
ERR_clear_error();
|
ERR_clear_error();
|
||||||
|
|
||||||
if ((key_file && !cert_file) || (!key_file && cert_file)) {
|
|
||||||
errstr = ERRSTR("Both the key & certificate files "
|
|
||||||
"must be specified");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((socket_type == PY_SSL_SERVER) &&
|
|
||||||
((key_file == NULL) || (cert_file == NULL))) {
|
|
||||||
errstr = ERRSTR("Both the key & certificate files "
|
|
||||||
"must be specified for server-side operation");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
PySSL_BEGIN_ALLOW_THREADS
|
PySSL_BEGIN_ALLOW_THREADS
|
||||||
if (proto_version == PY_SSL_VERSION_TLS1)
|
self->ssl = SSL_new(ctx);
|
||||||
self->ctx = SSL_CTX_new(TLSv1_method()); /* Set up context */
|
|
||||||
else if (proto_version == PY_SSL_VERSION_SSL3)
|
|
||||||
self->ctx = SSL_CTX_new(SSLv3_method()); /* Set up context */
|
|
||||||
else if (proto_version == PY_SSL_VERSION_SSL2)
|
|
||||||
self->ctx = SSL_CTX_new(SSLv2_method()); /* Set up context */
|
|
||||||
else if (proto_version == PY_SSL_VERSION_SSL23)
|
|
||||||
self->ctx = SSL_CTX_new(SSLv23_method()); /* Set up context */
|
|
||||||
PySSL_END_ALLOW_THREADS
|
PySSL_END_ALLOW_THREADS
|
||||||
|
SSL_set_fd(self->ssl, sock->sock_fd);
|
||||||
if (self->ctx == NULL) {
|
|
||||||
errstr = ERRSTR("Invalid SSL protocol variant specified.");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ciphers != NULL) {
|
|
||||||
ret = SSL_CTX_set_cipher_list(self->ctx, ciphers);
|
|
||||||
if (ret == 0) {
|
|
||||||
errstr = ERRSTR("No cipher can be selected.");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (certreq != PY_SSL_CERT_NONE) {
|
|
||||||
if (cacerts_file == NULL) {
|
|
||||||
errstr = ERRSTR("No root certificates specified for "
|
|
||||||
"verification of other-side certificates.");
|
|
||||||
goto fail;
|
|
||||||
} else {
|
|
||||||
PySSL_BEGIN_ALLOW_THREADS
|
|
||||||
ret = SSL_CTX_load_verify_locations(self->ctx,
|
|
||||||
cacerts_file,
|
|
||||||
NULL);
|
|
||||||
PySSL_END_ALLOW_THREADS
|
|
||||||
if (ret != 1) {
|
|
||||||
_setSSLError(NULL, 0, __FILE__, __LINE__);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (key_file) {
|
|
||||||
PySSL_BEGIN_ALLOW_THREADS
|
|
||||||
ret = SSL_CTX_use_PrivateKey_file(self->ctx, key_file,
|
|
||||||
SSL_FILETYPE_PEM);
|
|
||||||
PySSL_END_ALLOW_THREADS
|
|
||||||
if (ret != 1) {
|
|
||||||
_setSSLError(NULL, ret, __FILE__, __LINE__);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
PySSL_BEGIN_ALLOW_THREADS
|
|
||||||
ret = SSL_CTX_use_certificate_chain_file(self->ctx,
|
|
||||||
cert_file);
|
|
||||||
PySSL_END_ALLOW_THREADS
|
|
||||||
if (ret != 1) {
|
|
||||||
/*
|
|
||||||
fprintf(stderr, "ret is %d, errcode is %lu, %lu, with file \"%s\"\n",
|
|
||||||
ret, ERR_peek_error(), ERR_peek_last_error(), cert_file);
|
|
||||||
*/
|
|
||||||
if (ERR_peek_last_error() != 0) {
|
|
||||||
_setSSLError(NULL, ret, __FILE__, __LINE__);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ssl compatibility */
|
|
||||||
SSL_CTX_set_options(self->ctx, SSL_OP_ALL);
|
|
||||||
|
|
||||||
verification_mode = SSL_VERIFY_NONE;
|
|
||||||
if (certreq == PY_SSL_CERT_OPTIONAL)
|
|
||||||
verification_mode = SSL_VERIFY_PEER;
|
|
||||||
else if (certreq == PY_SSL_CERT_REQUIRED)
|
|
||||||
verification_mode = (SSL_VERIFY_PEER |
|
|
||||||
SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
|
|
||||||
SSL_CTX_set_verify(self->ctx, verification_mode,
|
|
||||||
NULL); /* set verify lvl */
|
|
||||||
|
|
||||||
PySSL_BEGIN_ALLOW_THREADS
|
|
||||||
self->ssl = SSL_new(self->ctx); /* New ssl struct */
|
|
||||||
PySSL_END_ALLOW_THREADS
|
|
||||||
SSL_set_fd(self->ssl, Sock->sock_fd); /* Set the socket for SSL */
|
|
||||||
#ifdef SSL_MODE_AUTO_RETRY
|
#ifdef SSL_MODE_AUTO_RETRY
|
||||||
SSL_set_mode(self->ssl, SSL_MODE_AUTO_RETRY);
|
SSL_set_mode(self->ssl, SSL_MODE_AUTO_RETRY);
|
||||||
#endif
|
#endif
|
||||||
|
@ -385,8 +293,7 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file,
|
||||||
/* If the socket is in non-blocking mode or timeout mode, set the BIO
|
/* If the socket is in non-blocking mode or timeout mode, set the BIO
|
||||||
* to non-blocking mode (blocking is the default)
|
* to non-blocking mode (blocking is the default)
|
||||||
*/
|
*/
|
||||||
if (Sock->sock_timeout >= 0.0) {
|
if (sock->sock_timeout >= 0.0) {
|
||||||
/* Set both the read and write BIO's to non-blocking mode */
|
|
||||||
BIO_set_nbio(SSL_get_rbio(self->ssl), 1);
|
BIO_set_nbio(SSL_get_rbio(self->ssl), 1);
|
||||||
BIO_set_nbio(SSL_get_wbio(self->ssl), 1);
|
BIO_set_nbio(SSL_get_wbio(self->ssl), 1);
|
||||||
}
|
}
|
||||||
|
@ -398,57 +305,13 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file,
|
||||||
SSL_set_accept_state(self->ssl);
|
SSL_set_accept_state(self->ssl);
|
||||||
PySSL_END_ALLOW_THREADS
|
PySSL_END_ALLOW_THREADS
|
||||||
|
|
||||||
self->Socket = PyWeakref_NewRef((PyObject *) Sock, Py_None);
|
self->Socket = PyWeakref_NewRef((PyObject *) sock, NULL);
|
||||||
return self;
|
return self;
|
||||||
fail:
|
|
||||||
if (errstr)
|
|
||||||
PyErr_SetString(PySSLErrorObject, errstr);
|
|
||||||
Py_DECREF(self);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
PySSL_sslwrap(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PySocketSockObject *Sock;
|
|
||||||
int server_side = 0;
|
|
||||||
int verification_mode = PY_SSL_CERT_NONE;
|
|
||||||
int protocol = PY_SSL_VERSION_SSL23;
|
|
||||||
char *key_file = NULL;
|
|
||||||
char *cert_file = NULL;
|
|
||||||
char *cacerts_file = NULL;
|
|
||||||
char *ciphers = NULL;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!i|zziizz:sslwrap",
|
|
||||||
PySocketModule.Sock_Type,
|
|
||||||
&Sock,
|
|
||||||
&server_side,
|
|
||||||
&key_file, &cert_file,
|
|
||||||
&verification_mode, &protocol,
|
|
||||||
&cacerts_file, &ciphers))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
/*
|
|
||||||
fprintf(stderr,
|
|
||||||
"server_side is %d, keyfile %p, certfile %p, verify_mode %d, "
|
|
||||||
"protocol %d, certs %p\n",
|
|
||||||
server_side, key_file, cert_file, verification_mode,
|
|
||||||
protocol, cacerts_file);
|
|
||||||
*/
|
|
||||||
|
|
||||||
return (PyObject *) newPySSLObject(Sock, key_file, cert_file,
|
|
||||||
server_side, verification_mode,
|
|
||||||
protocol, cacerts_file,
|
|
||||||
ciphers);
|
|
||||||
}
|
|
||||||
|
|
||||||
PyDoc_STRVAR(ssl_doc,
|
|
||||||
"sslwrap(socket, server_side, [keyfile, certfile, certs_mode, protocol,\n"
|
|
||||||
" cacertsfile, ciphers]) -> sslobject");
|
|
||||||
|
|
||||||
/* SSL object methods */
|
/* SSL object methods */
|
||||||
|
|
||||||
static PyObject *PySSL_SSLdo_handshake(PySSLObject *self)
|
static PyObject *PySSL_SSLdo_handshake(PySSLSocket *self)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
int err;
|
int err;
|
||||||
|
@ -986,7 +849,7 @@ PySSL_test_decode_certificate (PyObject *mod, PyObject *args) {
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
PySSL_peercert(PySSLObject *self, PyObject *args)
|
PySSL_peercert(PySSLSocket *self, PyObject *args)
|
||||||
{
|
{
|
||||||
PyObject *retval = NULL;
|
PyObject *retval = NULL;
|
||||||
int len;
|
int len;
|
||||||
|
@ -1017,8 +880,7 @@ PySSL_peercert(PySSLObject *self, PyObject *args)
|
||||||
return retval;
|
return retval;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
verification = SSL_CTX_get_verify_mode(SSL_get_SSL_CTX(self->ssl));
|
||||||
verification = SSL_CTX_get_verify_mode(self->ctx);
|
|
||||||
if ((verification & SSL_VERIFY_PEER) == 0)
|
if ((verification & SSL_VERIFY_PEER) == 0)
|
||||||
return PyDict_New();
|
return PyDict_New();
|
||||||
else
|
else
|
||||||
|
@ -1038,7 +900,7 @@ If the optional argument is True, returns a DER-encoded copy of the\n\
|
||||||
peer certificate, or None if no certificate was provided. This will\n\
|
peer certificate, or None if no certificate was provided. This will\n\
|
||||||
return the certificate even if it wasn't validated.");
|
return the certificate even if it wasn't validated.");
|
||||||
|
|
||||||
static PyObject *PySSL_cipher (PySSLObject *self) {
|
static PyObject *PySSL_cipher (PySSLSocket *self) {
|
||||||
|
|
||||||
PyObject *retval, *v;
|
PyObject *retval, *v;
|
||||||
SSL_CIPHER *current;
|
SSL_CIPHER *current;
|
||||||
|
@ -1084,14 +946,12 @@ static PyObject *PySSL_cipher (PySSLObject *self) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PySSL_dealloc(PySSLObject *self)
|
static void PySSL_dealloc(PySSLSocket *self)
|
||||||
{
|
{
|
||||||
if (self->peer_cert) /* Possible not to have one? */
|
if (self->peer_cert) /* Possible not to have one? */
|
||||||
X509_free (self->peer_cert);
|
X509_free (self->peer_cert);
|
||||||
if (self->ssl)
|
if (self->ssl)
|
||||||
SSL_free(self->ssl);
|
SSL_free(self->ssl);
|
||||||
if (self->ctx)
|
|
||||||
SSL_CTX_free(self->ctx);
|
|
||||||
Py_XDECREF(self->Socket);
|
Py_XDECREF(self->Socket);
|
||||||
PyObject_Del(self);
|
PyObject_Del(self);
|
||||||
}
|
}
|
||||||
|
@ -1166,7 +1026,7 @@ normal_return:
|
||||||
return rc == 0 ? SOCKET_HAS_TIMED_OUT : SOCKET_OPERATION_OK;
|
return rc == 0 ? SOCKET_HAS_TIMED_OUT : SOCKET_OPERATION_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args)
|
static PyObject *PySSL_SSLwrite(PySSLSocket *self, PyObject *args)
|
||||||
{
|
{
|
||||||
Py_buffer buf;
|
Py_buffer buf;
|
||||||
int len;
|
int len;
|
||||||
|
@ -1250,7 +1110,7 @@ PyDoc_STRVAR(PySSL_SSLwrite_doc,
|
||||||
Writes the string s into the SSL object. Returns the number\n\
|
Writes the string s into the SSL object. Returns the number\n\
|
||||||
of bytes written.");
|
of bytes written.");
|
||||||
|
|
||||||
static PyObject *PySSL_SSLpending(PySSLObject *self)
|
static PyObject *PySSL_SSLpending(PySSLSocket *self)
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
|
@ -1269,7 +1129,7 @@ PyDoc_STRVAR(PySSL_SSLpending_doc,
|
||||||
Returns the number of already decrypted bytes available for read,\n\
|
Returns the number of already decrypted bytes available for read,\n\
|
||||||
pending on the connection.\n");
|
pending on the connection.\n");
|
||||||
|
|
||||||
static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args)
|
static PyObject *PySSL_SSLread(PySSLSocket *self, PyObject *args)
|
||||||
{
|
{
|
||||||
PyObject *dest = NULL;
|
PyObject *dest = NULL;
|
||||||
Py_buffer buf;
|
Py_buffer buf;
|
||||||
|
@ -1392,7 +1252,7 @@ PyDoc_STRVAR(PySSL_SSLread_doc,
|
||||||
\n\
|
\n\
|
||||||
Read up to len bytes from the SSL socket.");
|
Read up to len bytes from the SSL socket.");
|
||||||
|
|
||||||
static PyObject *PySSL_SSLshutdown(PySSLObject *self)
|
static PyObject *PySSL_SSLshutdown(PySSLSocket *self)
|
||||||
{
|
{
|
||||||
int err, ssl_err, sockstate, nonblocking;
|
int err, ssl_err, sockstate, nonblocking;
|
||||||
int zeros = 0;
|
int zeros = 0;
|
||||||
|
@ -1497,10 +1357,10 @@ static PyMethodDef PySSLMethods[] = {
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
static PyTypeObject PySSL_Type = {
|
static PyTypeObject PySSLSocket_Type = {
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
"ssl.SSLContext", /*tp_name*/
|
"_ssl._SSLSocket", /*tp_name*/
|
||||||
sizeof(PySSLObject), /*tp_basicsize*/
|
sizeof(PySSLSocket), /*tp_basicsize*/
|
||||||
0, /*tp_itemsize*/
|
0, /*tp_itemsize*/
|
||||||
/* methods */
|
/* methods */
|
||||||
(destructor)PySSL_dealloc, /*tp_dealloc*/
|
(destructor)PySSL_dealloc, /*tp_dealloc*/
|
||||||
|
@ -1529,6 +1389,306 @@ static PyTypeObject PySSL_Type = {
|
||||||
PySSLMethods, /*tp_methods*/
|
PySSLMethods, /*tp_methods*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* _SSLContext objects
|
||||||
|
*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
context_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
char *kwlist[] = {"protocol", NULL};
|
||||||
|
PySSLContext *self;
|
||||||
|
int proto_version = PY_SSL_VERSION_SSL23;
|
||||||
|
SSL_CTX *ctx = NULL;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(
|
||||||
|
args, kwds, "i:_SSLContext", kwlist,
|
||||||
|
&proto_version))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
PySSL_BEGIN_ALLOW_THREADS
|
||||||
|
if (proto_version == PY_SSL_VERSION_TLS1)
|
||||||
|
ctx = SSL_CTX_new(TLSv1_method());
|
||||||
|
else if (proto_version == PY_SSL_VERSION_SSL3)
|
||||||
|
ctx = SSL_CTX_new(SSLv3_method());
|
||||||
|
else if (proto_version == PY_SSL_VERSION_SSL2)
|
||||||
|
ctx = SSL_CTX_new(SSLv2_method());
|
||||||
|
else if (proto_version == PY_SSL_VERSION_SSL23)
|
||||||
|
ctx = SSL_CTX_new(SSLv23_method());
|
||||||
|
else
|
||||||
|
proto_version = -1;
|
||||||
|
PySSL_END_ALLOW_THREADS
|
||||||
|
|
||||||
|
if (proto_version == -1) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"invalid protocol version");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (ctx == NULL) {
|
||||||
|
PyErr_SetString(PySSLErrorObject,
|
||||||
|
"failed to allocate SSL context");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(type != NULL && type->tp_alloc != NULL);
|
||||||
|
self = (PySSLContext *) type->tp_alloc(type, 0);
|
||||||
|
if (self == NULL) {
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
self->ctx = ctx;
|
||||||
|
/* Defaults */
|
||||||
|
SSL_CTX_set_verify(self->ctx, SSL_VERIFY_NONE, NULL);
|
||||||
|
SSL_CTX_set_options(self->ctx, SSL_OP_ALL);
|
||||||
|
|
||||||
|
return (PyObject *)self;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
context_dealloc(PySSLContext *self)
|
||||||
|
{
|
||||||
|
SSL_CTX_free(self->ctx);
|
||||||
|
Py_TYPE(self)->tp_free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
set_ciphers(PySSLContext *self, PyObject *args)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
const char *cipherlist;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "s:set_ciphers", &cipherlist))
|
||||||
|
return NULL;
|
||||||
|
ret = SSL_CTX_set_cipher_list(self->ctx, cipherlist);
|
||||||
|
if (ret == 0) {
|
||||||
|
PyErr_SetString(PySSLErrorObject,
|
||||||
|
"No cipher can be selected.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
get_verify_mode(PySSLContext *self, void *c)
|
||||||
|
{
|
||||||
|
switch (SSL_CTX_get_verify_mode(self->ctx)) {
|
||||||
|
case SSL_VERIFY_NONE:
|
||||||
|
return PyLong_FromLong(PY_SSL_CERT_NONE);
|
||||||
|
case SSL_VERIFY_PEER:
|
||||||
|
return PyLong_FromLong(PY_SSL_CERT_OPTIONAL);
|
||||||
|
case SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT:
|
||||||
|
return PyLong_FromLong(PY_SSL_CERT_REQUIRED);
|
||||||
|
}
|
||||||
|
PyErr_SetString(PySSLErrorObject,
|
||||||
|
"invalid return value from SSL_CTX_get_verify_mode");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
set_verify_mode(PySSLContext *self, PyObject *arg, void *c)
|
||||||
|
{
|
||||||
|
int n, mode;
|
||||||
|
if (!PyArg_Parse(arg, "i", &n))
|
||||||
|
return -1;
|
||||||
|
if (n == PY_SSL_CERT_NONE)
|
||||||
|
mode = SSL_VERIFY_NONE;
|
||||||
|
else if (n == PY_SSL_CERT_OPTIONAL)
|
||||||
|
mode = SSL_VERIFY_PEER;
|
||||||
|
else if (n == PY_SSL_CERT_REQUIRED)
|
||||||
|
mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
||||||
|
else {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"invalid value for verify_mode");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
SSL_CTX_set_verify(self->ctx, mode, NULL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
load_cert_chain(PySSLContext *self, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
char *kwlist[] = {"certfile", "keyfile", NULL};
|
||||||
|
PyObject *certfile, *keyfile = NULL;
|
||||||
|
PyObject *certfile_bytes = NULL, *keyfile_bytes = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds,
|
||||||
|
"O|O:load_cert_chain", kwlist,
|
||||||
|
&certfile, &keyfile))
|
||||||
|
return NULL;
|
||||||
|
if (keyfile == Py_None)
|
||||||
|
keyfile = NULL;
|
||||||
|
if (!PyUnicode_FSConverter(certfile, &certfile_bytes)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"certfile should be a valid filesystem path");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (keyfile && !PyUnicode_FSConverter(keyfile, &keyfile_bytes)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"keyfile should be a valid filesystem path");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
PySSL_BEGIN_ALLOW_THREADS
|
||||||
|
r = SSL_CTX_use_certificate_chain_file(self->ctx,
|
||||||
|
PyBytes_AS_STRING(certfile_bytes));
|
||||||
|
PySSL_END_ALLOW_THREADS
|
||||||
|
if (r != 1) {
|
||||||
|
_setSSLError(NULL, 0, __FILE__, __LINE__);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
PySSL_BEGIN_ALLOW_THREADS
|
||||||
|
r = SSL_CTX_use_RSAPrivateKey_file(self->ctx,
|
||||||
|
PyBytes_AS_STRING(keyfile ? keyfile_bytes : certfile_bytes),
|
||||||
|
SSL_FILETYPE_PEM);
|
||||||
|
PySSL_END_ALLOW_THREADS
|
||||||
|
Py_XDECREF(keyfile_bytes);
|
||||||
|
Py_XDECREF(certfile_bytes);
|
||||||
|
if (r != 1) {
|
||||||
|
_setSSLError(NULL, 0, __FILE__, __LINE__);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PySSL_BEGIN_ALLOW_THREADS
|
||||||
|
r = SSL_CTX_check_private_key(self->ctx);
|
||||||
|
PySSL_END_ALLOW_THREADS
|
||||||
|
if (r != 1) {
|
||||||
|
_setSSLError(NULL, 0, __FILE__, __LINE__);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
|
||||||
|
error:
|
||||||
|
Py_XDECREF(keyfile_bytes);
|
||||||
|
Py_XDECREF(certfile_bytes);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
load_verify_locations(PySSLContext *self, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
char *kwlist[] = {"cafile", "capath", NULL};
|
||||||
|
PyObject *cafile = NULL, *capath = NULL;
|
||||||
|
PyObject *cafile_bytes = NULL, *capath_bytes = NULL;
|
||||||
|
const char *cafile_buf = NULL, *capath_buf = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds,
|
||||||
|
"|OO:load_verify_locations", kwlist,
|
||||||
|
&cafile, &capath))
|
||||||
|
return NULL;
|
||||||
|
if (cafile == Py_None)
|
||||||
|
cafile = NULL;
|
||||||
|
if (capath == Py_None)
|
||||||
|
capath = NULL;
|
||||||
|
if (cafile == NULL && capath == NULL) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"cafile and capath cannot be both omitted");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (cafile && !PyUnicode_FSConverter(cafile, &cafile_bytes)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"cafile should be a valid filesystem path");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (capath && !PyUnicode_FSConverter(capath, &capath_bytes)) {
|
||||||
|
Py_DECREF(cafile_bytes);
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"capath should be a valid filesystem path");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (cafile)
|
||||||
|
cafile_buf = PyBytes_AS_STRING(cafile_bytes);
|
||||||
|
if (capath)
|
||||||
|
capath_buf = PyBytes_AS_STRING(capath_bytes);
|
||||||
|
PySSL_BEGIN_ALLOW_THREADS
|
||||||
|
r = SSL_CTX_load_verify_locations(self->ctx, cafile_buf, capath_buf);
|
||||||
|
PySSL_END_ALLOW_THREADS
|
||||||
|
Py_XDECREF(cafile_bytes);
|
||||||
|
Py_XDECREF(capath_bytes);
|
||||||
|
if (r != 1) {
|
||||||
|
_setSSLError(NULL, 0, __FILE__, __LINE__);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
context_wrap_socket(PySSLContext *self, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
char *kwlist[] = {"sock", "server_side", NULL};
|
||||||
|
PySocketSockObject *sock;
|
||||||
|
int server_side = 0;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!i:_wrap_socket", kwlist,
|
||||||
|
PySocketModule.Sock_Type,
|
||||||
|
&sock, &server_side))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return (PyObject *) newPySSLSocket(self->ctx, sock, server_side);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyGetSetDef context_getsetlist[] = {
|
||||||
|
{"verify_mode", (getter) get_verify_mode,
|
||||||
|
(setter) set_verify_mode, NULL},
|
||||||
|
{NULL}, /* sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct PyMethodDef context_methods[] = {
|
||||||
|
{"_wrap_socket", (PyCFunction) context_wrap_socket,
|
||||||
|
METH_VARARGS | METH_KEYWORDS, NULL},
|
||||||
|
{"set_ciphers", (PyCFunction) set_ciphers,
|
||||||
|
METH_VARARGS, NULL},
|
||||||
|
{"load_cert_chain", (PyCFunction) load_cert_chain,
|
||||||
|
METH_VARARGS | METH_KEYWORDS, NULL},
|
||||||
|
{"load_verify_locations", (PyCFunction) load_verify_locations,
|
||||||
|
METH_VARARGS | METH_KEYWORDS, NULL},
|
||||||
|
{NULL, NULL} /* sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyTypeObject PySSLContext_Type = {
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
"_ssl._SSLContext", /*tp_name*/
|
||||||
|
sizeof(PySSLContext), /*tp_basicsize*/
|
||||||
|
0, /*tp_itemsize*/
|
||||||
|
(destructor)context_dealloc, /*tp_dealloc*/
|
||||||
|
0, /*tp_print*/
|
||||||
|
0, /*tp_getattr*/
|
||||||
|
0, /*tp_setattr*/
|
||||||
|
0, /*tp_reserved*/
|
||||||
|
0, /*tp_repr*/
|
||||||
|
0, /*tp_as_number*/
|
||||||
|
0, /*tp_as_sequence*/
|
||||||
|
0, /*tp_as_mapping*/
|
||||||
|
0, /*tp_hash*/
|
||||||
|
0, /*tp_call*/
|
||||||
|
0, /*tp_str*/
|
||||||
|
0, /*tp_getattro*/
|
||||||
|
0, /*tp_setattro*/
|
||||||
|
0, /*tp_as_buffer*/
|
||||||
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
||||||
|
0, /*tp_doc*/
|
||||||
|
0, /*tp_traverse*/
|
||||||
|
0, /*tp_clear*/
|
||||||
|
0, /*tp_richcompare*/
|
||||||
|
0, /*tp_weaklistoffset*/
|
||||||
|
0, /*tp_iter*/
|
||||||
|
0, /*tp_iternext*/
|
||||||
|
context_methods, /*tp_methods*/
|
||||||
|
0, /*tp_members*/
|
||||||
|
context_getsetlist, /*tp_getset*/
|
||||||
|
0, /*tp_base*/
|
||||||
|
0, /*tp_dict*/
|
||||||
|
0, /*tp_descr_get*/
|
||||||
|
0, /*tp_descr_set*/
|
||||||
|
0, /*tp_dictoffset*/
|
||||||
|
0, /*tp_init*/
|
||||||
|
0, /*tp_alloc*/
|
||||||
|
context_new, /*tp_new*/
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef HAVE_OPENSSL_RAND
|
#ifdef HAVE_OPENSSL_RAND
|
||||||
|
|
||||||
/* helper routines for seeding the SSL PRNG */
|
/* helper routines for seeding the SSL PRNG */
|
||||||
|
@ -1598,8 +1758,6 @@ fails or if it does provide enough data to seed PRNG.");
|
||||||
/* List of functions exported by this module. */
|
/* List of functions exported by this module. */
|
||||||
|
|
||||||
static PyMethodDef PySSL_methods[] = {
|
static PyMethodDef PySSL_methods[] = {
|
||||||
{"sslwrap", PySSL_sslwrap,
|
|
||||||
METH_VARARGS, ssl_doc},
|
|
||||||
{"_test_decode_cert", PySSL_test_decode_certificate,
|
{"_test_decode_cert", PySSL_test_decode_certificate,
|
||||||
METH_VARARGS},
|
METH_VARARGS},
|
||||||
#ifdef HAVE_OPENSSL_RAND
|
#ifdef HAVE_OPENSSL_RAND
|
||||||
|
@ -1708,7 +1866,9 @@ PyInit__ssl(void)
|
||||||
unsigned int major, minor, fix, patch, status;
|
unsigned int major, minor, fix, patch, status;
|
||||||
PySocketModule_APIObject *socket_api;
|
PySocketModule_APIObject *socket_api;
|
||||||
|
|
||||||
if (PyType_Ready(&PySSL_Type) < 0)
|
if (PyType_Ready(&PySSLContext_Type) < 0)
|
||||||
|
return NULL;
|
||||||
|
if (PyType_Ready(&PySSLSocket_Type) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
m = PyModule_Create(&_sslmodule);
|
m = PyModule_Create(&_sslmodule);
|
||||||
|
@ -1741,8 +1901,11 @@ PyInit__ssl(void)
|
||||||
return NULL;
|
return NULL;
|
||||||
if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0)
|
if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
if (PyDict_SetItemString(d, "SSLType",
|
if (PyDict_SetItemString(d, "_SSLContext",
|
||||||
(PyObject *)&PySSL_Type) != 0)
|
(PyObject *)&PySSLContext_Type) != 0)
|
||||||
|
return NULL;
|
||||||
|
if (PyDict_SetItemString(d, "_SSLSocket",
|
||||||
|
(PyObject *)&PySSLSocket_Type) != 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
PyModule_AddIntConstant(m, "SSL_ERROR_ZERO_RETURN",
|
PyModule_AddIntConstant(m, "SSL_ERROR_ZERO_RETURN",
|
||||||
PY_SSL_ERROR_ZERO_RETURN);
|
PY_SSL_ERROR_ZERO_RETURN);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue