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,214 @@
1
+ require 'codtls/abstract_session'
2
+
3
+ module CoDTLS
4
+ # Error class for wrong data inputs (for exampe keyblock)
5
+ class SessionError < StandardError
6
+ end
7
+
8
+ # Storage class for a CoDTLS-Session with the following Fields:
9
+ # Type | Name | Standard-Value
10
+ # -------------------------------------------------------
11
+ # uint8_t | ip[16]; | Given IP
12
+ # uint8_t | id[8]; | empty
13
+ # uint16_t | epoch; | 0
14
+ # uint48_t | seq_num_r; | depends on implementation (-1, 0 or 1)
15
+ # uint48_t | seq_num_w; | depends on implementation (-1, 0 or 1)
16
+ # uint8_t | key_block[40]; | empty
17
+ # uint8_t | key_block_new[40]; | empty
18
+ # uint8_t | handshake; | 0
19
+ class RAMSession < CoDTLS::AbstractSession
20
+ @connections = []
21
+ # Constructor to create a new session for the given ip. When only ip is
22
+ # given, its the value for searching in database. When its not found, a
23
+ # new database entry with standard values will be created. When id is
24
+ # also given, id is the first value for searching in database. When its
25
+ # found, ip will be updated and other values are unchanged. In the other
26
+ # case, when id isnt found, its the same behavior as without id but with
27
+ # additional storing of the id. Throws excpetion if ip == nil.
28
+ #
29
+ # @param ip [IP] IP for this Session
30
+ # @param id [String] Session-Id for this Session
31
+ def initialize(ip, id = nil)
32
+ fail SessionError 'IP is nil, not a valid value.' if ip.nil? ||
33
+ ip.class != String
34
+ ip = IPAddr.new(ip)
35
+ fail SessionError 'IP is not in a valid format' if ip.nil?
36
+ ip = ip.to_s
37
+ @ip = ip
38
+ if id.nil?
39
+ entry = CoDTLS::RAMSession.connections.select { |c| c.ip == @ip }
40
+ if entry.empty?
41
+ create_standard_ip_object
42
+ else
43
+ @database_object = entry[0]
44
+ end
45
+ else
46
+ entry = CoDTLS::RAMSession.connections.select { |c| c.id == id }
47
+ create_standard_ip_object if entry.empty?
48
+ @database_object.id = id
49
+ end
50
+ end
51
+
52
+ # Sets the ID of the current session.
53
+ #
54
+ # @param id [String] the Session-Id
55
+ def id=(id)
56
+ create_standard_ip_object if @database_object.nil?
57
+ @database_object.session_id = id
58
+ end
59
+
60
+ # Returns the ID of the current session.
61
+ #
62
+ # @return [String] the Session-ID. nil if Session-ID is unknown
63
+ def id
64
+ create_standard_ip_object if @database_object.nil?
65
+ @database_object.session_id
66
+ end
67
+
68
+ # Returns the Epoch of the session for the specified IP.
69
+ #
70
+ # @return [Number] the Epoch
71
+ def epoch
72
+ create_standard_ip_object if @database_object.nil?
73
+ @database_object.epoch
74
+ end
75
+
76
+ # Increases the Epoch of the session by 1.
77
+ # Also copy key_block_new to key_block and sets key_block_new to empty.
78
+ # seq_num_r and seq_num_w are set back to standard value.
79
+ # Throws excpetion if keyblock is not 40 bytes long.
80
+ def increase_epoch
81
+ create_standard_ip_object if @database_object.nil?
82
+ if @database_object.key_block_new.nil?
83
+ fail CoDTLS::SessionError, 'no new keyblock to set for this epoch.'
84
+ end
85
+ @database_object.epoch = @database_object.epoch + 1
86
+ @database_object.seq_num_r = 0
87
+ @database_object.seq_num_w = 0
88
+ @database_object.key_block = @database_object.key_block_new
89
+ @database_object.key_block_new = nil
90
+ end
91
+
92
+ # Checks the sequenze number of an incoming paket. Valid number is
93
+ # -10 ... + 100 of the expected number.
94
+ # The inital LAST number is 0, so the next expected is 1.
95
+ #
96
+ # @param num [Number] the recieved sequence number
97
+ # @return [Bool] true if the number is valid. false if invalid
98
+ def check_seq(num)
99
+ create_standard_ip_entry if @database_object.nil?
100
+ return false if num < @database_object.seq_num_r - 9 ||
101
+ num > @database_object.seq_num_r + 101
102
+ true
103
+ end
104
+
105
+ # Sets the sequence number of the last received paket.
106
+ #
107
+ # @param num [Number] the new sequence number
108
+ def seq=(num)
109
+ create_standard_ip_object if @database_object.nil?
110
+ @database_object.seq_num_r = num
111
+ true
112
+ end
113
+
114
+ # Returns the sequence number for the next outgoing paket.
115
+ # The inital sequence number is 1. Every return value needs
116
+ # to be last value + 1.
117
+ #
118
+ # @return [Number] the sequence number for the next outgoing paket
119
+ def seq
120
+ create_standard_ip_object if @database_object.nil?
121
+ temp = @database_object.seq_num_w + 1
122
+ @database_object.seq_num_w = temp
123
+ end
124
+
125
+ # Inserts a new keyblock to key_block_new. Throws excpetion if
126
+ # keyblock is not 40 bytes long.
127
+ #
128
+ # @param keyBlock [String] the 40 byte long new keyblock to be inserted
129
+ def key_block=(keyBlock)
130
+ if keyBlock.b.length != 40
131
+ fail CoDTLS::SessionError, 'key blocks have to be 40 byte long'
132
+ end
133
+ create_standard_ip_object if @database_object.nil?
134
+ @database_object.key_block_new = keyBlock
135
+ end
136
+
137
+ # Returns the active keyblock (key_block) for the specified IP.
138
+ # If keyblock is empty, nil will be returned.
139
+ #
140
+ # @return [String] the 40 byte long keyblock or nil if empty
141
+ def key_block
142
+ create_standard_ip_object if @database_object.nil?
143
+ @database_object.key_block
144
+ end
145
+
146
+ # Causes the next messages to be send as handshake messages.
147
+ def enable_handshake
148
+ create_standard_ip_object if @database_object.nil?
149
+ @database_object.handshake = true
150
+ end
151
+
152
+ # Causes the next messages to not be send as handshake messages.
153
+ def disable_handshake
154
+ create_standard_ip_object if @database_object.nil?
155
+ @database_object.handshake = false
156
+ end
157
+
158
+ # Checks if the next message should be send as a handshake message.
159
+ #
160
+ # @return [Bool] true if the next messages are handshake messages
161
+ def handshake?
162
+ create_standard_ip_object if @database_object.nil?
163
+ @database_object.handshake
164
+ end
165
+
166
+ # Removes the whole entry from database.
167
+ def clear
168
+ fail CoDTLS::SessionError 'IP of a current session has'\
169
+ 'been deleted by a '\
170
+ 'third party.' if @database_object.nil?
171
+ CoDTLS::RAMSession.connections.delete(@database_object)
172
+ @database_object = nil
173
+ end
174
+
175
+ def self.clear_all
176
+ @connections = []
177
+ end
178
+
179
+ def self.connections
180
+ @connections unless @connections.nil?
181
+ end
182
+
183
+ private
184
+
185
+ def create_standard_ip_object
186
+ @database_object = TempDTLSConnection.new(@ip)
187
+ CoDTLS::RAMSession.connections.push(@database_object)
188
+ end
189
+ end
190
+
191
+ # class for representing a single connection. The IP address is unique.
192
+ class TempDTLSConnection
193
+ attr_accessor :ip,
194
+ :epoch,
195
+ :handshake,
196
+ :seq_num_w,
197
+ :seq_num_r,
198
+ :key_block,
199
+ :key_block_new,
200
+ :session_id
201
+
202
+ def initialize(ip)
203
+ @ip = ip
204
+ @epoch = 0
205
+ @handshake = false
206
+ @seq_num_w = 0
207
+ @seq_num_r = 0
208
+ end
209
+ end
210
+ end
211
+
212
+ def create_sqlite_db(dbname)
213
+ SQLite3::Database.new(dbname)
214
+ end
@@ -0,0 +1,87 @@
1
+ require 'active_record'
2
+ require 'sqlite3'
3
+ require 'pathname'
4
+
5
+ module CoDTLS
6
+ # A Class for managing PSKs per IP. The class has no initializer, because
7
+ # every method is static. All data are not saved, so they are lost at a
8
+ # restart.
9
+ class RAMPSKDB
10
+ @psks = []
11
+ # Sets PSK for the specified UUID. If PSK for UUID is already set,
12
+ # PSK ist saved into PSK_new. So PSK is set one time, while PSK_new
13
+ # maybe gets overwritten more times. Other values are set to standard.
14
+ #
15
+ # @param uuid [Binary] the UUID of the device in 16 byte binary form
16
+ # @param psk [String] the 16 byte long pre-shared key of the device
17
+ def self.set_psk(uuid, psk, desc = '')
18
+ entry = @psks.select { |p| p.uuid = uuid }
19
+ if entry.empty?
20
+ @psks.push(PSK.new(uuid, psk, desc))
21
+ else
22
+ entry = entry[0]
23
+ if entry.psk.nil?
24
+ entry.psk = psk
25
+ else
26
+ entry.psk_new = psk
27
+ end
28
+ end
29
+ end
30
+
31
+ # Gets PSK for the specified UUID. If PSK_new is set, the return value
32
+ # is PSK_new, else PSK is return value. If UUID is not existing
33
+ # nil will be returned.
34
+ #
35
+ # @param uuid [Binary] the UUID of the device in 16 byte binary form
36
+ # @return [String] the 16 byte long pre-shared key for the UUID
37
+ def self.get_psk(uuid)
38
+ entry = @psks.select { |p| p.uuid = uuid }
39
+ return nil if entry.empty?
40
+ entry = entry[0]
41
+ if entry.psk_new.nil?
42
+ return entry.psk
43
+ else
44
+ return entry.psk_new
45
+ end
46
+ end
47
+
48
+ # Deletes the PSK for the provided UUID. Handle with care, PSK_new and PSK
49
+ # are lost after this.
50
+ #
51
+ # @param uuid [Binary] the UUID of the device in 16 byte binary form
52
+ # @return [NilCLass] nil if uuid couldn't be found
53
+ def self.del_psk!(uuid)
54
+ entry = @psks.select { |p| p.uuid = uuid }
55
+ return nil if entry.empty?
56
+ @psks.delete(entry)
57
+ end
58
+
59
+ # Returns an array of all registered UUIDs.
60
+ #
61
+ # @return [Array] of {uuid: A, psk: B, desc: C}
62
+ def self.all_registered
63
+ entries = @psks
64
+ entries.map { |i| [i.uuid, i.psk_new.nil? ? i.psk : i.psk_new, i.desc] }
65
+ end
66
+
67
+ # Removes the all entrys from database in TABLE 2.
68
+ def self.clear_all
69
+ @psks = []
70
+ end
71
+ end
72
+
73
+ # Class representn the dtls_devices table in the database
74
+ class PSK
75
+ attr_accessor :uuid, :psk, :psk_new, :desc
76
+
77
+ def initialize(uuid, psk, desc)
78
+ @uuid = uuid
79
+ @psk = psk
80
+ @desc = desc
81
+ end
82
+ end
83
+ end
84
+
85
+ def create_sqlite_db(dbname)
86
+ SQLite3::Database.new(dbname)
87
+ end
@@ -0,0 +1,202 @@
1
+ module CoDTLS
2
+ # TODO
3
+ class RecordError < StandardError
4
+ end
5
+
6
+ # Tolle Klasse
7
+ class Record
8
+ TYPE = { bit8: 0, alert: 1, handshake: 2, appdata: 3 }
9
+ VERSION = { v10: 0, bit16: 1, v12: 2, reserved: 3 }
10
+ EPOCH = { e0: 0, e1: 1, e2: 2, e3: 3, e4: 4,
11
+ bit8: 5, bit16: 6, implizit: 7 }
12
+ SEQ_NUM = { none: 0, bit8: 1, bit16: 2, bit24: 3,
13
+ bit32: 4, bit40: 5, bit48: 6, implizit: 7 }
14
+ LENGTH = { l0: 0, bit8: 1, bit16: 2, implizit: 3 }
15
+
16
+ attr_reader :type, :version, :epoch, :seq_num, :length
17
+
18
+ def self.parse(data)
19
+ data.force_encoding('ASCII-8BIT')
20
+ fail RecordError, 'GR1' if data.length < 2
21
+ header = data.slice!(0...2).unpack('n')[0]
22
+
23
+ type = TYPE.keys[header >> 13]
24
+ if type == :bit8
25
+ fail RecordError, 'GR1' if data.length < 1
26
+ type = data.slice!(0...1).unpack('C')[0]
27
+ end
28
+
29
+ version = VERSION.keys[(header >> 11) & 0x03]
30
+ if version == :bit16
31
+ fail RecordError, 'GR1' if data.length < 2
32
+ version = data.slice!(0...2).unpack('n')[0]
33
+ end
34
+
35
+ epoch = EPOCH.keys[(header >> 8) & 0x07]
36
+ epoch = case epoch
37
+ when :implizit then :implizit
38
+ when :bit16
39
+ fail RecordError, 'GR1' if data.length < 2
40
+ data.slice!(0...2).unpack('n')[0]
41
+ when :bit8
42
+ fail RecordError, 'GR1' if data.length < 1
43
+ data.slice!(0...1).unpack('C')[0]
44
+ else EPOCH[epoch]
45
+ end
46
+
47
+ sequence = SEQ_NUM.keys[(header >> 2) & 0x07]
48
+ sequence = case sequence
49
+ when :none then :none
50
+ when :implizit then :implizit
51
+ else
52
+ bytes = SEQ_NUM[sequence]
53
+ fail RecordError, 'GR1' if data.length < bytes
54
+ num = data.slice!(0...bytes).reverse
55
+ num += "\x00" while num.length < 8
56
+ num.unpack('Q')[0]
57
+ end
58
+
59
+ length = LENGTH.keys[header & 0x03]
60
+ length = case length
61
+ when :implizit then :implizit
62
+ when :bit16
63
+ fail RecordError, 'GR1' if data.length < 2
64
+ data.slice!(0...2).unpack('n')[0]
65
+ when :bit8
66
+ fail RecordError, 'GR1' if data.length < 1
67
+ data.slice!(0...1).unpack('C')[0]
68
+ else LENGTH[length]
69
+ end
70
+
71
+ r = Record.new(type, epoch, sequence)
72
+ r.version = version
73
+ r.length = length
74
+ [r, data.slice!(length == :implizit ? 0..-1 : 0...length)]
75
+ end
76
+
77
+ public
78
+
79
+ def initialize(type, epoch, seq_num)
80
+ self.type = type
81
+ self.version = :v12
82
+ self.epoch = epoch
83
+ self.seq_num = seq_num
84
+ self.length = :implizit
85
+
86
+ @header_add = ''.force_encoding('ASCII-8BIT')
87
+ end
88
+
89
+ def type=(type)
90
+ check_param(type, TYPE, [1, 2, 3], 2**8 - 1)
91
+ @type = type
92
+ end
93
+
94
+ def version=(version)
95
+ check_param(version, VERSION, [0, 2], 2**16 - 1)
96
+ @version = version
97
+ end
98
+
99
+ def epoch=(epoch)
100
+ check_param(epoch, EPOCH, [7], 2**16 - 1)
101
+ @epoch = epoch
102
+ end
103
+
104
+ def seq_num=(seq_num)
105
+ check_param(seq_num, SEQ_NUM, [0, 7], 2**48 - 1)
106
+ @seq_num = seq_num
107
+ end
108
+
109
+ def length=(length)
110
+ check_param(length, LENGTH, [3], 2**16 - 1)
111
+ @length = length
112
+ end
113
+
114
+ def nonce(iv)
115
+ nonce = iv + [@seq_num.class == Symbol ? 0 : @seq_num].pack('Q').reverse
116
+ nonce[4..5] = [@epoch.class == Symbol ? 0 : @epoch].pack('n')
117
+ nonce
118
+ end
119
+
120
+ def to_wire
121
+ @header = 0x00C0
122
+ @header_add.clear
123
+
124
+ append_type
125
+ append_version
126
+ append_epoch
127
+ append_seq_num
128
+ append_length
129
+ end
130
+
131
+ private
132
+
133
+ def check_param(value, hash, valid, max)
134
+ case value
135
+ when Symbol
136
+ fail RecordError, 'GR1' unless valid.include?(hash[value])
137
+ when Integer
138
+ fail RecordError, 'GR2' unless value >= 0 && value <= max
139
+ else fail RecordError, "GRU #{hash}, Input: #{value.class}"
140
+ end
141
+ end
142
+
143
+ def append_type
144
+ case @type
145
+ when Symbol then @header |= TYPE[@type] << 13
146
+ when Integer
147
+ @header |= TYPE[:bit8] << 13
148
+ @header_add += [type].pack('C')
149
+ end
150
+ end
151
+
152
+ def append_version
153
+ case @version
154
+ when Symbol then @header |= VERSION[@version] << 11
155
+ when Integer
156
+ @header |= VERSION[:bit16] << 11
157
+ @header_add += [version].pack('n')
158
+ end
159
+ end
160
+
161
+ def append_epoch
162
+ case @epoch
163
+ when Symbol then @header |= EPOCH[@epoch] << 8
164
+ when Integer
165
+ case
166
+ when @epoch < 5 then @header |= @epoch << 8
167
+ else
168
+ num = [@epoch].pack('n').bytes.drop_while { |i| i == 0 }
169
+ @header_add += num.pack('C*')
170
+ @header |= (4 + num.length) << 8
171
+ end
172
+ end
173
+ end
174
+
175
+ def append_seq_num
176
+ case @seq_num
177
+ when Symbol then @header |= SEQ_NUM[@seq_num] << 2
178
+ when Integer
179
+ if @seq_num == 0
180
+ @header_add += "\x00"
181
+ @header |= 1 << 2
182
+ else
183
+ num = [@seq_num].pack('Q').reverse.bytes.drop_while { |i| i == 0 }
184
+ @header_add += num.pack('C*')
185
+ @header |= num.length << 2
186
+ end
187
+ end
188
+ end
189
+
190
+ def append_length
191
+ case @length
192
+ when Symbol then @header |= LENGTH[@length]
193
+ when Integer
194
+ num = [@length].pack('n').bytes.drop_while { |i| i == 0 }
195
+ @header_add += num.pack('C*')
196
+ @header |= num.length
197
+ end
198
+
199
+ [@header].pack('n') + @header_add
200
+ end
201
+ end
202
+ end