使用 X.509 证书将 SOAP 消息从 WCF 服务签名到 Java web 服务

问题描述:

这是我在网上提出的第一个问题.希望它有意义.

It's my first question over the web. Hope it will make sense.

我在网上看到了几个与这个问题相关的博客,我尝试了其中提出的一些想法,但没有成功.这是我的情况:

I have seen several blogs related to this issue over the Web, and I have tried few of the ideas presented in them with no success. Here's my situation:

我有一个 Web 应用程序调用 WCF Web 服务,然后调用 Java Web 服务.他们都在不同的服务器上.WCF Web 服务与 Java Web 服务之间的调用不通过 h​​ttps,因为证书足以识别调用者(因此消息安全).

I have a web App calling a WCF web service which then call a Java web service. They are all on different servers. The call between the WCF web service to the java web service is not over https as the certificate will be enough to identify the caller (Message security therefore).

  • Java 网络服务(黑匣子)

Java Web 服务需要接收签名消息并按以下方式工作:
在处理每个请求之前,处理程序会拦截所有传入的消息并执行以下验证规则:
1. 消息是否包含安全标头
2. 消息是否包含正确的安全头 ID
3. 消息是否正确签名
4. 消息是否包含 KeyInfo x.509 证书
5. 证书是否由受信任的 CA 颁发——基于配置
6. 证书是否有效(未过期、撤销)
7. 证书是否包含正确的策略 OID

The Java web service requires to received a signed message and works as per below:
Before each request is processed a handler intercepts all incoming messages and performs the following validation rules:
1. Does the message contain a security header
2. Does the message contain the correct security header ID
3. Is the message been signed correctly
4. Does the message contain a KeyInfo x.509 certificate
5. Is the certificate issued from a trusted CA – configuration based
6. Is the certificate valid (not expired, revoked)
7. Does the certificate contain the correct policy OID

确认所有这些步骤后,即可处理消息,如果任何步骤失败,则将返回soap消息异常.

Once all of these steps have been confirmed then the message can be processed, if any step fails then a soap message exception will be returned.

SOAP 安全标头应根据 xxx...w3.org/TR/SOAP-dsig/数字签名规范进行验证.

The SOAP security header should validate against xxx...w3.org/TR/SOAP-dsig/ digital signature specification.

最完整的描述可以在这里找到 xxx...ibm.com/developerworks/webservices/library/ws-security.html 这篇 IBM 文章列出了每个 WS-Security 标头的详细信息,另外还有一个示例签名 SOAP 消息具有已提供.

The most complete description can be found here xxx...ibm.com/developerworks/webservices/library/ws-security.html this IBM article lists the details of each WS-Security header, additionally a sample signed SOAP message has been provided.

在签署 SOAP 消息时,您还必须将他们的 x.509 证书添加到消息 KeyInfo 中,这是证书验证所必需的.

When signing the SOAP message you must also add they x.509 certificate into the message KeyInfo this is required for the certificate validation.

SOAP 请求应该是这样的:

SOAP Request should like this:

<?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 网络服务
  • 我有一个服务器证书(来自受信任 CA 的 p7b 格式),我使用 mmc 证书管理单元(目前证书在受信任的发布者中)安装在我的 WCF Web 服务工作站(开发)所在的位置.我认为我不需要 Java 服务器上的另一个证书,因为响应应该是清晰的(既没有签名也没有加密).我对这个证书 - 以及一般的证书 - 仍然有点困惑,因为它似乎只持有一个公钥.

    I have one server certificate (p7b format from a trusted CA) that I installed where my WCF web service workstation (dev) is by using the mmc Certificate Snap-in (at the moment cert is in the Trusted Publishers). I don't think I need another cert on the Java server as the response should be in clear (neither signed or encrypted). I am still a bit confused on this certificate -and certificates in general- as it seems to hold only a public key.

    这是我的测试项目的 app.config:

    Here is the app.config of my test project:

<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>

当我运行一个简单的测试时:
    WebAS entrustService = new WebAS();
    ActivationCodes certCodes = entrustService.createUser("testNomad");
我有错误:
    失败:System.Web.Services.Protocols.SoapException:javax.xml.soap.SOAPException:在soap消息中找不到签名元素

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 配置来做到这一点.任何帮助将不胜感激!

How could I force the signing process for each message? I was thinking I could do it through WCF configuration quite easily. Any help would be greatly appreciated !

好的.经过几次尝试和错误后,这里是使用 SignedXml 和 IClientMessageInspector/BeforeSendRequest 模式的解决方案.非常感谢 Yaron Naveh 的相关建议.

OK. After few tries and errors here is the solution using SignedXml and IClientMessageInspector/BeforeSendRequest pattern. Thanks a lot to Yaron Naveh for his relevant suggestions.

// 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 类:

and the CustomSignedXml class:

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