I have designed a WCF.net client that sends a SOAP request to the vendor. To meet vendor WS security requirements , I have to create a custom SOAP header and send request with the custom header to the web service on the vendor side. So i created a custom header by implementing a new class derieved from MessageHeader (see below)
public class SignOnlyMessageHeader : MessageHeader
{
private const string PREFIX_CP = "wsse";
public string m_Username { get; set; }
public string m_Envelope { get; set; }
public SignOnlyMessageHeader(string Username, string Envelope)
{
m_Username = Username;
m_Envelope = Envelope;
}
public override string Name
{
get { return "wsse:Security"; }
}
public override string Namespace
{
get { return null; }
}
public override bool MustUnderstand
{
get
{
return false;
}
}
protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
base.OnWriteStartHeader(writer, messageVersion);
writer.WriteXmlnsAttribute(PREFIX_CP, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteStartElement(PREFIX_CP, "UsernameToken", null);
writer.WriteAttributeString("wsu:Id", "UsernameToken-20");
writer.WriteXmlnsAttribute("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
writer.WriteElementString(PREFIX_CP, "Username", null, m_Username);
writer.WriteEndElement();
SignXmlFile(writer);
}
public void SignXmlFile(XmlDictionaryWriter writer)
{
string certificatePath = "C:\\Users\\22428-cert.p12";
System.Security.Cryptography.X509Certificates.X509Certificate2 cert = new X509Certificate2(certificatePath, "changeit");
// Create a new XML document.
XmlDocument doc = new XmlDocument();
// Format the document to ignore white spaces.
doc.PreserveWhitespace = false;
doc.LoadXml(m_Envelope);
// Create a SignedXml object.
SignedXml signedXml = new SignedXml(doc);
// Add the key to the SignedXml document.
//signedXml.SigningKey = Key;
signedXml.SigningKey = cert.PrivateKey;
// Create a new KeyInfo object.
KeyInfo keyInfo = new KeyInfo();
keyInfo.Id = "";
// 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;
signedXml.SignedInfo.CanonicalizationMethod = "http://www.w3.org/2001/10/xml-exc-c14n#";
// 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);
reference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
signedXml.Signature.Id = "";
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
// Check the signature and return the result.
if (!signedXml.CheckSignature(new X509Certificate2(certificatePath, "changeit"), true))
{
Console.WriteLine("invalid signature");
}
xmlDigitalSignature.WriteTo(writer);
}
So after creating the custom header class, I overrode the IClientMessageInspector.BeforeSendRequest method to intercept the outgoing request and add my custom header to the soap request. See code below,
object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)
{
request.Headers.RemoveAt(0);
SignOnlyMessageHeader header = new SignOnlyMessageHeader("x509user", env);
request.Headers.Add(header);
return null;
}
The end result is I am intercepting the SOAP request and correctly replacing the current header with the custom header. Before the request is sent out , I checked the updated the SOAP request (placed a breakpoint) , the structure matches EXACTLY what the vendor requested. But I receive an error after the request is processed at vendor side. It only says "Signature failed core validation". I think I am correctly signing the entire envelop in the "SignXmlFile" method. I even checked the validity within the method (if (!signedXml.CheckSignature(new X509Certificate2(certificatePath, "changeit"), true))), the statement returns a false which indicates signature is valid.
What am I doing wrong ?
Well I tried and tried, something with the way I am intercepting the header and after i inject the header with the Signature ..the validation is failing. As a work around, I striped out the entire header from my .net client. I am routing my request with just the soap baod to a XML gateway, we configured the gateway to intercept the request and add the necessary header init and forward the request to the external vendor. It worked.
Related
The idea of the web app is to sign documents with a digital signature that is loaded from a smart card.
It is published and set to work on a local user machine. I am using IIS for that matter to set the bindings and enable to accept client certificates.
It communicates with a web app that is hosted on the cloud.
I am successfully getting the certificate from the smart card and the private key as well.
I use the private key to sign the document.
private InvoiceResult SignDocument(XmlDocument doc)
{
InvoiceResult resultValue;
try
{
var (resultValue2, certificate) = GetDefaultCertificateStoredOnTheCard();
resultValue = resultValue2;
SignXmlDocumentWithCertificate(doc, certificate);
resultValue = InvoiceResult.Success;
}
catch (Exception ex)
{
_log.TraceInformation($"Error when compute signature and it is : {ex.Message}");
_log.TraceInformation($"Additional info => stack trace : {ex.StackTrace}");
resultValue = InvoiceResult.CannotSignXmlFiles;
}
return resultValue;
}
public (InvoiceResult resultValue, X509Certificate2 cert) GetDefaultCertificateStoredOnTheCard()
{
var resultValue = InvoiceResult.Success;
using X509Store x509Store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
X509Store store = x509Store;
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true);
certs = certs.Find(X509FindType.FindByThumbprint, Settings.Default.Thumbprint, true);
if (certs.Count == 0)
{
resultValue = InvoiceResult.CannotFindSignature;
}
X509Certificate2 cert = certs[0];
if (cert.HasPrivateKey)
{
// software cert
_ = cert.PrivateKey as RSACryptoServiceProvider;
}
else
{
// certificate from smartcard
CspParameters csp = new CspParameters(1, "Microsoft Base Smart Card Crypto Provider")
{
Flags = CspProviderFlags.UseDefaultKeyContainer
};
_ = new RSACryptoServiceProvider(csp);
}
return (resultValue, cert);
}
private InvoiceResult SignXmlDocumentWithCertificate(XmlDocument xmlDoc, X509Certificate2 cert)
{
InvoiceResult resultValue = InvoiceResult.Success;
SignedXml signedXml = new SignedXml(xmlDoc)
{
//we will sign it with private key
SigningKey = cert.PrivateKey
};
if (cert.PrivateKey == null)
{
resultValue = InvoiceResult.CannotSignXmlFiles;
// throw new ArgumentException("Please make sure the application for electronic signatures is installed, so the private key can be obtained from the smart card!");
}
Reference reference = new Reference
{
//sign the entire doc
Uri = ""
};
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
signedXml.AddReference(reference);
//PublicKey part
RSACryptoServiceProvider rsaprovider = (RSACryptoServiceProvider)cert.PublicKey.Key;
RSAKeyValue rkv = new RSAKeyValue(rsaprovider);
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
//We add the public key here
keyInfo.AddClause(rkv);
signedXml.KeyInfo = keyInfo;
_log.TraceInformation($"Cert has private key or not? {cert.HasPrivateKey}");
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
_log.TraceInformation($"It computes the signature succesfully");
XmlElement xmlDigitalSignature = signedXml.GetXml();
// Append the element to the XML document.
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
_log.TraceInformation($"It appends the signature succesfully");
return resultValue;
}
It works fine on Release/Debug but not in Publish. It gets a popup, asks for a PIN and once the PIN has been entered the docs are signed.
It gets to the signedxml.ComputeSignature and it returns an error :
The operation was canceled by the user.
Here is the exception that has been thrown :
System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)
at System.Security.Cryptography.Utils.SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash, Int32 cbHash, ObjectHandleOnStack retSignature)
at System.Security.Cryptography.Utils.SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash)
at System.Security.Cryptography.RSACryptoServiceProvider.SignHash(Byte[] rgbHash, Int32 calgHash)
at System.Security.Cryptography.Xml.SignedXml.ComputeSignature()
The only way I get this error on release/debug is if I cancel the window which asks for a PIN.
Is there another way to compute the signature and apply it to the XML? This is the only one I was able to find so far.
It potentially could be an IIS setting, but I have tried various things to no avail. The certificate can be found if I require SSL on Client Side is ticked and set it to Accept as in the image :
I have also tried exporting the private key which I saw on various posts, however, because it is a smart card I am unable to export the Private Key, I can only use it which is what I am doing with my code.
Once I start this part of the application it asks initially for the certificate and the PIN and returns the same error. On subsequent attempts , it never asks for the PIN or the certificate.
Change app pool identity to windows account
I am creating a webhook in a .Net Core 3 Web API for DocuSign Connect to invoke and provide me status updates + signed documents from envelopes my app has created. The C# example at https://www.docusign.com/blog/dsdev-adding-webhooks-application was very helpful in getting me almost to my goal. The code from the example is:
[HttpPost("api/[controller]/ConnectWebHook")]
public void ConnectWebHook(HttpRequestMessage request)
{
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(request.Content.ReadAsStreamAsync().Result);
var mgr = new XmlNamespaceManager(xmldoc.NameTable);
mgr.AddNamespace("a", "http://www.docusign.net/API/3.0");
XmlNode envelopeStatus = xmldoc.SelectSingleNode("//a:EnvelopeStatus", mgr);
XmlNode envelopeId = envelopeStatus.SelectSingleNode("//a:EnvelopeID", mgr);
XmlNode status = envelopeStatus.SelectSingleNode("./a:Status", mgr);
var targetFileDirectory = #"\\my-network-share\";
if (envelopeId != null)
{
System.IO.File.WriteAllText($"{targetFileDirectory}{envelopeId.InnerText}_{status.InnerText}_.xml", xmldoc.OuterXml);
}
if (status.InnerText == "Completed")
{
// Loop through the DocumentPDFs element, storing each document.
XmlNode docs = xmldoc.SelectSingleNode("//a:DocumentPDFs", mgr);
foreach (XmlNode doc in docs.ChildNodes)
{
string documentName = doc.ChildNodes[0].InnerText; // pdf.SelectSingleNode("//a:Name", mgr).InnerText;
string documentId = doc.ChildNodes[2].InnerText; // pdf.SelectSingleNode("//a:DocumentID", mgr).InnerText;
string byteStr = doc.ChildNodes[1].InnerText; // pdf.SelectSingleNode("//a:PDFBytes", mgr).InnerText;
System.IO.File.WriteAllText($"{targetFileDirectory}{envelopeId.InnerText}_{documentId}_{documentName}", byteStr);
}
}
}
For testing purposes, my Web API is allowing all origins and exposed to the outside world via NGROK, and I can hit other test endpoints (both GET and POST), but for some reason this webhook is not being hit by Connect when there is a notification-worthy event on my envelope.
I can see in the DocuSign Admin portal logs that Connect invoked my webhook but got The remote server returned an error: (415) Unsupported Media Type.. This led me to add the [FromBody] attribute to my method signature like so but I still get the same error when my webhook is invoked by Connect.
[HttpPost("api/[controller]/ConnectWebHook")]
public void ConnectWebHook([FromBody] HttpRequestMessage request)
{
// ... rest of the method was unchanged, removed for brevity
}
I have never used HttpRequestMessage before but it looks straightforward enough. I noticed in the DocuSign Admin portal logs that the data that Connect tried to send to the webhook is just XML. I could try to change the webhook's signature to look for an XmlDocument instead of an HttpRequestMessage but I am not sure what, if anything, I will be missing out on.
Has anyone else integrated with Connect via a webhook recently? And were you able to make the HttpRequestMessage work for you?
Added on 10/18/2019:
DocuSign mentions that the content type is XML. Here is what the content looks like:
<DocuSignEnvelopeInformation
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.docusign.net/API/3.0">
<EnvelopeStatus>...</EnvelopeStatus>
<DocumentPDFs>...</DocumentPDFs>
</DocuSignEnvelopeInformation>
I have added AddXmlSerializerFormatters() to the ConfigureServices method in Startup.cs. This being .Net Core 3, I had to set it up like services.AddControllers().AddXmlSerializerFormatters() instead of services.AddMVC().AddXmlSerializerFormatters() per https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.0&tabs=visual-studio.
With that change, I have now tried using [FromForm] like so and my webhook IS being hit, but the request input parameter is essentially empty ... request.Content = null:
[HttpPost("api/[controller]/ConnectWebHook")]
public void ConnectWebHook([FromForm] HttpRequestMessage request)
{
// ... rest of the method was unchanged, removed for brevity
}
Since the request is being sent from DocuSign Connect, I have no control over the headers/format/content. As far as I can tell, they are not submitting an XML object, not a form, so [FromForm] is probably not the way to go.
That linked example is not for .net core. HttpRequestMessage is no longer a first class citizen in asp.net-core framework and will treated as a normal model.
Just extract the content directly from the Request's body and the rest should be able to remain the same as in the example.
[HttpPost("api/[controller]/ConnectWebHook")]
public IActionResult ConnectWebHook() {
Stream stream = Request.Body;
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(stream);
var mgr = new XmlNamespaceManager(xmldoc.NameTable);
mgr.AddNamespace("a", "http://www.docusign.net/API/3.0");
XmlNode envelopeStatus = xmldoc.SelectSingleNode("//a:EnvelopeStatus", mgr);
XmlNode envelopeId = envelopeStatus.SelectSingleNode("//a:EnvelopeID", mgr);
XmlNode status = envelopeStatus.SelectSingleNode("./a:Status", mgr);
var targetFileDirectory = #"\\my-network-share\";
if (envelopeId != null) {
System.IO.File.WriteAllText($"{targetFileDirectory}{envelopeId.InnerText}_{status.InnerText}_.xml", xmldoc.OuterXml);
}
if (status.InnerText == "Completed") {
// Loop through the DocumentPDFs element, storing each document.
XmlNode docs = xmldoc.SelectSingleNode("//a:DocumentPDFs", mgr);
foreach (XmlNode doc in docs.ChildNodes) {
string documentName = doc.ChildNodes[0].InnerText; // pdf.SelectSingleNode("//a:Name", mgr).InnerText;
string documentId = doc.ChildNodes[2].InnerText; // pdf.SelectSingleNode("//a:DocumentID", mgr).InnerText;
string byteStr = doc.ChildNodes[1].InnerText; // pdf.SelectSingleNode("//a:PDFBytes", mgr).InnerText;
System.IO.File.WriteAllText($"{targetFileDirectory}{envelopeId.InnerText}_{documentId}_{documentName}", byteStr);
}
}
return Ok();
}
I'm trying to attach an api key to the OperationContext outgoing message header as follows:
public static void AddApikeyToHeader(string apikey, IContextChannel channel, string address)
{
using (OperationContextScope scope = new OperationContextScope(channel))
{
MessageHeader header = MessageHeader.CreateHeader("apikey", address, apikey);
OperationContext.Current.OutgoingMessageHeaders.Add(header);
}
}
but then I have no idea how to retrieve the header on the server side. I'm using a Service authorisation manager and I get the current operating context and try to retrieve the header like this:
public string GetApiKey(OperationContext operationContext)
{
var request = operationContext.RequestContext.RequestMessage;
var prop = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
return prop.Headers["apikey"];
}
but there is no apikey header attached there. Also, on debugging when I inspect the operationContext I cant seem to see my apikey header anywhere. Can anyone see where I'm going wrong?
You can add custom header by this way :
using (ChannelFactory<IMyServiceChannel> factory =
new ChannelFactory<IMyServiceChannel>(new NetTcpBinding()))
{
using (IMyServiceChannel proxy = factory.CreateChannel(...))
{
using ( OperationContextScope scope = new OperationContextScope(proxy) )
{
Guid apiKey = Guid.NewGuid();
MessageHeader<Guid> mhg = new MessageHeader<Guid>(apiKey);
MessageHeader untyped = mhg.GetUntypedHeader("apiKey", "ns");
OperationContext.Current.OutgoingMessageHeaders.Add(untyped);
proxy.DoOperation(...);
}
}
}
And service side, you can get header like :
Guid apiKey =
OperationContext.Current.IncomingMessageHeaders.GetHeader<Guid>("apiKey", "ns");
I'm assuming that you trying to consume your service using some Http Protocol based transport (SOAP, REST etc). I'm also assuming that what you want is to authorize the caller using the supplied API key. If both of those conditions apply to your question, you can read on.
I recently had to tackle a similar problem only that I did not pass an API key but a username/password hash combination using some HTTP custom headers. I ultimately solved it by implementing a custom authorization policy that once configured in Web.config hooked nicely into the WCF Pipeline.
The snippet below should be enough to get you started. You probably would have to replace the x-ms-credentials-XXX headers by a single one representing your API key.
internal class RESTAuthorizationPolicy : IAuthorizationPolicy
{
public RESTAuthorizationPolicy()
{
Id = Guid.NewGuid().ToString();
Issuer = ClaimSet.System;
}
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
const String HttpRequestKey = "httpRequest";
const String UsernameHeaderKey = "x-ms-credentials-username";
const String PasswordHeaderKey = "x-ms-credentials-password";
const String IdentitiesKey = "Identities";
const String PrincipalKey = "Principal";
// Check if the properties of the context has the identities list
if (evaluationContext.Properties.Count > 0 ||
evaluationContext.Properties.ContainsKey(IdentitiesKey) ||
!OperationContext.Current.IncomingMessageProperties.ContainsKey(HttpRequestKey))
return false;
// get http request
var httpRequest = (HttpRequestMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpRequestKey];
// extract credentials
var username = httpRequest.Headers[UsernameHeaderKey];
var password = httpRequest.Headers[PasswordHeaderKey];
// verify credentials complete
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return false;
// Get or create the identities list
if (!evaluationContext.Properties.ContainsKey(IdentitiesKey))
evaluationContext.Properties[IdentitiesKey] = new List<IIdentity>();
var identities = (List<IIdentity>) evaluationContext.Properties[IdentitiesKey];
// lookup user
using (var con = ServiceLocator.Current.GetInstance<IDbConnection>())
{
using (var userDao = ServiceLocator.Current.GetDao<IUserDao>(con))
{
var user = userDao.GetUserByUsernamePassword(username, password);
...
Did you take a look at this question: How to add a custom HTTP header to every WCF call? ? It may contain your solution.
I have the following code in C# that looks for an apiKey in the the following SOAP header:
SOAP Header:
<soap:Header>
<Authentication>
<apiKey>CCE4FB48-865D-4DCF-A091-6D4511F03B87</apiKey>
</Authentication>
</soap:Header>
C#:
This is what I have so far:
public string GetAPIKey(OperationContext operationContext)
{
string apiKey = null;
// Look at headers on incoming message.
for (int i = 0; i < OperationContext.Current.IncomingMessageHeaders.Count; i++)
{
MessageHeaderInfo h = OperationContext.Current.IncomingMessageHeaders[i];
// For any reference parameters with the correct name.
if (h.Name == "apiKey")
{
// Read the value of that header.
XmlReader xr = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(i);
apiKey = xr.ReadElementContentAsString();
}
}
// Return the API key (if present, null if not).
return apiKey;
}
PROBLEM: Returning null instead of the actual apiKey value:
CCE4FB48-865D-4DCF-A091-6D4511F03B87
UPDATE 1:
I added some logging. It looks like h.Name is in fact "Authentication", which means it won't actually be looking for "apiKey", which then means it won't be able to retrieve the value.
Is there a way to grab the <apiKey /> inside of <Authentication />?
UPDATE 2:
Ended up using the following code:
if (h.Name == "Authentication")
{
// Read the value of that header.
XmlReader xr = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(i);
xr.ReadToDescendant("apiKey");
apiKey = xr.ReadElementContentAsString();
}
I think your h.Name is Authentication because it is root type and apiKey is property of Authentication type. Try logging values of h.Name to some log file and check what does it return.
if (h.Name == "Authentication")
{
// Read the value of that header.
XmlReader xr = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(i);
//apiKey = xr.ReadElementContentAsString();
xr.ReadToFollowing("Authentication");
apiKey = xr.ReadElementContentAsString();
}
Ended up using the following code:
if (h.Name == "Authentication")
{
// Read the value of that header.
XmlReader xr = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(i);
xr.ReadToDescendant("apiKey");
apiKey = xr.ReadElementContentAsString();
}
There is a shorter solution:
public string GetAPIKey(OperationContext operationContext)
{
string apiKey = null;
MessageHeaders headers = OperationContext.Current.RequestContext.RequestMessage.Headers;
// Look at headers on incoming message.
if (headers.FindHeader("apiKey","") > -1)
apiKey = headers.GetHeader<string>(headers.FindHeader("apiKey",""));
// Return the API key (if present, null if not).
return apiKey;
}
I've added a custom soap header <MyApp:FOO> element to the <soap:Header> element and the requirments states that i must sign this element , how would one do that?
<MyApp:FOO> contains a number of things (username, preferences, etc) that identifies a user on higher level.
I've succesfully used a policy file and now a policyClass with CertificateAssertions and SoapFilters to sign wsu:Timestamp, wsu:action, wsu:MessageId etc. But now the <MyApp:FOO> element needs to signed aswell.
What i've understood this far is that the element that needs to be signed must be indentified with a wsu:Id attribute and then transformed using xml-exc-c14n.
So, how do I specify that the soap header should be signed aswell?
This is the current class that i use for signing my message.
internal class FOOClientOutFilter: SendSecurityFilter
{
X509SecurityToken clientToken;
public FOOClientOutFilter(SSEKCertificateAssertion parentAssertion)
: base(parentAssertion.ServiceActor, true)
{
// Get the client security token.
clientToken = X509TokenProvider.CreateToken(StoreLocation.CurrentUser, StoreName.My, "CN=TestClientCert");
// Get the server security token.
serverToken = X509TokenProvider.CreateToken(StoreLocation.LocalMachine, StoreName.My, "CN=TestServerCert");
}
public override void SecureMessage(SoapEnvelope envelope, Security security)
{
// Sign the SOAP message with the client's security token.
security.Tokens.Add(clientToken);
security.Elements.Add(new MessageSignature(clientToken));
}
}
My current version of SecureMessage seems to do the trick..
public override void SecureMessage(SoapEnvelope envelope, Security security)
{
//EncryptedData data = new EncryptedData(userToken);
SignatureReference ssekSignature = new SignatureReference();
MessageSignature signature = new MessageSignature(clientToken);
// encrypt custom headers
for (int index = 0; index < envelope.Header.ChildNodes.Count; index++)
{
XmlElement child =
envelope.Header.ChildNodes[index] as XmlElement;
// find all FOO headers
if (child != null && child.Name == "FOO")
{
string id = Guid.NewGuid().ToString();
child.SetAttribute("Id", "http://docs.oasis-" +
"open.org/wss/2004/01/oasis-200401-" +
"wss-wssecurity-utility-1.0.xsd", id);
signature.AddReference(new SignatureReference("#" + id));
}
}
// Sign the SOAP message with the client's security token.
security.Tokens.Add(clientToken);
security.Elements.Add(signature);
}
Including supplementary articles from MSDN
How to: Add an Id Attribute to a SOAP Header
How to: Digitally Sign a Custom SOAP Header