diff --git a/appservice/registration.go b/appservice/registration.go index b11bd84b..c0b62124 100644 --- a/appservice/registration.go +++ b/appservice/registration.go @@ -28,6 +28,7 @@ type Registration struct { SoruEphemeralEvents bool `yaml:"de.sorunome.msc2409.push_ephemeral,omitempty" json:"de.sorunome.msc2409.push_ephemeral,omitempty"` EphemeralEvents bool `yaml:"push_ephemeral,omitempty" json:"push_ephemeral,omitempty"` + MSC3202 bool `yaml:"org.matrix.msc3202,omitempty" json:"org.matrix.msc3202,omitempty"` } // CreateRegistration creates a Registration with random appservice and homeserver tokens. diff --git a/client.go b/client.go index 686ec21f..e1b36f87 100644 --- a/client.go +++ b/client.go @@ -110,6 +110,9 @@ type Client struct { // Should the ?user_id= query parameter be set in requests? // See https://spec.matrix.org/v1.6/application-service-api/#identity-assertion SetAppServiceUserID bool + // Should the org.matrix.msc3202.device_id query parameter be set in requests? + // See https://github.com/matrix-org/matrix-spec-proposals/pull/3202 + SetAppServiceDeviceID bool syncingID uint32 // Identifies the current Sync. Only one Sync can be active at any given time. } diff --git a/crypto/cryptohelper/cryptohelper.go b/crypto/cryptohelper/cryptohelper.go index 7bb7037d..5f1a952f 100644 --- a/crypto/cryptohelper/cryptohelper.go +++ b/crypto/cryptohelper/cryptohelper.go @@ -38,6 +38,8 @@ type CryptoHelper struct { LoginAs *mautrix.ReqLogin + ASEventProcessor crypto.ASEventProcessor + DBAccountID string } @@ -58,7 +60,7 @@ func NewCryptoHelper(cli *mautrix.Client, pickleKey []byte, store any) (*CryptoH return nil, fmt.Errorf("pickle key must be provided") } _, isExtensible := cli.Syncer.(mautrix.ExtensibleSyncer) - if !isExtensible { + if !cli.SetAppServiceDeviceID && !isExtensible { return nil, fmt.Errorf("the client syncer must implement ExtensibleSyncer") } @@ -111,7 +113,11 @@ func (helper *CryptoHelper) Init(ctx context.Context) error { } syncer, ok := helper.client.Syncer.(mautrix.ExtensibleSyncer) if !ok { - return fmt.Errorf("the client syncer must implement ExtensibleSyncer") + if !helper.client.SetAppServiceDeviceID { + return fmt.Errorf("the client syncer must implement ExtensibleSyncer") + } else if helper.ASEventProcessor == nil { + return fmt.Errorf("an appservice must be provided when using appservice mode encryption") + } } var stateStore crypto.StateStore @@ -140,7 +146,27 @@ func (helper *CryptoHelper) Init(ctx context.Context) error { if err != nil { return fmt.Errorf("failed to find existing device ID: %w", err) } - if helper.LoginAs != nil { + if helper.LoginAs != nil && helper.LoginAs.Type == mautrix.AuthTypeAppservice && helper.client.SetAppServiceDeviceID { + if storedDeviceID == "" { + helper.log.Debug(). + Str("username", helper.LoginAs.Identifier.User). + Msg("Logging in with appservice") + var resp *mautrix.RespLogin + resp, err = helper.client.Login(ctx, helper.LoginAs) + if err != nil { + return err + } + managedCryptoStore.DeviceID = resp.DeviceID + helper.client.DeviceID = resp.DeviceID + } else { + helper.log.Debug(). + Str("username", helper.LoginAs.Identifier.User). + Stringer("device_id", storedDeviceID). + Msg("Using existing device") + managedCryptoStore.DeviceID = storedDeviceID + helper.client.DeviceID = storedDeviceID + } + } else if helper.LoginAs != nil { if storedDeviceID != "" { helper.LoginAs.DeviceID = storedDeviceID } @@ -177,16 +203,29 @@ func (helper *CryptoHelper) Init(ctx context.Context) error { return err } - syncer.OnSync(helper.mach.ProcessSyncResponse) - syncer.OnEventType(event.StateMember, helper.mach.HandleMemberEvent) - if _, ok = helper.client.Syncer.(mautrix.DispatchableSyncer); ok { - syncer.OnEventType(event.EventEncrypted, helper.HandleEncrypted) + if syncer != nil { + syncer.OnSync(helper.mach.ProcessSyncResponse) + syncer.OnEventType(event.StateMember, helper.mach.HandleMemberEvent) + if _, ok = helper.client.Syncer.(mautrix.DispatchableSyncer); ok { + syncer.OnEventType(event.EventEncrypted, helper.HandleEncrypted) + } else { + helper.log.Warn().Msg("Client syncer does not implement DispatchableSyncer. Events will not be decrypted automatically.") + } + if helper.managedStateStore != nil { + syncer.OnEvent(helper.client.StateStoreSyncHandler) + } } else { - helper.log.Warn().Msg("Client syncer does not implement DispatchableSyncer. Events will not be decrypted automatically.") + helper.mach.AddAppserviceListener(helper.ASEventProcessor) + helper.ASEventProcessor.On(event.EventEncrypted, helper.HandleEncrypted) } - if helper.managedStateStore != nil { - syncer.OnEvent(helper.client.StateStoreSyncHandler) + + if helper.client.SetAppServiceDeviceID { + err = helper.mach.ShareKeys(ctx, -1) + if err != nil { + return fmt.Errorf("failed to share keys: %w", err) + } } + return nil } @@ -281,7 +320,11 @@ func (helper *CryptoHelper) HandleEncrypted(ctx context.Context, evt *event.Even func (helper *CryptoHelper) postDecrypt(ctx context.Context, decrypted *event.Event) { decrypted.Mautrix.EventSource |= event.SourceDecrypted - helper.client.Syncer.(mautrix.DispatchableSyncer).Dispatch(ctx, decrypted) + if helper.ASEventProcessor != nil { + helper.ASEventProcessor.Dispatch(ctx, decrypted) + } else { + helper.client.Syncer.(mautrix.DispatchableSyncer).Dispatch(ctx, decrypted) + } } func (helper *CryptoHelper) RequestSession(ctx context.Context, roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID, userID id.UserID, deviceID id.DeviceID) { diff --git a/crypto/machine.go b/crypto/machine.go index 2477b9e1..188aa210 100644 --- a/crypto/machine.go +++ b/crypto/machine.go @@ -208,13 +208,14 @@ func (mach *OlmMachine) OwnIdentity() *id.Device { } } -type asEventProcessor interface { +type ASEventProcessor interface { On(evtType event.Type, handler func(ctx context.Context, evt *event.Event)) OnOTK(func(ctx context.Context, otk *mautrix.OTKCount)) OnDeviceList(func(ctx context.Context, lists *mautrix.DeviceLists, since string)) + Dispatch(ctx context.Context, evt *event.Event) } -func (mach *OlmMachine) AddAppserviceListener(ep asEventProcessor) { +func (mach *OlmMachine) AddAppserviceListener(ep ASEventProcessor) { // ToDeviceForwardedRoomKey and ToDeviceRoomKey should only be present inside encrypted to-device events ep.On(event.ToDeviceEncrypted, mach.HandleToDeviceEvent) ep.On(event.ToDeviceRoomKeyRequest, mach.HandleToDeviceEvent) diff --git a/requests.go b/requests.go index f91aaa79..c49f7c9c 100644 --- a/requests.go +++ b/requests.go @@ -225,7 +225,7 @@ func (otk *OneTimeKey) MarshalJSON() ([]byte, error) { type ReqUploadKeys struct { DeviceKeys *DeviceKeys `json:"device_keys,omitempty"` - OneTimeKeys map[id.KeyID]OneTimeKey `json:"one_time_keys"` + OneTimeKeys map[id.KeyID]OneTimeKey `json:"one_time_keys,omitempty"` } type ReqKeysSignatures struct { diff --git a/url.go b/url.go index 4646b442..f35ae5e2 100644 --- a/url.go +++ b/url.go @@ -102,6 +102,10 @@ func (cli *Client) BuildURLWithQuery(urlPath PrefixableURLPath, urlQuery map[str if cli.SetAppServiceUserID { query.Set("user_id", string(cli.UserID)) } + if cli.SetAppServiceDeviceID && cli.DeviceID != "" { + query.Set("device_id", string(cli.DeviceID)) + query.Set("org.matrix.msc3202.device_id", string(cli.DeviceID)) + } if urlQuery != nil { for k, v := range urlQuery { query.Set(k, v)