package com.sunyard.chsm.sdf;


import com.sunyard.chsm.enums.AlgMode;
import com.sunyard.chsm.enums.KeyAlg;
import com.sunyard.chsm.enums.KeyCategory;
import com.sunyard.chsm.enums.Padding;
import com.sunyard.chsm.sdf.model.EccKey;
import com.sunyard.chsm.sdf.util.LangUtils;
import com.sunyard.chsm.utils.gm.BCSM2Utils;
import com.sunyard.chsm.utils.gm.BCSM3Utils;
import com.sunyard.chsm.utils.gm.BCSM4Utils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.util.BigIntegers;
import org.springframework.stereotype.Service;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.*;


/**
 * 基于 BC 库的国密软算法实现
 * 当前实现类的接口返回的对称密钥和私钥认为是明文，在上层 Service 进行使用和存储时的加密和解密运算。
 * <p>
 * 主密钥：
 * - 生成。 软随机数。项目启动时不存在则自动生成。
 * - 存储。 存储在 SC_PARAM_CONF 表, KEY 为 mk 的字段，值为固定的 48 字节，以 HEX 格式 96 字节存储。
 * 前 16 字节为主密钥值，后 32 字节为主密钥值使用 SM3 计算的校验值。
 * - 同步。基于数据库进行导入、导出、和备份、恢复。多台应用服务器连接同一个数据库，无需同步。
 * - 使用。顶层密钥，用于保护其他存于数据的密钥。
 *
 * @author liulu 、Cheney
 * @since 2024/10/23
 */
@Slf4j
@Service
public class BCSdfApiService extends AbstractSdfApiService {

    public BCSdfApiService() {
        super();
    }

    @Override
    public byte[] generateRandom(int len) {
        byte[] res = new byte[len];
        new SecureRandom().nextBytes(res);
        return res;
    }


    /**
     * 生成对称算法密钥
     *
     * @param alg    算法，只支持对称算法
     * @param keyLen 密钥长度(bit)，只针对密钥长度可变的算法。
     *               禁止传 null
     * @return
     */
    @Override
    public byte[] genSymKey(KeyAlg alg, Integer keyLen) {
        switch (alg) {
            case SM4: {
                if (null == keyLen || keyLen != BCSM4Utils.DEFAULT_KEY_SIZE) {
                    throw new IllegalArgumentException("Invalid key length: " + keyLen);
                }
                try {
                    return BCSM4Utils.generateKey();
                } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
                    throw new RuntimeException("算法实现错误", e);
                }
            }
            default: {
                throw new IllegalArgumentException("Unsupported algorithm: " + alg);
            }
        }
    }

    @Override
    public byte[] symEncrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data) {
        return symCalc(Cipher.ENCRYPT_MODE, alg, mode, padding, key, data);
    }


    @Override
    public byte[] symDecrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data) {
        return symCalc(Cipher.DECRYPT_MODE, alg, mode, padding, key, data);
    }


    /**
     * 对称加解密的统一实现
     *
     * @param alg  算法，只支持对称算法
     * @param key  密钥值，明文
     * @param data 加密时明文数据，解密时为密文数据
     */
    private byte[] symCalc(int cipherMode, KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data) {
        if (alg.getCategory() != KeyCategory.SYM_KEY) {
            throw new IllegalArgumentException("Must SYM_KEY, unsupported algorithm: " + alg);
        }

        // 算法
        String algName = null;
        if (alg == KeyAlg.SM4) {
            algName = "SM4/";
        } else {
            throw new IllegalArgumentException("Unsupported algorithm: " + alg);
        }

        // 算法轮模式
        algName += mode.getCode() + "/";

        // 填充模式
        algName += padding.getCode();


        Cipher cipher = null;
        try {
            cipher = BCSM4Utils.generateECBCipher(algName, cipherMode, key);
            return cipher.doFinal(data);
        } catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException e) {
            throw new RuntimeException("算法执行错误", e);
        }
    }

    @SneakyThrows
    @Override
    public EccKey genKeyPairEcc() {
        // 生成密钥对
        KeyPair keyPair = BCSM2Utils.generateKeyPair();
        BCECPublicKey pubKey = (BCECPublicKey) keyPair.getPublic();
        BCECPrivateKey priKey = (BCECPrivateKey) keyPair.getPrivate();
        byte[] x = pubKey.getQ().getXCoord().getEncoded();
        byte[] y = pubKey.getQ().getYCoord().getEncoded();
        byte[] d = BigIntegers.asUnsignedByteArray(32, priKey.getD());
        return new EccKey(LangUtils.merge(x, y), d);
    }


    @Override
    public byte[] calculateMAC(byte[] symKey, byte[] pucIv, byte[] pucData) {
        return new byte[0];
    }

    @Override
    public byte[] hmac(byte[] key, byte[] srcData) {
        return BCSM3Utils.hmac(key, srcData);
    }

    @Override
    public byte[] hash(byte[] pucData) {
        return BCSM3Utils.hash(pucData);
    }

    @Override
    public byte[] encryptByTMK(byte[] data) {
        return data;
    }

    @Override
    public byte[] decryptByTMK(byte[] data) {
        return data;
    }

}
