smartcard 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,5 @@
1
+ v0.4.0. High-level functionality for ISO7816 cards.
2
+
1
3
  v0.3.2. Fixed documentation for ReaderStates class.
2
4
 
3
5
  v0.3.1. Fixed documentation for the new PcscException class.
data/Manifest CHANGED
@@ -1,23 +1,37 @@
1
- BUILD
2
- CHANGELOG
3
- ext/smartcard_pcsc/extconf.rb
4
- ext/smartcard_pcsc/pcsc.h
5
- ext/smartcard_pcsc/pcsc_card.c
6
- ext/smartcard_pcsc/pcsc_constants.c
7
- ext/smartcard_pcsc/pcsc_context.c
8
- ext/smartcard_pcsc/pcsc_exception.c
9
- ext/smartcard_pcsc/pcsc_io_request.c
10
- ext/smartcard_pcsc/pcsc_main.c
11
- ext/smartcard_pcsc/pcsc_multi_strings.c
12
- ext/smartcard_pcsc/pcsc_namespace.c
13
- ext/smartcard_pcsc/pcsc_reader_states.c
14
- ext/smartcard_pcsc/pcsc_surrogate_reader.h
15
- ext/smartcard_pcsc/pcsc_surrogate_wintypes.h
16
- lib/smartcard/pcsc_exception.rb
17
- lib/smartcard.rb
18
- LICENSE
19
- Manifest
20
- README
21
- test/test_containers.rb
22
- test/test_smoke.rb
23
- tests/ts_pcsc_ext.rb
1
+ BUILD
2
+ CHANGELOG
3
+ ext/smartcard_pcsc/extconf.rb
4
+ ext/smartcard_pcsc/pcsc.h
5
+ ext/smartcard_pcsc/pcsc_card.c
6
+ ext/smartcard_pcsc/pcsc_constants.c
7
+ ext/smartcard_pcsc/pcsc_context.c
8
+ ext/smartcard_pcsc/pcsc_exception.c
9
+ ext/smartcard_pcsc/pcsc_io_request.c
10
+ ext/smartcard_pcsc/pcsc_main.c
11
+ ext/smartcard_pcsc/pcsc_multi_strings.c
12
+ ext/smartcard_pcsc/pcsc_namespace.c
13
+ ext/smartcard_pcsc/pcsc_reader_states.c
14
+ ext/smartcard_pcsc/pcsc_surrogate_reader.h
15
+ ext/smartcard_pcsc/pcsc_surrogate_wintypes.h
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
25
+ lib/smartcard.rb
26
+ LICENSE
27
+ Manifest
28
+ Rakefile
29
+ README
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
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", INT2NUM(INFINITE));
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 = NUM2INT(rbTimeout);
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");
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'
@@ -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
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.3.2"
5
+ s.version = "0.4.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Victor Costan"]
9
- s.date = %q{2009-05-31}
9
+ s.date = %q{2009-08-18}
10
10
  s.description = %q{Interface with ISO 7816 smart cards.}
11
11
  s.email = %q{victor@costan.us}
12
12
  s.extensions = ["ext/smartcard_pcsc/extconf.rb"]
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/test_containers.rb", "test/test_smoke.rb", "tests/ts_pcsc_ext.rb", "smartcard.gemspec", "Rakefile"]
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"]
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.4}
19
+ s.rubygems_version = %q{1.3.5}
20
20
  s.summary = %q{Interface with ISO 7816 smart cards.}
21
- s.test_files = ["test/test_containers.rb", "test/test_smoke.rb"]
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
@@ -1,6 +1,12 @@
1
- require 'test/unit'
1
+ # Author:: Victor Costan
2
+ # Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
3
+ # License:: MIT
4
+
2
5
  require 'smartcard'
3
6
 
7
+ require 'test/unit'
8
+
9
+
4
10
  class ContainersTest < Test::Unit::TestCase
5
11
  def setup
6
12
 
@@ -1,6 +1,12 @@
1
- require 'test/unit'
1
+ # Author:: Victor Costan
2
+ # Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
3
+ # License:: MIT
4
+
2
5
  require 'smartcard'
3
6
 
7
+ require 'test/unit'
8
+
9
+
4
10
  class SmokeTest < Test::Unit::TestCase
5
11
  def setup
6
12
  end
data/tests/ts_pcsc_ext.rb CHANGED
@@ -1,3 +1,7 @@
1
+ # Author:: Victor Costan
2
+ # Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
3
+ # License:: MIT
4
+
1
5
  require 'smartcard'
2
6
  require 'pp'
3
7
 
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.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
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-05-31 00:00:00 -04:00
12
+ date: 2009-08-18 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/pcsc_exception.rb
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/pcsc_exception.rb
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
- - Rakefile
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
  has_rdoc: true
69
89
  homepage: http://www.costan.us/smartcard
70
90
  licenses: []
@@ -95,10 +115,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
115
  requirements: []
96
116
 
97
117
  rubyforge_project: smartcard
98
- rubygems_version: 1.3.4
118
+ rubygems_version: 1.3.5
99
119
  signing_key:
100
120
  specification_version: 3
101
121
  summary: Interface with ISO 7816 smart cards.
102
122
  test_files:
103
- - test/test_containers.rb
104
- - test/test_smoke.rb
123
+ - test/gp/gp_card_mixin_test.rb
124
+ - test/iso/auto_configurator_test.rb
125
+ - test/iso/iso_card_mixin_test.rb
126
+ - test/iso/jcop_remote_test.rb
127
+ - test/pcsc/containers_test.rb
128
+ - test/pcsc/smoke_test.rb