diff --git a/Makefile b/Makefile index d450e1775595960ec32e01cbc6190ab8a1b1a770..f4e76c67d734c37f93e11dc05f3fbee128ca5479 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ build: update_release: GOFLAGS="" go get -u gitlab.com/elixxir/primitives@release - GOFLAGS="" go get -u gitlab.com/elixxir/crypto@release + GOFLAGS="" go get -u gitlab.com/elixxir/crypto@Optimus/e2e GOFLAGS="" go get -u gitlab.com/xx_network/crypto@release GOFLAGS="" go get -u gitlab.com/elixxir/comms@release GOFLAGS="" go get -u gitlab.com/xx_network/comms@release diff --git a/go.mod b/go.mod index 6dd76b7810f9c2616a1eafb0f7328fa37cc52dc7..a43c8c3439a7b9faeef6998dba6a3513b577e7a6 100644 --- a/go.mod +++ b/go.mod @@ -15,13 +15,13 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.6.2 - gitlab.com/elixxir/comms v0.0.0-20200810165153-3039323b5656 - gitlab.com/elixxir/crypto v0.0.0-20200806211835-b8ce4472f399 + gitlab.com/elixxir/comms v0.0.0-20200813225502-e879259ca741 + gitlab.com/elixxir/crypto v0.0.0-20200820010132-199706584933 gitlab.com/elixxir/ekv v0.0.0-20200729182028-159355ea5842 - gitlab.com/elixxir/primitives v0.0.0-20200805174810-86b366d1dd2d - gitlab.com/xx_network/comms v0.0.0-20200806235452-3a82720833ba - gitlab.com/xx_network/crypto v0.0.0-20200806235322-ede3c15881ce - gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da + gitlab.com/elixxir/primitives v0.0.0-20200812191102-31c01f08b4dc + gitlab.com/xx_network/comms v0.0.0-20200818182121-732dd75b1947 + gitlab.com/xx_network/crypto v0.0.0-20200812183430-c77a5281c686 + gitlab.com/xx_network/primitives v0.0.0-20200812183720-516a65a4a9b2 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de gopkg.in/ini.v1 v1.52.0 // indirect ) diff --git a/go.sum b/go.sum index 47b80d11956fc1757b47433946a17a1f1e4e7146..1b5a3e0c9844badecef661ff02e257f33531083e 100644 --- a/go.sum +++ b/go.sum @@ -163,11 +163,23 @@ github.com/zeebo/blake3 v0.0.4/go.mod h1:YOZo8A49yNqM0X/Y+JmDUZshJWLt1laHsNSn5ny github.com/zeebo/pcg v0.0.0-20181207190024-3cdc6b625a05/go.mod h1:Gr+78ptB0MwXxm//LBaEvBiaXY7hXJ6KGe2V32X2F6E= gitlab.com/elixxir/comms v0.0.0-20200810165153-3039323b5656 h1:A5S3E7EPL95s3+PGhgAiwnMaa7VcWj8/RtBur3lxdOw= gitlab.com/elixxir/comms v0.0.0-20200810165153-3039323b5656/go.mod h1:EeS1z5wXKrnWOvR0dJlVNVv8OzuiGJz7fa6LyUeN6Q0= +gitlab.com/elixxir/comms v0.0.0-20200813225502-e879259ca741 h1:yIjgre8xSDpnhJkDzTr1lgR7NC1bPWCk2Sgn8udiS2A= +gitlab.com/elixxir/comms v0.0.0-20200813225502-e879259ca741/go.mod h1:hEi6dhcR1v6TGcp3tBy+QFuE25zux206xymuB+PpUqs= gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4 h1:28ftZDeYEko7xptCZzeFWS1Iam95dj46TWFVVlKmw6A= gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c= gitlab.com/elixxir/crypto v0.0.0-20200805174804-bdf909f2a16d/go.mod h1:cu6uNoANVLV0J6HyTL6KqVtVyh9SHU1RjJhytYlsbVQ= gitlab.com/elixxir/crypto v0.0.0-20200806211835-b8ce4472f399 h1:U0oQ0cZeq1Wnv+MxZcny3RkMBONphpc1ambIgGbWovs= gitlab.com/elixxir/crypto v0.0.0-20200806211835-b8ce4472f399/go.mod h1:LthCESQ1AfV1H26URYL9kr+XgXXCE7JfEEPpomFPxIo= +gitlab.com/elixxir/crypto v0.0.0-20200811195343-de268a55c7c4 h1:KlaR5JBxj3oSxSsL3Rax2Rt494sxldM0hxzlr+fBy34= +gitlab.com/elixxir/crypto v0.0.0-20200811195343-de268a55c7c4/go.mod h1:LthCESQ1AfV1H26URYL9kr+XgXXCE7JfEEPpomFPxIo= +gitlab.com/elixxir/crypto v0.0.0-20200819222807-20573ff0ff99 h1:eC5/rPn5ubXkvpth/xeayhkCUcGsBbxnxj7cjJ6vgic= +gitlab.com/elixxir/crypto v0.0.0-20200819222807-20573ff0ff99/go.mod h1:pMageInOuHAQRZXnAD9yQMjhELcxb54bx4+NPT6K6tQ= +gitlab.com/elixxir/crypto v0.0.0-20200819231654-17c3bcbe322f h1:enLfJCcYwpJGTaS9CXZlZEXiJfBv+ruo9qj4y2PKkHk= +gitlab.com/elixxir/crypto v0.0.0-20200819231654-17c3bcbe322f/go.mod h1:pMageInOuHAQRZXnAD9yQMjhELcxb54bx4+NPT6K6tQ= +gitlab.com/elixxir/crypto v0.0.0-20200820001855-d8ff33b65ab5 h1:iZ8A3KnSxtI+WsMOvM7GcDhrb5l8jM+xYlT4VGUJgiw= +gitlab.com/elixxir/crypto v0.0.0-20200820001855-d8ff33b65ab5/go.mod h1:pMageInOuHAQRZXnAD9yQMjhELcxb54bx4+NPT6K6tQ= +gitlab.com/elixxir/crypto v0.0.0-20200820010132-199706584933 h1:A+wWm+OmGAUi4lnIqZUn6LtYguuO+wdLI5OOLwHqr5I= +gitlab.com/elixxir/crypto v0.0.0-20200820010132-199706584933/go.mod h1:pMageInOuHAQRZXnAD9yQMjhELcxb54bx4+NPT6K6tQ= gitlab.com/elixxir/ekv v0.0.0-20200729182028-159355ea5842 h1:m1zDQ6UadpuMnV7nvnyR+DUXE3AisRnVjajTb1xZE4c= gitlab.com/elixxir/ekv v0.0.0-20200729182028-159355ea5842/go.mod h1:bXY0kgbV5BHYda4YY5/hiG5bjimGK+R3PYub5yM9C/s= gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d h1:OKWTmYN5q8XVHo8JXThIH0TCuvl/fLXR7MGVacpqfRg= @@ -179,15 +191,24 @@ gitlab.com/elixxir/primitives v0.0.0-20200804182913-788f47bded40/go.mod h1:tzdFF gitlab.com/elixxir/primitives v0.0.0-20200804231232-ad79a9e8f113/go.mod h1:tzdFFvb1ESmuTCOl1z6+yf6oAICDxH2NPUemVgoNLxc= gitlab.com/elixxir/primitives v0.0.0-20200805174810-86b366d1dd2d h1:ky5oz0D2EmOzk2n/A6Ugwj7S1B6rftxMJwc19sjGkz8= gitlab.com/elixxir/primitives v0.0.0-20200805174810-86b366d1dd2d/go.mod h1:tzdFFvb1ESmuTCOl1z6+yf6oAICDxH2NPUemVgoNLxc= +gitlab.com/elixxir/primitives v0.0.0-20200812191102-31c01f08b4dc h1:43innow2sbJLflB73gwS8gg1meInFXNA1LGYeeDQ6lw= +gitlab.com/elixxir/primitives v0.0.0-20200812191102-31c01f08b4dc/go.mod h1:pJx2DZk9s8vVMnLN7x0hIPngDjbNSdOP6kk3RLlRxHg= gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw= gitlab.com/xx_network/comms v0.0.0-20200806235452-3a82720833ba h1:7nozLSNBX0CfP53DDiDNLJx9obhYGfGf5na0/c9rMso= gitlab.com/xx_network/comms v0.0.0-20200806235452-3a82720833ba/go.mod h1:idLzPGYig57XE7xuU93OlIF9s6NgSJj7OArQvsd5DjY= +gitlab.com/xx_network/comms v0.0.0-20200812204124-8dc2a2a1b9ca/go.mod h1:idLzPGYig57XE7xuU93OlIF9s6NgSJj7OArQvsd5DjY= +gitlab.com/xx_network/comms v0.0.0-20200818182121-732dd75b1947 h1:g0k4nP0o/6qkh09F9d/Fy7Ys93fkyZU+kK71JviLdMg= +gitlab.com/xx_network/comms v0.0.0-20200818182121-732dd75b1947/go.mod h1:idLzPGYig57XE7xuU93OlIF9s6NgSJj7OArQvsd5DjY= gitlab.com/xx_network/crypto v0.0.0-20200806202113-978fa1984bbf/go.mod h1:i0df/q6dDCBiscgD51fMoS2U2TBrm6LcyN822JmB5Tw= gitlab.com/xx_network/crypto v0.0.0-20200806235322-ede3c15881ce h1:gypNBUl2guESEv4MDgH+miwYqR4jPoWM8dLt2Zs5gIs= gitlab.com/xx_network/crypto v0.0.0-20200806235322-ede3c15881ce/go.mod h1:i0df/q6dDCBiscgD51fMoS2U2TBrm6LcyN822JmB5Tw= +gitlab.com/xx_network/crypto v0.0.0-20200812183430-c77a5281c686 h1:XP7U93c2gk08fs5RkNwtnQ+C+/tAT0AYuPC4xy8GF1A= +gitlab.com/xx_network/crypto v0.0.0-20200812183430-c77a5281c686/go.mod h1:i0df/q6dDCBiscgD51fMoS2U2TBrm6LcyN822JmB5Tw= gitlab.com/xx_network/primitives v0.0.0-20200803231956-9b192c57ea7c/go.mod h1:wtdCMr7DPePz9qwctNoAUzZtbOSHSedcK++3Df3psjA= gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da h1:CCVslUwNC7Ul7NG5nu3ThGTSVUt1TxNRX+47f5TUwnk= gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug= +gitlab.com/xx_network/primitives v0.0.0-20200812183720-516a65a4a9b2 h1:Jvv2fLk+2ULDCXiVTPI8Jlg2fBrmq2NSA+dlsN5t2Pk= +gitlab.com/xx_network/primitives v0.0.0-20200812183720-516a65a4a9b2/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug= gitlab.com/xx_network/ring v0.0.2 h1:TlPjlbFdhtJrwvRgIg4ScdngMTaynx/ByHBRZiXCoL0= gitlab.com/xx_network/ring v0.0.2/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= diff --git a/key/key.go b/key/key.go index 66395e39ac88c7209805b2223905f160c07d6d72..a970c4531046f5e976425bc012c6ce76b11b449c 100644 --- a/key/key.go +++ b/key/key.go @@ -2,11 +2,11 @@ package key import ( "github.com/pkg/errors" + "gitlab.com/elixxir/client/globals" "gitlab.com/elixxir/client/parse" - "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/e2e" "gitlab.com/elixxir/crypto/hash" "gitlab.com/elixxir/primitives/format" - jww "github.com/spf13/jwalterweatherman" ) type Key struct { @@ -23,10 +23,9 @@ type Key struct { keyNum uint32 } -func newKey(session *Session, key *cyclic.Int, outer parse.CryptoType, keynum uint32) *Key { +func newKey(session *Session, outer parse.CryptoType, keynum uint32) *Key { return &Key{ session: session, - key: key, outer: outer, keyNum: keynum, } @@ -35,23 +34,151 @@ func newKey(session *Session, key *cyclic.Int, outer parse.CryptoType, keynum ui // return pointers to higher level management structures func (k *Key) GetSession() *Session { return k.session } -// Get key value (cyclic.Int) -func (k *Key) GetKey() *cyclic.Int { return k.key } +// return the type of encryption this key is for +func (k *Key) GetCryptoType() parse.CryptoType { return k.outer } -// Get key type, E2E or Rekey -func (k *Key) GetOuterType() parse.CryptoType { return k.outer } - -// Generate key fingerprint -// NOTE: This function is not a getter, -// it returns a new byte array on each call +// returns the key fingerprint if it has it, otherwise generates it +// this function does not memoize the fingerprint if it doesnt have it because +// in most cases it will not be used for a long time and as a result should not +// be stored in ram. func (k *Key) Fingerprint() format.Fingerprint { - h, _ := hash.NewCMixHash() - h.Write(k.key.Bytes()) - fp := format.Fingerprint{} - copy(fp[:], h.Sum(nil)) + if k.fp != nil { + return *k.fp + } + + var fp format.Fingerprint + switch k.outer { + case parse.E2E: + fp = e2e.DeriveKeyFingerprint(k.session.baseKey, k.keyNum) + case parse.Rekey: + fp = e2e.DeriveReKeyFingerprint(k.session.baseKey, k.keyNum) + default: + globals.Log.FATAL.Panicf("Key has invalid cryptotype: %s", + k.outer) + } + return fp } +// the E2E key to encrypt msg to its intended recipient +// It also properly populates the associated data, including the MAC, fingerprint, +// and encrypted timestamp +func (k *Key) Encrypt(msg format.Message) format.Message { + fp := k.Fingerprint() + key := k.generateKey() + + // set the fingerprint + msg.SetKeyFP(fp) + + // encrypt the timestamp + msg.SetTimestamp(encryptTimestamp(fp, key, msg.GetTimestamp()[:15])) + + // encrypt the payload + encPayload, err := e2e.Encrypt(key, fp, msg.Contents.GetRightAligned(), format.ContentsLen) + if err != nil { + globals.Log.ERROR.Panicf(err.Error()) + } + msg.Contents.Set(encPayload) + + // create the MAC + // MAC is HMAC(key, ciphertext) + // Currently, the MAC doesn't include any of the associated data + MAC := hash.CreateHMAC(encPayload, key[:]) + msg.SetMAC(MAC) + + return msg +} + +// the E2E key to encrypt msg to its intended recipient +// It also properly populates the associated data, including the MAC, fingerprint, +// and encrypted timestamp +// does not handle padding, so the underlying data must be unique or there are +// cryptographic vulnerabilities +func (k *Key) EncryptUnsafe(msg format.Message) format.Message { + fp := k.Fingerprint() + key := k.generateKey() + + // set the fingerprint + msg.SetKeyFP(fp) + + // encrypt the timestamp + msg.SetTimestamp(encryptTimestamp(fp, key, msg.GetTimestamp())) + + // encrypt the payload + encPayload := e2e.CryptUnsafe(key, fp, msg.Contents.Get()) + msg.Contents.Set(encPayload) + + // create the MAC + // MAC is HMAC(key, ciphertext) + // Currently, the MAC doesn't include any of the associated data + MAC := hash.CreateHMAC(encPayload, key[:]) + msg.SetMAC(MAC) + + return msg +} + +// 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) +func (k *Key) Decrypt(msg format.Message) (format.Message, error) { + fp := k.Fingerprint() + key := k.generateKey() + + // Verify the MAC is correct + if !hash.VerifyHMAC(msg.Contents.Get(), msg.GetMAC(), key[:]) { + return format.Message{}, errors.New("HMAC verification failed for E2E message") + } + + //decrypt the timestamp + decryptedTimestamp, err := decryptTimestamp(fp, key, msg.GetTimestamp()) + if err != nil { + return format.Message{}, errors.Errorf("Failed to decrypt E2E "+ + "message: %s", err.Error()) + } + msg.SetTimestamp(decryptedTimestamp) + + // Decrypt the payload + decryptedPayload, err := e2e.Decrypt(key, fp, msg.Contents.Get()) + + if err != nil { + return format.Message{}, errors.Errorf("Failed to decrypt E2E "+ + "message: %s", err.Error()) + } + + //put the decrypted payload back in the message + msg.Contents.SetRightAligned(decryptedPayload) + + return msg, nil +} + +// Decrypt uses the E2E key to decrypt the message +// It returns an error in case of HMAC verification failure +// assumes the payload has no padding +func (k *Key) DecryptUnsafe(msg format.Message) (format.Message, error) { + fp := k.Fingerprint() + key := k.generateKey() + + // Verify the MAC is correct + if !hash.VerifyHMAC(msg.Contents.Get(), msg.GetMAC(), key[:]) { + return format.Message{}, errors.New("HMAC verification failed for E2E message") + } + + //decrypt the timestamp + decryptedTimestamp, err := decryptTimestamp(fp, key, msg.GetTimestamp()) + if err != nil { + return format.Message{}, errors.Errorf("Failed to decrypt E2E "+ + "message: %s", err.Error()) + } + msg.SetTimestamp(decryptedTimestamp) + + // Decrypt the payload + decryptedPayload := e2e.CryptUnsafe(key, fp, msg.Contents.Get()) + + //put the decrypted payload back in the message + msg.Contents.Set(decryptedPayload) + return msg, nil +} + // Sets the key as used func (k *Key) denoteUse() error { switch k.outer { @@ -67,7 +194,58 @@ func (k *Key) denoteUse() error { return errors.WithMessage(err, "Could not use e2e rekey") } default: - jww.FATAL.Panicf("Key has invalid cryptotype: %s", k.outer) + globals.Log.FATAL.Panicf("Key has invalid cryptotype: %s", + k.outer) } return nil -} \ No newline at end of file +} + +// Generates the key and returns it +func (k *Key) generateKey() e2e.Key { + + var key e2e.Key + switch k.outer { + case parse.E2E: + key = e2e.DeriveKey(k.session.baseKey, k.keyNum) + case parse.Rekey: + key = e2e.DeriveReKey(k.session.baseKey, k.keyNum) + default: + globals.Log.FATAL.Panicf("Key has invalid cryptotype: %s", + k.outer) + } + + return key +} + +//encrypts the timestamp +func encryptTimestamp(fp format.Fingerprint, key e2e.Key, ts []byte) []byte { + // 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[:], fp[:e2e.AESBlockSize]) + encryptedTimestamp, err := e2e.EncryptAES256WithIV(key[:], iv, + ts[:15]) + if err != nil { + panic(err) + } + return encryptedTimestamp +} + +//decrypts the timestamp +func decryptTimestamp(fp format.Fingerprint, key e2e.Key, ts []byte) ([]byte, error) { + //create the IV array + var iv [e2e.AESBlockSize]byte + copy(iv[:], fp[:e2e.AESBlockSize]) + + // decrypt the timestamp in the associated data + decryptedTimestamp, err := e2e.DecryptAES256WithIV(key[:], iv, ts) + if err != nil { + return nil, errors.Errorf("Timestamp decryption failed for "+ + "E2E message: %s", err.Error()) + } + + //pad the timestamp + decryptedTimestamp = append(decryptedTimestamp, 0) + return decryptedTimestamp, nil +} diff --git a/key/key_test.go b/key/key_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7fb4d9027cf13c2158ce82da4c5211140e6cced9 --- /dev/null +++ b/key/key_test.go @@ -0,0 +1,251 @@ +package key + +import ( + "bytes" + "gitlab.com/elixxir/client/parse" + "gitlab.com/elixxir/crypto/csprng" + "gitlab.com/elixxir/crypto/cyclic" + dh "gitlab.com/elixxir/crypto/diffieHellman" + "gitlab.com/elixxir/crypto/large" + "gitlab.com/elixxir/primitives/format" + "math/rand" + "reflect" + "testing" + "time" +) + +func TestKey_EncryptDecrypt_Key(t *testing.T) { + + const numTests = 100 + + grp := getGroup() + rng := csprng.NewSystemRNG() + prng := rand.New(rand.NewSource(42)) + + for i := 0; i < numTests; i++ { + //generate the baseKey and session + privateKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng) + publicKey := dh.GeneratePublicKey(privateKey, grp) + baseKey := dh.GenerateSessionKey(privateKey, publicKey, grp) + + s := &Session{ + baseKey: baseKey, + } + + //create the keys + k := newKey(s, parse.E2E, prng.Uint32()) + + //make the message to be encrypted + msg := format.NewMessage() + + //set the contents + contents := make([]byte, format.ContentsLen-format.PadMinLen) + prng.Read(contents) + msg.Contents.SetRightAligned(contents) + + //set the timestamp + now := time.Now() + nowBytes, _ := now.MarshalBinary() + extendedNowBytes := append(nowBytes, 0) + msg.SetTimestamp(extendedNowBytes) + + //Encrypt + ecrMsg := k.Encrypt(*msg) + + if !reflect.DeepEqual(k.Fingerprint(), ecrMsg.GetKeyFP()) { + t.Errorf("Fingerprint in the ecrypted payload is wrong: "+ + "Expected: %+v, Recieved: %+v", k.Fingerprint(), ecrMsg.GetKeyFP()) + } + + //Decrypt + resultMsg, _ := k.Decrypt(ecrMsg) + + if !bytes.Equal(resultMsg.Contents.Get(), msg.Contents.Get()) { + t.Errorf("contents in the decrypted payload does not match: "+ + "Expected: %v, Recieved: %v", msg.Contents.Get(), resultMsg.Contents.Get()) + } + } + +} + +func TestKey_EncryptDecrypt_ReKey(t *testing.T) { + + const numTests = 100 + + grp := getGroup() + rng := csprng.NewSystemRNG() + prng := rand.New(rand.NewSource(42)) + + for i := 0; i < numTests; i++ { + //generate the baseKey and session + privateKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng) + publicKey := dh.GeneratePublicKey(privateKey, grp) + baseKey := dh.GenerateSessionKey(privateKey, publicKey, grp) + + s := &Session{ + baseKey: baseKey, + } + + //create the keys + k := newKey(s, parse.Rekey, prng.Uint32()) + + //make the message to be encrypted + msg := format.NewMessage() + + //set the contents + contents := make([]byte, format.ContentsLen-format.PadMinLen) + prng.Read(contents) + msg.Contents.SetRightAligned(contents) + + //set the timestamp + now := time.Now() + nowBytes, _ := now.MarshalBinary() + extendedNowBytes := append(nowBytes, 0) + msg.SetTimestamp(extendedNowBytes) + + //Encrypt + ecrMsg := k.Encrypt(*msg) + + if !reflect.DeepEqual(k.Fingerprint(), ecrMsg.GetKeyFP()) { + t.Errorf("Fingerprint in the ecrypted payload is wrong: "+ + "Expected: %+v, Recieved: %+v", k.Fingerprint(), ecrMsg.GetKeyFP()) + } + + //Decrypt + resultMsg, _ := k.Decrypt(ecrMsg) + + if !bytes.Equal(resultMsg.Contents.Get(), msg.Contents.Get()) { + t.Errorf("contents in the decrypted payload does not match: "+ + "Expected: %v, Recieved: %v", msg.Contents.Get(), resultMsg.Contents.Get()) + } + } + +} + +func TestKey_EncryptDecrypt_Key_Unsafe(t *testing.T) { + + const numTests = 100 + + grp := getGroup() + rng := csprng.NewSystemRNG() + prng := rand.New(rand.NewSource(42)) + + for i := 0; i < numTests; i++ { + //generate the baseKey and session + privateKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng) + publicKey := dh.GeneratePublicKey(privateKey, grp) + baseKey := dh.GenerateSessionKey(privateKey, publicKey, grp) + + s := &Session{ + baseKey: baseKey, + } + + //create the keys + k := newKey(s, parse.E2E, 1) + + //make the message to be encrypted + msg := format.NewMessage() + + //set the contents + contents := make([]byte, format.ContentsLen) + prng.Read(contents) + msg.Contents.Set(contents) + + //set the timestamp + now := time.Now() + nowBytes, _ := now.MarshalBinary() + extendedNowBytes := append(nowBytes, 0) + msg.SetTimestamp(extendedNowBytes) + + //Encrypt + ecrMsg := k.EncryptUnsafe(*msg) + + if !reflect.DeepEqual(k.Fingerprint(), ecrMsg.GetKeyFP()) { + t.Errorf("Fingerprint in the ecrypted payload is wrong: "+ + "Expected: %+v, Recieved: %+v", k.Fingerprint(), ecrMsg.GetKeyFP()) + } + + //Decrypt + resultMsg, _ := k.DecryptUnsafe(ecrMsg) + + if !bytes.Equal(resultMsg.Contents.Get(), msg.Contents.Get()) { + t.Errorf("contents in the decrypted payload does not match: "+ + "Expected: %v, Recieved: %v", msg.Contents.Get(), resultMsg.Contents.Get()) + } + } +} + +func TestKey_EncryptDecrypt_ReKey_Unsafe(t *testing.T) { + + const numTests = 100 + + grp := getGroup() + rng := csprng.NewSystemRNG() + prng := rand.New(rand.NewSource(42)) + + for i := 0; i < numTests; i++ { + //generate the baseKey and session + privateKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng) + publicKey := dh.GeneratePublicKey(privateKey, grp) + baseKey := dh.GenerateSessionKey(privateKey, publicKey, grp) + + s := &Session{ + baseKey: baseKey, + } + + //create the keys + k := newKey(s, parse.E2E, 1) + + //make the message to be encrypted + msg := format.NewMessage() + + //set the contents + contents := make([]byte, format.ContentsLen) + prng.Read(contents) + msg.Contents.Set(contents) + + //set the timestamp + now := time.Now() + nowBytes, _ := now.MarshalBinary() + extendedNowBytes := append(nowBytes, 0) + msg.SetTimestamp(extendedNowBytes) + + //Encrypt + ecrMsg := k.EncryptUnsafe(*msg) + + if !reflect.DeepEqual(k.Fingerprint(), ecrMsg.GetKeyFP()) { + t.Errorf("Fingerprint in the ecrypted payload is wrong: "+ + "Expected: %+v, Recieved: %+v", k.Fingerprint(), ecrMsg.GetKeyFP()) + } + + //Decrypt + resultMsg, _ := k.DecryptUnsafe(ecrMsg) + + if !bytes.Equal(resultMsg.Contents.Get(), msg.Contents.Get()) { + t.Errorf("contents in the decrypted payload does not match: "+ + "Expected: %v, Recieved: %v", msg.Contents.Get(), resultMsg.Contents.Get()) + } + } + +} + +func getGroup() *cyclic.Group { + e2eGrp := cyclic.NewGroup( + large.NewIntFromString("E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B"+ + "7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE"+ + "DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F"+ + "8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041"+ + "023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45"+ + "3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209"+ + "6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29"+ + "A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E"+ + "37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2"+ + "78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696"+ + "015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E"+ + "6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873"+ + "847AEF49F66E43873", 16), + large.NewIntFromString("2", 16)) + + return e2eGrp + +} diff --git a/key/manager.go b/key/manager.go index af241d19f5c74803166cbc643f05445df4318aeb..68886a2df89819f8648c2e7f488abe7f7b1dd41c 100644 --- a/key/manager.go +++ b/key/manager.go @@ -26,7 +26,7 @@ func newManager(ctx *context, partnerID *id.ID, myPrivKey *cyclic.Int, m.send = NewSessionBuff(m, "send") m.receive = NewSessionBuff(m, "receive") - sendSession, err := newSession(m, myPrivKey, partnerPubKey, sendParams, true) + sendSession, err := newSession(m, myPrivKey, partnerPubKey, sendParams, Send) if err != nil { return nil, errors.WithMessage(err, "Failed to create the send session") @@ -38,7 +38,7 @@ func newManager(ctx *context, partnerID *id.ID, myPrivKey *cyclic.Int, "Failed to add the send session to buffer") } - receiveSession, err := newSession(m, myPrivKey, partnerPubKey, receiveParams, true) + receiveSession, err := newSession(m, myPrivKey, partnerPubKey, receiveParams, Receive) if err != nil { return nil, errors.WithMessage(err, "Failed to create the receive session") @@ -92,7 +92,7 @@ func (m *Manager) NewReceiveSession(partnerPubKey *cyclic.Int, params SessionPar myPrivKey := m.send.GetNewestConfirmed().GetMyPrivKey() //create the session - session, err := newSession(m, myPrivKey, partnerPubKey, params, true) + session, err := newSession(m, myPrivKey, partnerPubKey, params, Receive) if err != nil { return err @@ -116,7 +116,7 @@ func (m *Manager) NewSendSession(myPrivKey *cyclic.Int, params SessionParams) er //find the latest public key from the other party partnerPubKey := m.receive.GetNewestConfirmed().partnerPubKey - session, err := newSession(m, myPrivKey, partnerPubKey, params, false) + session, err := newSession(m, myPrivKey, partnerPubKey, params, Send) if err != nil { return err } diff --git a/key/params_test.go b/key/params_test.go index daa2d304059cc0b0d93272ba8242a9dd39569dbf..edf0360de01cecc811e5d861b28fdbf86665c2ee 100644 --- a/key/params_test.go +++ b/key/params_test.go @@ -6,7 +6,7 @@ import "testing" // Test that the GetDefaultParams function returns the right default data func Test_GetDefaultParams(t *testing.T) { - p := GetDefaultParams() + p := GetDefaultSessionParams() if p.MinKeys != minKeys { t.Errorf("MinKeys mismatch\r\tGot: %d\r\tExpected: %d", p.MinKeys, minKeys) } @@ -17,7 +17,7 @@ func Test_GetDefaultParams(t *testing.T) { t.Errorf("MinKeys mismatch\r\tGot: %d\r\tExpected: %d", p.NumRekeys, numReKeys) } if p.TTLScalar != ttlScalar { - t.Errorf("MinKeys mismatch\r\tGot: %d\r\tExpected: %d", p.TTLScalar, ttlScalar) + t.Errorf("MinKeys mismatch\r\tGot: %v\r\tExpected: %v", p.TTLScalar, ttlScalar) } if p.MinNumKeys != threshold { t.Errorf("MinKeys mismatch\r\tGot: %d\r\tExpected: %d", p.MinNumKeys, threshold) diff --git a/key/session.go b/key/session.go index d066232c0f11a267c64e2dfc0c3882152bb9e060..40898107f3d2eaa785099023c38b640897d89017 100644 --- a/key/session.go +++ b/key/session.go @@ -3,15 +3,20 @@ package key import ( "encoding/json" "github.com/pkg/errors" + "gitlab.com/elixxir/client/parse" "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/crypto/csprng" "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/diffieHellman" + dh "gitlab.com/elixxir/crypto/diffieHellman" + "gitlab.com/elixxir/crypto/e2e" "gitlab.com/elixxir/crypto/hash" "sync" "time" ) const currentSessionVersion = 0 +const keyEKVPrefix = "KEY" +const reKeyEKVPrefix = "REKEY" type Session struct { //pointer to manager @@ -29,6 +34,9 @@ type Session struct { // Partner Public Key partnerPubKey *cyclic.Int + //denotes if keys have been generated before + generated bool + //denotes if the other party has confirmed this key confirmed bool @@ -70,6 +78,7 @@ func newSession(manager *Manager, myPrivKey *cyclic.Int, partnerPubKey *cyclic.I myPrivKey: myPrivKey, partnerPubKey: partnerPubKey, confirmed: t == Receive, + generated: false, } err := session.generateKeys() @@ -102,6 +111,8 @@ func loadSession(manager *Manager, key string) (*Session, error) { return nil, err } + session.generated = true + return &session, nil } @@ -132,8 +143,8 @@ func (s *Session) Delete() error { s.mux.Lock() defer s.mux.Unlock() - s.manager.ctx.fa.remove(s.keys) - s.manager.ctx.fa.remove(s.reKeys) + //s.manager.ctx.fa.remove(s.keys) + //s.manager.ctx.fa.remove(s.reKeys) return s.manager.ctx.kv.Delete(makeSessionKey(s.GetID())) } @@ -219,21 +230,21 @@ func (s *Session) unmarshal(b []byte) error { // Pops the first unused key, skipping any which are denoted as used. The status // is returned to check if a rekey is nessessary func (s *Session) PopKey() (*Key, error) { - keynum, err := s.keyState.Next() + /*keynum, err := s.keyState.Next() if err != nil { return nil, err - } + }*/ - return s.keys[keynum], nil + return nil, nil } // Pops the first unused rekey, skipping any which are denoted as used func (s *Session) PopReKey() (*Key, error) { - keynum, err := s.reKeyState.Next() + /*keynum, err := s.reKeyState.Next() if err != nil { return nil, err - } - return s.reKeys[keynum], nil + }*/ + return nil, nil } // returns the state of the session, which denotes if the Session is active, @@ -298,7 +309,7 @@ func (s *Session) generateKeys() error { s.mux.Lock() defer s.mux.Unlock() - /*check required fields*/ + //check required fields if s.partnerPubKey == nil { return errors.New("Session must have a partner public key") } @@ -307,10 +318,56 @@ func (s *Session) generateKeys() error { return errors.New("Session must have a manager") } - /*generate optional fields if not present*/ + //generate optional fields if not present + grp := s.manager.ctx.grp if s.myPrivKey == nil { - s.myPrivKey, diffieHellman. + rng := csprng.NewSystemRNG() + s.myPrivKey = dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng) + } + + if s.baseKey == nil { + s.baseKey = dh.GenerateSessionKey(s.myPrivKey, s.partnerPubKey, grp) } + // generate key definitions if this is the first instantiation of the + // session + if !s.generated { + //generate ttl and keying info + keysTTL, numKeys := e2e.GenerateKeyTTL(s.baseKey.GetLargeInt(), + s.params.MinKeys, s.params.MaxKeys, s.params.TTLParams) + + s.ttl = uint32(keysTTL) + + s.keyState = newStateVector(s.manager.ctx, keyEKVPrefix, numKeys) + s.reKeyState = newStateVector(s.manager.ctx, reKeyEKVPrefix, uint32(s.params.NumRekeys)) + } + + // add the key and rekey fingerprints to the fingerprint map if in receiving + // mode + if s.t == Receive { + //generate key + keyNums := s.keyState.GetUnusedKeyNums() + + keys := make([]*Key, len(keyNums)) + for i, keyNum := range keyNums { + keys[i] = newKey(s, parse.E2E, keyNum) + } + + //register keys + s.manager.ctx.fa.add(keys) + + //generate rekeys + reKeyNums := s.reKeyState.GetUnusedKeyNums() + rekeys := make([]*Key, len(reKeyNums)) + for i, rekeyNum := range reKeyNums { + rekeys[i] = newKey(s, parse.Rekey, rekeyNum) + } + + //register rekeys + s.manager.ctx.fa.add(rekeys) + } + + s.generated = true + return nil } \ No newline at end of file diff --git a/key/stateVector.go b/key/stateVector.go index 2dbe610794989f18968a7b4acdb967985a771477..6dc36794d49d05e82e532ae0217421bd67e909fd 100644 --- a/key/stateVector.go +++ b/key/stateVector.go @@ -109,6 +109,10 @@ func (sv *stateVector) Used(keynum uint32) bool { sv.mux.RLock() defer sv.mux.RUnlock() + return sv.used(keynum) +} + +func (sv *stateVector) used(keynum uint32) bool { block := keynum / 64 pos := keynum % 64 @@ -138,6 +142,38 @@ func (sv *stateVector) GetNumKeys() uint32 { return sv.numkeys } +//returns a list of unused keys +func (sv *stateVector) GetUnusedKeyNums() []uint32 { + sv.mux.RLock() + defer sv.mux.RUnlock() + + keyNums := make([]uint32, sv.numAvalible) + + for keyNum := sv.firstAvailable; keyNum < sv.numkeys; keyNum++ { + if !sv.used(keyNum) { + keyNums[keyNum-sv.firstAvailable] = keyNum + } + } + + return keyNums +} + +//returns a list of used keys +func (sv *stateVector) GetUsedKeyNums() []uint32 { + sv.mux.RLock() + defer sv.mux.RUnlock() + + keyNums := make([]uint32, sv.numkeys-sv.numAvalible) + + for keyNum := sv.firstAvailable; keyNum < sv.numkeys; keyNum++ { + if sv.used(keyNum) { + keyNums[keyNum-sv.firstAvailable] = keyNum + } + } + + return keyNums +} + // finds the next used state and sets that as firstAvailable. This does not // execute a store and a store must be executed after. func (sv *stateVector) nextAvailable() { diff --git a/key/status.go b/key/status.go index 344a7e4b4a0de861e12b7e719fdb87adbaa3eede..d39610fe3b2ee7c8ff22f7c9a0836f483f870bfa 100644 --- a/key/status.go +++ b/key/status.go @@ -1,5 +1,7 @@ package key +import "fmt" + type Status uint8 const ( @@ -19,5 +21,7 @@ func (a Status) String() string { return "Empty" case RekeyEmpty: return "Rekey Empty" + default: + return fmt.Sprintf("Unknown: %v", int(a)) } } diff --git a/keyStore/keyManager.go b/keyStore/keyManager.go index eedcd5702ca5a21d4dace16a9b4e46a40d2ec696..92ce0f9fba6249bb73420c3ef824ea721faab53e 100644 --- a/keyStore/keyManager.go +++ b/keyStore/keyManager.go @@ -7,7 +7,7 @@ import ( "gitlab.com/elixxir/client/globals" "gitlab.com/elixxir/client/parse" "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/e2e" + //"gitlab.com/elixxir/crypto/e2e" "gitlab.com/elixxir/primitives/format" "gitlab.com/xx_network/primitives/id" "sync/atomic" @@ -274,9 +274,9 @@ func (km *KeyManager) GenerateKeys(grp *cyclic.Group, userID *id.ID) []*E2EKey { numGenSendReKeys := uint(km.numReKeys - usedSendReKeys) // Generate numGenSendKeys send keys - sendKeys := e2e.DeriveKeys(grp, km.baseKey, userID, numGenSendKeys) + sendKeys := make([]*cyclic.Int, numGenSendKeys) // Generate numGenSendReKeys send reKeys - sendReKeys := e2e.DeriveEmergencyKeys(grp, km.baseKey, userID, numGenSendReKeys) + sendReKeys := make([]*cyclic.Int, numGenSendReKeys) // Create Send Keys Stack on keyManager km.sendKeys = NewKeyStack() @@ -306,9 +306,9 @@ func (km *KeyManager) GenerateKeys(grp *cyclic.Group, userID *id.ID) []*E2EKey { // For receiving keys, generate all, and then only add to the map // the unused ones based on recvStates // Generate numKeys recv keys - recvKeys := e2e.DeriveKeys(grp, km.baseKey, km.partner, uint(km.numKeys)) + recvKeys := make([]*cyclic.Int, 5) // Generate numReKeys recv reKeys - recvReKeys := e2e.DeriveEmergencyKeys(grp, km.baseKey, km.partner, uint(km.numReKeys)) + recvReKeys := make([]*cyclic.Int, 5) // Create Receive E2E Keys and put them into the E2eKeys obbj to return into the parent // Skip keys that were already used as per recvStates diff --git a/keyStore/keyStore.go b/keyStore/keyStore.go index 0f49d3fafc2bc60de50677beaf50f795c726656e..8efba3a8ad03fd2ea32005d82318fdd7b1481ee4 100644 --- a/keyStore/keyStore.go +++ b/keyStore/keyStore.go @@ -113,26 +113,20 @@ type KeyStore struct { // Key generation parameters params *KeyParams + // Transmission Keys map + // Maps id.ID to *KeyManager + sendKeyManagers *keyManMap + // Reception Keys map // Maps format.Fingerprint to *E2EKey - fingerprintToKey map[format.Fingerprint]*E2EKey + receptionKeys *inKeyMap // Reception Key Managers map - KeyManagers map[id.ID]*KeyManagerBuffer + recvKeyManagers map[id.ID]*ReceptionKeyManagerBuffer lock sync.Mutex } -//storage version -type KeyStoreDisk struct { - // Key generation parameters - params *KeyParams - - contacts []id.ID -} - - - func NewStore() *KeyStore { ks := new(KeyStore) ks.params = &KeyParams{ diff --git a/keyStore/KeyManagerBuffer.go b/keyStore/recieveKeyManagerBuffer.go similarity index 96% rename from keyStore/KeyManagerBuffer.go rename to keyStore/recieveKeyManagerBuffer.go index c29f77516ebe80191d2be367ee24b83f069575b9..334b5c795b7dc48e8583a31676918a0353a56cef 100644 --- a/keyStore/KeyManagerBuffer.go +++ b/keyStore/recieveKeyManagerBuffer.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/pkg/errors" "gitlab.com/elixxir/primitives/format" - "gitlab.com/xx_network/ring" ) const ReceptionKeyManagerBufferLength = 5 @@ -19,11 +18,8 @@ func NewReceptionKeyManagerBuffer() *ReceptionKeyManagerBuffer { } type ReceptionKeyManagerBuffer struct { - managers ring.Buff -} - -type ReceptionKeyManagerBufferDisk struct { - managers ring.Buff + managers [ReceptionKeyManagerBufferLength]*KeyManager + loc int } // Push takes in a new keymanager obj, and adds it into our circular buffer of keymanagers, diff --git a/keyStore/KeyManagerBuffer_test.go b/keyStore/recieveKeyManagerBuffer_test.go similarity index 100% rename from keyStore/KeyManagerBuffer_test.go rename to keyStore/recieveKeyManagerBuffer_test.go diff --git a/storage/versionedkv.go b/storage/versionedkv.go index e36de9351881c5e91ab241ebe910f35228d1de13..771f47bdf51a812d349f805e4cb6d4bef357d93e 100644 --- a/storage/versionedkv.go +++ b/storage/versionedkv.go @@ -115,7 +115,7 @@ func (v *VersionedKV) Get(key string) (*VersionedObject, error) { // Delete removes a given key from the data store func (v *VersionedKV) Delete(key string) error { - return v.data.Delete(key) + return nil } // Set upserts new data into the storage