Побег из Крипто Про. ГОСТ 34.10-2012 edition

в 12:37, , рубрики: bouncycastle, cryptopro, java

На Хабре есть великолепная статья "Побег из Крипто Про. Режиссерская версия, СМЭВ-edition", но наступил 2019 год и все УЦ стали выдавать ЭЦП по ГОСТ 34.10-2012 вместо ГОСТ 34.10-2001.

Под катом рассказ как можно модифицировать свой софт на Bouncy Castle для поддержки работы с ключами по новым гостам.

image

Disclaimer

О юридических тонкостях подписывания документов через Bouncy Castle и прочих СКЗИ я ничего не знаю и общаться не готов. Перед использованием кода в продакшене проконсультируйтесь с юристом.

А зачем это вообще надо? хорошо написанно оригинальной статье. Повторяться не буду.

Получение ключа с Токена

image

Все известные мне УЦ выдают ключи с сертификатами на подобных токенах. На токен записан контейнер КриптоПро с приватным ключом и сертификатом. При экспорте ключа через CryptoPro CSP он экспортируется в особый «КриптоПро pfx» не совместимый ни с чем.

Просьбы выдать ключ в стандартном pfx или любом другом типовом контейнере УЦ игнорируют.
Если кто знает УЦ выдающие подписи в стандартных контейнерах поделитесь координатами в комментариях. Хороших людей не стыдно попиарить.

Для преобразования контейнера КриптоПро в стандартный pfx мы как и в оригинальной статье будем использовать P12FromGostCSP. Старые взломанные версии не работают с ключами по 2012 Госту. Идем на сайт авторов и покупаем новую.

Итак мы получили стандартный pfx с ключом и сертификатом.

Обновление Bouncy Castle

Обновляем Bouncy Castle до 1.60 Старые версии могут не поддерживать алгоритмы ГОСТ 2012.

		
<dependency>
	<groupId>org.bouncycastle</groupId>
	<artifactId>bcprov-jdk15on</artifactId>
	<version>1.60</version>
</dependency>
<dependency>
	<groupId>org.bouncycastle</groupId>
	<artifactId>bcpkix-jdk15on</artifactId>
	<version>1.60</version>
</dependency>

Инициализация Bouncy Castle

    static {
        BouncyCastleProvider bcProvider = new BouncyCastleProvider();
        String name = bcProvider.getName();
        Security.removeProvider(name); // remove old instance
        Security.addProvider(bcProvider);
    }

Разбор pfx

               
KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
ByteArrayInputStream baos = new ByteArrayInputStream(pfxFileContent);
keyStore.load(baos, password.toCharArray());

Enumeration<String> aliases = keyStore.aliases();

while (aliases.hasMoreElements()) {
    String alias = aliases.nextElement();
    if (keyStore.isKeyEntry(alias )) {
        Key key = keyStore.getKey(alias , keyPassword.toCharArray());
        java.security.cert.Certificate certificate = keyStore.getCertificate(alias );
        addKeyAndCertificateToStore((PrivateKey)key, (X509Certificate)certificate);
    }
}

Алиасы обязательно изменяем. Утилита P12FromGostCSP задает всегда один и тот же алиас «csp_exported» и при обработке уже второго ключа будут проблемы.

Для удобства работы ключ из pfx необходимо загрузить в стандартный Java KeyStore и дальше работать только с ним.

Загрузка KeyStore

FileInputStream is = new FileInputStream(keystorePath);
keystore = KeyStore.getInstance(KeyStore.getDefaultType());
char[] passwd = keystorePassword.toCharArray();
keystore.load(is, passwd);

Сохранение ключа с сертификатом в KeyStore


public void addKeyAndCertificateToStore(PrivateKey key, X509Certificate certificate) {
    synchronized (this) {
        keystore.setKeyEntry(alias.toLowerCase(), key, keyPassword.toCharArray(), new X509Certificate[] {certificate});

        FileOutputStream out = new FileOutputStream(keystorePath);
        keystore.store(out, keystorePassword.toCharArray());
        out.close();
   }
}

Загрузка ключей и сертификатов из KeyStore


Enumeration<String> aliases = keystore.aliases();

while (aliases.hasMoreElements()) {
    String alias = aliases.nextElement();

    if (keystore.isKeyEntry(alias)) {
        Key key = keystore.getKey(alias, keyPassword.toCharArray());
        keys.put(alias.toLowerCase(), key); //any key,value collection

        Certificate certificate = keystore.getCertificate(alias);
        if (certificate instanceof X509Certificate)
            certificates.put(alias.toLowerCase(), (X509Certificate) certificate); //any key,value collection
    }
}

Подпись файла


CMSProcessableByteArray msg = new CMSProcessableByteArray(dataToSign);
 
List certList = new ArrayList();
certList.add(cert);

Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner signer = new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder("GOST3411WITHECGOST3410-2012-256").setProvider("BC").build(privateKey);

gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(signer, certificate));
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(msg, false);
byte[] sign = sigData.getEncoded(); //result here

Есть вариант JcaContentSignerBuilder(«GOST3411WITHECGOST3410-2012-512») для 512 битовых ключей. Мои УЦ выдают 256 битные ничего не спрашивая и игнорируют уточняющие вопросы.

Проверка подписи


byte[] data = ...; //signed file data
byte[] signature = ...;//signature
boolean checkResult = false;

CMSProcessable signedContent = new CMSProcessableByteArray(data);
CMSSignedData signedData;
try {
    signedData = new CMSSignedData(signedContent, signature);
} catch (CMSException e) {
    return SIGNATURE_STATUS.ERROR;
}

SignerInformation signer;
try {
    Store<X509CertificateHolder> certStoreInSing = signedData.getCertificates();
    signer = signedData.getSignerInfos().getSigners().iterator().next();

    Collection certCollection = certStoreInSing.getMatches(signer.getSID());
    Iterator certIt = certCollection.iterator();

    X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
    X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(certHolder);

    checkResult = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(certificate));

} catch (Exception ex) {
    return SIGNATURE_STATUS.ERROR;
}

Проверка подписи полностью аналогична проверке по Госту 2001 года. Можно ничего не менять.

Резюме

В результате всех вышеописанных действий мы получили относительно легий способ избавиться от тяжкой ноши Крипто Про в 2019 году.

Автор: BugM

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js