Verify known host fingerprints and warn about new ones

This commit is contained in:
Hylke Bons 2012-04-13 00:44:31 +02:00
parent f2b516d7ed
commit 06b821a3a3
9 changed files with 106 additions and 46 deletions

View file

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

View file

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

View file

@ -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.");
}
} }
} }
} }

View file

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

View file

@ -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)
); );

View file

@ -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);

View file

@ -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

View file

@ -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
} }

View file

@ -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 ())