Problem
In a FIPS build (GOFIPS140=v1.0.0), crypto/tls does not check whether the server's certificate uses a FIPS-approved key when InsecureSkipVerify=true. A server presenting a 1024-bit RSA certificate is silently accepted, which violates FIPS 140-3.
The check (fipsAllowedChains) only runs when InsecureSkipVerify=false (handshake_client.go#L1147). Setting InsecureSkipVerify=true bypasses it entirely, even when a VerifyConnection callback is doing full certificate verification.
InsecureSkipVerify=true is not always "skip all checks"
The docs recommend InsecureSkipVerify=true + VerifyConnection as the correct way to implement custom certificate verification. One real use case where this is required: adding a trusted CA dynamically based on its fingerprint. If InsecureSkipVerify=false, Go's verifier rejects the certificate before VerifyConnection gets to add the CA — so the callback must own the full verification and InsecureSkipVerify=true is the only option.
In this pattern, the FIPS key check is silently bypassed — not intentionally, but as an unavoidable side effect, because of the structure in verifyServerCertificate:
} else if !c.config.InsecureSkipVerify {
chains, err := certs[0].Verify(opts)
...
c.verifiedChains, err = fipsAllowedChains(chains) // ← skipped when InsecureSkipVerify=true
...
}
// VerifyConnection is called unconditionally, but fipsAllowedChains is not:
if c.config.VerifyConnection != nil {
if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil { ... }
}
VerifyPeerCertificate has the same gap
VerifyPeerCertificate sits in the same position — outside the !InsecureSkipVerify block, called unconditionally but after fipsAllowedChains has already been skipped. Callers using either callback face the same problem.
What we'd like
Option A — check certificate key compliance unconditionally in FIPS mode, regardless of InsecureSkipVerify. The check only needs PeerCertificates (always available) and does not require a verified chain, so it can run independently:
// always, in FIPS mode:
for _, cert := range peerCertificates {
if !isCertificateAllowedFIPS(cert) { return error }
}
Option B — expose a public function so VerifyConnection implementations can call it themselves:
// in crypto/fips140 or crypto/tls:
func CheckFIPSCertChain(chain []*x509.Certificate) error
Option C — document that InsecureSkipVerify=true bypasses FIPS certificate key checks, and that callers using this pattern must implement the check themselves.
Go version
Go 1.26.4, GOFIPS140=v1.0.0.
References
Problem
In a FIPS build (
GOFIPS140=v1.0.0),crypto/tlsdoes not check whether the server's certificate uses a FIPS-approved key whenInsecureSkipVerify=true. A server presenting a 1024-bit RSA certificate is silently accepted, which violates FIPS 140-3.The check (
fipsAllowedChains) only runs whenInsecureSkipVerify=false(handshake_client.go#L1147). SettingInsecureSkipVerify=truebypasses it entirely, even when aVerifyConnectioncallback is doing full certificate verification.InsecureSkipVerify=true is not always "skip all checks"
The docs recommend
InsecureSkipVerify=true+VerifyConnectionas the correct way to implement custom certificate verification. One real use case where this is required: adding a trusted CA dynamically based on its fingerprint. IfInsecureSkipVerify=false, Go's verifier rejects the certificate beforeVerifyConnectiongets to add the CA — so the callback must own the full verification andInsecureSkipVerify=trueis the only option.In this pattern, the FIPS key check is silently bypassed — not intentionally, but as an unavoidable side effect, because of the structure in
verifyServerCertificate:VerifyPeerCertificate has the same gap
VerifyPeerCertificatesits in the same position — outside the!InsecureSkipVerifyblock, called unconditionally but afterfipsAllowedChainshas already been skipped. Callers using either callback face the same problem.What we'd like
Option A — check certificate key compliance unconditionally in FIPS mode, regardless of
InsecureSkipVerify. The check only needsPeerCertificates(always available) and does not require a verified chain, so it can run independently:Option B — expose a public function so
VerifyConnectionimplementations can call it themselves:Option C — document that
InsecureSkipVerify=truebypasses FIPS certificate key checks, and that callers using this pattern must implement the check themselves.Go version
Go 1.26.4,
GOFIPS140=v1.0.0.References
isCertificateAllowedFIPS