Repository: pyca/cryptography

Issue:
RFC 9266: Channel Bindings for TLS 1.3 support
Can you add the support of RFC 9266: Channel Bindings for TLS 1.3?
- https://datatracker.ietf.org/doc/html/rfc9266

Little details, to know easily:
- tls-unique for TLS =< 1.2
- tls-exporter for TLS = 1.3

Thanks in advance.


Discussion:
Could you share a bit about the use case you have for this?
It is used for SCRAM-SHA-1-PLUS / SCRAM-SHA-256-PLUS / SCRAM-SHA-512-PLUS / SCRAM-SHA3-512-PLUS.

Some RFCs:
- https://tools.ietf.org/html/rfc5802
- https://tools.ietf.org/html/rfc6120
- https://tools.ietf.org/html/rfc7677
- https://tools.ietf.org/html/rfc9051
- https://tools.ietf.org/html/draft-melnikov-scram-sha-512
- https://tools.ietf.org/html/draft-melnikov-scram-sha3-512
- https://tools.ietf.org/html/draft-melnikov-scram-bis
- https://tools.ietf.org/html/draft-ietf-kitten-scram-2fa

Products which use TLS Binding, some have already added tls-exporter in more tls-unique.

SCRAM-SHA-1(-PLUS) and SCRAM-SHA-256(-PLUS):
- Tigase XMPP Server 8.0.0 (XMPP server): https://docs.tigase.net/tigase-server/8.0.0/Administration_Guide/html/
- Gajim 1.2.x (XMPP client): https://gajim.org/
- nbxmpp 2.x, Python library (XMPP library): https://dev.gajim.org/gajim/python-nbxmpp/
- Prosody IM 0.12 (XMPP server): https://hg.prosody.im/trunk/rev/60b445183d84 + https://hg.prosody.im/trunk/rev/e458578ddfd3 https://prosody.im/
- GNU SASL 1.10.0 (Libgsasl): http://www.gnu.org/software/gsasl/
- aiosasl: https://github.com/horazont/aiosasl
- Mellium SASL: https://github.com/mellium/sasl
- Mellium XMPP (XMPP library): https://github.com/mellium/xmpp
- xmpp-rs (XMPP library): https://gitlab.com/xmpp-rs/xmpp-rs
- Multipurpose XMPP-Webhook (Built for DevOps Alerts): https://github.com/tmsmr/xmpp-webhook
- Stanza (XMPP library): https://github.com/legastero/stanza ("SCRAM-SHA-256 is supported now. -PLUS is too, technically, but BOSH/WebSocket don't provide channel binding info. It'll be there once TCP/TLS support is added.")
- Exim (Mail server): https://bugs.exim.org/show_bug.cgi?id=2349 // Exim uses GNU SASL

SCRAM-SHA-256(-PLUS):
- PostgreSQL 13: https://www.postgresql.org/docs/13/sasl-authentication.html + https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/how-to-securely-authenticate-with-scram-in-postgres-13/ba-p/1548319
- Native PostgreSQL driver for the Rust programming language: https://github.com/sfackler/rust-postgres
- Npgsql is the .NET data provider for PostgreSQL: https://www.npgsql.org/ + https://github.com/npgsql/npgsql
- A postgres driver for crystal: https://github.com/will/crystal-pg

SCRAM-SHA-1(-PLUS), SCRAM-SHA-256(-PLUS), SCRAM-SHA-512(-PLUS), SCRAM-SHA3-512(-PLUS):
- Python implementation of the SCRAM protocol (scramp): https://github.com/tlocke/scramp
- Jackal (XMPP server): https://github.com/ortuman/jackal

SCRAM-SHA-1(-PLUS), SCRAM-SHA-256(-PLUS), SCRAM-SHA-512(-PLUS):
- Gajim 1.4.x (XMPP client): https://gajim.org/
- nbxmpp 3.x, Python library (XMPP library): https://dev.gajim.org/gajim/python-nbxmpp/
- DJabberd (XMPP server): https://github.com/djabberd/DJabberd
- ProcessOne ejabberd (XMPP server): https://www.ejabberd.im/
- ProcessOne Erlang/Elixir XMPP (XMPP library): https://github.com/processone/xmpp
- CoyIM (XMPP client): https://github.com/coyim/coyim
- Tigase XMPP Server 8.1.0 (XMPP server): https://docs.tigase.net/tigase-server/8.1.0/Administration_Guide/html/
- Tigase XMPP Server 8.2.x-dev (XMPP server): https://docs.tigase.net/tigase-server/master-snapshot/Administration_Guide/html/
- Tigase JaXMPP (XMPP library): https://github.com/tigase/jaxmpp
- Tigase TTS-NG: https://github.com/tigase/tigase-tts-ng
- Tigase Stork IM / Tigase Android Messenger (XMPP client): https://github.com/tigase/stork
- Isode M-Link (XMPP server): https://www.isode.com/products/m-link.html
- Isode M-Vault: https://www.isode.com/products/m-vault.html
- Isode M-Switch: https://www.isode.com/products/m-switch-x400.html
- Isode M-Box: https://www.isode.com/products/m-box.html
- libscram: https://github.com/pwithnall/libscram
- MimeKit: https://github.com/jstedfast/MailKit + http://www.mimekit.net/docs/html/Introduction.htm
- MailKit: https://github.com/jstedfast/MailKit + http://www.mimekit.net/docs/html/Introduction.htm

SCRAM-SHA-1(-PLUS), SCRAM-SHA-224(-PLUS), SCRAM-SHA-256(-PLUS), SCRAM-SHA-384(-PLUS), SCRAM-SHA-512(-PLUS):
- Erlang Solutions Escalus: https://github.com/esl/escalus
- Erlang Solutions MongooseIM 3.7.0 (XMPP server): https://github.com/esl/MongooseIM
- Miranda NG (XMPP client): https://github.com/miranda-ng/miranda-ng
- Cyrus SASL 2.1.28 + Cyrus IMAP (Mail server): https://www.cyrusimap.org/sasl/sasl/authentication_mechanisms.html
- Postfix with Cyrus SASL: https://postfix.org/
- PostfixAdmin with Postfix and Cyrus SASL: https://github.com/postfixadmin
- Mutt (Mail client) with Cyrus SASL: http://mutt.org/
- NeoMutt (Mail client): https://neomutt.org/

SCRAM-SHA-1(-PLUS), SCRAM-SHA-256(-PLUS), SCRAM-SHA-384(-PLUS), SCRAM-SHA-512(-PLUS):
- Metronome IM (XMPP server): https://metronome.im/ + https://github.com/maranda/metronome
- Wocky XMPP library 2.66 (XMPP library): https://github.com/TelepathyIM/wocky
- WildFly Elytron: https://github.com/wildfly-security/wildfly-elytron (https://github.com/wildfly-security/wildfly-elytron/blob/master/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java)

SCRAM-SHA-1(-PLUS):
- GNU SASL 1.8.1 (Libgsasl): http://www.gnu.org/software/gsasl/ // SCRAM-SHA-256(-PLUS) in GNU SASL 1.10.0
- GNU SASL fork - gsasl clone to fix SCRAM-SHA1 server side: https://github.com/20centaurifux/gsasl
- Prosody IM < 0.12 (XMPP server): https://prosody.im/doc/plain_or_hashed#authenticating // SCRAM-SHA-256(-PLUS) in 0.12.
- Swift IM (XMPP client): https://swift.im/swift.html
- Stroke (XMPP library): https://swift.im/swiften.html
- Ignite Realtime Smack (XMPP library): https://igniterealtime.org/projects/smack/
- pyxmpp2 (XMPP library): https://github.com/Jajcus/pyxmpp2 + https://pypi.org/project/pyxmpp2/
-  XMPP library for .NET Core (XMPP library): https://github.com/ubiety/Ubiety.Xmpp.Core
- Salted Challenge and Response Authentication Mechanism library for .NET Core: https://github.com/ubiety/Ubiety.Scram.Core
- Racket SASL: https://github.com/racket/sasl + https://docs.racket-lang.org/sasl/

Linked to:
- https://github.com/scram-sasl/info/issues/1
Do you know if OpenSSL supports this?
@alex: I have done a ticket here:
- https://github.com/openssl/openssl/issues/18893
The channel binding can be implemented with [SSL_export_keying_material](https://www.openssl.org/docs/man1.1.1/man3/SSL_export_keying_material.html). It is mostly trivial except for two facts:

- You have to use an empty context. TLS 1.2 creates different keying material for empty context and missing context.
- You must ensure that the connection has an extended master secret. Again trivial for TLS 1.3 connections (EMS is always present) but tricky for TLS 1.2. OpenSSL gets in the way. A manual ``SSL_ctrl`` with ``SSL_CTRL_GET_EXTMS_SUPPORT`` seems to work.
`SSL_export_keying_material` is available in pyca/cryptography, which to me indicates that this is a pyOpenSSL bug, not a pyca/cryptography one.
You still need ``SSL_CTRL_GET_EXTMS_SUPPORT`` in order to check for EMS when the connection uses TLS 1.2.

>  This channel binding mechanism is defined only when the TLS handshake
>   results in unique master secrets.  This is true of TLS versions prior
>   to 1.3 when the extended master secret extension of [[RFC7627](https://datatracker.ietf.org/doc/html/rfc7627)] is in
>   use, and it is always true for TLS 1.3 (see [Appendix D of [RFC8446]](https://datatracker.ietf.org/doc/html/rfc8446#appendix-D)).
👍, I think we'd be ok taking a patch for that.

Resolving patch:
diff --git a/src/_cffi_src/openssl/ssl.py b/src/_cffi_src/openssl/ssl.py
--- a/src/_cffi_src/openssl/ssl.py
+++ b/src/_cffi_src/openssl/ssl.py
@@ -44,6 +44,7 @@
 static const long Cryptography_HAS_ALPN;
 static const long Cryptography_HAS_NEXTPROTONEG;
 static const long Cryptography_HAS_SET_CERT_CB;
+static const long Cryptography_HAS_GET_EXTMS_SUPPORT;
 static const long Cryptography_HAS_CUSTOM_EXT;
 static const long Cryptography_HAS_SRTP;
 static const long Cryptography_HAS_DTLS_GET_DATA_MTU;
@@ -186,6 +187,8 @@
 void SSL_set_verify(SSL *, int, int (*)(int, X509_STORE_CTX *));
 int SSL_get_verify_mode(const SSL *);
 
+long SSL_get_extms_support(SSL *);
+
 X509_VERIFY_PARAM *SSL_get0_param(SSL *);
 X509_VERIFY_PARAM *SSL_CTX_get0_param(SSL_CTX *);
 
@@ -605,8 +608,12 @@
 void (*SSL_CTX_set_cert_cb)(SSL_CTX *, int (*)(SSL *, void *), void *) = NULL;
 void (*SSL_set_cert_cb)(SSL *, int (*)(SSL *, void *), void *) = NULL;
 static const long Cryptography_HAS_SET_CERT_CB = 0;
+
+long (*SSL_get_extms_support)(SSL *) = NULL;
+static const long Cryptography_HAS_GET_EXTMS_SUPPORT = 0;
 #else
 static const long Cryptography_HAS_SET_CERT_CB = 1;
+static const long Cryptography_HAS_GET_EXTMS_SUPPORT = 1;
 #endif
 
 static const long Cryptography_HAS_SSL_CTX_CLEAR_OPTIONS = 1;
diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py
--- a/src/cryptography/hazmat/bindings/openssl/_conditional.py
+++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py
@@ -296,6 +296,10 @@ def cryptography_has_ssl_op_ignore_unexpected_eof() -> typing.List[str]:
     ]
 
 
+def cryptography_has_get_extms_support() -> typing.List[str]:
+    return ["SSL_get_extms_support"]
+
+
 # This is a mapping of
 # {condition: function-returning-names-dependent-on-that-condition} so we can
 # loop over them and delete unsupported names at runtime. It will be removed
@@ -352,4 +356,5 @@ def cryptography_has_ssl_op_ignore_unexpected_eof() -> typing.List[str]:
     "Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF": (
         cryptography_has_ssl_op_ignore_unexpected_eof
     ),
+    "Cryptography_HAS_GET_EXTMS_SUPPORT": cryptography_has_get_extms_support,
 }


Test patch: