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 {
|
||||
|
||||
private SparkleGit git;
|
||||
private bool fetch_prior_history = false;
|
||||
|
||||
|
||||
public SparkleFetcher (string server, string remote_path, string target_folder, bool fetch_prior_history) :
|
||||
base (server, remote_path, target_folder, fetch_prior_history)
|
||||
public SparkleFetcher (string server, string required_fingerprint, string remote_path,
|
||||
string target_folder, bool fetch_prior_history) : base (server, required_fingerprint, remote_path,
|
||||
target_folder, fetch_prior_history)
|
||||
{
|
||||
Uri uri = RemoteUrl;
|
||||
|
||||
|
@ -72,14 +72,12 @@ namespace SparkleLib.Git {
|
|||
|
||||
TargetFolder = target_folder;
|
||||
RemoteUrl = uri;
|
||||
|
||||
this.fetch_prior_history = fetch_prior_history;
|
||||
}
|
||||
|
||||
|
||||
public override bool Fetch ()
|
||||
{
|
||||
if (this.fetch_prior_history) {
|
||||
if (FetchPriorHistory) {
|
||||
this.git = new SparkleGit (SparkleConfig.DefaultConfig.TmpPath,
|
||||
"clone " +
|
||||
"--progress " +
|
||||
|
@ -171,15 +169,10 @@ namespace SparkleLib.Git {
|
|||
string output = git.StandardOutput.ReadToEnd ().Trim ();
|
||||
git.WaitForExit ();
|
||||
|
||||
if (string.IsNullOrEmpty (output)) {
|
||||
if (string.IsNullOrEmpty (output))
|
||||
return;
|
||||
|
||||
} else {
|
||||
Warnings = new string [] {
|
||||
string.Format ("You seem to have configured a system wide ‘gitignore’ file. " +
|
||||
"This may affect SparkleShare files:\n\n{0}", output)
|
||||
};
|
||||
}
|
||||
else
|
||||
Warnings.Add ("You seem to have a system wide ‘gitignore’ file, this may affect SparkleShare files.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -137,6 +137,7 @@ namespace SparkleLib {
|
|||
} finally {
|
||||
Load (FullPath);
|
||||
TmpPath = Path.Combine (FoldersPath, ".tmp");
|
||||
Directory.CreateDirectory (TmpPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,8 +16,11 @@
|
|||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
|
@ -42,16 +45,20 @@ namespace SparkleLib {
|
|||
public string TargetFolder;
|
||||
public Uri RemoteUrl;
|
||||
public string [] ExcludeRules;
|
||||
public string [] Warnings;
|
||||
public List<string> Warnings = new List<string> ();
|
||||
public bool IsActive { get; private set; }
|
||||
public string RequiredFingerprint;
|
||||
public bool FetchPriorHistory = false;
|
||||
|
||||
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)
|
||||
{
|
||||
remote_path = remote_path.Trim ("/".ToCharArray ());
|
||||
RequiredFingerprint = required_fingerprint;
|
||||
FetchPriorHistory = fetch_prior_history;
|
||||
remote_path = remote_path.Trim ("/".ToCharArray ());
|
||||
|
||||
if (server.EndsWith ("/"))
|
||||
server = server.Substring (0, server.Length - 1);
|
||||
|
@ -143,17 +150,19 @@ namespace SparkleLib {
|
|||
public void Start ()
|
||||
{
|
||||
IsActive = true;
|
||||
SparkleHelpers.DebugInfo ("Fetcher", "[" + TargetFolder + "] Fetching folder: " + RemoteUrl);
|
||||
|
||||
if (Started != null)
|
||||
Started ();
|
||||
|
||||
SparkleHelpers.DebugInfo ("Fetcher", "[" + TargetFolder + "] Fetching folder: " + RemoteUrl);
|
||||
|
||||
if (Directory.Exists (TargetFolder))
|
||||
Directory.Delete (TargetFolder, true);
|
||||
|
||||
|
||||
string host = RemoteUrl.Host;
|
||||
|
||||
if (String.IsNullOrEmpty (host)) {
|
||||
if (string.IsNullOrEmpty (host)) {
|
||||
if (Failed != null)
|
||||
Failed ();
|
||||
|
||||
|
@ -163,8 +172,35 @@ namespace SparkleLib {
|
|||
|
||||
string host_key = GetHostKey ();
|
||||
|
||||
if (host_key != null)
|
||||
AcceptHostKey (host_key);
|
||||
if (host_key == null) {
|
||||
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 {
|
||||
|
@ -175,7 +211,7 @@ namespace SparkleLib {
|
|||
IsActive = false;
|
||||
|
||||
if (Finished != null)
|
||||
Finished (Warnings);
|
||||
Finished (Warnings.ToArray ());
|
||||
|
||||
} else {
|
||||
Thread.Sleep (500);
|
||||
|
@ -228,7 +264,7 @@ namespace SparkleLib {
|
|||
|
||||
// Reading the standard output HAS to go before
|
||||
// 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 ();
|
||||
|
||||
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 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");
|
||||
|
||||
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
|
||||
public static bool IsSymlink (string file)
|
||||
{
|
||||
FileAttributes attr = File.GetAttributes (file);
|
||||
|
||||
return ((attr & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint);
|
||||
FileAttributes attributes = File.GetAttributes (file);
|
||||
return ((attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint);
|
||||
}
|
||||
|
||||
|
||||
|
@ -93,20 +92,13 @@ namespace SparkleLib {
|
|||
}
|
||||
|
||||
|
||||
// Gets the relative path of two hierarchical absolute paths
|
||||
public static string DiffPaths (string target, string source)
|
||||
{
|
||||
return target.Replace (source + Path.DirectorySeparatorChar, "");
|
||||
}
|
||||
|
||||
public static bool IsWindows
|
||||
{
|
||||
get
|
||||
{
|
||||
public static bool IsWindows {
|
||||
get {
|
||||
PlatformID platform = Environment.OSVersion.Platform;
|
||||
return (platform == PlatformID.Win32NT
|
||||
|| platform == PlatformID.Win32S
|
||||
|| platform == PlatformID.Win32Windows);
|
||||
|
||||
return (platform == PlatformID.Win32NT ||
|
||||
platform == PlatformID.Win32S ||
|
||||
platform == PlatformID.Win32Windows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -529,7 +529,7 @@ namespace SparkleShare {
|
|||
};
|
||||
|
||||
|
||||
if (warnings != null) {
|
||||
if (warnings.Length > 0) {
|
||||
Image warning_image = new Image (
|
||||
SparkleUIHelpers.GetIcon ("dialog-warning", 24)
|
||||
);
|
||||
|
|
|
@ -610,7 +610,7 @@ namespace SparkleShare {
|
|||
"’ added!";
|
||||
Description = "Access the files from your SparkleShare folder.";
|
||||
|
||||
if (warnings != null) {
|
||||
if (warnings.Length > 0) {
|
||||
WarningImage = NSImage.ImageNamed ("NSCaution");
|
||||
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)
|
||||
{
|
||||
if (announcements_url != null)
|
||||
|
@ -986,6 +986,7 @@ namespace SparkleShare {
|
|||
this.fetcher = (SparkleFetcherBase) Activator.CreateInstance (
|
||||
Type.GetType ("SparkleLib." + backend + ".SparkleFetcher, SparkleLib." + backend),
|
||||
address,
|
||||
required_fingerprint,
|
||||
remote_path,
|
||||
tmp_folder,
|
||||
fetch_prior_history
|
||||
|
|
|
@ -350,7 +350,7 @@ namespace SparkleShare {
|
|||
Program.Controller.FolderFetchError += AddPageFetchErrorDelegate;
|
||||
Program.Controller.FolderFetching += SyncingPageFetchingDelegate;
|
||||
|
||||
Program.Controller.FetchFolder (address, remote_path,
|
||||
Program.Controller.FetchFolder (address, SelectedPlugin.Fingerprint, remote_path,
|
||||
SelectedPlugin.AnnouncementsUrl, this.fetch_prior_history);
|
||||
}
|
||||
|
||||
|
@ -431,7 +431,7 @@ namespace SparkleShare {
|
|||
Program.Controller.FolderFetchError += InvitePageFetchErrorDelegate;
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -554,7 +554,7 @@ namespace SparkleShare {
|
|||
Content = "Open folder"
|
||||
};
|
||||
|
||||
if (warnings != null) {
|
||||
if (warnings.Length > 0) {
|
||||
Image warning_image = new Image () {
|
||||
Source = Imaging.CreateBitmapSourceFromHIcon (Drawing.SystemIcons.Warning.Handle,
|
||||
Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions ())
|
||||
|
|
Loading…
Reference in a new issue