Skip to content

[RFC] Certs and Creds from a managed smart subtransport #1178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 79 additions & 1 deletion LibGit2Sharp.Tests/SmartSubtransportFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Net;
using System.Net.Security;
using LibGit2Sharp.Tests.TestHelpers;
using LibGit2Sharp.Core;
using Xunit;
using Xunit.Extensions;

Expand Down Expand Up @@ -76,6 +77,58 @@ public void CustomSmartSubtransportTest(string scheme, string url)
}
}

[Theory]
[InlineData("https", "https://bitbucket.org/libgit2/testgitrepository.git", "libgit3", "libgit3")]
public void CanUseCredentials(string scheme, string url, string user, string pass)
{
string remoteName = "testRemote";

var scd = BuildSelfCleaningDirectory();
var repoPath = Repository.Init(scd.RootedDirectoryPath);

SmartSubtransportRegistration<MockSmartSubtransport> registration = null;

try
{
// Disable server certificate validation for testing.
// Do *NOT* enable this in production.
ServicePointManager.ServerCertificateValidationCallback = certificateValidationCallback;

registration = GlobalSettings.RegisterSmartSubtransport<MockSmartSubtransport>(scheme);
Assert.NotNull(registration);

using (var repo = new Repository(scd.DirectoryPath))
{
Remote remote = repo.Network.Remotes.Add(remoteName, url);

// Set up structures for the expected results
// and verifying the RemoteUpdateTips callback.
TestRemoteInfo expectedResults = TestRemoteInfo.TestRemoteInstance;
ExpectedFetchState expectedFetchState = new ExpectedFetchState(remoteName);

// Add expected branch objects
foreach (KeyValuePair<string, ObjectId> kvp in expectedResults.BranchTips)
{
expectedFetchState.AddExpectedBranch(kvp.Key, ObjectId.Zero, kvp.Value);
}

// Perform the actual fetch
repo.Network.Fetch(remote, new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler, TagFetchMode = TagFetchMode.Auto,
CredentialsProvider = (_user, _valid, _hostname) => new UsernamePasswordCredentials() { Username = "libgit3", Password = "libgit3" },
});

// Verify the expected
expectedFetchState.CheckUpdatedReferences(repo);
}
}
finally
{
GlobalSettings.UnregisterSmartSubtransport(registration);

ServicePointManager.ServerCertificateValidationCallback -= certificateValidationCallback;
}
}

[Fact]
public void CannotReregisterScheme()
{
Expand Down Expand Up @@ -234,14 +287,39 @@ private HttpWebResponse GetResponseWithRedirects()
}
}

response = (HttpWebResponse)request.GetResponse();
try
{
response = (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
response = ex.Response as HttpWebResponse;
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
Credentials cred;
int ret = SmartTransport.AcquireCredentials(out cred, null, typeof(UsernamePasswordCredentials));
if (ret != 0)
{
throw new InvalidOperationException("dunno");
}

request = CreateWebRequest(EndpointUrl, IsPost, ContentType);
UsernamePasswordCredentials userpass = (UsernamePasswordCredentials)cred;
request.Credentials = new NetworkCredential(userpass.Username, userpass.Password);
continue;
}

// rethrow if it's not 401
throw ex;
}

if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect)
{
request = CreateWebRequest(response.Headers["Location"], IsPost, ContentType);
continue;
}


break;
}

Expand Down
28 changes: 28 additions & 0 deletions LibGit2Sharp/CertificateSsh.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using LibGit2Sharp.Core;
using System;
using System.Runtime.InteropServices;

namespace LibGit2Sharp
{
Expand Down Expand Up @@ -49,5 +51,31 @@ internal CertificateSsh(GitCertificateSsh cert)
HashSHA1 = new byte[20];
cert.HashSHA1.CopyTo(HashSHA1, 0);
}

internal IntPtr ToPointer()
{
GitCertificateSshType sshCertType = 0;
if (HasMD5)
{
sshCertType |= GitCertificateSshType.MD5;
}
if (HasSHA1)
{
sshCertType |= GitCertificateSshType.SHA1;
}

var gitCert = new GitCertificateSsh
{
cert_type = GitCertificateType.Hostkey,
type = sshCertType,
};
HashMD5.CopyTo(gitCert.HashMD5, 0);
HashSHA1.CopyTo(gitCert.HashSHA1, 0);

var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(gitCert));
Marshal.StructureToPtr(gitCert, ptr, false);

return ptr;
}
}
}
21 changes: 20 additions & 1 deletion LibGit2Sharp/CertificateX509.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using LibGit2Sharp.Core;

Expand Down Expand Up @@ -28,5 +29,23 @@ internal CertificateX509(GitCertificateX509 cert)
Marshal.Copy(cert.data, data, 0, len);
Certificate = new X509Certificate(data);
}

internal IntPtr ToPointers(out IntPtr dataPtr)
{
var certData = Certificate.Export(X509ContentType.Cert);
dataPtr = Marshal.AllocHGlobal(certData.Length);
Marshal.Copy(certData, 0, dataPtr, certData.Length);
var gitCert = new GitCertificateX509()
{
cert_type = GitCertificateType.X509,
data = dataPtr,
len = (UIntPtr)certData.LongLength,
};

var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(gitCert));
Marshal.StructureToPtr(gitCert, ptr, false);

return ptr;
}
}
}
13 changes: 13 additions & 0 deletions LibGit2Sharp/Core/GitCredential.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Runtime.InteropServices;

namespace LibGit2Sharp.Core
{
[StructLayout(LayoutKind.Sequential)]
internal class GitCredential
{
public GitCredentialType credtype;
public IntPtr free;
}
}

14 changes: 14 additions & 0 deletions LibGit2Sharp/Core/GitCredentialUserpass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Runtime.InteropServices;

namespace LibGit2Sharp.Core
{
[StructLayout(LayoutKind.Sequential)]
internal class GitCredentialUserpass
{
public GitCredential parent;
public IntPtr username;
public IntPtr password;
}
}

17 changes: 17 additions & 0 deletions LibGit2Sharp/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ internal static extern int git_cred_userpass_plaintext_new(
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string password);

[DllImport(libgit2)]
internal static extern void git_cred_free(IntPtr cred);

[DllImport(libgit2)]
internal static extern int git_describe_commit(
out DescribeResultSafeHandle describe,
Expand Down Expand Up @@ -1732,6 +1735,20 @@ internal static extern int git_transport_smart(
IntPtr remote,
IntPtr definition);

[DllImport(libgit2)]
internal static extern int git_transport_smart_certificate_check(
IntPtr transport,
IntPtr cert,
int valid,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string hostname);

[DllImport(libgit2)]
internal static extern int git_transport_smart_credentials(
out IntPtr cred_out,
IntPtr transport,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string user,
int methods);

[DllImport(libgit2)]
internal static extern int git_transport_unregister(
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix);
Expand Down
18 changes: 18 additions & 0 deletions LibGit2Sharp/Core/Proxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,15 @@ public static ConfigurationSafeHandle git_config_snapshot(ConfigurationSafeHandl

#endregion

#region git_cred_

public static void git_cred_free(IntPtr cred)
{
NativeMethods.git_cred_free(cred);
}

#endregion

#region git_describe_

public static string git_describe_commit(
Expand Down Expand Up @@ -3181,6 +3190,15 @@ public static void git_transport_unregister(String prefix)

#endregion

#region git_transport_smart_

public static int git_transport_smart_credentials(out IntPtr cred, IntPtr transport, string user, int methods)
{
return NativeMethods.git_transport_smart_credentials(out cred, transport, user, methods);
}

#endregion

#region git_tree_

public static Mode git_tree_entry_attributes(SafeHandle entry)
Expand Down
2 changes: 2 additions & 0 deletions LibGit2Sharp/LibGit2Sharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@
<Compile Include="Core\GitCertificateSshType.cs" />
<Compile Include="CertificateSsh.cs" />
<Compile Include="IDiffResult.cs" />
<Compile Include="Core\GitCredential.cs" />
<Compile Include="Core\GitCredentialUserpass.cs" />
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="CustomDictionary.xml" />
Expand Down
72 changes: 72 additions & 0 deletions LibGit2Sharp/SmartSubtransport.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using LibGit2Sharp.Core;
using LibGit2Sharp.Core.Handles;

namespace LibGit2Sharp
{
Expand Down Expand Up @@ -47,6 +49,76 @@ public abstract class RpcSmartSubtransport : SmartSubtransport
/// </summary>
public abstract class SmartSubtransport
{
internal IntPtr Transport { get; set; }

/// <summary>
/// Call the certificate check callback
/// </summary>
/// <param name="cert">The certificate to send</param>
/// <param name="valid">Whether we consider the certificate to be valid</param>
/// <param name="hostname">The hostname we connected to</param>
public int CertificateCheck(Certificate cert, bool valid, string hostname)
{
CertificateSsh sshCert = cert as CertificateSsh;
CertificateX509 x509Cert = cert as CertificateX509;

if (sshCert == null && x509Cert == null)
{
throw new InvalidOperationException("Unsupported certificate type");
}

int ret;
if (sshCert != null)
{
var certPtr = sshCert.ToPointer();
ret = NativeMethods.git_transport_smart_certificate_check(Transport, certPtr, valid ? 1 : 0, hostname);
Marshal.FreeHGlobal(certPtr);
} else {
IntPtr certPtr, dataPtr;
certPtr = x509Cert.ToPointers(out dataPtr);
ret = NativeMethods.git_transport_smart_certificate_check(Transport, certPtr, valid ? 1 : 0, hostname);
Marshal.FreeHGlobal(dataPtr);
Marshal.FreeHGlobal(certPtr);
}

return ret;
}

public int AcquireCredentials(out Credentials cred, string user, params Type[] methods)
{
// Convert the user-provided types to libgit2's flags
int allowed = 0;
foreach (var method in methods)
{
if (method == typeof(UsernamePasswordCredentials))
{
allowed |= (int)GitCredentialType.UserPassPlaintext;
}
else
{
throw new InvalidOperationException("Unknown type passes as allowed credential");
}
}

IntPtr credHandle = IntPtr.Zero;
int res = Proxy.git_transport_smart_credentials(out credHandle, Transport, user, allowed);
if (res != 0)
{
cred = null;
return res;
}

var baseCred = credHandle.MarshalAs<GitCredential>();
switch (baseCred.credtype)
{
case GitCredentialType.UserPassPlaintext:
cred = UsernamePasswordCredentials.FromNative(credHandle.MarshalAs<GitCredentialUserpass>());
return 0;
default:
throw new InvalidOperationException("User returned an unkown credential type");
}
}

/// <summary>
/// Invoked by libgit2 to create a connection using this subtransport.
/// </summary>
Expand Down
5 changes: 4 additions & 1 deletion LibGit2Sharp/SmartSubtransportRegistration.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Runtime.InteropServices;
using LibGit2Sharp.Core;
using LibGit2Sharp.Core.Handles;

namespace LibGit2Sharp
{
Expand Down Expand Up @@ -75,7 +76,9 @@ private static int Subtransport(

try
{
subtransport = new T().GitSmartSubtransportPointer;
var obj = new T();
obj.Transport = transport;
subtransport = obj.GitSmartSubtransportPointer;

return 0;
}
Expand Down
Loading