wolfcrypt-jni/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java

2516 lines
86 KiB
Java

/* WolfCryptCipher.java
*
* Copyright (C) 2006-2026 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 3 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 java.io.ByteArrayOutputStream;
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.AEADBadTagException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.MessageDigest;
import java.security.KeyFactory;
import java.security.spec.MGF1ParameterSpec;
import java.security.AlgorithmParameters;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidParameterException;
import java.security.InvalidKeyException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import com.wolfssl.wolfcrypt.Aes;
import com.wolfssl.wolfcrypt.AesEcb;
import com.wolfssl.wolfcrypt.AesCtr;
import com.wolfssl.wolfcrypt.AesOfb;
import com.wolfssl.wolfcrypt.AesGcm;
import com.wolfssl.wolfcrypt.AesCcm;
import com.wolfssl.wolfcrypt.AesCts;
import com.wolfssl.wolfcrypt.Des3;
import com.wolfssl.wolfcrypt.Rsa;
import com.wolfssl.wolfcrypt.Rng;
import com.wolfssl.wolfcrypt.Sha256;
import com.wolfssl.wolfcrypt.Sha512;
import com.wolfssl.wolfcrypt.FeatureDetect;
import com.wolfssl.wolfcrypt.WolfCrypt;
import com.wolfssl.wolfcrypt.WolfCryptError;
import com.wolfssl.wolfcrypt.WolfCryptException;
/**
* 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_CTR,
WC_OFB,
WC_GCM,
WC_CCM,
WC_CTS
}
enum PaddingType {
WC_NONE,
WC_PKCS1,
WC_PKCS5,
WC_OAEP_SHA1,
WC_OAEP_SHA256
}
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;
/* Store original opmode (ENCRYPT, DECRYPT, WRAP, UNWRAP), used by
* post-doFinal reset to restore correct mode */
private int storedOpMode = 0;
private int blockSize = 0;
private Aes aes = null;
private AesEcb aesEcb = null;
private AesCtr aesCtr = null;
private AesOfb aesOfb = null;
private AesGcm aesGcm = null;
private AesCcm aesCcm = null;
private AesCts aesCts = null;
private Des3 des3 = null;
private Rsa rsa = null;
private Rng rng = null;
/* RSA-OAEP parameters */
private int oaepHashType = 0;
private int oaepMgf = 0;
/* 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/CCM tag length (bytes), default to 128 bits */
private int gcmTagLen = 16;
/* AAD data for AES-GCM/CCM, accumulated via engineUpdateAAD() */
private ByteArrayOutputStream aadStream = null;
/* Last (key, IV) set for AES-GCM encryption at init time, tracked to
* reject GCM nonce reuse on re-initialization. A digest of the encoded
* key (SHA-512, or SHA-256 when SHA-512 is not compiled in) is stored
* instead of the key bytes to avoid keeping an extra copy of key
* material in memory. */
private byte[] lastGcmEncryptKeyHash = null;
private byte[] lastGcmEncryptIv = null;
/* Set when an AES-GCM encryption completes, cleared by init. A second
* encryption without re-init would reuse the same key and IV */
private boolean gcmEncryptNeedsReinit = false;
/* 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, only the first bufferedLen bytes
* are valid. Capacity grows to max(2 * capacity, needed). */
private byte[] buffered = new byte[0];
private int bufferedLen = 0;
/* Max buffered size. Below Integer.MAX_VALUE, which is not allocatable
* in full because VMs reserve array header words. */
private static final int MAX_BUFFERED_SIZE = Integer.MAX_VALUE - 8;
/* Capacity above this is released on reset instead of kept, so one
* large message does not pin memory for the life of the Cipher. */
private static final int MAX_RETAINED_SIZE = 64 * 1024;
/**
* Append len bytes of src to the buffered data.
*
* Any buffer replaced during growth is zeroized before being dropped.
*
* @param src array to append from
* @param offset offset into src to start at
* @param len number of bytes to append
*
* @throws IllegalArgumentException if the total buffered size would
* exceed MAX_BUFFERED_SIZE
*/
private void bufferedAppend(byte[] src, int offset, int len) {
int newCap, needed;
if (len > (MAX_BUFFERED_SIZE - this.bufferedLen)) {
throw new IllegalArgumentException(
"Buffered input would exceed maximum size of " +
MAX_BUFFERED_SIZE + " bytes");
}
needed = this.bufferedLen + len;
if (needed > this.buffered.length) {
newCap = this.buffered.length * 2;
if (newCap < 0 || newCap > MAX_BUFFERED_SIZE) {
/* Doubling overflowed or passed the cap, pin to max */
newCap = MAX_BUFFERED_SIZE;
}
if (newCap < needed) {
/* Single append larger than double, cap at needed */
newCap = needed;
}
byte[] tmp = new byte[newCap];
System.arraycopy(this.buffered, 0, tmp, 0, this.bufferedLen);
Arrays.fill(this.buffered, 0, this.bufferedLen, (byte)0);
this.buffered = tmp;
}
System.arraycopy(src, offset, this.buffered, this.bufferedLen, len);
this.bufferedLen = needed;
}
/**
* Drop and zeroize all buffered data.
*
* Bytes at or past bufferedLen are always zero already, so only the
* used prefix is cleared. Capacity is kept for reuse unless it is
* above MAX_RETAINED_SIZE, which is released instead.
*/
private void bufferedReset() {
Arrays.fill(this.buffered, 0, this.bufferedLen, (byte)0);
this.bufferedLen = 0;
if (this.buffered.length > MAX_RETAINED_SIZE) {
this.buffered = new byte[0];
}
}
/**
* Drop the first count bytes, shifting the remainder to the front.
*
* @param count bytes to drop, must be between 0 and bufferedLen
*/
private void bufferedConsume(int count) {
int remaining = this.bufferedLen - count;
System.arraycopy(this.buffered, count, this.buffered, 0, remaining);
Arrays.fill(this.buffered, remaining, this.bufferedLen, (byte)0);
this.bufferedLen = remaining;
}
private WolfCryptCipher(CipherType type, CipherMode mode,
PaddingType pad) {
this.cipherType = type;
this.cipherMode = mode;
this.paddingType = pad;
/* Initialize OAEP parameters if using OAEP padding */
if (pad == PaddingType.WC_OAEP_SHA256) {
initOaepParams();
} else if (pad == PaddingType.WC_OAEP_SHA1) {
initOaepParamsSha1();
}
switch (cipherType) {
case WC_AES:
blockSize = Aes.BLOCK_SIZE;
break;
case WC_DES3:
blockSize = Des3.BLOCK_SIZE;
break;
case WC_RSA:
break;
}
if (WolfCryptDebug.DEBUG) {
algString = typeToString(cipherType);
algMode = modeToString(cipherMode);
}
}
/**
* Initialize OAEP parameters for RSA-OAEP padding.
* Uses SHA-256 for OAEP hash and SHA-1 for MGF1 hash to match
* JCE default behavior for OAEPWithSHA-256AndMGF1Padding.
*/
private void initOaepParams() {
this.oaepHashType = WolfCrypt.WC_HASH_TYPE_SHA256;
this.oaepMgf = Rsa.WC_MGF1SHA1;
}
/**
* Initialize OAEP parameters for RSA-OAEP padding with SHA-1.
* Uses SHA-1 for OAEP hash and SHA-1 for MGF1 hash to match
* JCE default behavior for OAEPWithSHA-1AndMGF1Padding.
*/
private void initOaepParamsSha1() {
this.oaepHashType = WolfCrypt.WC_HASH_TYPE_SHA;
this.oaepMgf = Rsa.WC_MGF1SHA1;
}
/**
* Convert JCE hash algorithm name to wolfCrypt hash type constant.
*
* @param hashAlgo JCE hash algorithm name (e.g., "SHA-256", "SHA-1")
* @return wolfCrypt hash type constant
* @throws InvalidAlgorithmParameterException if hash algorithm is not
* supported
*/
private int hashNameToWolfCryptType(String hashAlgo)
throws InvalidAlgorithmParameterException {
if (hashAlgo == null) {
throw new InvalidAlgorithmParameterException(
"Hash algorithm name cannot be null");
}
switch (hashAlgo.toUpperCase()) {
case "SHA-1":
case "SHA1":
return WolfCrypt.WC_HASH_TYPE_SHA;
case "SHA-224":
case "SHA224":
return WolfCrypt.WC_HASH_TYPE_SHA224;
case "SHA-256":
case "SHA256":
return WolfCrypt.WC_HASH_TYPE_SHA256;
case "SHA-384":
case "SHA384":
return WolfCrypt.WC_HASH_TYPE_SHA384;
case "SHA-512":
case "SHA512":
return WolfCrypt.WC_HASH_TYPE_SHA512;
default:
throw new InvalidAlgorithmParameterException(
"Unsupported OAEP hash algorithm: " + hashAlgo);
}
}
/**
* Convert MGF1ParameterSpec to wolfCrypt MGF type constant.
*
* @param mgfSpec MGF1ParameterSpec containing the hash algorithm
* @return wolfCrypt MGF type constant
* @throws InvalidAlgorithmParameterException if MGF hash algorithm is not
* supported
*/
private int mgf1SpecToWolfCryptMgf(MGF1ParameterSpec mgfSpec)
throws InvalidAlgorithmParameterException {
if (mgfSpec == null) {
throw new InvalidAlgorithmParameterException(
"MGF1ParameterSpec cannot be null");
}
String hashAlgo = mgfSpec.getDigestAlgorithm();
switch (hashAlgo.toUpperCase()) {
case "SHA-1":
case "SHA1":
return Rsa.WC_MGF1SHA1;
case "SHA-224":
case "SHA224":
return Rsa.WC_MGF1SHA224;
case "SHA-256":
case "SHA256":
return Rsa.WC_MGF1SHA256;
case "SHA-384":
case "SHA384":
return Rsa.WC_MGF1SHA384;
case "SHA-512":
case "SHA512":
return Rsa.WC_MGF1SHA512;
default:
throw new InvalidAlgorithmParameterException(
"Unsupported MGF1 hash algorithm: " + hashAlgo);
}
}
/**
* Set OAEP parameters from OAEPParameterSpec.
*
* @param spec OAEPParameterSpec containing OAEP parameters
* @throws InvalidAlgorithmParameterException if parameters are invalid
*/
@SuppressWarnings("deprecation")
private void setOaepParams(OAEPParameterSpec spec)
throws InvalidAlgorithmParameterException {
AlgorithmParameterSpec mgfParams = null;
PSource pSource = null;
if (spec == null) {
throw new InvalidAlgorithmParameterException(
"OAEPParameterSpec cannot be null");
}
/* Validate MGF algorithm is MGF1 */
if (!spec.getMGFAlgorithm().equals("MGF1") &&
!spec.getMGFAlgorithm().equals(OAEPParameterSpec.DEFAULT.
getMGFAlgorithm())) {
throw new InvalidAlgorithmParameterException(
"Only MGF1 is supported for OAEP, got: " +
spec.getMGFAlgorithm());
}
/* Get MGF parameters */
mgfParams = spec.getMGFParameters();
if (!(mgfParams instanceof MGF1ParameterSpec)) {
throw new InvalidAlgorithmParameterException(
"MGF parameters must be MGF1ParameterSpec");
}
/* Validate PSource is PSpecified with empty label (default) */
pSource = spec.getPSource();
if (pSource != null && pSource instanceof PSource.PSpecified) {
byte[] label = ((PSource.PSpecified) pSource).getValue();
if (label != null && label.length > 0) {
throw new InvalidAlgorithmParameterException(
"OAEP label (PSource) must be empty, custom labels " +
"are not supported");
}
}
/* Set OAEP hash type */
this.oaepHashType = hashNameToWolfCryptType(spec.getDigestAlgorithm());
/* Set MGF type */
this.oaepMgf = mgf1SpecToWolfCryptMgf((MGF1ParameterSpec) mgfParams);
log("set OAEP params: hash=" + spec.getDigestAlgorithm() +
", mgf1Hash=" + ((MGF1ParameterSpec) mgfParams).
getDigestAlgorithm());
}
/**
* 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_ECB) {
if (aesEcb != null) {
aesEcb.releaseNativeStruct();
aesEcb = null;
}
aesEcb = new AesEcb();
}
else if (cipherMode == CipherMode.WC_CTR) {
if (aesCtr != null) {
aesCtr.releaseNativeStruct();
aesCtr = null;
}
aesCtr = new AesCtr();
}
else if (cipherMode == CipherMode.WC_OFB) {
if (aesOfb != null) {
aesOfb.releaseNativeStruct();
aesOfb = null;
}
aesOfb = new AesOfb();
}
else if (cipherMode == CipherMode.WC_GCM) {
if (aesGcm != null) {
aesGcm.releaseNativeStruct();
aesGcm = null;
}
aesGcm = new AesGcm();
}
else if (cipherMode == CipherMode.WC_CCM) {
if (aesCcm != null) {
aesCcm.releaseNativeStruct();
aesCcm = null;
}
aesCcm = new AesCcm();
}
else if (cipherMode == CipherMode.WC_CTS) {
if (aesCts != null) {
aesCts.releaseNativeStruct();
aesCts = null;
}
aesCts = new AesCts();
}
break;
case WC_DES3:
if (des3 != null) {
des3.releaseNativeStruct();
des3 = null;
}
des3 = new Des3();
break;
case WC_RSA:
/* RSA struct creation handled in wolfCryptSetKey() */
break;
}
}
@Override
protected void engineSetMode(String mode)
throws NoSuchAlgorithmException {
int supported = 0;
if (mode.equals("ECB")) {
/* RSA and AES support ECB mode */
if (cipherType == CipherType.WC_RSA ||
cipherType == CipherType.WC_AES) {
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("CTR")) {
/* AES supports CTR */
if (cipherType == CipherType.WC_AES) {
cipherMode = CipherMode.WC_CTR;
supported = 1;
log("set mode to CTR");
}
} else if (mode.equals("OFB")) {
/* AES supports OFB */
if (cipherType == CipherType.WC_AES) {
cipherMode = CipherMode.WC_OFB;
supported = 1;
log("set mode to OFB");
}
} else if (mode.equals("GCM")) {
/* AES supports GCM */
if (cipherType == CipherType.WC_AES) {
cipherMode = CipherMode.WC_GCM;
supported = 1;
log("set mode to GCM");
}
} else if (mode.equals("CCM")) {
/* AES supports CCM */
if (cipherType == CipherType.WC_AES) {
cipherMode = CipherMode.WC_CCM;
supported = 1;
log("set mode to CCM");
}
} else if (mode.equals("CTS")) {
/* AES supports CTS */
if (cipherType == CipherType.WC_AES) {
cipherMode = CipherMode.WC_CTS;
supported = 1;
log("set mode to CTS");
}
}
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 ||
cipherMode == CipherMode.WC_ECB)) {
paddingType = PaddingType.WC_PKCS5;
supported = 1;
log("set padding to PKCS5Padding");
}
} else if (padding.equals("OAEPWithSHA-256AndMGF1Padding") ||
padding.equals("OAEPWithSHA256AndMGF1Padding")) {
if (cipherType == CipherType.WC_RSA) {
paddingType = PaddingType.WC_OAEP_SHA256;
initOaepParams();
supported = 1;
log("set padding to OAEPWithSHA-256AndMGF1Padding");
}
} else if (padding.equals("OAEPWithSHA-1AndMGF1Padding") ||
padding.equals("OAEPWithSHA1AndMGF1Padding")) {
if (cipherType == CipherType.WC_RSA) {
paddingType = PaddingType.WC_OAEP_SHA1;
initOaepParamsSha1();
supported = 1;
log("set padding to OAEPWithSHA-1AndMGF1Padding");
}
}
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 outSize = 0;
int totalSz = inputLen;
int totalBlocks = 0;
if (!this.cipherInitialized) {
throw new IllegalStateException(
"Cipher has not been initialized yet");
}
/* Add buffered data size to input length, calculate total blocks */
if (isBlockCipher()) {
if (bufferedLen > 0) {
totalSz = inputLen + bufferedLen;
} else {
totalSz = inputLen;
}
/* For block ciphers that require block boundaries, round
* to next block size. GCM, CCM, CTR, CTS, and OFB do not require
* block boundaries. */
if (cipherMode != CipherMode.WC_GCM &&
cipherMode != CipherMode.WC_CCM &&
cipherMode != CipherMode.WC_CTR &&
cipherMode != CipherMode.WC_CTS &&
cipherMode != CipherMode.WC_OFB) {
totalBlocks = totalSz / blockSize;
totalSz = totalBlocks * blockSize;
}
}
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, When decrypting, output
* size will have it taken off. */
if (this.direction == OpMode.WC_ENCRYPT) {
outSize = totalSz + this.gcmTagLen;
}
else {
outSize = totalSz - this.gcmTagLen;
}
outSize = Math.max(outSize, 0);
}
else {
/* wolfCrypt expects input to be padded by application
* to block size, thus output is same size as input.
* If we have buffered data, and that plus inputLen
* makes another block, we will have one more block of
* data. */
outSize = totalSz;
}
}
else if (paddingType == PaddingType.WC_PKCS5) {
outSize = inputLen;
if (bufferedLen > 0) {
outSize += bufferedLen;
}
/* Only add padding size when encrypting. When decrypting,
* the output size should not include padding bytes since
* they will be stripped off during decryption. */
if (this.direction == OpMode.WC_ENCRYPT) {
outSize += Aes.getPKCS7PadSize(outSize, 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 */
outSize = totalSz;
}
else if (paddingType == PaddingType.WC_PKCS5) {
outSize = inputLen;
if (bufferedLen > 0) {
outSize += bufferedLen;
}
/* Only add padding size when encrypting. When decrypting,
* the output size should not include padding bytes since
* they will be stripped off during decryption. */
if (this.direction == OpMode.WC_ENCRYPT) {
outSize += Des3.getPKCS7PadSize(outSize,
Des3.BLOCK_SIZE);
}
}
else {
throw new IllegalStateException(
"Unsupported padding mode for Cipher Des3");
}
break;
case WC_RSA:
outSize = this.rsa.getEncryptSize();
break;
}
return outSize;
}
@Override
protected byte[] engineGetIV() {
if (this.iv != null) {
return this.iv.clone();
}
return null;
}
@Override
protected AlgorithmParameters engineGetParameters() {
AlgorithmParameters params = null;
try {
switch (this.cipherMode) {
case WC_GCM:
case WC_CCM:
/* Return parameters only if initialized */
if (this.iv != null && this.gcmTagLen > 0) {
params = AlgorithmParameters.getInstance("GCM");
GCMParameterSpec gcmSpec = new GCMParameterSpec(
this.gcmTagLen * 8, this.iv);
params.init(gcmSpec);
}
break;
case WC_CBC:
case WC_CTR:
case WC_OFB:
if (this.iv != null) {
if (this.cipherType == CipherType.WC_AES) {
params = AlgorithmParameters.getInstance("AES");
}
else if (this.cipherType == CipherType.WC_DES3) {
params = AlgorithmParameters.getInstance("DESede");
}
if (params != null) {
IvParameterSpec ivSpec =
new IvParameterSpec(this.iv);
params.init(ivSpec);
}
}
break;
/* ECB mode doesn't have parameters to return */
case WC_ECB:
break;
}
} catch (NoSuchAlgorithmException |
InvalidParameterSpecException e) {
/* Return null if parameter creation fails */
params = null;
}
return params;
}
private void wolfCryptSetDirection(int opmode)
throws InvalidKeyException {
this.storedOpMode = opmode;
switch (opmode) {
case Cipher.ENCRYPT_MODE:
this.direction = OpMode.WC_ENCRYPT;
break;
case Cipher.DECRYPT_MODE:
this.direction = OpMode.WC_DECRYPT;
break;
case Cipher.WRAP_MODE:
this.direction = OpMode.WC_ENCRYPT;
break;
case Cipher.UNWRAP_MODE:
this.direction = OpMode.WC_DECRYPT;
break;
default:
throw new InvalidParameterException(
"Cipher opmode must be ENCRYPT_MODE, " +
"DECRYPT_MODE, WRAP_MODE, or UNWRAP_MODE");
}
}
/**
* Convert an AEAD tag length from bits to bytes.
*
* Rejects lengths that are not a whole number of bytes, since the byte
* count is what reaches native wolfSSL and a non-multiple of 8 would be
* silently truncated. Native validates which sizes the mode supports.
*
* @param modeName mode label used in the error message
* @param tagBits requested tag length in bits
*
* @return tag length in bytes
*
* @throws InvalidAlgorithmParameterException if tagBits is not a
* positive multiple of 8
*/
private static int tagLenToBytes(String modeName, int tagBits)
throws InvalidAlgorithmParameterException {
if (tagBits <= 0 || (tagBits % 8) != 0) {
throw new InvalidAlgorithmParameterException(
modeName + " tag length must be a positive multiple of " +
"8 bits, got " + tagBits);
}
return (tagBits / 8);
}
private void wolfCryptSetIV(AlgorithmParameterSpec spec,
SecureRandom random) throws InvalidAlgorithmParameterException {
/* store AlgorithmParameterSpec for class reset */
this.storedSpec = spec;
/* Handle RSA OAEP parameters if provided */
if (this.cipherType == CipherType.WC_RSA) {
if (spec != null) {
if (spec instanceof OAEPParameterSpec) {
if (this.paddingType != PaddingType.WC_OAEP_SHA256 &&
this.paddingType != PaddingType.WC_OAEP_SHA1) {
throw new InvalidAlgorithmParameterException(
"OAEPParameterSpec can only be used with " +
"OAEP padding modes");
}
setOaepParams((OAEPParameterSpec) spec);
} else {
throw new InvalidAlgorithmParameterException(
"AlgorithmParameterSpec for RSA OAEP must be of " +
"type OAEPParameterSpec");
}
}
return;
}
/* AES-ECB doesn't need an IV */
if (this.cipherType == CipherType.WC_AES &&
this.cipherMode == CipherMode.WC_ECB)
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();
this.gcmTagLen = tagLenToBytes("AES-GCM", gcmSpec.getTLen());
}
else if (cipherMode == CipherMode.WC_CCM) {
/*
* CCM Parameter Handling:
* We use GCMParameterSpec for CCM mode to maintain
* compatibility with:
* 1. Java 8+ (CCMParameterSpec only available in Java 11+)
* 2. BouncyCastle provider (uses GCMParameterSpec for CCM)
* 3. Existing developer expectations and code patterns
*/
if (!(spec instanceof GCMParameterSpec)) {
throw new InvalidAlgorithmParameterException(
"AlgorithmParameterSpec must be of type " +
"GCMParameterSpec for AES-CCM");
}
GCMParameterSpec ccmSpec = (GCMParameterSpec)spec;
if (ccmSpec.getIV() == null || ccmSpec.getIV().length == 0) {
throw new InvalidAlgorithmParameterException(
"AES-CCM nonce is null or 0 length");
}
/* CCM nonce length validation (7-15 bytes typical) */
if (ccmSpec.getIV().length < 7 || ccmSpec.getIV().length > 15) {
throw new InvalidAlgorithmParameterException(
"CCM nonce length must be 7-15 bytes, got: " +
ccmSpec.getIV().length);
}
this.iv = ccmSpec.getIV().clone();
this.gcmTagLen = tagLenToBytes("AES-CCM", ccmSpec.getTLen());
}
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 {
byte[] encodedKey;
/* validate key class type */
if (this.cipherType == CipherType.WC_RSA) {
if (key instanceof RSAPrivateKey) {
this.rsaKeyType = RsaKeyType.WC_RSA_PRIVATE;
/* wolfSSL requires CRT parameters for RSA private key
* operations. Non-CRT keys (created with only modulus and
* private exponent) will fail with "mp_exptmod error state"
* or similar in the native layer. */
if (!(key instanceof RSAPrivateCrtKey)) {
throw new InvalidKeyException(
"wolfSSL requires RSA private keys to include CRT " +
"parameters (p, q, dP, dQ, qInv). Keys created from " +
"only modulus and exponent are not supported.");
}
} 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");
}
try {
switch (cipherType) {
case WC_AES:
if (this.direction == OpMode.WC_ENCRYPT) {
if (cipherMode == CipherMode.WC_GCM) {
this.aesGcm.setKey(encodedKey);
}
else if (cipherMode == CipherMode.WC_CCM) {
this.aesCcm.setKey(encodedKey);
}
else if (cipherMode == CipherMode.WC_CTS) {
this.aesCts.setKey(
encodedKey, iv, AesCts.ENCRYPT_MODE);
}
else if (cipherMode == CipherMode.WC_ECB) {
this.aesEcb.setKey(
encodedKey, null, AesEcb.ENCRYPT_MODE);
}
else if (cipherMode == CipherMode.WC_CTR) {
this.aesCtr.setKey(encodedKey, iv);
}
else if (cipherMode == CipherMode.WC_OFB) {
this.aesOfb.setKey(
encodedKey, iv, AesOfb.ENCRYPT_MODE);
}
else {
this.aes.setKey(encodedKey, iv, Aes.ENCRYPT_MODE);
}
} else {
if (cipherMode == CipherMode.WC_GCM) {
this.aesGcm.setKey(encodedKey);
}
else if (cipherMode == CipherMode.WC_CCM) {
this.aesCcm.setKey(encodedKey);
}
else if (cipherMode == CipherMode.WC_CTS) {
this.aesCts.setKey(
encodedKey, iv, AesCts.DECRYPT_MODE);
}
else if (cipherMode == CipherMode.WC_ECB) {
this.aesEcb.setKey(
encodedKey, null, AesEcb.DECRYPT_MODE);
}
else if (cipherMode == CipherMode.WC_CTR) {
this.aesCtr.setKey(encodedKey, iv);
}
else if (cipherMode == CipherMode.WC_OFB) {
this.aesOfb.setKey(
encodedKey, iv, AesOfb.ENCRYPT_MODE);
}
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();
if (this.rng == null) {
this.rng = new Rng();
this.rng.init();
}
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;
}
} finally {
zeroArray(encodedKey);
}
}
/**
* Reject AES-GCM encrypt init with the same key+IV as the last encrypt
* init used (GCM nonce reuse).
*
* Only the pair from the most recent encrypt init is tracked, so any
* intermediate encrypt init (key-only with a fresh random IV, or a
* different explicit IV) erases the memory of a previously used pair
* and a caller can deliberately reuse an earlier (key, IV) pair. This
* is a best-effort guard against accidental reuse, kept for SunJCE
* interoperability. Callers that opt out this way own IV uniqueness
* themselves.
*
* Called from wolfCryptCipherInit(). The new pair is recorded by
* recordGcmKeyIv() only after wolfCryptSetKey() accepts the key, so
* a failed init does not erase the tracked pair. Encrypting again
* without re-init is rejected separately via gcmEncryptNeedsReinit.
*
* @param key key this Cipher is being initialized with
*
* @throws InvalidAlgorithmParameterException if key and IV match the
* previous AES-GCM encrypt initialization
*/
private void checkGcmKeyIvReuse(Key key)
throws InvalidAlgorithmParameterException {
byte[] keyEnc = null;
if ((this.cipherMode != CipherMode.WC_GCM) ||
(this.direction != OpMode.WC_ENCRYPT) || (key == null)) {
return;
}
keyEnc = key.getEncoded();
if (keyEnc == null) {
/* wolfCryptSetKey() will reject this key */
return;
}
try {
if (this.lastGcmEncryptIv != null &&
MessageDigest.isEqual(this.lastGcmEncryptIv, this.iv) &&
MessageDigest.isEqual(this.lastGcmEncryptKeyHash,
hashKeyForGcmTracking(keyEnc))) {
throw new InvalidAlgorithmParameterException(
"Cannot reuse iv for GCM encryption");
}
} finally {
zeroArray(keyEnc);
}
}
/**
* Compute digest of encoded key bytes for GCM (key, IV) reuse
* tracking. A digest is stored and compared instead of the raw key
* bytes to avoid keeping an extra copy of key material in memory.
*
* SHA-512 is preferred to keep this compatible with CNSA 2.0
* deployments, which restrict hashing to SHA-384/SHA-512. SHA-256 is
* used as fallback when SHA-512 is not compiled into native wolfSSL.
*
* @param keyEnc encoded key bytes
*
* @return digest of keyEnc
*/
private static byte[] hashKeyForGcmTracking(byte[] keyEnc) {
if (FeatureDetect.Sha512Enabled()) {
Sha512 sha = new Sha512();
try {
sha.update(keyEnc);
return sha.digest();
} finally {
sha.releaseNativeStruct();
}
}
else {
Sha256 sha = new Sha256();
try {
sha.update(keyEnc);
return sha.digest();
} finally {
sha.releaseNativeStruct();
}
}
}
/**
* Record the (key, IV) pair used for this AES-GCM encrypt init,
* checked against by checkGcmKeyIvReuse() on the next encrypt init.
*
* Called from wolfCryptCipherInit() after wolfCryptSetKey() has
* accepted the key.
*
* @param key key this Cipher was initialized with
*/
private void recordGcmKeyIv(Key key) {
byte[] keyEnc = null;
if ((this.cipherMode != CipherMode.WC_GCM) ||
(this.direction != OpMode.WC_ENCRYPT) || (key == null)) {
return;
}
keyEnc = key.getEncoded();
if (keyEnc == null) {
return;
}
try {
zeroArray(this.lastGcmEncryptKeyHash);
this.lastGcmEncryptKeyHash = hashKeyForGcmTracking(keyEnc);
} finally {
zeroArray(keyEnc);
}
zeroArray(this.lastGcmEncryptIv);
if (this.iv == null) {
this.lastGcmEncryptIv = null;
} else {
this.lastGcmEncryptIv = this.iv.clone();
}
}
/* called by engineInit() functions */
private void wolfCryptCipherInit(int opmode, Key key,
AlgorithmParameterSpec spec, SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException {
/* Reset buffered data from any previous operation */
bufferedReset();
InitializeNativeStructs();
wolfCryptSetDirection(opmode);
wolfCryptSetIV(spec, random);
checkGcmKeyIvReuse(key);
wolfCryptSetKey(key);
recordGcmKeyIv(key);
this.operationStarted = false;
this.cipherInitialized = true;
this.gcmEncryptNeedsReinit = false;
}
@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 = null;
try {
if (params != null) {
if (this.cipherType == CipherType.WC_RSA &&
(this.paddingType == PaddingType.WC_OAEP_SHA256 ||
this.paddingType == PaddingType.WC_OAEP_SHA1)) {
spec = params.getParameterSpec(OAEPParameterSpec.class);
}
else if (this.cipherMode == CipherMode.WC_GCM ||
this.cipherMode == CipherMode.WC_CCM) {
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;
}
/**
* If a call to update() would be a no-op (or just return byte[0]) for the
* selected cipher type, mode, and buffered data, return true.
*
* This happens in cases like RSA, or AES-GCM/CCM which don't support
* streaming and buffer all data until doFinal() is called.
*
* @param inputSz total size in bytes of data available for processing,
* including input data and buffered data.
*
* @return true if update() would be a no-op, otherwise false
*/
private boolean isNoOpUpdate(int inputSz) {
/* RSA keeps buffered data until final() call */
if (cipherType == CipherType.WC_RSA) {
return true;
}
/* AES-GCM, AES-CCM, and AES-CTS keep all data buffered until
* final() call. wolfJCE does not support streaming GCM/CCM yet.
* CTS requires the entire message for ciphertext stealing. */
if (cipherType == CipherType.WC_AES &&
(cipherMode == CipherMode.WC_GCM ||
cipherMode == CipherMode.WC_CCM ||
cipherMode == CipherMode.WC_CTS)) {
return true;
}
/* If total data input (plus buffered) is less than block size,
* update() is a no-op, except for CTR and OFB which are stream
* ciphers */
if ((inputSz < blockSize) &&
(cipherMode != CipherMode.WC_CTR) &&
(cipherMode != CipherMode.WC_OFB)) {
return true;
}
return false;
}
private byte[] wolfCryptUpdate(byte[] input, int inputOffset, int len)
throws IllegalArgumentException {
int blocks = 0;
int bytesToProcess = 0;
byte[] output = null;
byte[] tmpIn = null;
if (input == null || len < 0 || inputOffset < 0) {
throw new IllegalArgumentException(
"Null input buffer or len/offset < 0");
}
if (input.length < (inputOffset + len)) {
throw new IllegalArgumentException(
"Input buffer length smaller than inputOffset + len");
}
/* A second GCM encryption without re-init would reuse the same
* key and IV, fail here at update() time like SunJCE does */
if ((this.cipherMode == CipherMode.WC_GCM) &&
(this.direction == OpMode.WC_ENCRYPT) &&
this.gcmEncryptNeedsReinit) {
throw new IllegalStateException(
"Must use either different key or iv for GCM encryption");
}
this.operationStarted = true;
if ((bufferedLen + len) == 0) {
/* no data to process */
return null;
}
if (len > 0) {
/* add input bytes to buffered */
bufferedAppend(input, inputOffset, len);
}
/* Some algos/modes keep data buffered until the doFinal() call, like
* RSA or AES-GCM/CCM without stream mode compiled natively. Just
* return an empty byte array in those cases here. */
if (isNoOpUpdate(bufferedLen)) {
return new byte[0];
}
/* Calculate blocks and partial non-block size remaining */
blocks = bufferedLen / blockSize;
bytesToProcess = blocks * blockSize;
/* CTR and OFB are stream ciphers, process all available data */
if (cipherMode == CipherMode.WC_CTR ||
cipherMode == CipherMode.WC_OFB) {
bytesToProcess = bufferedLen;
}
/* If PKCS#5/7 padding, and decrypting, hold on to last block for
* padding check in wolfCryptFinal() */
else if (paddingType == PaddingType.WC_PKCS5 &&
direction == OpMode.WC_DECRYPT &&
bytesToProcess > 0) {
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);
/* keep remaining non-block size input buffered */
bufferedConsume(bytesToProcess);
/* process tmpIn[] */
switch (this.cipherType) {
/* Only CBC/ECB/CTR/OFB mode reaches this point currently,
* GCM/CCM/CTS cache all data internally above until final call */
case WC_AES:
if (cipherMode == CipherMode.WC_ECB) {
output = this.aesEcb.update(tmpIn, 0, tmpIn.length);
}
else if (cipherMode == CipherMode.WC_CTR) {
output = this.aesCtr.update(tmpIn, 0, tmpIn.length);
}
else if (cipherMode == CipherMode.WC_OFB) {
output = this.aesOfb.update(tmpIn, 0, tmpIn.length);
}
else {
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;
}
/**
* Normalize an RSA ciphertext byte array to the modulus byte length
* before passing it to native wc_RsaPrivateDecrypt / wc_RsaSSL_Verify.
*
* Proper PKCS#1 v1.5 and OAEP ciphertexts are exactly k bytes long
* (modulus length). In practice, zero bytes could be stripped. This
* left-pads input with zeros, leaving inputs longer than the modulus
* as error cases.
*
* @param in ciphertext bytes
* @param modSize RSA modulus length in bytes
* @return a byte[] of length exactly modSize (the same array if already
* the right size, otherwise a new zero-padded copy)
* @throws IllegalBlockSizeException if in.length greater than modSize
*/
private static byte[] leftPadRSACiphertext(byte[] in, int modSize)
throws IllegalBlockSizeException {
if (in.length == modSize) {
return in;
}
if (in.length > modSize) {
throw new IllegalBlockSizeException("RSA input length (" +
in.length + ") exceeds key size (" + modSize + ")");
}
byte[] padded = new byte[modSize];
System.arraycopy(in, 0, padded, modSize - in.length, in.length);
return padded;
}
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 = bufferedLen + len;
/* AES-CTS requires input length >= 16 bytes (RFC 3962/8009).
* For exactly 16 bytes, CTS reduces to plain CBC, handled in JNI. */
if (cipherMode == CipherMode.WC_CTS && totalSz < blockSize) {
throw new IllegalBlockSizeException(
"AES-CTS requires input length >= " + blockSize +
" bytes, got " + totalSz + " bytes");
}
/* AES-GCM, AES-CCM, AES-CTR, AES-CTS, and AES-OFB do not require
* block size inputs */
if (isBlockCipher() &&
(cipherMode != CipherMode.WC_GCM) &&
(cipherMode != CipherMode.WC_CCM) &&
(cipherMode != CipherMode.WC_CTR) &&
(cipherMode != CipherMode.WC_CTS) &&
(cipherMode != CipherMode.WC_OFB) &&
(this.direction == OpMode.WC_DECRYPT ||
(this.direction == OpMode.WC_ENCRYPT &&
this.paddingType != PaddingType.WC_PKCS5)) &&
(totalSz % blockSize != 0)) {
throw new IllegalBlockSizeException(
"Input length (" + totalSz + ") not multiple of " +
blockSize + " bytes. (" + bufferedLen +" buffered)");
}
/* do final encrypt over totalSz */
tmpIn = new byte[totalSz];
if (totalSz > 0) {
System.arraycopy(buffered, 0, tmpIn, 0, bufferedLen);
if (input != null && len > 0) {
System.arraycopy(input, inputOffset, tmpIn,
bufferedLen, 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. CCM, CTR, CTS, and OFB modes do not use
* padding */
if (this.direction == OpMode.WC_ENCRYPT &&
this.paddingType == PaddingType.WC_PKCS5 &&
cipherMode != CipherMode.WC_CCM &&
cipherMode != CipherMode.WC_CTR &&
cipherMode != CipherMode.WC_CTS &&
cipherMode != CipherMode.WC_OFB) {
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);
}
}
/* Flatten accumulated AAD to a single array for GCM/CCM calls below */
byte[] aad = (this.aadStream != null) ?
this.aadStream.toByteArray() : null;
switch (this.cipherType) {
case WC_AES:
if (cipherMode == CipherMode.WC_GCM) {
if (this.direction == OpMode.WC_ENCRYPT) {
/* A second encryption without re-init would reuse
* the same key and IV (GCM nonce reuse) */
if (this.gcmEncryptNeedsReinit) {
throw new IllegalStateException(
"Must use either different key or iv for " +
"GCM encryption");
}
byte[] tag = new byte[this.gcmTagLen];
tmpOut = this.aesGcm.encrypt(tmpIn, this.iv, tag,
aad);
this.gcmEncryptNeedsReinit = true;
/* 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 {
/* Case where input is only the authentication tag,
* zero-length plaintext */
if (tmpIn.length < this.gcmTagLen) {
throw new AEADBadTagException(
"Input too short for GCM tag, got " +
tmpIn.length + " bytes, need at least " +
this.gcmTagLen);
}
/* 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);
try {
tmpOut = this.aesGcm.decrypt(tmpIn, this.iv,
tag, aad);
} catch (WolfCryptException e) {
/* Convert to AEADBadTagException */
if (e.getCode() ==
WolfCryptError.AES_GCM_AUTH_E.getCode()) {
/* Authentication check fail */
throw new AEADBadTagException(e.getMessage());
}
throw e;
}
}
}
else if (cipherMode == CipherMode.WC_CCM) {
if (this.direction == OpMode.WC_ENCRYPT) {
byte[] tag = new byte[this.gcmTagLen];
tmpOut = this.aesCcm.encrypt(tmpIn, this.iv, tag,
aad);
/* 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 {
/* Case where input is only the authentication tag,
* zero-length plaintext */
if (tmpIn.length < this.gcmTagLen) {
throw new AEADBadTagException(
"Input too short for CCM tag, got " +
tmpIn.length + " bytes, need at least " +
this.gcmTagLen);
}
/* 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.aesCcm.decrypt(tmpIn, this.iv, tag,
aad);
}
}
else if (cipherMode == CipherMode.WC_ECB) {
tmpOut = this.aesEcb.update(tmpIn, 0, tmpIn.length);
}
else if (cipherMode == CipherMode.WC_CTR) {
tmpOut = this.aesCtr.update(tmpIn, 0, tmpIn.length);
}
else if (cipherMode == CipherMode.WC_CTS) {
tmpOut = this.aesCts.update(tmpIn, 0, tmpIn.length);
}
else if (cipherMode == CipherMode.WC_OFB) {
tmpOut = this.aesOfb.update(tmpIn, 0, tmpIn.length);
}
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,
* CCM, CTR, CTS, and OFB modes do not use padding */
if (tmpOut != null && tmpOut.length > 0) {
if (this.direction == OpMode.WC_DECRYPT &&
this.paddingType == PaddingType.WC_PKCS5 &&
cipherMode != CipherMode.WC_CCM &&
cipherMode != CipherMode.WC_CTR &&
cipherMode != CipherMode.WC_CTS &&
cipherMode != CipherMode.WC_OFB) {
try {
tmpOut = Aes.unPadPKCS7(tmpOut, Aes.BLOCK_SIZE);
} catch (WolfCryptException e) {
throw new BadPaddingException("Decryption error");
}
}
}
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 (tmpOut != null && tmpOut.length > 0) {
if (this.direction == OpMode.WC_DECRYPT &&
this.paddingType == PaddingType.WC_PKCS5) {
try {
tmpOut = Des3.unPadPKCS7(tmpOut, Des3.BLOCK_SIZE);
} catch (WolfCryptException e) {
throw new BadPaddingException("Decryption error");
}
}
}
break;
case WC_RSA:
if (this.paddingType == PaddingType.WC_OAEP_SHA256 ||
this.paddingType == PaddingType.WC_OAEP_SHA1) {
/* OAEP only supports public key encrypt, private decrypt */
if (this.direction == OpMode.WC_ENCRYPT) {
if (this.rsaKeyType == RsaKeyType.WC_RSA_PRIVATE) {
throw new IllegalStateException(
"OAEP padding requires public key for " +
"encryption");
}
tmpOut = this.rsa.encryptOaep(tmpIn, this.rng,
this.oaepHashType, this.oaepMgf);
} else {
if (this.rsaKeyType == RsaKeyType.WC_RSA_PUBLIC) {
throw new IllegalStateException(
"OAEP padding requires private key for " +
"decryption");
}
try {
tmpIn = leftPadRSACiphertext(tmpIn,
this.rsa.getEncryptSize());
tmpOut = this.rsa.decryptOaep(tmpIn,
this.oaepHashType, this.oaepMgf);
} catch (WolfCryptException e) {
throw new BadPaddingException("Decryption error");
}
}
} else {
/* PKCS#1 v1.5 padding */
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 {
try {
tmpIn = leftPadRSACiphertext(tmpIn,
this.rsa.getEncryptSize());
if (this.rsaKeyType == RsaKeyType.WC_RSA_PRIVATE) {
tmpOut = this.rsa.decrypt(tmpIn);
} else {
tmpOut = this.rsa.verify(tmpIn);
}
} catch (WolfCryptException e) {
throw new BadPaddingException("Decryption error");
}
}
}
break;
default:
throw new RuntimeException("Unsupported algorithm type");
};
/* reset state, user doesn't need to call init again before use */
try {
bufferedReset();
wolfCryptSetDirection(this.storedOpMode);
InitializeNativeStructs();
/* Preserve the existing IV during cipher reset to maintain
* consistency with JCE getIV() behavior. If storedSpec is null
* (no IV was provided initially), wolfCryptSetIV would generate
* a new random IV, overwriting the original one. */
if (storedSpec == null && this.iv != null) {
/* Create appropriate ParameterSpec with the current IV to avoid
* generating a new random IV during reset */
AlgorithmParameterSpec currentIvSpec;
if (cipherMode == CipherMode.WC_GCM) {
/* For GCM mode, create GCMParameterSpec with current
* IV and tag length */
currentIvSpec = new GCMParameterSpec(
this.gcmTagLen * 8, this.iv.clone());
} else {
/* For other modes, use IvParameterSpec */
currentIvSpec = new IvParameterSpec(this.iv.clone());
}
wolfCryptSetIV(currentIvSpec, null);
} else {
wolfCryptSetIV(storedSpec, null);
}
wolfCryptSetKey(storedKey);
this.aadStream = 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;
}
/**
* Sanity check output buffer size is large enough for update() call,
* based on padding and buffered data.
*
* @param inputSz size of input data to update()
* @param outputSz total size of output buffer provided
*
* @throws ShortBufferException if output buffer is too small
*/
private void checkUpdateOutputBufferSize(int inputSz, int outputSz)
throws ShortBufferException {
int outSize;
if (!isNoOpUpdate(inputSz)) {
outSize = engineGetOutputSize(inputSz);
/* update() in DECRYPT mode with PKCS5 padding will hold
* back one block of data for padding check in final() */
if (direction == OpMode.WC_DECRYPT &&
paddingType == PaddingType.WC_PKCS5) {
if (outSize % blockSize == 0) {
outSize -= blockSize;
}
else {
outSize -= (outSize % blockSize);
}
}
if (outputSz < outSize) {
throw new ShortBufferException(
"Output buffer too small, need " + outSize +
" bytes, got " + outputSz);
}
}
}
@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 (inputOffset: " + inputOffset + ", inputLen: " +
inputLen + ", outputOffset: " + outputOffset + ")");
if (output == null || (output.length < outputOffset)) {
throw new IllegalArgumentException(
"output is null or offset past output array sz");
}
if (input == null || (inputLen + inputOffset > input.length)) {
throw new IllegalArgumentException(
"input is null or inOffset + inputLen past input array size");
}
/* Sanitize output buffer size, throws ShortBufferException if needed */
checkUpdateOutputBufferSize(inputLen, output.length - outputOffset);
tmpOut = wolfCryptUpdate(input, inputOffset, inputLen);
if (tmpOut == null) {
return 0;
}
if (output.length - outputOffset < tmpOut.length) {
throw new ShortBufferException(
"Output buffer too small, need " + tmpOut.length +
" bytes, got " + (output.length - outputOffset));
}
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 +
", buffered: " + bufferedLen + ")");
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 (inputOffset: " + inputOffset + ", inputLen: " +
inputLen + ", outputOffset: " + outputOffset + ", buffered: " +
bufferedLen + ")");
if (output == null || (outputOffset > output.length)) {
throw new IllegalArgumentException(
"output is null or offset past output array sz");
}
/* SunJCE can save Cipher state so it can throw a more precise
* ShortBufferException after checking the actual length after
* stripping padding. But, native wolfCrypt does not support
* saving/restoring Aes state, so we err on the side of making callers
* give us up to the next block size of output space */
if ((output.length - outputOffset) < engineGetOutputSize(inputLen)) {
throw new ShortBufferException("Output buffer too small, need " +
engineGetOutputSize(inputLen) + " bytes, got " +
(output.length - outputOffset));
}
tmpOut = wolfCryptFinal(input, inputOffset, inputLen);
if (output.length - outputOffset < tmpOut.length) {
throw new ShortBufferException(
"Output buffer too small, need " + tmpOut.length +
" bytes, got " + (output.length - outputOffset));
}
System.arraycopy(tmpOut, 0, output, outputOffset, tmpOut.length);
return tmpOut.length;
}
@Override
protected int engineGetKeySize(Key key)
throws InvalidKeyException {
byte encodedKey[] = null;
int kLen = 0;
/* 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");
kLen = encodedKey.length;
zeroArray(encodedKey);
return kLen;
}
@Override
protected void engineUpdateAAD(byte[] src, int offset, int len)
throws IllegalArgumentException, IllegalStateException {
if (this.cipherType != CipherType.WC_AES ||
(this.cipherMode != CipherMode.WC_GCM &&
this.cipherMode != CipherMode.WC_CCM)) {
throw new IllegalStateException(
"AAD only supported for AES-GCM and AES-CCM");
}
if (this.operationStarted) {
throw new IllegalStateException(
"Must set AAD before calling Cipher.update/final");
}
if (!this.cipherInitialized ||
(this.cipherMode == CipherMode.WC_GCM && this.aesGcm == null) ||
(this.cipherMode == CipherMode.WC_CCM && this.aesCcm == 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.aadStream == null) {
this.aadStream = new ByteArrayOutputStream();
}
this.aadStream.write(src, offset, len);
}
@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;
}
}
@Override
protected byte[] engineWrap(Key key)
throws IllegalBlockSizeException, InvalidKeyException {
byte[] encodedKey = null;
byte[] wcBuf;
if (key == null) {
throw new InvalidKeyException(
"Key to be wrapped must not be null");
}
encodedKey = key.getEncoded();
if (encodedKey == null) {
throw new InvalidKeyException(
"Key does not support encoding, cannot wrap");
}
try {
wcBuf = wolfCryptFinal(encodedKey, 0, encodedKey.length);
} catch (BadPaddingException e) {
throw new InvalidKeyException("Failed to wrap key: " +
e.getMessage(), e);
} finally {
zeroArray(encodedKey);
}
return wcBuf;
}
@Override
protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgo,
int wrappedKeyType) throws InvalidKeyException,
NoSuchAlgorithmException {
byte[] unwrappedKey;
if (wrappedKey == null || wrappedKey.length == 0) {
throw new InvalidKeyException(
"Wrapped key bytes must not be null or empty");
}
try {
unwrappedKey = wolfCryptFinal(wrappedKey, 0, wrappedKey.length);
} catch (BadPaddingException | IllegalBlockSizeException e) {
throw new InvalidKeyException("Failed to unwrap key");
}
switch (wrappedKeyType) {
case Cipher.SECRET_KEY:
return new SecretKeySpec(unwrappedKey, wrappedKeyAlgo);
case Cipher.PUBLIC_KEY:
try {
KeyFactory kf = KeyFactory.getInstance(wrappedKeyAlgo);
return kf.generatePublic(
new X509EncodedKeySpec(unwrappedKey));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to reconstruct public key: " +
e.getMessage(), e);
}
case Cipher.PRIVATE_KEY:
try {
KeyFactory kf = KeyFactory.getInstance(wrappedKeyAlgo);
return kf.generatePrivate(
new PKCS8EncodedKeySpec(unwrappedKey));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to reconstruct private key: " +
e.getMessage(), e);
}
default:
throw new InvalidKeyException("Invalid wrappedKeyType: " +
wrappedKeyType);
}
}
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";
case WC_CCM:
return "CCM";
default:
return "None";
}
}
private void log(String msg) {
WolfCryptDebug.log(getClass(), WolfCryptDebug.INFO,
() -> "[" + algString + "-" + algMode + "] " + msg);
}
@SuppressWarnings({"deprecation", "removal"})
@Override
protected void finalize() throws Throwable {
try {
if (this.aes != null) {
this.aes.releaseNativeStruct();
this.aes = null;
}
if (this.aesEcb != null) {
this.aesEcb.releaseNativeStruct();
this.aesEcb = null;
}
if (this.aesCtr != null) {
this.aesCtr.releaseNativeStruct();
this.aesCtr = null;
}
if (this.aesOfb != null) {
this.aesOfb.releaseNativeStruct();
this.aesOfb = null;
}
if (this.aesGcm != null) {
this.aesGcm.releaseNativeStruct();
this.aesGcm = null;
}
if (this.aesCcm != null) {
this.aesCcm.releaseNativeStruct();
this.aesCcm = null;
}
if (this.aesCts != null) {
this.aesCts.releaseNativeStruct();
this.aesCts = 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.free();
this.rng.releaseNativeStruct();
this.rng = null;
}
zeroArray(this.iv);
zeroArray(this.lastGcmEncryptKeyHash);
zeroArray(this.lastGcmEncryptIv);
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 AES-CCM with no padding
*/
public static final class wcAESCCMNoPadding extends WolfCryptCipher {
/**
* Create new wcAESCCMNoPadding object
*/
public wcAESCCMNoPadding() {
super(CipherType.WC_AES, CipherMode.WC_CCM, 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 AES-ECB with no padding
*/
public static final class wcAESECBNoPadding extends WolfCryptCipher {
/**
* Create new wcAESECBNoPadding object
*/
public wcAESECBNoPadding() {
super(CipherType.WC_AES, CipherMode.WC_ECB, PaddingType.WC_NONE);
}
}
/**
* Class for AES-ECB with PKCS#5 padding
*/
public static final class wcAESECBPKCS5Padding extends WolfCryptCipher {
/**
* Create new wcAESECBPKCS5Padding object
*/
public wcAESECBPKCS5Padding() {
super(CipherType.WC_AES, CipherMode.WC_ECB, PaddingType.WC_PKCS5);
}
}
/**
* Class for AES-CTR with no padding
*/
public static final class wcAESCTRNoPadding extends WolfCryptCipher {
/**
* Create new wcAESCTRNoPadding object
*/
public wcAESCTRNoPadding() {
super(CipherType.WC_AES, CipherMode.WC_CTR, PaddingType.WC_NONE);
}
}
/**
* Class for AES-OFB with no padding
*/
public static final class wcAESOFBNoPadding extends WolfCryptCipher {
/**
* Create new wcAESOFBNoPadding object
*/
public wcAESOFBNoPadding() {
super(CipherType.WC_AES, CipherMode.WC_OFB, PaddingType.WC_NONE);
}
}
/**
* Class for AES-CTS with no padding
*/
public static final class wcAESCTSNoPadding extends WolfCryptCipher {
/**
* Create new wcAESCTSNoPadding object
*/
public wcAESCTSNoPadding() {
super(CipherType.WC_AES, CipherMode.WC_CTS, 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);
}
}
/**
* Class for RSA-ECB with OAEP SHA-256 padding
*/
public static final class wcRSAECBOAEPSHA256Padding
extends WolfCryptCipher {
/**
* Create new wcRSAECBOAEPSHA256Padding object
*/
public wcRSAECBOAEPSHA256Padding() {
super(CipherType.WC_RSA, CipherMode.WC_ECB,
PaddingType.WC_OAEP_SHA256);
}
}
/**
* Class for RSA-ECB with OAEP SHA-1 padding
*/
public static final class wcRSAECBOAEPSHA1Padding
extends WolfCryptCipher {
/**
* Create new wcRSAECBOAEPSHA1Padding object
*/
public wcRSAECBOAEPSHA1Padding() {
super(CipherType.WC_RSA, CipherMode.WC_ECB,
PaddingType.WC_OAEP_SHA1);
}
}
}