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

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

import java.nio.ByteOrder;
import javax.crypto.AEADBadTagException;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.GeneralSecurityException;
import com.google.crypto.tink.config.internal.TinkFipsUtil;

abstract class InsecureNonceChaCha20Poly1305Base
{
    public static final TinkFipsUtil.AlgorithmFipsCompatibility FIPS;
    private final InsecureNonceChaCha20Base chacha20;
    private final InsecureNonceChaCha20Base macKeyChaCha20;
    
    public InsecureNonceChaCha20Poly1305Base(final byte[] key) throws GeneralSecurityException {
        if (!InsecureNonceChaCha20Poly1305Base.FIPS.isCompatible()) {
            throw new GeneralSecurityException("Can not use ChaCha20Poly1305 in FIPS-mode.");
        }
        this.chacha20 = this.newChaCha20Instance(key, 1);
        this.macKeyChaCha20 = this.newChaCha20Instance(key, 0);
    }
    
    abstract InsecureNonceChaCha20Base newChaCha20Instance(final byte[] key, final int initialCounter) throws InvalidKeyException;
    
    public byte[] encrypt(final byte[] nonce, final byte[] plaintext, final byte[] associatedData) throws GeneralSecurityException {
        if (plaintext.length > 2147483631) {
            throw new GeneralSecurityException("plaintext too long");
        }
        final ByteBuffer ciphertext = ByteBuffer.allocate(plaintext.length + 16);
        this.encrypt(ciphertext, nonce, plaintext, associatedData);
        return ciphertext.array();
    }
    
    public void encrypt(final ByteBuffer output, final byte[] nonce, final byte[] plaintext, final byte[] associatedData) throws GeneralSecurityException {
        if (output.remaining() < plaintext.length + 16) {
            throw new IllegalArgumentException("Given ByteBuffer output is too small");
        }
        final int firstPosition = output.position();
        this.chacha20.encrypt(output, nonce, plaintext);
        output.position(firstPosition);
        output.limit(output.limit() - 16);
        byte[] aad = associatedData;
        if (aad == null) {
            aad = new byte[0];
        }
        final byte[] tag = Poly1305.computeMac(this.getMacKey(nonce), macDataRfc8439(aad, output));
        output.limit(output.limit() + 16);
        output.put(tag);
    }
    
    public byte[] decrypt(final byte[] nonce, final byte[] ciphertext, final byte[] associatedData) throws GeneralSecurityException {
        return this.decrypt(ByteBuffer.wrap(ciphertext), nonce, associatedData);
    }
    
    public byte[] decrypt(final ByteBuffer ciphertext, final byte[] nonce, final byte[] associatedData) throws GeneralSecurityException {
        if (ciphertext.remaining() < 16) {
            throw new GeneralSecurityException("ciphertext too short");
        }
        final int firstPosition = ciphertext.position();
        final byte[] tag = new byte[16];
        ciphertext.position(ciphertext.limit() - 16);
        ciphertext.get(tag);
        ciphertext.position(firstPosition);
        ciphertext.limit(ciphertext.limit() - 16);
        byte[] aad = associatedData;
        if (aad == null) {
            aad = new byte[0];
        }
        try {
            Poly1305.verifyMac(this.getMacKey(nonce), macDataRfc8439(aad, ciphertext), tag);
        }
        catch (final GeneralSecurityException ex) {
            throw new AEADBadTagException(ex.toString());
        }
        ciphertext.position(firstPosition);
        return this.chacha20.decrypt(nonce, ciphertext);
    }
    
    private byte[] getMacKey(final byte[] nonce) throws GeneralSecurityException {
        final ByteBuffer firstBlock = this.macKeyChaCha20.chacha20Block(nonce, 0);
        final byte[] result = new byte[32];
        firstBlock.get(result);
        return result;
    }
    
    private static byte[] macDataRfc8439(final byte[] aad, final ByteBuffer ciphertext) {
        final int aadPaddedLen = (aad.length % 16 == 0) ? aad.length : (aad.length + 16 - aad.length % 16);
        final int ciphertextLen = ciphertext.remaining();
        final int ciphertextPaddedLen = (ciphertextLen % 16 == 0) ? ciphertextLen : (ciphertextLen + 16 - ciphertextLen % 16);
        final ByteBuffer macData = ByteBuffer.allocate(aadPaddedLen + ciphertextPaddedLen + 16).order(ByteOrder.LITTLE_ENDIAN);
        macData.put(aad);
        macData.position(aadPaddedLen);
        macData.put(ciphertext);
        macData.position(aadPaddedLen + ciphertextPaddedLen);
        macData.putLong(aad.length);
        macData.putLong(ciphertextLen);
        return macData.array();
    }
    
    static {
        FIPS = TinkFipsUtil.AlgorithmFipsCompatibility.ALGORITHM_NOT_FIPS;
    }
}
