Fix SSH key loading for ed25519 and ECDSA keys

Unencrypted ed25519 and ECDSA keys crashed with an uncaught
SSHException, and the DSA fallback raised NameError since paramiko 4
removed DSSKey. Load the key with paramiko.PKey.from_path instead,
ask for the passphrase only when the key is encrypted, and log an
error instead of crashing when the key can't be loaded.

Require paramiko >= 3.2 for PKey.from_path.
This commit is contained in:
Fuxino
2026-10-08 20:06:57 +02:00
parent 2f207efcf9
commit 0671f86579
2 changed files with 34 additions and 42 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ classifiers = [
[project.optional-dependencies]
JOURNAL = ["systemd-python"]
NOTIFICATIONS = ["dbus-python"]
REMOTE = ["paramiko"]
REMOTE = ["paramiko>=3.2"]
[project.urls]
Homepage = "https://git.shouldnt.work/fuxino/simple_backup"
+33 -41
View File
@@ -36,7 +36,6 @@ from glob import glob
try:
import paramiko
from paramiko import RSAKey, Ed25519Key, ECDSAKey, DSSKey
except ImportError:
pass
@@ -397,48 +396,10 @@ class Backup:
return None
pkey = None
try:
pkey = RSAKey.from_private_key_file(self.ssh_keyfile)
except paramiko.PasswordRequiredException:
password = getpass(
f'Enter passwphrase for key \'{self.ssh_keyfile}\': ')
try:
pkey = RSAKey.from_private_key_file(self.ssh_keyfile, password)
except paramiko.SSHException:
pass
pkey = self._load_ssh_key()
if pkey is None:
try:
pkey = Ed25519Key.from_private_key_file(self.ssh_keyfile)
except paramiko.PasswordRequiredException:
try:
pkey = Ed25519Key.from_private_key_file(
self.ssh_keyfile, password)
except paramiko.SSHException:
pass
if pkey is None:
try:
pkey = ECDSAKey.from_private_key_file(self.ssh_keyfile)
except paramiko.PasswordRequiredException:
try:
pkey = ECDSAKey.from_private_key_file(
self.ssh_keyfile, password)
except paramiko.SSHException:
pass
if pkey is None:
try:
pkey = DSSKey.from_private_key_file(self.ssh_keyfile)
except paramiko.PasswordRequiredException:
try:
pkey = DSSKey.from_private_key_file(
self.ssh_keyfile, password)
except paramiko.SSHException:
pass
return None
try:
ssh.connect(self.ssh_host, username=self.ssh_user, pkey=pkey)
@@ -449,6 +410,37 @@ class Backup:
return ssh
def _load_ssh_key(self) -> Optional[paramiko.PKey]:
try:
return paramiko.PKey.from_path(self.ssh_keyfile)
except TypeError:
# Raised when the key is encrypted and no passphrase was given
pass
except (OSError, ValueError, paramiko.UnknownKeyType) as e:
logger.critical('Unable to load SSH key %s', self.ssh_keyfile)
logger.critical(e)
return None
try:
# Fail instead of reading the passphrase with echo if no terminal is available
with warnings.catch_warnings():
warnings.simplefilter('error', GetPassWarning)
passphrase = getpass(
f'Enter passphrase for key \'{self.ssh_keyfile}\': ')
except GetPassWarning as e:
logger.critical('Unable to get passphrase')
logger.critical(e)
return None
try:
return paramiko.PKey.from_path(self.ssh_keyfile, passphrase.encode())
except (ValueError, paramiko.SSHException):
logger.critical('Unable to load SSH key %s, wrong passphrase?', self.ssh_keyfile)
return None
def _returncode_log(self, returncode: int) -> None:
match returncode:
case 2: