private FileSignInfo TrackFile(string fullPath, ImmutableArray <byte> contentHash, bool isNested) { var fileSignInfo = ExtractSignInfo(fullPath, contentHash); var key = new SignedFileContentKey(contentHash, Path.GetFileName(fullPath)); if (_filesByContentKey.TryGetValue(key, out var existingSignInfo)) { // If we saw this file already we wouldn't call TrackFile unless this is a top-level file. Debug.Assert(!isNested); // Copy the signed content to the destination path. _filesToCopy.Add(new KeyValuePair <string, string>(existingSignInfo.FullPath, fullPath)); return(fileSignInfo); } if (FileSignInfo.IsZipContainer(fullPath)) { Debug.Assert(!_zipDataMap.ContainsKey(contentHash)); if (TryBuildZipData(fileSignInfo, out var zipData)) { _zipDataMap[contentHash] = zipData; } } _filesByContentKey.Add(key, fileSignInfo); if (fileSignInfo.SignInfo.ShouldSign || fileSignInfo.IsZipContainer()) { _filesToSign.Add(fileSignInfo); } return(fileSignInfo); }
private FileSignInfo TrackFile( string fullPath, string collisionPriorityId, ImmutableArray <byte> contentHash, bool isNested, bool forceRepack = false, string containerPath = null) { _log.LogMessage($"Tracking file '{fullPath}' isNested={isNested}"); // If there's a wixpack in ItemsToSign which corresponds to this file, pass along the path of // the wixpack so we can associate the wixpack with the item var wixPack = _wixPacks.SingleOrDefault(w => w.Moniker.Equals(Path.GetFileName(fullPath), StringComparison.OrdinalIgnoreCase)); var fileSignInfo = ExtractSignInfo(fullPath, collisionPriorityId, contentHash, forceRepack, wixPack.FullPath, containerPath); var key = new SignedFileContentKey(contentHash, Path.GetFileName(fullPath)); if (_filesByContentKey.TryGetValue(key, out var existingSignInfo)) { // If we saw this file already we wouldn't call TrackFile unless this is a top-level file. Debug.Assert(!isNested); // Copy the signed content to the destination path. _filesToCopy.Add(new KeyValuePair <string, string>(existingSignInfo.FullPath, fullPath)); return(fileSignInfo); } if (fileSignInfo.IsContainer()) { if (_zipDataMap.ContainsKey(contentHash)) { _log.LogError($"File '{fullPath}' has the same content hash as '{_zipDataMap[contentHash].FileSignInfo.FullPath}'. The incorrect file will be written."); } if (fileSignInfo.IsZipContainer()) { if (TryBuildZipData(fileSignInfo, out var zipData)) { _zipDataMap[contentHash] = zipData; } } else if (fileSignInfo.IsWixContainer()) { Console.WriteLine($"Trying to gather data for {fullPath}"); if (TryBuildWixData(fileSignInfo, out var msiData)) { _zipDataMap[contentHash] = msiData; } } } _log.LogMessage(MessageImportance.Low, $"Caching file {key.FileName} {key.StringHash}"); _filesByContentKey.Add(key, fileSignInfo); if (fileSignInfo.SignInfo.ShouldSign || fileSignInfo.ForceRepack || fileSignInfo.IsContainer()) { _filesToSign.Add(fileSignInfo); } return(fileSignInfo); }
internal BatchSignInput GenerateListOfFiles() { Stopwatch gatherInfoTime = Stopwatch.StartNew(); foreach (var itemToSign in _itemsToSign) { string fullPath = itemToSign.ItemSpec; string collisionPriorityId = itemToSign.GetMetadata(SignToolConstants.CollisionPriorityId); var contentHash = ContentUtil.GetContentHash(fullPath); var fileUniqueKey = new SignedFileContentKey(contentHash, Path.GetFileName(fullPath)); if (!_whichPackagesTheFileIsIn.TryGetValue(fileUniqueKey, out var packages)) { packages = new HashSet <string>(); } packages.Add(fullPath); _whichPackagesTheFileIsIn[fileUniqueKey] = packages; PathWithHash pathWithHash = new PathWithHash(fullPath, contentHash); TrackFile(pathWithHash, null, collisionPriorityId); } gatherInfoTime.Stop(); if (_telemetry != null) { _telemetry.AddMetric("Gather file info duration (s)", gatherInfoTime.ElapsedMilliseconds / 1000); } if (_errors.Any()) { // Iterate over each pair of <error code, unique file identity>. // We can be sure here that the same file won't have the same error code twice. foreach (var errorGroup in _errors) { switch (errorGroup.Key) { case SigningToolErrorCode.SIGN002: _log.LogError("Could not determine certificate name for signable file(s):"); break; } // For each file that had that error foreach (var erroredFile in errorGroup.Value) { _log.LogError($"\tFile: {erroredFile.FileName}"); // Get a list of all containers where the file showed up foreach (var containerName in _whichPackagesTheFileIsIn[erroredFile]) { _log.LogError($"\t\t{containerName}"); } } } } return(new BatchSignInput(_filesToSign.ToImmutableArray(), _zipDataMap.ToImmutableDictionary(), _filesToCopy.ToImmutableArray())); }
private void LogError(SigningToolErrorCode code, SignedFileContentKey targetFile) { if (!_errors.TryGetValue(code, out var filesErrored)) { filesErrored = new HashSet <SignedFileContentKey>(); } filesErrored.Add(targetFile); _errors[code] = filesErrored; }
internal FileSignInfo(PathWithHash pathWithHash, SignInfo signInfo, string targetFramework = null, string wixContentFilePath = null, bool hasSignableParts = false) { Debug.Assert(pathWithHash.FullPath != null); Debug.Assert(!pathWithHash.ContentHash.IsDefault && pathWithHash.ContentHash.Length == 256 / 8); Debug.Assert(targetFramework != ""); File = pathWithHash; FileContentKey = new SignedFileContentKey(File.ContentHash, File.FileName); SignInfo = signInfo; TargetFramework = targetFramework; WixContentFilePath = wixContentFilePath; HasSignableParts = hasSignableParts; }
internal BatchSignInput GenerateListOfFiles() { foreach (var itemToSign in _itemsToSign) { string fullPath = itemToSign.ItemSpec; string collisionPriorityId = itemToSign.GetMetadata(SignToolConstants.CollisionPriorityId); var fileUniqueKey = new SignedFileContentKey(ContentUtil.GetContentHash(fullPath), fullPath); if (!_whichPackagesTheFileIsIn.TryGetValue(fileUniqueKey, out var packages)) { packages = new HashSet <string>(); } packages.Add(fullPath); _whichPackagesTheFileIsIn[fileUniqueKey] = packages; TrackFile(fullPath, collisionPriorityId, ContentUtil.GetContentHash(fullPath), isNested: false); } if (_errors.Any()) { // Iterate over each pair of <error code, unique file identity>. // We can be sure here that the same file won't have the same error code twice. foreach (var errorGroup in _errors) { switch (errorGroup.Key) { case SigningToolErrorCode.SIGN002: _log.LogError("Could not determine certificate name for signable file(s):"); break; } // For each file that had that error foreach (var erroredFile in errorGroup.Value) { _log.LogError($"\tFile: {erroredFile.FileName}"); // Get a list of all containers where the file showed up foreach (var containerName in _whichPackagesTheFileIsIn[erroredFile]) { _log.LogError($"\t\t{containerName}"); } } } } return(new BatchSignInput(_filesToSign.ToImmutableArray(), _zipDataMap.ToImmutableDictionary(ByteSequenceComparer.Instance), _filesToCopy.ToImmutableArray())); }
internal FileSignInfo(string fullPath, ImmutableArray <byte> contentHash, SignInfo signInfo, string targetFramework = null, bool forceRepack = false, string wixContentFilePath = null, bool hasSignableParts = false) { Debug.Assert(fullPath != null); Debug.Assert(!contentHash.IsDefault && contentHash.Length == 256 / 8); Debug.Assert(targetFramework != ""); FileName = Path.GetFileName(fullPath); ContentHash = contentHash; FileContentKey = new SignedFileContentKey(contentHash, FileName); FullPath = fullPath; SignInfo = signInfo; TargetFramework = targetFramework; ForceRepack = forceRepack; WixContentFilePath = wixContentFilePath; HasSignableParts = hasSignableParts; }
private FileSignInfo TrackFile(string fullPath, ImmutableArray <byte> contentHash, bool isNested, bool forceRepack = false) { _log.LogMessage($"Tracking file '{fullPath}' isNested={isNested}"); var fileSignInfo = ExtractSignInfo(fullPath, contentHash, forceRepack); var key = new SignedFileContentKey(contentHash, Path.GetFileName(fullPath)); if (_filesByContentKey.TryGetValue(key, out var existingSignInfo)) { // If we saw this file already we wouldn't call TrackFile unless this is a top-level file. Debug.Assert(!isNested); // Copy the signed content to the destination path. _filesToCopy.Add(new KeyValuePair <string, string>(existingSignInfo.FullPath, fullPath)); return(fileSignInfo); } if (FileSignInfo.IsZipContainer(fullPath)) { if (_zipDataMap.ContainsKey(contentHash)) { _log.LogError($"File '{fullPath}' has the same content hash as '{_zipDataMap[contentHash].FileSignInfo.FullPath}'. The incorrect file will be written."); } if (TryBuildZipData(fileSignInfo, out var zipData)) { _zipDataMap[contentHash] = zipData; } } _log.LogMessage(MessageImportance.Low, $"Caching file {key.FileName} {key.StringHash}"); _filesByContentKey.Add(key, fileSignInfo); if (fileSignInfo.SignInfo.ShouldSign || fileSignInfo.ForceRepack || fileSignInfo.IsZipContainer()) { _filesToSign.Add(fileSignInfo); } return(fileSignInfo); }
/// <summary> /// Actually sign all of the described files. /// </summary> private bool SignFiles() { // Generate the list of signed files in a deterministic order. Makes it easier to track down // bugs if repeated runs use the same ordering. var toProcessList = _batchData.FilesToSign.ToList(); var toRepackSet = _batchData.FilesToSign.Where(x => x.ShouldRepack)?.Select(x => x.FullPath)?.ToHashSet(); var round = 0; var trackedSet = new HashSet <SignedFileContentKey>(); // Given a list of files that need signing, sign them in a batch. bool signGroup(IEnumerable <FileSignInfo> files, out int signedCount) { var filesToSign = files.Where(fileInfo => fileInfo.SignInfo.ShouldSign).ToArray(); signedCount = filesToSign.Length; if (filesToSign.Length == 0) { return(true); } _log.LogMessage(MessageImportance.High, $"Round {round}: Signing {filesToSign.Length} files."); foreach (var file in filesToSign) { string collisionIdInfo = string.Empty; if (_hashToCollisionIdMap != null) { if (_hashToCollisionIdMap.TryGetValue(file.FileContentKey, out string collisionPriorityId)) { collisionIdInfo = $"Collision Id='{collisionPriorityId}'"; } } _log.LogMessage(MessageImportance.Low, $"{file} {collisionIdInfo}"); } return(_signTool.Sign(_buildEngine, round, filesToSign)); } // Given a list of files that need signing, sign the installer engines // of those that are wix containers. bool signEngines(IEnumerable <FileSignInfo> files, out int signedCount) { var enginesToSign = files.Where(fileInfo => fileInfo.SignInfo.ShouldSign && fileInfo.IsWixContainer() && Path.GetExtension(fileInfo.FullPath) == ".exe").ToArray(); signedCount = enginesToSign.Length; if (enginesToSign.Length == 0) { return(true); } _log.LogMessage(MessageImportance.High, $"Round {round}: Signing {enginesToSign.Length} engines."); Dictionary <SignedFileContentKey, FileSignInfo> engines = new Dictionary <SignedFileContentKey, FileSignInfo>(); var workingDirectory = Path.Combine(_signTool.TempDir, "engines"); int engineContainer = 0; // extract engines foreach (var file in enginesToSign) { string engineFileName = $"{Path.Combine(workingDirectory, $"{engineContainer}", file.FileName)}{SignToolConstants.MsiEngineExtension}"; _log.LogMessage(MessageImportance.Normal, $"Extracting engine from {file.FullPath}"); if (!RunWixTool("insignia.exe", $"-ib {file.FullPath} -o {engineFileName}", workingDirectory, _signTool.WixToolsPath, _log)) { _log.LogError($"Failed to extract engine from {file.FullPath}"); return(false); } var fileUniqueKey = new SignedFileContentKey(file.ContentHash, engineFileName); engines.Add(fileUniqueKey, file); engineContainer++; } // sign engines bool signResult = _signTool.Sign(_buildEngine, round, engines.Select(engine => new FileSignInfo(new PathWithHash(engine.Key.FileName, engine.Value.ContentHash), engine.Value.SignInfo))); if (!signResult) { _log.LogError($"Failed to sign engines"); return(signResult); } // attach engines foreach (var engine in engines) { _log.LogMessage(MessageImportance.Normal, $"Attaching engine {engine.Key.FileName} to {engine.Value.FullPath}"); try { if (!RunWixTool("insignia.exe", $"-ab {engine.Key.FileName} {engine.Value.FullPath} -o {engine.Value.FullPath}", workingDirectory, _signTool.WixToolsPath, _log)) { _log.LogError($"Failed to attach engine to {engine.Value.FullPath}"); return(false); } } finally { // cleanup engines (they fail signing verification if they stay in the drop File.Delete(engine.Key.FileName); } } return(true); } // Given a group of file that are ready for processing, // repack those files that are containers. void repackGroup(IEnumerable <FileSignInfo> files, out int repackCount) { var repackList = files.Where(w => toRepackSet.Contains(w.FullPath)).ToList(); repackCount = repackList.Count(); if (repackCount == 0) { return; } _log.LogMessage(MessageImportance.High, $"Repacking {repackCount} containers."); ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 16; Parallel.ForEach(repackList, parallelOptions, file => { if (file.IsZipContainer()) { _log.LogMessage($"Repacking container: '{file.FileName}'"); _batchData.ZipDataMap[file.FileContentKey].Repack(_log); } else if (file.IsWixContainer()) { _log.LogMessage($"Packing wix container: '{file.FileName}'"); _batchData.ZipDataMap[file.FileContentKey].Repack(_log, _signTool.TempDir, _signTool.WixToolsPath); } else { _log.LogError($"Don't know how to repack file '{file.FullPath}'"); } toRepackSet.Remove(file.FullPath); }); } // Is this file ready to be signed or repackaged? That is are all of the items that it depends on already // signed, don't need signing, and are repacked. bool isReady(FileSignInfo file) { if (file.IsContainer()) { var zipData = _batchData.ZipDataMap[file.FileContentKey]; return(zipData.NestedParts.Values.All(x => (!x.FileSignInfo.SignInfo.ShouldSign || trackedSet.Contains(x.FileSignInfo.FileContentKey)) && !toRepackSet.Contains(x.FileSignInfo.FullPath) )); } return(true); } // Identify the next set of files that should be signed or repacked. // This is the set of files for which all of the dependencies have been signed, // are already signed, are repacked, etc. List <FileSignInfo> identifyNextGroup() { var list = new List <FileSignInfo>(); var i = 0; while (i < toProcessList.Count) { var current = toProcessList[i]; if (isReady(current)) { list.Add(current); toProcessList.RemoveAt(i); } else { i++; } } return(list); } // Telemetry data double telemetryTotalFilesSigned = 0; double telemetryTotalFilesRepacked = 0; Stopwatch telemetrySignedTime = new Stopwatch(); Stopwatch telemetryRepackedTime = new Stopwatch(); try { // Core algorithm of batch signing. // While there are files left to process, // Identify which files are ready for processing (ready to repack or sign) // Repack those of that set that are containers // Sign any of those files that need signing, along with their engines. while (toProcessList.Count > 0) { var trackList = identifyNextGroup(); if (trackList.Count == 0) { throw new InvalidOperationException("No progress made on signing which indicates a bug"); } int fileModifiedCount; telemetryRepackedTime.Start(); repackGroup(trackList, out fileModifiedCount); telemetryRepackedTime.Stop(); telemetryTotalFilesRepacked += fileModifiedCount; try { telemetrySignedTime.Start(); if (!signEngines(trackList, out fileModifiedCount)) { return(false); } if (fileModifiedCount > 0) { round++; telemetryTotalFilesSigned += fileModifiedCount; } if (!signGroup(trackList, out fileModifiedCount)) { return(false); } if (fileModifiedCount > 0) { round++; telemetryTotalFilesSigned += fileModifiedCount; } } finally { telemetrySignedTime.Stop(); } trackList.ForEach(x => trackedSet.Add(x.FileContentKey)); } } finally { if (_telemetry != null) { _telemetry.AddMetric("Signed file count", telemetryTotalFilesSigned); _telemetry.AddMetric("Repacked file count", telemetryTotalFilesRepacked); _telemetry.AddMetric("Signing duration (s)", telemetrySignedTime.ElapsedMilliseconds / 1000); _telemetry.AddMetric("Repacking duration (s)", telemetryRepackedTime.ElapsedMilliseconds / 1000); } } return(true); }
/// <summary> /// Build up the <see cref="ZipData"/> instance for a given zip container. This will also report any consistency /// errors found when examining the zip archive. /// </summary> private bool TryBuildZipData(FileSignInfo zipFileSignInfo, out ZipData zipData) { Debug.Assert(zipFileSignInfo.IsZipContainer()); try { using (var archive = new ZipArchive(File.OpenRead(zipFileSignInfo.FullPath), ZipArchiveMode.Read)) { var nestedParts = new List <ZipPart>(); foreach (ZipArchiveEntry entry in archive.Entries) { string relativePath = entry.FullName; // `entry` might be just a pointer to a folder. We skip those. if (relativePath.EndsWith("/") && entry.Name == "") { continue; } ImmutableArray <byte> contentHash; using (var stream = entry.Open()) { contentHash = ContentUtil.GetContentHash(stream); } var fileUniqueKey = new SignedFileContentKey(contentHash, relativePath); if (!_whichPackagesTheFileIsIn.TryGetValue(fileUniqueKey, out var packages)) { packages = new HashSet <string>(); } packages.Add(zipFileSignInfo.FileName); _whichPackagesTheFileIsIn[fileUniqueKey] = packages; // if we already encountered file that hash the same content we can reuse its signed version when repackaging the container. var fileName = Path.GetFileName(relativePath); if (!_filesByContentKey.TryGetValue(new SignedFileContentKey(contentHash, fileName), out var fileSignInfo)) { string extractPathRoot = _useHashInExtractionPath ? ContentUtil.HashToString(contentHash) : _filesByContentKey.Count().ToString(); string tempPath = Path.Combine(_pathToContainerUnpackingDirectory, extractPathRoot, relativePath); _log.LogMessage($"Extracting file '{fileName}' from '{zipFileSignInfo.FullPath}' to '{tempPath}'."); Directory.CreateDirectory(Path.GetDirectoryName(tempPath)); using (var stream = entry.Open()) using (var tempFileStream = File.OpenWrite(tempPath)) { stream.CopyTo(tempFileStream); } fileSignInfo = TrackFile(tempPath, contentHash, isNested: true); } if (fileSignInfo.SignInfo.ShouldSign || fileSignInfo.ForceRepack) { nestedParts.Add(new ZipPart(relativePath, fileSignInfo)); } } zipData = new ZipData(zipFileSignInfo, nestedParts.ToImmutableArray()); return(true); } } catch (Exception e) { _log.LogErrorFromException(e); zipData = null; return(false); } }
/// <summary> /// Build up the <see cref="ZipData"/> instance for a given zip container. This will also report any consistency /// errors found when examining the zip archive. /// </summary> private bool TryBuildZipData(FileSignInfo zipFileSignInfo, out ZipData zipData, string alternativeArchivePath = null) { string archivePath = zipFileSignInfo.FullPath; if (alternativeArchivePath != null) { archivePath = alternativeArchivePath; Debug.Assert(Path.GetExtension(archivePath) == ".zip"); } else { Debug.Assert(zipFileSignInfo.IsZipContainer()); } try { using (var archive = new ZipArchive(File.OpenRead(archivePath), ZipArchiveMode.Read)) { var nestedParts = new Dictionary <string, ZipPart>(); foreach (ZipArchiveEntry entry in archive.Entries) { string relativePath = entry.FullName; // `entry` might be just a pointer to a folder. We skip those. if (relativePath.EndsWith("/") && entry.Name == "") { continue; } using (var entryStream = entry.Open()) using (MemoryStream entryMemoryStream = new MemoryStream((int)entry.Length)) { entryStream.CopyTo(entryMemoryStream); entryMemoryStream.Position = 0; ImmutableArray <byte> contentHash = ContentUtil.GetContentHash(entryMemoryStream); var fileUniqueKey = new SignedFileContentKey(contentHash, Path.GetFileName(relativePath)); if (!_whichPackagesTheFileIsIn.TryGetValue(fileUniqueKey, out var packages)) { packages = new HashSet <string>(); } packages.Add(Path.GetFileName(archivePath)); _whichPackagesTheFileIsIn[fileUniqueKey] = packages; // if we already encountered file that has the same content we can reuse its signed version when repackaging the container. var fileName = Path.GetFileName(relativePath); if (!_filesByContentKey.TryGetValue(fileUniqueKey, out var fileSignInfo)) { string extractPathRoot = _useHashInExtractionPath ? fileUniqueKey.StringHash : _filesByContentKey.Count().ToString(); string tempPath = Path.Combine(_pathToContainerUnpackingDirectory, extractPathRoot, relativePath); _log.LogMessage($"Extracting file '{fileName}' from '{archivePath}' to '{tempPath}'."); Directory.CreateDirectory(Path.GetDirectoryName(tempPath)); entryMemoryStream.Position = 0; using (var tempFileStream = File.OpenWrite(tempPath)) { entryMemoryStream.CopyTo(tempFileStream); } _hashToCollisionIdMap.TryGetValue(fileUniqueKey, out string collisionPriorityId); PathWithHash nestedFile = new PathWithHash(tempPath, contentHash); fileSignInfo = TrackFile(nestedFile, zipFileSignInfo.File, collisionPriorityId); } if (fileSignInfo.ShouldTrack) { nestedParts.Add(relativePath, new ZipPart(relativePath, fileSignInfo)); } } } zipData = new ZipData(zipFileSignInfo, nestedParts.ToImmutableDictionary()); return(true); } } catch (Exception e) { _log.LogErrorFromException(e); zipData = null; return(false); } }
/// <summary> /// Determine the file signing info of this file. /// </summary> /// <param name="fullPath">Full path to the file</param> /// <param name="collisionPriorityId">ID used to disambiguate file signing info for nested files.</param> /// <param name="contentHash">Content hash of the file</param> /// <param name="wixContentFilePath">If a wix container, the corresponding wix pack zip</param> /// <param name="parentContainerPath">Path to the parent container. If this is a non-nested container, this should be null</param> /// <param name="parentContainerHash">Hash of the parent container. If this is a non-nested container, this should be null</param> /// <returns>File signing information for this file.</returns> private FileSignInfo ExtractSignInfo( PathWithHash file, PathWithHash parentContainer, string collisionPriorityId, string wixContentFilePath) { var extension = Path.GetExtension(file.FileName); string explicitCertificateName = null; var fileSpec = string.Empty; var isAlreadySigned = false; var matchedNameTokenFramework = false; var matchedNameToken = false; var matchedName = false; PEInfo peInfo = null; SignedFileContentKey signedFileContentKey = new SignedFileContentKey(file.ContentHash, file.FileName); // handle multi-part extensions like ".symbols.nupkg" specified in FileExtensionSignInfo if (_fileExtensionSignInfo != null) { extension = _fileExtensionSignInfo.OrderByDescending(o => o.Key.Length).FirstOrDefault(f => file.FileName.EndsWith(f.Key, StringComparison.OrdinalIgnoreCase)).Key ?? extension; } // Asset is nested asset part of a container. Try to get it from the visited assets first if (string.IsNullOrEmpty(collisionPriorityId) && parentContainer != null) { if (!_hashToCollisionIdMap.TryGetValue(signedFileContentKey, out collisionPriorityId)) { Debug.Assert(parentContainer.FullPath != file.FullPath); // Hash doesn't exist so we use the CollisionPriorityId from the parent container SignedFileContentKey parentSignedFileContentKey = new SignedFileContentKey(parentContainer.ContentHash, parentContainer.FileName); collisionPriorityId = _hashToCollisionIdMap[parentSignedFileContentKey]; } } // Update the hash map if (!_hashToCollisionIdMap.ContainsKey(signedFileContentKey)) { _hashToCollisionIdMap.Add(signedFileContentKey, collisionPriorityId); } else { string existingCollisionId = _hashToCollisionIdMap[signedFileContentKey]; // If we find that there is an asset which already was processed which has a lower // collision id, we use that and update the map so we give it precedence if (string.Compare(collisionPriorityId, existingCollisionId) < 0) { _hashToCollisionIdMap[signedFileContentKey] = collisionPriorityId; } } // Try to determine default certificate name by the extension of the file. Since there might be dupes // we get the one which maps a collision id or the first of the returned ones in case there is no // collision id bool hasSignInfos = _fileExtensionSignInfo.TryGetValue(extension, out var signInfos); SignInfo signInfo = SignInfo.Ignore; bool hasSignInfo = false; if (hasSignInfos) { if (!string.IsNullOrEmpty(collisionPriorityId)) { hasSignInfo = signInfos.Where(s => s.CollisionPriorityId == collisionPriorityId).Any(); signInfo = signInfos.Where(s => s.CollisionPriorityId == collisionPriorityId).FirstOrDefault(); } else { hasSignInfo = true; signInfo = signInfos.FirstOrDefault(); } } if (FileSignInfo.IsPEFile(file.FullPath)) { using (var stream = File.OpenRead(file.FullPath)) { isAlreadySigned = ContentUtil.IsAuthenticodeSigned(stream); } peInfo = GetPEInfo(file.FullPath); if (peInfo.IsManaged && _strongNameInfo.TryGetValue(peInfo.PublicKeyToken, out var pktBasedSignInfos)) { // Get the default sign info based on the PKT, if applicable. Since there might be dupes // we get the one which maps a collision id or the first of the returned ones in case there is no // collision id SignInfo pktBasedSignInfo = SignInfo.Ignore; if (!string.IsNullOrEmpty(collisionPriorityId)) { pktBasedSignInfo = pktBasedSignInfos.Where(s => s.CollisionPriorityId == collisionPriorityId).FirstOrDefault(); } else { pktBasedSignInfo = pktBasedSignInfos.FirstOrDefault(); } if (peInfo.IsCrossgened) { signInfo = new SignInfo(pktBasedSignInfo.Certificate, collisionPriorityId: _hashToCollisionIdMap[signedFileContentKey]); } else { signInfo = pktBasedSignInfo; } hasSignInfo = true; } // Check if we have more specific sign info: matchedNameTokenFramework = _fileSignInfo.TryGetValue( new ExplicitCertificateKey(file.FileName, peInfo.PublicKeyToken, peInfo.TargetFramework, _hashToCollisionIdMap[signedFileContentKey]), out explicitCertificateName); matchedNameToken = !matchedNameTokenFramework && _fileSignInfo.TryGetValue( new ExplicitCertificateKey(file.FileName, peInfo.PublicKeyToken, collisionPriorityId: _hashToCollisionIdMap[signedFileContentKey]), out explicitCertificateName); fileSpec = matchedNameTokenFramework ? $" (PublicKeyToken = {peInfo.PublicKeyToken}, Framework = {peInfo.TargetFramework})" : matchedNameToken ? $" (PublicKeyToken = {peInfo.PublicKeyToken})" : string.Empty; } else if (FileSignInfo.IsNupkg(file.FullPath) || FileSignInfo.IsVsix(file.FullPath)) { isAlreadySigned = VerifySignatures.IsSignedContainer(file.FullPath); if (!isAlreadySigned) { _log.LogMessage(MessageImportance.Low, $"Container {file.FullPath} does not have a signature marker."); } else { _log.LogMessage(MessageImportance.Low, $"Container {file.FullPath} has a signature marker."); } } else if (FileSignInfo.IsWix(file.FullPath)) { isAlreadySigned = VerifySignatures.IsDigitallySigned(file.FullPath); if (!isAlreadySigned) { _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is not digitally signed."); } else { _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is digitally signed."); } } else if (FileSignInfo.IsPowerShellScript(file.FullPath)) { isAlreadySigned = VerifySignatures.VerifySignedPowerShellFile(file.FullPath); if (!isAlreadySigned) { _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} does not have a signature block."); } else { _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} has a signature block."); } } // We didn't find any specific information for PE files using PKT + TargetFramework if (explicitCertificateName == null) { matchedName = _fileSignInfo.TryGetValue(new ExplicitCertificateKey(file.FileName, collisionPriorityId: _hashToCollisionIdMap[signedFileContentKey]), out explicitCertificateName); } // If has overriding info, is it for ignoring the file? if (SignToolConstants.IgnoreFileCertificateSentinel.Equals(explicitCertificateName, StringComparison.OrdinalIgnoreCase)) { _log.LogMessage(MessageImportance.Low, $"File configured to not be signed: {file.FullPath}{fileSpec}"); return(new FileSignInfo(file, SignInfo.Ignore)); } // Do we have an explicit certificate after all? if (explicitCertificateName != null) { signInfo = signInfo.WithCertificateName(explicitCertificateName, _hashToCollisionIdMap[signedFileContentKey]); hasSignInfo = true; } if (hasSignInfo) { bool dualCerts = _dualCertificates .Where(d => d.ItemSpec == signInfo.Certificate && (d.GetMetadata(SignToolConstants.CollisionPriorityId) == "" || d.GetMetadata(SignToolConstants.CollisionPriorityId) == _hashToCollisionIdMap[signedFileContentKey])).Any(); if (isAlreadySigned && !dualCerts) { return(new FileSignInfo(file, signInfo.WithIsAlreadySigned(isAlreadySigned), wixContentFilePath: wixContentFilePath)); } // TODO: implement this check for native PE files as well: // extract copyright from native resource (.rsrc section) if (signInfo.ShouldSign && peInfo != null && peInfo.IsManaged) { bool isMicrosoftLibrary = IsMicrosoftLibrary(peInfo.Copyright); bool isMicrosoftCertificate = !IsThirdPartyCertificate(signInfo.Certificate); if (isMicrosoftLibrary != isMicrosoftCertificate) { if (isMicrosoftLibrary) { LogWarning(SigningToolErrorCode.SIGN001, $"Signing Microsoft library '{file.FullPath}' with 3rd party certificate '{signInfo.Certificate}'. The library is considered Microsoft library due to its copyright: '{peInfo.Copyright}'."); } else { LogWarning(SigningToolErrorCode.SIGN001, $"Signing 3rd party library '{file.FullPath}' with Microsoft certificate '{signInfo.Certificate}'. The library is considered 3rd party library due to its copyright: '{peInfo.Copyright}'."); } } } return(new FileSignInfo(file, signInfo, (peInfo != null && peInfo.TargetFramework != "") ? peInfo.TargetFramework : null, wixContentFilePath: wixContentFilePath)); } if (SignToolConstants.SignableExtensions.Contains(extension) || SignToolConstants.SignableOSXExtensions.Contains(extension)) { // Extract the relative path inside the package / otherwise just return the full path of the file LogError(SigningToolErrorCode.SIGN002, signedFileContentKey); } else { _log.LogMessage(MessageImportance.Low, $"Ignoring non-signable file: {file.FullPath}"); } return(new FileSignInfo(file, SignInfo.Ignore, wixContentFilePath: wixContentFilePath)); }