From 06b821a3a34b6ee64a0fbcc5bde3a1f4ec7a0224 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Fri, 13 Apr 2012 00:44:31 +0200 Subject: [PATCH] Verify known host fingerprints and warn about new ones --- SparkleLib/Git/SparkleFetcherGit.cs | 21 ++---- SparkleLib/SparkleConfig.cs | 1 + SparkleLib/SparkleFetcherBase.cs | 93 +++++++++++++++++++++++--- SparkleLib/SparkleHelpers.cs | 24 +++---- SparkleShare/Linux/SparkleSetup.cs | 2 +- SparkleShare/Mac/SparkleSetup.cs | 2 +- SparkleShare/SparkleControllerBase.cs | 3 +- SparkleShare/SparkleSetupController.cs | 4 +- SparkleShare/Windows/SparkleSetup.cs | 2 +- 9 files changed, 106 insertions(+), 46 deletions(-) diff --git a/SparkleLib/Git/SparkleFetcherGit.cs b/SparkleLib/Git/SparkleFetcherGit.cs index 1760b864..6acec0c9 100755 --- a/SparkleLib/Git/SparkleFetcherGit.cs +++ b/SparkleLib/Git/SparkleFetcherGit.cs @@ -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."); } diff --git a/SparkleLib/SparkleConfig.cs b/SparkleLib/SparkleConfig.cs index b45bae43..17327cf2 100755 --- a/SparkleLib/SparkleConfig.cs +++ b/SparkleLib/SparkleConfig.cs @@ -137,6 +137,7 @@ namespace SparkleLib { } finally { Load (FullPath); TmpPath = Path.Combine (FoldersPath, ".tmp"); + Directory.CreateDirectory (TmpPath); } } diff --git a/SparkleLib/SparkleFetcherBase.cs b/SparkleLib/SparkleFetcherBase.cs index e6608bc3..f217dfcd 100755 --- a/SparkleLib/SparkleFetcherBase.cs +++ b/SparkleLib/SparkleFetcherBase.cs @@ -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 Warnings = new List (); 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."); + } } } } diff --git a/SparkleLib/SparkleHelpers.cs b/SparkleLib/SparkleHelpers.cs index 6de09a2e..5fb9a5eb 100755 --- a/SparkleLib/SparkleHelpers.cs +++ b/SparkleLib/SparkleHelpers.cs @@ -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); } } } diff --git a/SparkleShare/Linux/SparkleSetup.cs b/SparkleShare/Linux/SparkleSetup.cs index c379555e..5b2f18a4 100755 --- a/SparkleShare/Linux/SparkleSetup.cs +++ b/SparkleShare/Linux/SparkleSetup.cs @@ -529,7 +529,7 @@ namespace SparkleShare { }; - if (warnings != null) { + if (warnings.Length > 0) { Image warning_image = new Image ( SparkleUIHelpers.GetIcon ("dialog-warning", 24) ); diff --git a/SparkleShare/Mac/SparkleSetup.cs b/SparkleShare/Mac/SparkleSetup.cs index 2a1ede06..79e36c31 100755 --- a/SparkleShare/Mac/SparkleSetup.cs +++ b/SparkleShare/Mac/SparkleSetup.cs @@ -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); diff --git a/SparkleShare/SparkleControllerBase.cs b/SparkleShare/SparkleControllerBase.cs index acaa5b4e..142d0f25 100644 --- a/SparkleShare/SparkleControllerBase.cs +++ b/SparkleShare/SparkleControllerBase.cs @@ -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 diff --git a/SparkleShare/SparkleSetupController.cs b/SparkleShare/SparkleSetupController.cs index a94c54c7..9dc44c0d 100755 --- a/SparkleShare/SparkleSetupController.cs +++ b/SparkleShare/SparkleSetupController.cs @@ -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 } diff --git a/SparkleShare/Windows/SparkleSetup.cs b/SparkleShare/Windows/SparkleSetup.cs index f741a742..f2b9eb09 100644 --- a/SparkleShare/Windows/SparkleSetup.cs +++ b/SparkleShare/Windows/SparkleSetup.cs @@ -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 ())