From e3b3ba39f3d014aeab8ae16707d62097c425d3c3 Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Mon, 7 Oct 2019 20:46:15 +0200 Subject: [PATCH] Handle DH double ratchet errors Errors when generating a DH secret where silently ignored, resulting in invalid key material being used. This commit fixes the issue by upgrading the double ratchet library and implementing the updated interface, which uses now slices instead of fixed bytes keys. --- crypto/ethereum_crypto.go | 101 ++++++------ encryption/encryptor.go | 27 +--- encryption/persistence.go | 46 +++--- go.mod | 2 +- go.sum | 4 +- .../status-im/doubleratchet/README.md | 102 +----------- .../status-im/doubleratchet/crypto.go | 8 +- .../status-im/doubleratchet/default_crypto.go | 51 ++++-- .../status-im/doubleratchet/glide.yaml | 2 +- .../status-im/doubleratchet/keys_storage.go | 37 +++-- .../status-im/doubleratchet/message.go | 2 +- .../status-im/doubleratchet/session.go | 19 ++- .../status-im/doubleratchet/session_he.go | 153 ------------------ .../status-im/doubleratchet/state.go | 19 ++- vendor/modules.txt | 2 +- 15 files changed, 185 insertions(+), 390 deletions(-) delete mode 100644 vendor/github.com/status-im/doubleratchet/session_he.go diff --git a/crypto/ethereum_crypto.go b/crypto/ethereum_crypto.go index bf316d8..8c39d1b 100644 --- a/crypto/ethereum_crypto.go +++ b/crypto/ethereum_crypto.go @@ -27,32 +27,26 @@ func (c EthereumCrypto) GenerateDH() (dr.DHPair, error) { return nil, err } - var publicKey [32]byte - copy(publicKey[:], crypto.CompressPubkey(&keys.PublicKey)[:32]) - - var privateKey [32]byte - copy(privateKey[:], crypto.FromECDSA(keys)) - return DHPair{ - PrvKey: privateKey, - PubKey: publicKey, + PubKey: crypto.CompressPubkey(&keys.PublicKey), + PrvKey: crypto.FromECDSA(keys), }, nil } // See the Crypto interface. -func (c EthereumCrypto) DH(dhPair dr.DHPair, dhPub dr.Key) dr.Key { +func (c EthereumCrypto) DH(dhPair dr.DHPair, dhPub dr.Key) (dr.Key, error) { tmpKey := dhPair.PrivateKey() - privateKey, err := crypto.ToECDSA(tmpKey[:]) - eciesPrivate := ecies.ImportECDSA(privateKey) - var a [32]byte + privateKey, err := crypto.ToECDSA(tmpKey) if err != nil { - return a + return nil, err } - publicKey, err := crypto.DecompressPubkey(dhPub[:]) + eciesPrivate := ecies.ImportECDSA(privateKey) + + publicKey, err := crypto.DecompressPubkey(dhPub) if err != nil { - return a + return nil, err } eciesPublic := ecies.ImportECDSAPublic(publicKey) @@ -61,48 +55,52 @@ func (c EthereumCrypto) DH(dhPair dr.DHPair, dhPub dr.Key) dr.Key { 16, 16, ) - if err != nil { - return a + return nil, err } - copy(a[:], key) - return a - + return key, nil } // See the Crypto interface. -func (c EthereumCrypto) KdfRK(rk, dhOut dr.Key) (rootKey, chainKey, headerKey dr.Key) { +func (c EthereumCrypto) KdfRK(rk, dhOut dr.Key) (dr.Key, dr.Key, dr.Key) { var ( // We can use a non-secret constant as the last argument - r = hkdf.New(sha256.New, dhOut[:], rk[:], []byte("rsZUpEuXUqqwXBvSy3EcievAh4cMj6QL")) + r = hkdf.New(sha256.New, dhOut, rk, []byte("rsZUpEuXUqqwXBvSy3EcievAh4cMj6QL")) buf = make([]byte, 96) ) + rootKey := make(dr.Key, 32) + chainKey := make(dr.Key, 32) + headerKey := make(dr.Key, 32) + // The only error here is an entropy limit which won't be reached for such a short buffer. _, _ = io.ReadFull(r, buf) - copy(rootKey[:], buf[:32]) - copy(chainKey[:], buf[32:64]) - copy(headerKey[:], buf[64:96]) - return + copy(rootKey, buf[:32]) + copy(chainKey, buf[32:64]) + copy(headerKey, buf[64:96]) + return rootKey, chainKey, headerKey } // See the Crypto interface. -func (c EthereumCrypto) KdfCK(ck dr.Key) (chainKey dr.Key, msgKey dr.Key) { +func (c EthereumCrypto) KdfCK(ck dr.Key) (dr.Key, dr.Key) { const ( ckInput = 15 mkInput = 16 ) - h := hmac.New(sha256.New, ck[:]) + chainKey := make(dr.Key, 32) + msgKey := make(dr.Key, 32) + + h := hmac.New(sha256.New, ck) _, _ = h.Write([]byte{ckInput}) - copy(chainKey[:], h.Sum(nil)) + copy(chainKey, h.Sum(nil)) h.Reset() _, _ = h.Write([]byte{mkInput}) - copy(msgKey[:], h.Sum(nil)) + copy(msgKey, h.Sum(nil)) return chainKey, msgKey } @@ -110,19 +108,21 @@ func (c EthereumCrypto) KdfCK(ck dr.Key) (chainKey dr.Key, msgKey dr.Key) { // Encrypt uses a slightly different approach than in the algorithm specification: // it uses AES-256-CTR instead of AES-256-CBC for security, ciphertext length and implementation // complexity considerations. -func (c EthereumCrypto) Encrypt(mk dr.Key, plaintext, ad []byte) []byte { +func (c EthereumCrypto) Encrypt(mk dr.Key, plaintext, ad []byte) ([]byte, error) { encKey, authKey, iv := c.deriveEncKeys(mk) ciphertext := make([]byte, aes.BlockSize+len(plaintext)) copy(ciphertext, iv[:]) - var ( - block, _ = aes.NewCipher(encKey[:]) // No error will occur here as encKey is guaranteed to be 32 bytes. - stream = cipher.NewCTR(block, iv[:]) - ) + block, err := aes.NewCipher(encKey) + if err != nil { + return nil, err + } + + stream := cipher.NewCTR(block, iv[:]) stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) - return append(ciphertext, c.computeSignature(authKey[:], ciphertext, ad)...) + return append(ciphertext, c.computeSignature(authKey, ciphertext, ad)...), nil } // See the Crypto interface. @@ -136,37 +136,44 @@ func (c EthereumCrypto) Decrypt(mk dr.Key, authCiphertext, ad []byte) ([]byte, e // Check the signature. encKey, authKey, _ := c.deriveEncKeys(mk) - if s := c.computeSignature(authKey[:], ciphertext, ad); !bytes.Equal(s, signature) { + if s := c.computeSignature(authKey, ciphertext, ad); !bytes.Equal(s, signature) { return nil, fmt.Errorf("invalid signature") } // Decrypt. - var ( - block, _ = aes.NewCipher(encKey[:]) // No error will occur here as encKey is guaranteed to be 32 bytes. - stream = cipher.NewCTR(block, ciphertext[:aes.BlockSize]) - plaintext = make([]byte, len(ciphertext[aes.BlockSize:])) - ) + block, err := aes.NewCipher(encKey) + if err != nil { + return nil, err + } + + stream := cipher.NewCTR(block, ciphertext[:aes.BlockSize]) + plaintext := make([]byte, len(ciphertext[aes.BlockSize:])) + stream.XORKeyStream(plaintext, ciphertext[aes.BlockSize:]) return plaintext, nil } // deriveEncKeys derive keys for message encryption and decryption. Returns (encKey, authKey, iv, err). -func (c EthereumCrypto) deriveEncKeys(mk dr.Key) (encKey dr.Key, authKey dr.Key, iv [16]byte) { +func (c EthereumCrypto) deriveEncKeys(mk dr.Key) (dr.Key, dr.Key, [16]byte) { // First, derive encryption and authentication key out of mk. salt := make([]byte, 32) var ( - r = hkdf.New(sha256.New, mk[:], salt, []byte("pcwSByyx2CRdryCffXJwy7xgVZWtW5Sh")) + r = hkdf.New(sha256.New, mk, salt, []byte("pcwSByyx2CRdryCffXJwy7xgVZWtW5Sh")) buf = make([]byte, 80) ) + encKey := make(dr.Key, 32) + authKey := make(dr.Key, 32) + var iv [16]byte + // The only error here is an entropy limit which won't be reached for such a short buffer. _, _ = io.ReadFull(r, buf) - copy(encKey[:], buf[0:32]) - copy(authKey[:], buf[32:64]) + copy(encKey, buf[0:32]) + copy(authKey, buf[32:64]) copy(iv[:], buf[64:80]) - return + return encKey, authKey, iv } func (c EthereumCrypto) computeSignature(authKey, ciphertext, associatedData []byte) []byte { diff --git a/encryption/encryptor.go b/encryption/encryptor.go index 64a3ca9..ff95c90 100644 --- a/encryption/encryptor.go +++ b/encryption/encryptor.go @@ -272,14 +272,11 @@ func (s *encryptor) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, theirIdentit } if drHeader := msg.GetDRHeader(); drHeader != nil { - var dh [32]byte - copy(dh[:], drHeader.GetKey()) - drMessage := &dr.Message{ Header: dr.MessageHeader{ N: drHeader.GetN(), PN: drHeader.GetPn(), - DH: dh, + DH: drHeader.GetKey(), }, Ciphertext: msg.GetPayload(), } @@ -324,7 +321,7 @@ func (s *encryptor) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, theirIdentit return nil, errors.New("no key specified") } -func (s *encryptor) createNewSession(drInfo *RatchetInfo, sk [32]byte, keyPair crypto.DHPair) (dr.Session, error) { +func (s *encryptor) createNewSession(drInfo *RatchetInfo, sk []byte, keyPair crypto.DHPair) (dr.Session, error) { var err error var session dr.Session @@ -359,14 +356,10 @@ func (s *encryptor) encryptUsingDR(theirIdentityKey *ecdsa.PublicKey, drInfo *Ra var err error var session dr.Session - var sk, publicKey, privateKey [32]byte - copy(sk[:], drInfo.Sk) - copy(publicKey[:], drInfo.PublicKey[:32]) - copy(privateKey[:], drInfo.PrivateKey[:]) keyPair := crypto.DHPair{ - PrvKey: privateKey, - PubKey: publicKey, + PrvKey: drInfo.PrivateKey, + PubKey: drInfo.PublicKey, } // Load session from store first @@ -378,7 +371,7 @@ func (s *encryptor) encryptUsingDR(theirIdentityKey *ecdsa.PublicKey, drInfo *Ra // Create a new one if session == nil { - session, err = s.createNewSession(drInfo, sk, keyPair) + session, err = s.createNewSession(drInfo, drInfo.Sk, keyPair) if err != nil { return nil, nil, err } @@ -403,14 +396,10 @@ func (s *encryptor) decryptUsingDR(theirIdentityKey *ecdsa.PublicKey, drInfo *Ra var err error var session dr.Session - var sk, publicKey, privateKey [32]byte - copy(sk[:], drInfo.Sk) - copy(publicKey[:], drInfo.PublicKey[:32]) - copy(privateKey[:], drInfo.PrivateKey[:]) keyPair := crypto.DHPair{ - PrvKey: privateKey, - PubKey: publicKey, + PrvKey: drInfo.PrivateKey, + PubKey: drInfo.PublicKey, } session, err = s.getDRSession(drInfo.ID) @@ -419,7 +408,7 @@ func (s *encryptor) decryptUsingDR(theirIdentityKey *ecdsa.PublicKey, drInfo *Ra } if session == nil { - session, err = s.createNewSession(drInfo, sk, keyPair) + session, err = s.createNewSession(drInfo, drInfo.Sk, keyPair) if err != nil { return nil, err } diff --git a/encryption/persistence.go b/encryption/persistence.go index 8b3b55d..1706830 100644 --- a/encryption/persistence.go +++ b/encryption/persistence.go @@ -471,8 +471,7 @@ func newSQLiteKeysStorage(db *sql.DB) *sqliteKeysStorage { // Get retrieves the message key for a specified public key and message number func (s *sqliteKeysStorage) Get(pubKey dr.Key, msgNum uint) (dr.Key, bool, error) { - var keyBytes []byte - var key [32]byte + var key []byte stmt, err := s.db.Prepare(`SELECT message_key FROM keys WHERE public_key = ? AND msg_num = ? @@ -483,12 +482,11 @@ func (s *sqliteKeysStorage) Get(pubKey dr.Key, msgNum uint) (dr.Key, bool, error } defer stmt.Close() - err = stmt.QueryRow(pubKey[:], msgNum).Scan(&keyBytes) + err = stmt.QueryRow(pubKey, msgNum).Scan(&key) switch err { case sql.ErrNoRows: return key, false, nil case nil: - copy(key[:], keyBytes) return key, true, nil default: return key, false, err @@ -506,9 +504,9 @@ func (s *sqliteKeysStorage) Put(sessionID []byte, pubKey dr.Key, msgNum uint, mk _, err = stmt.Exec( sessionID, - pubKey[:], + pubKey, msgNum, - mk[:], + mk, seqNum, ) @@ -561,7 +559,7 @@ func (s *sqliteKeysStorage) DeleteMk(pubKey dr.Key, msgNum uint) error { defer stmt.Close() _, err = stmt.Exec( - pubKey[:], + pubKey, msgNum, ) @@ -579,7 +577,7 @@ func (s *sqliteKeysStorage) Count(pubKey dr.Key) (uint, error) { defer stmt.Close() var count uint - err = stmt.QueryRow(pubKey[:]).Scan(&count) + err = stmt.QueryRow(pubKey).Scan(&count) if err != nil { return 0, err } @@ -606,7 +604,7 @@ func (s *sqliteKeysStorage) CountAll() (uint, error) { } // All returns nil -func (s *sqliteKeysStorage) All() (map[dr.Key]map[uint]dr.Key, error) { +func (s *sqliteKeysStorage) All() (map[string]map[uint]dr.Key, error) { return nil, nil } @@ -622,7 +620,7 @@ func newSQLiteSessionStorage(db *sql.DB) *sqliteSessionStorage { // Save persists the specified double ratchet state func (s *sqliteSessionStorage) Save(id []byte, state *dr.State) error { - dhr := state.DHr[:] + dhr := state.DHr dhs := state.DHs dhsPublic := dhs.PublicKey() dhsPrivate := dhs.PrivateKey() @@ -630,12 +628,12 @@ func (s *sqliteSessionStorage) Save(id []byte, state *dr.State) error { step := state.Step keysCount := state.KeysCount - rootChainKey := state.RootCh.CK[:] + rootChainKey := state.RootCh.CK - sendChainKey := state.SendCh.CK[:] + sendChainKey := state.SendCh.CK sendChainN := state.SendCh.N - recvChainKey := state.RecvCh.CK[:] + recvChainKey := state.RecvCh.CK recvChainN := state.RecvCh.N stmt, err := s.db.Prepare(`INSERT INTO sessions(id, dhr, dhs_public, dhs_private, root_chain_key, send_chain_key, send_chain_n, recv_chain_key, recv_chain_n, pn, step, keys_count) @@ -648,8 +646,8 @@ func (s *sqliteSessionStorage) Save(id []byte, state *dr.State) error { _, err = stmt.Exec( id, dhr, - dhsPublic[:], - dhsPrivate[:], + dhsPublic, + dhsPrivate, rootChainKey, sendChainKey, sendChainN, @@ -705,23 +703,23 @@ func (s *sqliteSessionStorage) Load(id []byte) (*dr.State, error) { case sql.ErrNoRows: return nil, nil case nil: - state := dr.DefaultState(toKey(rootChainKey)) + state := dr.DefaultState(rootChainKey) state.PN = uint32(pn) state.Step = step state.KeysCount = keysCount state.DHs = ecrypto.DHPair{ - PrvKey: toKey(dhsPrivate), - PubKey: toKey(dhsPublic), + PrvKey: dhsPrivate, + PubKey: dhsPublic, } - state.DHr = toKey(dhr) + state.DHr = dhr - state.SendCh.CK = toKey(sendChainKey) + state.SendCh.CK = sendChainKey state.SendCh.N = uint32(sendChainN) - state.RecvCh.CK = toKey(recvChainKey) + state.RecvCh.CK = recvChainKey state.RecvCh.N = uint32(recvChainN) return &state, nil @@ -729,9 +727,3 @@ func (s *sqliteSessionStorage) Load(id []byte) (*dr.State, error) { return nil, err } } - -func toKey(a []byte) dr.Key { - var k [32]byte - copy(k[:], a) - return k -} diff --git a/go.mod b/go.mod index 2600e9b..7a71ec9 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/russolsen/ohyeah v0.0.0-20160324131710-f4938c005315 // indirect github.com/russolsen/same v0.0.0-20160222130632-f089df61f51d // indirect github.com/russolsen/transit v0.0.0-20180705123435-0794b4c4505a - github.com/status-im/doubleratchet v2.0.0+incompatible + github.com/status-im/doubleratchet v3.0.0+incompatible github.com/status-im/migrate/v4 v4.6.2-status.2 github.com/status-im/whisper v1.5.1 github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467 diff --git a/go.sum b/go.sum index 7271988..18685a1 100644 --- a/go.sum +++ b/go.sum @@ -249,8 +249,8 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9Nz github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/status-im/doubleratchet v2.0.0+incompatible h1:s77lF1lDubK0RKftxN2vH8G9gwtVVp13ggWfyY4O1q4= -github.com/status-im/doubleratchet v2.0.0+incompatible/go.mod h1:1sqR0+yhiM/bd+wrdX79AOt2csZuJOni0nUDzKNuqOU= +github.com/status-im/doubleratchet v3.0.0+incompatible h1:aJ1ejcSERpSzmWZBgtfYtiU2nF0Q8ZkGyuEPYETXkCY= +github.com/status-im/doubleratchet v3.0.0+incompatible/go.mod h1:1sqR0+yhiM/bd+wrdX79AOt2csZuJOni0nUDzKNuqOU= github.com/status-im/go-ethereum v1.9.5-status.4 h1:F5VrxH9LmTxWl4qwQjs0TI5TgG9dVuZKqGmdwHJ0cWk= github.com/status-im/go-ethereum v1.9.5-status.4/go.mod h1:Ulij8LMpMvXnbnPcmDqrpI+iXoXSjxItuY/wmbasTZU= github.com/status-im/migrate/v4 v4.6.2-status.2 h1:SdC+sMDl/aI7vUlwD2qj2p7KsK4T60IS9z4/rYCCbI8= diff --git a/vendor/github.com/status-im/doubleratchet/README.md b/vendor/github.com/status-im/doubleratchet/README.md index 49e7e08..4c74faa 100644 --- a/vendor/github.com/status-im/doubleratchet/README.md +++ b/vendor/github.com/status-im/doubleratchet/README.md @@ -1,9 +1,9 @@ # doubleratchet -[![Go Report Card](https://goreportcard.com/badge/github.com/tiabc/doubleratchet)](https://goreportcard.com/report/github.com/tiabc/doubleratchet) -[![Build Status](https://travis-ci.org/tiabc/doubleratchet.svg?branch=master)](https://travis-ci.org/tiabc/doubleratchet) -[![Coverage Status](https://coveralls.io/repos/github/tiabc/doubleratchet/badge.svg?branch=master)](https://coveralls.io/github/tiabc/doubleratchet?branch=master) -[![GoDoc](https://godoc.org/github.com/tiabc/doubleratchet?status.svg)](https://godoc.org/github.com/tiabc/doubleratchet) +[![Go Report Card](https://goreportcard.com/badge/github.com/status-im/doubleratchet)](https://goreportcard.com/report/github.com/status-im/doubleratchet) +[![Build Status](https://travis-ci.org/status-im/doubleratchet.svg?branch=master)](https://travis-ci.org/status-im/doubleratchet) +[![Coverage Status](https://coveralls.io/repos/github/status-im/doubleratchet/badge.svg?branch=master)](https://coveralls.io/github/status-im/doubleratchet?branch=master) +[![GoDoc](https://godoc.org/github.com/status-im/doubleratchet?status.svg)](https://godoc.org/github.com/status-im/doubleratchet) [The Double Ratchet Algorithm](https://whispersystems.org/docs/specifications/doubleratchet) is used by two parties to exchange encrypted messages based on a shared secret key. Typically the parties @@ -29,7 +29,6 @@ Let me know if you face any problems or have any questions or suggestions. 1. Skipped messages from a single ratchet step are deleted after 100 ratchet steps. 1. Both parties' sending and receiving chains are initialized with the shared key so that both of them could message each other from the very beginning. -1. Both plain and encrypted header versions are implemented. ### Cryptographic primitives @@ -40,7 +39,7 @@ of them could message each other from the very beginning. ## Installation - go get github.com/tiabc/doubleratchet + go get github.com/status-im/doubleratchet then `cd` into the project directory and install dependencies: @@ -131,97 +130,6 @@ doubleratchet.New( ) ``` -### Header encryption - -If you don't want anybody to see message ordering and your ratchet keys, you can utilize -header encryption. It makes your communication even more secure in a sense that an eavesdropper -can only see ciphertexts and nothing else. However, it adds more complexity to the implementation, -namely: - -1. Parties should agree on 2 more secret keys for encrypting headers before the double ratchet -session. -1. When a recipient receives a message she must first associate the message with its relevant -Double Ratchet session (assuming she has different sessions with different parties). -How this is done is outside of the scope of this library, although [the Pond protocol](https://github.com/agl/pond) offers some -ideas as stated in the Double Ratchet specification. -1. Header encryption makes messages 48 bytes longer. For example, if you're sending message -`how are you?` in a version without header encryption, it will be encrypted into -`iv + len(pt) + signature = 16 + 12 + 32 = 60` bytes plus a header `rk + pn + n = 32 + 4 + 4 = 40` bytes -with 100 bytes in total. In case of the header encryption modification the header will also -be encrypted which will add 48 more bytes with the total of 148 bytes. Note that the longer -your message, the more resulting length it takes. -1. It does a bit more computations especially for skipped messages and will work more slowly. - -#### Example - -In order to create a header-encrypted session, parties should agree upon 3 different shared keys -and Alice should know Bob's public key: - -```go -package main - -import ( - "fmt" - "log" - - "github.com/tiabc/doubleratchet" -) - -func main() { - // Shared keys both parties have already agreed upon before the communication. - var ( - // The key for message keys derivation. - sk = [32]byte{ - 0xeb, 0x8, 0x10, 0x7c, 0x33, 0x54, 0x0, 0x20, - 0xe9, 0x4f, 0x6c, 0x84, 0xe4, 0x39, 0x50, 0x5a, - 0x2f, 0x60, 0xbe, 0x81, 0xa, 0x78, 0x8b, 0xeb, - 0x1e, 0x2c, 0x9, 0x8d, 0x4b, 0x4d, 0xc1, 0x40, - } - - // Header encryption keys. - sharedHka = [32]byte{ - 0xbd, 0x29, 0x18, 0xcb, 0x18, 0x6c, 0x26, 0x32, - 0xd5, 0x82, 0x41, 0x2d, 0x11, 0xa4, 0x55, 0x87, - 0x1e, 0x5b, 0xa3, 0xb5, 0x5a, 0x6d, 0xe1, 0x97, - 0xde, 0xf7, 0x5e, 0xc3, 0xf2, 0xec, 0x1d, 0xd, - } - sharedNhkb = [32]byte{ - 0x32, 0x89, 0x3a, 0xed, 0x4b, 0xf0, 0xbf, 0xc1, - 0xa5, 0xa9, 0x53, 0x73, 0x5b, 0xf9, 0x76, 0xce, - 0x70, 0x8e, 0xe1, 0xa, 0xed, 0x98, 0x1d, 0xe3, - 0xb4, 0xe9, 0xa9, 0x88, 0x54, 0x94, 0xaf, 0x23, - } - ) - - keyPair, err := doubleratchet.DefaultCrypto{}.GenerateDH() - if err != nil { - log.Fatal(err) - } - - // Bob MUST be created with the shared secret, shared header keys and a DH key pair. - bob, err := doubleratchet.NewHE(sk, sharedHka, sharedNhkb, keyPair) - if err != nil { - log.Fatal(err) - } - - // Alic MUST be created with the shared secret, shared header keys and Bob's public key. - alice, err := doubleratchet.NewHEWithRemoteKey(sk, sharedHka, sharedNhkb, keyPair.PublicKey()) - if err != nil { - log.Fatal(err) - } - - // Encryption and decryption is done the same way as in the basic version. - m := alice.RatchetEncrypt([]byte("Hi Bob!"), nil) - - plaintext, err := bob.RatchetDecrypt(m, nil) - if err != nil { - log.Fatal(err) - } - - fmt.Println(string(plaintext)) -} -``` - ## License MIT diff --git a/vendor/github.com/status-im/doubleratchet/crypto.go b/vendor/github.com/status-im/doubleratchet/crypto.go index d4d2cf6..1f9fcfc 100644 --- a/vendor/github.com/status-im/doubleratchet/crypto.go +++ b/vendor/github.com/status-im/doubleratchet/crypto.go @@ -9,11 +9,11 @@ type Crypto interface { // DH returns the output from the Diffie-Hellman calculation between // the private key from the DH key pair dhPair and the DH public key dbPub. - DH(dhPair DHPair, dhPub Key) Key + DH(dhPair DHPair, dhPub Key) (Key, error) // Encrypt returns an AEAD encryption of plaintext with message key mk. The associated_data // is authenticated but is not included in the ciphertext. The AEAD nonce may be set to a constant. - Encrypt(mk Key, plaintext, ad []byte) (authCiphertext []byte) + Encrypt(mk Key, plaintext, ad []byte) (authCiphertext []byte, err error) // Decrypt returns the AEAD decryption of ciphertext with message key mk. Decrypt(mk Key, ciphertext, ad []byte) (plaintext []byte, err error) @@ -27,8 +27,8 @@ type DHPair interface { PublicKey() Key } -// Key is any 32-byte key. It's created for the possibility of pretty hex output. -type Key [32]byte +// Key is any byte representation of a key. +type Key []byte // Stringer interface compliance. func (k Key) String() string { diff --git a/vendor/github.com/status-im/doubleratchet/default_crypto.go b/vendor/github.com/status-im/doubleratchet/default_crypto.go index 5deb68f..ddcf03d 100644 --- a/vendor/github.com/status-im/doubleratchet/default_crypto.go +++ b/vendor/github.com/status-im/doubleratchet/default_crypto.go @@ -32,48 +32,66 @@ func (c DefaultCrypto) GenerateDH() (DHPair, error) { var pubKey [32]byte curve25519.ScalarBaseMult(&pubKey, &privKey) return dhPair{ - privateKey: privKey, - publicKey: pubKey, + privateKey: privKey[:], + publicKey: pubKey[:], }, nil } // DH returns the output from the Diffie-Hellman calculation between // the private key from the DH key pair dhPair and the DH public key dbPub. -func (c DefaultCrypto) DH(dhPair DHPair, dhPub Key) Key { +func (c DefaultCrypto) DH(dhPair DHPair, dhPub Key) (Key, error) { var ( dhOut [32]byte - privKey [32]byte = dhPair.PrivateKey() - pubKey [32]byte = dhPub + privKey [32]byte + pubKey [32]byte ) + if len(dhPair.PrivateKey()) != 32 { + return nil, fmt.Errorf("Invalid private key length: %d", len(dhPair.PrivateKey())) + } + + if len(dhPub) != 32 { + return nil, fmt.Errorf("Invalid private key length: %d", len(dhPair.PrivateKey())) + } + + copy(privKey[:], dhPair.PrivateKey()[:32]) + copy(pubKey[:], dhPub[:32]) + curve25519.ScalarMult(&dhOut, &privKey, &pubKey) - return dhOut + return dhOut[:], nil } // KdfRK returns a pair (32-byte root key, 32-byte chain key) as the output of applying // a KDF keyed by a 32-byte root key rk to a Diffie-Hellman output dhOut. -func (c DefaultCrypto) KdfRK(rk, dhOut Key) (rootKey, chainKey, headerKey Key) { +func (c DefaultCrypto) KdfRK(rk, dhOut Key) (Key, Key, Key) { var ( - r = hkdf.New(sha256.New, dhOut[:], rk[:], []byte("rsZUpEuXUqqwXBvSy3EcievAh4cMj6QL")) + r = hkdf.New(sha256.New, dhOut, rk, []byte("rsZUpEuXUqqwXBvSy3EcievAh4cMj6QL")) buf = make([]byte, 96) ) // The only error here is an entropy limit which won't be reached for such a short buffer. _, _ = io.ReadFull(r, buf) + rootKey := make(Key, 32) + headerKey := make(Key, 32) + chainKey := make(Key, 32) + copy(rootKey[:], buf[:32]) copy(chainKey[:], buf[32:64]) copy(headerKey[:], buf[64:96]) - return + return rootKey, chainKey, headerKey } // KdfCK returns a pair (32-byte chain key, 32-byte message key) as the output of applying // a KDF keyed by a 32-byte chain key ck to some constant. -func (c DefaultCrypto) KdfCK(ck Key) (chainKey Key, msgKey Key) { +func (c DefaultCrypto) KdfCK(ck Key) (Key, Key) { const ( ckInput = 15 mkInput = 16 ) + chainKey := make(Key, 32) + msgKey := make(Key, 32) + h := hmac.New(sha256.New, ck[:]) _, _ = h.Write([]byte{ckInput}) @@ -89,7 +107,7 @@ func (c DefaultCrypto) KdfCK(ck Key) (chainKey Key, msgKey Key) { // Encrypt uses a slightly different approach than in the algorithm specification: // it uses AES-256-CTR instead of AES-256-CBC for security, ciphertext length and implementation // complexity considerations. -func (c DefaultCrypto) Encrypt(mk Key, plaintext, ad []byte) []byte { +func (c DefaultCrypto) Encrypt(mk Key, plaintext, ad []byte) ([]byte, error) { encKey, authKey, iv := c.deriveEncKeys(mk) ciphertext := make([]byte, aes.BlockSize+len(plaintext)) @@ -101,7 +119,7 @@ func (c DefaultCrypto) Encrypt(mk Key, plaintext, ad []byte) []byte { ) stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) - return append(ciphertext, c.computeSignature(authKey[:], ciphertext, ad)...) + return append(ciphertext, c.computeSignature(authKey[:], ciphertext, ad)...), nil } // Decrypt returns the AEAD decryption of ciphertext with message key mk. @@ -131,7 +149,7 @@ func (c DefaultCrypto) Decrypt(mk Key, authCiphertext, ad []byte) ([]byte, error } // deriveEncKeys derive keys for message encryption and decryption. Returns (encKey, authKey, iv, err). -func (c DefaultCrypto) deriveEncKeys(mk Key) (encKey Key, authKey Key, iv [16]byte) { +func (c DefaultCrypto) deriveEncKeys(mk Key) (Key, Key, [16]byte) { // First, derive encryption and authentication key out of mk. salt := make([]byte, 32) var ( @@ -142,10 +160,15 @@ func (c DefaultCrypto) deriveEncKeys(mk Key) (encKey Key, authKey Key, iv [16]by // The only error here is an entropy limit which won't be reached for such a short buffer. _, _ = io.ReadFull(r, buf) + var encKey Key = make(Key, 32) + var authKey Key = make(Key, 32) + var iv [16]byte + copy(encKey[:], buf[0:32]) copy(authKey[:], buf[32:64]) copy(iv[:], buf[64:80]) - return + + return encKey, authKey, iv } func (c DefaultCrypto) computeSignature(authKey, ciphertext, associatedData []byte) []byte { diff --git a/vendor/github.com/status-im/doubleratchet/glide.yaml b/vendor/github.com/status-im/doubleratchet/glide.yaml index 6d3a1c1..8000d0d 100644 --- a/vendor/github.com/status-im/doubleratchet/glide.yaml +++ b/vendor/github.com/status-im/doubleratchet/glide.yaml @@ -1,4 +1,4 @@ -package: github.com/tiabc/doubleratchet +package: github.com/status-im/doubleratchet import: - package: golang.org/x/crypto subpackages: diff --git a/vendor/github.com/status-im/doubleratchet/keys_storage.go b/vendor/github.com/status-im/doubleratchet/keys_storage.go index ba67996..04cff6e 100644 --- a/vendor/github.com/status-im/doubleratchet/keys_storage.go +++ b/vendor/github.com/status-im/doubleratchet/keys_storage.go @@ -2,6 +2,7 @@ package doubleratchet import ( "bytes" + "fmt" "sort" ) @@ -26,20 +27,21 @@ type KeysStorage interface { Count(k Key) (uint, error) // All returns all the keys - All() (map[Key]map[uint]Key, error) + All() (map[string]map[uint]Key, error) } // KeysStorageInMemory is an in-memory message keys storage. type KeysStorageInMemory struct { - keys map[Key]map[uint]InMemoryKey + keys map[string]map[uint]InMemoryKey } // Get returns a message key by the given key and message number. func (s *KeysStorageInMemory) Get(pubKey Key, msgNum uint) (Key, bool, error) { + index := fmt.Sprintf("%x", pubKey) if s.keys == nil { return Key{}, false, nil } - msgs, ok := s.keys[pubKey] + msgs, ok := s.keys[index] if !ok { return Key{}, false, nil } @@ -58,13 +60,15 @@ type InMemoryKey struct { // Put saves the given mk under the specified key and msgNum. func (s *KeysStorageInMemory) Put(sessionID []byte, pubKey Key, msgNum uint, mk Key, seqNum uint) error { + index := fmt.Sprintf("%x", pubKey) + if s.keys == nil { - s.keys = make(map[Key]map[uint]InMemoryKey) + s.keys = make(map[string]map[uint]InMemoryKey) } - if _, ok := s.keys[pubKey]; !ok { - s.keys[pubKey] = make(map[uint]InMemoryKey) + if _, ok := s.keys[index]; !ok { + s.keys[index] = make(map[uint]InMemoryKey) } - s.keys[pubKey][msgNum] = InMemoryKey{ + s.keys[index][msgNum] = InMemoryKey{ sessionID: sessionID, messageKey: mk, seqNum: seqNum, @@ -74,18 +78,20 @@ func (s *KeysStorageInMemory) Put(sessionID []byte, pubKey Key, msgNum uint, mk // DeleteMk ensures there's no message key under the specified key and msgNum. func (s *KeysStorageInMemory) DeleteMk(pubKey Key, msgNum uint) error { + index := fmt.Sprintf("%x", pubKey) + if s.keys == nil { return nil } - if _, ok := s.keys[pubKey]; !ok { + if _, ok := s.keys[index]; !ok { return nil } - if _, ok := s.keys[pubKey][msgNum]; !ok { + if _, ok := s.keys[index][msgNum]; !ok { return nil } - delete(s.keys[pubKey], msgNum) - if len(s.keys[pubKey]) == 0 { - delete(s.keys, pubKey) + delete(s.keys[index], msgNum) + if len(s.keys[index]) == 0 { + delete(s.keys, index) } return nil } @@ -143,15 +149,16 @@ func (s *KeysStorageInMemory) DeleteOldMks(sessionID []byte, deleteUntilSeqKey u // Count returns number of message keys stored under the specified key. func (s *KeysStorageInMemory) Count(pubKey Key) (uint, error) { + index := fmt.Sprintf("%x", pubKey) if s.keys == nil { return 0, nil } - return uint(len(s.keys[pubKey])), nil + return uint(len(s.keys[index])), nil } // All returns all the keys -func (s *KeysStorageInMemory) All() (map[Key]map[uint]Key, error) { - response := make(map[Key]map[uint]Key) +func (s *KeysStorageInMemory) All() (map[string]map[uint]Key, error) { + response := make(map[string]map[uint]Key) for pubKey, keys := range s.keys { response[pubKey] = make(map[uint]Key) diff --git a/vendor/github.com/status-im/doubleratchet/message.go b/vendor/github.com/status-im/doubleratchet/message.go index a424dc2..8a758df 100644 --- a/vendor/github.com/status-im/doubleratchet/message.go +++ b/vendor/github.com/status-im/doubleratchet/message.go @@ -46,7 +46,7 @@ func (mh MessageEncHeader) Decode() (MessageHeader, error) { if len(mh) != 40 { return MessageHeader{}, fmt.Errorf("encoded message header must be 40 bytes, %d given", len(mh)) } - var dh Key + var dh Key = make(Key, 32) copy(dh[:], mh[8:40]) return MessageHeader{ DH: dh, diff --git a/vendor/github.com/status-im/doubleratchet/session.go b/vendor/github.com/status-im/doubleratchet/session.go index 8f297a0..ab1b9d2 100644 --- a/vendor/github.com/status-im/doubleratchet/session.go +++ b/vendor/github.com/status-im/doubleratchet/session.go @@ -1,6 +1,9 @@ package doubleratchet -import "fmt" +import ( + "bytes" + "fmt" +) // Session of the party involved in the Double Ratchet Algorithm. type Session interface { @@ -45,7 +48,12 @@ func NewWithRemoteKey(id []byte, sharedKey, remoteKey Key, storage SessionStorag return nil, fmt.Errorf("can't generate key pair: %s", err) } state.DHr = remoteKey - state.SendCh, _ = state.RootCh.step(state.Crypto.DH(state.DHs, state.DHr)) + secret, err := state.Crypto.DH(state.DHs, state.DHr) + if err != nil { + return nil, fmt.Errorf("can't generate dh secret: %s", err) + } + + state.SendCh, _ = state.RootCh.step(secret) session := &sessionState{id: id, State: state, storage: storage} @@ -94,7 +102,10 @@ func (s *sessionState) RatchetEncrypt(plaintext, ad []byte) (Message, error) { } mk = s.SendCh.step() ) - ct := s.Crypto.Encrypt(mk, plaintext, append(ad, h.Encode()...)) + ct, err := s.Crypto.Encrypt(mk, plaintext, append(ad, h.Encode()...)) + if err != nil { + return Message{}, err + } // Store state if err := s.store(); err != nil { @@ -137,7 +148,7 @@ func (s *sessionState) RatchetDecrypt(m Message, ad []byte) ([]byte, error) { ) // Is there a new ratchet key? - if m.Header.DH != sc.DHr { + if !bytes.Equal(m.Header.DH, sc.DHr) { if skippedKeys1, err = sc.skipMessageKeys(sc.DHr, uint(m.Header.PN)); err != nil { return nil, fmt.Errorf("can't skip previous chain message keys: %s", err) } diff --git a/vendor/github.com/status-im/doubleratchet/session_he.go b/vendor/github.com/status-im/doubleratchet/session_he.go deleted file mode 100644 index 0d2221b..0000000 --- a/vendor/github.com/status-im/doubleratchet/session_he.go +++ /dev/null @@ -1,153 +0,0 @@ -package doubleratchet - -import "fmt" - -// SessionHE is the session of the party involved the Double Ratchet Algorithm with encrypted header modification. -type SessionHE interface { - // RatchetEncrypt performs a symmetric-key ratchet step, then AEAD-encrypts - // the header-encrypted message with the resulting message key. - RatchetEncrypt(plaintext, associatedData []byte) MessageHE - - // RatchetDecrypt is called to AEAD-decrypt header-encrypted messages. - RatchetDecrypt(m MessageHE, associatedData []byte) ([]byte, error) -} - -type sessionHE struct { - State -} - -// NewHE creates session with the shared keys. -func NewHE(sharedKey, sharedHka, sharedNhkb Key, keyPair DHPair, opts ...option) (SessionHE, error) { - state, err := newState(sharedKey, opts...) - if err != nil { - return nil, err - } - state.DHs = keyPair - state.NHKs = sharedNhkb - state.HKs = sharedHka - state.NHKr = sharedHka - return &sessionHE{state}, nil -} - -// NewHEWithRemoteKey creates session with the shared keys and public key of the other party. -func NewHEWithRemoteKey(sharedKey, sharedHka, sharedNhkb, remoteKey Key, opts ...option) (SessionHE, error) { - state, err := newState(sharedKey, opts...) - if err != nil { - return nil, err - } - state.DHs, err = state.Crypto.GenerateDH() - if err != nil { - return nil, fmt.Errorf("can't generate key pair: %s", err) - } - state.DHr = remoteKey - state.SendCh, state.NHKs = state.RootCh.step(state.Crypto.DH(state.DHs, state.DHr)) - state.HKs = sharedHka - state.NHKr = sharedNhkb - state.HKr = sharedHka - return &sessionHE{state}, nil -} - -// RatchetEncrypt performs a symmetric-key ratchet step, then encrypts the header with -// the corresponding header key and the message with resulting message key. -func (s *sessionHE) RatchetEncrypt(plaintext, ad []byte) MessageHE { - var ( - h = MessageHeader{ - DH: s.DHs.PublicKey(), - N: s.SendCh.N, - PN: s.PN, - } - mk = s.SendCh.step() - hEnc = s.Crypto.Encrypt(s.HKs, h.Encode(), nil) - ) - return MessageHE{ - Header: hEnc, - Ciphertext: s.Crypto.Encrypt(mk, plaintext, append(ad, hEnc...)), - } -} - -// RatchetDecrypt is called to AEAD-decrypt header-encrypted messages. -func (s *sessionHE) RatchetDecrypt(m MessageHE, ad []byte) ([]byte, error) { - // Is the message one of the skipped? - if plaintext, err := s.trySkippedMessages(m, ad); err != nil || plaintext != nil { - return plaintext, err - } - - h, step, err := s.decryptHeader(m.Header) - if err != nil { - return nil, fmt.Errorf("can't decrypt header: %s", err) - } - - var ( - // All changes must be applied on a different session object, so that this session won't be modified nor left in a dirty session. - sc = s.State - - skippedKeys1 []skippedKey - skippedKeys2 []skippedKey - ) - if step { - if skippedKeys1, err = sc.skipMessageKeys(sc.HKr, uint(h.PN)); err != nil { - return nil, fmt.Errorf("can't skip previous chain message keys: %s", err) - } - if err = sc.dhRatchet(h); err != nil { - return nil, fmt.Errorf("can't perform ratchet step: %s", err) - } - } - - // After all, update the current chain. - if skippedKeys2, err = sc.skipMessageKeys(sc.HKr, uint(h.N)); err != nil { - return nil, fmt.Errorf("can't skip current chain message keys: %s", err) - } - mk := sc.RecvCh.step() - plaintext, err := s.Crypto.Decrypt(mk, m.Ciphertext, append(ad, m.Header...)) - if err != nil { - return nil, fmt.Errorf("can't decrypt: %s", err) - } - - if err = s.applyChanges(sc, []byte("FIXME"), append(skippedKeys1, skippedKeys2...)); err != nil { - return nil, fmt.Errorf("failed to apply changes: %s", err) - } - - return plaintext, nil -} - -func (s *sessionHE) decryptHeader(encHeader []byte) (MessageHeader, bool, error) { - if encoded, err := s.Crypto.Decrypt(s.HKr, encHeader, nil); err == nil { - h, err := MessageEncHeader(encoded).Decode() - return h, false, err - } - if encoded, err := s.Crypto.Decrypt(s.NHKr, encHeader, nil); err == nil { - h, err := MessageEncHeader(encoded).Decode() - return h, true, err - } - return MessageHeader{}, false, fmt.Errorf("invalid header") -} - -func (s *sessionHE) trySkippedMessages(m MessageHE, ad []byte) ([]byte, error) { - allMessages, err := s.MkSkipped.All() - if err != nil { - return nil, err - } - - for hk, keys := range allMessages { - for n, mk := range keys { - hEnc, err := s.Crypto.Decrypt(hk, m.Header, nil) - if err != nil { - continue - } - h, err := MessageEncHeader(hEnc).Decode() - if err != nil { - return nil, fmt.Errorf("can't decode header %s for skipped message key under (%s, %d)", hEnc, hk, n) - } - if uint(h.N) != n { - continue - } - plaintext, err := s.Crypto.Decrypt(mk, m.Ciphertext, append(ad, m.Header...)) - if err != nil { - return nil, fmt.Errorf("can't decrypt skipped message: %s", err) - } - _ = s.MkSkipped.DeleteMk(hk, n) - return plaintext, nil - } - } - return nil, nil -} diff --git a/vendor/github.com/status-im/doubleratchet/state.go b/vendor/github.com/status-im/doubleratchet/state.go index f3fe734..f173303 100644 --- a/vendor/github.com/status-im/doubleratchet/state.go +++ b/vendor/github.com/status-im/doubleratchet/state.go @@ -85,7 +85,7 @@ func (s *State) applyOptions(opts []option) error { } func newState(sharedKey Key, opts ...option) (State, error) { - if sharedKey == [32]byte{} { + if sharedKey == nil { return State{}, fmt.Errorf("sharedKey mustn't be empty") } @@ -103,13 +103,24 @@ func (s *State) dhRatchet(m MessageHeader) error { s.DHr = m.DH s.HKs = s.NHKs s.HKr = s.NHKr - s.RecvCh, s.NHKr = s.RootCh.step(s.Crypto.DH(s.DHs, s.DHr)) - var err error + + recvSecret, err := s.Crypto.DH(s.DHs, s.DHr) + if err != nil { + return fmt.Errorf("failed to generate dh recieve ratchet secret: %s", err) + } + s.RecvCh, s.NHKr = s.RootCh.step(recvSecret) + s.DHs, err = s.Crypto.GenerateDH() if err != nil { return fmt.Errorf("failed to generate dh pair: %s", err) } - s.SendCh, s.NHKs = s.RootCh.step(s.Crypto.DH(s.DHs, s.DHr)) + + sendSecret, err := s.Crypto.DH(s.DHs, s.DHr) + if err != nil { + return fmt.Errorf("failed to generate dh send ratchet secret: %s", err) + } + s.SendCh, s.NHKs = s.RootCh.step(sendSecret) + return nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 16a752f..c18d940 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -84,7 +84,7 @@ github.com/rs/cors github.com/russolsen/transit # github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 github.com/shopspring/decimal -# github.com/status-im/doubleratchet v2.0.0+incompatible +# github.com/status-im/doubleratchet v3.0.0+incompatible github.com/status-im/doubleratchet # github.com/status-im/migrate/v4 v4.6.2-status.2 github.com/status-im/migrate/v4