tttls1.3 0.3.4 → 0.3.5

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +4 -2
  3. data/.rubocop.yml +16 -11
  4. data/.ruby-version +1 -1
  5. data/Gemfile +3 -3
  6. data/README.md +4 -4
  7. data/Rakefile +3 -3
  8. data/example/helper.rb +1 -1
  9. data/example/https_client_using_0rtt.rb +1 -1
  10. data/example/https_client_using_ech.rb +1 -1
  11. data/example/https_client_using_hrr_and_ech.rb +1 -1
  12. data/example/https_client_using_hrr_and_ticket.rb +1 -1
  13. data/example/https_client_using_status_request.rb +1 -1
  14. data/example/https_client_using_ticket.rb +1 -1
  15. data/example/https_client_using_ticket_and_ech.rb +3 -3
  16. data/example/https_server.rb +1 -1
  17. data/interop/client_spec.rb +57 -31
  18. data/interop/server_spec.rb +74 -46
  19. data/interop/spec_helper.rb +2 -2
  20. data/lib/tttls1.3/cipher_suites.rb +21 -16
  21. data/lib/tttls1.3/client.rb +86 -73
  22. data/lib/tttls1.3/connection.rb +6 -15
  23. data/lib/tttls1.3/cryptograph/aead.rb +26 -16
  24. data/lib/tttls1.3/ech.rb +11 -15
  25. data/lib/tttls1.3/endpoint.rb +4 -25
  26. data/lib/tttls1.3/key_schedule.rb +1 -1
  27. data/lib/tttls1.3/logging.rb +1 -1
  28. data/lib/tttls1.3/message/alert.rb +3 -4
  29. data/lib/tttls1.3/message/certificate.rb +4 -7
  30. data/lib/tttls1.3/message/certificate_verify.rb +3 -5
  31. data/lib/tttls1.3/message/client_hello.rb +9 -15
  32. data/lib/tttls1.3/message/compressed_certificate.rb +3 -9
  33. data/lib/tttls1.3/message/encrypted_extensions.rb +1 -2
  34. data/lib/tttls1.3/message/extension/alpn.rb +1 -6
  35. data/lib/tttls1.3/message/extension/compress_certificate.rb +1 -2
  36. data/lib/tttls1.3/message/extension/cookie.rb +1 -2
  37. data/lib/tttls1.3/message/extension/early_data_indication.rb +1 -2
  38. data/lib/tttls1.3/message/extension/ech.rb +9 -16
  39. data/lib/tttls1.3/message/extension/ech_outer_extensions.rb +1 -2
  40. data/lib/tttls1.3/message/extension/key_share.rb +17 -43
  41. data/lib/tttls1.3/message/extension/pre_shared_key.rb +8 -17
  42. data/lib/tttls1.3/message/extension/psk_key_exchange_modes.rb +1 -2
  43. data/lib/tttls1.3/message/extension/record_size_limit.rb +1 -2
  44. data/lib/tttls1.3/message/extension/server_name.rb +1 -2
  45. data/lib/tttls1.3/message/extension/signature_algorithms.rb +1 -2
  46. data/lib/tttls1.3/message/extension/status_request.rb +4 -12
  47. data/lib/tttls1.3/message/extension/supported_groups.rb +1 -4
  48. data/lib/tttls1.3/message/extension/supported_versions.rb +2 -8
  49. data/lib/tttls1.3/message/extension/unknown_extension.rb +2 -3
  50. data/lib/tttls1.3/message/extensions.rb +1 -7
  51. data/lib/tttls1.3/message/finished.rb +1 -2
  52. data/lib/tttls1.3/message/new_session_ticket.rb +6 -12
  53. data/lib/tttls1.3/message/record.rb +10 -23
  54. data/lib/tttls1.3/message/server_hello.rb +10 -21
  55. data/lib/tttls1.3/named_group.rb +12 -6
  56. data/lib/tttls1.3/server.rb +39 -34
  57. data/lib/tttls1.3/shared_secret.rb +118 -0
  58. data/lib/tttls1.3/utils.rb +0 -15
  59. data/lib/tttls1.3/version.rb +1 -1
  60. data/lib/tttls1.3.rb +1 -1
  61. data/spec/certificate_verify_spec.rb +1 -1
  62. data/spec/client_hello_spec.rb +4 -4
  63. data/spec/client_spec.rb +13 -13
  64. data/spec/endpoint_spec.rb +11 -11
  65. data/spec/key_schedule_spec.rb +4 -4
  66. data/spec/new_session_ticket_spec.rb +4 -4
  67. data/spec/pre_shared_key_spec.rb +8 -8
  68. data/spec/record_spec.rb +1 -1
  69. data/spec/server_hello_spec.rb +5 -5
  70. data/spec/server_spec.rb +8 -8
  71. data/tttls1.3.gemspec +2 -2
  72. metadata +7 -10
  73. data/example/https_client_using_grease_psk.rb +0 -58
@@ -18,158 +18,186 @@ RSpec.describe Server do
18
18
  testcases = [
19
19
  [
20
20
  true,
21
- '-groups P-256:P-384:P-521 -ciphersuites TLS_AES_256_GCM_SHA384',
21
+ '-groups X25519:P-256:P-384:P-521 -ciphersuites TLS_AES_256_GCM_SHA384',
22
22
  FIXTURES_DIR + '/rsa_rsa.crt',
23
23
  FIXTURES_DIR + '/rsa_rsa.key',
24
- cipher_suites: [CipherSuite::TLS_AES_256_GCM_SHA384]
24
+ { cipher_suites: [CipherSuite::TLS_AES_256_GCM_SHA384] }
25
25
  ],
26
26
  [
27
27
  true,
28
- '-groups P-256:P-384:P-521 -ciphersuites TLS_CHACHA20_POLY1305_SHA256',
28
+ '-groups X25519:P-256:P-384:P-521 -ciphersuites TLS_CHACHA20_POLY1305_SHA256',
29
29
  FIXTURES_DIR + '/rsa_rsa.crt',
30
30
  FIXTURES_DIR + '/rsa_rsa.key',
31
- cipher_suites: [CipherSuite::TLS_CHACHA20_POLY1305_SHA256]
31
+ { cipher_suites: [CipherSuite::TLS_CHACHA20_POLY1305_SHA256] }
32
32
  ],
33
33
  [
34
34
  true,
35
- '-groups P-256:P-384:P-521 -ciphersuites TLS_AES_128_GCM_SHA256',
35
+ '-groups X25519:P-256:P-384:P-521 -ciphersuites TLS_AES_128_GCM_SHA256',
36
36
  FIXTURES_DIR + '/rsa_rsa.crt',
37
37
  FIXTURES_DIR + '/rsa_rsa.key',
38
- cipher_suites: [CipherSuite::TLS_AES_128_GCM_SHA256]
38
+ { cipher_suites: [CipherSuite::TLS_AES_128_GCM_SHA256] }
39
+ ],
40
+ [
41
+ true,
42
+ '-groups X25519:P-256:P-384:P-521 -ciphersuites TLS_AES_128_CCM_SHA256',
43
+ FIXTURES_DIR + '/rsa_rsa.crt',
44
+ FIXTURES_DIR + '/rsa_rsa.key',
45
+ { cipher_suites: [CipherSuite::TLS_AES_128_CCM_SHA256] }
46
+ ],
47
+ [
48
+ true,
49
+ "-groups X25519:P-256:P-384:P-521 -ciphersuites TLS_AES_128_CCM_8_SHA256 -cipher 'DEFAULT:@SECLEVEL=0'",
50
+ FIXTURES_DIR + '/rsa_rsa.crt',
51
+ FIXTURES_DIR + '/rsa_rsa.key',
52
+ { cipher_suites: [CipherSuite::TLS_AES_128_CCM_8_SHA256] }
39
53
  ],
40
54
  [
41
55
  false,
42
- '-groups P-256:P-384:P-521 -ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256',
56
+ '-groups X25519:P-256:P-384:P-521 -ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256',
57
+ FIXTURES_DIR + '/rsa_rsa.crt',
58
+ FIXTURES_DIR + '/rsa_rsa.key',
59
+ { cipher_suites: [CipherSuite::TLS_AES_128_GCM_SHA256] }
60
+ ],
61
+ [
62
+ true,
63
+ '-groups X25519',
64
+ FIXTURES_DIR + '/rsa_rsa.crt',
65
+ FIXTURES_DIR + '/rsa_rsa.key',
66
+ { supported_groups: [NamedGroup::X25519] }
67
+ ],
68
+ [
69
+ true,
70
+ '-groups X448',
43
71
  FIXTURES_DIR + '/rsa_rsa.crt',
44
72
  FIXTURES_DIR + '/rsa_rsa.key',
45
- cipher_suites: [CipherSuite::TLS_AES_128_GCM_SHA256]
73
+ { supported_groups: [NamedGroup::X448] }
46
74
  ],
47
75
  [
48
76
  true,
49
77
  '-groups P-256',
50
78
  FIXTURES_DIR + '/rsa_rsa.crt',
51
79
  FIXTURES_DIR + '/rsa_rsa.key',
52
- supported_groups: [NamedGroup::SECP256R1]
80
+ { supported_groups: [NamedGroup::SECP256R1] }
53
81
  ],
54
82
  [
55
83
  true,
56
84
  '-groups P-384',
57
85
  FIXTURES_DIR + '/rsa_rsa.crt',
58
86
  FIXTURES_DIR + '/rsa_rsa.key',
59
- supported_groups: [NamedGroup::SECP384R1]
87
+ { supported_groups: [NamedGroup::SECP384R1] }
60
88
  ],
61
89
  [
62
90
  true,
63
91
  '-groups P-521',
64
92
  FIXTURES_DIR + '/rsa_rsa.crt',
65
93
  FIXTURES_DIR + '/rsa_rsa.key',
66
- supported_groups: [NamedGroup::SECP521R1]
94
+ { supported_groups: [NamedGroup::SECP521R1] }
67
95
  ],
68
96
  [
69
97
  false,
70
- '-groups P-256:P-384',
98
+ '-groups X25519:P-256:P-384',
71
99
  FIXTURES_DIR + '/rsa_rsa.crt',
72
100
  FIXTURES_DIR + '/rsa_rsa.key',
73
- supported_groups: [NamedGroup::SECP521R1]
101
+ { supported_groups: [NamedGroup::SECP521R1] }
74
102
  ],
75
103
  [
76
104
  true,
77
- '-groups P-256:P-384:P-521 -sigalgs RSA-PSS+SHA256',
105
+ '-groups X25519:P-256:P-384:P-521 -sigalgs RSA-PSS+SHA256',
78
106
  FIXTURES_DIR + '/rsa_rsa.crt',
79
107
  FIXTURES_DIR + '/rsa_rsa.key',
80
- signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
81
- signature_algorithms: [SignatureScheme::RSA_PSS_RSAE_SHA256]
108
+ { signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
109
+ signature_algorithms: [SignatureScheme::RSA_PSS_RSAE_SHA256] }
82
110
  ],
83
111
  [
84
112
  true,
85
- '-groups P-256:P-384:P-521 -sigalgs RSA-PSS+SHA384',
113
+ '-groups X25519:P-256:P-384:P-521 -sigalgs RSA-PSS+SHA384',
86
114
  FIXTURES_DIR + '/rsa_rsa.crt',
87
115
  FIXTURES_DIR + '/rsa_rsa.key',
88
- signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
89
- signature_algorithms: [SignatureScheme::RSA_PSS_RSAE_SHA384]
116
+ { signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
117
+ signature_algorithms: [SignatureScheme::RSA_PSS_RSAE_SHA384] }
90
118
  ],
91
119
  [
92
120
  true,
93
- '-groups P-256:P-384:P-521 -sigalgs RSA-PSS+SHA512',
121
+ '-groups X25519:P-256:P-384:P-521 -sigalgs RSA-PSS+SHA512',
94
122
  FIXTURES_DIR + '/rsa_rsa.crt',
95
123
  FIXTURES_DIR + '/rsa_rsa.key',
96
- signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
97
- signature_algorithms: [SignatureScheme::RSA_PSS_RSAE_SHA512]
124
+ { signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
125
+ signature_algorithms: [SignatureScheme::RSA_PSS_RSAE_SHA512] }
98
126
  ],
99
127
  [
100
128
  true,
101
- '-groups P-256:P-384:P-521 -sigalgs ECDSA+SHA256',
129
+ '-groups X25519:P-256:P-384:P-521 -sigalgs ECDSA+SHA256',
102
130
  FIXTURES_DIR + '/rsa_secp256r1.crt',
103
131
  FIXTURES_DIR + '/rsa_secp256r1.key',
104
- signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
105
- signature_algorithms: [SignatureScheme::ECDSA_SECP256R1_SHA256]
132
+ { signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
133
+ signature_algorithms: [SignatureScheme::ECDSA_SECP256R1_SHA256] }
106
134
  ],
107
135
  [
108
136
  true,
109
- '-groups P-256:P-384:P-521 -sigalgs ECDSA+SHA384',
137
+ '-groups X25519:P-256:P-384:P-521 -sigalgs ECDSA+SHA384',
110
138
  FIXTURES_DIR + '/rsa_secp384r1.crt',
111
139
  FIXTURES_DIR + '/rsa_secp384r1.key',
112
- signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
113
- signature_algorithms: [SignatureScheme::ECDSA_SECP384R1_SHA384]
140
+ { signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
141
+ signature_algorithms: [SignatureScheme::ECDSA_SECP384R1_SHA384] }
114
142
  ],
115
143
  [
116
144
  true,
117
- '-groups P-256:P-384:P-521 -sigalgs ECDSA+SHA512',
145
+ '-groups X25519:P-256:P-384:P-521 -sigalgs ECDSA+SHA512',
118
146
  FIXTURES_DIR + '/rsa_secp521r1.crt',
119
147
  FIXTURES_DIR + '/rsa_secp521r1.key',
120
- signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
121
- signature_algorithms: [SignatureScheme::ECDSA_SECP521R1_SHA512]
148
+ { signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
149
+ signature_algorithms: [SignatureScheme::ECDSA_SECP521R1_SHA512] }
122
150
  ],
123
151
  [
124
152
  true,
125
- '-groups P-256:P-384:P-521 -sigalgs RSA-PSS+SHA256',
153
+ '-groups X25519:P-256:P-384:P-521 -sigalgs RSA-PSS+SHA256',
126
154
  FIXTURES_DIR + '/rsa_rsassaPss.crt',
127
155
  FIXTURES_DIR + '/rsa_rsassaPss.key',
128
- signature_algorithms_cert: [SignatureScheme::RSA_PSS_RSAE_SHA256],
129
- signature_algorithms: [SignatureScheme::RSA_PSS_RSAE_SHA256]
156
+ { signature_algorithms_cert: [SignatureScheme::RSA_PSS_RSAE_SHA256],
157
+ signature_algorithms: [SignatureScheme::RSA_PSS_RSAE_SHA256] }
130
158
  ],
131
159
  [
132
160
  false,
133
- '-groups P-256:P-384:P-521 -sigalgs ECDSA+SHA256:ECDSA+SHA384:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA+SHA256',
161
+ '-groups X25519:P-256:P-384:P-521 -sigalgs ECDSA+SHA256:ECDSA+SHA384:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA+SHA256',
134
162
  FIXTURES_DIR + '/rsa_rsa.crt',
135
163
  FIXTURES_DIR + '/rsa_rsa.key',
136
- signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
137
- signature_algorithms: [SignatureScheme::ECDSA_SECP521R1_SHA512]
164
+ { signature_algorithms_cert: [SignatureScheme::RSA_PKCS1_SHA256],
165
+ signature_algorithms: [SignatureScheme::ECDSA_SECP521R1_SHA512] }
138
166
  ],
139
167
  [
140
168
  true,
141
- '-groups P-256:P-384:P-521 -record_padding 8446',
169
+ '-groups X25519:P-256:P-384:P-521 -record_padding 8446',
142
170
  FIXTURES_DIR + '/rsa_rsa.crt',
143
171
  FIXTURES_DIR + '/rsa_rsa.key',
144
172
  {}
145
173
  ],
146
174
  [
147
175
  true,
148
- '-groups P-256:P-384:P-521 -alpn pingpong',
176
+ '-groups X25519:P-256:P-384:P-521 -alpn pingpong',
149
177
  FIXTURES_DIR + '/rsa_rsa.crt',
150
178
  FIXTURES_DIR + '/rsa_rsa.key',
151
- alpn: ['pingpong']
179
+ { alpn: ['pingpong'] }
152
180
  ],
153
181
  [
154
182
  true,
155
- '-groups P-256:P-384:P-521',
183
+ '-groups X25519:P-256:P-384:P-521',
156
184
  FIXTURES_DIR + '/rsa_rsa.crt',
157
185
  FIXTURES_DIR + '/rsa_rsa.key',
158
- alpn: ['pingpong']
186
+ { alpn: ['pingpong'] }
159
187
  ],
160
188
  [
161
189
  false,
162
- '-groups P-256:P-384:P-521 -alpn foo',
190
+ '-groups X25519:P-256:P-384:P-521 -alpn foo',
163
191
  FIXTURES_DIR + '/rsa_rsa.crt',
164
192
  FIXTURES_DIR + '/rsa_rsa.key',
165
- alpn: ['pingpong']
193
+ { alpn: ['pingpong'] }
166
194
  ],
167
195
  [
168
196
  true,
169
- '-groups P-256:P-384:P-521',
197
+ '-groups X25519:P-256:P-384:P-521',
170
198
  FIXTURES_DIR + '/rsa_rsa.crt',
171
199
  FIXTURES_DIR + '/rsa_rsa.key',
172
- compatibility_mode: false
200
+ { compatibility_mode: false }
173
201
  ]
174
202
  ]
175
203
  # rubocop: enable Layout/LineLength
@@ -12,7 +12,7 @@ include TTTLS13::Message::Extension
12
12
  include TTTLS13::Error
13
13
  # rubocop: enable Style/MixinUsage
14
14
 
15
- def wait_to_listen(host, port)
15
+ def wait_to_listen(host, port, verbose)
16
16
  10.times do
17
17
  soc = TCPSocket.open(host, port)
18
18
  ctx = OpenSSL::SSL::SSLContext.new
@@ -21,7 +21,7 @@ def wait_to_listen(host, port)
21
21
  ssl.sync_close = true
22
22
  ssl.connect
23
23
  rescue => e # rubocop: disable Style/RescueStandardError
24
- p e
24
+ p e if verbose
25
25
  soc&.close
26
26
  sleep(0.5)
27
27
  next
@@ -7,14 +7,14 @@ module TTTLS13
7
7
  TLS_AES_128_GCM_SHA256 = "\x13\x01"
8
8
  TLS_AES_256_GCM_SHA384 = "\x13\x02"
9
9
  TLS_CHACHA20_POLY1305_SHA256 = "\x13\x03"
10
- # TLS_AES_128_CCM_SHA256 = "\x13\x04" # UNSUPPORTED
11
- # TLS_AES_128_CCM_8_SHA256 = "\x13\x05" # UNSUPPORTED
10
+ TLS_AES_128_CCM_SHA256 = "\x13\x04"
11
+ TLS_AES_128_CCM_8_SHA256 = "\x13\x05"
12
12
 
13
13
  class << self
14
14
  def digest(cipher_suite)
15
15
  case cipher_suite
16
- when TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256
17
- # , TLS_AES_128_CCM_SHA256, TLS_AES_128_CCM_8_SHA256
16
+ when TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256,
17
+ TLS_AES_128_CCM_SHA256, TLS_AES_128_CCM_8_SHA256
18
18
  'SHA256'
19
19
  when TLS_AES_256_GCM_SHA384
20
20
  'SHA384'
@@ -25,8 +25,8 @@ module TTTLS13
25
25
 
26
26
  def hash_len(cipher_suite)
27
27
  case cipher_suite
28
- when TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256
29
- # , TLS_AES_128_CCM_SHA256, TLS_AES_128_CCM_8_SHA256
28
+ when TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256,
29
+ TLS_AES_128_CCM_SHA256, TLS_AES_128_CCM_8_SHA256
30
30
  32
31
31
  when TLS_AES_256_GCM_SHA384
32
32
  48
@@ -37,8 +37,8 @@ module TTTLS13
37
37
 
38
38
  def key_len(cipher_suite)
39
39
  case cipher_suite
40
- when TLS_AES_128_GCM_SHA256
41
- # , TLS_AES_128_CCM_SHA256, TLS_AES_128_CCM_8_SHA256
40
+ when TLS_AES_128_GCM_SHA256, TLS_AES_128_CCM_SHA256,
41
+ TLS_AES_128_CCM_8_SHA256
42
42
  16
43
43
  when TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256
44
44
  32
@@ -50,8 +50,8 @@ module TTTLS13
50
50
  def iv_len(cipher_suite)
51
51
  case cipher_suite
52
52
  when TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
53
- TLS_CHACHA20_POLY1305_SHA256
54
- # , TLS_AES_128_CCM_SHA256, TLS_AES_128_CCM_8_SHA256
53
+ TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_CCM_SHA256,
54
+ TLS_AES_128_CCM_8_SHA256
55
55
  12
56
56
  else
57
57
  raise Error::ErrorAlerts, :internal_error
@@ -61,14 +61,22 @@ module TTTLS13
61
61
  def auth_tag_len(cipher_suite)
62
62
  case cipher_suite
63
63
  when TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
64
- TLS_CHACHA20_POLY1305_SHA256 # , TLS_AES_128_CCM_SHA256
64
+ TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_CCM_SHA256
65
65
  16
66
- # when TLS_AES_128_CCM_8_SHA256
67
- # 8
66
+ when TLS_AES_128_CCM_8_SHA256
67
+ # https://datatracker.ietf.org/doc/html/rfc6655#section-6.2
68
+ 8
68
69
  else
69
70
  raise Error::ErrorAlerts, :internal_error
70
71
  end
71
72
  end
73
+
74
+ def ccm?(cipher_suite)
75
+ [
76
+ TLS_AES_128_CCM_SHA256,
77
+ TLS_AES_128_CCM_8_SHA256
78
+ ].include?(cipher_suite)
79
+ end
72
80
  end
73
81
  end
74
82
 
@@ -81,9 +89,6 @@ module TTTLS13
81
89
  # CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
82
90
  # CipherSuite::TLS_AES_128_GCM_SHA256
83
91
  # ])
84
- def initialize(cipher_suites)
85
- super(cipher_suites)
86
- end
87
92
 
88
93
  # @return [String]
89
94
  def serialize