Skip to content

Commit

Permalink
Merge pull request #1134 from libgit2/cmn/certs
Browse files Browse the repository at this point in the history
Certificate validation callback
  • Loading branch information
nulltoken committed Jul 16, 2015
2 parents 6564390 0558216 commit c7f10bb
Show file tree
Hide file tree
Showing 16 changed files with 323 additions and 1 deletion.
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;

/// <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;

/// <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);
}
}
}
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

0 comments on commit c7f10bb

Please sign in to comment.