2019-07-17 22:25:42 +00:00
package encryption
2018-09-24 18:07:34 +00:00
import (
"crypto/ecdsa"
2019-07-30 06:14:13 +00:00
"database/sql"
2019-02-19 12:58:42 +00:00
"encoding/hex"
2018-09-24 18:07:34 +00:00
"errors"
2019-02-19 12:58:42 +00:00
"sync"
"time"
2018-09-24 18:07:34 +00:00
dr "github.com/status-im/doubleratchet"
2020-01-02 09:10:19 +00:00
"go.uber.org/zap"
2019-11-23 17:57:05 +00:00
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/crypto/ecies"
2020-11-24 12:36:52 +00:00
"github.com/status-im/status-go/eth-node/types"
2018-09-24 18:07:34 +00:00
2019-11-21 16:19:22 +00:00
"github.com/status-im/status-go/protocol/encryption/multidevice"
2018-09-24 18:07:34 +00:00
)
2019-07-01 09:39:51 +00:00
var (
2019-07-17 22:25:42 +00:00
errSessionNotFound = errors . New ( "session not found" )
2019-07-01 09:39:51 +00:00
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.
2021-09-21 15:47:04 +00:00
ErrNotPairedDevice = errors . New ( "received a message from not paired device" )
ErrHashRatchetSeqNoTooHigh = errors . New ( "Hash ratchet seq no is too high" )
2019-07-01 09:39:51 +00:00
)
2018-09-24 18:07:34 +00:00
2018-11-06 08:05:32 +00:00
// If we have no bundles, we use a constant so that the message can reach any device.
2021-09-21 15:47:04 +00:00
const (
noInstallationID = "none"
maxHashRatchetSeqNoDelta = 1000
)
2018-10-16 10:31:05 +00:00
2019-07-17 22:25:42 +00:00
type confirmationData struct {
2019-02-19 12:58:42 +00:00
header * dr . MessageHeader
drInfo * RatchetInfo
}
2019-07-17 22:25:42 +00:00
// 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
2018-11-27 08:54:20 +00:00
mutex sync . Mutex
2019-07-17 22:25:42 +00:00
logger * zap . Logger
2018-11-27 08:54:20 +00:00
}
2019-07-17 22:25:42 +00:00
type encryptorConfig struct {
2018-11-27 08:54:20 +00:00
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
2018-11-28 11:34:39 +00:00
// How long before we refresh the interval in milliseconds
BundleRefreshInterval int64
2019-07-17 22:25:42 +00:00
// The logging object
Logger * zap . Logger
2018-09-24 18:07:34 +00:00
}
2019-07-17 22:25:42 +00:00
// 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 {
2018-12-21 10:07:25 +00:00
MaxInstallations : 3 ,
2018-11-27 08:54:20 +00:00
MaxSkip : 1000 ,
MaxKeep : 3000 ,
MaxMessageKeysPerSession : 2000 ,
2018-12-21 10:07:25 +00:00
BundleRefreshInterval : 24 * 60 * 60 * 1000 ,
2018-11-27 08:54:20 +00:00
InstallationID : installationID ,
2019-07-17 22:25:42 +00:00
Logger : logger ,
2018-11-27 08:54:20 +00:00
}
}
2019-07-17 22:25:42 +00:00
// newEncryptor creates a new EncryptionService instance.
2019-07-30 06:14:13 +00:00
func newEncryptor ( db * sql . DB , config encryptorConfig ) * encryptor {
2019-07-17 22:25:42 +00:00
return & encryptor {
persistence : newSQLitePersistence ( db ) ,
config : config ,
messageIDs : make ( map [ string ] * confirmationData ) ,
logger : config . Logger . With ( zap . Namespace ( "encryptor" ) ) ,
2019-07-30 06:14:13 +00:00
}
2018-09-24 18:07:34 +00:00
}
2019-07-17 22:25:42 +00:00
func ( s * encryptor ) keyFromActiveX3DH ( theirIdentityKey [ ] byte , theirSignedPreKey [ ] byte , myIdentityKey * ecdsa . PrivateKey ) ( [ ] byte , * ecdsa . PublicKey , error ) {
2018-09-24 18:07:34 +00:00
sharedKey , ephemeralPubKey , err := PerformActiveX3DH ( theirIdentityKey , theirSignedPreKey , myIdentityKey )
if err != nil {
return nil , nil , err
}
return sharedKey , ephemeralPubKey , nil
}
2019-07-17 22:25:42 +00:00
func ( s * encryptor ) getDRSession ( id [ ] byte ) ( dr . Session , error ) {
sessionStorage := s . persistence . SessionStorage ( )
2018-11-27 08:54:20 +00:00
return dr . Load (
id ,
sessionStorage ,
2019-07-17 22:25:42 +00:00
dr . WithKeysStorage ( s . persistence . KeysStorage ( ) ) ,
2018-11-27 08:54:20 +00:00
dr . WithMaxSkip ( s . config . MaxSkip ) ,
dr . WithMaxKeep ( s . config . MaxKeep ) ,
dr . WithMaxMessageKeysPerSession ( s . config . MaxMessageKeysPerSession ) ,
dr . WithCrypto ( crypto . EthereumCrypto { } ) ,
)
}
2019-02-19 12:58:42 +00:00
func confirmationIDString ( id [ ] byte ) string {
return hex . EncodeToString ( id )
}
// ConfirmMessagesProcessed confirms and deletes message keys for the given messages
2019-07-17 22:25:42 +00:00
func ( s * encryptor ) ConfirmMessageProcessed ( messageID [ ] byte ) error {
2019-02-19 12:58:42 +00:00
s . mutex . Lock ( )
defer s . mutex . Unlock ( )
2019-07-17 22:25:42 +00:00
id := confirmationIDString ( messageID )
confirmationData , ok := s . messageIDs [ id ]
if ! ok {
2019-08-20 11:20:25 +00:00
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
2019-07-17 22:25:42 +00:00
}
2019-02-19 12:58:42 +00:00
2019-07-17 22:25:42 +00:00
// Load session from store first
session , err := s . getDRSession ( confirmationData . drInfo . ID )
if err != nil {
return err
}
2019-02-19 12:58:42 +00:00
2019-07-17 22:25:42 +00:00
if err := session . DeleteMk ( confirmationData . header . DH , confirmationData . header . N ) ; err != nil {
return err
2019-02-19 12:58:42 +00:00
}
2019-07-17 22:25:42 +00:00
2019-08-20 11:20:25 +00:00
// Clean up
delete ( s . messageIDs , id )
2019-02-19 12:58:42 +00:00
return nil
}
2018-09-24 18:07:34 +00:00
// CreateBundle retrieves or creates an X3DH bundle given a private key
2019-07-17 22:25:42 +00:00
func ( s * encryptor ) CreateBundle ( privateKey * ecdsa . PrivateKey , installations [ ] * multidevice . Installation ) ( * Bundle , error ) {
2019-11-23 17:57:05 +00:00
ourIdentityKeyC := crypto . CompressPubkey ( & privateKey . PublicKey )
2018-11-06 08:05:32 +00:00
2019-05-23 07:54:28 +00:00
bundleContainer , err := s . persistence . GetAnyPrivateBundle ( ourIdentityKeyC , installations )
2018-09-24 18:07:34 +00:00
if err != nil {
return nil , err
}
2019-07-17 22:25:42 +00:00
expired := bundleContainer != nil && bundleContainer . GetBundle ( ) . Timestamp < time . Now ( ) . Add ( - 1 * time . Duration ( s . config . BundleRefreshInterval ) * time . Millisecond ) . UnixNano ( )
2018-09-24 18:07:34 +00:00
// If the bundle has expired we create a new one
2019-07-17 22:25:42 +00:00
if expired {
2018-09-24 18:07:34 +00:00
// 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
2018-11-27 08:54:20 +00:00
bundleContainer , err = NewBundleContainer ( privateKey , s . config . InstallationID )
2018-09-24 18:07:34 +00:00
if err != nil {
return nil , err
}
if err = s . persistence . AddPrivateBundle ( bundleContainer ) ; err != nil {
return nil , err
}
2019-05-23 08:47:20 +00:00
return s . CreateBundle ( privateKey , installations )
2018-09-24 18:07:34 +00:00
}
// DecryptWithDH decrypts message sent with a DH key exchange, and throws away the key after decryption
2019-07-17 22:25:42 +00:00
func ( s * encryptor ) DecryptWithDH ( myIdentityKey * ecdsa . PrivateKey , theirEphemeralKey * ecdsa . PublicKey , payload [ ] byte ) ( [ ] byte , error ) {
2018-09-24 18:07:34 +00:00
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
2019-07-17 22:25:42 +00:00
func ( s * encryptor ) keyFromPassiveX3DH ( myIdentityKey * ecdsa . PrivateKey , theirIdentityKey * ecdsa . PublicKey , theirEphemeralKey * ecdsa . PublicKey , ourBundleID [ ] byte ) ( [ ] byte , error ) {
2018-09-24 18:07:34 +00:00
bundlePrivateKey , err := s . persistence . GetPrivateKeyBundle ( ourBundleID )
if err != nil {
2019-07-17 22:25:42 +00:00
s . logger . Error ( "could not get private bundle" , zap . Error ( err ) )
2018-09-24 18:07:34 +00:00
return nil , err
}
if bundlePrivateKey == nil {
2019-07-17 22:25:42 +00:00
return nil , errSessionNotFound
2018-09-24 18:07:34 +00:00
}
2019-11-23 17:57:05 +00:00
signedPreKey , err := crypto . ToECDSA ( bundlePrivateKey )
2018-09-24 18:07:34 +00:00
if err != nil {
2019-07-17 22:25:42 +00:00
s . logger . Error ( "could not convert to ecdsa" , zap . Error ( err ) )
2018-09-24 18:07:34 +00:00
return nil , err
}
key , err := PerformPassiveX3DH (
theirIdentityKey ,
signedPreKey ,
theirEphemeralKey ,
myIdentityKey ,
)
if err != nil {
2019-07-17 22:25:42 +00:00
s . logger . Error ( "could not perform passive x3dh" , zap . Error ( err ) )
2018-09-24 18:07:34 +00:00
return nil , err
}
return key , nil
}
2019-05-23 08:47:20 +00:00
// ProcessPublicBundle persists a bundle
2019-07-17 22:25:42 +00:00
func ( s * encryptor ) ProcessPublicBundle ( myIdentityKey * ecdsa . PrivateKey , b * Bundle ) error {
2019-05-23 08:47:20 +00:00
return s . persistence . AddPublicBundle ( b )
2018-09-24 18:07:34 +00:00
}
2021-09-21 15:47:04 +00:00
// 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 ) {
2018-09-24 18:07:34 +00:00
s . mutex . Lock ( )
defer s . mutex . Unlock ( )
2018-11-27 08:54:20 +00:00
msg := msgs [ s . config . InstallationID ]
2018-09-24 18:07:34 +00:00
if msg == nil {
2018-10-16 10:31:05 +00:00
msg = msgs [ noInstallationID ]
2018-09-24 18:07:34 +00:00
}
2018-10-16 10:31:05 +00:00
// We should not be sending a signal if it's coming from us, as we receive our own messages
2019-07-01 09:39:51 +00:00
if msg == nil && ! samePublicKeys ( * theirIdentityKey , myIdentityKey . PublicKey ) {
2020-11-24 12:36:52 +00:00
s . logger . Debug ( "message is coming from someone else, but not targeting our installation id" )
2018-12-05 08:22:49 +00:00
return nil , ErrDeviceNotFound
2020-11-24 12:36:52 +00:00
} else if msg == nil && theirInstallationID != s . config . InstallationID {
s . logger . Debug ( "message is coming from same public key, but different installation id" )
2019-07-01 09:39:51 +00:00
return nil , ErrNotPairedDevice
2020-11-24 12:36:52 +00:00
} else if msg == nil && theirInstallationID == s . config . InstallationID {
s . logger . Debug ( "message is coming from us and is nil" )
return nil , nil
2018-09-24 18:07:34 +00:00
}
2019-07-01 09:39:51 +00:00
2018-09-24 18:07:34 +00:00
payload := msg . GetPayload ( )
if x3dhHeader := msg . GetX3DHHeader ( ) ; x3dhHeader != nil {
bundleID := x3dhHeader . GetId ( )
2019-11-23 17:57:05 +00:00
theirEphemeralKey , err := crypto . DecompressPubkey ( x3dhHeader . GetKey ( ) )
2018-09-24 18:07:34 +00:00
if err != nil {
return nil , err
}
symmetricKey , err := s . keyFromPassiveX3DH ( myIdentityKey , theirIdentityKey , theirEphemeralKey , bundleID )
if err != nil {
return nil , err
}
2019-11-23 17:57:05 +00:00
theirIdentityKeyC := crypto . CompressPubkey ( theirIdentityKey )
2018-10-16 10:31:05 +00:00
err = s . persistence . AddRatchetInfo ( symmetricKey , theirIdentityKeyC , bundleID , nil , theirInstallationID )
2018-09-24 18:07:34 +00:00
if err != nil {
return nil , err
}
}
if drHeader := msg . GetDRHeader ( ) ; drHeader != nil {
drMessage := & dr . Message {
Header : dr . MessageHeader {
N : drHeader . GetN ( ) ,
PN : drHeader . GetPn ( ) ,
2019-11-04 10:08:22 +00:00
DH : drHeader . GetKey ( ) ,
2018-09-24 18:07:34 +00:00
} ,
Ciphertext : msg . GetPayload ( ) ,
}
2019-11-23 17:57:05 +00:00
theirIdentityKeyC := crypto . CompressPubkey ( theirIdentityKey )
2018-09-24 18:07:34 +00:00
2018-10-16 10:31:05 +00:00
drInfo , err := s . persistence . GetRatchetInfo ( drHeader . GetId ( ) , theirIdentityKeyC , theirInstallationID )
2018-09-24 18:07:34 +00:00
if err != nil {
2019-07-17 22:25:42 +00:00
s . logger . Error ( "could not get ratchet info" , zap . Error ( err ) )
2018-09-24 18:07:34 +00:00
return nil , err
}
// We mark the exchange as successful so we stop sending x3dh header
2018-10-16 10:31:05 +00:00
if err = s . persistence . RatchetInfoConfirmed ( drHeader . GetId ( ) , theirIdentityKeyC , theirInstallationID ) ; err != nil {
2019-07-17 22:25:42 +00:00
s . logger . Error ( "could not confirm ratchet info" , zap . Error ( err ) )
2018-09-24 18:07:34 +00:00
return nil , err
}
if drInfo == nil {
2019-07-17 22:25:42 +00:00
s . logger . Error ( "could not find a session" )
return nil , errSessionNotFound
2018-09-24 18:07:34 +00:00
}
2019-07-17 22:25:42 +00:00
confirmationData := & confirmationData {
2019-02-19 12:58:42 +00:00
header : & drMessage . Header ,
drInfo : drInfo ,
}
s . messageIDs [ confirmationIDString ( messageID ) ] = confirmationData
2018-09-24 18:07:34 +00:00
return s . decryptUsingDR ( theirIdentityKey , drInfo , drMessage )
}
// Try DH
if header := msg . GetDHHeader ( ) ; header != nil {
2019-11-23 17:57:05 +00:00
decompressedKey , err := crypto . DecompressPubkey ( header . GetKey ( ) )
2018-09-24 18:07:34 +00:00
if err != nil {
return nil , err
}
return s . DecryptWithDH ( myIdentityKey , decompressedKey , payload )
}
2021-09-21 15:47:04 +00:00
// Try Hash Ratchet
if header := msg . GetHRHeader ( ) ; header != nil {
2022-05-27 09:14:40 +00:00
decryptedPayload , err := s . decryptWithHR ( header . GroupId , header . KeyId , header . SeqNo , payload )
return decryptedPayload , err
2021-09-21 15:47:04 +00:00
}
2018-09-24 18:07:34 +00:00
return nil , errors . New ( "no key specified" )
}
2019-11-04 10:08:22 +00:00
func ( s * encryptor ) createNewSession ( drInfo * RatchetInfo , sk [ ] byte , keyPair crypto . DHPair ) ( dr . Session , error ) {
2018-09-24 18:07:34 +00:00
var err error
var session dr . Session
if drInfo . PrivateKey != nil {
session , err = dr . New (
drInfo . ID ,
sk ,
keyPair ,
2019-07-17 22:25:42 +00:00
s . persistence . SessionStorage ( ) ,
dr . WithKeysStorage ( s . persistence . KeysStorage ( ) ) ,
2018-11-27 08:54:20 +00:00
dr . WithMaxSkip ( s . config . MaxSkip ) ,
dr . WithMaxKeep ( s . config . MaxKeep ) ,
dr . WithMaxMessageKeysPerSession ( s . config . MaxMessageKeysPerSession ) ,
2018-09-24 18:07:34 +00:00
dr . WithCrypto ( crypto . EthereumCrypto { } ) )
} else {
session , err = dr . NewWithRemoteKey (
drInfo . ID ,
sk ,
keyPair . PubKey ,
2019-07-17 22:25:42 +00:00
s . persistence . SessionStorage ( ) ,
dr . WithKeysStorage ( s . persistence . KeysStorage ( ) ) ,
2018-11-27 08:54:20 +00:00
dr . WithMaxSkip ( s . config . MaxSkip ) ,
dr . WithMaxKeep ( s . config . MaxKeep ) ,
dr . WithMaxMessageKeysPerSession ( s . config . MaxMessageKeysPerSession ) ,
2018-09-24 18:07:34 +00:00
dr . WithCrypto ( crypto . EthereumCrypto { } ) )
}
return session , err
}
2019-07-17 22:25:42 +00:00
func ( s * encryptor ) encryptUsingDR ( theirIdentityKey * ecdsa . PublicKey , drInfo * RatchetInfo , payload [ ] byte ) ( [ ] byte , * DRHeader , error ) {
2018-09-24 18:07:34 +00:00
var err error
var session dr . Session
keyPair := crypto . DHPair {
2019-11-04 10:08:22 +00:00
PrvKey : drInfo . PrivateKey ,
PubKey : drInfo . PublicKey ,
2018-09-24 18:07:34 +00:00
}
// Load session from store first
2018-11-27 08:54:20 +00:00
session , err = s . getDRSession ( drInfo . ID )
2018-09-24 18:07:34 +00:00
if err != nil {
return nil , nil , err
}
// Create a new one
if session == nil {
2019-11-04 10:08:22 +00:00
session , err = s . createNewSession ( drInfo , drInfo . Sk , keyPair )
2018-09-24 18:07:34 +00:00
if err != nil {
return nil , nil , err
}
}
response , err := session . RatchetEncrypt ( payload , nil )
if err != nil {
return nil , nil , err
}
2019-07-17 22:25:42 +00:00
header := & DRHeader {
2018-09-24 18:07:34 +00:00
Id : drInfo . BundleID ,
Key : response . Header . DH [ : ] ,
N : response . Header . N ,
Pn : response . Header . PN ,
}
return response . Ciphertext , header , nil
}
2019-07-17 22:25:42 +00:00
func ( s * encryptor ) decryptUsingDR ( theirIdentityKey * ecdsa . PublicKey , drInfo * RatchetInfo , payload * dr . Message ) ( [ ] byte , error ) {
2018-09-24 18:07:34 +00:00
var err error
var session dr . Session
keyPair := crypto . DHPair {
2019-11-04 10:08:22 +00:00
PrvKey : drInfo . PrivateKey ,
PubKey : drInfo . PublicKey ,
2018-09-24 18:07:34 +00:00
}
2018-11-27 08:54:20 +00:00
session , err = s . getDRSession ( drInfo . ID )
2018-09-24 18:07:34 +00:00
if err != nil {
return nil , err
}
if session == nil {
2019-11-04 10:08:22 +00:00
session , err = s . createNewSession ( drInfo , drInfo . Sk , keyPair )
2018-09-24 18:07:34 +00:00
if err != nil {
return nil , err
}
}
plaintext , err := session . RatchetDecrypt ( * payload , nil )
if err != nil {
return nil , err
}
return plaintext , nil
}
2021-09-21 15:47:04 +00:00
func ( s * encryptor ) encryptWithDH ( theirIdentityKey * ecdsa . PublicKey , payload [ ] byte ) ( * EncryptedMessageProtocol , error ) {
2018-09-24 18:07:34 +00:00
symmetricKey , ourEphemeralKey , err := PerformActiveDH ( theirIdentityKey )
if err != nil {
return nil , err
}
encryptedPayload , err := crypto . EncryptSymmetric ( symmetricKey , payload )
if err != nil {
return nil , err
}
2021-09-21 15:47:04 +00:00
return & EncryptedMessageProtocol {
2019-07-17 22:25:42 +00:00
DHHeader : & DHHeader {
2019-11-23 17:57:05 +00:00
Key : crypto . CompressPubkey ( ourEphemeralKey ) ,
2018-09-24 18:07:34 +00:00
} ,
Payload : encryptedPayload ,
} , nil
}
2021-09-21 15:47:04 +00:00
func ( s * encryptor ) EncryptPayloadWithDH ( theirIdentityKey * ecdsa . PublicKey , payload [ ] byte ) ( map [ string ] * EncryptedMessageProtocol , error ) {
response := make ( map [ string ] * EncryptedMessageProtocol )
2018-10-16 10:31:05 +00:00
dmp , err := s . encryptWithDH ( theirIdentityKey , payload )
if err != nil {
return nil , err
}
response [ noInstallationID ] = dmp
return response , nil
}
2019-02-12 11:07:13 +00:00
// GetPublicBundle returns the active installations bundles for a given user
2019-07-17 22:25:42 +00:00
func ( s * encryptor ) GetPublicBundle ( theirIdentityKey * ecdsa . PublicKey , installations [ ] * multidevice . Installation ) ( * Bundle , error ) {
2019-05-23 07:54:28 +00:00
return s . persistence . GetPublicBundle ( theirIdentityKey , installations )
2019-02-12 11:07:13 +00:00
}
2021-09-21 15:47:04 +00:00
// 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 ) {
2019-07-17 22:25:42 +00:00
logger := s . logger . With (
zap . String ( "site" , "EncryptPayload" ) ,
2020-11-24 12:36:52 +00:00
zap . String ( "their-identity-key" , types . EncodeHex ( crypto . FromECDSAPub ( theirIdentityKey ) ) ) )
2019-07-17 22:25:42 +00:00
2019-05-23 08:47:20 +00:00
// Which installations we are sending the message to
var targetedInstallations [ ] * multidevice . Installation
2018-09-24 18:07:34 +00:00
s . mutex . Lock ( )
defer s . mutex . Unlock ( )
2019-05-23 08:47:20 +00:00
if len ( installations ) == 0 {
2021-09-21 15:47:04 +00:00
// We don't have any, send a message with DH
2019-07-17 22:25:42 +00:00
logger . Debug ( "no installations, sending to all devices" )
2019-05-23 08:47:20 +00:00
encryptedPayload , err := s . EncryptPayloadWithDH ( theirIdentityKey , payload )
return encryptedPayload , targetedInstallations , err
2018-09-24 18:07:34 +00:00
}
2019-11-23 17:57:05 +00:00
theirIdentityKeyC := crypto . CompressPubkey ( theirIdentityKey )
2021-09-21 15:47:04 +00:00
response := make ( map [ string ] * EncryptedMessageProtocol )
2018-10-16 10:31:05 +00:00
2019-05-23 07:54:28 +00:00
for _ , installation := range installations {
installationID := installation . ID
2019-07-17 22:25:42 +00:00
ilogger := logger . With ( zap . String ( "installation-id" , installationID ) )
ilogger . Debug ( "processing installation" )
2018-11-27 08:54:20 +00:00
if s . config . InstallationID == installationID {
2018-09-24 18:07:34 +00:00
continue
}
2019-07-17 22:25:42 +00:00
2019-05-23 08:47:20 +00:00
bundle , err := s . persistence . GetPublicBundle ( theirIdentityKey , [ ] * multidevice . Installation { installation } )
2019-02-12 11:07:13 +00:00
if err != nil {
2019-05-23 08:47:20 +00:00
return nil , nil , err
2019-02-12 11:07:13 +00:00
}
2018-09-24 18:07:34 +00:00
// See if a session is there already
drInfo , err := s . persistence . GetAnyRatchetInfo ( theirIdentityKeyC , installationID )
if err != nil {
2019-05-23 08:47:20 +00:00
return nil , nil , err
2018-09-24 18:07:34 +00:00
}
2019-05-23 08:47:20 +00:00
targetedInstallations = append ( targetedInstallations , installation )
2018-09-24 18:07:34 +00:00
if drInfo != nil {
2019-07-17 22:25:42 +00:00
ilogger . Debug ( "found DR info for installation" )
2018-09-24 18:07:34 +00:00
encryptedPayload , drHeader , err := s . encryptUsingDR ( theirIdentityKey , drInfo , payload )
if err != nil {
2019-05-23 08:47:20 +00:00
return nil , nil , err
2018-09-24 18:07:34 +00:00
}
2021-09-21 15:47:04 +00:00
dmp := EncryptedMessageProtocol {
2018-09-24 18:07:34 +00:00
Payload : encryptedPayload ,
DRHeader : drHeader ,
}
if drInfo . EphemeralKey != nil {
2019-07-17 22:25:42 +00:00
dmp . X3DHHeader = & X3DHHeader {
2018-11-06 08:05:32 +00:00
Key : drInfo . EphemeralKey ,
Id : drInfo . BundleID ,
2018-09-24 18:07:34 +00:00
}
}
response [ drInfo . InstallationID ] = & dmp
2018-10-16 10:31:05 +00:00
continue
2018-09-24 18:07:34 +00:00
}
2019-02-12 11:07:13 +00:00
theirSignedPreKeyContainer := bundle . GetSignedPreKeys ( ) [ installationID ]
// This should not be nil at this point
if theirSignedPreKeyContainer == nil {
2019-07-17 22:25:42 +00:00
ilogger . Warn ( "could not find DR info or bundle for installation" )
2019-02-12 11:07:13 +00:00
continue
}
2019-07-17 22:25:42 +00:00
ilogger . Debug ( "DR info not found, using bundle" )
2019-02-12 11:07:13 +00:00
theirSignedPreKey := theirSignedPreKeyContainer . GetSignedPreKey ( )
2018-11-06 08:05:32 +00:00
sharedKey , ourEphemeralKey , err := s . keyFromActiveX3DH ( theirIdentityKeyC , theirSignedPreKey , myIdentityKey )
2018-09-24 18:07:34 +00:00
if err != nil {
2019-05-23 08:47:20 +00:00
return nil , nil , err
2018-09-24 18:07:34 +00:00
}
2019-11-23 17:57:05 +00:00
theirIdentityKeyC := crypto . CompressPubkey ( theirIdentityKey )
ourEphemeralKeyC := crypto . CompressPubkey ( ourEphemeralKey )
2018-09-24 18:07:34 +00:00
2018-11-06 08:05:32 +00:00
err = s . persistence . AddRatchetInfo ( sharedKey , theirIdentityKeyC , theirSignedPreKey , ourEphemeralKeyC , installationID )
if err != nil {
2019-05-23 08:47:20 +00:00
return nil , nil , err
2018-11-06 08:05:32 +00:00
}
2018-09-24 18:07:34 +00:00
2019-07-17 22:25:42 +00:00
x3dhHeader := & X3DHHeader {
2018-11-06 08:05:32 +00:00
Key : ourEphemeralKeyC ,
Id : theirSignedPreKey ,
}
2018-09-24 18:07:34 +00:00
2018-11-27 08:54:20 +00:00
drInfo , err = s . persistence . GetRatchetInfo ( theirSignedPreKey , theirIdentityKeyC , installationID )
2018-11-06 08:05:32 +00:00
if err != nil {
2019-05-23 08:47:20 +00:00
return nil , nil , err
2018-11-06 08:05:32 +00:00
}
2018-09-24 18:07:34 +00:00
2018-11-06 08:05:32 +00:00
if drInfo != nil {
encryptedPayload , drHeader , err := s . encryptUsingDR ( theirIdentityKey , drInfo , payload )
2018-09-24 18:07:34 +00:00
if err != nil {
2019-05-23 08:47:20 +00:00
return nil , nil , err
2018-09-24 18:07:34 +00:00
}
2021-09-21 15:47:04 +00:00
dmp := & EncryptedMessageProtocol {
2018-11-06 08:05:32 +00:00
Payload : encryptedPayload ,
X3DHHeader : x3dhHeader ,
DRHeader : drHeader ,
2018-09-24 18:07:34 +00:00
}
2018-11-06 08:05:32 +00:00
response [ drInfo . InstallationID ] = dmp
2018-09-24 18:07:34 +00:00
}
}
2019-07-17 22:25:42 +00:00
var installationIDs [ ] string
for _ , i := range targetedInstallations {
installationIDs = append ( installationIDs , i . ID )
}
logger . Info (
"built a message" ,
zap . Strings ( "installation-ids" , installationIDs ) ,
)
2019-02-12 11:07:13 +00:00
2019-05-23 08:47:20 +00:00
return response , targetedInstallations , nil
2018-09-24 18:07:34 +00:00
}
2019-07-01 09:39:51 +00:00
2022-05-27 09:14:40 +00:00
func ( s * encryptor ) getNextHashRatchetKeyID ( groupID [ ] byte ) ( uint32 , error ) {
2021-09-21 15:47:04 +00:00
latestKeyID , err := s . persistence . GetCurrentKeyForGroup ( groupID )
if err != nil {
return 0 , err
}
currentTime := ( uint32 ) ( time . Now ( ) . UnixNano ( ) / int64 ( time . Millisecond ) )
keyIDBump := ( uint32 ) ( 10 )
if latestKeyID < currentTime {
return currentTime + keyIDBump , nil
}
return latestKeyID + 1 , nil
}
// Generates and stores a hash ratchet key given a group ID
func ( s * encryptor ) GenerateHashRatchetKey ( groupID [ ] byte ) ( uint32 , error ) {
// Randomly generate a hash ratchet key
hrKey , err := crypto . GenerateKey ( )
if err != nil {
return 0 , err
}
hrKeyBytes := crypto . FromECDSA ( hrKey )
2022-05-27 09:14:40 +00:00
keyID , err := s . getNextHashRatchetKeyID ( groupID )
2021-09-21 15:47:04 +00:00
if err != nil {
return 0 , err
}
2022-05-27 09:14:40 +00:00
err = s . persistence . SaveHashRatchetKey ( groupID , keyID , hrKeyBytes )
2021-09-21 15:47:04 +00:00
return keyID , err
}
// EncryptHashRatchetPayload returns a new EncryptedMessageProtocol with a given payload encrypted, given a group's key
func ( s * encryptor ) EncryptHashRatchetPayload ( groupID [ ] byte , keyID uint32 , payload [ ] byte ) ( map [ string ] * EncryptedMessageProtocol , error ) {
logger := s . logger . With (
zap . String ( "site" , "EncryptHashRatchetPayload" ) ,
zap . Any ( "group-id" , groupID ) ,
zap . Any ( "key-id" , keyID ) )
s . mutex . Lock ( )
defer s . mutex . Unlock ( )
logger . Debug ( "encrypting hash ratchet message" )
dmp , err := s . encryptWithHR ( groupID , keyID , payload )
response := make ( map [ string ] * EncryptedMessageProtocol )
response [ noInstallationID ] = dmp
return response , err
}
2019-07-01 09:39:51 +00:00
func samePublicKeys ( pubKey1 , pubKey2 ecdsa . PublicKey ) bool {
return pubKey1 . X . Cmp ( pubKey2 . X ) == 0 && pubKey1 . Y . Cmp ( pubKey2 . Y ) == 0
}
2021-09-21 15:47:04 +00:00
func ( s * encryptor ) encryptWithHR ( groupID [ ] byte , keyID uint32 , payload [ ] byte ) ( * EncryptedMessageProtocol , error ) {
hrCache , err := s . persistence . GetHashRatchetKeyByID ( groupID , keyID , 0 ) // Get latest seqNo
if err != nil {
return nil , err
}
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 , err
}
newSeqNo := hrCache . SeqNo + 1
err = s . persistence . SaveHashRatchetKeyHash ( groupID , keyID , hash . Bytes ( ) , newSeqNo )
if err != nil {
return nil , err
}
dmp := & EncryptedMessageProtocol {
HRHeader : & HRHeader {
2022-05-27 09:14:40 +00:00
GroupId : groupID ,
2021-09-21 15:47:04 +00:00
KeyId : keyID ,
SeqNo : newSeqNo ,
} ,
Payload : encryptedPayload ,
}
return dmp , nil
}
func ( s * encryptor ) decryptWithHR ( groupID [ ] byte , keyID uint32 , seqNo uint32 , payload [ ] byte ) ( [ ] byte , error ) {
hrCache , err := s . persistence . GetHashRatchetKeyByID ( groupID , keyID , seqNo )
if err != nil {
return nil , err
}
// 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 ( groupID , keyID , hash , i + 1 )
if err != nil {
return nil , err
}
}
}
decryptedPayload , err := crypto . DecryptSymmetric ( hash , payload )
return decryptedPayload , err
}