mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
847 lines
43 KiB
C#
847 lines
43 KiB
C#
|
|
/********************************************************************++
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
--********************************************************************/
|
|
|
|
using Dbg = System.Management.Automation;
|
|
using System;
|
|
using System.Text;
|
|
using System.Security.Cryptography;
|
|
using System.Collections.Generic;
|
|
using System.Collections;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Management.Automation.Internal;
|
|
using System.Management.Automation.Provider;
|
|
using System.Management.Automation.Security;
|
|
using System.Runtime.InteropServices;
|
|
using DWORD = System.UInt32;
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
|
|
/// <summary>
|
|
/// Defines the possible status when validating integrity of catalog
|
|
/// </summary>
|
|
public enum CatalogValidationStatus
|
|
{
|
|
/// <summary>
|
|
/// Status when catalog is not tampered.
|
|
/// </summary>
|
|
Valid,
|
|
|
|
/// <summary>
|
|
/// Status when catalog is tampered.
|
|
/// </summary>
|
|
ValidationFailed
|
|
}
|
|
|
|
/// <summary>
|
|
/// Object returned by Catalog Cmdlets
|
|
/// </summary>
|
|
public class CatalogInformation
|
|
{
|
|
/// <summary>
|
|
/// status of catalog
|
|
/// </summary>
|
|
public CatalogValidationStatus Status { get; set; }
|
|
|
|
/// <summary>
|
|
/// Hash Algorithm used to calculate the hashes of files in Catalog
|
|
/// </summary>
|
|
public string HashAlgorithm { get; set; }
|
|
|
|
/// <summary>
|
|
/// Dictionary mapping files relative paths to their hash values found from Catalog
|
|
/// </summary>
|
|
public Dictionary<String, String> CatalogItems { get; set;}
|
|
|
|
/// <summary>
|
|
/// Dictionary mapping files relative paths to their hash values
|
|
/// </summary>
|
|
public Dictionary<String, String> PathItems { get; set;}
|
|
|
|
/// <summary>
|
|
/// Signature for the catalog
|
|
/// </summary>
|
|
public Signature Signature { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper functions for Windows Catalog functionality
|
|
/// </summary>
|
|
internal static class CatalogHelper
|
|
{
|
|
|
|
// Catalog Version is (0X100 = 256) for Catalog Version 1
|
|
private static int catalogVersion1 = 256;
|
|
|
|
// Catalog Version is (0X200 = 512) for Catalog Version 2
|
|
private static int catalogVersion2 = 512;
|
|
|
|
// Hash Algorithms supported by Windows Catalog
|
|
private static string HashAlgorithmSHA1 = "SHA1";
|
|
private static string HashAlgorithmSHA256 = "SHA256";
|
|
private static PSCmdlet _cmdlet = null;
|
|
|
|
/// <summary>
|
|
/// Find out the Version of Catalog by reading its Meta data. We can have either version 1 or version 2 catalog
|
|
/// </summary>
|
|
/// <param name="catalogHandle"> Handle to open catalog file </param>
|
|
/// <returns> Version of the catalog </returns>
|
|
private static int GetCatalogVersion(IntPtr catalogHandle)
|
|
{
|
|
int catalogVersion = -1;
|
|
|
|
IntPtr catalogData = NativeMethods.CryptCATStoreFromHandle(catalogHandle);
|
|
NativeMethods.CRYPTCATSTORE catalogInfo = ClrFacade.PtrToStructure<NativeMethods.CRYPTCATSTORE>(catalogData);
|
|
|
|
if (catalogInfo.dwPublicVersion == catalogVersion2)
|
|
{
|
|
catalogVersion = 2;
|
|
}
|
|
// One Windows 7 this API sent version information as decimal 1 not hex (0X100 = 256)
|
|
// so we are checking for that value as well. Reason we are not checking for version 2 above in
|
|
// this scenario because catalog verion 2 is not supported on win7.
|
|
else if ((catalogInfo.dwPublicVersion == catalogVersion1) || (catalogInfo.dwPublicVersion == 1))
|
|
{
|
|
catalogVersion = 1;
|
|
}
|
|
else
|
|
{
|
|
// catalog version we don't understand
|
|
Exception exception = new InvalidOperationException(StringUtil.Format(CatalogStrings.UnKnownCatalogVersion,
|
|
catalogVersion1.ToString("X"),
|
|
catalogVersion2.ToString("X")));
|
|
|
|
ErrorRecord errorRecord = new ErrorRecord(exception, "UnKnownCatalogVersion", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
return catalogVersion;
|
|
}
|
|
|
|
/// <summary>
|
|
/// HashAlgorithm used by the Catalog. It is based on the version of Catalog.
|
|
/// </summary>
|
|
/// <param name="catalogVersion"> Path of the output catalog file </param>
|
|
/// <returns> Version of the catalog </returns>
|
|
private static string GetCatalogHashAlgorithm(int catalogVersion)
|
|
{
|
|
string hashAlgorithm = string.Empty;
|
|
|
|
if (catalogVersion == 1)
|
|
{
|
|
hashAlgorithm = HashAlgorithmSHA1;
|
|
}
|
|
else if (catalogVersion == 2)
|
|
{
|
|
hashAlgorithm = HashAlgorithmSHA256;
|
|
}
|
|
else
|
|
{
|
|
// version we don't understand
|
|
Exception exception = new InvalidOperationException(StringUtil.Format(CatalogStrings.UnKnownCatalogVersion,
|
|
"1.0",
|
|
"2.0"));
|
|
|
|
ErrorRecord errorRecord = new ErrorRecord(exception, "UnKnownCatalogVersion", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
return hashAlgorithm;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate the Catalog Defintion File representing files and folders
|
|
/// </summary>
|
|
///
|
|
/// <param name="Path"> Path of expected output .cdf file </param>
|
|
///
|
|
/// <param name="catalogFilePath"> Path of the output catalog file </param>
|
|
///
|
|
/// <param name="cdfFilePath"> Path of the catalog definiton file </param>
|
|
///
|
|
/// <param name="catalogVersion"> Version of catalog</param>
|
|
///
|
|
/// <param name="hashAlgorithm"> hash method used to generate hashes for the Catalog </param>
|
|
/// <returns> HashSet for the relative Path for files in Catalog </returns>
|
|
internal static string GenerateCDFFile(Collection<string> Path, string catalogFilePath, string cdfFilePath, int catalogVersion, string hashAlgorithm)
|
|
{
|
|
HashSet<string> relativePaths = new HashSet<string>();
|
|
|
|
string cdfHeaderContent = string.Empty;
|
|
string cdfFilesContent = string.Empty;
|
|
int catAttributeCount = 0;
|
|
|
|
// First create header and files section for the catalog then write in file
|
|
cdfHeaderContent += "[CatalogHeader]" + Environment.NewLine;
|
|
cdfHeaderContent += @"Name=" + catalogFilePath + Environment.NewLine;
|
|
cdfHeaderContent += "CatalogVersion=" + catalogVersion + Environment.NewLine;
|
|
cdfHeaderContent += "HashAlgorithms=" + hashAlgorithm + Environment.NewLine;
|
|
cdfFilesContent += "[CatalogFiles]" + Environment.NewLine;
|
|
|
|
foreach (string catalogFile in Path)
|
|
{
|
|
if (System.IO.Directory.Exists(catalogFile))
|
|
{
|
|
var directoryItems = Directory.EnumerateFiles(catalogFile, "*.*", SearchOption.AllDirectories);
|
|
foreach (string fileItem in directoryItems)
|
|
{
|
|
ProcessFileToBeAddedInCatalogDefinitionFile(new FileInfo(fileItem), new DirectoryInfo(catalogFile), ref relativePaths, ref cdfHeaderContent, ref cdfFilesContent, ref catAttributeCount);
|
|
}
|
|
}
|
|
else if (System.IO.File.Exists(catalogFile))
|
|
{
|
|
ProcessFileToBeAddedInCatalogDefinitionFile(new FileInfo(catalogFile), null, ref relativePaths, ref cdfHeaderContent, ref cdfFilesContent, ref catAttributeCount);
|
|
}
|
|
}
|
|
|
|
using (System.IO.StreamWriter fileWriter = new System.IO.StreamWriter(new FileStream(cdfFilePath, FileMode.Create)))
|
|
{
|
|
fileWriter.WriteLine(cdfHeaderContent);
|
|
fileWriter.WriteLine();
|
|
fileWriter.WriteLine(cdfFilesContent);
|
|
}
|
|
return cdfFilePath;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get file attribute (Relative path in our case) from catalog
|
|
/// </summary>
|
|
/// <param name="fileToHash"> file to hash </param>
|
|
/// <param name="dirInfo"> directory information about file needed to calculate relative file path. </param>
|
|
/// <param name="relativePaths"> working set of relative paths of all files. </param>
|
|
/// <param name="cdfHeaderContent"> content to be added in CatalogHeader section of cdf File</param>
|
|
/// <param name="cdfFilesContent"> content to be added in CatalogFiles section of cdf File </param>
|
|
/// <param name="catAttributeCount"> indictaing the current no of catalog header level attributes </param>
|
|
/// <returns> void </returns>
|
|
internal static void ProcessFileToBeAddedInCatalogDefinitionFile(FileInfo fileToHash, DirectoryInfo dirInfo, ref HashSet<string> relativePaths, ref string cdfHeaderContent, ref string cdfFilesContent, ref int catAttributeCount)
|
|
{
|
|
string relativePath = string.Empty;
|
|
|
|
if (dirInfo != null)
|
|
{
|
|
//Relative path of the file is the path inside the containing folder excluding folder Name
|
|
relativePath = fileToHash.FullName.Substring(dirInfo.FullName.Length).TrimStart('\\');
|
|
}
|
|
else
|
|
{
|
|
relativePath = fileToHash.Name;
|
|
}
|
|
|
|
if (!relativePaths.Contains(relativePath))
|
|
{
|
|
relativePaths.Add(relativePath);
|
|
if (fileToHash.Length != 0)
|
|
{
|
|
cdfFilesContent += "<HASH>" + fileToHash.FullName + "=" + fileToHash.FullName + Environment.NewLine;
|
|
cdfFilesContent += "<HASH>" + fileToHash.FullName + "ATTR1=0x10010001:FilePath:" + relativePath + Environment.NewLine;
|
|
}
|
|
else
|
|
{
|
|
// zero length files are added as catalog level attributes because they can not be hashed
|
|
cdfHeaderContent += "CATATTR" + (++catAttributeCount) + "=0x10010001:FilePath:" + relativePath + Environment.NewLine;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If Files have same relative paths we can not distinguish them for
|
|
// Validation. So failing.
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.FoundDuplicateFilesRelativePath, relativePath)), "FoundDuplicateFilesRelativePath", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Generate the Catalog file for Input Catalog Defintion File
|
|
/// </summary>
|
|
///
|
|
/// <param name="cdfFilePath"> Path to the Input .cdf file </param>
|
|
internal static void GenerateCatalogFile(string cdfFilePath)
|
|
{
|
|
string pwszFilePath = cdfFilePath;
|
|
NativeMethods.CryptCATCDFOpenCallBack catOpenCallBack = new NativeMethods.CryptCATCDFOpenCallBack(ParseErrorCallback);
|
|
|
|
// Open CDF File
|
|
IntPtr resultCDF = NativeMethods.CryptCATCDFOpen(pwszFilePath, catOpenCallBack);
|
|
|
|
// navigate CDF header and files sections
|
|
if (resultCDF != IntPtr.Zero)
|
|
{
|
|
// First navigate all catalog level attributes entries first, they represent zero size files
|
|
IntPtr catalogAttr = IntPtr.Zero;
|
|
do
|
|
{
|
|
catalogAttr = NativeMethods.CryptCATCDFEnumCatAttributes(resultCDF, catalogAttr, catOpenCallBack);
|
|
|
|
if (catalogAttr != IntPtr.Zero)
|
|
{
|
|
string filePath = ProcessFilePathAttributeInCatalog(catalogAttr);
|
|
_cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.AddFileToCatalog, filePath, filePath));
|
|
}
|
|
} while (catalogAttr != IntPtr.Zero);
|
|
|
|
// navigate all the files hash entries in the .cdf file
|
|
IntPtr memberInfo = IntPtr.Zero;
|
|
try
|
|
{
|
|
IntPtr memberFile = IntPtr.Zero;
|
|
NativeMethods.CryptCATCDFEnumMembersByCDFTagExErrorCallBack memberCallBack = new NativeMethods.CryptCATCDFEnumMembersByCDFTagExErrorCallBack(ParseErrorCallback);
|
|
string fileName = string.Empty;
|
|
do
|
|
{
|
|
memberFile = NativeMethods.CryptCATCDFEnumMembersByCDFTagEx(resultCDF, memberFile, memberCallBack, ref memberInfo, true, IntPtr.Zero);
|
|
fileName = Marshal.PtrToStringUni(memberFile);
|
|
|
|
if (!String.IsNullOrEmpty(fileName))
|
|
{
|
|
|
|
IntPtr memberAttr = IntPtr.Zero;
|
|
string fileRelativePath = String.Empty;
|
|
do
|
|
{
|
|
memberAttr = NativeMethods.CryptCATCDFEnumAttributesWithCDFTag(resultCDF, memberFile, memberInfo, memberAttr, memberCallBack);
|
|
|
|
if (memberAttr != IntPtr.Zero)
|
|
{
|
|
fileRelativePath = ProcessFilePathAttributeInCatalog(memberAttr);
|
|
if (!String.IsNullOrEmpty(fileRelativePath))
|
|
{
|
|
// Found the attribute we are looking for
|
|
// Filename we read from the above API has <Hash> appended to its name as per CDF file tags convention
|
|
// Truncating that Information from the string.
|
|
string itemName = fileName.Substring(6);
|
|
_cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.AddFileToCatalog, itemName, fileRelativePath));
|
|
break;
|
|
}
|
|
}
|
|
} while (memberAttr != IntPtr.Zero);
|
|
}
|
|
} while (fileName != null);
|
|
}
|
|
finally
|
|
{
|
|
NativeMethods.CryptCATCDFClose(resultCDF);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we are not able to open CDF file we can not continue generating catalog
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(CatalogStrings.UnableToOpenCatalogDefinitionFile), "UnableToOpenCatalogDefinitionFile", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// To generate Catalog for the folder
|
|
/// </summary>
|
|
///
|
|
/// <param name="Path"> Path to folder or File </param>
|
|
/// <param name="catalogFilePath"> Catalog File Path </param>
|
|
/// <param name="catalogVersion"> Catalog File Path </param>
|
|
/// <param name="cmdlet"> Instance of cmdlet calling this method </param>
|
|
/// <returns> true if able to generate .cat file or false </returns>
|
|
internal static FileInfo GenerateCatalog(PSCmdlet cmdlet, Collection<string> Path, string catalogFilePath, int catalogVersion)
|
|
{
|
|
_cmdlet = cmdlet;
|
|
string hashAlgorithm = GetCatalogHashAlgorithm(catalogVersion);
|
|
|
|
if (!String.IsNullOrEmpty(hashAlgorithm))
|
|
{
|
|
// Generate Path for Catalog Defintion File
|
|
string cdfFilePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName());
|
|
cdfFilePath = cdfFilePath + ".cdf";
|
|
try
|
|
{
|
|
cdfFilePath = GenerateCDFFile(Path, catalogFilePath, cdfFilePath, catalogVersion, hashAlgorithm);
|
|
|
|
if (!File.Exists(cdfFilePath))
|
|
{
|
|
// If we are not able to generate catalog defintion file we can not continue generating catalog
|
|
// throw PSTraceSource.NewInvalidOperationException("catalog", CatalogStrings.CatalogDefintionFileNotGenerated);
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(CatalogStrings.CatalogDefintionFileNotGenerated), "CatalogDefintionFileNotGenerated", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
|
|
GenerateCatalogFile(cdfFilePath);
|
|
if (File.Exists(catalogFilePath))
|
|
{
|
|
return new FileInfo(catalogFilePath);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
File.Delete(cdfFilePath);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get file attribute (Relative path in our case) from catalog
|
|
/// </summary>
|
|
/// <param name="memberAttrInfo"> Pointer to current attribute of catalog member. </param>
|
|
/// <returns> value of the attribute </returns>
|
|
internal static string ProcessFilePathAttributeInCatalog(IntPtr memberAttrInfo)
|
|
{
|
|
string relativePath = string.Empty;
|
|
|
|
NativeMethods.CRYPTCATATTRIBUTE currentMemberAttr = ClrFacade.PtrToStructure<NativeMethods.CRYPTCATATTRIBUTE>(memberAttrInfo);
|
|
|
|
// check if this is the attribute we are looking for
|
|
// catalog generated other way not using New-FileCatalog can have attributes we don't understand
|
|
if (currentMemberAttr.pwszReferenceTag.Equals("FilePath", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// find the size for the current attribute value and then allocate buffer and copy from byte array
|
|
int attrValueSize = (int)currentMemberAttr.cbValue;
|
|
byte[] attrValue = new byte[attrValueSize];
|
|
Marshal.Copy(currentMemberAttr.pbValue, attrValue, 0, attrValueSize);
|
|
relativePath = System.Text.Encoding.Unicode.GetString(attrValue);
|
|
relativePath = relativePath.TrimEnd('\0');
|
|
}
|
|
|
|
return relativePath;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Make a hash for the file
|
|
/// </summary>
|
|
///
|
|
/// <param name="filePath"> Path of the file </param>
|
|
/// <param name="hashAlgorithm"> Used to calculate Hash </param>
|
|
/// <returns> HashValue for the file </returns>
|
|
internal static string CalculateFileHash(string filePath, string hashAlgorithm)
|
|
{
|
|
string hashValue = string.Empty;
|
|
IntPtr catAdmin = IntPtr.Zero;
|
|
|
|
// To get handle to the hash algorithm to be used to calculate hashes
|
|
if (!NativeMethods.CryptCATAdminAcquireContext2(ref catAdmin, IntPtr.Zero, hashAlgorithm, IntPtr.Zero, 0))
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToAcquireHashAlgorithmContext, hashAlgorithm)), "UnableToAcquireHashAlgorithmContext", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
|
|
DWORD GENERIC_READ = 0x80000000;
|
|
DWORD OPEN_EXISTING = 3;
|
|
IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
|
|
|
|
// Open the file that is to be hashed for reading and get its handle
|
|
IntPtr fileHandle = NativeMethods.CreateFile(filePath, GENERIC_READ, 0, 0 , OPEN_EXISTING, 0, IntPtr.Zero);
|
|
if (fileHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
try
|
|
{
|
|
DWORD hashBufferSize = 0;
|
|
IntPtr hashBuffer = IntPtr.Zero;
|
|
|
|
// Call first time to get the size of expected buffer to hold new hash value
|
|
if (!NativeMethods.CryptCATAdminCalcHashFromFileHandle2(catAdmin, fileHandle, ref hashBufferSize, hashBuffer, 0))
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToCreateFileHash, filePath)), "UnableToCreateFileHash", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
|
|
int size = (int)hashBufferSize;
|
|
hashBuffer = Marshal.AllocHGlobal(size);
|
|
try
|
|
{
|
|
// Call second time to actually get the hash value
|
|
if (!NativeMethods.CryptCATAdminCalcHashFromFileHandle2(catAdmin, fileHandle, ref hashBufferSize, hashBuffer, 0))
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToCreateFileHash, filePath)), "UnableToCreateFileHash", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
|
|
byte[] hashBytes = new byte[size];
|
|
Marshal.Copy(hashBuffer, hashBytes, 0, size);
|
|
hashValue = BitConverter.ToString(hashBytes).Replace("-", "");
|
|
|
|
}
|
|
finally
|
|
{
|
|
if (hashBuffer != IntPtr.Zero)
|
|
{
|
|
Marshal.FreeHGlobal(hashBuffer);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
NativeMethods.CryptCATAdminReleaseContext(catAdmin, 0);
|
|
NativeMethods.CloseHandle(fileHandle);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we are not able to open file that is to be hashed we can not continue with catalog validation
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToReadFileToHash, filePath)), "UnableToReadFileToHash", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
return hashValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Make list of hashes for given Catalog File
|
|
/// </summary>
|
|
/// <param name="catalogFilePath"> Path to the folder having catalog file </param>
|
|
/// <param name="excludedPatterns"></param>
|
|
/// <param name="catalogVersion"> The version of input catalog we read from catalog meta data after opening it.</param>
|
|
/// <returns> Dictionary mapping files relative paths to HashValues </returns>
|
|
internal static Dictionary<String, String> GetHashesFromCatalog(string catalogFilePath, WildcardPattern[] excludedPatterns,out int catalogVersion)
|
|
{
|
|
IntPtr resultCatalog = NativeMethods.CryptCATOpen(catalogFilePath, 0, IntPtr.Zero, 1, 0);
|
|
IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
|
|
Dictionary<String, String> catalogHashes = new Dictionary<String, String>(StringComparer.CurrentCultureIgnoreCase);
|
|
catalogVersion = 0;
|
|
|
|
if (resultCatalog != INVALID_HANDLE_VALUE)
|
|
{
|
|
try
|
|
{
|
|
IntPtr catAttrInfo = IntPtr.Zero;
|
|
|
|
// First traverse all catalog level attributes to get information about zero size file.
|
|
do
|
|
{
|
|
catAttrInfo = NativeMethods.CryptCATEnumerateCatAttr(resultCatalog, catAttrInfo);
|
|
|
|
// If we found attribute it is a file information retrieve its relative path
|
|
// and add it to catalog hash collection if its not in excluded files criteria
|
|
if (catAttrInfo != IntPtr.Zero)
|
|
{
|
|
string relativePath = ProcessFilePathAttributeInCatalog(catAttrInfo);
|
|
if (!String.IsNullOrEmpty(relativePath))
|
|
{
|
|
ProcessCatalogFile(relativePath, string.Empty, excludedPatterns, ref catalogHashes);
|
|
}
|
|
}
|
|
} while (catAttrInfo != IntPtr.Zero);
|
|
|
|
catalogVersion = GetCatalogVersion(resultCatalog);
|
|
|
|
IntPtr memberInfo = IntPtr.Zero;
|
|
// Next Navigate all members in Catalog files and get their relative paths and hashes
|
|
do
|
|
{
|
|
memberInfo = NativeMethods.CryptCATEnumerateMember(resultCatalog, memberInfo);
|
|
if (memberInfo != IntPtr.Zero)
|
|
{
|
|
NativeMethods.CRYPTCATMEMBER currentMember = ClrFacade.PtrToStructure<NativeMethods.CRYPTCATMEMBER>(memberInfo);
|
|
NativeMethods.SIP_INDIRECT_DATA pIndirectData = ClrFacade.PtrToStructure<NativeMethods.SIP_INDIRECT_DATA>(currentMember.pIndirectData);
|
|
|
|
// For Catalog version 2 CryptoAPI puts hashes of file attributes(relative path in our case) in Catalog as well
|
|
// We validate those along with file hashes so we are skipping duplicate entries
|
|
if (!((catalogVersion == 2) && (pIndirectData.DigestAlgorithm.pszObjId.Equals(new Oid("SHA1").Value, StringComparison.OrdinalIgnoreCase))))
|
|
{
|
|
string relativePath = String.Empty;
|
|
IntPtr memberAttrInfo = IntPtr.Zero;
|
|
do
|
|
{
|
|
memberAttrInfo = NativeMethods.CryptCATEnumerateAttr(resultCatalog, memberInfo, memberAttrInfo);
|
|
|
|
if (memberAttrInfo != IntPtr.Zero)
|
|
{
|
|
relativePath = ProcessFilePathAttributeInCatalog(memberAttrInfo);
|
|
if (!String.IsNullOrEmpty(relativePath))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
while (memberAttrInfo != IntPtr.Zero);
|
|
|
|
// If we did not find any Relative Path for the item in catalog we should quit
|
|
// This catalog must not be valid for our use as catalogs generated using New-FileCatalog
|
|
// always contains relative file Paths
|
|
if (String.IsNullOrEmpty(relativePath))
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToOpenCatalogFile, catalogFilePath)), "UnableToOpenCatalogFile", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
|
|
ProcessCatalogFile(relativePath, currentMember.pwszReferenceTag, excludedPatterns, ref catalogHashes);
|
|
}
|
|
}
|
|
} while (memberInfo != IntPtr.Zero);
|
|
}
|
|
finally
|
|
{
|
|
NativeMethods.CryptCATClose(resultCatalog);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToOpenCatalogFile, catalogFilePath)), "UnableToOpenCatalogFile", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
return catalogHashes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process file in path for its relative paths
|
|
/// </summary>
|
|
/// <param name="relativePath"> relative path of file found in catalog </param>
|
|
/// <param name="fileHash"> hash of file found in catalog. </param>
|
|
/// <param name="excludedPatterns"> skip file from validation if it matches these patterns </param>
|
|
/// <param name="catalogHashes"> collection of hashes of catalog </param>
|
|
/// <returns> void </returns>
|
|
internal static void ProcessCatalogFile(string relativePath, string fileHash, WildcardPattern[] excludedPatterns, ref Dictionary<String, String> catalogHashes)
|
|
{
|
|
// Found the attribute we are looking for
|
|
_cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.FoundFileHashInCatlogItem, relativePath, fileHash));
|
|
|
|
// Only add the file for validation if it does not meet exclusion criteria
|
|
if (!CheckExcludedCriteria((new FileInfo(relativePath)).Name, excludedPatterns))
|
|
{
|
|
// Add relativePath mapping to hashvalue for each file
|
|
catalogHashes.Add(relativePath, fileHash);
|
|
}
|
|
else
|
|
{
|
|
// Verbose about skipping file from catalog
|
|
_cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.SkipValidationOfCatalogFile, relativePath));
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Process file in path for its relative paths
|
|
/// </summary>
|
|
/// <param name="fileToHash"> file to hash </param>
|
|
/// <param name="dirInfo"> directory information about file needed to calculate relative file path. </param>
|
|
/// <param name="hashAlgorithm"> Used to calculate Hash </param>
|
|
/// <param name="excludedPatterns"> skip file if it matches these patterns </param>
|
|
/// <param name="fileHashes"> collection of hashes of files </param>
|
|
/// <returns> void </returns>
|
|
internal static void ProcessPathFile(FileInfo fileToHash, DirectoryInfo dirInfo, string hashAlgorithm, WildcardPattern[] excludedPatterns, ref Dictionary<String, String> fileHashes)
|
|
{
|
|
string relativePath = string.Empty;
|
|
string exclude = string.Empty;
|
|
|
|
if (dirInfo != null)
|
|
{
|
|
//Relative path of the file is the path inside the containing folder excluding folder Name
|
|
relativePath = fileToHash.FullName.Substring(dirInfo.FullName.Length).TrimStart('\\');
|
|
exclude = fileToHash.Name;
|
|
}
|
|
else
|
|
{
|
|
relativePath = fileToHash.Name;
|
|
exclude = relativePath;
|
|
}
|
|
|
|
if (!CheckExcludedCriteria(exclude, excludedPatterns))
|
|
{
|
|
string fileHash = string.Empty;
|
|
|
|
if(fileToHash.Length != 0)
|
|
{
|
|
fileHash = CalculateFileHash(fileToHash.FullName, hashAlgorithm);
|
|
}
|
|
|
|
if (!fileHashes.ContainsKey(relativePath))
|
|
{
|
|
fileHashes.Add(relativePath, fileHash);
|
|
_cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.FoundFileInPath, relativePath, fileHash));
|
|
}
|
|
else
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.FoundDuplicateFilesRelativePath, relativePath)), "FoundDuplicateFilesRelativePath", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Verbose about skipping file from path
|
|
_cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.SkipValidationOfPathFile, relativePath));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate the hashes of all the files in given folder
|
|
/// </summary>
|
|
///
|
|
/// <param name="folderPaths"> Path to folder or File </param>
|
|
/// <param name="catalogFilePath"> catalog file path it should be skipped when calculating the hashes </param>
|
|
/// <param name="hashAlgorithm"> Used to calculate Hash </param>
|
|
/// <param name="excludedPatterns"></param>
|
|
/// <returns> Dictionary mapping file relative paths to hashes. </returns>
|
|
internal static Dictionary<String, String> CalculateHashesFromPath(Collection<string> folderPaths, string catalogFilePath, string hashAlgorithm, WildcardPattern[] excludedPatterns)
|
|
{
|
|
// Create a HashTable of file Hashes
|
|
Dictionary<String, String> fileHashes = new Dictionary<String, String>(StringComparer.CurrentCultureIgnoreCase);
|
|
|
|
foreach (string folderPath in folderPaths)
|
|
{
|
|
if (System.IO.Directory.Exists(folderPath))
|
|
{
|
|
var directoryItems = Directory.EnumerateFiles(folderPath, "*.*", SearchOption.AllDirectories);
|
|
foreach (string fileItem in directoryItems)
|
|
{
|
|
// if its the catalog file we are validating we will skip it
|
|
if (String.Equals(fileItem, catalogFilePath, StringComparison.OrdinalIgnoreCase))
|
|
continue;
|
|
|
|
ProcessPathFile(new FileInfo(fileItem), new DirectoryInfo(folderPath), hashAlgorithm, excludedPatterns, ref fileHashes);
|
|
}
|
|
}
|
|
else if (System.IO.File.Exists(folderPath))
|
|
{
|
|
ProcessPathFile(new FileInfo(folderPath), null , hashAlgorithm, excludedPatterns, ref fileHashes);
|
|
}
|
|
}
|
|
return fileHashes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compare Dictionary objects
|
|
/// </summary>
|
|
///
|
|
/// <param name="catalogItems"> Hashes extracted from Catalog </param>
|
|
///
|
|
/// <param name="pathItems"> Hashes created from folders path </param>
|
|
///
|
|
/// <returns> True if both collections are same </returns>
|
|
internal static bool CompareDictionaries(Dictionary<String, String> catalogItems, Dictionary<String, String> pathItems)
|
|
{
|
|
bool Status = true;
|
|
|
|
List<string> relativePathsFromFolder = pathItems.Keys.ToList();
|
|
List<string> relativePathsFromCatalog = catalogItems.Keys.ToList();
|
|
|
|
// Find entires those are not in both list lists. These should be empty lists for success
|
|
// Hashes in Catalog should be exact similar to the ones from folder
|
|
List<string> relativePathsNotInFolder = relativePathsFromFolder.Except(relativePathsFromCatalog, StringComparer.CurrentCultureIgnoreCase).ToList();
|
|
List<string> relativePathsNotInCatalog = relativePathsFromCatalog.Except(relativePathsFromFolder, StringComparer.CurrentCultureIgnoreCase).ToList();
|
|
|
|
//Found extra hashes in Folder
|
|
if ((relativePathsNotInFolder.Count != 0) || (relativePathsNotInCatalog.Count != 0))
|
|
{
|
|
Status = false;
|
|
}
|
|
|
|
foreach (KeyValuePair<String, String> item in catalogItems)
|
|
{
|
|
string catalogHashValue = (string)catalogItems[item.Key];
|
|
if (pathItems.ContainsKey(item.Key))
|
|
{
|
|
string folderHashValue = (string)pathItems[item.Key];
|
|
if (folderHashValue.Equals(catalogHashValue))
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
Status = false;
|
|
}
|
|
}
|
|
}
|
|
return Status;
|
|
}
|
|
/// <summary>
|
|
/// To Validate the Integrity of Catalog
|
|
/// </summary>
|
|
///
|
|
/// <param name="catalogFolders"> Folder for which catalog is created </param>
|
|
/// <param name="catalogFilePath"> File Name of the Catalog </param>
|
|
/// <param name="excludedPatterns"></param>
|
|
/// <param name="cmdlet"> Instance of cmdlet calling this method </param>
|
|
/// <returns> Information about Catalog </returns>
|
|
internal static CatalogInformation ValidateCatalog(PSCmdlet cmdlet, Collection<string> catalogFolders, String catalogFilePath, WildcardPattern[] excludedPatterns)
|
|
{
|
|
_cmdlet = cmdlet;
|
|
int catalogVersion = 0;
|
|
Dictionary<String, String> catalogHashes = GetHashesFromCatalog(catalogFilePath, excludedPatterns, out catalogVersion);
|
|
string hashAlgorithm = GetCatalogHashAlgorithm(catalogVersion);
|
|
|
|
if (!String.IsNullOrEmpty(hashAlgorithm))
|
|
{
|
|
Dictionary<String, String> fileHashes = CalculateHashesFromPath(catalogFolders, catalogFilePath, hashAlgorithm, excludedPatterns);
|
|
CatalogInformation catalog = new CatalogInformation();
|
|
catalog.CatalogItems = catalogHashes;
|
|
catalog.PathItems = fileHashes;
|
|
bool status = CompareDictionaries(catalogHashes, fileHashes);
|
|
if (status == true)
|
|
{
|
|
catalog.Status = CatalogValidationStatus.Valid;
|
|
}
|
|
else
|
|
{
|
|
catalog.Status = CatalogValidationStatus.ValidationFailed;
|
|
}
|
|
catalog.HashAlgorithm = hashAlgorithm;
|
|
catalog.Signature = SignatureHelper.GetSignature(catalogFilePath, null);
|
|
return catalog;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if file meets the skip validation criteria
|
|
/// </summary>
|
|
/// <param name="filename"></param>
|
|
/// <param name="excludedPatterns"></param>
|
|
/// <returns>True if match is found else false</returns>
|
|
internal static bool CheckExcludedCriteria(string filename, WildcardPattern[] excludedPatterns)
|
|
{
|
|
if (excludedPatterns != null)
|
|
{
|
|
foreach (WildcardPattern patternItem in excludedPatterns)
|
|
{
|
|
if (patternItem.IsMatch(filename))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/// <summary>
|
|
/// Call back when error is thrown by catalog API's
|
|
/// </summary>
|
|
private static void ParseErrorCallback(DWORD dwErrorArea, DWORD dwLocalError, string pwszLine)
|
|
{
|
|
switch (dwErrorArea)
|
|
{
|
|
case NativeConstants.CRYPTCAT_E_AREA_HEADER: break;
|
|
case NativeConstants.CRYPTCAT_E_AREA_MEMBER: break;
|
|
case NativeConstants.CRYPTCAT_E_AREA_ATTRIBUTE: break;
|
|
default: break;
|
|
}
|
|
switch (dwLocalError)
|
|
{
|
|
case NativeConstants.CRYPTCAT_E_CDF_MEMBER_FILE_PATH:
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToFindFileNameOrPathForCatalogMember, pwszLine)), "UnableToFindFileNameOrPathForCatalogMember", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
break;
|
|
}
|
|
case NativeConstants.CRYPTCAT_E_CDF_MEMBER_INDIRECTDATA:
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToCreateFileHash, pwszLine)), "UnableToCreateFileHash", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
break;
|
|
}
|
|
case NativeConstants.CRYPTCAT_E_CDF_MEMBER_FILENOTFOUND:
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToFindFileToHash, pwszLine)), "UnableToFindFileToHash", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
break;
|
|
}
|
|
case NativeConstants.CRYPTCAT_E_CDF_BAD_GUID_CONV: break;
|
|
case NativeConstants.CRYPTCAT_E_CDF_ATTR_TYPECOMBO: break;
|
|
case NativeConstants.CRYPTCAT_E_CDF_ATTR_TOOFEWVALUES: break;
|
|
case NativeConstants.CRYPTCAT_E_CDF_UNSUPPORTED: break;
|
|
case NativeConstants.CRYPTCAT_E_CDF_DUPLICATE:
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.FoundDuplicateFileMemberInCatalog, pwszLine)), "FoundDuplicateFileMemberInCatalog", ErrorCategory.InvalidOperation, null);
|
|
_cmdlet.ThrowTerminatingError(errorRecord);
|
|
break;
|
|
}
|
|
case NativeConstants.CRYPTCAT_E_CDF_TAGNOTFOUND: break;
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|