|
@@ -1,13 +1,9 @@
|
|
import configparser
|
|
import configparser
|
|
-import getpass
|
|
|
|
import hmac
|
|
import hmac
|
|
import os
|
|
import os
|
|
-import shlex
|
|
|
|
-import sys
|
|
|
|
import textwrap
|
|
import textwrap
|
|
-import subprocess
|
|
|
|
from binascii import a2b_base64, b2a_base64, hexlify
|
|
from binascii import a2b_base64, b2a_base64, hexlify
|
|
-from hashlib import sha256, sha512, pbkdf2_hmac
|
|
|
|
|
|
+from hashlib import sha256
|
|
|
|
|
|
from ..logger import create_logger
|
|
from ..logger import create_logger
|
|
|
|
|
|
@@ -17,11 +13,10 @@ from ..constants import * # NOQA
|
|
from ..compress import Compressor
|
|
from ..compress import Compressor
|
|
from ..helpers import StableDict
|
|
from ..helpers import StableDict
|
|
from ..helpers import Error, IntegrityError
|
|
from ..helpers import Error, IntegrityError
|
|
-from ..helpers import yes
|
|
|
|
from ..helpers import get_keys_dir, get_security_dir
|
|
from ..helpers import get_keys_dir, get_security_dir
|
|
from ..helpers import get_limited_unpacker
|
|
from ..helpers import get_limited_unpacker
|
|
from ..helpers import bin_to_hex
|
|
from ..helpers import bin_to_hex
|
|
-from ..helpers import prepare_subprocess_env
|
|
|
|
|
|
+from ..helpers.passphrase import Passphrase, PasswordRetriesExceeded, PassphraseWrong
|
|
from ..helpers import msgpack
|
|
from ..helpers import msgpack
|
|
from ..item import Key, EncryptedKey
|
|
from ..item import Key, EncryptedKey
|
|
from ..platform import SaveFile
|
|
from ..platform import SaveFile
|
|
@@ -31,22 +26,6 @@ from .low_level import AES, bytes_to_long, long_to_bytes, bytes_to_int, num_ciph
|
|
from .low_level import AES256_CTR_HMAC_SHA256, AES256_CTR_BLAKE2b
|
|
from .low_level import AES256_CTR_HMAC_SHA256, AES256_CTR_BLAKE2b
|
|
|
|
|
|
|
|
|
|
-class NoPassphraseFailure(Error):
|
|
|
|
- """can not acquire a passphrase: {}"""
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-class PassphraseWrong(Error):
|
|
|
|
- """passphrase supplied in BORG_PASSPHRASE, by BORG_PASSCOMMAND or via BORG_PASSPHRASE_FD is incorrect."""
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-class PasscommandFailure(Error):
|
|
|
|
- """passcommand supplied in BORG_PASSCOMMAND failed: {}"""
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-class PasswordRetriesExceeded(Error):
|
|
|
|
- """exceeded the maximum password retries"""
|
|
|
|
-
|
|
|
|
-
|
|
|
|
class UnsupportedPayloadError(Error):
|
|
class UnsupportedPayloadError(Error):
|
|
"""Unsupported payload type {}. A newer version is required to access this repository."""
|
|
"""Unsupported payload type {}. A newer version is required to access this repository."""
|
|
|
|
|
|
@@ -420,115 +399,6 @@ class AESKeyBase(KeyBase):
|
|
self.nonce_manager = NonceManager(self.repository, nonce)
|
|
self.nonce_manager = NonceManager(self.repository, nonce)
|
|
|
|
|
|
|
|
|
|
-class Passphrase(str):
|
|
|
|
- @classmethod
|
|
|
|
- def _env_passphrase(cls, env_var, default=None):
|
|
|
|
- passphrase = os.environ.get(env_var, default)
|
|
|
|
- if passphrase is not None:
|
|
|
|
- return cls(passphrase)
|
|
|
|
-
|
|
|
|
- @classmethod
|
|
|
|
- def env_passphrase(cls, default=None):
|
|
|
|
- passphrase = cls._env_passphrase('BORG_PASSPHRASE', default)
|
|
|
|
- if passphrase is not None:
|
|
|
|
- return passphrase
|
|
|
|
- passphrase = cls.env_passcommand()
|
|
|
|
- if passphrase is not None:
|
|
|
|
- return passphrase
|
|
|
|
- passphrase = cls.fd_passphrase()
|
|
|
|
- if passphrase is not None:
|
|
|
|
- return passphrase
|
|
|
|
-
|
|
|
|
- @classmethod
|
|
|
|
- def env_passcommand(cls, default=None):
|
|
|
|
- passcommand = os.environ.get('BORG_PASSCOMMAND', None)
|
|
|
|
- if passcommand is not None:
|
|
|
|
- # passcommand is a system command (not inside pyinstaller env)
|
|
|
|
- env = prepare_subprocess_env(system=True)
|
|
|
|
- try:
|
|
|
|
- passphrase = subprocess.check_output(shlex.split(passcommand), universal_newlines=True, env=env)
|
|
|
|
- except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
|
|
|
- raise PasscommandFailure(e)
|
|
|
|
- return cls(passphrase.rstrip('\n'))
|
|
|
|
-
|
|
|
|
- @classmethod
|
|
|
|
- def fd_passphrase(cls):
|
|
|
|
- try:
|
|
|
|
- fd = int(os.environ.get('BORG_PASSPHRASE_FD'))
|
|
|
|
- except (ValueError, TypeError):
|
|
|
|
- return None
|
|
|
|
- with os.fdopen(fd, mode='r') as f:
|
|
|
|
- passphrase = f.read()
|
|
|
|
- return cls(passphrase.rstrip('\n'))
|
|
|
|
-
|
|
|
|
- @classmethod
|
|
|
|
- def env_new_passphrase(cls, default=None):
|
|
|
|
- return cls._env_passphrase('BORG_NEW_PASSPHRASE', default)
|
|
|
|
-
|
|
|
|
- @classmethod
|
|
|
|
- def getpass(cls, prompt):
|
|
|
|
- try:
|
|
|
|
- pw = getpass.getpass(prompt)
|
|
|
|
- except EOFError:
|
|
|
|
- if prompt:
|
|
|
|
- print() # avoid err msg appearing right of prompt
|
|
|
|
- msg = []
|
|
|
|
- for env_var in 'BORG_PASSPHRASE', 'BORG_PASSCOMMAND':
|
|
|
|
- env_var_set = os.environ.get(env_var) is not None
|
|
|
|
- msg.append('{} is {}.'.format(env_var, 'set' if env_var_set else 'not set'))
|
|
|
|
- msg.append('Interactive password query failed.')
|
|
|
|
- raise NoPassphraseFailure(' '.join(msg)) from None
|
|
|
|
- else:
|
|
|
|
- return cls(pw)
|
|
|
|
-
|
|
|
|
- @classmethod
|
|
|
|
- def verification(cls, passphrase):
|
|
|
|
- msg = 'Do you want your passphrase to be displayed for verification? [yN]: '
|
|
|
|
- if yes(msg, retry_msg=msg, invalid_msg='Invalid answer, try again.',
|
|
|
|
- retry=True, env_var_override='BORG_DISPLAY_PASSPHRASE'):
|
|
|
|
- print('Your passphrase (between double-quotes): "%s"' % passphrase,
|
|
|
|
- file=sys.stderr)
|
|
|
|
- print('Make sure the passphrase displayed above is exactly what you wanted.',
|
|
|
|
- file=sys.stderr)
|
|
|
|
- try:
|
|
|
|
- passphrase.encode('ascii')
|
|
|
|
- except UnicodeEncodeError:
|
|
|
|
- print('Your passphrase (UTF-8 encoding in hex): %s' %
|
|
|
|
- bin_to_hex(passphrase.encode('utf-8')),
|
|
|
|
- file=sys.stderr)
|
|
|
|
- print('As you have a non-ASCII passphrase, it is recommended to keep the UTF-8 encoding in hex together with the passphrase at a safe place.',
|
|
|
|
- file=sys.stderr)
|
|
|
|
-
|
|
|
|
- @classmethod
|
|
|
|
- def new(cls, allow_empty=False):
|
|
|
|
- passphrase = cls.env_new_passphrase()
|
|
|
|
- if passphrase is not None:
|
|
|
|
- return passphrase
|
|
|
|
- passphrase = cls.env_passphrase()
|
|
|
|
- if passphrase is not None:
|
|
|
|
- return passphrase
|
|
|
|
- for retry in range(1, 11):
|
|
|
|
- passphrase = cls.getpass('Enter new passphrase: ')
|
|
|
|
- if allow_empty or passphrase:
|
|
|
|
- passphrase2 = cls.getpass('Enter same passphrase again: ')
|
|
|
|
- if passphrase == passphrase2:
|
|
|
|
- cls.verification(passphrase)
|
|
|
|
- logger.info('Remember your passphrase. Your data will be inaccessible without it.')
|
|
|
|
- return passphrase
|
|
|
|
- else:
|
|
|
|
- print('Passphrases do not match', file=sys.stderr)
|
|
|
|
- else:
|
|
|
|
- print('Passphrase must not be blank', file=sys.stderr)
|
|
|
|
- else:
|
|
|
|
- raise PasswordRetriesExceeded
|
|
|
|
-
|
|
|
|
- def __repr__(self):
|
|
|
|
- return '<Passphrase "***hidden***">'
|
|
|
|
-
|
|
|
|
- def kdf(self, salt, iterations, length):
|
|
|
|
- return pbkdf2_hmac('sha256', self.encode('utf-8'), salt, iterations, length)
|
|
|
|
-
|
|
|
|
-
|
|
|
|
class FlexiKeyBase(AESKeyBase):
|
|
class FlexiKeyBase(AESKeyBase):
|
|
@classmethod
|
|
@classmethod
|
|
def detect(cls, repository, manifest_data):
|
|
def detect(cls, repository, manifest_data):
|