Не удается проверить подпись Xml для XmlElement

Заранее извиняюсь за довольно длинный блок кода, но это самый маленький компилируемый пример, который я смог создать. Я уже исключил все проверки ошибок из исходного кода. Я использую Visual Studio 2012 и .NET 4.5, хотя в 4.5 нет ничего нового, он должен работать с любой версией.

Я пытаюсь подписать элементы XML-документов, чтобы защитить их от подделки. Я не хочу защищать весь документ, а только некоторые его элементы. Возможно даже разные элементы с разными клавишами.

Однако, когда я подписываю три примера элементов и пытаюсь их проверить, первый всегда проверяется, а два других терпят неудачу. Что еще хуже, первый преуспевает даже в том случае, если я изменяю его после подписания. Я много гуглил, читал много руководств и даже спрашивал здесь теоретический вопрос, но я понятия не имею, что я делаю неправильно. Может ли кто-нибудь заметить мою ошибку?

Примечание. Я был бы более чем счастлив предложить ту же награду, что и в пятничном вопросе, любому, кто решит эту проблему.

Сертификат был создан путем выполнения:

"C:\Program Files (x86)\Microsoft SDK\Windows\v7.1A\Bin\makecert" -r -pe -n "CN=XMLDSIG_Test" -b 01/01/2013 -e 01/01/2014 -sky подпись -ss мой

Тестовый XML-файл:

<?xml version="1.0" encoding="utf-8" ?>
<PackageRoot>
  <Package>
    <Changes >
     <Change/>
    </Changes>
  </Package>
  <Package>
    <Changes>
     <Change/>
     <Change/>
    </Changes>
  </Package>
  <Package>
    <Changes>
     <Change/>
     <Change/>
     <Change/>
    </Changes>
  </Package>
</PackageRoot>

Код для подписи и проверки:

namespace SOExample
{
  using System;
  using System.Security.Cryptography.X509Certificates;
  using System.Security.Cryptography.Xml;
  using System.Xml;

  public static class Program
  {
    public static void Sign(this XmlElement element, X509Certificate2 certificate)
    {
        var identifier = Guid.NewGuid().ToString();

        element.SetAttribute("Id", identifier);

        var signedXml = new SignedXml(element) { SigningKey = certificate.PrivateKey };

        var reference = new Reference("#" + identifier);

        reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());

        signedXml.AddReference(reference);

        signedXml.ComputeSignature();

        var xmlDigitalSignature = signedXml.GetXml();

        element.AppendChild(element.OwnerDocument.ImportNode(xmlDigitalSignature, true));
    }

    public static bool VerifySignature(this XmlElement element, X509Certificate2 certificate)
    {
      var signedXml = new SignedXml(element);

      XmlNodeList nodeList = element.GetElementsByTagName("Signature");

      if (nodeList.Count != 1) return false;

      signedXml.LoadXml((XmlElement)nodeList[0]);

      return signedXml.CheckSignature(certificate, true);
    }

    public static void Main()
    {
        var xmlDoc = new XmlDocument { PreserveWhitespace = true };

        xmlDoc.Load("ExamplePackage.xml");

        var certificate = GetCertificateBySubject("CN=XMLDSIG_Test");

        foreach (XmlElement root in xmlDoc.GetElementsByTagName("PackageRoot"))
        {
          foreach (XmlElement package in root.GetElementsByTagName("Package"))
          {
            package.Sign(certificate);
          }
        }

        xmlDoc.Save("test_signed.xml");

        Console.WriteLine("XML file signed.");
        Console.WriteLine("Press any key to verify");
        Console.ReadLine();

        var signedDoc = new XmlDocument();

        signedDoc.Load("test_signed.xml");

        foreach (XmlElement root in xmlDoc.GetElementsByTagName("PackageRoot"))
        {
          foreach (XmlElement package in root.GetElementsByTagName("Package"))
          {
            Console.Write("Verifying Package " + package.GetAttribute("Id"));
            var success = package.VerifySignature(certificate);
            Console.WriteLine(success ? " successful!" : " failed!");
          }
        }

        Console.WriteLine("Done.");
        Console.ReadLine();
    }

    private static X509Certificate2 GetCertificateBySubject(string certificateSubject)
    {
      var store = new X509Store("My", StoreLocation.CurrentUser);

      store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

      foreach (X509Certificate2 c in store.Certificates)
      {
        if (c.Subject == certificateSubject)
        {
          store.Close();
          return c;
        }
      }

      store.Close();
      return null;
    }
  }
}

person nvoigt    schedule 13.05.2013    source источник
comment
Если вы что-то отрицаете, пожалуйста, оставьте комментарий, чтобы постер мог стать лучше.   -  person nvoigt    schedule 23.05.2013


Ответы (1)


У вас ошибка в тестовом коде. Второй цикл foreach снова проходит через xmlDoc вместо signedDoc. Исправление этого изменит результат на отказ для всех узлов.
Почему они не работают, я пока не знаю.


Я не мог понять, почему они терпят неудачу с вашим кодом, но я нашел способ заставить его работать. Разница: все подписи являются прямыми дочерними элементами корневого элемента:

public static void Main()
{

    // ...

    var signedDoc = new XmlDocument { PreserveWhitespace = true };

    signedDoc.Load("test_signed.xml");

    foreach (XmlElement root in signedDoc.GetElementsByTagName("PackageRoot"))
    {
        foreach (XmlElement signature in root.GetElementsByTagName("Signature"))
        {
            var success = signature.VerifySignature(certificate);
            Console.WriteLine(success ? " successful!" : " failed!");
        }
    }

    Console.WriteLine("Done.");
    Console.ReadLine();
}

public static void Sign(this XmlElement element, X509Certificate2 certificate)
{
    var identifier = Guid.NewGuid().ToString("N");
    element.SetAttribute("Id", identifier);

    var signedXml = new SignedXml(element) { SigningKey = certificate.PrivateKey };
    signedXml.AddReference(new Reference("#" + identifier));
    signedXml.ComputeSignature();

    var xmlDigitalSignature = signedXml.GetXml();

    element.OwnerDocument.DocumentElement.AppendChild(
        element.OwnerDocument.ImportNode(xmlDigitalSignature, true));
}

public static bool VerifySignature(this XmlElement element, X509Certificate2 certificate)
{
    var signedXml = new SignedXml(element.OwnerDocument);
    signedXml.LoadXml(element);

    return signedXml.CheckSignature(certificate, true);
}

Обратите внимание на одну важную деталь: PreserveWhitespace также необходимо установить на true для signedDoc.

person Daniel Hilgarth    schedule 13.05.2013
comment
Ой, это была глупая ошибка, спасибо, что нашли ошибку в тестовом коде. Я внесу изменения и посмотрю, смогу ли я заставить его работать. - person nvoigt; 13.05.2013
comment
Это сработало, большое спасибо! Если вы опубликуете однострочный ответ со ссылкой здесь, в ветке с наградой (ссылка выше), я тоже приму это. - person nvoigt; 13.05.2013