Cannot create SustainSys CertificateElement from X509Certificate2 object to update SAML2 configuration in code

670 views Asked by At

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:

  1. 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.
  2. 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:

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

There are 1 answers

1
Anders Abel On

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:

spOptions.ServiceCertificates.Add(myX509Certificate2);

Or, if you want to define the allowed usage:

spOptions.ServiceCertificates.InsertItem(0, new ServiceCertificate
{
  Certificate = myX509Certificate2,
  Use = CertificateUse.Signing
}