diff --git a/api/client.go b/api/client.go index 35bb2e8b1b8ed2dff03ca64229026b8a824ef7e4..13586c9c05721d3f5a4f7499d0f980675d5105fb 100644 --- a/api/client.go +++ b/api/client.go @@ -363,8 +363,8 @@ func (cl *Client) Login(UID *id.User, email, addr string, tlsCert string) (strin func (cl *Client) Send(message parse.MessageInterface) error { // FIXME: There should (at least) be a version of this that takes a byte array recipientID := message.GetRecipient() - err := cl.comm.SendMessage(cl.sess, recipientID, message.Pack()) - return err + cryptoType := message.GetCryptoType() + return cl.comm.SendMessage(cl.sess, recipientID, cryptoType, message.Pack()) } // DisableBlockingTransmission turns off blocking transmission, for diff --git a/api/mockserver_test.go b/api/mockserver_test.go index 2585ad51f3d9568462d6257dbcb6059c85dde6e0..80c2591692d2bfdb4ba69962021500b1faa98890 100644 --- a/api/mockserver_test.go +++ b/api/mockserver_test.go @@ -10,7 +10,7 @@ package api import ( "fmt" jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/crypto" + "gitlab.com/elixxir/client/globals" "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/comms/gateway" pb "gitlab.com/elixxir/comms/mixmessages" @@ -285,7 +285,7 @@ func testMainWrapper(m *testing.M) int { } func getGroup() *cyclic.Group { - return crypto.InitCrypto() + return globals.InitCrypto() } func fmtAddress(port int) string { return fmt.Sprintf("localhost:%d", port)} diff --git a/bindings/client.go b/bindings/client.go index 0fd6118632a7284c764158b69d19843cf6a42665..28f4eabc26255827e1f274b930c979f00d87c617 100644 --- a/bindings/client.go +++ b/bindings/client.go @@ -169,18 +169,25 @@ func (cl *Client) Login(UID []byte, email, addr string, // Sends a message structured via the message interface // Automatically serializes the message type before the rest of the payload // Returns an error if either sender or recipient are too short -func (cl *Client) Send(m Message) error { +func (cl *Client) Send(m Message, encrypt bool) error { sender := new(id.User).SetBytes(m.GetSender()) recipient := new(id.User).SetBytes(m.GetRecipient()) + var cryptoType format.CryptoType + if encrypt { + cryptoType = format.E2E + } else { + cryptoType = format.Unencrypted + } + return cl.client.Send(&parse.Message{ TypedBody: parse.TypedBody{ MessageType: m.GetMessageType(), - Body: m.GetPayload(), + Body: m.GetPayload(), }, - CryptoType: format.Unencrypted, - Sender: sender, - Receiver: recipient, + CryptoType: cryptoType, + Sender: sender, + Receiver: recipient, }) } diff --git a/bots/bots.go b/bots/bots.go index 1e4da644572051c6f5713a3347dff9ecb586b56b..e7d54b89587af40ca53802c22ccd0c4da034ce3f 100644 --- a/bots/bots.go +++ b/bots/bots.go @@ -86,7 +86,8 @@ func InitBots(s user.Session,m io.Communications) { // Callers that need to wait on a response should implement waiting with a // listener. func sendCommand(botID *id.User, command []byte) error { - return messaging.SendMessage(session, botID, command) + return messaging.SendMessage(session, botID, + format.Unencrypted, command) } // Nickname Lookup function diff --git a/bots/bots_test.go b/bots/bots_test.go index bf9543736bae42f5664799f65adbbe3933b724b2..517b3954fc1c24180e4fc2b61e8c2debb11ae51b 100644 --- a/bots/bots_test.go +++ b/bots/bots_test.go @@ -32,6 +32,7 @@ type dummyMessaging struct { // SendMessage to the server func (d *dummyMessaging) SendMessage(sess user.Session, recipientID *id.User, + cryptoType format.CryptoType, message []byte) error { jww.INFO.Printf("Sending: %s", string(message)) return nil diff --git a/cmd/root.go b/cmd/root.go index 60d5ec5e8f32cbeafceb8eda3d0c73d2b7d7ea18..62b8378e6370c001c235d85a6934c64da0210997 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -15,10 +15,8 @@ import ( jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" "gitlab.com/elixxir/client/api" - "gitlab.com/elixxir/client/bindings" "gitlab.com/elixxir/client/bots" "gitlab.com/elixxir/client/cmixproto" - "gitlab.com/elixxir/client/crypto" "gitlab.com/elixxir/client/globals" "gitlab.com/elixxir/client/parse" "gitlab.com/elixxir/client/user" @@ -311,7 +309,7 @@ var rootCmd = &cobra.Command{ parseUdbMessage(message, client) } else { // Handle sending to any other destination - wireOut := bindings.FormatTextMessage(message) + wireOut := api.FormatTextMessage(message) fmt.Printf("Sending Message to %d, %v: %s\n", destinationUserId, recipientNick, message) @@ -350,7 +348,7 @@ var rootCmd = &cobra.Command{ Sender: userID, TypedBody: parse.TypedBody{ MessageType: int32(cmixproto.Type_TEXT_MESSAGE), - Body: bindings.FormatTextMessage(message), + Body: api.FormatTextMessage(message), }, CryptoType: format.Unencrypted, Receiver: recipientId} @@ -458,7 +456,7 @@ func SetCertPaths(gwCertPath, registrationCertPath string) { // initConfig reads in config file and ENV variables if set. func initConfig() { // Temporarily need to get group as JSON data into viper - json, err := crypto.InitCrypto().MarshalJSON() + json, err := globals.InitCrypto().MarshalJSON() if err != nil { // panic } diff --git a/crypto/decrypt.go b/crypto/decrypt.go index 07743c8ad28595677f4b3422fb9e1eef105f1485..a1ea8b2aeda6a9028ed492885cf19b749e1bcf4f 100644 --- a/crypto/decrypt.go +++ b/crypto/decrypt.go @@ -9,72 +9,68 @@ package crypto import ( "errors" jww "github.com/spf13/jwalterweatherman" - pb "gitlab.com/elixxir/comms/mixmessages" + "gitlab.com/elixxir/client/keyStore" + "gitlab.com/elixxir/client/user" + "gitlab.com/elixxir/crypto/cmix" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/e2e" "gitlab.com/elixxir/crypto/hash" + pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/primitives/format" ) -// Decrypt decrypts messages -func Decrypt(nodeKey *cyclic.Int, grp *cyclic.Group, - cmixMsg *pb.CmixMessage) ( - *format.Message, error) { - - // Receive and decrypt a message - payload := grp.NewIntFromBytes(cmixMsg.MessagePayload) +// CMIX Decrypt performs the decryption +// of a message from a team of nodes +// It returns a new message +func CMIX_Decrypt(session user.Session, + msg *pb.CmixMessage) *format.Message { + salt := msg.Salt + nodeKeys := session.GetKeys() + baseKeys := make([]*cyclic.Int, len(nodeKeys)) + for i, key := range nodeKeys { + baseKeys[i] = key.ReceptionKey + //TODO: Add KMAC verification here + } - // perform the CMIX decryption - grp.Mul(payload, nodeKey, payload) + newMsg := format.NewMessage() + newMsg.Payload = format.DeserializePayload( + msg.MessagePayload) + newMsg.AssociatedData = format.DeserializeAssociatedData( + msg.AssociatedData) - // unpack the message from a MessageBytes - var message format.Message - payloadSerial := payload.LeftpadBytes(uint64(format.TOTAL_LEN)) - message.AssociatedData = format.DeserializeAssociatedData(cmixMsg.AssociatedData) - message.Payload = format.DeserializePayload(payloadSerial) + return cmix.ClientEncryptDecrypt(false, session.GetGroup(), newMsg, salt, baseKeys) +} - // TODO Should salt be []byte instead of cyclic.Int? - // TODO Should the method return []byte instead of cyclic.Int? - // That might give better results in the case that the key happens to be - // not the correct length in bytes. Unlikely, but possible. - clientKey := e2e.Keygen(grp, nil, nil) - // Assuming that result of e2e.Keygen() will be nil for non-e2e messages - // TODO BC: why is this assumption valid ? - if clientKey != nil { - clientKeyBytes := clientKey.LeftpadBytes(uint64(format.TOTAL_LEN)) - // First thing to do is check MAC - if !hash.VerifyHMAC(payloadSerial, message.GetMAC(), clientKeyBytes) { - return nil, errors.New("HMAC failed for end-to-end message") - } - var iv [e2e.AESBlockSize]byte - fp := message.GetKeyFingerprint() - copy(iv[:], fp[:e2e.AESBlockSize]) - // decrypt the timestamp in the associated data - decryptedTimestamp, err := e2e.DecryptAES256WithIV(clientKeyBytes, iv, message.GetTimestamp()) - if err != nil { - jww.ERROR.Panicf(err.Error()) - } - // TODO deserialize this somewhere along the line and provide methods - // to mobile developers on the bindings to interact with the timestamps - message.SetTimestamp(decryptedTimestamp) - // Decrypt e2e - decryptedPayload, err := e2e.Decrypt(grp, clientKey, payloadSerial) - if err != nil { - return nil, errors.New(err.Error() + - "Failed to decrypt e2e message despite non" + - "-nil client key result") - } - if message.SetSplitPayload(decryptedPayload) != len(decryptedPayload) { - jww.ERROR.Panicf("Error setting decrypted payload") - } - return &message, nil - } else { - // Check MAC for non-e2e - fp := message.GetKeyFingerprint() - if hash.VerifyHMAC(payloadSerial, message.GetMAC(), fp[:]) { - return &message, nil - } else { - return nil, errors.New("HMAC failed for plaintext message") - } +// E2E_Decrypt uses the E2E key to decrypt the message +// It returns an error in case of HMAC verification failure +// or in case of a decryption error (related to padding) +// If it succeeds, it modifies the passed message +func E2E_Decrypt(key *keyStore.E2EKey, grp *cyclic.Group, + msg *format.Message) error { + // First thing to do is check MAC + if !hash.VerifyHMAC(msg.SerializePayload(), + msg.GetMAC(), key.GetKey().Bytes()) { + return errors.New("HMAC verification failed for E2E message") + } + var iv [e2e.AESBlockSize]byte + fp := msg.GetKeyFingerprint() + copy(iv[:], fp[:e2e.AESBlockSize]) + // decrypt the timestamp in the associated data + decryptedTimestamp, err := e2e.DecryptAES256WithIV( + key.GetKey().Bytes(), iv, msg.GetTimestamp()) + if err != nil { + jww.ERROR.Panicf(err.Error()) + } + // TODO deserialize this somewhere along the line and provide methods + // to mobile developers on the bindings to interact with the timestamps + msg.SetTimestamp(decryptedTimestamp) + // Decrypt e2e + decryptedPayload, err := e2e.Decrypt(grp, key.GetKey(), msg.SerializePayload()) + if err != nil { + return errors.New("Failed to decrypt E2E message: " + err.Error()) + } + if msg.SetSplitPayload(decryptedPayload) != len(decryptedPayload) { + jww.ERROR.Panicf("Error setting decrypted payload") } + return nil } diff --git a/crypto/encrypt.go b/crypto/encrypt.go index cdc445d856f947a2b41e1c862f07832533117ed3..115a8f7dd6d39eec8156c1b9b8da8859ecfbd759 100644 --- a/crypto/encrypt.go +++ b/crypto/encrypt.go @@ -7,77 +7,77 @@ package crypto import ( - jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/globals" + "gitlab.com/elixxir/client/keyStore" + "gitlab.com/elixxir/client/user" + "gitlab.com/elixxir/crypto/cmix" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/e2e" "gitlab.com/elixxir/crypto/hash" "gitlab.com/elixxir/crypto/verification" "gitlab.com/elixxir/primitives/format" - "time" ) -// Encrypt uses the encryption key to encrypt the passed message and populate -// the associated data -// You must also encrypt the message for the nodes -func Encrypt(key *cyclic.Int, grp *cyclic.Group, - message *format.Message, e2eKey *cyclic.Int) (encryptedAssociatedData []byte, - encryptedPayload []byte) { - e2eKeyBytes := e2eKey.LeftpadBytes(uint64(format.TOTAL_LEN)) - // Key fingerprint is 256 bits given by H(e2ekey) - // For now use Blake2B - h, _ := hash.NewCMixHash() - h.Write(e2eKeyBytes) - keyFp := format.NewFingerprint(h.Sum(nil)) - message.SetKeyFingerprint(*keyFp) +// CMIX Encrypt performs the encryption +// of the msg to a team of nodes +// It returns a new msg +func CMIX_Encrypt(session user.Session, + salt []byte, + msg *format.Message) *format.Message { + // Generate the encryption key + nodeKeys := session.GetKeys() + baseKeys := make([]*cyclic.Int, len(nodeKeys)) + for i, key := range nodeKeys { + baseKeys[i] = key.TransmissionKey + //TODO: Add KMAC generation here + } + + fp := msg.GetKeyFingerprint() + // Calculate MIC + recipientMicList := [][]byte{ + msg.GetRecipientID(), + fp[:], + msg.GetTimestamp(), + msg.GetMAC(), + } + mic := verification.GenerateMIC(recipientMicList, uint64(format.AD_RMIC_LEN)) + msg.SetRecipientMIC(mic) + return cmix.ClientEncryptDecrypt(true, session.GetGroup(), msg, salt, baseKeys) +} + +// E2E_Encrypt uses the E2E key to encrypt msg +// to its intended recipient +// It also properly populates the associated data +// It modifies the passed msg instead of returning a new one +func E2E_Encrypt(key *keyStore.E2EKey, grp *cyclic.Group, + msg *format.Message) { + keyFP := key.KeyFingerprint() + msg.SetKeyFingerprint(keyFP) - // Encrypt the timestamp using the e2ekey - // TODO BC: this will produce a 32 byte ciphertext, where the first 16 bytes - // is the IV internally generated AES. This is fine right now since there are 32 bytes - // of space in Associated Data for the timestamp. - // If we want to decrease that to 16 bytes, we need to use the key fingerprint - // as the IV for AES encryption - // TODO: timestamp like this is kinda hacky, maybe it should be set right here - // However, this would lead to parts of same message having potentially different timestamps - // Get relevant bytes from timestamp by unmarshalling and then marshalling again - timestamp := time.Time{} - timestamp.UnmarshalBinary(message.GetTimestamp()) - timeBytes, _ := timestamp.MarshalBinary() + // Encrypt the timestamp using key + // Timestamp bytes were previously stored + // and GO only uses 15 bytes, so use those var iv [e2e.AESBlockSize]byte - copy(iv[:], keyFp[:e2e.AESBlockSize]) - encryptedTimestamp, err := e2e.EncryptAES256WithIV(e2eKeyBytes, iv, timeBytes) + copy(iv[:], keyFP[:e2e.AESBlockSize]) + encryptedTimestamp, err := + e2e.EncryptAES256WithIV(key.GetKey().Bytes(), iv, + msg.GetTimestamp()[:15]) + // Make sure the encrypted timestamp fits if len(encryptedTimestamp) != format.AD_TIMESTAMP_LEN || err != nil { - jww.ERROR.Panicf(err.Error()) + globals.Log.ERROR.Panicf(err.Error()) } - message.SetTimestamp(encryptedTimestamp) + msg.SetTimestamp(encryptedTimestamp) - // E2E encrypt the message - encPayload, err := e2e.Encrypt(grp, e2eKey, message.GetPayload()) + // E2E encrypt the msg + encPayload, err := e2e.Encrypt(grp, key.GetKey(), msg.GetPayload()) if len(encPayload) != format.TOTAL_LEN || err != nil { - jww.ERROR.Panicf(err.Error()) + globals.Log.ERROR.Panicf(err.Error()) } - message.SetPayload(encPayload) + msg.SetPayload(encPayload) // MAC is HMAC(key, ciphertext) // Currently, the MAC doesn't include any of the associated data - MAC := hash.CreateHMAC(encPayload, e2eKeyBytes) - message.SetMAC(MAC) - - recipientMicList := [][]byte{ - message.AssociatedData.GetRecipientID(), - keyFp[:], - message.AssociatedData.GetTimestamp(), - message.AssociatedData.GetMAC(), - } - mic := verification.GenerateMIC(recipientMicList, uint64(format.AD_RMIC_LEN)) - message.SetRecipientMIC(mic) - - // perform the CMIX encryption - resultPayload := grp.NewIntFromBytes(message.SerializePayload()) - resultAssociatedData := grp.NewIntFromBytes(message.SerializeAssociatedData()) - grp.Mul(resultPayload, key, resultPayload) - grp.Mul(resultAssociatedData, key, resultAssociatedData) - - return resultAssociatedData.LeftpadBytes(uint64(format.TOTAL_LEN)), - resultPayload.LeftpadBytes(uint64(format.TOTAL_LEN)) + MAC := hash.CreateHMAC(encPayload, key.GetKey().Bytes()) + msg.SetMAC(MAC) } diff --git a/crypto/encryptdecrypt_test.go b/crypto/encryptdecrypt_test.go index c7ce855dadf6050a2b8e6f47a52e33c67afb13f2..1d7e1659030217e071462d389877a3224166d17d 100644 --- a/crypto/encryptdecrypt_test.go +++ b/crypto/encryptdecrypt_test.go @@ -7,15 +7,9 @@ package crypto_test import ( - "bytes" - "gitlab.com/elixxir/client/crypto" "gitlab.com/elixxir/client/user" - pb "gitlab.com/elixxir/comms/mixmessages" - "gitlab.com/elixxir/crypto/cmix" "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/e2e" "gitlab.com/elixxir/crypto/large" - "gitlab.com/elixxir/primitives/format" "gitlab.com/elixxir/primitives/id" "testing" ) @@ -69,55 +63,3 @@ func setup(t *testing.T) { session = user.NewSession(nil, u, "", nk, nil, nil, grp) } - -func TestEncryptDecrypt(t *testing.T) { - setup(t) - - grp := session.GetGroup() - sender := id.NewUserFromUint(38, t) - recipient := id.NewUserFromUint(29, t) - msg := format.NewMessage() - msg.SetSender(sender) - msg.SetRecipient(recipient) - msgPayload := []byte("help me, i'm stuck in an" + - " EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory") - msg.SetPayloadData(msgPayload) - - // Generate a compound encryption key - encryptionKey := grp.NewInt(1) - for _, key := range session.GetKeys() { - baseKey := key.TransmissionKey - partialEncryptionKey := cmix.NewEncryptionKey(salt, baseKey, grp) - grp.Mul(encryptionKey, encryptionKey, partialEncryptionKey) - //TODO: Add KMAC generation here - } - - decryptionKey := grp.NewMaxInt() - grp.Inverse(encryptionKey, decryptionKey) - - // do the encryption and the decryption - e2eKey := e2e.Keygen(grp, nil, nil) - assocData, payload := crypto.Encrypt(encryptionKey, grp, msg, e2eKey) - encryptedNet := &pb.CmixMessage{ - SenderID: sender.Bytes(), - MessagePayload: payload, - AssociatedData: assocData, - } - decrypted, err := crypto.Decrypt(decryptionKey, grp, encryptedNet) - - if err != nil { - t.Fatalf("Couldn't decrypt message: %v", err.Error()) - } - if *decrypted.GetSender() != *sender { - t.Errorf("Sender differed from expected: Got %q, expected %q", - decrypted.GetRecipient(), sender) - } - if *decrypted.GetRecipient() != *recipient { - t.Errorf("Recipient differed from expected: Got %q, expected %q", - decrypted.GetRecipient(), sender) - } - if !bytes.Equal(decrypted.GetPayloadData(), msgPayload) { - t.Errorf("Decrypted payload differed from expected: Got %q, "+ - "expected %q", decrypted.GetPayloadData(), msgPayload) - } -} diff --git a/crypto/group.go b/globals/group.go similarity index 99% rename from crypto/group.go rename to globals/group.go index 30d00a0005477d8bc92539f3f1c96d59a605bca2..5f67d9e01be1f781ebadae1887885d19c3c09335 100644 --- a/crypto/group.go +++ b/globals/group.go @@ -3,7 +3,7 @@ // / // All rights reserved. / //////////////////////////////////////////////////////////////////////////////// -package crypto +package globals import ( "gitlab.com/elixxir/crypto/cyclic" diff --git a/io/interface.go b/io/interface.go index ae9f92897bb488c110992705fbadb6d6483568d1..53a72821a45db03425e6c2a3b1ca9e0780aca1db 100644 --- a/io/interface.go +++ b/io/interface.go @@ -9,6 +9,7 @@ package io import ( "gitlab.com/elixxir/client/user" + "gitlab.com/elixxir/primitives/format" "gitlab.com/elixxir/primitives/id" "time" ) @@ -16,7 +17,8 @@ import ( // Communication interface implements send/receive functionality with the server type Communications interface { // SendMessage to the server - SendMessage(session user.Session, recipientID *id.User, message []byte) error + SendMessage(session user.Session, recipientID *id.User, + cryptoType format.CryptoType, message []byte) error // MessageReceiver thread to get new messages MessageReceiver(session user.Session, delay time.Duration) } diff --git a/io/messaging.go b/io/messaging.go index b84264fefdc53dc20feb9bc31798e04d89afaaa8..fedc00f56d9517fb72419dd654dba5a4ed548c8c 100644 --- a/io/messaging.go +++ b/io/messaging.go @@ -13,13 +13,13 @@ import ( "fmt" "gitlab.com/elixxir/client/crypto" "gitlab.com/elixxir/client/globals" + "gitlab.com/elixxir/client/keyStore" "gitlab.com/elixxir/client/parse" "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/comms/client" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/crypto/cmix" "gitlab.com/elixxir/crypto/csprng" - "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/e2e" "gitlab.com/elixxir/primitives/format" "gitlab.com/elixxir/primitives/id" @@ -64,6 +64,7 @@ func NewMessenger() *Messaging { // w.r.t. generating message IDs for multi-part messages.) func (m *Messaging) SendMessage(session user.Session, recipientID *id.User, + cryptoType format.CryptoType, message []byte) error { // FIXME: We should really bring the plaintext parts of the NewMessage logic // into this module, then have an EncryptedMessage type that is sent to/from @@ -75,7 +76,6 @@ func (m *Messaging) SendMessage(session user.Session, // in this library? why not pass a sender object instead? globals.Log.DEBUG.Printf("Sending message to %q: %q", *recipientID, message) userID := session.GetCurrentUser().User - grp := session.GetGroup() parts, err := parse.Partition([]byte(message), m.nextId()) if err != nil { @@ -83,17 +83,9 @@ func (m *Messaging) SendMessage(session user.Session, } // Every part should have the same timestamp now := time.Now() - // TODO Is it better to use Golang's binary timestamp format, or - // use the 2 int64 technique with Unix seconds+nanoseconds? - // 2 int64s is 128 bits, which is as much as can fit in the timestamp field, - // but the binary serialization is 15 bytes, which is slightly smaller but - // not smaller enough to make a difference. - // The binary serialized timestamp also includes zone data, which could be - // a feature, but might compromise a little bit of anonymity. - // Using binary timestamp format for now. - // TODO BC: It is actually better to use the 15 byte version since this will - // allow the encrypted timestamp to fit in 16 bytes instead of 32, by using - // the key fingerprint as the IV for AES encryption + // GO Timestamp binary serialization is 15 bytes, which + // allows the encrypted timestamp to fit in 16 bytes + // using AES encryption nowBytes, err := now.MarshalBinary() if err != nil { return fmt.Errorf("SendMessage MarshalBinary() error: %v", err.Error()) @@ -103,9 +95,10 @@ func (m *Messaging) SendMessage(session user.Session, message.SetSender(userID) message.SetRecipient(recipientID) // The timestamp will be encrypted later + // NOTE: This sets 15 bytes, not 16 message.SetTimestamp(nowBytes) message.SetPayloadData(parts[i]) - err = m.send(session, userID, message, grp) + err = m.send(session, cryptoType, message) if err != nil { return fmt.Errorf("SendMessage send() error: %v", err.Error()) } @@ -113,9 +106,51 @@ func (m *Messaging) SendMessage(session user.Session, return nil } +// Send Message without doing partitions +// This function will be needed for example to send a Rekey +// message, where a new public key will take up all the message +func (m *Messaging) SendMessageNoPartition(session user.Session, + recipientID *id.User, + cryptoType format.CryptoType, + message []byte) error { + size := len(message) + if size > format.TOTAL_LEN { + return fmt.Errorf("SendMessageNoPartition() error: message to be sent is too big") + } + userID := session.GetCurrentUser().User + now := time.Now() + // GO Timestamp binary serialization is 15 bytes, which + // allows the encrypted timestamp to fit in 16 bytes + // using AES encryption + nowBytes, err := now.MarshalBinary() + if err != nil { + return fmt.Errorf("SendMessageNoPartition MarshalBinary() error: %v", err.Error()) + } + msg := format.NewMessage() + msg.SetRecipient(recipientID) + // The timestamp will be encrypted later + // NOTE: This sets 15 bytes, not 16 + msg.SetTimestamp(nowBytes) + // If message is bigger than payload size + // use SenderID space to send it + if size > format.MP_PAYLOAD_LEN { + msg.SetSenderID(message[:format.MP_SID_END]) + msg.SetPayloadData(message[format.MP_SID_END:]) + } else { + msg.SetSender(userID) + msg.SetPayloadData(message) + } + err = m.send(session, cryptoType, msg) + if err != nil { + return fmt.Errorf("SendMessageNoPartition send() error: %v", err.Error()) + } + return nil +} + // send actually sends the message to the server func (m *Messaging) send(session user.Session, - senderID *id.User, message *format.Message, grp *cyclic.Group) error { + cryptoType format.CryptoType, + message *format.Message) error { // Enable transmission blocking if enabled if m.BlockTransmissions { m.sendLock.Lock() @@ -125,39 +160,54 @@ func (m *Messaging) send(session user.Session, }() } - salt := cmix.NewSalt(csprng.Source(&csprng.SystemRNG{}), 16) - - // TBD: Add key macs to this message - macs := make([][]byte, 0) - - // Generate a compound encryption key - encryptionKey := grp.NewInt(1) - for _, key := range session.GetKeys() { - baseKey := key.TransmissionKey - partialEncryptionKey := cmix.NewEncryptionKey(salt, baseKey, grp) - grp.Mul(encryptionKey, partialEncryptionKey, encryptionKey) - //TODO: Add KMAC generation here + // Check message type + if cryptoType == format.E2E { + handleE2ESending(session, message) + } else { + padded, err := e2e.Pad(message.GetPayload(), format.TOTAL_LEN) + if err != nil { + return err + } + message.SetPayload(padded) + e2e.SetUnencrypted(message) } - // TBD: Is there a really good reason we have to specify the Grp and not a - // key? Should we even be doing the encryption here? - // TODO: Use salt here / generate n key map - e2eKey := e2e.Keygen(grp, nil, nil) - associatedData, payload := crypto.Encrypt(encryptionKey, grp, - message, e2eKey) + // CMIX Encryption + salt := cmix.NewSalt(csprng.Source(&csprng.SystemRNG{}), 16) + encMsg := crypto.CMIX_Encrypt(session, salt, message) + msgPacket := &pb.CmixMessage{ - SenderID: senderID.Bytes(), - MessagePayload: payload, - AssociatedData: associatedData, + SenderID: session.GetCurrentUser().User.Bytes(), + MessagePayload: encMsg.SerializePayload(), + AssociatedData: encMsg.SerializeAssociatedData(), Salt: salt, - KMACs: macs, + KMACs: make([][]byte, 0), } - var err error globals.Log.INFO.Println("Sending put message to gateway") - err = client.SendPutMessage(m.SendAddress, msgPacket) + return client.SendPutMessage(m.SendAddress, msgPacket) +} - return err +func handleE2ESending(session user.Session, + message *format.Message) { + recipientID := message.GetRecipient() + + // Get send key + sendKey, action := session.GetKeyStore(). + TransmissionKeys.Pop(recipientID) + + if sendKey == nil { + globals.Log.FATAL.Panicf("Couldn't get key to E2E encrypt message to" + + " user %v", *recipientID) + } else if action == keyStore.Deleted { + globals.Log.FATAL.Panicf("Key Manager is deleted when trying to get E2E Send Key") + } + + if action == keyStore.Rekey { + // TODO handle Send Rekey message to SW + } + + crypto.E2E_Encrypt(sendKey, session.GetGroup(), message) } // MessageReceiver is a polling thread for receiving messages -- again.. we @@ -185,6 +235,7 @@ func (m *Messaging) MessageReceiver(session user.Session, delay time.Duration) { decryptedMessages := m.receiveMessagesFromGateway(session, &pollingMessage) if decryptedMessages != nil { for i := range decryptedMessages { + // TODO Handle messages that do not need partitioning assembledMessage := m.collator.AddMessage( decryptedMessages[i], time.Minute) if assembledMessage != nil { @@ -197,6 +248,28 @@ func (m *Messaging) MessageReceiver(session user.Session, delay time.Duration) { } } +func handleE2EReceiving(session user.Session, + message *format.Message) error { + keyFingerprint := message.GetKeyFingerprint() + + // Lookup reception key + recpKey := session.GetKeyStore(). + ReceptionKeys.Pop(keyFingerprint) + + if recpKey == nil { + // TODO Handle sending error message to SW + return fmt.Errorf("E2EKey for matching fingerprint not found, can't process message") + } else if recpKey.GetOuterType() == format.Rekey { + // TODO Handle Receiving Keys Rekey (partner rekey) + } + + err := crypto.E2E_Decrypt(recpKey, session.GetGroup(), message) + if err != nil { + // TODO handle Garbled message to SW + } + return err +} + func (m *Messaging) receiveMessagesFromGateway(session user.Session, pollingMessage *pb.ClientPollMessage) []*format.Message { if session != nil { @@ -212,7 +285,6 @@ func (m *Messaging) receiveMessagesFromGateway(session user.Session, globals.Log.INFO.Printf("Checking novelty of %v messages", len(messages.MessageIDs)) results := make([]*format.Message, 0, len(messages.MessageIDs)) - grp := session.GetGroup() for _, messageID := range messages.MessageIDs { // Get the first unseen message from the list of IDs _, received := m.ReceivedMessages[messageID] @@ -239,15 +311,28 @@ func (m *Messaging) receiveMessagesFromGateway(session user.Session, continue } - // Generate a compound decryption key - salt := newMessage.Salt - decryptionKey := grp.NewInt(1) - for _, key := range session.GetKeys() { - baseKey := key.ReceptionKey - partialDecryptionKey := cmix.NewDecryptionKey(salt, baseKey, - grp) - grp.Mul(decryptionKey, partialDecryptionKey, decryptionKey) - //TODO: Add KMAC verification here + // CMIX Decryption + decMsg := crypto.CMIX_Decrypt(session, newMessage) + + var err error = nil + var unpadded []byte + // If message is E2E, handle decryption + if !e2e.IsUnencrypted(decMsg) { + err = handleE2EReceiving(session, decMsg) + } else { + // If message is non E2E, need to unpad payload + unpadded, err = e2e.Unpad(decMsg.SerializePayload()) + if err == nil { + decMsg.SetSplitPayload(unpadded) + } + } + + if err != nil { + globals.Log.WARN.Printf( + "Message did not decrypt properly, "+ + "not adding to results array: %v", err.Error()) + } else { + results = append(results, decMsg) } globals.Log.INFO.Printf( @@ -255,16 +340,6 @@ func (m *Messaging) receiveMessagesFromGateway(session user.Session, m.ReceivedMessages[messageID] = struct{}{} session.SetLastMessageID(messageID) session.StoreSession() - - decryptedMsg, err2 := crypto.Decrypt(decryptionKey, grp, - newMessage) - if err2 != nil { - globals.Log.WARN.Printf( - "Message did not decrypt properly, "+ - "not adding to results array: %v", err2.Error()) - } else { - results = append(results, decryptedMsg) - } } } } diff --git a/user/user.go b/user/user.go index 4f63125417fcf57db919e0d8727ea21bec3dda81..a65640cb12631b4a223fa89a3f735eee86f0d411 100644 --- a/user/user.go +++ b/user/user.go @@ -8,7 +8,6 @@ package user import ( "crypto/sha256" - "gitlab.com/elixxir/client/crypto" "gitlab.com/elixxir/client/globals" "gitlab.com/elixxir/primitives/id" ) @@ -58,7 +57,7 @@ func newRegistry() Registry { nk := make(map[id.User]*NodeKeys) // Initialize group object - grp := crypto.InitCrypto() + grp := globals.InitCrypto() // Deterministically create NUM_DEMO_USERS users // TODO Replace this with real user registration/discovery