guard invocations of tar from interactive mode
[python-delta-tar] / testing / test_encryption.py
CommitLineData
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
18import binascii
19import hashlib
480fdfc3 20import json
6e812ad9 21import os
480fdfc3 22import subprocess
6e812ad9 23
fbfda3d4 24from deltatar import crypto
6e812ad9
DGM
25from deltatar.tarfile import TarFile, GNU_FORMAT
26
27import filesplit
28from . import BaseTest
fbfda3d4 29from . import new_volume_handler, make_new_encryption_volume_handler
6e812ad9 30
fbfda3d4
PG
31DELTATAR_HEADER_VERSION = 1
32DELTATAR_PARAMETER_VERSION = 1
6e812ad9
DGM
33
34class 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