// 
// Decompiled by Procyon v0.6.0
// 

package com.google.crypto.tink.subtle;

import javax.crypto.AEADBadTagException;
import com.google.crypto.tink.internal.Util;
import java.security.spec.AlgorithmParameterSpec;
import java.security.Key;
import javax.crypto.spec.IvParameterSpec;
import java.util.Arrays;
import com.google.crypto.tink.prf.AesCmacPrfKey;
import com.google.crypto.tink.util.SecretBytes;
import com.google.crypto.tink.prf.AesCmacPrfParameters;
import com.google.crypto.tink.AccessesPartialKey;
import com.google.crypto.tink.InsecureSecretKeyAccess;
import java.security.GeneralSecurityException;
import com.google.crypto.tink.aead.AesEaxKey;
import javax.crypto.spec.SecretKeySpec;
import com.google.crypto.tink.prf.Prf;
import javax.crypto.Cipher;
import com.google.crypto.tink.config.internal.TinkFipsUtil;
import com.google.crypto.tink.Aead;

public final class AesEaxJce implements Aead
{
    public static final TinkFipsUtil.AlgorithmFipsCompatibility FIPS;
    private static final ThreadLocal<Cipher> localCtrCipher;
    static final int BLOCK_SIZE_IN_BYTES = 16;
    static final int TAG_SIZE_IN_BYTES = 16;
    private final byte[] outputPrefix;
    private final Prf cmac;
    private final SecretKeySpec keySpec;
    private final int ivSizeInBytes;
    
    @AccessesPartialKey
    public static Aead create(final AesEaxKey key) throws GeneralSecurityException {
        if (!AesEaxJce.FIPS.isCompatible()) {
            throw new GeneralSecurityException("Can not use AES-EAX in FIPS-mode.");
        }
        if (key.getParameters().getTagSizeBytes() != 16) {
            throw new GeneralSecurityException("AesEaxJce only supports 16 byte tag size, not " + key.getParameters().getTagSizeBytes());
        }
        return new AesEaxJce(key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get()), key.getParameters().getIvSizeBytes(), key.getOutputPrefix().toByteArray());
    }
    
    @AccessesPartialKey
    private static Prf createCmac(final byte[] key) throws GeneralSecurityException {
        return PrfAesCmac.create(AesCmacPrfKey.create(AesCmacPrfParameters.create(key.length), SecretBytes.copyFrom(key, InsecureSecretKeyAccess.get())));
    }
    
    private AesEaxJce(final byte[] key, final int ivSizeInBytes, final byte[] outputPrefix) throws GeneralSecurityException {
        if (!AesEaxJce.FIPS.isCompatible()) {
            throw new GeneralSecurityException("Can not use AES-EAX in FIPS-mode.");
        }
        if (ivSizeInBytes != 12 && ivSizeInBytes != 16) {
            throw new IllegalArgumentException("IV size should be either 12 or 16 bytes");
        }
        this.ivSizeInBytes = ivSizeInBytes;
        Validators.validateAesKeySize(key.length);
        this.keySpec = new SecretKeySpec(key, "AES");
        this.cmac = createCmac(key);
        this.outputPrefix = outputPrefix;
    }
    
    public AesEaxJce(final byte[] key, final int ivSizeInBytes) throws GeneralSecurityException {
        this(key, ivSizeInBytes, new byte[0]);
    }
    
    private byte[] omac(final int tag, final byte[] data, final int offset, final int length) throws GeneralSecurityException {
        final byte[] input = new byte[length + 16];
        input[15] = (byte)tag;
        System.arraycopy(data, offset, input, 16, length);
        return this.cmac.compute(input, 16);
    }
    
    @Override
    public byte[] encrypt(final byte[] plaintext, final byte[] associatedData) throws GeneralSecurityException {
        if (plaintext.length > Integer.MAX_VALUE - this.outputPrefix.length - this.ivSizeInBytes - 16) {
            throw new GeneralSecurityException("plaintext too long");
        }
        final byte[] ciphertext = Arrays.copyOf(this.outputPrefix, this.outputPrefix.length + this.ivSizeInBytes + plaintext.length + 16);
        final byte[] iv = Random.randBytes(this.ivSizeInBytes);
        System.arraycopy(iv, 0, ciphertext, this.outputPrefix.length, this.ivSizeInBytes);
        final byte[] n = this.omac(0, iv, 0, iv.length);
        byte[] aad = associatedData;
        if (aad == null) {
            aad = new byte[0];
        }
        final byte[] h = this.omac(1, aad, 0, aad.length);
        final Cipher ctr = AesEaxJce.localCtrCipher.get();
        ctr.init(1, this.keySpec, new IvParameterSpec(n));
        ctr.doFinal(plaintext, 0, plaintext.length, ciphertext, this.outputPrefix.length + this.ivSizeInBytes);
        final byte[] t = this.omac(2, ciphertext, this.outputPrefix.length + this.ivSizeInBytes, plaintext.length);
        final int offset = this.outputPrefix.length + plaintext.length + this.ivSizeInBytes;
        for (int i = 0; i < 16; ++i) {
            ciphertext[offset + i] = (byte)(h[i] ^ n[i] ^ t[i]);
        }
        return ciphertext;
    }
    
    @Override
    public byte[] decrypt(final byte[] ciphertext, final byte[] associatedData) throws GeneralSecurityException {
        final int plaintextLength = ciphertext.length - this.outputPrefix.length - this.ivSizeInBytes - 16;
        if (plaintextLength < 0) {
            throw new GeneralSecurityException("ciphertext too short");
        }
        if (!Util.isPrefix(this.outputPrefix, ciphertext)) {
            throw new GeneralSecurityException("Decryption failed (OutputPrefix mismatch).");
        }
        final byte[] n = this.omac(0, ciphertext, this.outputPrefix.length, this.ivSizeInBytes);
        byte[] aad = associatedData;
        if (aad == null) {
            aad = new byte[0];
        }
        final byte[] h = this.omac(1, aad, 0, aad.length);
        final byte[] t = this.omac(2, ciphertext, this.outputPrefix.length + this.ivSizeInBytes, plaintextLength);
        byte res = 0;
        final int offset = ciphertext.length - 16;
        for (int i = 0; i < 16; ++i) {
            res |= (byte)(ciphertext[offset + i] ^ h[i] ^ n[i] ^ t[i]);
        }
        if (res != 0) {
            throw new AEADBadTagException("tag mismatch");
        }
        final Cipher ctr = AesEaxJce.localCtrCipher.get();
        ctr.init(1, this.keySpec, new IvParameterSpec(n));
        return ctr.doFinal(ciphertext, this.outputPrefix.length + this.ivSizeInBytes, plaintextLength);
    }
    
    static {
        FIPS = TinkFipsUtil.AlgorithmFipsCompatibility.ALGORITHM_NOT_FIPS;
        localCtrCipher = new ThreadLocal<Cipher>() {
            @Override
            protected Cipher initialValue() {
                try {
                    return EngineFactory.CIPHER.getInstance("AES/CTR/NOPADDING");
                }
                catch (final GeneralSecurityException ex) {
                    throw new IllegalStateException(ex);
                }
            }
        };
    }
}
