fix(openvpn): derive the tls-auth HMAC digest from auth instead of hard-coding SHA-1 - #3189
Merged
Merged
Conversation
…rd-coding SHA-1 --tls-auth wrapped every control packet with HMAC-SHA1 regardless of the configured auth, while OpenVPN derives the tls-auth digest from --auth (init.c: tls_auth_key_type.digest = options->authname; crypto_openssl.c hmac_ctx_init: key_len = EVP_MD_size(kt)). Against a server running "auth SHA256" or "auth SHA512" the client's first packet carries a 20-byte tag where the server expects 32 or 64 bytes, so the server drops it with "TLS Error: cannot locate HMAC in incoming packet" and the handshake times out after the hard-reset retransmits (MetaCubeX#2992). Servers on the OpenVPN default "auth SHA1" were unaffected, which is why the wrapper appeared to work. NewTLSAuth now takes the auth name and, through newDataChannelAuth, uses that digest for the HMAC, sizes the tag to the digest, and takes digest-size bytes of HMAC key material from offset 64 of the direction's key slot - the same three places OpenVPN reads --auth. An empty auth follows the config default (SHA256), like the data channel. The simulated server in the rekey test now wraps with the client's configured digest; it used to pass only because both sides shared the same hard-coded SHA-1. Fixes MetaCubeX#2992 Refs MetaCubeX#2846
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
--tls-authwrapped every control-channel packet with HMAC-SHA1 no matter whatauthwas configured. OpenVPN derives the tls-auth digest from--auth, so against a server runningauth SHA256orauth SHA512the client's very first packet carries a 20-byte tag where the server expects 32 or 64 bytes. The server drops it withTLS Error: cannot locate HMAC in incoming packet, and the client times out withmake OpenVPN handshake: read hard reset response after 4 retransmits: context deadline exceeded. Servers on OpenVPN's defaultauth SHA1were unaffected, which is why tls-auth appeared to work.Fixes #2992 (tls-auth +
auth SHA512never completes the handshake). Refs #2846.Root cause
OpenVPN, at the pinned 2.7.5 source:
init.c: the tls-auth key type takes its digest from--auth— https://github.com/OpenVPN/openvpn/blob/b25bb2a8bda814edab39b4246d4e296330a7a29e/src/openvpn/init.c#L3087crypto_openssl.chmac_ctx_init: the HMAC key length is the digest size — https://github.com/OpenVPN/openvpn/blob/b25bb2a8bda814edab39b4246d4e296330a7a29e/src/openvpn/crypto_openssl.c#L1199ssl_pkt.c: a packet too short for the expected tag is rejected before authentication — https://github.com/OpenVPN/openvpn/blob/b25bb2a8bda814edab39b4246d4e296330a7a29e/src/openvpn/ssl_pkt.c#L216mihomo, before this change:
transport/openvpn/tlsauth.gohard-codedsha1.Sizefor both the tag and the key material andhmac.New(sha1.New, key), andNewClientnever passedconfig.Authto it.Change
NewTLSAuth(staticKey, keyDirection, authName): the digest comes fromnewDataChannelAuth(normalizeAuth(authName))(MD5 / SHA1 / SHA256 / SHA384 / SHA512, the same setauthaccepts). The tag is digest-sized and the HMAC key is the first digest-size bytes of the HMAC half (offset 64) of the direction's key slot — the three places OpenVPN reads--auth. An emptyauthfollows the config default (SHA256), as the data channel does. Unsupported digests are refused at construction.NewClientpassesconfig.Auth.TestRealTLSRekeySurvivesExtendedAuthPendingnow wraps with the client's configured digest; it passed before only because both sides shared the same hard-coded SHA-1.TLSAuthTagSizeis replaced by(*TLSAuth).TagSize(), since the size is no longer a constant.Tests
TestTLSAuthDigestFollowsAuth: for each of the five digests, a client/server round trip, the tag length equals the digest size, and the tag isHMAC-<digest>over packet id, header and payload keyed with the slot's HMAC material — independently derived from the OpenVPN sources above.TestTLSAuthDigestMismatchIsRejected: a SHA256 tag does not verify under a SHA1 peer.TestTLSAuthDefaultAndUnsupportedDigest: emptyauth→ SHA256;SHA224refused.go test ./transport/openvpn/andgo test -race ./transport/openvpn/pass;go vetclean.How this was found
We (Hako, a mihomo-based Apple client) were standing up an interop gate against an official OpenVPN 2.7.5 server for a user report about a tls-crypt profile (TokenPLS/Hako-Client#16). Adding a tls-auth arm with
auth SHA256exposed that the digest was fixed to SHA-1 — the same failure #2992 reports with SHA512. A run of that gate against the reference server will follow once available; the unit tests above are what this PR carries.Reproduce
Server (
openvpn --genkey tls-auth ta.key):Client:
Before: server logs
TLS Error: cannot locate HMAC in incoming packet from [AF_INET]..., client never completes the handshake. After: the handshake completes; a wrong key is still refused by the server withTLS Error: incoming packet authentication failed.