codtls 0.0.1.alpha

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 (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