diff options
author | Jeff Forcier <jeff@bitprophet.org> | 2017-09-13 08:48:04 -0700 |
---|---|---|
committer | Jeff Forcier <jeff@bitprophet.org> | 2017-09-13 08:48:04 -0700 |
commit | 2b3967e2f641bb265fd6a4570e8b5a7875159bc9 (patch) | |
tree | e2233ad33d7db1339fc0a146ff2d9525736dab04 /paramiko/client.py | |
parent | ad5c0d17fffd6b365a5477d2b884d3207879d8b8 (diff) | |
parent | b27b82c0ba10001d9f67eb7365e1bdc16a19e048 (diff) | |
download | paramiko-2b3967e2f641bb265fd6a4570e8b5a7875159bc9.tar.gz |
Merge branch 'master' into 1063-int
Diffstat (limited to 'paramiko/client.py')
-rw-r--r-- | paramiko/client.py | 73 |
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( |