суббота, 9 июня 2012 г.

Создание ЭЦП с помощью CryptoPro

В данной статье описывается процесс создания ЭЦП c помощью библиотеки CryptoPro.
Для создания подписи необходим приватный ключ и сертификат. Рассмотрим гипотетическую ситуацию, когда приватный ключ прислан по почте, а не сгенерирован самостоятельно - порочная, но широко распространенная практика.
Предположим, что файл клиентского сертификата называется client.cer, сертификата УЦ - CA.cer, а приватный ключ находится в директории 999996.000

1. Установка CryptoPro CSP и CryptoPro JCP

Добываем каким-либо образом дистрибутивы, самый простой способ - скачать их с сайта CryptoPro, предварительно там зарегистрировавшись. Триальная версия полнофункциональна и работает 30 дней.
При установке CSP пакет cprocsp-rdr-gui скорее всего не установится, поскольку использует древние версии Motif, но он не нужен для работы, так что установка данного пакета не является обязательной.
Также нужно убедиться, что в системе есть libcurl.so (эта библиотека используется для получения CRL)
После установки необходимо проверить, что в файле /etc/opt/cprocsp/config.ini прописан правильный путь к libcurl.so, по умолчанию он ведет на /usr/local/lib/64/libcurl.so что является ошибкой.
После этого устанавливаем CryptoPro JCP (jcp_plus_jtls_1.0.53.jar)
После успешной установки нужно запустить ControlPane.sh из-под рута и поменять путь к хранилищу ключей на /var/opt/cprocsp/keys/{$user.name}

2. Установка приватного ключа и сертификатов

CryptoPro имеет свой собственный формат приватного ключа и свои собственные контейнеры для хранения ключей и сертификатов. Чтобы установить ключ и сертификаты в контейнеры, нужно проделать следующие действия:

  • Скопировать в корень дискеты или флэшки сертификат и приватный ключ. Под приватным ключом понимается директория 999996.000 и ее содержимое, файлы header.key, masks2.key, masks.key, name.key, primary2.key, primary.key
    $ cp -R /path/to/key/999996.000 /media/flashdrive/
    $ cp /path/to/cert/client.cer /media/flashdrive/
    
  • Выполнить команду по копированию ключа с флэшки на диск. Ключ попадет в пользовательское хранилище 'My'. Выполнять команду нужно под пользователем, который будет использовать данный контейнер для подписи. 999996 - название (alias) контейнера. gate@example.com - то, что прописано в поле E сертификата ( можно посмотреть командой keytool --printcert -file /path/to/cert/client.cer )
    $ csptest -keycopy -src '\\.\FLASH\gate@example.com' -dest '\\.\HDIMAGE\999996'
    Проверить, что все скопировалось, можно командой
    $ ls -al /var/opt/cprocsp/keys/<username>
  • Альтернативный путь, если нет дискеты или csptest выдает ошибку
    Error number 0x8009000f (2148073487). Object already exists.
    Руками скопировать приватный ключ в хранилище командой
    $ cp -R /path/to/key/999996.000 /var/opt/cprocsp/keys/<username>/
  • Ассоциировать сертификат с контейнером. Сертификат попадет в пользовательское хранилище 'My'
    $ certmgr -inst -file /path/to/file/client.cer -cont '\\.\HDIMAGE\999996'
  • Установить сертификат УЦ из-под пользователя root командой
    # certmgr -inst -store root -file /path/to/file/CA.cer
Контейнер должен быть готов к использованию, проверить это можно командами:
$ certmgr --list
Certmgr 0.9 prerelease (c) "CryptoPro",  2007-2010.
program for managing certificate(CRL) and stores

=============================================================================
1-------
Issuer         : DC=ru, DC=issuer, CN=EXAMPLE
Subject        : C=RU, S=RUSSIA, L=MOSCOW, O=ORGANIZATION, OU=IT, CN=GATE_DEMO, E=gate@example.com
Serial         : 0x2225000000007DF78065
PrivateKey Link: Yes. Container: HDIMAGE\\999996.000\D7BB
=============================================================================

[ErrorCode: 0x00000000]
Обратите внимание на строку "PrivateKey Link: Yes. Container: HDIMAGE\\999996.000\D7BB". Она показывает наличие связи сертификата и приватного ключа, если выводится "PrivateKey Link: No" это означает, что связь не установлена и использовать такой контейнер для подписи не удастся.
$ certmgr --list -store root
Certmgr 0.9 prerelease (c) "CryptoPro",  2007-2010.
program for managing certificate(CRL) and stores

=============================================================================
1-------
Issuer         : DC=ru, DC=issuer, CN=EXAMPLE
Subject        : DC=ru, DC=issuer, CN=EXAMPLE
Serial         : 0xE44263EF7B42044F9E20FFF14C6F1327
PrivateKey Link: No
=============================================================================

[ErrorCode: 0x00000000]

3. Генерация ЭЦП

Под ЭЦП обычно понимается отсоединенная (detached) подпись в формате pkcs#7. Т.е помимо самой подписи, в сообщение внедряется вся цепочка сертификатов. CryptoPro не предоставляет отдельного пакета CMS для легкой генерации криптографических сообщений, но в принципе в пакете JCP есть все необходимое, чтобы сформировать корректное сообщение самостоятельно. Код по большей части взят из примеров, которые идут в JCP.
    private static byte[] signWithCryptoProJcp(byte[] data) throws Exception{

        String alias = "999996";
        String caFile = "/path/to/CA.cer";
        String certFile = "/path/to/client.cer";

        //load keys for sign
        final PrivateKey[] keys = new PrivateKey[1];
        keys[0] = CMStools.loadKey(alias, null);

        //load certificates chain
        final Certificate[] certs = new Certificate[2];
// функция CMStools.loadCertificate() почему-то не работает, хотя сертификат есть в хранилище
// пришлось читать сертификат из файла
        certs[0] = CMStools.readCertificate(certFile);
        certs[1] = CMStools.readCertificate(caFile);
        return createCMS(data, keys, certs, true);
    }


    private static byte[] createCMS(byte[] data, PrivateKey[] keys,
                                   Certificate[] certs,
                                   boolean detached)
            throws Exception {
        //create CMS
//        Array.writeFile("/home/grigory/test.msg", data);
        final ContentInfo all = new ContentInfo();
        all.contentType = new Asn1ObjectIdentifier(new OID(CMStools.STR_CMS_OID_SIGNED).value);
        final SignedData cms = new SignedData();
        all.content = cms;
        cms.version = new CMSVersion(1);
        // digest
        cms.digestAlgorithms = new DigestAlgorithmIdentifiers(1);
        final DigestAlgorithmIdentifier a = new DigestAlgorithmIdentifier(
                new OID(CMStools.DIGEST_OID).value);
        a.parameters = new Asn1Null();
        cms.digestAlgorithms.elements[0] = a;
        if (detached)
            cms.encapContentInfo = new EncapsulatedContentInfo(
                    new Asn1ObjectIdentifier(
                            new OID(CMStools.STR_CMS_OID_DATA).value),
                    null);
        else
            cms.encapContentInfo =
                    new EncapsulatedContentInfo(new Asn1ObjectIdentifier(
                            new OID(CMStools.STR_CMS_OID_DATA).value),
                            new Asn1OctetString(data));
        // certificates
        final int ncerts = certs.length;
        cms.certificates = new CertificateSet(ncerts);
        cms.certificates.elements = new CertificateChoices[ncerts];
        for (int i = 0; i < cms.certificates.elements.length; i++) {
            final ru.CryptoPro.JCP.ASN.PKIX1Explicit88.Certificate certificate =
                    new ru.CryptoPro.JCP.ASN.PKIX1Explicit88.Certificate();
            final Asn1BerDecodeBuffer decodeBuffer =
                    new Asn1BerDecodeBuffer(certs[i].getEncoded());
            certificate.decode(decodeBuffer);
            cms.certificates.elements[i] = new CertificateChoices();
            cms.certificates.elements[i].set_certificate(certificate);
        }
        // Signature.getInstance
        final Signature signature =
                Signature.getInstance(JCP.GOST_EL_SIGN_NAME);
        byte[] sign;
        // signer infos
        final int nsign = keys.length;
        cms.signerInfos = new SignerInfos(nsign);
        for (int i = 0; i < cms.signerInfos.elements.length; i++) {
            signature.initSign(keys[i]);
            signature.update(data);
            sign = signature.sign();
            cms.signerInfos.elements[i] = 
                     new ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.SignerInfo();
            cms.signerInfos.elements[i].version = new CMSVersion(1);
            cms.signerInfos.elements[i].sid = new SignerIdentifier();

            final byte[] encodedName =
                    ((X509Certificate) certs[i]).getIssuerX500Principal().getEncoded();
            final Asn1BerDecodeBuffer nameBuf = new Asn1BerDecodeBuffer(encodedName);
            final Name name = new Name();
            name.decode(nameBuf);

            final CertificateSerialNumber num = new CertificateSerialNumber(
                    ((X509Certificate) certs[i]).getSerialNumber());
            cms.signerInfos.elements[i].sid.set_issuerAndSerialNumber(
                    new IssuerAndSerialNumber(name, num));
            cms.signerInfos.elements[i].digestAlgorithm =
                    new DigestAlgorithmIdentifier(new OID(CMStools.DIGEST_OID).value);
            cms.signerInfos.elements[i].digestAlgorithm.parameters = new Asn1Null();
            cms.signerInfos.elements[i].signatureAlgorithm =
                    new SignatureAlgorithmIdentifier(new OID(CMStools.SIGN_OID).value);
            cms.signerInfos.elements[i].signatureAlgorithm.parameters = new Asn1Null();
            cms.signerInfos.elements[i].signature = new SignatureValue(sign);
        }
        // encode
        final Asn1BerEncodeBuffer asnBuf = new Asn1BerEncodeBuffer();
        all.encode(asnBuf, true);
//        Array.writeFile("/home/grigory/test.signature", asnBuf.getMsgCopy());
        return asnBuf.getMsgCopy();
    }
 
У CryptoPro есть замечательный сервис проверки ЭЦП, где можно проверить, что сгенерированная подпись верна.

Комментариев нет:

Отправить комментарий