tttls1.3 0.1.0

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 (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +16 -0
  5. data/.travis.yml +8 -0
  6. data/Gemfile +13 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +52 -0
  9. data/Rakefile +133 -0
  10. data/example/helper.rb +17 -0
  11. data/example/https_client.rb +32 -0
  12. data/example/https_client_using_0rtt.rb +64 -0
  13. data/example/https_client_using_hrr.rb +35 -0
  14. data/example/https_client_using_ticket.rb +56 -0
  15. data/lib/tttls1.3/cipher_suites.rb +102 -0
  16. data/lib/tttls1.3/client.rb +745 -0
  17. data/lib/tttls1.3/connection.rb +380 -0
  18. data/lib/tttls1.3/cryptograph/aead.rb +118 -0
  19. data/lib/tttls1.3/cryptograph/passer.rb +22 -0
  20. data/lib/tttls1.3/cryptograph.rb +3 -0
  21. data/lib/tttls1.3/error.rb +22 -0
  22. data/lib/tttls1.3/key_schedule.rb +242 -0
  23. data/lib/tttls1.3/message/alert.rb +86 -0
  24. data/lib/tttls1.3/message/application_data.rb +27 -0
  25. data/lib/tttls1.3/message/certificate.rb +121 -0
  26. data/lib/tttls1.3/message/certificate_verify.rb +59 -0
  27. data/lib/tttls1.3/message/change_cipher_spec.rb +26 -0
  28. data/lib/tttls1.3/message/client_hello.rb +100 -0
  29. data/lib/tttls1.3/message/encrypted_extensions.rb +65 -0
  30. data/lib/tttls1.3/message/end_of_early_data.rb +29 -0
  31. data/lib/tttls1.3/message/extension/alpn.rb +70 -0
  32. data/lib/tttls1.3/message/extension/cookie.rb +47 -0
  33. data/lib/tttls1.3/message/extension/early_data_indication.rb +58 -0
  34. data/lib/tttls1.3/message/extension/key_share.rb +236 -0
  35. data/lib/tttls1.3/message/extension/pre_shared_key.rb +205 -0
  36. data/lib/tttls1.3/message/extension/psk_key_exchange_modes.rb +54 -0
  37. data/lib/tttls1.3/message/extension/record_size_limit.rb +46 -0
  38. data/lib/tttls1.3/message/extension/server_name.rb +91 -0
  39. data/lib/tttls1.3/message/extension/signature_algorithms.rb +69 -0
  40. data/lib/tttls1.3/message/extension/signature_algorithms_cert.rb +25 -0
  41. data/lib/tttls1.3/message/extension/status_request.rb +106 -0
  42. data/lib/tttls1.3/message/extension/supported_groups.rb +145 -0
  43. data/lib/tttls1.3/message/extension/supported_versions.rb +98 -0
  44. data/lib/tttls1.3/message/extension/unknown_extension.rb +38 -0
  45. data/lib/tttls1.3/message/extensions.rb +173 -0
  46. data/lib/tttls1.3/message/finished.rb +44 -0
  47. data/lib/tttls1.3/message/new_session_ticket.rb +89 -0
  48. data/lib/tttls1.3/message/record.rb +232 -0
  49. data/lib/tttls1.3/message/server_hello.rb +116 -0
  50. data/lib/tttls1.3/message.rb +48 -0
  51. data/lib/tttls1.3/sequence_number.rb +31 -0
  52. data/lib/tttls1.3/signature_scheme.rb +31 -0
  53. data/lib/tttls1.3/transcript.rb +69 -0
  54. data/lib/tttls1.3/utils.rb +91 -0
  55. data/lib/tttls1.3/version.rb +5 -0
  56. data/lib/tttls1.3.rb +16 -0
  57. data/spec/aead_spec.rb +95 -0
  58. data/spec/alert_spec.rb +54 -0
  59. data/spec/alpn_spec.rb +55 -0
  60. data/spec/application_data_spec.rb +26 -0
  61. data/spec/certificate_spec.rb +55 -0
  62. data/spec/certificate_verify_spec.rb +51 -0
  63. data/spec/change_cipher_spec_spec.rb +26 -0
  64. data/spec/cipher_suites_spec.rb +39 -0
  65. data/spec/client_hello_spec.rb +83 -0
  66. data/spec/client_spec.rb +319 -0
  67. data/spec/connection_spec.rb +114 -0
  68. data/spec/cookie_spec.rb +98 -0
  69. data/spec/early_data_indication_spec.rb +64 -0
  70. data/spec/encrypted_extensions_spec.rb +94 -0
  71. data/spec/error_spec.rb +18 -0
  72. data/spec/extensions_spec.rb +170 -0
  73. data/spec/finished_spec.rb +55 -0
  74. data/spec/key_schedule_spec.rb +198 -0
  75. data/spec/key_share_spec.rb +199 -0
  76. data/spec/new_session_ticket_spec.rb +80 -0
  77. data/spec/pre_shared_key_spec.rb +167 -0
  78. data/spec/psk_key_exchange_modes_spec.rb +45 -0
  79. data/spec/record_size_limit_spec.rb +61 -0
  80. data/spec/record_spec.rb +105 -0
  81. data/spec/server_hello_spec.rb +101 -0
  82. data/spec/server_name_spec.rb +110 -0
  83. data/spec/signature_algorithms_cert_spec.rb +73 -0
  84. data/spec/signature_algorithms_spec.rb +100 -0
  85. data/spec/spec_helper.rb +872 -0
  86. data/spec/status_request_spec.rb +73 -0
  87. data/spec/supported_groups_spec.rb +79 -0
  88. data/spec/supported_versions_spec.rb +136 -0
  89. data/spec/transcript_spec.rb +69 -0
  90. data/spec/unknown_extension_spec.rb +90 -0
  91. data/spec/utils_spec.rb +215 -0
  92. data/tttls1.3.gemspec +25 -0
  93. metadata +197 -0
@@ -0,0 +1,65 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module TTTLS13
5
+ using Refinements
6
+ module Message
7
+ class EncryptedExtensions
8
+ attr_reader :msg_type
9
+ attr_reader :extensions
10
+
11
+ # https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#tls-extensiontype-values-1
12
+ ALLOWED_EXTENSIONS = [
13
+ ExtensionType::SERVER_NAME,
14
+ ExtensionType::MAX_FRAGMENT_LENGTH,
15
+ ExtensionType::SUPPORTED_GROUPS,
16
+ ExtensionType::USE_SRTP,
17
+ ExtensionType::HEARTBEAT,
18
+ ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION,
19
+ ExtensionType::CLIENT_CERTIFICATE_TYPE,
20
+ ExtensionType::SERVER_CERTIFICATE_TYPE,
21
+ ExtensionType::RECORD_SIZE_LIMIT,
22
+ ExtensionType::EARLY_DATA
23
+ ].freeze
24
+
25
+ # @param extensions [TTTLS13::Message::Extensions]
26
+ def initialize(extensions = Extensions.new)
27
+ @msg_type = HandshakeType::ENCRYPTED_EXTENSIONS
28
+ @extensions = extensions || Extensions.new
29
+ end
30
+
31
+ # @return [String]
32
+ def serialize
33
+ @msg_type + @extensions.serialize.prefix_uint24_length
34
+ end
35
+
36
+ alias fragment serialize
37
+
38
+ # @param binary [String]
39
+ #
40
+ # @raise [TTTLS13::Error::ErrorAlerts]
41
+ #
42
+ # @return [TTTLS13::Message::EncryptedExtensions]
43
+ def self.deserialize(binary)
44
+ raise Error::ErrorAlerts, :internal_error if binary.nil?
45
+ raise Error::ErrorAlerts, :decode_error if binary.length < 6
46
+ raise Error::ErrorAlerts, :internal_error \
47
+ unless binary[0] == HandshakeType::ENCRYPTED_EXTENSIONS
48
+
49
+ ee_len = Convert.bin2i(binary.slice(1, 3))
50
+ exs_len = Convert.bin2i(binary.slice(4, 2))
51
+ extensions = Extensions.deserialize(binary.slice(6, exs_len),
52
+ HandshakeType::ENCRYPTED_EXTENSIONS)
53
+ raise Error::ErrorAlerts, :decode_error \
54
+ unless exs_len + 2 == ee_len && exs_len + 6 == binary.length
55
+
56
+ EncryptedExtensions.new(extensions)
57
+ end
58
+
59
+ # @return [Boolean]
60
+ def any_forbidden_extensions?
61
+ !(@extensions.keys - ALLOWED_EXTENSIONS).empty?
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module TTTLS13
5
+ module Message
6
+ class EndOfEarlyData
7
+ # @return [String]
8
+ def serialize
9
+ ''
10
+ end
11
+
12
+ # @param binary [String]
13
+ #
14
+ # @raise [TTTLS13::Error::ErrorAlerts]
15
+ #
16
+ # @return [TTTLS13::Message::EndOfEarlyData]
17
+ def self.deserialize(binary)
18
+ raise Error::ErrorAlerts, :internal_error if binary.nil?
19
+ raise Error::ErrorAlerts, :decode_error unless binary.length == 4
20
+ raise Error::ErrorAlerts, :unexpected_message \
21
+ unless binary[0] == HandshakeType::END_OF_EARLY_DATA
22
+ raise Error::ErrorAlerts, :decode_error \
23
+ unless binary == "\x05\x00\x00\x00"
24
+
25
+ EndOfEarlyData.new
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module TTTLS13
5
+ using Refinements
6
+ module Message
7
+ module Extension
8
+ class Alpn
9
+ attr_reader :extension_type
10
+ attr_reader :protocol_name_list
11
+
12
+ # @param named_group_list [Array of String]
13
+ #
14
+ # @raise [TTTLS13::Error::ErrorAlerts]
15
+ #
16
+ # @example
17
+ # Alpn.new(['h2', 'http/1.1'])
18
+ #
19
+ # https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
20
+ def initialize(protocol_name_list)
21
+ @extension_type \
22
+ = ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION
23
+ @protocol_name_list = protocol_name_list || []
24
+ raise Error::ErrorAlerts, :internal_error \
25
+ if @protocol_name_list.empty?
26
+ end
27
+
28
+ # @return [String]
29
+ def serialize
30
+ binary = @protocol_name_list.map(&:prefix_uint8_length).join
31
+
32
+ @extension_type + binary.prefix_uint16_length.prefix_uint16_length
33
+ end
34
+
35
+ # @param binary [String]
36
+ #
37
+ # @raise [TTTLS13::Error::ErrorAlerts]
38
+ #
39
+ # @return [TTTLS13::Message::Extension::Alpn, nil]
40
+ # rubocop: disable Metrics/CyclomaticComplexity
41
+ # rubocop: disable Metrics/PerceivedComplexity
42
+ def self.deserialize(binary)
43
+ raise Error::ErrorAlerts, :internal_error if binary.nil?
44
+
45
+ return nil if binary.length < 2
46
+
47
+ pnlist_len = Convert.bin2i(binary.slice(0, 2))
48
+ i = 2
49
+ protocol_name_list = []
50
+ while i < pnlist_len + 2
51
+ return nil if i + 1 > binary.length
52
+
53
+ pn_len = Convert.bin2i(binary.slice(i, 1))
54
+ i += 1
55
+ return nil if i + pn_len > binary.length
56
+
57
+ protocol_name_list << binary.slice(i, pn_len)
58
+ i += pn_len
59
+ end
60
+ return nil unless i == binary.length &&
61
+ pnlist_len + 2 == binary.length
62
+
63
+ Alpn.new(protocol_name_list)
64
+ end
65
+ # rubocop: enable Metrics/CyclomaticComplexity
66
+ # rubocop: enable Metrics/PerceivedComplexity
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module TTTLS13
5
+ using Refinements
6
+ module Message
7
+ module Extension
8
+ class Cookie
9
+ attr_reader :extension_type
10
+ attr_reader :cookie
11
+
12
+ # @param cookie [String]
13
+ #
14
+ # @raise [TTTLS13::Error::ErrorAlerts]
15
+ def initialize(cookie)
16
+ @extension_type = ExtensionType::COOKIE
17
+ @cookie = cookie || ''
18
+ raise Error::ErrorAlerts, :internal_error \
19
+ if @cookie.length > 2**16 - 3
20
+ end
21
+
22
+ # @return [String]
23
+ def serialize
24
+ @extension_type + @cookie.prefix_uint16_length.prefix_uint16_length
25
+ end
26
+
27
+ # @param binary [String]
28
+ #
29
+ # @raise [TTTLS13::Error::ErrorAlerts]
30
+ #
31
+ # @return [TTTLS13::Message::Extensions::Cookie, nil]
32
+ def self.deserialize(binary)
33
+ raise Error::ErrorAlerts, :internal_error if binary.nil?
34
+
35
+ return nil if binary.length < 2
36
+
37
+ cookie_len = Convert.bin2i(binary.slice(0, 2))
38
+ cookie = binary.slice(2, cookie_len)
39
+ return nil unless cookie_len + 2 == binary.length &&
40
+ cookie_len <= 2**16 - 3
41
+
42
+ Cookie.new(cookie)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module TTTLS13
5
+ using Refinements
6
+ module Message
7
+ module Extension
8
+ class EarlyDataIndication
9
+ attr_reader :extension_type
10
+ attr_reader :max_early_data_size
11
+
12
+ # @param max_early_data_size [Integer, nil]
13
+ #
14
+ # @raise [TTTLS13::Error::ErrorAlerts]
15
+ def initialize(max_early_data_size = nil)
16
+ @extension_type = ExtensionType::EARLY_DATA
17
+ @max_early_data_size = max_early_data_size
18
+ raise Error::ErrorAlerts, :internal_error \
19
+ unless @max_early_data_size.nil? || @max_early_data_size < 2**32
20
+ end
21
+
22
+ # @return [String]
23
+ def serialize
24
+ binary = ''
25
+ binary = @max_early_data_size.to_uint32 \
26
+ unless @max_early_data_size.nil?
27
+
28
+ @extension_type + binary.prefix_uint16_length
29
+ end
30
+
31
+ # @param binary [String]
32
+ # @param msg_type [TTTLS13::Message::ContentType]
33
+ #
34
+ # @raise [TTTLS13::Error::ErrorAlerts]
35
+ #
36
+ # @return [TTTLS13::Message::Extensions::EarlyDataIndication, nil]
37
+ def self.deserialize(binary, msg_type)
38
+ raise Error::ErrorAlerts, :internal_error if binary.nil?
39
+
40
+ case msg_type
41
+ when HandshakeType::CLIENT_HELLO, HandshakeType::ENCRYPTED_EXTENSIONS
42
+ return nil unless binary.empty?
43
+
44
+ max_early_data_size = nil
45
+ when HandshakeType::NEW_SESSION_TICKET
46
+ return nil unless binary.length == 4
47
+
48
+ max_early_data_size = Convert.bin2i(binary)
49
+ else
50
+ raise Error::ErrorAlerts, :internal_error
51
+ end
52
+
53
+ EarlyDataIndication.new(max_early_data_size)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,236 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module TTTLS13
5
+ using Refinements
6
+ module Message
7
+ module Extension
8
+ # rubocop: disable Metrics/ClassLength
9
+ class KeyShare
10
+ attr_reader :extension_type
11
+ attr_reader :msg_type
12
+ attr_reader :key_share_entry
13
+
14
+ # @param msg_type [TTTLS13::Message::ContentType]
15
+ # @param key_share_entry [Array of KeyShareEntry]
16
+ #
17
+ # @raise [TTTLS13::Error::ErrorAlerts]
18
+ # rubocop: disable Metrics/CyclomaticComplexity
19
+ # rubocop: disable Metrics/PerceivedComplexity
20
+ def initialize(msg_type:, key_share_entry: [])
21
+ @extension_type = ExtensionType::KEY_SHARE
22
+ @msg_type = msg_type
23
+ @key_share_entry = key_share_entry || []
24
+ raise Error::ErrorAlerts, :internal_error \
25
+ unless (@msg_type == HandshakeType::CLIENT_HELLO &&
26
+ @key_share_entry.length >= 0 &&
27
+ @key_share_entry.all?(&:valid_key_share_client_hello?)) ||
28
+ (@msg_type == HandshakeType::SERVER_HELLO &&
29
+ @key_share_entry.length == 1 &&
30
+ @key_share_entry.first.valid_key_share_server_hello?) ||
31
+ (@msg_type == HandshakeType::HELLO_RETRY_REQUEST &&
32
+ @key_share_entry.length == 1 &&
33
+ @key_share_entry.first.valid_key_share_hello_retry_request?)
34
+ end
35
+ # rubocop: enable Metrics/CyclomaticComplexity
36
+ # rubocop: enable Metrics/PerceivedComplexity
37
+
38
+ # @raise [TTTLS13::Error::ErrorAlerts]
39
+ #
40
+ # @return [String]
41
+ def serialize
42
+ case @msg_type
43
+ when HandshakeType::CLIENT_HELLO
44
+ binary = @key_share_entry.map(&:serialize).join.prefix_uint16_length
45
+ when HandshakeType::SERVER_HELLO, HandshakeType::HELLO_RETRY_REQUEST
46
+ binary = @key_share_entry.first.serialize
47
+ else
48
+ raise Error::ErrorAlerts, :internal_error
49
+ end
50
+ @extension_type + binary.prefix_uint16_length
51
+ end
52
+
53
+ # @param binary [String]
54
+ # @param msg_type [TTTLS13::Message::HandshakeType]
55
+ #
56
+ # @raise [TTTLS13::Error::ErrorAlerts]
57
+ #
58
+ # @return [TTTLS13::Message::Extensions::KeyShare, nil]
59
+ # rubocop: disable Metrics/CyclomaticComplexity
60
+ def self.deserialize(binary, msg_type)
61
+ raise Error::ErrorAlerts, :internal_error if binary.nil?
62
+
63
+ case msg_type
64
+ when HandshakeType::CLIENT_HELLO
65
+ key_share_entry = deserialize_keyshare_ch(binary)
66
+ return nil \
67
+ unless key_share_entry.all?(&:valid_key_share_client_hello?)
68
+ when HandshakeType::SERVER_HELLO
69
+ key_share_entry = deserialize_keyshare_sh(binary)
70
+ return nil \
71
+ unless key_share_entry.first.valid_key_share_server_hello?
72
+ when HandshakeType::HELLO_RETRY_REQUEST
73
+ key_share_entry = deserialize_keyshare_hrr(binary)
74
+ return nil \
75
+ unless key_share_entry.first.valid_key_share_hello_retry_request?
76
+ else
77
+ raise Error::ErrorAlerts, :internal_error
78
+ end
79
+ return nil if key_share_entry.nil?
80
+
81
+ KeyShare.new(msg_type: msg_type,
82
+ key_share_entry: key_share_entry)
83
+ end
84
+ # rubocop: enable Metrics/CyclomaticComplexity
85
+
86
+ # @param groups [Array of TTTLS13::Message::Extension::NamedGroup]
87
+ #
88
+ # @return [TTTLS13::Message::Extensions::KeyShare]
89
+ # @return [Hash of NamedGroup => OpenSSL::PKey::EC.$Object]
90
+ def self.gen_ch_key_share(groups)
91
+ priv_keys = {}
92
+ kse = groups.map do |group|
93
+ curve = NamedGroup.curve_name(group)
94
+ ec = OpenSSL::PKey::EC.new(curve)
95
+ ec.generate_key!
96
+ # store private key to do the key-exchange
97
+ priv_keys.store(group, ec)
98
+ KeyShareEntry.new(
99
+ group: group,
100
+ key_exchange: ec.public_key.to_octet_string(:uncompressed)
101
+ )
102
+ end
103
+
104
+ key_share = KeyShare.new(
105
+ msg_type: HandshakeType::CLIENT_HELLO,
106
+ key_share_entry: kse
107
+ )
108
+
109
+ [key_share, priv_keys]
110
+ end
111
+
112
+ class << self
113
+ private
114
+
115
+ # NOTE:
116
+ # struct {
117
+ # KeyShareEntry client_shares<0..2^16-1>;
118
+ # } KeyShareClientHello;
119
+ #
120
+ # @param binary [String]
121
+ #
122
+ # @raise [TTTLS13::Error::ErrorAlerts]
123
+ #
124
+ # @return [Array of KeyShareEntry, nil]
125
+ def deserialize_keyshare_ch(binary)
126
+ raise Error::ErrorAlerts, :internal_error if binary.nil?
127
+
128
+ return nil if binary.length < 2
129
+
130
+ cs_len = Convert.bin2i(binary.slice(0, 2))
131
+ key_share_entry = []
132
+ itr = 2
133
+ while itr < cs_len + 2
134
+ return nil if itr + 4 > binary.length
135
+
136
+ group = binary.slice(itr, 2)
137
+ itr += 2
138
+ ke_len = Convert.bin2i(binary.slice(itr, 2))
139
+ itr += 2
140
+ key_exchange = binary.slice(itr, ke_len)
141
+ key_share_entry << KeyShareEntry.new(group: group,
142
+ key_exchange: key_exchange)
143
+ itr += ke_len
144
+ end
145
+ return nil unless itr == binary.length
146
+
147
+ key_share_entry
148
+ end
149
+
150
+ # NOTE:
151
+ # struct {
152
+ # KeyShareEntry server_share;
153
+ # } KeyShareServerHello;
154
+ #
155
+ # @param binary [String]
156
+ #
157
+ # @raise [TTTLS13::Error::ErrorAlerts]
158
+ #
159
+ # @return [Array of KeyShareEntry, nil]
160
+ def deserialize_keyshare_sh(binary)
161
+ raise Error::ErrorAlerts, :internal_error if binary.nil?
162
+
163
+ return nil if binary.length < 4
164
+
165
+ group = binary.slice(0, 2)
166
+ ke_len = Convert.bin2i(binary.slice(2, 2))
167
+ key_exchange = binary.slice(4, ke_len)
168
+ return nil unless ke_len + 4 == binary.length
169
+
170
+ [KeyShareEntry.new(group: group, key_exchange: key_exchange)]
171
+ end
172
+
173
+ # NOTE:
174
+ # struct {
175
+ # NamedGroup selected_group;
176
+ # } KeyShareHelloRetryRequest;
177
+ #
178
+ # @param binary [String]
179
+ #
180
+ # @raise [TTTLS13::Error::ErrorAlerts]
181
+ #
182
+ # @return [Array of KeyShareEntry, nil]
183
+ def deserialize_keyshare_hrr(binary)
184
+ raise Error::ErrorAlerts, :internal_error if binary.nil?
185
+
186
+ return nil unless binary.length == 2
187
+
188
+ group = binary.slice(0, 2)
189
+ [KeyShareEntry.new(group: group)]
190
+ end
191
+ end
192
+ end
193
+ # rubocop: enable Metrics/ClassLength
194
+
195
+ class KeyShareEntry
196
+ attr_reader :group
197
+ attr_reader :key_exchange
198
+
199
+ # @param group [TTTLS13::Message::Extension::NamedGroup]
200
+ # @param key_exchange [String]
201
+ #
202
+ # @raise [TTTLS13::Error::ErrorAlerts]
203
+ def initialize(group:, key_exchange: nil)
204
+ @group = group || ''
205
+ @key_exchange = key_exchange || ''
206
+ raise Error::ErrorAlerts, :internal_error unless @group.length == 2
207
+ end
208
+
209
+ # @return [Boolean]
210
+ def valid_key_share_client_hello?
211
+ @group.length == 2 && @key_exchange.length.positive?
212
+ end
213
+
214
+ # @return [Boolean]
215
+ def valid_key_share_server_hello?
216
+ @group.length == 2 && @key_exchange.length.positive?
217
+ end
218
+
219
+ # @return [Boolean]
220
+ def valid_key_share_hello_retry_request?
221
+ @group.length == 2 && @key_exchange.empty?
222
+ end
223
+
224
+ # @return [String]
225
+ def serialize
226
+ binary = ''
227
+ binary += @group
228
+ # KeyShareHelloRetryRequest doesn't have key_exchange.
229
+ binary += @key_exchange.prefix_uint16_length \
230
+ unless @key_exchange.empty?
231
+ binary
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end