/*
 * Copyright (c) 2014, 2016, 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.nio.ByteBuffer;
import java.util.Set;
import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.ConcurrentSkipListSet;
import java.lang.ref.*;

import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;


import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;

import javax.crypto.spec.IvParameterSpec;

Wrapper class which uses NativeCipher class and Java impls of padding scheme. This class currently supports - AES/ECB/PKCS5PADDING - AES/CBC/PKCS5PADDING - AES/CFB128/PKCS5PADDING
Since:9
/** * Wrapper class which uses NativeCipher class and Java impls of padding scheme. * This class currently supports * - AES/ECB/PKCS5PADDING * - AES/CBC/PKCS5PADDING * - AES/CFB128/PKCS5PADDING * * @since 9 */
public class NativeCipherWithJavaPadding extends CipherSpi { private static interface Padding { // ENC: generate and return the necessary padding bytes int getPadLen(int dataLen); // ENC: generate and return the necessary padding bytes byte[] getPaddingBytes(int dataLen); // DEC: process the decrypted data and buffer up the potential padding // bytes byte[] bufferBytes(byte[] intermediateData); // DEC: return the length of internally buffered pad bytes int getBufferedLength(); // DEC: unpad and place the output in 'out', starting from outOfs // and return the number of bytes unpadded into 'out'. int unpad(byte[] paddedData, byte[] out, int outOfs) throws BadPaddingException, IllegalBlockSizeException, ShortBufferException; // DEC: Clears the padding object to the initial state void clear(); } private static class PKCS5Padding implements Padding { private final int blockSize; // buffer for storing the potential padding bytes private ByteBuffer trailingBytes = null; PKCS5Padding(int blockSize) throws NoSuchPaddingException { if (blockSize == 0) { throw new NoSuchPaddingException ("PKCS#5 padding not supported with stream ciphers"); } this.blockSize = blockSize; } public int getPadLen(int dataLen) { return (blockSize - (dataLen & (blockSize - 1))); } public byte[] getPaddingBytes(int dataLen) { byte padValue = (byte) getPadLen(dataLen); byte[] paddingBytes = new byte[padValue]; Arrays.fill(paddingBytes, padValue); return paddingBytes; } public byte[] bufferBytes(byte[] dataFromUpdate) { if (dataFromUpdate == null || dataFromUpdate.length == 0) { return null; } byte[] result = null; if (trailingBytes == null) { trailingBytes = ByteBuffer.wrap(new byte[blockSize]); } int tbSize = trailingBytes.position(); if (dataFromUpdate.length > trailingBytes.remaining()) { int totalLen = dataFromUpdate.length + tbSize; int newTBSize = totalLen % blockSize; if (newTBSize == 0) { newTBSize = blockSize; } if (tbSize == 0) { result = Arrays.copyOf(dataFromUpdate, totalLen - newTBSize); } else { // combine 'trailingBytes' and 'dataFromUpdate' result = Arrays.copyOf(trailingBytes.array(), totalLen - newTBSize); if (result.length != tbSize) { System.arraycopy(dataFromUpdate, 0, result, tbSize, result.length - tbSize); } } // update 'trailingBytes' w/ remaining bytes in 'dataFromUpdate' trailingBytes.clear(); trailingBytes.put(dataFromUpdate, dataFromUpdate.length - newTBSize, newTBSize); } else { trailingBytes.put(dataFromUpdate); } return result; } public int getBufferedLength() { if (trailingBytes != null) { return trailingBytes.position(); } return 0; } public int unpad(byte[] lastData, byte[] out, int outOfs) throws BadPaddingException, IllegalBlockSizeException, ShortBufferException { int tbSize = (trailingBytes == null? 0:trailingBytes.position()); int dataLen = tbSize + lastData.length; // Special handling to match SunJCE provider behavior if (dataLen <= 0) { return 0; } else if (dataLen % blockSize != 0) { UcryptoProvider.debug("PKCS5Padding: unpad, buffered " + tbSize + " bytes, last block " + lastData.length + " bytes"); throw new IllegalBlockSizeException ("Input length must be multiples of " + blockSize); } // check padding bytes if (lastData.length == 0) { if (tbSize != 0) { // work on 'trailingBytes' directly lastData = Arrays.copyOf(trailingBytes.array(), tbSize); trailingBytes.clear(); tbSize = 0; } else { throw new BadPaddingException("No pad bytes found!"); } } byte padValue = lastData[lastData.length - 1]; if (padValue < 1 || padValue > blockSize) { UcryptoProvider.debug("PKCS5Padding: unpad, lastData: " + Arrays.toString(lastData)); UcryptoProvider.debug("PKCS5Padding: unpad, padValue=" + padValue); throw new BadPaddingException("Invalid pad value: " + padValue); } // sanity check padding bytes int padStartIndex = lastData.length - padValue; for (int i = padStartIndex; i < lastData.length; i++) { if (lastData[i] != padValue) { UcryptoProvider.debug("PKCS5Padding: unpad, lastData: " + Arrays.toString(lastData)); UcryptoProvider.debug("PKCS5Padding: unpad, padValue=" + padValue); throw new BadPaddingException("Invalid padding bytes!"); } } int actualOutLen = dataLen - padValue; // check output buffer capacity if (out.length - outOfs < actualOutLen) { throw new ShortBufferException("Output buffer too small, need " + actualOutLen + ", got " + (out.length - outOfs)); } try { if (tbSize != 0) { trailingBytes.rewind(); if (tbSize < actualOutLen) { trailingBytes.get(out, outOfs, tbSize); outOfs += tbSize; } else { // copy from trailingBytes and we are done trailingBytes.get(out, outOfs, actualOutLen); return actualOutLen; } } if (lastData.length > padValue) { System.arraycopy(lastData, 0, out, outOfs, lastData.length - padValue); } return actualOutLen; } finally { clear(); } } public void clear() { if (trailingBytes != null) trailingBytes.clear(); } } public static final class AesEcbPKCS5 extends NativeCipherWithJavaPadding { public AesEcbPKCS5() throws NoSuchAlgorithmException, NoSuchPaddingException { super(new NativeCipher.AesEcbNoPadding(), "PKCS5Padding"); } } public static final class AesCbcPKCS5 extends NativeCipherWithJavaPadding { public AesCbcPKCS5() throws NoSuchAlgorithmException, NoSuchPaddingException { super(new NativeCipher.AesCbcNoPadding(), "PKCS5Padding"); } } public static final class AesCfb128PKCS5 extends NativeCipherWithJavaPadding { public AesCfb128PKCS5() throws NoSuchAlgorithmException, NoSuchPaddingException { super(new NativeCipher.AesCfb128NoPadding(), "PKCS5Padding"); } } // fields (re)set in every init() private final NativeCipher nc; private final Padding padding; private final int blockSize; private int lastBlockLen = 0; // Only ECB, CBC, CTR, and CFB128 modes w/ NOPADDING for now NativeCipherWithJavaPadding(NativeCipher nc, String paddingScheme) throws NoSuchAlgorithmException, NoSuchPaddingException { this.nc = nc; this.blockSize = nc.engineGetBlockSize(); if (paddingScheme.toUpperCase(Locale.ROOT).equals("PKCS5PADDING")) { padding = new PKCS5Padding(blockSize); } else { throw new NoSuchAlgorithmException("Unsupported padding scheme: " + paddingScheme); } } void reset() { padding.clear(); lastBlockLen = 0; } @Override protected synchronized void engineSetMode(String mode) throws NoSuchAlgorithmException { nc.engineSetMode(mode); } // see JCE spec @Override protected void engineSetPadding(String padding) throws NoSuchPaddingException { // Disallow change of padding for now since currently it's explicitly // defined in transformation strings throw new NoSuchPaddingException("Unsupported padding " + padding); } // see JCE spec @Override protected int engineGetBlockSize() { return blockSize; } // see JCE spec @Override protected synchronized int engineGetOutputSize(int inputLen) { int result = nc.engineGetOutputSize(inputLen); if (nc.encrypt) { result += padding.getPadLen(result); } else { result += padding.getBufferedLength(); } return result; } // see JCE spec @Override protected synchronized byte[] engineGetIV() { return nc.engineGetIV(); } // see JCE spec @Override protected synchronized AlgorithmParameters engineGetParameters() { return nc.engineGetParameters(); } @Override protected int engineGetKeySize(Key key) throws InvalidKeyException { return nc.engineGetKeySize(key); } // see JCE spec @Override protected synchronized void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { reset(); nc.engineInit(opmode, key, random); } // see JCE spec @Override protected synchronized void engineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { reset(); nc.engineInit(opmode, key, params, random); } // see JCE spec @Override protected synchronized void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { reset(); nc.engineInit(opmode, key, params, random); } // see JCE spec @Override protected synchronized byte[] engineUpdate(byte[] in, int inOfs, int inLen) { if (nc.encrypt) { lastBlockLen += inLen; lastBlockLen &= (blockSize - 1); return nc.engineUpdate(in, inOfs, inLen); } else { return padding.bufferBytes(nc.engineUpdate(in, inOfs, inLen)); } } // see JCE spec @Override protected synchronized int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) throws ShortBufferException { if (nc.encrypt) { lastBlockLen += inLen; lastBlockLen &= (blockSize - 1); return nc.engineUpdate(in, inOfs, inLen, out, outOfs); } else { byte[] result = padding.bufferBytes(nc.engineUpdate(in, inOfs, inLen)); if (result != null) { System.arraycopy(result, 0, out, outOfs, result.length); return result.length; } else return 0; } } // see JCE spec @Override protected synchronized byte[] engineDoFinal(byte[] in, int inOfs, int inLen) throws IllegalBlockSizeException, BadPaddingException { int estimatedOutLen = engineGetOutputSize(inLen); byte[] out = new byte[estimatedOutLen]; try { int actualOut = this.engineDoFinal(in, inOfs, inLen, out, 0); // truncate off extra bytes if (actualOut != out.length) { out = Arrays.copyOf(out, actualOut); } } catch (ShortBufferException sbe) { throw new UcryptoException("Internal Error", sbe); } finally { reset(); } return out; } // see JCE spec @Override protected synchronized int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { int estimatedOutLen = engineGetOutputSize(inLen); if (out.length - outOfs < estimatedOutLen) { throw new ShortBufferException("Actual: " + (out.length - outOfs) + ". Estimated Out Length: " + estimatedOutLen); } try { if (nc.encrypt) { int k = nc.engineUpdate(in, inOfs, inLen, out, outOfs); lastBlockLen += inLen; lastBlockLen &= (blockSize - 1); byte[] padBytes = padding.getPaddingBytes(lastBlockLen); k += nc.engineDoFinal(padBytes, 0, padBytes.length, out, (outOfs + k)); return k; } else { byte[] tempOut = nc.engineDoFinal(in, inOfs, inLen); int len = padding.unpad(tempOut, out, outOfs); return len; } } finally { reset(); } } // see JCE spec @Override protected synchronized byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKeyException { byte[] result = null; try { byte[] encodedKey = key.getEncoded(); if ((encodedKey == null) || (encodedKey.length == 0)) { throw new InvalidKeyException("Cannot get an encoding of " + "the key to be wrapped"); } result = engineDoFinal(encodedKey, 0, encodedKey.length); } catch (BadPaddingException e) { // Should never happen for key wrapping throw new UcryptoException("Internal Error", e); } return result; } // see JCE spec @Override protected synchronized Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException { byte[] encodedKey; try { encodedKey = engineDoFinal(wrappedKey, 0, wrappedKey.length); } catch (Exception e) { throw (InvalidKeyException) (new InvalidKeyException()).initCause(e); } return NativeCipher.constructKey(wrappedKeyType, encodedKey, wrappedKeyAlgorithm); } }