]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: ssl: implement rsa/ecdsa selection with WolfSSL
authorWilliam Lallemand <wlallemand@haproxy.com>
Thu, 16 Nov 2023 17:16:53 +0000 (18:16 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Fri, 24 Nov 2023 19:07:27 +0000 (20:07 +0100)
PR https://github.com/wolfSSL/wolfssl/pull/6963 implements primitives to
extract ciphers and algorithm signatures.

It allows to chose a certificate depending on the sigals and
ciphers presented by the client (RSA or ECDSA).

Since WolfSSL does not implement the clienthello callback, the patch
uses the certificate callback (SSL_CTX_set_cert_cb())

The callback is inspired by our clienthello callback, however the
extraction of client ciphers and sigalgs is simpler,
wolfSSL_get_sigalg_info() and wolfSSL_get_ciphersuite_info() are used.

This is not enabled by default yet as the PR was not merged.

src/ssl_sock.c

index 8eed4f6c389fd7fcea17d346d03f7e3dbb460e26..082666188462d4f42121feaf093f34f3820eab05 100644 (file)
@@ -2771,6 +2771,203 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
 #endif /* (!) OPENSSL_IS_BORINGSSL */
 #endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */
 
+#if 0 && defined(USE_OPENSSL_WOLFSSL)
+/* This implement the equivalent of the clientHello Callback but using the cert_cb.
+ * WolfSSL is able to extract the sigalgs and ciphers of the client byt using the API
+ * provided in https://github.com/wolfSSL/wolfssl/pull/6963
+ *
+ * Not activated for now since the PR is not merged.
+ */
+static int ssl_sock_switchctx_wolfSSL_cbk(WOLFSSL* ssl, void* arg)
+{
+       struct bind_conf *s = arg;
+       int has_rsa_sig = 0, has_ecdsa_sig = 0;
+
+       char *wildp = NULL;
+       const char *servername;
+       struct ebmb_node *node, *n, *node_ecdsa = NULL, *node_rsa = NULL, *node_anonymous = NULL;
+       int i;
+
+       if (!s) {
+               /* must never happen */
+               ABORT_NOW();
+               return 0;
+       }
+
+       servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+       if (!servername) {
+               /* without SNI extension, is the default_ctx (need SSL_TLSEXT_ERR_NOACK) */
+               if (!s->strict_sni) {
+                       HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
+                       ssl_sock_switchctx_set(ssl, s->default_ctx);
+                       HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
+                       goto allow_early;
+               }
+               goto abort;
+       }
+
+       /* extract sigalgs and ciphers */
+       {
+               const byte* suites = NULL;
+               word16 suiteSz = 0;
+               const byte* hashSigAlgo = NULL;
+               word16 hashSigAlgoSz = 0;
+               word16 idx = 0;
+
+               wolfSSL_get_client_suites_sigalgs(ssl, &suites, &suiteSz, &hashSigAlgo, &hashSigAlgoSz);
+               if (suites == NULL || suiteSz == 0 || hashSigAlgo == NULL || hashSigAlgoSz == 0)
+                       return 0;
+
+               if (SSL_version(ssl) != TLS1_3_VERSION) {
+                       for (idx = 0; idx < suiteSz; idx += 2) {
+                               WOLFSSL_CIPHERSUITE_INFO info;
+                               info = wolfSSL_get_ciphersuite_info(suites[idx], suites[idx+1]);
+                               if (info.rsaAuth)
+                                       has_rsa_sig = 1;
+                               else if (info.eccAuth)
+                                       has_ecdsa_sig = 1;
+                       }
+               }
+
+               if (hashSigAlgoSz > 0) {
+                       /* sigalgs extension takes precedence over ciphersuites */
+                       has_ecdsa_sig = 0;
+                       has_rsa_sig = 0;
+               }
+               for (idx = 0; idx < hashSigAlgoSz; idx += 2) {
+                       enum wc_HashType hashAlgo;
+                       enum Key_Sum sigAlgo;
+
+                       wolfSSL_get_sigalg_info(hashSigAlgo[idx+0], hashSigAlgo[idx+1], &hashAlgo, &sigAlgo);
+
+                       if (sigAlgo == RSAk || sigAlgo == RSAPSSk)
+                               has_rsa_sig = 1;
+                       else if (sigAlgo == ECDSAk)
+                               has_ecdsa_sig = 1;
+               }
+       }
+
+       for (i = 0; i < trash.size; i++) {
+               if (!servername[i])
+                       break;
+               trash.area[i] = tolower((unsigned char)servername[i]);
+               if (!wildp && (trash.area[i] == '.'))
+                       wildp = &trash.area[i];
+       }
+       trash.area[i] = 0;
+
+
+       HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
+
+       /* Look for an ECDSA, RSA and DSA certificate, first in the single
+        * name and if not found in the wildcard  */
+       for (i = 0; i < 2; i++) {
+               if (i == 0)     /* lookup in full qualified names */
+                       node = ebst_lookup(&s->sni_ctx, trash.area);
+               else if (i == 1 && wildp)  /* lookup in wildcards names */
+                       node = ebst_lookup(&s->sni_w_ctx, wildp);
+               else
+                       break;
+
+               for (n = node; n; n = ebmb_next_dup(n)) {
+
+                       /* lookup a not neg filter */
+                       if (!container_of(n, struct sni_ctx, name)->neg) {
+                               struct sni_ctx *sni, *sni_tmp;
+                               int skip = 0;
+
+                               if (i == 1 && wildp) { /* wildcard */
+                                       /* If this is a wildcard, look for an exclusion on the same crt-list line */
+                                       sni = container_of(n, struct sni_ctx, name);
+                                       list_for_each_entry(sni_tmp, &sni->ckch_inst->sni_ctx, by_ckch_inst) {
+                                               if (sni_tmp->neg && (strcmp((const char *)sni_tmp->name.key, trash.area) == 0)) {
+                                                       skip = 1;
+                                                       break;
+                                               }
+                                       }
+                                       if (skip)
+                                               continue;
+                               }
+
+                               switch(container_of(n, struct sni_ctx, name)->kinfo.sig) {
+                               case TLSEXT_signature_ecdsa:
+                                       if (!node_ecdsa)
+                                               node_ecdsa = n;
+                                       break;
+                               case TLSEXT_signature_rsa:
+                                       if (!node_rsa)
+                                               node_rsa = n;
+                                       break;
+                               default: /* TLSEXT_signature_anonymous|dsa */
+                                       if (!node_anonymous)
+                                               node_anonymous = n;
+                                       break;
+                               }
+                       }
+               }
+       }
+       /* Once the certificates are found, select them depending on what is
+        * supported in the client and by key_signature priority order: EDSA >
+        * RSA > DSA */
+       if (has_ecdsa_sig && node_ecdsa)
+               node = node_ecdsa;
+       else if (has_rsa_sig && node_rsa)
+               node = node_rsa;
+       else if (node_anonymous)
+               node = node_anonymous;
+       else if (node_ecdsa)
+               node = node_ecdsa;      /* no ecdsa signature case (< TLSv1.2) */
+       else
+               node = node_rsa;        /* no rsa signature case (far far away) */
+
+       if (node) {
+               /* switch ctx */
+               struct ssl_bind_conf *conf = container_of(node, struct sni_ctx, name)->conf;
+               ssl_sock_switchctx_set(ssl, container_of(node, struct sni_ctx, name)->ctx);
+               if (conf) {
+                       methodVersions[conf->ssl_methods.min].ssl_set_version(ssl, SET_MIN);
+                       methodVersions[conf->ssl_methods.max].ssl_set_version(ssl, SET_MAX);
+               }
+               HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
+               goto allow_early;
+       }
+
+       HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
+       if (!s->strict_sni) {
+               /* no certificate match, is the default_ctx */
+               HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
+               ssl_sock_switchctx_set(ssl, s->default_ctx);
+               HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
+               goto allow_early;
+       }
+
+       /* We are about to raise an handshake error so the servername extension
+        * callback will never be called and the SNI will never be stored in the
+        * SSL context. In order for the ssl_fc_sni sample fetch to still work
+        * in such a case, we store the SNI ourselves as an ex_data information
+        * in the SSL context.
+        */
+       {
+               char *client_sni = pool_alloc(ssl_sock_client_sni_pool);
+               if (client_sni) {
+                       strncpy(client_sni, trash.area, TLSEXT_MAXLEN_host_name);
+                       client_sni[TLSEXT_MAXLEN_host_name] = '\0';
+                       SSL_set_ex_data(ssl, ssl_client_sni_index, client_sni);
+               }
+       }
+
+       /* other cases fallback on abort, if strict-sni is set but no node was found */
+
+ abort:
+       /* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */
+       return 0;
+
+allow_early:
+       return 1;
+}
+#endif
+
+
 #ifndef OPENSSL_NO_DH
 
 static inline HASSL_DH *ssl_new_dh_fromdata(BIGNUM *p, BIGNUM *g)
@@ -4197,7 +4394,10 @@ ssl_sock_initial_ctx(struct bind_conf *bind_conf)
 #  endif /* ! SSL_OP_NO_ANTI_REPLAY */
        SSL_CTX_set_client_hello_cb(ctx, ssl_sock_switchctx_cbk, NULL);
        SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_err_cbk);
-# else /* ! OPENSSL_IS_BORINGSSL && ! HAVE_SSL_CLIENT_HELLO_CB */
+# elif 0 && defined(USE_OPENSSL_WOLFSSL)
+       SSL_CTX_set_cert_cb(ctx, ssl_sock_switchctx_wolfSSL_cbk, bind_conf);
+# else
+       /* ! OPENSSL_IS_BORINGSSL && ! HAVE_SSL_CLIENT_HELLO_CB */
        SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_cbk);
 # endif
        SSL_CTX_set_tlsext_servername_arg(ctx, bind_conf);