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