Description
As instructed by @qmuntal I am opening this issue here in golang-fips/openssl.
The issue is that TLS 1.3 does not work in binaries built with microsoft/go and -tags goexperiment.opensslcrypto
when using the KeyPair FIPS Provider for OpenSSL 3 (https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/4724).
I have been in contact with Keypair and the root cause is that Keypair FIPS provider enforces a minimum HMAC key length of 112 bits. However, when crypto/tls calls HKDF Extract() it sometimes does this with nil salt and consequently when that salt is used as the HMAC key, the FIPS provider's enforcement kicks in and triggers a panic.
The issue can be reproduced with this simple app:
package main
import (
"crypto/tls"
"fmt"
"os"
)
func main() {
if len(os.Args) <= 1 {
panic("missing dst address")
}
c, err := tls.Dial("tcp", os.Args[1],
&tls.Config{
MinVersion: tls.VersionTLS13,
})
if err != nil {
panic(err)
}
fmt.Printf("successfully connected to %s (%s)\n", os.Args[1], c.RemoteAddr().String())
c.Close()
}
The panic looks following on microsoft/go1.23.6-20250211.6:
$ LD_LIBRARY_PATH=~/src/keypair/openssl-3.0.10-install/lib64 ./test www.google.com:443
panic: tls: HKDF-Extract invocation failed unexpectedly: EVP_PKEY_derive
openssl error(s):
goroutine 1 [running]:
crypto/tls.(*cipherSuiteTLS13).extract(0x80b460?, {0x0?, 0x0?, 0x0?}, {0x0?, 0x0?, 0x0?})
/usr/local/lib/microsoftgo/go1.23.6-20250211.6/src/crypto/tls/key_schedule.go:97 +0x193
crypto/tls.(*clientHandshakeStateTLS13).establishHandshakeKeys(0xc0001d9a08)
/usr/local/lib/microsoftgo/go1.23.6-20250211.6/src/crypto/tls/handshake_client_tls13.go:514 +0x2ce
crypto/tls.(*clientHandshakeStateTLS13).handshake(0xc0001d9a08)
/usr/local/lib/microsoftgo/go1.23.6-20250211.6/src/crypto/tls/handshake_client_tls13.go:132 +0x725
crypto/tls.(*Conn).clientHandshake(0xc000004708, {0x6af338, 0xc0000be140})
/usr/local/lib/microsoftgo/go1.23.6-20250211.6/src/crypto/tls/handshake_client.go:375 +0x845
crypto/tls.(*Conn).handshakeContext(0xc000004708, {0x6af178, 0x833040})
/usr/local/lib/microsoftgo/go1.23.6-20250211.6/src/crypto/tls/conn.go:1568 +0x3a6
crypto/tls.(*Conn).HandshakeContext(...)
/usr/local/lib/microsoftgo/go1.23.6-20250211.6/src/crypto/tls/conn.go:1508
crypto/tls.dial({0x6af178?, 0x833040?}, 0xc000085e30, {0x645d46, 0x3}, {0x7ffc4884e085, 0x12}, 0xc00019a000)
/usr/local/lib/microsoftgo/go1.23.6-20250211.6/src/crypto/tls/tls.go:159 +0x3a5
crypto/tls.DialWithDialer(...)
/usr/local/lib/microsoftgo/go1.23.6-20250211.6/src/crypto/tls/tls.go:119
crypto/tls.Dial({0x645d46?, 0x100451089?}, {0x7ffc4884e085?, 0xc000085f40?}, 0x40f36b?)
/usr/local/lib/microsoftgo/go1.23.6-20250211.6/src/crypto/tls/tls.go:173 +0x7a
main.main()
/home/xxxxx/tls.go:14 +0x6f
Similar panic is triggered in microsoft/go1.24.0-20250212.5:
$ LD_LIBRARY_PATH=~/src/keypair/openssl-3.0.10-install/lib64 ./test www.google.com:443
panic: EVP_KDF_derive
openssl error(s):
goroutine 1 [running]:
crypto/tls/internal/tls13.extract[...](0xc000290110?, {0x0?, 0xc00002f6b8?, 0x50d39b?}, {0x0?, 0x0?, 0x0?})
/usr/local/lib/microsoftgo/go1.24.0-20250212.5/src/crypto/tls/internal/tls13/tls13.go:52 +0xbd
crypto/tls/internal/tls13.NewEarlySecret[...](0xc000290110, {0x0, 0x0?, 0x0?})
/usr/local/lib/microsoftgo/go1.24.0-20250212.5/src/crypto/tls/internal/tls13/tls13.go:83 +0x35
crypto/tls.(*clientHandshakeStateTLS13).establishHandshakeKeys(0xc00002fa00)
/usr/local/lib/microsoftgo/go1.24.0-20250212.5/src/crypto/tls/handshake_client_tls13.go:519 +0x2ac
crypto/tls.(*clientHandshakeStateTLS13).handshake(0xc00002fa00)
/usr/local/lib/microsoftgo/go1.24.0-20250212.5/src/crypto/tls/handshake_client_tls13.go:134 +0x73e
crypto/tls.(*Conn).clientHandshake(0xc00028a008, {0x6e4b40, 0xc00028e000})
/usr/local/lib/microsoftgo/go1.24.0-20250212.5/src/crypto/tls/handshake_client.go:379 +0x810
crypto/tls.(*Conn).handshakeContext(0xc00028a008, {0x6e4898, 0x8b2000})
/usr/local/lib/microsoftgo/go1.24.0-20250212.5/src/crypto/tls/conn.go:1568 +0x39a
crypto/tls.(*Conn).HandshakeContext(...)
/usr/local/lib/microsoftgo/go1.24.0-20250212.5/src/crypto/tls/conn.go:1508
crypto/tls.dial({0x6e4898?, 0x8b2000?}, 0xc00007be30, {0x686261, 0x3}, {0x7fff1e8f1085, 0x12}, 0xc000138000)
/usr/local/lib/microsoftgo/go1.24.0-20250212.5/src/crypto/tls/tls.go:159 +0x3a5
crypto/tls.DialWithDialer(...)
/usr/local/lib/microsoftgo/go1.24.0-20250212.5/src/crypto/tls/tls.go:119
crypto/tls.Dial({0x686261?, 0x457b49?}, {0x7fff1e8f1085?, 0xc00007bf40?}, 0x416cc8?)
/usr/local/lib/microsoftgo/go1.24.0-20250212.5/src/crypto/tls/tls.go:173 +0x7a
main.main()
/home/xxxxx/tls.go:14 +0x6f
According to RFC 5869 Section 2.2, the salt "if not provided, it is set to a string of HashLen zeros.".
Patching the OpenSSL HKDF bindings in the golang-fips/openssl/v2/hkdf.go to convert nil salt to zeroed buffer does fix the issue.
For microsoft/go1.23.6-20250211.6 the patch looks like following:
diff -ruN go1.23.6-20250211.6.orig/src/vendor/github.com/golang-fips/openssl/v2/hkdf.go go1.23.6-20250211.6/src/vendor/github.com/golang-fips/openssl/v2/hkdf.go
--- go1.23.6-20250211.6.orig/src/vendor/github.com/golang-fips/openssl/v2/hkdf.go 2025-02-21 11:58:44.705750417 +0200
+++ go1.23.6-20250211.6/src/vendor/github.com/golang-fips/openssl/v2/hkdf.go 2025-02-21 12:00:59.960965835 +0200
@@ -110,6 +110,11 @@
if err != nil {
return nil, err
}
+
+ if salt == nil {
+ salt = make([]byte, h().Size())
+ }
+
switch vMajor {
case 3:
if C.go_openssl_EVP_PKEY_CTX_set1_hkdf_key(c.ctx,
For microsoft/go1.24.0-20250212.5 the patch looks like following, though I have not done very extensive testing on go1.24:
diff -ruN go1.24.0-20250212.5.orig/src/vendor/github.com/golang-fips/openssl/v2/hkdf.go go1.24.0-20250212.5/src/vendor/github.com/golang-fips/openssl/v2/hkdf.go
--- go1.24.0-20250212.5.orig/src/vendor/github.com/golang-fips/openssl/v2/hkdf.go 2025-02-21 12:20:33.666148363 +0200
+++ go1.24.0-20250212.5/src/vendor/github.com/golang-fips/openssl/v2/hkdf.go 2025-02-21 12:22:19.831310298 +0200
@@ -119,6 +119,10 @@
return nil, err
}
+ if salt == nil {
+ salt = make([]byte, h().Size())
+ }
+
switch vMajor {
case 1:
ctx, err := newHKDFCtx1(md, C.GO_EVP_KDF_HKDF_MODE_EXTRACT_ONLY, secret, salt, nil, nil)