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.
- checksums.yaml +7 -0
- data/.rubocop.yml +12 -0
- data/.yardopts +4 -0
- data/Gemfile +12 -0
- data/LICENSE +21 -0
- data/README.md +78 -0
- data/Rakefile +29 -0
- data/lib/codtls.rb +186 -0
- data/lib/codtls/abstract_session.rb +179 -0
- data/lib/codtls/alert.rb +64 -0
- data/lib/codtls/decrypt.rb +72 -0
- data/lib/codtls/ecc.rb +26 -0
- data/lib/codtls/encrypt.rb +29 -0
- data/lib/codtls/h_changecipherspec.rb +25 -0
- data/lib/codtls/h_chello.rb +79 -0
- data/lib/codtls/h_content.rb +57 -0
- data/lib/codtls/h_finished.rb +30 -0
- data/lib/codtls/h_keyexchange.rb +131 -0
- data/lib/codtls/h_shello.rb +51 -0
- data/lib/codtls/h_shellodone.rb +22 -0
- data/lib/codtls/h_type.rb +22 -0
- data/lib/codtls/h_verify.rb +30 -0
- data/lib/codtls/handshake.rb +173 -0
- data/lib/codtls/models/codtls_connection.rb +3 -0
- data/lib/codtls/models/codtls_device.rb +3 -0
- data/lib/codtls/prf.rb +40 -0
- data/lib/codtls/pskdb.rb +104 -0
- data/lib/codtls/ram_session.rb +214 -0
- data/lib/codtls/rampskdb.rb +87 -0
- data/lib/codtls/record.rb +202 -0
- data/lib/codtls/session.rb +284 -0
- data/lib/codtls/version.rb +3 -0
- data/lib/generators/codtls/codtls_generator.rb +56 -0
- data/lib/generators/codtls/templates/create_codtls_connections.rb +15 -0
- data/lib/generators/codtls/templates/create_codtls_devices.rb +11 -0
- data/test/test_codtls.rb +75 -0
- data/test/test_ecc.rb +44 -0
- data/test/test_h_chello.rb +40 -0
- data/test/test_h_content.rb +59 -0
- data/test/test_h_keyexchange.rb +36 -0
- data/test/test_helper.rb +3 -0
- data/test/test_pskdb.rb +37 -0
- data/test/test_ram_session.rb +131 -0
- data/test/test_rampskdb.rb +26 -0
- data/test/test_record.rb +128 -0
- data/test/test_send_recv.rb +178 -0
- data/test/test_session.rb +164 -0
- 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,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
|
data/test/test_codtls.rb
ADDED
@@ -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
|