guard invocations of tar from interactive mode
[python-delta-tar] / testing / test_encryption.py
1 # Copyright (C) 2013 Intra2net AG
2 #
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
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
11 # GNU Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program.  If not, see
15 # <http://www.gnu.org/licenses/lgpl-3.0.html>
16
17
18 import binascii
19 import hashlib
20 import json
21 import os
22 import subprocess
23
24 from deltatar import crypto
25 from deltatar.tarfile import TarFile, GNU_FORMAT
26
27 import filesplit
28 from . import BaseTest
29 from . import new_volume_handler, make_new_encryption_volume_handler
30
31 DELTATAR_HEADER_VERSION    = 1
32 DELTATAR_PARAMETER_VERSION = 1
33
34 class EncryptionTest(BaseTest):
35     """
36     Test encryption after compression in tarfiles
37     """
38
39     def test_cli_decrypt_pw (self):
40         """
41         Create a tar file with only one file inside, using concat
42         compression and encryption mode. Then decrypt with crypto.py,
43         decompress it with zcat and untar it with gnu tar.
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.
55         """
56         pw = "Where is my cow?"
57
58         # create the content of the file to compress and hash it
59         hash = self.create_file("big", 50000)
60
61         # create the encryption handler
62         encryptor = crypto.Encrypt (password=pw,
63                                     version=DELTATAR_HEADER_VERSION,
64                                     paramversion=DELTATAR_PARAMETER_VERSION)
65
66         # create the tar file with volumes
67         tarobj = TarFile.open("sample.tar.gz.pdtcrypt",
68                               mode="w#gz",
69                               format=GNU_FORMAT,
70                               encryption=encryptor)
71         tarobj.add("big")
72         tarobj.close()
73         os.unlink("big")
74
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")
84         os.system("tar xf sample.tar </dev/null")
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")
113
114         # decrypt outer archive layer with crypto.py
115         assert os.path.exists("sample.tar.gz.pdtcrypt")
116         ret = os.system("python3 ./deltatar/crypto.py process -k '%s' "
117                         "<sample.tar.gz.pdtcrypt >sample.tar.gz"
118                         % binascii.hexlify (key).decode ())
119         assert ret == 0
120         assert os.path.exists("sample.tar.gz")
121
122         # extract with normal tar and check output
123         os.system("zcat sample.tar.gz 2>/dev/null > sample.tar")
124         os.system("tar xf sample.tar </dev/null")
125         assert os.path.exists("big")
126         assert hash == self.md5sum("big")
127
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")
164         os.system("tar xf sample.tar </dev/null")
165         assert os.path.exists("big")
166         assert hash == self.md5sum("big")
167
168
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")
196         with subprocess.Popen ( [ "python3", "./deltatar/crypto.py", "scrypt"
197                                 , "-f", "params"
198                                 , "-i", "sample.tar.gz.pdtcrypt" ]
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"])
206         key = binascii.unhexlify (info ["key"])
207         kdf = crypto.kdf_by_version (1)
208         assert key, nacl == kdf (pw.encode (), nacl)
209
210
211     def test_cli_multiple_files_decrypt_pw_argv (self):
212         """
213         Create a tar file with multiple files inside, using concat
214         compression and encryption mode. Then decrypt and split with
215         ``crypto.py``, decompress it with zcat and untar it with gnu tar.
216         The password is specified as command line argument.
217         """
218         pw = "Is that my cow?"
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
226         # create the encryption handler
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)
252             os.system("tar xf sample.tar </dev/null")
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,
276                                     version=DELTATAR_HEADER_VERSION,
277                                     paramversion=DELTATAR_PARAMETER_VERSION)
278
279         # create the tar file with volumes
280         tarobj = TarFile.open("sample.tar.gz.pdtcrypt",
281                               mode="w#gz",
282                               format=GNU_FORMAT,
283                               encryption=encryptor)
284
285         for k in hash:
286             tarobj.add(k)
287         tarobj.close()
288
289         for k in hash:
290             os.unlink(k)
291
292         assert os.path.exists("sample.tar.gz.pdtcrypt")
293         ret = os.system("PDTCRYPT_PASSWORD='%s' python3 ./deltatar/crypto.py "
294                         "process -s --split -i sample.tar.gz.pdtcrypt -o ."
295                         % pw)
296         assert ret == 0
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)
301             os.system("tar xf sample.tar </dev/null")
302
303         for fname, digest in hash.items():
304             assert os.path.exists(fname)
305             assert digest == self.md5sum(fname)
306
307
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
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
323         # create the tar file with volumes
324         tarobj = TarFile.open("sample.tar.gz.pdtcrypt",
325                               mode="w#gz",
326                               format=GNU_FORMAT,
327                               encryption=encryptor)
328         tarobj.add("big")
329         tarobj.close()
330         os.unlink("big")
331
332         tarobj = TarFile.open("sample.tar.gz.pdtcrypt",
333                               mode="r#gz",
334                               format=GNU_FORMAT,
335                               encryption = decryptor)
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
354         encryptor = crypto.Encrypt (password="key",
355                                     version=DELTATAR_HEADER_VERSION,
356                                     paramversion=DELTATAR_PARAMETER_VERSION)
357
358         # create the tar file with volumes
359         tarobj = TarFile.open("sample.tar.gz.pdtcrypt",
360                               mode="w#gz",
361                               format=GNU_FORMAT,
362                               encryption=encryptor)
363
364         for k in hash:
365             tarobj.add(k)
366         tarobj.close()
367
368         for k in hash:
369             os.unlink(k)
370
371         decryptor = crypto.Decrypt (password="key")
372         tarobj = TarFile.open("sample.tar.gz.pdtcrypt",
373                               mode="r#gz",
374                               format=GNU_FORMAT,
375                               encryption=decryptor)
376
377         tarobj.extractall()
378         tarobj.close()
379
380         for key, value in hash.items():
381             assert os.path.exists(key)
382             assert value == self.md5sum(key)
383
384
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
397         # encryption handler
398         encryptor = crypto.Encrypt (password="key",
399                                     version=DELTATAR_HEADER_VERSION,
400                                     paramversion=DELTATAR_PARAMETER_VERSION)
401
402         # plug the encryption context into the volume handler
403         encrypt_volume = make_new_encryption_volume_handler (encryptor)
404
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",
410                               max_volume_size=20000,
411                               compresslevel=0,
412                               new_volume_handler=encrypt_volume,
413                               encryption=encryptor)
414
415         for k in hash:
416             tarobj.add(k)
417         tarobj.close()
418
419         assert os.path.exists("sample.tar.gz.pdtcrypt")
420         for k in hash:
421             os.unlink(k)
422
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
429         # extract
430         tarobj = TarFile.open("sample.tar.gz.pdtcrypt",
431                               mode="r#gz",
432                               new_volume_handler=decrypt_volume,
433                               encryption=decryptor)
434         tarobj.extractall()
435         tarobj.close()
436
437         # check output
438         for key, value in hash.items():
439             assert os.path.exists(key)
440             assert value == self.md5sum(key)
441