Skip to content

Commit

Permalink
Implement optional passphrase debugging
Browse files Browse the repository at this point in the history
- Displaying passphrase debugging information only when the `BORG_DEBUG_PASSPHRASE` environment variable is set to "YES".
- If the env var is not set or set to a value other than "YES", debugging info will not be displayed.
  • Loading branch information
alighazi288 committed Dec 30, 2024
1 parent 44466e1 commit 6dae920
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 38 deletions.
7 changes: 5 additions & 2 deletions src/borg/crypto/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,20 +364,23 @@ def detect(cls, repository, manifest_data):
target = key.find_key()
prompt = "Enter passphrase for key %s: " % target
passphrase = Passphrase.env_passphrase()
debug_enabled = os.environ.get("BORG_DEBUG_PASSPHRASE") == "YES"
if passphrase is None:
passphrase = Passphrase()
if not key.load(target, passphrase):
for retry in range(0, 3):
passphrase = Passphrase.getpass(prompt)
if key.load(target, passphrase):
break
else:
elif debug_enabled:
Passphrase.display_debug_info(passphrase)

Check warning on line 376 in src/borg/crypto/key.py

View check run for this annotation

Codecov / codecov/patch

src/borg/crypto/key.py#L376

Added line #L376 was not covered by tests
else:
raise PasswordRetriesExceeded
else:
if not key.load(target, passphrase):
raise PassphraseWrong(passphrase)
if debug_enabled:
Passphrase.display_debug_info(passphrase)

Check warning on line 382 in src/borg/crypto/key.py

View check run for this annotation

Codecov / codecov/patch

src/borg/crypto/key.py#L382

Added line #L382 was not covered by tests
raise PassphraseWrong
key.init_ciphers(manifest_data)
key._passphrase = passphrase
return key
Expand Down
51 changes: 19 additions & 32 deletions src/borg/helpers/passphrase.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ class PassphraseWrong(Error):

exit_mcode = 52

def __init__(self, passphrase):
super().__init__(self)
Passphrase.display_debug_info(passphrase)


class PasswordRetriesExceeded(Error):
"""exceeded the maximum password retries"""
Expand Down Expand Up @@ -113,37 +109,28 @@ def verification(cls, passphrase):
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)
print(
"Your passphrase (UTF-8 encoding in hex): %s" % bin_to_hex(passphrase.encode("utf-8")), file=sys.stderr
)
print(
"It is recommended to keep the UTF-8 encoding in hex together with the passphrase at a safe place. "
"In case you should ever run into passphrase issues, it could sometimes help debugging them.\n",
file=sys.stderr,
)
try:
passphrase.encode("ascii")
except UnicodeEncodeError:
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,
)
pw_msg = f"""
Your passphrase (between double-quotes): "{passphrase}"
Make sure the passphrase displayed above is exactly what you wanted.
Your passphrase (UTF-8 encoding in hex): {bin_to_hex(passphrase.encode("utf-8"))}
It is recommended to keep the UTF-8 encoding in hex together with the passphrase at a safe place.
In case you should ever run into passphrase issues, it could sometimes help debugging them.
"""
print(pw_msg, file=sys.stderr)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.

@staticmethod
def display_debug_info(passphrase):
print("Incorrect passphrase!", file=sys.stderr)
print(f'Passphrase used (between double-quotes): "{passphrase}"', file=sys.stderr)
print(f'Same, UTF-8 encoded, in hex: {bin_to_hex(passphrase.encode("utf-8"))}', file=sys.stderr)
print("Relevant Environment Variables:", file=sys.stderr)
for env_var in ["BORG_PASSPHRASE", "BORG_PASSCOMMAND", "BORG_PASSPHRASE_FD"]:
env_var_value = os.environ.get(env_var)
if env_var_value is not None:
print(f'{env_var} = "{env_var_value}"', file=sys.stderr)
else:
print(f"# {env_var} is not set", file=sys.stderr)
if os.environ.get("BORG_DEBUG_PASSPHRASE") == "YES":
print("Incorrect passphrase!", file=sys.stderr)
print(f'Passphrase used (between double-quotes): "{passphrase}"', file=sys.stderr)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.
print(f'Same, UTF-8 encoded, in hex: {bin_to_hex(passphrase.encode("utf-8"))}', file=sys.stderr)
print("Relevant Environment Variables:", file=sys.stderr)
for env_var in ["BORG_PASSPHRASE", "BORG_PASSCOMMAND", "BORG_PASSPHRASE_FD"]:
env_var_value = os.environ.get(env_var)
if env_var_value is not None:
print(f'{env_var} = "{env_var_value}"', file=sys.stderr)
else:
print(f"# {env_var} is not set", file=sys.stderr)

Check warning on line 133 in src/borg/helpers/passphrase.py

View check run for this annotation

Codecov / codecov/patch

src/borg/helpers/passphrase.py#L133

Added line #L133 was not covered by tests

@classmethod
def new(cls, allow_empty=False):
Expand Down
30 changes: 26 additions & 4 deletions src/borg/testsuite/helpers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1408,12 +1408,14 @@ def test_passphrase_new_retries(self, monkeypatch):
def test_passphrase_repr(self):
assert "secret" not in repr(Passphrase("secret"))

def test_passphrase_wrong(self, capsys, monkeypatch):
def test_passphrase_wrong_debug_enabled(self, capsys, monkeypatch):
monkeypatch.setenv("BORG_DEBUG_PASSPHRASE", "YES")
monkeypatch.setenv("BORG_PASSPHRASE", "wrong_passphrase")
monkeypatch.setenv("BORG_PASSCOMMAND", "echo wrong_passphrase")
monkeypatch.setenv("BORG_PASSPHRASE_FD", "123")

with pytest.raises(PassphraseWrong) as exc_info:
Passphrase.display_debug_info("wrong_passphrase")
raise PassphraseWrong("wrong_passphrase")

out, err = capsys.readouterr()
Expand All @@ -1428,6 +1430,17 @@ def test_passphrase_wrong(self, capsys, monkeypatch):
"passphrase supplied in BORG_PASSPHRASE, by BORG_PASSCOMMAND or via BORG_PASSPHRASE_FD is incorrect."
)

def test_passphrase_wrong_debug_disabled(self, capsys, monkeypatch):
monkeypatch.delenv("BORG_DEBUG_PASSPHRASE", raising=False)

with pytest.raises(PassphraseWrong):
Passphrase.display_debug_info("wrong_passphrase")
raise PassphraseWrong("wrong_passphrase")

out, err = capsys.readouterr()
assert "Incorrect passphrase!" not in err
assert 'Passphrase used (between double-quotes): "wrong_passphrase"' not in err

def test_verification(self, capsys, monkeypatch):
passphrase = "test_passphrase"

Expand All @@ -1451,6 +1464,7 @@ def test_verification(self, capsys, monkeypatch):

def test_display_debug_info(self, capsys, monkeypatch):
passphrase = "debug_test"
monkeypatch.setenv("BORG_DEBUG_PASSPHRASE", "YES")
monkeypatch.setenv("BORG_PASSPHRASE", "debug_env_passphrase")
monkeypatch.setenv("BORG_PASSCOMMAND", "command")
monkeypatch.setenv("BORG_PASSPHRASE_FD", "fd_value")
Expand All @@ -1461,9 +1475,17 @@ def test_display_debug_info(self, capsys, monkeypatch):
assert "Incorrect passphrase!" in err
assert 'Passphrase used (between double-quotes): "debug_test"' in err
assert "64656275675f74657374" in err # UTF-8 hex encoding of 'debug_test'
assert "BORG_PASSPHRASE" in err
assert "BORG_PASSCOMMAND" in err
assert "BORG_PASSPHRASE_FD" in err
assert 'BORG_PASSPHRASE = "debug_env_passphrase"' in err
assert 'BORG_PASSCOMMAND = "command"' in err
assert 'BORG_PASSPHRASE_FD = "fd_value"' in err

monkeypatch.delenv("BORG_DEBUG_PASSPHRASE", raising=False)

Passphrase.display_debug_info(passphrase)
out, err = capsys.readouterr()

assert "Incorrect passphrase!" not in err
assert 'Passphrase used (between double-quotes): "debug_test"' not in err


@pytest.mark.parametrize(
Expand Down

0 comments on commit 6dae920

Please sign in to comment.