Source file
src/crypto/ecdh/ecdh_test.go
1
2
3
4
5 package ecdh_test
6
7 import (
8 "bytes"
9 "crypto"
10 "crypto/cipher"
11 "crypto/ecdh"
12 "crypto/rand"
13 "crypto/sha256"
14 "encoding/hex"
15 "fmt"
16 "internal/testenv"
17 "io"
18 "os"
19 "path/filepath"
20 "regexp"
21 "strings"
22 "testing"
23
24 "golang.org/x/crypto/chacha20"
25 )
26
27
28
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 := bobKey.ECDH(aliceKey.PublicKey())
71 if err != nil {
72 t.Fatal(err)
73 }
74 aliceSecret, err := aliceKey.ECDH(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
105
106
107
108
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
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
138
139
140 ecdh.P521(): {
141 PrivateKey: "017eecc07ab4b329068fba65e56a1f8890aa935e57134ae0ffcce802735151f4eac6564f6ee9974c5e6887a1fefee5743ae2241bfeb95d5ce31ddcb6f9edb4d6fc47",
142 PublicKey: "0400602f9d0cf9e526b29e22381c203c48a886c2b0673033366314f1ffbcba240ba42f4ef38a76174635f91e6b4ed34275eb01c8467d05ca80315bf1a7bbd945f550a5" +
143 "01b7c85f26f5d4b2d7355cf6b02117659943762b6d1db5ab4f1dbc44ce7b2946eb6c7de342962893fd387d1b73d7a8672d1f236961170b7eb3579953ee5cdc88cd2d",
144 PeerPublicKey: "0400685a48e86c79f0f0875f7bc18d25eb5fc8c0b07e5da4f4370f3a9490340854334b1e1b87fa395464c60626124a4e70d0f785601d37c09870ebf176666877a2046d" +
145 "01ba52c56fc8776d9e8f5db4f0cc27636d0b741bbe05400697942e80b739884a83bde99e0f6716939e632bc8986fa18dccd443a348b6c3e522497955a4f3c302f676",
146 SharedSecret: "005fc70477c3e63bc3954bd0df3ea0d1f41ee21746ed95fc5e1fdf90930d5e136672d72cc770742d1711c3c3a4c334a0ad9759436a4d3c5bf6e74b9578fac148c831",
147 },
148
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 := key.ECDH(peer)
172 if err != nil {
173 t.Fatal(err)
174 }
175 if !bytes.Equal(secret, hexDecode(t, v.SharedSecret)) {
176 t.Errorf("shared secret does not match: %x %x %s %x", secret, sha256.Sum256(secret), v.SharedSecret,
177 sha256.Sum256(hexDecode(t, v.SharedSecret)))
178 }
179 })
180 }
181
182 func hexDecode(t *testing.T, s string) []byte {
183 b, err := hex.DecodeString(s)
184 if err != nil {
185 t.Fatal("invalid hex string:", s)
186 }
187 return b
188 }
189
190 func TestString(t *testing.T) {
191 testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
192 s := fmt.Sprintf("%s", curve)
193 if s[:1] != "P" && s[:1] != "X" {
194 t.Errorf("unexpected Curve string encoding: %q", s)
195 }
196 })
197 }
198
199 func TestX25519Failure(t *testing.T) {
200 identity := hexDecode(t, "0000000000000000000000000000000000000000000000000000000000000000")
201 lowOrderPoint := hexDecode(t, "e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800")
202 randomScalar := make([]byte, 32)
203 rand.Read(randomScalar)
204
205 t.Run("identity point", func(t *testing.T) { testX25519Failure(t, randomScalar, identity) })
206 t.Run("low order point", func(t *testing.T) { testX25519Failure(t, randomScalar, lowOrderPoint) })
207 }
208
209 func testX25519Failure(t *testing.T, private, public []byte) {
210 priv, err := ecdh.X25519().NewPrivateKey(private)
211 if err != nil {
212 t.Fatal(err)
213 }
214 pub, err := ecdh.X25519().NewPublicKey(public)
215 if err != nil {
216 t.Fatal(err)
217 }
218 secret, err := priv.ECDH(pub)
219 if err == nil {
220 t.Error("expected ECDH error")
221 }
222 if secret != nil {
223 t.Errorf("unexpected ECDH output: %x", secret)
224 }
225 }
226
227 var invalidPrivateKeys = map[ecdh.Curve][]string{
228 ecdh.P256(): {
229
230 "",
231 "01",
232 "01010101010101010101010101010101010101010101010101010101010101",
233 "000101010101010101010101010101010101010101010101010101010101010101",
234 strings.Repeat("01", 200),
235
236 "0000000000000000000000000000000000000000000000000000000000000000",
237
238 "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551",
239 "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552",
240 "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
241 },
242 ecdh.P384(): {
243
244 "",
245 "01",
246 "0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101",
247 "00010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101",
248 strings.Repeat("01", 200),
249
250 "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
251
252 "ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973",
253 "ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52974",
254 "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
255 },
256 ecdh.P521(): {
257
258 "",
259 "01",
260 "0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101",
261 "00010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101",
262 strings.Repeat("01", 200),
263
264 "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
265
266 "01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409",
267 "01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e9138640a",
268 "11fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409",
269 "03fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4a30d0f077e5f2cd6ff980291ee134ba0776b937113388f5d76df6e3d2270c812",
270 },
271 ecdh.X25519(): {
272
273 "",
274 "01",
275 "01010101010101010101010101010101010101010101010101010101010101",
276 "000101010101010101010101010101010101010101010101010101010101010101",
277 strings.Repeat("01", 200),
278 },
279 }
280
281 func TestNewPrivateKey(t *testing.T) {
282 testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
283 for _, input := range invalidPrivateKeys[curve] {
284 k, err := curve.NewPrivateKey(hexDecode(t, input))
285 if err == nil {
286 t.Errorf("unexpectedly accepted %q", input)
287 } else if k != nil {
288 t.Error("PrivateKey was not nil on error")
289 } else if strings.Contains(err.Error(), "boringcrypto") {
290 t.Errorf("boringcrypto error leaked out: %v", err)
291 }
292 }
293 })
294 }
295
296 var invalidPublicKeys = map[ecdh.Curve][]string{
297 ecdh.P256(): {
298
299 "",
300 "04",
301 strings.Repeat("04", 200),
302
303 "00",
304
305 "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
306 "02e2534a3532d08fbba02dde659ee62bd0031fe2db785596ef509302446b030852",
307
308 "046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f6",
309 "0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
310
311 "04ffffffff00000001000000000000000000000001000000000000000000000004ba6dbc4555a7e7fa016ec431667e8521ee35afc49b265c3accbea3f7cdb70433",
312 },
313 ecdh.P384(): {
314
315 "",
316 "04",
317 strings.Repeat("04", 200),
318
319 "00",
320
321 "03aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7",
322 "0208d999057ba3d2d969260045c55b97f089025959a6f434d651d207d19fb96e9e4fe0e86ebe0e64f85b96a9c75295df61",
323
324 "04aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab73617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e60",
325 "04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
326
327 "04fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff000000000000000100000001732152442fb6ee5c3e6ce1d920c059bc623563814d79042b903ce60f1d4487fccd450a86da03f3e6ed525d02017bfdb3",
328 },
329 ecdh.P521(): {
330
331 "",
332 "04",
333 strings.Repeat("04", 200),
334
335 "00",
336
337 "030035b5df64ae2ac204c354b483487c9070cdc61c891c5ff39afc06c5d55541d3ceac8659e24afe3d0750e8b88e9f078af066a1d5025b08e5a5e2fbc87412871902f3",
338 "0200c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66",
339
340 "0400c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16651",
341 "04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
342
343 "0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100d9254fdf800496acb33790b103c5ee9fac12832fe546c632225b0f7fce3da4574b1a879b623d722fa8fc34d5fc2a8731aad691a9a8bb8b554c95a051d6aa505acf",
344 },
345 ecdh.X25519(): {},
346 }
347
348 func TestNewPublicKey(t *testing.T) {
349 testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
350 for _, input := range invalidPublicKeys[curve] {
351 k, err := curve.NewPublicKey(hexDecode(t, input))
352 if err == nil {
353 t.Errorf("unexpectedly accepted %q", input)
354 } else if k != nil {
355 t.Error("PublicKey was not nil on error")
356 } else if strings.Contains(err.Error(), "boringcrypto") {
357 t.Errorf("boringcrypto error leaked out: %v", err)
358 }
359 }
360 })
361 }
362
363 func testAllCurves(t *testing.T, f func(t *testing.T, curve ecdh.Curve)) {
364 t.Run("P256", func(t *testing.T) { f(t, ecdh.P256()) })
365 t.Run("P384", func(t *testing.T) { f(t, ecdh.P384()) })
366 t.Run("P521", func(t *testing.T) { f(t, ecdh.P521()) })
367 t.Run("X25519", func(t *testing.T) { f(t, ecdh.X25519()) })
368 }
369
370 func BenchmarkECDH(b *testing.B) {
371 benchmarkAllCurves(b, func(b *testing.B, curve ecdh.Curve) {
372 c, err := chacha20.NewUnauthenticatedCipher(make([]byte, 32), make([]byte, 12))
373 if err != nil {
374 b.Fatal(err)
375 }
376 rand := cipher.StreamReader{
377 S: c, R: zeroReader,
378 }
379
380 peerKey, err := curve.GenerateKey(rand)
381 if err != nil {
382 b.Fatal(err)
383 }
384 peerShare := peerKey.PublicKey().Bytes()
385 b.ResetTimer()
386 b.ReportAllocs()
387
388 var allocationsSink byte
389
390 for i := 0; i < b.N; i++ {
391 key, err := curve.GenerateKey(rand)
392 if err != nil {
393 b.Fatal(err)
394 }
395 share := key.PublicKey().Bytes()
396 peerPubKey, err := curve.NewPublicKey(peerShare)
397 if err != nil {
398 b.Fatal(err)
399 }
400 secret, err := key.ECDH(peerPubKey)
401 if err != nil {
402 b.Fatal(err)
403 }
404 allocationsSink ^= secret[0] ^ share[0]
405 }
406 })
407 }
408
409 func benchmarkAllCurves(b *testing.B, f func(b *testing.B, curve ecdh.Curve)) {
410 b.Run("P256", func(b *testing.B) { f(b, ecdh.P256()) })
411 b.Run("P384", func(b *testing.B) { f(b, ecdh.P384()) })
412 b.Run("P521", func(b *testing.B) { f(b, ecdh.P521()) })
413 b.Run("X25519", func(b *testing.B) { f(b, ecdh.X25519()) })
414 }
415
416 type zr struct{}
417
418
419 func (zr) Read(dst []byte) (n int, err error) {
420 clear(dst)
421 return len(dst), nil
422 }
423
424 var zeroReader = zr{}
425
426 const linkerTestProgram = `
427 package main
428 import "crypto/ecdh"
429 import "crypto/rand"
430 func main() {
431 // Use P-256, since that's what the always-enabled CAST uses.
432 curve := ecdh.P256()
433 key, err := curve.GenerateKey(rand.Reader)
434 if err != nil { panic(err) }
435 _, err = curve.NewPublicKey(key.PublicKey().Bytes())
436 if err != nil { panic(err) }
437 _, err = curve.NewPrivateKey(key.Bytes())
438 if err != nil { panic(err) }
439 _, err = key.ECDH(key.PublicKey())
440 if err != nil { panic(err) }
441 println("OK")
442 }
443 `
444
445
446
447
448 func TestLinker(t *testing.T) {
449 if testing.Short() {
450 t.Skip("test requires running 'go build'")
451 }
452
453 dir := t.TempDir()
454 hello := filepath.Join(dir, "hello.go")
455 err := os.WriteFile(hello, []byte(linkerTestProgram), 0664)
456 if err != nil {
457 t.Fatal(err)
458 }
459
460 run := func(args ...string) string {
461 cmd := testenv.Command(t, args[0], args[1:]...)
462 cmd.Dir = dir
463 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
464 if err != nil {
465 t.Fatalf("%v: %v\n%s", args, err, string(out))
466 }
467 return string(out)
468 }
469
470 run(testenv.GoToolPath(t), "build", "-o", "hello.exe", "hello.go")
471 if out := run("./hello.exe"); out != "OK\n" {
472 t.Error("unexpected output:", out)
473 }
474
475
476
477 var consistent bool
478 nm := run(testenv.GoToolPath(t), "tool", "nm", "hello.exe")
479 for _, match := range regexp.MustCompile(`(?m)T (crypto/.*)$`).FindAllStringSubmatch(nm, -1) {
480 symbol := strings.ToLower(match[1])
481 if strings.Contains(symbol, "p256") {
482 consistent = true
483 }
484 if strings.Contains(symbol, "p224") || strings.Contains(symbol, "p384") || strings.Contains(symbol, "p521") {
485 t.Errorf("unexpected symbol in program using only ecdh.P256: %s", match[1])
486 }
487 }
488 if !consistent {
489 t.Error("no P256 symbols found in program using ecdh.P256, test is broken")
490 }
491 }
492
493 func TestMismatchedCurves(t *testing.T) {
494 curves := []struct {
495 name string
496 curve ecdh.Curve
497 }{
498 {"P256", ecdh.P256()},
499 {"P384", ecdh.P384()},
500 {"P521", ecdh.P521()},
501 {"X25519", ecdh.X25519()},
502 }
503
504 for _, privCurve := range curves {
505 priv, err := privCurve.curve.GenerateKey(rand.Reader)
506 if err != nil {
507 t.Fatalf("failed to generate test key: %s", err)
508 }
509
510 for _, pubCurve := range curves {
511 if privCurve == pubCurve {
512 continue
513 }
514 t.Run(fmt.Sprintf("%s/%s", privCurve.name, pubCurve.name), func(t *testing.T) {
515 pub, err := pubCurve.curve.GenerateKey(rand.Reader)
516 if err != nil {
517 t.Fatalf("failed to generate test key: %s", err)
518 }
519 expected := "crypto/ecdh: private key and public key curves do not match"
520 _, err = priv.ECDH(pub.PublicKey())
521 if err.Error() != expected {
522 t.Fatalf("unexpected error: want %q, got %q", expected, err)
523 }
524 })
525 }
526 }
527 }
528
View as plain text