Skip to content

Commit

Permalink
Add bundles.added signal & pairing endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
cammellos committed Oct 15, 2018
1 parent 4d5f808 commit ae3343a
Show file tree
Hide file tree
Showing 21 changed files with 437 additions and 145 deletions.
2 changes: 1 addition & 1 deletion api/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ func (b *StatusBackend) ProcessContactCode(contactCode string) error {
return err
}

if err := st.ProcessPublicBundle(selectedAccount.AccountKey.PrivateKey, bundle); err != nil {
if _, err := st.ProcessPublicBundle(selectedAccount.AccountKey.PrivateKey, bundle); err != nil {
b.log.Error("error adding bundle", "err", err)
return err
}
Expand Down
72 changes: 58 additions & 14 deletions services/shhext/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,11 @@ func (api *PublicAPI) SendPublicMessage(ctx context.Context, msg chat.SendPublic
}

// Enrich with transport layer info
whisperMessage := chat.PublicMessageToWhisper(&msg, protocolMessage)
whisperMessage := chat.PublicMessageToWhisper(msg, protocolMessage)
whisperMessage.SymKeyID = symKeyID

// And dispatch
return api.Post(ctx, *whisperMessage)
return api.Post(ctx, whisperMessage)
}

// SendDirectMessage sends a 1:1 chat message to the underlying transport
Expand Down Expand Up @@ -267,10 +267,10 @@ func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg chat.SendDirect
for key, message := range protocolMessages {
msg.PubKey = crypto.FromECDSAPub(key)
// Enrich with transport layer info
whisperMessage := chat.DirectMessageToWhisper(&msg, message)
whisperMessage := chat.DirectMessageToWhisper(msg, message)

// And dispatch
hash, err := api.Post(ctx, *whisperMessage)
hash, err := api.Post(ctx, whisperMessage)
if err != nil {
return nil, err
}
Expand All @@ -280,6 +280,42 @@ func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg chat.SendDirect
return response, nil
}

// 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) {
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
}

msg.PubKey = crypto.FromECDSAPub(&privateKey.PublicKey)
if err != nil {
return nil, err
}

protocolMessage, err := api.service.protocol.BuildPairingMessage(privateKey, msg.Payload)
if err != nil {
return nil, err
}

var response []hexutil.Bytes

// Enrich with transport layer info
whisperMessage := chat.DirectMessageToWhisper(msg, protocolMessage)

// And dispatch
hash, err := api.Post(ctx, whisperMessage)
if err != nil {
return nil, err
}
response = append(response, hash)

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 {
Expand Down Expand Up @@ -318,10 +354,10 @@ func (api *PublicAPI) SendGroupMessage(ctx context.Context, msg chat.SendGroupMe
}

// Enrich with transport layer info
whisperMessage := chat.DirectMessageToWhisper(&directMessage, message)
whisperMessage := chat.DirectMessageToWhisper(directMessage, message)

// And dispatch
hash, err := api.Post(ctx, *whisperMessage)
hash, err := api.Post(ctx, whisperMessage)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -355,26 +391,34 @@ func (api *PublicAPI) processPFSMessage(msg *whisper.Message) error {
}
}

payload, err := api.service.protocol.HandleMessage(privateKey, publicKey, msg.Payload)
response, err := api.service.protocol.HandleMessage(privateKey, publicKey, msg.Payload)

if err != nil {
api.log.Error("Failed handling message with error", "err", err)
}
handler := EnvelopeSignalHandler{}

// Notify that someone tried to contact us using an invalid bundle
if err == chat.ErrSessionNotFound {
api.log.Warn("Session not found, sending signal", "err", err)
keyString := fmt.Sprintf("0x%x", crypto.FromECDSAPub(publicKey))
handler := EnvelopeSignalHandler{}
handler.DecryptMessageFailed(keyString)
return nil
} else if err != nil {
// Ignore errors for now as those might be non-pfs messages
api.log.Error("Failed handling message with error", "err", err)
return nil
}

// Ignore errors for now
if err == nil {
msg.Payload = payload
// Add unencrypted payload
msg.Payload = response.Message

// Notify of added bundles
if response.AddedBundles != nil {
for _, bundle := range response.AddedBundles {
handler.BundleAdded(bundle[0], bundle[1])
}
}
return nil

return nil
}

// -----
Expand Down
64 changes: 44 additions & 20 deletions services/shhext/chat/encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import (

var ErrSessionNotFound = errors.New("session not found")

// If we have no bundles, we use a constant so that the message can reach any device
const noInstallationID = "none"

// EncryptionService defines a service that is responsible for the encryption aspect of the protocol
type EncryptionService struct {
log log.Logger
Expand All @@ -26,6 +29,8 @@ type EncryptionService struct {
mutex sync.Mutex
}

type IdentityAndIDPair [2]string

// NewEncryptionService creates a new EncryptionService instance
func NewEncryptionService(p PersistenceService, installationID string) *EncryptionService {
logger := log.New("package", "status-go/services/sshext.chat")
Expand Down Expand Up @@ -129,26 +134,40 @@ func (s *EncryptionService) keyFromPassiveX3DH(myIdentityKey *ecdsa.PrivateKey,
return key, nil
}

// ProcessPublicBundle persists a bundle
func (s *EncryptionService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, b *Bundle) error {
// ProcessPublicBundle persists a bundle and returns a list of tuples identity/installationID
func (s *EncryptionService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, b *Bundle) ([]IdentityAndIDPair, error) {
// Make sure the bundle belongs to who signed it
err := VerifyBundle(b)
identity, err := ExtractIdentity(b)
if err != nil {
return err
return nil, err
}
signedPreKeys := b.GetSignedPreKeys()
response := make([]IdentityAndIDPair, len(signedPreKeys))

if err = s.persistence.AddPublicBundle(b); err != nil {
return nil, err
}

index := 0
for installationID := range signedPreKeys {
response[index] = IdentityAndIDPair{identity, installationID}
index++
}
return s.persistence.AddPublicBundle(b)

return response, nil
}

// DecryptPayload decrypts the payload of a DirectMessageProtocol, given an identity private key and the sender's public key
func (s *EncryptionService) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, theirIdentityKey *ecdsa.PublicKey, msgs map[string]*DirectMessageProtocol) ([]byte, error) {
func (s *EncryptionService) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, theirIdentityKey *ecdsa.PublicKey, theirInstallationID string, msgs map[string]*DirectMessageProtocol) ([]byte, error) {
s.mutex.Lock()
defer s.mutex.Unlock()

msg := msgs[s.installationID]
if msg == nil {
msg = msgs["none"]
msg = msgs[noInstallationID]
}

// We should not be sending a signal if it's coming from us, as we receive our own messages
if msg == nil {
return nil, ErrSessionNotFound
}
Expand All @@ -168,7 +187,7 @@ func (s *EncryptionService) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, thei
}

theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)
err = s.persistence.AddRatchetInfo(symmetricKey, theirIdentityKeyC, bundleID, nil, x3dhHeader.GetInstallationId())
err = s.persistence.AddRatchetInfo(symmetricKey, theirIdentityKeyC, bundleID, nil, theirInstallationID)
if err != nil {
return nil, err
}
Expand All @@ -189,14 +208,14 @@ func (s *EncryptionService) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, thei

theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)

drInfo, err := s.persistence.GetRatchetInfo(drHeader.GetId(), theirIdentityKeyC)
drInfo, err := s.persistence.GetRatchetInfo(drHeader.GetId(), theirIdentityKeyC, theirInstallationID)
if err != nil {
s.log.Error("Could not get ratchet info", "err", err)
return nil, err
}

// We mark the exchange as successful so we stop sending x3dh header
if err = s.persistence.RatchetInfoConfirmed(drHeader.GetId(), theirIdentityKeyC); err != nil {
if err = s.persistence.RatchetInfoConfirmed(drHeader.GetId(), theirIdentityKeyC, theirInstallationID); err != nil {
s.log.Error("Could not confirm ratchet info", "err", err)
return nil, err
}
Expand Down Expand Up @@ -354,14 +373,24 @@ func (s *EncryptionService) encryptWithDH(theirIdentityKey *ecdsa.PublicKey, pay
}, nil
}

func (s *EncryptionService) EncryptPayloadWithDH(theirIdentityKey *ecdsa.PublicKey, payload []byte) (map[string]*DirectMessageProtocol, error) {
response := make(map[string]*DirectMessageProtocol)
dmp, err := s.encryptWithDH(theirIdentityKey, payload)
if err != nil {
return nil, err
}

response[noInstallationID] = dmp
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
// 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()

response := make(map[string]*DirectMessageProtocol)
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)

// Get their latest bundle
Expand All @@ -372,15 +401,11 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my

// We don't have any, send a message with DH
if theirBundle == nil && !bytes.Equal(theirIdentityKeyC, ecrypto.CompressPubkey(&myIdentityKey.PublicKey)) {
dmp, err := s.encryptWithDH(theirIdentityKey, payload)
if err != nil {
return nil, err
}

response["none"] = dmp
return response, nil
return s.EncryptPayloadWithDH(theirIdentityKey, payload)
}

response := make(map[string]*DirectMessageProtocol)

for installationID, signedPreKeyContainer := range theirBundle.GetSignedPreKeys() {
if s.installationID == installationID {
continue
Expand Down Expand Up @@ -413,8 +438,7 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
}

response[drInfo.InstallationID] = &dmp

return response, nil
continue
}

// check if a bundle is there
Expand Down
Loading

0 comments on commit ae3343a

Please sign in to comment.