Programmatic WCF Message Security with Certificates

mattmck picture mattmck · May 18, 2011 · Viewed 9.4k times · Source

I've written a self-hosted WCF service using WSHttpBindings and I'm trying to implement message-level security using certificates I've generated myself. Unfortunately I'm getting a buried exception (via the Service Trace Viewer) stating "The credentials supplied to the package were not recognized."

A couple notes:

  1. This has to be done in code, not in configuration
  2. (Server/Client)Cert are certificates that are in the local machine store with accessible private keys to my user while debugging.
  3. I've googled the hell out of this and found a good resource for setting up WCF message based security here

I'm not sure what I'm missing. Most of this stuff seems straight forward except for creating the endpoint identities. It fails with the same message whether I use DnsEndpointIdentities, cert based ones, or no identities at all.

Can anyone point me in the right direction?

Server side:

var binding = new WSHttpBinding
    {
      Security =
      {
        Mode = SecurityMode.Message,
        Message = 
        {
          ClientCredentialType = MessageCredentialType.Certificate,
          AlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256Rsa15
        }
      }
    };

_host = new ServiceHost(this)
{
  Credentials =
  {
    ServiceCertificate =
    {
      Certificate = ServiceCert
    },
    ClientCertificate =
    {
      Certificate = ClientCert,
      Authentication =
      {
        TrustedStoreLocation = StoreLocation.LocalMachine,
        RevocationMode = X509RevocationMode.NoCheck,
        CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust
       }
    }
  }
};
var address = new Uri(string.Format(@"http://serviceaddress"));
var ep = _host.AddServiceEndpoint(typeof (IService), binding, address);
ep.Address = new EndpointAddress(address, EndpointIdentity.CreateX509CertificateIdentity(ServiceCert));
_host.Open();

Client side:

var binding = new WSHttpBinding
    {
      Security =
      {
        Mode = SecurityMode.Message,
        Message =
        { 
          ClientCredentialType = MessageCredentialType.Certificate,
          AlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256Rsa15
        }
      }
    };
var address = new Uri(@"http://serviceaddress");
var endpoint = new EndpointAddress(address, EndpointIdentity.CreateX509CertificateIdentity(ServerCert));
var channelFactory = new ChannelFactory<IService>(binding, endpoint)
    {
      Credentials =
      {
        ServiceCertificate =
        {
          DefaultCertificate = ServerCert,
          Authentication =
          {
            RevocationMode = X509RevocationMode.NoCheck,
            TrustedStoreLocation = StoreLocation.LocalMachine,
            CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust
          }
        },
        ClientCertificate =
        {
          Certificate = ClientCert
        }
      }
    };
var channel = channelFactory.CreateChannel();

Answer

mattmck picture mattmck · May 18, 2011

this msdn article helped tremendously. I think the root of the problem was setting the following message security parameters to false:

httpBinding.Security.Message.NegotiateServiceCredential = false;
httpBinding.Security.Message.EstablishSecurityContext = false;

So now the overall code for the server side looks more like:

var httpBinding = new WSHttpBinding(SecurityMode.Message);
httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
httpBinding.Security.Message.NegotiateServiceCredential = false;
httpBinding.Security.Message.EstablishSecurityContext = false;
var httpUri = new Uri("http://serviceaddress");
_host = new ServiceHost(this, httpUri);
_host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, serverThumbprint);
_host.Credentials.ClientCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
_host.Credentials.ClientCertificate.Authentication.TrustedStoreLocation = StoreLocation.LocalMachine;
_host.AddServiceEndpoint(typeof(IMetaService), httpBinding, httpUri);
_host.Open();

and the client side:

var httpBinding = new WSHttpBinding(SecurityMode.Message);
httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
httpBinding.Security.Message.NegotiateServiceCredential = false;
httpBinding.Security.Message.EstablishSecurityContext = false;
var httpUri = new Uri("http://serviceaddress");
var httpEndpoint = new EndpointAddress(httpUri, EndpointIdentity.CreateDnsIdentity("name of server cert"));
var newFactory = new ChannelFactory<IMetaService>(httpBinding, httpEndpoint);
newFactory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, "client certificate thumbprint");
newFactory.Credentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, "server certificate thumbprint");
var channel = newFactory.CreateChannel();