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.
Files changed (76) hide show
  1. data/CHANGELOG +45 -0
  2. data/LICENSE +21 -0
  3. data/Manifest +75 -0
  4. data/README +8 -0
  5. data/Rakefile +23 -0
  6. data/bin/tem_bench +9 -0
  7. data/bin/tem_ca +13 -0
  8. data/bin/tem_irb +11 -0
  9. data/bin/tem_proxy +65 -0
  10. data/bin/tem_stat +35 -0
  11. data/dev_ca/ca_cert.cer +0 -0
  12. data/dev_ca/ca_cert.pem +32 -0
  13. data/dev_ca/ca_key.pem +27 -0
  14. data/dev_ca/config.yml +14 -0
  15. data/lib/tem/_cert.rb +158 -0
  16. data/lib/tem/apdus/buffers.rb +89 -0
  17. data/lib/tem/apdus/keys.rb +64 -0
  18. data/lib/tem/apdus/lifecycle.rb +13 -0
  19. data/lib/tem/apdus/tag.rb +38 -0
  20. data/lib/tem/auto_conf.rb +25 -0
  21. data/lib/tem/builders/abi.rb +482 -0
  22. data/lib/tem/builders/assembler.rb +314 -0
  23. data/lib/tem/builders/crypto.rb +124 -0
  24. data/lib/tem/builders/isa.rb +120 -0
  25. data/lib/tem/ca.rb +114 -0
  26. data/lib/tem/definitions/abi.rb +65 -0
  27. data/lib/tem/definitions/assembler.rb +23 -0
  28. data/lib/tem/definitions/isa.rb +188 -0
  29. data/lib/tem/ecert.rb +77 -0
  30. data/lib/tem/hive.rb +18 -0
  31. data/lib/tem/keys/asymmetric.rb +116 -0
  32. data/lib/tem/keys/key.rb +48 -0
  33. data/lib/tem/keys/symmetric.rb +47 -0
  34. data/lib/tem/sec_exec_error.rb +63 -0
  35. data/lib/tem/seclosures.rb +81 -0
  36. data/lib/tem/secpack.rb +107 -0
  37. data/lib/tem/tem.rb +31 -0
  38. data/lib/tem/toolkit.rb +101 -0
  39. data/lib/tem/transport/auto_configurator.rb +87 -0
  40. data/lib/tem/transport/java_card_mixin.rb +99 -0
  41. data/lib/tem/transport/jcop_remote_protocol.rb +59 -0
  42. data/lib/tem/transport/jcop_remote_server.rb +171 -0
  43. data/lib/tem/transport/jcop_remote_transport.rb +65 -0
  44. data/lib/tem/transport/pcsc_transport.rb +87 -0
  45. data/lib/tem/transport/transport.rb +10 -0
  46. data/lib/tem_ruby.rb +47 -0
  47. data/tem_ruby.gemspec +35 -0
  48. data/test/_test_cert.rb +70 -0
  49. data/test/builders/test_abi_builder.rb +298 -0
  50. data/test/tem_test_case.rb +26 -0
  51. data/test/tem_unit/test_tem_alu.rb +33 -0
  52. data/test/tem_unit/test_tem_bound_secpack.rb +51 -0
  53. data/test/tem_unit/test_tem_branching.rb +56 -0
  54. data/test/tem_unit/test_tem_crypto_asymmetric.rb +123 -0
  55. data/test/tem_unit/test_tem_crypto_hash.rb +35 -0
  56. data/test/tem_unit/test_tem_crypto_pstore.rb +53 -0
  57. data/test/tem_unit/test_tem_crypto_random.rb +25 -0
  58. data/test/tem_unit/test_tem_emit.rb +23 -0
  59. data/test/tem_unit/test_tem_memory.rb +48 -0
  60. data/test/tem_unit/test_tem_memory_compare.rb +65 -0
  61. data/test/tem_unit/test_tem_output.rb +32 -0
  62. data/test/tem_unit/test_tem_yaml_secpack.rb +47 -0
  63. data/test/test_driver.rb +108 -0
  64. data/test/test_exceptions.rb +35 -0
  65. data/test/transport/test_auto_configurator.rb +114 -0
  66. data/test/transport/test_java_card_mixin.rb +90 -0
  67. data/test/transport/test_jcop_remote.rb +82 -0
  68. data/timings/blank_bound_secpack.rb +18 -0
  69. data/timings/blank_sec.rb +14 -0
  70. data/timings/devchip_decrypt.rb +9 -0
  71. data/timings/post_buffer.rb +10 -0
  72. data/timings/simple_apdu.rb +5 -0
  73. data/timings/timings.rb +64 -0
  74. data/timings/vm_perf.rb +140 -0
  75. data/timings/vm_perf_bound.rb +141 -0
  76. metadata +201 -0
data/lib/tem/hive.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+
4
+ # The TEM's configuation hive
5
+ module Tem::Hive
6
+ @@hive_dir = File.join(Gem.user_home, ".tem")
7
+
8
+ def self.path_to(*hive_entry)
9
+ File.join(@@hive_dir, *hive_entry)
10
+ end
11
+
12
+ def self.create(*hive_entry)
13
+ path = File.join(@@hive_dir, *hive_entry)
14
+ FileUtils.mkdir_p File.dirname(path)
15
+ File.open(path, "w") { |f| }
16
+ return path
17
+ end
18
+ end
@@ -0,0 +1,116 @@
1
+ # :nodoc: namespace
2
+ module Tem::Keys
3
+
4
+ # Wraps a TEM asymmetric key, e.g. an RSA key.
5
+ class Asymmetric < Tem::Key
6
+ def self.new_from_array(array)
7
+ self.new(OpenSSL::PKey::RSA.new(array[0]), *array[1..-1])
8
+ end
9
+
10
+ def self.new_from_yaml_str(yaml_str)
11
+ array = YAML.load yaml_str
12
+ new_from_array array
13
+ end
14
+
15
+ def to_array
16
+ [@ssl_key.to_pem, @padding_type]
17
+ end
18
+
19
+ def to_yaml_str
20
+ self.to_array.to_yaml.to_s
21
+ end
22
+
23
+ # Generate a pair of asymmetric keys.
24
+ def self.generate_pair
25
+ ssl_key = OpenSSL::PKey::RSA.generate(2048, 65537)
26
+ new_pair_from_ssl_key ssl_key
27
+ end
28
+
29
+ # Creates a pair of asymmetric keys wrapping an OpenSSL private key.
30
+ def self.new_pair_from_ssl_key(ssl_key)
31
+ { :public => Tem::Keys::Asymmetric.new(ssl_key.public_key),
32
+ :private => Tem::Keys::Asymmetric.new(ssl_key) }
33
+ end
34
+
35
+ def initialize(ssl_key, padding_type = :pkcs1)
36
+ super ssl_key
37
+ @is_public = !ssl_key.d
38
+ @padding_type = padding_type
39
+
40
+ case padding_type
41
+ when :oaep
42
+ @padding_id = OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
43
+ @padding_bytes = 42
44
+ when :pkcs1
45
+ @padding_id = OpenSSL::PKey::RSA::PKCS1_PADDING
46
+ @padding_bytes = 11
47
+ else
48
+ raise "Unknown padding type #{padding_type}\n"
49
+ end
50
+
51
+ @size = 0
52
+ n = @is_public ? @ssl_key.n : (@ssl_key.p * @ssl_key.q)
53
+ while n != 0 do
54
+ @size += 1
55
+ n >>= 8
56
+ end
57
+ end
58
+ public_class_method :new
59
+
60
+ def is_public?
61
+ @is_public
62
+ end
63
+
64
+ def encrypt(data)
65
+ encrypt_or_decrypt data, @size - @padding_bytes,
66
+ @is_public ? :public_encrypt : :private_encrypt
67
+ end
68
+
69
+ def decrypt(data)
70
+ encrypt_or_decrypt data, @size,
71
+ @is_public ? :public_decrypt : :private_decrypt
72
+ end
73
+
74
+ def sign(data)
75
+ data = data.pack 'C*' if data.respond_to? :pack
76
+ # PKCS1-padding is forced in by openssl... sigh!
77
+ out_data = @ssl_key.sign OpenSSL::Digest::SHA1.new, data
78
+ data.respond_to?(:pack) ? out_data : out_data.unpack('C*')
79
+ end
80
+
81
+ def verify(data, signature)
82
+ data = data.pack 'C*' if data.respond_to? :pack
83
+ signature = signature.pack 'C*' if signature.respond_to? :pack
84
+ # PKCS1-padding is forced in by openssl... sigh!
85
+ @ssl_key.verify OpenSSL::Digest::SHA1.new, signature, data
86
+ end
87
+
88
+ def encrypt_or_decrypt(data, in_size, op)
89
+ chug_data(data, in_size) { |block| @ssl_key.send op, block, @padding_id }
90
+ end
91
+ private :encrypt_or_decrypt
92
+
93
+ def chug_data(data, in_size, &chug_block)
94
+ output = data.class.new
95
+ i = 0
96
+ while i < data.length do
97
+ block_size = (data.length - i < in_size) ? data.length - i : in_size
98
+ if data.kind_of? String
99
+ block = data[i...(i+block_size)]
100
+ else
101
+ block = data[i...(i+block_size)].pack('C*')
102
+ end
103
+ o_block = yield block
104
+ if data.kind_of? String
105
+ output += o_block
106
+ else
107
+ output += o_block.unpack('C*')
108
+ end
109
+ i += block_size
110
+ end
111
+ return output
112
+ end
113
+ private :chug_data
114
+ end
115
+
116
+ end # namespace Tem::Keys
@@ -0,0 +1,48 @@
1
+ # Base class for the TEM keys.
2
+ #
3
+ # This class consists of stubs describing the interface implemented by
4
+ # subclasses.
5
+ class Tem::Key
6
+ # The OpenSSL key wrapped by this TEM key.
7
+ attr_reader :ssl_key
8
+
9
+ # Creates a new key based on an OpenSSL key.
10
+ def initialize(ssl_key)
11
+ @ssl_key = ssl_key
12
+ end
13
+ # This class should not be instantiated directly.
14
+ private_class_method :new
15
+
16
+ # Serializes this key to the TEM ABI format.
17
+ def to_tem_key
18
+ Tem::Abi.to_tem_key self
19
+ end
20
+
21
+ # Encrypts a block of data into a TEM-friendly format.
22
+ def encrypt(data)
23
+ raise "TEM Key class #{self.class.name} didn't implement encrypt"
24
+ end
25
+
26
+ def decrypt(data)
27
+ raise "TEM Key class #{self.class.name} didn't implement decrypt"
28
+ end
29
+
30
+ def sign(data)
31
+ raise "TEM Key class #{self.class.name} didn't implement sign"
32
+ end
33
+
34
+ def verify(data)
35
+ raise "TEM Key class #{self.class.name} didn't implement verify"
36
+ end
37
+
38
+ # Creates a new TEM key wrapper from a SSL key
39
+ def self.new_from_ssl_key(ssl_key)
40
+ if ssl_key.kind_of? OpenSSL::PKey::PKey
41
+ Tem::Keys::Asymmetric.new ssl_key
42
+ elsif ssl_key.kind_of? OpenSSL::Cipher::Cipher
43
+ Tem::Keys::Symmetric.new ssl_key
44
+ else
45
+ raise "Can't handle keys of class #{ssl_key.class}"
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,47 @@
1
+ # :nodoc: namespace
2
+ module Tem::Keys
3
+
4
+ # Wraps a TEM symmetric key, e.g. an AES key.
5
+ class Symmetric < Tem::Key
6
+ @@cipher_mode = 'ECB'
7
+
8
+ # Generates a new symmetric key.
9
+ def self.generate
10
+ cipher = OpenSSL::Cipher::AES128.new @@cipher_mode
11
+ key = cipher.random_key
12
+ self.new key
13
+ end
14
+
15
+ # Creates a new symmetric key based on an OpenSSL Cipher instance, augmented
16
+ # with a key accessor.
17
+ def initialize(ssl_key)
18
+ super ssl_key
19
+ @key = ssl_key.key
20
+ @cipher_class = ssl_key.class
21
+ end
22
+ public_class_method :new
23
+
24
+ def encrypt_or_decrypt(data, do_encrypt)
25
+ cipher = @cipher_class.new @@cipher_mode
26
+ do_encrypt ? cipher.encrypt : cipher.decrypt
27
+ cipher.key = @key
28
+ cipher.iv = "\0" * 16
29
+
30
+ end
31
+
32
+ def encrypt(data)
33
+ cipher.encrypt_or_decrypt data, true
34
+ end
35
+
36
+ def decrypt(data)
37
+ cipher.encrypt_or_decrypt data, false
38
+ end
39
+
40
+ def sign(data)
41
+ end
42
+
43
+ def verify(data)
44
+ end
45
+ end
46
+
47
+ end # namespace Tem::Keys
@@ -0,0 +1,63 @@
1
+ # raised when executing a SEC
2
+ class Tem::SecExecError < StandardError
3
+ attr_reader :line_info
4
+ attr_reader :buffer_state, :key_state
5
+ attr_reader :trace
6
+
7
+ def initialize(line_info, tem_trace, buffer_state, key_state)
8
+ super 'SEC execution failed on the TEM'
9
+ @line_info = line_info
10
+ line_ip, atom, backtrace = *line_info
11
+ @atom = atom
12
+ if tem_trace and tem_trace[:ip]
13
+ @ip_delta = tem_trace[:ip] - line_ip
14
+ else
15
+ @ip_delta = 0
16
+ end
17
+ @trace = tem_trace
18
+ @buffer_state = buffer_state
19
+ @key_state = key_state
20
+ set_backtrace backtrace
21
+ end
22
+
23
+ def bstat_str
24
+ if @buffer_state.nil?
25
+ "no buffer state available"
26
+ else
27
+ @buffer_state.inspect
28
+ end
29
+ end
30
+
31
+ def kstat_str
32
+ if @key_state.nil?
33
+ "no key state available"
34
+ else
35
+ @key_state.inspect
36
+ end
37
+ end
38
+
39
+ def trace_str
40
+ if @trace.nil?
41
+ "no trace available"
42
+ else
43
+ "ip=#{'%04x' % @trace[:ip]} sp=#{'%04x' % @trace[:sp]} out=#{'%04x' % @trace[:out]} pscell=#{'%04x' % @trace[:pscell]}"
44
+ end
45
+ end
46
+
47
+ def to_s
48
+ string = <<ENDSTRING
49
+ SECpack execution generated an exception on the TEM
50
+
51
+ TEM Trace: #{trace_str}
52
+ TEM Buffer Status:#{bstat_str}
53
+ TEM Key Status:#{kstat_str}
54
+
55
+ TEM execution error at #{@atom}+#{@ip_delta}
56
+ ENDSTRING
57
+ string.strip
58
+ end
59
+
60
+ def inspect
61
+ trace_str + "\n" + bstat_str + "\n" + kstat_str
62
+ end
63
+ end
@@ -0,0 +1,81 @@
1
+ require 'yaml'
2
+
3
+ module Tem::SeClosures
4
+ module MixedMethods
5
+ def assemble(&block)
6
+ return Tem::Assembler.assemble(&block)
7
+ end
8
+ end
9
+
10
+ include MixedMethods
11
+ def self.included(klass)
12
+ klass.extend MixedMethods
13
+ end
14
+
15
+ def sec_trace
16
+ #begin
17
+ trace = @transport.applet_apdu! :ins => 0x54
18
+ if trace.length > 2
19
+ case read_tem_short(trace, 0) # trace version
20
+ when 1
21
+ return {:sp => read_tem_short(trace, 2), :ip => read_tem_short(trace, 4),
22
+ :out => read_tem_short(trace, 6), :pscell => read_tem_short(trace, 8)}
23
+ end
24
+ end
25
+ return nil # unreadable trace
26
+ #rescue
27
+ # return nil
28
+ #end
29
+ end
30
+
31
+ def solve_psfault
32
+ # TODO: better strategy, lol
33
+ next_cell = rand(16)
34
+ @transport.applet_apdu! :ins => 0x53, :p12 => to_tem_ushort(next_cell)
35
+ end
36
+
37
+ def execute(secpack, key_id = 0)
38
+ # load SECpack
39
+ buffer_id = post_buffer(secpack.tem_formatted_body)
40
+ response = @transport.applet_apdu! :ins => 0x50, :p1 => buffer_id,
41
+ :p2 => key_id
42
+ tem_secpack_error(response) if read_tem_byte(response, 0) != 1
43
+
44
+ # execute SEC
45
+ sec_exception = nil
46
+ loop do
47
+ response = @transport.applet_apdu! :ins => 0x52
48
+ sec_status = read_tem_byte(response, 0)
49
+ case sec_status
50
+ when 2 # success
51
+ break
52
+ when 3 # exception
53
+ # there is an exception, try to collect the trace
54
+ b_stat = stat_buffers() rescue nil
55
+ k_stat = stat_keys() rescue nil
56
+ trace = sec_trace()
57
+ if trace and trace[:ip]
58
+ line_info = secpack.line_info_for_ip(trace[:ip])
59
+ else
60
+ line_info = [0, :unknown, Kernel.caller]
61
+ end
62
+ sec_exception = Tem::SecExecError.new line_info, trace, b_stat, k_stat
63
+ break
64
+ when 4 # persistent store fault
65
+ solve_psfault
66
+ else
67
+ raise "Unrecognized execution engine status #{sec_status}"
68
+ end
69
+ end
70
+
71
+ # unbind SEC
72
+ response = @transport.applet_apdu! :ins => 0x51
73
+ raise sec_exception if sec_exception
74
+ buffer_id = read_tem_byte(response, 0)
75
+ buffer_length = read_tem_short(response, 1)
76
+ data_buffer = read_buffer buffer_id
77
+ release_buffer buffer_id
78
+
79
+ return data_buffer[0...buffer_length]
80
+ end
81
+ end
@@ -0,0 +1,107 @@
1
+ require 'yaml'
2
+
3
+ class Tem::SecPack
4
+ @@serialized_ivars = [:body, :labels, :ep, :sp, :extra_bytes, :signed_bytes,
5
+ :encrypted_bytes, :bound, :lines]
6
+
7
+ def self.new_from_array(array)
8
+ arg_hash = { }
9
+ @@serialized_ivars.each_with_index { |name, i| arg_hash[name] = array[i] }
10
+ self.new arg_hash
11
+ end
12
+
13
+ def self.new_from_yaml_str(yaml_str)
14
+ array = YAML.load yaml_str
15
+ new_from_array array
16
+ end
17
+
18
+ def to_array
19
+ @@serialized_ivars.map { |m| self.instance_variable_get :"@#{m}" }
20
+ end
21
+
22
+ def to_yaml_str
23
+ self.to_array.to_yaml.to_s
24
+ end
25
+
26
+ attr_reader :body, :bound
27
+ attr_reader :lines
28
+
29
+ def trim_extra_bytes
30
+ @extra_bytes = 0
31
+ while @extra_bytes < @body.length
32
+ break if @body[-@extra_bytes - 1] != 0
33
+ @extra_bytes += 1
34
+ end
35
+ @body.slice! @body.length - @extra_bytes, @extra_bytes
36
+ end
37
+
38
+ def expand_extra_bytes
39
+ @body += [0] * @extra_bytes
40
+ @extra_bytes = 0
41
+ end
42
+
43
+ def initialize(args)
44
+ @@serialized_ivars.map { |m| self.instance_variable_set :"@#{m}", args[m] }
45
+ @bound ||= false
46
+
47
+ @extra_bytes ||= 0
48
+ # trim_extra_bytes if @extra_bytes == 0
49
+ end
50
+
51
+ def label_address(label_name)
52
+ @labels[label_name.to_sym]
53
+ end
54
+
55
+ def bind(public_key, encrypt_from = 0, plaintext_from = 0)
56
+ expand_extra_bytes
57
+ encrypt_from = @labels[encrypt_from.to_sym] unless encrypt_from.kind_of? Numeric
58
+ plaintext_from = @labels[plaintext_from.to_sym] unless plaintext_from.kind_of? Numeric
59
+
60
+ @signed_bytes = encrypt_from
61
+ @encrypted_bytes = plaintext_from - encrypt_from
62
+
63
+ secpack_sig = Tem::Abi.tem_hash [tem_header, @body[0...plaintext_from]].flatten
64
+ crypt = public_key.encrypt [@body[encrypt_from...plaintext_from], secpack_sig].flatten
65
+ @body = [@body[0...encrypt_from], crypt, @body[plaintext_from..-1]].flatten
66
+
67
+ label_delta = crypt.length - @encrypted_bytes
68
+ @labels = Hash[*(@labels.map { |k, v|
69
+ if v < encrypt_from
70
+ [k, v]
71
+ elsif v < plaintext_from
72
+ []
73
+ else
74
+ [k, v + label_delta]
75
+ end
76
+ }.flatten)]
77
+
78
+ #trim_extra_bytes
79
+ @bound = true
80
+ end
81
+
82
+ def tem_header
83
+ # TODO: use 0x0100 (no tracing) depending on options
84
+ hh = [0x0101, @signed_bytes || 0, @encrypted_bytes || 0, @extra_bytes, @sp,
85
+ @ep].map { |n| Tem::Abi.to_tem_ushort n }.flatten
86
+ hh += Array.new((Tem::Abi.tem_hash [0]).length - hh.length, 0)
87
+ return hh
88
+ end
89
+ private :tem_header
90
+
91
+ def tem_formatted_body
92
+ # HACK: Ideally, we would allocate a bigger buffer, and then only fill part
93
+ # of it. Realistically, we'll just send in extra_bytes 0s.
94
+ [tem_header, @body, [0] * @extra_bytes].flatten
95
+ end
96
+
97
+ def line_info_for_ip(ip)
98
+ return nil unless @lines
99
+
100
+ @lines.reverse_each do |info|
101
+ # If something breaks, it's likely to happen after the opcode of the
102
+ # offending instruction has been read, so assume offending_ip < ip.
103
+ return info if ip >= info[0]
104
+ end
105
+ return info.first
106
+ end
107
+ end