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