SSL in self-hosted service just doesn't want to work
First, I'm fully aware of needing to use httpcfg to configure SSL for a self-hosted service. I've done the following steps successfully (several times):
- makecert -sr localmachine -ss my -a sha1 -n CN=localhost -sky exchange
- certmgr -add -r LocalMachine -s My -c -n localhost -r CurrentUser -s TrustedPeople
- httpcfg set ssl -i 0.0.0.0:13177 -h <my sha1 hash here>
The service starts up just fine, but I still get the following exception when the client (running on the same machine) tries to make the first call to the service.
An unhandled exception of type 'System.ServiceModel.CommunicationException' occurred in mscorlib.dll
Additional information: An error occurred while making the HTTP request tohttps://localhost:13177/service1. The most likely cause is a security binding mismatch, or the service endpoint is not configured for SSL, among other reasons.
Here's my client configuration:
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<bindingname="myBindingConfiguration">
<securitymode="TransportWithMessageCredential" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpointname="myService"address="https://localhost:13177/service1"binding="basicHttpBinding"bindingConfiguration="myBindingConfiguration"contract="WinFXServiceLibrary1.IService1" />
</client>
</system.serviceModel>
</configuration>And here's my service configuration:
<
configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<bindingname="myBindingConfiguration">
<securitymode="TransportWithMessageCredential" />
</binding>
</basicHttpBinding>
</bindings>
<services>
<servicename="WinFXServiceLibrary1.Service1"behaviorConfiguration="myBehavior">
<endpointaddress="https://localhost:13177/service1"contract="WinFXServiceLibrary1.IService1"binding="basicHttpBinding"bindingConfiguration="myBindingConfiguration" />
</service>
</services>
<behaviors>
<behaviorname="myBehavior">
<metadataPublishingenableGetWsdl="true"enableHelpPage="true" />
<serviceCredentials>
<userNameAuthenticationuserNamePasswordValidationMode="Custom"customUserNamePasswordValidatorType="WinFXServiceLibrary1.MyUserNamePasswordValidator, ServiceHost" />
</serviceCredentials>
</behavior>
</behaviors>
</system.serviceModel>
</configuration>I don't do anything in my service except start the host on thehttps://localhost:13177 uri like so:
Uri
baseAddress =newUri("https://localhost:13177/service1");
myServiceHost =newServiceHost(typeof(WinFXServiceLibrary1.Service1), baseAddress);
myServiceHost.Open();Anyone see any problems? 'Cause I don't know what it could be at this point.
Everything was working just fine until I took the plunge with transport security which I needed to do to be able to use message credentials. In other words if I just switch back to mode="None" and http all my basic functionality works.
FWIW, I can't hithttps://localhost:13177/service1 in the browser and get the metadata pages either, though if I do a netstat -a I can see that my service host .exe owns the 13177 port so it just seems to be an SSL problem.
TIA,
Drew
Can you please try changing the binding to wsHttpBinding?
Also for your WSDL, try using "http" for your base address instead of "https".
Hi, thanks for the suggestions. I changed to use wsHttpBinding and get the same exception. I wouldn't expect that changing the message style would fix the transport problem anyway...?
Also, not sure what you mean by changing my WSDL. I'm not really using any WSDL right now and my client was hand coded, not generated.
Thanks,
Drew
I suspect that it's because the name of your cert is CN=Testing but the name in your endpointaddress is localhost. Try creating a localhost cert and try it again.
Thanks!
Scott
Scott,
That was actually a good thought, but in actuality my CN is "localhost". I just put "Testing" there when I was writing this post up. Sorry about that and thanks for checking! I'll make sure to update the original post.
Cheers,
Drew
I have noticed that when you try to bind a cert to a port using httpcfg it doesn't overwrite the old cert (if there was one there). So if you created a new cert and want to replace an older one, be sure to delete it first, and then set it. You can verify this by doing an httpcfg query ssl command and checking the hash.
Thanks!
Scott
Scott,
Yeah it actually returns error code 183 if you try and set a cert when one already exists. I removed and reapplied the cert to make sure I get an error code of 0 and it still doesn't work. :(
What I find really weird is that I just went back to look and when I do:
certmgr -v -s My
I end up with the following output:
==============No Certificates ==========
==============No CTLs ==========
==============No CRLs ==========
==============================================
Hmmm... why the heck would that be if I get a nice CertMgr Succeeded when I try to add it? I admit this is the first time I'm working with the certificate store, but it doesn't seem like it should be so difficult. :\
Update: Ignore the part I've struck out above. I was forgetting the -r LocalMachine and it was showing what was in my CurrentUser instead (which was nothing).
Thanks again,
Drew
Wellllll, alright I made it a bit further. I was missing the -pe command line option on my makecert call which meant my key was not exportable and that's obviously no good. However, now that I've done that, I get this error from the client on first call:
Could not establish trust relationship for the SSL/TLS secure channel with authority 'localhost:13177'.
Any clues on that one? I'm going to start hunting...
Cheers,
Drew
Did you need to specify PeerOrChainTrust on the client?
<
behavior name="CertificateBehavior"><!--
The clientCredentials behavior allows one to define a certificate to present to a service.
A certificate is used by a client to authenticate itself to the service and provide message integrity.
This configuration references the "client.com" certificate installed during the setup instructions.
--> <
clientCredentials> <
serviceCertificate> <!--
Setting the certificateValidationMode to PeerOrChainTrust means that if the certificate
is in the user's Trusted People store, then it will be trusted without performing a
validation of the certificate's issuer chain. This setting is used here for convenience so that the
sample can be run without having to have certificates issued by a certificate authority (CA).
This setting is less secure than the default, ChainTrust. The security implications of this
setting should be carefully considered before using PeerOrChainTrust in production code.
--> <
authentication certificateValidationMode="PeerOrChainTrust"/> </
serviceCertificate> </
clientCredentials></
behavior>
TheJet,
Thanks for your suggestion. I read up on this setting and you're right, it seems you need to do this if you put the server cert in the trustedpeople store. Unfortunately I still get the same error message as before.
I honestly can't believe it's this hard to get SSL working. :\
Thanks,
Drew
That setting is only applicable for Message security, not Transport.
Try this to do the same thing at transport layer:
class PermissiveCertificatePolicy
{
string subjectName;
static PermissiveCertificatePolicy currentPolicy;
PermissiveCertificatePolicy(string subjectName)
{
this.subjectName = subjectName;
ServicePointManager.ServerCertificateValidationCallback +=
new System.Net.Security.RemoteCertificateValidationCallback(RemoteCertValidate);
}
public static void Enact(string subjectName)
{
currentPolicy = new PermissiveCertificatePolicy(subjectName);
}
bool RemoteCertValidate(object sender, X509Certificate cert, X509Chain chain, System.Net.Security.SslPolicyErrors error)
{
if (cert.Subject == subjectName)
{
return true;
}
return false;
}
}
Thanks!
Scott
Well, the interesting thing now is that I can hit my endpoint with the web browser and get the meta pages and what not. IE warns that the certificate is not from a trusted authority, but I can continue after prompted. So, at least I'm not completely insance and I know my cert is configured properly.
I'm honestly not sure what to do with the policy above from a configuration standpoint (i.e. how to hook it in). The sample code is not even implementing a well known interface or class that I could go explore to know where to get started. I stumbled across this post from Sajay, is that what you're saying to do? Seems like that might finally be the solution.
One thing I would say is that developers need support for this SSL scenario at a simple configuration level if we're to get any work done. It seems to me like this should just work out of the box. People aren't going to have access to trusted CA and shouldn't need to whip together some code.
Thanks,
Drew
The IE message is very telling as to the problem. If your cert is not from a trusted CA then it will have this problem. I had the same issue when I set it up. I was able to get around this first by using the code above and then by using a 'real' cert; one with a valid CA.
See this for Gudge's musings on this particular subject:
http://www.yeyan.cn/WebDesign/SimpleSecureIndigoHTTPS.aspx
Thanks!
Scott
Yep, I had already read Guge's post and that's actually what got me as far as I am now. I also already knew I wasn't using a trusted CA root certificate because I made it with makecert, but the second step in my original post is supposed to be setting that certificate up as trusted. Like I said, I don't really know that much about the certificate store, but from what I understand that command is supposed to be putting the certificate I've created into the TrustedPeople of the CurrentUser's store. I got that bit from this post by Sajay which is also now in the SDK here.
It would seem that that's the piece that's not working here...
Frustrated,
Drew
OK,
I am having a similar problem. So, I am in the process of hooking into my existing private CA to issue a new certificate that the service can use. However, I am having an issue with actually generating a certificate request that a Windows 2000 CA can process successfully. I've found a couple scripts online that will generate a new certificate request and capture the result from the certificate server, but is it possible to actually generate a certificate request that can be signed by my CA and installed for use with a self-hosted service? I've tried walking through the web-based front-end and filling in the custom request form, saving the resulting file to a local directory, but the problem is that I can't seem to locate the generated private key file on my local machine. As a result, I'm getting schannel errors in the event log stating that my chosen certificate's private key file cannot be found. I'm probably missing something very obvious, but has anyone actually gotten an appropriate certificate request generated for a self-hosted service? And once the associated request is issued, how do I link the signed public key with the private key that was generated in the first step?
Thanks!