I was able to resolve my issue and connect to DataPower (IBM Xi50) Web Service Gateway using the following WCF CustomBinding (CertificateOverTransport) and CustomCredentials (UsernameToken with Password Digest, client cert for transport authentication and service cert for message body signature.) I am not sure what exactly fixed the issue, but here is my working WCF code! I hope this helps others who are in a similar situation as I was.
Please verify that the DataPower Xi50 gateway is also configured for WCF. From IBM: "When using BasicHttpBinding with SSL: You may use disable-ssl-cipher-check parameter to disable cipher checks for any TransportBinding assertions. The Basic Auth Header is not supported by default in the Web services proxy. A custom configuration of an on-error rule to inject the WWW-Authenticate header is required to inter-op with WCF." For details, go here: https://publib.boulder.ibm.com/infocenter/ieduasst/v1r1m0/index.jsp?topic=/com.ibm.iea.wdatapower/wdatapower/1.0/xa35/380DataPowerWCFIntegration/player.html.
Make sure you have set ProtectionLevel.Sign on your service contract if you want your message body signed only (and not Encrypted.)
For DNS Identity, which I had issues with earlier, I was now able to put my client certificate subject name - earlier this wouldn't work.
I don't have any configuration in my web.config.
Here is the Proxy using CustomBinding:
private ClientProxy GetProxy()
{
XXXServiceClient proxy = new XXXServiceClient(GetCustomBinding(), new EndpointAddress(new Uri("<<GatewayURLHere>>"), EndpointIdentity.CreateDnsIdentity("<<DNS or Client Cert Subject Name>>"), new AddressHeaderCollection()));
proxy.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
proxy.Endpoint.Behaviors.Add(new CustomCredentials(<clientCertHere>, <signingCertHere>));
proxy.ClientCredentials.UserName.UserName = @"XXX";
proxy.ClientCredentials.UserName.Password = "yyy";
return proxy;
}
private Binding GetCustomBinding()
{
TransportSecurityBindingElement secBE = SecurityBindingElement.CreateCertificateOverTransportBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
secBE.EndpointSupportingTokenParameters.Signed.Add(new UserNameSecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.Never, RequireDerivedKeys = false });
secBE.EnableUnsecuredResponse = true;
secBE.IncludeTimestamp = true;
TextMessageEncodingBindingElement textEncBE = new TextMessageEncodingBindingElement(MessageVersion.Soap11WSAddressingAugust2004, System.Text.Encoding.UTF8);
HttpsTransportBindingElement httpsBE = new HttpsTransportBindingElement();
httpsBE.RequireClientCertificate = true;
CustomBinding myBinding = new CustomBinding();
myBinding.Elements.Add(secBE);
myBinding.Elements.Add(textEncBE);
myBinding.Elements.Add(httpsBE);
return myBinding;
}
Here is my CustomCredentials class that I put together from multiple sources including the above mentioned UsernameToken library - sets client certificate for (mutual?) authentication at the transport layer, service/signing certificate for signing the message body and UsernameToken with Password Digest in the SOAP header:
using System;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.Text;
namespace XXX_WCF
{
public class CustomCredentials : ClientCredentials
{
private X509Certificate2 clientAuthCert;
private X509Certificate2 clientSigningCert;
public CustomCredentials() : base() { }
public CustomCredentials(CustomCredentials other)
: base(other)
{
clientSigningCert = other.clientSigningCert;
clientAuthCert = other.clientAuthCert;
}
protected override ClientCredentials CloneCore()
{
CustomCredentials scc = new CustomCredentials(this);
return scc;
}
public CustomCredentials(X509Certificate2 ClientAuthCert, X509Certificate2 ClientSigningCert)
: base()
{
clientAuthCert = ClientAuthCert;
clientSigningCert = ClientSigningCert;
}
public X509Certificate2 ClientAuthCert
{
get { return clientAuthCert; }
set { clientAuthCert = value; }
}
public X509Certificate2 ClientSigningCert
{
get { return clientSigningCert; }
set { clientSigningCert = value; }
}
public override SecurityTokenManager CreateSecurityTokenManager()
{
return new CustomTokenManager(this);
}
}
public class CustomTokenManager : ClientCredentialsSecurityTokenManager
{
private CustomCredentials custCreds;
public CustomTokenManager(CustomCredentials CustCreds)
: base(CustCreds)
{
custCreds = CustCreds;
}
public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
{
if (tokenRequirement.TokenType == SecurityTokenTypes.X509Certificate)
{
x509CustomSecurityTokenProvider prov;
object temp = null;
TransportSecurityBindingElement secBE = null;
if (tokenRequirement.Properties.TryGetValue("http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/SecurityBindingElement", out temp))
{
secBE = (TransportSecurityBindingElement)temp;
}
if (secBE == null)
prov = new x509CustomSecurityTokenProvider(custCreds.ClientAuthCert);
else
prov = new x509CustomSecurityTokenProvider(custCreds.ClientSigningCert);
return prov;
}
return base.CreateSecurityTokenProvider(tokenRequirement);
}
public override System.IdentityModel.Selectors.SecurityTokenSerializer CreateSecurityTokenSerializer(System.IdentityModel.Selectors.SecurityTokenVersion version)
{
return new CustomTokenSerializer(System.ServiceModel.Security.SecurityVersion.WSSecurity10);
}
}
class x509CustomSecurityTokenProvider : SecurityTokenProvider
{
private X509Certificate2 clientCert;
public x509CustomSecurityTokenProvider(X509Certificate2 cert)
: base()
{
clientCert = cert;
}
protected override SecurityToken GetTokenCore(TimeSpan timeout)
{
return new X509SecurityToken(clientCert);
}
}
public class CustomTokenSerializer : WSSecurityTokenSerializer
{
public CustomTokenSerializer(SecurityVersion sv) : base(sv) { }
protected override void WriteTokenCore(System.Xml.XmlWriter writer, System.IdentityModel.Tokens.SecurityToken token)
{
if (writer == null)
{
throw new ArgumentNullException("writer");
}
if (token == null)
{
throw new ArgumentNullException("token");
}
if (token.GetType() == new UserNameSecurityToken("x", "y").GetType())
{
UserNameSecurityToken userToken = token as UserNameSecurityToken;
if (userToken == null)
{
throw new ArgumentNullException("userToken: " + token.ToString());
}
string tokennamespace = "o";
DateTime created = DateTime.Now;
string createdStr = created.ToString("yyyy-MM-ddThh:mm:ss.fffZ");
string phrase = Guid.NewGuid().ToString();
string nonce = GetSHA1String(phrase);
string password = GetSHA1String(nonce + createdStr + userToken.Password);
//string password = userToken.Password;
writer.WriteStartElement(tokennamespace, "UsernameToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteAttributeString("u", "Id", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", token.Id);
writer.WriteElementString(tokennamespace, "Username", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", userToken.UserName);
writer.WriteStartElement(tokennamespace, "Password", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
writer.WriteValue(password);
writer.WriteEndElement();
writer.WriteStartElement(tokennamespace, "Nonce", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteAttributeString("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
writer.WriteValue(nonce);
writer.WriteEndElement();
writer.WriteElementString(tokennamespace, "Created", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", createdStr);
writer.WriteEndElement();
writer.Flush();
}
else
{
base.WriteTokenCore(writer, token);
}
}
protected string GetSHA1String(string phrase)
{
SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
return Convert.ToBase64String(hashedDataBytes);
}
}//CustomTokenSerializer
}
Good luck!