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:
+1
-1
@@ -33,7 +33,7 @@ classifiers = [
|
|||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
JOURNAL = ["systemd-python"]
|
JOURNAL = ["systemd-python"]
|
||||||
NOTIFICATIONS = ["dbus-python"]
|
NOTIFICATIONS = ["dbus-python"]
|
||||||
REMOTE = ["paramiko"]
|
REMOTE = ["paramiko>=3.2"]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://git.shouldnt.work/fuxino/simple_backup"
|
Homepage = "https://git.shouldnt.work/fuxino/simple_backup"
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ from glob import glob
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import paramiko
|
import paramiko
|
||||||
from paramiko import RSAKey, Ed25519Key, ECDSAKey, DSSKey
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -397,48 +396,10 @@ class Backup:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
pkey = None
|
pkey = self._load_ssh_key()
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
if pkey is None:
|
if pkey is None:
|
||||||
try:
|
return None
|
||||||
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
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ssh.connect(self.ssh_host, username=self.ssh_user, pkey=pkey)
|
ssh.connect(self.ssh_host, username=self.ssh_user, pkey=pkey)
|
||||||
@@ -449,6 +410,37 @@ class Backup:
|
|||||||
|
|
||||||
return ssh
|
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:
|
def _returncode_log(self, returncode: int) -> None:
|
||||||
match returncode:
|
match returncode:
|
||||||
case 2:
|
case 2:
|
||||||
|
|||||||
Reference in New Issue
Block a user