|
@@ -20,18 +20,21 @@
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
*/
|
|
|
|
|
|
-package password.pwm.util;
|
|
|
+package password.pwm.util.secure;
|
|
|
|
|
|
import password.pwm.PwmConstants;
|
|
|
import password.pwm.error.ErrorInformation;
|
|
|
import password.pwm.error.PwmError;
|
|
|
import password.pwm.error.PwmUnrecoverableException;
|
|
|
+import password.pwm.util.Helper;
|
|
|
+import password.pwm.util.StringUtil;
|
|
|
import password.pwm.util.logging.PwmLogger;
|
|
|
|
|
|
import javax.crypto.Cipher;
|
|
|
+import javax.crypto.Mac;
|
|
|
import javax.crypto.SecretKey;
|
|
|
-import javax.crypto.spec.SecretKeySpec;
|
|
|
import java.io.*;
|
|
|
+import java.security.GeneralSecurityException;
|
|
|
import java.security.MessageDigest;
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
import java.util.Arrays;
|
|
@@ -42,81 +45,20 @@ public class SecureHelper {
|
|
|
|
|
|
private static final int HASH_BUFFER_SIZE = 1024;
|
|
|
|
|
|
- public static final HashAlgorithm DEFAULT_HASH_ALGORITHM = HashAlgorithm.SHA512;
|
|
|
- public static final BlockAlgorithm DEFAULT_BLOCK_ALGORITHM = BlockAlgorithm.AES;
|
|
|
-
|
|
|
- public enum HashAlgorithm {
|
|
|
- MD5("MD5"),
|
|
|
- SHA1("SHA1"),
|
|
|
- SHA256("SHA-256"),
|
|
|
- SHA512("SHA-512"),
|
|
|
-
|
|
|
- ;
|
|
|
-
|
|
|
- private final String algName;
|
|
|
-
|
|
|
- HashAlgorithm(String algName)
|
|
|
- {
|
|
|
- this.algName = algName;
|
|
|
- }
|
|
|
-
|
|
|
- public String getAlgName()
|
|
|
- {
|
|
|
- return algName;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public enum BlockAlgorithm {
|
|
|
- AES("AES"),
|
|
|
- AES_SHA2("AES"),
|
|
|
- AES_CHECKSUM("AES/CBC/PKCS5Padding"),
|
|
|
- CONFIG("AES"),
|
|
|
-
|
|
|
- ;
|
|
|
-
|
|
|
- private final String algName;
|
|
|
-
|
|
|
- BlockAlgorithm(String algName)
|
|
|
- {
|
|
|
- this.algName = algName;
|
|
|
- }
|
|
|
-
|
|
|
- public String getAlgName()
|
|
|
- {
|
|
|
- return algName;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public static String encryptToString(
|
|
|
- final String value,
|
|
|
- final SecretKey key
|
|
|
- )
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
- return encryptToString(value, key, false);
|
|
|
- }
|
|
|
-
|
|
|
- public static String encryptToString(
|
|
|
- final String value,
|
|
|
- final SecretKey key,
|
|
|
- final boolean urlSafe
|
|
|
- )
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
- return encryptToString(value, key, urlSafe, DEFAULT_BLOCK_ALGORITHM);
|
|
|
+ public enum Flag {
|
|
|
+ URL_SAFE,
|
|
|
}
|
|
|
|
|
|
public static String encryptToString(
|
|
|
final String value,
|
|
|
- final SecretKey key,
|
|
|
- final boolean urlSafe,
|
|
|
- final BlockAlgorithm blockAlgorithm
|
|
|
+ final PwmSecurityKey key,
|
|
|
+ final PwmBlockAlgorithm blockAlgorithm,
|
|
|
+ final Flag... flags
|
|
|
)
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
+ throws PwmUnrecoverableException {
|
|
|
try {
|
|
|
final byte[] encrypted = encryptToBytes(value, key, blockAlgorithm);
|
|
|
- return urlSafe
|
|
|
+ return Arrays.asList(flags).contains(Flag.URL_SAFE)
|
|
|
? StringUtil.base64Encode(encrypted, StringUtil.Base64Options.URL_SAFE, StringUtil.Base64Options.GZIP)
|
|
|
: StringUtil.base64Encode(encrypted);
|
|
|
} catch (Exception e) {
|
|
@@ -127,46 +69,31 @@ public class SecureHelper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public static byte[] encryptToBytes(
|
|
|
- final String value,
|
|
|
- final SecretKey key
|
|
|
- )
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
- return encryptToBytes(value, key, DEFAULT_BLOCK_ALGORITHM);
|
|
|
- }
|
|
|
|
|
|
public static byte[] encryptToBytes(
|
|
|
final String value,
|
|
|
- final SecretKey key,
|
|
|
- final BlockAlgorithm blockAlgorithm
|
|
|
+ final PwmSecurityKey key,
|
|
|
+ final PwmBlockAlgorithm blockAlgorithm
|
|
|
)
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
+ throws PwmUnrecoverableException {
|
|
|
try {
|
|
|
if (value == null || value.length() < 1) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
+ final SecretKey aesKey = key.getKey(PwmSecurityKey.Type.AES);
|
|
|
final Cipher cipher = Cipher.getInstance(blockAlgorithm.getAlgName());
|
|
|
- cipher.init(Cipher.ENCRYPT_MODE, key, cipher.getParameters());
|
|
|
+ cipher.init(Cipher.ENCRYPT_MODE, aesKey, cipher.getParameters());
|
|
|
final byte[] encryptedBytes = cipher.doFinal(value.getBytes(PwmConstants.DEFAULT_CHARSET));
|
|
|
- if (blockAlgorithm.equals(BlockAlgorithm.AES_SHA2)) {
|
|
|
- final byte[] hashChecksum;
|
|
|
- {
|
|
|
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
- baos.write(key.getEncoded());
|
|
|
- baos.write(encryptedBytes);
|
|
|
- hashChecksum = hashToBytes(new ByteArrayInputStream(baos.toByteArray()),HashAlgorithm.SHA256);
|
|
|
- }
|
|
|
- final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
- baos.write(hashChecksum);
|
|
|
- baos.write(encryptedBytes);
|
|
|
- return baos.toByteArray();
|
|
|
|
|
|
+ final byte[] output;
|
|
|
+ if (blockAlgorithm.equals(PwmBlockAlgorithm.AES_HMAC)) {
|
|
|
+ final byte[] hashChecksum = hmac(key, encryptedBytes);
|
|
|
+ output = appendByteArrays(blockAlgorithm.getPrefix(), hashChecksum, encryptedBytes);
|
|
|
} else {
|
|
|
- return encryptedBytes;
|
|
|
+ output = appendByteArrays(blockAlgorithm.getPrefix(), encryptedBytes);
|
|
|
}
|
|
|
+ return output;
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
final String errorMsg = "unexpected error performing simple crypt operation: " + e.getMessage();
|
|
@@ -176,41 +103,21 @@ public class SecureHelper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public static String decryptStringValue(
|
|
|
- final String value,
|
|
|
- final SecretKey key
|
|
|
- )
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
- return decryptStringValue(value, key, false);
|
|
|
- }
|
|
|
|
|
|
public static String decryptStringValue(
|
|
|
final String value,
|
|
|
- final SecretKey key,
|
|
|
- final boolean urlSafe
|
|
|
+ final PwmSecurityKey key,
|
|
|
+ final PwmBlockAlgorithm blockAlgorithm,
|
|
|
+ final Flag... flags
|
|
|
)
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
-
|
|
|
- return decryptStringValue(value, key, urlSafe, DEFAULT_BLOCK_ALGORITHM);
|
|
|
- }
|
|
|
-
|
|
|
- public static String decryptStringValue(
|
|
|
- final String value,
|
|
|
- final SecretKey key,
|
|
|
- final boolean urlSafe,
|
|
|
- final BlockAlgorithm blockAlgorithm
|
|
|
- )
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
+ throws PwmUnrecoverableException {
|
|
|
try {
|
|
|
if (value == null || value.length() < 1) {
|
|
|
return "";
|
|
|
}
|
|
|
|
|
|
- final byte[] decoded = urlSafe
|
|
|
- ? StringUtil.base64Decode(value, StringUtil.Base64Options.URL_SAFE,StringUtil.Base64Options.GZIP)
|
|
|
+ final byte[] decoded = Arrays.asList(flags).contains(Flag.URL_SAFE)
|
|
|
+ ? StringUtil.base64Decode(value, StringUtil.Base64Options.URL_SAFE, StringUtil.Base64Options.GZIP)
|
|
|
: StringUtil.base64Decode(value);
|
|
|
return decryptBytes(decoded, key, blockAlgorithm);
|
|
|
} catch (Exception e) {
|
|
@@ -220,51 +127,37 @@ public class SecureHelper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public static String decryptBytes(
|
|
|
- final byte[] value,
|
|
|
- final SecretKey key
|
|
|
- )
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
- return decryptBytes(value, key, DEFAULT_BLOCK_ALGORITHM);
|
|
|
- }
|
|
|
-
|
|
|
public static String decryptBytes(
|
|
|
byte[] value,
|
|
|
- final SecretKey key,
|
|
|
- final BlockAlgorithm blockAlgorithm
|
|
|
+ final PwmSecurityKey key,
|
|
|
+ final PwmBlockAlgorithm blockAlgorithm
|
|
|
)
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
+ throws PwmUnrecoverableException {
|
|
|
try {
|
|
|
if (value == null || value.length < 1) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- if (blockAlgorithm == BlockAlgorithm.AES_SHA2) {
|
|
|
+ value = verifyAndStripPrefix(blockAlgorithm, value);
|
|
|
+
|
|
|
+ final SecretKey aesKey = key.getKey(PwmSecurityKey.Type.AES);
|
|
|
+ if (blockAlgorithm == PwmBlockAlgorithm.AES_HMAC) {
|
|
|
final int CHECKSUM_SIZE = 32;
|
|
|
if (value.length <= CHECKSUM_SIZE) {
|
|
|
- throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_CRYPT_ERROR,"incoming AES_SHA2 data is missing checksum"));
|
|
|
+ throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_CRYPT_ERROR, "incoming " + blockAlgorithm.toString() + " data is missing checksum"));
|
|
|
}
|
|
|
- final byte[] suppliedChecksum = Arrays.copyOfRange(value, 0, CHECKSUM_SIZE);
|
|
|
- final byte[] suppliedPayload = Arrays.copyOfRange(value, CHECKSUM_SIZE, value.length);
|
|
|
- final byte[] computedChecksum;
|
|
|
- {
|
|
|
- final byte[] keyBytes = key.getEncoded();
|
|
|
- final byte[] payloadPlusKeyBytes = new byte[keyBytes.length + suppliedPayload.length];
|
|
|
- System.arraycopy(keyBytes, 0, payloadPlusKeyBytes, 0, keyBytes.length);
|
|
|
- System.arraycopy(suppliedPayload, 0, payloadPlusKeyBytes, keyBytes.length, suppliedPayload.length);
|
|
|
- computedChecksum = hashToBytes(new ByteArrayInputStream(payloadPlusKeyBytes), HashAlgorithm.SHA256);
|
|
|
+ final byte[] inputChecksum = Arrays.copyOfRange(value, 0, CHECKSUM_SIZE);
|
|
|
+ final byte[] inputPayload = Arrays.copyOfRange(value, CHECKSUM_SIZE, value.length);
|
|
|
+ final byte[] computedChecksum = hmac(key, inputPayload);
|
|
|
+ if (!Arrays.equals(inputChecksum, computedChecksum)) {
|
|
|
+ throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_CRYPT_ERROR, "incoming " + blockAlgorithm.toString() + " data has incorrect checksum"));
|
|
|
}
|
|
|
- if (!Arrays.equals(suppliedChecksum,computedChecksum)) {
|
|
|
- throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_CRYPT_ERROR,"incoming AES_SHA2 data has incorrect checksum"));
|
|
|
- }
|
|
|
- value = suppliedPayload;
|
|
|
+ value = inputPayload;
|
|
|
}
|
|
|
final Cipher cipher = Cipher.getInstance(blockAlgorithm.getAlgName());
|
|
|
- cipher.init(Cipher.DECRYPT_MODE, key);
|
|
|
+ cipher.init(Cipher.DECRYPT_MODE, aesKey);
|
|
|
final byte[] decrypted = cipher.doFinal(value);
|
|
|
- return new String(decrypted,PwmConstants.DEFAULT_CHARSET);
|
|
|
+ return new String(decrypted, PwmConstants.DEFAULT_CHARSET);
|
|
|
} catch (Exception e) {
|
|
|
final String errorMsg = "unexpected error performing simple decrypt operation: " + e.getMessage();
|
|
|
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_CRYPT_ERROR, errorMsg);
|
|
@@ -272,75 +165,32 @@ public class SecureHelper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public static SecretKey makeKey(final String text)
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
- try {
|
|
|
- final byte[] key = text.getBytes("iso-8859-1");
|
|
|
- return makeKey(key);
|
|
|
- } catch ( UnsupportedEncodingException e) {
|
|
|
- final String errorMsg = "unexpected error converting input text to crypto key bytes: " + e.getMessage();
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_CRYPT_ERROR, errorMsg);
|
|
|
- throw new PwmUnrecoverableException(errorInformation);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public static SecretKey makeKey(final byte[] inputBytes)
|
|
|
- throws PwmUnrecoverableException {
|
|
|
- try {
|
|
|
- final MessageDigest md = MessageDigest.getInstance("SHA1");
|
|
|
- md.update(inputBytes, 0, inputBytes.length);
|
|
|
- final byte[] key = new byte[16];
|
|
|
- System.arraycopy(md.digest(), 0, key, 0, 16);
|
|
|
- return new SecretKeySpec(key, "AES");
|
|
|
- } catch (NoSuchAlgorithmException e) {
|
|
|
- final String errorMsg = "unexpected error generating simple crypto key: " + e.getMessage();
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_CRYPT_ERROR, errorMsg);
|
|
|
- throw new PwmUnrecoverableException(errorInformation);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
public static String md5sum(final String input)
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
- return hash(input, HashAlgorithm.MD5);
|
|
|
- }
|
|
|
-
|
|
|
- public static String md5sum(final File theFile)
|
|
|
- throws PwmUnrecoverableException, IOException {
|
|
|
- return md5sum(new FileInputStream(theFile));
|
|
|
+ throws PwmUnrecoverableException {
|
|
|
+ return hash(input, PwmHashAlgorithm.MD5);
|
|
|
}
|
|
|
|
|
|
public static String md5sum(final InputStream is)
|
|
|
throws PwmUnrecoverableException {
|
|
|
- return hash(is, HashAlgorithm.MD5);
|
|
|
+ return hash(is, PwmHashAlgorithm.MD5);
|
|
|
}
|
|
|
|
|
|
public static String hash(
|
|
|
final byte[] input,
|
|
|
- final HashAlgorithm algorithm
|
|
|
+ final PwmHashAlgorithm algorithm
|
|
|
)
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
+ throws PwmUnrecoverableException {
|
|
|
if (input == null || input.length < 1) {
|
|
|
return null;
|
|
|
}
|
|
|
return hash(new ByteArrayInputStream(input), algorithm);
|
|
|
}
|
|
|
|
|
|
- public static String hash(
|
|
|
- final File file
|
|
|
- )
|
|
|
- throws IOException, PwmUnrecoverableException
|
|
|
- {
|
|
|
- return hash(file,DEFAULT_HASH_ALGORITHM);
|
|
|
- }
|
|
|
public static String hash(
|
|
|
final File file,
|
|
|
- final HashAlgorithm hashAlgorithm
|
|
|
+ final PwmHashAlgorithm hashAlgorithm
|
|
|
)
|
|
|
- throws IOException, PwmUnrecoverableException
|
|
|
- {
|
|
|
+ throws IOException, PwmUnrecoverableException {
|
|
|
if (file == null || !file.exists()) {
|
|
|
return null;
|
|
|
}
|
|
@@ -355,23 +205,11 @@ public class SecureHelper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public static String hash(
|
|
|
- final String input
|
|
|
- )
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
- if (input == null || input.length() < 1) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- return hash(new ByteArrayInputStream(input.getBytes(PwmConstants.DEFAULT_CHARSET)), DEFAULT_HASH_ALGORITHM);
|
|
|
- }
|
|
|
-
|
|
|
public static String hash(
|
|
|
final String input,
|
|
|
- final HashAlgorithm algorithm
|
|
|
+ final PwmHashAlgorithm algorithm
|
|
|
)
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
+ throws PwmUnrecoverableException {
|
|
|
if (input == null || input.length() < 1) {
|
|
|
return null;
|
|
|
}
|
|
@@ -380,21 +218,33 @@ public class SecureHelper {
|
|
|
|
|
|
public static String hash(
|
|
|
final InputStream is,
|
|
|
- final HashAlgorithm algorithm
|
|
|
+ final PwmHashAlgorithm algorithm
|
|
|
)
|
|
|
+ throws PwmUnrecoverableException {
|
|
|
+ return Helper.byteArrayToHexString(hashToBytes(is, algorithm));
|
|
|
+ }
|
|
|
+
|
|
|
+ static byte[] hmac(final PwmSecurityKey pwmSecurityKey, final byte[] input)
|
|
|
throws PwmUnrecoverableException
|
|
|
{
|
|
|
- return Helper.byteArrayToHexString(hashToBytes(is,algorithm));
|
|
|
+ try {
|
|
|
+ final Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
|
|
|
+ final SecretKey secret_key = pwmSecurityKey.getKey(PwmSecurityKey.Type.HMAC_256);
|
|
|
+ sha256_HMAC.init(secret_key);
|
|
|
+ return sha256_HMAC.doFinal(input);
|
|
|
+ } catch (GeneralSecurityException e) {
|
|
|
+ final String errorMsg = "error during hmac operation: " + e.getMessage();
|
|
|
+ final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_CRYPT_ERROR, errorMsg);
|
|
|
+ throw new PwmUnrecoverableException(errorInformation);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
|
|
|
-
|
|
|
public static byte[] hashToBytes(
|
|
|
final InputStream is,
|
|
|
- final HashAlgorithm algorithm
|
|
|
+ final PwmHashAlgorithm algorithm
|
|
|
)
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
+ throws PwmUnrecoverableException {
|
|
|
|
|
|
final InputStream bis = is instanceof BufferedInputStream ? is : new BufferedInputStream(is);
|
|
|
|
|
@@ -407,8 +257,7 @@ public class SecureHelper {
|
|
|
throw new PwmUnrecoverableException(errorInformation);
|
|
|
}
|
|
|
|
|
|
- try
|
|
|
- {
|
|
|
+ try {
|
|
|
final byte[] buffer = new byte[HASH_BUFFER_SIZE];
|
|
|
int length;
|
|
|
while (true) {
|
|
@@ -422,9 +271,49 @@ public class SecureHelper {
|
|
|
|
|
|
return messageDigest.digest();
|
|
|
} catch (IOException e) {
|
|
|
- final String errorMsg = "unexepected error during hash operation: " + e.getMessage();
|
|
|
+ final String errorMsg = "unexpected error during hash operation: " + e.getMessage();
|
|
|
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_CRYPT_ERROR, errorMsg);
|
|
|
throw new PwmUnrecoverableException(errorInformation);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ static byte[] appendByteArrays(final byte[]... input) {
|
|
|
+ if (input == null || input.length == 0) {
|
|
|
+ return new byte[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (input.length == 1) {
|
|
|
+ return input[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ int totalLength = 0;
|
|
|
+ for (final byte[] loopBa : input) {
|
|
|
+ totalLength += loopBa.length;
|
|
|
+ }
|
|
|
+
|
|
|
+ final byte[] output = new byte[totalLength];
|
|
|
+
|
|
|
+ int position = 0;
|
|
|
+ for (final byte[] loopBa : input) {
|
|
|
+ System.arraycopy(loopBa,0,output,position,loopBa.length);
|
|
|
+ position += loopBa.length;
|
|
|
+ }
|
|
|
+ return output;
|
|
|
+ }
|
|
|
+
|
|
|
+ static byte[] verifyAndStripPrefix(final PwmBlockAlgorithm blockAlgorithm, final byte[] input) throws PwmUnrecoverableException {
|
|
|
+ byte[] definedPrefix = blockAlgorithm.getPrefix();
|
|
|
+ if (definedPrefix.length == 0) {
|
|
|
+ return input;
|
|
|
+ }
|
|
|
+ byte[] inputPrefix = Arrays.copyOf(input, definedPrefix.length);
|
|
|
+ if (!Arrays.equals(definedPrefix, inputPrefix)) {
|
|
|
+ final String errorMsg = "value is missing valid prefix for decrpyption type";
|
|
|
+ final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_CRYPT_ERROR, errorMsg);
|
|
|
+ throw new PwmUnrecoverableException(errorInformation);
|
|
|
+ }
|
|
|
+
|
|
|
+ return Arrays.copyOfRange(input,definedPrefix.length,input.length);
|
|
|
+ }
|
|
|
+
|
|
|
}
|