summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobey Pointer <robey@lag.net>2003-12-30 22:24:21 +0000
committerRobey Pointer <robey@lag.net>2003-12-30 22:24:21 +0000
commitdaa8a2ec0d6d3706ea3864fcab5ed56597f3612a (patch)
tree73b01cd33f631034448bc6b1779fdda4a542794e
parent48c7d888a22a6810a32f6d25cdd6b561803166cd (diff)
[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-18]
added public-key support to server mode, more docs added public-key support to server mode (it can now verify a client signature) and added a demo of that to the demo_server.py script (user_rsa_key). in the process, cleaned up the API of PKey so that now it only has to know about signing and verifying ssh2 blobs, and can be hashed and compared with other keys (comparing & hashing only the public parts of the key). keys can also be created from strings now too. some more documentation and hiding private methods.
-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