2021-10-15 10:34:19 +02:00
|
|
|
package statuskeycardgo
|
2021-10-14 13:14:43 +03:00
|
|
|
|
2021-10-15 10:35:01 +03:00
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
"github.com/status-im/status-keycard-go/signal"
|
|
|
|
)
|
2021-10-14 13:14:43 +03:00
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
type cardStatus struct {
|
|
|
|
instanceUID string
|
|
|
|
keyUID string
|
|
|
|
freeSlots int
|
|
|
|
pinRetries int
|
|
|
|
pukRetries int
|
|
|
|
}
|
|
|
|
|
2021-10-18 14:50:56 +03:00
|
|
|
type KeycardFlow struct {
|
|
|
|
flowType FlowType
|
|
|
|
state runState
|
|
|
|
wakeUp chan (struct{})
|
2021-10-19 12:19:02 +03:00
|
|
|
pairings *pairingStore
|
|
|
|
params FlowParams
|
2021-10-20 11:44:57 +03:00
|
|
|
cardInfo cardStatus
|
2021-10-18 14:50:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewFlow(storageDir string) (*KeycardFlow, error) {
|
2021-10-19 12:19:02 +03:00
|
|
|
p, err := newPairingStore(storageDir)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-10-18 14:50:56 +03:00
|
|
|
flow := &KeycardFlow{
|
2021-10-19 12:19:02 +03:00
|
|
|
wakeUp: make(chan (struct{})),
|
|
|
|
pairings: p,
|
2021-10-14 13:14:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return flow, nil
|
|
|
|
}
|
|
|
|
|
2021-10-18 14:50:56 +03:00
|
|
|
func (f *KeycardFlow) Start(flowType FlowType, params FlowParams) error {
|
2021-10-15 10:35:01 +03:00
|
|
|
if f.state != Idle {
|
2021-10-14 13:14:43 +03:00
|
|
|
return errors.New("already running")
|
|
|
|
}
|
|
|
|
|
|
|
|
f.flowType = flowType
|
|
|
|
f.params = params
|
2021-10-15 10:35:01 +03:00
|
|
|
f.state = Running
|
2021-10-14 13:14:43 +03:00
|
|
|
go f.runFlow()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-18 14:50:56 +03:00
|
|
|
func (f *KeycardFlow) Resume(params FlowParams) error {
|
2021-10-15 10:35:01 +03:00
|
|
|
if f.state != Paused {
|
2021-10-14 13:14:43 +03:00
|
|
|
return errors.New("only paused flows can be resumed")
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range params {
|
|
|
|
f.params[k] = v
|
|
|
|
}
|
|
|
|
|
2021-10-15 10:35:01 +03:00
|
|
|
f.state = Resuming
|
2021-10-14 13:14:43 +03:00
|
|
|
f.wakeUp <- struct{}{}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-18 14:50:56 +03:00
|
|
|
func (f *KeycardFlow) Cancel() error {
|
2021-10-14 13:14:43 +03:00
|
|
|
prevState := f.state
|
|
|
|
|
2021-10-15 10:35:01 +03:00
|
|
|
if prevState != Idle {
|
2021-10-14 13:14:43 +03:00
|
|
|
return errors.New("cannot cancel idle flow")
|
|
|
|
}
|
|
|
|
|
2021-10-15 10:35:01 +03:00
|
|
|
f.state = Cancelling
|
|
|
|
if prevState == Paused {
|
2021-10-14 13:14:43 +03:00
|
|
|
f.wakeUp <- struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-18 14:50:56 +03:00
|
|
|
func (f *KeycardFlow) runFlow() {
|
2021-10-18 10:19:09 +03:00
|
|
|
var result FlowStatus
|
2021-10-18 16:25:20 +03:00
|
|
|
var err error
|
|
|
|
|
|
|
|
for {
|
2021-10-20 11:44:57 +03:00
|
|
|
f.cardInfo = cardStatus{freeSlots: -1, pinRetries: -1, pukRetries: -1}
|
2021-10-18 16:25:20 +03:00
|
|
|
result, err = f.connectedFlow()
|
2021-10-15 10:35:01 +03:00
|
|
|
|
2021-10-18 16:25:20 +03:00
|
|
|
if _, ok := err.(*restartError); !ok {
|
|
|
|
if result == nil {
|
|
|
|
result = FlowStatus{ErrorKey: err.Error()}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
2021-10-15 10:35:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if f.state != Cancelling {
|
|
|
|
signal.SendEvent(FlowResult, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
f.state = Idle
|
|
|
|
}
|
|
|
|
|
2021-10-18 16:25:20 +03:00
|
|
|
func (f *KeycardFlow) connectedFlow() (FlowStatus, error) {
|
2021-10-15 10:35:01 +03:00
|
|
|
kc := f.connect()
|
|
|
|
defer f.closeKeycard(kc)
|
|
|
|
|
|
|
|
if kc == nil {
|
2021-10-18 16:25:20 +03:00
|
|
|
return nil, errors.New(ErrorConnection)
|
|
|
|
}
|
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
if factoryReset, ok := f.params[FactoryReset]; ok && factoryReset.(bool) {
|
|
|
|
err := f.factoryReset(kc)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-18 16:25:20 +03:00
|
|
|
err := f.selectKeycard(kc)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-10-15 10:35:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
switch f.flowType {
|
2021-10-15 12:38:06 +03:00
|
|
|
case GetAppInfo:
|
|
|
|
return f.getAppInfoFlow(kc)
|
2021-10-18 15:47:27 +03:00
|
|
|
case RecoverAccount:
|
|
|
|
return f.recoverAccountFlow(kc)
|
2021-10-15 10:35:01 +03:00
|
|
|
default:
|
2021-10-18 16:25:20 +03:00
|
|
|
return nil, errors.New(ErrorUnknownFlow)
|
2021-10-15 10:35:01 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
func (f *KeycardFlow) pause(action string, errMsg string) {
|
|
|
|
status := FlowParams{}
|
|
|
|
|
|
|
|
if errMsg != "" {
|
|
|
|
status[ErrorKey] = errMsg
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.cardInfo.freeSlots != -1 {
|
|
|
|
status[InstanceUID] = f.cardInfo.instanceUID
|
|
|
|
status[KeyUID] = f.cardInfo.keyUID
|
|
|
|
status[FreeSlots] = f.cardInfo.freeSlots
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.cardInfo.pinRetries != -1 {
|
|
|
|
status[PINRetries] = f.cardInfo.pinRetries
|
|
|
|
status[PUKRetries] = f.cardInfo.pukRetries
|
|
|
|
}
|
|
|
|
|
2021-10-15 10:35:01 +03:00
|
|
|
signal.SendEvent(action, status)
|
|
|
|
f.state = Paused
|
|
|
|
}
|
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
func (f *KeycardFlow) pauseAndWait(action string, errMsg string) error {
|
|
|
|
f.pause(action, errMsg)
|
2021-10-15 10:35:01 +03:00
|
|
|
<-f.wakeUp
|
2021-10-15 12:38:06 +03:00
|
|
|
|
|
|
|
if f.state == Resuming {
|
|
|
|
f.state = Running
|
2021-10-18 16:29:55 +03:00
|
|
|
return nil
|
2021-10-15 12:38:06 +03:00
|
|
|
} else {
|
2021-10-18 16:29:55 +03:00
|
|
|
return errors.New("cancel")
|
2021-10-15 12:38:06 +03:00
|
|
|
}
|
2021-10-15 10:35:01 +03:00
|
|
|
}
|
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
func (f *KeycardFlow) pauseAndRestart(action string, errMsg string) error {
|
|
|
|
err := f.pauseAndWait(action, errMsg)
|
2021-10-18 16:34:15 +03:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return restartErr()
|
|
|
|
}
|
|
|
|
|
2021-10-18 14:50:56 +03:00
|
|
|
func (f *KeycardFlow) closeKeycard(kc *keycardContext) {
|
2021-10-15 10:35:01 +03:00
|
|
|
if kc != nil {
|
|
|
|
kc.stop()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-18 14:50:56 +03:00
|
|
|
func (f *KeycardFlow) connect() *keycardContext {
|
2021-10-15 10:35:01 +03:00
|
|
|
kc, err := startKeycardContext()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
f.pause(InsertCard, "")
|
2021-10-15 10:35:01 +03:00
|
|
|
select {
|
|
|
|
case <-f.wakeUp:
|
|
|
|
if f.state != Cancelling {
|
|
|
|
panic("Resuming is not expected during connection")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
case <-kc.connected:
|
|
|
|
if kc.runErr != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-18 14:50:56 +03:00
|
|
|
signal.SendEvent(CardInserted, FlowStatus{})
|
2021-10-15 10:35:01 +03:00
|
|
|
return kc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
func (f *KeycardFlow) factoryReset(kc *keycardContext) error {
|
|
|
|
// on success, remove the FactoryReset switch to avoid re-triggering it
|
|
|
|
// if card is disconnected/reconnected
|
|
|
|
delete(f.params, FactoryReset)
|
|
|
|
return errors.New("not implemented")
|
|
|
|
}
|
|
|
|
|
2021-10-18 15:47:27 +03:00
|
|
|
func (f *KeycardFlow) selectKeycard(kc *keycardContext) error {
|
2021-10-15 12:38:06 +03:00
|
|
|
appInfo, err := kc.selectApplet()
|
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
f.cardInfo.instanceUID = tox(appInfo.InstanceUID)
|
|
|
|
f.cardInfo.keyUID = tox(appInfo.KeyUID)
|
|
|
|
f.cardInfo.freeSlots = bytesToInt(appInfo.AvailableSlots)
|
|
|
|
|
2021-10-15 12:38:06 +03:00
|
|
|
if err != nil {
|
2021-10-18 16:36:53 +03:00
|
|
|
return restartErr()
|
2021-10-15 12:38:06 +03:00
|
|
|
}
|
|
|
|
|
2021-10-18 14:50:56 +03:00
|
|
|
if !appInfo.Installed {
|
2021-10-20 11:44:57 +03:00
|
|
|
return f.pauseAndRestart(SwapCard, ErrorNotAKeycard)
|
2021-10-18 14:50:56 +03:00
|
|
|
}
|
|
|
|
|
2021-10-15 12:38:06 +03:00
|
|
|
if requiredInstanceUID, ok := f.params[InstanceUID]; ok {
|
2021-10-20 11:44:57 +03:00
|
|
|
if f.cardInfo.instanceUID != requiredInstanceUID {
|
|
|
|
return f.pauseAndRestart(SwapCard, InstanceUID)
|
2021-10-15 12:38:06 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if requiredKeyUID, ok := f.params[KeyUID]; ok {
|
2021-10-20 11:44:57 +03:00
|
|
|
if f.cardInfo.keyUID != requiredKeyUID {
|
|
|
|
return f.pauseAndRestart(SwapCard, KeyUID)
|
2021-10-15 12:38:06 +03:00
|
|
|
}
|
|
|
|
}
|
2021-10-14 13:14:43 +03:00
|
|
|
|
2021-10-18 15:47:27 +03:00
|
|
|
return nil
|
2021-10-14 13:14:43 +03:00
|
|
|
}
|
2021-10-15 12:38:06 +03:00
|
|
|
|
2021-10-19 12:19:02 +03:00
|
|
|
func (f *KeycardFlow) pair(kc *keycardContext) error {
|
2021-10-20 11:44:57 +03:00
|
|
|
if f.cardInfo.freeSlots == 0 {
|
|
|
|
return f.pauseAndRestart(SwapCard, FreeSlots)
|
|
|
|
}
|
2021-10-19 12:19:02 +03:00
|
|
|
|
|
|
|
if pairingPass, ok := f.params[PairingPass]; ok {
|
|
|
|
pairing, err := kc.pair(pairingPass.(string))
|
|
|
|
|
|
|
|
if err == nil {
|
2021-10-20 11:44:57 +03:00
|
|
|
return f.pairings.store(f.cardInfo.instanceUID, toPairInfo(pairing))
|
2021-10-19 12:19:02 +03:00
|
|
|
} else if isSCardError(err) {
|
|
|
|
return restartErr()
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(f.params, PairingPass)
|
|
|
|
}
|
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
err := f.pauseAndWait(EnterPairing, "")
|
2021-10-19 12:19:02 +03:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return f.pair(kc)
|
|
|
|
}
|
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
func (f *KeycardFlow) initCard(kc *keycardContext) error {
|
|
|
|
//NOTE: after init a restart of the flow is always needed
|
|
|
|
return errors.New("not implemented")
|
|
|
|
}
|
|
|
|
|
2021-10-19 12:19:02 +03:00
|
|
|
func (f *KeycardFlow) openSC(kc *keycardContext) error {
|
2021-10-20 11:44:57 +03:00
|
|
|
if !kc.cmdSet.ApplicationInfo.Initialized {
|
|
|
|
return f.initCard(kc)
|
|
|
|
}
|
|
|
|
|
|
|
|
pairing := f.pairings.get(f.cardInfo.instanceUID)
|
2021-10-19 12:19:02 +03:00
|
|
|
|
|
|
|
if pairing != nil {
|
|
|
|
err := kc.openSecureChannel(pairing.Index, pairing.Key)
|
|
|
|
|
|
|
|
if err == nil {
|
2021-10-20 11:44:57 +03:00
|
|
|
appStatus, err := kc.getStatusApplication()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
// getStatus can only fail for connection errors
|
|
|
|
return restartErr()
|
|
|
|
}
|
|
|
|
|
|
|
|
f.cardInfo.pinRetries = appStatus.PinRetryCount
|
|
|
|
f.cardInfo.pukRetries = appStatus.PUKRetryCount
|
|
|
|
|
2021-10-19 12:19:02 +03:00
|
|
|
return nil
|
|
|
|
} else if isSCardError(err) {
|
|
|
|
return restartErr()
|
|
|
|
}
|
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
f.pairings.delete(f.cardInfo.instanceUID)
|
2021-10-19 12:19:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
err := f.pair(kc)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return f.openSC(kc)
|
|
|
|
}
|
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
func (f *KeycardFlow) unblockPUK(kc *keycardContext) error {
|
2021-10-18 16:25:20 +03:00
|
|
|
return errors.New("not yet implemented")
|
|
|
|
}
|
|
|
|
|
2021-10-20 11:44:57 +03:00
|
|
|
func (f *KeycardFlow) authenticate(kc *keycardContext) error {
|
|
|
|
if f.cardInfo.pukRetries == 0 {
|
|
|
|
return f.pauseAndRestart(SwapCard, PUKRetries)
|
|
|
|
} else if f.cardInfo.pinRetries == 0 {
|
|
|
|
// succesful PUK unblock leaves the card authenticated
|
|
|
|
return f.unblockPUK(kc)
|
|
|
|
}
|
|
|
|
|
|
|
|
pinError := ""
|
|
|
|
|
|
|
|
if pin, ok := f.params[PIN]; ok {
|
|
|
|
err := kc.verifyPin(pin.(string))
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
f.cardInfo.pinRetries = maxPINRetries
|
|
|
|
return nil
|
|
|
|
} else if isSCardError(err) {
|
|
|
|
return restartErr()
|
|
|
|
} else if leftRetries, ok := getPinRetries(err); ok {
|
|
|
|
f.cardInfo.pinRetries = leftRetries
|
|
|
|
}
|
|
|
|
|
|
|
|
pinError = PIN
|
|
|
|
}
|
|
|
|
|
|
|
|
err := f.pauseAndWait(EnterPIN, pinError)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return f.authenticate(kc)
|
|
|
|
}
|
|
|
|
|
2021-10-19 12:19:02 +03:00
|
|
|
func (f *KeycardFlow) openSCAndAuthenticate(kc *keycardContext) error {
|
|
|
|
err := f.openSC(kc)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return f.authenticate(kc)
|
|
|
|
}
|
|
|
|
|
2021-10-18 16:25:20 +03:00
|
|
|
func (f *KeycardFlow) getAppInfoFlow(kc *keycardContext) (FlowStatus, error) {
|
|
|
|
return FlowStatus{ErrorKey: ErrorOK, AppInfo: toAppInfo(kc.cmdSet.ApplicationInfo)}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *KeycardFlow) recoverAccountFlow(kc *keycardContext) (FlowStatus, error) {
|
|
|
|
err := f.openSCAndAuthenticate(kc)
|
2021-10-15 12:38:06 +03:00
|
|
|
|
|
|
|
if err != nil {
|
2021-10-18 16:25:20 +03:00
|
|
|
return nil, err
|
2021-10-15 12:38:06 +03:00
|
|
|
}
|
|
|
|
|
2021-10-18 16:25:20 +03:00
|
|
|
return nil, errors.New("not yet implemented")
|
2021-10-18 15:47:27 +03:00
|
|
|
}
|