feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions

17
vendor/github.com/status-im/doubleratchet/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,17 @@
/vendor
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
/glide.lock

View File

@@ -0,0 +1,74 @@
run:
concurrency: 4
deadline: 1m
issues-exit-code: 1
tests: true
# build-tags:
# - mytag
skip-dirs:
- static
skip-files:
- .*_mock.go
- jail/doc.go
output:
format: colored-line-number
print-issued-lines: true
print-linter-name: true
linters-settings:
errcheck:
check-type-assertions: false
check-blank: false
govet:
check-shadowing: false
golint:
min-confidence: 0.8
gofmt:
simplify: true
gocyclo:
min-complexity: 16
maligned:
suggest-new: true
dupl:
threshold: 50
goconst:
min-len: 3
min-occurrences: 2
# depguard:
# list-type: blacklist
# include-go-root: false
# packages:
# - github.com/davecgh/go-spew/spew
linters:
disable-all: true
enable:
- deadcode
#- depguard
- errcheck
- gas
- goconst
- gocyclo
- gofmt
- golint
- govet
- ineffassign
- interfacer
- megacheck
- misspell
- structcheck
- typecheck
- unconvert
- varcheck
fast: false
issues:
exclude:
- "composite literal uses unkeyed fields" # govet
# exclude-use-default: true
# max-per-linter: 0
# max-same: 0
# new: false
# new-from-rev: ""
# new-from-patch: ""

18
vendor/github.com/status-im/doubleratchet/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,18 @@
language: go
go:
- 1.8.1
- tip
install:
- go get github.com/stretchr/testify/require
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
- go get ./...
script:
- go test -v -covermode=count -coverprofile=coverage.out
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN
env:
secure: FOIA329dOVl+h897c764Zy7JJuW+BGD1ZqiQnVg6ejIpJoxQCyo/NuW+Pq2ZF8XPndit72pOcvvIuTpgubvyg439nJX7dOdnBfGtkBzeBxUp8IlpR+hK1xFqiXHvEGYuyti8cwfuykD6Umi5AENn60YMroWVaWSqE7iRHQE4rGp3bPbcdilqqkceZAbgzvf7vU3eUs0UGrJM87a7OjgK69tMCPS9Rcfa8HTADgdR3jKNv07lXQieCnomjzuAxFohoNPbP1bL/H5pjImwyHu1Bqsp+jZWXOV7TN2+EdMfaxoSBUjm4F4wPYmzPtyDOLVCfKVyszYtgG7e7R8gsTQALtuYtJed5JD7WLiU+ttJXYraKoerbjsngxT0dcj2YuTIqCWTpgwm30O2eMIeRBhVhY7TVQurrNZevXF83TYAdDM+amchtRFqbtmogUQUV5miG3aMgel7t/Ty209Yx/iRPgyLuvZTN7uzMdGXwk/tNHgdGula7HJoONpypPWRqonvjIZWx6nnHeJ4Ape5zMN6rbVyXjtBl5eUVrapUiEKFwVZadjBj/qCBxFTNiwmzHbBInuowlpPcS+Y/ZYsz3d915UmkPhfKNywVmA1sqGCKecSf9pdh4syAo86zoPy8WyHhsW8ziVOj9Oaq9pUmdzHrRUy1TageDwNIe61pIEQQQI=

21
vendor/github.com/status-im/doubleratchet/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Ivan Tomilov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

59
vendor/github.com/status-im/doubleratchet/Makefile generated vendored Normal file
View File

@@ -0,0 +1,59 @@
.PHONY: help
help: ##@other Show this help
@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
ifndef GOPATH
$(error GOPATH not set. Please set GOPATH and make sure status-go is located at $$GOPATH/src/github.com/status-im/status-go. \
For more information about the GOPATH environment variable, see https://golang.org/doc/code.html#GOPATH)
endif
EXPECTED_PATH=$(shell go env GOPATH)/src/github.com/status-im/doubleratchet
ifneq ($(CURDIR),$(EXPECTED_PATH))
define NOT_IN_GOPATH_ERROR
Current dir is $(CURDIR), which seems to be different from your GOPATH.
Please, build status-go from GOPATH for proper build.
GOPATH = $(shell go env GOPATH)
Current dir = $(CURDIR)
Expected dir = $(EXPECTED_PATH))
See https://golang.org/doc/code.html#GOPATH for more info
endef
$(error $(NOT_IN_GOPATH_ERROR))
endif
GOBIN=$(dir $(realpath $(firstword $(MAKEFILE_LIST))))build/bin
GIT_COMMIT := $(shell git rev-parse --short HEAD)
# This is a code for automatic help generator.
# It supports ANSI colors and categories.
# To add new item into help output, simply add comments
# starting with '##'. To add category, use @category.
GREEN := $(shell echo "\e[32m")
WHITE := $(shell echo "\e[37m")
YELLOW := $(shell echo "\e[33m")
RESET := $(shell echo "\e[0m")
HELP_FUN = \
%help; \
while(<>) { push @{$$help{$$2 // 'options'}}, [$$1, $$3] if /^([a-zA-Z0-9\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
print "Usage: make [target]\n\n"; \
for (sort keys %help) { \
print "${WHITE}$$_:${RESET}\n"; \
for (@{$$help{$$_}}) { \
$$sep = " " x (32 - length $$_->[0]); \
print " ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
}; \
print "\n"; \
}
setup: lint-install ##@other Prepare project for first build
lint-install:
@# The following installs a specific version of golangci-lint, which is appropriate for a CI server to avoid different results from build to build
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $(GOPATH)/bin v1.9.1
lint: ##@other Run linter
@echo "lint"
@golangci-lint run ./...

135
vendor/github.com/status-im/doubleratchet/README.md generated vendored Normal file
View File

@@ -0,0 +1,135 @@
# 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
will use some key agreement protocol (such as X3DH) to agree on the shared secret key.
Following this, the parties will use the Double Ratchet to send and receive encrypted messages.
The parties derive new keys for every Double Ratchet message so that earlier keys cannot be calculated
from later ones. The parties also send Diffie-Hellman public values attached to their messages.
The results of Diffie-Hellman calculations are mixed into the derived keys so that later keys cannot
be calculated from earlier ones. These properties gives some protection to earlier or later encrypted
messages in case of a compromise of a party's keys.
## Project status
The library is in beta version and ready for integration into production projects with care.
Let me know if you face any problems or have any questions or suggestions.
## Implementation notes
### The Double Ratchet logic
1. No more than 1000 messages can be skipped in a single chain.
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.
### Cryptographic primitives
1. **GENERATE_DH():** Curve25519
1. **KDF_RK(rk, dh_out):** HKDF with SHA-256
1. **KDF_CK(ck):** HMAC with SHA-256 and constant inputs
1. **ENCRYPT(mk, pt, associated_data):** AES-256-CTR with HMAC-SHA-256 and IV derived alongside an encryption key
## Installation
go get github.com/status-im/doubleratchet
then `cd` into the project directory and install dependencies:
glide up
If `glide` is not installed, [install it](https://github.com/Masterminds/glide).
## Usage
### Basic usage example
```go
package main
import (
"fmt"
"log"
"github.com/status-im/doubleratchet"
)
func main() {
// The shared key both parties have already agreed upon before the communication.
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,
}
// Diffie-Hellman key pair generated by one of the parties during key exchange or
// by any other means. The public key MUST be sent to another party for initialization
// before the communication begins.
keyPair, err := doubleratchet.DefaultCrypto{}.GenerateDH()
if err != nil {
log.Fatal(err)
}
// Bob MUST be created with the shared secret and a DH key pair.
bob, err := doubleratchet.New([]byte("bob-session-id"), sk, keyPair, nil)
if err != nil {
log.Fatal(err)
}
// Alice MUST be created with the shared secret and Bob's public key.
alice, err := doubleratchet.NewWithRemoteKey([]byte("alice-session-id"), sk, keyPair.PublicKey(), nil)
if err != nil {
log.Fatal(err)
}
// Alice can now encrypt messages under the Double Ratchet session.
m, err := alice.RatchetEncrypt([]byte("Hi Bob!"), nil)
if err != nil {
log.Fatal(err)
}
// Which Bob can decrypt.
plaintext, err := bob.RatchetDecrypt(m, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(plaintext))
}
```
### Options
Additional options can be passed to constructors to customize the algorithm behavior:
```go
doubleratchet.New(
sk, keyPair,
// Your own cryptography supplement implementing doubleratchet.Crypto.
WithCrypto(c),
// Custom storage for skipped keys implementing doubleratchet.KeysStorage.
WithKeysStorage(ks),
// The maximum number of skipped keys. Error will be raised in an attempt to store more keys
// in a single chain while decrypting.
WithMaxSkip(1200),
// The number of Diffie-Hellman ratchet steps skipped keys will be stored.
WithMaxKeep(90),
)
```
## License
MIT

46
vendor/github.com/status-im/doubleratchet/chains.go generated vendored Normal file
View File

@@ -0,0 +1,46 @@
package doubleratchet
// KDFer performs key derivation functions for chains.
type KDFer interface {
// 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.
KdfRK(rk, dhOut Key) (rootKey, chainKey, newHeaderKey Key)
// 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.
KdfCK(ck Key) (chainKey, msgKey Key)
}
type kdfChain struct {
Crypto KDFer
// 32-byte chain key.
CK Key
// Messages count in the chain.
N uint32
}
// step performs symmetric ratchet step and returns a new message key.
func (c *kdfChain) step() Key {
var mk Key
c.CK, mk = c.Crypto.KdfCK(c.CK)
c.N++
return mk
}
type kdfRootChain struct {
Crypto KDFer
// 32-byte kdfChain key.
CK Key
}
// step performs symmetric ratchet step and returns a new chain and new header key.
func (c *kdfRootChain) step(kdfInput Key) (ch kdfChain, nhk Key) {
ch = kdfChain{
Crypto: c.Crypto,
}
c.CK, ch.CK, nhk = c.Crypto.KdfRK(c.CK, kdfInput)
return ch, nhk
}

36
vendor/github.com/status-im/doubleratchet/crypto.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
package doubleratchet
import "encoding/hex"
// Crypto is a cryptography supplement for the library.
type Crypto interface {
// GenerateDH creates a new Diffie-Hellman key pair.
GenerateDH() (DHPair, error)
// 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, 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, err error)
// Decrypt returns the AEAD decryption of ciphertext with message key mk.
Decrypt(mk Key, ciphertext, ad []byte) (plaintext []byte, err error)
KDFer
}
// DHPair is a general interface for DH pairs representation.
type DHPair interface {
PrivateKey() Key
PublicKey() Key
}
// Key is any byte representation of a key.
type Key []byte
// Stringer interface compliance.
func (k Key) String() string {
return hex.EncodeToString(k[:])
}

View File

@@ -0,0 +1,196 @@
package doubleratchet
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"fmt"
"io"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
)
// DefaultCrypto is an implementation of Crypto with cryptographic primitives recommended
// by the Double Ratchet Algorithm specification. However, some details are different,
// see function comments for details.
type DefaultCrypto struct{}
// GenerateDH creates a new Diffie-Hellman key pair.
func (c DefaultCrypto) GenerateDH() (DHPair, error) {
var privKey [32]byte
if _, err := io.ReadFull(rand.Reader, privKey[:]); err != nil {
return dhPair{}, fmt.Errorf("couldn't generate privKey: %s", err)
}
privKey[0] &= 248
privKey[31] &= 127
privKey[31] |= 64
var pubKey [32]byte
curve25519.ScalarBaseMult(&pubKey, &privKey)
return dhPair{
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, error) {
var (
dhOut [32]byte
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[:], 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) (Key, Key, Key) {
var (
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 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) (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})
copy(chainKey[:], h.Sum(nil))
h.Reset()
_, _ = h.Write([]byte{mkInput})
copy(msgKey[:], h.Sum(nil))
return chainKey, msgKey
}
// 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, 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[:])
)
stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
return append(ciphertext, c.computeSignature(authKey[:], ciphertext, ad)...), nil
}
// Decrypt returns the AEAD decryption of ciphertext with message key mk.
func (c DefaultCrypto) Decrypt(mk Key, authCiphertext, ad []byte) ([]byte, error) {
var (
l = len(authCiphertext)
ciphertext = authCiphertext[:l-sha256.Size]
signature = authCiphertext[l-sha256.Size:]
)
// Check the signature.
encKey, authKey, _ := c.deriveEncKeys(mk)
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:]))
)
stream.XORKeyStream(plaintext, ciphertext[aes.BlockSize:])
return plaintext, nil
}
// deriveEncKeys derive keys for message encryption and decryption. Returns (encKey, authKey, iv, err).
func (c DefaultCrypto) deriveEncKeys(mk Key) (Key, 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"))
buf = make([]byte, 80)
)
// 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 encKey, authKey, iv
}
func (c DefaultCrypto) computeSignature(authKey, ciphertext, associatedData []byte) []byte {
h := hmac.New(sha256.New, authKey)
_, _ = h.Write(associatedData)
_, _ = h.Write(ciphertext)
return h.Sum(nil)
}
type dhPair struct {
privateKey Key
publicKey Key
}
func (p dhPair) PrivateKey() Key {
return p.privateKey
}
func (p dhPair) PublicKey() Key {
return p.publicKey
}
func (p dhPair) String() string {
return fmt.Sprintf("{privateKey: %s publicKey: %s}", p.privateKey, p.publicKey)
}

5
vendor/github.com/status-im/doubleratchet/glide.yaml generated vendored Normal file
View File

@@ -0,0 +1,5 @@
package: github.com/status-im/doubleratchet
import:
- package: golang.org/x/crypto
subpackages:
- curve25519

View File

@@ -0,0 +1,171 @@
package doubleratchet
import (
"bytes"
"fmt"
"sort"
)
// KeysStorage is an interface of an abstract in-memory or persistent keys storage.
type KeysStorage interface {
// Get returns a message key by the given key and message number.
Get(k Key, msgNum uint) (mk Key, ok bool, err error)
// Put saves the given mk under the specified key and msgNum.
Put(sessionID []byte, k Key, msgNum uint, mk Key, keySeqNum uint) error
// DeleteMk ensures there's no message key under the specified key and msgNum.
DeleteMk(k Key, msgNum uint) error
// DeleteOldMKeys deletes old message keys for a session.
DeleteOldMks(sessionID []byte, deleteUntilSeqKey uint) error
// TruncateMks truncates the number of keys to maxKeys.
TruncateMks(sessionID []byte, maxKeys int) error
// Count returns number of message keys stored under the specified key.
Count(k Key) (uint, error)
// All returns all the keys
All() (map[string]map[uint]Key, error)
}
// KeysStorageInMemory is an in-memory message keys storage.
type KeysStorageInMemory struct {
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[index]
if !ok {
return Key{}, false, nil
}
mk, ok := msgs[msgNum]
if !ok {
return Key{}, false, nil
}
return mk.messageKey, true, nil
}
type InMemoryKey struct {
messageKey Key
seqNum uint
sessionID []byte
}
// 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[string]map[uint]InMemoryKey)
}
if _, ok := s.keys[index]; !ok {
s.keys[index] = make(map[uint]InMemoryKey)
}
s.keys[index][msgNum] = InMemoryKey{
sessionID: sessionID,
messageKey: mk,
seqNum: seqNum,
}
return nil
}
// 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[index]; !ok {
return nil
}
if _, ok := s.keys[index][msgNum]; !ok {
return nil
}
delete(s.keys[index], msgNum)
if len(s.keys[index]) == 0 {
delete(s.keys, index)
}
return nil
}
// TruncateMks truncates the number of keys to maxKeys.
func (s *KeysStorageInMemory) TruncateMks(sessionID []byte, maxKeys int) error {
var seqNos []uint
// Collect all seq numbers
for _, keys := range s.keys {
for _, inMemoryKey := range keys {
if bytes.Equal(inMemoryKey.sessionID, sessionID) {
seqNos = append(seqNos, inMemoryKey.seqNum)
}
}
}
// Nothing to do if we haven't reached the limit
if len(seqNos) <= maxKeys {
return nil
}
// Take the sequence numbers we care about
sort.Slice(seqNos, func(i, j int) bool { return seqNos[i] < seqNos[j] })
toDeleteSlice := seqNos[:len(seqNos)-maxKeys]
// Put in map for easier lookup
toDelete := make(map[uint]bool)
for _, seqNo := range toDeleteSlice {
toDelete[seqNo] = true
}
for pubKey, keys := range s.keys {
for i, inMemoryKey := range keys {
if toDelete[inMemoryKey.seqNum] && bytes.Equal(inMemoryKey.sessionID, sessionID) {
delete(s.keys[pubKey], i)
}
}
}
return nil
}
// DeleteOldMKeys deletes old message keys for a session.
func (s *KeysStorageInMemory) DeleteOldMks(sessionID []byte, deleteUntilSeqKey uint) error {
for pubKey, keys := range s.keys {
for i, inMemoryKey := range keys {
if inMemoryKey.seqNum <= deleteUntilSeqKey && bytes.Equal(inMemoryKey.sessionID, sessionID) {
delete(s.keys[pubKey], i)
}
}
}
return nil
}
// 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[index])), nil
}
// All returns all the keys
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)
for n, key := range keys {
response[pubKey][n] = key.messageKey
}
}
return response, nil
}

56
vendor/github.com/status-im/doubleratchet/message.go generated vendored Normal file
View File

@@ -0,0 +1,56 @@
package doubleratchet
import (
"encoding/binary"
"fmt"
)
// MessageHE contains ciphertext and an encrypted header.
type MessageHE struct {
Header []byte `json:"header"`
Ciphertext []byte `json:"ciphertext"`
}
// Message is a single message exchanged by the parties.
type Message struct {
Header MessageHeader `json:"header"`
Ciphertext []byte `json:"ciphertext"`
}
// MessageHeader that is prepended to every message.
type MessageHeader struct {
// DHr is the sender's current ratchet public key.
DH Key `json:"dh"`
// N is the number of the message in the sending chain.
N uint32 `json:"n"`
// PN is the length of the previous sending chain.
PN uint32 `json:"pn"`
}
// Encode the header in the binary format.
func (mh MessageHeader) Encode() MessageEncHeader {
buf := make([]byte, 8)
binary.LittleEndian.PutUint32(buf[0:4], mh.N)
binary.LittleEndian.PutUint32(buf[4:8], mh.PN)
return append(buf, mh.DH[:]...)
}
// MessageEncHeader is a binary-encoded representation of a message header.
type MessageEncHeader []byte
// Decode message header out of the binary-encoded representation.
func (mh MessageEncHeader) Decode() (MessageHeader, error) {
// n (4 bytes) + pn (4 bytes) + dh (32 bytes)
if len(mh) != 40 {
return MessageHeader{}, fmt.Errorf("encoded message header must be 40 bytes, %d given", len(mh))
}
var dh Key = make(Key, 32)
copy(dh[:], mh[8:40])
return MessageHeader{
DH: dh,
N: binary.LittleEndian.Uint32(mh[0:4]),
PN: binary.LittleEndian.Uint32(mh[4:8]),
}, nil
}

69
vendor/github.com/status-im/doubleratchet/options.go generated vendored Normal file
View File

@@ -0,0 +1,69 @@
package doubleratchet
import "fmt"
// option is a constructor option.
type option func(*State) error
// WithMaxSkip specifies the maximum number of skipped message in a single chain.
// nolint: golint
func WithMaxSkip(n int) option {
return func(s *State) error {
if n < 0 {
return fmt.Errorf("n must be non-negative")
}
s.MaxSkip = uint(n)
return nil
}
}
// WithMaxKeep specifies how long we keep message keys, counted in number of messages received
// nolint: golint
func WithMaxKeep(n int) option {
return func(s *State) error {
if n < 0 {
return fmt.Errorf("n must be non-negative")
}
s.MaxKeep = uint(n)
return nil
}
}
// WithMaxMessageKeysPerSession specifies the maximum number of message keys per session
// nolint: golint
func WithMaxMessageKeysPerSession(n int) option {
return func(s *State) error {
if n < 0 {
return fmt.Errorf("n must be non-negative")
}
s.MaxMessageKeysPerSession = n
return nil
}
}
// WithKeysStorage replaces the default keys storage with the specified.
// nolint: golint
func WithKeysStorage(ks KeysStorage) option {
return func(s *State) error {
if ks == nil {
return fmt.Errorf("KeysStorage mustn't be nil")
}
s.MkSkipped = ks
return nil
}
}
// WithCrypto replaces the default cryptographic supplement with the specified.
// nolint: golint
func WithCrypto(c Crypto) option {
return func(s *State) error {
if c == nil {
return fmt.Errorf("Crypto mustn't be nil")
}
s.Crypto = c
s.RootCh.Crypto = c
s.SendCh.Crypto = c
s.RecvCh.Crypto = c
return nil
}
}

193
vendor/github.com/status-im/doubleratchet/session.go generated vendored Normal file
View File

@@ -0,0 +1,193 @@
package doubleratchet
import (
"bytes"
"fmt"
)
// Session of the party involved in the Double Ratchet Algorithm.
type Session interface {
// RatchetEncrypt performs a symmetric-key ratchet step, then AEAD-encrypts the message with
// the resulting message key.
RatchetEncrypt(plaintext, associatedData []byte) (Message, error)
// RatchetDecrypt is called to AEAD-decrypt messages.
RatchetDecrypt(m Message, associatedData []byte) ([]byte, error)
//DeleteMk remove a message key from the database
DeleteMk(Key, uint32) error
}
type sessionState struct {
id []byte
State
storage SessionStorage
}
// New creates session with the shared key.
func New(id []byte, sharedKey Key, keyPair DHPair, storage SessionStorage, opts ...option) (Session, error) {
state, err := newState(sharedKey, opts...)
if err != nil {
return nil, err
}
state.DHs = keyPair
session := &sessionState{id: id, State: state, storage: storage}
return session, session.store()
}
// NewWithRemoteKey creates session with the shared key and public key of the other party.
func NewWithRemoteKey(id []byte, sharedKey, remoteKey Key, storage SessionStorage, opts ...option) (Session, 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
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}
return session, session.store()
}
// Load a session from a SessionStorage implementation and apply options.
func Load(id []byte, store SessionStorage, opts ...option) (Session, error) {
state, err := store.Load(id)
if err != nil {
return nil, err
}
if state == nil {
return nil, nil
}
if err = state.applyOptions(opts); err != nil {
return nil, err
}
s := &sessionState{id: id, State: *state}
s.storage = store
return s, nil
}
func (s *sessionState) store() error {
if s.storage != nil {
err := s.storage.Save(s.id, &s.State)
if err != nil {
return err
}
}
return nil
}
// RatchetEncrypt performs a symmetric-key ratchet step, then encrypts the message with
// the resulting message key.
func (s *sessionState) RatchetEncrypt(plaintext, ad []byte) (Message, error) {
var (
h = MessageHeader{
DH: s.DHs.PublicKey(),
N: s.SendCh.N,
PN: s.PN,
}
mk = s.SendCh.step()
)
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 {
return Message{}, err
}
return Message{h, ct}, nil
}
// DeleteMk deletes a message key
func (s *sessionState) DeleteMk(dh Key, n uint32) error {
return s.MkSkipped.DeleteMk(dh, uint(n))
}
// RatchetDecrypt is called to decrypt messages.
func (s *sessionState) RatchetDecrypt(m Message, ad []byte) ([]byte, error) {
// Is the message one of the skipped?
mk, ok, err := s.MkSkipped.Get(m.Header.DH, uint(m.Header.N))
if err != nil {
return nil, err
}
if ok {
plaintext, err := s.Crypto.Decrypt(mk, m.Ciphertext, append(ad, m.Header.Encode()...))
if err != nil {
return nil, fmt.Errorf("can't decrypt skipped message: %s", err)
}
if err := s.store(); err != nil {
return nil, err
}
return plaintext, nil
}
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
)
// Is there a new ratchet key?
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)
}
if err = sc.dhRatchet(m.Header); 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.DHr, uint(m.Header.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.Encode()...))
if err != nil {
return nil, fmt.Errorf("can't decrypt: %s", err)
}
// Append current key, waiting for confirmation
skippedKeys := append(skippedKeys1, skippedKeys2...)
skippedKeys = append(skippedKeys, skippedKey{
key: sc.DHr,
nr: uint(m.Header.N),
mk: mk,
seq: sc.KeysCount,
})
// Increment the number of keys
sc.KeysCount++
// Apply changes.
if err := s.applyChanges(sc, s.id, skippedKeys); err != nil {
return nil, err
}
// Store state
if err := s.store(); err != nil {
return nil, err
}
return plaintext, nil
}

View File

@@ -0,0 +1,9 @@
package doubleratchet
type SessionStorage interface {
// Save state keyed by id
Save(id []byte, state *State) error
// Load state by id
Load(id []byte) (*State, error)
}

178
vendor/github.com/status-im/doubleratchet/state.go generated vendored Normal file
View File

@@ -0,0 +1,178 @@
package doubleratchet
// TODO: During each DH ratchet step a new ratchet key pair and sending chain are generated.
// As the sending chain is not needed right away, these steps could be deferred until the party
// is about to send a new message.
import (
"fmt"
)
// The double ratchet state.
type State struct {
Crypto Crypto
// DH Ratchet public key (the remote key).
DHr Key
// DH Ratchet key pair (the self ratchet key).
DHs DHPair
// Symmetric ratchet root chain.
RootCh kdfRootChain
// Symmetric ratchet sending and receiving chains.
SendCh, RecvCh kdfChain
// Number of messages in previous sending chain.
PN uint32
// Dictionary of skipped-over message keys, indexed by ratchet public key or header key
// and message number.
MkSkipped KeysStorage
// The maximum number of message keys that can be skipped in a single chain.
// WithMaxSkip should be set high enough to tolerate routine lost or delayed messages,
// but low enough that a malicious sender can't trigger excessive recipient computation.
MaxSkip uint
// Receiving header key and next header key. Only used for header encryption.
HKr, NHKr Key
// Sending header key and next header key. Only used for header encryption.
HKs, NHKs Key
// How long we keep messages keys, counted in number of messages received,
// for example if MaxKeep is 5 we only keep the last 5 messages keys, deleting everything n - 5.
MaxKeep uint
// Max number of message keys per session, older keys will be deleted in FIFO fashion
MaxMessageKeysPerSession int
// The number of the current ratchet step.
Step uint
// KeysCount the number of keys generated for decrypting
KeysCount uint
}
func DefaultState(sharedKey Key) State {
c := DefaultCrypto{}
return State{
DHs: dhPair{},
Crypto: c,
RootCh: kdfRootChain{CK: sharedKey, Crypto: c},
// Populate CKs and CKr with sharedKey so that both parties could send and receive
// messages from the very beginning.
SendCh: kdfChain{CK: sharedKey, Crypto: c},
RecvCh: kdfChain{CK: sharedKey, Crypto: c},
MkSkipped: &KeysStorageInMemory{},
MaxSkip: 1000,
MaxMessageKeysPerSession: 2000,
MaxKeep: 2000,
KeysCount: 0,
}
}
func (s *State) applyOptions(opts []option) error {
for i := range opts {
if err := opts[i](s); err != nil {
return fmt.Errorf("failed to apply option: %s", err)
}
}
return nil
}
func newState(sharedKey Key, opts ...option) (State, error) {
if sharedKey == nil {
return State{}, fmt.Errorf("sharedKey mustn't be empty")
}
s := DefaultState(sharedKey)
if err := s.applyOptions(opts); err != nil {
return State{}, err
}
return s, nil
}
// dhRatchet performs a single ratchet step.
func (s *State) dhRatchet(m MessageHeader) error {
s.PN = s.SendCh.N
s.DHr = m.DH
s.HKs = s.NHKs
s.HKr = s.NHKr
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)
}
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
}
type skippedKey struct {
key Key
nr uint
mk Key
seq uint
}
// skipMessageKeys skips message keys in the current receiving chain.
func (s *State) skipMessageKeys(key Key, until uint) ([]skippedKey, error) {
if until < uint(s.RecvCh.N) {
return nil, fmt.Errorf("bad until: probably an out-of-order message that was deleted")
}
if uint(s.RecvCh.N)+s.MaxSkip < until {
return nil, fmt.Errorf("too many messages")
}
skipped := []skippedKey{}
for uint(s.RecvCh.N) < until {
mk := s.RecvCh.step()
skipped = append(skipped, skippedKey{
key: key,
nr: uint(s.RecvCh.N - 1),
mk: mk,
seq: s.KeysCount,
})
// Increment key count
s.KeysCount++
}
return skipped, nil
}
func (s *State) applyChanges(sc State, sessionID []byte, skipped []skippedKey) error {
*s = sc
for _, skipped := range skipped {
if err := s.MkSkipped.Put(sessionID, skipped.key, skipped.nr, skipped.mk, skipped.seq); err != nil {
return err
}
}
if err := s.MkSkipped.TruncateMks(sessionID, s.MaxMessageKeysPerSession); err != nil {
return err
}
if s.KeysCount >= s.MaxKeep {
if err := s.MkSkipped.DeleteOldMks(sessionID, s.KeysCount-s.MaxKeep); err != nil {
return err
}
}
return nil
}