diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs
index b6d19896..57a7ba6e 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs
@@ -9,7 +9,6 @@ using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Threading.Tasks;
-using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlTools.EditorServices.Utility;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Contracts/Credential.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Contracts/Credential.cs
new file mode 100644
index 00000000..be595ec8
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Contracts/Credential.cs
@@ -0,0 +1,124 @@
+//
+// 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.Hosting.Protocol.Contracts;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials.Contracts
+{
+ ///
+ /// A Credential containing information needed to log into a resource. This is primarily
+ /// defined as a unique with an associated
+ /// that's linked to it.
+ ///
+ public class Credential
+ {
+ ///
+ /// A unique ID to identify the credential being saved.
+ ///
+ public string CredentialId { get; set; }
+
+ ///
+ /// The Password stored for this credential.
+ ///
+ public string Password { get; set; }
+
+ ///
+ /// Default Constructor
+ ///
+ public Credential()
+ {
+ }
+
+ ///
+ /// Constructor used when only is known
+ ///
+ ///
+ public Credential(string credentialId)
+ : this(credentialId, null)
+ {
+
+ }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ ///
+ public Credential(string credentialId, string password)
+ {
+ CredentialId = credentialId;
+ Password = password;
+ }
+
+ internal static Credential Copy(Credential credential)
+ {
+ return new Credential
+ {
+ CredentialId = credential.CredentialId,
+ Password = credential.Password
+ };
+ }
+
+ ///
+ /// Validates the credential has all the properties needed to look up the password
+ ///
+ public static void ValidateForLookup(Credential credential)
+ {
+ Validate.IsNotNull("credential", credential);
+ Validate.IsNotNullOrEmptyString("credential.CredentialId", credential.CredentialId);
+ }
+
+
+ ///
+ /// Validates the credential has all the properties needed to save a password
+ ///
+ public static void ValidateForSave(Credential credential)
+ {
+ ValidateForLookup(credential);
+ Validate.IsNotNullOrEmptyString("credential.Password", credential.Password);
+ }
+ }
+
+ ///
+ /// Read Credential request mapping entry. Expects a Credential with CredentialId,
+ /// and responds with the filled in if found
+ ///
+ public class ReadCredentialRequest
+ {
+ ///
+ /// Request definition
+ ///
+ public static readonly
+ RequestType Type =
+ RequestType.Create("credential/read");
+ }
+
+ ///
+ /// Save Credential request mapping entry
+ ///
+ public class SaveCredentialRequest
+ {
+ ///
+ /// Request definition
+ ///
+ public static readonly
+ RequestType Type =
+ RequestType.Create("credential/save");
+ }
+
+ ///
+ /// Delete Credential request mapping entry
+ ///
+ public class DeleteCredentialRequest
+ {
+ ///
+ /// Request definition
+ ///
+ public static readonly
+ RequestType Type =
+ RequestType.Create("credential/delete");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/CredentialService.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/CredentialService.cs
new file mode 100644
index 00000000..f1a80807
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/CredentialService.cs
@@ -0,0 +1,151 @@
+//
+// 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 System.Threading.Tasks;
+using Microsoft.SqlTools.EditorServices.Utility;
+using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
+using Microsoft.SqlTools.ServiceLayer.Credentials.Linux;
+using Microsoft.SqlTools.ServiceLayer.Credentials.OSX;
+using Microsoft.SqlTools.ServiceLayer.Credentials.Win32;
+using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials
+{
+ ///
+ /// Service responsible for securing credentials in a platform-neutral manner. This provides
+ /// a generic API for read, save and delete credentials
+ ///
+ public class CredentialService
+ {
+ internal static string DefaultSecretsFolder = ".sqlsecrets";
+ internal const string DefaultSecretsFile = "sqlsecrets.json";
+
+
+ ///
+ /// Singleton service instance
+ ///
+ private static Lazy instance
+ = new Lazy(() => new CredentialService());
+
+ ///
+ /// Gets the singleton service instance
+ ///
+ public static CredentialService Instance
+ {
+ get
+ {
+ return instance.Value;
+ }
+ }
+
+ private ICredentialStore credStore;
+
+ ///
+ /// Default constructor is private since it's a singleton class
+ ///
+ private CredentialService()
+ : this(null, new LinuxCredentialStore.StoreConfig()
+ { CredentialFolder = DefaultSecretsFolder, CredentialFile = DefaultSecretsFile, IsRelativeToUserHomeDir = true})
+ {
+ }
+
+ ///
+ /// Internal for testing purposes only
+ ///
+ internal CredentialService(ICredentialStore store, LinuxCredentialStore.StoreConfig config)
+ {
+ this.credStore = store != null ? store : GetStoreForOS(config);
+ }
+
+ ///
+ /// Internal for testing purposes only
+ ///
+ internal static ICredentialStore GetStoreForOS(LinuxCredentialStore.StoreConfig config)
+ {
+ if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return new Win32CredentialStore();
+ }
+ else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return new OSXCredentialStore();
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ return new LinuxCredentialStore(config);
+ }
+ throw new InvalidOperationException("Platform not currently supported");
+ }
+
+ public void InitializeService(IProtocolEndpoint serviceHost)
+ {
+ // Register request and event handlers with the Service Host
+ serviceHost.SetRequestHandler(ReadCredentialRequest.Type, HandleReadCredentialRequest);
+ serviceHost.SetRequestHandler(SaveCredentialRequest.Type, HandleSaveCredentialRequest);
+ serviceHost.SetRequestHandler(DeleteCredentialRequest.Type, HandleDeleteCredentialRequest);
+ }
+
+ public async Task HandleReadCredentialRequest(Credential credential, RequestContext requestContext)
+ {
+ Func doRead = () =>
+ {
+ return ReadCredential(credential);
+ };
+ await HandleRequest(doRead, requestContext, "HandleReadCredentialRequest");
+ }
+
+
+ private Credential ReadCredential(Credential credential)
+ {
+ Credential.ValidateForLookup(credential);
+
+ Credential result = Credential.Copy(credential);
+ string password;
+ if (credStore.TryGetPassword(credential.CredentialId, out password))
+ {
+ result.Password = password;
+ }
+ return result;
+ }
+
+ public async Task HandleSaveCredentialRequest(Credential credential, RequestContext requestContext)
+ {
+ Func doSave = () =>
+ {
+ Credential.ValidateForSave(credential);
+ return credStore.Save(credential);
+ };
+ await HandleRequest(doSave, requestContext, "HandleSaveCredentialRequest");
+ }
+
+ public async Task HandleDeleteCredentialRequest(Credential credential, RequestContext requestContext)
+ {
+ Func doDelete = () =>
+ {
+ Credential.ValidateForLookup(credential);
+ return credStore.DeletePassword(credential.CredentialId);
+ };
+ await HandleRequest(doDelete, requestContext, "HandleDeleteCredentialRequest");
+ }
+
+ private async Task HandleRequest(Func handler, RequestContext requestContext, string requestType)
+ {
+ Logger.Write(LogLevel.Verbose, requestType);
+
+ try
+ {
+ T result = handler();
+ await requestContext.SendResult(result);
+ }
+ catch (Exception ex)
+ {
+ await requestContext.SendError(ex.ToString());
+ }
+ }
+
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/ICredentialStore.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/ICredentialStore.cs
new file mode 100644
index 00000000..0fa51cdd
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/ICredentialStore.cs
@@ -0,0 +1,41 @@
+//
+// 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.ServiceLayer.Credentials.Contracts;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials
+{
+ ///
+ /// An support securely saving and retrieving passwords
+ ///
+ public interface ICredentialStore
+ {
+ ///
+ /// Saves a Password linked to a given Credential
+ ///
+ ///
+ /// A to be saved.
+ /// and are required
+ ///
+ /// True if successful, false otherwise
+ bool Save(Credential credential);
+
+ ///
+ /// Gets a Password and sets it into a object
+ ///
+ /// The name of the credential to find the password for. This is required
+ /// Out value
+ /// true if password was found, false otherwise
+ bool TryGetPassword(string credentialId, out string password);
+
+ ///
+ /// Deletes a password linked to a given credential
+ ///
+ /// The name of the credential to find the password for. This is required
+ /// True if password existed and was deleted, false otherwise
+ bool DeletePassword(string credentialId);
+
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/InteropUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/InteropUtils.cs
new file mode 100644
index 00000000..fdb5343e
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/InteropUtils.cs
@@ -0,0 +1,36 @@
+//
+// 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 System.Text;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials
+{
+ internal static class InteropUtils
+ {
+
+ ///
+ /// Gets the length in bytes for a Unicode string, for use in interop where length must be defined
+ ///
+ public static UInt32 GetLengthInBytes(string value)
+ {
+
+ return Convert.ToUInt32( (value != null ? Encoding.Unicode.GetByteCount(value) : 0) );
+ }
+
+ public static string CopyToString(IntPtr ptr, int length)
+ {
+ if (ptr == IntPtr.Zero || length == 0)
+ {
+ return null;
+ }
+ byte[] pwdBytes = new byte[length];
+ Marshal.Copy(ptr, pwdBytes, 0, (int)length);
+ return Encoding.Unicode.GetString(pwdBytes, 0, (int)length);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/CredentialsWrapper.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/CredentialsWrapper.cs
new file mode 100644
index 00000000..3deab819
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/CredentialsWrapper.cs
@@ -0,0 +1,18 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System.Collections.Generic;
+using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
+{
+ ///
+ /// Simplified class to enable writing a set of credentials to/from disk
+ ///
+ public class CredentialsWrapper
+ {
+ public List Credentials { get; set; }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/FileTokenStorage.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/FileTokenStorage.cs
new file mode 100644
index 00000000..ef2c2a67
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/FileTokenStorage.cs
@@ -0,0 +1,87 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.SqlTools.EditorServices.Utility;
+using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
+using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
+using Newtonsoft.Json;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
+{
+ public class FileTokenStorage
+ {
+ private const int OwnerAccessMode = 384; // Permission 0600 - owner read/write, nobody else has access
+
+ private object lockObject = new object();
+
+ private string fileName;
+
+ public FileTokenStorage(string fileName)
+ {
+ Validate.IsNotNullOrEmptyString("fileName", fileName);
+ this.fileName = fileName;
+ }
+
+ public void AddEntries(IEnumerable newEntries, IEnumerable existingEntries)
+ {
+ var allEntries = existingEntries.Concat(newEntries);
+ this.SaveEntries(allEntries);
+ }
+
+ public void Clear()
+ {
+ this.SaveEntries(new List());
+ }
+
+ public IEnumerable LoadEntries()
+ {
+ if(!File.Exists(this.fileName))
+ {
+ return Enumerable.Empty();
+ }
+
+ string serializedCreds;
+ lock (lockObject)
+ {
+ serializedCreds = File.ReadAllText(this.fileName);
+ }
+
+ CredentialsWrapper creds = JsonConvert.DeserializeObject(serializedCreds, Constants.JsonSerializerSettings);
+ if(creds != null)
+ {
+ return creds.Credentials;
+ }
+ return Enumerable.Empty();
+ }
+
+ public void SaveEntries(IEnumerable entries)
+ {
+ CredentialsWrapper credentials = new CredentialsWrapper() { Credentials = entries.ToList() };
+ string serializedCreds = JsonConvert.SerializeObject(credentials, Constants.JsonSerializerSettings);
+
+ lock(lockObject)
+ {
+ WriteToFile(this.fileName, serializedCreds);
+ }
+ }
+
+ private static void WriteToFile(string filePath, string fileContents)
+ {
+ string dir = Path.GetDirectoryName(filePath);
+ if(!Directory.Exists(dir))
+ {
+ Directory.CreateDirectory(dir);
+ }
+
+ // Overwrite file, then use ChMod to ensure we have
+ File.WriteAllText(filePath, fileContents);
+ // set appropriate permissions so only current user can read/write
+ Interop.Sys.ChMod(filePath, OwnerAccessMode);
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/Interop.Errors.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/Interop.Errors.cs
new file mode 100644
index 00000000..f3b1d5f5
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/Interop.Errors.cs
@@ -0,0 +1,221 @@
+//
+// 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;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials
+{
+ internal static partial class Interop
+ {
+ /// Common Unix errno error codes.
+ internal enum Error
+ {
+ // These values were defined in src/Native/System.Native/fxerrno.h
+ //
+ // They compare against values obtained via Interop.Sys.GetLastError() not Marshal.GetLastWin32Error()
+ // which obtains the raw errno that varies between unixes. The strong typing as an enum is meant to
+ // prevent confusing the two. Casting to or from int is suspect. Use GetLastErrorInfo() if you need to
+ // correlate these to the underlying platform values or obtain the corresponding error message.
+ //
+
+ SUCCESS = 0,
+
+ E2BIG = 0x10001, // Argument list too long.
+ EACCES = 0x10002, // Permission denied.
+ EADDRINUSE = 0x10003, // Address in use.
+ EADDRNOTAVAIL = 0x10004, // Address not available.
+ EAFNOSUPPORT = 0x10005, // Address family not supported.
+ EAGAIN = 0x10006, // Resource unavailable, try again (same value as EWOULDBLOCK),
+ EALREADY = 0x10007, // Connection already in progress.
+ EBADF = 0x10008, // Bad file descriptor.
+ EBADMSG = 0x10009, // Bad message.
+ EBUSY = 0x1000A, // Device or resource busy.
+ ECANCELED = 0x1000B, // Operation canceled.
+ ECHILD = 0x1000C, // No child processes.
+ ECONNABORTED = 0x1000D, // Connection aborted.
+ ECONNREFUSED = 0x1000E, // Connection refused.
+ ECONNRESET = 0x1000F, // Connection reset.
+ EDEADLK = 0x10010, // Resource deadlock would occur.
+ EDESTADDRREQ = 0x10011, // Destination address required.
+ EDOM = 0x10012, // Mathematics argument out of domain of function.
+ EDQUOT = 0x10013, // Reserved.
+ EEXIST = 0x10014, // File exists.
+ EFAULT = 0x10015, // Bad address.
+ EFBIG = 0x10016, // File too large.
+ EHOSTUNREACH = 0x10017, // Host is unreachable.
+ EIDRM = 0x10018, // Identifier removed.
+ EILSEQ = 0x10019, // Illegal byte sequence.
+ EINPROGRESS = 0x1001A, // Operation in progress.
+ EINTR = 0x1001B, // Interrupted function.
+ EINVAL = 0x1001C, // Invalid argument.
+ EIO = 0x1001D, // I/O error.
+ EISCONN = 0x1001E, // Socket is connected.
+ EISDIR = 0x1001F, // Is a directory.
+ ELOOP = 0x10020, // Too many levels of symbolic links.
+ EMFILE = 0x10021, // File descriptor value too large.
+ EMLINK = 0x10022, // Too many links.
+ EMSGSIZE = 0x10023, // Message too large.
+ EMULTIHOP = 0x10024, // Reserved.
+ ENAMETOOLONG = 0x10025, // Filename too long.
+ ENETDOWN = 0x10026, // Network is down.
+ ENETRESET = 0x10027, // Connection aborted by network.
+ ENETUNREACH = 0x10028, // Network unreachable.
+ ENFILE = 0x10029, // Too many files open in system.
+ ENOBUFS = 0x1002A, // No buffer space available.
+ ENODEV = 0x1002C, // No such device.
+ ENOENT = 0x1002D, // No such file or directory.
+ ENOEXEC = 0x1002E, // Executable file format error.
+ ENOLCK = 0x1002F, // No locks available.
+ ENOLINK = 0x10030, // Reserved.
+ ENOMEM = 0x10031, // Not enough space.
+ ENOMSG = 0x10032, // No message of the desired type.
+ ENOPROTOOPT = 0x10033, // Protocol not available.
+ ENOSPC = 0x10034, // No space left on device.
+ ENOSYS = 0x10037, // Function not supported.
+ ENOTCONN = 0x10038, // The socket is not connected.
+ ENOTDIR = 0x10039, // Not a directory or a symbolic link to a directory.
+ ENOTEMPTY = 0x1003A, // Directory not empty.
+ ENOTSOCK = 0x1003C, // Not a socket.
+ ENOTSUP = 0x1003D, // Not supported (same value as EOPNOTSUP).
+ ENOTTY = 0x1003E, // Inappropriate I/O control operation.
+ ENXIO = 0x1003F, // No such device or address.
+ EOVERFLOW = 0x10040, // Value too large to be stored in data type.
+ EPERM = 0x10042, // Operation not permitted.
+ EPIPE = 0x10043, // Broken pipe.
+ EPROTO = 0x10044, // Protocol error.
+ EPROTONOSUPPORT = 0x10045, // Protocol not supported.
+ EPROTOTYPE = 0x10046, // Protocol wrong type for socket.
+ ERANGE = 0x10047, // Result too large.
+ EROFS = 0x10048, // Read-only file system.
+ ESPIPE = 0x10049, // Invalid seek.
+ ESRCH = 0x1004A, // No such process.
+ ESTALE = 0x1004B, // Reserved.
+ ETIMEDOUT = 0x1004D, // Connection timed out.
+ ETXTBSY = 0x1004E, // Text file busy.
+ EXDEV = 0x1004F, // Cross-device link.
+ ESOCKTNOSUPPORT = 0x1005E, // Socket type not supported.
+ EPFNOSUPPORT = 0x10060, // Protocol family not supported.
+ ESHUTDOWN = 0x1006C, // Socket shutdown.
+ EHOSTDOWN = 0x10070, // Host is down.
+ ENODATA = 0x10071, // No data available.
+
+ // POSIX permits these to have the same value and we make them always equal so
+ // that CoreFX cannot introduce a dependency on distinguishing between them that
+ // would not work on all platforms.
+ EOPNOTSUPP = ENOTSUP, // Operation not supported on socket.
+ EWOULDBLOCK = EAGAIN, // Operation would block.
+ }
+
+
+ // Represents a platform-agnostic Error and underlying platform-specific errno
+ internal struct ErrorInfo
+ {
+ private Error _error;
+ private int _rawErrno;
+
+ internal ErrorInfo(int errno)
+ {
+ _error = Interop.Sys.ConvertErrorPlatformToPal(errno);
+ _rawErrno = errno;
+ }
+
+ internal ErrorInfo(Error error)
+ {
+ _error = error;
+ _rawErrno = -1;
+ }
+
+ internal Error Error
+ {
+ get { return _error; }
+ }
+
+ internal int RawErrno
+ {
+ get { return _rawErrno == -1 ? (_rawErrno = Interop.Sys.ConvertErrorPalToPlatform(_error)) : _rawErrno; }
+ }
+
+ internal string GetErrorMessage()
+ {
+ return Interop.Sys.StrError(RawErrno);
+ }
+
+ public override string ToString()
+ {
+ return string.Format(
+ "RawErrno: {0} Error: {1} GetErrorMessage: {2}", // No localization required; text is member names used for debugging purposes
+ RawErrno, Error, GetErrorMessage());
+ }
+ }
+
+ internal partial class Sys
+ {
+ internal static Error GetLastError()
+ {
+ return ConvertErrorPlatformToPal(Marshal.GetLastWin32Error());
+ }
+
+ internal static ErrorInfo GetLastErrorInfo()
+ {
+ return new ErrorInfo(Marshal.GetLastWin32Error());
+ }
+
+ internal static string StrError(int platformErrno)
+ {
+ int maxBufferLength = 1024; // should be long enough for most any UNIX error
+ IntPtr buffer = Marshal.AllocHGlobal(maxBufferLength);
+ try
+ {
+ IntPtr message = StrErrorR(platformErrno, buffer, maxBufferLength);
+
+ if (message == IntPtr.Zero)
+ {
+ // This means the buffer was not large enough, but still contains
+ // as much of the error message as possible and is guaranteed to
+ // be null-terminated. We're not currently resizing/retrying because
+ // maxBufferLength is large enough in practice, but we could do
+ // so here in the future if necessary.
+ message = buffer;
+ }
+
+ string returnMsg = Marshal.PtrToStringAnsi(message);
+ return returnMsg;
+ }
+ finally
+ {
+ // Deallocate the buffer we created
+ Marshal.FreeHGlobal(buffer);
+ }
+ }
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPlatformToPal")]
+ internal static extern Error ConvertErrorPlatformToPal(int platformErrno);
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPalToPlatform")]
+ internal static extern int ConvertErrorPalToPlatform(Error error);
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_StrErrorR")]
+ private static extern IntPtr StrErrorR(int platformErrno, IntPtr buffer, int bufferSize);
+ }
+ }
+
+ // NOTE: extension method can't be nested inside Interop class.
+ internal static class InteropErrorExtensions
+ {
+ // Intended usage is e.g. Interop.Error.EFAIL.Info() for brevity
+ // vs. new Interop.ErrorInfo(Interop.Error.EFAIL) for synthesizing
+ // errors. Errors originated from the system should be obtained
+ // via GetLastErrorInfo(), not GetLastError().Info() as that will
+ // convert twice, which is not only inefficient but also lossy if
+ // we ever encounter a raw errno that no equivalent in the Error
+ // enum.
+ public static Interop.ErrorInfo Info(this Interop.Error error)
+ {
+ return new Interop.ErrorInfo(error);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/Interop.Sys.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/Interop.Sys.cs
new file mode 100644
index 00000000..8777ab0c
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/Interop.Sys.cs
@@ -0,0 +1,42 @@
+//
+// 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;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials
+{
+ internal static partial class Interop
+ {
+ internal static partial class Sys
+ {
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ChMod", SetLastError = true)]
+ internal static extern int ChMod(string path, int mode);
+
+ internal struct Passwd
+ {
+ internal IntPtr Name; // char*
+ internal IntPtr Password; // char*
+ internal uint UserId;
+ internal uint GroupId;
+ internal IntPtr UserInfo; // char*
+ internal IntPtr HomeDirectory; // char*
+ internal IntPtr Shell; // char*
+ };
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetPwUidR", SetLastError = false)]
+ internal static extern int GetPwUidR(uint uid, out Passwd pwd, IntPtr buf, int bufLen);
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetEUid")]
+ internal static extern uint GetEUid();
+
+ private static partial class Libraries
+ {
+ internal const string SystemNative = "System.Native";
+ }
+ }
+
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/LinuxCredentialStore.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/LinuxCredentialStore.cs
new file mode 100644
index 00000000..6d6b5908
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/LinuxCredentialStore.cs
@@ -0,0 +1,231 @@
+//
+// 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.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Microsoft.SqlTools.EditorServices.Utility;
+using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
+{
+ ///
+ /// Linux implementation of the credential store.
+ ///
+ ///
+ /// This entire implementation may need to be revised to support encryption of
+ /// passwords and protection of them when loaded into memory.
+ ///
+ ///
+ internal class LinuxCredentialStore : ICredentialStore
+ {
+ internal struct StoreConfig
+ {
+ public string CredentialFolder { get; set; }
+ public string CredentialFile { get; set; }
+ public bool IsRelativeToUserHomeDir { get; set; }
+ }
+
+ private string credentialFolderPath;
+ private string credentialFileName;
+ private FileTokenStorage storage;
+
+ public LinuxCredentialStore(StoreConfig config)
+ {
+ Validate.IsNotNull("config", config);
+ Validate.IsNotNullOrEmptyString("credentialFolder", config.CredentialFolder);
+ Validate.IsNotNullOrEmptyString("credentialFileName", config.CredentialFile);
+
+ this.credentialFolderPath = config.IsRelativeToUserHomeDir ? GetUserScopedDirectory(config.CredentialFolder) : config.CredentialFolder;
+ this.credentialFileName = config.CredentialFile;
+
+
+ string combinedPath = Path.Combine(this.credentialFolderPath, this.credentialFileName);
+ storage = new FileTokenStorage(combinedPath);
+ }
+
+ public bool DeletePassword(string credentialId)
+ {
+ Validate.IsNotNullOrEmptyString("credentialId", credentialId);
+ IEnumerable creds;
+ if (LoadCredentialsAndFilterById(credentialId, out creds))
+ {
+ storage.SaveEntries(creds);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets filtered credentials with a specific ID filtered out
+ ///
+ /// True if the credential to filter was removed, false if it was not found
+ private bool LoadCredentialsAndFilterById(string idToFilter, out IEnumerable creds)
+ {
+ bool didRemove = false;
+ creds = storage.LoadEntries().Where(cred =>
+ {
+ if (IsCredentialMatch(idToFilter, cred))
+ {
+ didRemove = true;
+ return false; // filter this out
+ }
+ return true;
+ }).ToList(); // Call ToList ensures Where clause is executed so didRemove can be evaluated
+
+ return didRemove;
+ }
+
+ private static bool IsCredentialMatch(string credentialId, Credential cred)
+ {
+ return string.Equals(credentialId, cred.CredentialId, StringComparison.Ordinal);
+ }
+
+ public bool TryGetPassword(string credentialId, out string password)
+ {
+ Validate.IsNotNullOrEmptyString("credentialId", credentialId);
+ Credential cred = storage.LoadEntries().FirstOrDefault(c => IsCredentialMatch(credentialId, c));
+ if (cred != null)
+ {
+ password = cred.Password;
+ return true;
+ }
+
+ // Else this was not found in the list
+ password = null;
+ return false;
+ }
+
+ public bool Save(Credential credential)
+ {
+ Credential.ValidateForSave(credential);
+
+ // Load the credentials, removing the existing Cred for this
+ IEnumerable creds;
+ LoadCredentialsAndFilterById(credential.CredentialId, out creds);
+ storage.SaveEntries(creds.Append(credential));
+
+ return true;
+ }
+
+
+ ///
+ /// Internal for testing purposes only
+ ///
+ internal string CredentialFolderPath
+ {
+ get { return this.credentialFolderPath; }
+ }
+
+ ///
+ /// Concatenates a directory to the user home directory's path
+ ///
+ internal static string GetUserScopedDirectory(string userPath)
+ {
+ string homeDir = GetHomeDirectory() ?? string.Empty;
+ return Path.Combine(homeDir, userPath);
+ }
+
+
+ /// Gets the current user's home directory.
+ /// The path to the home directory, or null if it could not be determined.
+ internal static string GetHomeDirectory()
+ {
+ // First try to get the user's home directory from the HOME environment variable.
+ // This should work in most cases.
+ string userHomeDirectory = Environment.GetEnvironmentVariable("HOME");
+ if (!string.IsNullOrEmpty(userHomeDirectory))
+ {
+ return userHomeDirectory;
+ }
+
+ // In initialization conditions, however, the "HOME" environment variable may
+ // not yet be set. For such cases, consult with the password entry.
+
+ // First try with a buffer that should suffice for 99% of cases.
+ // Note that, theoretically, userHomeDirectory may be null in the success case
+ // if we simply couldn't find a home directory for the current user.
+ // In that case, we pass back the null value and let the caller decide
+ // what to do.
+ return GetHomeDirectoryFromPw();
+ }
+
+ internal static string GetHomeDirectoryFromPw()
+ {
+ string userHomeDirectory = null;
+ const int BufLen = 1024;
+ if (TryGetHomeDirectoryFromPasswd(BufLen, out userHomeDirectory))
+ {
+ return userHomeDirectory;
+ }
+ // Fallback to heap allocations if necessary, growing the buffer until
+ // we succeed. TryGetHomeDirectory will throw if there's an unexpected error.
+ int lastBufLen = BufLen;
+ while (true)
+ {
+ lastBufLen *= 2;
+ if (TryGetHomeDirectoryFromPasswd(lastBufLen, out userHomeDirectory))
+ {
+ return userHomeDirectory;
+ }
+ }
+ }
+
+ /// Wrapper for getpwuid_r.
+ /// The length of the buffer to use when storing the password result.
+ /// The resulting path; null if the user didn't have an entry.
+ /// true if the call was successful (path may still be null); false is a larger buffer is needed.
+ private static bool TryGetHomeDirectoryFromPasswd(int bufLen, out string path)
+ {
+ // Call getpwuid_r to get the passwd struct
+ Interop.Sys.Passwd passwd;
+ IntPtr buffer = Marshal.AllocHGlobal(bufLen);
+ try
+ {
+ int error = Interop.Sys.GetPwUidR(Interop.Sys.GetEUid(), out passwd, buffer, bufLen);
+
+ // If the call succeeds, give back the home directory path retrieved
+ if (error == 0)
+ {
+ Debug.Assert(passwd.HomeDirectory != IntPtr.Zero);
+ path = Marshal.PtrToStringAnsi(passwd.HomeDirectory);
+ return true;
+ }
+
+ // If the current user's entry could not be found, give back null
+ // path, but still return true as false indicates the buffer was
+ // too small.
+ if (error == -1)
+ {
+ path = null;
+ return true;
+ }
+
+ var errorInfo = new Interop.ErrorInfo(error);
+
+ // If the call failed because the buffer was too small, return false to
+ // indicate the caller should try again with a larger buffer.
+ if (errorInfo.Error == Interop.Error.ERANGE)
+ {
+ path = null;
+ return false;
+ }
+
+ // Otherwise, fail.
+ throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
+ }
+ finally
+ {
+ // Deallocate the buffer we created
+ Marshal.FreeHGlobal(buffer);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.CoreFoundation.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.CoreFoundation.cs
new file mode 100644
index 00000000..140dfc63
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.CoreFoundation.cs
@@ -0,0 +1,105 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials
+{
+ internal static partial class Interop
+ {
+ internal static partial class CoreFoundation
+ {
+ ///
+ /// Tells the OS what encoding the passed in String is in. These come from the CFString.h header file in the CoreFoundation framework.
+ ///
+ private enum CFStringBuiltInEncodings : uint
+ {
+ kCFStringEncodingMacRoman = 0,
+ kCFStringEncodingWindowsLatin1 = 0x0500,
+ kCFStringEncodingISOLatin1 = 0x0201,
+ kCFStringEncodingNextStepLatin = 0x0B01,
+ kCFStringEncodingASCII = 0x0600,
+ kCFStringEncodingUnicode = 0x0100,
+ kCFStringEncodingUTF8 = 0x08000100,
+ kCFStringEncodingNonLossyASCII = 0x0BFF,
+
+ kCFStringEncodingUTF16 = 0x0100,
+ kCFStringEncodingUTF16BE = 0x10000100,
+ kCFStringEncodingUTF16LE = 0x14000100,
+ kCFStringEncodingUTF32 = 0x0c000100,
+ kCFStringEncodingUTF32BE = 0x18000100,
+ kCFStringEncodingUTF32LE = 0x1c000100
+ }
+
+ ///
+ /// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it.
+ ///
+ /// Should be IntPtr.Zero
+ /// The string to get a CFStringRef for
+ /// The encoding of the str variable. This should be UTF 8 for OS X
+ /// Returns a pointer to a CFString on success; otherwise, returns IntPtr.Zero
+ /// For *nix systems, the CLR maps ANSI to UTF-8, so be explicit about that
+ [DllImport(Interop.Libraries.CoreFoundationLibrary, CharSet = CharSet.Ansi)]
+ private static extern SafeCreateHandle CFStringCreateWithCString(
+ IntPtr allocator,
+ string str,
+ CFStringBuiltInEncodings encoding);
+
+ ///
+ /// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it.
+ ///
+ /// The string to get a CFStringRef for
+ /// Returns a valid SafeCreateHandle to a CFString on success; otherwise, returns an invalid SafeCreateHandle
+ internal static SafeCreateHandle CFStringCreateWithCString(string str)
+ {
+ return CFStringCreateWithCString(IntPtr.Zero, str, CFStringBuiltInEncodings.kCFStringEncodingUTF8);
+ }
+
+ ///
+ /// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it.
+ ///
+ /// Should be IntPtr.Zero
+ /// The values to put in the array
+ /// The number of values in the array
+ /// Should be IntPtr.Zero
+ /// Returns a pointer to a CFArray on success; otherwise, returns IntPtr.Zero
+ [DllImport(Interop.Libraries.CoreFoundationLibrary)]
+ private static extern SafeCreateHandle CFArrayCreate(
+ IntPtr allocator,
+ [MarshalAs(UnmanagedType.LPArray)]
+ IntPtr[] values,
+ ulong numValues,
+ IntPtr callbacks);
+
+ ///
+ /// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it.
+ ///
+ /// The values to put in the array
+ /// The number of values in the array
+ /// Returns a valid SafeCreateHandle to a CFArray on success; otherwise, returns an invalid SafeCreateHandle
+ internal static SafeCreateHandle CFArrayCreate(IntPtr[] values, ulong numValues)
+ {
+ return CFArrayCreate(IntPtr.Zero, values, numValues, IntPtr.Zero);
+ }
+
+ ///
+ /// You should retain a Core Foundation object when you receive it from elsewhere
+ /// (that is, you did not create or copy it) and you want it to persist. If you
+ /// retain a Core Foundation object you are responsible for releasing it
+ ///
+ /// The CFType object to retain. This value must not be NULL
+ /// The input value
+ [DllImport(Interop.Libraries.CoreFoundationLibrary)]
+ internal extern static IntPtr CFRetain(IntPtr ptr);
+
+ ///
+ /// Decrements the reference count on the specified object and, if the ref count hits 0, cleans up the object.
+ ///
+ /// The pointer on which to decrement the reference count.
+ [DllImport(Interop.Libraries.CoreFoundationLibrary)]
+ internal extern static void CFRelease(IntPtr ptr);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.Libraries.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.Libraries.cs
new file mode 100644
index 00000000..7ad5b639
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.Libraries.cs
@@ -0,0 +1,17 @@
+//
+// 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
+{
+ internal static partial class Interop
+ {
+ private static partial class Libraries
+ {
+ internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
+ internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices";
+ internal const string SecurityLibrary = "/System/Library/Frameworks/Security.framework/Security";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.Security.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.Security.cs
new file mode 100644
index 00000000..0a6209e8
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.Security.cs
@@ -0,0 +1,459 @@
+//
+// 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;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials
+{
+ internal partial class Interop
+ {
+ internal partial class Security
+ {
+
+ [DllImport(Libraries.SecurityLibrary, CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern OSStatus SecKeychainAddGenericPassword(IntPtr keyChainRef, UInt32 serviceNameLength, string serviceName,
+ UInt32 accountNameLength, string accountName, UInt32 passwordLength, IntPtr password, [Out] IntPtr itemRef);
+
+ ///
+ /// Find a generic password based on the attributes passed
+ ///
+ ///
+ /// A reference to an array of keychains to search, a single keychain, or NULL to search the user's default keychain search list.
+ ///
+ /// The length of the buffer pointed to by serviceName.
+ /// A pointer to a string containing the service name.
+ /// The length of the buffer pointed to by accountName.
+ /// A pointer to a string containing the account name.
+ /// On return, the length of the buffer pointed to by passwordData.
+ ///
+ /// On return, a pointer to a data buffer containing the password.
+ /// Your application must call SecKeychainItemFreeContent(NULL, passwordData)
+ /// to release this data buffer when it is no longer needed.Pass NULL if you are not interested in retrieving the password data at
+ /// this time, but simply want to find the item reference.
+ ///
+ /// On return, a reference to the keychain item which was found.
+ /// A result code that should be in
+ ///
+ /// The SecKeychainFindGenericPassword function finds the first generic password item which matches the attributes you provide.
+ /// Most attributes are optional; you should pass only as many as you need to narrow the search sufficiently for your application's intended use.
+ /// SecKeychainFindGenericPassword optionally returns a reference to the found item.
+ ///
+ [DllImport(Libraries.SecurityLibrary, CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern OSStatus SecKeychainFindGenericPassword(IntPtr keyChainRef, UInt32 serviceNameLength, string serviceName,
+ UInt32 accountNameLength, string accountName, out UInt32 passwordLength, out IntPtr password, out IntPtr itemRef);
+
+ ///
+ /// Releases the memory used by the keychain attribute list and the keychain data retrieved in a previous call to SecKeychainItemCopyContent.
+ ///
+ /// A pointer to the attribute list to release. Pass NULL to ignore this parameter.
+ /// A pointer to the data buffer to release. Pass NULL to ignore this parameter.
+ /// A result code that should be in
+ [DllImport(Libraries.SecurityLibrary, SetLastError = true)]
+ internal static extern OSStatus SecKeychainItemFreeContent([In] IntPtr attrList, [In] IntPtr data);
+
+ ///
+ /// Deletes a keychain item from the default keychain's permanent data store.
+ ///
+ /// A keychain item reference of the item to delete.
+ /// A result code that should be in
+ ///
+ /// If itemRef has not previously been added to the keychain, SecKeychainItemDelete does nothing and returns ErrSecSuccess.
+ /// IMPORTANT: SecKeychainItemDelete does not dispose the memory occupied by the item reference itself;
+ /// use the CFRelease function when you are completely * * finished with an item.
+ ///
+ [DllImport(Libraries.SecurityLibrary, SetLastError = true)]
+ internal static extern OSStatus SecKeychainItemDelete(SafeHandle itemRef);
+
+ #region OSStatus Codes
+ /// Common Unix errno error codes.
+ internal enum OSStatus
+ {
+ ErrSecSuccess = 0, /* No error. */
+ ErrSecUnimplemented = -4, /* Function or operation not implemented. */
+ ErrSecDskFull = -34,
+ ErrSecIO = -36, /*I/O error (bummers)*/
+
+ ErrSecParam = -50, /* One or more parameters passed to a function were not valid. */
+ ErrSecWrPerm = -61, /* write permissions error*/
+ ErrSecAllocate = -108, /* Failed to allocate memory. */
+ ErrSecUserCanceled = -128, /* User canceled the operation. */
+ ErrSecBadReq = -909, /* Bad parameter or invalid state for operation. */
+
+ ErrSecInternalComponent = -2070,
+ ErrSecCoreFoundationUnknown = -4960,
+
+ ErrSecNotAvailable = -25291, /* No keychain is available. You may need to restart your computer. */
+ ErrSecReadOnly = -25292, /* This keychain cannot be modified. */
+ ErrSecAuthFailed = -25293, /* The user name or passphrase you entered is not correct. */
+ ErrSecNoSuchKeychain = -25294, /* The specified keychain could not be found. */
+ ErrSecInvalidKeychain = -25295, /* The specified keychain is not a valid keychain file. */
+ ErrSecDuplicateKeychain = -25296, /* A keychain with the same name already exists. */
+ ErrSecDuplicateCallback = -25297, /* The specified callback function is already installed. */
+ ErrSecInvalidCallback = -25298, /* The specified callback function is not valid. */
+ ErrSecDuplicateItem = -25299, /* The specified item already exists in the keychain. */
+ ErrSecItemNotFound = -25300, /* The specified item could not be found in the keychain. */
+ ErrSecBufferTooSmall = -25301, /* There is not enough memory available to use the specified item. */
+ ErrSecDataTooLarge = -25302, /* This item contains information which is too large or in a format that cannot be displayed. */
+ ErrSecNoSuchAttr = -25303, /* The specified attribute does not exist. */
+ ErrSecInvalidItemRef = -25304, /* The specified item is no longer valid. It may have been deleted from the keychain. */
+ ErrSecInvalidSearchRef = -25305, /* Unable to search the current keychain. */
+ ErrSecNoSuchClass = -25306, /* The specified item does not appear to be a valid keychain item. */
+ ErrSecNoDefaultKeychain = -25307, /* A default keychain could not be found. */
+ ErrSecInteractionNotAllowed = -25308, /* User interaction is not allowed. */
+ ErrSecReadOnlyAttr = -25309, /* The specified attribute could not be modified. */
+ ErrSecWrongSecVersion = -25310, /* This keychain was created by a different version of the system software and cannot be opened. */
+ ErrSecKeySizeNotAllowed = -25311, /* This item specifies a key size which is too large. */
+ ErrSecNoStorageModule = -25312, /* A required component (data storage module) could not be loaded. You may need to restart your computer. */
+ ErrSecNoCertificateModule = -25313, /* A required component (certificate module) could not be loaded. You may need to restart your computer. */
+ ErrSecNoPolicyModule = -25314, /* A required component (policy module) could not be loaded. You may need to restart your computer. */
+ ErrSecInteractionRequired = -25315, /* User interaction is required, but is currently not allowed. */
+ ErrSecDataNotAvailable = -25316, /* The contents of this item cannot be retrieved. */
+ ErrSecDataNotModifiable = -25317, /* The contents of this item cannot be modified. */
+ ErrSecCreateChainFailed = -25318, /* One or more certificates required to validate this certificate cannot be found. */
+ ErrSecInvalidPrefsDomain = -25319, /* The specified preferences domain is not valid. */
+ ErrSecInDarkWake = -25320, /* In dark wake, no UI possible */
+
+ ErrSecACLNotSimple = -25240, /* The specified access control list is not in standard (simple) form. */
+ ErrSecPolicyNotFound = -25241, /* The specified policy cannot be found. */
+ ErrSecInvalidTrustSetting = -25242, /* The specified trust setting is invalid. */
+ ErrSecNoAccessForItem = -25243, /* The specified item has no access control. */
+ ErrSecInvalidOwnerEdit = -25244, /* Invalid attempt to change the owner of this item. */
+ ErrSecTrustNotAvailable = -25245, /* No trust results are available. */
+ ErrSecUnsupportedFormat = -25256, /* Import/Export format unsupported. */
+ ErrSecUnknownFormat = -25257, /* Unknown format in import. */
+ ErrSecKeyIsSensitive = -25258, /* Key material must be wrapped for export. */
+ ErrSecMultiplePrivKeys = -25259, /* An attempt was made to import multiple private keys. */
+ ErrSecPassphraseRequired = -25260, /* Passphrase is required for import/export. */
+ ErrSecInvalidPasswordRef = -25261, /* The password reference was invalid. */
+ ErrSecInvalidTrustSettings = -25262, /* The Trust Settings Record was corrupted. */
+ ErrSecNoTrustSettings = -25263, /* No Trust Settings were found. */
+ ErrSecPkcs12VerifyFailure = -25264, /* MAC verification failed during PKCS12 import (wrong password?) */
+ ErrSecNotSigner = -26267, /* A certificate was not signed by its proposed parent. */
+
+ ErrSecDecode = -26275, /* Unable to decode the provided data. */
+
+ ErrSecServiceNotAvailable = -67585, /* The required service is not available. */
+ ErrSecInsufficientClientID = -67586, /* The client ID is not correct. */
+ ErrSecDeviceReset = -67587, /* A device reset has occurred. */
+ ErrSecDeviceFailed = -67588, /* A device failure has occurred. */
+ ErrSecAppleAddAppACLSubject = -67589, /* Adding an application ACL subject failed. */
+ ErrSecApplePublicKeyIncomplete = -67590, /* The public key is incomplete. */
+ ErrSecAppleSignatureMismatch = -67591, /* A signature mismatch has occurred. */
+ ErrSecAppleInvalidKeyStartDate = -67592, /* The specified key has an invalid start date. */
+ ErrSecAppleInvalidKeyEndDate = -67593, /* The specified key has an invalid end date. */
+ ErrSecConversionError = -67594, /* A conversion error has occurred. */
+ ErrSecAppleSSLv2Rollback = -67595, /* A SSLv2 rollback error has occurred. */
+ ErrSecDiskFull = -34, /* The disk is full. */
+ ErrSecQuotaExceeded = -67596, /* The quota was exceeded. */
+ ErrSecFileTooBig = -67597, /* The file is too big. */
+ ErrSecInvalidDatabaseBlob = -67598, /* The specified database has an invalid blob. */
+ ErrSecInvalidKeyBlob = -67599, /* The specified database has an invalid key blob. */
+ ErrSecIncompatibleDatabaseBlob = -67600, /* The specified database has an incompatible blob. */
+ ErrSecIncompatibleKeyBlob = -67601, /* The specified database has an incompatible key blob. */
+ ErrSecHostNameMismatch = -67602, /* A host name mismatch has occurred. */
+ ErrSecUnknownCriticalExtensionFlag = -67603, /* There is an unknown critical extension flag. */
+ ErrSecNoBasicConstraints = -67604, /* No basic constraints were found. */
+ ErrSecNoBasicConstraintsCA = -67605, /* No basic CA constraints were found. */
+ ErrSecInvalidAuthorityKeyID = -67606, /* The authority key ID is not valid. */
+ ErrSecInvalidSubjectKeyID = -67607, /* The subject key ID is not valid. */
+ ErrSecInvalidKeyUsageForPolicy = -67608, /* The key usage is not valid for the specified policy. */
+ ErrSecInvalidExtendedKeyUsage = -67609, /* The extended key usage is not valid. */
+ ErrSecInvalidIDLinkage = -67610, /* The ID linkage is not valid. */
+ ErrSecPathLengthConstraintExceeded = -67611, /* The path length constraint was exceeded. */
+ ErrSecInvalidRoot = -67612, /* The root or anchor certificate is not valid. */
+ ErrSecCRLExpired = -67613, /* The CRL has expired. */
+ ErrSecCRLNotValidYet = -67614, /* The CRL is not yet valid. */
+ ErrSecCRLNotFound = -67615, /* The CRL was not found. */
+ ErrSecCRLServerDown = -67616, /* The CRL server is down. */
+ ErrSecCRLBadURI = -67617, /* The CRL has a bad Uniform Resource Identifier. */
+ ErrSecUnknownCertExtension = -67618, /* An unknown certificate extension was encountered. */
+ ErrSecUnknownCRLExtension = -67619, /* An unknown CRL extension was encountered. */
+ ErrSecCRLNotTrusted = -67620, /* The CRL is not trusted. */
+ ErrSecCRLPolicyFailed = -67621, /* The CRL policy failed. */
+ ErrSecIDPFailure = -67622, /* The issuing distribution point was not valid. */
+ ErrSecSMIMEEmailAddressesNotFound = -67623, /* An email address mismatch was encountered. */
+ ErrSecSMIMEBadExtendedKeyUsage = -67624, /* The appropriate extended key usage for SMIME was not found. */
+ ErrSecSMIMEBadKeyUsage = -67625, /* The key usage is not compatible with SMIME. */
+ ErrSecSMIMEKeyUsageNotCritical = -67626, /* The key usage extension is not marked as critical. */
+ ErrSecSMIMENoEmailAddress = -67627, /* No email address was found in the certificate. */
+ ErrSecSMIMESubjAltNameNotCritical = -67628, /* The subject alternative name extension is not marked as critical. */
+ ErrSecSSLBadExtendedKeyUsage = -67629, /* The appropriate extended key usage for SSL was not found. */
+ ErrSecOCSPBadResponse = -67630, /* The OCSP response was incorrect or could not be parsed. */
+ ErrSecOCSPBadRequest = -67631, /* The OCSP request was incorrect or could not be parsed. */
+ ErrSecOCSPUnavailable = -67632, /* OCSP service is unavailable. */
+ ErrSecOCSPStatusUnrecognized = -67633, /* The OCSP server did not recognize this certificate. */
+ ErrSecEndOfData = -67634, /* An end-of-data was detected. */
+ ErrSecIncompleteCertRevocationCheck = -67635, /* An incomplete certificate revocation check occurred. */
+ ErrSecNetworkFailure = -67636, /* A network failure occurred. */
+ ErrSecOCSPNotTrustedToAnchor = -67637, /* The OCSP response was not trusted to a root or anchor certificate. */
+ ErrSecRecordModified = -67638, /* The record was modified. */
+ ErrSecOCSPSignatureError = -67639, /* The OCSP response had an invalid signature. */
+ ErrSecOCSPNoSigner = -67640, /* The OCSP response had no signer. */
+ ErrSecOCSPResponderMalformedReq = -67641, /* The OCSP responder was given a malformed request. */
+ ErrSecOCSPResponderInternalError = -67642, /* The OCSP responder encountered an internal error. */
+ ErrSecOCSPResponderTryLater = -67643, /* The OCSP responder is busy, try again later. */
+ ErrSecOCSPResponderSignatureRequired = -67644, /* The OCSP responder requires a signature. */
+ ErrSecOCSPResponderUnauthorized = -67645, /* The OCSP responder rejected this request as unauthorized. */
+ ErrSecOCSPResponseNonceMismatch = -67646, /* The OCSP response nonce did not match the request. */
+ ErrSecCodeSigningBadCertChainLength = -67647, /* Code signing encountered an incorrect certificate chain length. */
+ ErrSecCodeSigningNoBasicConstraints = -67648, /* Code signing found no basic constraints. */
+ ErrSecCodeSigningBadPathLengthConstraint= -67649, /* Code signing encountered an incorrect path length constraint. */
+ ErrSecCodeSigningNoExtendedKeyUsage = -67650, /* Code signing found no extended key usage. */
+ ErrSecCodeSigningDevelopment = -67651, /* Code signing indicated use of a development-only certificate. */
+ ErrSecResourceSignBadCertChainLength = -67652, /* Resource signing has encountered an incorrect certificate chain length. */
+ ErrSecResourceSignBadExtKeyUsage = -67653, /* Resource signing has encountered an error in the extended key usage. */
+ ErrSecTrustSettingDeny = -67654, /* The trust setting for this policy was set to Deny. */
+ ErrSecInvalidSubjectName = -67655, /* An invalid certificate subject name was encountered. */
+ ErrSecUnknownQualifiedCertStatement = -67656, /* An unknown qualified certificate statement was encountered. */
+ ErrSecMobileMeRequestQueued = -67657, /* The MobileMe request will be sent during the next connection. */
+ ErrSecMobileMeRequestRedirected = -67658, /* The MobileMe request was redirected. */
+ ErrSecMobileMeServerError = -67659, /* A MobileMe server error occurred. */
+ ErrSecMobileMeServerNotAvailable = -67660, /* The MobileMe server is not available. */
+ ErrSecMobileMeServerAlreadyExists = -67661, /* The MobileMe server reported that the item already exists. */
+ ErrSecMobileMeServerServiceErr = -67662, /* A MobileMe service error has occurred. */
+ ErrSecMobileMeRequestAlreadyPending = -67663, /* A MobileMe request is already pending. */
+ ErrSecMobileMeNoRequestPending = -67664, /* MobileMe has no request pending. */
+ ErrSecMobileMeCSRVerifyFailure = -67665, /* A MobileMe CSR verification failure has occurred. */
+ ErrSecMobileMeFailedConsistencyCheck = -67666, /* MobileMe has found a failed consistency check. */
+ ErrSecNotInitialized = -67667, /* A function was called without initializing CSSM. */
+ ErrSecInvalidHandleUsage = -67668, /* The CSSM handle does not match with the service type. */
+ ErrSecPVCReferentNotFound = -67669, /* A reference to the calling module was not found in the list of authorized callers. */
+ ErrSecFunctionIntegrityFail = -67670, /* A function address was not within the verified module. */
+ ErrSecInternalError = -67671, /* An internal error has occurred. */
+ ErrSecMemoryError = -67672, /* A memory error has occurred. */
+ ErrSecInvalidData = -67673, /* Invalid data was encountered. */
+ ErrSecMDSError = -67674, /* A Module Directory Service error has occurred. */
+ ErrSecInvalidPointer = -67675, /* An invalid pointer was encountered. */
+ ErrSecSelfCheckFailed = -67676, /* Self-check has failed. */
+ ErrSecFunctionFailed = -67677, /* A function has failed. */
+ ErrSecModuleManifestVerifyFailed = -67678, /* A module manifest verification failure has occurred. */
+ ErrSecInvalidGUID = -67679, /* An invalid GUID was encountered. */
+ ErrSecInvalidHandle = -67680, /* An invalid handle was encountered. */
+ ErrSecInvalidDBList = -67681, /* An invalid DB list was encountered. */
+ ErrSecInvalidPassthroughID = -67682, /* An invalid passthrough ID was encountered. */
+ ErrSecInvalidNetworkAddress = -67683, /* An invalid network address was encountered. */
+ ErrSecCRLAlreadySigned = -67684, /* The certificate revocation list is already signed. */
+ ErrSecInvalidNumberOfFields = -67685, /* An invalid number of fields were encountered. */
+ ErrSecVerificationFailure = -67686, /* A verification failure occurred. */
+ ErrSecUnknownTag = -67687, /* An unknown tag was encountered. */
+ ErrSecInvalidSignature = -67688, /* An invalid signature was encountered. */
+ ErrSecInvalidName = -67689, /* An invalid name was encountered. */
+ ErrSecInvalidCertificateRef = -67690, /* An invalid certificate reference was encountered. */
+ ErrSecInvalidCertificateGroup = -67691, /* An invalid certificate group was encountered. */
+ ErrSecTagNotFound = -67692, /* The specified tag was not found. */
+ ErrSecInvalidQuery = -67693, /* The specified query was not valid. */
+ ErrSecInvalidValue = -67694, /* An invalid value was detected. */
+ ErrSecCallbackFailed = -67695, /* A callback has failed. */
+ ErrSecACLDeleteFailed = -67696, /* An ACL delete operation has failed. */
+ ErrSecACLReplaceFailed = -67697, /* An ACL replace operation has failed. */
+ ErrSecACLAddFailed = -67698, /* An ACL add operation has failed. */
+ ErrSecACLChangeFailed = -67699, /* An ACL change operation has failed. */
+ ErrSecInvalidAccessCredentials = -67700, /* Invalid access credentials were encountered. */
+ ErrSecInvalidRecord = -67701, /* An invalid record was encountered. */
+ ErrSecInvalidACL = -67702, /* An invalid ACL was encountered. */
+ ErrSecInvalidSampleValue = -67703, /* An invalid sample value was encountered. */
+ ErrSecIncompatibleVersion = -67704, /* An incompatible version was encountered. */
+ ErrSecPrivilegeNotGranted = -67705, /* The privilege was not granted. */
+ ErrSecInvalidScope = -67706, /* An invalid scope was encountered. */
+ ErrSecPVCAlreadyConfigured = -67707, /* The PVC is already configured. */
+ ErrSecInvalidPVC = -67708, /* An invalid PVC was encountered. */
+ ErrSecEMMLoadFailed = -67709, /* The EMM load has failed. */
+ ErrSecEMMUnloadFailed = -67710, /* The EMM unload has failed. */
+ ErrSecAddinLoadFailed = -67711, /* The add-in load operation has failed. */
+ ErrSecInvalidKeyRef = -67712, /* An invalid key was encountered. */
+ ErrSecInvalidKeyHierarchy = -67713, /* An invalid key hierarchy was encountered. */
+ ErrSecAddinUnloadFailed = -67714, /* The add-in unload operation has failed. */
+ ErrSecLibraryReferenceNotFound = -67715, /* A library reference was not found. */
+ ErrSecInvalidAddinFunctionTable = -67716, /* An invalid add-in function table was encountered. */
+ ErrSecInvalidServiceMask = -67717, /* An invalid service mask was encountered. */
+ ErrSecModuleNotLoaded = -67718, /* A module was not loaded. */
+ ErrSecInvalidSubServiceID = -67719, /* An invalid subservice ID was encountered. */
+ ErrSecAttributeNotInContext = -67720, /* An attribute was not in the context. */
+ ErrSecModuleManagerInitializeFailed = -67721, /* A module failed to initialize. */
+ ErrSecModuleManagerNotFound = -67722, /* A module was not found. */
+ ErrSecEventNotificationCallbackNotFound = -67723, /* An event notification callback was not found. */
+ ErrSecInputLengthError = -67724, /* An input length error was encountered. */
+ ErrSecOutputLengthError = -67725, /* An output length error was encountered. */
+ ErrSecPrivilegeNotSupported = -67726, /* The privilege is not supported. */
+ ErrSecDeviceError = -67727, /* A device error was encountered. */
+ ErrSecAttachHandleBusy = -67728, /* The CSP handle was busy. */
+ ErrSecNotLoggedIn = -67729, /* You are not logged in. */
+ ErrSecAlgorithmMismatch = -67730, /* An algorithm mismatch was encountered. */
+ ErrSecKeyUsageIncorrect = -67731, /* The key usage is incorrect. */
+ ErrSecKeyBlobTypeIncorrect = -67732, /* The key blob type is incorrect. */
+ ErrSecKeyHeaderInconsistent = -67733, /* The key header is inconsistent. */
+ ErrSecUnsupportedKeyFormat = -67734, /* The key header format is not supported. */
+ ErrSecUnsupportedKeySize = -67735, /* The key size is not supported. */
+ ErrSecInvalidKeyUsageMask = -67736, /* The key usage mask is not valid. */
+ ErrSecUnsupportedKeyUsageMask = -67737, /* The key usage mask is not supported. */
+ ErrSecInvalidKeyAttributeMask = -67738, /* The key attribute mask is not valid. */
+ ErrSecUnsupportedKeyAttributeMask = -67739, /* The key attribute mask is not supported. */
+ ErrSecInvalidKeyLabel = -67740, /* The key label is not valid. */
+ ErrSecUnsupportedKeyLabel = -67741, /* The key label is not supported. */
+ ErrSecInvalidKeyFormat = -67742, /* The key format is not valid. */
+ ErrSecUnsupportedVectorOfBuffers = -67743, /* The vector of buffers is not supported. */
+ ErrSecInvalidInputVector = -67744, /* The input vector is not valid. */
+ ErrSecInvalidOutputVector = -67745, /* The output vector is not valid. */
+ ErrSecInvalidContext = -67746, /* An invalid context was encountered. */
+ ErrSecInvalidAlgorithm = -67747, /* An invalid algorithm was encountered. */
+ ErrSecInvalidAttributeKey = -67748, /* A key attribute was not valid. */
+ ErrSecMissingAttributeKey = -67749, /* A key attribute was missing. */
+ ErrSecInvalidAttributeInitVector = -67750, /* An init vector attribute was not valid. */
+ ErrSecMissingAttributeInitVector = -67751, /* An init vector attribute was missing. */
+ ErrSecInvalidAttributeSalt = -67752, /* A salt attribute was not valid. */
+ ErrSecMissingAttributeSalt = -67753, /* A salt attribute was missing. */
+ ErrSecInvalidAttributePadding = -67754, /* A padding attribute was not valid. */
+ ErrSecMissingAttributePadding = -67755, /* A padding attribute was missing. */
+ ErrSecInvalidAttributeRandom = -67756, /* A random number attribute was not valid. */
+ ErrSecMissingAttributeRandom = -67757, /* A random number attribute was missing. */
+ ErrSecInvalidAttributeSeed = -67758, /* A seed attribute was not valid. */
+ ErrSecMissingAttributeSeed = -67759, /* A seed attribute was missing. */
+ ErrSecInvalidAttributePassphrase = -67760, /* A passphrase attribute was not valid. */
+ ErrSecMissingAttributePassphrase = -67761, /* A passphrase attribute was missing. */
+ ErrSecInvalidAttributeKeyLength = -67762, /* A key length attribute was not valid. */
+ ErrSecMissingAttributeKeyLength = -67763, /* A key length attribute was missing. */
+ ErrSecInvalidAttributeBlockSize = -67764, /* A block size attribute was not valid. */
+ ErrSecMissingAttributeBlockSize = -67765, /* A block size attribute was missing. */
+ ErrSecInvalidAttributeOutputSize = -67766, /* An output size attribute was not valid. */
+ ErrSecMissingAttributeOutputSize = -67767, /* An output size attribute was missing. */
+ ErrSecInvalidAttributeRounds = -67768, /* The number of rounds attribute was not valid. */
+ ErrSecMissingAttributeRounds = -67769, /* The number of rounds attribute was missing. */
+ ErrSecInvalidAlgorithmParms = -67770, /* An algorithm parameters attribute was not valid. */
+ ErrSecMissingAlgorithmParms = -67771, /* An algorithm parameters attribute was missing. */
+ ErrSecInvalidAttributeLabel = -67772, /* A label attribute was not valid. */
+ ErrSecMissingAttributeLabel = -67773, /* A label attribute was missing. */
+ ErrSecInvalidAttributeKeyType = -67774, /* A key type attribute was not valid. */
+ ErrSecMissingAttributeKeyType = -67775, /* A key type attribute was missing. */
+ ErrSecInvalidAttributeMode = -67776, /* A mode attribute was not valid. */
+ ErrSecMissingAttributeMode = -67777, /* A mode attribute was missing. */
+ ErrSecInvalidAttributeEffectiveBits = -67778, /* An effective bits attribute was not valid. */
+ ErrSecMissingAttributeEffectiveBits = -67779, /* An effective bits attribute was missing. */
+ ErrSecInvalidAttributeStartDate = -67780, /* A start date attribute was not valid. */
+ ErrSecMissingAttributeStartDate = -67781, /* A start date attribute was missing. */
+ ErrSecInvalidAttributeEndDate = -67782, /* An end date attribute was not valid. */
+ ErrSecMissingAttributeEndDate = -67783, /* An end date attribute was missing. */
+ ErrSecInvalidAttributeVersion = -67784, /* A version attribute was not valid. */
+ ErrSecMissingAttributeVersion = -67785, /* A version attribute was missing. */
+ ErrSecInvalidAttributePrime = -67786, /* A prime attribute was not valid. */
+ ErrSecMissingAttributePrime = -67787, /* A prime attribute was missing. */
+ ErrSecInvalidAttributeBase = -67788, /* A base attribute was not valid. */
+ ErrSecMissingAttributeBase = -67789, /* A base attribute was missing. */
+ ErrSecInvalidAttributeSubprime = -67790, /* A subprime attribute was not valid. */
+ ErrSecMissingAttributeSubprime = -67791, /* A subprime attribute was missing. */
+ ErrSecInvalidAttributeIterationCount = -67792, /* An iteration count attribute was not valid. */
+ ErrSecMissingAttributeIterationCount = -67793, /* An iteration count attribute was missing. */
+ ErrSecInvalidAttributeDLDBHandle = -67794, /* A database handle attribute was not valid. */
+ ErrSecMissingAttributeDLDBHandle = -67795, /* A database handle attribute was missing. */
+ ErrSecInvalidAttributeAccessCredentials = -67796, /* An access credentials attribute was not valid. */
+ ErrSecMissingAttributeAccessCredentials = -67797, /* An access credentials attribute was missing. */
+ ErrSecInvalidAttributePublicKeyFormat = -67798, /* A public key format attribute was not valid. */
+ ErrSecMissingAttributePublicKeyFormat = -67799, /* A public key format attribute was missing. */
+ ErrSecInvalidAttributePrivateKeyFormat = -67800, /* A private key format attribute was not valid. */
+ ErrSecMissingAttributePrivateKeyFormat = -67801, /* A private key format attribute was missing. */
+ ErrSecInvalidAttributeSymmetricKeyFormat = -67802, /* A symmetric key format attribute was not valid. */
+ ErrSecMissingAttributeSymmetricKeyFormat = -67803, /* A symmetric key format attribute was missing. */
+ ErrSecInvalidAttributeWrappedKeyFormat = -67804, /* A wrapped key format attribute was not valid. */
+ ErrSecMissingAttributeWrappedKeyFormat = -67805, /* A wrapped key format attribute was missing. */
+ ErrSecStagedOperationInProgress = -67806, /* A staged operation is in progress. */
+ ErrSecStagedOperationNotStarted = -67807, /* A staged operation was not started. */
+ ErrSecVerifyFailed = -67808, /* A cryptographic verification failure has occurred. */
+ ErrSecQuerySizeUnknown = -67809, /* The query size is unknown. */
+ ErrSecBlockSizeMismatch = -67810, /* A block size mismatch occurred. */
+ ErrSecPublicKeyInconsistent = -67811, /* The public key was inconsistent. */
+ ErrSecDeviceVerifyFailed = -67812, /* A device verification failure has occurred. */
+ ErrSecInvalidLoginName = -67813, /* An invalid login name was detected. */
+ ErrSecAlreadyLoggedIn = -67814, /* The user is already logged in. */
+ ErrSecInvalidDigestAlgorithm = -67815, /* An invalid digest algorithm was detected. */
+ ErrSecInvalidCRLGroup = -67816, /* An invalid CRL group was detected. */
+ ErrSecCertificateCannotOperate = -67817, /* The certificate cannot operate. */
+ ErrSecCertificateExpired = -67818, /* An expired certificate was detected. */
+ ErrSecCertificateNotValidYet = -67819, /* The certificate is not yet valid. */
+ ErrSecCertificateRevoked = -67820, /* The certificate was revoked. */
+ ErrSecCertificateSuspended = -67821, /* The certificate was suspended. */
+ ErrSecInsufficientCredentials = -67822, /* Insufficient credentials were detected. */
+ ErrSecInvalidAction = -67823, /* The action was not valid. */
+ ErrSecInvalidAuthority = -67824, /* The authority was not valid. */
+ ErrSecVerifyActionFailed = -67825, /* A verify action has failed. */
+ ErrSecInvalidCertAuthority = -67826, /* The certificate authority was not valid. */
+ ErrSecInvaldCRLAuthority = -67827, /* The CRL authority was not valid. */
+ ErrSecInvalidCRLEncoding = -67828, /* The CRL encoding was not valid. */
+ ErrSecInvalidCRLType = -67829, /* The CRL type was not valid. */
+ ErrSecInvalidCRL = -67830, /* The CRL was not valid. */
+ ErrSecInvalidFormType = -67831, /* The form type was not valid. */
+ ErrSecInvalidID = -67832, /* The ID was not valid. */
+ ErrSecInvalidIdentifier = -67833, /* The identifier was not valid. */
+ ErrSecInvalidIndex = -67834, /* The index was not valid. */
+ ErrSecInvalidPolicyIdentifiers = -67835, /* The policy identifiers are not valid. */
+ ErrSecInvalidTimeString = -67836, /* The time specified was not valid. */
+ ErrSecInvalidReason = -67837, /* The trust policy reason was not valid. */
+ ErrSecInvalidRequestInputs = -67838, /* The request inputs are not valid. */
+ ErrSecInvalidResponseVector = -67839, /* The response vector was not valid. */
+ ErrSecInvalidStopOnPolicy = -67840, /* The stop-on policy was not valid. */
+ ErrSecInvalidTuple = -67841, /* The tuple was not valid. */
+ ErrSecMultipleValuesUnsupported = -67842, /* Multiple values are not supported. */
+ ErrSecNotTrusted = -67843, /* The trust policy was not trusted. */
+ ErrSecNoDefaultAuthority = -67844, /* No default authority was detected. */
+ ErrSecRejectedForm = -67845, /* The trust policy had a rejected form. */
+ ErrSecRequestLost = -67846, /* The request was lost. */
+ ErrSecRequestRejected = -67847, /* The request was rejected. */
+ ErrSecUnsupportedAddressType = -67848, /* The address type is not supported. */
+ ErrSecUnsupportedService = -67849, /* The service is not supported. */
+ ErrSecInvalidTupleGroup = -67850, /* The tuple group was not valid. */
+ ErrSecInvalidBaseACLs = -67851, /* The base ACLs are not valid. */
+ ErrSecInvalidTupleCredendtials = -67852, /* The tuple credentials are not valid. */
+ ErrSecInvalidEncoding = -67853, /* The encoding was not valid. */
+ ErrSecInvalidValidityPeriod = -67854, /* The validity period was not valid. */
+ ErrSecInvalidRequestor = -67855, /* The requestor was not valid. */
+ ErrSecRequestDescriptor = -67856, /* The request descriptor was not valid. */
+ ErrSecInvalidBundleInfo = -67857, /* The bundle information was not valid. */
+ ErrSecInvalidCRLIndex = -67858, /* The CRL index was not valid. */
+ ErrSecNoFieldValues = -67859, /* No field values were detected. */
+ ErrSecUnsupportedFieldFormat = -67860, /* The field format is not supported. */
+ ErrSecUnsupportedIndexInfo = -67861, /* The index information is not supported. */
+ ErrSecUnsupportedLocality = -67862, /* The locality is not supported. */
+ ErrSecUnsupportedNumAttributes = -67863, /* The number of attributes is not supported. */
+ ErrSecUnsupportedNumIndexes = -67864, /* The number of indexes is not supported. */
+ ErrSecUnsupportedNumRecordTypes = -67865, /* The number of record types is not supported. */
+ ErrSecFieldSpecifiedMultiple = -67866, /* Too many fields were specified. */
+ ErrSecIncompatibleFieldFormat = -67867, /* The field format was incompatible. */
+ ErrSecInvalidParsingModule = -67868, /* The parsing module was not valid. */
+ ErrSecDatabaseLocked = -67869, /* The database is locked. */
+ ErrSecDatastoreIsOpen = -67870, /* The data store is open. */
+ ErrSecMissingValue = -67871, /* A missing value was detected. */
+ ErrSecUnsupportedQueryLimits = -67872, /* The query limits are not supported. */
+ ErrSecUnsupportedNumSelectionPreds = -67873, /* The number of selection predicates is not supported. */
+ ErrSecUnsupportedOperator = -67874, /* The operator is not supported. */
+ ErrSecInvalidDBLocation = -67875, /* The database location is not valid. */
+ ErrSecInvalidAccessRequest = -67876, /* The access request is not valid. */
+ ErrSecInvalidIndexInfo = -67877, /* The index information is not valid. */
+ ErrSecInvalidNewOwner = -67878, /* The new owner is not valid. */
+ ErrSecInvalidModifyMode = -67879, /* The modify mode is not valid. */
+ ErrSecMissingRequiredExtension = -67880, /* A required certificate extension is missing. */
+ ErrSecExtendedKeyUsageNotCritical = -67881, /* The extended key usage extension was not marked critical. */
+ ErrSecTimestampMissing = -67882, /* A timestamp was expected but was not found. */
+ ErrSecTimestampInvalid = -67883, /* The timestamp was not valid. */
+ ErrSecTimestampNotTrusted = -67884, /* The timestamp was not trusted. */
+ ErrSecTimestampServiceNotAvailable = -67885, /* The timestamp service is not available. */
+ ErrSecTimestampBadAlg = -67886, /* An unrecognized or unsupported Algorithm Identifier in timestamp. */
+ ErrSecTimestampBadRequest = -67887, /* The timestamp transaction is not permitted or supported. */
+ ErrSecTimestampBadDataFormat = -67888, /* The timestamp data submitted has the wrong format. */
+ ErrSecTimestampTimeNotAvailable = -67889, /* The time source for the Timestamp Authority is not available. */
+ ErrSecTimestampUnacceptedPolicy = -67890, /* The requested policy is not supported by the Timestamp Authority. */
+ ErrSecTimestampUnacceptedExtension = -67891, /* The requested extension is not supported by the Timestamp Authority. */
+ ErrSecTimestampAddInfoNotAvailable = -67892, /* The additional information requested is not available. */
+ ErrSecTimestampSystemFailure = -67893, /* The timestamp request cannot be handled due to system failure. */
+ ErrSecSigningTimeMissing = -67894, /* A signing time was expected but was not found. */
+ ErrSecTimestampRejection = -67895, /* A timestamp transaction was rejected. */
+ ErrSecTimestampWaiting = -67896, /* A timestamp transaction is waiting. */
+ ErrSecTimestampRevocationWarning = -67897, /* A timestamp authority revocation warning was issued. */
+ ErrSecTimestampRevocationNotification = -67898, /* A timestamp authority revocation notification was issued. */
+ }
+
+ #endregion
+ }
+ }
+}
+
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/OSXCredentialStore.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/OSXCredentialStore.cs
new file mode 100644
index 00000000..dc868040
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/OSXCredentialStore.cs
@@ -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
+{
+ ///
+ /// OSX implementation of the credential store
+ ///
+ 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;
+ }
+
+ ///
+ /// Finds the first password matching this credential
+ ///
+ 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/SafeCreateHandle.OSX.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/SafeCreateHandle.OSX.cs
new file mode 100644
index 00000000..5beaaf26
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/SafeCreateHandle.OSX.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials
+
+{
+ ///
+ /// This class is a wrapper around the Create pattern in OS X where
+ /// if a Create* function is called, the caller must also CFRelease
+ /// on the same pointer in order to correctly free the memory.
+ ///
+ [System.Security.SecurityCritical]
+ internal partial class SafeCreateHandle : SafeHandle
+ {
+ internal SafeCreateHandle() : base(IntPtr.Zero, true) { }
+
+ internal SafeCreateHandle(IntPtr ptr) : base(IntPtr.Zero, true)
+ {
+ this.SetHandle(ptr);
+ }
+
+ [System.Security.SecurityCritical]
+ protected override bool ReleaseHandle()
+ {
+ Interop.CoreFoundation.CFRelease(handle);
+
+ return true;
+ }
+
+ public override bool IsInvalid
+ {
+ [System.Security.SecurityCritical]
+ get
+ {
+ return handle == IntPtr.Zero;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/SecureStringHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/SecureStringHelper.cs
new file mode 100644
index 00000000..070e0b20
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/SecureStringHelper.cs
@@ -0,0 +1,42 @@
+//
+// Code originally from http://credentialmanagement.codeplex.com/,
+// Licensed under the Apache License 2.0
+//
+
+using System;
+using System.Runtime.InteropServices;
+using System.Security;
+
+namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
+{
+ internal static class SecureStringHelper
+ {
+ // Methods
+ internal static SecureString CreateSecureString(string plainString)
+ {
+ SecureString str = new SecureString();
+ if (!string.IsNullOrEmpty(plainString))
+ {
+ foreach (char c in plainString)
+ {
+ str.AppendChar(c);
+ }
+ }
+ str.MakeReadOnly();
+ return str;
+ }
+
+ internal static string CreateString(SecureString value)
+ {
+ IntPtr ptr = SecureStringMarshal.SecureStringToGlobalAllocUnicode(value);
+ try
+ {
+ return Marshal.PtrToStringUni(ptr);
+ }
+ finally
+ {
+ Marshal.ZeroFreeGlobalAllocUnicode(ptr);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialResources.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialResources.cs
new file mode 100644
index 00000000..d3834c42
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialResources.cs
@@ -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.";
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialSet.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialSet.cs
new file mode 100644
index 00000000..f94a0f57
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialSet.cs
@@ -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, 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 credentialHandles =
+ ptrCredList.Select(ptrCred => new NativeMethods.CriticalCredentialHandle(ptrCred)).ToList();
+
+ IEnumerable 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);
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialType.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialType.cs
new file mode 100644
index 00000000..edc16d0d
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialType.cs
@@ -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
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/GlobalSuppressions.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/GlobalSuppressions.cs
new file mode 100644
index 00000000..3ee40dc8
Binary files /dev/null and b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/GlobalSuppressions.cs differ
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/NativeMethods.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/NativeMethods.cs
new file mode 100644
index 00000000..1e43205c
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/NativeMethods.cs
@@ -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(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;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/PersistanceType.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/PersistanceType.cs
new file mode 100644
index 00000000..b08eff08
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/PersistanceType.cs
@@ -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
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/Win32Credential.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/Win32Credential.cs
new file mode 100644
index 00000000..21c1c8b9
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/Win32Credential.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/Win32CredentialStore.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/Win32CredentialStore.cs
new file mode 100644
index 00000000..8a219854
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/Win32CredentialStore.cs
@@ -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
+{
+ ///
+ /// Win32 implementation of the credential store
+ ///
+ 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();
+ }
+
+ }
+ }
+
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/IProtocolEndpoint.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/IProtocolEndpoint.cs
index b688d3d5..496e3d56 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/IProtocolEndpoint.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/IProtocolEndpoint.cs
@@ -1,6 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
+//
+// 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.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
@@ -13,17 +16,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
///
public interface IProtocolEndpoint : IMessageSender
{
- void SetRequestHandler(
- RequestType requestType,
+ void SetRequestHandler(
+ RequestType requestType,
Func, Task> requestHandler);
- void SetEventHandler(
- EventType eventType,
+ void SetEventHandler(
+ EventType eventType,
Func eventHandler);
- void SetEventHandler(
- EventType eventType,
- Func eventHandler,
+ void SetEventHandler(
+ EventType eventType,
+ Func eventHandler,
bool overrideExisting);
}
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageReader.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageReader.cs
index 17d4b5e0..e70f33f4 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageReader.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageReader.cs
@@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Microsoft.SqlTools.EditorServices.Utility;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
-using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Program.cs b/src/Microsoft.SqlTools.ServiceLayer/Program.cs
index c0f547c2..f0d2d6e8 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Program.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Program.cs
@@ -2,19 +2,17 @@
// 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.Threading.Tasks;
using Microsoft.SqlTools.EditorServices.Utility;
using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
using Microsoft.SqlTools.ServiceLayer.Connection;
-using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
-using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
+using Microsoft.SqlTools.ServiceLayer.Credentials;
namespace Microsoft.SqlTools.ServiceLayer
-{
+{
///
/// Main application class for SQL Tools API Service Host executable
///
@@ -50,6 +48,7 @@ namespace Microsoft.SqlTools.ServiceLayer
AutoCompleteService.Instance.InitializeService(serviceHost);
LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext);
ConnectionService.Instance.InitializeService(serviceHost);
+ CredentialService.Instance.InitializeService(serviceHost);
QueryExecutionService.Instance.InitializeService(serviceHost);
serviceHost.Initialize();
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/CredentialServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/CredentialServiceTests.cs
new file mode 100644
index 00000000..7adbdebe
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/CredentialServiceTests.cs
@@ -0,0 +1,287 @@
+//
+// 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.IO;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ServiceLayer.Credentials;
+using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
+using Microsoft.SqlTools.ServiceLayer.Credentials.Linux;
+using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
+using Microsoft.SqlTools.ServiceLayer.Test.Utility;
+using Moq;
+using Xunit;
+
+namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
+{
+ ///
+ /// Credential Service tests that should pass on all platforms, regardless of backing store.
+ /// These tests run E2E, storing values in the native credential store for whichever platform
+ /// tests are being run on
+ ///
+ public class CredentialServiceTests : IDisposable
+ {
+ private static readonly LinuxCredentialStore.StoreConfig config = new LinuxCredentialStore.StoreConfig()
+ {
+ CredentialFolder = ".testsecrets",
+ CredentialFile = "sqltestsecrets.json",
+ IsRelativeToUserHomeDir = true
+ };
+
+ const string credentialId = "Microsoft_SqlToolsTest_TestId";
+ const string password1 = "P@ssw0rd1";
+ const string password2 = "2Pass2Furious";
+
+ const string otherCredId = credentialId + "2345";
+ const string otherPassword = credentialId + "2345";
+
+ // Test-owned credential store used to clean up before/after tests to ensure code works as expected
+ // even if previous runs stopped midway through
+ private ICredentialStore credStore;
+ private CredentialService service;
+ ///
+ /// Constructor called once for every test
+ ///
+ public CredentialServiceTests()
+ {
+ credStore = CredentialService.GetStoreForOS(config);
+ service = new CredentialService(credStore, config);
+ DeleteDefaultCreds();
+ }
+
+ public void Dispose()
+ {
+ DeleteDefaultCreds();
+ }
+
+ private void DeleteDefaultCreds()
+ {
+ credStore.DeletePassword(credentialId);
+ credStore.DeletePassword(otherCredId);
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ string credsFolder = ((LinuxCredentialStore)credStore).CredentialFolderPath;
+ if (Directory.Exists(credsFolder))
+ {
+ Directory.Delete(credsFolder, true);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task SaveCredentialThrowsIfCredentialIdMissing()
+ {
+ object errorResponse = null;
+ var contextMock = RequestContextMocks.Create(null).AddErrorHandling(obj => errorResponse = obj);
+
+ await service.HandleSaveCredentialRequest(new Credential(null), contextMock.Object);
+ VerifyErrorSent(contextMock);
+ Assert.True(((string)errorResponse).Contains("ArgumentException"));
+ }
+
+ [Fact]
+ public async Task SaveCredentialThrowsIfPasswordMissing()
+ {
+ object errorResponse = null;
+ var contextMock = RequestContextMocks.Create(null).AddErrorHandling(obj => errorResponse = obj);
+
+ await service.HandleSaveCredentialRequest(new Credential(credentialId), contextMock.Object);
+ VerifyErrorSent(contextMock);
+ Assert.True(((string)errorResponse).Contains("ArgumentException"));
+ }
+
+ [Fact]
+ public async Task SaveCredentialWorksForSingleCredential()
+ {
+ await RunAndVerify(
+ test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
+ verify: (actual => Assert.True(actual)));
+ }
+
+ [Fact]
+ public async Task SaveCredentialSupportsSavingCredentialMultipleTimes()
+ {
+ await RunAndVerify(
+ test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
+ verify: (actual => Assert.True(actual)));
+
+ await RunAndVerify(
+ test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
+ verify: (actual => Assert.True(actual)));
+ }
+
+ [Fact]
+ public async Task ReadCredentialWorksForSingleCredential()
+ {
+ // Given we have saved the credential
+ await RunAndVerify(
+ test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
+ verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully")));
+
+
+ // Expect read of the credential to return the password
+ await RunAndVerify(
+ test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId, null), requestContext),
+ verify: (actual =>
+ {
+ Assert.Equal(password1, actual.Password);
+ }));
+ }
+
+ [Fact]
+ public async Task ReadCredentialWorksForMultipleCredentials()
+ {
+
+ // Given we have saved multiple credentials
+ await RunAndVerify(
+ test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
+ verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully")));
+ await RunAndVerify(
+ test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(otherCredId, otherPassword), requestContext),
+ verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully")));
+
+
+ // Expect read of the credentials to return the right password
+ await RunAndVerify(
+ test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId, null), requestContext),
+ verify: (actual =>
+ {
+ Assert.Equal(password1, actual.Password);
+ }));
+ await RunAndVerify(
+ test: (requestContext) => service.HandleReadCredentialRequest(new Credential(otherCredId, null), requestContext),
+ verify: (actual =>
+ {
+ Assert.Equal(otherPassword, actual.Password);
+ }));
+ }
+
+ [Fact]
+ public async Task ReadCredentialHandlesPasswordUpdate()
+ {
+ // Given we have saved twice with a different password
+ await RunAndVerify(
+ test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
+ verify: (actual => Assert.True(actual)));
+
+ await RunAndVerify(
+ test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password2), requestContext),
+ verify: (actual => Assert.True(actual)));
+
+ // When we read the value for this credential
+ // Then we expect only the last saved password to be found
+ await RunAndVerify(
+ test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId), requestContext),
+ verify: (actual =>
+ {
+ Assert.Equal(password2, actual.Password);
+ }));
+ }
+
+ [Fact]
+ public async Task ReadCredentialThrowsIfCredentialIsNull()
+ {
+ object errorResponse = null;
+ var contextMock = RequestContextMocks.Create(null).AddErrorHandling(obj => errorResponse = obj);
+
+ // Verify throws on null, and this is sent as an error
+ await service.HandleReadCredentialRequest(null, contextMock.Object);
+ VerifyErrorSent(contextMock);
+ Assert.True(((string)errorResponse).Contains("ArgumentNullException"));
+ }
+
+ [Fact]
+ public async Task ReadCredentialThrowsIfIdMissing()
+ {
+ object errorResponse = null;
+ var contextMock = RequestContextMocks.Create(null).AddErrorHandling(obj => errorResponse = obj);
+
+ // Verify throws with no ID
+ await service.HandleReadCredentialRequest(new Credential(), contextMock.Object);
+ VerifyErrorSent(contextMock);
+ Assert.True(((string)errorResponse).Contains("ArgumentException"));
+ }
+
+ [Fact]
+ public async Task ReadCredentialReturnsNullPasswordForMissingCredential()
+ {
+ // Given a credential whose password doesn't exist
+ string credWithNoPassword = "Microsoft_SqlTools_CredThatDoesNotExist";
+
+ // When reading the credential
+ // Then expect the credential to be returned but password left blank
+ await RunAndVerify(
+ test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credWithNoPassword, null), requestContext),
+ verify: (actual =>
+ {
+ Assert.NotNull(actual);
+ Assert.Equal(credWithNoPassword, actual.CredentialId);
+ Assert.Null(actual.Password);
+ }));
+ }
+
+ [Fact]
+ public async Task DeleteCredentialThrowsIfIdMissing()
+ {
+ object errorResponse = null;
+ var contextMock = RequestContextMocks.Create(null).AddErrorHandling(obj => errorResponse = obj);
+
+ // Verify throws with no ID
+ await service.HandleDeleteCredentialRequest(new Credential(), contextMock.Object);
+ VerifyErrorSent(contextMock);
+ Assert.True(((string)errorResponse).Contains("ArgumentException"));
+ }
+
+ [Fact]
+ public async Task DeleteCredentialReturnsTrueOnlyIfCredentialExisted()
+ {
+ // Save should be true
+ await RunAndVerify(
+ test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
+ verify: (actual => Assert.True(actual)));
+
+ // Then delete - should return true
+ await RunAndVerify(
+ test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(credentialId), requestContext),
+ verify: (actual => Assert.True(actual)));
+
+ // Then delete - should return false as no longer exists
+ await RunAndVerify(
+ test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(credentialId), requestContext),
+ verify: (actual => Assert.False(actual)));
+ }
+
+ private async Task RunAndVerify(Func, Task> test, Action verify)
+ {
+ T result = default(T);
+ var contextMock = RequestContextMocks.Create(r => result = r).AddErrorHandling(null);
+ await test(contextMock.Object);
+ VerifyResult(contextMock, verify, result);
+ }
+
+ private void VerifyErrorSent(Mock> contextMock)
+ {
+ contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Never);
+ contextMock.Verify(c => c.SendError(It.IsAny()), Times.Once);
+ }
+
+ private void VerifyResult(Mock> contextMock, U expected, U actual)
+ {
+ contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Once);
+ Assert.Equal(expected, actual);
+ contextMock.Verify(c => c.SendError(It.IsAny()), Times.Never);
+ }
+
+ private void VerifyResult(Mock> contextMock, Action verify, T actual)
+ {
+ contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Once);
+ contextMock.Verify(c => c.SendError(It.IsAny()), Times.Never);
+ verify(actual);
+ }
+
+ }
+}
+
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Linux/LinuxInteropTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Linux/LinuxInteropTests.cs
new file mode 100644
index 00000000..1dcff8e6
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Linux/LinuxInteropTests.cs
@@ -0,0 +1,37 @@
+//
+// 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.ServiceLayer.Credentials;
+using Microsoft.SqlTools.ServiceLayer.Credentials.Linux;
+using Microsoft.SqlTools.ServiceLayer.Test.Utility;
+using Xunit;
+
+namespace Microsoft.SqlTools.ServiceLayer.Test.Credentials
+{
+ public class LinuxInteropTests
+ {
+ [Fact]
+ public void GetEUidReturnsInt()
+ {
+ TestUtils.RunIfLinux(() =>
+ {
+ Assert.NotNull(Interop.Sys.GetEUid());
+ });
+ }
+
+ [Fact]
+ public void GetHomeDirectoryFromPwFindsHomeDir()
+ {
+
+ TestUtils.RunIfLinux(() =>
+ {
+ string userDir = LinuxCredentialStore.GetHomeDirectoryFromPw();
+ Assert.StartsWith("/", userDir);
+ });
+ }
+
+ }
+}
+
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Win32/CredentialSetTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Win32/CredentialSetTests.cs
new file mode 100644
index 00000000..19edb663
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Win32/CredentialSetTests.cs
@@ -0,0 +1,99 @@
+//
+// Code originally from http://credentialmanagement.codeplex.com/,
+// Licensed under the Apache License 2.0
+//
+
+using System;
+using Microsoft.SqlTools.ServiceLayer.Credentials.Win32;
+using Microsoft.SqlTools.ServiceLayer.Test.Utility;
+using Xunit;
+
+namespace Microsoft.SqlTools.ServiceLayer.Test.Credentials
+{
+ public class CredentialSetTests
+ {
+ [Fact]
+ public void CredentialSetCreate()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Assert.NotNull(new CredentialSet());
+ });
+ }
+
+ [Fact]
+ public void CredentialSetCreateWithTarget()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Assert.NotNull(new CredentialSet("target"));
+ });
+ }
+
+ [Fact]
+ public void CredentialSetShouldBeIDisposable()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Assert.True(new CredentialSet() is IDisposable, "CredentialSet needs to implement IDisposable Interface.");
+ });
+ }
+
+ [Fact]
+ public void CredentialSetLoad()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Win32Credential credential = new Win32Credential
+ {
+ Username = "username",
+ Password = "password",
+ Target = "target",
+ Type = CredentialType.Generic
+ };
+ credential.Save();
+
+ CredentialSet set = new CredentialSet();
+ set.Load();
+ Assert.NotNull(set);
+ Assert.NotEmpty(set);
+
+ credential.Delete();
+
+ set.Dispose();
+ });
+ }
+
+ [Fact]
+ public void CredentialSetLoadShouldReturnSelf()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ CredentialSet set = new CredentialSet();
+ Assert.IsType(set.Load());
+
+ set.Dispose();
+ });
+ }
+
+ [Fact]
+ public void CredentialSetLoadWithTargetFilter()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Win32Credential credential = new Win32Credential
+ {
+ Username = "filteruser",
+ Password = "filterpassword",
+ Target = "filtertarget"
+ };
+ credential.Save();
+
+ CredentialSet set = new CredentialSet("filtertarget");
+ Assert.Equal(1, set.Load().Count);
+ set.Dispose();
+ });
+ }
+
+ }
+}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Win32/Win32CredentialTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Win32/Win32CredentialTests.cs
new file mode 100644
index 00000000..ae3f18b2
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Win32/Win32CredentialTests.cs
@@ -0,0 +1,145 @@
+//
+// Code originally from http://credentialmanagement.codeplex.com/,
+// Licensed under the Apache License 2.0
+//
+
+using System;
+using Microsoft.SqlTools.ServiceLayer.Credentials.Win32;
+using Microsoft.SqlTools.ServiceLayer.Test.Utility;
+using Xunit;
+
+namespace Microsoft.SqlTools.ServiceLayer.Test.Credentials
+{
+ public class Win32CredentialTests
+ {
+ [Fact]
+ public void Credential_Create_ShouldNotThrowNull()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Assert.NotNull(new Win32Credential());
+ });
+ }
+
+ [Fact]
+ public void Credential_Create_With_Username_ShouldNotThrowNull()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Assert.NotNull(new Win32Credential("username"));
+ });
+ }
+
+ [Fact]
+ public void Credential_Create_With_Username_And_Password_ShouldNotThrowNull()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Assert.NotNull(new Win32Credential("username", "password"));
+ });
+ }
+
+ [Fact]
+ public void Credential_Create_With_Username_Password_Target_ShouldNotThrowNull()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Assert.NotNull(new Win32Credential("username", "password", "target"));
+ });
+ }
+
+ [Fact]
+ public void Credential_ShouldBe_IDisposable()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Assert.True(new Win32Credential() is IDisposable, "Credential should implement IDisposable Interface.");
+ });
+ }
+
+ [Fact]
+ public void Credential_Dispose_ShouldNotThrowException()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ new Win32Credential().Dispose();
+ });
+ }
+
+ [Fact]
+ public void Credential_ShouldThrowObjectDisposedException()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Win32Credential disposed = new Win32Credential { Password = "password" };
+ disposed.Dispose();
+ Assert.Throws(() => disposed.Username = "username");
+ });
+ }
+
+ [Fact]
+ public void Credential_Save()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Win32Credential saved = new Win32Credential("username", "password", "target", CredentialType.Generic);
+ saved.PersistanceType = PersistanceType.LocalComputer;
+ Assert.True(saved.Save());
+ });
+ }
+
+ [Fact]
+ public void Credential_Delete()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ new Win32Credential("username", "password", "target").Save();
+ Assert.True(new Win32Credential("username", "password", "target").Delete());
+ });
+ }
+
+ [Fact]
+ public void Credential_Delete_NullTerminator()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Win32Credential credential = new Win32Credential((string)null, (string)null, "\0", CredentialType.None);
+ credential.Description = (string)null;
+ Assert.False(credential.Delete());
+ });
+ }
+
+ [Fact]
+ public void Credential_Load()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ Win32Credential setup = new Win32Credential("username", "password", "target", CredentialType.Generic);
+ setup.Save();
+
+ Win32Credential credential = new Win32Credential { Target = "target", Type = CredentialType.Generic };
+ Assert.True(credential.Load());
+
+ Assert.NotEmpty(credential.Username);
+ Assert.NotNull(credential.Password);
+ Assert.Equal("username", credential.Username);
+ Assert.Equal("password", credential.Password);
+ Assert.Equal("target", credential.Target);
+ });
+ }
+
+ [Fact]
+ public void Credential_Exists_Target_ShouldNotBeNull()
+ {
+ TestUtils.RunIfWindows(() =>
+ {
+ new Win32Credential { Username = "username", Password = "password", Target = "target" }.Save();
+
+ Win32Credential existingCred = new Win32Credential { Target = "target" };
+ Assert.True(existingCred.Exists());
+
+ existingCred.Delete();
+ });
+ }
+ }
+}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Properties/AssemblyInfo.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Properties/AssemblyInfo.cs
index 5cf54b90..a1587015 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/Properties/AssemblyInfo.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Properties/AssemblyInfo.cs
@@ -4,7 +4,6 @@
//
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs
index 05df93b9..7e0e5a4d 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs
@@ -7,6 +7,7 @@ using System;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
+using Microsoft.SqlTools.ServiceLayer.Test.Utility;
using Moq;
using Xunit;
@@ -21,7 +22,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// ... I request a query (doesn't matter what kind) and execute it
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
- var executeRequest = Common.GetQueryExecuteResultContextMock(null, null, null);
+ var executeRequest =
+ RequestContextMocks.SetupRequestContextMock(null, QueryExecuteCompleteEvent.Type, null, null);
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Fake that it hasn't completed execution
@@ -47,7 +49,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// ... I request a query (doesn't matter what kind) and wait for execution
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
var executeParams = new QueryExecuteParams {QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri};
- var executeRequest = Common.GetQueryExecuteResultContextMock(null, null, null);
+ var executeRequest =
+ RequestContextMocks.SetupRequestContextMock(null, QueryExecuteCompleteEvent.Type, null, null);
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
// ... And then I request to cancel the query
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs
index 9d2c1749..2437dc30 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs
@@ -3,24 +3,19 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
-using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Threading;
-using System.Threading.Tasks;
+using Microsoft.SqlTools.ServiceLayer.Connection;
+using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.SmoMetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
-using Microsoft.SqlTools.ServiceLayer.Connection;
-using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
-using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
-using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
-using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
@@ -215,46 +210,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
}
#endregion
-
- #region Request Mocking
-
- public static Mock> GetQueryExecuteResultContextMock(
- Action resultCallback,
- Action, QueryExecuteCompleteParams> eventCallback,
- Action