2015-06-01 13 views
12

Firmo digitalmente file XML, ma è necessario che i tag di firma contengano il prefisso dello spazio dei nomi "ds". Ho cercato abbastanza il google e ho trovato molte delle stesse domande, ma nessuna risposta soddisfacente.Genera firma digitale ma con prefisso spazio dei nomi specifico ("ds:")

Ho provato a inserire manualmente "ds" nel file, ma la firma non è più valida. Il tag "SignatureValue" firma il tag "SignedInfo" in modo che la firma non sia più valida.

Qualcuno potrebbe mostrarmi come si genera il valore del tag "SignatureValue" in modo da poter sostituire la firma dopo aver aggiunto il prefisso "ds"?

+6

Questo dovrebbe calci di nuovo in vista :) –

+0

Nota che la firma è posta sul * * canonicalized versione del testo in chiaro (l'elemento XML in cui la firma è posto su). È necessaria la chiave privata per generare la firma, quindi non è possibile sostituire il valore della firma stessa. Il trucco sarebbe quello di inserire lo spazio dei nomi "ds" senza alterare la rappresentazione canonica, in modo che la firma rimanga la stessa. –

+0

Penso di averlo risolto personalmente urlando agli utenti di un server MS BizTalk di utilizzare software che genera firme standardizzate :) –

risposta

10

Apparentemente un sacco di persone si sono imbattuti nello stesso problema. Dopo aver esaminato il codice sorgente della classe Signature, sono giunto alla conclusione che Microsoft mirava ad aiutarci. C'è il prefisso "ds" con hardcoded nel metodo LoadXml(). Quindi, è possibile generare la firma, quindi aggiungere il prefisso dello spazio dei nomi "ds", caricare la firma modificata indietro e ricalcolare "SignatureValue". Un bug insensato nella libreria rende le cose un po 'più difficili di quanto debbano essere. Il codice con soluzione e commenti è sotto.

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

Modifica: È possibile visualizzare un algoritmo a cui questo post fa riferimento nella mia altra risposta.

Supponendo che non sia possibile senza scrivere il proprio algoritmo per canonicalizzare e firmare il documento, una possibile soluzione potrebbe essere quella di "iniettare" il prefisso dello spazio dei nomi sugli elementi della firma dopo la firma e quindi rimuoverlo da questi prima di verificare .

Ad esempio:

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

Ho inviato una nuova risposta che descrive un algoritmo per ricalcolare la firma dopo aver anteposto gli elementi Signature e come è stato possibile gestire la convalida in C# in seguito. – Adrian

3

Dal momento che tu sei quella di firmare i documenti, questo dovrebbe essere abbastanza facile da fare, ma le classi in System.Security.Cryptography.Xml in realtà non supporta questa.

Se sono necessari solo gli elementi Signature da prefiggere, a condizione che la Firma non faccia riferimento in sé (o se faccia parte di un elemento referenziato, fintanto che viene rimossa con una trasformazione come in "http://www.w3.org/2000/09/xmldsig#enveloped-signature") allora tutto ciò che devi fare è ricalcolare il SignatureValue in base al tuo elemento SignedInfo modificato. Vedere il metodo SignEnveloped sotto per un esempio.

Tuttavia, la firma non supererà la convalida delineata in MSDN's How to: Verify the Digital Signatures of XML Documents, in quanto anziché calcolare il valore di SignatureValue confrontandolo con la lettura effettiva del file SignedInfo, la classe SignedXml sembra generarne una nuova senza elementi prefissati. La classe seguente funziona attorno alle implementazioni apparentemente errate di SignedXml, ma potrebbero esserci problemi di validazione in altri framework che non prevedono elementi prefissati.

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, potresti verificare se funziona correttamente per la tua situazione? Anche se sono interessato al risultato, non ho nulla contro cui confrontarmi. –

+0

Ho dovuto apportare alcune modifiche per farlo funzionare in modo specifico con la mia custodia, tuttavia, dopo aver apportato queste modifiche, sono stato in grado di calcolare la firma, aggiornare con il prefisso e verificare la firma con successo. – Russ