dc4bc/cmd/airgapped/main.go

253 lines
6.4 KiB
Go
Raw Normal View History

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"
"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",
})
t.addCommand("show_finished_dkg", &terminalCommand{
commandHandler: t.showFinishedDKGCommand,
description: "shows a list of finished dkg rounds",
})
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 {
qrPaths, err := t.airgapped.HandleQR()
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-10 02:58:00 -07:00
for idx, qrPath := range qrPaths {
fmt.Printf("Operation's chunk #%d: %s\n", idx, qrPath)
}
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
}
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
}
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 {
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()
}
}
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
)
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-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)
}
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)
go t.dropSensitiveData(passwordLifeDuration)
2020-09-10 02:58:00 -07:00
if err = t.run(); err != nil {
log.Fatalf(err.Error())
}
}