Skip to content

Protocol fixes - #2

Open
kobosko wants to merge 7 commits into
masterfrom
protocol-fixes
Open

kobosko wants to merge 7 commits into
masterfrom
protocol-fixes

Conversation

@kobosko

@kobosko kobosko commented Sep 11, 2026

Copy link
Copy Markdown
Owner

No description provided.

kobosko and others added 7 commits September 11, 2026 12:06
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.
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.
A Cisco concentrator sends CISCO_SPLIT_INC entries as 14 bytes: network,
mask, protocol, source port, destination port. strongSwan's charon sends
only the network and the mask, 8 bytes per entry, and so do appliances
built on it. We derive the entry count by dividing the payload by 14, so
a payload that is not a whole number of Cisco entries is rejected with
ISAKMP_N_PAYLOAD_MALFORMED. That discards the modecfg reply carrying the
split networks, and with it the connection, against a gateway that is
otherwise working.

Fall back to the 8 byte layout when the payload does not divide into
whole Cisco entries but does divide into 8, and treat the three missing
fields as zero, which is what "any" is encoded as when they are present.
A payload that fits neither layout is still rejected.
Both receive paths take the readiness reported by select() or poll() as a
promise that the recv which follows will not block. It is not one: the
kernel can discard the datagram they saw before we ask for it, a bad UDP
checksum being the usual way, and then the recv blocks with nothing else
on the way. On the data path that hangs the tunnel until a packet
happens to arrive; during negotiation it hangs vpnc outright, because
sendrecv() is single threaded and nothing else runs meanwhile.

Set SO_RCVTIMEO on the sockets we create and treat a timed out receive as
"nothing arrived". Both data path callers already handle a failed receive
that way, so they only needed to stop logging it as an error.

recv_ignore_dup() needed more than that: it called error(1) on any failed
receive, so adding the timeout without this would turn a stall into an
exit. It now returns -1 and lets sendrecv() retry, the same as for a
duplicate.
We can only offer CBC ciphers paired with an HMAC. Gateways are steadily
moving the other way: an IPsec policy that has been hardened to AEAD only
leaves vpnc with nothing to propose and the quick mode exchange fails,
even though every other part of the connection would work.

Add the three AES-GCM transforms from RFC 4106 with a 16 byte ICV, which
is what a gateway configured for aes128gcm/aes256gcm expects. They are
ESP only, so they carry no IKE id and are skipped when building the phase
1 proposal, and they authenticate by themselves, so their transform
offers no AUTH_ALG and pairs with no hash. Key material for them is four
bytes longer than the cipher key: the tail is the salt that RFC 4106
prepends to the explicit IV to form the nonce, and it has to be copied
again on a rekey along with the keys.

The proposal list is built back to front, so the AEAD ciphers are walked
last to have them offered first.

This goes through libgcrypt's GCM mode, which vpnc already links against,
so it works the same whether the build uses GnuTLS or OpenSSL for the
certificate paths.
vpnc knows two integrity algorithms, md5 and sha1, and md5 is refused
unless it is enabled explicitly. That leaves sha1 as the only thing we
can propose, so a gateway whose policy has been hardened to sha256 has
nothing to agree with us on, in either phase, and the connection fails
on a client that is otherwise perfectly able to talk to it.

Add sha256, sha384 and sha512 for both IKE and ESP. The IKE side needs
nothing else: key material and hash payloads are already sized from
gcry_md_get_algo_dlen().

ESP is not so simple. RFC 2403 and RFC 2404 truncate md5 and sha1 to 96
bits, and the ICV length is written into the packet paths as a literal
12. RFC 4868 truncates the SHA-2 family to half the digest instead, so
sha256, sha384 and sha512 want 16, 24 and 32 bytes. The length is now
computed per algorithm, carried on the SA next to md_len, and passed to
hmac_compute() rather than assumed by it. Getting this wrong is not a
visible error, just every packet failing its integrity check, so the
receive path also grows the length check the fixed size never needed.

The wire ids are the ones RFC 4868 assigns, 5 to 7, continuing the
authentication algorithm attribute after KPDK.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant