/** * */ package mil.nga.giat.geowave.core.cli.operations.config.security.crypto; import java.io.File; import java.security.Key; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import mil.nga.giat.geowave.core.cli.VersionUtils; import mil.nga.giat.geowave.core.cli.operations.config.options.ConfigOptions; import mil.nga.giat.geowave.core.cli.operations.config.security.utils.SecurityUtils; import mil.nga.giat.geowave.core.cli.utils.FileUtils; /** * Abstract base encryption class for setting up and defining common * encryption/decryption methods */ public abstract class BaseEncryption { private final static Logger LOGGER = LoggerFactory.getLogger(BaseEncryption.class); public static String resourceName = "geowave_crypto_key.dat"; private String resourceLocation; private Key key = null; /* * PROTECT this value. The salt value is the second-half of the protection * mechanism for key used when encrypting or decrypting the content.<br/> We * cannot generate a new random cryptography key each time, as that would * mean two different keys.<br/> At the same time, encrypted values would be * very vulnerable to unintentional exposure if (a wrong) someone got access * to the token key file, so this salt allows us to protect the encryption * with "2 locks" - both are needed to decrypt a value that was encrypted * with the SAME two values (salt - below - and token file - specified at * resourceLocation) */ protected byte[] salt = null; protected File tokenFile = null; private static final String PREFIX = "ENC{"; private static final String SUFFIX = "}"; public static final String WRAPPER = PREFIX + SUFFIX; private static final Pattern ENCCodePattern = Pattern.compile(PREFIX.replace( "{", "\\{") + "([^}]+)" + SUFFIX.replace( "{", "\\{")); private final String KEY_ENCRYPTION_ALGORITHM = "AES"; /** * Base constructor for encryption, allowing a resource location for the * cryptography token key to be specified, rather than using the * default-generated path * * @param resourceLocation * Path to cryptography token key file */ public BaseEncryption( final String resourceLocation ) { try { setResourceLocation(resourceLocation); init(); } catch (Throwable t) { LOGGER.error( t.getLocalizedMessage(), t); } } /** * Base constructor for encryption */ public BaseEncryption() { init(); } /** * Method to initialize all required fields, check for the existence of the * cryptography token key, and generate the key for encryption/decryption */ private void init() { try { checkForToken(); setResourceLocation(tokenFile.getCanonicalPath()); salt = "Ge0W@v3-Ro0t-K3y".getBytes("UTF-8"); generateRootKeyFromToken(); } catch (Throwable t) { LOGGER.error( t.getLocalizedMessage(), t); } } /** * Check if encryption token exists. If not, create one initially */ private void checkForToken() throws Throwable { if (getResourceLocation() != null) { tokenFile = new File( getResourceLocation()); } else { if (new ConfigOptions().getConfigFile() != null) { tokenFile = SecurityUtils.getFormattedTokenKeyFileForConfig(new File( new ConfigOptions().getConfigFile())); } else { tokenFile = SecurityUtils.getFormattedTokenKeyFileForConfig(ConfigOptions.getDefaultPropertyFile()); } } if (tokenFile == null || !tokenFile.exists()) { generateNewEncryptionToken(tokenFile); } } /** * Generates a token file resource name that includes the current version * * @return formatted token key file name */ public static String getFormattedTokenFileName() { final String tokenFileName = resourceName.substring( 0, resourceName.lastIndexOf(".")); final String tokenFileExtension = resourceName.substring(resourceName.lastIndexOf(".")); String formattedTokenFileName = String.format( "%s%s%s%s", VersionUtils.getVersion(), "-", tokenFileName, tokenFileExtension); return formattedTokenFileName; } /** * Generate a new token value in a specified file * * @param tokenFile * @return */ public static boolean generateNewEncryptionToken( File tokenFile ) throws Exception { boolean success = false; try { LOGGER.info( "Writing new encryption token to file at path {}", tokenFile.getCanonicalPath()); org.apache.commons.io.FileUtils.writeStringToFile( tokenFile, generateRandomSecretKey()); LOGGER.info("Completed writing new encryption token to file"); success = true; } catch (Exception ex) { LOGGER.error( "An error occurred writing new encryption token to file: " + ex.getLocalizedMessage(), ex); throw ex; } return success; } /* * INTERNAL METHODS */ /** * Returns the path on the file system to the resource for the token * * @return Path to resource to get the token */ public String getResourceLocation() { return resourceLocation; } /** * Sets the path to the resource for the token * * @param resourceLocation * Path to resource to get the token */ public void setResourceLocation( String resourceLoc ) throws Throwable { resourceLocation = resourceLoc; } /** * Checks to see if the data is properly wrapped with ENC{} * * @param data * @return boolean - true if properly wrapped, false otherwise */ public static boolean isProperlyWrapped( String data ) { return ENCCodePattern.matcher( data).matches(); } /** * Converts a binary value to a encoded string * * @param data * Binary value to encode as an encoded string * @return Encoded string from the binary value specified */ private String toString( byte[] data ) { return Hex.encodeHexString(data); } /** * Converts a string value to a decoded binary * * @param data * String value to convert to decoded hex * @return Decoded binary from the string value specified */ private byte[] fromString( String data ) { try { return Hex.decodeHex(data.toCharArray()); } catch (DecoderException e) { LOGGER.error( e.getLocalizedMessage(), e); return null; } } /** * Method to generate a new secret key from the specified token key file */ private void generateRootKeyFromToken() throws Throwable { if (!tokenFile.exists()) { throw new Throwable( "Token file not found at specified path [" + getResourceLocation() + "]"); } try { String strPassword = FileUtils.readFileContent(tokenFile); char[] password = strPassword != null ? strPassword.trim().toCharArray() : null; SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); SecretKey tmp = factory.generateSecret(new PBEKeySpec( password, salt, 65536, 256)); setKey(new SecretKeySpec( tmp.getEncoded(), KEY_ENCRYPTION_ALGORITHM)); } catch (Exception ex) { LOGGER.error( "An error occurred generating the root key from the specified token: " + ex.getLocalizedMessage(), ex); } } /** * Method to generate a new random token key value * * @return * @throws Exception */ private static String generateRandomSecretKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(256); SecretKey secretKey = keyGenerator.generateKey(); byte[] encoded = secretKey.getEncoded(); return DatatypeConverter.printBase64Binary(encoded); } /** * Set the key to use * * @param key */ protected void setKey( Key key ) { this.key = key; } /** * Get the key to use * * @return */ protected Key getKey() { return this.key; } /* * ENCRYPTION METHODS */ /** * Method to encrypt and hex-encode a string value using the specified token * resource * * @param data * String to encrypt * @return Encrypted and Hex-encoded string value using the specified token * resource * @throws Exception */ public String encryptAndHexEncode( String data ) throws Exception { if (data == null) { return null; } byte[] encryptedBytes = encryptBytes(data.getBytes("UTF-8")); return PREFIX + toString(encryptedBytes) + SUFFIX; } /* * DECRYPTION METHODS */ /** * Returns a decrypted value from the encrypted hex-encoded value specified * * @param data * Hex-Encoded string value to decrypt * @return Decrypted value from the encrypted hex-encoded value specified * @throws Exception */ public String decryptHexEncoded( String data ) throws Exception { if (data == null) { return null; } Matcher matcher = ENCCodePattern.matcher(data); if (matcher.matches()) { String codedString = matcher.group(1); return new String( decryptBytes(fromString(codedString)), "UTF-8"); } else { return data; } } /* * ABSTRACT METHODS */ /** * Encrypt the data as a byte array * * @param valueToEncrypt * value to encrypt */ abstract public byte[] encryptBytes( byte[] valueToEncrypt ) throws Exception; /** * Decrypt the encrypted data * * @param valueToDecrypt * value to encrypt */ abstract public byte[] decryptBytes( byte[] valueToDecrypt ) throws Exception; }