Add GetContactCode call, add DH flag (#1367)
This PR does a few things: 1) Add a call GetContactCode to check whether we have a bundle for a given user. 2) Add a DH flag to the API (non-breaking change), for those messages that we want to target all devices (contact-requests for example). 3) Fixes a few small issues with installations, namely if for example a messages is sent without a bundle (currently not done by any client), we still infer installation info, so that we can communicate securely and making it truly optional.
This commit is contained in:
parent
a249151d05
commit
2a4382369a
|
@ -2,6 +2,7 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -556,6 +557,35 @@ func (b *StatusBackend) CreateContactCode() (string, error) {
|
||||||
return bundle.ToBase64()
|
return bundle.ToBase64()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetContactCode return the latest contact code
|
||||||
|
func (b *StatusBackend) GetContactCode(identity string) (string, error) {
|
||||||
|
st, err := b.statusNode.ShhExtService()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyBytes, err := hex.DecodeString(identity)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, err := ethcrypto.UnmarshalPubkey(publicKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
bundle, err := st.GetPublicBundle(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bundle == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundle.ToBase64()
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessContactCode process and adds the someone else's bundle
|
// ProcessContactCode process and adds the someone else's bundle
|
||||||
func (b *StatusBackend) ProcessContactCode(contactCode string) error {
|
func (b *StatusBackend) ProcessContactCode(contactCode string) error {
|
||||||
selectedChatAccount, err := b.AccountManager().SelectedChatAccount()
|
selectedChatAccount, err := b.AccountManager().SelectedChatAccount()
|
||||||
|
|
|
@ -70,6 +70,24 @@ func ProcessContactCode(bundleString *C.char) *C.char {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get an X3DH bundle
|
||||||
|
//export GetContactCode
|
||||||
|
func GetContactCode(identityString *C.char) *C.char {
|
||||||
|
bundle, err := statusBackend.GetContactCode(C.GoString(identityString))
|
||||||
|
if err != nil {
|
||||||
|
return makeJSONResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(struct {
|
||||||
|
ContactCode string `json:"code"`
|
||||||
|
}{ContactCode: bundle})
|
||||||
|
if err != nil {
|
||||||
|
return makeJSONResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return C.CString(string(data))
|
||||||
|
}
|
||||||
|
|
||||||
//export ExtractIdentityFromContactCode
|
//export ExtractIdentityFromContactCode
|
||||||
func ExtractIdentityFromContactCode(bundleString *C.char) *C.char {
|
func ExtractIdentityFromContactCode(bundleString *C.char) *C.char {
|
||||||
bundle := C.GoString(bundleString)
|
bundle := C.GoString(bundleString)
|
||||||
|
|
|
@ -501,3 +501,21 @@ func SetMobileSignalHandler(handler SignalHandler) {
|
||||||
func SetSignalEventCallback(cb unsafe.Pointer) {
|
func SetSignalEventCallback(cb unsafe.Pointer) {
|
||||||
signal.SetSignalEventCallback(cb)
|
signal.SetSignalEventCallback(cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get an X3DH bundle
|
||||||
|
//export GetContactCode
|
||||||
|
func GetContactCode(identity string) string {
|
||||||
|
bundle, err := statusBackend.GetContactCode(identity)
|
||||||
|
if err != nil {
|
||||||
|
return makeJSONResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(struct {
|
||||||
|
ContactCode string `json:"code"`
|
||||||
|
}{ContactCode: bundle})
|
||||||
|
if err != nil {
|
||||||
|
return makeJSONResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
|
@ -422,7 +422,7 @@ func (api *PublicAPI) SendPublicMessage(ctx context.Context, msg chat.SendPublic
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendDirectMessage sends a 1:1 chat message to the underlying transport
|
// SendDirectMessage sends a 1:1 chat message to the underlying transport
|
||||||
func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg chat.SendDirectMessageRPC) ([]hexutil.Bytes, error) {
|
func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg chat.SendDirectMessageRPC) (hexutil.Bytes, error) {
|
||||||
if !api.service.pfsEnabled {
|
if !api.service.pfsEnabled {
|
||||||
return nil, ErrPFSNotEnabled
|
return nil, ErrPFSNotEnabled
|
||||||
}
|
}
|
||||||
|
@ -438,29 +438,26 @@ func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg chat.SendDirect
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is transport layer-agnostic
|
// This is transport layer-agnostic
|
||||||
protocolMessages, err := api.service.protocol.BuildDirectMessage(privateKey, msg.Payload, publicKey)
|
var protocolMessage []byte
|
||||||
|
|
||||||
|
if msg.DH {
|
||||||
|
protocolMessage, err = api.service.protocol.BuildDHMessage(privateKey, &privateKey.PublicKey, msg.Payload)
|
||||||
|
} else {
|
||||||
|
protocolMessage, err = api.service.protocol.BuildDirectMessage(privateKey, publicKey, msg.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var response []hexutil.Bytes
|
// Enrich with transport layer info
|
||||||
|
whisperMessage := chat.DirectMessageToWhisper(msg, protocolMessage)
|
||||||
|
|
||||||
for key, message := range protocolMessages {
|
// And dispatch
|
||||||
msg.PubKey = crypto.FromECDSAPub(key)
|
return api.Post(ctx, whisperMessage)
|
||||||
// Enrich with transport layer info
|
|
||||||
whisperMessage := chat.DirectMessageToWhisper(msg, message)
|
|
||||||
|
|
||||||
// And dispatch
|
|
||||||
hash, err := api.Post(ctx, whisperMessage)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response = append(response, hash)
|
|
||||||
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use SendDirectMessage with DH flag
|
||||||
// SendPairingMessage sends a 1:1 chat message to our own devices to initiate a pairing session
|
// SendPairingMessage sends a 1:1 chat message to our own devices to initiate a pairing session
|
||||||
func (api *PublicAPI) SendPairingMessage(ctx context.Context, msg chat.SendDirectMessageRPC) ([]hexutil.Bytes, error) {
|
func (api *PublicAPI) SendPairingMessage(ctx context.Context, msg chat.SendDirectMessageRPC) ([]hexutil.Bytes, error) {
|
||||||
if !api.service.pfsEnabled {
|
if !api.service.pfsEnabled {
|
||||||
|
@ -477,7 +474,7 @@ func (api *PublicAPI) SendPairingMessage(ctx context.Context, msg chat.SendDirec
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
protocolMessage, err := api.service.protocol.BuildPairingMessage(privateKey, msg.Payload)
|
protocolMessage, err := api.service.protocol.BuildDHMessage(privateKey, &privateKey.PublicKey, msg.Payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -497,57 +494,6 @@ func (api *PublicAPI) SendPairingMessage(ctx context.Context, msg chat.SendDirec
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendGroupMessage sends a group messag chat message to the underlying transport
|
|
||||||
func (api *PublicAPI) SendGroupMessage(ctx context.Context, msg chat.SendGroupMessageRPC) ([]hexutil.Bytes, error) {
|
|
||||||
if !api.service.pfsEnabled {
|
|
||||||
return nil, ErrPFSNotEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
// To be completely agnostic from whisper we should not be using whisper to store the key
|
|
||||||
privateKey, err := api.service.w.GetPrivateKey(msg.Sig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var keys []*ecdsa.PublicKey
|
|
||||||
|
|
||||||
for _, k := range msg.PubKeys {
|
|
||||||
publicKey, err := crypto.UnmarshalPubkey(k)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
keys = append(keys, publicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is transport layer-agnostic
|
|
||||||
protocolMessages, err := api.service.protocol.BuildDirectMessage(privateKey, msg.Payload, keys...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response []hexutil.Bytes
|
|
||||||
|
|
||||||
for key, message := range protocolMessages {
|
|
||||||
directMessage := chat.SendDirectMessageRPC{
|
|
||||||
PubKey: crypto.FromECDSAPub(key),
|
|
||||||
Payload: msg.Payload,
|
|
||||||
Sig: msg.Sig,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enrich with transport layer info
|
|
||||||
whisperMessage := chat.DirectMessageToWhisper(directMessage, message)
|
|
||||||
|
|
||||||
// And dispatch
|
|
||||||
hash, err := api.Post(ctx, whisperMessage)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response = append(response, hash)
|
|
||||||
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *PublicAPI) processPFSMessage(msg *whisper.Message) error {
|
func (api *PublicAPI) processPFSMessage(msg *whisper.Message) error {
|
||||||
|
|
||||||
privateKeyID := api.service.w.SelectedKeyPairID()
|
privateKeyID := api.service.w.SelectedKeyPairID()
|
||||||
|
@ -567,21 +513,26 @@ func (api *PublicAPI) processPFSMessage(msg *whisper.Message) error {
|
||||||
|
|
||||||
response, err := api.service.protocol.HandleMessage(privateKey, publicKey, msg.Payload)
|
response, err := api.service.protocol.HandleMessage(privateKey, publicKey, msg.Payload)
|
||||||
|
|
||||||
// Notify that someone tried to contact us using an invalid bundle
|
switch err {
|
||||||
if err == chat.ErrDeviceNotFound && privateKey.PublicKey != *publicKey {
|
case nil:
|
||||||
api.log.Warn("Device not found, sending signal", "err", err)
|
// Set the decrypted payload
|
||||||
keyString := fmt.Sprintf("0x%x", crypto.FromECDSAPub(publicKey))
|
msg.Payload = response
|
||||||
handler := EnvelopeSignalHandler{}
|
case chat.ErrDeviceNotFound:
|
||||||
handler.DecryptMessageFailed(keyString)
|
// Notify that someone tried to contact us using an invalid bundle
|
||||||
return nil
|
if privateKey.PublicKey != *publicKey {
|
||||||
} else if err != nil {
|
api.log.Warn("Device not found, sending signal", "err", err)
|
||||||
// Ignore errors for now as those might be non-pfs messages
|
keyString := fmt.Sprintf("0x%x", crypto.FromECDSAPub(publicKey))
|
||||||
|
handler := EnvelopeSignalHandler{}
|
||||||
|
handler.DecryptMessageFailed(keyString)
|
||||||
|
}
|
||||||
|
case chat.ErrNotProtocolMessage:
|
||||||
|
// Not using encryption, pass directly to the layer below
|
||||||
|
api.log.Debug("Not a protocol message", "err", err)
|
||||||
|
default:
|
||||||
|
// Log and pass to the client, even if failed to decrypt
|
||||||
api.log.Error("Failed handling message with error", "err", err)
|
api.log.Error("Failed handling message with error", "err", err)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add unencrypted payload
|
}
|
||||||
msg.Payload = response
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,6 +285,11 @@ func (s *EncryptionService) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, thei
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add installations with a timestamp of 0, as we don't have bundle informations
|
||||||
|
if err = s.persistence.AddInstallations(theirIdentityKeyC, 0, []string{theirInstallationID}, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// We mark the exchange as successful so we stop sending x3dh header
|
// We mark the exchange as successful so we stop sending x3dh header
|
||||||
if err = s.persistence.RatchetInfoConfirmed(drHeader.GetId(), theirIdentityKeyC, theirInstallationID); err != nil {
|
if err = s.persistence.RatchetInfoConfirmed(drHeader.GetId(), theirIdentityKeyC, theirInstallationID); err != nil {
|
||||||
s.log.Error("Could not confirm ratchet info", "err", err)
|
s.log.Error("Could not confirm ratchet info", "err", err)
|
||||||
|
@ -450,13 +455,8 @@ func (s *EncryptionService) EncryptPayloadWithDH(theirIdentityKey *ecdsa.PublicK
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptPayload returns a new DirectMessageProtocol with a given payload encrypted, given a recipient's public key and the sender private identity key
|
// GetPublicBundle returns the active installations bundles for a given user
|
||||||
// TODO: refactor this
|
func (s *EncryptionService) GetPublicBundle(theirIdentityKey *ecdsa.PublicKey) (*Bundle, error) {
|
||||||
// nolint: gocyclo
|
|
||||||
func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, myIdentityKey *ecdsa.PrivateKey, payload []byte) (map[string]*DirectMessageProtocol, error) {
|
|
||||||
s.mutex.Lock()
|
|
||||||
defer s.mutex.Unlock()
|
|
||||||
|
|
||||||
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)
|
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)
|
||||||
|
|
||||||
installationIDs, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations, theirIdentityKeyC)
|
installationIDs, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations, theirIdentityKeyC)
|
||||||
|
@ -464,25 +464,43 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get their latest bundle
|
return s.persistence.GetPublicBundle(theirIdentityKey, installationIDs)
|
||||||
theirBundle, err := s.persistence.GetPublicBundle(theirIdentityKey, installationIDs)
|
}
|
||||||
|
|
||||||
|
// EncryptPayload returns a new DirectMessageProtocol with a given payload encrypted, given a recipient's public key and the sender private identity key
|
||||||
|
// TODO: refactor this
|
||||||
|
// nolint: gocyclo
|
||||||
|
func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, myIdentityKey *ecdsa.PrivateKey, payload []byte) (map[string]*DirectMessageProtocol, error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
s.log.Debug("Sending message", "theirKey", theirIdentityKey)
|
||||||
|
|
||||||
|
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)
|
||||||
|
|
||||||
|
// Get their installationIds
|
||||||
|
installationIds, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations, theirIdentityKeyC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't have any, send a message with DH
|
// We don't have any, send a message with DH
|
||||||
if theirBundle == nil && !bytes.Equal(theirIdentityKeyC, ecrypto.CompressPubkey(&myIdentityKey.PublicKey)) {
|
if installationIds == nil && !bytes.Equal(theirIdentityKeyC, ecrypto.CompressPubkey(&myIdentityKey.PublicKey)) {
|
||||||
return s.EncryptPayloadWithDH(theirIdentityKey, payload)
|
return s.EncryptPayloadWithDH(theirIdentityKey, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
response := make(map[string]*DirectMessageProtocol)
|
response := make(map[string]*DirectMessageProtocol)
|
||||||
|
|
||||||
for installationID, signedPreKeyContainer := range theirBundle.GetSignedPreKeys() {
|
for _, installationID := range installationIds {
|
||||||
|
s.log.Debug("Processing installation", "installationID", installationID)
|
||||||
if s.config.InstallationID == installationID {
|
if s.config.InstallationID == installationID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
bundle, err := s.persistence.GetPublicBundle(theirIdentityKey, []string{installationID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
theirSignedPreKey := signedPreKeyContainer.GetSignedPreKey()
|
|
||||||
// See if a session is there already
|
// See if a session is there already
|
||||||
drInfo, err := s.persistence.GetAnyRatchetInfo(theirIdentityKeyC, installationID)
|
drInfo, err := s.persistence.GetAnyRatchetInfo(theirIdentityKeyC, installationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -490,6 +508,7 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
|
||||||
}
|
}
|
||||||
|
|
||||||
if drInfo != nil {
|
if drInfo != nil {
|
||||||
|
s.log.Debug("Found DR info", "installationID", installationID)
|
||||||
encryptedPayload, drHeader, err := s.encryptUsingDR(theirIdentityKey, drInfo, payload)
|
encryptedPayload, drHeader, err := s.encryptUsingDR(theirIdentityKey, drInfo, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -511,6 +530,18 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
theirSignedPreKeyContainer := bundle.GetSignedPreKeys()[installationID]
|
||||||
|
|
||||||
|
// This should not be nil at this point
|
||||||
|
if theirSignedPreKeyContainer == nil {
|
||||||
|
s.log.Warn("Could not find either a ratchet info or a bundle for installationId", "installationID", installationID)
|
||||||
|
continue
|
||||||
|
|
||||||
|
}
|
||||||
|
s.log.Debug("DR info not found, using bundle", "installationID", installationID)
|
||||||
|
|
||||||
|
theirSignedPreKey := theirSignedPreKeyContainer.GetSignedPreKey()
|
||||||
|
|
||||||
sharedKey, ourEphemeralKey, err := s.keyFromActiveX3DH(theirIdentityKeyC, theirSignedPreKey, myIdentityKey)
|
sharedKey, ourEphemeralKey, err := s.keyFromActiveX3DH(theirIdentityKeyC, theirSignedPreKey, myIdentityKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -549,5 +580,7 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.log.Debug("Built message", "theirKey", theirIdentityKey)
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ type ProtocolService struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrNotProtocolMessage = errors.New("Not a protocol message")
|
||||||
|
|
||||||
// NewProtocolService creates a new ProtocolService instance
|
// NewProtocolService creates a new ProtocolService instance
|
||||||
func NewProtocolService(encryption *EncryptionService, addedBundlesHandler func([]IdentityAndIDPair)) *ProtocolService {
|
func NewProtocolService(encryption *EncryptionService, addedBundlesHandler func([]IdentityAndIDPair)) *ProtocolService {
|
||||||
return &ProtocolService{
|
return &ProtocolService{
|
||||||
|
@ -62,38 +64,27 @@ func (p *ProtocolService) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, pa
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildDirectMessage marshals a 1:1 chat message given the user identity private key, the recipient's public key, and a payload
|
// BuildDirectMessage marshals a 1:1 chat message given the user identity private key, the recipient's public key, and a payload
|
||||||
func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte, theirPublicKeys ...*ecdsa.PublicKey) (map[*ecdsa.PublicKey][]byte, error) {
|
func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, payload []byte) ([]byte, error) {
|
||||||
response := make(map[*ecdsa.PublicKey][]byte)
|
// Encrypt payload
|
||||||
for _, publicKey := range theirPublicKeys {
|
encryptionResponse, err := p.encryption.EncryptPayload(publicKey, myIdentityKey, payload)
|
||||||
// Encrypt payload
|
if err != nil {
|
||||||
encryptionResponse, err := p.encryption.EncryptPayload(publicKey, myIdentityKey, payload)
|
p.log.Error("encryption-service", "error encrypting payload", err)
|
||||||
if err != nil {
|
return nil, err
|
||||||
p.log.Error("encryption-service", "error encrypting payload", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build message
|
|
||||||
protocolMessage := &ProtocolMessage{
|
|
||||||
InstallationId: p.encryption.config.InstallationID,
|
|
||||||
DirectMessage: encryptionResponse,
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, err := p.addBundleAndMarshal(myIdentityKey, protocolMessage, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(payload) != 0 {
|
|
||||||
response[publicKey] = payload
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return response, nil
|
|
||||||
|
// Build message
|
||||||
|
protocolMessage := &ProtocolMessage{
|
||||||
|
InstallationId: p.encryption.config.InstallationID,
|
||||||
|
DirectMessage: encryptionResponse,
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.addBundleAndMarshal(myIdentityKey, protocolMessage, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildPairingMessage sends a message to our own devices using DH so that it can be decrypted by any other device.
|
// BuildDHMessage builds a message with DH encryption so that it can be decrypted by any other device.
|
||||||
func (p *ProtocolService) BuildPairingMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte) ([]byte, error) {
|
func (p *ProtocolService) BuildDHMessage(myIdentityKey *ecdsa.PrivateKey, destination *ecdsa.PublicKey, payload []byte) ([]byte, error) {
|
||||||
// Encrypt payload
|
// Encrypt payload
|
||||||
encryptionResponse, err := p.encryption.EncryptPayloadWithDH(&myIdentityKey.PublicKey, payload)
|
encryptionResponse, err := p.encryption.EncryptPayloadWithDH(destination, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.log.Error("encryption-service", "error encrypting payload", err)
|
p.log.Error("encryption-service", "error encrypting payload", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -128,6 +119,11 @@ func (p *ProtocolService) DisableInstallation(myIdentityKey *ecdsa.PublicKey, in
|
||||||
return p.encryption.DisableInstallation(myIdentityKey, installationID)
|
return p.encryption.DisableInstallation(myIdentityKey, installationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPublicBundle retrieves a public bundle given an identity
|
||||||
|
func (p *ProtocolService) GetPublicBundle(theirIdentityKey *ecdsa.PublicKey) (*Bundle, error) {
|
||||||
|
return p.encryption.GetPublicBundle(theirIdentityKey)
|
||||||
|
}
|
||||||
|
|
||||||
// HandleMessage unmarshals a message and processes it, decrypting it if it is a 1:1 message.
|
// HandleMessage unmarshals a message and processes it, decrypting it if it is a 1:1 message.
|
||||||
func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, payload []byte) ([]byte, error) {
|
func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, payload []byte) ([]byte, error) {
|
||||||
if p.encryption == nil {
|
if p.encryption == nil {
|
||||||
|
@ -138,7 +134,7 @@ func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPu
|
||||||
protocolMessage := &ProtocolMessage{}
|
protocolMessage := &ProtocolMessage{}
|
||||||
|
|
||||||
if err := proto.Unmarshal(payload, protocolMessage); err != nil {
|
if err := proto.Unmarshal(payload, protocolMessage); err != nil {
|
||||||
return nil, err
|
return nil, ErrNotProtocolMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process bundle, deprecated, here for backward compatibility
|
// Process bundle, deprecated, here for backward compatibility
|
||||||
|
|
|
@ -80,13 +80,12 @@ func (s *ProtocolServiceTestSuite) TestBuildDirectMessage() {
|
||||||
})
|
})
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
marshaledMsg, err := s.alice.BuildDirectMessage(aliceKey, payload, &bobKey.PublicKey, &aliceKey.PublicKey)
|
marshaledMsg, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload)
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
s.NotNil(marshaledMsg, "It creates a message")
|
s.NotNil(marshaledMsg, "It creates a message")
|
||||||
s.NotNil(marshaledMsg[&aliceKey.PublicKey], "It creates a single message")
|
|
||||||
|
|
||||||
unmarshaledMsg := &ProtocolMessage{}
|
unmarshaledMsg := &ProtocolMessage{}
|
||||||
err = proto.Unmarshal(marshaledMsg[&bobKey.PublicKey], unmarshaledMsg)
|
err = proto.Unmarshal(marshaledMsg, unmarshaledMsg)
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
s.NotNilf(unmarshaledMsg.GetBundle(), "It adds a bundle to the message")
|
s.NotNilf(unmarshaledMsg.GetBundle(), "It adds a bundle to the message")
|
||||||
|
|
||||||
|
@ -116,12 +115,12 @@ func (s *ProtocolServiceTestSuite) TestBuildAndReadDirectMessage() {
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
// Message is sent with DH
|
// Message is sent with DH
|
||||||
marshaledMsg, err := s.alice.BuildDirectMessage(aliceKey, marshaledPayload, &bobKey.PublicKey)
|
marshaledMsg, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, marshaledPayload)
|
||||||
|
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
// Bob is able to decrypt the message
|
// Bob is able to decrypt the message
|
||||||
unmarshaledMsg, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, marshaledMsg[&bobKey.PublicKey])
|
unmarshaledMsg, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, marshaledMsg)
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
s.NotNil(unmarshaledMsg)
|
s.NotNil(unmarshaledMsg)
|
||||||
|
|
|
@ -20,11 +20,5 @@ type SendDirectMessageRPC struct {
|
||||||
Chat string
|
Chat string
|
||||||
Payload hexutil.Bytes
|
Payload hexutil.Bytes
|
||||||
PubKey hexutil.Bytes
|
PubKey hexutil.Bytes
|
||||||
}
|
DH bool
|
||||||
|
|
||||||
// SendGroupMessageRPC represents the RPC payload for the SendGroupMessage RPC method
|
|
||||||
type SendGroupMessageRPC struct {
|
|
||||||
Sig string
|
|
||||||
Payload hexutil.Bytes
|
|
||||||
PubKeys []hexutil.Bytes
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -983,11 +983,13 @@ func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We update timestamp if present without changing enabled
|
// We update timestamp if present without changing enabled, only if this is a new bundle
|
||||||
if err != sql.ErrNoRows {
|
if err != sql.ErrNoRows {
|
||||||
stmt, err = tx.Prepare(`UPDATE installations
|
stmt, err = tx.Prepare(`UPDATE installations
|
||||||
SET timestamp = ?, enabled = ?
|
SET timestamp = ?, enabled = ?
|
||||||
WHERE identity = ? AND installation_id = ?`)
|
WHERE identity = ?
|
||||||
|
AND installation_id = ?
|
||||||
|
AND timestamp < ?`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -997,6 +999,7 @@ func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64,
|
||||||
oldEnabled,
|
oldEnabled,
|
||||||
identity,
|
identity,
|
||||||
installationID,
|
installationID,
|
||||||
|
timestamp,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -204,6 +204,14 @@ func (s *Service) EnableInstallation(myIdentityKey *ecdsa.PublicKey, installatio
|
||||||
return s.protocol.EnableInstallation(myIdentityKey, installationID)
|
return s.protocol.EnableInstallation(myIdentityKey, installationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetPublicBundle(identityKey *ecdsa.PublicKey) (*chat.Bundle, error) {
|
||||||
|
if s.protocol == nil {
|
||||||
|
return nil, errProtocolNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.protocol.GetPublicBundle(identityKey)
|
||||||
|
}
|
||||||
|
|
||||||
// DisableInstallation disables an installation for multi-device sync.
|
// DisableInstallation disables an installation for multi-device sync.
|
||||||
func (s *Service) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
func (s *Service) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
||||||
if s.protocol == nil {
|
if s.protocol == nil {
|
||||||
|
|
Loading…
Reference in New Issue