From aefeeb4a3d6eb9d292b1f79b2d4848d30b465cfa Mon Sep 17 00:00:00 2001 From: Les Aker Date: Tue, 3 Jan 2023 01:32:57 -0500 Subject: [PATCH 1/4] allow serial number to be set --- ykoath.go | 58 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/ykoath.go b/ykoath.go index ec54197..f0f9205 100644 --- a/ykoath.go +++ b/ykoath.go @@ -1,7 +1,9 @@ package ykoath import ( + "encoding/binary" "fmt" + "strconv" "strings" "time" @@ -37,12 +39,17 @@ const ( errFailedToListSuitableReader = "no suitable reader found (out of %d readers)" errFailedToReleaseContext = "failed to release context" errFailedToTransmit = "failed to transmit APDU" + errFailedToReadSerial = "failed to read serial" errUnknownTag = "unknown tag (%x)" ) // New initializes a new OATH session func New() (*OATH, error) { + return NewFromSerial("") +} +// NewFromSerial creates an OATH session for a specific key +func NewFromSerial(serial string) (*OATH, error) { context, err := scard.EstablishContext() if err != nil { @@ -57,26 +64,37 @@ func New() (*OATH, error) { for _, reader := range readers { - if strings.Contains(strings.ToLower(reader), "yubikey") { + if !strings.Contains(strings.ToLower(reader), "yubikey") { + continue + } - card, err := context.Connect(reader, scard.ShareShared, scard.ProtocolAny) + card, err := context.Connect(reader, scard.ShareShared, scard.ProtocolAny) - if err != nil { - return nil, errors.Wrapf(err, errFailedToConnect) - } + if err != nil { + return nil, errors.Wrapf(err, errFailedToConnect) + } - return &OATH{ - card: card, - Clock: time.Now, - context: context, - }, nil + o := OATH{ + card: card, + Clock: time.Now, + context: context, + } + if serial == "" { + return &o, nil } + cardSerial, err := o.Serial() + if err != nil { + return nil, errors.Wrapf(err, errFailedToReadSerial) + } + + if serial == cardSerial { + return &o, nil + } } return nil, fmt.Errorf(errFailedToListSuitableReader, len(readers)) - } // Close terminates an OATH session @@ -94,6 +112,24 @@ func (o *OATH) Close() error { } +func (o *OATH) Serial() (string, error) { + _, err := o.card.Transmit([]byte{0x00, 0xA4, 0x04, 0x00, 0x08, 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17}) + if err != nil { + return "", err + } + resp, err := o.card.Transmit([]byte{0x00, 0x1d, 0x00, 0x00}) + if err != nil { + return "", err + } + kvs := read(resp[1 : len(resp)-2]) + for _, item := range kvs { + if item.tag == 0x02 { + return strconv.FormatUint(uint64(binary.BigEndian.Uint32(item.value)), 10), nil + } + } + return "", errors.Wrapf(fmt.Errorf("no serial tag found"), errFailedToReleaseContext) +} + // send sends an APDU to the card func (o *OATH) send(cla, ins, p1, p2 byte, data ...[]byte) (tvs, error) { From eb95436723d2d89818b7899a845c9660e3f3bdb3 Mon Sep 17 00:00:00 2001 From: Les Aker Date: Tue, 3 Jan 2023 22:42:20 -0500 Subject: [PATCH 2/4] support serial list --- ykoath.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ykoath.go b/ykoath.go index f0f9205..641e99a 100644 --- a/ykoath.go +++ b/ykoath.go @@ -50,6 +50,11 @@ func New() (*OATH, error) { // NewFromSerial creates an OATH session for a specific key func NewFromSerial(serial string) (*OATH, error) { + return NewFromSerialList([]string{serial}) +} + +// NewFromSerialList creates an OATH session from the first match found for a list of keys +func NewFromSerialList(serialList []string) (*OATH, error) { context, err := scard.EstablishContext() if err != nil { @@ -80,17 +85,19 @@ func NewFromSerial(serial string) (*OATH, error) { context: context, } - if serial == "" { + if len(serialList) == 0 { return &o, nil } - cardSerial, err := o.Serial() + serial, err := o.Serial() if err != nil { return nil, errors.Wrapf(err, errFailedToReadSerial) } - if serial == cardSerial { - return &o, nil + for _, match := range serialList { + if serial == match { + return &o, nil + } } } From 222a791b1187103e6ed1dc5cced3d9546d69f3b8 Mon Sep 17 00:00:00 2001 From: Les Aker Date: Wed, 4 Jan 2023 21:49:52 -0500 Subject: [PATCH 3/4] add NewSet --- ykoath.go | 58 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/ykoath.go b/ykoath.go index 641e99a..5e4377a 100644 --- a/ykoath.go +++ b/ykoath.go @@ -55,53 +55,63 @@ func NewFromSerial(serial string) (*OATH, error) { // NewFromSerialList creates an OATH session from the first match found for a list of keys func NewFromSerialList(serialList []string) (*OATH, error) { + yubikeys, err := NewSet() + + if err != nil { + return nil, err + } + + for _, yubikey := range yubikeys { + if len(serialList) == 0 { + return yubikey, nil + } + + serial, err := yubikey.Serial() + if err != nil { + return nil, errors.Wrapf(err, errFailedToReadSerial) + } + + for _, match := range serialList { + if serial == match { + return &yubikey, nil + } + } + } + + return nil, fmt.Errorf(errFailedToListSuitableReader, len(readers)) +} + +// NewSet returns a slice of all Yubikeys on the system +func NewSet() ([]*OATH, error) { context, err := scard.EstablishContext() if err != nil { - return nil, errors.Wrapf(err, errFailedToEstablishContext) + return []*OATH{}, errors.Wrapf(err, errFailedToEstablishContext) } readers, err := context.ListReaders() if err != nil { - return nil, errors.Wrapf(err, errFailedToListReaders) + return []*OATH{}, errors.Wrapf(err, errFailedToListReaders) } - for _, reader := range readers { + var yubikeys []*OATH + for _, reader := range readers { if !strings.Contains(strings.ToLower(reader), "yubikey") { continue } - card, err := context.Connect(reader, scard.ShareShared, scard.ProtocolAny) - - if err != nil { - return nil, errors.Wrapf(err, errFailedToConnect) - } - o := OATH{ card: card, Clock: time.Now, context: context, } - if len(serialList) == 0 { - return &o, nil - } - - serial, err := o.Serial() - if err != nil { - return nil, errors.Wrapf(err, errFailedToReadSerial) - } - - for _, match := range serialList { - if serial == match { - return &o, nil - } - } + yubikeys := append(yubikeys, &o) } - return nil, fmt.Errorf(errFailedToListSuitableReader, len(readers)) + return yubikeys, nil } // Close terminates an OATH session From 5e8d38e463a0b1f027d1f159c9800836f2facdeb Mon Sep 17 00:00:00 2001 From: Les Aker Date: Wed, 4 Jan 2023 21:52:07 -0500 Subject: [PATCH 4/4] fix formatting --- ykoath.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ykoath.go b/ykoath.go index 5e4377a..a27ebe6 100644 --- a/ykoath.go +++ b/ykoath.go @@ -73,12 +73,12 @@ func NewFromSerialList(serialList []string) (*OATH, error) { for _, match := range serialList { if serial == match { - return &yubikey, nil + return yubikey, nil } } } - return nil, fmt.Errorf(errFailedToListSuitableReader, len(readers)) + return nil, fmt.Errorf(errFailedToListSuitableReader, len(yubikeys)) } // NewSet returns a slice of all Yubikeys on the system @@ -102,13 +102,19 @@ func NewSet() ([]*OATH, error) { continue } + card, err := context.Connect(reader, scard.ShareShared, scard.ProtocolAny) + + if err != nil { + return nil, errors.Wrapf(err, errFailedToConnect) + } + o := OATH{ card: card, Clock: time.Now, context: context, } - yubikeys := append(yubikeys, &o) + yubikeys = append(yubikeys, &o) } return yubikeys, nil