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 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
@@ -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/pcsc_exception.rb
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
- test/test_containers.rb
22
- test/test_smoke.rb
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", 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");
@@ -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
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.3.2"
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-06-01}
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/test_containers.rb", "test/test_smoke.rb", "tests/ts_pcsc_ext.rb", "smartcard.gemspec", "Rakefile", "lib/smartcard/pcsc.so"]
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.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: 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-06-01 00:00:00 -04:00
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/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
  - 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.4
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/test_containers.rb
105
- - test/test_smoke.rb
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