17
vendor/github.com/status-im/doubleratchet/.gitignore
generated
vendored
Normal file
17
vendor/github.com/status-im/doubleratchet/.gitignore
generated
vendored
Normal 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
|
||||
74
vendor/github.com/status-im/doubleratchet/.golangci.yml
generated
vendored
Normal file
74
vendor/github.com/status-im/doubleratchet/.golangci.yml
generated
vendored
Normal 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
18
vendor/github.com/status-im/doubleratchet/.travis.yml
generated
vendored
Normal 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
21
vendor/github.com/status-im/doubleratchet/LICENSE
generated
vendored
Normal 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
59
vendor/github.com/status-im/doubleratchet/Makefile
generated
vendored
Normal 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
135
vendor/github.com/status-im/doubleratchet/README.md
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
# doubleratchet
|
||||
|
||||
[](https://goreportcard.com/report/github.com/status-im/doubleratchet)
|
||||
[](https://travis-ci.org/status-im/doubleratchet)
|
||||
[](https://coveralls.io/github/status-im/doubleratchet?branch=master)
|
||||
[](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
46
vendor/github.com/status-im/doubleratchet/chains.go
generated
vendored
Normal 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
36
vendor/github.com/status-im/doubleratchet/crypto.go
generated
vendored
Normal 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[:])
|
||||
}
|
||||
196
vendor/github.com/status-im/doubleratchet/default_crypto.go
generated
vendored
Normal file
196
vendor/github.com/status-im/doubleratchet/default_crypto.go
generated
vendored
Normal 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
5
vendor/github.com/status-im/doubleratchet/glide.yaml
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package: github.com/status-im/doubleratchet
|
||||
import:
|
||||
- package: golang.org/x/crypto
|
||||
subpackages:
|
||||
- curve25519
|
||||
171
vendor/github.com/status-im/doubleratchet/keys_storage.go
generated
vendored
Normal file
171
vendor/github.com/status-im/doubleratchet/keys_storage.go
generated
vendored
Normal 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
56
vendor/github.com/status-im/doubleratchet/message.go
generated
vendored
Normal 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
69
vendor/github.com/status-im/doubleratchet/options.go
generated
vendored
Normal 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
193
vendor/github.com/status-im/doubleratchet/session.go
generated
vendored
Normal 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
|
||||
}
|
||||
9
vendor/github.com/status-im/doubleratchet/session_storage.go
generated
vendored
Normal file
9
vendor/github.com/status-im/doubleratchet/session_storage.go
generated
vendored
Normal 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
178
vendor/github.com/status-im/doubleratchet/state.go
generated
vendored
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user