keycard-go/command_set.go

423 lines
9.1 KiB
Go
Raw Normal View History

package keycard
import (
2021-10-19 09:22:39 +00:00
"bytes"
2019-03-13 12:49:26 +00:00
"crypto/rand"
"crypto/sha256"
2021-10-19 09:22:39 +00:00
"encoding/binary"
2019-03-13 15:20:51 +00:00
"errors"
"fmt"
2019-03-13 12:49:26 +00:00
"github.com/status-im/keycard-go/apdu"
2019-03-13 12:49:26 +00:00
"github.com/status-im/keycard-go/crypto"
"github.com/status-im/keycard-go/globalplatform"
"github.com/status-im/keycard-go/identifiers"
"github.com/status-im/keycard-go/types"
)
var ErrNoAvailablePairingSlots = errors.New("no available pairing slots")
2021-10-19 09:22:39 +00:00
var ErrBadChecksumSize = errors.New("bad checksum size")
type WrongPINError struct {
2021-10-07 08:20:58 +00:00
RemainingAttempts int
}
func (e *WrongPINError) Error() string {
2021-10-07 08:20:58 +00:00
return fmt.Sprintf("wrong pin. remaining attempts: %d", e.RemainingAttempts)
}
2021-10-22 09:47:47 +00:00
type WrongPUKError struct {
RemainingAttempts int
}
func (e *WrongPUKError) Error() string {
return fmt.Sprintf("wrong puk. remaining attempts: %d", e.RemainingAttempts)
}
type CommandSet struct {
c types.Channel
2019-03-13 12:49:26 +00:00
sc *SecureChannel
2019-03-13 15:20:51 +00:00
ApplicationInfo *types.ApplicationInfo
2019-03-13 12:49:26 +00:00
PairingInfo *types.PairingInfo
}
func NewCommandSet(c types.Channel) *CommandSet {
return &CommandSet{
c: c,
sc: NewSecureChannel(c),
ApplicationInfo: &types.ApplicationInfo{},
}
}
2019-03-13 15:20:51 +00:00
func (cs *CommandSet) SetPairingInfo(key []byte, index int) {
cs.PairingInfo = &types.PairingInfo{
Key: key,
Index: index,
}
}
func (cs *CommandSet) Select() error {
instanceAID, err := identifiers.KeycardInstanceAID(identifiers.KeycardDefaultInstanceIndex)
if err != nil {
return err
}
2019-04-17 10:24:02 +00:00
cmd := globalplatform.NewCommandSelect(instanceAID)
cmd.SetLe(0)
resp, err := cs.c.Send(cmd)
2019-03-13 12:49:26 +00:00
if err = cs.checkOK(resp, err); err != nil {
return err
}
appInfo, err := types.ParseApplicationInfo(resp.Data)
if err != nil {
return err
}
cs.ApplicationInfo = appInfo
2019-03-13 12:49:26 +00:00
if cs.ApplicationInfo.HasSecureChannelCapability() {
err = cs.sc.GenerateSecret(cs.ApplicationInfo.SecureChannelPublicKey)
if err != nil {
return err
}
2019-03-13 12:49:26 +00:00
cs.sc.Reset()
}
2019-03-13 12:49:26 +00:00
return nil
}
2019-03-11 11:50:16 +00:00
func (cs *CommandSet) Init(secrets *Secrets) error {
2019-03-13 12:49:26 +00:00
data, err := cs.sc.OneShotEncrypt(secrets)
2019-03-11 11:50:16 +00:00
if err != nil {
return err
}
2019-03-13 12:49:26 +00:00
init := NewCommandInit(data)
resp, err := cs.c.Send(init)
return cs.checkOK(resp, err)
}
func (cs *CommandSet) Pair(pairingPass string) error {
challenge := make([]byte, 32)
if _, err := rand.Read(challenge); err != nil {
return err
}
cmd := NewCommandPairFirstStep(challenge)
resp, err := cs.c.Send(cmd)
2021-10-22 11:40:56 +00:00
if resp != nil && resp.Sw == SwNoAvailablePairingSlots {
return ErrNoAvailablePairingSlots
}
2019-03-13 12:49:26 +00:00
if err = cs.checkOK(resp, err); err != nil {
return err
}
cardCryptogram := resp.Data[:32]
cardChallenge := resp.Data[32:]
secretHash, err := crypto.VerifyCryptogram(challenge, pairingPass, cardCryptogram)
2019-03-11 11:50:16 +00:00
if err != nil {
return err
}
2019-03-13 12:49:26 +00:00
h := sha256.New()
h.Write(secretHash[:])
h.Write(cardChallenge)
cmd = NewCommandPairFinalStep(h.Sum(nil))
resp, err = cs.c.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
return err
}
2019-03-11 11:50:16 +00:00
2019-03-13 12:49:26 +00:00
h.Reset()
h.Write(secretHash[:])
h.Write(resp.Data[1:])
pairingKey := h.Sum(nil)
pairingIndex := resp.Data[0]
cs.PairingInfo = &types.PairingInfo{
Key: pairingKey,
Index: int(pairingIndex),
}
return nil
2019-03-11 11:50:16 +00:00
}
2019-03-27 12:05:46 +00:00
func (cs *CommandSet) Unpair(index uint8) error {
cmd := NewCommandUnpair(index)
resp, err := cs.sc.Send(cmd)
return cs.checkOK(resp, err)
}
2019-03-13 15:20:51 +00:00
func (cs *CommandSet) OpenSecureChannel() error {
if cs.ApplicationInfo == nil {
return errors.New("cannot open secure channel without setting PairingInfo")
}
cmd := NewCommandOpenSecureChannel(uint8(cs.PairingInfo.Index), cs.sc.RawPublicKey())
resp, err := cs.c.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
return err
}
encKey, macKey, iv := crypto.DeriveSessionKeys(cs.sc.Secret(), cs.PairingInfo.Key, resp.Data)
cs.sc.Init(iv, encKey, macKey)
err = cs.mutualAuthenticate()
if err != nil {
return err
}
return nil
}
2019-03-15 15:09:38 +00:00
func (cs *CommandSet) GetStatus(info uint8) (*types.ApplicationStatus, error) {
cmd := NewCommandGetStatus(info)
2019-03-13 15:20:51 +00:00
resp, err := cs.sc.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
return nil, err
}
return types.ParseApplicationStatus(resp.Data)
}
2019-03-15 15:09:38 +00:00
func (cs *CommandSet) GetStatusApplication() (*types.ApplicationStatus, error) {
return cs.GetStatus(P1GetStatusApplication)
}
func (cs *CommandSet) GetStatusKeyPath() (*types.ApplicationStatus, error) {
return cs.GetStatus(P1GetStatusKeyPath)
}
2019-03-14 15:31:30 +00:00
func (cs *CommandSet) VerifyPIN(pin string) error {
cmd := NewCommandVerifyPIN(pin)
resp, err := cs.sc.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
if resp.Sw&0x63C0 == 0x63C0 {
remainingAttempts := resp.Sw & 0x000F
return &WrongPINError{
2021-10-07 08:20:58 +00:00
RemainingAttempts: int(remainingAttempts),
}
}
return err
}
2019-03-14 15:31:30 +00:00
return nil
2019-03-14 15:31:30 +00:00
}
2019-03-27 13:34:55 +00:00
func (cs *CommandSet) ChangePIN(pin string) error {
cmd := NewCommandChangePIN(pin)
resp, err := cs.sc.Send(cmd)
return cs.checkOK(resp, err)
}
2021-10-22 09:47:47 +00:00
func (cs *CommandSet) UnblockPIN(puk string, newPIN string) error {
cmd := NewCommandUnblockPIN(puk, newPIN)
resp, err := cs.sc.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
if resp.Sw&0x63C0 == 0x63C0 {
remainingAttempts := resp.Sw & 0x000F
return &WrongPUKError{
RemainingAttempts: int(remainingAttempts),
}
}
return err
}
return nil
}
func (cs *CommandSet) ChangePUK(puk string) error {
cmd := NewCommandChangePUK(puk)
resp, err := cs.sc.Send(cmd)
return cs.checkOK(resp, err)
}
func (cs *CommandSet) ChangePairingSecret(password string) error {
secret := generatePairingToken(password)
cmd := NewCommandChangePairingSecret(secret)
resp, err := cs.sc.Send(cmd)
return cs.checkOK(resp, err)
}
2019-03-14 15:31:30 +00:00
func (cs *CommandSet) GenerateKey() ([]byte, error) {
cmd := NewCommandGenerateKey()
resp, err := cs.sc.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
return nil, err
}
return resp.Data, nil
}
2021-10-19 09:22:39 +00:00
func (cs *CommandSet) GenerateMnemonic(checksumSize int) ([]int, error) {
if checksumSize < 4 || checksumSize > 8 {
return nil, ErrBadChecksumSize
}
cmd := NewCommandGenerateMnemonic(byte(checksumSize))
resp, err := cs.sc.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
return nil, err
}
buf := bytes.NewBuffer(resp.Data)
indexes := make([]int, 0)
for {
var index int16
err := binary.Read(buf, binary.BigEndian, &index)
if err != nil {
break
}
indexes = append(indexes, int(index))
}
return indexes, nil
}
2019-03-27 10:22:20 +00:00
func (cs *CommandSet) RemoveKey() error {
cmd := NewCommandRemoveKey()
resp, err := cs.sc.Send(cmd)
return cs.checkOK(resp, err)
}
2019-03-15 00:03:24 +00:00
func (cs *CommandSet) DeriveKey(path string) error {
cmd, err := NewCommandDeriveKey(path)
2019-03-16 09:03:35 +00:00
if err != nil {
return err
}
resp, err := cs.sc.Send(cmd)
return cs.checkOK(resp, err)
}
2021-10-04 13:26:08 +00:00
func (cs *CommandSet) ExportKey(derive bool, makeCurrent bool, onlyPublic bool, path string) ([]byte, []byte, error) {
var p1 uint8
2019-04-24 13:30:14 +00:00
if derive == false {
p1 = P1ExportKeyCurrent
} else if makeCurrent == false {
p1 = P1ExportKeyDerive
} else {
2019-04-24 13:30:14 +00:00
p1 = P1ExportKeyDeriveAndMakeCurrent
}
var p2 uint8
2019-04-24 13:30:14 +00:00
if onlyPublic == true {
p2 = P2ExportKeyPublicOnly
} else {
2019-04-24 13:30:14 +00:00
p2 = P2ExportKeyPrivateAndPublic
}
2021-10-04 13:26:08 +00:00
cmd, err := NewCommandExportKey(p1, p2, path)
if err != nil {
2021-10-04 13:26:08 +00:00
return nil, nil, err
2019-04-24 13:30:14 +00:00
}
resp, err := cs.sc.Send(cmd)
err = cs.checkOK(resp, err)
if err != nil {
2021-10-04 13:26:08 +00:00
return nil, nil, err
}
2021-10-04 13:26:08 +00:00
return types.ParseExportKeyResponse(resp.Data)
}
2019-03-16 09:03:35 +00:00
func (cs *CommandSet) SetPinlessPath(path string) error {
2019-04-09 08:25:56 +00:00
cmd, err := NewCommandSetPinlessPath(path)
2019-03-15 00:03:24 +00:00
if err != nil {
return err
}
resp, err := cs.sc.Send(cmd)
return cs.checkOK(resp, err)
}
func (cs *CommandSet) Sign(data []byte) (*types.Signature, error) {
2019-11-14 11:46:15 +00:00
cmd, err := NewCommandSign(data, P1SignCurrentKey, "")
if err != nil {
return nil, err
}
resp, err := cs.sc.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
return nil, err
}
return types.ParseSignature(data, resp.Data)
}
func (cs *CommandSet) SignWithPath(data []byte, path string) (*types.Signature, error) {
cmd, err := NewCommandSign(data, P1SignDerive, path)
2019-04-09 08:25:56 +00:00
if err != nil {
return nil, err
}
resp, err := cs.sc.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
return nil, err
}
return types.ParseSignature(data, resp.Data)
}
func (cs *CommandSet) SignPinless(data []byte) (*types.Signature, error) {
2019-11-14 11:46:15 +00:00
cmd, err := NewCommandSign(data, P1SignPinless, "")
2019-03-15 00:03:24 +00:00
if err != nil {
return nil, err
}
2019-04-09 11:17:10 +00:00
resp, err := cs.c.Send(cmd)
2019-03-15 00:03:24 +00:00
if err = cs.checkOK(resp, err); err != nil {
return nil, err
}
2019-03-15 09:26:38 +00:00
return types.ParseSignature(data, resp.Data)
2019-03-15 00:03:24 +00:00
}
2020-01-07 11:56:50 +00:00
func (cs *CommandSet) LoadSeed(seed []byte) ([]byte, error) {
cmd := NewCommandLoadSeed(seed)
resp, err := cs.sc.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
return nil, err
}
return resp.Data, nil
}
2019-03-13 15:20:51 +00:00
func (cs *CommandSet) mutualAuthenticate() error {
data := make([]byte, 32)
if _, err := rand.Read(data); err != nil {
return err
}
cmd := NewCommandMutuallyAuthenticate(data)
resp, err := cs.sc.Send(cmd)
return cs.checkOK(resp, err)
}
func (cs *CommandSet) checkOK(resp *apdu.Response, err error, allowedResponses ...uint16) error {
2019-03-15 09:27:28 +00:00
if err != nil {
return err
}
if len(allowedResponses) == 0 {
allowedResponses = []uint16{apdu.SwOK}
}
for _, code := range allowedResponses {
if code == resp.Sw {
return nil
}
}
return apdu.NewErrBadResponse(resp.Sw, "unexpected response")
}