]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Merge branch 'multicert' of gitlab.com:openconnect/openconnect
authorDavid Woodhouse <dwmw2@infradead.org>
Tue, 12 Apr 2022 09:05:46 +0000 (11:05 +0200)
committerDavid Woodhouse <dwmw2@infradead.org>
Tue, 12 Apr 2022 09:05:46 +0000 (11:05 +0200)
12 files changed:
1  2 
Makefile.am
auth.c
gnutls.c
libopenconnect.map.in
library.c
main.c
openconnect-internal.h
openconnect.8.in
openconnect.h
openssl.c
tests/Makefile.am
www/changelog.xml

diff --cc Makefile.am
index c4c10ab4d133fe7bde2681395705e93afe547936,156f117a77736535a9dee2c96f386cf1dfed426d..e2ff4cd00d970873196a58031ce03c7fb0643f99
@@@ -34,8 -35,10 +34,10 @@@ openconnect_LDADD = libopenconnect.la $
  if OPENCONNECT_WIN32
  openconnect_SOURCES += openconnect.rc
  endif
- library_srcs = ssl.c http.c textbuf.c http-auth.c auth-common.c auth-html.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c mtucalc.c openconnect-internal.h
- lib_srcs_cisco = auth.c cstp.c hpke.c
+ library_srcs = ssl.c http.c textbuf.c http-auth.c auth-common.c \
+       auth-html.c library.c compat.c lzs.c mainloop.c script.c \
+       ntlm.c digest.c mtucalc.c openconnect-internal.h
 -lib_srcs_cisco = auth.c cstp.c multicert.c
++lib_srcs_cisco = auth.c cstp.c hpke.c multicert.c
  lib_srcs_juniper = oncp.c lzo.c auth-juniper.c
  lib_srcs_pulse = pulse.c
  lib_srcs_globalprotect = gpst.c win32-ipicmp.h auth-globalprotect.c
diff --cc auth.c
Simple merge
diff --cc gnutls.c
index c637262af76b0084d54d896959c0e4e53af7ed4a,720fe6c3bc259faa6c134bf2a6813f76d92b14e5..09fbc78ab64f5163dbd104f8935d6a145ae04bdf
+++ b/gnutls.c
@@@ -2798,252 -2825,349 +2825,599 @@@ void destroy_eap_ttls(struct openconnec
        gnutls_deinit(sess);
  }
  
 -                           _("Certificate maybe multiple certificate authentication incompatible.\n"));
 +static int generate_strap_key(gnutls_privkey_t *key, char **pubkey)
 +{
 +      int bits, pk, err;
 +      gnutls_pubkey_t pkey = NULL;
 +      gnutls_datum_t pdata = { };
 +      struct oc_text_buf *buf = NULL;
 +
 +#if GNUTLS_VERSION_NUMBER >= 0x030500
 +      pk = gnutls_ecc_curve_get_pk(GNUTLS_ECC_CURVE_SECP256R1);
 +#else
 +      pk = GNUTLS_PK_EC;
 +#endif
 +      bits = GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1);
 +
 +      err = gnutls_privkey_init(key);
 +      if (err)
 +              goto out;
 +
 +      err = gnutls_privkey_generate(*key, pk, bits, 0);
 +      if (err)
 +              goto out;
 +
 +      err = gnutls_pubkey_init(&pkey);
 +      if (err)
 +              goto out;
 +
 +      err = gnutls_pubkey_import_privkey(pkey, *key,
 +                                         GNUTLS_KEY_KEY_AGREEMENT, 0);
 +      if (err)
 +              goto out;
 +
 +      err = gnutls_pubkey_export2(pkey, GNUTLS_X509_FMT_DER, &pdata);
 +      if (err)
 +              goto out;
 +
 +      buf = buf_alloc();
 +      buf_append_base64(buf, pdata.data, pdata.size, 0);
 +      if (buf_error(buf)) {
 +              err = GNUTLS_E_MEMORY_ERROR;
 +              goto out;
 +      }
 +
 +      *pubkey = buf->data;
 +      buf->data = NULL;
 + out:
 +      buf_free(buf);
 +      gnutls_free(pdata.data);
 +      gnutls_pubkey_deinit(pkey);
 +      if (err) {
 +              gnutls_privkey_deinit(*key);
 +              *key = NULL;
 +              *pubkey = NULL;
 +      }
 +      return err;
 +}
 +
 +int generate_strap_keys(struct openconnect_info *vpninfo)
 +{
 +      int err;
 +
 +      err = generate_strap_key(&vpninfo->strap_key, &vpninfo->strap_pubkey);
 +      if (err) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("Failed to generate STRAP key: %s\n"),
 +                           gnutls_strerror(err));
 +              free_strap_keys(vpninfo);
 +              return -EIO;
 +      }
 +
 +      err = generate_strap_key(&vpninfo->strap_dh_key, &vpninfo->strap_dh_pubkey);
 +      if (err) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("Failed to generate STRAP DH key: %s\n"),
 +                           gnutls_strerror(err));
 +              free_strap_keys(vpninfo);
 +              return -EIO;
 +      }
 +      return 0;
 +}
 +
 +void free_strap_keys(struct openconnect_info *vpninfo)
 +{
 +      if (vpninfo->strap_key)
 +              gnutls_privkey_deinit(vpninfo->strap_key);
 +      if (vpninfo->strap_dh_key)
 +              gnutls_privkey_deinit(vpninfo->strap_dh_key);
 +
 +      vpninfo->strap_key = vpninfo->strap_dh_key = NULL;
 +}
 +
 +#ifdef HAVE_HPKE_SUPPORT
 +
 +#include <nettle/ecc.h>
 +#include <nettle/ecc-curve.h>
 +
 +int ecdh_compute_secp256r1(struct openconnect_info *vpninfo, const unsigned char *pubkey_der,
 +                         int pubkey_len, unsigned char *secret)
 +{
 +      int err, ret = -EIO;
 +      gnutls_pubkey_t pubkey;
 +      gnutls_datum_t d = { (void *)pubkey_der, pubkey_len };
 +
 +      if ((err = gnutls_pubkey_init(&pubkey)) ||
 +          (err = gnutls_pubkey_import(pubkey, &d, GNUTLS_X509_FMT_DER))) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("Failed to decode server DH key: %s\n"),
 +                           gnutls_strerror(err));
 +              goto out_pubkey;
 +      }
 +
 +      /* Yay, we have to do ECDH for ourselves. */
 +      gnutls_datum_t pub_x, pub_y, priv_k;
 +      gnutls_ecc_curve_t pub_curve, priv_curve;
 +
 +      if ((err = gnutls_privkey_export_ecc_raw(vpninfo->strap_dh_key, &priv_curve,
 +                                               NULL, NULL, &priv_k))) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("Failed to export DH private key parameters: %s\n"),
 +                           gnutls_strerror(err));
 +              goto out_pubkey;
 +      }
 +      if ((err = gnutls_pubkey_export_ecc_raw(pubkey, &pub_curve, &pub_x, &pub_y))) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("Failed to export server DH key parameters: %s\n"),
 +                           gnutls_strerror(err));
 +              goto out_priv_data;
 +      }
 +
 +      if (pub_curve != GNUTLS_ECC_CURVE_SECP256R1 ||
 +          priv_curve != GNUTLS_ECC_CURVE_SECP256R1) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("HPKE uses unsupported EC curve (%d, %d)\n"),
 +                           pub_curve, priv_curve);
 +              goto out_pub_data;
 +      }
 +
 +      mpz_t mx, my;
 +      nettle_mpz_init_set_str_256_u(mx, pub_x.size, pub_x.data);
 +      nettle_mpz_init_set_str_256_u(my, pub_y.size, pub_y.data);
 +
 +      struct ecc_point point;
 +      ecc_point_init(&point, nettle_get_secp_256r1());
 +      if (!ecc_point_set(&point, mx, my)) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("Failed to create ECC public point for ECDH\n"));
 +              goto out_point;
 +      }
 +
 +      mpz_t mk;
 +      nettle_mpz_init_set_str_256_u(mk, priv_k.size, priv_k.data);
 +
 +      struct ecc_scalar priv;
 +      ecc_scalar_init(&priv, nettle_get_secp_256r1());
 +      ecc_scalar_set(&priv, mk);
 +
 +      ecc_point_mul(&point, &priv, &point);
 +      ecc_point_get(&point, mx, my);
 +
 +      nettle_mpz_get_str_256(32, secret, mx);
 +
 +      ret = 0;
 +
 +      ecc_scalar_clear(&priv);
 +      mpz_clear(mk);
 + out_point:
 +      ecc_point_clear(&point);
 +      mpz_clear(mx);
 +      mpz_clear(my);
 + out_pub_data:
 +      gnutls_free(pub_x.data);
 +      gnutls_free(pub_y.data);
 + out_priv_data:
 +      gnutls_free(priv_k.data);
 + out_pubkey:
 +      gnutls_pubkey_deinit(pubkey);
 +
 +      return ret;
 +}
 +
 +int hkdf_sha256_extract_expand(struct openconnect_info *vpninfo, unsigned char *buf,
 +                             const char *info, int infolen)
 +{
 +      gnutls_datum_t d;
 +      d.data = buf;
 +      d.size = SHA256_SIZE;
 +
 +      int err = gnutls_hkdf_extract(GNUTLS_MAC_SHA256, &d, NULL, buf);
 +      if (err) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("HKDF extract failed: %s\n"),
 +                           gnutls_strerror(err));
 +              return -EIO;
 +      }
 +
 +      gnutls_datum_t info_d;
 +      info_d.data = (void *)info;
 +      info_d.size = infolen;
 +
 +      err = gnutls_hkdf_expand(GNUTLS_MAC_SHA256, &d, &info_d, d.data, d.size);
 +      if (err) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("HKDF expand failed: %s\n"),
 +                           gnutls_strerror(err));
 +              return -EIO;
 +      }
 +      return 0;
 +}
 +
 +int aes_256_gcm_decrypt(struct openconnect_info *vpninfo, unsigned char *key,
 +                      unsigned char *data, int len,
 +                      unsigned char *iv, unsigned char *tag)
 + {
 +      gnutls_cipher_hd_t h = NULL;
 +
 +      gnutls_datum_t d = { key, SHA256_SIZE };
 +      gnutls_datum_t iv_d = { iv, 12 };
 +
 +      int err = gnutls_cipher_init(&h, GNUTLS_CIPHER_AES_256_GCM, &d, &iv_d);
 +      if (err) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("Failed to init AES-256-GCM cipher: %s\n"),
 +                           gnutls_strerror(err));
 +              return -EIO;
 +      }
 +
 +      err = gnutls_cipher_decrypt(h, data, len);
 +      if (err) {
 +      dec_fail:
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("SSO token decryption failed: %s\n"),
 +                           gnutls_strerror(err));
 +              gnutls_cipher_deinit(h);
 +              return -EIO;
 +      }
 +
 +      /* Reusing the key buffer to fetch the auth tag */
 +      err = gnutls_cipher_tag(h, d.data, 12);
 +      if (err)
 +              goto dec_fail;
 +
 +      if (memcmp(d.data, tag, 12)) {
 +              err = GNUTLS_E_MAC_VERIFY_FAILED;
 +              goto dec_fail;
 +      }
 +
 +      gnutls_cipher_deinit(h);
 +      return 0;
 +}
 +#endif /* HAVE_HPKE_SUPPORT */
++
+ /**
+   * multiple-client certificate authentication
+   */
+ static int app_error(int err)
+ {
+       if (err >= 0)
+               return 0;
+       switch (err)
+       {
+       case GNUTLS_E_MEMORY_ERROR:
+                 return -ENOMEM;
+       case GNUTLS_E_ILLEGAL_PARAMETER:
+       case GNUTLS_E_INVALID_REQUEST:
+                 return -EINVAL;
+       case GNUTLS_E_CONSTRAINT_ERROR:
+       case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM:
+       default:
+                 return -EIO;
+       }
+ }
+ static int to_text_buf(struct oc_text_buf **bufp,
+                      const gnutls_datum_t *datum)
+ {
+       struct oc_text_buf *buf;
+       *bufp = NULL;
+       if (!(datum->size <= INT_MAX))
+               return GNUTLS_E_MEMORY_ERROR;
+       buf = buf_alloc();
+       if (!buf)
+               return GNUTLS_E_MEMORY_ERROR;
+       buf_append_bytes(buf, datum->data, (int) datum->size);
+       if (buf_error(buf) < 0)
+               goto fail;
+       *bufp = buf;
+       return 0;
+ fail:
+       buf_free(buf);
+       return GNUTLS_E_MEMORY_ERROR;
+ }
+ static int check_multicert_compat(struct openconnect_info *vpninfo,
+                                 struct cert_info *certinfo)
+ {
+ #ifndef GNUTLS_KP_ANY
+ #  define GNUTLS_KP_ANY                       "2.5.29.37.0"
+ #endif
+ #ifndef GNUTLS_KP_TLS_WWW_CLIENT
+ #  define GNUTLS_KP_TLS_WWW_CLIENT      "1.3.6.1.5.5.7.3.2"
+ #endif
+ #ifndef GNUTLS_KP_MS_SMART_CARD_LOGON
+ #  define GNUTLS_KP_MS_SMART_CARD_LOGON "1.3.6.1.4.1.311.20.2.2"
+ #endif
+ #define MAX_OID 128
+       char oid[MAX_OID];
+       struct gtls_cert_info *gci = certinfo->priv_info;
+       gnutls_x509_crt_t crt;
+       unsigned int usage = 0, critical;
+       gnutls_pk_algorithm_t pk;
+       size_t kp;
+       int err;
+       /**
+        * Multiple certificate authentication protocol parameterizes the
+        * digest independently of the pk algorithm. Warn if the signature
+        * algorithm doesn't operate this way. Warn if this isn't so.
+        */
+       crt = gci->certs[0];
+       pk = gnutls_x509_crt_get_pk_algorithm(crt, NULL);
+       switch (pk) {
+       default:
+               vpn_progress(vpninfo, PRG_INFO,
++                           _("Certificate may be multiple certificate authentication incompatible.\n"));
+               break;
+       case GNUTLS_PK_RSA:
+ #if GNUTLS_VERSION_NUMBER >= 0x030600
+       case GNUTLS_PK_RSA_PSS:
+ #endif
+       case GNUTLS_PK_DSA:
+ #if GNUTLS_VERSION_NUMBER >= 0x030500
+       case GNUTLS_PK_ECDSA:
+ #else
+       case GNUTLS_PK_EC:
+ #endif
+               break;
+       }
+       /**
+        * Now check if the certificate supports client authentication.
+        *
+        * extendedKeyUsage of either any, clientAuth, or msSmartcardLogin
+        * satisfy authentication purposes.
+        */
+       for (kp = 0; ; kp++) {
+               size_t oid_size = sizeof oid;
+               err = gnutls_x509_crt_get_key_purpose_oid(crt, kp,
+                                                         oid, &oid_size,
+                                                         &critical);
+               if (err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+                       /* EOF */
+                       break;
+               } else if (err == GNUTLS_E_SHORT_MEMORY_BUFFER) {
+                       /**
+                        * The oids we are concerned with have length less than
+                        * MAX_OID
+                        */
+                       continue;
+               } else if (err < 0) {
+                       vpn_progress(vpninfo, PRG_DEBUG,
+                                    _("gnutls_x509_crt_get_key_purpose_oid: %s.\n"),
+                                    gnutls_strerror(err));
+                       return -1;
+               }
+               if (strcmp(oid, GNUTLS_KP_ANY) == 0 ||
+                   strcmp(oid, GNUTLS_KP_TLS_WWW_CLIENT) == 0 ||
+                   strcmp(oid, GNUTLS_KP_MS_SMART_CARD_LOGON) == 0)
+                       return 1;
+       }
+       /**
+        * The certificate does not specify extendedKeyUsage; try
+        * keyUsage.
+        */
+       if (kp == 0) {
+               /**
+                * keyUsage of digitalSignature, nonRepudiation, or
+                * both satisfy authenticatio.n
+                */
+               err = gnutls_x509_crt_get_key_usage(crt, &usage, &critical);
+               if (err < 0 && err != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+                       vpn_progress(vpninfo, PRG_DEBUG,
+                                    _("gnutls_X509_crt_get_key_usage: %s.\n"),
+                                    gnutls_strerror(err));
+               }
+               if (err < 0)
+                       usage = 0;
+               if (usage&
+                   (GNUTLS_KEY_DIGITAL_SIGNATURE|GNUTLS_KEY_NON_REPUDIATION))
+                       return 1;
+       }
+       /**
+        * extendedKeyUsage, keyUsage, or both are specified, but
+        * purposes are incompatible for authentication.
+        */
+       if (kp > 0 || usage != 0) {
+               vpn_progress(vpninfo, PRG_INFO,
+                            _("The certificate specifies key usages "
+                              "incompatible with authentication.\n"));
+               return 0;
+       }
+       /**
+        * Found neither keyUsage nor extendedKeyUsage, defaults to any
+        * purpose.
+        */
+       vpn_progress(vpninfo, PRG_INFO,
+                    _("Certificate doesn't specify key usage.\n"));
+       return 1;
+ }
+ int export_certificate_pkcs7(struct openconnect_info *vpninfo,
+                            struct cert_info *certinfo,
+                            cert_format_t format,
+                            struct oc_text_buf **pp7b)
+ {
+       struct gtls_cert_info *gci = certinfo->priv_info;
+       gnutls_pkcs7_t pkcs7;
+       gnutls_datum_t data;
+       gnutls_x509_crt_fmt_t certform;
+       int err;
+       if (!gci) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Precondition failed %s[%s]:%d\n"),
+                            __FILE__, __func__, __LINE__);
+               return -EINVAL;
+       }
+       *pp7b = NULL;
+       /**
+        * Note! The PKCS7 structure produced by GnuTLS and this code is
+        * different from protocol captures in three ways:
+        *
+        * x: The root certificate is not added.
+        *
+        * x: The first object of the signedData is a OBJECT IDENTIFIER
+        * digestedData (1 2 840 113549 1 7 5), instead of OBJECT IDENTIFIER
+        * data (1 2 840 113549 1 7 1) and zero-length * OCTET STRING.
+        *
+        * x: Certificates are ordered in ASN.1 canonical order instead of the
+        * order added. The practical consequence is the server must identity
+        * the user certificate (a certificate for which basicConstraints
+        * CA:TRUE is false) and reconstruct the certificate path.
+        *
+        * Testing has not shown that these differences are meaningful, but the
+        * future will tell.
+        */
+       err = gnutls_pkcs7_init(&pkcs7);
+       if (err < 0)
+               goto error;
+       for (size_t i = 0, ncerts = gci->nr_certs; i < ncerts; i++) {
+               err = gnutls_pkcs7_set_crt(pkcs7, gci->certs[i]);
+               if (err < 0)
+                       goto error;
+       }
+       if (format == CERT_FORMAT_ASN1) {
+               certform = GNUTLS_X509_FMT_DER;
+       } else if (format == CERT_FORMAT_PEM) {
+               certform = GNUTLS_X509_FMT_PEM;
+       } else {
+               err = GNUTLS_E_INVALID_REQUEST;
+               goto error;
+       }
+       err = gnutls_pkcs7_export2(pkcs7, certform, &data);
+       if (err < 0)
+               goto error;
+       err = to_text_buf(pp7b, &data);
+       gnutls_free(data.data);
+       if (err < 0) {
+ error:
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to generate the PKCS#7 structure: %s.\n"),
+                            gnutls_strerror(err));
+       }
+       gnutls_pkcs7_deinit(pkcs7);
+       return app_error(err);
+ }
+ int multicert_sign_data(struct openconnect_info *vpninfo,
+                       struct cert_info *certinfo,
+                       unsigned int hashes,
+                       const void *data, size_t len,
+                       struct oc_text_buf **psig)
+ {
+       static const struct {
+               openconnect_hash_type id;
+               gnutls_digest_algorithm_t hash;
+       } hash_map[] = {
+               { OPENCONNECT_HASH_SHA512, GNUTLS_DIG_SHA512 },
+               { OPENCONNECT_HASH_SHA384, GNUTLS_DIG_SHA384 },
+               { OPENCONNECT_HASH_SHA256, GNUTLS_DIG_SHA256 },
+               { OPENCONNECT_HASH_UNKNOWN, GNUTLS_DIG_UNKNOWN },
+       };
+       gnutls_datum_t datum = { (void *) data, len };
+       gnutls_datum_t sign_data = { 0 };
+       struct gtls_cert_info *gci = certinfo->priv_info;
+       struct oc_text_buf *sig_buf = NULL;
+       openconnect_hash_type hash;
+       gnutls_pk_algorithm_t pk;
+       gnutls_sign_algorithm_t sign;
+       int ret, err;
+       if (!(gci && data && len && psig)) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Precondition failed %s[%s]:%d.\n"),
+                            __FILE__, __func__, __LINE__);
+               return -EINVAL;
+       }
+       err = gnutls_x509_crt_get_pk_algorithm(gci->certs[0], NULL);
+       if (err < 0)
+               goto error;
+       pk = err;
+       /**
+        * Sign data using hashes with decreasing hash size as
+        * Anyconnect prefers SHA2-512.
+        */
+       for (size_t i = 0;
+            (hash = hash_map[i].id) != OPENCONNECT_HASH_UNKNOWN;
+            i++) {
+               if ((hashes & MULTICERT_HASH_FLAG(hash)) == 0)
+                       continue;
+               sign = gnutls_pk_to_sign(pk, hash_map[i].hash);
+ #if GNUTLS_VERSION_NUMBER >= 0x030600
+               err = gnutls_privkey_sign_data2(gci->pkey,
+                                               sign,
+                                       /* flag */ 0, &datum, &sign_data);
+ #else
+               err = gnutls_privkey_sign_data(gci->pkey,
+                                      gnutls_sign_get_hash_algorithm(sign),
+                                      /* flag */ 0, &datum, &sign_data);
+ #endif
+               if (err < 0) {
+                       vpn_progress(vpninfo, PRG_DEBUG,
+                                    _("gnutls_privkey_sign_data: %s.\n"),
+                                    gnutls_strerror(err));
+                       if (err == GNUTLS_E_INVALID_REQUEST || err == GNUTLS_E_CONSTRAINT_ERROR)
+                               continue;
+                       goto error;
+               }
+               err = to_text_buf(&sig_buf, &sign_data);
+               gnutls_free(sign_data.data);
+               break;
+       }
+       /**
+        * Since we tested for compatibility when we loaded the certificate,
+        * this condition is unlikely to happen.
+        */
+       if (hash == OPENCONNECT_HASH_UNKNOWN)
+               err = GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM;
+       if (err < 0) {
+ error:
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to sign data with second certificate: %s.\n"),
+                            gnutls_strerror(err));
+               ret = app_error(err);
+               goto done;
+       }
+       *psig = sig_buf;
+       ret = hash;
+ done:
+       return ret;
+ }
index 757341beb12f71ecd25813f42547a085269218c8,bd3151d78b5163b25a9d66af7510ca90a36e50cc..227bf8b78059f24091c42aa8b2a9bdade3673aa3
@@@ -115,14 -115,9 +115,16 @@@ OPENCONNECT_5_7 
        openconnect_get_auth_expiration;
        openconnect_disable_dtls;
        openconnect_get_connect_url;
 -} OPENCONNECT_5_6;
 +      openconnect_set_webview_callback;
 +      openconnect_webview_load_changed;
 +} OPENCONNECT_5_6;
 +
 +OPENCONNECT_5_8 {
 + global:
 +      openconnect_set_external_browser_callback;
+       openconnect_set_mca_cert;
+       openconnect_set_mca_key_password;
 +} OPENCONNECT_5_7;
  
  OPENCONNECT_PRIVATE {
   global: @SYMVER_TIME@ @SYMVER_GETLINE@ @SYMVER_JAVA@ @SYMVER_ASPRINTF@ @SYMVER_VASPRINTF@ @SYMVER_WIN32_STRERROR@ @SYMVER_WIN32_SETENV@
diff --cc library.c
Simple merge
diff --cc main.c
Simple merge
Simple merge
Simple merge
diff --cc openconnect.h
index ae11b6f4ba7825ce3c0c8c1ae5ce111041e41f28,5d1af88eb2917536a548f17cd1633d58c6410d8b..4fda33e0b80b2a2dbf37d8652a3501f6827bb050
@@@ -36,10 -36,7 +36,11 @@@ extern "C" 
  #define OPENCONNECT_API_VERSION_MINOR 7
  
  /*
 - * API version 5.7:
 + * API version 5.8:
 + *  - Add openconnect_set_external_browser_callback()
++ *  - Add openconnect_set_mca_cert() and openconnect_set_mca_key_password()
 + *
 + * API version 5.7 (v8.20; 2022-02-20):
   *  - Add openconnect_get_connect_url()
   *  - Add openconnect_set_cookie()
   *  - Add openconnect_set_allow_insecure_crypto()
diff --cc openssl.c
index e8f57c97f4eadb9c6ada5d2631b547e1c649db28,0d17c21639e4ebd956fdbc3222defef1d58ee48a..456f12ed776e813294d9f1f931fd380383712811
+++ b/openssl.c
@@@ -2248,152 -2275,172 +2281,323 @@@ void destroy_eap_ttls(struct openconnec
         * have to call BIO_get_new_index() more times than is necessary */
  }
  
 -              
 +static int generate_strap_key(EC_KEY **key, EC_GROUP *grp, char **pubkey)
 +{
 +      struct oc_text_buf *buf = NULL;
 +      unsigned char *der = NULL;
 +      int len;
 +
 +      *key = EC_KEY_new();
 +      if (!*key)
 +              return -EIO;
 +
 +      if (!EC_KEY_set_group(*key, grp))
 +              return -EIO;
 +
 +      if (!EC_KEY_generate_key(*key))
 +              return -EIO;
 +
 +      len = i2d_EC_PUBKEY(*key, &der);
 +      buf = buf_alloc();
 +      buf_append_base64(buf, der, len, 0);
 +      free(der);
 +      if (buf_error(buf))
 +              return buf_free(buf);
 +
 +      *pubkey = buf->data;
 +      buf->data = NULL;
 +      buf_free(buf);
 +      return 0;
 +}
 +
 +int generate_strap_keys(struct openconnect_info *vpninfo)
 +{
 +      int err;
 +      EC_GROUP *grp = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
 +
 +      if (!grp) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("Failed to create prime256v1 EC group\n"));
 +              return -EIO;
 +      }
 +
 +      err = generate_strap_key(&vpninfo->strap_key, grp, &vpninfo->strap_pubkey);
 +      if (err) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("Failed to generate STRAP key"));
 +              openconnect_report_ssl_errors(vpninfo);
 +              free_strap_keys(vpninfo);
 +              EC_GROUP_free(grp);
 +              return -EIO;
 +      }
 +
 +      err = generate_strap_key(&vpninfo->strap_dh_key, grp, &vpninfo->strap_dh_pubkey);
 +      if (err) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("Failed to generate STRAP DH key\n"));
 +              openconnect_report_ssl_errors(vpninfo);
 +              free_strap_keys(vpninfo);
 +              EC_GROUP_free(grp);
 +              return -EIO;
 +      }
 +      EC_GROUP_free(grp);
 +      return 0;
 +}
 +
 +void free_strap_keys(struct openconnect_info *vpninfo)
 +{
 +      if (vpninfo->strap_key)
 +              EC_KEY_free(vpninfo->strap_key);
 +      if (vpninfo->strap_dh_key)
 +              EC_KEY_free(vpninfo->strap_dh_key);
 +
 +      vpninfo->strap_key = vpninfo->strap_dh_key = NULL;
 +}
 +
 +#ifdef HAVE_HPKE_SUPPORT
 +
 +#include <openssl/kdf.h>
 +
 +int ecdh_compute_secp256r1(struct openconnect_info *vpninfo, const unsigned char *pubkey,
 +                         int pubkey_len, unsigned char *secret)
 +{
 +      const EC_POINT *point;
 +      EC_KEY *pkey;
 +      int ret = 0;
 +
 +      if (!(pkey = d2i_EC_PUBKEY(NULL, &pubkey, pubkey_len)) ||
 +          !(point = EC_KEY_get0_public_key(pkey))) {
 +              vpn_progress(vpninfo, PRG_ERR,
 +                           _("Failed to decode server DH key\n"));
 +              openconnect_report_ssl_errors(vpninfo);
 +              ret = -EIO;
 +              goto out;
 +
 +      }
 +
 +      /* Perform the DH secret derivation from our STRAP-DH key
 +       * and the one the server returned to us in the payload. */
 +      if (ECDH_compute_key(secret, 32, point, vpninfo->strap_dh_key, NULL) <= 0) {
 +              vpn_progress(vpninfo, PRG_ERR, _("Failed to compute DH secret\n"));
 +              openconnect_report_ssl_errors(vpninfo);
 +              ret = -EIO;
 +      }
 + out:
 +      EC_KEY_free(pkey);
 +      return ret;
 +}
 +
 +int hkdf_sha256_extract_expand(struct openconnect_info *vpninfo, unsigned char *buf,
 +                             const char *info, int infolen)
 +{
 +      size_t buflen = 32;
 +      int ret = 0;
 +
 +      /* Next, use HKDF to generate the actual key used for encryption. */
 +      EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
 +      if (!ctx || !EVP_PKEY_derive_init(ctx) ||
 +          !EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256()) ||
 +          !EVP_PKEY_CTX_set1_hkdf_key(ctx, buf, buflen) ||
 +          !EVP_PKEY_CTX_hkdf_mode(ctx, EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) ||
 +          !EVP_PKEY_CTX_add1_hkdf_info(ctx, info, infolen) ||
 +          EVP_PKEY_derive(ctx, buf, &buflen) != 1) {
 +              vpn_progress(vpninfo, PRG_ERR, _("HKDF key derivation failed\n"));
 +              openconnect_report_ssl_errors(vpninfo);
 +              ret = -EINVAL;
 +      }
 +      EVP_PKEY_CTX_free(ctx);
 +      return ret;
 +}
 +
 +int aes_256_gcm_decrypt(struct openconnect_info *vpninfo, unsigned char *key,
 +                      unsigned char *data, int len,
 +                      unsigned char *iv, unsigned char *tag)
 +{
 +      /* Finally, we actually decrypt the sso-token */
 +      EVP_CIPHER_CTX *cctx = EVP_CIPHER_CTX_new();
 +      int ret = 0, i = 0;
 +
 +      if (!cctx ||
 +          !EVP_DecryptInit_ex(cctx, EVP_aes_256_gcm(), NULL, key, iv) ||
 +          !EVP_CIPHER_CTX_ctrl(cctx, EVP_CTRL_AEAD_SET_TAG, 12, tag) ||
 +          !EVP_DecryptUpdate(cctx, data, &len, data, len) ||
 +          !EVP_DecryptFinal(cctx, NULL, &i)) {
 +              vpn_progress(vpninfo, PRG_ERR, _("SSO token decryption failed\n"));
 +              openconnect_report_ssl_errors(vpninfo);
 +              ret = -EINVAL;
 +      }
 +      EVP_CIPHER_CTX_free(cctx);
 +      return ret;
 +}
 +#endif /* HAVE_HPKE_SUPPORT */
++
+ int export_certificate_pkcs7(struct openconnect_info *vpninfo,
+                            struct cert_info *certinfo,
+                            cert_format_t format,
+                            struct oc_text_buf **pp7b)
+ {
+       struct ossl_cert_info *oci;
+       PKCS7 *p7 = NULL;
+       BIO *bio = NULL;
+       BUF_MEM *bptr = NULL;
+       struct oc_text_buf *p7b = NULL;
+       int ret, ok;
+       if (!(certinfo && (oci = certinfo->priv_info) && pp7b))
+               return -EINVAL;
+       /* We have the client certificate in 'oci.cert' and *optionally*
+        * a stack of intermediate certs in oci.extra_certs. For the TLS
+        * connection those would be used by SSL_CTX_use_certificate() and
+        * SSL_CTX_add_extra_chain_cert() respectively. For PKCS7_sign()
+        * we need the actual cert at the head of the stack, so *create*
+        * one if needed, and insert oci.cert at position zero. */
+       if (!oci->extra_certs)
+               oci->extra_certs = sk_X509_new_null();
+       if (!oci->extra_certs)
+               goto err;
+       if (!sk_X509_insert(oci->extra_certs, oci->cert, 0))
+               goto err;
+       X509_up_ref(oci->cert);
+       p7 = PKCS7_sign(NULL, NULL, oci->extra_certs, NULL, PKCS7_DETACHED);
+       if (!p7) {
+       err:
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to create PKCS#7 structure\n"));
+               ret = -EIO;
+               goto out;
+       }
+       ret = 0;
+       bio = BIO_new(BIO_s_mem());
+       if (!bio) {
+               ret = -ENOMEM;
+               goto pkcs7_error;
+       }
+       if (format == CERT_FORMAT_ASN1) {
+               ok = i2d_PKCS7_bio(bio, p7);
+       } else if (format == CERT_FORMAT_PEM) {
+               ok = PEM_write_bio_PKCS7(bio, p7);
+       } else {
+               ret = -EINVAL;
+               goto pkcs7_error;
+       }
+       if (!ok) {
+               ret = -EIO;
+               goto pkcs7_error;
+       }
+       BIO_get_mem_ptr(bio, &bptr);
+       p7b = buf_alloc();
+       if (!p7b)
+               ret = -ENOMEM;
+       if (ret < 0) {
+ pkcs7_error:
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to output PKCS#7 structure\n"));
+               goto out;
+       }
+       BIO_set_close(bio, BIO_NOCLOSE);
+       p7b->data = bptr->data;
+       p7b->pos = bptr->length;
+       *pp7b = p7b;
+       p7b = NULL;
+ out:
+       buf_free(p7b);
+       BIO_free(bio);
+       if (p7)
+               PKCS7_free(p7);
+       return ret;
+ }
+ int multicert_sign_data(struct openconnect_info *vpninfo,
+                       struct cert_info *certinfo,
+                       unsigned int hashes,
+                       const void *chdata, size_t chdata_len,
+                       struct oc_text_buf **psignature)
+ {
+       struct table_entry {
+               openconnect_hash_type id;
+               const EVP_MD *(*evp_md_fn)(void);
+       };
+       static struct table_entry table[] = {
+               { OPENCONNECT_HASH_SHA512, &EVP_sha512 },
+               { OPENCONNECT_HASH_SHA384, &EVP_sha384 },
+               { OPENCONNECT_HASH_SHA256, &EVP_sha256 },
+               { OPENCONNECT_HASH_UNKNOWN },
+       };
+       struct ossl_cert_info *oci;
+       struct oc_text_buf *signature;
+       openconnect_hash_type hash;
+       int ret;
+       /**
+        * Check preconditions...
+        */
+       if (!(certinfo && (oci = certinfo->priv_info)
+             && hashes && chdata && chdata_len && psignature))
+               return -EINVAL;
+       *psignature = NULL;
+       signature = buf_alloc();
+       if (!signature)
+               goto out_of_memory;
+       for (const struct table_entry *entry = table;
+            (hash = entry->id) != OPENCONNECT_HASH_UNKNOWN;
+            entry++) {
+               if ((hashes & MULTICERT_HASH_FLAG(hash)) == 0)
+                       continue;
+               EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
+               if (!mdctx)
+                       goto out_of_memory;
+               const EVP_MD *md = (*entry->evp_md_fn)();
+               size_t siglen = 0;
+               int ok = (EVP_DigestSignInit(mdctx, NULL, md, NULL, oci->key) > 0 &&
+                         EVP_DigestSignUpdate(mdctx, chdata, chdata_len) > 0 &&
+                         EVP_DigestSignFinal(mdctx, NULL, &siglen) > 0 &&
+                         !buf_ensure_space(signature, siglen) &&
+                         EVP_DigestSignFinal(mdctx, (void *)signature->data, &siglen) > 0);
+               EVP_MD_CTX_free(mdctx);
+               if (ok) {
+                       signature->pos = siglen;
+                       *psignature = signature;
+                       return hash;
+               }
+       }
+       /** Error path */
+       ret = -EIO;
+       if (buf_error(signature)) {
+ out_of_memory:
+               ret = -ENOMEM;
+       }
++
+       buf_free(signature);
+       vpn_progress(vpninfo, PRG_ERR,
+                    _("Failed to generate signature for multiple certificate authentication\n"));
+       openconnect_report_ssl_errors(vpninfo);
+       return ret;
+ }
++
Simple merge
index 7aa1527a6b287bc4d37076d9769a911266314102,68d351af29a8fcb9d60f074091e41c4091f7bfd8..0e79c994834b1f240a3b6af5a3d80b61f78fdd7f
  <a href="https://git.infradead.org/users/dwmw2/openconnect.git">gitweb</a>.</p>
  <ul>
     <li><b>OpenConnect HEAD</b>
 +     <ul>
 +       <li>Add support for AnyConnect "external browser" SSO mode (<a href="https://gitlab.com/openconnect/openconnect/-/merge_requests/354">!354</a>).</li>
 +       <li>On Windows, fix crash on tunnel setup. (<a href="https://gitlab.com/openconnect/openconnect/-/issues/370">#370</a>, <a href="https://gitlab.com/openconnect/openconnect/commit/6a2ffbbcd1c4ef0b689cce3d17154f6d4c2e3bc0">6a2ffbb</a>)</li>
 +       <li>Bugfix RSA SecurID token decryption and PIN entry forms, broken in v8.20. (<a href="https://gitlab.com/openconnect/openconnect/-/issues/388">#388</a>, <a href="https://gitlab.com/openconnect/openconnect/-/merge_requests/344">!344</a>)</li>
++       <li>Support <a href="https://www.cisco.com/c/en/us/support/docs/security/anyconnect-secure-mobility-client-v4x/212483-configure-asa-as-the-ssl-gateway-for-any.html">Cisco's multiple-certificate authentication</a> (<a href="https://gitlab.com/openconnect/openconnect/-/merge_requests/194">!194</a>).</li>
 +     </ul><br/>
 +  </li>
 +  <li><b><a href="https://www.infradead.org/openconnect/download/openconnect-8.20.tar.gz">OpenConnect v8.20</a></b>
 +     <i>(<a href="https://www.infradead.org/openconnect/download/openconnect-8.20.tar.gz.asc">PGP signature</a>)</i> &#8212; 2022-02-20
       <ul>
         <li>When the queue length <i>(<tt>-Q</tt> option)</i> is 16 or more, try using <a
         href="https://www.redhat.com/en/blog/virtqueues-and-virtio-ring-how-data-travels">vhost-net</a> to accelerate tun device access.</li>