[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.
This commit is contained in:
parent
e3ed1616d1
commit
ae18228d07
79
demo.py
79
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
|
||||
|
||||
# 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
|
||||
agent_auth(username, t, event)
|
||||
|
||||
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)
|
||||
# ask for what kind of authentication to try
|
||||
if not t.is_authenticated():
|
||||
manual_auth(username, hostname, event)
|
||||
|
||||
event.wait(10)
|
||||
# print repr(t)
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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()
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue