/*
 * oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
 *
 * Copyright (c) 2014, Gluu
 */

package org.gluu.oxauth.model.jwe;

import org.apache.commons.lang.ArrayUtils;
import org.gluu.oxauth.model.crypto.encryption.BlockEncryptionAlgorithm;
import org.gluu.oxauth.model.exception.InvalidParameterException;
import org.gluu.oxauth.model.util.Base64Util;
import org.gluu.oxauth.model.util.Util;
import org.gluu.util.security.SecurityProviderUtility;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;

/**
 * @author Javier Rojas Blum
 * @version July 31, 2016
 */
public class KeyDerivationFunction {

    public static byte[] generateCek(byte[] cmk, BlockEncryptionAlgorithm blockEncryptionAlgorithm)
            throws UnsupportedEncodingException, NoSuchProviderException, NoSuchAlgorithmException, InvalidParameterException {
        if (cmk == null) {
            throw new InvalidParameterException("The content master key (CMK) is null");
        }
        if (blockEncryptionAlgorithm == null) {
            throw new InvalidParameterException("The block encryption algorithm is null");
        }
        if (blockEncryptionAlgorithm != BlockEncryptionAlgorithm.A128CBC_PLUS_HS256
                && blockEncryptionAlgorithm != BlockEncryptionAlgorithm.A256CBC_PLUS_HS512) {
            throw new InvalidParameterException("The block encryption algorithm is not supported");
        }

        byte[] round1 = Base64Util.unsignedToBytes(new int[]{0, 0, 0, 1});
        byte[] outputBitSize = null;
        if (blockEncryptionAlgorithm != BlockEncryptionAlgorithm.A128CBC_PLUS_HS256) {
            outputBitSize = Base64Util.unsignedToBytes(new int[]{0, 0, 0, 128});
        } else { //A256CBC_PLUS_HS512
            outputBitSize = Base64Util.unsignedToBytes(new int[]{0, 0, 1, 0});
        }
        byte[] encValue = blockEncryptionAlgorithm.getName().getBytes(Util.UTF8_STRING_ENCODING);
        byte[] epu = Base64Util.unsignedToBytes(new int[]{0, 0, 0, 0});
        byte[] epv = Base64Util.unsignedToBytes(new int[]{0, 0, 0, 0});
        byte[] label = "Encryption".getBytes(Util.UTF8_STRING_ENCODING);
        byte[] round1Input = ArrayUtils.addAll(round1, cmk);
        round1Input = ArrayUtils.addAll(round1Input, outputBitSize);
        round1Input = ArrayUtils.addAll(round1Input, encValue);
        round1Input = ArrayUtils.addAll(round1Input, epu);
        round1Input = ArrayUtils.addAll(round1Input, epv);
        round1Input = ArrayUtils.addAll(round1Input, label);

        MessageDigest mda = MessageDigest.getInstance(blockEncryptionAlgorithm.getMessageDiggestAlgorithm(), SecurityProviderUtility.getBCProvider());
        byte[] round1Hash = mda.digest(round1Input);
        byte[] cek = Arrays.copyOf(round1Hash, blockEncryptionAlgorithm.getCekLength() / 8);

        return cek;
    }

    public static byte[] generateCik(byte[] cmk, BlockEncryptionAlgorithm blockEncryptionAlgorithm)
            throws UnsupportedEncodingException, NoSuchProviderException, NoSuchAlgorithmException, InvalidParameterException {
        if (cmk == null) {
            throw new InvalidParameterException("The content master key (CMK) is null");
        }
        if (blockEncryptionAlgorithm == null) {
            throw new InvalidParameterException("The block encryption algorithm is null");
        }
        if (blockEncryptionAlgorithm != BlockEncryptionAlgorithm.A128CBC_PLUS_HS256
                && blockEncryptionAlgorithm != BlockEncryptionAlgorithm.A256CBC_PLUS_HS512) {
            throw new InvalidParameterException("The block encryption algorithm is not supported");
        }

        byte[] round1 = Base64Util.unsignedToBytes(new int[]{0, 0, 0, 1});
        byte[] outputBitSize = null;
        if (blockEncryptionAlgorithm != BlockEncryptionAlgorithm.A128CBC_PLUS_HS256) {
            outputBitSize = Base64Util.unsignedToBytes(new int[]{0, 0, 1, 0});
        } else { //A256CBC_PLUS_HS512
            outputBitSize = Base64Util.unsignedToBytes(new int[]{0, 0, 2, 0});
        }
        byte[] encValue = blockEncryptionAlgorithm.getName().getBytes(Util.UTF8_STRING_ENCODING);
        byte[] epu = Base64Util.unsignedToBytes(new int[]{0, 0, 0, 0});
        byte[] epv = Base64Util.unsignedToBytes(new int[]{0, 0, 0, 0});
        byte[] label = "Integrity".getBytes(Util.UTF8_STRING_ENCODING);
        byte[] round1Input = ArrayUtils.addAll(round1, cmk);
        round1Input = ArrayUtils.addAll(round1Input, outputBitSize);
        round1Input = ArrayUtils.addAll(round1Input, encValue);
        round1Input = ArrayUtils.addAll(round1Input, epu);
        round1Input = ArrayUtils.addAll(round1Input, epv);
        round1Input = ArrayUtils.addAll(round1Input, label);

        MessageDigest mda = MessageDigest.getInstance(blockEncryptionAlgorithm.getMessageDiggestAlgorithm(), SecurityProviderUtility.getBCProvider());
        byte[] cik = mda.digest(round1Input);

        return cik;
    }
}