Closed Chebura closed 1 year ago
Поддерживаю. Это серьезная проблема, так как часто внутри ЦОД используется RSA, а ГОСТ для защиты коммуникаций с внешними участниками.
Ветка https://github.com/CryptoPro/corefx/tree/unix_rsa_rebuild Поправил попытку сварить CNG ключ при работе с открытым ключом. В этом месте делаем традиционно - пытаемся варить CNG ключ (в винде отрабатывает данная ветка), если не выходит - CSP (сюда упадёт Unix).
В целом RSA всё ещё не работает на Unix.
RSA.Create
в Unix создаёт OpenSsl ключ, при попытке воспользоваться им ничего хорошего не выходит. Править в Cryptography.Algorithms. Возможно придётся по аналогии тащить csp реализацию в Algorithms
.
При попытке воспользоваться RsaCryptoServiceProvider
падает на internal static int GenerateKey
в CapiHelper.Windows.cs
.
var rsa = new RSACryptoServiceProvider();
rsa.SignData(new byte[] {0}, 0, 1, SHA256.Create());
Версия csp 5.0.11823
Я попробовал использовать код:
var rsa = RSA.Create();
rsa.ImportRSAPublicKey(encodedKeyValue, out _);
return rsa;
Не нашёл каких-то проблем, Linux/Debian, корректно работает с ключом. А в чём заключается проблема при использовании класса RSA? То что он не использует при этом CAPI, и, получается, работает в обход КП CSP?
На unix метод Rsa.Create()
использует сейчас метод определённый в RSAOpenSsl, в результате чего объект будет класса RSAOpenSsl
что не очень хорошо, так как пытаемся уйти от реализации RSA через openssl в сторону реализации через csp.
Если верить дорожной карте openssl (https://www.openssl.org/docs/OpenSSLStrategicArchitecture.html) они готовят реализацию более продвинутой архитектуры в unix. Вы не рассматриваете уход в сторону openssl при имплементации фреймворка в linux?
В настоящий момент планы использовать CSP на unix для всей криптографии (включая sha-rsa). Если сейчас что-то до сих пор использует openssl - скорее всего это временно. На windows RSA реализация без изменений. На openssl в unix возвращаться пока не планируем.
Спасибо за ответ. Скажите, планируете ли вы обновить рантайм с внесённым патчем? И если да - то когда?
Постараемся посмотреть в ближайшее время, как только стабилизируем ветку с шифрованием.
Текущая проблема 1 - RSACryptoServiceProvider при создании через пустой конструктор создаётся в Verify_Context, exportable, keySpec =1. В этом сценарии CSP работать не будет (см https://jira.cp.ru/browse/CPCSP-9734).
При генерации ключа надо явно указывать Aldig, что текущим кодом от ms вообще не предусмотрено. Можно сделать через пару грязных правок
RsaCsp
public RSACryptoServiceProvider()
: this(0, new CspParameters(CapiHelper.DefaultRsaProviderType,
null,
null,
s_useMachineKeyStore)
// begin: gost
// явно проставляем CALG_RSA_KEYX в keyNumber, дабы он передался дальше как algid. CSP не умеет работать с KeyNumber в algid в VERIFY_CONTEXT
{ KeyNumber = CALG_RSA_KEYX }
// end: gost
,
true)
{
}
CapiHelper
internal static CspParameters SaveCspParameters(
CspAlgorithmType keyType,
CspParameters userParameters,
CspProviderFlags defaultFlags,
out bool randomKeyContainer)
{
CspParameters parameters;
//begin: gost
if (userParameters != null && userParameters.ProviderType != (int)keyType)
{
switch (keyType)
{
case CspAlgorithmType.Dss:
userParameters.ProviderType = DefaultDssProviderType;
break;
case CspAlgorithmType.Gost2001:
case CspAlgorithmType.Gost2012_256:
case CspAlgorithmType.Gost2012_512:
userParameters.ProviderType = (int)keyType;
break;
case CspAlgorithmType.Rsa:
default:
userParameters.ProviderType = DefaultRsaProviderType;
break;
}
}
//end: gost
if (userParameters == null)
{
//begin: gost
switch (keyType)
{
case CspAlgorithmType.Dss:
parameters = new CspParameters(DefaultDssProviderType, null, null, defaultFlags);
break;
case CspAlgorithmType.Gost2001:
case CspAlgorithmType.Gost2012_256:
case CspAlgorithmType.Gost2012_512:
parameters = new CspParameters((int)keyType, null, null, defaultFlags);
break;
case CspAlgorithmType.Rsa:
default:
parameters = new CspParameters(DefaultRsaProviderType, null, null, defaultFlags);
break;
}
//end: gost
}
else
{
ValidateCspFlags(userParameters.Flags);
parameters = new CspParameters(userParameters);
}
if (parameters.KeyNumber == -1)
{
// if gost goes here it ends with KeyNumber.Exchange
parameters.KeyNumber = keyType == CapiHelper.CspAlgorithmType.Dss
? (int)KeyNumber.Signature
: (int)KeyNumber.Exchange;
}
else if (parameters.KeyNumber == CALG_DSS_SIGN ||
// begin: gost
//parameters.KeyNumber == CALG_RSA_SIGN ||
// end: gost
parameters.KeyNumber == GostConstants.CALG_GR3410EL ||
parameters.KeyNumber == GostConstants.CALG_GR3410_12_256 ||
parameters.KeyNumber == GostConstants.CALG_GR3410_12_256)
{
parameters.KeyNumber = (int)KeyNumber.Signature;
}
else if (
// begin: gost
//parameters.KeyNumber == CALG_RSA_KEYX ||
// end: gost
parameters.KeyNumber == GostConstants.CALG_DH_EL_SF ||
parameters.KeyNumber == GostConstants.CALG_DH_EL_SF ||
parameters.KeyNumber == GostConstants.CALG_DH_GR3410_12_512_SF)
{
parameters.KeyNumber = (int)KeyNumber.Exchange;
}
// begin: gost
// явно проставляем CALG_RSA_KEYX в keyNumber, дабы он передался дальше как algid. CSP не умеет работать с KeyNumber в algid в VERIFY_CONTEXT
// виндовая ветка сюда не попадёь, т.к. она работает через родной CNG
// parameters.KeyNumber уже сожержит ALGID для RSA (проставили в конструкторе), нужно только его сохранить как есть
else if (
parameters.KeyNumber == CALG_RSA_SIGN ||
parameters.KeyNumber == CALG_RSA_KEYX)
{
// do nothing
}
// end: gost
// If no key container was specified and UseDefaultKeyContainer is not used, then use CRYPT_VERIFYCONTEXT
// to generate an ephemeral key
randomKeyContainer = IsFlagBitSet((uint)parameters.Flags, (uint)CspProviderFlags.CreateEphemeralKey);
if (parameters.KeyContainerName == null && !IsFlagBitSet((uint)parameters.Flags,
(uint)CspProviderFlags.UseDefaultKeyContainer))
{
// add: gost
switch (parameters.ProviderType)
{
case (int)CspAlgorithmType.Gost2001:
case (int)CspAlgorithmType.Gost2012_256:
case (int)CspAlgorithmType.Gost2012_512:
{
parameters.KeyContainerName = GetRandomKeyContainer();
break;
}
default:
{
parameters.Flags |= CspProviderFlags.CreateEphemeralKey;
break;
}
}
// end: gost
randomKeyContainer = true;
}
return parameters;
}
Проблема вторая - после генерации ключа на нём не получается подписать, передавая AT_XXXX
в keyspec. Кидает "набор ключей не существует". Возможно помогло бы генерация ключ, экспорт его в privatekeyblob, сразу же импорт обратно.
И тогда вроде можно и подписывать (Всё это в VerifyContext), но код по экпорту\импорту сейчас тоже из коробки не заводится. Нужно будет выяснять.
Альтернативный подход - работать как с ГОСТами, исключительно через контейнеры без VerifyContext. Проблема - те же пустые конструкторы. Возможно просто явно ставить SILENT и случайное имя в конструкторах, и надеяться что настроена гамма. В теории должно быть проще в плане совместимости работать с такими ключами.
Текущие правки пока не заливал в ветку, всё равно не работает.
Создание AT_xxx ключей в VerifyContext мы скорее всего не поддержим никогда. Вот запрос про это: https://jira.cp.ru/browse/CPCSP-8131 Давайте пока работать, как с ГОСТ. Наши криптопровайдеры -- через случайное имя контейнера, а генерация в VerifyContext без ДСЧ уж точно никогда не будет реализована.
https://github.com/CryptoPro/corefx/blob/master/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/X509Pal.PublicKey.cs
Строка 42
Сборка linux_x64 (обращаю внимание, что в сборку для linux попадает пространство имён .Windows., что в общем-то странно).
Загрузка в X509Certificate2 сертификата с открытым ключом и попытка извлечения открытого ключа (GetPublicKey()) в Linux вызывается вышеуказанный код с использованием CNG, которая в linux не поддерживается. В результате возникает ошибка 0x00000057 (87) The parameter is incorrect. Использование класса RSA (без CNG) решает эту проблему.
Пожалуйста, проверьте, чтобы в сборках под linux не использовались CNG.
Из-за данной проблемы в aspnetcore не работает валиадация JWT-токена (Bearer-схемы), при валидном сертификате RSA (а это серьёзная проблема):
{Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10511: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey, KeyId: '260A93852F5760A68378xxxxxx1475E7D501317', InternalId: 'JgqThS9XYKaDePxyHRFHXn1QExc'. , KeyId: 260A93852F5760A68378Fxxxxxx1475E7D501317 '. kid: '260A93852F5760A68378Fxxxxxx1475E7D501317'. Exceptions caught: 'Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: The parameter is incorrect.
at Internal.Cryptography.Pal.X509Pal.DecodeKeyBlob(CryptDecodeObjectStructType lpszStructType, Byte[] encodedKeyValue) at Internal.Cryptography.Pal.X509Pal.DecodePublicKey(Oid oid, Byte[] encodedKeyValue, Byte[] encodedParameters, ICertificatePal certificatePal) at Internal.Cryptography.Pal.CertificateExtensionsCommon.GetPublicKey[T](X509Certificate2 certificate, Predicate`1 matchesConstraints) at System.Security.Cryptography.X509Certificates.RSACertificateExtensions.GetRSAPublicKey(X509Certificate2 certificate) at Microsoft.IdentityModel.Tokens.X509SecurityKey.get_PublicKey() at Microsoft.IdentityModel.Tokens.SupportedAlgorithms.IsSupportedAlgorithm(String algorithm, SecurityKey key) at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.IsSupportedAlgorithm(String algorithm, SecurityKey key) at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures, Boolean cacheProvider) at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForVerifying(SecurityKey key, String algorithm, Boolean cacheProvider) at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForVerifying(SecurityKey key, String algorithm) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(Byte[] encodedBytes, Byte[] signature, SecurityKey key, String algorithm, SecurityToken securityToken, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)