Подписание сообщений SOAP с использованием сертификата X.509 из службы WCF в веб-службу Java

Это мой первый вопрос в сети. Надеюсь, это будет иметь смысл.

Я видел в Интернете несколько блогов, посвященных этому вопросу, и безуспешно опробовал лишь некоторые из представленных в них идей. Вот моя ситуация:

У меня есть веб-приложение, вызывающее веб-службу WCF, которая затем вызывает веб-службу Java. Они все на разных серверах. Вызов между веб-службой WCF и веб-службой java не осуществляется через https, поскольку сертификата будет достаточно для идентификации вызывающего абонента (поэтому безопасность сообщений).

  • Веб-служба Java (черный ящик)

Веб-служба Java требует получения подписанного сообщения и работает следующим образом:
Перед обработкой каждого запроса обработчик перехватывает все входящие сообщения и выполняет следующие правила проверки:
1. Содержит ли сообщение заголовок безопасности< br> 2. Содержит ли сообщение правильный идентификатор заголовка безопасности
3. Правильно ли подписано сообщение
4. Содержит ли сообщение сертификат KeyInfo x.509
5. Выдан ли сертификат доверенный ЦС – конфигурация на основе
6. Является ли сертификат действительным (не просроченным, отозванным)
7. Содержит ли сертификат правильный OID политики

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

Заголовок безопасности SOAP должен соответствовать спецификации цифровой подписи xxx...w3.org/TR/SOAP-dsig/.

Наиболее полное описание можно найти здесь xxx...ibm.com/developerworks/webservices/library/ws-security.html. В этой статье IBM перечислены подробности каждого заголовка WS-Security, кроме того, был предоставлен образец подписанного сообщения SOAP.

При подписании сообщения SOAP вы также должны добавить сертификат x.509 в сообщение KeyInfo, это необходимо для проверки сертификата.

SOAP-запрос должен выглядеть следующим образом:

<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<ds:Signature xmlns:ds="xxx...w3.org/2000/09/xmldsig#" Id="Signature001">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="xxx...w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="xxx...w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="">
<ds:Transforms>
<ds:Transform Algorithm="xxx...w3.org/2000/09/xmldsig#enveloped-signature"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="xxx...w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>soe1PnaGXVGrsauC61JSHD+uqGw=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#KeyInfo001">
<ds:DigestMethod Algorithm="xxx...w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>Y9SRPQ9TcDu+GazO3LFwodEdhaA=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>jBX/8XkY2aCte7qgXEp1sbNWmQcK/90iVL58sAvwYAEcBABGzOk2agxR0HvWrNa6ixkocAQ205lggwOxnxZJvoVozVYAAjcLtayPBOUYrnSEBFrwKWP/vxgvUDRIdXeIuw5GLY87NrTQMm1Ehf/HvMX9hTBJn4Nm8RdDiUmPcIo=</ds:SignatureValue>
<ds:KeyInfo Id="KeyInfo001">
<ds:X509Data>
<ds:X509Certificate>MIIEbZCCA1WgAwIBAgIES1XpMjANBgkqhkiG9w0BAQUFADBYMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFzAVBgoJkiaJk/IsZAEZFgdlbnRydXN0MRIwEAYDVQQDEwllbnRydXN0U00xEjAQBgNVBAMTCWVudHJ1c3RDQTAeFw0xMDA0MjIxMDQ4MDBaFw0xMzA0MjIxMTE4MDBaMGoxFTATBgoJkiaJk/IsZAEZFgVsb2NhbDEXMBUGCgmSJomT8ixkARkWB2VudHJ1c3QxEjAQBgNVBAMTCWVudHJ1c3RTTTESMBAGA1UEAxMJZW50cnVzdENBMRAwDgYDVQQDEwdSYnMgUmJzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMf88L2JjLPG1hNmTA/KBiC53WVwS2WU9Jh3lC1Rob6RMzOojomZ/dNrvSRB6nzWeXJpZXwik4XFrsAq24By2SZpLTO4p8Vcq71mTAfDu33cnO49Au2pwNvcMn5qIKBk1Xx+oVb4fzK9ncTRu7bW46HsIYth+qkGhbI2JEHwr/zwIDAQABo4IBrzCCAaswCwYDVR0PBAQDAgeAMCsGA1UdEAQkMCKADzIwMTAwNDIyMTA0ODAwWoEPMjAxMjA1MjgxNTE4MDBaMCMGA1UdIAQcMBowCwYJYIZIAYb6awoEMAsGCSqGSIb2fQdLAzAbBgNVHQkEFDASMBAGCSqGSIb2fQdEHTEDAgEBMIHGBgNVHR8Egb4wgbswb6BtoGukaTBnMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFzAVBgoJkiaJk/IsZAEZFgdlbnRydXN0MRIwEAYDVQQDEwllbnRydXN0U00xEjAQBgNVBAMTCWVudHJ1c3RDQTENMAsGA1UEAxMEQ1JMMTBIoEagRIZCZmlsZTovLy8vTVNJREhVLTQ0NUE0RkVFL0NSTC9lbnRydXN0Y2FfZW50cnVzdHNtX2xvY2FsX2NybGZpbGUuY3JsMB8GA1UdIwQYMBaAFBvSL6cPz8L5shubV58yf0pczKzuMB0GA1UdDgQWBBT1/j6OSS8FTjwqluvew16sv7h+VzAJBgNVHRMEAjAAMBkGCSqGSIb2fQdBAAQMMAobBFY4LjADAgSwMA0GCSqGSIb3DQEBBQUAA4IBAQBXxRIA4HUvGSw4L+4uaR51pY4ISjUQWo2Fh7FYBMt29NsKCTdur1OWVVdndt1yjXP4yWXxoAhHtvZL+XNALUFlR2HAWiXuL1nRcxHkB98N5gPqQzW/lJk9cLtL4hVp28EiEpgmKT3I3NP2Pdb2G5MMOdvQ/GFb2y6OwblR8ViPQ8B2aHWzXMrH+0qadPAuBhXyAohwb+mMuYT/ms6xpGi1NMYuYMf6XONz9GkZgnGnMwa+9CCQws1HNz8WYHtmFIxLsVuEWc/0a1vg4IYX1Ds/ttyhJGTVXOSJSkBz8kRyj1pNBDdc1KeG8M++O8m8VgRTJvYaPc7NMiclISukGpea</ds:X509Certificate> </ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</S:Header>
<S:Body Id="ABC">
<ns2:createUser xmlns:ns2="http://webservice.rbs.emea.ps.entrust.com/" xmlns:ns3="http://webservice.rbs.emea.ps.entrust.com/types/CertificateException" xmlns:ns4="http://webservice.rbs.emea.ps.entrust.com/types/UserException">
<userID>0061020051</userID>
</ns2:createUser>
</S:Body>
</S:Envelope>
  • Веб-служба WCF

У меня есть один сертификат сервера (формат p7b от доверенного центра сертификации), который я установил на своей рабочей станции веб-службы WCF (dev) с помощью оснастки сертификата mmc (на данный момент сертификат находится в доверенных издателях). Я не думаю, что мне нужен еще один сертификат на сервере Java, поскольку ответ должен быть четким (ни подписанным, ни зашифрованным). Я все еще немного смущен этим сертификатом и сертификатами в целом, поскольку он, кажется, содержит только открытый ключ.

Вот файл app.config моего тестового проекта:

<client>
  <endpoint address="http://entrust-user-certification-uat.fm.rbsgrp.net/rbs/WebAS"
    behaviorConfiguration="endpointCredentialsBehavior" binding="wsHttpBinding"
    bindingConfiguration="WebAsServicePortTypeBinding" contract="IWebAsServicePortType"
    name="WebAsServicePortType">
    <!--<identity>
      <dns value="entrust-user-certification-uat.fm.rbsgrp.net" />
    </identity>-->
  </endpoint>
</client>
<bindings>
  <wsHttpBinding>
    <binding name="WebAsServicePortTypeBinding" closeTimeout="00:01:00"
      openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
      bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
      maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text"
      textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
      <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
      <security mode="Message">
        <message clientCredentialType="Certificate" negotiateServiceCredential="false"
          establishSecurityContext="false" />
      </security>
    </binding>
  </wsHttpBinding>
</bindings>

<behaviors>
  <endpointBehaviors>
    <behavior name="endpointCredentialsBehavior">
      <clientCredentials>
        <clientCertificate findValue="entrust-user-certification-uat.fm.rbsgrp.net"
           storeLocation="LocalMachine" storeName="TrustedPublisher"
          x509FindType="FindBySubjectName"></clientCertificate>
        <serviceCertificate>
          <!--   
          Setting the certificateValidationMode to PeerOrChainTrust means that if the certificate 
          is in the user's Trusted People store, then it will be trusted without performing a
          validation of the certificate's issuer chain. This setting is used here for convenience so that the 
          sample can be run without having to have certificates issued by a certificate authority (CA).
          This setting is less secure than the default, ChainTrust. The security implications of this 
          setting should be carefully considered before using PeerOrChainTrust in production code. 
          -->
          <authentication certificateValidationMode="None" revocationMode="NoCheck" trustedStoreLocation="LocalMachine"/>
        </serviceCertificate>
      </clientCredentials>
    </behavior>
  </endpointBehaviors>
</behaviors>

When I ran a simple test:
     WebAS entrustService = new WebAS();
     ActivationCodes certCodes = entrustService.createUser("testNomad");
I've got the error:
     failed: System.Web.Services.Protocols.SoapException: javax.xml.soap.SOAPException: No Signature element found in soap message

Как я могу заставить процесс подписи для каждого сообщения? Я думал, что смогу сделать это с помощью конфигурации WCF довольно легко. Любая помощь будет принята с благодарностью!


person Nomadefv    schedule 12.01.2011    source источник
comment
Привет Nomadefv, Как вы в IClientMessageInspector/BeforeSendReques вставляете возвращенную строку XML в запрос? АЖД   -  person    schedule 08.02.2011
comment
Прости. Немного не в последнюю пару недель. Вы можете попробовать:   -  person Nomadefv    schedule 23.02.2011
comment
'public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, канал System.ServiceModel.IClientChannel) '{ //... получить другую переменную из параметра безопасной конфигурации '// Загрузить сертификат из хранилища сертификатов. 'X509Certificate2 cert = GetCertificateBySubject(certificateSubjectName, CertificateStoreName, CertificateStoreLocation); '// Подписать запрос 'string signedSoapMessage = SignRequest(request.ToString(), cert, signalId, keyInfoRefId, bodyId);   -  person Nomadefv    schedule 23.02.2011
comment
'// Изменить запрос подписанным сообщением SOAP 'MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(signedSoapMessage)); 'var reader = XmlDictionaryReader.CreateTextReader(ms, new XmlDictionaryReaderQuotas()); 'запрос = Сообщение.СоздатьСообщение(читатель, Int32.MaxValue, запрос.Версия); 'возвратить ноль; }   -  person Nomadefv    schedule 23.02.2011


Ответы (2)


В ПОРЯДКЕ. После нескольких попыток и ошибок вот решение, использующее шаблон SignedXml и IClientMessageInspector/BeforeSendRequest. Большое спасибо Ярону Наве за его важные предложения.

// Sign an XML request and return it
public static string SignRequest(string request, string SubjectName, string Signature, string keyInfoRefId)
{
    if (string.IsNullOrEmpty(request))
        throw new ArgumentNullException("request");
    if (string.IsNullOrEmpty(SubjectName))
        throw new ArgumentNullException("SubjectName");

    // Load the certificate from the certificate store.
    X509Certificate2 cert = GetCertificateBySubject(SubjectName);

    // Create a new XML document.
    XmlDocument doc = new XmlDocument();

    // Format the document to ignore white spaces.
    doc.PreserveWhitespace = false;

    // Load the passed XML 
    doc.LoadXml(request);

    // Add the declaration as per Entrust sample provided -don't think it's necessary though
    if (!(doc.FirstChild is XmlDeclaration))
    {
        XmlDeclaration declaration = doc.CreateXmlDeclaration("1.0", "UTF-8", string.Empty);
        doc.InsertBefore(declaration, doc.FirstChild);
    }

    // Remove the Action (MustUnderstand). 
    // TODO: Need to find a more elegant way to do so
    XmlNode headerNode = null;
    XmlNodeList nodeList = doc.GetElementsByTagName("Action");
    if (nodeList.Count > 0)
    {
        headerNode = nodeList[0].ParentNode;
        headerNode.RemoveChild(nodeList[0]);
    }

    // Set the body id - not in used but could be useful at a later stage of this project
    XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);    
    ns.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/");
    XmlElement body = doc.DocumentElement.SelectSingleNode(@"//s:Body", ns) as XmlElement;    
    if (body == null)    
        throw new ApplicationException("No body tag found");
    body.RemoveAllAttributes();  // no need to have namespace
    body.SetAttribute("Id", "ABC"); // Body Id could be passed as a param

    // Create a custom SignedXml object so that we could sign the keyinfo
    CustomSignedXml signedXml = new CustomSignedXml(doc);

    // Add the key to the SignedXml document. 
    signedXml.SigningKey = cert.PrivateKey;

    // Create a new KeyInfo object.
    KeyInfo keyInfo = new KeyInfo();
    keyInfo.Id = keyInfoRefId;

    // Load the certificate into a KeyInfoX509Data object
    // and add it to the KeyInfo object.
    KeyInfoX509Data keyInfoData = new KeyInfoX509Data();
    keyInfoData.AddCertificate(cert);
    keyInfo.AddClause(keyInfoData);

    // Add the KeyInfo object to the SignedXml object.
    signedXml.KeyInfo = keyInfo;

    // Create a reference to be signed.
    Reference reference = new Reference();
    reference.Uri = "";

    // Add an enveloped transformation to the reference.
    XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
    reference.AddTransform(env);

    // Add the reference to the SignedXml object.
    signedXml.AddReference(reference);

    Reference reference2 = new Reference();
    reference2.Uri = "#" + keyInfoRefId;
    signedXml.AddReference(reference2);

    // Add the Signature Id
    signedXml.Signature.Id = Signature;

    // Compute the signature.
    signedXml.ComputeSignature();

    // Get the XML representation of the signature and save
    // it to an XmlElement object.
    XmlElement xmlDigitalSignature = signedXml.GetXml();

    // Append the Signature element to the XML document.
    if (headerNode != null)
    {                
        headerNode.AppendChild(doc.ImportNode(xmlDigitalSignature, true));
    }

    return doc.InnerXml;
}

public static X509Certificate2 GetCertificateBySubject(string CertificateSubject)
{
    // Check the args.
    if (string.IsNullOrEmpty(CertificateSubject))
        throw new ArgumentNullException("CertificateSubject");

    // Load the certificate from the certificate store.
    X509Certificate2 cert = null;

    X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);

    try
    {
        // Open the store.
        store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

        // Find the certificate with the specified subject.
        cert = store.Certificates.Find(X509FindType.FindBySubjectName, CertificateSubject, false)[0];

        // Throw an exception of the certificate was not found.
        if (cert == null)
        {
            throw new CryptographicException("The certificate could not be found.");
        }
    }
    finally
    {
        // Close the store even if an exception was thrown.
        store.Close();
    }

    return cert;
}

и класс CustomSignedXml:

public class CustomSignedXml : SignedXml
{
    public CustomSignedXml(XmlDocument doc) : base(doc)
    {
        return;
    }
    public override XmlElement GetIdElement(XmlDocument doc, string id)
    {
        // see if this is the key info being referenced, otherwise fall back to default behavior
        if (String.Compare(id, this.KeyInfo.Id, StringComparison.OrdinalIgnoreCase) == 0)
            return this.KeyInfo.GetXml();
        else
            return base.GetIdElement(doc, id);
    }
}
person Nomadefv    schedule 27.01.2011
comment
Привет, мне не очень понятно, как использовать это в методе BeforeSendRequest. Можешь объяснить? - person Mr. Robot; 27.10.2016

Можете ли вы перехватить сообщение, отправленное вашей службой WCF? Кстати. безопасность сообщений, используемая службой Java, описанная в WSDL, - это значительно упростило бы задачу.

По вашему описанию я думаю, что ваша конфигурация неверна, потому что, когда используются учетные данные клиента сертификата, вам нужны два сертификата - сертификат клиента с открытым и закрытым ключом и сертификат сервера с открытым ключом.

Это также, вероятно, описано в ваших требованиях:

Содержит ли сообщение сертификат KeyInfo x.509?

Сертификат выдан доверенным ЦС – на основе конфигурации

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

Но это только предположение, потому что реальные требования обычно описываются на общем языке - утверждения WSDL + WS-Security.

Управление подписью и шифрованием возможно на нескольких уровнях. Прежде всего, каждый ServiceContract и MessageContract имеет свойство ProtectionLevel, которое по умолчанию равно EncryptAndSign. Вы можете изменить его на Sign.

person Ladislav Mrnka    schedule 12.01.2011
comment
Большое спасибо за ваши комментарии! После напряженного дня, когда мы снова рассмотрели эту проблему и поговорили с разработчиком Java, нам действительно нужен только один сертификат на клиенте (wcf) и просто подписывать им все исходящие сообщения. Я посмотрел немного больше, и реализация IClientMessageInspector может быть решением. еще копаю... - person Nomadefv; 12.01.2011
comment
Безопасность сообщений службы Java wev не описана в WSDL. Глядя сейчас на IClientMessageInspector и BeforeSendRequest. - person Nomadefv; 12.01.2011