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:
Kevin Cunnane
2016-09-06 18:12:39 -07:00
committed by GitHub
parent 76e7ea041c
commit 8ca88992be
41 changed files with 3120 additions and 165 deletions

View File

@@ -0,0 +1,158 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Runtime.InteropServices;
using Microsoft.SqlTools.EditorServices.Utility;
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Credentials.OSX
{
/// <summary>
/// OSX implementation of the credential store
/// </summary>
internal class OSXCredentialStore : ICredentialStore
{
public bool DeletePassword(string credentialId)
{
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
return DeletePasswordImpl(credentialId);
}
public bool TryGetPassword(string credentialId, out string password)
{
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
return FindPassword(credentialId, out password);
}
public bool Save(Credential credential)
{
Credential.ValidateForSave(credential);
bool result = false;
// Note: OSX blocks AddPassword if the credential
// already exists, so for now we delete the password if already present since we're updating
// the value. In the future, we could consider updating but it's low value to solve this
DeletePasswordImpl(credential.CredentialId);
// Now add the password
result = AddGenericPassword(credential);
return result;
}
private bool AddGenericPassword(Credential credential)
{
IntPtr passwordPtr = Marshal.StringToCoTaskMemUni(credential.Password);
Interop.Security.OSStatus status = Interop.Security.SecKeychainAddGenericPassword(
IntPtr.Zero,
InteropUtils.GetLengthInBytes(credential.CredentialId),
credential.CredentialId,
0,
null,
InteropUtils.GetLengthInBytes(credential.Password),
passwordPtr,
IntPtr.Zero);
return status == Interop.Security.OSStatus.ErrSecSuccess;
}
/// <summary>
/// Finds the first password matching this credential
/// </summary>
private bool FindPassword(string credentialId, out string password)
{
password = null;
using (KeyChainItemHandle handle = LookupKeyChainItem(credentialId))
{
if( handle == null)
{
return false;
}
password = handle.Password;
}
return true;
}
private KeyChainItemHandle LookupKeyChainItem(string credentialId)
{
UInt32 passwordLength;
IntPtr passwordPtr;
IntPtr item;
Interop.Security.OSStatus status = Interop.Security.SecKeychainFindGenericPassword(
IntPtr.Zero,
InteropUtils.GetLengthInBytes(credentialId),
credentialId,
0,
null,
out passwordLength,
out passwordPtr,
out item);
if(status == Interop.Security.OSStatus.ErrSecSuccess)
{
return new KeyChainItemHandle(item, passwordPtr, passwordLength);
}
return null;
}
private bool DeletePasswordImpl(string credentialId)
{
// Find password, then Delete, then cleanup
using (KeyChainItemHandle handle = LookupKeyChainItem(credentialId))
{
if (handle == null)
{
return false;
}
Interop.Security.OSStatus status = Interop.Security.SecKeychainItemDelete(handle);
return status == Interop.Security.OSStatus.ErrSecSuccess;
}
}
private class KeyChainItemHandle : SafeCreateHandle
{
private IntPtr passwordPtr;
private int passwordLength;
public KeyChainItemHandle() : base()
{
}
public KeyChainItemHandle(IntPtr itemPtr) : this(itemPtr, IntPtr.Zero, 0)
{
}
public KeyChainItemHandle(IntPtr itemPtr, IntPtr passwordPtr, UInt32 passwordLength)
: base(itemPtr)
{
this.passwordPtr = passwordPtr;
this.passwordLength = (int) passwordLength;
}
public string Password
{
get {
if (IsInvalid)
{
return null;
}
return InteropUtils.CopyToString(passwordPtr, passwordLength);
}
}
protected override bool ReleaseHandle()
{
if (passwordPtr != IntPtr.Zero)
{
Interop.Security.SecKeychainItemFreeContent(IntPtr.Zero, passwordPtr);
}
base.ReleaseHandle();
return true;
}
}
}
}