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