Генерация цифровой подписи, но с определенным префиксом пространства имен ("ds:")
Я подписываю файлы XML цифровой подписью, но мне нужно, чтобы теги подписи содержали префикс пространства имен "ds". Я исследовал довольно Google и нашел много таких же вопросов, но не получил удовлетворительного ответа.
Я попытался поместить "ds" вручную в файл, но подпись становится недействительной. Тег "SignatureValue" подписывает тег "SignedInfo", поэтому подпись становится недействительной.
Может кто-нибудь показать мне, как я генерирую значение тега "SignatureValue", чтобы я мог заменить подпись после добавления префикса "ds"?
3 ответа
Видимо, много людей столкнулись с той же проблемой. Изучив исходный код класса Signature, я пришел к выводу, что Microsoft стремилась нам помочь. В методе LoadXml() есть жестко заданный префикс "ds". Таким образом, можно сгенерировать подпись, затем добавить к ней префикс пространства имен "ds", загрузить обратно измененную подпись и пересчитать "SignatureValue". К сожалению, ошибка в библиотеке делает вещи немного сложнее, чем они должны быть. Код с обходным решением и комментариями ниже.
public static void SignXml(XmlDocument xmlDoc, X509Certificate2 cert)
{
// transformation cert -> key omitted
RSACryptoServiceProvider key;
// Create a SignedXml object.
SignedXml signedXml = new SignedXml(xmlDoc);
// Add the key to the SignedXml document.
signedXml.SigningKey = key;
signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
// Create a reference to be signed.
Reference reference = new Reference();
reference.Uri = "#foo";
reference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
// Add an enveloped transformation to the reference.
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
signedXml.AddReference(reference);
KeyInfo keyInfo = new KeyInfo();
KeyInfoX509Data keyInfoData = new KeyInfoX509Data();
keyInfoData.AddIssuerSerial(cert.IssuerName.Format(false), cert.SerialNumber);
keyInfo.AddClause(keyInfoData);
signedXml.KeyInfo = keyInfo;
// Compute the signature.
signedXml.ComputeSignature();
// Add prefix "ds:" to signature
XmlElement signature = signedXml.GetXml();
SetPrefix("ds", signature);
// Load modified signature back
signedXml.LoadXml(signature);
// this is workaround for overcoming a bug in the library
signedXml.SignedInfo.References.Clear();
// Recompute the signature
signedXml.ComputeSignature();
string recomputedSignature = Convert.ToBase64String(signedXml.SignatureValue);
// Replace value of the signature with recomputed one
ReplaceSignature(signature, recomputedSignature);
// Append the signature to the XML document.
xmlDoc.DocumentElement.InsertAfter(xmlDoc.ImportNode(signature, true), xmlDoc.DocumentElement.FirstChild);
}
private static void SetPrefix(string prefix, XmlNode node)
{
node.Prefix = prefix;
foreach (XmlNode n in node.ChildNodes)
{
SetPrefix(prefix, n);
}
}
private static void ReplaceSignature(XmlElement signature, string newValue)
{
if (signature == null) throw new ArgumentNullException(nameof(signature));
if (signature.OwnerDocument == null) throw new ArgumentException("No owner document", nameof(signature));
XmlNamespaceManager nsm = new XmlNamespaceManager(signature.OwnerDocument.NameTable);
nsm.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl);
XmlNode signatureValue = signature.SelectSingleNode("ds:SignatureValue", nsm);
if (signatureValue == null)
throw new Exception("Signature does not contain 'ds:SignatureValue'");
signatureValue.InnerXml = newValue;
}
Редактировать: Вы можете увидеть алгоритм, который этот пост вроде как упоминается в моем другом ответе.
Предполагая, что невозможно без написания собственного алгоритма канонизации и подписи документа, возможный обходной путь может заключаться в том, чтобы "вставить" префикс пространства имен в элементы подписи после подписания, а затем удалить его из них перед проверкой.
Например:
void SignXml(XmlDocument xmlDoc, RSA Key)
{
SignedXml signedXml = new SignedXml(xmlDoc);
signedXml.SigningKey = Key;
Reference reference = new Reference("");
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
signedXml.AddReference(reference);
signedXml.ComputeSignature();
XmlElement xmlSignature = signedXml.GetXml();
//Here we set the namespace prefix on the signature element and all child elements to "ds", invalidating the signature.
AssignNameSpacePrefixToElementTree(xmlSignature, "ds");
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlSignature, true));
}
bool VerifyXmlSignature(XmlDocument xmlDoc, RSA Key, string prefix)
{
SignedXml signedXml = new SignedXml(xmlDoc);
//Get the <ds:Signature /> element
XmlElement xmlSignature = (XmlElement)xmlDoc.GetElementsByTagName(prefix + ":Signature")[0];
//Undo what we did after signing
AssignNameSpacePrefixToElementTree(xmlSignature, "");
//Now it will pass verification.
signedXml.LoadXml(xmlSignature);
return signedXml.CheckSignature(Key);
}
void AssignNameSpacePrefixToElementTree(XmlElement element, string prefix)
{
element.Prefix = prefix;
foreach (var child in element.ChildNodes)
{
if (child is XmlElement)
AssignNameSpacePrefixToElementTree(child as XmlElement, prefix);
}
}
Поскольку именно вы подписываете документы, это должно быть достаточно просто, но классы в System.Security.Cryptography.Xml действительно не поддерживают это.
Если вам нужен только элемент Signature с префиксом, то при условии, что подпись не указана сама по себе (или если она является частью ссылочного элемента, тогда, если она удалена с преобразованием, как в " http://www.w3.org/2000/09/xmldsig") тогда все, что вам нужно сделать, это пересчитать SignatureValue на основе вашего измененного элемента SignedInfo. См. Ниже метод SignEnveloped для примера.
Однако ваша подпись не пройдет проверку, описанную в MSDN Как: проверить цифровые подписи документов XML, потому что вместо вычисления SignatureValue для проверки путем фактического чтения SignedInfo документа, класс SignedXml, по-видимому, создает новую без префиксные элементы. Приведенный ниже класс работает вокруг, казалось бы, ошибочных реализаций SignedXmls, но могут быть и проблемы с проверкой в других средах, не ожидающих префиксных элементов.
public static class XmlSigning
{
private static Type tSignedXml = typeof(SignedXml);
private static ResourceManager SecurityResources = new ResourceManager("system.security", tSignedXml.Assembly);
//these methods from the SignedXml class still work with prefixed Signature elements, but they are private
private static ParameterExpression thisSignedXmlParam = Expression.Parameter(tSignedXml);
private static Func<SignedXml, bool> CheckSignatureFormat
= Expression.Lambda<Func<SignedXml, bool>>(
Expression.Call(thisSignedXmlParam, tSignedXml.GetMethod("CheckSignatureFormat", BindingFlags.NonPublic | BindingFlags.Instance)),
thisSignedXmlParam).Compile();
private static Func<SignedXml, bool> CheckDigestedReferences
= Expression.Lambda<Func<SignedXml, bool>>(
Expression.Call(thisSignedXmlParam, tSignedXml.GetMethod("CheckDigestedReferences", BindingFlags.NonPublic | BindingFlags.Instance)),
thisSignedXmlParam).Compile();
public static void SignEnveloped(XmlDocument xmlDoc, RSACryptoServiceProvider key, string signatureNamespacePrefix)
{
SignedXml signedXml = new SignedXml(xmlDoc);
signedXml.SigningKey = key;
Reference reference = new Reference("");
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
signedXml.AddReference(reference);
signedXml.ComputeSignature();
XmlElement xmlSignature = signedXml.GetXml();
if (!string.IsNullOrEmpty(signatureNamespacePrefix))
{
//Here we set the namespace prefix on the signature element and all child elements to "ds", invalidating the signature.
AssignNameSpacePrefixToElementTree(xmlSignature, "ds");
//So let's recompute the SignatureValue based on our new SignatureInfo...
//For XPath
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
namespaceManager.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); //this prefix is arbitrary and used only for XPath
XmlElement xmlSignedInfo = xmlSignature.SelectSingleNode("ds:SignedInfo", namespaceManager) as XmlElement;
//Canonicalize the SignedInfo element
XmlDsigC14NTransform transform = new XmlDsigC14NTransform();
XmlDocument signedInfoDoc = new XmlDocument();
signedInfoDoc.LoadXml(xmlSignedInfo.OuterXml);
transform.LoadInput(signedInfoDoc);
//Compute the new SignatureValue
string signatureValue = Convert.ToBase64String(key.SignData(transform.GetOutput() as MemoryStream, new SHA1CryptoServiceProvider()));
//Set it in the xml
XmlElement xmlSignatureValue = xmlSignature.SelectSingleNode("ds:SignatureValue", namespaceManager) as XmlElement;
xmlSignatureValue.InnerText = signatureValue;
}
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlSignature, true));
}
public static bool CheckSignature(XmlDocument xmlDoc, RSACryptoServiceProvider key)
{
if (key == null)
throw new ArgumentNullException("key");
SignedXml signedXml = new SignedXml(xmlDoc);
//For XPath
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
namespaceManager.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); //this prefix is arbitrary and used only for XPath
XmlElement xmlSignature = xmlDoc.SelectSingleNode("//ds:Signature", namespaceManager) as XmlElement;
signedXml.LoadXml(xmlSignature);
//These are the three methods called in SignedXml's CheckSignature method, but the built-in CheckSignedInfo will not validate prefixed Signature elements
return CheckSignatureFormat(signedXml) && CheckDigestedReferences(signedXml) && CheckSignedInfo(signedXml, key);
}
private static bool CheckSignedInfo(SignedXml signedXml, AsymmetricAlgorithm key)
{
//Copied from reflected System.Security.Cryptography.Xml.SignedXml
SignatureDescription signatureDescription = CryptoConfig.CreateFromName(signedXml.SignatureMethod) as SignatureDescription;
if (signatureDescription == null)
throw new CryptographicException(SecurityResources.GetString("Cryptography_Xml_SignatureDescriptionNotCreated"));
Type type = Type.GetType(signatureDescription.KeyAlgorithm);
Type type2 = key.GetType();
if (type != type2 && !type.IsSubclassOf(type2) && !type2.IsSubclassOf(type))
return false;
HashAlgorithm hashAlgorithm = signatureDescription.CreateDigest();
if (hashAlgorithm == null)
throw new CryptographicException(SecurityResources.GetString("Cryptography_Xml_CreateHashAlgorithmFailed"));
//Except this. The SignedXml class creates and cananicalizes a Signature element without any prefix, rather than using the element from the document provided
byte[] c14NDigest = GetC14NDigest(signedXml, hashAlgorithm);
AsymmetricSignatureDeformatter asymmetricSignatureDeformatter = signatureDescription.CreateDeformatter(key);
return asymmetricSignatureDeformatter.VerifySignature(c14NDigest, signedXml.Signature.SignatureValue);
}
private static byte[] GetC14NDigest(SignedXml signedXml, HashAlgorithm hashAlgorithm)
{
Transform canonicalizeTransform = signedXml.SignedInfo.CanonicalizationMethodObject;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(signedXml.SignedInfo.GetXml().OuterXml);
canonicalizeTransform.LoadInput(xmlDoc);
return canonicalizeTransform.GetDigestedOutput(hashAlgorithm);
}
private static void AssignNameSpacePrefixToElementTree(XmlElement element, string prefix)
{
element.Prefix = prefix;
foreach (var child in element.ChildNodes)
{
if (child is XmlElement)
AssignNameSpacePrefixToElementTree(child as XmlElement, prefix);
}
}
}