Add enabling/disabling of installations (#1264)

This commit adds a list new table, installations, which is used to keep
track of which installation are active for a given identity key.

In general, we limit the number of installation that we keep
synchronized to 5, to avoid excessive usage of resources.

Any installation coming from our own identity, will have to be manually
enabled, otherwise we trust the other peer has correctly paired their
devices.

We use a timestamp to decide which installations to keep synchronized as
a logical clock would have make the creation of the bundle more
complicated, but this can always be converted to a logical clock at
later stages without breaking compatibility.
This commit is contained in:
Andrea Maria Piana 2018-11-06 09:05:32 +01:00 committed by GitHub
parent 3fe5b25ff2
commit 1f6cccd0fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 733 additions and 221 deletions

View File

@ -517,12 +517,6 @@ func (b *StatusBackend) ExtractIdentityFromContactCode(contactCode string) (stri
return chat.ExtractIdentity(bundle)
}
// DEPRECATED
// VerifyGroupMembershipSignatures verifies that the signatures are valid
func (b *StatusBackend) VerifyGroupMembershipSignatures(signaturePairs [][3]string) error {
return crypto.VerifySignatures(signaturePairs)
}
// ExtractGroupMembershipSignatures extract signatures from tuples of content/signature
func (b *StatusBackend) ExtractGroupMembershipSignatures(signaturePairs [][2]string) ([]string, error) {
return crypto.ExtractSignatures(signaturePairs)
@ -537,3 +531,43 @@ func (b *StatusBackend) SignGroupMembership(content string) (string, error) {
return crypto.Sign(content, selectedAccount.AccountKey.PrivateKey)
}
// EnableInstallation enables an installation for multi-device sync.
func (b *StatusBackend) EnableInstallation(installationID string) error {
selectedAccount, err := b.AccountManager().SelectedAccount()
if err != nil {
return err
}
st, err := b.statusNode.ShhExtService()
if err != nil {
return err
}
if err := st.EnableInstallation(&selectedAccount.AccountKey.PrivateKey.PublicKey, installationID); err != nil {
b.log.Error("error enabling installation", "err", err)
return err
}
return nil
}
// DisableInstallation disables an installation for multi-device sync.
func (b *StatusBackend) DisableInstallation(installationID string) error {
selectedAccount, err := b.AccountManager().SelectedAccount()
if err != nil {
return err
}
st, err := b.statusNode.ShhExtService()
if err != nil {
return err
}
if err := st.DisableInstallation(&selectedAccount.AccountKey.PrivateKey.PublicKey, installationID); err != nil {
b.log.Error("error disabling installation", "err", err)
return err
}
return nil
}

View File

@ -94,29 +94,6 @@ func ExtractIdentityFromContactCode(bundleString *C.char) *C.char {
return C.CString(string(data))
}
// VerifyGroupMembershipSignatures ensure the signature pairs are valid
//export VerifyGroupMembershipSignatures
func VerifyGroupMembershipSignatures(signaturePairsStr *C.char) *C.char {
var signaturePairs [][3]string
if err := json.Unmarshal([]byte(C.GoString(signaturePairsStr)), &signaturePairs); err != nil {
return makeJSONResponse(err)
}
if err := statusBackend.VerifyGroupMembershipSignatures(signaturePairs); err != nil {
return makeJSONResponse(err)
}
data, err := json.Marshal(struct {
Success bool `json:"success"`
}{Success: true})
if err != nil {
return makeJSONResponse(err)
}
return C.CString(string(data))
}
// ExtractGroupMembershipSignatures extract public keys from tuples of content/signature
//export ExtractGroupMembershipSignatures
func ExtractGroupMembershipSignatures(signaturePairsStr *C.char) *C.char {
@ -159,6 +136,42 @@ func SignGroupMembership(content *C.char) *C.char {
return C.CString(string(data))
}
// EnableInstallation enables an installation for multi-device sync.
//export EnableInstallation
func EnableInstallation(installationID *C.char) *C.char {
err := statusBackend.EnableInstallation(C.GoString(installationID))
if err != nil {
return makeJSONResponse(err)
}
data, err := json.Marshal(struct {
Response string `json:"response"`
}{Response: "ok"})
if err != nil {
return makeJSONResponse(err)
}
return C.CString(string(data))
}
// DisableInstallation disables an installation for multi-device sync.
//export DisableInstallation
func DisableInstallation(installationID *C.char) *C.char {
err := statusBackend.DisableInstallation(C.GoString(installationID))
if err != nil {
return makeJSONResponse(err)
}
data, err := json.Marshal(struct {
Response string `json:"response"`
}{Response: "ok"})
if err != nil {
return makeJSONResponse(err)
}
return C.CString(string(data))
}
//ValidateNodeConfig validates config for status node
//export ValidateNodeConfig
func ValidateNodeConfig(configJSON *C.char) *C.char {

View File

@ -3,9 +3,11 @@
package chat
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@ -39,6 +41,7 @@ func (*ChatMessagePayload) ProtoMessage() {}
func (*ChatMessagePayload) Descriptor() ([]byte, []int) {
return fileDescriptor_8c585a45e2093e54, []int{0}
}
func (m *ChatMessagePayload) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChatMessagePayload.Unmarshal(m, b)
}
@ -106,6 +109,7 @@ func (*ContactUpdatePayload) ProtoMessage() {}
func (*ContactUpdatePayload) Descriptor() ([]byte, []int) {
return fileDescriptor_8c585a45e2093e54, []int{1}
}
func (m *ContactUpdatePayload) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ContactUpdatePayload.Unmarshal(m, b)
}
@ -168,6 +172,7 @@ func (*OneToOneRPC) ProtoMessage() {}
func (*OneToOneRPC) Descriptor() ([]byte, []int) {
return fileDescriptor_8c585a45e2093e54, []int{2}
}
func (m *OneToOneRPC) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_OneToOneRPC.Unmarshal(m, b)
}
@ -222,6 +227,7 @@ func (*ContactUpdateRPC) ProtoMessage() {}
func (*ContactUpdateRPC) Descriptor() ([]byte, []int) {
return fileDescriptor_8c585a45e2093e54, []int{3}
}
func (m *ContactUpdateRPC) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ContactUpdateRPC.Unmarshal(m, b)
}
@ -275,6 +281,7 @@ func (*ChatProtocolMessage) ProtoMessage() {}
func (*ChatProtocolMessage) Descriptor() ([]byte, []int) {
return fileDescriptor_8c585a45e2093e54, []int{4}
}
func (m *ChatProtocolMessage) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChatProtocolMessage.Unmarshal(m, b)
}

View File

@ -4,6 +4,7 @@ import (
"bytes"
"crypto/ecdsa"
"errors"
"fmt"
ecrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies"
@ -18,19 +19,22 @@ 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
// Max number of installations we keep synchronized.
const maxInstallations = 5
// If we have no bundles, we use a constant so that the message can reach any device.
const noInstallationID = "none"
// How many consecutive messages can be skipped in the receiving chain
// How many consecutive messages can be skipped in the receiving chain.
const maxSkip = 1000
// Any message with seqNo <= currentSeq - maxKeep will be deleted
// Any message with seqNo <= currentSeq - maxKeep will be deleted.
const maxKeep = 3000
// How many keys do we store in total per session
// How many keys do we store in total per session.
const maxMessageKeysPerSession = 2000
// 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 {
log log.Logger
persistence PersistenceService
@ -40,7 +44,7 @@ type EncryptionService struct {
type IdentityAndIDPair [2]string
// NewEncryptionService creates a new EncryptionService instance
// NewEncryptionService creates a new EncryptionService instance.
func NewEncryptionService(p PersistenceService, installationID string) *EncryptionService {
logger := log.New("package", "status-go/services/sshext.chat")
logger.Info("Initialized encryption service", "installationID", installationID)
@ -64,13 +68,21 @@ func (s *EncryptionService) keyFromActiveX3DH(theirIdentityKey []byte, theirSign
// CreateBundle retrieves or creates an X3DH bundle given a private key
func (s *EncryptionService) CreateBundle(privateKey *ecdsa.PrivateKey) (*Bundle, error) {
ourIdentityKeyC := ecrypto.CompressPubkey(&privateKey.PublicKey)
bundleContainer, err := s.persistence.GetAnyPrivateBundle(ourIdentityKeyC)
installationIDs, err := s.persistence.GetActiveInstallations(maxInstallations-1, ourIdentityKeyC)
if err != nil {
return nil, err
}
installationIDs = append(installationIDs, s.installationID)
bundleContainer, err := s.persistence.GetAnyPrivateBundle(ourIdentityKeyC, installationIDs)
if err != nil {
return nil, err
}
// If the bundle has expired we create a new one
if bundleContainer != nil && bundleContainer.Timestamp < time.Now().AddDate(0, 0, -14).UnixNano() {
if bundleContainer != nil && bundleContainer.GetBundle().Timestamp < time.Now().AddDate(0, 0, -14).UnixNano() {
// Mark sessions has expired
if err := s.persistence.MarkBundleExpired(bundleContainer.GetBundle().GetIdentity()); err != nil {
return nil, err
@ -143,6 +155,16 @@ func (s *EncryptionService) keyFromPassiveX3DH(myIdentityKey *ecdsa.PrivateKey,
return key, nil
}
func (s *EncryptionService) EnableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
myIdentityKeyC := ecrypto.CompressPubkey(myIdentityKey)
return s.persistence.EnableInstallation(myIdentityKeyC, installationID)
}
func (s *EncryptionService) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
myIdentityKeyC := ecrypto.CompressPubkey(myIdentityKey)
return s.persistence.DisableInstallation(myIdentityKeyC, installationID)
}
// 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
@ -151,16 +173,27 @@ func (s *EncryptionService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey,
return nil, err
}
signedPreKeys := b.GetSignedPreKeys()
response := make([]IdentityAndIDPair, len(signedPreKeys))
var response []IdentityAndIDPair
var installationIDs []string
myIdentityStr := fmt.Sprintf("0x%x", ecrypto.FromECDSAPub(&myIdentityKey.PublicKey))
if err = s.persistence.AddPublicBundle(b); err != nil {
// Any device from other peers will be considered enabled, ours needs to
// be explicitly enabled
fromOurIdentity := identity != myIdentityStr
for installationID := range signedPreKeys {
if installationID != s.installationID {
installationIDs = append(installationIDs, installationID)
response = append(response, IdentityAndIDPair{identity, installationID})
}
}
if err = s.persistence.AddInstallations(b.GetIdentity(), b.GetTimestamp(), installationIDs, fromOurIdentity); err != nil {
return nil, err
}
index := 0
for installationID := range signedPreKeys {
response[index] = IdentityAndIDPair{identity, installationID}
index++
if err = s.persistence.AddPublicBundle(b); err != nil {
return nil, err
}
return response, nil
@ -414,8 +447,13 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)
installationIDs, err := s.persistence.GetActiveInstallations(maxInstallations, theirIdentityKeyC)
if err != nil {
return nil, err
}
// Get their latest bundle
theirBundle, err := s.persistence.GetPublicBundle(theirIdentityKey)
theirBundle, err := s.persistence.GetPublicBundle(theirIdentityKey, installationIDs)
if err != nil {
return nil, err
}
@ -452,9 +490,8 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
if drInfo.EphemeralKey != nil {
dmp.X3DHHeader = &X3DHHeader{
Key: drInfo.EphemeralKey,
Id: drInfo.BundleID,
InstallationId: s.installationID,
Key: drInfo.EphemeralKey,
Id: drInfo.BundleID,
}
}
@ -462,50 +499,41 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
continue
}
// check if a bundle is there
theirBundle, err := s.persistence.GetPublicBundle(theirIdentityKey)
sharedKey, ourEphemeralKey, err := s.keyFromActiveX3DH(theirIdentityKeyC, theirSignedPreKey, myIdentityKey)
if err != nil {
return nil, err
}
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)
ourEphemeralKeyC := ecrypto.CompressPubkey(ourEphemeralKey)
err = s.persistence.AddRatchetInfo(sharedKey, theirIdentityKeyC, theirSignedPreKey, ourEphemeralKeyC, installationID)
if err != nil {
return nil, err
}
if theirBundle != nil {
sharedKey, ourEphemeralKey, err := s.keyFromActiveX3DH(theirIdentityKeyC, theirSignedPreKey, myIdentityKey)
if err != nil {
return nil, err
}
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)
ourEphemeralKeyC := ecrypto.CompressPubkey(ourEphemeralKey)
x3dhHeader := &X3DHHeader{
Key: ourEphemeralKeyC,
Id: theirSignedPreKey,
}
err = s.persistence.AddRatchetInfo(sharedKey, theirIdentityKeyC, theirSignedPreKey, ourEphemeralKeyC, installationID)
drInfo, err = s.persistence.GetAnyRatchetInfo(theirIdentityKeyC, installationID)
if err != nil {
return nil, err
}
if drInfo != nil {
encryptedPayload, drHeader, err := s.encryptUsingDR(theirIdentityKey, drInfo, payload)
if err != nil {
return nil, err
}
x3dhHeader := &X3DHHeader{
Key: ourEphemeralKeyC,
Id: theirSignedPreKey,
InstallationId: s.installationID,
dmp := &DirectMessageProtocol{
Payload: encryptedPayload,
X3DHHeader: x3dhHeader,
DRHeader: drHeader,
}
drInfo, err := s.persistence.GetAnyRatchetInfo(theirIdentityKeyC, installationID)
if err != nil {
return nil, err
}
if drInfo != nil {
encryptedPayload, drHeader, err := s.encryptUsingDR(theirIdentityKey, drInfo, payload)
if err != nil {
return nil, err
}
dmp := &DirectMessageProtocol{
Payload: encryptedPayload,
X3DHHeader: x3dhHeader,
DRHeader: drHeader,
}
response[drInfo.InstallationID] = dmp
}
response[drInfo.InstallationID] = dmp
}
}

View File

@ -3,9 +3,11 @@
package chat
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@ -32,6 +34,7 @@ func (*SignedPreKey) ProtoMessage() {}
func (*SignedPreKey) Descriptor() ([]byte, []int) {
return fileDescriptor_8293a649ce9418c6, []int{0}
}
func (m *SignedPreKey) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SignedPreKey.Unmarshal(m, b)
}
@ -71,7 +74,9 @@ type Bundle struct {
// Installation id
SignedPreKeys map[string]*SignedPreKey `protobuf:"bytes,2,rep,name=signed_pre_keys,json=signedPreKeys,proto3" json:"signed_pre_keys,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// Prekey signature
Signature []byte `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"`
Signature []byte `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"`
// When the bundle was created locally
Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -83,6 +88,7 @@ func (*Bundle) ProtoMessage() {}
func (*Bundle) Descriptor() ([]byte, []int) {
return fileDescriptor_8293a649ce9418c6, []int{1}
}
func (m *Bundle) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Bundle.Unmarshal(m, b)
}
@ -122,13 +128,18 @@ func (m *Bundle) GetSignature() []byte {
return nil
}
func (m *Bundle) GetTimestamp() int64 {
if m != nil {
return m.Timestamp
}
return 0
}
type BundleContainer struct {
// X3DH prekey bundle
Bundle *Bundle `protobuf:"bytes,1,opt,name=bundle,proto3" json:"bundle,omitempty"`
// Private signed prekey
PrivateSignedPreKey []byte `protobuf:"bytes,2,opt,name=private_signed_pre_key,json=privateSignedPreKey,proto3" json:"private_signed_pre_key,omitempty"`
// Local time creation
Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
PrivateSignedPreKey []byte `protobuf:"bytes,2,opt,name=private_signed_pre_key,json=privateSignedPreKey,proto3" json:"private_signed_pre_key,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -140,6 +151,7 @@ func (*BundleContainer) ProtoMessage() {}
func (*BundleContainer) Descriptor() ([]byte, []int) {
return fileDescriptor_8293a649ce9418c6, []int{2}
}
func (m *BundleContainer) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_BundleContainer.Unmarshal(m, b)
}
@ -172,13 +184,6 @@ func (m *BundleContainer) GetPrivateSignedPreKey() []byte {
return nil
}
func (m *BundleContainer) GetTimestamp() int64 {
if m != nil {
return m.Timestamp
}
return 0
}
type DRHeader struct {
// Current ratchet public key
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
@ -199,6 +204,7 @@ func (*DRHeader) ProtoMessage() {}
func (*DRHeader) Descriptor() ([]byte, []int) {
return fileDescriptor_8293a649ce9418c6, []int{3}
}
func (m *DRHeader) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DRHeader.Unmarshal(m, b)
}
@ -259,6 +265,7 @@ func (*DHHeader) ProtoMessage() {}
func (*DHHeader) Descriptor() ([]byte, []int) {
return fileDescriptor_8293a649ce9418c6, []int{4}
}
func (m *DHHeader) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DHHeader.Unmarshal(m, b)
}
@ -288,9 +295,7 @@ type X3DHHeader struct {
// Ephemeral key used
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
// Used bundle's signed prekey
Id []byte `protobuf:"bytes,4,opt,name=id,proto3" json:"id,omitempty"`
// DEPRECATED: The device id
InstallationId string `protobuf:"bytes,3,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"`
Id []byte `protobuf:"bytes,4,opt,name=id,proto3" json:"id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -302,6 +307,7 @@ func (*X3DHHeader) ProtoMessage() {}
func (*X3DHHeader) Descriptor() ([]byte, []int) {
return fileDescriptor_8293a649ce9418c6, []int{5}
}
func (m *X3DHHeader) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_X3DHHeader.Unmarshal(m, b)
}
@ -334,13 +340,6 @@ func (m *X3DHHeader) GetId() []byte {
return nil
}
func (m *X3DHHeader) GetInstallationId() string {
if m != nil {
return m.InstallationId
}
return ""
}
// Direct message value
type DirectMessageProtocol struct {
X3DHHeader *X3DHHeader `protobuf:"bytes,1,opt,name=X3DH_header,json=X3DHHeader,proto3" json:"X3DH_header,omitempty"`
@ -359,6 +358,7 @@ func (*DirectMessageProtocol) ProtoMessage() {}
func (*DirectMessageProtocol) Descriptor() ([]byte, []int) {
return fileDescriptor_8293a649ce9418c6, []int{6}
}
func (m *DirectMessageProtocol) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DirectMessageProtocol.Unmarshal(m, b)
}
@ -426,6 +426,7 @@ func (*ProtocolMessage) ProtoMessage() {}
func (*ProtocolMessage) Descriptor() ([]byte, []int) {
return fileDescriptor_8293a649ce9418c6, []int{7}
}
func (m *ProtocolMessage) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ProtocolMessage.Unmarshal(m, b)
}
@ -488,39 +489,39 @@ func init() {
func init() { proto.RegisterFile("encryption.proto", fileDescriptor_8293a649ce9418c6) }
var fileDescriptor_8293a649ce9418c6 = []byte{
// 539 bytes of a gzipped FileDescriptorProto
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,
0x28, 0x5c, 0xfb, 0x22, 0x3e, 0x6a, 0xc5, 0xaa, 0xa8, 0xc7, 0x2a, 0xe8, 0x8b, 0x84, 0x6d, 0x33,
0xde, 0x2d, 0xa6, 0x9b, 0xb0, 0xd9, 0x16, 0xfa, 0x17, 0xfc, 0x43, 0xbe, 0xf9, 0x5f, 0xfc, 0x27,
0x92, 0xdd, 0xa4, 0xdd, 0xf6, 0x7a, 0xe0, 0x5b, 0x66, 0x76, 0xf6, 0x9b, 0xef, 0xfb, 0x66, 0x27,
0xd0, 0x47, 0xb1, 0x94, 0xdb, 0x5c, 0xf1, 0x4c, 0x8c, 0x73, 0x99, 0xa9, 0x8c, 0x9c, 0x2d, 0x6f,
0x98, 0x0a, 0x3f, 0x82, 0xf7, 0x99, 0x5f, 0x0b, 0x4c, 0xae, 0x24, 0xbe, 0xc7, 0x2d, 0xb9, 0x00,
0xbf, 0xd0, 0x71, 0x9c, 0x4b, 0x8c, 0x7f, 0xe2, 0x36, 0x70, 0x46, 0x4e, 0xe4, 0x51, 0xaf, 0xb0,
0xab, 0x02, 0x38, 0xdf, 0xa0, 0x2c, 0x78, 0x26, 0x02, 0x77, 0xe4, 0x44, 0x5d, 0x5a, 0x87, 0xe1,
0x5f, 0x07, 0x9a, 0x2f, 0xd7, 0x22, 0x49, 0x91, 0x0c, 0xa0, 0xc5, 0x13, 0x14, 0x8a, 0xab, 0x1a,
0x64, 0x17, 0x93, 0x37, 0xd0, 0x3b, 0x6c, 0x53, 0x04, 0xee, 0xa8, 0x11, 0x75, 0x26, 0x8f, 0xc7,
0x25, 0xad, 0xb1, 0x81, 0x18, 0xdb, 0xd4, 0x8a, 0xd7, 0x42, 0xc9, 0x2d, 0xed, 0xda, 0x44, 0x0a,
0x32, 0x84, 0x76, 0x99, 0x60, 0x6a, 0x2d, 0x31, 0x38, 0xd3, 0x5d, 0xf6, 0x89, 0xc1, 0x17, 0x20,
0xb7, 0x21, 0x48, 0x1f, 0x1a, 0xb5, 0xb0, 0x36, 0x2d, 0x3f, 0x49, 0x04, 0xf7, 0x36, 0x2c, 0x5d,
0xa3, 0x56, 0xd3, 0x99, 0x10, 0x43, 0xc2, 0xbe, 0x4a, 0x4d, 0xc1, 0x0b, 0xf7, 0xb9, 0x13, 0xfe,
0x72, 0xa0, 0x67, 0x08, 0xbe, 0xca, 0x84, 0x62, 0x5c, 0xa0, 0x24, 0x17, 0xd0, 0x5c, 0xe8, 0x94,
0x86, 0xed, 0x4c, 0x3c, 0x5b, 0x07, 0xad, 0xce, 0xc8, 0x14, 0x1e, 0xe6, 0x92, 0x6f, 0x98, 0xc2,
0xf8, 0xc8, 0x65, 0x57, 0x53, 0xbf, 0x5f, 0x9d, 0x1e, 0x8c, 0x64, 0x08, 0x6d, 0xc5, 0x57, 0x58,
0x28, 0xb6, 0xca, 0x83, 0xc6, 0xc8, 0x89, 0x1a, 0x74, 0x9f, 0x08, 0xdf, 0x41, 0x6b, 0x46, 0xe7,
0xc8, 0x12, 0x94, 0xb6, 0x30, 0xcf, 0x08, 0xf3, 0xc0, 0xa9, 0x47, 0xe4, 0x08, 0xe2, 0x83, 0x9b,
0x0b, 0x0d, 0xd1, 0xa5, 0x6e, 0xae, 0x63, 0x9e, 0x54, 0xae, 0xb9, 0x3c, 0x09, 0x87, 0xd0, 0x9a,
0xcd, 0xef, 0xc2, 0x0a, 0xbf, 0x02, 0x7c, 0x9b, 0xde, 0x7d, 0x7e, 0x8c, 0x46, 0x9e, 0x42, 0x8f,
0x8b, 0x42, 0xb1, 0x34, 0x65, 0xe5, 0xb3, 0x8b, 0x79, 0xa2, 0x5b, 0xb7, 0xa9, 0x6f, 0xa7, 0xdf,
0x26, 0xe1, 0x1f, 0x07, 0x1e, 0xcc, 0xb8, 0xc4, 0xa5, 0xfa, 0x80, 0x45, 0xc1, 0xae, 0xf1, 0xaa,
0x7c, 0xa0, 0xcb, 0x2c, 0x25, 0x97, 0xd0, 0x29, 0x5b, 0xc6, 0x37, 0xba, 0x67, 0x65, 0x6d, 0xdf,
0x58, 0xbb, 0xe7, 0x42, 0x6d, 0x5e, 0xcf, 0xa0, 0x3d, 0xa3, 0xf5, 0x05, 0x33, 0x4e, 0xdf, 0x5c,
0xa8, 0x6d, 0xa2, 0x7b, 0xc3, 0xca, 0xe2, 0x1d, 0x3a, 0x1e, 0x14, 0xcf, 0x77, 0xc5, 0x35, 0x72,
0x00, 0xe7, 0x39, 0xdb, 0xa6, 0x19, 0x33, 0x3a, 0x3c, 0x5a, 0x87, 0xe1, 0x6f, 0x17, 0x7a, 0x35,
0xe7, 0x4a, 0xc2, 0x7f, 0x3e, 0x88, 0x13, 0x1e, 0xb9, 0xa7, 0x3c, 0x22, 0x9f, 0xc0, 0x4f, 0xb4,
0x45, 0xf1, 0xca, 0x34, 0x08, 0x50, 0xef, 0x4b, 0x64, 0x60, 0x8f, 0xba, 0x8f, 0x0f, 0xec, 0xac,
0x16, 0x27, 0xb1, 0x73, 0xe4, 0x09, 0xf8, 0xf9, 0x7a, 0x91, 0xf2, 0xe5, 0x0e, 0xf0, 0x87, 0x16,
0xd5, 0x35, 0xd9, 0xaa, 0x6c, 0xf0, 0x1d, 0xc8, 0x6d, 0xac, 0x13, 0x1b, 0x74, 0x79, 0xb8, 0x41,
0x8f, 0x2a, 0x17, 0x4f, 0x4d, 0xd5, 0x5a, 0xa5, 0x45, 0x53, 0xff, 0x8b, 0xa6, 0xff, 0x02, 0x00,
0x00, 0xff, 0xff, 0x42, 0x9c, 0x34, 0x10, 0x9f, 0x04, 0x00, 0x00,
// 535 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x5d, 0x8b, 0xd3, 0x40,
0x14, 0x25, 0x69, 0xb7, 0xdb, 0xde, 0xa6, 0x69, 0x19, 0x51, 0x42, 0x5d, 0xb0, 0x84, 0x15, 0x03,
0x42, 0x61, 0x5b, 0x1f, 0xc4, 0x47, 0xad, 0x58, 0x57, 0xd4, 0x65, 0xf4, 0xc1, 0x17, 0x09, 0xd3,
0xe6, 0xba, 0x3b, 0x98, 0x4e, 0xc2, 0x64, 0x5a, 0xe8, 0x2f, 0xf3, 0xcd, 0xbf, 0xa6, 0x64, 0x26,
0x69, 0xa7, 0xdd, 0x5d, 0xd8, 0xb7, 0xde, 0x8f, 0x39, 0xe7, 0xdc, 0x73, 0x7b, 0x03, 0x03, 0x14,
0x4b, 0xb9, 0xcd, 0x15, 0xcf, 0xc4, 0x38, 0x97, 0x99, 0xca, 0x48, 0x73, 0x79, 0xc3, 0x54, 0xf8,
0x05, 0xbc, 0x6f, 0xfc, 0x5a, 0x60, 0x72, 0x25, 0xf1, 0x13, 0x6e, 0xc9, 0x39, 0xf8, 0x85, 0x8e,
0xe3, 0x5c, 0x62, 0xfc, 0x1b, 0xb7, 0x81, 0x33, 0x72, 0x22, 0x8f, 0x7a, 0x85, 0xdd, 0x15, 0xc0,
0xe9, 0x06, 0x65, 0xc1, 0x33, 0x11, 0xb8, 0x23, 0x27, 0xea, 0xd1, 0x3a, 0x0c, 0xff, 0x39, 0xd0,
0x7a, 0xbb, 0x16, 0x49, 0x8a, 0x64, 0x08, 0x6d, 0x9e, 0xa0, 0x50, 0x5c, 0xd5, 0x20, 0xbb, 0x98,
0x7c, 0x80, 0xfe, 0x21, 0x4d, 0x11, 0xb8, 0xa3, 0x46, 0xd4, 0x9d, 0x3c, 0x1b, 0x97, 0xb2, 0xc6,
0x06, 0x62, 0x6c, 0x4b, 0x2b, 0xde, 0x0b, 0x25, 0xb7, 0xb4, 0x67, 0x0b, 0x29, 0xc8, 0x19, 0x74,
0xca, 0x04, 0x53, 0x6b, 0x89, 0x41, 0x53, 0xb3, 0xec, 0x13, 0x65, 0x55, 0xf1, 0x15, 0x16, 0x8a,
0xad, 0xf2, 0xe0, 0x64, 0xe4, 0x44, 0x0d, 0xba, 0x4f, 0x0c, 0xbf, 0x03, 0xb9, 0x4d, 0x40, 0x06,
0xd0, 0xa8, 0xc7, 0xee, 0xd0, 0xf2, 0x27, 0x89, 0xe0, 0x64, 0xc3, 0xd2, 0x35, 0xea, 0x59, 0xbb,
0x13, 0x62, 0x24, 0xda, 0x4f, 0xa9, 0x69, 0x78, 0xe3, 0xbe, 0x76, 0x42, 0x09, 0x7d, 0xa3, 0xfe,
0x5d, 0x26, 0x14, 0xe3, 0x02, 0x25, 0x39, 0x87, 0xd6, 0x42, 0xa7, 0x34, 0x6a, 0x77, 0xe2, 0xd9,
0x43, 0xd2, 0xaa, 0x46, 0xa6, 0xf0, 0x24, 0x97, 0x7c, 0xc3, 0x14, 0xc6, 0x47, 0x2b, 0x70, 0xf5,
0x5c, 0x8f, 0xaa, 0xaa, 0x4d, 0x7c, 0xd9, 0x6c, 0x37, 0x06, 0xcd, 0xf0, 0x12, 0xda, 0x33, 0x3a,
0x47, 0x96, 0xa0, 0xb4, 0xf5, 0x7b, 0x46, 0xbf, 0x07, 0x4e, 0xbd, 0x27, 0x47, 0x10, 0x1f, 0xdc,
0x5c, 0x04, 0x0d, 0x1d, 0xba, 0xb9, 0x8e, 0x79, 0x52, 0x59, 0xe7, 0xf2, 0x24, 0x3c, 0x83, 0xf6,
0x6c, 0x7e, 0x1f, 0x56, 0xf8, 0x0a, 0xe0, 0xc7, 0xf4, 0xfe, 0xfa, 0x31, 0x5a, 0xa5, 0xef, 0xaf,
0x03, 0x8f, 0x67, 0x5c, 0xe2, 0x52, 0x7d, 0xc6, 0xa2, 0x60, 0xd7, 0x78, 0x55, 0xfe, 0x05, 0x97,
0x59, 0x4a, 0x2e, 0xa0, 0x5b, 0xe2, 0xc5, 0x37, 0x1a, 0xb0, 0xf2, 0x67, 0x60, 0xfc, 0xd9, 0x13,
0x51, 0x9b, 0xf4, 0x25, 0x74, 0x66, 0xb4, 0x7e, 0x60, 0x56, 0xe2, 0x9b, 0x07, 0xb5, 0x07, 0x74,
0xef, 0x46, 0xd9, 0xbc, 0x43, 0xc7, 0x83, 0xe6, 0xf9, 0xae, 0xb9, 0x46, 0x0e, 0xe0, 0x34, 0x67,
0xdb, 0x34, 0x63, 0x89, 0xf6, 0xc7, 0xa3, 0x75, 0x18, 0xfe, 0x71, 0xa1, 0x5f, 0x6b, 0xae, 0x46,
0x78, 0xe0, 0x56, 0x5f, 0x40, 0x9f, 0x8b, 0x42, 0xb1, 0x34, 0x65, 0xe5, 0xf1, 0xc5, 0x3c, 0xd1,
0x9a, 0x3b, 0xd4, 0xb7, 0xd3, 0x1f, 0x13, 0xf2, 0x15, 0xfc, 0x44, 0x5b, 0x14, 0xaf, 0x0c, 0x41,
0x80, 0xfa, 0x22, 0x22, 0x03, 0x7b, 0xc4, 0x3e, 0x3e, 0xb0, 0xb3, 0x3a, 0x8d, 0xc4, 0xce, 0x91,
0xe7, 0xe0, 0xe7, 0xeb, 0x45, 0xca, 0x97, 0x3b, 0xc0, 0x5f, 0x7a, 0xa8, 0x9e, 0xc9, 0x56, 0x6d,
0xc3, 0x9f, 0x40, 0x6e, 0x63, 0xdd, 0x71, 0x05, 0x17, 0x87, 0x57, 0xf0, 0xb4, 0x72, 0xf1, 0xae,
0xad, 0x5a, 0xe7, 0xb0, 0x68, 0xe9, 0xaf, 0xcd, 0xf4, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xed,
0x53, 0xaf, 0x00, 0x81, 0x04, 0x00, 0x00,
}

View File

@ -15,16 +15,17 @@ message Bundle {
map<string,SignedPreKey> signed_pre_keys = 2;
// Prekey signature
bytes signature = 4;
// When the bundle was created locally
int64 timestamp = 5;
}
message BundleContainer {
reserved 3;
// X3DH prekey bundle
Bundle bundle = 1;
// Private signed prekey
bytes private_signed_pre_key = 2;
// Local time creation
int64 timestamp = 3;
}
message DRHeader {
@ -44,12 +45,11 @@ message DHHeader {
}
message X3DHHeader {
reserved 3;
// Ephemeral key used
bytes key = 1;
// Used bundle's signed prekey
bytes id = 4;
// DEPRECATED: The device id
string installation_id = 3;
}
// Direct message value

View File

@ -18,6 +18,7 @@ type EncryptionServiceMultiDeviceSuite struct {
bob1 *EncryptionService
alice2 *EncryptionService
bob2 *EncryptionService
alice3 *EncryptionService
}
func (s *EncryptionServiceMultiDeviceSuite) SetupTest() {
@ -26,6 +27,8 @@ func (s *EncryptionServiceMultiDeviceSuite) SetupTest() {
aliceDBKey1 = "alice1"
aliceDBPath2 = "/tmp/alice2.db"
aliceDBKey2 = "alice2"
aliceDBPath3 = "/tmp/alice3.db"
aliceDBKey3 = "alice3"
bobDBPath1 = "/tmp/bob1.db"
bobDBKey1 = "bob1"
bobDBPath2 = "/tmp/bob2.db"
@ -36,6 +39,7 @@ func (s *EncryptionServiceMultiDeviceSuite) SetupTest() {
os.Remove(bobDBPath1)
os.Remove(aliceDBPath2)
os.Remove(bobDBPath2)
os.Remove(aliceDBPath3)
alicePersistence1, err := NewSQLLitePersistence(aliceDBPath1, aliceDBKey1)
if err != nil {
@ -47,6 +51,11 @@ func (s *EncryptionServiceMultiDeviceSuite) SetupTest() {
panic(err)
}
alicePersistence3, err := NewSQLLitePersistence(aliceDBPath3, aliceDBKey3)
if err != nil {
panic(err)
}
bobPersistence1, err := NewSQLLitePersistence(bobDBPath1, bobDBKey1)
if err != nil {
panic(err)
@ -63,41 +72,79 @@ func (s *EncryptionServiceMultiDeviceSuite) SetupTest() {
s.alice2 = NewEncryptionService(alicePersistence2, "alice2")
s.bob2 = NewEncryptionService(bobPersistence2, "bob2")
s.alice3 = NewEncryptionService(alicePersistence3, "alice3")
}
func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundle() {
aliceKey, err := crypto.GenerateKey()
s.Require().NoError(err)
alice1Bundle, err := s.alice1.CreateBundle(aliceKey)
s.Require().NoError(err)
alice1Identity, err := ExtractIdentity(alice1Bundle)
s.Require().NoError(err)
alice2Bundle, err := s.alice2.CreateBundle(aliceKey)
s.Require().NoError(err)
alice2Identity, err := ExtractIdentity(alice2Bundle)
s.Require().NoError(err)
alice3Bundle, err := s.alice3.CreateBundle(aliceKey)
s.Require().NoError(err)
alice3Identity, err := ExtractIdentity(alice2Bundle)
s.Require().NoError(err)
// Add alice2 bundle
response, err := s.alice1.ProcessPublicBundle(aliceKey, alice2Bundle)
s.Require().NoError(err)
s.Require().Equal(IdentityAndIDPair{alice2Identity, "alice2"}, response[0])
// Add alice3 bundle
response, err = s.alice1.ProcessPublicBundle(aliceKey, alice3Bundle)
s.Require().NoError(err)
s.Require().Equal(IdentityAndIDPair{alice3Identity, "alice3"}, response[0])
// No installation is enabled
alice1MergedBundle1, err := s.alice1.CreateBundle(aliceKey)
s.Require().NoError(err)
s.Require().Equal(1, len(alice1MergedBundle1.GetSignedPreKeys()))
s.Require().NotNil(alice1MergedBundle1.GetSignedPreKeys()["alice1"])
s.Require().NotNil(alice1MergedBundle1.GetSignedPreKeys()["alice2"])
response, err = s.alice1.ProcessPublicBundle(aliceKey, alice1MergedBundle1)
// We enable the installations
err = s.alice1.EnableInstallation(&aliceKey.PublicKey, "alice2")
s.Require().NoError(err)
err = s.alice1.EnableInstallation(&aliceKey.PublicKey, "alice3")
s.Require().NoError(err)
alice1MergedBundle2, err := s.alice1.CreateBundle(aliceKey)
s.Require().NoError(err)
// We get back a bundle with all the installations
s.Require().Equal(3, len(alice1MergedBundle2.GetSignedPreKeys()))
s.Require().NotNil(alice1MergedBundle2.GetSignedPreKeys()["alice1"])
s.Require().NotNil(alice1MergedBundle2.GetSignedPreKeys()["alice2"])
s.Require().NotNil(alice1MergedBundle2.GetSignedPreKeys()["alice3"])
response, err = s.alice1.ProcessPublicBundle(aliceKey, alice1MergedBundle2)
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])
// We only get back installationIDs not equal to us
s.Require().Equal(2, len(response))
s.Require().Equal(IdentityAndIDPair{alice2Identity, "alice2"}, response[0])
s.Require().Equal(IdentityAndIDPair{alice2Identity, "alice3"}, response[1])
// We disable the installations
err = s.alice1.DisableInstallation(&aliceKey.PublicKey, "alice2")
s.Require().NoError(err)
alice1MergedBundle3, err := s.alice1.CreateBundle(aliceKey)
s.Require().NoError(err)
// We get back a bundle with all the installations
s.Require().Equal(2, len(alice1MergedBundle3.GetSignedPreKeys()))
s.Require().NotNil(alice1MergedBundle3.GetSignedPreKeys()["alice1"])
s.Require().NotNil(alice1MergedBundle3.GetSignedPreKeys()["alice3"])
}
func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundleOutOfOrder() {
@ -116,10 +163,14 @@ func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundleOutOfOrder()
_, err = s.alice2.CreateBundle(aliceKey)
s.Require().NoError(err)
// It should contain both bundles
alice1MergedBundle1, err := s.alice2.CreateBundle(aliceKey)
// We enable the installation
err = s.alice2.EnableInstallation(&aliceKey.PublicKey, "alice1")
s.Require().NoError(err)
s.Require().NotNil(alice1MergedBundle1.GetSignedPreKeys()["alice1"])
s.Require().NotNil(alice1MergedBundle1.GetSignedPreKeys()["alice2"])
// It should contain both bundles
alice2MergedBundle1, err := s.alice2.CreateBundle(aliceKey)
s.Require().NoError(err)
s.Require().NotNil(alice2MergedBundle1.GetSignedPreKeys()["alice1"])
s.Require().NotNil(alice2MergedBundle1.GetSignedPreKeys()["alice2"])
}

View File

@ -6,6 +6,8 @@
// 1539249977_update_ratchet_info.up.sql
// 1540715431_add_version.down.sql
// 1540715431_add_version.up.sql
// 1541164797_add_installations.down.sql
// 1541164797_add_installations.up.sql
// static.go
// DO NOT EDIT!
@ -169,7 +171,7 @@ func _1540715431_add_versionDownSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1540715431_add_version.down.sql", size: 127, mode: os.FileMode(420), modTime: time.Unix(1540989119, 0)}
info := bindataFileInfo{name: "1540715431_add_version.down.sql", size: 127, mode: os.FileMode(420), modTime: time.Unix(1541170185, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@ -189,7 +191,47 @@ func _1540715431_add_versionUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1540715431_add_version.up.sql", size: 265, mode: os.FileMode(420), modTime: time.Unix(1540989075, 0)}
info := bindataFileInfo{name: "1540715431_add_version.up.sql", size: 265, mode: os.FileMode(420), modTime: time.Unix(1541170185, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __1541164797_add_installationsDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xc8\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x2b\xb6\xe6\x02\x04\x00\x00\xff\xff\xd8\xbf\x14\x75\x1a\x00\x00\x00")
func _1541164797_add_installationsDownSqlBytes() ([]byte, error) {
return bindataRead(
__1541164797_add_installationsDownSql,
"1541164797_add_installations.down.sql",
)
}
func _1541164797_add_installationsDownSql() (*asset, error) {
bytes, err := _1541164797_add_installationsDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1541164797_add_installations.down.sql", size: 26, mode: os.FileMode(420), modTime: time.Unix(1541165366, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __1541164797_add_installationsUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\xce\xb1\x6a\xc3\x30\x14\x85\xe1\xdd\x4f\x71\x46\x1b\xbc\x74\xee\x24\xc9\xd7\x46\x70\xb9\x6a\x5d\x09\xba\x05\x05\x6b\x10\xd8\x4a\xc0\x5a\xf2\xf6\xc1\x43\x20\xce\xfc\x7f\x70\x8e\x99\x49\x79\x82\x57\x9a\x09\xb9\xec\x35\xae\x6b\xac\xf9\x56\x76\xa0\x6d\x80\xbc\xa4\x52\x73\x7d\x40\xb3\xd3\x10\xe7\x21\x81\xb9\x3f\xca\x1b\xbe\xe4\x05\x9e\xfe\xfd\x09\xd4\xbc\xa5\xbd\xc6\xed\x8e\x20\x7f\x76\x12\x1a\xa0\xed\x04\x2b\x67\x96\x4a\xbc\xae\x69\x81\x76\x8e\x49\x09\x06\x1a\x55\x60\x8f\xaf\x23\x06\xb1\xbf\x81\xda\xd7\x8b\xfe\x73\xb5\x83\x13\x18\x27\x23\x5b\xe3\x31\xd3\x0f\x2b\x43\x4d\xf7\xdd\x3c\x03\x00\x00\xff\xff\x28\x14\xac\x9d\xd8\x00\x00\x00")
func _1541164797_add_installationsUpSqlBytes() ([]byte, error) {
return bindataRead(
__1541164797_add_installationsUpSql,
"1541164797_add_installations.up.sql",
)
}
func _1541164797_add_installationsUpSql() (*asset, error) {
bytes, err := _1541164797_add_installationsUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1541164797_add_installations.up.sql", size: 216, mode: os.FileMode(420), modTime: time.Unix(1541165467, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@ -272,6 +314,8 @@ var _bindata = map[string]func() (*asset, error){
"1539249977_update_ratchet_info.up.sql": _1539249977_update_ratchet_infoUpSql,
"1540715431_add_version.down.sql": _1540715431_add_versionDownSql,
"1540715431_add_version.up.sql": _1540715431_add_versionUpSql,
"1541164797_add_installations.down.sql": _1541164797_add_installationsDownSql,
"1541164797_add_installations.up.sql": _1541164797_add_installationsUpSql,
"static.go": staticGo,
}
@ -321,6 +365,8 @@ var _bintree = &bintree{nil, map[string]*bintree{
"1539249977_update_ratchet_info.up.sql": &bintree{_1539249977_update_ratchet_infoUpSql, map[string]*bintree{}},
"1540715431_add_version.down.sql": &bintree{_1540715431_add_versionDownSql, map[string]*bintree{}},
"1540715431_add_version.up.sql": &bintree{_1540715431_add_versionUpSql, map[string]*bintree{}},
"1541164797_add_installations.down.sql": &bintree{_1541164797_add_installationsDownSql, map[string]*bintree{}},
"1541164797_add_installations.up.sql": &bintree{_1541164797_add_installationsUpSql, map[string]*bintree{}},
"static.go": &bintree{staticGo, map[string]*bintree{}},
}}

View File

@ -20,32 +20,41 @@ type RatchetInfo struct {
// PersistenceService defines the interface for a storage service
type PersistenceService interface {
// GetKeysStorage returns the associated double ratchet KeysStorage object
// GetKeysStorage returns the associated double ratchet KeysStorage object.
GetKeysStorage() dr.KeysStorage
// GetSessionStorage returns the associated double ratchet SessionStorage object
// GetSessionStorage returns the associated double ratchet SessionStorage object.
GetSessionStorage() dr.SessionStorage
// GetPublicBundle retrieves an existing Bundle for the specified public key
GetPublicBundle(*ecdsa.PublicKey) (*Bundle, error)
// GetPublicBundle retrieves an existing Bundle for the specified public key & installationIDs.
GetPublicBundle(*ecdsa.PublicKey, []string) (*Bundle, error)
// AddPublicBundle persists a specified Bundle
AddPublicBundle(*Bundle) error
// GetAnyPrivateBundle retrieves any bundle containing a private key
GetAnyPrivateBundle([]byte) (*BundleContainer, error)
// GetPrivateKeyBundle retrieves a BundleContainer with the specified signed prekey
// GetAnyPrivateBundle retrieves any bundle for our identity & installationIDs
GetAnyPrivateBundle([]byte, []string) (*BundleContainer, error)
// GetPrivateKeyBundle retrieves a BundleContainer with the specified signed prekey.
GetPrivateKeyBundle([]byte) ([]byte, error)
// AddPrivateBundle persists a BundleContainer
// AddPrivateBundle persists a BundleContainer.
AddPrivateBundle(*BundleContainer) error
// MarkBundleExpired marks a bundle as expired, not to be used for encryption anymore
// MarkBundleExpired marks a private bundle as expired, not to be used for encryption anymore.
MarkBundleExpired([]byte) error
// AddRatchetInfo persists the specified ratchet info
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, 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)
// 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, string) error
// GetActiveInstallations returns the active installations for a given identity.
GetActiveInstallations(maxInstallations uint, identity []byte) ([]string, error)
// AddInstallations adds the installations for a given identity.
AddInstallations(identity []byte, timestamp int64, installationIDs []string, enabled bool) error
// EnableInstallation enables the installation.
EnableInstallation(identity []byte, installationID string) error
// DisableInstallation disable the installation.
DisableInstallation(identity []byte, installationID string) error
}

View File

@ -87,7 +87,7 @@ func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, pa
return response, nil
}
// BuildPairingMessage sends a message to our own devices using DH so that it can be decrypted by any other device
// 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)
@ -105,17 +105,27 @@ func (p *ProtocolService) BuildPairingMessage(myIdentityKey *ecdsa.PrivateKey, p
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) ([]IdentityAndIDPair, error) {
return p.encryption.ProcessPublicBundle(myIdentityKey, bundle)
}
// GetBundle retrieves or creates a X3DH bundle, given a private identity key
// GetBundle retrieves or creates a X3DH bundle, given a private identity key.
func (p *ProtocolService) GetBundle(myIdentityKey *ecdsa.PrivateKey) (*Bundle, error) {
return p.encryption.CreateBundle(myIdentityKey)
}
// HandleMessage unmarshals a message and processes it, decrypting it if it is a 1:1 message
// 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.
func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, payload []byte) (*HandleMessageResponse, error) {
if p.encryption == nil {
return nil, errors.New("encryption service not initialized")

View File

@ -3,7 +3,7 @@ package chat
import (
"crypto/ecdsa"
"database/sql"
"time"
"strings"
"github.com/ethereum/go-ethereum/crypto"
@ -103,13 +103,13 @@ func (s *SQLLitePersistence) Open(path string, key string) error {
}
// AddPrivateBundle adds the specified BundleContainer to the database
func (s *SQLLitePersistence) AddPrivateBundle(b *BundleContainer) error {
func (s *SQLLitePersistence) AddPrivateBundle(bc *BundleContainer) error {
tx, err := s.db.Begin()
if err != nil {
return err
}
for installationID, signedPreKey := range b.GetBundle().GetSignedPreKeys() {
for installationID, signedPreKey := range bc.GetBundle().GetSignedPreKeys() {
var version uint32
stmt, err := tx.Prepare("SELECT version FROM bundles WHERE installation_id = ? AND identity = ? ORDER BY version DESC LIMIT 1")
if err != nil {
@ -118,7 +118,7 @@ func (s *SQLLitePersistence) AddPrivateBundle(b *BundleContainer) error {
defer stmt.Close()
err = stmt.QueryRow(installationID, b.GetBundle().GetIdentity()).Scan(&version)
err = stmt.QueryRow(installationID, bc.GetBundle().GetIdentity()).Scan(&version)
if err != nil && err != sql.ErrNoRows {
return err
}
@ -130,12 +130,12 @@ func (s *SQLLitePersistence) AddPrivateBundle(b *BundleContainer) error {
defer stmt.Close()
_, err = stmt.Exec(
b.GetBundle().GetIdentity(),
b.GetPrivateSignedPreKey(),
bc.GetBundle().GetIdentity(),
bc.GetPrivateSignedPreKey(),
signedPreKey.GetSignedPreKey(),
installationID,
version+1,
time.Now().UnixNano(),
bc.GetBundle().GetTimestamp(),
)
if err != nil {
_ = tx.Rollback()
@ -173,7 +173,7 @@ func (s *SQLLitePersistence) AddPublicBundle(b *Bundle) error {
signedPreKey,
installationID,
version,
time.Now().UnixNano(),
b.GetTimestamp(),
)
if err != nil {
_ = tx.Rollback()
@ -202,8 +202,11 @@ func (s *SQLLitePersistence) AddPublicBundle(b *Bundle) error {
}
// GetAnyPrivateBundle retrieves any bundle from the database containing a private key
func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte) (*BundleContainer, error) {
stmt, err := s.db.Prepare("SELECT identity, private_key, signed_pre_key, installation_id, timestamp FROM bundles WHERE identity = ? AND expired = 0")
func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte, installationIDs []string) (*BundleContainer, error) {
/* #nosec */
statement := "SELECT identity, private_key, signed_pre_key, installation_id, timestamp FROM bundles WHERE expired = 0 AND identity = ? AND installation_id IN (?" + strings.Repeat(",?", len(installationIDs)-1) + ")"
stmt, err := s.db.Prepare(statement)
if err != nil {
return nil, err
}
@ -213,7 +216,13 @@ func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte) (*BundleC
var identity []byte
var privateKey []byte
rows, err := stmt.Query(myIdentityKey)
args := make([]interface{}, len(installationIDs)+1)
args[0] = myIdentityKey
for i, installationID := range installationIDs {
args[i+1] = installationID
}
rows, err := stmt.Query(args...)
rowCount := 0
if err != nil {
@ -246,7 +255,7 @@ func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte) (*BundleC
}
// If there is a private key, we set the timestamp of the bundle container
if privateKey != nil {
bundleContainer.Timestamp = timestamp
bundle.Timestamp = timestamp
}
bundle.SignedPreKeys[installationID] = &SignedPreKey{SignedPreKey: signedPreKey}
@ -254,7 +263,7 @@ func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte) (*BundleC
}
// If no records are found or no record with private key, return nil
if rowCount == 0 || bundleContainer.Timestamp == 0 {
if rowCount == 0 || bundleContainer.GetBundle().Timestamp == 0 {
return nil, nil
}
@ -283,10 +292,9 @@ func (s *SQLLitePersistence) GetPrivateKeyBundle(bundleID []byte) ([]byte, error
}
}
// RatchetInfoConfirmed clears the ephemeral key in the RatchetInfo
// associated with the specified bundle ID and interlocutor identity public key
// MarkBundleExpired expires any private bundle for a given identity
func (s *SQLLitePersistence) MarkBundleExpired(identity []byte) error {
stmt, err := s.db.Prepare("UPDATE bundles SET expired = 1 WHERE identity = ?")
stmt, err := s.db.Prepare("UPDATE bundles SET expired = 1 WHERE identity = ? AND private_key IS NOT NULL")
if err != nil {
return err
}
@ -298,16 +306,29 @@ func (s *SQLLitePersistence) MarkBundleExpired(identity []byte) error {
}
// GetPublicBundle retrieves an existing Bundle for the specified public key from the database
func (s *SQLLitePersistence) GetPublicBundle(publicKey *ecdsa.PublicKey) (*Bundle, error) {
func (s *SQLLitePersistence) GetPublicBundle(publicKey *ecdsa.PublicKey, installationIDs []string) (*Bundle, error) {
if len(installationIDs) == 0 {
return nil, nil
}
identity := crypto.CompressPubkey(publicKey)
stmt, err := s.db.Prepare("SELECT signed_pre_key,installation_id, version FROM bundles WHERE expired = 0 AND identity = ? ORDER BY version DESC")
/* #nosec */
statement := "SELECT signed_pre_key,installation_id, version FROM bundles WHERE expired = 0 AND identity = ? AND installation_id IN (?" + strings.Repeat(",?", len(installationIDs)-1) + ") ORDER BY version DESC"
stmt, err := s.db.Prepare(statement)
if err != nil {
return nil, err
}
defer stmt.Close()
rows, err := stmt.Query(identity)
args := make([]interface{}, len(installationIDs)+1)
args[0] = identity
for i, installationID := range installationIDs {
args[i+1] = installationID
}
rows, err := stmt.Query(args...)
rowCount := 0
if err != nil {
@ -687,6 +708,126 @@ func (s *SQLLiteSessionStorage) Load(id []byte) (*dr.State, error) {
}
}
// GetActiveInstallations returns the active installations for a given identity
func (s *SQLLitePersistence) GetActiveInstallations(maxInstallations uint, identity []byte) ([]string, error) {
stmt, err := s.db.Prepare("SELECT installation_id FROM installations WHERE enabled = 1 AND identity = ? ORDER BY timestamp DESC LIMIT ?")
if err != nil {
return nil, err
}
var installations []string
rows, err := stmt.Query(identity, maxInstallations)
if err != nil {
return nil, err
}
for rows.Next() {
var installationID string
err = rows.Scan(
&installationID,
)
if err != nil {
return nil, err
}
installations = append(installations, installationID)
}
return installations, nil
}
// AddInstallations adds the installations for a given identity, maintaining the enabled flag
func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64, installationIDs []string, defaultEnabled bool) error {
tx, err := s.db.Begin()
if err != nil {
return nil
}
for _, installationID := range installationIDs {
stmt, err := tx.Prepare("SELECT enabled FROM installations WHERE identity = ? AND installation_id = ? LIMIT 1")
if err != nil {
return err
}
defer stmt.Close()
var oldEnabled bool
err = stmt.QueryRow(identity, installationID).Scan(&oldEnabled)
if err != nil && err != sql.ErrNoRows {
return err
}
// We update timestamp if present without changing enabled
if err != sql.ErrNoRows {
stmt, err = tx.Prepare("UPDATE installations SET timestamp = ?, enabled = ? WHERE identity = ? AND installation_id = ?")
if err != nil {
return err
}
_, err = stmt.Exec(
timestamp,
oldEnabled,
identity,
installationID,
)
if err != nil {
return err
}
defer stmt.Close()
} else {
stmt, err = tx.Prepare("INSERT INTO installations(identity, installation_id, timestamp, enabled) VALUES (?, ?, ?, ?)")
if err != nil {
return err
}
_, err = stmt.Exec(
identity,
installationID,
timestamp,
defaultEnabled,
)
if err != nil {
return err
}
defer stmt.Close()
}
}
if err := tx.Commit(); err != nil {
_ = tx.Rollback()
return err
}
return nil
}
// EnableInstallation enables the installation
func (s *SQLLitePersistence) EnableInstallation(identity []byte, installationID string) error {
stmt, err := s.db.Prepare("UPDATE installations SET enabled = 1 WHERE identity = ? AND installation_id = ?")
if err != nil {
return err
}
_, err = stmt.Exec(identity, installationID)
return err
}
// DisableInstallation disable the installation
func (s *SQLLitePersistence) DisableInstallation(identity []byte, installationID string) error {
stmt, err := s.db.Prepare("UPDATE installations SET enabled = 0 WHERE identity = ? AND installation_id = ?")
if err != nil {
return err
}
_, err = stmt.Exec(identity, installationID)
return err
}
func toKey(a []byte) dr.Key {
var k [32]byte
copy(k[:], a)

View File

@ -54,7 +54,7 @@ func (s *SQLLitePersistenceTestSuite) TestPrivateBundle() {
s.Require().NoError(err, "Error was not returned even though bundle is not there")
s.Nil(actualKey)
anyPrivateBundle, err := s.service.GetAnyPrivateBundle([]byte("non-existing-id"))
anyPrivateBundle, err := s.service.GetAnyPrivateBundle([]byte("non-existing-id"), []string{installationID})
s.Require().NoError(err)
s.Nil(anyPrivateBundle)
@ -71,7 +71,7 @@ func (s *SQLLitePersistenceTestSuite) TestPrivateBundle() {
s.Equal(bundle.GetPrivateSignedPreKey(), actualKey, "It returns the same key")
identity := crypto.CompressPubkey(&key.PublicKey)
anyPrivateBundle, err = s.service.GetAnyPrivateBundle(identity)
anyPrivateBundle, err = s.service.GetAnyPrivateBundle(identity, []string{installationID})
s.Require().NoError(err)
s.NotNil(anyPrivateBundle)
s.True(proto.Equal(bundle.GetBundle(), anyPrivateBundle.GetBundle()), "It returns the same bundle")
@ -81,7 +81,7 @@ func (s *SQLLitePersistenceTestSuite) TestPublicBundle() {
key, err := crypto.GenerateKey()
s.Require().NoError(err)
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey)
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []string{"1"})
s.Require().NoError(err, "Error was not returned even though bundle is not there")
s.Nil(actualBundle)
@ -92,7 +92,7 @@ func (s *SQLLitePersistenceTestSuite) TestPublicBundle() {
err = s.service.AddPublicBundle(bundle)
s.Require().NoError(err)
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey)
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []string{"1"})
s.Require().NoError(err)
s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the right identity")
s.Equal(bundle.GetSignedPreKeys(), actualBundle.GetSignedPreKeys(), "It sets the right prekeys")
@ -102,7 +102,7 @@ func (s *SQLLitePersistenceTestSuite) TestUpdatedBundle() {
key, err := crypto.GenerateKey()
s.Require().NoError(err)
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey)
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []string{"1"})
s.Require().NoError(err, "Error was not returned even though bundle is not there")
s.Nil(actualBundle)
@ -124,7 +124,7 @@ func (s *SQLLitePersistenceTestSuite) TestUpdatedBundle() {
err = s.service.AddPublicBundle(bundle)
s.Require().NoError(err)
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey)
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []string{"1"})
s.Require().NoError(err)
s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the right identity")
s.Equal(bundle.GetSignedPreKeys(), actualBundle.GetSignedPreKeys(), "It sets the right prekeys")
@ -134,7 +134,7 @@ func (s *SQLLitePersistenceTestSuite) TestOutOfOrderBundles() {
key, err := crypto.GenerateKey()
s.Require().NoError(err)
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey)
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []string{"1"})
s.Require().NoError(err, "Error was not returned even though bundle is not there")
s.Nil(actualBundle)
@ -161,7 +161,7 @@ func (s *SQLLitePersistenceTestSuite) TestOutOfOrderBundles() {
err = s.service.AddPublicBundle(bundle1)
s.Require().NoError(err)
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey)
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []string{"1"})
s.Require().NoError(err)
s.Equal(bundle2.GetIdentity(), actualBundle.GetIdentity(), "It sets the right identity")
s.Equal(bundle2.GetSignedPreKeys()["1"].GetVersion(), uint32(1))
@ -172,7 +172,7 @@ func (s *SQLLitePersistenceTestSuite) TestMultiplePublicBundle() {
key, err := crypto.GenerateKey()
s.Require().NoError(err)
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey)
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []string{"1"})
s.Require().NoError(err, "Error was not returned even though bundle is not there")
s.Nil(actualBundle)
@ -198,7 +198,7 @@ func (s *SQLLitePersistenceTestSuite) TestMultiplePublicBundle() {
s.Require().NoError(err)
// Returns the most recent bundle
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey)
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []string{"1"})
s.Require().NoError(err)
s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the identity")
@ -210,7 +210,7 @@ func (s *SQLLitePersistenceTestSuite) TestMultiDevicePublicBundle() {
key, err := crypto.GenerateKey()
s.Require().NoError(err)
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey)
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []string{"1"})
s.Require().NoError(err, "Error was not returned even though bundle is not there")
s.Nil(actualBundle)
@ -234,7 +234,7 @@ func (s *SQLLitePersistenceTestSuite) TestMultiDevicePublicBundle() {
s.Require().NoError(err)
// Returns the most recent bundle
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey)
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []string{"1", "2"})
s.Require().NoError(err)
s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the identity")
@ -344,4 +344,137 @@ func (s *SQLLitePersistenceTestSuite) TestRatchetInfoNoBundle() {
s.Nil(ratchetInfo, "It returns nil when no bundle is there")
}
func (s *SQLLitePersistenceTestSuite) TestAddInstallations() {
identity := []byte("alice")
installations := []string{"alice-1", "alice-2"}
err := s.service.AddInstallations(
identity,
1,
installations,
true,
)
s.Require().NoError(err)
enabledInstallations, err := s.service.GetActiveInstallations(5, identity)
s.Require().NoError(err)
s.Require().Equal(installations, enabledInstallations)
}
func (s *SQLLitePersistenceTestSuite) TestAddInstallationsLimit() {
identity := []byte("alice")
installations := []string{"alice-1", "alice-2"}
err := s.service.AddInstallations(
identity,
1,
installations,
true,
)
s.Require().NoError(err)
installations = []string{"alice-2", "alice-3"}
err = s.service.AddInstallations(
identity,
2,
installations,
true,
)
s.Require().NoError(err)
installations = []string{"alice-2", "alice-3", "alice-4"}
err = s.service.AddInstallations(
identity,
3,
installations,
true,
)
s.Require().NoError(err)
enabledInstallations, err := s.service.GetActiveInstallations(3, identity)
s.Require().NoError(err)
s.Require().Equal([]string{"alice-2", "alice-3", "alice-4"}, enabledInstallations)
}
func (s *SQLLitePersistenceTestSuite) TestAddInstallationsDisabled() {
identity := []byte("alice")
installations := []string{"alice-1", "alice-2"}
err := s.service.AddInstallations(
identity,
1,
installations,
false,
)
s.Require().NoError(err)
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
s.Require().NoError(err)
s.Require().Nil(actualInstallations)
}
func (s *SQLLitePersistenceTestSuite) TestDisableInstallation() {
identity := []byte("alice")
installations := []string{"alice-1", "alice-2"}
err := s.service.AddInstallations(
identity,
1,
installations,
true,
)
s.Require().NoError(err)
err = s.service.DisableInstallation(identity, "alice-1")
s.Require().NoError(err)
// We add the installations again
installations = []string{"alice-1", "alice-2"}
err = s.service.AddInstallations(
identity,
1,
installations,
true,
)
s.Require().NoError(err)
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
s.Require().NoError(err)
s.Require().Equal([]string{"alice-2"}, actualInstallations)
}
func (s *SQLLitePersistenceTestSuite) TestEnableInstallation() {
identity := []byte("alice")
installations := []string{"alice-1", "alice-2"}
err := s.service.AddInstallations(
identity,
1,
installations,
true,
)
s.Require().NoError(err)
err = s.service.DisableInstallation(identity, "alice-1")
s.Require().NoError(err)
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
s.Require().NoError(err)
s.Require().Equal([]string{"alice-2"}, actualInstallations)
err = s.service.EnableInstallation(identity, "alice-1")
s.Require().NoError(err)
actualInstallations, err = s.service.GetActiveInstallations(3, identity)
s.Require().NoError(err)
s.Require().Equal([]string{"alice-1", "alice-2"}, actualInstallations)
}
// TODO: Add test for MarkBundleExpired

View File

@ -6,6 +6,8 @@ import (
"errors"
"fmt"
"sort"
"strconv"
"time"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies"
@ -43,9 +45,12 @@ func FromBase64(str string) (*Bundle, error) {
return bundle, nil
}
func buildSignatureMaterial(signedPreKeys *map[string]*SignedPreKey) []byte {
func buildSignatureMaterial(bundle *Bundle) []byte {
signedPreKeys := bundle.GetSignedPreKeys()
timestamp := bundle.GetTimestamp()
var keys []string
for k := range *signedPreKeys {
for k := range signedPreKeys {
keys = append(keys, k)
}
var signatureMaterial []byte
@ -53,18 +58,23 @@ func buildSignatureMaterial(signedPreKeys *map[string]*SignedPreKey) []byte {
sort.Strings(keys)
for _, installationID := range keys {
signedPreKey := (*signedPreKeys)[installationID]
signedPreKey := signedPreKeys[installationID]
signatureMaterial = append(signatureMaterial, []byte(installationID)...)
signatureMaterial = append(signatureMaterial, signedPreKey.SignedPreKey...)
signatureMaterial = append(signatureMaterial, []byte(fmt.Sprint(signedPreKey.Version))...)
signatureMaterial = append(signatureMaterial, []byte(strconv.FormatUint(uint64(signedPreKey.Version), 10))...)
// We don't use timestamp in the signature if it's 0, for backward compatibility
}
if timestamp != 0 {
signatureMaterial = append(signatureMaterial, []byte(strconv.FormatInt(timestamp, 10))...)
}
return signatureMaterial
}
func SignBundle(identity *ecdsa.PrivateKey, bundleContainer *BundleContainer) error {
signedPreKeys := bundleContainer.GetBundle().GetSignedPreKeys()
signatureMaterial := buildSignatureMaterial(&signedPreKeys)
signatureMaterial := buildSignatureMaterial(bundleContainer.GetBundle())
signature, err := crypto.Sign(crypto.Keccak256(signatureMaterial), identity)
if err != nil {
@ -89,6 +99,7 @@ func NewBundleContainer(identity *ecdsa.PrivateKey, installationID string) (*Bun
signedPreKeys[installationID] = &SignedPreKey{SignedPreKey: compressedPreKey}
bundle := Bundle{
Timestamp: time.Now().UnixNano(),
Identity: compressedIdentityKey,
SignedPreKeys: signedPreKeys,
}
@ -112,8 +123,7 @@ func ExtractIdentity(bundle *Bundle) (string, error) {
return "", err
}
signedPreKeys := bundle.GetSignedPreKeys()
signatureMaterial := buildSignatureMaterial(&signedPreKeys)
signatureMaterial := buildSignatureMaterial(bundle)
recoveredKey, err := crypto.SigToPub(
crypto.Keccak256(signatureMaterial),

View File

@ -1,6 +1,7 @@
package chat
import (
"fmt"
"testing"
"github.com/ethereum/go-ethereum/crypto"
@ -65,6 +66,7 @@ func TestNewBundleContainer(t *testing.T) {
signatureMaterial := append([]byte(bobInstallationID), bundle.GetSignedPreKeys()[bobInstallationID].GetSignedPreKey()...)
signatureMaterial = append(signatureMaterial, []byte("0")...)
signatureMaterial = append(signatureMaterial, []byte(fmt.Sprint(bundle.GetTimestamp()))...)
recoveredPublicKey, err := crypto.SigToPub(
crypto.Keccak256(signatureMaterial),
bundle.Signature,
@ -102,6 +104,7 @@ func TestSignBundle(t *testing.T) {
signatureMaterial = append(signatureMaterial, []byte("2")...)
signatureMaterial = append(signatureMaterial, []byte("key")...)
signatureMaterial = append(signatureMaterial, []byte("0")...)
signatureMaterial = append(signatureMaterial, []byte(fmt.Sprint(bundle1.GetTimestamp()))...)
recoveredPublicKey, err := crypto.SigToPub(
crypto.Keccak256(signatureMaterial),

View File

@ -122,6 +122,24 @@ func (s *Service) GetBundle(myIdentityKey *ecdsa.PrivateKey) (*chat.Bundle, erro
return s.protocol.GetBundle(myIdentityKey)
}
// EnableInstallation enables an installation for multi-device sync.
func (s *Service) EnableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
if s.protocol == nil {
return errProtocolNotInitialized
}
return s.protocol.EnableInstallation(myIdentityKey, installationID)
}
// DisableInstallation disables an installation for multi-device sync.
func (s *Service) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
if s.protocol == nil {
return errProtocolNotInitialized
}
return s.protocol.DisableInstallation(myIdentityKey, installationID)
}
// APIs returns a list of new APIs.
func (s *Service) APIs() []rpc.API {
apis := []rpc.API{

View File

@ -88,7 +88,7 @@ func ConfigCliFleetEthBetaJson() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "../config/cli/fleet-eth.beta.json", size: 3237, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)}
info := bindataFileInfo{name: "../config/cli/fleet-eth.beta.json", size: 3237, mode: os.FileMode(420), modTime: time.Unix(1541148771, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@ -108,7 +108,7 @@ func ConfigCliFleetEthStagingJson() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "../config/cli/fleet-eth.staging.json", size: 1838, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)}
info := bindataFileInfo{name: "../config/cli/fleet-eth.staging.json", size: 1838, mode: os.FileMode(420), modTime: time.Unix(1541148771, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@ -128,7 +128,7 @@ func ConfigCliFleetEthTestJson() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "../config/cli/fleet-eth.test.json", size: 1519, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)}
info := bindataFileInfo{name: "../config/cli/fleet-eth.test.json", size: 1519, mode: os.FileMode(420), modTime: time.Unix(1541148771, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}

View File

@ -0,0 +1 @@
DROP TABLE installations;

View File

@ -0,0 +1,7 @@
CREATE TABLE installations (
identity BLOB NOT NULL,
installation_id TEXT NOT NULL,
timestamp UNSIGNED BIG INT NOT NULL,
enabled BOOLEAN DEFAULT 1,
UNIQUE(identity, installation_id) ON CONFLICT REPLACE
);