]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Work around OpenSSL crash with EC keys lacking public key
authorDavid Woodhouse <David.Woodhouse@intel.com>
Tue, 6 Sep 2016 20:27:20 +0000 (21:27 +0100)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Tue, 6 Sep 2016 20:27:20 +0000 (21:27 +0100)
https://github.com/openssl/openssl/issues/1532

Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
openssl-pkcs11.c
tests/Makefile.am

index 42d26bf0ddbfa207d10a3c54e7b093ff4aee4cb4..9cdce4e6f52e691d9f6820633809c08e48140ec9 100644 (file)
@@ -23,6 +23,7 @@
 #include <ctype.h>
 
 #include "openconnect-internal.h"
+#include <openssl/rand.h>
 
 #ifdef HAVE_LIBP11 /* And p11-kit */
 
@@ -477,6 +478,67 @@ static PKCS11_KEY *slot_find_key(struct openconnect_info *vpninfo, PKCS11_CTX *c
        return NULL;
 }
 
+#ifndef OPENSSL_NO_EC
+static int validate_ecdsa_key(struct openconnect_info *vpninfo, EC_KEY *priv_ec)
+{
+       EVP_PKEY *pub_pkey;
+       EC_KEY *pub_ec;
+       unsigned char rdata[SHA1_SIZE];
+       unsigned int siglen = ECDSA_size(priv_ec);
+       unsigned char *sig;
+       int ret = -EINVAL;
+
+       pub_pkey = X509_get_pubkey(vpninfo->cert_x509);
+       if (!pub_pkey) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Certificate has no public key\n"));
+               goto out;
+       }
+       pub_ec = EVP_PKEY_get1_EC_KEY(pub_pkey);
+       if (!pub_ec) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Certificate does not match private key\n"));
+               goto out_pkey;
+       }
+       vpn_progress(vpninfo, PRG_TRACE, _("Checking EC key matches cert\n"));
+       sig = malloc(siglen);
+       if (!sig) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to allocate signature buffer\n"));
+               ret = -ENOMEM;
+               goto out_pubec;
+       }
+       if (!RAND_bytes(rdata, sizeof(rdata))) {
+               /* Actually, who cares? */
+       }
+       if (!ECDSA_sign(NID_sha1, rdata, sizeof(rdata),
+                       sig, &siglen, priv_ec)) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to sign dummy data to validate EC key\n"));
+               openconnect_report_ssl_errors(vpninfo);
+               goto out_sig;
+       }
+       if (!ECDSA_verify(NID_sha1, rdata, sizeof(rdata), sig, siglen, pub_ec)) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Certificate does not match private key\n"));
+               goto out_sig;
+       }
+
+       /* Finally, copy the public EC_POINT data now that we know it really did match */
+       EC_KEY_set_public_key(priv_ec, EC_KEY_get0_public_key(pub_ec));
+       ret = 0;
+
+ out_sig:
+       free(sig);
+ out_pubec:
+       EC_KEY_free(pub_ec);
+ out_pkey:
+       EVP_PKEY_free(pub_pkey);
+ out:
+       return ret;
+}
+#endif
+
 int load_pkcs11_key(struct openconnect_info *vpninfo)
 {
        PKCS11_CTX *ctx;
@@ -587,6 +649,28 @@ int load_pkcs11_key(struct openconnect_info *vpninfo)
                        goto out;
                }
 
+#ifndef OPENSSL_NO_EC
+               /*
+                * If an EC EVP_PKEY has no public key, OpenSSL will crash
+                * when trying to check it matches the certificate:
+                * https://github.com/openssl/openssl/issues/1532
+                *
+                * Work around this by detecting this condition, manually
+                * checking that the certificate *does* match by performing
+                * a signature and validating it against the cert, then
+                * copying the EC_POINT public key information from the cert.
+                */
+               if (pkey->type == EVP_PKEY_EC) {
+                       EC_KEY *priv_ec = EVP_PKEY_get1_EC_KEY(pkey);
+
+                       ret = 0;
+                       if (!EC_KEY_get0_public_key(priv_ec))
+                               ret = validate_ecdsa_key(vpninfo, priv_ec);
+                       EC_KEY_free(priv_ec);
+                       if (ret)
+                               goto out;
+               }
+#endif
                if (!SSL_CTX_use_PrivateKey(vpninfo->https_ctx, pkey)) {
                        vpn_progress(vpninfo, PRG_ERR, _("Add key from PKCS#11 failed\n"));
                        openconnect_report_ssl_errors(vpninfo);
index ab372ca727ed4e59873b3c86da496b736906398b..57c33e300ed8cfffe6c7ec216efa578cb6352122 100644 (file)
@@ -45,7 +45,7 @@ dist_check_SCRIPTS += auth-username-pass auth-certificate
 if TEST_PKCS11
 dist_check_SCRIPTS += auth-pkcs11
 
-PKCS11_TOKENS = openconnect-test
+PKCS11_TOKENS = openconnect-test openconnect-test1
 
 PKCS11_KEYS = object=RSA id=%01
 # Neither GnuTLS or libp11 support this
@@ -53,11 +53,9 @@ PKCS11_KEYS = object=RSA id=%01
 PKCS11_KEYS += object=EC id=%03
 
 if OPENCONNECT_GNUTLS
-# OpenSSL fails test1 with a SEGV on the EC key:
-# https://github.com/openssl/openssl/issues/1532
 # We fail test2 because PKCS11_enumerate_certs() still doesn't seem to return
 # the certs after we log in. Perhaps it's cached the results?
-PKCS11_TOKENS += openconnect-test1 openconnect-test2
+PKCS11_TOKENS += openconnect-test2
 endif # OPENCONNECT_GNUTLS
 endif # TEST_PKCS11
 endif # HAVE_CWRAP