/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package util; import java.security.SecureRandom; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.digest.Crypt; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import com.emc.storageos.db.client.model.EncryptionProvider; import com.emc.vipr.client.ViPRSystemClient; import com.emc.vipr.client.exceptions.ServiceErrorException; import play.Logger; import plugin.StorageOsPlugin; public class PasswordUtil { /** Identifier for SHA-512 password. */ private static final String CRYPT_SHA_512 = "$6$"; /** The length of the SHA-512 hash generated by Crypt. */ private static final int SHA_512_HASH_LENGTH = 106; /** Prefix for encrypted values. */ private static String ENC_PREFIX = "${enc:"; /** Suffix for encrypted values. */ private static String ENC_SUFFIX = "}"; /** * Encrypts a password for a local user. * * @param password * the clear text password. * @return the encrypted password. */ public static String generateHash(String password) { String salt = generateSalt(); return Crypt.crypt(password, CRYPT_SHA_512 + salt); } /** * Determines if the string is a hash generated by the {@link #generateHash(String)} method. * * @param str * the string to test. * @return true if the string is a hash. */ public static boolean isHash(String str) { return StringUtils.startsWith(str, CRYPT_SHA_512) && (StringUtils.length(str) == SHA_512_HASH_LENGTH); } public static boolean isValid(String value) { if (StringUtils.isNotBlank(value)) { // Check to ensure if there is an encrypted value it can be decrypted String decrypted = decryptedValue(value); return StringUtils.isNotBlank(decrypted); } return true; } public static boolean isNotValid(String value) { return !isValid(value); } /** * Validates a password using the ViPR api call. * * @param plaintext password to validate * @return If validation passes, returns an empty string. If validation fails, returns the validation error message. */ public static String validatePassword(String value) { ViPRSystemClient client = BourneUtil.getSysClient(); try { String decrypted = decryptedValue(value); client.password().validate(decrypted); } catch (ServiceErrorException e) { if (e.getHttpCode() == 400 && e.getServiceError() != null) { return e.getServiceError().getDetailedMessage(); } } catch (Exception e) { Logger.error(e, "Error executing api call to validate password"); return MessagesUtils.get("setup.password.notValid"); } return StringUtils.EMPTY; } /** * Validates a password using the ViPR api call during self update. * * @param plaintext password to validate * @return If validation passes, returns an empty string. If validation fails, returns the validation error message. */ public static String validatePasswordforUpdate(String oldPassword, String newPassword) { ViPRSystemClient client = BourneUtil.getSysClient(); try { oldPassword = decryptedValue(oldPassword); newPassword = decryptedValue(newPassword); client.password().validateUpdate(oldPassword, newPassword); } catch (ServiceErrorException e) { if (e.getHttpCode() == 400 && e.getServiceError() != null) { return e.getServiceError().getDetailedMessage(); } } catch (Exception e) { Logger.error(e, "Error executing api call to validate password"); return MessagesUtils.get("setup.password.notValid"); } return StringUtils.EMPTY; } public static String encryptedValue(String value) { if (StringUtils.isBlank(value)) { return value; } else if (isEncrypted(value)) { return value; } else { return ENC_PREFIX + encrypt(value) + ENC_SUFFIX; } } public static String decryptedValue(String value) { if (StringUtils.isBlank(value)) { return value; } else if (isEncrypted(value)) { String data = StringUtils.substring(value, ENC_PREFIX.length(), value.length() - ENC_SUFFIX.length()); return decrypt(data); } else { return value; } } private static boolean isEncrypted(String value) { return StringUtils.startsWith(value, ENC_PREFIX) && StringUtils.endsWith(value, ENC_SUFFIX); } public static String encrypt(String value) { if (StringUtils.isNotBlank(value)) { EncryptionProvider encryption = StorageOsPlugin.getInstance().getEncryptionProvider(); if (encryption != null) { try { return Base64.encodeBase64String(encryption.encrypt(value)); } catch (RuntimeException e) { Logger.error("Faild to encrypt value", e); return null; } } } return value; } public static String decrypt(String value) { if (StringUtils.isNotBlank(value)) { EncryptionProvider encryption = StorageOsPlugin.getInstance().getEncryptionProvider(); if (encryption != null) { try { return encryption.decrypt(Base64.decodeBase64(value)); } catch (RuntimeException e) { Logger.error("Failed to decrypt value: %s", e.getMessage()); return null; } } } return value; } public static String getPasswordValidPromptRules(String[][] PASSWORD_VALID_PROMPT) { // NOSONAR // ("Suppressing Sonar violation of Field name should comply with naming convention") ViPRSystemClient client = BourneUtil.getSysClient(); List<String> promptRules = new ArrayList<String>(); StringBuilder promptString = new StringBuilder(); Map<String, String> properties = client.config().getProperties().getProperties(); for (int i = 0; i < PASSWORD_VALID_PROMPT.length; i++) { String key = PASSWORD_VALID_PROMPT[i][0]; String value = properties.get(key); if (NumberUtils.toInt(value) != 0) { promptRules.add(MessageFormat.format(PASSWORD_VALID_PROMPT[i][1], value)); } } promptString.append("<p>Password Validation Rules:</p>"); promptString.append("<ul>"); for (String item : promptRules) { promptString.append("<li>").append(item).append("</li>"); } promptString.append("</ul>"); return promptString.toString(); } /** * Generate a random salt */ private static String generateSalt() { // number of Base64 characters for salt is dependent on the number of salt bytes final int SALT_LENGTH = 16;// NOSONAR ("Suppressing Sonar violation of Field name should comply with naming convention") // valid chars as part of salt acceptable by commons-codec final String SALT_BASE_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./";// NOSONAR // ("Suppressing Sonar violation of Field name should comply with naming convention") // create the salt of random bytes SecureRandom random = new SecureRandom(); StringBuilder salt = new StringBuilder(SALT_LENGTH); for (int i = 0; i < SALT_LENGTH; i++) { salt.append(SALT_BASE_CHARS.charAt(random.nextInt(SALT_BASE_CHARS.length()))); } return salt.toString(); } }