costan-tem_ruby 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|