package inbound 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" ) // Server3DHAuthChannel wraps implementation of im.ricochet.auth.hidden-service" type Server3DHAuthChannel struct { // PrivateKey must be set for client-side authentication channels ServerIdentity identity.Identity // Callbacks ServerAuthValid func(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) ServerAuthInvalid func(err error) // Internal state clientPubKey, clientEphmeralPublicKey, serverEphemeralPublicKey ed25519.PublicKey serverEphemeralPrivateKey ed25519.PrivateKey channel *channels.Channel } // Type returns the type string for this channel, e.g. "im.ricochet.chat". func (ah *Server3DHAuthChannel) Type() string { return "im.ricochet.auth.3dh" } // Singleton Returns whether or not the given channel type is a singleton func (ah *Server3DHAuthChannel) Singleton() bool { return true } // OnlyClientCanOpen ... func (ah *Server3DHAuthChannel) OnlyClientCanOpen() bool { return true } // Bidirectional Returns whether or not the given channel allows anyone to send messages func (ah *Server3DHAuthChannel) Bidirectional() bool { return false } // RequiresAuthentication Returns whether or not the given channel type requires authentication func (ah *Server3DHAuthChannel) RequiresAuthentication() string { return "none" } // Closed is called when the channel is closed for any reason. func (ah *Server3DHAuthChannel) 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 *Server3DHAuthChannel) OpenInbound(channel *channels.Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) { ah.channel = channel clientPublicKey, _ := proto.GetExtension(oc, Protocol_Data_Auth_TripleEDH.E_ClientPublicKey) clientEphmeralPublicKey, _ := proto.GetExtension(oc, Protocol_Data_Auth_TripleEDH.E_ClientEphmeralPublicKey) clientPubKeyBytes := clientPublicKey.([]byte) ah.clientPubKey = ed25519.PublicKey(clientPubKeyBytes[:]) clientEphmeralPublicKeyBytes := clientEphmeralPublicKey.([]byte) ah.clientEphmeralPublicKey = ed25519.PublicKey(clientEphmeralPublicKeyBytes[:]) clientHostname := utils.GetTorV3Hostname(clientPubKeyBytes) log.Debugf("Received inbound auth 3DH request from %v", clientHostname) // Generate Ephemeral Keys pubkey, privkey, _ := ed25519.GenerateKey(rand.Reader) ah.serverEphemeralPublicKey = pubkey ah.serverEphemeralPrivateKey = privkey var serverPubKeyBytes, serverEphemeralPubKeyBytes [32]byte copy(serverPubKeyBytes[:], ah.ServerIdentity.PublicKeyBytes()[:]) copy(serverEphemeralPubKeyBytes[:], ah.serverEphemeralPublicKey[:]) messageBuilder := new(utils.MessageBuilder) channel.Pending = false return messageBuilder.Confirm3EDHAuthChannel(ah.channel.ID, serverPubKeyBytes, serverEphemeralPubKeyBytes), nil } // 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 *Server3DHAuthChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) { return nil, errors.New("server is not allowed to open 3dh channels") } // 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 *Server3DHAuthChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) { // } // Packet is called for each raw packet received on this channel. // Input: Client -> [Proof] -> Remote // OR // Input: Remote -> [Result] -> Client func (ah *Server3DHAuthChannel) Packet(data []byte) { res := new(Protocol_Data_Auth_TripleEDH.Packet) err := proto.Unmarshal(data[:], res) if err != nil { ah.channel.CloseChannel() return } if res.GetProof() != nil && ah.channel.Direction == channels.Inbound { // Server Identity <-> Client Ephemeral secret1 := ah.ServerIdentity.EDH(ah.clientEphmeralPublicKey) // Server Ephemeral <-> Client Identity secret2 := utils.EDH(ah.serverEphemeralPrivateKey, ah.clientPubKey) // Ephemeral <-> Ephemeral secret3 := utils.EDH(ah.serverEphemeralPrivateKey, ah.clientEphmeralPublicKey) var secret [96]byte copy(secret[0:32], secret1[:]) copy(secret[32:64], secret2[:]) copy(secret[64:96], secret3[:]) pkey := pbkdf2.Key(secret[:], secret[:], 4096, 32, sha3.New512) var key [32]byte copy(key[:], pkey[:]) var decryptNonce [24]byte ciphertext := res.GetProof().GetProof() if len(ciphertext) > 24 { copy(decryptNonce[:], ciphertext[:24]) decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &key) if ok && string(decrypted) == "Hello World" { allowed, known := ah.ServerAuthValid(utils.GetTorV3Hostname(ah.clientPubKey), ah.clientPubKey) ah.channel.DelegateAuthorization() ah.channel.DelegateEncryption(key) log.Debugf("3DH Session Decrypted OK. Authenticating Connection!") messageBuilder := new(utils.MessageBuilder) result := messageBuilder.AuthResult3DH(allowed, known) ah.channel.SendMessage(result) ah.channel.CloseChannel() return } } messageBuilder := new(utils.MessageBuilder) result := messageBuilder.AuthResult3DH(false, false) ah.channel.SendMessage(result) } // Any other combination of packets is completely invalid // Fail the Authorization right here. ah.channel.CloseChannel() }