Skip to content

Commit 78132a1

Browse files
cpugopherbot
authored andcommitted
crypto/internal/fips140test: add ECDSA ACVP tests
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 <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]> Auto-Submit: Filippo Valsorda <[email protected]> TryBot-Bypass: Filippo Valsorda <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent f99a214 commit 78132a1

File tree

3 files changed

+240
-2
lines changed

3 files changed

+240
-2
lines changed

src/crypto/internal/fips140test/acvp_capabilities.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,10 @@
4242
{"algorithm":"EDDSA","mode":"keyGen","revision":"1.0","curve":["ED-25519"]},
4343
{"algorithm":"EDDSA","mode":"keyVer","revision":"1.0","curve":["ED-25519"]},
4444
{"algorithm":"EDDSA","mode":"sigGen","revision":"1.0","pure":true,"preHash":true,"contextLength":[{"min":0,"max":255,"increment":1}],"curve":["ED-25519"]},
45-
{"algorithm":"EDDSA","mode":"sigVer","revision":"1.0","pure":true,"preHash":true,"curve":["ED-25519"]}
45+
{"algorithm":"EDDSA","mode":"sigVer","revision":"1.0","pure":true,"preHash":true,"curve":["ED-25519"]},
46+
47+
{"algorithm":"ECDSA","mode":"keyGen","revision":"FIPS186-5","curve":["P-224","P-256","P-384","P-521"],"secretGenerationMode":["testing candidates"]},
48+
{"algorithm":"ECDSA","mode":"keyVer","revision":"FIPS186-5","curve":["P-224","P-256","P-384","P-521"]},
49+
{"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"]}]},
50+
{"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"]}]}
4651
]

src/crypto/internal/fips140test/acvp_test.config.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@
2929

3030
{"Wrapper": "go", "In": "vectors/hmacDRBG.bz2", "Out": "expected/hmacDRBG.bz2"},
3131

32-
{"Wrapper": "go", "In": "vectors/EDDSA.bz2", "Out": "expected/EDDSA.bz2"}
32+
{"Wrapper": "go", "In": "vectors/EDDSA.bz2", "Out": "expected/EDDSA.bz2"},
33+
34+
{"Wrapper": "go", "In": "vectors/ECDSA.bz2", "Out": "expected/ECDSA.bz2"}
3335
]

src/crypto/internal/fips140test/acvp_test.go

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package fipstest
2121
import (
2222
"bufio"
2323
"bytes"
24+
"crypto/elliptic"
2425
"crypto/internal/cryptotest"
2526
"crypto/internal/fips140"
2627
"crypto/internal/fips140/ecdsa"
@@ -32,12 +33,14 @@ import (
3233
"crypto/internal/fips140/sha256"
3334
"crypto/internal/fips140/sha3"
3435
"crypto/internal/fips140/sha512"
36+
"crypto/rand"
3537
_ "embed"
3638
"encoding/binary"
3739
"errors"
3840
"fmt"
3941
"internal/testenv"
4042
"io"
43+
"math/big"
4144
"os"
4245
"path/filepath"
4346
"strings"
@@ -85,6 +88,8 @@ var (
8588
// https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#section-7.2
8689
// EDDSA algorithm capabilities:
8790
// https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#section-7
91+
// ECDSA algorithm capabilities:
92+
// https://pages.nist.gov/ACVP/draft-fussell-acvp-ecdsa.html#section-7
8893
//go:embed acvp_capabilities.json
8994
capabilitiesJson []byte
9095

@@ -151,6 +156,11 @@ var (
151156
"EDDSA/keyVer": cmdEddsaKeyVerAft(),
152157
"EDDSA/sigGen": cmdEddsaSigGenAftBft(),
153158
"EDDSA/sigVer": cmdEddsaSigVerAft(),
159+
160+
"ECDSA/keyGen": cmdEcdsaKeyGenAft(),
161+
"ECDSA/keyVer": cmdEcdsaKeyVerAft(),
162+
"ECDSA/sigGen": cmdEcdsaSigGenAft(),
163+
"ECDSA/sigVer": cmdEcdsaSigVerAft(),
154164
}
155165
)
156166

@@ -526,6 +536,208 @@ func cmdEddsaSigVerAft() command {
526536
}
527537
}
528538

539+
func cmdEcdsaKeyGenAft() command {
540+
return command{
541+
requiredArgs: 1, // Curve name
542+
handler: func(args [][]byte) ([][]byte, error) {
543+
curve, err := lookupCurve(string(args[0]))
544+
if err != nil {
545+
return nil, err
546+
}
547+
548+
var sk *ecdsa.PrivateKey
549+
switch curve.Params() {
550+
case elliptic.P224().Params():
551+
sk, err = ecdsa.GenerateKey(ecdsa.P224(), rand.Reader)
552+
case elliptic.P256().Params():
553+
sk, err = ecdsa.GenerateKey(ecdsa.P256(), rand.Reader)
554+
case elliptic.P384().Params():
555+
sk, err = ecdsa.GenerateKey(ecdsa.P384(), rand.Reader)
556+
case elliptic.P521().Params():
557+
sk, err = ecdsa.GenerateKey(ecdsa.P521(), rand.Reader)
558+
default:
559+
return nil, fmt.Errorf("unsupported curve: %v", curve)
560+
}
561+
562+
if err != nil {
563+
return nil, err
564+
}
565+
566+
pubBytes := sk.PublicKey().Bytes()
567+
byteLen := (curve.Params().BitSize + 7) / 8
568+
569+
return [][]byte{
570+
sk.Bytes(),
571+
pubBytes[1 : 1+byteLen],
572+
pubBytes[1+byteLen:],
573+
}, nil
574+
},
575+
}
576+
}
577+
578+
func cmdEcdsaKeyVerAft() command {
579+
return command{
580+
requiredArgs: 3, // Curve name, X, Y
581+
handler: func(args [][]byte) ([][]byte, error) {
582+
curve, err := lookupCurve(string(args[0]))
583+
if err != nil {
584+
return nil, err
585+
}
586+
587+
x := new(big.Int).SetBytes(args[1])
588+
y := new(big.Int).SetBytes(args[2])
589+
590+
if curve.IsOnCurve(x, y) {
591+
return [][]byte{{1}}, nil
592+
}
593+
594+
return [][]byte{{0}}, nil
595+
},
596+
}
597+
}
598+
599+
// pointFromAffine is used to convert the PublicKey to a nistec SetBytes input.
600+
// Duplicated from crypto/ecdsa.go's pointFromAffine.
601+
func pointFromAffine(curve elliptic.Curve, x, y *big.Int) ([]byte, error) {
602+
bitSize := curve.Params().BitSize
603+
// Reject values that would not get correctly encoded.
604+
if x.Sign() < 0 || y.Sign() < 0 {
605+
return nil, errors.New("negative coordinate")
606+
}
607+
if x.BitLen() > bitSize || y.BitLen() > bitSize {
608+
return nil, errors.New("overflowing coordinate")
609+
}
610+
// Encode the coordinates and let SetBytes reject invalid points.
611+
byteLen := (bitSize + 7) / 8
612+
buf := make([]byte, 1+2*byteLen)
613+
buf[0] = 4 // uncompressed point
614+
x.FillBytes(buf[1 : 1+byteLen])
615+
y.FillBytes(buf[1+byteLen : 1+2*byteLen])
616+
return buf, nil
617+
}
618+
619+
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) {
620+
priv, err := ecdsa.NewPrivateKey(c, sk, q)
621+
if err != nil {
622+
return nil, fmt.Errorf("invalid private key: %w", err)
623+
}
624+
625+
sig, err := ecdsa.Sign(c, h, priv, rand.Reader, digest)
626+
if err != nil {
627+
return nil, fmt.Errorf("signing failed: %w", err)
628+
}
629+
630+
return sig, nil
631+
}
632+
633+
func cmdEcdsaSigGenAft() command {
634+
return command{
635+
requiredArgs: 4, // Curve name, private key, hash name, message
636+
handler: func(args [][]byte) ([][]byte, error) {
637+
curve, err := lookupCurve(string(args[0]))
638+
if err != nil {
639+
return nil, err
640+
}
641+
642+
sk := args[1]
643+
644+
newH, err := lookupHash(string(args[2]))
645+
if err != nil {
646+
return nil, err
647+
}
648+
649+
msg := args[3]
650+
hashFunc := newH()
651+
hashFunc.Write(msg)
652+
digest := hashFunc.Sum(nil)
653+
654+
d := new(big.Int).SetBytes(sk)
655+
x, y := curve.ScalarBaseMult(d.Bytes())
656+
q, err := pointFromAffine(curve, x, y)
657+
if err != nil {
658+
return nil, err
659+
}
660+
661+
var sig *ecdsa.Signature
662+
switch curve.Params() {
663+
case elliptic.P224().Params():
664+
sig, err = signEcdsa(ecdsa.P224(), newH, q, sk, digest)
665+
case elliptic.P256().Params():
666+
sig, err = signEcdsa(ecdsa.P256(), newH, q, sk, digest)
667+
case elliptic.P384().Params():
668+
sig, err = signEcdsa(ecdsa.P384(), newH, q, sk, digest)
669+
case elliptic.P521().Params():
670+
sig, err = signEcdsa(ecdsa.P521(), newH, q, sk, digest)
671+
default:
672+
return nil, fmt.Errorf("unsupported curve: %v", curve)
673+
}
674+
if err != nil {
675+
return nil, err
676+
}
677+
678+
return [][]byte{sig.R, sig.S}, nil
679+
},
680+
}
681+
}
682+
683+
func cmdEcdsaSigVerAft() command {
684+
return command{
685+
requiredArgs: 7, // Curve name, hash name, message, X, Y, R, S
686+
handler: func(args [][]byte) ([][]byte, error) {
687+
curve, err := lookupCurve(string(args[0]))
688+
if err != nil {
689+
return nil, err
690+
}
691+
692+
newH, err := lookupHash(string(args[1]))
693+
if err != nil {
694+
return nil, err
695+
}
696+
697+
msg := args[2]
698+
hashFunc := newH()
699+
hashFunc.Write(msg)
700+
digest := hashFunc.Sum(nil)
701+
702+
x, y := args[3], args[4]
703+
q, err := pointFromAffine(curve, new(big.Int).SetBytes(x), new(big.Int).SetBytes(y))
704+
if err != nil {
705+
return nil, fmt.Errorf("invalid x/y coordinates: %v", err)
706+
}
707+
708+
signature := &ecdsa.Signature{R: args[5], S: args[6]}
709+
710+
switch curve.Params() {
711+
case elliptic.P224().Params():
712+
err = verifyEcdsa(ecdsa.P224(), q, digest, signature)
713+
case elliptic.P256().Params():
714+
err = verifyEcdsa(ecdsa.P256(), q, digest, signature)
715+
case elliptic.P384().Params():
716+
err = verifyEcdsa(ecdsa.P384(), q, digest, signature)
717+
case elliptic.P521().Params():
718+
err = verifyEcdsa(ecdsa.P521(), q, digest, signature)
719+
default:
720+
return nil, fmt.Errorf("unsupported curve: %v", curve)
721+
}
722+
723+
if err == nil {
724+
return [][]byte{{1}}, nil
725+
}
726+
727+
return [][]byte{{0}}, nil
728+
},
729+
}
730+
}
731+
732+
func verifyEcdsa[P ecdsa.Point[P]](c *ecdsa.Curve[P], q []byte, digest []byte, sig *ecdsa.Signature) error {
733+
pub, err := ecdsa.NewPublicKey(c, q)
734+
if err != nil {
735+
return fmt.Errorf("invalid public key: %w", err)
736+
}
737+
738+
return ecdsa.Verify(c, pub, digest, sig)
739+
}
740+
529741
func lookupHash(name string) (func() fips140.Hash, error) {
530742
var h func() fips140.Hash
531743

@@ -714,6 +926,25 @@ func cmdHmacDrbgAft(h func() fips140.Hash) command {
714926
}
715927
}
716928

929+
func lookupCurve(name string) (elliptic.Curve, error) {
930+
var c elliptic.Curve
931+
932+
switch name {
933+
case "P-224":
934+
c = elliptic.P224()
935+
case "P-256":
936+
c = elliptic.P256()
937+
case "P-384":
938+
c = elliptic.P384()
939+
case "P-521":
940+
c = elliptic.P521()
941+
default:
942+
return nil, fmt.Errorf("unknown curve name: %q", name)
943+
}
944+
945+
return c, nil
946+
}
947+
717948
func TestACVP(t *testing.T) {
718949
testenv.SkipIfShortAndSlow(t)
719950

0 commit comments

Comments
 (0)