package globalplatform import ( "crypto/rand" "errors" "os" "github.com/status-im/keycard-go/apdu" "github.com/status-im/keycard-go/identifiers" "github.com/status-im/keycard-go/types" ) var ErrSecureChannelNotOpen = errors.New("secure channel not open") type LoadingCallback = func(loadingBlock, totalBlocks int) type CommandSet struct { c types.Channel sc *SecureChannel session *Session } func NewCommandSet(c types.Channel) *CommandSet { return &CommandSet{ c: c, } } func (cs *CommandSet) Select() error { return cs.SelectAID(nil) } func (cs *CommandSet) SelectAID(aid []byte) error { cmd := apdu.NewCommand( 0x00, InsSelect, uint8(0x04), uint8(0x00), aid, ) cmd.SetLe(0) resp, err := cs.c.Send(cmd) return cs.checkOK(resp, err) } func (cs *CommandSet) OpenSecureChannel() error { hostChallenge, err := generateHostChallenge() if err != nil { return err } err = cs.initializeUpdate(hostChallenge) if err != nil { return err } return cs.externalAuthenticate() } func (cs *CommandSet) DeleteKeycardInstancesAndPackage() error { if cs.sc == nil { return ErrSecureChannelNotOpen } instanceAID, err := identifiers.KeycardInstanceAID(identifiers.KeycardDefaultInstanceIndex) if err != nil { return err } ids := [][]byte{ identifiers.NdefInstanceAID, instanceAID, identifiers.PackageAID, } for _, aid := range ids { err := cs.Delete(aid) if err != nil { return err } } return nil } func (cs *CommandSet) Delete(aid []byte) error { cmd := NewCommandDelete(aid) resp, err := cs.sc.Send(cmd) return cs.checkOK(resp, err, SwOK, SwReferencedDataNotFound) } func (cs *CommandSet) LoadKeycardPackage(capFile *os.File, callback LoadingCallback) error { return cs.LoadPackage(capFile, identifiers.PackageAID, callback) } func (cs *CommandSet) LoadPackage(capFile *os.File, pkgAID []byte, callback LoadingCallback) error { if cs.sc == nil { return ErrSecureChannelNotOpen } preLoad := NewCommandInstallForLoad(pkgAID, []byte{}) resp, err := cs.sc.Send(preLoad) if err = cs.checkOK(resp, err); err != nil { return err } load, err := NewLoadCommandStream(capFile) if err != nil { return err } for load.Next() { cmd := load.GetCommand() callback(int(load.Index()), load.BlocksCount()) resp, err = cs.sc.Send(cmd) if err = cs.checkOK(resp, err); err != nil { return err } } return nil } func (cs *CommandSet) InstallNDEFApplet(ndefRecord []byte) error { return cs.InstallForInstall( identifiers.PackageAID, identifiers.NdefAID, identifiers.NdefInstanceAID, ndefRecord) } func (cs *CommandSet) InstallKeycardApplet() error { instanceAID, err := identifiers.KeycardInstanceAID(identifiers.KeycardDefaultInstanceIndex) if err != nil { return err } return cs.InstallForInstall( identifiers.PackageAID, identifiers.KeycardAID, instanceAID, []byte{}) } func (cs *CommandSet) InstallForInstall(packageAID, appletAID, instanceAID, params []byte) error { cmd := NewCommandInstallForInstall(packageAID, appletAID, instanceAID, params) resp, err := cs.sc.Send(cmd) return cs.checkOK(resp, err) } func (cs *CommandSet) initializeUpdate(hostChallenge []byte) error { cmd := NewCommandInitializeUpdate(hostChallenge) resp, err := cs.c.Send(cmd) if err = cs.checkOK(resp, err); err != nil { return err } // verify cryptogram and initialize session keys session, err := cs.initializeSession(resp, hostChallenge) if err != nil { return err } cs.sc = NewSecureChannel(session, cs.c) cs.session = session return nil } func (cs *CommandSet) initializeSession(resp *apdu.Response, hostChallenge []byte) (session *Session, err error) { keySets := []struct { name string key []byte }{ {"globalplatform", identifiers.GlobalPlatformDefaultKey}, {"keycard", identifiers.KeycardDevelopmentKey}, } for _, set := range keySets { logger.Debug("initialize session", "keys", set.name) keys := NewSCP02Keys(set.key, set.key) session, err = NewSession(keys, resp, hostChallenge) // good keys if err == nil { break } // try the next keys if err == errBadCryptogram { continue } // unexpected error return nil, err } return session, err } func (cs *CommandSet) externalAuthenticate() error { if cs.session == nil { return errors.New("session must be initialized using initializeUpdate") } encKey := cs.session.Keys().Enc() cmd, err := NewCommandExternalAuthenticate(encKey, cs.session.CardChallenge(), cs.session.HostChallenge()) if err != nil { return err } resp, err := cs.sc.Send(cmd) return cs.checkOK(resp, err) } func (cs *CommandSet) checkOK(resp *apdu.Response, err error, allowedResponses ...uint16) error { 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") } func generateHostChallenge() ([]byte, error) { c := make([]byte, 8) _, err := rand.Read(c) return c, err }