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

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

import com.google.crypto.tink.subtle.AesSiv;
import com.google.crypto.tink.daead.AesSivKey;
import com.google.crypto.tink.DeterministicAead;
import com.google.crypto.tink.subtle.Bytes;
import com.google.crypto.tink.AccessesPartialKey;
import com.google.crypto.tink.subtle.EncryptThenAuthenticate;
import com.google.crypto.tink.util.SecretBytes;
import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.aead.AesCtrHmacAeadKey;
import com.google.crypto.tink.Aead;
import javax.crypto.Cipher;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.SecretKey;
import java.util.Arrays;
import java.security.Key;
import com.google.crypto.tink.subtle.Random;
import com.google.crypto.tink.aead.internal.AesGcmJceUtil;
import com.google.crypto.tink.Parameters;
import java.security.GeneralSecurityException;
import com.google.crypto.tink.daead.AesSivParameters;
import com.google.crypto.tink.aead.AesCtrHmacAeadParameters;
import com.google.crypto.tink.aead.AesGcmParameters;
import com.google.crypto.tink.hybrid.EciesParameters;

public final class EciesDemHelper
{
    private static final byte[] EMPTY_AAD;
    
    public static Dem getDem(final EciesParameters parameters) throws GeneralSecurityException {
        final Parameters demParameters = parameters.getDemParameters();
        if (demParameters instanceof AesGcmParameters) {
            return new AesGcmDem((AesGcmParameters)demParameters);
        }
        if (demParameters instanceof AesCtrHmacAeadParameters) {
            return new AesCtrHmacDem((AesCtrHmacAeadParameters)demParameters);
        }
        if (demParameters instanceof AesSivParameters) {
            return new AesSivDem((AesSivParameters)demParameters);
        }
        throw new GeneralSecurityException("Unsupported DEM parameters: " + demParameters);
    }
    
    private EciesDemHelper() {
    }
    
    static {
        EMPTY_AAD = new byte[0];
    }
    
    private static final class AesGcmDem implements Dem
    {
        private static final int AES_GCM_IV_SIZE_IN_BYTES = 12;
        private static final int AES_GCM_TAG_SIZE_IN_BYTES = 16;
        private final int keySizeInBytes;
        
        public AesGcmDem(final AesGcmParameters parameters) throws GeneralSecurityException {
            if (parameters.getIvSizeBytes() != 12) {
                throw new GeneralSecurityException("invalid IV size");
            }
            if (parameters.getTagSizeBytes() != 16) {
                throw new GeneralSecurityException("invalid tag size");
            }
            if (parameters.getVariant() != AesGcmParameters.Variant.NO_PREFIX) {
                throw new GeneralSecurityException("invalid variant");
            }
            this.keySizeInBytes = parameters.getKeySizeBytes();
        }
        
        @Override
        public int getSymmetricKeySizeInBytes() {
            return this.keySizeInBytes;
        }
        
        @Override
        public byte[] encrypt(final byte[] demKeyValue, final byte[] prefix, final byte[] header, final byte[] plaintext) throws GeneralSecurityException {
            if (demKeyValue.length != this.keySizeInBytes) {
                throw new GeneralSecurityException("invalid key size");
            }
            final SecretKey keySpec = AesGcmJceUtil.getSecretKey(demKeyValue);
            final byte[] nonce = Random.randBytes(12);
            final AlgorithmParameterSpec params = AesGcmJceUtil.getParams(nonce);
            final Cipher cipher = AesGcmJceUtil.getThreadLocalCipher();
            cipher.init(1, keySpec, params);
            final int outputSize = cipher.getOutputSize(plaintext.length);
            final int prefixAndHeaderSize = prefix.length + header.length;
            if (outputSize > Integer.MAX_VALUE - prefixAndHeaderSize - 12) {
                throw new GeneralSecurityException("plaintext too long");
            }
            final int len = prefixAndHeaderSize + 12 + outputSize;
            final byte[] output = Arrays.copyOf(prefix, len);
            System.arraycopy(header, 0, output, prefix.length, header.length);
            System.arraycopy(nonce, 0, output, prefixAndHeaderSize, 12);
            final int written = cipher.doFinal(plaintext, 0, plaintext.length, output, prefixAndHeaderSize + 12);
            if (written != outputSize) {
                throw new GeneralSecurityException("not enough data written");
            }
            return output;
        }
        
        @Override
        public byte[] decrypt(final byte[] demKeyValue, final byte[] ciphertext, final int prefixAndHeaderSize) throws GeneralSecurityException {
            if (ciphertext.length < prefixAndHeaderSize) {
                throw new GeneralSecurityException("ciphertext too short");
            }
            if (demKeyValue.length != this.keySizeInBytes) {
                throw new GeneralSecurityException("invalid key size");
            }
            final SecretKey key = AesGcmJceUtil.getSecretKey(demKeyValue);
            if (ciphertext.length < prefixAndHeaderSize + 12 + 16) {
                throw new GeneralSecurityException("ciphertext too short");
            }
            final AlgorithmParameterSpec params = AesGcmJceUtil.getParams(ciphertext, prefixAndHeaderSize, 12);
            final Cipher cipher = AesGcmJceUtil.getThreadLocalCipher();
            cipher.init(2, key, params);
            final int offset = prefixAndHeaderSize + 12;
            final int len = ciphertext.length - prefixAndHeaderSize - 12;
            return cipher.doFinal(ciphertext, offset, len);
        }
    }
    
    private static final class AesCtrHmacDem implements Dem
    {
        private final AesCtrHmacAeadParameters parameters;
        private final int keySizeInBytes;
        
        public AesCtrHmacDem(final AesCtrHmacAeadParameters parameters) {
            this.parameters = parameters;
            this.keySizeInBytes = parameters.getAesKeySizeBytes() + parameters.getHmacKeySizeBytes();
        }
        
        @Override
        public int getSymmetricKeySizeInBytes() {
            return this.keySizeInBytes;
        }
        
        @AccessesPartialKey
        private Aead getAead(final byte[] symmetricKeyValue) throws GeneralSecurityException {
            final byte[] aesCtrKeyValue = Arrays.copyOf(symmetricKeyValue, this.parameters.getAesKeySizeBytes());
            final byte[] hmacKeyValue = Arrays.copyOfRange(symmetricKeyValue, this.parameters.getAesKeySizeBytes(), this.parameters.getAesKeySizeBytes() + this.parameters.getHmacKeySizeBytes());
            return EncryptThenAuthenticate.create(AesCtrHmacAeadKey.builder().setParameters(this.parameters).setAesKeyBytes(SecretBytes.copyFrom(aesCtrKeyValue, InsecureSecretKeyAccess.get())).setHmacKeyBytes(SecretBytes.copyFrom(hmacKeyValue, InsecureSecretKeyAccess.get())).build());
        }
        
        @Override
        public byte[] encrypt(final byte[] demKeyValue, final byte[] prefix, final byte[] header, final byte[] plaintext) throws GeneralSecurityException {
            final byte[] ciphertext = this.getAead(demKeyValue).encrypt(plaintext, EciesDemHelper.EMPTY_AAD);
            return Bytes.concat(new byte[][] { prefix, header, ciphertext });
        }
        
        @Override
        public byte[] decrypt(final byte[] demKeyValue, final byte[] ciphertext, final int prefixAndHeaderSize) throws GeneralSecurityException {
            if (ciphertext.length < prefixAndHeaderSize) {
                throw new GeneralSecurityException("ciphertext too short");
            }
            final byte[] demCiphertext = Arrays.copyOfRange(ciphertext, prefixAndHeaderSize, ciphertext.length);
            return this.getAead(demKeyValue).decrypt(demCiphertext, EciesDemHelper.EMPTY_AAD);
        }
    }
    
    private static final class AesSivDem implements Dem
    {
        private final AesSivParameters parameters;
        private final int keySizeInBytes;
        
        public AesSivDem(final AesSivParameters parameters) {
            this.parameters = parameters;
            this.keySizeInBytes = parameters.getKeySizeBytes();
        }
        
        @Override
        public int getSymmetricKeySizeInBytes() {
            return this.keySizeInBytes;
        }
        
        @AccessesPartialKey
        private DeterministicAead getDaead(final byte[] symmetricKeyValue) throws GeneralSecurityException {
            return AesSiv.create(AesSivKey.builder().setParameters(this.parameters).setKeyBytes(SecretBytes.copyFrom(symmetricKeyValue, InsecureSecretKeyAccess.get())).build());
        }
        
        @Override
        public byte[] encrypt(final byte[] demKeyValue, final byte[] prefix, final byte[] header, final byte[] plaintext) throws GeneralSecurityException {
            final byte[] ciphertext = this.getDaead(demKeyValue).encryptDeterministically(plaintext, EciesDemHelper.EMPTY_AAD);
            return Bytes.concat(new byte[][] { prefix, header, ciphertext });
        }
        
        @Override
        public byte[] decrypt(final byte[] demKeyValue, final byte[] ciphertext, final int prefixAndHeaderSize) throws GeneralSecurityException {
            if (ciphertext.length < prefixAndHeaderSize) {
                throw new GeneralSecurityException("ciphertext too short");
            }
            final byte[] demCiphertext = Arrays.copyOfRange(ciphertext, prefixAndHeaderSize, ciphertext.length);
            return this.getDaead(demKeyValue).decryptDeterministically(demCiphertext, EciesDemHelper.EMPTY_AAD);
        }
    }
    
    public interface Dem
    {
        int getSymmetricKeySizeInBytes();
        
        byte[] encrypt(final byte[] demKeyValue, final byte[] prefix, final byte[] header, final byte[] plaintext) throws GeneralSecurityException;
        
        byte[] decrypt(final byte[] demKeyValue, final byte[] ciphertext, final int prefixAndHeaderSize) throws GeneralSecurityException;
    }
}
