summaryrefslogtreecommitdiff
path: root/paramiko/client.py
diff options
context:
space:
mode:
authorJeff Forcier <jeff@bitprophet.org>2017-09-13 08:48:04 -0700
committerJeff Forcier <jeff@bitprophet.org>2017-09-13 08:48:04 -0700
commit2b3967e2f641bb265fd6a4570e8b5a7875159bc9 (patch)
treee2233ad33d7db1339fc0a146ff2d9525736dab04 /paramiko/client.py
parentad5c0d17fffd6b365a5477d2b884d3207879d8b8 (diff)
parentb27b82c0ba10001d9f67eb7365e1bdc16a19e048 (diff)
downloadparamiko-2b3967e2f641bb265fd6a4570e8b5a7875159bc9.tar.gz
Merge branch 'master' into 1063-int
Diffstat (limited to 'paramiko/client.py')
-rw-r--r--paramiko/client.py73
1 files changed, 58 insertions, 15 deletions
diff --git a/paramiko/client.py b/paramiko/client.py
index a9677b05..e1d64ff6 100644
--- a/paramiko/client.py
+++ b/paramiko/client.py
@@ -241,9 +241,23 @@ class SSHClient (ClosingContextManager):
Authentication is attempted in the following order of priority:
- The ``pkey`` or ``key_filename`` passed in (if any)
+
+ - ``key_filename`` may contain OpenSSH public certificate paths
+ as well as regular private-key paths; when files ending in
+ ``-cert.pub`` are found, they are assumed to match a private
+ key, and both components will be loaded. (The private key
+ itself does *not* need to be listed in ``key_filename`` for
+ this to occur - *just* the certificate.)
+
- Any key we can find through an SSH agent
- Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in
``~/.ssh/``
+
+ - When OpenSSH-style public certificates exist that match an
+ existing such private key (so e.g. one has ``id_rsa`` and
+ ``id_rsa-cert.pub``) the certificate will be loaded alongside
+ the private key and used for authentication.
+
- Plain username/password auth, if a password was given
If a private key requires a password to unlock it, and a password is
@@ -258,8 +272,8 @@ class SSHClient (ClosingContextManager):
a password to use for authentication or for unlocking a private key
:param .PKey pkey: an optional private key to use for authentication
:param str key_filename:
- the filename, or list of filenames, of optional private key(s) to
- try for authentication
+ the filename, or list of filenames, of optional private key(s)
+ and/or certs to try for authentication
:param float timeout:
an optional timeout (in seconds) for the TCP connect
:param bool allow_agent:
@@ -500,12 +514,44 @@ class SSHClient (ClosingContextManager):
"""
return self._transport
+ def _key_from_filepath(self, filename, klass, password):
+ """
+ Attempt to derive a `.PKey` from given string path ``filename``:
+
+ - If ``filename`` appears to be a cert, the matching private key is
+ loaded.
+ - Otherwise, the filename is assumed to be a private key, and the
+ matching public cert will be loaded if it exists.
+ """
+ cert_suffix = '-cert.pub'
+ # Assume privkey, not cert, by default
+ if filename.endswith(cert_suffix):
+ key_path = filename[:-len(cert_suffix)]
+ cert_path = filename
+ else:
+ key_path = filename
+ cert_path = filename + cert_suffix
+ # Blindly try the key path; if no private key, nothing will work.
+ key = klass.from_private_key_file(key_path, password)
+ # TODO: change this to 'Loading' instead of 'Trying' sometime; probably
+ # when #387 is released, since this is a critical log message users are
+ # likely testing/filtering for (bah.)
+ msg = "Trying discovered key {0} in {1}".format(
+ hexlify(key.get_fingerprint()), key_path,
+ )
+ self._log(DEBUG, msg)
+ # Attempt to load cert if it exists.
+ if os.path.isfile(cert_path):
+ key.load_certificate(cert_path)
+ self._log(DEBUG, "Adding public certificate {0}".format(cert_path))
+ return key
+
def _auth(self, username, password, pkey, key_filenames, allow_agent,
look_for_keys, gss_auth, gss_kex, gss_deleg_creds, gss_host):
"""
Try, in order:
- - The key passed in, if one was passed in.
+ - The key(s) passed in, if one was passed in.
- Any key we can find through an SSH agent (if allowed).
- Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in ~/.ssh/
(if allowed).
@@ -557,12 +603,9 @@ class SSHClient (ClosingContextManager):
for key_filename in key_filenames:
for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key):
try:
- key = pkey_class.from_private_key_file(
- key_filename, password)
- self._log(
- DEBUG,
- 'Trying key %s from %s' % (
- hexlify(key.get_fingerprint()), key_filename))
+ key = self._key_from_filepath(
+ key_filename, pkey_class, password,
+ )
allowed_types = set(
self._transport.auth_publickey(username, key))
two_factor = (allowed_types & two_factor_types)
@@ -608,19 +651,19 @@ class SSHClient (ClosingContextManager):
"~/%s/id_%s" % (directory, name)
)
if os.path.isfile(full_path):
+ # TODO: only do this append if below did not run
keyfiles.append((keytype, full_path))
+ if os.path.isfile(full_path + '-cert.pub'):
+ keyfiles.append((keytype, full_path + '-cert.pub'))
if not look_for_keys:
keyfiles = []
for pkey_class, filename in keyfiles:
try:
- key = pkey_class.from_private_key_file(filename, password)
- self._log(
- DEBUG,
- 'Trying discovered key %s in %s' % (
- hexlify(key.get_fingerprint()), filename))
-
+ key = self._key_from_filepath(
+ filename, pkey_class, password,
+ )
# for 2-factor auth a successfully auth'd key will result
# in ['password']
allowed_types = set(