| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- // BSD 3-Clause License
- //
- // Copyright (c) 2019, Guillaume Ballet
- // All rights reserved.
- //
- // Redistribution and use in source and binary forms, with or without
- // modification, are permitted provided that the following conditions are met:
- //
- // * Redistributions of source code must retain the above copyright notice, this
- // list of conditions and the following disclaimer.
- //
- // * Redistributions in binary form must reproduce the above copyright notice,
- // this list of conditions and the following disclaimer in the documentation
- // and/or other materials provided with the distribution.
- //
- // * Neither the name of the copyright holder nor the names of its
- // contributors may be used to endorse or promote products derived from
- // this software without specific prior written permission.
- //
- // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- package pcsc
- import (
- "encoding/binary"
- "fmt"
- "net"
- "sync"
- "unsafe"
- )
- // Client contains all the information needed to establish
- // and maintain a connection to the deamon/card.
- type Client struct {
- conn net.Conn
- minor uint32
- major uint32
- ctx uint32
- mutex sync.Mutex
- readerStateDescriptors [MaxReaderStateDescriptors]ReaderState
- }
- // EstablishContext asks the PCSC daemon to create a context
- // handle for further communication with connected cards and
- // readers.
- func EstablishContext(path string, scope uint32) (*Client, error) {
- client := &Client{}
- conn, err := clientSetupSession(path)
- if err != nil {
- return nil, err
- }
- client.conn = conn
- payload := make([]byte, 12)
- response := make([]byte, 12)
- var code uint32
- var minor uint32
- for minor = ProtocolVersionMinor; minor <= ProtocolVersionMinor+1; minor++ {
- /* Exchange version information */
- binary.LittleEndian.PutUint32(payload, ProtocolVersionMajor)
- binary.LittleEndian.PutUint32(payload[4:], minor)
- binary.LittleEndian.PutUint32(payload[8:], SCardSuccess.Code())
- err = messageSendWithHeader(CommandVersion, conn, payload)
- if err != nil {
- return nil, err
- }
- n, err := conn.Read(response)
- if err != nil {
- return nil, err
- }
- if n != len(response) {
- return nil, fmt.Errorf("invalid response length: expected %d, got %d", len(response), n)
- }
- code = binary.LittleEndian.Uint32(response[8:])
- if code != SCardSuccess.Code() {
- continue
- }
- client.major = binary.LittleEndian.Uint32(response)
- client.minor = binary.LittleEndian.Uint32(response[4:])
- if client.major != ProtocolVersionMajor || client.minor != minor {
- continue
- }
- break
- }
- if code != SCardSuccess.Code() {
- return nil, fmt.Errorf("invalid response code: expected %d, got %d (%v)", SCardSuccess, code, ErrorCode(code).Error())
- }
- if client.major != ProtocolVersionMajor || (client.minor != minor && client.minor+1 != minor) {
- return nil, fmt.Errorf("invalid version found: expected %d.%d, got %d.%d", ProtocolVersionMajor, ProtocolVersionMinor, client.major, client.minor)
- }
- /* Establish the context proper */
- binary.LittleEndian.PutUint32(payload, scope)
- binary.LittleEndian.PutUint32(payload[4:], 0)
- binary.LittleEndian.PutUint32(payload[8:], SCardSuccess.Code())
- err = messageSendWithHeader(SCardEstablishContext, conn, payload)
- if err != nil {
- return nil, err
- }
- response = make([]byte, 12)
- n, err := conn.Read(response)
- if err != nil {
- return nil, err
- }
- if n != len(response) {
- return nil, fmt.Errorf("invalid response length: expected %d, got %d", len(response), n)
- }
- code = binary.LittleEndian.Uint32(response[8:])
- if code != SCardSuccess.Code() {
- return nil, fmt.Errorf("invalid response code: expected %d, got %d (%v)", SCardSuccess, code, ErrorCode(code).Error())
- }
- client.ctx = binary.LittleEndian.Uint32(response[4:])
- return client, nil
- }
- // ReleaseContext tells the daemon that the client will no longer
- // need the context.
- func (client *Client) ReleaseContext() error {
- client.mutex.Lock()
- defer client.mutex.Unlock()
- data := [8]byte{}
- binary.LittleEndian.PutUint32(data[:], client.ctx)
- binary.LittleEndian.PutUint32(data[4:], SCardSuccess.Code())
- err := messageSendWithHeader(SCardReleaseContext, client.conn, data[:])
- if err != nil {
- return err
- }
- total := 0
- for total < len(data) {
- n, err := client.conn.Read(data[total:])
- if err != nil {
- return err
- }
- total += n
- }
- code := binary.LittleEndian.Uint32(data[4:])
- if code != SCardSuccess.Code() {
- return fmt.Errorf("invalid return code: %x, %v", code, ErrorCode(code).Error())
- }
- return nil
- }
- // Constants related to the reader state structure
- const (
- ReaderStateNameLength = 128
- ReaderStateMaxAtrSizeLength = 33
- // NOTE: ATR is 32-byte aligned in the C version, which means it's
- // actually 36 byte long and not 33.
- ReaderStateDescriptorLength = ReaderStateNameLength + ReaderStateMaxAtrSizeLength + 5*4 + 3
- MaxReaderStateDescriptors = 16
- )
- // ReaderState represent the state of a single reader, as reported
- // by the PCSC daemon.
- type ReaderState struct {
- Name string /* reader name */
- eventCounter uint32 /* number of card events */
- readerState uint32 /* SCARD_* bit field */
- readerSharing uint32 /* PCSCLITE_SHARING_* sharing status */
- cardAtr [ReaderStateMaxAtrSizeLength]byte /* ATR */
- cardAtrLength uint32 /* ATR length */
- cardProtocol uint32 /* SCARD_PROTOCOL_* value */
- }
- func getReaderState(data []byte) (ReaderState, error) {
- ret := ReaderState{}
- if len(data) < ReaderStateDescriptorLength {
- return ret, fmt.Errorf("could not unmarshall data of length %d < %d", len(data), ReaderStateDescriptorLength)
- }
- ret.Name = string(data[:ReaderStateNameLength])
- ret.eventCounter = binary.LittleEndian.Uint32(data[unsafe.Offsetof(ret.eventCounter):])
- ret.readerState = binary.LittleEndian.Uint32(data[unsafe.Offsetof(ret.readerState):])
- ret.readerSharing = binary.LittleEndian.Uint32(data[unsafe.Offsetof(ret.readerSharing):])
- copy(ret.cardAtr[:], data[unsafe.Offsetof(ret.cardAtr):unsafe.Offsetof(ret.cardAtr)+ReaderStateMaxAtrSizeLength])
- ret.cardAtrLength = binary.LittleEndian.Uint32(data[unsafe.Offsetof(ret.cardAtrLength):])
- ret.cardProtocol = binary.LittleEndian.Uint32(data[unsafe.Offsetof(ret.cardProtocol):])
- return ret, nil
- }
- // ListReaders gets the list of readers from the daemon
- func (client *Client) ListReaders() ([]string, error) {
- client.mutex.Lock()
- defer client.mutex.Unlock()
- err := messageSendWithHeader(CommandGetReaderState, client.conn, []byte{})
- if err != nil {
- return nil, err
- }
- response := make([]byte, ReaderStateDescriptorLength*MaxReaderStateDescriptors)
- total := 0
- for total < len(response) {
- n, err := client.conn.Read(response[total:])
- if err != nil {
- return nil, err
- }
- total += n
- }
- var names []string
- for i := range client.readerStateDescriptors {
- desc, err := getReaderState(response[i*ReaderStateDescriptorLength:])
- if err != nil {
- return nil, err
- }
- client.readerStateDescriptors[i] = desc
- if desc.Name[0] == 0 {
- break
- }
- names = append(names, desc.Name)
- }
- return names, nil
- }
- // Offsets into the Connect request/response packet
- const (
- SCardConnectReaderNameOffset = 4
- SCardConnectShareModeOffset = SCardConnectReaderNameOffset + ReaderStateNameLength
- SCardConnectPreferredProtocolOffset = SCardConnectShareModeOffset + 4
- SCardConnectReturnValueOffset = SCardConnectPreferredProtocolOffset + 12
- )
- // Card represents the connection to a card
- type Card struct {
- handle uint32
- activeProto uint32
- client *Client
- }
- // Connect asks the daemon to connect to the card
- func (client *Client) Connect(name string, shareMode uint32, preferredProtocol uint32) (*Card, error) {
- client.mutex.Lock()
- defer client.mutex.Unlock()
- request := make([]byte, ReaderStateNameLength+4*6)
- binary.LittleEndian.PutUint32(request, client.ctx)
- copy(request[SCardConnectReaderNameOffset:], []byte(name))
- binary.LittleEndian.PutUint32(request[SCardConnectShareModeOffset:], shareMode)
- binary.LittleEndian.PutUint32(request[SCardConnectPreferredProtocolOffset:], preferredProtocol)
- binary.LittleEndian.PutUint32(request[SCardConnectReturnValueOffset:], SCardSuccess.Code())
- err := messageSendWithHeader(SCardConnect, client.conn, request)
- if err != nil {
- return nil, err
- }
- response := make([]byte, ReaderStateNameLength+4*6)
- total := 0
- for total < len(response) {
- n, err := client.conn.Read(response[total:])
- if err != nil {
- return nil, err
- }
- // fmt.Println("total, n", total, n, response)
- total += n
- }
- code := binary.LittleEndian.Uint32(response[148:])
- if code != SCardSuccess.Code() {
- return nil, fmt.Errorf("invalid return code: %x (%v)", code, ErrorCode(code).Error())
- }
- handle := binary.LittleEndian.Uint32(response[140:])
- active := binary.LittleEndian.Uint32(response[SCardConnectPreferredProtocolOffset:])
- return &Card{handle: handle, activeProto: active, client: client}, nil
- }
- /**
- * @brief contained in \ref SCARD_TRANSMIT Messages.
- *
- * These data are passed throw the field \c sharedSegmentMsg.data.
- */
- type transmit struct {
- hCard uint32
- ioSendPciProtocol uint32
- ioSendPciLength uint32
- cbSendLength uint32
- ioRecvPciProtocol uint32
- ioRecvPciLength uint32
- pcbRecvLength uint32
- rv uint32
- }
- // SCardIoRequest contains the info needed for performing an IO request
- type SCardIoRequest struct {
- proto uint32
- length uint32
- }
- const (
- TransmitRequestLength = 32
- )
- // Transmit sends request data to a card and returns the response
- func (card *Card) Transmit(adpu []byte) ([]byte, *SCardIoRequest, error) {
- card.client.mutex.Lock()
- defer card.client.mutex.Unlock()
- request := [TransmitRequestLength]byte{}
- binary.LittleEndian.PutUint32(request[:], card.handle)
- binary.LittleEndian.PutUint32(request[4:] /*card.activeProto*/, 2)
- binary.LittleEndian.PutUint32(request[8:], 8)
- binary.LittleEndian.PutUint32(request[12:], uint32(len(adpu)))
- binary.LittleEndian.PutUint32(request[16:], 0)
- binary.LittleEndian.PutUint32(request[20:], 0)
- binary.LittleEndian.PutUint32(request[24:], 0x10000)
- binary.LittleEndian.PutUint32(request[28:], SCardSuccess.Code())
- err := messageSendWithHeader(SCardTransmit, card.client.conn, request[:])
- if err != nil {
- return nil, nil, err
- }
- // Add the ADPU payload after the transmit descriptor
- n, err := card.client.conn.Write(adpu)
- if err != nil {
- return nil, nil, err
- }
- if n != len(adpu) {
- return nil, nil, fmt.Errorf("Invalid number of bytes written: expected %d, got %d", len(adpu), n)
- }
- response := [TransmitRequestLength]byte{}
- total := 0
- for total < len(response) {
- n, err = card.client.conn.Read(response[total:])
- if err != nil {
- return nil, nil, err
- }
- total += n
- }
- code := binary.LittleEndian.Uint32(response[28:])
- if code != SCardSuccess.Code() {
- return nil, nil, fmt.Errorf("invalid return code: %x (%v)", code, ErrorCode(code).Error())
- }
- // Recover the response data
- recvProto := binary.LittleEndian.Uint32(response[16:])
- recvLength := binary.LittleEndian.Uint32(response[20:])
- recv := &SCardIoRequest{proto: recvProto, length: recvLength}
- recvLength = binary.LittleEndian.Uint32(response[24:])
- recvData := make([]byte, recvLength)
- total = 0
- for uint32(total) < recvLength {
- n, err := card.client.conn.Read(recvData[total:])
- if err != nil {
- return nil, nil, err
- }
- total += n
- }
- return recvData, recv, nil
- }
- // Disconnect tells the PCSC daemon that the client is no longer
- // interested in communicating with the card.
- func (card *Card) Disconnect(disposition uint32) error {
- card.client.mutex.Lock()
- defer card.client.mutex.Unlock()
- data := [12]byte{}
- binary.LittleEndian.PutUint32(data[:], card.handle)
- binary.LittleEndian.PutUint32(data[4:], disposition)
- binary.LittleEndian.PutUint32(data[8:], SCardSuccess.Code())
- err := messageSendWithHeader(SCardDisConnect, card.client.conn, data[:])
- if err != nil {
- return err
- }
- total := 0
- for total < len(data) {
- n, err := card.client.conn.Read(data[total:])
- if err != nil {
- return err
- }
- total += n
- }
- code := binary.LittleEndian.Uint32(data[8:])
- if code != SCardSuccess.Code() {
- return fmt.Errorf("invalid return code: %x (%v)", code, ErrorCode(code).Error())
- }
- return nil
- }
|