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

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

import com.google.crypto.tink.subtle.EngineFactory;
import com.google.crypto.tink.mac.internal.AesUtil;
import java.util.Arrays;
import com.google.crypto.tink.subtle.Bytes;
import java.security.Key;
import java.security.InvalidAlgorithmParameterException;
import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.prf.AesCmacPrfKey;
import javax.crypto.spec.SecretKeySpec;
import com.google.crypto.tink.subtle.Validators;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import com.google.crypto.tink.config.internal.TinkFipsUtil;
import com.google.crypto.tink.AccessesPartialKey;
import com.google.errorprone.annotations.Immutable;
import com.google.crypto.tink.prf.Prf;

@Immutable
@AccessesPartialKey
public final class PrfAesCmac implements Prf
{
    public static final TinkFipsUtil.AlgorithmFipsCompatibility FIPS;
    private final SecretKey keySpec;
    private byte[] subKey1;
    private byte[] subKey2;
    private static final ThreadLocal<Cipher> localAesCipher;
    
    private static Cipher instance() throws GeneralSecurityException {
        if (!PrfAesCmac.FIPS.isCompatible()) {
            throw new GeneralSecurityException("Can not use AES-CMAC in FIPS-mode.");
        }
        return PrfAesCmac.localAesCipher.get();
    }
    
    private PrfAesCmac(final byte[] key) throws GeneralSecurityException {
        Validators.validateAesKeySize(key.length);
        this.keySpec = new SecretKeySpec(key, "AES");
        this.generateSubKeys();
    }
    
    public static Prf create(final AesCmacPrfKey key) throws GeneralSecurityException {
        return new PrfAesCmac(key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get()));
    }
    
    static int calcN(final int dataLength) {
        if (dataLength == 0) {
            return 1;
        }
        return (dataLength - 1) / 16 + 1;
    }
    
    private static void xorBlock(final byte[] x, final byte[] y, final int offsetY, final byte[] output) {
        for (int i = 0; i < 16; ++i) {
            output[i] = (byte)(x[i] ^ y[i + offsetY]);
        }
    }
    
    @Override
    public byte[] compute(final byte[] data, final int outputLength) throws GeneralSecurityException {
        if (outputLength > 16) {
            throw new InvalidAlgorithmParameterException("outputLength too large, max is 16 bytes");
        }
        final Cipher aes = instance();
        aes.init(1, this.keySpec);
        final int n = calcN(data.length);
        final boolean flag = n * 16 == data.length;
        byte[] mLast;
        if (flag) {
            mLast = Bytes.xor(data, (n - 1) * 16, this.subKey1, 0, 16);
        }
        else {
            mLast = Bytes.xor(AesUtil.cmacPad(Arrays.copyOfRange(data, (n - 1) * 16, data.length)), this.subKey2);
        }
        final byte[] x = new byte[16];
        final byte[] y = new byte[16];
        for (int i = 0; i < n - 1; ++i) {
            xorBlock(x, data, i * 16, y);
            final int written = aes.doFinal(y, 0, 16, x);
            if (written != 16) {
                throw new IllegalStateException("Cipher didn't write full block");
            }
        }
        xorBlock(x, mLast, 0, y);
        final int written2 = aes.doFinal(y, 0, 16, x);
        if (written2 != 16) {
            throw new IllegalStateException("Cipher didn't write full block");
        }
        if (x.length == outputLength) {
            return x;
        }
        return Arrays.copyOf(x, outputLength);
    }
    
    private void generateSubKeys() throws GeneralSecurityException {
        final Cipher aes = instance();
        aes.init(1, this.keySpec);
        final byte[] zeroes = new byte[16];
        final byte[] l = aes.doFinal(zeroes);
        this.subKey1 = AesUtil.dbl(l);
        this.subKey2 = AesUtil.dbl(this.subKey1);
    }
    
    static {
        FIPS = TinkFipsUtil.AlgorithmFipsCompatibility.ALGORITHM_NOT_FIPS;
        localAesCipher = new ThreadLocal<Cipher>() {
            @Override
            protected Cipher initialValue() {
                try {
                    return EngineFactory.CIPHER.getInstance("AES/ECB/NoPadding");
                }
                catch (final GeneralSecurityException ex) {
                    throw new IllegalStateException(ex);
                }
            }
        };
    }
}
