--- /dev/null Wed Oct 15 20:54:58 2014 +++ new/src/jdk.crypto.ucrypto/solaris/classes/com/oracle/security/ucrypto/NativeGCMCipher.java Wed Oct 15 20:54:57 2014 @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.security.ucrypto; + +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; + +import java.util.Set; +import java.util.Arrays; +import java.security.*; +import java.security.spec.*; +import javax.crypto.*; +import javax.crypto.spec.SecretKeySpec; +import javax.crypto.spec.GCMParameterSpec; + +/** + * Cipher wrapper class utilizing ucrypto APIs. This class currently supports + * - AES/GCM/NoPADDING + * + * @since 1.9 + */ +class NativeGCMCipher extends NativeCipher { + + public static final class AesGcmNoPadding extends NativeGCMCipher { + public AesGcmNoPadding() throws NoSuchAlgorithmException { + super(-1); + } + } + public static final class Aes128GcmNoPadding extends NativeGCMCipher { + public Aes128GcmNoPadding() throws NoSuchAlgorithmException { + super(16); + } + } + public static final class Aes192GcmNoPadding extends NativeGCMCipher { + public Aes192GcmNoPadding() throws NoSuchAlgorithmException { + super(24); + } + } + public static final class Aes256GcmNoPadding extends NativeGCMCipher { + public Aes256GcmNoPadding() throws NoSuchAlgorithmException { + super(32); + } + } + + private static final int DEFAULT_TAG_LEN = 128; // same as SunJCE provider + + // buffer for storing AAD data; if null, meaning buffer content has been + // supplied to native context + private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream(); + + // buffer for storing input in decryption, not used for encryption + private ByteArrayOutputStream ibuffer = null; + + private int tagLen = DEFAULT_TAG_LEN; + + /* + * variables used for performing the GCM (key+iv) uniqueness check. + * To use GCM mode safely, the cipher object must be re-initialized + * with a different combination of key + iv values for each + * ENCRYPTION operation. However, checking all past key + iv values + * isn't feasible. Thus, we only do a per-instance check of the + * key + iv values used in previous encryption. + * For decryption operations, no checking is necessary. + */ + private boolean requireReinit = false; + private byte[] lastEncKey = null; + private byte[] lastEncIv = null; + + NativeGCMCipher(int fixedKeySize) throws NoSuchAlgorithmException { + super(UcryptoMech.CRYPTO_AES_GCM, fixedKeySize); + } + + @Override + protected void ensureInitialized() { + if (!initialized) { + if (aadBuffer != null && aadBuffer.size() > 0) { + init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); + aadBuffer = null; + } else { + init(encrypt, keyValue, iv, tagLen, null); + } + if (!initialized) { + throw new UcryptoException("Cannot initialize Cipher"); + } + } + } + + @Override + protected int getOutputSizeByOperation(int inLen, boolean isDoFinal) { + if (inLen < 0) return 0; + + if (!isDoFinal && (inLen == 0)) { + return 0; + } + + int result = inLen + bytesBuffered; + if (encrypt) { + if (isDoFinal) { + result += tagLen/8; + } + } else { + if (ibuffer != null) { + result += ibuffer.size(); + } + if (isDoFinal) { + result -= tagLen/8; + } + } + if (result < 0) { + result = 0; + } + return result; + } + + @Override + protected void reset(boolean doCancel) { + super.reset(doCancel); + if (aadBuffer == null) { + aadBuffer = new ByteArrayOutputStream(); + } else { + aadBuffer.reset(); + } + + if (ibuffer != null) { + ibuffer.reset(); + } + if (!encrypt) requireReinit = false; + } + + // actual init() implementation - caller should clone key and iv if needed + protected void init(boolean encrypt, byte[] keyVal, byte[] ivVal, int tLen, byte[] aad) { + reset(true); + this.encrypt = encrypt; + this.keyValue = keyVal; + this.iv = ivVal; + long pCtxtVal = NativeCipher.nativeInit(mech.value(), encrypt, keyValue, iv, + tLen, aad); + initialized = (pCtxtVal != 0L); + if (initialized) { + pCtxt = new CipherContextRef(this, pCtxtVal, encrypt); + } else { + throw new UcryptoException("Cannot initialize Cipher"); + } + } + + // see JCE spec + @Override + protected synchronized AlgorithmParameters engineGetParameters() { + AlgorithmParameters params = null; + try { + if (iv != null) { + GCMParameterSpec gcmSpec = new GCMParameterSpec(tagLen, iv.clone()); + params = AlgorithmParameters.getInstance("GCM"); + params.init(gcmSpec); + } + } catch (GeneralSecurityException e) { + // NoSuchAlgorithmException, NoSuchProviderException + // InvalidParameterSpecException + throw new UcryptoException("Could not encode parameters", e); + } + return params; + } + + // see JCE spec + @Override + protected synchronized void engineInit(int opmode, Key key, + AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + checkKey(key); + if (opmode != Cipher.ENCRYPT_MODE && + opmode != Cipher.DECRYPT_MODE && + opmode != Cipher.WRAP_MODE && + opmode != Cipher.UNWRAP_MODE) { + throw new InvalidAlgorithmParameterException + ("Unsupported mode: " + opmode); + } + boolean doEncrypt = (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE); + byte[] keyBytes = key.getEncoded().clone(); + byte[] ivBytes = null; + if (params != null) { + if (!(params instanceof GCMParameterSpec)) { + throw new InvalidAlgorithmParameterException("GCMParameterSpec required"); + } else { + tagLen = ((GCMParameterSpec) params).getTLen(); + ivBytes = ((GCMParameterSpec) params).getIV(); + } + } else { + if (doEncrypt) { + tagLen = DEFAULT_TAG_LEN; + + // generate IV if none supplied for encryption + ivBytes = new byte[blockSize]; + new SecureRandom().nextBytes(ivBytes); + } else { + throw new InvalidAlgorithmParameterException("Parameters required for decryption"); + } + } + if (doEncrypt) { + requireReinit = Arrays.equals(ivBytes, lastEncIv) && + Arrays.equals(keyBytes, lastEncKey); + if (requireReinit) { + throw new InvalidAlgorithmParameterException + ("Cannot reuse iv for GCM encryption"); + } + lastEncIv = ivBytes; + lastEncKey = keyBytes; + } else { + requireReinit = false; + ibuffer = new ByteArrayOutputStream(); + } + init(doEncrypt, keyBytes, ivBytes, tagLen, null); + } + + // see JCE spec + @Override + protected synchronized void engineInit(int opmode, Key key, AlgorithmParameters params, + SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + AlgorithmParameterSpec spec = null; + if (params != null) { + try { + // mech must be UcryptoMech.CRYPTO_AES_GCM + spec = params.getParameterSpec(GCMParameterSpec.class); + } catch (InvalidParameterSpecException iaps) { + throw new InvalidAlgorithmParameterException(iaps); + } + } + engineInit(opmode, key, spec, random); + } + + // see JCE spec + @Override + protected synchronized byte[] engineUpdate(byte[] in, int inOfs, int inLen) { + if (aadBuffer != null && aadBuffer.size() > 0) { + // init again with AAD data + init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); + aadBuffer = null; + } + if (requireReinit) { + throw new IllegalStateException + ("Must use either different key or iv for GCM encryption"); + } + if (inLen > 0) { + if (!encrypt) { + ibuffer.write(in, inOfs, inLen); + return null; + } + return super.engineUpdate(in, inOfs, inLen); + } else return null; + } + + // see JCE spec + @Override + protected synchronized int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out, + int outOfs) throws ShortBufferException { + int len = getOutputSizeByOperation(inLen, false); + if (out.length - outOfs < len) { + throw new ShortBufferException("Output buffer must be " + + "(at least) " + len + + " bytes long"); + } + if (aadBuffer != null && aadBuffer.size() > 0) { + // init again with AAD data + init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); + aadBuffer = null; + } + if (requireReinit) { + throw new IllegalStateException + ("Must use either different key or iv for GCM encryption"); + } + if (inLen > 0) { + if (!encrypt) { + ibuffer.write(in, inOfs, inLen); + return 0; + } else { + return super.engineUpdate(in, inOfs, inLen, out, outOfs); + } + } + return 0; + } + + // see JCE spec + @Override + protected synchronized void engineUpdateAAD(byte[] src, int srcOfs, int srcLen) + throws IllegalStateException { + + if ((src == null) || (srcOfs < 0) || (srcOfs + srcLen > src.length)) { + throw new IllegalArgumentException("Invalid AAD"); + } + if (keyValue == null) { + throw new IllegalStateException("Need to initialize Cipher first"); + } + if (requireReinit) { + throw new IllegalStateException + ("Must use either different key or iv for GCM encryption"); + } + if (aadBuffer != null) { + aadBuffer.write(src, srcOfs, srcLen); + } else { + // update has already been called + throw new IllegalStateException + ("Update has been called; no more AAD data"); + } + } + + // see JCE spec + @Override + protected void engineUpdateAAD(ByteBuffer src) + throws IllegalStateException { + if (src == null) { + throw new IllegalArgumentException("Invalid AAD"); + } + if (keyValue == null) { + throw new IllegalStateException("Need to initialize Cipher first"); + } + if (requireReinit) { + throw new IllegalStateException + ("Must use either different key or iv for GCM encryption"); + } + if (aadBuffer != null) { + if (src.hasRemaining()) { + byte[] srcBytes = new byte[src.remaining()]; + src.get(srcBytes); + aadBuffer.write(srcBytes, 0, srcBytes.length); + } + } else { + // update has already been called + throw new IllegalStateException + ("Update has been called; no more AAD data"); + } + } + + // see JCE spec + @Override + protected synchronized byte[] engineDoFinal(byte[] in, int inOfs, int inLen) + throws IllegalBlockSizeException, BadPaddingException { + byte[] out = new byte[getOutputSizeByOperation(inLen, true)]; + try { + // delegate to the other engineDoFinal(...) method + int k = engineDoFinal(in, inOfs, inLen, out, 0); + if (out.length != k) { + out = Arrays.copyOf(out, k); + } + return out; + } catch (ShortBufferException e) { + throw new UcryptoException("Internal Error", e); + } + } + + // see JCE spec + @Override + protected synchronized int engineDoFinal(byte[] in, int inOfs, int inLen, + byte[] out, int outOfs) + throws ShortBufferException, IllegalBlockSizeException, + BadPaddingException { + int len = getOutputSizeByOperation(inLen, true); + if (out.length - outOfs < len) { + throw new ShortBufferException("Output buffer must be " + + "(at least) " + len + + " bytes long"); + } + if (aadBuffer != null && aadBuffer.size() > 0) { + // init again with AAD data + init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray()); + aadBuffer = null; + } + if (requireReinit) { + throw new IllegalStateException + ("Must use either different key or iv for GCM encryption"); + } + if (!encrypt) { + if (inLen > 0) { + ibuffer.write(in, inOfs, inLen); + } + inLen = ibuffer.size(); + if (inLen < tagLen/8) { + // Otherwise, Solaris lib will error out w/ CRYPTO_BUFFER_TOO_SMALL + // when ucrypto_decrypt_final() is called + throw new AEADBadTagException("Input too short - need tag"); + } + // refresh 'in' to all buffered-up bytes + in = ibuffer.toByteArray(); + inOfs = 0; + ibuffer.reset(); + } + try { + return super.engineDoFinal(in, inOfs, inLen, out, outOfs); + } catch (UcryptoException ue) { + if (ue.getMessage().equals("CRYPTO_INVALID_MAC")) { + throw new AEADBadTagException("Tag does not match"); + } else { + // pass it up + throw ue; + } + } finally { + requireReinit = encrypt; + } + } +}