codtls 0.0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +12 -0
  3. data/.yardopts +4 -0
  4. data/Gemfile +12 -0
  5. data/LICENSE +21 -0
  6. data/README.md +78 -0
  7. data/Rakefile +29 -0
  8. data/lib/codtls.rb +186 -0
  9. data/lib/codtls/abstract_session.rb +179 -0
  10. data/lib/codtls/alert.rb +64 -0
  11. data/lib/codtls/decrypt.rb +72 -0
  12. data/lib/codtls/ecc.rb +26 -0
  13. data/lib/codtls/encrypt.rb +29 -0
  14. data/lib/codtls/h_changecipherspec.rb +25 -0
  15. data/lib/codtls/h_chello.rb +79 -0
  16. data/lib/codtls/h_content.rb +57 -0
  17. data/lib/codtls/h_finished.rb +30 -0
  18. data/lib/codtls/h_keyexchange.rb +131 -0
  19. data/lib/codtls/h_shello.rb +51 -0
  20. data/lib/codtls/h_shellodone.rb +22 -0
  21. data/lib/codtls/h_type.rb +22 -0
  22. data/lib/codtls/h_verify.rb +30 -0
  23. data/lib/codtls/handshake.rb +173 -0
  24. data/lib/codtls/models/codtls_connection.rb +3 -0
  25. data/lib/codtls/models/codtls_device.rb +3 -0
  26. data/lib/codtls/prf.rb +40 -0
  27. data/lib/codtls/pskdb.rb +104 -0
  28. data/lib/codtls/ram_session.rb +214 -0
  29. data/lib/codtls/rampskdb.rb +87 -0
  30. data/lib/codtls/record.rb +202 -0
  31. data/lib/codtls/session.rb +284 -0
  32. data/lib/codtls/version.rb +3 -0
  33. data/lib/generators/codtls/codtls_generator.rb +56 -0
  34. data/lib/generators/codtls/templates/create_codtls_connections.rb +15 -0
  35. data/lib/generators/codtls/templates/create_codtls_devices.rb +11 -0
  36. data/test/test_codtls.rb +75 -0
  37. data/test/test_ecc.rb +44 -0
  38. data/test/test_h_chello.rb +40 -0
  39. data/test/test_h_content.rb +59 -0
  40. data/test/test_h_keyexchange.rb +36 -0
  41. data/test/test_helper.rb +3 -0
  42. data/test/test_pskdb.rb +37 -0
  43. data/test/test_ram_session.rb +131 -0
  44. data/test/test_rampskdb.rb +26 -0
  45. data/test/test_record.rb +128 -0
  46. data/test/test_send_recv.rb +178 -0
  47. data/test/test_session.rb +164 -0
  48. metadata +303 -0
@@ -0,0 +1,284 @@
1
+ require 'active_record'
2
+ require 'sqlite3'
3
+ require 'pathname'
4
+
5
+ require 'codtls/abstract_session'
6
+ require 'codtls/models/codtls_connection'
7
+
8
+ module CoDTLS
9
+ # Error class for wrong data inputs (for exampe keyblock)
10
+ class SessionError < StandardError
11
+ end
12
+
13
+ # Storage class for a CoDTLS-Session with the following Fields:
14
+ #
15
+ # TABLE 1
16
+ # Type | Name | Standard-Value
17
+ # -------------------------------------------------------
18
+ # uint8_t | uuid[16]; | uuid - couldnt be empty
19
+ # uint8_t | psk[16]; | psk - couldnt be empty
20
+ # uint8_t | psk_new[16]; | empty
21
+ #
22
+ # TABLE 2
23
+ # Type | Name | Standard-Value
24
+ # -------------------------------------------------------
25
+ # uint8_t | ip[16]; | Given IP
26
+ # uint8_t | id[8]; | empty
27
+ # uint16_t | epoch; | 0
28
+ # uint48_t | seq_num_r; | depends on implementation (-1, 0 or 1)
29
+ # uint48_t | seq_num_w; | depends on implementation (-1, 0 or 1)
30
+ # uint8_t | key_block[40]; | empty
31
+ # uint8_t | key_block_new[40]; | empty
32
+ # uint8_t | handshake; | 0
33
+ class Session < CoDTLS::AbstractSession
34
+ @ip_list = []
35
+ def self.ip_list
36
+ @ip_list
37
+ end
38
+
39
+ # Constructor to create a new session for the given ip. When only ip is
40
+ # given, its the value for searching in database. When its not found, a
41
+ # new database entry with standard values will be created. When id is
42
+ # also given, id is the first value for searching in database. When its
43
+ # found, ip will be updated and other values are unchanged. In the other
44
+ # case, when id isnt found, its the same behavior as without id but with
45
+ # additional storing of the id. Throws excpetion if ip == nil.
46
+ #
47
+ # @param ip [IP] IP for this Session
48
+ # @param id [String] Session-Id for this Session
49
+ def initialize(ip, id = nil)
50
+ # logger = Logger.new(STDOUT)
51
+ # logger.level = CoDTLS::LOG_LEVEL
52
+ # logger.debug("Session wird erstellt")
53
+ fail SessionError 'IP is nil, not a valid value.' if ip.nil? ||
54
+ ip.class != String
55
+ # normalize IP
56
+ ip = IPAddr.new(ip)
57
+ fail SessionError 'IP is not in a valid format' if ip.nil?
58
+ @ip = ip.to_s
59
+
60
+ # Find IP Object, if not found -> create it
61
+ database_entry = CoDTLS::Session.ip_list.select { |i| i[0] == @ip }
62
+ if database_entry.empty?
63
+ if id.nil?
64
+ create_standard_ip_entry
65
+ else
66
+ ActiveRecord::Base.connection_pool.with_connection do
67
+ database_entry = CODTLSConnection.find_by_id(id)
68
+ end
69
+ if (database_entry.nil?)
70
+ create_standard_ip_entry
71
+ else
72
+ CoDTLS::Session.ip_list.push([ip, database_entry])
73
+ end
74
+ database_entry.session_id = id.to_s
75
+ database_entry.save
76
+ end
77
+ else
78
+ database_entry = database_entry[0][1]
79
+ end
80
+ ObjectSpace.define_finalizer(self, proc { entry = get_database_entry(ip)
81
+ entry.save unless entry.nil? })
82
+ end
83
+
84
+ # Sets the ID of the current session.
85
+ #
86
+ # @param id [String] the Session-Id
87
+ def id=(id)
88
+ database_entry = get_database_entry(@ip)
89
+ database_entry.session_id = id
90
+ database_entry.save
91
+ end
92
+
93
+ # Returns the ID of the current session.
94
+ #
95
+ # @return [String] the Session-ID. nil if Session-ID is unknown
96
+ def id
97
+ database_entry = get_database_entry(@ip)
98
+ return nil if database_entry.session_id.nil?
99
+ database_entry.session_id
100
+ end
101
+
102
+ # Returns the Epoch of the session for the specified IP.
103
+ #
104
+ # @return [Number] the Epoch
105
+ def epoch
106
+ database_entry = get_database_entry(@ip)
107
+ database_entry.epoch
108
+ end
109
+
110
+ # Increases the Epoch of the session by 1.
111
+ # Also copy key_block_new to key_block and sets key_block_new to empty.
112
+ # seq_num_r and seq_num_w are set back to standard value.
113
+ # Throws excpetion if keyblock is not 40 bytes long.
114
+ def increase_epoch
115
+ database_entry = get_database_entry(@ip)
116
+
117
+ if database_entry.key_block_new.nil?
118
+ fail CoDTLS::SessionError, 'no new keyblock to set for this epoch.'
119
+ end
120
+ database_entry.epoch = database_entry.epoch + 1
121
+ database_entry.seq_num_r = 0
122
+ database_entry.seq_num_w = 0
123
+ database_entry.key_block = database_entry.key_block_new
124
+ database_entry.key_block_new = nil
125
+
126
+ ActiveRecord::Base.connection_pool.with_connection do
127
+ database_entry.save
128
+ end
129
+ end
130
+
131
+ # Checks the sequenze number of an incoming paket. Valid number is
132
+ # -10 ... + 100 of the expected number.
133
+ # The inital LAST number is 0, so the next expected is 1.
134
+ #
135
+ # @param num [Number] the recieved sequence number
136
+ # @return [Bool] true if the number is valid. false if invalid
137
+ def check_seq(num)
138
+ database_entry = get_database_entry(@ip)
139
+ return false if num < database_entry.seq_num_r - 9 ||
140
+ num > database_entry.seq_num_r + 101
141
+ true
142
+ end
143
+
144
+ # Sets the sequence number of the last received paket.
145
+ #
146
+ # @param num [Number] the new sequence number
147
+ def seq=(num)
148
+ database_entry = get_database_entry(@ip)
149
+ database_entry.seq_num_r = num
150
+ database_entry.save
151
+ true
152
+ end
153
+
154
+ # Returns the sequence number for the next outgoing paket.
155
+ # The inital sequence number is 1. Every return value needs
156
+ # to be last value + 1.
157
+ #
158
+ # @return [Number] the sequence number for the next outgoing paket
159
+ def seq
160
+ database_entry = get_database_entry(@ip)
161
+ database_entry.seq_num_w += 1
162
+ database_entry.save
163
+ database_entry.seq_num_w
164
+ end
165
+
166
+ # Inserts a new keyblock to key_block_new. Throws excpetion if
167
+ # keyblock is not 40 bytes long.
168
+ #
169
+ # @param keyBlock [String] the 40 byte long new keyblock to be inserted
170
+ def key_block=(keyBlock)
171
+ if keyBlock.b.length != 40
172
+ fail CoDTLS::SessionError, 'key blocks have to be 40 byte long'
173
+ end
174
+ database_entry = get_database_entry(@ip)
175
+ database_entry.key_block_new = keyBlock
176
+ database_entry.save
177
+ end
178
+
179
+ # Returns the active keyblock (key_block) for the specified IP.
180
+ # If keyblock is empty, nil will be returned.
181
+ #
182
+ # @return [String] the 40 byte long keyblock or nil if empty
183
+ def key_block
184
+ database_entry = get_database_entry(@ip)
185
+ database_entry.key_block
186
+ end
187
+
188
+ # Causes the next messages to be send as handshake messages.
189
+ def enable_handshake
190
+ database_entry = get_database_entry(@ip)
191
+ database_entry.handshake = true
192
+ database_entry.save
193
+ true
194
+ end
195
+
196
+ # Causes the next messages to not be send as handshake messages.
197
+ def disable_handshake
198
+ database_entry = get_database_entry(@ip)
199
+ database_entry.handshake = false
200
+ database_entry.save
201
+ true
202
+ end
203
+
204
+ # Checks if the next message should be send as a handshake message.
205
+ #
206
+ # @return [Bool] true if the next messages are handshake messages
207
+ def handshake?
208
+ database_entry = get_database_entry(@ip)
209
+ database_entry.handshake
210
+ end
211
+
212
+ # Removes the whole entry from database.
213
+ def clear
214
+ database_entry = nil
215
+ ActiveRecord::Base.connection_pool.with_connection do
216
+ database_entry = CODTLSConnection.find_by_ip(@ip)
217
+ end
218
+ if database_entry
219
+ ActiveRecord::Base.connection_pool.with_connection do
220
+ database_entry.destroy
221
+ end
222
+ entry = CoDTLS::Session.ip_list.select { |i| i[1] == database_entry }
223
+ return nil if entry.empty?
224
+ CoDTLS::Session.ip_list.delete(entry[0])
225
+ # database_entry.session_id = nil
226
+ # database_entry.epoch = 0
227
+ # database_entry.seq_num_r = 0
228
+ # database_entry.seq_num_w = 0
229
+ # database_entry.key_block = nil
230
+ # database_entry.key_block_new = nil
231
+ # database_entry.handshake = false
232
+ # database_entry.save
233
+ # database_entry = nil
234
+ end
235
+ end
236
+
237
+ def get_database_entry(ip)
238
+ # mutex lock
239
+ entry = CoDTLS::Session.ip_list.select { |i| i[1].ip == ip }
240
+ if entry.empty?
241
+ entry = create_standard_ip_entry
242
+ # return the new entry
243
+ else
244
+ entry = entry[0][1]
245
+ end
246
+ return entry
247
+ # mutex unlock
248
+ end
249
+
250
+ def self.clear_all
251
+ # ActiveRecord::Base.connection_pool.with_connection do
252
+ # CoDTLSConnection.destroy_all
253
+ # end
254
+ @ip_list = []
255
+ end
256
+
257
+ private
258
+
259
+ def create_standard_ip_entry
260
+ database_entry = nil
261
+ ActiveRecord::Base.connection_pool.with_connection do
262
+ if (database_entry = CODTLSConnection.find_by_ip(@ip)).nil?
263
+ database_entry = CODTLSConnection.create(ip: @ip,
264
+ epoch: 0,
265
+ handshake: false,
266
+ seq_num_w: 0,
267
+ seq_num_r: 0
268
+ )
269
+ database_entry.save
270
+ CoDTLS::Session.ip_list.push([@ip, database_entry])
271
+ end
272
+ end
273
+ return database_entry
274
+ end
275
+ end
276
+ end
277
+
278
+ def create_sqlite_db(dbname)
279
+ SQLite3::Database.new(dbname)
280
+ end
281
+
282
+ # refactoren: jedes mal, wenn auf den "database_entry" zugegriffen wird,
283
+ # muss er aus der Tabelle geholt werden. Am besten noch den Zugriff auf
284
+ # die Tabelle mit nem Mutex schützen
@@ -0,0 +1,3 @@
1
+ module CoDTLS
2
+ VERSION = '0.0.1.alpha'
3
+ end
@@ -0,0 +1,56 @@
1
+ # http://5minutenpause.com/blog/2012/06/03/install-generators-for-ruby-gems/
2
+ # http://stackoverflow.com/questions/15671099/add-new-migrations-from-rails-
3
+ # engine-gem-to-app-via-generator
4
+
5
+ # require 'rails/generators/migration'
6
+ # module CoDTLS
7
+ # module Generators
8
+ # class InstallGenerator < Rails::Generators::Baseinclude
9
+ # include Rails::Generators::Migration
10
+ # source_root File.expand_path("../templates", __FILE__)
11
+ # def copy_migrations
12
+ # copy_migration "20140116124500_create_dtls_devices.rb"
13
+ # copy_migration "20140116124501_create_dtls_connections.rb"
14
+ # end
15
+
16
+ # protected
17
+
18
+ # def copy_migration(filename)
19
+ # if self.class.migration_exists?("db/migrate", "#{filename}")
20
+ # say_status("skipped", "Migration #{filename}.rb already exists")
21
+ # else
22
+ # migration_template "migrations/#{filename}.rb",
23
+ # "db/migrate/#{filename}.rb"
24
+ # end
25
+ # end
26
+ # end
27
+ # end
28
+ # end
29
+
30
+ require 'rails/generators'
31
+ require 'rails/generators/migration'
32
+
33
+ # Description
34
+ class CodtlsGenerator < Rails::Generators::Base
35
+ include Rails::Generators::Migration
36
+
37
+ def self.source_root
38
+ @source_root ||= File.join(File.dirname(__FILE__), 'templates')
39
+ end
40
+
41
+ def self.next_migration_number(dirname)
42
+ if ActiveRecord::Base.timestamped_migrations
43
+ sleep 0.2
44
+ Time.new.utc.strftime('%Y%m%d%H%M%S%L')
45
+ else
46
+ sprintf '%.3d', (current_migration_number(dirname) + 1)
47
+ end
48
+ end
49
+
50
+ def create_migration_file
51
+ migration_template 'create_codtls_devices.rb',
52
+ 'db/migrate/create_codtls_devices.rb'
53
+ migration_template 'create_codtls_connections.rb',
54
+ 'db/migrate/create_codtls_connections.rb'
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ # Creates the base table for CoDTLS Connections
2
+ class CreateCodtlsConnections < ActiveRecord::Migration
3
+ def change
4
+ create_table :codtls_connections do |t|
5
+ t.string :ip
6
+ t.string :session_id
7
+ t.integer :epoch
8
+ t.integer :seq_num_r
9
+ t.integer :seq_num_w
10
+ t.binary :key_block
11
+ t.binary :key_block_new
12
+ t.boolean :handshake
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # Creation of the Base Device Table
2
+ class CreateCodtlsDevices < ActiveRecord::Migration
3
+ def change
4
+ create_table :codtls_devices do |t|
5
+ t.binary :uuid
6
+ t.string :psk
7
+ t.string :psk_new
8
+ t.string :desc
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,75 @@
1
+ require 'test_helper'
2
+ require 'codtls'
3
+
4
+ # Testclass
5
+ class CoDTLSTest < Minitest::Test
6
+ def setup
7
+ fail CoDTLS::SessionError 'testdatabase already exists' if File.exist?(
8
+ 'testdatabase.sqlite')
9
+ SQLite3::Database.new('testdatabase.sqlite')
10
+ ActiveRecord::Base.establish_connection(
11
+ adapter: 'sqlite3',
12
+ database: 'testdatabase.sqlite')
13
+ ActiveRecord::Base.connection
14
+ ActiveRecord::Migration.verbose = false # debug messages
15
+ ActiveRecord::Migrator.migrate 'db/migrate'
16
+ @session = CoDTLS::Session.new('127.0.0.1')
17
+ end
18
+
19
+ def teardown
20
+ ActiveRecord::Base.remove_connection
21
+ FileUtils.rm('testdatabase.sqlite') if File.exist?('testdatabase.sqlite')
22
+ end
23
+
24
+ def info(numeric_address, code)
25
+ assert_equal numeric_address, '::1'
26
+ @listener_test = 1 if numeric_address == '::1'
27
+ end
28
+
29
+ =begin
30
+ def test_listener
31
+ @listener_test = 0
32
+ CoDTLS::SecureSocket.add_new_node_listener(self)
33
+ sleep(1)
34
+ d = UDPSocket.new(Socket::AF_INET6)
35
+ d.connect('::1', 5684)
36
+ d.send("\x50\x03\x00", 0)
37
+ sleep(1)
38
+ assert_equal 1, @listener_test
39
+ d.close
40
+ end
41
+ =end
42
+
43
+ def test_psk
44
+ assert_equal [], CoDTLS::SecureSocket.psks
45
+
46
+ CoDTLS::SecureSocket.add_psk(
47
+ ['a9d984d1fe2b4c06afe8da98d8924005'].pack('H*'),
48
+ 'ABCDEFGHIJKLMNOP', 'Temperaturgerät 1')
49
+ assert_equal [[['a9d984d1fe2b4c06afe8da98d8924005'].pack('H*'),
50
+ 'ABCDEFGHIJKLMNOP', 'Temperaturgerät 1']],
51
+ CoDTLS::SecureSocket.psks
52
+
53
+ CoDTLS::SecureSocket.del_psk(
54
+ ['a9d984d1fe2b4c06afe8da98d8924005'].pack('H*'))
55
+ assert_equal [], CoDTLS::SecureSocket.psks
56
+
57
+ CoDTLS::SecureSocket.add_psk(
58
+ ['a9d984d1fe2b4c06afe8da98d8924005'].pack('H*'),
59
+ 'ABCDEFGHIJKLMNOP', 'Temperaturgerät 1')
60
+ CoDTLS::SecureSocket.add_psk(
61
+ ['9425f01d39034295ad9447161e13251b'].pack('H*'),
62
+ 'abcdefghijklmnop', 'Rolladen Nummer 5')
63
+ assert_equal [[['a9d984d1fe2b4c06afe8da98d8924005'].pack('H*'),
64
+ 'ABCDEFGHIJKLMNOP', 'Temperaturgerät 1'],
65
+ [['9425f01d39034295ad9447161e13251b'].pack('H*'),
66
+ 'abcdefghijklmnop', 'Rolladen Nummer 5']],
67
+ CoDTLS::SecureSocket.psks
68
+
69
+ CoDTLS::SecureSocket.del_psk(
70
+ ['a9d984d1fe2b4c06afe8da98d8924005'].pack('H*'))
71
+ assert_equal [[['9425f01d39034295ad9447161e13251b'].pack('H*'),
72
+ 'abcdefghijklmnop', 'Rolladen Nummer 5']],
73
+ CoDTLS::SecureSocket.psks
74
+ end
75
+ end