From 6997bcd820a70664b9d353092bd46bf0b88c4eac Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Fri, 15 May 2026 15:23:59 -0400 Subject: [PATCH] crypto/x509: add RawSignatureAlgorithm Fixes #76133 Change-Id: I7889aeff2ed197ee15f2ce7689b99b936a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/778660 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Filippo Valsorda Reviewed-by: Austin Clements Reviewed-by: Roland Shoemaker --- api/next/76133.txt | 3 + .../6-stdlib/99-minor/crypto/x509/76133.md | 4 + src/crypto/x509/parser.go | 12 ++- src/crypto/x509/x509.go | 32 +++++--- src/crypto/x509/x509_test.go | 81 +++++++++++++++++++ 5 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 api/next/76133.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/x509/76133.md diff --git a/api/next/76133.txt b/api/next/76133.txt new file mode 100644 index 0000000000..8551950c69 --- /dev/null +++ b/api/next/76133.txt @@ -0,0 +1,3 @@ +pkg crypto/x509, type Certificate struct, RawSignatureAlgorithm []uint8 #76133 +pkg crypto/x509, type CertificateRequest struct, RawSignatureAlgorithm []uint8 #76133 +pkg crypto/x509, type RevocationList struct, RawSignatureAlgorithm []uint8 #76133 diff --git a/doc/next/6-stdlib/99-minor/crypto/x509/76133.md b/doc/next/6-stdlib/99-minor/crypto/x509/76133.md new file mode 100644 index 0000000000..af7dd6b31f --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/x509/76133.md @@ -0,0 +1,4 @@ +The new [Certificate.RawSignatureAlgorithm], [CertificateRequest.RawSignatureAlgorithm], +and [RevocationList.RawSignatureAlgorithm] fields expose the DER-encoded +AlgorithmIdentifier of the signature algorithm, including when the +SignatureAlgorithm field is [UnknownSignatureAlgorithm]. diff --git a/src/crypto/x509/parser.go b/src/crypto/x509/parser.go index e255f7d604..9203223e76 100644 --- a/src/crypto/x509/parser.go +++ b/src/crypto/x509/parser.go @@ -936,7 +936,11 @@ func parseCertificate(der []byte) (*Certificate, error) { cert.SerialNumber = serial var sigAISeq cryptobyte.String - if !tbs.ReadASN1(&sigAISeq, cryptobyte_asn1.SEQUENCE) { + if !tbs.ReadASN1Element(&sigAISeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed signature algorithm identifier") + } + cert.RawSignatureAlgorithm = sigAISeq + if !sigAISeq.ReadASN1(&sigAISeq, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("x509: malformed signature algorithm identifier") } // Before parsing the inner algorithm identifier, extract @@ -1143,7 +1147,11 @@ func ParseRevocationList(der []byte) (*RevocationList, error) { } var sigAISeq cryptobyte.String - if !tbs.ReadASN1(&sigAISeq, cryptobyte_asn1.SEQUENCE) { + if !tbs.ReadASN1Element(&sigAISeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed signature algorithm identifier") + } + rl.RawSignatureAlgorithm = sigAISeq + if !sigAISeq.ReadASN1(&sigAISeq, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("x509: malformed signature algorithm identifier") } // Before parsing the inner algorithm identifier, extract diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index 7953b615f5..b2cb888fbc 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -707,6 +707,7 @@ type Certificate struct { RawSubjectPublicKeyInfo []byte // DER encoded SubjectPublicKeyInfo. RawSubject []byte // DER encoded Subject RawIssuer []byte // DER encoded Issuer + RawSignatureAlgorithm []byte // DER encoded AlgorithmIdentifier Signature []byte SignatureAlgorithm SignatureAlgorithm @@ -1914,6 +1915,7 @@ type CertificateRequest struct { RawTBSCertificateRequest []byte // Certificate request info part of raw ASN.1 DER content. RawSubjectPublicKeyInfo []byte // DER encoded SubjectPublicKeyInfo. RawSubject []byte // DER encoded Subject. + RawSignatureAlgorithm []byte // DER encoded AlgorithmIdentifier. Version int Signature []byte @@ -1966,8 +1968,12 @@ type tbsCertificateRequest struct { type certificateRequest struct { Raw asn1.RawContent TBSCSR tbsCertificateRequest - SignatureAlgorithm pkix.AlgorithmIdentifier - SignatureValue asn1.BitString + SignatureAlgorithm struct { + Raw asn1.RawContent + Algorithm asn1.ObjectIdentifier + Parameters asn1.RawValue `asn1:"optional"` + } + SignatureValue asn1.BitString } // oidExtensionRequest is a PKCS #9 OBJECT IDENTIFIER that indicates requested @@ -2202,11 +2208,12 @@ func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv return nil, err } - return asn1.Marshal(certificateRequest{ - TBSCSR: tbsCSR, - SignatureAlgorithm: algorithmIdentifier, - SignatureValue: asn1.BitString{Bytes: signature, BitLength: len(signature) * 8}, - }) + cr := certificateRequest{} + cr.TBSCSR = tbsCSR + cr.SignatureAlgorithm.Algorithm = algorithmIdentifier.Algorithm + cr.SignatureAlgorithm.Parameters = algorithmIdentifier.Parameters + cr.SignatureValue = asn1.BitString{Bytes: signature, BitLength: len(signature) * 8} + return asn1.Marshal(cr) } // ParseCertificateRequest parses a single certificate request from the @@ -2230,9 +2237,13 @@ func parseCertificateRequest(in *certificateRequest) (*CertificateRequest, error RawTBSCertificateRequest: in.TBSCSR.Raw, RawSubjectPublicKeyInfo: in.TBSCSR.PublicKey.Raw, RawSubject: in.TBSCSR.Subject.FullBytes, + RawSignatureAlgorithm: in.SignatureAlgorithm.Raw, - Signature: in.SignatureValue.RightAlign(), - SignatureAlgorithm: getSignatureAlgorithmFromAI(in.SignatureAlgorithm), + Signature: in.SignatureValue.RightAlign(), + SignatureAlgorithm: getSignatureAlgorithmFromAI(pkix.AlgorithmIdentifier{ + Algorithm: in.SignatureAlgorithm.Algorithm, + Parameters: in.SignatureAlgorithm.Parameters, + }), PublicKeyAlgorithm: getPublicKeyAlgorithmFromOID(in.TBSCSR.PublicKey.Algorithm.Algorithm), @@ -2327,6 +2338,9 @@ type RevocationList struct { RawTBSRevocationList []byte // RawIssuer contains the DER encoded Issuer. RawIssuer []byte + // RawSignatureAlgorithm contains the DER encoded signature algorithm as a + // PKIX AlgorithmIdentifier. + RawSignatureAlgorithm []byte // Issuer contains the DN of the issuing certificate. Issuer pkix.Name diff --git a/src/crypto/x509/x509_test.go b/src/crypto/x509/x509_test.go index f08af3a133..5e04fd11c3 100644 --- a/src/crypto/x509/x509_test.go +++ b/src/crypto/x509/x509_test.go @@ -3506,6 +3506,87 @@ Qc4= } } +func TestRawSignatureAlgorithm(t *testing.T) { + // checkAI verifies that raw is a DER-encoded AlgorithmIdentifier with the + // expected OID. + checkAI := func(t *testing.T, raw []byte, wantOID asn1.ObjectIdentifier) { + t.Helper() + if len(raw) == 0 { + t.Fatal("RawSignatureAlgorithm is empty") + } + var ai pkix.AlgorithmIdentifier + rest, err := asn1.Unmarshal(raw, &ai) + if err != nil { + t.Fatalf("failed to unmarshal RawSignatureAlgorithm: %s", err) + } + if len(rest) != 0 { + t.Fatalf("trailing data after RawSignatureAlgorithm: %x", rest) + } + if !ai.Algorithm.Equal(wantOID) { + t.Fatalf("unexpected OID: got %v, want %v", ai.Algorithm, wantOID) + } + } + + t.Run("Certificate", func(t *testing.T) { + p, _ := pem.Decode([]byte(pemCertificate)) + cert, err := ParseCertificate(p.Bytes) + if err != nil { + t.Fatalf("failed to parse certificate: %s", err) + } + checkAI(t, cert.RawSignatureAlgorithm, oidSignatureSHA256WithRSA) + }) + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate key: %s", err) + } + + t.Run("CertificateRequest", func(t *testing.T) { + csrDER, err := CreateCertificateRequest(rand.Reader, &CertificateRequest{}, priv) + if err != nil { + t.Fatalf("failed to create CSR: %s", err) + } + csr, err := ParseCertificateRequest(csrDER) + if err != nil { + t.Fatalf("failed to parse CSR: %s", err) + } + checkAI(t, csr.RawSignatureAlgorithm, oidSignatureECDSAWithSHA256) + }) + + t.Run("RevocationList", func(t *testing.T) { + issuerTmpl := &Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "issuer"}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: KeyUsageCRLSign, + IsCA: true, + BasicConstraintsValid: true, + } + issuerDER, err := CreateCertificate(rand.Reader, issuerTmpl, issuerTmpl, priv.Public(), priv) + if err != nil { + t.Fatalf("failed to create issuer: %s", err) + } + issuer, err := ParseCertificate(issuerDER) + if err != nil { + t.Fatalf("failed to parse issuer: %s", err) + } + crlDER, err := CreateRevocationList(rand.Reader, &RevocationList{ + Number: big.NewInt(1), + ThisUpdate: time.Now(), + NextUpdate: time.Now().Add(time.Hour), + }, issuer, priv) + if err != nil { + t.Fatalf("failed to create CRL: %s", err) + } + crl, err := ParseRevocationList(crlDER) + if err != nil { + t.Fatalf("failed to parse CRL: %s", err) + } + checkAI(t, crl.RawSignatureAlgorithm, oidSignatureECDSAWithSHA256) + }) +} + func TestParseCertificateRawEquals(t *testing.T) { p, _ := pem.Decode([]byte(pemCertificate)) cert, err := ParseCertificate(p.Bytes)