diff --git a/group/edwards25519/const.go b/group/edwards25519/const.go index 2d212d1c2..a9c8c5ce2 100644 --- a/group/edwards25519/const.go +++ b/group/edwards25519/const.go @@ -1444,3 +1444,29 @@ var base = [32][8]preComputedGroupElement{ }, }, } + +// Ed25519 "weak keys" +// Copied from https://github.com/jedisct1/libsodium/blob/4744636721d2e420f8bbe2d563f31b1f5e682229/src/libsodium/crypto_core/ed25519/ref10/ed25519_ref10.c#L1130 +// We did not consider `prime` and `prime+1` since they would be discarded because of `IsCanonical` checks +// TODO: is there a reference to the SUPERCOP ref10 implementation available? +var weakKeys = [][]byte{ + /* 0 (order 4) */ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + /* 1 (order 1) */ + {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + /* 2707385501144840649318225287225658788936804267575313519463743609750303402022 (order 8) */ + {0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, + 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, + 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05}, + /* 55188659117513257062467267217118295137698188065244968500265048394206261417927 (order 8) */ + {0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, + 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, + 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a}, + /* p-1 (order 2) */ + {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}} diff --git a/group/edwards25519/point.go b/group/edwards25519/point.go index 0ed839dd0..27009191d 100644 --- a/group/edwards25519/point.go +++ b/group/edwards25519/point.go @@ -229,3 +229,37 @@ func (P *point) Mul(s kyber.Scalar, A kyber.Point) kyber.Point { return P } + +// HasSmallOrder determines whether the group element has small order +// +// Provides resilience against malicious key substitution attacks (M-S-UEO) +// and message bound security (MSB) even for malicious keys +// See paper https://eprint.iacr.org/2020/823.pdf for definitions and theorems +// +// This is the same code as in +// https://github.com/jedisct1/libsodium/blob/4744636721d2e420f8bbe2d563f31b1f5e682229/src/libsodium/crypto_core/ed25519/ref10/ed25519_ref10.c#L1170 +func (P *point) HasSmallOrder() bool { + s, err := P.MarshalBinary() + if err != nil { + return false + } + + var c [5]byte + + for j := 0; j < 31; j++ { + for i := 0; i < 5; i++ { + c[i] |= s[j] ^ weakKeys[i][j] + } + } + for i := 0; i < 5; i++ { + c[i] |= (s[31] & 0x7f) ^ weakKeys[i][31] + } + + // Constant time verification if one or more of the c's are zero + var k uint16 + for i := 0; i < 5; i++ { + k |= uint16(c[i]) - 1 + } + + return (k>>8)&1 > 0 +} diff --git a/group/edwards25519/point_test.go b/group/edwards25519/point_test.go index eb4667d0f..9d2a26803 100644 --- a/group/edwards25519/point_test.go +++ b/group/edwards25519/point_test.go @@ -1,6 +1,7 @@ package edwards25519 import ( + "encoding/hex" "fmt" "testing" @@ -11,3 +12,14 @@ func TestPoint_Marshal(t *testing.T) { p := point{} require.Equal(t, "ed.point", fmt.Sprintf("%s", p.MarshalID())) } + +// TestPoint_HasSmallOrder ensures weakKeys are considered to have +// a small order +func TestPoint_HasSmallOrder(t *testing.T) { + for _, key := range weakKeys { + p := point{} + err := p.UnmarshalBinary(key) + require.Nil(t, err) + require.True(t, p.HasSmallOrder(), fmt.Sprintf("%s should be considered to have a small order", hex.EncodeToString(key))) + } +} diff --git a/sign/eddsa/eddsa.go b/sign/eddsa/eddsa.go index 00cc0141e..e7bfbf135 100644 --- a/sign/eddsa/eddsa.go +++ b/sign/eddsa/eddsa.go @@ -7,6 +7,7 @@ import ( "crypto/sha512" "errors" "fmt" + "math/big" "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/group/edwards25519" @@ -14,6 +15,11 @@ import ( var group = new(edwards25519.Curve) +// TODO: maybe export prime and primeOrder from edwards25519/const or allow it to be +// retrieved from the curve? +var prime, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10) +var primeOrder, _ = new(big.Int).SetString("7237005577332262213973186563042994240857116359379907606001950938285454250989", 10) + // EdDSA is a structure holding the data necessary to make a series of // EdDSA signatures. type EdDSA struct { @@ -26,6 +32,15 @@ type EdDSA struct { prefix []byte } +// edDSAPoint is used to verify signatures +// with checks around canonicality and group order +type edDSAPoint interface { + kyber.Point + // HasSmallOrder checks if the given buffer (in little endian) + // represents a point with a small order + HasSmallOrder() bool +} + // NewEdDSA will return a freshly generated key pair to use for generating // EdDSA signatures. func NewEdDSA(stream cipher.Stream) *EdDSA { @@ -119,31 +134,50 @@ func (e *EdDSA) Sign(msg []byte) ([]byte, error) { return sig[:], nil } -// Verify uses a public key, a message and a signature. It will return nil if -// sig is a valid signature for msg created by key public, or an error otherwise. -func Verify(public kyber.Point, msg, sig []byte) error { +// VerifyWithChecks uses a public key buffer, a message and a signature. +// It will return nil if sig is a valid signature for msg created by +// key public, or an error otherwise. Compared to `Verify`, it performs +// additional checks around the canonicality and ensures the public key +// does not have a small order. +func VerifyWithChecks(pub, msg, sig []byte) error { if len(sig) != 64 { return fmt.Errorf("signature length invalid, expect 64 but got %v", len(sig)) } + if !scalarIsCanonical(sig[32:]) { + return fmt.Errorf("signature is not canonical") + } + if !pointIsCanonical(pub) { + return fmt.Errorf("public key is not canonical") + } + if !pointIsCanonical(sig[:32]) { + return fmt.Errorf("R is not canonical") + } R := group.Point() if err := R.UnmarshalBinary(sig[:32]); err != nil { return fmt.Errorf("got R invalid point: %s", err) } + if R.(edDSAPoint).HasSmallOrder() { + return fmt.Errorf("R has small order") + } s := group.Scalar() if err := s.UnmarshalBinary(sig[32:]); err != nil { return fmt.Errorf("schnorr: s invalid scalar %s", err) } - // reconstruct h = H(R || Public || Msg) - Pbuff, err := public.MarshalBinary() - if err != nil { - return err + public := group.Point() + if err := public.UnmarshalBinary(pub); err != nil { + return fmt.Errorf("invalid public key: %s", err) + } + if public.(edDSAPoint).HasSmallOrder() { + return fmt.Errorf("public key has small order") } + + // reconstruct h = H(R || Public || Msg) hash := sha512.New() _, _ = hash.Write(sig[:32]) - _, _ = hash.Write(Pbuff) + _, _ = hash.Write(pub) _, _ = hash.Write(msg) h := group.Scalar().SetBytes(hash.Sum(nil)) @@ -157,3 +191,69 @@ func Verify(public kyber.Point, msg, sig []byte) error { } return nil } + +// Verify uses a public key, a message and a signature. It will return nil if +// sig is a valid signature for msg created by key public, or an error otherwise. +func Verify(public kyber.Point, msg, sig []byte) error { + PBuf, err := public.MarshalBinary() + if err != nil { + return fmt.Errorf("error unmarshalling public key: %s", err) + } + return VerifyWithChecks(PBuf, msg, sig) +} + +// scalarIsCanonical whether scalar s is in the range 0<=s= 0; i-- { + // subtraction might lead to an underflow which needs + // to be accounted for in the right shift + c |= byte((uint16(sb[i])-uint16(L[i]))>>8) & n + n &= byte((uint16(sb[i]) ^ uint16(L[i]) - 1) >> 8) + } + + return c != 0 +} + +// pointIsCanonical determines whether the group element is canonical +// +// Checks whether group element s is less than p, according to RFC8032ยง5.1.3.1 +// https://tools.ietf.org/html/rfc8032#section-5.1.3 +// +// Taken from +// https://github.com/jedisct1/libsodium/blob/4744636721d2e420f8bbe2d563f31b1f5e682229/src/libsodium/crypto_core/ed25519/ref10/ed25519_ref10.c#L1113 +func pointIsCanonical(s []byte) bool { + if len(s) != 32 { + return false + } + + c := (s[31] & 0x7f) ^ 0x7f + for i := 30; i > 0; i-- { + c |= s[i] ^ 0xff + } + + // subtraction might underflow + c = byte((uint16(c) - 1) >> 8) + d := byte((0xed - 1 - uint16(s[0])) >> 8) + + return 1-(c&d&1) == 1 +} diff --git a/sign/eddsa/eddsa_test.go b/sign/eddsa/eddsa_test.go index 07b0d61d5..126139e76 100644 --- a/sign/eddsa/eddsa_test.go +++ b/sign/eddsa/eddsa_test.go @@ -6,14 +6,18 @@ import ( "compress/gzip" "crypto/cipher" "encoding/hex" + "fmt" + "math/big" "math/rand" "os" "strings" "testing" "go.dedis.ch/kyber/v3/group/edwards25519" + "go.dedis.ch/kyber/v3/util/random" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // EdDSATestVectors taken from RFC8032 section 7.1 @@ -101,6 +105,144 @@ func TestEdDSASigning(t *testing.T) { } } +// Test signature malleability +func TestEdDSAVerifyMalleability(t *testing.T) { + /* l = 2^252+27742317777372353535851937790883648493, prime order of the base point */ + var L []uint16 = []uint16{0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, + 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10} + var c uint16 = 0 + + suite := edwards25519.NewBlakeSHA256Ed25519() + randomStream := suite.RandomStream() + ed := NewEdDSA(randomStream) + + msg := random.Bits(256, true, randomStream) + + sig, err := ed.Sign(msg) + require.Nil(t, err) + require.Nil(t, Verify(ed.Public, msg, sig)) + + // Add l to signature + for i := 0; i < 32; i++ { + c += uint16(sig[32+i]) + L[i] + sig[32+i] = byte(c) + c >>= 8 + } + + err = Verify(ed.Public, msg, sig) + require.EqualError(t, err, "signature is not canonical") + + // Additional malleability test from golang/crypto + // https://github.com/golang/crypto/blob/master/ed25519/ed25519_test.go#L167 + msg2 := []byte{0x54, 0x65, 0x73, 0x74} + sig2 := []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} + + err = VerifyWithChecks(publicKey, msg2, sig2) + require.EqualError(t, err, "signature is not canonical") +} + +// Test non-canonical R +func TestEdDSAVerifyNonCanonicalR(t *testing.T) { + var nonCanonicalR []byte = []byte{0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + + suite := edwards25519.NewBlakeSHA256Ed25519() + randomStream := suite.RandomStream() + ed := NewEdDSA(randomStream) + + msg := random.Bits(256, true, randomStream) + + sig, err := ed.Sign(msg) + require.Nil(t, err) + require.Nil(t, Verify(ed.Public, msg, sig)) + + for i := 0; i < 32; i++ { + sig[i] = nonCanonicalR[i] + } + err = Verify(ed.Public, msg, sig) + require.EqualError(t, err, "R is not canonical") +} + +// Test non-canonical keys +func TestEdDSAVerifyNonCanonicalPK(t *testing.T) { + var nonCanonicalPk []byte = []byte{0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + + suite := edwards25519.NewBlakeSHA256Ed25519() + randomStream := suite.RandomStream() + ed := NewEdDSA(randomStream) + + msg := random.Bits(256, true, randomStream) + + sig, err := ed.Sign(msg) + require.Nil(t, err) + require.Nil(t, Verify(ed.Public, msg, sig)) + + err = VerifyWithChecks(nonCanonicalPk, msg, sig) + require.EqualError(t, err, "public key is not canonical") +} + +// Test for small order R +func TestEdDSAVerifySmallOrderR(t *testing.T) { + var smallOrderR []byte = []byte{0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, + 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, + 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a} + + suite := edwards25519.NewBlakeSHA256Ed25519() + randomStream := suite.RandomStream() + ed := NewEdDSA(randomStream) + + msg := random.Bits(256, true, randomStream) + + sig, err := ed.Sign(msg) + require.Nil(t, err) + require.Nil(t, Verify(ed.Public, msg, sig)) + + for i := 0; i < 32; i++ { + sig[i] = smallOrderR[i] + } + + err = Verify(ed.Public, msg, sig) + require.EqualError(t, err, "R has small order") +} + +// Test for small order public key +func TestEdDSAVerifySmallOrderPK(t *testing.T) { + var smallOrderPk []byte = []byte{0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, + 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, + 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a} + + suite := edwards25519.NewBlakeSHA256Ed25519() + randomStream := suite.RandomStream() + ed := NewEdDSA(randomStream) + + msg := random.Bits(256, true, randomStream) + + sig, err := ed.Sign(msg) + require.Nil(t, err) + require.Nil(t, Verify(ed.Public, msg, sig)) + + err = ed.Public.UnmarshalBinary(smallOrderPk) + require.Nil(t, err) + + err = Verify(ed.Public, msg, sig) + require.EqualError(t, err, "public key has small order") +} + // Test the property of a EdDSA signature func TestEdDSASigningRandom(t *testing.T) { suite := edwards25519.NewBlakeSHA256Ed25519() @@ -204,3 +346,60 @@ func TestGolden(t *testing.T) { t.Fatalf("error reading test data: %s", err) } } + +// Test_pointIsCanonical ensures that elements >= p are considered +// non canonical +func Test_pointIsCanonical(t *testing.T) { + + // buffer stores the candidate points (in little endian) that we'll test + // against, starting with `prime` + buffer := prime.Bytes() + for i, j := 0, len(buffer)-1; i < j; i, j = i+1, j-1 { + buffer[i], buffer[j] = buffer[j], buffer[i] + } + + // Iterate over the 19*2 finite field elements + point := group.Point() + actualNonCanonicalCount := 0 + expectedNonCanonicalCount := 24 + for i := 0; i < 19; i++ { + buffer[0] = byte(237 + i) + buffer[31] = byte(127) + + // Check if it's a valid point on the curve that's + // not canonical + err := point.UnmarshalBinary(buffer) + if err == nil && !pointIsCanonical(buffer) { + actualNonCanonicalCount++ + } + + // flip bit + buffer[31] |= 128 + + // Check if it's a valid point on the curve that's + // not canonical + err = point.UnmarshalBinary(buffer) + if err == nil && !pointIsCanonical(buffer) { + actualNonCanonicalCount++ + } + } + require.Equal(t, expectedNonCanonicalCount, actualNonCanonicalCount, "Incorrect number of non canonical points detected") +} + +func Test_scalarIsCanonical(t *testing.T) { + candidate := big.NewInt(-2) + candidate.Add(candidate, primeOrder) + + candidateBuf := candidate.Bytes() + for i, j := 0, len(candidateBuf)-1; i < j; i, j = i+1, j-1 { + candidateBuf[i], candidateBuf[j] = candidateBuf[j], candidateBuf[i] + } + + expected := []bool{true, true, false, false} + + // We check in range [L-2, L+4) + for i := 0; i < 4; i++ { + require.Equal(t, expected[i], scalarIsCanonical(candidateBuf), fmt.Sprintf("`lMinus2 + %d` does not pass canonicality test", i)) + candidateBuf[0]++ + } +}