Skip to content

Commit

Permalink
Merge pull request #29 from mjcheetham/interop-refactor
Browse files Browse the repository at this point in the history
Move all interop code under `Interop` namespace
  • Loading branch information
mjcheetham committed Apr 24, 2019
2 parents 48553d0 + d7dc070 commit 1d70ec3
Show file tree
Hide file tree
Showing 22 changed files with 334 additions and 332 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license.
using System.Threading.Tasks;
using Microsoft.Git.CredentialManager.Commands;
using Microsoft.Git.CredentialManager.SecureStorage;
using Microsoft.Git.CredentialManager.Tests.Objects;
using Moq;
using Xunit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license.
using System.Threading.Tasks;
using Microsoft.Git.CredentialManager.Commands;
using Microsoft.Git.CredentialManager.SecureStorage;
using Microsoft.Git.CredentialManager.Tests.Objects;
using Moq;
using Xunit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// Licensed under the MIT license.
using System;
using Xunit;
using Microsoft.Git.CredentialManager.SecureStorage;
using Microsoft.Git.CredentialManager.Interop.MacOS;

namespace Microsoft.Git.CredentialManager.Tests.SecureStorage
namespace Microsoft.Git.CredentialManager.Tests.Interop.MacOS
{
public class MacOSKeychainTests
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// Licensed under the MIT license.
using System;
using Xunit;
using Microsoft.Git.CredentialManager.SecureStorage;
using Microsoft.Git.CredentialManager.Interop.Windows;

namespace Microsoft.Git.CredentialManager.Tests.SecureStorage
namespace Microsoft.Git.CredentialManager.Tests.Interop.Windows
{
public class WindowsCredentialManagerTests
{
Expand Down
1 change: 0 additions & 1 deletion src/shared/Microsoft.Git.CredentialManager/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.ComponentModel;
using System.Threading.Tasks;
using Microsoft.Git.CredentialManager.Commands;
using Microsoft.Git.CredentialManager.SecureStorage;

namespace Microsoft.Git.CredentialManager
{
Expand Down
3 changes: 2 additions & 1 deletion src/shared/Microsoft.Git.CredentialManager/CommandContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.Git.CredentialManager.SecureStorage;
using Microsoft.Git.CredentialManager.Interop.MacOS;
using Microsoft.Git.CredentialManager.Interop.Windows;

namespace Microsoft.Git.CredentialManager
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license.
using System;
using System.Threading.Tasks;
using Microsoft.Git.CredentialManager.SecureStorage;

namespace Microsoft.Git.CredentialManager.Commands
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Git.CredentialManager.SecureStorage;

namespace Microsoft.Git.CredentialManager.Commands
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,46 @@

namespace Microsoft.Git.CredentialManager
{
/// <summary>
/// Represents a simple credential; user name and password pair.
/// </summary>
public interface ICredential
{
/// <summary>
/// User name.
/// </summary>
string UserName { get; }

/// <summary>
/// Password.
/// </summary>
string Password { get; }
}

/// <summary>
/// Represents a credential (username/password pair) that Git can use to authenticate to a remote repository.
/// </summary>
public class GitCredential : SecureStorage.ICredential
public class GitCredential : ICredential
{
public GitCredential(string userName, string password)
{
UserName = userName;
Password = password;
}

/// <summary>
/// User name.
/// </summary>
public string UserName { get; }

/// <summary>
/// Password.
/// </summary>
public string Password { get; }
}

public static class CredentialExtensions
{
/// <summary>
/// Returns the base-64 encoded, {username}:{password} formatted string of this `<see cref="GitCredential"/>`.
/// Returns the base-64 encoded, {username}:{password} formatted string of this `<see cref="ICredential"/>`.
/// </summary>
public string ToBase64String()
public static string ToBase64String(this ICredential credential)
{
string basicAuthValue = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", UserName, Password);
string basicAuthValue = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", credential.UserName, credential.Password);
byte[] authBytes = Encoding.UTF8.GetBytes(basicAuthValue);
return Convert.ToBase64String(authBytes);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
namespace Microsoft.Git.CredentialManager.SecureStorage

namespace Microsoft.Git.CredentialManager
{
/// <summary>
/// Represents a secure storage location for <see cref="ICredential"/>s.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
using System;
using System.Runtime.InteropServices;

namespace Microsoft.Git.CredentialManager.SecureStorage
namespace Microsoft.Git.CredentialManager.Interop
{
internal static partial class NativeMethods
internal static class InteropUtils
{
public static byte[] ToByteArray(IntPtr ptr, long count)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using static Microsoft.Git.CredentialManager.SecureStorage.NativeMethods.MacOS;
using Microsoft.Git.CredentialManager.Interop.MacOS.Native;
using static Microsoft.Git.CredentialManager.Interop.MacOS.Native.SecurityFramework;

namespace Microsoft.Git.CredentialManager.SecureStorage
namespace Microsoft.Git.CredentialManager.Interop.MacOS
{
public class MacOSKeychain : ICredentialStore
{
Expand Down Expand Up @@ -50,10 +51,10 @@ public ICredential Get(string key)
string userName = Encoding.UTF8.GetString(userNameBytes);

// Decode the password from the raw data
byte[] passwordBytes = NativeMethods.ToByteArray(passwordData, passwordLength);
byte[] passwordBytes = InteropUtils.ToByteArray(passwordData, passwordLength);
string password = Encoding.UTF8.GetString(passwordBytes);

return new Credential(userName, password);
return new GitCredential(userName, password);

case ErrorSecItemNotFound:
return null;
Expand All @@ -72,7 +73,7 @@ public ICredential Get(string key)

if (itemRef != IntPtr.Zero)
{
CFRelease(itemRef);
CoreFoundation.CFRelease(itemRef);
}
}
}
Expand Down Expand Up @@ -124,7 +125,7 @@ public void AddOrUpdate(string key, ICredential credential)

if (itemRef != IntPtr.Zero)
{
CFRelease(itemRef);
CoreFoundation.CFRelease(itemRef);
}
}
}
Expand Down Expand Up @@ -165,7 +166,7 @@ public bool Remove(string key)

if (itemRef != IntPtr.Zero)
{
CFRelease(itemRef);
CoreFoundation.CFRelease(itemRef);
}
}
}
Expand Down Expand Up @@ -207,7 +208,7 @@ private static byte[] GetAccountNameAttributeData(IntPtr itemRef)

SecKeychainAttribute attribute = Marshal.PtrToStructure<SecKeychainAttribute>(attrList.Attributes);

return NativeMethods.ToByteArray(attribute.Data, attribute.Length);
return InteropUtils.ToByteArray(attribute.Data, attribute.Length);
}
finally
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Runtime.InteropServices;

namespace Microsoft.Git.CredentialManager.Interop.MacOS.Native
{
public static class CoreFoundation
{
private const string CoreFoundationFrameworkLib = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";

[DllImport(CoreFoundationFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void CFRelease(IntPtr cf);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Runtime.InteropServices;

namespace Microsoft.Git.CredentialManager.Interop.MacOS.Native
{
// https://developer.apple.com/documentation/security/keychain_services/keychain_items
public static class SecurityFramework
{
private const string SecurityFrameworkLib = "/System/Library/Frameworks/Security.framework/Security";

[DllImport(SecurityFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int SecKeychainAddGenericPassword(
IntPtr keychain,
uint serviceNameLength,
string serviceName,
uint accountNameLength,
string accountName,
uint passwordLength,
byte[] passwordData,
out IntPtr itemRef);

[DllImport(SecurityFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int SecKeychainFindGenericPassword(
IntPtr keychainOrArray,
uint serviceNameLength,
string serviceName,
uint accountNameLength,
string accountName,
out uint passwordLength,
out IntPtr passwordData,
out IntPtr itemRef);

[DllImport(SecurityFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int SecKeychainItemCopyAttributesAndData(
IntPtr itemRef,
ref SecKeychainAttributeInfo info,
IntPtr itemClass, // SecItemClass*
out IntPtr attrList, // SecKeychainAttributeList*
out uint dataLength,
IntPtr data);

[DllImport(SecurityFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int SecKeychainItemModifyAttributesAndData(
IntPtr itemRef,
IntPtr attrList, // SecKeychainAttributeList*
uint length,
byte[] data);

[DllImport(SecurityFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int SecKeychainItemDelete(
IntPtr itemRef);

[DllImport(SecurityFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int SecKeychainItemFreeContent(
IntPtr attrList, // SecKeychainAttributeList*
IntPtr data);

[DllImport(SecurityFrameworkLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int SecKeychainItemFreeAttributesAndData(
IntPtr attrList, // SecKeychainAttributeList*
IntPtr data);

// https://developer.apple.com/documentation/security/1542001-security_framework_result_codes
public const int OK = 0;
public const int ErrorSecNoSuchKeychain = -25294;
public const int ErrorSecInvalidKeychain = -25295;
public const int ErrorSecAuthFailed = -25293;
public const int ErrorSecDuplicateItem = -25299;
public const int ErrorSecItemNotFound = -25300;
public const int ErrorSecInteractionNotAllowed = -25308;
public const int ErrorSecInteractionRequired = -25315;
public const int ErrorSecNoSuchAttr = -25303;

public static void ThrowIfError(int error, string defaultErrorMessage = "Unknown error.")
{
switch (error)
{
case OK:
return;
case ErrorSecNoSuchKeychain:
throw new InvalidOperationException($"The keychain does not exist. ({ErrorSecNoSuchKeychain})");
case ErrorSecInvalidKeychain:
throw new InvalidOperationException($"The keychain is not valid. ({ErrorSecInvalidKeychain})");
case ErrorSecAuthFailed:
throw new InvalidOperationException($"Authorization/Authentication failed. ({ErrorSecAuthFailed})");
case ErrorSecDuplicateItem:
throw new InvalidOperationException($"The item already exists. ({ErrorSecDuplicateItem})");
case ErrorSecItemNotFound:
throw new InvalidOperationException($"The item cannot be found. ({ErrorSecItemNotFound})");
case ErrorSecInteractionNotAllowed:
throw new InvalidOperationException($"Interaction with the Security Server is not allowed. ({ErrorSecInteractionNotAllowed})");
case ErrorSecInteractionRequired:
throw new InvalidOperationException($"User interaction is required. ({ErrorSecInteractionRequired})");
case ErrorSecNoSuchAttr:
throw new InvalidOperationException($"The attribute does not exist. ({ErrorSecNoSuchAttr})");
default:
throw new Exception($"{defaultErrorMessage} ({error})");
}
}
}

[StructLayout(LayoutKind.Sequential)]
public struct SecKeychainAttributeInfo
{
public uint Count;
public IntPtr Tag; // uint* (SecKeychainAttrType*)
public IntPtr Format; // uint* (CssmDbAttributeFormat*)
}

[StructLayout(LayoutKind.Sequential)]
public struct SecKeychainAttributeList
{
public uint Count;
public IntPtr Attributes; // SecKeychainAttribute*
}

[StructLayout(LayoutKind.Sequential)]
public struct SecKeychainAttribute
{
public SecKeychainAttrType Tag;
public uint Length;
public IntPtr Data;
}

public enum CssmDbAttributeFormat : uint
{
String = 0,
SInt32 = 1,
UInt32 = 2,
BigNum = 3,
Real = 4,
TimeDate = 5,
Blob = 6,
MultiUInt32 = 7,
Complex = 8
};

public enum SecKeychainAttrType : uint
{
// https://developer.apple.com/documentation/security/secitemattr/accountitemattr
AccountItem = 1633903476,
}
}
Loading

0 comments on commit 1d70ec3

Please sign in to comment.