From 77a1514c900a0d422a616a548496a48cf6baf35f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 24 Jul 2020 21:47:22 +0300 Subject: Add device list and legacy verification commands --- ui/commands.go | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 194 insertions(+), 1 deletion(-) (limited to 'ui/commands.go') diff --git a/ui/commands.go b/ui/commands.go index 49bff0f..b406278 100644 --- a/ui/commands.go +++ b/ui/commands.go @@ -37,6 +37,7 @@ import ( "github.com/russross/blackfriday/v2" "maunium.net/go/mautrix" + "maunium.net/go/mautrix/crypto" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" @@ -365,6 +366,187 @@ func cmdFingerprint(cmd *Command) { } } +// region TODO these four functions currently use the crypto internals directly. switch to interfaces before releasing + +func autocompleteDeviceUserID(cmd *CommandAutocomplete) (completions []string, newText string) { + userCompletions := cmd.Room.AutocompleteUser(cmd.Args[0]) + if len(userCompletions) == 1 { + newText = fmt.Sprintf("/%s %s ", cmd.OrigCommand, userCompletions[0].id) + } else { + completions = make([]string, len(userCompletions)) + for i, completion := range userCompletions { + completions[i] = completion.id + } + } + return +} + +func autocompleteDeviceDeviceID(cmd *CommandAutocomplete) (completions []string, newText string) { + mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) + devices, err := mach.CryptoStore.GetDevices(id.UserID(cmd.Args[0])) + if len(devices) == 0 || err != nil { + return + } + var completedDeviceID id.DeviceID + if len(cmd.Args) > 1 { + existingID := strings.ToUpper(cmd.Args[1]) + for _, device := range devices { + deviceIDStr := string(device.DeviceID) + if deviceIDStr == existingID { + // We don't want to do any autocompletion if there's already a full device ID there. + return []string{}, "" + } else if strings.HasPrefix(strings.ToUpper(device.Name), existingID) || strings.HasPrefix(deviceIDStr, existingID) { + completedDeviceID = device.DeviceID + completions = append(completions, fmt.Sprintf("%s (%s)", device.DeviceID, device.Name)) + } + } + } else { + completions = make([]string, len(devices)) + i := 0 + for _, device := range devices { + completedDeviceID = device.DeviceID + completions[i] = fmt.Sprintf("%s (%s)", device.DeviceID, device.Name) + i++ + } + } + if len(completions) == 1 { + newText = fmt.Sprintf("/%s %s %s ", cmd.OrigCommand, cmd.Args[0], completedDeviceID) + } + return +} + +func autocompleteDevice(cmd *CommandAutocomplete) ([]string, string) { + if len(cmd.Args) == 0 { + return []string{}, "" + } else if len(cmd.Args) == 1 && !unicode.IsSpace(rune(cmd.RawArgs[len(cmd.RawArgs)-1])) { + return autocompleteDeviceUserID(cmd) + } else if cmd.Command != "devices" { + return autocompleteDeviceDeviceID(cmd) + } + return []string{}, "" +} + +func getDevice(cmd *Command) *crypto.DeviceIdentity { + if len(cmd.Args) < 2 { + cmd.Reply("Usage: /%s [fingerprint]", cmd.Command) + return nil + } + mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) + device, err := mach.GetOrFetchDevice(id.UserID(cmd.Args[0]), id.DeviceID(cmd.Args[1])) + if err != nil { + cmd.Reply("Failed to get device: %v", err) + return nil + } + return device +} + +func putDevice(cmd *Command, device *crypto.DeviceIdentity, action string) { + mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) + err := mach.CryptoStore.PutDevice(device.UserID, device) + if err != nil { + cmd.Reply("Failed to save device: %v", err) + } else { + cmd.Reply("Successfully %s %s/%s (%s)", action, device.UserID, device.DeviceID, device.Name) + } + mach.OnDevicesChanged(device.UserID) +} + +func cmdDevices(cmd *Command) { + if len(cmd.Args) == 0 { + cmd.Reply("Usage: /devices ") + return + } + userID := id.UserID(cmd.Args[0]) + mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) + devices, err := mach.CryptoStore.GetDevices(userID) + if err != nil { + cmd.Reply("Failed to get device list: %v", err) + } + if len(devices) == 0 { + cmd.Reply("Fetching device list from server...") + devices = mach.LoadDevices(userID) + } + if len(devices) == 0 { + cmd.Reply("No devices found for %s", userID) + return + } + var buf strings.Builder + for _, device := range devices { + _, _ = fmt.Fprintf(&buf, "%s (%s) - %s - %s\n", device.DeviceID, device.Name, device.Trust.String(), device.Fingerprint()) + } + resp := buf.String() + cmd.Reply(resp[:len(resp)-1]) +} + +func cmdDevice(cmd *Command) { + device := getDevice(cmd) + if device == nil { + return + } + deviceType := "Device" + if device.Deleted { + deviceType = "Deleted device" + } + cmd.Reply("%s %s of %s\nFingerprint: %s\nIdentity key: %s\nDevice name: %s\nTrust state: %s", + deviceType, device.DeviceID, device.UserID, + device.Fingerprint(), device.IdentityKey, + device.Name, device.Trust.String()) +} + +func cmdVerify(cmd *Command) { + device := getDevice(cmd) + if device == nil { + return + } + if len(cmd.Args) == 2 { + cmd.Reply("Interactive verification UI is not yet implemented") + } else { + fingerprint := strings.Join(cmd.Args[2:], "") + if string(device.SigningKey) != fingerprint { + cmd.Reply("Mismatching fingerprint") + return + } + action := "verified" + if device.Trust == crypto.TrustStateBlacklisted { + action = "unblacklisted and verified" + } + device.Trust = crypto.TrustStateVerified + putDevice(cmd, device, action) + } +} + +func cmdUnverify(cmd *Command) { + device := getDevice(cmd) + if device == nil { + return + } + if device.Trust == crypto.TrustStateUnset { + cmd.Reply("That device is already not verified") + return + } + action := "unverified" + if device.Trust == crypto.TrustStateBlacklisted { + action = "unblacklisted" + } + device.Trust = crypto.TrustStateUnset + putDevice(cmd, device, action) +} + +func cmdBlacklist(cmd *Command) { + device := getDevice(cmd) + if device == nil { + return + } + action := "blacklisted" + if device.Trust == crypto.TrustStateVerified { + action = "unverified and blacklisted" + } + device.Trust = crypto.TrustStateBlacklisted + putDevice(cmd, device, action) +} + +// endregion + func cmdHeapProfile(cmd *Command) { if len(cmd.Args) == 0 || cmd.Args[0] != "nogc" { runtime.GC() @@ -449,7 +631,18 @@ Things: rooms, users, baremessages, images, typingnotif /rainbowme - Send rainbow text in an emote. /reply [text] - Reply to the selected message. /react - React to the selected message. -/redact [reason] - Redact the selected message. +/redact [reason] - Redact the selected message. + +# Encryption +/fingerprint - View the fingerprint of your device. + +/devices - View the device list of a user. +/device - Show info about a specific device. +/unverify - Un-verify a device. +/blacklist - Blacklist a device. +/verify [fingerprint] + - Verify a device. If the fingerprint is not provided, + interactive emoji verification will be started. # Rooms /pm <...> - Create a private chat with the given user(s). -- cgit v1.2.3 From 341f8829d67b197ece20fe1cb0da929486665853 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 24 Jul 2020 23:44:04 +0300 Subject: Add very crude interactive verification support --- ui/commands.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'ui/commands.go') diff --git a/ui/commands.go b/ui/commands.go index b406278..a10445f 100644 --- a/ui/commands.go +++ b/ui/commands.go @@ -472,7 +472,7 @@ func cmdDevices(cmd *Command) { } var buf strings.Builder for _, device := range devices { - _, _ = fmt.Fprintf(&buf, "%s (%s) - %s - %s\n", device.DeviceID, device.Name, device.Trust.String(), device.Fingerprint()) + _, _ = fmt.Fprintf(&buf, "%s (%s) - %s\n Fingerprint: %s\n", device.DeviceID, device.Name, device.Trust.String(), device.Fingerprint()) } resp := buf.String() cmd.Reply(resp[:len(resp)-1]) @@ -499,7 +499,17 @@ func cmdVerify(cmd *Command) { return } if len(cmd.Args) == 2 { - cmd.Reply("Interactive verification UI is not yet implemented") + mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) + timeout := 60 * time.Second + err := mach.NewSASVerificationWith(device, "", timeout, true) + if err != nil { + cmd.Reply("Failed to start interactive verification: %v", err) + return + } + modal := NewVerificationModal(cmd.MainView, device, timeout) + mach.VerifySASEmojisMatch = modal.VerifyEmojisMatch + mach.VerifySASNumbersMatch = modal.VerifyNumbersMatch + cmd.MainView.ShowModal(modal) } else { fingerprint := strings.Join(cmd.Args[2:], "") if string(device.SigningKey) != fingerprint { -- cgit v1.2.3 From ead7e0bf1d9c584224c1738b32ad26e314957220 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 25 Jul 2020 18:40:31 +0300 Subject: Make verification modal wait for confirmation --- ui/commands.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'ui/commands.go') diff --git a/ui/commands.go b/ui/commands.go index a10445f..a6309fd 100644 --- a/ui/commands.go +++ b/ui/commands.go @@ -498,18 +498,20 @@ func cmdVerify(cmd *Command) { if device == nil { return } + if device.Trust == crypto.TrustStateVerified { + cmd.Reply("That device is already verified") + return + } if len(cmd.Args) == 2 { mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) - timeout := 60 * time.Second - err := mach.NewSASVerificationWith(device, "", timeout, true) + mach.DefaultSASTimeout = 120 * time.Second + modal := NewVerificationModal(cmd.MainView, device, mach.DefaultSASTimeout) + cmd.MainView.ShowModal(modal) + err := mach.NewSimpleSASVerificationWith(device, modal) if err != nil { cmd.Reply("Failed to start interactive verification: %v", err) return } - modal := NewVerificationModal(cmd.MainView, device, timeout) - mach.VerifySASEmojisMatch = modal.VerifyEmojisMatch - mach.VerifySASNumbersMatch = modal.VerifyNumbersMatch - cmd.MainView.ShowModal(modal) } else { fingerprint := strings.Join(cmd.Args[2:], "") if string(device.SigningKey) != fingerprint { @@ -547,6 +549,10 @@ func cmdBlacklist(cmd *Command) { if device == nil { return } + if device.Trust == crypto.TrustStateBlacklisted { + cmd.Reply("That device is already blacklisted") + return + } action := "blacklisted" if device.Trust == crypto.TrustStateVerified { action = "unverified and blacklisted" -- cgit v1.2.3 From ee3594db46fe261962f0a8a11c48cb9d6f84938f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 25 Jul 2020 20:54:32 +0300 Subject: Add toggle to only send to verified devices --- ui/commands.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'ui/commands.go') diff --git a/ui/commands.go b/ui/commands.go index a6309fd..1877c89 100644 --- a/ui/commands.go +++ b/ui/commands.go @@ -475,7 +475,7 @@ func cmdDevices(cmd *Command) { _, _ = fmt.Fprintf(&buf, "%s (%s) - %s\n Fingerprint: %s\n", device.DeviceID, device.Name, device.Trust.String(), device.Fingerprint()) } resp := buf.String() - cmd.Reply(resp[:len(resp)-1]) + cmd.Reply("%s", resp[:len(resp)-1]) } func cmdDevice(cmd *Command) { @@ -561,6 +561,15 @@ func cmdBlacklist(cmd *Command) { putDevice(cmd, device, action) } +func cmdResetSession(cmd *Command) { + err := cmd.Matrix.Crypto().(*crypto.OlmMachine).CryptoStore.RemoveOutboundGroupSession(cmd.Room.Room.ID) + if err != nil { + cmd.Reply("Failed to remove outbound group session: %v", err) + } else { + cmd.Reply("Removed outbound group session for this room") + } +} + // endregion func cmdHeapProfile(cmd *Command) { @@ -638,7 +647,7 @@ func cmdHelp(cmd *Command) { /logout - Log out of Matrix. /toggle - Temporary command to toggle various UI features. -Things: rooms, users, baremessages, images, typingnotif +Things: rooms, users, baremessages, images, typingnotif, unverified # Sending special messages /me - Send an emote message. @@ -919,6 +928,7 @@ var toggleMsg = map[string]ToggleMessage{ "markdown": SimpleToggleMessage("markdown input"), "downloads": SimpleToggleMessage("automatic downloads"), "notifications": SimpleToggleMessage("desktop notifications"), + "unverified": SimpleToggleMessage("sending messages to unverified devices"), } func makeUsage() string { @@ -959,6 +969,8 @@ func cmdToggle(cmd *Command) { val = &cmd.Config.Preferences.DisableDownloads case "notifications": val = &cmd.Config.Preferences.DisableNotifications + case "unverified": + val = &cmd.Config.SendToVerifiedOnly default: cmd.Reply("Unknown toggle %s. Use /toggle without arguments for a list of togglable things.", thing) return -- cgit v1.2.3 From 2f5f0674b600f129204958c810843d998f6a2f6a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 30 Jul 2020 14:32:59 +0300 Subject: Update mautrix-go and make it build without crypto --- ui/commands.go | 207 --------------------------------------------------------- 1 file changed, 207 deletions(-) (limited to 'ui/commands.go') diff --git a/ui/commands.go b/ui/commands.go index 1877c89..9d38396 100644 --- a/ui/commands.go +++ b/ui/commands.go @@ -37,7 +37,6 @@ import ( "github.com/russross/blackfriday/v2" "maunium.net/go/mautrix" - "maunium.net/go/mautrix/crypto" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" @@ -366,212 +365,6 @@ func cmdFingerprint(cmd *Command) { } } -// region TODO these four functions currently use the crypto internals directly. switch to interfaces before releasing - -func autocompleteDeviceUserID(cmd *CommandAutocomplete) (completions []string, newText string) { - userCompletions := cmd.Room.AutocompleteUser(cmd.Args[0]) - if len(userCompletions) == 1 { - newText = fmt.Sprintf("/%s %s ", cmd.OrigCommand, userCompletions[0].id) - } else { - completions = make([]string, len(userCompletions)) - for i, completion := range userCompletions { - completions[i] = completion.id - } - } - return -} - -func autocompleteDeviceDeviceID(cmd *CommandAutocomplete) (completions []string, newText string) { - mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) - devices, err := mach.CryptoStore.GetDevices(id.UserID(cmd.Args[0])) - if len(devices) == 0 || err != nil { - return - } - var completedDeviceID id.DeviceID - if len(cmd.Args) > 1 { - existingID := strings.ToUpper(cmd.Args[1]) - for _, device := range devices { - deviceIDStr := string(device.DeviceID) - if deviceIDStr == existingID { - // We don't want to do any autocompletion if there's already a full device ID there. - return []string{}, "" - } else if strings.HasPrefix(strings.ToUpper(device.Name), existingID) || strings.HasPrefix(deviceIDStr, existingID) { - completedDeviceID = device.DeviceID - completions = append(completions, fmt.Sprintf("%s (%s)", device.DeviceID, device.Name)) - } - } - } else { - completions = make([]string, len(devices)) - i := 0 - for _, device := range devices { - completedDeviceID = device.DeviceID - completions[i] = fmt.Sprintf("%s (%s)", device.DeviceID, device.Name) - i++ - } - } - if len(completions) == 1 { - newText = fmt.Sprintf("/%s %s %s ", cmd.OrigCommand, cmd.Args[0], completedDeviceID) - } - return -} - -func autocompleteDevice(cmd *CommandAutocomplete) ([]string, string) { - if len(cmd.Args) == 0 { - return []string{}, "" - } else if len(cmd.Args) == 1 && !unicode.IsSpace(rune(cmd.RawArgs[len(cmd.RawArgs)-1])) { - return autocompleteDeviceUserID(cmd) - } else if cmd.Command != "devices" { - return autocompleteDeviceDeviceID(cmd) - } - return []string{}, "" -} - -func getDevice(cmd *Command) *crypto.DeviceIdentity { - if len(cmd.Args) < 2 { - cmd.Reply("Usage: /%s [fingerprint]", cmd.Command) - return nil - } - mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) - device, err := mach.GetOrFetchDevice(id.UserID(cmd.Args[0]), id.DeviceID(cmd.Args[1])) - if err != nil { - cmd.Reply("Failed to get device: %v", err) - return nil - } - return device -} - -func putDevice(cmd *Command, device *crypto.DeviceIdentity, action string) { - mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) - err := mach.CryptoStore.PutDevice(device.UserID, device) - if err != nil { - cmd.Reply("Failed to save device: %v", err) - } else { - cmd.Reply("Successfully %s %s/%s (%s)", action, device.UserID, device.DeviceID, device.Name) - } - mach.OnDevicesChanged(device.UserID) -} - -func cmdDevices(cmd *Command) { - if len(cmd.Args) == 0 { - cmd.Reply("Usage: /devices ") - return - } - userID := id.UserID(cmd.Args[0]) - mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) - devices, err := mach.CryptoStore.GetDevices(userID) - if err != nil { - cmd.Reply("Failed to get device list: %v", err) - } - if len(devices) == 0 { - cmd.Reply("Fetching device list from server...") - devices = mach.LoadDevices(userID) - } - if len(devices) == 0 { - cmd.Reply("No devices found for %s", userID) - return - } - var buf strings.Builder - for _, device := range devices { - _, _ = fmt.Fprintf(&buf, "%s (%s) - %s\n Fingerprint: %s\n", device.DeviceID, device.Name, device.Trust.String(), device.Fingerprint()) - } - resp := buf.String() - cmd.Reply("%s", resp[:len(resp)-1]) -} - -func cmdDevice(cmd *Command) { - device := getDevice(cmd) - if device == nil { - return - } - deviceType := "Device" - if device.Deleted { - deviceType = "Deleted device" - } - cmd.Reply("%s %s of %s\nFingerprint: %s\nIdentity key: %s\nDevice name: %s\nTrust state: %s", - deviceType, device.DeviceID, device.UserID, - device.Fingerprint(), device.IdentityKey, - device.Name, device.Trust.String()) -} - -func cmdVerify(cmd *Command) { - device := getDevice(cmd) - if device == nil { - return - } - if device.Trust == crypto.TrustStateVerified { - cmd.Reply("That device is already verified") - return - } - if len(cmd.Args) == 2 { - mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) - mach.DefaultSASTimeout = 120 * time.Second - modal := NewVerificationModal(cmd.MainView, device, mach.DefaultSASTimeout) - cmd.MainView.ShowModal(modal) - err := mach.NewSimpleSASVerificationWith(device, modal) - if err != nil { - cmd.Reply("Failed to start interactive verification: %v", err) - return - } - } else { - fingerprint := strings.Join(cmd.Args[2:], "") - if string(device.SigningKey) != fingerprint { - cmd.Reply("Mismatching fingerprint") - return - } - action := "verified" - if device.Trust == crypto.TrustStateBlacklisted { - action = "unblacklisted and verified" - } - device.Trust = crypto.TrustStateVerified - putDevice(cmd, device, action) - } -} - -func cmdUnverify(cmd *Command) { - device := getDevice(cmd) - if device == nil { - return - } - if device.Trust == crypto.TrustStateUnset { - cmd.Reply("That device is already not verified") - return - } - action := "unverified" - if device.Trust == crypto.TrustStateBlacklisted { - action = "unblacklisted" - } - device.Trust = crypto.TrustStateUnset - putDevice(cmd, device, action) -} - -func cmdBlacklist(cmd *Command) { - device := getDevice(cmd) - if device == nil { - return - } - if device.Trust == crypto.TrustStateBlacklisted { - cmd.Reply("That device is already blacklisted") - return - } - action := "blacklisted" - if device.Trust == crypto.TrustStateVerified { - action = "unverified and blacklisted" - } - device.Trust = crypto.TrustStateBlacklisted - putDevice(cmd, device, action) -} - -func cmdResetSession(cmd *Command) { - err := cmd.Matrix.Crypto().(*crypto.OlmMachine).CryptoStore.RemoveOutboundGroupSession(cmd.Room.Room.ID) - if err != nil { - cmd.Reply("Failed to remove outbound group session: %v", err) - } else { - cmd.Reply("Removed outbound group session for this room") - } -} - -// endregion - func cmdHeapProfile(cmd *Command) { if len(cmd.Args) == 0 || cmd.Args[0] != "nogc" { runtime.GC() -- cgit v1.2.3