[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