[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.
This commit is contained in:
Robey Pointer 2003-12-30 22:24:21 +00:00
parent 48c7d888a2
commit daa8a2ec0d
12 changed files with 324 additions and 188 deletions

View File

@ -8,8 +8,9 @@ RELEASE=charmander
release: release:
python ./setup.py sdist --formats=zip python ./setup.py sdist --formats=zip
docs: docs: always
epydoc -o docs/ paramiko epydoc -o docs/ paramiko
always:
# places where the version number is stored: # places where the version number is stored:
# #

View File

@ -1,6 +1,6 @@
#!/usr/bin/python #!/usr/bin/python
import sys, os, socket, threading, logging, traceback import sys, os, socket, threading, logging, traceback, base64
import paramiko import paramiko
# setup logging # setup logging
@ -18,10 +18,14 @@ if len(l.handlers) == 0:
host_key = paramiko.DSSKey() host_key = paramiko.DSSKey()
host_key.read_private_key_file('demo_dss_key') 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): 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): def check_channel_request(self, kind, chanid):
if kind == 'session': if kind == 'session':
return ServerChannel(chanid) return ServerChannel(chanid)
@ -32,6 +36,11 @@ class ServerTransport(paramiko.Transport):
return self.AUTH_SUCCESSFUL return self.AUTH_SUCCESSFUL
return self.AUTH_FAILED 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): class ServerChannel(paramiko.Channel):
"Channel descendant that pretends to understand pty and shell requests" "Channel descendant that pretends to understand pty and shell requests"
@ -79,11 +88,13 @@ try:
t.add_server_key(host_key) t.add_server_key(host_key)
t.ultra_debug = 0 t.ultra_debug = 0
t.start_server(event) t.start_server(event)
# print repr(t) while 1:
event.wait(10) event.wait(0.1)
if not t.is_active(): if not t.is_active():
print '*** SSH negotiation failed.' print '*** SSH negotiation failed.'
sys.exit(1) sys.exit(1)
if event.isSet():
break
# print repr(t) # print repr(t)
# wait for auth # wait for auth

View File

@ -41,5 +41,5 @@ class DSSKey (dsskey.DSSKey):
__all__ = [ 'Transport', 'Channel', 'RSAKey', 'DSSKey', 'transport', __all__ = [ 'Transport', 'Channel', 'RSAKey', 'DSSKey', 'transport',
'auth_transport', 'channel', 'rsakey', 'ddskey', 'util', 'auth_transport', 'channel', 'rsakey', 'dsskey', 'util',
'SSHException' ] 'SSHException' ]

View File

@ -13,7 +13,10 @@ _DISCONNECT_SERVICE_NOT_AVAILABLE, _DISCONNECT_AUTH_CANCELLED_BY_USER, \
class Transport (BaseTransport): 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) AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED = range(3)
@ -53,15 +56,23 @@ class Transport (BaseTransport):
""" """
return self.authenticated and self.active 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): 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): 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') raise SSHException('No existing session')
try: try:
self.lock.acquire() self.lock.acquire()
@ -74,7 +85,20 @@ class Transport (BaseTransport):
self.lock.release() self.lock.release()
def auth_password(self, username, password, event): 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): 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 send the password unless we're on a secure link
raise SSHException('No existing session') raise SSHException('No existing session')
@ -88,7 +112,58 @@ class Transport (BaseTransport):
finally: finally:
self.lock.release() 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 = Message()
m.add_byte(chr(_MSG_DISCONNECT)) m.add_byte(chr(_MSG_DISCONNECT))
m.add_int(_DISCONNECT_SERVICE_NOT_AVAILABLE) m.add_int(_DISCONNECT_SERVICE_NOT_AVAILABLE)
@ -97,7 +172,7 @@ class Transport (BaseTransport):
self._send_message(m) self._send_message(m)
self.close() self.close()
def disconnect_no_more_auth(self): def _disconnect_no_more_auth(self):
m = Message() m = Message()
m.add_byte(chr(_MSG_DISCONNECT)) m.add_byte(chr(_MSG_DISCONNECT))
m.add_int(_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE) m.add_int(_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
@ -106,7 +181,19 @@ class Transport (BaseTransport):
self._send_message(m) self._send_message(m)
self.close() 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() service = m.get_string()
if self.server_mode and (service == 'ssh-userauth'): if self.server_mode and (service == 'ssh-userauth'):
# accepted # accepted
@ -116,9 +203,9 @@ class Transport (BaseTransport):
self._send_message(m) self._send_message(m)
return return
# dunno this one # 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() service = m.get_string()
if service == 'ssh-userauth': if service == 'ssh-userauth':
self._log(DEBUG, 'userauth is OK') self._log(DEBUG, 'userauth is OK')
@ -134,30 +221,16 @@ class Transport (BaseTransport):
m.add_boolean(1) m.add_boolean(1)
m.add_string(self.private_key.get_name()) m.add_string(self.private_key.get_name())
m.add_string(str(self.private_key)) 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: else:
raise SSHException('Unknown auth method "%s"' % self.auth_method) raise SSHException('Unknown auth method "%s"' % self.auth_method)
self._send_message(m) self._send_message(m)
else: else:
self._log(DEBUG, 'Service request "%s" accepted (?)' % service) self._log(DEBUG, 'Service request "%s" accepted (?)' % service)
def get_allowed_auths(self, username): def _parse_userauth_request(self, m):
"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):
if not self.server_mode: if not self.server_mode:
# er, uh... what? # er, uh... what?
m = Message() m = Message()
@ -174,11 +247,11 @@ class Transport (BaseTransport):
method = m.get_string() method = m.get_string()
self._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username)) self._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username))
if service != 'ssh-connection': if service != 'ssh-connection':
self.disconnect_service_not_available() self._disconnect_service_not_available()
return return
if (self.auth_username is not None) and (self.auth_username != username): 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._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 return
if method == 'none': if method == 'none':
result = self.check_auth_none(username) result = self.check_auth_none(username)
@ -194,8 +267,32 @@ class Transport (BaseTransport):
else: else:
result = self.check_auth_password(username, password) result = self.check_auth_password(username, password)
elif method == 'publickey': elif method == 'publickey':
# FIXME sig_attached = m.get_boolean()
result = self.check_auth_none(username) 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: else:
result = self.check_auth_none(username) result = self.check_auth_none(username)
# okay, send result # okay, send result
@ -215,15 +312,15 @@ class Transport (BaseTransport):
self.auth_fail_count += 1 self.auth_fail_count += 1
self._send_message(m) self._send_message(m)
if self.auth_fail_count >= 10: 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._log(INFO, 'Authentication successful!')
self.authenticated = True self.authenticated = True
if self.auth_event != None: if self.auth_event != None:
self.auth_event.set() self.auth_event.set()
def parse_userauth_failure(self, m): def _parse_userauth_failure(self, m):
authlist = m.get_list() authlist = m.get_list()
partial = m.get_boolean() partial = m.get_boolean()
if partial: if partial:
@ -237,7 +334,7 @@ class Transport (BaseTransport):
if self.auth_event != None: if self.auth_event != None:
self.auth_event.set() self.auth_event.set()
def parse_userauth_banner(self, m): def _parse_userauth_banner(self, m):
banner = m.get_string() banner = m.get_string()
lang = m.get_string() lang = m.get_string()
self._log(INFO, 'Auth banner: ' + banner) self._log(INFO, 'Auth banner: ' + banner)
@ -245,11 +342,11 @@ class Transport (BaseTransport):
_handler_table = BaseTransport._handler_table.copy() _handler_table = BaseTransport._handler_table.copy()
_handler_table.update({ _handler_table.update({
_MSG_SERVICE_REQUEST: parse_service_request, _MSG_SERVICE_REQUEST: _parse_service_request,
_MSG_SERVICE_ACCEPT: parse_service_accept, _MSG_SERVICE_ACCEPT: _parse_service_accept,
_MSG_USERAUTH_REQUEST: parse_userauth_request, _MSG_USERAUTH_REQUEST: _parse_userauth_request,
_MSG_USERAUTH_SUCCESS: parse_userauth_success, _MSG_USERAUTH_SUCCESS: _parse_userauth_success,
_MSG_USERAUTH_FAILURE: parse_userauth_failure, _MSG_USERAUTH_FAILURE: _parse_userauth_failure,
_MSG_USERAUTH_BANNER: parse_userauth_banner, _MSG_USERAUTH_BANNER: _parse_userauth_banner,
}) })

View File

@ -3,7 +3,6 @@
import base64 import base64
from ssh_exception import SSHException from ssh_exception import SSHException
from message import Message from message import Message
from transport import _MSG_USERAUTH_REQUEST
from util import inflate_long, deflate_long from util import inflate_long, deflate_long
from Crypto.PublicKey import DSA from Crypto.PublicKey import DSA
from Crypto.Hash import SHA from Crypto.Hash import SHA
@ -14,9 +13,11 @@ from util import format_binary
class DSSKey (PKey): class DSSKey (PKey):
def __init__(self, msg=None): def __init__(self, msg=None, data=None):
self.valid = 0 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 return
self.p = msg.get_mpint() self.p = msg.get_mpint()
self.q = msg.get_mpint() self.q = msg.get_mpint()
@ -36,9 +37,33 @@ class DSSKey (PKey):
m.add_mpint(self.y) m.add_mpint(self.y)
return str(m) 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): def get_name(self):
return 'ssh-dss' 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): def verify_ssh_sig(self, data, msg):
if not self.valid: if not self.valid:
return 0 return 0
@ -59,21 +84,6 @@ class DSSKey (PKey):
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q))) dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q)))
return dss.verify(sigM, (sigR, sigS)) 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): def read_private_key_file(self, filename):
# private key file contains: # private key file contains:
# DSAPrivateKey = { version = 0, p, q, g, y, x } # DSAPrivateKey = { version = 0, p, q, g, y, x }
@ -94,15 +104,3 @@ class DSSKey (PKey):
self.x = keylist[5] self.x = keylist[5]
self.size = len(deflate_long(self.p, 0)) self.size = len(deflate_long(self.p, 0))
self.valid = 1 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))

View File

@ -145,7 +145,7 @@ class KexGex(object):
m.add_byte(chr(_MSG_KEXDH_GEX_REPLY)) m.add_byte(chr(_MSG_KEXDH_GEX_REPLY))
m.add_string(key) m.add_string(key)
m.add_mpint(self.f) m.add_mpint(self.f)
m.add_string(sig) m.add_string(str(sig))
self.transport._send_message(m) self.transport._send_message(m)
self.transport._activate_outbound() self.transport._activate_outbound()

View File

@ -97,6 +97,6 @@ class KexGroup1(object):
m.add_byte(chr(_MSG_KEXDH_REPLY)) m.add_byte(chr(_MSG_KEXDH_REPLY))
m.add_string(key) m.add_string(key)
m.add_mpint(self.f) m.add_mpint(self.f)
m.add_string(sig) m.add_string(str(sig))
self.transport._send_message(m) self.transport._send_message(m)
self.transport._activate_outbound() self.transport._activate_outbound()

View File

@ -7,28 +7,50 @@ class PKey (object):
Base class for public keys. 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 Create a new instance of this public key type. If C{msg} is given,
C{None}, the key's public part(s) will be filled in from the the key's public part(s) will be filled in from the message. If
message. 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 @param msg: an optional SSH L{Message} containing a public key of this
type. type.
@type msg: L{Message} @type msg: L{Message}
@param data: an optional string containing a public key of this type
@type data: string
""" """
pass pass
def __str__(self): def __str__(self):
""" """
Return a string of an SSH L{Message} made up of the public part(s) of 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. @return: string representation of an SSH key message.
@rtype: string @rtype: string
""" """
return '' 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): def get_name(self):
""" """
Return the name of this private key implementation. Return the name of this private key implementation.
@ -50,6 +72,20 @@ class PKey (object):
""" """
return MD5.new(str(self)).digest() 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): def verify_ssh_sig(self, data, msg):
""" """
Given a blob of data, and an SSH message representing a signature of Given a blob of data, and an SSH message representing a signature of
@ -65,23 +101,6 @@ class PKey (object):
""" """
return False 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): def read_private_key_file(self, filename):
""" """
Read private key contents from a file into this object. Read private key contents from a file into this object.
@ -94,19 +113,3 @@ class PKey (object):
@raise binascii.Error: on base64 decoding error @raise binascii.Error: on base64 decoding error
""" """
pass 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
"""

View File

@ -1,7 +1,6 @@
#!/usr/bin/python #!/usr/bin/python
from message import Message from message import Message
from transport import _MSG_USERAUTH_REQUEST
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from Crypto.Hash import SHA from Crypto.Hash import SHA
from ber import BER from ber import BER
@ -11,9 +10,11 @@ import base64
class RSAKey (PKey): class RSAKey (PKey):
def __init__(self, msg=None): def __init__(self, msg=None, data=''):
self.valid = 0 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 return
self.e = msg.get_mpint() self.e = msg.get_mpint()
self.n = msg.get_mpint() self.n = msg.get_mpint()
@ -29,6 +30,12 @@ class RSAKey (PKey):
m.add_mpint(self.n) m.add_mpint(self.n)
return str(m) 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): def get_name(self):
return 'ssh-rsa' return 'ssh-rsa'
@ -41,6 +48,15 @@ class RSAKey (PKey):
filler = '\xff' * (self.size - len(SHA1_DIGESTINFO) - len(data) - 3) filler = '\xff' * (self.size - len(SHA1_DIGESTINFO) - len(data) - 3)
return '\x00\x01' + filler + '\x00' + SHA1_DIGESTINFO + data 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): def verify_ssh_sig(self, data, msg):
if (not self.valid) or (msg.get_string() != 'ssh-rsa'): if (not self.valid) or (msg.get_string() != 'ssh-rsa'):
return False return False
@ -52,15 +68,6 @@ class RSAKey (PKey):
rsa = RSA.construct((long(self.n), long(self.e))) rsa = RSA.construct((long(self.n), long(self.e)))
return rsa.verify(hash, (sig,)) 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): def read_private_key_file(self, filename):
# private key file contains: # private key file contains:
# RSAPrivateKey = { version = 0, n, e, d, p, q, d mod p-1, d mod q-1, q**-1 mod p } # 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.q = keylist[5]
self.size = len(deflate_long(self.n, 0)) self.size = len(deflate_long(self.n, 0))
self.valid = 1 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))

View File

@ -155,6 +155,26 @@ class BaseTransport (threading.Thread):
self.server_accepts = [ ] self.server_accepts = [ ]
self.server_accept_cv = threading.Condition(self.lock) 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): def start_client(self, event=None):
self.completion_event = event self.completion_event = event
self.start() self.start()
@ -179,6 +199,19 @@ class BaseTransport (threading.Thread):
self.server_key_dict[key.get_name()] = key self.server_key_dict[key.get_name()] = key
def get_server_key(self): 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: try:
return self.server_key_dict[self.host_key_type] return self.server_key_dict[self.host_key_type]
except KeyError: except KeyError:
@ -228,37 +261,6 @@ class BaseTransport (threading.Thread):
return False return False
load_server_moduli = staticmethod(load_server_moduli) 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): def close(self):
""" """
Close this session, and any open channels that are tied to it. Close this session, and any open channels that are tied to it.
@ -468,6 +470,17 @@ class BaseTransport (threading.Thread):
### internals... ### 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): def _unlink_channel(self, chanid):
"used by a Channel to remove itself from the active channel list" "used by a Channel to remove itself from the active channel list"
try: try:
@ -597,13 +610,16 @@ class BaseTransport (threading.Thread):
"used by a kex object to register the next packet type it expects to see" "used by a kex object to register the next packet type it expects to see"
self.expected_packet = type self.expected_packet = type
def _verify_key(self, host_key, sig): def _key_from_blob(self, keytype, keyblob):
if self.host_key_type == 'ssh-rsa': if keytype == 'ssh-rsa':
key = RSAKey(Message(host_key)) return RSAKey(Message(keyblob))
elif self.host_key_type == 'ssh-dss': elif keytype == 'ssh-dss':
key = DSSKey(Message(host_key)) return DSSKey(Message(keyblob))
else: 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: if (key == None) or not key.valid:
raise SSHException('Unknown host key type') raise SSHException('Unknown host key type')
if not key.verify_ssh_sig(self.H, Message(sig)): if not key.verify_ssh_sig(self.H, Message(sig)):
@ -745,7 +761,7 @@ class BaseTransport (threading.Thread):
kind of key negotiation we support. kind of key negotiation we support.
""" """
if self.server_mode: 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 # can't do group-exchange if we don't have a pack of potential primes
self.preferred_kex.remove('diffie-hellman-group-exchange-sha1') self.preferred_kex.remove('diffie-hellman-group-exchange-sha1')
available_server_keys = filter(self.server_key_dict.keys().__contains__, available_server_keys = filter(self.server_key_dict.keys().__contains__,

15
user_rsa_key Normal file
View File

@ -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-----

1
user_rsa_key.pub Normal file
View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hpfAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMCKDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iTUWT10hcuO4Ks8= robey@ralph.lag.net