Skip to content

Certificate validation callback #1134

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 1 commit into from
Jul 16, 2015
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
60 changes: 60 additions & 0 deletions LibGit2Sharp.Tests/CloneFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,66 @@ public void CanCloneFromBBWithCredentials(string url, string user, string pass,
}
}

[SkippableTheory]
[InlineData("https://github.com/libgit2/TestGitRepository.git", "github.com", typeof(CertificateX509))]
[InlineData("[email protected]:libgit2/TestGitRepository.git", "github.com", typeof(CertificateSsh))]
public void CanInspectCertificateOnClone(string url, string hostname, Type certType)
{
var scd = BuildSelfCleaningDirectory();

InconclusiveIf(
() =>
certType == typeof (CertificateSsh) && !GlobalSettings.Version.Features.HasFlag(BuiltInFeatures.Ssh),
"SSH not supported");

bool wasCalled = false;
bool checksHappy = false;

var options = new CloneOptions {
CertificateCheck = (cert, valid, host) => {
wasCalled = true;

Assert.Equal(hostname, host);
Assert.Equal(certType, cert.GetType());

if (certType == typeof(CertificateX509)) {
Assert.True(valid);
var x509 = ((CertificateX509)cert).Certificate;
// we get a string with the different fields instead of a structure, so...
Assert.True(x509.Subject.Contains("CN=github.com,"));
checksHappy = true;
return false;
}

if (certType == typeof(CertificateSsh)) {
var hostkey = (CertificateSsh)cert;
Assert.True(hostkey.HasMD5);
/*
* Once you've connected and thus your ssh has stored the hostkey,
* you can get the hostkey for a host with
*
* ssh-keygen -F github.com -l | tail -n 1 | cut -d ' ' -f 2 | tr -d ':'
*
* though GitHub's hostkey won't change anytime soon.
*/
Assert.Equal("1627aca576282d36631b564debdfa648",
BitConverter.ToString(hostkey.HashMD5).ToLower().Replace("-", ""));
checksHappy = true;
return false;
}

return false;
},
};

Assert.Throws<UserCancelledException>(() =>
Repository.Clone(url, scd.DirectoryPath, options)
);

Assert.True(wasCalled);
Assert.True(checksHappy);
}

[Fact]
public void CloningAnUrlWithoutPathThrows()
{
Expand Down
9 changes: 9 additions & 0 deletions LibGit2Sharp/Certificate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace LibGit2Sharp
{
/// <summary>
/// Top-level certificate type. The usable certificates inherit from this class.
/// </summary>
public abstract class Certificate
{
}
}
53 changes: 53 additions & 0 deletions LibGit2Sharp/CertificateSsh.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using LibGit2Sharp.Core;

namespace LibGit2Sharp
{
/// <summary>
/// This class represents the hostkey which is avaiable when connecting to a SSH host.
/// </summary>
public class CertificateSsh : Certificate
{
/// <summary>
/// For mocking purposes
/// </summary>
protected CertificateSsh()
{ }

/// <summary>
/// The MD5 hash of the host. Meaningful if <see cref="HasMD5"/> is true
/// </summary>
public readonly byte[] HashMD5;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How dumb would is it to convert this into a readable string?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which one, though? There's at least three ways to represent this string. OpenSSH does it lowercase with colons between each byte, I did it without the colons in libgit2 testing to abuse the oid functions, and BitConverter does uppercase with dashes for each byte.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Therzok Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not let the user decide?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How so?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave it as byte array, I mean. And provide convenience functions for transformation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was my rationale. It's a bunch of bytes, let's present it that way. If you want to ask the user, it's up to you to create the string representation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might also wish to note that System.Security.Cryptography.HashAlgorithm (from which C#'s hash algo classes inherit) uses a byte[] to represent the resulting hash.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not let the user decide?

👍


/// <summary>
/// The SHA1 hash of the host. Meaningful if <see cref="HasSHA1"/> is true
/// </summary>
public readonly byte[] HashSHA1;

/// <summary>
/// True if we have the MD5 hostkey hash from the server
/// </summary>
public readonly bool HasMD5;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many kinds of Hash can we get? Rather then exposing named properties, can't we this into an enum?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And only expose a Hash property?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to get both. My ssh and libssh2 only seem to give back MD5 for the servers I connect to, but it claims it could be both.


/// <summary>
/// True if we have the SHA1 hostkey hash from the server
/// </summary>
public readonly bool HasSHA1;

/// <summary>
/// True if we have the SHA1 hostkey hash from the server
/// </summary>public readonly bool HasSHA1;

internal CertificateSsh(GitCertificateSsh cert)
{

HasMD5 = cert.type.HasFlag(GitCertificateSshType.MD5);
HasSHA1 = cert.type.HasFlag(GitCertificateSshType.SHA1);

HashMD5 = new byte[16];
cert.HashMD5.CopyTo(HashMD5, 0);

HashSHA1 = new byte[20];
cert.HashSHA1.CopyTo(HashSHA1, 0);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we've brought these arrays into C# already, I wonder if it'd be fine to assing HashMD5 = cert.HashMD5 and the same for SHA1.

}
}
}
32 changes: 32 additions & 0 deletions LibGit2Sharp/CertificateX509.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using LibGit2Sharp.Core;

namespace LibGit2Sharp
{
/// <summary>
/// Conains a X509 certificate
/// </summary>
public class CertificateX509 : Certificate
{

/// <summary>
/// For mocking purposes
/// </summary>
protected CertificateX509()
{ }

/// <summary>
/// The certificate.
/// </summary>
public virtual X509Certificate Certificate { get; private set; }

internal CertificateX509(GitCertificateX509 cert)
{
int len = checked((int) cert.len.ToUInt32());
byte[] data = new byte[len];
Marshal.Copy(cert.data, data, 0, len);
Certificate = new X509Certificate(data);
}
}
}
10 changes: 10 additions & 0 deletions LibGit2Sharp/Core/GitCertificate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Runtime.InteropServices;

namespace LibGit2Sharp.Core
{
[StructLayout(LayoutKind.Sequential)]
internal struct GitCertificate
{
public GitCertificateType type;
}
}
23 changes: 23 additions & 0 deletions LibGit2Sharp/Core/GitCertificateSsh.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Runtime.InteropServices;

namespace LibGit2Sharp.Core
{
[StructLayout(LayoutKind.Sequential)]
internal struct GitCertificateSsh
{
public GitCertificateType cert_type;
public GitCertificateSshType type;

/// <summary>
/// The MD5 hash (if appropriate)
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public byte[] HashMD5;

/// <summary>
/// The MD5 hash (if appropriate)
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public byte[] HashSHA1;
}
}
11 changes: 11 additions & 0 deletions LibGit2Sharp/Core/GitCertificateSshType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace LibGit2Sharp.Core
{
[Flags]
internal enum GitCertificateSshType
{
MD5 = (1 << 0),
SHA1 = (1 << 1),
}
}
29 changes: 29 additions & 0 deletions LibGit2Sharp/Core/GitCertificateType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace LibGit2Sharp.Core
{
/// <summary>
/// Git certificate types to present to the user
/// </summary>
internal enum GitCertificateType
{
/// <summary>
/// No information about the certificate is available.
/// </summary>
None = 0,

/// <summary>
/// The certificate is a x509 certificate
/// </summary>
X509 = 1,

/// <summary>
/// The "certificate" is in fact a hostkey identification for ssh.
/// </summary>
Hostkey = 2,

/// <summary>
/// The "certificate" is in fact a collection of `name:content` strings
/// containing information about the certificate.
/// </summary>
StrArray = 3,
}
}
22 changes: 22 additions & 0 deletions LibGit2Sharp/Core/GitCertificateX509.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Runtime.InteropServices;

namespace LibGit2Sharp.Core
{
[StructLayout(LayoutKind.Sequential)]
internal struct GitCertificateX509
{
/// <summary>
/// Type of the certificate, in this case, GitCertificateType.X509
/// </summary>
public GitCertificateType cert_type;
/// <summary>
/// Pointer to the X509 certificate data
/// </summary>
public IntPtr data;
/// <summary>
/// The size of the certificate data
/// </summary>
public UIntPtr len;
}
}
2 changes: 1 addition & 1 deletion LibGit2Sharp/Core/GitRemoteCallbacks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal struct GitRemoteCallbacks

internal NativeMethods.git_cred_acquire_cb acquire_credentials;

internal IntPtr certificate_check;
internal NativeMethods.git_transport_certificate_check_cb certificate_check;

internal NativeMethods.git_transfer_progress_callback download_progress;

Expand Down
2 changes: 2 additions & 0 deletions LibGit2Sharp/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,8 @@ internal static extern int git_tag_delete(

internal delegate int git_transport_cb(out IntPtr transport, IntPtr remote, IntPtr payload);

internal delegate int git_transport_certificate_check_cb(IntPtr cert, int valid, IntPtr hostname, IntPtr payload);

[DllImport(libgit2)]
internal static extern int git_transport_register(
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix,
Expand Down
6 changes: 6 additions & 0 deletions LibGit2Sharp/FetchOptionsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ internal FetchOptionsBase()
/// </summary>
public CredentialsHandler CredentialsProvider { get; set; }

/// <summary>
/// This hanlder will be called to let the user make a decision on whether to allow
/// the connection to preoceed based on the certificate presented by the server.
/// </summary>
public CertificateCheckHandler CertificateCheck { get; set; }

/// <summary>
/// Starting to operate on a new repository.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions LibGit2Sharp/Handlers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;

namespace LibGit2Sharp.Handlers
{
Expand Down Expand Up @@ -32,6 +33,15 @@ namespace LibGit2Sharp.Handlers
/// <param name="types">Credential types which the server accepts</param>
public delegate Credentials CredentialsHandler(string url, string usernameFromUrl, SupportedCredentialTypes types);

/// <summary>
/// Delegate definition for the certificate validation
/// </summary>
/// <param name="certificate">The certificate which the server sent</param>
/// <param name="host">The hostname which we tried to connect to</param>
/// <param name="valid">Whether libgit2 thinks this certificate is valid</param>
/// <returns>True to continue, false to cancel</returns>
public delegate bool CertificateCheckHandler(Certificate certificate, bool valid, string host);

/// <summary>
/// Delegate definition for transfer progress callback.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions LibGit2Sharp/LibGit2Sharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,14 @@
<Compile Include="StashApplyOptions.cs" />
<Compile Include="StashApplyStatus.cs" />
<Compile Include="Core\GitStashApplyOpts.cs" />
<Compile Include="Core\GitCertificateType.cs" />
<Compile Include="Core\GitCertificate.cs" />
<Compile Include="Core\GitCertificateX509.cs" />
<Compile Include="Certificate.cs" />
<Compile Include="CertificateX509.cs" />
<Compile Include="Core\GitCertificateSsh.cs" />
<Compile Include="Core\GitCertificateSshType.cs" />
<Compile Include="CertificateSsh.cs" />
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="CustomDictionary.xml" />
Expand Down
6 changes: 6 additions & 0 deletions LibGit2Sharp/PushOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ public sealed class PushOptions
/// </summary>
public CredentialsHandler CredentialsProvider { get; set; }

/// <summary>
/// This hanlder will be called to let the user make a decision on whether to allow
/// the connection to preoceed based on the certificate presented by the server.
/// </summary>
public CertificateCheckHandler CertificateCheck { get; set; }

/// <summary>
/// If the transport being used to push to the remote requires the creation
/// of a pack file, this controls the number of worker threads used by
Expand Down
Loading