Ключевое слово в защите информации
КЛЮЧЕВОЕ СЛОВО
в защите информации
Получить ГОСТ TLS-сертификат для домена (SSL-сертификат)
Добро пожаловать, Гость! Чтобы использовать все возможности Вход или Регистрация.

Уведомление

Icon
Error

Опции
К последнему сообщению К первому непрочитанному
Offline Kir Sorokin  
#1 Оставлено : 16 ноября 2022 г. 6:26:55(UTC)
Kir Sorokin

Статус: Новичок

Группы: Участники
Зарегистрирован: 16.11.2022(UTC)
Сообщений: 4
Российская Федерация
Откуда: СПб

Коллеги, здравствуйте.

Подскажите, был ли у кого-нибудь опыт использования JCP для подписания JWT токена при взаимодействии с сервисами опубликованными через ИПС ЕГИСЗ?

Пытаюсь сделать правильный JWT по их методрекам — https://portal.egisz.rosminzdrav.ru/materials/3625, но явно где-то сворачиваю не туда. В качестве алгоритма подписи там предлагается выбрать (и указать в генерируемом токене) из RS256, RS384, RS512, ECGOST3410-2012. Самое похожее, что я могу подобрать из алгоритмов в JCP — GOST3411_2012_256withGOST3410_2012_256, но это явным образом не срабатывает.

Токен создаю и подписываю вот так:
Код:

Security.addProvider(new ru.CryptoPro.JCP.JCP());
HDImageStore.setDir("...");

KeyStore keystore = KeyStore.getInstance(JCP.HD_STORE_NAME);
keystore.load(null, null);

String alias = keystore.aliases().nextElement();

X509Certificate certificate = (X509Certificate) keystore.getCertificate(keystore.aliases().nextElement());
PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, "...".toCharArray());

ObjectMapper mapper = new ObjectMapper();

var header = mapper.createObjectNode();
header.putArray("x5c").add(certificate.getEncoded());
header.put("alg", "ECGOST3410-2012");

var payload = mapper.createObjectNode();
payload.put("sub", "cb4592ef-56f3-594c-f2a1-562b61677b98");
payload.put("aud", "https://ips-test.rosminzdrav.ru");
payload.put("iat", Instant.now().getEpochSecond());
payload.put("exp", Instant.now().plus(1, ChronoUnit.MINUTES).getEpochSecond());

String jwt = "%s.%s".formatted(
        Base64.encodeBase64URLSafeString(header.toString().getBytes()),
        Base64.encodeBase64URLSafeString(payload.toString().getBytes()));

Signature signature = Signature.getInstance("GOST3411_2012_256withGOST3410_2012_256");
signature.initSign(privateKey);
signature.update(jwt.getBytes());
byte[] sign = signature.sign();

String authorization = "Bearer %s.%s".formatted(jwt, Base64.encodeBase64URLSafeString(sign));


Навстречу из ИПС получаю ругань про неверную подпись токена.

Вижу также, что ECGOST3410-2012-256 есть в BouncyCastle, но он не готов поработать с ключом, что получается у меня — „cannot recognise key type in ECGOST-2012-256 signer“.
Offline Евгений Афанасьев  
#2 Оставлено : 16 ноября 2022 г. 22:08:38(UTC)
Евгений Афанасьев

Статус: Сотрудник

Группы: Участники
Зарегистрирован: 06.12.2008(UTC)
Сообщений: 3,963
Российская Федерация
Откуда: Крипто-Про

Сказал(а) «Спасибо»: 20 раз
Поблагодарили: 704 раз в 665 постах
Здравствуйте.
Скорее всего, вам нужна подпись формата CMS или CAdES, посмотрите примеры CMS_samples/CMSSign или CAdES в архиве samples-sources.jar.
Offline ivan7776666  
#3 Оставлено : 20 ноября 2022 г. 19:12:46(UTC)
ivan7776666

Статус: Новичок

Группы: Участники
Зарегистрирован: 20.11.2022(UTC)
Сообщений: 3

Автор: Kir Sorokin Перейти к цитате
Коллеги, здравствуйте.

Подскажите, был ли у кого-нибудь опыт использования JCP для подписания JWT токена при взаимодействии с сервисами опубликованными через ИПС ЕГИСЗ?

Пытаюсь сделать правильный JWT по их методрекам — https://portal.egisz.rosminzdrav.ru/materials/3625, но явно где-то сворачиваю не туда. В качестве алгоритма подписи там предлагается выбрать (и указать в генерируемом токене) из RS256, RS384, RS512, ECGOST3410-2012. Самое похожее, что я могу подобрать из алгоритмов в JCP — GOST3411_2012_256withGOST3410_2012_256, но это явным образом не срабатывает.

Токен создаю и подписываю вот так:
Код:

Security.addProvider(new ru.CryptoPro.JCP.JCP());
HDImageStore.setDir("...");

KeyStore keystore = KeyStore.getInstance(JCP.HD_STORE_NAME);
keystore.load(null, null);

String alias = keystore.aliases().nextElement();

X509Certificate certificate = (X509Certificate) keystore.getCertificate(keystore.aliases().nextElement());
PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, "...".toCharArray());

ObjectMapper mapper = new ObjectMapper();

var header = mapper.createObjectNode();
header.putArray("x5c").add(certificate.getEncoded());
header.put("alg", "ECGOST3410-2012");

var payload = mapper.createObjectNode();
payload.put("sub", "cb4592ef-56f3-594c-f2a1-562b61677b98");
payload.put("aud", "https://ips-test.rosminzdrav.ru");
payload.put("iat", Instant.now().getEpochSecond());
payload.put("exp", Instant.now().plus(1, ChronoUnit.MINUTES).getEpochSecond());

String jwt = "%s.%s".formatted(
        Base64.encodeBase64URLSafeString(header.toString().getBytes()),
        Base64.encodeBase64URLSafeString(payload.toString().getBytes()));

Signature signature = Signature.getInstance("GOST3411_2012_256withGOST3410_2012_256");
signature.initSign(privateKey);
signature.update(jwt.getBytes());
byte[] sign = signature.sign();

String authorization = "Bearer %s.%s".formatted(jwt, Base64.encodeBase64URLSafeString(sign));


Навстречу из ИПС получаю ругань про неверную подпись токена.

Вижу также, что ECGOST3410-2012-256 есть в BouncyCastle, но он не готов поработать с ключом, что получается у меня — „cannot recognise key type in ECGOST-2012-256 signer“.


Подскажите, удалось ли в итоге собрать верный для ЕГИСЗ jwt?
Offline Kir Sorokin  
#4 Оставлено : 21 ноября 2022 г. 10:57:28(UTC)
Kir Sorokin

Статус: Новичок

Группы: Участники
Зарегистрирован: 16.11.2022(UTC)
Сообщений: 4
Российская Федерация
Откуда: СПб

Автор: Евгений Афанасьев Перейти к цитате
Здравствуйте.
Скорее всего, вам нужна подпись формата CMS или CAdES, посмотрите примеры CMS_samples/CMSSign или CAdES в архиве samples-sources.jar.


Добрый день, спасибо большое. Попробовали, но безрезультатно. Воспроизвели подпись по примерам, но ЕГИСЗ отвечает — Size of a request header field exceeds server limit. Видимо, совсем не оно.

Мне кажется, стоит пробовать копать куда-то в сторону имеющегося в BC ECGOST3410-2012-256, но вот не получается использовать полученный из хранилища приватный ключ. Если использовать напрямую, то он выдает „cannot recognise key type in ECGOST-2012-256 signer“ так как ключ очевидным образом не является экземпляром org.bouncycastle.jce.interfaces.ECKey.

Подскажите, пожалуйста, есть ли способ как-то привести ключ к нужному виду?
Offline Kir Sorokin  
#5 Оставлено : 21 ноября 2022 г. 10:58:12(UTC)
Kir Sorokin

Статус: Новичок

Группы: Участники
Зарегистрирован: 16.11.2022(UTC)
Сообщений: 4
Российская Федерация
Откуда: СПб

Автор: ivan7776666 Перейти к цитате

Подскажите, удалось ли в итоге собрать верный для ЕГИСЗ jwt?


Увы, пока успеха нет. Если у вас получится — поделитесь, пожалуйста.

Offline ivan7776666  
#6 Оставлено : 21 ноября 2022 г. 14:29:45(UTC)
ivan7776666

Статус: Новичок

Группы: Участники
Зарегистрирован: 20.11.2022(UTC)
Сообщений: 3

Автор: Kir Sorokin Перейти к цитате
Автор: ivan7776666 Перейти к цитате

Подскажите, удалось ли в итоге собрать верный для ЕГИСЗ jwt?


Увы, пока успеха нет. Если у вас получится — поделитесь, пожалуйста.



По комментариям службы поддержки нужно двигаться в сторону pkcs7 cades с хэшированием.
Да, как будет какой-то результат - напишу.

Offline ivan7776666  
#7 Оставлено : 23 ноября 2022 г. 13:36:57(UTC)
ivan7776666

Статус: Новичок

Группы: Участники
Зарегистрирован: 20.11.2022(UTC)
Сообщений: 3

Автор: Kir Sorokin Перейти к цитате
Автор: ivan7776666 Перейти к цитате

Подскажите, удалось ли в итоге собрать верный для ЕГИСЗ jwt?


Увы, пока успеха нет. Если у вас получится — поделитесь, пожалуйста.



Подскажите, получилось ли собрать токен?
Offline Kir Sorokin  
#8 Оставлено : 4 декабря 2022 г. 10:20:53(UTC)
Kir Sorokin

Статус: Новичок

Группы: Участники
Зарегистрирован: 16.11.2022(UTC)
Сообщений: 4
Российская Федерация
Откуда: СПб

В итоге все получилось. Да, под ECGOST3410-2012 товарищи понимают упакованную в CMS подпись (PKCS#7 detached), поэтому базисом для решения задачки является файл samples-sources/CMS_samples/CMS.java из дистрибутива. Прикладываю ниже код, который у меня заработал.

Важное — у ИПС как-то кривенько настроены серверы, поэтому полноценный CMS туда не пролезает по размеру заголовка. По совету их СТП из CMS были убраны данные сертификата (он все равно передается еще и в заголовке JWT), см. закомментированные строки в createCMS.

Код:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.objsys.asn1j.runtime.*;
import kong.unirest.*;
import org.apache.commons.codec.binary.Base64;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.*;
import ru.CryptoPro.JCP.ASN.PKIX1Explicit88.CertificateSerialNumber;
import ru.CryptoPro.JCP.ASN.PKIX1Explicit88.Name;
import ru.CryptoPro.JCP.JCP;
import ru.CryptoPro.JCP.KeyStore.HDImage.HDImageStore;
import ru.CryptoPro.JCP.params.OID;
import ru.CryptoPro.JCP.tools.AlgorithmUtility;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.temporal.ChronoUnit;

public class Application {

    public static byte[] signCMS(
            byte[] data, PrivateKey key, Certificate cert, boolean detached) throws Exception {

        String keyAlg  = key.getAlgorithm();
        String signOid = AlgorithmUtility.keyAlgToSignatureOid(keyAlg);

        // sign
        final Signature signature = Signature.getInstance(signOid, JCP.PROVIDER_NAME);
        signature.initSign(key);
        signature.update(data);

        final byte[] sign = signature.sign();

        // create cms format
        return createCMS(data, sign, cert, detached);
    }

    public static byte[] createCMS(
            byte[] buffer, byte[] sign, Certificate cert, boolean detached) throws Exception {

        String pubKeyAlg = cert.getPublicKey().getAlgorithm();
        String digestOid = AlgorithmUtility.keyAlgToDigestOid(pubKeyAlg);
        String keyOid    = AlgorithmUtility.keyAlgToKeyAlgorithmOid(pubKeyAlg); // алгоритм ключа подписи

        final String STR_CMS_OID_SIGNED = "1.2.840.113549.1.7.2";

        final ContentInfo all = new ContentInfo();
        all.contentType = new Asn1ObjectIdentifier(
                new OID(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(digestOid).value);

        a.parameters = new Asn1Null();
        cms.digestAlgorithms.elements[0] = a;

        final String STR_CMS_OID_DATA = "1.2.840.113549.1.7.1";

        if (detached) {
            cms.encapContentInfo = new EncapsulatedContentInfo(
                    new Asn1ObjectIdentifier(
                            new OID(STR_CMS_OID_DATA).value), null);
        } // if
        else {
            cms.encapContentInfo =
                    new EncapsulatedContentInfo(new Asn1ObjectIdentifier(
                            new OID(STR_CMS_OID_DATA).value),
                            new Asn1OctetString(buffer));
        } // else

        // certificate
        // cms.certificates = new CertificateSet(1);
        // final ru.CryptoPro.JCP.ASN.PKIX1Explicit88.Certificate certificate =
        //         new ru.CryptoPro.JCP.ASN.PKIX1Explicit88.Certificate();
        // final Asn1BerDecodeBuffer decodeBuffer =
        //         new Asn1BerDecodeBuffer(cert.getEncoded());
        // certificate.decode(decodeBuffer);
        // 
        // cms.certificates.elements = new CertificateChoices[1];
        // cms.certificates.elements[0] = new CertificateChoices();
        // cms.certificates.elements[0].set_certificate(certificate);

        // signer info
        cms.signerInfos = new SignerInfos(1);
        cms.signerInfos.elements[0] = new SignerInfo();
        cms.signerInfos.elements[0].version = new CMSVersion(1);
        cms.signerInfos.elements[0].sid = new SignerIdentifier();

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

        final CertificateSerialNumber num = new CertificateSerialNumber(
                ((X509Certificate) cert).getSerialNumber());
        cms.signerInfos.elements[0].sid.set_issuerAndSerialNumber(
                new IssuerAndSerialNumber(name, num));
        cms.signerInfos.elements[0].digestAlgorithm =
                new DigestAlgorithmIdentifier(new OID(digestOid).value);
        cms.signerInfos.elements[0].digestAlgorithm.parameters = new Asn1Null();
        cms.signerInfos.elements[0].signatureAlgorithm =
                new SignatureAlgorithmIdentifier(new OID(keyOid).value);
        cms.signerInfos.elements[0].signatureAlgorithm.parameters = new Asn1Null();
        cms.signerInfos.elements[0].signature = new SignatureValue(sign);

        // encode
        final Asn1BerEncodeBuffer asnBuf = new Asn1BerEncodeBuffer();
        all.encode(asnBuf, true);
        return asnBuf.getMsgCopy();
    }
    
    public static void main(String[] args) throws Exception {
        final String storePath = "...";
        final String storePass = "...";
        final String systemId = "...";

        Security.addProvider(new ru.CryptoPro.JCP.JCP());
        HDImageStore.setDir(storePath);

        KeyStore keystore = KeyStore.getInstance(JCP.HD_STORE_NAME);
        keystore.load(null, null);

        X509Certificate certificate =
                (X509Certificate) keystore.getCertificate(keystore.aliases().nextElement());
        PrivateKey privateKey = (PrivateKey) keystore.getKey(
                keystore.aliases().nextElement(), storePass.toCharArray());

        ObjectMapper mapper = new ObjectMapper();

        var header = mapper.createObjectNode();
        header.putArray("x5c").add(certificate.getEncoded());
        header.put("alg", "ECGOST3410-2012");

        var payload = mapper.createObjectNode();
        payload.put("sub", systemId);
        payload.put("aud", "https://ips-test.rosminzdrav.ru");
        payload.put("iat", Instant.now().getEpochSecond());
        payload.put("exp", Instant.now().plus(1, ChronoUnit.MINUTES).getEpochSecond());

        String jwt = "%s.%s".formatted(
                Base64.encodeBase64URLSafeString(header.toString().getBytes(StandardCharsets.UTF_8)),
                Base64.encodeBase64URLSafeString(payload.toString().getBytes(StandardCharsets.UTF_8)));
        byte[] data = jwt.getBytes(StandardCharsets.UTF_8);

        byte sign[] = signCMS(data, privateKey, certificate, true, JCP.PROVIDER_NAME);

        String authorization = "Bearer %s.%s".formatted(jwt, Base64.encodeBase64URLSafeString(sign));

        final HttpResponse<String> response = Unirest
                .get("https://ips-test.rosminzdrav.ru/4f52d90e921a0/v2/org?orgTypeId=1&offset=0&limit=5")
                .header("Host", "ips-test.rosminzdrav.ru")
                .header("Authorization", authorization)
                .asString();
        System.out.println(response.getBody());
    }

}
RSS Лента  Atom Лента
Пользователи, просматривающие эту тему
Guest (3)
Быстрый переход  
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.