| 
					
				 | 
			
			
				@@ -5,15 +5,17 @@ import os 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import sys 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import textwrap 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 from hmac import HMAC, compare_digest 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-from hashlib import sha256, pbkdf2_hmac 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from hashlib import sha256, sha512, pbkdf2_hmac 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-from .helpers import IntegrityError, get_keys_dir, Error, yes, bin_to_hex 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import msgpack 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from .helpers import StableDict, IntegrityError, get_keys_dir, get_security_dir, Error, yes, bin_to_hex 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 from .logger import create_logger 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 logger = create_logger() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 from .crypto import AES, bytes_to_long, long_to_bytes, bytes_to_int, num_aes_blocks 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-from .compress import Compressor 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import msgpack 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from .crypto import hkdf_hmac_sha512 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from .compress import Compressor, CNONE 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 PREFIX = b'\0' * 8 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -30,6 +32,10 @@ class UnsupportedPayloadError(Error): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     """Unsupported payload type {}. A newer version is required to access this repository.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+class UnsupportedManifestError(Error): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Unsupported manifest envelope. A newer version is required to access this repository.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 class KeyfileNotFoundError(Error): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     """No key file for repository {} found in {}.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -38,6 +44,32 @@ class RepoKeyNotFoundError(Error): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     """No key entry found in the config of repository {}.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+class TAMRequiredError(IntegrityError): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    __doc__ = textwrap.dedent(""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    Manifest is unauthenticated, but it is required for this repository. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    This either means that you are under attack, or that you modified this repository 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    with a Borg version older than 1.0.9 after TAM authentication was enabled. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    In the latter case, use "borg upgrade --tam --force '{}'" to re-authenticate the manifest. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """).strip() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    traceback = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+class TAMInvalid(IntegrityError): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    __doc__ = IntegrityError.__doc__ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    traceback = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __init__(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        # Error message becomes: "Data integrity error: Manifest authentication did not verify" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        super().__init__('Manifest authentication did not verify') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+class TAMUnsupportedSuiteError(IntegrityError): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Could not verify manifest: Unsupported suite {!r}; a newer version is needed.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    traceback = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 def key_creator(repository, args): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if args.encryption == 'keyfile': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         return KeyfileKey.create(repository, args) 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -63,6 +95,16 @@ def key_factory(repository, manifest_data): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         raise UnsupportedPayloadError(key_type) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+def tam_required_file(repository): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    security_dir = get_security_dir(bin_to_hex(repository.id)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return os.path.join(security_dir, 'tam_required') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+def tam_required(repository): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    file = tam_required_file(repository) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return os.path.isfile(file) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 class KeyBase: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     TYPE = None  # override in subclasses 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -71,23 +113,90 @@ class KeyBase: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self.repository = repository 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self.target = None  # key location file path / repo obj 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self.compressor = Compressor('none') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.tam_required = True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def id_hash(self, data): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         """Return HMAC hash using the "id" HMAC key 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    def encrypt(self, data): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def encrypt(self, data, none_compression=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         pass 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def decrypt(self, id, data): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         pass 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def _tam_key(self, salt, context): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return hkdf_hmac_sha512( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ikm=self.id_key + self.enc_key + self.enc_hmac_key, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            salt=salt, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            info=b'borg-metadata-authentication-' + context, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            output_length=64 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def pack_and_authenticate_metadata(self, metadata_dict, context=b'manifest'): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        metadata_dict = StableDict(metadata_dict) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        tam = metadata_dict['tam'] = StableDict({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            'type': 'HKDF_HMAC_SHA512', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            'hmac': bytes(64), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            'salt': os.urandom(64), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        packed = msgpack.packb(metadata_dict, unicode_errors='surrogateescape') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        tam_key = self._tam_key(tam['salt'], context) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        tam['hmac'] = HMAC(tam_key, packed, sha512).digest() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return msgpack.packb(metadata_dict, unicode_errors='surrogateescape') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def unpack_and_verify_manifest(self, data, force_tam_not_required=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Unpack msgpacked *data* and return (object, did_verify).""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if data.startswith(b'\xc1' * 4): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            # This is a manifest from the future, we can't read it. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            raise UnsupportedManifestError() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        tam_required = self.tam_required 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if force_tam_not_required and tam_required: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            logger.warning('Manifest authentication DISABLED.') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            tam_required = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        data = bytearray(data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        # Since we don't trust these bytes we use the slower Python unpacker, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        # which is assumed to have a lower probability of security issues. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        unpacked = msgpack.fallback.unpackb(data, object_hook=StableDict, unicode_errors='surrogateescape') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if b'tam' not in unpacked: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if tam_required: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                raise TAMRequiredError(self.repository._location.canonical_path()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                logger.debug('TAM not found and not required') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return unpacked, False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        tam = unpacked.pop(b'tam', None) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not isinstance(tam, dict): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            raise TAMInvalid() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        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 TAMInvalid() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        offset = data.index(tam_hmac) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        data[offset:offset + 64] = bytes(64) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        tam_key = self._tam_key(tam_salt, context=b'manifest') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        calculated_hmac = HMAC(tam_key, data, sha512).digest() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not compare_digest(calculated_hmac, tam_hmac): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            raise TAMInvalid() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        logger.debug('TAM-verified manifest') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return unpacked, True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 class PlaintextKey(KeyBase): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     TYPE = 0x02 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     chunk_seed = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __init__(self, repository): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        super().__init__(repository) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.tam_required = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     @classmethod 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def create(cls, repository, args): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         logger.info('Encryption NOT enabled.\nUse the "--encryption=repokey|keyfile" to enable encryption.') 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -100,8 +209,12 @@ class PlaintextKey(KeyBase): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def id_hash(self, data): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         return sha256(data).digest() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    def encrypt(self, data): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        return b''.join([self.TYPE_STR, self.compressor.compress(data)]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def encrypt(self, data, none_compression=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if none_compression: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            compressed = CNONE().compress(data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            compressed = self.compressor.compress(data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return b''.join([self.TYPE_STR, compressed]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def decrypt(self, id, data): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         if data[0] != self.TYPE: 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -112,6 +225,9 @@ class PlaintextKey(KeyBase): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             raise IntegrityError('Chunk %s: id verification failed' % bin_to_hex(id)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         return data 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def _tam_key(self, salt, context): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return salt + context 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 class AESKeyBase(KeyBase): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     """Common base class shared by KeyfileKey and PassphraseKey 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -133,8 +249,11 @@ class AESKeyBase(KeyBase): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         return HMAC(self.id_key, data, sha256).digest() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    def encrypt(self, data): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        data = self.compressor.compress(data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def encrypt(self, data, none_compression=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if none_compression: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            data = CNONE().compress(data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            data = self.compressor.compress(data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self.enc_cipher.reset() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         data = b''.join((self.enc_cipher.iv[8:], self.enc_cipher.encrypt(data))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         hmac = HMAC(self.enc_hmac_key, data, sha256).digest() 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -269,6 +388,7 @@ class PassphraseKey(AESKeyBase): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 key.decrypt(None, manifest_data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 num_blocks = num_aes_blocks(len(manifest_data) - 41) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 key.init_ciphers(PREFIX + long_to_bytes(key.extract_nonce(manifest_data) + num_blocks)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                key._passphrase = passphrase 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 return key 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             except IntegrityError: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 passphrase = Passphrase.getpass(prompt) 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -284,6 +404,7 @@ class PassphraseKey(AESKeyBase): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def init(self, repository, passphrase): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self.init_from_random_data(passphrase.kdf(repository.id, self.iterations, 100)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self.init_ciphers() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.tam_required = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 class KeyfileKeyBase(AESKeyBase): 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -307,6 +428,7 @@ class KeyfileKeyBase(AESKeyBase): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 raise PassphraseWrong 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         num_blocks = num_aes_blocks(len(manifest_data) - 41) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         key.init_ciphers(PREFIX + long_to_bytes(key.extract_nonce(manifest_data) + num_blocks)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        key._passphrase = passphrase 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         return key 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def find_key(self): 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -327,6 +449,7 @@ class KeyfileKeyBase(AESKeyBase): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             self.enc_hmac_key = key[b'enc_hmac_key'] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             self.id_key = key[b'id_key'] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             self.chunk_seed = key[b'chunk_seed'] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.tam_required = key.get(b'tam_required', tam_required(self.repository)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         return False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -363,15 +486,16 @@ class KeyfileKeyBase(AESKeyBase): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             'enc_hmac_key': self.enc_hmac_key, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             'id_key': self.id_key, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             'chunk_seed': self.chunk_seed, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            'tam_required': self.tam_required, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         data = self.encrypt_key_file(msgpack.packb(key), passphrase) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         key_data = '\n'.join(textwrap.wrap(b2a_base64(data).decode('ascii'))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         return key_data 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    def change_passphrase(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        passphrase = Passphrase.new(allow_empty=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def change_passphrase(self, passphrase=None): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if passphrase is None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            passphrase = Passphrase.new(allow_empty=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self.save(self.target, passphrase) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        logger.info('Key updated') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     @classmethod 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def create(cls, repository, args): 
			 |