dc4bc/airgapped/airgapped.go

273 lines
8.0 KiB
Go
Raw Normal View History

2020-08-10 09:00:29 -07:00
package airgapped
import (
"encoding/json"
"fmt"
client "github.com/depools/dc4bc/client/types"
2020-08-10 09:00:29 -07:00
"github.com/depools/dc4bc/dkg"
"github.com/depools/dc4bc/fsm/fsm"
"github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
"github.com/depools/dc4bc/fsm/types/requests"
"github.com/depools/dc4bc/qr"
2020-08-17 03:22:46 -07:00
"github.com/syndtr/goleveldb/leveldb"
2020-08-10 09:00:29 -07:00
"go.dedis.ch/kyber/v3"
2020-08-11 06:42:32 -07:00
"go.dedis.ch/kyber/v3/encrypt/ecies"
2020-08-10 09:00:29 -07:00
"go.dedis.ch/kyber/v3/pairing/bn256"
2020-08-14 03:55:01 -07:00
"log"
2020-08-11 06:42:32 -07:00
"sync"
)
const (
2020-08-17 03:22:46 -07:00
resultQRFolder = "result_qr_codes"
pubKeyDBKey = "public_key"
privateKeyDBKey = "private_key"
2020-08-10 09:00:29 -07:00
)
type AirgappedMachine struct {
2020-08-11 06:42:32 -07:00
sync.Mutex
dkgInstances map[string]*dkg.DKG
2020-08-11 02:46:13 -07:00
qrProcessor qr.Processor
2020-08-13 08:27:28 -07:00
pubKey kyber.Point
secKey kyber.Scalar
suite *bn256.Suite
2020-08-18 11:52:04 -07:00
db *leveldb.DB
2020-08-10 09:00:29 -07:00
}
2020-08-17 03:22:46 -07:00
func NewAirgappedMachine(dbPath string) (*AirgappedMachine, error) {
2020-08-18 11:52:04 -07:00
var (
err error
)
2020-08-13 08:27:28 -07:00
am := &AirgappedMachine{
2020-08-11 02:46:13 -07:00
dkgInstances: make(map[string]*dkg.DKG),
qrProcessor: qr.NewCameraProcessor(),
2020-08-10 09:00:29 -07:00
}
2020-08-13 08:27:28 -07:00
am.suite = bn256.NewSuiteG2()
2020-08-18 11:52:04 -07:00
if am.db, err = leveldb.OpenFile(dbPath, nil); err != nil {
return nil, fmt.Errorf("failed to open db file %s for keys: %w", dbPath, err)
}
err = am.loadKeysFromDB(dbPath)
2020-08-17 03:22:46 -07:00
if err != nil && err != leveldb.ErrNotFound {
return nil, fmt.Errorf("failed to load keys from db %s: %w", dbPath, err)
}
// if keys were not generated yet
if err == leveldb.ErrNotFound {
am.secKey = am.suite.Scalar().Pick(am.suite.RandomStream())
am.pubKey = am.suite.Point().Mul(am.secKey, nil)
return am, am.saveKeysToDB(dbPath)
}
return am, nil
}
func (am *AirgappedMachine) loadKeysFromDB(dbPath string) error {
2020-08-18 11:52:04 -07:00
pubKeyBz, err := am.db.Get([]byte(pubKeyDBKey), nil)
2020-08-17 03:22:46 -07:00
if err != nil {
if err == leveldb.ErrNotFound {
return err
}
return fmt.Errorf("failed to get public key from db %s: %w", dbPath, err)
}
2020-08-18 11:52:04 -07:00
privateKeyBz, err := am.db.Get([]byte(privateKeyDBKey), nil)
2020-08-17 03:22:46 -07:00
if err != nil {
if err == leveldb.ErrNotFound {
return err
}
return fmt.Errorf("failed to get private key from db %s: %w", dbPath, err)
}
am.pubKey = am.suite.Point()
if err = am.pubKey.UnmarshalBinary(pubKeyBz); err != nil {
return fmt.Errorf("failed to unmarshal public key: %w", err)
}
am.secKey = am.suite.Scalar()
if err = am.secKey.UnmarshalBinary(privateKeyBz); err != nil {
return fmt.Errorf("failed to unmarshal private key: %w", err)
}
return nil
}
func (am *AirgappedMachine) saveKeysToDB(dbPath string) error {
pubKeyBz, err := am.pubKey.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal pub key: %w", err)
}
privateKeyBz, err := am.secKey.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal private key: %w", err)
}
2020-08-18 11:52:04 -07:00
tx, err := am.db.OpenTransaction()
2020-08-17 03:22:46 -07:00
if err != nil {
return fmt.Errorf("failed to open transcation for db %s: %w", dbPath, err)
}
defer tx.Discard()
if err = tx.Put([]byte(pubKeyDBKey), pubKeyBz, nil); err != nil {
return fmt.Errorf("failed to put pub key into db %s: %w", dbPath, err)
}
if err = tx.Put([]byte(privateKeyDBKey), privateKeyBz, nil); err != nil {
return fmt.Errorf("failed to put private key into db %s: %w", dbPath, err)
}
if err = tx.Commit(); err != nil {
return fmt.Errorf("failed to commit tx for saving keys into db %s: %w", dbPath, err)
}
return nil
2020-08-10 09:00:29 -07:00
}
2020-08-12 08:16:18 -07:00
func (am *AirgappedMachine) getParticipantID(dkgIdentifier string) (int, error) {
dkgInstance, ok := am.dkgInstances[dkgIdentifier]
if !ok {
return 0, fmt.Errorf("invalid dkg identifier: %s", dkgIdentifier)
}
return dkgInstance.ParticipantID, nil
}
2020-08-11 06:42:32 -07:00
func (am *AirgappedMachine) encryptData(dkgIdentifier, to string, data []byte) ([]byte, error) {
dkgInstance, ok := am.dkgInstances[dkgIdentifier]
if !ok {
return nil, fmt.Errorf("invalid dkg identifier: %s", dkgIdentifier)
}
2020-08-13 08:27:28 -07:00
pk, err := dkgInstance.GetPubKeyByParticipant(to)
2020-08-11 06:42:32 -07:00
if err != nil {
return nil, fmt.Errorf("failed to get pk for participant %s: %w", to, err)
}
2020-08-14 03:55:01 -07:00
encryptedData, err := ecies.Encrypt(am.suite, pk, data, am.suite.Hash)
2020-08-11 06:42:32 -07:00
if err != nil {
return nil, fmt.Errorf("failed to encrypt data: %w", err)
}
return encryptedData, nil
}
2020-08-13 08:27:28 -07:00
func (am *AirgappedMachine) decryptData(data []byte) ([]byte, error) {
2020-08-14 03:55:01 -07:00
decryptedData, err := ecies.Decrypt(am.suite, am.secKey, data, am.suite.Hash)
2020-08-11 06:42:32 -07:00
if err != nil {
return nil, fmt.Errorf("failed to decrypt data: %w", err)
}
return decryptedData, nil
}
2020-08-13 08:27:28 -07:00
func (am *AirgappedMachine) HandleOperation(operation client.Operation) ([]client.Operation, error) {
2020-08-10 09:00:29 -07:00
var (
2020-08-12 08:16:18 -07:00
err error
// output operations (cause of deals)
2020-08-11 06:42:32 -07:00
operations []client.Operation
2020-08-10 09:00:29 -07:00
)
2020-08-11 06:42:32 -07:00
am.Lock()
defer am.Unlock()
2020-08-12 08:16:18 -07:00
// handler gets a pointer to an operation, do necessary things
// and write a result (or an error) to .Result field of operation
2020-08-10 09:00:29 -07:00
switch fsm.State(operation.Type) {
case dkg_proposal_fsm.StateDkgCommitsAwaitConfirmations:
2020-08-12 08:16:18 -07:00
err = am.handleStateDkgCommitsAwaitConfirmations(&operation)
2020-08-10 09:00:29 -07:00
case dkg_proposal_fsm.StateDkgDealsAwaitConfirmations:
2020-08-14 03:55:01 -07:00
operations, err = am.handleStateDkgDealsAwaitConfirmations(operation)
2020-08-10 09:00:29 -07:00
case dkg_proposal_fsm.StateDkgResponsesAwaitConfirmations:
2020-08-12 08:16:18 -07:00
err = am.handleStateDkgResponsesAwaitConfirmations(&operation)
case dkg_proposal_fsm.StateDkgMasterKeyAwaitConfirmations:
err = am.handleStateDkgMasterKeyAwaitConfirmations(&operation)
2020-08-10 09:00:29 -07:00
default:
2020-08-12 08:16:18 -07:00
err = fmt.Errorf("invalid operation type: %s", operation.Type)
}
// if we have error after handling the operation, we write the error to the operation, so we can feed it to a FSM
if err != nil {
2020-08-14 03:55:01 -07:00
log.Println(fmt.Sprintf("failed to handle operation %s, returning response with errot to client: %v",
operation.Type, err))
2020-08-12 08:16:18 -07:00
if e := am.writeErrorRequestToOperation(&operation, err); e != nil {
return nil, fmt.Errorf("failed to write error request to an operation: %w", e)
}
2020-08-10 09:00:29 -07:00
}
2020-08-11 06:42:32 -07:00
if len(operation.Result) > 0 {
operations = append(operations, operation)
2020-08-10 09:00:29 -07:00
}
2020-08-13 08:27:28 -07:00
return operations, nil
}
// HandleQR - gets an operation from a QR code, do necessary things for the operation and returns paths to QR-code images
func (am *AirgappedMachine) HandleQR() ([]string, error) {
var (
err error
// input operation
operation client.Operation
qrData []byte
// output operations (cause of deals)
operations []client.Operation
)
if qrData, err = am.qrProcessor.ReadQR(); err != nil {
return nil, fmt.Errorf("failed to read QR: %w", err)
}
if err = json.Unmarshal(qrData, &operation); err != nil {
return nil, fmt.Errorf("failed to unmarshal operation: %w", err)
}
if operations, err = am.HandleOperation(operation); err != nil {
return nil, err
}
2020-08-11 06:42:32 -07:00
qrPath := "%s/%s_%s_%s.png"
qrPaths := make([]string, 0, len(operations))
for _, o := range operations {
operationBz, err := json.Marshal(o)
if err != nil {
return nil, fmt.Errorf("failed to marshal operation: %w", err)
}
2020-08-12 11:01:25 -07:00
if err = am.qrProcessor.WriteQR(fmt.Sprintf(qrPath, resultQRFolder, o.Type, o.ID, o.To), operationBz); err != nil {
2020-08-11 06:42:32 -07:00
return nil, fmt.Errorf("failed to write QR")
}
qrPaths = append(qrPaths, qrPath)
2020-08-10 09:00:29 -07:00
}
2020-08-11 06:42:32 -07:00
return qrPaths, nil
2020-08-10 09:00:29 -07:00
}
2020-08-12 08:16:18 -07:00
func (am *AirgappedMachine) writeErrorRequestToOperation(o *client.Operation, handlerError error) error {
// each type of request should have a required event even error
// maybe should be global?
eventToErrorMap := map[fsm.State]fsm.Event{
dkg_proposal_fsm.StateDkgCommitsAwaitConfirmations: dkg_proposal_fsm.EventDKGCommitConfirmationError,
dkg_proposal_fsm.StateDkgDealsAwaitConfirmations: dkg_proposal_fsm.EventDKGDealConfirmationError,
dkg_proposal_fsm.StateDkgResponsesAwaitConfirmations: dkg_proposal_fsm.EventDKGResponseConfirmationError,
dkg_proposal_fsm.StateDkgMasterKeyAwaitConfirmations: dkg_proposal_fsm.EventDKGMasterKeyConfirmationError,
2020-08-12 08:16:18 -07:00
}
pid, err := am.getParticipantID(o.DKGIdentifier)
if err != nil {
return fmt.Errorf("failed to get participant id: %w", err)
}
req := requests.DKGProposalConfirmationErrorRequest{
Error: handlerError,
ParticipantId: pid,
CreatedAt: o.CreatedAt,
2020-08-12 08:16:18 -07:00
}
errorEvent := eventToErrorMap[fsm.State(o.Type)]
2020-08-14 03:55:01 -07:00
reqBz, err := json.Marshal(req)
2020-08-12 08:16:18 -07:00
if err != nil {
return fmt.Errorf("failed to generate fsm request: %w", err)
}
o.Result = reqBz
2020-08-14 03:55:01 -07:00
o.Event = errorEvent
2020-08-12 08:16:18 -07:00
return nil
}