mirror of
https://github.com/status-im/status-go.git
synced 2025-01-09 22:26:30 +00:00
81d8ca82a2
Currently PFS messages are decrypted and therefore modified before being passed to the client. This make IDs computation difficult, as we pass the whole object to the client and expect the object be passed back once confirmed. This changes the behavior allowing confirmation by ID, which is passed to the client instead of the raw object. This is a breaking change, but status-react is already forward compatible.
186 lines
6.3 KiB
Go
186 lines
6.3 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)
|
|
}
|
|
|
|
// ConfirmMessagesProcessed confirms and deletes message keys for the given messages
|
|
func (p *ProtocolService) ConfirmMessagesProcessed(messageIDs [][]byte) error {
|
|
return p.encryption.ConfirmMessagesProcessed(messageIDs)
|
|
}
|
|
|
|
// 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, messageID []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, messageID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return message, nil
|
|
}
|
|
|
|
// Return error
|
|
return nil, errors.New("no payload")
|
|
}
|