pcsc

package module
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 8, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

go-ctap/pcsc

Go Reference Go

go-ctap/pcsc is a cgo-free Go library for PC/SC smart-card readers. It supports Windows, macOS and Linux.

[!WARNING] This module is under active development. Its public API may change during v0.x.

Support

The package supports:

  • reader enumeration and connection events;
  • shared, exclusive and direct connections;
  • card status and ATR;
  • APDU and reader control commands;
  • transactions and reconnect;
  • reader and card attributes.

It uses the native PC/SC service on each platform:

Platform Backend
Windows winscard.dll
macOS PCSC.framework
Linux libpcsclite.so.1

The native library is loaded on the first PC/SC operation, not during package import. If it is not available, the operation returns pcsc.ErrUnavailable. This lets an application keep PC/SC support optional.

Installation

go get github.com/telesma-app/pcsc@latest

See go.mod for the required Go version.

Quick start

This example lists all readers and opens the first one:

package main

import (
	"fmt"
	"log"

	"github.com/telesma-app/pcsc"
)

func main() {
	var readerName string

	for reader, err := range pcsc.Enumerate() {
		if err != nil {
			log.Fatal(err)
		}

		fmt.Printf("reader: %s, state: 0x%x, ATR: %x\n",
			reader.Name, reader.State, reader.ATR)

		if readerName == "" {
			readerName = reader.Name
		}
	}

	if readerName == "" {
		log.Fatal("no PC/SC readers found")
	}

	card, err := pcsc.Open(readerName)
	if err != nil {
		log.Fatal(err)
	}
	defer card.Close()

	status, err := card.Status()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("protocol: %d, ATR: %x\n", status.Protocol, status.ATR)
}

Open uses a shared connection, offers T=0 and T=1, and leaves the card unchanged on Close. These settings can be changed with options:

card, err := pcsc.Open(
	readerName,
	pcsc.WithShareMode(pcsc.ShareModeExclusive),
	pcsc.WithPreferredProtocols(pcsc.ProtocolT1),
	pcsc.WithDisconnectDisposition(pcsc.DispositionResetCard),
)

In direct mode, the default preferred protocol is ProtocolUndefined.

Card operations

Transmit sends one raw APDU. The returned data includes the final SW1 and SW2 status bytes:

response, err := card.Transmit(ctx, []byte{
	0x00, 0x84, 0x00, 0x00, 0x08, // GET CHALLENGE
})
if err != nil {
	log.Fatal(err)
}
fmt.Printf("response: %x\n", response)

Control sends a reader-specific control command. The last argument is the expected response-buffer size:

response, err := card.Control(ctx, pcsc.ControlCode(3400), nil, 4096)

Control commands are not retried automatically because they may have side effects.

The package also provides:

  • Status for reader names, card state, protocol and ATR;
  • BeginTransaction and EndTransaction for exclusive card access;
  • Reconnect for new connection settings or card reset;
  • GetAttribute and SetAttribute for PC/SC attributes.

Connection events

Watch captures the current reader and card state in an initial snapshot. It then sends live changes in order:

  • DeviceEventReaderConnected;
  • DeviceEventReaderDisconnected;
  • DeviceEventCardInserted;
  • DeviceEventCardRemoved.

Each watcher has its own PC/SC context and must be closed:

watcher, err := pcsc.Watch()
if err != nil {
	log.Fatal(err)
}
defer watcher.Close()

for _, reader := range watcher.Snapshot().Readers {
	fmt.Printf("present: %s\n", reader.Name)
}

for event := range watcher.Listen() {
	fmt.Printf("%s: %s\n", event.Type, event.ReaderInfo.Name)
}

if err := watcher.Close(); err != nil {
	log.Printf("PC/SC watcher stopped: %v", err)
}

If the watcher stops because of a PC/SC error, Listen closes and Close returns that terminal error.

Transactions

Transactions stop other PC/SC applications from sending commands between your operations:

if err := card.BeginTransaction(ctx); err != nil {
	log.Fatal(err)
}
defer card.EndTransaction(pcsc.DispositionLeaveCard)

A transaction does not provide rollback. The disposition passed to EndTransaction only tells PC/SC what to do with the card.

Cancellation and concurrency

One Card runs one operation at a time. A call waiting for another operation can be canceled through its context.

On Windows, the package asks PC/SC to cancel an active card operation. On macOS and Linux, an APDU, control or transaction call may continue in the background after the Go method returns ctx.Err(). The card stays locked until the native call ends, and Close waits for it.

Do not retry a canceled APDU or control command automatically. The card or reader may already have processed it.

Errors

PC/SC errors support errors.Is with the package error values:

if errors.Is(err, pcsc.ErrNoCard) {
	log.Print("insert a smart card")
}

The original PC/SC result is available as *pcsc.Error. It contains the failed operation and the native result code:

var pcscErr *pcsc.Error
if errors.As(err, &pcscErr) {
	log.Printf("%s failed with 0x%08x", pcscErr.Operation, pcscErr.Code)
}

Common errors include ErrUnavailable, ErrNoReaders, ErrNoCard, ErrCardReset, ErrCardRemoved and ErrClosed.

Platform notes

  • CardState values are platform-specific. Windows uses an enum. macOS and Linux use a bitmask.
  • ProtocolRaw has a different native value on Windows and Unix systems.
  • Direct connections have small platform differences. Use the Go constants instead of hard-coded native values.
  • Linux needs pcsc-lite, a running pcscd service and permission to access the reader.

Scope

This package provides low-level PC/SC access. It does not provide:

  • ISO 7816 APDU encoding;
  • status-word processing;
  • GET RESPONSE or command chaining;
  • application protocols such as CTAP, PIV or OpenPGP.

Use go-ctap/ctap for FIDO2 commands and go-ctap/token2 for Token2 device support.

Testing

Run the normal test suite without hardware:

go test ./...

The generic hardware test is read-only and works with any connected smart card:

PCSC_TEST=1 go test -run TestPCSCLifecycle -v

The CTAP-over-NFC test needs a FIDO authenticator connected through a PC/SC reader:

PCSC_TEST_CTAPNFC=1 go test -run TestCTAPNFC -v

License

Apache License 2.0. See LICENSE.

Documentation

Index

Constants

View Source
const (
	AttributeAsyncProtocolTypes Attribute = attributeClassProtocol<<16 | 0x0120
	AttributeSyncProtocolTypes  Attribute = attributeClassProtocol<<16 | 0x0126

	AttributeDeviceFriendlyName = AttributeDeviceFriendlyNameA
	AttributeDeviceSystemName   = AttributeDeviceSystemNameA
)

Variables

View Source
var (
	// ErrUnavailable indicates that the platform PC/SC runtime could not be loaded.
	ErrUnavailable = errors.New("pcsc: unavailable")

	ErrInternalError          = errors.New("pcsc: internal error")
	ErrCanceled               = errors.New("pcsc: canceled")
	ErrInvalidHandle          = errors.New("pcsc: invalid handle")
	ErrInvalidParameter       = errors.New("pcsc: invalid parameter")
	ErrInvalidTarget          = errors.New("pcsc: invalid target")
	ErrNoMemory               = errors.New("pcsc: not enough memory")
	ErrWaitedTooLong          = errors.New("pcsc: waited too long")
	ErrTimeout                = errors.New("pcsc: timeout")
	ErrInsufficientBuffer     = errors.New("pcsc: insufficient buffer")
	ErrUnknownReader          = errors.New("pcsc: unknown reader")
	ErrNoCard                 = errors.New("pcsc: no smart card")
	ErrUnknownCard            = errors.New("pcsc: unknown card")
	ErrCannotDispose          = errors.New("pcsc: cannot dispose")
	ErrProtocolMismatch       = errors.New("pcsc: protocol mismatch")
	ErrNotReady               = errors.New("pcsc: not ready")
	ErrInvalidValue           = errors.New("pcsc: invalid value")
	ErrSystemCanceled         = errors.New("pcsc: canceled by system")
	ErrCommunication          = errors.New("pcsc: communication error")
	ErrUnknownError           = errors.New("pcsc: unknown internal error")
	ErrInvalidATR             = errors.New("pcsc: invalid ATR")
	ErrNotTransacted          = errors.New("pcsc: not transacted")
	ErrReaderUnavailable      = errors.New("pcsc: reader unavailable")
	ErrShutdown               = errors.New("pcsc: shutdown")
	ErrPCITooSmall            = errors.New("pcsc: PCI buffer too small")
	ErrReaderUnsupported      = errors.New("pcsc: reader unsupported")
	ErrDuplicateReader        = errors.New("pcsc: duplicate reader")
	ErrCardUnsupported        = errors.New("pcsc: card unsupported")
	ErrCardReset              = errors.New("pcsc: card reset")
	ErrCardRemoved            = errors.New("pcsc: card removed")
	ErrSharingViolation       = errors.New("pcsc: sharing violation")
	ErrNoService              = errors.New("pcsc: no service")
	ErrServiceStopped         = errors.New("pcsc: service stopped")
	ErrUnexpected             = errors.New("pcsc: unexpected error")
	ErrICCInstallation        = errors.New("pcsc: ICC installation unavailable")
	ErrICCCreationOrder       = errors.New("pcsc: ICC creation order unsupported")
	ErrUnsupportedFeature     = errors.New("pcsc: unsupported feature")
	ErrDirectoryNotFound      = errors.New("pcsc: directory not found")
	ErrFileNotFound           = errors.New("pcsc: file not found")
	ErrNotDirectory           = errors.New("pcsc: path is not a directory")
	ErrNotFile                = errors.New("pcsc: path is not a file")
	ErrNoAccess               = errors.New("pcsc: access denied")
	ErrWriteTooMany           = errors.New("pcsc: insufficient card memory")
	ErrBadSeek                = errors.New("pcsc: bad seek")
	ErrInvalidCHV             = errors.New("pcsc: invalid CHV")
	ErrUnknownResourceManager = errors.New("pcsc: unknown resource manager error")
	ErrNoSuchCertificate      = errors.New("pcsc: certificate not found")
	ErrCertificateUnavailable = errors.New("pcsc: certificate unavailable")
	ErrNoReaders              = errors.New("pcsc: no readers available")
	ErrCommunicationDataLost  = errors.New("pcsc: communication data lost")
	ErrNoKeyContainer         = errors.New("pcsc: key container not found")
	ErrServerTooBusy          = errors.New("pcsc: server too busy")
	ErrUnsupportedCard        = errors.New("pcsc: unsupported card")
	ErrUnresponsiveCard       = errors.New("pcsc: unresponsive card")
	ErrUnpoweredCard          = errors.New("pcsc: unpowered card")
	ErrSecurityViolation      = errors.New("pcsc: security violation")
	ErrWrongCHV               = errors.New("pcsc: wrong CHV")
	ErrCHVBlocked             = errors.New("pcsc: CHV blocked")
	ErrEndOfFile              = errors.New("pcsc: end of file")
	ErrCanceledByUser         = errors.New("pcsc: canceled by user")
	ErrCardNotAuthenticated   = errors.New("pcsc: card not authenticated")
	ErrClosed                 = errors.New("pcsc: card closed")
)

Functions

func ControlCode

func ControlCode(function uint32) uint32

ControlCode converts a PC/SC function number to the platform control code accepted by Card.Control.

func Enumerate

func Enumerate() iter.Seq2[*ReaderInfo, error]

Enumerate returns the currently registered PC/SC readers and their current card state.

Types

type Attribute

type Attribute uint32

Attribute identifies a PC/SC reader or card attribute.

const (
	AttributeVendorName        Attribute = attributeClassVendorInfo<<16 | 0x0100
	AttributeVendorIFDType     Attribute = attributeClassVendorInfo<<16 | 0x0101
	AttributeVendorIFDVersion  Attribute = attributeClassVendorInfo<<16 | 0x0102
	AttributeVendorIFDSerialNo Attribute = attributeClassVendorInfo<<16 | 0x0103
	AttributeChannelID         Attribute = attributeClassCommunication<<16 | 0x0110
	AttributeDefaultCLK        Attribute = attributeClassProtocol<<16 | 0x0121
	AttributeMaxCLK            Attribute = attributeClassProtocol<<16 | 0x0122
	AttributeDefaultDataRate   Attribute = attributeClassProtocol<<16 | 0x0123
	AttributeMaxDataRate       Attribute = attributeClassProtocol<<16 | 0x0124
	AttributeMaxIFSD           Attribute = attributeClassProtocol<<16 | 0x0125
)
const (
	AttributePowerManagementSupport Attribute = attributeClassPower<<16 | 0x0131
	AttributeUserToCardAuthDevice   Attribute = attributeClassSecurity<<16 | 0x0140
	AttributeUserAuthInputDevice    Attribute = attributeClassSecurity<<16 | 0x0142
	AttributeCharacteristics        Attribute = attributeClassMechanical<<16 | 0x0150
)
const (
	AttributeCurrentProtocolType Attribute = attributeClassIFDProtocol<<16 | 0x0201
	AttributeCurrentCLK          Attribute = attributeClassIFDProtocol<<16 | 0x0202
	AttributeCurrentF            Attribute = attributeClassIFDProtocol<<16 | 0x0203
	AttributeCurrentD            Attribute = attributeClassIFDProtocol<<16 | 0x0204
	AttributeCurrentN            Attribute = attributeClassIFDProtocol<<16 | 0x0205
	AttributeCurrentW            Attribute = attributeClassIFDProtocol<<16 | 0x0206
	AttributeCurrentIFSC         Attribute = attributeClassIFDProtocol<<16 | 0x0207
	AttributeCurrentIFSD         Attribute = attributeClassIFDProtocol<<16 | 0x0208
	AttributeCurrentBWT          Attribute = attributeClassIFDProtocol<<16 | 0x0209
	AttributeCurrentCWT          Attribute = attributeClassIFDProtocol<<16 | 0x020a
	AttributeCurrentEBCEncoding  Attribute = attributeClassIFDProtocol<<16 | 0x020b
	AttributeExtendedBWT         Attribute = attributeClassIFDProtocol<<16 | 0x020c
)
const (
	AttributeICCPresence        Attribute = attributeClassICCState<<16 | 0x0300
	AttributeICCInterfaceStatus Attribute = attributeClassICCState<<16 | 0x0301
	AttributeCurrentIOState     Attribute = attributeClassICCState<<16 | 0x0302
	AttributeATRString          Attribute = attributeClassICCState<<16 | 0x0303
	AttributeICCTypePerATR      Attribute = attributeClassICCState<<16 | 0x0304
)
const (
	AttributeESCReset       Attribute = attributeClassVendorDefined<<16 | 0xa000
	AttributeESCCancel      Attribute = attributeClassVendorDefined<<16 | 0xa003
	AttributeESCAuthRequest Attribute = attributeClassVendorDefined<<16 | 0xa005
	AttributeMaxInput       Attribute = attributeClassVendorDefined<<16 | 0xa007
)
const (
	AttributeDeviceUnit          Attribute = attributeClassSystem<<16 | 0x0001
	AttributeDeviceInUse         Attribute = attributeClassSystem<<16 | 0x0002
	AttributeDeviceFriendlyNameA Attribute = attributeClassSystem<<16 | 0x0003
	AttributeDeviceSystemNameA   Attribute = attributeClassSystem<<16 | 0x0004
	AttributeDeviceFriendlyNameW Attribute = attributeClassSystem<<16 | 0x0005
	AttributeDeviceSystemNameW   Attribute = attributeClassSystem<<16 | 0x0006

	// AttributeSupressT1IFSRequest retains the spelling used by the native
	// SCARD_ATTR_SUPRESS_T1_IFS_REQUEST constant.
	AttributeSupressT1IFSRequest Attribute = attributeClassSystem<<16 | 0x0007
)

type Card

type Card struct {
	// contains filtered or unexported fields
}

Card is a connection to a smart card. Context cancellation returns promptly, but the native operation may continue in the background on implementations that cannot cancel card operations, including pcsc-lite.

func Open

func Open(reader string, opts ...OpenOption) (*Card, error)

Open connects to the card in reader. By default it uses shared access, negotiates T=0 or T=1, and leaves the card powered when closed.

func (*Card) BeginTransaction

func (card *Card) BeginTransaction(ctx context.Context) error

BeginTransaction prevents other PC/SC applications from interleaving card operations until EndTransaction is called.

func (*Card) Close

func (card *Card) Close() error

Close prevents new operations, asks the native implementation to cancel an in-flight card operation when supported, waits for it to finish, disconnects from the card, and releases its PC/SC context.

func (*Card) Control

func (card *Card) Control(
	ctx context.Context,
	controlCode uint32,
	input []byte,
	responseSize int,
) ([]byte, error)

Control sends a reader-specific control request using an output buffer of responseSize bytes. It does not retry the request because a control operation may have side effects.

func (*Card) EndTransaction

func (card *Card) EndTransaction(disposition Disposition) error

EndTransaction releases a transaction and applies disposition to the card.

func (*Card) GetAttribute

func (card *Card) GetAttribute(attribute Attribute) ([]byte, error)

GetAttribute returns a reader or card attribute.

func (*Card) Interface

func (card *Card) Interface() CardInterface

Interface returns the best-effort physical interface of the connected card. Drivers that do not expose enough information produce CardInterfaceUnknown.

func (*Card) Reconnect

func (card *Card) Reconnect(
	ctx context.Context,
	shareMode ShareMode,
	preferredProtocols Protocol,
	initialization Disposition,
) (Protocol, error)

Reconnect changes the connection parameters and optionally resets the card.

func (*Card) SetAttribute

func (card *Card) SetAttribute(attribute Attribute, value []byte) error

SetAttribute sets a reader or card attribute.

func (*Card) Status

func (card *Card) Status() (*CardStatus, error)

func (*Card) Transmit

func (card *Card) Transmit(ctx context.Context, apdu []byte) ([]byte, error)

Transmit sends one raw APDU and returns the complete response, including SW1/SW2.

type CardInterface

type CardInterface string

CardInterface identifies how a card is coupled to its reader.

const (
	CardInterfaceUnknown     CardInterface = "unknown"
	CardInterfaceContact     CardInterface = "contact"
	CardInterfaceContactless CardInterface = "contactless"
)

type CardState

type CardState uint32

CardState is the state reported for an open card by SCardStatus. PC/SC uses platform-specific values for this type.

const (
	CardStateUnknown    CardState = 0x0001
	CardStateAbsent     CardState = 0x0002
	CardStatePresent    CardState = 0x0004
	CardStateSwallowed  CardState = 0x0008
	CardStatePowered    CardState = 0x0010
	CardStateNegotiable CardState = 0x0020
	CardStateSpecific   CardState = 0x0040
)

pcsc-lite and Apple's PCSC framework report SCardStatus states as a bitmask.

type CardStatus

type CardStatus struct {
	// ReaderNames contains every reader name or alias returned by SCardStatus.
	ReaderNames []string
	State       CardState
	Protocol    Protocol
	ATR         []byte
}

CardStatus is a snapshot of a connected card.

type DeviceEvent

type DeviceEvent struct {
	Type       DeviceEventType
	ReaderInfo *ReaderInfo
}

DeviceEvent describes a PC/SC reader or card-presence change.

type DeviceEventType

type DeviceEventType string
const (
	DeviceEventReaderConnected    DeviceEventType = "reader-connected"
	DeviceEventReaderDisconnected DeviceEventType = "reader-disconnected"
	DeviceEventCardInserted       DeviceEventType = "card-inserted"
	DeviceEventCardRemoved        DeviceEventType = "card-removed"
)

type Disposition

type Disposition uint32

Disposition controls what PC/SC does with a card when a connection or transaction ends.

const (
	DispositionLeaveCard   Disposition = 0
	DispositionResetCard   Disposition = 1
	DispositionUnpowerCard Disposition = 2
	DispositionEjectCard   Disposition = 3
)

type Error

type Error struct {
	Operation string
	Code      uint32
}

Error is a PC/SC return code annotated with the operation which failed.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Is

func (e *Error) Is(target error) bool

type OpenOption

type OpenOption func(*openOptions)

func WithDisconnectDisposition

func WithDisconnectDisposition(disposition Disposition) OpenOption

WithDisconnectDisposition configures what Close does with the card.

func WithPreferredProtocols

func WithPreferredProtocols(protocols Protocol) OpenOption

WithPreferredProtocols configures the protocols offered to SCardConnect.

func WithShareMode

func WithShareMode(mode ShareMode) OpenOption

WithShareMode configures how the card connection is shared with other applications.

type Protocol

type Protocol uint32

Protocol is the transport protocol negotiated with a card.

const (
	ProtocolUndefined Protocol = 0
	ProtocolT0        Protocol = 1
	ProtocolT1        Protocol = 2
)
const (
	ProtocolRaw Protocol = 0x0004
	ProtocolAny Protocol = ProtocolT0 | ProtocolT1
)
const ProtocolT15 Protocol = 0x0008

ProtocolT15 is the pcsc-lite T=15 protocol identifier.

type ReaderInfo

type ReaderInfo struct {
	Name  string
	State ReaderState
	ATR   []byte
}

ReaderInfo describes a PC/SC reader snapshot. Name is the reader name accepted by Open for this snapshot.

type ReaderState

type ReaderState uint32

ReaderState is the state bitmask used by SCardGetStatusChange.

const (
	ReaderStateUnaware     ReaderState = 0x0000
	ReaderStateIgnore      ReaderState = 0x0001
	ReaderStateChanged     ReaderState = 0x0002
	ReaderStateUnknown     ReaderState = 0x0004
	ReaderStateUnavailable ReaderState = 0x0008
	ReaderStateEmpty       ReaderState = 0x0010
	ReaderStatePresent     ReaderState = 0x0020
	ReaderStateATRMatch    ReaderState = 0x0040
	ReaderStateExclusive   ReaderState = 0x0080
	ReaderStateInUse       ReaderState = 0x0100
	ReaderStateMute        ReaderState = 0x0200
	ReaderStateUnpowered   ReaderState = 0x0400
)

type ShareMode

type ShareMode uint32

ShareMode controls how a card connection is shared with other applications.

const (
	ShareModeExclusive ShareMode = 1
	ShareModeShared    ShareMode = 2
	ShareModeDirect    ShareMode = 3
)

type Snapshot

type Snapshot struct {
	Readers []*ReaderInfo
}

Snapshot is the initial reader state captured by Watch.

type Watcher

type Watcher interface {
	Snapshot() Snapshot
	Listen() <-chan DeviceEvent
	Close() error
}

Watcher publishes reader and card changes which happen after Snapshot.

func Watch

func Watch() (Watcher, error)

Watch captures the current reader and card snapshot and then publishes later reader and card-presence changes. Each call creates an independent watcher.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL