From 07bae89a6adcda455bd484342123f6b512329783 Mon Sep 17 00:00:00 2001 From: Kristoffer Kobosko Date: Fri, 11 Sep 2026 11:30:22 +0200 Subject: [PATCH 1/2] Accept non-generic xauth types A Fortigate that authenticates with a FortiToken sends XAUTH_TYPE 2 rather than 0, so requiring the generic type aborts the handshake with xauth packet unsupported: (ISAKMP_N_ATTRIBUTES_NOT_SUPPORTED)(13) right after the password prompt, on a gateway that is otherwise happy to talk to us. Nothing in the exchange is driven by that value: which credentials we send is decided by the attributes the server asks for, which we validate individually anyway. So check that the type is well formed and note anything unusual at debug level 2 instead of refusing to continue. Name the offending attribute when we do reject one, too. Without it an unsupported attribute and an unsupported type produce the same bare (13) and neither says which attribute was at fault. --- src/vpnc.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vpnc.c b/src/vpnc.c index e684bbd..cec63da 100644 --- a/src/vpnc.c +++ b/src/vpnc.c @@ -2305,8 +2305,13 @@ static int do_phase2_xauth(struct sa_block *s) for (ap = a; ap && reject == 0; ap = ap->next) switch (ap->type) { case ISAKMP_XAUTH_06_ATTRIB_TYPE: - if (ap->af != isakmp_attr_16 || ap->u.attr_16 != 0) + if (ap->af != isakmp_attr_16) reject = ISAKMP_N_ATTRIBUTES_NOT_SUPPORTED; + else if (ap->u.attr_16 != 0) + /* Fortigate uses a non-generic xauth type for + * token based authentication; the exchange is + * driven by the attributes anyway, so accept it */ + DEBUG(2, printf("got xauth type %d, treating it as generic\n", ap->u.attr_16)); break; case ISAKMP_XAUTH_06_ATTRIB_USER_NAME: case ISAKMP_XAUTH_06_ATTRIB_USER_PASSWORD: @@ -2330,6 +2335,7 @@ static int do_phase2_xauth(struct sa_block *s) } break; default: + printf("got unsupported xauth attribute type %d / 0x%X\n", ap->type, ap->type); reject = ISAKMP_N_ATTRIBUTES_NOT_SUPPORTED; } if (reject != 0) From 403f60b73bc10c770064e5f5b3e5d0b636a7d4b3 Mon Sep 17 00:00:00 2001 From: Kristoffer Kobosko Date: Fri, 11 Sep 2026 11:30:34 +0200 Subject: [PATCH 2/2] Do not offer algorithms we would refuse We advertise md5, des, 3des and null in both the IKE and the quick mode proposals, and then call error(1) the moment the peer selects one of them, telling the user to pass --enable-weak-authentication or --enable-weak-encryption. Offering an algorithm we will not accept can only lose us a handshake we had already won: a Fortigate that pairs md5 with dh2 in one of its phase 1 proposals answers with IKE SA selected psk+xauth-aes256-md5 Peer has selected md5 as authentication method. and the connection dies after phase 1 completed, even though the same gateway negotiates aes256-sha1 happily once md5 is off the table. Filter the proposals through the --enable-* options that already decide whether we would accept the result. The checks on the peer's selection stay as they are; they still catch a gateway that picks something we never offered. --- src/vpnc.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/vpnc.c b/src/vpnc.c index cec63da..c508fca 100644 --- a/src/vpnc.c +++ b/src/vpnc.c @@ -1147,6 +1147,29 @@ static struct isakmp_attribute *make_transform_ike(int dh_group, int crypt, int return a; } +/* + * Offering an algorithm that we refuse as soon as the peer selects it only + * turns a usable gateway into a failed handshake, so keep the weak ones out + * of our proposals unless they have been enabled explicitly. + */ +static int may_offer_crypt(const supported_algo_t *algo) +{ + switch (algo->my_id) { + case GCRY_CIPHER_NONE: + return opt_no_encryption; + case GCRY_CIPHER_DES: + case GCRY_CIPHER_3DES: + return opt_weak_encryption; + default: + return 1; + } +} + +static int may_offer_hash(const supported_algo_t *algo) +{ + return (algo->my_id == GCRY_MD_MD5) ? opt_weak_authentication : 1; +} + static struct isakmp_payload *make_our_sa_ike(void) { struct isakmp_payload *r = new_isakmp_payload(ISAKMP_PAYLOAD_SA); @@ -1177,8 +1200,12 @@ static struct isakmp_payload *make_our_sa_ike(void) continue; } for (crypt = 0; supp_crypt[crypt].name != NULL; crypt++) { + if (!may_offer_crypt(&supp_crypt[crypt])) + continue; keylen = supp_crypt[crypt].keylen; for (hash = 0; supp_hash[hash].name != NULL; hash++) { + if (!may_offer_hash(&supp_hash[hash])) + continue; tn = t; t = new_isakmp_payload(ISAKMP_PAYLOAD_T); t->u.t.id = ISAKMP_IPSEC_KEY_IKE; @@ -2604,8 +2631,12 @@ static struct isakmp_payload *make_our_sa_ipsec(struct sa_block *s) r->u.sa.doi = ISAKMP_DOI_IPSEC; r->u.sa.situation = ISAKMP_IPSEC_SIT_IDENTITY_ONLY; for (crypt = 0; supp_crypt[crypt].name != NULL; crypt++) { + if (!may_offer_crypt(&supp_crypt[crypt])) + continue; keylen = supp_crypt[crypt].keylen; for (hash = 0; supp_hash[hash].name != NULL; hash++) { + if (!may_offer_hash(&supp_hash[hash])) + continue; pn = p; p = new_isakmp_payload(ISAKMP_PAYLOAD_P); p->u.p.spi_size = 4;