smartcard 0.3.2-x86-mswin32-60 → 0.4.0-x86-mswin32-60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +2 -0
- data/Manifest +17 -3
- data/ext/smartcard_pcsc/pcsc_constants.c +1 -1
- data/ext/smartcard_pcsc/pcsc_context.c +1 -1
- data/lib/smartcard/gp/gp_card_mixin.rb +28 -0
- data/lib/smartcard/iso/auto_configurator.rb +93 -0
- data/lib/smartcard/iso/iso_card_mixin.rb +92 -0
- data/lib/smartcard/iso/jcop_remote_protocol.rb +66 -0
- data/lib/smartcard/iso/jcop_remote_server.rb +178 -0
- data/lib/smartcard/iso/jcop_remote_transport.rb +72 -0
- data/lib/smartcard/iso/pcsc_transport.rb +94 -0
- data/lib/smartcard/iso/transport.rb +15 -0
- data/lib/smartcard/{pcsc_exception.rb → pcsc/pcsc_exception.rb} +0 -0
- data/lib/smartcard/pcsc.so +0 -0
- data/lib/smartcard.rb +14 -0
- data/smartcard.gemspec +6 -6
- data/test/gp/gp_card_mixin_test.rb +33 -0
- data/test/iso/auto_configurator_test.rb +119 -0
- data/test/iso/iso_card_mixin_test.rb +95 -0
- data/test/iso/jcop_remote_test.rb +87 -0
- data/test/{test_containers.rb → pcsc/containers_test.rb} +7 -1
- data/test/{test_smoke.rb → pcsc/smoke_test.rb} +7 -1
- data/tests/ts_pcsc_ext.rb +4 -0
- metadata +35 -11
data/CHANGELOG
CHANGED
data/Manifest
CHANGED
@@ -13,11 +13,25 @@ ext/smartcard_pcsc/pcsc_namespace.c
|
|
13
13
|
ext/smartcard_pcsc/pcsc_reader_states.c
|
14
14
|
ext/smartcard_pcsc/pcsc_surrogate_reader.h
|
15
15
|
ext/smartcard_pcsc/pcsc_surrogate_wintypes.h
|
16
|
-
lib/smartcard/
|
16
|
+
lib/smartcard/gp/gp_card_mixin.rb
|
17
|
+
lib/smartcard/iso/auto_configurator.rb
|
18
|
+
lib/smartcard/iso/iso_card_mixin.rb
|
19
|
+
lib/smartcard/iso/jcop_remote_protocol.rb
|
20
|
+
lib/smartcard/iso/jcop_remote_server.rb
|
21
|
+
lib/smartcard/iso/jcop_remote_transport.rb
|
22
|
+
lib/smartcard/iso/pcsc_transport.rb
|
23
|
+
lib/smartcard/iso/transport.rb
|
24
|
+
lib/smartcard/pcsc/pcsc_exception.rb
|
17
25
|
lib/smartcard.rb
|
18
26
|
LICENSE
|
19
27
|
Manifest
|
28
|
+
Rakefile
|
20
29
|
README
|
21
|
-
|
22
|
-
test/
|
30
|
+
smartcard.gemspec
|
31
|
+
test/gp/gp_card_mixin_test.rb
|
32
|
+
test/iso/auto_configurator_test.rb
|
33
|
+
test/iso/iso_card_mixin_test.rb
|
34
|
+
test/iso/jcop_remote_test.rb
|
35
|
+
test/pcsc/containers_test.rb
|
36
|
+
test/pcsc/smoke_test.rb
|
23
37
|
tests/ts_pcsc_ext.rb
|
@@ -119,7 +119,7 @@ void Init_PCSC_Consts() {
|
|
119
119
|
#endif /* SCARD_STATE_UNPOWERED */
|
120
120
|
|
121
121
|
/* INFINITE : Infinite timeout. */
|
122
|
-
rb_define_const(mPcsc, "INFINITE_TIMEOUT",
|
122
|
+
rb_define_const(mPcsc, "INFINITE_TIMEOUT", UINT2NUM(INFINITE));
|
123
123
|
|
124
124
|
|
125
125
|
/* SCARD_UNKNOWNU : Card is absent. */
|
@@ -225,7 +225,7 @@ static VALUE PCSC_Context_get_status_change(VALUE self, VALUE rbReaderStates, VA
|
|
225
225
|
if(TYPE(rbTimeout) == T_NIL || TYPE(rbTimeout) == T_FALSE)
|
226
226
|
timeout = INFINITE;
|
227
227
|
else
|
228
|
-
timeout =
|
228
|
+
timeout = NUM2UINT(rbTimeout);
|
229
229
|
|
230
230
|
if(_PCSC_ReaderStates_lowlevel_get(rbReaderStates, &reader_states, &reader_states_count) == 0)
|
231
231
|
rb_raise(rb_eArgError, "first parameter is not a ReaderStates instance or nil");
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# GlobalPlatform (formerly OpenPlatform) interface.
|
2
|
+
#
|
3
|
+
# Author:: Victor Costan
|
4
|
+
# Copyright:: Copyright (C) 2009 Massachusetts Institute of Technology
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
# :nodoc: namespace
|
8
|
+
module Smartcard::Gp
|
9
|
+
|
10
|
+
|
11
|
+
module GpCardMixin
|
12
|
+
include Smartcard::Iso::IsoCardMixin
|
13
|
+
|
14
|
+
# Selects a GlobalPlatform application.
|
15
|
+
def select_application(app_id)
|
16
|
+
iso_apdu! :ins => 0xA4, :p1 => 0x04, :p2 => 0x00, :data => app_id
|
17
|
+
end
|
18
|
+
|
19
|
+
# Installs a JavaCard applet on the JavaCard.
|
20
|
+
#
|
21
|
+
# This would be really, really nice to have. Sadly, it's a far away TBD right
|
22
|
+
# now.
|
23
|
+
def install_applet(cap_contents)
|
24
|
+
raise "Not implemeted; it'd be nice though, right?"
|
25
|
+
end
|
26
|
+
end # module GpCardMixin
|
27
|
+
|
28
|
+
end # namespace Smartcard::Gp
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# Automatic smart-card transport selection and configuration.
|
2
|
+
#
|
3
|
+
# Author:: Victor Costan
|
4
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
# :nodoc: namespace
|
8
|
+
module Smartcard::Iso
|
9
|
+
|
10
|
+
# Automatic configuration code.
|
11
|
+
module AutoConfigurator
|
12
|
+
# The name of the environment variable that might supply the transport
|
13
|
+
# configuration.
|
14
|
+
ENVIRONMENT_VARIABLE_NAME = 'SCARD_PORT'
|
15
|
+
|
16
|
+
# The default configurations to be tried if no configuration is specified.
|
17
|
+
DEFAULT_CONFIGURATIONS = [
|
18
|
+
{ :class => JcopRemoteTransport,
|
19
|
+
:opts => { :host => '127.0.0.1', :port => 8050} },
|
20
|
+
{ :class => PcscTransport, :opts => { :reader_index => 0 }}
|
21
|
+
]
|
22
|
+
|
23
|
+
# Creates a transport based on available configuration information.
|
24
|
+
def self.auto_transport
|
25
|
+
configuration = env_configuration
|
26
|
+
return try_transport(configuration) if configuration
|
27
|
+
|
28
|
+
DEFAULT_CONFIGURATIONS.each do |config|
|
29
|
+
transport = try_transport(config)
|
30
|
+
return transport if transport
|
31
|
+
end
|
32
|
+
return nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Retrieves transport configuration information from an environment variable.
|
36
|
+
#
|
37
|
+
# :call-seq:
|
38
|
+
# AutoConfigurator.env_configuration -> hash
|
39
|
+
#
|
40
|
+
# The returned configuration has the keys required by
|
41
|
+
# AutoConfigurator#try_transport
|
42
|
+
def self.env_configuration
|
43
|
+
return nil unless conf = ENV[ENVIRONMENT_VARIABLE_NAME]
|
44
|
+
|
45
|
+
case conf[0]
|
46
|
+
when ?:
|
47
|
+
# :8050 -- JCOP emulator at port 8050
|
48
|
+
transport_class = JcopRemoteTransport
|
49
|
+
transport_opts = { :host => '127.0.0.1' }
|
50
|
+
transport_opts[:port] = conf[1..-1].to_i
|
51
|
+
when ?@
|
52
|
+
# @127.0.0.1:8050 -- JCOP emulator at host 127.0.0.1 port 8050
|
53
|
+
transport_class = JcopRemoteTransport
|
54
|
+
port_index = conf.rindex(?:) || conf.length
|
55
|
+
transport_opts = { :host => conf[1...port_index] }
|
56
|
+
transport_opts[:port] = conf[(port_index + 1)..-1].to_i
|
57
|
+
when ?#
|
58
|
+
# #2 -- 2nd PC/SC reader in the system
|
59
|
+
transport_class = PcscTransport
|
60
|
+
transport_opts = { :reader_index => conf[1..-1].to_i - 1 }
|
61
|
+
else
|
62
|
+
# Reader Name -- the PC/SC reader with the given name
|
63
|
+
transport_class = PcscTransport
|
64
|
+
transport_opts = { :reader_name => conf }
|
65
|
+
end
|
66
|
+
|
67
|
+
transport_opts[:port] = 8050 if transport_opts[:port] == 0
|
68
|
+
if transport_opts[:reader_index] and transport_opts[:reader_index] < 0
|
69
|
+
transport_opts[:reader_index] = 0
|
70
|
+
end
|
71
|
+
{ :class => transport_class, :opts => transport_opts }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Attempts to create a new ISO7816 transport with the given configuration.
|
75
|
+
# :call-seq:
|
76
|
+
# AutoConfigurator.try_transport(configuration) -> Transport or nil
|
77
|
+
#
|
78
|
+
# The configuration should have the following keys:
|
79
|
+
# class:: the Ruby class implementing the transport
|
80
|
+
# opts:: the options to be passed to the implementation's constructor
|
81
|
+
def self.try_transport(configuration)
|
82
|
+
raise 'No transport class specified' unless configuration[:class]
|
83
|
+
begin
|
84
|
+
transport = configuration[:class].new(configuration[:opts] || {})
|
85
|
+
transport.connect
|
86
|
+
return transport
|
87
|
+
rescue Exception
|
88
|
+
return nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end # module AutoConfigurator
|
92
|
+
|
93
|
+
end # module Smartcard::Iso
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# Common code for talking to ISO7816 smart-cards on all transports.
|
2
|
+
#
|
3
|
+
# Author:: Victor Costan
|
4
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
# :nodoc: namespace
|
8
|
+
module Smartcard::Iso
|
9
|
+
|
10
|
+
|
11
|
+
# Module intended to be mixed into transport implementations to mediate between
|
12
|
+
# a high level format for ISO7816-specific APDUs and the wire-level APDU
|
13
|
+
# request and response formats.
|
14
|
+
#
|
15
|
+
# The mix-in calls exchange_apdu in the transport implementation. It supplies
|
16
|
+
# the APDU data as an array of integers between 0 and 255, and expects a
|
17
|
+
# response in the same format.
|
18
|
+
module IsoCardMixin
|
19
|
+
# APDU exchange with the ISO7816 card, raising an exception if the return
|
20
|
+
# code is not success (0x9000).
|
21
|
+
#
|
22
|
+
# :call_seq:
|
23
|
+
# transport.iso_apdu!(apdu_data) -> array
|
24
|
+
#
|
25
|
+
# The apdu_data should be in the format expected by
|
26
|
+
# IsoCardMixin#serialize_apdu. Returns the response data, if the response
|
27
|
+
# status indicates success (0x9000). Otherwise, raises an exeception.
|
28
|
+
def iso_apdu!(apdu_data)
|
29
|
+
response = self.iso_apdu apdu_data
|
30
|
+
return response[:data] if response[:status] == 0x9000
|
31
|
+
raise "JavaCard response has error status 0x#{'%04x' % response[:status]}"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Performs an APDU exchange with the ISO7816 card.
|
35
|
+
#
|
36
|
+
# :call-seq:
|
37
|
+
# transport.iso_apdu(apdu_data) -> hash
|
38
|
+
#
|
39
|
+
# The apdu_data should be in the format expected by
|
40
|
+
# IsoCardMixin#serialize_apdu. The response will be as specified in
|
41
|
+
# IsoCardMixin#deserialize_response.
|
42
|
+
def iso_apdu(apdu_data)
|
43
|
+
response = self.exchange_apdu IsoCardMixin.serialize_apdu(apdu_data)
|
44
|
+
IsoCardMixin.deserialize_response response
|
45
|
+
end
|
46
|
+
|
47
|
+
# Serializes an APDU for wire transmission.
|
48
|
+
#
|
49
|
+
# :call-seq:
|
50
|
+
# transport.wire_apdu(apdu_data) -> array
|
51
|
+
#
|
52
|
+
# The following keys are recognized in the APDU hash:
|
53
|
+
# cla:: the CLA byte in the APDU (optional, defaults to 0)
|
54
|
+
# ins:: the INS byte in the APDU -- the first byte seen by a JavaCard applet
|
55
|
+
# p12:: 2-byte array containing the P1 and P2 bytes in the APDU
|
56
|
+
# p1, p2:: the P1 and P2 bytes in the APDU (optional, both default to 0)
|
57
|
+
# data:: the extra data in the APDU (optional, defaults to nothing)
|
58
|
+
def self.serialize_apdu(apdu_data)
|
59
|
+
raise 'Unspecified INS in apdu_data' unless apdu_data[:ins]
|
60
|
+
apdu = [ apdu_data[:cla] || 0, apdu_data[:ins] ]
|
61
|
+
if apdu_data[:p12]
|
62
|
+
unless apdu_data[:p12].length == 2
|
63
|
+
raise "Malformed P1,P2 - #{apdu_data[:p12]}"
|
64
|
+
end
|
65
|
+
apdu += apdu_data[:p12]
|
66
|
+
else
|
67
|
+
apdu << (apdu_data[:p1] || 0)
|
68
|
+
apdu << (apdu_data[:p2] || 0)
|
69
|
+
end
|
70
|
+
if apdu_data[:data]
|
71
|
+
apdu << apdu_data[:data].length
|
72
|
+
apdu += apdu_data[:data]
|
73
|
+
else
|
74
|
+
apdu << 0
|
75
|
+
end
|
76
|
+
apdu
|
77
|
+
end
|
78
|
+
|
79
|
+
# De-serializes a ISO7816 response APDU.
|
80
|
+
#
|
81
|
+
# :call-seq:
|
82
|
+
# transport.deserialize_response(response) -> hash
|
83
|
+
#
|
84
|
+
# The response contains the following keys:
|
85
|
+
# status:: the 2-byte status code (e.g. 0x9000 is OK)
|
86
|
+
# data:: the additional data in the response
|
87
|
+
def self.deserialize_response(response)
|
88
|
+
{ :status => response[-2] * 256 + response[-1], :data => response[0...-2] }
|
89
|
+
end
|
90
|
+
end # module IsoCardMixin
|
91
|
+
|
92
|
+
end # module Smartcard::Iso
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# The protocol used to talk to ISO7816 smart-cards in IBM JCOP simulators.
|
2
|
+
#
|
3
|
+
# Author:: Victor Costan
|
4
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
# :nodoc: namespace
|
8
|
+
module Smartcard::Iso
|
9
|
+
|
10
|
+
|
11
|
+
# Mixin implementing the JCOP simulator protocol.
|
12
|
+
#
|
13
|
+
# The (pretty informal) protocol specification is contained in the JavaDocs for
|
14
|
+
# the class com.ibm.jc.terminal.RemoteJCTerminal and should be easy to find by
|
15
|
+
# http://www.google.com/search?q=%22com.ibm.jc.terminal.RemoteJCTerminal%22
|
16
|
+
module JcopRemoteProtocol
|
17
|
+
# Encodes and sends a JCOP simulator message to a TCP socket.
|
18
|
+
#
|
19
|
+
# The message must contain the following keys:
|
20
|
+
# type:: Integer expressing the message type (e.g. 1 = APDU exchange)
|
21
|
+
# node:: Integer expressing the node address (e.g. 0 for most purposes)
|
22
|
+
# data:: message payload, as an array of Integers ranging from 0 to 255
|
23
|
+
def send_message(socket, message)
|
24
|
+
raw_message = [message[:type], message[:node], message[:data].length].
|
25
|
+
pack('CCn') + message[:data].pack('C*')
|
26
|
+
socket.send raw_message, 0
|
27
|
+
end
|
28
|
+
|
29
|
+
# Reads and decodes a JCOP simulator message from a TCP socket.
|
30
|
+
#
|
31
|
+
# :call_seq:
|
32
|
+
# client.read_message(socket) -> Hash or nil
|
33
|
+
#
|
34
|
+
# If the other side of the TCP socket closes the connection, this method
|
35
|
+
# returns nil. Otherwise, a Hash is returned, with the format required by the
|
36
|
+
# JcopRemoteProtocol#send_message.
|
37
|
+
def recv_message(socket)
|
38
|
+
header = ''
|
39
|
+
while header.length < 4
|
40
|
+
begin
|
41
|
+
partial = socket.recv 4 - header.length
|
42
|
+
rescue # Abrupt hangups result in exceptions that we catch here.
|
43
|
+
return nil
|
44
|
+
end
|
45
|
+
return false if partial.length == 0
|
46
|
+
header += partial
|
47
|
+
end
|
48
|
+
message_type, node_address, data_length = *header.unpack('CCn')
|
49
|
+
raw_data = ''
|
50
|
+
while raw_data.length < data_length
|
51
|
+
begin
|
52
|
+
partial = socket.recv data_length - raw_data.length
|
53
|
+
rescue # Abrupt hangups result in exceptions that we catch here.
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
return false if partial.length == 0
|
57
|
+
raw_data += partial
|
58
|
+
end
|
59
|
+
|
60
|
+
return false unless raw_data.length == data_length
|
61
|
+
data = raw_data.unpack('C*')
|
62
|
+
return { :type => message_type, :node => node_address, :data => data }
|
63
|
+
end
|
64
|
+
end # module JcopRemoteProtocol
|
65
|
+
|
66
|
+
end # namespace Smartcard::Iso
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# The protocol used to talk to ISO7816 smart-cards in IBM JCOP simulators.
|
2
|
+
#
|
3
|
+
# Author:: Victor Costan
|
4
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
require 'socket'
|
8
|
+
|
9
|
+
# :nodoc: namespace
|
10
|
+
module Smartcard::Iso
|
11
|
+
|
12
|
+
|
13
|
+
# Stubs out the methods that can be implemented by the serving logic in a
|
14
|
+
# JCOP remote server. Serving logic classes should mix in this module, to
|
15
|
+
# avoid having unimplemented methods.
|
16
|
+
module JcopRemoteServingStubs
|
17
|
+
# Called when a client connection accepted.
|
18
|
+
#
|
19
|
+
# This method serves as a notification to the serving logic implementation.
|
20
|
+
# Its return value is discarded.
|
21
|
+
def connection_start
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# Called when a client connection is closed.
|
26
|
+
#
|
27
|
+
# This method serves as a notification to the serving logic implementation.
|
28
|
+
# Its return value is discarded.
|
29
|
+
def connection_end
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
# Serving logic handling an APDU exchange.
|
34
|
+
#
|
35
|
+
# :call-seq:
|
36
|
+
# logic.exchange_apdu(apdu) -> array
|
37
|
+
#
|
38
|
+
# The |apdu| parameter is the request APDU, formatted as an array of
|
39
|
+
# integers between 0 and 255. The method should return the response APDU,
|
40
|
+
# formatted in a similar manner.
|
41
|
+
def exchange_apdu(apdu)
|
42
|
+
# Dumb implementation that always returns OK.
|
43
|
+
[0x90, 0x00]
|
44
|
+
end
|
45
|
+
end # module JcopRemoteServingStubs
|
46
|
+
|
47
|
+
|
48
|
+
# A server for the JCOP simulator protocol.
|
49
|
+
#
|
50
|
+
# The JCOP simulator protocol is generally useful when talking to a real JCOP
|
51
|
+
# simulator. This server is only handy for testing, and for forwarding
|
52
|
+
# connections (JCOP's Eclipse plug-in makes the simulator listen to 127.0.0.1,
|
53
|
+
# and sometimes you want to use it from another box).
|
54
|
+
class JcopRemoteServer
|
55
|
+
include JcopRemoteProtocol
|
56
|
+
|
57
|
+
# Creates a new JCOP server.
|
58
|
+
#
|
59
|
+
# The options hash supports the following keys:
|
60
|
+
# port:: the port to serve on
|
61
|
+
# ip:: the IP of the interface to serve on (defaults to all interfaces)
|
62
|
+
# reusable:: if set, the serving port can be shared with another
|
63
|
+
# application (REUSEADDR flag will be set on the socket)
|
64
|
+
#
|
65
|
+
# If the |serving_logic| parameter is nil, a serving logic implementation
|
66
|
+
# must be provided when calling JcopRemoteServer#run. The server will crash
|
67
|
+
# otherwise.
|
68
|
+
def initialize(options, serving_logic = nil)
|
69
|
+
@logic = serving_logic
|
70
|
+
@running = false
|
71
|
+
@options = options
|
72
|
+
@mutex = Mutex.new
|
73
|
+
end
|
74
|
+
|
75
|
+
# Runs the serving loop indefinitely.
|
76
|
+
#
|
77
|
+
# This method serves incoming conenctions until #stop is called.
|
78
|
+
#
|
79
|
+
# If |serving_logic| contains a non-nil value, it overrides any previously
|
80
|
+
# specified serving logic implementation. If no implementation is specified
|
81
|
+
# when the server is instantiated via JcopRemoteServer#new, one must be
|
82
|
+
# passed into |serving_logic|.
|
83
|
+
def run(serving_logic = nil)
|
84
|
+
@mutex.synchronize do
|
85
|
+
@logic ||= serving_logic
|
86
|
+
@serving_socket = serving_socket @options
|
87
|
+
@running = true
|
88
|
+
end
|
89
|
+
loop do
|
90
|
+
break unless @mutex.synchronize { @running }
|
91
|
+
begin
|
92
|
+
client_socket, client_address = @serving_socket.accept
|
93
|
+
rescue
|
94
|
+
# An exception will occur if the socket is closed
|
95
|
+
break
|
96
|
+
end
|
97
|
+
@logic.connection_start
|
98
|
+
loop do
|
99
|
+
break unless @mutex.synchronize { @running }
|
100
|
+
break unless process_request client_socket
|
101
|
+
end
|
102
|
+
client_socket.close rescue nil
|
103
|
+
@logic.connection_end # implemented by subclass
|
104
|
+
end
|
105
|
+
@mutex.synchronize do
|
106
|
+
@serving_socket.close if @serving_socket
|
107
|
+
@serving_socket = nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Stops the serving loop.
|
112
|
+
def stop
|
113
|
+
@mutex.synchronize do
|
114
|
+
if @running
|
115
|
+
@serving_socket.close rescue nil
|
116
|
+
@serving_socket = nil
|
117
|
+
@running = false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# TODO(costan): figure out a way to let serving logic reach this directly.
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
# Creates a socket listening to incoming connections to this server.
|
126
|
+
#
|
127
|
+
# :call-seq:
|
128
|
+
# server.establish_socket(options) -> Socket
|
129
|
+
#
|
130
|
+
# The |options| parameter supports the same keys as the options parameter
|
131
|
+
# of JcopRemoteServer#new.
|
132
|
+
#
|
133
|
+
# Returns a Socket configured to accept incoming connections.
|
134
|
+
def serving_socket(options)
|
135
|
+
port = options[:port] || 0
|
136
|
+
interface_ip = options[:ip] || '0.0.0.0'
|
137
|
+
serving_address = Socket.pack_sockaddr_in port, interface_ip
|
138
|
+
|
139
|
+
socket = Socket.new Socket::AF_INET, Socket::SOCK_STREAM,
|
140
|
+
Socket::PF_UNSPEC
|
141
|
+
|
142
|
+
if options[:reusable]
|
143
|
+
socket.setsockopt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true
|
144
|
+
end
|
145
|
+
socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true
|
146
|
+
socket.bind serving_address
|
147
|
+
socket.listen 5
|
148
|
+
socket
|
149
|
+
end
|
150
|
+
private :serving_socket
|
151
|
+
|
152
|
+
# Performs a request/response cycle.
|
153
|
+
#
|
154
|
+
# :call-seq:
|
155
|
+
# server.process_request(socket) -> Boolean
|
156
|
+
#
|
157
|
+
# Returns true if the server should do another request/response cycle, or
|
158
|
+
# false if this client indicated it's done talking to the server.
|
159
|
+
def process_request(socket)
|
160
|
+
return false unless request = recv_message(socket)
|
161
|
+
|
162
|
+
case request[:type]
|
163
|
+
when 0
|
164
|
+
# Wait for card; no-op, because that should have happen when the client
|
165
|
+
# connected.
|
166
|
+
send_message socket, :type => 0, :node => 0, :data => [3, 1, 4, 1, 5, 9]
|
167
|
+
when 1
|
168
|
+
# ATR exchange; the class' bread and butter
|
169
|
+
response = @logic.exchange_apdu request[:data]
|
170
|
+
send_message socket, :type => 1, :node => 0, :data => response
|
171
|
+
else
|
172
|
+
send_message socket, :type => request[:type], :node => 0, :data => []
|
173
|
+
end
|
174
|
+
end
|
175
|
+
private :process_request
|
176
|
+
end # module JcopRemoteServer
|
177
|
+
|
178
|
+
end # namespace Smartcard::Iso
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Interface to ISO7816 smart-cards in IBM JCOP simulators.
|
2
|
+
#
|
3
|
+
# Author:: Victor Costan
|
4
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
require 'socket'
|
8
|
+
|
9
|
+
# :nodoc: namespace
|
10
|
+
module Smartcard::Iso
|
11
|
+
|
12
|
+
|
13
|
+
# Implements the transport layer for a JCOP simulator instance.
|
14
|
+
class JcopRemoteTransport
|
15
|
+
include IsoCardMixin
|
16
|
+
include JcopRemoteProtocol
|
17
|
+
|
18
|
+
# Creates a new unconnected transport for a JCOP simulator serving TCP/IP.
|
19
|
+
#
|
20
|
+
# The options parameter must have the following keys:
|
21
|
+
# host:: the DNS name or IP of the host running the JCOP simulator
|
22
|
+
# port:: the TCP/IP port of the JCOP simulator server
|
23
|
+
def initialize(options)
|
24
|
+
@host, @port = options[:host], options[:port]
|
25
|
+
@socket = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
def exchange_apdu(apdu)
|
30
|
+
send_message @socket, :type => 1, :node => 0, :data => apdu
|
31
|
+
recv_message(@socket)[:data]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Makes a transport-level connection to the TEM.
|
35
|
+
def connect
|
36
|
+
begin
|
37
|
+
Socket.getaddrinfo(@host, @port, Socket::AF_INET,
|
38
|
+
Socket::SOCK_STREAM).each do |addr_info|
|
39
|
+
begin
|
40
|
+
@socket = Socket.new(addr_info[4], addr_info[5], addr_info[6])
|
41
|
+
@socket.connect Socket.pack_sockaddr_in(addr_info[1], addr_info[3])
|
42
|
+
break
|
43
|
+
rescue
|
44
|
+
@socket = nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
raise 'Connection refused' unless @socket
|
48
|
+
|
49
|
+
# Wait for the card to be inserted.
|
50
|
+
send_message @socket, :type => 0, :node => 0, :data => [0, 1, 0, 0]
|
51
|
+
recv_message @socket # ATR should come here, but who cares
|
52
|
+
rescue Exception
|
53
|
+
@socket = nil
|
54
|
+
raise
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Breaks down the transport-level connection to the TEM.
|
59
|
+
def disconnect
|
60
|
+
if @socket
|
61
|
+
@socket.close
|
62
|
+
@socket = nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
"#<JCOP Remote Terminal: disconnected>" if @socket.nil?
|
68
|
+
"#<JCOP Remote Terminal: #{@host}:#{@port}>"
|
69
|
+
end
|
70
|
+
end # class JcopRemoteTransport
|
71
|
+
|
72
|
+
end # module Smartcard::Iso
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Interface to ISO7816 smart-cards in PC/SC readers.
|
2
|
+
#
|
3
|
+
# Author:: Victor Costan
|
4
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'smartcard'
|
9
|
+
|
10
|
+
# :nodoc: namespace
|
11
|
+
module Smartcard::Iso
|
12
|
+
|
13
|
+
# Implements the transport layer for a smartcard connected to a PC/SC reader.
|
14
|
+
class PcscTransport
|
15
|
+
include IsoCardMixin
|
16
|
+
PCSC = Smartcard::PCSC
|
17
|
+
|
18
|
+
def initialize(options)
|
19
|
+
@options = options
|
20
|
+
@context = nil
|
21
|
+
@card = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def exchange_apdu(apdu)
|
25
|
+
xmit_apdu_string = apdu.pack('C*')
|
26
|
+
result_string = @card.transmit xmit_apdu_string, @xmit_ioreq, @recv_ioreq
|
27
|
+
return result_string.unpack('C*')
|
28
|
+
end
|
29
|
+
|
30
|
+
def connect
|
31
|
+
@context = PCSC::Context.new(PCSC::SCOPE_SYSTEM) if @context.nil?
|
32
|
+
|
33
|
+
if @options[:reader_name]
|
34
|
+
@reader_name = @options[:reader_name]
|
35
|
+
else
|
36
|
+
# get the first reader
|
37
|
+
readers = @context.list_readers nil
|
38
|
+
@reader_name = readers[@options[:reader_index] || 0]
|
39
|
+
end
|
40
|
+
|
41
|
+
# get the reader's status
|
42
|
+
reader_states = PCSC::ReaderStates.new(1)
|
43
|
+
reader_states.set_reader_name_of!(0, @reader_name)
|
44
|
+
reader_states.set_current_state_of!(0, PCSC::STATE_UNKNOWN)
|
45
|
+
@context.get_status_change reader_states, 100
|
46
|
+
reader_states.acknowledge_events!
|
47
|
+
|
48
|
+
# prompt for card insertion unless that already happened
|
49
|
+
if (reader_states.current_state_of(0) & PCSC::STATE_PRESENT) == 0
|
50
|
+
puts "Please insert TEM card in reader #{@reader_name}\n"
|
51
|
+
while (reader_states.current_state_of(0) & PCSC::STATE_PRESENT) == 0 do
|
52
|
+
@context.get_status_change reader_states, PCSC::INFINITE_TIMEOUT
|
53
|
+
reader_states.acknowledge_events!
|
54
|
+
end
|
55
|
+
puts "Card detected\n"
|
56
|
+
end
|
57
|
+
|
58
|
+
# connect to card
|
59
|
+
@card = PCSC::Card.new @context, @reader_name, PCSC::SHARE_EXCLUSIVE,
|
60
|
+
PCSC::PROTOCOL_ANY
|
61
|
+
|
62
|
+
# build the transmit / receive IoRequests
|
63
|
+
status = @card.status
|
64
|
+
@xmit_ioreq = @@xmit_iorequest[status[:protocol]]
|
65
|
+
if RUBY_PLATFORM =~ /win/ and (not RUBY_PLATFORM =~ /darwin/)
|
66
|
+
@recv_ioreq = nil
|
67
|
+
else
|
68
|
+
@recv_ioreq = PCSC::IoRequest.new
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def disconnect
|
73
|
+
unless @card.nil?
|
74
|
+
@card.disconnect PCSC::DISPOSITION_LEAVE
|
75
|
+
@card = nil
|
76
|
+
end
|
77
|
+
unless @context.nil?
|
78
|
+
@context.release
|
79
|
+
@context = nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
"#<PC/SC Terminal: disconnected>" if @card.nil?
|
85
|
+
"#<PC/SC Terminal: #{@reader_name}>"
|
86
|
+
end
|
87
|
+
|
88
|
+
@@xmit_iorequest = {
|
89
|
+
Smartcard::PCSC::PROTOCOL_T0 => Smartcard::PCSC::IOREQUEST_T0,
|
90
|
+
Smartcard::PCSC::PROTOCOL_T1 => Smartcard::PCSC::IOREQUEST_T1,
|
91
|
+
}
|
92
|
+
end # class PcscTransport
|
93
|
+
|
94
|
+
end # module Smartcard::Iso
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Interface for ISO7816 cards.
|
2
|
+
#
|
3
|
+
# Author:: Victor Costan
|
4
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
|
8
|
+
# The transport module contains classes responsible for transferring APDUs
|
9
|
+
# from a high-level representation to the smart card hardware.
|
10
|
+
module Smartcard::Iso
|
11
|
+
# Shortcut for Smartcard::Iso::AutoConfigurator#auto_transport
|
12
|
+
def self.auto_transport
|
13
|
+
Smartcard::Iso::AutoConfigurator.auto_transport
|
14
|
+
end
|
15
|
+
end
|
File without changes
|
data/lib/smartcard/pcsc.so
CHANGED
Binary file
|
data/lib/smartcard.rb
CHANGED
@@ -1 +1,15 @@
|
|
1
1
|
require 'smartcard/pcsc'
|
2
|
+
require 'smartcard/pcsc/pcsc_exception.rb'
|
3
|
+
|
4
|
+
|
5
|
+
require 'smartcard/iso/iso_card_mixin.rb'
|
6
|
+
require 'smartcard/iso/jcop_remote_protocol.rb'
|
7
|
+
require 'smartcard/iso/jcop_remote_transport.rb'
|
8
|
+
require 'smartcard/iso/jcop_remote_server.rb'
|
9
|
+
require 'smartcard/iso/pcsc_transport.rb'
|
10
|
+
require 'smartcard/iso/transport.rb'
|
11
|
+
|
12
|
+
require 'smartcard/iso/auto_configurator.rb'
|
13
|
+
|
14
|
+
|
15
|
+
require 'smartcard/gp/gp_card_mixin.rb'
|
data/smartcard.gemspec
CHANGED
@@ -2,23 +2,23 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{smartcard}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.4.0"
|
6
6
|
s.platform = %q{x86-mswin32-60}
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
9
9
|
s.authors = ["Victor Costan"]
|
10
|
-
s.date = %q{2009-
|
10
|
+
s.date = %q{2009-08-19}
|
11
11
|
s.description = %q{Interface with ISO 7816 smart cards.}
|
12
12
|
s.email = %q{victor@costan.us}
|
13
|
-
s.extra_rdoc_files = ["BUILD", "CHANGELOG", "ext/smartcard_pcsc/extconf.rb", "ext/smartcard_pcsc/pcsc.h", "ext/smartcard_pcsc/pcsc_card.c", "ext/smartcard_pcsc/pcsc_constants.c", "ext/smartcard_pcsc/pcsc_context.c", "ext/smartcard_pcsc/pcsc_exception.c", "ext/smartcard_pcsc/pcsc_io_request.c", "ext/smartcard_pcsc/pcsc_main.c", "ext/smartcard_pcsc/pcsc_multi_strings.c", "ext/smartcard_pcsc/pcsc_namespace.c", "ext/smartcard_pcsc/pcsc_reader_states.c", "ext/smartcard_pcsc/pcsc_surrogate_reader.h", "ext/smartcard_pcsc/pcsc_surrogate_wintypes.h", "lib/smartcard/pcsc_exception.rb", "lib/smartcard.rb", "LICENSE", "README"]
|
14
|
-
s.files = ["BUILD", "CHANGELOG", "ext/smartcard_pcsc/extconf.rb", "ext/smartcard_pcsc/pcsc.h", "ext/smartcard_pcsc/pcsc_card.c", "ext/smartcard_pcsc/pcsc_constants.c", "ext/smartcard_pcsc/pcsc_context.c", "ext/smartcard_pcsc/pcsc_exception.c", "ext/smartcard_pcsc/pcsc_io_request.c", "ext/smartcard_pcsc/pcsc_main.c", "ext/smartcard_pcsc/pcsc_multi_strings.c", "ext/smartcard_pcsc/pcsc_namespace.c", "ext/smartcard_pcsc/pcsc_reader_states.c", "ext/smartcard_pcsc/pcsc_surrogate_reader.h", "ext/smartcard_pcsc/pcsc_surrogate_wintypes.h", "lib/smartcard/pcsc_exception.rb", "lib/smartcard.rb", "LICENSE", "Manifest", "README", "test/
|
13
|
+
s.extra_rdoc_files = ["BUILD", "CHANGELOG", "ext/smartcard_pcsc/extconf.rb", "ext/smartcard_pcsc/pcsc.h", "ext/smartcard_pcsc/pcsc_card.c", "ext/smartcard_pcsc/pcsc_constants.c", "ext/smartcard_pcsc/pcsc_context.c", "ext/smartcard_pcsc/pcsc_exception.c", "ext/smartcard_pcsc/pcsc_io_request.c", "ext/smartcard_pcsc/pcsc_main.c", "ext/smartcard_pcsc/pcsc_multi_strings.c", "ext/smartcard_pcsc/pcsc_namespace.c", "ext/smartcard_pcsc/pcsc_reader_states.c", "ext/smartcard_pcsc/pcsc_surrogate_reader.h", "ext/smartcard_pcsc/pcsc_surrogate_wintypes.h", "lib/smartcard/gp/gp_card_mixin.rb", "lib/smartcard/iso/auto_configurator.rb", "lib/smartcard/iso/iso_card_mixin.rb", "lib/smartcard/iso/jcop_remote_protocol.rb", "lib/smartcard/iso/jcop_remote_server.rb", "lib/smartcard/iso/jcop_remote_transport.rb", "lib/smartcard/iso/pcsc_transport.rb", "lib/smartcard/iso/transport.rb", "lib/smartcard/pcsc/pcsc_exception.rb", "lib/smartcard.rb", "LICENSE", "README"]
|
14
|
+
s.files = ["BUILD", "CHANGELOG", "ext/smartcard_pcsc/extconf.rb", "ext/smartcard_pcsc/pcsc.h", "ext/smartcard_pcsc/pcsc_card.c", "ext/smartcard_pcsc/pcsc_constants.c", "ext/smartcard_pcsc/pcsc_context.c", "ext/smartcard_pcsc/pcsc_exception.c", "ext/smartcard_pcsc/pcsc_io_request.c", "ext/smartcard_pcsc/pcsc_main.c", "ext/smartcard_pcsc/pcsc_multi_strings.c", "ext/smartcard_pcsc/pcsc_namespace.c", "ext/smartcard_pcsc/pcsc_reader_states.c", "ext/smartcard_pcsc/pcsc_surrogate_reader.h", "ext/smartcard_pcsc/pcsc_surrogate_wintypes.h", "lib/smartcard/gp/gp_card_mixin.rb", "lib/smartcard/iso/auto_configurator.rb", "lib/smartcard/iso/iso_card_mixin.rb", "lib/smartcard/iso/jcop_remote_protocol.rb", "lib/smartcard/iso/jcop_remote_server.rb", "lib/smartcard/iso/jcop_remote_transport.rb", "lib/smartcard/iso/pcsc_transport.rb", "lib/smartcard/iso/transport.rb", "lib/smartcard/pcsc/pcsc_exception.rb", "lib/smartcard.rb", "LICENSE", "Manifest", "Rakefile", "README", "smartcard.gemspec", "test/gp/gp_card_mixin_test.rb", "test/iso/auto_configurator_test.rb", "test/iso/iso_card_mixin_test.rb", "test/iso/jcop_remote_test.rb", "test/pcsc/containers_test.rb", "test/pcsc/smoke_test.rb", "tests/ts_pcsc_ext.rb", "lib/smartcard/pcsc.so"]
|
15
15
|
s.homepage = %q{http://www.costan.us/smartcard}
|
16
16
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Smartcard", "--main", "README"]
|
17
17
|
s.require_paths = ["lib", "ext"]
|
18
18
|
s.rubyforge_project = %q{smartcard}
|
19
|
-
s.rubygems_version = %q{1.3.
|
19
|
+
s.rubygems_version = %q{1.3.5}
|
20
20
|
s.summary = %q{Interface with ISO 7816 smart cards.}
|
21
|
-
s.test_files = ["test/
|
21
|
+
s.test_files = ["test/gp/gp_card_mixin_test.rb", "test/iso/auto_configurator_test.rb", "test/iso/iso_card_mixin_test.rb", "test/iso/jcop_remote_test.rb", "test/pcsc/containers_test.rb", "test/pcsc/smoke_test.rb"]
|
22
22
|
|
23
23
|
if s.respond_to? :specification_version then
|
24
24
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Author:: Victor Costan
|
2
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
3
|
+
# License:: MIT
|
4
|
+
|
5
|
+
require 'smartcard'
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'flexmock/test_unit'
|
11
|
+
|
12
|
+
|
13
|
+
class GpCardMixinTest < Test::Unit::TestCase
|
14
|
+
GpCardMixin = Smartcard::Gp::GpCardMixin
|
15
|
+
|
16
|
+
# The sole purpose of this class is wrapping the mixin under test.
|
17
|
+
class MixinWrapper
|
18
|
+
include GpCardMixin
|
19
|
+
include Smartcard::Iso::IsoCardMixin
|
20
|
+
end
|
21
|
+
|
22
|
+
def setup
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_select_application
|
26
|
+
mock = MixinWrapper.new
|
27
|
+
flexmock(mock).should_receive(:exchange_apdu).
|
28
|
+
with([0x00, 0xA4, 0x04, 0x00, 0x05,
|
29
|
+
0x19, 0x83, 0x12, 0x29, 0x10]).
|
30
|
+
and_return([0x90, 0x00])
|
31
|
+
mock.select_application([0x19, 0x83, 0x12, 0x29, 0x10])
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# Author:: Victor Costan
|
2
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
3
|
+
# License:: MIT
|
4
|
+
|
5
|
+
require 'smartcard'
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'flexmock/test_unit'
|
11
|
+
|
12
|
+
|
13
|
+
class AutoConfiguratorTest < Test::Unit::TestCase
|
14
|
+
AutoConfigurator = Smartcard::Iso::AutoConfigurator
|
15
|
+
PcscTransport = Smartcard::Iso::PcscTransport
|
16
|
+
JcopRemoteTransport = Smartcard::Iso::JcopRemoteTransport
|
17
|
+
|
18
|
+
def setup
|
19
|
+
@env_var = AutoConfigurator::ENVIRONMENT_VARIABLE_NAME
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_env_configuration_blank
|
23
|
+
flexmock(ENV).should_receive(:[]).with(@env_var).and_return(nil)
|
24
|
+
assert_equal nil, AutoConfigurator.env_configuration
|
25
|
+
end
|
26
|
+
def test_env_configuration_remote_port
|
27
|
+
flexmock(ENV).should_receive(:[]).with(@env_var).and_return(':6996')
|
28
|
+
conf = AutoConfigurator.env_configuration
|
29
|
+
assert_equal JcopRemoteTransport, conf[:class]
|
30
|
+
assert_equal({:host => '127.0.0.1', :port => 6996}, conf[:opts])
|
31
|
+
end
|
32
|
+
def test_env_configuration_remote_noport
|
33
|
+
flexmock(ENV).should_receive(:[]).with(@env_var).and_return(':')
|
34
|
+
conf = AutoConfigurator.env_configuration
|
35
|
+
assert_equal JcopRemoteTransport, conf[:class]
|
36
|
+
assert_equal({:host => '127.0.0.1', :port => 8050}, conf[:opts])
|
37
|
+
end
|
38
|
+
def test_env_configuration_remote_host_port
|
39
|
+
flexmock(ENV).should_receive(:[]).with(@env_var).
|
40
|
+
and_return('@moonstone:6996')
|
41
|
+
conf = AutoConfigurator.env_configuration
|
42
|
+
assert_equal JcopRemoteTransport, conf[:class]
|
43
|
+
assert_equal({:host => 'moonstone', :port => 6996}, conf[:opts])
|
44
|
+
end
|
45
|
+
def test_env_configuration_remote_host_noport
|
46
|
+
flexmock(ENV).should_receive(:[]).with(@env_var).and_return('@moonstone')
|
47
|
+
conf = AutoConfigurator.env_configuration
|
48
|
+
assert_equal JcopRemoteTransport, conf[:class]
|
49
|
+
assert_equal({:host => 'moonstone', :port => 8050}, conf[:opts])
|
50
|
+
end
|
51
|
+
def test_env_configuration_remote_ipv6_port
|
52
|
+
flexmock(ENV).should_receive(:[]).with(@env_var).
|
53
|
+
and_return('@ff80::0080:6996')
|
54
|
+
conf = AutoConfigurator.env_configuration
|
55
|
+
assert_equal JcopRemoteTransport, conf[:class]
|
56
|
+
assert_equal({:host => 'ff80::0080', :port => 6996}, conf[:opts])
|
57
|
+
end
|
58
|
+
def test_env_configuration_remote_ipv6_noport
|
59
|
+
flexmock(ENV).should_receive(:[]).with(@env_var).and_return('@ff80::0080:')
|
60
|
+
conf = AutoConfigurator.env_configuration
|
61
|
+
assert_equal JcopRemoteTransport, conf[:class]
|
62
|
+
assert_equal({:host => 'ff80::0080', :port => 8050}, conf[:opts])
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_env_configuration_pcsc_reader_index
|
66
|
+
flexmock(ENV).should_receive(:[]).with(@env_var).and_return('#1')
|
67
|
+
conf = AutoConfigurator.env_configuration
|
68
|
+
assert_equal PcscTransport, conf[:class]
|
69
|
+
assert_equal({:reader_index => 0}, conf[:opts])
|
70
|
+
end
|
71
|
+
def test_env_configuration_pcsc_reader_name
|
72
|
+
reader_name = 'Awesome Reader'
|
73
|
+
flexmock(ENV).should_receive(:[]).with(@env_var).
|
74
|
+
and_return(reader_name)
|
75
|
+
conf = AutoConfigurator.env_configuration
|
76
|
+
assert_equal PcscTransport, conf[:class]
|
77
|
+
assert_equal({:reader_name => reader_name}, conf[:opts])
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_try_transport
|
81
|
+
transport = Object.new
|
82
|
+
flexmock(PcscTransport).should_receive(:new).with(:reader_index => 1).
|
83
|
+
and_return(transport)
|
84
|
+
flexmock(transport).should_receive(:connect)
|
85
|
+
flexmock(PcscTransport).should_receive(:new).with(:reader_index => 2).
|
86
|
+
and_raise('Boom headshot')
|
87
|
+
failport = Object.new
|
88
|
+
flexmock(PcscTransport).should_receive(:new).with(:reader_index => 3).
|
89
|
+
and_return(failport)
|
90
|
+
flexmock(failport).should_receive(:connect).and_raise('Lag')
|
91
|
+
|
92
|
+
config = { :class => PcscTransport, :opts => {:reader_index => 1} }
|
93
|
+
assert_equal transport, AutoConfigurator.try_transport(config)
|
94
|
+
config = { :class => PcscTransport, :opts => {:reader_index => 2} }
|
95
|
+
assert_equal nil, AutoConfigurator.try_transport(config)
|
96
|
+
config = { :class => PcscTransport, :opts => {:reader_index => 3} }
|
97
|
+
assert_equal nil, AutoConfigurator.try_transport(config)
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_auto_transport_uses_env
|
101
|
+
flexmock(ENV).should_receive(:[]).with(@env_var).and_return('#1')
|
102
|
+
transport = Object.new
|
103
|
+
flexmock(PcscTransport).should_receive(:new).with(:reader_index => 0).
|
104
|
+
and_return(transport)
|
105
|
+
flexmock(transport).should_receive(:connect)
|
106
|
+
|
107
|
+
assert_equal transport, AutoConfigurator.auto_transport
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_auto_transport_with_defaults
|
111
|
+
flexmock(ENV).should_receive(:[]).with(@env_var).and_return(nil)
|
112
|
+
transport = Object.new
|
113
|
+
flexmock(JcopRemoteTransport).should_receive(:new).and_return(nil)
|
114
|
+
flexmock(PcscTransport).should_receive(:new).and_return(transport)
|
115
|
+
flexmock(transport).should_receive(:connect)
|
116
|
+
|
117
|
+
assert_equal transport, AutoConfigurator.auto_transport
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# Author:: Victor Costan
|
2
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
3
|
+
# License:: MIT
|
4
|
+
|
5
|
+
require 'smartcard'
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'flexmock/test_unit'
|
11
|
+
|
12
|
+
|
13
|
+
class IsoCardMixinTest < Test::Unit::TestCase
|
14
|
+
IsoCardMixin = Smartcard::Iso::IsoCardMixin
|
15
|
+
|
16
|
+
# The sole purpose of this class is wrapping the mixin under test.
|
17
|
+
class MixinWrapper
|
18
|
+
include IsoCardMixin
|
19
|
+
end
|
20
|
+
|
21
|
+
def setup
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_serialize_apdu
|
25
|
+
s = lambda { |apdu| IsoCardMixin.serialize_apdu apdu }
|
26
|
+
|
27
|
+
assert_equal [0x00, 0x05, 0x00, 0x00, 0x00], s[:ins => 0x05],
|
28
|
+
'Specified INS'
|
29
|
+
assert_equal [0x00, 0x09, 0x00, 0x01, 0x00], s[:ins => 0x09, :p2 => 0x01],
|
30
|
+
'Specified INS and P2'
|
31
|
+
assert_equal [0x00, 0xF9, 0xAC, 0xEF, 0x00],
|
32
|
+
s[:ins => 0xF9, :p1 => 0xAC, :p2 => 0xEF],
|
33
|
+
'Specified INS, P1, P2'
|
34
|
+
assert_equal [0x00, 0xFA, 0xAD, 0xEC, 0x00],
|
35
|
+
s[:ins => 0xFA, :p12 => [0xAD, 0xEC]],
|
36
|
+
'Specified INS, P1+P2'
|
37
|
+
assert_equal [0x00, 0x0E, 0x00, 0x00, 0x04, 0x33, 0x95, 0x81, 0x63],
|
38
|
+
s[:ins => 0x0E, :data => [0x33, 0x95, 0x81, 0x63]],
|
39
|
+
'Specified INS and DATA'
|
40
|
+
assert_equal [0x80, 0x0F, 0xBA, 0xBE, 0x03, 0x31, 0x41, 0x59],
|
41
|
+
s[:cla => 0x80, :ins => 0x0F, :p1 => 0xBA, :p2 => 0xBE,
|
42
|
+
:data => [0x31, 0x41, 0x59]],
|
43
|
+
'Specified everything'
|
44
|
+
assert_raise(RuntimeError, 'Did not specify INS') do
|
45
|
+
s[:cla => 0x80, :p1 => 0xBA, :p2 => 0xBE, :data => [0x31, 0x41, 0x59]]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_deserialize_response
|
50
|
+
d = lambda { |response| IsoCardMixin.deserialize_response response }
|
51
|
+
|
52
|
+
assert_equal({ :status => 0x9000, :data => [] }, d[[0x90, 0x00]])
|
53
|
+
assert_equal({ :status => 0x8631, :data => [] }, d[[0x86, 0x31]])
|
54
|
+
assert_equal({ :status => 0x9000, :data => [0x31, 0x41, 0x59, 0x26] },
|
55
|
+
d[[0x31, 0x41, 0x59, 0x26, 0x90, 0x00]])
|
56
|
+
assert_equal({ :status => 0x7395, :data => [0x31, 0x41, 0x59, 0x26] },
|
57
|
+
d[[0x31, 0x41, 0x59, 0x26, 0x73, 0x95]])
|
58
|
+
end
|
59
|
+
|
60
|
+
def win_mock
|
61
|
+
mock = MixinWrapper.new
|
62
|
+
flexmock(mock).should_receive(:exchange_apdu).
|
63
|
+
with([0x00, 0xF9, 0xAC, 0x00, 0x02, 0x31, 0x41]).
|
64
|
+
and_return([0x67, 0x31, 0x90, 0x00])
|
65
|
+
mock
|
66
|
+
end
|
67
|
+
def win_apdu
|
68
|
+
{:ins => 0xF9, :p1 => 0xAC, :data => [0x31, 0x41]}
|
69
|
+
end
|
70
|
+
|
71
|
+
def lose_mock
|
72
|
+
mock = MixinWrapper.new
|
73
|
+
flexmock(mock).should_receive(:exchange_apdu).
|
74
|
+
with([0x00, 0xF9, 0xAC, 0x00, 0x02, 0x31, 0x41]).
|
75
|
+
and_return([0x86, 0x31])
|
76
|
+
mock
|
77
|
+
end
|
78
|
+
def lose_apdu
|
79
|
+
win_apdu
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_iso_apdu
|
83
|
+
assert_equal({:status => 0x9000, :data => [0x67, 0x31]},
|
84
|
+
win_mock.iso_apdu(win_apdu))
|
85
|
+
assert_equal({:status => 0x8631, :data => []},
|
86
|
+
lose_mock.iso_apdu(lose_apdu))
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_iso_apdu_bang
|
90
|
+
assert_equal [0x67, 0x31], win_mock.iso_apdu!(win_apdu)
|
91
|
+
assert_raise(RuntimeError) do
|
92
|
+
lose_mock.iso_apdu!(lose_apdu)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# Author:: Victor Costan
|
2
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
3
|
+
# License:: MIT
|
4
|
+
|
5
|
+
require 'smartcard'
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'flexmock/test_unit'
|
11
|
+
|
12
|
+
|
13
|
+
# Tests JcopRemoteProtocol, JcopRemoteServer, and JcopRemoteTransport.
|
14
|
+
class JcopRemoteTest < Test::Unit::TestCase
|
15
|
+
Protocol = Smartcard::Iso::JcopRemoteProtocol
|
16
|
+
Server = Smartcard::Iso::JcopRemoteServer
|
17
|
+
Transport = Smartcard::Iso::JcopRemoteTransport
|
18
|
+
|
19
|
+
# Serving logic that records what it receives and replays a log.
|
20
|
+
class Logic
|
21
|
+
include Protocol
|
22
|
+
attr_reader :received
|
23
|
+
def initialize(responses)
|
24
|
+
@responses = responses
|
25
|
+
@received = []
|
26
|
+
end
|
27
|
+
def connection_start
|
28
|
+
@received << :start
|
29
|
+
end
|
30
|
+
def connection_end
|
31
|
+
@received << :end
|
32
|
+
end
|
33
|
+
def exchange_apdu(apdu)
|
34
|
+
@received << apdu
|
35
|
+
@responses.shift
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def setup
|
40
|
+
@server = Server.new(:ip => '127.0.0.1', :port => 51995)
|
41
|
+
@client = Transport.new :host => '127.0.0.1', :port => 51995
|
42
|
+
@old_abort_on_exception = Thread.abort_on_exception
|
43
|
+
Thread.abort_on_exception = true
|
44
|
+
end
|
45
|
+
|
46
|
+
def teardown
|
47
|
+
Thread.abort_on_exception = @old_abort_on_exception
|
48
|
+
@server.stop
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_apdu_exchange
|
52
|
+
apdu_request = [0x31, 0x41, 0x59, 0x26, 0x53]
|
53
|
+
apdu_response = [0x27, 0x90, 0x00]
|
54
|
+
|
55
|
+
logic = Logic.new([apdu_response])
|
56
|
+
Thread.new do
|
57
|
+
begin
|
58
|
+
@server.run logic
|
59
|
+
rescue Exception
|
60
|
+
print $!, "\n"
|
61
|
+
print $!.backtrace.join("\n")
|
62
|
+
raise
|
63
|
+
end
|
64
|
+
end
|
65
|
+
Kernel.sleep 0.05 # Wait for the server to start up.
|
66
|
+
@client.connect
|
67
|
+
assert_equal apdu_response, @client.exchange_apdu(apdu_request)
|
68
|
+
@client.disconnect
|
69
|
+
Kernel.sleep 0.05 # Wait for the server to process the disconnect.
|
70
|
+
assert_equal [:start, apdu_request, :end], logic.received
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_java_card_integration
|
74
|
+
apdu_request = [0x00, 0x31, 0x41, 0x59, 0x00]
|
75
|
+
apdu_response = [0x27, 0x90, 0x00]
|
76
|
+
|
77
|
+
logic = Logic.new([apdu_response])
|
78
|
+
Thread.new { @server.run logic }
|
79
|
+
Kernel.sleep 0.05 # Wait for the server to start up.
|
80
|
+
@client.connect
|
81
|
+
assert_equal [0x27],
|
82
|
+
@client.iso_apdu!(:ins => 0x31, :p1 => 0x41, :p2 => 0x59)
|
83
|
+
@client.disconnect
|
84
|
+
Kernel.sleep 0.05 # Wait for the server to process the disconnect.
|
85
|
+
assert_equal [:start, apdu_request, :end], logic.received
|
86
|
+
end
|
87
|
+
end
|
data/tests/ts_pcsc_ext.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smartcard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: x86-mswin32-60
|
6
6
|
authors:
|
7
7
|
- Victor Costan
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-08-19 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -35,7 +35,15 @@ extra_rdoc_files:
|
|
35
35
|
- ext/smartcard_pcsc/pcsc_reader_states.c
|
36
36
|
- ext/smartcard_pcsc/pcsc_surrogate_reader.h
|
37
37
|
- ext/smartcard_pcsc/pcsc_surrogate_wintypes.h
|
38
|
-
- lib/smartcard/
|
38
|
+
- lib/smartcard/gp/gp_card_mixin.rb
|
39
|
+
- lib/smartcard/iso/auto_configurator.rb
|
40
|
+
- lib/smartcard/iso/iso_card_mixin.rb
|
41
|
+
- lib/smartcard/iso/jcop_remote_protocol.rb
|
42
|
+
- lib/smartcard/iso/jcop_remote_server.rb
|
43
|
+
- lib/smartcard/iso/jcop_remote_transport.rb
|
44
|
+
- lib/smartcard/iso/pcsc_transport.rb
|
45
|
+
- lib/smartcard/iso/transport.rb
|
46
|
+
- lib/smartcard/pcsc/pcsc_exception.rb
|
39
47
|
- lib/smartcard.rb
|
40
48
|
- LICENSE
|
41
49
|
- README
|
@@ -55,16 +63,28 @@ files:
|
|
55
63
|
- ext/smartcard_pcsc/pcsc_reader_states.c
|
56
64
|
- ext/smartcard_pcsc/pcsc_surrogate_reader.h
|
57
65
|
- ext/smartcard_pcsc/pcsc_surrogate_wintypes.h
|
58
|
-
- lib/smartcard/
|
66
|
+
- lib/smartcard/gp/gp_card_mixin.rb
|
67
|
+
- lib/smartcard/iso/auto_configurator.rb
|
68
|
+
- lib/smartcard/iso/iso_card_mixin.rb
|
69
|
+
- lib/smartcard/iso/jcop_remote_protocol.rb
|
70
|
+
- lib/smartcard/iso/jcop_remote_server.rb
|
71
|
+
- lib/smartcard/iso/jcop_remote_transport.rb
|
72
|
+
- lib/smartcard/iso/pcsc_transport.rb
|
73
|
+
- lib/smartcard/iso/transport.rb
|
74
|
+
- lib/smartcard/pcsc/pcsc_exception.rb
|
59
75
|
- lib/smartcard.rb
|
60
76
|
- LICENSE
|
61
77
|
- Manifest
|
78
|
+
- Rakefile
|
62
79
|
- README
|
63
|
-
- test/test_containers.rb
|
64
|
-
- test/test_smoke.rb
|
65
|
-
- tests/ts_pcsc_ext.rb
|
66
80
|
- smartcard.gemspec
|
67
|
-
-
|
81
|
+
- test/gp/gp_card_mixin_test.rb
|
82
|
+
- test/iso/auto_configurator_test.rb
|
83
|
+
- test/iso/iso_card_mixin_test.rb
|
84
|
+
- test/iso/jcop_remote_test.rb
|
85
|
+
- test/pcsc/containers_test.rb
|
86
|
+
- test/pcsc/smoke_test.rb
|
87
|
+
- tests/ts_pcsc_ext.rb
|
68
88
|
- lib/smartcard/pcsc.so
|
69
89
|
has_rdoc: true
|
70
90
|
homepage: http://www.costan.us/smartcard
|
@@ -96,10 +116,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
116
|
requirements: []
|
97
117
|
|
98
118
|
rubyforge_project: smartcard
|
99
|
-
rubygems_version: 1.3.
|
119
|
+
rubygems_version: 1.3.5
|
100
120
|
signing_key:
|
101
121
|
specification_version: 3
|
102
122
|
summary: Interface with ISO 7816 smart cards.
|
103
123
|
test_files:
|
104
|
-
- test/
|
105
|
-
- test/
|
124
|
+
- test/gp/gp_card_mixin_test.rb
|
125
|
+
- test/iso/auto_configurator_test.rb
|
126
|
+
- test/iso/iso_card_mixin_test.rb
|
127
|
+
- test/iso/jcop_remote_test.rb
|
128
|
+
- test/pcsc/containers_test.rb
|
129
|
+
- test/pcsc/smoke_test.rb
|