Приветствую!
Пишу сервис обмена данными со СМЭВ 3.
Транспортный уровень написал без всяких проблем, благо СМЭВ сервис работает даже на тестовом стенде нормально.
но что касается подписи документа и передачи уже подписанного документа, это кромешный кошмар!!! =(((
Скачал для тестов КриптоПро .NET и признаться удручает жуткая реализация обертки, чтобы понять в чем проблема прохождения валидации, нужно просто с Бубнами
танцевать. в итоге я так проблему и не решил..
Перед вопросом приведу код, так будет проще понять тем, кто через эти шаманства проходил:
Собственно Код:
Код:
//--------------------------------------------------------------------
// получение сертификата из хранилища
//--------------------------------------------------------------------
private static CpX509Certificate2 GetCert(string certName)
{
error = "";
try
{
using (var store = new CpX509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadOnly);
var cert = store.Certificates.Find(X509FindType.FindByIssuerName, certName, false);
if (cert != null && cert.Count > 0)
return cert[0];
}
}catch (Exception ex) { error = ex.Message; }
return null;
}
//--------------------------------------------------------------------
// Генерация раздела с подписью для конверта CМЭВ
//--------------------------------------------------------------------
public static XmlElement CreateSign(XmlDocument BodyForSign, string certName, string uri = "", string crtPass = "")
{
error = "";
try
{
CpX509Certificate2 Certificate = GetCert(certName);
if (Certificate != null)
{
Gost3410_2012_256CryptoServiceProvider pKey = (Gost3410_2012_256CryptoServiceProvider)Certificate.PrivateKey;
if (pKey != null)
{
if (!string.IsNullOrEmpty(crtPass))
pKey.SetContainerPassword(String2SecureString(crtPass));
CpSignedXml signedXml = new CpSignedXml(BodyForSign);
if (signedXml != null)
{
signedXml.SigningKey = pKey;
signedXml.SafeCanonicalizationMethods.Add("urn://smev-gov-ru/xmldsig/transform");
var reference = new CpReference
{
Uri = string.IsNullOrEmpty(uri) ? "" : $"#{uri}",
DigestMethod = CpSignedXml.XmlDsigGost3411_2012_256Url
};
signedXml.AddReference(reference);
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
signedXml.SignedInfo.SignatureMethod = CpSignedXml.XmlDsigGost3410_2012_256Url;
signedXml.KeyInfo.AddClause(new CpKeyInfoX509Data(Certificate));
reference.AddTransform(new CpXmlDsigExcC14NTransform());
reference.AddTransform(new XmlDsigSmevTransform());
signedXml.ComputeSignature();
return signedXml.GetXml();
}
}
else
error = "[Error] - Private Key not contained in Cert";
}
}
catch (Exception ex)
{
error = ex.Message;
}
return null;
}
//--------------------------------------------------------------------
// Верификация подписи (для проверки что подписанный документ проходит проверку)
//--------------------------------------------------------------------
public static bool VerifySign(XmlDocument xmlDoc)
{
error = "";
try
{
CpSignedXml signedXml = new CpSignedXml(xmlDoc);
if (signedXml != null)
{
signedXml.SafeCanonicalizationMethods.Add("urn://smev-gov-ru/xmldsig/transform");
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
if (nodeList != null && nodeList.Count > 0)
{
XmlNodeList certificates = xmlDoc.GetElementsByTagName("X509Certificate");
if (certificates != null && certificates.Count > 0)
{
CpX509Certificate2 certFromSignedXML = new CpX509Certificate2(Convert.FromBase64String(certificates[0].InnerText));
if (certFromSignedXML != null)
{
foreach (XmlElement element in nodeList)
{
signedXml.LoadXml(element);
bool passes = signedXml.CheckSignature(certFromSignedXML, true);
return passes;
}
} else { error = "[ERROR] - Get and decode base64 Certificate from signed XML"; }
} else { error = "[ERROR] - Get Certificate from X509Certificate tag"; }
} else { error = "[ERROR] - Signature tag not found in signed XML"; }
} else { error = "[ERROR] - Create Signed XML object"; }
}
catch (Exception ex)
{
error = ex.Message;
}
return false;
}
Дьявольщина при проверке происходит вот тут - "CpSignedXml signedXml = new CpSignedXml(xmlDoc);"
Если я передаю xmlDoc (XmlDocument) сразу же после подписи в качестве аргумента конструктора CpSignedXml,
то ВСЕ отлично!
CheckSignature возвращает -
TRUE!
Но если передавать: xmlDoc.InnerXml, xmlDoc.OuterXml, причем перед подписью или после, включая-выключая параметр - PreserveWhitespace.
Передача осуществляется через промежуточные сохранения в файл и последующее чтение или создание нового объекта XmlDocument и загрузку через .LoadXml(InnerXml).
Метод
CheckSignature всегда возвращает -
FALSE.
Я пробовал удалять пробелы и всякие служебные символы и символы переноса программно, переводить в разные кодировки, в одну строку сохранять XML, с переносами, через массивы байтов шаманить, мудрить со Stream, даже на С++ обертки писал...
НИЧЕГО НЕ ПОМОГАЕТ!
Самое плохое, что невозможно посмотреть что делает класс CpSignedXml с XmlDocument в своем конструкторе, берет InnerXml и что-то
делает с самим объектом XmlDocument на уровне парсинга Нод.
Что добавляет или удаляет из текста XML перед генерацией подписи?
Из-за этого СМЭВ меня отплевывает постоянно с воплями - "16006 : SMEV-100: ЭП-ОВ не соответствует подписанным данным: Нарушена целостность ЭП"
Собственно вопрос - Куда рыть-то? Явно проблемы с тем что подпись генерится по разделу InnerText, но он преобразовывается в CpSignedXml, что-то добавляется или
удаляется или приводится к какому-то формату... В итоге есть отличия в структуре текста раздела XML на основание которого создана подпись.
P.S. Уже просто неясно что в XML нужно изменить, чтобы попасть в формат который использовался при генерации ЭЦП.