1 /* 2 * Copyright (c) 2014, 2015, 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 public AesGcmNoPadding(int keySize) throws NoSuchAlgorithmException { 52 super(keySize); 53 } 54 } 55 56 private static final int DEFAULT_TAG_LEN = 128; // same as SunJCE provider 57 58 // buffer for storing AAD data; if null, meaning buffer content has been 59 // supplied to native context 60 private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream(); 61 62 // buffer for storing input in decryption, not used for encryption 63 private ByteArrayOutputStream ibuffer = null; 64 65 private int tagLen = DEFAULT_TAG_LEN; 66 67 /* 68 * variables used for performing the GCM (key+iv) uniqueness check. 69 * To use GCM mode safely, the cipher object must be re-initialized 70 * with a different combination of key + iv values for each 71 * ENCRYPTION operation. However, checking all past key + iv values 72 * isn't feasible. Thus, we only do a per-instance check of the 73 * key + iv values used in previous encryption. 74 * For decryption operations, no checking is necessary. 75 */ 76 private boolean requireReinit = false; 77 private byte[] lastEncKey = null; 78 private byte[] lastEncIv = null; 79 80 NativeGCMCipher(int fixedKeySize) throws NoSuchAlgorithmException { 81 super(UcryptoMech.CRYPTO_AES_GCM, fixedKeySize); 82 } 83 84 @Override 85 protected void ensureInitialized() { 86 if (!initialized) { 87 if (aadBuffer != null && aadBuffer.size() > 0) { 88 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); 89 aadBuffer = null; 90 } else { 91 init(encrypt, keyValue, iv, tagLen, null); 92 } 93 if (!initialized) { 94 throw new UcryptoException("Cannot initialize Cipher"); 95 } 96 } 97 } 98 99 @Override 100 protected int getOutputSizeByOperation(int inLen, boolean isDoFinal) { 101 if (inLen < 0) return 0; 102 103 if (!isDoFinal && (inLen == 0)) { 104 return 0; 105 } 106 107 int result = inLen + bytesBuffered; 108 if (encrypt) { 109 if (isDoFinal) { 110 result += tagLen/8; 111 } 112 } else { 113 if (ibuffer != null) { 114 result += ibuffer.size(); 115 } 116 if (isDoFinal) { 117 result -= tagLen/8; 118 } 119 } 120 if (result < 0) { 121 result = 0; 122 } 123 return result; 124 } 125 126 @Override 127 protected void reset(boolean doCancel) { 128 super.reset(doCancel); 129 if (aadBuffer == null) { 130 aadBuffer = new ByteArrayOutputStream(); 131 } else { 132 aadBuffer.reset(); 133 } 134 135 if (ibuffer != null) { 136 ibuffer.reset(); 137 } 138 if (!encrypt) requireReinit = false; 139 } 140 141 // actual init() implementation - caller should clone key and iv if needed 142 protected void init(boolean encrypt, byte[] keyVal, byte[] ivVal, int tLen, byte[] aad) { 143 reset(true); 144 this.encrypt = encrypt; 145 this.keyValue = keyVal; 146 this.iv = ivVal; 147 long pCtxtVal = NativeCipher.nativeInit(mech.value(), encrypt, keyValue, iv, 148 tLen, aad); 149 initialized = (pCtxtVal != 0L); 150 if (initialized) { 151 pCtxt = new CipherContextRef(this, pCtxtVal, encrypt); 152 } else { 153 throw new UcryptoException("Cannot initialize Cipher"); 154 } 155 } 156 157 // see JCE spec 158 @Override 159 protected synchronized AlgorithmParameters engineGetParameters() { 160 AlgorithmParameters params = null; 161 try { 162 if (iv != null) { 163 GCMParameterSpec gcmSpec = new GCMParameterSpec(tagLen, iv.clone()); 164 params = AlgorithmParameters.getInstance("GCM"); 165 params.init(gcmSpec); 166 } 167 } catch (GeneralSecurityException e) { 168 // NoSuchAlgorithmException, NoSuchProviderException 169 // InvalidParameterSpecException 170 throw new UcryptoException("Could not encode parameters", e); 171 } 172 return params; 173 } 174 175 // see JCE spec 176 @Override 177 protected synchronized void engineInit(int opmode, Key key, 178 AlgorithmParameterSpec params, SecureRandom random) 179 throws InvalidKeyException, InvalidAlgorithmParameterException { 180 checkKey(key); 181 if (opmode != Cipher.ENCRYPT_MODE && 182 opmode != Cipher.DECRYPT_MODE && 183 opmode != Cipher.WRAP_MODE && 184 opmode != Cipher.UNWRAP_MODE) { 185 throw new InvalidAlgorithmParameterException 186 ("Unsupported mode: " + opmode); 187 } 188 boolean doEncrypt = (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE); 189 byte[] keyBytes = key.getEncoded().clone(); 190 byte[] ivBytes = null; 191 if (params != null) { 192 if (!(params instanceof GCMParameterSpec)) { 193 throw new InvalidAlgorithmParameterException("GCMParameterSpec required"); 194 } else { 195 tagLen = ((GCMParameterSpec) params).getTLen(); 196 ivBytes = ((GCMParameterSpec) params).getIV(); 197 } 198 } else { 199 if (doEncrypt) { 200 tagLen = DEFAULT_TAG_LEN; 201 202 // generate IV if none supplied for encryption 203 ivBytes = new byte[blockSize]; 204 new SecureRandom().nextBytes(ivBytes); 205 } else { 206 throw new InvalidAlgorithmParameterException("Parameters required for decryption"); 207 } 208 } 209 if (doEncrypt) { 210 requireReinit = Arrays.equals(ivBytes, lastEncIv) && 211 Arrays.equals(keyBytes, lastEncKey); 212 if (requireReinit) { 213 throw new InvalidAlgorithmParameterException 214 ("Cannot reuse iv for GCM encryption"); 215 } 216 lastEncIv = ivBytes; 217 lastEncKey = keyBytes; 218 } else { 219 requireReinit = false; 220 ibuffer = new ByteArrayOutputStream(); 221 } 222 init(doEncrypt, keyBytes, ivBytes, tagLen, null); 223 } 224 225 // see JCE spec 226 @Override 227 protected synchronized void engineInit(int opmode, Key key, AlgorithmParameters params, 228 SecureRandom random) 229 throws InvalidKeyException, InvalidAlgorithmParameterException { 230 AlgorithmParameterSpec spec = null; 231 if (params != null) { 232 try { 233 // mech must be UcryptoMech.CRYPTO_AES_GCM 234 spec = params.getParameterSpec(GCMParameterSpec.class); 235 } catch (InvalidParameterSpecException iaps) { 236 throw new InvalidAlgorithmParameterException(iaps); 237 } 238 } 239 engineInit(opmode, key, spec, random); 240 } 241 242 // see JCE spec 243 @Override 244 protected synchronized byte[] engineUpdate(byte[] in, int inOfs, int inLen) { 245 if (aadBuffer != null && aadBuffer.size() > 0) { 246 // init again with AAD data 247 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); 248 aadBuffer = null; 249 } 250 if (requireReinit) { 251 throw new IllegalStateException 252 ("Must use either different key or iv for GCM encryption"); 253 } 254 if (inLen > 0) { 255 if (!encrypt) { 256 ibuffer.write(in, inOfs, inLen); 257 return null; 258 } 259 return super.engineUpdate(in, inOfs, inLen); 260 } else return null; 261 } 262 263 // see JCE spec 264 @Override 265 protected synchronized int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out, 266 int outOfs) throws ShortBufferException { 267 int len = getOutputSizeByOperation(inLen, false); 268 if (out.length - outOfs < len) { 269 throw new ShortBufferException("Output buffer must be " 270 + "(at least) " + len 271 + " bytes long"); 272 } 273 if (aadBuffer != null && aadBuffer.size() > 0) { 274 // init again with AAD data 275 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); 276 aadBuffer = null; 277 } 278 if (requireReinit) { 279 throw new IllegalStateException 280 ("Must use either different key or iv for GCM encryption"); 281 } 282 if (inLen > 0) { 283 if (!encrypt) { 284 ibuffer.write(in, inOfs, inLen); 285 return 0; 286 } else { 287 return super.engineUpdate(in, inOfs, inLen, out, outOfs); 288 } 289 } 290 return 0; 291 } 292 293 // see JCE spec 294 @Override 295 protected synchronized void engineUpdateAAD(byte[] src, int srcOfs, int srcLen) 296 throws IllegalStateException { 297 298 if ((src == null) || (srcOfs < 0) || (srcOfs + srcLen > src.length)) { 299 throw new IllegalArgumentException("Invalid AAD"); 300 } 301 if (keyValue == null) { 302 throw new IllegalStateException("Need to initialize Cipher first"); 303 } 304 if (requireReinit) { 305 throw new IllegalStateException 306 ("Must use either different key or iv for GCM encryption"); 307 } 308 if (aadBuffer != null) { 309 aadBuffer.write(src, srcOfs, srcLen); 310 } else { 311 // update has already been called 312 throw new IllegalStateException 313 ("Update has been called; no more AAD data"); 314 } 315 } 316 317 // see JCE spec 318 @Override 319 protected void engineUpdateAAD(ByteBuffer src) 320 throws IllegalStateException { 321 if (src == null) { 322 throw new IllegalArgumentException("Invalid AAD"); 323 } 324 if (keyValue == null) { 325 throw new IllegalStateException("Need to initialize Cipher first"); 326 } 327 if (requireReinit) { 328 throw new IllegalStateException 329 ("Must use either different key or iv for GCM encryption"); 330 } 331 if (aadBuffer != null) { 332 if (src.hasRemaining()) { 333 byte[] srcBytes = new byte[src.remaining()]; 334 src.get(srcBytes); 335 aadBuffer.write(srcBytes, 0, srcBytes.length); 336 } 337 } else { 338 // update has already been called 339 throw new IllegalStateException 340 ("Update has been called; no more AAD data"); 341 } 342 } 343 344 // see JCE spec 345 @Override 346 protected synchronized byte[] engineDoFinal(byte[] in, int inOfs, int inLen) 347 throws IllegalBlockSizeException, BadPaddingException { 348 byte[] out = new byte[getOutputSizeByOperation(inLen, true)]; 349 try { 350 // delegate to the other engineDoFinal(...) method 351 int k = engineDoFinal(in, inOfs, inLen, out, 0); 352 if (out.length != k) { 353 out = Arrays.copyOf(out, k); 354 } 355 return out; 356 } catch (ShortBufferException e) { 357 throw new UcryptoException("Internal Error", e); 358 } 359 } 360 361 // see JCE spec 362 @Override 363 protected synchronized int engineDoFinal(byte[] in, int inOfs, int inLen, 364 byte[] out, int outOfs) 365 throws ShortBufferException, IllegalBlockSizeException, 366 BadPaddingException { 367 int len = getOutputSizeByOperation(inLen, true); 368 if (out.length - outOfs < len) { 369 throw new ShortBufferException("Output buffer must be " 370 + "(at least) " + len 371 + " bytes long"); 372 } 373 if (aadBuffer != null && aadBuffer.size() > 0) { 374 // init again with AAD data 375 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); 376 aadBuffer = null; 377 } 378 if (requireReinit) { 379 throw new IllegalStateException 380 ("Must use either different key or iv for GCM encryption"); 381 } 382 if (!encrypt) { 383 if (inLen > 0) { 384 ibuffer.write(in, inOfs, inLen); 385 } 386 inLen = ibuffer.size(); 387 if (inLen < tagLen/8) { 388 // Otherwise, Solaris lib will error out w/ CRYPTO_BUFFER_TOO_SMALL 389 // when ucrypto_decrypt_final() is called 390 throw new AEADBadTagException("Input too short - need tag"); 391 } 392 // refresh 'in' to all buffered-up bytes 393 in = ibuffer.toByteArray(); 394 inOfs = 0; 395 ibuffer.reset(); 396 } 397 try { 398 return super.engineDoFinal(in, inOfs, inLen, out, outOfs); 399 } catch (UcryptoException ue) { 400 if (ue.getMessage().equals("CRYPTO_INVALID_MAC")) { 401 throw new AEADBadTagException("Tag does not match"); 402 } else { 403 // pass it up 404 throw ue; 405 } 406 } finally { 407 requireReinit = encrypt; 408 } 409 } 410 }