Repository: pyca/cryptography

Issue:
Add bindings for signature algorithm API
``SSL_CTX_set1_sigalgs_list()`` et al are required to limit the signature algorithm in order to enforce a specific certificate type. For example Facebook uses a ECDSA server cert by default. In order to enforce RSA certs with TLS 1.2 or 1.3, ``SSL_CTX_set1_sigalgs_list(ctx, "RSA+SHA256:RSA+SHA384)`` can be used to retrieve cert with RSA PKCS#1 v1.5 signature. For TLS 1.2 it is also possible to limit the supported cipher suites to ``aRSA``. This trick will no longer work with TLS 1.3, though.

Background: https://bugs.python.org/issue31892

https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set1_sigalgs.html

https://www.openssl.org/docs/manmaster/man3/SSL_get_shared_sigalgs.html


Discussion:
Makes sense. What version were these added in?
OpenSSL: 1.0.2 final
LibreSSL: n/a

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
@@ -27,6 +27,7 @@
 static const long Cryptography_HAS_SSL_CTX_CLEAR_OPTIONS;
 static const long Cryptography_HAS_DTLS;
 static const long Cryptography_HAS_GENERIC_DTLS_METHOD;
+static const long Cryptography_HAS_SIGALGS;
 
 /* Internally invented symbol to tell us if SNI is supported */
 static const long Cryptography_HAS_TLSEXT_HOSTNAME;
@@ -186,6 +187,9 @@
 int SSL_use_PrivateKey_file(SSL *, const char *, int);
 int SSL_check_private_key(const SSL *);
 
+int SSL_get_sigalgs(SSL *, int, int *, int *, int *, unsigned char *,
+                    unsigned char *);
+
 Cryptography_STACK_OF_X509 *SSL_get_peer_cert_chain(const SSL *);
 Cryptography_STACK_OF_X509_NAME *SSL_get_client_CA_list(const SSL *);
 
@@ -233,6 +237,8 @@
 void SSL_CTX_set_info_callback(SSL_CTX *, void (*)(const SSL *, int, int));
 void (*SSL_CTX_get_info_callback(SSL_CTX *))(const SSL *, int, int);
 
+long SSL_CTX_set1_sigalgs_list(SSL_CTX *, const char *);
+
 /*  SSL_SESSION */
 void SSL_SESSION_free(SSL_SESSION *);
 
@@ -623,4 +629,13 @@
 
     return r;
 }
+
+#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_102
+static const long Cryptography_HAS_SIGALGS = 0;
+const int (*SSL_get_sigalgs)(SSL *, int, int *, int *, int *, unsigned char *,
+                             unsigned char *) = NULL;
+const long (*SSL_CTX_set1_sigalgs_list)(SSL_CTX *, const char *) = NULL;
+#else
+static const long Cryptography_HAS_SIGALGS = 1;
+#endif
 """
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
@@ -250,6 +250,13 @@ def cryptography_has_fips():
     ]
 
 
+def cryptography_has_ssl_sigalgs():
+    return [
+        "SSL_CTX_set1_sigalgs_list",
+        "SSL_get_sigalgs",
+    ]
+
+
 # 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
@@ -300,4 +307,5 @@ def cryptography_has_fips():
         cryptography_has_evp_pkey_get_set_tls_encodedpoint
     ),
     "Cryptography_HAS_FIPS": cryptography_has_fips,
+    "Cryptography_HAS_SIGALGS": cryptography_has_ssl_sigalgs,
 }


Test patch: