diff --git a/channels/channel.go b/channels/channel.go index 53b8632..57bfb51 100644 --- a/channels/channel.go +++ b/channels/channel.go @@ -25,4 +25,5 @@ type Channel struct { SendMessage func([]byte) CloseChannel func() DelegateAuthorization func() + DelegateEncryption func([32]byte) } diff --git a/channels/v3/inbound/3dhauthchannel.go b/channels/v3/inbound/3dhauthchannel.go index 3ccbd0a..09db998 100644 --- a/channels/v3/inbound/3dhauthchannel.go +++ b/channels/v3/inbound/3dhauthchannel.go @@ -152,11 +152,13 @@ func (ah *Server3DHAuthChannel) Packet(data []byte) { 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 } } diff --git a/channels/v3/inbound/3dhauthchannel_test.go b/channels/v3/inbound/3dhauthchannel_test.go index f80acdd..9b04916 100644 --- a/channels/v3/inbound/3dhauthchannel_test.go +++ b/channels/v3/inbound/3dhauthchannel_test.go @@ -19,6 +19,7 @@ func TestServer3DHAuthChannel(t *testing.T) { cc.ID = 1 closed := false cc.CloseChannel = func() { closed = true } + cc.DelegateEncryption = func([32]byte) {} clientChannel := new(outbound.Client3DHAuthChannel) pub, priv, _ := ed25519.GenerateKey(rand.Reader) cid := identity.InitializeV3("", &priv, &pub) @@ -75,6 +76,7 @@ func TestServer3DHAuthChannelReject(t *testing.T) { cc := new(channels.Channel) cc.ID = 1 cc.CloseChannel = func() {} + cc.DelegateEncryption = func([32]byte) {} clientChannel := new(outbound.Client3DHAuthChannel) pub, priv, _ := ed25519.GenerateKey(rand.Reader) cid := identity.InitializeV3("", &priv, &pub) diff --git a/channels/v3/outbound/3dhauthchannel.go b/channels/v3/outbound/3dhauthchannel.go index b0bc661..df2d52b 100644 --- a/channels/v3/outbound/3dhauthchannel.go +++ b/channels/v3/outbound/3dhauthchannel.go @@ -27,6 +27,7 @@ type Client3DHAuthChannel struct { 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". @@ -135,12 +136,13 @@ func (ah *Client3DHAuthChannel) OpenOutboundResult(err error, crm *Protocol_Data } pkey := pbkdf2.Key(secret[:], secret[:], 4096, 32, sha3.New512) - var key [32]byte - copy(key[:], pkey[:]) - encrypted := secretbox.Seal(nonce[:], []byte("Hello World"), &nonce, &key) + + 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. diff --git a/connection/connection.go b/connection/connection.go index a4d6ff3..28208e1 100644 --- a/connection/connection.go +++ b/connection/connection.go @@ -229,6 +229,9 @@ func (rc *Connection) buildChannel(handler channels.Handler, openChannelFunc fun rc.SendRicochetPacket(rc.conn, channel.ID, []byte{}) rc.channelManager.RemoveChannel(channel.ID) } + channel.DelegateEncryption = func(key [32]byte) { + rc.SetEncryptionKey(key) + } return channel, nil } return nil, err diff --git a/testing/integration_test.go b/testing/integration_test.go index da93432..2e329c1 100644 --- a/testing/integration_test.go +++ b/testing/integration_test.go @@ -8,7 +8,6 @@ import ( "git.openprivacy.ca/openprivacy/libricochet-go/identity" "git.openprivacy.ca/openprivacy/libricochet-go/log" "git.openprivacy.ca/openprivacy/libricochet-go/utils" - "os" "runtime" "strconv" "sync" @@ -75,9 +74,9 @@ func (bot *ChatEchoBot) ChatMessage(messageID uint32, when time.Time, message st return true } -func SendMessage(rai *application.Instance, message string) { +func SendMessage(rai *application.Instance, message string) error { log.Infof("SendMessage(to: %v, %v)\n", rai.RemoteHostname, message) - rai.Connection.Do(func() error { + return rai.Connection.Do(func() error { log.Infof("Finding Chat Channel") channel := rai.Connection.Channel("im.ricochet.chat", channels.Outbound) @@ -99,7 +98,7 @@ func (bot *ChatEchoBot) ChatMessageAck(messageID uint32, accepted bool) { } func TestApplicationIntegration(t *testing.T) { - log.SetLevel(log.LevelInfo) + log.SetLevel(log.LevelDebug) startGoRoutines := runtime.NumGoroutine() acn, err := connectivity.StartTor(".", "") @@ -177,11 +176,11 @@ func TestApplicationIntegration(t *testing.T) { if err != nil { t.Fatalf("Error Alice connecting to Bob: %v", err) } - time.Sleep(10 * time.Second) + time.Sleep(30 * time.Second) fmt.Println("Alice request open chat channel...") // TODO: opening a channel should be easier? - alicei.Connection.Do(func() error { + err = alicei.Connection.Do(func() error { handler, err := alicei.OnOpenChannelRequest("im.ricochet.chat") if err != nil { log.Infof("Could not get chat handler!\n") @@ -190,14 +189,18 @@ func TestApplicationIntegration(t *testing.T) { _, err = alicei.Connection.RequestOpenChannel("im.ricochet.chat", handler) return err }) - time.Sleep(5 * time.Second) - - fmt.Println("Alice sending message to Bob...") - SendMessage(alicei, "Hello Bob!") if err != nil { - log.Errorf("Error dialing from Alice to Bob: %v", err) - os.Exit(1) + t.Errorf("Error Opening a Channel: %v", err) + } + + time.Sleep(30 * time.Second) + + fmt.Println("Alice sending message to Bob...") + err = SendMessage(alicei, "Hello Bob!") + + if err != nil { + t.Errorf("Error dialing from Alice to Bob: %v", err) } time.Sleep(10 * time.Second) diff --git a/utils/networking.go b/utils/networking.go index 782d8f6..f3adaeb 100644 --- a/utils/networking.go +++ b/utils/networking.go @@ -2,7 +2,11 @@ package utils import ( "bytes" + "crypto/rand" "encoding/binary" + "errors" + "git.openprivacy.ca/openprivacy/libricochet-go/log" + "golang.org/x/crypto/nacl/secretbox" "io" ) @@ -21,7 +25,7 @@ type RicochetData struct { Data []byte } -//Equals compares a RicochetData object to another and returns true if contain +// Equals compares a RicochetData object to another and returns true if contain // the same data. func (rd RicochetData) Equals(other RicochetData) bool { return rd.Channel == other.Channel && bytes.Equal(rd.Data, other.Data) @@ -36,6 +40,17 @@ type RicochetNetworkInterface interface { // RicochetNetwork is a concrete implementation of the RicochetNetworkInterface type RicochetNetwork struct { + // Derived ephemeral session key for connection + key [32]byte + encrypt bool +} + +// SetEncryptionKey sets the ephemeral encryption key for this session. +func (rn *RicochetNetwork) SetEncryptionKey(key [32]byte) { + log.Debugf("turning on ephemeral session encryption for connection") + copy(rn.key[:], key[:]) + + rn.encrypt = true } // SendRicochetPacket places the data into a structure needed for the client to @@ -52,6 +67,17 @@ func (rn *RicochetNetwork) SendRicochetPacket(dst io.Writer, channel int32, data binary.BigEndian.PutUint16(packet[2:4], uint16(channel)) copy(packet[4:], data[:]) + if rn.encrypt { + var nonce [24]byte + if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { + panic(err) + + } + encrypted := secretbox.Seal(nonce[:], packet[2:], &nonce, &rn.key) + binary.BigEndian.PutUint16(packet[0:2], uint16(len(encrypted)+2)) + packet = append(packet[0:2], encrypted...) + } + for pos := 0; pos < len(packet); { n, err := dst.Write(packet[pos:]) if err != nil { @@ -59,16 +85,18 @@ func (rn *RicochetNetwork) SendRicochetPacket(dst io.Writer, channel int32, data } pos += n } + return nil } // RecvRicochetPacket returns the next packet from reader as a RicochetData // structure, or an error. func (rn *RicochetNetwork) RecvRicochetPacket(reader io.Reader) (RicochetData, error) { + packet := RicochetData{} // Read the four-byte header to get packet length - header := make([]byte, 4) + header := make([]byte, 2) if _, err := io.ReadAtLeast(reader, header, len(header)); err != nil { return packet, err } @@ -78,12 +106,30 @@ func (rn *RicochetNetwork) RecvRicochetPacket(reader io.Reader) (RicochetData, e return packet, InvalidPacketLengthError } - packet.Channel = int32(binary.BigEndian.Uint16(header[2:4])) - packet.Data = make([]byte, size-4) - - if _, err := io.ReadAtLeast(reader, packet.Data, len(packet.Data)); err != nil { + packetBytes := make([]byte, size-2) + _, err := io.ReadAtLeast(reader, packetBytes, size-2) + if err != nil { return packet, err } + if rn.encrypt { + var decryptNonce [24]byte + if len(packetBytes) > 24 { + copy(decryptNonce[:], packetBytes[:24]) + decrypted, ok := secretbox.Open(nil, packetBytes[24:], &decryptNonce, &rn.key) + if ok { + packetBytes = decrypted + } else { + return packet, errors.New("failed to decrypt encrypted ricochet packet") + } + + } else { + return packet, errors.New("ciphertext length was too short") + } + } + + packet.Channel = int32(binary.BigEndian.Uint16(packetBytes[0:2])) + packet.Data = make([]byte, len(packetBytes)-2) + copy(packet.Data[:], packetBytes[2:]) return packet, nil }