private static IEnumerable <CatalogPackageEntry> ReadCatalog(string hashCatalogFilePath) { // catalog is optional, we can also just check assembly properties or nupkg marker files if (string.IsNullOrWhiteSpace(hashCatalogFilePath)) { return(Enumerable.Empty <CatalogPackageEntry>()); } var doc = new XmlDocument(); using (var stream = File.OpenRead(hashCatalogFilePath)) { doc.Load(stream); } var packages = new List <CatalogPackageEntry>(); var catalog = doc.FirstChild; foreach (XmlElement p in catalog.ChildNodes) { var package = new CatalogPackageEntry { Id = p.Attributes["Id"].Value, Version = p.Attributes["Version"].Value, OriginalHash = p.Attributes["OriginalHash"].Value.ToBytes(), PoisonedHash = p.Attributes["PoisonedHash"]?.Value?.ToBytes(), Path = p.Attributes["Path"].Value, }; packages.Add(package); foreach (XmlNode f in p.ChildNodes) { var fEntry = new CatalogFileEntry { OriginalHash = f.Attributes["OriginalHash"].Value.ToBytes(), PoisonedHash = f.Attributes["PoisonedHash"]?.Value?.ToBytes(), Path = f.Attributes["Path"].Value, }; package.Files.Add(fEntry); } } return(packages); }
public override bool Execute() { var tempDirName = Path.GetRandomFileName(); var tempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), tempDirName)); var packageEntries = new List <CatalogPackageEntry>(); using (var sha = SHA256.Create()) { foreach (var p in PackagesToMark) { var packageEntry = new CatalogPackageEntry(); packageEntries.Add(packageEntry); packageEntry.Path = p.ItemSpec; using (var stream = File.OpenRead(p.ItemSpec)) { packageEntry.OriginalHash = sha.ComputeHash(stream); } var packageIdentity = ReadNuGetPackageInfos.ReadIdentity(p.ItemSpec); packageEntry.Id = packageIdentity.Id; packageEntry.Version = packageIdentity.Version.OriginalVersion; var packageTempPath = Path.Combine(tempDir.FullName, Path.GetFileName(p.ItemSpec)); ZipFile.ExtractToDirectory(p.ItemSpec, packageTempPath, true); foreach (string f in Directory.EnumerateFiles(packageTempPath, "*", SearchOption.AllDirectories)) { var catalogFileEntry = new CatalogFileEntry(); packageEntry.Files.Add(catalogFileEntry); catalogFileEntry.Path = Utility.MakeRelativePath(f, packageTempPath); AssemblyDefinition asm = null; // There seem to be some weird issues with using a file stream both for hashing and // assembly loading, even closing it in between. Use a MemoryStream to avoid issues. var memStream = new MemoryStream(); using (var stream = File.OpenRead(f)) { stream.CopyTo(memStream); } // First get the original hash of the file memStream.Seek(0, SeekOrigin.Begin); catalogFileEntry.OriginalHash = sha.ComputeHash(memStream); // Now try to read it as an assembly memStream.Seek(0, SeekOrigin.Begin); try { asm = AssemblyDefinition.ReadAssembly(memStream, new ReaderParameters(ReadingMode.Deferred)); } catch { // this is okay, it's not an assembly we can read } // if we read it, now poison and write it back out if (asm != null) { Poison(asm); try { // Cecil doesn't try to do some modifications until it writes out the file, // and then throws after we've already truncated the file if it finds out it can't do them. // Write to a memory stream first and then copy to the real stream if it suceeds. If it // fails, we won't truncate the file and we will depend on hashes instead in that case. using (var testMemStream = new MemoryStream()) { asm.Write(testMemStream); testMemStream.Seek(0, SeekOrigin.Begin); using (var stream = File.Open(f, FileMode.Create, FileAccess.ReadWrite)) { testMemStream.CopyTo(stream); } } // then get the hash of the now-poisoned file using (var stream = File.OpenRead(f)) { catalogFileEntry.PoisonedHash = sha.ComputeHash(stream); } } catch { // see above note in the try - this is okay. } } } if (!string.IsNullOrWhiteSpace(MarkerFileName)) { var markerFilePath = Path.Combine(packageTempPath, MarkerFileName); if (File.Exists(markerFilePath)) { throw new ArgumentException($"Marker file name '{MarkerFileName}' is not sufficiently unique! Exists in '{p.ItemSpec}'.", nameof(MarkerFileName)); } // mostly we just need to write something unique to this so it's not hashed as a matching file when we check it later. // but it's also convenient to have the package catalog handy. File.WriteAllText(markerFilePath, packageEntry.ToXml().ToString()); } // create a temp file for this so if something goes wrong in the process we're not in too weird of a state var poisonedPackageName = Path.GetFileName(p.ItemSpec) + ".poisoned"; var poisonedPackagePath = Path.Combine(tempDir.FullName, poisonedPackageName); ZipFile.CreateFromDirectory(packageTempPath, poisonedPackagePath); // Get the hash of the poisoned package (with poisoned marker file and poisoned assemblies inside) using (var stream = File.OpenRead(poisonedPackagePath)) { packageEntry.PoisonedHash = sha.ComputeHash(stream); } File.Delete(p.ItemSpec); File.Move(poisonedPackagePath, p.ItemSpec); } } // if we should write out the catalog, do that if (!string.IsNullOrWhiteSpace(CatalogOutputFilePath)) { var outputFileDir = Path.GetDirectoryName(CatalogOutputFilePath); if (!Directory.Exists(outputFileDir)) { Directory.CreateDirectory(outputFileDir); } File.WriteAllText(CatalogOutputFilePath, (new XElement("HashCatalog", packageEntries.Select(p => p.ToXml()))).ToString()); } tempDir.Delete(true); return(!Log.HasLoggedErrors); }