package kz.gamma;

import kz.gamma.asn1.ASN1InputStream;
import kz.gamma.asn1.ASN1Sequence;
import kz.gamma.asn1.DERObjectIdentifier;
import kz.gamma.asn1.cryptopro.KZObjectIndentifiers;
import kz.gamma.asn1.x509.SubjectPublicKeyInfo;
import kz.gamma.core.UtilCM;

import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.security.MessageDigest;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class Main {

    public static void main(String[] args) throws Exception {
        test1();
    }

    private static void test1() throws Exception {
        /*1. Серийный номер регистрационного свидетельства: 41ba7e602f6c4c7729828d4091e92020b6415a10
          2. Идентификатор алгоритма ЭЦП: Алгоритм подписи ГОСТ 34.310.
          3. Имя издателя регистрационного свидетельства: (значение параметра CN) bank.kz Root Certificate Authority
          4. Алгоритм криптографического преобразования издателя регистрационного свидетельства: Алгоритм хеширования ГОСТ 34.311.
          5. Срок действия регистрационного свидетельства: Действительно с ‎16 ‎октября ‎2020 ‎г. 15:57:50 по ‎16 ‎октября ‎2021 ‎г. 16:02:50
          6. Имя Владельца регистрационного свидетельства: (значение параметра CN) Аркинязова Жанархан
          7. Закрытый ключ владельца регистрационного свидетельства: (длина ключа, скорее всего значение будет вшитое): 256 бит
          8. Открытый ключ владельца регистрационного свидетельства (длина ключа): 512 бит
          9. значение открытого ключа: 04 40 c0 e6 49 9e a8 41 c1 01 a7 c8 2a cd 54 f1 30 a2 3c 9a 01 57 14 26 f4 59 c7 fe 6d 07 0b 04 55 8b 74 28 a6 1e 93 4e 22 05 a5 93 ca c7 a5 1e d4 71 2a ee e0 05 3a 86 ad 0e 9c 76 9d 0b 98 17 49 ae
          10. Назначение ключа: Цифровая подпись, Неотрекаемость (c0)
          11. Применение ключа: Защищенная электронная почта (1.3.6.1.5.5.7.3.4)
          12. значение отпечатка сертификата: 3460d105b64625f45ec73e23d194fa9e1272d179*/

        HashMap<String, String> oidNameMap = getOidNameMap();
        X509Certificate cert = getCert("certSign.cer");

        SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy г. HH:mm:ss");

        //get public key:
        SubjectPublicKeyInfo info = new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(cert.getPublicKey().getEncoded()).readObject());
        byte[] publicKeyData = info.getPublicKeyData().getBytes();
        int publicKeyLength = publicKeyData[1];
        byte[] publicKey = new byte[publicKeyLength];
        System.arraycopy(publicKeyData, 2, publicKey, 0, publicKey.length);

        System.out.println("1. Серийный номер регистрационного свидетельства: " + UtilCM.array2hex(cert.getSerialNumber().toByteArray()));
        System.out.println("2. Идентификатор алгоритма ЭЦП: " + oidNameMap.get(cert.getSigAlgName()));
        System.out.println("3. Имя издателя регистрационного свидетельства: " + getCnFromDn(cert.getIssuerDN().getName()));
        System.out.println("4. Алгоритм криптографического преобразования издателя регистрационного свидетельства: " + oidNameMap.get(cert.getSigAlgOID()));
        System.out.println("5. Срок действия регистрационного свидетельства: " + dateFormat.format(cert.getNotBefore()) + " по " + dateFormat.format(cert.getNotAfter()));
        System.out.println("6. Имя Владельца регистрационного свидетельства: " + getCnFromDn(cert.getSubjectDN().getName()));
        System.out.println("7. Закрытый ключ владельца регистрационного свидетельства: " + publicKey.length / 2 * 8 + " бит");
        System.out.println("8. Открытый ключ владельца регистрационного свидетельства (длина ключа): " + publicKey.length * 8 + " бит");
        System.out.println("9. значение открытого ключа: " + UtilCM.array2hex(publicKey));
        System.out.println("10. Назначение ключа: " + getKeyUsageInfo(cert.getKeyUsage()));
        System.out.println("11. Применение ключа: " + cert.getExtendedKeyUsage());
        System.out.println("12. значение отпечатка сертификата: " + UtilCM.array2hex(MessageDigest.getInstance("SHA-1").digest(cert.getEncoded())));
        System.out.println("13. ЭЦП издателя: " + UtilCM.array2hex(cert.getSignature()));
    }

    private static HashMap<String, String> getOidNameMap() throws IllegalAccessException {
        HashMap<String, String> res = new HashMap<>();
        List<Field> fields = new ArrayList<>();
        Field[] allFields = KZObjectIndentifiers.class.getDeclaredFields();
        for (Field field : allFields) {
            fields.add(field);
            String name = field.getName();
            DERObjectIdentifier identifier = (DERObjectIdentifier) field.get(null);
            res.put(identifier.getId(), name);
        }
        return res;
    }

    private static X509Certificate getCert(String path) throws Exception {
        FileInputStream fis = new FileInputStream(path);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate) cf.generateCertificate(fis);
        return cert;
    }


    private static String getKeyUsageInfo(boolean[] keyUsage) {
//from java.security.cert.X509Certificate:
//     *     digitalSignature        (0),
//     *     nonRepudiation          (1),
//     *     keyEncipherment         (2),
//     *     dataEncipherment        (3),
//     *     keyAgreement            (4),
//     *     keyCertSign             (5),
//     *     cRLSign                 (6),
//     *     encipherOnly            (7),
//     *     decipherOnly            (8)
        StringBuilder sb = new StringBuilder();
        if (keyUsage[0])
            sb.append("digitalSignature, ");
        if (keyUsage[1])
            sb.append("nonRepudiation, ");
        if (keyUsage[2])
            sb.append("keyEncipherment, ");
        if (keyUsage[3])
            sb.append("dataEncipherment, ");
        if (keyUsage[4])
            sb.append("keyAgreement, ");
        if (keyUsage[5])
            sb.append("keyCertSign, ");
        if (keyUsage[6])
            sb.append("cRLSign, ");
        if (keyUsage[7])
            sb.append("encipherOnly, ");
        if (keyUsage[8])
            sb.append("decipherOnly, ");

        //delete 2 last chars:
        if (sb.length() > 0) {
            sb.delete(sb.length() - 2, sb.length());
        }
        return sb.append(" (").append(UtilCM.array2hex(toBytes(keyUsage))).append(")").toString();
    }

    private static byte[] toBytes(boolean[] input) {
        byte[] toReturn = new byte[input.length / 8];
        for (int entry = 0; entry < toReturn.length; entry++) {
            for (int bit = 0; bit < 8; bit++) {
                if (input[entry * 8 + bit]) {
                    toReturn[entry] |= (128 >> bit);
                }
            }
        }
        return toReturn;
    }

    private static String getCnFromDn(String dn) throws InvalidNameException {
        LdapName ln = new LdapName(dn);

        for (Rdn rdn : ln.getRdns()) {
            if (rdn.getType().equalsIgnoreCase("CN")) {
                return (String) rdn.getValue();
            }
        }
        return null;
    }
}