internal/wycheproof: add crypto/ecdh tests
Alongside the existing ECDH tests, add tests that use the new
crypto/ecdh package. The test vectors include a number of private
that use non-standard sizes, which we reject, but aren't flagged,
so we need to skip them.
Change-Id: Iaaef225b0149a86833095f51748d230385d43bfe
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/424274
Reviewed-by: Russ Cox <rsc@golang.org>
Run-TryBot: Roland Shoemaker <roland@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Roland Shoemaker <roland@golang.org>
diff --git a/internal/wycheproof/ecdh_stdlib_test.go b/internal/wycheproof/ecdh_stdlib_test.go
new file mode 100644
index 0000000..44315fc
--- /dev/null
+++ b/internal/wycheproof/ecdh_stdlib_test.go
@@ -0,0 +1,142 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.20
+
+package wycheproof
+
+import (
+ "bytes"
+ "crypto/ecdh"
+ "fmt"
+ "testing"
+)
+
+func TestECDHStdLib(t *testing.T) {
+ type ECDHTestVector struct {
+ // A brief description of the test case
+ Comment string `json:"comment,omitempty"`
+ // A list of flags
+ Flags []string `json:"flags,omitempty"`
+ // the private key
+ Private string `json:"private,omitempty"`
+ // Encoded public key
+ Public string `json:"public,omitempty"`
+ // Test result
+ Result string `json:"result,omitempty"`
+ // The shared secret key
+ Shared string `json:"shared,omitempty"`
+ // Identifier of the test case
+ TcID int `json:"tcId,omitempty"`
+ }
+
+ type ECDHTestGroup struct {
+ Curve string `json:"curve,omitempty"`
+ Tests []*ECDHTestVector `json:"tests,omitempty"`
+ }
+
+ type Root struct {
+ TestGroups []*ECDHTestGroup `json:"testGroups,omitempty"`
+ }
+
+ flagsShouldPass := map[string]bool{
+ // We don't support compressed points.
+ "CompressedPoint": false,
+ // We don't support decoding custom curves.
+ "UnnamedCurve": false,
+ // WrongOrder and UnusedParam are only found with UnnamedCurve.
+ "WrongOrder": false,
+ "UnusedParam": false,
+
+ // X25519 specific flags
+ "Twist": true,
+ "SmallPublicKey": false,
+ "LowOrderPublic": false,
+ "ZeroSharedSecret": false,
+ "NonCanonicalPublic": true,
+ }
+
+ // curveToCurve is a map of all elliptic curves supported
+ // by crypto/elliptic, which can subsequently be parsed and tested.
+ curveToCurve := map[string]ecdh.Curve{
+ "secp256r1": ecdh.P256(),
+ "secp384r1": ecdh.P384(),
+ "secp521r1": ecdh.P521(),
+ "curve25519": ecdh.X25519(),
+ }
+
+ curveToKeySize := map[string]int{
+ "secp256r1": 32,
+ "secp384r1": 48,
+ "secp521r1": 66,
+ "curve25519": 32,
+ }
+
+ for _, f := range []string{
+ "ecdh_secp256r1_ecpoint_test.json",
+ "ecdh_secp384r1_ecpoint_test.json",
+ "ecdh_secp521r1_ecpoint_test.json",
+ "x25519_test.json",
+ } {
+ var root Root
+ readTestVector(t, f, &root)
+ for _, tg := range root.TestGroups {
+ if _, ok := curveToCurve[tg.Curve]; !ok {
+ continue
+ }
+ for _, tt := range tg.Tests {
+ tg, tt := tg, tt
+ t.Run(fmt.Sprintf("%s/%d", tg.Curve, tt.TcID), func(t *testing.T) {
+ t.Logf("Type: %v", tt.Result)
+ t.Logf("Flags: %q", tt.Flags)
+ t.Log(tt.Comment)
+
+ shouldPass := shouldPass(tt.Result, tt.Flags, flagsShouldPass)
+
+ curve := curveToCurve[tg.Curve]
+ p := decodeHex(tt.Public)
+ pub, err := curve.NewPublicKey(p)
+ if err != nil {
+ if shouldPass {
+ t.Errorf("NewPublicKey: %v", err)
+ }
+ return
+ }
+
+ privBytes := decodeHex(tt.Private)
+ if len(privBytes) != curveToKeySize[tg.Curve] {
+ t.Skipf("non-standard key size %d", len(privBytes))
+ }
+
+ priv, err := curve.NewPrivateKey(privBytes)
+ if err != nil {
+ if shouldPass {
+ t.Errorf("NewPrivateKey: %v", err)
+ }
+ return
+ }
+
+ shared := decodeHex(tt.Shared)
+ x, err := curve.ECDH(priv, pub)
+ if err != nil {
+ if tg.Curve == "curve25519" && !shouldPass {
+ // ECDH is expected to only return an error when using X25519,
+ // in all other cases an error is unexpected.
+ return
+ }
+ t.Fatalf("ECDH: %v", err)
+ }
+
+ if bytes.Equal(shared, x) != shouldPass {
+ if shouldPass {
+ t.Errorf("ECDH = %x, want %x", shared, x)
+ } else {
+ t.Errorf("ECDH = %x, want anything else", shared)
+ }
+ }
+ })
+ }
+ }
+ }
+}