import kz.gamma.RegisterRequest;
import kz.gamma.asn1.*;
import kz.gamma.asn1.ocsp.*;
import kz.gamma.asn1.x509.AlgorithmIdentifier;
import kz.gamma.asn1.x509.GeneralName;
import kz.gamma.asn1.x509.X509Extension;
import kz.gamma.asn1.x509.X509Extensions;
import kz.gamma.jce.provider.GammaTechProvider;
import kz.gamma.x509.extension.AuthorityKeyIdentifierStructure;

import javax.naming.Context;
import javax.naming.ldap.ExtendedResponse;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import java.io.*;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Hashtable;
import java.util.Vector;

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

    /**
     * @param args
     */
    public static void main(String[] args) {
        try {
            // Данный метод добавляет JCE в окружение java.security.
            Security.addProvider(new GammaTechProvider());
            // Загружаем сертификат из файла
            X509Certificate Cert = loadCertFromFile("C:\\Temp\\1111.cer");
            // Формируем OCSP запрос
            OCSPRequest req = generateRequest(Cert, "TestRequestor");
            // Отправляем запрос на сервер и получаем ответ
           // byte[] resp = sendRequest(req.getDEREncoded(), "ldap://192.168.7.152:62222");
            byte[] resp = sendRequest("http://172.16.172.75:62301", req.getDEREncoded(), "application/pkcs10", "POST");
            // Получаем статус OCSP ответа
            int status = getOCSPStatus(resp);
            System.out.println("Статус сертификата - " + status);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Метод загружает сертификат из файла
     */
    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 e) {
            e.printStackTrace();
        }
        return cert;
    }

    /**
     * Метод формирующий OCSP запрос
     *
     * @param cert
     * @param requestor
     * @return
     * @throws NoSuchAlgorithmException
     * @throws CertificateEncodingException
     * @throws IOException
     * @throws CertificateParsingException
     */
    public static OCSPRequest generateRequest(X509Certificate cert, String requestor)
            throws NoSuchAlgorithmException, CertificateEncodingException, IOException, CertificateParsingException {
        CertID certId;
        certId = buildCertId(cert);
        X509Extensions altNameExtensions = null;
        Request req = new Request(certId, altNameExtensions);
        ASN1Sequence seq = new DERSequence(new ASN1Encodable[]{req});
        GeneralName requestorName = new GeneralName(GeneralName.uniformResourceIdentifier, new DERIA5String(requestor.getBytes("ASCII")));
        X509Extensions nonceExtensions = createNonceExtensions();
        TBSRequest tbs = new TBSRequest(requestorName, seq, nonceExtensions);
        OCSPRequest res = new OCSPRequest(tbs, null);
        return res;
    }

    /**
     * Метод формирующий расширение для формирования OCSP
     *
     * @return
     */
    public static X509Extensions createNonceExtensions() {
        BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis());
        Vector<DERObjectIdentifier> oids = new Vector<DERObjectIdentifier>();
        Vector<X509Extension> values = new Vector<X509Extension>();
        oids.add(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
        values.add(new X509Extension(false, new DEROctetString(nonce.toByteArray())));
        X509Extensions res = new X509Extensions(oids, values);
        return res;
    }

    /**
     * Метод формирующий CertID из сертификата
     *
     * @param cert
     * @return
     * @throws NoSuchAlgorithmException
     * @throws CertificateEncodingException
     * @throws IOException
     */
    public static CertID buildCertId(X509Certificate cert)
            throws NoSuchAlgorithmException, CertificateEncodingException, IOException {
        AlgorithmIdentifier algId = new AlgorithmIdentifier(new DERObjectIdentifier("1.3.6.1.4.1.6801.1.1.1"), DERNull.INSTANCE);
        String issuerName = cert.getIssuerX500Principal().getName();
        byte[] issuerNameData = issuerName.getBytes("ASCII");
        ASN1OctetString issuerNameHash = new DEROctetString(issuerNameData);
        byte[] issuerKeyData = getAuthorityKeyId(cert);
        ASN1OctetString issuerKeyHash = new DEROctetString(issuerKeyData);
        DERInteger serialNumber = new DERInteger(cert.getSerialNumber());
        CertID res = new CertID(algId, issuerNameHash, issuerKeyHash, serialNumber);
        return res;
    }

    /**
     * Метод формирующий идентификатор ЦС
     *
     * @param cert
     * @return
     * @throws IOException
     */
    public static byte[] getAuthorityKeyId(X509Certificate cert)
            throws IOException {
        byte[] extValue = cert.getExtensionValue("2.5.29.35");
        AuthorityKeyIdentifierStructure keyId = new AuthorityKeyIdentifierStructure(extValue);
        byte[] res = keyId.getKeyIdentifier();
        return res;
    }

    /**
     * @param serviceURL    service URL. For example, http://192.168.10.30:62295
     * @param request       ASN.1 request
     * @param contentType   Content type. For example, "application/pkixcmp", "application/ocsp-request" and so forth
     * @param requestMethod Request method. For example, "POST", "GET" and so forth
     * @return Service response
     */
    public static byte[] sendRequest(String serviceURL, byte[] request, String contentType, String requestMethod) {
        DataOutputStream dataOutputStream = null;
        DataInputStream dataInputStream = null;
        try {
            HttpURLConnection conn = (HttpURLConnection) new URL(serviceURL).openConnection();
            if (requestMethod != null) {
                conn.setRequestMethod(requestMethod);
            }
            conn.setRequestProperty("Content-Type", contentType);
            conn.setDoOutput(true);
            dataOutputStream = new DataOutputStream(conn.getOutputStream());
            dataOutputStream.write(request);
            dataOutputStream.flush();
            int responseSize = conn.getContentLength();
            dataInputStream = new DataInputStream(conn.getInputStream());
            byte[] response = new byte[responseSize];
            int totalRead = 0;
            while (totalRead < responseSize) {
                int bytesRead = dataInputStream.read(response, totalRead, responseSize - totalRead);
                if (bytesRead < 0) {
                    break;
                }
                totalRead += bytesRead;
            }
            return response;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Cannot send request");
        } finally {
            if (dataOutputStream != null) {
                try {
                    dataOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (dataInputStream != null) {
                try {
                    dataInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /**
     * Метод отсылающий OCSP запрос на сервер
     *
     * @param req
     * @param url
     * @return
     */
    public static byte[] sendRequest(byte[] req, String url) {
        byte[] resp = null;
        LdapContext ctx = null;
        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, "");
            ctx = new InitialLdapContext(env, null);

            RegisterRequest request1 = new RegisterRequest(req, "1.3.6.1.4.1.6801.11.1.1");
            ExtendedResponse response = ctx.extendedOperation(request1);
            resp = response.getEncodedValue();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ctx != null) {
                try {
                    ctx.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return resp;
    }

    /**
     * Получение статуса проверяемого сертификата
     *
     * @param req
     * @return
     */
    public static int getOCSPStatus(byte[] req) throws IOException {
        int status = -1;
        ASN1InputStream respStream = null;
        ASN1InputStream respStream2 = null;
        try {
            respStream = new ASN1InputStream(req);
            DERObject respObject = respStream.readObject();
            ASN1Sequence respSeq = (ASN1Sequence) respObject;
            OCSPResponse resp = new OCSPResponse(respSeq);
            BigInteger statusBig = resp.getResponseStatus().getValue();
            ResponseBytes rbytes = resp.getResponseBytes();
            ASN1OctetString octetResp = rbytes.getResponse();

            byte[] octetbytes = octetResp.getOctets();
            respStream2 = new ASN1InputStream(octetbytes);
            DERObject responseBytes = respStream2.readObject();
            BasicOCSPResponse ocspResponse = new BasicOCSPResponse((ASN1Sequence) responseBytes);
            ResponseData respData = ocspResponse.getTbsResponseData();
            if(respData.getResponses().size() >= 1) {
                SingleResponse singleResponse = SingleResponse.getInstance(respData.getResponses().getObjectAt(0));
                CertStatus certStatus = singleResponse.getCertStatus();
                status = certStatus.getTagNo();
                /*RevokedInfo revokedInfo = RevokedInfo.getInstance(certStatus.getStatus());
                if (status == 1) {
                    System.out.println("Certificate was revoked on: " + revokedInfo.getRevocationTime().getDate()
                            + "  " + revokedInfo.getRevocationReason());
                }*/
            } else
                System.out.println("No Single Responses found in ocsp response");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(respStream != null) respStream.close();
            if(respStream2!= null) respStream2.close();
        }
        return status;
    }
}