EMMA Coverage Report (generated Wed Jun 28 22:15:27 PDT 2006)
[all classes][org.apache.derby.impl.services.jce]

COVERAGE SUMMARY FOR SOURCE FILE [JCECipherFactory.java]

nameclass, %method, %block, %line, %
JCECipherFactory.java100% (1/1)100% (23/23)88%  (1132/1280)84%  (239.1/284)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class JCECipherFactory100% (1/1)100% (23/23)88%  (1132/1280)84%  (239.1/284)
JCECipherFactory (): void 100% (1/1)100% (3/3)100% (1/1)
boot (boolean, Properties): void 100% (1/1)91%  (340/374)83%  (76.3/92)
changeBootPassword (String, Properties, CipherProvider): String 100% (1/1)94%  (89/95)89%  (16/18)
createNewCipher (int): CipherProvider 100% (1/1)100% (8/8)100% (1/1)
createNewCipher (int, SecretKey, byte []): CipherProvider 100% (1/1)100% (11/11)100% (1/1)
decryptKey (String, int, byte []): byte [] 100% (1/1)100% (44/44)100% (9/9)
digest (byte []): int 100% (1/1)100% (47/47)100% (7/7)
encryptKey (byte [], byte []): String 100% (1/1)100% (58/58)100% (12/12)
generateIV (byte []): byte [] 100% (1/1)100% (123/123)100% (15/15)
generateKey (byte []): SecretKey 100% (1/1)39%  (22/57)45%  (5/11)
generateUniqueBytes (): byte [] 100% (1/1)81%  (38/47)77%  (10/13)
getDatabaseSecretKey (Properties, byte [], String): byte [] 100% (1/1)87%  (52/60)81%  (13/16)
getMD5Checksum (byte []): byte [] 100% (1/1)58%  (7/12)50%  (2/4)
getMuckFromBootPassword (byte [], int): byte [] 100% (1/1)100% (50/50)100% (8/8)
getSecureRandom (): SecureRandom 100% (1/1)100% (6/6)100% (1/1)
handleBootPassword (boolean, Properties): byte [] 100% (1/1)81%  (38/47)83%  (10/12)
padKey (byte [], int): byte [] 100% (1/1)31%  (9/29)50%  (3/6)
privAccessFile (StorageFactory, String, String): StorageRandomAccessFile 100% (1/1)78%  (18/23)71%  (5/7)
providerErrorName (String): String 100% (1/1)83%  (5/6)83%  (0.8/1)
run (): Object 100% (1/1)81%  (26/32)80%  (8/10)
saveSecretKey (byte [], byte []): String 100% (1/1)100% (20/20)100% (3/3)
stop (): void 100% (1/1)100% (1/1)100% (1/1)
verifyKey (boolean, StorageFactory, Properties): void 100% (1/1)92%  (117/127)89%  (31/35)

1/*
2 
3   Derby - Class org.apache.derby.impl.services.jce.JCECipherFactory
4 
5   Copyright 2000, 2004 The Apache Software Foundation or its licensors, as applicable.
6 
7   Licensed under the Apache License, Version 2.0 (the "License");
8   you may not use this file except in compliance with the License.
9   You may obtain a copy of the License at
10 
11      http://www.apache.org/licenses/LICENSE-2.0
12 
13   Unless required by applicable law or agreed to in writing, software
14   distributed under the License is distributed on an "AS IS" BASIS,
15   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   See the License for the specific language governing permissions and
17   limitations under the License.
18 
19 */
20 
21package org.apache.derby.impl.services.jce;
22 
23import org.apache.derby.iapi.services.crypto.CipherFactory;
24import org.apache.derby.iapi.services.crypto.CipherProvider;
25 
26import org.apache.derby.iapi.services.monitor.Monitor;
27import org.apache.derby.iapi.services.sanity.SanityManager;
28 
29import org.apache.derby.iapi.error.StandardException;
30 
31import org.apache.derby.iapi.services.info.JVMInfo;
32import org.apache.derby.iapi.util.StringUtil;
33 
34import org.apache.derby.iapi.reference.SQLState;
35import org.apache.derby.iapi.reference.Attribute;
36import org.apache.derby.iapi.util.StringUtil;
37 
38import java.util.Properties;
39import java.util.Enumeration;
40import java.security.Key;
41import java.security.Provider;
42import java.security.SecureRandom;
43import java.security.Security;
44import java.security.InvalidKeyException;
45import java.security.NoSuchAlgorithmException;
46import java.security.MessageDigest;
47import java.security.spec.KeySpec;
48import java.security.spec.InvalidKeySpecException;
49import java.io.FileNotFoundException;
50import java.io.IOException;
51 
52import javax.crypto.KeyGenerator;
53import javax.crypto.SecretKey;
54import javax.crypto.SecretKeyFactory;
55import javax.crypto.spec.DESKeySpec;
56import javax.crypto.spec.SecretKeySpec;
57import org.apache.derby.iapi.store.raw.RawStoreFactory;
58 
59import org.apache.derby.io.StorageFactory;
60import org.apache.derby.io.WritableStorageFactory;
61import org.apache.derby.io.StorageFile;
62import org.apache.derby.io.StorageRandomAccessFile;
63/**
64        This CipherFactory creates new JCECipherProvider.
65 
66        @see CipherFactory
67 */
68public final class JCECipherFactory implements CipherFactory, java.security.PrivilegedExceptionAction
69{
70    private final static String MESSAGE_DIGEST = "MD5";
71 
72        private final static String DEFAULT_PROVIDER = "com.sun.crypto.provider.SunJCE";
73        private final static String DEFAULT_ALGORITHM = "DES/CBC/NoPadding";
74        private final static String DES = "DES";
75        private final static String DESede = "DESede";
76    private final static String TripleDES = "TripleDES";
77    private final static String AES = "AES";
78 
79    // minimum boot password length in bytes
80    private final static int BLOCK_LENGTH = 8;
81 
82    /**
83        AES encryption takes in an default Initialization vector length (IV) length of 16 bytes
84        This is needed to generate an IV to use for encryption and decryption process 
85        @see CipherProvider
86     */
87    private final static int AES_IV_LENGTH = 16;
88 
89    // key length in bytes
90        private int keyLengthBits;
91    private int encodedKeyLength;
92    private String cryptoAlgorithm;
93    private String cryptoAlgorithmShort;
94    private String cryptoProvider;
95    private String cryptoProviderShort;
96        private MessageDigest messageDigest;
97 
98        private SecretKey mainSecretKey;
99        private byte[] mainIV;
100 
101    // properties that needs to be stored in the
102    // in the service.properties file.
103    private Properties persistentProperties;
104 
105 
106        /**
107            Amount of data that is used for verification of external encryption key
108            This does not include the MD5 checksum bytes
109         */
110        private final static int VERIFYKEY_DATALEN = 4096;
111        private StorageFile activeFile;
112        private int action;
113        private String activePerms;
114 
115 
116    /*
117     * Constructor of JCECipherFactory, initializes the new instances.
118     *
119     * @param create    true, if the database is getting configured 
120     *                  for encryption.
121     * @param props            encryption properties/attributes to use
122     *                  for creating the cipher factory.
123     * @param newAttrs  true, if cipher factory has to be created using 
124     *                  should using the new attributes specified by the user.  
125     *                  For example to reencrypt the database with 
126     *                  a new password.
127     */
128    public JCECipherFactory(boolean create, 
129                            Properties props,
130                            boolean newAttributes) 
131        throws StandardException
132    {
133        init(create, props, newAttributes);
134    }
135    
136 
137 
138        static String providerErrorName(String cps) {
139 
140                return cps == null ? "default" : cps;
141        }
142 
143 
144        private byte[] generateUniqueBytes() throws StandardException
145        {
146                try {
147 
148                        String provider = cryptoProviderShort;
149 
150                        KeyGenerator keyGen;
151                        if (provider == null)
152                        {
153                                keyGen = KeyGenerator.getInstance(cryptoAlgorithmShort);
154                        }
155                        else
156                        {
157                                if( provider.equals("BouncyCastleProvider"))
158                                        provider = "BC";
159                                keyGen = KeyGenerator.getInstance(cryptoAlgorithmShort, provider);
160                        }
161 
162                        keyGen.init(keyLengthBits);
163 
164                        SecretKey key = keyGen.generateKey();
165 
166                        return key.getEncoded();
167 
168                } catch (java.security.NoSuchAlgorithmException nsae) {
169                    throw StandardException.newException(SQLState.ENCRYPTION_NOSUCH_ALGORITHM, cryptoAlgorithm,
170                                JCECipherFactory.providerErrorName(cryptoProviderShort));
171                } catch (java.security.NoSuchProviderException nspe) {
172                        throw StandardException.newException(SQLState.ENCRYPTION_BAD_PROVIDER,
173                                JCECipherFactory.providerErrorName(cryptoProviderShort));
174                }
175        }
176 
177        /**
178                Encrypt the secretKey with the boot password.
179                This includes the following steps, 
180                getting muck from the boot password and then using this to generate a key,
181                generating an appropriate IV using the muck
182                using the key and IV thus generated to create the appropriate cipher provider
183                and encrypting the secretKey 
184                @return hexadecimal string of the encrypted secretKey
185 
186                @exception StandardException Standard Cloudscape error policy
187         */
188        private String encryptKey(byte[] secretKey, byte[] bootPassword)
189                 throws StandardException
190        {
191                // In case of AES, care needs to be taken to allow for 16 bytes muck as well
192                // as to have the secretKey that needs encryption to be a aligned appropriately
193                // AES supports 16 bytes block size
194 
195                int muckLength = secretKey.length;
196                if(cryptoAlgorithmShort.equals(AES))
197                        muckLength = AES_IV_LENGTH;                
198 
199                byte[] muck = getMuckFromBootPassword(bootPassword, muckLength);
200                SecretKey key = generateKey(muck);
201                byte[] IV = generateIV(muck);
202                CipherProvider tmpCipherProvider = createNewCipher(ENCRYPT,key,IV);
203                
204                // store the actual secretKey.length before any possible padding  
205                encodedKeyLength = secretKey.length;
206 
207                // for the secretKey to be encrypted, first ensure that it is aligned to the block size of the 
208                // encryption algorithm by padding bytes appropriately if needed
209                secretKey = padKey(secretKey,tmpCipherProvider.getEncryptionBlockSize());
210 
211                byte[] result = new byte[secretKey.length];
212 
213                // encrypt the secretKey using the key generated of muck from  boot password and the generated IV  
214                tmpCipherProvider.encrypt(secretKey, 0, secretKey.length, result, 0);
215 
216                return org.apache.derby.iapi.util.StringUtil.toHexString(result, 0, result.length);
217 
218        }
219        
220        /**
221            For block ciphers, and  algorithms using the NoPadding scheme, the data that has 
222            to be encrypted needs to be a multiple of the expected block size for the cipher 
223            Pad the key with appropriate padding to make it blockSize align
224            @param     secretKey        the data that needs blocksize alignment
225            @param     blockSizeAlign   secretKey needs to be blocksize aligned                
226            @return    a byte array with the contents of secretKey along with padded bytes in the end
227                       to make it blockSize aligned
228         */
229        private byte[] padKey(byte[] secretKey,int blockSizeAlign)
230        {
231            byte [] result = secretKey;
232            if(secretKey.length % blockSizeAlign != 0 )
233            {
234                int encryptedLength = secretKey.length + blockSizeAlign - (secretKey.length % blockSizeAlign);
235                result = new byte[encryptedLength];
236                System.arraycopy(secretKey,0,result,0,secretKey.length);
237            }
238            return result;
239        }
240 
241        /**
242            Decrypt the secretKey with the user key .
243            This includes the following steps, 
244            retrieve the encryptedKey, generate the muck from the boot password and generate an appropriate IV using
245            the muck,and using the key and IV decrypt the encryptedKey 
246            @return decrypted key  
247                @exception StandardException Standard Cloudscape error policy
248         */
249        private byte[] decryptKey(String encryptedKey, int encodedKeyCharLength, byte[] bootPassword)
250                 throws StandardException
251        {
252                byte[] secretKey = org.apache.derby.iapi.util.StringUtil.fromHexString(encryptedKey, 0, encodedKeyCharLength);
253                // In case of AES, care needs to be taken to allow for 16 bytes muck as well
254                // as to have the secretKey that needs encryption to be a aligned appropriately
255                // AES supports 16 bytes block size
256                int muckLength;
257                if(cryptoAlgorithmShort.equals(AES))
258                    muckLength = AES_IV_LENGTH;
259                else
260                    muckLength = secretKey.length;        
261 
262                byte[] muck = getMuckFromBootPassword(bootPassword, muckLength);
263 
264 
265                // decrypt the encryptedKey with the mucked up boot password to recover
266                // the secretKey
267                SecretKey key = generateKey(muck);
268                byte[] IV = generateIV(muck);
269 
270 
271                createNewCipher(DECRYPT, key, IV).
272                        decrypt(secretKey, 0, secretKey.length, secretKey, 0);
273 
274                return secretKey;
275        }
276 
277        private byte[] getMuckFromBootPassword(byte[] bootPassword, int encodedKeyByteLength) {
278                int ulength = bootPassword.length;
279 
280                byte[] muck = new byte[encodedKeyByteLength];
281                
282 
283                int rotation = 0;
284                for (int i = 0; i < bootPassword.length; i++)
285                        rotation += bootPassword[i];
286 
287                for (int i = 0; i < encodedKeyByteLength; i++)
288                        muck[i] = (byte)(bootPassword[(i+rotation)%ulength] ^
289                (bootPassword[i%ulength] << 4));
290 
291                return muck;
292        }
293 
294        /**
295                Generate a Key object using the input secretKey that can be used by
296                JCECipherProvider to encrypt or decrypt.
297 
298                @exception StandardException Standard Cloudscape Error Policy
299         */
300        private SecretKey generateKey(byte[] secretKey) throws StandardException
301        {
302                int length = secretKey.length;
303 
304                if (length < CipherFactory.MIN_BOOTPASS_LENGTH)
305                        throw StandardException.newException(SQLState.ILLEGAL_BP_LENGTH, new Integer(MIN_BOOTPASS_LENGTH));
306 
307                try
308                {
309            if (cryptoAlgorithmShort.equals(DES))
310            {   // single DES
311                            if (DESKeySpec.isWeak(secretKey, 0))
312                            {
313                                    // OK, it is weak, spice it up
314                                    byte[] spice = StringUtil.getAsciiBytes("louDScap");
315                                    for (int i = 0; i < 7; i++)
316                                            secretKey[i] = (byte)((spice[i] << 3) ^ secretKey[i]);
317                            }
318            }
319                        return new SecretKeySpec(secretKey, cryptoAlgorithmShort);
320                }
321                catch (InvalidKeyException ike)
322                {
323                        throw StandardException.newException(SQLState.CRYPTO_EXCEPTION, ike);
324                }
325 
326        }
327 
328        /**
329                Generate an IV using the input secretKey that can be used by
330                JCECipherProvider to encrypt or decrypt.
331         */
332        private byte[] generateIV(byte[] secretKey)
333        {
334 
335                // do a little simple minded muddling to make the IV not
336                // strictly alphanumeric and the number of total possible keys a little
337                // bigger.
338                int IVlen = BLOCK_LENGTH;
339                
340                byte[] iv = null;
341                if(cryptoAlgorithmShort.equals(AES))
342                {
343                        IVlen = AES_IV_LENGTH;
344                        iv = new byte[IVlen];
345                        iv[0] = (byte)(((secretKey[secretKey.length-1] << 2) | 0xF) ^ secretKey[0]);
346                        for (int i = 1; i < BLOCK_LENGTH; i++)
347                                iv[i] = (byte)(((secretKey[i-1] << (i%5)) | 0xF) ^ secretKey[i]);
348                        
349                        for(int i = BLOCK_LENGTH ; i < AES_IV_LENGTH ; i++)
350                        {
351                                iv[i]=iv[i-BLOCK_LENGTH];
352                        }
353                        
354                }        
355                else
356                {
357                        iv = new byte[BLOCK_LENGTH];
358                        iv[0] = (byte)(((secretKey[secretKey.length-1] << 2) | 0xF) ^ secretKey[0]);
359                        for (int i = 1; i < BLOCK_LENGTH; i++)
360                                iv[i] = (byte)(((secretKey[i-1] << (i%5)) | 0xF) ^ secretKey[i]);        
361                }
362 
363                return iv;
364        }
365 
366        private int digest(byte[] input)
367        {
368                messageDigest.reset();
369                byte[] digest = messageDigest.digest(input);
370                byte[] condenseDigest = new byte[2];
371 
372                // no matter how long the digest is, condense it into an short.
373                for (int i = 0; i < digest.length; i++)
374                        condenseDigest[i%2] ^= digest[i];
375 
376                int retval = (condenseDigest[0] & 0xFF) | ((condenseDigest[1] << 8) & 0xFF00);
377 
378                return retval;
379        }
380 
381        public SecureRandom getSecureRandom() {
382                return new SecureRandom(mainIV);
383        }
384 
385        public CipherProvider createNewCipher(int mode)
386                                                                                  throws StandardException {
387                return createNewCipher(mode, mainSecretKey, mainIV);
388        }
389 
390 
391        private CipherProvider createNewCipher(int mode, SecretKey secretKey,
392                                                                                  byte[] iv)
393                 throws StandardException
394        {
395                return new JCECipherProvider(mode, secretKey, iv, cryptoAlgorithm, cryptoProviderShort);
396        }
397 
398 
399    /*
400     * Initilize the new instance of this class. 
401     */
402        public void        init(boolean create, Properties properties, boolean newAttrs)
403                throws StandardException
404        {
405 
406        boolean provider_or_algo_specified = false;
407                boolean storeProperties = create;
408        persistentProperties = new Properties();
409 
410        // get the external key specified by the user to 
411        // encrypt the database. If user is reencrypting the
412        // database with a new encryption key,  read the value of 
413        // the new encryption key. 
414        String externalKey =  properties.getProperty((newAttrs ? 
415                                                      Attribute.NEW_CRYPTO_EXTERNAL_KEY:
416                                                      Attribute.CRYPTO_EXTERNAL_KEY));
417                if (externalKey != null) {
418                        storeProperties = false;
419                }
420 
421        cryptoProvider = properties.getProperty(Attribute.CRYPTO_PROVIDER);
422 
423                if (cryptoProvider == null)
424                {
425                        // JDK 1.3 does not create providers by itself.
426                        if (JVMInfo.JDK_ID == JVMInfo.J2SE_13) {
427 
428                                String vendor;
429                                try {
430                                        vendor = System.getProperty("java.vendor", "");
431                                } catch (SecurityException se) {
432                                        vendor = "";
433                                }
434 
435                                vendor = StringUtil.SQLToUpperCase(vendor);
436 
437                                if (vendor.startsWith("IBM "))
438                                        cryptoProvider = "com.ibm.crypto.provider.IBMJCE";
439                                else if (vendor.startsWith("SUN "))
440                                        cryptoProvider = "com.sun.crypto.provider.SunJCE";
441 
442                        }
443                }
444                else
445                {
446            provider_or_algo_specified = true;
447 
448                        // explictly putting the properties back into the properties
449                        // saves then in service.properties at create time.
450                //        if (storeProperties)
451                //                properties.put(Attribute.CRYPTO_PROVIDER, cryptoProvider);
452 
453                        int dotPos = cryptoProvider.lastIndexOf('.');
454                        if (dotPos == -1)
455                                cryptoProviderShort = cryptoProvider;
456                        else
457                                cryptoProviderShort = cryptoProvider.substring(dotPos+1);
458 
459                }
460 
461        cryptoAlgorithm = properties.getProperty(Attribute.CRYPTO_ALGORITHM);
462        if (cryptoAlgorithm == null)
463            cryptoAlgorithm = DEFAULT_ALGORITHM;
464        else {
465            provider_or_algo_specified = true;
466 
467                }
468 
469                // explictly putting the properties back into the properties
470                // saves then in service.properties at create time.
471        if (storeProperties)
472                        persistentProperties.put(Attribute.CRYPTO_ALGORITHM, 
473                                     cryptoAlgorithm);
474 
475        int firstSlashPos = cryptoAlgorithm.indexOf('/');
476        int lastSlashPos = cryptoAlgorithm.lastIndexOf('/');
477        if (firstSlashPos < 0 || lastSlashPos < 0 || firstSlashPos == lastSlashPos)
478                    throw StandardException.newException(SQLState.ENCRYPTION_BAD_ALG_FORMAT, cryptoAlgorithm);
479 
480        cryptoAlgorithmShort = cryptoAlgorithm.substring(0,firstSlashPos);
481 
482        if (provider_or_algo_specified)
483        {
484            // Track 3715 - disable use of provider/aglo specification if
485            // jce environment is not 1.2.1.  The ExemptionMechanism class
486            // exists in jce1.2.1 and not in jce1.2, so try and load the
487            // class and if you can't find it don't allow the encryption.
488            // This is a requirement from the government to give cloudscape
489            // export clearance for 3.6.  Note that the check is not needed
490            // if no provider/algo is specified, in that case we default to
491            // a DES weak encryption algorithm which also is allowed for
492            // export (this is how 3.5 got it's clearance).
493            try
494            {
495                Class c = Class.forName("javax.crypto.ExemptionMechanism");
496            }
497            catch (Throwable t)
498            {
499                throw StandardException.newException(
500                            SQLState.ENCRYPTION_BAD_JCE);
501            }
502        }
503 
504                // If connecting to an existing database and Attribute.CRYPTO_KEY_LENGTH is set
505                // then obtain the encoded key length values without padding bytes and retrieve
506                // the keylength in bits if boot password mechanism is used 
507                // note: Attribute.CRYPTO_KEY_LENGTH is set during creation time to a supported
508                // key length in the connection url. Internally , two values are stored in this property
509                // if encryptionKey is used, this property will have only the encoded key length
510                // if boot password mechanism is used, this property will have the following 
511                // keylengthBits-EncodedKeyLength 
512                 
513                if(!create)
514                {
515                    // if available, parse the keylengths stored in Attribute.CRYPTO_KEY_LENGTH 
516                    if(properties.getProperty(Attribute.CRYPTO_KEY_LENGTH) != null)
517                    {
518                        String keyLengths = properties.getProperty(Attribute.CRYPTO_KEY_LENGTH);
519                         int pos = keyLengths.lastIndexOf('-');
520                        encodedKeyLength = Integer.parseInt(keyLengths.substring(pos+1)); 
521                        if(pos != -1)
522                           keyLengthBits = Integer.parseInt(keyLengths.substring(0,pos));
523                    }
524                }
525                        
526 
527                // case 1 - if 'encryptionKey' is not set and 'encryptionKeyLength' is set, then use
528                // the 'encryptionKeyLength' property value  as the keyLength in bits.
529                // case 2 - 'encryptionKey' property is not set and 'encryptionKeyLength' is not set, then
530                // use the defaults keylength:  56bits for DES, 168 for DESede and 128 for any other encryption
531                // algorithm
532 
533                if (externalKey == null && create) {
534                        if(properties.getProperty(Attribute.CRYPTO_KEY_LENGTH) != null)
535                        {
536                                keyLengthBits = Integer.parseInt(properties.getProperty(Attribute.CRYPTO_KEY_LENGTH));
537                        }
538                        else if (cryptoAlgorithmShort.equals(DES)) {
539                                keyLengthBits = 56;
540                        } else if (cryptoAlgorithmShort.equals(DESede) || cryptoAlgorithmShort.equals(TripleDES)) {
541                                keyLengthBits = 168;
542 
543                        } else {
544                                keyLengthBits = 128;
545                        }
546                }
547 
548        // check the feedback mode
549        String feedbackMode = cryptoAlgorithm.substring(firstSlashPos+1,lastSlashPos);
550 
551        if (!feedbackMode.equals("CBC") && !feedbackMode.equals("CFB") &&
552            !feedbackMode.equals("ECB") && !feedbackMode.equals("OFB"))
553                    throw StandardException.newException(SQLState.ENCRYPTION_BAD_FEEDBACKMODE, feedbackMode);
554 
555        // check the NoPadding mode is used
556        String padding = cryptoAlgorithm.substring(lastSlashPos+1,cryptoAlgorithm.length());
557        if (!padding.equals("NoPadding"))
558                    throw StandardException.newException(SQLState.ENCRYPTION_BAD_PADDING, padding);
559 
560                Throwable t;
561                try
562                {
563                        if (cryptoProvider != null) {
564                                // provider package should be set by property
565                                if (Security.getProvider(cryptoProviderShort) == null)
566                                {
567                                        action = 1;
568                                        // add provider through privileged block.
569                                        java.security.AccessController.doPrivileged(this);
570                                }
571                        }
572 
573                        // need this to check the boot password
574                        messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST);
575 
576                        byte[] generatedKey;
577                        if (externalKey != null) {
578 
579                                // incorrect to specify external key and boot password
580                                if (properties.getProperty((newAttrs ? 
581                                            Attribute.NEW_BOOT_PASSWORD :
582                                            Attribute.BOOT_PASSWORD)) != null)
583                                        throw StandardException.newException(SQLState.SERVICE_WRONG_BOOT_PASSWORD);
584 
585                                generatedKey = 
586                    org.apache.derby.iapi.util.StringUtil.fromHexString(externalKey, 
587                                                                        0, 
588                                                                        externalKey.length());
589                if (generatedKey == null) {
590                    throw StandardException.newException(
591                        // If length is even, we assume invalid character(s),
592                        // based on how 'fromHexString' behaves.
593                        externalKey.length() % 2 == 0 
594                            ? SQLState.ENCRYPTION_ILLEGAL_EXKEY_CHARS
595                            : SQLState.ENCRYPTION_INVALID_EXKEY_LENGTH);
596                }
597 
598                        } else {
599 
600                                generatedKey = handleBootPassword(create, properties, newAttrs);
601                                if(create || newAttrs)
602                                   persistentProperties.put(Attribute.CRYPTO_KEY_LENGTH,
603                                            keyLengthBits+"-"+generatedKey.length);
604                        }
605 
606                        // Make a key and IV object out of the generated key
607                        mainSecretKey = generateKey(generatedKey);
608                        mainIV = generateIV(generatedKey);
609 
610                        if (create)
611                        {
612                                persistentProperties.put(Attribute.DATA_ENCRYPTION, "true");
613 
614                                // Set two new properties to allow for future changes to the log and data encryption
615                                // schemes. This property is introduced in version 10 , value starts at 1.
616                                persistentProperties.put(RawStoreFactory.DATA_ENCRYPT_ALGORITHM_VERSION,
617                                               String.valueOf(1));
618                                persistentProperties.put(RawStoreFactory.LOG_ENCRYPT_ALGORITHM_VERSION,
619                                               String.valueOf(1));
620                        }
621 
622                        return;
623                }
624                catch (java.security.PrivilegedActionException  pae)
625                {
626                        t = pae.getException();
627                }
628                catch (NoSuchAlgorithmException nsae)
629                {
630                        t = nsae;
631                }
632                catch (SecurityException se)
633                {
634                        t = se;
635                } catch (LinkageError le) {
636                        t = le;
637                } catch (ClassCastException cce) {
638                        t = cce;
639                }
640 
641                throw StandardException.newException(SQLState.MISSING_ENCRYPTION_PROVIDER, t);
642        }
643 
644 
645        private byte[] handleBootPassword(boolean create, 
646                                      Properties properties, 
647                                      boolean newPasswd)
648                throws StandardException {
649 
650 
651        // get the key  specifed by the user. If user is reencrypting the
652        // database; read the value of the new password. 
653                String inputKey = properties.getProperty((newPasswd ? 
654                                                  Attribute.NEW_BOOT_PASSWORD : 
655                                                  Attribute.BOOT_PASSWORD));
656                if (inputKey == null)
657                {
658                        throw StandardException.newException(SQLState.SERVICE_WRONG_BOOT_PASSWORD);
659                }
660 
661                byte[] bootPassword = StringUtil.getAsciiBytes(inputKey);
662 
663                if (bootPassword.length < CipherFactory.MIN_BOOTPASS_LENGTH)
664                {
665                        String messageId = create ? SQLState.SERVICE_BOOT_PASSWORD_TOO_SHORT :
666                                                                                SQLState.SERVICE_WRONG_BOOT_PASSWORD;
667 
668                        throw StandardException.newException(messageId);
669                }
670 
671                // Each database has its own unique encryption key that is
672                // not known even to the user.  However, this key is masked
673                // with the user input key and stored in the
674                // services.properties file so that, with the user key, the
675                // encryption key can easily be recovered.
676                // To change the user encryption key to a database, simply
677                // recover the unique real encryption key and masked it
678                // with the new user key.
679 
680                byte[] generatedKey;
681 
682                if (create || newPasswd)
683                {
684                        //
685                        generatedKey = generateUniqueBytes();
686 
687                        persistentProperties.put(RawStoreFactory.ENCRYPTED_KEY, 
688                                           saveSecretKey(generatedKey, bootPassword));
689 
690                }
691                else
692                {
693                        generatedKey = getDatabaseSecretKey(properties, bootPassword, SQLState.SERVICE_WRONG_BOOT_PASSWORD);
694                }
695 
696                return generatedKey;
697        }
698 
699    /* 
700     * put all the encyrpion cipger related properties that has to 
701     * be made peristent into the database service properties list.
702     * @param  properties  properties object that is used to store 
703     *                     cipher properties persistently. 
704     */
705    public void saveProperties(Properties properties) 
706    {
707        // put the cipher properties to be persistent into the 
708        // system perisistent properties. 
709        for (Enumeration e = persistentProperties.keys(); 
710             e.hasMoreElements(); ) 
711        {
712            String key = (String) e.nextElement();
713            properties.put(key, persistentProperties.get(key));
714                }
715 
716        // clear the cipher properties to be persistent. 
717        persistentProperties = null;
718    }
719 
720 
721        /**
722                get the secretkey used for encryption and decryption when boot password mechanism is used for encryption
723                Steps include 
724                retrieve the stored key, decrypt the stored key and verify if the correct boot password was passed 
725                There is a possibility that the decrypted key includes the original key and padded bytes in order to have
726                been block size aligned during encryption phase. Hence extract the original key 
727                
728                @param        properties        properties to retrieve the encrypted key  
729                @param        bootPassword        boot password used to connect to the encrypted database
730                @param        errorState        errorstate to account for any errors during retrieval /creation of the secretKey
731                @return the original unencrypted key bytes to use for encryption and decrytion   
732                
733         */
734        private byte[] getDatabaseSecretKey(Properties properties, byte[] bootPassword, String errorState) throws StandardException {
735 
736                // recover the generated secret encryption key from the
737                // services.properties file and the user key.
738                String keyString = properties.getProperty(RawStoreFactory.ENCRYPTED_KEY);
739                if (keyString == null)
740                        throw StandardException.newException(errorState);
741 
742                int encodedKeyCharLength = keyString.indexOf('-');
743 
744                if (encodedKeyCharLength == -1) // bad form
745                        throw StandardException.newException(errorState);
746 
747                int verifyKey = Integer.parseInt(keyString.substring(encodedKeyCharLength+1));
748                byte[] generatedKey = decryptKey(keyString, encodedKeyCharLength, bootPassword);
749 
750                int checkKey = digest(generatedKey);
751 
752                if (checkKey != verifyKey)
753                        throw StandardException.newException(errorState);
754 
755                // if encodedKeyLength is not defined, then either it is an old version with no support for different
756                // key sizes and padding except for defaults
757                byte[] result;        
758                if(encodedKeyLength != 0)
759                {
760                        result = new byte[encodedKeyLength];
761 
762                        // extract the generated key without the padding bytes
763                        System.arraycopy(generatedKey,0,result,0,encodedKeyLength);
764                        return result;
765                }
766 
767                return generatedKey;
768        }
769 
770        private String saveSecretKey(byte[] secretKey, byte[] bootPassword) throws StandardException {
771                String encryptedKey = encryptKey(secretKey, bootPassword);
772 
773                // make a verification key out of the message digest of
774                // the generated key
775                int verifyKey = digest(secretKey);
776 
777                return encryptedKey.concat("-" + verifyKey);
778 
779        }
780 
781        public String changeBootPassword(String changeString, Properties properties, CipherProvider verify)
782                throws StandardException {
783 
784                // the new bootPassword is expected to be of the form
785                // oldkey , newkey.
786                int seperator = changeString.indexOf(',');
787                if (seperator == -1)
788                        throw StandardException.newException(SQLState.WRONG_PASSWORD_CHANGE_FORMAT);
789 
790                String oldBP = changeString.substring(0, seperator).trim();
791                byte[] oldBPAscii = StringUtil.getAsciiBytes(oldBP);
792                if (oldBPAscii == null || oldBPAscii.length < CipherFactory.MIN_BOOTPASS_LENGTH)
793                        throw StandardException.newException(SQLState.WRONG_BOOT_PASSWORD);;
794 
795                String newBP = changeString.substring(seperator+1).trim();
796                byte[] newBPAscii = StringUtil.getAsciiBytes(newBP);
797                if (newBPAscii == null || newBPAscii.length < CipherFactory.MIN_BOOTPASS_LENGTH)
798                        throw StandardException.newException(SQLState.ILLEGAL_BP_LENGTH,
799                new Integer(CipherFactory.MIN_BOOTPASS_LENGTH));
800 
801                // verify old key
802 
803                byte[] generatedKey = getDatabaseSecretKey(properties, oldBPAscii, SQLState.WRONG_BOOT_PASSWORD);
804 
805                // make sure the oldKey is correct
806                byte[] IV = generateIV(generatedKey);
807 
808                if (!((JCECipherProvider) verify).verifyIV(IV))
809                        throw StandardException.newException(SQLState.WRONG_BOOT_PASSWORD);
810 
811 
812                // Make the new key.  The generated key is unchanged, only the
813                // encrypted key is changed.
814                String newkey = saveSecretKey(generatedKey, newBPAscii);
815                
816                properties.put(Attribute.CRYPTO_KEY_LENGTH,keyLengthBits+"-"+encodedKeyLength);
817                
818 
819                return saveSecretKey(generatedKey, newBPAscii);
820        }
821 
822        /**
823                 perform actions with privileges enabled.
824         */
825        public final Object run() throws StandardException, InstantiationException, IllegalAccessException {
826 
827                try {
828 
829                        switch(action)
830                        {
831                                case 1:
832                                        Security.addProvider(
833                                        (Provider)(Class.forName(cryptoProvider).newInstance()));
834                                        break;
835                                case 2:
836                                        // SECURITY PERMISSION - MP1 and/or OP4
837                                        // depends on the value of activePerms
838                                        return activeFile.getRandomAccessFile(activePerms);
839 
840                        }
841 
842                } catch (ClassNotFoundException cnfe) {
843                        throw StandardException.newException(SQLState.ENCRYPTION_NO_PROVIDER_CLASS,cryptoProvider);
844                }
845                catch(FileNotFoundException fnfe) {
846                        throw StandardException.newException(SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,cryptoProvider);
847                }
848                return null;
849        }
850 
851 
852 
853        /**
854            The database can be encrypted with an encryption key given in connection url.
855            For security reasons, this key is not made persistent in the database.
856 
857            But it is necessary to verify the encryption key when booting the database if it is similar
858            to the one used when creating the database
859            This needs to happen before we access the data/logs to avoid the risk of corrupting the 
860            database because of a wrong encryption key.
861 
862            This method performs the steps necessary to verify the encryption key if an external
863            encryption key is given.
864 
865            At database creation, 4k of random data is generated using SecureRandom and MD5 is used
866            to compute the checksum for the random data thus generated.  This 4k page of random data
867            is then encrypted using the encryption key. The checksum of unencrypted data and
868            encrypted data is made persistent in the database in file by name given by
869            Attribute.CRYPTO_EXTERNAL_KEY_VERIFYFILE (verifyKey.dat). This file exists directly under the
870            database root directory.
871 
872            When trying to boot an existing encrypted database, the given encryption key is used to decrypt
873            the data in the verifyKey.dat and the checksum is calculated and compared against the original
874            stored checksum. If these checksums dont match an exception is thrown.
875 
876            Please note, this process of verifying the key  does not provide any added security but only is 
877            intended to allow to fail gracefully if a wrong encryption key is used
878 
879            StandardException is thrown if there are any problems during the process of verification
880                            of the encryption key or if there is any mismatch of the encryption key.
881 
882         */
883 
884        public void verifyKey(boolean create, StorageFactory sf, Properties properties)
885                throws StandardException
886        {
887 
888                if(properties.getProperty(Attribute.CRYPTO_EXTERNAL_KEY) == null)
889                        return;
890 
891                // if firstTime ( ie during creation of database, initial key used )
892                // In order to allow for verifying the external key for future database boot,
893                // generate random 4k of data and store the encrypted random data and the checksum
894                // using MD5 of the unencrypted data. That way, on next database boot a check is performed
895                // to verify if the key is the same as used when the database was created
896 
897                StorageRandomAccessFile verifyKeyFile = null;
898                byte[] data = new byte[VERIFYKEY_DATALEN];
899                try
900                {
901                        if(create)
902                        {
903                                getSecureRandom().nextBytes(data);
904                                // get the checksum
905                                byte[] checksum = getMD5Checksum(data);
906 
907                                CipherProvider tmpCipherProvider = createNewCipher(ENCRYPT,mainSecretKey,mainIV);
908                                tmpCipherProvider.encrypt(data, 0, data.length, data, 0);
909                                // openFileForWrite
910                                verifyKeyFile = privAccessFile(sf,Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE,"rw");
911                                // write the checksum length as int, and then the checksum and then the encrypted data
912                                verifyKeyFile.writeInt(checksum.length);
913                                verifyKeyFile.write(checksum);
914                                verifyKeyFile.write(data);
915                                verifyKeyFile.sync(true);
916                        }
917                        else
918                        {
919                                // open file for reading only
920                                verifyKeyFile = privAccessFile(sf,Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE,"r");
921                                // then read the checksum length 
922                                int checksumLen = verifyKeyFile.readInt();
923 
924                                byte[] originalChecksum = new byte[checksumLen];
925                                verifyKeyFile.readFully(originalChecksum);
926 
927                                verifyKeyFile.readFully(data);
928 
929                                // decrypt data with key
930                                CipherProvider tmpCipherProvider = createNewCipher(DECRYPT,mainSecretKey,mainIV);
931                                tmpCipherProvider.decrypt(data, 0, data.length, data, 0);
932 
933                                byte[] verifyChecksum = getMD5Checksum(data);
934 
935                                if(!MessageDigest.isEqual(originalChecksum,verifyChecksum))
936                                {
937                                        throw StandardException.newException(SQLState.ENCRYPTION_BAD_EXTERNAL_KEY);
938                                }
939 
940                        }
941                }
942                catch(IOException ioe)
943                {
944                        throw StandardException.newException(SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,ioe);
945                }
946                finally
947                {
948                        try
949                        {
950                                if(verifyKeyFile != null)
951                                        verifyKeyFile.close();
952                        }
953                        catch(IOException ioee)
954                        {
955                                throw StandardException.newException(SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,ioee);
956                        }
957                }
958                return ;
959        }
960 
961 
962        /**
963                Use MD5 MessageDigest algorithm to generate checksum
964                @param data        data to be used to compute the hash value
965                @return returns the hash value computed using the data
966 
967         */
968        private byte[] getMD5Checksum(byte[] data)
969                throws StandardException
970        {
971                try
972                {
973                        // get the checksum
974                        MessageDigest md5 = MessageDigest.getInstance("MD5");
975                        return md5.digest(data);
976                }
977                catch(NoSuchAlgorithmException nsae)
978                {
979                        throw StandardException.newException(SQLState.ENCRYPTION_BAD_ALG_FORMAT,MESSAGE_DIGEST);
980                }
981 
982        }
983 
984 
985        /**
986                 access a file for either read/write
987                 @param storageFactory        factory used for io access
988                 @param        fileName                name of the file to create and open for write
989                                                         The file will be created directly under the database root directory
990                @param        filePerms                file permissions, if "rw" open file with read and write permissions
991                                                            if "r" , open file with read permissions
992                 @return        StorageRandomAccessFile returns file with fileName for writing
993                @exception IOException Any exception during accessing the file for read/write
994         */
995        private StorageRandomAccessFile privAccessFile(StorageFactory storageFactory,String fileName,String filePerms)
996                throws java.io.IOException
997        {
998                StorageFile verifyKeyFile = storageFactory.newStorageFile("",fileName);
999                activeFile  = verifyKeyFile;
1000                this.action = 2;
1001                activePerms = filePerms;
1002            try
1003        {
1004                        return (StorageRandomAccessFile)java.security.AccessController.doPrivileged(this);
1005                }
1006                catch( java.security.PrivilegedActionException pae)
1007                {
1008                        throw (java.io.IOException)pae.getException();
1009                }
1010        }
1011 
1012 
1013}

[all classes][org.apache.derby.impl.services.jce]
EMMA 2.0.5312 (C) Vladimir Roubtsov