From: Filippo Valsorda Date: Thu, 2 May 2019 21:31:56 +0000 (-0400) Subject: crypto/ed25519: promote from golang.org/x/crypto/ed25519 X-Git-Tag: go1.13beta1~393 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=53374e7e068c032b9e6278e592974e973a554365;p=gostls13.git crypto/ed25519: promote from golang.org/x/crypto/ed25519 The crypto/tls and crypto/x509 APIs leak PublicKey and PrivateKey types, so in order to add support for Ed25519 certificates we need the ed25519 package in the stdlib. It's also a primitive that's reasonable to use directly in applications, as it is a modern, safe and fast signing algorithm, for which there aren't higher level APIs. (The nacl/sign API is limiting in that it repeats the message.) A few docs changes will come in a follow-up, and a CL will land on golang.org/x/crypto/ed25519 to make it a type alias wrapper on Go 1.13+. Updates #25355 Change-Id: I057f20cc7d1aca2b95c29ce73eb03c3b237e413f Reviewed-on: https://go-review.googlesource.com/c/go/+/174945 Run-TryBot: Filippo Valsorda TryBot-Result: Gobot Gobot Reviewed-by: Adam Langley --- diff --git a/misc/nacl/testzip.proto b/misc/nacl/testzip.proto index 19b8ceae69..5a9804b36d 100644 --- a/misc/nacl/testzip.proto +++ b/misc/nacl/testzip.proto @@ -97,6 +97,9 @@ go src=.. + zlib crypto + ed25519 + testdata + + rsa testdata + diff --git a/src/cmd/go/internal/note/note.go b/src/cmd/go/internal/note/note.go index 4b257739cc..f770da24b3 100644 --- a/src/cmd/go/internal/note/note.go +++ b/src/cmd/go/internal/note/note.go @@ -183,6 +183,7 @@ package note import ( "bytes" + "crypto/ed25519" "crypto/sha256" "encoding/base64" "encoding/binary" @@ -193,8 +194,6 @@ import ( "strings" "unicode" "unicode/utf8" - - "golang.org/x/crypto/ed25519" ) // A Verifier verifies messages signed with a specific key. diff --git a/src/cmd/go/internal/note/note_test.go b/src/cmd/go/internal/note/note_test.go index 96c8c91aa9..729324647e 100644 --- a/src/cmd/go/internal/note/note_test.go +++ b/src/cmd/go/internal/note/note_test.go @@ -5,13 +5,12 @@ package note import ( + "crypto/ed25519" "crypto/rand" "errors" "strings" "testing" "testing/iotest" - - "golang.org/x/crypto/ed25519" ) func TestNewVerifier(t *testing.T) { @@ -165,7 +164,7 @@ func TestFromEd25519(t *testing.T) { } // newSignerFromEd25519Seed constructs a new signer from a verifier name and a -// golang.org/x/crypto/ed25519 private key seed. +// crypto/ed25519 private key seed. func newSignerFromEd25519Seed(name string, seed []byte) (Signer, error) { if len(seed) != ed25519.SeedSize { return nil, errors.New("invalid seed size") diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt index 5974e060dd..803f6de7c8 100644 --- a/src/cmd/vendor/modules.txt +++ b/src/cmd/vendor/modules.txt @@ -22,8 +22,6 @@ golang.org/x/arch/arm64/arm64asm golang.org/x/arch/ppc64/ppc64asm golang.org/x/arch/x86/x86asm # golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c -golang.org/x/crypto/ed25519 -golang.org/x/crypto/ed25519/internal/edwards25519 golang.org/x/crypto/ssh/terminal # golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 golang.org/x/sys/unix diff --git a/src/cmd/vendor/golang.org/x/crypto/ed25519/ed25519.go b/src/crypto/ed25519/ed25519.go similarity index 99% rename from src/cmd/vendor/golang.org/x/crypto/ed25519/ed25519.go rename to src/crypto/ed25519/ed25519.go index d6f683ba3f..dc47e5585d 100644 --- a/src/cmd/vendor/golang.org/x/crypto/ed25519/ed25519.go +++ b/src/crypto/ed25519/ed25519.go @@ -18,13 +18,12 @@ package ed25519 import ( "bytes" "crypto" + "crypto/ed25519/internal/edwards25519" cryptorand "crypto/rand" "crypto/sha512" "errors" "io" "strconv" - - "golang.org/x/crypto/ed25519/internal/edwards25519" ) const ( diff --git a/src/crypto/ed25519/ed25519_test.go b/src/crypto/ed25519/ed25519_test.go new file mode 100644 index 0000000000..9c980fceff --- /dev/null +++ b/src/crypto/ed25519/ed25519_test.go @@ -0,0 +1,219 @@ +// Copyright 2016 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. + +package ed25519 + +import ( + "bufio" + "bytes" + "compress/gzip" + "crypto" + "crypto/ed25519/internal/edwards25519" + "crypto/rand" + "encoding/hex" + "os" + "strings" + "testing" +) + +type zeroReader struct{} + +func (zeroReader) Read(buf []byte) (int, error) { + for i := range buf { + buf[i] = 0 + } + return len(buf), nil +} + +func TestUnmarshalMarshal(t *testing.T) { + pub, _, _ := GenerateKey(rand.Reader) + + var A edwards25519.ExtendedGroupElement + var pubBytes [32]byte + copy(pubBytes[:], pub) + if !A.FromBytes(&pubBytes) { + t.Fatalf("ExtendedGroupElement.FromBytes failed") + } + + var pub2 [32]byte + A.ToBytes(&pub2) + + if pubBytes != pub2 { + t.Errorf("FromBytes(%v)->ToBytes does not round-trip, got %x\n", pubBytes, pub2) + } +} + +func TestSignVerify(t *testing.T) { + var zero zeroReader + public, private, _ := GenerateKey(zero) + + message := []byte("test message") + sig := Sign(private, message) + if !Verify(public, message, sig) { + t.Errorf("valid signature rejected") + } + + wrongMessage := []byte("wrong message") + if Verify(public, wrongMessage, sig) { + t.Errorf("signature of different message accepted") + } +} + +func TestCryptoSigner(t *testing.T) { + var zero zeroReader + public, private, _ := GenerateKey(zero) + + signer := crypto.Signer(private) + + publicInterface := signer.Public() + public2, ok := publicInterface.(PublicKey) + if !ok { + t.Fatalf("expected PublicKey from Public() but got %T", publicInterface) + } + + if !bytes.Equal(public, public2) { + t.Errorf("public keys do not match: original:%x vs Public():%x", public, public2) + } + + message := []byte("message") + var noHash crypto.Hash + signature, err := signer.Sign(zero, message, noHash) + if err != nil { + t.Fatalf("error from Sign(): %s", err) + } + + if !Verify(public, message, signature) { + t.Errorf("Verify failed on signature from Sign()") + } +} + +func TestGolden(t *testing.T) { + // sign.input.gz is a selection of test cases from + // https://ed25519.cr.yp.to/python/sign.input + testDataZ, err := os.Open("testdata/sign.input.gz") + if err != nil { + t.Fatal(err) + } + defer testDataZ.Close() + testData, err := gzip.NewReader(testDataZ) + if err != nil { + t.Fatal(err) + } + defer testData.Close() + + scanner := bufio.NewScanner(testData) + lineNo := 0 + + for scanner.Scan() { + lineNo++ + + line := scanner.Text() + parts := strings.Split(line, ":") + if len(parts) != 5 { + t.Fatalf("bad number of parts on line %d", lineNo) + } + + privBytes, _ := hex.DecodeString(parts[0]) + pubKey, _ := hex.DecodeString(parts[1]) + msg, _ := hex.DecodeString(parts[2]) + sig, _ := hex.DecodeString(parts[3]) + // The signatures in the test vectors also include the message + // at the end, but we just want R and S. + sig = sig[:SignatureSize] + + if l := len(pubKey); l != PublicKeySize { + t.Fatalf("bad public key length on line %d: got %d bytes", lineNo, l) + } + + var priv [PrivateKeySize]byte + copy(priv[:], privBytes) + copy(priv[32:], pubKey) + + sig2 := Sign(priv[:], msg) + if !bytes.Equal(sig, sig2[:]) { + t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2) + } + + if !Verify(pubKey, msg, sig2) { + t.Errorf("signature failed to verify on line %d", lineNo) + } + + priv2 := NewKeyFromSeed(priv[:32]) + if !bytes.Equal(priv[:], priv2) { + t.Errorf("recreating key pair gave different private key on line %d: %x vs %x", lineNo, priv[:], priv2) + } + + if pubKey2 := priv2.Public().(PublicKey); !bytes.Equal(pubKey, pubKey2) { + t.Errorf("recreating key pair gave different public key on line %d: %x vs %x", lineNo, pubKey, pubKey2) + } + + if seed := priv2.Seed(); !bytes.Equal(priv[:32], seed) { + t.Errorf("recreating key pair gave different seed on line %d: %x vs %x", lineNo, priv[:32], seed) + } + } + + if err := scanner.Err(); err != nil { + t.Fatalf("error reading test data: %s", err) + } +} + +func TestMalleability(t *testing.T) { + // https://tools.ietf.org/html/rfc8032#section-5.1.7 adds an additional test + // that s be in [0, order). This prevents someone from adding a multiple of + // order to s and obtaining a second valid signature for the same message. + msg := []byte{0x54, 0x65, 0x73, 0x74} + sig := []byte{ + 0x7c, 0x38, 0xe0, 0x26, 0xf2, 0x9e, 0x14, 0xaa, 0xbd, 0x05, 0x9a, + 0x0f, 0x2d, 0xb8, 0xb0, 0xcd, 0x78, 0x30, 0x40, 0x60, 0x9a, 0x8b, + 0xe6, 0x84, 0xdb, 0x12, 0xf8, 0x2a, 0x27, 0x77, 0x4a, 0xb0, 0x67, + 0x65, 0x4b, 0xce, 0x38, 0x32, 0xc2, 0xd7, 0x6f, 0x8f, 0x6f, 0x5d, + 0xaf, 0xc0, 0x8d, 0x93, 0x39, 0xd4, 0xee, 0xf6, 0x76, 0x57, 0x33, + 0x36, 0xa5, 0xc5, 0x1e, 0xb6, 0xf9, 0x46, 0xb3, 0x1d, + } + publicKey := []byte{ + 0x7d, 0x4d, 0x0e, 0x7f, 0x61, 0x53, 0xa6, 0x9b, 0x62, 0x42, 0xb5, + 0x22, 0xab, 0xbe, 0xe6, 0x85, 0xfd, 0xa4, 0x42, 0x0f, 0x88, 0x34, + 0xb1, 0x08, 0xc3, 0xbd, 0xae, 0x36, 0x9e, 0xf5, 0x49, 0xfa, + } + + if Verify(publicKey, msg, sig) { + t.Fatal("non-canonical signature accepted") + } +} + +func BenchmarkKeyGeneration(b *testing.B) { + var zero zeroReader + for i := 0; i < b.N; i++ { + if _, _, err := GenerateKey(zero); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkSigning(b *testing.B) { + var zero zeroReader + _, priv, err := GenerateKey(zero) + if err != nil { + b.Fatal(err) + } + message := []byte("Hello, world!") + b.ResetTimer() + for i := 0; i < b.N; i++ { + Sign(priv, message) + } +} + +func BenchmarkVerification(b *testing.B) { + var zero zeroReader + pub, priv, err := GenerateKey(zero) + if err != nil { + b.Fatal(err) + } + message := []byte("Hello, world!") + signature := Sign(priv, message) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Verify(pub, message, signature) + } +} diff --git a/src/cmd/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go b/src/crypto/ed25519/internal/edwards25519/const.go similarity index 100% rename from src/cmd/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go rename to src/crypto/ed25519/internal/edwards25519/const.go diff --git a/src/cmd/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go b/src/crypto/ed25519/internal/edwards25519/edwards25519.go similarity index 100% rename from src/cmd/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go rename to src/crypto/ed25519/internal/edwards25519/edwards25519.go diff --git a/src/crypto/ed25519/testdata/sign.input.gz b/src/crypto/ed25519/testdata/sign.input.gz new file mode 100644 index 0000000000..e6dc728056 Binary files /dev/null and b/src/crypto/ed25519/testdata/sign.input.gz differ diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 006edb6923..6d46ee82f0 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -375,6 +375,10 @@ var pkgDeps = map[string][]string{ // math/big, which imports fmt. "crypto/rand": {"L4", "CRYPTO", "OS", "math/big", "syscall", "syscall/js", "internal/syscall/unix"}, + // Not part of CRYPTO because it imports crypto/rand and crypto/sha512. + "crypto/ed25519": {"L3", "CRYPTO", "crypto/rand", "crypto/ed25519/internal/edwards25519"}, + "crypto/ed25519/internal/edwards25519": {"encoding/binary"}, + // Mathematical crypto: dependencies on fmt (L4) and math/big. // We could avoid some of the fmt, but math/big imports fmt anyway. "crypto/dsa": {"L4", "CRYPTO", "math/big"},