From d2e7f7b1895d3c375bffd8dced2f289085e987f2 Mon Sep 17 00:00:00 2001 From: Henrique Feliciano de Azevedo Date: Wed, 25 Jun 2025 02:03:34 -0300 Subject: [PATCH 1/3] fix(SignCommand): Correctly resolve absolute glob paths Refactors path resolution to support absolute glob patterns. --- src/AzureSignTool/AzureSignTool.csproj | 70 +++++----- src/AzureSignTool/Program.cs | 91 ++++++++---- test/AzureSignTool.Tests/SignCommandTests.cs | 137 +++++++++++++++++++ 3 files changed, 241 insertions(+), 57 deletions(-) create mode 100644 test/AzureSignTool.Tests/SignCommandTests.cs diff --git a/src/AzureSignTool/AzureSignTool.csproj b/src/AzureSignTool/AzureSignTool.csproj index 6e5b588..c3ce5f0 100644 --- a/src/AzureSignTool/AzureSignTool.csproj +++ b/src/AzureSignTool/AzureSignTool.csproj @@ -1,36 +1,42 @@ - + - - Exe - net8.0 - true - azuresigntool - Azure Sign Tool is similar to signtool in the Windows SDK, with the major difference being that it uses Azure Key Vault for performing the signing process. The usage is like signtool, except with a limited set of options for signing and options for authenticating to Azure Key Vault. - win-x64;win-arm64;win-x86 - v - true - true - false - false - Size - MIT - + + Exe + net8.0 + true + azuresigntool + Azure Sign Tool is similar to signtool in the Windows SDK, with the major difference being that it uses Azure Key Vault for performing the signing process. The usage is like signtool, except with a limited set of options for signing and options for authenticating to Azure Key Vault. + win-x64;win-arm64;win-x86 + v + true + true + false + false + Size + MIT + - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - - - + + + + + + + <_Parameter1>AzureSignTool.Tests + + diff --git a/src/AzureSignTool/Program.cs b/src/AzureSignTool/Program.cs index a2cfc85..b170427 100644 --- a/src/AzureSignTool/Program.cs +++ b/src/AzureSignTool/Program.cs @@ -64,8 +64,8 @@ static string GetVersion() internal sealed class SignCommand : Command { private HashSet? _allFiles; - private List Files { get; set; } = []; + internal List Files { get; set; } = []; internal string? KeyVaultUrl { get; set; } internal string? KeyVaultClientId { get; set; } internal string? KeyVaultClientSecret { get; set; } @@ -100,52 +100,76 @@ internal HashSet AllFiles if (_allFiles is null) { _allFiles = []; - Matcher matcher = new(); - - foreach (string file in Files) - { - Add(_allFiles, matcher, file); - } - + List files = [..Files]; if (!string.IsNullOrWhiteSpace(InputFileList)) { - foreach(string line in File.ReadLines(InputFileList)) + foreach (string line in File.ReadLines(InputFileList)) { if (string.IsNullOrWhiteSpace(line)) { continue; } - Add(_allFiles, matcher, line); + files.Add(line); } } - PatternMatchingResult results = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo("."))); + List absGlobs = []; + Matcher relMatcher = new(); - if (results.HasMatches) + foreach (var file in files) { - foreach (var result in results.Files) + // We require explicit glob pattern wildcards in order to treat it as a glob. e.g. + // dir/ will not be treated as a directory. It must be explicitly dir/*.exe or dir/**/*.exe, for example. + if (file.Contains('*')) { - _allFiles.Add(result.Path); + if (Path.IsPathRooted(file)) + { + absGlobs.Add(file); + } + else + { + relMatcher.AddInclude(file); + } + } + else + { + _allFiles.Add(file); } } - } - - return _allFiles; - static void Add(HashSet collection, Matcher matcher, string item) - { - // We require explicit glob pattern wildcards in order to treat it as a glob. e.g. - // dir/ will not be treated as a directory. It must be explicitly dir/*.exe or dir/**/*.exe, for example. - if (item.Contains('*')) + PatternMatchingResult relResults = relMatcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo("."))); + if (relResults.HasMatches) { - matcher.AddInclude(item); + foreach (var result in relResults.Files) + { + _allFiles.Add(result.Path); + } } - else + + foreach (string absGlob in absGlobs) { - collection.Add(item); + string rootDir = GetPathRoot(absGlob); + if (!Directory.Exists(rootDir)) + { + continue; + } + + var absMatcher = new Matcher(StringComparison.OrdinalIgnoreCase); + absMatcher.AddInclude(absGlob.Replace(rootDir, "")); + + var absoluteResults = absMatcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(rootDir))); + if (absoluteResults.HasMatches) + { + foreach (var match in absoluteResults.Files) + { + _allFiles.Add(Path.GetFullPath(Path.Combine(rootDir, match.Path))); + } + } } } + + return _allFiles; } } @@ -618,6 +642,23 @@ private static bool OneTrue(params bool[] values) return count == 1; } + private static string GetPathRoot(string fullPathPattern) + { + int firstWildcardIndex = fullPathPattern.IndexOf('*'); + if (firstWildcardIndex == -1) + { + return string.Empty; + } + + int lastSeparatorIndex = fullPathPattern.LastIndexOfAny([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar], firstWildcardIndex); + if (lastSeparatorIndex == -1) + { + return Path.GetPathRoot(fullPathPattern) ?? string.Empty; + } + + return fullPathPattern[..lastSeparatorIndex]; + } + private static readonly string[] s_hashAlgorithm = ["SHA1", "SHA256", "SHA384", "SHA512"]; } } diff --git a/test/AzureSignTool.Tests/SignCommandTests.cs b/test/AzureSignTool.Tests/SignCommandTests.cs new file mode 100644 index 0000000..71fcb51 --- /dev/null +++ b/test/AzureSignTool.Tests/SignCommandTests.cs @@ -0,0 +1,137 @@ +using System; +using System.IO; +using Xunit; + +namespace AzureSignTool.Tests; + +public class SignCommandTests +{ + [Fact] + public void AllFiles_WithAbsoluteGlobPath_FindsFileCorrectly() + { + var tempDirectory = Path.Combine(Path.GetTempPath(), $"absolute-glob-test-{Guid.NewGuid()}"); + Directory.CreateDirectory(tempDirectory); + var testFilePath = Path.Combine(tempDirectory, "file-to-sign.txt"); + File.WriteAllText(testFilePath, "content"); + + var command = new SignCommand(); + var absoluteGlobPattern = Path.Combine(tempDirectory, "**", "*.txt"); + command.Files.Add(absoluteGlobPattern); + + try + { + var foundFiles = command.AllFiles; + var foundFile = Assert.Single(foundFiles); + Assert.Equal(Path.GetFullPath(testFilePath), foundFile, ignoreCase: true); + } + finally + { + if (Directory.Exists(tempDirectory)) + Directory.Delete(tempDirectory, recursive: true); + } + } + + [Fact] + public void AllFiles_WithSingleAbsoluteExistingFile_ReturnsOneFile() + { + var tempFilePath = Path.Combine(Path.GetTempPath(), $"single-file-test-{Guid.NewGuid()}.tmp"); + File.WriteAllText(tempFilePath, "content"); + + var command = new SignCommand(); + command.Files.Add(tempFilePath); + + try + { + var foundFiles = command.AllFiles; + var foundFile = Assert.Single(foundFiles); + Assert.Equal(Path.GetFullPath(tempFilePath), foundFile, ignoreCase: true); + } + finally + { + if (File.Exists(tempFilePath)) + File.Delete(tempFilePath); + } + } + + [Fact] + public void AllFiles_ShouldIncludeExplicitPath_WhenFileDoesNotExist() + { + var command = new SignCommand(); + var nonExistentFilePath = Path.GetFullPath(Path.Combine("non", "existent", "path", $"file-{Guid.NewGuid()}.dll")); + + command.Files.Add(nonExistentFilePath); + + var foundFiles = command.AllFiles; + + var foundFile = Assert.Single(foundFiles); + Assert.Equal(nonExistentFilePath, foundFile, ignoreCase: true); + } + + [Fact] + public void AllFiles_ShouldIncludeExplicitPath_WhenFileExists() + { + var tempFile = Path.GetTempFileName(); + var command = new SignCommand(); + command.Files.Add(tempFile); + + try + { + var foundFiles = command.AllFiles; + var foundFile = Assert.Single(foundFiles); + Assert.Equal(Path.GetFullPath(tempFile), foundFile, ignoreCase: true); + } + finally + { + if (File.Exists(tempFile)) File.Delete(tempFile); + } + } + + [Fact] + public void AllFiles_ShouldReturnEmpty_WhenGlobMatchesNoFiles() + { + var tempDirectory = Path.Combine(Path.GetTempPath(), $"empty-glob-test-{Guid.NewGuid()}"); + Directory.CreateDirectory(tempDirectory); + + var command = new SignCommand(); + command.Files.Add(Path.Combine(tempDirectory, "*.nomatchtype")); + + try + { + var foundFiles = command.AllFiles; + Assert.Empty(foundFiles); + } + finally + { + if (Directory.Exists(tempDirectory)) + Directory.Delete(tempDirectory, true); + } + } + + [Fact] + public void AllFiles_ShouldReturnCombinedSet_ForMixedInputs() + { + var nonExistentFilePath = Path.GetFullPath(Path.Combine("c:", "path", "to", $"non-existent-file-{Guid.NewGuid()}.txt")); + + var tempDirectory = Path.Combine(Path.GetTempPath(), $"mixed-test-{Guid.NewGuid()}"); + Directory.CreateDirectory(tempDirectory); + var globbedFilePath = Path.Combine(tempDirectory, "app.exe"); + File.WriteAllText(globbedFilePath, "content"); + + var command = new SignCommand(); + command.Files.Add(nonExistentFilePath); + command.Files.Add(Path.Combine(tempDirectory, "*.exe")); + + try + { + var foundFiles = command.AllFiles; + Assert.Equal(2, foundFiles.Count); + Assert.Contains(nonExistentFilePath, foundFiles, StringComparer.OrdinalIgnoreCase); + Assert.Contains(Path.GetFullPath(globbedFilePath), foundFiles, StringComparer.OrdinalIgnoreCase); + } + finally + { + if (Directory.Exists(tempDirectory)) + Directory.Delete(tempDirectory, true); + } + } +} From f91f040ec288d9f1f38eae81560a96a7857512a9 Mon Sep 17 00:00:00 2001 From: Henrique Azevedo Date: Sat, 13 Sep 2025 16:29:30 -0300 Subject: [PATCH 2/3] Update formatting and coding conventions Updated `.editorconfig` to enforce `indent_style = space` and added configurations for `.csproj` files, including `indent_size = 2` and `charset = utf-8`. Ensured `insert_final_newline = true` and `dotnet_sort_system_directives_first = true` for `.cs` and `.vb` files. Reformatted `AzureSignTool.csproj` to align with the updated `.editorconfig` settings. Adjusted indentation and spacing in `` and `` sections for consistency. No functional or behavioral changes were introduced. --- .editorconfig | 6 +++ src/AzureSignTool/AzureSignTool.csproj | 72 +++++++++++++------------- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/.editorconfig b/.editorconfig index 33d602c..11355ce 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,6 +11,12 @@ indent_size = 4 insert_final_newline = true charset = utf-8 ############################### +# Project files (.csproj) # +############################### +[*.csproj] +indent_size = 2 +charset = utf-8 +############################### # .NET Coding Conventions # ############################### [*.{cs,vb}] diff --git a/src/AzureSignTool/AzureSignTool.csproj b/src/AzureSignTool/AzureSignTool.csproj index c3ce5f0..440055d 100644 --- a/src/AzureSignTool/AzureSignTool.csproj +++ b/src/AzureSignTool/AzureSignTool.csproj @@ -1,42 +1,42 @@  - - Exe - net8.0 - true - azuresigntool - Azure Sign Tool is similar to signtool in the Windows SDK, with the major difference being that it uses Azure Key Vault for performing the signing process. The usage is like signtool, except with a limited set of options for signing and options for authenticating to Azure Key Vault. - win-x64;win-arm64;win-x86 - v - true - true - false - false - Size - MIT - + + Exe + net8.0 + true + azuresigntool + Azure Sign Tool is similar to signtool in the Windows SDK, with the major difference being that it uses Azure Key Vault for performing the signing process. The usage is like signtool, except with a limited set of options for signing and options for authenticating to Azure Key Vault. + win-x64;win-arm64;win-x86 + v + true + true + false + false + Size + MIT + - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - - - + + + - - - <_Parameter1>AzureSignTool.Tests - - + + + <_Parameter1>AzureSignTool.Tests + + From 31e3fd4cdac85a16acd3414c3d77f5cfd70a1e01 Mon Sep 17 00:00:00 2001 From: Henrique Azevedo Date: Sat, 13 Sep 2025 16:50:02 -0300 Subject: [PATCH 3/3] Moved IVT from .csproj to properties.cs --- src/AzureSignTool/AzureSignTool.csproj | 6 ------ src/AzureSignTool/Properties.cs | 2 ++ 2 files changed, 2 insertions(+), 6 deletions(-) create mode 100644 src/AzureSignTool/Properties.cs diff --git a/src/AzureSignTool/AzureSignTool.csproj b/src/AzureSignTool/AzureSignTool.csproj index da6f444..6e5b588 100644 --- a/src/AzureSignTool/AzureSignTool.csproj +++ b/src/AzureSignTool/AzureSignTool.csproj @@ -33,10 +33,4 @@ - - - - <_Parameter1>AzureSignTool.Tests - - diff --git a/src/AzureSignTool/Properties.cs b/src/AzureSignTool/Properties.cs new file mode 100644 index 0000000..1a6660d --- /dev/null +++ b/src/AzureSignTool/Properties.cs @@ -0,0 +1,2 @@ +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("AzureSignTool.Tests")]