[Security-sig] Unified TLS API for Python: Round 2
Christian Heimes
christian at cheimes.de
Thu Jan 26 11:06:16 EST 2017
On 2017-01-26 15:23, Nick Coghlan wrote:
> On 26 January 2017 at 10:50, Cory Benfield <cory at lukasa.co.uk> wrote:
>>
>>> On 26 Jan 2017, at 07:49, Nick Coghlan <ncoghlan at gmail.com> wrote:
>>>
>>> Option 4: tls.TLSError, tls.WantReadError, tls.WantWriteError are
>>> defined as inheriting from ssl.SSLError, ssl.SSLWantReadError, and
>>> ssl.SSLWantWriteError *if* the latter are defined
>>>
>>> Option 5: as with Option 4, but the "ssl" module is also changed such
>>> that it *always* defines at least ssl.SSLError, ssl.SSLWantReadError,
>>> and ssl.SSLWantWriteError (and perhaps some of the other APIs that can
>>> be emulated atop the new tls abstraction), even if OpenSSL itself is
>>> unavailable
>>
>> Here’s my problem with this:
>>
>> try:
>> socket.recv(8192)
>> except tls.WantWriteError:
>> socket.write(some_buffer)
>>
>> This code does not work with the legacy ssl module, because isinstance(ssl.SSLWantWriteError, tls.WantWriteError) is false. This means that we need to write a shim over the legacy ssl module that wraps *all* API calls, catches all exceptions and then translates them into subclasses of the tls error classes. That seems entirely batty to me.
>
> OK, so we have two competing problems here:
>
> 1. How do we write *new* API client code that is agnostic to whether
> or not the security implementation is traditional ssl or a new tls
> backend?
> 2. How does a library that exports the ssl exceptions migrate to using
> a tls backend instead, without having to catch and rewrap tls
> exceptions in the legacy ones?
>
> Meeting both constraints at the same time would require exception
> *aliases* rather than one set of exceptions inheriting from the other,
> such that we get:
>
> assert tls.TLSError is ssl.SSLError
> assert tls.WantWriteError is ssl.SSLWantWriteError
> assert tls.WantReadError is ssl.SSLWantReadError
>
> In an ideal world, that could be handled just by having the ssl module
> import the new tls module and alias the exceptions accordingly.
> Talking to Christian about it, things might be a little messier in
> practice due to the way the _ssl/ssl C/Python split works, but there
> shouldn't be any insurmountable barriers to going down the exception
> aliasing path in 3.7+.
>
> Backports to older versions would still need to contend with these
> being different exception objects, but one possible way of tackling
> that would be to inject a dummy ssl module into sys.modules before the
> regular one had a chance to be imported.
For technical reasons it is beneficial to define SSL exception in C
code. I don't want to force people to load _ssl to avoid the overhead of
OpenSSL loading and initialization. After some brain storming with Nick
I came up with the idea to define the exceptions in _socket. The _ssl
module imports a PyCapsule from _socket any way. I can just piggyback on
the PyCapsule. For maximum compatibility with Python 2, the tls module
should subclass from socket.error anyway, too.
Python 2:
>>> import ssl
>>> ssl.SSLError.__mro__
(<class 'ssl.SSLError'>, <class 'socket.error'>, <type
'exceptions.IOError'>, <type 'exceptions.EnvironmentError'>, <type
'exceptions.StandardError'>, <type 'exceptions.Exception'>, <type
'exceptions.BaseException'>, <type 'object'>)
Python 3:
>>> import ssl, socket
>>> ssl.SSLError.__mro__
(<class 'ssl.SSLError'>, <class 'OSError'>, <class 'Exception'>, <class
'BaseException'>, <class 'object'>)
>>> socket.error
<class 'OSError'>
This import block should (hopefully) cover all bases:
try:
# CPython 3.7+
from _socket import SSLError as TLSError
from _socket import SSLWantWriteError as WantWriteError
from _socket import SSLWantReadError as WantReadError
except ImportError:
# CPython < 3.7 and other implementations
try:
from ssl import SSLError as TLSError
from ssl import SSLWantWriteError as WantWriteError
from ssl import SSLWantReadError as WantReadError
except ImportError:
# ssl module is not available
# Python 2: socket.error is a subclass of IOError
# Python 3: socket.error is just an alias of OSError
import socket
class TLSError(socket.error):
pass
class WantWriteError(TLSError):
pass
class WantReadError(TLSError):
pass
Christian
More information about the Security-SIG
mailing list