If you plan to write WCF services that will be consumed by clients of
technologies other than .NET--and would like to include some level of
security, like message signing (via X.509 certificates)--chances are
you'll have to find some way to emit a BinarySecurityToken value in
your responses.
When I first configured the security of my service, I went with a custom binding and a configuration like this:
<binding name="CustomBindingForX509">
<textMessageEncoding messageVersion="Soap11" />
<security allowSerializedSigningTokenOnReply="true" authenticationMode="MutualCertificate"
requireDerivedKeys="false" securityHeaderLayout="Lax" messageProtectionOrder="EncryptBeforeSign"
messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
<localClientSettings detectReplays="false" />
<localServiceSettings detectReplays="false" />
<secureConversationBootstrap />
</security>
<httpTransport />
</binding>
This
seemed to make sense (as far as WCF configuration can make sense,
anyway): I was sharing certificates with my client (a Java
Axis2 client), hence the MutualCertificate
setting seemed appropriate. I was also going for the lowest common
denominator SOAP settings (as most folks in the interop space seem to
recommend), so I went with those recommended SOAP settings. This
configuration had the effect of rendering my response to look as such
(I only show the SOAP header section since it is what's relevant here):
<s:Header>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="uuid-5965cceb-61d9-4d3f-a503-c3f4dc7fe08a-3">
<u:Created>2008-07-17T20:33:42.153Z</u:Created>
<u:Expires>2008-07-17T20:38:42.153Z</u:Expires>
</u:Timestamp>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>
<Reference URI="#_1">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
<DigestValue>3dcC17frvtyzp8G+kR5otzreQf0=</DigestValue>
</Reference>
<Reference URI="#uuid-5965cceb-61d9-4d3f-a503-c3f4dc7fe08a-3">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
<DigestValue>h/DtKybbi4Q3RRtYKm26SGM2mcM=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>IRlfHoVu/JispcD5CdMCKnbHNZcSVVNNLBtXbnP3fcid+nPi1F4WNGVsHjkF6PnaIzKM/5j2Vhnxbkm1tTwFjeKelQipCHErrwXsxOKMaVKlP/2gjeiJ0K2kkEO7LIUIcmqQ9MNx/AfGr9zE4c6EPGrkbPJVYLvra5jUhypMAcM=</SignatureValue>
<KeyInfo>
<o:SecurityTokenReference>
<X509Data>
<X509IssuerSerial>
<X509IssuerName>CN=Sample Service, OU=Rampart, O=Apache, L=Colombo, S=Western, C=LK</X509IssuerName>
<X509SerialNumber>1187603713</X509SerialNumber>
</X509IssuerSerial>
</X509Data>
</o:SecurityTokenReference>
</KeyInfo>
</Signature>
</o:Security>
</s:Header>
The
problem is, my client didn't like these settings and kept throwing the
error "The signature verification failed". I suspect the client didn't
like this response because the MutualCertificate setting seems to want
the client to look up the certificate via distinguished name or serial
number to verify that the message was signed appropriately. Java
clients, and apparently a few other technologies, don't seem to work
that way.
So, what to do? Well, eventually
I found
this post describing
a similar problem between a WCF service and an
SAP NetWeaver client.
Part III
of the post describes what they did to resolve the signature
verification problem: namely, get the WCF service to inject a
BinarySecurityToken instead of the SecurityTokenReference (with issuer
name and serial number).
I'm not totally
fond of their implementation--they did the old school
write-a-console-app-to-host-my-WCF-service approach--but this gave me
an important clue to how to inject the BinarySecurityToken: use the
AsymmetricSecurityBindingElement class! Wow, why didn't I think of
that? That's fairly...uh...obvious.
Ok, so
how do I use this AsymmetricSecurityBindingElement class? I know I
don't want to write a console app just to plug in this
functionality--that's what IIS is for. Guess I'll have to go to a
custom BindingElement extension (see the attachment for the code and
thanks to Scott, the Microsoft tech who wrote it--and helped me with
this problem):
(excerpt from the client and service configurations)
<extensions>
<bindingElementExtensions>
<add name="MySecurityBindingElement" type="MySecurityBE.AsymetricSecurityBEExtentionElement, MySecurityBE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bindingElementExtensions>
</extensions>
<bindings>
<customBinding>
<binding name="MyCoolBinding">
<MySecurityBindingElement/>
<textMessageEncoding messageVersion="Soap11"/>
<httpTransport/>
</binding>
</customBinding>
</bindings>
But
wait, there's more! It turns out that you don't even need to write
your own custom BindingElement. It turns out that the authentication
mode MutualCertificateDuplex makes use of the
AsymmetricSecurityBindingElement class. What? Didn't you know that? So, all you really have to do
is expose an endpoint with this kind of binding and you're set. So,
this service configuration:
<binding name="CustomBindingForX509">
<security authenticationMode="MutualCertificateDuplex"
requireDerivedKeys="false" messageProtectionOrder="EncryptBeforeSign"
messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"/>
<textMessageEncoding messageVersion="Soap11" />
<httpTransport />
</binding>
Will render this response (again, I'm just including the header for brevity):
<s:Header>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="uuid-39531f92-c0d4-4129-b491-6222c06d3bf5-1">
<u:Created>2008-07-25T16:03:59.959Z</u:Created>
<u:Expires>2008-07-25T16:08:59.959Z</u:Expires>
</u:Timestamp>
<o:BinarySecurityToken>
<!-- Removed-->
</o:BinarySecurityToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>
<Reference URI="#_1">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
<DigestValue>2x4uPP0r/Zo7auuboFg+8h0k3Yo=</DigestValue>
</Reference>
<Reference URI="#uuid-39531f92-c0d4-4129-b491-6222c06d3bf5-1">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
<DigestValue>Z161SvyizqMC3alennK2c6FLiZg=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>EDpLDry/QuwxVuXe+/0LZrmrLkTcoZ1Ls45qvu+RebTRFfkq8HksKMN3Ip4T2begyDCLfGOTpEHfX/ohyMS7HIsxluyIwJ971kyLVt6nUZPfjqQ3iD3hCI2cCSRtNbC1p+aZDr3Tn/KbLxjWQ4aFfm7lRKbLGeVDEY5BJYyVCqY=</SignatureValue>
<KeyInfo>
<o:SecurityTokenReference>
<o:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" URI="#uuid-f32fc9b9-695e-4542-98bb-acf40424321b-2"></o:Reference>
</o:SecurityTokenReference>
</KeyInfo>
</Signature>
</o:Security>
</s:Header>
Note
that the response now includes the BinarySecurityToken and, ironically,
has removed the x509 name and serial number data. Don't worry about
that "removed" comment, the binary token was sent to my client, it's
just that WCF automatically removes privacy information from its
logs--and I'm pulling this data from the log files. For more info on
privacy removal, see
here.
Now,
my non-.NET clients are happy, but I've just hosed my .NET clients.
From what I'm told, duplex mode is no walk in the park for .NET
clients. So, what to do? Well, I'll tell you what I plan to do: just
expose another endpoint. If you're a .NET client, you'll get a
traditional MutualCertificate binding endpoint. If you're not a .NET
client, you'll get the duplex endpoint. The custom extension is nice,
but not absolutely necessary at this point.
AsymExample.zip (899.63 kb)