Current package https://dotnet.myget.org/F/dotnet-core/api/v2/package/System.Security.Cryptography.ProtectedData/4.0.0-rc3-24022-00 throws PlatformNotSupported on non-Windows.
This feature is needed by PowerShell and Nuget and it doesn't make sense for everyone to roll their own.
The original issue has been Closed
so creating this new one to track.
We have no plans to do this. It requires OS features that are only available on Windows.
Trying to read and keep track of this issue is very difficult on github, cross references all over the place and all seem to get closed without any actual resolvement.
Will we be able to use this feature on MacOS / Linux or won't we?
@reddwarf666 No, this API will only ever work on Windows. (and if we were to ever make something that worked cross-platform (which I don't forsee) it would probably be a new API, not this one).
ProtectedData is part of Windows DPAPI (Data Protection API). It encrypts the data using an algorithm of its choice and a key that it probably makes up on the spot. That key it needs to write down, but the whole point of the API is to not require a key, so it encrypts the key with another key.
How does it get the other key? For user-scoped data it uses a key that it derives off of your account. The simplest form is to say that it derives something other than your logon key from your password, such as by using PBKDF2 with a special DPAPI-engineered salt (since I assume it also works on systems where the user has only ever logged in with a smartcard, it must use something other than the password per se),
So, on Windows we call an API that we give some data to, and it knows who the user is, and it encrypts the data. Huzzah. It also has a machine scope, so any user on this computer can read it, but no other computer can recover it. Huzzah.
On macOS the closest equivalent is a keychain. But with the keychain things are different... for one, you can browse it with the Keychain Access program. So if we generate a random KEK and we shove it in the keychain and you delete it then the data is unrecoverable. What keychain do we use? If we make a custom one, what would we do on macOS? How would we do machine-scoped protection in a manner that worked for non-administrators?
On Linux there's not even something like Keychain. If this was 1981 we could read the user entry out of passwd and use their password hash as the input to a KDF for building the KEK. But now security exists, and that's hidden in the (/etc/)shadow. The only way to capture user secrets is at login with a PAM module. So now .NET needs to write an authentication model purely for the purposes of sniffing your password. And SSH needs to do something... So maybe we use a PAM module to sneak a peek at the shadow file. Vunderbar. Oh, except accounts can exist that allow SSH login which don't have passwords. Oh, and we still didn't solve the machine-scoped thing.
In the end, DPAPI is a Windows feature. The ProtectedData
class is a very thin wrapper over DPAPI's CryptProtectData
(and CryptUnprotectData
). There's no parallel on Linux or macOS (that I can find). Without a thing specifically being a DPAPI port to another platform we couldn't really use it, because it might have different constraints (doesn't work after a reboot; doesn't work after a hostname change; doesn't work if the first device returned in NIC enumeration changes (or changes MAC address); et cetera) that we just wouldn't know about, and couldn't control, and then the API becomes dangerous to rely on.
What we have in .NET Core is clear. On Windows it does whatever DPAPI does. On non-Windows it does what Windows DPAPI does on those platforms: not exist.
@bartonjs Thanks for the extensive write-up, appreciated. I guess we have to live with this reality that not everything will be cross platform. I do hope a general solution will be found but I understand the issue a bit better now.
karelz modified the milestone: 2.1.0 on 14 Aug
@karelz This issue was closed but then got assigned to the 2.1.0 milestone. Does that mean it is being addressed somehow now despite the technical challenges enumerated by @bartonjs?
No, milestone just captures when it was closed, it does not imply resolution as Fixed.
@bartonjs NuGet uses ProtectedData
for encrypting passwords (https://github.com/NuGet/Home/issues/1851). Because it is not available, the only option on non-Windows is plain text passwords.
mono has an implementation of ProtectedData
. When the API is first used, a keypair is generated and stored in the file system with appropriate permissions. Then that key is used for encrypting/decrypting. Perhaps corefx can implement something similar.
Lack of this API means storing passwords plain text or people rolling their own. Providing an implementation similar to mono seems better than those options.
@bartonjs could this be implemented in a similar way as Mono? See comment above.
@tmds I think I already covered that in https://github.com/dotnet/corefx/issues/22510#issuecomment-317197092.
NuGet is free to base something off of the Mono implementation of ProtectData (with all the bugs and policy violations fixed), but the real DPAPI is complicated and provides much better guarantees (assuming that https://www.passcape.com/index.php?section=docsys&cmd=details&id=28 is accurate); and I'm not comfortable with "On Windows, a user-scoped secret can't be forced by an admin" (at least, if you never log in again) and "On Linux, well... sorry."
Could a DPAPI-inspired solution be created? Sure, but it would almost certainly be a new, different API.
Most helpful comment
@reddwarf666 No, this API will only ever work on Windows. (and if we were to ever make something that worked cross-platform (which I don't forsee) it would probably be a new API, not this one).
ProtectedData is part of Windows DPAPI (Data Protection API). It encrypts the data using an algorithm of its choice and a key that it probably makes up on the spot. That key it needs to write down, but the whole point of the API is to not require a key, so it encrypts the key with another key.
How does it get the other key? For user-scoped data it uses a key that it derives off of your account. The simplest form is to say that it derives something other than your logon key from your password, such as by using PBKDF2 with a special DPAPI-engineered salt (since I assume it also works on systems where the user has only ever logged in with a smartcard, it must use something other than the password per se),
So, on Windows we call an API that we give some data to, and it knows who the user is, and it encrypts the data. Huzzah. It also has a machine scope, so any user on this computer can read it, but no other computer can recover it. Huzzah.
On macOS the closest equivalent is a keychain. But with the keychain things are different... for one, you can browse it with the Keychain Access program. So if we generate a random KEK and we shove it in the keychain and you delete it then the data is unrecoverable. What keychain do we use? If we make a custom one, what would we do on macOS? How would we do machine-scoped protection in a manner that worked for non-administrators?
On Linux there's not even something like Keychain. If this was 1981 we could read the user entry out of passwd and use their password hash as the input to a KDF for building the KEK. But now security exists, and that's hidden in the (/etc/)shadow. The only way to capture user secrets is at login with a PAM module. So now .NET needs to write an authentication model purely for the purposes of sniffing your password. And SSH needs to do something... So maybe we use a PAM module to sneak a peek at the shadow file. Vunderbar. Oh, except accounts can exist that allow SSH login which don't have passwords. Oh, and we still didn't solve the machine-scoped thing.
In the end, DPAPI is a Windows feature. The
ProtectedData
class is a very thin wrapper over DPAPI'sCryptProtectData
(andCryptUnprotectData
). There's no parallel on Linux or macOS (that I can find). Without a thing specifically being a DPAPI port to another platform we couldn't really use it, because it might have different constraints (doesn't work after a reboot; doesn't work after a hostname change; doesn't work if the first device returned in NIC enumeration changes (or changes MAC address); et cetera) that we just wouldn't know about, and couldn't control, and then the API becomes dangerous to rely on.What we have in .NET Core is clear. On Windows it does whatever DPAPI does. On non-Windows it does what Windows DPAPI does on those platforms: not exist.