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;
+ }
* 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;
+ }
++