|
1 /* |
|
2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package com.oracle.security.ucrypto; |
|
27 |
|
28 import java.io.ByteArrayOutputStream; |
|
29 import java.nio.ByteBuffer; |
|
30 |
|
31 import java.util.Set; |
|
32 import java.util.Arrays; |
|
33 import java.security.*; |
|
34 import java.security.spec.*; |
|
35 import javax.crypto.*; |
|
36 import javax.crypto.spec.SecretKeySpec; |
|
37 import javax.crypto.spec.GCMParameterSpec; |
|
38 |
|
39 /** |
|
40 * Cipher wrapper class utilizing ucrypto APIs. This class currently supports |
|
41 * - AES/GCM/NoPADDING |
|
42 * |
|
43 * @since 1.9 |
|
44 */ |
|
45 class NativeGCMCipher extends NativeCipher { |
|
46 |
|
47 public static final class AesGcmNoPadding extends NativeGCMCipher { |
|
48 public AesGcmNoPadding() throws NoSuchAlgorithmException { |
|
49 super(-1); |
|
50 } |
|
51 } |
|
52 public static final class Aes128GcmNoPadding extends NativeGCMCipher { |
|
53 public Aes128GcmNoPadding() throws NoSuchAlgorithmException { |
|
54 super(16); |
|
55 } |
|
56 } |
|
57 public static final class Aes192GcmNoPadding extends NativeGCMCipher { |
|
58 public Aes192GcmNoPadding() throws NoSuchAlgorithmException { |
|
59 super(24); |
|
60 } |
|
61 } |
|
62 public static final class Aes256GcmNoPadding extends NativeGCMCipher { |
|
63 public Aes256GcmNoPadding() throws NoSuchAlgorithmException { |
|
64 super(32); |
|
65 } |
|
66 } |
|
67 |
|
68 private static final int DEFAULT_TAG_LEN = 128; // same as SunJCE provider |
|
69 |
|
70 // buffer for storing AAD data; if null, meaning buffer content has been |
|
71 // supplied to native context |
|
72 private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream(); |
|
73 |
|
74 // buffer for storing input in decryption, not used for encryption |
|
75 private ByteArrayOutputStream ibuffer = null; |
|
76 |
|
77 private int tagLen = DEFAULT_TAG_LEN; |
|
78 |
|
79 /* |
|
80 * variables used for performing the GCM (key+iv) uniqueness check. |
|
81 * To use GCM mode safely, the cipher object must be re-initialized |
|
82 * with a different combination of key + iv values for each |
|
83 * ENCRYPTION operation. However, checking all past key + iv values |
|
84 * isn't feasible. Thus, we only do a per-instance check of the |
|
85 * key + iv values used in previous encryption. |
|
86 * For decryption operations, no checking is necessary. |
|
87 */ |
|
88 private boolean requireReinit = false; |
|
89 private byte[] lastEncKey = null; |
|
90 private byte[] lastEncIv = null; |
|
91 |
|
92 NativeGCMCipher(int fixedKeySize) throws NoSuchAlgorithmException { |
|
93 super(UcryptoMech.CRYPTO_AES_GCM, fixedKeySize); |
|
94 } |
|
95 |
|
96 @Override |
|
97 protected void ensureInitialized() { |
|
98 if (!initialized) { |
|
99 if (aadBuffer != null && aadBuffer.size() > 0) { |
|
100 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); |
|
101 aadBuffer = null; |
|
102 } else { |
|
103 init(encrypt, keyValue, iv, tagLen, null); |
|
104 } |
|
105 if (!initialized) { |
|
106 throw new UcryptoException("Cannot initialize Cipher"); |
|
107 } |
|
108 } |
|
109 } |
|
110 |
|
111 @Override |
|
112 protected int getOutputSizeByOperation(int inLen, boolean isDoFinal) { |
|
113 if (inLen < 0) return 0; |
|
114 |
|
115 if (!isDoFinal && (inLen == 0)) { |
|
116 return 0; |
|
117 } |
|
118 |
|
119 int result = inLen + bytesBuffered; |
|
120 if (encrypt) { |
|
121 if (isDoFinal) { |
|
122 result += tagLen/8; |
|
123 } |
|
124 } else { |
|
125 if (ibuffer != null) { |
|
126 result += ibuffer.size(); |
|
127 } |
|
128 if (isDoFinal) { |
|
129 result -= tagLen/8; |
|
130 } |
|
131 } |
|
132 if (result < 0) { |
|
133 result = 0; |
|
134 } |
|
135 return result; |
|
136 } |
|
137 |
|
138 @Override |
|
139 protected void reset(boolean doCancel) { |
|
140 super.reset(doCancel); |
|
141 if (aadBuffer == null) { |
|
142 aadBuffer = new ByteArrayOutputStream(); |
|
143 } else { |
|
144 aadBuffer.reset(); |
|
145 } |
|
146 |
|
147 if (ibuffer != null) { |
|
148 ibuffer.reset(); |
|
149 } |
|
150 if (!encrypt) requireReinit = false; |
|
151 } |
|
152 |
|
153 // actual init() implementation - caller should clone key and iv if needed |
|
154 protected void init(boolean encrypt, byte[] keyVal, byte[] ivVal, int tLen, byte[] aad) { |
|
155 reset(true); |
|
156 this.encrypt = encrypt; |
|
157 this.keyValue = keyVal; |
|
158 this.iv = ivVal; |
|
159 long pCtxtVal = NativeCipher.nativeInit(mech.value(), encrypt, keyValue, iv, |
|
160 tLen, aad); |
|
161 initialized = (pCtxtVal != 0L); |
|
162 if (initialized) { |
|
163 pCtxt = new CipherContextRef(this, pCtxtVal, encrypt); |
|
164 } else { |
|
165 throw new UcryptoException("Cannot initialize Cipher"); |
|
166 } |
|
167 } |
|
168 |
|
169 // see JCE spec |
|
170 @Override |
|
171 protected synchronized AlgorithmParameters engineGetParameters() { |
|
172 AlgorithmParameters params = null; |
|
173 try { |
|
174 if (iv != null) { |
|
175 GCMParameterSpec gcmSpec = new GCMParameterSpec(tagLen, iv.clone()); |
|
176 params = AlgorithmParameters.getInstance("GCM"); |
|
177 params.init(gcmSpec); |
|
178 } |
|
179 } catch (GeneralSecurityException e) { |
|
180 // NoSuchAlgorithmException, NoSuchProviderException |
|
181 // InvalidParameterSpecException |
|
182 throw new UcryptoException("Could not encode parameters", e); |
|
183 } |
|
184 return params; |
|
185 } |
|
186 |
|
187 // see JCE spec |
|
188 @Override |
|
189 protected synchronized void engineInit(int opmode, Key key, |
|
190 AlgorithmParameterSpec params, SecureRandom random) |
|
191 throws InvalidKeyException, InvalidAlgorithmParameterException { |
|
192 checkKey(key); |
|
193 if (opmode != Cipher.ENCRYPT_MODE && |
|
194 opmode != Cipher.DECRYPT_MODE && |
|
195 opmode != Cipher.WRAP_MODE && |
|
196 opmode != Cipher.UNWRAP_MODE) { |
|
197 throw new InvalidAlgorithmParameterException |
|
198 ("Unsupported mode: " + opmode); |
|
199 } |
|
200 boolean doEncrypt = (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE); |
|
201 byte[] keyBytes = key.getEncoded().clone(); |
|
202 byte[] ivBytes = null; |
|
203 if (params != null) { |
|
204 if (!(params instanceof GCMParameterSpec)) { |
|
205 throw new InvalidAlgorithmParameterException("GCMParameterSpec required"); |
|
206 } else { |
|
207 tagLen = ((GCMParameterSpec) params).getTLen(); |
|
208 ivBytes = ((GCMParameterSpec) params).getIV(); |
|
209 } |
|
210 } else { |
|
211 if (doEncrypt) { |
|
212 tagLen = DEFAULT_TAG_LEN; |
|
213 |
|
214 // generate IV if none supplied for encryption |
|
215 ivBytes = new byte[blockSize]; |
|
216 new SecureRandom().nextBytes(ivBytes); |
|
217 } else { |
|
218 throw new InvalidAlgorithmParameterException("Parameters required for decryption"); |
|
219 } |
|
220 } |
|
221 if (doEncrypt) { |
|
222 requireReinit = Arrays.equals(ivBytes, lastEncIv) && |
|
223 Arrays.equals(keyBytes, lastEncKey); |
|
224 if (requireReinit) { |
|
225 throw new InvalidAlgorithmParameterException |
|
226 ("Cannot reuse iv for GCM encryption"); |
|
227 } |
|
228 lastEncIv = ivBytes; |
|
229 lastEncKey = keyBytes; |
|
230 } else { |
|
231 requireReinit = false; |
|
232 ibuffer = new ByteArrayOutputStream(); |
|
233 } |
|
234 init(doEncrypt, keyBytes, ivBytes, tagLen, null); |
|
235 } |
|
236 |
|
237 // see JCE spec |
|
238 @Override |
|
239 protected synchronized void engineInit(int opmode, Key key, AlgorithmParameters params, |
|
240 SecureRandom random) |
|
241 throws InvalidKeyException, InvalidAlgorithmParameterException { |
|
242 AlgorithmParameterSpec spec = null; |
|
243 if (params != null) { |
|
244 try { |
|
245 // mech must be UcryptoMech.CRYPTO_AES_GCM |
|
246 spec = params.getParameterSpec(GCMParameterSpec.class); |
|
247 } catch (InvalidParameterSpecException iaps) { |
|
248 throw new InvalidAlgorithmParameterException(iaps); |
|
249 } |
|
250 } |
|
251 engineInit(opmode, key, spec, random); |
|
252 } |
|
253 |
|
254 // see JCE spec |
|
255 @Override |
|
256 protected synchronized byte[] engineUpdate(byte[] in, int inOfs, int inLen) { |
|
257 if (aadBuffer != null && aadBuffer.size() > 0) { |
|
258 // init again with AAD data |
|
259 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); |
|
260 aadBuffer = null; |
|
261 } |
|
262 if (requireReinit) { |
|
263 throw new IllegalStateException |
|
264 ("Must use either different key or iv for GCM encryption"); |
|
265 } |
|
266 if (inLen > 0) { |
|
267 if (!encrypt) { |
|
268 ibuffer.write(in, inOfs, inLen); |
|
269 return null; |
|
270 } |
|
271 return super.engineUpdate(in, inOfs, inLen); |
|
272 } else return null; |
|
273 } |
|
274 |
|
275 // see JCE spec |
|
276 @Override |
|
277 protected synchronized int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out, |
|
278 int outOfs) throws ShortBufferException { |
|
279 int len = getOutputSizeByOperation(inLen, false); |
|
280 if (out.length - outOfs < len) { |
|
281 throw new ShortBufferException("Output buffer must be " |
|
282 + "(at least) " + len |
|
283 + " bytes long"); |
|
284 } |
|
285 if (aadBuffer != null && aadBuffer.size() > 0) { |
|
286 // init again with AAD data |
|
287 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); |
|
288 aadBuffer = null; |
|
289 } |
|
290 if (requireReinit) { |
|
291 throw new IllegalStateException |
|
292 ("Must use either different key or iv for GCM encryption"); |
|
293 } |
|
294 if (inLen > 0) { |
|
295 if (!encrypt) { |
|
296 ibuffer.write(in, inOfs, inLen); |
|
297 return 0; |
|
298 } else { |
|
299 return super.engineUpdate(in, inOfs, inLen, out, outOfs); |
|
300 } |
|
301 } |
|
302 return 0; |
|
303 } |
|
304 |
|
305 // see JCE spec |
|
306 @Override |
|
307 protected synchronized void engineUpdateAAD(byte[] src, int srcOfs, int srcLen) |
|
308 throws IllegalStateException { |
|
309 |
|
310 if ((src == null) || (srcOfs < 0) || (srcOfs + srcLen > src.length)) { |
|
311 throw new IllegalArgumentException("Invalid AAD"); |
|
312 } |
|
313 if (keyValue == null) { |
|
314 throw new IllegalStateException("Need to initialize Cipher first"); |
|
315 } |
|
316 if (requireReinit) { |
|
317 throw new IllegalStateException |
|
318 ("Must use either different key or iv for GCM encryption"); |
|
319 } |
|
320 if (aadBuffer != null) { |
|
321 aadBuffer.write(src, srcOfs, srcLen); |
|
322 } else { |
|
323 // update has already been called |
|
324 throw new IllegalStateException |
|
325 ("Update has been called; no more AAD data"); |
|
326 } |
|
327 } |
|
328 |
|
329 // see JCE spec |
|
330 @Override |
|
331 protected void engineUpdateAAD(ByteBuffer src) |
|
332 throws IllegalStateException { |
|
333 if (src == null) { |
|
334 throw new IllegalArgumentException("Invalid AAD"); |
|
335 } |
|
336 if (keyValue == null) { |
|
337 throw new IllegalStateException("Need to initialize Cipher first"); |
|
338 } |
|
339 if (requireReinit) { |
|
340 throw new IllegalStateException |
|
341 ("Must use either different key or iv for GCM encryption"); |
|
342 } |
|
343 if (aadBuffer != null) { |
|
344 if (src.hasRemaining()) { |
|
345 byte[] srcBytes = new byte[src.remaining()]; |
|
346 src.get(srcBytes); |
|
347 aadBuffer.write(srcBytes, 0, srcBytes.length); |
|
348 } |
|
349 } else { |
|
350 // update has already been called |
|
351 throw new IllegalStateException |
|
352 ("Update has been called; no more AAD data"); |
|
353 } |
|
354 } |
|
355 |
|
356 // see JCE spec |
|
357 @Override |
|
358 protected synchronized byte[] engineDoFinal(byte[] in, int inOfs, int inLen) |
|
359 throws IllegalBlockSizeException, BadPaddingException { |
|
360 byte[] out = new byte[getOutputSizeByOperation(inLen, true)]; |
|
361 try { |
|
362 // delegate to the other engineDoFinal(...) method |
|
363 int k = engineDoFinal(in, inOfs, inLen, out, 0); |
|
364 if (out.length != k) { |
|
365 out = Arrays.copyOf(out, k); |
|
366 } |
|
367 return out; |
|
368 } catch (ShortBufferException e) { |
|
369 throw new UcryptoException("Internal Error", e); |
|
370 } |
|
371 } |
|
372 |
|
373 // see JCE spec |
|
374 @Override |
|
375 protected synchronized int engineDoFinal(byte[] in, int inOfs, int inLen, |
|
376 byte[] out, int outOfs) |
|
377 throws ShortBufferException, IllegalBlockSizeException, |
|
378 BadPaddingException { |
|
379 int len = getOutputSizeByOperation(inLen, true); |
|
380 if (out.length - outOfs < len) { |
|
381 throw new ShortBufferException("Output buffer must be " |
|
382 + "(at least) " + len |
|
383 + " bytes long"); |
|
384 } |
|
385 if (aadBuffer != null && aadBuffer.size() > 0) { |
|
386 // init again with AAD data |
|
387 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); |
|
388 aadBuffer = null; |
|
389 } |
|
390 if (requireReinit) { |
|
391 throw new IllegalStateException |
|
392 ("Must use either different key or iv for GCM encryption"); |
|
393 } |
|
394 if (!encrypt) { |
|
395 if (inLen > 0) { |
|
396 ibuffer.write(in, inOfs, inLen); |
|
397 } |
|
398 inLen = ibuffer.size(); |
|
399 if (inLen < tagLen/8) { |
|
400 // Otherwise, Solaris lib will error out w/ CRYPTO_BUFFER_TOO_SMALL |
|
401 // when ucrypto_decrypt_final() is called |
|
402 throw new AEADBadTagException("Input too short - need tag"); |
|
403 } |
|
404 // refresh 'in' to all buffered-up bytes |
|
405 in = ibuffer.toByteArray(); |
|
406 inOfs = 0; |
|
407 ibuffer.reset(); |
|
408 } |
|
409 try { |
|
410 return super.engineDoFinal(in, inOfs, inLen, out, outOfs); |
|
411 } catch (UcryptoException ue) { |
|
412 if (ue.getMessage().equals("CRYPTO_INVALID_MAC")) { |
|
413 throw new AEADBadTagException("Tag does not match"); |
|
414 } else { |
|
415 // pass it up |
|
416 throw ue; |
|
417 } |
|
418 } finally { |
|
419 requireReinit = encrypt; |
|
420 } |
|
421 } |
|
422 } |