| // 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.cloudstack.server.auth; |
| |
| import static java.lang.String.format; |
| |
| import java.io.UnsupportedEncodingException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.SecureRandom; |
| import java.security.spec.InvalidKeySpecException; |
| import java.util.Map; |
| |
| import javax.inject.Inject; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.log4j.Logger; |
| import org.bouncycastle.crypto.PBEParametersGenerator; |
| import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; |
| import org.bouncycastle.crypto.params.KeyParameter; |
| import org.bouncycastle.util.encoders.Base64; |
| |
| import com.cloud.server.auth.UserAuthenticator; |
| import com.cloud.user.UserAccount; |
| import com.cloud.user.dao.UserAccountDao; |
| import com.cloud.utils.ConstantTimeComparator; |
| import com.cloud.utils.Pair; |
| import com.cloud.utils.component.AdapterBase; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| |
| public class PBKDF2UserAuthenticator extends AdapterBase implements UserAuthenticator { |
| public static final Logger s_logger = Logger.getLogger(PBKDF2UserAuthenticator.class); |
| private static final int s_saltlen = 64; |
| private static final int s_rounds = 100000; |
| private static final int s_keylen = 512; |
| |
| @Inject |
| private UserAccountDao _userAccountDao; |
| |
| @Override |
| public Pair<Boolean, UserAuthenticator.ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters) { |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Retrieving user: " + username); |
| } |
| |
| if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { |
| s_logger.debug("Username or Password cannot be empty"); |
| return new Pair<Boolean, ActionOnFailedAuthentication>(false, null); |
| } |
| |
| boolean isValidUser = false; |
| UserAccount user = this._userAccountDao.getUserAccount(username, domainId); |
| if (user != null) { |
| isValidUser = true; |
| } else { |
| s_logger.debug("Unable to find user with " + username + " in domain " + domainId); |
| } |
| |
| byte[] salt = new byte[0]; |
| int rounds = s_rounds; |
| try { |
| if (isValidUser) { |
| String[] storedPassword = user.getPassword().split(":"); |
| if ((storedPassword.length != 3) || (!StringUtils.isNumeric(storedPassword[2]))) { |
| s_logger.warn("The stored password for " + username + " isn't in the right format for this authenticator"); |
| isValidUser = false; |
| } else { |
| // Encoding format = <salt>:<password hash>:<rounds> |
| salt = decode(storedPassword[0]); |
| rounds = Integer.parseInt(storedPassword[2]); |
| } |
| } |
| boolean result = false; |
| if (isValidUser && validateCredentials(password, salt)) { |
| result = ConstantTimeComparator.compareStrings(user.getPassword(), encode(password, salt, rounds)); |
| } |
| |
| UserAuthenticator.ActionOnFailedAuthentication action = null; |
| if ((!result) && (isValidUser)) { |
| action = UserAuthenticator.ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT; |
| } |
| return new Pair(Boolean.valueOf(result), action); |
| } catch (NumberFormatException e) { |
| throw new CloudRuntimeException("Unable to hash password", e); |
| } catch (NoSuchAlgorithmException e) { |
| throw new CloudRuntimeException("Unable to hash password", e); |
| } catch (UnsupportedEncodingException e) { |
| throw new CloudRuntimeException("Unable to hash password", e); |
| } catch (InvalidKeySpecException e) { |
| throw new CloudRuntimeException("Unable to hash password", e); |
| } |
| } |
| |
| @Override |
| public String encode(String password) |
| { |
| try |
| { |
| return encode(password, makeSalt(), s_rounds); |
| } catch (NoSuchAlgorithmException e) { |
| throw new CloudRuntimeException("Unable to hash password", e); |
| } catch (UnsupportedEncodingException e) { |
| throw new CloudRuntimeException("Unable to hash password", e); |
| } catch (InvalidKeySpecException e) { |
| s_logger.error("Exception in EncryptUtil.createKey ", e); |
| throw new CloudRuntimeException("Unable to hash password", e); |
| } |
| } |
| |
| public String encode(String password, byte[] salt, int rounds) |
| throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeySpecException { |
| PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(); |
| generator.init(PBEParametersGenerator.PKCS5PasswordToBytes( |
| password.toCharArray()), |
| salt, |
| rounds); |
| return format("%s:%s:%d", encode(salt), |
| encode(((KeyParameter)generator.generateDerivedParameters(s_keylen)).getKey()), rounds); |
| } |
| |
| public static byte[] makeSalt() throws NoSuchAlgorithmException { |
| SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); |
| byte[] salt = new byte[s_saltlen]; |
| sr.nextBytes(salt); |
| return salt; |
| } |
| |
| private static boolean validateCredentials(String plainPassword, byte[] hash) { |
| return !(plainPassword == null || plainPassword.isEmpty() || hash == null || hash.length == 0); |
| } |
| |
| private static String encode(byte[] input) throws UnsupportedEncodingException { |
| return new String(Base64.encode(input), "UTF-8"); |
| } |
| |
| private static byte[] decode(String input) throws UnsupportedEncodingException { |
| return Base64.decode(input.getBytes("UTF-8")); |
| } |
| } |