В данной статье описывается процесс создания ЭЦП 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 есть замечательный
сервис проверки ЭЦП, где можно проверить, что сгенерированная подпись верна.