package applications import ( "crypto/subtle" "cwtch.im/tapir" "cwtch.im/tapir/primitives" "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" ) // AuthMessage is exchanged between peers to obtain the Auth Capability type AuthMessage struct { LongTermPublicKey ed25519.PublicKey EphemeralPublicKey ed25519.PublicKey } // 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 { return new(AuthApp) } // Init runs the entire AuthApp protocol, at the end of the protocol either the connection is granted AUTH capability // or the connection is closed. func (ea AuthApp) Init(connection tapir.Connection) { longTermPubKey := ed25519.PublicKey(connection.ID().PublicKeyBytes()) ephemeralIdentity, _ := primitives.InitializeEphemeral() authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralIdentity.PublicKey()} serialized, _ := json.Marshal(authMessage) connection.Send(serialized) message := connection.Expect() var remoteAuthMessage AuthMessage err := json.Unmarshal(message, &remoteAuthMessage) if err != nil { connection.Close() return } key := primitives.Perform3DH(connection.ID(), &ephemeralIdentity, remoteAuthMessage.LongTermPublicKey, remoteAuthMessage.EphemeralPublicKey, connection.IsOutbound()) connection.SetEncryptionKey(key) // Wait to Sync (we need to ensure that both the Local and Remote server have turned encryption on // otherwise our next Send will fail. time.Sleep(time.Second) // TODO: Replace this with proper transcript primitive challengeRemote, err := json.Marshal(remoteAuthMessage) if err != nil { connection.Close() return } challengeLocal, err := json.Marshal(authMessage) if err != nil { connection.Close() return } challenge := sha3.New512() if connection.IsOutbound() { 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)) connection.SetCapability(AuthCapability) } else { log.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes) connection.Close() } }