Skip to content

Commit

Permalink
aead, ecdh, skademlia: replaced Schnorr signatures for assembly-optim…
Browse files Browse the repository at this point in the history
…ized EdDSA, and remove dependency on Kyber

internal/edwards25519: add a scalar-point mul func GeScalarMult(), and use constant time compare on signature verification
  • Loading branch information
iwasaki-kenta committed Feb 16, 2019
1 parent 4f7caeb commit b697165
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 114 deletions.
23 changes: 15 additions & 8 deletions cipher/aead/crypto.go
Expand Up @@ -3,8 +3,8 @@ package aead
import (
"crypto/aes"
"crypto/cipher"
"github.com/perlin-network/noise/internal/edwards25519"
"github.com/pkg/errors"
"go.dedis.ch/kyber/v3"
"golang.org/x/crypto/hkdf"
"hash"
)
Expand All @@ -13,13 +13,8 @@ const sharedKeyLength = 32

// deriveCipherSuite derives an AEAD via. AES-256 GCM (Galois Counter Mode) cipher suite given an ephemeral shared key
// typically produced from a handshake/key exchange protocol.
func deriveCipherSuite(fn func() hash.Hash, ephemeralSharedKey kyber.Point, context []byte) (cipher.AEAD, []byte, error) {
ephemeralBuf, err := ephemeralSharedKey.MarshalBinary()
if err != nil {
return nil, nil, errors.Wrap(err, "failed to marshal ephemeral shared key for AEAD")
}

deriver := hkdf.New(fn, ephemeralBuf, nil, context)
func deriveCipherSuite(fn func() hash.Hash, ephemeralSharedKey []byte, context []byte) (cipher.AEAD, []byte, error) {
deriver := hkdf.New(fn, ephemeralSharedKey, nil, context)

sharedKey := make([]byte, sharedKeyLength)
if _, err := deriver.Read(sharedKey); err != nil {
Expand All @@ -31,3 +26,15 @@ func deriveCipherSuite(fn func() hash.Hash, ephemeralSharedKey kyber.Point, cont

return gcm, sharedKey, nil
}

func isEd25519GroupElement(buf []byte) bool {
if len(buf) != edwards25519.PublicKeySize {
return false
}

var buff [32]byte
copy(buff[:], buf)

var A edwards25519.ExtendedGroupElement
return A.FromBytes(&buff)
}
21 changes: 3 additions & 18 deletions cipher/aead/crypto_test.go
Expand Up @@ -2,26 +2,13 @@ package aead

import (
"crypto/sha256"
"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/group/edwards25519"
"math/rand"
"reflect"
"github.com/stretchr/testify/assert"
"testing"
"testing/quick"
)

type quickPoint struct {
kyber.Point
}

func (p *quickPoint) Generate(rand *rand.Rand, size int) reflect.Value {
curve := edwards25519.NewBlakeSHA256Ed25519()
scalar := curve.Scalar().Pick(curve.RandomStream())
return reflect.ValueOf(&quickPoint{Point: curve.Point().Mul(scalar, curve.Point().Base())})
}

func TestDeriveSharedKey(t *testing.T) {
check := func(ephemeralSharedKey *quickPoint, context []byte) bool {
check := func(ephemeralSharedKey []byte, context []byte) bool {
_, _, err := deriveCipherSuite(sha256.New, ephemeralSharedKey, context)

if err != nil {
Expand All @@ -31,7 +18,5 @@ func TestDeriveSharedKey(t *testing.T) {
return true
}

if err := quick.Check(check, nil); err != nil {
t.Error(err)
}
assert.NoError(t, quick.Check(check, nil))
}
21 changes: 5 additions & 16 deletions cipher/aead/mod.go
Expand Up @@ -4,11 +4,9 @@ import (
"crypto/sha256"
"encoding/binary"
"github.com/perlin-network/noise"
"github.com/perlin-network/noise/crypto"
"github.com/perlin-network/noise/log"
"github.com/perlin-network/noise/protocol"
"github.com/pkg/errors"
"go.dedis.ch/kyber/v3/group/edwards25519"
"hash"
"sync/atomic"
"time"
Expand All @@ -24,24 +22,18 @@ type block struct {
opcodeACK noise.Opcode

ackTimeout time.Duration
curve crypto.EllipticSuite
hash func() hash.Hash
}

func New() *block {
return &block{hash: sha256.New, curve: edwards25519.NewBlakeSHA256Ed25519(), ackTimeout: 3 * time.Second}
return &block{hash: sha256.New, ackTimeout: 3 * time.Second}
}

func (b *block) WithHash(hash func() hash.Hash) *block {
b.hash = hash
return b
}

func (b *block) WithCurve(curve crypto.EllipticSuite) *block {
b.curve = curve
return b
}

func (b *block) WithACKTimeout(ackTimeout time.Duration) *block {
b.ackTimeout = ackTimeout
return b
Expand All @@ -52,17 +44,14 @@ func (b *block) OnRegister(p *protocol.Protocol, node *noise.Node) {
}

func (b *block) OnBegin(p *protocol.Protocol, peer *noise.Peer) error {
ephemeralSharedKeyBuf := protocol.LoadSharedKey(peer)
ephemeralSharedKey := protocol.LoadSharedKey(peer)

if ephemeralSharedKeyBuf == nil {
if ephemeralSharedKey == nil {
return errors.Wrap(protocol.DisconnectPeer, "session was established, but no ephemeral shared key found")
}

ephemeralSharedKey := b.curve.Point()

err := ephemeralSharedKey.UnmarshalBinary(ephemeralSharedKeyBuf)
if err != nil {
return errors.Wrap(errors.Wrap(protocol.DisconnectPeer, err.Error()), "failed to unmarshal ephemeral shared key buf")
if !isEd25519GroupElement(ephemeralSharedKey) {
return errors.Wrap(protocol.DisconnectPeer, "failed to unmarshal ephemeral shared key buf")
}

suite, sharedKey, err := deriveCipherSuite(b.hash, ephemeralSharedKey, nil)
Expand Down
3 changes: 1 addition & 2 deletions cipher/aead/mod_test.go
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/perlin-network/noise/transport"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"go.dedis.ch/kyber/v3/group/edwards25519"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -72,7 +71,7 @@ func TestBlock_OnBeginEdgeCases(t *testing.T) {
defer bob.Kill()

// Enforce protocols.
block := New().WithHash(sha512.New).WithCurve(edwards25519.NewBlakeSHA256Ed25519()).WithACKTimeout(1 * time.Millisecond)
block := New().WithHash(sha512.New).WithACKTimeout(1 * time.Millisecond)
proto := protocol.New().Register(block)

proto.Enforce(alice)
Expand Down
10 changes: 0 additions & 10 deletions crypto/suite.go

This file was deleted.

3 changes: 1 addition & 2 deletions go.mod
@@ -1,12 +1,11 @@
module github.com/perlin-network/noise

require (
github.com/golang/mock v1.2.0
github.com/huin/goupnp v1.0.0
github.com/jackpal/go-nat-pmp v1.0.1
github.com/pkg/errors v0.8.1
github.com/rs/zerolog v1.11.0
github.com/stretchr/testify v1.3.0
go.dedis.ch/kyber/v3 v3.0.0-pre2
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b
golang.org/x/sys v0.0.0-20190124100055-b90733256f2e // indirect
)
7 changes: 0 additions & 7 deletions go.sum
@@ -1,7 +1,5 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo=
github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
Expand All @@ -16,11 +14,6 @@ github.com/rs/zerolog v1.11.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs=
go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw=
go.dedis.ch/kyber/v3 v3.0.0-pre2 h1:ezviD36AEOytXJn91tsvQeR+rEzo3UOh75P/PzSisRo=
go.dedis.ch/kyber/v3 v3.0.0-pre2/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ=
go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRLo=
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b h1:Elez2XeF2p9uyVj0yEUDqQ56NFcDtcBNkYP7yv8YbUE=
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
Expand Down
40 changes: 36 additions & 4 deletions handshake/ecdh/crypto.go
@@ -1,10 +1,42 @@
package ecdh

import (
"github.com/perlin-network/noise/crypto"
"go.dedis.ch/kyber/v3"
"crypto/sha512"
"github.com/perlin-network/noise/internal/edwards25519"
)

func computeSharedKey(suite crypto.EllipticSuite, nodePrivateKey kyber.Scalar, remotePublicKey kyber.Point) kyber.Point {
return suite.Point().Mul(nodePrivateKey, remotePublicKey)
func computeSharedKey(nodePrivateKey edwards25519.PrivateKey, remotePublicKey edwards25519.PublicKey) []byte {
var nodeSecretKeyBuf, remotePublicKeyBuf, sharedKeyBuf [32]byte
copy(nodeSecretKeyBuf[:], deriveSecretKey(nodePrivateKey))
copy(remotePublicKeyBuf[:], remotePublicKey[:])

var sharedKeyElement, publicKeyElement edwards25519.ExtendedGroupElement
publicKeyElement.FromBytes(&remotePublicKeyBuf)

edwards25519.GeScalarMult(&sharedKeyElement, &nodeSecretKeyBuf, &publicKeyElement)

sharedKeyElement.ToBytes(&sharedKeyBuf)

return sharedKeyBuf[:]
}

func deriveSecretKey(privateKey edwards25519.PrivateKey) []byte {
digest := sha512.Sum512(privateKey[:32])
digest[0] &= 248
digest[31] &= 127
digest[31] |= 64

return digest[:32]
}

func isEd25519GroupElement(buf []byte) bool {
if len(buf) != edwards25519.PublicKeySize {
return false
}

var buff [32]byte
copy(buff[:], buf)

var A edwards25519.ExtendedGroupElement
return A.FromBytes(&buff)
}
55 changes: 16 additions & 39 deletions handshake/ecdh/mod.go
@@ -1,13 +1,12 @@
package ecdh

import (
"crypto"
"github.com/perlin-network/noise"
"github.com/perlin-network/noise/crypto"
"github.com/perlin-network/noise/internal/edwards25519"
"github.com/perlin-network/noise/log"
"github.com/perlin-network/noise/protocol"
"github.com/pkg/errors"
"go.dedis.ch/kyber/v3/group/edwards25519"
"go.dedis.ch/kyber/v3/sign/schnorr"
"time"
)

Expand All @@ -19,27 +18,19 @@ var (

type block struct {
opcodeHandshake noise.Opcode

suite crypto.EllipticSuite
timeoutDuration time.Duration
}

// New returns an ECDH policy with sensible defaults.
//
// By default, should a peer not complete the handshake protocol in 10 seconds, they will be disconnected.
// All handshake-related messages are appended with Schnorr signatures that are automatically verified.
// All handshake-related messages are appended with ECDSA signatures that are automatically verified.
func New() *block {
return &block{
suite: edwards25519.NewBlakeSHA256Ed25519(),
timeoutDuration: 10 * time.Second,
}
}

func (b *block) WithSuite(suite crypto.EllipticSuite) *block {
b.suite = suite
return b
}

func (b *block) TimeoutAfter(timeoutDuration time.Duration) *block {
b.timeoutDuration = timeoutDuration
return b
Expand All @@ -51,20 +42,15 @@ func (b *block) OnRegister(p *protocol.Protocol, node *noise.Node) {

func (b *block) OnBegin(p *protocol.Protocol, peer *noise.Peer) error {
// Send a handshake request with a generated ephemeral keypair.
ephemeralPrivateKey := b.suite.Scalar().Pick(b.suite.RandomStream())
ephemeralPublicKey := b.suite.Point().Mul(ephemeralPrivateKey, b.suite.Point().Base())

var err error
var req Handshake

req.publicKey, err = ephemeralPublicKey.MarshalBinary()
ephemeralPublicKey, ephemeralPrivateKey, err := edwards25519.GenerateKey(nil)
if err != nil {
return errors.Wrap(errors.Wrap(protocol.DisconnectPeer, err.Error()), "failed to marshal ephemeral public key into binary")
return errors.Wrap(errors.Wrap(protocol.DisconnectPeer, err.Error()), "failed to generate ephemeral keypair")
}

req.signature, err = schnorr.Sign(b.suite, ephemeralPrivateKey, []byte(msgEphemeralHandshake))
req := Handshake{publicKey: ephemeralPublicKey}
req.signature, err = ephemeralPrivateKey.Sign(nil, []byte(msgEphemeralHandshake), crypto.Hash(0))
if err != nil {
return errors.Wrap(errors.Wrap(protocol.DisconnectPeer, err.Error()), "failed to sign handshake message using Schnorr signature scheme")
return errors.Wrap(errors.Wrap(protocol.DisconnectPeer, err.Error()), "failed to sign handshake message using ECDSA")
}

err = peer.SendMessage(req)
Expand All @@ -86,30 +72,21 @@ func (b *block) OnBegin(p *protocol.Protocol, peer *noise.Peer) error {
}
}

peersPublicKey := b.suite.Point()
peersPublicKey := edwards25519.PublicKey(res.publicKey)

err = peersPublicKey.UnmarshalBinary(res.publicKey)
if err != nil {
return errors.Wrap(errors.Wrap(protocol.DisconnectPeer, err.Error()), "failed to unmarshal our peers ephemeral public key")
}

err = schnorr.Verify(b.suite, peersPublicKey, []byte(msgEphemeralHandshake), res.signature)
if err != nil {
return errors.Wrap(errors.Wrap(protocol.DisconnectPeer, err.Error()), "failed to verify signature in handshake request")
if !isEd25519GroupElement(peersPublicKey) {
return errors.Wrap(protocol.DisconnectPeer, "failed to unmarshal our peers ephemeral public key")
}

ephemeralSharedKey := computeSharedKey(b.suite, ephemeralPrivateKey, peersPublicKey)

sharedKeyBuf, err := ephemeralSharedKey.MarshalBinary()
if err != nil {
peer.Disconnect()
return errors.Wrap(err, "failed to marshal post-handshake shared key")
if !edwards25519.Verify(peersPublicKey, []byte(msgEphemeralHandshake), res.signature) {
return errors.Wrap(protocol.DisconnectPeer, "failed to verify signature in handshake request")
}

protocol.SetSharedKey(peer, sharedKeyBuf)
ephemeralSharedKey := computeSharedKey(ephemeralPrivateKey, peersPublicKey)
protocol.SetSharedKey(peer, ephemeralSharedKey[:])

log.Debug().
Str("ephemeral_shared_key", ephemeralSharedKey.String()).
Hex("ephemeral_shared_key", ephemeralSharedKey[:]).
Msg("Successfully performed ECDH with our peer.")

return nil
Expand Down

0 comments on commit b697165

Please sign in to comment.