Securing SOAP API Calls with X.509 Signatures


When interacting with secure SOAP web services, simple username/password authentication is often not enough. A more robust approach is to use cryptographic signatures to prove the client's identity and ensure the integrity of the request data. This method involves signing parts of your request with a private key stored in an X.509 certificate.
This article walks you through a practical C# example of this process. For a complete, working example that you can run and explore, a demonstration project is available on my GitHub.
Part 1: The Core Signing Logic
The first step is to create a method that can generate a cryptographic signature. The signature is typically a hash of specific request data (like an access token and a timestamp), which is then encrypted using your private key. The server can then use your public key to verify that the signature is valid.
Our Sign
method accomplishes this using the RSACryptoServiceProvider
.
using System.Configuration;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
public static class KhurSign
{
public static byte[] Sign(string accessToken, int timeStamp)
{
// 1. Load the certificate from a .pfx file
X509Certificate2 cert = new X509Certificate2(
Configuration.KEY_PATH,
Configuration.KEY_FILE_PASSWORD,
X509KeyStorageFlags.Exportable
);
// 2. Get the RSA private key
var rsa = cert.GetRSAPrivateKey();
// 3. Ensure we use the right Crypto Service Provider for signing
var csp = new RSACryptoServiceProvider();
csp.ImportParameters(rsa.ExportParameters(true));
// 4. Prepare the data to be signed
byte[] data = Encoding.UTF8.GetBytes(accessToken + "." + timeStamp);
// 5. Sign the data hash with SHA256
return csp.SignData(data, CryptoConfig.MapNameToOID("SHA256"));
}
}
How It Works:
Load Certificate: We load our X.509 certificate from a
.pfx
file, which contains both the public and private keys.Get Private Key: We extract the RSA private key object from the certificate.
Import to CSP: We import the key parameters into a new
RSACryptoServiceProvider
to perform the signing operation.Prepare Data: The server expects a signature of a specific string. In this case, it's the
accessToken
andtimeStamp
concatenated with a ".". The server will reconstruct this exact same string for verification.Sign Data:
SignData
first computes a SHA256 hash of the data and then encrypts that hash with the private key. The result is the digital signature.
Part 2: Integrating the Signature into a SOAP Client Call
Now that we have our signing method, we need to integrate it into our WCF SOAP client. We'll use OperationContextScope
to add custom HTTP headers to the outgoing request before the API call is made.
You can see this client-side code in action in the demo project on GitHub.
using System;
using System.Net;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Security.Cryptography.X509Certificates;
public async void GetAllService() {
try
{
var metaClient = new MetaServiceClient();
// WARNING: For development only. Bypasses SSL certificate validation.
System.Net.ServicePointManager.ServerCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) => true;
var timeStamp = (int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
// Use OperationContextScope to modify the outgoing request
using (new OperationContextScope(metaClient.InnerChannel))
{
// 1. Generate the signature
var signature = KhurSign.Sign(Configuration.ACCESS_TOKEN, timeStamp);
// 2. Create a property for custom HTTP headers
var requestHeader = new HttpRequestMessageProperty();
requestHeader.Headers["accessToken"] = Configuration.ACCESS_TOKEN;
requestHeader.Headers["timeStamp"] = timeStamp.ToString();
requestHeader.Headers["signature"] = Convert.ToBase64String(signature);
// 3. Add the headers to the current operation context
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestHeader;
// 4. Make the API call
var result = metaClient.WS100001_listAccessAsync().Result;
// Process the result...
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Connection error!");
}
}
Security Warning: The line
ServerCertificateValidationCallback = ... => true;
disables all SSL/TLS validation. This is extremely insecure and should only be used in a trusted local development environment to bypass self-signed certificate errors. In a production environment, you must remove this line and ensure your client trusts the server's SSL certificate properly.
Conclusion and Demo Project
This pattern provides a powerful way to secure client-server communication. By signing key pieces of data with an X.509 certificate, the server can be confident that the request is from an authenticated source and that the critical data has not been tampered with.
To see all these pieces working together and to run the code yourself, please check out the complete demonstration project on GitHub.
I hope you found this article helpful! ๐
Subscribe to my newsletter
Read articles from Buyandelger Tsendsuren directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
