CryptoPro / corefx

This repo contains the .NET Core foundational libraries, called CoreFX. It includes classes for collections, file systems, console, XML, async and many others. We welcome contributions.
https://github.com/dotnet/core
MIT License
27 stars 7 forks source link

Ошибка работы с RSA в Linux #39

Closed Chebura closed 1 year ago

Chebura commented 3 years ago

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)

IvanKarmanov commented 3 years ago

Поддерживаю. Это серьезная проблема, так как часто внутри ЦОД используется RSA, а ГОСТ для защиты коммуникаций с внешними участниками.

Fasjeit commented 3 years ago

Ветка https://github.com/CryptoPro/corefx/tree/unix_rsa_rebuild Поправил попытку сварить CNG ключ при работе с открытым ключом. В этом месте делаем традиционно - пытаемся варить CNG ключ (в винде отрабатывает данная ветка), если не выходит - CSP (сюда упадёт Unix).

https://github.com/CryptoPro/corefx/blob/632718bc9a472071823de39657f768db0d6efd7e/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/X509Pal.PublicKey.cs#L39-L55

В целом RSA всё ещё не работает на Unix.

Текущие проблемы

1.

RSA.Create в Unix создаёт OpenSsl ключ, при попытке воспользоваться им ничего хорошего не выходит. Править в Cryptography.Algorithms. Возможно придётся по аналогии тащить csp реализацию в Algorithms.

2.

При попытке воспользоваться 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

Chebura commented 3 years ago

Я попробовал использовать код:

var rsa = RSA.Create();
rsa.ImportRSAPublicKey(encodedKeyValue, out _);
return rsa;

Не нашёл каких-то проблем, Linux/Debian, корректно работает с ключом. А в чём заключается проблема при использовании класса RSA? То что он не использует при этом CAPI, и, получается, работает в обход КП CSP?

Fasjeit commented 3 years ago

На unix метод Rsa.Create() использует сейчас метод определённый в RSAOpenSsl, в результате чего объект будет класса RSAOpenSsl что не очень хорошо, так как пытаемся уйти от реализации RSA через openssl в сторону реализации через csp.

Chebura commented 3 years ago

Если верить дорожной карте openssl (https://www.openssl.org/docs/OpenSSLStrategicArchitecture.html) они готовят реализацию более продвинутой архитектуры в unix. Вы не рассматриваете уход в сторону openssl при имплементации фреймворка в linux?

Fasjeit commented 3 years ago

В настоящий момент планы использовать CSP на unix для всей криптографии (включая sha-rsa). Если сейчас что-то до сих пор использует openssl - скорее всего это временно. На windows RSA реализация без изменений. На openssl в unix возвращаться пока не планируем.

Chebura commented 3 years ago

Спасибо за ответ. Скажите, планируете ли вы обновить рантайм с внесённым патчем? И если да - то когда?

Fasjeit commented 3 years ago

Постараемся посмотреть в ближайшее время, как только стабилизируем ветку с шифрованием.

Fasjeit commented 3 years ago

Текущая проблема 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 и случайное имя в конструкторах, и надеяться что настроена гамма. В теории должно быть проще в плане совместимости работать с такими ключами.

Текущие правки пока не заливал в ветку, всё равно не работает.

maxdm commented 3 years ago

Создание AT_xxx ключей в VerifyContext мы скорее всего не поддержим никогда. Вот запрос про это: https://jira.cp.ru/browse/CPCSP-8131 Давайте пока работать, как с ГОСТ. Наши криптопровайдеры -- через случайное имя контейнера, а генерация в VerifyContext без ДСЧ уж точно никогда не будет реализована.