Wednesday, December 23, 2009

WCF endpoint over https = no wsdl response?

We've had a torturous time getting a client connecting to an externally exposed (http) WCF endpoint. The biggest "fix" was enabling rich error reporting which sped up troubleshooting incredibly. To enable the real error to come through to your client consuming your endpoint (this probably isn't a great Production setting, but it's invaluable in Test), add this to your endpoint behavior:

<behavior name="MetadataBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="True" />
</behavior>

So we get out of our sandbox environment and get closer to production, and the client starts reporting they can't add our almost-production-but-not-quite endpoint as a service reference. We pick at it and we're seeing errors like this:

The document at the url https://sanitized/sanitized.svc was not recognized as a known document type.
The error message from each known type may help you fix the problem:
- Report from 'https://sanitized/sanitized.svc' is 'The document format is not recognized (the content type is 'text/html; charset=UTF-8').'.
- Report from 'DISCO Document' is 'Discovery document at the URL https://sanitized/sanitized.svc?disco could not be found.'.
- The document format is not recognized.

(etc etc)

It turned out if you called https://sanitized/sanitized.svc?wsdl via your browser, you didn't get a wsdl response. You got the regular "You have created a service." front page. That's what the error message from above is actually saying - it was expecting a text wsdl response and it got a nice html Hello World page from your service.

With that clue, our lead dev on this tracked it down quickly - you have to enable https on the endpoint behavior. We had already set the Security from None to Transport at an earlier point when we had the client go over SSL, but it wasn't enough apparently for this service.
I set the endpoint behavior to this:

<behavior name="MetadataBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="True" />
</behavior>

and things got much better.

Strong Naming Assemblies in MSBuild

For a few years, it's been requested our core dll's be strongly named so they can be invoked by 3rd party tools. It's always been a bigger effort (we thought) then the payback so we kept putting it off. Now we need them strongly named so they can be used by say, SSIS.

I spent a ton of time on this just going in circles, and when I actually got it working it was pretty simple. You can strong name your assemblies many ways, but two stand out.
First off, I had problem after problem using the pkcs#12 key I generated using OpenSSL for various tasks (such as signing the manifest for a ClickOnce install, another MS technology). Eventually I read that Microsoft’s strong naming algorithm is just too tight on unnamed standards, and it’s pretty common to get stuck in the endless “Import Key” popup during a manual build, which I did. The fallback is to just generate a key using sn.exe, which I ultimately did.

Using sn.exe to generate a strong naming key is simple, if you’re not concerned about key security. We’re not a shrink-wrapped software place and we have tight controls about the migration of compiled code so I wasn’t concerned about that.

On your build server just do the following out of the bin folder of the appropriate Windows SDK you have installed:

sn -k CompanyBuildKey.snk

and now you have a MS-compliant strong naming key.

The first way to strong name assemblies is to do it in the msbuild script, at compile time.
A good practices is to copy the key to the C:\Program Files\MSBuild folder on the build server, and then you can reference it like this in your msbuild scripts:

$(MSBuildExtensionsPath)\CompanyBuildKey.snk

In your msbuild file on your build server change the following sample line from this:

<MSBuild
Projects="@(ProjectList)"
Properties="Configuration=$(Configuration)"
Targets="Build" />

to this:

<MSBuild
Projects="@(ProjectList)"
Properties="Configuration=$(Configuration);SignAssembly=true;DelaySign=false;AssemblyOriginatorKeyFile=$(MSBuildExtensionsPath)\CompanyBuildKey.snk" Targets="Build" />

This worked fairly well, but it started to generate two worlds – the developers are all unsigned but the build server is signing everything. If a developer wants to use compiled dll’s as references in another project, they can run into issues mixing strong (from the build server) and non-strong dll’s – a strongly named dll cannot call a non-strongly named dll.

So the second method is probably for the best, as it puts everyone on the same page.

Second method:

Move the CompanyBuildKey.snk into a neutral reference. If you have a common library folder in source control that the build server & developers all use, this is a good spot for it.

Move your strong name compiling into each project file and out of the msbuild script.
Add this to each project file:

<AssemblyOriginatorKeyFile>\Common\Folder\For\All\CompanyBuildKey.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>

I hate to put this out without a clean followup, but I can’t do the bulk of the strong naming I need to do for another couple weeks. I’ll try to do a follow-up post on how that works out.

Thursday, December 17, 2009

Chained SSL + Java or Smartphone = Not Trusted?

For years we’ve seen our partners that use Java servers, particularly Sun servers, have to manually retrust our VeriSign chained SSL certs every time we had to renew them. The serial number would change (or something) and we’d go from trusted back to untrusted. We’ve also noticed some smartphones also don’t seem to trust our certs either. A year ago I installed an intermediate CA cert to get a clean Mac+Safari machine to trust our site without a popup, but Java servers & smartphones continued to have initial trust issues.

A typical error from a client with Java servers would be something along the lines of this:

Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.Validator Exception: PKIX path building failed: sun.security.provider.certpath.SunCertPath BuilderException: unable to find valid certification path to requested target
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1 591)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:187)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:181)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(Clien tHandshaker.java:1035)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHa ndshaker.java:124)
at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:5 16)
at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.jav

I put a resolution in place yesterday, it turns out that this is a pretty common problem with consumers of VeriSign chained certs.

VeriSign has a java applet SSL Cert “installation checker” that walks your certification path and looks for gaps in your chain. You can find that applet here:

https://knowledge.verisign.com/support/ssl-certificates-support/index?page=content&id=AR1130

To run the applet you’ll need to install the Java JRE runtime if you don’t already have it, it just takes a min, if can be found here:

http://www.java.com/en/download/manual.jsp

So run the applet, point it at your externally accessible domain, and have it crawl your cert chain. If it finds a gap it will tell you, and if you’re using VeriSign certs above and below the gap it should even give you the public key you’ll need to import.

VeriSign changed their chained cert public key in May of 2009, so if you have a new cert since then you’ll need the latest chained key. The applet correctly identified that for us and pointed me to the newest key automatically.

We have our VeriSign certs loaded on the F5 load balancers, and we’re on version 9 of the OS, which makes chained cert key installation a snap.

  1. Import the public key as an SSL cert on the F5.
  2. Edit your client-facing SSL Cert “Profile”.
  3. Go into Advanced Configuration.
  4. Enable “Chain” and select the chained public key that you installed.

It takes effect immediately on all virtual servers using that Client SSL profile.

And now Java/Sun shops will trust your chained cert automatically, and the non-Windows based smartphones (the majority of them) should now trust your website without question!