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