2019-06-05 19:02:03 +00:00
package applications
2019-05-15 19:45:45 +00:00
import (
"crypto/subtle"
2019-06-05 19:02:03 +00:00
"cwtch.im/tapir"
2019-08-08 18:11:31 +00:00
"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
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-08-07 20:08:02 +00:00
func ( ea AuthApp ) Init ( connection tapir . Connection ) {
longTermPubKey := ed25519 . PublicKey ( connection . ID ( ) . PublicKeyBytes ( ) )
2019-08-08 19:07:13 +00:00
ephemeralIdentity , _ := primitives . InitializeEphemeralIdentity ( )
2019-08-08 18:11:31 +00:00
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 {
2019-06-05 19:02:03 +00:00
connection . Close ( )
2019-05-15 19:45:45 +00:00
return
}
2019-08-12 20:04:39 +00:00
// If we are an outbound connection we can perform an additional check to ensure that the server sent us back the correct long term
// public key
if connection . IsOutbound ( ) && utils . GetTorV3Hostname ( remoteAuthMessage . LongTermPublicKey ) != connection . Hostname ( ) {
log . Errorf ( "The remote server (%v) has attempted to authenticate with a different public key %v" , connection . Hostname ( ) , utils . GetTorV3Hostname ( remoteAuthMessage . LongTermPublicKey ) )
connection . Close ( )
return
}
// Perform the triple-diffie-hellman exchange.
2019-08-08 18:11:31 +00:00
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 )
2019-06-05 19:02:03 +00:00
connection . Close ( )
2019-05-15 19:45:45 +00:00
}
}