1010require 'json'
1111require 'securerandom'
1212
13- module EncryptorTests
14- def self . included ( _base )
15- describe 'encryptor' do
16- it 'initialize does not destroy key string' do
17- encryptor_class . new ( @secret )
13+ def all_versions_tests ( opts = { } )
14+ Module . new do
15+ define_method ( :default_opts ) do
16+ opts
17+ end
1818
19- @secret . size . must_equal 64
20- end
19+ def self . included ( _base )
20+ describe 'encryptor' do
21+ it 'initialize does not destroy key string' do
22+ new_encryptor ( @secret )
2123
22- it 'initialize raises ArgumentError on invalid key' do
23- -> { encryptor_class . new [ 'foo' ] } . must_raise ArgumentError
24- end
24+ @secret . size . must_equal 64
25+ end
2526
26- it 'initialize raises ArgumentError on short key' do
27- -> { encryptor_class . new 'key' } . must_raise ArgumentError
28- end
27+ it 'initialize raises ArgumentError on invalid key' do
28+ -> { new_encryptor [ 'foo' ] } . must_raise ArgumentError
29+ end
2930
30- it 'decrypts an encrypted message' do
31- encryptor = encryptor_class . new ( @secret )
31+ it 'initialize raises ArgumentError on short key' do
32+ -> { new_encryptor 'key' } . must_raise ArgumentError
33+ end
3234
33- message = encryptor . encrypt ( { 'foo' => 'bar' } )
35+ it 'decrypts an encrypted message' do
36+ encryptor = new_encryptor ( @secret )
3437
35- encryptor . decrypt ( message ) . must_equal ( { 'foo' => 'bar' } )
36- end
38+ message = encryptor . encrypt ( { 'foo' => 'bar' } )
3739
38- it 'decrypt raises InvalidSignature for tampered messages' do
39- encryptor = encryptor_class . new ( @secret )
40+ encryptor . decrypt ( message ) . must_equal ( { 'foo' => 'bar' } )
41+ end
4042
41- message = encryptor . encrypt ( { 'foo' => 'bar' } )
43+ it 'decrypt raises InvalidSignature for tampered messages' do
44+ encryptor = new_encryptor ( @secret )
4245
43- decoded_message = Base64 . urlsafe_decode64 ( message )
44- tampered_message = Base64 . urlsafe_encode64 ( decoded_message . tap do |m |
45- m [ m . size - 1 ] = ( m [ m . size - 1 ] . unpack1 ( 'C' ) ^ 1 ) . chr
46- end )
46+ message = encryptor . encrypt ( { 'foo' => 'bar' } )
4747
48- lambda {
49- encryptor . decrypt ( tampered_message )
50- } . must_raise Rack :: Session :: Encryptor :: InvalidSignature
51- end
48+ decoded_message = Base64 . urlsafe_decode64 ( message )
49+ tampered_message = Base64 . urlsafe_encode64 ( decoded_message . tap do | m |
50+ m [ m . size - 1 ] = ( m [ m . size - 1 ] . unpack1 ( 'C' ) ^ 1 ) . chr
51+ end )
5252
53- it 'decrypts an encrypted message with purpose' do
54- encryptor = encryptor_class . new ( @secret , purpose : 'testing' )
53+ lambda {
54+ encryptor . decrypt ( tampered_message )
55+ } . must_raise Rack ::Session ::Encryptor ::InvalidSignature
56+ end
5557
56- message = encryptor . encrypt ( { 'foo' => 'bar' } )
58+ it 'decrypts an encrypted message with purpose' do
59+ encryptor = new_encryptor ( @secret , purpose : 'testing' )
5760
58- encryptor . decrypt ( message ) . must_equal ( { 'foo' => 'bar' } )
59- end
61+ message = encryptor . encrypt ( { 'foo' => 'bar' } )
6062
61- # The V1 encryptor defaults to the Marshal serializer, while the V2
62- # encryptor always uses the JSON serializer. This means that we are
63- # indirectly covering both serializers.
64- it 'decrypts an encrypted message with UTF-8 data' do
65- encryptor = encryptor_class . new ( @secret )
63+ encryptor . decrypt ( message ) . must_equal ( { 'foo' => 'bar' } )
64+ end
6665
67- encrypted_message = encryptor . encrypt ( { 'foo' => 'bar 😀' } )
68- decrypted_message = encryptor . decrypt ( encrypted_message )
66+ it 'decrypts an encrypted message with UTF-8 data' do
67+ encryptor = new_encryptor ( @secret )
6968
70- decrypted_message . must_equal ( { 'foo' => 'bar 😀' } )
71- end
69+ encrypted_message = encryptor . encrypt ( { 'foo' => 'bar 😀' } )
70+ decrypted_message = encryptor . decrypt ( encrypted_message )
7271
73- it 'decrypts raises InvalidSignature without purpose' do
74- encryptor = encryptor_class . new ( @secret , purpose : 'testing' )
75- other_encryptor = encryptor_class . new ( @secret )
72+ decrypted_message . must_equal ( { 'foo' => 'bar 😀' } )
73+ end
7674
77- message = other_encryptor . encrypt ( { 'foo' => 'bar' } )
75+ it 'decrypts raises InvalidSignature without purpose' do
76+ encryptor = new_encryptor ( @secret , purpose : 'testing' )
77+ other_encryptor = new_encryptor ( @secret )
7878
79- -> { encryptor . decrypt ( message ) } . must_raise Rack ::Session ::Encryptor ::InvalidSignature
80- end
79+ message = other_encryptor . encrypt ( { 'foo' => 'bar' } )
8180
82- it 'decrypts raises InvalidSignature with different purpose' do
83- encryptor = encryptor_class . new ( @secret , purpose : 'testing' )
84- other_encryptor = encryptor_class . new ( @secret , purpose : 'other' )
81+ -> { encryptor . decrypt ( message ) } . must_raise Rack ::Session ::Encryptor ::InvalidSignature
82+ end
8583
86- message = other_encryptor . encrypt ( { 'foo' => 'bar' } )
84+ it 'decrypts raises InvalidSignature with different purpose' do
85+ encryptor = new_encryptor ( @secret , purpose : 'testing' )
86+ other_encryptor = new_encryptor ( @secret , purpose : 'other' )
8787
88- -> { encryptor . decrypt ( message ) } . must_raise Rack ::Session ::Encryptor ::InvalidSignature
89- end
88+ message = other_encryptor . encrypt ( { 'foo' => 'bar' } )
9089
91- it 'initialize raises ArgumentError on invalid pad_size' do
92- -> { encryptor_class . new @secret , pad_size : :bar } . must_raise ArgumentError
93- end
90+ -> { encryptor . decrypt ( message ) } . must_raise Rack ::Session ::Encryptor ::InvalidSignature
91+ end
9492
95- it 'initialize raises ArgumentError on to short pad_size' do
96- -> { encryptor_class . new @secret , pad_size : 1 } . must_raise ArgumentError
97- end
93+ it 'initialize raises ArgumentError on invalid pad_size' do
94+ -> { new_encryptor @secret , pad_size : :bar } . must_raise ArgumentError
95+ end
9896
99- it 'initialize raises ArgumentError on to long pad_size' do
100- -> { encryptor_class . new @secret , pad_size : 8023 } . must_raise ArgumentError
101- end
97+ it 'initialize raises ArgumentError on to short pad_size' do
98+ -> { new_encryptor @secret , pad_size : 1 } . must_raise ArgumentError
99+ end
100+
101+ it 'initialize raises ArgumentError on to long pad_size' do
102+ -> { new_encryptor @secret , pad_size : 8023 } . must_raise ArgumentError
103+ end
104+
105+ it 'decrypts an encrypted message without pad_size' do
106+ encryptor = new_encryptor ( @secret , purpose : 'testing' , pad_size : nil )
102107
103- it 'decrypts an encrypted message without pad_size' do
104- encryptor = encryptor_class . new ( @secret , purpose : 'testing' , pad_size : nil )
108+ message = encryptor . encrypt ( { 'foo' => 'bar' } )
105109
106- message = encryptor . encrypt ( { 'foo' => 'bar' } )
110+ encryptor . decrypt ( message ) . must_equal ( { 'foo' => 'bar' } )
111+ end
107112
108- encryptor . decrypt ( message ) . must_equal ( { 'foo' => 'bar' } )
113+ it 'encryptor with pad_size increases message size' do
114+ no_pad_encryptor = new_encryptor ( @secret , purpose : 'testing' , pad_size : nil )
115+ pad_encryptor = new_encryptor ( @secret , purpose : 'testing' , pad_size : 64 )
116+
117+ message_without = Base64 . urlsafe_decode64 ( no_pad_encryptor . encrypt ( '' ) )
118+ message_with = Base64 . urlsafe_decode64 ( pad_encryptor . encrypt ( '' ) )
119+ message_size_diff = message_with . bytesize - message_without . bytesize
120+
121+ message_with . bytesize . must_be :> , message_without . bytesize
122+ serializer = default_opts [ :serialize_json ] ? JSON : Marshal
123+ message_size_diff . must_equal 64 - serializer . dump ( '' ) . bytesize - 2
124+ end
109125 end
110126 end
111127 end
@@ -116,15 +132,24 @@ def setup
116132 @secret = SecureRandom . random_bytes ( 64 )
117133 end
118134
135+ def new_encryptor ( secret , opts = { } )
136+ if respond_to? ( :default_opts )
137+ encryptor_class . new ( secret , default_opts . merge ( opts ) )
138+ else
139+ encryptor_class . new ( secret , opts )
140+ end
141+ end
142+
119143 describe 'V1' do
120144 def encryptor_class
121145 Rack ::Session ::Encryptor ::V1
122146 end
123147
124- include EncryptorTests
148+ include all_versions_tests ( serialize_json : false )
149+ include all_versions_tests ( serialize_json : true )
125150
126151 it 'encryptor with pad_size has message payload size to multiple of pad_size' do
127- encryptor = encryptor_class . new ( @secret , purpose : 'testing' , pad_size : 24 )
152+ encryptor = new_encryptor ( @secret , purpose : 'testing' , pad_size : 24 )
128153 message = encryptor . encrypt ( { 'foo' => 'bar' * 4 } )
129154
130155 decoded_message = Base64 . urlsafe_decode64 ( message )
@@ -136,24 +161,12 @@ def encryptor_class
136161 ( encrypted_payload . bytesize % 24 ) . must_equal 0
137162 end
138163
139- it 'encryptor with pad_size increases message size' do
140- no_pad_encryptor = encryptor_class . new ( @secret , purpose : 'testing' , pad_size : nil )
141- pad_encryptor = encryptor_class . new ( @secret , purpose : 'testing' , pad_size : 64 )
142-
143- message_without = Base64 . urlsafe_decode64 ( no_pad_encryptor . encrypt ( '' ) )
144- message_with = Base64 . urlsafe_decode64 ( pad_encryptor . encrypt ( '' ) )
145- message_size_diff = message_with . bytesize - message_without . bytesize
146-
147- message_with . bytesize . must_be :> , message_without . bytesize
148- message_size_diff . must_equal 64 - Marshal . dump ( '' ) . bytesize - 2
149- end
150-
151164 # This test checks the one-time message key IS NOT used as the cipher key.
152165 # Doing so would remove the confidentiality assurances as the key is
153166 # essentially included in plaintext then.
154167 it 'uses a secret cipher key for encryption and decryption' do
155168 cipher = OpenSSL ::Cipher . new ( 'aes-256-ctr' )
156- encryptor = encryptor_class . new ( @secret )
169+ encryptor = new_encryptor ( @secret )
157170
158171 message = encryptor . encrypt ( { 'foo' => 'bar' } )
159172 raw_message = Base64 . urlsafe_decode64 ( message )
@@ -178,7 +191,7 @@ def encryptor_class
178191 end
179192
180193 it 'it calls set_cipher_key with the correct key' do
181- encryptor = encryptor_class . new ( @secret , purpose : 'testing' , pad_size : 24 )
194+ encryptor = new_encryptor ( @secret , purpose : 'testing' , pad_size : 24 )
182195 message = encryptor . encrypt ( { 'foo' => 'bar' } )
183196
184197 message_key = Base64 . urlsafe_decode64 ( message ) . slice ( 1 , 32 )
@@ -194,17 +207,36 @@ def encryptor_class
194207 encryptor . decrypt message
195208 end
196209 end
210+
211+ it 'preserves symbols when payloads are not encoded into JSON' do
212+ encryptor = new_encryptor ( @secret , purpose : 'testing' , serialize_json : false )
213+
214+ encrypted_message = encryptor . encrypt ( { foo : 'bar' } )
215+ decrypted_message = encryptor . decrypt ( encrypted_message )
216+
217+ decrypted_message . must_equal ( { foo : 'bar' } )
218+ end
219+
220+ it 'does not preserves symbols when payloads are encoded into JSON' do
221+ encryptor = new_encryptor ( @secret , purpose : 'testing' , serialize_json : true )
222+
223+ encrypted_message = encryptor . encrypt ( { foo : 'bar' } )
224+ decrypted_message = encryptor . decrypt ( encrypted_message )
225+
226+ decrypted_message . must_equal ( { 'foo' => 'bar' } )
227+ end
197228 end
198229
199230 describe 'V2' do
200231 def encryptor_class
201232 Rack ::Session ::Encryptor ::V2
202233 end
203234
204- include EncryptorTests
235+ include all_versions_tests ( serialize_json : false )
236+ include all_versions_tests ( serialize_json : true )
205237
206238 it 'encryptor with pad_size has message payload size to multiple of pad_size' do
207- encryptor = encryptor_class . new ( @secret , purpose : 'testing' , pad_size : 24 )
239+ encryptor = new_encryptor ( @secret , purpose : 'testing' , pad_size : 24 )
208240 message = encryptor . encrypt ( { 'foo' => 'bar' * 4 } )
209241
210242 decoded_message = Base64 . strict_decode64 ( message )
@@ -216,20 +248,8 @@ def encryptor_class
216248 ( encrypted_payload . bytesize % 24 ) . must_equal 0
217249 end
218250
219- it 'encryptor with pad_size increases message size' do
220- no_pad_encryptor = encryptor_class . new ( @secret , purpose : 'testing' , pad_size : nil )
221- pad_encryptor = encryptor_class . new ( @secret , purpose : 'testing' , pad_size : 64 )
222-
223- message_without = Base64 . strict_decode64 ( no_pad_encryptor . encrypt ( '' ) )
224- message_with = Base64 . strict_decode64 ( pad_encryptor . encrypt ( '' ) )
225- message_size_diff = message_with . bytesize - message_without . bytesize
226-
227- message_with . bytesize . must_be :> , message_without . bytesize
228- message_size_diff . must_equal 64 - JSON . dump ( '' ) . bytesize - 2
229- end
230-
231251 it 'raises InvalidMessage on version mismatch' do
232- encryptor = encryptor_class . new ( @secret , purpose : 'testing' )
252+ encryptor = new_encryptor ( @secret , purpose : 'testing' )
233253 message = encryptor . encrypt ( { 'foo' => 'bar' } )
234254
235255 decoded_message = Base64 . strict_decode64 ( message )
@@ -244,7 +264,7 @@ def encryptor_class
244264 # essentially included in plaintext then.
245265 it 'uses a secret cipher key for encryption and decryption' do
246266 cipher = OpenSSL ::Cipher . new ( 'aes-256-gcm' )
247- encryptor = encryptor_class . new ( @secret )
267+ encryptor = new_encryptor ( @secret )
248268
249269 message = encryptor . encrypt ( { 'foo' => 'bar' } )
250270 raw_message = Base64 . strict_decode64 ( message )
@@ -264,7 +284,7 @@ def encryptor_class
264284 end
265285
266286 it 'it calls set_cipher_key with the correct key' do
267- encryptor = encryptor_class . new ( @secret , purpose : 'testing' , pad_size : 24 )
287+ encryptor = new_encryptor ( @secret , purpose : 'testing' , pad_size : 24 )
268288 message = encryptor . encrypt ( { 'foo' => 'bar' } )
269289
270290 message_key = Base64 . strict_decode64 ( message ) . slice ( 1 , 32 )
@@ -281,13 +301,22 @@ def encryptor_class
281301 end
282302 end
283303
284- it 'ignores serialize_json' do
285- encryptor_no_json = encryptor_class . new ( @secret , purpose : 'testing' , serialize_json : false )
286- encryptor = encryptor_class . new ( @secret , purpose : 'testing' , serialize_json : true )
304+ it 'preserves symbols when payloads are not encoded into JSON' do
305+ encryptor = new_encryptor ( @secret , purpose : 'testing' , serialize_json : false )
287306
288- message = encryptor_no_json . encrypt ( { 'foo' => 'bar' } )
307+ encrypted_message = encryptor . encrypt ( { foo : 'bar' } )
308+ decrypted_message = encryptor . decrypt ( encrypted_message )
289309
290- encryptor . decrypt ( message ) . must_equal ( { 'foo' => 'bar' } )
310+ decrypted_message . must_equal ( { foo : 'bar' } )
311+ end
312+
313+ it 'does not preserves symbols when payloads are encoded into JSON' do
314+ encryptor = new_encryptor ( @secret , purpose : 'testing' , serialize_json : true )
315+
316+ encrypted_message = encryptor . encrypt ( { foo : 'bar' } )
317+ decrypted_message = encryptor . decrypt ( encrypted_message )
318+
319+ decrypted_message . must_equal ( { 'foo' => 'bar' } )
291320 end
292321 end
293322
0 commit comments