Search code examples
c#asp.net.netdata-protectiondpapi

Data Protection in .NET6 with multiple web applications


I have 2 load balanced IIS servers - mirrored. Each server has multiple .NetFramework web applications. Each app is running under different pool user and the code is placed in different folders.

Now I need to migrate these apps to .NET6

I have MSSQL database with dp.Keys table. And I faced with the problem with DataProtection - all apps are using the same key. Because of this I can't use DpapiNG keys protection. I also want to have 1 key per app (app1 on 1st server and app1 on 2nd server use key1 from DB).

enter image description here

Here is my code:

services
   .AddDataProtection()
   .SetApplicationName("App1")
   .AddKeyManagementOptions(options => options.XmlRepository = new SqlServerXmlRepository(connectionString, "dp", "Keys"));

I made some digging and found that DefaultKeyResolver (is used by AddDataProtection()) takes FirstOrDefault() key. It does not look for a key for this particular app.

var preferredDefaultKey = (from key in allKeys
     where key.ActivationDate <= now + _maxServerToServerClockSkew
     orderby key.ActivationDate descending, key.KeyId ascending
     select key).FirstOrDefault();

Is that expected behavior? Is that safe to use 1 key for all apps? Looks like the only option with keys protection is certificate?


Solution

  • Found the answer here: https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-6.0

    Data Protection and app isolation

    • When multiple apps are pointed at the same key repository, the intention is that the apps share the same master key material. Data Protection is developed with the assumption that all apps sharing a key ring can access all items in that key ring. The application unique identifier is used to isolate application specific keys derived from the key ring provided keys. It doesn't expect item level permissions, such as those provided by Azure KeyVault to be used to enforce extra isolation. Attempting item level permissions generates application errors. If you don't want to rely on the built-in application isolation, separate key store locations should be used and not shared between applications.
    • The application discriminator is used to allow different apps to share the same master key material but to keep their cryptographic payloads distinct from one another. For the apps to be able to read each other's cryptographic payloads, they must have the same application discriminator.
    • If an app is compromised (for example, by an RCE attack), all master key material accessible to that app must also be considered compromised, regardless of its protection-at-rest state. This implies that if two apps are pointed at the same repository, even if they use different app discriminators, a compromise of one is functionally equivalent to a compromise of both. This "functionally equivalent to a compromise of both" clause holds even if the two apps use different mechanisms for key protection at rest. Typically, this isn't an expected configuration. The protection-at-rest mechanism is intended to provide protection in the event an adversary gains read access to the repository. An adversary who gains write access to the repository (perhaps because they attained code execution permission within an app) can insert malicious keys into storage. The Data Protection system intentionally doesn't provide protection against an adversary who gains write access to the key repository.
    • If apps need to remain truly isolated from one another, they should use different key repositories. This naturally falls out of the definition of "isolated". Apps are not isolated if they all have Read and Write access to each other's data stores.

    I decided to use Azure blob storage (container per app) with DpapiNG protection. It works perfect.

    new BlobContainerClient(connectionString, containerName).CreateIfNotExists();            
    services
        .AddDataProtection()
        .PersistKeysToAzureBlobStorage(connectionString, containerName, "encryptedKey.xml")
        .ProtectKeysWithDpapiNG();