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

View File

@@ -0,0 +1,12 @@
# protocol/encryption package
## Hash ratchet encryption
`encryptor.GenerateHashRatchetKey()` generates a hash ratchet key and stores it in in the DB.
There, 2 new tables are created: `hash_ratchet_encryption` and `hash_ratchet_encryption_cache`.
Each hash ratchet key is uniquely identified by the `(groupId, keyId)` pair, where `keyId` is derived from a clock value.
`protocol.BuildHashRatchetKeyExchangeMessage` builds an 1-on-1 message containing the hash ratchet key, given it's ID.
`protocol.BuildHashRatchetMessage` builds a hash ratchet message with arbitrary payload, given `groupId`. It will use the latest hash ratchet key available. `encryptor.encryptWithHR` encrypts the payload using Hash Ratchet algorithms. Intermediate hashes are stored in `hash_ratchet_encryption_cache` table.
`protocol.HandleMessage` uses `encryptor.decryptWithHR` fn for decryption.

View File

@@ -0,0 +1,763 @@
package encryption
import (
"crypto/ecdsa"
"database/sql"
"encoding/hex"
"errors"
"sync"
"time"
dr "github.com/status-im/doubleratchet"
"go.uber.org/zap"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/crypto/ecies"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/encryption/multidevice"
)
var (
errSessionNotFound = errors.New("session not found")
ErrDeviceNotFound = errors.New("device not found")
// ErrNotPairedDevice means that we received a message signed with our public key
// but from a device that has not been paired.
// This should not happen because the protocol forbids sending a message to
// non-paired devices, however, in theory it is possible to receive such a message.
ErrNotPairedDevice = errors.New("received a message from not paired device")
ErrHashRatchetSeqNoTooHigh = errors.New("Hash ratchet seq no is too high")
ErrHashRatchetGroupIDNotFound = errors.New("Hash ratchet group id not found")
ErrNoEncryptionKey = errors.New("no encryption key found for the community")
)
// If we have no bundles, we use a constant so that the message can reach any device.
const (
noInstallationID = "none"
maxHashRatchetSeqNoDelta = 100000
)
type confirmationData struct {
header *dr.MessageHeader
drInfo *RatchetInfo
}
// encryptor defines a service that is responsible for the encryption aspect of the protocol.
type encryptor struct {
persistence *sqlitePersistence
config encryptorConfig
messageIDs map[string]*confirmationData
mutex sync.Mutex
logger *zap.Logger
}
type encryptorConfig struct {
InstallationID string
// Max number of installations we keep synchronized.
MaxInstallations int
// How many consecutive messages can be skipped in the receiving chain.
MaxSkip int
// Any message with seqNo <= currentSeq - maxKeep will be deleted.
MaxKeep int
// How many keys do we store in total per session.
MaxMessageKeysPerSession int
// How long before we refresh the interval in milliseconds
BundleRefreshInterval int64
// The logging object
Logger *zap.Logger
}
// defaultEncryptorConfig returns the default values used by the encryption service
func defaultEncryptorConfig(installationID string, logger *zap.Logger) encryptorConfig {
if logger == nil {
logger = zap.NewNop()
}
return encryptorConfig{
MaxInstallations: 3,
MaxSkip: 1000,
MaxKeep: 3000,
MaxMessageKeysPerSession: 2000,
BundleRefreshInterval: 24 * 60 * 60 * 1000,
InstallationID: installationID,
Logger: logger,
}
}
// newEncryptor creates a new EncryptionService instance.
func newEncryptor(db *sql.DB, config encryptorConfig) *encryptor {
return &encryptor{
persistence: newSQLitePersistence(db),
config: config,
messageIDs: make(map[string]*confirmationData),
logger: config.Logger.With(zap.Namespace("encryptor")),
}
}
func (s *encryptor) keyFromActiveX3DH(theirIdentityKey []byte, theirSignedPreKey []byte, myIdentityKey *ecdsa.PrivateKey) ([]byte, *ecdsa.PublicKey, error) {
sharedKey, ephemeralPubKey, err := PerformActiveX3DH(theirIdentityKey, theirSignedPreKey, myIdentityKey)
if err != nil {
return nil, nil, err
}
return sharedKey, ephemeralPubKey, nil
}
func (s *encryptor) getDRSession(id []byte) (dr.Session, error) {
sessionStorage := s.persistence.SessionStorage()
return dr.Load(
id,
sessionStorage,
dr.WithKeysStorage(s.persistence.KeysStorage()),
dr.WithMaxSkip(s.config.MaxSkip),
dr.WithMaxKeep(s.config.MaxKeep),
dr.WithMaxMessageKeysPerSession(s.config.MaxMessageKeysPerSession),
dr.WithCrypto(crypto.EthereumCrypto{}),
)
}
func confirmationIDString(id []byte) string {
return hex.EncodeToString(id)
}
// ConfirmMessagesProcessed confirms and deletes message keys for the given messages
func (s *encryptor) ConfirmMessageProcessed(messageID []byte) error {
s.mutex.Lock()
defer s.mutex.Unlock()
id := confirmationIDString(messageID)
confirmationData, ok := s.messageIDs[id]
if !ok {
s.logger.Debug("could not confirm message or message already confirmed", zap.String("messageID", id))
// We are ok with this, means no key material is stored (public message, or already confirmed)
return nil
}
// Load session from store first
session, err := s.getDRSession(confirmationData.drInfo.ID)
if err != nil {
return err
}
if err := session.DeleteMk(confirmationData.header.DH, confirmationData.header.N); err != nil {
return err
}
// Clean up
delete(s.messageIDs, id)
return nil
}
// CreateBundle retrieves or creates an X3DH bundle given a private key
func (s *encryptor) CreateBundle(privateKey *ecdsa.PrivateKey, installations []*multidevice.Installation) (*Bundle, error) {
ourIdentityKeyC := crypto.CompressPubkey(&privateKey.PublicKey)
bundleContainer, err := s.persistence.GetAnyPrivateBundle(ourIdentityKeyC, installations)
if err != nil {
return nil, err
}
expired := bundleContainer != nil && bundleContainer.GetBundle().Timestamp < time.Now().Add(-1*time.Duration(s.config.BundleRefreshInterval)*time.Millisecond).UnixNano()
// If the bundle has expired we create a new one
if expired {
// Mark sessions has expired
if err := s.persistence.MarkBundleExpired(bundleContainer.GetBundle().GetIdentity()); err != nil {
return nil, err
}
} else if bundleContainer != nil {
err = SignBundle(privateKey, bundleContainer)
if err != nil {
return nil, err
}
return bundleContainer.GetBundle(), nil
}
// needs transaction/mutex to avoid creating multiple bundles
// although not a problem
bundleContainer, err = NewBundleContainer(privateKey, s.config.InstallationID)
if err != nil {
return nil, err
}
if err = s.persistence.AddPrivateBundle(bundleContainer); err != nil {
return nil, err
}
return s.CreateBundle(privateKey, installations)
}
// DecryptWithDH decrypts message sent with a DH key exchange, and throws away the key after decryption
func (s *encryptor) DecryptWithDH(myIdentityKey *ecdsa.PrivateKey, theirEphemeralKey *ecdsa.PublicKey, payload []byte) ([]byte, error) {
key, err := PerformDH(
ecies.ImportECDSA(myIdentityKey),
ecies.ImportECDSAPublic(theirEphemeralKey),
)
if err != nil {
return nil, err
}
return crypto.DecryptSymmetric(key, payload)
}
// keyFromPassiveX3DH decrypts message sent with a X3DH key exchange, storing the key for future exchanges
func (s *encryptor) keyFromPassiveX3DH(myIdentityKey *ecdsa.PrivateKey, theirIdentityKey *ecdsa.PublicKey, theirEphemeralKey *ecdsa.PublicKey, ourBundleID []byte) ([]byte, error) {
bundlePrivateKey, err := s.persistence.GetPrivateKeyBundle(ourBundleID)
if err != nil {
s.logger.Error("could not get private bundle", zap.Error(err))
return nil, err
}
if bundlePrivateKey == nil {
return nil, errSessionNotFound
}
signedPreKey, err := crypto.ToECDSA(bundlePrivateKey)
if err != nil {
s.logger.Error("could not convert to ecdsa", zap.Error(err))
return nil, err
}
key, err := PerformPassiveX3DH(
theirIdentityKey,
signedPreKey,
theirEphemeralKey,
myIdentityKey,
)
if err != nil {
s.logger.Error("could not perform passive x3dh", zap.Error(err))
return nil, err
}
return key, nil
}
// ProcessPublicBundle persists a bundle
func (s *encryptor) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, b *Bundle) error {
return s.persistence.AddPublicBundle(b)
}
func (s *encryptor) GetMessage(msgs map[string]*EncryptedMessageProtocol) *EncryptedMessageProtocol {
msg := msgs[s.config.InstallationID]
if msg == nil {
msg = msgs[noInstallationID]
}
return msg
}
// DecryptPayload decrypts the payload of a EncryptedMessageProtocol, given an identity private key and the sender's public key
func (s *encryptor) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, theirIdentityKey *ecdsa.PublicKey, theirInstallationID string, msgs map[string]*EncryptedMessageProtocol, messageID []byte) ([]byte, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
msg := s.GetMessage(msgs)
// We should not be sending a signal if it's coming from us, as we receive our own messages
if msg == nil && !samePublicKeys(*theirIdentityKey, myIdentityKey.PublicKey) {
s.logger.Debug("message is coming from someone else, but not targeting our installation id")
return nil, ErrDeviceNotFound
} else if msg == nil && theirInstallationID != s.config.InstallationID {
s.logger.Debug("message is coming from same public key, but different installation id")
return nil, ErrNotPairedDevice
} else if msg == nil && theirInstallationID == s.config.InstallationID {
s.logger.Debug("message is coming from us and is nil")
return nil, nil
}
payload := msg.GetPayload()
if x3dhHeader := msg.GetX3DHHeader(); x3dhHeader != nil {
bundleID := x3dhHeader.GetId()
theirEphemeralKey, err := crypto.DecompressPubkey(x3dhHeader.GetKey())
if err != nil {
return nil, err
}
symmetricKey, err := s.keyFromPassiveX3DH(myIdentityKey, theirIdentityKey, theirEphemeralKey, bundleID)
if err != nil {
return nil, err
}
theirIdentityKeyC := crypto.CompressPubkey(theirIdentityKey)
err = s.persistence.AddRatchetInfo(symmetricKey, theirIdentityKeyC, bundleID, nil, theirInstallationID)
if err != nil {
return nil, err
}
}
if drHeader := msg.GetDRHeader(); drHeader != nil {
drMessage := &dr.Message{
Header: dr.MessageHeader{
N: drHeader.GetN(),
PN: drHeader.GetPn(),
DH: drHeader.GetKey(),
},
Ciphertext: msg.GetPayload(),
}
theirIdentityKeyC := crypto.CompressPubkey(theirIdentityKey)
drInfo, err := s.persistence.GetRatchetInfo(drHeader.GetId(), theirIdentityKeyC, theirInstallationID)
if err != nil {
s.logger.Error("could not get ratchet info", zap.Error(err))
return nil, err
}
// We mark the exchange as successful so we stop sending x3dh header
if err = s.persistence.RatchetInfoConfirmed(drHeader.GetId(), theirIdentityKeyC, theirInstallationID); err != nil {
s.logger.Error("could not confirm ratchet info", zap.Error(err))
return nil, err
}
if drInfo == nil {
s.logger.Error("could not find a session")
return nil, errSessionNotFound
}
confirmationData := &confirmationData{
header: &drMessage.Header,
drInfo: drInfo,
}
s.messageIDs[confirmationIDString(messageID)] = confirmationData
return s.decryptUsingDR(theirIdentityKey, drInfo, drMessage)
}
// Try DH
if header := msg.GetDHHeader(); header != nil {
decompressedKey, err := crypto.DecompressPubkey(header.GetKey())
if err != nil {
return nil, err
}
return s.DecryptWithDH(myIdentityKey, decompressedKey, payload)
}
// Try Hash Ratchet
if header := msg.GetHRHeader(); header != nil {
ratchet := &HashRatchetKeyCompatibility{
GroupID: header.GroupId,
// NOTE: this would be nil in the old format
keyID: header.KeyId,
}
// Old key format
if header.DeprecatedKeyId != 0 {
ratchet.Timestamp = uint64(header.DeprecatedKeyId)
}
decryptedPayload, err := s.DecryptWithHR(ratchet, header.SeqNo, payload)
return decryptedPayload, err
}
return nil, errors.New("no key specified")
}
func (s *encryptor) createNewSession(drInfo *RatchetInfo, sk []byte, keyPair crypto.DHPair) (dr.Session, error) {
var err error
var session dr.Session
if drInfo.PrivateKey != nil {
session, err = dr.New(
drInfo.ID,
sk,
keyPair,
s.persistence.SessionStorage(),
dr.WithKeysStorage(s.persistence.KeysStorage()),
dr.WithMaxSkip(s.config.MaxSkip),
dr.WithMaxKeep(s.config.MaxKeep),
dr.WithMaxMessageKeysPerSession(s.config.MaxMessageKeysPerSession),
dr.WithCrypto(crypto.EthereumCrypto{}))
} else {
session, err = dr.NewWithRemoteKey(
drInfo.ID,
sk,
keyPair.PubKey,
s.persistence.SessionStorage(),
dr.WithKeysStorage(s.persistence.KeysStorage()),
dr.WithMaxSkip(s.config.MaxSkip),
dr.WithMaxKeep(s.config.MaxKeep),
dr.WithMaxMessageKeysPerSession(s.config.MaxMessageKeysPerSession),
dr.WithCrypto(crypto.EthereumCrypto{}))
}
return session, err
}
func (s *encryptor) encryptUsingDR(theirIdentityKey *ecdsa.PublicKey, drInfo *RatchetInfo, payload []byte) ([]byte, *DRHeader, error) {
var err error
var session dr.Session
keyPair := crypto.DHPair{
PrvKey: drInfo.PrivateKey,
PubKey: drInfo.PublicKey,
}
// Load session from store first
session, err = s.getDRSession(drInfo.ID)
if err != nil {
return nil, nil, err
}
// Create a new one
if session == nil {
session, err = s.createNewSession(drInfo, drInfo.Sk, keyPair)
if err != nil {
return nil, nil, err
}
}
response, err := session.RatchetEncrypt(payload, nil)
if err != nil {
return nil, nil, err
}
header := &DRHeader{
Id: drInfo.BundleID,
Key: response.Header.DH[:],
N: response.Header.N,
Pn: response.Header.PN,
}
return response.Ciphertext, header, nil
}
func (s *encryptor) decryptUsingDR(theirIdentityKey *ecdsa.PublicKey, drInfo *RatchetInfo, payload *dr.Message) ([]byte, error) {
var err error
var session dr.Session
keyPair := crypto.DHPair{
PrvKey: drInfo.PrivateKey,
PubKey: drInfo.PublicKey,
}
session, err = s.getDRSession(drInfo.ID)
if err != nil {
return nil, err
}
if session == nil {
session, err = s.createNewSession(drInfo, drInfo.Sk, keyPair)
if err != nil {
return nil, err
}
}
plaintext, err := session.RatchetDecrypt(*payload, nil)
if err != nil {
return nil, err
}
return plaintext, nil
}
func (s *encryptor) encryptWithDH(theirIdentityKey *ecdsa.PublicKey, payload []byte) (*EncryptedMessageProtocol, error) {
symmetricKey, ourEphemeralKey, err := PerformActiveDH(theirIdentityKey)
if err != nil {
return nil, err
}
encryptedPayload, err := crypto.EncryptSymmetric(symmetricKey, payload)
if err != nil {
return nil, err
}
return &EncryptedMessageProtocol{
DHHeader: &DHHeader{
Key: crypto.CompressPubkey(ourEphemeralKey),
},
Payload: encryptedPayload,
}, nil
}
func (s *encryptor) EncryptPayloadWithDH(theirIdentityKey *ecdsa.PublicKey, payload []byte) (map[string]*EncryptedMessageProtocol, error) {
response := make(map[string]*EncryptedMessageProtocol)
dmp, err := s.encryptWithDH(theirIdentityKey, payload)
if err != nil {
return nil, err
}
response[noInstallationID] = dmp
return response, nil
}
// GetPublicBundle returns the active installations bundles for a given user
func (s *encryptor) GetPublicBundle(theirIdentityKey *ecdsa.PublicKey, installations []*multidevice.Installation) (*Bundle, error) {
return s.persistence.GetPublicBundle(theirIdentityKey, installations)
}
// EncryptPayload returns a new EncryptedMessageProtocol with a given payload encrypted, given a recipient's public key and the sender private identity key
func (s *encryptor) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, myIdentityKey *ecdsa.PrivateKey, installations []*multidevice.Installation, payload []byte) (map[string]*EncryptedMessageProtocol, []*multidevice.Installation, error) {
logger := s.logger.With(
zap.String("site", "EncryptPayload"),
zap.String("their-identity-key", types.EncodeHex(crypto.FromECDSAPub(theirIdentityKey))))
// Which installations we are sending the message to
var targetedInstallations []*multidevice.Installation
s.mutex.Lock()
defer s.mutex.Unlock()
if len(installations) == 0 {
// We don't have any, send a message with DH
logger.Debug("no installations, sending to all devices")
encryptedPayload, err := s.EncryptPayloadWithDH(theirIdentityKey, payload)
return encryptedPayload, targetedInstallations, err
}
theirIdentityKeyC := crypto.CompressPubkey(theirIdentityKey)
response := make(map[string]*EncryptedMessageProtocol)
for _, installation := range installations {
installationID := installation.ID
ilogger := logger.With(zap.String("installation-id", installationID))
ilogger.Debug("processing installation")
if s.config.InstallationID == installationID {
continue
}
bundle, err := s.persistence.GetPublicBundle(theirIdentityKey, []*multidevice.Installation{installation})
if err != nil {
return nil, nil, err
}
// See if a session is there already
drInfo, err := s.persistence.GetAnyRatchetInfo(theirIdentityKeyC, installationID)
if err != nil {
return nil, nil, err
}
targetedInstallations = append(targetedInstallations, installation)
if drInfo != nil {
ilogger.Debug("found DR info for installation")
encryptedPayload, drHeader, err := s.encryptUsingDR(theirIdentityKey, drInfo, payload)
if err != nil {
return nil, nil, err
}
dmp := EncryptedMessageProtocol{
Payload: encryptedPayload,
DRHeader: drHeader,
}
if drInfo.EphemeralKey != nil {
dmp.X3DHHeader = &X3DHHeader{
Key: drInfo.EphemeralKey,
Id: drInfo.BundleID,
}
}
response[drInfo.InstallationID] = &dmp
continue
}
theirSignedPreKeyContainer := bundle.GetSignedPreKeys()[installationID]
// This should not be nil at this point
if theirSignedPreKeyContainer == nil {
ilogger.Warn("could not find DR info or bundle for installation")
continue
}
ilogger.Debug("DR info not found, using bundle")
theirSignedPreKey := theirSignedPreKeyContainer.GetSignedPreKey()
sharedKey, ourEphemeralKey, err := s.keyFromActiveX3DH(theirIdentityKeyC, theirSignedPreKey, myIdentityKey)
if err != nil {
return nil, nil, err
}
theirIdentityKeyC := crypto.CompressPubkey(theirIdentityKey)
ourEphemeralKeyC := crypto.CompressPubkey(ourEphemeralKey)
err = s.persistence.AddRatchetInfo(sharedKey, theirIdentityKeyC, theirSignedPreKey, ourEphemeralKeyC, installationID)
if err != nil {
return nil, nil, err
}
x3dhHeader := &X3DHHeader{
Key: ourEphemeralKeyC,
Id: theirSignedPreKey,
}
drInfo, err = s.persistence.GetRatchetInfo(theirSignedPreKey, theirIdentityKeyC, installationID)
if err != nil {
return nil, nil, err
}
if drInfo != nil {
encryptedPayload, drHeader, err := s.encryptUsingDR(theirIdentityKey, drInfo, payload)
if err != nil {
return nil, nil, err
}
dmp := &EncryptedMessageProtocol{
Payload: encryptedPayload,
X3DHHeader: x3dhHeader,
DRHeader: drHeader,
}
response[drInfo.InstallationID] = dmp
}
}
var installationIDs []string
for _, i := range targetedInstallations {
installationIDs = append(installationIDs, i.ID)
}
logger.Info(
"built a message",
zap.Strings("installation-ids", installationIDs),
)
return response, targetedInstallations, nil
}
func (s *encryptor) getNextHashRatchet(groupID []byte) (*HashRatchetKeyCompatibility, error) {
latestKey, err := s.persistence.GetCurrentKeyForGroup(groupID)
if err != nil {
return nil, err
}
return latestKey.GenerateNext()
}
// GenerateHashRatchetKey Generates and stores a hash ratchet key given a group ID
func (s *encryptor) GenerateHashRatchetKey(groupID []byte) (*HashRatchetKeyCompatibility, error) {
key, err := s.getNextHashRatchet(groupID)
if err != nil {
return nil, err
}
return key, s.persistence.SaveHashRatchetKey(key)
}
// EncryptHashRatchetPayload returns a new EncryptedMessageProtocol with a given payload encrypted, given a group's key
func (s *encryptor) EncryptHashRatchetPayload(ratchet *HashRatchetKeyCompatibility, payload []byte) (map[string]*EncryptedMessageProtocol, error) {
logger := s.logger.With(
zap.String("site", "EncryptHashRatchetPayload"),
zap.Any("group-id", ratchet.GroupID),
zap.Any("key-id", ratchet.keyID))
s.mutex.Lock()
defer s.mutex.Unlock()
logger.Debug("encrypting hash ratchet message")
encryptedPayload, newSeqNo, err := s.EncryptWithHR(ratchet, payload)
if err != nil {
return nil, err
}
keyID, err := ratchet.GetKeyID()
if err != nil {
return nil, err
}
dmp := &EncryptedMessageProtocol{
HRHeader: &HRHeader{
DeprecatedKeyId: ratchet.DeprecatedKeyID(),
GroupId: ratchet.GroupID,
KeyId: keyID,
SeqNo: newSeqNo,
},
Payload: encryptedPayload,
}
response := make(map[string]*EncryptedMessageProtocol)
response[noInstallationID] = dmp
return response, err
}
func samePublicKeys(pubKey1, pubKey2 ecdsa.PublicKey) bool {
return pubKey1.X.Cmp(pubKey2.X) == 0 && pubKey1.Y.Cmp(pubKey2.Y) == 0
}
func (s *encryptor) EncryptWithHR(ratchet *HashRatchetKeyCompatibility, payload []byte) ([]byte, uint32, error) {
hrCache, err := s.persistence.GetHashRatchetCache(ratchet, 0) // Get latest seqNo
if err != nil {
return nil, 0, err
}
if hrCache == nil {
return nil, 0, ErrNoEncryptionKey
}
var dbHash []byte
if len(hrCache.Hash) == 0 {
dbHash = hrCache.Key
} else {
dbHash = hrCache.Hash
}
hash := crypto.Keccak256Hash(dbHash)
encryptedPayload, err := crypto.EncryptSymmetric(hash.Bytes(), payload)
if err != nil {
return nil, 0, err
}
newSeqNo := hrCache.SeqNo + 1
err = s.persistence.SaveHashRatchetKeyHash(ratchet, hash.Bytes(), newSeqNo)
if err != nil {
return nil, 0, err
}
return encryptedPayload, newSeqNo, nil
}
func (s *encryptor) DecryptWithHR(ratchet *HashRatchetKeyCompatibility, seqNo uint32, payload []byte) ([]byte, error) {
// Key exchange message, nothing to decrypt
if seqNo == 0 {
return payload, nil
}
hrCache, err := s.persistence.GetHashRatchetCache(ratchet, seqNo)
if err != nil {
return nil, err
}
if hrCache == nil {
return nil, ErrHashRatchetGroupIDNotFound
}
// Handle mesages with seqNo less than the one in db
// 1. Check cache. If present for a particular seqNo, all good
// 2. Otherwise, get the latest one for that keyId
// 3. Every time the key is generated, it has to be saved in the cache along with the hash
var hash []byte = hrCache.Hash
if hrCache.SeqNo == seqNo {
// We already have the hash for this seqNo
hash = hrCache.Hash
} else {
if hrCache.SeqNo == 0 {
// No cache records found for this keyId
hash = hrCache.Key
}
// We should not have "holes" in seq numbers,
// so a case when hrCache.SeqNo > seqNo shouldn't occur
if seqNo-hrCache.SeqNo > maxHashRatchetSeqNoDelta {
return nil, ErrHashRatchetSeqNoTooHigh
}
for i := hrCache.SeqNo; i < seqNo; i++ {
hash = crypto.Keccak256Hash(hash).Bytes()
err := s.persistence.SaveHashRatchetKeyHash(ratchet, hash, i+1)
if err != nil {
return nil, err
}
}
}
decryptedPayload, err := crypto.DecryptSymmetric(hash, payload)
if err != nil {
s.logger.Error("failed to decrypt hash", zap.Error(err))
return nil, err
}
return decryptedPayload, nil
}

View File

@@ -0,0 +1,164 @@
package encryption
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/rand"
"encoding/binary"
"errors"
"io"
"time"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/crypto/ecies"
)
const keyBumpValue = uint64(10)
// GetCurrentTime64 returns the current unix time in milliseconds
func GetCurrentTime() uint64 {
return (uint64)(time.Now().UnixNano() / int64(time.Millisecond))
}
// bumpKeyID takes a timestampID and returns its value incremented by the keyBumpValue
func bumpKeyID(timestampID uint64) uint64 {
return timestampID + keyBumpValue
}
func generateHashRatchetKeyID(groupID []byte, timestamp uint64, keyBytes []byte) []byte {
var keyMaterial []byte
keyMaterial = append(keyMaterial, groupID...)
timestampBytes := make([]byte, 8) // 8 bytes for a uint64
binary.LittleEndian.PutUint64(timestampBytes, timestamp)
keyMaterial = append(keyMaterial, timestampBytes...)
keyMaterial = append(keyMaterial, keyBytes...)
return crypto.Keccak256(keyMaterial)
}
func publicKeyMostRelevantBytes(key *ecdsa.PublicKey) uint32 {
keyBytes := crypto.FromECDSAPub(key)
return binary.LittleEndian.Uint32(keyBytes[1:5])
}
func encrypt(plaintext []byte, key []byte, reader io.Reader) ([]byte, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(reader, nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
func generateSharedKey(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey) ([]byte, error) {
const encryptedPayloadKeyLength = 16
return ecies.ImportECDSA(privateKey).GenerateShared(
ecies.ImportECDSAPublic(publicKey),
encryptedPayloadKeyLength,
encryptedPayloadKeyLength,
)
}
func buildGroupRekeyMessage(privateKey *ecdsa.PrivateKey, groupID []byte, timestamp uint64, keyMaterial []byte, keys []*ecdsa.PublicKey) (*RekeyGroup, error) {
message := &RekeyGroup{
Timestamp: timestamp,
}
message.Keys = make(map[uint32][]byte)
for _, k := range keys {
sharedKey, err := generateSharedKey(privateKey, k)
if err != nil {
return nil, err
}
encryptedKey, err := encrypt(keyMaterial, sharedKey, rand.Reader)
if err != nil {
return nil, err
}
kBytes := publicKeyMostRelevantBytes(k)
if message.Keys[kBytes] == nil {
message.Keys[kBytes] = encryptedKey
} else {
message.Keys[kBytes] = append(message.Keys[kBytes], encryptedKey...)
}
}
return message, nil
}
const nonceLength = 12
func decrypt(cyphertext []byte, key []byte) ([]byte, error) {
if len(cyphertext) < nonceLength {
return nil, errors.New("invalid cyphertext length")
}
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return nil, err
}
nonce := cyphertext[:nonceLength]
return gcm.Open(nil, nonce, cyphertext[nonceLength:], nil)
}
const keySize = 60
func decryptGroupRekeyMessage(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, message *RekeyGroup) ([]byte, error) {
kBytes := publicKeyMostRelevantBytes(&privateKey.PublicKey)
if message.Keys == nil || message.Keys[kBytes] == nil {
return nil, nil
}
sharedKey, err := generateSharedKey(privateKey, publicKey)
if err != nil {
return nil, err
}
keys := message.Keys[kBytes]
nKeys := len(keys) / keySize
var decryptedKey []byte
for i := 0; i < nKeys; i++ {
encryptedKey := keys[i*keySize : i*keySize+keySize]
decryptedKey, err = decrypt(encryptedKey, sharedKey)
if err != nil {
continue
} else {
break
}
}
return decryptedKey, nil
}

View File

@@ -0,0 +1,712 @@
// Code generated by go-bindata. DO NOT EDIT.
// sources:
// 1536754952_initial_schema.down.sql (83B)
// 1536754952_initial_schema.up.sql (962B)
// 1539249977_update_ratchet_info.down.sql (311B)
// 1539249977_update_ratchet_info.up.sql (368B)
// 1540715431_add_version.down.sql (127B)
// 1540715431_add_version.up.sql (265B)
// 1541164797_add_installations.down.sql (26B)
// 1541164797_add_installations.up.sql (216B)
// 1558084410_add_secret.down.sql (56B)
// 1558084410_add_secret.up.sql (301B)
// 1558588866_add_version.down.sql (47B)
// 1558588866_add_version.up.sql (57B)
// 1559627659_add_contact_code.down.sql (32B)
// 1559627659_add_contact_code.up.sql (198B)
// 1561368210_add_installation_metadata.down.sql (35B)
// 1561368210_add_installation_metadata.up.sql (267B)
// 1632236298_add_communities.down.sql (151B)
// 1632236298_add_communities.up.sql (584B)
// 1636536507_add_index_bundles.up.sql (347B)
// doc.go (377B)
package migrations
import (
"bytes"
"compress/gzip"
"crypto/sha256"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("read %q: %w", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("read %q: %w", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
digest [sha256.Size]byte
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var __1536754952_initial_schemaDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\x4e\x2d\x2e\xce\xcc\xcf\x2b\xb6\xe6\x42\x12\x4c\x2a\xcd\x4b\xc9\x49\x45\x15\xcb\x4e\xad\x44\x15\x28\x4a\x2c\x49\xce\x48\x2d\x89\xcf\xcc\x4b\xcb\xb7\xe6\x02\x04\x00\x00\xff\xff\x72\x61\x3f\x92\x53\x00\x00\x00")
func _1536754952_initial_schemaDownSqlBytes() ([]byte, error) {
return bindataRead(
__1536754952_initial_schemaDownSql,
"1536754952_initial_schema.down.sql",
)
}
func _1536754952_initial_schemaDownSql() (*asset, error) {
bytes, err := _1536754952_initial_schemaDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1536754952_initial_schema.down.sql", size: 83, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x44, 0xcf, 0x76, 0x71, 0x1f, 0x5e, 0x9a, 0x43, 0xd8, 0xcd, 0xb8, 0xc3, 0x70, 0xc3, 0x7f, 0xfc, 0x90, 0xb4, 0x25, 0x1e, 0xf4, 0x66, 0x20, 0xb8, 0x33, 0x7e, 0xb0, 0x76, 0x1f, 0xc, 0xc0, 0x75}}
return a, nil
}
var __1536754952_initial_schemaUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x52\xc1\x8e\x9b\x30\x10\xbd\xe7\x2b\xe6\x98\x48\x39\xf4\xde\x13\xb0\x13\x84\x4a\xcd\xd6\x0b\x52\xf7\x64\x79\xe3\x69\xb0\x16\x1b\x64\x3b\xab\xe6\xef\x2b\x20\xa5\xb8\x65\xdb\xde\x98\xc7\x9b\x99\x37\xef\x39\xe3\x98\xd4\x08\x75\x92\x96\x08\x9e\xbc\xd7\xbd\xf5\xb0\xdf\x01\xa8\xd6\x41\x5a\x56\xe9\x71\xfa\xf6\x62\xb8\xbe\x74\xfa\x1c\x43\x4e\xbf\xc9\x40\x0b\xe6\xfa\x3e\x88\x73\x2b\xb5\x15\xaf\x74\x5b\x60\x4f\x56\xfd\x1d\xb6\x50\xb0\x1a\x73\xe4\xd3\x14\x3a\xbf\x6d\xd0\x57\x70\x44\xf7\x81\x86\x75\x3d\x58\x58\x97\x5a\x4d\x13\x80\x55\x35\xb0\xa6\x2c\xe1\x91\x17\x9f\x13\xfe\x0c\x9f\xf0\x79\xfc\xdf\xb0\xe2\x4b\x83\x7b\xad\x0e\x50\x31\xc8\x2a\x76\x2a\x8b\xac\x06\x8e\x8f\x65\x92\xe1\xee\xf0\x71\xb7\x8b\x3c\x7a\xa5\xdb\xec\xcf\xec\xc7\x22\x71\x59\x30\x0e\x35\xfe\x22\xec\xd5\xac\x75\x18\xf2\x5e\x5e\x68\x9b\x3f\x8b\x80\xfd\xbd\xef\xb8\x66\xff\xa7\xae\x97\xab\x55\x1d\xcd\xd2\xb4\x22\x1b\x74\xd8\x58\xa4\xad\x0f\xb2\xeb\x64\xd0\xbd\x15\x5a\x41\x8d\x5f\xeb\x88\x70\x8f\x34\x0e\x4a\x5f\x2c\x29\x31\xb8\x0d\xf5\x6b\x3b\x23\xa1\x45\xce\x2a\x8e\x63\x7b\xd0\x86\x7c\x90\x66\x80\x86\x3d\x15\x39\xc3\x07\x48\x8b\x7c\xf4\x26\xda\x4c\xdf\x07\xed\x48\x41\x5a\x55\x25\x26\x0c\x1e\xf0\x94\x34\x65\x0d\x1f\xfe\xbc\xd5\xc9\x70\x6e\x29\x08\x6d\xbf\xf5\xd3\xc1\xf3\xf1\xe2\xf7\xac\xa7\xb1\x43\x4b\x86\x9c\xec\xa2\x93\xde\x77\xc8\xdf\x8c\xa1\xe0\xde\x4b\xf6\x9f\x06\xde\xdf\xd3\xa2\xe8\xb8\xec\xda\x0c\x72\x6c\x39\x55\x1c\x8b\x9c\x4d\x16\xfe\x6a\x3c\x00\xc7\x13\x72\x64\x19\x3e\xfd\x4c\x77\x1f\x47\x71\x18\xad\xf9\x11\x00\x00\xff\xff\xa9\x50\xa8\xb2\xc2\x03\x00\x00")
func _1536754952_initial_schemaUpSqlBytes() ([]byte, error) {
return bindataRead(
__1536754952_initial_schemaUpSql,
"1536754952_initial_schema.up.sql",
)
}
func _1536754952_initial_schemaUpSql() (*asset, error) {
bytes, err := _1536754952_initial_schemaUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1536754952_initial_schema.up.sql", size: 962, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xea, 0x90, 0x5a, 0x59, 0x3e, 0x3, 0xe2, 0x3c, 0x81, 0x42, 0xcd, 0x4c, 0x9a, 0xe8, 0xda, 0x93, 0x2b, 0x70, 0xa4, 0xd5, 0x29, 0x3e, 0xd5, 0xc9, 0x27, 0xb6, 0xb7, 0x65, 0xff, 0x0, 0xcb, 0xde}}
return a, nil
}
var __1539249977_update_ratchet_infoDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8f\x41\x4b\xc4\x30\x10\x85\xef\xf9\x15\xef\xd8\xc2\x9e\xbc\xee\xa9\x8d\x53\x29\x86\x64\x8d\x29\xe8\x29\xd4\xed\xe8\x06\xdb\xec\xd2\x46\xa1\xff\x5e\x22\x52\x59\xf4\x3a\xdf\xf7\x78\x6f\x6e\xad\x39\xc0\x55\xb5\x22\xcc\x7d\x3a\x9e\x38\xf9\x10\x5f\xcf\xfe\xf3\x66\x2f\x84\xb4\x54\x39\xfa\x07\xa3\x10\xc0\xcb\x47\x1c\x46\xf6\x61\x40\xad\x4c\x0d\x6d\x1c\x74\xa7\xd4\x4e\x00\x7c\x39\xf1\xc4\x73\x3f\xfa\x77\x5e\xbf\x71\xbe\x86\x81\x63\x0a\x69\xfd\xeb\x2f\xeb\x34\x71\x9a\xc3\x71\xf3\xaf\x70\x88\x4b\xea\xc7\xb1\x4f\xe1\x1c\x73\x9f\xa3\x27\x77\x25\x74\xba\x7d\xe8\xa8\xd8\x16\xed\xb6\xae\x12\x46\x43\x1a\xdd\xa8\x56\x3a\x58\x3a\xa8\x4a\x52\x8e\x34\xc6\x52\x7b\xa7\x71\x4f\xcf\xf8\x0d\x96\xb0\xd4\x90\x25\x2d\xe9\xf1\xe7\xc1\xa5\x58\xc2\x5b\xe4\xc1\x5f\x66\xce\xf3\x4a\x51\xee\xc5\x57\x00\x00\x00\xff\xff\x69\x51\x9b\xb4\x37\x01\x00\x00")
func _1539249977_update_ratchet_infoDownSqlBytes() ([]byte, error) {
return bindataRead(
__1539249977_update_ratchet_infoDownSql,
"1539249977_update_ratchet_info.down.sql",
)
}
func _1539249977_update_ratchet_infoDownSql() (*asset, error) {
bytes, err := _1539249977_update_ratchet_infoDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1539249977_update_ratchet_info.down.sql", size: 311, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1, 0xa4, 0xeb, 0xa0, 0xe6, 0xa0, 0xd4, 0x48, 0xbb, 0xad, 0x6f, 0x7d, 0x67, 0x8c, 0xbd, 0x25, 0xde, 0x1f, 0x73, 0x9a, 0xbb, 0xa8, 0xc9, 0x30, 0xb7, 0xa9, 0x7c, 0xaf, 0xb5, 0x1, 0x61, 0xdd}}
return a, nil
}
var __1539249977_update_ratchet_infoUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8f\x41\x4f\x84\x30\x10\x85\xef\xfd\x15\x73\x84\x84\x93\x57\x4e\x50\x07\x43\xac\xed\x5a\x4b\xa2\xa7\x06\x97\xd1\x6d\x16\xca\x86\x56\x13\xfe\xbd\xa9\x31\x28\xea\xf5\xbd\x6f\xde\x7b\x73\x8d\x02\x0d\x42\xa3\xd5\x1d\x04\x0a\xc1\xcd\x3e\x94\xec\xa7\x7a\xa6\x35\x29\x5a\x1d\xc0\x54\xb5\x40\x58\xfa\x78\x3c\x51\xb4\xce\xbf\xcc\x25\x63\x5c\x63\x65\xf0\x1f\xcf\xbe\x5f\x41\xc6\x00\x9e\xdf\xfc\x30\x92\x75\x03\xd4\x42\xd5\x20\x95\x01\xd9\x09\x51\x30\x00\xba\x9c\x68\xa2\xa5\x1f\xed\x99\xd6\x4f\x3b\xa9\x6e\x20\x1f\x5d\x5c\xff\xf2\x61\x9d\x26\x8a\x8b\x3b\x6e\xfc\xce\x76\x3e\xc4\x7e\x1c\xfb\xe8\x66\x9f\xfa\x0c\x3e\x9a\x1d\xd0\xc9\xf6\xbe\xc3\x6c\x5b\x54\x6c\x5d\xc5\xef\xe3\x1c\x94\x04\xae\x64\x23\x5a\x6e\x40\xe3\x41\x54\x1c\x53\x46\xa3\x34\xb6\x37\x12\x6e\xf1\x09\xbe\x93\x72\xd0\xd8\xa0\x46\xc9\xf1\xe1\xeb\xe3\x90\x05\xf7\xea\x69\xb0\x97\x85\xd2\xde\x9c\xe5\x25\xfb\x08\x00\x00\xff\xff\xb6\x31\x2b\x32\x70\x01\x00\x00")
func _1539249977_update_ratchet_infoUpSqlBytes() ([]byte, error) {
return bindataRead(
__1539249977_update_ratchet_infoUpSql,
"1539249977_update_ratchet_info.up.sql",
)
}
func _1539249977_update_ratchet_infoUpSql() (*asset, error) {
bytes, err := _1539249977_update_ratchet_infoUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1539249977_update_ratchet_info.up.sql", size: 368, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc, 0x8e, 0xbf, 0x6f, 0xa, 0xc0, 0xe1, 0x3c, 0x42, 0x28, 0x88, 0x1d, 0xdb, 0xba, 0x1c, 0x83, 0xec, 0xba, 0xd3, 0x5f, 0x5c, 0x77, 0x5e, 0xa7, 0x46, 0x36, 0xec, 0x69, 0xa, 0x4b, 0x17, 0x79}}
return a, nil
}
var __1540715431_add_versionDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xc8\x4e\xad\x2c\x56\x70\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\x28\x4e\x2d\x2e\xce\xcc\xcf\x8b\xcf\x4c\xb1\xe6\x42\x56\x08\x15\x47\x55\x0c\xd2\x1d\x9f\x9c\x5f\x9a\x57\x82\xaa\x38\xa9\x34\x2f\x25\x27\x15\x55\x6d\x59\x6a\x11\xc8\x00\x6b\x2e\x40\x00\x00\x00\xff\xff\xda\x5d\x80\x2d\x7f\x00\x00\x00")
func _1540715431_add_versionDownSqlBytes() ([]byte, error) {
return bindataRead(
__1540715431_add_versionDownSql,
"1540715431_add_version.down.sql",
)
}
func _1540715431_add_versionDownSql() (*asset, error) {
bytes, err := _1540715431_add_versionDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1540715431_add_version.down.sql", size: 127, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf5, 0x9, 0x4, 0xe3, 0x76, 0x2e, 0xb8, 0x9, 0x23, 0xf0, 0x70, 0x93, 0xc4, 0x50, 0xe, 0x9d, 0x84, 0x22, 0x8c, 0x94, 0xd3, 0x24, 0x9, 0x9a, 0xc1, 0xa1, 0x48, 0x45, 0xfd, 0x40, 0x6e, 0xe6}}
return a, nil
}
var __1540715431_add_versionUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\xcd\xb1\x0e\x02\x21\x0c\xc6\xf1\xdd\xa7\xf8\x1e\xc1\xdd\x09\xa4\x67\x4c\x7a\x90\x90\x32\x93\xe8\x31\x5c\x54\x2e\x8a\x98\xf8\xf6\x06\xe3\xc2\xa2\xae\x6d\xff\xbf\x1a\x62\x12\xc2\xe0\xdd\x88\x53\x7a\x96\xcd\x4a\xb1\x90\x87\x28\xcd\xf4\x9e\x40\x19\x83\xad\xe3\x30\x5a\x94\x74\x8d\xb9\x5e\xb0\xb7\x42\x3b\xf2\xb0\x4e\x60\x03\x33\x0c\x0d\x2a\xb0\x60\xfd\xab\x2f\x65\x5e\x72\x9c\x27\x68\x76\xba\x3f\xfe\x2c\xbb\xa0\x01\xf1\xb8\xd4\x7c\xff\xfb\xe7\xa1\xe6\xe9\x9c\x3a\xe5\x91\x6e\x4d\xfe\x4a\xbc\x02\x00\x00\xff\xff\x0e\x27\x2c\x52\x09\x01\x00\x00")
func _1540715431_add_versionUpSqlBytes() ([]byte, error) {
return bindataRead(
__1540715431_add_versionUpSql,
"1540715431_add_version.up.sql",
)
}
func _1540715431_add_versionUpSql() (*asset, error) {
bytes, err := _1540715431_add_versionUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1540715431_add_version.up.sql", size: 265, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc7, 0x4c, 0x36, 0x96, 0xdf, 0x16, 0x10, 0xa6, 0x27, 0x1a, 0x79, 0x8b, 0x42, 0x83, 0x23, 0xc, 0x7e, 0xb6, 0x3d, 0x2, 0xda, 0xa4, 0xb4, 0xd, 0x27, 0x55, 0xba, 0xdc, 0xb2, 0x88, 0x8f, 0xa6}}
return a, nil
}
var __1541164797_add_installationsDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xc8\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x2b\xb6\xe6\x02\x04\x00\x00\xff\xff\xd8\xbf\x14\x75\x1a\x00\x00\x00")
func _1541164797_add_installationsDownSqlBytes() ([]byte, error) {
return bindataRead(
__1541164797_add_installationsDownSql,
"1541164797_add_installations.down.sql",
)
}
func _1541164797_add_installationsDownSql() (*asset, error) {
bytes, err := _1541164797_add_installationsDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1541164797_add_installations.down.sql", size: 26, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf5, 0xfd, 0xe6, 0xd8, 0xca, 0x3b, 0x38, 0x18, 0xee, 0x0, 0x5f, 0x36, 0x9e, 0x1e, 0xd, 0x19, 0x3e, 0xb4, 0x73, 0x53, 0xe9, 0xa5, 0xac, 0xdd, 0xa1, 0x2f, 0xc7, 0x6c, 0xa8, 0xd9, 0xa, 0x88}}
return a, nil
}
var __1541164797_add_installationsUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\xce\xb1\x6a\xc3\x30\x14\x85\xe1\xdd\x4f\x71\x46\x1b\xbc\x74\xee\x24\xc9\xd7\x46\x70\xb9\x6a\x5d\x09\xba\x05\x05\x6b\x10\xd8\x4a\xc0\x5a\xf2\xf6\xc1\x43\x20\xce\xfc\x7f\x70\x8e\x99\x49\x79\x82\x57\x9a\x09\xb9\xec\x35\xae\x6b\xac\xf9\x56\x76\xa0\x6d\x80\xbc\xa4\x52\x73\x7d\x40\xb3\xd3\x10\xe7\x21\x81\xb9\x3f\xca\x1b\xbe\xe4\x05\x9e\xfe\xfd\x09\xd4\xbc\xa5\xbd\xc6\xed\x8e\x20\x7f\x76\x12\x1a\xa0\xed\x04\x2b\x67\x96\x4a\xbc\xae\x69\x81\x76\x8e\x49\x09\x06\x1a\x55\x60\x8f\xaf\x23\x06\xb1\xbf\x81\xda\xd7\x8b\xfe\x73\xb5\x83\x13\x18\x27\x23\x5b\xe3\x31\xd3\x0f\x2b\x43\x4d\xf7\xdd\x3c\x03\x00\x00\xff\xff\x28\x14\xac\x9d\xd8\x00\x00\x00")
func _1541164797_add_installationsUpSqlBytes() ([]byte, error) {
return bindataRead(
__1541164797_add_installationsUpSql,
"1541164797_add_installations.up.sql",
)
}
func _1541164797_add_installationsUpSql() (*asset, error) {
bytes, err := _1541164797_add_installationsUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1541164797_add_installations.up.sql", size: 216, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2d, 0x18, 0x26, 0xb8, 0x88, 0x47, 0xdb, 0x83, 0xcc, 0xb6, 0x9d, 0x1c, 0x1, 0xae, 0x2f, 0xde, 0x97, 0x82, 0x3, 0x30, 0xa8, 0x63, 0xa1, 0x78, 0x4b, 0xa5, 0x9, 0x8, 0x75, 0xa2, 0x57, 0x81}}
return a, nil
}
var __1558084410_add_secretDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\x4e\x4d\x2e\x4a\x2d\x89\xcf\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x8b\xcf\x4c\x29\xb6\xe6\xc2\x50\x53\x6c\xcd\x05\x08\x00\x00\xff\xff\xd3\xcd\x41\x83\x38\x00\x00\x00")
func _1558084410_add_secretDownSqlBytes() ([]byte, error) {
return bindataRead(
__1558084410_add_secretDownSql,
"1558084410_add_secret.down.sql",
)
}
func _1558084410_add_secretDownSql() (*asset, error) {
bytes, err := _1558084410_add_secretDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1558084410_add_secret.down.sql", size: 56, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x49, 0xb, 0x65, 0xdf, 0x59, 0xbf, 0xe9, 0x5, 0x5b, 0x6f, 0xd5, 0x3a, 0xb7, 0x57, 0xe8, 0x78, 0x38, 0x73, 0x53, 0x57, 0xf7, 0x24, 0x4, 0xe4, 0xa2, 0x49, 0x22, 0xa2, 0xc6, 0xfd, 0x80, 0xa4}}
return a, nil
}
var __1558084410_add_secretUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x50\xcf\x0a\x82\x30\x1c\xbe\xef\x29\xbe\xa3\x82\x6f\xd0\x49\xc7\x4f\x19\xad\xdf\x6a\x4d\xc8\x93\x48\xf3\x30\x10\x83\xdc\xa5\xb7\x0f\x23\x45\xa1\xce\xdf\xff\x4f\x5a\xca\x1d\xc1\xe5\x85\x26\x4c\xfd\xfd\xd9\xc7\x09\x89\x00\x82\xef\xc7\x18\xe2\x0b\x85\x36\x05\xd8\x38\x70\xad\x35\xce\x56\x9d\x72\xdb\xe0\x48\x0d\x0c\x43\x1a\x2e\xb5\x92\x0e\xaa\x62\x63\x29\x13\xf8\x9a\xec\x65\x22\x3d\x08\xf1\x23\xaa\x0d\xe3\x14\xbb\x61\xe8\x62\x78\x8c\x6d\xf0\x4b\x34\x1c\xdd\xdc\xaa\xce\x36\x75\xda\xe0\xf7\xd6\x33\x58\xb3\xba\xd4\x94\x04\x9f\x6d\x79\xe9\x9f\x82\xa5\xb1\xa4\x2a\xfe\x4c\x48\x76\x7c\x4b\x25\x59\x62\x49\xd7\xe5\x8a\x15\x4f\xe7\x09\xef\x00\x00\x00\xff\xff\xa6\xbb\x2c\x23\x2d\x01\x00\x00")
func _1558084410_add_secretUpSqlBytes() ([]byte, error) {
return bindataRead(
__1558084410_add_secretUpSql,
"1558084410_add_secret.up.sql",
)
}
func _1558084410_add_secretUpSql() (*asset, error) {
bytes, err := _1558084410_add_secretUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1558084410_add_secret.up.sql", size: 301, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf5, 0x32, 0x36, 0x8e, 0x47, 0xb0, 0x8f, 0xc1, 0xc6, 0xf7, 0xc6, 0x9f, 0x2d, 0x44, 0x75, 0x2b, 0x26, 0xec, 0x6, 0xa0, 0x7b, 0xa5, 0xbd, 0xc8, 0x76, 0x8a, 0x82, 0x68, 0x2, 0x42, 0xb5, 0xf4}}
return a, nil
}
var __1558588866_add_versionDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xc8\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x2b\x56\x70\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\x28\x4b\x2d\x2a\xce\xcc\xcf\xb3\xe6\x02\x04\x00\x00\xff\xff\xdf\x6b\x9f\xbb\x2f\x00\x00\x00")
func _1558588866_add_versionDownSqlBytes() ([]byte, error) {
return bindataRead(
__1558588866_add_versionDownSql,
"1558588866_add_version.down.sql",
)
}
func _1558588866_add_versionDownSql() (*asset, error) {
bytes, err := _1558588866_add_versionDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1558588866_add_version.down.sql", size: 47, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xde, 0x52, 0x34, 0x3c, 0x46, 0x4a, 0xf0, 0x72, 0x47, 0x6f, 0x49, 0x5c, 0xc7, 0xf9, 0x32, 0xce, 0xc4, 0x3d, 0xfd, 0x61, 0xa1, 0x8b, 0x8f, 0xf2, 0x31, 0x34, 0xde, 0x15, 0x49, 0xa6, 0xde, 0xb9}}
return a, nil
}
var __1558588866_add_versionUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xc8\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x2b\x56\x70\x74\x71\x51\x28\x4b\x2d\x2a\xce\xcc\xcf\x53\xf0\xf4\x0b\x71\x75\x77\x0d\x52\x70\x71\x75\x73\x0c\xf5\x09\x51\x30\xb0\xe6\x02\x04\x00\x00\xff\xff\x14\x7b\x07\xb5\x39\x00\x00\x00")
func _1558588866_add_versionUpSqlBytes() ([]byte, error) {
return bindataRead(
__1558588866_add_versionUpSql,
"1558588866_add_version.up.sql",
)
}
func _1558588866_add_versionUpSql() (*asset, error) {
bytes, err := _1558588866_add_versionUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1558588866_add_version.up.sql", size: 57, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2a, 0xea, 0x64, 0x39, 0x61, 0x20, 0x83, 0x83, 0xb, 0x2e, 0x79, 0x64, 0xb, 0x53, 0xfa, 0xfe, 0xc6, 0xf7, 0x67, 0x42, 0xd3, 0x4f, 0xdc, 0x7e, 0x30, 0x32, 0xe8, 0x14, 0x41, 0xe9, 0xe7, 0x3b}}
return a, nil
}
var __1559627659_add_contact_codeDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x48\xce\xcf\x2b\x49\x4c\x2e\x89\x4f\xce\x4f\x49\x8d\x4f\xce\xcf\x4b\xcb\x4c\xb7\xe6\x02\x04\x00\x00\xff\xff\x73\x7b\x50\x80\x20\x00\x00\x00")
func _1559627659_add_contact_codeDownSqlBytes() ([]byte, error) {
return bindataRead(
__1559627659_add_contact_codeDownSql,
"1559627659_add_contact_code.down.sql",
)
}
func _1559627659_add_contact_codeDownSql() (*asset, error) {
bytes, err := _1559627659_add_contact_codeDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1559627659_add_contact_code.down.sql", size: 32, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x5d, 0x64, 0x6d, 0xce, 0x24, 0x42, 0x20, 0x8d, 0x4f, 0x37, 0xaa, 0x9d, 0xc, 0x57, 0x98, 0xc1, 0xd1, 0x1a, 0x34, 0xcd, 0x9f, 0x8f, 0x34, 0x86, 0xb3, 0xd3, 0xdc, 0xf1, 0x7d, 0xe5, 0x1b, 0x6e}}
return a, nil
}
var __1559627659_add_contact_codeUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\xce\xc1\x8e\x82\x30\x18\x04\xe0\x7b\x9f\x62\x6e\x40\xb2\x07\xf6\xcc\xa9\xbb\xfb\xaf\x21\xd6\x62\x4a\x31\x72\x22\xb5\xa0\x34\x21\x45\xa1\xf8\xfc\x06\x13\xe3\xc5\xeb\xe4\x9b\xc9\xfc\x2a\xe2\x9a\xa0\xf9\x8f\x20\xd8\xd1\x07\x63\x43\x63\xc7\xb6\x6b\xec\xe8\xcf\xee\x82\x98\x01\x58\xbc\xbb\x2d\xcf\x68\x0e\x93\x71\x3e\xe0\x6e\x26\xdb\x9b\x29\xfe\x4e\x20\x0b\x0d\x59\x09\x81\xbd\xca\x77\x5c\xd5\xd8\x52\x8d\x3f\xfa\xe7\x95\xd0\x88\x8e\xd1\x17\x03\x06\x33\x87\xe6\xba\x9c\x06\x37\xf7\x5d\x8b\x5c\x6a\xda\x90\x7a\x57\x5f\x3c\x65\x49\xc6\x58\x2e\x4b\x52\x7a\x55\xc5\xc7\x4f\x07\x2e\x2a\x2a\x11\xaf\xe3\x48\x93\x8c\x3d\x02\x00\x00\xff\xff\xdc\x7c\x0c\xd3\xc6\x00\x00\x00")
func _1559627659_add_contact_codeUpSqlBytes() ([]byte, error) {
return bindataRead(
__1559627659_add_contact_codeUpSql,
"1559627659_add_contact_code.up.sql",
)
}
func _1559627659_add_contact_codeUpSql() (*asset, error) {
bytes, err := _1559627659_add_contact_codeUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1559627659_add_contact_code.up.sql", size: 198, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x16, 0xf6, 0xc2, 0x62, 0x9c, 0xd2, 0xc9, 0x1e, 0xd8, 0xea, 0xaa, 0xea, 0x95, 0x8f, 0x89, 0x6a, 0x85, 0x5d, 0x9d, 0x99, 0x78, 0x3c, 0x90, 0x66, 0x99, 0x3e, 0x4b, 0x19, 0x62, 0xfb, 0x31, 0x4d}}
return a, nil
}
var __1561368210_add_installation_metadataDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xc8\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x2b\x8e\xcf\x4d\x2d\x49\x4c\x49\x2c\x49\xb4\xe6\x02\x04\x00\x00\xff\xff\x03\x72\x7f\x08\x23\x00\x00\x00")
func _1561368210_add_installation_metadataDownSqlBytes() ([]byte, error) {
return bindataRead(
__1561368210_add_installation_metadataDownSql,
"1561368210_add_installation_metadata.down.sql",
)
}
func _1561368210_add_installation_metadataDownSql() (*asset, error) {
bytes, err := _1561368210_add_installation_metadataDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1561368210_add_installation_metadata.down.sql", size: 35, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa8, 0xde, 0x3f, 0xd2, 0x4a, 0x50, 0x98, 0x56, 0xe3, 0xc0, 0xcd, 0x9d, 0xb0, 0x34, 0x3b, 0xe5, 0x62, 0x18, 0xb5, 0x20, 0xc9, 0x3e, 0xdc, 0x6a, 0x40, 0x36, 0x66, 0xea, 0x51, 0x8c, 0x71, 0xf5}}
return a, nil
}
var __1561368210_add_installation_metadataUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\xce\xc1\x8a\x83\x30\x10\xc6\xf1\xbb\x4f\xf1\xdd\x54\xf0\x0d\xf6\x14\xb3\x23\x08\x21\xd9\x95\x04\x7a\x93\x60\x52\x08\xd5\x58\xe8\x50\xf0\xed\x8b\x87\x42\xed\xc1\xeb\xcc\xef\x83\xbf\x1c\x48\x58\x82\x15\xad\x22\xa4\xfc\x60\x3f\xcf\x9e\xd3\x9a\xc7\x25\xb2\x0f\x9e\x3d\x50\x15\x40\x0a\x31\x73\xe2\x0d\xad\x32\x2d\xb4\xb1\xd0\x4e\xa9\x66\xff\x7c\x8e\x52\x80\xa5\x8b\x3d\x80\xec\x97\x78\xbc\xe2\x97\x3a\xe1\x94\x45\x59\xee\x20\xc4\x67\x9a\xe2\xc8\xdb\xfd\xdc\x5d\xa7\x65\xe4\xf5\x16\xf3\xa9\x72\xba\xff\x77\x54\xbd\x83\x9b\xef\xc0\x1a\x46\x43\x1a\xdd\xa9\x5e\x5a\x0c\xf4\xa7\x84\xa4\xa2\xfe\x29\x5e\x01\x00\x00\xff\xff\x5d\x6f\xe6\xd3\x0b\x01\x00\x00")
func _1561368210_add_installation_metadataUpSqlBytes() ([]byte, error) {
return bindataRead(
__1561368210_add_installation_metadataUpSql,
"1561368210_add_installation_metadata.up.sql",
)
}
func _1561368210_add_installation_metadataUpSql() (*asset, error) {
bytes, err := _1561368210_add_installation_metadataUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1561368210_add_installation_metadata.up.sql", size: 267, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb4, 0x71, 0x8f, 0x29, 0xb1, 0xaa, 0xd6, 0xd1, 0x8c, 0x17, 0xef, 0x6c, 0xd5, 0x80, 0xb8, 0x2c, 0xc3, 0xfe, 0xec, 0x24, 0x4d, 0xc8, 0x25, 0xd3, 0xb4, 0xcd, 0xa9, 0xac, 0x63, 0x61, 0xb2, 0x9c}}
return a, nil
}
var __1632236298_add_communitiesDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\xf0\xf4\x73\x71\x8d\x50\xc8\x4c\xa9\x88\xcf\x48\x2c\xce\x88\x2f\x4a\x2c\x49\xce\x48\x2d\x89\x4f\xcd\x4b\xb6\xe6\x22\xa0\x20\x3e\x39\x31\x39\x23\xd5\x9a\x0b\xa2\x2e\xc4\xd1\xc9\xc7\x55\x01\x5d\x4d\x51\x65\x41\x49\x66\x7e\x1e\x4c\x29\x61\x95\xd6\x5c\x80\x00\x00\x00\xff\xff\xa4\x97\x4f\xad\x97\x00\x00\x00")
func _1632236298_add_communitiesDownSqlBytes() ([]byte, error) {
return bindataRead(
__1632236298_add_communitiesDownSql,
"1632236298_add_communities.down.sql",
)
}
func _1632236298_add_communitiesDownSql() (*asset, error) {
bytes, err := _1632236298_add_communitiesDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1632236298_add_communities.down.sql", size: 151, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x26, 0xe5, 0x47, 0xd1, 0xe5, 0xec, 0x5b, 0x3e, 0xdc, 0x22, 0xf4, 0x27, 0xee, 0x70, 0xf3, 0x9, 0x4f, 0xd2, 0x9f, 0x92, 0xf, 0x5a, 0x18, 0x11, 0xb7, 0x40, 0xab, 0xf1, 0x98, 0x72, 0xd6, 0x60}}
return a, nil
}
var __1632236298_add_communitiesUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x90\xdf\x4a\xc3\x30\x14\xc6\xef\xf3\x14\xe7\x72\x83\xbe\x81\x57\xed\x3c\x1b\xc1\x7a\xa2\x31\x05\x77\x15\x4a\x16\x4c\x10\xd3\xd9\x46\xb0\x6f\x2f\xc1\x0c\x71\x71\xd2\xdd\x7e\x9c\xf3\xfd\xf9\x6d\x24\xd6\x0a\x41\xd5\x4d\x8b\xe0\xfa\xc9\xe9\xb1\x8f\xc6\xd9\xa8\x6d\x30\xe3\x7c\x8c\x7e\x08\xb0\x62\x00\x2f\xe3\xf0\x71\xd4\xfe\x00\x4d\x2b\x1a\x20\xa1\x80\xba\xb6\xad\x18\xc0\xab\x9d\x93\xce\x49\x9d\xcb\xe5\xed\x83\xe4\xf7\xb5\xdc\xc3\x1d\xee\x57\x27\xc7\x2a\x3b\xac\xd9\xfa\x86\xb1\x5c\xa8\x23\xfe\xd8\x21\x70\xba\xc5\x67\xf0\x87\x4f\x7d\xde\x0d\x04\x5d\xea\x5b\x3a\xff\xf8\xfe\x3b\x54\x9b\xde\x38\xbb\x6c\xae\x0f\xf1\x97\x3c\xd9\x77\x1d\x86\x44\x01\x77\x28\x93\x92\x42\xca\xf7\x69\x7e\x4b\x89\xfa\x84\x27\x69\x5b\x21\x91\xef\xe8\x6f\x2a\x20\x71\x8b\x12\x69\x83\x4f\xcb\x07\x5f\x85\x32\xcf\xbe\x0c\xf4\xfb\xa0\x48\xa9\xf2\xe8\x94\xf5\x15\x00\x00\xff\xff\x61\x30\xb4\xa0\x48\x02\x00\x00")
func _1632236298_add_communitiesUpSqlBytes() ([]byte, error) {
return bindataRead(
__1632236298_add_communitiesUpSql,
"1632236298_add_communities.up.sql",
)
}
func _1632236298_add_communitiesUpSql() (*asset, error) {
bytes, err := _1632236298_add_communitiesUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1632236298_add_communities.up.sql", size: 584, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8f, 0xe0, 0x1, 0x6e, 0x84, 0xc, 0x35, 0xe4, 0x5a, 0xf, 0xbe, 0xcb, 0xf7, 0xd2, 0xa8, 0x25, 0xf5, 0xdb, 0x7, 0xcb, 0xa3, 0xe6, 0xf4, 0xc4, 0x1b, 0xa5, 0xec, 0x32, 0x1e, 0x1e, 0x48, 0x60}}
return a, nil
}
var __1636536507_add_index_bundlesUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x0e\x72\x75\x0c\x71\x55\xf0\xf4\x73\x71\x8d\x50\x48\xcd\x4b\x2e\xaa\x2c\x28\xc9\xcc\xcf\x8b\x4f\x2a\xcd\x4b\xc9\x49\x2d\x8e\x4f\xad\x28\xc8\x2c\x4a\x4d\x89\xcf\x4c\x49\xcd\x2b\xc9\x2c\xa9\x8c\xcf\xcc\x2b\x2e\x49\xcc\xc9\x49\x04\xab\xca\x4c\x89\x2f\x4b\x2d\x2a\xce\xcc\xcf\x53\xc8\xcf\x53\x80\xea\x51\xd0\x80\x6a\xd2\x51\x80\xe9\xd2\x51\x40\xd3\xa6\xa3\x00\xd5\xa7\x69\xcd\x45\xa9\x13\x50\xac\x26\xdd\xc6\xa2\xc4\x92\xe4\x8c\xd4\x92\xf8\xcc\xbc\xb4\xfc\xf8\x32\x23\xbc\xd6\xa0\xa9\xc5\x63\x9d\xa6\x35\x17\x20\x00\x00\xff\xff\xd4\xde\x07\x5c\x5b\x01\x00\x00")
func _1636536507_add_index_bundlesUpSqlBytes() ([]byte, error) {
return bindataRead(
__1636536507_add_index_bundlesUpSql,
"1636536507_add_index_bundles.up.sql",
)
}
func _1636536507_add_index_bundlesUpSql() (*asset, error) {
bytes, err := _1636536507_add_index_bundlesUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1636536507_add_index_bundles.up.sql", size: 347, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf1, 0xb9, 0x3c, 0x16, 0xfc, 0xfb, 0xb2, 0xb4, 0x3b, 0xfe, 0xdc, 0xf5, 0x9c, 0x42, 0xa0, 0xa0, 0xd4, 0xd, 0x5b, 0x97, 0x10, 0x80, 0x95, 0xe, 0x13, 0xc1, 0x18, 0x8, 0xee, 0xf, 0x99, 0xee}}
return a, nil
}
var _docGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x8f\xbb\x6e\xc3\x30\x0c\x45\x77\x7f\xc5\x45\x96\x2c\xb5\xb4\x74\xea\xd6\xb1\x7b\x7f\x80\x91\x68\x89\x88\x1e\xae\x48\xe7\xf1\xf7\x85\xd3\x02\xcd\xd6\xf5\x00\xe7\xf0\xd2\x7b\x7c\x66\x51\x2c\x52\x18\xa2\x68\x1c\x58\x95\xc6\x1d\x27\x0e\xb4\x29\xe3\x90\xc4\xf2\x76\x72\xa1\x57\xaf\x46\xb6\xe9\x2c\xd5\x57\x49\x83\x8c\xfd\xe5\xf5\x30\x79\x8f\x40\xed\x68\xc8\xd4\x62\xe1\x47\x4b\xa1\x46\xc3\xa4\x25\x5c\xc5\x32\x08\xeb\xe0\x45\x6e\x0e\xef\x86\xc2\xa4\x06\xcb\x64\x47\x85\x65\x46\x20\xe5\x3d\xb3\xf4\x81\xd4\xe7\x93\xb4\x48\x46\x6e\x47\x1f\xcb\x13\xd9\x17\x06\x2a\x85\x23\x96\xd1\xeb\xc3\x55\xaa\x8c\x28\x83\x83\xf5\x71\x7f\x01\xa9\xb2\xa1\x51\x65\xdd\xfd\x4c\x17\x46\xeb\xbf\xe7\x41\x2d\xfe\xff\x11\xae\x7d\x9c\x15\xa4\xe0\xdb\xca\xc1\x38\xba\x69\x5a\x29\x9c\x29\x31\xf4\xab\x88\xf1\x34\x79\x9f\xfa\x5b\xe2\xc6\xbb\xf5\xbc\x71\x5e\xcf\x09\x3f\x35\xe9\x4d\x31\x77\x38\xe7\xff\x80\x4b\x1d\x6e\xfa\x0e\x00\x00\xff\xff\x9d\x60\x3d\x88\x79\x01\x00\x00")
func docGoBytes() ([]byte, error) {
return bindataRead(
_docGo,
"doc.go",
)
}
func docGo() (*asset, error) {
bytes, err := docGoBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "doc.go", size: 377, mode: os.FileMode(0644), modTime: time.Unix(1704739012, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xef, 0xaf, 0xdf, 0xcf, 0x65, 0xae, 0x19, 0xfc, 0x9d, 0x29, 0xc1, 0x91, 0xaf, 0xb5, 0xd5, 0xb1, 0x56, 0xf3, 0xee, 0xa8, 0xba, 0x13, 0x65, 0xdb, 0xab, 0xcf, 0x4e, 0xac, 0x92, 0xe9, 0x60, 0xf1}}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// AssetString returns the asset contents as a string (instead of a []byte).
func AssetString(name string) (string, error) {
data, err := Asset(name)
return string(data), err
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// MustAssetString is like AssetString but panics when Asset would return an
// error. It simplifies safe initialization of global variables.
func MustAssetString(name string) string {
return string(MustAsset(name))
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetDigest returns the digest of the file with the given name. It returns an
// error if the asset could not be found or the digest could not be loaded.
func AssetDigest(name string) ([sha256.Size]byte, error) {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
}
return a.digest, nil
}
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
}
// Digests returns a map of all known files and their checksums.
func Digests() (map[string][sha256.Size]byte, error) {
mp := make(map[string][sha256.Size]byte, len(_bindata))
for name := range _bindata {
a, err := _bindata[name]()
if err != nil {
return nil, err
}
mp[name] = a.digest
}
return mp, nil
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"1536754952_initial_schema.down.sql": _1536754952_initial_schemaDownSql,
"1536754952_initial_schema.up.sql": _1536754952_initial_schemaUpSql,
"1539249977_update_ratchet_info.down.sql": _1539249977_update_ratchet_infoDownSql,
"1539249977_update_ratchet_info.up.sql": _1539249977_update_ratchet_infoUpSql,
"1540715431_add_version.down.sql": _1540715431_add_versionDownSql,
"1540715431_add_version.up.sql": _1540715431_add_versionUpSql,
"1541164797_add_installations.down.sql": _1541164797_add_installationsDownSql,
"1541164797_add_installations.up.sql": _1541164797_add_installationsUpSql,
"1558084410_add_secret.down.sql": _1558084410_add_secretDownSql,
"1558084410_add_secret.up.sql": _1558084410_add_secretUpSql,
"1558588866_add_version.down.sql": _1558588866_add_versionDownSql,
"1558588866_add_version.up.sql": _1558588866_add_versionUpSql,
"1559627659_add_contact_code.down.sql": _1559627659_add_contact_codeDownSql,
"1559627659_add_contact_code.up.sql": _1559627659_add_contact_codeUpSql,
"1561368210_add_installation_metadata.down.sql": _1561368210_add_installation_metadataDownSql,
"1561368210_add_installation_metadata.up.sql": _1561368210_add_installation_metadataUpSql,
"1632236298_add_communities.down.sql": _1632236298_add_communitiesDownSql,
"1632236298_add_communities.up.sql": _1632236298_add_communitiesUpSql,
"1636536507_add_index_bundles.up.sql": _1636536507_add_index_bundlesUpSql,
"doc.go": docGo,
}
// AssetDebug is true if the assets were built with the debug flag enabled.
const AssetDebug = false
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
//
// data/
// foo.txt
// img/
// a.png
// b.png
//
// then AssetDir("data") would return []string{"foo.txt", "img"},
// AssetDir("data/img") would return []string{"a.png", "b.png"},
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
canonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(canonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"1536754952_initial_schema.down.sql": {_1536754952_initial_schemaDownSql, map[string]*bintree{}},
"1536754952_initial_schema.up.sql": {_1536754952_initial_schemaUpSql, map[string]*bintree{}},
"1539249977_update_ratchet_info.down.sql": {_1539249977_update_ratchet_infoDownSql, map[string]*bintree{}},
"1539249977_update_ratchet_info.up.sql": {_1539249977_update_ratchet_infoUpSql, map[string]*bintree{}},
"1540715431_add_version.down.sql": {_1540715431_add_versionDownSql, map[string]*bintree{}},
"1540715431_add_version.up.sql": {_1540715431_add_versionUpSql, map[string]*bintree{}},
"1541164797_add_installations.down.sql": {_1541164797_add_installationsDownSql, map[string]*bintree{}},
"1541164797_add_installations.up.sql": {_1541164797_add_installationsUpSql, map[string]*bintree{}},
"1558084410_add_secret.down.sql": {_1558084410_add_secretDownSql, map[string]*bintree{}},
"1558084410_add_secret.up.sql": {_1558084410_add_secretUpSql, map[string]*bintree{}},
"1558588866_add_version.down.sql": {_1558588866_add_versionDownSql, map[string]*bintree{}},
"1558588866_add_version.up.sql": {_1558588866_add_versionUpSql, map[string]*bintree{}},
"1559627659_add_contact_code.down.sql": {_1559627659_add_contact_codeDownSql, map[string]*bintree{}},
"1559627659_add_contact_code.up.sql": {_1559627659_add_contact_codeUpSql, map[string]*bintree{}},
"1561368210_add_installation_metadata.down.sql": {_1561368210_add_installation_metadataDownSql, map[string]*bintree{}},
"1561368210_add_installation_metadata.up.sql": {_1561368210_add_installation_metadataUpSql, map[string]*bintree{}},
"1632236298_add_communities.down.sql": {_1632236298_add_communitiesDownSql, map[string]*bintree{}},
"1632236298_add_communities.up.sql": {_1632236298_add_communitiesUpSql, map[string]*bintree{}},
"1636536507_add_index_bundles.up.sql": {_1636536507_add_index_bundlesUpSql, map[string]*bintree{}},
"doc.go": {docGo, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory.
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = os.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
}
// RestoreAssets restores an asset under the given directory recursively.
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
canonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
}

View File

@@ -0,0 +1,125 @@
package multidevice
import (
"crypto/ecdsa"
"database/sql"
"github.com/status-im/status-go/eth-node/crypto"
)
type InstallationMetadata struct {
// The name of the device
Name string `json:"name"`
// The type of device
DeviceType string `json:"deviceType"`
// The FCMToken for mobile devices
FCMToken string `json:"fcmToken"`
}
type Installation struct {
// Identity is the string identity of the owner
Identity string `json:"identity"`
// The installation-id of the device
ID string `json:"id"`
// The last known protocol version of the device
Version uint32 `json:"version"`
// Enabled is whether the installation is enabled
Enabled bool `json:"enabled"`
// Timestamp is the last time we saw this device
Timestamp int64 `json:"timestamp"`
// InstallationMetadata
InstallationMetadata *InstallationMetadata `json:"metadata"`
}
type Config struct {
MaxInstallations int
ProtocolVersion uint32
InstallationID string
}
type Multidevice struct {
persistence *sqlitePersistence
config *Config
}
func New(db *sql.DB, config *Config) *Multidevice {
return &Multidevice{
config: config,
persistence: newSQLitePersistence(db),
}
}
func (s *Multidevice) InstallationID() string {
return s.config.InstallationID
}
func (s *Multidevice) GetActiveInstallations(identity *ecdsa.PublicKey) ([]*Installation, error) {
identityC := crypto.CompressPubkey(identity)
return s.persistence.GetActiveInstallations(s.config.MaxInstallations, identityC)
}
func (s *Multidevice) GetOurActiveInstallations(identity *ecdsa.PublicKey) ([]*Installation, error) {
identityC := crypto.CompressPubkey(identity)
installations, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations-1, identityC)
if err != nil {
return nil, err
}
installations = append(installations, &Installation{
ID: s.config.InstallationID,
Version: s.config.ProtocolVersion,
})
return installations, nil
}
func (s *Multidevice) GetOurInstallations(identity *ecdsa.PublicKey) ([]*Installation, error) {
var found bool
identityC := crypto.CompressPubkey(identity)
installations, err := s.persistence.GetInstallations(identityC)
if err != nil {
return nil, err
}
for _, installation := range installations {
if installation.ID == s.config.InstallationID {
found = true
installation.Enabled = true
installation.Version = s.config.ProtocolVersion
}
}
if !found {
installations = append(installations, &Installation{
ID: s.config.InstallationID,
Enabled: true,
Version: s.config.ProtocolVersion,
})
}
return installations, nil
}
func (s *Multidevice) AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) ([]*Installation, error) {
return s.persistence.AddInstallations(identity, timestamp, installations, defaultEnabled)
}
func (s *Multidevice) SetInstallationMetadata(identity *ecdsa.PublicKey, installationID string, metadata *InstallationMetadata) error {
identityC := crypto.CompressPubkey(identity)
return s.persistence.SetInstallationMetadata(identityC, installationID, metadata)
}
func (s *Multidevice) SetInstallationName(identity *ecdsa.PublicKey, installationID string, name string) error {
identityC := crypto.CompressPubkey(identity)
return s.persistence.SetInstallationName(identityC, installationID, name)
}
func (s *Multidevice) EnableInstallation(identity *ecdsa.PublicKey, installationID string) error {
identityC := crypto.CompressPubkey(identity)
return s.persistence.EnableInstallation(identityC, installationID)
}
func (s *Multidevice) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
myIdentityKeyC := crypto.CompressPubkey(myIdentityKey)
return s.persistence.DisableInstallation(myIdentityKeyC, installationID)
}

View File

@@ -0,0 +1,278 @@
package multidevice
import "database/sql"
type sqlitePersistence struct {
db *sql.DB
}
func newSQLitePersistence(db *sql.DB) *sqlitePersistence {
return &sqlitePersistence{db: db}
}
// GetActiveInstallations returns the active installations for a given identity
func (s *sqlitePersistence) GetActiveInstallations(maxInstallations int, identity []byte) ([]*Installation, error) {
stmt, err := s.db.Prepare(`SELECT installation_id, version
FROM installations
WHERE enabled = 1 AND identity = ?
ORDER BY timestamp DESC
LIMIT ?`)
if err != nil {
return nil, err
}
var installations []*Installation
rows, err := stmt.Query(identity, maxInstallations)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var (
installationID string
version uint32
)
err = rows.Scan(
&installationID,
&version,
)
if err != nil {
return nil, err
}
installations = append(installations, &Installation{
ID: installationID,
Version: version,
Enabled: true,
})
}
return installations, nil
}
// GetInstallations returns all the installations for a given identity
// we both return the installations & the metadata
// metadata is currently stored in a separate table, as in some cases we
// might have metadata for a device, but no other information on the device
func (s *sqlitePersistence) GetInstallations(identity []byte) ([]*Installation, error) {
installationMap := make(map[string]*Installation)
var installations []*Installation
// We query both tables as sqlite does not support full outer joins
installationsStmt, err := s.db.Prepare(`SELECT installation_id, version, enabled, timestamp FROM installations WHERE identity = ?`)
if err != nil {
return nil, err
}
defer installationsStmt.Close()
installationRows, err := installationsStmt.Query(identity)
if err != nil {
return nil, err
}
for installationRows.Next() {
var installation Installation
err = installationRows.Scan(
&installation.ID,
&installation.Version,
&installation.Enabled,
&installation.Timestamp,
)
if err != nil {
return nil, err
}
// We initialized to empty in this case as we want to
// return metadata as well in this endpoint, but not in others
installation.InstallationMetadata = &InstallationMetadata{}
installationMap[installation.ID] = &installation
}
metadataStmt, err := s.db.Prepare(`SELECT installation_id, name, device_type, fcm_token FROM installation_metadata WHERE identity = ?`)
if err != nil {
return nil, err
}
defer metadataStmt.Close()
metadataRows, err := metadataStmt.Query(identity)
if err != nil {
return nil, err
}
for metadataRows.Next() {
var (
installationID string
name sql.NullString
deviceType sql.NullString
fcmToken sql.NullString
installation *Installation
)
err = metadataRows.Scan(
&installationID,
&name,
&deviceType,
&fcmToken,
)
if err != nil {
return nil, err
}
if _, ok := installationMap[installationID]; ok {
installation = installationMap[installationID]
} else {
installation = &Installation{ID: installationID}
}
installation.InstallationMetadata = &InstallationMetadata{
Name: name.String,
DeviceType: deviceType.String,
FCMToken: fcmToken.String,
}
installationMap[installationID] = installation
}
for _, installation := range installationMap {
installations = append(installations, installation)
}
return installations, nil
}
// AddInstallations adds the installations for a given identity, maintaining the enabled flag
func (s *sqlitePersistence) AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) ([]*Installation, error) {
tx, err := s.db.Begin()
if err != nil {
return nil, err
}
var insertedInstallations []*Installation
for _, installation := range installations {
stmt, err := tx.Prepare(`SELECT enabled, version
FROM installations
WHERE identity = ? AND installation_id = ?
LIMIT 1`)
if err != nil {
return nil, err
}
defer stmt.Close()
var oldEnabled bool
// We don't override version once we saw one
var oldVersion uint32
latestVersion := installation.Version
err = stmt.QueryRow(identity, installation.ID).Scan(&oldEnabled, &oldVersion)
if err != nil && err != sql.ErrNoRows {
return nil, err
}
if err == sql.ErrNoRows {
stmt, err = tx.Prepare(`INSERT INTO installations(identity, installation_id, timestamp, enabled, version)
VALUES (?, ?, ?, ?, ?)`)
if err != nil {
return nil, err
}
defer stmt.Close()
_, err = stmt.Exec(
identity,
installation.ID,
timestamp,
defaultEnabled,
latestVersion,
)
if err != nil {
return nil, err
}
insertedInstallations = append(insertedInstallations, installation)
} else {
// We update timestamp if present without changing enabled, only if this is a new bundle
// and we set the version to the latest we ever saw
if oldVersion > installation.Version {
latestVersion = oldVersion
}
stmt, err = tx.Prepare(`UPDATE installations
SET timestamp = ?, enabled = ?, version = ?
WHERE identity = ?
AND installation_id = ?
AND timestamp < ?`)
if err != nil {
return nil, err
}
defer stmt.Close()
_, err = stmt.Exec(
timestamp,
oldEnabled,
latestVersion,
identity,
installation.ID,
timestamp,
)
if err != nil {
return nil, err
}
}
}
if err := tx.Commit(); err != nil {
_ = tx.Rollback()
return nil, err
}
return insertedInstallations, nil
}
// EnableInstallation enables the installation
func (s *sqlitePersistence) EnableInstallation(identity []byte, installationID string) error {
stmt, err := s.db.Prepare(`UPDATE installations
SET enabled = 1
WHERE identity = ? AND installation_id = ?`)
if err != nil {
return err
}
_, err = stmt.Exec(identity, installationID)
return err
}
// DisableInstallation disable the installation
func (s *sqlitePersistence) DisableInstallation(identity []byte, installationID string) error {
stmt, err := s.db.Prepare(`UPDATE installations
SET enabled = 0
WHERE identity = ? AND installation_id = ?`)
if err != nil {
return err
}
_, err = stmt.Exec(identity, installationID)
return err
}
// SetInstallationMetadata sets the metadata for a given installation
func (s *sqlitePersistence) SetInstallationMetadata(identity []byte, installationID string, metadata *InstallationMetadata) error {
stmt, err := s.db.Prepare(`INSERT INTO installation_metadata(name, device_type, fcm_token, identity, installation_id) VALUES(?,?,?,?,?)`)
if err != nil {
return err
}
_, err = stmt.Exec(metadata.Name, metadata.DeviceType, metadata.FCMToken, identity, installationID)
return err
}
// SetInstallationName sets the only the name in metadata for a given installation
func (s *sqlitePersistence) SetInstallationName(identity []byte, installationID string, name string) error {
stmt, err := s.db.Prepare(`UPDATE installation_metadata
SET name = ?
WHERE identity = ? AND installation_id = ?`)
if err != nil {
return err
}
_, err = stmt.Exec(name, identity, installationID)
return err
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,824 @@
package encryption
import (
"bytes"
"crypto/ecdsa"
"database/sql"
"fmt"
"go.uber.org/zap"
"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/encryption/publisher"
"github.com/status-im/status-go/protocol/encryption/sharedsecret"
)
//go:generate protoc --go_out=. ./protocol_message.proto
const (
protocolVersion = 1
sharedSecretNegotiationVersion = 1
partitionedTopicMinVersion = 1
defaultMinVersion = 0
)
type PartitionTopicMode int
const (
PartitionTopicNoSupport PartitionTopicMode = iota
PartitionTopicV1
)
type ProtocolMessageSpec struct {
Message *ProtocolMessage
// Installations is the targeted devices
Installations []*multidevice.Installation
// SharedSecret is a shared secret established among the installations
SharedSecret *sharedsecret.Secret
// AgreedSecret indicates whether the shared secret has been agreed
AgreedSecret bool
// Public means that the spec contains a public wrapped message
Public bool
}
func (p *ProtocolMessageSpec) MinVersion() uint32 {
if len(p.Installations) == 0 {
return defaultMinVersion
}
version := p.Installations[0].Version
for _, installation := range p.Installations[1:] {
if installation.Version < version {
version = installation.Version
}
}
return version
}
func (p *ProtocolMessageSpec) PartitionedTopicMode() PartitionTopicMode {
if p.MinVersion() >= partitionedTopicMinVersion {
return PartitionTopicV1
}
return PartitionTopicNoSupport
}
type Protocol struct {
encryptor *encryptor
secret *sharedsecret.SharedSecret
multidevice *multidevice.Multidevice
publisher *publisher.Publisher
subscriptions *Subscriptions
logger *zap.Logger
}
var (
// ErrNoPayload means that there was no payload found in the received protocol message.
ErrNoPayload = errors.New("no payload")
ErrNoRatchetKey = errors.New("no ratchet key for given keyID")
)
// New creates a new ProtocolService instance
func New(
db *sql.DB,
installationID string,
logger *zap.Logger,
) *Protocol {
return NewWithEncryptorConfig(
db,
installationID,
defaultEncryptorConfig(installationID, logger),
logger,
)
}
// DB and migrations are shared between encryption package
// and its sub-packages.
func NewWithEncryptorConfig(
db *sql.DB,
installationID string,
encryptorConfig encryptorConfig,
logger *zap.Logger,
) *Protocol {
return &Protocol{
encryptor: newEncryptor(db, encryptorConfig),
secret: sharedsecret.New(db, logger),
multidevice: multidevice.New(db, &multidevice.Config{
MaxInstallations: 3,
ProtocolVersion: protocolVersion,
InstallationID: installationID,
}),
publisher: publisher.New(logger),
logger: logger.With(zap.Namespace("Protocol")),
}
}
type Subscriptions struct {
SharedSecrets []*sharedsecret.Secret
SendContactCode <-chan struct{}
Quit chan struct{}
}
func (p *Protocol) Start(myIdentity *ecdsa.PrivateKey) (*Subscriptions, error) {
// Propagate currently cached shared secrets.
secrets, err := p.secret.All()
if err != nil {
return nil, errors.Wrap(err, "failed to get all secrets")
}
p.subscriptions = &Subscriptions{
SharedSecrets: secrets,
SendContactCode: p.publisher.Start(),
Quit: make(chan struct{}),
}
return p.subscriptions, nil
}
func (p *Protocol) Stop() error {
p.publisher.Stop()
if p.subscriptions != nil {
close(p.subscriptions.Quit)
}
return nil
}
func (p *Protocol) addBundle(myIdentityKey *ecdsa.PrivateKey, msg *ProtocolMessage) error {
// Get a bundle
installations, err := p.multidevice.GetOurActiveInstallations(&myIdentityKey.PublicKey)
if err != nil {
return err
}
bundle, err := p.encryptor.CreateBundle(myIdentityKey, installations)
if err != nil {
return err
}
msg.Bundles = []*Bundle{bundle}
return nil
}
// BuildPublicMessage marshals a public chat message given the user identity private key and a payload
func (p *Protocol) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte) (*ProtocolMessageSpec, error) {
// Build message not encrypted
message := &ProtocolMessage{
InstallationId: p.encryptor.config.InstallationID,
PublicMessage: payload,
}
err := p.addBundle(myIdentityKey, message)
if err != nil {
return nil, err
}
return &ProtocolMessageSpec{Message: message, Public: true}, nil
}
// BuildEncryptedMessage returns a 1:1 chat message and optionally a negotiated topic given the user identity private key, the recipient's public key, and a payload
func (p *Protocol) BuildEncryptedMessage(myIdentityKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, payload []byte) (*ProtocolMessageSpec, error) {
// Get recipients installations.
activeInstallations, err := p.multidevice.GetActiveInstallations(publicKey)
if err != nil {
return nil, err
}
// Encrypt payload
encryptedMessagesByInstalls, installations, err := p.encryptor.EncryptPayload(publicKey, myIdentityKey, activeInstallations, payload)
if err != nil {
return nil, err
}
// Build message
message := &ProtocolMessage{
InstallationId: p.encryptor.config.InstallationID,
EncryptedMessage: encryptedMessagesByInstalls,
}
err = p.addBundle(myIdentityKey, message)
if err != nil {
return nil, err
}
// Check who we are sending the message to, and see if we have a shared secret
// across devices
var installationIDs []string
for installationID := range message.GetEncryptedMessage() {
if installationID != noInstallationID {
installationIDs = append(installationIDs, installationID)
}
}
sharedSecret, agreed, err := p.secret.Agreed(myIdentityKey, p.encryptor.config.InstallationID, publicKey, installationIDs)
if err != nil {
return nil, err
}
spec := &ProtocolMessageSpec{
SharedSecret: sharedSecret,
AgreedSecret: agreed,
Message: message,
Installations: installations,
}
return spec, nil
}
func (p *Protocol) GenerateHashRatchetKey(groupID []byte) (*HashRatchetKeyCompatibility, error) {
return p.encryptor.GenerateHashRatchetKey(groupID)
}
func (p *Protocol) GetAllHREncodedKeys(groupID []byte) ([]byte, error) {
keys, err := p.encryptor.persistence.GetKeysForGroup(groupID)
if err != nil {
return nil, err
}
if len(keys) == 0 {
return nil, nil
}
return p.GetMarshaledHREncodedKeys(groupID, keys)
}
// GetKeyIDsForGroup returns a slice of key IDs belonging to a given group ID
func (p *Protocol) GetKeysForGroup(groupID []byte) ([]*HashRatchetKeyCompatibility, error) {
return p.encryptor.persistence.GetKeysForGroup(groupID)
}
func (p *Protocol) GetHREncodedKeys(groupID []byte, ratchets []*HashRatchetKeyCompatibility) *HRKeys {
keys := &HRKeys{}
for _, ratchet := range ratchets {
key := &HRKey{
DeprecatedKeyId: ratchet.DeprecatedKeyID(),
Key: ratchet.Key,
Timestamp: ratchet.Timestamp,
}
keys.Keys = append(keys.Keys, key)
}
return keys
}
func (p *Protocol) GetMarshaledHREncodedKeys(groupID []byte, ratchets []*HashRatchetKeyCompatibility) ([]byte, error) {
keys := p.GetHREncodedKeys(groupID, ratchets)
return proto.Marshal(keys)
}
// BuildHashRatchetRekeyGroup builds a public message
// with the new key
func (p *Protocol) BuildHashRatchetReKeyGroupMessage(myIdentityKey *ecdsa.PrivateKey, recipients []*ecdsa.PublicKey, groupID []byte, payload []byte, ratchet *HashRatchetKeyCompatibility) (*ProtocolMessageSpec, error) {
var err error
if ratchet == nil {
ratchet, err = p.GenerateHashRatchetKey(groupID)
if err != nil {
return nil, err
}
}
message, err := buildGroupRekeyMessage(myIdentityKey, groupID, ratchet.Timestamp, ratchet.Key, recipients)
if err != nil {
return nil, err
}
keys := &HRKeys{
RekeyGroup: message,
}
spec := &ProtocolMessageSpec{
Public: true,
Message: &ProtocolMessage{
InstallationId: p.encryptor.config.InstallationID,
EncryptedMessage: map[string]*EncryptedMessageProtocol{noInstallationID: &EncryptedMessageProtocol{
HRHeader: &HRHeader{
SeqNo: 0,
GroupId: groupID,
Keys: keys,
},
Payload: payload,
},
},
},
}
return spec, nil
}
// BuildHashRatchetKeyExchangeMessage builds a 1:1 message
// containing newly generated hash ratchet key
func (p *Protocol) BuildHashRatchetKeyExchangeMessage(myIdentityKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, groupID []byte, ratchets []*HashRatchetKeyCompatibility) (*ProtocolMessageSpec, error) {
keys := p.GetHREncodedKeys(groupID, ratchets)
encodedKeys, err := proto.Marshal(keys)
if err != nil {
return nil, err
}
response, err := p.BuildEncryptedMessage(myIdentityKey, publicKey, encodedKeys)
if err != nil {
return nil, err
}
// Loop through installations and assign HRHeader
// SeqNo=0 has a special meaning for HandleMessage
// and signifies a message with hash ratchet key payload
for _, v := range response.Message.EncryptedMessage {
v.HRHeader = &HRHeader{
SeqNo: 0,
GroupId: groupID,
Keys: keys,
}
}
return response, err
}
func (p *Protocol) BuildHashRatchetKeyExchangeMessageWithPayload(myIdentityKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, groupID []byte, ratchets []*HashRatchetKeyCompatibility, payload []byte) (*ProtocolMessageSpec, error) {
keys := p.GetHREncodedKeys(groupID, ratchets)
response, err := p.BuildEncryptedMessage(myIdentityKey, publicKey, payload)
if err != nil {
return nil, err
}
// Loop through installations and assign HRHeader
// SeqNo=0 has a special meaning for HandleMessage
// and signifies a message with hash ratchet key payload
for _, v := range response.Message.EncryptedMessage {
v.HRHeader = &HRHeader{
SeqNo: 0,
GroupId: groupID,
Keys: keys,
}
}
return response, err
}
func (p *Protocol) GetCurrentKeyForGroup(groupID []byte) (*HashRatchetKeyCompatibility, error) {
return p.encryptor.persistence.GetCurrentKeyForGroup(groupID)
}
// BuildHashRatchetMessage returns a hash ratchet chat message
func (p *Protocol) BuildHashRatchetMessage(groupID []byte, payload []byte) (*ProtocolMessageSpec, error) {
ratchet, err := p.encryptor.persistence.GetCurrentKeyForGroup(groupID)
if err != nil {
return nil, err
}
// Encrypt payload
encryptedMessagesByInstalls, err := p.encryptor.EncryptHashRatchetPayload(ratchet, payload)
if err != nil {
return nil, err
}
// Build message
message := &ProtocolMessage{
InstallationId: p.encryptor.config.InstallationID,
EncryptedMessage: encryptedMessagesByInstalls,
}
spec := &ProtocolMessageSpec{
Message: message,
}
return spec, nil
}
func (p *Protocol) GetKeyExMessageSpecs(groupID []byte, identity *ecdsa.PrivateKey, recipients []*ecdsa.PublicKey, forceRekey bool) ([]*ProtocolMessageSpec, error) {
var ratchets []*HashRatchetKeyCompatibility
var err error
if !forceRekey {
ratchets, err = p.encryptor.persistence.GetKeysForGroup(groupID)
if err != nil {
return nil, err
}
}
if len(ratchets) == 0 || forceRekey {
ratchet, err := p.GenerateHashRatchetKey(groupID)
if err != nil {
return nil, err
}
ratchets = []*HashRatchetKeyCompatibility{ratchet}
}
specs := make([]*ProtocolMessageSpec, len(recipients))
for i, recipient := range recipients {
keyExMsg, err := p.BuildHashRatchetKeyExchangeMessage(identity, recipient, groupID, ratchets)
if err != nil {
return nil, err
}
specs[i] = keyExMsg
}
return specs, nil
}
// BuildDHMessage builds a message with DH encryption so that it can be decrypted by any other device.
func (p *Protocol) BuildDHMessage(myIdentityKey *ecdsa.PrivateKey, destination *ecdsa.PublicKey, payload []byte) (*ProtocolMessageSpec, error) {
// Encrypt payload
encryptionResponse, err := p.encryptor.EncryptPayloadWithDH(destination, payload)
if err != nil {
return nil, err
}
// Build message
message := &ProtocolMessage{
InstallationId: p.encryptor.config.InstallationID,
EncryptedMessage: encryptionResponse,
}
err = p.addBundle(myIdentityKey, message)
if err != nil {
return nil, err
}
return &ProtocolMessageSpec{Message: message}, nil
}
// ProcessPublicBundle processes a received X3DH bundle.
func (p *Protocol) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *Bundle) ([]*multidevice.Installation, error) {
logger := p.logger.With(zap.String("site", "ProcessPublicBundle"))
if err := p.encryptor.ProcessPublicBundle(myIdentityKey, bundle); err != nil {
return nil, err
}
installations, enabled, err := p.recoverInstallationsFromBundle(myIdentityKey, bundle)
if err != nil {
return nil, err
}
// TODO(adam): why do we add installations using identity obtained from GetIdentity()
// instead of the output of crypto.CompressPubkey()? I tried the second option
// and the unit tests TestTopic and TestMaxDevices fail.
identityFromBundle := bundle.GetIdentity()
theirIdentity, err := ExtractIdentity(bundle)
if err != nil {
logger.Panic("unrecoverable error extracting identity", zap.Error(err))
}
compressedIdentity := crypto.CompressPubkey(theirIdentity)
if !bytes.Equal(identityFromBundle, compressedIdentity) {
logger.Panic("identity from bundle and compressed are not equal")
}
return p.multidevice.AddInstallations(bundle.GetIdentity(), bundle.GetTimestamp(), installations, enabled)
}
func (p *Protocol) GetMultiDevice() *multidevice.Multidevice {
return p.multidevice
}
// recoverInstallationsFromBundle extracts installations from the bundle.
// It returns extracted installations and true if the installations
// are ours, i.e. the bundle was created by our identity key.
func (p *Protocol) recoverInstallationsFromBundle(myIdentityKey *ecdsa.PrivateKey, bundle *Bundle) ([]*multidevice.Installation, bool, error) {
var installations []*multidevice.Installation
theirIdentity, err := ExtractIdentity(bundle)
if err != nil {
return nil, false, err
}
myIdentityStr := fmt.Sprintf("0x%x", crypto.FromECDSAPub(&myIdentityKey.PublicKey))
theirIdentityStr := fmt.Sprintf("0x%x", crypto.FromECDSAPub(theirIdentity))
// Any device from other peers will be considered enabled, ours needs to
// be explicitly enabled.
enabled := theirIdentityStr != myIdentityStr
signedPreKeys := bundle.GetSignedPreKeys()
for installationID, signedPreKey := range signedPreKeys {
if installationID != p.multidevice.InstallationID() {
installations = append(installations, &multidevice.Installation{
Identity: theirIdentityStr,
ID: installationID,
Version: signedPreKey.GetProtocolVersion(),
})
}
}
return installations, enabled, nil
}
// GetBundle retrieves or creates a X3DH bundle, given a private identity key.
func (p *Protocol) GetBundle(myIdentityKey *ecdsa.PrivateKey) (*Bundle, error) {
installations, err := p.multidevice.GetOurActiveInstallations(&myIdentityKey.PublicKey)
if err != nil {
return nil, err
}
return p.encryptor.CreateBundle(myIdentityKey, installations)
}
// EnableInstallation enables an installation for multi-device sync.
func (p *Protocol) EnableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
return p.multidevice.EnableInstallation(myIdentityKey, installationID)
}
// DisableInstallation disables an installation for multi-device sync.
func (p *Protocol) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
return p.multidevice.DisableInstallation(myIdentityKey, installationID)
}
// GetOurInstallations returns all the installations available given an identity
func (p *Protocol) GetOurInstallations(myIdentityKey *ecdsa.PublicKey) ([]*multidevice.Installation, error) {
return p.multidevice.GetOurInstallations(myIdentityKey)
}
// GetOurActiveInstallations returns all the active installations available given an identity
func (p *Protocol) GetOurActiveInstallations(myIdentityKey *ecdsa.PublicKey) ([]*multidevice.Installation, error) {
return p.multidevice.GetOurActiveInstallations(myIdentityKey)
}
// SetInstallationMetadata sets the metadata for our own installation
func (p *Protocol) SetInstallationMetadata(myIdentityKey *ecdsa.PublicKey, installationID string, data *multidevice.InstallationMetadata) error {
return p.multidevice.SetInstallationMetadata(myIdentityKey, installationID, data)
}
// SetInstallationName sets the metadata for our own installation
func (p *Protocol) SetInstallationName(myIdentityKey *ecdsa.PublicKey, installationID string, name string) error {
return p.multidevice.SetInstallationName(myIdentityKey, installationID, name)
}
// GetPublicBundle retrieves a public bundle given an identity
func (p *Protocol) GetPublicBundle(theirIdentityKey *ecdsa.PublicKey) (*Bundle, error) {
installations, err := p.multidevice.GetActiveInstallations(theirIdentityKey)
if err != nil {
return nil, err
}
return p.encryptor.GetPublicBundle(theirIdentityKey, installations)
}
// ConfirmMessageProcessed confirms and deletes message keys for the given messages
func (p *Protocol) ConfirmMessageProcessed(messageID []byte) error {
logger := p.logger.With(zap.String("site", "ConfirmMessageProcessed"))
logger.Debug("confirming message", zap.String("messageID", types.EncodeHex(messageID)))
return p.encryptor.ConfirmMessageProcessed(messageID)
}
type HashRatchetInfo struct {
GroupID []byte
KeyID []byte
}
type DecryptMessageResponse struct {
DecryptedMessage []byte
Installations []*multidevice.Installation
SharedSecrets []*sharedsecret.Secret
HashRatchetInfo []*HashRatchetInfo
}
func (p *Protocol) HandleHashRatchetKeysPayload(groupID, encodedKeys []byte, myIdentityKey *ecdsa.PrivateKey, theirIdentityKey *ecdsa.PublicKey) ([]*HashRatchetInfo, error) {
keys := &HRKeys{}
err := proto.Unmarshal(encodedKeys, keys)
if err != nil {
return nil, err
}
return p.HandleHashRatchetKeys(groupID, keys, myIdentityKey, theirIdentityKey)
}
func (p *Protocol) HandleHashRatchetKeys(groupID []byte, keys *HRKeys, myIdentityKey *ecdsa.PrivateKey, theirIdentityKey *ecdsa.PublicKey) ([]*HashRatchetInfo, error) {
if keys == nil {
return nil, nil
}
var info []*HashRatchetInfo
for _, key := range keys.Keys {
ratchet := &HashRatchetKeyCompatibility{
GroupID: groupID,
Timestamp: key.Timestamp,
Key: key.Key,
}
// If there's no timestamp, is coming from an older client
if key.Timestamp == 0 {
ratchet.Timestamp = uint64(key.DeprecatedKeyId)
}
keyID, err := ratchet.GetKeyID()
if err != nil {
return nil, err
}
p.logger.Debug("retrieved keys", zap.String("keyID", types.Bytes2Hex(keyID)))
// Payload contains hash ratchet key
err = p.encryptor.persistence.SaveHashRatchetKey(ratchet)
if err != nil {
return nil, err
}
info = append(info, &HashRatchetInfo{GroupID: groupID, KeyID: keyID})
}
if keys.RekeyGroup != nil {
if keys.RekeyGroup.Timestamp == 0 {
return nil, errors.New("timestamp can't be nil")
}
encryptionKey, err := decryptGroupRekeyMessage(myIdentityKey, theirIdentityKey, keys.RekeyGroup)
if err != nil {
return nil, err
}
if len(encryptionKey) != 0 {
ratchet := &HashRatchetKeyCompatibility{
GroupID: groupID,
Timestamp: keys.RekeyGroup.Timestamp,
Key: encryptionKey,
}
keyID, err := ratchet.GetKeyID()
if err != nil {
return nil, err
}
p.logger.Debug("retrieved group keys", zap.String("keyID", types.Bytes2Hex(keyID)))
// Payload contains hash ratchet key
err = p.encryptor.persistence.SaveHashRatchetKey(ratchet)
if err != nil {
return nil, err
}
info = append(info, &HashRatchetInfo{GroupID: groupID, KeyID: keyID})
}
}
return info, nil
}
// HandleMessage unmarshals a message and processes it, decrypting it if it is a 1:1 message.
func (p *Protocol) HandleMessage(
myIdentityKey *ecdsa.PrivateKey,
theirPublicKey *ecdsa.PublicKey,
protocolMessage *ProtocolMessage,
messageID []byte,
) (*DecryptMessageResponse, error) {
logger := p.logger.With(zap.String("site", "HandleMessage"))
response := &DecryptMessageResponse{}
logger.Debug("received a protocol message",
zap.String("sender-public-key",
types.EncodeHex(crypto.FromECDSAPub(theirPublicKey))),
zap.String("my-installation-id", p.encryptor.config.InstallationID),
zap.String("messageID", types.EncodeHex(messageID)))
if p.encryptor == nil {
return nil, errors.New("encryption service not initialized")
}
// Process bundles
for _, bundle := range protocolMessage.GetBundles() {
// Should we stop processing if the bundle cannot be verified?
newInstallations, err := p.ProcessPublicBundle(myIdentityKey, bundle)
if err != nil {
return nil, err
}
response.Installations = newInstallations
}
// Check if it's a public message
if publicMessage := protocolMessage.GetPublicMessage(); publicMessage != nil {
// Nothing to do, as already in cleartext
response.DecryptedMessage = publicMessage
return response, nil
}
// Decrypt message
if encryptedMessage := protocolMessage.GetEncryptedMessage(); encryptedMessage != nil {
message, err := p.encryptor.DecryptPayload(
myIdentityKey,
theirPublicKey,
protocolMessage.GetInstallationId(),
encryptedMessage,
messageID,
)
if err == ErrHashRatchetGroupIDNotFound {
msg := p.encryptor.GetMessage(encryptedMessage)
if msg != nil {
if header := msg.GetHRHeader(); header != nil {
response.HashRatchetInfo = append(response.HashRatchetInfo, &HashRatchetInfo{GroupID: header.GroupId, KeyID: header.KeyId})
}
}
return response, err
}
if err != nil {
return nil, err
}
dmProtocol := encryptedMessage[p.encryptor.config.InstallationID]
if dmProtocol == nil {
dmProtocol = encryptedMessage[noInstallationID]
}
if dmProtocol != nil {
hrHeader := dmProtocol.HRHeader
if hrHeader != nil && hrHeader.SeqNo == 0 {
var hashRatchetKeys []*HashRatchetInfo
if hrHeader.Keys != nil {
hashRatchetKeys, err = p.HandleHashRatchetKeys(hrHeader.GroupId, hrHeader.Keys, myIdentityKey, theirPublicKey)
if err != nil {
return nil, err
}
} else {
// For backward compatibility
hashRatchetKeys, err = p.HandleHashRatchetKeysPayload(hrHeader.GroupId, message, myIdentityKey, theirPublicKey)
if err != nil {
return nil, err
}
}
response.HashRatchetInfo = hashRatchetKeys
}
}
bundles := protocolMessage.GetBundles()
version := getProtocolVersion(bundles, protocolMessage.GetInstallationId())
if version >= sharedSecretNegotiationVersion {
sharedSecret, err := p.secret.Generate(myIdentityKey, theirPublicKey, protocolMessage.GetInstallationId())
if err != nil {
return nil, err
}
response.SharedSecrets = []*sharedsecret.Secret{sharedSecret}
}
response.DecryptedMessage = message
return response, nil
}
// Return error
return nil, ErrNoPayload
}
func (p *Protocol) ShouldAdvertiseBundle(publicKey *ecdsa.PublicKey, time int64) (bool, error) {
return p.publisher.ShouldAdvertiseBundle(publicKey, time)
}
func (p *Protocol) ConfirmBundleAdvertisement(publicKey *ecdsa.PublicKey, time int64) {
p.publisher.SetLastAck(publicKey, time)
}
func (p *Protocol) BuildBundleAdvertiseMessage(myIdentityKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey) (*ProtocolMessageSpec, error) {
return p.BuildDHMessage(myIdentityKey, publicKey, nil)
}
func getProtocolVersion(bundles []*Bundle, installationID string) uint32 {
if installationID == "" {
return defaultMinVersion
}
for _, bundle := range bundles {
if bundle != nil {
signedPreKeys := bundle.GetSignedPreKeys()
if signedPreKeys == nil {
continue
}
signedPreKey := signedPreKeys[installationID]
if signedPreKey == nil {
return defaultMinVersion
}
return signedPreKey.GetProtocolVersion()
}
}
return defaultMinVersion
}
func (p *Protocol) EncryptWithHashRatchet(groupID []byte, payload []byte) ([]byte, *HashRatchetKeyCompatibility, uint32, error) {
ratchet, err := p.encryptor.persistence.GetCurrentKeyForGroup(groupID)
if err != nil {
return nil, nil, 0, err
}
encryptedPayload, newSeqNo, err := p.encryptor.EncryptWithHR(ratchet, payload)
if err != nil {
return nil, nil, 0, err
}
return encryptedPayload, ratchet, newSeqNo, nil
}
func (p *Protocol) DecryptWithHashRatchet(keyID []byte, seqNo uint32, payload []byte) ([]byte, error) {
ratchet, err := p.encryptor.persistence.GetHashRatchetKeyByID(keyID)
if err != nil {
return nil, err
}
if ratchet == nil {
return nil, ErrNoRatchetKey
}
return p.encryptor.DecryptWithHR(ratchet, seqNo, payload)
}

View File

@@ -0,0 +1,791 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: protocol_message.proto
package encryption
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type SignedPreKey struct {
SignedPreKey []byte `protobuf:"bytes,1,opt,name=signed_pre_key,json=signedPreKey,proto3" json:"signed_pre_key,omitempty"`
Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"`
ProtocolVersion uint32 `protobuf:"varint,3,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SignedPreKey) Reset() { *m = SignedPreKey{} }
func (m *SignedPreKey) String() string { return proto.CompactTextString(m) }
func (*SignedPreKey) ProtoMessage() {}
func (*SignedPreKey) Descriptor() ([]byte, []int) {
return fileDescriptor_4e37b52004a72e16, []int{0}
}
func (m *SignedPreKey) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SignedPreKey.Unmarshal(m, b)
}
func (m *SignedPreKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SignedPreKey.Marshal(b, m, deterministic)
}
func (m *SignedPreKey) XXX_Merge(src proto.Message) {
xxx_messageInfo_SignedPreKey.Merge(m, src)
}
func (m *SignedPreKey) XXX_Size() int {
return xxx_messageInfo_SignedPreKey.Size(m)
}
func (m *SignedPreKey) XXX_DiscardUnknown() {
xxx_messageInfo_SignedPreKey.DiscardUnknown(m)
}
var xxx_messageInfo_SignedPreKey proto.InternalMessageInfo
func (m *SignedPreKey) GetSignedPreKey() []byte {
if m != nil {
return m.SignedPreKey
}
return nil
}
func (m *SignedPreKey) GetVersion() uint32 {
if m != nil {
return m.Version
}
return 0
}
func (m *SignedPreKey) GetProtocolVersion() uint32 {
if m != nil {
return m.ProtocolVersion
}
return 0
}
// X3DH prekey bundle
type Bundle struct {
// Identity key
Identity []byte `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"`
// Installation id
SignedPreKeys map[string]*SignedPreKey `protobuf:"bytes,2,rep,name=signed_pre_keys,json=signedPreKeys,proto3" json:"signed_pre_keys,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// Prekey signature
Signature []byte `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"`
// When the bundle was created locally
Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Bundle) Reset() { *m = Bundle{} }
func (m *Bundle) String() string { return proto.CompactTextString(m) }
func (*Bundle) ProtoMessage() {}
func (*Bundle) Descriptor() ([]byte, []int) {
return fileDescriptor_4e37b52004a72e16, []int{1}
}
func (m *Bundle) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Bundle.Unmarshal(m, b)
}
func (m *Bundle) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Bundle.Marshal(b, m, deterministic)
}
func (m *Bundle) XXX_Merge(src proto.Message) {
xxx_messageInfo_Bundle.Merge(m, src)
}
func (m *Bundle) XXX_Size() int {
return xxx_messageInfo_Bundle.Size(m)
}
func (m *Bundle) XXX_DiscardUnknown() {
xxx_messageInfo_Bundle.DiscardUnknown(m)
}
var xxx_messageInfo_Bundle proto.InternalMessageInfo
func (m *Bundle) GetIdentity() []byte {
if m != nil {
return m.Identity
}
return nil
}
func (m *Bundle) GetSignedPreKeys() map[string]*SignedPreKey {
if m != nil {
return m.SignedPreKeys
}
return nil
}
func (m *Bundle) GetSignature() []byte {
if m != nil {
return m.Signature
}
return nil
}
func (m *Bundle) GetTimestamp() int64 {
if m != nil {
return m.Timestamp
}
return 0
}
type BundleContainer struct {
// X3DH prekey bundle
Bundle *Bundle `protobuf:"bytes,1,opt,name=bundle,proto3" json:"bundle,omitempty"`
// Private signed prekey
PrivateSignedPreKey []byte `protobuf:"bytes,2,opt,name=private_signed_pre_key,json=privateSignedPreKey,proto3" json:"private_signed_pre_key,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *BundleContainer) Reset() { *m = BundleContainer{} }
func (m *BundleContainer) String() string { return proto.CompactTextString(m) }
func (*BundleContainer) ProtoMessage() {}
func (*BundleContainer) Descriptor() ([]byte, []int) {
return fileDescriptor_4e37b52004a72e16, []int{2}
}
func (m *BundleContainer) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_BundleContainer.Unmarshal(m, b)
}
func (m *BundleContainer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_BundleContainer.Marshal(b, m, deterministic)
}
func (m *BundleContainer) XXX_Merge(src proto.Message) {
xxx_messageInfo_BundleContainer.Merge(m, src)
}
func (m *BundleContainer) XXX_Size() int {
return xxx_messageInfo_BundleContainer.Size(m)
}
func (m *BundleContainer) XXX_DiscardUnknown() {
xxx_messageInfo_BundleContainer.DiscardUnknown(m)
}
var xxx_messageInfo_BundleContainer proto.InternalMessageInfo
func (m *BundleContainer) GetBundle() *Bundle {
if m != nil {
return m.Bundle
}
return nil
}
func (m *BundleContainer) GetPrivateSignedPreKey() []byte {
if m != nil {
return m.PrivateSignedPreKey
}
return nil
}
type DRHeader struct {
// Current ratchet public key
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
// Number of the message in the sending chain
N uint32 `protobuf:"varint,2,opt,name=n,proto3" json:"n,omitempty"`
// Length of the previous sending chain
Pn uint32 `protobuf:"varint,3,opt,name=pn,proto3" json:"pn,omitempty"`
// Bundle ID
Id []byte `protobuf:"bytes,4,opt,name=id,proto3" json:"id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *DRHeader) Reset() { *m = DRHeader{} }
func (m *DRHeader) String() string { return proto.CompactTextString(m) }
func (*DRHeader) ProtoMessage() {}
func (*DRHeader) Descriptor() ([]byte, []int) {
return fileDescriptor_4e37b52004a72e16, []int{3}
}
func (m *DRHeader) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DRHeader.Unmarshal(m, b)
}
func (m *DRHeader) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_DRHeader.Marshal(b, m, deterministic)
}
func (m *DRHeader) XXX_Merge(src proto.Message) {
xxx_messageInfo_DRHeader.Merge(m, src)
}
func (m *DRHeader) XXX_Size() int {
return xxx_messageInfo_DRHeader.Size(m)
}
func (m *DRHeader) XXX_DiscardUnknown() {
xxx_messageInfo_DRHeader.DiscardUnknown(m)
}
var xxx_messageInfo_DRHeader proto.InternalMessageInfo
func (m *DRHeader) GetKey() []byte {
if m != nil {
return m.Key
}
return nil
}
func (m *DRHeader) GetN() uint32 {
if m != nil {
return m.N
}
return 0
}
func (m *DRHeader) GetPn() uint32 {
if m != nil {
return m.Pn
}
return 0
}
func (m *DRHeader) GetId() []byte {
if m != nil {
return m.Id
}
return nil
}
type DHHeader struct {
// Compressed ephemeral public key
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *DHHeader) Reset() { *m = DHHeader{} }
func (m *DHHeader) String() string { return proto.CompactTextString(m) }
func (*DHHeader) ProtoMessage() {}
func (*DHHeader) Descriptor() ([]byte, []int) {
return fileDescriptor_4e37b52004a72e16, []int{4}
}
func (m *DHHeader) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DHHeader.Unmarshal(m, b)
}
func (m *DHHeader) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_DHHeader.Marshal(b, m, deterministic)
}
func (m *DHHeader) XXX_Merge(src proto.Message) {
xxx_messageInfo_DHHeader.Merge(m, src)
}
func (m *DHHeader) XXX_Size() int {
return xxx_messageInfo_DHHeader.Size(m)
}
func (m *DHHeader) XXX_DiscardUnknown() {
xxx_messageInfo_DHHeader.DiscardUnknown(m)
}
var xxx_messageInfo_DHHeader proto.InternalMessageInfo
func (m *DHHeader) GetKey() []byte {
if m != nil {
return m.Key
}
return nil
}
type X3DHHeader struct {
// Ephemeral key used
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
// Used bundle's signed prekey
Id []byte `protobuf:"bytes,4,opt,name=id,proto3" json:"id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *X3DHHeader) Reset() { *m = X3DHHeader{} }
func (m *X3DHHeader) String() string { return proto.CompactTextString(m) }
func (*X3DHHeader) ProtoMessage() {}
func (*X3DHHeader) Descriptor() ([]byte, []int) {
return fileDescriptor_4e37b52004a72e16, []int{5}
}
func (m *X3DHHeader) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_X3DHHeader.Unmarshal(m, b)
}
func (m *X3DHHeader) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_X3DHHeader.Marshal(b, m, deterministic)
}
func (m *X3DHHeader) XXX_Merge(src proto.Message) {
xxx_messageInfo_X3DHHeader.Merge(m, src)
}
func (m *X3DHHeader) XXX_Size() int {
return xxx_messageInfo_X3DHHeader.Size(m)
}
func (m *X3DHHeader) XXX_DiscardUnknown() {
xxx_messageInfo_X3DHHeader.DiscardUnknown(m)
}
var xxx_messageInfo_X3DHHeader proto.InternalMessageInfo
func (m *X3DHHeader) GetKey() []byte {
if m != nil {
return m.Key
}
return nil
}
func (m *X3DHHeader) GetId() []byte {
if m != nil {
return m.Id
}
return nil
}
// Hash Ratchet Header
type HRHeader struct {
// deprecated group key ID
DeprecatedKeyId uint32 `protobuf:"varint,1,opt,name=deprecated_key_id,json=deprecatedKeyId,proto3" json:"deprecated_key_id,omitempty"`
// group message number for this key_id
SeqNo uint32 `protobuf:"varint,2,opt,name=seq_no,json=seqNo,proto3" json:"seq_no,omitempty"`
// group ID
GroupId []byte `protobuf:"bytes,3,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
// group key ID
KeyId []byte `protobuf:"bytes,4,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"`
Keys *HRKeys `protobuf:"bytes,5,opt,name=keys,proto3" json:"keys,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *HRHeader) Reset() { *m = HRHeader{} }
func (m *HRHeader) String() string { return proto.CompactTextString(m) }
func (*HRHeader) ProtoMessage() {}
func (*HRHeader) Descriptor() ([]byte, []int) {
return fileDescriptor_4e37b52004a72e16, []int{6}
}
func (m *HRHeader) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HRHeader.Unmarshal(m, b)
}
func (m *HRHeader) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HRHeader.Marshal(b, m, deterministic)
}
func (m *HRHeader) XXX_Merge(src proto.Message) {
xxx_messageInfo_HRHeader.Merge(m, src)
}
func (m *HRHeader) XXX_Size() int {
return xxx_messageInfo_HRHeader.Size(m)
}
func (m *HRHeader) XXX_DiscardUnknown() {
xxx_messageInfo_HRHeader.DiscardUnknown(m)
}
var xxx_messageInfo_HRHeader proto.InternalMessageInfo
func (m *HRHeader) GetDeprecatedKeyId() uint32 {
if m != nil {
return m.DeprecatedKeyId
}
return 0
}
func (m *HRHeader) GetSeqNo() uint32 {
if m != nil {
return m.SeqNo
}
return 0
}
func (m *HRHeader) GetGroupId() []byte {
if m != nil {
return m.GroupId
}
return nil
}
func (m *HRHeader) GetKeyId() []byte {
if m != nil {
return m.KeyId
}
return nil
}
func (m *HRHeader) GetKeys() *HRKeys {
if m != nil {
return m.Keys
}
return nil
}
type RekeyGroup struct {
Timestamp uint64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
Keys map[uint32][]byte `protobuf:"bytes,4,rep,name=keys,proto3" json:"keys,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RekeyGroup) Reset() { *m = RekeyGroup{} }
func (m *RekeyGroup) String() string { return proto.CompactTextString(m) }
func (*RekeyGroup) ProtoMessage() {}
func (*RekeyGroup) Descriptor() ([]byte, []int) {
return fileDescriptor_4e37b52004a72e16, []int{7}
}
func (m *RekeyGroup) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RekeyGroup.Unmarshal(m, b)
}
func (m *RekeyGroup) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RekeyGroup.Marshal(b, m, deterministic)
}
func (m *RekeyGroup) XXX_Merge(src proto.Message) {
xxx_messageInfo_RekeyGroup.Merge(m, src)
}
func (m *RekeyGroup) XXX_Size() int {
return xxx_messageInfo_RekeyGroup.Size(m)
}
func (m *RekeyGroup) XXX_DiscardUnknown() {
xxx_messageInfo_RekeyGroup.DiscardUnknown(m)
}
var xxx_messageInfo_RekeyGroup proto.InternalMessageInfo
func (m *RekeyGroup) GetTimestamp() uint64 {
if m != nil {
return m.Timestamp
}
return 0
}
func (m *RekeyGroup) GetKeys() map[uint32][]byte {
if m != nil {
return m.Keys
}
return nil
}
type HRKeys struct {
Keys []*HRKey `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty"`
RekeyGroup *RekeyGroup `protobuf:"bytes,2,opt,name=rekey_group,json=rekeyGroup,proto3" json:"rekey_group,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *HRKeys) Reset() { *m = HRKeys{} }
func (m *HRKeys) String() string { return proto.CompactTextString(m) }
func (*HRKeys) ProtoMessage() {}
func (*HRKeys) Descriptor() ([]byte, []int) {
return fileDescriptor_4e37b52004a72e16, []int{8}
}
func (m *HRKeys) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HRKeys.Unmarshal(m, b)
}
func (m *HRKeys) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HRKeys.Marshal(b, m, deterministic)
}
func (m *HRKeys) XXX_Merge(src proto.Message) {
xxx_messageInfo_HRKeys.Merge(m, src)
}
func (m *HRKeys) XXX_Size() int {
return xxx_messageInfo_HRKeys.Size(m)
}
func (m *HRKeys) XXX_DiscardUnknown() {
xxx_messageInfo_HRKeys.DiscardUnknown(m)
}
var xxx_messageInfo_HRKeys proto.InternalMessageInfo
func (m *HRKeys) GetKeys() []*HRKey {
if m != nil {
return m.Keys
}
return nil
}
func (m *HRKeys) GetRekeyGroup() *RekeyGroup {
if m != nil {
return m.RekeyGroup
}
return nil
}
type HRKey struct {
DeprecatedKeyId uint32 `protobuf:"varint,1,opt,name=deprecated_key_id,json=deprecatedKeyId,proto3" json:"deprecated_key_id,omitempty"`
Key []byte `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
Timestamp uint64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *HRKey) Reset() { *m = HRKey{} }
func (m *HRKey) String() string { return proto.CompactTextString(m) }
func (*HRKey) ProtoMessage() {}
func (*HRKey) Descriptor() ([]byte, []int) {
return fileDescriptor_4e37b52004a72e16, []int{9}
}
func (m *HRKey) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HRKey.Unmarshal(m, b)
}
func (m *HRKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HRKey.Marshal(b, m, deterministic)
}
func (m *HRKey) XXX_Merge(src proto.Message) {
xxx_messageInfo_HRKey.Merge(m, src)
}
func (m *HRKey) XXX_Size() int {
return xxx_messageInfo_HRKey.Size(m)
}
func (m *HRKey) XXX_DiscardUnknown() {
xxx_messageInfo_HRKey.DiscardUnknown(m)
}
var xxx_messageInfo_HRKey proto.InternalMessageInfo
func (m *HRKey) GetDeprecatedKeyId() uint32 {
if m != nil {
return m.DeprecatedKeyId
}
return 0
}
func (m *HRKey) GetKey() []byte {
if m != nil {
return m.Key
}
return nil
}
func (m *HRKey) GetTimestamp() uint64 {
if m != nil {
return m.Timestamp
}
return 0
}
// Direct message value
type EncryptedMessageProtocol struct {
X3DHHeader *X3DHHeader `protobuf:"bytes,1,opt,name=X3DH_header,json=X3DHHeader,proto3" json:"X3DH_header,omitempty"`
DRHeader *DRHeader `protobuf:"bytes,2,opt,name=DR_header,json=DRHeader,proto3" json:"DR_header,omitempty"`
DHHeader *DHHeader `protobuf:"bytes,101,opt,name=DH_header,json=DHHeader,proto3" json:"DH_header,omitempty"`
HRHeader *HRHeader `protobuf:"bytes,102,opt,name=HR_header,json=HRHeader,proto3" json:"HR_header,omitempty"`
// Encrypted payload
Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *EncryptedMessageProtocol) Reset() { *m = EncryptedMessageProtocol{} }
func (m *EncryptedMessageProtocol) String() string { return proto.CompactTextString(m) }
func (*EncryptedMessageProtocol) ProtoMessage() {}
func (*EncryptedMessageProtocol) Descriptor() ([]byte, []int) {
return fileDescriptor_4e37b52004a72e16, []int{10}
}
func (m *EncryptedMessageProtocol) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_EncryptedMessageProtocol.Unmarshal(m, b)
}
func (m *EncryptedMessageProtocol) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_EncryptedMessageProtocol.Marshal(b, m, deterministic)
}
func (m *EncryptedMessageProtocol) XXX_Merge(src proto.Message) {
xxx_messageInfo_EncryptedMessageProtocol.Merge(m, src)
}
func (m *EncryptedMessageProtocol) XXX_Size() int {
return xxx_messageInfo_EncryptedMessageProtocol.Size(m)
}
func (m *EncryptedMessageProtocol) XXX_DiscardUnknown() {
xxx_messageInfo_EncryptedMessageProtocol.DiscardUnknown(m)
}
var xxx_messageInfo_EncryptedMessageProtocol proto.InternalMessageInfo
func (m *EncryptedMessageProtocol) GetX3DHHeader() *X3DHHeader {
if m != nil {
return m.X3DHHeader
}
return nil
}
func (m *EncryptedMessageProtocol) GetDRHeader() *DRHeader {
if m != nil {
return m.DRHeader
}
return nil
}
func (m *EncryptedMessageProtocol) GetDHHeader() *DHHeader {
if m != nil {
return m.DHHeader
}
return nil
}
func (m *EncryptedMessageProtocol) GetHRHeader() *HRHeader {
if m != nil {
return m.HRHeader
}
return nil
}
func (m *EncryptedMessageProtocol) GetPayload() []byte {
if m != nil {
return m.Payload
}
return nil
}
// Top-level protocol message
type ProtocolMessage struct {
// The device id of the sender
InstallationId string `protobuf:"bytes,2,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"`
// List of bundles
Bundles []*Bundle `protobuf:"bytes,3,rep,name=bundles,proto3" json:"bundles,omitempty"`
// One to one message, encrypted, indexed by installation_id
// TODO map here is redundant in case of community messages
EncryptedMessage map[string]*EncryptedMessageProtocol `protobuf:"bytes,101,rep,name=encrypted_message,json=encryptedMessage,proto3" json:"encrypted_message,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// Public chats, not encrypted
PublicMessage []byte `protobuf:"bytes,102,opt,name=public_message,json=publicMessage,proto3" json:"public_message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ProtocolMessage) Reset() { *m = ProtocolMessage{} }
func (m *ProtocolMessage) String() string { return proto.CompactTextString(m) }
func (*ProtocolMessage) ProtoMessage() {}
func (*ProtocolMessage) Descriptor() ([]byte, []int) {
return fileDescriptor_4e37b52004a72e16, []int{11}
}
func (m *ProtocolMessage) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ProtocolMessage.Unmarshal(m, b)
}
func (m *ProtocolMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ProtocolMessage.Marshal(b, m, deterministic)
}
func (m *ProtocolMessage) XXX_Merge(src proto.Message) {
xxx_messageInfo_ProtocolMessage.Merge(m, src)
}
func (m *ProtocolMessage) XXX_Size() int {
return xxx_messageInfo_ProtocolMessage.Size(m)
}
func (m *ProtocolMessage) XXX_DiscardUnknown() {
xxx_messageInfo_ProtocolMessage.DiscardUnknown(m)
}
var xxx_messageInfo_ProtocolMessage proto.InternalMessageInfo
func (m *ProtocolMessage) GetInstallationId() string {
if m != nil {
return m.InstallationId
}
return ""
}
func (m *ProtocolMessage) GetBundles() []*Bundle {
if m != nil {
return m.Bundles
}
return nil
}
func (m *ProtocolMessage) GetEncryptedMessage() map[string]*EncryptedMessageProtocol {
if m != nil {
return m.EncryptedMessage
}
return nil
}
func (m *ProtocolMessage) GetPublicMessage() []byte {
if m != nil {
return m.PublicMessage
}
return nil
}
func init() {
proto.RegisterType((*SignedPreKey)(nil), "encryption.SignedPreKey")
proto.RegisterType((*Bundle)(nil), "encryption.Bundle")
proto.RegisterMapType((map[string]*SignedPreKey)(nil), "encryption.Bundle.SignedPreKeysEntry")
proto.RegisterType((*BundleContainer)(nil), "encryption.BundleContainer")
proto.RegisterType((*DRHeader)(nil), "encryption.DRHeader")
proto.RegisterType((*DHHeader)(nil), "encryption.DHHeader")
proto.RegisterType((*X3DHHeader)(nil), "encryption.X3DHHeader")
proto.RegisterType((*HRHeader)(nil), "encryption.HRHeader")
proto.RegisterType((*RekeyGroup)(nil), "encryption.RekeyGroup")
proto.RegisterMapType((map[uint32][]byte)(nil), "encryption.RekeyGroup.KeysEntry")
proto.RegisterType((*HRKeys)(nil), "encryption.HRKeys")
proto.RegisterType((*HRKey)(nil), "encryption.HRKey")
proto.RegisterType((*EncryptedMessageProtocol)(nil), "encryption.EncryptedMessageProtocol")
proto.RegisterType((*ProtocolMessage)(nil), "encryption.ProtocolMessage")
proto.RegisterMapType((map[string]*EncryptedMessageProtocol)(nil), "encryption.ProtocolMessage.EncryptedMessageEntry")
}
func init() {
proto.RegisterFile("protocol_message.proto", fileDescriptor_4e37b52004a72e16)
}
var fileDescriptor_4e37b52004a72e16 = []byte{
// 770 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0xdb, 0x4e, 0xdc, 0x48,
0x10, 0x95, 0xed, 0xb9, 0xd6, 0x5c, 0xe9, 0x05, 0xe4, 0x1d, 0xf1, 0x30, 0xb2, 0x60, 0x77, 0x16,
0xad, 0xbc, 0xe2, 0x22, 0xb1, 0x22, 0x6f, 0x04, 0x94, 0x01, 0x44, 0x84, 0x3a, 0x52, 0x14, 0xf1,
0x10, 0xcb, 0x8c, 0x0b, 0xb0, 0x18, 0x6c, 0x63, 0x7b, 0x50, 0xfc, 0x03, 0x79, 0xc9, 0x17, 0xe4,
0x0b, 0xf2, 0x4f, 0xf9, 0x9a, 0xa8, 0x2f, 0xb6, 0x7b, 0x2e, 0x3c, 0xe4, 0xcd, 0x5d, 0x5d, 0x75,
0xea, 0x54, 0xd5, 0xe9, 0x32, 0x6c, 0x46, 0x71, 0x98, 0x86, 0x93, 0x70, 0xea, 0x3c, 0x61, 0x92,
0xb8, 0xf7, 0x68, 0x73, 0x03, 0x01, 0x0c, 0x26, 0x71, 0x16, 0xa5, 0x7e, 0x18, 0x58, 0x19, 0xb4,
0x3f, 0xf8, 0xf7, 0x01, 0x7a, 0xd7, 0x31, 0x5e, 0x62, 0x46, 0xb6, 0xa1, 0x9b, 0xf0, 0xb3, 0x13,
0xc5, 0xe8, 0x3c, 0x62, 0x66, 0x6a, 0x43, 0x6d, 0xd4, 0xa6, 0xed, 0x44, 0xf5, 0x32, 0xa1, 0xfe,
0x82, 0x71, 0xe2, 0x87, 0x81, 0xa9, 0x0f, 0xb5, 0x51, 0x87, 0xe6, 0x47, 0xf2, 0x0f, 0xf4, 0x8b,
0xac, 0xb9, 0x8b, 0xc1, 0x5d, 0x7a, 0xb9, 0xfd, 0xa3, 0x30, 0x5b, 0xdf, 0x74, 0xa8, 0x9d, 0xcc,
0x02, 0x6f, 0x8a, 0x64, 0x00, 0x0d, 0xdf, 0xc3, 0x20, 0xf5, 0xd3, 0x3c, 0x5f, 0x71, 0x26, 0x57,
0xd0, 0x9b, 0x67, 0x94, 0x98, 0xfa, 0xd0, 0x18, 0xb5, 0xf6, 0x77, 0xec, 0xb2, 0x0e, 0x5b, 0x00,
0xd9, 0x6a, 0x2d, 0xc9, 0x59, 0x90, 0xc6, 0x19, 0xed, 0xa8, 0xcc, 0x13, 0xb2, 0x05, 0x4d, 0x66,
0x70, 0xd3, 0x59, 0x8c, 0x66, 0x85, 0xe7, 0x2a, 0x0d, 0xec, 0x36, 0xf5, 0x9f, 0x30, 0x49, 0xdd,
0xa7, 0xc8, 0xac, 0x0e, 0xb5, 0x91, 0x41, 0x4b, 0xc3, 0xe0, 0x06, 0xc8, 0x72, 0x02, 0xd2, 0x07,
0x23, 0xef, 0x53, 0x93, 0xb2, 0x4f, 0x62, 0x43, 0xf5, 0xc5, 0x9d, 0xce, 0x90, 0x37, 0xa7, 0xb5,
0x6f, 0xaa, 0x44, 0x55, 0x00, 0x2a, 0xdc, 0x8e, 0xf5, 0xff, 0x35, 0xeb, 0x0b, 0xf4, 0x44, 0x0d,
0x6f, 0xc3, 0x20, 0x75, 0xfd, 0x00, 0x63, 0xb2, 0x0b, 0xb5, 0x5b, 0x6e, 0xe2, 0xd8, 0xad, 0x7d,
0xb2, 0x5c, 0x30, 0x95, 0x1e, 0xe4, 0x80, 0x4d, 0xdb, 0x7f, 0x71, 0x53, 0x74, 0x16, 0xe6, 0xa7,
0xf3, 0x1a, 0xff, 0x90, 0xb7, 0x6a, 0xfa, 0x8b, 0x4a, 0xc3, 0xe8, 0x57, 0xac, 0x0b, 0x68, 0x9c,
0xd2, 0x31, 0xba, 0x1e, 0xc6, 0x6a, 0x2d, 0x6d, 0x51, 0x4b, 0x1b, 0xb4, 0x7c, 0xc8, 0x5a, 0x40,
0xba, 0xa0, 0x47, 0xf9, 0x40, 0xf5, 0x88, 0x9f, 0x7d, 0x4f, 0xb6, 0x51, 0xf7, 0x3d, 0x6b, 0x0b,
0x1a, 0xa7, 0xe3, 0xd7, 0xb0, 0xac, 0x43, 0x80, 0x4f, 0x07, 0xaf, 0xdf, 0x2f, 0xa2, 0x49, 0x7e,
0x3f, 0x34, 0x68, 0x8c, 0x73, 0x82, 0xbb, 0xb0, 0xe6, 0x61, 0x14, 0xe3, 0xc4, 0x4d, 0xd1, 0x63,
0xf5, 0x39, 0xbe, 0xc7, 0x21, 0x3a, 0xb4, 0x57, 0x5e, 0x5c, 0x62, 0x76, 0xee, 0x91, 0x0d, 0xa8,
0x25, 0xf8, 0xec, 0x04, 0xa1, 0xe4, 0x5f, 0x4d, 0xf0, 0xf9, 0x7d, 0x48, 0xfe, 0x84, 0xc6, 0x7d,
0x1c, 0xce, 0x22, 0x16, 0x69, 0xf0, 0x5c, 0x75, 0x7e, 0x16, 0x11, 0x12, 0x52, 0x90, 0xa8, 0x3e,
0x72, 0xa0, 0xbf, 0xa0, 0xc2, 0x75, 0x57, 0x5d, 0x1e, 0xc3, 0x98, 0x32, 0x21, 0x50, 0x7e, 0x6f,
0x7d, 0xd7, 0x00, 0x28, 0x3e, 0x62, 0xf6, 0x8e, 0xe1, 0xcd, 0x8b, 0x89, 0x51, 0xa8, 0x28, 0x62,
0x22, 0x87, 0x12, 0xb4, 0xc2, 0xc5, 0x3c, 0x54, 0x41, 0x4b, 0x0c, 0xbb, 0xd4, 0x31, 0xf7, 0x1e,
0x1c, 0x41, 0x73, 0xa5, 0xf2, 0x3a, 0xa2, 0x83, 0xeb, 0xaa, 0xf2, 0xda, 0xaa, 0xbe, 0x1e, 0xa0,
0x26, 0xb8, 0x92, 0x1d, 0x99, 0x58, 0xe3, 0x89, 0xd7, 0x96, 0xaa, 0x11, 0x99, 0xc8, 0x11, 0xb4,
0x62, 0xc6, 0xc3, 0xe1, 0xcd, 0x91, 0x52, 0xde, 0x5c, 0x4d, 0x93, 0x42, 0x5c, 0x7c, 0x5b, 0x13,
0xa8, 0x72, 0x9c, 0xdf, 0x9a, 0x95, 0x2c, 0x45, 0x2f, 0xc5, 0x30, 0xd7, 0x3d, 0x63, 0xa1, 0x7b,
0xd6, 0x57, 0x1d, 0xcc, 0x33, 0x41, 0x05, 0xbd, 0x2b, 0xb1, 0xde, 0xae, 0xe5, 0x82, 0x61, 0xd4,
0x99, 0xce, 0x9c, 0x07, 0xae, 0x19, 0xf9, 0x7a, 0xe6, 0xa8, 0x97, 0x32, 0xa4, 0xaa, 0x24, 0xf7,
0xa0, 0x79, 0x4a, 0xf3, 0x30, 0x51, 0xf1, 0xba, 0x1a, 0x96, 0xbf, 0x13, 0x5a, 0xbe, 0x18, 0x16,
0x52, 0x64, 0xc2, 0x15, 0x21, 0xe3, 0x22, 0x44, 0xc9, 0x32, 0x2e, 0xb2, 0xdc, 0x2d, 0x87, 0x8c,
0x8b, 0x2c, 0x85, 0xec, 0x4d, 0xa8, 0x47, 0x6e, 0x36, 0x0d, 0xdd, 0x42, 0xb2, 0xf2, 0x68, 0xfd,
0xd4, 0xa1, 0x97, 0x17, 0x2e, 0xfb, 0x40, 0xfe, 0x86, 0x9e, 0x1f, 0x24, 0xa9, 0x3b, 0x9d, 0xba,
0x0c, 0x90, 0xb5, 0x5d, 0xe7, 0xdb, 0xa9, 0xab, 0x9a, 0xcf, 0x3d, 0xf2, 0x2f, 0xd4, 0xc5, 0xfe,
0x48, 0x4c, 0x83, 0xab, 0x61, 0xd5, 0x8a, 0xc9, 0x5d, 0xc8, 0x67, 0x58, 0xc3, 0xbc, 0xe5, 0xf9,
0x2f, 0xc5, 0x44, 0x1e, 0xb7, 0xa7, 0xc6, 0x2d, 0xd0, 0xb1, 0x17, 0xe7, 0x24, 0xf4, 0xdc, 0xc7,
0x05, 0x33, 0xd9, 0x81, 0x6e, 0x34, 0xbb, 0x9d, 0xfa, 0x93, 0x02, 0xfc, 0x8e, 0xd7, 0xda, 0x11,
0x56, 0xe9, 0x36, 0xf0, 0x61, 0x63, 0x25, 0xe2, 0x8a, 0x45, 0x7c, 0x3c, 0xbf, 0x88, 0xb7, 0x55,
0x96, 0xaf, 0xa9, 0x47, 0x79, 0x34, 0x27, 0xbd, 0x9b, 0x8e, 0xfd, 0xdf, 0x9b, 0x32, 0xe8, 0xb6,
0xc6, 0x7f, 0x62, 0x07, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0x5d, 0x20, 0xbb, 0xc5, 0x5b, 0x07,
0x00, 0x00,
}

View File

@@ -0,0 +1,111 @@
syntax = "proto3";
option go_package = "./;encryption";
package encryption;
message SignedPreKey {
bytes signed_pre_key = 1;
uint32 version = 2;
uint32 protocol_version = 3;
}
// X3DH prekey bundle
message Bundle {
// Identity key
bytes identity = 1;
// Installation id
map<string,SignedPreKey> signed_pre_keys = 2;
// Prekey signature
bytes signature = 4;
// When the bundle was created locally
int64 timestamp = 5;
}
message BundleContainer {
reserved 3;
// X3DH prekey bundle
Bundle bundle = 1;
// Private signed prekey
bytes private_signed_pre_key = 2;
}
message DRHeader {
// Current ratchet public key
bytes key = 1;
// Number of the message in the sending chain
uint32 n = 2;
// Length of the previous sending chain
uint32 pn = 3;
// Bundle ID
bytes id = 4;
}
message DHHeader {
// Compressed ephemeral public key
bytes key = 1;
}
message X3DHHeader {
reserved 3;
// Ephemeral key used
bytes key = 1;
// Used bundle's signed prekey
bytes id = 4;
}
// Hash Ratchet Header
message HRHeader {
// deprecated group key ID
uint32 deprecated_key_id = 1;
// group message number for this key_id
uint32 seq_no = 2;
// group ID
bytes group_id = 3;
// group key ID
bytes key_id = 4;
HRKeys keys = 5;
}
message RekeyGroup {
uint64 timestamp = 2;
map<uint32, bytes> keys = 4;
}
message HRKeys {
repeated HRKey keys = 1;
RekeyGroup rekey_group = 2;
}
message HRKey {
uint32 deprecated_key_id = 1;
bytes key = 2;
uint64 timestamp = 3;
}
// Direct message value
message EncryptedMessageProtocol {
X3DHHeader X3DH_header = 1;
DRHeader DR_header = 2;
DHHeader DH_header = 101;
HRHeader HR_header = 102;
// Encrypted payload
bytes payload = 3;
}
// Top-level protocol message
message ProtocolMessage {
// The device id of the sender
string installation_id = 2;
// List of bundles
repeated Bundle bundles = 3;
// One to one message, encrypted, indexed by installation_id
// TODO map here is redundant in case of community messages
map<string,EncryptedMessageProtocol> encrypted_message = 101;
// Public chats, not encrypted
bytes public_message = 102;
}

View File

@@ -0,0 +1,6 @@
// Publisher periodically publishes an info about itself on a known channel.
// This channel is a particular topic calculated from the public key.
// It is required for other peers to start a secure conversation immediately
// using distibuted data through the channel.
package publisher

View File

@@ -0,0 +1,38 @@
package publisher
import (
"encoding/hex"
"sync"
)
type persistence struct {
lastAcksMutex sync.Mutex
lastPublished int64
lastAcks map[string]int64
}
func newPersistence() *persistence {
return &persistence{
lastAcks: make(map[string]int64),
}
}
func (s *persistence) getLastPublished() int64 {
return s.lastPublished
}
func (s *persistence) setLastPublished(lastPublished int64) {
s.lastPublished = lastPublished
}
func (s *persistence) lastAck(identity []byte) int64 {
s.lastAcksMutex.Lock()
defer s.lastAcksMutex.Unlock()
return s.lastAcks[hex.EncodeToString(identity)]
}
func (s *persistence) setLastAck(identity []byte, lastAck int64) {
s.lastAcksMutex.Lock()
defer s.lastAcksMutex.Unlock()
s.lastAcks[hex.EncodeToString(identity)] = lastAck
}

View File

@@ -0,0 +1,128 @@
package publisher
import (
"crypto/ecdsa"
"errors"
"time"
"go.uber.org/zap"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/logutils"
)
const (
// How often a ticker fires in seconds.
tickerInterval = 120
// How often we should publish a contact code in seconds.
publishInterval = 21600
// Cooldown period on acking messages when not targeting our device.
deviceNotFoundAckInterval = 7200
)
var (
errNotEnoughTimePassed = errors.New("not enough time passed")
)
type Publisher struct {
persistence *persistence
logger *zap.Logger
notifyCh chan struct{}
quit chan struct{}
}
func New(logger *zap.Logger) *Publisher {
if logger == nil {
logger = logutils.ZapLogger()
}
return &Publisher{
persistence: newPersistence(),
logger: logger.With(zap.Namespace("Publisher")),
}
}
func (p *Publisher) Start() <-chan struct{} {
logger := p.logger.With(zap.String("site", "Start"))
logger.Info("starting publisher")
p.notifyCh = make(chan struct{}, 100)
p.quit = make(chan struct{})
go p.tickerLoop()
return p.notifyCh
}
func (p *Publisher) Stop() {
// If hasn't started, ignore
if p.quit == nil {
return
}
select {
case _, ok := <-p.quit:
if !ok {
// channel already closed
return
}
default:
close(p.quit)
}
}
func (p *Publisher) tickerLoop() {
ticker := time.NewTicker(tickerInterval * time.Second)
go func() {
logger := p.logger.With(zap.String("site", "tickerLoop"))
for {
select {
case <-ticker.C:
err := p.notify()
switch err {
case errNotEnoughTimePassed:
logger.Debug("not enough time passed")
case nil:
// skip
default:
logger.Error("error while sending a contact code", zap.Error(err))
}
case <-p.quit:
ticker.Stop()
return
}
}
}()
}
func (p *Publisher) notify() error {
lastPublished := p.persistence.getLastPublished()
now := time.Now().Unix()
if now-lastPublished < publishInterval {
return errNotEnoughTimePassed
}
select {
case p.notifyCh <- struct{}{}:
default:
p.logger.Warn("publisher channel full, dropping message")
}
p.persistence.setLastPublished(now)
return nil
}
func (p *Publisher) ShouldAdvertiseBundle(publicKey *ecdsa.PublicKey, now int64) (bool, error) {
identity := crypto.CompressPubkey(publicKey)
lastAcked := p.persistence.lastAck(identity)
return now-lastAcked < deviceNotFoundAckInterval, nil
}
func (p *Publisher) SetLastAck(publicKey *ecdsa.PublicKey, now int64) {
identity := crypto.CompressPubkey(publicKey)
p.persistence.setLastAck(identity, now)
}

View File

@@ -0,0 +1,120 @@
package sharedsecret
import (
"database/sql"
"strings"
)
type Response struct {
secret []byte
installationIDs map[string]bool
}
type sqlitePersistence struct {
db *sql.DB
}
func newSQLitePersistence(db *sql.DB) *sqlitePersistence {
return &sqlitePersistence{db: db}
}
func (s *sqlitePersistence) Add(identity []byte, secret []byte, installationID string) error {
tx, err := s.db.Begin()
if err != nil {
return err
}
insertSecretStmt, err := tx.Prepare("INSERT INTO secrets(identity, secret) VALUES (?, ?)")
if err != nil {
_ = tx.Rollback()
return err
}
defer insertSecretStmt.Close()
_, err = insertSecretStmt.Exec(identity, secret)
if err != nil {
_ = tx.Rollback()
return err
}
insertInstallationIDStmt, err := tx.Prepare("INSERT INTO secret_installation_ids(id, identity_id) VALUES (?, ?)")
if err != nil {
_ = tx.Rollback()
return err
}
defer insertInstallationIDStmt.Close()
_, err = insertInstallationIDStmt.Exec(installationID, identity)
if err != nil {
_ = tx.Rollback()
return err
}
return tx.Commit()
}
func (s *sqlitePersistence) Get(identity []byte, installationIDs []string) (*Response, error) {
response := &Response{
installationIDs: make(map[string]bool),
}
args := make([]interface{}, len(installationIDs)+1)
args[0] = identity
for i, installationID := range installationIDs {
args[i+1] = installationID
}
/* #nosec */
query := `SELECT secret, id
FROM secrets t
JOIN
secret_installation_ids tid
ON t.identity = tid.identity_id
WHERE
t.identity = ?
AND
tid.id IN (?` + strings.Repeat(",?", len(installationIDs)-1) + `)`
rows, err := s.db.Query(query, args...)
if err != nil && err != sql.ErrNoRows {
return nil, err
}
defer rows.Close()
for rows.Next() {
var installationID string
var secret []byte
err = rows.Scan(&secret, &installationID)
if err != nil {
return nil, err
}
response.secret = secret
response.installationIDs[installationID] = true
}
return response, nil
}
func (s *sqlitePersistence) All() ([][][]byte, error) {
query := "SELECT identity, secret FROM secrets"
var secrets [][][]byte
rows, err := s.db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var secret []byte
var identity []byte
err = rows.Scan(&identity, &secret)
if err != nil {
return nil, err
}
secrets = append(secrets, [][]byte{identity, secret})
}
return secrets, nil
}

View File

@@ -0,0 +1,111 @@
package sharedsecret
import (
"bytes"
"crypto/ecdsa"
"database/sql"
"errors"
"go.uber.org/zap"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/crypto/ecies"
)
const sskLen = 16
type Secret struct {
Identity *ecdsa.PublicKey
Key []byte
}
// SharedSecret generates and manages negotiated secrets.
// Identities (public keys) stored by SharedSecret
// are compressed.
// TODO: make compression of public keys a responsibility of sqlitePersistence instead of SharedSecret.
type SharedSecret struct {
persistence *sqlitePersistence
logger *zap.Logger
}
func New(db *sql.DB, logger *zap.Logger) *SharedSecret {
if logger == nil {
logger = zap.NewNop()
}
return &SharedSecret{
persistence: newSQLitePersistence(db),
logger: logger.With(zap.Namespace("SharedSecret")),
}
}
func (s *SharedSecret) generate(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, installationID string) (*Secret, error) {
sharedKey, err := ecies.ImportECDSA(myPrivateKey).GenerateShared(
ecies.ImportECDSAPublic(theirPublicKey),
sskLen,
sskLen,
)
if err != nil {
return nil, err
}
theirIdentity := crypto.CompressPubkey(theirPublicKey)
if err = s.persistence.Add(theirIdentity, sharedKey, installationID); err != nil {
return nil, err
}
return &Secret{Key: sharedKey, Identity: theirPublicKey}, err
}
// Generate will generate a shared secret for a given identity, and return it.
func (s *SharedSecret) Generate(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, installationID string) (*Secret, error) {
return s.generate(myPrivateKey, theirPublicKey, installationID)
}
// Agreed returns true if a secret has been acknowledged by all the installationIDs.
func (s *SharedSecret) Agreed(myPrivateKey *ecdsa.PrivateKey, myInstallationID string, theirPublicKey *ecdsa.PublicKey, theirInstallationIDs []string) (*Secret, bool, error) {
secret, err := s.generate(myPrivateKey, theirPublicKey, myInstallationID)
if err != nil {
return nil, false, err
}
if len(theirInstallationIDs) == 0 {
return secret, false, nil
}
theirIdentity := crypto.CompressPubkey(theirPublicKey)
response, err := s.persistence.Get(theirIdentity, theirInstallationIDs)
if err != nil {
return nil, false, err
}
for _, installationID := range theirInstallationIDs {
if !response.installationIDs[installationID] {
return secret, false, nil
}
}
if !bytes.Equal(secret.Key, response.secret) {
return nil, false, errors.New("computed and saved secrets are different for a given identity")
}
return secret, true, nil
}
func (s *SharedSecret) All() ([]*Secret, error) {
var secrets []*Secret
tuples, err := s.persistence.All()
if err != nil {
return nil, err
}
for _, tuple := range tuples {
key, err := crypto.DecompressPubkey(tuple[0])
if err != nil {
return nil, err
}
secrets = append(secrets, &Secret{Identity: key, Key: tuple[1]})
}
return secrets, nil
}

View File

@@ -0,0 +1,249 @@
package encryption
import (
"crypto/ecdsa"
"errors"
"sort"
"strconv"
"time"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/crypto/ecies"
)
const (
// Shared secret key length
sskLen = 16
)
func buildSignatureMaterial(bundle *Bundle) []byte {
signedPreKeys := bundle.GetSignedPreKeys()
timestamp := bundle.GetTimestamp()
var keys []string
for k := range signedPreKeys {
keys = append(keys, k)
}
var signatureMaterial []byte
sort.Strings(keys)
for _, installationID := range keys {
signedPreKey := signedPreKeys[installationID]
signatureMaterial = append(signatureMaterial, []byte(installationID)...)
signatureMaterial = append(signatureMaterial, signedPreKey.SignedPreKey...)
signatureMaterial = append(signatureMaterial, []byte(strconv.FormatUint(uint64(signedPreKey.Version), 10))...)
// We don't use timestamp in the signature if it's 0, for backward compatibility
}
if timestamp != 0 {
signatureMaterial = append(signatureMaterial, []byte(strconv.FormatInt(timestamp, 10))...)
}
return signatureMaterial
}
// SignBundle signs the bundle and refreshes the timestamps
func SignBundle(identity *ecdsa.PrivateKey, bundleContainer *BundleContainer) error {
bundleContainer.Bundle.Timestamp = time.Now().UnixNano()
signatureMaterial := buildSignatureMaterial(bundleContainer.GetBundle())
signature, err := crypto.Sign(crypto.Keccak256(signatureMaterial), identity)
if err != nil {
return err
}
bundleContainer.Bundle.Signature = signature
return nil
}
// NewBundleContainer creates a new BundleContainer from an identity private key
func NewBundleContainer(identity *ecdsa.PrivateKey, installationID string) (*BundleContainer, error) {
preKey, err := crypto.GenerateKey()
if err != nil {
return nil, err
}
compressedPreKey := crypto.CompressPubkey(&preKey.PublicKey)
compressedIdentityKey := crypto.CompressPubkey(&identity.PublicKey)
encodedPreKey := crypto.FromECDSA(preKey)
signedPreKeys := make(map[string]*SignedPreKey)
signedPreKeys[installationID] = &SignedPreKey{
ProtocolVersion: protocolVersion,
SignedPreKey: compressedPreKey,
}
bundle := Bundle{
Timestamp: time.Now().UnixNano(),
Identity: compressedIdentityKey,
SignedPreKeys: signedPreKeys,
}
return &BundleContainer{
Bundle: &bundle,
PrivateSignedPreKey: encodedPreKey,
}, nil
}
// VerifyBundle checks that a bundle is valid
func VerifyBundle(bundle *Bundle) error {
_, err := ExtractIdentity(bundle)
return err
}
// ExtractIdentity extracts the identity key from a given bundle
func ExtractIdentity(bundle *Bundle) (*ecdsa.PublicKey, error) {
bundleIdentityKey, err := crypto.DecompressPubkey(bundle.GetIdentity())
if err != nil {
return nil, err
}
signatureMaterial := buildSignatureMaterial(bundle)
recoveredKey, err := crypto.SigToPub(
crypto.Keccak256(signatureMaterial),
bundle.GetSignature(),
)
if err != nil {
return nil, err
}
if crypto.PubkeyToAddress(*recoveredKey) != crypto.PubkeyToAddress(*bundleIdentityKey) {
return nil, errors.New("identity key and signature mismatch")
}
return recoveredKey, nil
}
// PerformDH generates a shared key given a private and a public key
func PerformDH(privateKey *ecies.PrivateKey, publicKey *ecies.PublicKey) ([]byte, error) {
return privateKey.GenerateShared(
publicKey,
sskLen,
sskLen,
)
}
func getSharedSecret(dh1 []byte, dh2 []byte, dh3 []byte) []byte {
secretInput := append(append(dh1, dh2...), dh3...)
return crypto.Keccak256(secretInput)
}
// x3dhActive handles initiating an X3DH session
func x3dhActive(
myIdentityKey *ecies.PrivateKey,
theirSignedPreKey *ecies.PublicKey,
myEphemeralKey *ecies.PrivateKey,
theirIdentityKey *ecies.PublicKey,
) ([]byte, error) {
var dh1, dh2, dh3 []byte
var err error
if dh1, err = PerformDH(myIdentityKey, theirSignedPreKey); err != nil {
return nil, err
}
if dh2, err = PerformDH(myEphemeralKey, theirIdentityKey); err != nil {
return nil, err
}
if dh3, err = PerformDH(myEphemeralKey, theirSignedPreKey); err != nil {
return nil, err
}
return getSharedSecret(dh1, dh2, dh3), nil
}
// x3dhPassive handles the response to an initiated X3DH session
func x3dhPassive(
theirIdentityKey *ecies.PublicKey,
mySignedPreKey *ecies.PrivateKey,
theirEphemeralKey *ecies.PublicKey,
myIdentityKey *ecies.PrivateKey,
) ([]byte, error) {
var dh1, dh2, dh3 []byte
var err error
if dh1, err = PerformDH(mySignedPreKey, theirIdentityKey); err != nil {
return nil, err
}
if dh2, err = PerformDH(myIdentityKey, theirEphemeralKey); err != nil {
return nil, err
}
if dh3, err = PerformDH(mySignedPreKey, theirEphemeralKey); err != nil {
return nil, err
}
return getSharedSecret(dh1, dh2, dh3), nil
}
// PerformActiveDH performs a Diffie-Hellman exchange using a public key and a generated ephemeral key.
// Returns the key resulting from the DH exchange as well as the ephemeral public key.
func PerformActiveDH(publicKey *ecdsa.PublicKey) ([]byte, *ecdsa.PublicKey, error) {
ephemeralKey, err := crypto.GenerateKey()
if err != nil {
return nil, nil, err
}
key, err := PerformDH(
ecies.ImportECDSA(ephemeralKey),
ecies.ImportECDSAPublic(publicKey),
)
if err != nil {
return nil, nil, err
}
return key, &ephemeralKey.PublicKey, err
}
// PerformActiveX3DH takes someone else's bundle and calculates shared secret.
// Returns the shared secret and the ephemeral key used.
func PerformActiveX3DH(identity []byte, signedPreKey []byte, prv *ecdsa.PrivateKey) ([]byte, *ecdsa.PublicKey, error) {
bundleIdentityKey, err := crypto.DecompressPubkey(identity)
if err != nil {
return nil, nil, err
}
bundleSignedPreKey, err := crypto.DecompressPubkey(signedPreKey)
if err != nil {
return nil, nil, err
}
ephemeralKey, err := crypto.GenerateKey()
if err != nil {
return nil, nil, err
}
sharedSecret, err := x3dhActive(
ecies.ImportECDSA(prv),
ecies.ImportECDSAPublic(bundleSignedPreKey),
ecies.ImportECDSA(ephemeralKey),
ecies.ImportECDSAPublic(bundleIdentityKey),
)
if err != nil {
return nil, nil, err
}
return sharedSecret, &ephemeralKey.PublicKey, nil
}
// PerformPassiveX3DH handles the part of the protocol where
// our interlocutor used our bundle, with ID of the signedPreKey,
// we loaded our identity key and the correct signedPreKey and we perform X3DH
func PerformPassiveX3DH(theirIdentityKey *ecdsa.PublicKey, mySignedPreKey *ecdsa.PrivateKey, theirEphemeralKey *ecdsa.PublicKey, myPrivateKey *ecdsa.PrivateKey) ([]byte, error) {
sharedSecret, err := x3dhPassive(
ecies.ImportECDSAPublic(theirIdentityKey),
ecies.ImportECDSA(mySignedPreKey),
ecies.ImportECDSAPublic(theirEphemeralKey),
ecies.ImportECDSA(myPrivateKey),
)
if err != nil {
return nil, err
}
return sharedSecret, nil
}