tem_ruby 0.9.2 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/Manifest +47 -36
- data/Rakefile +23 -0
- data/bin/tem_bench +0 -0
- data/bin/tem_ca +0 -0
- data/bin/tem_irb +1 -8
- data/bin/tem_proxy +65 -0
- data/bin/tem_stat +8 -12
- data/dev_ca/config.yml +2 -0
- data/lib/tem/auto_conf.rb +25 -0
- data/lib/tem/buffers.rb +21 -34
- data/lib/tem/crypto_abi.rb +78 -30
- data/lib/tem/keys.rb +21 -22
- data/lib/tem/lifecycle.rb +2 -2
- data/lib/tem/seclosures.rb +9 -13
- data/lib/tem/tag.rb +19 -14
- data/lib/tem/tem.rb +9 -25
- data/lib/tem/transport/auto_configurator.rb +87 -0
- data/lib/tem/transport/java_card_mixin.rb +99 -0
- data/lib/tem/transport/jcop_remote_protocol.rb +51 -0
- data/lib/tem/transport/jcop_remote_server.rb +171 -0
- data/lib/tem/transport/jcop_remote_transport.rb +65 -0
- data/lib/tem/transport/pcsc_transport.rb +87 -0
- data/lib/tem/transport/transport.rb +10 -0
- data/lib/tem_ruby.rb +12 -4
- data/tem_ruby.gemspec +24 -40
- data/test/_test_cert.rb +2 -13
- data/test/tem_test_case.rb +26 -0
- data/test/test_driver.rb +3 -22
- data/test/test_exceptions.rb +3 -22
- data/test/test_tem.rb +2 -21
- data/test/transport/test_auto_configurator.rb +114 -0
- data/test/transport/test_java_card_mixin.rb +90 -0
- data/test/transport/test_jcop_remote.rb +82 -0
- data/timings/timings.rb +2 -9
- metadata +94 -62
- data/lib/scard/java_card.rb +0 -31
- data/lib/scard/jcop_remote_terminal.rb +0 -52
- data/lib/scard/pcsc_terminal.rb +0 -83
data/lib/tem/lifecycle.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Tem::Lifecycle
|
2
2
|
def activate
|
3
|
-
|
3
|
+
@transport.applet_apdu(:ins => 0x10)[:status] == 0x9000
|
4
4
|
end
|
5
5
|
def kill
|
6
|
-
|
6
|
+
@transport.applet_apdu(:ins => 0x11)[:status] == 0x9000
|
7
7
|
end
|
8
8
|
end
|
data/lib/tem/seclosures.rb
CHANGED
@@ -14,8 +14,7 @@ module Tem::SeClosures
|
|
14
14
|
|
15
15
|
def sec_trace
|
16
16
|
#begin
|
17
|
-
|
18
|
-
trace = reply_data(response)
|
17
|
+
trace = @transport.applet_apdu! :ins => 0x54
|
19
18
|
if trace.length > 2
|
20
19
|
case read_tem_short(trace, 0) # trace version
|
21
20
|
when 1
|
@@ -32,22 +31,20 @@ module Tem::SeClosures
|
|
32
31
|
def solve_psfault
|
33
32
|
# TODO: better strategy, lol
|
34
33
|
next_cell = rand(16)
|
35
|
-
|
36
|
-
tem_error(response) if failure_code(response)
|
34
|
+
@transport.applet_apdu! :ins => 0x53, :p12 => to_tem_ushort(next_cell)
|
37
35
|
end
|
38
36
|
|
39
37
|
def execute(secpack, key_id = 0)
|
40
38
|
# load SECpack
|
41
39
|
buffer_id = post_buffer(secpack.tem_formatted_body)
|
42
|
-
response =
|
43
|
-
|
40
|
+
response = @transport.applet_apdu! :ins => 0x50, :p1 => buffer_id,
|
41
|
+
:p2 => key_id
|
44
42
|
tem_secpack_error(response) if read_tem_byte(response, 0) != 1
|
45
43
|
|
46
44
|
# execute SEC
|
47
45
|
sec_exception = nil
|
48
46
|
loop do
|
49
|
-
response =
|
50
|
-
tem_error(response) if failure_code(response)
|
47
|
+
response = @transport.applet_apdu! :ins => 0x52
|
51
48
|
sec_status = read_tem_byte(response, 0)
|
52
49
|
case sec_status
|
53
50
|
when 2 # success
|
@@ -67,15 +64,14 @@ module Tem::SeClosures
|
|
67
64
|
end
|
68
65
|
end
|
69
66
|
|
70
|
-
# TODO: handle response to figure out if we need to do page faults or something
|
71
|
-
|
72
67
|
# unbind SEC
|
73
|
-
response =
|
68
|
+
response = @transport.applet_apdu! :ins => 0x51
|
74
69
|
raise sec_exception if sec_exception
|
75
|
-
buffer_id
|
70
|
+
buffer_id = read_tem_byte(response, 0)
|
71
|
+
buffer_length = read_tem_short(response, 1)
|
76
72
|
data_buffer = read_buffer buffer_id
|
77
73
|
release_buffer buffer_id
|
78
74
|
|
79
75
|
return data_buffer[0...buffer_length]
|
80
76
|
end
|
81
|
-
end
|
77
|
+
end
|
data/lib/tem/tag.rb
CHANGED
@@ -1,28 +1,33 @@
|
|
1
1
|
module Tem::Tag
|
2
|
-
def set_tag(tag_data)
|
3
|
-
buffer_id = post_buffer
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
def set_tag(tag_data)
|
3
|
+
buffer_id = post_buffer tag_data
|
4
|
+
begin
|
5
|
+
@transport.applet_apdu! :ins => 0x30, :p1 => buffer_id
|
6
|
+
ensure
|
7
|
+
release_buffer buffer_id
|
8
|
+
end
|
7
9
|
end
|
8
10
|
|
9
11
|
def get_tag_length
|
10
|
-
response =
|
11
|
-
tem_error(response) if failure_code(response)
|
12
|
+
response = @transport.applet_apdu! :ins => 0x31
|
12
13
|
return read_tem_short(response, 0)
|
13
14
|
end
|
14
15
|
|
15
16
|
def get_tag_data(offset, length)
|
16
|
-
buffer_id = alloc_buffer
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
buffer_id = alloc_buffer length
|
18
|
+
begin
|
19
|
+
@transport.applet_apdu! :ins => 0x32, :p1 => buffer_id,
|
20
|
+
:data => [to_tem_short(offset),
|
21
|
+
to_tem_short(length)].flatten
|
22
|
+
tag_data = read_buffer buffer_id
|
23
|
+
ensure
|
24
|
+
release_buffer buffer_id
|
25
|
+
end
|
26
|
+
tag_data
|
22
27
|
end
|
23
28
|
|
24
29
|
def get_tag
|
25
30
|
tag_length = self.get_tag_length
|
26
|
-
get_tag_data
|
31
|
+
get_tag_data 0, tag_length
|
27
32
|
end
|
28
33
|
end
|
data/lib/tem/tem.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'pp'
|
2
|
-
|
3
1
|
class Tem::Session
|
4
2
|
include Tem::Abi
|
5
3
|
include Tem::Buffers
|
@@ -12,33 +10,19 @@ class Tem::Session
|
|
12
10
|
include Tem::Tag
|
13
11
|
include Tem::Toolkit
|
14
12
|
|
15
|
-
|
13
|
+
CAPPLET_AID = [0x19, 0x83, 0x12, 0x29, 0x10, 0xBA, 0xBE]
|
16
14
|
|
17
|
-
|
18
|
-
@card = javacard
|
19
|
-
@card.select_applet(@@aid)
|
20
|
-
end
|
15
|
+
attr_reader :transport
|
21
16
|
|
22
|
-
def
|
23
|
-
|
24
|
-
@
|
17
|
+
def initialize(transport)
|
18
|
+
@transport = transport
|
19
|
+
@transport.select_applet CAPPLET_AID
|
25
20
|
end
|
26
21
|
|
27
|
-
def
|
28
|
-
@
|
29
|
-
|
30
|
-
|
31
|
-
def failure_code(reply_apdu)
|
32
|
-
@card.failure_code reply_apdu
|
33
|
-
end
|
34
|
-
|
35
|
-
def reply_data(reply_apdu)
|
36
|
-
@card.reply_data reply_apdu
|
37
|
-
end
|
38
|
-
|
39
|
-
def tem_error(response)
|
40
|
-
fcode = failure_code response
|
41
|
-
raise "TEM returned error 0x#{'%04x' % fcode} while processing the request"
|
22
|
+
def disconnect
|
23
|
+
return unless @transport
|
24
|
+
@transport.disconnect
|
25
|
+
@transport = nil
|
42
26
|
end
|
43
27
|
|
44
28
|
def tem_secpack_error(response)
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Tem::Transport
|
3
|
+
|
4
|
+
# Automatic configuration code.
|
5
|
+
module AutoConfigurator
|
6
|
+
# The name of the environment variable that might supply the transport
|
7
|
+
# configuration.
|
8
|
+
ENVIRONMENT_VARIABLE_NAME = 'TEM_PORT'
|
9
|
+
|
10
|
+
# The default configurations to be tried if no configuration is specified.
|
11
|
+
DEFAULT_CONFIGURATIONS = [
|
12
|
+
{ :class => JcopRemoteTransport,
|
13
|
+
:opts => { :host => '127.0.0.1', :port => 8050} },
|
14
|
+
{ :class => PcscTransport, :opts => { :reader_index => 0 }}
|
15
|
+
]
|
16
|
+
|
17
|
+
# Creates a transport based on available configuration information.
|
18
|
+
def self.auto_transport
|
19
|
+
configuration = env_configuration
|
20
|
+
return try_transport(configuration) if configuration
|
21
|
+
|
22
|
+
DEFAULT_CONFIGURATIONS.each do |config|
|
23
|
+
transport = try_transport(config)
|
24
|
+
return transport if transport
|
25
|
+
end
|
26
|
+
return nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# Retrieves transport configuration information from an environment variable.
|
30
|
+
#
|
31
|
+
# :call-seq:
|
32
|
+
# AutoConfigurator.env_configuration -> hash
|
33
|
+
#
|
34
|
+
# The returned configuration has the keys required by
|
35
|
+
# AutoConfigurator#try_transport
|
36
|
+
def self.env_configuration
|
37
|
+
return nil unless conf = ENV[ENVIRONMENT_VARIABLE_NAME]
|
38
|
+
|
39
|
+
case conf[0]
|
40
|
+
when ?:
|
41
|
+
# :8050 -- JCOP emulator at port 8050
|
42
|
+
transport_class = JcopRemoteTransport
|
43
|
+
transport_opts = { :host => '127.0.0.1' }
|
44
|
+
transport_opts[:port] = conf[1..-1].to_i
|
45
|
+
when ?@
|
46
|
+
# @127.0.0.1:8050 -- JCOP emulator at host 127.0.0.1 port 8050
|
47
|
+
transport_class = JcopRemoteTransport
|
48
|
+
port_index = conf.rindex(?:) || conf.length
|
49
|
+
transport_opts = { :host => conf[1...port_index] }
|
50
|
+
transport_opts[:port] = conf[(port_index + 1)..-1].to_i
|
51
|
+
when ?#
|
52
|
+
# #2 -- 2nd PC/SC reader in the system
|
53
|
+
transport_class = PcscTransport
|
54
|
+
transport_opts = { :reader_index => conf[1..-1].to_i - 1 }
|
55
|
+
else
|
56
|
+
# Reader Name -- the PC/SC reader with the given name
|
57
|
+
transport_class = PcscTransport
|
58
|
+
transport_opts = { :reader_name => conf }
|
59
|
+
end
|
60
|
+
|
61
|
+
transport_opts[:port] = 8050 if transport_opts[:port] == 0
|
62
|
+
if transport_opts[:reader_index] and transport_opts[:reader_index] < 0
|
63
|
+
transport_opts[:reader_index] = 0
|
64
|
+
end
|
65
|
+
{ :class => transport_class, :opts => transport_opts }
|
66
|
+
end
|
67
|
+
|
68
|
+
# Attempts to create a new TEM transport with the given configuration.
|
69
|
+
# :call-seq:
|
70
|
+
# AutoConfigurator.try_transport(configuration) -> Transport or nil
|
71
|
+
#
|
72
|
+
# The configuration should have the following keys:
|
73
|
+
# class:: the Ruby class implementing the transport
|
74
|
+
# opts:: the options to be passed to the implementation's constructor
|
75
|
+
def self.try_transport(configuration)
|
76
|
+
raise 'No transport class specified' unless configuration[:class]
|
77
|
+
begin
|
78
|
+
transport = configuration[:class].new(configuration[:opts] || {})
|
79
|
+
transport.connect
|
80
|
+
return transport
|
81
|
+
rescue Exception
|
82
|
+
return nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end # module AutoConfigurator
|
86
|
+
|
87
|
+
end # module Tem::Transport
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Tem::Transport
|
3
|
+
|
4
|
+
# Module intended to be mixed into transport implementations to mediate between
|
5
|
+
# a high level format for Javacard-specific APDUs and the wire-level APDU
|
6
|
+
# request and response formats.
|
7
|
+
#
|
8
|
+
# The mix-in calls exchange_apdu in the transport implementation. It supplies
|
9
|
+
# the APDU data as an array of integers between 0 and 255, and expects a
|
10
|
+
# response in the same format.
|
11
|
+
module JavaCardMixin
|
12
|
+
# Selects a Javacard applet.
|
13
|
+
def select_applet(applet_id)
|
14
|
+
applet_apdu! :ins => 0xA4, :p1 => 0x04, :p2 => 0x00, :data => applet_id
|
15
|
+
end
|
16
|
+
|
17
|
+
# APDU exchange with the JavaCard applet, raising an exception if the return
|
18
|
+
# code is not success (0x9000).
|
19
|
+
#
|
20
|
+
# :call_seq:
|
21
|
+
# transport.applet_apdu!(apdu_data) -> array
|
22
|
+
#
|
23
|
+
# The apdu_data should be in the format expected by
|
24
|
+
# JavaCardMixin#serialize_apdu. Returns the response data, if the response
|
25
|
+
# status indicates success (0x9000). Otherwise, raises an exeception.
|
26
|
+
def applet_apdu!(apdu_data)
|
27
|
+
response = self.applet_apdu apdu_data
|
28
|
+
return response[:data] if response[:status] == 0x9000
|
29
|
+
raise "JavaCard response has error status 0x#{'%04x' % response[:status]}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Performs an APDU exchange with the JavaCard applet.
|
33
|
+
#
|
34
|
+
# :call-seq:
|
35
|
+
# transport.applet_apdu(apdu_data) -> hash
|
36
|
+
#
|
37
|
+
# The apdu_data should be in the format expected by
|
38
|
+
# JavaCardMixin#serialize_apdu. The response will be as specified in
|
39
|
+
# JavaCardMixin#deserialize_response.
|
40
|
+
def applet_apdu(apdu_data)
|
41
|
+
apdu = Tem::Transport::JavaCardMixin.serialize_apdu apdu_data
|
42
|
+
response = self.exchange_apdu apdu
|
43
|
+
JavaCardMixin.deserialize_response response
|
44
|
+
end
|
45
|
+
|
46
|
+
# Serializes an APDU for wire transmission.
|
47
|
+
#
|
48
|
+
# :call-seq:
|
49
|
+
# transport.wire_apdu(apdu_data) -> array
|
50
|
+
#
|
51
|
+
# The following keys are recognized in the APDU hash:
|
52
|
+
# cla:: the CLA byte in the APDU (optional, defaults to 0)
|
53
|
+
# ins:: the INS byte in the APDU -- the first byte seen by a JavaCard applet
|
54
|
+
# p::
|
55
|
+
# p1, p2:: the P1 and P2 bytes in the APDU (optional, both default to 0)
|
56
|
+
# data:: the extra data in the APDU (optional, defaults to nothing)
|
57
|
+
def self.serialize_apdu(apdu_data)
|
58
|
+
raise 'Unspecified INS in apdu_data' unless apdu_data[:ins]
|
59
|
+
apdu = [ apdu_data[:cla] || 0, apdu_data[:ins] ]
|
60
|
+
if apdu_data[:p12]
|
61
|
+
unless apdu_data[:p12].length == 2
|
62
|
+
raise "Malformed P1,P2 - #{apdu_data[:p12]}"
|
63
|
+
end
|
64
|
+
apdu += apdu_data[:p12]
|
65
|
+
else
|
66
|
+
apdu << (apdu_data[:p1] || 0)
|
67
|
+
apdu << (apdu_data[:p2] || 0)
|
68
|
+
end
|
69
|
+
if apdu_data[:data]
|
70
|
+
apdu << apdu_data[:data].length
|
71
|
+
apdu += apdu_data[:data]
|
72
|
+
else
|
73
|
+
apdu << 0
|
74
|
+
end
|
75
|
+
apdu
|
76
|
+
end
|
77
|
+
|
78
|
+
# De-serializes a JavaCard response APDU.
|
79
|
+
#
|
80
|
+
# :call-seq:
|
81
|
+
# transport.deserialize_response(response) -> hash
|
82
|
+
#
|
83
|
+
# The response contains the following keys:
|
84
|
+
# status:: the 2-byte status code (e.g. 0x9000 is OK)
|
85
|
+
# data:: the additional data in the response
|
86
|
+
def self.deserialize_response(response)
|
87
|
+
{ :status => response[-2] * 256 + response[-1], :data => response[0...-2] }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Installs a JavaCard applet on the JavaCard.
|
91
|
+
#
|
92
|
+
# This would be really, really nice to have. Sadly, it's a far away TBD right
|
93
|
+
# now.
|
94
|
+
def install_applet(cap_contents)
|
95
|
+
raise "Not implemeted; it'd be nice though, right?"
|
96
|
+
end
|
97
|
+
end # module Tem
|
98
|
+
|
99
|
+
end # module Tem::Transport
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Tem::Transport
|
3
|
+
|
4
|
+
# Mixin implementing the JCOP simulator protocol.
|
5
|
+
#
|
6
|
+
# The (pretty informal) protocol specification is contained in the JavaDocs for
|
7
|
+
# the class com.ibm.jc.terminal.RemoteJCTerminal and should be easy to find by
|
8
|
+
# http://www.google.com/search?q=%22com.ibm.jc.terminal.RemoteJCTerminal%22
|
9
|
+
module JcopRemoteProtocol
|
10
|
+
# Encodes and sends a JCOP simulator message to a TCP socket.
|
11
|
+
#
|
12
|
+
# The message must contain the following keys:
|
13
|
+
# type:: Integer expressing the message type (e.g. 1 = APDU exchange)
|
14
|
+
# node:: Integer expressing the node address (e.g. 0 for most purposes)
|
15
|
+
# data:: message payload, as an array of Integers ranging from 0 to 255
|
16
|
+
def send_message(socket, message)
|
17
|
+
raw_message = [message[:type], message[:node], message[:data].length].
|
18
|
+
pack('CCn') + message[:data].pack('C*')
|
19
|
+
socket.send raw_message, 0
|
20
|
+
end
|
21
|
+
|
22
|
+
# Reads and decodes a JCOP simulator message from a TCP socket.
|
23
|
+
#
|
24
|
+
# :call_seq:
|
25
|
+
# client.read_message(socket) -> Hash or nil
|
26
|
+
#
|
27
|
+
# If the other side of the TCP socket closes the connection, this method
|
28
|
+
# returns nil. Otherwise, a Hash is returned, with the format required by the
|
29
|
+
# JcopRemoteProtocol#send_message.
|
30
|
+
def recv_message(socket)
|
31
|
+
header = ''
|
32
|
+
while header.length < 4
|
33
|
+
partial = socket.recv 4 - header.length
|
34
|
+
return false if partial.length == 0
|
35
|
+
header += partial
|
36
|
+
end
|
37
|
+
message_type, node_address, data_length = *header.unpack('CCn')
|
38
|
+
raw_data = ''
|
39
|
+
while raw_data.length < data_length
|
40
|
+
partial = socket.recv data_length - raw_data.length
|
41
|
+
return false if partial.length == 0
|
42
|
+
raw_data += partial
|
43
|
+
end
|
44
|
+
|
45
|
+
return false unless raw_data.length == data_length
|
46
|
+
data = raw_data.unpack('C*')
|
47
|
+
return { :type => message_type, :node => node_address, :data => data }
|
48
|
+
end
|
49
|
+
end # module JcopRemoteProtocol
|
50
|
+
|
51
|
+
end # namespace Tem::Transport
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
# :nodoc: namespace
|
4
|
+
module Tem::Transport
|
5
|
+
|
6
|
+
# Stubs out the methods that can be implemented by the serving logic in a
|
7
|
+
# JCOP remote server. Serving logic classes should mix in this module, to
|
8
|
+
# avoid having unimplemented methods.
|
9
|
+
module JcopRemoteServingStubs
|
10
|
+
# Called when a client connection accepted.
|
11
|
+
#
|
12
|
+
# This method serves as a notification to the serving logic implementation.
|
13
|
+
# Its return value is discarded.
|
14
|
+
def connection_start
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# Called when a client connection is closed.
|
19
|
+
#
|
20
|
+
# This method serves as a notification to the serving logic implementation.
|
21
|
+
# Its return value is discarded.
|
22
|
+
def connection_end
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# Serving logic handling an APDU exchange.
|
27
|
+
#
|
28
|
+
# :call-seq:
|
29
|
+
# logic.exchange_apdu(apdu) -> array
|
30
|
+
#
|
31
|
+
# The |apdu| parameter is the request APDU, formatted as an array of
|
32
|
+
# integers between 0 and 255. The method should return the response APDU,
|
33
|
+
# formatted in a similar manner.
|
34
|
+
def exchange_apdu(apdu)
|
35
|
+
# Dumb implementation that always returns OK.
|
36
|
+
[0x90, 0x00]
|
37
|
+
end
|
38
|
+
end # module JcopRemoteServingStubs
|
39
|
+
|
40
|
+
|
41
|
+
# A server for the JCOP simulator protocol.
|
42
|
+
#
|
43
|
+
# The JCOP simulator protocol is generally useful when talking to a real JCOP
|
44
|
+
# simulator. This server is only handy for testing, and for forwarding
|
45
|
+
# connections (JCOP's Eclipse plug-in makes the simulator listen to 127.0.0.1,
|
46
|
+
# and sometimes you want to use it from another box).
|
47
|
+
class JcopRemoteServer
|
48
|
+
include JcopRemoteProtocol
|
49
|
+
|
50
|
+
# Creates a new JCOP server.
|
51
|
+
#
|
52
|
+
# The options hash supports the following keys:
|
53
|
+
# port:: the port to serve on
|
54
|
+
# ip:: the IP of the interface to serve on (defaults to all interfaces)
|
55
|
+
# reusable:: if set, the serving port can be shared with another
|
56
|
+
# application (REUSEADDR flag will be set on the socket)
|
57
|
+
#
|
58
|
+
# If the |serving_logic| parameter is nil, a serving logic implementation
|
59
|
+
# must be provided when calling JcopRemoteServer#run. The server will crash
|
60
|
+
# otherwise.
|
61
|
+
def initialize(options, serving_logic = nil)
|
62
|
+
@logic = serving_logic
|
63
|
+
@running = false
|
64
|
+
@options = options
|
65
|
+
@mutex = Mutex.new
|
66
|
+
end
|
67
|
+
|
68
|
+
# Runs the serving loop indefinitely.
|
69
|
+
#
|
70
|
+
# This method serves incoming conenctions until #stop is called.
|
71
|
+
#
|
72
|
+
# If |serving_logic| contains a non-nil value, it overrides any previously
|
73
|
+
# specified serving logic implementation. If no implementation is specified
|
74
|
+
# when the server is instantiated via JcopRemoteServer#new, one must be
|
75
|
+
# passed into |serving_logic|.
|
76
|
+
def run(serving_logic = nil)
|
77
|
+
@mutex.synchronize do
|
78
|
+
@logic ||= serving_logic
|
79
|
+
@serving_socket = serving_socket @options
|
80
|
+
@running = true
|
81
|
+
end
|
82
|
+
loop do
|
83
|
+
break unless @mutex.synchronize { @running }
|
84
|
+
begin
|
85
|
+
client_socket, client_address = @serving_socket.accept
|
86
|
+
rescue
|
87
|
+
# An exception will occur if the socket is closed
|
88
|
+
break
|
89
|
+
end
|
90
|
+
@logic.connection_start
|
91
|
+
loop do
|
92
|
+
break unless @mutex.synchronize { @running }
|
93
|
+
break unless process_request client_socket
|
94
|
+
end
|
95
|
+
client_socket.close rescue nil
|
96
|
+
@logic.connection_end # implemented by subclass
|
97
|
+
end
|
98
|
+
@mutex.synchronize do
|
99
|
+
@serving_socket.close if @serving_socket
|
100
|
+
@serving_socket = nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Stops the serving loop.
|
105
|
+
def stop
|
106
|
+
@mutex.synchronize do
|
107
|
+
if @running
|
108
|
+
@serving_socket.close rescue nil
|
109
|
+
@serving_socket = nil
|
110
|
+
@running = false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# TODO(costan): figure out a way to let serving logic reach this directly.
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
# Creates a socket listening to incoming connections to this server.
|
119
|
+
#
|
120
|
+
# :call-seq:
|
121
|
+
# server.establish_socket(options) -> Socket
|
122
|
+
#
|
123
|
+
# The |options| parameter supports the same keys as the options parameter
|
124
|
+
# of JcopRemoteServer#new.
|
125
|
+
#
|
126
|
+
# Returns a Socket configured to accept incoming connections.
|
127
|
+
def serving_socket(options)
|
128
|
+
port = options[:port] || 0
|
129
|
+
interface_ip = options[:ip] || '0.0.0.0'
|
130
|
+
serving_address = Socket.pack_sockaddr_in port, interface_ip
|
131
|
+
|
132
|
+
socket = Socket.new Socket::AF_INET, Socket::SOCK_STREAM,
|
133
|
+
Socket::PF_UNSPEC
|
134
|
+
|
135
|
+
if options[:reusable]
|
136
|
+
socket.setsockopt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true
|
137
|
+
end
|
138
|
+
socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true
|
139
|
+
socket.bind serving_address
|
140
|
+
socket.listen 5
|
141
|
+
socket
|
142
|
+
end
|
143
|
+
private :serving_socket
|
144
|
+
|
145
|
+
# Performs a request/response cycle.
|
146
|
+
#
|
147
|
+
# :call-seq:
|
148
|
+
# server.process_request(socket) -> Boolean
|
149
|
+
#
|
150
|
+
# Returns true if the server should do another request/response cycle, or
|
151
|
+
# false if this client indicated it's done talking to the server.
|
152
|
+
def process_request(socket)
|
153
|
+
return false unless request = recv_message(socket)
|
154
|
+
|
155
|
+
case request[:type]
|
156
|
+
when 0
|
157
|
+
# Wait for card; no-op, because that should have happen when the client
|
158
|
+
# connected.
|
159
|
+
send_message socket, :type => 0, :node => 0, :data => [3, 1, 4, 1, 5, 9]
|
160
|
+
when 1
|
161
|
+
# ATR exchange; the class' bread and butter
|
162
|
+
response = @logic.exchange_apdu request[:data]
|
163
|
+
send_message socket, :type => 1, :node => 0, :data => response
|
164
|
+
else
|
165
|
+
send_message socket, :type => request[:type], :node => 0, :data => []
|
166
|
+
end
|
167
|
+
end
|
168
|
+
private :process_request
|
169
|
+
end # module JcopRemoteServer
|
170
|
+
|
171
|
+
end # namespace Tem::Transport
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
# :nodoc: namespace
|
4
|
+
module Tem::Transport
|
5
|
+
|
6
|
+
# Implements the transport layer for a JCOP simulator instance.
|
7
|
+
class JcopRemoteTransport
|
8
|
+
include JavaCardMixin
|
9
|
+
include JcopRemoteProtocol
|
10
|
+
|
11
|
+
# Creates a new unconnected transport for a JCOP simulator serving TCP/IP.
|
12
|
+
#
|
13
|
+
# The options parameter must have the following keys:
|
14
|
+
# host:: the DNS name or IP of the host running the JCOP simulator
|
15
|
+
# port:: the TCP/IP port of the JCOP simulator server
|
16
|
+
def initialize(options)
|
17
|
+
@host, @port = options[:host], options[:port]
|
18
|
+
@socket = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
def exchange_apdu(apdu)
|
23
|
+
send_message @socket, :type => 1, :node => 0, :data => apdu
|
24
|
+
recv_message(@socket)[:data]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Makes a transport-level connection to the TEM.
|
28
|
+
def connect
|
29
|
+
begin
|
30
|
+
Socket.getaddrinfo(@host, @port, Socket::AF_INET,
|
31
|
+
Socket::SOCK_STREAM).each do |addr_info|
|
32
|
+
begin
|
33
|
+
@socket = Socket.new(addr_info[4], addr_info[5], addr_info[6])
|
34
|
+
@socket.connect Socket.pack_sockaddr_in(addr_info[1], addr_info[3])
|
35
|
+
break
|
36
|
+
rescue
|
37
|
+
@socket = nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
raise 'Connection refused' unless @socket
|
41
|
+
|
42
|
+
# Wait for the card to be inserted.
|
43
|
+
send_message @socket, :type => 0, :node => 0, :data => [0, 1, 0, 0]
|
44
|
+
recv_message @socket # ATR should come here, but who cares
|
45
|
+
rescue Exception
|
46
|
+
@socket = nil
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Breaks down the transport-level connection to the TEM.
|
52
|
+
def disconnect
|
53
|
+
if @socket
|
54
|
+
@socket.close
|
55
|
+
@socket = nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
"#<JCOP Remote Terminal: disconnected>" if @socket.nil?
|
61
|
+
"#<JCOP Remote Terminal: #{@host}:#{@port}>"
|
62
|
+
end
|
63
|
+
end # class JcopRemoteTransport
|
64
|
+
|
65
|
+
end # module Tem::Transport
|