From: David Woodhouse Date: Tue, 12 Apr 2022 09:05:46 +0000 (+0200) Subject: Merge branch 'multicert' of gitlab.com:openconnect/openconnect X-Git-Tag: v9.00~48 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=652fdace9a9f2f345a0572dbb6a6768c63353f92;p=users%2Fdwmw2%2Fopenconnect.git Merge branch 'multicert' of gitlab.com:openconnect/openconnect --- 652fdace9a9f2f345a0572dbb6a6768c63353f92 diff --cc Makefile.am index c4c10ab4,156f117a..e2ff4cd0 --- a/Makefile.am +++ b/Makefile.am @@@ -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 gnutls.c index c637262a,720fe6c3..09fbc78a --- a/gnutls.c +++ b/gnutls.c @@@ -2798,252 -2825,349 +2825,599 @@@ void destroy_eap_ttls(struct openconnec gnutls_deinit(sess); } +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 +#include + +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 maybe multiple certificate authentication incompatible.\n")); ++ _("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; + } diff --cc libopenconnect.map.in index 757341be,bd3151d7..227bf8b7 --- a/libopenconnect.map.in +++ b/libopenconnect.map.in @@@ -115,14 -115,9 +115,16 @@@ OPENCONNECT_5_7 openconnect_get_auth_expiration; openconnect_disable_dtls; openconnect_get_connect_url; + 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_6; +} OPENCONNECT_5_7; OPENCONNECT_PRIVATE { global: @SYMVER_TIME@ @SYMVER_GETLINE@ @SYMVER_JAVA@ @SYMVER_ASPRINTF@ @SYMVER_VASPRINTF@ @SYMVER_WIN32_STRERROR@ @SYMVER_WIN32_SETENV@ diff --cc openconnect.h index ae11b6f4,5d1af88e..4fda33e0 --- a/openconnect.h +++ b/openconnect.h @@@ -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 e8f57c97,0d17c216..456f12ed --- a/openssl.c +++ 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 + +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; + } ++ diff --cc www/changelog.xml index 7aa1527a,68d351af..0e79c994 --- a/www/changelog.xml +++ b/www/changelog.xml @@@ -14,14 -14,6 +14,15 @@@ gitweb.