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,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
|
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
|
data/lib/codtls/pskdb.rb
ADDED
@@ -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
|