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 }