Skip to content

Commit 03e6875

Browse files
committed
Allow the caller to inspect the host's certificate
A caller might want to inspect the certificate or simply ignore who the server claims to be.
1 parent 00478e2 commit 03e6875

17 files changed

+317
-3
lines changed

LibGit2Sharp.Tests/CloneFixture.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,44 @@ public void CanCloneFromBBWithCredentials(string url, string user, string pass,
237237
}
238238
}
239239

240+
[Theory]
241+
[InlineData("https://github.com/libgit2/TestGitRepository.git", "github.com", typeof(CertificateX509))]
242+
[InlineData("git@github.com/libgit2/TestGitRepository.git", "github.com", typeof(CertificateSsh))]
243+
public void CanInspectCertificateOnClone(string url, string hostname, Type certType)
244+
{
245+
var scd = BuildSelfCleaningDirectory();
246+
247+
Assert.Throws<UserCancelledException>(() => {
248+
if (!GlobalSettings.Version.Features.HasFlag(BuiltInFeatures.Ssh))
249+
{
250+
throw new UserCancelledException("SSH not supported, aborting test");
251+
}
252+
253+
Repository.Clone(url, scd.DirectoryPath, new CloneOptions()
254+
{
255+
CertificateCheck = (cert, valid, host) =>
256+
{
257+
Assert.Equal(hostname, host);
258+
Assert.Equal(certType, cert.GetType());
259+
if (certType == typeof(CertificateX509))
260+
{
261+
Assert.True(valid);
262+
var x509 = ((CertificateX509)cert).Certificate;
263+
// we get a string with the different fields instead of a structure, so...
264+
Assert.True(x509.Subject.Contains("CN=github.com,"));
265+
}
266+
else
267+
{
268+
var hostkey = (CertificateSsh)cert;
269+
Assert.True(hostkey.HasMD5);
270+
Assert.Equal("1627aca576282d36631b564debdfa648", BitConverter.ToString(hostkey.HashMD5));
271+
}
272+
return false;
273+
},
274+
});
275+
});
276+
}
277+
240278
[Fact]
241279
public void CloningAnUrlWithoutPathThrows()
242280
{

LibGit2Sharp/Certificate.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace LibGit2Sharp
4+
{
5+
/// <summary>
6+
/// Top-level certificate type. The usable certificates inherit from this class.
7+
/// </summary>
8+
public abstract class Certificate
9+
{
10+
/// <summary>
11+
/// For mocking
12+
/// </summary>
13+
protected Certificate()
14+
{ }
15+
}
16+
}
17+

LibGit2Sharp/CertificateSsh.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.Diagnostics;
3+
using LibGit2Sharp.Core;
4+
5+
namespace LibGit2Sharp
6+
{
7+
/// <summary>
8+
/// This class represents the hostkey which is avaiable when connecting to a SSH host.
9+
/// </summary>
10+
public class CertificateSsh : Certificate
11+
{
12+
/// <summary>
13+
/// The MD5 hash of the host. Meaningful if <see cref="HasMD5"/> is true
14+
/// </summary>
15+
public readonly byte[] HashMD5;
16+
17+
/// <summary>
18+
/// The SHA1 hash of the host. Meaningful if <see cref="HasSHA1"/> is true
19+
/// </summary>
20+
public readonly byte[] HashSHA1;
21+
22+
/// <summary>
23+
/// True if we have the MD5 hostkey hash from the server
24+
/// </summary>
25+
public readonly bool HasMD5;
26+
27+
/// <summary>
28+
/// True if we have the SHA1 hostkey hash from the server
29+
/// </summary>
30+
public readonly bool HasSHA1;
31+
32+
internal CertificateSsh(GitCertificateSsh cert)
33+
{
34+
35+
HasMD5 = cert.type.HasFlag(GitCertificateSshType.MD5);
36+
HasSHA1 = cert.type.HasFlag(GitCertificateSshType.SHA1);
37+
38+
HashMD5 = new byte[16];
39+
cert.HashMD5.CopyTo(HashMD5, 0);
40+
41+
HashSHA1 = new byte[20];
42+
cert.HashSHA1.CopyTo(HashSHA1, 0);
43+
}
44+
45+
/// <summary>
46+
/// For mocking purposes
47+
/// </summary>
48+
protected CertificateSsh()
49+
{ }
50+
}
51+
}
52+

LibGit2Sharp/CertificateX509.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Runtime.InteropServices;
2+
using System.Security.Cryptography.X509Certificates;
3+
using LibGit2Sharp.Core;
4+
5+
namespace LibGit2Sharp
6+
{
7+
/// <summary>
8+
/// Conains a X509 certificate
9+
/// </summary>
10+
public class CertificateX509 : Certificate
11+
{
12+
/// <summary>
13+
/// The certificate.
14+
/// </summary>
15+
public virtual X509Certificate Certificate { get; private set; }
16+
17+
internal CertificateX509(GitCertificateX509 cert)
18+
{
19+
int len = checked((int) cert.len.ToUInt32());
20+
byte[] data = new byte[len];
21+
Marshal.Copy(cert.data, data, 0, len);
22+
Certificate = new X509Certificate(data);
23+
}
24+
25+
/// <summary>
26+
/// For mocking purposes
27+
/// </summary>
28+
protected CertificateX509()
29+
{ }
30+
}
31+
}
32+

LibGit2Sharp/Core/CertificateNone.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace LibGit2Sharp
4+
{
5+
public class CertificateNone : Certificate
6+
{
7+
public CertificateNone()
8+
{
9+
}
10+
}
11+
}
12+

LibGit2Sharp/Core/GitCertificate.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace LibGit2Sharp.Core
5+
{
6+
[StructLayout(LayoutKind.Sequential)]
7+
internal struct GitCertificate
8+
{
9+
public GitCertificateType type;
10+
}
11+
}
12+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace LibGit2Sharp.Core
5+
{
6+
[StructLayout(LayoutKind.Sequential)]
7+
internal struct GitCertificateSsh
8+
{
9+
public GitCertificateType cert_type;
10+
public GitCertificateSshType type;
11+
12+
/// <summary>
13+
/// The MD5 hash (if appropriate)
14+
/// </summary>
15+
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
16+
public byte[] HashMD5;
17+
18+
/// <summary>
19+
/// The MD5 hash (if appropriate)
20+
/// </summary>
21+
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
22+
public byte[] HashSHA1;
23+
}
24+
}
25+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace LibGit2Sharp.Core
4+
{
5+
[Flags]
6+
internal enum GitCertificateSshType
7+
{
8+
MD5 = (1 << 0),
9+
SHA1 = (1 << 1),
10+
}
11+
}
12+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
3+
namespace LibGit2Sharp.Core
4+
{
5+
/// <summary>
6+
/// Git certificate types to present to the user
7+
/// </summary>
8+
internal enum GitCertificateType
9+
{
10+
/// <summary>
11+
/// The certificate is a x509 certificate
12+
/// </summary>
13+
X509 = 0,
14+
/// <summary>
15+
/// The "certificate" is in fact a hostkey identification for ssh.
16+
/// </summary>
17+
Hostkey = 1,
18+
}
19+
}
20+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace LibGit2Sharp.Core
5+
{
6+
[StructLayout(LayoutKind.Sequential)]
7+
internal struct GitCertificateX509
8+
{
9+
/// <summary>
10+
/// Type of the certificate, in this case, GitCertificateType.X509
11+
/// </summary>
12+
public GitCertificateType cert_type;
13+
/// <summary>
14+
/// Pointer to the X509 certificate data
15+
/// </summary>
16+
public IntPtr data;
17+
/// <summary>
18+
/// The size of the certificate data
19+
/// </summary>
20+
public UIntPtr len;
21+
}
22+
}
23+

0 commit comments

Comments
 (0)