KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > derby > impl > services > jce > JCECipherFactory


1 /*
2
3    Derby - Class org.apache.derby.impl.services.jce.JCECipherFactory
4
5    Licensed to the Apache Software Foundation (ASF) under one or more
6    contributor license agreements. See the NOTICE file distributed with
7    this work for additional information regarding copyright ownership.
8    The ASF licenses this file to you under the Apache License, Version 2.0
9    (the "License"); you may not use this file except in compliance with
10    the License. You may obtain a copy of the License at
11
12       http://www.apache.org/licenses/LICENSE-2.0
13
14    Unless required by applicable law or agreed to in writing, software
15    distributed under the License is distributed on an "AS IS" BASIS,
16    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17    See the License for the specific language governing permissions and
18    limitations under the License.
19
20  */

21
22 package org.apache.derby.impl.services.jce;
23
24 import org.apache.derby.iapi.services.crypto.CipherFactory;
25 import org.apache.derby.iapi.services.crypto.CipherProvider;
26
27 import org.apache.derby.iapi.services.monitor.Monitor;
28 import org.apache.derby.iapi.services.sanity.SanityManager;
29
30 import org.apache.derby.iapi.error.StandardException;
31
32 import org.apache.derby.iapi.services.info.JVMInfo;
33 import org.apache.derby.iapi.util.StringUtil;
34
35 import org.apache.derby.iapi.reference.SQLState;
36 import org.apache.derby.iapi.reference.Attribute;
37 import org.apache.derby.iapi.util.StringUtil;
38
39 import java.util.Properties JavaDoc;
40 import java.util.Enumeration JavaDoc;
41 import java.security.Key JavaDoc;
42 import java.security.Provider JavaDoc;
43 import java.security.SecureRandom JavaDoc;
44 import java.security.Security JavaDoc;
45 import java.security.InvalidKeyException JavaDoc;
46 import java.security.NoSuchAlgorithmException JavaDoc;
47 import java.security.MessageDigest JavaDoc;
48 import java.security.spec.KeySpec JavaDoc;
49 import java.security.spec.InvalidKeySpecException JavaDoc;
50 import java.io.FileNotFoundException JavaDoc;
51 import java.io.IOException JavaDoc;
52 import java.io.InputStream JavaDoc;
53 import java.io.DataInputStream JavaDoc;
54
55 import javax.crypto.KeyGenerator;
56 import javax.crypto.SecretKey;
57 import javax.crypto.SecretKeyFactory;
58 import javax.crypto.spec.DESKeySpec;
59 import javax.crypto.spec.SecretKeySpec;
60 import org.apache.derby.iapi.store.raw.RawStoreFactory;
61
62 import org.apache.derby.io.StorageFactory;
63 import org.apache.derby.io.WritableStorageFactory;
64 import org.apache.derby.io.StorageFile;
65 import org.apache.derby.io.StorageRandomAccessFile;
66 /**
67     This CipherFactory creates new JCECipherProvider.
68
69     @see CipherFactory
70  */

71 public final class JCECipherFactory implements CipherFactory, java.security.PrivilegedExceptionAction JavaDoc
72 {
73     private final static String JavaDoc MESSAGE_DIGEST = "MD5";
74
75     private final static String JavaDoc DEFAULT_PROVIDER = "com.sun.crypto.provider.SunJCE";
76     private final static String JavaDoc DEFAULT_ALGORITHM = "DES/CBC/NoPadding";
77     private final static String JavaDoc DES = "DES";
78     private final static String JavaDoc DESede = "DESede";
79     private final static String JavaDoc TripleDES = "TripleDES";
80     private final static String JavaDoc AES = "AES";
81
82     // minimum boot password length in bytes
83
private final static int BLOCK_LENGTH = 8;
84
85     /**
86     AES encryption takes in an default Initialization vector length (IV) length of 16 bytes
87     This is needed to generate an IV to use for encryption and decryption process
88     @see CipherProvider
89      */

90     private final static int AES_IV_LENGTH = 16;
91
92     // key length in bytes
93
private int keyLengthBits;
94     private int encodedKeyLength;
95     private String JavaDoc cryptoAlgorithm;
96     private String JavaDoc cryptoAlgorithmShort;
97     private String JavaDoc cryptoProvider;
98     private String JavaDoc cryptoProviderShort;
99     private MessageDigest JavaDoc messageDigest;
100
101     private SecretKey mainSecretKey;
102     private byte[] mainIV;
103
104     // properties that needs to be stored in the
105
// in the service.properties file.
106
private Properties JavaDoc persistentProperties;
107
108
109     /**
110         Amount of data that is used for verification of external encryption key
111         This does not include the MD5 checksum bytes
112      */

113     private final static int VERIFYKEY_DATALEN = 4096;
114     private StorageFile activeFile;
115     private int action;
116     private String JavaDoc activePerms;
117
118
119     /*
120      * Constructor of JCECipherFactory, initializes the new instances.
121      *
122      * @param create true, if the database is getting configured
123      * for encryption.
124      * @param props encryption properties/attributes to use
125      * for creating the cipher factory.
126      * @param newAttrs true, if cipher factory has to be created using
127      * should using the new attributes specified by the user.
128      * For example to reencrypt the database with
129      * a new password.
130      */

131     public JCECipherFactory(boolean create,
132                             Properties JavaDoc props,
133                             boolean newAttributes)
134         throws StandardException
135     {
136         init(create, props, newAttributes);
137     }
138     
139
140
141     static String JavaDoc providerErrorName(String JavaDoc cps) {
142
143         return cps == null ? "default" : cps;
144     }
145
146
147     private byte[] generateUniqueBytes() throws StandardException
148     {
149         try {
150
151             String JavaDoc provider = cryptoProviderShort;
152
153             KeyGenerator keyGen;
154             if (provider == null)
155             {
156                 keyGen = KeyGenerator.getInstance(cryptoAlgorithmShort);
157             }
158             else
159             {
160                 if( provider.equals("BouncyCastleProvider"))
161                     provider = "BC";
162                 keyGen = KeyGenerator.getInstance(cryptoAlgorithmShort, provider);
163             }
164
165             keyGen.init(keyLengthBits);
166
167             SecretKey key = keyGen.generateKey();
168
169             return key.getEncoded();
170
171         } catch (java.security.NoSuchAlgorithmException JavaDoc nsae) {
172             throw StandardException.newException(SQLState.ENCRYPTION_NOSUCH_ALGORITHM, cryptoAlgorithm,
173                 JCECipherFactory.providerErrorName(cryptoProviderShort));
174         } catch (java.security.NoSuchProviderException JavaDoc nspe) {
175             throw StandardException.newException(SQLState.ENCRYPTION_BAD_PROVIDER,
176                 JCECipherFactory.providerErrorName(cryptoProviderShort));
177         }
178     }
179
180     /**
181         Encrypt the secretKey with the boot password.
182         This includes the following steps,
183         getting muck from the boot password and then using this to generate a key,
184         generating an appropriate IV using the muck
185         using the key and IV thus generated to create the appropriate cipher provider
186         and encrypting the secretKey
187         @return hexadecimal string of the encrypted secretKey
188
189         @exception StandardException Standard Cloudscape error policy
190      */

191     private String JavaDoc encryptKey(byte[] secretKey, byte[] bootPassword)
192          throws StandardException
193     {
194         // In case of AES, care needs to be taken to allow for 16 bytes muck as well
195
// as to have the secretKey that needs encryption to be a aligned appropriately
196
// AES supports 16 bytes block size
197

198         int muckLength = secretKey.length;
199         if(cryptoAlgorithmShort.equals(AES))
200             muckLength = AES_IV_LENGTH;
201
202         byte[] muck = getMuckFromBootPassword(bootPassword, muckLength);
203         SecretKey key = generateKey(muck);
204         byte[] IV = generateIV(muck);
205                 CipherProvider tmpCipherProvider = createNewCipher(ENCRYPT,key,IV);
206         
207         // store the actual secretKey.length before any possible padding
208
encodedKeyLength = secretKey.length;
209
210         // for the secretKey to be encrypted, first ensure that it is aligned to the block size of the
211
// encryption algorithm by padding bytes appropriately if needed
212
secretKey = padKey(secretKey,tmpCipherProvider.getEncryptionBlockSize());
213
214                 byte[] result = new byte[secretKey.length];
215
216         // encrypt the secretKey using the key generated of muck from boot password and the generated IV
217
tmpCipherProvider.encrypt(secretKey, 0, secretKey.length, result, 0);
218
219         return org.apache.derby.iapi.util.StringUtil.toHexString(result, 0, result.length);
220
221     }
222     
223     /**
224             For block ciphers, and algorithms using the NoPadding scheme, the data that has
225             to be encrypted needs to be a multiple of the expected block size for the cipher
226         Pad the key with appropriate padding to make it blockSize align
227         @param secretKey the data that needs blocksize alignment
228         @param blockSizeAlign secretKey needs to be blocksize aligned
229         @return a byte array with the contents of secretKey along with padded bytes in the end
230                to make it blockSize aligned
231          */

232     private byte[] padKey(byte[] secretKey,int blockSizeAlign)
233     {
234         byte [] result = secretKey;
235         if(secretKey.length % blockSizeAlign != 0 )
236         {
237         int encryptedLength = secretKey.length + blockSizeAlign - (secretKey.length % blockSizeAlign);
238         result = new byte[encryptedLength];
239         System.arraycopy(secretKey,0,result,0,secretKey.length);
240         }
241         return result;
242     }
243
244     /**
245         Decrypt the secretKey with the user key .
246         This includes the following steps,
247         retrieve the encryptedKey, generate the muck from the boot password and generate an appropriate IV using
248         the muck,and using the key and IV decrypt the encryptedKey
249         @return decrypted key
250         @exception StandardException Standard Cloudscape error policy
251      */

252     private byte[] decryptKey(String JavaDoc encryptedKey, int encodedKeyCharLength, byte[] bootPassword)
253          throws StandardException
254     {
255         byte[] secretKey = org.apache.derby.iapi.util.StringUtil.fromHexString(encryptedKey, 0, encodedKeyCharLength);
256         // In case of AES, care needs to be taken to allow for 16 bytes muck as well
257
// as to have the secretKey that needs encryption to be a aligned appropriately
258
// AES supports 16 bytes block size
259
int muckLength;
260         if(cryptoAlgorithmShort.equals(AES))
261             muckLength = AES_IV_LENGTH;
262         else
263                 muckLength = secretKey.length;
264
265         byte[] muck = getMuckFromBootPassword(bootPassword, muckLength);
266
267
268         // decrypt the encryptedKey with the mucked up boot password to recover
269
// the secretKey
270
SecretKey key = generateKey(muck);
271         byte[] IV = generateIV(muck);
272
273
274         createNewCipher(DECRYPT, key, IV).
275             decrypt(secretKey, 0, secretKey.length, secretKey, 0);
276
277         return secretKey;
278     }
279
280     private byte[] getMuckFromBootPassword(byte[] bootPassword, int encodedKeyByteLength) {
281         int ulength = bootPassword.length;
282
283         byte[] muck = new byte[encodedKeyByteLength];
284         
285
286         int rotation = 0;
287         for (int i = 0; i < bootPassword.length; i++)
288             rotation += bootPassword[i];
289
290         for (int i = 0; i < encodedKeyByteLength; i++)
291             muck[i] = (byte)(bootPassword[(i+rotation)%ulength] ^
292                 (bootPassword[i%ulength] << 4));
293
294         return muck;
295     }
296
297     /**
298         Generate a Key object using the input secretKey that can be used by
299         JCECipherProvider to encrypt or decrypt.
300
301         @exception StandardException Standard Cloudscape Error Policy
302      */

303     private SecretKey generateKey(byte[] secretKey) throws StandardException
304     {
305         int length = secretKey.length;
306
307         if (length < CipherFactory.MIN_BOOTPASS_LENGTH)
308             throw StandardException.newException(SQLState.ILLEGAL_BP_LENGTH, new Integer JavaDoc(MIN_BOOTPASS_LENGTH));
309
310         try
311         {
312             if (cryptoAlgorithmShort.equals(DES))
313             { // single DES
314
if (DESKeySpec.isWeak(secretKey, 0))
315                 {
316                     // OK, it is weak, spice it up
317
byte[] spice = StringUtil.getAsciiBytes("louDScap");
318                     for (int i = 0; i < 7; i++)
319                         secretKey[i] = (byte)((spice[i] << 3) ^ secretKey[i]);
320                 }
321             }
322             return new SecretKeySpec(secretKey, cryptoAlgorithmShort);
323         }
324         catch (InvalidKeyException JavaDoc ike)
325         {
326             throw StandardException.newException(SQLState.CRYPTO_EXCEPTION, ike);
327         }
328
329     }
330
331     /**
332         Generate an IV using the input secretKey that can be used by
333         JCECipherProvider to encrypt or decrypt.
334      */

335     private byte[] generateIV(byte[] secretKey)
336     {
337
338         // do a little simple minded muddling to make the IV not
339
// strictly alphanumeric and the number of total possible keys a little
340
// bigger.
341
int IVlen = BLOCK_LENGTH;
342         
343         byte[] iv = null;
344         if(cryptoAlgorithmShort.equals(AES))
345         {
346             IVlen = AES_IV_LENGTH;
347             iv = new byte[IVlen];
348             iv[0] = (byte)(((secretKey[secretKey.length-1] << 2) | 0xF) ^ secretKey[0]);
349             for (int i = 1; i < BLOCK_LENGTH; i++)
350                 iv[i] = (byte)(((secretKey[i-1] << (i%5)) | 0xF) ^ secretKey[i]);
351             
352             for(int i = BLOCK_LENGTH ; i < AES_IV_LENGTH ; i++)
353             {
354                 iv[i]=iv[i-BLOCK_LENGTH];
355             }
356             
357         }
358         else
359         {
360             iv = new byte[BLOCK_LENGTH];
361             iv[0] = (byte)(((secretKey[secretKey.length-1] << 2) | 0xF) ^ secretKey[0]);
362             for (int i = 1; i < BLOCK_LENGTH; i++)
363                 iv[i] = (byte)(((secretKey[i-1] << (i%5)) | 0xF) ^ secretKey[i]);
364         }
365
366         return iv;
367     }
368
369     private int digest(byte[] input)
370     {
371         messageDigest.reset();
372         byte[] digest = messageDigest.digest(input);
373         byte[] condenseDigest = new byte[2];
374
375         // no matter how long the digest is, condense it into an short.
376
for (int i = 0; i < digest.length; i++)
377             condenseDigest[i%2] ^= digest[i];
378
379         int retval = (condenseDigest[0] & 0xFF) | ((condenseDigest[1] << 8) & 0xFF00);
380
381         return retval;
382     }
383
384     public SecureRandom JavaDoc getSecureRandom() {
385         return new SecureRandom JavaDoc(mainIV);
386     }
387
388     public CipherProvider createNewCipher(int mode)
389                                           throws StandardException {
390         return createNewCipher(mode, mainSecretKey, mainIV);
391     }
392
393
394     private CipherProvider createNewCipher(int mode, SecretKey secretKey,
395                                           byte[] iv)
396          throws StandardException
397     {
398         return new JCECipherProvider(mode, secretKey, iv, cryptoAlgorithm, cryptoProviderShort);
399     }
400
401
402     /*
403      * Initilize the new instance of this class.
404      */

405     public void init(boolean create, Properties JavaDoc properties, boolean newAttrs)
406         throws StandardException
407     {
408
409         boolean provider_or_algo_specified = false;
410         boolean storeProperties = create;
411         persistentProperties = new Properties JavaDoc();
412
413         // get the external key specified by the user to
414
// encrypt the database. If user is reencrypting the
415
// database with a new encryption key, read the value of
416
// the new encryption key.
417
String JavaDoc externalKey = properties.getProperty((newAttrs ?
418                                                       Attribute.NEW_CRYPTO_EXTERNAL_KEY:
419                                                       Attribute.CRYPTO_EXTERNAL_KEY));
420         if (externalKey != null) {
421             storeProperties = false;
422         }
423
424         cryptoProvider = properties.getProperty(Attribute.CRYPTO_PROVIDER);
425
426         if (cryptoProvider == null)
427         {
428             // JDK 1.3 does not create providers by itself.
429
if (JVMInfo.JDK_ID == JVMInfo.J2SE_13) {
430
431                 String JavaDoc vendor;
432                 try {
433                     vendor = System.getProperty("java.vendor", "");
434                 } catch (SecurityException JavaDoc se) {
435                     vendor = "";
436                 }
437
438                 vendor = StringUtil.SQLToUpperCase(vendor);
439
440                 if (vendor.startsWith("IBM "))
441                     cryptoProvider = "com.ibm.crypto.provider.IBMJCE";
442                 else if (vendor.startsWith("SUN "))
443                     cryptoProvider = "com.sun.crypto.provider.SunJCE";
444
445             }
446         }
447         else
448         {
449             provider_or_algo_specified = true;
450
451             // explictly putting the properties back into the properties
452
// saves then in service.properties at create time.
453
// if (storeProperties)
454
// properties.put(Attribute.CRYPTO_PROVIDER, cryptoProvider);
455

456             int dotPos = cryptoProvider.lastIndexOf('.');
457             if (dotPos == -1)
458                 cryptoProviderShort = cryptoProvider;
459             else
460                 cryptoProviderShort = cryptoProvider.substring(dotPos+1);
461
462         }
463
464         cryptoAlgorithm = properties.getProperty(Attribute.CRYPTO_ALGORITHM);
465         if (cryptoAlgorithm == null)
466             cryptoAlgorithm = DEFAULT_ALGORITHM;
467         else {
468             provider_or_algo_specified = true;
469
470         }
471
472         // explictly putting the properties back into the properties
473
// saves then in service.properties at create time.
474
if (storeProperties)
475             persistentProperties.put(Attribute.CRYPTO_ALGORITHM,
476                                      cryptoAlgorithm);
477
478         int firstSlashPos = cryptoAlgorithm.indexOf('/');
479         int lastSlashPos = cryptoAlgorithm.lastIndexOf('/');
480         if (firstSlashPos < 0 || lastSlashPos < 0 || firstSlashPos == lastSlashPos)
481             throw StandardException.newException(SQLState.ENCRYPTION_BAD_ALG_FORMAT, cryptoAlgorithm);
482
483         cryptoAlgorithmShort = cryptoAlgorithm.substring(0,firstSlashPos);
484
485         if (provider_or_algo_specified)
486         {
487             // Track 3715 - disable use of provider/aglo specification if
488
// jce environment is not 1.2.1. The ExemptionMechanism class
489
// exists in jce1.2.1 and not in jce1.2, so try and load the
490
// class and if you can't find it don't allow the encryption.
491
// This is a requirement from the government to give cloudscape
492
// export clearance for 3.6. Note that the check is not needed
493
// if no provider/algo is specified, in that case we default to
494
// a DES weak encryption algorithm which also is allowed for
495
// export (this is how 3.5 got it's clearance).
496
try
497             {
498                 Class JavaDoc c = Class.forName("javax.crypto.ExemptionMechanism");
499             }
500             catch (Throwable JavaDoc t)
501             {
502                 throw StandardException.newException(
503                             SQLState.ENCRYPTION_BAD_JCE);
504             }
505         }
506
507         // If connecting to an existing database and Attribute.CRYPTO_KEY_LENGTH is set
508
// then obtain the encoded key length values without padding bytes and retrieve
509
// the keylength in bits if boot password mechanism is used
510
// note: Attribute.CRYPTO_KEY_LENGTH is set during creation time to a supported
511
// key length in the connection url. Internally , two values are stored in this property
512
// if encryptionKey is used, this property will have only the encoded key length
513
// if boot password mechanism is used, this property will have the following
514
// keylengthBits-EncodedKeyLength
515

516         if(!create)
517         {
518             // if available, parse the keylengths stored in Attribute.CRYPTO_KEY_LENGTH
519
if(properties.getProperty(Attribute.CRYPTO_KEY_LENGTH) != null)
520             {
521             String JavaDoc keyLengths = properties.getProperty(Attribute.CRYPTO_KEY_LENGTH);
522             int pos = keyLengths.lastIndexOf('-');
523             encodedKeyLength = Integer.parseInt(keyLengths.substring(pos+1));
524             if(pos != -1)
525                keyLengthBits = Integer.parseInt(keyLengths.substring(0,pos));
526             }
527         }
528             
529
530         // case 1 - if 'encryptionKey' is not set and 'encryptionKeyLength' is set, then use
531
// the 'encryptionKeyLength' property value as the keyLength in bits.
532
// case 2 - 'encryptionKey' property is not set and 'encryptionKeyLength' is not set, then
533
// use the defaults keylength: 56bits for DES, 168 for DESede and 128 for any other encryption
534
// algorithm
535

536         if (externalKey == null && create) {
537             if(properties.getProperty(Attribute.CRYPTO_KEY_LENGTH) != null)
538             {
539                 keyLengthBits = Integer.parseInt(properties.getProperty(Attribute.CRYPTO_KEY_LENGTH));
540             }
541             else if (cryptoAlgorithmShort.equals(DES)) {
542                 keyLengthBits = 56;
543             } else if (cryptoAlgorithmShort.equals(DESede) || cryptoAlgorithmShort.equals(TripleDES)) {
544                 keyLengthBits = 168;
545
546             } else {
547                 keyLengthBits = 128;
548             }
549         }
550
551         // check the feedback mode
552
String JavaDoc feedbackMode = cryptoAlgorithm.substring(firstSlashPos+1,lastSlashPos);
553
554         if (!feedbackMode.equals("CBC") && !feedbackMode.equals("CFB") &&
555             !feedbackMode.equals("ECB") && !feedbackMode.equals("OFB"))
556             throw StandardException.newException(SQLState.ENCRYPTION_BAD_FEEDBACKMODE, feedbackMode);
557
558         // check the NoPadding mode is used
559
String JavaDoc padding = cryptoAlgorithm.substring(lastSlashPos+1,cryptoAlgorithm.length());
560         if (!padding.equals("NoPadding"))
561             throw StandardException.newException(SQLState.ENCRYPTION_BAD_PADDING, padding);
562
563         Throwable JavaDoc t;
564         try
565         {
566             if (cryptoProvider != null) {
567                 // provider package should be set by property
568
if (Security.getProvider(cryptoProviderShort) == null)
569                 {
570                     action = 1;
571                     // add provider through privileged block.
572
java.security.AccessController.doPrivileged(this);
573                 }
574             }
575
576             // need this to check the boot password
577
messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST);
578
579             byte[] generatedKey;
580             if (externalKey != null) {
581
582                 // incorrect to specify external key and boot password
583
if (properties.getProperty((newAttrs ?
584                                             Attribute.NEW_BOOT_PASSWORD :
585                                             Attribute.BOOT_PASSWORD)) != null)
586                     throw StandardException.newException(SQLState.SERVICE_WRONG_BOOT_PASSWORD);
587
588                 generatedKey =
589                     org.apache.derby.iapi.util.StringUtil.fromHexString(externalKey,
590                                                                         0,
591                                                                         externalKey.length());
592                 if (generatedKey == null) {
593                     throw StandardException.newException(
594                         // If length is even, we assume invalid character(s),
595
// based on how 'fromHexString' behaves.
596
externalKey.length() % 2 == 0
597                             ? SQLState.ENCRYPTION_ILLEGAL_EXKEY_CHARS
598                             : SQLState.ENCRYPTION_INVALID_EXKEY_LENGTH);
599                 }
600
601             } else {
602
603                 generatedKey = handleBootPassword(create, properties, newAttrs);
604                 if(create || newAttrs)
605                    persistentProperties.put(Attribute.CRYPTO_KEY_LENGTH,
606                                             keyLengthBits+"-"+generatedKey.length);
607             }
608
609             // Make a key and IV object out of the generated key
610
mainSecretKey = generateKey(generatedKey);
611             mainIV = generateIV(generatedKey);
612
613             if (create)
614             {
615                 persistentProperties.put(Attribute.DATA_ENCRYPTION, "true");
616
617                 // Set two new properties to allow for future changes to the log and data encryption
618
// schemes. This property is introduced in version 10 , value starts at 1.
619
persistentProperties.put(RawStoreFactory.DATA_ENCRYPT_ALGORITHM_VERSION,
620                                                String.valueOf(1));
621                 persistentProperties.put(RawStoreFactory.LOG_ENCRYPT_ALGORITHM_VERSION,
622                                                String.valueOf(1));
623             }
624
625             return;
626         }
627         catch (java.security.PrivilegedActionException JavaDoc pae)
628         {
629             t = pae.getException();
630         }
631         catch (NoSuchAlgorithmException JavaDoc nsae)
632         {
633             t = nsae;
634         }
635         catch (SecurityException JavaDoc se)
636         {
637             t = se;
638         } catch (LinkageError JavaDoc le) {
639             t = le;
640         } catch (ClassCastException JavaDoc cce) {
641             t = cce;
642         }
643
644         throw StandardException.newException(SQLState.MISSING_ENCRYPTION_PROVIDER, t);
645     }
646
647
648     private byte[] handleBootPassword(boolean create,
649                                       Properties JavaDoc properties,
650                                       boolean newPasswd)
651         throws StandardException {
652
653
654         // get the key specifed by the user. If user is reencrypting the
655
// database; read the value of the new password.
656
String JavaDoc inputKey = properties.getProperty((newPasswd ?
657                                                   Attribute.NEW_BOOT_PASSWORD :
658                                                   Attribute.BOOT_PASSWORD));
659         if (inputKey == null)
660         {
661             throw StandardException.newException(SQLState.SERVICE_WRONG_BOOT_PASSWORD);
662         }
663
664         byte[] bootPassword = StringUtil.getAsciiBytes(inputKey);
665
666         if (bootPassword.length < CipherFactory.MIN_BOOTPASS_LENGTH)
667         {
668             String JavaDoc messageId = create ? SQLState.SERVICE_BOOT_PASSWORD_TOO_SHORT :
669                                         SQLState.SERVICE_WRONG_BOOT_PASSWORD;
670
671             throw StandardException.newException(messageId);
672         }
673
674         // Each database has its own unique encryption key that is
675
// not known even to the user. However, this key is masked
676
// with the user input key and stored in the
677
// services.properties file so that, with the user key, the
678
// encryption key can easily be recovered.
679
// To change the user encryption key to a database, simply
680
// recover the unique real encryption key and masked it
681
// with the new user key.
682

683         byte[] generatedKey;
684
685         if (create || newPasswd)
686         {
687             //
688
generatedKey = generateUniqueBytes();
689
690             persistentProperties.put(RawStoreFactory.ENCRYPTED_KEY,
691                                            saveSecretKey(generatedKey, bootPassword));
692
693         }
694         else
695         {
696             generatedKey = getDatabaseSecretKey(properties, bootPassword, SQLState.SERVICE_WRONG_BOOT_PASSWORD);
697         }
698
699         return generatedKey;
700     }
701
702     /*
703      * put all the encyrpion cipger related properties that has to
704      * be made peristent into the database service properties list.
705      * @param properties properties object that is used to store
706      * cipher properties persistently.
707      */

708     public void saveProperties(Properties JavaDoc properties)
709     {
710         // put the cipher properties to be persistent into the
711
// system perisistent properties.
712
for (Enumeration JavaDoc e = persistentProperties.keys();
713              e.hasMoreElements(); )
714         {
715             String JavaDoc key = (String JavaDoc) e.nextElement();
716             properties.put(key, persistentProperties.get(key));
717         }
718
719         // clear the cipher properties to be persistent.
720
persistentProperties = null;
721     }
722
723
724     /**
725         get the secretkey used for encryption and decryption when boot password mechanism is used for encryption
726         Steps include
727         retrieve the stored key, decrypt the stored key and verify if the correct boot password was passed
728         There is a possibility that the decrypted key includes the original key and padded bytes in order to have
729         been block size aligned during encryption phase. Hence extract the original key
730         
731         @param properties properties to retrieve the encrypted key
732         @param bootPassword boot password used to connect to the encrypted database
733         @param errorState errorstate to account for any errors during retrieval /creation of the secretKey
734         @return the original unencrypted key bytes to use for encryption and decrytion
735         
736          */

737     private byte[] getDatabaseSecretKey(Properties JavaDoc properties, byte[] bootPassword, String JavaDoc errorState) throws StandardException {
738
739         // recover the generated secret encryption key from the
740
// services.properties file and the user key.
741
String JavaDoc keyString = properties.getProperty(RawStoreFactory.ENCRYPTED_KEY);
742         if (keyString == null)
743             throw StandardException.newException(errorState);
744
745         int encodedKeyCharLength = keyString.indexOf('-');
746
747         if (encodedKeyCharLength == -1) // bad form
748
throw StandardException.newException(errorState);
749
750         int verifyKey = Integer.parseInt(keyString.substring(encodedKeyCharLength+1));
751         byte[] generatedKey = decryptKey(keyString, encodedKeyCharLength, bootPassword);
752
753         int checkKey = digest(generatedKey);
754
755         if (checkKey != verifyKey)
756             throw StandardException.newException(errorState);
757
758         // if encodedKeyLength is not defined, then either it is an old version with no support for different
759
// key sizes and padding except for defaults
760
byte[] result;
761         if(encodedKeyLength != 0)
762         {
763             result = new byte[encodedKeyLength];
764
765             // extract the generated key without the padding bytes
766
System.arraycopy(generatedKey,0,result,0,encodedKeyLength);
767             return result;
768         }
769
770         return generatedKey;
771     }
772
773     private String JavaDoc saveSecretKey(byte[] secretKey, byte[] bootPassword) throws StandardException {
774         String JavaDoc encryptedKey = encryptKey(secretKey, bootPassword);
775
776         // make a verification key out of the message digest of
777
// the generated key
778
int verifyKey = digest(secretKey);
779
780         return encryptedKey.concat("-" + verifyKey);
781
782     }
783
784     public String JavaDoc changeBootPassword(String JavaDoc changeString, Properties JavaDoc properties, CipherProvider verify)
785         throws StandardException {
786
787         // the new bootPassword is expected to be of the form
788
// oldkey , newkey.
789
int seperator = changeString.indexOf(',');
790         if (seperator == -1)
791             throw StandardException.newException(SQLState.WRONG_PASSWORD_CHANGE_FORMAT);
792
793         String JavaDoc oldBP = changeString.substring(0, seperator).trim();
794         byte[] oldBPAscii = StringUtil.getAsciiBytes(oldBP);
795         if (oldBPAscii == null || oldBPAscii.length < CipherFactory.MIN_BOOTPASS_LENGTH)
796             throw StandardException.newException(SQLState.WRONG_BOOT_PASSWORD);;
797
798         String JavaDoc newBP = changeString.substring(seperator+1).trim();
799         byte[] newBPAscii = StringUtil.getAsciiBytes(newBP);
800         if (newBPAscii == null || newBPAscii.length < CipherFactory.MIN_BOOTPASS_LENGTH)
801             throw StandardException.newException(SQLState.ILLEGAL_BP_LENGTH,
802                 new Integer JavaDoc(CipherFactory.MIN_BOOTPASS_LENGTH));
803
804         // verify old key
805

806         byte[] generatedKey = getDatabaseSecretKey(properties, oldBPAscii, SQLState.WRONG_BOOT_PASSWORD);
807
808         // make sure the oldKey is correct
809
byte[] IV = generateIV(generatedKey);
810
811         if (!((JCECipherProvider) verify).verifyIV(IV))
812             throw StandardException.newException(SQLState.WRONG_BOOT_PASSWORD);
813
814
815         // Make the new key. The generated key is unchanged, only the
816
// encrypted key is changed.
817
String JavaDoc newkey = saveSecretKey(generatedKey, newBPAscii);
818         
819         properties.put(Attribute.CRYPTO_KEY_LENGTH,keyLengthBits+"-"+encodedKeyLength);
820         
821
822         return saveSecretKey(generatedKey, newBPAscii);
823     }
824
825     /**
826         perform actions with privileges enabled.
827      */

828     public final Object JavaDoc run() throws StandardException, InstantiationException JavaDoc, IllegalAccessException JavaDoc {
829
830         try {
831
832             switch(action)
833             {
834                 case 1:
835                     Security.addProvider(
836                     (Provider)(Class.forName(cryptoProvider).newInstance()));
837                     break;
838                 case 2:
839                     // SECURITY PERMISSION - MP1 and/or OP4
840
// depends on the value of activePerms
841
return activeFile.getRandomAccessFile(activePerms);
842                 case 3:
843                     return activeFile.getInputStream();
844
845             }
846
847         } catch (ClassNotFoundException JavaDoc cnfe) {
848             throw StandardException.newException(SQLState.ENCRYPTION_NO_PROVIDER_CLASS,cryptoProvider);
849         }
850         catch(FileNotFoundException JavaDoc fnfe) {
851             throw StandardException.newException(SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,cryptoProvider);
852         }
853         return null;
854     }
855
856
857
858     /**
859         The database can be encrypted with an encryption key given in connection url.
860         For security reasons, this key is not made persistent in the database.
861
862         But it is necessary to verify the encryption key when booting the database if it is similar
863         to the one used when creating the database
864         This needs to happen before we access the data/logs to avoid the risk of corrupting the
865         database because of a wrong encryption key.
866
867         This method performs the steps necessary to verify the encryption key if an external
868         encryption key is given.
869
870         At database creation, 4k of random data is generated using SecureRandom and MD5 is used
871         to compute the checksum for the random data thus generated. This 4k page of random data
872         is then encrypted using the encryption key. The checksum of unencrypted data and
873         encrypted data is made persistent in the database in file by name given by
874         Attribute.CRYPTO_EXTERNAL_KEY_VERIFYFILE (verifyKey.dat). This file exists directly under the
875         database root directory.
876
877         When trying to boot an existing encrypted database, the given encryption key is used to decrypt
878         the data in the verifyKey.dat and the checksum is calculated and compared against the original
879         stored checksum. If these checksums dont match an exception is thrown.
880
881         Please note, this process of verifying the key does not provide any added security but only is
882         intended to allow to fail gracefully if a wrong encryption key is used
883
884         StandardException is thrown if there are any problems during the process of verification
885                 of the encryption key or if there is any mismatch of the encryption key.
886
887      */

888
889     public void verifyKey(boolean create, StorageFactory sf, Properties JavaDoc properties)
890         throws StandardException
891     {
892
893         if(properties.getProperty(Attribute.CRYPTO_EXTERNAL_KEY) == null)
894             return;
895
896         // if firstTime ( ie during creation of database, initial key used )
897
// In order to allow for verifying the external key for future database boot,
898
// generate random 4k of data and store the encrypted random data and the checksum
899
// using MD5 of the unencrypted data. That way, on next database boot a check is performed
900
// to verify if the key is the same as used when the database was created
901

902         InputStream JavaDoc verifyKeyInputStream = null;
903         StorageRandomAccessFile verifyKeyFile = null;
904         byte[] data = new byte[VERIFYKEY_DATALEN];
905         try
906         {
907             if(create)
908             {
909                 getSecureRandom().nextBytes(data);
910                 // get the checksum
911
byte[] checksum = getMD5Checksum(data);
912
913                 CipherProvider tmpCipherProvider = createNewCipher(ENCRYPT,mainSecretKey,mainIV);
914                 tmpCipherProvider.encrypt(data, 0, data.length, data, 0);
915                 // openFileForWrite
916
verifyKeyFile = privAccessFile(sf,Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE,"rw");
917                 // write the checksum length as int, and then the checksum and then the encrypted data
918
verifyKeyFile.writeInt(checksum.length);
919                 verifyKeyFile.write(checksum);
920                 verifyKeyFile.write(data);
921                 verifyKeyFile.sync(true);
922             }
923             else
924             {
925                 // Read from verifyKey.dat as an InputStream. This allows for
926
// reading the information from verifyKey.dat successfully even when using the jar
927
// subprotocol to boot derby. (DERBY-1373)
928
verifyKeyInputStream = privAccessGetInputStream(sf,Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE);
929                 DataInputStream JavaDoc dis = new DataInputStream JavaDoc(verifyKeyInputStream);
930                 // then read the checksum length
931
int checksumLen = dis.readInt();
932
933                 byte[] originalChecksum = new byte[checksumLen];
934                 dis.readFully(originalChecksum);
935
936                 dis.readFully(data);
937
938                 // decrypt data with key
939
CipherProvider tmpCipherProvider = createNewCipher(DECRYPT,mainSecretKey,mainIV);
940                 tmpCipherProvider.decrypt(data, 0, data.length, data, 0);
941
942                 byte[] verifyChecksum = getMD5Checksum(data);
943
944                 if(!MessageDigest.isEqual(originalChecksum,verifyChecksum))
945                 {
946                     throw StandardException.newException(SQLState.ENCRYPTION_BAD_EXTERNAL_KEY);
947                 }
948
949             }
950         }
951         catch(IOException JavaDoc ioe)
952         {
953             throw StandardException.newException(SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,ioe);
954         }
955         finally
956         {
957             try
958             {
959                 if(verifyKeyFile != null)
960                     verifyKeyFile.close();
961                 if (verifyKeyInputStream != null )
962                     verifyKeyInputStream.close();
963             }
964             catch(IOException JavaDoc ioee)
965             {
966                 throw StandardException.newException(SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,ioee);
967             }
968         }
969         return ;
970     }
971
972
973     /**
974         Use MD5 MessageDigest algorithm to generate checksum
975         @param data data to be used to compute the hash value
976         @return returns the hash value computed using the data
977
978      */

979     private byte[] getMD5Checksum(byte[] data)
980         throws StandardException
981     {
982         try
983         {
984             // get the checksum
985
MessageDigest JavaDoc md5 = MessageDigest.getInstance("MD5");
986             return md5.digest(data);
987         }
988         catch(NoSuchAlgorithmException JavaDoc nsae)
989         {
990             throw StandardException.newException(SQLState.ENCRYPTION_BAD_ALG_FORMAT,MESSAGE_DIGEST);
991         }
992
993     }
994
995
996     /**
997         access a file for either read/write
998         @param storageFactory factory used for io access
999         @param fileName name of the file to create and open for write
1000                            The file will be created directly under the database root directory
1001        @param filePerms file permissions, if "rw" open file with read and write permissions
1002                                if "r" , open file with read permissions
1003        @return StorageRandomAccessFile returns file with fileName for writing
1004        @exception IOException Any exception during accessing the file for read/write
1005     */

1006    private StorageRandomAccessFile privAccessFile(StorageFactory storageFactory,String JavaDoc fileName,String JavaDoc filePerms)
1007        throws java.io.IOException JavaDoc
1008    {
1009        StorageFile verifyKeyFile = storageFactory.newStorageFile("",fileName);
1010        activeFile = verifyKeyFile;
1011        this.action = 2;
1012        activePerms = filePerms;
1013        try
1014        {
1015            return (StorageRandomAccessFile)java.security.AccessController.doPrivileged(this);
1016        }
1017        catch( java.security.PrivilegedActionException JavaDoc pae)
1018        {
1019            throw (java.io.IOException JavaDoc)pae.getException();
1020        }
1021    }
1022
1023    /**
1024     access a InputStream for a given file for reading.
1025     @param storageFactory factory used for io access
1026     @param fileName name of the file to open as a stream for reading
1027     @return InputStream returns the stream for the file with fileName for reading
1028     @exception IOException Any exception during accessing the file for read
1029     */

1030    private InputStream JavaDoc privAccessGetInputStream(StorageFactory storageFactory,String JavaDoc fileName)
1031    throws StandardException
1032    {
1033        StorageFile verifyKeyFile = storageFactory.newStorageFile("",fileName);
1034        activeFile = verifyKeyFile;
1035        this.action = 3;
1036        try
1037        {
1038            return (InputStream JavaDoc)java.security.AccessController.doPrivileged(this);
1039        }
1040        catch( java.security.PrivilegedActionException JavaDoc pae)
1041        {
1042            throw (StandardException)pae.getException();
1043        }
1044    }
1045
1046}
1047
Popular Tags