I'm using Tomcat 8.5.59 and have the following Realm in my context.xml:
<Realm className="org.apache.catalina.realm.LockOutRealm" >
<Realm className="org.apache.catalina.realm.DataSourceRealm" dataSourceName="jdbc/MyName" localDataSource="true">
<CredentialHandler className="org.apache.catalina.realm.NestedCredentialHandler">
<CredentialHandler className="org.apache.catalina.realm.SecretKeyCredentialHandler" />
<CredentialHandler className="org.apache.catalina.realm.MessageDigestCredentialHandler" algorithm="SHA-512" />
</CredentialHandler>
</Realm>
</Realm>
I would like to get the CredentialHandler in my Java app to hash and store passwords. As per Christopher Schultz's presentation at http://people.apache.org/~schultz/ApacheCon%20NA%202016/Seamless%20Upgrades%20for%20Credential%20Security%20in%20Apache%20Tomcat.pdf I get it like this:
CredentialHandler ch = (CredentialHandler) application.getAttribute(Globals.CREDENTIAL_HANDLER);
The problem is that it returns a blank/default credential handler, not the one I have configured.
If I remove the LockoutRealm definition, it works fine, so it seems that having a nested realm (LockoutRealm, DataSourceRealm), causes it to fail. Looking through the Tomcat code, it seems that the code that calls setAttribute(Globals.CREDENTIAL_HANDLER)
doesn't take CombinedRealm
into account.
How can I get the configured credential handler in my app so that I can call the .matches() and .mutate() methods? I would rather not remove the Lockout Realm because that would compromise security.
Edit:
This use-case is a common one: The application must be able to save the mutated password to the database so that tomcat can do form-based authentication against it when users log in. Every time a new user account is created, the user's password must be mutated and saved to the database.
Additionally, when a user wants to change their password, a form would ask for the current and new passwords - .matches() is used to confirm that the "current" password matches their existing password before changing it to the new password (which again must be mutated).
Using the credential handler defined in the context is important to ensure the password is mutated in the exact way that tomcat requires. The alternative would be for every application to provide their own corresponding libraries, which seems wasteful and error-prone given that tomcat already has them.
This is all described in Christopher Schultz's link at the start of my question.
I think this is all pretty standard and Tomcat provides CredentialHandler ch = (CredentialHandler) application.getAttribute(Globals.CREDENTIAL_HANDLER)
for this purpose. The issue is that the implementation doesn't account for LockoutRealm being used - it assumes that the credential handler is defined directly under the top-level realm, so I'm wondering if it's an oversight/bug in tomcat or if it works like that by design and if there's some way I can access the .mutate() and .matches() functions of the defined CredentialHandler
while also using the LockOutRealm
.
Also, tomcat used to provide RealmBase.Digest(String credentials, String algorithm, String encoding)
for this use-case, but that method is deprecated in 8.5 and removed in 9, so I understand CredentialHandler ch = (CredentialHandler) application.getAttribute(Globals.CREDENTIAL_HANDLER)
is the new way we're supposed to be doing it.
Edit 2:
getAttribute(Globals.CREDENTIAL_HANDLER)
returns a default CredentialHandler
that doesn't encrypt the password at all. The debugger shows the class being StandardContext
. Stepping through the code, that all seems correct, but it doesn't drill into the DataSourceRealm
to get the defined NestedCredentialHandler
, instead, it looks at LockoutRealm
, doesn't find an immediate child CredentialHandler
, so creates and returns a default one. I believe this is as documented in https://tomcat.apache.org/tomcat-8.5-doc/config/credentialhandler.html:
A CredentialHandler element MUST be nested inside a Realm component. If it is not included, a default CredentialHandler will be created using the MessageDigestCredentialHandler.