blob: 211582c4ed961c8eb94b48cba0793942f4cd8831 [file] [log] [blame]
/*
* 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
*/
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);
}
}