costan-tem_ruby 0.10.2
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 +45 -0
- data/LICENSE +21 -0
- data/Manifest +75 -0
- data/README +8 -0
- data/Rakefile +23 -0
- data/bin/tem_bench +9 -0
- data/bin/tem_ca +13 -0
- data/bin/tem_irb +11 -0
- data/bin/tem_proxy +65 -0
- data/bin/tem_stat +35 -0
- data/dev_ca/ca_cert.cer +0 -0
- data/dev_ca/ca_cert.pem +32 -0
- data/dev_ca/ca_key.pem +27 -0
- data/dev_ca/config.yml +14 -0
- data/lib/tem/_cert.rb +158 -0
- data/lib/tem/apdus/buffers.rb +89 -0
- data/lib/tem/apdus/keys.rb +64 -0
- data/lib/tem/apdus/lifecycle.rb +13 -0
- data/lib/tem/apdus/tag.rb +38 -0
- data/lib/tem/auto_conf.rb +25 -0
- data/lib/tem/builders/abi.rb +482 -0
- data/lib/tem/builders/assembler.rb +314 -0
- data/lib/tem/builders/crypto.rb +124 -0
- data/lib/tem/builders/isa.rb +120 -0
- data/lib/tem/ca.rb +114 -0
- data/lib/tem/definitions/abi.rb +65 -0
- data/lib/tem/definitions/assembler.rb +23 -0
- data/lib/tem/definitions/isa.rb +188 -0
- data/lib/tem/ecert.rb +77 -0
- data/lib/tem/hive.rb +18 -0
- data/lib/tem/keys/asymmetric.rb +116 -0
- data/lib/tem/keys/key.rb +48 -0
- data/lib/tem/keys/symmetric.rb +47 -0
- data/lib/tem/sec_exec_error.rb +63 -0
- data/lib/tem/seclosures.rb +81 -0
- data/lib/tem/secpack.rb +107 -0
- data/lib/tem/tem.rb +31 -0
- data/lib/tem/toolkit.rb +101 -0
- 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 +59 -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 +47 -0
- data/tem_ruby.gemspec +35 -0
- data/test/_test_cert.rb +70 -0
- data/test/builders/test_abi_builder.rb +298 -0
- data/test/tem_test_case.rb +26 -0
- data/test/tem_unit/test_tem_alu.rb +33 -0
- data/test/tem_unit/test_tem_bound_secpack.rb +51 -0
- data/test/tem_unit/test_tem_branching.rb +56 -0
- data/test/tem_unit/test_tem_crypto_asymmetric.rb +123 -0
- data/test/tem_unit/test_tem_crypto_hash.rb +35 -0
- data/test/tem_unit/test_tem_crypto_pstore.rb +53 -0
- data/test/tem_unit/test_tem_crypto_random.rb +25 -0
- data/test/tem_unit/test_tem_emit.rb +23 -0
- data/test/tem_unit/test_tem_memory.rb +48 -0
- data/test/tem_unit/test_tem_memory_compare.rb +65 -0
- data/test/tem_unit/test_tem_output.rb +32 -0
- data/test/tem_unit/test_tem_yaml_secpack.rb +47 -0
- data/test/test_driver.rb +108 -0
- data/test/test_exceptions.rb +35 -0
- 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/blank_bound_secpack.rb +18 -0
- data/timings/blank_sec.rb +14 -0
- data/timings/devchip_decrypt.rb +9 -0
- data/timings/post_buffer.rb +10 -0
- data/timings/simple_apdu.rb +5 -0
- data/timings/timings.rb +64 -0
- data/timings/vm_perf.rb +140 -0
- data/timings/vm_perf_bound.rb +141 -0
- metadata +201 -0
data/lib/tem/tem.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
class Tem::Session
|
2
|
+
include Tem::Abi
|
3
|
+
include Tem::Apdus::Buffers
|
4
|
+
include Tem::Apdus::Keys
|
5
|
+
include Tem::Apdus::Lifecycle
|
6
|
+
include Tem::Apdus::Tag
|
7
|
+
|
8
|
+
include Tem::CA
|
9
|
+
include Tem::ECert
|
10
|
+
include Tem::SeClosures
|
11
|
+
include Tem::Toolkit
|
12
|
+
|
13
|
+
CAPPLET_AID = [0x19, 0x83, 0x12, 0x29, 0x10, 0xBA, 0xBE]
|
14
|
+
|
15
|
+
attr_reader :transport
|
16
|
+
|
17
|
+
def initialize(transport)
|
18
|
+
@transport = transport
|
19
|
+
@transport.select_applet CAPPLET_AID
|
20
|
+
end
|
21
|
+
|
22
|
+
def disconnect
|
23
|
+
return unless @transport
|
24
|
+
@transport.disconnect
|
25
|
+
@transport = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def tem_secpack_error(response)
|
29
|
+
raise "TEM refused the SECpack"
|
30
|
+
end
|
31
|
+
end
|
data/lib/tem/toolkit.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
module Tem::Toolkit
|
2
|
+
def tk_firmware_ver
|
3
|
+
tag = get_tag
|
4
|
+
return { :major => read_tem_ubyte(tag, 0), :minor => read_tem_ubyte(tag, 1) }
|
5
|
+
end
|
6
|
+
|
7
|
+
def tk_gen_key(type = :asymmetric, authz = nil)
|
8
|
+
gen_sec = assemble do |s|
|
9
|
+
s.ldbc authz.nil? ? 24 : 4
|
10
|
+
s.outnew
|
11
|
+
if authz.nil?
|
12
|
+
# no authorization given, must generate one
|
13
|
+
s.ldbc 20
|
14
|
+
s.ldwc :key_auth
|
15
|
+
s.dupn :n => 2
|
16
|
+
s.rnd
|
17
|
+
s.outvb
|
18
|
+
end
|
19
|
+
s.genkp :type => (type == :asymmetric) ? 0x00 : 0x80
|
20
|
+
s.authk :auth => :key_auth
|
21
|
+
s.outw
|
22
|
+
s.authk :auth => :key_auth
|
23
|
+
s.outw
|
24
|
+
s.halt
|
25
|
+
s.label :key_auth
|
26
|
+
if authz.nil?
|
27
|
+
s.zeros :tem_ubyte, 20
|
28
|
+
else
|
29
|
+
s.data :tem_ubyte, authz
|
30
|
+
end
|
31
|
+
s.stack 4
|
32
|
+
end
|
33
|
+
|
34
|
+
kp_buffer = execute gen_sec
|
35
|
+
keys_offset = authz.nil? ? 20 : 0
|
36
|
+
k1id = read_tem_ushort kp_buffer, keys_offset
|
37
|
+
k2id = read_tem_ushort kp_buffer, keys_offset + 2
|
38
|
+
if type == :asymmetric
|
39
|
+
return_val = { :pubk_id => k1id, :privk_id => k2id }
|
40
|
+
else
|
41
|
+
return_val = { :key_id => k1id }
|
42
|
+
end
|
43
|
+
return { :authz => authz.nil? ? kp_buffer[0...20] : authz }.merge!(return_val)
|
44
|
+
end
|
45
|
+
|
46
|
+
def tk_read_key(key_id, authz)
|
47
|
+
read_sec = assemble do |s|
|
48
|
+
s.ldbc :const => key_id
|
49
|
+
s.authk :auth => :key_auth
|
50
|
+
s.ldkl
|
51
|
+
s.outnew
|
52
|
+
s.ldbc :const => key_id
|
53
|
+
s.ldbc(-1)
|
54
|
+
s.stk
|
55
|
+
s.halt
|
56
|
+
s.label :key_auth
|
57
|
+
s.data :tem_ubyte, authz
|
58
|
+
s.stack 4
|
59
|
+
end
|
60
|
+
|
61
|
+
key_string = execute read_sec
|
62
|
+
return read_tem_key(key_string, 0)
|
63
|
+
end
|
64
|
+
|
65
|
+
def tk_delete_key(key_id, authz)
|
66
|
+
del_sec = assemble do |s|
|
67
|
+
s.ldbc :const => key_id
|
68
|
+
s.authk :auth => :key_auth
|
69
|
+
s.relk
|
70
|
+
s.ldbc :const => 1
|
71
|
+
s.outnew
|
72
|
+
s.ldbc :const => key_id
|
73
|
+
s.outb
|
74
|
+
s.halt
|
75
|
+
s.label :key_auth
|
76
|
+
s.data :tem_ubyte, authz
|
77
|
+
s.stack 4
|
78
|
+
end
|
79
|
+
|
80
|
+
execute del_sec
|
81
|
+
end
|
82
|
+
|
83
|
+
def tk_post_key(key, authz)
|
84
|
+
post_sec = assemble do |s|
|
85
|
+
s.ldbc :const => 1
|
86
|
+
s.outnew
|
87
|
+
s.ldwc :const => :key_data
|
88
|
+
s.rdk
|
89
|
+
s.authk :auth => :key_auth
|
90
|
+
s.outb
|
91
|
+
s.halt
|
92
|
+
s.label :key_data
|
93
|
+
s.data :tem_ubyte, key.to_tem_key
|
94
|
+
s.label :key_auth
|
95
|
+
s.data :tem_ubyte, authz
|
96
|
+
s.stack 4
|
97
|
+
end
|
98
|
+
id_string = execute post_sec
|
99
|
+
return read_tem_ubyte(id_string, 0)
|
100
|
+
end
|
101
|
+
end
|
@@ -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,59 @@
|
|
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
|
+
begin
|
34
|
+
partial = socket.recv 4 - header.length
|
35
|
+
rescue # Abrupt hangups result in exceptions that we catch here.
|
36
|
+
return nil
|
37
|
+
end
|
38
|
+
return false if partial.length == 0
|
39
|
+
header += partial
|
40
|
+
end
|
41
|
+
message_type, node_address, data_length = *header.unpack('CCn')
|
42
|
+
raw_data = ''
|
43
|
+
while raw_data.length < data_length
|
44
|
+
begin
|
45
|
+
partial = socket.recv data_length - raw_data.length
|
46
|
+
rescue # Abrupt hangups result in exceptions that we catch here.
|
47
|
+
return nil
|
48
|
+
end
|
49
|
+
return false if partial.length == 0
|
50
|
+
raw_data += partial
|
51
|
+
end
|
52
|
+
|
53
|
+
return false unless raw_data.length == data_length
|
54
|
+
data = raw_data.unpack('C*')
|
55
|
+
return { :type => message_type, :node => node_address, :data => data }
|
56
|
+
end
|
57
|
+
end # module JcopRemoteProtocol
|
58
|
+
|
59
|
+
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
|