costan-tem_ruby 0.10.2

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