Статус: Участник
Группы: Участники
Зарегистрирован: 07.07.2020(UTC) Сообщений: 19
Сказал(а) «Спасибо»: 1 раз
|
Доброго дня. Нам необходимо сформировать алгоритм подписи запроса в формате PKCS#7 detached signature в кодировке UTF-8 от 4-х параметров запроса. На данном ресурсе был найден удовлетворяющий требованиям код, но формируемая им подпись не верифицируется принимающей стороной, которая возвращает ошибку: The signature in client secret parameter was not verified. Вопрос: Каким образом можно проверить подписывающий алгоритм? Какие нибудь идеи или комментарии? Ниже под сполером сам алгоритм. В архивах тестовые ключи и сертификаты и сам тестовый проект. Наш стенд: Java 13: Лицензионный jcp-2.0.41664-A:
Код:
package com.test.api;
import org.apache.commons.codec.binary.Hex;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.objsys.asn1j.runtime.Asn1BerDecodeBuffer;
import com.objsys.asn1j.runtime.Asn1BerEncodeBuffer;
import com.objsys.asn1j.runtime.Asn1Null;
import com.objsys.asn1j.runtime.Asn1ObjectIdentifier;
import com.objsys.asn1j.runtime.Asn1OctetString;
import java.io.*;
import java.net.URI;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import ru.CryptoPro.Crypto.CryptoProvider;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.CMSVersion;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.CertificateChoices;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.CertificateSet;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.ContentInfo;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.DigestAlgorithmIdentifier;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.DigestAlgorithmIdentifiers;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.EncapsulatedContentInfo;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.IssuerAndSerialNumber;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.SignatureAlgorithmIdentifier;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.SignatureValue;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.SignedData;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.SignerIdentifier;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.SignerInfo;
import ru.CryptoPro.JCP.ASN.CryptographicMessageSyntax.SignerInfos;
import ru.CryptoPro.JCP.ASN.PKIX1Explicit88.CertificateSerialNumber;
import ru.CryptoPro.JCP.ASN.PKIX1Explicit88.Name;
import ru.CryptoPro.JCP.JCP;
import ru.CryptoPro.JCP.params.OID;
import ru.CryptoPro.reprov.RevCheck;
@Controller
@RequestMapping("/")
public class authEsia {
String scope = "fullname";
String clientid = "NPCPN";
String responseType = "code";
String datetime = GetDateTime();
String state = GetState();
public String getResponseType() {
return responseType;
}
public String getScope() {
return scope;
}
public String getClientid() {
return clientid;
}
public String getDatetime() {
return datetime;
}
public String getState() {
return state;
}
private String GetState(){
UUID uuid = UUID.randomUUID();
return uuid.toString();
}
private String GetDateTime(){
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy.MM.dd+HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.now();
return dtf.format(localDateTime).replace(":","%3A")+"+%2B0300";
}
public static X509Certificate GetCertificateFromPersonalStore() {
Security.addProvider(new JCP());
Security.addProvider(new RevCheck());
Security.addProvider(new CryptoProvider());// провайдер шифрования JCryptoP
X509Certificate x509 = null;
try {
KeyStore keystore = KeyStore.getInstance("HDImageStore");
keystore.load((InputStream)null, (char[])null);
Enumeration en = keystore.aliases();
while(en.hasMoreElements()) {
String s = (String)en.nextElement();
if (keystore.isKeyEntry(s)) {
Certificate[] kcerts = keystore.getCertificateChain(s);
if (kcerts[0] instanceof X509Certificate) {
x509 = (X509Certificate)kcerts[0];
}
}
if (keystore.isCertificateEntry(s)) {
Certificate c = keystore.getCertificate(s);
if (c instanceof X509Certificate) {
x509 = (X509Certificate)c;
}
}
}
} catch (Exception var6) {
var6.printStackTrace();
return null;
}
return x509;
}
public static byte[] sign(X509Certificate cert, byte[] data) throws Exception {
char [] pass = "1234567890".toCharArray();
PrivateKey privateKey = getPrivateKey(cert, pass);
return CMSSign(data, privateKey, cert, false);
}
public static PrivateKey getPrivateKey(X509Certificate cert, char[] password) {
try {
KeyStore keystore = KeyStore.getInstance("HDImageStore");
keystore.load((InputStream)null, (char[])null);
Enumeration en = keystore.aliases();
while(en.hasMoreElements()) {
String s = (String)en.nextElement();
if (keystore.isKeyEntry(s)) {
Certificate[] kcerts = keystore.getCertificateChain(s);
if (kcerts[0] instanceof X509Certificate) {
X509Certificate x509 = (X509Certificate)kcerts[0];
if (getThumbPrint(x509).startsWith(getThumbPrint(cert))) {
return (PrivateKey)keystore.getKey(s, password);
}
}
}
if (keystore.isCertificateEntry(s)) {
Certificate c = keystore.getCertificate(s);
if (c instanceof X509Certificate && getThumbPrint((X509Certificate)c).startsWith(getThumbPrint(cert))) {
return (PrivateKey)keystore.getKey(s, password);
}
}
}
} catch (Exception var7) {
var7.printStackTrace();
}
return null;
}
public static String getThumbPrint(X509Certificate cert) throws NoSuchAlgorithmException, CertificateEncodingException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] der = cert.getEncoded();
md.update(der);
byte[] digest = md.digest();
return new String(Hex.encodeHex(digest));
}
public static byte[] CMSSign(byte[] data, PrivateKey key, Certificate cert, boolean detached) throws Exception {
//Алгоритм подписи
Signature signature = Signature.getInstance("GOST3411_2012_256withGOST3410_2012_256");
signature.initSign( key );
signature.update( data );
byte[] sign = signature.sign();
return createCMS( data, sign, cert, detached );
}
public static byte[] createCMS(byte[] buffer, byte[] sign, Certificate cert, boolean detached) throws Exception {
ContentInfo all = new ContentInfo();
all.contentType = new Asn1ObjectIdentifier((new OID("1.2.840.113549.1.7.2")).value);
SignedData cms = new SignedData();
all.content = cms;
cms.version = new CMSVersion(1L);
cms.digestAlgorithms = new DigestAlgorithmIdentifiers(1);
DigestAlgorithmIdentifier a = new DigestAlgorithmIdentifier((new OID("1.2.643.7.1.1.2.2")).value);
a.parameters = new Asn1Null();
cms.digestAlgorithms.elements[0] = a;
if (detached) {
cms.encapContentInfo = new EncapsulatedContentInfo(new Asn1ObjectIdentifier((new OID("1.2.840.113549.1.7.1")).value), (Asn1OctetString)null);
} else {
cms.encapContentInfo = new EncapsulatedContentInfo(new Asn1ObjectIdentifier((new OID("1.2.840.113549.1.7.1")).value), new Asn1OctetString(buffer));
}
cms.certificates = new CertificateSet(1);
ru.CryptoPro.JCP.ASN.PKIX1Explicit88.Certificate certificate = new ru.CryptoPro.JCP.ASN.PKIX1Explicit88.Certificate();
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);
cms.signerInfos = new SignerInfos(1);
cms.signerInfos.elements[0] = new SignerInfo();
cms.signerInfos.elements[0].version = new CMSVersion(1L);
cms.signerInfos.elements[0].sid = new SignerIdentifier();
byte[] encodedName = ((X509Certificate)cert).getIssuerX500Principal().getEncoded();
Asn1BerDecodeBuffer nameBuf = new Asn1BerDecodeBuffer(encodedName);
Name name = new Name();
name.decode(nameBuf);
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("1.2.643.7.1.1.2.2")).value);
cms.signerInfos.elements[0].digestAlgorithm.parameters = new Asn1Null();
cms.signerInfos.elements[0].signatureAlgorithm = new SignatureAlgorithmIdentifier((new OID("1.2.643.7.1.1.1.1")).value);
cms.signerInfos.elements[0].signatureAlgorithm.parameters = new Asn1Null();
cms.signerInfos.elements[0].signature = new SignatureValue(sign);
Asn1BerEncodeBuffer asnBuf = new Asn1BerEncodeBuffer();
all.encode(asnBuf, true);
return asnBuf.getMsgCopy();
}
private String GetClientSecret(String state) throws Exception {
String msg = getScope() + getDatetime() + getClientid() + getState();
byte[] msgBytes = msg.getBytes("UTF8");
byte[] encodedSignature = sign(GetCertificateFromPersonalStore(), msgBytes);
return Base64UrlEncode(encodedSignature);
}
public static String Base64UrlEncode(byte[] arg)
{
String encodedString = Base64.getEncoder().encodeToString(arg);
encodedString = encodedString.replace("+","-");
encodedString = encodedString.replace("/","_");
encodedString = encodedString.replace("=","");
return encodedString;
}
@GetMapping("/authEsia")
ResponseEntity<Void> getCode() throws Exception {
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("https://esia-portal1.test.gosuslugi.ru/aas/oauth2/ac?" +
"client_id=" + getClientid() + "&" +
"client_secret=" + GetClientSecret(getState()) + "&" +
"redirect_uri=http://localhost:8080/redirect&" +
"scope=" + getScope() + "&" +
"response_type=" + getResponseType() + "&" +
"state=" + getState() + "&" +
"access_type=offline&" +
"timestamp=" + getDatetime()
))
.build();
}
}
|
|
|
|
Статус: Эксперт
Группы: Участники
Зарегистрирован: 05.03.2015(UTC) Сообщений: 1,602 Откуда: Иркутская область Сказал(а) «Спасибо»: 110 раз Поблагодарили: 395 раз в 366 постах
|
Добрый день. Архивов что-то не вижу. На первый взгляд вроде бы похоже на правильный код, хотя конкретно авторизацией ЕСИА я не занимался, возможно там какие-то нюансы. Там точно надо собирать CMS? Бросилось в глаза что несколько sign - и метод и массив и signature.sign, да и вообще много что в переменных/методах отличается только регистром. Вроде как и не ошибка, но рискованно так писать. Код:return CMSSign(data, privateKey, cert, false);
Однако больше всего подозрительно, что передаете в данной строке в параметр detached значение false и ожидаете получить отсоединенную подпись. Если код верный, то туда надо передать true для получения отсоединенной подписи. Отредактировано пользователем 5 августа 2020 г. 12:59:55(UTC)
| Причина: Не указана
|
|
|
|
Статус: Участник
Группы: Участники
Зарегистрирован: 07.07.2020(UTC) Сообщений: 19
Сказал(а) «Спасибо»: 1 раз
|
Добавил архивы с проектом и тестовыми сертификатами, ключами Sert_key_dataRequest_errdataResponse.zip (10kb) загружен 9 раз(а). authEsia.zip (20kb) загружен 8 раз(а).Автор: two_oceans Там точно надо собирать CMS?
Я считал, что CMS обязательна для подписи в формате PKCS#7 на фрейморке jcp.
|
|
|
|
Статус: Участник
Группы: Участники
Зарегистрирован: 07.07.2020(UTC) Сообщений: 19
Сказал(а) «Спасибо»: 1 раз
|
Автор: two_oceans Однако больше всего подозрительно, что передаете в данной строке в параметр detached значение false и ожидаете получить отсоединенную подпись.
Верно, поправил, но не полегчало. ( Попробовал другим способом с помощью командной строки. Результат тот же. Код:cryptcp.x64 -signf -der -strict -cert -detached -thumbprint "d45c892fbda933e0c0c3e0b55d5a5b3a2355f323" -pin "1234567890" messagestr.txt
В чем то ошибаюсь, но понять не могу.
|
|
|
|
Статус: Эксперт
Группы: Участники
Зарегистрирован: 05.03.2015(UTC) Сообщений: 1,602 Откуда: Иркутская область Сказал(а) «Спасибо»: 110 раз Поблагодарили: 395 раз в 366 постах
|
Сравнил что в архиве со парой статей.
Запрос отличается от второй статьи, тем что для redirect_url в архиве не выполнено urlencode. Еще там до кучи запихали openid в scope, не знаю влияет ли на что. В первой еще и плюсики в строке запроса заменили на %2b (так двойное кодирование может выйти) и подписываемую строку кодировали в UTF-8 (но русских букв в ней ведь нет?) Для ручной сборки CMS с гост еще возможно надо перевернуть значение подписи (которое byte[] sign), поэтому наверно надо отработать версию с urlencode на полученной подписи из утилиты, не забывая закодировать base64 и заменить плюсики и равно, а уж потом смотреть по сборке. Отредактировано пользователем 7 августа 2020 г. 13:21:34(UTC)
| Причина: Не указана
|
|
|
|
Статус: Участник
Группы: Участники
Зарегистрирован: 07.07.2020(UTC) Сообщений: 19
Сказал(а) «Спасибо»: 1 раз
|
Автор: two_oceans поэтому наверно надо отработать версию с urlencode на полученной подписи из утилиты, не забывая закодировать base64 и заменить плюсики и равно, а уж потом смотреть по сборке. Убрал из команды утилиты ключ -der, теперь подпись формируется в base64, по умолчаню. Код:Runtime.getRuntime().exec(new String[]{"cmd.exe","/c","start cryptcp.x64 -signf -strict -cert -detached -thumbprint \"d45c892fbda933e0c0c3e0b55d5a5b3a2355f323\" -pin \"1234567890\" messagestr.txt"});
Тут сама утилита которую кладу в корень проекта www.cryptopro.ru/sites/default/files/private/csp/50/11823/cryptcp.x64.exe Под сполером листинг в котором вызываю утилиту не руками, а программно.
Код:
package com.test.api;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.io.*;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
@Controller
@RequestMapping("/")
public class authEsia {
String scope = "fullname";
String clientid = "NPCPN";
String responseType = "code";
String datetime = GetDateTime();
String state = GetState();
public String getResponseType() {
return responseType;
}
public String getScope() {
return scope;
}
public String getClientid() {
return clientid;
}
public String getDatetime() {
return datetime;
}
public String getState() {
return state;
}
private String GetState(){
UUID uuid = UUID.randomUUID();
return uuid.toString();
}
private String GetDateTime(){
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy.MM.dd+HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.now();
return dtf.format(localDateTime).replace(":","%3A")+"+%2B0300";
}
private String getSignClientSecret() throws IOException {
String str = new String(Files.readAllBytes(Paths.get("messagestr.txt.sgn")));
str = str.replace("\n","");
str = str.replace("+","-");
str = str.replace("/","_");
str = str.replace("=","");
return str;
}
private void getMessage(){
try(FileWriter writer = new FileWriter("messagestr.txt",false)){
String text = getScope() + getDatetime() + getClientid() + getState();
writer.write(text);
writer.flush();
}catch (IOException ex){
System.out.println(ex.getMessage());
}
}
@GetMapping("/authEsia")
ResponseEntity<Void> getCode() throws Exception {
getMessage();
Runtime.getRuntime().exec(new String[]{"cmd.exe","/c","start cryptcp.x64 -signf -strict -cert -detached -thumbprint \"d45c892fbda933e0c0c3e0b55d5a5b3a2355f323\" -pin \"1234567890\" messagestr.txt"});
Thread.sleep(5000);
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("https://esia-portal1.test.gosuslugi.ru/aas/oauth2/ac?" +
"client_id=" + getClientid() + "&" +
"client_secret=" + getSignClientSecret() + "&" +
"redirect_uri=http://localhost:8080/redirect&" +
"scope=" + getScope() + "&" +
"response_type=" + getResponseType() + "&" +
"state=" + getState() + "&" +
"access_type=offline&" +
"timestamp=" + getDatetime()
))
.build();
}
}
Результат опять не радует (( Всё та же ошибка ESIA-007005: The client is not authorized to request an access token using this method.
|
|
|
|
Статус: Сотрудник
Группы: Модератор, Участники Зарегистрирован: 03.12.2018(UTC) Сообщений: 1,213 Сказал(а) «Спасибо»: 101 раз Поблагодарили: 284 раз в 264 постах
|
Добрый день! а что у вас в Цитата:new String[]{"cmd.exe","/c","start cryptcp.x64 -signf -strict -cert -detached -thumbprint \"d45c892fbda933e0c0c3e0b55d5a5b3a2355f323\" -pin \"1234567890\" messagestr.txt"} мне кажется там будет вывод информации о подписании, но не сама подпись |
|
|
|
|
Статус: Участник
Группы: Участники
Зарегистрирован: 07.07.2020(UTC) Сообщений: 19
Сказал(а) «Спасибо»: 1 раз
|
Автор: Санчир Момолдаев мне кажется там будет вывод информации о подписании, но не сама подпись
Нет там сама подпись на выходе. Но ошибку я нашёл - она закралась в формате формирования даты. Спасибо, всем кто смотрел и помог советом!! Тему можно закрыть как решенную.
|
|
|
|
Статус: Новичок
Группы: Участники
Зарегистрирован: 13.01.2021(UTC) Сообщений: 1
|
Base64.encodeBase64URLSafeString(sign(GetCertificateFromPersonalStore(),(AUTH_SCOPE + timestamp + clientId + state).getBytes(StandardCharsets.UTF_8)))
|
|
|
|
Быстрый переход
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.
Important Information:
The Форум КриптоПро uses cookies. By continuing to browse this site, you are agreeing to our use of cookies.
More Details
Close