[ZODB-Dev] SecureServerStorage and SecureClientStorage

Greg Jarman gregjarman@yahoo.com
Wed, 9 Oct 2002 10:12:11 -0700 (PDT)


>>>>> "CR" == Christian Reis <kiko@async.com.br> writes:

 CR> Does anybody (*wink*) have the time to look and maybe suggest
 CR> some changes so we could implement something generically useful
 CR> and acceptable for inclusion?

I added a simple authenticator to the Standalone distribution several
months ago. It authenticates both the client to the server and the
server to the client using a chap-like method (the passwords are never
sent across the network).

To test it:
1. Patch StorageServer.py and ClientStorage.py
2. Modify your zeo server startup (probably start.py?) to pass a
ZEOAuthentication.ServerAuthenticator object to the StorageServer
constructor.
3. Modify your client to pass a ZEOAuthentication.ClientAuthenticator
object to the ClientStorage constructor (using the auth= keyword
argument if necessary).
4. Cross your fingers...

I hope yahoo mail doesn't mess up the formatting...

Cheers
Greg

--- StorageServer.patch
--- StorageServer.py.orig       2002-10-09 16:52:15.000000000 +0100
+++ StorageServer.py    2002-10-09 16:59:03.000000000 +0100
@@ -128,7 +128,7 @@

 class StorageServer(asyncore.dispatcher):

-    def __init__(self, connection, storages):
+    def __init__(self, connection, storages, auth=None):

         self.__storages=storages
         for n, s in storages.items():
@@ -136,6 +136,7 @@

         self.__connections={}
         self.__get_connections=self.__connections.get
+       self._authenticator = auth

         self._pack_trigger = trigger.trigger()
         asyncore.dispatcher.__init__(self)
@@ -216,6 +217,7 @@
     'tpc_finish', 'undo', 'undoLog', 'undoInfo', 'versionEmpty',
'versions',
     'transactionalUndo',
     'vote', 'zeoLoad', 'zeoVerify', 'beginZeoVerify', 'endZeoVerify',
+    'require_auth', 'auth_get_info', 'auth_client',
     ):
     storage_methods[n]=1
 storage_method=storage_methods.has_key
@@ -247,6 +249,7 @@
         self.__server=server
         self.__invalidated=[]
         self.__closed=None
+       self._authenticated = 0
         if __debug__: debug='ZEO Server'
         else: debug=0
         SizedMessageAsyncConnection.__init__(self, sock, addr,
debug=debug)
@@ -296,6 +299,12 @@
                 apply(blather,
                       ("call", id(self), ":", name,) + args)

+           if self.__server._authenticator is not None and \
+               not self._authenticated and not
name.startswith('auth'):
+               # Perhaps we should raise an exception here, but lets
just
+               # silently ignore it for now.
+               return
+
             if not storage_method(name):
                 raise 'Invalid Method Name', name
             if hasattr(self, name):
@@ -334,6 +343,31 @@

         self.message_output('E'+r)

+    def require_auth(self, username, message):
+        if self.__server._authenticator is not None:
+           return self.__server._authenticator.encrypt(self.addr,
username, \
+               message)
+       else:
+           return _noreturn
+
+    def auth_get_info(self):
+        if self.__server._authenticator is not None:
+           self.__authentication_message = \
+               self.__server._authenticator.generateMessage()
+           return self.__server._authenticator.getUsername(), \
+                   self.__authentication_message
+       else:
+           return _noreturn
+
+    def auth_client(self, digest):
+        if self.__server._authenticator is not None:
+           if self.__server._authenticator.compare( \
+                   self.__server._authenticator.getUsername(),
+                   self.__authentication_message,
+                   digest):
+               self._authenticated = 1
+           else:
+               raise Exception, "Authentication failed"

     def get_info(self):
         storage=self.__storage
@@ -544,6 +578,7 @@
         storage.tpc_begin(t)
         self.__invalidated=[]

+
     def tpc_begin_sync(self, id, user, description, ext):
         if self.__closed: return
         t=self._transaction

--- ClientStorage.patch
--- ClientStorage.py.orig       2002-10-09 16:51:53.000000000 +0100
+++ ClientStorage.py    2002-10-09 16:57:40.000000000 +0100
@@ -76,6 +76,7 @@
         self._oids=[]
         self._serials=[]
         self._seriald={}
+       self._authenticator = auth

         ClientStorage.inheritedAttribute('__init__')(self, name)

@@ -179,6 +180,22 @@
                     self._call.sendMessage('zeoVerify', oid, s, vs)
                 self._call.sendMessage('endZeoVerify')

+           if self._authenticator is not None:
+               username, message = self._call('auth_get_info')
+               if username != "":
+                   # this server requires authentication
+                   self._call('auth_client', \
+                       self._authenticator.encrypt(None, username,
message))
+
+            if self._authenticator is not None:
+               message = self._authenticator.generateMessage()
+               digest = self._call('require_auth', \
+                   self._authenticator.getUsername(), message)
+               if self._authenticator.compare( \
+                       self._authenticator.getUsername(), message, \
+                       digest) == 0:
+                   raise Exception, "Server failed to authenticate"
+
         finally: self._lock_release()

         if self._async:

--- ZEOAuthentication.py
import sha
import anydbm
import string
import whrandom

class SHAAuthenticator:
    def encrypt(self, addr, username, message):
        s = sha.new()
        s.update(self.getPasswordForUser(username))
        s.update(message)
        return s.hexdigest()

    def compare(self, username, message, digest):
        s = sha.new()
        s.update(self.getPasswordForUser(username))
        s.update(message)
        return digest == s.hexdigest()

    def generateMessage(self):
        """Borrowed from the ASPN Python cookbook"""
        chars = string.letters + string.digits
        passwd = ""

        # determine password size (randomly, but between the given
range)
        passwd_size = whrandom.randint(8, 15)

        for x in range(passwd_size):
            # choose a random alpha-numeric character
            passwd += whrandom.choice(chars)

        return passwd


class ServerAuthenticator(SHAAuthenticator):
    """Example server-side authenticator."""
    def __init__(self, username, passwordfile):
        self._username = 'server'
        self._passwords = { 'server': 'server-password', \
            'user': 'user-password' }

    def getUsername(self):
        """Return our username"""
        return self._username

    def getPasswordForUser(self, username):
        return self._passwords[username]

class ClientAuthenticator(SHAAuthenticator):
    """Example client-side authenticator. You could over-ride these
methods
    to use a GUI to prompt for the password"""
    def __init__(self):
        self._username = None
        self._passwords = {}
        pass

    def getUsername(self):
        """Return our username"""
        while self._username is None or len(self._username) == 0:
            self._username = raw_input("Enter username: ")
        return self._username

    def getPasswordForUser(self, username):
        if not self._passwords.has_key(username):
            self._passwords[username] = None
            while self._passwords[username] is None:
                self._passwords[username] = \
                    raw_input("Enter password for " + username + ": ")
        return self._passwords[username]



__________________________________________________
Do you Yahoo!?
Faith Hill - Exclusive Performances, Videos & More
http://faith.yahoo.com