1package keycard 2 3import ( 4 "crypto/rand" 5 "crypto/sha256" 6 "errors" 7 8 "github.com/status-im/keycard-go/apdu" 9 "github.com/status-im/keycard-go/crypto" 10 "github.com/status-im/keycard-go/globalplatform" 11 "github.com/status-im/keycard-go/identifiers" 12 "github.com/status-im/keycard-go/types" 13) 14 15type CommandSet struct { 16 c types.Channel 17 sc *SecureChannel 18 ApplicationInfo *types.ApplicationInfo 19 PairingInfo *types.PairingInfo 20} 21 22func NewCommandSet(c types.Channel) *CommandSet { 23 return &CommandSet{ 24 c: c, 25 sc: NewSecureChannel(c), 26 } 27} 28 29func (cs *CommandSet) SetPairingInfo(key []byte, index int) { 30 cs.PairingInfo = &types.PairingInfo{ 31 Key: key, 32 Index: index, 33 } 34} 35 36func (cs *CommandSet) Select() error { 37 instanceAID, err := identifiers.KeycardInstanceAID(identifiers.KeycardDefaultInstanceIndex) 38 if err != nil { 39 return err 40 } 41 42 cmd := apdu.NewCommand( 43 0x00, 44 globalplatform.InsSelect, 45 uint8(0x04), 46 uint8(0x00), 47 instanceAID, 48 ) 49 50 cmd.SetLe(0) 51 resp, err := cs.c.Send(cmd) 52 if err = cs.checkOK(resp, err); err != nil { 53 cs.ApplicationInfo = &types.ApplicationInfo{} 54 return err 55 } 56 57 appInfo, err := types.ParseApplicationInfo(resp.Data) 58 if err != nil { 59 return err 60 } 61 62 cs.ApplicationInfo = appInfo 63 64 if cs.ApplicationInfo.HasSecureChannelCapability() { 65 err = cs.sc.GenerateSecret(cs.ApplicationInfo.SecureChannelPublicKey) 66 if err != nil { 67 return err 68 } 69 70 cs.sc.Reset() 71 } 72 73 return nil 74} 75 76func (cs *CommandSet) Init(secrets *Secrets) error { 77 data, err := cs.sc.OneShotEncrypt(secrets) 78 if err != nil { 79 return err 80 } 81 82 init := NewCommandInit(data) 83 resp, err := cs.c.Send(init) 84 85 return cs.checkOK(resp, err) 86} 87 88func (cs *CommandSet) Pair(pairingPass string) error { 89 challenge := make([]byte, 32) 90 if _, err := rand.Read(challenge); err != nil { 91 return err 92 } 93 94 cmd := NewCommandPairFirstStep(challenge) 95 resp, err := cs.c.Send(cmd) 96 if err = cs.checkOK(resp, err); err != nil { 97 return err 98 } 99 100 cardCryptogram := resp.Data[:32] 101 cardChallenge := resp.Data[32:] 102 103 secretHash, err := crypto.VerifyCryptogram(challenge, pairingPass, cardCryptogram) 104 if err != nil { 105 return err 106 } 107 108 h := sha256.New() 109 h.Write(secretHash[:]) 110 h.Write(cardChallenge) 111 cmd = NewCommandPairFinalStep(h.Sum(nil)) 112 resp, err = cs.c.Send(cmd) 113 if err = cs.checkOK(resp, err); err != nil { 114 return err 115 } 116 117 h.Reset() 118 h.Write(secretHash[:]) 119 h.Write(resp.Data[1:]) 120 121 pairingKey := h.Sum(nil) 122 pairingIndex := resp.Data[0] 123 124 cs.PairingInfo = &types.PairingInfo{ 125 Key: pairingKey, 126 Index: int(pairingIndex), 127 } 128 129 return nil 130} 131 132func (cs *CommandSet) OpenSecureChannel() error { 133 if cs.ApplicationInfo == nil { 134 return errors.New("cannot open secure channel without setting PairingInfo") 135 } 136 137 cmd := NewCommandOpenSecureChannel(uint8(cs.PairingInfo.Index), cs.sc.RawPublicKey()) 138 resp, err := cs.c.Send(cmd) 139 if err = cs.checkOK(resp, err); err != nil { 140 return err 141 } 142 143 encKey, macKey, iv := crypto.DeriveSessionKeys(cs.sc.Secret(), cs.PairingInfo.Key, resp.Data) 144 cs.sc.Init(iv, encKey, macKey) 145 146 err = cs.mutualAuthenticate() 147 if err != nil { 148 return err 149 } 150 151 return nil 152} 153 154func (cs *CommandSet) GetStatus(info uint8) (*types.ApplicationStatus, error) { 155 cmd := NewCommandGetStatus(info) 156 resp, err := cs.sc.Send(cmd) 157 if err = cs.checkOK(resp, err); err != nil { 158 return nil, err 159 } 160 161 return types.ParseApplicationStatus(resp.Data) 162} 163 164func (cs *CommandSet) GetStatusApplication() (*types.ApplicationStatus, error) { 165 return cs.GetStatus(P1GetStatusApplication) 166} 167 168func (cs *CommandSet) GetStatusKeyPath() (*types.ApplicationStatus, error) { 169 return cs.GetStatus(P1GetStatusKeyPath) 170} 171 172func (cs *CommandSet) VerifyPIN(pin string) error { 173 cmd := NewCommandVerifyPIN(pin) 174 resp, err := cs.sc.Send(cmd) 175 176 return cs.checkOK(resp, err) 177} 178 179func (cs *CommandSet) GenerateKey() ([]byte, error) { 180 cmd := NewCommandGenerateKey() 181 resp, err := cs.sc.Send(cmd) 182 if err = cs.checkOK(resp, err); err != nil { 183 return nil, err 184 } 185 186 return resp.Data, nil 187} 188 189func (cs *CommandSet) DeriveKey(path string) error { 190 cmd, err := NewCommandDeriveKey(path) 191 if err != nil { 192 return err 193 } 194 195 resp, err := cs.sc.Send(cmd) 196 return cs.checkOK(resp, err) 197} 198 199func (cs *CommandSet) SetPinlessPath(path string) error { 200 cmd, err := NewCommandDeriveKey(path) 201 if err != nil { 202 return err 203 } 204 205 resp, err := cs.sc.Send(cmd) 206 return cs.checkOK(resp, err) 207} 208 209func (cs *CommandSet) Sign(data []byte) (*types.Signature, error) { 210 cmd, err := NewCommandSign(data) 211 if err != nil { 212 return nil, err 213 } 214 215 resp, err := cs.sc.Send(cmd) 216 if err = cs.checkOK(resp, err); err != nil { 217 return nil, err 218 } 219 220 return types.ParseSignature(data, resp.Data) 221} 222 223func (cs *CommandSet) mutualAuthenticate() error { 224 data := make([]byte, 32) 225 if _, err := rand.Read(data); err != nil { 226 return err 227 } 228 229 cmd := NewCommandMutuallyAuthenticate(data) 230 resp, err := cs.sc.Send(cmd) 231 232 return cs.checkOK(resp, err) 233} 234 235func (cs *CommandSet) checkOK(resp *apdu.Response, err error, allowedResponses ...uint16) error { 236 if err != nil { 237 return err 238 } 239 240 if len(allowedResponses) == 0 { 241 allowedResponses = []uint16{apdu.SwOK} 242 } 243 244 for _, code := range allowedResponses { 245 if code == resp.Sw { 246 return nil 247 } 248 } 249 250 return apdu.NewErrBadResponse(resp.Sw, "unexpected response") 251} 252