This looks great! I assume it’s all intended as part of the specified API right now, so if anything I refer to isn’t meant to be spec, just ignore it.
I wonder if we can get away with less specific enums, looking specifically at CipherSuite
, NextProtocol
and TlsVersion
? There is likely to be system-wide configuration for these, and it may get complicated to resolve specific (even default) requests from the Python side with what the OS allows (well, complicated for the user who has to deal with a random TLSError
that we raised ).
Off the cuff, I’d prefer an enum/factory with “system recommended settings”, “most secure settings”, “least secure settings” (and would seriously consider a noisy warning if the last one is ever used!) At the very least, DEFAULT_CIPHER_LIST
should be its own enum value, so that the backend gets to decide it rather than having to deal with whatever default gets into the API.
I’d really like TrustStore
, Certificate
and PrivateKey
to be initialisable from “arbitrary identifier”, and to not offer any kind of introspection (as a required API). This is to allow for things like specifying a certificate by subject or thumbprint/hash and letting the OS locate it in any of its trust stores - in some configurations, this can be the only option on Windows, and extracting any certificate (or a list of certs) from the system is disallowed.
TrustStore.system()
would be likely to return a marker object (possibly None
) rather than an actual trust store reference on Windows - something that the backend would identify and then handle totally differently from a custom-built trust store. That looks possible with this structure, but I mention it to make sure it remains that way
Again, it seems implied now, but might be worth specifying that the properties on TLSClientConfiguration
and TLSServerConfiguration
only return values if they were provided in the initializer, and aren’t meant to retrieve the current settings from the backend. (If they are intended to retrieve settings from the backend, then my suggestion is to not require that, but they appear to be data carriers rather than something the backend would implement.)
In case it’s helpful, I mapped a few of the concepts in the structure to the SChannel APIs that I think would be used to implement them. For the most part, they seem pretty well aligned, which is great! Maybe you’ve already got the mapping somewhere.
On my final look at the current structure, I have a vague concern that we could accidentally push users into having a tricky time matching types, particularly if Certificate
and TrustStore
are meant to be backend-specific. I may be wrong, but it feels like we want pure data all the way up to ServerContext
and ClientContext
, and then the backend decides how to handle it.
(A specific example: I see that OpenSSLCertificate.from_buffer
creates the temporary file and then returns an instance with reference to the filename. I think it’s better to keep the buffer at that point and have the _configure_context...
functions create a file if needed. This way, Certificate
can be a concrete type, possibly with a mechanism for smuggling extra key/values to a known backend.)
A hypothetical example: if we made a WindowsCertificate
class that creates the CERT_CONTEXT
immediately and loads the certificate in, that object is now functionally incompatible with certain backends, despite being type compatible (modulo all the type checking stuff that I just ignored). But a concrete Certificate
that doesn’t load the certificate immediately would be fully compatible with either, and it removes the temptation to create incompatible types.
That got longer than I intended, I’ll leave it there for now. Generally, I think we’re on a good path though, and I’m getting excited to actually have a go at implementing this on the Windows side (though I won’t stop anyone else from doing it if they’re more excited).