aspnet / Security

[Archived] Middleware for security and authorization of web apps. Project moved to https://github.com/aspnet/AspNetCore
Apache License 2.0
1.27k stars 600 forks source link

DataProtection with ProtectKeysWithCertificate for Cookie Authentication shared by multi-apps. #1879

Closed kevinlo closed 5 years ago

kevinlo commented 5 years ago

I have App1 and App2 that need to shared cookie authentication. App1 is the first access application that will create the cookie to be shared by App2. I put the following codes in both apps and it works fine. I can see the key.xml with the encryptedSecret tag.

            services.AddDataProtection()
                .PersistKeysToFileSystem(new DirectoryInfo(myDataProtectionPath))
                .SetApplicationName("MyApp")
                .ProtectKeysWithCertificate(myCertificate);

            services.AddAuthentication()
                .AddCookie(CookieScheme, options =>
            {
                options.Cookie = new CookieBuilder()
                {
                    HttpOnly = true,
                    Name = "MyCookie",
                };
            };

Then , I remove the ProtectKeysWithCertificate(myCertificate) call in App2 like this

            services.AddDataProtection()
                .PersistKeysToFileSystem(new DirectoryInfo(myDataProtectionPath))
                .SetApplicationName("MyApp");

I expect the App2 cannot decrypt the cookie and the authentication will fail. But instead, the cookie authentication pass and I see a new protection key file with unencrypted master key is created.

Is it a bug or I am missing something? Could someone explain how it works?

Tratcher commented 5 years ago

That's expected. When App2 can't read any keys it will create its own. Is the concern that it wiped out the first one?

kevinlo commented 5 years ago

@Tratcher Could you explain how it works in this case? App2 read the encrypted key file, it cannot decrypt it so it creates its own unencrypted one. Is that key same as the one used by the App1 so it can use it to decrypt the cookie?

In that case, why does it need to ProtectKeysWithCertificate, any app that knows the DataProtectionPath and ApplicationName can create the same key to decrypt the protected data?

Tratcher commented 5 years ago

@natemcmaster to confirm.

kevinlo commented 5 years ago

Just find DataProtection Issue#286, it says

Decrypt is limited to search the store for a matching certificate

So, is it that App2 reads the matching certificate through the store? That's why it works without the ProtectKeysWithCertificate.

But then for the web farm case, the App2 in another Node may not find the certificate in the store and it would fail and it must call the ProtectKeysWithCertificate with the same certificate.

natemcmaster commented 5 years ago

why does it need to ProtectKeysWithCertificate, any app that knows the DataProtectionPath and ApplicationName can create the same key to decrypt the protected data?

ProtectKeysWithCertificate is not needed on Windows machines. It's an option available to you if you want to protect keys at rest using an X.509 certificate. Data protection has a set of defaults described in https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/default-settings. These defaults are tuned for single machines scenarios and Azure Web Apps. If the app is spread across multiple machines, it may be convenient to distribute a shared X.509 certificate across the machines and configure the hosted apps to use the certificate for encryption of keys at rest. See https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/implementation/key-encryption-at-rest.

So, is it that App2 reads the matching certificate through the store? That's why it works without the ProtectKeysWithCertificate.

Yes, DataProtection will attempt to read all keys in your storage location (in your case, "myDataProtectionPath"). If it can decrypt a key, it will load and use it.

But then for the web farm case, the App2 in another Node may not find the certificate in the store and it would fail and it must call the ProtectKeysWithCertificate with the same certificate.

You must distribute your keys and certificates in a web farm case. If the key was encrypted with a certificate and that certificate is neither in the cert store nor given via UnprotectKeysWithAnyCertificate, then the key cannot be used.

kevinlo commented 5 years ago

@natemcmaster

Yes, DataProtection will attempt to read all keys in your storage location (in your case, "myDataProtectionPath"). If it can decrypt a key, it will load and use it.

App2 does not have the myCertificate. Why can it decrypt the key? That is the main question I have why the App2 can decrypt the cookie even it does not have the myCertificate. Does it read the certificate from the store?

natemcmaster commented 5 years ago

Yes, DataProtection will use certificates installed in the cert store, even if you did not programmatically call .ProtectKeysWithCertificate or .UnprotectKeysWithAnyCertificate.

Can you clarify the exact behavior you are seeing, and what you're expecting? I'm not sure what the problem is. Is App2 using the exact same key as App1 and shouldn't? Is the certificate used to encrypt the key for App1 installed in the machine running App2 but shouldn't be used? Something else?

kevinlo commented 5 years ago

Ok. I just test it more. I create the myCertificate in my testing server. It does add the certificate to the Personal store. After I delete that certificate in the store and now App2 has

20181015 13:38:12,462 ERROR [XmlKeyManager] An exception occurred while processing the key element '<key id="9a207536-9af2-4dc6-bbff-9cb07e9b3bd1" version="1" />'. [(null) (null):3  ] 
System.Security.Cryptography.CryptographicException: Unable to retrieve the decryption key.
   at System.Security.Cryptography.Xml.EncryptedXml.GetDecryptionKey(EncryptedData encryptedData, String symmetricAlgorithmUri)
   at System.Security.Cryptography.Xml.EncryptedXml.DecryptDocument()
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.Decrypt(XElement encryptedElement)
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.XmlEncryptionExtensions.DecryptElement(XElement element, IActivator activator)
   at Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.DeserializeDescriptorFromKeyElement(XElement keyElement)

After I add ProtectKeysWithCertificate back to App2 using the same myCertificate, the exception is gone.

That is what I expect.

@natemcmaster I think you have answered my question

DataProtection will use certificates installed in the cert store, even if you did not programmatically call .ProtectKeysWithCertificate

Could you please point me to the class and function that read the certificate in the store? I would like to read how it works. Does it try every certificate in the store?

natemcmaster commented 5 years ago

The class which decrypts the key is here: https://github.com/aspnet/DataProtection/blob/2.1.0/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/EncryptedXmlDecryptor.cs. It uses System.Security.Cryptography.Xml.EncryptedXml which finds certs in the CurrentUser\My store.

kevinlo commented 5 years ago

Got it. The Utils.BuildBagOfCerts in System.Security.Cryptography.Xml read the certificates from the LocalMachine and CurrentUser store.

Thanks all answering my questions. I''ll close this issue.

hem-sharma commented 5 years ago

Using services.AddDataProtection().PersistKeysToAzureBlobStorage(Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(connectionString), keyPersistentPath) doesn't resolves private key, results "Keyset not exists" error, can anyone shed some light on it?