[ZODB-Dev] RFC: Proposal for AuthZEO (was SecureZEO one day)

Christian Reis kiko@async.com.br
Wed, 15 Jan 2003 19:35:41 -0200


So me and Johan have finally found some time to work on this again. I'm
making an outline here of what we are planning and we would welcome
comments. Comments are appreciated, hopefully sooner since we want to
have this done soon. We would specially like comments on sections 3, 4
and 5.

One note we'd like to make is the change from the original SecureZEO [1]
name to AuthZEO. Itamar suggested this in light of the fact that we
aren't providing necessarily any security beyond trivial user
authentication (Toby's message the past week [2] is a good reminder of
other important security issues).

[1] http://www.zope.org/Wikis/ZODB/ZEO2
[2] http://lists.zope.org/pipermail/zodb-dev/2003-January/004169.html

AuthZEO Proposal

1. Objective

To provide authentication based on user and password strings provided at
storage instantiation time by the ZEO client. The ZEO server should
allow or deny access comparing these strings to a trusted local database
at connection time. All subsequent transactions are considered to be
trusted.

This mechanism might be extendable to provide generic permissions to
objects. This is not currently in this proposal, but contributions are
welcome. We don't touch on the issue of read-only versus read-write
access to the storage, but we believe it would be a simple extension.

2. Basic Design

Three new classes are introduced: AuthStorageServer, AuthZEOStorage
and AuthClientStorage. These classes inherit from StorageServer,
ZEOStorage and ClientStorage respectively. They will implement the
additional methods required to provide authentication. This allows
previous applications to run unchanged and minimizes the impact in code
for the implementation.

A AuthStorageServer should only be accessible from a
AuthStorageClient, and they should not access or be accessible from
their non-authenticated counterparts.

The new classes will implement an authentication protocol (which is
described in section 3) and in the case of valid authentication, proceed
normally. If authentication fails, an exception should be raised to the
client.

The client will initiate the connection creating a AuthClientStorage:

    from ZEO.ClientStorage import AuthClientStorage
    storage = AuthClientStorage(('localhost', 23020),
                                  username='johndoe', 
                                  password='secret')

This should be the only user-visible change necessary related to ZODB
authentication.

3. Changes to ZEO Required

The changes that need to be made can be summarized as:
    
    a. Define AuthStorageServer (should be trivial):

        class AuthStorageServer(StorageServer):
            ZEOStorageClass = AuthZEOStorage

    b. Implement hashing mechanism (see next section). This might go
       into a common module since it will be used by both client and
       server.

    c. Implement AuthZEOStorage:
       
        - Implement auth() to do the actual authentication (compare
          ciphers) and set state that indicates the client is
          authenticated. 
        - Alter register() to verify if client is authenticated; this
          avoids a client attack with a modified ClientStorage.

    d. Implement AuthClientStorage:

        - auth()'s matching RPC stub must be implemented.
        - modify testConnection() to produce ciphertext and call the RPC
          stub. 
          
          XXX: Would another method be more appropriate to have this
               call done?

    e. Alter zrpc/client.py:ConnectWrapper to handle exceptions raised
       by testConnection().

Other details may appear, this is only a preliminary analysis based on
the previous work done.

4. Protocol

The basic protocol works as follows:

    a. Client requests a new connection to the server by creating a new
       AuthClientStorage.

    b. Server acknowledges and sends a challenge to the client. The
       challenge consists of an integer which will be randomly generated.

    c. The client receives the integer and encrypts the password using
       it. The ciphertext generated is sent to the server.

       This requires some discussion. We could use the integer as a key
       in a one-way encryption scheme, but it seems the sha and md5
       modules as available in the Python 2.1 standard library are
       simple hashes. In order to avoid having to roll our own
       encryption implementation, we could simply do:

       # Uses the pickle and sha modules

       auth = pickle.dumps({ 'user': username,
                             'pw'  : password,
                             'challenge': server_challenge })
       ciphertext = sha.new(auth).hexdigest()
       
       on both client and server-side. I believe this approach is safe
       from both a malicious client implementation and network sniffing
       attacks, but I can't vouch for cryptographic safety beyond that
       (i.e. what artifacts pickle and a python dictionary might
       present upon digest).

    d. The server implements the same mechanism and compares its
       ciphertext with the ciphertext the client sent. If they match,
       the client is considered to be authenticated and the normal
       storage initialization proceeds.

5. Notes and Issues (RFC)

- Where should we store the username/password database?

    The obvious alternatives are a text file or a storage. Using a
    storage seems to be a complex solution, however, because at
    authentication time no storage is guaranteed to be open and we have
    to consider the possibility to manipulate authenticate information
    remotely. We *could* use a secondary storage which is used only to
    store authentication data, and a special API to manipulate this
    storage.

    The simpler alternative is storing a text file with a
    username:password mapping. This requires being on the server to
    manipulate data unless a specialized interface and protocol is
    developed for this.

- Should we store encrypted passwords or plaintext passwords on the
  server side?
  
    Storing plaintext passwords provides for easier manipulation and
    providing reminders to forgetful users. However, it is always
    arguable that it is an evil solution. Making this file readable only
    to a certain user may improve things, but requires the server to be
    setup to run as a special user.

    Storing a crypted passwords provides some safety but none of the
    above niceties. If we decide to store crypted versions of the
    passwords, in the protocol above where 'password' is cited read
    'password ciphertext'. I'm not sure of encryption artifacts that may
    result from rehashing the ciphertext.

- Is the hashing algorithm safe? How big should the server challenge be?

- What exception should be raised if authentication fails?

- Is trusting everything post-authentication a reasonable expectation?
  Can the connection be easily hijacked?

- Should we place the new classes in the the ClientStorage/StorageServer
  modules, or in a special module/set of modules?

Take care,
--
Christian Reis, Senior Engineer, Async Open Source, Brazil.
http://async.com.br/~kiko/ | [+55 16] 261 2331 | NMFL