1 /* 2 * Copyright (c) 2014, 2018, 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 import 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 */ 47 class 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 try { 250 init(doEncrypt, keyBytes, ivBytes, tagLen, null); 251 } catch (UcryptoException ex) { 252 if (ex.getError() == 253 UcryptoException.Error.CRYPTO_MECHANISM_PARAM_INVALID) { 254 255 throw new InvalidAlgorithmParameterException(ex.getMessage()); 256 } else { 257 throw ex; 258 } 259 } 260 } 261 262 // see JCE spec 263 @Override 264 protected synchronized void engineInit(int opmode, Key key, AlgorithmParameters params, 265 SecureRandom random) 266 throws InvalidKeyException, InvalidAlgorithmParameterException { 267 AlgorithmParameterSpec spec = null; 268 if (params != null) { 269 try { 270 // mech must be UcryptoMech.CRYPTO_AES_GCM 271 spec = params.getParameterSpec(GCMParameterSpec.class); 272 } catch (InvalidParameterSpecException iaps) { 273 throw new InvalidAlgorithmParameterException(iaps); 274 } 275 } 276 engineInit(opmode, key, spec, random); 277 } 278 279 // see JCE spec 280 @Override 281 protected synchronized byte[] engineUpdate(byte[] in, int inOfs, int inLen) { 282 if (aadBuffer != null) { 283 if (aadBuffer.size() > 0) { 284 // init again with AAD data 285 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); 286 } 287 aadBuffer = null; 288 } 289 if (requireReinit) { 290 throw new IllegalStateException 291 ("Must use either different key or iv for GCM encryption"); 292 } 293 checkAndUpdateProcessed(inLen); 294 if (inLen > 0) { 295 if (!encrypt) { 296 ibuffer.write(in, inOfs, inLen); 297 return null; 298 } 299 return super.engineUpdate(in, inOfs, inLen); 300 } else return null; 301 } 302 303 // see JCE spec 304 @Override 305 protected synchronized int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out, 306 int outOfs) throws ShortBufferException { 307 int len = getOutputSizeByOperation(inLen, false); 308 if (out.length - outOfs < len) { 309 throw new ShortBufferException("Output buffer must be " + 310 "(at least) " + len + " bytes long. Got: " + 311 (out.length - outOfs)); 312 } 313 if (aadBuffer != null) { 314 if (aadBuffer.size() > 0) { 315 // init again with AAD data 316 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); 317 } 318 aadBuffer = null; 319 } 320 if (requireReinit) { 321 throw new IllegalStateException 322 ("Must use either different key or iv for GCM encryption"); 323 } 324 checkAndUpdateProcessed(inLen); 325 if (inLen > 0) { 326 if (!encrypt) { 327 ibuffer.write(in, inOfs, inLen); 328 return 0; 329 } else { 330 return super.engineUpdate(in, inOfs, inLen, out, outOfs); 331 } 332 } 333 return 0; 334 } 335 336 // see JCE spec 337 @Override 338 protected synchronized void engineUpdateAAD(byte[] src, int srcOfs, int srcLen) 339 throws IllegalStateException { 340 341 if ((src == null) || (srcOfs < 0) || (srcOfs + srcLen > src.length)) { 342 throw new IllegalArgumentException("Invalid AAD"); 343 } 344 if (keyValue == null) { 345 throw new IllegalStateException("Need to initialize Cipher first"); 346 } 347 if (requireReinit) { 348 throw new IllegalStateException 349 ("Must use either different key or iv for GCM encryption"); 350 } 351 if (aadBuffer != null) { 352 aadBuffer.write(src, srcOfs, srcLen); 353 } else { 354 // update has already been called 355 throw new IllegalStateException 356 ("Update has been called; no more AAD data"); 357 } 358 } 359 360 // see JCE spec 361 @Override 362 protected void engineUpdateAAD(ByteBuffer src) 363 throws IllegalStateException { 364 if (src == null) { 365 throw new IllegalArgumentException("Invalid AAD"); 366 } 367 if (keyValue == null) { 368 throw new IllegalStateException("Need to initialize Cipher first"); 369 } 370 if (requireReinit) { 371 throw new IllegalStateException 372 ("Must use either different key or iv for GCM encryption"); 373 } 374 if (aadBuffer != null) { 375 if (src.hasRemaining()) { 376 byte[] srcBytes = new byte[src.remaining()]; 377 src.get(srcBytes); 378 aadBuffer.write(srcBytes, 0, srcBytes.length); 379 } 380 } else { 381 // update has already been called 382 throw new IllegalStateException 383 ("Update has been called; no more AAD data"); 384 } 385 } 386 387 // see JCE spec 388 @Override 389 protected synchronized byte[] engineDoFinal(byte[] in, int inOfs, int inLen) 390 throws IllegalBlockSizeException, BadPaddingException { 391 byte[] out = new byte[getOutputSizeByOperation(inLen, true)]; 392 try { 393 // delegate to the other engineDoFinal(...) method 394 int k = engineDoFinal(in, inOfs, inLen, out, 0); 395 if (out.length != k) { 396 out = Arrays.copyOf(out, k); 397 } 398 return out; 399 } catch (ShortBufferException e) { 400 throw new UcryptoException("Internal Error", e); 401 } 402 } 403 404 // see JCE spec 405 @Override 406 protected synchronized int engineDoFinal(byte[] in, int inOfs, int inLen, 407 byte[] out, int outOfs) 408 throws ShortBufferException, IllegalBlockSizeException, 409 BadPaddingException { 410 int len = getOutputSizeByOperation(inLen, true); 411 if (out.length - outOfs < len) { 412 throw new ShortBufferException("Output buffer must be " 413 + "(at least) " + len + " bytes long. Got: " + 414 (out.length - outOfs)); 415 } 416 if (aadBuffer != null) { 417 if (aadBuffer.size() > 0) { 418 // init again with AAD data 419 init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); 420 } 421 aadBuffer = null; 422 } 423 if (requireReinit) { 424 throw new IllegalStateException 425 ("Must use either different key or iv for GCM encryption"); 426 } 427 428 checkAndUpdateProcessed(inLen); 429 if (!encrypt) { 430 if (inLen > 0) { 431 ibuffer.write(in, inOfs, inLen); 432 } 433 inLen = ibuffer.size(); 434 if (inLen < tagLen/8) { 435 // Otherwise, Solaris lib will error out w/ CRYPTO_BUFFER_TOO_SMALL 436 // when ucrypto_decrypt_final() is called 437 throw new AEADBadTagException("Input too short - need tag." + 438 " inLen: " + inLen + ". tagLen: " + tagLen); 439 } 440 // refresh 'in' to all buffered-up bytes 441 in = ibuffer.toByteArray(); 442 inOfs = 0; 443 ibuffer.reset(); 444 } 445 try { 446 return super.engineDoFinal(in, inOfs, inLen, out, outOfs); 447 } catch (UcryptoException ue) { 448 if (ue.getMessage().equals("CRYPTO_INVALID_MAC")) { 449 throw new AEADBadTagException("Tag does not match"); 450 } else { 451 // pass it up 452 throw ue; 453 } 454 } finally { 455 requireReinit = encrypt; 456 } 457 } 458 }