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

View file

@ -137,6 +137,7 @@ namespace SparkleLib {
} finally {
Load (FullPath);
TmpPath = Path.Combine (FoldersPath, ".tmp");
Directory.CreateDirectory (TmpPath);
}
}

View file

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

View file

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

View file

@ -529,7 +529,7 @@ namespace SparkleShare {
};
if (warnings != null) {
if (warnings.Length > 0) {
Image warning_image = new Image (
SparkleUIHelpers.GetIcon ("dialog-warning", 24)
);

View file

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

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)
{
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

View file

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

View file

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