package cn.anicert.secure.access.crypto.util;

import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.EncryptedContentInfo;
import org.bouncycastle.asn1.cms.EnvelopedData;
import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
import org.bouncycastle.asn1.cms.RecipientInfo;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.pkcs.ContentInfo;
import org.bouncycastle.asn1.pkcs.IssuerAndSerialNumber;
import org.bouncycastle.asn1.pkcs.SignedData;
import org.bouncycastle.asn1.pkcs.SignerInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.engines.SM2Engine.Mode;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;

import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECFieldFp;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Enumeration;

/**
 * @company cn.anicert
 * @author: wangpengyuan
 * @create: 2024-03-07
 * @description SM2工具类
 **/
public class SM2Utils extends BaseCryptoUtil {
    //////////////////////////////////////////////////////////////////////////////////////
    /*
     * 以下为SM2推荐曲线参数
     */
    public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
    public final static BigInteger SM2_ECC_P = CURVE.getQ();
    public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
    public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
    public final static BigInteger SM2_ECC_N = CURVE.getOrder();
    public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
    public final static BigInteger SM2_ECC_GX = new BigInteger(
            "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
    public final static BigInteger SM2_ECC_GY = new BigInteger(
            "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
    public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
    public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT,
            SM2_ECC_N, SM2_ECC_H);

    public static final EllipticCurve JDK_CURVE = new EllipticCurve(new ECFieldFp(SM2_ECC_P), SM2_ECC_A, SM2_ECC_B);
    public static final java.security.spec.ECPoint JDK_G_POINT = new java.security.spec.ECPoint(
            G_POINT.getAffineXCoord().toBigInteger(), G_POINT.getAffineYCoord().toBigInteger());
    public static final java.security.spec.ECParameterSpec JDK_EC_SPEC = new java.security.spec.ECParameterSpec(
            JDK_CURVE, JDK_G_POINT, SM2_ECC_N, SM2_ECC_H.intValue());

    private static final String ALGO_NAME_EC = "EC";
    public static Base64.Decoder decoder = Base64.getDecoder();
    public static Base64.Encoder encoder = Base64.getEncoder();

    private static final int C1_LEN = 65;
    private static final int C3_LEN = 32;

    public static ECPrivateKeyParameters getPriKey(String priKeyStr) {
        return getPriKey(Base64.getDecoder().decode(priKeyStr));
    }

    public static ECPrivateKeyParameters getPriKey(byte[] priKeyByte) {
        return new ECPrivateKeyParameters(new BigInteger(1, priKeyByte), DOMAIN_PARAMS);
    }


    public static String normalSign(String privateKey, String data) throws InvalidKeySpecException, InvalidKeyException, CryptoException {
        BCECPrivateKey parameters = SM2KeyUtil.decodeWithPKCS8(privateKey);
        CipherParameters param = ECUtil.generatePrivateKeyParameter(parameters);
        SM2Signer signer = new SM2Signer();
        signer.init(true, param);
        signer.update(data.getBytes(StandardCharsets.UTF_8), 0, data.length());
        return Base64.getEncoder().encodeToString(signer.generateSignature());
    }

    public static boolean normalVerifySign(String publicKey, String sign, String data) throws InvalidKeySpecException, InvalidKeyException, CryptoException {
        BCECPublicKey bcecPublicKey = SM2KeyUtil.decodeWithX509(publicKey);
        CipherParameters publicKeyParameter = ECUtil.generatePublicKeyParameter(bcecPublicKey);
        SM2Signer signer = new SM2Signer();
        signer.init(false, publicKeyParameter);
        signer.update(data.getBytes(StandardCharsets.UTF_8), 0, data.length());
        return signer.verifySignature(Base64.getDecoder().decode(sign));
    }

    public static String sign(String privateKey, String data) throws Exception {
        return Base64.getEncoder().encodeToString(sign(getPriKey(privateKey), data.getBytes(StandardCharsets.UTF_8)));
    }

    /**
     * @param priKeyParameters 私钥
     * @param srcData          带签名原文
     * @description: [priKeyParameters, srcData]
     * @return: byte[] DER编码后的签名值
     */
    public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] srcData)
            throws CryptoException {
        SM2Signer signer = new SM2Signer();
        ParametersWithRandom pwr = new ParametersWithRandom(priKeyParameters, new SecureRandom());
        signer.init(true, pwr);
        signer.update(srcData, 0, srcData.length);
        return signer.generateSignature();
    }

    public static String pkcs7SignDetach(String prikey, String cert, String oriData) throws Exception {
        Certificate signCert = CertUtils.parseSM2Certificate(cert);
        String sign = sign(prikey, oriData);
        byte[] signByte = Base64.getDecoder().decode(sign);

        //开始拼接P7
        int version = 1;
        int signerversion = 1;
        String hashAlg = "SM3";
        ASN1ObjectIdentifier hashOid = new ASN1ObjectIdentifier("1.2.156.10197.1").branch("401");
        ASN1ObjectIdentifier encryptOid = new ASN1ObjectIdentifier("1.2.156.10197.1").branch("301.1");
        ASN1ObjectIdentifier dataOid = new ASN1ObjectIdentifier("1.2.156.10197.6.1.4.2").branch("1");
        ASN1ObjectIdentifier signDataOid = new ASN1ObjectIdentifier("1.2.156.10197.6.1.4.2").branch("2");
        // Get all the digest
        ASN1EncodableVector digestV = new ASN1EncodableVector();
        AlgorithmIdentifier a = new AlgorithmIdentifier(hashOid);
        digestV.add(a);
        ASN1Set digestSet = new DERSet(digestV);
        // Get all the certificates
        ASN1EncodableVector certV = new ASN1EncodableVector();
        certV.add(signCert);
        ASN1Set certSet = new DERSet(certV);
        ASN1EncodableVector contentV = new ASN1EncodableVector();
        contentV.add(dataOid);
        DERSequence contentSeq = new DERSequence(contentV);
        // Create signerinfo structure.
        ASN1EncodableVector signInfoV = new ASN1EncodableVector();
        // Add the signerInfo version
        signInfoV.add(new ASN1Integer(signerversion));
        // Add Issuer SerialNumber
        IssuerAndSerialNumber isAnds = new IssuerAndSerialNumber(
                signCert.getIssuer(), signCert.getSerialNumber().getPositiveValue());
        signInfoV.add(isAnds);
        // Add the digestAlgorithm
        signInfoV.add(new AlgorithmIdentifier(
                hashOid));
        // Add the digestEncryptionAlgorithm
        signInfoV.add(new AlgorithmIdentifier(encryptOid));
        // Add the signValue
        signInfoV.add(new DEROctetString(signByte));
        DERSequence signInfoSeq = new DERSequence(signInfoV);
        ASN1Set signInfoSet = new DERSet(signInfoSeq);
        ASN1EncodableVector body = new ASN1EncodableVector();
        body.add(new ASN1Integer(version));
        body.add(digestSet);
        body.add(contentSeq);
        body.add(new DERTaggedObject(false, 0, certSet));
        body.add(signInfoSet);
        DERSequence bodySeq = new DERSequence(body);
        ASN1EncodableVector whole = new ASN1EncodableVector();
        whole.add(signDataOid);
        whole.add(new DERTaggedObject(0, bodySeq));
        DERSequence wholeSeq = new DERSequence(whole);
        return Base64.getEncoder().encodeToString(wholeSeq.getEncoded());
    }

    public static boolean pkcs7Verify(byte[] signValue, byte[] inputData) throws Exception {

        SignedData data = SignedData.getInstance(ContentInfo.getInstance(ASN1Sequence.getInstance(signValue)).getContent());
        Enumeration certEnum = data.getCertificates().getObjects();
        ASN1Sequence certSeq = (ASN1Sequence) certEnum.nextElement();
        Certificate signCert = Certificate.getInstance(certSeq);
        String publicKey = CertUtils.getCertInfo(signCert, 30);
        Enumeration signerInfoEnum = data.getSignerInfos().getObjects();
        ASN1Sequence signerInfoSeq = (ASN1Sequence) signerInfoEnum.nextElement();
        SignerInfo signerInfo = SignerInfo.getInstance(signerInfoSeq);
        byte[] pubKey = Base64.getDecoder().decode(publicKey);
        byte[] sign = signerInfo.getEncryptedDigest().getOctets();
        byte[] ori = null;
        ASN1Encodable content = data.getContentInfo().getContent();
        if (content == null) {//detach,使用传入的原文
            if (inputData == null) {
                throw new RuntimeException("找不到原文数据");
            }
            ori = inputData;
        } else {//atach,传入的原文参数失效，使用P7中解析的原文
            DEROctetString contentOct = (DEROctetString) content;
            ori = contentOct.getOctets();
        }
        return verify(pubKey, inputData, sign);
    }

    public static boolean verify(String publicKey, String data, String charset, String signature) throws Exception {
        return verify(Base64.getDecoder().decode(publicKey), data.getBytes(charset), Base64.getDecoder().decode(signature));
    }

    public static boolean verify(byte[] publicKey, byte[] data, byte[] signature) throws Exception {
        return verify(getPubKey(publicKey), data, signature);
    }

    /**
     * 验签
     *
     * @param pubKeyParameters 公钥
     * @param srcData          原文
     * @param sign             DER编码的签名值
     * @return 验签成功返回true，失败返回false
     */
    public static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] srcData, byte[] sign) {
        SM2Signer signer = new SM2Signer();
        signer.init(false, pubKeyParameters);
        signer.update(srcData, 0, srcData.length);
        return signer.verifySignature(sign);
    }

    public static ECPublicKeyParameters getPubKey(byte[] pubKeyByte) {
        return new ECPublicKeyParameters(CURVE.decodePoint(pubKeyByte), DOMAIN_PARAMS);
    }

    public static byte[] encodeEnvelop(String userCert, byte[] dataInput) throws Exception {
        //生成对称密钥
        byte[] key = SM4Utils.generateKey();
        byte[] encData = SM4Utils.encrypt_ECB_Padding(key, dataInput);
        Certificate certOb = CertUtils.parseSM2Certificate(userCert);
        String pubKey = CertUtils.getCertInfo(certOb, 30);
        byte[] encKey = encryptReByte(pubKey, key);
        //将C1C3C2模式裸数据改成ASN1格式
        ASN1EncodableVector encDataVec = changeC1C3C2ToAsn1(encKey);
        ASN1Integer version = new ASN1Integer(1);
        ASN1Integer receptionVer = new ASN1Integer(1);

        IssuerAndSerialNumber isAndSN = new IssuerAndSerialNumber(certOb.getIssuer(), certOb.getSerialNumber().getPositiveValue());
        AlgorithmIdentifier derAlgEncKey = new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.156.10197.1").branch("301.3"));
        DEROctetString derEncKey = new DEROctetString(new DERSequence(encDataVec));

        ASN1EncodableVector recipientVector = new ASN1EncodableVector();
        recipientVector.add(receptionVer);
        recipientVector.add(isAndSN);
        recipientVector.add(derAlgEncKey);
        recipientVector.add(derEncKey);
        ASN1Sequence recipientInfo = new DERSequence(recipientVector);

        AlgorithmIdentifier derAlg = new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.156.10197.1").branch("104"));
        DERSet recipientInfos = new DERSet(recipientInfo);
        ASN1ObjectIdentifier contentType = new ASN1ObjectIdentifier("1.2.156.10197.6.1.4.2").branch("1");
        ASN1OctetString encContent = new DEROctetString(encData);
        BERTaggedObject berTag = new BERTaggedObject(false, 0, encContent);
        ASN1EncodableVector contVector = new ASN1EncodableVector();
        contVector.add(contentType);
        contVector.add(derAlg);
        contVector.add(berTag);
        DERSequence cont = new DERSequence(contVector);

        ASN1EncodableVector envVector = new ASN1EncodableVector();
        envVector.add(version);
        envVector.add(recipientInfos);
        envVector.add(cont);
        DERSequence env = new DERSequence(envVector);
        ASN1EncodableVector p7Vector = new ASN1EncodableVector();
        p7Vector.add(new ASN1ObjectIdentifier("1.2.156.10197.6.1.4.2").branch("3"));
        DERTaggedObject dtCont = new DERTaggedObject(true, 0, env);
        p7Vector.add(dtCont);
        DERSequence p7 = new DERSequence(p7Vector);
        return p7.getEncoded();
    }

    /**
     * sm2加密
     *
     * @param publicKey
     * @param data
     * @return
     * @throws Exception
     */
    public static String encrypt(String publicKey, String data) throws Exception {
        return Base64.getEncoder().encodeToString(encryptReByte(Base64.getDecoder().decode(publicKey), data.getBytes(StandardCharsets.UTF_8)));
    }

    public static byte[] encryptReByte(String publicKey, byte[] data) throws Exception {
        return encryptReByte(Base64.getDecoder().decode(publicKey), data);
    }

    /**
     * @param publicKey 公钥
     * @param data      加密源数据
     * @description: [publicKey, data]
     * @return: byte[]
     */
    public static byte[] encryptReByte(byte[] publicKey, byte[] data) throws Exception {
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(CURVE.decodePoint(publicKey), DOMAIN_PARAMS);
        ParametersWithRandom pwr = new ParametersWithRandom(publicKeyParameters, new SecureRandom());
        SM2Engine engine = new SM2Engine(Mode.C1C3C2);
        engine.init(true, pwr);
        return engine.processBlock(data, 0, data.length);
    }

    /**
     * sm2解密
     *
     * @param privateKey
     * @param encryptedData
     * @return
     * @throws Exception
     */
    public static String decrypt(String privateKey, String encryptedData) throws Exception {
        return new String(decryptReByte(Base64.getDecoder().decode(privateKey), Base64.getDecoder().decode(encryptedData)), StandardCharsets.UTF_8);
    }

    /**
     * 数字信封解密
     *
     * @param priKey
     * @param envelop
     * @return
     * @throws Exception
     */
    public static byte[] decodeEnvelop(String priKey, byte[] envelop) throws Exception {
        ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(envelop));
        ContentInfo info = ContentInfo.getInstance(aIn.readObject());
        EnvelopedData envData = EnvelopedData.getInstance(info.getContent());
        ASN1Set recipientInfoSet = envData.getRecipientInfos();
        Enumeration recipientInfoEnum = recipientInfoSet.getObjects();
        RecipientInfo recipientInfo = RecipientInfo.getInstance(recipientInfoEnum.nextElement());
        KeyTransRecipientInfo keyTransRecipientInfo = (KeyTransRecipientInfo) recipientInfo.getInfo();
        //非对称加密算法解密对称密钥
        byte[] kek = keyTransRecipientInfo.getEncryptedKey().getOctets();
        //解C1C3C2结构
        byte[] key = decryptReByte(priKey, changeAsn1ToC1C3C2(kek));
        //对称密钥解密数据
        EncryptedContentInfo encryptedContentInfo = envData.getEncryptedContentInfo();
        byte[] encContent = encryptedContentInfo.getEncryptedContent().getOctets();
        return SM4Utils.decrypt_ECB_Padding(key, encContent);
    }

    public static byte[] decryptReByte(String privateKey, byte[] encryptedData) throws Exception {
        return decryptReByte(Base64.getDecoder().decode(privateKey), encryptedData);
    }

    /**
     * @param privateKey    私钥
     * @param encryptedData 待解密的密文
     * @description: [privateKey, encryptedData]
     * @return: byte[]
     */
    public static byte[] decryptReByte(byte[] privateKey, byte[] encryptedData) throws Exception {
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(new BigInteger(1, privateKey), DOMAIN_PARAMS);
        SM2Engine engine = new SM2Engine(Mode.C1C3C2);
        engine.init(false, privateKeyParameters);
        return engine.processBlock(encryptedData, 0, encryptedData.length);
    }

    /**
     * 将ASN1格式转成c1c3c2
     */
    public static byte[] changeAsn1ToC1C3C2(byte[] asn1) throws IOException {
        ASN1InputStream aIn = new ASN1InputStream(asn1);
        ASN1Sequence seq = (ASN1Sequence) aIn.readObject();
        BigInteger x = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();
        BigInteger y = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue();
        byte[] c3 = ASN1OctetString.getInstance(seq.getObjectAt(2)).getOctets();
        byte[] c2 = ASN1OctetString.getInstance(seq.getObjectAt(3)).getOctets();

        ECPoint c1Point = GMNamedCurves.getByName("sm2p256v1").getCurve().createPoint(x, y);
        byte[] c1 = c1Point.getEncoded(false);
        return concatenateByteArrays(c1, c3, c2);
    }

    public static byte[] concatenateByteArrays(byte[]... arrays) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        for (byte[] array : arrays) {
            outputStream.write(array);
        }
        return outputStream.toByteArray();
    }

    /**
     * 将c1c3c2转成标准的ASN1格式
     */
    public static ASN1EncodableVector changeC1C3C2ToAsn1(byte[] c1c3c2) throws IOException {
        byte[] c1 = Arrays.copyOfRange(c1c3c2, 0, C1_LEN);
        byte[] c3 = Arrays.copyOfRange(c1c3c2, C1_LEN, C1_LEN + C3_LEN);
        byte[] c2 = Arrays.copyOfRange(c1c3c2, C1_LEN + C3_LEN, c1c3c2.length);
        byte[] c1X = Arrays.copyOfRange(c1, 1, 33);
        byte[] c1Y = Arrays.copyOfRange(c1, 33, 65);

        BigInteger r = new BigInteger(1, c1X);
        BigInteger s = new BigInteger(1, c1Y);

        ASN1Integer x = new ASN1Integer(r);
        ASN1Integer y = new ASN1Integer(s);
        DEROctetString derDig = new DEROctetString(c3);
        DEROctetString derEnc = new DEROctetString(c2);
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(x);
        v.add(y);
        v.add(derDig);
        v.add(derEnc);
        return v;
    }


//    public static void main(String[] args) throws InvalidKeySpecException, InvalidKeyException, CryptoException {
//        String privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgaXYLE/mEwONu3ohCCMd2uOjUdeRnp0qy/yMcpzC1R3KgCgYIKoEcz1UBgi2hRANCAATWabavR0v2s/iud6oQzpkd4IFx2Caa7kQVZ95pIP++h567ozYXGDq6c0qjmZsd0SD93gVx5oEgsvSo6whQ4/DC";
//        String content = "talk is cheap,show me the code";
//        String publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE1mm2r0dL9rP4rneqEM6ZHeCBcdgmmu5EFWfeaSD/voeeu6M2Fxg6unNKo5mbHdEg/d4FceaBILL0qOsIUOPwwg==";
//        String signData = SM2Utils.normalSign(privateKey, content);
//        System.out.println("sign:" + signData);
//        Boolean bool = SM2Utils.normalVerifySign(publicKey, signData, content);
//        System.out.println("verify sign:" + bool);
//    }
}
