mirror of
https://github.com/mautrix/signal.git
synced 2025-03-14 14:15:36 +00:00
parent
cf216cba7a
commit
29d55ebbde
17 changed files with 145 additions and 19 deletions
55
pkg/libsignalgo/accountentropy.go
Normal file
55
pkg/libsignalgo/accountentropy.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
// mautrix-signal - A Matrix-signal puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package libsignalgo
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lsignal_ffi -ldl -lm
|
||||
#include "./libsignal-ffi.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type AccountEntropyPool string
|
||||
|
||||
func (aep AccountEntropyPool) DeriveSVRKey() ([]byte, error) {
|
||||
var out [C.SignalSVR_KEY_LEN]byte
|
||||
signalFfiError := C.signal_account_entropy_pool_derive_svr_key(
|
||||
(*[C.SignalSVR_KEY_LEN]C.uint8_t)(unsafe.Pointer(&out)),
|
||||
C.CString(string(aep)),
|
||||
)
|
||||
runtime.KeepAlive(aep)
|
||||
if signalFfiError != nil {
|
||||
return nil, wrapError(signalFfiError)
|
||||
}
|
||||
return out[:], nil
|
||||
}
|
||||
|
||||
func (aep AccountEntropyPool) DeriveBackupKey() ([]byte, error) {
|
||||
var out [C.SignalBACKUP_KEY_LEN]byte
|
||||
signalFfiError := C.signal_account_entropy_pool_derive_backup_key(
|
||||
(*[C.SignalBACKUP_KEY_LEN]C.uint8_t)(unsafe.Pointer(&out)),
|
||||
C.CString(string(aep)),
|
||||
)
|
||||
runtime.KeepAlive(aep)
|
||||
if signalFfiError != nil {
|
||||
return nil, wrapError(signalFfiError)
|
||||
}
|
||||
return out[:], nil
|
||||
}
|
2
pkg/signalmeow/protobuf/ContactDiscovery.pb.go
generated
2
pkg/signalmeow/protobuf/ContactDiscovery.pb.go
generated
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc v3.21.12
|
||||
// source: ContactDiscovery.proto
|
||||
|
||||
|
|
2
pkg/signalmeow/protobuf/DeviceName.pb.go
generated
2
pkg/signalmeow/protobuf/DeviceName.pb.go
generated
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc v3.21.12
|
||||
// source: DeviceName.proto
|
||||
|
||||
|
|
2
pkg/signalmeow/protobuf/Groups.pb.go
generated
2
pkg/signalmeow/protobuf/Groups.pb.go
generated
|
@ -5,7 +5,7 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc v3.21.12
|
||||
// source: Groups.proto
|
||||
|
||||
|
|
2
pkg/signalmeow/protobuf/Provisioning.pb.go
generated
2
pkg/signalmeow/protobuf/Provisioning.pb.go
generated
|
@ -5,7 +5,7 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc v3.21.12
|
||||
// source: Provisioning.proto
|
||||
|
||||
|
|
22
pkg/signalmeow/protobuf/SignalService.pb.go
generated
22
pkg/signalmeow/protobuf/SignalService.pb.go
generated
|
@ -5,7 +5,7 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc v3.21.12
|
||||
// source: SignalService.proto
|
||||
|
||||
|
@ -6243,8 +6243,10 @@ type SyncMessage_Keys struct {
|
|||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// @deprecated
|
||||
StorageService []byte `protobuf:"bytes,1,opt,name=storageService" json:"storageService,omitempty"`
|
||||
Master []byte `protobuf:"bytes,2,opt,name=master" json:"master,omitempty"`
|
||||
StorageService []byte `protobuf:"bytes,1,opt,name=storageService" json:"storageService,omitempty"`
|
||||
Master []byte `protobuf:"bytes,2,opt,name=master" json:"master,omitempty"`
|
||||
AccountEntropyPool *string `protobuf:"bytes,3,opt,name=accountEntropyPool" json:"accountEntropyPool,omitempty"` // Copied manually from Signal Desktop
|
||||
MediaRootBackupKey []byte `protobuf:"bytes,4,opt,name=mediaRootBackupKey" json:"mediaRootBackupKey,omitempty"` // Copied manually from Signal Desktop
|
||||
}
|
||||
|
||||
func (x *SyncMessage_Keys) Reset() {
|
||||
|
@ -6291,6 +6293,20 @@ func (x *SyncMessage_Keys) GetMaster() []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *SyncMessage_Keys) GetAccountEntropyPool() string {
|
||||
if x != nil && x.AccountEntropyPool != nil {
|
||||
return *x.AccountEntropyPool
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SyncMessage_Keys) GetMediaRootBackupKey() []byte {
|
||||
if x != nil {
|
||||
return x.MediaRootBackupKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SyncMessage_MessageRequestResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
|
BIN
pkg/signalmeow/protobuf/SignalService.pb.raw
generated
BIN
pkg/signalmeow/protobuf/SignalService.pb.raw
generated
Binary file not shown.
|
@ -547,6 +547,8 @@ message SyncMessage {
|
|||
// @deprecated
|
||||
optional bytes storageService = 1;
|
||||
optional bytes master = 2;
|
||||
optional string accountEntropyPool = 3; // Copied manually from Signal Desktop
|
||||
optional bytes mediaRootBackupKey = 4; // Copied manually from Signal Desktop
|
||||
}
|
||||
|
||||
message MessageRequestResponse {
|
||||
|
|
2
pkg/signalmeow/protobuf/StickerResources.pb.go
generated
2
pkg/signalmeow/protobuf/StickerResources.pb.go
generated
|
@ -5,7 +5,7 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc v3.21.12
|
||||
// source: StickerResources.proto
|
||||
|
||||
|
|
13
pkg/signalmeow/protobuf/StorageService.pb.go
generated
13
pkg/signalmeow/protobuf/StorageService.pb.go
generated
|
@ -5,7 +5,7 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc v3.21.12
|
||||
// source: StorageService.proto
|
||||
|
||||
|
@ -623,7 +623,8 @@ type ManifestRecord struct {
|
|||
|
||||
Version uint64 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
|
||||
SourceDevice uint32 `protobuf:"varint,3,opt,name=sourceDevice,proto3" json:"sourceDevice,omitempty"`
|
||||
Identifiers []*ManifestRecord_Identifier `protobuf:"bytes,2,rep,name=identifiers,proto3" json:"identifiers,omitempty"` // Next ID: 4
|
||||
Identifiers []*ManifestRecord_Identifier `protobuf:"bytes,2,rep,name=identifiers,proto3" json:"identifiers,omitempty"` // Next ID: 4
|
||||
RecordIkm []byte `protobuf:"bytes,4,opt,name=recordIkm,proto3,oneof" json:"recordIkm,omitempty"` // Copied manually from Signal Desktop
|
||||
}
|
||||
|
||||
func (x *ManifestRecord) Reset() {
|
||||
|
@ -677,6 +678,13 @@ func (x *ManifestRecord) GetIdentifiers() []*ManifestRecord_Identifier {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *ManifestRecord) GetRecordIkm() []byte {
|
||||
if x != nil {
|
||||
return x.RecordIkm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type StorageRecord struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
@ -2130,6 +2138,7 @@ func file_StorageService_proto_init() {
|
|||
if File_StorageService_proto != nil {
|
||||
return
|
||||
}
|
||||
file_StorageService_proto_msgTypes[5].OneofWrappers = []any{}
|
||||
file_StorageService_proto_msgTypes[6].OneofWrappers = []any{
|
||||
(*StorageRecord_Contact)(nil),
|
||||
(*StorageRecord_GroupV1)(nil),
|
||||
|
|
BIN
pkg/signalmeow/protobuf/StorageService.pb.raw
generated
BIN
pkg/signalmeow/protobuf/StorageService.pb.raw
generated
Binary file not shown.
|
@ -61,6 +61,8 @@ message ManifestRecord {
|
|||
uint32 sourceDevice = 3;
|
||||
repeated Identifier identifiers = 2;
|
||||
// Next ID: 4
|
||||
|
||||
optional bytes recordIkm = 4; // Copied manually from Signal Desktop
|
||||
}
|
||||
|
||||
message StorageRecord {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc v3.21.12
|
||||
// source: UnidentifiedDelivery.proto
|
||||
|
||||
|
|
2
pkg/signalmeow/protobuf/WebSocketResources.pb.go
generated
2
pkg/signalmeow/protobuf/WebSocketResources.pb.go
generated
|
@ -5,7 +5,7 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc v3.21.12
|
||||
// source: WebSocketResources.proto
|
||||
|
||||
|
|
|
@ -166,6 +166,14 @@ func PerformProvisioning(ctx context.Context, deviceStore store.DeviceStore, dev
|
|||
Password: password,
|
||||
MasterKey: provisioningMessage.GetMasterKey(),
|
||||
}
|
||||
if provisioningMessage.GetMasterKey() == nil && provisioningMessage.GetAccountEntropyPool() != "" {
|
||||
data.MasterKey, err = libsignalgo.AccountEntropyPool(provisioningMessage.GetAccountEntropyPool()).DeriveSVRKey()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to derive master key from account entropy pool")
|
||||
} else {
|
||||
log.Debug().Msg("Derived master key from account entropy pool")
|
||||
}
|
||||
}
|
||||
|
||||
// Store the provisioning data
|
||||
err = deviceStore.PutDevice(ctx, data)
|
||||
|
@ -334,6 +342,7 @@ func continueProvisioning(ctx context.Context, ws *websocket.Conn, provisioningC
|
|||
var signalCapabilities = map[string]any{
|
||||
"deleteSync": true,
|
||||
"versionedExpirationTimer": true,
|
||||
"ssre2": true,
|
||||
}
|
||||
|
||||
var signalCapabilitiesBody = exerrors.Must(json.Marshal(signalCapabilities))
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package signalmeow
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
@ -718,7 +719,23 @@ func (cli *Client) handleDecryptedResult(
|
|||
// TODO: handle more sync messages
|
||||
if content.SyncMessage != nil {
|
||||
if content.SyncMessage.Keys != nil {
|
||||
aep := libsignalgo.AccountEntropyPool(content.SyncMessage.Keys.GetAccountEntropyPool())
|
||||
cli.Store.MasterKey = content.SyncMessage.Keys.GetMaster()
|
||||
if aep != "" {
|
||||
aepMasterKey, err := aep.DeriveSVRKey()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to derive master key from account entropy pool")
|
||||
} else if cli.Store.MasterKey == nil {
|
||||
cli.Store.MasterKey = aepMasterKey
|
||||
log.Debug().Msg("Derived master key from account entropy pool (no master key in sync message)")
|
||||
} else if !bytes.Equal(aepMasterKey, cli.Store.MasterKey) {
|
||||
log.Warn().Msg("Derived master key doesn't match one in sync message")
|
||||
} else {
|
||||
log.Debug().Msg("Derived master key matches one in sync message")
|
||||
}
|
||||
} else {
|
||||
log.Debug().Msg("No account entropy pool in sync message")
|
||||
}
|
||||
err = cli.Store.DeviceStore.PutDevice(ctx, &cli.Store.DeviceData)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to save device after receiving master key")
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/exerrors"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
@ -146,7 +147,7 @@ func (cli *Client) FetchStorage(ctx context.Context, masterKey []byte, currentVe
|
|||
}
|
||||
delete(newKeys, key)
|
||||
}
|
||||
newRecords, missingKeys, err := cli.fetchStorageRecords(ctx, storageKey, newKeys)
|
||||
newRecords, missingKeys, err := cli.fetchStorageRecords(ctx, storageKey, manifest.GetRecordIkm(), newKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -178,10 +179,20 @@ func deriveStorageManifestKey(storageKey []byte, version uint64) []byte {
|
|||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func deriveStorageItemKey(storageKey []byte, itemID string) []byte {
|
||||
h := hmac.New(sha256.New, storageKey)
|
||||
exerrors.Must(fmt.Fprintf(h, "Item_%s", itemID))
|
||||
return h.Sum(nil)
|
||||
const storageServiceItemKeyInfoPrefix = "20240801_SIGNAL_STORAGE_SERVICE_ITEM_"
|
||||
const storageServiceItemKeyLen = 32
|
||||
|
||||
func deriveStorageItemKey(storageKey, recordIKM []byte, itemID string) []byte {
|
||||
if recordIKM == nil {
|
||||
h := hmac.New(sha256.New, storageKey)
|
||||
exerrors.Must(fmt.Fprintf(h, "Item_%s", itemID))
|
||||
return h.Sum(nil)
|
||||
} else {
|
||||
h := hkdf.New(sha256.New, recordIKM, []byte{}, append([]byte(storageServiceItemKeyInfoPrefix), itemID...))
|
||||
out := make([]byte, storageServiceItemKeyLen)
|
||||
exerrors.Must(io.ReadFull(h, out))
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
// MaxReadStorageRecords is the maximum number of storage records to fetch at once
|
||||
|
@ -231,7 +242,12 @@ func (cli *Client) fetchStorageManifest(ctx context.Context, storageKey []byte,
|
|||
}
|
||||
}
|
||||
|
||||
func (cli *Client) fetchStorageRecords(ctx context.Context, storageKey []byte, inputRecords map[string]signalpb.ManifestRecord_Identifier_Type) ([]*DecryptedStorageRecord, []string, error) {
|
||||
func (cli *Client) fetchStorageRecords(
|
||||
ctx context.Context,
|
||||
storageKey []byte,
|
||||
recordIKM []byte,
|
||||
inputRecords map[string]signalpb.ManifestRecord_Identifier_Type,
|
||||
) ([]*DecryptedStorageRecord, []string, error) {
|
||||
recordKeys := make([][]byte, 0, len(inputRecords))
|
||||
for key := range inputRecords {
|
||||
decoded, err := base64.StdEncoding.DecodeString(key)
|
||||
|
@ -262,7 +278,7 @@ func (cli *Client) fetchStorageRecords(ctx context.Context, storageKey []byte, i
|
|||
log.Warn().Int("item_index", i).Str("item_key", base64Key).Msg("Received unexpected storage item")
|
||||
continue
|
||||
}
|
||||
itemKey := deriveStorageItemKey(storageKey, base64Key)
|
||||
itemKey := deriveStorageItemKey(storageKey, recordIKM, base64Key)
|
||||
decryptedItemBytes, err := decryptBytes(itemKey, encryptedItem.GetValue())
|
||||
if err != nil {
|
||||
log.Warn().Err(err).
|
||||
|
|
Loading…
Add table
Reference in a new issue