1195 lines
36 KiB
Java
1195 lines
36 KiB
Java
/* WolfCryptCipher.java
|
|
*
|
|
* Copyright (C) 2006-2024 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfSSL.
|
|
*
|
|
* wolfSSL is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfSSL 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 for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
|
|
package com.wolfssl.provider.jce;
|
|
|
|
import java.util.Arrays;
|
|
import java.nio.ByteBuffer;
|
|
|
|
import javax.crypto.Cipher;
|
|
import javax.crypto.CipherSpi;
|
|
import javax.crypto.SecretKey;
|
|
import javax.crypto.BadPaddingException;
|
|
import javax.crypto.ShortBufferException;
|
|
import javax.crypto.IllegalBlockSizeException;
|
|
import javax.crypto.NoSuchPaddingException;
|
|
import javax.crypto.spec.IvParameterSpec;
|
|
import javax.crypto.spec.GCMParameterSpec;
|
|
|
|
import java.security.SecureRandom;
|
|
import java.security.AlgorithmParameters;
|
|
import java.security.spec.AlgorithmParameterSpec;
|
|
import java.security.spec.InvalidParameterSpecException;
|
|
import java.security.Key;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.InvalidAlgorithmParameterException;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.interfaces.RSAPrivateKey;
|
|
import java.security.interfaces.RSAPublicKey;
|
|
|
|
import com.wolfssl.wolfcrypt.WolfCrypt;
|
|
import com.wolfssl.wolfcrypt.Asn;
|
|
import com.wolfssl.wolfcrypt.Aes;
|
|
import com.wolfssl.wolfcrypt.AesGcm;
|
|
import com.wolfssl.wolfcrypt.Des3;
|
|
import com.wolfssl.wolfcrypt.Rsa;
|
|
import com.wolfssl.wolfcrypt.Rng;
|
|
|
|
import com.wolfssl.provider.jce.WolfCryptDebug;
|
|
|
|
/**
|
|
* wolfCrypt JCE Cipher (AES, 3DES) wrapper
|
|
*/
|
|
public class WolfCryptCipher extends CipherSpi {
|
|
|
|
enum CipherType {
|
|
WC_AES,
|
|
WC_DES3,
|
|
WC_RSA
|
|
}
|
|
|
|
enum CipherMode {
|
|
WC_ECB,
|
|
WC_CBC,
|
|
WC_GCM
|
|
}
|
|
|
|
enum PaddingType {
|
|
WC_NONE,
|
|
WC_PKCS1,
|
|
WC_PKCS5
|
|
}
|
|
|
|
enum OpMode {
|
|
WC_ENCRYPT,
|
|
WC_DECRYPT
|
|
}
|
|
|
|
enum RsaKeyType {
|
|
WC_RSA_PRIVATE,
|
|
WC_RSA_PUBLIC
|
|
}
|
|
|
|
private CipherType cipherType = null;
|
|
private CipherMode cipherMode = null;
|
|
private PaddingType paddingType = null;
|
|
private OpMode direction = null;
|
|
private RsaKeyType rsaKeyType = null;
|
|
|
|
private int blockSize = 0;
|
|
|
|
private Aes aes = null;
|
|
private AesGcm aesGcm = null;
|
|
private Des3 des3 = null;
|
|
private Rsa rsa = null;
|
|
private Rng rng = null;
|
|
|
|
/* for debug logging */
|
|
private String algString;
|
|
private String algMode;
|
|
|
|
/* stash key and IV here for easy lookup */
|
|
private Key storedKey = null;
|
|
private AlgorithmParameterSpec storedSpec = null;
|
|
private byte[] iv = null;
|
|
|
|
/* AES-GCM tag length (bytes) */
|
|
private int gcmTagLen = 0;
|
|
|
|
/* AAD data for AES-GCM, populated via engineUpdateAAD() */
|
|
private byte[] aadData = null;
|
|
|
|
/* Has update/final been called yet, gates setting of AAD for GCM */
|
|
private boolean operationStarted = false;
|
|
|
|
/* Has this Cipher been inintialized? */
|
|
private boolean cipherInitialized = false;
|
|
|
|
/* buffered data from update calls */
|
|
private byte[] buffered = new byte[0];
|
|
|
|
private WolfCryptCipher(CipherType type, CipherMode mode,
|
|
PaddingType pad) {
|
|
|
|
this.cipherType = type;
|
|
this.cipherMode = mode;
|
|
this.paddingType = pad;
|
|
|
|
this.rng = new Rng();
|
|
this.rng.init();
|
|
|
|
switch (cipherType) {
|
|
case WC_AES:
|
|
blockSize = Aes.BLOCK_SIZE;
|
|
break;
|
|
|
|
case WC_DES3:
|
|
blockSize = Des3.BLOCK_SIZE;
|
|
break;
|
|
}
|
|
|
|
if (WolfCryptDebug.DEBUG) {
|
|
algString = typeToString(cipherType);
|
|
algMode = modeToString(cipherMode);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset / re-create internal native struct for algorithm.
|
|
* Should be called during wolfCryptInit() and wolfCryptFinal()
|
|
*/
|
|
private void InitializeNativeStructs() {
|
|
switch (this.cipherType) {
|
|
case WC_AES:
|
|
if (cipherMode == CipherMode.WC_CBC) {
|
|
if (aes != null) {
|
|
aes.releaseNativeStruct();
|
|
aes = null;
|
|
}
|
|
aes = new Aes();
|
|
}
|
|
else if (cipherMode == CipherMode.WC_GCM) {
|
|
if (aesGcm != null) {
|
|
aesGcm.releaseNativeStruct();
|
|
aesGcm = null;
|
|
}
|
|
aesGcm = new AesGcm();
|
|
}
|
|
break;
|
|
|
|
case WC_DES3:
|
|
if (des3 != null) {
|
|
des3.releaseNativeStruct();
|
|
des3 = null;
|
|
}
|
|
des3 = new Des3();
|
|
break;
|
|
|
|
case WC_RSA:
|
|
if (rsa != null) {
|
|
rsa.releaseNativeStruct();
|
|
rsa = null;
|
|
}
|
|
rsa = new Rsa();
|
|
rsa.setRng(this.rng);
|
|
break;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void engineSetMode(String mode)
|
|
throws NoSuchAlgorithmException {
|
|
|
|
int supported = 0;
|
|
|
|
if (mode.equals("ECB")) {
|
|
|
|
/* RSA is ECB mode */
|
|
if (cipherType == CipherType.WC_RSA) {
|
|
cipherMode = CipherMode.WC_ECB;
|
|
supported = 1;
|
|
|
|
log("set mode to ECB");
|
|
}
|
|
|
|
} else if (mode.equals("CBC")) {
|
|
|
|
/* AES and 3DES support CBC */
|
|
if (cipherType == CipherType.WC_AES ||
|
|
cipherType == CipherType.WC_DES3 ) {
|
|
cipherMode = CipherMode.WC_CBC;
|
|
supported = 1;
|
|
|
|
log("set mode to CBC");
|
|
}
|
|
|
|
} else if (mode.equals("GCM")) {
|
|
|
|
/* AES supports GCM */
|
|
if (cipherType == CipherType.WC_AES) {
|
|
cipherMode = CipherMode.WC_GCM;
|
|
supported = 1;
|
|
|
|
log("set mode to GCM");
|
|
}
|
|
}
|
|
|
|
if (supported == 0) {
|
|
throw new NoSuchAlgorithmException(
|
|
"Unsupported cipher mode for active algorithm choice: " +
|
|
mode);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void engineSetPadding(String padding)
|
|
throws NoSuchPaddingException {
|
|
|
|
int supported = 0;
|
|
|
|
if (padding.equals("NoPadding")) {
|
|
|
|
if (cipherType == CipherType.WC_AES ||
|
|
cipherType == CipherType.WC_DES3) {
|
|
paddingType = PaddingType.WC_NONE;
|
|
supported = 1;
|
|
|
|
log("set padding to NoPadding");
|
|
}
|
|
|
|
} else if (padding.equals("PKCS1Padding")) {
|
|
|
|
if (cipherType == CipherType.WC_RSA) {
|
|
paddingType = PaddingType.WC_PKCS1;
|
|
supported = 1;
|
|
|
|
log("set padding to PKCS1Padding");
|
|
}
|
|
|
|
} else if (padding.equals("PKCS5Padding")) {
|
|
|
|
if ((cipherType == CipherType.WC_AES) &&
|
|
(cipherMode == CipherMode.WC_CBC)) {
|
|
|
|
paddingType = PaddingType.WC_PKCS5;
|
|
supported = 1;
|
|
|
|
log("set padding to PKCS5Padding");
|
|
}
|
|
}
|
|
|
|
if (supported == 0) {
|
|
throw new NoSuchPaddingException(
|
|
"Unsupported padding type for active algorithm choice: " +
|
|
padding);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected int engineGetBlockSize() {
|
|
return this.blockSize;
|
|
}
|
|
|
|
@Override
|
|
protected int engineGetOutputSize(int inputLen)
|
|
throws IllegalStateException {
|
|
|
|
int size = 0;
|
|
|
|
if (!this.cipherInitialized) {
|
|
throw new IllegalStateException(
|
|
"Cipher has not been initialized yet");
|
|
}
|
|
|
|
switch (this.cipherType) {
|
|
case WC_AES:
|
|
if (paddingType == PaddingType.WC_NONE) {
|
|
if (cipherMode == CipherMode.WC_GCM) {
|
|
/* In AES-GCM mode we append the authentication tag
|
|
* to the end of ciphertext */
|
|
size = inputLen + this.gcmTagLen;
|
|
}
|
|
else {
|
|
/* wolfCrypt expects input to be padded by application to
|
|
* block size, thus output is same size as input */
|
|
size = inputLen;
|
|
}
|
|
}
|
|
else if (paddingType == PaddingType.WC_PKCS5) {
|
|
size = buffered.length + inputLen;
|
|
size += Aes.getPKCS7PadSize(size, Aes.BLOCK_SIZE);
|
|
}
|
|
else {
|
|
throw new IllegalStateException(
|
|
"Unsupported padding mode for Cipher Aes");
|
|
}
|
|
|
|
break;
|
|
|
|
case WC_DES3:
|
|
if (paddingType == PaddingType.WC_NONE) {
|
|
/* wolfCrypt expects input to be padded by application to
|
|
* block size, thus output is same size as input */
|
|
size = inputLen;
|
|
}
|
|
else if (paddingType == PaddingType.WC_PKCS5) {
|
|
size = buffered.length + inputLen;
|
|
size += Des3.getPKCS7PadSize(size, Des3.BLOCK_SIZE);
|
|
}
|
|
else {
|
|
throw new IllegalStateException(
|
|
"Unsupported padding mode for Cipher Des3");
|
|
}
|
|
|
|
break;
|
|
|
|
case WC_RSA:
|
|
size = this.rsa.getEncryptSize();
|
|
break;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
@Override
|
|
protected byte[] engineGetIV() {
|
|
return this.iv;
|
|
}
|
|
|
|
@Override
|
|
protected AlgorithmParameters engineGetParameters() {
|
|
/* not currently supported by wolfCrypt JCE provider */
|
|
return null;
|
|
}
|
|
|
|
private void wolfCryptSetDirection(int opmode)
|
|
throws InvalidKeyException {
|
|
|
|
/* we don't currently support AES key wrap in JCE yet,
|
|
* so don't allow WRAP_MODE or UNWRAP_MODE */
|
|
switch (opmode) {
|
|
case Cipher.ENCRYPT_MODE:
|
|
this.direction = OpMode.WC_ENCRYPT;
|
|
break;
|
|
|
|
case Cipher.DECRYPT_MODE:
|
|
this.direction = OpMode.WC_DECRYPT;
|
|
break;
|
|
|
|
default:
|
|
throw new InvalidKeyException(
|
|
"Cipher opmode must be ENCRYPT_MODE or DECRPYT_MODE");
|
|
}
|
|
}
|
|
|
|
private void wolfCryptSetIV(AlgorithmParameterSpec spec,
|
|
SecureRandom random) throws InvalidAlgorithmParameterException {
|
|
|
|
/* store AlgorithmParameterSpec for class reset */
|
|
this.storedSpec = spec;
|
|
|
|
/* RSA doesn't need an IV */
|
|
if (this.cipherType == CipherType.WC_RSA)
|
|
return;
|
|
|
|
/* store IV, or generate random IV if not available */
|
|
if (spec == null) {
|
|
this.iv = new byte[this.blockSize];
|
|
|
|
if (random != null) {
|
|
random.nextBytes(this.iv);
|
|
} else {
|
|
SecureRandom rand = new SecureRandom();
|
|
rand.nextBytes(this.iv);
|
|
}
|
|
|
|
} else {
|
|
if (cipherMode == CipherMode.WC_GCM) {
|
|
if (!(spec instanceof GCMParameterSpec)) {
|
|
throw new InvalidAlgorithmParameterException(
|
|
"AlgorithmParameterSpec must be of type " +
|
|
"GCMParameterSpec");
|
|
}
|
|
|
|
GCMParameterSpec gcmSpec = (GCMParameterSpec)spec;
|
|
|
|
if (gcmSpec.getIV() == null ||
|
|
gcmSpec.getIV().length == 0) {
|
|
throw new InvalidAlgorithmParameterException(
|
|
"AES-GCM IV is null or 0 length");
|
|
}
|
|
|
|
this.iv = gcmSpec.getIV().clone();
|
|
|
|
/* store tag length as bytes */
|
|
if (gcmSpec.getTLen() == 0) {
|
|
throw new InvalidAlgorithmParameterException(
|
|
"Tag length cannot be zero");
|
|
}
|
|
this.gcmTagLen = (gcmSpec.getTLen() / 8);
|
|
}
|
|
else {
|
|
if (!(spec instanceof IvParameterSpec)) {
|
|
throw new InvalidAlgorithmParameterException(
|
|
"AlgorithmParameterSpec must be of type " +
|
|
"IvParameterSpec");
|
|
}
|
|
|
|
IvParameterSpec ivSpec = (IvParameterSpec)spec;
|
|
|
|
/* IV should be of block size length */
|
|
if (ivSpec.getIV().length != this.blockSize) {
|
|
throw new InvalidAlgorithmParameterException(
|
|
"Bad IV length (" + ivSpec.getIV().length +
|
|
"), must be " + blockSize + " bytes long");
|
|
}
|
|
|
|
this.iv = ivSpec.getIV().clone();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void wolfCryptSetKey(Key key)
|
|
throws InvalidKeyException {
|
|
|
|
int ret = 0;
|
|
long[] idx = {0};
|
|
byte[] encodedKey;
|
|
|
|
/* validate key class type */
|
|
if (this.cipherType == CipherType.WC_RSA) {
|
|
|
|
if (key instanceof RSAPrivateKey) {
|
|
this.rsaKeyType = RsaKeyType.WC_RSA_PRIVATE;
|
|
|
|
} else if (key instanceof RSAPublicKey) {
|
|
this.rsaKeyType = RsaKeyType.WC_RSA_PUBLIC;
|
|
|
|
} else {
|
|
throw new InvalidKeyException(
|
|
"Cipher key must be of type RSAPrivateKey or " +
|
|
"RSAPublicKey when used for RSA encrypt or decrypt");
|
|
}
|
|
|
|
} else if (!(key instanceof SecretKey)) {
|
|
throw new InvalidKeyException(
|
|
"Cipher key must be of type SecretKey");
|
|
}
|
|
|
|
/* save key for class state resets */
|
|
this.storedKey = key;
|
|
|
|
/* import key */
|
|
encodedKey = key.getEncoded();
|
|
if (encodedKey == null) {
|
|
throw new InvalidKeyException("Key does not support encoding");
|
|
}
|
|
|
|
switch (cipherType) {
|
|
case WC_AES:
|
|
if (this.direction == OpMode.WC_ENCRYPT) {
|
|
if (cipherMode == CipherMode.WC_GCM) {
|
|
this.aesGcm.setKey(encodedKey);
|
|
}
|
|
else {
|
|
this.aes.setKey(encodedKey, iv, Aes.ENCRYPT_MODE);
|
|
}
|
|
} else {
|
|
if (cipherMode == CipherMode.WC_GCM) {
|
|
this.aesGcm.setKey(encodedKey);
|
|
}
|
|
else {
|
|
this.aes.setKey(encodedKey, iv, Aes.DECRYPT_MODE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WC_DES3:
|
|
if (this.direction == OpMode.WC_ENCRYPT) {
|
|
this.des3.setKey(encodedKey, iv, Des3.ENCRYPT_MODE);
|
|
} else {
|
|
this.des3.setKey(encodedKey, iv, Des3.DECRYPT_MODE);
|
|
}
|
|
break;
|
|
|
|
case WC_RSA:
|
|
|
|
/* reset key struct if needed */
|
|
if (this.rsa != null)
|
|
this.rsa.releaseNativeStruct();
|
|
|
|
this.rsa = new Rsa();
|
|
this.rsa.setRng(this.rng);
|
|
|
|
if (this.rsaKeyType == RsaKeyType.WC_RSA_PRIVATE) {
|
|
|
|
this.rsa.decodePrivateKeyPKCS8(encodedKey);
|
|
|
|
} else {
|
|
this.rsa.decodePublicKey(encodedKey);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* called by engineInit() functions */
|
|
private void wolfCryptCipherInit(int opmode, Key key,
|
|
AlgorithmParameterSpec spec, SecureRandom random)
|
|
throws InvalidKeyException, InvalidAlgorithmParameterException {
|
|
|
|
InitializeNativeStructs();
|
|
wolfCryptSetDirection(opmode);
|
|
wolfCryptSetIV(spec, random);
|
|
wolfCryptSetKey(key);
|
|
this.operationStarted = false;
|
|
this.cipherInitialized = true;
|
|
}
|
|
|
|
@Override
|
|
protected void engineInit(int opmode, Key key, SecureRandom random)
|
|
throws InvalidKeyException {
|
|
|
|
try {
|
|
|
|
wolfCryptCipherInit(opmode, key, null, random);
|
|
|
|
log("initialized with key");
|
|
|
|
} catch (InvalidAlgorithmParameterException iape) {
|
|
throw new InvalidKeyException("Invalid algorithm parameters");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void engineInit(int opmode, Key key,
|
|
AlgorithmParameterSpec params, SecureRandom random)
|
|
throws InvalidKeyException, InvalidAlgorithmParameterException {
|
|
|
|
wolfCryptCipherInit(opmode, key, params, random);
|
|
|
|
log("initialized with key and AlgorithmParameterSpec");
|
|
}
|
|
|
|
@Override
|
|
protected void engineInit(int opmode, Key key,
|
|
AlgorithmParameters params, SecureRandom random)
|
|
throws InvalidKeyException, InvalidAlgorithmParameterException {
|
|
|
|
AlgorithmParameterSpec spec;
|
|
|
|
try {
|
|
|
|
if (this.cipherMode == CipherMode.WC_GCM) {
|
|
spec = params.getParameterSpec(GCMParameterSpec.class);
|
|
}
|
|
else {
|
|
spec = params.getParameterSpec(IvParameterSpec.class);
|
|
}
|
|
|
|
log("initialized with key and AlgorithmParameters");
|
|
|
|
} catch (InvalidParameterSpecException ipe) {
|
|
throw new InvalidAlgorithmParameterException(ipe);
|
|
}
|
|
|
|
wolfCryptCipherInit(opmode, key, spec, random);
|
|
}
|
|
|
|
/* return 1 if this is a block cipher, otherwise 0 */
|
|
private boolean isBlockCipher() {
|
|
|
|
boolean isBlockCipher = false;
|
|
|
|
switch (this.cipherType) {
|
|
case WC_AES:
|
|
case WC_DES3:
|
|
isBlockCipher = true;
|
|
break;
|
|
default:
|
|
isBlockCipher = false;
|
|
break;
|
|
};
|
|
|
|
return isBlockCipher;
|
|
}
|
|
|
|
/* return 1 if cipher is a block cipher and lenth is a block
|
|
* length multiple, otherwise 0 */
|
|
private int isValidBlockLength(int length) {
|
|
|
|
/* skip if not a block cipher */
|
|
if (isBlockCipher() == false) {
|
|
return 1;
|
|
}
|
|
|
|
if ((length % this.blockSize) != 0) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
private byte[] wolfCryptUpdate(byte[] input, int inputOffset, int len)
|
|
throws IllegalArgumentException {
|
|
|
|
int blocks = 0;
|
|
int remaining = 0;
|
|
int bytesToProcess = 0;
|
|
byte[] output = null;
|
|
byte[] tmpIn = null;
|
|
byte[] tmpBuf = null;
|
|
|
|
if (input == null || len < 0)
|
|
throw new IllegalArgumentException("Null input buffer or len < 0");
|
|
|
|
this.operationStarted = true;
|
|
|
|
if ((buffered.length + len) == 0) {
|
|
/* no data to process */
|
|
return null;
|
|
}
|
|
|
|
if (len > 0) {
|
|
/* add input bytes to buffered */
|
|
tmpIn = new byte[buffered.length + len];
|
|
System.arraycopy(buffered, 0, tmpIn, 0, buffered.length);
|
|
System.arraycopy(input, inputOffset, tmpIn, buffered.length, len);
|
|
buffered = tmpIn;
|
|
}
|
|
|
|
/* keep buffered data if RSA or data is less than block size, or doing
|
|
* AES-GCM without stream mode compiled natively */
|
|
if ((cipherType == CipherType.WC_RSA) ||
|
|
((cipherType == CipherType.WC_AES) &&
|
|
(cipherMode == CipherMode.WC_GCM)) ||
|
|
(buffered.length < blockSize)) {
|
|
return new byte[0];
|
|
}
|
|
|
|
/* calculate blocks and partial non-block size remaining */
|
|
blocks = buffered.length / blockSize;
|
|
remaining = buffered.length % blockSize;
|
|
bytesToProcess = blocks * blockSize;
|
|
|
|
/* if PKCS#5/7 padding, and decrypting, hold on to last block for
|
|
* padding check in wolfCryptFinal() */
|
|
if (paddingType == PaddingType.WC_PKCS5 &&
|
|
direction == OpMode.WC_DECRYPT) {
|
|
bytesToProcess -= blockSize;
|
|
}
|
|
|
|
/* not enough data to process yet return until more or final */
|
|
if (bytesToProcess == 0) {
|
|
return new byte[0];
|
|
}
|
|
|
|
tmpIn = new byte[bytesToProcess];
|
|
System.arraycopy(buffered, 0, tmpIn, 0, bytesToProcess);
|
|
|
|
/* buffer remaining non-block size input, or reset */
|
|
tmpBuf = new byte[buffered.length - bytesToProcess];
|
|
System.arraycopy(buffered, bytesToProcess, tmpBuf, 0, tmpBuf.length);
|
|
buffered = tmpBuf;
|
|
|
|
/* process tmpIn[] */
|
|
switch (this.cipherType) {
|
|
|
|
/* Only CBC mode reaches this point currently, GCM caches all
|
|
* data internally above until final call */
|
|
case WC_AES:
|
|
output = this.aes.update(tmpIn, 0, tmpIn.length);
|
|
|
|
/* truncate */
|
|
output = Arrays.copyOfRange(output, 0, tmpIn.length);
|
|
|
|
break;
|
|
|
|
case WC_DES3:
|
|
output = this.des3.update(tmpIn, 0, tmpIn.length);
|
|
|
|
/* truncate */
|
|
output = Arrays.copyOfRange(output, 0, tmpIn.length);
|
|
|
|
break;
|
|
|
|
default:
|
|
throw new RuntimeException("Unsupported algorithm type");
|
|
};
|
|
|
|
if (output == null) {
|
|
/* For interop compatibility, return empty byte array */
|
|
output = new byte[0];
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
private byte[] wolfCryptFinal(byte[] input, int inputOffset, int len)
|
|
throws IllegalBlockSizeException, BadPaddingException {
|
|
|
|
int totalSz = 0;
|
|
byte tmpIn[] = null;
|
|
byte tmpOut[] = null;
|
|
|
|
this.operationStarted = true;
|
|
totalSz = buffered.length + len;
|
|
|
|
/* AES-GCM does not require block size inputs */
|
|
if (isBlockCipher() &&
|
|
(cipherMode != CipherMode.WC_GCM) &&
|
|
(this.direction == OpMode.WC_DECRYPT ||
|
|
(this.direction == OpMode.WC_ENCRYPT &&
|
|
this.paddingType != PaddingType.WC_PKCS5)) &&
|
|
(totalSz % blockSize != 0)) {
|
|
throw new IllegalBlockSizeException(
|
|
"Input length not multiple of " + blockSize + " bytes");
|
|
}
|
|
|
|
/* do final encrypt over totalSz */
|
|
tmpIn = new byte[totalSz];
|
|
System.arraycopy(buffered, 0, tmpIn, 0, buffered.length);
|
|
if (input != null && len > 0) {
|
|
System.arraycopy(input, inputOffset, tmpIn, buffered.length, len);
|
|
}
|
|
|
|
/* add padding if encrypting and PKCS5 padding is used. PKCS#5 padding
|
|
* is treated the same as PKCS#7 padding here, using each algorithm's
|
|
* specific block size */
|
|
if (this.direction == OpMode.WC_ENCRYPT &&
|
|
this.paddingType == PaddingType.WC_PKCS5) {
|
|
if (this.cipherType == CipherType.WC_AES) {
|
|
tmpIn = Aes.padPKCS7(tmpIn, Aes.BLOCK_SIZE);
|
|
} else if (this.cipherType == CipherType.WC_DES3) {
|
|
tmpIn = Des3.padPKCS7(tmpIn, Des3.BLOCK_SIZE);
|
|
}
|
|
}
|
|
|
|
switch (this.cipherType) {
|
|
|
|
case WC_AES:
|
|
if (cipherMode == CipherMode.WC_GCM) {
|
|
if (this.direction == OpMode.WC_ENCRYPT) {
|
|
byte[] tag = new byte[this.gcmTagLen];
|
|
tmpOut = this.aesGcm.encrypt(tmpIn, this.iv, tag,
|
|
this.aadData);
|
|
|
|
/* Concatenate auth tag to end of ciphertext */
|
|
byte[] totalOut = new byte[tmpOut.length + tag.length];
|
|
System.arraycopy(tmpOut, 0, totalOut, 0, tmpOut.length);
|
|
System.arraycopy(tag, 0, totalOut, tmpOut.length,
|
|
tag.length);
|
|
tmpOut = totalOut;
|
|
}
|
|
else {
|
|
/* Get auth tag from end of ciphertext */
|
|
byte[] tag = Arrays.copyOfRange(tmpIn,
|
|
tmpIn.length - this.gcmTagLen,
|
|
tmpIn.length);
|
|
|
|
/* Shrink ciphertext array down to not include tag */
|
|
tmpIn = Arrays.copyOfRange(tmpIn, 0,
|
|
tmpIn.length - this.gcmTagLen);
|
|
|
|
tmpOut = this.aesGcm.decrypt(tmpIn, this.iv, tag,
|
|
this.aadData);
|
|
}
|
|
}
|
|
else {
|
|
tmpOut = this.aes.update(tmpIn, 0, tmpIn.length);
|
|
|
|
/* truncate */
|
|
tmpOut = Arrays.copyOfRange(tmpOut, 0, tmpIn.length);
|
|
}
|
|
|
|
/* strip PKCS#5/PKCS#7 padding if required */
|
|
if (this.direction == OpMode.WC_DECRYPT &&
|
|
this.paddingType == PaddingType.WC_PKCS5) {
|
|
tmpOut = Aes.unPadPKCS7(tmpOut, Aes.BLOCK_SIZE);
|
|
}
|
|
|
|
break;
|
|
|
|
case WC_DES3:
|
|
tmpOut = this.des3.update(tmpIn, 0, tmpIn.length);
|
|
|
|
/* truncate */
|
|
tmpOut = Arrays.copyOfRange(tmpOut, 0, tmpIn.length);
|
|
|
|
/* strip PKCS#5/PKCS#7 padding if required */
|
|
if (this.direction == OpMode.WC_DECRYPT &&
|
|
this.paddingType == PaddingType.WC_PKCS5) {
|
|
tmpOut = Des3.unPadPKCS7(tmpOut, Des3.BLOCK_SIZE);
|
|
}
|
|
|
|
break;
|
|
|
|
case WC_RSA:
|
|
|
|
if (this.direction == OpMode.WC_ENCRYPT) {
|
|
|
|
if (this.rsaKeyType == RsaKeyType.WC_RSA_PRIVATE) {
|
|
tmpOut = this.rsa.sign(tmpIn, this.rng);
|
|
|
|
} else {
|
|
tmpOut = this.rsa.encrypt(tmpIn, this.rng);
|
|
}
|
|
|
|
} else {
|
|
if (this.rsaKeyType == RsaKeyType.WC_RSA_PRIVATE) {
|
|
tmpOut = this.rsa.decrypt(tmpIn);
|
|
} else {
|
|
tmpOut = this.rsa.verify(tmpIn);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw new RuntimeException("Unsupported algorithm type");
|
|
};
|
|
|
|
/* reset state, user doesn't need to call init again before use */
|
|
try {
|
|
buffered = new byte[0];
|
|
|
|
if (this.direction == OpMode.WC_ENCRYPT) {
|
|
wolfCryptSetDirection(Cipher.ENCRYPT_MODE);
|
|
} else {
|
|
wolfCryptSetDirection(Cipher.DECRYPT_MODE);
|
|
}
|
|
|
|
InitializeNativeStructs();
|
|
wolfCryptSetIV(storedSpec, null);
|
|
wolfCryptSetKey(storedKey);
|
|
|
|
this.aadData = null;
|
|
this.operationStarted = false;
|
|
this.cipherInitialized = true;
|
|
|
|
} catch (InvalidKeyException e) {
|
|
throw new RuntimeException(e.getMessage());
|
|
} catch (InvalidAlgorithmParameterException e) {
|
|
throw new RuntimeException(e.getMessage());
|
|
}
|
|
|
|
return tmpOut;
|
|
}
|
|
|
|
@Override
|
|
protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen)
|
|
throws IllegalStateException {
|
|
|
|
byte output[];
|
|
|
|
if (!this.cipherInitialized) {
|
|
throw new IllegalStateException(
|
|
"Cipher has not been initialized yet");
|
|
}
|
|
|
|
log("update (offset: " + inputOffset + ", len: " + inputLen + ")");
|
|
|
|
output = wolfCryptUpdate(input, inputOffset, inputLen);
|
|
|
|
return output;
|
|
}
|
|
|
|
@Override
|
|
protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
|
|
byte[] output, int outputOffset)
|
|
throws IllegalStateException, ShortBufferException {
|
|
|
|
byte tmpOut[];
|
|
|
|
if (!this.cipherInitialized) {
|
|
throw new IllegalStateException(
|
|
"Cipher has not been initialized yet");
|
|
}
|
|
|
|
log("update (in offset: " + inputOffset + ", len: " +
|
|
inputLen + ", out offset: " + outputOffset + ")");
|
|
|
|
tmpOut = wolfCryptUpdate(input, inputOffset, inputLen);
|
|
if (tmpOut == null) {
|
|
return 0;
|
|
}
|
|
System.arraycopy(tmpOut, 0, output, outputOffset, tmpOut.length);
|
|
|
|
return tmpOut.length;
|
|
}
|
|
|
|
private void zeroArray(byte[] in) {
|
|
|
|
if (in == null)
|
|
return;
|
|
|
|
for (int i = 0; i < in.length; i++) {
|
|
in[i] = 0;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected byte[] engineDoFinal(byte[] input, int inputOffset,
|
|
int inputLen)
|
|
throws IllegalStateException, IllegalBlockSizeException,
|
|
BadPaddingException {
|
|
|
|
if (!this.cipherInitialized) {
|
|
throw new IllegalStateException(
|
|
"Cipher has not been initialized yet");
|
|
}
|
|
|
|
log("final (offset: " + inputOffset + ", len: " + inputLen + ")");
|
|
|
|
return wolfCryptFinal(input, inputOffset, inputLen);
|
|
}
|
|
|
|
@Override
|
|
protected int engineDoFinal(byte[] input, int inputOffset,
|
|
int inputLen, byte[] output, int outputOffset)
|
|
throws IllegalStateException, ShortBufferException,
|
|
IllegalBlockSizeException, BadPaddingException {
|
|
|
|
byte tmpOut[];
|
|
|
|
if (!this.cipherInitialized) {
|
|
throw new IllegalStateException(
|
|
"Cipher has not been initialized yet");
|
|
}
|
|
|
|
log("final (in offset: " + inputOffset + ", len: " +
|
|
inputLen + ", out offset: " + outputOffset + ")");
|
|
|
|
tmpOut = wolfCryptFinal(input, inputOffset, inputLen);
|
|
|
|
System.arraycopy(tmpOut, 0, output, outputOffset, tmpOut.length);
|
|
|
|
return tmpOut.length;
|
|
}
|
|
|
|
@Override
|
|
protected int engineGetKeySize(Key key)
|
|
throws InvalidKeyException {
|
|
|
|
byte encodedKey[] = null;
|
|
|
|
/* validate key class type */
|
|
if (this.cipherType == CipherType.WC_RSA) {
|
|
|
|
if (key instanceof RSAPrivateKey) {
|
|
this.rsaKeyType = RsaKeyType.WC_RSA_PRIVATE;
|
|
|
|
} else if (key instanceof RSAPublicKey) {
|
|
this.rsaKeyType = RsaKeyType.WC_RSA_PUBLIC;
|
|
|
|
} else {
|
|
throw new InvalidKeyException(
|
|
"Cipher key must be of type RSAPrivateKey or " +
|
|
"RSAPublicKey when used for RSA encrypt or decrypt");
|
|
}
|
|
|
|
} else if (!(key instanceof SecretKey)) {
|
|
throw new InvalidKeyException(
|
|
"Cipher key must be of type SecretKey");
|
|
}
|
|
|
|
encodedKey = key.getEncoded();
|
|
if (encodedKey == null)
|
|
throw new InvalidKeyException("Key does not support encoding");
|
|
|
|
return encodedKey.length;
|
|
}
|
|
|
|
@Override
|
|
protected void engineUpdateAAD(byte[] src, int offset, int len)
|
|
throws IllegalArgumentException, IllegalStateException {
|
|
|
|
if (this.cipherType != CipherType.WC_AES ||
|
|
this.cipherMode != CipherMode.WC_GCM) {
|
|
throw new IllegalStateException(
|
|
"AAD only supported for AES-GCM");
|
|
}
|
|
|
|
if (this.operationStarted) {
|
|
throw new IllegalStateException(
|
|
"Must set AAD before calling Cipher.update/final");
|
|
}
|
|
|
|
if (!this.cipherInitialized || this.aesGcm == null) {
|
|
throw new IllegalStateException(
|
|
"Cipher not initialized yet");
|
|
}
|
|
|
|
if (src == null || offset < 0 || len < 0 ||
|
|
(src.length < (offset + len))) {
|
|
throw new IllegalArgumentException(
|
|
"Source buffer is null or bad offset/len");
|
|
}
|
|
|
|
if (this.aadData == null) {
|
|
/* Store as new array inside object */
|
|
this.aadData = new byte[len];
|
|
System.arraycopy(src, offset, this.aadData, 0, len);
|
|
}
|
|
else {
|
|
/* Append to existing AAD array held inside object */
|
|
byte[] tmp = new byte[this.aadData.length + len];
|
|
System.arraycopy(this.aadData, 0, tmp, 0, this.aadData.length);
|
|
System.arraycopy(src, offset, tmp, this.aadData.length, len);
|
|
this.aadData = tmp;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void engineUpdateAAD(ByteBuffer src)
|
|
throws IllegalArgumentException, IllegalStateException {
|
|
|
|
int originalPos = 0;
|
|
byte[] remaining = null;
|
|
|
|
if (src == null) {
|
|
throw new IllegalArgumentException("Source buffer is null");
|
|
}
|
|
|
|
originalPos = src.position();
|
|
remaining = new byte[src.remaining()];
|
|
|
|
src.get(remaining);
|
|
|
|
try {
|
|
engineUpdateAAD(remaining, 0, remaining.length);
|
|
} catch (IllegalStateException | IllegalArgumentException e) {
|
|
/* restore state of ByteBuffer on state error before returning */
|
|
src.position(originalPos);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
private String typeToString(CipherType type) {
|
|
switch (type) {
|
|
case WC_AES:
|
|
return "AES";
|
|
case WC_DES3:
|
|
return "3DES";
|
|
case WC_RSA:
|
|
return "RSA";
|
|
default:
|
|
return "None";
|
|
}
|
|
}
|
|
|
|
private String modeToString(CipherMode type) {
|
|
switch (type) {
|
|
case WC_ECB:
|
|
return "ECB";
|
|
case WC_CBC:
|
|
return "CBC";
|
|
case WC_GCM:
|
|
return "GCM";
|
|
default:
|
|
return "None";
|
|
}
|
|
}
|
|
|
|
private void log(String msg) {
|
|
WolfCryptDebug.print("[Cipher, " + algString + "-" +
|
|
algMode + "] " + msg);
|
|
}
|
|
|
|
@SuppressWarnings("deprecation")
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
try {
|
|
if (this.aes != null) {
|
|
this.aes.releaseNativeStruct();
|
|
this.aes = null;
|
|
}
|
|
|
|
if (this.aesGcm != null) {
|
|
this.aesGcm.releaseNativeStruct();
|
|
this.aesGcm = null;
|
|
}
|
|
|
|
if (this.des3 != null) {
|
|
this.des3.releaseNativeStruct();
|
|
this.des3 = null;
|
|
}
|
|
|
|
if (this.rsa != null) {
|
|
this.rsa.releaseNativeStruct();
|
|
this.rsa = null;
|
|
}
|
|
|
|
if (this.rng != null) {
|
|
this.rng.releaseNativeStruct();
|
|
this.rng = null;
|
|
}
|
|
|
|
zeroArray(this.iv);
|
|
|
|
this.storedKey = null;
|
|
this.storedSpec = null;
|
|
|
|
} finally {
|
|
super.finalize();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class for AES-CBC with no padding
|
|
*/
|
|
public static final class wcAESCBCNoPadding extends WolfCryptCipher {
|
|
/**
|
|
* Create new wcAESCBCNoPadding object
|
|
*/
|
|
public wcAESCBCNoPadding() {
|
|
super(CipherType.WC_AES, CipherMode.WC_CBC, PaddingType.WC_NONE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class for AES-CBC with PKCS#5 padding
|
|
*/
|
|
public static final class wcAESCBCPKCS5Padding extends WolfCryptCipher {
|
|
/**
|
|
* Create new wcAESCBCPkcs5Padding object
|
|
*/
|
|
public wcAESCBCPKCS5Padding() {
|
|
super(CipherType.WC_AES, CipherMode.WC_CBC, PaddingType.WC_PKCS5);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class for AES-GCM with no padding
|
|
*/
|
|
public static final class wcAESGCMNoPadding extends WolfCryptCipher {
|
|
/**
|
|
* Create new wcAESGCMNoPadding object
|
|
*/
|
|
public wcAESGCMNoPadding() {
|
|
super(CipherType.WC_AES, CipherMode.WC_GCM, PaddingType.WC_NONE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class for DES-EDE-CBC with no padding
|
|
*/
|
|
public static final class wcDESedeCBCNoPadding extends WolfCryptCipher {
|
|
/**
|
|
* Create new wcDESedeCBCNoPadding object
|
|
*/
|
|
public wcDESedeCBCNoPadding() {
|
|
super(CipherType.WC_DES3, CipherMode.WC_CBC, PaddingType.WC_NONE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class for RSA-ECB with PKCS1 padding
|
|
*/
|
|
public static final class wcRSAECBPKCS1Padding extends WolfCryptCipher {
|
|
/**
|
|
* Create new wcRSAECBPKCS1Padding object
|
|
*/
|
|
public wcRSAECBPKCS1Padding() {
|
|
super(CipherType.WC_RSA, CipherMode.WC_ECB, PaddingType.WC_PKCS1);
|
|
}
|
|
}
|
|
}
|
|
|