diff --git a/VisualRust.Setup/VisualRust.Setup.wixproj b/VisualRust.Setup/VisualRust.Setup.wixproj index eaa9fb33..bcf66874 100644 --- a/VisualRust.Setup/VisualRust.Setup.wixproj +++ b/VisualRust.Setup/VisualRust.Setup.wixproj @@ -38,6 +38,8 @@ obj\$(Platform)\$(Configuration)\ + + diff --git a/VisualRust.Setup/gpg2013.wxs b/VisualRust.Setup/gpg2013.wxs new file mode 100644 index 00000000..74e25581 --- /dev/null +++ b/VisualRust.Setup/gpg2013.wxs @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VisualRust.Setup/gpg2015.wxs b/VisualRust.Setup/gpg2015.wxs new file mode 100644 index 00000000..7fe85323 --- /dev/null +++ b/VisualRust.Setup/gpg2015.wxs @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VisualRust.Setup/vsx2013.wxs b/VisualRust.Setup/vsx2013.wxs index cf2929b5..7e60c97e 100644 --- a/VisualRust.Setup/vsx2013.wxs +++ b/VisualRust.Setup/vsx2013.wxs @@ -37,6 +37,8 @@ + + diff --git a/VisualRust.Setup/vsx2015.wxs b/VisualRust.Setup/vsx2015.wxs index 37298d99..fed91ea0 100644 --- a/VisualRust.Setup/vsx2015.wxs +++ b/VisualRust.Setup/vsx2015.wxs @@ -37,6 +37,8 @@ + + diff --git a/VisualRust/Downloader.cs b/VisualRust/Downloader.cs new file mode 100644 index 00000000..943a48b2 --- /dev/null +++ b/VisualRust/Downloader.cs @@ -0,0 +1,102 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Runtime.Serialization; + +namespace VisualRust +{ + public class Downloader + { + private static String[] Sha256Fingerprints = + { + "0C:0B:AC:4C:96:D9:F2:2C:8D:7A:00:9F:2F:48:3D:7B:46:FE:2C:60:0B:52:19:5B:B4:80:47:36:7C:03:E9:41", + // all the known static.rust-lang.org cert fingerprints should be specified here. they expire every + // 2 years. + }; + + private static bool ValidateCertificate(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + if (sslPolicyErrors != SslPolicyErrors.None) + { + return false; + } + + var cert2 = new X509Certificate2(certificate); + var time = System.DateTime.Now; + + if (time > cert2.NotAfter || time < cert2.NotBefore) + { + // expiry + return false; + } + + var der_encoded = certificate.Export(X509ContentType.Cert); + var hash = SHA256.Create().ComputeHash(der_encoded); + var received_fingerprint = BitConverter.ToString(hash).Replace('-', ':'); + + foreach (String fingerprint in Sha256Fingerprints) + { + if (fingerprint == received_fingerprint) { return true; } + } + + return false; + } + + private static WebResponse RawDownload(String relative_path) + { + HttpWebRequest wc = (HttpWebRequest)WebRequest.Create("https://static.rust-lang.org/dist/" + relative_path); + wc.ServerCertificateValidationCallback = ValidateCertificate; + return wc.GetResponse(); + } + + /// + /// Download and verify the signature of a file rust-lang.org, writing the contents into write_into. + /// + /// + /// Thrown when the signature of the downloaded file could not be verified with Rust's signing key. + /// Note that the stream will still contain the contents of the file even if verification failed. But, + /// if verification fails, the contents should not be trusted. + /// + /// Appended to https://static.rust-lang.org/dist/ to determine the file to download + /// The contents of the downloaded file (but not the signature) will be written into this stream. + public static void Download(String relative_path, Stream write_into) + { + var saved_pos = write_into.Position; + MemoryStream sig = new MemoryStream(); + RawDownload(relative_path).GetResponseStream().CopyTo(write_into); + RawDownload(relative_path + ".asc").GetResponseStream().CopyTo(sig); + write_into.Position = 0; + sig.Position = 0; + var res = GPG.Gpg.Instance.Verify(write_into, sig); + if (!res.Item1) + { + File.WriteAllText("C:/Users/Elaine/Desktop/gpg_log.txt", res.Item2); + throw new VerificationException(res.Item2); + } + } + } + + [Serializable] + public class VerificationException : Exception + { + public VerificationException() + { + } + + public VerificationException(string message) : base(message) + { + } + + public VerificationException(string message, Exception innerException) : base(message, innerException) + { + } + + protected VerificationException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/VisualRust/Forms/RustOptionsPage.cs b/VisualRust/Forms/RustOptionsPage.cs index 7125984d..f849041c 100644 --- a/VisualRust/Forms/RustOptionsPage.cs +++ b/VisualRust/Forms/RustOptionsPage.cs @@ -15,6 +15,12 @@ public class RustOptionsPage : DialogPage public bool UseCustomSources { get; set; } public string CustomSourcesPath { get; set; } + public bool UseCustomGpg { get; set; } + public string CustomGpgPath { get; set; } + + public bool UseCustomGpgHomedir { get; set; } + public string CustomGpgHomedir { get; set; } + private RustOptionsPageControl _page; protected override IWin32Window Window diff --git a/VisualRust/Racer/RacerSingleton.cs b/VisualRust/Racer/RacerSingleton.cs index 1ab991d9..9eaa2c8a 100644 --- a/VisualRust/Racer/RacerSingleton.cs +++ b/VisualRust/Racer/RacerSingleton.cs @@ -51,23 +51,18 @@ public static void Init() private RacerSingleton() { } - - private T GetVisualRustProperty(DTE env, string key) - { - return (T)env.get_Properties("Visual Rust", "General").Item(key).Value; - } - + private void ReinitializeRacerPaths() { DTE env = (DTE)VisualRustPackage.GetGlobalService(typeof(DTE)); // If path to racer.exe is specifed, use it - if(GetVisualRustProperty(env, "UseCustomRacer")) - racerPathForExecution = GetVisualRustProperty(env, "CustomRacerPath"); + if(Utils.GetVisualRustProperty(env, "UseCustomRacer")) + racerPathForExecution = Utils.GetVisualRustProperty(env, "CustomRacerPath"); else racerPathForExecution = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Racer", BundledRacerExecutable); // Same for custom RUST_SRC_PATH - if(GetVisualRustProperty(env, "UseCustomSources")) - racerSourcesLocation = GetVisualRustProperty(env, "CustomSourcesPath"); + if(Utils.GetVisualRustProperty(env, "UseCustomSources")) + racerSourcesLocation = Utils.GetVisualRustProperty(env, "CustomSourcesPath"); else racerSourcesLocation = null; } @@ -136,27 +131,5 @@ private string Exec(string args) return ""; } } - - class WindowsErrorMode : IDisposable - { - [DllImport("kernel32.dll", SetLastError = true)] - private static extern int SetErrorMode(int wMode); - - private readonly int oldErrorMode; - - /// - /// Creates a new error mode context. - /// - /// Error mode to use. 3 is a useful value. - public WindowsErrorMode(int mode) - { - oldErrorMode = SetErrorMode(mode); - } - - public void Dispose() - { - SetErrorMode(oldErrorMode); - } - } } } diff --git a/VisualRust/Utils.cs b/VisualRust/Utils.cs index 4faea8c0..1b9dc9ed 100644 --- a/VisualRust/Utils.cs +++ b/VisualRust/Utils.cs @@ -17,6 +17,8 @@ namespace VisualRust using RustLexer; using Microsoft.VisualStudio.Text; using Antlr4.Runtime; + using System.Runtime.InteropServices; + using EnvDTE; static class Utils { @@ -293,6 +295,11 @@ internal static Tuple GetTokensAtPosition(SnapshotPoint snapshot return Tuple.Create(leftToken, currentToken); } + + public static T GetVisualRustProperty(DTE env, string key) + { + return (T)env.get_Properties("Visual Rust", "General").Item(key).Value; + } } public class TemporaryFile : IDisposable @@ -327,4 +334,25 @@ public void Dispose() } } + public class WindowsErrorMode : IDisposable + { + [DllImport("kernel32.dll", SetLastError = true)] + private static extern int SetErrorMode(int wMode); + + private readonly int oldErrorMode; + + /// + /// Creates a new error mode context. + /// + /// Error mode to use. 3 is a useful value. + public WindowsErrorMode(int mode) + { + oldErrorMode = SetErrorMode(mode); + } + + public void Dispose() + { + SetErrorMode(oldErrorMode); + } + } } diff --git a/VisualRust/VisualRust.csproj b/VisualRust/VisualRust.csproj index cb132a2c..029cf592 100644 --- a/VisualRust/VisualRust.csproj +++ b/VisualRust/VisualRust.csproj @@ -212,6 +212,8 @@ + + @@ -289,6 +291,38 @@ LICENSE.txt true + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + PreserveNewest true diff --git a/VisualRust/VisualRustPackage.cs b/VisualRust/VisualRustPackage.cs index dd82db23..4e3788f1 100644 --- a/VisualRust/VisualRustPackage.cs +++ b/VisualRust/VisualRustPackage.cs @@ -101,6 +101,7 @@ protected override void Initialize() docEventsListener = new RunningDocTableEventsListener((IVsRunningDocumentTable)GetService(typeof(SVsRunningDocumentTable))); Racer.RacerSingleton.Init(); + GPG.Gpg.Init(); } protected override void Dispose(bool disposing) diff --git a/VisualRust/gpg/Gpg.cs b/VisualRust/gpg/Gpg.cs new file mode 100644 index 00000000..751c7841 --- /dev/null +++ b/VisualRust/gpg/Gpg.cs @@ -0,0 +1,121 @@ +using EnvDTE; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Process = System.Diagnostics.Process; + +namespace VisualRust.GPG +{ + /// + /// Wrapper for the native gpg2.exe binary + /// + public class Gpg + { + private string homedir; + private string gpgPath; + private const string BundledGpgExecutable = "gpg2.exe"; + private const int TimeoutMillis = 3000; + private static Gpg instance; + + /// + /// Gets the singleton instance. + /// + public static Gpg Instance + { + get + { + if (instance == null) + Init(); + return instance; + } + } + + /// + /// Initializes the environment for the racer autocompleter. + /// Can be called from package/command init to init ahead of first use. + /// + public static void Init() + { + if (instance == null) + instance = new Gpg(); + } + + private void ReinitializeGpgPaths() + { + DTE env = (DTE)VisualRustPackage.GetGlobalService(typeof(DTE)); + if (Utils.GetVisualRustProperty(env, "UseCustomGpg")) + gpgPath = Utils.GetVisualRustProperty(env, "CustomGpgPath"); + else + gpgPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "gpg", BundledGpgExecutable); + + if (Utils.GetVisualRustProperty(env, "UseCustomGpgHomedir")) + homedir = Utils.GetVisualRustProperty(env, "CustomGpgHomedir"); + else + homedir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "gpg"); + } + + private Gpg() + { + ReinitializeGpgPaths(); + } + + private Tuple Exec(string args, Stream stdin) + { + try + { + using (new WindowsErrorMode(3)) + using (Process process = new Process()) + { + + process.StartInfo.FileName = gpgPath; + process.StartInfo.Arguments = args; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardInput = true; + process.StartInfo.RedirectStandardError = true; + process.StartInfo.CreateNoWindow = true; + + process.Start(); + + stdin.CopyTo(process.StandardInput.BaseStream); + process.StandardInput.Close(); + string result = process.StandardOutput.ReadToEnd(); + result += process.StandardError.ReadToEnd(); + process.WaitForExit(); + + return Tuple.Create(process.ExitCode, result); + } + } + catch (Exception ex) + { + Utils.DebugPrintToOutput("Error executing gpg2.exe: {0}", ex); + return null; + } + } + + /// + /// Verify that data is validly signed. + /// + /// Stream to verify signature of. + /// true if the stream is signed with a key in the keyring, false otherwise. + public Tuple Verify(Stream data_to_verify, Stream signature) + { + // We could avoid hitting the filesystem, and ideally we'd be using GPGME instead of shelling out, but this is... far easier. + using (var tmp = new TemporaryFile("")) + { + using (var f = new FileStream(tmp.Path, FileMode.OpenOrCreate)) + { + signature.CopyTo(f); + f.Flush(); + } + var res = Exec("--verify --no-permission-warning --keyring \"" + homedir + "\\rust-key.gpg\" " + tmp.Path + " -", + data_to_verify); + return Tuple.Create(res.Item1 == 0, res.Item2); + } + } + } +} diff --git a/VisualRust/gpg/gpg2.exe b/VisualRust/gpg/gpg2.exe new file mode 100644 index 00000000..d6661e06 Binary files /dev/null and b/VisualRust/gpg/gpg2.exe differ diff --git a/VisualRust/gpg/gpgconf.exe b/VisualRust/gpg/gpgconf.exe new file mode 100644 index 00000000..0b91238a Binary files /dev/null and b/VisualRust/gpg/gpgconf.exe differ diff --git a/VisualRust/gpg/libadns-1.dll b/VisualRust/gpg/libadns-1.dll new file mode 100644 index 00000000..67cf70ec Binary files /dev/null and b/VisualRust/gpg/libadns-1.dll differ diff --git a/VisualRust/gpg/libassuan-0.dll b/VisualRust/gpg/libassuan-0.dll new file mode 100644 index 00000000..0adbcfd0 Binary files /dev/null and b/VisualRust/gpg/libassuan-0.dll differ diff --git a/VisualRust/gpg/libgcrypt-20.dll b/VisualRust/gpg/libgcrypt-20.dll new file mode 100644 index 00000000..82d61a58 Binary files /dev/null and b/VisualRust/gpg/libgcrypt-20.dll differ diff --git a/VisualRust/gpg/libgpg-error-0.dll b/VisualRust/gpg/libgpg-error-0.dll new file mode 100644 index 00000000..6db71018 Binary files /dev/null and b/VisualRust/gpg/libgpg-error-0.dll differ diff --git a/VisualRust/gpg/libiconv-2.dll b/VisualRust/gpg/libiconv-2.dll new file mode 100644 index 00000000..52ddcd1a Binary files /dev/null and b/VisualRust/gpg/libiconv-2.dll differ diff --git a/VisualRust/gpg/rust-key.gpg b/VisualRust/gpg/rust-key.gpg new file mode 100644 index 00000000..bc448d26 Binary files /dev/null and b/VisualRust/gpg/rust-key.gpg differ diff --git a/VisualRust/gpg/zlib1.dll b/VisualRust/gpg/zlib1.dll new file mode 100644 index 00000000..01926599 Binary files /dev/null and b/VisualRust/gpg/zlib1.dll differ