summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rwxr-xr-xdemo_server.py25
-rw-r--r--paramiko/__init__.py2
-rw-r--r--paramiko/auth_transport.py189
-rw-r--r--paramiko/dsskey.py58
-rw-r--r--paramiko/kex_gex.py2
-rw-r--r--paramiko/kex_group1.py2
-rw-r--r--paramiko/pkey.py79
-rw-r--r--paramiko/rsakey.py44
-rw-r--r--paramiko/transport.py92
-rw-r--r--user_rsa_key15
-rw-r--r--user_rsa_key.pub1
12 files changed, 324 insertions, 188 deletions
diff --git a/Makefile b/Makefile
index c4d38736..593dcde8 100644
--- a/Makefile
+++ b/Makefile
@@ -8,8 +8,9 @@ RELEASE=charmander
release:
python ./setup.py sdist --formats=zip
-docs:
+docs: always
epydoc -o docs/ paramiko
+always:
# places where the version number is stored:
#
diff --git a/demo_server.py b/demo_server.py
index 7fd25ad3..65b45cf7 100755
--- a/demo_server.py
+++ b/demo_server.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-import sys, os, socket, threading, logging, traceback
+import sys, os, socket, threading, logging, traceback, base64
import paramiko
# setup logging
@@ -18,10 +18,14 @@ if len(l.handlers) == 0:
host_key = paramiko.DSSKey()
host_key.read_private_key_file('demo_dss_key')
-print 'Read key: ' + paramiko.hexify(host_key.get_fingerprint())
+print 'Read key: ' + paramiko.util.hexify(host_key.get_fingerprint())
class ServerTransport(paramiko.Transport):
+ # 'data' is the output of base64.encodestring(str(key))
+ data = 'AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hpfAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMCKDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iTUWT10hcuO4Ks8='
+ good_pub_key = paramiko.RSAKey(data=base64.decodestring(data))
+
def check_channel_request(self, kind, chanid):
if kind == 'session':
return ServerChannel(chanid)
@@ -32,6 +36,11 @@ class ServerTransport(paramiko.Transport):
return self.AUTH_SUCCESSFUL
return self.AUTH_FAILED
+ def check_auth_publickey(self, username, key):
+ if (username == 'robey') and (key == self.good_pub_key):
+ return self.AUTH_SUCCESSFUL
+ return self.AUTH_FAILED
+
class ServerChannel(paramiko.Channel):
"Channel descendant that pretends to understand pty and shell requests"
@@ -79,11 +88,13 @@ try:
t.add_server_key(host_key)
t.ultra_debug = 0
t.start_server(event)
- # print repr(t)
- event.wait(10)
- if not t.is_active():
- print '*** SSH negotiation failed.'
- sys.exit(1)
+ while 1:
+ event.wait(0.1)
+ if not t.is_active():
+ print '*** SSH negotiation failed.'
+ sys.exit(1)
+ if event.isSet():
+ break
# print repr(t)
# wait for auth
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index 81d41edb..76531611 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -41,5 +41,5 @@ class DSSKey (dsskey.DSSKey):
__all__ = [ 'Transport', 'Channel', 'RSAKey', 'DSSKey', 'transport',
- 'auth_transport', 'channel', 'rsakey', 'ddskey', 'util',
+ 'auth_transport', 'channel', 'rsakey', 'dsskey', 'util',
'SSHException' ]
diff --git a/paramiko/auth_transport.py b/paramiko/auth_transport.py
index 34f11744..5c4516f5 100644
--- a/paramiko/auth_transport.py
+++ b/paramiko/auth_transport.py
@@ -13,7 +13,10 @@ _DISCONNECT_SERVICE_NOT_AVAILABLE, _DISCONNECT_AUTH_CANCELLED_BY_USER, \
class Transport (BaseTransport):
- "BaseTransport with the auth framework hooked up"
+ """
+ Subclass of L{BaseTransport} that handles authentication. This separation
+ keeps either class file from being too unwieldy.
+ """
AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED = range(3)
@@ -53,15 +56,23 @@ class Transport (BaseTransport):
"""
return self.authenticated and self.active
- def _request_auth(self):
- m = Message()
- m.add_byte(chr(_MSG_SERVICE_REQUEST))
- m.add_string('ssh-userauth')
- self._send_message(m)
-
def auth_key(self, username, key, event):
+ """
+ Authenticate to the server using a private key. The key is used to
+ sign data from the server, so it must include the private part. The
+ given L{event} is triggered on success or failure. On success,
+ L{is_authenticated} will return C{True}.
+
+ @param username: the username to authenticate as.
+ @type username: string
+ @param key: the private key to authenticate with.
+ @type key: L{PKey <pkey.PKey>}
+ @param event: an event to trigger when the authentication attempt is
+ complete (whether it was successful or not)
+ @type event: threading.Event
+ """
if (not self.active) or (not self.initial_kex_done):
- # we should never try to send the password unless we're on a secure link
+ # we should never try to authenticate unless we're on a secure link
raise SSHException('No existing session')
try:
self.lock.acquire()
@@ -74,7 +85,20 @@ class Transport (BaseTransport):
self.lock.release()
def auth_password(self, username, password, event):
- 'authenticate using a password; event is triggered on success or fail'
+ """
+ Authenticate to the server using a password. The username and password
+ are sent over an encrypted link, and the given L{event} is triggered on
+ success or failure. On success, L{is_authenticated} will return
+ C{True}.
+
+ @param username: the username to authenticate as.
+ @type username: string
+ @param password: the password to authenticate with.
+ @type password: string
+ @param event: an event to trigger when the authentication attempt is
+ complete (whether it was successful or not)
+ @type event: threading.Event
+ """
if (not self.active) or (not self.initial_kex_done):
# we should never try to send the password unless we're on a secure link
raise SSHException('No existing session')
@@ -88,7 +112,58 @@ class Transport (BaseTransport):
finally:
self.lock.release()
- def disconnect_service_not_available(self):
+ def get_allowed_auths(self, username):
+ "override me!"
+ return 'password'
+
+ def check_auth_none(self, username):
+ "override me! return int ==> auth status"
+ return self.AUTH_FAILED
+
+ def check_auth_password(self, username, password):
+ "override me! return int ==> auth status"
+ return self.AUTH_FAILED
+
+ def check_auth_publickey(self, username, key):
+ """
+ I{(subclass override)}
+ Determine if a given key supplied by the client is acceptable for use
+ in authentication. You should override this method in server mode to
+ check the username and key and decide if you would accept a signature
+ made using this key.
+
+ Return C{AUTH_FAILED} if the key is not accepted, C{AUTH_SUCCESSFUL}
+ if the key is accepted and completes the authentication, or
+ C{AUTH_PARTIALLY_SUCCESSFUL} if your authentication is stateful, and
+ this key is accepted for authentication, but more authentication is
+ required. (In this latter case, L{get_allowed_auths} will be called
+ to report to the client what options it has for continuing the
+ authentication.)
+
+ The default implementation always returns C{AUTH_FAILED}.
+
+ @param username: the username of the authenticating client.
+ @type username: string
+ @param key: the key object provided by the client.
+ @type key: L{PKey <pkey.PKey>}
+ @return: C{AUTH_FAILED} if the client can't authenticate with this key;
+ C{AUTH_SUCCESSFUL} if it can; C{AUTH_PARTIALLY_SUCCESSFUL} if it can
+ authenticate with this key but must continue with authentication.
+ @rtype: int
+ """
+ return self.AUTH_FAILED
+
+
+ ### internals...
+
+
+ def _request_auth(self):
+ m = Message()
+ m.add_byte(chr(_MSG_SERVICE_REQUEST))
+ m.add_string('ssh-userauth')
+ self._send_message(m)
+
+ def _disconnect_service_not_available(self):
m = Message()
m.add_byte(chr(_MSG_DISCONNECT))
m.add_int(_DISCONNECT_SERVICE_NOT_AVAILABLE)
@@ -97,7 +172,7 @@ class Transport (BaseTransport):
self._send_message(m)
self.close()
- def disconnect_no_more_auth(self):
+ def _disconnect_no_more_auth(self):
m = Message()
m.add_byte(chr(_MSG_DISCONNECT))
m.add_int(_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
@@ -106,7 +181,19 @@ class Transport (BaseTransport):
self._send_message(m)
self.close()
- def parse_service_request(self, m):
+ def _get_session_blob(self, key, service, username):
+ m = Message()
+ m.add_string(self.session_id)
+ m.add_byte(chr(_MSG_USERAUTH_REQUEST))
+ m.add_string(username)
+ m.add_string(service)
+ m.add_string('publickey')
+ m.add_boolean(1)
+ m.add_string(key.get_name())
+ m.add_string(str(key))
+ return str(m)
+
+ def _parse_service_request(self, m):
service = m.get_string()
if self.server_mode and (service == 'ssh-userauth'):
# accepted
@@ -116,9 +203,9 @@ class Transport (BaseTransport):
self._send_message(m)
return
# dunno this one
- self.disconnect_service_not_available()
+ self._disconnect_service_not_available()
- def parse_service_accept(self, m):
+ def _parse_service_accept(self, m):
service = m.get_string()
if service == 'ssh-userauth':
self._log(DEBUG, 'userauth is OK')
@@ -134,30 +221,16 @@ class Transport (BaseTransport):
m.add_boolean(1)
m.add_string(self.private_key.get_name())
m.add_string(str(self.private_key))
- m.add_string(self.private_key.sign_ssh_session(self.randpool, self.H, self.username))
+ blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username)
+ sig = self.private_key.sign_ssh_data(self.randpool, blob)
+ m.add_string(str(sig))
else:
raise SSHException('Unknown auth method "%s"' % self.auth_method)
self._send_message(m)
else:
self._log(DEBUG, 'Service request "%s" accepted (?)' % service)
- def get_allowed_auths(self, username):
- "override me!"
- return 'password'
-
- def check_auth_none(self, username):
- "override me! return int ==> auth status"
- return self.AUTH_FAILED
-
- def check_auth_password(self, username, password):
- "override me! return int ==> auth status"
- return self.AUTH_FAILED
-
- def check_auth_publickey(self, username, key):
- "override me! return int ==> auth status"
- return self.AUTH_FAILED
-
- def parse_userauth_request(self, m):
+ def _parse_userauth_request(self, m):
if not self.server_mode:
# er, uh... what?
m = Message()
@@ -174,11 +247,11 @@ class Transport (BaseTransport):
method = m.get_string()
self._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username))
if service != 'ssh-connection':
- self.disconnect_service_not_available()
+ self._disconnect_service_not_available()
return
if (self.auth_username is not None) and (self.auth_username != username):
self._log(DEBUG, 'Auth rejected because the client attempted to change username in mid-flight')
- self.disconnect_no_more_auth()
+ self._disconnect_no_more_auth()
return
if method == 'none':
result = self.check_auth_none(username)
@@ -194,8 +267,32 @@ class Transport (BaseTransport):
else:
result = self.check_auth_password(username, password)
elif method == 'publickey':
- # FIXME
- result = self.check_auth_none(username)
+ sig_attached = m.get_boolean()
+ keytype = m.get_string()
+ keyblob = m.get_string()
+ key = self._key_from_blob(keytype, keyblob)
+ if (key is None) or (not key.valid):
+ self._log(DEBUG, 'Auth rejected: unsupported or mangled public key')
+ self._disconnect_no_more_auth()
+ return
+ # first check if this key is okay... if not, we can skip the verify
+ result = self.check_auth_publickey(username, key)
+ if result != self.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(chr(_MSG_USERAUTH_PK_OK))
+ m.add_string(keytype)
+ m.add_string(keyblob)
+ self._send_message(m)
+ return
+ sig = Message(m.get_string())
+ blob = self._get_session_blob(key, service, username)
+ if not key.verify_ssh_sig(blob, sig):
+ self._log(DEBUG, 'Auth rejected: invalid signature')
+ result = self.AUTH_FAILED
else:
result = self.check_auth_none(username)
# okay, send result
@@ -215,15 +312,15 @@ class Transport (BaseTransport):
self.auth_fail_count += 1
self._send_message(m)
if self.auth_fail_count >= 10:
- self.disconnect_no_more_auth()
+ self._disconnect_no_more_auth()
- def parse_userauth_success(self, m):
+ def _parse_userauth_success(self, m):
self._log(INFO, 'Authentication successful!')
self.authenticated = True
if self.auth_event != None:
self.auth_event.set()
- def parse_userauth_failure(self, m):
+ def _parse_userauth_failure(self, m):
authlist = m.get_list()
partial = m.get_boolean()
if partial:
@@ -237,7 +334,7 @@ class Transport (BaseTransport):
if self.auth_event != None:
self.auth_event.set()
- def parse_userauth_banner(self, m):
+ def _parse_userauth_banner(self, m):
banner = m.get_string()
lang = m.get_string()
self._log(INFO, 'Auth banner: ' + banner)
@@ -245,11 +342,11 @@ class Transport (BaseTransport):
_handler_table = BaseTransport._handler_table.copy()
_handler_table.update({
- _MSG_SERVICE_REQUEST: parse_service_request,
- _MSG_SERVICE_ACCEPT: parse_service_accept,
- _MSG_USERAUTH_REQUEST: parse_userauth_request,
- _MSG_USERAUTH_SUCCESS: parse_userauth_success,
- _MSG_USERAUTH_FAILURE: parse_userauth_failure,
- _MSG_USERAUTH_BANNER: parse_userauth_banner,
+ _MSG_SERVICE_REQUEST: _parse_service_request,
+ _MSG_SERVICE_ACCEPT: _parse_service_accept,
+ _MSG_USERAUTH_REQUEST: _parse_userauth_request,
+ _MSG_USERAUTH_SUCCESS: _parse_userauth_success,
+ _MSG_USERAUTH_FAILURE: _parse_userauth_failure,
+ _MSG_USERAUTH_BANNER: _parse_userauth_banner,
})
diff --git a/paramiko/dsskey.py b/paramiko/dsskey.py
index 3eca8589..e0006e53 100644
--- a/paramiko/dsskey.py
+++ b/paramiko/dsskey.py
@@ -3,7 +3,6 @@
import base64
from ssh_exception import SSHException
from message import Message
-from transport import _MSG_USERAUTH_REQUEST
from util import inflate_long, deflate_long
from Crypto.PublicKey import DSA
from Crypto.Hash import SHA
@@ -14,9 +13,11 @@ from util import format_binary
class DSSKey (PKey):
- def __init__(self, msg=None):
+ def __init__(self, msg=None, data=None):
self.valid = 0
- if (msg == None) or (msg.get_string() != 'ssh-dss'):
+ if (msg is None) and (data is not None):
+ msg = Message(data)
+ if (msg is None) or (msg.get_string() != 'ssh-dss'):
return
self.p = msg.get_mpint()
self.q = msg.get_mpint()
@@ -36,9 +37,33 @@ class DSSKey (PKey):
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 sign_ssh_data(self, randpool, data):
+ hash = 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(deflate_long(self.q, 0))
+ while 1:
+ k = inflate_long(randpool.get_bytes(qsize), 1)
+ if (k > 2) and (k < self.q):
+ break
+ r, s = dss.sign(inflate_long(hash, 1), k)
+ m = Message()
+ m.add_string('ssh-dss')
+ m.add_string(deflate_long(r, 0) + deflate_long(s, 0))
+ return m
+
def verify_ssh_sig(self, data, msg):
if not self.valid:
return 0
@@ -59,21 +84,6 @@ class DSSKey (PKey):
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q)))
return dss.verify(sigM, (sigR, sigS))
- def sign_ssh_data(self, randpool, data):
- hash = 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(deflate_long(self.q, 0))
- while 1:
- k = inflate_long(randpool.get_bytes(qsize), 1)
- if (k > 2) and (k < self.q):
- break
- r, s = dss.sign(inflate_long(hash, 1), k)
- m = Message()
- m.add_string('ssh-dss')
- m.add_string(deflate_long(r, 0) + deflate_long(s, 0))
- return str(m)
-
def read_private_key_file(self, filename):
# private key file contains:
# DSAPrivateKey = { version = 0, p, q, g, y, x }
@@ -94,15 +104,3 @@ class DSSKey (PKey):
self.x = keylist[5]
self.size = len(deflate_long(self.p, 0))
self.valid = 1
-
- def sign_ssh_session(self, randpool, sid, username):
- m = Message()
- m.add_string(sid)
- m.add_byte(chr(_MSG_USERAUTH_REQUEST))
- m.add_string(username)
- m.add_string('ssh-connection')
- m.add_string('publickey')
- m.add_boolean(1)
- m.add_string('ssh-dss')
- m.add_string(str(self))
- return self.sign_ssh_data(randpool, str(m))
diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py
index f14c7864..3fcb8f26 100644
--- a/paramiko/kex_gex.py
+++ b/paramiko/kex_gex.py
@@ -145,7 +145,7 @@ class KexGex(object):
m.add_byte(chr(_MSG_KEXDH_GEX_REPLY))
m.add_string(key)
m.add_mpint(self.f)
- m.add_string(sig)
+ m.add_string(str(sig))
self.transport._send_message(m)
self.transport._activate_outbound()
diff --git a/paramiko/kex_group1.py b/paramiko/kex_group1.py
index 31123269..13585135 100644
--- a/paramiko/kex_group1.py
+++ b/paramiko/kex_group1.py
@@ -97,6 +97,6 @@ class KexGroup1(object):
m.add_byte(chr(_MSG_KEXDH_REPLY))
m.add_string(key)
m.add_mpint(self.f)
- m.add_string(sig)
+ m.add_string(str(sig))
self.transport._send_message(m)
self.transport._activate_outbound()
diff --git a/paramiko/pkey.py b/paramiko/pkey.py
index c4c7599d..6ad2845d 100644
--- a/paramiko/pkey.py
+++ b/paramiko/pkey.py
@@ -7,28 +7,50 @@ class PKey (object):
Base class for public keys.
"""
- def __init__(self, msg=None):
+ def __init__(self, msg=None, data=None):
"""
- Create a new instance of this public key type. If C{msg} is not
- C{None}, the key's public part(s) will be filled in from the
- message.
+ 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: string
"""
pass
def __str__(self):
"""
Return a string of an SSH L{Message} made up of the public part(s) of
- this key.
+ 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: string
"""
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.
@@ -50,6 +72,20 @@ class PKey (object):
"""
return MD5.new(str(self)).digest()
+ 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: string
+ @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
@@ -65,23 +101,6 @@ class PKey (object):
"""
return False
- def sign_ssh_data(self, randpool, data):
- """
- Sign a blob of data with this private key, and return a string
- representing an SSH signature message.
-
- @bug: It would be cleaner for this method to return a L{Message}
- object, so it would be complementary to L{verify_ssh_sig}. FIXME.
-
- @param randpool: a secure random number generator.
- @type randpool: L{Crypto.Util.randpool.RandomPool}
- @param data: the data to sign.
- @type data: string
- @return: string representation of an SSH signature message.
- @rtype: string
- """
- return ''
-
def read_private_key_file(self, filename):
"""
Read private key contents from a file into this object.
@@ -94,19 +113,3 @@ class PKey (object):
@raise binascii.Error: on base64 decoding error
"""
pass
-
- def sign_ssh_session(self, randpool, sid, username):
- """
- Sign an SSH authentication request.
-
- @bug: Same as L{sign_ssh_data}
-
- @param randpool: a secure random number generator.
- @type randpool: L{Crypto.Util.randpool.RandomPool}
- @param sid: the session ID given by the server
- @type sid: string
- @param username: the username to use in the authentication request
- @type username: string
- @return: string representation of an SSH signature message.
- @rtype: string
- """
diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py
index e0935d5d..b797c6b9 100644
--- a/paramiko/rsakey.py
+++ b/paramiko/rsakey.py
@@ -1,7 +1,6 @@
#!/usr/bin/python
from message import Message
-from transport import _MSG_USERAUTH_REQUEST
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA
from ber import BER
@@ -11,9 +10,11 @@ import base64
class RSAKey (PKey):
- def __init__(self, msg=None):
+ def __init__(self, msg=None, data=''):
self.valid = 0
- if (msg == None) or (msg.get_string() != 'ssh-rsa'):
+ if (msg is None) and (data is not None):
+ msg = Message(data)
+ if (msg is None) or (msg.get_string() != 'ssh-rsa'):
return
self.e = msg.get_mpint()
self.n = msg.get_mpint()
@@ -29,6 +30,12 @@ class RSAKey (PKey):
m.add_mpint(self.n)
return str(m)
+ def __hash__(self):
+ h = hash(self.get_name())
+ h = h * 37 + hash(self.e)
+ h = h * 37 + hash(self.n)
+ return hash(h)
+
def get_name(self):
return 'ssh-rsa'
@@ -41,6 +48,15 @@ class RSAKey (PKey):
filler = '\xff' * (self.size - len(SHA1_DIGESTINFO) - len(data) - 3)
return '\x00\x01' + filler + '\x00' + SHA1_DIGESTINFO + data
+ def sign_ssh_data(self, randpool, data):
+ hash = SHA.new(data).digest()
+ rsa = RSA.construct((long(self.n), long(self.e), long(self.d)))
+ sig = deflate_long(rsa.sign(self._pkcs1imify(hash), '')[0], 0)
+ m = Message()
+ m.add_string('ssh-rsa')
+ m.add_string(sig)
+ return m
+
def verify_ssh_sig(self, data, msg):
if (not self.valid) or (msg.get_string() != 'ssh-rsa'):
return False
@@ -52,15 +68,6 @@ class RSAKey (PKey):
rsa = RSA.construct((long(self.n), long(self.e)))
return rsa.verify(hash, (sig,))
- def sign_ssh_data(self, randpool, data):
- hash = SHA.new(data).digest()
- rsa = RSA.construct((long(self.n), long(self.e), long(self.d)))
- sig = deflate_long(rsa.sign(self._pkcs1imify(hash), '')[0], 0)
- m = Message()
- m.add_string('ssh-rsa')
- m.add_string(sig)
- return str(m)
-
def read_private_key_file(self, filename):
# private key file contains:
# RSAPrivateKey = { version = 0, n, e, d, p, q, d mod p-1, d mod q-1, q**-1 mod p }
@@ -82,16 +89,3 @@ class RSAKey (PKey):
self.q = keylist[5]
self.size = len(deflate_long(self.n, 0))
self.valid = 1
-
- def sign_ssh_session(self, randpool, sid, username):
- m = Message()
- m.add_string(sid)
- m.add_byte(chr(_MSG_USERAUTH_REQUEST))
- m.add_string(username)
- m.add_string('ssh-connection')
- m.add_string('publickey')
- m.add_boolean(1)
- m.add_string('ssh-rsa')
- m.add_string(str(self))
- return self.sign_ssh_data(randpool, str(m))
-
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 1a42fb76..cf914f75 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -155,6 +155,26 @@ class BaseTransport (threading.Thread):
self.server_accepts = [ ]
self.server_accept_cv = threading.Condition(self.lock)
+ def __repr__(self):
+ """
+ Returns a string representation of this object, for debugging.
+
+ @rtype: string
+ """
+ if not self.active:
+ return '<paramiko.BaseTransport (unconnected)>'
+ out = '<paramiko.BaseTransport'
+ #if self.remote_version != '':
+ # out += ' (server version "%s")' % self.remote_version
+ if self.local_cipher != '':
+ out += ' (cipher %s)' % self.local_cipher
+ if len(self.channels) == 1:
+ out += ' (active; 1 open channel)'
+ else:
+ out += ' (active; %d open channels)' % len(self.channels)
+ out += '>'
+ return out
+
def start_client(self, event=None):
self.completion_event = event
self.start()
@@ -179,6 +199,19 @@ class BaseTransport (threading.Thread):
self.server_key_dict[key.get_name()] = key
def get_server_key(self):
+ """
+ Return the active host key, in server mode. After negotiating with the
+ client, this method will return the negotiated host key. If only one
+ type of host key was set with L{add_server_key}, that's the only key
+ that will ever be returned. But in cases where you have set more than
+ one type of host key (for example, an RSA key and a DSS key), the key
+ type will be negotiated by the client, and this method will return the
+ key of the type agreed on. If the host key has not been negotiated
+ yet, C{None} is returned. In client mode, the behavior is undefined.
+
+ @return: host key of the type negotiated by the client, or C{None}.
+ @rtype: L{PKey <pkey.PKey>}
+ """
try:
return self.server_key_dict[self.host_key_type]
except KeyError:
@@ -228,37 +261,6 @@ class BaseTransport (threading.Thread):
return False
load_server_moduli = staticmethod(load_server_moduli)
- def _get_modulus_pack(self):
- "used by KexGex to find primes for group exchange"
- return self._modulus_pack
-
- def __repr__(self):
- """
- Returns a string representation of this object, for debugging.
-
- @rtype: string
- """
- if not self.active:
- return '<paramiko.BaseTransport (unconnected)>'
- out = '<paramiko.BaseTransport'
- #if self.remote_version != '':
- # out += ' (server version "%s")' % self.remote_version
- if self.local_cipher != '':
- out += ' (cipher %s)' % self.local_cipher
- if len(self.channels) == 1:
- out += ' (active; 1 open channel)'
- else:
- out += ' (active; %d open channels)' % len(self.channels)
- out += '>'
- return out
-
- def _log(self, level, msg):
- if type(msg) == type([]):
- for m in msg:
- self.logger.log(level, m)
- else:
- self.logger.log(level, msg)
-
def close(self):
"""
Close this session, and any open channels that are tied to it.
@@ -468,6 +470,17 @@ class BaseTransport (threading.Thread):
### internals...
+ def _log(self, level, msg):
+ if type(msg) == type([]):
+ for m in msg:
+ self.logger.log(level, m)
+ else:
+ self.logger.log(level, msg)
+
+ def _get_modulus_pack(self):
+ "used by KexGex to find primes for group exchange"
+ return self._modulus_pack
+
def _unlink_channel(self, chanid):
"used by a Channel to remove itself from the active channel list"
try:
@@ -597,13 +610,16 @@ class BaseTransport (threading.Thread):
"used by a kex object to register the next packet type it expects to see"
self.expected_packet = type
- def _verify_key(self, host_key, sig):
- if self.host_key_type == 'ssh-rsa':
- key = RSAKey(Message(host_key))
- elif self.host_key_type == 'ssh-dss':
- key = DSSKey(Message(host_key))
+ def _key_from_blob(self, keytype, keyblob):
+ if keytype == 'ssh-rsa':
+ return RSAKey(Message(keyblob))
+ elif keytype == 'ssh-dss':
+ return DSSKey(Message(keyblob))
else:
- key = None
+ return None
+
+ def _verify_key(self, host_key, sig):
+ key = self._key_from_blob(self.host_key_type, host_key)
if (key == None) or not key.valid:
raise SSHException('Unknown host key type')
if not key.verify_ssh_sig(self.H, Message(sig)):
@@ -745,7 +761,7 @@ class BaseTransport (threading.Thread):
kind of key negotiation we support.
"""
if self.server_mode:
- if (self.modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self.preferred_kex):
+ if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self.preferred_kex):
# can't do group-exchange if we don't have a pack of potential primes
self.preferred_kex.remove('diffie-hellman-group-exchange-sha1')
available_server_keys = filter(self.server_key_dict.keys().__contains__,
diff --git a/user_rsa_key b/user_rsa_key
new file mode 100644
index 00000000..ee64f23c
--- /dev/null
+++ b/user_rsa_key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDI7iK3d8eWYZlYloat94c5VjtFY7c/0zuGl8C7uMnZ3t6i2G99
+66hEW0nCFSZkOW5F0XKEVj+EUCHvo8koYC6wiohAqWQnEwIoOoh7GSAcB8gP/qaq
++adIl/Rvlby/mHakj+y05LBND6nFWHAn1y1gOFFKUXSJNRZPXSFy47gqzwIBIwKB
+gQCbANjz7q/pCXZLp1Hz6tYHqOvlEmjK1iabB1oqafrMpJ0eibUX/u+FMHq6StR5
+M5413BaDWHokPdEJUnabfWXXR3SMlBUKrck0eAer1O8m78yxu3OEdpRk+znVo4DL
+guMeCdJB/qcF0kEsx+Q8HP42MZU1oCmk3PbfXNFwaHbWuwJBAOQ/ry/hLD7AqB8x
+DmCM82A9E59ICNNlHOhxpJoh6nrNTPCsBAEu/SmqrL8mS6gmbRKUaya5Lx1pkxj2
+s/kWOokCQQDhXCcYXjjWiIfxhl6Rlgkk1vmI0l6785XSJNv4P7pXjGmShXfIzroh
+S8uWK3tL0GELY7+UAKDTUEVjjQdGxYSXAkEA3bo1JzKCwJ3lJZ1ebGuqmADRO6UP
+40xH977aadfN1mEI6cusHmgpISl0nG5YH7BMsvaT+bs1FUH8m+hXDzoqOwJBAK3Z
+X/za+KV/REya2z0b+GzgWhkXUGUa/owrEBdHGriQ47osclkUgPUdNqcLmaDilAF4
+1Z4PHPrI5RJIONAx+JECQQC/fChqjBgFpk6iJ+BOdSexQpgfxH/u/457W10Y43HR
+soS+8btbHqjQkowQ/2NTlUfWvqIlfxs6ZbFsIp/HrhZL
+-----END RSA PRIVATE KEY-----
diff --git a/user_rsa_key.pub b/user_rsa_key.pub
new file mode 100644
index 00000000..ac722f17
--- /dev/null
+++ b/user_rsa_key.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hpfAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMCKDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iTUWT10hcuO4Ks8= robey@ralph.lag.net