]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/internal/fips140test: add ECDSA ACVP tests
authorDaniel McCarney <daniel@binaryparadox.net>
Thu, 14 Nov 2024 20:54:54 +0000 (15:54 -0500)
committerGopher Robot <gobot@golang.org>
Fri, 7 Feb 2025 16:57:34 +0000 (08:57 -0800)
This commit adds ACVP test coverage for the non-deterministic ECDSA
vectors (keyGen, keyVer, sigGen, sigVer) based on the NIST spec:

  https://pages.nist.gov/ACVP/draft-fussell-acvp-ecdsa.html

Updates #69642

Change-Id: Iec8b18a247b0a652d13f9167a78de2cb74f4dfd0
Reviewed-on: https://go-review.googlesource.com/c/go/+/620935
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
TryBot-Bypass: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
src/crypto/internal/fips140test/acvp_capabilities.json
src/crypto/internal/fips140test/acvp_test.config.json
src/crypto/internal/fips140test/acvp_test.go

index 368c7809deb980964e49f6d6e4a7988051274d79..47ae58e9e0701722b7ccc5bd4e272b23464f0037 100644 (file)
   {"algorithm":"EDDSA","mode":"keyGen","revision":"1.0","curve":["ED-25519"]},
   {"algorithm":"EDDSA","mode":"keyVer","revision":"1.0","curve":["ED-25519"]},
   {"algorithm":"EDDSA","mode":"sigGen","revision":"1.0","pure":true,"preHash":true,"contextLength":[{"min":0,"max":255,"increment":1}],"curve":["ED-25519"]},
-  {"algorithm":"EDDSA","mode":"sigVer","revision":"1.0","pure":true,"preHash":true,"curve":["ED-25519"]}
+  {"algorithm":"EDDSA","mode":"sigVer","revision":"1.0","pure":true,"preHash":true,"curve":["ED-25519"]},
+
+  {"algorithm":"ECDSA","mode":"keyGen","revision":"FIPS186-5","curve":["P-224","P-256","P-384","P-521"],"secretGenerationMode":["testing candidates"]},
+  {"algorithm":"ECDSA","mode":"keyVer","revision":"FIPS186-5","curve":["P-224","P-256","P-384","P-521"]},
+  {"algorithm":"ECDSA","mode":"sigGen","revision":"FIPS186-5","capabilities":[{"curve":["P-224","P-256","P-384","P-521"],"hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}]},
+  {"algorithm":"ECDSA","mode":"sigVer","revision":"FIPS186-5","capabilities":[{"curve":["P-224","P-256","P-384","P-521"],"hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}]}
 ]
index 2afd457f46ce90ec1458807f6786a63b79646eca..4c1879380c2c911f4aab2e27ffc419270efb8a3e 100644 (file)
@@ -29,5 +29,7 @@
 
   {"Wrapper": "go", "In": "vectors/hmacDRBG.bz2", "Out": "expected/hmacDRBG.bz2"},
 
-  {"Wrapper": "go", "In": "vectors/EDDSA.bz2", "Out": "expected/EDDSA.bz2"}
+  {"Wrapper": "go", "In": "vectors/EDDSA.bz2", "Out": "expected/EDDSA.bz2"},
+
+  {"Wrapper": "go", "In": "vectors/ECDSA.bz2", "Out": "expected/ECDSA.bz2"}
 ]
\ No newline at end of file
index b160f60d17ccccad508058bda329bd30d190233a..ae0009c938e4b0018d65d57c68970a6b44186b22 100644 (file)
@@ -21,6 +21,7 @@ package fipstest
 import (
        "bufio"
        "bytes"
+       "crypto/elliptic"
        "crypto/internal/cryptotest"
        "crypto/internal/fips140"
        "crypto/internal/fips140/ecdsa"
@@ -32,12 +33,14 @@ import (
        "crypto/internal/fips140/sha256"
        "crypto/internal/fips140/sha3"
        "crypto/internal/fips140/sha512"
+       "crypto/rand"
        _ "embed"
        "encoding/binary"
        "errors"
        "fmt"
        "internal/testenv"
        "io"
+       "math/big"
        "os"
        "path/filepath"
        "strings"
@@ -85,6 +88,8 @@ var (
        //   https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#section-7.2
        // EDDSA algorithm capabilities:
        //   https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#section-7
+       // ECDSA algorithm capabilities:
+       //   https://pages.nist.gov/ACVP/draft-fussell-acvp-ecdsa.html#section-7
        //go:embed acvp_capabilities.json
        capabilitiesJson []byte
 
@@ -151,6 +156,11 @@ var (
                "EDDSA/keyVer": cmdEddsaKeyVerAft(),
                "EDDSA/sigGen": cmdEddsaSigGenAftBft(),
                "EDDSA/sigVer": cmdEddsaSigVerAft(),
+
+               "ECDSA/keyGen": cmdEcdsaKeyGenAft(),
+               "ECDSA/keyVer": cmdEcdsaKeyVerAft(),
+               "ECDSA/sigGen": cmdEcdsaSigGenAft(),
+               "ECDSA/sigVer": cmdEcdsaSigVerAft(),
        }
 )
 
@@ -526,6 +536,208 @@ func cmdEddsaSigVerAft() command {
        }
 }
 
+func cmdEcdsaKeyGenAft() command {
+       return command{
+               requiredArgs: 1, // Curve name
+               handler: func(args [][]byte) ([][]byte, error) {
+                       curve, err := lookupCurve(string(args[0]))
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       var sk *ecdsa.PrivateKey
+                       switch curve.Params() {
+                       case elliptic.P224().Params():
+                               sk, err = ecdsa.GenerateKey(ecdsa.P224(), rand.Reader)
+                       case elliptic.P256().Params():
+                               sk, err = ecdsa.GenerateKey(ecdsa.P256(), rand.Reader)
+                       case elliptic.P384().Params():
+                               sk, err = ecdsa.GenerateKey(ecdsa.P384(), rand.Reader)
+                       case elliptic.P521().Params():
+                               sk, err = ecdsa.GenerateKey(ecdsa.P521(), rand.Reader)
+                       default:
+                               return nil, fmt.Errorf("unsupported curve: %v", curve)
+                       }
+
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       pubBytes := sk.PublicKey().Bytes()
+                       byteLen := (curve.Params().BitSize + 7) / 8
+
+                       return [][]byte{
+                               sk.Bytes(),
+                               pubBytes[1 : 1+byteLen],
+                               pubBytes[1+byteLen:],
+                       }, nil
+               },
+       }
+}
+
+func cmdEcdsaKeyVerAft() command {
+       return command{
+               requiredArgs: 3, // Curve name, X, Y
+               handler: func(args [][]byte) ([][]byte, error) {
+                       curve, err := lookupCurve(string(args[0]))
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       x := new(big.Int).SetBytes(args[1])
+                       y := new(big.Int).SetBytes(args[2])
+
+                       if curve.IsOnCurve(x, y) {
+                               return [][]byte{{1}}, nil
+                       }
+
+                       return [][]byte{{0}}, nil
+               },
+       }
+}
+
+// pointFromAffine is used to convert the PublicKey to a nistec SetBytes input.
+// Duplicated from crypto/ecdsa.go's pointFromAffine.
+func pointFromAffine(curve elliptic.Curve, x, y *big.Int) ([]byte, error) {
+       bitSize := curve.Params().BitSize
+       // Reject values that would not get correctly encoded.
+       if x.Sign() < 0 || y.Sign() < 0 {
+               return nil, errors.New("negative coordinate")
+       }
+       if x.BitLen() > bitSize || y.BitLen() > bitSize {
+               return nil, errors.New("overflowing coordinate")
+       }
+       // Encode the coordinates and let SetBytes reject invalid points.
+       byteLen := (bitSize + 7) / 8
+       buf := make([]byte, 1+2*byteLen)
+       buf[0] = 4 // uncompressed point
+       x.FillBytes(buf[1 : 1+byteLen])
+       y.FillBytes(buf[1+byteLen : 1+2*byteLen])
+       return buf, nil
+}
+
+func signEcdsa[P ecdsa.Point[P], H fips140.Hash](c *ecdsa.Curve[P], h func() H, q []byte, sk []byte, digest []byte) (*ecdsa.Signature, error) {
+       priv, err := ecdsa.NewPrivateKey(c, sk, q)
+       if err != nil {
+               return nil, fmt.Errorf("invalid private key: %w", err)
+       }
+
+       sig, err := ecdsa.Sign(c, h, priv, rand.Reader, digest)
+       if err != nil {
+               return nil, fmt.Errorf("signing failed: %w", err)
+       }
+
+       return sig, nil
+}
+
+func cmdEcdsaSigGenAft() command {
+       return command{
+               requiredArgs: 4, // Curve name, private key, hash name, message
+               handler: func(args [][]byte) ([][]byte, error) {
+                       curve, err := lookupCurve(string(args[0]))
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       sk := args[1]
+
+                       newH, err := lookupHash(string(args[2]))
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       msg := args[3]
+                       hashFunc := newH()
+                       hashFunc.Write(msg)
+                       digest := hashFunc.Sum(nil)
+
+                       d := new(big.Int).SetBytes(sk)
+                       x, y := curve.ScalarBaseMult(d.Bytes())
+                       q, err := pointFromAffine(curve, x, y)
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       var sig *ecdsa.Signature
+                       switch curve.Params() {
+                       case elliptic.P224().Params():
+                               sig, err = signEcdsa(ecdsa.P224(), newH, q, sk, digest)
+                       case elliptic.P256().Params():
+                               sig, err = signEcdsa(ecdsa.P256(), newH, q, sk, digest)
+                       case elliptic.P384().Params():
+                               sig, err = signEcdsa(ecdsa.P384(), newH, q, sk, digest)
+                       case elliptic.P521().Params():
+                               sig, err = signEcdsa(ecdsa.P521(), newH, q, sk, digest)
+                       default:
+                               return nil, fmt.Errorf("unsupported curve: %v", curve)
+                       }
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       return [][]byte{sig.R, sig.S}, nil
+               },
+       }
+}
+
+func cmdEcdsaSigVerAft() command {
+       return command{
+               requiredArgs: 7, // Curve name, hash name, message, X, Y, R, S
+               handler: func(args [][]byte) ([][]byte, error) {
+                       curve, err := lookupCurve(string(args[0]))
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       newH, err := lookupHash(string(args[1]))
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       msg := args[2]
+                       hashFunc := newH()
+                       hashFunc.Write(msg)
+                       digest := hashFunc.Sum(nil)
+
+                       x, y := args[3], args[4]
+                       q, err := pointFromAffine(curve, new(big.Int).SetBytes(x), new(big.Int).SetBytes(y))
+                       if err != nil {
+                               return nil, fmt.Errorf("invalid x/y coordinates: %v", err)
+                       }
+
+                       signature := &ecdsa.Signature{R: args[5], S: args[6]}
+
+                       switch curve.Params() {
+                       case elliptic.P224().Params():
+                               err = verifyEcdsa(ecdsa.P224(), q, digest, signature)
+                       case elliptic.P256().Params():
+                               err = verifyEcdsa(ecdsa.P256(), q, digest, signature)
+                       case elliptic.P384().Params():
+                               err = verifyEcdsa(ecdsa.P384(), q, digest, signature)
+                       case elliptic.P521().Params():
+                               err = verifyEcdsa(ecdsa.P521(), q, digest, signature)
+                       default:
+                               return nil, fmt.Errorf("unsupported curve: %v", curve)
+                       }
+
+                       if err == nil {
+                               return [][]byte{{1}}, nil
+                       }
+
+                       return [][]byte{{0}}, nil
+               },
+       }
+}
+
+func verifyEcdsa[P ecdsa.Point[P]](c *ecdsa.Curve[P], q []byte, digest []byte, sig *ecdsa.Signature) error {
+       pub, err := ecdsa.NewPublicKey(c, q)
+       if err != nil {
+               return fmt.Errorf("invalid public key: %w", err)
+       }
+
+       return ecdsa.Verify(c, pub, digest, sig)
+}
+
 func lookupHash(name string) (func() fips140.Hash, error) {
        var h func() fips140.Hash
 
@@ -714,6 +926,25 @@ func cmdHmacDrbgAft(h func() fips140.Hash) command {
        }
 }
 
+func lookupCurve(name string) (elliptic.Curve, error) {
+       var c elliptic.Curve
+
+       switch name {
+       case "P-224":
+               c = elliptic.P224()
+       case "P-256":
+               c = elliptic.P256()
+       case "P-384":
+               c = elliptic.P384()
+       case "P-521":
+               c = elliptic.P521()
+       default:
+               return nil, fmt.Errorf("unknown curve name: %q", name)
+       }
+
+       return c, nil
+}
+
 func TestACVP(t *testing.T) {
        testenv.SkipIfShortAndSlow(t)