From d6faf5bce6b4a211ac96e8a18238c16689a6082b Mon Sep 17 00:00:00 2001 From: robert Date: Thu, 5 Mar 2026 08:11:18 +1000 Subject: [PATCH] Support libssh2 based callbacks --- LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj | 6 +- LibGit2Sharp.Tests/NetworkFixture.cs | 10 +- LibGit2Sharp/Core/SshExtensions.cs | 141 +++++++++++++++++++ LibGit2Sharp/GlobalSettings.cs | 2 +- LibGit2Sharp/ListRemoteOptions.cs | 26 ++++ LibGit2Sharp/Network.cs | 105 ++++++++++++-- LibGit2Sharp/RemoteCallbacks.cs | 22 +++ LibGit2Sharp/Repository.cs | 12 ++ LibGit2Sharp/SupportedCredentialTypes.cs | 4 + 9 files changed, 309 insertions(+), 19 deletions(-) create mode 100644 LibGit2Sharp/Core/SshExtensions.cs create mode 100644 LibGit2Sharp/ListRemoteOptions.cs diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index fb81a76a3..4cab82113 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -1,13 +1,13 @@  - net472;net8.0;net9.0 + net10.0 - - + diff --git a/LibGit2Sharp.Tests/NetworkFixture.cs b/LibGit2Sharp.Tests/NetworkFixture.cs index f4ad922f6..00099a41e 100644 --- a/LibGit2Sharp.Tests/NetworkFixture.cs +++ b/LibGit2Sharp.Tests/NetworkFixture.cs @@ -9,8 +9,8 @@ namespace LibGit2Sharp.Tests public class NetworkFixture : BaseFixture { [Theory] - [InlineData("http://github.com/libgit2/TestGitRepository")] - [InlineData("https://github.com/libgit2/TestGitRepository")] + //[InlineData("http://github.com/libgit2/TestGitRepository")] + [InlineData("git@github.com:libgit2/TestGitRepository.git")] public void CanListRemoteReferences(string url) { string remoteName = "testRemote"; @@ -20,7 +20,11 @@ public void CanListRemoteReferences(string url) using (var repo = new Repository(repoPath)) { Remote remote = repo.Network.Remotes.Add(remoteName, url); - IList references = repo.Network.ListReferences(remote).ToList(); + IList references = repo.Network.ListReferences(remote, (s, fromUrl, types) => + { + + return null; + }).ToList(); foreach (var reference in references) diff --git a/LibGit2Sharp/Core/SshExtensions.cs b/LibGit2Sharp/Core/SshExtensions.cs new file mode 100644 index 000000000..3e249b2d9 --- /dev/null +++ b/LibGit2Sharp/Core/SshExtensions.cs @@ -0,0 +1,141 @@ + + using LibGit2Sharp.Core; +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Ssh +{ + + internal static class NativeMethods + { + private const string libgit2 = NativeDllName.Name; + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_cred_ssh_key_new( + out IntPtr cred, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string publickey, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string privatekey, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string passphrase); + + [DllImport(libgit2)] + internal static extern int git_cred_ssh_key_memory_new( + out IntPtr cred, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string publickey, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string privatekey, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string passphrase); + } + + /// + /// Class that holds SSH username with key credentials for remote repository access. + /// + public sealed class SshUserKeyCredentials : Credentials + { + /// + /// Callback to acquire a credential object. + /// + /// The newly created credential object. + /// 0 for success, < 0 to indicate an error, > 0 to indicate no credential was acquired. + protected internal override int GitCredentialHandler(out IntPtr cred) + { + if (Username == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null Username."); + } + + if (Passphrase == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null Passphrase."); + } + + if (PublicKey == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null PublicKey."); + } + + if (PrivateKey == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null PrivateKey."); + } + + return NativeMethods.git_cred_ssh_key_new(out cred, Username, PublicKey, PrivateKey, Passphrase); + } + + /// + /// Username for SSH authentication. + /// + public string Username { get; set; } + + /// + /// Public key file location for SSH authentication. + /// + public string PublicKey { get; set; } + + /// + /// Private key file location for SSH authentication. + /// + public string PrivateKey { get; set; } + + /// + /// Passphrase for SSH authentication. + /// + public string Passphrase { get; set; } + } + + /// + /// Class that holds SSH username with in-memory key credentials for remote repository access. + /// + public sealed class SshUserKeyMemoryCredentials : Credentials + { + /// + /// Callback to acquire a credential object. + /// + /// The newly created credential object. + /// 0 for success, < 0 to indicate an error, > 0 to indicate no credential was acquired. + protected internal override int GitCredentialHandler(out IntPtr cred) + { + if (Username == null) + { + throw new InvalidOperationException("SshUserKeyMemoryCredentials contains a null Username."); + } + + if (Passphrase == null) + { + throw new InvalidOperationException("SshUserKeyMemoryCredentials contains a null Passphrase."); + } + + if (PublicKey == null) + { + //throw new InvalidOperationException("SshUserKeyMemoryCredentials contains a null PublicKey."); + } + + if (PrivateKey == null) + { + throw new InvalidOperationException("SshUserKeyMemoryCredentials contains a null PrivateKey."); + } + + return NativeMethods.git_cred_ssh_key_memory_new(out cred, Username, PublicKey, PrivateKey, Passphrase); + } + + /// + /// Username for SSH authentication. + /// + public string Username { get; set; } + + /// + /// Public key for SSH authentication. + /// + public string PublicKey { get; set; } + + /// + /// Private key for SSH authentication. + /// + public string PrivateKey { get; set; } + + /// + /// Passphrase for SSH authentication. + /// + public string Passphrase { get; set; } + } +} diff --git a/LibGit2Sharp/GlobalSettings.cs b/LibGit2Sharp/GlobalSettings.cs index 9807155e7..917db9b43 100644 --- a/LibGit2Sharp/GlobalSettings.cs +++ b/LibGit2Sharp/GlobalSettings.cs @@ -20,7 +20,7 @@ public static class GlobalSettings private static string nativeLibraryPath; private static bool nativeLibraryPathLocked; - private static readonly string nativeLibraryDefaultPath = null; + private static readonly string nativeLibraryDefaultPath = "/Users/robert/Development/Sandbox/libgit2/build"; static GlobalSettings() { diff --git a/LibGit2Sharp/ListRemoteOptions.cs b/LibGit2Sharp/ListRemoteOptions.cs new file mode 100644 index 000000000..d7ec62835 --- /dev/null +++ b/LibGit2Sharp/ListRemoteOptions.cs @@ -0,0 +1,26 @@ +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp; + +/// +/// Options controlling ListRemote behavior. +/// +public sealed class ListRemoteOptions +{ + /// + /// Handler to generate for authentication. + /// + public CredentialsHandler CredentialsProvider { get; set; } + + /// + /// This handler will be called to let the user make a decision on whether to allow + /// the connection to proceed based on the certificate presented by the server. + /// + public CertificateCheckHandler CertificateCheck { get; set; } + + + /// + /// Options for connecting through a proxy. + /// + public ProxyOptions ProxyOptions { get; set; } = new(); +} diff --git a/LibGit2Sharp/Network.cs b/LibGit2Sharp/Network.cs index ba0a33144..93c971e41 100644 --- a/LibGit2Sharp/Network.cs +++ b/LibGit2Sharp/Network.cs @@ -52,9 +52,34 @@ public virtual IEnumerable ListReferences(Remote remote) { Ensure.ArgumentNotNull(remote, "remote"); - return ListReferencesInternal(remote.Url, null, new ProxyOptions()); + var options = new ListRemoteOptions() + { + ProxyOptions = new ProxyOptions() + }; + + return ListReferencesInternal(remote.Url, options); + } + + /// + /// List references in a repository. + /// + /// When the remote tips are ahead of the local ones, the retrieved + /// s may point to non existing + /// s in the local repository. In that + /// case, will return null. + /// + /// + /// The to list from. + /// The options for the remote request. + /// The references in the repository. + public virtual IEnumerable ListReferences(Remote remote, ListRemoteOptions options) + { + Ensure.ArgumentNotNull(remote, "remote"); + + return ListReferencesInternal(remote.Url, options); } + /// /// List references in a repository. /// @@ -71,7 +96,12 @@ public virtual IEnumerable ListReferences(Remote remote, ProxyOptions { Ensure.ArgumentNotNull(remote, "remote"); - return ListReferencesInternal(remote.Url, null, proxyOptions); + var options = new ListRemoteOptions() + { + ProxyOptions = proxyOptions + }; + + return ListReferencesInternal(remote.Url, options); } /// @@ -91,7 +121,13 @@ public virtual IEnumerable ListReferences(Remote remote, CredentialsH Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); - return ListReferencesInternal(remote.Url, credentialsProvider, new ProxyOptions()); + var options = new ListRemoteOptions() + { + ProxyOptions = new ProxyOptions(), + CredentialsProvider = credentialsProvider + }; + + return ListReferencesInternal(remote.Url, options); } /// @@ -112,7 +148,32 @@ public virtual IEnumerable ListReferences(Remote remote, CredentialsH Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); - return ListReferencesInternal(remote.Url, credentialsProvider, proxyOptions); + var options = new ListRemoteOptions() + { + ProxyOptions = proxyOptions, + CredentialsProvider = credentialsProvider + }; + + return ListReferencesInternal(remote.Url, options); + } + + /// + /// List references in a remote repository. + /// + /// When the remote tips are ahead of the local ones, the retrieved + /// s may point to non existing + /// s in the local repository. In that + /// case, will return null. + /// + /// + /// The url to list from. + /// The options for the remote request. + /// The references in the remote repository. + public virtual IEnumerable ListReferences(string url, ListRemoteOptions options) + { + Ensure.ArgumentNotNull(url, "url"); + + return ListReferencesInternal(url, options); } /// @@ -130,7 +191,12 @@ public virtual IEnumerable ListReferences(string url) { Ensure.ArgumentNotNull(url, "url"); - return ListReferencesInternal(url, null, new ProxyOptions()); + var options = new ListRemoteOptions() + { + ProxyOptions = new ProxyOptions() + }; + + return ListReferencesInternal(url, options); } /// @@ -148,8 +214,12 @@ public virtual IEnumerable ListReferences(string url) public virtual IEnumerable ListReferences(string url, ProxyOptions proxyOptions) { Ensure.ArgumentNotNull(url, "url"); + var options = new ListRemoteOptions() + { + ProxyOptions = proxyOptions + }; - return ListReferencesInternal(url, null, proxyOptions); + return ListReferencesInternal(url, options); } /// @@ -169,7 +239,13 @@ public virtual IEnumerable ListReferences(string url, CredentialsHand Ensure.ArgumentNotNull(url, "url"); Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); - return ListReferencesInternal(url, credentialsProvider, new ProxyOptions()); + var options = new ListRemoteOptions() + { + CredentialsProvider = credentialsProvider, + ProxyOptions = new ProxyOptions() + }; + + return ListReferencesInternal(url, options); } /// @@ -190,21 +266,26 @@ public virtual IEnumerable ListReferences(string url, CredentialsHand Ensure.ArgumentNotNull(url, "url"); Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); - return ListReferencesInternal(url, credentialsProvider, new ProxyOptions()); + var options = new ListRemoteOptions() + { + CredentialsProvider = credentialsProvider, + ProxyOptions = new ProxyOptions() + }; + return ListReferencesInternal(url, options); } - private IEnumerable ListReferencesInternal(string url, CredentialsHandler credentialsProvider, ProxyOptions proxyOptions) + private IEnumerable ListReferencesInternal(string url, ListRemoteOptions options) { - proxyOptions ??= new(); + var proxyOptions = options?.ProxyOptions ?? new(); using RemoteHandle remoteHandle = BuildRemoteHandle(repository.Handle, url); using var proxyOptionsWrapper = new GitProxyOptionsWrapper(proxyOptions.CreateGitProxyOptions()); GitRemoteCallbacks gitCallbacks = new GitRemoteCallbacks { version = 1 }; - if (credentialsProvider != null) + if (options != null) { - var callbacks = new RemoteCallbacks(credentialsProvider); + var callbacks = new RemoteCallbacks(options); gitCallbacks = callbacks.GenerateCallbacks(); } diff --git a/LibGit2Sharp/RemoteCallbacks.cs b/LibGit2Sharp/RemoteCallbacks.cs index 6061b10e1..558fad2a2 100644 --- a/LibGit2Sharp/RemoteCallbacks.cs +++ b/LibGit2Sharp/RemoteCallbacks.cs @@ -17,6 +17,17 @@ internal RemoteCallbacks(CredentialsHandler credentialsProvider) CredentialsProvider = credentialsProvider; } + internal RemoteCallbacks(ListRemoteOptions listRemoteOptions) + { + if (listRemoteOptions == null) + { + return; + } + + CertificateCheck = listRemoteOptions.CertificateCheck; + CredentialsProvider = listRemoteOptions.CredentialsProvider; + } + internal RemoteCallbacks(PushOptions pushOptions) { if (pushOptions == null) @@ -285,6 +296,17 @@ private int GitCredentialHandler( types |= SupportedCredentialTypes.Default; } + if (credTypes.HasFlag(GitCredentialType.SshKey)) + { + types |= SupportedCredentialTypes.SShKey; + } + + if (credTypes.HasFlag(GitCredentialType.SshMemory)) + { + types |= SupportedCredentialTypes.SSMemory; + } + + ptr = IntPtr.Zero; try { diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index 9ac5e2424..57846b584 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -670,6 +670,17 @@ public static IEnumerable ListRemoteReferences(string url, ProxyOptio return ListRemoteReferences(url, null, proxyOptions); } + /// + /// Lists the Remote Repository References. + /// + /// The url to list from. + /// Options for connecting through a proxy. + /// The references in the remote repository. + public static IEnumerable ListRemoteReferences(string url, ListRemoteOptions listRemoteOptions) + { + return ListRemoteReferences(url, listRemoteOptions); + } + /// /// Lists the Remote Repository References. /// @@ -686,6 +697,7 @@ public static IEnumerable ListRemoteReferences(string url, Credential return ListRemoteReferences(url, credentialsProvider, new ProxyOptions()); } + /// /// Lists the Remote Repository References. /// diff --git a/LibGit2Sharp/SupportedCredentialTypes.cs b/LibGit2Sharp/SupportedCredentialTypes.cs index bc38a259e..4698d923f 100644 --- a/LibGit2Sharp/SupportedCredentialTypes.cs +++ b/LibGit2Sharp/SupportedCredentialTypes.cs @@ -18,5 +18,9 @@ public enum SupportedCredentialTypes /// Ask Windows to provide its default credentials for the current user (e.g. NTLM) /// Default = (1 << 1), + + SShKey = (1 << 2), + + SSMemory = (1 << 3), } }