WCF Self Host Via Https Fails - c#

So I am pretty stuck here. I've always been able to host WCF apps with no problem over http. I can setup https WCF apps in IIS. But when I'm trying to use a self hosted https wcf app this has been nothing but a nightmare. I am running both the client and self hosted service on the same computer. Also, the service opens each time I run it with no errors. It's reported state is open. When I try to connect with the client (which is activating the service via channel factories) it crashes with the SSL/TLS error as described below. I've been at this for about 2 days now and can't get it to work :(
I have tried following several guides such as (but not limited to) the ones here: http://blogs.msdn.com/b/james_osbornes_blog/archive/2010/12/10/selfhosting-a-wcf-service-over-https.aspx as well as here: http://msdn.microsoft.com/en-us/library/ms733791.aspx. The first document I follow it to the letter and at the end when the author says "And that's it! now we can call the program and it will invoke the service" it doesn't. It gives me an error:
"Could not establish trust relationship for the SSL/TLS secure channel".
So I tried a slightly different approach upon coming on to the second article. I tried to use an existing certification already listed for my server (which is stored under personal certifcations). I copied the thumbprint and registered it with the port creating my own app id. That didn't work so I thought well lets try to force the client certificate thumbprint on both the service and the client by specifying the client credentials and looking it up via thumbprint like so:
factory.Credentials.ClientCertificate.SetCertificate(
StoreLocation.LocalMachine,
StoreName.My,
X509FindType.FindByThumbprint,
"The actual thumbprint is here in my code");
I still get the same results. What am I missing? Here is the code for both the service and the client.
Client:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
using HttpsSelfHost;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
string address = "https://localhost:8007/HelloWorldSvc";
WSHttpBinding binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
try
{
ChannelFactory<IHelloWorldSvc> factory = new ChannelFactory<IHelloWorldSvc>(binding, address);
factory.Credentials.ClientCertificate.SetCertificate(System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine, System.Security.Cryptography.X509Certificates.StoreName.My,
System.Security.Cryptography.X509Certificates.X509FindType.FindByThumbprint, "f80e16f75e805b951e6099979f6dcea56bce3273");
IHelloWorldSvc client = factory.CreateChannel();
Console.WriteLine("Invoking service.");
string str = client.HelloWorld();
Console.WriteLine("Returned: {0}", str);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.WriteLine("Press enter to quit.");
Console.ReadLine();
}
}
}
Service:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
namespace HttpsSelfHost
{
class Program
{
static void Main(string[] args)
{
string address = "https://localhost:8007/HelloWorldSvc";
WSHttpBinding binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
using (ServiceHost host = new ServiceHost(typeof(HelloWorldSvc)))
{
host.AddServiceEndpoint(typeof(IHelloWorldSvc), binding, address);
host.Credentials.ClientCertificate.SetCertificate(System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine, System.Security.Cryptography.X509Certificates.StoreName.My,
System.Security.Cryptography.X509Certificates.X509FindType.FindByThumbprint, "f80e16f75e805b951e6099979f6dcea56bce3273");
host.Open();
Console.WriteLine("Host is: {0}. Press enter to close.", host.State);
Console.ReadLine();
host.Close();
}
}
}
}

It's likely that your client is rejecting the certificate during the negotiation process. I suspect that the certificate you bound on the port that the host is listening at is self-signed.
Here's something that you can use on the client in the testing environment and possibly production if you're okay with certificate pinning.
/// <summary>
/// Sets the Certificate Validation Policy for the Client
/// </summary>
public static void SetCertificatePolicy()
{
ServicePointManager.ServerCertificateValidationCallback
+= ValidateRemoteCertificate;
}
/// <summary>
/// Allows for validation of SSL conversations with the server.
/// </summary>
private static bool ValidateRemoteCertificate(object sender, X509Certificate cert,
X509Chain chain, SslPolicyErrors error)
{
if (cert.GetCertHashString() == "Your Certificate SHA1 HashString")
{
return true;
}
else
{
string text = "Failed to establish secure channel for SSL/TLS."
+ Environment.NewLine + Environment.NewLine +
"Connection with server has been disallowed.";
MessageBox.Show(text, #"SSL Validation Failure",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
return false;
}
}

Related

WCF Service over HTTPS without IIS, with SSL Certificate from CERT and KEY strings or files

I've done work with WCF before - but since it was for in-house use only, not accessable from the internet at all, i just used net.tcp and not cared much about security.
However, i am now in pre-production for a project that will be made availlable over the internet to customers, so security must be planed for.
I've been doing some research on the matter, and from what i gathered (correct me if I am wrong here), HTTPS is my best bet, as HTTP isn't secured at all (by default) and net.tcp could find problems with some firewalls.
Howerer, I don't want to force customers to have to install IIS in their servers if they don't want to, so the plan is to use a self hosted Windows Service. However, i can't seem to find any information on how to setup the server to use HTTPS without na IIS.
I found information about using makecert and httpcfg set ssl to add a new certificate to the store and then set it to a port - that's ok for testing but i'm not seeing this feaseable in the customer's server - not to mention this means i'll be using aself signed certificate - again ok for testing, not so much in production
I also found information (ServiceCredentials MSDN page) about using something like
sh.Credentials.ServiceCertificate.SetCertificate(
StoreLocation.LocalMachine, StoreName.My,
X509FindType.FindByThumbprint,
"af1f50b20cd413ed9cd00c315bbb6dc1c08da5e6");
to set a certificate that is already in the server's certificate store - that would almost be ok - it still require the customer to know how to manage certificates in the store, not perfect but ok. However i couldn't get it to work - i don't get any error starting the servisse, but if i try to go to the service address in a browser i get na error regarding TLS beeing out of date - Q1: Any idea what could be the problem here?
Q2: Is it possible to have a configuration somewhere where the customer could input the has or at least location for the cert and key files one gets when buying a certificate and use that to secure the service?
Q1: As mentioned in the errors, there may be a problem in your certificate. be sure that the certificate is valid(self signed certificate can not expire).
Q2: As far as I know, we could save the certificate as a file(pfx, cert) or install the certificate in the certificate store(certlm.msc, certmgr.msc) in order to manage.
Do you want to host the WCF service over https in windows service project? I have made a demo, wish it is useful to you.
Service1.cs
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
Uri uri = new Uri("https://localhost:1017");
ServiceHost sh = null;
protected override void OnStart(string[] args)
{
BasicHttpBinding binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
try
{
ServiceHost sh = new ServiceHost(typeof(MyService), uri);
sh.AddServiceEndpoint(typeof(IService), binding, "");
ServiceMetadataBehavior smb;
smb = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb == null)
{
smb = new ServiceMetadataBehavior()
{
HttpsGetEnabled=true,
};
sh.Description.Behaviors.Add(smb);
}
Binding mexbinding = MetadataExchangeBindings.CreateMexHttpsBinding();
sh.AddServiceEndpoint(typeof(IMetadataExchange), mexbinding, "mex");
sh.Open();
WriteLog($"Service is ready at {DateTime.Now.ToString("hh-mm-ss")}");
}
catch (Exception e)
{
WriteLog(e.ToString());
throw;
}
}
protected override void OnStop()
{
if (sh!=null&&sh.State==CommunicationState.Opened)
{
sh.Close();
WriteLog($"Service is closed at {DateTime.Now.ToString("hh-mm-ss")}");
}
}
public static void WriteLog(string text)
{
using (StreamWriter sw = File.AppendText(#"C:\Mylog.txt"))
{
sw.WriteLine(text);
sw.Flush();
}
}
}
[ServiceContract(Namespace = "mydomain")]
public interface IService
{
[OperationContract]
string SayHello();
}
public class MyService : IService
{
public string SayHello()
{
Service1.WriteLog(string.Format("Wow, I have been called at {0}", DateTime.Now.ToString("hh-mm-ss")));
return "Hello stranger";
}
}
ProjectInstaller.cs
Install the windows service(administrator privilege CMD)
Bind the certificate to the application port.
https://learn.microsoft.com/en-us/windows/desktop/http/add-sslcert
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-configure-a-port-with-an-ssl-certificate
Certhash parameter specifies the thumbprint of the certificate. The appid parameter is a GUID that can be used to identify the owning application(open the project.csproj file)
<ProjectGuid>{56FDE5B9-3821-49DB-82D3-9DCE376D950A}</ProjectGuid>
Start the windows service.
Test(Server Ip is 10.157.13.70):
Client invocation(there is a step that validates the server certificate by default)
static void Main(string[] args)
{
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
ServiceReference1.ServiceClient client = new ServiceReference1.ServiceClient();
try
{
var result = client.SayHello();
Console.WriteLine(result);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
Result
Feel free to let me know if there is anything I can help with.

How to access a SOAP service through an authenticated proxy server in C#?

Let me start by saying I am new to C# and SOAP services. I need to wring a program that will read data from a flat file and make a call to a SOAP web service to insert the data into that system.
My network is behind a proxy server that I have to authenticate with a username and password in order to access anything external to our network or on the internet. I can not figure out how to do this. I have been searching for answers and trying the results I have found, but I have had no luck. The error message I get is
"The request failed with an empty response"
I have set up the service in SOAPUI, and if I supply my proxy settings to the SOAPUI preferences, it is able to leave our network and get a response.
The username and password I have in the example below are not real, but the response doesn't change either way. It seems to me like it never attempts to access or authenticate through the proxy server.
Can anyone help me out? Thank you.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
namespace ConsoleApp6
{
class Program
{
static void Main(string[] args)
{
ConsoleApp6.ASC.Service1 soap = new ConsoleApp6.ASC.Service1();
ConsoleApp6.ASC.UserCredentialsSoapHeader credential = new ConsoleApp6.ASC.UserCredentialsSoapHeader();
credential.UserName = "soap_user_name";
credential.Password = "soap_password";
soap.UserCredentialsSoapHeaderValue = credential;
soap.Proxy = new WebProxy("http://gateway.zscalertwo.net:80/", true);
soap.Proxy.Credentials = new NetworkCredential("username", "password", "domain");
try
{
ASC.AppraiserLicense licenceInfo = soap.GetLicenseByLicenseNumber("46000049784", 1, "NY");
Console.WriteLine(licenceInfo);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
Console.ReadLine();
}
}
}
Console Output
System.Net.WebException: The request failed with an empty response.
at
System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClie
ntMessage message, WebResponse response, Stream responseStream,
Boolean asyncCal l) at
System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String
methodN ame, Object[] parameters) at
ConsoleApp6.ASC.Service1.GetLicenseByLicenseNumber(String
LicenseNumber, B yte LicType, String st_abbr) in
C:\Users\mkurick\Documents\Visual Studio 2017\Pr
ojects\ConsoleApp6\ConsoleApp6\Web References\ASC\Reference.cs:line
120 at ConsoleApp6.Program.Main(String[] args) in
C:\Users\mkurick\Documents\Visu al Studio
2017\Projects\ConsoleApp6\ConsoleApp6\Program.cs:line 26

Communicate to a Windows Service in a server from console app in clients

I have a C# console app that will be installed in about 500 users pc's that won't have access to internet. This console app will be executed by other application after a specific task is completed.
On the other hand I will create a Windows Service (don't know if this is correct or I should host a server in this windows service) installed on the main server that will have internet access.
The idea is that all the console apps send information (such as machine name, user, ip address, etc.) to the Windows Service on server and then the server will push that information to a web service located on the internet.
I don't know if having a self-hosted C# server on the server is the right way to go, or maybe there is a simple way to do this.
This is a code that found for self-hosted WCF that could be an option:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;
namespace SelfHost
{
[ServiceContract]
public interface IHelloWorldService
{
[OperationContract]
string SayHello(string name);
}
public class HelloWorldService : IHelloWorldService
{
public string SayHello(string name)
{
return string.Format("Hello, {0}", name);
}
}
class Program
{
static void Main(string[] args)
{
Uri baseAddress = new Uri("http://localhost:8080/hello");
// Create the ServiceHost.
using (ServiceHost host = new ServiceHost(typeof(HelloWorldService), baseAddress))
{
// Enable metadata publishing.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(smb);
// Open the ServiceHost to start listening for messages. Since
// no endpoints are explicitly configured, the runtime will create
// one endpoint per base address for each service contract implemented
// by the service.
host.Open();
Console.WriteLine("The service is ready at {0}", baseAddress);
Console.WriteLine("Press <Enter> to stop the service.");
Console.ReadLine();
// Close the ServiceHost.
host.Close();
}
}
}
}

Wcf with certificate as ClientCredentials

In my WCF self-hosting WebService using mutual certificate to validate the client, i set the CertificateValidationMode = PeerTrust but its seems ignored, since i can still execute the methods with some client wich i have deleted the corresponding certificate of the TrustedPeople server store.
Heres the host example:
static void Main()
{
var httpsUri = new Uri("https://192.168.0.57:xxx/HelloServer");
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.Transport,
Transport = {ClientCredentialType = HttpClientCredentialType.Certificate}
};
var host = new ServiceHost(typeof(HelloWorld), httpsUri);
//This line is not working
host.Credentials.ClientCertificate.Authentication.CertificateValidationMode =X509CertificateValidationMode.PeerTrust;
host.AddServiceEndpoint(typeof(IHelloWorld), binding, string.Empty, httpsUri);
host.Credentials.ServiceCertificate.SetCertificate(
StoreLocation.LocalMachine,
StoreName.My,
X509FindType.FindBySubjectName,
"server.com");
// Open the service.
host.Open();
Console.WriteLine("Listening on {0}...", httpsUri);
Console.ReadLine();
// Close the service.
host.Close();
}
The client app:
static void Main(string[] args)
{
try
{
var c = new HelloWorld.HelloWorldClient();
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, error) => true;
c.ClientCredentials.ClientCertificate.SetCertificate(
StoreLocation.LocalMachine,
StoreName.My,
X509FindType.FindBySubjectName,
"client.com");
Console.WriteLine(c.GetIp());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
I generate the server.com and the client.com with a RootCA certificate. This RootCA certificate is instaled on the trusted root store of the client and server.
The question is, i should not execute the GetIp() method if my client.com certificate is not in the TrustedPeople store of the server, right? But im executing it without any problems.
The question is, how to, in this scenario, validate the client certificate put its public key on TrustedPeople of server?
ps: In this MSDN article of Transport security with client certificate, theres a quote saying The server’s certificate must be trusted by the client and the client’s certificate must be trusted by the server. But i can execute the webmethods from client even if the client certificate isnt in the server TrustedPeople store.
My suggestion would be to use custom validation. This way you can set some breakpoints and watch the validation take place as well as see what other validation options you could come up with based on the data available throughout the validation process.
First make sure you have your binding requiring Certificate for Message Client Credentials. If you only use Certificate for Transport, the Client in my tests did not validate. This alone may fix your issue.
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
binding.Security.Message.ClientCredentialType =
MessageCredentialType.Certificate;
To setup a custom validator follow the rest.
Replace:
host.Credentials.ClientCertificate.Authentication.CertificateValidationMode
=X509CertificateValidationMode.PeerTrust;
With:
host.Credentials.ClientCertificate.Authentication.CertificateValidationMode
=X509CertificateValidationMode.Custom;
host.Credentials.ClientCertificate.Authentication.CustomCertificateValidator =
new IssuerNameCertValidator("CN=client.com");
Then add this to create the custom validator and tweak as needed (this one validates based on Issuer):
public class IssuerNameCertValidator : X509CertificateValidator
{
string allowedIssuerName;
public IssuerNameCertValidator(string allowedIssuerName)
{
if (allowedIssuerName == null)
{
throw new ArgumentNullException("allowedIssuerName");
}
this.allowedIssuerName = allowedIssuerName;
}
public override void Validate(X509Certificate2 certificate)
{
// Check that there is a certificate.
if (certificate == null)
{
throw new ArgumentNullException("certificate");
}
// Check that the certificate issuer matches the configured issuer.
if (allowedIssuerName != certificate.IssuerName.Name)
{
throw new SecurityTokenValidationException
("Certificate was not issued by a trusted issuer");
}
}
}

How to use Microsoft.Exchange.WebServices?

i try to use : Microsoft.Exchange.WebServices.dll to use outlook. but connection return error
Error return line:service.AutodiscoverUrl("myusernamek#xxxx.com");
The Autodiscover service could not be located. my codes:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Mail;
using System.Net;
using Microsoft.Exchange.WebServices.Data;
using Microsoft.Exchange.WebServices.Autodiscover;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace test
{
class Program
{
static void Main(string[] args)
{
try
{
// Connect to Exchange Web Services as user1 at contoso.com.
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2007_SP1);
service.Credentials = new WebCredentials("myusernamek#xxxx.com", "mypassword", "xxxx.com");
service.TraceEnabled = true;
service.AutodiscoverUrl("myusernamek#xxxx.com");
// Create the e-mail message, set its properties, and send it to user2#contoso.com, saving a copy to the Sent Items folder.
EmailMessage message = new EmailMessage(service);
message.Subject = "Interesting";
message.Body = "The proposition has been considered.";
message.ToRecipients.Add("recipientname#xxxx.aero");
message.SendAndSaveCopy();
// Write confirmation message to console window.
Console.WriteLine("Message sent!");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
Console.ReadLine();
}
}
}
}
I know this is an old question, but recently wrestled with this and similar looking error (including ISA server). It was fixed with:
service.EnableScpLookup = false;
This was not required when working with an explicit URL, but was when using AutoDiscover
This is a common problem , Autodiscover service error is encountered when this autodiscover service by exchange is down
Resolution is to provide actual URL for exchange location , rather than autodiscovering it.
This solved my same issue.
The code suggest that you have an Exchange 2007 server... Is it properly configured for using the Autodiscover features? Confirm that you can ping autodiscover.XXXX.com and view https://autodiscover.XXXX.com in a web browser.
Alternately, you may need to use your internal domain name for autodiscovery and login. For example, in my office the external email addresses are on a domain like CompanyX.com, but the internal Active Directory domain is like CompanyX.local, and we do not have autodiscover on the open Internet, so my EWS needs to locate Autodiscover.CompanyX.local.
this is an old post but maybe someone will need it.
do not use auto discover, it is rly slow.
how to find your exchange url:
-open your outlook application and connect to your exchange
-hold Ctrl key and right click on the outlook icon in the system tray
-select "test e-mail auto configuration"
-click the test button
-look for the following line:
oh and to use the url you trop that extra line of code:
service.Url = new Uri("your url here");
try these concept:
private static ExchangeService getService(String userEmail, String login, String password, String hostName)
{
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010);
AutodiscoverService auservice = new AutodiscoverService(hostName);
if (auservice.ServerInfo != null)
{
try
{
service.AutodiscoverUrl(userEmail, RedirectionUrlValidationCallback);
}
catch (AutodiscoverRemoteException ex)
{
Console.WriteLine("Exception thrown: " + ex.Error.Message);
}
}
else
{
service.Url = new Uri("https://" + hostName + "/EWS/Exchange.asmx");
}
service.UseDefaultCredentials = true;
if (service.ServerInfo == null)
{
service.Credentials = new WebCredentials(login, password);
}
return service;
}

Categories

Resources