Hello there,
We’ve been talking with @jvdprng over the github issues of tlslib and he suggested I write a message here 
I’ve been working on my own sans-io pure-python crypto-agnostic implementation of TLS 1.3 for the past two years (for fun): siotls. The latest stable-ish version (0.0.5) doesn’t implement this PEP and contains my own vision of what a TLS lib for python could look like.
I knew about PEP 543 but it seemed stale so I didn’t care to implement it. I discovered this revival not too long ago and I am eager to adapt siotls to use tlslib.
I’ve got many questions, I did read the whole forum but kinda diagonally, sorry if I’m asking anything that has been decided already.
Ok here it goes.
Socket Wrapping and Blocking Socket
There something I really like about the current ssl library, it is that it offers a wrap_socket function to wrap an existing socket. That’s something that doesn’t exist inside tlslib, the only two public functions are connect() and create_buffer(). Server-side the connect() function of tlslib.stdlib creates a non-blocking IPv4 + TCP socket. In my very first attempt, I tried to create an IPv6 server on ::1 which didn’t work because the underlying socket was AF_INET and not AF_INET6, see issue #68. The second thing I tried (using 127.0.0.1 instead) was to call accept() on the created TLSSocket object, I was expecting it to block until I could connect a client, but it instead rose an exception because socket had been set non-blocking by tlslib, see issue #71.
So in my first attempt, I got two assumptions wrong: that it would work with an IPv6 address, and that I could use it as a regular blocking socket.
I understand the desire to expose a simple interface to users. tlslib.stdlib could maybe do a dns lookup on the address to determine the socket family to use, but it won’t work with everything, e.g. Unix Domain Sockets. I don’t understand why it returns a non-blocking socket. Is it part of the spec? Is it for async? Aren’t async libraries gonna use TLSBuffer? What should people in a sync environment do? Is setting the socket blocking ok? Should we instead implement our own logic on top of TLSBuffer?
It is annoying that there is nothing to secure a regular blocking socket.
Configuration
In my own lib I have the following:
TLSConfiguration(side=...), unified for client and server
- No Context factory
TLSConnection(config, server_hostname=None), which is TLSBuffer in tlslib terms
tls_connection.wrap(tcp_socket) that returns a object close to TLSSocket in tlslib terms
A ton of ValueErrors / warnings for everything erroneous / insecure.
The split client vs server and the context factories are nice. I wasn’t too fond at first, but they indeed make the user experience better, I’ll adopt the structure 
I’ve got a whole set of questions/remarks regarding the configuration.
Client vs Server certificate_chain
The TLSServerConfiguration object is similar to the client one, except that it takes a Sequence[SigningChain] as the certificate_chain parameter.
Why? I mean I understand why it should be Sequence[SigningChain] server-side, but why is it a single SigningChain client-side?
All the Nones
There something else I don’t understand, why all the Nones? I get it that a user might not want to decide what ciphers to instantiate the configuration with, to let the system decides on a sane default value, but why can the property returns None as well?
I have a pretty strong feeling that the two configuration classes should perform some sanity checks, like to assert lowest_supported_version <= highest_supported_version, or that the private key and certificate match (but that requires access to crypto primitives, so maybe not for the stdlib). I think it should also decide on sane default values when the user provides nothing.
Maybe the configuration should be abstract in tlslib, to let every implementation decides on its own checks and to possibly narrow down the returned types. We could have some checks in the base class for the easy stuff (e.g. lowest_supported_version <= highest_supported_version). It would mean users must import and instantiate the concrete TLS(Client|Server)Configuration class from the implementation they are using. Shouldn’t be a problem as this is already the case for (Client|Server)Context.
Remove default for client truststore and server certificate_chain
Unless we want to support TLS 1.2 unsafe ciphers DH_anon_*, the ServerCertificate.certificate_chain must be set and non-empty (it’s a requirement of TLS), it should be a mandatory parameter server-side.
The same goes for ClientCertificate.truststore, it is set None by default at the moment, making the connection insecure by default. It should be a mandatory parameter client-side. Users who wish for an insecure connection are free to explicitly set the param None.
Ciphers
At the moment the only cryptographic primitives that are configurable with tlslib are the cipher suites (AES). I don’t know for TLS 1.2, but in TLS 1.3 the key exchanges (DH/x25519) and signature schemes (RSA/ECDSA) are negotiated as well. TLS 1.3 can even use a configuration to select the peer’s public key, and another to select the peer’s certificate chain (not implemented in siotls yet).
Negotiated Options
At the moment all the negotiated options end up on the TLSBuffer/TLSSocket class as negotiated_... attribute. In siotls I’m using a dedicated class for all the negotiated options, which is saved as attribute of my TLSBuffer.
tlslib:
tlssocket.negotiated_protocol
siotls:
tlsconn.nconfig.alpn # am renaming it inner_protocol, for // with tlslib Config
(there’s also the peer_certificate that is saved in my nconfig object, but I’ll move it on the socket, makes more sense to have it on the socket like tlslib does)
Like the configuration, I’ve got more negotiated stuff than tlslib, hence the dedicated object. I have no strong opinion but I fear there’s gonna be lots of negotiated_... attributes, just wanna open the discussion on what you think seems best?
Raw Public Key
siotls allows to setup a TLS connection between a client and server certificate-less using a public key server-side, and a list of trusted public keys client-side. That’s RFC 7250 and that’s pretty much like your everyday SSH server. I haven’t seen it deployed anywhere, but IMO it is great for IoT and outside the web.
For this to work with tlslib, I would need a PublicKey class pretty much like the existing PrivateKey one. There should also be an entry for a Collection[PublicKey] in the config, or could it be something in TrustStore? Same goes for SigningChain.
Anyway, I would like to open the discussion on this matter as well.
Certificate Chain, Certificate Revocation List
It seems that the current Certificate class only is about loading a single certificate. Certificate chains and certificate revocation lists (CRLs) are typically stored as multiple concatenated PEM-encoded certificates, how do we load those?
As a user, should I open and parse those files myself? Using Certificate.from_buffer for every PEM-certificate I find in the files? Or can I use Certificate.from_file directly?
Same goes for the other side, in siotls, should I expect a Certificate.buffer to hold multiple concatenated PEM certificates?
IMO there should be two other constructors:
@classmethod
def many_from_buffer(cls, buffer: bytes) -> Collection[Certificate]
@classmethod
def many_from_file(cls, path: os.PathLike) -> Collection[Certificate]
or maybe chain_from_buffer(...) -> Sequence[Certificate] and crl_from_buffer(...) -> Set[Certificate] (idem for from_file).
Please note that even if I have had my mind in TLS stuff for two years, I still don’t know if the leaf certificate appears first or last in a certificate chain. I expect other users will struggle with this as well. It would be nice if we don’t have to extract the leaf certificate from the chain ourselves for SigningChain, and just throw the fullchain and private key and let the class deal with it.
Save DER in-place
Is it ok if we change in-place the Certificate and PublicKey objects provided by the user to always guarantee buffer is set, and is DER? I am using asn1crypto in various places of siotls to extract information out of the cert/privkey, for doing sanity checks while loading the config, but also for communicating with backend cryptographic libraries. I don’t want to read and decore a PEM-encoded file every time I need to access this peer’s private key. At the moment I read and decode everything while instantiating the TLSConfiguration object and save it in-place in the certificate/private-key object. It is ok to do that? Should I make a copy instead?
This is my first time contributing to a PEP. Hopefully this message, the content and tone are appropriate, if not please tell me.
Thank you for this PEP. Let’s make something great!
Regards,
Julien