2015-06-01 5 views
12

Ich signiere XML-Dateien digital, aber die Signatur-Tags müssen das Namespacepräfix "ds" enthalten. Ich recherchierte ziemlich Google und fand viele der gleichen Fragen, aber keine befriedigende Antwort.Generieren Sie digitale Signatur aber mit einem spezifischen Namespace Prefix ("ds:")

Ich habe versucht, die "ds" manuell in die Datei zu setzen, aber die Signatur wird ungültig. Das Tag "SignatureValue" signiert das Tag "SignedInfo", so dass die Signatur ungültig wird.

Könnte mir jemand zeigen, wie ich den Wert des Tags "SignatureValue" erzeuge, damit ich die Signatur ersetzen kann, nachdem ich das Präfix "ds" hinzugefügt habe?

+6

Das es wieder in den Blick treten sollte :) –

+0

Beachten Sie, dass die Unterschrift auf der * canonicalized platziert * Version des Klartextes (das XML-Element, wo die Signatur auf platziert ist). Sie benötigen den privaten Schlüssel zum Generieren der Signatur, sodass Sie den Signaturwert nicht selbst ersetzen können. Der Trick wäre, den Namespace "ds" einzufügen, ohne die kanonische Repräsentation zu verändern, so dass die Signatur gleich bleibt. –

+0

Ich denke, ich persönlich löste das, indem ich an die Benutzer eines MS BizTalk-Servers schriebe, um Software zu verwenden, die standardisierte Signaturen erzeugt :) –

Antwort

10

Anscheinend lief eine Menge Leute in das gleiche Problem. Nachdem ich den Quellcode der Klasse Signature untersucht hatte, kam ich zu dem Schluss, dass Microsoft uns helfen wollte. In der Methode LoadXml() gibt es das hardcoded Präfix "ds". So ist es möglich, eine Signatur zu erzeugen, dann das Namespacepräfix "ds" hinzuzufügen, die modifizierte Signatur zurück zu laden und "SignatureValue" neu zu berechnen. Leider macht ein Fehler in der Bibliothek die Dinge ein bisschen schwieriger als sie sein müssen. Der Code mit der Problemumgehung und den Kommentaren ist unten.

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; 
    } 
3

Edit: Sie können einen Algorithmus sehen, auf den dieser Beitrag in meiner anderen Antwort irgendwie angespielt hat.

Angenommen, dass es nicht möglich ist, ohne einen eigenen Algorithmus zum Kanonisieren und Signieren des Dokuments zu schreiben, könnte es möglich sein, das Namespacepräfix nach dem Signieren auf die Signaturelemente zu "injizieren" und dann vor der Überprüfung zu entfernen .

Zum Beispiel:

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); 
    } 
} 
+0

Ich habe eine neue Antwort eingereicht, in der ein Algorithmus zur Neuberechnung der Signatur nach dem Voranstellen der Signature-Elemente und der anschließenden Validierung in C# beschrieben wird. – Adrian

3

Da du die Unterzeichnung der Dokumente ist, soll dies einfach genug zu tun, aber die Klassen in System.Security.Cryptography.Xml nicht wirklich dies unterstützen.

Wenn die Signature-Elemente nur vorangestellt werden müssen, sofern die Signatur nicht in sich selbst referenziert ist (oder wenn sie Teil eines referenzierten Elements ist, solange sie mit einer Transformation wie in "http://www.w3.org/2000/09/xmldsig#enveloped-signature" entfernt wird) Dann müssen Sie den SignatureValue nur anhand Ihres geänderten SignedInfo-Elements neu berechnen. Ein Beispiel finden Sie in der SignEnveloped-Methode.

Ihre Signatur wird jedoch die in MSDN's How to: Verify the Digital Signatures of XML Documents beschriebene Validierung nicht bestehen, da die SignedXml-Klasse statt der Berechnung des SignatureValue durch das tatsächliche Lesen der SignedInfo des Dokuments scheinbar eine neue ohne vorangestellte Elemente generiert. Die folgende Klasse arbeitet mit SignedXmls scheinbar fehlerhaften Implementierungen, aber es könnte auch Validierungsprobleme in anderen Frameworks geben, die keine vorangestellten Elemente erwarten.

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); 
     } 
    } 
} 
+0

Renato, könnten Sie bitte überprüfen, ob dies für Ihre Situation richtig funktioniert? Obwohl ich an dem Ergebnis interessiert bin, habe ich nichts zu testen. –

+0

Ich musste einige Anpassungen vornehmen, um es speziell mit meinem Fall zu arbeiten, aber nachdem ich diese Anpassungen vorgenommen hatte, konnte ich die Signatur berechnen, mit dem Präfix aktualisieren und die Signatur erfolgreich überprüfen. – Russ