Add very crude interactive verification support
This commit is contained in:
		@@ -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
									
								
							
							
						
						
									
										192
									
								
								ui/verification-modal.go
									
									
									
									
									
										Normal 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()
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user