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 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 result -= tagLen/8; 117 } 118 if (result < 0) { 119 result = 0; 120 } 121 return result; 122 } 123 124 @Override 125 protected void reset(boolean doCancel) { 126 super.reset(doCancel); 127 if (aadBuffer == null) { 128 aadBuffer = new ByteArrayOutputStream(); 129 } else { 130 aadBuffer.reset(); 131 } 132 133 if (ibuffer != null) { 134 ibuffer.reset(); 135 } 136 if (!encrypt) requireReinit = false; 137 } 138 139 // actual init() implementation - caller should clone key and iv if needed 140 protected void init(boolean encrypt, byte[] keyVal, byte[] ivVal, int tLen, byte[] aad) { 141 reset(true); 142 this.encrypt = encrypt; 143 this.keyValue = keyVal; 144 this.iv = ivVal; 145 long pCtxtVal = NativeCipher.nativeInit(mech.value(), encrypt, keyValue, iv, 146 tLen, aad); 147 initialized = (pCtxtVal != 0L); 148 if (initialized) { 149 pCtxt = new CipherContextRef(this, pCtxtVal, encrypt); 150 } else { 151 throw new UcryptoException("Cannot initialize Cipher"); 152 } 153 } 154 155 // see JCE spec 156 @Override 157 protected synchronized AlgorithmParameters engineGetParameters() { 158 AlgorithmParameters params = null; 159 try { 160 if (iv != null) { 161 GCMParameterSpec gcmSpec = new GCMParameterSpec(tagLen, iv.clone()); 162 params = AlgorithmParameters.getInstance("GCM"); 163 params.init(gcmSpec); 164 } 165 } catch (GeneralSecurityException e) { 166 // NoSuchAlgorithmException, NoSuchProviderException 167 // InvalidParameterSpecException 168 throw new UcryptoException("Could not encode parameters", e); 169 } 170 return params; 171 } 172 173 // see JCE spec 174 @Override 175 protected synchronized void engineInit(int opmode, Key key, 176 AlgorithmParameterSpec params, SecureRandom random) 177 throws InvalidKeyException, InvalidAlgorithmParameterException { 178 checkKey(key); 179 if (opmode != Cipher.ENCRYPT_MODE && 180 opmode != Cipher.DECRYPT_MODE && 181 opmode != Cipher.WRAP_MODE && 182 opmode != Cipher.UNWRAP_MODE) { 183 throw new InvalidAlgorithmParameterException 184 ("Unsupported mode: " + opmode); 185 } 186 boolean doEncrypt = (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE); 187 byte[] keyBytes = key.getEncoded().clone(); 188 byte[] ivBytes = null; 189 if (params != null) { 190 if (!(params instanceof GCMParameterSpec)) { 191 throw new InvalidAlgorithmParameterException("GCMParameterSpec required." + 192 " Received: " + params.getClass().getName()); 193 } else { 194 tagLen = ((GCMParameterSpec) params).getTLen(); 195 ivBytes = ((GCMParameterSpec) params).getIV(); 196 } 197 } else { 198 if (doEncrypt) { 199 tagLen = DEFAULT_TAG_LEN; 200 201 // generate IV if none supplied for encryption 202 ivBytes = new byte[blockSize]; 203 new SecureRandom().nextBytes(ivBytes); 204 } else { 205 throw new InvalidAlgorithmParameterException("Parameters required for decryption"); 206 } 207 } 208 if (doEncrypt) { 209 requireReinit = Arrays.equals(ivBytes, lastEncIv) && 210 MessageDigest.isEqual(keyBytes, lastEncKey); 211 if (requireReinit) { 212 throw new InvalidAlgorithmParameterException 213 ("Cannot reuse iv for GCM encryption"); 214 } 215 lastEncIv = ivBytes; 216 lastEncKey = keyBytes; 217 } else { 218 requireReinit = false; 219 ibuffer = new ByteArrayOutputStream(); 220 } 221 init(doEncrypt, keyBytes, ivBytes, tagLen, null); 222 } 223 224 // see JCE spec 225 @Override 226 protected synchronized void engineInit(int opmode, Key key, AlgorithmParameters params, 227 SecureRandom random) 228 throws InvalidKeyException, InvalidAlgorithmParameterException { 229 AlgorithmParameterSpec spec = null; 230 if (params != null) { 231 try { 232 // mech must be UcryptoMech.CRYPTO_AES_GCM 233 spec = params.getParameterSpec(GCMParameterSpec.class); 234 } catch (InvalidParameterSpecException iaps) { 235 throw new InvalidAlgorithmParameterException(iaps); 236 } 237 } 238 engineInit(opmode, key, spec, random); 239 } 240 241 // see JCE spec 242 @Override 243 protected synchronized byte[] engineUpdate(byte[] in, int inOfs, int inLen) { 244 if (aadBuffer != null && aadBuffer.size() > 0) { 245 // init again with AAD data 246 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); 247 aadBuffer = null; 248 } 249 if (requireReinit) { 250 throw new IllegalStateException 251 ("Must use either different key or iv for GCM encryption"); 252 } 253 if (inLen > 0) { 254 if (!encrypt) { 255 ibuffer.write(in, inOfs, inLen); 256 return null; 257 } 258 return super.engineUpdate(in, inOfs, inLen); 259 } else return null; 260 } 261 262 // see JCE spec 263 @Override 264 protected synchronized int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out, 265 int outOfs) throws ShortBufferException { 266 int len = getOutputSizeByOperation(inLen, false); 267 if (out.length - outOfs < len) { 268 throw new ShortBufferException("Output buffer must be " + 269 "(at least) " + len + " bytes long. Got: " + 270 (out.length - outOfs)); 271 } 272 if (aadBuffer != null && aadBuffer.size() > 0) { 273 // init again with AAD data 274 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); 275 aadBuffer = null; 276 } 277 if (requireReinit) { 278 throw new IllegalStateException 279 ("Must use either different key or iv for GCM encryption"); 280 } 281 if (inLen > 0) { 282 if (!encrypt) { 283 ibuffer.write(in, inOfs, inLen); 284 return 0; 285 } else { 286 return super.engineUpdate(in, inOfs, inLen, out, outOfs); 287 } 288 } 289 return 0; 290 } 291 292 // see JCE spec 293 @Override 294 protected synchronized void engineUpdateAAD(byte[] src, int srcOfs, int srcLen) 295 throws IllegalStateException { 296 297 if ((src == null) || (srcOfs < 0) || (srcOfs + srcLen > src.length)) { 298 throw new IllegalArgumentException("Invalid AAD"); 299 } 300 if (keyValue == null) { 301 throw new IllegalStateException("Need to initialize Cipher first"); 302 } 303 if (requireReinit) { 304 throw new IllegalStateException 305 ("Must use either different key or iv for GCM encryption"); 306 } 307 if (aadBuffer != null) { 308 aadBuffer.write(src, srcOfs, srcLen); 309 } else { 310 // update has already been called 311 throw new IllegalStateException 312 ("Update has been called; no more AAD data"); 313 } 314 } 315 316 // see JCE spec 317 @Override 318 protected void engineUpdateAAD(ByteBuffer src) 319 throws IllegalStateException { 320 if (src == null) { 321 throw new IllegalArgumentException("Invalid AAD"); 322 } 323 if (keyValue == null) { 324 throw new IllegalStateException("Need to initialize Cipher first"); 325 } 326 if (requireReinit) { 327 throw new IllegalStateException 328 ("Must use either different key or iv for GCM encryption"); 329 } 330 if (aadBuffer != null) { 331 if (src.hasRemaining()) { 332 byte[] srcBytes = new byte[src.remaining()]; 333 src.get(srcBytes); 334 aadBuffer.write(srcBytes, 0, srcBytes.length); 335 } 336 } else { 337 // update has already been called 338 throw new IllegalStateException 339 ("Update has been called; no more AAD data"); 340 } 341 } 342 343 // see JCE spec 344 @Override 345 protected synchronized byte[] engineDoFinal(byte[] in, int inOfs, int inLen) 346 throws IllegalBlockSizeException, BadPaddingException { 347 byte[] out = new byte[getOutputSizeByOperation(inLen, true)]; 348 try { 349 // delegate to the other engineDoFinal(...) method 350 int k = engineDoFinal(in, inOfs, inLen, out, 0); 351 if (out.length != k) { 352 out = Arrays.copyOf(out, k); 353 } 354 return out; 355 } catch (ShortBufferException e) { 356 throw new UcryptoException("Internal Error", e); 357 } 358 } 359 360 // see JCE spec 361 @Override 362 protected synchronized int engineDoFinal(byte[] in, int inOfs, int inLen, 363 byte[] out, int outOfs) 364 throws ShortBufferException, IllegalBlockSizeException, 365 BadPaddingException { 366 int len = getOutputSizeByOperation(inLen, true); 367 if (out.length - outOfs < len) { 368 throw new ShortBufferException("Output buffer must be " 369 + "(at least) " + len + " bytes long. Got: " + 370 (out.length - outOfs)); 371 } 372 if (aadBuffer != null && aadBuffer.size() > 0) { 373 // init again with AAD data 374 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); 375 aadBuffer = null; 376 } 377 if (requireReinit) { 378 throw new IllegalStateException 379 ("Must use either different key or iv for GCM encryption"); 380 } 381 if (!encrypt) { 382 if (inLen > 0) { 383 ibuffer.write(in, inOfs, inLen); 384 } 385 inLen = ibuffer.size(); 386 if (inLen < tagLen/8) { 387 // Otherwise, Solaris lib will error out w/ CRYPTO_BUFFER_TOO_SMALL 388 // when ucrypto_decrypt_final() is called 389 throw new AEADBadTagException("Input too short - need tag." + 390 " inLen: " + inLen + ". tagLen: " + tagLen); 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 }