[security-dev] Credentials API redesign

Anil Saldhana Anil.Saldhana at redhat.com
Wed Dec 5 22:50:29 EST 2012


Comments are inline.  Also we need to be thinking about:
a) combined authentication mechanisms.
b) session management.


On 12/05/2012 08:18 PM, Shane Bryzak wrote:
> Hey guys,
>
> I've completed the first round of redesigns for the credentials API
> based on the feedback provided by Darran and others.  It might require
> some minimal tweaking to address a couple of minor edge cases, however
> at this point in time I'd like to run through the basic design to see
> what you guys think.  Up front I must say that it's more complex than I
> had hoped, however after analysing all the use cases I feel that the
> complexity is a necessity for providing a truly robust, extensible API.
>
> To try and present this in a logical manner, I'm going to describe the
> credentials API in terms of the chronological order of events during the
> authentication process.  To start with, let's take a look at the
> LoginCredentials interface:
>
> public interface LoginCredentials {
>       void invalidate();
> }
invalidate() can be called clear()?
>
> Implementations of this interface are intended to contain the state
> provided by the user in order to carry out authentication.  While the
> Credential interface is designed to represent an atomic credential value
> (such as a password, certificate, or biometric data such as a
> fingerprint), LoginCredentials is designed to encapsulate the credential
> value with additional state required by the authentication process.
> Let's take a look at an implementation supporting the most common form
> of credentials, a username and password:
>
> public class UsernamePasswordCredentials implements LoginCredentials {
>
>       private String username;
>
>       private PasswordCredential password;
>
>       public String getUsername() {
>           return username;
>       }
>
>       public void setUsername(String username) {
>           this.username = username;
>       }
>
>       public PasswordCredential getPassword() {
>           return password;
>       }
>
>       public void setPassword(PasswordCredential password) {
>           this.password = password;
>       }
>
>       @Override
>       public void invalidate() {
>           username = null;
>           password.clear();
>       }
> }
>
> This implementation provides the capability for setting a username
> (represented as a String) and a PasswordCredential.  Other
> implementations are free to define whichever properties they require,
> for example a CertificateCredentials class may simply contain an
> X509CertificateCredential and nothing else.
>
> Once the user provides their credentials, the LoginCredentials instance
> can be passed to IdentityManager via the following method:
>
> public interface IdentityManager {
>       User validateCredentials(LoginCredentials credentials);
> }
>
> To handle the actual processing of the credentials and perform whatever
> business logic is required to authenticate the user, the following SPI
> interface is used:
>
> public interface CredentialHandler {
>       User validate(LoginCredentials credentials, IdentityStore store);
>       void update(User user, Credential credential, IdentityStore store);
> }

The credential validation/updation should be an implementation strategy of
the IdentityStore implementation rather than the IdentityManager.

In the case of LDAP servers, they never store user password in the clear 
but have
some form of hashing/salting etc. The LDAPIdentityStore implementation 
always provides
the ldap server with the clear text password and the ldap server uses 
its internal strategies to
convert the clear text password into some hash etc and validates with 
the store value.

I think the same strategy can be used by the JPA implementation. The 
IdentityManager passes it
the clear text password to the IS implementation.  Based on pluggable 
credential handling strategies
at the IdentityStore implementation, computations are performed on the 
passed credential and validated/updated
in the underlying data store.

In Darran's use case of (username + hash + realmname), the underlying 
store needs to store one value that
is a combination of (hash + realmname).  Now the IdentityManager will 
pass to the IdentityStore the string (hash+realm),
now Darran's plugged handlers at the IdentityStore implementation will 
convert this into some form that matches with
the stored value in the DB/LDAP.

>
> Since authentication is handled differently depending on what type of
> IdentityStore is being used, there will be many CredentialHandler
> implementations to address each use case combination.  To determine the
> correct CredentialHandler implementation to use for authentication, the
> IdentityManager calls CredentialHandlerFactory.getCredentialHandler(),
> passing in the class of the LoginCredentials parameter and the class of
> the IdentityStore parameter:
>
> public interface CredentialHandlerFactory {
>
>       CredentialHandler getCredentialHandler(Class<? extends
> LoginCredentials> credentialsClass,
>               Class<? extends IdentityStore> identityStoreClass);
> }
>
> This allows us to provide a separate CredentialHandler implementation
> for each combination of LoginCredentials and IdentityStore.  For
> example, for a standard username/password authentication using
> JPAIdentityStore we may have a JPAUsernamePasswordCredentialHandler that
> compares a calculated hash based on the password (and possible other
> values) against a pre-calculated hash stored in the database.  For a
> username/password authentication against LDAPIdentityStore we may have
> an LDAPUsernamePasswordCredentialHandler that performs a bind operation
> against the directory to ensure that the credentials supplied are
> valid.  As the SPI is extensible, it is also possible for developers to
> provide their own CredentialHandler implementations to suit whichever
> authentication process they require.
>
> If the CredentialHandler.validate() method completes successfully, it is
> expected to return a User instance to indicate that authentication was
> successful.  If authentication is unsuccessful (or possibly requires
> multiple steps) then it's up to the caller and implementation to cater
> for this, most likely by providing additional state within the
> LoginCredentials implementation.
>
> To sidetrack for a moment, the CredentialHandlerFactory is configured as
> part of the IdentityConfiguration which is passed to the
> IdentityManager's bootstrap method during startup:
>
> public class IdentityConfiguration {
>         public CredentialHandlerFactory getCredentialHandlerFactory() {
>           return credentialHandlerFactory;
>       }
>
>       public void setCredentialHandlerFactory(CredentialHandlerFactory
> factory) {
>           this.credentialHandlerFactory = factory;
>       }
> }
>
> Continuing on, the CredentialHandler is free to execute whichever
> business logic is required to authenticate the provided LoginCredentials
> value.  It has direct access to the IdentityStore instance, in which the
> previous credential management methods have been replaced with the
> following two new methods (side note - the CredentialHandler doesn't
> *have* to use these methods, it could instead possibly invoke other
> IdentityStore methods by casting the IdentityStore to the appropriate type):
>
> public interface IdentityStore {
>       void storeCredential(CredentialStorage storage);
>
>       CredentialStorage retrieveCredential(Class<? extends
> CredentialStorage> storageClass);
> }
>
> At present CredentialStorage is a simple SPI interface that declares a
> single method:
>
> public interface CredentialStorage {
>       Date getExpiryDate();
> }
>
> The reason that we have introduced a totally new interface here rather
> than just use the Credential interface, is that credential information
> stored by the IdentityStore is often different to the credential
> information provided by a user to authenticate.  For example, in
> username/password authentication the user provides a plain text
> password, however the data we have stored in the IdentityStore is
> typically not the password itself, but a hash value (or at least I would
> hope so).  With that in mind, the introduction of the CredentialStorage
> interface formalizes the disconnect between user-provided credential
> state and stored credential state, and in turn provides a more flexible
> design.  This is not to say that a Credential implementation can't be
> dual purpose, in fact this is fully supported thanks to
> CredentialStorage essentially being a marker interface, so
> X509CertificateCredential for example could easily implement both the
> Credential and CredentialStorage interfaces.
>
> To round this off with a solid example, here's what a CredentialStorage
> implementation that stores a password hash might look like:
>
> public class PasswordHash implements CredentialStorage {
>       private byte[] hash;
>       public void setHash(byte[] hash) {
>           this.hash = hash;
>       }
>       public byte[] getHash() {
>           return hash;
>       }
> }
>
> There's actually one more step here, and that's the metadata required by
> the IdentityStore to know which state of the CredentialStorage instance
> to actually store.  To enable this, we have introduced the @Stored
> annotation, which when added to the above example looks like this:
>
> public class PasswordHash implements CredentialStorage {
>       @Stored private byte[] hash;
>       public void setHash(byte[] hash) {
>           this.hash = hash;
>       }
>       public byte[] getHash() {
>           return hash;
>       }
> }
>
> Any properties annotated with @Stored must be Serializable, to allow
> storage by the IdentityStore.
>
> One last thing that needs to be covered is the updating of credentials.
> IdentityManager also provides this method which is used to update a
> user's credentials:
>
> public interface IdentityManager {
>       void updateCredential(User user, Credential credential);
> }
>
> Like credential validation, a CredentialHandler is looked up the same
> way and its update() method is invoked with the User, Credential and
> IdentityStore parameters.  It is up to the CredentialHandler
> implementation to create the appropriate CredentialStorage instances (or
> perform whichever other business logic that might be required) to
> persist to the backend IdentityStore.
>
> That pretty much covers everything.  I think this design in essences
> addresses all of the use cases discussed so far, and should be
> relatively future-proof.  Sorry again for another wall of text, but I'm
> looking forward to hearing feedback on this.  There's still quite a lot
> of work to do in this area on the implementation side of things (such as
> decoupling the existing credential validation code from the
> IdentityStore implementations and implementing it as CredentialHandlers)
> but we'll get to that once everyone has a chance to review this new design.
>
> Thanks,
> Shane


More information about the security-dev mailing list