12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
+ // Package pcsc implements the libpcsclite protocol for communicating with pcscd.
16
+ //
17
+ // This package is a pure Go implementation of libpcsclite, allowing piv-go to
18
+ // communicate directly with pcscd without cgo. This still relies on pcscd to
19
+ // communicate with the OS, and will be less reliable than linking against the
20
+ // shared libraries provided by the pcscd packages.
21
+ //
22
+ // This package will NOT work with the native Mac and Windows libraries, which
23
+ // are provided directly by the OS. Though pcsclite can be installed on Mac.
15
24
package pcsc
16
25
17
26
import (
18
27
"bytes"
19
28
"context"
20
29
"encoding/binary"
30
+ "encoding/hex"
21
31
"fmt"
22
32
"io"
23
33
"net"
24
34
"os"
25
35
)
26
36
37
+ // pcscd messages directly encode C structs over a unix domain socket. Determine
38
+ // the host's byte order with build tags so encoding/binary can replicate this
39
+ // behavior.
40
+ //
41
+ // https://groups.google.com/g/golang-nuts/c/3GEzwKfRRQw/m/ppkJKrT4cfAJ
27
42
var nativeByteOrder binary.ByteOrder
28
43
29
44
const (
@@ -32,40 +47,82 @@ const (
32
47
)
33
48
34
49
const (
35
- scardSSuccess = 0
50
+ // RVSuccess is a return value indicating the operation succeeded.
51
+ RVSuccess = 0
52
+
53
+ majorVersion = 4
54
+ minorVersion = 4
36
55
37
56
// https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/winscard_msg.h#L76
38
- commandVersion = 0x11
39
- commandGetReadersState = 0x12
57
+ commandEstablishContext = 0x01
58
+ commandReleaseContext = 0x02
59
+ commandConnect = 0x04
60
+ commandDisconnect = 0x05
61
+ commandBeginTransaction = 0x07
62
+ commandEndTransaction = 0x08
63
+ commandTransmit = 0x09
64
+ commandVersion = 0x11
65
+ commandGetReadersState = 0x12
66
+
67
+ // Context modes to be passed to NewContext.
68
+ //
69
+ // https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/PCSC/pcsclite.h.in#L248
70
+ Exclusive = 0x0001
71
+ Shared = 0x0002
72
+ Direct = 0x0003
73
+
74
+ // Different protocols that can be used when connecting to a card.
75
+ ProtocolT0 = 0x0001
76
+ ProtocolT1 = 0x0002
77
+ ProtocolRaw = 0x0004
78
+ ProtocolT15 = 0x0005
79
+
80
+ // Disconnect option.
81
+ LeaveCard = 0x0000
82
+ ResetCard = 0x0001
83
+ UnpowerCard = 0x0002
84
+ EjectCard = 0x0003
40
85
)
41
86
87
+ // RVError wraps an underlying PCSC error code.
88
+ type RVError struct {
89
+ RV uint32
90
+ }
91
+
92
+ // Error returns a string encoding of the error code. Note that human readable
93
+ // messages are not provided by this package, and are handled by the piv
94
+ // package instead.
95
+ func (r * RVError ) Error () string {
96
+ return fmt .Sprintf ("rv 0x%x" , r .RV )
97
+ }
98
+
99
+ // Client represents a connection with the pcscd process.
42
100
type Client struct {
43
101
conn net.Conn
44
102
}
45
103
104
+ // Close releases the underlying connection. It does not release any contexts
105
+ // which must be closed separately.
46
106
func (c * Client ) Close () error {
47
107
return c .conn .Close ()
48
108
}
49
109
50
- func (c * Client ) version () ( major , minor int32 , err error ) {
110
+ func (c * Client ) checkVersion () error {
51
111
req := struct {
52
112
Major int32
53
113
Minor int32
54
114
RV uint32
55
- }{4 , 4 , scardSSuccess }
56
-
57
- var resp struct {
58
- Major int32
59
- Minor int32
60
- RV uint32
115
+ }{majorVersion , minorVersion , RVSuccess }
116
+ if err := c .sendMessage (commandVersion , req , & req ); err != nil {
117
+ return fmt .Errorf ("send message: %v" , err )
61
118
}
62
- if err := c . sendMessage ( commandVersion , req , & resp ); err != nil {
63
- return 0 , 0 , fmt . Errorf ( "send message: %v" , err )
119
+ if req . RV != RVSuccess {
120
+ return & RVError { RV : req . RV }
64
121
}
65
- if resp . RV != scardSSuccess {
66
- return 0 , 0 , fmt .Errorf ("invalid response value : %x " , resp . RV )
122
+ if req . Major != majorVersion {
123
+ return fmt .Errorf ("unsupported major version of pcscd protocol : %d " , req . Major )
67
124
}
68
- return resp . Major , resp . Minor , nil
125
+ return nil
69
126
}
70
127
71
128
const (
@@ -77,25 +134,14 @@ const (
77
134
maxReaders = 16
78
135
)
79
136
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 */
137
+ // readerState holds metadata about a PCSC card.
87
138
//
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
-
139
+ // https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/eventhandler.h#L52
94
140
type readerState struct {
95
141
Name [maxReaderNameSize ]byte
96
142
EventCounter uint32
97
143
State uint32
98
- Sharing uint32
144
+ Sharing int32
99
145
100
146
Attr [maxAttributeSize ]byte
101
147
AttrSize uint32
@@ -115,11 +161,11 @@ func (r readerState) name() string {
115
161
return string (r .Name [:i ])
116
162
}
117
163
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 )
164
+ // Readers returns the names of all readers that are connected to the device.
165
+ func ( c * Client ) Readers () ([] string , error ) {
166
+ resp , err := c . readers ()
167
+ if err != nil {
168
+ return nil , err
123
169
}
124
170
125
171
var names []string
@@ -132,6 +178,182 @@ func (c *Client) readers() ([]string, error) {
132
178
return names , nil
133
179
}
134
180
181
+ func (c * Client ) readers () (states [maxReaders ]readerState , err error ) {
182
+ if err := c .sendMessage (commandGetReadersState , nil , & states ); err != nil {
183
+ return states , fmt .Errorf ("send message: %v" , err )
184
+ }
185
+ return states , nil
186
+ }
187
+
188
+ // https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/winscard_msg.h#L118
189
+ type establishRequest struct {
190
+ Scope uint32
191
+ Context uint32
192
+ RV uint32
193
+ }
194
+
195
+ // Context holds an open PCSC context, which is required to perform actions
196
+ // such as starting transactions or transmitting data to a card.
197
+ type Context struct {
198
+ client * Client
199
+ context uint32
200
+ }
201
+
202
+ // NewContext attempts to establish a context with the PCSC daemon. The returned
203
+ // context is only valid while the client is open.
204
+ func (c * Client ) NewContext () (* Context , error ) {
205
+ const scopeSystem = 0x0002
206
+ req := establishRequest {
207
+ Scope : scopeSystem ,
208
+ RV : RVSuccess ,
209
+ }
210
+ if err := c .sendMessage (commandEstablishContext , req , & req ); err != nil {
211
+ return nil , fmt .Errorf ("establish context: %v" , err )
212
+ }
213
+ if req .RV != RVSuccess {
214
+ return nil , & RVError {RV : req .RV }
215
+ }
216
+ return & Context {client : c , context : req .Context }, nil
217
+ }
218
+
219
+ // https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/winscard_msg.h#L118
220
+ type releaseRequest struct {
221
+ Context uint32
222
+ RV uint32
223
+ }
224
+
225
+ // Close releases the context with the PCSC daemon.
226
+ func (c * Context ) Close () error {
227
+ req := releaseRequest {
228
+ Context : c .context ,
229
+ RV : RVSuccess ,
230
+ }
231
+ if err := c .client .sendMessage (commandReleaseContext , req , & req ); err != nil {
232
+ return fmt .Errorf ("release context: %v" , err )
233
+ }
234
+ if req .RV != RVSuccess {
235
+ return & RVError {RV : req .RV }
236
+ }
237
+ return nil
238
+ }
239
+
240
+ // Connection represents a connection to a specific smartcard.
241
+ type Connection struct {
242
+ client * Client
243
+ context uint32
244
+ card int32
245
+ }
246
+
247
+ // https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/winscard_msg.h#L141
248
+ type connectRequest struct {
249
+ Context uint32
250
+ Reader [maxReaderNameSize ]byte
251
+ ShareMode uint32
252
+ PreferredProtocols uint32
253
+ Card int32
254
+ ActiveProtocols uint32
255
+ RV uint32
256
+ }
257
+
258
+ func (c * Context ) Connect (reader string , mode uint32 ) (* Connection , error ) {
259
+ req := connectRequest {
260
+ Context : c .context ,
261
+ ShareMode : Shared ,
262
+ PreferredProtocols : ProtocolT1 ,
263
+ RV : RVSuccess ,
264
+ }
265
+
266
+ if len (reader )+ 1 > maxReaderNameSize {
267
+ return nil , fmt .Errorf ("reader name too long" )
268
+ }
269
+ copy (req .Reader [:], []byte (reader ))
270
+
271
+ if err := c .client .sendMessage (commandConnect , req , & req ); err != nil {
272
+ return nil , fmt .Errorf ("send message: %v" , err )
273
+ }
274
+ if req .RV != RVSuccess {
275
+ return nil , & RVError {RV : req .RV }
276
+ }
277
+ if req .Card == 0 {
278
+ return nil , fmt .Errorf ("card returned no value" )
279
+ }
280
+ return & Connection {
281
+ client : c .client , context : c .context , card : req .Card ,
282
+ }, nil
283
+ }
284
+
285
+ //https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/winscard_msg.h#L172
286
+ type disconnectRequest struct {
287
+ Card int32
288
+ Disposition uint32
289
+ RV uint32
290
+ }
291
+
292
+ func (c * Connection ) Close () error {
293
+ req := disconnectRequest {
294
+ Card : c .card ,
295
+ Disposition : LeaveCard ,
296
+ RV : RVSuccess ,
297
+ }
298
+
299
+ if err := c .client .sendMessage (commandDisconnect , req , & req ); err != nil {
300
+ return fmt .Errorf ("send message: %v" , err )
301
+ }
302
+ if req .RV != RVSuccess {
303
+ return & RVError {RV : req .RV }
304
+ }
305
+ return nil
306
+ }
307
+
308
+ // https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/winscard_msg.h#L184
309
+ type beginRequest struct {
310
+ Card int32
311
+ RV uint32
312
+ }
313
+
314
+ // BeginTransaction is called before transmitting data to the card.
315
+ func (c * Connection ) BeginTransaction () error {
316
+ req := beginRequest {
317
+ Card : c .card ,
318
+ RV : RVSuccess ,
319
+ }
320
+
321
+ if err := c .client .sendMessage (commandBeginTransaction , req , & req ); err != nil {
322
+ return fmt .Errorf ("send message: %v" , err )
323
+ }
324
+ if req .RV != RVSuccess {
325
+ return & RVError {RV : req .RV }
326
+ }
327
+ return nil
328
+ }
329
+
330
+ // https://github.com/LudovicRousseau/PCSC/blob/1.9.0/src/winscard_msg.h#L195
331
+ type endRequest struct {
332
+ Card int32
333
+ Disposition uint32
334
+ RV uint32
335
+ }
336
+
337
+ func (c * Connection ) EndTransaction () error {
338
+ req := endRequest {
339
+ Card : c .card ,
340
+ Disposition : LeaveCard ,
341
+ RV : RVSuccess ,
342
+ }
343
+
344
+ if err := c .client .sendMessage (commandEndTransaction , req , & req ); err != nil {
345
+ return fmt .Errorf ("send message: %v" , err )
346
+ }
347
+ if req .RV != RVSuccess {
348
+ return & RVError {RV : req .RV }
349
+ }
350
+ return nil
351
+ }
352
+
353
+ func (c * Connection ) Transmit (b []byte ) ([]byte , error ) {
354
+ return nil , nil
355
+ }
356
+
135
357
func (c * Client ) sendMessage (command uint32 , req , resp interface {}) error {
136
358
var data []byte
137
359
if req != nil {
@@ -156,16 +378,33 @@ func (c *Client) sendMessage(command uint32, req, resp interface{}) error {
156
378
return fmt .Errorf ("write request bytes: %v" , err )
157
379
}
158
380
159
- if err := binary .Read (c .conn , nativeByteOrder , resp ); err != nil {
381
+ respData := & bytes.Buffer {}
382
+ if err := binary .Read (io .TeeReader (c .conn , respData ), nativeByteOrder , resp ); err != nil {
160
383
return fmt .Errorf ("read response: %v" , err )
161
384
}
385
+
386
+ fmt .Fprintln (os .Stderr , "Request:" )
387
+ fmt .Fprintln (os .Stderr , hex .Dump (data [8 :]))
388
+
389
+ fmt .Fprintln (os .Stderr , "Response:" )
390
+ fmt .Fprintln (os .Stderr , hex .Dump (respData .Bytes ()))
162
391
return nil
163
392
}
164
393
394
+ // Config is used to modify client behavior.
165
395
type Config struct {
396
+ // SocketPath can be used to override a path to the pcscd socket. This field
397
+ // is generally not required unless pcscd has been compiled with modified
398
+ // options.
399
+ //
400
+ // This value defaults to the pcsclite behavior, preferring the value of the
401
+ // PCSCLITE_CSOCK_NAME environment variable then defaulting to
402
+ // "/run/pcscd/pcscd.comm".
166
403
SocketPath string
167
404
}
168
405
406
+ // NewClient attempts to initialize a connection with pcscd. The context is used
407
+ // for dialing the unix domain socket.
169
408
func NewClient (ctx context.Context , c * Config ) (* Client , error ) {
170
409
p := c .SocketPath
171
410
if p == "" {
@@ -180,7 +419,10 @@ func NewClient(ctx context.Context, c *Config) (*Client, error) {
180
419
if err != nil {
181
420
return nil , fmt .Errorf ("dial unix socket: %v" , err )
182
421
}
183
- return & Client {
184
- conn : conn ,
185
- }, nil
422
+ client := & Client {conn : conn }
423
+ if err := client .checkVersion (); err != nil {
424
+ client .Close ()
425
+ return nil , err
426
+ }
427
+ return client , nil
186
428
}
0 commit comments