Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
agent.py498 linesDownload Raw Back to paramiko
1# Copyright (C) 2003-2007  John Rochester <john@jrochester.org>2#3# This file is part of paramiko.4#5# Paramiko is free software; you can redistribute it and/or modify it under the6# terms of the GNU Lesser General Public License as published by the Free7# Software Foundation; either version 2.1 of the License, or (at your option)8# any later version.9#10# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR12# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more13# details.14#15# You should have received a copy of the GNU Lesser General Public License16# along with Paramiko; if not, write to the Free Software Foundation, Inc.,17# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.18 19"""20SSH Agent interface21"""22 23import os24import socket25import struct26import sys27import threading28import time29import tempfile30import stat31from logging import DEBUG32from select import select33from paramiko.common import io_sleep, byte_chr34 35from paramiko.ssh_exception import SSHException, AuthenticationException36from paramiko.message import Message37from paramiko.pkey import PKey, UnknownKeyType38from paramiko.util import asbytes, get_logger39 40cSSH2_AGENTC_REQUEST_IDENTITIES = byte_chr(11)41SSH2_AGENT_IDENTITIES_ANSWER = 1242cSSH2_AGENTC_SIGN_REQUEST = byte_chr(13)43SSH2_AGENT_SIGN_RESPONSE = 1444 45SSH_AGENT_RSA_SHA2_256 = 246SSH_AGENT_RSA_SHA2_512 = 447# NOTE: RFC mildly confusing; while these flags are OR'd together, OpenSSH at48# least really treats them like "AND"s, in the sense that if it finds the49# SHA256 flag set it won't continue looking at the SHA512 one; it50# short-circuits right away.51# Thus, we never want to eg submit 6 to say "either's good".52ALGORITHM_FLAG_MAP = {53    "rsa-sha2-256": SSH_AGENT_RSA_SHA2_256,54    "rsa-sha2-512": SSH_AGENT_RSA_SHA2_512,55}56for key, value in list(ALGORITHM_FLAG_MAP.items()):57    ALGORITHM_FLAG_MAP[f"{key}-cert-v01@openssh.com"] = value58 59 60# TODO 4.0: rename all these - including making some of their methods public?61class AgentSSH:62    def __init__(self):63        self._conn = None64        self._keys = ()65 66    def get_keys(self):67        """68        Return the list of keys available through the SSH agent, if any.  If69        no SSH agent was running (or it couldn't be contacted), an empty list70        will be returned.71 72        This method performs no IO, just returns the list of keys retrieved73        when the connection was made.74 75        :return:76            a tuple of `.AgentKey` objects representing keys available on the77            SSH agent78        """79        return self._keys80 81    def _connect(self, conn):82        self._conn = conn83        ptype, result = self._send_message(cSSH2_AGENTC_REQUEST_IDENTITIES)84        if ptype != SSH2_AGENT_IDENTITIES_ANSWER:85            raise SSHException("could not get keys from ssh-agent")86        keys = []87        for i in range(result.get_int()):88            keys.append(89                AgentKey(90                    agent=self,91                    blob=result.get_binary(),92                    comment=result.get_text(),93                )94            )95        self._keys = tuple(keys)96 97    def _close(self):98        if self._conn is not None:99            self._conn.close()100        self._conn = None101        self._keys = ()102 103    def _send_message(self, msg):104        msg = asbytes(msg)105        self._conn.send(struct.pack(">I", len(msg)) + msg)106        data = self._read_all(4)107        msg = Message(self._read_all(struct.unpack(">I", data)[0]))108        return ord(msg.get_byte()), msg109 110    def _read_all(self, wanted):111        result = self._conn.recv(wanted)112        while len(result) < wanted:113            if len(result) == 0:114                raise SSHException("lost ssh-agent")115            extra = self._conn.recv(wanted - len(result))116            if len(extra) == 0:117                raise SSHException("lost ssh-agent")118            result += extra119        return result120 121 122class AgentProxyThread(threading.Thread):123    """124    Class in charge of communication between two channels.125    """126 127    def __init__(self, agent):128        threading.Thread.__init__(self, target=self.run)129        self._agent = agent130        self._exit = False131 132    def run(self):133        try:134            (r, addr) = self.get_connection()135            # Found that r should be either136            # a socket from the socket library or None137            self.__inr = r138            # The address should be an IP address as a string? or None139            self.__addr = addr140            self._agent.connect()141            if not isinstance(self._agent, int) and (142                self._agent._conn is None143                or not hasattr(self._agent._conn, "fileno")144            ):145                raise AuthenticationException("Unable to connect to SSH agent")146            self._communicate()147        except:148            # XXX Not sure what to do here ... raise or pass ?149            raise150 151    def _communicate(self):152        import fcntl153 154        oldflags = fcntl.fcntl(self.__inr, fcntl.F_GETFL)155        fcntl.fcntl(self.__inr, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)156        while not self._exit:157            events = select([self._agent._conn, self.__inr], [], [], 0.5)158            for fd in events[0]:159                if self._agent._conn == fd:160                    data = self._agent._conn.recv(512)161                    if len(data) != 0:162                        self.__inr.send(data)163                    else:164                        self._close()165                        break166                elif self.__inr == fd:167                    data = self.__inr.recv(512)168                    if len(data) != 0:169                        self._agent._conn.send(data)170                    else:171                        self._close()172                        break173            time.sleep(io_sleep)174 175    def _close(self):176        self._exit = True177        self.__inr.close()178        self._agent._conn.close()179 180 181class AgentLocalProxy(AgentProxyThread):182    """183    Class to be used when wanting to ask a local SSH Agent being184    asked from a remote fake agent (so use a unix socket for ex.)185    """186 187    def __init__(self, agent):188        AgentProxyThread.__init__(self, agent)189 190    def get_connection(self):191        """192        Return a pair of socket object and string address.193 194        May block!195        """196        conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)197        try:198            conn.bind(self._agent._get_filename())199            conn.listen(1)200            (r, addr) = conn.accept()201            return r, addr202        except:203            raise204 205 206class AgentRemoteProxy(AgentProxyThread):207    """208    Class to be used when wanting to ask a remote SSH Agent209    """210 211    def __init__(self, agent, chan):212        AgentProxyThread.__init__(self, agent)213        self.__chan = chan214 215    def get_connection(self):216        return self.__chan, None217 218 219def get_agent_connection():220    """221    Returns some SSH agent object, or None if none were found/supported.222 223    .. versionadded:: 2.10224    """225    if ("SSH_AUTH_SOCK" in os.environ) and (sys.platform != "win32"):226        conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)227        try:228            conn.connect(os.environ["SSH_AUTH_SOCK"])229            return conn230        except:231            # probably a dangling env var: the ssh agent is gone232            return233    elif sys.platform == "win32":234        from . import win_pageant, win_openssh235 236        conn = None237        if win_pageant.can_talk_to_agent():238            conn = win_pageant.PageantConnection()239        elif win_openssh.can_talk_to_agent():240            conn = win_openssh.OpenSSHAgentConnection()241        return conn242    else:243        # no agent support244        return245 246 247class AgentClientProxy:248    """249    Class proxying request as a client:250 251    #. client ask for a request_forward_agent()252    #. server creates a proxy and a fake SSH Agent253    #. server ask for establishing a connection when needed,254       calling the forward_agent_handler at client side.255    #. the forward_agent_handler launch a thread for connecting256       the remote fake agent and the local agent257    #. Communication occurs ...258    """259 260    def __init__(self, chanRemote):261        self._conn = None262        self.__chanR = chanRemote263        self.thread = AgentRemoteProxy(self, chanRemote)264        self.thread.start()265 266    def __del__(self):267        self.close()268 269    def connect(self):270        """271        Method automatically called by ``AgentProxyThread.run``.272        """273        conn = get_agent_connection()274        if not conn:275            return276        self._conn = conn277 278    def close(self):279        """280        Close the current connection and terminate the agent281        Should be called manually282        """283        if hasattr(self, "thread"):284            self.thread._exit = True285            self.thread.join(1000)286        if self._conn is not None:287            self._conn.close()288 289 290class AgentServerProxy(AgentSSH):291    """292    Allows an SSH server to access a forwarded agent.293 294    This also creates a unix domain socket on the system to allow external295    programs to also access the agent. For this reason, you probably only want296    to create one of these.297 298    :meth:`connect` must be called before it is usable. This will also load the299    list of keys the agent contains. You must also call :meth:`close` in300    order to clean up the unix socket and the thread that maintains it.301    (:class:`contextlib.closing` might be helpful to you.)302 303    :param .Transport t: Transport used for SSH Agent communication forwarding304 305    :raises: `.SSHException` -- mostly if we lost the agent306    """307 308    def __init__(self, t):309        AgentSSH.__init__(self)310        self.__t = t311        self._dir = tempfile.mkdtemp("sshproxy")312        os.chmod(self._dir, stat.S_IRWXU)313        self._file = self._dir + "/sshproxy.ssh"314        self.thread = AgentLocalProxy(self)315        self.thread.start()316 317    def __del__(self):318        self.close()319 320    def connect(self):321        conn_sock = self.__t.open_forward_agent_channel()322        if conn_sock is None:323            raise SSHException("lost ssh-agent")324        conn_sock.set_name("auth-agent")325        self._connect(conn_sock)326 327    def close(self):328        """329        Terminate the agent, clean the files, close connections330        Should be called manually331        """332        os.remove(self._file)333        os.rmdir(self._dir)334        self.thread._exit = True335        self.thread.join(1000)336        self._close()337 338    def get_env(self):339        """340        Helper for the environment under unix341 342        :return:343            a dict containing the ``SSH_AUTH_SOCK`` environment variables344        """345        return {"SSH_AUTH_SOCK": self._get_filename()}346 347    def _get_filename(self):348        return self._file349 350 351class AgentRequestHandler:352    """353    Primary/default implementation of SSH agent forwarding functionality.354 355    Simply instantiate this class, handing it a live command-executing session356    object, and it will handle forwarding any local SSH agent processes it357    finds.358 359    For example::360 361        # Connect362        client = SSHClient()363        client.connect(host, port, username)364        # Obtain session365        session = client.get_transport().open_session()366        # Forward local agent367        AgentRequestHandler(session)368        # Commands executed after this point will see the forwarded agent on369        # the remote end.370        session.exec_command("git clone https://my.git.repository/")371    """372 373    def __init__(self, chanClient):374        self._conn = None375        self.__chanC = chanClient376        chanClient.request_forward_agent(self._forward_agent_handler)377        self.__clientProxys = []378 379    def _forward_agent_handler(self, chanRemote):380        self.__clientProxys.append(AgentClientProxy(chanRemote))381 382    def __del__(self):383        self.close()384 385    def close(self):386        for p in self.__clientProxys:387            p.close()388 389 390class Agent(AgentSSH):391    """392    Client interface for using private keys from an SSH agent running on the393    local machine.  If an SSH agent is running, this class can be used to394    connect to it and retrieve `.PKey` objects which can be used when395    attempting to authenticate to remote SSH servers.396 397    Upon initialization, a session with the local machine's SSH agent is398    opened, if one is running. If no agent is running, initialization will399    succeed, but `get_keys` will return an empty tuple.400 401    :raises: `.SSHException` --402        if an SSH agent is found, but speaks an incompatible protocol403 404    .. versionchanged:: 2.10405        Added support for native openssh agent on windows (extending previous406        putty pageant support)407    """408 409    def __init__(self):410        AgentSSH.__init__(self)411 412        conn = get_agent_connection()413        if not conn:414            return415        self._connect(conn)416 417    def close(self):418        """419        Close the SSH agent connection.420        """421        self._close()422 423 424class AgentKey(PKey):425    """426    Private key held in a local SSH agent.  This type of key can be used for427    authenticating to a remote server (signing).  Most other key operations428    work as expected.429 430    .. versionchanged:: 3.2431        Added the ``comment`` kwarg and attribute.432 433    .. versionchanged:: 3.2434        Added the ``.inner_key`` attribute holding a reference to the 'real'435        key instance this key is a proxy for, if one was obtainable, else None.436    """437 438    def __init__(self, agent, blob, comment=""):439        self.agent = agent440        self.blob = blob441        self.comment = comment442        msg = Message(blob)443        self.name = msg.get_text()444        self._logger = get_logger(__file__)445        self.inner_key = None446        try:447            self.inner_key = PKey.from_type_string(448                key_type=self.name, key_bytes=blob449            )450        except UnknownKeyType:451            # Log, but don't explode, since inner_key is a best-effort thing.452            err = "Unable to derive inner_key for agent key of type {!r}"453            self.log(DEBUG, err.format(self.name))454 455    def log(self, *args, **kwargs):456        return self._logger.log(*args, **kwargs)457 458    def asbytes(self):459        # Prefer inner_key.asbytes, since that will differ for eg RSA-CERT460        return self.inner_key.asbytes() if self.inner_key else self.blob461 462    def get_name(self):463        return self.name464 465    def get_bits(self):466        # Have to work around PKey's default get_bits being crap467        if self.inner_key is not None:468            return self.inner_key.get_bits()469        return super().get_bits()470 471    def __getattr__(self, name):472        """473        Proxy any un-implemented methods/properties to the inner_key.474        """475        if self.inner_key is None:  # nothing to proxy to476            raise AttributeError(name)477        return getattr(self.inner_key, name)478 479    @property480    def _fields(self):481        fallback = [self.get_name(), self.blob]482        return self.inner_key._fields if self.inner_key else fallback483 484    def sign_ssh_data(self, data, algorithm=None):485        msg = Message()486        msg.add_byte(cSSH2_AGENTC_SIGN_REQUEST)487        # NOTE: this used to be just self.blob, which is not entirely right for488        # RSA-CERT 'keys' - those end up always degrading to ssh-rsa type489        # signatures, for reasons probably internal to OpenSSH's agent code,490        # even if everything else wants SHA2 (including our flag map).491        msg.add_string(self.asbytes())492        msg.add_string(data)493        msg.add_int(ALGORITHM_FLAG_MAP.get(algorithm, 0))494        ptype, result = self.agent._send_message(msg)495        if ptype != SSH2_AGENT_SIGN_RESPONSE:496            raise SSHException("key cannot be used for signing")497        return result.get_binary()498 
codekingpro/portable-devtools · Team Ai