Merged 5.1-MC1 (5.1.0) to HEAD (5.1)

119071 adavis: Merged 5.1.N (5.1.1) to 5.1-MC1 (5.1.0)
      117354 adavis: Merged 5.0.2-CLOUD42 (Cloud ) to 5.1.N (5.1.1)
         117261 adavis: Merged 5.0.2-CLOUD (Cloud ) to 5.0.2-CLOUD42 (Cloud )
            114795 gjames: BCRYPT RA-609: Ensure system does not double hash encoders if not safe


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@119910 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Jean-Pierre Huynh
2015-12-10 10:01:25 +00:00
parent 9b124acb5b
commit 1d9ad4fcac
4 changed files with 88 additions and 16 deletions

View File

@@ -26,6 +26,7 @@ import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -75,6 +76,48 @@ public class CompositePasswordEncoder
return false;
}
/**
* Determines if its safe to encode the encoding chain. This applies particularly to double-hashing.
* BCRYPT uses its own internal salt so its NOT safe to use it more than once in an encoding chain
* (because the result will be different each time.) BCRYPT CAN BE USED successfully as the last element in
* an encoding chain.
*
* Anything that implements springframework PasswordEncoder is considered "unsafe"
* (because the method takes no salt param).
*
* @param encodingChain mandatory encoding chain
* @return true if it is okay to encode this chain.
*/
public boolean isSafeToEncodeChain(List<String> encodingChain)
{
if (encodingChain!= null && encodingChain.size() > 0 )
{
List<String> unsafeEncoders = new ArrayList<>();
for (String encoderKey : encodingChain)
{
Object encoder = encoders.get(encoderKey);
if (encoder == null) throw new AlfrescoRuntimeException("Invalid encoder specified: "+encoderKey);
if (encoder instanceof org.springframework.security.crypto.password.PasswordEncoder)
{
//BCRYPT uses its own internal salt so its NOT safe to use it more than once in an encoding chain.
//the Spring PasswordEncoder class doesn't require a salt and BCRYPTEncoder implements this, so
//we will count the instances of Spring PasswordEncoder
unsafeEncoders.add(encoderKey);
}
}
if (unsafeEncoders.isEmpty()) return true;
if (unsafeEncoders.size() == 1 && unsafeEncoders.get(0).equals(encodingChain.get(encodingChain.size()-1)))
{
//The unsafe encoder is used at the end so that's ok.
return true;
}
logger.warn("Unsafe encoders in the encoding chain: "+Arrays.toString(unsafeEncoders.toArray())
+". Only 1 unsafe encoder is allowed at the end of the chain: "+Arrays.toString(encodingChain.toArray()));
}
return false;
}
/**
* Basic init method for checking mandatory properties
*/

View File

@@ -281,21 +281,31 @@ public class UpgradePasswordHashWorker implements ApplicationContextAware, Initi
// determine if current password hash matches the preferred encoding
if (!passwordEncoder.lastEncodingIsPreferred(passwordHash.getFirst()))
{
String username = (String)properties.get(ContentModel.PROP_USER_USERNAME);
// We need to double hash
if (logger.isTraceEnabled())
{
String username = (String)properties.get(ContentModel.PROP_USER_USERNAME);
logger.trace("Double hashing user '" + username + "'.");
}
List<String> nowHashed = new ArrayList<String>();
nowHashed.addAll(passwordHash.getFirst());
nowHashed.add(passwordEncoder.getPreferredEncoding());
Object salt = properties.get(ContentModel.PROP_SALT);
properties.put(ContentModel.PROP_PASSWORD_HASH, passwordEncoder.encodePreferred(new String(passwordHash.getSecond()), salt));
properties.put(ContentModel.PROP_HASH_INDICATOR, (Serializable)nowHashed);
properties.remove(ContentModel.PROP_PASSWORD);
properties.remove(ContentModel.PROP_PASSWORD_SHA256);
return true;
if (passwordEncoder.isSafeToEncodeChain(nowHashed))
{
if (logger.isTraceEnabled())
{
logger.trace("Double hashing user '" + username + "'.");
}
Object salt = properties.get(ContentModel.PROP_SALT);
properties.put(ContentModel.PROP_PASSWORD_HASH, passwordEncoder.encodePreferred(new String(passwordHash.getSecond()), salt));
properties.put(ContentModel.PROP_HASH_INDICATOR, (Serializable)nowHashed);
properties.remove(ContentModel.PROP_PASSWORD);
properties.remove(ContentModel.PROP_PASSWORD_SHA256);
return true;
}
else
{
logger.warn("Unsafe to Double Hash user: " + username + "'. The user needs to login first.");
return false;
}
}
// ensure password hash is in the correct place