How to implement a custom sub security token resolver
The custom security store resolver and sub resolver provide the extensible points needed to plug in custom code that can handle any kind of KeyInfo found in a SAML 2.0 message. A KeyInfo element contains information about a security key (aka. a certificate) that is used to sign or to encrypt a message. One of the steps of processing a SAML message is to look up the certificate from a store by using the KeyInfo. Until now, Safewhere Identify has and will support resolving certificates in the Windows Certificate store whose corresponding KeyInfo contains direct information about the certificate, e.g., X509Data, Subject, or Thumbprint. From version 4.3, a new extensible point can help plug in custom resolvers to handle other types of KeyInfo such as indirect KeyName or no KeyInfo. In addition, it is now possible to write a custom resolver that looks up certificates from a database or other sources than the default Windows Certificate store.
This topic will guide you through the process of writing a custom sub security resolver that can resolve security token from a list of predefined certificates from KeyInfo that uses KeyName.
Implement a Sub Signing Security Token Resolver
A sub signing security token resolver needs to implement the ISubSigningSecurityTokenResolver interface that is defined in the Safewhere.External.dll library. The dll can be found in the Runtime\bin folder of any Identify tenant. Code for a sub signing security token resolver is pretty self-explanatory:
public class PredefinedSigningCertificateKeyNameSubSecurityTokenResolver : ISubSigningSecurityTokenResolver
{
// The value of this dictionary can be loaded from a config file or from a set metadata.
private static readonly Dictionary<string, X509Certificate2> findCertificateByKeyName =
new Dictionary<string, X509Certificate2>();
public bool DoTryResolveSecurityKeyCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityKey key)
{
// Check if the clause is a KeyName clause
KeyNameIdentifierClause keyNameClause = keyIdentifierClause as KeyNameIdentifierClause;
if (keyNameClause != null)
{
// Check if the predefined list contains that KeyName
if (findCertificateByKeyName.ContainsKey(keyNameClause.KeyName))
{
X509Certificate2 certificate = findCertificateByKeyName[keyNameClause.KeyName];
key = new X509AsymmetricSecurityKey(certificate);
return true;
}
}
key = null;
return false;
}
public bool DoTryResolveTokenCore(SecurityKeyIdentifier keyIdentifier, out SecurityToken token)
{
Log.Instance.LogDebug("SigningKeyNameSubSecurityTokenResolver.DoTryResolveTokenCore...");
KeyNameIdentifierClause keyNameClause;
if (keyIdentifier.TryFind<KeyNameIdentifierClause>(out keyNameClause))
{
if (findCertificateByKeyName.ContainsKey(keyNameClause.KeyName))
{
X509Certificate2 certificate = findCertificateByKeyName[keyNameClause.KeyName];
token = new X509SecurityToken(certificate);
return true;
}
}
token = null;
return false;
}
public SecurityTokenResolver ParentSecurityTokenResolver { get; set; }
}
Note that the ParentSecurityTokenResolver property can be just an auto one. Safewhere Identify’s resolver framework will be responsible for setting a value to it.
Implement a Sub Encrypting Security Token Resolver
In fact, a sub encrypting security token resolver is responsible for resolving a security token that is used to decrypt encrypted token. The interface to implement is ISubEncryptingSecurityTokenResolver.
public class PredefinedEncryptingCertificateKeyNameSubSecurityTokenResolver : ISubEncryptingSecurityTokenResolver
{
// The value of this dictionary can be loaded from a config file or from a database.
private static readonly Dictionary<string, X509Certificate2> findCertificateByKeyName =
new Dictionary<string, X509Certificate2>();
public bool DoTryResolveSecurityKeyCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityKey key)
{
// check if the clause is an EncryptedKeyIdentifierClause clause
EncryptedKeyIdentifierClause encryptedKeyClause = keyIdentifierClause as EncryptedKeyIdentifierClause;
if (encryptedKeyClause != null && encryptedKeyClause.EncryptingKeyIdentifier != null)
{
// Check if it contains a KeyName clause
KeyNameIdentifierClause keyNameIdentifierClause;
if (encryptedKeyClause.EncryptingKeyIdentifier.TryFind<KeyNameIdentifierClause>(
out keyNameIdentifierClause))
{
if (findCertificateByKeyName.ContainsKey(keyNameIdentifierClause.KeyName))
{
X509Certificate2 certificate = findCertificateByKeyName[keyNameIdentifierClause.KeyName];
// This is the tricky part: build a typical X509RawData clause using the resolved certificate
// so that the parent resolver can resolve the security key and return it to WIF’s code correctly
return ResolveEncryptingSecurityKey(ParentSecurityTokenResolver, encryptedKeyClause, certificate, out key);
}
}
}
key = null;
return false;
}
public bool DoTryResolveTokenCore(SecurityKeyIdentifier keyIdentifier, out SecurityToken token)
{
Log.Instance.LogDebug("SigningKeyNameSubSecurityTokenResolver.DoTryResolveTokenCore...");
KeyNameIdentifierClause keyNameClause;
if (keyIdentifier.TryFind<KeyNameIdentifierClause>(out keyNameClause))
{
if (findCertificateByKeyName.ContainsKey(keyNameClause.KeyName))
{
X509Certificate2 certificate = findCertificateByKeyName[keyNameClause.KeyName];
token = new X509SecurityToken(certificate);
return true;
}
}
token = null;
return false;
}
public SecurityTokenResolver ParentSecurityTokenResolver { get; set; }
private static bool ResolveEncryptingSecurityKey(SecurityTokenResolver parentSecurityTokenResolver,
EncryptedKeyIdentifierClause encryptedKeyClause, X509Certificate2 certificate, out SecurityKey key)
{
if (parentSecurityTokenResolver == null)
{
key = null;
return false;
}
var nextClause = new EncryptedKeyIdentifierClause(
encryptedKeyClause.GetEncryptedKey(),
encryptedKeyClause.EncryptionMethod,
new SecurityKeyIdentifier(
new SecurityKeyIdentifierClause[]
{
new X509RawDataKeyIdentifierClause(certificate)
}),
encryptedKeyClause.CarriedKeyName,
encryptedKeyClause.GetDerivationNonce(),
encryptedKeyClause.DerivationLength);
return parentSecurityTokenResolver.TryResolveSecurityKey(nextClause, out key);
}
}
Deployment
After combining your code into a DLL, simply copy and drop it into the [Tenant]\Runtime\bin folder. After restarting IIS, all your new sub resolvers will appear in the sub security token resolver section in System Setup