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 }