Add very crude interactive verification support

This commit is contained in:
Tulir Asokan 2020-07-24 23:44:04 +03:00
parent 77a1514c90
commit 341f8829d6
2 changed files with 204 additions and 2 deletions

View File

@ -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 {

192
ui/verification-modal.go Normal file
View File

@ -0,0 +1,192 @@
// gomuks - A terminal Matrix client written in Go.
// Copyright (C) 2020 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 ui
import (
"fmt"
"strconv"
"strings"
"time"
"maunium.net/go/mauview"
"maunium.net/go/tcell"
"maunium.net/go/gomuks/debug"
"maunium.net/go/mautrix/crypto"
)
type EmojiView struct {
mauview.SimpleEventHandler
Numbers *[3]uint
Emojis *[7]crypto.VerificationEmoji
}
func (e *EmojiView) Draw(screen mauview.Screen) {
if e.Emojis != nil {
width := 10
for i, emoji := range e.Emojis {
x := i*width + i
y := 0
if i >= 4 {
x = (i-4)*width + i
y = 2
}
mauview.Print(screen, string(emoji.Emoji), x, y, width, mauview.AlignCenter, tcell.ColorDefault)
mauview.Print(screen, emoji.Description, x, y+1, width, mauview.AlignCenter, tcell.ColorDefault)
}
} else if e.Numbers != nil {
maxWidth := 43
for i, number := range e.Numbers {
mauview.Print(screen, strconv.FormatUint(uint64(number), 10), 0, i, maxWidth, mauview.AlignCenter, tcell.ColorDefault)
}
}
}
type VerificationModal struct {
mauview.Component
container *mauview.Box
waitingBar *mauview.ProgressBar
infoText *mauview.TextView
emojiText *EmojiView
inputBar *mauview.InputField
stopWaiting chan struct{}
confirmChan chan bool
parent *MainView
}
func NewVerificationModal(mainView *MainView, device *crypto.DeviceIdentity, timeout time.Duration) *VerificationModal {
vm := &VerificationModal{
parent: mainView,
stopWaiting: make(chan struct{}),
confirmChan: make(chan bool),
}
progress := int(timeout.Seconds())
vm.waitingBar = mauview.NewProgressBar().
SetMax(progress).
SetProgress(progress).
SetIndeterminate(false)
vm.infoText = mauview.NewTextView()
vm.infoText.SetText(fmt.Sprintf("Waiting for %s to accept", device.UserID))
vm.emojiText = &EmojiView{}
vm.inputBar = mauview.NewInputField().SetBackgroundColor(tcell.ColorDefault)
flex := mauview.NewFlex().
SetDirection(mauview.FlexRow).
AddFixedComponent(vm.waitingBar, 1).
AddFixedComponent(vm.infoText, 4).
AddFixedComponent(vm.emojiText, 4).
AddFixedComponent(vm.inputBar, 1)
vm.container = mauview.NewBox(flex).
SetBorder(true).
SetTitle("Interactive verification")
vm.Component = mauview.Center(vm.container, 45, 12).SetAlwaysFocusChild(true)
go vm.decrementWaitingBar(progress)
return vm
}
func (vm *VerificationModal) decrementWaitingBar(progress int) {
for {
select {
case <-time.Tick(time.Second):
if progress <= 0 {
vm.parent.HideModal()
vm.parent.parent.Render()
return
}
progress--
vm.waitingBar.SetProgress(progress)
vm.parent.parent.Render()
case <-vm.stopWaiting:
vm.waitingBar.SetIndeterminate(true)
break
}
}
}
func (vm *VerificationModal) VerifyEmojisMatch(emojis [7]crypto.VerificationEmoji, _ *crypto.DeviceIdentity) bool {
vm.infoText.SetText("Check if the other device is showing the same emojis as below, then type \"yes\" to accept, or \"no\" to reject")
vm.inputBar.
SetTextColor(tcell.ColorWhite).
SetBackgroundColor(tcell.ColorDarkCyan).
SetPlaceholder("Type \"yes\" or \"no\"").
Focus()
vm.emojiText.Emojis = &emojis
vm.parent.parent.Render()
vm.stopWaiting <- struct{}{}
confirm := <-vm.confirmChan
// TODO this should hook into cancel/success of the verification and display a success message instead of just closing
vm.parent.HideModal()
vm.parent.parent.Render()
return confirm
}
func (vm *VerificationModal) VerifyNumbersMatch(numbers [3]uint, _ *crypto.DeviceIdentity) bool {
vm.infoText.SetText("Check if the other device is showing the same numbers as below, then type \"yes\" to accept, or \"no\" to reject")
vm.inputBar.
SetTextColor(tcell.ColorWhite).
SetBackgroundColor(tcell.ColorDarkCyan).
SetPlaceholder("Type \"yes\" or \"no\"").
Focus()
vm.emojiText.Numbers = &numbers
vm.parent.parent.Render()
vm.stopWaiting <- struct{}{}
confirm := <-vm.confirmChan
// TODO this should hook into cancel/success of the verification and display a success message instead of just closing
vm.parent.HideModal()
vm.parent.parent.Render()
return confirm
}
func (vm *VerificationModal) OnKeyEvent(event mauview.KeyEvent) bool {
if vm.emojiText.Emojis == nil && vm.emojiText.Numbers == nil {
debug.Print("Ignoring pre-emoji key event")
return false
}
if event.Key() == tcell.KeyEnter {
text := strings.ToLower(strings.TrimSpace(vm.inputBar.GetText()))
if text == "yes" {
debug.Print("Confirming verification")
vm.confirmChan <- true
} else if text == "no" {
debug.Print("Rejecting verification")
vm.confirmChan <- false
}
return true
} else {
return vm.inputBar.OnKeyEvent(event)
}
}
func (vm *VerificationModal) Focus() {
vm.container.Focus()
}
func (vm *VerificationModal) Blur() {
vm.container.Blur()
}