/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina.realm;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;

import org.apache.catalina.CredentialHandler;
import org.apache.juli.logging.Log;
import org.apache.tomcat.util.buf.HexUtils;
import org.apache.tomcat.util.res.StringManager;

Base implementation for the Tomcat provided CredentialHandlers.
/** * Base implementation for the Tomcat provided {@link CredentialHandler}s. */
public abstract class DigestCredentialHandlerBase implements CredentialHandler { protected static final StringManager sm = StringManager.getManager(DigestCredentialHandlerBase.class); public static final int DEFAULT_SALT_LENGTH = 32; private int iterations = getDefaultIterations(); private int saltLength = getDefaultSaltLength(); private final Object randomLock = new Object(); private volatile Random random = null; private boolean logInvalidStoredCredentials = false;
Returns:the number of iterations of the associated algorithm that will be used when creating a new stored credential for a given input credential.
/** * @return the number of iterations of the associated algorithm that will be * used when creating a new stored credential for a given input credential. */
public int getIterations() { return iterations; }
Set the number of iterations of the associated algorithm that will be used when creating a new stored credential for a given input credential.
Params:
  • iterations – the iterations count
/** * Set the number of iterations of the associated algorithm that will be * used when creating a new stored credential for a given input credential. * @param iterations the iterations count */
public void setIterations(int iterations) { this.iterations = iterations; }
Returns:the salt length that will be used when creating a new stored credential for a given input credential.
/** * @return the salt length that will be used when creating a new stored * credential for a given input credential. */
public int getSaltLength() { return saltLength; }
Set the salt length that will be used when creating a new stored credential for a given input credential.
Params:
  • saltLength – the salt length
/** * Set the salt length that will be used when creating a new stored * credential for a given input credential. * @param saltLength the salt length */
public void setSaltLength(int saltLength) { this.saltLength = saltLength; }
When checking input credentials against stored credentials will a warning message be logged if invalid stored credentials are discovered?
Returns:true if logging will occur
/** * When checking input credentials against stored credentials will a warning * message be logged if invalid stored credentials are discovered? * @return <code>true</code> if logging will occur */
public boolean getLogInvalidStoredCredentials() { return logInvalidStoredCredentials; }
Set whether a warning message will be logged if invalid stored credentials are discovered while checking input credentials against stored credentials?
Params:
  • logInvalidStoredCredentials – true to log, the default value is false
/** * Set whether a warning message will be logged if invalid stored * credentials are discovered while checking input credentials against * stored credentials? * @param logInvalidStoredCredentials <code>true</code> to log, the * default value is <code>false</code> */
public void setLogInvalidStoredCredentials(boolean logInvalidStoredCredentials) { this.logInvalidStoredCredentials = logInvalidStoredCredentials; } @Override public String mutate(String userCredential) { byte[] salt = null; int iterations = getIterations(); int saltLength = getSaltLength(); if (saltLength == 0) { salt = new byte[0]; } else if (saltLength > 0) { // Double checked locking. OK since random is volatile. if (random == null) { synchronized (randomLock) { if (random == null) { random = new SecureRandom(); } } } salt = new byte[saltLength]; // Concurrent use of this random is unlikely to be a performance // issue as it is only used during stored password generation. random.nextBytes(salt); } String serverCredential = mutate(userCredential, salt, iterations); // Failed to generate server credential from user credential. Points to // a configuration issue. The root cause should have been logged in the // mutate() method. if (serverCredential == null) { return null; } if (saltLength == 0 && iterations == 1) { // Output the simple/old format for backwards compatibility return serverCredential; } else { StringBuilder result = new StringBuilder((saltLength << 1) + 10 + serverCredential.length() + 2); result.append(HexUtils.toHexString(salt)); result.append('$'); result.append(iterations); result.append('$'); result.append(serverCredential); return result.toString(); } }
Checks whether the provided credential matches the stored credential when the stored credential is in the form salt$iteration-count$credential
Params:
  • inputCredentials – The input credential
  • storedCredentials – The stored credential
Returns:true if they match, otherwise false
/** * Checks whether the provided credential matches the stored credential when * the stored credential is in the form salt$iteration-count$credential * * @param inputCredentials The input credential * @param storedCredentials The stored credential * * @return <code>true</code> if they match, otherwise <code>false</code> */
protected boolean matchesSaltIterationsEncoded(String inputCredentials, String storedCredentials) { if (storedCredentials == null) { // Stored credentials are invalid // This may be expected if nested credential handlers are being used logInvalidStoredCredentials(null); return false; } int sep1 = storedCredentials.indexOf('$'); int sep2 = storedCredentials.indexOf('$', sep1 + 1); if (sep1 < 0 || sep2 < 0) { // Stored credentials are invalid // This may be expected if nested credential handlers are being used logInvalidStoredCredentials(storedCredentials); return false; } String hexSalt = storedCredentials.substring(0, sep1); int iterations = Integer.parseInt(storedCredentials.substring(sep1 + 1, sep2)); String storedHexEncoded = storedCredentials.substring(sep2 + 1); byte[] salt; try { salt = HexUtils.fromHexString(hexSalt); } catch (IllegalArgumentException iae) { logInvalidStoredCredentials(storedCredentials); return false; } String inputHexEncoded = mutate(inputCredentials, salt, iterations, HexUtils.fromHexString(storedHexEncoded).length * Byte.SIZE); if (inputHexEncoded == null) { // Failed to mutate user credentials. Automatic fail. // Root cause should be logged by mutate() return false; } return storedHexEncoded.equalsIgnoreCase(inputHexEncoded); } private void logInvalidStoredCredentials(String storedCredentials) { if (logInvalidStoredCredentials) { // Logging credentials could be a security concern but they are // invalid and that is probably a bigger problem getLog().warn(sm.getString("credentialHandler.invalidStoredCredential", storedCredentials)); } }
Returns:the default salt length used by the CredentialHandler.
/** * @return the default salt length used by the {@link CredentialHandler}. */
protected int getDefaultSaltLength() { return DEFAULT_SALT_LENGTH; }
Generates the equivalent stored credentials for the given input credentials, salt and iterations. If the algorithm requires a key length, the default will be used.
Params:
  • inputCredentials – User provided credentials
  • salt – Salt, if any
  • iterations – Number of iterations of the algorithm associated with this CredentialHandler applied to the inputCredentials to generate the equivalent stored credentials
Returns: The equivalent stored credentials for the given input credentials or null if the generation fails
/** * Generates the equivalent stored credentials for the given input * credentials, salt and iterations. If the algorithm requires a key length, * the default will be used. * * @param inputCredentials User provided credentials * @param salt Salt, if any * @param iterations Number of iterations of the algorithm associated * with this CredentialHandler applied to the * inputCredentials to generate the equivalent * stored credentials * * @return The equivalent stored credentials for the given input * credentials or <code>null</code> if the generation fails */
protected abstract String mutate(String inputCredentials, byte[] salt, int iterations);
Generates the equivalent stored credentials for the given input credentials, salt, iterations and key length. The default implementation calls ignores the key length and calls mutate(String, byte[], int). Sub-classes that use the key length should override this method.
Params:
  • inputCredentials – User provided credentials
  • salt – Salt, if any
  • iterations – Number of iterations of the algorithm associated with this CredentialHandler applied to the inputCredentials to generate the equivalent stored credentials
  • keyLength – Length of the produced digest in bits for implementations where it's applicable
Returns: The equivalent stored credentials for the given input credentials or null if the generation fails
/** * Generates the equivalent stored credentials for the given input * credentials, salt, iterations and key length. The default implementation * calls ignores the key length and calls * {@link #mutate(String, byte[], int)}. Sub-classes that use the key length * should override this method. * * @param inputCredentials User provided credentials * @param salt Salt, if any * @param iterations Number of iterations of the algorithm associated * with this CredentialHandler applied to the * inputCredentials to generate the equivalent * stored credentials * @param keyLength Length of the produced digest in bits for * implementations where it's applicable * * @return The equivalent stored credentials for the given input * credentials or <code>null</code> if the generation fails */
protected String mutate(String inputCredentials, byte[] salt, int iterations, int keyLength) { return mutate(inputCredentials, salt, iterations); }
Set the algorithm used to convert input credentials to stored credentials.
Params:
  • algorithm – the algorithm
Throws:
/** * Set the algorithm used to convert input credentials to stored * credentials. * @param algorithm the algorithm * @throws NoSuchAlgorithmException if the specified algorithm * is not supported */
public abstract void setAlgorithm(String algorithm) throws NoSuchAlgorithmException;
Returns:the algorithm used to convert input credentials to stored credentials.
/** * @return the algorithm used to convert input credentials to stored * credentials. */
public abstract String getAlgorithm();
Returns:the default number of iterations used by the CredentialHandler.
/** * @return the default number of iterations used by the * {@link CredentialHandler}. */
protected abstract int getDefaultIterations();
Returns:the logger for the CredentialHandler instance.
/** * @return the logger for the CredentialHandler instance. */
protected abstract Log getLog(); }