public static bool VerifyUnpackedFolder(string folder, UpdateInfo version = null) { try { UpdateInfo update; FileEntry manifest; var sha256 = System.Security.Cryptography.SHA256.Create(); var md5 = System.Security.Cryptography.MD5.Create(); using(var fs = System.IO.File.OpenRead(System.IO.Path.Combine(folder, UPDATE_MANIFEST_FILENAME))) { using(var ss = new SignatureReadingStream(fs, SIGN_KEY)) using(var tr = new System.IO.StreamReader(ss)) using(var jr = new Newtonsoft.Json.JsonTextReader(tr)) update = new Newtonsoft.Json.JsonSerializer().Deserialize<UpdateInfo>(jr); sha256.Initialize(); md5.Initialize(); fs.Position = 0; var h1 = Convert.ToBase64String(sha256.ComputeHash(fs)); fs.Position = 0; var h2 = Convert.ToBase64String(md5.ComputeHash(fs)); manifest = new FileEntry() { Path = UPDATE_MANIFEST_FILENAME, Ignore = false, LastWriteTime = update.ReleaseTime, SHA256 = h1, MD5 = h2 }; } if (version != null && (update.Displayname != version.Displayname || update.ReleaseTime != version.ReleaseTime)) throw new Exception("The found version was not the expected version"); var paths = update.Files.Where(x => !x.Ignore).ToDictionary(x => x.Path.Replace('/', System.IO.Path.DirectorySeparatorChar), Library.Utility.Utility.ClientFilenameStringComparer); paths.Add(manifest.Path, manifest); var ignores = (from x in update.Files where x.Ignore select Library.Utility.Utility.AppendDirSeparator(x.Path.Replace('/', System.IO.Path.DirectorySeparatorChar))).ToList(); folder = Library.Utility.Utility.AppendDirSeparator(folder); var baselen = folder.Length; foreach(var file in Library.Utility.Utility.EnumerateFileSystemEntries(folder)) { var relpath = file.Substring(baselen); if (string.IsNullOrWhiteSpace(relpath)) continue; FileEntry fe; if (!paths.TryGetValue(relpath, out fe)) { var ignore = false; foreach(var c in ignores) if (ignore = relpath.StartsWith(c)) break; if (ignore) continue; throw new Exception(string.Format("Found unexpected file: {0}", file)); } paths.Remove(relpath); if (fe.Path.EndsWith("/")) continue; sha256.Initialize(); md5.Initialize(); using(var fs = System.IO.File.OpenRead(file)) { if (Convert.ToBase64String(sha256.ComputeHash(fs)) != fe.SHA256) throw new Exception(string.Format("Invalid sha256 hash for file: {0}", file)); fs.Position = 0; if (Convert.ToBase64String(md5.ComputeHash(fs)) != fe.MD5) throw new Exception(string.Format("Invalid md5 hash for file: {0}", file)); } } var filteredpaths = (from p in paths where !string.IsNullOrWhiteSpace(p.Key) && !p.Key.EndsWith("/") select p.Key).ToList(); if (filteredpaths.Count == 1) throw new Exception(string.Format("Folder {0} is missing: {1}", folder, filteredpaths.First())); else if (filteredpaths.Count > 0) throw new Exception(string.Format("Folder {0} is missing {1} and {2} other file(s)", folder, filteredpaths.First(), filteredpaths.Count - 1)); return true; } catch (Exception ex) { if (OnError != null) OnError(ex); } return false; }
private static UpdateInfo ReadInstalledManifest(string folder) { var manifest = System.IO.Path.Combine(folder, UPDATE_MANIFEST_FILENAME); if (System.IO.File.Exists(manifest)) { try { using(var fs = System.IO.File.OpenRead(manifest)) using(var ss = new SignatureReadingStream(fs, SIGN_KEY)) using(var tr = new System.IO.StreamReader(ss)) using(var jr = new Newtonsoft.Json.JsonTextReader(tr)) return new Newtonsoft.Json.JsonSerializer().Deserialize<UpdateInfo>(jr); } catch (Exception ex) { if (OnError != null) OnError(ex); } } return null; }
public static UpdateInfo CheckForUpdate() { foreach(var url in MANIFEST_URLS) { try { using(var tmpfile = new Library.Utility.TempFile()) { System.Net.WebClient wc = new System.Net.WebClient(); wc.Headers.Add(System.Net.HttpRequestHeader.UserAgent, string.Format("{0} v{1}{2}", APPNAME, SelfVersion.Version, string.IsNullOrWhiteSpace(InstallID) ? "" : " -" + InstallID)); wc.Headers.Add("X-Install-ID", InstallID); wc.DownloadFile(url, tmpfile); using(var fs = System.IO.File.OpenRead(tmpfile)) using(var ss = new SignatureReadingStream(fs, SIGN_KEY)) using(var tr = new System.IO.StreamReader(ss)) using(var jr = new Newtonsoft.Json.JsonTextReader(tr)) { var update = new Newtonsoft.Json.JsonSerializer().Deserialize<UpdateInfo>(jr); if (TryParseVersion(update.Version) <= TryParseVersion(SelfVersion.Version)) return null; if (string.Equals(SelfVersion.ReleaseType, "Debug", StringComparison.InvariantCultureIgnoreCase) && !string.Equals(update.ReleaseType, SelfVersion.ReleaseType, StringComparison.CurrentCultureIgnoreCase)) return null; LastUpdateCheckVersion = update; return update; } } } catch (Exception ex) { if (OnError != null) OnError(ex); } } return null; }
public static UpdateInfo CheckForUpdate(ReleaseType channel = ReleaseType.Unknown) { if (channel == ReleaseType.Unknown) channel = AutoUpdateSettings.DefaultUpdateChannel; foreach(var rawurl in MANIFEST_URLS) { var url = rawurl; // Attempt to match the url to change the channel if possible // This allows overrides to the URLs for deployment of custom builds, // but does not require that they adopt the channel system var match = AutoUpdateSettings.MATCH_AUTOUPDATE_URL.Match(url); if (match.Success) { var mg = match.Groups[AutoUpdateSettings.MATCH_UPDATE_URL_CHANNEL_GROUP]; // Replace the channel name with the chosen channel url = url.Substring(0, mg.Index) + channel.ToString().ToLowerInvariant() + url.Substring(mg.Index + mg.Length); } try { using(var tmpfile = new Library.Utility.TempFile()) { System.Net.WebClient wc = new System.Net.WebClient(); wc.Headers.Add(System.Net.HttpRequestHeader.UserAgent, string.Format("{0} v{1}{2}", APPNAME, SelfVersion.Version, string.IsNullOrWhiteSpace(InstallID) ? "" : " -" + InstallID)); wc.Headers.Add("X-Install-ID", InstallID); wc.DownloadFile(url, tmpfile); using(var fs = System.IO.File.OpenRead(tmpfile)) using(var ss = new SignatureReadingStream(fs, SIGN_KEY)) using(var tr = new System.IO.StreamReader(ss)) using(var jr = new Newtonsoft.Json.JsonTextReader(tr)) { var update = new Newtonsoft.Json.JsonSerializer().Deserialize<UpdateInfo>(jr); if (TryParseVersion(update.Version) <= TryParseVersion(SelfVersion.Version)) return null; // Don't install a debug update on a release build and vice versa if (string.Equals(SelfVersion.ReleaseType, "Debug", StringComparison.InvariantCultureIgnoreCase) && !string.Equals(update.ReleaseType, SelfVersion.ReleaseType, StringComparison.CurrentCultureIgnoreCase)) return null; ReleaseType rt; if (!Enum.TryParse<ReleaseType>(update.ReleaseType, true, out rt)) rt = ReleaseType.Unknown; // If the update is too low to be considered, skip it // Should never happen, but protects against mistakes in deployment if (rt > channel) return null; LastUpdateCheckVersion = update; return update; } } } catch (Exception ex) { if (OnError != null) OnError(ex); } } return null; }
public static bool VerifyUnpackedFolder(string folder, UpdateInfo version = null) { try { UpdateInfo update; FileEntry manifest; var sha256 = System.Security.Cryptography.SHA256.Create(); var md5 = System.Security.Cryptography.MD5.Create(); using (var fs = System.IO.File.OpenRead(System.IO.Path.Combine(folder, UPDATE_MANIFEST_FILENAME))) { using (var ss = new SignatureReadingStream(fs, SIGN_KEY)) using (var tr = new System.IO.StreamReader(ss)) using (var jr = new Newtonsoft.Json.JsonTextReader(tr)) update = new Newtonsoft.Json.JsonSerializer().Deserialize <UpdateInfo>(jr); sha256.Initialize(); md5.Initialize(); fs.Position = 0; var h1 = Convert.ToBase64String(sha256.ComputeHash(fs)); fs.Position = 0; var h2 = Convert.ToBase64String(md5.ComputeHash(fs)); manifest = new FileEntry() { Path = UPDATE_MANIFEST_FILENAME, Ignore = false, LastWriteTime = update.ReleaseTime, SHA256 = h1, MD5 = h2 }; } if (version != null && (update.Displayname != version.Displayname || update.ReleaseTime != version.ReleaseTime)) { throw new Exception("The found version was not the expected version"); } var paths = update.Files.Where(x => !x.Ignore).ToDictionary(x => x.Path.Replace('/', System.IO.Path.DirectorySeparatorChar), Library.Utility.Utility.ClientFilenameStringComparer); paths.Add(manifest.Path, manifest); var ignores = (from x in update.Files where x.Ignore select Library.Utility.Utility.AppendDirSeparator(x.Path.Replace('/', System.IO.Path.DirectorySeparatorChar))).ToList(); folder = Library.Utility.Utility.AppendDirSeparator(folder); var baselen = folder.Length; foreach (var file in Library.Utility.Utility.EnumerateFileSystemEntries(folder)) { var relpath = file.Substring(baselen); if (string.IsNullOrWhiteSpace(relpath)) { continue; } FileEntry fe; if (!paths.TryGetValue(relpath, out fe)) { var ignore = false; foreach (var c in ignores) { if (ignore = relpath.StartsWith(c)) { break; } } if (ignore) { continue; } throw new Exception(string.Format("Found unexpected file: {0}", file)); } paths.Remove(relpath); if (fe.Path.EndsWith("/")) { continue; } sha256.Initialize(); md5.Initialize(); using (var fs = System.IO.File.OpenRead(file)) { if (Convert.ToBase64String(sha256.ComputeHash(fs)) != fe.SHA256) { throw new Exception(string.Format("Invalid sha256 hash for file: {0}", file)); } fs.Position = 0; if (Convert.ToBase64String(md5.ComputeHash(fs)) != fe.MD5) { throw new Exception(string.Format("Invalid md5 hash for file: {0}", file)); } } } var filteredpaths = (from p in paths where !string.IsNullOrWhiteSpace(p.Key) && !p.Key.EndsWith("/") select p.Key).ToList(); if (filteredpaths.Count == 1) { throw new Exception(string.Format("Folder {0} is missing: {1}", folder, filteredpaths.First())); } else if (filteredpaths.Count > 0) { throw new Exception(string.Format("Folder {0} is missing {1} and {2} other file(s)", folder, filteredpaths.First(), filteredpaths.Count - 1)); } return(true); } catch (Exception ex) { if (OnError != null) { OnError(ex); } } return(false); }
public static void CreateUpdatePackage(System.Security.Cryptography.RSACryptoServiceProvider key, string inputfolder, string outputfolder, string manifest = null) { // Read the existing manifest UpdateInfo remoteManifest; var manifestpath = manifest ?? System.IO.Path.Combine(inputfolder, UPDATE_MANIFEST_FILENAME); using (var s = System.IO.File.OpenRead(manifestpath)) using (var sr = new System.IO.StreamReader(s)) using (var jr = new Newtonsoft.Json.JsonTextReader(sr)) remoteManifest = new Newtonsoft.Json.JsonSerializer().Deserialize <UpdateInfo>(jr); if (remoteManifest.Files == null) { remoteManifest.Files = new FileEntry[0]; } if (remoteManifest.ReleaseTime.Ticks == 0) { remoteManifest.ReleaseTime = DateTime.UtcNow; } var ignoreFiles = (from n in remoteManifest.Files where n.Ignore select n).ToArray(); var ignoreMap = ignoreFiles.ToDictionary(k => k.Path, k => "", Duplicati.Library.Utility.Utility.ClientFilenameStringComparer); remoteManifest.MD5 = null; remoteManifest.SHA256 = null; remoteManifest.Files = null; remoteManifest.UncompressedSize = 0; var localManifest = remoteManifest.Clone(); localManifest.RemoteURLS = null; inputfolder = Duplicati.Library.Utility.Utility.AppendDirSeparator(inputfolder); var baselen = inputfolder.Length; var dirsep = System.IO.Path.DirectorySeparatorChar.ToString(); ignoreMap.Add(UPDATE_MANIFEST_FILENAME, ""); var md5 = System.Security.Cryptography.MD5.Create(); var sha256 = System.Security.Cryptography.SHA256.Create(); Func <string, string> computeMD5 = (path) => { md5.Initialize(); using (var fs = System.IO.File.OpenRead(path)) return(Convert.ToBase64String(md5.ComputeHash(fs))); }; Func <string, string> computeSHA256 = (path) => { sha256.Initialize(); using (var fs = System.IO.File.OpenRead(path)) return(Convert.ToBase64String(sha256.ComputeHash(fs))); }; // Build a zip using (var archive_temp = new Duplicati.Library.Utility.TempFile()) { using (var zipfile = new Duplicati.Library.Compression.FileArchiveZip(archive_temp, new Dictionary <string, string>())) { Func <string, string, bool> addToArchive = (path, relpath) => { if (ignoreMap.ContainsKey(relpath)) { return(false); } if (path.EndsWith(dirsep)) { return(true); } using (var source = System.IO.File.OpenRead(path)) using (var target = zipfile.CreateFile(relpath, Duplicati.Library.Interface.CompressionHint.Compressible, System.IO.File.GetLastAccessTimeUtc(path))) { source.CopyTo(target); remoteManifest.UncompressedSize += source.Length; } return(true); }; // Build the update manifest localManifest.Files = (from fse in Duplicati.Library.Utility.Utility.EnumerateFileSystemEntries(inputfolder) let relpath = fse.Substring(baselen) where addToArchive(fse, relpath) select new FileEntry() { Path = relpath, LastWriteTime = System.IO.File.GetLastAccessTimeUtc(fse), MD5 = fse.EndsWith(dirsep) ? null : computeMD5(fse), SHA256 = fse.EndsWith(dirsep) ? null : computeSHA256(fse) }) .Union(ignoreFiles).ToArray(); // Write a signed manifest with the files using (var ms = new System.IO.MemoryStream()) using (var sw = new System.IO.StreamWriter(ms)) { new Newtonsoft.Json.JsonSerializer().Serialize(sw, localManifest); sw.Flush(); using (var ms2 = new System.IO.MemoryStream()) { SignatureReadingStream.CreateSignedStream(ms, ms2, key); ms2.Position = 0; using (var sigfile = zipfile.CreateFile(UPDATE_MANIFEST_FILENAME, Duplicati.Library.Interface.CompressionHint.Compressible, DateTime.UtcNow)) ms2.CopyTo(sigfile); } } } remoteManifest.CompressedSize = new System.IO.FileInfo(archive_temp).Length; remoteManifest.MD5 = computeMD5(archive_temp); remoteManifest.SHA256 = computeSHA256(archive_temp); System.IO.File.Move(archive_temp, System.IO.Path.Combine(outputfolder, "package.zip")); } // Write a signed manifest for upload using (var tf = new Duplicati.Library.Utility.TempFile()) { using (var ms = new System.IO.MemoryStream()) using (var sw = new System.IO.StreamWriter(ms)) { new Newtonsoft.Json.JsonSerializer().Serialize(sw, remoteManifest); sw.Flush(); using (var fs = System.IO.File.Create(tf)) SignatureReadingStream.CreateSignedStream(ms, fs, key); } System.IO.File.Move(tf, System.IO.Path.Combine(outputfolder, UPDATE_MANIFEST_FILENAME)); } }
public static UpdateInfo CheckForUpdate(ReleaseType channel = ReleaseType.Unknown) { if (channel == ReleaseType.Unknown) { channel = AutoUpdateSettings.DefaultUpdateChannel; } foreach (var rawurl in MANIFEST_URLS) { var url = rawurl; // Attempt to match the url to change the channel if possible // This allows overrides to the URLs for deployment of custom builds, // but does not require that they adopt the channel system var match = AutoUpdateSettings.MATCH_AUTOUPDATE_URL.Match(url); if (match.Success) { var mg = match.Groups[AutoUpdateSettings.MATCH_UPDATE_URL_CHANNEL_GROUP]; // Replace the channel name with the chosen channel url = url.Substring(0, mg.Index) + channel.ToString().ToLowerInvariant() + url.Substring(mg.Index + mg.Length); } try { using (var tmpfile = new Library.Utility.TempFile()) { System.Net.WebClient wc = new System.Net.WebClient(); wc.Headers.Add(System.Net.HttpRequestHeader.UserAgent, string.Format("{0} v{1}{2}", APPNAME, SelfVersion.Version, string.IsNullOrWhiteSpace(InstallID) ? "" : " -" + InstallID)); wc.Headers.Add("X-Install-ID", InstallID); wc.DownloadFile(url, tmpfile); using (var fs = System.IO.File.OpenRead(tmpfile)) using (var ss = new SignatureReadingStream(fs, SIGN_KEY)) using (var tr = new System.IO.StreamReader(ss)) using (var jr = new Newtonsoft.Json.JsonTextReader(tr)) { var update = new Newtonsoft.Json.JsonSerializer().Deserialize <UpdateInfo>(jr); if (TryParseVersion(update.Version) <= TryParseVersion(SelfVersion.Version)) { return(null); } // Don't install a debug update on a release build and vice versa if (string.Equals(SelfVersion.ReleaseType, "Debug", StringComparison.InvariantCultureIgnoreCase) && !string.Equals(update.ReleaseType, SelfVersion.ReleaseType, StringComparison.CurrentCultureIgnoreCase)) { return(null); } ReleaseType rt; if (!Enum.TryParse <ReleaseType>(update.ReleaseType, true, out rt)) { rt = ReleaseType.Unknown; } // If the update is too low to be considered, skip it // Should never happen, but protects against mistakes in deployment if (rt > channel) { return(null); } LastUpdateCheckVersion = update; return(update); } } } catch (Exception ex) { if (OnError != null) { OnError(ex); } } } return(null); }