/// <summary> /// Sets information about an available update. If provided data is incorrect, stores "no udate" and throws. /// </summary> internal static void SetUpdate(string url, string urlHash, string fileHash, int verMajor, int verMinor, DateTime releaseDate, string releaseNotesUrl, string uiLang) { try { Uri updateUri = new Uri(url); if (updateUri.IsFile || (updateUri.Scheme != "http" && updateUri.Scheme != "https")) { throw new ArgumentException("Invalid update URL."); } if (!SignatureCheck.VerifySignature(url, urlHash)) { throw new ArgumentException("Update URL signature incorrect."); } if (verMajor < 1 || verMajor > 255) { throw new ArgumentException("Invalid major version."); } if (verMinor < 0 || verMinor > 255) { throw new ArgumentException("Invalid minor version."); } Uri notesUri = new Uri(releaseNotesUrl); if (notesUri.IsFile || (notesUri.Scheme != "http" && notesUri.Scheme != "https")) { throw new ArgumentException("Invalid release notes URL."); } // OK: store info int verInOne = verMajor; verInOne <<= 8; verInOne += verMinor; Data.UpdateAvailable = true; Data.UpdateUrl = url; Data.UpdateUrlHash = urlHash; Data.UpdateFileHash = fileHash; Data.UpdateVersionInOne = (ushort)verInOne; Data.UpdateReleaseDate = releaseDate.Year.ToString() + "-" + releaseDate.Month.ToString("00") + "-" + releaseDate.Day.ToString("00"); Data.ReleaseNotesUrl = releaseNotesUrl; Data.UILang = uiLang; saveData(); } catch { try { ClearUpdate(); } catch { } throw; } }
/// <summary> /// Verifies downloaded file against its hash; updates state as needed. /// </summary> private bool doVerifyFile(string fname, string fhash) { try { doSetStateSafe(State.Verifying); Thread.Sleep(1000); // Verify can be fast if (!SignatureCheck.VerifySignature(new FileInfo(fname), fhash)) { doSetStateSafe(State.VerifyFailed); return(false); } } catch { doSetStateSafe(State.VerifyFailed); return(false); } return(true); }
/// <summary> /// <para>Signs an update URL and and update package with the provided private key.</para> /// <para>The private key belongs to the embedded public key that we use for verification, but it's NOT included in the code base.</para> /// </summary> private static void doSignUpdate(string[] args) { if (args.Length != 5) { throw new Exception("Expected usage: /sign <url> <installer-file> <private-key-file> <output-file>"); } using (StreamReader srKey = new StreamReader(args[3])) using (StreamWriter srOut = new StreamWriter(args[4])) { string keyXml = srKey.ReadToEnd(); string sigUrl = SignatureCheck.Sign(args[1], keyXml); string sigFile = SignatureCheck.Sign(new FileInfo(args[2]), keyXml); srOut.WriteLine("URL signature:"); srOut.WriteLine(sigUrl); srOut.WriteLine(); srOut.WriteLine("File signature:"); srOut.WriteLine(sigFile); srOut.WriteLine(); } }
/// <summary> /// Downloads update, reports failure and updates states as needed. /// </summary> private bool doDownload(out string fname, out string fileHash) { WebClient wc = null; string url, urlHash; fileHash = null; fname = null; // Start download try { // First, figure out what to download. UpdateInfo.GetDownloadInfo(out url, out urlHash, out fileHash); // Verify URL hash if (!SignatureCheck.VerifySignature(url, urlHash)) { // No good: download fails. doSetStateSafe(State.DLoadFailed); return(false); } // Get to download. Schedule file we'll be downloading for deletion right now. fname = Helper.GetTempExePath(); scheduleFileToDelete(fname); dloadLastProgress = DateTime.Now; wc = new WebClient(); wc.Headers["user-agent"] = "Zydeo updater"; wc.UseDefaultCredentials = true; wc.Proxy = WebRequest.GetSystemWebProxy(); wc.DownloadFileCompleted += onDownloadCompleted; wc.DownloadProgressChanged += onDownloadProgressChanged; wc.DownloadFileAsync(new Uri(url), fname); // Keep waiting while (true) { // If done, break out of cycle. if (dloadCompleteEvent.WaitOne(100)) { break; } // Check for timeout - when did we last receive progress? DateTime dtLastProgress; lock (dloadCompleteEvent) { dtLastProgress = dloadLastProgress; } TimeSpan elapsed = DateTime.Now.Subtract(dtLastProgress); // If timeout has elapsed, cancel download and - throw. if (elapsed.TotalSeconds > Magic.DownloadTimeoutSec) { // This will trigger "completed", but we won't check the exception reported by it anymore. wc.CancelAsync(); // Timeout = download fails. throw new Exception("Download timed out."); } // Did user cancel download? if (cancel) { // This will trigger "completed", but we won't check the exception reported by it anymore. wc.CancelAsync(); // Set state to download canceled. doSetStateSafe(State.DLoadCanceled); return(false); } } // If completed with error, throw here if (dloadException != null) { throw dloadException; } } catch (Exception ex) { FileLogger.Instance.LogException(ex); doSetStateSafe(State.DLoadFailed); return(false); } finally { if (wc != null) { try { wc.Dispose(); } catch { } } } return(true); }
/// <summary> /// Runs as LOCAL SYSTEM from temp path; receives info from updater UI; checks package; executes installer. /// </summary> public static void Work() { // The service pipe we're listening through. NamedPipeStream pstream = null; try { // Create service pipe FileLogger.Instance.LogInfo("Creating named pipe."); FileSecurity pipeSecurity = new FileSecurity(); pipeSecurity.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier("S-1-5-11"), FileSystemRights.FullControl, AccessControlType.Allow)); pstream = NamedPipeStream.Create(Magic.ServiceShortName, NamedPipeStream.ServerMode.Bidirectional, pipeSecurity); // Wait for client (updater UI) to connect; but not too long. FileLogger.Instance.LogInfo("Waiting for client to connect."); if (!doListen(ref pstream)) { throw new Exception("Client didn't show up; tired of waiting."); } FileLogger.Instance.LogInfo("Client connected, reading paths and hash."); // Read location of downloaded installer and its file hash. string fname, fhash; doReadRequest(pstream, out fname, out fhash); // Verify signature FileLogger.Instance.LogInfo("Info received; verifying signature."); if (!SignatureCheck.VerifySignature(new FileInfo(fname), fhash)) { throw new Exception("Signature incorrect."); } FileLogger.Instance.LogInfo("Installer signature OK; launching installer"); // Let caller know we've started installer pstream.WriteByte(Magic.SrvCodeInstallStarted); // Launch installer int exitCode = doRunInstaller(fname); FileLogger.Instance.LogInfo("Installer returned exit code " + exitCode.ToString()); // Exit code 1 is failure if (exitCode == 1) { throw new Exception("Installer failed."); } // We've succeeded; let caller know. pstream.WriteByte(Magic.SrvCodeSuccess); FileLogger.Instance.LogInfo("Finished with success; quitting."); } catch { // Upon error, return failure code to caller. if (pstream != null) { try { pstream.WriteByte(Magic.SrvCodeFail); } catch { } } throw; } finally { // Close & dispose of service pipe before we exit left. if (pstream != null) { try { pstream.Close(); pstream.Dispose(); pstream = null; } catch { } } } }