Skip to content

Issue with new X509CertificateLoader #109007

@shaneocms

Description

@shaneocms

Description

Hi there,

Our production environment runs two IIS servers behind a load balancer. To allow cookies to be decrypted by both servers, we do the following in our Program.cs in current .NET 8 projects to load a .pfx certificate file from disk.

static void SetupDataProtection(WebApplicationBuilder builder)
{
    string filename = builder.Configuration["AppSettings:DataProtection:CertificateFilename"]!;
    string password = builder.Configuration["AppSettings:DataProtection:CertificatePassword"]!;
    string path = Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)!, "Certificates", filename);

    X509Certificate2 certificate;

    if (builder.Environment.IsProduction())
    {
        certificate = new X509Certificate2(path, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
    }
    else
    {
        certificate = new X509Certificate2(path, password, X509KeyStorageFlags.EphemeralKeySet);
    }

    builder.Services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)!, "KeyRing")))
        .ProtectKeysWithCertificate(certificate);
}

This has worked fine until now, but when trying out the .NET 9 preview, we got the warning about the X509Certificate2 constructor being obsolete during compilation.

So I've adjusted as follows, replacing the new X509Certificate2() with X509CertificateLoader.LoadPkcs12FromFile and keeping the same storage flags.

static void SetupDataProtection(WebApplicationBuilder builder)
{
    string filename = builder.Configuration["AppSettings:DataProtection:CertificateFilename"]!;
    string password = builder.Configuration["AppSettings:DataProtection:CertificatePassword"]!;
    string path = Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory), "Certificates", filename);

    X509Certificate2 certificate;

    if (builder.Environment.IsProduction())
    {
        certificate = X509CertificateLoader.LoadPkcs12FromFile(path, password,
            X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
    }
    else
    {
        certificate = X509CertificateLoader.LoadPkcs12FromFile(path, password, X509KeyStorageFlags.EphemeralKeySet);
    }

    builder.Services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)!, "KeyRing")))
        .ProtectKeysWithCertificate(certificate);
}

The application runs fine locally on my machine, but in production behind the load balancer, it's not working correctly. When you log into the application, if your request goes to server 1, then any further requests that go to server 1 work fine, but any requests sent to server 2 fail. This issue is the behaviour I would expect by default if doing nothing of the above.

Changing back to the obsolete constructor (even while the project is still using .NET 9) resolves the issue.

Is there something I'm doing wrong with the new one or is it a bug in the new one, such as with how it's handling the flags?

Reproduction Steps

Code sample provided above.

Expected behavior

Requests to both servers behind the load balancer should continue to work as it did before.

Actual behavior

Cookies cannot be decrypted if created by the other server.

Regression?

Still works fine with the obsolete constructor even with the project using .NET 9.

Known Workarounds

No response

Configuration

No response

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions