Adding JMCR-Stable version
[Benchmarks_CSolver.git] / JMCR-Stable / real-world application / derby-10.3.2.1 / java / engine / org / apache / derby / impl / services / jce / JCECipherFactory.java
diff --git a/JMCR-Stable/real-world application/derby-10.3.2.1/java/engine/org/apache/derby/impl/services/jce/JCECipherFactory.java b/JMCR-Stable/real-world application/derby-10.3.2.1/java/engine/org/apache/derby/impl/services/jce/JCECipherFactory.java
new file mode 100644 (file)
index 0000000..619a684
--- /dev/null
@@ -0,0 +1,1028 @@
+/*\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