2019-06-05 19:02:03 +00:00
|
|
|
package applications
|
2019-05-15 19:45:45 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/subtle"
|
2019-06-05 19:02:03 +00:00
|
|
|
"cwtch.im/tapir"
|
2019-05-15 19:45:45 +00:00
|
|
|
"encoding/json"
|
|
|
|
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
|
|
|
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
|
|
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
|
|
|
"golang.org/x/crypto/ed25519"
|
|
|
|
"golang.org/x/crypto/sha3"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2019-05-21 18:28:10 +00:00
|
|
|
// AuthMessage is exchanged between peers to obtain the Auth Capability
|
2019-05-15 19:45:45 +00:00
|
|
|
type AuthMessage struct {
|
|
|
|
LongTermPublicKey ed25519.PublicKey
|
|
|
|
EphemeralPublicKey ed25519.PublicKey
|
|
|
|
}
|
|
|
|
|
2019-05-21 18:28:10 +00:00
|
|
|
// AuthCapability defines the Authentication Capability granted by AuthApp
|
|
|
|
const AuthCapability = "AUTH"
|
|
|
|
|
|
|
|
// AuthApp is the concrete Application type that handles Authentication
|
|
|
|
type AuthApp struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewInstance creates a new instance of the AuthApp
|
2019-06-05 19:02:03 +00:00
|
|
|
func (ea AuthApp) NewInstance() tapir.Application {
|
2019-05-15 19:45:45 +00:00
|
|
|
return new(AuthApp)
|
|
|
|
}
|
|
|
|
|
2019-05-21 18:28:10 +00:00
|
|
|
// Init runs the entire AuthApp protocol, at the end of the protocol either the connection is granted AUTH capability
|
|
|
|
// or the connection is closed.
|
2019-06-05 19:02:03 +00:00
|
|
|
func (ea AuthApp) Init(connection *tapir.Connection) {
|
2019-05-15 19:45:45 +00:00
|
|
|
longTermPubKey := ed25519.PublicKey(connection.ID.PublicKeyBytes())
|
|
|
|
epk, esk, _ := ed25519.GenerateKey(rand.Reader)
|
|
|
|
ephemeralPublicKey := ed25519.PublicKey(epk)
|
|
|
|
ephemeralPrivateKey := ed25519.PrivateKey(esk)
|
|
|
|
ephemeralIdentity := identity.InitializeV3("", &ephemeralPrivateKey, &ephemeralPublicKey)
|
|
|
|
authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralPublicKey}
|
|
|
|
serialized, _ := json.Marshal(authMessage)
|
|
|
|
connection.Send(serialized)
|
|
|
|
message := connection.Expect()
|
|
|
|
|
|
|
|
var remoteAuthMessage AuthMessage
|
|
|
|
err := json.Unmarshal(message, &remoteAuthMessage)
|
|
|
|
if err != nil {
|
2019-06-05 19:02:03 +00:00
|
|
|
connection.Close()
|
2019-05-15 19:45:45 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3DH Handshake
|
|
|
|
l2e := connection.ID.EDH(remoteAuthMessage.EphemeralPublicKey)
|
|
|
|
e2l := ephemeralIdentity.EDH(remoteAuthMessage.LongTermPublicKey)
|
|
|
|
e2e := ephemeralIdentity.EDH(remoteAuthMessage.EphemeralPublicKey)
|
|
|
|
|
|
|
|
// We need to define an order for the result concatenation so that both sides derive the same key.
|
|
|
|
var result [96]byte
|
|
|
|
if connection.Outbound {
|
|
|
|
copy(result[0:32], l2e)
|
|
|
|
copy(result[32:64], e2l)
|
|
|
|
copy(result[64:96], e2e)
|
|
|
|
} else {
|
|
|
|
copy(result[0:32], e2l)
|
|
|
|
copy(result[32:64], l2e)
|
|
|
|
copy(result[64:96], e2e)
|
|
|
|
}
|
|
|
|
connection.SetEncryptionKey(sha3.Sum256(result[:]))
|
|
|
|
|
|
|
|
// Wait to Sync
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
|
|
|
// TODO: Replace this with proper transcript
|
|
|
|
challengeRemote, err := json.Marshal(remoteAuthMessage)
|
|
|
|
challengeLocal, err := json.Marshal(authMessage)
|
|
|
|
challenge := sha3.New512()
|
|
|
|
|
|
|
|
if connection.Outbound {
|
|
|
|
challenge.Write(challengeLocal)
|
|
|
|
challenge.Write(challengeRemote)
|
|
|
|
} else {
|
|
|
|
challenge.Write(challengeRemote)
|
|
|
|
challenge.Write(challengeLocal)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Since we have set the encryption key on the connection the connection will encrypt any messages we send with that key
|
|
|
|
// To test that the remote peer has done the same we calculate a challenge hash based on the transcript so far and send it to them
|
|
|
|
// We expect the remote to do the same, and compare the two.
|
|
|
|
// If successful we extend our auth capability to the connection and reassert the hostname.
|
|
|
|
challengeBytes := challenge.Sum([]byte{})
|
|
|
|
connection.Send(challengeBytes)
|
|
|
|
remoteChallenge := connection.Expect()
|
|
|
|
if subtle.ConstantTimeCompare(challengeBytes, remoteChallenge) == 1 {
|
|
|
|
connection.SetHostname(utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey))
|
2019-05-21 18:28:10 +00:00
|
|
|
connection.SetCapability(AuthCapability)
|
2019-05-15 19:45:45 +00:00
|
|
|
} else {
|
|
|
|
log.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes)
|
2019-06-05 19:02:03 +00:00
|
|
|
connection.Close()
|
2019-05-15 19:45:45 +00:00
|
|
|
}
|
|
|
|
}
|