encrypt password for "LocalCertificates" section in JSON SAML file

I have separate SAML JSON file for storing SAML configuration settings. I’m trying to implement service provider side, but one of my requirements is that the “Password” value used for PFX file for service provider certificate needs to be encrypted. I looked at this in detail, and I came up with my CustomSamlConfigurationResolver class that extends built-in SamlConfigurationResolver class. The code looks like this: (The assumption here is that passwords are already encrypted, which is done by separate code)

public class CustomSamlConfigurationResolver : SamlConfigurationResolver
{
private readonly Lazy protector;

private bool isUnprotected;

public CustomSamlConfigurationResolver(IDataProtectionProvider dataProtectionProvider, IOptionsSnapshot samlConfigurations)
: base(samlConfigurations)
{
this.protector = new Lazy(() => dataProtectionProvider.CreateProtector(“Certs”));
}

public override Task GetLocalServiceProviderConfigurationAsync(string configurationID)
{
var task = base.GetLocalServiceProviderConfigurationAsync(configurationID);

if (this.isUnprotected)
{
return task;
}

var localServiceProviderConfiguration = task.Result;

this.isUnprotected = true;
foreach (var certificate in localServiceProviderConfiguration.LocalCertificates)
{
if (!string.IsNullOrWhiteSpace(certificate.Password))
{
certificate.Password = this.protector.Value.Unprotect(certificate.Password);
}
}

return task;
}

The above class is registered in Startup class like this:

services.TryAddScoped<ISamlConfigurationResolver, CustomSamlConfigurationResolver>();
services.AddSaml(this.Configuration.GetSection(“SAML”));

Everything seems to work, but I’d like to know if there is easier way to accomplish this.

Thanks,
Eric

Hi Eric,
That looks good to me.
One alternative is to implement the ICertificateLoader interface instead of extending the SamlConfigurationResolver.
You would do this by extending the CertificateLoader class and overriding the LoadCertificateFromFileAsync method.
For example:


public override Task LoadCertificateFromFileAsync(string certificateFile, string certificatePassword)
{
if (!string.IsNullOrEmpty(certificatePassword))
{
certificatePassword= this.protector.Value.Unprotect(certificatePassword);
}

return LoadCertificateFromFileAsync(certificateFile, certificatePassword);
}



This class and the interface are under the ComponentSpace.Saml2.Certificates namespace.

[quote]
ComponentSpace - 8/15/2019
Hi Eric,
That looks good to me.
One alternative is to implement the ICertificateLoader interface instead of extending the SamlConfigurationResolver.
You would do this by extending the CertificateLoader class and overriding the LoadCertificateFromFileAsync method.
For example:


public override Task LoadCertificateFromFileAsync(string certificateFile, string certificatePassword)
{
if (!string.IsNullOrEmpty(certificatePassword))
{
certificatePassword= this.protector.Value.Unprotect(certificatePassword);
}

return LoadCertificateFromFileAsync(certificateFile, certificatePassword);
}



This class and the interface are under the ComponentSpace.Saml2.Certificates namespace.

[/quote]

Thanks you a quick reply. I implemented your suggestion, and it does look better and cleaner. I basically extended CertificateLoader class and did override on LoadCertificateFromFileAsync() and LoadCertificateFromStringAsync() methods. I also registered the new class with TryAddTransient() method. Everything looks good, but I do have one question. Do I have to create my own CachedCertificateLoader class as well? In the default CachedCertificateLoader class, it initializes standard CertificateLoader class, so I'm thinking I will need to have custom CachedCertificateLoader class where it initializes my custom CertificateLoader class. Is this correct?

Thanks,
Eric

That’s correct. Of course, caching certificates is optional but if you wish to with your custom certificate loader you need to provide a custom cached certificate loader that uses the custom certificate loader.

The easiest way to do this is to extend the AbstractCachedCertificateLoader and specify your custom certificate loader. For example:


public class CustomCertificateLoader : CertificateLoader
{
public CustomCertificateLoader(IConfiguration configuration, ILoggerFactory loggerFactory)
: base(configuration, loggerFactory)
{
}

public override Task LoadCertificateFromFileAsync(string certificateFile, string certificatePassword)
{
if (!string.IsNullOrEmpty(certificatePassword))
{
certificatePassword = this.protector.Value.Unprotect(certificatePassword);
}

return LoadCertificateFromFileAsync(certificateFile, certificatePassword);
}
}

public class CustomCachedCertificateLoader : AbstractCachedCertificateLoader
{
public CustomCachedCertificateLoader(
IConfiguration configuration,
IOptionsSnapshot certificateCacheOptions,
IDistributedCache distributedCache,
ILoggerFactory loggerFactory)
: base(new CustomCertificateLoader(configuration, loggerFactory), certificateCacheOptions, distributedCache, loggerFactory)
{
}
}



Register the CustomCachedCertificateLoader with TryAddTransient().

[quote]
ComponentSpace - 8/16/2019
That's correct. Of course, caching certificates is optional but if you wish to with your custom certificate loader you need to provide a custom cached certificate loader that uses the custom certificate loader.

The easiest way to do this is to extend the AbstractCachedCertificateLoader and specify your custom certificate loader. For example:


public class CustomCertificateLoader : CertificateLoader
{
public CustomCertificateLoader(IConfiguration configuration, ILoggerFactory loggerFactory)
: base(configuration, loggerFactory)
{
}

public override Task LoadCertificateFromFileAsync(string certificateFile, string certificatePassword)
{
if (!string.IsNullOrEmpty(certificatePassword))
{
certificatePassword = this.protector.Value.Unprotect(certificatePassword);
}

return LoadCertificateFromFileAsync(certificateFile, certificatePassword);
}
}

public class CustomCachedCertificateLoader : AbstractCachedCertificateLoader
{
public CustomCachedCertificateLoader(
IConfiguration configuration,
IOptionsSnapshot certificateCacheOptions,
IDistributedCache distributedCache,
ILoggerFactory loggerFactory)
: base(new CustomCertificateLoader(configuration, loggerFactory), certificateCacheOptions, distributedCache, loggerFactory)
{
}
}



Register the CustomCachedCertificateLoader with TryAddTransient().
[/quote]

That's exactly what I did. Thanks a reply!

You’re welcome.