status-keycard-go/pkg/flow/commands.go
2025-01-20 11:57:27 +01:00

611 lines
12 KiB
Go

package flow
import (
"errors"
"io"
"strings"
"github.com/status-im/keycard-go"
"github.com/status-im/keycard-go/apdu"
"github.com/status-im/keycard-go/derivationpath"
ktypes "github.com/status-im/keycard-go/types"
"github.com/status-im/status-keycard-go/internal"
"github.com/status-im/status-keycard-go/pkg/pairing"
"github.com/status-im/status-keycard-go/pkg/utils"
)
func (f *KeycardFlow) factoryReset(kc *internal.KeycardContext) error {
err := kc.FactoryReset(true)
if err == nil {
delete(f.params, FactoryReset)
return restartErr()
} else if internal.IsSCardError(err) {
return restartErr()
} else {
return err
}
}
func (f *KeycardFlow) selectKeycard(kc *internal.KeycardContext) error {
appInfo, err := kc.SelectApplet()
if err != nil {
return restartErr()
}
f.cardInfo.instanceUID = utils.Btox(appInfo.InstanceUID)
f.cardInfo.keyUID = utils.Btox(appInfo.KeyUID)
f.cardInfo.freeSlots = internal.BytesToInt(appInfo.AvailableSlots)
f.cardInfo.version = internal.BytesToInt(appInfo.Version)
if !appInfo.Installed {
return f.pauseAndRestart(SwapCard, internal.ErrorNotAKeycard)
}
if requiredInstanceUID, ok := f.params[InstanceUID]; ok {
if f.cardInfo.instanceUID != requiredInstanceUID {
return f.pauseAndRestart(SwapCard, InstanceUID)
}
}
if requiredKeyUID, ok := f.params[KeyUID]; ok {
if f.cardInfo.keyUID != requiredKeyUID {
return f.pauseAndRestart(SwapCard, KeyUID)
}
}
return nil
}
func (f *KeycardFlow) pair(kc *internal.KeycardContext) error {
if f.cardInfo.freeSlots == 0 {
return f.pauseAndRestart(SwapCard, FreeSlots)
}
pairingPass, ok := f.params[PairingPass]
if !ok {
pairingPass = internal.DefPairing
}
pair, err := kc.Pair(pairingPass.(string))
if err == nil {
return f.pairings.Store(f.cardInfo.instanceUID, pairing.ToPairInfo(pair))
} else if internal.IsSCardError(err) {
return restartErr()
}
delete(f.params, PairingPass)
err = f.pauseAndWait(EnterPairing, internal.ErrorPairing)
if err != nil {
return err
}
return f.pair(kc)
}
func (f *KeycardFlow) initCard(kc *internal.KeycardContext) error {
newPIN, pinOK := f.params[NewPIN]
if !pinOK {
err := f.pauseAndWait(EnterNewPIN, internal.ErrorRequireInit)
if err != nil {
return err
}
return f.initCard(kc)
}
newPUK, pukOK := f.params[NewPUK]
if !pukOK {
err := f.pauseAndWait(EnterNewPUK, internal.ErrorRequireInit)
if err != nil {
return err
}
return f.initCard(kc)
}
newPairing, pairingOK := f.params[NewPairing]
if !pairingOK {
newPairing = internal.DefPairing
}
err := kc.Init(newPIN.(string), newPUK.(string), newPairing.(string))
if internal.IsSCardError(err) {
return restartErr()
} else if err != nil {
return err
}
f.params[PIN] = newPIN
f.params[PairingPass] = newPairing
delete(f.params, NewPIN)
delete(f.params, NewPUK)
delete(f.params, NewPairing)
return restartErr()
}
func (f *KeycardFlow) verifyAuthenticity(kc *internal.KeycardContext) error {
if (len(f.knownCA) == 0) || (f.cardInfo.instanceUID == f.params[SkipAuthUID]) {
return nil
}
ca, err := kc.Identify()
if (err != nil) || !internal.ContainsString(ca, f.knownCA) {
return authenticityErr()
}
return nil
}
func (f *KeycardFlow) openSC(kc *internal.KeycardContext, giveup bool) error {
var pairing *pairing.Info
if !kc.ApplicationInfo().Initialized && !giveup {
return f.initCard(kc)
} else {
pairing = f.pairings.Get(f.cardInfo.instanceUID)
}
if pairing != nil {
err := kc.OpenSecureChannel(pairing.Index, pairing.Key)
if err == nil {
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
return nil
} else if internal.IsSCardError(err) {
return restartErr()
}
f.pairings.Delete(f.cardInfo.instanceUID)
}
err := f.verifyAuthenticity(kc)
if err != nil {
return err
}
if giveup {
return giveupErr()
}
err = f.pair(kc)
if err != nil {
return err
}
return f.openSC(kc, giveup)
}
func (f *KeycardFlow) unblockPIN(kc *internal.KeycardContext) error {
if f.cardInfo.pukRetries == 0 {
return f.pauseAndRestart(SwapCard, PUKRetries)
}
pukError := ""
var err error
newPIN, pinOK := f.params[NewPIN]
puk, pukOK := f.params[PUK]
if pinOK && pukOK {
err = kc.UnblockPIN(puk.(string), newPIN.(string))
if err == nil {
f.cardInfo.pinRetries = internal.MaxPINRetries
f.cardInfo.pukRetries = internal.MaxPUKRetries
f.params[PIN] = newPIN
delete(f.params, NewPIN)
delete(f.params, PUK)
return nil
} else if internal.IsSCardError(err) {
return restartErr()
} else if leftRetries, ok := internal.GetRetries(err); ok {
f.cardInfo.pukRetries = leftRetries
delete(f.params, PUK)
pukOK = false
}
pukError = PUK
}
if f.cardInfo.pukRetries == 0 {
return f.pauseAndRestart(SwapCard, PUKRetries)
}
if !pukOK {
err = f.pauseAndWait(EnterPUK, pukError)
} else if !pinOK {
err = f.pauseAndWait(EnterNewPIN, internal.ErrorUnblocking)
}
if err != nil {
return err
}
return f.unblockPIN(kc)
}
func (f *KeycardFlow) authenticate(kc *internal.KeycardContext) error {
if f.cardInfo.pinRetries == 0 {
// succesful unblock leaves the card authenticated
return f.unblockPIN(kc)
}
pinError := ""
if pin, ok := f.params[PIN]; ok {
err := kc.VerifyPin(pin.(string))
if err == nil {
f.cardInfo.pinRetries = internal.MaxPINRetries
return nil
} else if internal.IsSCardError(err) {
return restartErr()
} else if leftRetries, ok := internal.GetRetries(err); ok {
f.cardInfo.pinRetries = leftRetries
delete(f.params, PIN)
}
pinError = PIN
}
if f.cardInfo.pinRetries == 0 {
return f.unblockPIN(kc)
}
err := f.pauseAndWait(EnterPIN, pinError)
if err != nil {
return err
}
return f.authenticate(kc)
}
func (f *KeycardFlow) openSCAndAuthenticate(kc *internal.KeycardContext, giveup bool) error {
err := f.openSC(kc, giveup)
if err != nil {
return err
}
return f.authenticate(kc)
}
func (f *KeycardFlow) unpairCurrent(kc *internal.KeycardContext) error {
err := kc.UnpairCurrent()
if internal.IsSCardError(err) {
return restartErr()
}
return err
}
func (f *KeycardFlow) unpair(kc *internal.KeycardContext, idx int) error {
err := kc.Unpair(uint8(idx))
if internal.IsSCardError(err) {
return restartErr()
}
return err
}
func (f *KeycardFlow) removeKey(kc *internal.KeycardContext) error {
err := kc.RemoveKey()
if internal.IsSCardError(err) {
return restartErr()
}
return err
}
func (f *KeycardFlow) getMetadata(kc *internal.KeycardContext) (*internal.Metadata, error) {
m, err := kc.GetMetadata()
if err == nil {
return internal.ToMetadata(m), nil
} else if internal.IsSCardError(err) {
return nil, restartErr()
} else if serr, ok := err.(*apdu.ErrBadResponse); ok {
if serr.Sw == 0x6d00 {
return nil, errors.New(internal.ErrorNoKeys)
} else {
return nil, err
}
} else if err == io.EOF {
return nil, errors.New(internal.ErrorNoData)
} else {
return nil, err
}
}
func (f *KeycardFlow) storeMetadata(kc *internal.KeycardContext) error {
cardName, cardNameOK := f.params[CardName]
if !cardNameOK {
err := f.pauseAndWait(EnterName, internal.ErrorStoreMeta)
if err != nil {
return err
}
return f.storeMetadata(kc)
}
w, walletsOK := f.params[WalletPaths]
if !walletsOK {
err := f.pauseAndWait(EnterWallets, internal.ErrorStoreMeta)
if err != nil {
return err
}
return f.storeMetadata(kc)
}
wallets := w.([]interface{})
paths := make([]uint32, len(wallets))
for i, p := range wallets {
if !strings.HasPrefix(p.(string), internal.WalletRoothPath) {
return errors.New("path must start with " + internal.WalletRoothPath)
}
_, components, err := derivationpath.Decode(p.(string))
if err != nil {
return err
}
paths[i] = components[len(components)-1]
}
m, err := ktypes.NewMetadata(cardName.(string), paths)
if err != nil {
return err
}
err = kc.StoreMetadata(m)
if internal.IsSCardError(err) {
return restartErr()
}
return err
}
func (f *KeycardFlow) exportKey(kc *internal.KeycardContext, path string, onlyPublic bool) (*internal.KeyPair, error) {
var p2 uint8
if onlyPublic {
p2 = keycard.P2ExportKeyPublicOnly
} else {
p2 = keycard.P2ExportKeyPrivateAndPublic
}
return f.exportKeyExtended(kc, path, p2)
}
func (f *KeycardFlow) exportKeyExtended(kc *internal.KeycardContext, path string, p2 uint8) (*internal.KeyPair, error) {
keyPair, err := kc.ExportKey(true, path == internal.MasterPath, p2, path)
if internal.IsSCardError(err) {
return nil, restartErr()
}
return keyPair, err
}
func (f *KeycardFlow) exportBIP44Key(kc *internal.KeycardContext) (interface{}, error) {
if path, ok := f.params[BIP44Path]; ok {
exportPrivParam, ok := f.params[ExportPriv]
exportPrivate := (!ok || !exportPrivParam.(bool))
if pathStr, ok := path.(string); ok {
return f.exportKey(kc, pathStr, exportPrivate)
} else if paths, ok := path.([]interface{}); ok {
keys := make([]*internal.KeyPair, len(paths))
for i, path := range paths {
key, err := f.exportKey(kc, path.(string), exportPrivate)
if err != nil {
return nil, err
}
keys[i] = key
}
return keys, nil
} else {
delete(f.params, BIP44Path)
return f.exportBIP44Key(kc)
}
}
err := f.pauseAndWait(EnterPath, internal.ErrorExporting)
if err != nil {
return nil, err
}
return f.exportBIP44Key(kc)
}
func (f *KeycardFlow) loadKeys(kc *internal.KeycardContext) error {
if mnemonic, ok := f.params[Mnemonic]; ok {
keyUID, err := kc.LoadMnemonic(mnemonic.(string), "")
if internal.IsSCardError(err) {
return restartErr()
} else if err != nil {
return err
}
f.cardInfo.keyUID = utils.Btox(keyUID)
return nil
}
tmpMnemonic, ok := f.params[MnemonicLen]
var mnemonicLength int
if ok {
switch t := tmpMnemonic.(type) {
case int:
mnemonicLength = t
case float64:
mnemonicLength = int(t)
default:
mnemonicLength = internal.DefMnemoLen
}
} else {
mnemonicLength = internal.DefMnemoLen
}
indexes, err := kc.GenerateMnemonic(mnemonicLength / 3)
if internal.IsSCardError(err) {
return restartErr()
} else if err != nil {
return err
}
err = f.pauseAndWaitWithStatus(EnterMnemonic, internal.ErrorLoading, FlowParams{MnemonicIdxs: indexes})
if err != nil {
return err
}
return f.loadKeys(kc)
}
func (f *KeycardFlow) changePIN(kc *internal.KeycardContext) error {
if newPIN, ok := f.params[NewPIN]; ok {
err := kc.ChangePin(newPIN.(string))
if internal.IsSCardError(err) {
return restartErr()
} else if err != nil {
return err
}
return nil
}
err := f.pauseAndWait(EnterNewPIN, internal.ErrorChanging)
if err != nil {
return err
}
return f.changePIN(kc)
}
func (f *KeycardFlow) changePUK(kc *internal.KeycardContext) error {
if newPUK, ok := f.params[NewPUK]; ok {
err := kc.ChangePuk(newPUK.(string))
if internal.IsSCardError(err) {
return restartErr()
} else if err != nil {
return err
}
return nil
}
err := f.pauseAndWait(EnterNewPUK, internal.ErrorChanging)
if err != nil {
return err
}
return f.changePUK(kc)
}
func (f *KeycardFlow) changePairing(kc *internal.KeycardContext) error {
if newPairing, ok := f.params[NewPairing]; ok {
err := kc.ChangePairingPassword(newPairing.(string))
if internal.IsSCardError(err) {
return restartErr()
} else if err != nil {
return err
}
return nil
}
err := f.pauseAndWait(EnterNewPair, internal.ErrorChanging)
if err != nil {
return err
}
return f.changePairing(kc)
}
func (f *KeycardFlow) sign(kc *internal.KeycardContext) (*internal.Signature, error) {
var err error
path, pathOK := f.params[BIP44Path]
if !pathOK {
err = f.pauseAndWait(EnterPath, internal.ErrorSigning)
if err != nil {
return nil, err
}
return f.sign(kc)
}
hash, hashOK := f.params[TXHash]
var rawHash []byte
if hashOK {
rawHash, err = utils.Xtob(hash.(string))
if err != nil {
hashOK = false
}
}
if !hashOK {
err := f.pauseAndWait(EnterTXHash, internal.ErrorSigning)
if err != nil {
return nil, err
}
return f.sign(kc)
}
signature, err := kc.SignWithPath(rawHash, path.(string))
if internal.IsSCardError(err) {
return nil, restartErr()
} else if err != nil {
return nil, err
}
return internal.ToSignature(signature), nil
}