github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/crypto/ecdh/ecdh_test.go (about)

     1  // Copyright 2022 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ecdh_test
     6  
     7  import (
     8  	"bytes"
     9  	"crypto"
    10  	"crypto/cipher"
    11  	"crypto/ecdh"
    12  	"crypto/rand"
    13  	"encoding/hex"
    14  	"fmt"
    15  	"internal/testenv"
    16  	"io"
    17  	"os"
    18  	"os/exec"
    19  	"path/filepath"
    20  	"regexp"
    21  	"strings"
    22  	"testing"
    23  
    24  	"golang.org/x/crypto/chacha20"
    25  )
    26  
    27  // Check that PublicKey and PrivateKey implement the interfaces documented in
    28  // crypto.PublicKey and crypto.PrivateKey.
    29  var _ interface {
    30  	Equal(x crypto.PublicKey) bool
    31  } = &ecdh.PublicKey{}
    32  var _ interface {
    33  	Public() crypto.PublicKey
    34  	Equal(x crypto.PrivateKey) bool
    35  } = &ecdh.PrivateKey{}
    36  
    37  func TestECDH(t *testing.T) {
    38  	testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
    39  		aliceKey, err := curve.GenerateKey(rand.Reader)
    40  		if err != nil {
    41  			t.Fatal(err)
    42  		}
    43  		bobKey, err := curve.GenerateKey(rand.Reader)
    44  		if err != nil {
    45  			t.Fatal(err)
    46  		}
    47  
    48  		alicePubKey, err := curve.NewPublicKey(aliceKey.PublicKey().Bytes())
    49  		if err != nil {
    50  			t.Error(err)
    51  		}
    52  		if !bytes.Equal(aliceKey.PublicKey().Bytes(), alicePubKey.Bytes()) {
    53  			t.Error("encoded and decoded public keys are different")
    54  		}
    55  		if !aliceKey.PublicKey().Equal(alicePubKey) {
    56  			t.Error("encoded and decoded public keys are different")
    57  		}
    58  
    59  		alicePrivKey, err := curve.NewPrivateKey(aliceKey.Bytes())
    60  		if err != nil {
    61  			t.Error(err)
    62  		}
    63  		if !bytes.Equal(aliceKey.Bytes(), alicePrivKey.Bytes()) {
    64  			t.Error("encoded and decoded private keys are different")
    65  		}
    66  		if !aliceKey.Equal(alicePrivKey) {
    67  			t.Error("encoded and decoded private keys are different")
    68  		}
    69  
    70  		bobSecret, err := curve.ECDH(bobKey, aliceKey.PublicKey())
    71  		if err != nil {
    72  			t.Fatal(err)
    73  		}
    74  		aliceSecret, err := curve.ECDH(aliceKey, bobKey.PublicKey())
    75  		if err != nil {
    76  			t.Fatal(err)
    77  		}
    78  
    79  		if !bytes.Equal(bobSecret, aliceSecret) {
    80  			t.Error("two ECDH computations came out different")
    81  		}
    82  	})
    83  }
    84  
    85  type countingReader struct {
    86  	r io.Reader
    87  	n int
    88  }
    89  
    90  func (r *countingReader) Read(p []byte) (int, error) {
    91  	n, err := r.r.Read(p)
    92  	r.n += n
    93  	return n, err
    94  }
    95  
    96  func TestGenerateKey(t *testing.T) {
    97  	testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
    98  		r := &countingReader{r: rand.Reader}
    99  		k, err := curve.GenerateKey(r)
   100  		if err != nil {
   101  			t.Fatal(err)
   102  		}
   103  
   104  		// GenerateKey does rejection sampling. If the masking works correctly,
   105  		// the probability of a rejection is 1-ord(G)/2^ceil(log2(ord(G))),
   106  		// which for all curves is small enough (at most 2^-32, for P-256) that
   107  		// a bit flip is more likely to make this test fail than bad luck.
   108  		// Account for the extra MaybeReadByte byte, too.
   109  		if got, expected := r.n, len(k.Bytes())+1; got > expected {
   110  			t.Errorf("expected GenerateKey to consume at most %v bytes, got %v", expected, got)
   111  		}
   112  	})
   113  }
   114  
   115  var vectors = map[ecdh.Curve]struct {
   116  	PrivateKey, PublicKey string
   117  	PeerPublicKey         string
   118  	SharedSecret          string
   119  }{
   120  	// NIST vectors from CAVS 14.1, ECC CDH Primitive (SP800-56A).
   121  	ecdh.P256(): {
   122  		PrivateKey: "7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534",
   123  		PublicKey: "04ead218590119e8876b29146ff89ca61770c4edbbf97d38ce385ed281d8a6b230" +
   124  			"28af61281fd35e2fa7002523acc85a429cb06ee6648325389f59edfce1405141",
   125  		PeerPublicKey: "04700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287" +
   126  			"db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac",
   127  		SharedSecret: "46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b",
   128  	},
   129  	ecdh.P384(): {
   130  		PrivateKey: "3cc3122a68f0d95027ad38c067916ba0eb8c38894d22e1b15618b6818a661774ad463b205da88cf699ab4d43c9cf98a1",
   131  		PublicKey: "049803807f2f6d2fd966cdd0290bd410c0190352fbec7ff6247de1302df86f25d34fe4a97bef60cff548355c015dbb3e5f" +
   132  			"ba26ca69ec2f5b5d9dad20cc9da711383a9dbe34ea3fa5a2af75b46502629ad54dd8b7d73a8abb06a3a3be47d650cc99",
   133  		PeerPublicKey: "04a7c76b970c3b5fe8b05d2838ae04ab47697b9eaf52e764592efda27fe7513272734466b400091adbf2d68c58e0c50066" +
   134  			"ac68f19f2e1cb879aed43a9969b91a0839c4c38a49749b661efedf243451915ed0905a32b060992b468c64766fc8437a",
   135  		SharedSecret: "5f9d29dc5e31a163060356213669c8ce132e22f57c9a04f40ba7fcead493b457e5621e766c40a2e3d4d6a04b25e533f1",
   136  	},
   137  	// For some reason all field elements in the test vector (both scalars and
   138  	// base field elements), but not the shared secret output, have two extra
   139  	// leading zero bytes (which in big-endian are irrelevant). Removed here.
   140  	ecdh.P521(): {
   141  		PrivateKey: "017eecc07ab4b329068fba65e56a1f8890aa935e57134ae0ffcce802735151f4eac6564f6ee9974c5e6887a1fefee5743ae2241bfeb95d5ce31ddcb6f9edb4d6fc47",
   142  		PublicKey: "0400602f9d0cf9e526b29e22381c203c48a886c2b0673033366314f1ffbcba240ba42f4ef38a76174635f91e6b4ed34275eb01c8467d05ca80315bf1a7bbd945f550a5" +
   143  			"01b7c85f26f5d4b2d7355cf6b02117659943762b6d1db5ab4f1dbc44ce7b2946eb6c7de342962893fd387d1b73d7a8672d1f236961170b7eb3579953ee5cdc88cd2d",
   144  		PeerPublicKey: "0400685a48e86c79f0f0875f7bc18d25eb5fc8c0b07e5da4f4370f3a9490340854334b1e1b87fa395464c60626124a4e70d0f785601d37c09870ebf176666877a2046d" +
   145  			"01ba52c56fc8776d9e8f5db4f0cc27636d0b741bbe05400697942e80b739884a83bde99e0f6716939e632bc8986fa18dccd443a348b6c3e522497955a4f3c302f676",
   146  		SharedSecret: "005fc70477c3e63bc3954bd0df3ea0d1f41ee21746ed95fc5e1fdf90930d5e136672d72cc770742d1711c3c3a4c334a0ad9759436a4d3c5bf6e74b9578fac148c831",
   147  	},
   148  	// X25519 test vector from RFC 7748, Section 6.1.
   149  	ecdh.X25519(): {
   150  		PrivateKey:    "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
   151  		PublicKey:     "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a",
   152  		PeerPublicKey: "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f",
   153  		SharedSecret:  "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742",
   154  	},
   155  }
   156  
   157  func TestVectors(t *testing.T) {
   158  	testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
   159  		v := vectors[curve]
   160  		key, err := curve.NewPrivateKey(hexDecode(t, v.PrivateKey))
   161  		if err != nil {
   162  			t.Fatal(err)
   163  		}
   164  		if !bytes.Equal(key.PublicKey().Bytes(), hexDecode(t, v.PublicKey)) {
   165  			t.Error("public key derived from the private key does not match")
   166  		}
   167  		peer, err := curve.NewPublicKey(hexDecode(t, v.PeerPublicKey))
   168  		if err != nil {
   169  			t.Fatal(err)
   170  		}
   171  		secret, err := curve.ECDH(key, peer)
   172  		if err != nil {
   173  			t.Fatal(err)
   174  		}
   175  		if !bytes.Equal(secret, hexDecode(t, v.SharedSecret)) {
   176  			t.Error("shared secret does not match")
   177  		}
   178  	})
   179  }
   180  
   181  func hexDecode(t *testing.T, s string) []byte {
   182  	b, err := hex.DecodeString(s)
   183  	if err != nil {
   184  		t.Fatal("invalid hex string:", s)
   185  	}
   186  	return b
   187  }
   188  
   189  func TestString(t *testing.T) {
   190  	testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
   191  		s := fmt.Sprintf("%s", curve)
   192  		if s[:1] != "P" && s[:1] != "X" {
   193  			t.Errorf("unexpected Curve string encoding: %q", s)
   194  		}
   195  	})
   196  }
   197  
   198  func testAllCurves(t *testing.T, f func(t *testing.T, curve ecdh.Curve)) {
   199  	t.Run("P256", func(t *testing.T) { f(t, ecdh.P256()) })
   200  	t.Run("P384", func(t *testing.T) { f(t, ecdh.P384()) })
   201  	t.Run("P521", func(t *testing.T) { f(t, ecdh.P521()) })
   202  	t.Run("X25519", func(t *testing.T) { f(t, ecdh.X25519()) })
   203  }
   204  
   205  func BenchmarkECDH(b *testing.B) {
   206  	benchmarkAllCurves(b, func(b *testing.B, curve ecdh.Curve) {
   207  		c, err := chacha20.NewUnauthenticatedCipher(make([]byte, 32), make([]byte, 12))
   208  		if err != nil {
   209  			b.Fatal(err)
   210  		}
   211  		rand := cipher.StreamReader{
   212  			S: c, R: zeroReader,
   213  		}
   214  
   215  		peerKey, err := curve.GenerateKey(rand)
   216  		if err != nil {
   217  			b.Fatal(err)
   218  		}
   219  		peerShare := peerKey.PublicKey().Bytes()
   220  		b.ResetTimer()
   221  		b.ReportAllocs()
   222  
   223  		var allocationsSink byte
   224  
   225  		for i := 0; i < b.N; i++ {
   226  			key, err := curve.GenerateKey(rand)
   227  			if err != nil {
   228  				b.Fatal(err)
   229  			}
   230  			share := key.PublicKey().Bytes()
   231  			peerPubKey, err := curve.NewPublicKey(peerShare)
   232  			if err != nil {
   233  				b.Fatal(err)
   234  			}
   235  			secret, err := curve.ECDH(key, peerPubKey)
   236  			if err != nil {
   237  				b.Fatal(err)
   238  			}
   239  			allocationsSink ^= secret[0] ^ share[0]
   240  		}
   241  	})
   242  }
   243  
   244  func benchmarkAllCurves(b *testing.B, f func(b *testing.B, curve ecdh.Curve)) {
   245  	b.Run("P256", func(b *testing.B) { f(b, ecdh.P256()) })
   246  	b.Run("P384", func(b *testing.B) { f(b, ecdh.P384()) })
   247  	b.Run("P521", func(b *testing.B) { f(b, ecdh.P521()) })
   248  	b.Run("X25519", func(b *testing.B) { f(b, ecdh.X25519()) })
   249  }
   250  
   251  type zr struct{}
   252  
   253  // Read replaces the contents of dst with zeros. It is safe for concurrent use.
   254  func (zr) Read(dst []byte) (n int, err error) {
   255  	for i := range dst {
   256  		dst[i] = 0
   257  	}
   258  	return len(dst), nil
   259  }
   260  
   261  var zeroReader = zr{}
   262  
   263  const linkerTestProgram = `
   264  package main
   265  import "crypto/ecdh"
   266  import "crypto/rand"
   267  func main() {
   268  	curve := ecdh.P384()
   269  	key, err := curve.GenerateKey(rand.Reader)
   270  	if err != nil { panic(err) }
   271  	_, err = curve.NewPublicKey(key.PublicKey().Bytes())
   272  	if err != nil { panic(err) }
   273  	_, err = curve.NewPrivateKey(key.Bytes())
   274  	if err != nil { panic(err) }
   275  	_, err = curve.ECDH(key, key.PublicKey())
   276  	if err != nil { panic(err) }
   277  	println("OK")
   278  }
   279  `
   280  
   281  // TestLinker ensures that using one curve does not bring all other
   282  // implementations into the binary. This also guarantees that govulncheck can
   283  // avoid warning about a curve-specific vulnerability if that curve is not used.
   284  func TestLinker(t *testing.T) {
   285  	if testing.Short() {
   286  		t.Skip("test requires running 'go build'")
   287  	}
   288  	testenv.MustHaveGoBuild(t)
   289  
   290  	dir := t.TempDir()
   291  	hello := filepath.Join(dir, "hello.go")
   292  	err := os.WriteFile(hello, []byte(linkerTestProgram), 0664)
   293  	if err != nil {
   294  		t.Fatal(err)
   295  	}
   296  
   297  	run := func(args ...string) string {
   298  		cmd := exec.Command(args[0], args[1:]...)
   299  		cmd.Dir = dir
   300  		out, err := cmd.CombinedOutput()
   301  		if err != nil {
   302  			t.Fatalf("%v: %v\n%s", args, err, string(out))
   303  		}
   304  		return string(out)
   305  	}
   306  
   307  	goBin := testenv.GoToolPath(t)
   308  	run(goBin, "build", "-o", "hello.exe", "hello.go")
   309  	if out := run("./hello.exe"); out != "OK\n" {
   310  		t.Error("unexpected output:", out)
   311  	}
   312  
   313  	// List all text symbols under crypto/... and make sure there are some for
   314  	// P384, but none for the other curves.
   315  	var consistent bool
   316  	nm := run(goBin, "tool", "nm", "hello.exe")
   317  	for _, match := range regexp.MustCompile(`(?m)T (crypto/.*)$`).FindAllStringSubmatch(nm, -1) {
   318  		symbol := strings.ToLower(match[1])
   319  		if strings.Contains(symbol, "p384") {
   320  			consistent = true
   321  		}
   322  		if strings.Contains(symbol, "p224") || strings.Contains(symbol, "p256") || strings.Contains(symbol, "p521") {
   323  			t.Errorf("unexpected symbol in program using only ecdh.P384: %s", match[1])
   324  		}
   325  	}
   326  	if !consistent {
   327  		t.Error("no P384 symbols found in program using ecdh.P384, test is broken")
   328  	}
   329  }