Ключевое слово в защите информации
ключевое слово
в защите информации
Получить ГОСТ TLS-сертификат для домена (SSL-сертификат)
Добро пожаловать, Гость! Чтобы использовать все возможности Вход. Новые регистрации запрещены.

Уведомление

Icon
Error

Опции
К последнему сообщению К первому непрочитанному
Offline [ETC]Dema  
#1 Оставлено : 12 января 2016 г. 20:02:20(UTC)
[ETC]Dema

Статус: Новичок

Группы: Участники
Зарегистрирован: 12.01.2016(UTC)
Сообщений: 4

Есть следующий код:

Код:
byte[] encodedCMS	= ...GetBytes...;
var cms				= new SignedCms();
try
{
	// Распаковка - нет проблем
	cms.Decode(encodedCMS);
	// Проверяем количество подписей - нет проблем, SignerInfos.Count == 1
	if (cms.SignerInfos.Count!=1)
		throw new Exception("Количество подписавших не равно 1");

	// Получаем данные сертификата из подписи
	var si			= cms.SignerInfos[0];
	var sid			= si.SignerIdentifier; // Нет проблем, данные правильные:
	// sid.IssuerName 		= [CN=УЦ Федерального казначейства, O=Федеральное казначейство, C=RU, L=Москва, STREET="улица Ильинка, дом 7", ОГРН=1047797019830, ИНН=007710568760, S=77 г. Москва, E=uc_fk@roskazna.ru, OID.1.2.840.113549.1.9.2=Server CA]
	// sid.SerialNumber		= [0BFE1D]
	
	var cert		= si.Certificate; // ПРОБЛЕМА:
	// cert.SubjectName		= [CN=УЦ Федерального казначейства, O=Федеральное казначейство, C=RU, L=Москва, STREET="улица Ильинка, дом 7", ОГРН=1047797019830, ИНН=007710568760, S=77 г. Москва, E=uc_fk@roskazna.ru, OID.1.2.840.113549.1.9.2=Server CA]
	// cert.SerialNumber	= [01]
	// cert.IssuerName 		= [CN=УЦ Федерального казначейства, O=Федеральное казначейство, C=RU, L=Москва, STREET="улица Ильинка, дом 7", ОГРН=1047797019830, ИНН=007710568760, S=77 г. Москва, E=uc_fk@roskazna.ru, OID.1.2.840.113549.1.9.2=Server CA]

	// Проверка подписи - ПРОБЛЕМА: HRes(-2146889714) Не найден автор исходной подписи.\r\n
	cms.CheckSignature(true);
} catch (Exception ex)
{
	// Получаем ошибку:
	// HRes(-2146889714) Не найден автор исходной подписи.\r\n
	//	в System.Security.Cryptography.Pkcs.SignerInfo.Verify(X509Certificate2Collection extraStore, X509Certificate2 certificate, Boolean verifySignatureOnly)
	//	в System.Security.Cryptography.Pkcs.SignerInfo.CheckSignature(X509Certificate2Collection extraStore, Boolean verifySignatureOnly)
	//	в System.Security.Cryptography.Pkcs.SignedCms.CheckSignatures(SignerInfoCollection signers, X509Certificate2Collection extraStore, Boolean verifySignatureOnly)
	//	в System.Security.Cryptography.Pkcs.SignedCms.CheckSignature(X509Certificate2Collection extraStore, Boolean verifySignatureOnly)
	//	в System.Security.Cryptography.Pkcs.SignedCms.CheckSignature(Boolean verifySignatureOnly)
}


При детальной отладке выяснилось, что проблема в si.Certificate - вместо сертификата, который использовался для подписи получаем сертификат УЦ.

При изучении исходников .Net видим, что поиск сертификата при обращении к System.Security.Cryptography.Pkcs.SignerInfo.Certificate идет следующим образом:

Код:
public X509Certificate2 Certificate
{
  get
  {
    if (this.m_certificate == null)
      this.m_certificate = PkcsUtils.FindCertificate(this.SignerIdentifier, this.m_signedCms.Certificates);
    return this.m_certificate;
  }
}


Смотрим System.Security.Cryptography.Pkcs.PkcsUtils.FindCertificate:

Код:
internal static X509Certificate2 FindCertificate(SubjectIdentifier identifier, X509Certificate2Collection certificates)
{
  X509Certificate2 x509Certificate2 = (X509Certificate2) null;
  if (certificates != null && certificates.Count > 0)
  {
	switch (identifier.Type)
	{
	  case SubjectIdentifierType.IssuerAndSerialNumber:
		X509Certificate2Collection certificate2Collection1 = certificates.Find(X509FindType.FindByIssuerDistinguishedName, (object) ((X509IssuerSerial) identifier.Value).IssuerName, false);
		if (certificate2Collection1.Count > 0)
		{
		  X509Certificate2Collection certificate2Collection2 = certificate2Collection1.Find(X509FindType.FindBySerialNumber, (object) ((X509IssuerSerial) identifier.Value).SerialNumber, false);
		  if (certificate2Collection2.Count > 0)
		  {
			x509Certificate2 = certificate2Collection2[0];
			break;
		  }
		  break;
		}
		break;
	  case SubjectIdentifierType.SubjectKeyIdentifier:
		X509Certificate2Collection certificate2Collection3 = certificates.Find(X509FindType.FindBySubjectKeyIdentifier, identifier.Value, false);
		if (certificate2Collection3.Count > 0)
		{
		  x509Certificate2 = certificate2Collection3[0];
		  break;
		}
		break;
	}
  }
  return x509Certificate2;
}


Проверяем cms.SignerInfos[0].SignerIdentifier и видим корректные данные:

Код:

IssuerName 		= [CN=УЦ Федерального казначейства, O=Федеральное казначейство, C=RU, L=Москва, STREET="улица Ильинка, дом 7", ОГРН=1047797019830, ИНН=007710568760, S=77 г. Москва, E=uc_fk@roskazna.ru, OID.1.2.840.113549.1.9.2=Server CA]
SerialNumber	= [0BFE1D]


Проверяем cms.Certificates и видим 2 сертификата:

Код:
[0].SubjectName		= [CN=УЦ Федерального казначейства, O=Федеральное казначейство, C=RU, L=Москва, STREET="улица Ильинка, дом 7", ОГРН=1047797019830, ИНН=007710568760, S=77 г. Москва, E=uc_fk@roskazna.ru, OID.1.2.840.113549.1.9.2=Server CA]
[0].SerialNumber	= [01]
[0].IssuerName 		= [CN=УЦ Федерального казначейства, O=Федеральное казначейство, C=RU, L=Москва, STREET="улица Ильинка, дом 7", ОГРН=1047797019830, ИНН=007710568760, S=77 г. Москва, E=uc_fk@roskazna.ru, OID.1.2.840.113549.1.9.2=Server CA]

[1].SubjectName		= [CN=Бунеев Валерий Леонтьевич, OID.1.2.840.113549.1.9.2=1.2.643.3.61.1.1.6.502710.3.4.2.1, T=Председатель, SN=Бунеев, G=Валерий Леонтьевич, OU=Руководство, O=Избирательная комиссия го Воронеж, L=Воронеж, S=36 Воронежская область, C=RU, E=vgik227241@yandex.ru, ОГРН=1073668014056, СНИЛС=05943640481, ИНН=3666150124]
[1].SerialNumber	= [0BFE1D]
[1].IssuerName 		= [CN=УЦ Федерального казначейства, O=Федеральное казначейство, C=RU, L=Москва, STREET="улица Ильинка, дом 7", ОГРН=1047797019830, ИНН=007710568760, S=77 г. Москва, E=uc_fk@roskazna.ru, OID.1.2.840.113549.1.9.2=Server CA]


Вся информация корректна.

Эмулируем поиск сертификата System.Security.Cryptography.Pkcs.PkcsUtils.FindCertificate:

Код:

var res = cms.Certificates
   .Find(X509FindType.FindByIssuerDistinguishedName, (object) ((System.Security.Cryptography.Xml.X509IssuerSerial) cms.SignerInfos[0].SignerIdentifier.Value).IssuerName, false)
   .Find(X509FindType.FindBySerialNumber, (object) ((System.Security.Cryptography.Xml.X509IssuerSerial) cms.SignerInfos[0].SignerIdentifier.Value).SerialNumber, false);


получаем res.Count = 2:

Код:
[0].SubjectName		= [CN=УЦ Федерального казначейства, O=Федеральное казначейство, C=RU, L=Москва, STREET="улица Ильинка, дом 7", ОГРН=1047797019830, ИНН=007710568760, S=77 г. Москва, E=uc_fk@roskazna.ru, OID.1.2.840.113549.1.9.2=Server CA]
[0].SerialNumber	= [01]
[0].IssuerName 		= [CN=УЦ Федерального казначейства, O=Федеральное казначейство, C=RU, L=Москва, STREET="улица Ильинка, дом 7", ОГРН=1047797019830, ИНН=007710568760, S=77 г. Москва, E=uc_fk@roskazna.ru, OID.1.2.840.113549.1.9.2=Server CA]

[1].SubjectName		= [CN=Бунеев Валерий Леонтьевич, OID.1.2.840.113549.1.9.2=1.2.643.3.61.1.1.6.502710.3.4.2.1, T=Председатель, SN=Бунеев, G=Валерий Леонтьевич, OU=Руководство, O=Избирательная комиссия го Воронеж, L=Воронеж, S=36 Воронежская область, C=RU, E=vgik227241@yandex.ru, ОГРН=1073668014056, СНИЛС=05943640481, ИНН=3666150124]
[1].SerialNumber	= [0BFE1D]
[1].IssuerName 		= [CN=УЦ Федерального казначейства, O=Федеральное казначейство, C=RU, L=Москва, STREET="улица Ильинка, дом 7", ОГРН=1047797019830, ИНН=007710568760, S=77 г. Москва, E=uc_fk@roskazna.ru, OID.1.2.840.113549.1.9.2=Server CA]


что и приводит к проблеме с var cert = si.Certificate.

Данная проблема проявляется с несколькими сертификатами, выданными с использованием именно этого сертификата УЦФК, с остальными сертификатами УЦ такой проблемы не наблюдается. Установлен CSP 4.0 + CryptoPro.NET, ЭП была создана с использованием BrowserPlugin (код взят с тестовой страницы сайта), утилиты проверки ЭП показывают правильный сертификат создателя подписи.

Так как из .NET вызовы на получение данных сертификатов и вызов методов поиска уходит в неуправляемый код, то самостоятельно добраться до проблемы не получается, проблема наблюдается только с ГОСТ сертификатами.

В чем и где может быть проблема и как можно решить данную проблему?

В файле data.zip (23kb) загружен 4 раз(а).:

  • base64EncodedCMS.cms - проверяемая ЭП
  • osinfo.xml - данные о системе
  • user.cer - открытая часть сертификата пользователя, создавшего ЭП
  • УЦФК.cer - открытая часть сертификата УЦ

Отредактировано пользователем 12 января 2016 г. 20:26:47(UTC)  | Причина: Не указана

Offline Павел Смирнов  
#2 Оставлено : 14 января 2016 г. 15:16:21(UTC)
Павел Смирнов

Статус: Вам и не снилось

Группы: Администраторы
Зарегистрирован: 24.12.2007(UTC)
Сообщений: 831
Откуда: Крипто-Про

Сказал(а) «Спасибо»: 1 раз
Поблагодарили: 48 раз в 44 постах
КриптоПро CSP и КриптоПро .NET здесь ни при чём. Вы нашли феерическую багу в .NET Framework.

Смотрим реализацию X509CertificateCollection.Find(X509FindType.FindBySerialNumber):
Код:

...
SafeCertStoreHandle safeTargetStoreHandle = FindCertInStore(safeSourceStoreHandle, findType, findValue, validOnly);
...

Смотрим FindCertInStore:
Код:

...
            case X509FindType.FindBySerialNumber:
                if (findValue.GetType() != typeof(string))
                    throw new CryptographicException(SR.GetString(SR.Cryptography_X509_InvalidFindValue));
                pfnCertCallback1 = new FindProcDelegate(FindSerialNumberCallback);
                pfnCertCallback2 = new FindProcDelegate(FindSerialNumberCallback);
                BigInt h = new BigInt();
                h.FromHexadecimal((string) findValue);
                pvCallbackData1 = (byte[]) h.ToByteArray();
                h.FromDecimal((string) findValue);
                pvCallbackData2 = (byte[]) h.ToByteArray();
                break;
...
 FindByCert(safeSourceStoreHandle, 
                       dwFindType,
                       pvFindPara, 
                       validOnly, 
                       pfnCertCallback1,
                       pfnCertCallback2, 
                       pvCallbackData1,
                       pvCallbackData2, 
                       safeTargetStoreHandle);

FindByCert в итоге для каждого сертификата в хранилище вызывает FindSerialNumberCallback сначала с pvCallbackData1, потом, если сертификат не подошёл, то с pvCallbackData2. FindSerialNumberCallback просто сравнивает серийный номера сертификата побайтово с переданным массивом. Правильный сертификат совпадает по pvCallbackData1, неправильный - по pvCallbackData2, т.к. BigInt.FromDecimal(string) просто пропускает шестнадцатеричные символы, из-за чего "0BFE1D" превращается в byte[] = { 1 }.
Техническую поддержку оказываем тут.
Наша база знаний.
thanks 1 пользователь поблагодарил Павел Смирнов за этот пост.
Андрей * оставлено 14.01.2016(UTC)
Offline [ETC]Dema  
#3 Оставлено : 14 января 2016 г. 15:29:22(UTC)
[ETC]Dema

Статус: Новичок

Группы: Участники
Зарегистрирован: 12.01.2016(UTC)
Сообщений: 4

Вот как повезло-то :) получается такая бага будет со всеми сертификатами УЦ, где серийный номер = 1. Вот повезло-то с сертификатом УЦ ФК. Самое плохое, что нельзя пропатчить.

Спасибо за ответ - я просматривал указанные Вами исходники, но не обратил внимание на преобразования.

Отредактировано пользователем 14 января 2016 г. 15:30:24(UTC)  | Причина: Не указана

Offline Павел Смирнов  
#4 Оставлено : 14 января 2016 г. 15:31:46(UTC)
Павел Смирнов

Статус: Вам и не снилось

Группы: Администраторы
Зарегистрирован: 24.12.2007(UTC)
Сообщений: 831
Откуда: Крипто-Про

Сказал(а) «Спасибо»: 1 раз
Поблагодарили: 48 раз в 44 постах
Для обхода проблемы можно, например, через reflection записать правильный сертификат в m_certificate в SignerInfo.

Отредактировано пользователем 14 января 2016 г. 15:33:07(UTC)  | Причина: Не указана

Техническую поддержку оказываем тут.
Наша база знаний.
Offline [ETC]Dema  
#5 Оставлено : 14 января 2016 г. 19:12:28(UTC)
[ETC]Dema

Статус: Новичок

Группы: Участники
Зарегистрирован: 12.01.2016(UTC)
Сообщений: 4

Была такая мысль, сделал подмену через reflection, сертификат теперь нормальный получаю, а вот при проверке cms.CheckSignature(true); все равно получаю ошибку 0x8009100E: "Не найден автор исходной подписи." где-то глубоко в методе Verify, подозрение падает на http://referencesource.m...hy/pkcs/pkcsutils.cs,279 где и встречается как раз генерация CRYPT_E_SIGNER_NOT_FOUND, но опять же там неуправляемый код.

Пропатчить проверку видимо все-таки не получиться, к сожалению.

На всякий случай метод установки правильного сертификата:

Код:
public static void FixNETBug(SignerInfo si, X509Certificate2Collection certs)
{
	// Обход бага с поиском сертификата подписавшего в случае, когда:
	// SerialNumber сертификата УЦ содержит цифры
	// и
	// SerialNumber сертификата подписавшего при выполении BigInt.FromDecimal дает соответствие SerialNumber сертификата УЦ
	// см. http://referencesource.microsoft.com/#System/security/system/security/cryptography/x509/x509certificate2collection.cs,847422362bfb0c64 
	// далее в коде с "case X509FindType.FindBySerialNumber:"
	if (si==null)
		return; // Проверять нечего

	var sid				= si.SignerIdentifier;
	if (sid.Type!=SubjectIdentifierType.IssuerAndSerialNumber)
		return; // Если поиск не соответствует нужному - выходим.

	var cert			= si.Certificate;
	var val				= (X509IssuerSerial) sid.Value;
	if (cert.SerialNumber==val.SerialNumber && cert.IssuerName.Name==val.IssuerName)
		return; // Если серийный номер и издатель совпадают с ожидаемым - все хорошо.

	// Начинаем исправлять проблему:
	// 1. Найдем нужный сертификат в коллекции сертификатов из ЭП
	cert				= null;
	foreach (var c in certs)
	{
		if (c.SerialNumber==val.SerialNumber && c.IssuerName.Name==val.IssuerName)
		{
			cert		= c;
			break;
		}	
	}

	if (cert==null)
		throw new KeyNotFoundException("Not found certificate (SerialNumber==\""+val.SerialNumber+"\" && IssuerName==\""+val.IssuerName+"\") in X509Certificate2Collection");

	// 2. Хирургическим путем меняем сертификат
	var fi				= typeof(SignerInfo).GetField("m_certificate", BindingFlags.Instance | BindingFlags.NonPublic);
	fi.SetValue(si, cert);
}


Вероятно тут будет проблема с cert.IssuerName.Name==val.IssuerName и c.IssuerName.Name==val.IssuerName, но все равно это неправильно (нужно исправлять в .NET) и не приводит к корректной проверки. ЭП через "КриптоАРМ 4" проверяется без проблем и ошибок. Видимо проблема есть где-то в глубине CryptoAPI Microsoft.
Offline Павел Смирнов  
#6 Оставлено : 14 января 2016 г. 20:09:57(UTC)
Павел Смирнов

Статус: Вам и не снилось

Группы: Администраторы
Зарегистрирован: 24.12.2007(UTC)
Сообщений: 831
Откуда: Крипто-Про

Сказал(а) «Спасибо»: 1 раз
Поблагодарили: 48 раз в 44 постах
CryptoAPI работает штатно, т.к., например, certutil проверяет такую подпись. Выложите законченный пример с вашим фиксом, мы посмотрим в чём дело.
Техническую поддержку оказываем тут.
Наша база знаний.
Offline [ETC]Dema  
#7 Оставлено : 14 января 2016 г. 20:18:38(UTC)
[ETC]Dema

Статус: Новичок

Группы: Участники
Зарегистрирован: 12.01.2016(UTC)
Сообщений: 4

Понятно, что проблема на стороне .NET и получилось полностью побороть проблему.

При вызове cms.CheckSignature(true); идет обращение к cms.SignerInfos, где можно увидеть, что коллекция при каждом обращении создается заново:

http://referencesource.m...3203146d42672,references

Код:
CheckSignatures(this.SignerInfos, extraStore, verifySignatureOnly);


http://referencesource.m...140b786126098,references
Код:

        public SignerInfoCollection SignerInfos {
            [SecuritySafeCritical]
            get {
                if (m_safeCryptMsgHandle == null || m_safeCryptMsgHandle.IsInvalid) {
                    return new SignerInfoCollection();
                }
 
                return new SignerInfoCollection(this);
            }
        }


Таким образом результат работы FixNETBug сводится на нет. Но нам повезло! Реализация CheckSignatures достаточно проста и можно ее повторить, применив FixNETBug:


Код:

private static void FixNETBugCheckSignature(SignerInfoCollection signers, X509Certificate2Collection extraStore, bool verifySignatureOnly)
{
	// Несмотря на выполнение метода FixNETBug при вызове cms.CheckSignature(true); будет создана еще одна коллекция 
	// SignerInfoCollection, см. реализацию http://referencesource.microsoft.com/#System.Security/system/security/cryptography/pkcs/signedpkcs7.cs,115
	// Где опять будет неправильный сертификат, поэтому проверкой займемся сами.
	// Код взят из http://referencesource.microsoft.com/#System.Security/system/security/cryptography/pkcs/signedpkcs7.cs,438

	foreach (SignerInfo signer in signers)
	{
		FixNETBug(signer, extraStore); // В CounterSignerInfos тоже каждый раз создается новая коллекция - опять нужно проверять
		signer.CheckSignature(extraStore, verifySignatureOnly);

		if (signer.CounterSignerInfos.Count > 0)
		{
			FixNETBugCheckSignature(signer.CounterSignerInfos, extraStore, verifySignatureOnly);
		}
	}
}


Остается только соблюдать несколько правил:


  1. Минимизировать вызовы SignerInfos и CounterSignerInfos
  2. Перед работой с элементами этих коллекций пропускать их через FixNETBug
  3. Место cms.CheckSignature использовать FixNETBugCheckSignature так:
    Код:
    FixNETBugCheckSignature(cms.SignerInfos, cms.Certificates, [true|false]);



ВНИМАНИЕ! Ровно такая же проблема возможна с методом CheckHash:
http://referencesource.m.../pkcs/signedpkcs7.cs,265

Код:
        public void CheckHash () {
 
            if (m_safeCryptMsgHandle == null || m_safeCryptMsgHandle.IsInvalid)
                throw new InvalidOperationException(
                    SecurityResources.GetResourceString("Cryptography_Cms_MessageNotSigned"));
 
            CheckHashes(this.SignerInfos);
        }


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

Остается или написать в Microsoft или сделать статик класс для обхода ошибок.

Отредактировано пользователем 14 января 2016 г. 20:19:47(UTC)  | Причина: Не указана

RSS Лента  Atom Лента
Пользователи, просматривающие эту тему
Guest
Быстрый переход  
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.