Browse Source

backup: add 'backup_id' to integrity protection

This prevent switching parts of backup of the same VM between different
backups made by the same user (or actually: with the same passphrase).

QubesOS/qubes-issues#971
Marek Marczykowski-Górecki 7 years ago
parent
commit
4ad15c082b
1 changed files with 25 additions and 4 deletions
  1. 25 4
      qubes/backup.py

+ 25 - 4
qubes/backup.py

@@ -80,6 +80,7 @@ class BackupHeader(object):
         'compression-filter': 'compression_filter',
         'crypto-algorithm': 'crypto_algorithm',
         'hmac-algorithm': 'hmac_algorithm',
+        'backup-id': 'backup_id'
     }
     bool_options = ['encrypted', 'compressed']
     int_options = ['version']
@@ -91,7 +92,8 @@ class BackupHeader(object):
             compressed=None,
             compression_filter=None,
             hmac_algorithm=None,
-            crypto_algorithm=None):
+            crypto_algorithm=None,
+            backup_id=None):
         # repeat the list to help code completion...
         self.version = version
         self.encrypted = encrypted
@@ -101,6 +103,7 @@ class BackupHeader(object):
         self.compression_filter = compression_filter
         self.hmac_algorithm = hmac_algorithm
         self.crypto_algorithm = crypto_algorithm
+        self.backup_id = backup_id
 
         if header_data is not None:
             self.load(header_data)
@@ -152,6 +155,8 @@ class BackupHeader(object):
                 expected_attrs += ['crypto_algorithm']
             if self.version >= 3 and self.compressed:
                 expected_attrs += ['compression_filter']
+            if self.version >= 4:
+                expected_attrs += ['backup_id']
             for key in expected_attrs:
                 if getattr(self, key) is None:
                     raise qubes.exc.QubesException(
@@ -353,6 +358,10 @@ class Backup(object):
         #: callback for progress reporting. Will be called with one argument
         #: - progress in percents
         self.progress_callback = None
+        #: backup ID, needs to be unique (for a given user),
+        #: not necessary unpredictable; automatically generated
+        self.backup_id = datetime.datetime.now().strftime(
+            '%Y%m%dT%H%M%S-' + str(os.getpid()))
 
         for key, value in kwargs.iteritems():
             if hasattr(self, key):
@@ -535,6 +544,7 @@ class Backup(object):
             encrypted=self.encrypted,
             compressed=self.compressed,
             compression_filter=self.compression_filter,
+            backup_id=self.backup_id,
         )
         backup_header.save(header_file_path)
         # Start encrypt, scrypt will also handle integrity
@@ -722,8 +732,12 @@ class Backup(object):
 
                     # Start encrypt, scrypt will also handle integrity
                     # protection
-                    scrypt_passphrase = os.path.relpath(chunkfile[:-4],
-                        self.tmpdir) + '!' + passphrase
+                    scrypt_passphrase = \
+                        '{backup_id}!{filename}!{passphrase}'.format(
+                            backup_id=self.backup_id,
+                            filename=os.path.relpath(chunkfile[:-4],
+                                self.tmpdir),
+                            passphrase=passphrase)
                     scrypt = launch_scrypt(
                         "enc", "-", chunkfile, scrypt_passphrase)
 
@@ -1622,7 +1636,14 @@ class BackupRestore(object):
             fulloutput = os.path.join(self.tmpdir, output)
         else:
             fulloutput = os.path.join(self.tmpdir, origname)
-        passphrase = origname + '!' + self.passphrase.encode('utf-8')
+        if origname == HEADER_FILENAME:
+            passphrase = origname + '!' + self.passphrase.encode('utf-8')
+        else:
+            passphrase = \
+                '{backup_id}!{filename}!{passphrase}'.format(
+                    backup_id=self.header_data.backup_id,
+                    filename=origname,
+                    passphrase=self.passphrase.encode('utf-8'))
         p = launch_scrypt('dec', fullname, fulloutput, passphrase)
         (_, stderr) = p.communicate()
         if p.returncode != 0: