2020-09-10 02:58:00 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2020-09-10 03:52:30 -07:00
|
|
|
"encoding/base64"
|
2020-09-17 09:31:14 -07:00
|
|
|
"flag"
|
2020-09-10 02:58:00 -07:00
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
2020-09-29 03:10:16 -07:00
|
|
|
"os/signal"
|
2020-09-10 07:31:53 -07:00
|
|
|
"runtime"
|
2020-09-10 02:58:00 -07:00
|
|
|
"strings"
|
2020-09-17 09:31:14 -07:00
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
passwordTerminal "golang.org/x/crypto/ssh/terminal"
|
2020-09-11 04:48:15 -07:00
|
|
|
|
|
|
|
"github.com/depools/dc4bc/airgapped"
|
2020-09-10 02:58:00 -07:00
|
|
|
)
|
|
|
|
|
2020-09-10 07:31:53 -07:00
|
|
|
func init() {
|
|
|
|
runtime.LockOSThread()
|
|
|
|
}
|
|
|
|
|
2020-09-22 06:15:18 -07:00
|
|
|
// terminalCommand holds a description of a command and its handler
|
2020-09-10 02:58:00 -07:00
|
|
|
type terminalCommand struct {
|
|
|
|
commandHandler func() error
|
|
|
|
description string
|
|
|
|
}
|
|
|
|
|
2020-09-22 06:15:18 -07:00
|
|
|
// terminal a basic implementation of a prompt
|
2020-09-10 02:58:00 -07:00
|
|
|
type terminal struct {
|
2020-09-10 03:52:30 -07:00
|
|
|
reader *bufio.Reader
|
2020-09-10 02:58:00 -07:00
|
|
|
airgapped *airgapped.AirgappedMachine
|
|
|
|
commands map[string]*terminalCommand
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewTerminal(machine *airgapped.AirgappedMachine) *terminal {
|
2020-09-10 03:52:30 -07:00
|
|
|
t := terminal{bufio.NewReader(os.Stdin), machine, make(map[string]*terminalCommand)}
|
2020-09-10 02:58:00 -07:00
|
|
|
t.addCommand("read_qr", &terminalCommand{
|
|
|
|
commandHandler: t.readQRCommand,
|
|
|
|
description: "Reads QR chunks from camera, handle a decoded operation and returns paths to qr chunks of operation's result",
|
|
|
|
})
|
|
|
|
t.addCommand("help", &terminalCommand{
|
|
|
|
commandHandler: t.helpCommand,
|
|
|
|
description: "shows available commands",
|
|
|
|
})
|
2020-09-10 03:52:30 -07:00
|
|
|
t.addCommand("show_dkg_pub_key", &terminalCommand{
|
|
|
|
commandHandler: t.showDKGPubKeyCommand,
|
|
|
|
description: "shows a dkg pub key",
|
|
|
|
})
|
2020-09-11 06:05:28 -07:00
|
|
|
t.addCommand("show_finished_dkg", &terminalCommand{
|
|
|
|
commandHandler: t.showFinishedDKGCommand,
|
|
|
|
description: "shows a list of finished dkg rounds",
|
|
|
|
})
|
2020-09-29 03:10:16 -07:00
|
|
|
t.addCommand("replay_operations_log", &terminalCommand{
|
|
|
|
commandHandler: t.replayOperationLogCommand,
|
|
|
|
description: "replays the operation log for a given dkg round",
|
|
|
|
})
|
|
|
|
t.addCommand("drop_operations_log", &terminalCommand{
|
|
|
|
commandHandler: t.dropOperationLogCommand,
|
|
|
|
description: "drops the operation log for a given dkg round",
|
|
|
|
})
|
|
|
|
t.addCommand("exit", &terminalCommand{
|
|
|
|
commandHandler: func() error {
|
|
|
|
log.Fatal("interrupted")
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
description: "stops the machine",
|
|
|
|
})
|
2020-09-10 02:58:00 -07:00
|
|
|
return &t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *terminal) addCommand(name string, command *terminalCommand) {
|
|
|
|
t.commands[name] = command
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *terminal) readQRCommand() error {
|
2020-09-29 08:16:20 -07:00
|
|
|
qrPath, err := t.airgapped.HandleQR()
|
2020-09-10 02:58:00 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-09-28 01:01:40 -07:00
|
|
|
fmt.Println("An operation in the read QR code handled successfully, a result operation saved by chunks in following qr codes:")
|
2020-09-29 08:16:20 -07:00
|
|
|
fmt.Printf("Operation's chunk: %s\n", qrPath)
|
2020-09-10 02:58:00 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-10 03:52:30 -07:00
|
|
|
func (t *terminal) showDKGPubKeyCommand() error {
|
|
|
|
pubkey := t.airgapped.GetPubKey()
|
|
|
|
pubkeyBz, err := pubkey.MarshalBinary()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to marshal DKG pub key: %w", err)
|
|
|
|
}
|
|
|
|
pubKeyBase64 := base64.StdEncoding.EncodeToString(pubkeyBz)
|
|
|
|
fmt.Println(pubKeyBase64)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-10 02:58:00 -07:00
|
|
|
func (t *terminal) helpCommand() error {
|
|
|
|
fmt.Println("Available commands:")
|
|
|
|
for commandName, command := range t.commands {
|
|
|
|
fmt.Printf("* %s - %s\n", commandName, command.description)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-11 06:05:28 -07:00
|
|
|
func (t *terminal) showFinishedDKGCommand() error {
|
|
|
|
keyrings, err := t.airgapped.GetBLSKeyrings()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to get a list of finished dkgs: %w", err)
|
|
|
|
}
|
|
|
|
for dkgID, keyring := range keyrings {
|
|
|
|
fmt.Printf("DKG identifier: %s\n", dkgID)
|
|
|
|
fmt.Printf("PubKey: %s\n", keyring.PubPoly.Commit().String())
|
|
|
|
fmt.Println("-----------------------------------------------------")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-29 03:10:16 -07:00
|
|
|
func (t *terminal) replayOperationLogCommand() error {
|
|
|
|
fmt.Print("> Enter the DKGRoundIdentifier: ")
|
|
|
|
dkgRoundIdentifier, err := t.reader.ReadString('\n')
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to read dkgRoundIdentifier: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.airgapped.ReplayOperationsLog(dkgRoundIdentifier); err != nil {
|
|
|
|
return fmt.Errorf("failed to ReplayOperationsLog: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *terminal) dropOperationLogCommand() error {
|
|
|
|
fmt.Print("> Enter the DKGRoundIdentifier: ")
|
|
|
|
dkgRoundIdentifier, err := t.reader.ReadString('\n')
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to read dkgRoundIdentifier: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.airgapped.DropOperationsLog(dkgRoundIdentifier); err != nil {
|
|
|
|
return fmt.Errorf("failed to DropOperationsLog: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-17 09:31:14 -07:00
|
|
|
func (t *terminal) enterEncryptionPasswordIfNeeded() error {
|
|
|
|
t.airgapped.Lock()
|
|
|
|
defer t.airgapped.Unlock()
|
|
|
|
|
|
|
|
if !t.airgapped.SensitiveDataRemoved() {
|
|
|
|
return nil
|
|
|
|
}
|
2020-09-10 03:52:30 -07:00
|
|
|
|
2020-09-17 09:31:14 -07:00
|
|
|
for {
|
|
|
|
fmt.Print("Enter encryption password: ")
|
|
|
|
password, err := passwordTerminal.ReadPassword(syscall.Stdin)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to read password: %w", err)
|
|
|
|
}
|
2020-09-17 10:45:49 -07:00
|
|
|
fmt.Println()
|
2020-09-17 09:31:14 -07:00
|
|
|
t.airgapped.SetEncryptionKey(password)
|
|
|
|
if err = t.airgapped.InitKeys(); err != nil {
|
|
|
|
fmt.Printf("Failed to init keys: %v\n", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2020-09-10 03:52:30 -07:00
|
|
|
|
2020-09-17 09:31:14 -07:00
|
|
|
func (t *terminal) run() error {
|
|
|
|
if err := t.enterEncryptionPasswordIfNeeded(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-09-10 02:58:00 -07:00
|
|
|
if err := t.helpCommand(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-09-10 03:52:30 -07:00
|
|
|
fmt.Println("Waiting for command...")
|
2020-09-10 02:58:00 -07:00
|
|
|
for {
|
|
|
|
fmt.Print(">>> ")
|
2020-09-17 09:31:14 -07:00
|
|
|
command, err := t.reader.ReadString('\n')
|
2020-09-10 02:58:00 -07:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to read command: %w", err)
|
|
|
|
}
|
|
|
|
handler, ok := t.commands[strings.Trim(command, "\n")]
|
|
|
|
if !ok {
|
|
|
|
fmt.Printf("unknown command: %s\n", command)
|
|
|
|
continue
|
|
|
|
}
|
2020-09-17 09:31:14 -07:00
|
|
|
if err = t.enterEncryptionPasswordIfNeeded(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t.airgapped.Lock()
|
2020-09-10 02:58:00 -07:00
|
|
|
if err := handler.commandHandler(); err != nil {
|
2020-09-29 03:10:16 -07:00
|
|
|
fmt.Printf("failled to execute command %s: %v \n", command, err)
|
2020-09-17 09:31:14 -07:00
|
|
|
t.airgapped.Unlock()
|
2020-09-10 02:58:00 -07:00
|
|
|
continue
|
|
|
|
}
|
2020-09-17 09:31:14 -07:00
|
|
|
t.airgapped.Unlock()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 01:48:36 -07:00
|
|
|
func (t *terminal) dropSensitiveData(passExpiration time.Duration) {
|
2020-09-17 09:31:14 -07:00
|
|
|
ticker := time.NewTicker(passExpiration)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
|
|
|
t.airgapped.DropSensitiveData()
|
|
|
|
}
|
2020-09-10 02:58:00 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-17 09:31:14 -07:00
|
|
|
var (
|
|
|
|
passwordExpiration string
|
|
|
|
dbPath string
|
2020-09-29 08:16:20 -07:00
|
|
|
framesDelay int
|
|
|
|
chunkSize int
|
2020-09-30 02:45:24 -07:00
|
|
|
qrCodesFolder string
|
2020-09-17 09:31:14 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
flag.StringVar(&passwordExpiration, "password_expiration", "10m", "Expiration of the encryption password")
|
|
|
|
flag.StringVar(&dbPath, "db_path", "airgapped_db", "Path to airgapped levelDB storage")
|
2020-09-29 08:16:20 -07:00
|
|
|
flag.IntVar(&framesDelay, "frames_delay", 10, "Delay times between frames in 100ths of a second")
|
2020-09-30 02:45:24 -07:00
|
|
|
flag.IntVar(&chunkSize, "chunk_size", 256, "QR-code's chunk size")
|
|
|
|
flag.StringVar(&qrCodesFolder, "qr_codes_folder", "/tmp/", "Folder to save result QR codes")
|
2020-09-17 09:31:14 -07:00
|
|
|
}
|
|
|
|
|
2020-09-10 02:58:00 -07:00
|
|
|
func main() {
|
2020-09-17 09:31:14 -07:00
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
passwordLifeDuration, err := time.ParseDuration(passwordExpiration)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("invalid password expiration syntax: %v", err)
|
2020-09-10 02:58:00 -07:00
|
|
|
}
|
2020-09-17 09:31:14 -07:00
|
|
|
|
|
|
|
air, err := airgapped.NewAirgappedMachine(dbPath)
|
2020-09-10 02:58:00 -07:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to init airgapped machine %v", err)
|
|
|
|
}
|
2020-09-29 08:16:20 -07:00
|
|
|
air.SetQRProcessorFramesDelay(framesDelay)
|
|
|
|
air.SetQRProcessorChunkSize(chunkSize)
|
2020-09-30 02:45:24 -07:00
|
|
|
air.SetResultQRFolder(qrCodesFolder)
|
2020-09-10 02:58:00 -07:00
|
|
|
|
2020-09-29 03:10:16 -07:00
|
|
|
c := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(c, os.Interrupt)
|
|
|
|
go func() {
|
|
|
|
for _ = range c {
|
|
|
|
fmt.Printf("Intercepting SIGINT, please type `exit` to stop the machine\n>>> ")
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-09-10 02:58:00 -07:00
|
|
|
t := NewTerminal(air)
|
2020-09-21 01:48:36 -07:00
|
|
|
go t.dropSensitiveData(passwordLifeDuration)
|
2020-09-10 02:58:00 -07:00
|
|
|
if err = t.run(); err != nil {
|
|
|
|
log.Fatalf(err.Error())
|
|
|
|
}
|
|
|
|
}
|