package com.oracle.security.ucrypto;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.Set;
import java.util.Arrays;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.GCMParameterSpec;
import sun.security.jca.JCAUtil;
class NativeGCMCipher extends NativeCipher {
public static final class AesGcmNoPadding extends NativeGCMCipher {
public AesGcmNoPadding() throws NoSuchAlgorithmException {
super(-1);
}
public AesGcmNoPadding(int keySize) throws NoSuchAlgorithmException {
super(keySize);
}
}
private static final int DEFAULT_TAG_LEN = 128;
private static final int MAX_BUF_SIZE = Integer.MAX_VALUE;
private ByteArrayOutputStream aadBuffer;
private ByteArrayOutputStream ibuffer;
private int processed;
private int tagLen = DEFAULT_TAG_LEN;
private boolean requireReinit;
private byte[] lastEncKey = null;
private byte[] lastEncIv = null;
private void checkAndUpdateProcessed(int len) {
int inputDataLimit = MAX_BUF_SIZE - tagLen;
if (processed > inputDataLimit - len) {
throw new ProviderException("OracleUcrypto provider only supports " +
"input size up to " + inputDataLimit + " bytes");
}
processed += len;
}
NativeGCMCipher(int fixedKeySize) throws NoSuchAlgorithmException {
super(UcryptoMech.CRYPTO_AES_GCM, fixedKeySize);
}
@Override
protected void ensureInitialized() {
if (!initialized) {
byte[] aad = null;
if (aadBuffer != null) {
if (aadBuffer.size() > 0) {
aad = aadBuffer.toByteArray();
}
}
init(encrypt, keyValue, iv, tagLen, aad);
aadBuffer = null;
if (!initialized) {
throw new UcryptoException("Cannot initialize Cipher");
}
}
}
@Override
protected int getOutputSizeByOperation(int inLen, boolean isDoFinal) {
if (inLen < 0) return 0;
if (!isDoFinal && (inLen == 0)) {
return 0;
}
int result = inLen + bytesBuffered;
if (encrypt) {
if (isDoFinal) {
result += tagLen/8;
}
} else {
if (ibuffer != null) {
result += ibuffer.size();
}
result -= tagLen/8;
}
if (result < 0) {
result = 0;
}
return result;
}
@Override
protected void reset(boolean doCancel) {
super.reset(doCancel);
if (aadBuffer == null) {
aadBuffer = new ByteArrayOutputStream();
} else {
aadBuffer.reset();
}
if (ibuffer != null) {
ibuffer.reset();
}
if (!encrypt) requireReinit = false;
processed = 0;
}
protected void init(boolean encrypt, byte[] keyVal, byte[] ivVal, int tLen, byte[] aad) {
reset(true);
this.encrypt = encrypt;
this.keyValue = keyVal;
this.iv = ivVal;
long pCtxtVal = NativeCipher.nativeInit(mech.value(), encrypt, keyValue, iv,
tLen, aad);
initialized = (pCtxtVal != 0L);
if (initialized) {
pCtxt = new CipherContextRef(this, pCtxtVal, encrypt);
} else {
throw new UcryptoException("Cannot initialize Cipher");
}
}
@Override
protected synchronized AlgorithmParameters engineGetParameters() {
AlgorithmParameters params = null;
try {
if (iv != null) {
GCMParameterSpec gcmSpec = new GCMParameterSpec(tagLen, iv.clone());
params = AlgorithmParameters.getInstance("GCM");
params.init(gcmSpec);
}
} catch (GeneralSecurityException e) {
throw new UcryptoException("Could not encode parameters", e);
}
return params;
}
@Override
protected synchronized void engineInit(int opmode, Key key,
AlgorithmParameterSpec params, SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException {
checkKey(key);
if (opmode != Cipher.ENCRYPT_MODE &&
opmode != Cipher.DECRYPT_MODE &&
opmode != Cipher.WRAP_MODE &&
opmode != Cipher.UNWRAP_MODE) {
throw new InvalidAlgorithmParameterException
("Unsupported mode: " + opmode);
}
aadBuffer = new ByteArrayOutputStream();
boolean doEncrypt = (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE);
byte[] keyBytes = key.getEncoded().clone();
byte[] ivBytes = null;
if (params != null) {
if (!(params instanceof GCMParameterSpec)) {
throw new InvalidAlgorithmParameterException("GCMParameterSpec required." +
" Received: " + params.getClass().getName());
} else {
tagLen = ((GCMParameterSpec) params).getTLen();
ivBytes = ((GCMParameterSpec) params).getIV();
}
} else {
if (doEncrypt) {
tagLen = DEFAULT_TAG_LEN;
ivBytes = new byte[blockSize];
if (random == null) {
random = JCAUtil.getSecureRandom();
}
random.nextBytes(ivBytes);
} else {
throw new InvalidAlgorithmParameterException("Parameters required for decryption");
}
}
if (doEncrypt) {
requireReinit = Arrays.equals(ivBytes, lastEncIv) &&
MessageDigest.isEqual(keyBytes, lastEncKey);
if (requireReinit) {
throw new InvalidAlgorithmParameterException
("Cannot reuse iv for GCM encryption");
}
lastEncIv = ivBytes;
lastEncKey = keyBytes;
ibuffer = null;
} else {
requireReinit = false;
ibuffer = new ByteArrayOutputStream();
}
try {
init(doEncrypt, keyBytes, ivBytes, tagLen, null);
} catch (UcryptoException ex) {
if (ex.getError() ==
UcryptoException.Error.CRYPTO_MECHANISM_PARAM_INVALID) {
throw new InvalidAlgorithmParameterException(ex.getMessage());
} else {
throw ex;
}
}
}
@Override
protected synchronized void engineInit(int opmode, Key key, AlgorithmParameters params,
SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException {
AlgorithmParameterSpec spec = null;
if (params != null) {
try {
spec = params.getParameterSpec(GCMParameterSpec.class);
} catch (InvalidParameterSpecException iaps) {
throw new InvalidAlgorithmParameterException(iaps);
}
}
engineInit(opmode, key, spec, random);
}
@Override
protected synchronized byte[] engineUpdate(byte[] in, int inOfs, int inLen) {
if (aadBuffer != null) {
if (aadBuffer.size() > 0) {
init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray());
}
aadBuffer = null;
}
if (requireReinit) {
throw new IllegalStateException
("Must use either different key or iv for GCM encryption");
}
checkAndUpdateProcessed(inLen);
if (inLen > 0) {
if (!encrypt) {
ibuffer.write(in, inOfs, inLen);
return null;
}
return super.engineUpdate(in, inOfs, inLen);
} else return null;
}
@Override
protected synchronized int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out,
int outOfs) throws ShortBufferException {
int len = getOutputSizeByOperation(inLen, false);
if (out.length - outOfs < len) {
throw new ShortBufferException("Output buffer must be " +
"(at least) " + len + " bytes long. Got: " +
(out.length - outOfs));
}
if (aadBuffer != null) {
if (aadBuffer.size() > 0) {
init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray());
}
aadBuffer = null;
}
if (requireReinit) {
throw new IllegalStateException
("Must use either different key or iv for GCM encryption");
}
checkAndUpdateProcessed(inLen);
if (inLen > 0) {
if (!encrypt) {
ibuffer.write(in, inOfs, inLen);
return 0;
} else {
return super.engineUpdate(in, inOfs, inLen, out, outOfs);
}
}
return 0;
}
@Override
protected synchronized void engineUpdateAAD(byte[] src, int srcOfs, int srcLen)
throws IllegalStateException {
if ((src == null) || (srcOfs < 0) || (srcOfs + srcLen > src.length)) {
throw new IllegalArgumentException("Invalid AAD");
}
if (keyValue == null) {
throw new IllegalStateException("Need to initialize Cipher first");
}
if (requireReinit) {
throw new IllegalStateException
("Must use either different key or iv for GCM encryption");
}
if (aadBuffer != null) {
aadBuffer.write(src, srcOfs, srcLen);
} else {
throw new IllegalStateException
("Update has been called; no more AAD data");
}
}
@Override
protected void engineUpdateAAD(ByteBuffer src)
throws IllegalStateException {
if (src == null) {
throw new IllegalArgumentException("Invalid AAD");
}
if (keyValue == null) {
throw new IllegalStateException("Need to initialize Cipher first");
}
if (requireReinit) {
throw new IllegalStateException
("Must use either different key or iv for GCM encryption");
}
if (aadBuffer != null) {
if (src.hasRemaining()) {
byte[] srcBytes = new byte[src.remaining()];
src.get(srcBytes);
aadBuffer.write(srcBytes, 0, srcBytes.length);
}
} else {
throw new IllegalStateException
("Update has been called; no more AAD data");
}
}
@Override
protected synchronized byte[] engineDoFinal(byte[] in, int inOfs, int inLen)
throws IllegalBlockSizeException, BadPaddingException {
byte[] out = new byte[getOutputSizeByOperation(inLen, true)];
try {
int k = engineDoFinal(in, inOfs, inLen, out, 0);
if (out.length != k) {
out = Arrays.copyOf(out, k);
}
return out;
} catch (ShortBufferException e) {
throw new UcryptoException("Internal Error", e);
}
}
@Override
protected synchronized int engineDoFinal(byte[] in, int inOfs, int inLen,
byte[] out, int outOfs)
throws ShortBufferException, IllegalBlockSizeException,
BadPaddingException {
int len = getOutputSizeByOperation(inLen, true);
if (out.length - outOfs < len) {
throw new ShortBufferException("Output buffer must be "
+ "(at least) " + len + " bytes long. Got: " +
(out.length - outOfs));
}
if (aadBuffer != null) {
if (aadBuffer.size() > 0) {
init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray());
}
aadBuffer = null;
}
if (requireReinit) {
throw new IllegalStateException
("Must use either different key or iv for GCM encryption");
}
checkAndUpdateProcessed(inLen);
if (!encrypt) {
if (inLen > 0) {
ibuffer.write(in, inOfs, inLen);
}
inLen = ibuffer.size();
if (inLen < tagLen/8) {
throw new AEADBadTagException("Input too short - need tag." +
" inLen: " + inLen + ". tagLen: " + tagLen);
}
in = ibuffer.toByteArray();
inOfs = 0;
ibuffer.reset();
}
try {
return super.engineDoFinal(in, inOfs, inLen, out, outOfs);
} catch (UcryptoException ue) {
if (ue.getMessage().equals("CRYPTO_INVALID_MAC")) {
throw new AEADBadTagException("Tag does not match");
} else {
throw ue;
}
} finally {
requireReinit = encrypt;
}
}
}