Как использовать фрагменты URI для подписи XML в С#

Я работаю над отправкой XML-сообщения в государственное учреждение (используя спецификации этого государственного учреждения, поэтому я не могу контролировать, как должен выглядеть результирующий XML), и я использую C# для своей разработки (политика компании).

Два человека, которые намного лучше меня разбираются в C# и интернет-технологиях, просмотрели XML раньше меня и сообщили мне, что WCF не поддерживает методы, необходимые для создания подписи в XML-документе (это было немного облегчением, поскольку я не разрабатывал никаких проектов WCF, и это пугает, так как я понимаю, что WCF является зрелой веб-технологией).

Итак, я использовал комбинацию LINQ to XML и System.Xml для создания сообщения и попытки подписать его.

Вот немного урезанный образец XML:

<soapenv:Envelope 
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:soap-sec="http://schemas.xmlsoap.org/security/2000-12" 
  xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy" 
  xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
  xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
  xmlns:cns="http://customNamespace1.com" 
  xmlns:cnt="http://customNamespace2.com" 
  >
  <soapenv:Header>
    <ns2:Element1 xmlns:ns2="http://namespace2.element1.com" wsu:Id="id-1">
      ...
    </ns2:Element1>
    <ns2:Element2 xmlns:ns2="http://namespace2.element2.com/" wsu:Id="id-2">
      ...
    </ns2:Element2>
    <wsse:Security SOAP-ENV:mustUnderstand="1" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
      <wsu:Timestamp wsu:Id="id-3">
        ...
      </wsu:Timestamp>
      <wsse:UsernameToken wsu:Id="id-4">
        ...
      </wsse:UsernameToken>
      <wsse:BinarySecurityToken ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-5bf699c7-5336-4695-b395-88d2b984fe54" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        ...
      </wsse:BinarySecurityToken>
      <ds:Signature Id="SIG-6" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
          <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
            <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsse wsu xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
          </ds:CanonicalizationMethod>
          <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
          <ds:Reference URI="#id-1">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsse wsu xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            <ds:DigestValue>...</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#id-2">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsse wsu xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            <ds:DigestValue>...</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#id-3">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsse xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            <ds:DigestValue>...</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#id-4">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsu xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            <ds:DigestValue>...</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#id-5">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec sp cns wsdl wsp wsse wsu xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            <ds:DigestValue>...</ds:DigestValue>
          </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>
          ...
        </ds:SignatureValue>
        <ds:KeyInfo Id="KI-ABDCFEC7595B7819C213402151542862">
          <wsse:SecurityTokenReference wsu:Id="STR-ABDCFEC7595B7819C213402151542863">
            <wsse:Reference URI="#SecurityToken-5bf699c7-5336-4695-b395-88d2b984fe54" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" />
          </wsse:SecurityTokenReference>
        </ds:KeyInfo>
      </ds:Signature>
    </wsse:Security>
  </soapenv:Header>
  <soapenv:Body wsu:Id="id-5">
    <ns5:bodyelement xmlns:ns4="http://namespace4.com/" xmlns:ns3="http://namespace3.com/" xmlns:ns2="http://bodynamespace2.com/" xmlns:ns5="http://namespace5.com/">
      ...
    </ns5:bodyelement>
  </soapenv:Body>
</soapenv:Envelope>

Вот часть кода, который я пробовал (3 разных метода попытки заставить фрагмент uri работать). Я публикую здесь только часть кода, так как для создания соответствующего XML-кода потребовалось более 200 строк кода, который я сейчас пытаюсь подписать:

RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)Key;
SignedXml xmlWSig = new SignedXml(myDoc);
xmlWSig.SigningKey = Key;
xmlWSig.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
XmlDsigExcC14NTransform canMethod = (XmlDsigExcC14NTransform)xmlWSig.SignedInfo.CanonicalizationMethodObject;
canMethod.InclusiveNamespacesPrefixList = "SOAP-ENV cns soap-sec soapenv sp cnt wsdl wsp wsse wsu xs xsi";

Uri uri = new Uri("#id-1");
Reference ref1 = new Reference(uri.ToString());
XmlDsigExcC14NTransform transform1 = new XmlDsigExcC14NTransform("SOAP-ENV cns soap-sec soapenv sp cnt wsdl wsp wsse wsu xs xsi");
ref1.AddTransform(transform1);
ref1.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
xmlWSig.AddReference(ref1);

Reference ref2 = new Reference("#id-2"); 
XmlDsigExcC14NTransform transform2 = new XmlDsigExcC14NTransform("SOAP-ENV cns soap-sec soapenv sp cnt wsdl wsp wsse wsu xs xsi");
ref2.AddTransform(transform2);
ref2.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
xmlWSig.AddReference(ref2);

Reference ref3 = new Reference("");  
ref3.Uri = "#id-3";
XmlDsigExcC14NTransform transform3 = new XmlDsigExcC14NTransform("SOAP-ENV cns soap-sec soapenv sp cnt wsdl wsp wsse xs xsi");
ref3.AddTransform(transform3);
ref3.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
xmlWSig.AddReference(ref3);

//repeat things for id-4, and id-5

KeyInfo myKeyInfo = new KeyInfo();
myKeyInfo.AddClause(new RSAKeyValue((RSA)Key));
xmlWSig.KeyInfo = myKeyInfo;

xmlWSig.ComputeSignature();
XmlElement signedXmlElement = xmlWSig.GetXml();

Ключ — это закрытый ключ, полученный из сертификата X509 (и это то, что следует использовать в качестве ключа для подписи документа). myDoc — это сгенерированный мной System.Xml XmlDocument, в который мне нужно вставить подпись.

Метод № 1 дает мне System.UriFormatException: Invalid URI: формат URI не может быть определен.

Метод № 2. Дает мне элемент ссылки System.Security.Cryptography.CryptograpicException: искаженный формат (если я удаляю # из Uri, это дает мне System.UriFormatException: Invalid URI: URI пуст).

Метод №3 дает те же ошибки, что и метод №2.

Из всей документации по использованию Uris для подписей разрешено использование только фрагмента Uri (при условии, что элемент, на который делается ссылка, находится внутри того же документа), но класс Uri в C#, похоже, не принимает фрагменты как приемлемый Uri.

Ссылочному классу также требуется полный Uri, а не только фрагмент.

Я открыт для любых предложений о том, как я могу правильно сгенерировать подпись в этом XML, используя спецификации.

ОБНОВЛЕНИЕ: Хотя SignedXml + Reference + Transform кажется лучшим решением, теперь я начинаю полагать, что .NET имеет большой пробел в этих библиотеках, и переход к некоторым библиотекам более низкого уровня для создания подписи может быть необходимый.

К сожалению, я все еще пытаюсь сказать, какие библиотеки потребуются, и алгоритм поиска того, что нужно подписать. Мое понимание эксклюзивной канонизации заключалось в том, что вы подписывали только элементы, указанные префиксами, перечисленными в InclusiveNamespaces PrefixList, но URI в справке указывает вложенный документ, над которым должна стоять подпись, но элементы внутри указанных элементов не не использовать большинство включенных пространств имен. Я понимаю, как должны работать эти ссылки?


person FinrodFelagund    schedule 04.11.2013    source источник


Ответы (2)


Похоже, мне, возможно, придется продублировать алгоритм создания URI + инклюзивных пространств имен для себя (еще нужно выяснить, как это будет работать) и преобразовать эти элементы в массивы байтов. Затем используйте библиотеки более низкого уровня для подписей.

Что-то вроде этого:

RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey;
signedMessage = rsa.SignData(originalMessage, CryptoConfig.MapNameToOID("SHA1"));

Затем signedMessage можно преобразовать в строку Base64 и вставить в каждое из соответствующих значений дайджеста.

Намного больше работы, чем я надеялся, но если .NET не поддерживает фрагменты URI, думаю, мне придется делать то, что я должен делать.

Из-за сложности этого решения я определенно открыт для альтернатив, если у кого-то есть что-то более плавное.

РЕДАКТИРОВАТЬ: после нескольких часов изучения документации по канонизации, похоже, что эксклюзивная канонизация больше связана с тем, какие пространства имен вы вставляете в XML, когда копируете его, чтобы подписать. Если пространство имен не используется напрямую и не включено в список инклюзивных префиксов пространств имен, вы не добавляете это пространство имен к подписываемым элементам до их подписания. Это все еще кажется мне странным, так как вы не обязательно хотите, чтобы это пространство имен было в элементе внутри более полного контекста XML, а его подпись означает, что оно не может измениться, но вы его не включаете.

ОБНОВЛЕНИЕ: после многих часов тестирования я наконец заставил это работать. Но в нем было много, и очень много очень отвратительного кода. По сути, мне приходилось извлекать каждый элемент, который мы выписывали, в копию и вручную обновлять пространства имен, которые были включены в этот элемент, а затем генерировать из него хэш (включая выполнение более полного преобразования на данный момент). Но это сработало.

person FinrodFelagund    schedule 07.11.2013

Оказывается, причина, по которой .NET не принимала его, заключалась в префиксе пространства имен wsu. перевернув wsu:Id=, чтобы он был просто id=, удалось сгенерировать по крайней мере подпись. Затем я наткнулся на это: >'Искаженный элемент ссылки' при добавлении ссылки на основе атрибута Id с классом SignedXml

При разработке использовалось значительно меньше кода, чем в моем предыдущем ответе, и его намного легче читать/обслуживать.

person FinrodFelagund    schedule 18.12.2013