crypto/x509: mitigate DoS vector when intermediate certificate contains DSA public key

An attacker could craft an intermediate X.509 certificate
containing a DSA public key and can crash a remote host
with an unauthenticated call to any endpoint that
verifies the certificate chain.

Thank you to Jakub Ciolek for reporting this issue.

Fixes CVE-2025-58188
Fixes #75675

Change-Id: I2ecbb87b9b8268dbc55c8795891e596ab60f0088
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2780
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/709853
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Neal Patel 2025-09-11 16:27:04 -04:00 committed by Gopher Robot
parent 6f7926589d
commit 6e4007e8cf
2 changed files with 131 additions and 1 deletions

View file

@ -927,7 +927,10 @@ func alreadyInChain(candidate *Certificate, chain []*Certificate) bool {
if !bytes.Equal(candidate.RawSubject, cert.RawSubject) {
continue
}
if !candidate.PublicKey.(pubKeyEqual).Equal(cert.PublicKey) {
// We enforce the canonical encoding of SPKI (by only allowing the
// correct AI paremeter encodings in parseCertificate), so it's safe to
// directly compare the raw bytes.
if !bytes.Equal(candidate.RawSubjectPublicKeyInfo, cert.RawSubjectPublicKeyInfo) {
continue
}
var certSAN *pkix.Extension

View file

@ -6,6 +6,7 @@ package x509
import (
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
@ -3048,3 +3049,129 @@ func TestInvalidPolicyWithAnyKeyUsage(t *testing.T) {
t.Fatalf("unexpected error, got %q, want %q", err, expectedErr)
}
}
func TestCertificateChainSignedByECDSA(t *testing.T) {
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
root := &Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "X"},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
IsCA: true,
KeyUsage: KeyUsageCertSign | KeyUsageCRLSign,
BasicConstraintsValid: true,
}
caDER, err := CreateCertificate(rand.Reader, root, root, &caKey.PublicKey, caKey)
if err != nil {
t.Fatal(err)
}
root, err = ParseCertificate(caDER)
if err != nil {
t.Fatal(err)
}
leafKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
leaf := &Certificate{
SerialNumber: big.NewInt(42),
Subject: pkix.Name{CommonName: "leaf"},
NotBefore: time.Now().Add(-10 * time.Minute),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: KeyUsageDigitalSignature,
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
leafDER, err := CreateCertificate(rand.Reader, leaf, root, &leafKey.PublicKey, caKey)
if err != nil {
t.Fatal(err)
}
leaf, err = ParseCertificate(leafDER)
if err != nil {
t.Fatal(err)
}
inter, err := ParseCertificate(dsaSelfSignedCNX(t))
if err != nil {
t.Fatal(err)
}
inters := NewCertPool()
inters.AddCert(root)
inters.AddCert(inter)
wantErr := "certificate signed by unknown authority"
_, err = leaf.Verify(VerifyOptions{Intermediates: inters, Roots: NewCertPool()})
if !strings.Contains(err.Error(), wantErr) {
t.Errorf("got %v, want %q", err, wantErr)
}
}
// dsaSelfSignedCNX produces DER-encoded
// certificate with the properties:
//
// Subject=Issuer=CN=X
// DSA SPKI
// Matching inner/outer signature OIDs
// Dummy ECDSA signature
func dsaSelfSignedCNX(t *testing.T) []byte {
t.Helper()
var params dsa.Parameters
if err := dsa.GenerateParameters(&params, rand.Reader, dsa.L1024N160); err != nil {
t.Fatal(err)
}
var dsaPriv dsa.PrivateKey
dsaPriv.Parameters = params
if err := dsa.GenerateKey(&dsaPriv, rand.Reader); err != nil {
t.Fatal(err)
}
dsaPub := &dsaPriv.PublicKey
type dsaParams struct{ P, Q, G *big.Int }
paramDER, err := asn1.Marshal(dsaParams{dsaPub.P, dsaPub.Q, dsaPub.G})
if err != nil {
t.Fatal(err)
}
yDER, err := asn1.Marshal(dsaPub.Y)
if err != nil {
t.Fatal(err)
}
spki := publicKeyInfo{
Algorithm: pkix.AlgorithmIdentifier{
Algorithm: oidPublicKeyDSA,
Parameters: asn1.RawValue{FullBytes: paramDER},
},
PublicKey: asn1.BitString{Bytes: yDER, BitLength: 8 * len(yDER)},
}
rdn := pkix.Name{CommonName: "X"}.ToRDNSequence()
b, err := asn1.Marshal(rdn)
if err != nil {
t.Fatal(err)
}
rawName := asn1.RawValue{FullBytes: b}
algoIdent := pkix.AlgorithmIdentifier{Algorithm: oidSignatureDSAWithSHA256}
tbs := tbsCertificate{
Version: 0,
SerialNumber: big.NewInt(1002),
SignatureAlgorithm: algoIdent,
Issuer: rawName,
Validity: validity{NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(24 * time.Hour)},
Subject: rawName,
PublicKey: spki,
}
c := certificate{
TBSCertificate: tbs,
SignatureAlgorithm: algoIdent,
SignatureValue: asn1.BitString{Bytes: []byte{0}, BitLength: 8},
}
dsaDER, err := asn1.Marshal(c)
if err != nil {
t.Fatal(err)
}
return dsaDER
}