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"
2020-02-06 23:54:13 +00:00
torProvider "git.openprivacy.ca/openprivacy/connectivity/tor"
"git.openprivacy.ca/openprivacy/log"
2019-05-15 19:45:45 +00:00
"golang.org/x/crypto/ed25519"
)
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
2019-09-15 21:20:05 +00:00
const AuthCapability = tapir . Capability ( "AuthenticationCapability" )
2019-05-21 18:28:10 +00:00
// AuthApp is the concrete Application type that handles Authentication
type AuthApp struct {
2019-09-14 23:44:19 +00:00
TranscriptApp
2019-05-21 18:28:10 +00:00
}
// 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-09-14 23:44:19 +00:00
func ( ea * AuthApp ) Init ( connection tapir . Connection ) {
ea . TranscriptApp . Init ( connection )
2019-08-07 20:08:02 +00:00
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
2020-02-06 23:54:13 +00:00
if connection . IsOutbound ( ) && torProvider . GetTorV3Hostname ( remoteAuthMessage . LongTermPublicKey ) != connection . Hostname ( ) {
log . Errorf ( "The remote server (%v) has attempted to authenticate with a different public key %v" , connection . Hostname ( ) , torProvider . GetTorV3Hostname ( remoteAuthMessage . LongTermPublicKey ) )
2019-08-12 20:04:39 +00:00
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-26 19:23:21 +00:00
// We just successfully unmarshaled both of these, so we can safely ignore the err return from these functions.
challengeRemote , _ := json . Marshal ( remoteAuthMessage )
challengeLocal , _ := json . Marshal ( authMessage )
2019-05-15 19:45:45 +00:00
2019-11-26 21:10:09 +00:00
// Define canonical labels so both sides of the connection can generate the same key
2019-08-26 19:23:21 +00:00
var outboundAuthMessage [ ] byte
var outboundHostname string
var inboundAuthMessage [ ] byte
var inboundHostname string
2019-05-15 19:45:45 +00:00
2019-08-07 20:08:02 +00:00
if connection . IsOutbound ( ) {
2019-08-26 19:23:21 +00:00
outboundHostname = connection . ID ( ) . Hostname ( )
2020-02-06 23:54:13 +00:00
inboundHostname = torProvider . GetTorV3Hostname ( remoteAuthMessage . LongTermPublicKey )
2019-08-26 19:23:21 +00:00
outboundAuthMessage = challengeLocal
inboundAuthMessage = challengeRemote
2019-05-15 19:45:45 +00:00
} else {
2020-02-06 23:54:13 +00:00
outboundHostname = torProvider . GetTorV3Hostname ( remoteAuthMessage . LongTermPublicKey )
2019-08-26 19:23:21 +00:00
inboundHostname = connection . ID ( ) . Hostname ( )
outboundAuthMessage = challengeRemote
inboundAuthMessage = challengeLocal
2019-05-15 19:45:45 +00:00
}
2019-08-26 19:23:21 +00:00
// Derive a challenge from the transcript of the public parameters of this authentication protocol
2019-09-14 23:44:19 +00:00
transcript := ea . Transcript ( )
2019-09-15 21:20:05 +00:00
transcript . NewProtocol ( "auth-app" )
transcript . AddToTranscript ( "outbound-hostname" , [ ] byte ( outboundHostname ) )
transcript . AddToTranscript ( "inbound-hostname" , [ ] byte ( inboundHostname ) )
2019-08-26 19:23:21 +00:00
transcript . AddToTranscript ( "outbound-challenge" , outboundAuthMessage )
transcript . AddToTranscript ( "inbound-challenge" , inboundAuthMessage )
challengeBytes := transcript . CommitToTranscript ( "3dh-auth-challenge" )
// If debug is turned on we will dump the transcript to log.
// There is nothing sensitive in this transcript
log . Debugf ( "Transcript: %s" , transcript . OutputTranscriptToAudit ( ) )
2019-05-15 19:45:45 +00:00
// 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
2019-09-15 21:20:05 +00:00
// along with our hostname
2019-05-15 19:45:45 +00:00
// 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.
2019-08-26 19:23:21 +00:00
// We note that the only successful scenario here requires that the remote peer have successfully derived the same
// encryption key and the same transcript challenge.
2019-09-15 21:20:05 +00:00
connection . Send ( append ( challengeBytes , [ ] byte ( connection . ID ( ) . Hostname ( ) ) ... ) )
2019-05-15 19:45:45 +00:00
remoteChallenge := connection . Expect ( )
2020-02-06 23:54:13 +00:00
assertedHostname := torProvider . GetTorV3Hostname ( remoteAuthMessage . LongTermPublicKey )
2019-09-15 21:20:05 +00:00
if subtle . ConstantTimeCompare ( append ( challengeBytes , [ ] byte ( assertedHostname ) ... ) , remoteChallenge ) == 1 {
connection . SetHostname ( assertedHostname )
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
}
}