Verify known host fingerprints and warn about new ones
This commit is contained in:
parent
f2b516d7ed
commit
06b821a3a3
|
@ -29,11 +29,11 @@ namespace SparkleLib.Git {
|
||||||
public class SparkleFetcher : SparkleFetcherBase {
|
public class SparkleFetcher : SparkleFetcherBase {
|
||||||
|
|
||||||
private SparkleGit git;
|
private SparkleGit git;
|
||||||
private bool fetch_prior_history = false;
|
|
||||||
|
|
||||||
|
|
||||||
public SparkleFetcher (string server, string remote_path, string target_folder, bool fetch_prior_history) :
|
public SparkleFetcher (string server, string required_fingerprint, string remote_path,
|
||||||
base (server, remote_path, target_folder, fetch_prior_history)
|
string target_folder, bool fetch_prior_history) : base (server, required_fingerprint, remote_path,
|
||||||
|
target_folder, fetch_prior_history)
|
||||||
{
|
{
|
||||||
Uri uri = RemoteUrl;
|
Uri uri = RemoteUrl;
|
||||||
|
|
||||||
|
@ -72,14 +72,12 @@ namespace SparkleLib.Git {
|
||||||
|
|
||||||
TargetFolder = target_folder;
|
TargetFolder = target_folder;
|
||||||
RemoteUrl = uri;
|
RemoteUrl = uri;
|
||||||
|
|
||||||
this.fetch_prior_history = fetch_prior_history;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override bool Fetch ()
|
public override bool Fetch ()
|
||||||
{
|
{
|
||||||
if (this.fetch_prior_history) {
|
if (FetchPriorHistory) {
|
||||||
this.git = new SparkleGit (SparkleConfig.DefaultConfig.TmpPath,
|
this.git = new SparkleGit (SparkleConfig.DefaultConfig.TmpPath,
|
||||||
"clone " +
|
"clone " +
|
||||||
"--progress " +
|
"--progress " +
|
||||||
|
@ -171,15 +169,10 @@ namespace SparkleLib.Git {
|
||||||
string output = git.StandardOutput.ReadToEnd ().Trim ();
|
string output = git.StandardOutput.ReadToEnd ().Trim ();
|
||||||
git.WaitForExit ();
|
git.WaitForExit ();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty (output)) {
|
if (string.IsNullOrEmpty (output))
|
||||||
return;
|
return;
|
||||||
|
else
|
||||||
} else {
|
Warnings.Add ("You seem to have a system wide ‘gitignore’ file, this may affect SparkleShare files.");
|
||||||
Warnings = new string [] {
|
|
||||||
string.Format ("You seem to have configured a system wide ‘gitignore’ file. " +
|
|
||||||
"This may affect SparkleShare files:\n\n{0}", output)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -137,6 +137,7 @@ namespace SparkleLib {
|
||||||
} finally {
|
} finally {
|
||||||
Load (FullPath);
|
Load (FullPath);
|
||||||
TmpPath = Path.Combine (FoldersPath, ".tmp");
|
TmpPath = Path.Combine (FoldersPath, ".tmp");
|
||||||
|
Directory.CreateDirectory (TmpPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,11 @@
|
||||||
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
|
@ -42,15 +45,19 @@ namespace SparkleLib {
|
||||||
public string TargetFolder;
|
public string TargetFolder;
|
||||||
public Uri RemoteUrl;
|
public Uri RemoteUrl;
|
||||||
public string [] ExcludeRules;
|
public string [] ExcludeRules;
|
||||||
public string [] Warnings;
|
public List<string> Warnings = new List<string> ();
|
||||||
public bool IsActive { get; private set; }
|
public bool IsActive { get; private set; }
|
||||||
|
public string RequiredFingerprint;
|
||||||
|
public bool FetchPriorHistory = false;
|
||||||
|
|
||||||
private Thread thread;
|
private Thread thread;
|
||||||
|
|
||||||
|
|
||||||
public SparkleFetcherBase (string server, string remote_path,
|
public SparkleFetcherBase (string server, string required_fingerprint, string remote_path,
|
||||||
string target_folder, bool fetch_prior_history)
|
string target_folder, bool fetch_prior_history)
|
||||||
{
|
{
|
||||||
|
RequiredFingerprint = required_fingerprint;
|
||||||
|
FetchPriorHistory = fetch_prior_history;
|
||||||
remote_path = remote_path.Trim ("/".ToCharArray ());
|
remote_path = remote_path.Trim ("/".ToCharArray ());
|
||||||
|
|
||||||
if (server.EndsWith ("/"))
|
if (server.EndsWith ("/"))
|
||||||
|
@ -143,17 +150,19 @@ namespace SparkleLib {
|
||||||
public void Start ()
|
public void Start ()
|
||||||
{
|
{
|
||||||
IsActive = true;
|
IsActive = true;
|
||||||
SparkleHelpers.DebugInfo ("Fetcher", "[" + TargetFolder + "] Fetching folder: " + RemoteUrl);
|
|
||||||
|
|
||||||
if (Started != null)
|
if (Started != null)
|
||||||
Started ();
|
Started ();
|
||||||
|
|
||||||
|
SparkleHelpers.DebugInfo ("Fetcher", "[" + TargetFolder + "] Fetching folder: " + RemoteUrl);
|
||||||
|
|
||||||
if (Directory.Exists (TargetFolder))
|
if (Directory.Exists (TargetFolder))
|
||||||
Directory.Delete (TargetFolder, true);
|
Directory.Delete (TargetFolder, true);
|
||||||
|
|
||||||
|
|
||||||
string host = RemoteUrl.Host;
|
string host = RemoteUrl.Host;
|
||||||
|
|
||||||
if (String.IsNullOrEmpty (host)) {
|
if (string.IsNullOrEmpty (host)) {
|
||||||
if (Failed != null)
|
if (Failed != null)
|
||||||
Failed ();
|
Failed ();
|
||||||
|
|
||||||
|
@ -163,8 +172,35 @@ namespace SparkleLib {
|
||||||
|
|
||||||
string host_key = GetHostKey ();
|
string host_key = GetHostKey ();
|
||||||
|
|
||||||
if (host_key != null)
|
if (host_key == null) {
|
||||||
AcceptHostKey (host_key);
|
if (Failed != null)
|
||||||
|
Failed ();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool warn = true;
|
||||||
|
if (RequiredFingerprint != null) {
|
||||||
|
string host_fingerprint = GetFingerprint (host_key);
|
||||||
|
|
||||||
|
if (!RequiredFingerprint.Equals (host_fingerprint)) {
|
||||||
|
SparkleHelpers.DebugInfo ("Auth", "Fingerprint doesn't match");
|
||||||
|
|
||||||
|
if (Failed != null)
|
||||||
|
Failed ();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
warn = false;
|
||||||
|
SparkleHelpers.DebugInfo ("Auth", "Fingerprint matches");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
SparkleHelpers.DebugInfo ("Auth", "Skipping fingerprint check");
|
||||||
|
}
|
||||||
|
|
||||||
|
AcceptHostKey (host_key, warn);
|
||||||
|
|
||||||
|
|
||||||
this.thread = new Thread (new ThreadStart (delegate {
|
this.thread = new Thread (new ThreadStart (delegate {
|
||||||
|
@ -175,7 +211,7 @@ namespace SparkleLib {
|
||||||
IsActive = false;
|
IsActive = false;
|
||||||
|
|
||||||
if (Finished != null)
|
if (Finished != null)
|
||||||
Finished (Warnings);
|
Finished (Warnings.ToArray ());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Thread.Sleep (500);
|
Thread.Sleep (500);
|
||||||
|
@ -228,7 +264,7 @@ namespace SparkleLib {
|
||||||
|
|
||||||
// Reading the standard output HAS to go before
|
// Reading the standard output HAS to go before
|
||||||
// WaitForExit, or it will hang forever on output > 4096 bytes
|
// WaitForExit, or it will hang forever on output > 4096 bytes
|
||||||
string host_key = process.StandardOutput.ReadToEnd ().TrimEnd ();
|
string host_key = process.StandardOutput.ReadToEnd ().Trim ();
|
||||||
process.WaitForExit ();
|
process.WaitForExit ();
|
||||||
|
|
||||||
if (process.ExitCode == 0)
|
if (process.ExitCode == 0)
|
||||||
|
@ -238,7 +274,39 @@ namespace SparkleLib {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void AcceptHostKey (string host_key)
|
// FIXME: Calculate fingerprint natively: decode base64 -> md5
|
||||||
|
private string GetFingerprint (string public_key)
|
||||||
|
{
|
||||||
|
string tmp_file_path = Path.Combine (SparkleConfig.DefaultConfig.TmpPath, "hostkey.tmp");
|
||||||
|
File.WriteAllText (tmp_file_path, public_key + Environment.NewLine);
|
||||||
|
|
||||||
|
Process process = new Process () {
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
|
||||||
|
process.StartInfo.WorkingDirectory = SparkleConfig.DefaultConfig.TmpPath;
|
||||||
|
process.StartInfo.UseShellExecute = false;
|
||||||
|
process.StartInfo.RedirectStandardOutput = true;
|
||||||
|
process.StartInfo.CreateNoWindow = true;
|
||||||
|
|
||||||
|
process.StartInfo.FileName = "ssh-keygen";
|
||||||
|
process.StartInfo.Arguments = "-lf " + tmp_file_path;
|
||||||
|
|
||||||
|
process.Start ();
|
||||||
|
|
||||||
|
// Reading the standard output HAS to go before
|
||||||
|
// WaitForExit, or it will hang forever on output > 4096 bytes
|
||||||
|
string fingerprint = process.StandardOutput.ReadToEnd ().Trim ();
|
||||||
|
process.WaitForExit ();
|
||||||
|
|
||||||
|
fingerprint = fingerprint.Substring (fingerprint.IndexOf (" ") + 1, 47);
|
||||||
|
File.Delete (tmp_file_path);
|
||||||
|
|
||||||
|
return fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void AcceptHostKey (string host_key, bool warn)
|
||||||
{
|
{
|
||||||
string ssh_config_path = Path.Combine (SparkleConfig.DefaultConfig.HomePath, ".ssh");
|
string ssh_config_path = Path.Combine (SparkleConfig.DefaultConfig.HomePath, ".ssh");
|
||||||
string known_hosts_file_path = Path.Combine (ssh_config_path, "known_hosts");
|
string known_hosts_file_path = Path.Combine (ssh_config_path, "known_hosts");
|
||||||
|
@ -265,6 +333,11 @@ namespace SparkleLib {
|
||||||
File.AppendAllText (known_hosts_file_path, "\n" + host_key + "\n");
|
File.AppendAllText (known_hosts_file_path, "\n" + host_key + "\n");
|
||||||
|
|
||||||
SparkleHelpers.DebugInfo ("Auth", "Accepted host key for " + host);
|
SparkleHelpers.DebugInfo ("Auth", "Accepted host key for " + host);
|
||||||
|
|
||||||
|
if (warn) {
|
||||||
|
Warnings.Add ("The accepted fingerprint is:\n" + GetFingerprint (host_key) +
|
||||||
|
"\n\nIf the above key doesn't match due to a possible attack, delete this project folder immediately.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,9 +79,8 @@ namespace SparkleLib {
|
||||||
// Check if a file is a symbolic link
|
// Check if a file is a symbolic link
|
||||||
public static bool IsSymlink (string file)
|
public static bool IsSymlink (string file)
|
||||||
{
|
{
|
||||||
FileAttributes attr = File.GetAttributes (file);
|
FileAttributes attributes = File.GetAttributes (file);
|
||||||
|
return ((attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint);
|
||||||
return ((attr & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,20 +92,13 @@ namespace SparkleLib {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Gets the relative path of two hierarchical absolute paths
|
public static bool IsWindows {
|
||||||
public static string DiffPaths (string target, string source)
|
get {
|
||||||
{
|
|
||||||
return target.Replace (source + Path.DirectorySeparatorChar, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsWindows
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
PlatformID platform = Environment.OSVersion.Platform;
|
PlatformID platform = Environment.OSVersion.Platform;
|
||||||
return (platform == PlatformID.Win32NT
|
|
||||||
|| platform == PlatformID.Win32S
|
return (platform == PlatformID.Win32NT ||
|
||||||
|| platform == PlatformID.Win32Windows);
|
platform == PlatformID.Win32S ||
|
||||||
|
platform == PlatformID.Win32Windows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -529,7 +529,7 @@ namespace SparkleShare {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (warnings != null) {
|
if (warnings.Length > 0) {
|
||||||
Image warning_image = new Image (
|
Image warning_image = new Image (
|
||||||
SparkleUIHelpers.GetIcon ("dialog-warning", 24)
|
SparkleUIHelpers.GetIcon ("dialog-warning", 24)
|
||||||
);
|
);
|
||||||
|
|
|
@ -610,7 +610,7 @@ namespace SparkleShare {
|
||||||
"’ added!";
|
"’ added!";
|
||||||
Description = "Access the files from your SparkleShare folder.";
|
Description = "Access the files from your SparkleShare folder.";
|
||||||
|
|
||||||
if (warnings != null) {
|
if (warnings.Length > 0) {
|
||||||
WarningImage = NSImage.ImageNamed ("NSCaution");
|
WarningImage = NSImage.ImageNamed ("NSCaution");
|
||||||
WarningImage.Size = new SizeF (24, 24);
|
WarningImage.Size = new SizeF (24, 24);
|
||||||
|
|
||||||
|
|
|
@ -954,7 +954,7 @@ namespace SparkleShare {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void FetchFolder (string address, string remote_path, string announcements_url,
|
public void FetchFolder (string address, string required_fingerprint, string remote_path, string announcements_url,
|
||||||
bool fetch_prior_history)
|
bool fetch_prior_history)
|
||||||
{
|
{
|
||||||
if (announcements_url != null)
|
if (announcements_url != null)
|
||||||
|
@ -986,6 +986,7 @@ namespace SparkleShare {
|
||||||
this.fetcher = (SparkleFetcherBase) Activator.CreateInstance (
|
this.fetcher = (SparkleFetcherBase) Activator.CreateInstance (
|
||||||
Type.GetType ("SparkleLib." + backend + ".SparkleFetcher, SparkleLib." + backend),
|
Type.GetType ("SparkleLib." + backend + ".SparkleFetcher, SparkleLib." + backend),
|
||||||
address,
|
address,
|
||||||
|
required_fingerprint,
|
||||||
remote_path,
|
remote_path,
|
||||||
tmp_folder,
|
tmp_folder,
|
||||||
fetch_prior_history
|
fetch_prior_history
|
||||||
|
|
|
@ -350,7 +350,7 @@ namespace SparkleShare {
|
||||||
Program.Controller.FolderFetchError += AddPageFetchErrorDelegate;
|
Program.Controller.FolderFetchError += AddPageFetchErrorDelegate;
|
||||||
Program.Controller.FolderFetching += SyncingPageFetchingDelegate;
|
Program.Controller.FolderFetching += SyncingPageFetchingDelegate;
|
||||||
|
|
||||||
Program.Controller.FetchFolder (address, remote_path,
|
Program.Controller.FetchFolder (address, SelectedPlugin.Fingerprint, remote_path,
|
||||||
SelectedPlugin.AnnouncementsUrl, this.fetch_prior_history);
|
SelectedPlugin.AnnouncementsUrl, this.fetch_prior_history);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,7 +431,7 @@ namespace SparkleShare {
|
||||||
Program.Controller.FolderFetchError += InvitePageFetchErrorDelegate;
|
Program.Controller.FolderFetchError += InvitePageFetchErrorDelegate;
|
||||||
Program.Controller.FolderFetching += SyncingPageFetchingDelegate;
|
Program.Controller.FolderFetching += SyncingPageFetchingDelegate;
|
||||||
|
|
||||||
Program.Controller.FetchFolder (PendingInvite.Address,
|
Program.Controller.FetchFolder (PendingInvite.Address, PendingInvite.Fingerprint,
|
||||||
PendingInvite.RemotePath, PendingInvite.AnnouncementsUrl, false); // TODO: checkbox on invite page
|
PendingInvite.RemotePath, PendingInvite.AnnouncementsUrl, false); // TODO: checkbox on invite page
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -554,7 +554,7 @@ namespace SparkleShare {
|
||||||
Content = "Open folder"
|
Content = "Open folder"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (warnings != null) {
|
if (warnings.Length > 0) {
|
||||||
Image warning_image = new Image () {
|
Image warning_image = new Image () {
|
||||||
Source = Imaging.CreateBitmapSourceFromHIcon (Drawing.SystemIcons.Warning.Handle,
|
Source = Imaging.CreateBitmapSourceFromHIcon (Drawing.SystemIcons.Warning.Handle,
|
||||||
Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions ())
|
Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions ())
|
||||||
|
|
Loading…
Reference in a new issue