|
|
@@ -0,0 +1,371 @@
|
|
|
+// 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"
|
|
|
+ "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
|
|
|
+
|
|
|
+ readerStateDescriptors [MaxReaderStateDescriptors]ReaderState
|
|
|
+}
|
|
|
+
|
|
|
+// EstablishContext asks the PCSC daemon to create a context
|
|
|
+// handle for further communication with connected cards and
|
|
|
+// readers.
|
|
|
+func EstablishContext(scope uint32) (*Client, error) {
|
|
|
+ client := &Client{}
|
|
|
+
|
|
|
+ conn, err := clientSetupSession()
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ client.conn = conn
|
|
|
+
|
|
|
+ /* Exchange version information */
|
|
|
+ payload := make([]byte, 12)
|
|
|
+ binary.LittleEndian.PutUint32(payload, ProtocolVersionMajor)
|
|
|
+ binary.LittleEndian.PutUint32(payload[4:], ProtocolVersionMinor)
|
|
|
+ binary.LittleEndian.PutUint32(payload[8:], SCardSuccess)
|
|
|
+ err = messageSendWithHeader(CommandVersion, 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 {
|
|
|
+ return nil, fmt.Errorf("invalid response code: expected %d, got %d", SCardSuccess, code)
|
|
|
+ }
|
|
|
+ client.major = binary.LittleEndian.Uint32(response)
|
|
|
+ client.minor = binary.LittleEndian.Uint32(response[4:])
|
|
|
+ if client.major != ProtocolVersionMajor || client.minor != ProtocolVersionMinor {
|
|
|
+ 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)
|
|
|
+ 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 {
|
|
|
+ return nil, fmt.Errorf("invalid response code: expected %d, got %d", SCardSuccess, code)
|
|
|
+ }
|
|
|
+ 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 {
|
|
|
+ data := [8]byte{}
|
|
|
+ binary.LittleEndian.PutUint32(data[:], client.ctx)
|
|
|
+ binary.LittleEndian.PutUint32(data[4:], SCardSuccess)
|
|
|
+ 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 {
|
|
|
+ return fmt.Errorf("invalid return code: %x", code)
|
|
|
+ }
|
|
|
+
|
|
|
+ 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) {
|
|
|
+ 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) {
|
|
|
+ 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)
|
|
|
+
|
|
|
+ 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 {
|
|
|
+ return nil, fmt.Errorf("invalid return code: %x", code)
|
|
|
+ }
|
|
|
+ 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) {
|
|
|
+ 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)
|
|
|
+ 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 {
|
|
|
+ return nil, nil, fmt.Errorf("invalid return code: %x", code)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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 {
|
|
|
+ data := [12]byte{}
|
|
|
+ binary.LittleEndian.PutUint32(data[:], card.handle)
|
|
|
+ binary.LittleEndian.PutUint32(data[4:], disposition)
|
|
|
+ binary.LittleEndian.PutUint32(data[8:], SCardSuccess)
|
|
|
+ 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 {
|
|
|
+ return fmt.Errorf("invalid return code: %x", code)
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|