Summary
I am trying to implement an SSL-protected RESTFul WCF service, but following error occurred and communication failed.
making the HTTP request to ‘https://123.123.123.123:5000/TestService/PostMsg’. This could be due to the fact that the server certificate is not configured properly with HTTP.SYS in the HTTPS case. This could also be caused by a mismatch of the security binding between the client and the server. on some customers machines.
Can anyone help me? Best regards
Tried things :
I have succeeded to communicate without SSL-protection.
Code
Service Implementation
using System;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.ServiceModel.Web;
namespace TestService
{
class Program
{
static WebServiceHost host;
static void Main()
{
WebHttpBinding binding = new WebHttpBinding();
binding.Security.Mode = WebHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
Uri uri = new Uri("https://localhost:5000/TestService");
host = new WebServiceHost(typeof(TestService));
ServiceEndpoint se = host.AddServiceEndpoint(typeof(ITestService), binding, uri);
var behavior = new WebHttpBehavior();
behavior.FaultExceptionEnabled = false;
behavior.HelpEnabled = true;
behavior.DefaultOutgoingRequestFormat = WebMessageFormat.Json;
behavior.DefaultOutgoingResponseFormat = WebMessageFormat.Json;
se.EndpointBehaviors.Add(behavior);
ServiceDebugBehavior debug = host.Description.Behaviors.Find<ServiceDebugBehavior>();
debug.IncludeExceptionDetailInFaults = true;
ServiceMetadataBehavior metad = new ServiceMetadataBehavior();
metad.HttpGetEnabled = true;
metad.HttpsGetEnabled = true;
host.Description.Behaviors.Add(metad);
var certificate = new X509Certificate2(@"D:\Work\TestService\ServerCert1.pfx", "paswd", X509KeyStorageFlags.UserKeySet);
host.Credentials.ServiceCertificate.Certificate = certificate;
host.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
host.Open();
Console.WriteLine(string.Format(null, "URL : {0}", uri.ToString()));
Console.WriteLine("Press <ENTER> to terminate");
Console.ReadLine();
host.Close();
}
}
}
Interface
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace TestService
{
[ServiceContract]
interface ITestService
{
[OperationContract]
[WebInvoke(Method = "POST"
, RequestFormat = WebMessageFormat.Json
, UriTemplate = "/PostMsg"
)]
MessageData PostMsg(MessageData msg);
}
[DataContract]
public class MessageData
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int Gender { get; set; }
[DataMember]
public int Age { get; set; }
}
}
using System;
namespace TestService
{
class TestService : ITestService
{
public MessageData PostMsg(MessageData msg)
{
Console.WriteLine(string.Format(null, "Recieved Name: {0}, Gender:{1}, Age:{2}", msg.Name , msg.Gender , msg.Age));
return new MessageData()
{
Name = msg.Name,
Gender = msg.Gender,
Age = msg.Age + 1
};
}
}
}
Client Implementation
using System;
using System.Windows.Forms;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.ServiceModel.Web;
using TestService;
namespace TestClient
{
public partial class Form1 : Form
{
WebChannelFactory<ITestService> cf = null;
ITestService channel = null;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
listBox1.HorizontalScrollbar = true;
Uri uri = new Uri("https://123.123.123.123:5000/TestService");
EndpointAddress endpointAddress = new EndpointAddress(uri);
cf = new WebChannelFactory<ITestService>(uri);
WebHttpBinding binding = cf.Endpoint.Binding as WebHttpBinding;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding.Security.Mode = WebHttpSecurityMode.Transport;
var behavior = new WebHttpBehavior();
behavior.FaultExceptionEnabled = false;
behavior.HelpEnabled = true;
behavior.DefaultOutgoingRequestFormat = WebMessageFormat.Json;
behavior.DefaultOutgoingResponseFormat = WebMessageFormat.Json;
cf.Endpoint.Behaviors.Add(behavior);
var clientCertificate = new X509Certificate2(@"D:\Work\TestService\ServerCert1.pfx", "pswd", X509KeyStorageFlags.UserKeySet);
cf.Credentials.ClientCertificate.Certificate = clientCertificate;
cf.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
channel = cf.CreateChannel();
}
private void button1_Click(object sender, EventArgs e)
{
MessageData msg = new MessageData()
{
Name = "Taro",
Gender = 1,
Age = 3
};
MessageData rtn = channel.PostMsg(msg);
listBox1.Items.Insert(0, string.Format("Name:{0}, Gender:{1}, Age{2} ", rtn.Name, rtn.Gender, rtn.Age));
}
}
}
Script for creating pfx file.
@echo ----------------------------------------------
@echo Script for creating self certificate
@echo ----------------------------------------------
@set "TOOL_DIR=E:\Windows Kits\10\bin\10.0.18362.0\x86"
@if not exist "%TOOL_DIR%" (
@echo Tools do not exists. %TOOL_DIR%
@goto ERROR_EXIT
)
@set "PATH=%TOOL_DIR%;%PATH%"
@set "WORK_DIR=D:\Work\TestService"
@if not exist %WORK_DIR% (
@echo Work folder does not exist. %WORK_DIR%
@goto ERROR_EXIT
)
cd /d %WORK_DIR%
@openfiles > NUL 2>&1
@if NOT %ERRORLEVEL% EQU 0 (
@echo It is not being executed as an administor.
goto ERROR_EXIT
)
@SET /P ANS="Create a certificate file. Are you sure (Y / N)?"
@if /i %ANS% NEQ y if /i %ANS% NEQ Y goto ERROR_EXIT
del %WORK_DIR%\*.*
@echo;
@echo Create Self-Signed Certificate
makecert -n "CN=ServerCN1" -a sha1 -eku 1.3.6.1.5.5.7.3.3 -r -sv ServerCert1.pvk ServerCert1.cer -cy authority -b 11/06/2019 -e 12/31/2019
@echo;
@echo Create Software Publisher Certificate File
cert2spc ServerCert1.cer ServerCert1.spc
@echo;
@echo Create Personal Information Exchange File
pvk2pfx -pvk ServerCert1.pvk -spc ServerCert1.spc -po pswd -pfx ServerCert1.pfx -f
@echo;
:ERROR_EXIT
@SET /P ANS="Finished."
Requisite
I don't want to use IIS because I think there are a lot of settings with it. Instead, I am using X509Certificate2 method of X509Certificate2 class in "System.Security.Cryptography.X509Certificates" Name space. (.NETFramework\v4.7.2\System.dll)
Server and client are on the same computer at this moment.
- Ports of Windows defender firewall are opened.
Using a certificate to secure the communication requires that we bind the certificate to the particular computer port with the below command.
https://learn.microsoft.com/en-us/windows/win32/http/add-sslcert
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-configure-a-port-with-an-ssl-certificate
This feature is auto-completed in IIS when we set up the site binding.
About authenticating the client with a certificate, please refer to the below official document. https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/transport-security-with-certificate-authentication
There are usually two points we need to pay attention to.
1. We should ensure that both the service certificate and the client certificate should have client authentication and server authentication purpose.
2. We had better build the project over the Dotnet framwork4.6.2 since there is something wrong with reading the private key of the certificate created by using PowerShell certificate.
Feel free to let me know if there is anything I can help with.