mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-19 09:35:36 -05:00
Credentials store API (#38)
* CredentialService initial impl with Win32 support - Basic CredentialService APIs for Save, Read, Delete - E2E unit tests for Credential Service - Win32 implementation with unit tests * Save Password support on Mac v1 - Basic keychain support on Mac using Interop with the KeyChain APIs - All but 1 unit test passing. This will pass once API is changed, but checking this in with the existing API so that if we decide to alter behavior, we have a reference point. * Remove Username from Credentials API - Removed Username option from Credentials as this caused conflicting behavior on Mac vs Windows * Cleanup Using Statements and add Copyright * Linux CredentialStore Prototype * Linux credential store support - Full support for Linux credential store with tests * Plumbed CredentialService into Program init * Addressing Pull Request comments
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
// TODO Replace this strings class with a resx file
|
||||
internal class CredentialResources
|
||||
{
|
||||
public const string PasswordLengthExceeded = "The password has exceeded 512 bytes.";
|
||||
public const string TargetRequiredForDelete = "Target must be specified to delete a credential.";
|
||||
public const string TargetRequiredForLookup = "Target must be specified to check existance of a credential.";
|
||||
public const string CredentialDisposed = "Win32Credential object is already disposed.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
public class CredentialSet: List<Win32Credential>, IDisposable
|
||||
{
|
||||
bool _disposed;
|
||||
|
||||
public CredentialSet()
|
||||
{
|
||||
}
|
||||
|
||||
public CredentialSet(string target)
|
||||
: this()
|
||||
{
|
||||
if (string.IsNullOrEmpty(target))
|
||||
{
|
||||
throw new ArgumentNullException("target");
|
||||
}
|
||||
Target = target;
|
||||
}
|
||||
|
||||
public string Target { get; set; }
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
|
||||
// Prevent GC Collection since we have already disposed of this object
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~CredentialSet()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (Count > 0)
|
||||
{
|
||||
ForEach(cred => cred.Dispose());
|
||||
}
|
||||
}
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public CredentialSet Load()
|
||||
{
|
||||
LoadInternal();
|
||||
return this;
|
||||
}
|
||||
|
||||
private void LoadInternal()
|
||||
{
|
||||
uint count;
|
||||
|
||||
IntPtr pCredentials = IntPtr.Zero;
|
||||
bool result = NativeMethods.CredEnumerateW(Target, 0, out count, out pCredentials);
|
||||
if (!result)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, string.Format("Win32Exception: {0}", new Win32Exception(Marshal.GetLastWin32Error()).ToString()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read in all of the pointers first
|
||||
IntPtr[] ptrCredList = new IntPtr[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ptrCredList[i] = Marshal.ReadIntPtr(pCredentials, IntPtr.Size*i);
|
||||
}
|
||||
|
||||
// Now let's go through all of the pointers in the list
|
||||
// and create our Credential object(s)
|
||||
List<NativeMethods.CriticalCredentialHandle> credentialHandles =
|
||||
ptrCredList.Select(ptrCred => new NativeMethods.CriticalCredentialHandle(ptrCred)).ToList();
|
||||
|
||||
IEnumerable<Win32Credential> existingCredentials = credentialHandles
|
||||
.Select(handle => handle.GetCredential())
|
||||
.Select(nativeCredential =>
|
||||
{
|
||||
Win32Credential credential = new Win32Credential();
|
||||
credential.LoadInternal(nativeCredential);
|
||||
return credential;
|
||||
});
|
||||
AddRange(existingCredentials);
|
||||
|
||||
// The individual credentials should not be free'd
|
||||
credentialHandles.ForEach(handle => handle.SetHandleAsInvalid());
|
||||
|
||||
// Clean up memory to the Enumeration pointer
|
||||
NativeMethods.CredFree(pCredentials);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
public enum CredentialType: uint
|
||||
{
|
||||
None = 0,
|
||||
Generic = 1,
|
||||
DomainPassword = 2,
|
||||
DomainCertificate = 3,
|
||||
DomainVisiblePassword = 4
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
internal class NativeMethods
|
||||
{
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct CREDENTIAL
|
||||
{
|
||||
public int Flags;
|
||||
public int Type;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string TargetName;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string Comment;
|
||||
public long LastWritten;
|
||||
public int CredentialBlobSize;
|
||||
public IntPtr CredentialBlob;
|
||||
public int Persist;
|
||||
public int AttributeCount;
|
||||
public IntPtr Attributes;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string TargetAlias;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string UserName;
|
||||
}
|
||||
|
||||
[DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern bool CredRead(string target, CredentialType type, int reservedFlag, out IntPtr CredentialPtr);
|
||||
|
||||
[DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern bool CredWrite([In] ref CREDENTIAL userCredential, [In] UInt32 flags);
|
||||
|
||||
[DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)]
|
||||
internal static extern bool CredFree([In] IntPtr cred);
|
||||
|
||||
[DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode)]
|
||||
internal static extern bool CredDelete(StringBuilder target, CredentialType type, int flags);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
internal static extern bool CredEnumerateW(string filter, int flag, out uint count, out IntPtr pCredentials);
|
||||
|
||||
[DllImport("ole32.dll")]
|
||||
internal static extern void CoTaskMemFree(IntPtr ptr);
|
||||
|
||||
|
||||
internal abstract class CriticalHandleZeroOrMinusOneIsInvalid : CriticalHandle
|
||||
{
|
||||
protected CriticalHandleZeroOrMinusOneIsInvalid() : base(IntPtr.Zero)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsInvalid
|
||||
{
|
||||
get { return handle == new IntPtr(0) || handle == new IntPtr(-1); }
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
// Set the handle.
|
||||
internal CriticalCredentialHandle(IntPtr preexistingHandle)
|
||||
{
|
||||
SetHandle(preexistingHandle);
|
||||
}
|
||||
|
||||
internal CREDENTIAL GetCredential()
|
||||
{
|
||||
if (!IsInvalid)
|
||||
{
|
||||
// Get the Credential from the mem location
|
||||
return (CREDENTIAL)Marshal.PtrToStructure<CREDENTIAL>(handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Invalid CriticalHandle!");
|
||||
}
|
||||
}
|
||||
|
||||
// Perform any specific actions to release the handle in the ReleaseHandle method.
|
||||
// Often, you need to use Pinvoke to make a call into the Win32 API to release the
|
||||
// handle. In this case, however, we can use the Marshal class to release the unmanaged memory.
|
||||
|
||||
override protected bool ReleaseHandle()
|
||||
{
|
||||
// If the handle was set, free it. Return success.
|
||||
if (!IsInvalid)
|
||||
{
|
||||
// NOTE: We should also ZERO out the memory allocated to the handle, before free'ing it
|
||||
// so there are no traces of the sensitive data left in memory.
|
||||
CredFree(handle);
|
||||
// Mark the handle as invalid for future users.
|
||||
SetHandleAsInvalid();
|
||||
return true;
|
||||
}
|
||||
// Return false.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
public enum PersistanceType : uint
|
||||
{
|
||||
Session = 1,
|
||||
LocalComputer = 2,
|
||||
Enterprise = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
public class Win32Credential: IDisposable
|
||||
{
|
||||
bool disposed;
|
||||
|
||||
CredentialType type;
|
||||
string target;
|
||||
SecureString password;
|
||||
string username;
|
||||
string description;
|
||||
DateTime lastWriteTime;
|
||||
PersistanceType persistanceType;
|
||||
|
||||
public Win32Credential()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public Win32Credential(string username)
|
||||
: this(username, null)
|
||||
{
|
||||
}
|
||||
|
||||
public Win32Credential(string username, string password)
|
||||
: this(username, password, null)
|
||||
{
|
||||
}
|
||||
|
||||
public Win32Credential(string username, string password, string target)
|
||||
: this(username, password, target, CredentialType.Generic)
|
||||
{
|
||||
}
|
||||
|
||||
public Win32Credential(string username, string password, string target, CredentialType type)
|
||||
{
|
||||
Username = username;
|
||||
Password = password;
|
||||
Target = target;
|
||||
Type = type;
|
||||
PersistanceType = PersistanceType.Session;
|
||||
lastWriteTime = DateTime.MinValue;
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
|
||||
// Prevent GC Collection since we have already disposed of this object
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
SecurePassword.Clear();
|
||||
SecurePassword.Dispose();
|
||||
}
|
||||
}
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
private void CheckNotDisposed()
|
||||
{
|
||||
if (disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(CredentialResources.CredentialDisposed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string Username {
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return username;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
username = value;
|
||||
}
|
||||
}
|
||||
public string Password
|
||||
{
|
||||
get
|
||||
{
|
||||
return SecureStringHelper.CreateString(SecurePassword);
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
SecurePassword = SecureStringHelper.CreateSecureString(string.IsNullOrEmpty(value) ? string.Empty : value);
|
||||
}
|
||||
}
|
||||
public SecureString SecurePassword
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return null == password ? new SecureString() : password.Copy();
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
if (null != password)
|
||||
{
|
||||
password.Clear();
|
||||
password.Dispose();
|
||||
}
|
||||
password = null == value ? new SecureString() : value.Copy();
|
||||
}
|
||||
}
|
||||
public string Target
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return target;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
target = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return description;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
description = value;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime LastWriteTime
|
||||
{
|
||||
get
|
||||
{
|
||||
return LastWriteTimeUtc.ToLocalTime();
|
||||
}
|
||||
}
|
||||
public DateTime LastWriteTimeUtc
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return lastWriteTime;
|
||||
}
|
||||
private set { lastWriteTime = value; }
|
||||
}
|
||||
|
||||
public CredentialType Type
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return type;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
type = value;
|
||||
}
|
||||
}
|
||||
|
||||
public PersistanceType PersistanceType
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return persistanceType;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
persistanceType = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Save()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
byte[] passwordBytes = Encoding.Unicode.GetBytes(Password);
|
||||
if (Password.Length > (512))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(CredentialResources.PasswordLengthExceeded);
|
||||
}
|
||||
|
||||
NativeMethods.CREDENTIAL credential = new NativeMethods.CREDENTIAL();
|
||||
credential.TargetName = Target;
|
||||
credential.UserName = Username;
|
||||
credential.CredentialBlob = Marshal.StringToCoTaskMemUni(Password);
|
||||
credential.CredentialBlobSize = passwordBytes.Length;
|
||||
credential.Comment = Description;
|
||||
credential.Type = (int)Type;
|
||||
credential.Persist = (int) PersistanceType;
|
||||
|
||||
bool result = NativeMethods.CredWrite(ref credential, 0);
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LastWriteTimeUtc = DateTime.UtcNow;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Delete()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
if (string.IsNullOrEmpty(Target))
|
||||
{
|
||||
throw new InvalidOperationException(CredentialResources.TargetRequiredForDelete);
|
||||
}
|
||||
|
||||
StringBuilder target = string.IsNullOrEmpty(Target) ? new StringBuilder() : new StringBuilder(Target);
|
||||
bool result = NativeMethods.CredDelete(target, Type, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool Load()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
IntPtr credPointer;
|
||||
|
||||
bool result = NativeMethods.CredRead(Target, Type, 0, out credPointer);
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
using (NativeMethods.CriticalCredentialHandle credentialHandle = new NativeMethods.CriticalCredentialHandle(credPointer))
|
||||
{
|
||||
LoadInternal(credentialHandle.GetCredential());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Exists()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
if (string.IsNullOrEmpty(Target))
|
||||
{
|
||||
throw new InvalidOperationException(CredentialResources.TargetRequiredForLookup);
|
||||
}
|
||||
|
||||
using (Win32Credential existing = new Win32Credential { Target = Target, Type = Type })
|
||||
{
|
||||
return existing.Load();
|
||||
}
|
||||
}
|
||||
|
||||
internal void LoadInternal(NativeMethods.CREDENTIAL credential)
|
||||
{
|
||||
Username = credential.UserName;
|
||||
if (credential.CredentialBlobSize > 0)
|
||||
{
|
||||
Password = Marshal.PtrToStringUni(credential.CredentialBlob, credential.CredentialBlobSize / 2);
|
||||
}
|
||||
Target = credential.TargetName;
|
||||
Type = (CredentialType)credential.Type;
|
||||
PersistanceType = (PersistanceType)credential.Persist;
|
||||
Description = credential.Comment;
|
||||
LastWriteTimeUtc = DateTime.FromFileTimeUtc(credential.LastWritten);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
/// <summary>
|
||||
/// Win32 implementation of the credential store
|
||||
/// </summary>
|
||||
internal class Win32CredentialStore : ICredentialStore
|
||||
{
|
||||
private const string AnyUsername = "*";
|
||||
|
||||
public bool DeletePassword(string credentialId)
|
||||
{
|
||||
using (Win32Credential cred = new Win32Credential() { Target = credentialId, Username = AnyUsername })
|
||||
{
|
||||
return cred.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetPassword(string credentialId, out string password)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||
password = null;
|
||||
|
||||
using (CredentialSet set = new CredentialSet(credentialId).Load())
|
||||
{
|
||||
// Note: Credentials are disposed on disposal of the set
|
||||
Win32Credential foundCred = null;
|
||||
if (set.Count > 0)
|
||||
{
|
||||
foundCred = set[0];
|
||||
}
|
||||
|
||||
if (foundCred != null)
|
||||
{
|
||||
password = foundCred.Password;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Save(Credential credential)
|
||||
{
|
||||
Credential.ValidateForSave(credential);
|
||||
|
||||
using (Win32Credential cred =
|
||||
new Win32Credential(AnyUsername, credential.Password, credential.CredentialId, CredentialType.Generic)
|
||||
{ PersistanceType = PersistanceType.LocalComputer })
|
||||
{
|
||||
return cred.Save();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user