--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.services.jce.JCECipherFactory\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.services.jce;\r
+\r
+import org.apache.derby.iapi.services.crypto.CipherFactory;\r
+import org.apache.derby.iapi.services.crypto.CipherProvider;\r
+\r
+import org.apache.derby.iapi.services.monitor.Monitor;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.services.info.JVMInfo;\r
+import org.apache.derby.iapi.util.StringUtil;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.reference.Attribute;\r
+import org.apache.derby.iapi.util.StringUtil;\r
+\r
+import java.util.Properties;\r
+import java.util.Enumeration;\r
+import java.security.Key;\r
+import java.security.Provider;\r
+import java.security.SecureRandom;\r
+import java.security.Security;\r
+import java.security.InvalidKeyException;\r
+import java.security.NoSuchAlgorithmException;\r
+import java.security.MessageDigest;\r
+import java.security.spec.KeySpec;\r
+import java.security.spec.InvalidKeySpecException;\r
+import java.io.FileNotFoundException;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.DataInputStream;\r
+\r
+import javax.crypto.KeyGenerator;\r
+import javax.crypto.SecretKey;\r
+import javax.crypto.SecretKeyFactory;\r
+import javax.crypto.spec.DESKeySpec;\r
+import javax.crypto.spec.SecretKeySpec;\r
+import org.apache.derby.iapi.store.raw.RawStoreFactory;\r
+\r
+import org.apache.derby.io.StorageFactory;\r
+import org.apache.derby.io.WritableStorageFactory;\r
+import org.apache.derby.io.StorageFile;\r
+import org.apache.derby.io.StorageRandomAccessFile;\r
+/**\r
+ This CipherFactory creates new JCECipherProvider.\r
+\r
+ @see CipherFactory\r
+ */\r
+public final class JCECipherFactory implements CipherFactory, java.security.PrivilegedExceptionAction\r
+{\r
+ private final static String MESSAGE_DIGEST = "MD5";\r
+\r
+ private final static String DEFAULT_ALGORITHM = "DES/CBC/NoPadding";\r
+ private final static String DES = "DES";\r
+ private final static String DESede = "DESede";\r
+ private final static String TripleDES = "TripleDES";\r
+ private final static String AES = "AES";\r
+\r
+ // minimum boot password length in bytes\r
+ private final static int BLOCK_LENGTH = 8;\r
+\r
+ /**\r
+ AES encryption takes in an default Initialization vector length (IV) length of 16 bytes\r
+ This is needed to generate an IV to use for encryption and decryption process \r
+ @see CipherProvider\r
+ */\r
+ private final static int AES_IV_LENGTH = 16;\r
+\r
+ // key length in bytes\r
+ private int keyLengthBits;\r
+ private int encodedKeyLength;\r
+ private String cryptoAlgorithm;\r
+ private String cryptoAlgorithmShort;\r
+ private String cryptoProvider;\r
+ private String cryptoProviderShort;\r
+ private MessageDigest messageDigest;\r
+\r
+ private SecretKey mainSecretKey;\r
+ private byte[] mainIV;\r
+\r
+ // properties that needs to be stored in the\r
+ // in the service.properties file.\r
+ private Properties persistentProperties;\r
+\r
+\r
+ /**\r
+ Amount of data that is used for verification of external encryption key\r
+ This does not include the MD5 checksum bytes\r
+ */\r
+ private final static int VERIFYKEY_DATALEN = 4096;\r
+ private StorageFile activeFile;\r
+ private int action;\r
+ private String activePerms;\r
+\r
+\r
+ /*\r
+ * Constructor of JCECipherFactory, initializes the new instances.\r
+ *\r
+ * @param create true, if the database is getting configured \r
+ * for encryption.\r
+ * @param props encryption properties/attributes to use\r
+ * for creating the cipher factory.\r
+ * @param newAttrs true, if cipher factory has to be created using \r
+ * should using the new attributes specified by the user. \r
+ * For example to reencrypt the database with \r
+ * a new password.\r
+ */\r
+ public JCECipherFactory(boolean create, \r
+ Properties props,\r
+ boolean newAttributes) \r
+ throws StandardException\r
+ {\r
+ init(create, props, newAttributes);\r
+ }\r
+ \r
+\r
+\r
+ static String providerErrorName(String cps) {\r
+\r
+ return cps == null ? "default" : cps;\r
+ }\r
+\r
+\r
+ private byte[] generateUniqueBytes() throws StandardException\r
+ {\r
+ try {\r
+\r
+ String provider = cryptoProviderShort;\r
+\r
+ KeyGenerator keyGen;\r
+ if (provider == null)\r
+ {\r
+ keyGen = KeyGenerator.getInstance(cryptoAlgorithmShort);\r
+ }\r
+ else\r
+ {\r
+ if( provider.equals("BouncyCastleProvider"))\r
+ provider = "BC";\r
+ keyGen = KeyGenerator.getInstance(cryptoAlgorithmShort, provider);\r
+ }\r
+\r
+ keyGen.init(keyLengthBits);\r
+\r
+ SecretKey key = keyGen.generateKey();\r
+\r
+ return key.getEncoded();\r
+\r
+ } catch (java.security.NoSuchAlgorithmException nsae) {\r
+ throw StandardException.newException(SQLState.ENCRYPTION_NOSUCH_ALGORITHM, cryptoAlgorithm,\r
+ JCECipherFactory.providerErrorName(cryptoProviderShort));\r
+ } catch (java.security.NoSuchProviderException nspe) {\r
+ throw StandardException.newException(SQLState.ENCRYPTION_BAD_PROVIDER,\r
+ JCECipherFactory.providerErrorName(cryptoProviderShort));\r
+ }\r
+ }\r
+\r
+ /**\r
+ Encrypt the secretKey with the boot password.\r
+ This includes the following steps, \r
+ getting muck from the boot password and then using this to generate a key,\r
+ generating an appropriate IV using the muck\r
+ using the key and IV thus generated to create the appropriate cipher provider\r
+ and encrypting the secretKey \r
+ @return hexadecimal string of the encrypted secretKey\r
+\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ private String encryptKey(byte[] secretKey, byte[] bootPassword)\r
+ throws StandardException\r
+ {\r
+ // In case of AES, care needs to be taken to allow for 16 bytes muck as well\r
+ // as to have the secretKey that needs encryption to be a aligned appropriately\r
+ // AES supports 16 bytes block size\r
+\r
+ int muckLength = secretKey.length;\r
+ if(cryptoAlgorithmShort.equals(AES))\r
+ muckLength = AES_IV_LENGTH; \r
+\r
+ byte[] muck = getMuckFromBootPassword(bootPassword, muckLength);\r
+ SecretKey key = generateKey(muck);\r
+ byte[] IV = generateIV(muck);\r
+ CipherProvider tmpCipherProvider = createNewCipher(ENCRYPT,key,IV);\r
+ \r
+ // store the actual secretKey.length before any possible padding \r
+ encodedKeyLength = secretKey.length;\r
+\r
+ // for the secretKey to be encrypted, first ensure that it is aligned to the block size of the \r
+ // encryption algorithm by padding bytes appropriately if needed\r
+ secretKey = padKey(secretKey,tmpCipherProvider.getEncryptionBlockSize());\r
+\r
+ byte[] result = new byte[secretKey.length];\r
+\r
+ // encrypt the secretKey using the key generated of muck from boot password and the generated IV \r
+ tmpCipherProvider.encrypt(secretKey, 0, secretKey.length, result, 0);\r
+\r
+ return org.apache.derby.iapi.util.StringUtil.toHexString(result, 0, result.length);\r
+\r
+ }\r
+ \r
+ /**\r
+ For block ciphers, and algorithms using the NoPadding scheme, the data that has \r
+ to be encrypted needs to be a multiple of the expected block size for the cipher \r
+ Pad the key with appropriate padding to make it blockSize align\r
+ @param secretKey the data that needs blocksize alignment\r
+ @param blockSizeAlign secretKey needs to be blocksize aligned \r
+ @return a byte array with the contents of secretKey along with padded bytes in the end\r
+ to make it blockSize aligned\r
+ */\r
+ private byte[] padKey(byte[] secretKey,int blockSizeAlign)\r
+ {\r
+ byte [] result = secretKey;\r
+ if(secretKey.length % blockSizeAlign != 0 )\r
+ {\r
+ int encryptedLength = secretKey.length + blockSizeAlign - (secretKey.length % blockSizeAlign);\r
+ result = new byte[encryptedLength];\r
+ System.arraycopy(secretKey,0,result,0,secretKey.length);\r
+ }\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ Decrypt the secretKey with the user key .\r
+ This includes the following steps, \r
+ retrieve the encryptedKey, generate the muck from the boot password and generate an appropriate IV using\r
+ the muck,and using the key and IV decrypt the encryptedKey \r
+ @return decrypted key \r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ private byte[] decryptKey(String encryptedKey, int encodedKeyCharLength, byte[] bootPassword)\r
+ throws StandardException\r
+ {\r
+ byte[] secretKey = org.apache.derby.iapi.util.StringUtil.fromHexString(encryptedKey, 0, encodedKeyCharLength);\r
+ // In case of AES, care needs to be taken to allow for 16 bytes muck as well\r
+ // as to have the secretKey that needs encryption to be a aligned appropriately\r
+ // AES supports 16 bytes block size\r
+ int muckLength;\r
+ if(cryptoAlgorithmShort.equals(AES))\r
+ muckLength = AES_IV_LENGTH;\r
+ else\r
+ muckLength = secretKey.length; \r
+\r
+ byte[] muck = getMuckFromBootPassword(bootPassword, muckLength);\r
+\r
+\r
+ // decrypt the encryptedKey with the mucked up boot password to recover\r
+ // the secretKey\r
+ SecretKey key = generateKey(muck);\r
+ byte[] IV = generateIV(muck);\r
+\r
+\r
+ createNewCipher(DECRYPT, key, IV).\r
+ decrypt(secretKey, 0, secretKey.length, secretKey, 0);\r
+\r
+ return secretKey;\r
+ }\r
+\r
+ private byte[] getMuckFromBootPassword(byte[] bootPassword, int encodedKeyByteLength) {\r
+ int ulength = bootPassword.length;\r
+\r
+ byte[] muck = new byte[encodedKeyByteLength];\r
+ \r
+\r
+ int rotation = 0;\r
+ for (int i = 0; i < bootPassword.length; i++)\r
+ rotation += bootPassword[i];\r
+\r
+ for (int i = 0; i < encodedKeyByteLength; i++)\r
+ muck[i] = (byte)(bootPassword[(i+rotation)%ulength] ^\r
+ (bootPassword[i%ulength] << 4));\r
+\r
+ return muck;\r
+ }\r
+\r
+ /**\r
+ Generate a Key object using the input secretKey that can be used by\r
+ JCECipherProvider to encrypt or decrypt.\r
+\r
+ @exception StandardException Standard Derby Error Policy\r
+ */\r
+ private SecretKey generateKey(byte[] secretKey) throws StandardException\r
+ {\r
+ int length = secretKey.length;\r
+\r
+ if (length < CipherFactory.MIN_BOOTPASS_LENGTH)\r
+ throw StandardException.newException(SQLState.ILLEGAL_BP_LENGTH, new Integer(MIN_BOOTPASS_LENGTH));\r
+\r
+ try\r
+ {\r
+ if (cryptoAlgorithmShort.equals(DES))\r
+ { // single DES\r
+ if (DESKeySpec.isWeak(secretKey, 0))\r
+ {\r
+ // OK, it is weak, spice it up\r
+ byte[] spice = StringUtil.getAsciiBytes("louDScap");\r
+ for (int i = 0; i < 7; i++)\r
+ secretKey[i] = (byte)((spice[i] << 3) ^ secretKey[i]);\r
+ }\r
+ }\r
+ return new SecretKeySpec(secretKey, cryptoAlgorithmShort);\r
+ }\r
+ catch (InvalidKeyException ike)\r
+ {\r
+ throw StandardException.newException(SQLState.CRYPTO_EXCEPTION, ike);\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ Generate an IV using the input secretKey that can be used by\r
+ JCECipherProvider to encrypt or decrypt.\r
+ */\r
+ private byte[] generateIV(byte[] secretKey)\r
+ {\r
+\r
+ // do a little simple minded muddling to make the IV not\r
+ // strictly alphanumeric and the number of total possible keys a little\r
+ // bigger.\r
+ int IVlen = BLOCK_LENGTH;\r
+ \r
+ byte[] iv = null;\r
+ if(cryptoAlgorithmShort.equals(AES))\r
+ {\r
+ IVlen = AES_IV_LENGTH;\r
+ iv = new byte[IVlen];\r
+ iv[0] = (byte)(((secretKey[secretKey.length-1] << 2) | 0xF) ^ secretKey[0]);\r
+ for (int i = 1; i < BLOCK_LENGTH; i++)\r
+ iv[i] = (byte)(((secretKey[i-1] << (i%5)) | 0xF) ^ secretKey[i]);\r
+ \r
+ for(int i = BLOCK_LENGTH ; i < AES_IV_LENGTH ; i++)\r
+ {\r
+ iv[i]=iv[i-BLOCK_LENGTH];\r
+ }\r
+ \r
+ } \r
+ else\r
+ {\r
+ iv = new byte[BLOCK_LENGTH];\r
+ iv[0] = (byte)(((secretKey[secretKey.length-1] << 2) | 0xF) ^ secretKey[0]);\r
+ for (int i = 1; i < BLOCK_LENGTH; i++)\r
+ iv[i] = (byte)(((secretKey[i-1] << (i%5)) | 0xF) ^ secretKey[i]); \r
+ }\r
+\r
+ return iv;\r
+ }\r
+\r
+ private int digest(byte[] input)\r
+ {\r
+ messageDigest.reset();\r
+ byte[] digest = messageDigest.digest(input);\r
+ byte[] condenseDigest = new byte[2];\r
+\r
+ // no matter how long the digest is, condense it into an short.\r
+ for (int i = 0; i < digest.length; i++)\r
+ condenseDigest[i%2] ^= digest[i];\r
+\r
+ int retval = (condenseDigest[0] & 0xFF) | ((condenseDigest[1] << 8) & 0xFF00);\r
+\r
+ return retval;\r
+ }\r
+\r
+ public SecureRandom getSecureRandom() {\r
+ return new SecureRandom(mainIV);\r
+ }\r
+\r
+ public CipherProvider createNewCipher(int mode)\r
+ throws StandardException {\r
+ return createNewCipher(mode, mainSecretKey, mainIV);\r
+ }\r
+\r
+\r
+ private CipherProvider createNewCipher(int mode, SecretKey secretKey,\r
+ byte[] iv)\r
+ throws StandardException\r
+ {\r
+ return new JCECipherProvider(mode, secretKey, iv, cryptoAlgorithm, cryptoProviderShort);\r
+ }\r
+\r
+\r
+ /*\r
+ * Initilize the new instance of this class. \r
+ */\r
+ public void init(boolean create, Properties properties, boolean newAttrs)\r
+ throws StandardException\r
+ {\r
+\r
+ boolean provider_or_algo_specified = false;\r
+ boolean storeProperties = create;\r
+ persistentProperties = new Properties();\r
+\r
+ // get the external key specified by the user to \r
+ // encrypt the database. If user is reencrypting the\r
+ // database with a new encryption key, read the value of \r
+ // the new encryption key. \r
+ String externalKey = properties.getProperty((newAttrs ? \r
+ Attribute.NEW_CRYPTO_EXTERNAL_KEY:\r
+ Attribute.CRYPTO_EXTERNAL_KEY));\r
+ if (externalKey != null) {\r
+ storeProperties = false;\r
+ }\r
+\r
+ cryptoProvider = properties.getProperty(Attribute.CRYPTO_PROVIDER);\r
+\r
+ if (cryptoProvider != null)\r
+ {\r
+ provider_or_algo_specified = true;\r
+\r
+ // explictly putting the properties back into the properties\r
+ // saves then in service.properties at create time.\r
+ // if (storeProperties)\r
+ // properties.put(Attribute.CRYPTO_PROVIDER, cryptoProvider);\r
+\r
+ int dotPos = cryptoProvider.lastIndexOf('.');\r
+ if (dotPos == -1)\r
+ cryptoProviderShort = cryptoProvider;\r
+ else\r
+ cryptoProviderShort = cryptoProvider.substring(dotPos+1);\r
+\r
+ }\r
+\r
+ cryptoAlgorithm = properties.getProperty(Attribute.CRYPTO_ALGORITHM);\r
+ if (cryptoAlgorithm == null)\r
+ cryptoAlgorithm = DEFAULT_ALGORITHM;\r
+ else {\r
+ provider_or_algo_specified = true;\r
+\r
+ }\r
+\r
+ // explictly putting the properties back into the properties\r
+ // saves then in service.properties at create time.\r
+ if (storeProperties)\r
+ persistentProperties.put(Attribute.CRYPTO_ALGORITHM, \r
+ cryptoAlgorithm);\r
+\r
+ int firstSlashPos = cryptoAlgorithm.indexOf('/');\r
+ int lastSlashPos = cryptoAlgorithm.lastIndexOf('/');\r
+ if (firstSlashPos < 0 || lastSlashPos < 0 || firstSlashPos == lastSlashPos)\r
+ throw StandardException.newException(SQLState.ENCRYPTION_BAD_ALG_FORMAT, cryptoAlgorithm);\r
+\r
+ cryptoAlgorithmShort = cryptoAlgorithm.substring(0,firstSlashPos);\r
+\r
+ if (provider_or_algo_specified)\r
+ {\r
+ // Track 3715 - disable use of provider/aglo specification if\r
+ // jce environment is not 1.2.1. The ExemptionMechanism class\r
+ // exists in jce1.2.1 and not in jce1.2, so try and load the\r
+ // class and if you can't find it don't allow the encryption.\r
+ // This is a requirement from the government to give Cloudscape\r
+ // export clearance for 3.6. Note that the check is not needed\r
+ // if no provider/algo is specified, in that case we default to\r
+ // a DES weak encryption algorithm which also is allowed for\r
+ // export (this is how 3.5 got it's clearance).\r
+ try\r
+ {\r
+ Class c = Class.forName("javax.crypto.ExemptionMechanism");\r
+ //Added by Jeff Huang\r
+ //TODO: FIXIT\r
+ }\r
+ catch (Throwable t)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.ENCRYPTION_BAD_JCE);\r
+ }\r
+ }\r
+\r
+ // If connecting to an existing database and Attribute.CRYPTO_KEY_LENGTH is set\r
+ // then obtain the encoded key length values without padding bytes and retrieve\r
+ // the keylength in bits if boot password mechanism is used \r
+ // note: Attribute.CRYPTO_KEY_LENGTH is set during creation time to a supported\r
+ // key length in the connection url. Internally , two values are stored in this property\r
+ // if encryptionKey is used, this property will have only the encoded key length\r
+ // if boot password mechanism is used, this property will have the following \r
+ // keylengthBits-EncodedKeyLength \r
+ \r
+ if(!create)\r
+ {\r
+ // if available, parse the keylengths stored in Attribute.CRYPTO_KEY_LENGTH \r
+ if(properties.getProperty(Attribute.CRYPTO_KEY_LENGTH) != null)\r
+ {\r
+ String keyLengths = properties.getProperty(Attribute.CRYPTO_KEY_LENGTH);\r
+ int pos = keyLengths.lastIndexOf('-');\r
+ encodedKeyLength = Integer.parseInt(keyLengths.substring(pos+1)); \r
+ if(pos != -1)\r
+ keyLengthBits = Integer.parseInt(keyLengths.substring(0,pos));\r
+ }\r
+ }\r
+ \r
+\r
+ // case 1 - if 'encryptionKey' is not set and 'encryptionKeyLength' is set, then use\r
+ // the 'encryptionKeyLength' property value as the keyLength in bits.\r
+ // case 2 - 'encryptionKey' property is not set and 'encryptionKeyLength' is not set, then\r
+ // use the defaults keylength: 56bits for DES, 168 for DESede and 128 for any other encryption\r
+ // algorithm\r
+\r
+ if (externalKey == null && create) {\r
+ if(properties.getProperty(Attribute.CRYPTO_KEY_LENGTH) != null)\r
+ {\r
+ keyLengthBits = Integer.parseInt(properties.getProperty(Attribute.CRYPTO_KEY_LENGTH));\r
+ }\r
+ else if (cryptoAlgorithmShort.equals(DES)) {\r
+ keyLengthBits = 56;\r
+ } else if (cryptoAlgorithmShort.equals(DESede) || cryptoAlgorithmShort.equals(TripleDES)) {\r
+ keyLengthBits = 168;\r
+\r
+ } else {\r
+ keyLengthBits = 128;\r
+ }\r
+ }\r
+\r
+ // check the feedback mode\r
+ String feedbackMode = cryptoAlgorithm.substring(firstSlashPos+1,lastSlashPos);\r
+\r
+ if (!feedbackMode.equals("CBC") && !feedbackMode.equals("CFB") &&\r
+ !feedbackMode.equals("ECB") && !feedbackMode.equals("OFB"))\r
+ throw StandardException.newException(SQLState.ENCRYPTION_BAD_FEEDBACKMODE, feedbackMode);\r
+\r
+ // check the NoPadding mode is used\r
+ String padding = cryptoAlgorithm.substring(lastSlashPos+1,cryptoAlgorithm.length());\r
+ if (!padding.equals("NoPadding"))\r
+ throw StandardException.newException(SQLState.ENCRYPTION_BAD_PADDING, padding);\r
+\r
+ Throwable t;\r
+ try\r
+ {\r
+ if (cryptoProvider != null) {\r
+ // provider package should be set by property\r
+ if (Security.getProvider(cryptoProviderShort) == null)\r
+ {\r
+ action = 1;\r
+ // add provider through privileged block.\r
+ java.security.AccessController.doPrivileged(this);\r
+ }\r
+ }\r
+\r
+ // need this to check the boot password\r
+ messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST);\r
+\r
+ byte[] generatedKey;\r
+ if (externalKey != null) {\r
+\r
+ // incorrect to specify external key and boot password\r
+ if (properties.getProperty((newAttrs ? \r
+ Attribute.NEW_BOOT_PASSWORD :\r
+ Attribute.BOOT_PASSWORD)) != null)\r
+ throw StandardException.newException(SQLState.SERVICE_WRONG_BOOT_PASSWORD);\r
+\r
+ generatedKey = \r
+ org.apache.derby.iapi.util.StringUtil.fromHexString(externalKey, \r
+ 0, \r
+ externalKey.length());\r
+ if (generatedKey == null) {\r
+ throw StandardException.newException(\r
+ // If length is even, we assume invalid character(s),\r
+ // based on how 'fromHexString' behaves.\r
+ externalKey.length() % 2 == 0 \r
+ ? SQLState.ENCRYPTION_ILLEGAL_EXKEY_CHARS\r
+ : SQLState.ENCRYPTION_INVALID_EXKEY_LENGTH);\r
+ }\r
+\r
+ } else {\r
+\r
+ generatedKey = handleBootPassword(create, properties, newAttrs);\r
+ if(create || newAttrs)\r
+ persistentProperties.put(Attribute.CRYPTO_KEY_LENGTH,\r
+ keyLengthBits+"-"+generatedKey.length);\r
+ }\r
+\r
+ // Make a key and IV object out of the generated key\r
+ mainSecretKey = generateKey(generatedKey);\r
+ mainIV = generateIV(generatedKey);\r
+\r
+ if (create)\r
+ {\r
+ persistentProperties.put(Attribute.DATA_ENCRYPTION, "true");\r
+\r
+ // Set two new properties to allow for future changes to the log and data encryption\r
+ // schemes. This property is introduced in version 10 , value starts at 1.\r
+ persistentProperties.put(RawStoreFactory.DATA_ENCRYPT_ALGORITHM_VERSION,\r
+ String.valueOf(1));\r
+ persistentProperties.put(RawStoreFactory.LOG_ENCRYPT_ALGORITHM_VERSION,\r
+ String.valueOf(1));\r
+ }\r
+\r
+ return;\r
+ }\r
+ catch (java.security.PrivilegedActionException pae)\r
+ {\r
+ t = pae.getException();\r
+ }\r
+ catch (NoSuchAlgorithmException nsae)\r
+ {\r
+ t = nsae;\r
+ }\r
+ catch (SecurityException se)\r
+ {\r
+ t = se;\r
+ } catch (LinkageError le) {\r
+ t = le;\r
+ } catch (ClassCastException cce) {\r
+ t = cce;\r
+ }\r
+\r
+ throw StandardException.newException(SQLState.MISSING_ENCRYPTION_PROVIDER, t);\r
+ }\r
+\r
+\r
+ private byte[] handleBootPassword(boolean create, \r
+ Properties properties, \r
+ boolean newPasswd)\r
+ throws StandardException {\r
+\r
+\r
+ // get the key specifed by the user. If user is reencrypting the\r
+ // database; read the value of the new password. \r
+ String inputKey = properties.getProperty((newPasswd ? \r
+ Attribute.NEW_BOOT_PASSWORD : \r
+ Attribute.BOOT_PASSWORD));\r
+ if (inputKey == null)\r
+ {\r
+ throw StandardException.newException(SQLState.SERVICE_WRONG_BOOT_PASSWORD);\r
+ }\r
+\r
+ byte[] bootPassword = StringUtil.getAsciiBytes(inputKey);\r
+\r
+ if (bootPassword.length < CipherFactory.MIN_BOOTPASS_LENGTH)\r
+ {\r
+ String messageId = create ? SQLState.SERVICE_BOOT_PASSWORD_TOO_SHORT :\r
+ SQLState.SERVICE_WRONG_BOOT_PASSWORD;\r
+\r
+ throw StandardException.newException(messageId);\r
+ }\r
+\r
+ // Each database has its own unique encryption key that is\r
+ // not known even to the user. However, this key is masked\r
+ // with the user input key and stored in the\r
+ // services.properties file so that, with the user key, the\r
+ // encryption key can easily be recovered.\r
+ // To change the user encryption key to a database, simply\r
+ // recover the unique real encryption key and masked it\r
+ // with the new user key.\r
+\r
+ byte[] generatedKey;\r
+\r
+ if (create || newPasswd)\r
+ {\r
+ //\r
+ generatedKey = generateUniqueBytes();\r
+\r
+ persistentProperties.put(RawStoreFactory.ENCRYPTED_KEY, \r
+ saveSecretKey(generatedKey, bootPassword));\r
+\r
+ }\r
+ else\r
+ {\r
+ generatedKey = getDatabaseSecretKey(properties, bootPassword, SQLState.SERVICE_WRONG_BOOT_PASSWORD);\r
+ }\r
+\r
+ return generatedKey;\r
+ }\r
+\r
+ /* \r
+ * put all the encyrpion cipger related properties that has to \r
+ * be made peristent into the database service properties list.\r
+ * @param properties properties object that is used to store \r
+ * cipher properties persistently. \r
+ */\r
+ public void saveProperties(Properties properties) \r
+ {\r
+ // put the cipher properties to be persistent into the \r
+ // system perisistent properties. \r
+ for (Enumeration e = persistentProperties.keys(); \r
+ e.hasMoreElements(); ) \r
+ {\r
+ String key = (String) e.nextElement();\r
+ properties.put(key, persistentProperties.get(key));\r
+ }\r
+\r
+ // clear the cipher properties to be persistent. \r
+ persistentProperties = null;\r
+ }\r
+\r
+\r
+ /**\r
+ get the secretkey used for encryption and decryption when boot password mechanism is used for encryption\r
+ Steps include \r
+ retrieve the stored key, decrypt the stored key and verify if the correct boot password was passed \r
+ There is a possibility that the decrypted key includes the original key and padded bytes in order to have\r
+ been block size aligned during encryption phase. Hence extract the original key \r
+ \r
+ @param properties properties to retrieve the encrypted key \r
+ @param bootPassword boot password used to connect to the encrypted database\r
+ @param errorState errorstate to account for any errors during retrieval /creation of the secretKey\r
+ @return the original unencrypted key bytes to use for encryption and decrytion \r
+ \r
+ */\r
+ private byte[] getDatabaseSecretKey(Properties properties, byte[] bootPassword, String errorState) throws StandardException {\r
+\r
+ // recover the generated secret encryption key from the\r
+ // services.properties file and the user key.\r
+ String keyString = properties.getProperty(RawStoreFactory.ENCRYPTED_KEY);\r
+ if (keyString == null)\r
+ throw StandardException.newException(errorState);\r
+\r
+ int encodedKeyCharLength = keyString.indexOf('-');\r
+\r
+ if (encodedKeyCharLength == -1) // bad form\r
+ throw StandardException.newException(errorState);\r
+\r
+ int verifyKey = Integer.parseInt(keyString.substring(encodedKeyCharLength+1));\r
+ byte[] generatedKey = decryptKey(keyString, encodedKeyCharLength, bootPassword);\r
+\r
+ int checkKey = digest(generatedKey);\r
+\r
+ if (checkKey != verifyKey)\r
+ throw StandardException.newException(errorState);\r
+\r
+ // if encodedKeyLength is not defined, then either it is an old version with no support for different\r
+ // key sizes and padding except for defaults\r
+ byte[] result; \r
+ if(encodedKeyLength != 0)\r
+ {\r
+ result = new byte[encodedKeyLength];\r
+\r
+ // extract the generated key without the padding bytes\r
+ System.arraycopy(generatedKey,0,result,0,encodedKeyLength);\r
+ return result;\r
+ }\r
+\r
+ return generatedKey;\r
+ }\r
+\r
+ private String saveSecretKey(byte[] secretKey, byte[] bootPassword) throws StandardException {\r
+ String encryptedKey = encryptKey(secretKey, bootPassword);\r
+\r
+ // make a verification key out of the message digest of\r
+ // the generated key\r
+ int verifyKey = digest(secretKey);\r
+\r
+ return encryptedKey.concat("-" + verifyKey);\r
+\r
+ }\r
+\r
+ public String changeBootPassword(String changeString, Properties properties, CipherProvider verify)\r
+ throws StandardException {\r
+\r
+ // the new bootPassword is expected to be of the form\r
+ // oldkey , newkey.\r
+ int seperator = changeString.indexOf(',');\r
+ if (seperator == -1)\r
+ throw StandardException.newException(SQLState.WRONG_PASSWORD_CHANGE_FORMAT);\r
+\r
+ String oldBP = changeString.substring(0, seperator).trim();\r
+ byte[] oldBPAscii = StringUtil.getAsciiBytes(oldBP);\r
+ if (oldBPAscii == null || oldBPAscii.length < CipherFactory.MIN_BOOTPASS_LENGTH)\r
+ throw StandardException.newException(SQLState.WRONG_BOOT_PASSWORD);;\r
+\r
+ String newBP = changeString.substring(seperator+1).trim();\r
+ byte[] newBPAscii = StringUtil.getAsciiBytes(newBP);\r
+ if (newBPAscii == null || newBPAscii.length < CipherFactory.MIN_BOOTPASS_LENGTH)\r
+ throw StandardException.newException(SQLState.ILLEGAL_BP_LENGTH,\r
+ new Integer(CipherFactory.MIN_BOOTPASS_LENGTH));\r
+\r
+ // verify old key\r
+\r
+ byte[] generatedKey = getDatabaseSecretKey(properties, oldBPAscii, SQLState.WRONG_BOOT_PASSWORD);\r
+\r
+ // make sure the oldKey is correct\r
+ byte[] IV = generateIV(generatedKey);\r
+\r
+ if (!((JCECipherProvider) verify).verifyIV(IV))\r
+ throw StandardException.newException(SQLState.WRONG_BOOT_PASSWORD);\r
+\r
+\r
+ // Make the new key. The generated key is unchanged, only the\r
+ // encrypted key is changed.\r
+ String newkey = saveSecretKey(generatedKey, newBPAscii);\r
+ \r
+ properties.put(Attribute.CRYPTO_KEY_LENGTH,keyLengthBits+"-"+encodedKeyLength);\r
+ \r
+\r
+ return saveSecretKey(generatedKey, newBPAscii);\r
+ }\r
+\r
+ /**\r
+ perform actions with privileges enabled.\r
+ */\r
+ public final Object run() throws StandardException, InstantiationException, IllegalAccessException {\r
+\r
+ try {\r
+\r
+ switch(action)\r
+ {\r
+ case 1:\r
+ Security.addProvider(\r
+ (Provider)(Class.forName(cryptoProvider).newInstance()));\r
+ //Added by Jeff Huang\r
+ //TODO: FIXIT\r
+ break;\r
+ case 2:\r
+ // SECURITY PERMISSION - MP1 and/or OP4\r
+ // depends on the value of activePerms\r
+ return activeFile.getRandomAccessFile(activePerms);\r
+ case 3:\r
+ return activeFile.getInputStream();\r
+\r
+ }\r
+\r
+ } catch (ClassNotFoundException cnfe) {\r
+ throw StandardException.newException(SQLState.ENCRYPTION_NO_PROVIDER_CLASS,cryptoProvider);\r
+ }\r
+ catch(FileNotFoundException fnfe) {\r
+ throw StandardException.newException(SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,cryptoProvider);\r
+ }\r
+ return null;\r
+ }\r
+\r
+\r
+\r
+ /**\r
+ The database can be encrypted with an encryption key given in connection url.\r
+ For security reasons, this key is not made persistent in the database.\r
+\r
+ But it is necessary to verify the encryption key when booting the database if it is similar\r
+ to the one used when creating the database\r
+ This needs to happen before we access the data/logs to avoid the risk of corrupting the \r
+ database because of a wrong encryption key.\r
+\r
+ This method performs the steps necessary to verify the encryption key if an external\r
+ encryption key is given.\r
+\r
+ At database creation, 4k of random data is generated using SecureRandom and MD5 is used\r
+ to compute the checksum for the random data thus generated. This 4k page of random data\r
+ is then encrypted using the encryption key. The checksum of unencrypted data and\r
+ encrypted data is made persistent in the database in file by name given by\r
+ Attribute.CRYPTO_EXTERNAL_KEY_VERIFYFILE (verifyKey.dat). This file exists directly under the\r
+ database root directory.\r
+\r
+ When trying to boot an existing encrypted database, the given encryption key is used to decrypt\r
+ the data in the verifyKey.dat and the checksum is calculated and compared against the original\r
+ stored checksum. If these checksums dont match an exception is thrown.\r
+\r
+ Please note, this process of verifying the key does not provide any added security but only is \r
+ intended to allow to fail gracefully if a wrong encryption key is used\r
+\r
+ StandardException is thrown if there are any problems during the process of verification\r
+ of the encryption key or if there is any mismatch of the encryption key.\r
+\r
+ */\r
+\r
+ public void verifyKey(boolean create, StorageFactory sf, Properties properties)\r
+ throws StandardException\r
+ {\r
+\r
+ if(properties.getProperty(Attribute.CRYPTO_EXTERNAL_KEY) == null)\r
+ return;\r
+\r
+ // if firstTime ( ie during creation of database, initial key used )\r
+ // In order to allow for verifying the external key for future database boot,\r
+ // generate random 4k of data and store the encrypted random data and the checksum\r
+ // using MD5 of the unencrypted data. That way, on next database boot a check is performed\r
+ // to verify if the key is the same as used when the database was created\r
+\r
+ InputStream verifyKeyInputStream = null;\r
+ StorageRandomAccessFile verifyKeyFile = null;\r
+ byte[] data = new byte[VERIFYKEY_DATALEN];\r
+ try\r
+ {\r
+ if(create)\r
+ {\r
+ getSecureRandom().nextBytes(data);\r
+ // get the checksum\r
+ byte[] checksum = getMD5Checksum(data);\r
+\r
+ CipherProvider tmpCipherProvider = createNewCipher(ENCRYPT,mainSecretKey,mainIV);\r
+ tmpCipherProvider.encrypt(data, 0, data.length, data, 0);\r
+ // openFileForWrite\r
+ verifyKeyFile = privAccessFile(sf,Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE,"rw");\r
+ // write the checksum length as int, and then the checksum and then the encrypted data\r
+ verifyKeyFile.writeInt(checksum.length);\r
+ verifyKeyFile.write(checksum);\r
+ verifyKeyFile.write(data);\r
+ verifyKeyFile.sync(true);\r
+ }\r
+ else\r
+ {\r
+ // Read from verifyKey.dat as an InputStream. This allows for \r
+ // reading the information from verifyKey.dat successfully even when using the jar\r
+ // subprotocol to boot derby. (DERBY-1373) \r
+ verifyKeyInputStream = privAccessGetInputStream(sf,Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE);\r
+ DataInputStream dis = new DataInputStream(verifyKeyInputStream);\r
+ // then read the checksum length \r
+ int checksumLen = dis.readInt();\r
+\r
+ byte[] originalChecksum = new byte[checksumLen];\r
+ dis.readFully(originalChecksum);\r
+\r
+ dis.readFully(data);\r
+\r
+ // decrypt data with key\r
+ CipherProvider tmpCipherProvider = createNewCipher(DECRYPT,mainSecretKey,mainIV);\r
+ tmpCipherProvider.decrypt(data, 0, data.length, data, 0);\r
+\r
+ byte[] verifyChecksum = getMD5Checksum(data);\r
+\r
+ if(!MessageDigest.isEqual(originalChecksum,verifyChecksum))\r
+ {\r
+ throw StandardException.newException(SQLState.ENCRYPTION_BAD_EXTERNAL_KEY);\r
+ }\r
+\r
+ }\r
+ }\r
+ catch(IOException ioe)\r
+ {\r
+ throw StandardException.newException(SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,ioe);\r
+ }\r
+ finally\r
+ {\r
+ try\r
+ {\r
+ if(verifyKeyFile != null)\r
+ verifyKeyFile.close();\r
+ if (verifyKeyInputStream != null )\r
+ verifyKeyInputStream.close();\r
+ }\r
+ catch(IOException ioee)\r
+ {\r
+ throw StandardException.newException(SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,ioee);\r
+ }\r
+ }\r
+ return ;\r
+ }\r
+\r
+\r
+ /**\r
+ Use MD5 MessageDigest algorithm to generate checksum\r
+ @param data data to be used to compute the hash value\r
+ @return returns the hash value computed using the data\r
+\r
+ */\r
+ private byte[] getMD5Checksum(byte[] data)\r
+ throws StandardException\r
+ {\r
+ try\r
+ {\r
+ // get the checksum\r
+ MessageDigest md5 = MessageDigest.getInstance("MD5");\r
+ return md5.digest(data);\r
+ }\r
+ catch(NoSuchAlgorithmException nsae)\r
+ {\r
+ throw StandardException.newException(SQLState.ENCRYPTION_BAD_ALG_FORMAT,MESSAGE_DIGEST);\r
+ }\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ access a file for either read/write\r
+ @param storageFactory factory used for io access\r
+ @param fileName name of the file to create and open for write\r
+ The file will be created directly under the database root directory\r
+ @param filePerms file permissions, if "rw" open file with read and write permissions\r
+ if "r" , open file with read permissions\r
+ @return StorageRandomAccessFile returns file with fileName for writing\r
+ @exception IOException Any exception during accessing the file for read/write\r
+ */\r
+ private StorageRandomAccessFile privAccessFile(StorageFactory storageFactory,String fileName,String filePerms)\r
+ throws java.io.IOException\r
+ {\r
+ StorageFile verifyKeyFile = storageFactory.newStorageFile("",fileName);\r
+ activeFile = verifyKeyFile;\r
+ this.action = 2;\r
+ activePerms = filePerms;\r
+ try\r
+ {\r
+ return (StorageRandomAccessFile)java.security.AccessController.doPrivileged(this);\r
+ }\r
+ catch( java.security.PrivilegedActionException pae)\r
+ {\r
+ throw (java.io.IOException)pae.getException();\r
+ }\r
+ }\r
+\r
+ /**\r
+ access a InputStream for a given file for reading.\r
+ @param storageFactory factory used for io access\r
+ @param fileName name of the file to open as a stream for reading\r
+ @return InputStream returns the stream for the file with fileName for reading\r
+ @exception IOException Any exception during accessing the file for read\r
+ */\r
+ private InputStream privAccessGetInputStream(StorageFactory storageFactory,String fileName)\r
+ throws StandardException\r
+ {\r
+ StorageFile verifyKeyFile = storageFactory.newStorageFile("",fileName);\r
+ activeFile = verifyKeyFile;\r
+ this.action = 3;\r
+ try\r
+ {\r
+ return (InputStream)java.security.AccessController.doPrivileged(this);\r
+ }\r
+ catch( java.security.PrivilegedActionException pae)\r
+ {\r
+ throw (StandardException)pae.getException();\r
+ }\r
+ }\r
+\r
+}\r