1 | /* |
2 | |
3 | Derby - Class org.apache.derby.impl.services.jce.JCECipherProvider |
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 | |
21 | package org.apache.derby.impl.services.jce; |
22 | |
23 | import org.apache.derby.iapi.services.crypto.CipherFactory; |
24 | import org.apache.derby.iapi.services.crypto.CipherProvider; |
25 | import org.apache.derby.iapi.services.sanity.SanityManager; |
26 | |
27 | import org.apache.derby.iapi.error.StandardException; |
28 | import org.apache.derby.iapi.reference.SQLState; |
29 | |
30 | import java.security.Key; |
31 | import java.security.InvalidKeyException; |
32 | import java.security.NoSuchAlgorithmException; |
33 | import java.security.GeneralSecurityException; |
34 | import java.security.NoSuchProviderException; |
35 | |
36 | import javax.crypto.Cipher; |
37 | import javax.crypto.spec.IvParameterSpec; |
38 | import javax.crypto.SecretKeyFactory; |
39 | import javax.crypto.spec.SecretKeySpec; |
40 | import javax.crypto.SecretKey; |
41 | |
42 | |
43 | /** |
44 | This is a wrapper for a Cipher |
45 | |
46 | @see CipherFactory |
47 | */ |
48 | class JCECipherProvider implements CipherProvider |
49 | { |
50 | private Cipher cipher; |
51 | private int mode; |
52 | private boolean ivUsed = true; |
53 | private final IvParameterSpec ivspec; |
54 | private final int encryptionBlockSize; |
55 | private boolean sunjce; //default of bool is false |
56 | |
57 | // other provider workaround, we need to re-init the cipher before every encrypt/decrypt |
58 | private SecretKey cryptixKey; |
59 | |
60 | JCECipherProvider(int mode, SecretKey secretKey, byte[] iv, String algorithm, String provider) |
61 | throws StandardException |
62 | { |
63 | Throwable t; |
64 | ivspec = new IvParameterSpec(iv); |
65 | try |
66 | { |
67 | |
68 | |
69 | if (provider == null) |
70 | { |
71 | cipher = Cipher.getInstance(algorithm); |
72 | |
73 | // see below. |
74 | if ("SunJCE".equals(cipher.getProvider().getName())) |
75 | sunjce = true; |
76 | } |
77 | else |
78 | { |
79 | /* The Sun encryption provider does not need to re-init the cipher |
80 | * after each encrypt/decrypt. This is a speed up trick. |
81 | * Other crypto providers needs this because the encrypt/decrypt |
82 | * ciphers becomes out of sync after an encrypt/decrypt operation. |
83 | */ |
84 | if( provider.equals("SunJCE") ) |
85 | { |
86 | sunjce = true; |
87 | } |
88 | else |
89 | { |
90 | /* The BouncyCastle encryption provider is named "BC". |
91 | * The full "BouncyCastleProvider" name used to work until |
92 | * version 103 came out. (ie. Beta3 and Beta4 works fine) |
93 | * This trick is so that Cipher.getInstance(algo, prov) will |
94 | * not throw an exception. Resolve 3765. |
95 | */ |
96 | if( provider.equals( "BouncyCastleProvider" ) ) |
97 | provider = "BC"; |
98 | } |
99 | |
100 | cipher = Cipher.getInstance(algorithm,provider); |
101 | } |
102 | |
103 | // At creation time, the encryption block size is stored in order |
104 | // to do appropriate padding |
105 | encryptionBlockSize = cipher.getBlockSize(); |
106 | |
107 | this.mode = mode; |
108 | try { |
109 | |
110 | // ECB feedback mode does not require an IV |
111 | if (mode == CipherFactory.ENCRYPT) |
112 | { |
113 | if ((algorithm.indexOf("/ECB") > -1)) |
114 | cipher.init(Cipher.ENCRYPT_MODE, secretKey); |
115 | else |
116 | cipher.init(Cipher.ENCRYPT_MODE, secretKey,ivspec); |
117 | } |
118 | else if (mode == CipherFactory.DECRYPT) |
119 | { |
120 | if ((algorithm.indexOf("/ECB") > -1)) |
121 | cipher.init(Cipher.DECRYPT_MODE, secretKey); |
122 | else |
123 | cipher.init(Cipher.DECRYPT_MODE, secretKey,ivspec); |
124 | } |
125 | else |
126 | throw StandardException.newException(SQLState.ILLEGAL_CIPHER_MODE); |
127 | } catch (InvalidKeyException ike) { |
128 | |
129 | if (algorithm.startsWith("DES")) { |
130 | |
131 | SecretKeyFactory skf; |
132 | if (provider == null) |
133 | skf = SecretKeyFactory.getInstance(secretKey.getAlgorithm()); |
134 | else |
135 | skf = SecretKeyFactory.getInstance(secretKey.getAlgorithm(), provider); |
136 | |
137 | |
138 | // Since the key may be a series of bytes generated by an arbitary means |
139 | // we need to translate it into a key suitable for the algorithm. |
140 | secretKey = skf.translateKey(new SecretKeySpec(secretKey.getEncoded(), secretKey.getAlgorithm())); |
141 | |
142 | // ECB mode does not require IV |
143 | if (mode == CipherFactory.ENCRYPT ) |
144 | { |
145 | if ((algorithm.indexOf("/ECB") > -1)) |
146 | cipher.init(Cipher.ENCRYPT_MODE, secretKey); |
147 | else |
148 | cipher.init(Cipher.ENCRYPT_MODE, secretKey,ivspec); |
149 | } |
150 | else if (mode == CipherFactory.DECRYPT) |
151 | { |
152 | if ((algorithm.indexOf("/ECB") > -1)) |
153 | cipher.init(Cipher.DECRYPT_MODE, secretKey); |
154 | else |
155 | cipher.init(Cipher.DECRYPT_MODE, secretKey,ivspec); |
156 | } |
157 | |
158 | } |
159 | else |
160 | throw StandardException.newException(SQLState.CRYPTO_EXCEPTION, ike); |
161 | } |
162 | cryptixKey = secretKey; |
163 | |
164 | if (cipher.getIV() == null) |
165 | ivUsed = false; |
166 | |
167 | if (SanityManager.DEBUG) |
168 | SanityManager.ASSERT(verifyIV(iv)); |
169 | |
170 | return; |
171 | |
172 | } |
173 | catch (InvalidKeyException ike) |
174 | { |
175 | t = ike; |
176 | } |
177 | catch (NoSuchAlgorithmException nsae) |
178 | { |
179 | throw StandardException.newException(SQLState.ENCRYPTION_NOSUCH_ALGORITHM, algorithm, JCECipherFactory.providerErrorName(provider)); |
180 | } |
181 | catch (NoSuchProviderException nspe) |
182 | { |
183 | throw StandardException.newException(SQLState.ENCRYPTION_BAD_PROVIDER, JCECipherFactory.providerErrorName(provider)); |
184 | } |
185 | catch (GeneralSecurityException gse) |
186 | { |
187 | t = gse; |
188 | } |
189 | throw StandardException.newException(SQLState.CRYPTO_EXCEPTION, t); |
190 | |
191 | } |
192 | |
193 | /** |
194 | @see CipherProvider#encrypt |
195 | |
196 | @exception StandardException Standard Cloudscape Error Policy |
197 | */ |
198 | public int encrypt(byte[] cleartext, int offset, int length, |
199 | byte[] ciphertext, int outputOffset) |
200 | throws StandardException |
201 | { |
202 | if (SanityManager.DEBUG) |
203 | { |
204 | SanityManager.ASSERT(mode == CipherFactory.ENCRYPT, |
205 | "calling encrypt on a decryption engine"); |
206 | SanityManager.ASSERT(cleartext != null, "encrypting null cleartext"); |
207 | SanityManager.ASSERT(offset >= 0, "offset < 0"); |
208 | SanityManager.ASSERT(length > 0, "length <= 0"); |
209 | SanityManager.ASSERT(offset+length <= cleartext.length, |
210 | "offset+length > cleartext.length"); |
211 | SanityManager.ASSERT(length <= ciphertext.length-outputOffset, |
212 | "provided ciphertext buffer insufficient"); |
213 | } |
214 | |
215 | int retval = 0; |
216 | try |
217 | { |
218 | // this same cipher is shared across the entire raw store, make it |
219 | // MT safe |
220 | synchronized(this) |
221 | { |
222 | if( !sunjce ) |
223 | { |
224 | // this code is a workaround for other providers |
225 | try |
226 | { |
227 | //ivspec = new IvParameterSpec(cipher.getIV()); |
228 | if (mode == CipherFactory.ENCRYPT) |
229 | { |
230 | if (ivUsed) |
231 | cipher.init(Cipher.ENCRYPT_MODE, cryptixKey, ivspec); |
232 | else |
233 | cipher.init(Cipher.ENCRYPT_MODE,cryptixKey); |
234 | } |
235 | else if (mode == CipherFactory.DECRYPT) |
236 | { |
237 | if (ivUsed) |
238 | cipher.init(Cipher.DECRYPT_MODE, cryptixKey, ivspec); |
239 | else |
240 | cipher.init(Cipher.DECRYPT_MODE, cryptixKey); |
241 | } |
242 | |
243 | } |
244 | catch (InvalidKeyException ike) |
245 | { |
246 | System.out.println("A " + ike); |
247 | throw StandardException.newException(SQLState.CRYPTO_EXCEPTION, ike); |
248 | } |
249 | } |
250 | |
251 | retval = cipher.doFinal(cleartext, offset, length, ciphertext, outputOffset); |
252 | } |
253 | } |
254 | catch (IllegalStateException ise) |
255 | { |
256 | // should never happen |
257 | if (SanityManager.DEBUG) |
258 | SanityManager.THROWASSERT("Illegal state exception"); |
259 | } |
260 | catch (GeneralSecurityException gse) |
261 | { |
262 | System.out.println("B " + gse); |
263 | throw StandardException.newException(SQLState.CRYPTO_EXCEPTION, gse); |
264 | } |
265 | |
266 | if (SanityManager.DEBUG) |
267 | SanityManager.ASSERT(retval == length, "ciphertext length != length"); |
268 | |
269 | return retval; |
270 | } |
271 | |
272 | |
273 | /** |
274 | @see CipherProvider#decrypt |
275 | |
276 | @exception StandardException Standard Cloudscape Error Policy |
277 | */ |
278 | public int decrypt(byte[] ciphertext, int offset, int length, |
279 | byte[] cleartext, int outputOffset) |
280 | throws StandardException |
281 | { |
282 | if (SanityManager.DEBUG) |
283 | { |
284 | SanityManager.ASSERT(mode == CipherFactory.DECRYPT, |
285 | "calling decrypt on a encryption engine"); |
286 | SanityManager.ASSERT(ciphertext != null, "decrypting null ciphertext"); |
287 | SanityManager.ASSERT(offset >= 0, "offset < 0"); |
288 | SanityManager.ASSERT(length > 0, "length <= 0"); |
289 | SanityManager.ASSERT(offset+length <= ciphertext.length, |
290 | "offset+length > ciphertext.length"); |
291 | SanityManager.ASSERT(length <= cleartext.length-outputOffset, |
292 | "provided cleartexte buffer insufficient"); |
293 | } |
294 | |
295 | int retval = 0; |
296 | try |
297 | { |
298 | // this same cipher is shared across the entire raw store, make it |
299 | // MT safe |
300 | synchronized(this) |
301 | { |
302 | if( !sunjce ) |
303 | { |
304 | // this code is a workaround for other providers |
305 | try |
306 | { |
307 | //ivspec = new IvParameterSpec(cipher.getIV()); |
308 | |
309 | if (mode == CipherFactory.ENCRYPT) |
310 | { |
311 | if (ivUsed) |
312 | cipher.init(Cipher.ENCRYPT_MODE, cryptixKey, ivspec); |
313 | else |
314 | cipher.init(Cipher.ENCRYPT_MODE,cryptixKey); |
315 | } |
316 | else if (mode == CipherFactory.DECRYPT) |
317 | { |
318 | if (ivUsed) |
319 | cipher.init(Cipher.DECRYPT_MODE, cryptixKey, ivspec); |
320 | else |
321 | cipher.init(Cipher.DECRYPT_MODE, cryptixKey); |
322 | } |
323 | |
324 | } |
325 | catch (InvalidKeyException ike) |
326 | { |
327 | System.out.println("C " + ike); |
328 | throw StandardException.newException(SQLState.CRYPTO_EXCEPTION, ike); |
329 | } |
330 | |
331 | } |
332 | |
333 | retval = cipher.doFinal(ciphertext, offset, length, cleartext, outputOffset); |
334 | } |
335 | } |
336 | catch (IllegalStateException ise) |
337 | { |
338 | // should never happen |
339 | if (SanityManager.DEBUG) |
340 | SanityManager.THROWASSERT("Illegal state exception"); |
341 | } |
342 | catch (GeneralSecurityException gse) |
343 | { |
344 | System.out.println("D " + gse); |
345 | throw StandardException.newException(SQLState.CRYPTO_EXCEPTION, gse); |
346 | } |
347 | |
348 | if (SanityManager.DEBUG) |
349 | { |
350 | SanityManager.ASSERT(retval == length, |
351 | "cleartext length != length"); |
352 | } |
353 | |
354 | return retval; |
355 | } |
356 | |
357 | boolean verifyIV(byte[] IV) |
358 | { |
359 | byte[] myIV = cipher.getIV(); |
360 | // null IV is OK only if IV is not used |
361 | if (myIV == null) |
362 | return !ivUsed; |
363 | if (myIV.length != IV.length) |
364 | return false; |
365 | for (int i = 0; i < IV.length; i++) |
366 | if (myIV[i] != IV[i]) |
367 | return false; |
368 | return true; |
369 | } |
370 | |
371 | public int getEncryptionBlockSize() |
372 | { |
373 | return encryptionBlockSize; |
374 | } |
375 | } |