From fadc40ba27a3adffae9a531cb2c616d4d1cc777b Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Thu, 14 Jan 2021 12:34:35 -0800 Subject: [PATCH] piv/internal/pcsc: add pure go pcsc client implementation --- piv/internal/pcsc/pcsc.go | 186 +++++++++++++++++++++++++++++++ piv/internal/pcsc/pcsc_big.go | 23 ++++ piv/internal/pcsc/pcsc_little.go | 23 ++++ piv/internal/pcsc/pcsc_test.go | 48 ++++++++ 4 files changed, 280 insertions(+) create mode 100644 piv/internal/pcsc/pcsc.go create mode 100644 piv/internal/pcsc/pcsc_big.go create mode 100644 piv/internal/pcsc/pcsc_little.go create mode 100644 piv/internal/pcsc/pcsc_test.go diff --git a/piv/internal/pcsc/pcsc.go b/piv/internal/pcsc/pcsc.go new file mode 100644 index 0000000..bed8a20 --- /dev/null +++ b/piv/internal/pcsc/pcsc.go @@ -0,0 +1,186 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pcsc + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "io" + "net" + "os" +) + +var nativeByteOrder binary.ByteOrder + +const ( + pcscSocketPathEnv = "PCSCLITE_CSOCK_NAME" + pcscSocketPath = "/run/pcscd/pcscd.comm" +) + +const ( + scardSSuccess = 0 + + // https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/winscard_msg.h#L76 + commandVersion = 0x11 + commandGetReadersState = 0x12 +) + +type Client struct { + conn net.Conn +} + +func (c *Client) Close() error { + return c.conn.Close() +} + +func (c *Client) version() (major, minor int32, err error) { + req := struct { + Major int32 + Minor int32 + RV uint32 + }{4, 4, scardSSuccess} + + var resp struct { + Major int32 + Minor int32 + RV uint32 + } + if err := c.sendMessage(commandVersion, req, &resp); err != nil { + return 0, 0, fmt.Errorf("send message: %v", err) + } + if resp.RV != scardSSuccess { + return 0, 0, fmt.Errorf("invalid response value: %x", resp.RV) + } + return resp.Major, resp.Minor, nil +} + +const ( + // https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/PCSC/pcsclite.h.in#L286 + maxReaderNameSize = 128 + // https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/PCSC/pcsclite.h.in#L59 + maxAttributeSize = 33 + // https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/PCSC/pcsclite.h.in#L284 + maxReaders = 16 +) + +// https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/eventhandler.h#L52 +// typedef struct pubReaderStatesList +// { +// char readerName[MAX_READERNAME]; /**< reader name */ +// uint32_t eventCounter; /**< number of card events */ +// uint32_t readerState; /**< SCARD_* bit field */ +// int32_t readerSharing; /**< PCSCLITE_SHARING_* sharing status */ +// +// UCHAR cardAtr[MAX_ATR_SIZE]; /**< ATR */ +// uint32_t cardAtrLength; /**< ATR length */ +// uint32_t cardProtocol; /**< SCARD_PROTOCOL_* value */ +// } +// READER_STATE; + +type readerState struct { + Name [maxReaderNameSize]byte + EventCounter uint32 + State uint32 + Sharing uint32 + + Attr [maxAttributeSize]byte + AttrSize uint32 + Protocol uint32 +} + +func (r readerState) name() string { + if r.Name[0] == 0x00 { + return "" + } + i := len(r.Name) + for ; i > 0; i-- { + if r.Name[i-1] != 0x00 { + break + } + } + return string(r.Name[:i]) +} + +func (c *Client) readers() ([]string, error) { + var resp [maxReaders]readerState + + if err := c.sendMessage(commandGetReadersState, nil, &resp); err != nil { + return nil, fmt.Errorf("send message: %v", err) + } + + var names []string + for _, r := range resp { + name := r.name() + if name != "" { + names = append(names, name) + } + } + return names, nil +} + +func (c *Client) sendMessage(command uint32, req, resp interface{}) error { + var data []byte + if req != nil { + b := &bytes.Buffer{} + if err := binary.Write(b, nativeByteOrder, req); err != nil { + return fmt.Errorf("marshaling message body: %v", err) + } + + size := uint32(b.Len()) + + data = make([]byte, b.Len()+4+4) + nativeByteOrder.PutUint32(data[0:4], size) + nativeByteOrder.PutUint32(data[4:8], command) + io.ReadFull(b, data[8:]) + } else { + data = make([]byte, 4+4) + nativeByteOrder.PutUint32(data[0:4], 0) + nativeByteOrder.PutUint32(data[4:8], command) + } + + if _, err := c.conn.Write(data); err != nil { + return fmt.Errorf("write request bytes: %v", err) + } + + if err := binary.Read(c.conn, nativeByteOrder, resp); err != nil { + return fmt.Errorf("read response: %v", err) + } + return nil +} + +type Config struct { + SocketPath string +} + +func NewClient(ctx context.Context, c *Config) (*Client, error) { + p := c.SocketPath + if p == "" { + p = os.Getenv(pcscSocketPathEnv) + } + if p == "" { + p = pcscSocketPath + } + + var d net.Dialer + conn, err := d.DialContext(ctx, "unix", p) + if err != nil { + return nil, fmt.Errorf("dial unix socket: %v", err) + } + return &Client{ + conn: conn, + }, nil +} diff --git a/piv/internal/pcsc/pcsc_big.go b/piv/internal/pcsc/pcsc_big.go new file mode 100644 index 0000000..e2003d2 --- /dev/null +++ b/piv/internal/pcsc/pcsc_big.go @@ -0,0 +1,23 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build armbe arm64be ppc64 mips mips64 mips64p32 ppc sparc sparc64 s390 s390x + +package pcsc + +import "encoding/binary" + +func init() { + nativeByteOrder = binary.BigEndian +} diff --git a/piv/internal/pcsc/pcsc_little.go b/piv/internal/pcsc/pcsc_little.go new file mode 100644 index 0000000..ab37a85 --- /dev/null +++ b/piv/internal/pcsc/pcsc_little.go @@ -0,0 +1,23 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build 386 amd64 amd64p32 arm arm64 mipsle mis64le mips64p32le riscv riscv64 + +package pcsc + +import "encoding/binary" + +func init() { + nativeByteOrder = binary.LittleEndian +} diff --git a/piv/internal/pcsc/pcsc_test.go b/piv/internal/pcsc/pcsc_test.go new file mode 100644 index 0000000..46d1501 --- /dev/null +++ b/piv/internal/pcsc/pcsc_test.go @@ -0,0 +1,48 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pcsc + +import ( + "context" + "testing" +) + +func TestVersion(t *testing.T) { + c, err := NewClient(context.Background(), &Config{}) + if err != nil { + t.Fatalf("create client: %v", err) + } + defer c.Close() + + major, minor, err := c.version() + if err != nil { + t.Fatalf("getting client version: %v", err) + } + t.Log(major, minor) +} + +func TestListReaders(t *testing.T) { + c, err := NewClient(context.Background(), &Config{}) + if err != nil { + t.Fatalf("create client: %v", err) + } + defer c.Close() + + readers, err := c.readers() + if err != nil { + t.Fatalf("listing readers: %v", err) + } + t.Log(readers) +}