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