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