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 +2 -0
- data/Manifest +37 -23
- data/ext/smartcard_pcsc/pcsc_constants.c +1 -1
- data/ext/smartcard_pcsc/pcsc_context.c +1 -1
- data/lib/smartcard.rb +14 -0
- 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/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
@@ -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/
|
17
|
-
lib/smartcard.rb
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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",
|
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");
|
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
|
File without changes
|
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
|
|
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-
|
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/
|
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.
|
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: 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-
|
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/
|
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
|
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.
|
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/
|
104
|
-
- test/
|
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
|