symmetric-encryption 3.9.1 → 4.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +72 -0
- data/bin/symmetric-encryption +5 -0
- data/lib/symmetric_encryption/cipher.rb +162 -419
- data/lib/symmetric_encryption/cli.rb +343 -0
- data/lib/symmetric_encryption/coerce.rb +5 -20
- data/lib/symmetric_encryption/config.rb +128 -50
- data/lib/symmetric_encryption/extensions/mongo_mapper/plugins/encrypted_key.rb +2 -2
- data/lib/symmetric_encryption/generator.rb +3 -2
- data/lib/symmetric_encryption/header.rb +260 -0
- data/lib/symmetric_encryption/key.rb +106 -0
- data/lib/symmetric_encryption/keystore/environment.rb +90 -0
- data/lib/symmetric_encryption/keystore/file.rb +102 -0
- data/lib/symmetric_encryption/keystore/memory.rb +53 -0
- data/lib/symmetric_encryption/keystore.rb +124 -0
- data/lib/symmetric_encryption/railtie.rb +5 -7
- data/lib/symmetric_encryption/reader.rb +74 -55
- data/lib/symmetric_encryption/rsa_key.rb +24 -0
- data/lib/symmetric_encryption/symmetric_encryption.rb +64 -102
- data/lib/symmetric_encryption/utils/re_encrypt_files.rb +140 -0
- data/lib/symmetric_encryption/version.rb +1 -1
- data/lib/symmetric_encryption/writer.rb +104 -117
- data/lib/symmetric_encryption.rb +9 -4
- data/test/active_record_test.rb +61 -40
- data/test/cipher_test.rb +179 -236
- data/test/config/symmetric-encryption.yml +140 -82
- data/test/header_test.rb +218 -0
- data/test/key_test.rb +231 -0
- data/test/keystore/environment_test.rb +119 -0
- data/test/keystore/file_test.rb +125 -0
- data/test/keystore_test.rb +59 -0
- data/test/mongoid_test.rb +13 -13
- data/test/reader_test.rb +52 -53
- data/test/symmetric_encryption_test.rb +50 -135
- data/test/test_db.sqlite3 +0 -0
- data/test/writer_test.rb +52 -31
- metadata +26 -14
- data/examples/symmetric-encryption.yml +0 -108
- data/lib/rails/generators/symmetric_encryption/config/config_generator.rb +0 -22
- data/lib/rails/generators/symmetric_encryption/config/templates/symmetric-encryption.yml +0 -50
- data/lib/rails/generators/symmetric_encryption/heroku_config/heroku_config_generator.rb +0 -20
- data/lib/rails/generators/symmetric_encryption/heroku_config/templates/symmetric-encryption.yml +0 -78
- data/lib/rails/generators/symmetric_encryption/new_keys/new_keys_generator.rb +0 -14
- data/lib/symmetric_encryption/key_encryption_key.rb +0 -32
- data/lib/symmetric_encryption/railties/symmetric_encryption.rake +0 -84
- data/lib/symmetric_encryption/utils/re_encrypt_config_files.rb +0 -82
data/test/cipher_test.rb
CHANGED
@@ -1,267 +1,210 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# Tests for SymmetricEncryption::Cipher
|
5
4
|
class CipherTest < Minitest::Test
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'not require an iv' do
|
19
|
-
cipher = SymmetricEncryption::Cipher.new(
|
20
|
-
key: '1234567890ABCDEF1234567890ABCDEF',
|
21
|
-
encoding: :none
|
22
|
-
)
|
23
|
-
result = "\302<\351\227oj\372\3331\310\260V\001\v'\346"
|
24
|
-
# Note: This test fails on JRuby 1.7 RC1 since it's OpenSSL
|
25
|
-
# behaves differently when no IV is supplied.
|
26
|
-
# It instead encrypts to the following value:
|
27
|
-
# result = "0h\x92\x88\xA1\xFE\x8D\xF5\xF3v\x82\xAF(P\x83Y"
|
28
|
-
result.force_encoding('binary') if defined?(Encoding)
|
29
|
-
assert_equal result, cipher.encrypt('Hello World')
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'throw an exception on bad data' do
|
33
|
-
cipher = SymmetricEncryption::Cipher.new(
|
34
|
-
cipher_name: 'aes-128-cbc',
|
35
|
-
key: '1234567890ABCDEF',
|
36
|
-
iv: '1234567890ABCDEF',
|
37
|
-
encoding: :none
|
38
|
-
)
|
39
|
-
assert_raises OpenSSL::Cipher::CipherError do
|
40
|
-
cipher.decrypt('bad data')
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
[false, true].each do |always_add_header|
|
47
|
-
[:none, :base64, :base64strict, :base16].each do |encoding|
|
48
|
-
describe "encoding: #{encoding} with#{'out' unless always_add_header} header" do
|
49
|
-
before do
|
50
|
-
@social_security_number = '987654321'
|
51
|
-
@social_security_number_encrypted =
|
52
|
-
case encoding
|
53
|
-
when :base64
|
54
|
-
always_add_header ? "QEVuQwAAyTeLjsHTa8ykoO95K0KQmg==\n" : "yTeLjsHTa8ykoO95K0KQmg==\n"
|
55
|
-
when :base64strict
|
56
|
-
always_add_header ? 'QEVuQwAAyTeLjsHTa8ykoO95K0KQmg==' : 'yTeLjsHTa8ykoO95K0KQmg=='
|
57
|
-
when :base16
|
58
|
-
always_add_header ? '40456e430000c9378b8ec1d36bcca4a0ef792b42909a' : 'c9378b8ec1d36bcca4a0ef792b42909a'
|
59
|
-
when :none
|
60
|
-
bin = always_add_header ? "@EnC\x00\x00\xC97\x8B\x8E\xC1\xD3k\xCC\xA4\xA0\xEFy+B\x90\x9A" : "\xC97\x8B\x8E\xC1\xD3k\xCC\xA4\xA0\xEFy+B\x90\x9A"
|
61
|
-
bin.force_encoding(Encoding.find('binary'))
|
62
|
-
else
|
63
|
-
raise "Add test for encoding: #{encoding}"
|
64
|
-
end
|
65
|
-
@social_security_number_encrypted_with_secondary_1 = "D1UCu38pqJ3jc0GvwJHiow==\n"
|
66
|
-
@non_utf8 = "\xc2".force_encoding('binary')
|
67
|
-
@cipher = SymmetricEncryption::Cipher.new(
|
68
|
-
key: 'ABCDEF1234567890',
|
69
|
-
iv: 'ABCDEF1234567890',
|
70
|
-
cipher_name: 'aes-128-cbc',
|
71
|
-
encoding: encoding,
|
72
|
-
always_add_header: always_add_header
|
5
|
+
['aes-128-cbc'].each do |cipher_name|
|
6
|
+
#['aes-128-cbc', 'aes-128-gcm'].each do |cipher_name|
|
7
|
+
describe "Cipher: #{cipher_name}" do
|
8
|
+
describe 'standalone' do
|
9
|
+
it 'allows setting the cipher_name' do
|
10
|
+
cipher = SymmetricEncryption::Cipher.new(
|
11
|
+
cipher_name: cipher_name,
|
12
|
+
key: '1234567890ABCDEF',
|
13
|
+
iv: '1234567890ABCDEF',
|
14
|
+
encoding: :none
|
73
15
|
)
|
16
|
+
assert_equal cipher_name, cipher.cipher_name
|
74
17
|
end
|
75
18
|
|
76
|
-
it '
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
it 'return BINARY encoding for non-UTF-8 encrypted data' do
|
87
|
-
assert_equal Encoding.find('binary'), @non_utf8.encoding
|
88
|
-
assert_equal true, @non_utf8.valid_encoding?
|
89
|
-
assert encrypted = @cipher.encrypt(@non_utf8)
|
90
|
-
assert decrypted = @cipher.decrypt(encrypted)
|
91
|
-
assert_equal true, decrypted.valid_encoding?
|
92
|
-
assert_equal Encoding.find('binary'), decrypted.encoding, decrypted
|
93
|
-
assert_equal @non_utf8, decrypted
|
94
|
-
end
|
95
|
-
|
96
|
-
it 'return nil when encrypting nil' do
|
97
|
-
assert_nil @cipher.encrypt(nil)
|
98
|
-
end
|
99
|
-
|
100
|
-
it "return '' when encrypting ''" do
|
101
|
-
assert_equal '', @cipher.encrypt('')
|
102
|
-
end
|
103
|
-
|
104
|
-
it 'return nil when decrypting nil' do
|
105
|
-
assert_nil @cipher.decrypt(nil)
|
19
|
+
it 'does not require an iv' do
|
20
|
+
cipher = SymmetricEncryption::Cipher.new(
|
21
|
+
key: '1234567890ABCDEF',
|
22
|
+
cipher_name: cipher_name,
|
23
|
+
encoding: :none,
|
24
|
+
always_add_header: false
|
25
|
+
)
|
26
|
+
assert result = cipher.encrypt('Hello World')
|
27
|
+
assert_equal 'Hello World', cipher.decrypt(result)
|
106
28
|
end
|
107
29
|
|
108
|
-
it
|
109
|
-
|
30
|
+
it 'throw an exception on bad data' do
|
31
|
+
cipher = SymmetricEncryption::Cipher.new(
|
32
|
+
cipher_name: cipher_name,
|
33
|
+
key: '1234567890ABCDEF',
|
34
|
+
iv: '1234567890ABCDEF',
|
35
|
+
encoding: :none
|
36
|
+
)
|
37
|
+
assert_raises OpenSSL::Cipher::CipherError do
|
38
|
+
cipher.decrypt('bad data')
|
39
|
+
end
|
110
40
|
end
|
111
41
|
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
describe 'with configuration' do
|
116
|
-
before do
|
117
|
-
@cipher = SymmetricEncryption::Cipher.new(
|
118
|
-
key: '1234567890ABCDEF1234567890ABCDEF',
|
119
|
-
iv: '1234567890ABCDEF',
|
120
|
-
encoding: :none
|
121
|
-
)
|
122
|
-
@social_security_number = '987654321'
|
123
42
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
43
|
+
[false, true].each do |always_add_header|
|
44
|
+
[:none, :base64, :base64strict, :base16].each do |encoding|
|
45
|
+
describe "encoding: #{encoding} with#{'out' unless always_add_header} header" do
|
46
|
+
before do
|
47
|
+
@social_security_number = '987654321'
|
48
|
+
@encrypted_values = {
|
49
|
+
'aes-128-cbc' => {
|
50
|
+
base64: {
|
51
|
+
header: "QEVuQwAAyTeLjsHTa8ykoO95K0KQmg==\n",
|
52
|
+
no_header: "yTeLjsHTa8ykoO95K0KQmg==\n"
|
53
|
+
},
|
54
|
+
base64strict: {
|
55
|
+
header: 'QEVuQwAAyTeLjsHTa8ykoO95K0KQmg==',
|
56
|
+
no_header: 'yTeLjsHTa8ykoO95K0KQmg=='
|
57
|
+
},
|
58
|
+
base16: {
|
59
|
+
header: '40456e430000c9378b8ec1d36bcca4a0ef792b42909a',
|
60
|
+
no_header: 'c9378b8ec1d36bcca4a0ef792b42909a'
|
61
|
+
},
|
62
|
+
none: {
|
63
|
+
header: "@EnC\x00\x00\xC97\x8B\x8E\xC1\xD3k\xCC\xA4\xA0\xEFy+B\x90\x9A",
|
64
|
+
no_header: "\xC97\x8B\x8E\xC1\xD3k\xCC\xA4\xA0\xEFy+B\x90\x9A"
|
65
|
+
},
|
66
|
+
},
|
67
|
+
# 'aes-128-gcm' => {
|
68
|
+
# base64: {
|
69
|
+
# header: "QEVuQwAAOcqz9UDbd1Sn\n",
|
70
|
+
# no_header: "Ocqz9UDbd1Sn\n"
|
71
|
+
# },
|
72
|
+
# base64strict: {
|
73
|
+
# header: 'QEVuQwAAOcqz9UDbd1Sn',
|
74
|
+
# no_header: 'Ocqz9UDbd1Sn'
|
75
|
+
# },
|
76
|
+
# base16: {
|
77
|
+
# header: '40456e43000039cab3f540db7754a7',
|
78
|
+
# no_header: '39cab3f540db7754a7'
|
79
|
+
# },
|
80
|
+
# none: {
|
81
|
+
# header: "@EnC\x00\x009\xCA\xB3\xF5@\xDBwT\xA7",
|
82
|
+
# no_header: "9\xCA\xB3\xF5@\xDBwT\xA7"
|
83
|
+
# },
|
84
|
+
# }
|
85
|
+
}
|
86
|
+
|
87
|
+
@non_utf8 = "\xc2".force_encoding('binary')
|
88
|
+
@cipher = SymmetricEncryption::Cipher.new(
|
89
|
+
key: 'ABCDEF1234567890',
|
90
|
+
iv: 'ABCDEF1234567890',
|
91
|
+
cipher_name: cipher_name,
|
92
|
+
encoding: encoding,
|
93
|
+
always_add_header: always_add_header
|
94
|
+
)
|
95
|
+
|
96
|
+
h = @encrypted_values[cipher_name][encoding] if @encrypted_values[cipher_name]
|
97
|
+
skip "Add @encrypted_values for cipher_name: #{cipher_name} and encoding: #{encoding}, value: #{@cipher.encrypt(@social_security_number).inspect}" unless h
|
98
|
+
@social_security_number_encrypted = h[always_add_header ? :header : :no_header]
|
99
|
+
|
100
|
+
@social_security_number_encrypted.force_encoding(Encoding.find('binary')) if encoding == :none
|
101
|
+
end
|
140
102
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
assert_equal true, header.compressed
|
146
|
-
assert random_cipher = SymmetricEncryption::Cipher.new(random_key_pair)
|
147
|
-
assert_equal random_cipher.cipher_name, header.cipher_name, 'Ciphers differ'
|
148
|
-
assert_equal random_cipher.send(:key), header.key, 'Keys differ'
|
149
|
-
assert_equal random_cipher.send(:iv), header.iv, 'IVs differ'
|
103
|
+
it 'encrypt simple string' do
|
104
|
+
assert encrypted = @cipher.encrypt(@social_security_number)
|
105
|
+
assert_equal @social_security_number_encrypted, encrypted
|
106
|
+
end
|
150
107
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
108
|
+
it 'decrypt string' do
|
109
|
+
assert decrypted = @cipher.decrypt(@social_security_number_encrypted)
|
110
|
+
assert_equal @social_security_number, decrypted
|
111
|
+
assert_equal Encoding.find('utf-8'), decrypted.encoding, decrypted
|
112
|
+
end
|
156
113
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
114
|
+
it 'encrypt and decrypt string' do
|
115
|
+
assert encrypted = @cipher.encrypt(@social_security_number)
|
116
|
+
assert_equal @social_security_number_encrypted, encrypted
|
117
|
+
assert decrypted = @cipher.decrypt(encrypted)
|
118
|
+
assert_equal @social_security_number, decrypted
|
119
|
+
assert_equal Encoding.find('utf-8'), decrypted.encoding, decrypted
|
120
|
+
end
|
161
121
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
122
|
+
it 'return BINARY encoding for non-UTF-8 encrypted data' do
|
123
|
+
assert_equal Encoding.find('binary'), @non_utf8.encoding
|
124
|
+
assert_equal true, @non_utf8.valid_encoding?
|
125
|
+
assert encrypted = @cipher.encrypt(@non_utf8)
|
126
|
+
assert decrypted = @cipher.decrypt(encrypted)
|
127
|
+
assert_equal true, decrypted.valid_encoding?
|
128
|
+
assert_equal Encoding.find('binary'), decrypted.encoding, decrypted
|
129
|
+
assert_equal @non_utf8, decrypted
|
130
|
+
end
|
166
131
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
end
|
132
|
+
it 'return nil when encrypting nil' do
|
133
|
+
assert_nil @cipher.encrypt(nil)
|
134
|
+
end
|
171
135
|
|
172
|
-
|
136
|
+
it "return '' when encrypting ''" do
|
137
|
+
assert_equal '', @cipher.encrypt('')
|
138
|
+
end
|
173
139
|
|
174
|
-
|
140
|
+
it 'return nil when decrypting nil' do
|
141
|
+
assert_nil @cipher.decrypt(nil)
|
142
|
+
end
|
175
143
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
SymmetricEncryption::Cipher.generate_random_keys(wrong_params: '')
|
144
|
+
it "return '' when decrypting ''" do
|
145
|
+
assert_equal '', @cipher.decrypt('')
|
146
|
+
end
|
147
|
+
end
|
181
148
|
end
|
182
|
-
|
183
|
-
assert_equal "SymmetricEncryption::Cipher Invalid options {:wrong_params=>\"\"}", error.message
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
describe 'without keys' do
|
188
|
-
it 'creates new keys' do
|
189
|
-
h = SymmetricEncryption::Cipher.generate_random_keys
|
190
|
-
assert_equal 'aes-256-cbc', h[:cipher_name]
|
191
|
-
assert_equal :base64strict, h[:encoding]
|
192
|
-
assert h.has_key?(:key), h
|
193
|
-
assert h.has_key?(:iv), h
|
194
149
|
end
|
195
|
-
end
|
196
150
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
describe 'with encrypted keys' do
|
208
|
-
it 'creates new encrypted keys' do
|
209
|
-
key_encryption_key = SymmetricEncryption::KeyEncryptionKey.generate
|
210
|
-
h = SymmetricEncryption::Cipher.generate_random_keys(
|
211
|
-
encrypted_key: '',
|
212
|
-
encrypted_iv: '',
|
213
|
-
private_rsa_key: key_encryption_key
|
214
|
-
)
|
215
|
-
assert_equal 'aes-256-cbc', h[:cipher_name]
|
216
|
-
assert_equal :base64strict, h[:encoding]
|
217
|
-
assert h.has_key?(:encrypted_key), h
|
218
|
-
assert h.has_key?(:encrypted_iv), h
|
219
|
-
end
|
220
|
-
|
221
|
-
it 'exception on missing rsa key' do
|
222
|
-
assert_raises SymmetricEncryption::ConfigError do
|
223
|
-
SymmetricEncryption::Cipher.generate_random_keys(
|
224
|
-
encrypted_key: '',
|
225
|
-
encrypted_iv: ''
|
151
|
+
describe 'with configuration' do
|
152
|
+
before do
|
153
|
+
@cipher = SymmetricEncryption::Cipher.new(
|
154
|
+
key: '1234567890ABCDEF',
|
155
|
+
iv: '1234567890ABCDEF',
|
156
|
+
cipher_name: 'aes-128-cbc',
|
157
|
+
encoding: :none
|
226
158
|
)
|
227
|
-
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
describe 'with files' do
|
232
|
-
before do
|
233
|
-
@key_filename = 'blah.key'
|
234
|
-
@iv_filename = 'blah.iv'
|
235
|
-
end
|
159
|
+
@social_security_number = '987654321'
|
236
160
|
|
237
|
-
|
238
|
-
|
239
|
-
File.delete(@iv_filename) if File.exist?(@iv_filename)
|
240
|
-
end
|
161
|
+
@social_security_number_encrypted = "A\335*\314\336\250V\340\023%\000S\177\305\372\266"
|
162
|
+
@social_security_number_encrypted.force_encoding('binary')
|
241
163
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
iv_filename: @iv_filename,
|
247
|
-
private_rsa_key: key_encryption_key
|
248
|
-
)
|
249
|
-
assert_equal 'aes-256-cbc', h[:cipher_name]
|
250
|
-
assert_equal :base64strict, h[:encoding]
|
251
|
-
assert h.has_key?(:key_filename), h
|
252
|
-
assert h.has_key?(:iv_filename), h
|
253
|
-
assert File.exist?(@key_filename)
|
254
|
-
assert File.exist?(@iv_filename)
|
255
|
-
end
|
164
|
+
@sample_data = [
|
165
|
+
{text: '555052345', encrypted: ''}
|
166
|
+
]
|
167
|
+
end
|
256
168
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
169
|
+
describe 'with header' do
|
170
|
+
before do
|
171
|
+
@social_security_number = '987654321'
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'build and parse header' do
|
175
|
+
key = SymmetricEncryption::Key.new(cipher_name: 'aes-128-cbc')
|
176
|
+
assert binary_header = SymmetricEncryption::Cipher.build_header(SymmetricEncryption.cipher.version, true, key.iv, key.key, key.cipher_name)
|
177
|
+
header = SymmetricEncryption::Header.new
|
178
|
+
header.parse(binary_header)
|
179
|
+
assert_equal true, header.compressed?
|
180
|
+
assert random_cipher = SymmetricEncryption::Cipher.new(iv: key.iv, key: key.key, cipher_name: key.cipher_name)
|
181
|
+
assert_equal random_cipher.cipher_name, header.cipher_name, 'Ciphers differ'
|
182
|
+
assert_equal random_cipher.send(:key), header.key, 'Keys differ'
|
183
|
+
assert_equal random_cipher.send(:iv), header.iv, 'IVs differ'
|
184
|
+
|
185
|
+
string = 'Hello World'
|
186
|
+
cipher = SymmetricEncryption::Cipher.new(key: header.key, iv: header.iv, cipher_name: header.cipher_name)
|
187
|
+
# Test Encryption
|
188
|
+
assert_equal random_cipher.encrypt(string), cipher.encrypt(string), 'Encrypted values differ'
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'encrypt and then decrypt without a header' do
|
192
|
+
assert encrypted = @cipher.binary_encrypt(@social_security_number, header: false)
|
193
|
+
assert_equal @social_security_number, @cipher.decrypt(encrypted)
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'encrypt and then decrypt using random iv' do
|
197
|
+
assert encrypted = @cipher.encrypt(@social_security_number, random_iv: true)
|
198
|
+
assert_equal @social_security_number, @cipher.decrypt(encrypted)
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'encrypt and then decrypt using random iv with compression' do
|
202
|
+
assert encrypted = @cipher.encrypt(@social_security_number, random_iv: true, compress: true)
|
203
|
+
assert_equal @social_security_number, @cipher.decrypt(encrypted)
|
204
|
+
end
|
263
205
|
end
|
264
206
|
end
|
207
|
+
|
265
208
|
end
|
266
209
|
end
|
267
210
|
end
|