|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package com.sun.crypto.provider; |
|
|
|
import java.util.Arrays; |
|
import java.security.*; |
|
import java.security.spec.*; |
|
import javax.crypto.*; |
|
import javax.crypto.spec.*; |
|
import static com.sun.crypto.provider.KWUtil.*; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
abstract class KeyWrapCipher extends CipherSpi { |
|
|
|
|
|
public static final class AES_KW_NoPadding extends KeyWrapCipher { |
|
public AES_KW_NoPadding() { |
|
super(new AESKeyWrap(), null, -1); |
|
} |
|
} |
|
|
|
|
|
public static final class AES128_KW_NoPadding extends KeyWrapCipher { |
|
public AES128_KW_NoPadding() { |
|
super(new AESKeyWrap(), null, 16); |
|
} |
|
} |
|
|
|
|
|
public static final class AES192_KW_NoPadding extends KeyWrapCipher { |
|
public AES192_KW_NoPadding() { |
|
super(new AESKeyWrap(), null, 24); |
|
} |
|
} |
|
|
|
|
|
public static final class AES256_KW_NoPadding extends KeyWrapCipher { |
|
public AES256_KW_NoPadding() { |
|
super(new AESKeyWrap(), null, 32); |
|
} |
|
} |
|
|
|
|
|
public static final class AES_KW_PKCS5Padding extends KeyWrapCipher { |
|
public AES_KW_PKCS5Padding() { |
|
super(new AESKeyWrap(), new PKCS5Padding(16), -1); |
|
} |
|
} |
|
|
|
|
|
public static final class AES128_KW_PKCS5Padding extends KeyWrapCipher { |
|
public AES128_KW_PKCS5Padding() { |
|
super(new AESKeyWrap(), new PKCS5Padding(16), 16); |
|
} |
|
} |
|
|
|
|
|
public static final class AES192_KW_PKCS5Padding extends KeyWrapCipher { |
|
public AES192_KW_PKCS5Padding() { |
|
super(new AESKeyWrap(), new PKCS5Padding(16), 24); |
|
} |
|
} |
|
|
|
|
|
public static final class AES256_KW_PKCS5Padding extends KeyWrapCipher { |
|
public AES256_KW_PKCS5Padding() { |
|
super(new AESKeyWrap(), new PKCS5Padding(16), 32); |
|
} |
|
} |
|
|
|
|
|
public static final class AES_KWP_NoPadding extends KeyWrapCipher { |
|
public AES_KWP_NoPadding() { |
|
super(new AESKeyWrapPadded(), null, -1); |
|
} |
|
} |
|
|
|
|
|
public static final class AES128_KWP_NoPadding extends KeyWrapCipher { |
|
public AES128_KWP_NoPadding() { |
|
super(new AESKeyWrapPadded(), null, 16); |
|
} |
|
} |
|
|
|
|
|
public static final class AES192_KWP_NoPadding extends KeyWrapCipher { |
|
public AES192_KWP_NoPadding() { |
|
super(new AESKeyWrapPadded(), null, 24); |
|
} |
|
} |
|
|
|
|
|
public static final class AES256_KWP_NoPadding extends KeyWrapCipher { |
|
public AES256_KWP_NoPadding() { |
|
super(new AESKeyWrapPadded(), null, 32); |
|
} |
|
} |
|
|
|
// store the specified bytes, e.g. in[inOfs...(inOfs+inLen-1)] into |
|
// 'dataBuf' starting at 'dataIdx'. |
|
// NOTE: if 'in' is null, this method will ensure that 'dataBuf' has enough |
|
|
|
private void store(byte[] in, int inOfs, int inLen) { |
|
// In NIST SP 800-38F, KWP input size is limited to be no longer |
|
// than 2^32 bytes. Otherwise, the length cannot be encoded in 32 bits |
|
// However, given the current spec requirement that recovered text |
|
// can only be returned after successful tag verification, we are |
|
// bound by limiting the data size to the size limit of java byte array, |
|
|
|
int remain = Integer.MAX_VALUE - dataIdx; |
|
if (inLen > remain) { |
|
throw new ProviderException("SunJCE provider can only take " + |
|
remain + " more bytes"); |
|
} |
|
|
|
|
|
if (dataBuf == null || dataBuf.length - dataIdx < inLen) { |
|
int newSize = Math.addExact(dataIdx, inLen); |
|
int lastBlk = (dataIdx + inLen - SEMI_BLKSIZE) % BLKSIZE; |
|
if (lastBlk != 0 || padding != null) { |
|
newSize = Math.addExact(newSize, BLKSIZE - lastBlk); |
|
} |
|
byte[] temp = new byte[newSize]; |
|
if (dataBuf != null && dataIdx > 0) { |
|
System.arraycopy(dataBuf, 0, temp, 0, dataIdx); |
|
} |
|
dataBuf = temp; |
|
} |
|
|
|
if (in != null) { |
|
System.arraycopy(in, inOfs, dataBuf, dataIdx, inLen); |
|
dataIdx += inLen; |
|
} |
|
} |
|
|
|
// internal cipher object which does the real work. |
|
|
|
private final FeedbackCipher cipher; |
|
|
|
|
|
private final Padding padding; |
|
|
|
// encrypt/wrap or decrypt/unwrap? |
|
private int opmode = -1; |
|
|
|
/* |
|
* needed to support oids which associates a fixed key size |
|
* to the cipher object. |
|
*/ |
|
private final int fixedKeySize; |
|
|
|
// internal data buffer for encrypt, decrypt calls |
|
|
|
private byte[] dataBuf; |
|
private int dataIdx; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public KeyWrapCipher(FeedbackCipher cipher, Padding padding, int keySize) { |
|
this.cipher = cipher; |
|
this.padding = padding; |
|
this.fixedKeySize = keySize; |
|
this.dataBuf = null; |
|
this.dataIdx = 0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected void engineSetMode(String mode) throws NoSuchAlgorithmException { |
|
if (mode != null && !cipher.getFeedback().equalsIgnoreCase(mode)) { |
|
throw new NoSuchAlgorithmException(mode + " cannot be used"); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected void engineSetPadding(String padding) |
|
throws NoSuchPaddingException { |
|
if ((this.padding == null && !"NoPadding".equalsIgnoreCase(padding)) || |
|
this.padding instanceof PKCS5Padding && |
|
!"PKCS5Padding".equalsIgnoreCase(padding)) { |
|
throw new NoSuchPaddingException("Unsupported padding " + padding); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected int engineGetBlockSize() { |
|
return cipher.getBlockSize(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected int engineGetOutputSize(int inLen) { |
|
|
|
int result; |
|
|
|
if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE) { |
|
result = (dataIdx > 0? |
|
Math.addExact(inLen, dataIdx - SEMI_BLKSIZE) : inLen); |
|
|
|
int padLen = 0; |
|
if (padding != null) { |
|
padLen = padding.padLength(result); |
|
} else if (cipher instanceof AESKeyWrapPadded) { |
|
int n = result % SEMI_BLKSIZE; |
|
if (n != 0) { |
|
padLen = SEMI_BLKSIZE - n; |
|
} |
|
} |
|
|
|
result = Math.addExact(result, SEMI_BLKSIZE + padLen); |
|
} else { |
|
result = inLen - SEMI_BLKSIZE; |
|
if (dataIdx > 0) { |
|
result = Math.addExact(result, dataIdx); |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected byte[] engineGetIV() { |
|
byte[] iv = cipher.getIV(); |
|
return (iv == null? null : iv.clone()); |
|
} |
|
|
|
|
|
private void implInit(int opmode, Key key, byte[] iv, SecureRandom random) |
|
throws InvalidKeyException, InvalidAlgorithmParameterException { |
|
byte[] keyBytes = key.getEncoded(); |
|
if (keyBytes == null) { |
|
throw new InvalidKeyException("Null key"); |
|
} |
|
this.opmode = opmode; |
|
boolean decrypting = (opmode == Cipher.DECRYPT_MODE || |
|
opmode == Cipher.UNWRAP_MODE); |
|
try { |
|
cipher.init(decrypting, key.getAlgorithm(), keyBytes, iv); |
|
dataBuf = null; |
|
dataIdx = 0; |
|
} finally { |
|
Arrays.fill(keyBytes, (byte) 0); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected void engineInit(int opmode, Key key, SecureRandom random) |
|
throws InvalidKeyException { |
|
try { |
|
implInit(opmode, key, (byte[])null, random); |
|
} catch (InvalidAlgorithmParameterException iae) { |
|
|
|
throw new AssertionError(iae); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected void engineInit(int opmode, Key key, |
|
AlgorithmParameterSpec params, SecureRandom random) |
|
throws InvalidKeyException, InvalidAlgorithmParameterException { |
|
if (params != null && !(params instanceof IvParameterSpec)) { |
|
throw new InvalidAlgorithmParameterException( |
|
"Only IvParameterSpec is accepted"); |
|
} |
|
byte[] iv = (params == null? null : ((IvParameterSpec)params).getIV()); |
|
implInit(opmode, key, iv, random); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected void engineInit(int opmode, Key key, AlgorithmParameters params, |
|
SecureRandom random) throws InvalidKeyException, |
|
InvalidAlgorithmParameterException { |
|
byte[] iv = null; |
|
if (params != null) { |
|
try { |
|
AlgorithmParameterSpec spec = |
|
params.getParameterSpec(IvParameterSpec.class); |
|
iv = ((IvParameterSpec)spec).getIV(); |
|
} catch (InvalidParameterSpecException ispe) { |
|
throw new InvalidAlgorithmParameterException( |
|
"Only IvParameterSpec is accepted"); |
|
} |
|
} |
|
try { |
|
implInit(opmode, key, iv, random); |
|
} catch (IllegalArgumentException iae) { |
|
throw new InvalidAlgorithmParameterException(iae.getMessage()); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected byte[] engineUpdate(byte[] in, int inOffset, int inLen) { |
|
if (opmode != Cipher.ENCRYPT_MODE && opmode != Cipher.DECRYPT_MODE) { |
|
throw new IllegalStateException |
|
("Cipher not initialized for update"); |
|
} |
|
implUpdate(in, inOffset, inLen); |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected int engineUpdate(byte[] in, int inOffset, int inLen, |
|
byte[] out, int outOffset) throws ShortBufferException { |
|
if (opmode != Cipher.ENCRYPT_MODE && opmode != Cipher.DECRYPT_MODE) { |
|
throw new IllegalStateException |
|
("Cipher not initialized for update"); |
|
} |
|
implUpdate(in, inOffset, inLen); |
|
return 0; |
|
} |
|
|
|
|
|
private void implUpdate(byte[] in, int inOfs, int inLen) { |
|
if (inLen <= 0) return; |
|
|
|
if (opmode == Cipher.ENCRYPT_MODE && dataIdx == 0) { |
|
|
|
dataIdx = SEMI_BLKSIZE; |
|
} |
|
store(in, inOfs, inLen); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected byte[] engineDoFinal(byte[] in, int inOfs, int inLen) |
|
throws IllegalBlockSizeException, BadPaddingException { |
|
|
|
int estOutLen = engineGetOutputSize(inLen); |
|
byte[] out = new byte[estOutLen]; |
|
try { |
|
int outLen = engineDoFinal(in, inOfs, inLen, out, 0); |
|
|
|
if (outLen < estOutLen) { |
|
try { |
|
return Arrays.copyOf(out, outLen); |
|
} finally { |
|
Arrays.fill(out, (byte)0); |
|
} |
|
} else { |
|
return out; |
|
} |
|
} catch (ShortBufferException sbe) { |
|
|
|
throw new AssertionError(sbe); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected int engineDoFinal(byte[] in, int inOfs, int inLen, |
|
byte[] out, int outOfs) throws IllegalBlockSizeException, |
|
ShortBufferException, BadPaddingException { |
|
|
|
if (opmode != Cipher.ENCRYPT_MODE && opmode != Cipher.DECRYPT_MODE) { |
|
throw new IllegalStateException |
|
("Cipher not initialized for doFinal"); |
|
} |
|
|
|
int estOutLen = engineGetOutputSize(inLen); |
|
if (out.length - outOfs < estOutLen) { |
|
throw new ShortBufferException("Need at least " + estOutLen); |
|
} |
|
|
|
try { |
|
// cannot write out the result for decryption due to verification |
|
|
|
if (outOfs == 0 && opmode == Cipher.ENCRYPT_MODE) { |
|
return implDoFinal(in, inOfs, inLen, out); |
|
} else { |
|
// use 'dataBuf' as output buffer and then copy into 'out' |
|
|
|
store(null, 0, inLen); |
|
int outLen = implDoFinal(in, inOfs, inLen, dataBuf); |
|
if (outLen > estOutLen) { |
|
throw new AssertionError |
|
("Actual output length exceeds estimated length"); |
|
} |
|
System.arraycopy(dataBuf, 0, out, outOfs, outLen); |
|
return outLen; |
|
} |
|
} finally { |
|
if (dataBuf != null) { |
|
Arrays.fill(dataBuf, (byte)0); |
|
} |
|
dataBuf = null; |
|
dataIdx = 0; |
|
} |
|
} |
|
|
|
// actual impl for various engineDoFinal(...) methods. |
|
// prepare 'out' buffer with the buffered bytes in 'dataBuf', |
|
// and the to-be-processed bytes in 'in', then perform single-part |
|
|
|
private int implDoFinal(byte[] in, int inOfs, int inLen, byte[] out) |
|
throws IllegalBlockSizeException, BadPaddingException, |
|
ShortBufferException { |
|
|
|
int len = (out == dataBuf? dataIdx : 0); |
|
|
|
|
|
if (out != dataBuf && dataIdx > 0) { |
|
System.arraycopy(dataBuf, 0, out, 0, dataIdx); |
|
len = dataIdx; |
|
} |
|
|
|
if (opmode == Cipher.ENCRYPT_MODE && len == 0) { |
|
len = SEMI_BLKSIZE; |
|
} |
|
|
|
if (inLen > 0) { |
|
System.arraycopy(in, inOfs, out, len, inLen); |
|
len += inLen; |
|
} |
|
|
|
try { |
|
return (opmode == Cipher.ENCRYPT_MODE ? |
|
helperEncrypt(out, len) : helperDecrypt(out, len)); |
|
} finally { |
|
if (dataBuf != null && dataBuf != out) { |
|
Arrays.fill(dataBuf, (byte)0); |
|
} |
|
} |
|
} |
|
|
|
// helper routine for in-place encryption. |
|
// 'inBuf' = semiblock | plain text | extra bytes if padding is used |
|
|
|
private int helperEncrypt(byte[] inBuf, int inLen) |
|
throws IllegalBlockSizeException, ShortBufferException { |
|
|
|
|
|
if (padding != null) { |
|
int paddingLen = padding.padLength(inLen - SEMI_BLKSIZE); |
|
|
|
if (inLen + paddingLen > inBuf.length) { |
|
throw new AssertionError("encrypt buffer too small"); |
|
} |
|
|
|
try { |
|
padding.padWithLen(inBuf, inLen, paddingLen); |
|
inLen += paddingLen; |
|
} catch (ShortBufferException sbe) { |
|
|
|
throw new AssertionError(sbe); |
|
} |
|
} |
|
return cipher.encryptFinal(inBuf, 0, inLen, null, 0); |
|
} |
|
|
|
// helper routine for in-place decryption. |
|
// 'inBuf' = cipher text |
|
|
|
private int helperDecrypt(byte[] inBuf, int inLen) |
|
throws IllegalBlockSizeException, BadPaddingException, |
|
ShortBufferException { |
|
|
|
int outLen = cipher.decryptFinal(inBuf, 0, inLen, null, 0); |
|
|
|
if (padding != null) { |
|
int padIdx = padding.unpad(inBuf, 0, outLen); |
|
if (padIdx <= 0) { |
|
throw new BadPaddingException("Bad Padding: " + padIdx); |
|
} |
|
outLen = padIdx; |
|
} |
|
return outLen; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected AlgorithmParameters engineGetParameters() { |
|
AlgorithmParameters params = null; |
|
|
|
byte[] iv = cipher.getIV(); |
|
if (iv == null) { |
|
iv = (cipher instanceof AESKeyWrap? |
|
AESKeyWrap.ICV1 : AESKeyWrapPadded.ICV2); |
|
} |
|
try { |
|
params = AlgorithmParameters.getInstance("AES"); |
|
params.init(new IvParameterSpec(iv)); |
|
} catch (NoSuchAlgorithmException | InvalidParameterSpecException e) { |
|
|
|
throw new AssertionError(); |
|
} |
|
return params; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected int engineGetKeySize(Key key) throws InvalidKeyException { |
|
byte[] encoded = key.getEncoded(); |
|
if (encoded == null) { |
|
throw new InvalidKeyException("Cannot decide key length"); |
|
} |
|
|
|
|
|
Arrays.fill(encoded, (byte) 0); |
|
int keyLen = encoded.length; |
|
if (!key.getAlgorithm().equalsIgnoreCase("AES") || |
|
!AESCrypt.isKeySizeValid(keyLen) || |
|
(fixedKeySize != -1 && fixedKeySize != keyLen)) { |
|
throw new InvalidKeyException("Invalid key length: " + |
|
keyLen + " bytes"); |
|
} |
|
return Math.multiplyExact(keyLen, 8); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected byte[] engineWrap(Key key) |
|
throws IllegalBlockSizeException, InvalidKeyException { |
|
|
|
if (opmode != Cipher.WRAP_MODE) { |
|
throw new IllegalStateException("Cipher not initialized for wrap"); |
|
} |
|
byte[] encoded = key.getEncoded(); |
|
if ((encoded == null) || (encoded.length == 0)) { |
|
throw new InvalidKeyException("Cannot get an encoding of " + |
|
"the key to be wrapped"); |
|
} |
|
|
|
byte[] out = new byte[engineGetOutputSize(encoded.length)]; |
|
|
|
|
|
int len = SEMI_BLKSIZE; |
|
System.arraycopy(encoded, 0, out, len, encoded.length); |
|
len += encoded.length; |
|
|
|
|
|
Arrays.fill(encoded, (byte) 0); |
|
|
|
try { |
|
int outLen = helperEncrypt(out, len); |
|
if (outLen != out.length) { |
|
throw new AssertionError("Wrong output buffer size"); |
|
} |
|
return out; |
|
} catch (ShortBufferException sbe) { |
|
|
|
throw new AssertionError(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, |
|
int wrappedKeyType) throws InvalidKeyException, |
|
NoSuchAlgorithmException { |
|
|
|
if (opmode != Cipher.UNWRAP_MODE) { |
|
throw new IllegalStateException |
|
("Cipher not initialized for unwrap"); |
|
} |
|
|
|
byte[] buf = wrappedKey.clone(); |
|
try { |
|
int outLen = helperDecrypt(buf, buf.length); |
|
return ConstructKeys.constructKey(buf, 0, outLen, |
|
wrappedKeyAlgorithm, wrappedKeyType); |
|
} catch (ShortBufferException sbe) { |
|
|
|
throw new AssertionError(); |
|
} catch (IllegalBlockSizeException | BadPaddingException e) { |
|
throw new InvalidKeyException(e); |
|
} finally { |
|
Arrays.fill(buf, (byte) 0); |
|
} |
|
} |
|
} |