.NET Core: How to access Windows Credential Manager if running on Windows (otherwise ignore)?
Asked Answered
D

2

7

So far, to store and retrieve secrets (like credentials) in .NET applications, I successfully used the CredentialManagement package on Windows. Now I'd like to go cross-platform.

So I need to access the Windows Credential Manager from a .NET Core cross-platform application. If it's running on Windows - use the Credential Manager. If it's running on Linux - don't crash (use key chain or whatever, that is the next step).

How would this be done?

(Note: I'm open to alternatives to the Windows Credential Manager but they should provide an equal level of protection.)

Delrosario answered 2/8, 2021 at 8:8 Comment(0)
D
7

I ended up using the Data Protection API for Windows (DPAPI) to store secrets in a file that is encrypted within the scope of the logged in user.

Encrypted storage of a password:

try
{
    var passwordBytes = Encoding.UTF8.GetBytes(password);
    var protectedPasswordBytes = ProtectedData.Protect(passwordBytes, null, DataProtectionScope.CurrentUser);
    var protectedPasswordBytesBase64 = Convert.ToBase64String(protectedPasswordBytes);
    File.WriteAllText(passwordFilePath, protectedPasswordBytesBase64);
} catch (PlatformNotSupportedException)
{
    Debug.WriteLine("Could not store credentials");
}

Decryption:

if (File.Exists(passwordFilePath))
{
    var protectedPasswordBytesBase64 = File.ReadAllText(passwordFilePath);
    var protectedPasswordBytes = Convert.FromBase64String(protectedPasswordBytesBase64);
    var passwordBytes = ProtectedData.Unprotect(protectedPasswordBytes, null, DataProtectionScope.CurrentUser);
    password = Encoding.UTF8.GetString(passwordBytes);
}

(Only works on Windows, for other OSes I'll use other solutions.)

Delrosario answered 27/8, 2021 at 19:43 Comment(3)
Just for cross-reference: I only later found another question that is more generally phrased and also got the DPAPI as answer: #67226627Delrosario
It's worth noting that this does not work on Windows Home and will throw a NotSupportedException. learn.microsoft.com/en-us/dotnet/api/…Reinhardt
@HeinrichUlbricht as stated above DPAPI will not work on Windows Home Edition systems or non-NTFS drives. Did you ignored this tradeoff (and continued to use DPAPI) or switched to another technology? And BTW what approach do you use whe running on Linux? Thank you.Timelag
B
1

To determine the operating system on which your application is running. This can help, for reference

  1. RuntimeInformation.IsOSPlatform(OSPlatform) Method
  2. OSPlatform.Windows Property

A complete example for windows(CredentialManagement + Detect Operating System),

using CredentialManagement;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace DetectOSCredentialManagement
{
    class Program
    {
        static void Main(string[] args)
        {
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                Console.WriteLine("Hello Beauty!");
                Program.SetCredentials("FOO", "friday", "fr!d@y0", PersistanceType.LocalComputer);
                var userpass = Program.GetCredential("FOO");
                Console.WriteLine($"User: {userpass.Username} Password: {userpass.Password}");
                Program.RemoveCredentials("FOO");
                Debug.Assert(Program.GetCredential("FOO") == null);
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                Console.WriteLine("Hello Cutie!");
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
            {
                Console.WriteLine("Too Costly!");
            }
        }

        public static UserPass GetCredential(string target)
        {
            var cm = new Credential { Target = target };
            if (!cm.Load())
            {
                return null;
            }

            // UserPass is just a class with two string properties for user and pass
            return new UserPass(cm.Username, cm.Password);
        }

        public static bool SetCredentials(
             string target, string username, string password, PersistanceType persistenceType)
        {
            return new Credential
            {
                Target = target,
                Username = username,
                Password = password,
                PersistanceType = persistenceType
            }.Save();
        }

        public static bool RemoveCredentials(string target)
        {
            return new Credential { Target = target }.Delete();
        }
    }
    public class UserPass
    {
        public string Username { get; set; }
        public string Password { get; set; }

        public UserPass(string username, string password)
        {
            Username = username;
            Password = password;
        }
    }
}

System.Security.Permissions -- This dll is also need to run above application.

Bournemouth answered 2/8, 2021 at 8:21 Comment(8)
Thank you for this sample! I assume that you used the CredentialManagement.Standard Nuget package instead of CredentialManagement (which is for .NET Framework)? I'm worried about this dependency then: Microsoft.Win32.SystemEvents.4.5.0 - won't this be a problem?Delrosario
You are welcome, I've used CredentialManagement not CredentialManagement.Standard.Bournemouth
Hm did you try this on non-Windows? Because of the .NET Framework dependency this won't work there - or I don't understand it yet 🙃Delrosario
Which DLL CredentialManagement or System.Runtime.InteropServices?Bournemouth
I mean this message that pops up when adding CredentialManagement to a .NET 5 (~=Core) project: Warning NU1701 Package 'CredentialManagement 1.0.2' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8' instead of the project target framework 'net5.0'. This package may not be fully compatible with your project.Delrosario
Yes, I'm also not sure about it therefore I'm trying to find an alternate way to store credentials. Why don't you use database or json file to store credentials? You can encrypt or decrypt them accordingly.Bournemouth
I'm open for alternatives but don't want to reinvent the wheel. But maybe the Data Protection API could be something to look at as an alternative. learn.microsoft.com/en-us/aspnet/core/security/data-protection/…Delrosario
Let us continue this discussion in chat.Bournemouth

© 2022 - 2024 — McMap. All rights reserved.