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,64 @@
1
+ require 'codtls/h_type'
2
+
3
+ module CoDTLS
4
+ # Tolle Klasse
5
+ class Alert < Handshake::Type
6
+ LEVEL = { warning: 1, fatal: 2 }
7
+ DESCRIPTION = { close_notify: 0,
8
+ unexpected_message: 10,
9
+ bad_record_mac: 20,
10
+ decryption_failed_reserved: 21,
11
+ record_overflow: 22,
12
+ decompression_failure: 30,
13
+ handshake_failure: 40,
14
+ no_certificate_reserved: 41,
15
+ bad_certificate: 42,
16
+ unsupported_certificate: 43,
17
+ certificate_revoked: 44,
18
+ certificate_expired: 45,
19
+ certificate_unknown: 46,
20
+ illegal_parameter: 47,
21
+ unknown_ca: 48,
22
+ access_denied: 49,
23
+ decode_error: 50,
24
+ decrypt_error: 51,
25
+ export_restriction_reserved: 60,
26
+ protocol_version: 70,
27
+ insufficient_security: 71,
28
+ internal_error: 80,
29
+ user_canceled: 90,
30
+ no_renegotiation: 100,
31
+ unsupported_extension: 110 }
32
+
33
+ attr_reader :level, :description
34
+
35
+ def self.parse(data)
36
+ data.force_encoding('ASCII-8BIT')
37
+ Alert.new(LEVEL.key(data[0].ord), DESCRIPTION.key(data[1].ord))
38
+ end
39
+
40
+ public
41
+
42
+ def initialize(level, description)
43
+ super(33)
44
+ self.level = level
45
+ self.description = description
46
+ end
47
+
48
+ def level=(level)
49
+ fail HandshakeError, 'Unknown Level' if LEVEL[level].nil?
50
+ @level = level
51
+ end
52
+
53
+ def description=(description)
54
+ fail HandshakeError, 'Unknown Level' if DESCRIPTION[description].nil?
55
+ @description = description
56
+ end
57
+
58
+ def to_wire
59
+ w = ''.force_encoding('ASCII-8BIT')
60
+ w.concat(LEVEL[@level].chr)
61
+ w.concat(DESCRIPTION[@description].chr)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,72 @@
1
+ require 'codtls/record'
2
+ require 'codtls/session'
3
+ require 'openssl/ccm'
4
+ require 'codtls/alert'
5
+
6
+ module CoDTLS
7
+ # TODO
8
+ module RecordLayer
9
+ # first dtls message will be removed from mesg, so u can call parse
10
+ # multiple times on a concatenation of many dtls records
11
+ def self.decrypt(packet, maxlen)
12
+ # packet = mesg, (address_family, port, hostname, numeric_address)
13
+
14
+ mesg, sender_inet_addr = packet
15
+
16
+ begin
17
+ record, data = Record.parse(mesg)
18
+ rescue RecordError
19
+ send_alert(sender_inet_addr, :fatal, :decode_error)
20
+ return ['', sender_inet_addr]
21
+ end
22
+
23
+ session = Session.new(sender_inet_addr[3])
24
+ unless session.check_seq(record.seq_num)
25
+ send_alert(sender_inet_addr, :fatal, :decode_error)
26
+ return ['', sender_inet_addr]
27
+ end
28
+
29
+ if record.epoch > 0
30
+ keyblock = session.key_block
31
+ if keyblock.empty?
32
+ send_alert(sender_inet_addr, :fatal, :decode_error)
33
+ return ['', sender_inet_addr]
34
+ end
35
+
36
+ ccm = OpenSSL::CCM.new('AES', keyblock[16...32], 8)
37
+ data = ccm.decrypt(data, record.nonce(keyblock[36...40]))
38
+ if data.empty?
39
+ send_alert(sender_inet_addr, :fatal, :bad_record_mac)
40
+ return ['', sender_inet_addr]
41
+ end
42
+ else
43
+ if session.epoch > 0
44
+ # When Epoch > 0 is known, message in epoch 0 isnt acceptable
45
+ send_alert(sender_inet_addr, :fatal, :unexpected_message)
46
+ return ['', sender_inet_addr]
47
+ end
48
+
49
+ # WARNING: !!! -> disabled for testing purpose
50
+ # if record.type == :appdata
51
+ # send_alert(sender_inet_addr, :fatal, :unexpected_message)
52
+ # return ['', sender_inet_addr]
53
+ # end
54
+ end
55
+
56
+ if record.type == :alert
57
+ session.clear
58
+ return ['', sender_inet_addr]
59
+ end
60
+
61
+ session.seq = record.seq_num
62
+ [data[0...maxlen], sender_inet_addr]
63
+ end
64
+
65
+ def self.send_alert(sender_inet_addr, lvl, desc)
66
+ e = encrypt(Alert.new(lvl, desc).to_wire, sender_inet_addr[3], :alert)
67
+
68
+ s = UDPSocket.new(sender_inet_addr[0])
69
+ s.send(e, 0, sender_inet_addr[3], sender_inet_addr[1])
70
+ end
71
+ end
72
+ end
data/lib/codtls/ecc.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'openssl'
2
+
3
+ module CoDTLS
4
+ # Class for multiplicaion on elliptic curve secp256r1 (prime256v1)
5
+ class ECC
6
+ # Does a scalar multiplication with a point on elliptic curve secp256r1.
7
+ #
8
+ # @param private_key [String] scalar for the multiplication (msb)
9
+ # @param public_key [String] point for the multiplication (0x04, x, y)
10
+ #
11
+ # @return [String] the resulting point (0x04, x, y)
12
+ def self.mult(private_key, public_key = nil)
13
+ key = OpenSSL::BN.new(private_key, 2)
14
+
15
+ group = OpenSSL::PKey::EC::Group.new('prime256v1')
16
+ if public_key.nil?
17
+ point = group.generator
18
+ else
19
+ bignum = OpenSSL::BN.new(public_key, 2)
20
+ point = OpenSSL::PKey::EC::Point.new(group, bignum)
21
+ end
22
+
23
+ point.mul(key).to_bn.to_s(2)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ require 'codtls/record'
2
+ require 'codtls/session'
3
+ require 'openssl/ccm'
4
+
5
+ module CoDTLS
6
+ # TODO
7
+ module RecordLayer
8
+ # TODO
9
+ def self.encrypt(mesg, ip, type = :default)
10
+ session = Session.new(ip)
11
+ type = session.handshake? ? :handshake : :appdata if type == :default
12
+
13
+ # WARNING: !!! -> disabled for testing purpose
14
+ # if session.epoch == 0 && type == :appdata
15
+ # fail SecureSocketError, 'app-data not allowed in epoch 0'
16
+ # end
17
+
18
+ record = Record.new(type, session.epoch, session.seq)
19
+
20
+ if record.epoch > 0
21
+ keyblock = session.key_block
22
+ ccm = OpenSSL::CCM.new('AES', keyblock[0...16], 8)
23
+ record.to_wire + ccm.encrypt(mesg, record.nonce(keyblock[32...36]))
24
+ else
25
+ record.to_wire + mesg
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ require 'codtls/h_type'
2
+
3
+ module CoDTLS
4
+ module Handshake
5
+ # Tolle Klasse
6
+ class ChangeCipherSpec < Type
7
+ def self.parse(data)
8
+ data.force_encoding('ASCII-8BIT')
9
+ fail HandshakeError, 'Missing data' if data.length < 1
10
+ fail HandshakeError, 'Wrong value' unless data[0] == "\x01"
11
+ ChangeCipherSpec.new
12
+ end
13
+
14
+ public
15
+
16
+ def initialize
17
+ super(32)
18
+ end
19
+
20
+ def to_wire
21
+ "\x01".force_encoding('ASCII-8BIT')
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,79 @@
1
+ require 'codtls/h_type'
2
+
3
+ module CoDTLS
4
+ module Handshake
5
+ # Tolle Klasse
6
+ class ClientHello < Type
7
+ attr_reader :time, :random, :cookie
8
+
9
+ public
10
+
11
+ def initialize(time, random, cookie = nil)
12
+ super(1)
13
+ self.time = time
14
+ self.random = random
15
+ self.cookie = cookie
16
+ end
17
+
18
+ def time=(time)
19
+ if time.nil? || time > 0xFFFFFFFF
20
+ fail HandshakeError, 'Invalid time value'
21
+ end
22
+ @time = time
23
+ end
24
+
25
+ def random=(random)
26
+ if random.nil? || random.b.length != 28
27
+ fail HandshakeError, 'Random needs to have a length of 28 byte'
28
+ else
29
+ @random = random.force_encoding('ASCII-8BIT')
30
+ end
31
+ end
32
+
33
+ def cookie=(cookie)
34
+ @cookie = cookie
35
+ unless @cookie.nil?
36
+ @cookie.force_encoding('ASCII-8BIT')
37
+ if @cookie.length > 255
38
+ fail HandshakeError, 'Maximum cookie length is 255'
39
+ end
40
+ end
41
+ end
42
+
43
+ def to_wire
44
+ s = String.new("\xFE\xFD".force_encoding('ASCII-8BIT')) # Version 1.2
45
+ s.concat([@time].pack('N'))
46
+ s.concat(@random)
47
+ if @cookie.nil?
48
+ s.concat('00'.hex.chr)
49
+ else
50
+ s.concat([@cookie.length].pack('C'))
51
+ s.concat(@cookie)
52
+ end
53
+ s.concat('00'.hex.chr) # Ciphersuite Length
54
+ s.concat('02'.hex.chr) # Ciphersuite Length
55
+ s.concat('FF'.hex.chr) # Ciphersuite: TLS_PSK_ECDH_WITH_AES_128_CCM_8
56
+ s.concat('01'.hex.chr) # Ciphersuite: TLS_PSK_ECDH_WITH_AES_128_CCM_8
57
+ s.concat('01'.hex.chr) # Compression Methods Length
58
+ s.concat('00'.hex.chr) # No Compression
59
+ s.concat('00'.hex.chr) # Extensions Length
60
+ s.concat('0E'.hex.chr) # Extensions Length
61
+ s.concat('00'.hex.chr) # Supported Elliptic Curves Extension
62
+ s.concat('0a'.hex.chr) # Supported Elliptic Curves Extension
63
+ s.concat('00'.hex.chr) # Supported Elliptic Curves Extension Length
64
+ s.concat('04'.hex.chr) # Supported Elliptic Curves Extension Length
65
+ s.concat('00'.hex.chr) # Elliptic Curves Arrays Length
66
+ s.concat('02'.hex.chr) # Elliptic Curves Arrays Length
67
+ s.concat('00'.hex.chr) # Elliptic Curve secp256r1
68
+ s.concat('23'.hex.chr) # Elliptic Curve secp256r1
69
+ s.concat('00'.hex.chr) # Supported Point Formats Extension
70
+ s.concat('0B'.hex.chr) # Supported Point Formats Extension
71
+ s.concat('00'.hex.chr) # Supported Point Formats Extension Length
72
+ s.concat('02'.hex.chr) # Supported Point Formats Extension Length
73
+ s.concat('01'.hex.chr) # Point Formats Arrays Length
74
+ s.concat('00'.hex.chr) # Uncompressed Point
75
+ s
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,57 @@
1
+ require 'codtls/h_chello'
2
+ require 'codtls/h_verify'
3
+ require 'codtls/h_shello'
4
+ require 'codtls/h_keyexchange'
5
+ require 'codtls/h_shellodone'
6
+ require 'codtls/h_finished'
7
+ require 'codtls/h_changecipherspec'
8
+ require 'codtls/alert'
9
+
10
+ module CoDTLS
11
+ module Handshake
12
+ # Tolle Klasse
13
+ class Content
14
+ TYPE = Array.new(64, nil)
15
+ # TYPE[0] = hello_request
16
+ TYPE[1] = Handshake::ClientHello
17
+ TYPE[2] = Handshake::ServerHello
18
+ TYPE[3] = Handshake::HelloVerifyRequest
19
+ # TYPE[11] = certificate
20
+ TYPE[12] = Handshake::ServerKeyExchange
21
+ # TYPE[13] = certificate_request
22
+ TYPE[14] = Handshake::ServerHelloDone
23
+ # TYPE[15] = certificate_verify
24
+ TYPE[16] = Handshake::ClientKeyExchange
25
+ TYPE[20] = Handshake::Finished
26
+ TYPE[32] = Handshake::ChangeCipherSpec
27
+ TYPE[33] = Alert
28
+
29
+ # data == string -> content vorne abnehmen.
30
+ # rueckgabe ist spezifisches content object
31
+ def self.get_content(data)
32
+ data.force_encoding('ASCII-8BIT')
33
+ header = data.slice!(0).ord
34
+ (4 - header & 0x03).times { data.insert(0, "\x00") }
35
+ length = data.slice!(0...4).unpack('N')[0]
36
+ if TYPE[(header & 0xFC) >> 2].nil?
37
+ fail HandshakeError, 'unknown content type'
38
+ end
39
+ fail HandshakeError, 'missing handshake data' if data.length < length
40
+ TYPE[(header & 0xFC) >> 2].parse(data.slice!(0...length))
41
+ end
42
+
43
+ # content braucht to_wire methode.
44
+ # wird in content verpackt und an data-string angehangen
45
+ def self.add_content(data, content)
46
+ header = content.id << 2
47
+ content = content.to_wire
48
+ length = [content.length].pack('N')
49
+ length.slice!(0) while length[0] == "\x00"
50
+ header |= length.length
51
+ data.concat(header.chr)
52
+ data.concat(length)
53
+ data.concat(content)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,30 @@
1
+ require 'codtls/h_type'
2
+
3
+ module CoDTLS
4
+ module Handshake
5
+ # Tolle Klasse
6
+ class Finished < Type
7
+ attr_reader :value
8
+
9
+ def self.parse(data)
10
+ if data.force_encoding('ASCII-8BIT').length < 2
11
+ fail HandshakeError, 'Missing data'
12
+ end
13
+ Finished.new(data)
14
+ end
15
+
16
+ public
17
+
18
+ def initialize(value)
19
+ super(20)
20
+ @value = value
21
+ @value = '' if value.nil?
22
+ @value.force_encoding('ASCII-8BIT')
23
+ end
24
+
25
+ def to_wire
26
+ @value.force_encoding('ASCII-8BIT')
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,131 @@
1
+ require 'codtls/h_type'
2
+
3
+ module CoDTLS
4
+ module Handshake
5
+ # Tolle Klasse
6
+ class KeyExchange < Type
7
+ ECCURVETYPE = { explicit_prime: "\x01",
8
+ explicit_char2: "\x02",
9
+ named_curve: "\x03"
10
+ # reserved(248..255)
11
+ # max = 255
12
+ }
13
+ NAMEDCURVE = { sect163k1: "\x00\x01",
14
+ sect163r1: "\x00\x02",
15
+ sect163r2: "\x00\x03",
16
+ sect193r1: "\x00\x04",
17
+ sect193r2: "\x00\x05",
18
+ sect233k1: "\x00\x06",
19
+ sect233r1: "\x00\x07",
20
+ sect239k1: "\x00\x08",
21
+ sect283k1: "\x00\x09",
22
+ sect283r1: "\x00\x10",
23
+ sect409k1: "\x00\x11",
24
+ sect409r1: "\x00\x12",
25
+ sect571k1: "\x00\x13",
26
+ sect571r1: "\x00\x14",
27
+ secp160k1: "\x00\x15",
28
+ secp160r1: "\x00\x16",
29
+ secp160r2: "\x00\x17",
30
+ secp192k1: "\x00\x18",
31
+ secp192r1: "\x00\x19",
32
+ secp224k1: "\x00\x20",
33
+ secp224r1: "\x00\x21",
34
+ secp256k1: "\x00\x22",
35
+ secp256r1: "\x00\x23",
36
+ secp384r1: "\x00\x24",
37
+ secp521r1: "\x00\x25",
38
+ # reserved: \xfe\x00..\xfe\xff
39
+ arbitrary_explicit_prime_curves: "\xff\x01",
40
+ arbitrary_explicit_char2_curves: "\xff\x02"
41
+ # max: \xffff
42
+ }
43
+ POINTTYPE = { compressed: "\x02",
44
+ uncompressed: "\x04",
45
+ hybrid: "\x06"
46
+ }
47
+
48
+ attr_reader :psk_hint, :curve, :point
49
+
50
+ def self.parse_ex(type, data)
51
+ # pskHint_len (2) + pskHint (16) + ECTyp (1) + NamedCurve (2) +
52
+ # ECPoint_len (1) + Point_type (1) + p_X (32) + p_Y (32) = (87)
53
+ data.force_encoding('ASCII-8BIT')
54
+ psk_len = data.slice!(0...2).unpack('n')[0]
55
+ psk_hint = data.slice!(0...psk_len)
56
+ curve = data.slice!(0...3)
57
+ point_len = data.slice!(0...1).ord
58
+ point = data.slice!(0...point_len)
59
+ type.new(psk_hint, curve, point)
60
+ end
61
+
62
+ public
63
+
64
+ def initialize(type, psk_hint, curve, point)
65
+ super(type)
66
+ self.psk_hint = psk_hint
67
+ self.curve = curve
68
+ self.point = point
69
+ end
70
+
71
+ def psk_hint=(psk_hint)
72
+ if psk_hint.nil? || psk_hint.b.length == 0
73
+ fail HandshakeError, 'PSK-Hint needed'
74
+ else
75
+ @psk_hint = psk_hint.force_encoding('ASCII-8BIT')
76
+ end
77
+ end
78
+
79
+ def curve=(curve)
80
+ if curve.nil? || curve.b.length == 0
81
+ fail HandshakeError, 'Curve needed'
82
+ else
83
+ @curve = curve.force_encoding('ASCII-8BIT')
84
+ end
85
+ end
86
+
87
+ def point=(point)
88
+ if point.nil? || point.b.length == 0
89
+ fail HandshakeError, 'Point needed'
90
+ else
91
+ @point = point.force_encoding('ASCII-8BIT')
92
+ end
93
+ end
94
+
95
+ def to_wire
96
+ # pskHint_len (2) + pskHint (16) + ECTyp (1) + NamedCurve (2) +
97
+ # ECPoint_len (1) + Point_type (1) + p_X (32) + p_Y (32) = (87)
98
+ s = ''
99
+ s.force_encoding('ASCII-8BIT')
100
+ s.concat([@psk_hint.length].pack('n'))
101
+ s.concat(@psk_hint)
102
+ s.concat(@curve)
103
+ s.concat([@point.length].pack('c'))
104
+ s.concat(@point)
105
+ s
106
+ end
107
+ end
108
+
109
+ # laber
110
+ class ServerKeyExchange < KeyExchange
111
+ def self.parse(data)
112
+ parse_ex(ServerKeyExchange, data)
113
+ end
114
+
115
+ def initialize(psk_hint, curve, point)
116
+ super(12, psk_hint, curve, point)
117
+ end
118
+ end
119
+
120
+ # laber
121
+ class ClientKeyExchange < KeyExchange
122
+ def self.parse(data)
123
+ parse_ex(ClientKeyExchange, data)
124
+ end
125
+
126
+ def initialize(psk_hint, curve, point)
127
+ super(16, psk_hint, curve, point)
128
+ end
129
+ end
130
+ end
131
+ end