diff --git a/single/cypher.go b/single/cypher.go index 065cfe2d6525c60a8ae59b770b60b192287c4b16..6776c0cc673c5d768bf18935ea7c5f66e0ce0de4 100644 --- a/single/cypher.go +++ b/single/cypher.go @@ -9,13 +9,18 @@ package single import ( "github.com/pkg/errors" - "gitlab.com/elixxir/client/single/message" "gitlab.com/elixxir/crypto/cyclic" cAuth "gitlab.com/elixxir/crypto/e2e/auth" "gitlab.com/elixxir/crypto/e2e/singleUse" "gitlab.com/elixxir/primitives/format" ) +// Error messages. +const ( + // cypher.decrypt + errMacVerification = "failed to verify the single-use MAC" +) + type newKeyFn func(dhKey *cyclic.Int, keyNum uint64) []byte type newFpFn func(dhKey *cyclic.Int, keyNum uint64) format.Fingerprint @@ -25,10 +30,10 @@ func makeCyphers(dhKey *cyclic.Int, messageCount uint8, newKey newKeyFn, cypherList := make([]cypher, messageCount) - for i := uint8(0); i < messageCount; i++ { + for i := range cypherList { cypherList[i] = cypher{ dhKey: dhKey, - num: i, + num: uint8(i), newKey: newKey, newFp: newFp, } @@ -44,33 +49,38 @@ type cypher struct { newFp newFpFn // Function used to create new fingerprint } -func (rk *cypher) getKey() []byte { - return rk.newKey(rk.dhKey, uint64(rk.num)) +// getKey generates a new encryption/description key from the DH key and number. +func (c *cypher) getKey() []byte { + return c.newKey(c.dhKey, uint64(c.num)) } -func (rk *cypher) GetFingerprint() format.Fingerprint { - return rk.newFp(rk.dhKey, uint64(rk.num)) +// getFingerprint generates a new key fingerprint from the DH key and number. +func (c *cypher) getFingerprint() format.Fingerprint { + return c.newFp(c.dhKey, uint64(c.num)) } -func (rk *cypher) Encrypt(rp message.ResponsePart) ( - fp format.Fingerprint, encryptedPayload, mac []byte) { - fp = rk.GetFingerprint() - key := rk.getKey() +// encrypt encrypts the payload. +func (c *cypher) encrypt( + payload []byte) (fp format.Fingerprint, encryptedPayload, mac []byte) { + fp = c.getFingerprint() + key := c.getKey() // FIXME: Encryption is identical to what is used by e2e.Crypt, lets make // them the same code path. - encryptedPayload = cAuth.Crypt(key, fp[:24], rp.Marshal()) + encryptedPayload = cAuth.Crypt(key, fp[:24], payload) mac = singleUse.MakeMAC(key, encryptedPayload) + return fp, encryptedPayload, mac } -func (rk *cypher) Decrypt(contents, mac []byte) ([]byte, error) { - fp := rk.GetFingerprint() - key := rk.getKey() +// decrypt decrypts the payload. +func (c *cypher) decrypt(contents, mac []byte) ([]byte, error) { + fp := c.getFingerprint() + key := c.getKey() - // Verify the CMix message MAC + // Verify the cMix message MAC if !singleUse.VerifyMAC(key, contents, mac) { - return nil, errors.New("failed to verify the single-use MAC") + return nil, errors.New(errMacVerification) } // Decrypt the payload diff --git a/single/cypher_test.go b/single/cypher_test.go new file mode 100644 index 0000000000000000000000000000000000000000..13a47e354db61d1c96f913b6d10a7bf97f029744 --- /dev/null +++ b/single/cypher_test.go @@ -0,0 +1,150 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package single + +import ( + "bytes" + "fmt" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/diffieHellman" + "gitlab.com/elixxir/crypto/e2e/singleUse" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/crypto/large" + "testing" +) + +// Tests that makeCyphers returns a list of cyphers of the correct length and +// that each cypher in the list has the correct DH key and number. +func Test_makeCyphers(t *testing.T) { + grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2)) + dhKey := diffieHellman.GeneratePublicKey(grp.NewInt(42), grp) + messageCount := 16 + + cyphers := makeCyphers(dhKey, uint8(messageCount), nil, nil) + + if len(cyphers) != messageCount { + t.Errorf("Wrong number of cyphers.\nexpected: %d\nreceived: %d", + messageCount, len(cyphers)) + } + + for i, c := range cyphers { + if dhKey.Cmp(c.dhKey) != 0 { + t.Errorf("Cypher #%d has incorrect DH key."+ + "\nexpected: %s\nreceived: %s", + i, dhKey.Text(10), c.dhKey.Text(10)) + } + if int(c.num) != i { + t.Errorf("Cypher #%d has incorrect number."+ + "\nexpected: %d\nreceived: %d", i, i, c.num) + } + } +} + +// Tests that cypher.getKey returns the expected key from the passed in newKey +// function. Also tests that the expected DH key and number are passed to the +// new key function. +func Test_cypher_getKey(t *testing.T) { + grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2)) + newKey := func(dhKey *cyclic.Int, keyNum uint64) []byte { + return []byte(fmt.Sprintf("KEY #%d %s", keyNum, dhKey.Text(10))) + } + + c := &cypher{ + dhKey: diffieHellman.GeneratePublicKey(grp.NewInt(42), grp), + num: 6, + newKey: newKey, + } + + expectedKey := newKey(c.dhKey, uint64(c.num)) + + key := c.getKey() + if !bytes.Equal(expectedKey, key) { + t.Errorf( + "Unexpected key.\nexpected: %q\nreceived: %q", expectedKey, key) + } +} + +// Tests that cypher.getFingerprint returns the expected fingerprint from the +// passed in newFp function. Also tests that the expected DH key and number are +// passed to the new fingerprint function. +func Test_cypher_getFingerprint(t *testing.T) { + grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2)) + newFp := func(dhKey *cyclic.Int, keyNum uint64) format.Fingerprint { + return format.NewFingerprint([]byte( + fmt.Sprintf("FP #%d %s", keyNum, dhKey.Text(10)))) + } + + c := &cypher{ + dhKey: diffieHellman.GeneratePublicKey(grp.NewInt(42), grp), + num: 6, + newFp: newFp, + } + + expectedFp := newFp(c.dhKey, uint64(c.num)) + + fp := c.getFingerprint() + if expectedFp != fp { + t.Errorf("Unexpected fingerprint.\nexpected: %s\nreceived: %s", + expectedFp, fp) + } +} + +// Tests that a payload encrypted by cypher.encrypt and decrypted by +// cypher.decrypt matches the original. Tests with both the response and request +// part key and fingerprint functions. +func Test_cypher_encrypt_decrypt(t *testing.T) { + grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2)) + funcs := []struct { + newKey newKeyFn + newFp newFpFn + }{ + {singleUse.NewResponseKey, singleUse.NewResponseFingerprint}, + {singleUse.NewRequestPartKey, singleUse.NewRequestPartFingerprint}, + } + c := &cypher{ + dhKey: diffieHellman.GeneratePublicKey(grp.NewInt(42), grp), + num: 6, + } + + for i, fn := range funcs { + c.newKey = fn.newKey + c.newFp = fn.newFp + + payload := []byte("I am a single-use payload message.") + + _, encryptedPayload, mac := c.encrypt(payload) + + decryptedPayload, err := c.decrypt(encryptedPayload, mac) + if err != nil { + t.Errorf("decrypt returned an error (%d): %+v", i, err) + } + + if !bytes.Equal(payload, decryptedPayload) { + t.Errorf("Decrypted payload does not match original (%d)."+ + "\nexpected: %q\nreceived: %q", i, payload, decryptedPayload) + } + } +} + +// Error path: tests that cypher.decrypt returns the expected error when the MAC +// is invalid. +func Test_cypher_decrypt_InvalidMacError(t *testing.T) { + grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2)) + c := &cypher{ + dhKey: diffieHellman.GeneratePublicKey(grp.NewInt(42), grp), + num: 6, + newKey: singleUse.NewResponseKey, + newFp: singleUse.NewResponseFingerprint, + } + + _, err := c.decrypt([]byte("contents"), []byte("mac")) + if err == nil || err.Error() != errMacVerification { + t.Errorf("decrypt did not return the expected error with invalid MAC."+ + "\nexpected: %s\nreceived: %+v", errMacVerification, err) + } +} diff --git a/single/interfaces.go b/single/interfaces.go index 8ee9bf00212ff187cc5059798d7f44a2fb2a9cda..d72ba2fa178555e827544358b03a9c2bbfdc3af8 100644 --- a/single/interfaces.go +++ b/single/interfaces.go @@ -10,9 +10,9 @@ import ( "time" ) -// CMix is a sub-interface of the cmix.Client. It contains the methods -// relevant to what is used in this package. -type CMix interface { +// Cmix is a sub-interface of the cmix.Client. It contains the methods relevant +// to what is used in this package. +type Cmix interface { IsHealthy() bool GetAddressSpace() uint8 GetMaxMessageLength() int diff --git a/single/listener.go b/single/listener.go index 6ba7d365ca212427e62cdecf02f18ce70fe2cb28..869cbac443b93baf6fed142f89653a7f88113987 100644 --- a/single/listener.go +++ b/single/listener.go @@ -2,10 +2,10 @@ package single import ( "fmt" - + "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/cmix/identity/receptionID" - cmixMsg "gitlab.com/elixxir/client/cmix/message" + cMixMsg "gitlab.com/elixxir/client/cmix/message" "gitlab.com/elixxir/client/cmix/rounds" "gitlab.com/elixxir/client/single/message" "gitlab.com/elixxir/crypto/cyclic" @@ -13,10 +13,9 @@ import ( "gitlab.com/elixxir/crypto/e2e/singleUse" "gitlab.com/elixxir/primitives/format" "gitlab.com/xx_network/primitives/id" + "strings" ) -const listenerProcessorName = "listenerProcessorName" - type Receiver interface { Callback(*Request, receptionID.EphemeralIdentity, []rounds.Round) } @@ -27,12 +26,12 @@ type Listener interface { } type listener struct { - myId *id.ID - myPrivKey *cyclic.Int tag string grp *cyclic.Group + myID *id.ID + myPrivKey *cyclic.Int cb Receiver - net CMix + net ListenCmix } // Listen allows a server to listen for single use requests. It will register a @@ -40,38 +39,60 @@ type listener struct { // listener can be active for a tag-myID pair, and an error will be returned if // that is violated. When requests are received, they will be called on the // Receiver interface. -func Listen(tag string, myId *id.ID, privKey *cyclic.Int, net CMix, +func Listen(tag string, myID *id.ID, privKey *cyclic.Int, net ListenCmix, e2eGrp *cyclic.Group, cb Receiver) Listener { l := &listener{ - myId: myId, - myPrivKey: privKey, tag: tag, grp: e2eGrp, + myID: myID, + myPrivKey: privKey, cb: cb, net: net, } - svc := cmixMsg.Service{ - Identifier: myId[:], + svc := cMixMsg.Service{ + Identifier: myID[:], Tag: tag, - Metadata: myId[:], + Metadata: myID[:], } - net.AddService(myId, svc, l) + net.AddService(myID, svc, l) return l } +type ListenCmix interface { + RequestCmix + AddFingerprint(identity *id.ID, fingerprint format.Fingerprint, + mp cMixMsg.Processor) error + AddService( + clientID *id.ID, newService cMixMsg.Service, response cMixMsg.Processor) + DeleteService( + clientID *id.ID, toDelete cMixMsg.Service, processor cMixMsg.Processor) + CheckInProgressMessages() +} + +// Process decrypts and collates the encrypted single-use request message. func (l *listener) Process(ecrMsg format.Message, receptionID receptionID.EphemeralIdentity, round rounds.Round) { + err := l.process(ecrMsg, receptionID, round) + if err != nil { + jww.ERROR.Printf( + "[SU] Failed to process single-use request to %s on tag %q: %+v", + l.myID, l.tag, err) + } +} + +// process is a helper functions for Process that returns errors for easier +// testing. +func (l *listener) process(ecrMsg format.Message, + receptionID receptionID.EphemeralIdentity, round rounds.Round) error { // Unmarshal the cMix message contents to a request message requestMsg, err := message.UnmarshalRequest(ecrMsg.GetContents(), l.grp.GetP().ByteLen()) if err != nil { - jww.WARN.Printf("Failed to unmarshal contents on single use "+ - "request to %s on tag %s: %+v", l.myId, l.tag, err) - return + return errors.Errorf("could not unmarshal contents: %+v", err) } // Generate DH key and symmetric key @@ -81,25 +102,21 @@ func (l *listener) Process(ecrMsg format.Message, // Verify the MAC if !singleUse.VerifyMAC(key, requestMsg.GetPayload(), ecrMsg.GetMac()) { - jww.WARN.Printf("mac check failed on single use request to %s "+ - "on tag %s", l.myId, l.tag) - return + return errors.New("failed to verify MAC") } // Decrypt the request message payload fp := ecrMsg.GetKeyFP() decryptedPayload := cAuth.Crypt(key, fp[:24], requestMsg.GetPayload()) + // Unmarshal payload payload, err := message.UnmarshalRequestPayload(decryptedPayload) if err != nil { - jww.WARN.Printf("[SU] Failed to unmarshal decrypted payload on "+ - "single use request to %s on tag %s: %+v", l.myId, l.tag, err) - return + return errors.Errorf("could not unmarshal decrypted payload: %+v", err) } cbFunc := func(payloadContents []byte, rounds []rounds.Round) { used := uint32(0) - r := Request{ sender: payload.GetRecipientID(requestMsg.GetPubKey(l.grp)), senderPubKey: senderPubkey, @@ -118,28 +135,29 @@ func (l *listener) Process(ecrMsg format.Message, c := message.NewCollator(numParts) _, _, err = c.Collate(payload) if err != nil { - - return + return errors.Errorf("could not collate initial payload: %+v", err) } + cyphers := makeCyphers(dhKey, numParts, singleUse.NewRequestPartKey, singleUse.NewRequestPartFingerprint) ridCollector := newRoundIdCollector(int(numParts)) + for i, cy := range cyphers { key = singleUse.NewRequestPartKey(dhKey, uint64(i+1)) fp = singleUse.NewRequestPartFingerprint(dhKey, uint64(i+1)) p := &requestPartProcessor{ - myId: l.myId, + myId: l.myID, tag: l.tag, cb: cbFunc, c: c, cy: cy, roundIDs: ridCollector, } - err = l.net.AddFingerprint(l.myId, fp, p) + + err = l.net.AddFingerprint(l.myID, fp, p) if err != nil { - jww.ERROR.Printf("Failed to add fingerprint for request part "+ - "%d of %d (%s): %+v", i, numParts, l.tag, err) - return + return errors.Errorf("could not add fingerprint for single-"+ + "use request part %d of %d: %+v", i, numParts, err) } } @@ -147,16 +165,45 @@ func (l *listener) Process(ecrMsg format.Message, } else { cbFunc(payload.GetContents(), []rounds.Round{round}) } + + return nil } +// Stop stops the listener from receiving messages. func (l *listener) Stop() { - svc := cmixMsg.Service{ - Identifier: l.myId[:], + svc := cMixMsg.Service{ + Identifier: l.myID[:], Tag: l.tag, } - l.net.DeleteService(l.myId, svc, l) + l.net.DeleteService(l.myID, svc, l) } +// String prints a name that identifies this single use listener. Adheres to the +// fmt.Stringer interface. func (l *listener) String() string { - return fmt.Sprintf("SingleUse(%s)", l.myId) + return "SingleUse(" + l.myID.String() + ")" +} + +// GoString prints the fields of the listener in a human-readable form. +// Adheres to the fmt.GoStringer interface and prints values passed as an +// operand to a %#v format. +func (l *listener) GoString() string { + cb := "<nil>" + if l.cb != nil { + cb = fmt.Sprintf("%p", l.cb) + } + net := "<nil>" + if l.net != nil { + net = fmt.Sprintf("%p", l.net) + } + fields := []string{ + "tag:" + fmt.Sprintf("%q", l.tag), + "grp:" + l.grp.GetFingerprintText(), + "myID:" + l.myID.String(), + "myPrivKey:\"" + l.myPrivKey.Text(10) + "\"", + "cb:" + cb, + "net:" + net, + } + + return strings.Join(fields, " ") } diff --git a/single/listener_test.go b/single/listener_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3e2755a8f1d85404d2a0af6bdc7a0a8bf0c8be07 --- /dev/null +++ b/single/listener_test.go @@ -0,0 +1,337 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package single + +import ( + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + cMixMsg "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/single/message" + "gitlab.com/elixxir/comms/network" + "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/e2e/auth" + "gitlab.com/elixxir/crypto/e2e/singleUse" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/crypto/large" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "gitlab.com/xx_network/primitives/netTime" + "io" + "math/rand" + "reflect" + "sync" + "testing" + "time" +) + +type cbReceiver struct { + requestChan chan *Request +} + +func newCbReceiver(requestChan chan *Request) *cbReceiver { + return &cbReceiver{requestChan: requestChan} +} + +func (cr *cbReceiver) Callback( + r *Request, _ receptionID.EphemeralIdentity, _ []rounds.Round) { + cr.requestChan <- r +} + +// Tests that Listen returns the expected listener. +func TestListen(t *testing.T) { + grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2)) + tag := "myTag" + myID := id.NewIdFromString("myID", id.User, t) + privKey := grp.NewInt(34) + handler := newListenMockCmixHandler() + + expected := &listener{ + tag: tag, + grp: grp, + myID: myID, + myPrivKey: privKey, + net: newMockListenCmix(handler), + } + + l := Listen(tag, myID, privKey, + newMockListenCmix(handler), grp, nil) + + if !reflect.DeepEqual(expected, l) { + t.Errorf("New Listener does not match expected."+ + "\nexpected: %#v\nreceived: %#v", expected, l) + } +} + +// Tests that listener.process correctly unmarshalls the payload and returns the +// Request with the expected fields on the callback. +func Test_listener_Process(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2)) + privKey := grp.NewInt(42) + recipient := contact.Contact{ + ID: id.NewIdFromString("recipientID", id.User, t), + DhPubKey: grp.ExpG(privKey, grp.NewInt(1)), + } + handler := newListenMockCmixHandler() + + payload := []byte("I am the payload!") + msg, sendingID, publicKey, dhKey := newRequestMessage( + payload, grp, recipient, prng, handler, t) + + requestChan := make(chan *Request, 10) + l := &listener{ + tag: "tag", + grp: grp, + myID: id.NewIdFromString("myID", id.User, t), + myPrivKey: privKey, + cb: newCbReceiver(requestChan), + net: newMockListenCmix(handler), + } + + err := l.process(msg, sendingID, rounds.Round{}) + if err != nil { + t.Errorf("process returned an error: %+v", err) + } + + used := uint32(0) + expected := &Request{ + sender: sendingID.Source, + senderPubKey: publicKey, + dhKey: dhKey, + tag: l.tag, + maxParts: 6, + used: &used, + requestPayload: payload, + net: l.net, + } + + select { + case r := <-requestChan: + if !reflect.DeepEqual(expected, r) { + t.Errorf("Received unexpected values."+ + "\nexpected: %+v\nreceived: %+v", expected, r) + } + case <-time.After(15 * time.Millisecond): + t.Error("Timed out waiting to receive callback.") + } +} + +// newRequestMessage creates a new encrypted request message for testing. +func newRequestMessage(payload []byte, grp *cyclic.Group, recipient contact.Contact, + rng io.Reader, handler *mockListenCmixHandler, t *testing.T) ( + format.Message, receptionID.EphemeralIdentity, *cyclic.Int, *cyclic.Int) { + + net := newMockListenCmix(handler) + maxResponseMessages := uint8(6) + params := GetDefaultRequestParams() + timeStart := netTime.Now() + + // Generate DH key and public key + dhKey, publicKey, err := generateDhKeys(grp, recipient.DhPubKey, rng) + if err != nil { + t.Errorf("Failed to generate DH keys: %+v", err) + } + + // Build the message payload + request := message.NewRequest( + net.GetMaxMessageLength(), grp.GetP().ByteLen()) + requestPayload := message.NewRequestPayload( + request.GetPayloadSize(), payload, maxResponseMessages) + + // Generate new user ID and address ID + var sendingID receptionID.EphemeralIdentity + requestPayload, sendingID, err = makeIDs( + requestPayload, publicKey, 8, params.Timeout, timeStart, rng) + if err != nil { + t.Errorf("Failed to make new sending ID: %+v", err) + } + + // Encrypt and assemble payload + fp := singleUse.NewRequestFingerprint(recipient.DhPubKey) + key := singleUse.NewRequestKey(dhKey) + encryptedPayload := auth.Crypt(key, fp[:24], requestPayload.Marshal()) + + // Generate cMix message MAC + mac := singleUse.MakeMAC(key, encryptedPayload) + + // Assemble the payload + request.SetPubKey(publicKey) + request.SetPayload(encryptedPayload) + + msg := format.NewMessage(net.numPrimeBytes) + msg.SetMac(mac) + msg.SetContents(request.Marshal()) + msg.SetKeyFP(fp) + + return msg, sendingID, publicKey, dhKey +} + +// First successfully sends and receives request. Then, once listener.Stop is +// called, the next send is never received. +func Test_listener_Stop(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2)) + privKey := grp.NewInt(42) + recipient := contact.Contact{ + ID: id.NewIdFromString("recipientID", id.User, t), + DhPubKey: grp.ExpG(privKey, grp.NewInt(1)), + } + handler := newListenMockCmixHandler() + net := newMockListenCmix(handler) + + payload := []byte("I am the payload!") + msg, _, _, _ := newRequestMessage( + payload, grp, recipient, prng, handler, t) + + requestChan := make(chan *Request, 10) + myID := id.NewIdFromString("myID", id.User, t) + tag := "tag" + l := Listen(tag, myID, privKey, net, grp, newCbReceiver(requestChan)) + + svc := cMixMsg.Service{ + Identifier: myID[:], + Tag: tag, + Metadata: myID[:], + } + _, _, _ = net.Send(myID, msg.GetKeyFP(), svc, msg.GetContents(), + msg.GetMac(), cmix.CMIXParams{}) + + select { + case <-requestChan: + case <-time.After(15 * time.Millisecond): + t.Error("Timed out waiting to receive callback.") + } + + l.Stop() + _, _, _ = net.Send(myID, msg.GetKeyFP(), svc, msg.GetContents(), + msg.GetMac(), cmix.CMIXParams{}) + + select { + case r := <-requestChan: + t.Errorf("Received callback when it should have been stopped: %+v", r) + case <-time.After(15 * time.Millisecond): + } +} + +type mockListenCmixHandler struct { + fingerprintMap map[id.ID]map[format.Fingerprint][]cMixMsg.Processor + serviceMap map[id.ID]map[string][]cMixMsg.Processor + sync.Mutex +} + +func newListenMockCmixHandler() *mockListenCmixHandler { + return &mockListenCmixHandler{ + serviceMap: make(map[id.ID]map[string][]cMixMsg.Processor), + } +} + +type mockListenCmix struct { + numPrimeBytes int + handler *mockListenCmixHandler +} + +func newMockListenCmix(handler *mockListenCmixHandler) *mockListenCmix { + return &mockListenCmix{ + numPrimeBytes: 4096, + handler: handler, + } +} + +func (m mockListenCmix) GetMaxMessageLength() int { + return format.NewMessage(m.numPrimeBytes).ContentsSize() +} + +func (m mockListenCmix) Send(recipient *id.ID, fingerprint format.Fingerprint, + service cMixMsg.Service, payload, mac []byte, _ cmix.CMIXParams) ( + id.Round, ephemeral.Id, error) { + msg := format.NewMessage(m.numPrimeBytes) + msg.SetContents(payload) + msg.SetMac(mac) + msg.SetKeyFP(fingerprint) + + m.handler.Lock() + defer m.handler.Unlock() + for _, p := range m.handler.serviceMap[*recipient][service.Tag] { + p.Process(msg, receptionID.EphemeralIdentity{}, rounds.Round{}) + } + for _, p := range m.handler.fingerprintMap[*recipient][fingerprint] { + p.Process(msg, receptionID.EphemeralIdentity{}, rounds.Round{}) + } + + return 0, ephemeral.Id{}, nil +} + +func (m mockListenCmix) GetInstance() *network.Instance { + // TODO implement me + panic("implement me") +} + +func (m mockListenCmix) AddFingerprint(identity *id.ID, fp format.Fingerprint, + mp cMixMsg.Processor) error { + m.handler.Lock() + defer m.handler.Unlock() + + if _, exists := m.handler.fingerprintMap[*identity]; !exists { + m.handler.fingerprintMap[*identity] = + map[format.Fingerprint][]cMixMsg.Processor{fp: {mp}} + return nil + } else if _, exists = m.handler.fingerprintMap[*identity][fp]; !exists { + m.handler.fingerprintMap[*identity][fp] = + []cMixMsg.Processor{mp} + return nil + } + + m.handler.fingerprintMap[*identity][fp] = + append(m.handler.fingerprintMap[*identity][fp], mp) + return nil +} + +func (m mockListenCmix) AddService( + clientID *id.ID, ms cMixMsg.Service, mp cMixMsg.Processor) { + m.handler.Lock() + defer m.handler.Unlock() + + if _, exists := m.handler.serviceMap[*clientID]; !exists { + m.handler.serviceMap[*clientID] = + map[string][]cMixMsg.Processor{ms.Tag: {mp}} + return + } else if _, exists = m.handler.serviceMap[*clientID][ms.Tag]; !exists { + m.handler.serviceMap[*clientID][ms.Tag] = + []cMixMsg.Processor{mp} + return + } + + m.handler.serviceMap[*clientID][ms.Tag] = + append(m.handler.serviceMap[*clientID][ms.Tag], mp) +} + +func (m mockListenCmix) DeleteService( + clientID *id.ID, toDelete cMixMsg.Service, processor cMixMsg.Processor) { + m.handler.Lock() + defer m.handler.Unlock() + + for i, p := range m.handler.serviceMap[*clientID][toDelete.Tag] { + if p == processor { + m.handler.serviceMap[*clientID][toDelete.Tag] = + remove(m.handler.serviceMap[*clientID][toDelete.Tag], i) + } + } +} + +func remove(s []cMixMsg.Processor, i int) []cMixMsg.Processor { + s2 := make([]cMixMsg.Processor, 0) + s2 = append(s2, s[:i]...) + return append(s2, s[i+1:]...) +} + +func (m mockListenCmix) CheckInProgressMessages() { + // TODO implement me + panic("implement me") +} diff --git a/single/receivedRequest.go b/single/receivedRequest.go index c0fdb2956055958f392db7c3e80dc010d937adb5..534ac46376b503cc2d1b5c89d2b5f12caeaffc35 100644 --- a/single/receivedRequest.go +++ b/single/receivedRequest.go @@ -8,11 +8,14 @@ import ( "gitlab.com/elixxir/client/cmix" cmixMsg "gitlab.com/elixxir/client/cmix/message" "gitlab.com/elixxir/client/single/message" + "gitlab.com/elixxir/comms/network" ds "gitlab.com/elixxir/comms/network/dataStructures" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/e2e/singleUse" + "gitlab.com/elixxir/primitives/format" "gitlab.com/elixxir/primitives/states" "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" "sync" "sync/atomic" "time" @@ -27,7 +30,15 @@ type Request struct { maxParts uint8 // Max number of messages allowed in reply used *uint32 // Atomic variable requestPayload []byte - net CMix + net RequestCmix +} + +type RequestCmix interface { + GetMaxMessageLength() int + Send(recipient *id.ID, fingerprint format.Fingerprint, + service cmixMsg.Service, payload, mac []byte, + cmixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) + GetInstance() *network.Instance } // GetMaxParts returns the maximum number of message parts that can be sent in a @@ -61,31 +72,31 @@ func (r Request) GetPayload() []byte { // String returns a string of the Contact structure. func (r Request) String() string { - format := "Request{sender:%s senderPubKey:%s dhKey:%s tagFP:%s " + - "maxParts:%d}" - return fmt.Sprintf(format, r.sender, r.senderPubKey.Text(10), - r.dhKey.Text(10), r.tag, r.maxParts) + return fmt.Sprintf( + "{sender:%s senderPubKey:%s dhKey:%s tag:%q maxParts:%d used:%p(%d) "+ + "requestPayload:%q net:%p}", + r.sender, r.senderPubKey.Text(10), r.dhKey.Text(10), r.tag, r.maxParts, + r.used, atomic.LoadUint32(r.used), r.requestPayload, r.net) } // Respond is used to respond to the request. It sends a payload up to // Request.GetMaxResponseLength. It will chunk the message into multiple cMix // messages if it is too long for a single message. It will fail if a single // cMix message cannot be sent. -func (r Request) Respond(payload []byte, cmixParams cmix.CMIXParams, +func (r Request) Respond(payload []byte, cMixParams cmix.CMIXParams, timeout time.Duration) ([]id.Round, error) { // make sure this has only been run once newRun := atomic.CompareAndSwapUint32(r.used, 0, 1) if !newRun { - return nil, errors.Errorf("cannot respond to " + - "single-use response that has already been responded to.") + return nil, errors.Errorf("cannot respond to single-use response " + + "that has already been responded to") } // Check that the payload is not too long if len(payload) > r.GetMaxResponseLength() { - return nil, errors.Errorf("length of provided "+ - "payload too long for message payload capacity, max: %d, "+ - "received: %d", r.GetMaxResponseLength(), - len(payload)) + return nil, errors.Errorf("length of provided payload too long for "+ + "message payload capacity, max: %d, received: %d", + r.GetMaxResponseLength(), len(payload)) } // Partition the payload @@ -97,11 +108,8 @@ func (r Request) Respond(payload []byte, cmixParams cmix.CMIXParams, rounds := make([]id.Round, len(parts)) sendResults := make(chan ds.EventReturn, len(parts)) - wg := sync.WaitGroup{} - wg.Add(len(parts)) - - if cmixParams.DebugTag != cmix.DefaultDebugTag { - cmixParams.DebugTag = "single.Response" + if cMixParams.DebugTag != cmix.DefaultDebugTag { + cMixParams.DebugTag = "single.Response" } // fixme: should the above debug tag and the below service tag be flipped?? @@ -111,28 +119,32 @@ func (r Request) Respond(payload []byte, cmixParams cmix.CMIXParams, Metadata: nil, } + var wg sync.WaitGroup + wg.Add(len(parts)) failed := uint32(0) for i := 0; i < len(parts); i++ { - go func(j int) { + go func(i int, part []byte) { defer wg.Done() - partFP, ecrPart, mac := cyphers[j].Encrypt(parts[j]) + partFP, ecrPart, mac := cyphers[i].encrypt(part) + // Send Message - round, ephID, err := r.net.Send(r.sender, partFP, svc, - ecrPart, mac, - cmixParams) + round, ephID, err := r.net.Send( + r.sender, partFP, svc, ecrPart, mac, cMixParams) if err != nil { atomic.AddUint32(&failed, 1) - jww.ERROR.Printf("Failed to send single-use response CMIX "+ - "message part %d: %+v", j, err) + jww.ERROR.Printf("[SU] Failed to send single-use response "+ + "cMix message part %d of %d: %+v", i, len(parts), err) + return } - jww.DEBUG.Printf("Sending single-use response CMIX message part "+ - "%d on round %d to address ID %d.", j, round, ephID.Int64()) - rounds[j] = round - r.net.GetInstance().GetRoundEvents().AddRoundEventChan(round, sendResults, - timeout, states.COMPLETED, states.FAILED) - }(i) + jww.DEBUG.Printf("[SU] Sent single-use response cMix message part "+ + "%d on round %d to address ID %d.", i, round, ephID.Int64()) + rounds[i] = round + + r.net.GetInstance().GetRoundEvents().AddRoundEventChan( + round, sendResults, timeout, states.COMPLETED, states.FAILED) + }(i, parts[i].Marshal()) } // Wait for all go routines to finish @@ -158,7 +170,6 @@ func (r Request) Respond(payload []byte, cmixParams cmix.CMIXParams, success, numRoundFail, numTimeOut := cmix.TrackResults( sendResults, len(roundMap)) if !success { - return nil, errors.Errorf("tracking results of %d rounds: %d round "+ "failures, %d round event time outs; the send cannot be retried.", len(rounds), numRoundFail, numTimeOut) @@ -195,7 +206,7 @@ func partitionResponse(payload []byte, cmixMessageLength int, maxParts uint8) [] // them in a slice. Each part's size is less than or equal to maxSize. Any extra // data in the payload is not used if it is longer than the maximum capacity. func splitPayload(payload []byte, maxSize, maxParts int) [][]byte { - var parts [][]byte + parts := make([][]byte, 0, len(payload)/maxSize) buff := bytes.NewBuffer(payload) for i := 0; i < maxParts && buff.Len() > 0; i++ { diff --git a/single/request.go b/single/request.go index 2b8a11bb2d5fa29ce6dfea434bdee910fa5be3f9..6f2d71ae8183a3f45f0f6dde7edaff80d104fc29 100644 --- a/single/request.go +++ b/single/request.go @@ -68,7 +68,7 @@ const ( const maxNumRequestParts = 255 // GetMaxRequestSize returns the maximum size of a request payload. -func GetMaxRequestSize(net CMix, e2eGrp *cyclic.Group) int { +func GetMaxRequestSize(net Cmix, e2eGrp *cyclic.Group) int { payloadSize := message.GetRequestPayloadSize(net.GetMaxMessageLength(), e2eGrp.GetP().ByteLen()) requestSize := message.GetRequestContentsSize(payloadSize) @@ -96,7 +96,7 @@ func GetMaxRequestSize(net CMix, e2eGrp *cyclic.Group) int { // // The network follower must be running and healthy to transmit. func TransmitRequest(recipient contact.Contact, tag string, payload []byte, - callback Response, param RequestParams, net CMix, rng csprng.Source, + callback Response, param RequestParams, net Cmix, rng csprng.Source, e2eGrp *cyclic.Group) ([]id.Round, receptionID.EphemeralIdentity, error) { if len(payload) > GetMaxRequestSize(net, e2eGrp) { @@ -146,9 +146,9 @@ func TransmitRequest(recipient contact.Contact, tag string, payload []byte, // Encrypt and assemble payload fp := singleUse.NewRequestFingerprint(recipient.DhPubKey) key := singleUse.NewRequestKey(dhKey) - encryptedPayload := auth.Crypt(key, fp[:24], requestPayload.Marshal()) - // Generate CMix message MAC + + // Generate cMix message MAC mac := singleUse.MakeMAC(key, encryptedPayload) // Assemble the payload @@ -188,7 +188,7 @@ func TransmitRequest(recipient contact.Contact, tag string, payload []byte, } err = net.AddFingerprint( - sendingID.Source, processor.cy.GetFingerprint(), &processor) + sendingID.Source, processor.cy.getFingerprint(), &processor) if err != nil { return nil, receptionID.EphemeralIdentity{}, errors.Errorf( errAddFingerprint, i, len(cyphers), tag, recipient, err) @@ -205,8 +205,8 @@ func TransmitRequest(recipient contact.Contact, tag string, payload []byte, } param.CmixParam.Timeout = param.Timeout - rid, _, err := net.Send(recipient.ID, fp, svc, - request.Marshal(), mac, param.CmixParam) + rid, _, err := net.Send( + recipient.ID, fp, svc, request.Marshal(), mac, param.CmixParam) if err != nil { return nil, receptionID.EphemeralIdentity{}, errors.Errorf(errSendRequest, tag, recipient, err) @@ -251,11 +251,12 @@ func generateDhKeys(grp *cyclic.Group, dhPubKey *cyclic.Int, rng io.Reader) ( return nil, nil, errors.Errorf(errGenerateInGroup, err) } privKey := grp.NewIntFromBytes(privKeyBytes) + // Generate public key and DH key publicKey = grp.ExpG(privKey, grp.NewInt(1)) dhKey = grp.Exp(dhPubKey, privKey, grp.NewInt(1)) - return + return dhKey, publicKey, nil } // makeIDs generates a new user ID and address ID with a start and end within diff --git a/single/requestPartProcessor.go b/single/requestPartProcessor.go index b6564d1e8712741b01a6ff675372455c6c5307a0..55575023ebcb62dad92c10d3a0e5045191c27ba5 100644 --- a/single/requestPartProcessor.go +++ b/single/requestPartProcessor.go @@ -31,7 +31,7 @@ type requestPartProcessor struct { func (rpp *requestPartProcessor) Process(msg format.Message, _ receptionID.EphemeralIdentity, round rounds.Round) { - decrypted, err := rpp.cy.Decrypt(msg.GetContents(), msg.GetMac()) + decrypted, err := rpp.cy.decrypt(msg.GetContents(), msg.GetMac()) if err != nil { jww.ERROR.Printf("[SU] Failed to decrypt single-use request payload "+ "for %s to %s: %+v", rpp.tag, rpp.myId, err) diff --git a/single/responseProcessor.go b/single/responseProcessor.go index dac81e10cf77ad0bbaa98ad12953fa2746a05007..99b4be25a286f0fc501eb8999bd4006ecb8c7920 100644 --- a/single/responseProcessor.go +++ b/single/responseProcessor.go @@ -33,7 +33,7 @@ type responseProcessor struct { func (rsp *responseProcessor) Process(ecrMsg format.Message, receptionID receptionID.EphemeralIdentity, round rounds.Round) { - decrypted, err := rsp.cy.Decrypt(ecrMsg.GetContents(), ecrMsg.GetMac()) + decrypted, err := rsp.cy.decrypt(ecrMsg.GetContents(), ecrMsg.GetMac()) if err != nil { jww.ERROR.Printf("[SU] Failed to decrypt single-use response "+ "payload for %s to %s: %+v", diff --git a/ud/interfaces.go b/ud/interfaces.go index 33db0140789e9185b671f7dceee8f05dcc177098..6042ae755fd34b770a3dcf503768d76346ebf50c 100644 --- a/ud/interfaces.go +++ b/ud/interfaces.go @@ -13,7 +13,7 @@ import ( type CMix interface { // CMix is passed down into the single use package, // and thus has to adhere to the sub-interface defined in that package - single.CMix + single.Cmix } // E2E is a sub-interface of the e2e.Handler. It contains the methods