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

package com.google.crypto.tink.aead.internal;

import com.google.crypto.tink.subtle.Hex;
import com.google.crypto.tink.internal.Util;
import java.util.Arrays;
import com.google.crypto.tink.subtle.Random;
import com.google.crypto.tink.subtle.EngineFactory;
import com.google.crypto.tink.AccessesPartialKey;
import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.aead.ChaCha20Poly1305Key;
import java.security.InvalidKeyException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.GeneralSecurityException;
import java.security.Key;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.Cipher;
import java.security.Provider;
import javax.crypto.SecretKey;
import com.google.crypto.tink.config.internal.TinkFipsUtil;
import com.google.errorprone.annotations.Immutable;
import com.google.crypto.tink.Aead;

@Immutable
public final class ChaCha20Poly1305Jce implements Aead
{
    private static final TinkFipsUtil.AlgorithmFipsCompatibility FIPS;
    private static final int NONCE_SIZE_IN_BYTES = 12;
    private static final int TAG_SIZE_IN_BYTES = 16;
    private static final int KEY_SIZE_IN_BYTES = 32;
    private static final String CIPHER_NAME = "ChaCha20-Poly1305";
    private static final String KEY_NAME = "ChaCha20";
    private static final byte[] testKey;
    private static final byte[] testNonce;
    private static final byte[] testCiphertextOfEmpty;
    private final SecretKey keySpec;
    private final byte[] outputPrefix;
    private final Provider provider;
    
    private static boolean isValid(final Cipher cipher) {
        try {
            final AlgorithmParameterSpec params = new IvParameterSpec(ChaCha20Poly1305Jce.testNonce);
            cipher.init(2, new SecretKeySpec(ChaCha20Poly1305Jce.testKey, "ChaCha20"), params);
            final byte[] output = cipher.doFinal(ChaCha20Poly1305Jce.testCiphertextOfEmpty);
            if (output.length != 0) {
                return false;
            }
            cipher.init(2, new SecretKeySpec(ChaCha20Poly1305Jce.testKey, "ChaCha20"), params);
            final byte[] output2 = cipher.doFinal(ChaCha20Poly1305Jce.testCiphertextOfEmpty);
            return output2.length == 0;
        }
        catch (final GeneralSecurityException ex) {
            return false;
        }
    }
    
    private ChaCha20Poly1305Jce(final byte[] key, final byte[] outputPrefix, final Provider provider) throws GeneralSecurityException {
        if (!ChaCha20Poly1305Jce.FIPS.isCompatible()) {
            throw new GeneralSecurityException("Can not use ChaCha20Poly1305 in FIPS-mode.");
        }
        if (key.length != 32) {
            throw new InvalidKeyException("The key length in bytes must be 32.");
        }
        this.keySpec = new SecretKeySpec(key, "ChaCha20");
        this.outputPrefix = outputPrefix;
        this.provider = provider;
    }
    
    @AccessesPartialKey
    public static Aead create(final ChaCha20Poly1305Key key) throws GeneralSecurityException {
        final Cipher cipher = getValidCipherInstance();
        return new ChaCha20Poly1305Jce(key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get()), key.getOutputPrefix().toByteArray(), cipher.getProvider());
    }
    
    static Cipher getValidCipherInstance() throws GeneralSecurityException {
        final Cipher cipher = EngineFactory.CIPHER.getInstance("ChaCha20-Poly1305");
        if (!isValid(cipher)) {
            throw new GeneralSecurityException("JCE does not support algorithm: ChaCha20-Poly1305");
        }
        return cipher;
    }
    
    static Cipher getCipherInstance(final Provider provider) throws GeneralSecurityException {
        return Cipher.getInstance("ChaCha20-Poly1305", provider);
    }
    
    public static boolean isSupported() {
        try {
            final Cipher unused = getValidCipherInstance();
            return true;
        }
        catch (final GeneralSecurityException ex) {
            return false;
        }
    }
    
    @Override
    public byte[] encrypt(final byte[] plaintext, final byte[] associatedData) throws GeneralSecurityException {
        if (plaintext == null) {
            throw new NullPointerException("plaintext is null");
        }
        final byte[] nonce = Random.randBytes(12);
        final AlgorithmParameterSpec params = new IvParameterSpec(nonce);
        final Cipher cipher = getCipherInstance(this.provider);
        cipher.init(1, this.keySpec, params);
        if (associatedData != null && associatedData.length != 0) {
            cipher.updateAAD(associatedData);
        }
        final int outputSize = cipher.getOutputSize(plaintext.length);
        if (outputSize > Integer.MAX_VALUE - this.outputPrefix.length - 12) {
            throw new GeneralSecurityException("plaintext too long");
        }
        final int len = this.outputPrefix.length + 12 + outputSize;
        final byte[] output = Arrays.copyOf(this.outputPrefix, len);
        System.arraycopy(nonce, 0, output, this.outputPrefix.length, 12);
        final int written = cipher.doFinal(plaintext, 0, plaintext.length, output, this.outputPrefix.length + 12);
        if (written != outputSize) {
            throw new GeneralSecurityException("not enough data written");
        }
        return output;
    }
    
    @Override
    public byte[] decrypt(final byte[] ciphertext, final byte[] associatedData) throws GeneralSecurityException {
        if (ciphertext == null) {
            throw new NullPointerException("ciphertext is null");
        }
        if (ciphertext.length < this.outputPrefix.length + 12 + 16) {
            throw new GeneralSecurityException("ciphertext too short");
        }
        if (!Util.isPrefix(this.outputPrefix, ciphertext)) {
            throw new GeneralSecurityException("Decryption failed (OutputPrefix mismatch).");
        }
        final byte[] nonce = new byte[12];
        System.arraycopy(ciphertext, this.outputPrefix.length, nonce, 0, 12);
        final AlgorithmParameterSpec params = new IvParameterSpec(nonce);
        final Cipher cipher = getCipherInstance(this.provider);
        cipher.init(2, this.keySpec, params);
        if (associatedData != null && associatedData.length != 0) {
            cipher.updateAAD(associatedData);
        }
        final int offset = this.outputPrefix.length + 12;
        final int len = ciphertext.length - this.outputPrefix.length - 12;
        return cipher.doFinal(ciphertext, offset, len);
    }
    
    static {
        FIPS = TinkFipsUtil.AlgorithmFipsCompatibility.ALGORITHM_NOT_FIPS;
        testKey = Hex.decode("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f");
        testNonce = Hex.decode("070000004041424344454647");
        testCiphertextOfEmpty = Hex.decode("a0784d7a4716f3feb4f64e7f4b39bf04");
    }
}
