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