Skip to content

Commit fa2ee19

Browse files
committed
x509: Add VerifyOptions.UnknownAlgorithmVerifier
This allows callers to verify certificates using algorithms that Go does not support (yet). For instance, here we're verifying the ML-KEM-512 example certificate from the LAMPS WG signed by a ML-DSA-44 public key. https://github.com/lamps-wg/dilithium-certificates/blob/main/examples/ML-DSA-44.crt https://github.com/lamps-wg/kyber-certificates/blob/main/example/ML-KEM-512.crt package main import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "errors" "fmt" "github.com/cloudflare/circl/sign/schemes" "os" ) func loadCert(path string) (*x509.Certificate, error) { raw, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("ReadFile(%s): %w", path, err) } block, _ := pem.Decode(raw) if block == nil { return nil, fmt.Errorf("pem.Decode(%s) failed", path) } return x509.ParseCertificate(block.Bytes) } func main() { dsaCert, err := loadCert("ML-DSA-44.crt") if err != nil { panic(err) } kemCert, err := loadCert("ML-KEM-512.crt") if err != nil { panic(err) } roots := x509.NewCertPool() roots.AddCert(dsaCert) _, err = kemCert.Verify(x509.VerifyOptions{ Roots: roots, UnknownAlgorithmVerifier: func(alg pkix.AlgorithmIdentifier, signed, signature, pk []byte) error { if !alg.Algorithm.Equal(asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 17}) { return errors.New("unsupported scheme") } scheme := schemes.ByName("ML-DSA-44") ppk, err := scheme.UnmarshalBinaryPublicKey(pk) if err != nil { return err } if !scheme.Verify(ppk, signed, signature, nil) { return errors.New("invalid signature") } return nil }, }) if err != nil { panic(err) } }
1 parent 3432c68 commit fa2ee19

File tree

4 files changed

+49
-17
lines changed

4 files changed

+49
-17
lines changed

src/crypto/x509/parser.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,11 +1038,14 @@ func parseCertificate(der []byte) (*Certificate, error) {
10381038
if !spki.ReadASN1BitString(&spk) {
10391039
return nil, errors.New("x509: malformed subjectPublicKey")
10401040
}
1041-
if cert.PublicKeyAlgorithm != UnknownPublicKeyAlgorithm {
1042-
cert.PublicKey, err = parsePublicKey(&publicKeyInfo{
1043-
Algorithm: pkAI,
1044-
PublicKey: spk,
1045-
})
1041+
pki := &publicKeyInfo{
1042+
Algorithm: pkAI,
1043+
PublicKey: spk,
1044+
}
1045+
if cert.PublicKeyAlgorithm == UnknownPublicKeyAlgorithm {
1046+
cert.PublicKey = pki
1047+
} else {
1048+
cert.PublicKey, err = parsePublicKey(pki)
10461049
if err != nil {
10471050
return nil, err
10481051
}

src/crypto/x509/root_windows.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ func verifyChain(c *Certificate, chainCtx *syscall.CertChainContext, opts *Verif
190190
if parent.PublicKeyAlgorithm != ECDSA {
191191
continue
192192
}
193-
if err := parent.CheckSignature(chain[i].SignatureAlgorithm,
194-
chain[i].RawTBSCertificate, chain[i].Signature); err != nil {
193+
if err := checkSignature(chain[i].SignatureAlgorithm,
194+
chain[i].RawTBSCertificate, chain[i].Signature, parent.PublicKey, true, opts); err != nil {
195195
return nil, err
196196
}
197197
}

src/crypto/x509/verify.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,10 @@ type VerifyOptions struct {
218218
// field implies any valid policy is acceptable.
219219
CertificatePolicies []OID
220220

221+
// UnknownAlgorithmVerifier specifies a callback to use to verify
222+
// a signature with an unknown AlgorithmIdentifier.
223+
UnknownAlgorithmVerifier func(alg pkix.AlgorithmIdentifier, signed, signature, pk []byte) error
224+
221225
// The following policy fields are unexported, because we do not expect
222226
// users to actually need to use them, but are useful for testing the
223227
// policy validation code.
@@ -975,7 +979,7 @@ func (c *Certificate) buildChains(currentChain []*Certificate, sigChecks *int, o
975979
return
976980
}
977981

978-
if err := c.CheckSignatureFrom(candidate.cert); err != nil {
982+
if err := c.checkSignatureFrom(candidate.cert, opts); err != nil {
979983
if hintErr == nil {
980984
hintErr = err
981985
hintCert = candidate.cert

src/crypto/x509/x509.go

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,17 @@ type publicKeyInfo struct {
205205
PublicKey asn1.BitString
206206
}
207207

208+
func (pki *publicKeyInfo) Equal(other crypto.PublicKey) bool {
209+
pki2, ok := other.(*publicKeyInfo)
210+
if !ok {
211+
return false
212+
}
213+
return (pki.Algorithm.Algorithm.Equal(pki2.Algorithm.Algorithm) &&
214+
bytes.Equal(pki.Algorithm.Parameters.FullBytes, pki2.Algorithm.Parameters.FullBytes) &&
215+
pki.PublicKey.BitLength == pki2.PublicKey.BitLength &&
216+
bytes.Equal(pki.PublicKey.Bytes, pki2.PublicKey.Bytes))
217+
}
218+
208219
// RFC 5280, 4.2.1.1
209220
type authKeyId struct {
210221
Id []byte `asn1:"optional,tag:0"`
@@ -909,6 +920,10 @@ func (c *Certificate) hasSANExtension() bool {
909920
// This is a low-level API that performs very limited checks, and not a full
910921
// path verifier. Most users should use [Certificate.Verify] instead.
911922
func (c *Certificate) CheckSignatureFrom(parent *Certificate) error {
923+
return c.checkSignatureFrom(parent, nil)
924+
}
925+
926+
func (c *Certificate) checkSignatureFrom(parent *Certificate, opts *VerifyOptions) error {
912927
// RFC 5280, 4.2.1.9:
913928
// "If the basic constraints extension is not present in a version 3
914929
// certificate, or the extension is present but the cA boolean is not
@@ -923,11 +938,7 @@ func (c *Certificate) CheckSignatureFrom(parent *Certificate) error {
923938
return ConstraintViolationError{}
924939
}
925940

926-
if parent.PublicKeyAlgorithm == UnknownPublicKeyAlgorithm {
927-
return ErrUnsupportedAlgorithm
928-
}
929-
930-
return checkSignature(c.SignatureAlgorithm, c.RawTBSCertificate, c.Signature, parent.PublicKey, false)
941+
return checkSignature(c.SignatureAlgorithm, c.RawTBSCertificate, c.Signature, parent.PublicKey, false, opts)
931942
}
932943

933944
// CheckSignature verifies that signature is a valid signature over signed from
@@ -938,7 +949,7 @@ func (c *Certificate) CheckSignatureFrom(parent *Certificate) error {
938949
// [MD5WithRSA] signatures are rejected, while [SHA1WithRSA] and [ECDSAWithSHA1]
939950
// signatures are currently accepted.
940951
func (c *Certificate) CheckSignature(algo SignatureAlgorithm, signed, signature []byte) error {
941-
return checkSignature(algo, signed, signature, c.PublicKey, true)
952+
return checkSignature(algo, signed, signature, c.PublicKey, true, nil)
942953
}
943954

944955
func (c *Certificate) hasNameConstraints() bool {
@@ -960,10 +971,24 @@ func signaturePublicKeyAlgoMismatchError(expectedPubKeyAlgo PublicKeyAlgorithm,
960971

961972
// checkSignature verifies that signature is a valid signature over signed from
962973
// a crypto.PublicKey.
963-
func checkSignature(algo SignatureAlgorithm, signed, signature []byte, publicKey crypto.PublicKey, allowSHA1 bool) (err error) {
974+
func checkSignature(algo SignatureAlgorithm, signed, signature []byte, publicKey crypto.PublicKey, allowSHA1 bool, opts *VerifyOptions) (err error) {
964975
var hashType crypto.Hash
965976
var pubKeyAlgo PublicKeyAlgorithm
966977

978+
if algo == UnknownSignatureAlgorithm {
979+
pki, ok := publicKey.(*publicKeyInfo)
980+
if !ok || opts == nil || opts.UnknownAlgorithmVerifier == nil {
981+
return ErrUnsupportedAlgorithm
982+
}
983+
984+
return opts.UnknownAlgorithmVerifier(
985+
pki.Algorithm,
986+
signed,
987+
signature,
988+
pki.PublicKey.Bytes,
989+
)
990+
}
991+
967992
for _, details := range signatureAlgorithmDetails {
968993
if details.algo == algo {
969994
hashType = details.hash
@@ -1585,7 +1610,7 @@ func signTBS(tbs []byte, key crypto.Signer, sigAlg SignatureAlgorithm, rand io.R
15851610
}
15861611

15871612
// Check the signature to ensure the crypto.Signer behaved correctly.
1588-
if err := checkSignature(sigAlg, tbs, signature, key.Public(), true); err != nil {
1613+
if err := checkSignature(sigAlg, tbs, signature, key.Public(), true, nil); err != nil {
15891614
return nil, fmt.Errorf("x509: signature returned by signer is invalid: %w", err)
15901615
}
15911616

@@ -2259,7 +2284,7 @@ func parseCertificateRequest(in *certificateRequest) (*CertificateRequest, error
22592284

22602285
// CheckSignature reports whether the signature on c is valid.
22612286
func (c *CertificateRequest) CheckSignature() error {
2262-
return checkSignature(c.SignatureAlgorithm, c.RawTBSCertificateRequest, c.Signature, c.PublicKey, true)
2287+
return checkSignature(c.SignatureAlgorithm, c.RawTBSCertificateRequest, c.Signature, c.PublicKey, true, nil)
22632288
}
22642289

22652290
// RevocationListEntry represents an entry in the revokedCertificates

0 commit comments

Comments
 (0)