diff --git a/applications/auth.go b/applications/auth.go index f8750ab..898bd55 100644 --- a/applications/auth.go +++ b/applications/auth.go @@ -56,7 +56,12 @@ func (ea *AuthApp) Init(connection tapir.Connection) { } // Perform the triple-diffie-hellman exchange. - key := primitives.Perform3DH(connection.ID(), &ephemeralIdentity, remoteAuthMessage.LongTermPublicKey, remoteAuthMessage.EphemeralPublicKey, connection.IsOutbound()) + key, err := primitives.Perform3DH(connection.ID(), &ephemeralIdentity, remoteAuthMessage.LongTermPublicKey, remoteAuthMessage.EphemeralPublicKey, connection.IsOutbound()) + if err != nil { + log.Errorf("Failed Auth Challenge %v", err) + connection.Close() + return + } connection.SetEncryptionKey(key) // We just successfully unmarshaled both of these, so we can safely ignore the err return from these functions. diff --git a/go.mod b/go.mod index 10637e0..8acb5dd 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,14 @@ module cwtch.im/tapir require ( git.openprivacy.ca/openprivacy/connectivity v1.1.0 git.openprivacy.ca/openprivacy/log v1.0.0 - github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 github.com/davecgh/go-spew v1.1.1 // indirect github.com/gtank/merlin v0.1.1 github.com/gtank/ristretto255 v0.1.2 github.com/kr/pretty v0.2.0 // indirect github.com/stretchr/testify v1.4.0 // indirect - go.etcd.io/bbolt v1.3.3 + go.etcd.io/bbolt v1.3.4 golang.org/x/crypto v0.0.0-20200206161412-a0c6ece9d31a golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect - golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect ) diff --git a/go.sum b/go.sum index 6642113..da9baef 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ git.openprivacy.ca/openprivacy/connectivity v1.1.0 h1:9PEeKuPdoIRYeA62BUkBW2BfK4 git.openprivacy.ca/openprivacy/connectivity v1.1.0/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E= git.openprivacy.ca/openprivacy/log v1.0.0 h1:Rvqm1weUdR4AOnJ79b1upHCc9vC/QF1rhSD2Um7sr1Y= git.openprivacy.ca/openprivacy/log v1.0.0/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= -github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= -github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca h1:Q2r7AxHdJwWfLtBZwvW621M3sPqxPc6ITv2j1FGsYpw= github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -26,8 +24,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200206161412-a0c6ece9d31a h1:aczoJ0HPNE92XKa7DrIzkNN6esOKO2TBwiiYoKcINhA= diff --git a/networks/tor/BaseOnionService.go b/networks/tor/BaseOnionService.go index bf6f1f9..f9e0080 100644 --- a/networks/tor/BaseOnionService.go +++ b/networks/tor/BaseOnionService.go @@ -20,7 +20,7 @@ type BaseOnionService struct { id *primitives.Identity privateKey ed25519.PrivateKey ls connectivity.ListenService - lock sync.Mutex + lock sync.Mutex } // Init initializes a BaseOnionService with a given private key and identity diff --git a/primitives/3DH.go b/primitives/3DH.go index a84cbd0..1c9d611 100644 --- a/primitives/3DH.go +++ b/primitives/3DH.go @@ -14,11 +14,15 @@ import ( // Alice Ephemeral <-> Bob Ephemeral // // Through this, a unique session key is derived. The exchange is offline-deniable (in the context of Tapir and Onion Service) -func Perform3DH(longtermIdentity *Identity, ephemeralIdentity *Identity, remoteLongTermPublicKey ed25519.PublicKey, remoteEphemeralPublicKey ed25519.PublicKey, outbound bool) [32]byte { +func Perform3DH(longtermIdentity *Identity, ephemeralIdentity *Identity, remoteLongTermPublicKey ed25519.PublicKey, remoteEphemeralPublicKey ed25519.PublicKey, outbound bool) ([32]byte, error) { // 3DH Handshake - l2e := longtermIdentity.EDH(remoteEphemeralPublicKey) - e2l := ephemeralIdentity.EDH(remoteLongTermPublicKey) - e2e := ephemeralIdentity.EDH(remoteEphemeralPublicKey) + l2e, err1 := longtermIdentity.EDH(remoteEphemeralPublicKey) + e2l, err2 := ephemeralIdentity.EDH(remoteLongTermPublicKey) + e2e, err3 := ephemeralIdentity.EDH(remoteEphemeralPublicKey) + + if err1 != nil || err2 != nil || err3 != nil { + return [32]byte{}, err1 + } // We need to define an order for the result concatenation so that both sides derive the same key. var result [96]byte @@ -31,5 +35,5 @@ func Perform3DH(longtermIdentity *Identity, ephemeralIdentity *Identity, remoteL copy(result[32:64], l2e) copy(result[64:96], e2e) } - return sha3.Sum256(result[:]) + return sha3.Sum256(result[:]), nil } diff --git a/primitives/identity.go b/primitives/identity.go index 54f7217..1ac8995 100644 --- a/primitives/identity.go +++ b/primitives/identity.go @@ -43,9 +43,9 @@ func (i *Identity) PublicKey() ed25519.PublicKey { } // EDH performs a diffie-hellman operation on this identities private key with the given public key. -func (i *Identity) EDH(key ed25519.PublicKey) []byte { - secret := utils.EDH(*i.edpk, key) - return secret[:] +func (i *Identity) EDH(key ed25519.PublicKey) ([]byte, error) { + secret, err := utils.EDH(*i.edpk, key) + return secret[:], err } // Hostname provides the onion address associated with this Identity. diff --git a/primitives/identity_test.go b/primitives/identity_test.go index 49f5e23..9f3002c 100644 --- a/primitives/identity_test.go +++ b/primitives/identity_test.go @@ -1,6 +1,7 @@ package primitives import ( + "crypto/subtle" "testing" ) @@ -9,9 +10,28 @@ func TestIdentity_EDH(t *testing.T) { id1, _ := InitializeEphemeralIdentity() id2, _ := InitializeEphemeralIdentity() - k1 := id1.EDH(id2.PublicKey()) - k2 := id2.EDH(id1.PublicKey()) + k1, err1 := id1.EDH(id2.PublicKey()) + k2, err2 := id2.EDH(id1.PublicKey()) - t.Logf("k1: %x\nk2: %x\n", k1, k2) + if err1 == nil && err2 == nil && subtle.ConstantTimeCompare(k1, k2) == 1 { + t.Logf("k1: %x\nk2: %x\n", k1, k2) + } else { + t.Fatalf("The derived keys should be identical") + } } + +func BenchmarkEDH(b *testing.B) { + id1, _ := InitializeEphemeralIdentity() + id2, _ := InitializeEphemeralIdentity() + + for i := 0; i < b.N; i++ { + k1, err1 := id1.EDH(id2.PublicKey()) + k2, err2 := id2.EDH(id1.PublicKey()) + if err1 == nil && err2 == nil && subtle.ConstantTimeCompare(k1, k2) == 1 { + //b.Logf("k1: %x\nk2: %x\n", k1, k2) + } else { + b.Fatalf("The derived keys should be identical") + } + } +} diff --git a/utils/crypto.go b/utils/crypto.go index 1e01942..5bcfda1 100644 --- a/utils/crypto.go +++ b/utils/crypto.go @@ -1,22 +1,95 @@ package utils import ( - "github.com/agl/ed25519/extra25519" + "crypto/sha512" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/ed25519" + "math/big" ) // EDH implements diffie hellman using curve25519 keys derived from ed25519 keys -// NOTE: This uses a 3rd party library extra25519 as the key conversion is not in the core golang lib -// as such this definitely needs further review. -func EDH(privateKey ed25519.PrivateKey, remotePublicKey ed25519.PublicKey) [32]byte { +func EDH(privateKey ed25519.PrivateKey, remotePublicKey ed25519.PublicKey) ([]byte, error) { var privKeyBytes [64]byte var remotePubKeyBytes [32]byte copy(privKeyBytes[:], privateKey[:]) copy(remotePubKeyBytes[:], remotePublicKey[:]) - var secret, curve25519priv, curve25519pub [32]byte - extra25519.PrivateKeyToCurve25519(&curve25519priv, &privKeyBytes) - extra25519.PublicKeyToCurve25519(&curve25519pub, &remotePubKeyBytes) - curve25519.ScalarMult(&secret, &curve25519priv, &curve25519pub) - return secret + var curve25519priv [32]byte + + PrivateKeyToCurve25519(&curve25519priv, &privKeyBytes) + curve25519pub := ed25519PublicKeyToCurve25519(remotePublicKey) + secret, err := curve25519.X25519(curve25519priv[:], curve25519pub[:]) + return secret, err +} + +// https://github.com/FiloSottile/age/blob/master/internal/age/ssh.go#L174 +// Copyright 2019 Google LLC +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are +//met: +// +// * Redistributions of source code must retain the above copyright +//notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +//copyright notice, this list of conditions and the following disclaimer +//in the documentation and/or other materials provided with the +//distribution. +// * Neither the name of Google LLC nor the names of its +//contributors may be used to endorse or promote products derived from +//this software without specific prior written permission. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +//"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +//LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +//A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +//OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +//LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +//DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +//THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +//OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10) + +func ed25519PublicKeyToCurve25519(pk ed25519.PublicKey) []byte { + // ed25519.PublicKey is a little endian representation of the y-coordinate, + // with the most significant bit set based on the sign of the x-coordinate. + bigEndianY := make([]byte, ed25519.PublicKeySize) + for i, b := range pk { + bigEndianY[ed25519.PublicKeySize-i-1] = b + } + bigEndianY[0] &= 0b0111_1111 + + // The Montgomery u-coordinate is derived through the bilinear map + // + // u = (1 + y) / (1 - y) + // + // See https://blog.filippo.io/using-ed25519-keys-for-encryption. + y := new(big.Int).SetBytes(bigEndianY) + denom := big.NewInt(1) + denom.ModInverse(denom.Sub(denom, y), curve25519P) // 1 / (1 - y) + u := y.Mul(y.Add(y, big.NewInt(1)), denom) + u.Mod(u, curve25519P) + + out := make([]byte, curve25519.PointSize) + uBytes := u.Bytes() + for i, b := range uBytes { + out[len(uBytes)-i-1] = b + } + + return out +} + +// PrivateKeyToCurve25519 converts an ed25519 private key into a corresponding +// curve25519 private key +func PrivateKeyToCurve25519(curve25519Private *[32]byte, privateKey *[64]byte) { + h := sha512.New() + h.Write(privateKey[:32]) + digest := h.Sum(nil) + + digest[0] &= 248 + digest[31] &= 127 + digest[31] |= 64 + + copy(curve25519Private[:], digest) }