jdk/src/jdk.crypto.ucrypto/solaris/classes/com/oracle/security/ucrypto/NativeGCMCipher.java
changeset 27182 4525d13b8af1
child 30915 504f95d17f58
equal deleted inserted replaced
27181:29f9c4f56e80 27182:4525d13b8af1
       
     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 }