Commit | Line | Data |
---|---|---|
866c42e6 DGM |
1 | # Copyright (C) 2013 Intra2net AG |
2 | # | |
494b38aa DGM |
3 | # This program is free software; you can redistribute it and/or modify |
4 | # it under the terms of the GNU Lesser General Public License as published | |
5 | # by the Free Software Foundation; either version 3 of the License, or | |
866c42e6 DGM |
6 | # (at your option) any later version. |
7 | # | |
8 | # This program is distributed in the hope that it will be useful, | |
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
494b38aa | 11 | # GNU Lesser General Public License for more details. |
866c42e6 DGM |
12 | # |
13 | # You should have received a copy of the GNU General Public License | |
494b38aa DGM |
14 | # along with this program. If not, see |
15 | # <http://www.gnu.org/licenses/lgpl-3.0.html> | |
866c42e6 DGM |
16 | |
17 | ||
a83fa4ed PG |
18 | import binascii |
19 | import hashlib | |
480fdfc3 | 20 | import json |
6e812ad9 | 21 | import os |
480fdfc3 | 22 | import subprocess |
6e812ad9 | 23 | |
fbfda3d4 | 24 | from deltatar import crypto |
6e812ad9 DGM |
25 | from deltatar.tarfile import TarFile, GNU_FORMAT |
26 | ||
27 | import filesplit | |
28 | from . import BaseTest | |
fbfda3d4 | 29 | from . import new_volume_handler, make_new_encryption_volume_handler |
6e812ad9 | 30 | |
fbfda3d4 PG |
31 | DELTATAR_HEADER_VERSION = 1 |
32 | DELTATAR_PARAMETER_VERSION = 1 | |
6e812ad9 DGM |
33 | |
34 | class EncryptionTest(BaseTest): | |
35 | """ | |
36 | Test encryption after compression in tarfiles | |
37 | """ | |
38 | ||
a83fa4ed | 39 | def test_cli_decrypt_pw (self): |
6e812ad9 DGM |
40 | """ |
41 | Create a tar file with only one file inside, using concat | |
fbfda3d4 | 42 | compression and encryption mode. Then decrypt with crypto.py, |
6e812ad9 | 43 | decompress it with zcat and untar it with gnu tar. |
fbfda3d4 PG |
44 | |
45 | Note that in an earlier implementation of the Deltatar crypto | |
46 | layer, files could be decrypted directly using OpenSSL command | |
47 | line tools. With the version 1 parameters this is no longer | |
48 | possible since OpenSSL does not ship with a command line tool | |
49 | that understands GCM and even if it did, it would be very | |
50 | unlikely that it could be made with the raw pdtcrypt format. | |
51 | ||
52 | Thus, we rely on the functionality of our encryption library | |
53 | ``crypto.py`` to decrypt on the command line; the rest of the | |
54 | data pipeline (→ gzip → tar → files) remains the same. | |
6e812ad9 | 55 | """ |
a83fa4ed | 56 | pw = "Where is my cow?" |
6e812ad9 DGM |
57 | |
58 | # create the content of the file to compress and hash it | |
59 | hash = self.create_file("big", 50000) | |
60 | ||
fbfda3d4 | 61 | # create the encryption handler |
a83fa4ed | 62 | encryptor = crypto.Encrypt (password=pw, |
fbfda3d4 PG |
63 | version=DELTATAR_HEADER_VERSION, |
64 | paramversion=DELTATAR_PARAMETER_VERSION) | |
65 | ||
6e812ad9 | 66 | # create the tar file with volumes |
fbfda3d4 PG |
67 | tarobj = TarFile.open("sample.tar.gz.pdtcrypt", |
68 | mode="w#gz", | |
6e812ad9 | 69 | format=GNU_FORMAT, |
fbfda3d4 | 70 | encryption=encryptor) |
6e812ad9 DGM |
71 | tarobj.add("big") |
72 | tarobj.close() | |
73 | os.unlink("big") | |
74 | ||
a83fa4ed PG |
75 | # decrypt outer archive layer with crypto.py |
76 | assert os.path.exists("sample.tar.gz.pdtcrypt") | |
77 | ret = os.system("python3 ./deltatar/crypto.py process -p '%s' " | |
78 | "<sample.tar.gz.pdtcrypt >sample.tar.gz" % pw) | |
79 | assert ret == 0 | |
80 | assert os.path.exists("sample.tar.gz") | |
81 | ||
82 | # extract with normal tar and check output | |
83 | os.system("zcat sample.tar.gz 2>/dev/null > sample.tar") | |
5cf23eab | 84 | os.system("tar xf sample.tar </dev/null") |
a83fa4ed PG |
85 | assert os.path.exists("big") |
86 | assert hash == self.md5sum("big") | |
87 | ||
88 | ||
89 | def test_cli_decrypt_key_argv (self): | |
90 | """ | |
91 | Encrypt, then decrypt using the crypto.py → gzip → tar pipe, | |
92 | supplying the encryption key on the command line. | |
93 | """ | |
94 | key = os.urandom (16) | |
95 | nacl = hashlib.md5 ("Stráðu á mig salti".encode ()).digest () | |
96 | ||
97 | # create the content of the file to compress and hash it | |
98 | hash = self.create_file("big", 50000) | |
99 | ||
100 | # create the encryption handler | |
101 | encryptor = crypto.Encrypt (key=key, nacl=nacl, | |
102 | version=DELTATAR_HEADER_VERSION, | |
103 | paramversion=DELTATAR_PARAMETER_VERSION) | |
104 | ||
105 | # create the tar file with volumes | |
106 | tarobj = TarFile.open("sample.tar.gz.pdtcrypt", | |
107 | mode="w#gz", | |
108 | format=GNU_FORMAT, | |
109 | encryption=encryptor) | |
110 | tarobj.add("big") | |
111 | tarobj.close() | |
112 | os.unlink("big") | |
6e812ad9 | 113 | |
fbfda3d4 PG |
114 | # decrypt outer archive layer with crypto.py |
115 | assert os.path.exists("sample.tar.gz.pdtcrypt") | |
a83fa4ed PG |
116 | ret = os.system("python3 ./deltatar/crypto.py process -k '%s' " |
117 | "<sample.tar.gz.pdtcrypt >sample.tar.gz" | |
118 | % binascii.hexlify (key).decode ()) | |
fbfda3d4 PG |
119 | assert ret == 0 |
120 | assert os.path.exists("sample.tar.gz") | |
6e812ad9 | 121 | |
fbfda3d4 | 122 | # extract with normal tar and check output |
02f34a22 | 123 | os.system("zcat sample.tar.gz 2>/dev/null > sample.tar") |
5cf23eab | 124 | os.system("tar xf sample.tar </dev/null") |
6e812ad9 DGM |
125 | assert os.path.exists("big") |
126 | assert hash == self.md5sum("big") | |
127 | ||
a83fa4ed PG |
128 | |
129 | def test_cli_decrypt_key_envp (self): | |
130 | """ | |
131 | Encrypt, then decrypt using the crypto.py → gzip → tar pipe, | |
132 | supplying the encryption key through the process environment. | |
133 | """ | |
134 | key = os.urandom (16) | |
135 | nacl = hashlib.md5 ("Stráðu á mig salti".encode ()).digest () | |
136 | ||
137 | # create the content of the file to compress and hash it | |
138 | hash = self.create_file("big", 50000) | |
139 | ||
140 | # create the encryption handler | |
141 | encryptor = crypto.Encrypt (key=key, nacl=nacl, | |
142 | version=DELTATAR_HEADER_VERSION, | |
143 | paramversion=DELTATAR_PARAMETER_VERSION) | |
144 | ||
145 | # create the tar file with volumes | |
146 | tarobj = TarFile.open("sample.tar.gz.pdtcrypt", | |
147 | mode="w#gz", | |
148 | format=GNU_FORMAT, | |
149 | encryption=encryptor) | |
150 | tarobj.add("big") | |
151 | tarobj.close() | |
152 | os.unlink("big") | |
153 | ||
154 | # decrypt outer archive layer with crypto.py | |
155 | assert os.path.exists("sample.tar.gz.pdtcrypt") | |
156 | ret = os.system("PDTCRYPT_KEY='%s' python3 ./deltatar/crypto.py process " | |
157 | "<sample.tar.gz.pdtcrypt >sample.tar.gz" | |
158 | % binascii.hexlify (key).decode ()) | |
159 | assert ret == 0 | |
160 | assert os.path.exists("sample.tar.gz") | |
161 | ||
162 | # extract with normal tar and check output | |
163 | os.system("zcat sample.tar.gz 2>/dev/null > sample.tar") | |
5cf23eab | 164 | os.system("tar xf sample.tar </dev/null") |
a83fa4ed PG |
165 | assert os.path.exists("big") |
166 | assert hash == self.md5sum("big") | |
167 | ||
168 | ||
480fdfc3 PG |
169 | def test_cli_scrypt (self): |
170 | """ | |
171 | Create an encrypted archive, then have crypto.py extract the | |
172 | scrypt hash from it. | |
173 | """ | |
174 | pw = "It goes baah, it is a sheep." | |
175 | nacl = hashlib.md5 ("Stráðu á mig salti".encode ()).digest () | |
176 | ||
177 | # create the content of the file to compress and hash it | |
178 | _void = self.create_file("big", 50000) | |
179 | ||
180 | # create the encryption handler | |
181 | encryptor = crypto.Encrypt (password=pw, nacl=nacl, | |
182 | version=DELTATAR_HEADER_VERSION, | |
183 | paramversion=DELTATAR_PARAMETER_VERSION) | |
184 | ||
185 | # create the tar file with volumes | |
186 | tarobj = TarFile.open("sample.tar.gz.pdtcrypt", | |
187 | mode="w#gz", | |
188 | format=GNU_FORMAT, | |
189 | encryption=encryptor) | |
190 | tarobj.add("big") | |
191 | tarobj.close() | |
192 | os.unlink("big") | |
193 | ||
194 | # decrypt outer archive layer with crypto.py | |
195 | assert os.path.exists("sample.tar.gz.pdtcrypt") | |
4f6405d6 PG |
196 | with subprocess.Popen ( [ "python3", "./deltatar/crypto.py", "scrypt" |
197 | , "-f", "params" | |
198 | , "-i", "sample.tar.gz.pdtcrypt" ] | |
480fdfc3 PG |
199 | , env={ "PDTCRYPT_PASSWORD" : pw } |
200 | , stdout=subprocess.PIPE | |
201 | ) as p: | |
202 | raw = p.stdout.read ().strip ().decode () | |
203 | ||
204 | info = json.loads (raw) | |
205 | assert nacl == binascii.unhexlify (info ["salt"]) | |
4f6405d6 | 206 | key = binascii.unhexlify (info ["key"]) |
480fdfc3 PG |
207 | kdf = crypto.kdf_by_version (1) |
208 | assert key, nacl == kdf (pw.encode (), nacl) | |
209 | ||
210 | ||
a83fa4ed | 211 | def test_cli_multiple_files_decrypt_pw_argv (self): |
6e812ad9 DGM |
212 | """ |
213 | Create a tar file with multiple files inside, using concat | |
b07633d3 PG |
214 | compression and encryption mode. Then decrypt and split with |
215 | ``crypto.py``, decompress it with zcat and untar it with gnu tar. | |
a83fa4ed | 216 | The password is specified as command line argument. |
6e812ad9 | 217 | """ |
a83fa4ed | 218 | pw = "Is that my cow?" |
6e812ad9 DGM |
219 | |
220 | # create sample data | |
221 | hash = dict() | |
222 | hash["big"] = self.create_file("big", 50000) | |
223 | hash["small"] = self.create_file("small", 100) | |
224 | hash["small2"] = self.create_file("small2", 354) | |
225 | ||
fbfda3d4 | 226 | # create the encryption handler |
a83fa4ed PG |
227 | encryptor = crypto.Encrypt (password=pw, |
228 | version=DELTATAR_HEADER_VERSION, | |
229 | paramversion=DELTATAR_PARAMETER_VERSION) | |
230 | ||
231 | # create the tar file with volumes | |
232 | tarobj = TarFile.open("sample.tar.gz.pdtcrypt", | |
233 | mode="w#gz", | |
234 | format=GNU_FORMAT, | |
235 | encryption=encryptor) | |
236 | ||
237 | for k in hash: | |
238 | tarobj.add(k) | |
239 | tarobj.close() | |
240 | ||
241 | for k in hash: | |
242 | os.unlink(k) | |
243 | ||
244 | assert os.path.exists("sample.tar.gz.pdtcrypt") | |
245 | ret = os.system("python3 ./deltatar/crypto.py process -p '%s' -s --split " | |
246 | "-i sample.tar.gz.pdtcrypt -o ." % pw) | |
247 | assert ret == 0 | |
248 | for i in range (len (hash)): | |
249 | fname = "pdtcrypt-object-%d.bin" % (i + 1) | |
250 | assert os.path.exists(fname) | |
251 | os.system("zcat '%s' 2>/dev/null > sample.tar" % fname) | |
5cf23eab | 252 | os.system("tar xf sample.tar </dev/null") |
a83fa4ed PG |
253 | |
254 | for fname, digest in hash.items(): | |
255 | assert os.path.exists(fname) | |
256 | assert digest == self.md5sum(fname) | |
257 | ||
258 | ||
259 | def test_cli_multiple_files_decrypt_envp (self): | |
260 | """ | |
261 | Create a tar file with multiple files inside, using concat | |
262 | compression and encryption mode. Then decrypt and split with | |
263 | ``crypto.py``, decompress it with zcat and untar it with gnu tar. | |
264 | The password is given as environment variable. | |
265 | """ | |
266 | pw = "Is that my cow?" | |
267 | ||
268 | # create sample data | |
269 | hash = dict() | |
270 | hash["big"] = self.create_file("big", 50000) | |
271 | hash["small"] = self.create_file("small", 100) | |
272 | hash["small2"] = self.create_file("small2", 354) | |
273 | ||
274 | # create the encryption handler | |
275 | encryptor = crypto.Encrypt (password=pw, | |
fbfda3d4 PG |
276 | version=DELTATAR_HEADER_VERSION, |
277 | paramversion=DELTATAR_PARAMETER_VERSION) | |
278 | ||
6e812ad9 | 279 | # create the tar file with volumes |
fbfda3d4 PG |
280 | tarobj = TarFile.open("sample.tar.gz.pdtcrypt", |
281 | mode="w#gz", | |
6e812ad9 | 282 | format=GNU_FORMAT, |
fbfda3d4 | 283 | encryption=encryptor) |
6e812ad9 DGM |
284 | |
285 | for k in hash: | |
286 | tarobj.add(k) | |
287 | tarobj.close() | |
288 | ||
289 | for k in hash: | |
290 | os.unlink(k) | |
291 | ||
fbfda3d4 | 292 | assert os.path.exists("sample.tar.gz.pdtcrypt") |
a83fa4ed PG |
293 | ret = os.system("PDTCRYPT_PASSWORD='%s' python3 ./deltatar/crypto.py " |
294 | "process -s --split -i sample.tar.gz.pdtcrypt -o ." | |
295 | % pw) | |
fbfda3d4 | 296 | assert ret == 0 |
b07633d3 PG |
297 | for i in range (len (hash)): |
298 | fname = "pdtcrypt-object-%d.bin" % (i + 1) | |
299 | assert os.path.exists(fname) | |
300 | os.system("zcat '%s' 2>/dev/null > sample.tar" % fname) | |
5cf23eab | 301 | os.system("tar xf sample.tar </dev/null") |
6e812ad9 | 302 | |
a83fa4ed PG |
303 | for fname, digest in hash.items(): |
304 | assert os.path.exists(fname) | |
305 | assert digest == self.md5sum(fname) | |
7d372216 | 306 | |
fbfda3d4 | 307 | |
7d372216 DGM |
308 | def test_decrypt(self): |
309 | """ | |
310 | Create a tar file with only one file inside, using concat | |
311 | compression and encryption mode. Then decrypt it. | |
312 | """ | |
313 | ||
314 | # create the content of the file to compress and hash it | |
315 | hash = self.create_file("big", 50000) | |
316 | ||
fbfda3d4 PG |
317 | # encryption handling |
318 | encryptor = crypto.Encrypt (password="key", | |
319 | version=DELTATAR_HEADER_VERSION, | |
320 | paramversion=DELTATAR_PARAMETER_VERSION) | |
321 | decryptor = crypto.Decrypt (password="key") | |
322 | ||
7d372216 | 323 | # create the tar file with volumes |
fbfda3d4 PG |
324 | tarobj = TarFile.open("sample.tar.gz.pdtcrypt", |
325 | mode="w#gz", | |
7d372216 | 326 | format=GNU_FORMAT, |
fbfda3d4 | 327 | encryption=encryptor) |
7d372216 DGM |
328 | tarobj.add("big") |
329 | tarobj.close() | |
330 | os.unlink("big") | |
331 | ||
fbfda3d4 PG |
332 | tarobj = TarFile.open("sample.tar.gz.pdtcrypt", |
333 | mode="r#gz", | |
7d372216 | 334 | format=GNU_FORMAT, |
fbfda3d4 | 335 | encryption = decryptor) |
7d372216 DGM |
336 | tarobj.extractall() |
337 | tarobj.close() | |
338 | assert os.path.exists("big") | |
339 | assert hash == self.md5sum("big") | |
340 | ||
341 | ||
342 | def test_multiple_file_decrypt(self): | |
343 | """ | |
344 | Create a tar file with only one file inside, using concat | |
345 | compression and encryption mode. Then decrypt it. | |
346 | """ | |
347 | ||
348 | # create sample data | |
349 | hash = dict() | |
350 | hash["big"] = self.create_file("big", 50000) | |
351 | hash["small"] = self.create_file("small", 100) | |
352 | hash["small2"] = self.create_file("small2", 354) | |
353 | ||
fbfda3d4 PG |
354 | encryptor = crypto.Encrypt (password="key", |
355 | version=DELTATAR_HEADER_VERSION, | |
356 | paramversion=DELTATAR_PARAMETER_VERSION) | |
357 | ||
7d372216 | 358 | # create the tar file with volumes |
fbfda3d4 PG |
359 | tarobj = TarFile.open("sample.tar.gz.pdtcrypt", |
360 | mode="w#gz", | |
7d372216 | 361 | format=GNU_FORMAT, |
fbfda3d4 | 362 | encryption=encryptor) |
7d372216 DGM |
363 | |
364 | for k in hash: | |
365 | tarobj.add(k) | |
366 | tarobj.close() | |
367 | ||
368 | for k in hash: | |
369 | os.unlink(k) | |
370 | ||
fbfda3d4 PG |
371 | decryptor = crypto.Decrypt (password="key") |
372 | tarobj = TarFile.open("sample.tar.gz.pdtcrypt", | |
373 | mode="r#gz", | |
7d372216 | 374 | format=GNU_FORMAT, |
fbfda3d4 PG |
375 | encryption=decryptor) |
376 | ||
7d372216 DGM |
377 | tarobj.extractall() |
378 | tarobj.close() | |
379 | ||
be60ffd0 | 380 | for key, value in hash.items(): |
7d372216 DGM |
381 | assert os.path.exists(key) |
382 | assert value == self.md5sum(key) | |
415c5e69 | 383 | |
fbfda3d4 | 384 | |
415c5e69 DGM |
385 | def test_multivol_file_decrypt(self): |
386 | ''' | |
387 | Test multivol tarball with encryption. | |
388 | ''' | |
389 | ||
390 | # create sample data | |
391 | hash = dict() | |
392 | hash["big"] = self.create_file("big", 50000) | |
393 | hash["big2"] = self.create_file("big2", 10200) | |
394 | hash["small"] = self.create_file("small", 100) | |
395 | hash["small2"] = self.create_file("small2", 354) | |
396 | ||
fbfda3d4 PG |
397 | # encryption handler |
398 | encryptor = crypto.Encrypt (password="key", | |
399 | version=DELTATAR_HEADER_VERSION, | |
400 | paramversion=DELTATAR_PARAMETER_VERSION) | |
169fcbb4 | 401 | |
fbfda3d4 PG |
402 | # plug the encryption context into the volume handler |
403 | encrypt_volume = make_new_encryption_volume_handler (encryptor) | |
169fcbb4 | 404 | |
fbfda3d4 PG |
405 | # create the tar file with volumes; we need to use a low compression |
406 | # level to force volume split because the test data has too little | |
407 | # entropy | |
408 | tarobj = TarFile.open("sample.tar.gz.pdtcrypt", | |
409 | mode="w#gz", | |
169fcbb4 | 410 | max_volume_size=20000, |
fbfda3d4 PG |
411 | compresslevel=0, |
412 | new_volume_handler=encrypt_volume, | |
413 | encryption=encryptor) | |
169fcbb4 DGM |
414 | |
415 | for k in hash: | |
416 | tarobj.add(k) | |
417 | tarobj.close() | |
418 | ||
fbfda3d4 | 419 | assert os.path.exists("sample.tar.gz.pdtcrypt") |
169fcbb4 DGM |
420 | for k in hash: |
421 | os.unlink(k) | |
422 | ||
fbfda3d4 PG |
423 | # decrypt |
424 | decryptor = crypto.Decrypt (password="key") | |
425 | ||
426 | # plug the encryption context into the volume handler | |
427 | decrypt_volume = make_new_encryption_volume_handler (decryptor) | |
428 | ||
169fcbb4 | 429 | # extract |
fbfda3d4 PG |
430 | tarobj = TarFile.open("sample.tar.gz.pdtcrypt", |
431 | mode="r#gz", | |
432 | new_volume_handler=decrypt_volume, | |
433 | encryption=decryptor) | |
169fcbb4 DGM |
434 | tarobj.extractall() |
435 | tarobj.close() | |
436 | ||
437 | # check output | |
be60ffd0 | 438 | for key, value in hash.items(): |
169fcbb4 DGM |
439 | assert os.path.exists(key) |
440 | assert value == self.md5sum(key) | |
441 |