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.
This commit is contained in:
parent
143453d660
commit
e3b3ba39f3
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
2
go.mod
2
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
|
||||
|
|
4
go.sum
4
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=
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package: github.com/tiabc/doubleratchet
|
||||
package: github.com/status-im/doubleratchet
|
||||
import:
|
||||
- package: golang.org/x/crypto
|
||||
subpackages:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue