SSL client/server mutual authentication - c#

Hello I am trying to do in C# an ssl client/server communication with mutual authentication using server and client certificate. A managed to do the ssl communication only using server certificate, where on the client side I use sth like that:
TcpClient client = new TcpClient(machineName, port);
//Create an SSL stream that will close the client's stream.
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
try
{
// The server name must match the name on the server certificate.
sslStream.AuthenticateAsClient(serverName);
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
client.Close();
return;
}
I assume I would need to use
AuthenticateAsClient(string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
method, am I corrent? Could anyone please show me how to use it with all things around?Even on the server side, or point me to a basic example?
Thank you a lot.

static void HTTPSClient()
{
try
{
string message = "GET / HTTP/1.0\r\nHost: host.com\r\n\r\n";
byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
string server = "host.com";
int nPort = 443;
TcpClient client = new TcpClient(server, nPort);
X509Certificate2Collection cCollection = new X509Certificate2Collection();
cCollection.Add(new X509Certificate2("cert.pfx", "password"));
using (SslStream sslStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null))
{
// Add a client certificate to the ssl connection
sslStream.AuthenticateAsClient(server, cCollection, System.Security.Authentication.SslProtocols.Default, true);
sslStream.Write(data, 0, data.Length);
data = new Byte[8192];
int bytes = 0;
string responseData = "";
do
{
bytes = sslStream.Read(data, 0, data.Length);
if (bytes > 0)
{
responseData += System.Text.Encoding.ASCII.GetString(data, 0, bytes);
}
}
while (bytes > 0);
Console.WriteLine("Response: " + responseData);
}
// Disconnect and close the client
client.Close();
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.ToString());
}
}
public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
// Do not allow this client to communicate with unauthenticated servers.
return false;
}

You need a x509 self certificate, to create it simple, download pluralsight self cert
Generate certificate as in image
Create new web site, there choose wcf service.
Add in solution new console application, to test our service.
In web.config of service put configuration:
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceCredentialsBehavior">
<serviceCredentials>
<serviceCertificate findValue="cn=cool" storeName="TrustedPeople" storeLocation="CurrentUser" />
</serviceCredentials>
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="ServiceCredentialsBehavior" name="Service">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="MessageAndUserName" name="SecuredByTransportEndpoint" contract="IService"/>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="MessageAndUserName">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<client/>
In Service class, delete existing methods and add:
public string TestAccess()
{
return OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name;
}
in IService delete Data Contract, delete operation contracts and add new operation contract:
[OperationContract]
public string TestAccess();
Run service and add service reference in client application to our service
Client config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="LocalCertValidation">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="PeerTrust" trustedStoreLocation="CurrentUser" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IService" >
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="your service addresss"
binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IService"
contract="ServiceReference1.IService"
name="WSHttpBinding_IService" behaviorConfiguration="LocalCertValidation">
<identity>
<dns value ="cool" />
</identity>
</endpoint>
</client>
Client code:
ServiceClient client = new ServiceClient();
client.ClientCredentials.UserName.UserName = "Your windows user";
client.ClientCredentials.UserName.Password = "Your windows user password";
Console.WriteLine(client.TestAccess());
Console.ReadLine();
if you dont want to use windows login/password you have to create a custom user/passwd validator ->msdn:
Regards,
Sergiu.

Related

Unable to define user/password for WCF service

I have a WCF web service working with basic authentification.
I want to define a user/password for this service. So I wrote my web.config to user basic authentification :
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="UsernameWithTransportCredentialOnly">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceWithMetaData">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceAuthorization serviceAuthorizationManagerType="InterfaceWS.CredentialsChecker,App_Code.CredentialsChecker"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="ServiceWithMetaData" name="InterfaceWS.MyService" >
<endpoint
address="https://localhost:44336/MyService.svc"
binding="basicHttpBinding"
bindingConfiguration="UsernameWithTransportCredentialOnly"
name="BasicEndpoint"
contract="InterfaceWS.IErpService">
</endpoint>
</service>
</services>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="false" />
</system.serviceModel>
I created a class inherited from ServiceAuthorizationManager :
namespace InterfaceWS
{
public class CredentialsChecker : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
//Extract the Authorization header, and parse out the credentials converting the Base64 string:
var authHeader = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
if ((authHeader != null) && (authHeader != string.Empty))
{
var svcCredentials = System.Text.ASCIIEncoding.ASCII
.GetString(Convert.FromBase64String(authHeader.Substring(6)))
.Split(':');
var user = new
{
Name = svcCredentials[0],
Password = svcCredentials[1]
};
if ((user.Name == "testuser" && user.Password == "testpassword"))
{
//User is authrized and originating call will proceed
return true;
}
else
{
//not authorized
return false;
}
}
else
{
//No authorization header was provided, so challenge the client to provide before proceeding:
WebOperationContext.Current.OutgoingResponse.Headers.Add("WWW-Authenticate: Basic realm=\"MyWCFService\"");
//Throw an exception with the associated HTTP status code equivalent to HTTP status 401
throw new WebFaultException(HttpStatusCode.Unauthorized);
}
}
public override bool CheckAccess(OperationContext operationContext)
{
return false;
}
}
}
But the CheckAccessCore is never reached and i'm unable to connect to my Service. What did I do wrong ?
serviceAuthorization is about granting access to particular resources based on user credentials. You want authentication.
You can use serviceCredentials/userNameAuthentication tags in the configuration.
Example of configuration is given here: https://learn.microsoft.com/en-us/dotnet/framework/wcf/samples/membership-and-role-provider

Why WCF nettcpBinding is slow on local machine

I am trying to check the WCF latency on my local machine. And it takes 2-4 millisecond/request (which seems to slow). I am not sending or receiving any complex object (so no serialization/deserialization involved).
Here is the service part:
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
public CompositeType GetDataUsingDataContract(CompositeType composite)
{
if (composite == null)
{
throw new ArgumentNullException("composite");
}
if (composite.BoolValue)
{
composite.StringValue += "Suffix";
}
return composite;
}
}
Here is the server's config.
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https"/>
</protocolMapping>
<bindings>
<netTcpBinding>
<binding name="defaultNetTcpBinding" closeTimeout="00:10:00" sendTimeout="00:10:00" receiveTimeout="00:10:00" openTimeout="00:10:00" maxReceivedMessageSize="2147483647" maxBufferPoolSize="2147483647" maxBufferSize="2147483647">
<readerQuotas maxArrayLength="2147483647" maxStringContentLength="2147483647" />
<security mode="None"></security>
</binding>
</netTcpBinding>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
<services>
<service name="WcfTestApplication.Service1">
<endpoint address="net.tcp://localhost/WcfTestApplication/Service1.svc" contract="WcfTestApplication.IService1" binding="netTcpBinding" />
<endpoint address="mex" contract="IMetadataExchange" binding="mexTcpBinding"/>
</service>
</services>
And here is the client code
NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.None;
EndpointAddress address = new EndpointAddress("net.tcp://localhost/WcfTestApplication/Service1.svc");
ChannelFactory<IService1> channelFactory = new ChannelFactory<IService1>(binding, address);
IService1 _clientProxy = channelFactory.CreateChannel();
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1000; i++)
{
IService1 _clientProxy1 = channelFactory.CreateChannel();
var data = _clientProxy1.GetData(4);
((IClientChannel)_clientProxy1).Close();
}
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds.ToString());
Console.ReadLine();
Updated:
Strange enough, there is no difference when I change binding to Named pipe. It still takes 2-4 millisecond/request.

WCF REST service url routing based on query parameters

Since WCF routing doesn't support routing for REST services, I created a REST service that has one enpoint which accepts all incoming requests and than redirects those requests based on the query parameters.
I did this by following this article http://blog.tonysneed.com/2012/04/24/roll-your-own-rest-ful-wcf-router/.
This approach works for passing through requests and returning the results. The problem is whenever I get an error, like a 404, from the actual service the message that is returned to the client is a 400 (Bad Request).
What I would like to have is a routing proxy that actually just redirects the calls to the real service based on the query and returns all the errors to the client as they come from the real service.
Is this even the right approach to what I'm trying to accomplish, or are there easier or better solutions?
Any help is appreciated!
In the following I added what my code looks like.
app.config:
<!--
System.net
-->
<system.net>
<settings>
<servicePointManager expect100Continue="false" useNagleAlgorithm="false" />
</settings>
<connectionManagement>
<add address="*" maxconnection="24" />
</connectionManagement>
</system.net>
<!--
System.ServiceModel
-->
<system.serviceModel>
<!--
Services
-->
<services>
<service name="RoutingGateway.RoutingService">
<endpoint address="/api/routing" binding="webHttpBinding" bindingConfiguration="secureWebHttpBinding" contract="RoutingGateway.IRoutingService" behaviorConfiguration="RESTBehaviour" />
</service>
</services>
<client>
<endpoint binding="webHttpBinding" bindingConfiguration="secureWebHttpBinding" contract="RoutingGateway.IRoutingService" name="routingService" behaviorConfiguration="RESTBehaviour" />
</client>
<!--
Bindings
-->
<bindings>
<webHttpBinding>
<binding name="secureWebHttpBinding" hostNameComparisonMode="StrongWildcard" maxReceivedMessageSize="2147483647" transferMode="Streamed">
<security mode="Transport">
<transport clientCredentialType="None" />
</security>
</binding>
</webHttpBinding>
</bindings>
<!--
Behaviors
-->
<behaviors>
<endpointBehaviors>
<behavior name="RESTBehaviour">
<dispatcherSynchronization asynchronousSendEnabled="true" />
<webHttp helpEnabled="true" />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false before deployment -->
<serviceMetadata httpsGetEnabled="false" />
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false" />
<!-- Enable Throttling -->
<serviceThrottling maxConcurrentCalls="100" maxConcurrentInstances="100" maxConcurrentSessions="100" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
IRoutingService.cs:
[ServiceContract(Namespace = "https://test/api/routing")]
public interface IRoutingService
{
[OperationContract(Action = "*", ReplyAction = "*")]
[WebInvoke(UriTemplate = "*", Method = "*")]
Message ProcessRequest(Message requestMessage);
}
RoutingService.cs:
public Message ProcessRequest(Message requestMessage)
{
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;
Uri originalRequestUri = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri;
// Gets the URI depending on the query parameters
Uri uri = GetUriForRequest(requestMessage);
// Select rest client endpoint
string endpoint = "routingService";
// Create channel factory
var factory = new ChannelFactory<IRoutingService>(endpoint);
Uri requestUri = new Uri(uri, originalRequestUri.PathAndQuery);
factory.Endpoint.Address = new EndpointAddress(requestUri);
requestMessage.Headers.To = requestUri;
// Create client channel
_client = factory.CreateChannel();
// Begin request
Message result = _client.ProcessRequest(requestMessage);
return result;
}
I ended up catching all CommunicationExceptions and then rethrowing WebFaultExceptions with the appropriate messages and status codes.
Here is the code:
Message result = null;
try
{
result = _client.ProcessRequest(requestMessage);
}
catch (CommunicationException ex)
{
if (ex.InnerException == null ||
!(ex.InnerException is WebException))
{
throw new WebFaultException<string>("An unknown internal Server Error occurred.",
HttpStatusCode.InternalServerError);
}
else
{
var webException = ex.InnerException as WebException;
var webResponse = webException.Response as HttpWebResponse;
if (webResponse == null)
{
throw new WebFaultException<string>(webException.Message, HttpStatusCode.InternalServerError);
}
else
{
var responseStream = webResponse.GetResponseStream();
string message = string.Empty;
if (responseStream != null)
{
using (StreamReader sr = new StreamReader(responseStream))
{
message = sr.ReadToEnd();
}
throw new WebFaultException<string>(message, webResponse.StatusCode);
}
else
{
throw new WebFaultException<string>(webException.Message, webResponse.StatusCode);
}
}
}
}

Call WCF via another WCF call

I have a situation where I would like to make a WCF call as another call is coming in.
Site1 request--> Site2
Site2 request --> Site3
Site2 <-- Site3 response
Site1 <-- Site2 response
The problem I am having is that when Site2 tries to send a message to Site3 while Site1 is sending to Site2; Site2 says it cannot find Site3.
The actual error message is:
Could not find endpoint element with name 'Endpoint_IEchoService' and contract
'FakeCompany.API.Services.Contract.IEchoService' in the ServiceModel client
configuration section. This might be because no configuration file was found
for your application, or because no endpoint element matching this name could be
found in the client element.
Each site is the same configuration and code base. The client, proxy and server are all in the same project. The apps are clones calling each other. It is one website with multiple address bindings. Other regular calls between the sites work fine until I try a call within a call.
As you can probably guess from the contact name, not much in the complex way is happening in my echo service. Single echo calls between the sites work. My problem is when i make a cascade call on the service side to another site.
I am wondering if this is not allowed or if a configuration setting change is required.
Some code and config.
Endpoint addresses are changed at runtime.
If you see something "funky", it is because the client, proxy and service inherit from generic base classes.
//-- ServiceModel Client
<endpoint address="http://FakeCompany.unittest/Services/EchoService.svc"
binding="basicHttpBinding" bindingConfiguration="SecureBinding"
contract="FakeCompany.API.Services.Contract.IEchoService" name="Endpoint_IEchoService">
<identity>
<servicePrincipalName value="host/mikev-ws" />
</identity>
</endpoint>
//-- Bindings
<bindings>
<basicHttpBinding>
<binding name="SecureBinding"
maxReceivedMessageSize="10000000"
sendTimeout="00:05:00">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic" />
</security>
</binding>
</basicHttpBinding>
</bindings>
//-- Behaviours
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceTypeBehaviors">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
//-- Services
<service name="FakeCompany.API.Services.Service.EchoService" behaviorConfiguration="MyServiceTypeBehaviors">
<endpoint address="" binding="basicHttpBinding" contract="FakeCompany.API.Services.Contract.IEchoService" bindingConfiguration="SecureBinding" />
</service>
//-- TEST
[Test]
public void CascadeMessage()
{
//-- TEST: That a wcf call can occur within another wcf call.
//-- ARRANGE
DTO_Echo_Cascade_Request request = new DTO_Echo_Cascade_Request(unit1, unit2);
request.NextCall = string.Format("{0};{1};{2};", unit3, unit4, unit5);
//-- ACT
var response = EchoAgent.Cascade(request);
//-- ASSERT
Assert.AreEqual("TBA", response.Response);
}
//-- AGENT
internal static DTO_Echo_Response Cascade(DTO_Echo_Cascade_Request request)
{
DTO_Echo_Response response;
using (EchoServiceClient serviceClient = new EchoServiceClient(request))
{
response = serviceClient.Cascade(request);
}
return response;
}
//-- CLIENT
public DTO_Echo_Response Cascade(DTO_Echo_Cascade_Request request)
{
return Process(() => Proxy.Cascade(request));
}
CONTRACT, DTO, PROXY are omitted.
//-- SERVICE
public DTO_Echo_Response Cascade(DTO_Echo_Cascade_Request request)
{
DTO_Echo_Response response = new DTO_Echo_Response();
response.Response += string.Format("Hello from {0};", request.TargetAddress);
if (request.NextCall.NotNullOrEmpty())
{
var split = request.NextCall.Split(new [] {';'}, StringSplitOptions.RemoveEmptyEntries);
if (split.GetUpperBound(0) > 0)
{
DTO_Echo_Cascade_Request nextRequest = new DTO_Echo_Cascade_Request(request.TargetAddress, split[0]);
for (int i = 1; i < split.GetUpperBound(0); i++)
{
nextRequest.NextCall += split[i] + ";";
}
response.Response += EchoAgent.Cascade(nextRequest).Response;
}
}
return response;
}
The exception occurs on the following line
response.Response += EchoAgent.Cascade(nextRequest).Response;

Wcf self hosted service with X.509 certificate connection error

I have a self hosted Wcf service running on Windows XP and am attempting to use Certificates for message security. This is being done via the service and client config files. Both service and client are running on the same machine and I have created certificates for both using makecert.exe. This worked fine when I had clientCredentialType="Windows" but when I modified the configuration files to use certificates it no longer works. The problem is that when I attempt to connect to the service from the client I am getting the following exception:
Exception Type: System.ServiceModel.Security.SecurityNegotiationException, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message: Incoming binary negotiation has invalid ValueType http://schemas.xmlsoap.org/ws/2005/02/trust/tlsnego.
My configuration settings are:
Service config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsHttpBinding0" closeTimeout="00:10:00" sendTimeout="00:10:00">
<security>
<!-- <transport clientCredentialType="Certificate"/> -->
<message clientCredentialType="Certificate"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="CommMgr.ServiceBehavior">
<serviceMetadata httpGetEnabled="true" policyVersion="Policy15" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<clientCertificate>
<!--
<authentication certificateValidationMode="PeerTrust"/>
-->
<authentication certificateValidationMode="None"/>
</clientCertificate>
<serviceCertificate findValue="WcfServer" storeLocation="CurrentUser"
storeName="My" x509FindType="FindBySubjectName" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="CommMgr.Service" behaviorConfiguration="CommMgr.ServiceBehavior">
<endpoint address="http://localhost:8002/Service"
binding="wsHttpBinding"
name="DataService"
bindingNamespace="CommMgr"
contract="CommMgr.Service"
bindingConfiguration="wsHttpBinding0">
<!--
<identity>
<dns value="localhost"/>
</identity>
-->
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8080/Service/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
<connectionStrings>
</configuration>
Client config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_Service" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="16384" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<!-- <transport clientCredentialType="Certificate"/> -->
<transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
<message clientCredentialType="Certificate" negotiateServiceCredential="true"
algorithmSuite="Default" establishSecurityContext="true"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="ClientCertificateBehavior">
<clientCredentials>
<clientCertificate findValue="WcfClient" storeLocation="CurrentUser"
storeName="My" x509FindType="FindBySubjectName" />
<serviceCertificate>
<!--
<authentication certificateValidationMode="PeerTrust"/>
-->
<authentication certificateValidationMode="None"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://localhost:8080/Service" behaviorConfiguration="ClientCertificateBehavior"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_Service"
contract="ServiceReference.Service" name="WSHttpBinding_Service">
<identity>
<!-- <dns value="WcfServer" /> -->
<certificate encodedValue="MIIBuTCCAWOgAwIBAgIQD6mW56bjgapOill7ECgRMzANBgkqhkiG9w0BAQQFADAWMRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0xMDA3MjAxODMwMThaFw0zOTEyMzEyMzU5NTlaMBQxEjAQBgNVBAMTCVdjZkNsaWVudDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAv2p/0NDo4iZU35gN+k7nGXe0LZWdnP9i4MHYD3IsFcZGIamMyXwRT8//3jx+1fs1xEb+8+QbZuj8TXt/7aX6x2kz2O5tynuholP35iObDqOd7nYSXN+70QDrZ/uktPOkLrw/nfrA8sK0aZCZjfiINHCRt/izJIzESOGzDOh1if0CAwEAAaNLMEkwRwYDVR0BBEAwPoAQEuQJLQYdHU8AjWEh3BZkY6EYMBYxFDASBgNVBAMTC1Jvb3QgQWdlbmN5ghAGN2wAqgBkihHPuNSqXDX0MA0GCSqGSIb3DQEBBAUAA0EALA+gVZDyjk4+qL7zAEV8esMX38X5QKGXHxBdd6K1+xApnSU79bRCWI9xU+HZ4rRhRJgtOdGQ1qfc9/WfvWXcYw=="/>
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
Try turning off the negotiateServiceCredential settings in your binding:
<wsHttpBinding>
<binding >
<security mode="Message">
<message clientCredentialType="UserName" negotiateServiceCredential="false" />
</security>
</binding>
</wsHttpBinding>
After one week of hard work, this works fine. o:)
Server:
using Demo.Auth;
using System;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.Text;
namespace Demo.Services
{
public class TcpHostService
{
public const string CertificateName = "MyCertificateName";
public static ServiceHost GetServiceHost()
{
string tcpHost = GetTcpHost();
var portsharingBinding = new NetTcpBinding();
portsharingBinding.Security.Mode = SecurityMode.TransportWithMessageCredential;
portsharingBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
portsharingBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Certificate;
var serviceHost = new ServiceHost(typeof(RemotingService), new Uri(tcpHost));
serviceHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
serviceHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();
serviceHost.AddServiceEndpoint(typeof(IRemote), portsharingBinding, tcpHost);
if (!File.Exists("Certificate.pfx"))
{
MakeCert();
}
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, CertificateName, false);
if (certificates == null || certificates.Count == 0)
{
InstallCert();
}
}
serviceHost.Credentials.ServiceCertificate.SetCertificate(
StoreLocation.CurrentUser, StoreName.My,
X509FindType.FindBySubjectName, CertificateName);
Console.WriteLine("Server escutando " + tcpHost);
return serviceHost;
}
private static void MakeCert()
{
var rsa = RSA.Create(2048);
var req = new CertificateRequest($"cn={CertificateName},OU=UserAccounts,DC=corp,DC=contoso,DC=com",
rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Parse("127.0.0.1"));
req.CertificateExtensions.Add(sanBuilder.Build());
var oidCollection = new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.2")
};
req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(oidCollection, true));
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation, false));
using (X509Certificate2 cert = req.CreateSelfSigned(DateTimeOffset.Now.AddDays(-10), DateTimeOffset.Now.AddYears(5)))
{
cert.FriendlyName = "JJConsulting Integration Certificate";
// Create PFX (PKCS #12) with private key
File.WriteAllBytes("Certificate.pfx", cert.Export(X509ContentType.Pfx, "pwd123"));
// Create Base 64 encoded CER (public key only)
File.WriteAllText("Certificate.cer",
"-----BEGIN CERTIFICATE-----\r\n"
+ Convert.ToBase64String(cert.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)
+ "\r\n-----END CERTIFICATE-----");
}
}
public static void InstallCert()
{
using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
var cert = new X509Certificate2("Certificate.pfx", "pwd123", X509KeyStorageFlags.PersistKeySet);
store.Open(OpenFlags.ReadWrite);
store.Add(cert); //where cert is an X509Certificate object
}
}
private static string GetTcpHost()
{
return "net.tcp://localhost:5050/myservice1";
}
}
}
Client:
private ChannelFactory<IRemote> GetChannelFactory()
{
var sTcp = "net.tcp://localhost:5050/myservice1"
var myBinding = new NetTcpBinding();
myBinding.Security.Mode = SecurityMode.TransportWithMessageCredential;
myBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
myBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Certificate;
var endpointIdentity = EndpointIdentity.CreateDnsIdentity("MyCertificateName");
var myEndpoint = new EndpointAddress(new Uri(sTcp), endpointIdentity);
var factory = new ChannelFactory<IRemote>(myBinding, myEndpoint);
factory.Credentials.UserName.UserName = User;
factory.Credentials.UserName.Password = Password;
factory.Credentials.ServiceCertificate.SslCertificateAuthentication =
new X509ServiceCertificateAuthentication()
{
CertificateValidationMode = X509CertificateValidationMode.None,
RevocationMode = X509RevocationMode.NoCheck
};
return factory;
}
User Validator:
using System;
using System.IdentityModel.Selectors;
using System.ServiceModel;
namespace Demo.Auth
{
public class CustomUserNameValidator : UserNamePasswordValidator
{
// This method validates users. It allows in two users, test1 and test2
// with passwords 1tset and 2tset respectively.
// This code is for illustration purposes only and
// must not be used in a production environment because it is not secure.
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException();
}
if (!"user1".Equals(userName) || !"pwd".Equals(password))
{
throw new FaultException("Usuário ou senha inválido");
// When you do not want to throw an infomative fault to the client,
// throw the following exception.
// throw new SecurityTokenException("Unknown Username or Incorrect Password");
}
}
}
}

Categories

Resources