diff --git a/Arcade.sln b/Arcade.sln index b2eeb0ff6e2..8695cd2388e 100644 --- a/Arcade.sln +++ b/Arcade.sln @@ -35,12 +35,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.SwaggerGen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.GenAPI", "src\Microsoft.DotNet.GenAPI\Microsoft.DotNet.GenAPI.csproj", "{9427265E-C224-4B40-8157-F05529D084D3}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SignCheck", "SignCheck", "{A704E5B2-86A4-4D4F-902D-C10EA18C12DF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.SignCheck", "src\SignCheck\SignCheck\Microsoft.DotNet.SignCheck.csproj", "{AF298985-511F-476A-8F62-79908BE8DF01}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.SignCheckLibrary", "src\SignCheck\Microsoft.SignCheck\Microsoft.DotNet.SignCheckLibrary.csproj", "{2A62655C-1AE0-480E-9F1A-E21F21C1D095}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.SignCheckLibrary", "src\SignCheck\Microsoft.SignCheck\Microsoft.DotNet.SignCheckLibrary.csproj", "{39B69A56-43E9-480D-9A60-8E0917795FE8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.SignCheck", "src\SignCheck\SignCheck\Microsoft.DotNet.SignCheck.csproj", "{40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.SignCheckTask", "src\SignCheck\SignCheckTask\Microsoft.DotNet.SignCheckTask.csproj", "{F6F5E649-F477-44ED-BAB2-11D4F0E87402}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinShimmer", "src\WinShimmer\WinShimmer.csproj", "{39C3FC42-21E8-40BF-AE3F-CB9C4CC226D7}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Build.Tasks.Packaging", "src\Microsoft.DotNet.Build.Tasks.Packaging\src\Microsoft.DotNet.Build.Tasks.Packaging.csproj", "{EFC8C541-1DC4-40DA-B6E5-FBF44F6DD8BC}" @@ -327,26 +327,6 @@ Global {9427265E-C224-4B40-8157-F05529D084D3}.Release|x64.Build.0 = Release|Any CPU {9427265E-C224-4B40-8157-F05529D084D3}.Release|x86.ActiveCfg = Release|Any CPU {9427265E-C224-4B40-8157-F05529D084D3}.Release|x86.Build.0 = Release|Any CPU - {AF298985-511F-476A-8F62-79908BE8DF01}.Debug|Any CPU.ActiveCfg = Debug|x86 - {AF298985-511F-476A-8F62-79908BE8DF01}.Debug|Any CPU.Build.0 = Debug|x86 - {AF298985-511F-476A-8F62-79908BE8DF01}.Debug|x64.ActiveCfg = Debug|x86 - {AF298985-511F-476A-8F62-79908BE8DF01}.Debug|x86.ActiveCfg = Debug|x86 - {AF298985-511F-476A-8F62-79908BE8DF01}.Debug|x86.Build.0 = Debug|x86 - {AF298985-511F-476A-8F62-79908BE8DF01}.Release|Any CPU.ActiveCfg = Release|x86 - {AF298985-511F-476A-8F62-79908BE8DF01}.Release|Any CPU.Build.0 = Release|x86 - {AF298985-511F-476A-8F62-79908BE8DF01}.Release|x64.ActiveCfg = Release|x86 - {AF298985-511F-476A-8F62-79908BE8DF01}.Release|x86.ActiveCfg = Release|x86 - {AF298985-511F-476A-8F62-79908BE8DF01}.Release|x86.Build.0 = Release|x86 - {39B69A56-43E9-480D-9A60-8E0917795FE8}.Debug|Any CPU.ActiveCfg = Debug|x86 - {39B69A56-43E9-480D-9A60-8E0917795FE8}.Debug|Any CPU.Build.0 = Debug|x86 - {39B69A56-43E9-480D-9A60-8E0917795FE8}.Debug|x64.ActiveCfg = Debug|x86 - {39B69A56-43E9-480D-9A60-8E0917795FE8}.Debug|x86.ActiveCfg = Debug|x86 - {39B69A56-43E9-480D-9A60-8E0917795FE8}.Debug|x86.Build.0 = Debug|x86 - {39B69A56-43E9-480D-9A60-8E0917795FE8}.Release|Any CPU.ActiveCfg = Release|x86 - {39B69A56-43E9-480D-9A60-8E0917795FE8}.Release|Any CPU.Build.0 = Release|x86 - {39B69A56-43E9-480D-9A60-8E0917795FE8}.Release|x64.ActiveCfg = Release|x86 - {39B69A56-43E9-480D-9A60-8E0917795FE8}.Release|x86.ActiveCfg = Release|x86 - {39B69A56-43E9-480D-9A60-8E0917795FE8}.Release|x86.Build.0 = Release|x86 {39C3FC42-21E8-40BF-AE3F-CB9C4CC226D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {39C3FC42-21E8-40BF-AE3F-CB9C4CC226D7}.Debug|Any CPU.Build.0 = Debug|Any CPU {39C3FC42-21E8-40BF-AE3F-CB9C4CC226D7}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -971,6 +951,42 @@ Global {1F5118A8-A5C5-4D18-AF34-FFB60FECCD45}.Release|x64.Build.0 = Release|Any CPU {1F5118A8-A5C5-4D18-AF34-FFB60FECCD45}.Release|x86.ActiveCfg = Release|Any CPU {1F5118A8-A5C5-4D18-AF34-FFB60FECCD45}.Release|x86.Build.0 = Release|Any CPU + {2A62655C-1AE0-480E-9F1A-E21F21C1D095}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A62655C-1AE0-480E-9F1A-E21F21C1D095}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A62655C-1AE0-480E-9F1A-E21F21C1D095}.Debug|x64.ActiveCfg = Debug|Any CPU + {2A62655C-1AE0-480E-9F1A-E21F21C1D095}.Debug|x64.Build.0 = Debug|Any CPU + {2A62655C-1AE0-480E-9F1A-E21F21C1D095}.Debug|x86.ActiveCfg = Debug|Any CPU + {2A62655C-1AE0-480E-9F1A-E21F21C1D095}.Debug|x86.Build.0 = Debug|Any CPU + {2A62655C-1AE0-480E-9F1A-E21F21C1D095}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A62655C-1AE0-480E-9F1A-E21F21C1D095}.Release|Any CPU.Build.0 = Release|Any CPU + {2A62655C-1AE0-480E-9F1A-E21F21C1D095}.Release|x64.ActiveCfg = Release|Any CPU + {2A62655C-1AE0-480E-9F1A-E21F21C1D095}.Release|x64.Build.0 = Release|Any CPU + {2A62655C-1AE0-480E-9F1A-E21F21C1D095}.Release|x86.ActiveCfg = Release|Any CPU + {2A62655C-1AE0-480E-9F1A-E21F21C1D095}.Release|x86.Build.0 = Release|Any CPU + {40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}.Debug|x64.ActiveCfg = Debug|Any CPU + {40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}.Debug|x64.Build.0 = Debug|Any CPU + {40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}.Debug|x86.ActiveCfg = Debug|Any CPU + {40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}.Debug|x86.Build.0 = Debug|Any CPU + {40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}.Release|Any CPU.Build.0 = Release|Any CPU + {40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}.Release|x64.ActiveCfg = Release|Any CPU + {40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}.Release|x64.Build.0 = Release|Any CPU + {40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}.Release|x86.ActiveCfg = Release|Any CPU + {40CB2592-3B06-4D0D-9878-7DFD5C4B3E9E}.Release|x86.Build.0 = Release|Any CPU + {F6F5E649-F477-44ED-BAB2-11D4F0E87402}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F6F5E649-F477-44ED-BAB2-11D4F0E87402}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F6F5E649-F477-44ED-BAB2-11D4F0E87402}.Debug|x64.ActiveCfg = Debug|Any CPU + {F6F5E649-F477-44ED-BAB2-11D4F0E87402}.Debug|x64.Build.0 = Debug|Any CPU + {F6F5E649-F477-44ED-BAB2-11D4F0E87402}.Debug|x86.ActiveCfg = Debug|Any CPU + {F6F5E649-F477-44ED-BAB2-11D4F0E87402}.Debug|x86.Build.0 = Debug|Any CPU + {F6F5E649-F477-44ED-BAB2-11D4F0E87402}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F6F5E649-F477-44ED-BAB2-11D4F0E87402}.Release|Any CPU.Build.0 = Release|Any CPU + {F6F5E649-F477-44ED-BAB2-11D4F0E87402}.Release|x64.ActiveCfg = Release|Any CPU + {F6F5E649-F477-44ED-BAB2-11D4F0E87402}.Release|x64.Build.0 = Release|Any CPU + {F6F5E649-F477-44ED-BAB2-11D4F0E87402}.Release|x86.ActiveCfg = Release|Any CPU + {F6F5E649-F477-44ED-BAB2-11D4F0E87402}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -982,8 +998,6 @@ Global {FF9DA4D9-3A84-441B-8AAE-B73B6D1EE8B3} = {BB99CE55-AD68-484A-9474-B8BACED247AB} {697EB593-3277-4C43-B8F3-C578E21B051B} = {BB99CE55-AD68-484A-9474-B8BACED247AB} {4FA6B420-53A3-4956-9C02-8649A720A9F5} = {BB99CE55-AD68-484A-9474-B8BACED247AB} - {AF298985-511F-476A-8F62-79908BE8DF01} = {A704E5B2-86A4-4D4F-902D-C10EA18C12DF} - {39B69A56-43E9-480D-9A60-8E0917795FE8} = {A704E5B2-86A4-4D4F-902D-C10EA18C12DF} {F23ED302-7451-4D40-85C3-FACFCF884661} = {C53DD924-C212-49EA-9BC4-1827421361EF} {BA4250FD-9258-4469-A16D-48A0CAF5A2F8} = {C53DD924-C212-49EA-9BC4-1827421361EF} {DC0D0EE5-1C0C-4D3E-BA0B-D6A59717DA94} = {6BE49638-F842-4329-BE8A-30C0F30CCAA5} diff --git a/eng/common/sdk-task.sh b/eng/common/sdk-task.sh new file mode 100755 index 00000000000..b9b9e58db9a --- /dev/null +++ b/eng/common/sdk-task.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash + +show_usage() { + echo "Common settings:" + echo " --task Name of Arcade task (name of a project in SdkTasks directory of the Arcade SDK package)" + echo " --restore Restore dependencies" + echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]" + echo " --help Print help and exit" + echo "" + echo "Command line arguments not listed above are passed thru to msbuild." +} + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +Build() { + local target=$1 + local log_suffix="" + [[ "$target" != "Execute" ]] && log_suffix=".$target" + local log="$log_dir/$task$log_suffix.binlog" + local output_path="$toolset_dir/$task/" + + MSBuild "$taskProject" \ + /bl:"$log" \ + /t:"$target" \ + /p:Configuration="$configuration" \ + /p:RepoRoot="$repo_root" \ + /p:BaseIntermediateOutputPath="$output_path" \ + /v:"$verbosity" \ + $properties +} + +configuration="Debug" +verbosity="minimal" +restore=false +help=false +properties='' + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --task) + task=$2 + shift 2 + ;; + --restore) + restore=true + shift 1 + ;; + --verbosity) + verbosity=$2 + shift 2 + ;; + --help) + help=true + shift 1 + ;; + *) + properties="$properties $1" + shift 1 + ;; + esac +done + +ci=true +binaryLog=true +warnAsError=true + +if $help; then + show_usage + exit 0 +fi + +. "$scriptroot/tools.sh" +InitializeToolset + +if [[ -z "$task" ]]; then + Write-PipelineTelemetryError -Category 'Task' -Name 'MissingTask' -Message "Missing required parameter '-task '" + ExitWithExitCode 1 +fi + +taskProject=$(GetSdkTaskProject "$task") +if [[ ! -e "$taskProject" ]]; then + Write-PipelineTelemetryError -Category 'Task' -Name 'UnknownTask' -Message "Unknown task: $task" + ExitWithExitCode 1 +fi + +if $restore; then + Build "Restore" +fi + +Build "Execute" + + +ExitWithExitCode 0 diff --git a/eng/common/tools.sh b/eng/common/tools.sh index df203b51784..4a5fa99478d 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -528,6 +528,12 @@ function GetDarc { "$eng_root/common/darc-init.sh" --toolpath "$darc_path" $version } +# Returns a full path to an Arcade SDK task project file. +function GetSdkTaskProject { + taskName=$1 + echo "$(dirname $_InitializeToolset)/SdkTasks/$taskName.proj" +} + ResolvePath "${BASH_SOURCE[0]}" _script_dir=`dirname "$_ResolvePath"` diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SigningValidation.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SigningValidation.proj index c16f0b04e66..5319f12873e 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SigningValidation.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SigningValidation.proj @@ -21,10 +21,11 @@ $(NetToolCurrent) Build - $(NuGetPackageRoot)Microsoft.DotNet.SignCheck\$(MicrosoftDotNetSignCheckVersion)\tools\Microsoft.DotNet.SignCheck.exe - + + + @@ -35,14 +36,12 @@ @(ItemsToSign) - - - - - - - diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/Versions.props b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/Versions.props index 68197767aea..ace43b4f8cc 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/Versions.props +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/Versions.props @@ -7,7 +7,7 @@ $(MicrosoftDotNetMaestroTasksVersion) $(ArcadeSdkVersion) $(ArcadeSdkVersion) - $(ArcadeSdkVersion) + $(ArcadeSdkVersion) $(MicrosoftSymbolUploaderBuildTaskVersion) diff --git a/src/SignCheck/Microsoft.SignCheck/Microsoft.DotNet.SignCheckLibrary.csproj b/src/SignCheck/Microsoft.SignCheck/Microsoft.DotNet.SignCheckLibrary.csproj index 924724dc659..e0e120d99da 100644 --- a/src/SignCheck/Microsoft.SignCheck/Microsoft.DotNet.SignCheckLibrary.csproj +++ b/src/SignCheck/Microsoft.SignCheck/Microsoft.DotNet.SignCheckLibrary.csproj @@ -3,22 +3,23 @@ - $(NetFrameworkMinimum) - x86 + $(NetToolCurrent);$(NetFrameworkMinimum) false Microsoft.SignCheck false true + true - + + @@ -35,6 +36,27 @@ CopyToOutputDirectory="PreserveNewest" /> + + + + + + + + + + + + + + + + + + + + + /// Converts a string containing wildcards (*, ?) into a regular expression pattern string. /// diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCode.cs b/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCode.cs index 1a8e0086634..3c0014bfa9e 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCode.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCode.cs @@ -200,6 +200,6 @@ public static IEnumerable GetTimestamps(string path) return timestamps; } - + } } diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarManifestFile.cs b/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarManifestFile.cs index 1c4199e0f5d..b42097228fd 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarManifestFile.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarManifestFile.cs @@ -48,7 +48,7 @@ private bool Verify(JarIndividualEntry entry, ZipArchiveEntry archiveEntry) { using (Stream stream = archiveEntry.Open()) { - HashAlgorithm ha = HashAlgorithm.Create(entry.HashAlgorithmName); + HashAlgorithm ha = Utils.CreateHashAlgorithm(entry.HashAlgorithmName); byte[] computedHash = ha.ComputeHash(stream); string hashDigest = Convert.ToBase64String(computedHash); @@ -65,7 +65,7 @@ private bool Verify(JarIndividualEntry entry, ZipArchiveEntry archiveEntry) private string GetHashDigest(string input, string algorithmName) { - using (HashAlgorithm hashAlgorithm = HashAlgorithm.Create(algorithmName)) + using (HashAlgorithm hashAlgorithm = Utils.CreateHashAlgorithm(algorithmName)) { byte[] hashValue = hashAlgorithm.ComputeHash(new UTF8Encoding().GetBytes(input.ToCharArray())); return Convert.ToBase64String(hashValue); diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarManifestFileBase.cs b/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarManifestFileBase.cs index b3c8f71e246..9712869d943 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarManifestFileBase.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarManifestFileBase.cs @@ -130,7 +130,7 @@ public JarManifestFileBase(string archivePath, string manifestPath) /// A base64 encoded string of the manifest hash. public string GetManifestDigest(string algorithmName) { - using (HashAlgorithm hashAlgorithm = HashAlgorithm.Create(algorithmName)) + using (HashAlgorithm hashAlgorithm = Utils.CreateHashAlgorithm(algorithmName)) { byte[] hashValue = hashAlgorithm.ComputeHash(new UTF8Encoding().GetBytes(ManifestText.ToCharArray())); return Convert.ToBase64String(hashValue); diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarSignatureFile.cs b/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarSignatureFile.cs index 70dbfb5e51d..bfc22f73062 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarSignatureFile.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarSignatureFile.cs @@ -209,7 +209,7 @@ private bool VerifySignatureDsa() byte[] signatureBlockBytes = JarUtils.ReadBytes(ArchivePath, SignatureBlockFilePath); byte[] signatureFileBytes = JarUtils.ReadBytes(ArchivePath, SignatureFilePath); - SHA1Managed sha = new SHA1Managed(); // lgtm [cs/weak-crypto] Hash algorithm specified by signature algorithm + SHA1 sha = SHA1.Create(); // lgtm [cs/weak-crypto] Hash algorithm specified by signature algorithm byte[] hash = sha.ComputeHash(signatureFileBytes); ContentInfo ci = new ContentInfo(signatureFileBytes); @@ -240,7 +240,7 @@ private bool VerifySignatureRsa() byte[] signatureBlockBytes = JarUtils.ReadBytes(ArchivePath, SignatureBlockFilePath); byte[] signatureFileBytes = JarUtils.ReadBytes(ArchivePath, SignatureFilePath); - SHA256Managed sha = new SHA256Managed(); + SHA256 sha = SHA256.Create(); byte[] hash = sha.ComputeHash(signatureFileBytes); ContentInfo ci = new ContentInfo(signatureFileBytes); diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarUtils.cs b/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarUtils.cs index 723e67509e6..ac21809ec1a 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarUtils.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/Jar/JarUtils.cs @@ -48,7 +48,7 @@ public static byte[] ReadBytes(string archivePath, string path) /// public static string GetHashDigest(string input, string algorithmName) { - using (HashAlgorithm hashAlgorithm = HashAlgorithm.Create(algorithmName)) + using (HashAlgorithm hashAlgorithm = Utils.CreateHashAlgorithm(algorithmName)) { byte[] hashValue = hashAlgorithm.ComputeHash(new UTF8Encoding().GetBytes(input.ToCharArray())); return Convert.ToBase64String(hashValue); diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs b/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs index 63a48c6dd6e..4dc50de0b49 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs @@ -6,7 +6,9 @@ using System.IO; using System.IO.Compression; using System.Linq; +# if NETFRAMEWORK using Microsoft.SignCheck.Interop.PortableExecutable; +#endif using Microsoft.SignCheck.Logging; namespace Microsoft.SignCheck.Verification @@ -86,21 +88,24 @@ public SignatureVerificationManager(Exclusions exclusions, Log log, SignatureVer Log = log; Options = options; +#if NETFRAMEWORK AddFileVerifier(new CabVerifier(log, exclusions, options, ".cab")); AddFileVerifier(new PortableExecutableVerifier(log, exclusions, options, ".dll")); AddFileVerifier(new ExeVerifier(log, exclusions, options, ".exe")); AddFileVerifier(new JarVerifier(log, exclusions, options)); AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".js")); - AddFileVerifier(new LzmaVerifier(log, exclusions, options)); AddFileVerifier(new MsiVerifier(log, exclusions, options)); AddFileVerifier(new MspVerifier(log, exclusions, options)); AddFileVerifier(new MsuVerifier(log, exclusions, options)); - AddFileVerifier(new NupkgVerifier(log, exclusions, options)); AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".psd1")); AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".psm1")); AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".ps1")); AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".ps1xml")); AddFileVerifier(new VsixVerifier(log, exclusions, options)); +#endif + + AddFileVerifier(new LzmaVerifier(log, exclusions, options)); + AddFileVerifier(new NupkgVerifier(log, exclusions, options)); AddFileVerifier(new XmlVerifier(log, exclusions, options)); AddFileVerifier(new ZipVerifier(log, exclusions, options)); } @@ -257,7 +262,7 @@ public static FileVerifier GetFileVerifierByHeader(string path) fileVerifier = GetFileVerifierByExtension(".cab"); } } - +#if NETFRAMEWORK reader.BaseStream.Seek(0, SeekOrigin.Begin); if (stream.Length > 2) { @@ -276,6 +281,7 @@ public static FileVerifier GetFileVerifierByHeader(string path) } } } +#endif } return fileVerifier; diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/StrongName.cs b/src/SignCheck/Microsoft.SignCheck/Verification/StrongName.cs index c6bc6a574e1..6a26f36e1d4 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/StrongName.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/StrongName.cs @@ -15,7 +15,9 @@ public static class StrongName public const int S_OK = 0; +#pragma warning disable SYSLIB0019 internal static ICLRStrongName ClrStrongName = (ICLRStrongName)RuntimeEnvironment.GetRuntimeInterfaceAsObject(new Guid(CLSID_CLRStrongName), new Guid(IID_ICLRStrongName)); +#pragma warning restore SYSLIB0019 /// /// Retrieve the StrongName token from an assembly. diff --git a/src/SignCheck/SignCheck.sln b/src/SignCheck/SignCheck.sln index e09728f59a5..50ce010095a 100644 --- a/src/SignCheck/SignCheck.sln +++ b/src/SignCheck/SignCheck.sln @@ -1,31 +1,73 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SignCheck", "SignCheck\Microsoft.DotNet.SignCheck.csproj", "{FA490389-CA35-41B4-8EEA-15DB39093716}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.SignCheck", "Microsoft.SignCheck", "{CD7E959D-5591-47DF-162F-B5B86872F1DE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SignCheck", "Microsoft.SignCheck\Microsoft.DotNet.SignCheckLibrary.csproj", "{55C6C9BF-ED45-4BE2-B122-1BD989DFEB7F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.SignCheckLibrary", "Microsoft.SignCheck\Microsoft.DotNet.SignCheckLibrary.csproj", "{CBE112E8-93E4-4C42-9E16-464F8AC68EB3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SignCheck", "SignCheck", "{D8339024-BB1A-8F47-539A-ACF3F6EBF1D1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.SignCheck", "SignCheck\Microsoft.DotNet.SignCheck.csproj", "{21E7EEA3-78BF-4087-9954-3AE67C3D496C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SignCheckTask", "SignCheckTask", "{2DC3DF24-5ED9-C6AB-8979-11D5B8524D15}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.SignCheckTask", "SignCheckTask\Microsoft.DotNet.SignCheckTask.csproj", "{834EAA78-032E-496E-9F45-9C86E6ED157B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FA490389-CA35-41B4-8EEA-15DB39093716}.Debug|x86.ActiveCfg = Debug|x86 - {FA490389-CA35-41B4-8EEA-15DB39093716}.Debug|x86.Build.0 = Debug|x86 - {FA490389-CA35-41B4-8EEA-15DB39093716}.Release|x86.ActiveCfg = Release|x86 - {FA490389-CA35-41B4-8EEA-15DB39093716}.Release|x86.Build.0 = Release|x86 - {55C6C9BF-ED45-4BE2-B122-1BD989DFEB7F}.Debug|x86.ActiveCfg = Debug|x86 - {55C6C9BF-ED45-4BE2-B122-1BD989DFEB7F}.Debug|x86.Build.0 = Debug|x86 - {55C6C9BF-ED45-4BE2-B122-1BD989DFEB7F}.Release|x86.ActiveCfg = Release|x86 - {55C6C9BF-ED45-4BE2-B122-1BD989DFEB7F}.Release|x86.Build.0 = Release|x86 + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3}.Debug|x64.ActiveCfg = Debug|Any CPU + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3}.Debug|x64.Build.0 = Debug|Any CPU + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3}.Debug|x86.ActiveCfg = Debug|Any CPU + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3}.Debug|x86.Build.0 = Debug|Any CPU + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3}.Release|Any CPU.Build.0 = Release|Any CPU + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3}.Release|x64.ActiveCfg = Release|Any CPU + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3}.Release|x64.Build.0 = Release|Any CPU + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3}.Release|x86.ActiveCfg = Release|Any CPU + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3}.Release|x86.Build.0 = Release|Any CPU + {21E7EEA3-78BF-4087-9954-3AE67C3D496C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21E7EEA3-78BF-4087-9954-3AE67C3D496C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21E7EEA3-78BF-4087-9954-3AE67C3D496C}.Debug|x64.ActiveCfg = Debug|Any CPU + {21E7EEA3-78BF-4087-9954-3AE67C3D496C}.Debug|x64.Build.0 = Debug|Any CPU + {21E7EEA3-78BF-4087-9954-3AE67C3D496C}.Debug|x86.ActiveCfg = Debug|Any CPU + {21E7EEA3-78BF-4087-9954-3AE67C3D496C}.Debug|x86.Build.0 = Debug|Any CPU + {21E7EEA3-78BF-4087-9954-3AE67C3D496C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21E7EEA3-78BF-4087-9954-3AE67C3D496C}.Release|Any CPU.Build.0 = Release|Any CPU + {21E7EEA3-78BF-4087-9954-3AE67C3D496C}.Release|x64.ActiveCfg = Release|Any CPU + {21E7EEA3-78BF-4087-9954-3AE67C3D496C}.Release|x64.Build.0 = Release|Any CPU + {21E7EEA3-78BF-4087-9954-3AE67C3D496C}.Release|x86.ActiveCfg = Release|Any CPU + {21E7EEA3-78BF-4087-9954-3AE67C3D496C}.Release|x86.Build.0 = Release|Any CPU + {834EAA78-032E-496E-9F45-9C86E6ED157B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {834EAA78-032E-496E-9F45-9C86E6ED157B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {834EAA78-032E-496E-9F45-9C86E6ED157B}.Debug|x64.ActiveCfg = Debug|Any CPU + {834EAA78-032E-496E-9F45-9C86E6ED157B}.Debug|x64.Build.0 = Debug|Any CPU + {834EAA78-032E-496E-9F45-9C86E6ED157B}.Debug|x86.ActiveCfg = Debug|Any CPU + {834EAA78-032E-496E-9F45-9C86E6ED157B}.Debug|x86.Build.0 = Debug|Any CPU + {834EAA78-032E-496E-9F45-9C86E6ED157B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {834EAA78-032E-496E-9F45-9C86E6ED157B}.Release|Any CPU.Build.0 = Release|Any CPU + {834EAA78-032E-496E-9F45-9C86E6ED157B}.Release|x64.ActiveCfg = Release|Any CPU + {834EAA78-032E-496E-9F45-9C86E6ED157B}.Release|x64.Build.0 = Release|Any CPU + {834EAA78-032E-496E-9F45-9C86E6ED157B}.Release|x86.ActiveCfg = Release|Any CPU + {834EAA78-032E-496E-9F45-9C86E6ED157B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {4B2E85BD-1C2C-41C3-87F0-D753E5D809D1} + GlobalSection(NestedProjects) = preSolution + {CBE112E8-93E4-4C42-9E16-464F8AC68EB3} = {CD7E959D-5591-47DF-162F-B5B86872F1DE} + {21E7EEA3-78BF-4087-9954-3AE67C3D496C} = {D8339024-BB1A-8F47-539A-ACF3F6EBF1D1} + {834EAA78-032E-496E-9F45-9C86E6ED157B} = {2DC3DF24-5ED9-C6AB-8979-11D5B8524D15} EndGlobalSection EndGlobal diff --git a/src/SignCheck/SignCheck/Microsoft.DotNet.SignCheck.csproj b/src/SignCheck/SignCheck/Microsoft.DotNet.SignCheck.csproj index 5cd72211d5d..d0628c78798 100644 --- a/src/SignCheck/SignCheck/Microsoft.DotNet.SignCheck.csproj +++ b/src/SignCheck/SignCheck/Microsoft.DotNet.SignCheck.csproj @@ -1,15 +1,14 @@ - + $(NetFrameworkToolCurrent) Exe - x86 - True + true false true - + true Build artifact signing validation tool Arcade Signing Validation Tool @@ -18,23 +17,7 @@ - - - - - - - - - - - + diff --git a/src/SignCheck/SignCheck/SignCheck.cs b/src/SignCheck/SignCheck/SignCheck.cs index 86dce6b920c..94791bf5f43 100644 --- a/src/SignCheck/SignCheck/SignCheck.cs +++ b/src/SignCheck/SignCheck/SignCheck.cs @@ -2,499 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using CommandLine; -using Microsoft.SignCheck.Logging; -using Microsoft.SignCheck.Verification; +using SignCheckTask; namespace SignCheck { - internal class SignCheck + class SignCheck { - private static readonly char[] _wildcards = new char[] { '*', '?' }; - - // Location where files can be downloaded - private static readonly string _appData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SignCheck"); - - internal List _inputFiles; - - internal Exclusions Exclusions - { - get; - set; - } - - internal bool HasArgErrors - { - get; - set; - } - - internal bool LoggedResults - { - get; - set; - } - - internal string[] ResultDetails - { - get; - set; - } - - internal IEnumerable InputFiles - { - get - { - if (_inputFiles == null) - { - _inputFiles = GetInputFilesFromOptions(); - } - return _inputFiles; - } - } - - public FileStatus FileStatus - { - get; - set; - } - - public Options Options - { - get; - set; - } - - public bool NoSignIssues - { - get; - set; - } - - public Log Log - { - get; - set; - } - - public int TotalFiles - { - get; - set; - } - - public int TotalUnsignedFiles - { - get; - set; - } - - public int TotalSignedFiles - { - get; - set; - } - - public int TotalSkippedFiles - { - get; - set; - } - - public int TotalExcludedFiles - { - get; - set; - } - - public int TotalSkippedExcludedFiles - { - get; - set; - } - - public SignCheck(string[] args) - { - Options = new Options(); - ParserResult parseResult = Parser.Default.ParseArguments(args). - WithParsed(options => HandleOptions(options)). - WithNotParsed(errors => HandleErrors(errors)); - } - - public SignCheck(Options options) - { - HandleOptions(options ?? new Options()); - } - - private void HandleOptions(Options options) - { - Options = options; - - Log = new Log(options.LogFile, options.ErrorLogFile, options.Verbosity); - - if (Options.FileStatus.Count() > 0) - { - FileStatus = FileStatus.NoFiles; - foreach (string value in Options.FileStatus) - { - FileStatus result; - if (Enum.TryParse(value, out result)) - { - FileStatus |= result; - } - else - { - Log.WriteError(LogVerbosity.Minimum, SignCheckResources.scErrorUnknownFileStatus, value); - } - } - - if (FileStatus == FileStatus.NoFiles) - { - } - } - else - { - FileStatus = FileStatus.UnsignedFiles; - } - - if (!String.IsNullOrEmpty(Options.ExclusionsFile)) - { - ProcessExclusions(Options.ExclusionsFile); - } - else - { - Exclusions = new Exclusions(); - } - // Add some well-known exclusions for WiX - Exclusions.Add(new Exclusion("*netfxca*;*.msi;Wix custom action (NGEN")); - Exclusions.Add(new Exclusion("*wixdepca*;*.msi;WiX custom action")); - Exclusions.Add(new Exclusion("*wixuiwixca*;*.msi;WiX custom action")); - Exclusions.Add(new Exclusion("*wixca*;*.msi;Wix custom action")); - Exclusions.Add(new Exclusion("*wixstdba.dll*;*.exe;WiX standard bundle application")); - - if (!Directory.Exists(_appData)) - { - Directory.CreateDirectory(_appData); - } - } - - private void HandleErrors(IEnumerable errors) - { - HasArgErrors = true; - } - - private List GetInputFilesFromOptions() - { - var inputFiles = new List(); - var downloadFiles = new List(); - if (Options.InputFiles == null) - { - return inputFiles; - } - foreach (string inputFile in Options.InputFiles) - { - Uri uriResult; - - if ((Uri.TryCreate(inputFile, UriKind.Absolute, out uriResult)) && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps)) - { - string downloadPath = Path.Combine(_appData, Path.GetFileName(uriResult.LocalPath)); - inputFiles.Add(downloadPath); - downloadFiles.Add(uriResult); - } - else if (inputFile.IndexOfAny(_wildcards) > -1) - { - SearchOption fileSearchOptions = Options.TraverseSubFolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; - string fileSearchPath = Path.GetDirectoryName(inputFile); - string fileSearchPattern = Path.GetFileName(inputFile); - string[] matchedFiles = null; - - if (String.IsNullOrEmpty(fileSearchPath)) - { - // CASE 1: No path, pattern in filename, e.g. "--input-File *.txt" or "-i Foo?Bar.txt" - fileSearchPath = Directory.GetCurrentDirectory(); - matchedFiles = Directory.GetFiles(fileSearchPath, fileSearchPattern, fileSearchOptions); - } - else - { - if (fileSearchPath.IndexOfAny(_wildcards) > -1) - { - // CASE 2: Path contains wildcards, e.g. "-i C:\Foo*\Bar.txt" or "-i C:\Foo*\Bar*.txt" - string[] wildcardDirectories = Utils.GetDirectories(fileSearchPath, null, fileSearchOptions); - - var _matchedFiles = new List(); - - foreach (string dir in wildcardDirectories) - { - _matchedFiles.AddRange(Directory.GetFiles(dir, fileSearchPattern, fileSearchOptions)); - } - - matchedFiles = _matchedFiles.ToArray(); - } - else - { - // CASE 3: Path contains no search patterns, e.g. "-i C:\Foo\Bar\*.txt" - if (Directory.Exists(fileSearchPath)) - { - matchedFiles = Directory.GetFiles(fileSearchPath, fileSearchPattern, fileSearchOptions); - } - else - { - Log.WriteError(String.Format(SignCheckResources.scDirDoesNotExist, fileSearchPath)); - } - } - } - - if (matchedFiles != null) - { - foreach (string file in matchedFiles) - { - inputFiles.Add(file); - } - } - } - else - { - if (Directory.Exists(inputFile)) - { - SearchOption searchOption = Options.Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; - - foreach (string dirFile in Directory.GetFiles(inputFile, "*.*", searchOption)) - { - inputFiles.Add(dirFile); - } - } - else if (File.Exists(Path.GetFullPath(inputFile))) - { - inputFiles.Add(inputFile); - } - else - { - Log.WriteError(String.Format(SignCheckResources.scInputFileDoesNotExist, inputFile)); - } - } - } - - if (downloadFiles.Count > 0) - { - DownloadFilesAsync(downloadFiles).Wait(); - } - - // Exclude log files in case they are created in the folder being scanned. - if (!String.IsNullOrEmpty(Options.ErrorLogFile)) - { - inputFiles.Remove(Path.GetFullPath(Options.ErrorLogFile)); - } - - if (!String.IsNullOrEmpty(Options.LogFile)) - { - inputFiles.Remove(Path.GetFullPath(Options.LogFile)); - } - return inputFiles; - } - - private void ProcessExclusions(string exclusionsFile) - { - Log.WriteMessage(LogVerbosity.Diagnostic, SignCheckResources.scProcessExclusions); - Exclusions = new Exclusions(exclusionsFile); - } - - private void ProcessResults(IEnumerable results, int indent) - { - foreach (SignatureVerificationResult result in results) - { - TotalFiles++; - - if (result.IsSigned && !result.IsExcluded) - { - TotalSignedFiles++; - } - else if (!(result.IsExcluded || result.IsSkipped) && (!result.IsSigned && !result.IsDoNotSign)) - { - TotalUnsignedFiles++; - } - - if (result.IsExcluded || (!result.IsSigned && result.IsDoNotSign)) - { - TotalExcludedFiles++; - } - - if (result.IsSkipped) - { - TotalSkippedFiles++; - } - - if (result.IsSkipped && result.IsExcluded) - { - TotalSkippedExcludedFiles++; - } - - // Regardless of the file status reporting settings, a container file like an MSI or NuGet package - // is always reported to keep the file hierarchy in the log readable. - if (((result.IsSkipped) && ((FileStatus & FileStatus.SkippedFiles) != 0)) || - ((result.IsSigned) && ((FileStatus & FileStatus.SignedFiles) != 0)) || - ((result.IsExcluded) && ((FileStatus & FileStatus.ExcludedFiles) != 0)) || - ((result.IsSigned) && (result.IsDoNotSign)) || - ((result.NestedResults.Count() > 0) && (Options.Recursive)) || - ((FileStatus & FileStatus.AllFiles) == FileStatus.AllFiles) || - ((!result.IsSigned && !result.IsDoNotSign) && (!result.IsSkipped) && (!result.IsExcluded) && ((FileStatus & FileStatus.UnsignedFiles) != 0))) - { - LoggedResults = true; - Log.WriteMessage(LogVerbosity.Minimum, String.Empty.PadLeft(indent) + result.ToString(result.IsExcluded ? DetailKeys.ResultKeysExcluded : ResultDetails)); - } - - if (((!result.IsSigned) && (!(result.IsSkipped || result.IsExcluded || result.IsDoNotSign))) || (result.IsSigned && result.IsDoNotSign)) - { - NoSignIssues = false; - } - - if (result.NestedResults.Count > 0) - { - ProcessResults(result.NestedResults, indent + 2); - } - } - } - - public void GenerateExclusionsFile(StreamWriter writer, IEnumerable results) - { - foreach (SignatureVerificationResult result in results) - { - if ((!result.IsSigned) && (!result.IsSkipped)) - { - writer.WriteLine(result.ExclusionEntry); - } - - if (result.NestedResults.Count > 0) - { - GenerateExclusionsFile(writer, result.NestedResults); - } - } - } - - internal int Run() - { - try - { - Log.WriteMessage("Starting execution of SignCheck."); - - SignatureVerificationOptions options = SignatureVerificationOptions.None; - options |= Options.Recursive ? SignatureVerificationOptions.VerifyRecursive : SignatureVerificationOptions.None; - options |= Options.EnableXmlSignatureVerification ? SignatureVerificationOptions.VerifyXmlSignatures : SignatureVerificationOptions.None; - options |= Options.SkipTimestamp ? SignatureVerificationOptions.None : SignatureVerificationOptions.VerifyAuthentiCodeTimestamps; - options |= Options.VerifyStrongName ? SignatureVerificationOptions.VerifyStrongNameSignature : SignatureVerificationOptions.None; - options |= Options.EnableJarSignatureVerification ? SignatureVerificationOptions.VerifyJarSignatures : SignatureVerificationOptions.None; - options |= !String.IsNullOrEmpty(Options.ExclusionsOutput) ? SignatureVerificationOptions.GenerateExclusion : SignatureVerificationOptions.None; - - var signatureVerificationManager = new SignatureVerificationManager(Exclusions, Log, options); - - ResultDetails = Options.Verbosity > LogVerbosity.Normal ? DetailKeys.ResultKeysVerbose : DetailKeys.ResultKeysNormal; - - if (InputFiles != null && InputFiles.Count() > 0) - { - DateTime startTime = DateTime.Now; - IEnumerable results = signatureVerificationManager.VerifyFiles(InputFiles); - DateTime endTime = DateTime.Now; - - NoSignIssues = true; - Log.WriteLine(); - Log.WriteMessage(LogVerbosity.Minimum, SignCheckResources.scResults); - Log.WriteLine(); - ProcessResults(results, 0); - - // Generate an exclusions file for any unsigned files that were reported. - if (!String.IsNullOrEmpty(Options.ExclusionsOutput)) - { - if (!Directory.Exists(Options.ExclusionsOutput)) - { - Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(Options.ExclusionsOutput))); - } - using (var exclusionsWriter = new StreamWriter(Options.ExclusionsOutput, append: false)) - { - GenerateExclusionsFile(exclusionsWriter, results); - } - } - - if (LoggedResults) - { - Log.WriteLine(); - } - - if (NoSignIssues) - { - Log.WriteMessage(LogVerbosity.Minimum, SignCheckResources.scNoSignIssues); - } - else - { - Log.WriteError(LogVerbosity.Minimum, SignCheckResources.scSignIssuesFound); - } - - TimeSpan totalTime = endTime - startTime; - Log.WriteMessage(LogVerbosity.Minimum, String.Format(SignCheckResources.scTime, totalTime)); - Log.WriteMessage(LogVerbosity.Minimum, String.Format(SignCheckResources.scStats, - TotalFiles, TotalSignedFiles, TotalUnsignedFiles, TotalSkippedFiles, TotalExcludedFiles, TotalSkippedExcludedFiles)); - } - else - { - Log.WriteMessage(LogVerbosity.Minimum, SignCheckResources.scNoFilesProcessed); - } - } - - catch (Exception e) - { - Log.WriteError(e.ToString()); - } - finally - { - if (Log != null) - { - Log.Close(); - } - } - - return Log.HasLoggedErrors ? -1 : 0; - } - - private async Task DownloadFileAsync(Uri uri) - { - try - { - ServicePointManager.CheckCertificateRevocationList = true; - - using (var wc = new WebClient()) - { - string downloadPath = Path.Combine(_appData, Path.GetFileName(uri.LocalPath)); - - if (File.Exists(downloadPath)) - { - Log.WriteMessage(LogVerbosity.Detailed, SignCheckResources.scDeleteExistingFile, downloadPath); - File.Delete(downloadPath); - } - - Log.WriteMessage(LogVerbosity.Detailed, SignCheckResources.scDownloading, uri.AbsoluteUri, downloadPath); - await wc.DownloadFileTaskAsync(uri, downloadPath); - } - } - catch (Exception e) - { - Log.WriteError(e.Message); - } - } - - private async Task DownloadFilesAsync(IEnumerable uris) - { - await Task.WhenAll(uris.Select(u => DownloadFileAsync(u))); - } - - [STAThread] - static int Main(string[] args) + public static int Main(string[] args) { // Exit code 3 for help output int retVal = 3; - var sc = new SignCheck(args); + var sc = new SignCheckTask.SignCheck(args); if ((sc.Options != null) && (!sc.HasArgErrors)) { retVal = sc.Run(); @@ -502,4 +20,4 @@ static int Main(string[] args) return retVal; } } -} +} \ No newline at end of file diff --git a/src/SignCheck/SignCheckTask/Microsoft.DotNet.SignCheckTask.csproj b/src/SignCheck/SignCheckTask/Microsoft.DotNet.SignCheckTask.csproj new file mode 100644 index 00000000000..ede49db7e8c --- /dev/null +++ b/src/SignCheck/SignCheckTask/Microsoft.DotNet.SignCheckTask.csproj @@ -0,0 +1,42 @@ + + + + $(NetToolCurrent);$(NetFrameworkToolCurrent) + true + false + true + Build artifact signing validation tool + Arcade Signing Validation Tool + SignCheckTask + false + $(NoWarn);NU5128 + true + + lib + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SignCheck/SignCheckTask/build/Microsoft.DotNet.SignCheckTask.props b/src/SignCheck/SignCheckTask/build/Microsoft.DotNet.SignCheckTask.props new file mode 100644 index 00000000000..0f13a7c52f4 --- /dev/null +++ b/src/SignCheck/SignCheckTask/build/Microsoft.DotNet.SignCheckTask.props @@ -0,0 +1,11 @@ + + + + + $(MSBuildThisFileDirectory)..\lib\net\Microsoft.DotNet.SignCheckTask.dll + $(MSBuildThisFileDirectory)..\lib\netframework\Microsoft.DotNet.SignCheckTask.dll + + + + + diff --git a/src/SignCheck/SignCheck/FileStatus.cs b/src/SignCheck/SignCheckTask/src/FileStatus.cs similarity index 94% rename from src/SignCheck/SignCheck/FileStatus.cs rename to src/SignCheck/SignCheckTask/src/FileStatus.cs index d9489c32239..bfc33d31a07 100644 --- a/src/SignCheck/SignCheck/FileStatus.cs +++ b/src/SignCheck/SignCheckTask/src/FileStatus.cs @@ -3,7 +3,7 @@ using System; -namespace SignCheck +namespace SignCheckTask { [Flags] public enum FileStatus diff --git a/src/SignCheck/SignCheck/LICENSE.TXT b/src/SignCheck/SignCheckTask/src/LICENSE.TXT similarity index 100% rename from src/SignCheck/SignCheck/LICENSE.TXT rename to src/SignCheck/SignCheckTask/src/LICENSE.TXT diff --git a/src/SignCheck/SignCheck/Options.cs b/src/SignCheck/SignCheckTask/src/Options.cs similarity index 99% rename from src/SignCheck/SignCheck/Options.cs rename to src/SignCheck/SignCheckTask/src/Options.cs index d40ce0da753..a39c7182224 100644 --- a/src/SignCheck/SignCheck/Options.cs +++ b/src/SignCheck/SignCheckTask/src/Options.cs @@ -5,7 +5,7 @@ using Microsoft.SignCheck.Logging; using System.Collections.Generic; -namespace SignCheck +namespace SignCheckTask { public class Options { diff --git a/src/SignCheck/SignCheckTask/src/SignCheck.cs b/src/SignCheck/SignCheckTask/src/SignCheck.cs new file mode 100644 index 00000000000..a1d08731bab --- /dev/null +++ b/src/SignCheck/SignCheckTask/src/SignCheck.cs @@ -0,0 +1,499 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.IO; +using System.Linq; +using CommandLine; +using System.Threading.Tasks; +using Microsoft.SignCheck.Logging; +using Microsoft.SignCheck.Verification; + +namespace SignCheckTask +{ + public class SignCheck + { + private static readonly char[] _wildcards = new char[] { '*', '?' }; + + // Location where files can be downloaded + private static readonly string _appData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SignCheck"); + + internal List _inputFiles; + + internal Exclusions Exclusions + { + get; + set; + } + + internal bool LoggedResults + { + get; + set; + } + + internal string[] ResultDetails + { + get; + set; + } + + internal IEnumerable InputFiles + { + get + { + if (_inputFiles == null) + { + _inputFiles = GetInputFilesFromOptions(); + } + return _inputFiles; + } + } + + public FileStatus FileStatus + { + get; + set; + } + + public bool HasArgErrors + { + get; + set; + } + + public Options Options + { + get; + set; + } + + public bool NoSignIssues + { + get; + set; + } + + public Log Log + { + get; + set; + } + + public int TotalFiles + { + get; + set; + } + + public int TotalUnsignedFiles + { + get; + set; + } + + public int TotalSignedFiles + { + get; + set; + } + + public int TotalSkippedFiles + { + get; + set; + } + + public int TotalExcludedFiles + { + get; + set; + } + + public int TotalSkippedExcludedFiles + { + get; + set; + } + + public SignCheck(string[] args) + { + Options = new Options(); + ParserResult parseResult = Parser.Default.ParseArguments(args). + WithParsed(options => HandleOptions(options)). + WithNotParsed(errors => HandleErrors(errors)); + } + + public SignCheck(Options options) + { + HandleOptions(options ?? new Options()); + } + + private void HandleOptions(Options options) + { + Options = options; + + Log = new Log(options.LogFile, options.ErrorLogFile, options.Verbosity); + + if (Options.FileStatus.Count() > 0) + { + FileStatus = FileStatus.NoFiles; + foreach (string value in Options.FileStatus) + { + FileStatus result; + if (Enum.TryParse(value, out result)) + { + FileStatus |= result; + } + else + { + Log.WriteError(LogVerbosity.Minimum, SignCheckResources.scErrorUnknownFileStatus, value); + } + } + + if (FileStatus == FileStatus.NoFiles) + { + } + } + else + { + FileStatus = FileStatus.UnsignedFiles; + } + + if (!String.IsNullOrEmpty(Options.ExclusionsFile)) + { + ProcessExclusions(Options.ExclusionsFile); + } + else + { + Exclusions = new Exclusions(); + } + // Add some well-known exclusions for WiX + Exclusions.Add(new Exclusion("*netfxca*;*.msi;Wix custom action (NGEN")); + Exclusions.Add(new Exclusion("*wixdepca*;*.msi;WiX custom action")); + Exclusions.Add(new Exclusion("*wixuiwixca*;*.msi;WiX custom action")); + Exclusions.Add(new Exclusion("*wixca*;*.msi;Wix custom action")); + Exclusions.Add(new Exclusion("*wixstdba.dll*;*.exe;WiX standard bundle application")); + + if (!Directory.Exists(_appData)) + { + Directory.CreateDirectory(_appData); + } + } + + private void HandleErrors(IEnumerable errors) + { + HasArgErrors = true; + } + + private List GetInputFilesFromOptions() + { + var inputFiles = new List(); + var downloadFiles = new List(); + if (Options.InputFiles == null) + { + return inputFiles; + } + foreach (string inputFile in Options.InputFiles) + { + Uri uriResult; + + if ((Uri.TryCreate(inputFile, UriKind.Absolute, out uriResult)) && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps)) + { + string downloadPath = Path.Combine(_appData, Path.GetFileName(uriResult.LocalPath)); + inputFiles.Add(downloadPath); + downloadFiles.Add(uriResult); + } + else if (inputFile.IndexOfAny(_wildcards) > -1) + { + SearchOption fileSearchOptions = Options.TraverseSubFolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; + string fileSearchPath = Path.GetDirectoryName(inputFile); + string fileSearchPattern = Path.GetFileName(inputFile); + string[] matchedFiles = null; + + if (String.IsNullOrEmpty(fileSearchPath)) + { + // CASE 1: No path, pattern in filename, e.g. "--input-File *.txt" or "-i Foo?Bar.txt" + fileSearchPath = Directory.GetCurrentDirectory(); + matchedFiles = Directory.GetFiles(fileSearchPath, fileSearchPattern, fileSearchOptions); + } + else + { + if (fileSearchPath.IndexOfAny(_wildcards) > -1) + { + // CASE 2: Path contains wildcards, e.g. "-i C:\Foo*\Bar.txt" or "-i C:\Foo*\Bar*.txt" + string[] wildcardDirectories = Utils.GetDirectories(fileSearchPath, null, fileSearchOptions); + + var _matchedFiles = new List(); + + foreach (string dir in wildcardDirectories) + { + _matchedFiles.AddRange(Directory.GetFiles(dir, fileSearchPattern, fileSearchOptions)); + } + + matchedFiles = _matchedFiles.ToArray(); + } + else + { + // CASE 3: Path contains no search patterns, e.g. "-i C:\Foo\Bar\*.txt" + if (Directory.Exists(fileSearchPath)) + { + matchedFiles = Directory.GetFiles(fileSearchPath, fileSearchPattern, fileSearchOptions); + } + else + { + Log.WriteError(String.Format(SignCheckResources.scDirDoesNotExist, fileSearchPath)); + } + } + } + + if (matchedFiles != null) + { + foreach (string file in matchedFiles) + { + inputFiles.Add(file); + } + } + } + else + { + if (Directory.Exists(inputFile)) + { + SearchOption searchOption = Options.Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; + + foreach (string dirFile in Directory.GetFiles(inputFile, "*.*", searchOption)) + { + inputFiles.Add(dirFile); + } + } + else if (File.Exists(Path.GetFullPath(inputFile))) + { + inputFiles.Add(inputFile); + } + else + { + Log.WriteError(String.Format(SignCheckResources.scInputFileDoesNotExist, inputFile)); + } + } + } + + if (downloadFiles.Count > 0) + { + DownloadFilesAsync(downloadFiles).Wait(); + } + + // Exclude log files in case they are created in the folder being scanned. + if (!String.IsNullOrEmpty(Options.ErrorLogFile)) + { + inputFiles.Remove(Path.GetFullPath(Options.ErrorLogFile)); + } + + if (!String.IsNullOrEmpty(Options.LogFile)) + { + inputFiles.Remove(Path.GetFullPath(Options.LogFile)); + } + return inputFiles; + } + + private void ProcessExclusions(string exclusionsFile) + { + Log.WriteMessage(LogVerbosity.Diagnostic, SignCheckResources.scProcessExclusions); + Exclusions = new Exclusions(exclusionsFile); + } + + private void ProcessResults(IEnumerable results, int indent) + { + foreach (SignatureVerificationResult result in results) + { + TotalFiles++; + + if (result.IsSigned && !result.IsExcluded) + { + TotalSignedFiles++; + } + else if (!(result.IsExcluded || result.IsSkipped) && (!result.IsSigned && !result.IsDoNotSign)) + { + TotalUnsignedFiles++; + } + + if (result.IsExcluded || (!result.IsSigned && result.IsDoNotSign)) + { + TotalExcludedFiles++; + } + + if (result.IsSkipped) + { + TotalSkippedFiles++; + } + + if (result.IsSkipped && result.IsExcluded) + { + TotalSkippedExcludedFiles++; + } + + // Regardless of the file status reporting settings, a container file like an MSI or NuGet package + // is always reported to keep the file hierarchy in the log readable. + if (((result.IsSkipped) && ((FileStatus & FileStatus.SkippedFiles) != 0)) || + ((result.IsSigned) && ((FileStatus & FileStatus.SignedFiles) != 0)) || + ((result.IsExcluded) && ((FileStatus & FileStatus.ExcludedFiles) != 0)) || + ((result.IsSigned) && (result.IsDoNotSign)) || + ((result.NestedResults.Count() > 0) && (Options.Recursive)) || + ((FileStatus & FileStatus.AllFiles) == FileStatus.AllFiles) || + ((!result.IsSigned && !result.IsDoNotSign) && (!result.IsSkipped) && (!result.IsExcluded) && ((FileStatus & FileStatus.UnsignedFiles) != 0))) + { + LoggedResults = true; + Log.WriteMessage(LogVerbosity.Minimum, String.Empty.PadLeft(indent) + result.ToString(result.IsExcluded ? DetailKeys.ResultKeysExcluded : ResultDetails)); + } + + if (((!result.IsSigned) && (!(result.IsSkipped || result.IsExcluded || result.IsDoNotSign))) || (result.IsSigned && result.IsDoNotSign)) + { + NoSignIssues = false; + } + + if (result.NestedResults.Count > 0) + { + ProcessResults(result.NestedResults, indent + 2); + } + } + } + + public void GenerateExclusionsFile(StreamWriter writer, IEnumerable results) + { + foreach (SignatureVerificationResult result in results) + { + if ((!result.IsSigned) && (!result.IsSkipped)) + { + writer.WriteLine(result.ExclusionEntry); + } + + if (result.NestedResults.Count > 0) + { + GenerateExclusionsFile(writer, result.NestedResults); + } + } + } + + public int Run() + { + try + { + Log.WriteMessage("Starting execution of SignCheck."); + + SignatureVerificationOptions options = SignatureVerificationOptions.None; + options |= Options.Recursive ? SignatureVerificationOptions.VerifyRecursive : SignatureVerificationOptions.None; + options |= Options.EnableXmlSignatureVerification ? SignatureVerificationOptions.VerifyXmlSignatures : SignatureVerificationOptions.None; + options |= Options.SkipTimestamp ? SignatureVerificationOptions.None : SignatureVerificationOptions.VerifyAuthentiCodeTimestamps; + options |= Options.VerifyStrongName ? SignatureVerificationOptions.VerifyStrongNameSignature : SignatureVerificationOptions.None; + options |= Options.EnableJarSignatureVerification ? SignatureVerificationOptions.VerifyJarSignatures : SignatureVerificationOptions.None; + options |= !String.IsNullOrEmpty(Options.ExclusionsOutput) ? SignatureVerificationOptions.GenerateExclusion : SignatureVerificationOptions.None; + + var signatureVerificationManager = new SignatureVerificationManager(Exclusions, Log, options); + + ResultDetails = Options.Verbosity > LogVerbosity.Normal ? DetailKeys.ResultKeysVerbose : DetailKeys.ResultKeysNormal; + + if (InputFiles != null && InputFiles.Count() > 0) + { + DateTime startTime = DateTime.Now; + IEnumerable results = signatureVerificationManager.VerifyFiles(InputFiles); + DateTime endTime = DateTime.Now; + + NoSignIssues = true; + Log.WriteLine(); + Log.WriteMessage(LogVerbosity.Minimum, SignCheckResources.scResults); + Log.WriteLine(); + ProcessResults(results, 0); + + // Generate an exclusions file for any unsigned files that were reported. + if (!String.IsNullOrEmpty(Options.ExclusionsOutput)) + { + if (!Directory.Exists(Options.ExclusionsOutput)) + { + Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(Options.ExclusionsOutput))); + } + using (var exclusionsWriter = new StreamWriter(Options.ExclusionsOutput, append: false)) + { + GenerateExclusionsFile(exclusionsWriter, results); + } + } + + if (LoggedResults) + { + Log.WriteLine(); + } + + if (NoSignIssues) + { + Log.WriteMessage(LogVerbosity.Minimum, SignCheckResources.scNoSignIssues); + } + else + { + Log.WriteError(LogVerbosity.Minimum, SignCheckResources.scSignIssuesFound); + } + + TimeSpan totalTime = endTime - startTime; + Log.WriteMessage(LogVerbosity.Minimum, String.Format(SignCheckResources.scTime, totalTime)); + Log.WriteMessage(LogVerbosity.Minimum, String.Format(SignCheckResources.scStats, + TotalFiles, TotalSignedFiles, TotalUnsignedFiles, TotalSkippedFiles, TotalExcludedFiles, TotalSkippedExcludedFiles)); + } + else + { + Log.WriteMessage(LogVerbosity.Minimum, SignCheckResources.scNoFilesProcessed); + } + } + + catch (Exception e) + { + Log.WriteError(e.ToString()); + } + finally + { + if (Log != null) + { + Log.Close(); + } + } + + return Log.HasLoggedErrors ? -1 : 0; + } + + private async Task DownloadFileAsync(Uri uri) + { + try + { + using (var httpClient = new HttpClient(new HttpClientHandler { CheckCertificateRevocationList = true })) + { + httpClient.Timeout = TimeSpan.FromSeconds(10000); // 10 seconds + + string downloadPath = Path.Combine(_appData, Path.GetFileName(uri.LocalPath)); + + if (File.Exists(downloadPath)) + { + Log.WriteMessage(LogVerbosity.Detailed, SignCheckResources.scDeleteExistingFile, downloadPath); + File.Delete(downloadPath); + } + + Log.WriteMessage(LogVerbosity.Detailed, SignCheckResources.scDownloading, uri.AbsoluteUri, downloadPath); + + using (var stream = await httpClient.GetStreamAsync(uri)) + { + using (var fileStream = new FileStream(downloadPath, FileMode.Create, FileAccess.Write)) + { + await stream.CopyToAsync(fileStream); + } + } + } + } + catch (Exception e) + { + Log.WriteError(e.Message); + } + } + + private async Task DownloadFilesAsync(IEnumerable uris) + { + await Task.WhenAll(uris.Select(u => DownloadFileAsync(u))); + } + } +} diff --git a/src/SignCheck/SignCheck/SignCheckResources.Designer.cs b/src/SignCheck/SignCheckTask/src/SignCheckResources.Designer.cs similarity index 97% rename from src/SignCheck/SignCheck/SignCheckResources.Designer.cs rename to src/SignCheck/SignCheckTask/src/SignCheckResources.Designer.cs index af26b7c4b83..47a626d8acc 100644 --- a/src/SignCheck/SignCheck/SignCheckResources.Designer.cs +++ b/src/SignCheck/SignCheckTask/src/SignCheckResources.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace SignCheck { +namespace SignCheckTask { using System; @@ -39,7 +39,7 @@ internal SignCheckResources() { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SignCheck.SignCheckResources", typeof(SignCheckResources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SignCheckTask.src.SignCheckResources", typeof(SignCheckResources).Assembly); resourceMan = temp; } return resourceMan; diff --git a/src/SignCheck/SignCheck/SignCheckResources.resx b/src/SignCheck/SignCheckTask/src/SignCheckResources.resx similarity index 100% rename from src/SignCheck/SignCheck/SignCheckResources.resx rename to src/SignCheck/SignCheckTask/src/SignCheckResources.resx diff --git a/src/SignCheck/SignCheck/SignCheckTask.cs b/src/SignCheck/SignCheckTask/src/SignCheckTask.cs similarity index 88% rename from src/SignCheck/SignCheck/SignCheckTask.cs rename to src/SignCheck/SignCheckTask/src/SignCheckTask.cs index 1b40fd98456..01d38f3165b 100644 --- a/src/SignCheck/SignCheck/SignCheckTask.cs +++ b/src/SignCheck/SignCheckTask/src/SignCheckTask.cs @@ -3,20 +3,29 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +#if NET472 +using AppDomainIsolatedTask = Microsoft.Build.Utilities.AppDomainIsolatedTask; +#else +using BuildTask = Microsoft.Build.Utilities.Task; +#endif using Microsoft.SignCheck.Logging; using System; using System.Collections.Generic; using System.IO; using System.Linq; -namespace SignCheck +namespace SignCheckTask { - [Microsoft.Build.Framework.LoadInSeparateAppDomain] +#if NETFRAMEWORK + [LoadInSeparateAppDomain] [RunInSTA] public class SignCheckTask : AppDomainIsolatedTask { static SignCheckTask() => Microsoft.DotNet.AssemblyResolution.Initialize(); - +#else + public class SignCheckTask : BuildTask + { +#endif public bool EnableJarSignatureVerification { get; @@ -90,7 +99,24 @@ public string ArtifactFolder public override bool Execute() { +#if NETFRAMEWORK Microsoft.DotNet.AssemblyResolution.Log = Log; +#endif + try + { + bool succeeded = ExecuteImpl(); + return succeeded && !Log.HasLoggedErrors; + } + finally + { +#if NETFRAMEWORK + Microsoft.DotNet.AssemblyResolution.Log = null; +#endif + } + } + + private bool ExecuteImpl() + { Options options = new Options(); options.EnableJarSignatureVerification = EnableJarSignatureVerification; options.EnableXmlSignatureVerification = EnableXmlSignatureVerification; @@ -149,8 +175,7 @@ public override bool Execute() var sc = new SignCheck(options); int result = sc.Run(); - Microsoft.DotNet.AssemblyResolution.Log = null; - return (result == 0 && !Log.HasLoggedErrors); + return result == 0; } } } diff --git a/src/SignCheck/SignCheck/Utils.cs b/src/SignCheck/SignCheckTask/src/Utils.cs similarity index 98% rename from src/SignCheck/SignCheck/Utils.cs rename to src/SignCheck/SignCheckTask/src/Utils.cs index 8b9de17db30..d41a852eff8 100644 --- a/src/SignCheck/SignCheck/Utils.cs +++ b/src/SignCheck/SignCheckTask/src/Utils.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.IO; -namespace SignCheck +namespace SignCheckTask { public static class Utils {