Add bundles.added signal & pairing endpoint (#1237)

This commit is contained in:
Andrea Maria Piana 2018-10-16 12:31:05 +02:00 committed by GitHub
parent 659d79277b
commit e4ba365b8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 441 additions and 145 deletions

View File

@ -483,7 +483,7 @@ func (b *StatusBackend) ProcessContactCode(contactCode string) error {
return err 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) b.log.Error("error adding bundle", "err", err)
return err return err
} }

View File

@ -237,11 +237,11 @@ func (api *PublicAPI) SendPublicMessage(ctx context.Context, msg chat.SendPublic
} }
// Enrich with transport layer info // Enrich with transport layer info
whisperMessage := chat.PublicMessageToWhisper(&msg, protocolMessage) whisperMessage := chat.PublicMessageToWhisper(msg, protocolMessage)
whisperMessage.SymKeyID = symKeyID whisperMessage.SymKeyID = symKeyID
// And dispatch // And dispatch
return api.Post(ctx, *whisperMessage) return api.Post(ctx, whisperMessage)
} }
// SendDirectMessage sends a 1:1 chat message to the underlying transport // SendDirectMessage sends a 1:1 chat message to the underlying transport
@ -272,10 +272,10 @@ func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg chat.SendDirect
for key, message := range protocolMessages { for key, message := range protocolMessages {
msg.PubKey = crypto.FromECDSAPub(key) msg.PubKey = crypto.FromECDSAPub(key)
// Enrich with transport layer info // Enrich with transport layer info
whisperMessage := chat.DirectMessageToWhisper(&msg, message) whisperMessage := chat.DirectMessageToWhisper(msg, message)
// And dispatch // And dispatch
hash, err := api.Post(ctx, *whisperMessage) hash, err := api.Post(ctx, whisperMessage)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -285,6 +285,42 @@ func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg chat.SendDirect
return response, nil 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 // SendGroupMessage sends a group messag chat message to the underlying transport
func (api *PublicAPI) SendGroupMessage(ctx context.Context, msg chat.SendGroupMessageRPC) ([]hexutil.Bytes, error) { func (api *PublicAPI) SendGroupMessage(ctx context.Context, msg chat.SendGroupMessageRPC) ([]hexutil.Bytes, error) {
if !api.service.pfsEnabled { if !api.service.pfsEnabled {
@ -323,10 +359,10 @@ func (api *PublicAPI) SendGroupMessage(ctx context.Context, msg chat.SendGroupMe
} }
// Enrich with transport layer info // Enrich with transport layer info
whisperMessage := chat.DirectMessageToWhisper(&directMessage, message) whisperMessage := chat.DirectMessageToWhisper(directMessage, message)
// And dispatch // And dispatch
hash, err := api.Post(ctx, *whisperMessage) hash, err := api.Post(ctx, whisperMessage)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -360,11 +396,9 @@ 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 { handler := EnvelopeSignalHandler{}
api.log.Error("Failed handling message with error", "err", err)
}
// Notify that someone tried to contact us using an invalid bundle // Notify that someone tried to contact us using an invalid bundle
if err == chat.ErrSessionNotFound { if err == chat.ErrSessionNotFound {
@ -372,14 +406,24 @@ func (api *PublicAPI) processPFSMessage(msg *whisper.Message) error {
keyString := fmt.Sprintf("0x%x", crypto.FromECDSAPub(publicKey)) keyString := fmt.Sprintf("0x%x", crypto.FromECDSAPub(publicKey))
handler := EnvelopeSignalHandler{} handler := EnvelopeSignalHandler{}
handler.DecryptMessageFailed(keyString) 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 // Add unencrypted payload
if err == nil { msg.Payload = response.Message
msg.Payload = payload
// Notify of added bundles
if response.AddedBundles != nil {
for _, bundle := range response.AddedBundles {
handler.BundleAdded(bundle[0], bundle[1])
}
} }
return nil return nil
} }
// ----- // -----

View File

@ -18,6 +18,9 @@ import (
var ErrSessionNotFound = errors.New("session not found") 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 // EncryptionService defines a service that is responsible for the encryption aspect of the protocol
type EncryptionService struct { type EncryptionService struct {
log log.Logger log log.Logger
@ -26,6 +29,8 @@ type EncryptionService struct {
mutex sync.Mutex mutex sync.Mutex
} }
type IdentityAndIDPair [2]string
// NewEncryptionService creates a new EncryptionService instance // NewEncryptionService creates a new EncryptionService instance
func NewEncryptionService(p PersistenceService, installationID string) *EncryptionService { func NewEncryptionService(p PersistenceService, installationID string) *EncryptionService {
logger := log.New("package", "status-go/services/sshext.chat") logger := log.New("package", "status-go/services/sshext.chat")
@ -129,26 +134,40 @@ func (s *EncryptionService) keyFromPassiveX3DH(myIdentityKey *ecdsa.PrivateKey,
return key, nil return key, nil
} }
// ProcessPublicBundle persists a bundle // ProcessPublicBundle persists a bundle and returns a list of tuples identity/installationID
func (s *EncryptionService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, b *Bundle) error { func (s *EncryptionService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, b *Bundle) ([]IdentityAndIDPair, error) {
// Make sure the bundle belongs to who signed it // Make sure the bundle belongs to who signed it
err := VerifyBundle(b) identity, err := ExtractIdentity(b)
if err != nil { if err != nil {
return err return nil, err
} }
return s.persistence.AddPublicBundle(b) 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 response, nil
} }
// DecryptPayload decrypts the payload of a DirectMessageProtocol, given an identity private key and the sender's public key // 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() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
msg := msgs[s.installationID] msg := msgs[s.installationID]
if msg == nil { 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 { if msg == nil {
return nil, ErrSessionNotFound return nil, ErrSessionNotFound
} }
@ -168,7 +187,7 @@ func (s *EncryptionService) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, thei
} }
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey) 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 { if err != nil {
return nil, err return nil, err
} }
@ -189,14 +208,14 @@ func (s *EncryptionService) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, thei
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey) theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)
drInfo, err := s.persistence.GetRatchetInfo(drHeader.GetId(), theirIdentityKeyC) drInfo, err := s.persistence.GetRatchetInfo(drHeader.GetId(), theirIdentityKeyC, theirInstallationID)
if err != nil { if err != nil {
s.log.Error("Could not get ratchet info", "err", err) s.log.Error("Could not get ratchet info", "err", err)
return nil, err 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); 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)
return nil, err return nil, err
} }
@ -354,6 +373,17 @@ func (s *EncryptionService) encryptWithDH(theirIdentityKey *ecdsa.PublicKey, pay
}, nil }, 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 // 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 // TODO: refactor this
// nolint: gocyclo // nolint: gocyclo
@ -361,7 +391,6 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
response := make(map[string]*DirectMessageProtocol)
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey) theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)
// Get their latest bundle // Get their latest bundle
@ -372,15 +401,11 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
// 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 theirBundle == nil && !bytes.Equal(theirIdentityKeyC, ecrypto.CompressPubkey(&myIdentityKey.PublicKey)) {
dmp, err := s.encryptWithDH(theirIdentityKey, payload) return s.EncryptPayloadWithDH(theirIdentityKey, payload)
if err != nil {
return nil, err
}
response["none"] = dmp
return response, nil
} }
response := make(map[string]*DirectMessageProtocol)
for installationID, signedPreKeyContainer := range theirBundle.GetSignedPreKeys() { for installationID, signedPreKeyContainer := range theirBundle.GetSignedPreKeys() {
if s.installationID == installationID { if s.installationID == installationID {
continue continue
@ -413,8 +438,7 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
} }
response[drInfo.InstallationID] = &dmp response[drInfo.InstallationID] = &dmp
continue
return response, nil
} }
// check if a bundle is there // check if a bundle is there

View File

@ -289,7 +289,7 @@ type X3DHHeader struct {
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
// Used bundle's signed prekey // Used bundle's signed prekey
Id []byte `protobuf:"bytes,4,opt,name=id,proto3" json:"id,omitempty"` Id []byte `protobuf:"bytes,4,opt,name=id,proto3" json:"id,omitempty"`
// The device id // DEPRECATED: The device id
InstallationId string `protobuf:"bytes,3,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` InstallationId string `protobuf:"bytes,3,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
@ -343,9 +343,9 @@ func (m *X3DHHeader) GetInstallationId() string {
// Direct message value // Direct message value
type DirectMessageProtocol struct { type DirectMessageProtocol struct {
X3DHHeader *X3DHHeader `protobuf:"bytes,1,opt,name=X3DH_header,json=x3DHHeader,proto3" json:"X3DH_header,omitempty"` X3DHHeader *X3DHHeader `protobuf:"bytes,1,opt,name=X3DH_header,json=X3DHHeader,proto3" json:"X3DH_header,omitempty"`
DRHeader *DRHeader `protobuf:"bytes,2,opt,name=DR_header,json=dRHeader,proto3" json:"DR_header,omitempty"` DRHeader *DRHeader `protobuf:"bytes,2,opt,name=DR_header,json=DRHeader,proto3" json:"DR_header,omitempty"`
DHHeader *DHHeader `protobuf:"bytes,101,opt,name=DH_header,json=dHHeader,proto3" json:"DH_header,omitempty"` DHHeader *DHHeader `protobuf:"bytes,101,opt,name=DH_header,json=DHHeader,proto3" json:"DH_header,omitempty"`
// Encrypted payload // Encrypted payload
Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
@ -409,6 +409,8 @@ func (m *DirectMessageProtocol) GetPayload() []byte {
type ProtocolMessage struct { type ProtocolMessage struct {
// An optional bundle is exchanged with each message // An optional bundle is exchanged with each message
Bundle *Bundle `protobuf:"bytes,1,opt,name=bundle,proto3" json:"bundle,omitempty"` Bundle *Bundle `protobuf:"bytes,1,opt,name=bundle,proto3" json:"bundle,omitempty"`
// The device id of the sender
InstallationId string `protobuf:"bytes,2,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"`
// One to one message, encrypted, indexed by installation_id // One to one message, encrypted, indexed by installation_id
DirectMessage map[string]*DirectMessageProtocol `protobuf:"bytes,101,rep,name=direct_message,json=directMessage,proto3" json:"direct_message,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` DirectMessage map[string]*DirectMessageProtocol `protobuf:"bytes,101,rep,name=direct_message,json=directMessage,proto3" json:"direct_message,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// Public chats, not encrypted // Public chats, not encrypted
@ -449,6 +451,13 @@ func (m *ProtocolMessage) GetBundle() *Bundle {
return nil return nil
} }
func (m *ProtocolMessage) GetInstallationId() string {
if m != nil {
return m.InstallationId
}
return ""
}
func (m *ProtocolMessage) GetDirectMessage() map[string]*DirectMessageProtocol { func (m *ProtocolMessage) GetDirectMessage() map[string]*DirectMessageProtocol {
if m != nil { if m != nil {
return m.DirectMessage return m.DirectMessage
@ -479,39 +488,39 @@ func init() {
func init() { proto.RegisterFile("encryption.proto", fileDescriptor_8293a649ce9418c6) } func init() { proto.RegisterFile("encryption.proto", fileDescriptor_8293a649ce9418c6) }
var fileDescriptor_8293a649ce9418c6 = []byte{ var fileDescriptor_8293a649ce9418c6 = []byte{
// 535 bytes of a gzipped FileDescriptorProto // 539 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x51, 0x8b, 0xd3, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x51, 0x8b, 0xd3, 0x40,
0x10, 0x26, 0xe9, 0xd9, 0x6b, 0xa7, 0x69, 0x5a, 0x56, 0x94, 0x50, 0x0b, 0x96, 0x70, 0x62, 0x40, 0x10, 0x26, 0xe9, 0xd9, 0x6b, 0xa7, 0x69, 0x5a, 0x56, 0x94, 0x50, 0x0b, 0x96, 0x70, 0x62, 0x40,
0x28, 0x5c, 0xfb, 0x22, 0x3e, 0x6a, 0xc5, 0xaa, 0xa8, 0xc7, 0x2a, 0xe8, 0x8b, 0x84, 0x6d, 0x77, 0x28, 0x5c, 0xfb, 0x22, 0x3e, 0x6a, 0xc5, 0xaa, 0xa8, 0xc7, 0x2a, 0xe8, 0x8b, 0x84, 0x6d, 0x33,
0xbc, 0x5b, 0x4c, 0x37, 0x61, 0xb3, 0x2d, 0xf6, 0x2f, 0xf8, 0xe0, 0x4f, 0xf2, 0xbf, 0xf8, 0x4f, 0xde, 0x2d, 0xa6, 0x9b, 0xb0, 0xd9, 0x16, 0xfa, 0x17, 0xfc, 0x43, 0xbe, 0xf9, 0x5f, 0xfc, 0x27,
0x24, 0xbb, 0x49, 0x9b, 0xf6, 0xee, 0xe0, 0xde, 0x32, 0xb3, 0xb3, 0xdf, 0x7c, 0xdf, 0x37, 0x3b, 0x92, 0xdd, 0xa4, 0xdd, 0xf6, 0x7a, 0xe0, 0x5b, 0x66, 0x76, 0xf6, 0x9b, 0xef, 0xfb, 0x66, 0x27,
0x81, 0x3e, 0xca, 0xa5, 0xda, 0x66, 0x5a, 0xa4, 0x72, 0x9c, 0xa9, 0x54, 0xa7, 0xe4, 0x64, 0x79, 0xd0, 0x47, 0xb1, 0x94, 0xdb, 0x5c, 0xf1, 0x4c, 0x8c, 0x73, 0x99, 0xa9, 0x8c, 0x9c, 0x2d, 0x6f,
0xc5, 0x74, 0xf8, 0x11, 0xbc, 0xcf, 0xe2, 0x52, 0x22, 0xbf, 0x50, 0xf8, 0x1e, 0xb7, 0xe4, 0x0c, 0x98, 0x0a, 0x3f, 0x82, 0xf7, 0x99, 0x5f, 0x0b, 0x4c, 0xae, 0x24, 0xbe, 0xc7, 0x2d, 0xb9, 0x00,
0xfc, 0xdc, 0xc4, 0x71, 0xa6, 0x30, 0xfe, 0x89, 0xdb, 0xc0, 0x19, 0x39, 0x91, 0x47, 0xbd, 0xbc, 0xbf, 0xd0, 0x71, 0x9c, 0x4b, 0x8c, 0x7f, 0xe2, 0x36, 0x70, 0x46, 0x4e, 0xe4, 0x51, 0xaf, 0xb0,
0x5e, 0x15, 0xc0, 0xe9, 0x06, 0x55, 0x2e, 0x52, 0x19, 0xb8, 0x23, 0x27, 0xea, 0xd2, 0x2a, 0x0c, 0xab, 0x02, 0x38, 0xdf, 0xa0, 0x2c, 0x78, 0x26, 0x02, 0x77, 0xe4, 0x44, 0x5d, 0x5a, 0x87, 0xe1,
0xff, 0x39, 0xd0, 0x7c, 0xb9, 0x96, 0x3c, 0x41, 0x32, 0x80, 0x96, 0xe0, 0x28, 0xb5, 0xd0, 0x15, 0x5f, 0x07, 0x9a, 0x2f, 0xd7, 0x22, 0x49, 0x91, 0x0c, 0xa0, 0xc5, 0x13, 0x14, 0x8a, 0xab, 0x1a,
0xc8, 0x2e, 0x26, 0x6f, 0xa0, 0x77, 0xd8, 0x26, 0x0f, 0xdc, 0x51, 0x23, 0xea, 0x4c, 0x1e, 0x8f, 0x64, 0x17, 0x93, 0x37, 0xd0, 0x3b, 0x6c, 0x53, 0x04, 0xee, 0xa8, 0x11, 0x75, 0x26, 0x8f, 0xc7,
0x0b, 0x5a, 0x63, 0x0b, 0x31, 0xae, 0x53, 0xcb, 0x5f, 0x4b, 0xad, 0xb6, 0xb4, 0x5b, 0x27, 0x92, 0x25, 0xad, 0xb1, 0x81, 0x18, 0xdb, 0xd4, 0x8a, 0xd7, 0x42, 0xc9, 0x2d, 0xed, 0xda, 0x44, 0x0a,
0x93, 0x21, 0xb4, 0x8b, 0x04, 0xd3, 0x6b, 0x85, 0xc1, 0x89, 0xe9, 0xb2, 0x4f, 0x0c, 0xbe, 0x00, 0x32, 0x84, 0x76, 0x99, 0x60, 0x6a, 0x2d, 0x31, 0x38, 0xd3, 0x5d, 0xf6, 0x89, 0xc1, 0x17, 0x20,
0xb9, 0x0e, 0x41, 0xfa, 0xd0, 0xa8, 0x84, 0xb5, 0x69, 0xf1, 0x49, 0x22, 0xb8, 0xb7, 0x61, 0xc9, 0xb7, 0x21, 0x48, 0x1f, 0x1a, 0xb5, 0xb0, 0x36, 0x2d, 0x3f, 0x49, 0x04, 0xf7, 0x36, 0x2c, 0x5d,
0x1a, 0x8d, 0x9a, 0xce, 0x84, 0x58, 0x12, 0xf5, 0xab, 0xd4, 0x16, 0xbc, 0x70, 0x9f, 0x3b, 0xe1, 0xa3, 0x56, 0xd3, 0x99, 0x10, 0x43, 0xc2, 0xbe, 0x4a, 0x4d, 0xc1, 0x0b, 0xf7, 0xb9, 0x13, 0xfe,
0x6f, 0x07, 0x7a, 0x96, 0xe0, 0xab, 0x54, 0x6a, 0x26, 0x24, 0x2a, 0x72, 0x06, 0xcd, 0x85, 0x49, 0x72, 0xa0, 0x67, 0x08, 0xbe, 0xca, 0x84, 0x62, 0x5c, 0xa0, 0x24, 0x17, 0xd0, 0x5c, 0xe8, 0x94,
0x19, 0xd8, 0xce, 0xc4, 0xab, 0xeb, 0xa0, 0xe5, 0x19, 0x99, 0xc2, 0xc3, 0x4c, 0x89, 0x0d, 0xd3, 0x86, 0xed, 0x4c, 0x3c, 0x5b, 0x07, 0xad, 0xce, 0xc8, 0x14, 0x1e, 0xe6, 0x92, 0x6f, 0x98, 0xc2,
0x18, 0x1f, 0xb9, 0xec, 0x1a, 0xea, 0xf7, 0xcb, 0xd3, 0x83, 0x91, 0x0c, 0xa1, 0xad, 0xc5, 0x0a, 0xf8, 0xc8, 0x65, 0x57, 0x53, 0xbf, 0x5f, 0x9d, 0x1e, 0x8c, 0x64, 0x08, 0x6d, 0xc5, 0x57, 0x58,
0x73, 0xcd, 0x56, 0x59, 0xd0, 0x18, 0x39, 0x51, 0x83, 0xee, 0x13, 0xe1, 0x3b, 0x68, 0xcd, 0xe8, 0x28, 0xb6, 0xca, 0x83, 0xc6, 0xc8, 0x89, 0x1a, 0x74, 0x9f, 0x08, 0xdf, 0x41, 0x6b, 0x46, 0xe7,
0x1c, 0x19, 0x47, 0x55, 0x17, 0xe6, 0x59, 0x61, 0x1e, 0x38, 0xd5, 0x88, 0x1c, 0x49, 0x7c, 0x70, 0xc8, 0x12, 0x94, 0xb6, 0x30, 0xcf, 0x08, 0xf3, 0xc0, 0xa9, 0x47, 0xe4, 0x08, 0xe2, 0x83, 0x9b,
0x33, 0x69, 0x20, 0xba, 0xd4, 0xcd, 0x4c, 0x2c, 0x78, 0xe9, 0x9a, 0x2b, 0x78, 0x38, 0x84, 0xd6, 0x0b, 0x0d, 0xd1, 0xa5, 0x6e, 0xae, 0x63, 0x9e, 0x54, 0xae, 0xb9, 0x3c, 0x09, 0x87, 0xd0, 0x9a,
0x6c, 0x7e, 0x1b, 0x56, 0xf8, 0x15, 0xe0, 0xdb, 0xf4, 0xf6, 0xf3, 0x63, 0x34, 0xf2, 0x14, 0x7a, 0xcd, 0xef, 0xc2, 0x0a, 0xbf, 0x02, 0x7c, 0x9b, 0xde, 0x7d, 0x7e, 0x8c, 0x46, 0x9e, 0x42, 0x8f,
0x42, 0xe6, 0x9a, 0x25, 0x09, 0x2b, 0x9e, 0x5d, 0x2c, 0xb8, 0x69, 0xdd, 0xa6, 0x7e, 0x3d, 0xfd, 0x8b, 0x42, 0xb1, 0x34, 0x65, 0xe5, 0xb3, 0x8b, 0x79, 0xa2, 0x5b, 0xb7, 0xa9, 0x6f, 0xa7, 0xdf,
0x96, 0x87, 0x7f, 0x1d, 0x78, 0x30, 0x13, 0x0a, 0x97, 0xfa, 0x03, 0xe6, 0x39, 0xbb, 0xc4, 0x8b, 0x26, 0xe1, 0x1f, 0x07, 0x1e, 0xcc, 0xb8, 0xc4, 0xa5, 0xfa, 0x80, 0x45, 0xc1, 0xae, 0xf1, 0xaa,
0xe2, 0x81, 0x2e, 0xd3, 0x84, 0x9c, 0x43, 0xa7, 0x68, 0x19, 0x5f, 0x99, 0x9e, 0xa5, 0xb5, 0x7d, 0x7c, 0xa0, 0xcb, 0x2c, 0x25, 0x97, 0xd0, 0x29, 0x5b, 0xc6, 0x37, 0xba, 0x67, 0x65, 0x6d, 0xdf,
0x6b, 0xed, 0x9e, 0x0b, 0x85, 0x5f, 0x7b, 0x5e, 0xcf, 0xa0, 0x3d, 0xa3, 0xd5, 0x05, 0x3b, 0x4e, 0x58, 0xbb, 0xe7, 0x42, 0x6d, 0x5e, 0xcf, 0xa0, 0x3d, 0xa3, 0xf5, 0x05, 0x33, 0x4e, 0xdf, 0x5c,
0xdf, 0x5e, 0xa8, 0x6c, 0xa2, 0x2d, 0x4e, 0x6b, 0xc5, 0x3b, 0x74, 0x3c, 0x28, 0x9e, 0xef, 0x8a, 0xa8, 0x6d, 0xa2, 0x7b, 0xc3, 0xca, 0xe2, 0x1d, 0x3a, 0x1e, 0x14, 0xcf, 0x77, 0xc5, 0x35, 0x72,
0x2b, 0xe4, 0x00, 0x4e, 0x33, 0xb6, 0x4d, 0x52, 0x66, 0x75, 0x78, 0xb4, 0x0a, 0xc3, 0x3f, 0x2e, 0x00, 0xe7, 0x39, 0xdb, 0xa6, 0x19, 0x33, 0x3a, 0x3c, 0x5a, 0x87, 0xe1, 0x6f, 0x17, 0x7a, 0x35,
0xf4, 0x2a, 0xce, 0xa5, 0x84, 0x3b, 0x3e, 0x88, 0x4f, 0xe0, 0x73, 0xa3, 0x3c, 0x5e, 0xd9, 0x7b, 0xe7, 0x4a, 0xc2, 0x7f, 0x3e, 0x88, 0x13, 0x1e, 0xb9, 0xa7, 0x3c, 0x22, 0x9f, 0xc0, 0x4f, 0xb4,
0x01, 0x9a, 0x35, 0x88, 0x6c, 0xf5, 0x11, 0xe8, 0xf8, 0xc0, 0xa5, 0x72, 0x1f, 0x78, 0x3d, 0x47, 0x45, 0xf1, 0xca, 0x34, 0x08, 0x50, 0xef, 0x4b, 0x64, 0x60, 0x8f, 0xba, 0x8f, 0x0f, 0xec, 0xac,
0x9e, 0x80, 0x9f, 0xad, 0x17, 0x89, 0x58, 0xee, 0x00, 0x7f, 0x18, 0xae, 0x5d, 0x9b, 0x2d, 0xcb, 0x16, 0x27, 0xb1, 0x73, 0xe4, 0x09, 0xf8, 0xf9, 0x7a, 0x91, 0xf2, 0xe5, 0x0e, 0xf0, 0x87, 0x16,
0x06, 0xdf, 0x81, 0x5c, 0xc7, 0xba, 0x61, 0x31, 0xce, 0x0f, 0x17, 0xe3, 0x51, 0x69, 0xce, 0x4d, 0xd5, 0x35, 0xd9, 0xaa, 0x6c, 0xf0, 0x1d, 0xc8, 0x6d, 0xac, 0x13, 0x1b, 0x74, 0x79, 0xb8, 0x41,
0xc3, 0xaa, 0x6d, 0xc8, 0xa2, 0x69, 0x7e, 0x31, 0xd3, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x23, 0x8f, 0x2a, 0x17, 0x4f, 0x4d, 0xd5, 0x5a, 0xa5, 0x45, 0x53, 0xff, 0x8b, 0xa6, 0xff, 0x02, 0x00,
0x4b, 0xe1, 0x7c, 0x76, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0x42, 0x9c, 0x34, 0x10, 0x9f, 0x04, 0x00, 0x00,
} }

View File

@ -48,7 +48,7 @@ message X3DHHeader {
bytes key = 1; bytes key = 1;
// Used bundle's signed prekey // Used bundle's signed prekey
bytes id = 4; bytes id = 4;
// The device id // DEPRECATED: The device id
string installation_id = 3; string installation_id = 3;
} }
@ -66,9 +66,13 @@ message ProtocolMessage {
// An optional bundle is exchanged with each message // An optional bundle is exchanged with each message
Bundle bundle = 1; Bundle bundle = 1;
// The device id of the sender
string installation_id = 2;
// One to one message, encrypted, indexed by installation_id // One to one message, encrypted, indexed by installation_id
map<string,DirectMessageProtocol> direct_message = 101; map<string,DirectMessageProtocol> direct_message = 101;
// Public chats, not encrypted // Public chats, not encrypted
bytes public_message = 102; bytes public_message = 102;
} }

View File

@ -4,6 +4,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"os" "os"
"sort"
"testing" "testing"
) )
@ -68,18 +69,57 @@ func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundle() {
aliceKey, err := crypto.GenerateKey() aliceKey, err := crypto.GenerateKey()
s.Require().NoError(err) s.Require().NoError(err)
_, err = s.alice1.CreateBundle(aliceKey) alice1Bundle, err := s.alice1.CreateBundle(aliceKey)
s.Require().NoError(err)
alice1Identity, err := ExtractIdentity(alice1Bundle)
s.Require().NoError(err) s.Require().NoError(err)
alice2Bundle, err := s.alice2.CreateBundle(aliceKey) alice2Bundle, err := s.alice2.CreateBundle(aliceKey)
s.Require().NoError(err) s.Require().NoError(err)
err = s.alice1.ProcessPublicBundle(aliceKey, alice2Bundle) alice2Identity, err := ExtractIdentity(alice2Bundle)
s.Require().NoError(err) s.Require().NoError(err)
response, err := s.alice1.ProcessPublicBundle(aliceKey, alice2Bundle)
s.Require().NoError(err)
s.Require().Equal(IdentityAndIDPair{alice2Identity, "alice2"}, response[0])
alice1MergedBundle1, err := s.alice1.CreateBundle(aliceKey) alice1MergedBundle1, err := s.alice1.CreateBundle(aliceKey)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(alice1MergedBundle1.GetSignedPreKeys()["alice1"]) s.Require().NotNil(alice1MergedBundle1.GetSignedPreKeys()["alice1"])
s.Require().NotNil(alice1MergedBundle1.GetSignedPreKeys()["alice2"]) s.Require().NotNil(alice1MergedBundle1.GetSignedPreKeys()["alice2"])
response, err = s.alice1.ProcessPublicBundle(aliceKey, alice1MergedBundle1)
s.Require().NoError(err)
sort.Slice(response, func(i, j int) bool {
return response[i][1] < response[j][1]
})
s.Require().Equal(IdentityAndIDPair{alice1Identity, "alice1"}, response[0])
s.Require().Equal(IdentityAndIDPair{alice2Identity, "alice2"}, response[1])
}
func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundleOutOfOrder() {
aliceKey, err := crypto.GenerateKey()
s.Require().NoError(err)
// Alice1 creates a bundle
alice1Bundle, err := s.alice1.CreateBundle(aliceKey)
s.Require().NoError(err)
// Alice2 Receives the bundle
_, err = s.alice2.ProcessPublicBundle(aliceKey, alice1Bundle)
s.Require().NoError(err)
// Alice2 Creates a Bundle
_, err = s.alice2.CreateBundle(aliceKey)
s.Require().NoError(err)
// It should contain both bundles
alice1MergedBundle1, err := s.alice2.CreateBundle(aliceKey)
s.Require().NoError(err)
s.Require().NotNil(alice1MergedBundle1.GetSignedPreKeys()["alice1"])
s.Require().NotNil(alice1MergedBundle1.GetSignedPreKeys()["alice2"])
} }

View File

@ -94,7 +94,7 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadNoBundle() {
s.NotEqual(cyphertext1, cleartext, "It encrypts the payload correctly") s.NotEqual(cyphertext1, cleartext, "It encrypts the payload correctly")
// On the receiver side, we should be able to decrypt using our private key and the ephemeral just sent // On the receiver side, we should be able to decrypt using our private key and the ephemeral just sent
decryptedPayload1, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, encryptionResponse1) decryptedPayload1, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse1)
s.Require().NoError(err) s.Require().NoError(err)
s.Equal(cleartext, decryptedPayload1, "It correctly decrypts the payload using DH") s.Equal(cleartext, decryptedPayload1, "It correctly decrypts the payload using DH")
@ -109,7 +109,7 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadNoBundle() {
s.NotEqual(cyphertext1, cyphertext2, "It does not re-use the symmetric key") s.NotEqual(cyphertext1, cyphertext2, "It does not re-use the symmetric key")
s.NotEqual(ephemeralKey1, ephemeralKey2, "It does not re-use the ephemeral key") s.NotEqual(ephemeralKey1, ephemeralKey2, "It does not re-use the ephemeral key")
decryptedPayload2, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, encryptionResponse2) decryptedPayload2, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse2)
s.Require().NoError(err) s.Require().NoError(err)
s.Equal(cleartext, decryptedPayload2, "It correctly decrypts the payload using DH") s.Equal(cleartext, decryptedPayload2, "It correctly decrypts the payload using DH")
} }
@ -129,7 +129,7 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadBundle() {
s.Require().NoError(err) s.Require().NoError(err)
// We add bob bundle // We add bob bundle
err = s.alice.ProcessPublicBundle(aliceKey, bobBundle) _, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
s.Require().NoError(err) s.Require().NoError(err)
// We send a message using the bundle // We send a message using the bundle
@ -161,7 +161,7 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadBundle() {
s.Equal(uint32(0), drHeader.GetPn(), "It adds the correct length of the message chain") s.Equal(uint32(0), drHeader.GetPn(), "It adds the correct length of the message chain")
// Bob is able to decrypt it using the bundle // Bob is able to decrypt it using the bundle
decryptedPayload1, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, encryptionResponse1) decryptedPayload1, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse1)
s.Require().NoError(err) s.Require().NoError(err)
s.Equal(cleartext, decryptedPayload1, "It correctly decrypts the payload using X3DH") s.Equal(cleartext, decryptedPayload1, "It correctly decrypts the payload using X3DH")
} }
@ -188,7 +188,7 @@ func (s *EncryptionServiceTestSuite) TestConsequentMessagesBundle() {
s.Require().NoError(err) s.Require().NoError(err)
// We add bob bundle // We add bob bundle
err = s.alice.ProcessPublicBundle(aliceKey, bobBundle) _, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
s.Require().NoError(err) s.Require().NoError(err)
// We send a message using the bundle // We send a message using the bundle
@ -225,7 +225,7 @@ func (s *EncryptionServiceTestSuite) TestConsequentMessagesBundle() {
s.Equal(uint32(0), drHeader.GetPn(), "It adds the correct length of the message chain") s.Equal(uint32(0), drHeader.GetPn(), "It adds the correct length of the message chain")
// Bob is able to decrypt it using the bundle // Bob is able to decrypt it using the bundle
decryptedPayload1, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, encryptionResponse) decryptedPayload1, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse)
s.Require().NoError(err) s.Require().NoError(err)
s.Equal(cleartext2, decryptedPayload1, "It correctly decrypts the payload using X3DH") s.Equal(cleartext2, decryptedPayload1, "It correctly decrypts the payload using X3DH")
@ -257,11 +257,11 @@ func (s *EncryptionServiceTestSuite) TestConversation() {
s.Require().NoError(err) s.Require().NoError(err)
// We add bob bundle // We add bob bundle
err = s.alice.ProcessPublicBundle(aliceKey, bobBundle) _, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
s.Require().NoError(err) s.Require().NoError(err)
// We add alice bundle // We add alice bundle
err = s.bob.ProcessPublicBundle(bobKey, aliceBundle) _, err = s.bob.ProcessPublicBundle(bobKey, aliceBundle)
s.Require().NoError(err) s.Require().NoError(err)
// Alice sends a message // Alice sends a message
@ -269,7 +269,7 @@ func (s *EncryptionServiceTestSuite) TestConversation() {
s.Require().NoError(err) s.Require().NoError(err)
// Bob receives the message // Bob receives the message
_, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, encryptionResponse) _, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse)
s.Require().NoError(err) s.Require().NoError(err)
// Bob replies to the message // Bob replies to the message
@ -277,7 +277,7 @@ func (s *EncryptionServiceTestSuite) TestConversation() {
s.Require().NoError(err) s.Require().NoError(err)
// Alice receives the message // Alice receives the message
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, encryptionResponse) _, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, encryptionResponse)
s.Require().NoError(err) s.Require().NoError(err)
// We send another message using the bundle // We send another message using the bundle
@ -308,7 +308,7 @@ func (s *EncryptionServiceTestSuite) TestConversation() {
s.Equal(uint32(1), drHeader.GetPn(), "It adds the correct length of the message chain") s.Equal(uint32(1), drHeader.GetPn(), "It adds the correct length of the message chain")
// Bob is able to decrypt it using the bundle // Bob is able to decrypt it using the bundle
decryptedPayload1, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, encryptionResponse) decryptedPayload1, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse)
s.Require().NoError(err) s.Require().NoError(err)
s.Equal(cleartext2, decryptedPayload1, "It correctly decrypts the payload using X3DH") s.Equal(cleartext2, decryptedPayload1, "It correctly decrypts the payload using X3DH")
@ -339,7 +339,7 @@ func (s *EncryptionServiceTestSuite) TestConcurrentBundles() {
s.Require().NoError(err) s.Require().NoError(err)
// We add bob bundle // We add bob bundle
err = s.alice.ProcessPublicBundle(aliceKey, bobBundle) _, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
s.Require().NoError(err) s.Require().NoError(err)
// Create a bundle // Create a bundle
@ -347,7 +347,7 @@ func (s *EncryptionServiceTestSuite) TestConcurrentBundles() {
s.Require().NoError(err) s.Require().NoError(err)
// We add alice bundle // We add alice bundle
err = s.bob.ProcessPublicBundle(bobKey, aliceBundle) _, err = s.bob.ProcessPublicBundle(bobKey, aliceBundle)
s.Require().NoError(err) s.Require().NoError(err)
// Alice sends a message // Alice sends a message
@ -359,11 +359,11 @@ func (s *EncryptionServiceTestSuite) TestConcurrentBundles() {
s.Require().NoError(err) s.Require().NoError(err)
// Bob receives the message // Bob receives the message
_, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceMessage1) _, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, aliceMessage1)
s.Require().NoError(err) s.Require().NoError(err)
// Alice receives the message // Alice receives the message
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobMessage1) _, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage1)
s.Require().NoError(err) s.Require().NoError(err)
// Bob replies to the message // Bob replies to the message
@ -375,11 +375,11 @@ func (s *EncryptionServiceTestSuite) TestConcurrentBundles() {
s.Require().NoError(err) s.Require().NoError(err)
// Alice receives the message // Alice receives the message
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobMessage2) _, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage2)
s.Require().NoError(err) s.Require().NoError(err)
// Bob receives the message // Bob receives the message
_, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceMessage2) _, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, aliceMessage2)
s.Require().NoError(err) s.Require().NoError(err)
} }
@ -420,13 +420,14 @@ func receiver(
s *EncryptionService, s *EncryptionService,
privateKey *ecdsa.PrivateKey, privateKey *ecdsa.PrivateKey,
publicKey *ecdsa.PublicKey, publicKey *ecdsa.PublicKey,
installationID string,
errChan chan error, errChan chan error,
input chan map[string]*DirectMessageProtocol, input chan map[string]*DirectMessageProtocol,
) { ) {
i := 0 i := 0
for payload := range input { for payload := range input {
actualCleartext, err := s.DecryptPayload(privateKey, publicKey, payload) actualCleartext, err := s.DecryptPayload(privateKey, publicKey, installationID, payload)
if err != nil { if err != nil {
errChan <- err errChan <- err
return return
@ -459,7 +460,7 @@ func (s *EncryptionServiceTestSuite) TestRandomised() {
s.Require().NoError(err) s.Require().NoError(err)
// We add bob bundle // We add bob bundle
err = s.alice.ProcessPublicBundle(aliceKey, bobBundle) _, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
s.Require().NoError(err) s.Require().NoError(err)
// Create a bundle // Create a bundle
@ -467,7 +468,7 @@ func (s *EncryptionServiceTestSuite) TestRandomised() {
s.Require().NoError(err) s.Require().NoError(err)
// We add alice bundle // We add alice bundle
err = s.bob.ProcessPublicBundle(bobKey, aliceBundle) _, err = s.bob.ProcessPublicBundle(bobKey, aliceBundle)
s.Require().NoError(err) s.Require().NoError(err)
aliceChan := make(chan map[string]*DirectMessageProtocol, 100) aliceChan := make(chan map[string]*DirectMessageProtocol, 100)
@ -485,10 +486,10 @@ func (s *EncryptionServiceTestSuite) TestRandomised() {
go publisher(s.bob, bobKey, &aliceKey.PublicKey, bobPublisherErrChan, aliceChan) go publisher(s.bob, bobKey, &aliceKey.PublicKey, bobPublisherErrChan, aliceChan)
// Set up bob receiver // Set up bob receiver
go receiver(s.bob, bobKey, &aliceKey.PublicKey, bobReceiverErrChan, bobChan) go receiver(s.bob, bobKey, &aliceKey.PublicKey, aliceInstallationID, bobReceiverErrChan, bobChan)
// Set up alice receiver // Set up alice receiver
go receiver(s.alice, aliceKey, &bobKey.PublicKey, aliceReceiverErrChan, aliceChan) go receiver(s.alice, aliceKey, &bobKey.PublicKey, bobInstallationID, aliceReceiverErrChan, aliceChan)
aliceErr := <-alicePublisherErrChan aliceErr := <-alicePublisherErrChan
s.Require().NoError(aliceErr) s.Require().NoError(aliceErr)
@ -525,7 +526,7 @@ func (s *EncryptionServiceTestSuite) TestBundleNotExisting() {
bobBundle := bobBundleContainer.GetBundle() bobBundle := bobBundleContainer.GetBundle()
// We add bob bundle // We add bob bundle
err = s.alice.ProcessPublicBundle(aliceKey, bobBundle) _, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle)
s.Require().NoError(err) s.Require().NoError(err)
// Alice sends a message // Alice sends a message
@ -533,7 +534,7 @@ func (s *EncryptionServiceTestSuite) TestBundleNotExisting() {
s.Require().NoError(err) s.Require().NoError(err)
// Bob receives the message, and returns a bundlenotfound error // Bob receives the message, and returns a bundlenotfound error
_, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceMessage) _, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, aliceMessage)
s.Require().Error(err) s.Require().Error(err)
s.Equal(ErrSessionNotFound, err) s.Equal(ErrSessionNotFound, err)
} }
@ -561,7 +562,7 @@ func (s *EncryptionServiceTestSuite) TestRefreshedBundle() {
s.Require().NoError(err) s.Require().NoError(err)
// We add the first bob bundle // We add the first bob bundle
err = s.alice.ProcessPublicBundle(aliceKey, bobBundle1.GetBundle()) _, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle1.GetBundle())
s.Require().NoError(err) s.Require().NoError(err)
// Alice sends a message // Alice sends a message
@ -578,7 +579,7 @@ func (s *EncryptionServiceTestSuite) TestRefreshedBundle() {
s.Equal(bobBundle1.GetBundle().GetSignedPreKeys()[bobInstallationID].GetSignedPreKey(), x3dhHeader1.GetId()) s.Equal(bobBundle1.GetBundle().GetSignedPreKeys()[bobInstallationID].GetSignedPreKey(), x3dhHeader1.GetId())
// We add the second bob bundle // We add the second bob bundle
err = s.alice.ProcessPublicBundle(aliceKey, bobBundle2.GetBundle()) _, err = s.alice.ProcessPublicBundle(aliceKey, bobBundle2.GetBundle())
s.Require().NoError(err) s.Require().NoError(err)
// Alice sends a message // Alice sends a message

View File

@ -2,6 +2,8 @@
// sources: // sources:
// 1536754952_initial_schema.down.sql // 1536754952_initial_schema.down.sql
// 1536754952_initial_schema.up.sql // 1536754952_initial_schema.up.sql
// 1539249977_update_ratchet_info.down.sql
// 1539249977_update_ratchet_info.up.sql
// static.go // static.go
// DO NOT EDIT! // DO NOT EDIT!
@ -85,7 +87,7 @@ func _1536754952_initial_schemaDownSql() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "1536754952_initial_schema.down.sql", size: 83, mode: os.FileMode(420), modTime: time.Unix(1536915096, 0)} info := bindataFileInfo{name: "1536754952_initial_schema.down.sql", size: 83, mode: os.FileMode(420), modTime: time.Unix(1537862328, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
@ -105,7 +107,47 @@ func _1536754952_initial_schemaUpSql() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "1536754952_initial_schema.up.sql", size: 962, mode: os.FileMode(420), modTime: time.Unix(1536915096, 0)} info := bindataFileInfo{name: "1536754952_initial_schema.up.sql", size: 962, mode: os.FileMode(420), modTime: time.Unix(1539252806, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __1539249977_update_ratchet_infoDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8f\x41\x4b\xc4\x30\x10\x85\xef\xf9\x15\xef\xd8\xc2\x9e\xbc\xee\xa9\x8d\x53\x29\x86\x64\x8d\x29\xe8\x29\xd4\xed\xe8\x06\xdb\xec\xd2\x46\xa1\xff\x5e\x22\x52\x59\xf4\x3a\xdf\xf7\x78\x6f\x6e\xad\x39\xc0\x55\xb5\x22\xcc\x7d\x3a\x9e\x38\xf9\x10\x5f\xcf\xfe\xf3\x66\x2f\x84\xb4\x54\x39\xfa\x07\xa3\x10\xc0\xcb\x47\x1c\x46\xf6\x61\x40\xad\x4c\x0d\x6d\x1c\x74\xa7\xd4\x4e\x00\x7c\x39\xf1\xc4\x73\x3f\xfa\x77\x5e\xbf\x71\xbe\x86\x81\x63\x0a\x69\xfd\xeb\x2f\xeb\x34\x71\x9a\xc3\x71\xf3\xaf\x70\x88\x4b\xea\xc7\xb1\x4f\xe1\x1c\x73\x9f\xa3\x27\x77\x25\x74\xba\x7d\xe8\xa8\xd8\x16\xed\xb6\xae\x12\x46\x43\x1a\xdd\xa8\x56\x3a\x58\x3a\xa8\x4a\x52\x8e\x34\xc6\x52\x7b\xa7\x71\x4f\xcf\xf8\x0d\x96\xb0\xd4\x90\x25\x2d\xe9\xf1\xe7\xc1\xa5\x58\xc2\x5b\xe4\xc1\x5f\x66\xce\xf3\x4a\x51\xee\xc5\x57\x00\x00\x00\xff\xff\x69\x51\x9b\xb4\x37\x01\x00\x00")
func _1539249977_update_ratchet_infoDownSqlBytes() ([]byte, error) {
return bindataRead(
__1539249977_update_ratchet_infoDownSql,
"1539249977_update_ratchet_info.down.sql",
)
}
func _1539249977_update_ratchet_infoDownSql() (*asset, error) {
bytes, err := _1539249977_update_ratchet_infoDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1539249977_update_ratchet_info.down.sql", size: 311, mode: os.FileMode(420), modTime: time.Unix(1539250187, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __1539249977_update_ratchet_infoUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8f\x41\x4f\x84\x30\x10\x85\xef\xfd\x15\x73\x84\x84\x93\x57\x4e\x50\x07\x43\xac\xed\x5a\x4b\xa2\xa7\x06\x97\xd1\x6d\x16\xca\x86\x56\x13\xfe\xbd\xa9\x31\x28\xea\xf5\xbd\x6f\xde\x7b\x73\x8d\x02\x0d\x42\xa3\xd5\x1d\x04\x0a\xc1\xcd\x3e\x94\xec\xa7\x7a\xa6\x35\x29\x5a\x1d\xc0\x54\xb5\x40\x58\xfa\x78\x3c\x51\xb4\xce\xbf\xcc\x25\x63\x5c\x63\x65\xf0\x1f\xcf\xbe\x5f\x41\xc6\x00\x9e\xdf\xfc\x30\x92\x75\x03\xd4\x42\xd5\x20\x95\x01\xd9\x09\x51\x30\x00\xba\x9c\x68\xa2\xa5\x1f\xed\x99\xd6\x4f\x3b\xa9\x6e\x20\x1f\x5d\x5c\xff\xf2\x61\x9d\x26\x8a\x8b\x3b\x6e\xfc\xce\x76\x3e\xc4\x7e\x1c\xfb\xe8\x66\x9f\xfa\x0c\x3e\x9a\x1d\xd0\xc9\xf6\xbe\xc3\x6c\x5b\x54\x6c\x5d\xc5\xef\xe3\x1c\x94\x04\xae\x64\x23\x5a\x6e\x40\xe3\x41\x54\x1c\x53\x46\xa3\x34\xb6\x37\x12\x6e\xf1\x09\xbe\x93\x72\xd0\xd8\xa0\x46\xc9\xf1\xe1\xeb\xe3\x90\x05\xf7\xea\x69\xb0\x97\x85\xd2\xde\x9c\xe5\x25\xfb\x08\x00\x00\xff\xff\xb6\x31\x2b\x32\x70\x01\x00\x00")
func _1539249977_update_ratchet_infoUpSqlBytes() ([]byte, error) {
return bindataRead(
__1539249977_update_ratchet_infoUpSql,
"1539249977_update_ratchet_info.up.sql",
)
}
func _1539249977_update_ratchet_infoUpSql() (*asset, error) {
bytes, err := _1539249977_update_ratchet_infoUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1539249977_update_ratchet_info.up.sql", size: 368, mode: os.FileMode(420), modTime: time.Unix(1539250201, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
@ -125,7 +167,7 @@ func staticGo() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "static.go", size: 188, mode: os.FileMode(420), modTime: time.Unix(1536915096, 0)} info := bindataFileInfo{name: "static.go", size: 188, mode: os.FileMode(420), modTime: time.Unix(1537862328, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
@ -184,6 +226,8 @@ func AssetNames() []string {
var _bindata = map[string]func() (*asset, error){ var _bindata = map[string]func() (*asset, error){
"1536754952_initial_schema.down.sql": _1536754952_initial_schemaDownSql, "1536754952_initial_schema.down.sql": _1536754952_initial_schemaDownSql,
"1536754952_initial_schema.up.sql": _1536754952_initial_schemaUpSql, "1536754952_initial_schema.up.sql": _1536754952_initial_schemaUpSql,
"1539249977_update_ratchet_info.down.sql": _1539249977_update_ratchet_infoDownSql,
"1539249977_update_ratchet_info.up.sql": _1539249977_update_ratchet_infoUpSql,
"static.go": staticGo, "static.go": staticGo,
} }
@ -229,6 +273,8 @@ type bintree struct {
var _bintree = &bintree{nil, map[string]*bintree{ var _bintree = &bintree{nil, map[string]*bintree{
"1536754952_initial_schema.down.sql": &bintree{_1536754952_initial_schemaDownSql, map[string]*bintree{}}, "1536754952_initial_schema.down.sql": &bintree{_1536754952_initial_schemaDownSql, map[string]*bintree{}},
"1536754952_initial_schema.up.sql": &bintree{_1536754952_initial_schemaUpSql, map[string]*bintree{}}, "1536754952_initial_schema.up.sql": &bintree{_1536754952_initial_schemaUpSql, map[string]*bintree{}},
"1539249977_update_ratchet_info.down.sql": &bintree{_1539249977_update_ratchet_infoDownSql, map[string]*bintree{}},
"1539249977_update_ratchet_info.up.sql": &bintree{_1539249977_update_ratchet_infoUpSql, map[string]*bintree{}},
"static.go": &bintree{staticGo, map[string]*bintree{}}, "static.go": &bintree{staticGo, map[string]*bintree{}},
}} }}

View File

@ -42,10 +42,10 @@ type PersistenceService interface {
// AddRatchetInfo persists the specified ratchet info // AddRatchetInfo persists the specified ratchet info
AddRatchetInfo([]byte, []byte, []byte, []byte, string) error AddRatchetInfo([]byte, []byte, []byte, []byte, string) error
// GetRatchetInfo retrieves the existing RatchetInfo for a specified bundle ID and interlocutor public key // GetRatchetInfo retrieves the existing RatchetInfo for a specified bundle ID and interlocutor public key
GetRatchetInfo([]byte, []byte) (*RatchetInfo, error) GetRatchetInfo([]byte, []byte, string) (*RatchetInfo, error)
// GetAnyRatchetInfo retrieves any existing RatchetInfo for a specified interlocutor public key // GetAnyRatchetInfo retrieves any existing RatchetInfo for a specified interlocutor public key
GetAnyRatchetInfo([]byte, string) (*RatchetInfo, error) GetAnyRatchetInfo([]byte, string) (*RatchetInfo, error)
// RatchetInfoConfirmed clears the ephemeral key in the RatchetInfo // RatchetInfoConfirmed clears the ephemeral key in the RatchetInfo
// associated with the specified bundle ID and interlocutor identity public key // associated with the specified bundle ID and interlocutor identity public key
RatchetInfoConfirmed([]byte, []byte) error RatchetInfoConfirmed([]byte, []byte, string) error
} }

View File

@ -14,6 +14,11 @@ type ProtocolService struct {
Enabled bool Enabled bool
} }
type HandleMessageResponse struct {
AddedBundles []IdentityAndIDPair
Message []byte
}
// NewProtocolService creates a new ProtocolService instance // NewProtocolService creates a new ProtocolService instance
func NewProtocolService(encryption *EncryptionService) *ProtocolService { func NewProtocolService(encryption *EncryptionService) *ProtocolService {
return &ProtocolService{ return &ProtocolService{
@ -46,7 +51,8 @@ func (p *ProtocolService) addBundleAndMarshal(myIdentityKey *ecdsa.PrivateKey, m
func (p *ProtocolService) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte) ([]byte, error) { func (p *ProtocolService) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte) ([]byte, error) {
// Build message not encrypted // Build message not encrypted
protocolMessage := &ProtocolMessage{ protocolMessage := &ProtocolMessage{
PublicMessage: payload, InstallationId: p.encryption.installationID,
PublicMessage: payload,
} }
return p.addBundleAndMarshal(myIdentityKey, protocolMessage) return p.addBundleAndMarshal(myIdentityKey, protocolMessage)
@ -65,7 +71,8 @@ func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, th
// Build message // Build message
protocolMessage := &ProtocolMessage{ protocolMessage := &ProtocolMessage{
DirectMessage: encryptionResponse, InstallationId: p.encryption.installationID,
DirectMessage: encryptionResponse,
} }
payload, err := p.addBundleAndMarshal(myIdentityKey, protocolMessage) payload, err := p.addBundleAndMarshal(myIdentityKey, protocolMessage)
@ -80,8 +87,26 @@ func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, th
return response, nil return response, nil
} }
// BuildPairingMessage sends a message to our own devices using DH so that it can be decrypted by any other device
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{
InstallationId: p.encryption.installationID,
DirectMessage: encryptionResponse,
}
return p.addBundleAndMarshal(myIdentityKey, protocolMessage)
}
// ProcessPublicBundle processes a received X3DH bundle // ProcessPublicBundle processes a received X3DH bundle
func (p *ProtocolService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *Bundle) error { func (p *ProtocolService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *Bundle) ([]IdentityAndIDPair, error) {
return p.encryption.ProcessPublicBundle(myIdentityKey, bundle) return p.encryption.ProcessPublicBundle(myIdentityKey, bundle)
} }
@ -91,11 +116,13 @@ func (p *ProtocolService) GetBundle(myIdentityKey *ecdsa.PrivateKey) (*Bundle, e
} }
// 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) (*HandleMessageResponse, error) {
if p.encryption == nil { if p.encryption == nil {
return nil, errors.New("encryption service not initialized") return nil, errors.New("encryption service not initialized")
} }
response := &HandleMessageResponse{}
// Unmarshal message // Unmarshal message
protocolMessage := &ProtocolMessage{} protocolMessage := &ProtocolMessage{}
@ -106,21 +133,29 @@ func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPu
// Process bundle // Process bundle
if bundle := protocolMessage.GetBundle(); bundle != nil { if bundle := protocolMessage.GetBundle(); bundle != nil {
// Should we stop processing if the bundle cannot be verified? // Should we stop processing if the bundle cannot be verified?
err := p.encryption.ProcessPublicBundle(myIdentityKey, bundle) addedBundles, err := p.encryption.ProcessPublicBundle(myIdentityKey, bundle)
if err != nil { if err != nil {
return nil, err return nil, err
} }
response.AddedBundles = addedBundles
} }
// Check if it's a public message // Check if it's a public message
if publicMessage := protocolMessage.GetPublicMessage(); publicMessage != nil { if publicMessage := protocolMessage.GetPublicMessage(); publicMessage != nil {
response.Message = publicMessage
// Nothing to do, as already in cleartext // Nothing to do, as already in cleartext
return publicMessage, nil return response, nil
} }
// Decrypt message // Decrypt message
if directMessage := protocolMessage.GetDirectMessage(); directMessage != nil { if directMessage := protocolMessage.GetDirectMessage(); directMessage != nil {
return p.encryption.DecryptPayload(myIdentityKey, theirPublicKey, directMessage) message, err := p.encryption.DecryptPayload(myIdentityKey, theirPublicKey, protocolMessage.GetInstallationId(), directMessage)
if err != nil {
return nil, err
}
response.Message = message
return response, nil
} }
// Return error // Return error

View File

@ -106,8 +106,9 @@ func (s *ProtocolServiceTestSuite) TestBuildAndReadDirectMessage() {
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]) response, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, marshaledMsg[&bobKey.PublicKey])
s.NoError(err) s.NoError(err)
unmarshaledMsg := response.Message
s.NotNil(unmarshaledMsg) s.NotNil(unmarshaledMsg)

View File

@ -183,7 +183,7 @@ func (s *SQLLitePersistence) AddPublicBundle(b *Bundle) error {
// GetAnyPrivateBundle retrieves any bundle from the database containing a private key // GetAnyPrivateBundle retrieves any bundle from the database containing a private key
func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte) (*BundleContainer, error) { func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte) (*BundleContainer, error) {
stmt, err := s.db.Prepare("SELECT identity, signed_pre_key, installation_id, timestamp FROM bundles WHERE identity = ? AND expired = 0") stmt, err := s.db.Prepare("SELECT identity, private_key, signed_pre_key, installation_id, timestamp FROM bundles WHERE identity = ? AND expired = 0")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -191,6 +191,7 @@ func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte) (*BundleC
var timestamp int64 var timestamp int64
var identity []byte var identity []byte
var privateKey []byte
rows, err := stmt.Query(myIdentityKey) rows, err := stmt.Query(myIdentityKey)
rowCount := 0 rowCount := 0
@ -205,12 +206,17 @@ func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte) (*BundleC
SignedPreKeys: make(map[string]*SignedPreKey), SignedPreKeys: make(map[string]*SignedPreKey),
} }
bundleContainer := &BundleContainer{
Bundle: bundle,
}
for rows.Next() { for rows.Next() {
var signedPreKey []byte var signedPreKey []byte
var installationID string var installationID string
rowCount++ rowCount++
err = rows.Scan( err = rows.Scan(
&identity, &identity,
&privateKey,
&signedPreKey, &signedPreKey,
&installationID, &installationID,
&timestamp, &timestamp,
@ -218,18 +224,21 @@ func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte) (*BundleC
if err != nil { if err != nil {
return nil, err return nil, err
} }
// If there is a private key, we set the timestamp of the bundle container
if privateKey != nil {
bundleContainer.Timestamp = timestamp
}
bundle.SignedPreKeys[installationID] = &SignedPreKey{SignedPreKey: signedPreKey} bundle.SignedPreKeys[installationID] = &SignedPreKey{SignedPreKey: signedPreKey}
bundle.Identity = identity bundle.Identity = identity
} }
if rowCount == 0 { // If no records are found or no record with private key, return nil
if rowCount == 0 || bundleContainer.Timestamp == 0 {
return nil, nil return nil, nil
} }
return &BundleContainer{
Bundle: bundle, return bundleContainer, nil
Timestamp: timestamp,
}, nil
} }
@ -318,7 +327,7 @@ func (s *SQLLitePersistence) GetPublicBundle(publicKey *ecdsa.PublicKey) (*Bundl
// AddRatchetInfo persists the specified ratchet info into the database // AddRatchetInfo persists the specified ratchet info into the database
func (s *SQLLitePersistence) AddRatchetInfo(key []byte, identity []byte, bundleID []byte, ephemeralKey []byte, installationID string) error { func (s *SQLLitePersistence) AddRatchetInfo(key []byte, identity []byte, bundleID []byte, ephemeralKey []byte, installationID string) error {
stmt, err := s.db.Prepare("INSERT INTO ratchet_info(symmetric_key, identity, bundle_id, ephemeral_key, installation_id) VALUES(?, ?, ?, ?, ?)") stmt, err := s.db.Prepare("INSERT INTO ratchet_info_v2(symmetric_key, identity, bundle_id, ephemeral_key, installation_id) VALUES(?, ?, ?, ?, ?)")
if err != nil { if err != nil {
return err return err
} }
@ -336,8 +345,8 @@ func (s *SQLLitePersistence) AddRatchetInfo(key []byte, identity []byte, bundleI
} }
// GetRatchetInfo retrieves the existing RatchetInfo for a specified bundle ID and interlocutor public key from the database // GetRatchetInfo retrieves the existing RatchetInfo for a specified bundle ID and interlocutor public key from the database
func (s *SQLLitePersistence) GetRatchetInfo(bundleID []byte, theirIdentity []byte) (*RatchetInfo, error) { func (s *SQLLitePersistence) GetRatchetInfo(bundleID []byte, theirIdentity []byte, installationID string) (*RatchetInfo, error) {
stmt, err := s.db.Prepare("SELECT ratchet_info.identity, ratchet_info.symmetric_key, bundles.private_key, bundles.signed_pre_key, ratchet_info.ephemeral_key, ratchet_info.installation_id FROM ratchet_info JOIN bundles ON bundle_id = signed_pre_key WHERE ratchet_info.identity = ? AND bundle_id = ? LIMIT 1") stmt, err := s.db.Prepare("SELECT ratchet_info_v2.identity, ratchet_info_v2.symmetric_key, bundles.private_key, bundles.signed_pre_key, ratchet_info_v2.ephemeral_key, ratchet_info_v2.installation_id FROM ratchet_info_v2 JOIN bundles ON bundle_id = signed_pre_key WHERE ratchet_info_v2.identity = ? AND ratchet_info_v2.installation_id = ? AND bundle_id = ? LIMIT 1")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -347,7 +356,7 @@ func (s *SQLLitePersistence) GetRatchetInfo(bundleID []byte, theirIdentity []byt
BundleID: bundleID, BundleID: bundleID,
} }
err = stmt.QueryRow(theirIdentity, bundleID).Scan( err = stmt.QueryRow(theirIdentity, installationID, bundleID).Scan(
&ratchetInfo.Identity, &ratchetInfo.Identity,
&ratchetInfo.Sk, &ratchetInfo.Sk,
&ratchetInfo.PrivateKey, &ratchetInfo.PrivateKey,
@ -368,7 +377,7 @@ func (s *SQLLitePersistence) GetRatchetInfo(bundleID []byte, theirIdentity []byt
// GetAnyRatchetInfo retrieves any existing RatchetInfo for a specified interlocutor public key from the database // GetAnyRatchetInfo retrieves any existing RatchetInfo for a specified interlocutor public key from the database
func (s *SQLLitePersistence) GetAnyRatchetInfo(identity []byte, installationID string) (*RatchetInfo, error) { func (s *SQLLitePersistence) GetAnyRatchetInfo(identity []byte, installationID string) (*RatchetInfo, error) {
stmt, err := s.db.Prepare("SELECT symmetric_key, bundles.private_key, signed_pre_key, bundle_id, ephemeral_key FROM ratchet_info JOIN bundles ON bundle_id = signed_pre_key WHERE expired = 0 AND ratchet_info.identity = ? AND ratchet_info.installation_id = ? LIMIT 1") stmt, err := s.db.Prepare("SELECT symmetric_key, bundles.private_key, signed_pre_key, bundle_id, ephemeral_key FROM ratchet_info_v2 JOIN bundles ON bundle_id = signed_pre_key WHERE expired = 0 AND ratchet_info_v2.identity = ? AND ratchet_info_v2.installation_id = ? LIMIT 1")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -399,8 +408,8 @@ func (s *SQLLitePersistence) GetAnyRatchetInfo(identity []byte, installationID s
// RatchetInfoConfirmed clears the ephemeral key in the RatchetInfo // RatchetInfoConfirmed clears the ephemeral key in the RatchetInfo
// associated with the specified bundle ID and interlocutor identity public key // associated with the specified bundle ID and interlocutor identity public key
func (s *SQLLitePersistence) RatchetInfoConfirmed(bundleID []byte, theirIdentity []byte) error { func (s *SQLLitePersistence) RatchetInfoConfirmed(bundleID []byte, theirIdentity []byte, installationID string) error {
stmt, err := s.db.Prepare("UPDATE ratchet_info SET ephemeral_key = NULL WHERE identity = ? AND bundle_id = ?") stmt, err := s.db.Prepare("UPDATE ratchet_info_v2 SET ephemeral_key = NULL WHERE identity = ? AND bundle_id = ? AND installation_id = ?")
if err != nil { if err != nil {
return err return err
} }
@ -409,6 +418,7 @@ func (s *SQLLitePersistence) RatchetInfoConfirmed(bundleID []byte, theirIdentity
_, err = stmt.Exec( _, err = stmt.Exec(
theirIdentity, theirIdentity,
bundleID, bundleID,
installationID,
) )
return err return err

View File

@ -134,6 +134,42 @@ func (s *SQLLitePersistenceTestSuite) TestMultiplePublicBundle() {
} }
func (s *SQLLitePersistenceTestSuite) TestMultiDevicePublicBundle() {
key, err := crypto.GenerateKey()
s.Require().NoError(err)
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey)
s.Require().NoError(err, "It does not return an error if the bundle is not there")
s.Nil(actualBundle)
bundleContainer, err := NewBundleContainer(key, "1")
s.Require().NoError(err)
bundle := bundleContainer.GetBundle()
err = s.service.AddPublicBundle(bundle)
s.Require().NoError(err)
// Adding it again does not throw an error
err = s.service.AddPublicBundle(bundle)
s.Require().NoError(err)
// Adding a different bundle from a different instlation id
bundleContainer, err = NewBundleContainer(key, "2")
s.Require().NoError(err)
bundle = bundleContainer.GetBundle()
err = s.service.AddPublicBundle(bundle)
s.Require().NoError(err)
// Returns the most recent bundle
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey)
s.Require().NoError(err)
s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the identity")
s.NotNil(actualBundle.GetSignedPreKeys()["1"])
s.NotNil(actualBundle.GetSignedPreKeys()["2"])
}
func (s *SQLLitePersistenceTestSuite) TestRatchetInfoPrivateBundle() { func (s *SQLLitePersistenceTestSuite) TestRatchetInfoPrivateBundle() {
key, err := crypto.GenerateKey() key, err := crypto.GenerateKey()
s.Require().NoError(err) s.Require().NoError(err)
@ -154,9 +190,10 @@ func (s *SQLLitePersistenceTestSuite) TestRatchetInfoPrivateBundle() {
) )
s.Require().NoError(err) s.Require().NoError(err)
ratchetInfo, err := s.service.GetRatchetInfo(bundle.GetBundle().GetSignedPreKeys()["2"].GetSignedPreKey(), []byte("their-public-key")) ratchetInfo, err := s.service.GetRatchetInfo(bundle.GetBundle().GetSignedPreKeys()["2"].GetSignedPreKey(), []byte("their-public-key"), "1")
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(ratchetInfo)
s.NotNil(ratchetInfo.ID, "It adds an id") s.NotNil(ratchetInfo.ID, "It adds an id")
s.Equal(ratchetInfo.PrivateKey, bundle.GetPrivateSignedPreKey(), "It returns the private key") s.Equal(ratchetInfo.PrivateKey, bundle.GetPrivateSignedPreKey(), "It returns the private key")
s.Equal(ratchetInfo.Sk, []byte("symmetric-key"), "It returns the symmetric key") s.Equal(ratchetInfo.Sk, []byte("symmetric-key"), "It returns the symmetric key")
@ -191,7 +228,7 @@ func (s *SQLLitePersistenceTestSuite) TestRatchetInfoPublicBundle() {
) )
s.Require().NoError(err) s.Require().NoError(err)
ratchetInfo, err := s.service.GetRatchetInfo(signedPreKey, theirPublicKey) ratchetInfo, err := s.service.GetRatchetInfo(signedPreKey, theirPublicKey, installationID)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(ratchetInfo, "It returns the ratchet info") s.Require().NotNil(ratchetInfo, "It returns the ratchet info")
@ -227,7 +264,7 @@ func (s *SQLLitePersistenceTestSuite) TestRatchetInfoNoBundle() {
s.Error(err, "It returns an error") s.Error(err, "It returns an error")
_, err = s.service.GetRatchetInfo([]byte("non-existing-bundle"), []byte("their-public-key")) _, err = s.service.GetRatchetInfo([]byte("non-existing-bundle"), []byte("their-public-key"), "none")
s.Require().NoError(err) s.Require().NoError(err)
ratchetInfo, err := s.service.GetAnyRatchetInfo([]byte("their-public-key"), "4") ratchetInfo, err := s.service.GetAnyRatchetInfo([]byte("their-public-key"), "4")

View File

@ -12,8 +12,8 @@ func toTopic(s string) whisper.TopicType {
return whisper.BytesToTopic(crypto.Keccak256([]byte(s))) return whisper.BytesToTopic(crypto.Keccak256([]byte(s)))
} }
func defaultWhisperMessage() *whisper.NewMessage { func defaultWhisperMessage() whisper.NewMessage {
msg := &whisper.NewMessage{} msg := whisper.NewMessage{}
msg.TTL = 10 msg.TTL = 10
msg.PowTarget = 0.002 msg.PowTarget = 0.002
@ -22,7 +22,7 @@ func defaultWhisperMessage() *whisper.NewMessage {
return msg return msg
} }
func PublicMessageToWhisper(rpcMsg *SendPublicMessageRPC, payload []byte) *whisper.NewMessage { func PublicMessageToWhisper(rpcMsg SendPublicMessageRPC, payload []byte) whisper.NewMessage {
msg := defaultWhisperMessage() msg := defaultWhisperMessage()
msg.Topic = toTopic(rpcMsg.Chat) msg.Topic = toTopic(rpcMsg.Chat)
@ -33,7 +33,7 @@ func PublicMessageToWhisper(rpcMsg *SendPublicMessageRPC, payload []byte) *whisp
return msg return msg
} }
func DirectMessageToWhisper(rpcMsg *SendDirectMessageRPC, payload []byte) *whisper.NewMessage { func DirectMessageToWhisper(rpcMsg SendDirectMessageRPC, payload []byte) whisper.NewMessage {
msg := defaultWhisperMessage() msg := defaultWhisperMessage()

View File

@ -9,7 +9,7 @@ import (
) )
func TestPublicMessageToWhisper(t *testing.T) { func TestPublicMessageToWhisper(t *testing.T) {
rpcMessage := &SendPublicMessageRPC{ rpcMessage := SendPublicMessageRPC{
Chat: "test-chat", Chat: "test-chat",
Sig: "test", Sig: "test",
} }
@ -24,7 +24,7 @@ func TestPublicMessageToWhisper(t *testing.T) {
} }
func TestDirectMessageToWhisper(t *testing.T) { func TestDirectMessageToWhisper(t *testing.T) {
rpcMessage := &SendDirectMessageRPC{ rpcMessage := SendDirectMessageRPC{
PubKey: []byte("some pubkey"), PubKey: []byte("some pubkey"),
Sig: "test", Sig: "test",
} }

View File

@ -56,6 +56,7 @@ func buildSignatureMaterial(signedPreKeys *map[string]*SignedPreKey) []byte {
signedPreKey := (*signedPreKeys)[installationID] signedPreKey := (*signedPreKeys)[installationID]
signatureMaterial = append(signatureMaterial, []byte(installationID)...) signatureMaterial = append(signatureMaterial, []byte(installationID)...)
signatureMaterial = append(signatureMaterial, signedPreKey.SignedPreKey...) signatureMaterial = append(signatureMaterial, signedPreKey.SignedPreKey...)
signatureMaterial = append(signatureMaterial, []byte(fmt.Sprint(signedPreKey.Version))...)
} }
return signatureMaterial return signatureMaterial

View File

@ -64,6 +64,7 @@ func TestNewBundleContainer(t *testing.T) {
require.NotNil(t, bundle, "Bundle should be generated without errors") require.NotNil(t, bundle, "Bundle should be generated without errors")
signatureMaterial := append([]byte(bobInstallationID), bundle.GetSignedPreKeys()[bobInstallationID].GetSignedPreKey()...) signatureMaterial := append([]byte(bobInstallationID), bundle.GetSignedPreKeys()[bobInstallationID].GetSignedPreKey()...)
signatureMaterial = append(signatureMaterial, []byte("0")...)
recoveredPublicKey, err := crypto.SigToPub( recoveredPublicKey, err := crypto.SigToPub(
crypto.Keccak256(signatureMaterial), crypto.Keccak256(signatureMaterial),
bundle.Signature, bundle.Signature,
@ -97,8 +98,10 @@ func TestSignBundle(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
signatureMaterial := append([]byte("1"), bundle1.GetSignedPreKeys()["1"].GetSignedPreKey()...) signatureMaterial := append([]byte("1"), bundle1.GetSignedPreKeys()["1"].GetSignedPreKey()...)
signatureMaterial = append(signatureMaterial, []byte("0")...)
signatureMaterial = append(signatureMaterial, []byte("2")...) signatureMaterial = append(signatureMaterial, []byte("2")...)
signatureMaterial = append(signatureMaterial, []byte("key")...) signatureMaterial = append(signatureMaterial, []byte("key")...)
signatureMaterial = append(signatureMaterial, []byte("0")...)
recoveredPublicKey, err := crypto.SigToPub( recoveredPublicKey, err := crypto.SigToPub(
crypto.Keccak256(signatureMaterial), crypto.Keccak256(signatureMaterial),

View File

@ -106,9 +106,9 @@ func (s *Service) InitProtocol(address string, password string) error {
return nil return nil
} }
func (s *Service) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *chat.Bundle) error { func (s *Service) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *chat.Bundle) ([]chat.IdentityAndIDPair, error) {
if s.protocol == nil { if s.protocol == nil {
return errProtocolNotInitialized return nil, errProtocolNotInitialized
} }
return s.protocol.ProcessPublicBundle(myIdentityKey, bundle) return s.protocol.ProcessPublicBundle(myIdentityKey, bundle)

View File

@ -31,3 +31,7 @@ func (h EnvelopeSignalHandler) MailServerRequestExpired(hash common.Hash) {
func (h EnvelopeSignalHandler) DecryptMessageFailed(pubKey string) { func (h EnvelopeSignalHandler) DecryptMessageFailed(pubKey string) {
signal.SendDecryptMessageFailed(pubKey) signal.SendDecryptMessageFailed(pubKey)
} }
func (h EnvelopeSignalHandler) BundleAdded(identity string, installationID string) {
signal.SendBundleAdded(identity, installationID)
}

View File

@ -23,6 +23,9 @@ const (
// EventDecryptMessageFailed is triggered when we receive a message from a bundle we don't have // EventDecryptMessageFailed is triggered when we receive a message from a bundle we don't have
EventDecryptMessageFailed = "messages.decrypt.failed" EventDecryptMessageFailed = "messages.decrypt.failed"
// EventBundleAdded is triggered when we receive a bundle
EventBundleAdded = "bundles.added"
) )
// EnvelopeSignal includes hash of the envelope. // EnvelopeSignal includes hash of the envelope.
@ -42,6 +45,12 @@ type DecryptMessageFailedSignal struct {
Sender string `json:"sender"` Sender string `json:"sender"`
} }
// BundleAddedSignal holds the identity and installation id of the user
type BundleAddedSignal struct {
Identity string `json:"identity"`
InstallationID string `json:"installationID"`
}
// SendEnvelopeSent triggered when envelope delivered at least to 1 peer. // SendEnvelopeSent triggered when envelope delivered at least to 1 peer.
func SendEnvelopeSent(hash common.Hash) { func SendEnvelopeSent(hash common.Hash) {
send(EventEnvelopeSent, EnvelopeSignal{hash}) send(EventEnvelopeSent, EnvelopeSignal{hash})
@ -85,3 +94,7 @@ func SendEnodeDiscovered(enode, topic string) {
func SendDecryptMessageFailed(sender string) { func SendDecryptMessageFailed(sender string) {
send(EventDecryptMessageFailed, DecryptMessageFailedSignal{sender}) send(EventDecryptMessageFailed, DecryptMessageFailedSignal{sender})
} }
func SendBundleAdded(identity string, installationID string) {
send(EventBundleAdded, BundleAddedSignal{Identity: identity, InstallationID: installationID})
}

View File

@ -88,7 +88,7 @@ func ConfigCliFleetEthBetaJson() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "../config/cli/fleet-eth.beta.json", size: 3237, mode: os.FileMode(420), modTime: time.Unix(1537450683, 0)} info := bindataFileInfo{name: "../config/cli/fleet-eth.beta.json", size: 3237, mode: os.FileMode(420), modTime: time.Unix(1537514234, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
@ -108,7 +108,7 @@ func ConfigCliFleetEthStagingJson() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "../config/cli/fleet-eth.staging.json", size: 1838, mode: os.FileMode(420), modTime: time.Unix(1537450683, 0)} info := bindataFileInfo{name: "../config/cli/fleet-eth.staging.json", size: 1838, mode: os.FileMode(420), modTime: time.Unix(1537514234, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
@ -128,7 +128,7 @@ func ConfigCliFleetEthTestJson() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "../config/cli/fleet-eth.test.json", size: 1519, mode: os.FileMode(420), modTime: time.Unix(1537450683, 0)} info := bindataFileInfo{name: "../config/cli/fleet-eth.test.json", size: 1519, mode: os.FileMode(420), modTime: time.Unix(1537514234, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
@ -148,12 +148,12 @@ func ConfigCliLesEnabledJson() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "../config/cli/les-enabled.json", size: 58, mode: os.FileMode(420), modTime: time.Unix(1537450683, 0)} info := bindataFileInfo{name: "../config/cli/les-enabled.json", size: 58, mode: os.FileMode(420), modTime: time.Unix(1536858252, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
var _ConfigCliMailserverEnabledJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xaa\xe6\x52\x50\x50\x50\x50\x0a\xcf\xc8\x2c\x2e\x48\x2d\x72\xce\xcf\x4b\xcb\x4c\x57\xb2\x52\x80\x08\x83\xa5\x5c\xf3\x12\x93\x72\x52\x7d\x13\x33\x73\x82\x53\x8b\xca\x52\x8b\x94\xac\x14\x4a\x8a\x4a\x53\x75\x10\x2a\x10\x72\x01\x89\xc5\xc5\xe5\xf9\x45\x29\x4a\x56\x0a\x4a\xc5\x25\x89\x25\xa5\xc5\xba\xf9\x69\x69\x39\x99\x79\xa9\xba\x99\x79\x49\xf9\x15\x4a\x60\x4d\xb5\x5c\xb5\x5c\x80\x00\x00\x00\xff\xff\x84\xf6\x09\xc4\x78\x00\x00\x00") var _ConfigCliMailserverEnabledJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xaa\xe6\x52\x50\x50\x50\x50\x0a\xcf\xc8\x2c\x2e\x48\x2d\x72\xce\xcf\x4b\xcb\x4c\x57\xb2\x52\x80\x08\x83\xa5\x5c\xf3\x12\x93\x72\x52\x53\x94\xac\x14\x4a\x8a\x4a\x53\x75\xd0\x25\xfc\x42\x02\x82\x2b\xf3\x92\x71\x49\xfb\x26\x66\xe6\x04\xa7\x16\x95\xa5\x16\x61\xaa\x40\xc8\x05\x24\x16\x17\x97\xe7\x17\x81\x2c\x51\x2a\x2e\x49\x2c\x29\x2d\xd6\xcd\x4f\x4b\xcb\xc9\xcc\x4b\xd5\xcd\xcc\x4b\xca\xaf\x50\x02\x6b\xaa\xe5\xaa\xe5\x02\x04\x00\x00\xff\xff\x7c\x73\xee\xbb\xb0\x00\x00\x00")
func ConfigCliMailserverEnabledJsonBytes() ([]byte, error) { func ConfigCliMailserverEnabledJsonBytes() ([]byte, error) {
return bindataRead( return bindataRead(
@ -168,7 +168,7 @@ func ConfigCliMailserverEnabledJson() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "../config/cli/mailserver-enabled.json", size: 120, mode: os.FileMode(420), modTime: time.Unix(1537450683, 0)} info := bindataFileInfo{name: "../config/cli/mailserver-enabled.json", size: 176, mode: os.FileMode(420), modTime: time.Unix(1538032850, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
@ -188,7 +188,7 @@ func ConfigStatusChainGenesisJson() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "../config/status-chain-genesis.json", size: 612, mode: os.FileMode(420), modTime: time.Unix(1537450683, 0)} info := bindataFileInfo{name: "../config/status-chain-genesis.json", size: 612, mode: os.FileMode(420), modTime: time.Unix(1536858252, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }

View File

@ -0,0 +1,11 @@
DROP TABLE ratchet_info_v2;
CREATE TABLE ratchet_info (
bundle_id BLOB NOT NULL,
ephemeral_key BLOB,
identity BLOB NOT NULL,
symmetric_key BLOB NOT NULL,
installation_id TEXT NOT NULL,
UNIQUE(bundle_id, identity) ON CONFLICT REPLACE,
FOREIGN KEY (bundle_id) REFERENCES bundles(signed_pre_key)
);

View File

@ -0,0 +1,13 @@
DELETE FROM sessions;
DELETE FROM keys;
DROP TABLE ratchet_info;
CREATE TABLE ratchet_info_v2 (
bundle_id BLOB NOT NULL,
ephemeral_key BLOB,
identity BLOB NOT NULL,
symmetric_key BLOB NOT NULL,
installation_id TEXT NOT NULL,
UNIQUE(bundle_id, identity, installation_id) ON CONFLICT REPLACE,
FOREIGN KEY (bundle_id) REFERENCES bundles(signed_pre_key)
);