summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobey Pointer <robey@lag.net>2005-07-07 01:35:31 +0000
committerRobey Pointer <robey@lag.net>2005-07-07 01:35:31 +0000
commitae18228d079224d71b20846b9c4bcc2a5537e135 (patch)
tree6433f221f2dca0242fe7cf413208067981998fca
parente3ed1616d17f64ca394e7357b05eaf4c6b55ea1f (diff)
[project @ Arch-1:robey@lag.net--2005-master-shake%paramiko--dev--1--patch-26]
new ssh agent support! from john rochester. added a bunch of docs to it, and changed demo.py to use an Agent if it finds a working key there.
-rwxr-xr-xdemo.py79
-rw-r--r--paramiko/__init__.py4
-rw-r--r--paramiko/agent.py133
-rw-r--r--paramiko/sftp_client.py2
4 files changed, 186 insertions, 32 deletions
diff --git a/demo.py b/demo.py
index 691b6f9b..b85fc013 100755
--- a/demo.py
+++ b/demo.py
@@ -50,6 +50,51 @@ def load_host_keys():
f.close()
return keys
+def agent_auth(username, t, event):
+ agent = paramiko.Agent()
+ agent_keys = agent.get_keys()
+ if len(agent_keys) > 0:
+ for key in agent_keys:
+ print 'Trying ssh-agent key %s' % paramiko.util.hexify(key.get_fingerprint()),
+ t.auth_publickey(username, key, event)
+ event.wait(10)
+ if t.is_authenticated():
+ print '... success!'
+ return
+ print '... nope.'
+
+def manual_auth(username, hostname, event):
+ default_auth = 'p'
+ auth = raw_input('Auth by (p)assword, (r)sa key, or (d)ss key? [%s] ' % default_auth)
+ if len(auth) == 0:
+ auth = default_auth
+
+ if auth == 'r':
+ default_path = os.environ['HOME'] + '/.ssh/id_rsa'
+ path = raw_input('RSA key [%s]: ' % default_path)
+ if len(path) == 0:
+ path = default_path
+ try:
+ key = paramiko.RSAKey.from_private_key_file(path)
+ except paramiko.PasswordRequiredException:
+ password = getpass.getpass('RSA key password: ')
+ key = paramiko.RSAKey.from_private_key_file(path, password)
+ t.auth_publickey(username, key, event)
+ elif auth == 'd':
+ default_path = os.environ['HOME'] + '/.ssh/id_dsa'
+ path = raw_input('DSS key [%s]: ' % default_path)
+ if len(path) == 0:
+ path = default_path
+ try:
+ key = paramiko.DSSKey.from_private_key_file(path)
+ except paramiko.PasswordRequiredException:
+ password = getpass.getpass('DSS key password: ')
+ key = paramiko.DSSKey.from_private_key_file(path, password)
+ t.auth_publickey(username, key, event)
+ else:
+ pw = getpass.getpass('Password for %s@%s: ' % (username, hostname))
+ t.auth_password(username, pw, event)
+
##### main demo
@@ -113,37 +158,11 @@ try:
if len(username) == 0:
username = default_username
+ agent_auth(username, t, event)
+
# ask for what kind of authentication to try
- default_auth = 'p'
- auth = raw_input('Auth by (p)assword, (r)sa key, or (d)ss key? [%s] ' % default_auth)
- if len(auth) == 0:
- auth = default_auth
-
- if auth == 'r':
- default_path = os.environ['HOME'] + '/.ssh/id_rsa'
- path = raw_input('RSA key [%s]: ' % default_path)
- if len(path) == 0:
- path = default_path
- try:
- key = paramiko.RSAKey.from_private_key_file(path)
- except paramiko.PasswordRequiredException:
- password = getpass.getpass('RSA key password: ')
- key = paramiko.RSAKey.from_private_key_file(path, password)
- t.auth_publickey(username, key, event)
- elif auth == 'd':
- default_path = os.environ['HOME'] + '/.ssh/id_dsa'
- path = raw_input('DSS key [%s]: ' % default_path)
- if len(path) == 0:
- path = default_path
- try:
- key = paramiko.DSSKey.from_private_key_file(path)
- except paramiko.PasswordRequiredException:
- password = getpass.getpass('DSS key password: ')
- key = paramiko.DSSKey.from_private_key_file(path, password)
- t.auth_publickey(username, key, event)
- else:
- pw = getpass.getpass('Password for %s@%s: ' % (username, hostname))
- t.auth_password(username, pw, event)
+ if not t.is_authenticated():
+ manual_auth(username, hostname, event)
event.wait(10)
# print repr(t)
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index 0cb84058..1f2b364e 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -65,7 +65,7 @@ __license__ = "GNU Lesser General Public License (LGPL)"
import transport, auth_transport, channel, rsakey, dsskey, message
-import ssh_exception, file, packet
+import ssh_exception, file, packet, agent
import sftp, sftp_client, sftp_attr, sftp_file, sftp_handle, sftp_server, sftp_si
randpool = transport.randpool
@@ -89,6 +89,7 @@ SubsystemHandler = server.SubsystemHandler
SecurityOptions = transport.SecurityOptions
BufferedFile = file.BufferedFile
Packetizer = packet.Packetizer
+Agent = agent.Agent
from common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, \
OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, \
@@ -103,6 +104,7 @@ __all__ = [ 'Transport',
'Channel',
'RSAKey',
'DSSKey',
+ 'Agent',
'Message',
'SSHException',
'PasswordRequiredException',
diff --git a/paramiko/agent.py b/paramiko/agent.py
new file mode 100644
index 00000000..7e32c79d
--- /dev/null
+++ b/paramiko/agent.py
@@ -0,0 +1,133 @@
+# Copyright (C) 2003-2005 John Rochester <john@jrochester.org>
+#
+# This file is part of paramiko.
+#
+# Paramiko is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+"""
+SSH Agent interface for Unix clients.
+"""
+
+import os, socket, struct
+
+from ssh_exception import SSHException
+from message import Message
+from pkey import PKey
+
+SSH2_AGENTC_REQUEST_IDENTITIES, SSH2_AGENT_IDENTITIES_ANSWER, \
+ SSH2_AGENTC_SIGN_REQUEST, SSH2_AGENT_SIGN_RESPONSE = range(11, 15)
+
+class Agent:
+ """
+ Client interface for using private keys from an SSH agent running on the
+ local machine. If an SSH agent is running, this class can be used to
+ connect to it and retreive L{PKey} objects which can be used when
+ attempting to authenticate to remote SSH servers.
+
+ Because the SSH agent protocol uses environment variables and unix-domain
+ sockets, this probably doesn't work on Windows. It does work on most
+ posix platforms though (Linux and MacOS X, for example).
+ """
+
+ def __init__(self):
+ """
+ Open a session with the local machine's SSH agent, if one is running.
+ If no agent is running, initialization will succeed, but L{get_keys}
+ will return an empty tuple.
+
+ @raise SSHException: if an SSH agent is found, but speaks an
+ incompatible protocol
+ """
+ if 'SSH_AUTH_SOCK' in os.environ:
+ conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ conn.connect(os.environ['SSH_AUTH_SOCK'])
+ self.conn = conn
+ type, result = self._send_message(chr(SSH2_AGENTC_REQUEST_IDENTITIES))
+ if type != SSH2_AGENT_IDENTITIES_ANSWER:
+ raise SSHException('could not get keys from ssh-agent')
+ keys = []
+ for i in range(result.get_int()):
+ keys.append(AgentKey(self, result.get_string()))
+ result.get_string()
+ self.keys = tuple(keys)
+ else:
+ self.keys = ()
+
+ def close(self):
+ """
+ Close the SSH agent connection.
+ """
+ self.conn.close()
+ self.conn = None
+ self.keys = ()
+
+ def get_keys(self):
+ """
+ Return the list of keys available through the SSH agent, if any. If
+ no SSH agent was running (or it couldn't be contacted), an empty list
+ will be returned.
+
+ @return: a list of keys available on the SSH agent
+ @rtype: tuple of L{AgentKey}
+ """
+ return self.keys
+
+ def _send_message(self, msg):
+ msg = str(msg)
+ self.conn.send(struct.pack('>I', len(msg)) + msg)
+ l = self._read_all(4)
+ msg = Message(self._read_all(struct.unpack('>I', l)[0]))
+ return ord(msg.get_byte()), msg
+
+ def _read_all(self, wanted):
+ result = self.conn.recv(wanted)
+ while len(result) < wanted:
+ if len(result) == 0:
+ raise SSHException('lost ssh-agent')
+ extra = self.conn.recv(wanted - len(result))
+ if len(extra) == 0:
+ raise SSHException('lost ssh-agent')
+ result += extra
+ return result
+
+
+class AgentKey(PKey):
+ """
+ Private key held in a local SSH agent. This type of key can be used for
+ authenticating to a remote server (signing). Most other key operations
+ work as expected.
+ """
+
+ def __init__(self, agent, blob):
+ self.agent = agent
+ self.blob = blob
+ self.name = Message(blob).get_string()
+
+ def __str__(self):
+ return self.blob
+
+ def get_name(self):
+ return self.name
+
+ def sign_ssh_data(self, randpool, data):
+ msg = Message()
+ msg.add_byte(chr(SSH2_AGENTC_SIGN_REQUEST))
+ msg.add_string(self.blob)
+ msg.add_string(data)
+ msg.add_int(0)
+ type, result = self.agent._send_message(msg)
+ if type != SSH2_AGENT_SIGN_RESPONSE:
+ raise SSHException('key cannot be used for signing')
+ return result.get_string()
diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py
index 50591cbb..8a86ec16 100644
--- a/paramiko/sftp_client.py
+++ b/paramiko/sftp_client.py
@@ -114,7 +114,7 @@ class SFTPClient (BaseSFTP):
raise SFTPError('Expected handle')
handle = msg.get_string()
filelist = []
- while 1:
+ while True:
try:
t, msg = self._request(CMD_READDIR, handle)
except EOFError, e: