diff --git a/api/next/31804.txt b/api/next/31804.txt index e5968c882656bb..bedbc4f04efa0f 100644 --- a/api/next/31804.txt +++ b/api/next/31804.txt @@ -2,3 +2,4 @@ pkg crypto/ed25519, func VerifyWithOptions(PublicKey, []uint8, []uint8, *Options pkg crypto/ed25519, method (*Options) HashFunc() crypto.Hash #31804 pkg crypto/ed25519, type Options struct #31804 pkg crypto/ed25519, type Options struct, Hash crypto.Hash #31804 +pkg crypto/ed25519, type Options struct, Context string #31804 diff --git a/src/crypto/ed25519/ed25519.go b/src/crypto/ed25519/ed25519.go index cb6b293d475f95..921bbaa8edd14f 100644 --- a/src/crypto/ed25519/ed25519.go +++ b/src/crypto/ed25519/ed25519.go @@ -81,18 +81,30 @@ func (priv PrivateKey) Seed() []byte { // crypto.Hash(0) and the message must not be hashed, as Ed25519 performs two // passes over messages to be signed. func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) { - switch opts.HashFunc() { - case crypto.SHA512: + hash := opts.HashFunc() + context := "" + if opts, ok := opts.(*Options); ok { + context = opts.Context + } + if l := len(context); l > 255 { + return nil, errors.New("ed25519: bad Ed25519ph context length: " + strconv.Itoa(l)) + } + switch { + case hash == crypto.SHA512: // Ed25519ph if l := len(message); l != sha512.Size { return nil, errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l)) } signature := make([]byte, SignatureSize) - sign(signature, priv, message, domPrefixPh) + sign(signature, priv, message, domPrefixPh, context) + return signature, nil + case hash == crypto.Hash(0) && context != "": // Ed25519ctx + signature := make([]byte, SignatureSize) + sign(signature, priv, message, domPrefixCtx, context) return signature, nil - case crypto.Hash(0): + case hash == crypto.Hash(0): // Ed25519 return Sign(priv, message), nil default: - return nil, errors.New("ed25519: expected opts zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)") + return nil, errors.New("ed25519: expected opts.HashFunc() zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)") } } @@ -102,8 +114,9 @@ type Options struct { // Hash can be zero for regular Ed25519, or crypto.SHA512 for Ed25519ph. Hash crypto.Hash - // TODO(filippo): add Context, a string of at most 255 bytes which when - // non-zero selects Ed25519ctx. + // Context, if not empty, selects Ed25519ctx or provides the context string + // for Ed25519ph. It can be at most 255 bytes in length. + Context string } func (o *Options) HashFunc() crypto.Hash { return o.Hash } @@ -162,20 +175,24 @@ func Sign(privateKey PrivateKey, message []byte) []byte { // Outline the function body so that the returned signature can be // stack-allocated. signature := make([]byte, SignatureSize) - sign(signature, privateKey, message, domPrefixPure) + sign(signature, privateKey, message, domPrefixPure, "") return signature } -// Domain separation prefixes used to disambiguate Ed25519/Ed25519ph. +// Domain separation prefixes used to disambiguate Ed25519/Ed25519ph/Ed25519ctx. // See RFC 8032, Section 2 and Section 5.1. const ( // domPrefixPure is empty for pure Ed25519. domPrefixPure = "" - // domPrefixPh is dom2(phflag=1, context="") for Ed25519ph. - domPrefixPh = "SigEd25519 no Ed25519 collisions\x01\x00" + // domPrefixPh is dom2(phflag=1) for Ed25519ph. It must be followed by the + // uint8-length prefixed context. + domPrefixPh = "SigEd25519 no Ed25519 collisions\x01" + // domPrefixCtx is dom2(phflag=0) for Ed25519ctx. It must be followed by the + // uint8-length prefixed context. + domPrefixCtx = "SigEd25519 no Ed25519 collisions\x00" ) -func sign(signature, privateKey, message []byte, domPrefix string) { +func sign(signature, privateKey, message []byte, domPrefix, context string) { if l := len(privateKey); l != PrivateKeySize { panic("ed25519: bad private key length: " + strconv.Itoa(l)) } @@ -189,7 +206,11 @@ func sign(signature, privateKey, message []byte, domPrefix string) { prefix := h[32:] mh := sha512.New() - mh.Write([]byte(domPrefix)) + if domPrefix != domPrefixPure { + mh.Write([]byte(domPrefix)) + mh.Write([]byte{byte(len(context))}) + mh.Write([]byte(context)) + } mh.Write(prefix) mh.Write(message) messageDigest := make([]byte, 0, sha512.Size) @@ -202,7 +223,11 @@ func sign(signature, privateKey, message []byte, domPrefix string) { R := (&edwards25519.Point{}).ScalarBaseMult(r) kh := sha512.New() - kh.Write([]byte(domPrefix)) + if domPrefix != domPrefixPure { + kh.Write([]byte(domPrefix)) + kh.Write([]byte{byte(len(context))}) + kh.Write([]byte(context)) + } kh.Write(R.Bytes()) kh.Write(publicKey) kh.Write(message) @@ -222,36 +247,47 @@ func sign(signature, privateKey, message []byte, domPrefix string) { // Verify reports whether sig is a valid signature of message by publicKey. It // will panic if len(publicKey) is not PublicKeySize. func Verify(publicKey PublicKey, message, sig []byte) bool { - return verify(publicKey, message, sig, domPrefixPure) + return verify(publicKey, message, sig, domPrefixPure, "") } // VerifyWithOptions reports whether sig is a valid signature of message by // publicKey. A valid signature is indicated by returning a nil error. -// If opts.HashFunc() is crypto.SHA512, the pre-hashed variant Ed25519ph is used -// and message is expected to be a SHA-512 hash, otherwise opts.HashFunc() must +// If opts.Hash is crypto.SHA512, the pre-hashed variant Ed25519ph is used +// and message is expected to be a SHA-512 hash, otherwise opts.Hash must // be crypto.Hash(0) and the message must not be hashed, as Ed25519 performs two // passes over messages to be signed. func VerifyWithOptions(publicKey PublicKey, message, sig []byte, opts *Options) error { - switch opts.HashFunc() { - case crypto.SHA512: + switch { + case opts.Hash == crypto.SHA512: // Ed25519ph if l := len(message); l != sha512.Size { return errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l)) } - if !verify(publicKey, message, sig, domPrefixPh) { + if l := len(opts.Context); l > 255 { + return errors.New("ed25519: bad Ed25519ph context length: " + strconv.Itoa(l)) + } + if !verify(publicKey, message, sig, domPrefixPh, opts.Context) { + return errors.New("ed25519: invalid signature") + } + return nil + case opts.Hash == crypto.Hash(0) && opts.Context != "": // Ed25519ctx + if l := len(opts.Context); l > 255 { + return errors.New("ed25519: bad Ed25519ctx context length: " + strconv.Itoa(l)) + } + if !verify(publicKey, message, sig, domPrefixCtx, opts.Context) { return errors.New("ed25519: invalid signature") } return nil - case crypto.Hash(0): - if !verify(publicKey, message, sig, domPrefixPure) { + case opts.Hash == crypto.Hash(0): // Ed25519 + if !verify(publicKey, message, sig, domPrefixPure, "") { return errors.New("ed25519: invalid signature") } return nil default: - return errors.New("ed25519: expected opts zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)") + return errors.New("ed25519: expected opts.Hash zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)") } } -func verify(publicKey PublicKey, message, sig []byte, domPrefix string) bool { +func verify(publicKey PublicKey, message, sig []byte, domPrefix, context string) bool { if l := len(publicKey); l != PublicKeySize { panic("ed25519: bad public key length: " + strconv.Itoa(l)) } @@ -266,7 +302,11 @@ func verify(publicKey PublicKey, message, sig []byte, domPrefix string) bool { } kh := sha512.New() - kh.Write([]byte(domPrefix)) + if domPrefix != domPrefixPure { + kh.Write([]byte(domPrefix)) + kh.Write([]byte{byte(len(context))}) + kh.Write([]byte(context)) + } kh.Write(sig[:32]) kh.Write(publicKey) kh.Write(message) diff --git a/src/crypto/ed25519/ed25519_test.go b/src/crypto/ed25519/ed25519_test.go index fbd4982cc2de2d..ae5c872e02fc66 100644 --- a/src/crypto/ed25519/ed25519_test.go +++ b/src/crypto/ed25519/ed25519_test.go @@ -71,6 +71,10 @@ func TestSignVerifyHashed(t *testing.T) { t.Errorf("valid signature rejected: %v", err) } + if err := VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA256}); err == nil { + t.Errorf("expected error for wrong hash") + } + wrongHash := sha512.Sum512([]byte("wrong message")) if VerifyWithOptions(public, wrongHash[:], sig, &Options{Hash: crypto.SHA512}) == nil { t.Errorf("signature of different message accepted") @@ -85,6 +89,60 @@ func TestSignVerifyHashed(t *testing.T) { if VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA512}) == nil { t.Errorf("invalid signature accepted") } + + // The RFC provides no test vectors for Ed25519ph with context, so just sign + // and verify something. + sig, err = private.Sign(nil, hash[:], &Options{Hash: crypto.SHA512, Context: "123"}) + if err != nil { + t.Fatal(err) + } + if err := VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA512, Context: "123"}); err != nil { + t.Errorf("valid signature rejected: %v", err) + } + if err := VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA512, Context: "321"}); err == nil { + t.Errorf("expected error for wrong context") + } + if err := VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA256, Context: "123"}); err == nil { + t.Errorf("expected error for wrong hash") + } +} + +func TestSignVerifyContext(t *testing.T) { + // From RFC 8032, Section 7.2 + key, _ := hex.DecodeString("0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292") + expectedSig, _ := hex.DecodeString("55a4cc2f70a54e04288c5f4cd1e45a7bb520b36292911876cada7323198dd87a8b36950b95130022907a7fb7c4e9b2d5f6cca685a587b4b21f4b888e4e7edb0d") + message, _ := hex.DecodeString("f726936d19c800494e3fdaff20b276a8") + context := "foo" + + private := PrivateKey(key) + public := private.Public().(PublicKey) + sig, err := private.Sign(nil, message, &Options{Context: context}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(sig, expectedSig) { + t.Error("signature doesn't match test vector") + } + if err := VerifyWithOptions(public, message, sig, &Options{Context: context}); err != nil { + t.Errorf("valid signature rejected: %v", err) + } + + if VerifyWithOptions(public, []byte("bar"), sig, &Options{Context: context}) == nil { + t.Errorf("signature of different message accepted") + } + if VerifyWithOptions(public, message, sig, &Options{Context: "bar"}) == nil { + t.Errorf("signature with different context accepted") + } + + sig[0] ^= 0xff + if VerifyWithOptions(public, message, sig, &Options{Context: context}) == nil { + t.Errorf("invalid signature accepted") + } + sig[0] ^= 0xff + sig[SignatureSize-1] ^= 0xff + if VerifyWithOptions(public, message, sig, &Options{Context: context}) == nil { + t.Errorf("invalid signature accepted") + } } func TestCryptoSigner(t *testing.T) {