/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. */ package org.opends.server.core; import java.io.InputStream; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.opends.server.config.ConfigException; import static org.opends.server.loggers.Debug.*; import static org.opends.server.util.StaticUtils.*; /** * This class provides the interface to the Directory Server cryptographic * framework, which may be used for hashing, encryption, and other kinds of * cryptographic operations. Note that it also contains methods for compressing * and uncompressing data. Although these are not strictly cryptographic * operations, there are a lot of similarities and it may be conceivable at * some point that accelerated compression may be available just as it is for * cryptographic operations. */ public class CryptoManager { /** * The fully-qualified name of this class for debugging purposes. */ private static final String CLASS_NAME = "org.opends.server.core.CryptoManager"; // The default secret key that we will use for encryption and decryption. private SecretKey secretKey; // The preferred cipher for the Directory Server. private String preferredCipher; // The preferred message digest algorithm for the Directory Server. private String preferredDigestAlgorithm; // The preferred MAC algorithm for the Directory Server. private String preferredMACAlgorithm; /** * Creates a new instance of this crypto manager object. * * @throws ConfigException If a problem occurs while creating this crypto * manager that is a result of a problem in the * configuration. * * @throws InitializationException If a problem occurs while creating this * crypto manager that is not the result of * a problem in the configuration. */ public CryptoManager() throws ConfigException, InitializationException { assert debugConstructor(CLASS_NAME); // FIXME -- Get the defaults from the configuration rather than hard-coding // them. preferredDigestAlgorithm = "SHA-1"; preferredMACAlgorithm = "HmacSHA1"; preferredCipher = "AES/CBC/PKCS5Padding"; // FIXME -- Use a much more secure way of constructing the secret key. secretKey = new SecretKeySpec(new byte[16], "AES"); // Make sure that we can create instances of the preferred digest, MAC, and // cipher algorithms. try { MessageDigest.getInstance(preferredDigestAlgorithm); } catch (Exception e) { assert debugException(CLASS_NAME, "", e); // FIXME -- Number this. throw new InitializationException(-1, "Can't get preferred digest: " + stackTraceToSingleLineString(e), e); } try { Mac mac = Mac.getInstance(preferredMACAlgorithm); mac.init(secretKey); } catch (Exception e) { assert debugException(CLASS_NAME, "", e); // FIXME -- Number this. throw new InitializationException(-1, "Can't get preferred MAC " + "provider: " + stackTraceToSingleLineString(e), e); } try { Cipher cipher = Cipher.getInstance(preferredCipher); cipher.init(Cipher.ENCRYPT_MODE, secretKey); } catch (Exception e) { assert debugException(CLASS_NAME, "", e); // FIXME -- Number this. throw new InitializationException(-1, "Can't get preferred cipher: " + stackTraceToSingleLineString(e), e); } } /** * Retrieves an instance of a secure random number generator. * * @return An instance of a secure random number generator. */ public SecureRandom getSecureRandom() { assert debugEnter(CLASS_NAME, "getSecureRandom"); // FIXME -- Is this threadsafe? Can we share a single instance among all // threads? return new SecureRandom(); } /** * Retrieves the name of the preferred message digest algorithm. * * @return The name of the preferred message digest algorithm */ public String getPreferredMessageDigestAlgorithm() { assert debugEnter(CLASS_NAME, "getPreferredMessageDigestAlgorithm"); return preferredDigestAlgorithm; } /** * Retrieves a MessageDigest object that may be used to generate * digests using the preferred digest algorithm. * * @return A MessageDigest object that may be used to generate * digests using the preferred digest algorithm. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. */ public MessageDigest getPreferredMessageDigest() throws NoSuchAlgorithmException { assert debugEnter(CLASS_NAME, "getPreferredMessageDigest"); return MessageDigest.getInstance(preferredDigestAlgorithm); } /** * Retrieves a MessageDigest object that may be used to generate * digests using the specified algorithm. * * @param digestAlgorithm The algorithm to use to generate the message * digest. * * @return A MessageDigest object that may be used to generate * digests using the specified algorithm. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. */ public MessageDigest getMessageDigest(String digestAlgorithm) throws NoSuchAlgorithmException { assert debugEnter(CLASS_NAME, "getMessageDigest", String.valueOf(digestAlgorithm)); return MessageDigest.getInstance(digestAlgorithm); } /** * Retrieves a byte array containing a message digest based on the provided * data, using the preferred digest algorithm. * * @param data The data to be digested. * * @return A byte array containing the generated message digest. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. */ public byte[] digest(byte[] data) throws NoSuchAlgorithmException { assert debugEnter(CLASS_NAME, "digest", "byte[" + data.length + "]"); return MessageDigest.getInstance(preferredDigestAlgorithm).digest(data); } /** * Retrieves a byte array containing a message digest based on the provided * data, using the requested digest algorithm. * * @param digestAlgorithm The algorithm to use to generate the message * digest. * @param data The data to be digested. * * @return A byte array containing the generated message digest. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. */ public byte[] digest(String digestAlgorithm, byte[] data) throws NoSuchAlgorithmException { assert debugEnter(CLASS_NAME, "digest", String.valueOf(digestAlgorithm), "byte[" + data.length + "]"); return MessageDigest.getInstance(digestAlgorithm).digest(data); } /** * Retrieves a byte array containing a message digest based on the data read * from the provided input stream, using the preferred digest algorithm. Data * will be read until the end of the stream is reached. * * @param inputStream The input stream from which the data is to be read. * * @return A byte array containing the generated message digest. * * @throws IOException If a problem occurs while reading data from the * provided stream. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. */ public byte[] digest(InputStream inputStream) throws IOException, NoSuchAlgorithmException { assert debugEnter(CLASS_NAME, "digest", "java.io.InputStream"); MessageDigest digest = MessageDigest.getInstance(preferredDigestAlgorithm); byte[] buffer = new byte[8192]; while (true) { int bytesRead = inputStream.read(buffer); if (bytesRead < 0) { break; } digest.update(buffer, 0, bytesRead); } return digest.digest(); } /** * Retrieves a byte array containing a message digest based on the data read * from the provided input stream, using the requested digest algorithm. Data * will be read until the end of the stream is reached. * * @param digestAlgorithm The algorithm to use to generate the message * digest. * @param inputStream The input stream from which the data is to be * read. * * @return A byte array containing the generated message digest. * * @throws IOException If a problem occurs while reading data from the * provided stream. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. */ public byte[] digest(String digestAlgorithm, InputStream inputStream) throws IOException, NoSuchAlgorithmException { assert debugEnter(CLASS_NAME, "digest", "java.io.InputStream"); MessageDigest digest = MessageDigest.getInstance(digestAlgorithm); byte[] buffer = new byte[8192]; while (true) { int bytesRead = inputStream.read(buffer); if (bytesRead < 0) { break; } digest.update(buffer, 0, bytesRead); } return digest.digest(); } /** * Retrieves the name of the preferred MAC algorithm. * * @return The name of the preferred MAC algorithm */ public String getPreferredMACAlgorithm() { assert debugEnter(CLASS_NAME, "getPreferredMessageDigestAlgorithm"); return preferredMACAlgorithm; } /** * Retrieves a MAC provider using the preferred algorithm. * * @return A MAC provider using the preferred algorithm. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. * * @throws InvalidKeyException If the provided key is not appropriate for * use with the requested MAC algorithm. */ public Mac getPreferredMACProvider() throws NoSuchAlgorithmException, InvalidKeyException { assert debugEnter(CLASS_NAME, "getPreferredMACProvider"); Mac mac = Mac.getInstance(preferredMACAlgorithm); mac.init(secretKey); return mac; } /** * Retrieves a MAC provider using the specified algorithm. * * @param macAlgorithm The algorithm to use for the MAC provider. * * @return A MAC provider using the specified algorithm. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. * * @throws InvalidKeyException If the provided key is not appropriate for * use with the requested MAC algorithm. */ public Mac getMACProvider(String macAlgorithm) throws NoSuchAlgorithmException, InvalidKeyException { assert debugEnter(CLASS_NAME, "getMACProvider", String.valueOf(macAlgorithm)); Mac mac = Mac.getInstance(macAlgorithm); mac.init(secretKey); return mac; } /** * Retrieves a byte array containing a MAC based on the provided data, using * the preferred MAC algorithm. * * @param data The data for which to generate the MAC. * * @return A byte array containing the generated MAC. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. */ public byte[] mac(byte[] data) throws NoSuchAlgorithmException { assert debugEnter(CLASS_NAME, "mac", "byte[" + data.length + "]"); return Mac.getInstance(preferredMACAlgorithm).doFinal(data); } /** * Retrieves a byte array containing a MAC based on the provided data, using * the requested MAC algorithm. * * @param macAlgorithm The algorithm to use for the MAC. * @param data The data for which to generate the MAC. * * @return A byte array containing the generated MAC. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. */ public byte[] mac(String macAlgorithm, byte[] data) throws NoSuchAlgorithmException { assert debugEnter(CLASS_NAME, "mac", String.valueOf(macAlgorithm), "byte[" + data.length + "]"); return Mac.getInstance(macAlgorithm).doFinal(data); } /** * Retrieves a byte array containing a MAC based on the data read from the * provided input stream, using the preferred MAC algorithm. Data will be * read until the end of the stream is reached. * * @param inputStream The input stream from which the data is to be read. * * @return A byte array containing the generated MAC. * * @throws IOException If a problem occurs while reading data from the * provided stream. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. */ public byte[] mac(InputStream inputStream) throws IOException, NoSuchAlgorithmException { assert debugEnter(CLASS_NAME, "digest", "java.io.InputStream"); Mac mac = Mac.getInstance(preferredMACAlgorithm); byte[] buffer = new byte[8192]; while (true) { int bytesRead = inputStream.read(buffer); if (bytesRead < 0) { break; } mac.update(buffer, 0, bytesRead); } return mac.doFinal(); } /** * Retrieves a byte array containing a MAC based on the data read from the * provided input stream, using the preferred MAC algorithm. Data will be * read until the end of the stream is reached. * * @param macAlgorithm The algorithm to use for the MAC. * @param inputStream The input stream from which the data is to be read. * * @return A byte array containing the generated MAC. * * @throws IOException If a problem occurs while reading data from the * provided stream. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. */ public byte[] mac(String macAlgorithm, InputStream inputStream) throws IOException, NoSuchAlgorithmException { assert debugEnter(CLASS_NAME, "digest", String.valueOf(macAlgorithm), "java.io.InputStream"); Mac mac = Mac.getInstance(macAlgorithm); byte[] buffer = new byte[8192]; while (true) { int bytesRead = inputStream.read(buffer); if (bytesRead < 0) { break; } mac.update(buffer, 0, bytesRead); } return mac.doFinal(); } /** * Retrieves the name of the preferred cipher algorithm. * * @return The name of the preferred cipher algorithm */ public String getPreferredCipherAlgorithm() { assert debugEnter(CLASS_NAME, "getPreferredCipherAlgorithm"); return preferredCipher; } /** * Retrieves a cipher using the preferred algorithm and the specified cipher * mode. * * @param cipherMode The cipher mode that indicates how the cipher will be * used (e.g., encryption, decryption, wrapping, * unwrapping). * * @return A cipher using the preferred algorithm. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. * * @throws NoSuchPaddingException If the requested padding mechanism is not * supported or is unavailable. * * @throws InvalidKeyException If the provided key is not appropriate for * use with the requested cipher algorithm. * * @throws InvalidAlgorithmParameterException If an internal problem occurs * as a result of the * initialization vector used. */ public Cipher getPreferredCipher(int cipherMode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException { assert debugEnter(CLASS_NAME, "getPreferredCipher", String.valueOf(cipherMode)); Cipher cipher = Cipher.getInstance(preferredCipher); // FIXME -- This needs to be more secure. IvParameterSpec iv = new IvParameterSpec(new byte[16]); cipher.init(cipherMode, secretKey, iv); return cipher; } /** * Retrieves a cipher using the preferred algorithm and the specified cipher * mode. * * @param cipherAlgorithm The algorithm to use for the cipher. * @param cipherMode The cipher mode that indicates how the cipher will * be used (e.g., encryption, decryption, wrapping, * unwrapping). * * @return A cipher using the preferred algorithm. * * @throws NoSuchAlgorithmException If the requested algorithm is not * supported or is unavailable. * * @throws NoSuchPaddingException If the requested padding mechanism is not * supported or is unavailable. * * @throws InvalidKeyException If the provided key is not appropriate for * use with the requested cipher algorithm. * * @throws InvalidAlgorithmParameterException If an internal problem occurs * as a result of the * initialization vector used. */ public Cipher getCipher(String cipherAlgorithm, int cipherMode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException { assert debugEnter(CLASS_NAME, "getCipher", String.valueOf(cipherAlgorithm), String.valueOf(cipherMode)); Cipher cipher = Cipher.getInstance(cipherAlgorithm); // FIXME -- This needs to be more secure. IvParameterSpec iv = new IvParameterSpec(new byte[16]); cipher.init(cipherMode, secretKey, iv); return cipher; } /** * Encrypts the data in the provided byte array using the preferred cipher. * * @param data The data to be encrypted. * * @return A byte array containing the encrypted representation of the * provided data. * * @throws GeneralSecurityException If a problem occurs while attempting to * encrypt the data. */ public byte[] encrypt(byte[] data) throws GeneralSecurityException { assert debugEnter(CLASS_NAME, "encrypt", "byte[" + data.length + "]"); Cipher cipher = Cipher.getInstance(preferredCipher); // FIXME -- This needs to be more secure. IvParameterSpec iv = new IvParameterSpec(new byte[16]); cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); return cipher.doFinal(data); } /** * Decrypts the data in the provided byte array using the preferred cipher. * * @param data The data to be decrypted. * * @return A byte array containing the cleartext representation of the * provided data. * * @throws GeneralSecurityException If a problem occurs while attempting to * decrypt the data. */ public byte[] decrypt(byte[] data) throws GeneralSecurityException { assert debugEnter(CLASS_NAME, "decrypt", "byte[" + data.length + "]"); Cipher cipher = Cipher.getInstance(preferredCipher); // FIXME -- This needs to be more secure. IvParameterSpec iv = new IvParameterSpec(new byte[16]); cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); return cipher.doFinal(data); } /** * Encrypts the data in the provided byte array using the preferred cipher. * * @param cipherAlgorithm The algorithm to use to encrypt the data. * @param data The data to be encrypted. * * @return A byte array containing the encrypted representation of the * provided data. * * @throws GeneralSecurityException If a problem occurs while attempting to * encrypt the data. */ public byte[] encrypt(String cipherAlgorithm, byte[] data) throws GeneralSecurityException { assert debugEnter(CLASS_NAME, "encrypt", "byte[" + data.length + "]"); Cipher cipher = Cipher.getInstance(cipherAlgorithm); // FIXME -- This needs to be more secure. IvParameterSpec iv = new IvParameterSpec(new byte[16]); cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); return cipher.doFinal(data); } /** * Decrypts the data in the provided byte array using the requested cipher. * * @param cipherAlgorithm The algorithm to use to decrypt the data. * @param data The data to be decrypted. * * @return A byte array containing the cleartext representation of the * provided data. * * @throws GeneralSecurityException If a problem occurs while attempting to * decrypt the data. */ public byte[] decrypt(String cipherAlgorithm, byte[] data) throws GeneralSecurityException { assert debugEnter(CLASS_NAME, "decrypt", "byte[" + data.length + "]"); Cipher cipher = Cipher.getInstance(cipherAlgorithm); // FIXME -- This needs to be more secure. IvParameterSpec iv = new IvParameterSpec(new byte[16]); cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); return cipher.doFinal(data); } /** * Attempts to compress the data in the provided source array into the given * destination array. If the compressed data will fit into the destination * array, then this method will return the number of bytes of compressed data * in the array. Otherwise, it will return -1 to indicate that the * compression was not successful. Note that if -1 is returned, then the data * in the destination array should be considered invalid. * * @param src The array containing the raw data to compress. * @param dst The array into which the compressed data should be written. * * @return The number of bytes of compressed data, or -1 if it was not * possible to actually compress the data. */ public int compress(byte[] src, byte[] dst) { Deflater deflater = new Deflater(); try { deflater.setInput(src); deflater.finish(); int compressedLength = deflater.deflate(dst); if (deflater.finished()) { return compressedLength; } else { return -1; } } finally { deflater.end(); } } /** * Attempts to uncompress the data in the provided source array into the given * destination array. If the uncompressed data will fit into the given * destination array, then this method will return the number of bytes of * uncompressed data written into the destination buffer. Otherwise, it will * return a negative value to indicate that the destination buffer was not * large enough. The absolute value of that negative return value will * indicate the buffer size required to fully decompress the data. Note that * if a negative value is returned, then the data in the destination array * should be considered invalid. * * @param src The array containing the compressed data. * @param dst The array into which the uncompressed data should be written. * * @return A positive value containing the number of bytes of uncompressed * data written into the destination buffer, or a negative value * whose absolute value is the size of the destination buffer * required to fully decompress the provided data. * * @throws DataFormatException If a problem occurs while attempting to * uncompress the data. */ public int uncompress(byte[] src, byte[] dst) throws DataFormatException { Inflater inflater = new Inflater(); try { inflater.setInput(src); int decompressedLength = inflater.inflate(dst); if (inflater.finished()) { return decompressedLength; } else { int totalLength = decompressedLength; while (! inflater.finished()) { totalLength += inflater.inflate(dst); } return -totalLength; } } finally { inflater.end(); } } }