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,51 @@
1
+ require 'codtls/h_type'
2
+
3
+ module CoDTLS
4
+ module Handshake
5
+ # Tolle Klasse
6
+ class ServerHello < Type
7
+ attr_reader :time, :random, :session
8
+
9
+ def self.parse(data)
10
+ data.force_encoding('ASCII-8BIT')
11
+ data.slice!(0...2) # TODO: check version
12
+ t = data.slice!(0...4).unpack('N')[0]
13
+ r = data.slice!(0...28)
14
+ s = data.slice!(0...data.slice!(0).ord)
15
+ ServerHello.new(t, r, s)
16
+ end
17
+
18
+ public
19
+
20
+ def initialize(time, random, session)
21
+ super(2)
22
+ self.time = time
23
+ self.random = random
24
+ self.session = session
25
+ end
26
+
27
+ def time=(time)
28
+ if time.nil? || time > 0xFFFFFFFF
29
+ fail HandshakeError, 'Invalid time value'
30
+ end
31
+ @time = time
32
+ end
33
+
34
+ def random=(random)
35
+ if random.nil? || random.b.length != 28
36
+ fail HandshakeError, 'Random needs to have a length of 28 byte'
37
+ else
38
+ @random = random.force_encoding('ASCII-8BIT')
39
+ end
40
+ end
41
+
42
+ def session=(session)
43
+ if session.nil? || session.b.length.between?(1, 255)
44
+ fail HandshakeError, 'Session length needs to be in 1 - 255'
45
+ end
46
+ @session = session
47
+ @session.force_encoding('ASCII-8BIT')
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,22 @@
1
+ require 'codtls/h_type'
2
+
3
+ module CoDTLS
4
+ module Handshake
5
+ # Tolle Klasse
6
+ class ServerHelloDone < Type
7
+ def self.parse(data)
8
+ ServerHelloDone.new
9
+ end
10
+
11
+ public
12
+
13
+ def initialize
14
+ super(32)
15
+ end
16
+
17
+ def to_wire
18
+ ''.force_encoding('ASCII-8BIT')
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module CoDTLS
2
+ module Handshake
3
+ # Tolle Klasse
4
+ class Type
5
+ attr_reader :id
6
+
7
+ def self.parse(data)
8
+ fail HandshakeError, 'Not implemented'
9
+ end
10
+
11
+ public
12
+
13
+ def initialize(id)
14
+ @id = id
15
+ end
16
+
17
+ def to_wire
18
+ fail HandshakeError, 'Not implemented'
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ require 'codtls/h_type'
2
+
3
+ module CoDTLS
4
+ module Handshake
5
+ # Tolle Klasse
6
+ class HelloVerifyRequest < Type
7
+ attr_reader :cookie
8
+
9
+ def self.parse(data)
10
+ # typedef struct {
11
+ # ProtocolVersion server_version;
12
+ # uint8_t cookie_len;
13
+ # uint8_t cookie[0];
14
+ # } __attribute__ ((packed)) HelloVerifyRequest_t;
15
+
16
+ # Zu erledigen: checks auf version und laenge
17
+ HelloVerifyRequest.new(data.force_encoding('ASCII-8BIT')[3..-1])
18
+ end
19
+
20
+ public
21
+
22
+ def initialize(cookie)
23
+ super(3)
24
+ @cookie = cookie
25
+ @cookie = '' if cookie.nil?
26
+ @cookie.force_encoding('ASCII-8BIT')
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,173 @@
1
+ require 'codtls/h_content'
2
+ require 'codtls/ecc'
3
+ require 'openssl/cmac'
4
+ require 'openssl/ccm'
5
+ require 'codtls/prf'
6
+ require 'codtls/pskdb'
7
+ require 'coap'
8
+
9
+ module CoDTLS
10
+ # TODO
11
+ class HandshakeError < StandardError
12
+ end
13
+
14
+ # Tolles Modul
15
+ module Handshake
16
+ # TODO
17
+ def self.handshake(numeric_address)
18
+ logger = Logger.new(STDOUT)
19
+ logger.level = CoDTLS::LOG_LEVEL
20
+ logger.debug("Handshake gestarted #{numeric_address}")
21
+
22
+ session = Session.new(numeric_address)
23
+ session.enable_handshake
24
+
25
+ logger.debug("Session created")
26
+
27
+ c = CoAP::Client.new(48).use_dtls
28
+ uuid = c.get(numeric_address, 5684, '/d/uuid').payload
29
+
30
+ logger.debug("UUID erhalten: #{uuid}")
31
+
32
+ psk = CoDTLS::PSKDB.get_psk(uuid)
33
+ logger.debug("PSK: #{psk}")
34
+ psk.nil? ? 5 : 0
35
+ =begin
36
+ session = Session.new(numeric_address)
37
+ session.enable_handshake
38
+
39
+ c = CoAP::Client.new(48).use_dtls
40
+
41
+ state = State.new
42
+
43
+ # Step 1 - ClientHello ohne Cookie
44
+ msg = ''.force_encoding('ASCII-8BIT')
45
+ client_hello = ClientHello.new(state.client_time, state.client_random)
46
+ Content.add_content(msg, client_hello)
47
+ r = c.post(numeric_address, 5684, '/dtls', msg)
48
+
49
+ hello_verify = Content.get_content(r.payload)
50
+ return 1 unless hello_verify.class == HelloVerifyRequest
51
+
52
+ # Step 2 - ClientHello mit Cookie
53
+ msg.clear
54
+ client_hello.cookie = hello_verify.cookie
55
+ Content.add_content(msg, client_hello)
56
+ state.add_finished_source(msg)
57
+ r = c.post(numeric_address, 5684, '/dtls', msg)
58
+ state.add_finished_source(r)
59
+
60
+ server_hello = Content.get_content(r.payload)
61
+ return 2 unless server_hello == ServerHello
62
+ server_key_exchange = Content.get_content(r.payload)
63
+ return 3 unless server_key_exchange == ServerKeyExchange
64
+ server_hello_done = Content.get_content(r.payload)
65
+ return 4 unless server_hello_done == ServerHelloDone
66
+
67
+ # Step 3 - ClientKeyExchange, ChangeCipherSpec, Finished
68
+ msg.clear
69
+ state.server_time = server_hello.time
70
+ state.server_random = server_hello.random
71
+ session.id = server_hello.session
72
+ return 5 unless state.choose_psk(server_key_exchange.psk_hint)
73
+ state.server_key = server_key_exchange.point
74
+ session.key_block = state.key_block
75
+
76
+ client_key_exchange = ClientKeyExchange.new(
77
+ server_key_exchange.psk_hint,
78
+ KeyExchange::NAMEDCURVE[:secp256r1],
79
+ state.public_key)
80
+ Content.add_content(msg, client_key_exchange)
81
+ state.add_finished_source(msg)
82
+
83
+ client_finished = state.finished('client finished')
84
+
85
+ change_cipher_spec = ChangeCipherSpec.new
86
+ Content.add_content(msg, change_cipher_spec)
87
+ finished = Finished.new(client_finished)
88
+
89
+ finished_content = ''.force_encoding('ASCII-8BIT')
90
+ Content.add_content(msg, change_cipher_spec)
91
+ state.add_finished_source(finished_content)
92
+
93
+ server_finished = state.finished('server finished')
94
+
95
+ ccm = OpenSSL::CCM.new('AES', state.key_block[0..15], 8)
96
+ nonce = state.key_block[32..35] + ("\x00" * 8)
97
+ msg.concat(ccm.encrypt(finished_content, nonce))
98
+
99
+ r = c.post(numeric_address, 5684, '/dtls', msg)
100
+
101
+ 0
102
+ =end
103
+ end
104
+
105
+ # Finished muss enthalten:
106
+ # ClientHello, ServerHello, ServerKeyExchange, ServerHelloDone,
107
+ # ClientKeyExchange, (ClientFinished)
108
+
109
+ # Tolle Klasse
110
+ class State
111
+ attr_reader :client_time, :client_random
112
+ attr_accessor :server_time, :server_random
113
+
114
+ def initialize
115
+ @client_time = Time.new.to_i
116
+ @client_random = Random.new.bytes(28)
117
+ @private_key = Random.new.bytes(32)
118
+ @finished_source = ''.force_encoding('ASCII-8BIT')
119
+ @psk = ''
120
+ @master_secret = ''
121
+ end
122
+
123
+ def client_full_random
124
+ [@client_time].pack('N') + @client_random
125
+ end
126
+
127
+ def choose_psk(psk_hint)
128
+ @psk = CoDTLS::PSKDB.get_psk(psk_hint)
129
+ @psk.nil? ? false : true
130
+ end
131
+
132
+ def public_key
133
+ CoDTLS::ECC.mult(@private_key)
134
+ end
135
+
136
+ def server_key=(key)
137
+ secret = CoDTLS::ECC.mult(@private_key, key)
138
+ pre_master = "\x00\x10"
139
+ pre_master += @psk
140
+ pre_master += "\x00\x20"
141
+ pre_master += secret[1..32]
142
+
143
+ server_full_random = [@server_time].pack('N') + @server_random
144
+
145
+ prf = OpenSSL::PRF.new(pre_master, 'master secret',
146
+ client_full_random + server_full_random)
147
+ @master_secret = prf.get(48)
148
+ end
149
+
150
+ def key_block
151
+ fail HandshakeError, 'Missing mastersecret' if @master_secret == ''
152
+
153
+ server_full_random = [@server_time].pack('N') + @server_random
154
+ prf = OpenSSL::PRF.new(@master_secret, 'key expansion',
155
+ server_full_random + client_full_random)
156
+ prf.get(40)
157
+ end
158
+
159
+ def add_finished_source(data)
160
+ @finished_source.concat(data)
161
+ end
162
+
163
+ def finished(label)
164
+ fail HandshakeError, 'Missing mastersecret' if @master_secret == ''
165
+ fail HandshakeError, 'Missing pre-shared key' if @psk == ''
166
+
167
+ seed = OpenSSL::CMAC.digest('AES', @psk, @finished_source)
168
+ prf = OpenSSL::PRF.new(@master_secret, label, seed)
169
+ prf.get(12)
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,3 @@
1
+ # Class representing the dtls_connections table in the database.
2
+ class CODTLSConnection < ActiveRecord::Base
3
+ end
@@ -0,0 +1,3 @@
1
+ # Class representn the dtls_devices table in the database
2
+ class CODTLSDevice < ActiveRecord::Base
3
+ end
data/lib/codtls/prf.rb ADDED
@@ -0,0 +1,40 @@
1
+ require 'openssl'
2
+
3
+ module OpenSSL
4
+ class PRFError < StandardError
5
+ end
6
+
7
+ # PRF(secret, label, seed) = P_<hash>(secret, label + seed)
8
+ #
9
+ # P_hash(secret, seed) = CMAC_hash(secret, A(1) + seed) +
10
+ # CMAC_hash(secret, A(2) + seed) +
11
+ # CMAC_hash(secret, A(3) + seed) + ...
12
+ #
13
+ # A() is defined as:
14
+ # A(0) = seed
15
+ # A(i) = CMAC_hash(secret, A(i-1))
16
+ class PRF
17
+ def initialize(secret, label, seed)
18
+ @secret = secret.force_encoding('ASCII-8BIT')
19
+ @seed = label.force_encoding('ASCII-8BIT')
20
+ @seed += seed.force_encoding('ASCII-8BIT')
21
+ @buffer = ''.force_encoding('ASCII-8BIT')
22
+ @a_x = @seed.dup
23
+ end
24
+
25
+ def get(bytes)
26
+ output = ''.force_encoding('ASCII-8BIT')
27
+ while output.length < bytes
28
+ output += @buffer.slice!(0...(bytes - output.length))
29
+ fill_buffer if @buffer.length == 0
30
+ end
31
+ output
32
+ end
33
+
34
+ def fill_buffer
35
+ @a_x = OpenSSL::CMAC.digest('AES', @secret, @a_x)
36
+ @buffer = OpenSSL::CMAC.digest('AES', @secret, @a_x + @seed)
37
+ @buffer.force_encoding('ASCII-8BIT')
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,104 @@
1
+ require 'active_record'
2
+ require 'sqlite3'
3
+ require 'pathname'
4
+ require 'codtls/models/codtls_device'
5
+
6
+ module CoDTLS
7
+ # A Class for managing PSKs per IP. The class has no initializer, because
8
+ # every method is static.
9
+ class PSKDB
10
+ # Migrates the database to the current version. An ActiveRecord Connection
11
+ # has to exist for migration.
12
+ def self.migrate
13
+ ActiveRecord::Migration.verbose = false
14
+ ActiveRecord::Migrator.migrate('db/migrate')
15
+ end
16
+
17
+ # Sets PSK for the specified UUID. If PSK for UUID is already set,
18
+ # PSK ist saved into PSK_new. So PSK is set one time, while PSK_new
19
+ # maybe gets overwritten more times. Other values are set to standard.
20
+ #
21
+ # @param uuid [Binary] the UUID of the device in 16 byte binary form
22
+ # @param psk [String] the 16 byte long pre-shared key of the device
23
+ def self.set_psk(uuid, psk, desc = '')
24
+ ActiveRecord::Base.connection_pool.with_connection do
25
+ entry = CODTLSDevice.find_by_uuid(uuid)
26
+ if entry.nil?
27
+ CODTLSDevice.create(psk: psk,
28
+ uuid: uuid,
29
+ desc: desc
30
+ )
31
+ else
32
+ if entry.psk.nil?
33
+ entry.psk = psk
34
+ else
35
+ entry.psk_new = psk
36
+ end
37
+ entry.save
38
+ end
39
+ end
40
+ end
41
+
42
+ # Gets PSK for the specified UUID. If PSK_new is set, the return value
43
+ # is PSK_new, else PSK is return value. If UUID is not existing
44
+ # nil will be returned.
45
+ #
46
+ # @param uuid [Binary] the UUID of the device in 16 byte binary form
47
+ # @return [String] the 16 byte long pre-shared key for the UUID
48
+ def self.get_psk(uuid)
49
+ logger = Logger.new(STDOUT)
50
+ logger.level = CoDTLS::LOG_LEVEL
51
+ logger.debug('get_psk 1')
52
+ entry = nil
53
+ ActiveRecord::Base.connection_pool.with_connection do
54
+ logger.debug('get_psk 2')
55
+ entry = CODTLSDevice.find_by_uuid(uuid)
56
+ end
57
+ logger.debug('get_psk 3')
58
+ return nil if entry.nil?
59
+
60
+ if entry.psk_new.nil?
61
+ logger.debug('get_psk 4.1')
62
+ return entry.psk
63
+ else
64
+ logger.debug('get_psk 4.2')
65
+ return entry.psk_new
66
+ end
67
+ end
68
+
69
+ # Deletes the PSK for the provided UUID. Handle with care, PSK_new and PSK
70
+ # are lost after this.
71
+ #
72
+ # @param uuid [Binary] the UUID of the device in 16 byte binary form
73
+ # @return [NilCLass] nil if uuid couldn't be found
74
+ def self.del_psk!(uuid)
75
+ ActiveRecord::Base.connection_pool.with_connection do
76
+ entry = CODTLSDevice.find_by_uuid(uuid)
77
+ return nil if entry.nil?
78
+ entry.destroy
79
+ end
80
+ end
81
+
82
+ # Returns an array of all registered UUIDs.
83
+ #
84
+ # @return [Array] of {uuid: A, psk: B, desc: C}
85
+ def self.all_registered
86
+ ActiveRecord::Base.connection_pool.with_connection do
87
+ entries = CODTLSDevice.all
88
+ return nil if entries.nil?
89
+ entries.map { |i| [i.uuid, i.psk_new.nil? ? i.psk : i.psk_new, i.desc] }
90
+ end
91
+ end
92
+
93
+ # Removes the all entrys from database in TABLE 2.
94
+ def self.clear_all
95
+ ActiveRecord::Base.connection_pool.with_connection do
96
+ CODTLSDevice.destroy_all
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ def create_sqlite_db(dbname)
103
+ SQLite3::Database.new(dbname)
104
+ end