-
Notifications
You must be signed in to change notification settings - Fork 899
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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() | ||
{ | ||
|
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 | ||
{ | ||
} | ||
} |
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And only expose a There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
} | ||
} | ||
} |
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); | ||
} | ||
} | ||
} |
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; | ||
} | ||
} |
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; | ||
} | ||
} |
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), | ||
} | ||
} |
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, | ||
} | ||
} |
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; | ||
} | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Therzok Thoughts?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How so?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 abyte[]
to represent the resulting hash.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍