commit ccb40d2bed2178d22049f1b98344b2f026d15c48
parent 3b649c030501b0edab31f19beac570ea1c533bff
Author: Sheng <webmaster0115@gmail.com>
Date: Wed, 11 Dec 2019 09:32:42 +0800
Support openssh new format key
Diffstat:
4 files changed, 105 insertions(+), 16 deletions(-)
diff --git a/tests/data/test_new_dsa.key b/tests/data/test_new_dsa.key
@@ -0,0 +1,21 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdzc2gtZH
+NzAAAAgQC5Y5rQ1EN+eWQUFv/9K/DLfPgjGC0mwyqvKsKyv6RLpKLc0vi0VDj8lY0WUcuG
+CzdYnhIOSa9aB0buGe10gIjU2vAxkhqv1yaR+Zuj3dLDHQk6jpAAgNHciKlQSf1zho/seL
+7nehYq/waXfU8/iJuXqywQgqpMLfaHOnIl/tPLGQAAABUArINMjWcrsmEgLmzf6k+sroko
+5GkAAACAMQsRQjOtQGQA8/XI7vOWnEMCVntwt1Xi4RsLH5+4GpUMUcm4CvqjfFfSF4CufH
+pjlywFhrAC2/ouQIpGJPGToWotk7dt5zWckGX5DscMiRVON7fxdpUMn16IO6DdUctXlWa9
+SY+NdfRESKoUCjgH5nlM8k7N2MwCK5phHHkoPu8AAACADgxrRWeNqX3gmZUM1qhrDO0mOH
+oHJFrBuvJCdQ6+S1GvjuBI0rNm225+gcaAhia9k/LGk8NwCbWG1FbpesuNaNFt/FxS9LVS
+qEaZoXtKuY+CUCn1BfBWF97/u0oMPwanXKIJEAhU81f5TXZM8Ui7OEIyTx1t9qgva+5/gF
+cL48kAAAHoLtDYCy7Q2AsAAAAHc3NoLWRzcwAAAIEAuWOa0NRDfnlkFBb//Svwy3z4Ixgt
+JsMqryrCsr+kS6Si3NL4tFQ4/JWNFlHLhgs3WJ4SDkmvWgdG7hntdICI1NrwMZIar9cmkf
+mbo93Swx0JOo6QAIDR3IipUEn9c4aP7Hi+53oWKv8Gl31PP4ibl6ssEIKqTC32hzpyJf7T
+yxkAAAAVAKyDTI1nK7JhIC5s3+pPrK6JKORpAAAAgDELEUIzrUBkAPP1yO7zlpxDAlZ7cL
+dV4uEbCx+fuBqVDFHJuAr6o3xX0heArnx6Y5csBYawAtv6LkCKRiTxk6FqLZO3bec1nJBl
++Q7HDIkVTje38XaVDJ9eiDug3VHLV5VmvUmPjXX0REiqFAo4B+Z5TPJOzdjMAiuaYRx5KD
+7vAAAAgA4Ma0Vnjal94JmVDNaoawztJjh6ByRawbryQnUOvktRr47gSNKzZttufoHGgIYm
+vZPyxpPDcAm1htRW6XrLjWjRbfxcUvS1UqhGmaF7SrmPglAp9QXwVhfe/7tKDD8Gp1yiCR
+AIVPNX+U12TPFIuzhCMk8dbfaoL2vuf4BXC+PJAAAAFBVcac1iVzrWVnLglRZRenUhlKLr
+AAAADHNoZW5nQHNlcnZlcgECAwQFBgc=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/tests/data/test_new_rsa_password.key b/tests/data/test_new_rsa_password.key
@@ -0,0 +1,39 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+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
+PR/8/w==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/tests/test_handler.py b/tests/test_handler.py
@@ -196,6 +196,15 @@ class TestPrivateKey(unittest.TestCase):
password = 'abc123'
self._test_with_encrypted_key(fname, password, paramiko.Ed25519Key)
+ def test_get_pkey_obj_with_encrypted_new_rsa_key(self):
+ fname = 'test_new_rsa_password.key'
+ password = '123456'
+ self._test_with_encrypted_key(fname, password, paramiko.RSAKey)
+
+ def test_get_pkey_obj_with_plain_new_dsa_key(self):
+ pk = self.get_pk_obj('test_new_dsa.key')
+ self.assertIsInstance(pk.get_pkey_obj(), paramiko.DSSKey)
+
def test_parse_name(self):
key = u'-----BEGIN PRIVATE KEY-----'
pk = PrivateKey(key)
diff --git a/webssh/handler.py b/webssh/handler.py
@@ -118,6 +118,7 @@ class PrivateKey(object):
self.password = password
self.check_length()
self.iostr = io.StringIO(privatekey)
+ self.last_exception = None
def check_length(self):
if len(self.privatekey) > self.max_length:
@@ -138,30 +139,49 @@ class PrivateKey(object):
break
return name, len(line_)
- def get_pkey_obj(self):
- name, length = self.parse_name(self.iostr, self.tag_to_name)
- if not name:
- raise InvalidValueError('Invalid key {}.'.format(self.filename))
-
- offset = self.iostr.tell() - length
+ def get_specific_pkey(self, name, offset, password):
self.iostr.seek(offset)
logging.debug('Reset offset to {}.'.format(offset))
- logging.info('Parsing {} key'.format(name))
+ logging.debug('Try parsing it as {} type key'.format(name))
pkeycls = getattr(paramiko, name+'Key')
- password = to_bytes(self.password) if self.password else None
+ pkey = None
try:
- return pkeycls.from_private_key(self.iostr, password=password)
+ pkey = pkeycls.from_private_key(self.iostr, password=password)
except paramiko.PasswordRequiredException:
raise InvalidValueError('Need a passphrase to decrypt the key.')
- except paramiko.SSHException as exc:
- logging.error(str(exc))
- msg = 'Invalid key'
- if self.password:
- msg += ' or wrong passphrase "{}" for decrypting it.'.format(
- self.password)
- raise InvalidValueError(msg)
+ except (paramiko.SSHException, ValueError) as exc:
+ self.last_exception = exc
+ logging.debug(str(exc))
+
+ return pkey
+
+ def get_pkey_obj(self):
+ logging.info('Parsing private key {!r}'.format(self.filename))
+ name, length = self.parse_name(self.iostr, self.tag_to_name)
+ if not name:
+ raise InvalidValueError('Invalid key {}.'.format(self.filename))
+
+ offset = self.iostr.tell() - length
+ password = to_bytes(self.password) if self.password else None
+ pkey = self.get_specific_pkey(name, offset, password)
+
+ if pkey is None and name == 'Ed25519':
+ for name in ['RSA', 'ECDSA', 'DSS']:
+ pkey = self.get_specific_pkey(name, offset, password)
+ if pkey:
+ break
+
+ if pkey:
+ return pkey
+
+ logging.error(str(self.last_exception))
+ msg = 'Invalid key'
+ if self.password:
+ msg += ' or wrong passphrase "{}" for decrypting it.'.format(
+ self.password)
+ raise InvalidValueError(msg)
class MixinHandler(object):