2018-09-24 18:07:34 +00:00
|
|
|
package chat
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/log"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
)
|
|
|
|
|
|
|
|
type ProtocolService struct {
|
2018-12-05 08:22:49 +00:00
|
|
|
log log.Logger
|
|
|
|
encryption *EncryptionService
|
|
|
|
addedBundlesHandler func([]IdentityAndIDPair)
|
|
|
|
Enabled bool
|
2018-10-16 10:31:05 +00:00
|
|
|
}
|
|
|
|
|
2018-09-24 18:07:34 +00:00
|
|
|
// NewProtocolService creates a new ProtocolService instance
|
2018-12-05 08:22:49 +00:00
|
|
|
func NewProtocolService(encryption *EncryptionService, addedBundlesHandler func([]IdentityAndIDPair)) *ProtocolService {
|
2018-09-24 18:07:34 +00:00
|
|
|
return &ProtocolService{
|
2018-12-05 08:22:49 +00:00
|
|
|
log: log.New("package", "status-go/services/sshext.chat"),
|
|
|
|
encryption: encryption,
|
|
|
|
addedBundlesHandler: addedBundlesHandler,
|
2018-09-24 18:07:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *ProtocolService) addBundleAndMarshal(myIdentityKey *ecdsa.PrivateKey, msg *ProtocolMessage) ([]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
|
|
|
|
}
|
|
|
|
|
2018-12-21 10:07:25 +00:00
|
|
|
msg.Bundles = []*Bundle{bundle}
|
2018-09-24 18:07:34 +00:00
|
|
|
|
|
|
|
// 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{
|
2018-11-27 08:54:20 +00:00
|
|
|
InstallationId: p.encryption.config.InstallationID,
|
2018-10-16 10:31:05 +00:00
|
|
|
PublicMessage: payload,
|
2018-09-24 18:07:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return p.addBundleAndMarshal(myIdentityKey, protocolMessage)
|
|
|
|
}
|
|
|
|
|
|
|
|
// BuildDirectMessage marshals a 1:1 chat message given the user identity private key, the recipient's public key, and a payload
|
2018-10-16 15:22:28 +00:00
|
|
|
func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte, theirPublicKeys ...*ecdsa.PublicKey) (map[*ecdsa.PublicKey][]byte, error) {
|
2018-09-24 18:07:34 +00:00
|
|
|
response := make(map[*ecdsa.PublicKey][]byte)
|
Change handling of skipped/deleted keys & add version (#1261)
- Skipped keys
The purpose of limiting the number of skipped keys generated is to avoid a dos
attack whereby an attacker would send a large N, forcing the device to
compute all the keys between currentN..N .
Previously the logic for handling skipped keys was:
- If in the current receiving chain there are more than maxSkip keys,
throw an error
This is problematic as in long-lived session dropped/unreceived messages starts
piling up, eventually reaching the threshold (1000 dropped/unreceived
messages).
This logic has been changed to be more inline with signals spec, and now
it is:
- If N is > currentN + maxSkip, throw an error
The purpose of limiting the number of skipped keys stored is to avoid a dos
attack whereby an attacker would force us to store a large number of
keys, filling up our storage.
Previously the logic for handling old keys was:
- Once you have maxKeep ratchet steps, delete any key from
currentRatchet - maxKeep.
This, in combination with the maxSkip implementation, capped the number of stored keys to
maxSkip * maxKeep.
The logic has been changed to:
- Keep a maximum of MaxMessageKeysPerSession
and additionally we delete any key that has a sequence number <
currentSeqNum - maxKeep
- Version
We check now the version of the bundle so that when we get a bundle from
the same installationID with a higher version, we mark the previous
bundle as expired and use the new bundle the next time a message is sent
2018-11-05 19:00:04 +00:00
|
|
|
for _, publicKey := range theirPublicKeys {
|
2018-09-24 18:07:34 +00:00
|
|
|
// 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{
|
2018-11-27 08:54:20 +00:00
|
|
|
InstallationId: p.encryption.config.InstallationID,
|
2018-10-16 10:31:05 +00:00
|
|
|
DirectMessage: encryptionResponse,
|
2018-09-24 18:07:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
payload, err := p.addBundleAndMarshal(myIdentityKey, protocolMessage)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(payload) != 0 {
|
|
|
|
response[publicKey] = payload
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2018-11-06 08:05:32 +00:00
|
|
|
// BuildPairingMessage sends a message to our own devices using DH so that it can be decrypted by any other device.
|
2018-10-16 10:31:05 +00:00
|
|
|
func (p *ProtocolService) BuildPairingMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte) ([]byte, error) {
|
|
|
|
// Encrypt payload
|
|
|
|
encryptionResponse, err := p.encryption.EncryptPayloadWithDH(&myIdentityKey.PublicKey, payload)
|
|
|
|
if err != nil {
|
|
|
|
p.log.Error("encryption-service", "error encrypting payload", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build message
|
|
|
|
protocolMessage := &ProtocolMessage{
|
2018-11-27 08:54:20 +00:00
|
|
|
InstallationId: p.encryption.config.InstallationID,
|
2018-10-16 10:31:05 +00:00
|
|
|
DirectMessage: encryptionResponse,
|
|
|
|
}
|
|
|
|
|
|
|
|
return p.addBundleAndMarshal(myIdentityKey, protocolMessage)
|
|
|
|
}
|
|
|
|
|
2018-11-06 08:05:32 +00:00
|
|
|
// ProcessPublicBundle processes a received X3DH bundle.
|
2018-10-16 10:31:05 +00:00
|
|
|
func (p *ProtocolService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *Bundle) ([]IdentityAndIDPair, error) {
|
2018-09-24 18:07:34 +00:00
|
|
|
return p.encryption.ProcessPublicBundle(myIdentityKey, bundle)
|
|
|
|
}
|
|
|
|
|
2018-11-06 08:05:32 +00:00
|
|
|
// GetBundle retrieves or creates a X3DH bundle, given a private identity key.
|
2018-09-24 18:07:34 +00:00
|
|
|
func (p *ProtocolService) GetBundle(myIdentityKey *ecdsa.PrivateKey) (*Bundle, error) {
|
|
|
|
return p.encryption.CreateBundle(myIdentityKey)
|
|
|
|
}
|
|
|
|
|
2018-11-06 08:05:32 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleMessage unmarshals a message and processes it, decrypting it if it is a 1:1 message.
|
2018-12-05 08:22:49 +00:00
|
|
|
func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, payload []byte) ([]byte, error) {
|
2018-09-24 18:07:34 +00:00
|
|
|
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, err
|
|
|
|
}
|
|
|
|
|
2018-12-21 10:07:25 +00:00
|
|
|
// Process bundle, deprecated, here for backward compatibility
|
2018-09-24 18:07:34 +00:00
|
|
|
if bundle := protocolMessage.GetBundle(); bundle != nil {
|
|
|
|
// Should we stop processing if the bundle cannot be verified?
|
2018-10-16 10:31:05 +00:00
|
|
|
addedBundles, err := p.encryption.ProcessPublicBundle(myIdentityKey, bundle)
|
2018-09-24 18:07:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-12-05 08:22:49 +00:00
|
|
|
|
|
|
|
p.addedBundlesHandler(addedBundles)
|
2018-09-24 18:07:34 +00:00
|
|
|
}
|
|
|
|
|
2018-12-21 10:07:25 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2018-09-24 18:07:34 +00:00
|
|
|
// Check if it's a public message
|
|
|
|
if publicMessage := protocolMessage.GetPublicMessage(); publicMessage != nil {
|
|
|
|
// Nothing to do, as already in cleartext
|
2018-12-05 08:22:49 +00:00
|
|
|
return publicMessage, nil
|
2018-09-24 18:07:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Decrypt message
|
|
|
|
if directMessage := protocolMessage.GetDirectMessage(); directMessage != nil {
|
2018-10-16 10:31:05 +00:00
|
|
|
message, err := p.encryption.DecryptPayload(myIdentityKey, theirPublicKey, protocolMessage.GetInstallationId(), directMessage)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-12-05 08:22:49 +00:00
|
|
|
return message, nil
|
2018-09-24 18:07:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Return error
|
|
|
|
return nil, errors.New("no payload")
|
|
|
|
}
|