package outbound import ( "crypto/rand" "errors" "git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/identity" "git.openprivacy.ca/openprivacy/libricochet-go/log" "git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/wire/auth/3edh" "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" "github.com/golang/protobuf/proto" "golang.org/x/crypto/ed25519" "golang.org/x/crypto/nacl/secretbox" "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/sha3" "io" ) // Client3DHAuthChannel wraps implementation of im.ricochet.auth.hidden-service" type Client3DHAuthChannel struct { // PrivateKey must be set for client-side authentication channels ClientIdentity identity.Identity ServerHostname string ClientAuthResult func(bool, bool) // Internal state serverPubKey, serverEphemeralPublicKey, clientEphemeralPublicKey ed25519.PublicKey clientEphemeralPrivateKey ed25519.PrivateKey channel *channels.Channel key [32]byte } // Type returns the type string for this channel, e.g. "im.ricochet.chat". func (ah *Client3DHAuthChannel) Type() string { return "im.ricochet.auth.3dh" } // Singleton Returns whether or not the given channel type is a singleton func (ah *Client3DHAuthChannel) Singleton() bool { return true } // OnlyClientCanOpen ... func (ah *Client3DHAuthChannel) OnlyClientCanOpen() bool { return true } // Bidirectional Returns whether or not the given channel allows anyone to send messages func (ah *Client3DHAuthChannel) Bidirectional() bool { return false } // RequiresAuthentication Returns whether or not the given channel type requires authentication func (ah *Client3DHAuthChannel) RequiresAuthentication() string { return "none" } // Closed is called when the channel is closed for any reason. func (ah *Client3DHAuthChannel) Closed(err error) { } // OpenInbound is the first method called for an inbound channel request. // Infof an error is returned, the channel is rejected. Infof a RawMessage is // returned, it will be sent as the ChannelResult message. // Remote -> [Open Authentication Channel] -> Local func (ah *Client3DHAuthChannel) OpenInbound(channel *channels.Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) { return nil, errors.New("server is not allowed to open inbound auth.3dh channels") } // OpenOutbound is the first method called for an outbound channel request. // Infof an error is returned, the channel is not opened. Infof a RawMessage is // returned, it will be sent as the OpenChannel message. // Local -> [Open Authentication Channel] -> Remote func (ah *Client3DHAuthChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) { ah.channel = channel log.Debugf("Opening an outbound connection to %v", ah.ServerHostname) // Generate Ephemeral Keys pubkey, privkey, _ := ed25519.GenerateKey(rand.Reader) ah.clientEphemeralPublicKey = pubkey ah.clientEphemeralPrivateKey = privkey messageBuilder := new(utils.MessageBuilder) channel.Pending = false var clientPubKeyBytes, clientEphemeralPubKeyBytes [32]byte copy(clientPubKeyBytes[:], ah.ClientIdentity.PublicKeyBytes()[:]) copy(clientEphemeralPubKeyBytes[:], ah.clientEphemeralPublicKey[:]) return messageBuilder.Open3EDHAuthenticationChannel(ah.channel.ID, clientPubKeyBytes, clientEphemeralPubKeyBytes), nil } // OpenOutboundResult is called when a response is received for an // outbound OpenChannel request. Infof `err` is non-nil, the channel was // rejected and Closed will be called immediately afterwards. `raw` // contains the raw protocol message including any extension data. // Input: Remote -> [ChannelResult] -> {Client} // Output: {Client} -> [Proof] -> Remote func (ah *Client3DHAuthChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) { serverPublicKey, _ := proto.GetExtension(crm, Protocol_Data_Auth_TripleEDH.E_ServerPublicKey) serverEphemeralPublicKey, _ := proto.GetExtension(crm, Protocol_Data_Auth_TripleEDH.E_ServerEphmeralPublicKey) serverPubKeyBytes := serverPublicKey.([]byte) ah.serverPubKey = ed25519.PublicKey(serverPubKeyBytes[:]) if utils.GetTorV3Hostname(ah.serverPubKey) != ah.ServerHostname { ah.channel.CloseChannel() return } serverEphmeralPublicKeyBytes := serverEphemeralPublicKey.([]byte) ah.serverEphemeralPublicKey = ed25519.PublicKey(serverEphmeralPublicKeyBytes[:]) log.Debugf("Public Keys Exchanged. Deriving Encryption Keys and Sending Encrypted Test Message") // Server Ephemeral <-> Client Identity secret1 := utils.EDH(ah.clientEphemeralPrivateKey, ah.serverPubKey) // Server Identity <-> Client Ephemeral secret2 := ah.ClientIdentity.EDH(ah.serverEphemeralPublicKey) // Ephemeral <-> Ephemeral secret3 := utils.EDH(ah.clientEphemeralPrivateKey, ah.serverEphemeralPublicKey) var secret [96]byte copy(secret[0:32], secret1[:]) copy(secret[32:64], secret2[:]) copy(secret[64:96], secret3[:]) var nonce [24]byte if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { panic(err) } pkey := pbkdf2.Key(secret[:], secret[:], 4096, 32, sha3.New512) copy(ah.key[:], pkey[:]) encrypted := secretbox.Seal(nonce[:], []byte("Hello World"), &nonce, &ah.key) messageBuilder := new(utils.MessageBuilder) proof := messageBuilder.Proof3DH(encrypted) ah.channel.SendMessage(proof) ah.channel.DelegateEncryption(ah.key) } // Packet is called for each raw packet received on this channel. // Input: Client -> [Proof] -> Remote // OR // Input: Remote -> [Result] -> Client func (ah *Client3DHAuthChannel) Packet(data []byte) { res := new(Protocol_Data_Auth_TripleEDH.Packet) err := proto.Unmarshal(data[:], res) if err != nil { ah.channel.CloseChannel() return } if res.GetResult() != nil { ah.ClientAuthResult(res.GetResult().GetAccepted(), res.GetResult().GetIsKnownContact()) if res.GetResult().GetAccepted() { log.Debugf("3DH Session Accepted OK. Authenticated! Connection!") ah.channel.DelegateAuthorization() } return } // Any other combination of packets is completely invalid // Fail the Authorization right here. ah.channel.CloseChannel() }