I'm working on an ASP.NET SAML2 service provider client based on Sustainsys.Saml2.HttpModule. In general Sustainsys seems to be a mature library and I had a basic website using SAML2 authentication up and running quickly. However, I run into a problem that is blocking for me. We need to provide the website's configuration in code, not in web.config. That is well possible, except for the SP <serviceCertificates>
collection. Here we need at least one certificate having a private key, to sign the SP's assertions. In the CertificateElement
class there are two options to load a certificate:
- Load from file. That doesn't work, because the SustainSys code lacks the possibility to specify the file's password. A certificate file containing a private key must have a password.
- Load from certificate store. That will in general only work if the website runs as LocalSystem. An IIS application pool running as a regular user cannot access the private key, not even in the CurrentUser/My store, unless the Load User Profile setting of the application pool is set. And you cannot in general control that in a hosted environment.
The easiest fix of the issue would be to have a third option, where the CertificateElement
loads its certificate from an X509Certificate2
object.
My question: is there another solution? Am I overlooking something? I would prefer to not edit the library code.
[Edit] Using the answer of @anders-abel, my complicated library change is not required at all (and I am glad!). I removed that code outline.
Only two things left:
- Before application start, I register the SustainSys HttpModule and one of my own. To initialize SustainSys, I write a temp application.config file and remove it again. Now the ServiceCertificates node is no longer added in that config.
string configFilePath = Path.Combine(Path.GetTempPath(), configFileName);
StringBuilder sb = GetTextResource("sustainsys.config");
if (sb != null)
{
sb.Replace("$entityId", entityId);
//sb.Replace("$serviceCertificate", SigningCertificate.ToBase64String());
sb.Replace("$identityProviders", GetIdentityProvidersList());
File.WriteAllText(configFilePath, sb.ToString());
SustainsysSaml2Section.Configuration
= ConfigurationManager.OpenMappedExeConfiguration(
new ExeConfigurationFileMap() { ExeConfigFilename = configFilePath },
ConfigurationUserLevel.None, true);
File.Delete(configFilePath);
return true;
}
- To use Anders Abel's solution, I have to wait until the SustainSys module is initialized. I add the service certificate in my own HttpModule initn, that is called after SustainSys's Init. Because that is done more than once, I only add the certifcate if
ServiceCertificates.Count == 0
.
/// <summary>
/// Initializes the Decos.Saml2 HttpModule.
/// </summary>
/// <param name="context"></param>
public void Init(HttpApplication context)
{
_usingIntegratedPipeline = HttpRuntime.UsingIntegratedPipeline;
context.BeginRequest += OnBeginRequest;
if (Enabled)
{
if (Saml2AuthenticationModule.Options.SPOptions.ServiceCertificates.Count == 0)
{
Saml2AuthenticationModule.Options.SPOptions.ServiceCertificates
.Add(ConfigSustainSys.SigningCertificate);
}
Saml2AuthenticationModule.Options.Notifications.Unsafe
.TokenValidationParametersCreated
= (validationParameters, idp, idpResponseXml) =>
{
SetBestMatchingNameClaim(validationParameters, idpResponseXml);
};
Saml2AuthenticationModule.Options.Notifications
.AcsCommandResultCreated = (result, response) =>
{
// TODO
};
}
}
When using
web.config
there is support for specifying the store etc. and the code to load from a store is in the library.When configuring from code, the idea is that you get an
X509Certificate2
instance by your own code. That could be loading from a store, from file, from database or anything.Then to use it you register it with the SpOptions:
Or, if you want to define the allowed usage: