Skip to content

crypto/tls: FIPS 140-3 certificate key check is skipped when InsecureSkipVerify=true #80074

Description

@macdewee

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsFixThe path to resolution is known, but the work has not been done.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions