Search code examples
c#soapcertificatesoapuipfx

Send SOAP request with PFX Certificate via C#


I need to call a SOAP web service with PFX certificate on. I'm trying to write it in .NET 4.7.2 Console App. Below is what I wrote.

public async Task<string> CreateSoapEnvelope()
{
    string soapString = @"<?xml version=""1.0"" encoding=""utf-8""?>
  <soapenv:Envelope xmlns:soapenv=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:b2b=""Some_URL"">
       <soapenv:Header/>
       <soapenv:Body>
          SOAP_BODY
       </soapenv:Body>
    </soapenv:Envelope>";       

    

    HttpResponseMessage response = await PostXmlRequest("CLIENT_URL", soapString);
    string content = await response.Content.ReadAsStringAsync();

    return content;
}

public static async Task<HttpResponseMessage> PostXmlRequest(string baseUrl, string xmlString)
{
    try
    {
        // Create HttpClientHandler instance
        var handler = new HttpClientHandler();

        // Add the certificate
        var certPath = @"PathToCertificate"; // Path to PFX fle
        var cert = new X509Certificate2(certPath, "Password");
        handler.ClientCertificates.Add(cert);
        using (var httpClient = new HttpClient(handler))
        {
            var httpContent = new StringContent(xmlString, Encoding.UTF8, "text/xml");
            httpContent.Headers.Add("SOAPAction", "");

            return await httpClient.PostAsync(baseUrl, httpContent);
        }
    }catch(Exception ex)
    {
    }
    return null;
}

Now when I call CreateSoapEnvelope(), it always error out with InternalServerError.

Below is the fault string I received.

<faultstring>No signature in message! (from client). Rejected by filter; SOAP fault sent.Rejected by filter; SOAP fault sent. </faultstring><detail><service_error_message>No signature in message!</service_error_message>

Am I doing anything wrong? I'm completely new to SOAP and PFX. Any help is appreciated.


Solution

  • As mentioned in one of the comment by @bartonjs, the issue was that the service was expecting my message to be signed. What I ended up doing is added Signature explicitly to the message and it worked. I was in impression that the client certificate itself should be fine however that was not the case. I'm still looking for cleaner solution but for now below worked.

     public static async Task<string> SignXml(XmlDocument document, X509Certificate2 cert)
        {
            return await Task.Run(() =>
            {
                CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription), "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
    
                // Export private key from cert.PrivateKey and import into a PROV_RSA_AES provider:
                var exportedKeyMaterial = cert.PrivateKey.ToXmlString( /* includePrivateParameters = */ true);
                var key = new RSACryptoServiceProvider(new CspParameters(24 /* PROV_RSA_AES */));
                key.PersistKeyInCsp = false;
                key.FromXmlString(exportedKeyMaterial);
    
    
                SignedXml signedXml = new SignedXml(document);
                signedXml.SigningKey = key;
                signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
    
                // 
                // Add a signing reference, the uri is empty and so the whole document 
                // is signed. 
                Reference reference = new Reference();
                reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
                reference.AddTransform(new XmlDsigExcC14NTransform());
                reference.Uri = "";
                signedXml.AddReference(reference);
    
                
                // Add the certificate as key info, because of this the certificate 
                // with the public key will be added in the signature part. 
                KeyInfo keyInfo = new KeyInfo();
                keyInfo.AddClause(new KeyInfoX509Data(cert));
                signedXml.KeyInfo = keyInfo;
    
    
    
                // Compute the signature.
                signedXml.ComputeSignature();
    
                // Get the XML representation of the signature and save 
                // it to an XmlElement object.
                XmlElement xmlDigitalSignature = signedXml.GetXml();
    
                //get the SOAP-SEC header and add our signature to it
                var soapSecurityList = document.GetElementsByTagName("Header", "http://schemas.xmlsoap.org/soap/envelope/");
                if (soapSecurityList.Count == 0)
                {
                    throw new Exception("Could not find SOAP-SEC header!");
                }
                var soapSecurity = soapSecurityList.Item(0);
    
                soapSecurity.AppendChild(xmlDigitalSignature);
    
                return document.OuterXml;
            });
        }