Package ssh :: Module agent
[frames] | no frames]

Source Code for Module ssh.agent

  1  # Copyright (C) 2011  John Rochester <john@jrochester.org> 
  2  # 
  3  # This file is part of ssh. 
  4  # 
  5  # 'ssh' is free software; you can redistribute it and/or modify it under the 
  6  # terms of the GNU Lesser General Public License as published by the Free 
  7  # Software Foundation; either version 2.1 of the License, or (at your option) 
  8  # any later version. 
  9  # 
 10  # 'ssh' is distrubuted in the hope that it will be useful, but WITHOUT ANY 
 11  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 12  # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
 13  # details. 
 14  # 
 15  # You should have received a copy of the GNU Lesser General Public License 
 16  # along with 'ssh'; if not, write to the Free Software Foundation, Inc., 
 17  # 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA. 
 18   
 19  """ 
 20  SSH Agent interface for Unix clients. 
 21  """ 
 22   
 23  import os 
 24  import socket 
 25  import struct 
 26  import sys 
 27  import threading 
 28  import time 
 29  import tempfile 
 30  import stat 
 31  from select import select 
 32   
 33  from ssh.ssh_exception import SSHException 
 34  from ssh.message import Message 
 35  from ssh.pkey import PKey 
 36  from ssh.channel import Channel 
 37  from ssh.common import io_sleep 
 38   
 39  SSH2_AGENTC_REQUEST_IDENTITIES, SSH2_AGENT_IDENTITIES_ANSWER, \ 
 40      SSH2_AGENTC_SIGN_REQUEST, SSH2_AGENT_SIGN_RESPONSE = range(11, 15) 
 41   
42 -class AgentSSH(object):
43 """ 44 Client interface for using private keys from an SSH agent running on the 45 local machine. If an SSH agent is running, this class can be used to 46 connect to it and retreive L{PKey} objects which can be used when 47 attempting to authenticate to remote SSH servers. 48 49 Because the SSH agent protocol uses environment variables and unix-domain 50 sockets, this probably doesn't work on Windows. It does work on most 51 posix platforms though (Linux and MacOS X, for example). 52 """
53 - def __init__(self):
54 self._conn = None 55 self._keys = ()
56
57 - def get_keys(self):
58 """ 59 Return the list of keys available through the SSH agent, if any. If 60 no SSH agent was running (or it couldn't be contacted), an empty list 61 will be returned. 62 63 @return: a list of keys available on the SSH agent 64 @rtype: tuple of L{AgentKey} 65 """ 66 return self._keys
67
68 - def _connect(self, conn):
69 self._conn = conn 70 ptype, result = self._send_message(chr(SSH2_AGENTC_REQUEST_IDENTITIES)) 71 if ptype != SSH2_AGENT_IDENTITIES_ANSWER: 72 raise SSHException('could not get keys from ssh-agent') 73 keys = [] 74 for i in range(result.get_int()): 75 keys.append(AgentKey(self, result.get_string())) 76 result.get_string() 77 self._keys = tuple(keys)
78
79 - def _close(self):
80 #self._conn.close() 81 self._conn = None 82 self._keys = ()
83
84 - def _send_message(self, msg):
85 msg = str(msg) 86 self._conn.send(struct.pack('>I', len(msg)) + msg) 87 l = self._read_all(4) 88 msg = Message(self._read_all(struct.unpack('>I', l)[0])) 89 return ord(msg.get_byte()), msg
90
91 - def _read_all(self, wanted):
92 result = self._conn.recv(wanted) 93 while len(result) < wanted: 94 if len(result) == 0: 95 raise SSHException('lost ssh-agent') 96 extra = self._conn.recv(wanted - len(result)) 97 if len(extra) == 0: 98 raise SSHException('lost ssh-agent') 99 result += extra 100 return result
101
102 -class AgentProxyThread(threading.Thread):
103 """ Class in charge of communication between two chan """
104 - def __init__(self, agent):
105 threading.Thread.__init__(self, target=self.run) 106 self._agent = agent 107 self._exit = False
108
109 - def run(self):
110 try: 111 (r,addr) = self.get_connection() 112 self.__inr = r 113 self.__addr = addr 114 self._agent.connect() 115 self._communicate() 116 except: 117 #XXX Not sure what to do here ... raise or pass ? 118 raise
119
120 - def _communicate(self):
121 import fcntl 122 oldflags = fcntl.fcntl(self.__inr, fcntl.F_GETFL) 123 fcntl.fcntl(self.__inr, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) 124 while not self._exit: 125 events = select([self._agent._conn, self.__inr], [], [], 0.5) 126 for fd in events[0]: 127 if self._agent._conn == fd: 128 data = self._agent._conn.recv(512) 129 if len(data) != 0: 130 self.__inr.send(data) 131 else: 132 break 133 elif self.__inr == fd: 134 data = self.__inr.recv(512) 135 if len(data) != 0: 136 self._agent._conn.send(data) 137 else: 138 break 139 time.sleep(io_sleep)
140
141 -class AgentLocalProxy(AgentProxyThread):
142 """ 143 Class to be used when wanting to ask a local SSH Agent being 144 asked from a remote fake agent (so use a unix socket for ex.) 145 """
146 - def __init__(self, agent):
148
149 - def get_connection(self):
150 """ Return a pair of socket object and string address 151 May Block ! 152 """ 153 conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 154 try: 155 conn.bind(self._agent._get_filename()) 156 conn.listen(1) 157 (r,addr) = conn.accept() 158 return (r, addr) 159 except: 160 raise 161 return None
162
163 -class AgentRemoteProxy(AgentProxyThread):
164 """ 165 Class to be used when wanting to ask a remote SSH Agent 166 """
167 - def __init__(self, agent, chan):
168 AgentProxyThread.__init__(self, agent) 169 self.__chan = chan
170
171 - def get_connection(self):
172 """ 173 Class to be used when wanting to ask a local SSH Agent being 174 asked from a remote fake agent (so use a unix socket for ex.) 175 """ 176 return (self.__chan, None)
177
178 -class AgentClientProxy(object):
179 """ 180 Class proxying request as a client: 181 -> client ask for a request_forward_agent() 182 -> server creates a proxy and a fake SSH Agent 183 -> server ask for establishing a connection when needed, 184 calling the forward_agent_handler at client side. 185 -> the forward_agent_handler launch a thread for connecting 186 the remote fake agent and the local agent 187 -> Communication occurs ... 188 """
189 - def __init__(self, chanRemote):
190 self._conn = None 191 self.__chanR = chanRemote 192 self.thread = AgentRemoteProxy(self, chanRemote) 193 self.thread.start()
194
195 - def __del__(self):
196 self.close()
197
198 - def connect(self):
199 """ 200 Method automatically called by the run() method of the AgentProxyThread 201 """ 202 if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'): 203 conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 204 try: 205 conn.connect(os.environ['SSH_AUTH_SOCK']) 206 except: 207 # probably a dangling env var: the ssh agent is gone 208 return 209 elif sys.platform == 'win32': 210 import win_pageant 211 if win_pageant.can_talk_to_agent(): 212 conn = win_pageant.PageantConnection() 213 else: 214 return 215 else: 216 # no agent support 217 return 218 self._conn = conn
219
220 - def close(self):
221 """ 222 Close the current connection and terminate the agent 223 Should be called manually 224 """ 225 if hasattr(self, "thread"): 226 self.thread._exit = True 227 self.thread.join(1000) 228 if self._conn is not None: 229 self._conn.close()
230
231 -class AgentServerProxy(AgentSSH):
232 """ 233 @param t : transport used for the Forward for SSH Agent communication 234 235 @raise SSHException: mostly if we lost the agent 236 """
237 - def __init__(self, t):
238 AgentSSH.__init__(self) 239 self.__t = t 240 self._dir = tempfile.mkdtemp('sshproxy') 241 os.chmod(self._dir, stat.S_IRWXU) 242 self._file = self._dir + '/sshproxy.ssh' 243 self.thread = AgentLocalProxy(self) 244 self.thread.start()
245
246 - def __del__(self):
247 self.close()
248
249 - def connect(self):
250 conn_sock = self.__t.open_forward_agent_channel() 251 if conn_sock is None: 252 raise SSHException('lost ssh-agent') 253 conn_sock.set_name('auth-agent') 254 self._connect(conn_sock)
255
256 - def close(self):
257 """ 258 Terminate the agent, clean the files, close connections 259 Should be called manually 260 """ 261 os.remove(self._file) 262 os.rmdir(self._dir) 263 self.thread._exit = True 264 self.thread.join(1000) 265 self._close()
266
267 - def get_env(self):
268 """ 269 Helper for the environnement under unix 270 271 @return: the SSH_AUTH_SOCK Environnement variables 272 @rtype: dict 273 """ 274 env = {} 275 env['SSH_AUTH_SOCK'] = self._get_filename() 276 return env
277
278 - def _get_filename(self):
279 return self._file
280
281 -class AgentRequestHandler(object):
282 - def __init__(self, chanClient):
283 self._conn = None 284 self.__chanC = chanClient 285 chanClient.request_forward_agent(self._forward_agent_handler) 286 self.__clientProxys = []
287
288 - def _forward_agent_handler(self, chanRemote):
289 self.__clientProxys.append(AgentClientProxy(chanRemote))
290
291 - def __del__(self):
292 self.close()
293
294 - def close(self):
295 for p in self.__clientProxys: 296 p.close()
297
298 -class Agent(AgentSSH):
299 """ 300 Client interface for using private keys from an SSH agent running on the 301 local machine. If an SSH agent is running, this class can be used to 302 connect to it and retreive L{PKey} objects which can be used when 303 attempting to authenticate to remote SSH servers. 304 305 Because the SSH agent protocol uses environment variables and unix-domain 306 sockets, this probably doesn't work on Windows. It does work on most 307 posix platforms though (Linux and MacOS X, for example). 308 """ 309
310 - def __init__(self):
311 """ 312 Open a session with the local machine's SSH agent, if one is running. 313 If no agent is running, initialization will succeed, but L{get_keys} 314 will return an empty tuple. 315 316 @raise SSHException: if an SSH agent is found, but speaks an 317 incompatible protocol 318 """ 319 AgentSSH.__init__(self) 320 321 if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'): 322 conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 323 try: 324 conn.connect(os.environ['SSH_AUTH_SOCK']) 325 except: 326 # probably a dangling env var: the ssh agent is gone 327 return 328 elif sys.platform == 'win32': 329 import win_pageant 330 if win_pageant.can_talk_to_agent(): 331 conn = win_pageant.PageantConnection() 332 else: 333 return 334 else: 335 # no agent support 336 return 337 self._connect(conn)
338
339 - def close(self):
340 """ 341 Close the SSH agent connection. 342 """ 343 self._close()
344
345 -class AgentKey(PKey):
346 """ 347 Private key held in a local SSH agent. This type of key can be used for 348 authenticating to a remote server (signing). Most other key operations 349 work as expected. 350 """ 351
352 - def __init__(self, agent, blob):
353 self.agent = agent 354 self.blob = blob 355 self.name = Message(blob).get_string()
356
357 - def __str__(self):
358 return self.blob
359
360 - def get_name(self):
361 return self.name
362
363 - def sign_ssh_data(self, rng, data):
364 msg = Message() 365 msg.add_byte(chr(SSH2_AGENTC_SIGN_REQUEST)) 366 msg.add_string(self.blob) 367 msg.add_string(data) 368 msg.add_int(0) 369 ptype, result = self.agent._send_message(msg) 370 if ptype != SSH2_AGENT_SIGN_RESPONSE: 371 raise SSHException('key cannot be used for signing') 372 return result.get_string()
373