2
0
Эх сурвалжийг харах

check: rebuild_manifest must verify archive TAM

Thomas Waldmann 2 жил өмнө
parent
commit
d2f653e816

+ 13 - 0
src/borg/archive.py

@@ -1435,6 +1435,19 @@ class ArchiveChecker:
             except (TypeError, ValueError, StopIteration):
                 continue
             if valid_archive(archive):
+                # **after** doing the low-level checks and having a strong indication that we
+                # are likely looking at an archive item here, also check the TAM authentication:
+                try:
+                    archive, verified = self.key.unpack_and_verify_archive(data, force_tam_not_required=False)
+                except IntegrityError:
+                    # TAM issues - do not accept this archive!
+                    # either somebody is trying to attack us with a fake archive data or
+                    # we have an ancient archive made before TAM was a thing (borg < 1.0.9) **and** this repo
+                    # was not correctly upgraded to borg 1.2.5 (see advisory at top of the changelog).
+                    # borg can't tell the difference, so it has to assume this archive might be an attack
+                    # and drops this archive.
+                    continue
+                # note: if we get here and verified is False, a TAM is not required.
                 archive = ArchiveItem(internal_dict=archive)
                 name = archive.name
                 logger.info('Found archive %s', name)

+ 56 - 0
src/borg/crypto/key.py

@@ -87,6 +87,13 @@ class TAMRequiredError(IntegrityError):
     traceback = False
 
 
+class ArchiveTAMRequiredError(TAMRequiredError):
+    __doc__ = textwrap.dedent("""
+    Archive '{}' is unauthenticated, but it is required for this repository.
+    """).strip()
+    traceback = False
+
+
 class TAMInvalid(IntegrityError):
     __doc__ = IntegrityError.__doc__
     traceback = False
@@ -96,6 +103,15 @@ class TAMInvalid(IntegrityError):
         super().__init__('Manifest authentication did not verify')
 
 
+class ArchiveTAMInvalid(IntegrityError):
+    __doc__ = IntegrityError.__doc__
+    traceback = False
+
+    def __init__(self):
+        # Error message becomes: "Data integrity error: Archive authentication did not verify"
+        super().__init__('Archive authentication did not verify')
+
+
 class TAMUnsupportedSuiteError(IntegrityError):
     """Could not verify manifest: Unsupported suite {!r}; a newer version is needed."""
     traceback = False
@@ -264,6 +280,46 @@ class KeyBase:
         logger.debug('TAM-verified manifest')
         return unpacked, True
 
+    def unpack_and_verify_archive(self, data, force_tam_not_required=False):
+        """Unpack msgpacked *data* and return (object, did_verify)."""
+        tam_required = self.tam_required
+        if force_tam_not_required and tam_required:
+            logger.warning('Archive authentication DISABLED.')
+            tam_required = False
+        data = bytearray(data)
+        unpacker = get_limited_unpacker('archive')
+        unpacker.feed(data)
+        unpacked = unpacker.unpack()
+        if b'tam' not in unpacked:
+            if tam_required:
+                archive_name = unpacked.get(b'name', b'<unknown>').decode('ascii', 'replace')
+                raise ArchiveTAMRequiredError(archive_name)
+            else:
+                logger.debug('TAM not found and not required')
+                return unpacked, False
+        tam = unpacked.pop(b'tam', None)
+        if not isinstance(tam, dict):
+            raise ArchiveTAMInvalid()
+        tam_type = tam.get(b'type', b'<none>').decode('ascii', 'replace')
+        if tam_type != 'HKDF_HMAC_SHA512':
+            if tam_required:
+                raise TAMUnsupportedSuiteError(repr(tam_type))
+            else:
+                logger.debug('Ignoring TAM made with unsupported suite, since TAM is not required: %r', tam_type)
+                return unpacked, False
+        tam_hmac = tam.get(b'hmac')
+        tam_salt = tam.get(b'salt')
+        if not isinstance(tam_salt, bytes) or not isinstance(tam_hmac, bytes):
+            raise ArchiveTAMInvalid()
+        offset = data.index(tam_hmac)
+        data[offset:offset + 64] = bytes(64)
+        tam_key = self._tam_key(tam_salt, context=b'archive')
+        calculated_hmac = HMAC(tam_key, data, sha512).digest()
+        if not compare_digest(calculated_hmac, tam_hmac):
+            raise ArchiveTAMInvalid()
+        logger.debug('TAM-verified archive')
+        return unpacked, True
+
 
 class PlaintextKey(KeyBase):
     TYPE = 0x02

+ 6 - 0
src/borg/helpers.py

@@ -213,6 +213,12 @@ def get_limited_unpacker(kind):
                          object_hook=StableDict,
                          unicode_errors='surrogateescape',
                          ))
+    elif kind == 'archive':
+        args.update(dict(use_list=True,  # default value
+                         max_map_len=100,  # ARCHIVE_KEYS ~= 20
+                         max_str_len=10000,  # comment
+                         object_hook=StableDict,
+                         ))
     elif kind == 'key':
         args.update(dict(use_list=True,  # default value
                          max_array_len=0,  # not used

+ 3 - 2
src/borg/testsuite/archiver.py

@@ -3417,7 +3417,7 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase):
             corrupted_manifest = manifest + b'corrupted!'
             repository.put(Manifest.MANIFEST_ID, corrupted_manifest)
 
-            archive = msgpack.packb({
+            archive_dict = {
                 'cmdline': [],
                 'items': [],
                 'hostname': 'foo',
@@ -3425,7 +3425,8 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase):
                 'name': 'archive1',
                 'time': '2016-12-15T18:49:51.849711',
                 'version': 1,
-            })
+            }
+            archive = key.pack_and_authenticate_metadata(archive_dict, context=b'archive')
             archive_id = key.id_hash(archive)
             repository.put(archive_id, key.encrypt(archive))
             repository.commit()