Здравствуйте! Возникает проблема при валидации RAW подписи. В нашем случае есть два модуля для работы с CryptoPro CSP, в одном код на JAVA, во втором код на C++
Подпись, сгенерированная в C++ коде, тут же успешно верифицируется функцией CryptVerifySignature, однако при верификации на JAVA, подпись проверку не проходит.
Подпись, сгенерированная в самом JAVA модуле, верификацию проходит.
Хотел бы попросить оценить, в чем может быть проблема, возможно в данных модулях используются разные подходы/алгоритмы или просто содержится ошибка при генерации подписи в модуле C++. На что стоило бы обратить внимание? Сертификат используется один и тот же, он валидный, не отозван, не просрочен.
В обоих случаях используется алгоритм GOST3410_2012_256
Прилагаю код на JAVA и C++, возможно внесёт какую-то ясность:
--JAVA--
/**
* Проверка сырой подписи
*
* @param request запрос на проверку подписи
*/
public VerifyResult verify(VerifySign request) throws SignerException {
LOGGER.debug("Проверяем сырую подпись");
Map<String, String> additionalParams = request.getParams();
if (additionalParams == null) {
throw new SignerException("Отсутствуют дополнительные параметры");
}
byte[] certificateInBytes = CertificateUtils.extractCertificateFromParams(additionalParams);
if (certificateInBytes == null) {
throw new SignerException("Сертификат не был передан внутри дополнительных параметров");
}
String algorithm = additionalParams.get(ALGORITHM_PARAM_NAME); // здесь algorithm == "GOST3410_2012_256"
if (StringUtils.isBlank(algorithm)) {
throw new SignerException("Алгоритм не был передан внутри дополнительных параметров");
}
String needReverse = additionalParams.get(NEED_SIGNATURE_REVERSE_PARAM_NAME);
//Ниже описана реализация RawSigner
RawSigner verifier = factory.get(getCertificate(certificateInBytes), signatureCertificateChecker);
verifier.setCrlDir(crlDir);
verifier.setKeyStore(keyStore);
verifier.setKeyStorePath(keyStorePath);
byte[] signature = ByteUtil.getBytes(request.getSign(), "signature");
byte[] message = ByteUtil.getBytes(request.getMessage(), "message");
if ("true".equalsIgnoreCase(needReverse)) {
signature = Arrays.reverse(signature);
}
verifier.verify(algorithm, signature, message);
LOGGER.debug("Сырая подпись проверена");
return VerifyResult.builder()
.errorCode(0)
.build();
}
public class VerifySign {
private SignType type;
private String message;
private String sign;
private Map<String, String> params;
}
public final class RawSigner extends AbstractSigner {
private static final int SERIAL_NUMBER_RADIX = 16;
private RawSigner(X509Certificate certificate, X509CertificateChecker signatureCertificateChecker) {
this.signatureCertificateChecker = signatureCertificateChecker;
this.certificate = certificate;
}
/**
* Проверка сырой подписи
*
* @param algorithm алгоритм подписи
* @param sign закодированная в base64 подпись
* @param message закодированное в base64 сообщение
* @throws SignerException ошибка при проверке подписи, либо подпись не прошла проверку
*/
public void verify(String algorithm, byte[] sign, byte[] message) throws SignerException {
try {
checkCertificate(certificate);
Signature signature = getSignature(AlgorithmUtils.getRawSignatureName(algorithm));
byte[] digest = SignatureUtil.digest(
message,
AlgorithmUtils.getDigestName(algorithm)
);
boolean verify = SignatureUtil.verify(certificate.getPublicKey(), signature, sign, digest);
if (!verify) {
String errorMessage = String.format(
"Подпись не прошла проверку. Полученный сертификат: %s, серийным номером: %s, издателем: %s",
certificate.getSubjectDN().getName(),
certificate.getSerialNumber().toString(SERIAL_NUMBER_RADIX),
certificate.getIssuerDN().getName()
);
throw new SignerException(errorMessage);
}
} catch (NoSuchAlgorithmException e) {
LOGGER.error("Алгоритм {} не поддерживается", algorithm, e);
throw new SignerException(e);
}
}
public class SignatureUtil {
/**
* Метод вычисляющий дайджест по заданному алгоритму
*
* @param data данные из которых необходимо вычислить дайджест
* @param algorithm алгорим дайджеста
* @return вычисленный дайджест
* @throws SignerException ошибка, возникающая если переданный алгоритм не будет найден
*/
public byte[] digest(byte[] data, String algorithm) throws SignerException {
try {
MessageDigest digest = MessageDigest.getInstance(algorithm);
digest.update(data);
return digest.digest();
} catch (NoSuchAlgorithmException ex) {
throw new SignerException(ex);
}
}
/**
* Проверка подписи
*
* @param publicKey публичный ключ
* @param signature подпись
* @param dataToVerify данные, которые необходимо проверить
* @param trustedData данные, на основании которых осуществляется проверка
* @return {@code true} если подпись прошла проверку успешно, {@code false} если подпись не прошла проверку
* @throws SignerException если при проверке или инициализации появилась ошибка
*/
public boolean verify(
PublicKey publicKey,
Signature signature,
byte[] dataToVerify,
byte[] trustedData
) throws SignerException {
try {
signature.initVerify(publicKey);
signature.update(trustedData);
return signature.verify(dataToVerify);
} catch (GeneralSecurityException ex) {
throw new SignerException("Ошибка проверки подписи: " + ex.getMessage(), ex);
}
}
-- C++ --
std::unique_ptr<RawSigner> RawSigner::MakeSigner(const ContainerParams& containerParams) {
std::unique_ptr<RawSigner> signer{new RawSigner(containerParams.alias, containerParams.password)};
BOOST_LOG_TRIVIAL(info) << "Получение контекста ключа для алиаса " << containerParams.alias;
signer->ConnectToContainer();
signer->ProcessKeys();
BOOST_LOG_TRIVIAL(info) << "Создание хеш объекта";
signer->CreateHashObject();
BOOST_LOG_TRIVIAL(info) << "Хеш объект создан";
return signer;
}
void RawSigner::ConnectToContainer() {
auto keyAliasBytes = this->keyAlias.c_str();
LPCSTR pszProvider = nullptr;
bool cryptAcquireContext = CryptAcquireContext(
&(this->hProv),
keyAliasBytes,
pszProvider,
PROV_GOST_2012_256,
CRYPT_SILENT);
ExceptionUtils::ThrowIfFalse(cryptAcquireContext, "Ошибка CryptAcquireContext");
BOOST_LOG_TRIVIAL(info) << "Контекст ключа получен";
BOOST_LOG_TRIVIAL(info) << "Установка пароля контейнера";
bool cryptSetProvParam = CryptSetProvParam(
this->hProv,
PP_KEYEXCHANGE_PIN,
(BYTE*) this->password.c_str(),
0);
ExceptionUtils::ThrowIfFalse(cryptSetProvParam, "Ошибка CryptSetProvParam");
BOOST_LOG_TRIVIAL(info) << "Установка пароля контейнера завершена";
}
void RawSigner::ProcessKeys() {
bool cryptGetUserKey = CryptGetUserKey(
this->hProv,
AT_SIGNATURE,
&this->hKey);
ExceptionUtils::ThrowIfFalse(cryptGetUserKey, "Ошибка CryptGetUserKey");
BOOST_LOG_TRIVIAL(info) << "Определение размера BLOB для публичного ключа";
DWORD dwBlobLen;
if (CryptExportKey(
this->hKey,
0,
PUBLICKEYBLOB,
0,
nullptr,
&dwBlobLen)) {
printf("Size of the BLOB for the public key determined. \n");
} else {
if (GetLastError() == (DWORD) SCARD_W_WRONG_CHV) {
throw DomainException("Неправильный пароль. CryptExportKey");
} else {
throw DomainException("Ошибка при вычислении размера BLOB. CryptExportKey");
}
}
this->pbKeyBlob = (BYTE*) malloc(dwBlobLen);
if (!this->pbKeyBlob)
throw DomainException("Out of memory. ExportKey. pbKeyBlob");
if (CryptExportKey(
this->hKey,
0,
PUBLICKEYBLOB,
0,
this->pbKeyBlob,
&dwBlobLen)) {
} else {
if (GetLastError() == (DWORD) SCARD_W_WRONG_CHV) {
throw DomainException("Неправильный пароль. CryptExportKey");
} else {
throw DomainException("Ошибка при вычислении размера BLOB. CryptExportKey");
}
}
BOOST_LOG_TRIVIAL(info) << "Размер BLOB для публичного ключа определён";
}
void RawSigner::CreateHashObject() {
DWORD cbHash;
bool cryptCreateHash = CryptCreateHash(
this->hProv,
CALG_GR3411_2012_256,
0,
0,
&this->hHash);
ExceptionUtils::ThrowIfFalse(cryptCreateHash, "Ошибка CryptCreateHash");
bool cryptGetHashParam = CryptGetHashParam(
this->hHash,
HP_OID,
nullptr,
&cbHash,
0
);
ExceptionUtils::ThrowIfFalse(cryptGetHashParam, "Ошибка CryptGetHashParam");
this->pbHash = (BYTE*) malloc(cbHash);
if (!this->pbHash)
throw DomainException("Out of memory. GetHashParam. pbHash");
cryptGetHashParam = CryptGetHashParam(
this->hHash,
HP_OID,
this->pbHash,
&cbHash,
0
);
ExceptionUtils::ThrowIfFalse(cryptGetHashParam, "Ошибка CryptGetHashParam");
}
SignResult RawSigner::Sign(const std::string& data) {
BYTE* pbBuffer = (BYTE*) &data[0];
DWORD dwSigLen = 0;
auto dwBufferLen = (DWORD) (strlen((char*) pbBuffer) + 1);
bool cryptHashData = CryptHashData(
this->hHash,
pbBuffer,
dwBufferLen,
0);
ExceptionUtils::ThrowIfFalse(cryptHashData, "Ошибка CryptHashData");
bool cryptSignHash = CryptSignHash(
this->hHash,
AT_SIGNATURE,
nullptr,
0,
nullptr,
&dwSigLen);
ExceptionUtils::ThrowIfFalse(cryptSignHash, "Ошибка вычисления размера dwSigLen CryptSignHash");
this->pbSignature = (BYTE*) malloc(dwSigLen);
cryptSignHash = CryptSignHash(
this->hHash,
AT_SIGNATURE,
nullptr,
0,
this->pbSignature,
&dwSigLen);
ExceptionUtils::ThrowIfFalse(cryptSignHash, "Ошибка CryptSignHash");
auto signature = base64_encode(this->pbSignature, dwSigLen);
bool result = CryptVerifySignature(hHash, this->pbSignature, dwSigLen, hKey, NULL, 0);
if (!result) {
throw DomainException{"Подпись не прошла валидацию"};
}
return SignResult::Success(signature);
}
RawSigner::~RawSigner() {
free(this->pbHash);
free(this->pbKeyBlob);
free(this->pbSignature);
if (this->hHash)
CryptDestroyHash(this->hHash);
if (this->hKey)
CryptDestroyKey(hKey);
if (this->hProv)
CryptReleaseContext(this->hProv, 0);
}