У меня есть новая фабричная служба, предназначенная для .Net 4.7 В 4.7 были внесены крипто-изменения, которые нарушили некоторые сценарии аутентификации, включая keyvault.Изменения были внесены в код авторизации для обработки 4.5 и 4.7.В моем локальном кластере все работает нормально, но в реальном кластере происходит сбой, за исключением следующего:
Сообщение: сбой RunAsync из-за необработанного исключения, вызывающего сбой хост-системы: System.Security.Cryptography.CryptographicException: Ключ недопустим для использования в указанном состоянии.
в System.Security.Cryptography.CryptographicException.ThrowCryptographicException (Int32 ч) в System.Security.Cryptography.Utils._ExportKey (SafeKeyHandle hKey, Int32 blobTT, Объект cspObject) в System.Security.Cryptography.RSACryptoServiceProvider.ExportParameters (Boolean includePrivateParameters) в System.Security.Cryptography.RSA.ToXmlString (логические includePrivateParameters) установлены *1000* найдено 1010
по коду.Закрытые ключи сертификатов также имеют соответствующие разрешения, поэтому они могут быть прочитаны NetworkService, от имени которого работает служба.Похоже, что эта ошибка вызвана тем, что сертификат, развернутый в сервисной фабрике, не помечен как экспортируемый.Ниже приведен код метода Sign, который используется для того, чтобы наши приложения из 4.5 и 4.7 могли использовать keyvault с одной и той же библиотекой.С другими нашими облачными сервисами все в порядке, просто сервисная фабрика выходит из строя.
Я полагаю, что возможным решением было бы написать код для конкретного метода 4.7, но я не нашел конкретного примера 4.7, который не используеттот же метод, который я уже использую.Я также не нашел способа получить шаблон развертывания, чтобы пометить сертификаты как экспортируемые.
/// <summary>
/// Sign a message with the private key of the certificate provided
/// The signature is expected use the SHA256 algorithm or the assertion will not be valid.
/// </summary>
/// <param name="message">The message to sign.</param>
/// <returns>Array of signed bytes.</returns>
public byte[] Sign(string message)
{
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
X509AsymmetricSecurityKey x509Key = new X509AsymmetricSecurityKey(this.certificate);
using (RSA rsa = x509Key.GetAsymmetricAlgorithm(SecurityAlgorithms.RsaSha256Signature, true) as RSA)
{
RSACryptoServiceProvider newRsa = null;
try
{
if (rsa is RSACryptoServiceProvider)
{
// For .NET 4.6 and below we get the old RSACryptoServiceProvider implementation as the default.
// Try and get an instance of RSACryptoServiceProvider which supports SHA256
newRsa = GetCryptoProviderForSha256((RSACryptoServiceProvider)rsa);
}
else
{
// For .NET Framework 4.7 and onwards the RSACng implementation is the default.
// Since we're targeting .NET Framework 4.5, we cannot actually use this type as it was
// only introduced with .NET Framework 4.6.
// Instead we try and create an RSACryptoServiceProvider based on the private key from the
// certificate.
newRsa = GetCryptoProviderForSha256(this.certificate);
}
using (SHA256Cng sha = new SHA256Cng())
{
return newRsa.SignData(messageBytes, sha);
}
}
finally
{
// We only want to dispose of the 'newRsa' instance if it is a *different instance*
// from the original one that was used to create it.
if (newRsa != null && !ReferenceEquals(rsa, newRsa))
{
newRsa.Dispose();
}
}
}
}
/// <summary>
/// Create a <see cref="RSACryptoServiceProvider"/> using the private key from the given <see cref="X509Certificate2"/>.
/// </summary>
/// <param name="certificate">Certificate including private key with which to initialize the <see cref="RSACryptoServiceProvider"/> with</param>
/// <returns><see cref="RSACryptoServiceProvider"/> initialized with private key from <paramref name="certificate"/></returns>
private static RSACryptoServiceProvider GetCryptoProviderForSha256(X509Certificate2 certificate)
{
string privateKeyXmlParams = certificate.PrivateKey.ToXmlString(true);
RSACryptoServiceProvider rsa = null;
try
{
rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(privateKeyXmlParams);
return rsa;
}
catch (Exception)
{
if (rsa != null)
{
rsa.Dispose();
}
throw;
}
}
Обновление:
Получает решение для чисто 4,7просто
else if (rsa is RSACng)
{
return rsa.SignData(messageBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Но это не очень хорошо работает для библиотек, на которые могут ссылаться проекты в более старых версиях .Net, например, для библиотеки, из которой я позаимствовал этот код.Более новый тип RSACng не будет доступен в этих случаях.Я предполагаю, что это может быть обработано кучей #if NETXXX, но для этого потребуется обновление в каждом выпуске .Net.