I am currently working on a solution using an STS, a client and a WCF service which is consumed by the client. Currently this is all done through configuration with the client successfully retrieving the token and passing it on to the WCF service.
The problem occurs with certificates, we are using net.tcp binding secured with transport security as well as the security token and as a requirement of this we require an SSL certificate. This certificate is configured as follows (I have stripped out irrelevant xml):
<behavior name="Federated">
<serviceAuthorization principalPermissionMode="Always" />
<serviceCredentials useIdentityConfiguration="true">
<serviceCertificate findValue="CN=SSLCert" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName" />
</serviceCredentials>
</behavior>
The problem is that the service certificate specified here is also the certificate which WIF uses to decrypt the token which it receives, As the Relying Party in this case is spread accross multiple machines, with tokens being passed between them, it is unacceptable to use the SSL certificate as the encryption (RP) certificate.
If there a way to specify a separate SSL certificate and encryption certificate for a net.tcp binding or do they always have to be the same?
Just to re-iterate the flow of the token is as follows:
sts*(encrypted)* > client*(encrypted)* > dmz-broker*(requires decryption)* > internal-server*(requires decryption)*
I have attempted changing the service certificate to the encryption certificate but then it uses it for SSL and fails. I have also attempted to set the identity of the endpoint specifying certificates and DNS values, all without any luck.
Thanks in advance for any help.
I managed to resolve this eventually using a custom SecurityToken resolver. This involved copying the SimpleTokenResolver which is a standard .NET class (http://referencesource.microsoft.com/#System.IdentityModel/System/IdentityModel/Selectors/SecurityTokenResolver.cs) and then creating it passing in a security token that relates to the certificate used to decrypt the token.
We can see in the .NET 4.5 source code that when WIF is initialised a token resolver is created with the sevice certificate pass in as a token:
SecurityTokenResolver serviceCertificateResolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(new ReadOnlyCollection<SecurityToken>(
new SecurityToken[] { new X509SecurityToken(this.ServiceCertificate) }), false);
This means that the framework by default creates a resolver which decrypts using the exact same certificate which you specify for SSL.
Unfortunately the SimpleTokenResolver which is used internally by the CreateDefaultSecurityTokenResolver method is private and cannot be inherited from or overridden, however by taking the code from the link above and passing in the correct certificate in the constructor (which can be read from an app setting) you can add your own resolver.
public CustomSecurityTokenResolver()
: this(new ReadOnlyCollection<SecurityToken>(new SecurityToken[] { new X509SecurityToken(CertificateHelper.GetFromAppSetting("EncryptionCertificate")) }), false)
{
}
This token resolver can then be specified in the configuration as follows:
<system.identityModel>
<identityConfiguration>
<securityTokenHandlers>
<securityTokenHandlerConfiguration>
<serviceTokenResolver type="MySecurity.CustomSecurityTokenResolver, MySecurity">
</serviceTokenResolver>
</securityTokenHandlerConfiguration>
</securityTokenHandlers>
</identityConfiguration>
</system.identityModel>
Note that the other resolver are still added to the collection of security token resolvers and this one will be hit after the default created by the framework.
The code for the entire custom resolver is shown below:
public class CustomSecurityTokenResolver: SecurityTokenResolver
{
ReadOnlyCollection<SecurityToken> tokens;
bool canMatchLocalId;
public CustomSecurityTokenResolver()
: this(new ReadOnlyCollection<SecurityToken>(new SecurityToken[] { new X509SecurityToken(CertificateHelper.GetFromAppSetting("EncryptionCertificate")) }), false)
{
}
public CustomSecurityTokenResolver(ReadOnlyCollection<SecurityToken> tokens, bool canMatchLocalId)
{
this.tokens = tokens;
this.canMatchLocalId = canMatchLocalId;
}
protected override bool TryResolveSecurityKeyCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityKey key)
{
key = null;
for (int i = 0; i < this.tokens.Count; ++i)
{
SecurityKey securityKey = this.tokens[i].ResolveKeyIdentifierClause(keyIdentifierClause);
if (securityKey != null)
{
key = securityKey;
return true;
}
}
if (keyIdentifierClause is EncryptedKeyIdentifierClause)
{
EncryptedKeyIdentifierClause keyClause = (EncryptedKeyIdentifierClause)keyIdentifierClause;
SecurityKeyIdentifier keyIdentifier = keyClause.EncryptingKeyIdentifier;
if (keyIdentifier != null && keyIdentifier.Count > 0)
{
for (int i = 0; i < keyIdentifier.Count; i++)
{
SecurityKey unwrappingSecurityKey = null;
if (TryResolveSecurityKey(keyIdentifier[i], out unwrappingSecurityKey))
{
byte[] wrappedKey = keyClause.GetEncryptedKey();
string wrappingAlgorithm = keyClause.EncryptionMethod;
byte[] unwrappedKey = unwrappingSecurityKey.DecryptKey(wrappingAlgorithm, wrappedKey);
key = new InMemorySymmetricSecurityKey(unwrappedKey, false);
return true;
}
}
}
}
return key != null;
}
protected override bool TryResolveTokenCore(SecurityKeyIdentifier keyIdentifier, out SecurityToken token)
{
token = null;
for (int i = 0; i < keyIdentifier.Count; ++i)
{
SecurityToken securityToken = ResolveSecurityToken(keyIdentifier[i]);
if (securityToken != null)
{
token = securityToken;
break;
}
}
return (token != null);
}
protected override bool TryResolveTokenCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityToken token)
{
token = null;
SecurityToken securityToken = ResolveSecurityToken(keyIdentifierClause);
if (securityToken != null)
token = securityToken;
return (token != null);
}
SecurityToken ResolveSecurityToken(SecurityKeyIdentifierClause keyIdentifierClause)
{
if (!this.canMatchLocalId && keyIdentifierClause is LocalIdKeyIdentifierClause)
return null;
for (int i = 0; i < this.tokens.Count; ++i)
{
if (this.tokens[i].MatchesKeyIdentifierClause(keyIdentifierClause))
return this.tokens[i];
}
return null;
}
}