Skip to content

Commit 2baba6d

Browse files
committed
piv/internal/pcsc: add pure go pcsc client implementation
1 parent 48282da commit 2baba6d

File tree

4 files changed

+280
-0
lines changed

4 files changed

+280
-0
lines changed

piv/internal/pcsc/pcsc.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package pcsc
16+
17+
import (
18+
"bytes"
19+
"context"
20+
"encoding/binary"
21+
"fmt"
22+
"io"
23+
"net"
24+
"os"
25+
)
26+
27+
var nativeByteOrder binary.ByteOrder
28+
29+
const (
30+
pcscSocketPathEnv = "PCSCLITE_CSOCK_NAME"
31+
pcscSocketPath = "/run/pcscd/pcscd.comm"
32+
)
33+
34+
const (
35+
scardSSuccess = 0
36+
37+
// https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/winscard_msg.h#L76
38+
commandVersion = 0x11
39+
commandGetReadersState = 0x12
40+
)
41+
42+
type Client struct {
43+
conn net.Conn
44+
}
45+
46+
func (c *Client) Close() error {
47+
return c.conn.Close()
48+
}
49+
50+
func (c *Client) version() (major, minor int32, err error) {
51+
req := struct {
52+
Major int32
53+
Minor int32
54+
RV uint32
55+
}{4, 4, scardSSuccess}
56+
57+
var resp struct {
58+
Major int32
59+
Minor int32
60+
RV uint32
61+
}
62+
if err := c.sendMessage(commandVersion, req, &resp); err != nil {
63+
return 0, 0, fmt.Errorf("send message: %v", err)
64+
}
65+
if resp.RV != scardSSuccess {
66+
return 0, 0, fmt.Errorf("invalid response value: %x", resp.RV)
67+
}
68+
return resp.Major, resp.Minor, nil
69+
}
70+
71+
const (
72+
// https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/PCSC/pcsclite.h.in#L286
73+
maxReaderNameSize = 128
74+
// https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/PCSC/pcsclite.h.in#L59
75+
maxAttributeSize = 33
76+
// https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/PCSC/pcsclite.h.in#L284
77+
maxReaders = 16
78+
)
79+
80+
// https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/eventhandler.h#L52
81+
// typedef struct pubReaderStatesList
82+
// {
83+
// char readerName[MAX_READERNAME]; /**< reader name */
84+
// uint32_t eventCounter; /**< number of card events */
85+
// uint32_t readerState; /**< SCARD_* bit field */
86+
// int32_t readerSharing; /**< PCSCLITE_SHARING_* sharing status */
87+
//
88+
// UCHAR cardAtr[MAX_ATR_SIZE]; /**< ATR */
89+
// uint32_t cardAtrLength; /**< ATR length */
90+
// uint32_t cardProtocol; /**< SCARD_PROTOCOL_* value */
91+
// }
92+
// READER_STATE;
93+
94+
type readerState struct {
95+
Name [maxReaderNameSize]byte
96+
EventCounter uint32
97+
State uint32
98+
Sharing uint32
99+
100+
Attr [maxAttributeSize]byte
101+
AttrSize uint32
102+
Protocol uint32
103+
}
104+
105+
func (r readerState) name() string {
106+
if r.Name[0] == 0x00 {
107+
return ""
108+
}
109+
i := len(r.Name)
110+
for ; i > 0; i-- {
111+
if r.Name[i-1] != 0x00 {
112+
break
113+
}
114+
}
115+
return string(r.Name[:i])
116+
}
117+
118+
func (c *Client) readers() ([]string, error) {
119+
var resp [maxReaders]readerState
120+
121+
if err := c.sendMessage(commandGetReadersState, nil, &resp); err != nil {
122+
return nil, fmt.Errorf("send message: %v", err)
123+
}
124+
125+
var names []string
126+
for _, r := range resp {
127+
name := r.name()
128+
if name != "" {
129+
names = append(names, name)
130+
}
131+
}
132+
return names, nil
133+
}
134+
135+
func (c *Client) sendMessage(command uint32, req, resp interface{}) error {
136+
var data []byte
137+
if req != nil {
138+
b := &bytes.Buffer{}
139+
if err := binary.Write(b, nativeByteOrder, req); err != nil {
140+
return fmt.Errorf("marshaling message body: %v", err)
141+
}
142+
143+
size := uint32(b.Len())
144+
145+
data = make([]byte, b.Len()+4+4)
146+
nativeByteOrder.PutUint32(data[0:4], size)
147+
nativeByteOrder.PutUint32(data[4:8], command)
148+
io.ReadFull(b, data[8:])
149+
} else {
150+
data = make([]byte, 4+4)
151+
nativeByteOrder.PutUint32(data[0:4], 0)
152+
nativeByteOrder.PutUint32(data[4:8], command)
153+
}
154+
155+
if _, err := c.conn.Write(data); err != nil {
156+
return fmt.Errorf("write request bytes: %v", err)
157+
}
158+
159+
if err := binary.Read(c.conn, nativeByteOrder, resp); err != nil {
160+
return fmt.Errorf("read response: %v", err)
161+
}
162+
return nil
163+
}
164+
165+
type Config struct {
166+
SocketPath string
167+
}
168+
169+
func NewClient(ctx context.Context, c *Config) (*Client, error) {
170+
p := c.SocketPath
171+
if p == "" {
172+
p = os.Getenv(pcscSocketPathEnv)
173+
}
174+
if p == "" {
175+
p = pcscSocketPath
176+
}
177+
178+
var d net.Dialer
179+
conn, err := d.DialContext(ctx, "unix", p)
180+
if err != nil {
181+
return nil, fmt.Errorf("dial unix socket: %v", err)
182+
}
183+
return &Client{
184+
conn: conn,
185+
}, nil
186+
}

piv/internal/pcsc/pcsc_big.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// +build armbe arm64be ppc64 mips mips64 mips64p32 ppc sparc sparc64 s390 s390x
16+
17+
package pcsc
18+
19+
import "encoding/binary"
20+
21+
func init() {
22+
nativeByteOrder = binary.BigEndian
23+
}

piv/internal/pcsc/pcsc_little.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// +build 386 amd64 amd64p32 arm arm64 mipsle mis64le mips64p32le riscv riscv64
16+
17+
package pcsc
18+
19+
import "encoding/binary"
20+
21+
func init() {
22+
nativeByteOrder = binary.LittleEndian
23+
}

piv/internal/pcsc/pcsc_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package pcsc
16+
17+
import (
18+
"context"
19+
"testing"
20+
)
21+
22+
func TestVersion(t *testing.T) {
23+
c, err := NewClient(context.Background(), &Config{})
24+
if err != nil {
25+
t.Fatalf("create client: %v", err)
26+
}
27+
defer c.Close()
28+
29+
major, minor, err := c.version()
30+
if err != nil {
31+
t.Fatalf("getting client version: %v", err)
32+
}
33+
t.Log(major, minor)
34+
}
35+
36+
func TestListReaders(t *testing.T) {
37+
c, err := NewClient(context.Background(), &Config{})
38+
if err != nil {
39+
t.Fatalf("create client: %v", err)
40+
}
41+
defer c.Close()
42+
43+
readers, err := c.readers()
44+
if err != nil {
45+
t.Fatalf("listing readers: %v", err)
46+
}
47+
t.Log(readers)
48+
}

0 commit comments

Comments
 (0)