tapir/applications/auth.go

93 lines
3.1 KiB
Go
Raw Normal View History

package applications
2019-05-15 19:45:45 +00:00
import (
"crypto/subtle"
"cwtch.im/tapir"
"cwtch.im/tapir/primitives"
2019-05-15 19:45:45 +00:00
"encoding/json"
"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
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-08-07 20:08:02 +00:00
func (ea AuthApp) Init(connection tapir.Connection) {
longTermPubKey := ed25519.PublicKey(connection.ID().PublicKeyBytes())
ephemeralIdentity, _ := primitives.InitializeEphemeral()
authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralIdentity.PublicKey()}
2019-05-15 19:45:45 +00:00
serialized, _ := json.Marshal(authMessage)
connection.Send(serialized)
message := connection.Expect()
var remoteAuthMessage AuthMessage
err := json.Unmarshal(message, &remoteAuthMessage)
if err != nil {
connection.Close()
2019-05-15 19:45:45 +00:00
return
}
key := primitives.Perform3DH(connection.ID(), &ephemeralIdentity, remoteAuthMessage.LongTermPublicKey, remoteAuthMessage.EphemeralPublicKey, connection.IsOutbound())
connection.SetEncryptionKey(key)
2019-05-15 19:45:45 +00:00
2019-08-07 20:08:02 +00:00
// Wait to Sync (we need to ensure that both the Local and Remote server have turned encryption on
// otherwise our next Send will fail.
2019-05-15 19:45:45 +00:00
time.Sleep(time.Second)
2019-08-07 20:08:02 +00:00
// TODO: Replace this with proper transcript primitive
2019-05-15 19:45:45 +00:00
challengeRemote, err := json.Marshal(remoteAuthMessage)
2019-08-07 20:08:02 +00:00
if err != nil {
connection.Close()
return
}
2019-05-15 19:45:45 +00:00
challengeLocal, err := json.Marshal(authMessage)
2019-08-07 20:08:02 +00:00
if err != nil {
connection.Close()
return
}
2019-05-15 19:45:45 +00:00
challenge := sha3.New512()
2019-08-07 20:08:02 +00:00
if connection.IsOutbound() {
2019-05-15 19:45:45 +00:00
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)
connection.Close()
2019-05-15 19:45:45 +00:00
}
}