mirror of
https://github.com/status-im/status-go.git
synced 2025-02-14 07:46:51 +00:00
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.
181 lines
6.0 KiB
Go
181 lines
6.0 KiB
Go
package chat
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"errors"
|
|
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/golang/protobuf/proto"
|
|
)
|
|
|
|
type ProtocolService struct {
|
|
log log.Logger
|
|
encryption *EncryptionService
|
|
addedBundlesHandler func([]IdentityAndIDPair)
|
|
Enabled bool
|
|
}
|
|
|
|
var ErrNotProtocolMessage = errors.New("Not a protocol message")
|
|
|
|
// NewProtocolService creates a new ProtocolService instance
|
|
func NewProtocolService(encryption *EncryptionService, addedBundlesHandler func([]IdentityAndIDPair)) *ProtocolService {
|
|
return &ProtocolService{
|
|
log: log.New("package", "status-go/services/sshext.chat"),
|
|
encryption: encryption,
|
|
addedBundlesHandler: addedBundlesHandler,
|
|
}
|
|
}
|
|
|
|
func (p *ProtocolService) addBundleAndMarshal(myIdentityKey *ecdsa.PrivateKey, msg *ProtocolMessage, sendSingle bool) ([]byte, error) {
|
|
// Get a bundle
|
|
bundle, err := p.encryption.CreateBundle(myIdentityKey)
|
|
if err != nil {
|
|
p.log.Error("encryption-service", "error creating bundle", err)
|
|
return nil, err
|
|
}
|
|
|
|
if sendSingle {
|
|
// DEPRECATED: This is only for backward compatibility, remove once not
|
|
// an issue anymore
|
|
msg.Bundle = bundle
|
|
} else {
|
|
msg.Bundles = []*Bundle{bundle}
|
|
}
|
|
|
|
// marshal for sending to wire
|
|
marshaledMessage, err := proto.Marshal(msg)
|
|
if err != nil {
|
|
p.log.Error("encryption-service", "error marshaling message", err)
|
|
return nil, err
|
|
}
|
|
|
|
return marshaledMessage, nil
|
|
}
|
|
|
|
// BuildPublicMessage marshals a public chat message given the user identity private key and a payload
|
|
func (p *ProtocolService) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte) ([]byte, error) {
|
|
// Build message not encrypted
|
|
protocolMessage := &ProtocolMessage{
|
|
InstallationId: p.encryption.config.InstallationID,
|
|
PublicMessage: payload,
|
|
}
|
|
|
|
return p.addBundleAndMarshal(myIdentityKey, protocolMessage, false)
|
|
}
|
|
|
|
// 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, publicKey *ecdsa.PublicKey, payload []byte) ([]byte, error) {
|
|
// Encrypt payload
|
|
encryptionResponse, err := p.encryption.EncryptPayload(publicKey, myIdentityKey, payload)
|
|
if err != nil {
|
|
p.log.Error("encryption-service", "error encrypting payload", err)
|
|
return nil, err
|
|
}
|
|
|
|
// Build message
|
|
protocolMessage := &ProtocolMessage{
|
|
InstallationId: p.encryption.config.InstallationID,
|
|
DirectMessage: encryptionResponse,
|
|
}
|
|
|
|
return p.addBundleAndMarshal(myIdentityKey, protocolMessage, true)
|
|
}
|
|
|
|
// BuildDHMessage builds a message with DH encryption so that it can be decrypted by any other device.
|
|
func (p *ProtocolService) BuildDHMessage(myIdentityKey *ecdsa.PrivateKey, destination *ecdsa.PublicKey, payload []byte) ([]byte, error) {
|
|
// Encrypt payload
|
|
encryptionResponse, err := p.encryption.EncryptPayloadWithDH(destination, payload)
|
|
if err != nil {
|
|
p.log.Error("encryption-service", "error encrypting payload", err)
|
|
return nil, err
|
|
}
|
|
|
|
// Build message
|
|
protocolMessage := &ProtocolMessage{
|
|
InstallationId: p.encryption.config.InstallationID,
|
|
DirectMessage: encryptionResponse,
|
|
}
|
|
|
|
return p.addBundleAndMarshal(myIdentityKey, protocolMessage, true)
|
|
}
|
|
|
|
// ProcessPublicBundle processes a received X3DH bundle.
|
|
func (p *ProtocolService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *Bundle) ([]IdentityAndIDPair, error) {
|
|
return p.encryption.ProcessPublicBundle(myIdentityKey, bundle)
|
|
}
|
|
|
|
// GetBundle retrieves or creates a X3DH bundle, given a private identity key.
|
|
func (p *ProtocolService) GetBundle(myIdentityKey *ecdsa.PrivateKey) (*Bundle, error) {
|
|
return p.encryption.CreateBundle(myIdentityKey)
|
|
}
|
|
|
|
// EnableInstallation enables an installation for multi-device sync.
|
|
func (p *ProtocolService) EnableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
|
return p.encryption.EnableInstallation(myIdentityKey, installationID)
|
|
}
|
|
|
|
// DisableInstallation disables an installation for multi-device sync.
|
|
func (p *ProtocolService) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
|
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.
|
|
func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, payload []byte) ([]byte, error) {
|
|
if p.encryption == nil {
|
|
return nil, errors.New("encryption service not initialized")
|
|
}
|
|
|
|
// Unmarshal message
|
|
protocolMessage := &ProtocolMessage{}
|
|
|
|
if err := proto.Unmarshal(payload, protocolMessage); err != nil {
|
|
return nil, ErrNotProtocolMessage
|
|
}
|
|
|
|
// Process bundle, deprecated, here for backward compatibility
|
|
if bundle := protocolMessage.GetBundle(); bundle != nil {
|
|
// Should we stop processing if the bundle cannot be verified?
|
|
addedBundles, err := p.encryption.ProcessPublicBundle(myIdentityKey, bundle)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.addedBundlesHandler(addedBundles)
|
|
}
|
|
|
|
// Process bundles
|
|
for _, bundle := range protocolMessage.GetBundles() {
|
|
// Should we stop processing if the bundle cannot be verified?
|
|
addedBundles, err := p.encryption.ProcessPublicBundle(myIdentityKey, bundle)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.addedBundlesHandler(addedBundles)
|
|
}
|
|
|
|
// Check if it's a public message
|
|
if publicMessage := protocolMessage.GetPublicMessage(); publicMessage != nil {
|
|
// Nothing to do, as already in cleartext
|
|
return publicMessage, nil
|
|
}
|
|
|
|
// Decrypt message
|
|
if directMessage := protocolMessage.GetDirectMessage(); directMessage != nil {
|
|
message, err := p.encryption.DecryptPayload(myIdentityKey, theirPublicKey, protocolMessage.GetInstallationId(), directMessage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return message, nil
|
|
}
|
|
|
|
// Return error
|
|
return nil, errors.New("no payload")
|
|
}
|