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;
}
}