package kz.gamma;

import kz.gamma.asn1.*;
import kz.gamma.asn1.cms.Attribute;
import kz.gamma.asn1.cms.EnvelopedData;
import kz.gamma.asn1.x509.X509Name;
import kz.gamma.cms.CMSException;
import kz.gamma.cms.CMSGammaEnvelopedData;
import kz.gamma.jce.provider.GammaTechProvider;
import kz.gamma.jce.provider.JCEECPrivateKey;
import kz.gamma.tumarcsp.params.StoreObjectParam;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Enumeration;

/**
 * Пример демонстрирующий работу с кртиптографическими функциями:
 * Формирование и чтение шифрованного сообщения.
 */
public class SampleJCEEnvelopedCMS {

    /**
     * @param args
     */
    public static void main(String[] args) {
        try {
            // Данный метод добавляет JCE в окружение java.security.
            Security.addProvider(new GammaTechProvider());
            // Чтение сообщения которое необходимо зашифровать
            FileInputStream f = new FileInputStream("C:\\temp\\test2.bin");
            byte[] buf = new byte[f.available()];
            f.read(buf, 0, f.available());
            f.close();
            // Зашифровывание сообщения
            byte[] encBuf = encryptText(buf);
            // Сохранение сообщения в файл
            FileOutputStream f0 = new FileOutputStream("c:\\temp\\encryptBlob.bin");
            f0.write(encBuf);
            f0.close();
            // Расшифровывание сообщения
            byte[] origBuf = dencryptText(encBuf);
            // Сохраниения текста в файл
            f0 = new FileOutputStream("c:\\temp\\test2_enc.bin");
            f0.write(origBuf);
            f0.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

    /**
     * Функция шифрования сообщения
     *
     * @param text
     * @return
     * @throws NoSuchProviderException
     * @throws KeyStoreException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws UnrecoverableKeyException
     */
    public static byte[] encryptText(byte[] text) throws NoSuchProviderException, KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {
        // Формируем класс формирования шифрованного сообщения
        CMSGammaEnvelopedData data = new CMSGammaEnvelopedData();
        // Загружаем хранилище ключей и сертификатов (собственного закрытого ключа)
        // Можно использовать любые типы хранилища ключей.
        KeyStore store = KeyStore.getInstance("PKS", "GAMMA");
        store.load(null, null);
        // Получение закрытого ключа отправителя
        String dn = new String("CN=TestCrypt, O=Гамма, C=KZ".getBytes("CP1251"), "UTF8");
        JCEECPrivateKey prvKey = (JCEECPrivateKey) getPrivateKey(dn, store, "");
        // Получение сертификата отправителя
        X509Certificate certS = (X509Certificate) getCertificate(dn, store, "");
        //Загружаем сертификат пользователя которому будем шифровать из файла
        X509Certificate certR = loadCertFromFile("C:\\temp\\Den.cer");
        // Шифрование сообщения
        data.cryptText(text, prvKey);
        // Добавление получателей сообщения
        data.addKeyRecipient(certR);
        // Добавление аттрибутов сообщения
        data.addUnprotectedAttrs(new Attribute((new DERObjectIdentifier("1.2.840.113549.1.9.3")), new DERSet(new DERObjectIdentifier("1.2.840.113549.1.7.3"))));
        data.addUnprotectedAttrs(new Attribute((new DERObjectIdentifier("1.2.840.113549.1.9.5")), new DERSet(new DERGeneralizedTime(new Date()))));
        data.addUnprotectedAttrs(new Attribute((new DERObjectIdentifier("1.2.840.113549.1.9.13")), new DERSet(new DERPrintableString("DESCRIPTOR"))));
        // Формирование зашифрованного сообщения
        EnvelopedData env = data.generateEnvelopedData(certS);
        return env.getEncoded();
    }

    /**
     * Функция расшифровывания сообщения
     *
     * @param cms
     * @return
     * @throws IOException
     * @throws CMSException
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws KeyStoreException
     */
    public static byte[] dencryptText(byte[] cms) throws IOException, CMSException, NoSuchProviderException, NoSuchAlgorithmException, CertificateException, KeyStoreException {
        // Формируем класс чтения Enveloped cms
        CMSGammaEnvelopedData cmsEn = new CMSGammaEnvelopedData(cms);
        // Загружаем хранилище ключей и сертификатов (собственного закрытого ключа)
        // Можно использовать любые типы хранилища ключей.
        KeyStore store = KeyStore.getInstance("PKS", "GAMMA");
        store.load(null, null);
        //Получение сертификата отправителя
        X509Certificate certSend = cmsEn.getSenderCert();
        //Получение аттрибутов сообщения
        ASN1EncodableVector unattr = cmsEn.getUnAttr();
        // Расшифровать сообщение
        return cmsEn.dencryptText(store, "");
    }

    /**
     * Метод загружает сертификат из файла
     *
     * @param fileName
     * @return
     */
    public static X509Certificate loadCertFromFile(String fileName) {
        X509Certificate cert = null;
        byte[] buf = null;
        try {
            FileInputStream f = new FileInputStream(fileName);
            buf = new byte[f.available()];
            f.read(buf, 0, f.available());
            // Указываем классу CertificateFactory что необходимо использовать JCE GAMMA.
            CertificateFactory cf = CertificateFactory.getInstance("X.509", "GAMMA");
            cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(buf));
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        return cert;
    }

    /**
     * Функция создает экземпляр класса приватного ключа для подписи.
     * Если будет несколько сертификатов с одним именем то загрузить самый новый
     *
     * @param DName
     * @param store
     * @param pass
     * @return
     * @throws KeyStoreException
     * @throws NoSuchAlgorithmException
     * @throws UnrecoverableKeyException
     */
    public static PrivateKey getPrivateKey(String DName, KeyStore store, String pass) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
        Enumeration en = store.aliases();
        Date tmpDate = null;
        String tmpSN = "";
        while (en.hasMoreElements()) {
            StoreObjectParam prm = (StoreObjectParam) en.nextElement();
            if (prm.dn.length() > 2) {
                if ((new X509Name(DName)).equals(new X509Name(prm.dn))) {
                    if (tmpDate == null) {
                        tmpDate = prm.timeCreate;
                        tmpSN = prm.sn;
                    } else {
                        if (prm.timeCreate.after(tmpDate)) {
                            tmpDate = prm.timeCreate;
                            tmpSN = prm.sn;
                        }
                    }
                }
            }
        }
        return (PrivateKey) store.getKey(tmpSN, pass.toCharArray());
    }

    /**
     * Функция создает экземпляр класса сертификата.
     * Если будет несколько сертификатов с одним именем то загрузить самый новый
     *
     * @param DName
     * @param store
     * @param pass
     * @return
     * @throws KeyStoreException
     * @throws NoSuchAlgorithmException
     * @throws UnrecoverableKeyException
     */
    public static java.security.cert.Certificate getCertificate(String DName, KeyStore store, String pass) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
        Enumeration en = store.aliases();
        Date tmpDate = null;
        String tmpSN = "";
        while (en.hasMoreElements()) {
            StoreObjectParam prm = (StoreObjectParam) en.nextElement();
            if (prm.dn.length() > 2) {
                if ((new X509Name(DName)).equals(new X509Name(prm.dn))) {
                    if (tmpDate == null) {
                        tmpDate = prm.timeCreate;
                        tmpSN = prm.sn;
                    } else {
                        if (prm.timeCreate.after(tmpDate)) {
                            tmpDate = prm.timeCreate;
                            tmpSN = prm.sn;
                        }
                    }
                }
            }
        }
        return store.getCertificate(tmpSN);
    }
}