From 78ed35d2fe7bee4dd691e9ae96dfbf7c94de1f78 Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Thu, 23 May 2019 09:54:28 +0200 Subject: [PATCH] Add protocol version to bundle --- services/shhext/chat/db/migrations/bindata.go | 23 ++++ services/shhext/chat/encryption.go | 35 +++-- services/shhext/chat/encryption.pb.go | 92 ++++++------- services/shhext/chat/encryption.proto | 4 +- services/shhext/chat/persistence.go | 13 +- services/shhext/chat/protocol.go | 31 ++++- services/shhext/chat/protocol_test.go | 2 +- services/shhext/chat/sql_lite_persistence.go | 82 ++++++++---- .../shhext/chat/sql_lite_persistence_test.go | 126 ++++++++++++++---- services/shhext/chat/x3dh.go | 5 +- services/shhext/filter/service.go | 16 ++- services/shhext/filter/service_test.go | 4 +- services/shhext/service.go | 7 +- .../1558588866_add_version.up.sql | 1 + 14 files changed, 305 insertions(+), 136 deletions(-) create mode 100644 static/chat_db_migrations/1558588866_add_version.up.sql diff --git a/services/shhext/chat/db/migrations/bindata.go b/services/shhext/chat/db/migrations/bindata.go index df8c0c383..006ed5863 100644 --- a/services/shhext/chat/db/migrations/bindata.go +++ b/services/shhext/chat/db/migrations/bindata.go @@ -10,6 +10,7 @@ // 1541164797_add_installations.up.sql // 1558084410_add_topic.down.sql // 1558084410_add_topic.up.sql +// 1558588866_add_version.up.sql // static.go // DO NOT EDIT! @@ -278,6 +279,26 @@ func _1558084410_add_topicUpSql() (*asset, error) { return a, nil } +var __1558588866_add_versionUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xc8\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x2b\x56\x70\x74\x71\x51\x28\x4b\x2d\x2a\xce\xcc\xcf\x53\xf0\xf4\x0b\x71\x75\x77\x0d\x52\x70\x71\x75\x73\x0c\xf5\x09\x51\x30\xb0\xe6\x02\x04\x00\x00\xff\xff\x14\x7b\x07\xb5\x39\x00\x00\x00") + +func _1558588866_add_versionUpSqlBytes() ([]byte, error) { + return bindataRead( + __1558588866_add_versionUpSql, + "1558588866_add_version.up.sql", + ) +} + +func _1558588866_add_versionUpSql() (*asset, error) { + bytes, err := _1558588866_add_versionUpSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "1558588866_add_version.up.sql", size: 57, mode: os.FileMode(420), modTime: time.Unix(1558588995, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + var _staticGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\xcc\x41\x6a\x03\x31\x0c\x46\xe1\xbd\x4f\xf1\x2f\x5b\xe8\x58\xfb\x9e\xa0\x94\x16\x0a\xcd\x05\x64\x8f\x90\xc5\x30\xf6\x60\x29\x21\xc7\xcf\x26\x21\x64\xf9\xe0\xf1\x11\xe1\x8f\xeb\xc6\x2a\xf0\xe0\xb0\x0a\xd9\x8b\xac\xfe\xa8\xb7\xef\xff\x0f\x7c\x9d\x7e\x7f\xde\x31\xc5\xc7\x79\x56\x71\x4c\xd3\x16\xb0\x1e\x03\xd1\x04\xc5\x3a\x4f\x13\x4f\xc7\x8b\x94\x12\x91\x8e\x4f\x95\x2e\x93\x43\xa0\x63\x29\xd6\x57\x0e\xc6\x72\x6c\x8a\xdd\x74\x72\xd8\xe8\x8e\x65\x20\x67\xca\x99\x5c\xe6\xc5\xaa\x38\x79\x6b\x72\x0d\xaa\x8d\x83\xd6\x42\xcf\x97\xee\x46\xd6\x81\x9c\x6e\x01\x00\x00\xff\xff\x6c\x21\xbf\x7a\xbf\x00\x00\x00") func staticGoBytes() ([]byte, error) { @@ -360,6 +381,7 @@ var _bindata = map[string]func() (*asset, error){ "1541164797_add_installations.up.sql": _1541164797_add_installationsUpSql, "1558084410_add_topic.down.sql": _1558084410_add_topicDownSql, "1558084410_add_topic.up.sql": _1558084410_add_topicUpSql, + "1558588866_add_version.up.sql": _1558588866_add_versionUpSql, "static.go": staticGo, } @@ -413,6 +435,7 @@ var _bintree = &bintree{nil, map[string]*bintree{ "1541164797_add_installations.up.sql": &bintree{_1541164797_add_installationsUpSql, map[string]*bintree{}}, "1558084410_add_topic.down.sql": &bintree{_1558084410_add_topicDownSql, map[string]*bintree{}}, "1558084410_add_topic.up.sql": &bintree{_1558084410_add_topicUpSql, map[string]*bintree{}}, + "1558588866_add_version.up.sql": &bintree{_1558588866_add_versionUpSql, map[string]*bintree{}}, "static.go": &bintree{staticGo, map[string]*bintree{}}, }} diff --git a/services/shhext/chat/encryption.go b/services/shhext/chat/encryption.go index 483ce7443..7f075abbb 100644 --- a/services/shhext/chat/encryption.go +++ b/services/shhext/chat/encryption.go @@ -135,14 +135,17 @@ func (s *EncryptionService) ConfirmMessagesProcessed(messageIDs [][]byte) error func (s *EncryptionService) CreateBundle(privateKey *ecdsa.PrivateKey) (*Bundle, error) { ourIdentityKeyC := ecrypto.CompressPubkey(&privateKey.PublicKey) - installationIDs, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations-1, ourIdentityKeyC) + installations, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations-1, ourIdentityKeyC) if err != nil { return nil, err } - installationIDs = append(installationIDs, s.config.InstallationID) + installations = append(installations, &Installation{ + ID: s.config.InstallationID, + Version: protocolCurrentVersion, + }) - bundleContainer, err := s.persistence.GetAnyPrivateBundle(ourIdentityKeyC, installationIDs) + bundleContainer, err := s.persistence.GetAnyPrivateBundle(ourIdentityKeyC, installations) if err != nil { return nil, err } @@ -240,21 +243,24 @@ func (s *EncryptionService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, } signedPreKeys := b.GetSignedPreKeys() var response []IdentityAndIDPair - var installationIDs []string + var installations []*Installation myIdentityStr := fmt.Sprintf("0x%x", ecrypto.FromECDSAPub(&myIdentityKey.PublicKey)) // Any device from other peers will be considered enabled, ours needs to // be explicitly enabled fromOurIdentity := identity != myIdentityStr - for installationID := range signedPreKeys { + for installationID, signedPreKey := range signedPreKeys { if installationID != s.config.InstallationID { - installationIDs = append(installationIDs, installationID) + installations = append(installations, &Installation{ + ID: installationID, + Version: signedPreKey.GetProtocolVersion(), + }) response = append(response, IdentityAndIDPair{identity, installationID}) } } - if err = s.persistence.AddInstallations(b.GetIdentity(), b.GetTimestamp(), installationIDs, fromOurIdentity); err != nil { + if err = s.persistence.AddInstallations(b.GetIdentity(), b.GetTimestamp(), installations, fromOurIdentity); err != nil { return nil, err } @@ -323,7 +329,7 @@ func (s *EncryptionService) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, thei } // Add installations with a timestamp of 0, as we don't have bundle informations - if err = s.persistence.AddInstallations(theirIdentityKeyC, 0, []string{theirInstallationID}, true); err != nil { + if err = s.persistence.AddInstallations(theirIdentityKeyC, 0, []*Installation{{ID: theirInstallationID, Version: 0}}, true); err != nil { return nil, err } @@ -502,12 +508,12 @@ func (s *EncryptionService) EncryptPayloadWithDH(theirIdentityKey *ecdsa.PublicK func (s *EncryptionService) GetPublicBundle(theirIdentityKey *ecdsa.PublicKey) (*Bundle, error) { theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey) - installationIDs, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations, theirIdentityKeyC) + installations, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations, theirIdentityKeyC) if err != nil { return nil, err } - return s.persistence.GetPublicBundle(theirIdentityKey, installationIDs) + return s.persistence.GetPublicBundle(theirIdentityKey, installations) } // EncryptPayload returns a new DirectMessageProtocol with a given payload encrypted, given a recipient's public key and the sender private identity key @@ -522,24 +528,25 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey) // Get their installationIds - installationIds, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations, theirIdentityKeyC) + installations, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations, theirIdentityKeyC) if err != nil { return nil, err } // We don't have any, send a message with DH - if installationIds == nil && !bytes.Equal(theirIdentityKeyC, ecrypto.CompressPubkey(&myIdentityKey.PublicKey)) { + if installations == nil && !bytes.Equal(theirIdentityKeyC, ecrypto.CompressPubkey(&myIdentityKey.PublicKey)) { return s.EncryptPayloadWithDH(theirIdentityKey, payload) } response := make(map[string]*DirectMessageProtocol) - for _, installationID := range installationIds { + for _, installation := range installations { + installationID := installation.ID s.log.Debug("Processing installation", "installationID", installationID) if s.config.InstallationID == installationID { continue } - bundle, err := s.persistence.GetPublicBundle(theirIdentityKey, []string{installationID}) + bundle, err := s.persistence.GetPublicBundle(theirIdentityKey, []*Installation{installation}) if err != nil { return nil, err } diff --git a/services/shhext/chat/encryption.pb.go b/services/shhext/chat/encryption.pb.go index 876c91295..88e0a90c6 100644 --- a/services/shhext/chat/encryption.pb.go +++ b/services/shhext/chat/encryption.pb.go @@ -23,6 +23,7 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type SignedPreKey struct { SignedPreKey []byte `protobuf:"bytes,1,opt,name=signed_pre_key,json=signedPreKey,proto3" json:"signed_pre_key,omitempty"` Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + ProtocolVersion uint32 `protobuf:"varint,3,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -67,6 +68,13 @@ func (m *SignedPreKey) GetVersion() uint32 { return 0 } +func (m *SignedPreKey) GetProtocolVersion() uint32 { + if m != nil { + return m.ProtocolVersion + } + return 0 +} + // X3DH prekey bundle type Bundle struct { // Identity key @@ -416,9 +424,7 @@ type ProtocolMessage struct { // 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"` // Public chats, not encrypted - PublicMessage []byte `protobuf:"bytes,102,opt,name=public_message,json=publicMessage,proto3" json:"public_message,omitempty"` - // Version of the protocol - Version uint32 `protobuf:"varint,103,opt,name=version,proto3" json:"version,omitempty"` + PublicMessage []byte `protobuf:"bytes,102,opt,name=public_message,json=publicMessage,proto3" json:"public_message,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -484,13 +490,6 @@ func (m *ProtocolMessage) GetPublicMessage() []byte { return nil } -func (m *ProtocolMessage) GetVersion() uint32 { - if m != nil { - return m.Version - } - return 0 -} - func init() { proto.RegisterType((*SignedPreKey)(nil), "chat.SignedPreKey") proto.RegisterType((*Bundle)(nil), "chat.Bundle") @@ -507,40 +506,41 @@ func init() { func init() { proto.RegisterFile("encryption.proto", fileDescriptor_8293a649ce9418c6) } var fileDescriptor_8293a649ce9418c6 = []byte{ - // 553 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x5d, 0x8b, 0xd3, 0x40, - 0x14, 0x25, 0x49, 0x77, 0xb7, 0xbd, 0x4d, 0xd3, 0x32, 0xa2, 0x84, 0xba, 0x60, 0x09, 0xab, 0x06, - 0x84, 0xc2, 0xb6, 0x3e, 0x88, 0x8f, 0x5a, 0xb1, 0xae, 0xa8, 0xcb, 0xe8, 0x83, 0x2f, 0x12, 0xa6, - 0xcd, 0xd8, 0x1d, 0x4c, 0x27, 0x61, 0x32, 0x2d, 0xf4, 0xcf, 0xf9, 0xbf, 0x7c, 0x52, 0x32, 0x93, - 0x69, 0x27, 0xdd, 0x5d, 0xf0, 0xad, 0xf7, 0x63, 0xce, 0x3d, 0xf7, 0xdc, 0x9c, 0xc2, 0x80, 0xf2, - 0xa5, 0xd8, 0x15, 0x92, 0xe5, 0x7c, 0x5c, 0x88, 0x5c, 0xe6, 0xa8, 0xb5, 0xbc, 0x21, 0x32, 0xfa, - 0x0c, 0xfe, 0x57, 0xb6, 0xe2, 0x34, 0xbd, 0x16, 0xf4, 0x23, 0xdd, 0xa1, 0x0b, 0x08, 0x4a, 0x15, - 0x27, 0x85, 0xa0, 0xc9, 0x2f, 0xba, 0x0b, 0x9d, 0x91, 0x13, 0xfb, 0xd8, 0x2f, 0xed, 0xae, 0x10, - 0xce, 0xb6, 0x54, 0x94, 0x2c, 0xe7, 0xa1, 0x3b, 0x72, 0xe2, 0x1e, 0x36, 0x61, 0xf4, 0xd7, 0x81, - 0xd3, 0x37, 0x1b, 0x9e, 0x66, 0x14, 0x0d, 0xa1, 0xcd, 0x52, 0xca, 0x25, 0x93, 0x06, 0x64, 0x1f, - 0xa3, 0xf7, 0xd0, 0x6f, 0x8e, 0x29, 0x43, 0x77, 0xe4, 0xc5, 0xdd, 0xc9, 0x93, 0x71, 0x45, 0x6b, - 0xac, 0x21, 0xc6, 0x36, 0xb5, 0xf2, 0x1d, 0x97, 0x62, 0x87, 0x7b, 0x36, 0x91, 0x12, 0x9d, 0x43, - 0xa7, 0x4a, 0x10, 0xb9, 0x11, 0x34, 0x6c, 0xa9, 0x29, 0x87, 0x44, 0x55, 0x95, 0x6c, 0x4d, 0x4b, - 0x49, 0xd6, 0x45, 0x78, 0x32, 0x72, 0x62, 0x0f, 0x1f, 0x12, 0xc3, 0x6f, 0x80, 0x6e, 0x0f, 0x40, - 0x03, 0xf0, 0xcc, 0xda, 0x1d, 0x5c, 0xfd, 0x44, 0x31, 0x9c, 0x6c, 0x49, 0xb6, 0xa1, 0x6a, 0xd7, - 0xee, 0x04, 0x69, 0x8a, 0xf6, 0x53, 0xac, 0x1b, 0x5e, 0xbb, 0xaf, 0x9c, 0x48, 0x40, 0x5f, 0xb3, - 0x7f, 0x9b, 0x73, 0x49, 0x18, 0xa7, 0x02, 0x5d, 0xc0, 0xe9, 0x42, 0xa5, 0x14, 0x6a, 0x77, 0xe2, - 0xdb, 0x4b, 0xe2, 0xba, 0x86, 0xa6, 0xf0, 0xa8, 0x10, 0x6c, 0x4b, 0x24, 0x4d, 0x8e, 0x4e, 0xe0, - 0xaa, 0xbd, 0x1e, 0xd4, 0x55, 0x7b, 0xf0, 0x55, 0xab, 0xed, 0x0d, 0x5a, 0xd1, 0x15, 0xb4, 0x67, - 0x78, 0x4e, 0x49, 0x4a, 0x85, 0xcd, 0xdf, 0xd7, 0xfc, 0x7d, 0x70, 0xcc, 0x9d, 0x1c, 0x8e, 0x02, - 0x70, 0x0b, 0x1e, 0x7a, 0x2a, 0x74, 0x0b, 0x15, 0xb3, 0xb4, 0x96, 0xce, 0x65, 0x69, 0x74, 0x0e, - 0xed, 0xd9, 0xfc, 0x3e, 0xac, 0xe8, 0x25, 0xc0, 0xf7, 0xe9, 0xfd, 0xf5, 0x63, 0xb4, 0x9a, 0xdf, - 0x6f, 0x07, 0x1e, 0xce, 0x98, 0xa0, 0x4b, 0xf9, 0x89, 0x96, 0x25, 0x59, 0xd1, 0xeb, 0xea, 0x13, - 0x5c, 0xe6, 0x19, 0xba, 0x84, 0x6e, 0x85, 0x97, 0xdc, 0x28, 0xc0, 0x5a, 0x9f, 0x81, 0xd6, 0xe7, - 0x30, 0x08, 0xdb, 0x43, 0x5f, 0x40, 0x67, 0x86, 0xcd, 0x03, 0x7d, 0x92, 0x40, 0x3f, 0x30, 0x1a, - 0xe0, 0x83, 0x1a, 0x55, 0xf3, 0x1e, 0x9d, 0x36, 0x9a, 0xe7, 0xfb, 0x66, 0x83, 0x1c, 0xc2, 0x59, - 0x41, 0x76, 0x59, 0x4e, 0x52, 0xa5, 0x8f, 0x8f, 0x4d, 0x18, 0xfd, 0x71, 0xa1, 0x6f, 0x38, 0xd7, - 0x2b, 0xfc, 0xe7, 0x55, 0x9f, 0x43, 0x9f, 0xf1, 0x52, 0x92, 0x2c, 0x23, 0x95, 0xf9, 0x12, 0x96, - 0x2a, 0xce, 0x1d, 0x1c, 0xd8, 0xe9, 0x0f, 0x29, 0x7a, 0x06, 0x67, 0xfa, 0x49, 0x19, 0x7a, 0xca, - 0x0a, 0x4d, 0x3c, 0x53, 0x44, 0x5f, 0x20, 0x48, 0x95, 0x94, 0xc9, 0x5a, 0x13, 0x09, 0xa9, 0x6a, - 0x8f, 0x75, 0xfb, 0x11, 0xcb, 0x71, 0x43, 0xf6, 0xda, 0x42, 0xa9, 0x9d, 0x43, 0x4f, 0x21, 0x28, - 0x36, 0x8b, 0x8c, 0x2d, 0xf7, 0x80, 0x3f, 0xd5, 0xf2, 0x3d, 0x9d, 0x35, 0x6d, 0x96, 0xe7, 0x57, - 0x0d, 0xcf, 0x0f, 0x7f, 0x00, 0xba, 0x3d, 0xe5, 0x0e, 0x1f, 0x5d, 0x36, 0x7d, 0xf4, 0xb8, 0xbe, - 0xc3, 0x5d, 0xdf, 0x85, 0x65, 0xa8, 0xc5, 0xa9, 0xfa, 0xbf, 0x9a, 0xfe, 0x0b, 0x00, 0x00, 0xff, - 0xff, 0x53, 0xcb, 0xc9, 0xb7, 0xc3, 0x04, 0x00, 0x00, + // 562 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x61, 0x8b, 0xd3, 0x4c, + 0x10, 0x26, 0x49, 0xef, 0xda, 0x4e, 0xd3, 0xb4, 0xec, 0xcb, 0x2b, 0xa1, 0x1e, 0x58, 0xc2, 0xa9, + 0x11, 0xa1, 0x70, 0xad, 0x1f, 0xc4, 0x8f, 0x5a, 0xb1, 0x9e, 0x88, 0xc7, 0x2a, 0xe2, 0x17, 0x09, + 0xdb, 0x66, 0xbd, 0x5b, 0x4c, 0x93, 0xb0, 0xbb, 0x2d, 0xe4, 0xcf, 0xf9, 0x57, 0xfc, 0x29, 0x4a, + 0x76, 0xb3, 0xed, 0xb6, 0x77, 0x07, 0x7e, 0xeb, 0xcc, 0x3c, 0xfb, 0xcc, 0x33, 0xcf, 0x74, 0x02, + 0x43, 0x9a, 0xaf, 0x78, 0x55, 0x4a, 0x56, 0xe4, 0x93, 0x92, 0x17, 0xb2, 0x40, 0xad, 0xd5, 0x0d, + 0x91, 0x51, 0x05, 0xfe, 0x67, 0x76, 0x9d, 0xd3, 0xf4, 0x8a, 0xd3, 0x0f, 0xb4, 0x42, 0xe7, 0x10, + 0x08, 0x15, 0x27, 0x25, 0xa7, 0xc9, 0x4f, 0x5a, 0x85, 0xce, 0xd8, 0x89, 0x7d, 0xec, 0x0b, 0x1b, + 0x15, 0x42, 0x7b, 0x4b, 0xb9, 0x60, 0x45, 0x1e, 0xba, 0x63, 0x27, 0xee, 0x63, 0x13, 0xa2, 0x67, + 0x30, 0x54, 0xf4, 0xab, 0x22, 0x4b, 0x0c, 0xc4, 0x53, 0x90, 0x81, 0xc9, 0x7f, 0xd5, 0xe9, 0xe8, + 0x8f, 0x03, 0xa7, 0xaf, 0x37, 0x79, 0x9a, 0x51, 0x34, 0x82, 0x0e, 0x4b, 0x69, 0x2e, 0x99, 0x34, + 0xfd, 0x76, 0x31, 0x7a, 0x07, 0x83, 0x43, 0x45, 0x22, 0x74, 0xc7, 0x5e, 0xdc, 0x9b, 0x3e, 0x9a, + 0xd4, 0x13, 0x4c, 0x34, 0xc5, 0xc4, 0x9e, 0x42, 0xbc, 0xcd, 0x25, 0xaf, 0x70, 0xdf, 0xd6, 0x2c, + 0xd0, 0x19, 0x74, 0xeb, 0x04, 0x91, 0x1b, 0x4e, 0xc3, 0x96, 0xea, 0xb2, 0x4f, 0xd4, 0x55, 0xc9, + 0xd6, 0x54, 0x48, 0xb2, 0x2e, 0xc3, 0x93, 0xb1, 0x13, 0x7b, 0x78, 0x9f, 0x18, 0x7d, 0x01, 0x74, + 0xbb, 0x01, 0x1a, 0x82, 0x67, 0x1c, 0xea, 0xe2, 0xfa, 0x27, 0x8a, 0xe1, 0x64, 0x4b, 0xb2, 0x0d, + 0x55, 0xb6, 0xf4, 0xa6, 0x48, 0x4b, 0xb4, 0x9f, 0x62, 0x0d, 0x78, 0xe5, 0xbe, 0x74, 0x22, 0x0e, + 0x03, 0xad, 0xfe, 0x4d, 0x91, 0x4b, 0xc2, 0x72, 0xca, 0xd1, 0x39, 0x9c, 0x2e, 0x55, 0x4a, 0xb1, + 0xf6, 0xa6, 0xbe, 0x3d, 0x24, 0x6e, 0x6a, 0x68, 0x06, 0x0f, 0x4a, 0xce, 0xb6, 0x44, 0xd2, 0xe4, + 0x68, 0x5b, 0xae, 0x9a, 0xeb, 0xbf, 0xa6, 0x6a, 0x37, 0xbe, 0x6c, 0x75, 0xbc, 0x61, 0x2b, 0xba, + 0x84, 0xce, 0x1c, 0x2f, 0x28, 0x49, 0x29, 0xb7, 0xf5, 0xfb, 0x5a, 0xbf, 0x0f, 0x8e, 0x59, 0xa9, + 0x93, 0xa3, 0x00, 0xdc, 0xd2, 0xac, 0xcf, 0x2d, 0x55, 0xcc, 0xd2, 0xc6, 0x3a, 0x97, 0xa5, 0xd1, + 0x19, 0x74, 0xe6, 0x8b, 0xfb, 0xb8, 0xa2, 0x17, 0x00, 0xdf, 0x66, 0xf7, 0xd7, 0x8f, 0xd9, 0x1a, + 0x7d, 0xbf, 0x1c, 0xf8, 0x7f, 0xce, 0x38, 0x5d, 0xc9, 0x8f, 0x54, 0x08, 0x72, 0x4d, 0xaf, 0x9a, + 0xbf, 0x0d, 0xba, 0x80, 0x5e, 0xcd, 0x97, 0xdc, 0x28, 0xc2, 0xc6, 0x9f, 0xa1, 0xf6, 0x67, 0xdf, + 0x08, 0xdb, 0x4d, 0x9f, 0x43, 0x77, 0x8e, 0xcd, 0x03, 0xbd, 0x92, 0x40, 0x3f, 0x30, 0x1e, 0xe0, + 0xbd, 0x1b, 0x35, 0x78, 0xc7, 0x4e, 0x0f, 0xc0, 0x8b, 0x1d, 0xd8, 0x30, 0x87, 0xd0, 0x2e, 0x49, + 0x95, 0x15, 0x24, 0x55, 0xfe, 0xf8, 0xd8, 0x84, 0xd1, 0x6f, 0x17, 0x06, 0x46, 0x73, 0x33, 0xc2, + 0x3f, 0x6e, 0xf5, 0x29, 0x0c, 0x58, 0x2e, 0x24, 0xc9, 0x32, 0x52, 0xdf, 0x69, 0xc2, 0x52, 0xa5, + 0xb9, 0x8b, 0x03, 0x3b, 0xfd, 0x3e, 0x45, 0x4f, 0xa0, 0xad, 0x9f, 0x88, 0xd0, 0x53, 0xa7, 0x70, + 0xc8, 0x67, 0x8a, 0xe8, 0x13, 0x04, 0xa9, 0xb2, 0x32, 0x59, 0x6b, 0x21, 0x21, 0x55, 0xf0, 0x58, + 0xc3, 0x8f, 0x54, 0x4e, 0x0e, 0x6c, 0x6f, 0x4e, 0x28, 0xb5, 0x73, 0xe8, 0x31, 0x04, 0xe5, 0x66, + 0x99, 0xb1, 0xd5, 0x8e, 0xf0, 0x87, 0x1a, 0xbe, 0xaf, 0xb3, 0x0d, 0x6c, 0xf4, 0x1d, 0xd0, 0x6d, + 0xae, 0x3b, 0xae, 0xe5, 0xe2, 0xf0, 0x5a, 0x1e, 0x36, 0x6e, 0xdf, 0xb5, 0x7d, 0xeb, 0x6c, 0x96, + 0xa7, 0xea, 0x4b, 0x32, 0xfb, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x75, 0x6d, 0x59, 0xd4, 0x04, + 0x00, 0x00, } diff --git a/services/shhext/chat/encryption.proto b/services/shhext/chat/encryption.proto index 5ac619119..54e374489 100644 --- a/services/shhext/chat/encryption.proto +++ b/services/shhext/chat/encryption.proto @@ -5,6 +5,7 @@ package chat; message SignedPreKey { bytes signed_pre_key = 1; uint32 version = 2; + uint32 protocol_version = 3; } // X3DH prekey bundle @@ -77,7 +78,4 @@ message ProtocolMessage { // Public chats, not encrypted bytes public_message = 102; - - // Version of the protocol - uint32 version = 103; } diff --git a/services/shhext/chat/persistence.go b/services/shhext/chat/persistence.go index cc44de086..669784638 100644 --- a/services/shhext/chat/persistence.go +++ b/services/shhext/chat/persistence.go @@ -6,6 +6,11 @@ import ( dr "github.com/status-im/doubleratchet" ) +type Installation struct { + ID string + Version uint32 +} + // RatchetInfo holds the current ratchet state type RatchetInfo struct { ID []byte @@ -26,12 +31,12 @@ type PersistenceService interface { GetSessionStorage() dr.SessionStorage // GetPublicBundle retrieves an existing Bundle for the specified public key & installationIDs. - GetPublicBundle(*ecdsa.PublicKey, []string) (*Bundle, error) + GetPublicBundle(*ecdsa.PublicKey, []*Installation) (*Bundle, error) // AddPublicBundle persists a specified Bundle AddPublicBundle(*Bundle) error // GetAnyPrivateBundle retrieves any bundle for our identity & installationIDs - GetAnyPrivateBundle([]byte, []string) (*BundleContainer, error) + GetAnyPrivateBundle([]byte, []*Installation) (*BundleContainer, error) // GetPrivateKeyBundle retrieves a BundleContainer with the specified signed prekey. GetPrivateKeyBundle([]byte) ([]byte, error) // AddPrivateBundle persists a BundleContainer. @@ -50,9 +55,9 @@ type PersistenceService interface { RatchetInfoConfirmed([]byte, []byte, string) error // GetActiveInstallations returns the active installations for a given identity. - GetActiveInstallations(maxInstallations int, identity []byte) ([]string, error) + GetActiveInstallations(maxInstallations int, identity []byte) ([]*Installation, error) // AddInstallations adds the installations for a given identity. - AddInstallations(identity []byte, timestamp int64, installationIDs []string, enabled bool) error + AddInstallations(identity []byte, timestamp int64, installations []*Installation, enabled bool) error // EnableInstallation enables the installation. EnableInstallation(identity []byte, installationID string) error // DisableInstallation disable the installation. diff --git a/services/shhext/chat/protocol.go b/services/shhext/chat/protocol.go index 1a763a8cb..5de322cd1 100644 --- a/services/shhext/chat/protocol.go +++ b/services/shhext/chat/protocol.go @@ -58,7 +58,6 @@ func (p *ProtocolService) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, pa protocolMessage := &ProtocolMessage{ InstallationId: p.encryption.config.InstallationID, PublicMessage: payload, - Version: protocolCurrentVersion, } return p.addBundle(myIdentityKey, protocolMessage, false) @@ -77,7 +76,6 @@ func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, pu protocolMessage := &ProtocolMessage{ InstallationId: p.encryption.config.InstallationID, DirectMessage: encryptionResponse, - Version: protocolCurrentVersion, } msg, err := p.addBundle(myIdentityKey, protocolMessage, true) @@ -109,9 +107,8 @@ func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, pu if agreed { return msg, sharedSecret.Key, nil - } else { - return msg, nil, nil } + return msg, nil, nil } // BuildDHMessage builds a message with DH encryption so that it can be decrypted by any other device. @@ -127,7 +124,6 @@ func (p *ProtocolService) BuildDHMessage(myIdentityKey *ecdsa.PrivateKey, destin protocolMessage := &ProtocolMessage{ InstallationId: p.encryption.config.InstallationID, DirectMessage: encryptionResponse, - Version: protocolCurrentVersion, } msg, err := p.addBundle(myIdentityKey, protocolMessage, true) @@ -211,7 +207,8 @@ func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPu p.log.Info("Checking version") // Handle protocol negotiation for compatible clients - if protocolMessage.Version >= topicNegotiationVersion { + version := getProtocolVersion(protocolMessage.GetBundles(), protocolMessage.GetInstallationId()) + if version >= topicNegotiationVersion { p.log.Info("Version greater than 1 negotianting") sharedSecret, err := p.topic.Receive(myIdentityKey, theirPublicKey, protocolMessage.GetInstallationId()) if err != nil { @@ -227,3 +224,25 @@ func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPu // Return error return nil, errors.New("no payload") } + +func getProtocolVersion(bundles []*Bundle, installationID string) uint32 { + if installationID == "" { + return 0 + } + + for _, bundle := range bundles { + signedPreKeys := bundle.GetSignedPreKeys() + if signedPreKeys == nil { + continue + } + + signedPreKey := signedPreKeys[installationID] + if signedPreKey == nil { + return 0 + } + + return signedPreKey.GetProtocolVersion() + } + + return 0 +} diff --git a/services/shhext/chat/protocol_test.go b/services/shhext/chat/protocol_test.go index b17becff6..f3e741c5c 100644 --- a/services/shhext/chat/protocol_test.go +++ b/services/shhext/chat/protocol_test.go @@ -39,7 +39,7 @@ func (s *ProtocolServiceTestSuite) SetupTest() { } addedBundlesHandler := func(addedBundles []IdentityAndIDPair) {} - onNewTopicHandler := func(topic [][]byte) {} + onNewTopicHandler := func(topic []*topic.Secret) {} s.alice = NewProtocolService( NewEncryptionService(alicePersistence, DefaultEncryptionServiceConfig("1")), diff --git a/services/shhext/chat/sql_lite_persistence.go b/services/shhext/chat/sql_lite_persistence.go index 9659efa6f..6af73f8ee 100644 --- a/services/shhext/chat/sql_lite_persistence.go +++ b/services/shhext/chat/sql_lite_persistence.go @@ -203,12 +203,13 @@ func (s *SQLLitePersistence) AddPublicBundle(b *Bundle) error { } // GetAnyPrivateBundle retrieves any bundle from the database containing a private key -func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte, installationIDs []string) (*BundleContainer, error) { +func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte, installations []*Installation) (*BundleContainer, error) { + versions := make(map[string]uint32) /* #nosec */ statement := `SELECT identity, private_key, signed_pre_key, installation_id, timestamp, version FROM bundles - WHERE expired = 0 AND identity = ? AND installation_id IN (?` + strings.Repeat(",?", len(installationIDs)-1) + ")" + WHERE expired = 0 AND identity = ? AND installation_id IN (?` + strings.Repeat(",?", len(installations)-1) + ")" stmt, err := s.db.Prepare(statement) if err != nil { return nil, err @@ -220,10 +221,13 @@ func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte, installat var privateKey []byte var version uint32 - args := make([]interface{}, len(installationIDs)+1) + args := make([]interface{}, len(installations)+1) args[0] = myIdentityKey - for i, installationID := range installationIDs { - args[i+1] = installationID + for i, installation := range installations { + // Lookup up map for versions + versions[installation.ID] = installation.Version + + args[i+1] = installation.ID } rows, err := stmt.Query(args...) @@ -263,7 +267,11 @@ func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte, installat bundle.Timestamp = timestamp } - bundle.SignedPreKeys[installationID] = &SignedPreKey{SignedPreKey: signedPreKey, Version: version} + bundle.SignedPreKeys[installationID] = &SignedPreKey{ + SignedPreKey: signedPreKey, + Version: version, + ProtocolVersion: versions[installationID], + } bundle.Identity = identity } @@ -315,18 +323,19 @@ 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, installationIDs []string) (*Bundle, error) { +func (s *SQLLitePersistence) GetPublicBundle(publicKey *ecdsa.PublicKey, installations []*Installation) (*Bundle, error) { - if len(installationIDs) == 0 { + if len(installations) == 0 { return nil, nil } + versions := make(map[string]uint32) identity := crypto.CompressPubkey(publicKey) /* #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) + `) + WHERE expired = 0 AND identity = ? AND installation_id IN (?` + strings.Repeat(",?", len(installations)-1) + `) ORDER BY version DESC` stmt, err := s.db.Prepare(statement) if err != nil { @@ -334,10 +343,12 @@ func (s *SQLLitePersistence) GetPublicBundle(publicKey *ecdsa.PublicKey, install } defer stmt.Close() - args := make([]interface{}, len(installationIDs)+1) + args := make([]interface{}, len(installations)+1) args[0] = identity - for i, installationID := range installationIDs { - args[i+1] = installationID + for i, installation := range installations { + // Lookup up map for versions + versions[installation.ID] = installation.Version + args[i+1] = installation.ID } rows, err := stmt.Query(args...) @@ -369,8 +380,9 @@ func (s *SQLLitePersistence) GetPublicBundle(publicKey *ecdsa.PublicKey, install } bundle.SignedPreKeys[installationID] = &SignedPreKey{ - SignedPreKey: signedPreKey, - Version: version, + SignedPreKey: signedPreKey, + Version: version, + ProtocolVersion: versions[installationID], } } @@ -743,8 +755,8 @@ func (s *SQLLiteSessionStorage) Load(id []byte) (*dr.State, error) { } // GetActiveInstallations returns the active installations for a given identity -func (s *SQLLitePersistence) GetActiveInstallations(maxInstallations int, identity []byte) ([]string, error) { - stmt, err := s.db.Prepare(`SELECT installation_id +func (s *SQLLitePersistence) GetActiveInstallations(maxInstallations int, identity []byte) ([]*Installation, error) { + stmt, err := s.db.Prepare(`SELECT installation_id, version FROM installations WHERE enabled = 1 AND identity = ? ORDER BY timestamp DESC @@ -753,7 +765,7 @@ func (s *SQLLitePersistence) GetActiveInstallations(maxInstallations int, identi return nil, err } - var installations []string + var installations []*Installation rows, err := stmt.Query(identity, maxInstallations) if err != nil { return nil, err @@ -761,13 +773,19 @@ func (s *SQLLitePersistence) GetActiveInstallations(maxInstallations int, identi for rows.Next() { var installationID string + var version uint32 err = rows.Scan( &installationID, + &version, ) if err != nil { return nil, err } - installations = append(installations, installationID) + installations = append(installations, &Installation{ + ID: installationID, + Version: version, + }) + } return installations, nil @@ -775,14 +793,14 @@ func (s *SQLLitePersistence) GetActiveInstallations(maxInstallations int, identi } // 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 { +func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) error { tx, err := s.db.Begin() if err != nil { return nil } - for _, installationID := range installationIDs { - stmt, err := tx.Prepare(`SELECT enabled + for _, installation := range installations { + stmt, err := tx.Prepare(`SELECT enabled, version FROM installations WHERE identity = ? AND installation_id = ? LIMIT 1`) @@ -792,16 +810,24 @@ func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64, defer stmt.Close() var oldEnabled bool + // We don't override version once we saw one + var oldVersion uint32 + latestVersion := installation.Version - err = stmt.QueryRow(identity, installationID).Scan(&oldEnabled) + err = stmt.QueryRow(identity, installation.ID).Scan(&oldEnabled, &oldVersion) if err != nil && err != sql.ErrNoRows { return err } // We update timestamp if present without changing enabled, only if this is a new bundle + // and we set the version to the latest we ever saw if err != sql.ErrNoRows { + if oldVersion > installation.Version { + latestVersion = oldVersion + } + stmt, err = tx.Prepare(`UPDATE installations - SET timestamp = ?, enabled = ? + SET timestamp = ?, enabled = ?, version = ? WHERE identity = ? AND installation_id = ? AND timestamp < ?`) @@ -812,8 +838,9 @@ func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64, _, err = stmt.Exec( timestamp, oldEnabled, + latestVersion, identity, - installationID, + installation.ID, timestamp, ) if err != nil { @@ -822,17 +849,18 @@ func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64, defer stmt.Close() } else { - stmt, err = tx.Prepare(`INSERT INTO installations(identity, installation_id, timestamp, enabled) - VALUES (?, ?, ?, ?)`) + stmt, err = tx.Prepare(`INSERT INTO installations(identity, installation_id, timestamp, enabled, version) + VALUES (?, ?, ?, ?, ?)`) if err != nil { return err } _, err = stmt.Exec( identity, - installationID, + installation.ID, timestamp, defaultEnabled, + latestVersion, ) if err != nil { return err diff --git a/services/shhext/chat/sql_lite_persistence_test.go b/services/shhext/chat/sql_lite_persistence_test.go index 31e79e9a4..097ffc66c 100644 --- a/services/shhext/chat/sql_lite_persistence_test.go +++ b/services/shhext/chat/sql_lite_persistence_test.go @@ -53,7 +53,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"), []string{installationID}) + anyPrivateBundle, err := s.service.GetAnyPrivateBundle([]byte("non-existing-id"), []*Installation{{ID: installationID, Version: 1}}) s.Require().NoError(err) s.Nil(anyPrivateBundle) @@ -70,7 +70,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, []string{installationID}) + anyPrivateBundle, err = s.service.GetAnyPrivateBundle(identity, []*Installation{{ID: installationID, Version: 1}}) s.Require().NoError(err) s.NotNil(anyPrivateBundle) s.Equal(bundle.GetBundle().GetSignedPreKeys()[installationID].SignedPreKey, anyPrivateBundle.GetBundle().GetSignedPreKeys()[installationID].SignedPreKey, "It returns the same bundle") @@ -80,7 +80,7 @@ func (s *SQLLitePersistenceTestSuite) TestPublicBundle() { key, err := crypto.GenerateKey() s.Require().NoError(err) - actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []string{"1"}) + actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}}) s.Require().NoError(err, "Error was not returned even though bundle is not there") s.Nil(actualBundle) @@ -91,7 +91,7 @@ func (s *SQLLitePersistenceTestSuite) TestPublicBundle() { err = s.service.AddPublicBundle(bundle) s.Require().NoError(err) - actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []string{"1"}) + actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 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") @@ -101,7 +101,7 @@ func (s *SQLLitePersistenceTestSuite) TestUpdatedBundle() { key, err := crypto.GenerateKey() s.Require().NoError(err) - actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []string{"1"}) + actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}}) s.Require().NoError(err, "Error was not returned even though bundle is not there") s.Nil(actualBundle) @@ -123,7 +123,7 @@ func (s *SQLLitePersistenceTestSuite) TestUpdatedBundle() { err = s.service.AddPublicBundle(bundle) s.Require().NoError(err) - actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []string{"1"}) + actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 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") @@ -133,7 +133,7 @@ func (s *SQLLitePersistenceTestSuite) TestOutOfOrderBundles() { key, err := crypto.GenerateKey() s.Require().NoError(err) - actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []string{"1"}) + actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}}) s.Require().NoError(err, "Error was not returned even though bundle is not there") s.Nil(actualBundle) @@ -160,7 +160,7 @@ func (s *SQLLitePersistenceTestSuite) TestOutOfOrderBundles() { err = s.service.AddPublicBundle(bundle1) s.Require().NoError(err) - actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []string{"1"}) + actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}}) s.Require().NoError(err) s.Equal(bundle2.GetIdentity(), actualBundle.GetIdentity(), "It sets the right identity") s.Equal(bundle2.GetSignedPreKeys()["1"].GetVersion(), uint32(1)) @@ -171,7 +171,7 @@ func (s *SQLLitePersistenceTestSuite) TestMultiplePublicBundle() { key, err := crypto.GenerateKey() s.Require().NoError(err) - actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []string{"1"}) + actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}}) s.Require().NoError(err, "Error was not returned even though bundle is not there") s.Nil(actualBundle) @@ -197,7 +197,7 @@ func (s *SQLLitePersistenceTestSuite) TestMultiplePublicBundle() { s.Require().NoError(err) // Returns the most recent bundle - actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []string{"1"}) + actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}}) s.Require().NoError(err) s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the identity") @@ -209,7 +209,7 @@ func (s *SQLLitePersistenceTestSuite) TestMultiDevicePublicBundle() { key, err := crypto.GenerateKey() s.Require().NoError(err) - actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []string{"1"}) + actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}}) s.Require().NoError(err, "Error was not returned even though bundle is not there") s.Nil(actualBundle) @@ -233,7 +233,11 @@ func (s *SQLLitePersistenceTestSuite) TestMultiDevicePublicBundle() { s.Require().NoError(err) // Returns the most recent bundle - actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []string{"1", "2"}) + actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, + []*Installation{ + {ID: "1", Version: 1}, + {ID: "2", Version: 1}, + }) s.Require().NoError(err) s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the identity") @@ -345,7 +349,10 @@ func (s *SQLLitePersistenceTestSuite) TestRatchetInfoNoBundle() { func (s *SQLLitePersistenceTestSuite) TestAddInstallations() { identity := []byte("alice") - installations := []string{"alice-1", "alice-2"} + installations := []*Installation{ + {ID: "alice-1", Version: 1}, + {ID: "alice-2", Version: 2}, + } err := s.service.AddInstallations( identity, 1, @@ -361,10 +368,50 @@ func (s *SQLLitePersistenceTestSuite) TestAddInstallations() { s.Require().Equal(installations, enabledInstallations) } +func (s *SQLLitePersistenceTestSuite) TestAddInstallationVersions() { + identity := []byte("alice") + installations := []*Installation{ + {ID: "alice-1", Version: 1}, + } + 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) + + installationsWithDowngradedVersion := []*Installation{ + {ID: "alice-1", Version: 0}, + } + + err = s.service.AddInstallations( + identity, + 3, + installationsWithDowngradedVersion, + 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"} + installations := []*Installation{ + {ID: "alice-1", Version: 1}, + {ID: "alice-2", Version: 2}, + } + err := s.service.AddInstallations( identity, 1, @@ -373,7 +420,11 @@ func (s *SQLLitePersistenceTestSuite) TestAddInstallationsLimit() { ) s.Require().NoError(err) - installations = []string{"alice-2", "alice-3"} + installations = []*Installation{ + {ID: "alice-1", Version: 1}, + {ID: "alice-3", Version: 3}, + } + err = s.service.AddInstallations( identity, 2, @@ -382,7 +433,12 @@ func (s *SQLLitePersistenceTestSuite) TestAddInstallationsLimit() { ) s.Require().NoError(err) - installations = []string{"alice-2", "alice-3", "alice-4"} + installations = []*Installation{ + {ID: "alice-2", Version: 2}, + {ID: "alice-3", Version: 3}, + {ID: "alice-4", Version: 4}, + } + err = s.service.AddInstallations( identity, 3, @@ -394,13 +450,17 @@ func (s *SQLLitePersistenceTestSuite) TestAddInstallationsLimit() { enabledInstallations, err := s.service.GetActiveInstallations(3, identity) s.Require().NoError(err) - s.Require().Equal([]string{"alice-2", "alice-3", "alice-4"}, enabledInstallations) + s.Require().Equal(installations, enabledInstallations) } func (s *SQLLitePersistenceTestSuite) TestAddInstallationsDisabled() { identity := []byte("alice") - installations := []string{"alice-1", "alice-2"} + installations := []*Installation{ + {ID: "alice-1", Version: 1}, + {ID: "alice-2", Version: 2}, + } + err := s.service.AddInstallations( identity, 1, @@ -418,7 +478,11 @@ func (s *SQLLitePersistenceTestSuite) TestAddInstallationsDisabled() { func (s *SQLLitePersistenceTestSuite) TestDisableInstallation() { identity := []byte("alice") - installations := []string{"alice-1", "alice-2"} + installations := []*Installation{ + {ID: "alice-1", Version: 1}, + {ID: "alice-2", Version: 2}, + } + err := s.service.AddInstallations( identity, 1, @@ -431,7 +495,11 @@ func (s *SQLLitePersistenceTestSuite) TestDisableInstallation() { s.Require().NoError(err) // We add the installations again - installations = []string{"alice-1", "alice-2"} + installations = []*Installation{ + {ID: "alice-1", Version: 1}, + {ID: "alice-2", Version: 2}, + } + err = s.service.AddInstallations( identity, 1, @@ -443,13 +511,18 @@ func (s *SQLLitePersistenceTestSuite) TestDisableInstallation() { actualInstallations, err := s.service.GetActiveInstallations(3, identity) s.Require().NoError(err) - s.Require().Equal([]string{"alice-2"}, actualInstallations) + expected := []*Installation{{ID: "alice-2", Version: 2}} + s.Require().Equal(expected, actualInstallations) } func (s *SQLLitePersistenceTestSuite) TestEnableInstallation() { identity := []byte("alice") - installations := []string{"alice-1", "alice-2"} + installations := []*Installation{ + {ID: "alice-1", Version: 1}, + {ID: "alice-2", Version: 2}, + } + err := s.service.AddInstallations( identity, 1, @@ -464,7 +537,8 @@ func (s *SQLLitePersistenceTestSuite) TestEnableInstallation() { actualInstallations, err := s.service.GetActiveInstallations(3, identity) s.Require().NoError(err) - s.Require().Equal([]string{"alice-2"}, actualInstallations) + expected := []*Installation{{ID: "alice-2", Version: 2}} + s.Require().Equal(expected, actualInstallations) err = s.service.EnableInstallation(identity, "alice-1") s.Require().NoError(err) @@ -472,7 +546,11 @@ func (s *SQLLitePersistenceTestSuite) TestEnableInstallation() { actualInstallations, err = s.service.GetActiveInstallations(3, identity) s.Require().NoError(err) - s.Require().Equal([]string{"alice-1", "alice-2"}, actualInstallations) + expected = []*Installation{ + {ID: "alice-1", Version: 1}, + {ID: "alice-2", Version: 2}, + } + s.Require().Equal(expected, actualInstallations) } diff --git a/services/shhext/chat/x3dh.go b/services/shhext/chat/x3dh.go index 703963586..082410676 100644 --- a/services/shhext/chat/x3dh.go +++ b/services/shhext/chat/x3dh.go @@ -96,7 +96,10 @@ func NewBundleContainer(identity *ecdsa.PrivateKey, installationID string) (*Bun encodedPreKey := crypto.FromECDSA(preKey) signedPreKeys := make(map[string]*SignedPreKey) - signedPreKeys[installationID] = &SignedPreKey{SignedPreKey: compressedPreKey} + signedPreKeys[installationID] = &SignedPreKey{ + ProtocolVersion: protocolCurrentVersion, + SignedPreKey: compressedPreKey, + } bundle := Bundle{ Timestamp: time.Now().UnixNano(), diff --git a/services/shhext/filter/service.go b/services/shhext/filter/service.go index fabf36ecf..9aac3536b 100644 --- a/services/shhext/filter/service.go +++ b/services/shhext/filter/service.go @@ -37,10 +37,10 @@ func chatIDToPartitionedTopic(identity string) (string, error) { partition.Mod(publicKey.X, nPartitions) - return fmt.Sprintf("contact-discovery-%d", partition), nil + return fmt.Sprintf("contact-discovery-%d", partition.Int64()), nil } -type FilterAndTopic struct { +type Filter struct { FilterID string Topic []byte SymKeyID string @@ -143,7 +143,9 @@ func (s *Service) Init(chats []*Chat) error { } for _, secret := range secrets { - s.ProcessNegotiatedSecret(secret) + if err := s.ProcessNegotiatedSecret(secret); err != nil { + return err + } } return nil @@ -215,7 +217,7 @@ func (s *Service) LoadOneToOne(myKey *ecdsa.PrivateKey, identity string, listen return nil } -func (s *Service) AddSymmetric(chatID string) (*FilterAndTopic, error) { +func (s *Service) AddSymmetric(chatID string) (*Filter, error) { var symKey []byte topic := toTopic(chatID) @@ -244,14 +246,14 @@ func (s *Service) AddSymmetric(chatID string) (*FilterAndTopic, error) { return nil, err } - return &FilterAndTopic{ + return &Filter{ FilterID: id, SymKeyID: symKeyID, Topic: topic, }, nil } -func (s *Service) AddAsymmetricFilter(keyAsym *ecdsa.PrivateKey, chatID string, listen bool) (*FilterAndTopic, error) { +func (s *Service) AddAsymmetricFilter(keyAsym *ecdsa.PrivateKey, chatID string, listen bool) (*Filter, error) { var err error var pow float64 @@ -278,7 +280,7 @@ func (s *Service) AddAsymmetricFilter(keyAsym *ecdsa.PrivateKey, chatID string, return nil, err } - return &FilterAndTopic{FilterID: id, Topic: topic}, nil + return &Filter{FilterID: id, Topic: topic}, nil } func (s *Service) LoadPublic(chat *Chat) error { diff --git a/services/shhext/filter/service_test.go b/services/shhext/filter/service_test.go index 98fd83393..55968933c 100644 --- a/services/shhext/filter/service_test.go +++ b/services/shhext/filter/service_test.go @@ -101,10 +101,10 @@ func (s *ServiceTestSuite) TestDiscoveryAndPartitionedTopic() { func (s *ServiceTestSuite) TestPublicAndOneToOneChats() { chats := []*Chat{ - &Chat{ + { ChatID: "status", }, - &Chat{ + { ChatID: s.keys[1].PublicKeyString(), Identity: s.keys[1].PublicKeyString(), OneToOne: true, diff --git a/services/shhext/service.go b/services/shhext/service.go index 2b52c849f..37ab2cbe0 100644 --- a/services/shhext/service.go +++ b/services/shhext/service.go @@ -308,7 +308,12 @@ func (s *Service) Stop() error { s.requestsRegistry.Clear() s.envelopesMonitor.Stop() s.mailMonitor.Stop() - s.filter.Stop() + if s.filter != nil { + if err := s.filter.Stop(); err != nil { + log.Error("Failed to stop filter service with error", "err", err) + } + } + return nil } diff --git a/static/chat_db_migrations/1558588866_add_version.up.sql b/static/chat_db_migrations/1558588866_add_version.up.sql new file mode 100644 index 000000000..10c117208 --- /dev/null +++ b/static/chat_db_migrations/1558588866_add_version.up.sql @@ -0,0 +1 @@ +ALTER TABLE installations ADD version INTEGER DEFAULT 0;