0001: /*
0002:
0003: Derby - Class org.apache.derby.impl.services.jce.JCECipherFactory
0004:
0005: Licensed to the Apache Software Foundation (ASF) under one or more
0006: contributor license agreements. See the NOTICE file distributed with
0007: this work for additional information regarding copyright ownership.
0008: The ASF licenses this file to you under the Apache License, Version 2.0
0009: (the "License"); you may not use this file except in compliance with
0010: the License. You may obtain a copy of the License at
0011:
0012: http://www.apache.org/licenses/LICENSE-2.0
0013:
0014: Unless required by applicable law or agreed to in writing, software
0015: distributed under the License is distributed on an "AS IS" BASIS,
0016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: See the License for the specific language governing permissions and
0018: limitations under the License.
0019:
0020: */
0021:
0022: package org.apache.derby.impl.services.jce;
0023:
0024: import org.apache.derby.iapi.services.crypto.CipherFactory;
0025: import org.apache.derby.iapi.services.crypto.CipherProvider;
0026:
0027: import org.apache.derby.iapi.services.monitor.Monitor;
0028: import org.apache.derby.iapi.services.sanity.SanityManager;
0029:
0030: import org.apache.derby.iapi.error.StandardException;
0031:
0032: import org.apache.derby.iapi.services.info.JVMInfo;
0033: import org.apache.derby.iapi.util.StringUtil;
0034:
0035: import org.apache.derby.iapi.reference.SQLState;
0036: import org.apache.derby.iapi.reference.Attribute;
0037: import org.apache.derby.iapi.util.StringUtil;
0038:
0039: import java.util.Properties;
0040: import java.util.Enumeration;
0041: import java.security.Key;
0042: import java.security.Provider;
0043: import java.security.SecureRandom;
0044: import java.security.Security;
0045: import java.security.InvalidKeyException;
0046: import java.security.NoSuchAlgorithmException;
0047: import java.security.MessageDigest;
0048: import java.security.spec.KeySpec;
0049: import java.security.spec.InvalidKeySpecException;
0050: import java.io.FileNotFoundException;
0051: import java.io.IOException;
0052: import java.io.InputStream;
0053: import java.io.DataInputStream;
0054:
0055: import javax.crypto.KeyGenerator;
0056: import javax.crypto.SecretKey;
0057: import javax.crypto.SecretKeyFactory;
0058: import javax.crypto.spec.DESKeySpec;
0059: import javax.crypto.spec.SecretKeySpec;
0060: import org.apache.derby.iapi.store.raw.RawStoreFactory;
0061:
0062: import org.apache.derby.io.StorageFactory;
0063: import org.apache.derby.io.WritableStorageFactory;
0064: import org.apache.derby.io.StorageFile;
0065: import org.apache.derby.io.StorageRandomAccessFile;
0066:
0067: /**
0068: This CipherFactory creates new JCECipherProvider.
0069:
0070: @see CipherFactory
0071: */
0072: public final class JCECipherFactory implements CipherFactory,
0073: java.security.PrivilegedExceptionAction {
0074: private final static String MESSAGE_DIGEST = "MD5";
0075:
0076: private final static String DEFAULT_PROVIDER = "com.sun.crypto.provider.SunJCE";
0077: private final static String DEFAULT_ALGORITHM = "DES/CBC/NoPadding";
0078: private final static String DES = "DES";
0079: private final static String DESede = "DESede";
0080: private final static String TripleDES = "TripleDES";
0081: private final static String AES = "AES";
0082:
0083: // minimum boot password length in bytes
0084: private final static int BLOCK_LENGTH = 8;
0085:
0086: /**
0087: AES encryption takes in an default Initialization vector length (IV) length of 16 bytes
0088: This is needed to generate an IV to use for encryption and decryption process
0089: @see CipherProvider
0090: */
0091: private final static int AES_IV_LENGTH = 16;
0092:
0093: // key length in bytes
0094: private int keyLengthBits;
0095: private int encodedKeyLength;
0096: private String cryptoAlgorithm;
0097: private String cryptoAlgorithmShort;
0098: private String cryptoProvider;
0099: private String cryptoProviderShort;
0100: private MessageDigest messageDigest;
0101:
0102: private SecretKey mainSecretKey;
0103: private byte[] mainIV;
0104:
0105: // properties that needs to be stored in the
0106: // in the service.properties file.
0107: private Properties persistentProperties;
0108:
0109: /**
0110: Amount of data that is used for verification of external encryption key
0111: This does not include the MD5 checksum bytes
0112: */
0113: private final static int VERIFYKEY_DATALEN = 4096;
0114: private StorageFile activeFile;
0115: private int action;
0116: private String activePerms;
0117:
0118: /*
0119: * Constructor of JCECipherFactory, initializes the new instances.
0120: *
0121: * @param create true, if the database is getting configured
0122: * for encryption.
0123: * @param props encryption properties/attributes to use
0124: * for creating the cipher factory.
0125: * @param newAttrs true, if cipher factory has to be created using
0126: * should using the new attributes specified by the user.
0127: * For example to reencrypt the database with
0128: * a new password.
0129: */
0130: public JCECipherFactory(boolean create, Properties props,
0131: boolean newAttributes) throws StandardException {
0132: init(create, props, newAttributes);
0133: }
0134:
0135: static String providerErrorName(String cps) {
0136:
0137: return cps == null ? "default" : cps;
0138: }
0139:
0140: private byte[] generateUniqueBytes() throws StandardException {
0141: try {
0142:
0143: String provider = cryptoProviderShort;
0144:
0145: KeyGenerator keyGen;
0146: if (provider == null) {
0147: keyGen = KeyGenerator.getInstance(cryptoAlgorithmShort);
0148: } else {
0149: if (provider.equals("BouncyCastleProvider"))
0150: provider = "BC";
0151: keyGen = KeyGenerator.getInstance(cryptoAlgorithmShort,
0152: provider);
0153: }
0154:
0155: keyGen.init(keyLengthBits);
0156:
0157: SecretKey key = keyGen.generateKey();
0158:
0159: return key.getEncoded();
0160:
0161: } catch (java.security.NoSuchAlgorithmException nsae) {
0162: throw StandardException.newException(
0163: SQLState.ENCRYPTION_NOSUCH_ALGORITHM,
0164: cryptoAlgorithm, JCECipherFactory
0165: .providerErrorName(cryptoProviderShort));
0166: } catch (java.security.NoSuchProviderException nspe) {
0167: throw StandardException.newException(
0168: SQLState.ENCRYPTION_BAD_PROVIDER, JCECipherFactory
0169: .providerErrorName(cryptoProviderShort));
0170: }
0171: }
0172:
0173: /**
0174: Encrypt the secretKey with the boot password.
0175: This includes the following steps,
0176: getting muck from the boot password and then using this to generate a key,
0177: generating an appropriate IV using the muck
0178: using the key and IV thus generated to create the appropriate cipher provider
0179: and encrypting the secretKey
0180: @return hexadecimal string of the encrypted secretKey
0181:
0182: @exception StandardException Standard Cloudscape error policy
0183: */
0184: private String encryptKey(byte[] secretKey, byte[] bootPassword)
0185: throws StandardException {
0186: // In case of AES, care needs to be taken to allow for 16 bytes muck as well
0187: // as to have the secretKey that needs encryption to be a aligned appropriately
0188: // AES supports 16 bytes block size
0189:
0190: int muckLength = secretKey.length;
0191: if (cryptoAlgorithmShort.equals(AES))
0192: muckLength = AES_IV_LENGTH;
0193:
0194: byte[] muck = getMuckFromBootPassword(bootPassword, muckLength);
0195: SecretKey key = generateKey(muck);
0196: byte[] IV = generateIV(muck);
0197: CipherProvider tmpCipherProvider = createNewCipher(ENCRYPT,
0198: key, IV);
0199:
0200: // store the actual secretKey.length before any possible padding
0201: encodedKeyLength = secretKey.length;
0202:
0203: // for the secretKey to be encrypted, first ensure that it is aligned to the block size of the
0204: // encryption algorithm by padding bytes appropriately if needed
0205: secretKey = padKey(secretKey, tmpCipherProvider
0206: .getEncryptionBlockSize());
0207:
0208: byte[] result = new byte[secretKey.length];
0209:
0210: // encrypt the secretKey using the key generated of muck from boot password and the generated IV
0211: tmpCipherProvider.encrypt(secretKey, 0, secretKey.length,
0212: result, 0);
0213:
0214: return org.apache.derby.iapi.util.StringUtil.toHexString(
0215: result, 0, result.length);
0216:
0217: }
0218:
0219: /**
0220: For block ciphers, and algorithms using the NoPadding scheme, the data that has
0221: to be encrypted needs to be a multiple of the expected block size for the cipher
0222: Pad the key with appropriate padding to make it blockSize align
0223: @param secretKey the data that needs blocksize alignment
0224: @param blockSizeAlign secretKey needs to be blocksize aligned
0225: @return a byte array with the contents of secretKey along with padded bytes in the end
0226: to make it blockSize aligned
0227: */
0228: private byte[] padKey(byte[] secretKey, int blockSizeAlign) {
0229: byte[] result = secretKey;
0230: if (secretKey.length % blockSizeAlign != 0) {
0231: int encryptedLength = secretKey.length + blockSizeAlign
0232: - (secretKey.length % blockSizeAlign);
0233: result = new byte[encryptedLength];
0234: System.arraycopy(secretKey, 0, result, 0, secretKey.length);
0235: }
0236: return result;
0237: }
0238:
0239: /**
0240: Decrypt the secretKey with the user key .
0241: This includes the following steps,
0242: retrieve the encryptedKey, generate the muck from the boot password and generate an appropriate IV using
0243: the muck,and using the key and IV decrypt the encryptedKey
0244: @return decrypted key
0245: @exception StandardException Standard Cloudscape error policy
0246: */
0247: private byte[] decryptKey(String encryptedKey,
0248: int encodedKeyCharLength, byte[] bootPassword)
0249: throws StandardException {
0250: byte[] secretKey = org.apache.derby.iapi.util.StringUtil
0251: .fromHexString(encryptedKey, 0, encodedKeyCharLength);
0252: // In case of AES, care needs to be taken to allow for 16 bytes muck as well
0253: // as to have the secretKey that needs encryption to be a aligned appropriately
0254: // AES supports 16 bytes block size
0255: int muckLength;
0256: if (cryptoAlgorithmShort.equals(AES))
0257: muckLength = AES_IV_LENGTH;
0258: else
0259: muckLength = secretKey.length;
0260:
0261: byte[] muck = getMuckFromBootPassword(bootPassword, muckLength);
0262:
0263: // decrypt the encryptedKey with the mucked up boot password to recover
0264: // the secretKey
0265: SecretKey key = generateKey(muck);
0266: byte[] IV = generateIV(muck);
0267:
0268: createNewCipher(DECRYPT, key, IV).decrypt(secretKey, 0,
0269: secretKey.length, secretKey, 0);
0270:
0271: return secretKey;
0272: }
0273:
0274: private byte[] getMuckFromBootPassword(byte[] bootPassword,
0275: int encodedKeyByteLength) {
0276: int ulength = bootPassword.length;
0277:
0278: byte[] muck = new byte[encodedKeyByteLength];
0279:
0280: int rotation = 0;
0281: for (int i = 0; i < bootPassword.length; i++)
0282: rotation += bootPassword[i];
0283:
0284: for (int i = 0; i < encodedKeyByteLength; i++)
0285: muck[i] = (byte) (bootPassword[(i + rotation) % ulength] ^ (bootPassword[i
0286: % ulength] << 4));
0287:
0288: return muck;
0289: }
0290:
0291: /**
0292: Generate a Key object using the input secretKey that can be used by
0293: JCECipherProvider to encrypt or decrypt.
0294:
0295: @exception StandardException Standard Cloudscape Error Policy
0296: */
0297: private SecretKey generateKey(byte[] secretKey)
0298: throws StandardException {
0299: int length = secretKey.length;
0300:
0301: if (length < CipherFactory.MIN_BOOTPASS_LENGTH)
0302: throw StandardException.newException(
0303: SQLState.ILLEGAL_BP_LENGTH, new Integer(
0304: MIN_BOOTPASS_LENGTH));
0305:
0306: try {
0307: if (cryptoAlgorithmShort.equals(DES)) { // single DES
0308: if (DESKeySpec.isWeak(secretKey, 0)) {
0309: // OK, it is weak, spice it up
0310: byte[] spice = StringUtil.getAsciiBytes("louDScap");
0311: for (int i = 0; i < 7; i++)
0312: secretKey[i] = (byte) ((spice[i] << 3) ^ secretKey[i]);
0313: }
0314: }
0315: return new SecretKeySpec(secretKey, cryptoAlgorithmShort);
0316: } catch (InvalidKeyException ike) {
0317: throw StandardException.newException(
0318: SQLState.CRYPTO_EXCEPTION, ike);
0319: }
0320:
0321: }
0322:
0323: /**
0324: Generate an IV using the input secretKey that can be used by
0325: JCECipherProvider to encrypt or decrypt.
0326: */
0327: private byte[] generateIV(byte[] secretKey) {
0328:
0329: // do a little simple minded muddling to make the IV not
0330: // strictly alphanumeric and the number of total possible keys a little
0331: // bigger.
0332: int IVlen = BLOCK_LENGTH;
0333:
0334: byte[] iv = null;
0335: if (cryptoAlgorithmShort.equals(AES)) {
0336: IVlen = AES_IV_LENGTH;
0337: iv = new byte[IVlen];
0338: iv[0] = (byte) (((secretKey[secretKey.length - 1] << 2) | 0xF) ^ secretKey[0]);
0339: for (int i = 1; i < BLOCK_LENGTH; i++)
0340: iv[i] = (byte) (((secretKey[i - 1] << (i % 5)) | 0xF) ^ secretKey[i]);
0341:
0342: for (int i = BLOCK_LENGTH; i < AES_IV_LENGTH; i++) {
0343: iv[i] = iv[i - BLOCK_LENGTH];
0344: }
0345:
0346: } else {
0347: iv = new byte[BLOCK_LENGTH];
0348: iv[0] = (byte) (((secretKey[secretKey.length - 1] << 2) | 0xF) ^ secretKey[0]);
0349: for (int i = 1; i < BLOCK_LENGTH; i++)
0350: iv[i] = (byte) (((secretKey[i - 1] << (i % 5)) | 0xF) ^ secretKey[i]);
0351: }
0352:
0353: return iv;
0354: }
0355:
0356: private int digest(byte[] input) {
0357: messageDigest.reset();
0358: byte[] digest = messageDigest.digest(input);
0359: byte[] condenseDigest = new byte[2];
0360:
0361: // no matter how long the digest is, condense it into an short.
0362: for (int i = 0; i < digest.length; i++)
0363: condenseDigest[i % 2] ^= digest[i];
0364:
0365: int retval = (condenseDigest[0] & 0xFF)
0366: | ((condenseDigest[1] << 8) & 0xFF00);
0367:
0368: return retval;
0369: }
0370:
0371: public SecureRandom getSecureRandom() {
0372: return new SecureRandom(mainIV);
0373: }
0374:
0375: public CipherProvider createNewCipher(int mode)
0376: throws StandardException {
0377: return createNewCipher(mode, mainSecretKey, mainIV);
0378: }
0379:
0380: private CipherProvider createNewCipher(int mode,
0381: SecretKey secretKey, byte[] iv) throws StandardException {
0382: return new JCECipherProvider(mode, secretKey, iv,
0383: cryptoAlgorithm, cryptoProviderShort);
0384: }
0385:
0386: /*
0387: * Initilize the new instance of this class.
0388: */
0389: public void init(boolean create, Properties properties,
0390: boolean newAttrs) throws StandardException {
0391:
0392: boolean provider_or_algo_specified = false;
0393: boolean storeProperties = create;
0394: persistentProperties = new Properties();
0395:
0396: // get the external key specified by the user to
0397: // encrypt the database. If user is reencrypting the
0398: // database with a new encryption key, read the value of
0399: // the new encryption key.
0400: String externalKey = properties
0401: .getProperty((newAttrs ? Attribute.NEW_CRYPTO_EXTERNAL_KEY
0402: : Attribute.CRYPTO_EXTERNAL_KEY));
0403: if (externalKey != null) {
0404: storeProperties = false;
0405: }
0406:
0407: cryptoProvider = properties
0408: .getProperty(Attribute.CRYPTO_PROVIDER);
0409:
0410: if (cryptoProvider == null) {
0411: // JDK 1.3 does not create providers by itself.
0412: if (JVMInfo.JDK_ID == JVMInfo.J2SE_13) {
0413:
0414: String vendor;
0415: try {
0416: vendor = System.getProperty("java.vendor", "");
0417: } catch (SecurityException se) {
0418: vendor = "";
0419: }
0420:
0421: vendor = StringUtil.SQLToUpperCase(vendor);
0422:
0423: if (vendor.startsWith("IBM "))
0424: cryptoProvider = "com.ibm.crypto.provider.IBMJCE";
0425: else if (vendor.startsWith("SUN "))
0426: cryptoProvider = "com.sun.crypto.provider.SunJCE";
0427:
0428: }
0429: } else {
0430: provider_or_algo_specified = true;
0431:
0432: // explictly putting the properties back into the properties
0433: // saves then in service.properties at create time.
0434: // if (storeProperties)
0435: // properties.put(Attribute.CRYPTO_PROVIDER, cryptoProvider);
0436:
0437: int dotPos = cryptoProvider.lastIndexOf('.');
0438: if (dotPos == -1)
0439: cryptoProviderShort = cryptoProvider;
0440: else
0441: cryptoProviderShort = cryptoProvider
0442: .substring(dotPos + 1);
0443:
0444: }
0445:
0446: cryptoAlgorithm = properties
0447: .getProperty(Attribute.CRYPTO_ALGORITHM);
0448: if (cryptoAlgorithm == null)
0449: cryptoAlgorithm = DEFAULT_ALGORITHM;
0450: else {
0451: provider_or_algo_specified = true;
0452:
0453: }
0454:
0455: // explictly putting the properties back into the properties
0456: // saves then in service.properties at create time.
0457: if (storeProperties)
0458: persistentProperties.put(Attribute.CRYPTO_ALGORITHM,
0459: cryptoAlgorithm);
0460:
0461: int firstSlashPos = cryptoAlgorithm.indexOf('/');
0462: int lastSlashPos = cryptoAlgorithm.lastIndexOf('/');
0463: if (firstSlashPos < 0 || lastSlashPos < 0
0464: || firstSlashPos == lastSlashPos)
0465: throw StandardException
0466: .newException(SQLState.ENCRYPTION_BAD_ALG_FORMAT,
0467: cryptoAlgorithm);
0468:
0469: cryptoAlgorithmShort = cryptoAlgorithm.substring(0,
0470: firstSlashPos);
0471:
0472: if (provider_or_algo_specified) {
0473: // Track 3715 - disable use of provider/aglo specification if
0474: // jce environment is not 1.2.1. The ExemptionMechanism class
0475: // exists in jce1.2.1 and not in jce1.2, so try and load the
0476: // class and if you can't find it don't allow the encryption.
0477: // This is a requirement from the government to give cloudscape
0478: // export clearance for 3.6. Note that the check is not needed
0479: // if no provider/algo is specified, in that case we default to
0480: // a DES weak encryption algorithm which also is allowed for
0481: // export (this is how 3.5 got it's clearance).
0482: try {
0483: Class c = Class
0484: .forName("javax.crypto.ExemptionMechanism");
0485: } catch (Throwable t) {
0486: throw StandardException
0487: .newException(SQLState.ENCRYPTION_BAD_JCE);
0488: }
0489: }
0490:
0491: // If connecting to an existing database and Attribute.CRYPTO_KEY_LENGTH is set
0492: // then obtain the encoded key length values without padding bytes and retrieve
0493: // the keylength in bits if boot password mechanism is used
0494: // note: Attribute.CRYPTO_KEY_LENGTH is set during creation time to a supported
0495: // key length in the connection url. Internally , two values are stored in this property
0496: // if encryptionKey is used, this property will have only the encoded key length
0497: // if boot password mechanism is used, this property will have the following
0498: // keylengthBits-EncodedKeyLength
0499:
0500: if (!create) {
0501: // if available, parse the keylengths stored in Attribute.CRYPTO_KEY_LENGTH
0502: if (properties.getProperty(Attribute.CRYPTO_KEY_LENGTH) != null) {
0503: String keyLengths = properties
0504: .getProperty(Attribute.CRYPTO_KEY_LENGTH);
0505: int pos = keyLengths.lastIndexOf('-');
0506: encodedKeyLength = Integer.parseInt(keyLengths
0507: .substring(pos + 1));
0508: if (pos != -1)
0509: keyLengthBits = Integer.parseInt(keyLengths
0510: .substring(0, pos));
0511: }
0512: }
0513:
0514: // case 1 - if 'encryptionKey' is not set and 'encryptionKeyLength' is set, then use
0515: // the 'encryptionKeyLength' property value as the keyLength in bits.
0516: // case 2 - 'encryptionKey' property is not set and 'encryptionKeyLength' is not set, then
0517: // use the defaults keylength: 56bits for DES, 168 for DESede and 128 for any other encryption
0518: // algorithm
0519:
0520: if (externalKey == null && create) {
0521: if (properties.getProperty(Attribute.CRYPTO_KEY_LENGTH) != null) {
0522: keyLengthBits = Integer.parseInt(properties
0523: .getProperty(Attribute.CRYPTO_KEY_LENGTH));
0524: } else if (cryptoAlgorithmShort.equals(DES)) {
0525: keyLengthBits = 56;
0526: } else if (cryptoAlgorithmShort.equals(DESede)
0527: || cryptoAlgorithmShort.equals(TripleDES)) {
0528: keyLengthBits = 168;
0529:
0530: } else {
0531: keyLengthBits = 128;
0532: }
0533: }
0534:
0535: // check the feedback mode
0536: String feedbackMode = cryptoAlgorithm.substring(
0537: firstSlashPos + 1, lastSlashPos);
0538:
0539: if (!feedbackMode.equals("CBC") && !feedbackMode.equals("CFB")
0540: && !feedbackMode.equals("ECB")
0541: && !feedbackMode.equals("OFB"))
0542: throw StandardException.newException(
0543: SQLState.ENCRYPTION_BAD_FEEDBACKMODE, feedbackMode);
0544:
0545: // check the NoPadding mode is used
0546: String padding = cryptoAlgorithm.substring(lastSlashPos + 1,
0547: cryptoAlgorithm.length());
0548: if (!padding.equals("NoPadding"))
0549: throw StandardException.newException(
0550: SQLState.ENCRYPTION_BAD_PADDING, padding);
0551:
0552: Throwable t;
0553: try {
0554: if (cryptoProvider != null) {
0555: // provider package should be set by property
0556: if (Security.getProvider(cryptoProviderShort) == null) {
0557: action = 1;
0558: // add provider through privileged block.
0559: java.security.AccessController.doPrivileged(this );
0560: }
0561: }
0562:
0563: // need this to check the boot password
0564: messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST);
0565:
0566: byte[] generatedKey;
0567: if (externalKey != null) {
0568:
0569: // incorrect to specify external key and boot password
0570: if (properties
0571: .getProperty((newAttrs ? Attribute.NEW_BOOT_PASSWORD
0572: : Attribute.BOOT_PASSWORD)) != null)
0573: throw StandardException
0574: .newException(SQLState.SERVICE_WRONG_BOOT_PASSWORD);
0575:
0576: generatedKey = org.apache.derby.iapi.util.StringUtil
0577: .fromHexString(externalKey, 0, externalKey
0578: .length());
0579: if (generatedKey == null) {
0580: throw StandardException.newException(
0581: // If length is even, we assume invalid character(s),
0582: // based on how 'fromHexString' behaves.
0583: externalKey.length() % 2 == 0 ? SQLState.ENCRYPTION_ILLEGAL_EXKEY_CHARS
0584: : SQLState.ENCRYPTION_INVALID_EXKEY_LENGTH);
0585: }
0586:
0587: } else {
0588:
0589: generatedKey = handleBootPassword(create, properties,
0590: newAttrs);
0591: if (create || newAttrs)
0592: persistentProperties.put(
0593: Attribute.CRYPTO_KEY_LENGTH, keyLengthBits
0594: + "-" + generatedKey.length);
0595: }
0596:
0597: // Make a key and IV object out of the generated key
0598: mainSecretKey = generateKey(generatedKey);
0599: mainIV = generateIV(generatedKey);
0600:
0601: if (create) {
0602: persistentProperties.put(Attribute.DATA_ENCRYPTION,
0603: "true");
0604:
0605: // Set two new properties to allow for future changes to the log and data encryption
0606: // schemes. This property is introduced in version 10 , value starts at 1.
0607: persistentProperties.put(
0608: RawStoreFactory.DATA_ENCRYPT_ALGORITHM_VERSION,
0609: String.valueOf(1));
0610: persistentProperties.put(
0611: RawStoreFactory.LOG_ENCRYPT_ALGORITHM_VERSION,
0612: String.valueOf(1));
0613: }
0614:
0615: return;
0616: } catch (java.security.PrivilegedActionException pae) {
0617: t = pae.getException();
0618: } catch (NoSuchAlgorithmException nsae) {
0619: t = nsae;
0620: } catch (SecurityException se) {
0621: t = se;
0622: } catch (LinkageError le) {
0623: t = le;
0624: } catch (ClassCastException cce) {
0625: t = cce;
0626: }
0627:
0628: throw StandardException.newException(
0629: SQLState.MISSING_ENCRYPTION_PROVIDER, t);
0630: }
0631:
0632: private byte[] handleBootPassword(boolean create,
0633: Properties properties, boolean newPasswd)
0634: throws StandardException {
0635:
0636: // get the key specifed by the user. If user is reencrypting the
0637: // database; read the value of the new password.
0638: String inputKey = properties
0639: .getProperty((newPasswd ? Attribute.NEW_BOOT_PASSWORD
0640: : Attribute.BOOT_PASSWORD));
0641: if (inputKey == null) {
0642: throw StandardException
0643: .newException(SQLState.SERVICE_WRONG_BOOT_PASSWORD);
0644: }
0645:
0646: byte[] bootPassword = StringUtil.getAsciiBytes(inputKey);
0647:
0648: if (bootPassword.length < CipherFactory.MIN_BOOTPASS_LENGTH) {
0649: String messageId = create ? SQLState.SERVICE_BOOT_PASSWORD_TOO_SHORT
0650: : SQLState.SERVICE_WRONG_BOOT_PASSWORD;
0651:
0652: throw StandardException.newException(messageId);
0653: }
0654:
0655: // Each database has its own unique encryption key that is
0656: // not known even to the user. However, this key is masked
0657: // with the user input key and stored in the
0658: // services.properties file so that, with the user key, the
0659: // encryption key can easily be recovered.
0660: // To change the user encryption key to a database, simply
0661: // recover the unique real encryption key and masked it
0662: // with the new user key.
0663:
0664: byte[] generatedKey;
0665:
0666: if (create || newPasswd) {
0667: //
0668: generatedKey = generateUniqueBytes();
0669:
0670: persistentProperties.put(RawStoreFactory.ENCRYPTED_KEY,
0671: saveSecretKey(generatedKey, bootPassword));
0672:
0673: } else {
0674: generatedKey = getDatabaseSecretKey(properties,
0675: bootPassword, SQLState.SERVICE_WRONG_BOOT_PASSWORD);
0676: }
0677:
0678: return generatedKey;
0679: }
0680:
0681: /*
0682: * put all the encyrpion cipger related properties that has to
0683: * be made peristent into the database service properties list.
0684: * @param properties properties object that is used to store
0685: * cipher properties persistently.
0686: */
0687: public void saveProperties(Properties properties) {
0688: // put the cipher properties to be persistent into the
0689: // system perisistent properties.
0690: for (Enumeration e = persistentProperties.keys(); e
0691: .hasMoreElements();) {
0692: String key = (String) e.nextElement();
0693: properties.put(key, persistentProperties.get(key));
0694: }
0695:
0696: // clear the cipher properties to be persistent.
0697: persistentProperties = null;
0698: }
0699:
0700: /**
0701: get the secretkey used for encryption and decryption when boot password mechanism is used for encryption
0702: Steps include
0703: retrieve the stored key, decrypt the stored key and verify if the correct boot password was passed
0704: There is a possibility that the decrypted key includes the original key and padded bytes in order to have
0705: been block size aligned during encryption phase. Hence extract the original key
0706:
0707: @param properties properties to retrieve the encrypted key
0708: @param bootPassword boot password used to connect to the encrypted database
0709: @param errorState errorstate to account for any errors during retrieval /creation of the secretKey
0710: @return the original unencrypted key bytes to use for encryption and decrytion
0711:
0712: */
0713: private byte[] getDatabaseSecretKey(Properties properties,
0714: byte[] bootPassword, String errorState)
0715: throws StandardException {
0716:
0717: // recover the generated secret encryption key from the
0718: // services.properties file and the user key.
0719: String keyString = properties
0720: .getProperty(RawStoreFactory.ENCRYPTED_KEY);
0721: if (keyString == null)
0722: throw StandardException.newException(errorState);
0723:
0724: int encodedKeyCharLength = keyString.indexOf('-');
0725:
0726: if (encodedKeyCharLength == -1) // bad form
0727: throw StandardException.newException(errorState);
0728:
0729: int verifyKey = Integer.parseInt(keyString
0730: .substring(encodedKeyCharLength + 1));
0731: byte[] generatedKey = decryptKey(keyString,
0732: encodedKeyCharLength, bootPassword);
0733:
0734: int checkKey = digest(generatedKey);
0735:
0736: if (checkKey != verifyKey)
0737: throw StandardException.newException(errorState);
0738:
0739: // if encodedKeyLength is not defined, then either it is an old version with no support for different
0740: // key sizes and padding except for defaults
0741: byte[] result;
0742: if (encodedKeyLength != 0) {
0743: result = new byte[encodedKeyLength];
0744:
0745: // extract the generated key without the padding bytes
0746: System.arraycopy(generatedKey, 0, result, 0,
0747: encodedKeyLength);
0748: return result;
0749: }
0750:
0751: return generatedKey;
0752: }
0753:
0754: private String saveSecretKey(byte[] secretKey, byte[] bootPassword)
0755: throws StandardException {
0756: String encryptedKey = encryptKey(secretKey, bootPassword);
0757:
0758: // make a verification key out of the message digest of
0759: // the generated key
0760: int verifyKey = digest(secretKey);
0761:
0762: return encryptedKey.concat("-" + verifyKey);
0763:
0764: }
0765:
0766: public String changeBootPassword(String changeString,
0767: Properties properties, CipherProvider verify)
0768: throws StandardException {
0769:
0770: // the new bootPassword is expected to be of the form
0771: // oldkey , newkey.
0772: int seperator = changeString.indexOf(',');
0773: if (seperator == -1)
0774: throw StandardException
0775: .newException(SQLState.WRONG_PASSWORD_CHANGE_FORMAT);
0776:
0777: String oldBP = changeString.substring(0, seperator).trim();
0778: byte[] oldBPAscii = StringUtil.getAsciiBytes(oldBP);
0779: if (oldBPAscii == null
0780: || oldBPAscii.length < CipherFactory.MIN_BOOTPASS_LENGTH)
0781: throw StandardException
0782: .newException(SQLState.WRONG_BOOT_PASSWORD);
0783: ;
0784:
0785: String newBP = changeString.substring(seperator + 1).trim();
0786: byte[] newBPAscii = StringUtil.getAsciiBytes(newBP);
0787: if (newBPAscii == null
0788: || newBPAscii.length < CipherFactory.MIN_BOOTPASS_LENGTH)
0789: throw StandardException.newException(
0790: SQLState.ILLEGAL_BP_LENGTH, new Integer(
0791: CipherFactory.MIN_BOOTPASS_LENGTH));
0792:
0793: // verify old key
0794:
0795: byte[] generatedKey = getDatabaseSecretKey(properties,
0796: oldBPAscii, SQLState.WRONG_BOOT_PASSWORD);
0797:
0798: // make sure the oldKey is correct
0799: byte[] IV = generateIV(generatedKey);
0800:
0801: if (!((JCECipherProvider) verify).verifyIV(IV))
0802: throw StandardException
0803: .newException(SQLState.WRONG_BOOT_PASSWORD);
0804:
0805: // Make the new key. The generated key is unchanged, only the
0806: // encrypted key is changed.
0807: String newkey = saveSecretKey(generatedKey, newBPAscii);
0808:
0809: properties.put(Attribute.CRYPTO_KEY_LENGTH, keyLengthBits + "-"
0810: + encodedKeyLength);
0811:
0812: return saveSecretKey(generatedKey, newBPAscii);
0813: }
0814:
0815: /**
0816: perform actions with privileges enabled.
0817: */
0818: public final Object run() throws StandardException,
0819: InstantiationException, IllegalAccessException {
0820:
0821: try {
0822:
0823: switch (action) {
0824: case 1:
0825: Security.addProvider((Provider) (Class
0826: .forName(cryptoProvider).newInstance()));
0827: break;
0828: case 2:
0829: // SECURITY PERMISSION - MP1 and/or OP4
0830: // depends on the value of activePerms
0831: return activeFile.getRandomAccessFile(activePerms);
0832: case 3:
0833: return activeFile.getInputStream();
0834:
0835: }
0836:
0837: } catch (ClassNotFoundException cnfe) {
0838: throw StandardException.newException(
0839: SQLState.ENCRYPTION_NO_PROVIDER_CLASS,
0840: cryptoProvider);
0841: } catch (FileNotFoundException fnfe) {
0842: throw StandardException.newException(
0843: SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,
0844: cryptoProvider);
0845: }
0846: return null;
0847: }
0848:
0849: /**
0850: The database can be encrypted with an encryption key given in connection url.
0851: For security reasons, this key is not made persistent in the database.
0852:
0853: But it is necessary to verify the encryption key when booting the database if it is similar
0854: to the one used when creating the database
0855: This needs to happen before we access the data/logs to avoid the risk of corrupting the
0856: database because of a wrong encryption key.
0857:
0858: This method performs the steps necessary to verify the encryption key if an external
0859: encryption key is given.
0860:
0861: At database creation, 4k of random data is generated using SecureRandom and MD5 is used
0862: to compute the checksum for the random data thus generated. This 4k page of random data
0863: is then encrypted using the encryption key. The checksum of unencrypted data and
0864: encrypted data is made persistent in the database in file by name given by
0865: Attribute.CRYPTO_EXTERNAL_KEY_VERIFYFILE (verifyKey.dat). This file exists directly under the
0866: database root directory.
0867:
0868: When trying to boot an existing encrypted database, the given encryption key is used to decrypt
0869: the data in the verifyKey.dat and the checksum is calculated and compared against the original
0870: stored checksum. If these checksums dont match an exception is thrown.
0871:
0872: Please note, this process of verifying the key does not provide any added security but only is
0873: intended to allow to fail gracefully if a wrong encryption key is used
0874:
0875: StandardException is thrown if there are any problems during the process of verification
0876: of the encryption key or if there is any mismatch of the encryption key.
0877:
0878: */
0879:
0880: public void verifyKey(boolean create, StorageFactory sf,
0881: Properties properties) throws StandardException {
0882:
0883: if (properties.getProperty(Attribute.CRYPTO_EXTERNAL_KEY) == null)
0884: return;
0885:
0886: // if firstTime ( ie during creation of database, initial key used )
0887: // In order to allow for verifying the external key for future database boot,
0888: // generate random 4k of data and store the encrypted random data and the checksum
0889: // using MD5 of the unencrypted data. That way, on next database boot a check is performed
0890: // to verify if the key is the same as used when the database was created
0891:
0892: InputStream verifyKeyInputStream = null;
0893: StorageRandomAccessFile verifyKeyFile = null;
0894: byte[] data = new byte[VERIFYKEY_DATALEN];
0895: try {
0896: if (create) {
0897: getSecureRandom().nextBytes(data);
0898: // get the checksum
0899: byte[] checksum = getMD5Checksum(data);
0900:
0901: CipherProvider tmpCipherProvider = createNewCipher(
0902: ENCRYPT, mainSecretKey, mainIV);
0903: tmpCipherProvider
0904: .encrypt(data, 0, data.length, data, 0);
0905: // openFileForWrite
0906: verifyKeyFile = privAccessFile(sf,
0907: Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE, "rw");
0908: // write the checksum length as int, and then the checksum and then the encrypted data
0909: verifyKeyFile.writeInt(checksum.length);
0910: verifyKeyFile.write(checksum);
0911: verifyKeyFile.write(data);
0912: verifyKeyFile.sync(true);
0913: } else {
0914: // Read from verifyKey.dat as an InputStream. This allows for
0915: // reading the information from verifyKey.dat successfully even when using the jar
0916: // subprotocol to boot derby. (DERBY-1373)
0917: verifyKeyInputStream = privAccessGetInputStream(sf,
0918: Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE);
0919: DataInputStream dis = new DataInputStream(
0920: verifyKeyInputStream);
0921: // then read the checksum length
0922: int checksumLen = dis.readInt();
0923:
0924: byte[] originalChecksum = new byte[checksumLen];
0925: dis.readFully(originalChecksum);
0926:
0927: dis.readFully(data);
0928:
0929: // decrypt data with key
0930: CipherProvider tmpCipherProvider = createNewCipher(
0931: DECRYPT, mainSecretKey, mainIV);
0932: tmpCipherProvider
0933: .decrypt(data, 0, data.length, data, 0);
0934:
0935: byte[] verifyChecksum = getMD5Checksum(data);
0936:
0937: if (!MessageDigest.isEqual(originalChecksum,
0938: verifyChecksum)) {
0939: throw StandardException
0940: .newException(SQLState.ENCRYPTION_BAD_EXTERNAL_KEY);
0941: }
0942:
0943: }
0944: } catch (IOException ioe) {
0945: throw StandardException.newException(
0946: SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION, ioe);
0947: } finally {
0948: try {
0949: if (verifyKeyFile != null)
0950: verifyKeyFile.close();
0951: if (verifyKeyInputStream != null)
0952: verifyKeyInputStream.close();
0953: } catch (IOException ioee) {
0954: throw StandardException.newException(
0955: SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,
0956: ioee);
0957: }
0958: }
0959: return;
0960: }
0961:
0962: /**
0963: Use MD5 MessageDigest algorithm to generate checksum
0964: @param data data to be used to compute the hash value
0965: @return returns the hash value computed using the data
0966:
0967: */
0968: private byte[] getMD5Checksum(byte[] data) throws StandardException {
0969: try {
0970: // get the checksum
0971: MessageDigest md5 = MessageDigest.getInstance("MD5");
0972: return md5.digest(data);
0973: } catch (NoSuchAlgorithmException nsae) {
0974: throw StandardException.newException(
0975: SQLState.ENCRYPTION_BAD_ALG_FORMAT, MESSAGE_DIGEST);
0976: }
0977:
0978: }
0979:
0980: /**
0981: access a file for either read/write
0982: @param storageFactory factory used for io access
0983: @param fileName name of the file to create and open for write
0984: The file will be created directly under the database root directory
0985: @param filePerms file permissions, if "rw" open file with read and write permissions
0986: if "r" , open file with read permissions
0987: @return StorageRandomAccessFile returns file with fileName for writing
0988: @exception IOException Any exception during accessing the file for read/write
0989: */
0990: private StorageRandomAccessFile privAccessFile(
0991: StorageFactory storageFactory, String fileName,
0992: String filePerms) throws java.io.IOException {
0993: StorageFile verifyKeyFile = storageFactory.newStorageFile("",
0994: fileName);
0995: activeFile = verifyKeyFile;
0996: this .action = 2;
0997: activePerms = filePerms;
0998: try {
0999: return (StorageRandomAccessFile) java.security.AccessController
1000: .doPrivileged(this );
1001: } catch (java.security.PrivilegedActionException pae) {
1002: throw (java.io.IOException) pae.getException();
1003: }
1004: }
1005:
1006: /**
1007: access a InputStream for a given file for reading.
1008: @param storageFactory factory used for io access
1009: @param fileName name of the file to open as a stream for reading
1010: @return InputStream returns the stream for the file with fileName for reading
1011: @exception IOException Any exception during accessing the file for read
1012: */
1013: private InputStream privAccessGetInputStream(
1014: StorageFactory storageFactory, String fileName)
1015: throws StandardException {
1016: StorageFile verifyKeyFile = storageFactory.newStorageFile("",
1017: fileName);
1018: activeFile = verifyKeyFile;
1019: this .action = 3;
1020: try {
1021: return (InputStream) java.security.AccessController
1022: .doPrivileged(this );
1023: } catch (java.security.PrivilegedActionException pae) {
1024: throw (StandardException) pae.getException();
1025: }
1026: }
1027:
1028: }
|