key.py 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025
  1. import binascii
  2. import configparser
  3. import getpass
  4. import hmac
  5. import os
  6. import shlex
  7. import sys
  8. import textwrap
  9. import subprocess
  10. from hashlib import sha256, sha512, pbkdf2_hmac
  11. from ..logger import create_logger
  12. logger = create_logger()
  13. from .. import helpers
  14. from ..constants import * # NOQA
  15. from ..compress import Compressor
  16. from ..helpers import StableDict
  17. from ..helpers import Error, IntegrityError
  18. from ..helpers import yes
  19. from ..helpers import get_keys_dir, get_security_dir
  20. from ..helpers import get_limited_unpacker
  21. from ..helpers import bin_to_hex
  22. from ..helpers import prepare_subprocess_env
  23. from ..helpers import msgpack
  24. from ..item import Key, EncryptedKey
  25. from ..platform import SaveFile
  26. from .nonces import NonceManager
  27. from .low_level import AES, bytes_to_long, long_to_bytes, bytes_to_int, num_cipher_blocks, hmac_sha256, blake2b_256, hkdf_hmac_sha512
  28. from .low_level import AES256_CTR_HMAC_SHA256, AES256_CTR_BLAKE2b
  29. # workaround for lost passphrase or key in "authenticated" or "authenticated-blake2" mode
  30. AUTHENTICATED_NO_KEY = 'authenticated_no_key' in helpers.workarounds
  31. class NoPassphraseFailure(Error):
  32. """Cannot acquire a passphrase: {}"""
  33. exit_mcode = 50
  34. class PasscommandFailure(Error):
  35. """Passcommand supplied in BORG_PASSCOMMAND failed: {}"""
  36. exit_mcode = 51
  37. class PassphraseWrong(Error):
  38. """Passphrase supplied in BORG_PASSPHRASE, by BORG_PASSCOMMAND, or via BORG_PASSPHRASE_FD is incorrect."""
  39. exit_mcode = 52
  40. class PasswordRetriesExceeded(Error):
  41. """Exceeded the maximum password retries."""
  42. exit_mcode = 53
  43. class UnsupportedPayloadError(Error):
  44. """Unsupported payload type {}. A newer version is required to access this repository."""
  45. exit_mcode = 48
  46. class UnsupportedManifestError(Error):
  47. """Unsupported manifest envelope. A newer version is required to access this repository."""
  48. exit_mcode = 27
  49. class KeyfileNotFoundError(Error):
  50. """No key file for repository {} found in {}."""
  51. exit_mcode = 42
  52. class KeyfileInvalidError(Error):
  53. """Invalid key data for repository {} found in {}."""
  54. exit_mcode = 40
  55. class KeyfileMismatchError(Error):
  56. """Mismatch between repository {} and key file {}."""
  57. exit_mcode = 41
  58. class RepoKeyNotFoundError(Error):
  59. """No key entry found in the config of repository {}."""
  60. exit_mcode = 44
  61. class TAMRequiredError(IntegrityError):
  62. __doc__ = textwrap.dedent("""
  63. Manifest is unauthenticated, but it is required for this repository.
  64. """).strip()
  65. traceback = True
  66. exit_mcode = 98
  67. class ArchiveTAMRequiredError(TAMRequiredError):
  68. __doc__ = textwrap.dedent("""
  69. Archive '{}' is unauthenticated, but it is required for this repository.
  70. """).strip()
  71. traceback = True
  72. exit_mcode = 96
  73. class TAMInvalid(IntegrityError):
  74. __doc__ = IntegrityError.__doc__
  75. traceback = True
  76. exit_mcode = 97
  77. def __init__(self):
  78. # Error message becomes: "Data integrity error: Manifest authentication did not verify"
  79. super().__init__('Manifest authentication did not verify')
  80. class ArchiveTAMInvalid(IntegrityError):
  81. __doc__ = IntegrityError.__doc__
  82. traceback = True
  83. exit_mcode = 95
  84. def __init__(self):
  85. # Error message becomes: "Data integrity error: Archive authentication did not verify"
  86. super().__init__('Archive authentication did not verify')
  87. class TAMUnsupportedSuiteError(IntegrityError):
  88. """Could not verify manifest: Unsupported suite {!r}; a newer version is needed."""
  89. traceback = True
  90. exit_mcode = 99
  91. class KeyBlobStorage:
  92. NO_STORAGE = 'no_storage'
  93. KEYFILE = 'keyfile'
  94. REPO = 'repository'
  95. def key_creator(repository, args):
  96. for key in AVAILABLE_KEY_TYPES:
  97. if key.ARG_NAME == args.encryption:
  98. assert key.ARG_NAME is not None
  99. return key.create(repository, args)
  100. else:
  101. raise ValueError('Invalid encryption mode "%s"' % args.encryption)
  102. def key_argument_names():
  103. return [key.ARG_NAME for key in AVAILABLE_KEY_TYPES if key.ARG_NAME]
  104. def identify_key(manifest_data):
  105. key_type = manifest_data[0]
  106. if key_type == PassphraseKey.TYPE:
  107. # we just dispatch to repokey mode and assume the passphrase was migrated to a repokey.
  108. # see also comment in PassphraseKey class.
  109. return RepoKey
  110. for key in AVAILABLE_KEY_TYPES:
  111. if key.TYPE == key_type:
  112. return key
  113. else:
  114. raise UnsupportedPayloadError(key_type)
  115. def key_factory(repository, manifest_data):
  116. return identify_key(manifest_data).detect(repository, manifest_data)
  117. def tam_required_file(repository):
  118. security_dir = get_security_dir(bin_to_hex(repository.id))
  119. return os.path.join(security_dir, 'tam_required')
  120. def tam_required(repository):
  121. file = tam_required_file(repository)
  122. return os.path.isfile(file)
  123. class KeyBase:
  124. # Numeric key type ID, must fit in one byte.
  125. TYPE = None # override in subclasses
  126. # Human-readable name
  127. NAME = 'UNDEFINED'
  128. # Name used in command line / API (e.g. borg init --encryption=...)
  129. ARG_NAME = 'UNDEFINED'
  130. # Storage type (no key blob storage / keyfile / repo)
  131. STORAGE = KeyBlobStorage.NO_STORAGE
  132. # Seed for the buzhash chunker (borg.algorithms.chunker.Chunker)
  133. # type: int
  134. chunk_seed = None
  135. # Whether this *particular instance* is encrypted from a practical point of view,
  136. # i.e. when it's using encryption with an empty passphrase, then
  137. # that may be *technically* called encryption, but for all intents and purposes
  138. # that's as good as not encrypting in the first place, and this member should be False.
  139. #
  140. # The empty passphrase is also special because Borg tries it first when no passphrase
  141. # was supplied, and if an empty passphrase works, then Borg won't ask for one.
  142. logically_encrypted = False
  143. def __init__(self, repository):
  144. self.TYPE_STR = bytes([self.TYPE])
  145. self.repository = repository
  146. self.target = None # key location file path / repo obj
  147. # Some commands write new chunks (e.g. rename) but don't take a --compression argument. This duplicates
  148. # the default used by those commands who do take a --compression argument.
  149. self.compressor = Compressor('lz4')
  150. self.decompress = self.compressor.decompress
  151. self.tam_required = True
  152. def id_hash(self, data):
  153. """Return HMAC using the "id" HMAC key."""
  154. def encrypt(self, chunk):
  155. pass
  156. def decrypt(self, id, data, decompress=True):
  157. pass
  158. def assert_id(self, id, data):
  159. if id:
  160. id_computed = self.id_hash(data)
  161. if not hmac.compare_digest(id_computed, id):
  162. raise IntegrityError('Chunk %s: id verification failed' % bin_to_hex(id))
  163. def _tam_key(self, salt, context):
  164. return hkdf_hmac_sha512(
  165. ikm=self.id_key + self.enc_key + self.enc_hmac_key,
  166. salt=salt,
  167. info=b'borg-metadata-authentication-' + context,
  168. output_length=64
  169. )
  170. def pack_and_authenticate_metadata(self, metadata_dict, context=b'manifest', salt=None):
  171. if salt is None:
  172. salt = os.urandom(64)
  173. metadata_dict = StableDict(metadata_dict)
  174. tam = metadata_dict['tam'] = StableDict({
  175. 'type': 'HKDF_HMAC_SHA512',
  176. 'hmac': bytes(64),
  177. 'salt': salt,
  178. })
  179. packed = msgpack.packb(metadata_dict)
  180. tam_key = self._tam_key(salt, context)
  181. tam['hmac'] = hmac.digest(tam_key, packed, 'sha512')
  182. return msgpack.packb(metadata_dict)
  183. def unpack_and_verify_manifest(self, data, force_tam_not_required=False):
  184. """Unpack msgpacked *data* and return (object, did_verify)."""
  185. if data.startswith(b'\xc1' * 4):
  186. # This is a manifest from the future, we can't read it.
  187. raise UnsupportedManifestError()
  188. tam_required = self.tam_required
  189. if force_tam_not_required and tam_required:
  190. logger.warning('Manifest authentication DISABLED.')
  191. tam_required = False
  192. data = bytearray(data)
  193. unpacker = get_limited_unpacker('manifest')
  194. unpacker.feed(data)
  195. unpacked = unpacker.unpack()
  196. if AUTHENTICATED_NO_KEY:
  197. return unpacked, True # True is a lie.
  198. if b'tam' not in unpacked:
  199. if tam_required:
  200. raise TAMRequiredError(self.repository._location.canonical_path())
  201. else:
  202. logger.debug('Manifest TAM not found and not required')
  203. return unpacked, False
  204. tam = unpacked.pop(b'tam', None)
  205. if not isinstance(tam, dict):
  206. raise TAMInvalid()
  207. tam_type = tam.get(b'type', b'<none>').decode('ascii', 'replace')
  208. if tam_type != 'HKDF_HMAC_SHA512':
  209. if tam_required:
  210. raise TAMUnsupportedSuiteError(repr(tam_type))
  211. else:
  212. logger.debug('Ignoring manifest TAM made with unsupported suite, since TAM is not required: %r', tam_type)
  213. return unpacked, False
  214. tam_hmac = tam.get(b'hmac')
  215. tam_salt = tam.get(b'salt')
  216. if not isinstance(tam_salt, bytes) or not isinstance(tam_hmac, bytes):
  217. raise TAMInvalid()
  218. offset = data.index(tam_hmac)
  219. data[offset:offset + 64] = bytes(64)
  220. tam_key = self._tam_key(tam_salt, context=b'manifest')
  221. calculated_hmac = hmac.digest(tam_key, data, 'sha512')
  222. if not hmac.compare_digest(calculated_hmac, tam_hmac):
  223. raise TAMInvalid()
  224. logger.debug('TAM-verified manifest')
  225. return unpacked, True
  226. def unpack_and_verify_archive(self, data, force_tam_not_required=False):
  227. """Unpack msgpacked *data* and return (object, did_verify, salt)."""
  228. tam_required = self.tam_required
  229. if force_tam_not_required and tam_required:
  230. # For a long time, Borg only checked the manifest for "tam_required" and
  231. # people might have archives without TAM, so don't be too annoyingly loud here:
  232. logger.debug('Archive authentication DISABLED.')
  233. tam_required = False
  234. data = bytearray(data)
  235. unpacker = get_limited_unpacker('archive')
  236. unpacker.feed(data)
  237. unpacked = unpacker.unpack()
  238. if AUTHENTICATED_NO_KEY:
  239. return unpacked, True, None # True is a lie.
  240. if b'tam' not in unpacked:
  241. if tam_required:
  242. archive_name = unpacked.get(b'name', b'<unknown>').decode('ascii', 'replace')
  243. raise ArchiveTAMRequiredError(archive_name)
  244. else:
  245. logger.debug('Archive TAM not found and not required')
  246. return unpacked, False, None
  247. tam = unpacked.pop(b'tam', None)
  248. if not isinstance(tam, dict):
  249. raise ArchiveTAMInvalid()
  250. tam_type = tam.get(b'type', b'<none>').decode('ascii', 'replace')
  251. if tam_type != 'HKDF_HMAC_SHA512':
  252. if tam_required:
  253. raise TAMUnsupportedSuiteError(repr(tam_type))
  254. else:
  255. logger.debug('Ignoring archive TAM made with unsupported suite, since TAM is not required: %r', tam_type)
  256. return unpacked, False, None
  257. tam_hmac = tam.get(b'hmac')
  258. tam_salt = tam.get(b'salt')
  259. if not isinstance(tam_salt, bytes) or not isinstance(tam_hmac, bytes):
  260. raise ArchiveTAMInvalid()
  261. offset = data.index(tam_hmac)
  262. data[offset:offset + 64] = bytes(64)
  263. tam_key = self._tam_key(tam_salt, context=b'archive')
  264. calculated_hmac = hmac.digest(tam_key, data, 'sha512')
  265. if not hmac.compare_digest(calculated_hmac, tam_hmac):
  266. if 'ignore_invalid_archive_tam' in helpers.workarounds:
  267. logger.debug('ignoring invalid archive TAM due to BORG_WORKAROUNDS')
  268. return unpacked, False, None # same as if no TAM is present
  269. else:
  270. raise ArchiveTAMInvalid()
  271. logger.debug('TAM-verified archive')
  272. return unpacked, True, tam_salt
  273. class PlaintextKey(KeyBase):
  274. TYPE = 0x02
  275. NAME = 'plaintext'
  276. ARG_NAME = 'none'
  277. STORAGE = KeyBlobStorage.NO_STORAGE
  278. chunk_seed = 0
  279. logically_encrypted = False
  280. def __init__(self, repository):
  281. super().__init__(repository)
  282. self.tam_required = False
  283. @classmethod
  284. def create(cls, repository, args):
  285. logger.info('Encryption NOT enabled.\nUse the "--encryption=repokey|keyfile" to enable encryption.')
  286. return cls(repository)
  287. @classmethod
  288. def detect(cls, repository, manifest_data):
  289. return cls(repository)
  290. def id_hash(self, data):
  291. return sha256(data).digest()
  292. def encrypt(self, chunk):
  293. data = self.compressor.compress(chunk)
  294. return b''.join([self.TYPE_STR, data])
  295. def decrypt(self, id, data, decompress=True):
  296. if data[0] != self.TYPE:
  297. id_str = bin_to_hex(id) if id is not None else '(unknown)'
  298. raise IntegrityError('Chunk %s: Invalid encryption envelope' % id_str)
  299. payload = memoryview(data)[1:]
  300. if not decompress:
  301. return payload
  302. data = self.decompress(payload)
  303. self.assert_id(id, data)
  304. return data
  305. def _tam_key(self, salt, context):
  306. return salt + context
  307. def random_blake2b_256_key():
  308. # This might look a bit curious, but is the same construction used in the keyed mode of BLAKE2b.
  309. # Why limit the key to 64 bytes and pad it with 64 nulls nonetheless? The answer is that BLAKE2b
  310. # has a 128 byte block size, but only 64 bytes of internal state (this is also referred to as a
  311. # "local wide pipe" design, because the compression function transforms (block, state) => state,
  312. # and len(block) >= len(state), hence wide.)
  313. # In other words, a key longer than 64 bytes would have simply no advantage, since the function
  314. # has no way of propagating more than 64 bytes of entropy internally.
  315. # It's padded to a full block so that the key is never buffered internally by blake2b_update, i.e.
  316. # it remains in a single memory location that can be tracked and could be erased securely, if we
  317. # wanted to.
  318. return os.urandom(64) + bytes(64)
  319. class ID_BLAKE2b_256:
  320. """
  321. Key mix-in class for using BLAKE2b-256 for the id key.
  322. The id_key length must be 32 bytes.
  323. """
  324. def id_hash(self, data):
  325. return blake2b_256(self.id_key, data)
  326. def init_from_random_data(self, data=None):
  327. assert data is None # PassphraseKey is the only caller using *data*
  328. super().init_from_random_data()
  329. self.enc_hmac_key = random_blake2b_256_key()
  330. self.id_key = random_blake2b_256_key()
  331. class ID_HMAC_SHA_256:
  332. """
  333. Key mix-in class for using HMAC-SHA-256 for the id key.
  334. The id_key length must be 32 bytes.
  335. """
  336. def id_hash(self, data):
  337. return hmac_sha256(self.id_key, data)
  338. class AESKeyBase(KeyBase):
  339. """
  340. Common base class shared by KeyfileKey and PassphraseKey.
  341. Chunks are encrypted using 256-bit AES in Counter Mode (CTR).
  342. Payload layout: TYPE(1) + HMAC(32) + NONCE(8) + CIPHERTEXT
  343. To reduce payload size, only 8 bytes of the 16-byte nonce are saved
  344. in the payload; the first 8 bytes are always zeros. This does not
  345. affect security but limits the maximum repository capacity to
  346. only 295 exabytes!
  347. """
  348. PAYLOAD_OVERHEAD = 1 + 32 + 8 # TYPE + HMAC + NONCE
  349. CIPHERSUITE = AES256_CTR_HMAC_SHA256
  350. logically_encrypted = True
  351. def encrypt(self, chunk):
  352. data = self.compressor.compress(chunk)
  353. next_iv = self.nonce_manager.ensure_reservation(self.cipher.next_iv(),
  354. self.cipher.block_count(len(data)))
  355. return self.cipher.encrypt(data, header=self.TYPE_STR, iv=next_iv)
  356. def decrypt(self, id, data, decompress=True):
  357. if not (data[0] == self.TYPE or
  358. data[0] == PassphraseKey.TYPE and isinstance(self, RepoKey)):
  359. id_str = bin_to_hex(id) if id is not None else '(unknown)'
  360. raise IntegrityError('Chunk %s: Invalid encryption envelope' % id_str)
  361. try:
  362. payload = self.cipher.decrypt(data)
  363. except IntegrityError as e:
  364. raise IntegrityError(f"Chunk {bin_to_hex(id)}: Could not decrypt [{str(e)}]")
  365. if not decompress:
  366. return payload
  367. data = self.decompress(payload)
  368. self.assert_id(id, data)
  369. return data
  370. def init_from_random_data(self, data=None):
  371. if data is None:
  372. data = os.urandom(100)
  373. self.enc_key = data[0:32]
  374. self.enc_hmac_key = data[32:64]
  375. self.id_key = data[64:96]
  376. self.chunk_seed = bytes_to_int(data[96:100])
  377. # Convert to signed int32
  378. if self.chunk_seed & 0x80000000:
  379. self.chunk_seed = self.chunk_seed - 0xffffffff - 1
  380. def init_ciphers(self, manifest_data=None):
  381. self.cipher = self.CIPHERSUITE(mac_key=self.enc_hmac_key, enc_key=self.enc_key, header_len=1, aad_offset=1)
  382. if manifest_data is None:
  383. nonce = 0
  384. else:
  385. if not (manifest_data[0] == self.TYPE or
  386. manifest_data[0] == PassphraseKey.TYPE and isinstance(self, RepoKey)):
  387. raise IntegrityError('Manifest: Invalid encryption envelope')
  388. # manifest_blocks is a safe upper bound on the amount of cipher blocks needed
  389. # to encrypt the manifest. depending on the ciphersuite and overhead, it might
  390. # be a bit too high, but that does not matter.
  391. manifest_blocks = num_cipher_blocks(len(manifest_data))
  392. nonce = self.cipher.extract_iv(manifest_data) + manifest_blocks
  393. self.cipher.set_iv(nonce)
  394. self.nonce_manager = NonceManager(self.repository, nonce)
  395. class Passphrase(str):
  396. @classmethod
  397. def _env_passphrase(cls, env_var, default=None):
  398. passphrase = os.environ.get(env_var, default)
  399. if passphrase is not None:
  400. return cls(passphrase)
  401. @classmethod
  402. def env_passphrase(cls, default=None):
  403. passphrase = cls._env_passphrase('BORG_PASSPHRASE', default)
  404. if passphrase is not None:
  405. return passphrase
  406. passphrase = cls.env_passcommand()
  407. if passphrase is not None:
  408. return passphrase
  409. passphrase = cls.fd_passphrase()
  410. if passphrase is not None:
  411. return passphrase
  412. @classmethod
  413. def env_passcommand(cls, default=None):
  414. passcommand = os.environ.get('BORG_PASSCOMMAND', None)
  415. if passcommand is not None:
  416. # The passcommand is a system command (not inside the PyInstaller environment)
  417. env = prepare_subprocess_env(system=True)
  418. try:
  419. passphrase = subprocess.check_output(shlex.split(passcommand), text=True, env=env)
  420. except (subprocess.CalledProcessError, FileNotFoundError) as e:
  421. raise PasscommandFailure(e)
  422. return cls(passphrase.rstrip('\n'))
  423. @classmethod
  424. def fd_passphrase(cls):
  425. try:
  426. fd = int(os.environ.get('BORG_PASSPHRASE_FD'))
  427. except (ValueError, TypeError):
  428. return None
  429. with os.fdopen(fd, mode='r') as f:
  430. passphrase = f.read()
  431. return cls(passphrase.rstrip('\n'))
  432. @classmethod
  433. def env_new_passphrase(cls, default=None):
  434. return cls._env_passphrase('BORG_NEW_PASSPHRASE', default)
  435. @classmethod
  436. def getpass(cls, prompt):
  437. try:
  438. pw = getpass.getpass(prompt)
  439. except EOFError:
  440. if prompt:
  441. print() # avoid err msg appearing right of prompt
  442. msg = []
  443. for env_var in 'BORG_PASSPHRASE', 'BORG_PASSCOMMAND':
  444. env_var_set = os.environ.get(env_var) is not None
  445. msg.append('{} is {}.'.format(env_var, 'set' if env_var_set else 'not set'))
  446. msg.append('Interactive password query failed.')
  447. raise NoPassphraseFailure(' '.join(msg)) from None
  448. else:
  449. return cls(pw)
  450. @classmethod
  451. def verification(cls, passphrase):
  452. msg = 'Do you want your passphrase to be displayed for verification? [yN]: '
  453. if yes(msg, retry_msg=msg, invalid_msg='Invalid answer, try again.',
  454. retry=True, env_var_override='BORG_DISPLAY_PASSPHRASE'):
  455. print('Your passphrase (between double-quotes): "%s"' % passphrase,
  456. file=sys.stderr)
  457. print('Make sure the passphrase displayed above is exactly what you wanted.',
  458. file=sys.stderr)
  459. try:
  460. passphrase.encode('ascii')
  461. except UnicodeEncodeError:
  462. print('Your passphrase (UTF-8 encoding in hex): %s' %
  463. bin_to_hex(passphrase.encode('utf-8')),
  464. file=sys.stderr)
  465. 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.',
  466. file=sys.stderr)
  467. @classmethod
  468. def new(cls, allow_empty=False):
  469. passphrase = cls.env_new_passphrase()
  470. if passphrase is not None:
  471. return passphrase
  472. passphrase = cls.env_passphrase()
  473. if passphrase is not None:
  474. return passphrase
  475. for retry in range(1, 11):
  476. passphrase = cls.getpass('Enter new passphrase: ')
  477. if allow_empty or passphrase:
  478. passphrase2 = cls.getpass('Enter same passphrase again: ')
  479. if passphrase == passphrase2:
  480. cls.verification(passphrase)
  481. logger.info('Remember your passphrase. Your data will be inaccessible without it.')
  482. return passphrase
  483. else:
  484. print('Passphrases do not match', file=sys.stderr)
  485. else:
  486. print('Passphrase must not be blank', file=sys.stderr)
  487. else:
  488. raise PasswordRetriesExceeded
  489. def __repr__(self):
  490. return '<Passphrase "***hidden***">'
  491. def kdf(self, salt, iterations, length):
  492. return pbkdf2_hmac('sha256', self.encode('utf-8'), salt, iterations, length)
  493. class PassphraseKey(ID_HMAC_SHA_256, AESKeyBase):
  494. # This mode was killed in borg 1.0, see: https://github.com/borgbackup/borg/issues/97
  495. # Reasons:
  496. # - you can never ever change your passphrase for existing repos.
  497. # - you can never ever use a different iterations count for existing repos.
  498. # "Killed" means:
  499. # - there is no automatic dispatch to this class via type byte
  500. # - --encryption=passphrase is an invalid argument now
  501. # This class is kept for a while to support migration from passphrase to repokey mode.
  502. TYPE = 0x01
  503. NAME = 'passphrase'
  504. ARG_NAME = None
  505. STORAGE = KeyBlobStorage.NO_STORAGE
  506. iterations = 100000 # must not be changed ever!
  507. @classmethod
  508. def create(cls, repository, args):
  509. key = cls(repository)
  510. logger.warning('WARNING: "passphrase" mode is unsupported since borg 1.0.')
  511. passphrase = Passphrase.new(allow_empty=False)
  512. key.init(repository, passphrase)
  513. return key
  514. @classmethod
  515. def detect(cls, repository, manifest_data):
  516. prompt = 'Enter passphrase for %s: ' % repository._location.canonical_path()
  517. key = cls(repository)
  518. passphrase = Passphrase.env_passphrase()
  519. if passphrase is None:
  520. passphrase = Passphrase.getpass(prompt)
  521. for retry in range(1, 3):
  522. key.init(repository, passphrase)
  523. try:
  524. key.decrypt(None, manifest_data)
  525. key.init_ciphers(manifest_data)
  526. key._passphrase = passphrase
  527. return key
  528. except IntegrityError:
  529. passphrase = Passphrase.getpass(prompt)
  530. else:
  531. raise PasswordRetriesExceeded
  532. def change_passphrase(self):
  533. class ImmutablePassphraseError(Error):
  534. """The passphrase for this encryption key type can't be changed."""
  535. raise ImmutablePassphraseError
  536. def init(self, repository, passphrase):
  537. self.init_from_random_data(passphrase.kdf(repository.id, self.iterations, 100))
  538. self.init_ciphers()
  539. self.tam_required = False
  540. class KeyfileKeyBase(AESKeyBase):
  541. @classmethod
  542. def detect(cls, repository, manifest_data):
  543. key = cls(repository)
  544. target = key.find_key()
  545. prompt = 'Enter passphrase for key %s: ' % target
  546. passphrase = Passphrase.env_passphrase()
  547. if passphrase is None:
  548. passphrase = Passphrase()
  549. if not key.load(target, passphrase):
  550. for retry in range(0, 3):
  551. passphrase = Passphrase.getpass(prompt)
  552. if key.load(target, passphrase):
  553. break
  554. else:
  555. raise PasswordRetriesExceeded
  556. else:
  557. if not key.load(target, passphrase):
  558. raise PassphraseWrong
  559. key.init_ciphers(manifest_data)
  560. key._passphrase = passphrase
  561. return key
  562. def find_key(self):
  563. raise NotImplementedError
  564. def load(self, target, passphrase):
  565. raise NotImplementedError
  566. def _load(self, key_data, passphrase):
  567. try:
  568. key = binascii.a2b_base64(key_data)
  569. except (ValueError, binascii.Error):
  570. raise KeyfileInvalidError(self.repository._location.canonical_path(), "(repokey)") from None
  571. if len(key) < 20:
  572. # this is in no way a precise check, usually we have about 400b key data.
  573. raise KeyfileInvalidError(self.repository._location.canonical_path(), "(repokey)")
  574. data = self.decrypt_key_file(key, passphrase)
  575. if data:
  576. data = msgpack.unpackb(data)
  577. key = Key(internal_dict=data)
  578. if key.version != 1:
  579. raise Error("key version %d is not supported by this borg version.")
  580. self.repository_id = key.repository_id
  581. self.enc_key = key.enc_key
  582. self.enc_hmac_key = key.enc_hmac_key
  583. self.id_key = key.id_key
  584. self.chunk_seed = key.chunk_seed
  585. self.tam_required = key.get('tam_required', tam_required(self.repository))
  586. return True
  587. return False
  588. def decrypt_key_file(self, data, passphrase):
  589. unpacker = get_limited_unpacker('key')
  590. unpacker.feed(data)
  591. data = unpacker.unpack()
  592. enc_key = EncryptedKey(internal_dict=data)
  593. if enc_key.version != 1:
  594. raise Error("encrypted key version %d is not supported by this borg version." % enc_key.version)
  595. if enc_key.algorithm != 'sha256':
  596. raise Error("encrypted key algorithm '%s' is not supported by this borg version." % enc_key.algorithm)
  597. key = passphrase.kdf(enc_key.salt, enc_key.iterations, 32)
  598. data = AES(key, b'\0'*16).decrypt(enc_key.data)
  599. if hmac.compare_digest(hmac_sha256(key, data), enc_key.hash):
  600. return data
  601. def encrypt_key_file(self, data, passphrase):
  602. salt = os.urandom(32)
  603. iterations = PBKDF2_ITERATIONS
  604. key = passphrase.kdf(salt, iterations, 32)
  605. hash = hmac_sha256(key, data)
  606. cdata = AES(key, b'\0'*16).encrypt(data)
  607. enc_key = EncryptedKey(
  608. version=1,
  609. salt=salt,
  610. iterations=iterations,
  611. algorithm='sha256',
  612. hash=hash,
  613. data=cdata,
  614. )
  615. return msgpack.packb(enc_key.as_dict())
  616. def _save(self, passphrase):
  617. key = Key(
  618. version=1,
  619. repository_id=self.repository_id,
  620. enc_key=self.enc_key,
  621. enc_hmac_key=self.enc_hmac_key,
  622. id_key=self.id_key,
  623. chunk_seed=self.chunk_seed,
  624. tam_required=self.tam_required,
  625. )
  626. data = self.encrypt_key_file(msgpack.packb(key.as_dict()), passphrase)
  627. key_data = '\n'.join(textwrap.wrap(binascii.b2a_base64(data).decode('ascii')))
  628. return key_data
  629. def change_passphrase(self, passphrase=None):
  630. if passphrase is None:
  631. passphrase = Passphrase.new(allow_empty=True)
  632. self.save(self.target, passphrase)
  633. @classmethod
  634. def create(cls, repository, args):
  635. passphrase = Passphrase.new(allow_empty=True)
  636. key = cls(repository)
  637. key.repository_id = repository.id
  638. key.init_from_random_data()
  639. key.init_ciphers()
  640. target = key.get_new_target(args)
  641. key.save(target, passphrase, create=True)
  642. logger.info('Key in "%s" created.' % target)
  643. logger.info('Keep this key safe. Your data will be inaccessible without it.')
  644. return key
  645. def save(self, target, passphrase, create=False):
  646. raise NotImplementedError
  647. def get_new_target(self, args):
  648. raise NotImplementedError
  649. class KeyfileKey(ID_HMAC_SHA_256, KeyfileKeyBase):
  650. TYPE = 0x00
  651. NAME = 'key file'
  652. ARG_NAME = 'keyfile'
  653. STORAGE = KeyBlobStorage.KEYFILE
  654. FILE_ID = 'BORG_KEY'
  655. def sanity_check(self, filename, id):
  656. file_id = self.FILE_ID.encode() + b' '
  657. repo_id = bin_to_hex(id).encode('ascii')
  658. with open(filename, 'rb') as fd:
  659. # we do the magic / id check in binary mode to avoid stumbling over
  660. # decoding errors if somebody has binary files in the keys dir for some reason.
  661. if fd.read(len(file_id)) != file_id:
  662. raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
  663. if fd.read(len(repo_id)) != repo_id:
  664. raise KeyfileMismatchError(self.repository._location.canonical_path(), filename)
  665. # we get here if it really looks like a borg key for this repo,
  666. # do some more checks that are close to how borg reads/parses the key.
  667. with open(filename) as fd:
  668. lines = fd.readlines()
  669. if len(lines) < 2:
  670. logger.warning(f"borg key sanity check: expected 2+ lines total. [{filename}]")
  671. raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
  672. if len(lines[0].rstrip()) > len(file_id) + len(repo_id):
  673. logger.warning(f"borg key sanity check: key line 1 seems too long. [{filename}]")
  674. raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
  675. key_b64 = ''.join(lines[1:])
  676. try:
  677. key = binascii.a2b_base64(key_b64)
  678. except (ValueError, binascii.Error):
  679. logger.warning(f"borg key sanity check: key line 2+ does not look like base64. [{filename}]")
  680. raise KeyfileInvalidError(self.repository._location.canonical_path(), filename) from None
  681. if len(key) < 20:
  682. # this is in no way a precise check, usually we have about 400b key data.
  683. logger.warning(f"borg key sanity check: binary encrypted key data from key line 2+ suspiciously short."
  684. f" [{filename}]")
  685. raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
  686. # looks good!
  687. return filename
  688. def find_key(self):
  689. keyfile = self._find_key_file_from_environment()
  690. if keyfile is not None:
  691. return self.sanity_check(keyfile, self.repository.id)
  692. keyfile = self._find_key_in_keys_dir()
  693. if keyfile is not None:
  694. return keyfile
  695. raise KeyfileNotFoundError(self.repository._location.canonical_path(), get_keys_dir())
  696. def get_existing_or_new_target(self, args):
  697. keyfile = self._find_key_file_from_environment()
  698. if keyfile is not None:
  699. return keyfile
  700. keyfile = self._find_key_in_keys_dir()
  701. if keyfile is not None:
  702. return keyfile
  703. return self._get_new_target_in_keys_dir(args)
  704. def _find_key_in_keys_dir(self):
  705. id = self.repository.id
  706. keys_dir = get_keys_dir()
  707. for name in os.listdir(keys_dir):
  708. filename = os.path.join(keys_dir, name)
  709. try:
  710. return self.sanity_check(filename, id)
  711. except (KeyfileInvalidError, KeyfileMismatchError):
  712. pass
  713. def get_new_target(self, args):
  714. keyfile = self._find_key_file_from_environment()
  715. if keyfile is not None:
  716. return keyfile
  717. return self._get_new_target_in_keys_dir(args)
  718. def _find_key_file_from_environment(self):
  719. keyfile = os.environ.get('BORG_KEY_FILE')
  720. if keyfile:
  721. return os.path.abspath(keyfile)
  722. def _get_new_target_in_keys_dir(self, args):
  723. filename = args.location.to_key_filename()
  724. path = filename
  725. i = 1
  726. while os.path.exists(path):
  727. i += 1
  728. path = filename + '.%d' % i
  729. return path
  730. def load(self, target, passphrase):
  731. with open(target) as fd:
  732. key_data = ''.join(fd.readlines()[1:])
  733. success = self._load(key_data, passphrase)
  734. if success:
  735. self.target = target
  736. return success
  737. def save(self, target, passphrase, create=False):
  738. if create and os.path.isfile(target):
  739. # if a new keyfile key repository is created, ensure that an existing keyfile of another
  740. # keyfile key repo is not accidentally overwritten by careless use of the BORG_KEY_FILE env var.
  741. # see issue #6036
  742. raise Error('Aborting because key in "%s" already exists.' % target)
  743. key_data = self._save(passphrase)
  744. with SaveFile(target) as fd:
  745. fd.write(f'{self.FILE_ID} {bin_to_hex(self.repository_id)}\n')
  746. fd.write(key_data)
  747. fd.write('\n')
  748. self.target = target
  749. class RepoKey(ID_HMAC_SHA_256, KeyfileKeyBase):
  750. TYPE = 0x03
  751. NAME = 'repokey'
  752. ARG_NAME = 'repokey'
  753. STORAGE = KeyBlobStorage.REPO
  754. def find_key(self):
  755. loc = self.repository._location.canonical_path()
  756. key = self.repository.load_key()
  757. if not key:
  758. # if we got an empty key, it means there is no key.
  759. raise RepoKeyNotFoundError(loc) from None
  760. return loc
  761. def get_new_target(self, args):
  762. return self.repository
  763. def load(self, target, passphrase):
  764. # While the repository is encrypted, we consider a repokey repository with a blank
  765. # passphrase an unencrypted repository.
  766. self.logically_encrypted = passphrase != ''
  767. # what we get in target is just a repo location, but we already have the repo obj:
  768. target = self.repository
  769. key_data = target.load_key()
  770. if not key_data:
  771. # if we got an empty key, it means there is no key.
  772. loc = target._location.canonical_path()
  773. raise RepoKeyNotFoundError(loc) from None
  774. key_data = key_data.decode('utf-8') # remote repo: msgpack issue #99, getting bytes
  775. success = self._load(key_data, passphrase)
  776. if success:
  777. self.target = target
  778. return success
  779. def save(self, target, passphrase, create=False):
  780. self.logically_encrypted = passphrase != ''
  781. key_data = self._save(passphrase)
  782. key_data = key_data.encode('utf-8') # remote repo: msgpack issue #99, giving bytes
  783. target.save_key(key_data)
  784. self.target = target
  785. class Blake2KeyfileKey(ID_BLAKE2b_256, KeyfileKey):
  786. TYPE = 0x04
  787. NAME = 'key file BLAKE2b'
  788. ARG_NAME = 'keyfile-blake2'
  789. STORAGE = KeyBlobStorage.KEYFILE
  790. FILE_ID = 'BORG_KEY'
  791. CIPHERSUITE = AES256_CTR_BLAKE2b
  792. class Blake2RepoKey(ID_BLAKE2b_256, RepoKey):
  793. TYPE = 0x05
  794. NAME = 'repokey BLAKE2b'
  795. ARG_NAME = 'repokey-blake2'
  796. STORAGE = KeyBlobStorage.REPO
  797. CIPHERSUITE = AES256_CTR_BLAKE2b
  798. class AuthenticatedKeyBase(RepoKey):
  799. STORAGE = KeyBlobStorage.REPO
  800. # It's only authenticated, not encrypted.
  801. logically_encrypted = False
  802. def _load(self, key_data, passphrase):
  803. if AUTHENTICATED_NO_KEY:
  804. # fake _load if we have no key or passphrase
  805. NOPE = bytes(32) # 256 bit all-zero
  806. self.repository_id = NOPE
  807. self.enc_key = NOPE
  808. self.enc_hmac_key = NOPE
  809. self.id_key = NOPE
  810. self.chunk_seed = 0
  811. self.tam_required = False
  812. return True
  813. return super()._load(key_data, passphrase)
  814. def load(self, target, passphrase):
  815. success = super().load(target, passphrase)
  816. self.logically_encrypted = False
  817. return success
  818. def save(self, target, passphrase, create=False):
  819. super().save(target, passphrase, create=create)
  820. self.logically_encrypted = False
  821. def init_ciphers(self, manifest_data=None):
  822. if manifest_data is not None and manifest_data[0] != self.TYPE:
  823. raise IntegrityError('Manifest: Invalid encryption envelope')
  824. def encrypt(self, chunk):
  825. data = self.compressor.compress(chunk)
  826. return b''.join([self.TYPE_STR, data])
  827. def decrypt(self, id, data, decompress=True):
  828. if data[0] != self.TYPE:
  829. id_str = bin_to_hex(id) if id is not None else '(unknown)'
  830. raise IntegrityError('Chunk %s: Invalid envelope' % id_str)
  831. payload = memoryview(data)[1:]
  832. if not decompress:
  833. return payload
  834. data = self.decompress(payload)
  835. if AUTHENTICATED_NO_KEY:
  836. return data
  837. self.assert_id(id, data)
  838. return data
  839. class AuthenticatedKey(AuthenticatedKeyBase):
  840. TYPE = 0x07
  841. NAME = 'authenticated'
  842. ARG_NAME = 'authenticated'
  843. class Blake2AuthenticatedKey(ID_BLAKE2b_256, AuthenticatedKeyBase):
  844. TYPE = 0x06
  845. NAME = 'authenticated BLAKE2b'
  846. ARG_NAME = 'authenticated-blake2'
  847. AVAILABLE_KEY_TYPES = (
  848. PlaintextKey,
  849. PassphraseKey,
  850. KeyfileKey, RepoKey, AuthenticatedKey,
  851. Blake2KeyfileKey, Blake2RepoKey, Blake2AuthenticatedKey,
  852. )