Skip to content

Commit 64cafa2

Browse files
committed
Merge pull request #1178 from carlosmn/cmn/subtransport-owner
[RFC] Certs and Creds from a managed smart subtransport
2 parents 2ead770 + c3a3be5 commit 64cafa2

11 files changed

+278
-3
lines changed

LibGit2Sharp.Tests/SmartSubtransportFixture.cs

+79-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Net;
55
using System.Net.Security;
66
using LibGit2Sharp.Tests.TestHelpers;
7+
using LibGit2Sharp.Core;
78
using Xunit;
89
using Xunit.Extensions;
910

@@ -76,6 +77,58 @@ public void CustomSmartSubtransportTest(string scheme, string url)
7677
}
7778
}
7879

80+
[Theory]
81+
[InlineData("https", "https://bitbucket.org/libgit2/testgitrepository.git", "libgit3", "libgit3")]
82+
public void CanUseCredentials(string scheme, string url, string user, string pass)
83+
{
84+
string remoteName = "testRemote";
85+
86+
var scd = BuildSelfCleaningDirectory();
87+
var repoPath = Repository.Init(scd.RootedDirectoryPath);
88+
89+
SmartSubtransportRegistration<MockSmartSubtransport> registration = null;
90+
91+
try
92+
{
93+
// Disable server certificate validation for testing.
94+
// Do *NOT* enable this in production.
95+
ServicePointManager.ServerCertificateValidationCallback = certificateValidationCallback;
96+
97+
registration = GlobalSettings.RegisterSmartSubtransport<MockSmartSubtransport>(scheme);
98+
Assert.NotNull(registration);
99+
100+
using (var repo = new Repository(scd.DirectoryPath))
101+
{
102+
Remote remote = repo.Network.Remotes.Add(remoteName, url);
103+
104+
// Set up structures for the expected results
105+
// and verifying the RemoteUpdateTips callback.
106+
TestRemoteInfo expectedResults = TestRemoteInfo.TestRemoteInstance;
107+
ExpectedFetchState expectedFetchState = new ExpectedFetchState(remoteName);
108+
109+
// Add expected branch objects
110+
foreach (KeyValuePair<string, ObjectId> kvp in expectedResults.BranchTips)
111+
{
112+
expectedFetchState.AddExpectedBranch(kvp.Key, ObjectId.Zero, kvp.Value);
113+
}
114+
115+
// Perform the actual fetch
116+
repo.Network.Fetch(remote, new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler, TagFetchMode = TagFetchMode.Auto,
117+
CredentialsProvider = (_user, _valid, _hostname) => new UsernamePasswordCredentials() { Username = "libgit3", Password = "libgit3" },
118+
});
119+
120+
// Verify the expected
121+
expectedFetchState.CheckUpdatedReferences(repo);
122+
}
123+
}
124+
finally
125+
{
126+
GlobalSettings.UnregisterSmartSubtransport(registration);
127+
128+
ServicePointManager.ServerCertificateValidationCallback -= certificateValidationCallback;
129+
}
130+
}
131+
79132
[Fact]
80133
public void CannotReregisterScheme()
81134
{
@@ -234,14 +287,39 @@ private HttpWebResponse GetResponseWithRedirects()
234287
}
235288
}
236289

237-
response = (HttpWebResponse)request.GetResponse();
290+
try
291+
{
292+
response = (HttpWebResponse)request.GetResponse();
293+
}
294+
catch (WebException ex)
295+
{
296+
response = ex.Response as HttpWebResponse;
297+
if (response.StatusCode == HttpStatusCode.Unauthorized)
298+
{
299+
Credentials cred;
300+
int ret = SmartTransport.AcquireCredentials(out cred, null, typeof(UsernamePasswordCredentials));
301+
if (ret != 0)
302+
{
303+
throw new InvalidOperationException("dunno");
304+
}
305+
306+
request = CreateWebRequest(EndpointUrl, IsPost, ContentType);
307+
UsernamePasswordCredentials userpass = (UsernamePasswordCredentials)cred;
308+
request.Credentials = new NetworkCredential(userpass.Username, userpass.Password);
309+
continue;
310+
}
311+
312+
// rethrow if it's not 401
313+
throw ex;
314+
}
238315

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

322+
245323
break;
246324
}
247325

LibGit2Sharp/CertificateSsh.cs

+28
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using LibGit2Sharp.Core;
2+
using System;
3+
using System.Runtime.InteropServices;
24

35
namespace LibGit2Sharp
46
{
@@ -49,5 +51,31 @@ internal CertificateSsh(GitCertificateSsh cert)
4951
HashSHA1 = new byte[20];
5052
cert.HashSHA1.CopyTo(HashSHA1, 0);
5153
}
54+
55+
internal IntPtr ToPointer()
56+
{
57+
GitCertificateSshType sshCertType = 0;
58+
if (HasMD5)
59+
{
60+
sshCertType |= GitCertificateSshType.MD5;
61+
}
62+
if (HasSHA1)
63+
{
64+
sshCertType |= GitCertificateSshType.SHA1;
65+
}
66+
67+
var gitCert = new GitCertificateSsh
68+
{
69+
cert_type = GitCertificateType.Hostkey,
70+
type = sshCertType,
71+
};
72+
HashMD5.CopyTo(gitCert.HashMD5, 0);
73+
HashSHA1.CopyTo(gitCert.HashSHA1, 0);
74+
75+
var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(gitCert));
76+
Marshal.StructureToPtr(gitCert, ptr, false);
77+
78+
return ptr;
79+
}
5280
}
5381
}

LibGit2Sharp/CertificateX509.cs

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Runtime.InteropServices;
1+
using System;
2+
using System.Runtime.InteropServices;
23
using System.Security.Cryptography.X509Certificates;
34
using LibGit2Sharp.Core;
45

@@ -28,5 +29,23 @@ internal CertificateX509(GitCertificateX509 cert)
2829
Marshal.Copy(cert.data, data, 0, len);
2930
Certificate = new X509Certificate(data);
3031
}
32+
33+
internal IntPtr ToPointers(out IntPtr dataPtr)
34+
{
35+
var certData = Certificate.Export(X509ContentType.Cert);
36+
dataPtr = Marshal.AllocHGlobal(certData.Length);
37+
Marshal.Copy(certData, 0, dataPtr, certData.Length);
38+
var gitCert = new GitCertificateX509()
39+
{
40+
cert_type = GitCertificateType.X509,
41+
data = dataPtr,
42+
len = (UIntPtr)certData.LongLength,
43+
};
44+
45+
var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(gitCert));
46+
Marshal.StructureToPtr(gitCert, ptr, false);
47+
48+
return ptr;
49+
}
3150
}
3251
}

LibGit2Sharp/Core/GitCredential.cs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace LibGit2Sharp.Core
5+
{
6+
[StructLayout(LayoutKind.Sequential)]
7+
internal class GitCredential
8+
{
9+
public GitCredentialType credtype;
10+
public IntPtr free;
11+
}
12+
}
13+
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace LibGit2Sharp.Core
5+
{
6+
[StructLayout(LayoutKind.Sequential)]
7+
internal class GitCredentialUserpass
8+
{
9+
public GitCredential parent;
10+
public IntPtr username;
11+
public IntPtr password;
12+
}
13+
}
14+

LibGit2Sharp/Core/NativeMethods.cs

+17
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,9 @@ internal static extern int git_cred_userpass_plaintext_new(
459459
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username,
460460
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string password);
461461

462+
[DllImport(libgit2)]
463+
internal static extern void git_cred_free(IntPtr cred);
464+
462465
[DllImport(libgit2)]
463466
internal static extern int git_describe_commit(
464467
out DescribeResultSafeHandle describe,
@@ -1732,6 +1735,20 @@ internal static extern int git_transport_smart(
17321735
IntPtr remote,
17331736
IntPtr definition);
17341737

1738+
[DllImport(libgit2)]
1739+
internal static extern int git_transport_smart_certificate_check(
1740+
IntPtr transport,
1741+
IntPtr cert,
1742+
int valid,
1743+
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string hostname);
1744+
1745+
[DllImport(libgit2)]
1746+
internal static extern int git_transport_smart_credentials(
1747+
out IntPtr cred_out,
1748+
IntPtr transport,
1749+
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string user,
1750+
int methods);
1751+
17351752
[DllImport(libgit2)]
17361753
internal static extern int git_transport_unregister(
17371754
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix);

LibGit2Sharp/Core/Proxy.cs

+18
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,15 @@ public static ConfigurationSafeHandle git_config_snapshot(ConfigurationSafeHandl
628628

629629
#endregion
630630

631+
#region git_cred_
632+
633+
public static void git_cred_free(IntPtr cred)
634+
{
635+
NativeMethods.git_cred_free(cred);
636+
}
637+
638+
#endregion
639+
631640
#region git_describe_
632641

633642
public static string git_describe_commit(
@@ -3181,6 +3190,15 @@ public static void git_transport_unregister(String prefix)
31813190

31823191
#endregion
31833192

3193+
#region git_transport_smart_
3194+
3195+
public static int git_transport_smart_credentials(out IntPtr cred, IntPtr transport, string user, int methods)
3196+
{
3197+
return NativeMethods.git_transport_smart_credentials(out cred, transport, user, methods);
3198+
}
3199+
3200+
#endregion
3201+
31843202
#region git_tree_
31853203

31863204
public static Mode git_tree_entry_attributes(SafeHandle entry)

LibGit2Sharp/LibGit2Sharp.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,8 @@
383383
<Compile Include="Core\GitCertificateSshType.cs" />
384384
<Compile Include="CertificateSsh.cs" />
385385
<Compile Include="IDiffResult.cs" />
386+
<Compile Include="Core\GitCredential.cs" />
387+
<Compile Include="Core\GitCredentialUserpass.cs" />
386388
</ItemGroup>
387389
<ItemGroup>
388390
<CodeAnalysisDictionary Include="CustomDictionary.xml" />

LibGit2Sharp/SmartSubtransport.cs

+72
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Runtime.InteropServices;
3+
using System.Text;
34
using LibGit2Sharp.Core;
5+
using LibGit2Sharp.Core.Handles;
46

57
namespace LibGit2Sharp
68
{
@@ -47,6 +49,76 @@ public abstract class RpcSmartSubtransport : SmartSubtransport
4749
/// </summary>
4850
public abstract class SmartSubtransport
4951
{
52+
internal IntPtr Transport { get; set; }
53+
54+
/// <summary>
55+
/// Call the certificate check callback
56+
/// </summary>
57+
/// <param name="cert">The certificate to send</param>
58+
/// <param name="valid">Whether we consider the certificate to be valid</param>
59+
/// <param name="hostname">The hostname we connected to</param>
60+
public int CertificateCheck(Certificate cert, bool valid, string hostname)
61+
{
62+
CertificateSsh sshCert = cert as CertificateSsh;
63+
CertificateX509 x509Cert = cert as CertificateX509;
64+
65+
if (sshCert == null && x509Cert == null)
66+
{
67+
throw new InvalidOperationException("Unsupported certificate type");
68+
}
69+
70+
int ret;
71+
if (sshCert != null)
72+
{
73+
var certPtr = sshCert.ToPointer();
74+
ret = NativeMethods.git_transport_smart_certificate_check(Transport, certPtr, valid ? 1 : 0, hostname);
75+
Marshal.FreeHGlobal(certPtr);
76+
} else {
77+
IntPtr certPtr, dataPtr;
78+
certPtr = x509Cert.ToPointers(out dataPtr);
79+
ret = NativeMethods.git_transport_smart_certificate_check(Transport, certPtr, valid ? 1 : 0, hostname);
80+
Marshal.FreeHGlobal(dataPtr);
81+
Marshal.FreeHGlobal(certPtr);
82+
}
83+
84+
return ret;
85+
}
86+
87+
public int AcquireCredentials(out Credentials cred, string user, params Type[] methods)
88+
{
89+
// Convert the user-provided types to libgit2's flags
90+
int allowed = 0;
91+
foreach (var method in methods)
92+
{
93+
if (method == typeof(UsernamePasswordCredentials))
94+
{
95+
allowed |= (int)GitCredentialType.UserPassPlaintext;
96+
}
97+
else
98+
{
99+
throw new InvalidOperationException("Unknown type passes as allowed credential");
100+
}
101+
}
102+
103+
IntPtr credHandle = IntPtr.Zero;
104+
int res = Proxy.git_transport_smart_credentials(out credHandle, Transport, user, allowed);
105+
if (res != 0)
106+
{
107+
cred = null;
108+
return res;
109+
}
110+
111+
var baseCred = credHandle.MarshalAs<GitCredential>();
112+
switch (baseCred.credtype)
113+
{
114+
case GitCredentialType.UserPassPlaintext:
115+
cred = UsernamePasswordCredentials.FromNative(credHandle.MarshalAs<GitCredentialUserpass>());
116+
return 0;
117+
default:
118+
throw new InvalidOperationException("User returned an unkown credential type");
119+
}
120+
}
121+
50122
/// <summary>
51123
/// Invoked by libgit2 to create a connection using this subtransport.
52124
/// </summary>

LibGit2Sharp/SmartSubtransportRegistration.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Runtime.InteropServices;
33
using LibGit2Sharp.Core;
4+
using LibGit2Sharp.Core.Handles;
45

56
namespace LibGit2Sharp
67
{
@@ -75,7 +76,9 @@ private static int Subtransport(
7576

7677
try
7778
{
78-
subtransport = new T().GitSmartSubtransportPointer;
79+
var obj = new T();
80+
obj.Transport = transport;
81+
subtransport = obj.GitSmartSubtransportPointer;
7982

8083
return 0;
8184
}

0 commit comments

Comments
 (0)