package kz.gamma;

import kz.gamma.cms.Pkcs7Data;
import kz.gamma.core.UtilCM;
import kz.gamma.jce.provider.GammaTechProvider;
import kz.gamma.util.encoders.Base64;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import java.io.ByteArrayInputStream;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;

/**
 * Created by a_mukusheva on 12.02.2016.
 */
public class CMSVerification {
    public Pkcs7Data pkcs7Data;

    /**
     * Получение сертификата пользователя из УЦ;
     *
     * @param DN  DN
     * @param sn  Серийный номер
     * @param url URL, на который отправляется запрос
     * @return Сертификат, null - если сертификат с данными DN и серийным номером не был найден в хранилище
     */
    public X509Certificate getCertificate(String DN, String sn, String url) {
        X509Certificate ret = null;
        boolean isTrue = false;
        try {
            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            env.put(Context.SECURITY_AUTHENTICATION, "SIMPLE");
            env.put(Context.PROVIDER_URL, url);
            env.put(Context.SECURITY_PRINCIPAL, "");
            env.put(Context.SECURITY_CREDENTIALS, "");
            env.put("java.naming.ldap.attributes.binary", "userCertificate");

            LdapContext ctx = new InitialLdapContext(env, null);
            SearchControls ctls = new SearchControls();
            ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            NamingEnumeration answer = ctx.search(DN, "(&(userCertificate=*))", ctls);
            if (answer.hasMoreElements()) {
                while (answer.hasMore()) {
                    if (isTrue) {
                        break;
                    }
                    SearchResult sr = (SearchResult) answer.next();
                    Attributes pp = sr.getAttributes();
                    for (Enumeration e = pp.getAll(); e.hasMoreElements(); ) {
                        if (isTrue) {
                            break;
                        }
                        Attribute attr = (Attribute) e.nextElement();
                        String attrID = attr.getID();
                        if (attrID.equals("userCertificate")) {
                            for (int i = 0; i < attr.size(); i++) {
                                Object csd = attr.get(i);
                                byte[] csd_b = (byte[]) csd;
                                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                                Collection c = cf.generateCertificates(new ByteArrayInputStream(csd_b));
                                Iterator iterator = c.iterator();
                                ret = (X509Certificate) iterator.next();
                                if (UtilCM.array2hex(ret.getSerialNumber().toByteArray()).equals(sn)) {
                                    isTrue = true;
                                    break;
                                }
                            }
                        }
                    }
                }
            } else {
                throw new RuntimeException("Сертификат с данным DN не был найден в хранилище");
            }
            ctx.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ret;
    }


    /**
     * Проверка cms
     *
     * @param cms        - cms для проверки
     * @param dataSigned - данные, которые были подписаны в cms
     * @param url        - url к ldap хранилищу
     * @param DN         - dn сертификата пользователя, которым подписывалась cms
     * @return - true/false
     */
    public boolean verifyPkcs7(byte[] cms, String dataSigned, String url, String DN) {
        Pkcs7Data pkcs7Data = new Pkcs7Data(cms, dataSigned);
        //получаем серийный номер сертификата пользователя из cms
        String serialNumber = UtilCM.array2hex(pkcs7Data.getSignerInformation().getSID().getSerialNumber().toByteArray());
        //получаем сертификат из хранилища УЦ
        X509Certificate certificate = getCertificate(DN, serialNumber, url);
        //проверяем cms
        //при проверке получаем хэш из подписи, формируем хэш на данные которые подписывали,
        //сравниваем полученные значения, проверяем подпись
        return pkcs7Data.verify(certificate);
    }


    public static void main(String[] args) {
        if (args.length != 4) {
            System.out.println("Usage:\n" +
                    "CMS в Base64 кодировке\t" + "Данные для подписи\t" + "DN\t" + "URL LDAP хранилища УЦ\n");
            return;
        }

        byte[] cms = Base64.decode(args[0]);
        String dataToSign = args[1];
        String DN = args[2];//например "cn=usernika,o=gamma,c=KZ"
        String url = args[3];//например "ldap://192.168.12.153:62222"

        Security.addProvider(new GammaTechProvider());
        CMSVerification cmsVerification = new CMSVerification();
        System.out.println("CMS verification result: " + cmsVerification.verifyPkcs7(cms, dataToSign, url, DN));
    }
}
