mirror of
https://github.com/golang/go
synced 2024-10-14 11:53:56 +00:00
crypto/ed25519: implement Ed25519ctx and Ed25519ph with context
This is missing a test for Ed25519ph with context, since the RFC doesn't provide one. Fixes #31804 Change-Id: I20947374c51c6b22fb2835317d00edf816c9a2d2 Reviewed-on: https://go-review.googlesource.com/c/go/+/404274 Auto-Submit: Filippo Valsorda <filippo@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Filippo Valsorda <filippo@golang.org> Reviewed-by: Cherry Mui <cherryyz@google.com> Reviewed-by: Roland Shoemaker <roland@golang.org>
This commit is contained in:
parent
8614c525b3
commit
831c6509cc
|
@ -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, method (*Options) HashFunc() crypto.Hash #31804
|
||||||
pkg crypto/ed25519, type Options struct #31804
|
pkg crypto/ed25519, type Options struct #31804
|
||||||
pkg crypto/ed25519, type Options struct, Hash crypto.Hash #31804
|
pkg crypto/ed25519, type Options struct, Hash crypto.Hash #31804
|
||||||
|
pkg crypto/ed25519, type Options struct, Context string #31804
|
||||||
|
|
|
@ -81,18 +81,30 @@ func (priv PrivateKey) Seed() []byte {
|
||||||
// crypto.Hash(0) and the message must not be hashed, as Ed25519 performs two
|
// crypto.Hash(0) and the message must not be hashed, as Ed25519 performs two
|
||||||
// passes over messages to be signed.
|
// passes over messages to be signed.
|
||||||
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
switch opts.HashFunc() {
|
hash := opts.HashFunc()
|
||||||
case crypto.SHA512:
|
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 {
|
if l := len(message); l != sha512.Size {
|
||||||
return nil, errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l))
|
return nil, errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l))
|
||||||
}
|
}
|
||||||
signature := make([]byte, SignatureSize)
|
signature := make([]byte, SignatureSize)
|
||||||
sign(signature, priv, message, domPrefixPh)
|
sign(signature, priv, message, domPrefixPh, context)
|
||||||
return signature, nil
|
return signature, nil
|
||||||
case crypto.Hash(0):
|
case hash == crypto.Hash(0) && context != "": // Ed25519ctx
|
||||||
|
signature := make([]byte, SignatureSize)
|
||||||
|
sign(signature, priv, message, domPrefixCtx, context)
|
||||||
|
return signature, nil
|
||||||
|
case hash == crypto.Hash(0): // Ed25519
|
||||||
return Sign(priv, message), nil
|
return Sign(priv, message), nil
|
||||||
default:
|
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 can be zero for regular Ed25519, or crypto.SHA512 for Ed25519ph.
|
||||||
Hash crypto.Hash
|
Hash crypto.Hash
|
||||||
|
|
||||||
// TODO(filippo): add Context, a string of at most 255 bytes which when
|
// Context, if not empty, selects Ed25519ctx or provides the context string
|
||||||
// non-zero selects Ed25519ctx.
|
// for Ed25519ph. It can be at most 255 bytes in length.
|
||||||
|
Context string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Options) HashFunc() crypto.Hash { return o.Hash }
|
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
|
// Outline the function body so that the returned signature can be
|
||||||
// stack-allocated.
|
// stack-allocated.
|
||||||
signature := make([]byte, SignatureSize)
|
signature := make([]byte, SignatureSize)
|
||||||
sign(signature, privateKey, message, domPrefixPure)
|
sign(signature, privateKey, message, domPrefixPure, "")
|
||||||
return signature
|
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.
|
// See RFC 8032, Section 2 and Section 5.1.
|
||||||
const (
|
const (
|
||||||
// domPrefixPure is empty for pure Ed25519.
|
// domPrefixPure is empty for pure Ed25519.
|
||||||
domPrefixPure = ""
|
domPrefixPure = ""
|
||||||
// domPrefixPh is dom2(phflag=1, context="") for Ed25519ph.
|
// domPrefixPh is dom2(phflag=1) for Ed25519ph. It must be followed by the
|
||||||
domPrefixPh = "SigEd25519 no Ed25519 collisions\x01\x00"
|
// 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 {
|
if l := len(privateKey); l != PrivateKeySize {
|
||||||
panic("ed25519: bad private key length: " + strconv.Itoa(l))
|
panic("ed25519: bad private key length: " + strconv.Itoa(l))
|
||||||
}
|
}
|
||||||
|
@ -189,7 +206,11 @@ func sign(signature, privateKey, message []byte, domPrefix string) {
|
||||||
prefix := h[32:]
|
prefix := h[32:]
|
||||||
|
|
||||||
mh := sha512.New()
|
mh := sha512.New()
|
||||||
|
if domPrefix != domPrefixPure {
|
||||||
mh.Write([]byte(domPrefix))
|
mh.Write([]byte(domPrefix))
|
||||||
|
mh.Write([]byte{byte(len(context))})
|
||||||
|
mh.Write([]byte(context))
|
||||||
|
}
|
||||||
mh.Write(prefix)
|
mh.Write(prefix)
|
||||||
mh.Write(message)
|
mh.Write(message)
|
||||||
messageDigest := make([]byte, 0, sha512.Size)
|
messageDigest := make([]byte, 0, sha512.Size)
|
||||||
|
@ -202,7 +223,11 @@ func sign(signature, privateKey, message []byte, domPrefix string) {
|
||||||
R := (&edwards25519.Point{}).ScalarBaseMult(r)
|
R := (&edwards25519.Point{}).ScalarBaseMult(r)
|
||||||
|
|
||||||
kh := sha512.New()
|
kh := sha512.New()
|
||||||
|
if domPrefix != domPrefixPure {
|
||||||
kh.Write([]byte(domPrefix))
|
kh.Write([]byte(domPrefix))
|
||||||
|
kh.Write([]byte{byte(len(context))})
|
||||||
|
kh.Write([]byte(context))
|
||||||
|
}
|
||||||
kh.Write(R.Bytes())
|
kh.Write(R.Bytes())
|
||||||
kh.Write(publicKey)
|
kh.Write(publicKey)
|
||||||
kh.Write(message)
|
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
|
// Verify reports whether sig is a valid signature of message by publicKey. It
|
||||||
// will panic if len(publicKey) is not PublicKeySize.
|
// will panic if len(publicKey) is not PublicKeySize.
|
||||||
func Verify(publicKey PublicKey, message, sig []byte) bool {
|
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
|
// VerifyWithOptions reports whether sig is a valid signature of message by
|
||||||
// publicKey. A valid signature is indicated by returning a nil error.
|
// publicKey. A valid signature is indicated by returning a nil error.
|
||||||
// If opts.HashFunc() is crypto.SHA512, the pre-hashed variant Ed25519ph is used
|
// 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.HashFunc() must
|
// 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
|
// be crypto.Hash(0) and the message must not be hashed, as Ed25519 performs two
|
||||||
// passes over messages to be signed.
|
// passes over messages to be signed.
|
||||||
func VerifyWithOptions(publicKey PublicKey, message, sig []byte, opts *Options) error {
|
func VerifyWithOptions(publicKey PublicKey, message, sig []byte, opts *Options) error {
|
||||||
switch opts.HashFunc() {
|
switch {
|
||||||
case crypto.SHA512:
|
case opts.Hash == crypto.SHA512: // Ed25519ph
|
||||||
if l := len(message); l != sha512.Size {
|
if l := len(message); l != sha512.Size {
|
||||||
return errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l))
|
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 errors.New("ed25519: invalid signature")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case crypto.Hash(0):
|
case opts.Hash == crypto.Hash(0) && opts.Context != "": // Ed25519ctx
|
||||||
if !verify(publicKey, message, sig, domPrefixPure) {
|
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 opts.Hash == crypto.Hash(0): // Ed25519
|
||||||
|
if !verify(publicKey, message, sig, domPrefixPure, "") {
|
||||||
return errors.New("ed25519: invalid signature")
|
return errors.New("ed25519: invalid signature")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
default:
|
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 {
|
if l := len(publicKey); l != PublicKeySize {
|
||||||
panic("ed25519: bad public key length: " + strconv.Itoa(l))
|
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 := sha512.New()
|
||||||
|
if domPrefix != domPrefixPure {
|
||||||
kh.Write([]byte(domPrefix))
|
kh.Write([]byte(domPrefix))
|
||||||
|
kh.Write([]byte{byte(len(context))})
|
||||||
|
kh.Write([]byte(context))
|
||||||
|
}
|
||||||
kh.Write(sig[:32])
|
kh.Write(sig[:32])
|
||||||
kh.Write(publicKey)
|
kh.Write(publicKey)
|
||||||
kh.Write(message)
|
kh.Write(message)
|
||||||
|
|
|
@ -71,6 +71,10 @@ func TestSignVerifyHashed(t *testing.T) {
|
||||||
t.Errorf("valid signature rejected: %v", err)
|
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"))
|
wrongHash := sha512.Sum512([]byte("wrong message"))
|
||||||
if VerifyWithOptions(public, wrongHash[:], sig, &Options{Hash: crypto.SHA512}) == nil {
|
if VerifyWithOptions(public, wrongHash[:], sig, &Options{Hash: crypto.SHA512}) == nil {
|
||||||
t.Errorf("signature of different message accepted")
|
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 {
|
if VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA512}) == nil {
|
||||||
t.Errorf("invalid signature accepted")
|
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) {
|
func TestCryptoSigner(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue