Esempio n. 1
0
        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);
        }
Esempio n. 2
0
        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);
        }