0

I have a WCF Service that has Message security and I'm trying to connect a client to it.

The connection works wonders using the default Web.Config settings generated automatically by "Add Service Reference" in Visual Studio, with the following settings:

Endpoint:

  <endpoint address="http://localhost:56017/services/MaternumPdfService.svc/soap"
    binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IMaternumPdfService"
    contract="MaternumPdfServiceReference.IMaternumPdfService" name="WSHttpBinding_IMaternumPdfService">
    <identity>
      <certificate encodedValue="BASE64 IS HERE" />
    </identity>
  </endpoint>

Binding:

    <binding name="WSHttpBinding_IMaternumPdfService" maxReceivedMessageSize="2147483647">
      <security>
        <message clientCredentialType="UserName" negotiateServiceCredential="false"
          establishSecurityContext="false" />
      </security>
      <readerQuotas maxStringContentLength="2147483647" maxDepth="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
    </binding>

This works without a hitch:

        using (MaternumPdfServiceClient pdfService = new MaternumPdfServiceClient("WSHttpBinding_IMaternumPdfService"))
        {
            pdfService.ClientCredentials.UserName.UserName = "sample";
            pdfService.ClientCredentials.UserName.Password = "sample";
            pdfService.Open();

            if (!pdfService.State.Equals(CommunicationState.Opened))
            {
                return null;
            }

            MemoryStream ms = new MemoryStream();
            pdfService.GetMaternumPdf(pdftype,params).CopyTo(ms);
        }

However, trying to construct the connection without these settings fails. Here is how I build the connection:

        X509Store store = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadOnly);
        X509Certificate2 cert = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, $"CN={CN}", false)[0];
        store.Close();

        Uri mpsUri = new Uri("http://localhost:56017/services/MaternumPdfService.svc/soap");
        X509CertificateEndpointIdentity mpsIdentity = new X509CertificateEndpointIdentity(cert);
        EndpointAddress mpsEndpoint = new EndpointAddress(mpsUri, mpsIdentity);

        WSHttpBinding mpsBinding = new WSHttpBinding()
        {
            Name = "WSHttpBinding_IMaternumPdfService",
            MaxReceivedMessageSize = Int32.MaxValue
        };
        //mpsBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
        mpsBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
        mpsBinding.Security.Message.NegotiateServiceCredential = false;
        mpsBinding.Security.Message.EstablishSecurityContext = false;
        mpsBinding.ReaderQuotas.MaxDepth = Int32.MaxValue;
        mpsBinding.ReaderQuotas.MaxStringContentLength = Int32.MaxValue;
        mpsBinding.ReaderQuotas.MaxArrayLength = Int32.MaxValue;
        mpsBinding.ReaderQuotas.MaxBytesPerRead = Int32.MaxValue;
        mpsBinding.ReaderQuotas.MaxNameTableCharCount = Int32.MaxValue;

        using (MaternumPdfServiceClient pdfService = new MaternumPdfServiceClient(mpsBinding, mpsEndpoint))
        {
            pdfService.ClientCredentials.UserName.UserName = "sample";
            pdfService.ClientCredentials.UserName.Password = "sample";
            pdfService.ClientCredentials.ClientCertificate.Certificate = cert;
            pdfService.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerTrust;
            pdfService.ClientCredentials.ServiceCertificate.DefaultCertificate = GetCertificate("MaternumCertificateClient");
            pdfService.Endpoint.Contract.Name = "MaternumPdfServiceReference.IMaternumPdfService";
            pdfService.Endpoint.Name = "EndpointBehaviorWithAuthentication";
            pdfService.Open();

            if (!pdfService.State.Equals(CommunicationState.Opened))
            {
                return null;
            }
            MemoryStream ms = new MemoryStream();
            //Next line throws the exception
            pdfService.GetMaternumPdf(myPdf.PdfType, myPdf.JsonParameters).CopyTo(ms);
        }

I thought I was constructing completely equivalent code between the C# version and the one that uses Web.Config settings, but connecting with the second one leads me to an exception:

'An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail.'

With the following InnerException:

An error occurred when verifying security for the message.

Not particularly clear... It seems to be something about message security, but the connection with Web.Config works perfectly fine, which leads me to believe I'm not setting the certificate in the correct class.

I've looked into:

WCF gives an unsecured or incorrectly secured fault error

  • Server and Client are both localhost, so it shouldn't be a time mismatch
  • Credentials are correct, as the Web.Config version uses the same credentials and validates fine
  • I'm using HTTP, not HTTPS
  • Using IISExpress, not IIS

That's about it. I think I'm setting the client certificate in the wrong place, how should I set it if not via ClientCredentials.ClientCertificate.Certificate or the identity of the endpoint?

Cloud
  • 1
  • 7

2 Answers2

0

...As is often the case, I had the wrong configuration. I was getting a certificate called "MaternumCertificateClient" instead of what my server has configured, which is "MaternumCertificateServer".

More important is how I got to the error.

First step, I set up Wireshark to see what the server replied with:

HTTP/1.1 500 Internal Server Error
Cache-Control: private
Content-Type: application/soap+xml; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ASP.NET_SessionId=k0xmopx3eitnvmocv1rjas4h; path=/; HttpOnly
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcREVWXE1BVFxNYXRlcm51bV9hcHBcc2VydmljZXNcTWF0ZXJudW1QZGZTZXJ2aWNlLnN2Y1xzb2Fw?=
X-Powered-By: ASP.NET
Date: Thu, 22 Aug 2019 16:25:19 GMT
Content-Length: 648

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
    <a:Action s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/soap/fault</a:Action>
    <a:RelatesTo>urn:uuid:uid is here</a:RelatesTo>
</s:Header>
<s:Body>
    <s:Fault>
        <s:Code>
            <s:Value>s:Sender</s:Value>
            <s:Subcode>
                <s:Value xmlns:a="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">a:InvalidSecurity</s:Value>
            </s:Subcode>
        </s:Code>
        <s:Reason>
            <s:Text xml:lang="en-GB">An error occurred when verifying security for the message.</s:Text>
        </s:Reason>
    </s:Fault>
</s:Body>
</s:Envelope>

Not much better, but I at least knew who was throwing the exception. So, I looked up if I could find out in more detail. I came across this: An error occurred when verifying security for the message

So, as indicated, I set my server with the following:

<serviceSecurityAudit auditLogLocation=“Application“ 
    serviceAuthorizationAuditLevel=“Failure“ 
    messageAuthenticationAuditLevel=“Failure“ 
    suppressAuditFailure=“true“ />

At configuration/system.serviceModel/behaviors/serviceBehaviors/behavior.

Then, Windows Event Viewer had details on the error.

Windows Event Viewer details

The message shown,

MessageSecurityException: The EncryptedKey clause was not wrapped with the required encryption token 'System.IdentityModel.Tokens.X509SecurityToken'.

indicates a mismatched certificate. I was loading one named MaternumCertificateClient, and I needed MaternumCertificateServer. Additionally, the lines

pdfService.ClientCredentials.ClientCertificate.Certificate = cert;

pdfService.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerTrust;

pdfService.ClientCredentials.ServiceCertificate.DefaultCertificate = GetCertificate("MaternumCertificateClient");

are actually not needed; the certificate is read from the endpoint's identity and these settings weren't required for my setup to work.

Cloud
  • 1
  • 7
0

As you pointed out, there is no need to provide a certificate on the client-side, except that we authenticate the client with a certificate. Such as the following configuration.

<wsHttpBinding>  
    <binding name="WSHttpBinding_ICalculator">  
      <security mode="Message">  
        <message clientCredentialType="Certificate" />  
      </security>  
    </binding>  
  </wsHttpBinding>  

Please refer to the below link. Client-side provides a certificate to represent the identity and encrypt the communication.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/message-security-with-a-certificate-client
In your binding configuration on the server-side, only the username/password credential is required.

  <security>
    <message clientCredentialType="UserName" negotiateServiceCredential="false"
      establishSecurityContext="false" />
  </security>

The certificate provides message security by encrypting the soap message with the public key. During the communication, the public key of the certificate is exchanged between the client-side and the server-side. we even could specify the server domain name as identity, it is just used to ensure the identity of the server.
Besides, if your reply solve your problem. Please mark it useful so that help others that encountered a similar issue.
Feel free to let me know if there is anything I can help with.

Abraham Qian
  • 7,117
  • 1
  • 8
  • 22
  • Hmm. So, the client needs only to a) validate the server's certificate, and b) send credentials as required by 's clientCredentialType. And, should I also want a certificate for the client, I should use TransportWithMessageCredentials, an HTTPS binding, keep my authentication with message , and add a clientCredentialType=Certificate in the Transport part? (I'll test this out when I get the chance) – Cloud Aug 29 '19 at 09:53