Skip to content

ci: Add MSI certificate validation to ArtifactBuilder #1752

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 9 additions & 17 deletions .github/workflows/all_solutions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,6 @@ jobs:
echo "version=$agentVersion" >> $env:GITHUB_OUTPUT
shell: powershell

- name: Archive NewRelic.NuGetHelper
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: NewRelic.NuGetHelper
path: ${{ github.workspace }}\build\NewRelic.NuGetHelper\bin
if-no-files-found: error

- name: Archive NewRelic.Agent.Extensions
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
Expand Down Expand Up @@ -591,7 +584,7 @@ jobs:

create-package-rpm:
needs: build-fullagent-msi
if: ${{ github.event.release }} || github.event_name == 'workflow_dispatch'
if: ${{ github.event.release || (github.event_name == 'workflow_dispatch' && github.event.inputs.build-for-release == 'true') }}
name: Create RPM Package
runs-on: ubuntu-22.04

Expand Down Expand Up @@ -659,7 +652,7 @@ jobs:

create-package-deb:
needs: build-fullagent-msi
if: ${{ github.event.release }} || github.event_name == 'workflow_dispatch'
if: ${{ github.event.release || (github.event_name == 'workflow_dispatch' && github.event.inputs.build-for-release == 'true') }}
name: Create Debian package
runs-on: ubuntu-22.04

Expand Down Expand Up @@ -713,7 +706,7 @@ jobs:

run-artifactbuilder:
needs: [create-package-rpm, create-package-deb]
if: ${{ github.event.release }} || github.event_name == 'workflow_dispatch'
if: ${{ github.event.release || (github.event_name == 'workflow_dispatch' && github.event.inputs.build-for-release == 'true') }}
name: Run ArtifactBuilder
runs-on: windows-2022

Expand Down Expand Up @@ -747,12 +740,6 @@ jobs:
name: rpm-build-artifacts
path: src/_build/CoreArtifacts

- name: Download NewRelic.NuGetHelper
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with:
name: NewRelic.NuGetHelper
path: build/NewRelic.NuGetHelper/bin

- name: Download NewRelic.Agent.Extensions
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with:
Expand All @@ -765,6 +752,11 @@ jobs:
name: NewRelic.OpenTracing.AmazonLambda.Tracer
path: src/AwsLambda/AwsLambdaOpenTracer/bin/Release/netstandard2.0-ILRepacked

- name: Build NewRelic.NuGetHelper
run: |
MSBuild.exe -restore -m -p:Configuration=Release ${{ github.workspace }}\build\NewRelic.NuGetHelper\NewRelic.NuGetHelper.csproj
shell: powershell

- name: Run ArtifactBuilder
run: |
${{ github.workspace }}\build\package.ps1 -configuration Release -IncludeDownloadSite
Expand All @@ -784,7 +776,7 @@ jobs:
contents: write
name: Build and Publish Multiverse Testing Suite
needs: build-fullagent-msi
if: ${{ github.event.release }}
if: ${{ github.event.release || (github.event_name == 'workflow_dispatch' && github.event.inputs.build-for-release == 'true') }}
uses: newrelic/newrelic-dotnet-agent/.github/workflows/multiverse_run.yml@main
with:
agentVersion: ${{ needs.build-fullagent-msi.outputs.agentVersion }}
7 changes: 0 additions & 7 deletions FullAgent.sln
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLogLogging", "src\Agent\Ne
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackExchangeRedis2Plus", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\StackExchangeRedis2Plus\StackExchangeRedis2Plus.csproj", "{EC34F023-223D-432F-9401-9C3ED1B75DE4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewRelic.NuGetHelper", "build\NewRelic.NuGetHelper\NewRelic.NuGetHelper.csproj", "{94BF8D27-2122-4573-AA79-90B977B40EF3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elasticsearch", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\Elasticsearch\Elasticsearch.csproj", "{D9428449-3E4B-4723-A8AA-1191315C7AAD}"
EndProject
Global
Expand Down Expand Up @@ -436,10 +434,6 @@ Global
{EC34F023-223D-432F-9401-9C3ED1B75DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC34F023-223D-432F-9401-9C3ED1B75DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC34F023-223D-432F-9401-9C3ED1B75DE4}.Release|Any CPU.Build.0 = Release|Any CPU
{94BF8D27-2122-4573-AA79-90B977B40EF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94BF8D27-2122-4573-AA79-90B977B40EF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94BF8D27-2122-4573-AA79-90B977B40EF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94BF8D27-2122-4573-AA79-90B977B40EF3}.Release|Any CPU.Build.0 = Release|Any CPU
{D9428449-3E4B-4723-A8AA-1191315C7AAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9428449-3E4B-4723-A8AA-1191315C7AAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9428449-3E4B-4723-A8AA-1191315C7AAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -512,7 +506,6 @@ Global
{2E6CF650-CB50-453D-830A-D00F0540FC2C} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
{3D69B4C9-FD16-461F-95AF-6FCA6EAA914E} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
{EC34F023-223D-432F-9401-9C3ED1B75DE4} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
{94BF8D27-2122-4573-AA79-90B977B40EF3} = {C0BB7A5D-6820-4058-AC47-0111ECC34015}
{D9428449-3E4B-4723-A8AA-1191315C7AAD} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
7 changes: 7 additions & 0 deletions build/ArtifactBuilder/Artifacts/MsiInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ protected override void InternalBuild()

if (TryGetMsiPath(out var msiPath))
{
ValidateCodeSigningCertificate(msiPath);

FileHelpers.CopyFile(msiPath, OutputDirectory);
File.WriteAllText($@"{OutputDirectory}\checksum.sha256", FileHelpers.GetSha256Checksum(msiPath));
}
Expand Down Expand Up @@ -399,5 +401,10 @@ private SortedSet<string> GetExpectedComponents(string installedFilesRoot)
return expectedComponents;
}

private void ValidateCodeSigningCertificate(string msiPath)
{
if (!SecurityHelpers.VerifyEmbeddedSignature(msiPath))
throw new PackagingException("Code signing certificate is not valid or not trusted.");
}
}
}
199 changes: 199 additions & 0 deletions build/ArtifactBuilder/SecurityHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
using System;
using System.Runtime.InteropServices;

namespace ArtifactBuilder
{
// based on http://www.pinvoke.net/default.aspx/wintrust.winverifytrust

#region WinTrustData struct field enums
enum WinTrustDataUIChoice : uint
{
All = 1,
None = 2,
NoBad = 3,
NoGood = 4
}

enum WinTrustDataRevocationChecks : uint
{
None = 0x00000000,
WholeChain = 0x00000001
}

enum WinTrustDataChoice : uint
{
File = 1,
Catalog = 2,
Blob = 3,
Signer = 4,
Certificate = 5
}

enum WinTrustDataStateAction : uint
{
Ignore = 0x00000000,
Verify = 0x00000001,
Close = 0x00000002,
AutoCache = 0x00000003,
AutoCacheFlush = 0x00000004
}

[FlagsAttribute]
enum WinTrustDataProvFlags : uint
{
UseIe4TrustFlag = 0x00000001,
NoIe4ChainFlag = 0x00000002,
NoPolicyUsageFlag = 0x00000004,
RevocationCheckNone = 0x00000010,
RevocationCheckEndCert = 0x00000020,
RevocationCheckChain = 0x00000040,
RevocationCheckChainExcludeRoot = 0x00000080,
SaferFlag = 0x00000100, // Used by software restriction policies. Should not be used.
HashOnlyFlag = 0x00000200,
UseDefaultOsverCheck = 0x00000400,
LifetimeSigningFlag = 0x00000800,
CacheOnlyUrlRetrieval = 0x00001000, // affects CRL retrieval and AIA retrieval
DisableMD2andMD4 = 0x00002000 // Win7 SP1+: Disallows use of MD2 or MD4 in the chain except for the root
}

enum WinTrustDataUIContext : uint
{
Execute = 0,
Install = 1
}
#endregion

#region WinTrust structures
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
class WinTrustFileInfo
{
UInt32 StructSize = (UInt32)Marshal.SizeOf(typeof(WinTrustFileInfo));
IntPtr pszFilePath; // required, file name to be verified
IntPtr hFile = IntPtr.Zero; // optional, open handle to FilePath
IntPtr pgKnownSubject = IntPtr.Zero; // optional, subject type if it is known

public WinTrustFileInfo(String _filePath)
{
pszFilePath = Marshal.StringToCoTaskMemAuto(_filePath);
}
public void Dispose()
{
if (pszFilePath != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pszFilePath);
pszFilePath = IntPtr.Zero;
}
}
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
class WinTrustData
{
UInt32 StructSize = (UInt32)Marshal.SizeOf(typeof(WinTrustData));
IntPtr PolicyCallbackData = IntPtr.Zero;
IntPtr SIPClientData = IntPtr.Zero;
// required: UI choice
WinTrustDataUIChoice UIChoice = WinTrustDataUIChoice.None;
// required: certificate revocation check options
WinTrustDataRevocationChecks RevocationChecks = WinTrustDataRevocationChecks.None;
// required: which structure is being passed in?
WinTrustDataChoice UnionChoice = WinTrustDataChoice.File;
// individual file
IntPtr FileInfoPtr;
WinTrustDataStateAction StateAction = WinTrustDataStateAction.Ignore;
IntPtr StateData = IntPtr.Zero;
String URLReference = null;
WinTrustDataProvFlags ProvFlags = WinTrustDataProvFlags.RevocationCheckChainExcludeRoot;
WinTrustDataUIContext UIContext = WinTrustDataUIContext.Execute;

// constructor for silent WinTrustDataChoice.File check
public WinTrustData(WinTrustFileInfo _fileInfo)
{
// On Win7SP1+, don't allow MD2 or MD4 signatures
if ((Environment.OSVersion.Version.Major > 6) ||
((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor > 1)) ||
((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor == 1) && !String.IsNullOrEmpty(Environment.OSVersion.ServicePack)))
{
ProvFlags |= WinTrustDataProvFlags.DisableMD2andMD4;
}

var wtfiData = _fileInfo;
FileInfoPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(WinTrustFileInfo)));
Marshal.StructureToPtr(wtfiData, FileInfoPtr, false);
}
public void Dispose()
{
if (FileInfoPtr != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(FileInfoPtr);
FileInfoPtr = IntPtr.Zero;
}
}
}
#endregion

enum WinVerifyTrustResult : uint
{
Success = 0,
ProviderUnknown = 0x800b0001, // Trust provider is not recognized on this system
ActionUnknown = 0x800b0002, // Trust provider does not support the specified action
SubjectFormUnknown = 0x800b0003, // Trust provider does not support the form specified for the subject
SubjectNotTrusted = 0x800b0004, // Subject failed the specified verification action
FileNotSigned = 0x800B0100, // TRUST_E_NOSIGNATURE - File was not signed
SubjectExplicitlyDistrusted = 0x800B0111, // Signer's certificate is in the Untrusted Publishers store
SignatureOrFileCorrupt = 0x80096010, // TRUST_E_BAD_DIGEST - file was probably corrupt
SubjectCertExpired = 0x800B0101, // CERT_E_EXPIRED - Signer's certificate was expired
SubjectCertificateRevoked = 0x800B010C, // CERT_E_REVOKED Subject's certificate was revoked
UntrustedRoot = 0x800B0109 // CERT_E_UNTRUSTEDROOT - A certification chain processed correctly but terminated in a root certificate that is not trusted by the trust provider.
}

static class SecurityHelpers
{
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
// GUID of the action to perform
private const string WINTRUST_ACTION_GENERIC_VERIFY_V2 = "{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}";

[DllImport("wintrust.dll", ExactSpelling = true, SetLastError = false, CharSet = CharSet.Unicode)]
static extern WinVerifyTrustResult WinVerifyTrust(
[In] IntPtr hwnd,
[In][MarshalAs(UnmanagedType.LPStruct)] Guid pgActionID,
[In] WinTrustData pWVTData
);

public static bool VerifyEmbeddedSignature(string fileName)
{
WinTrustFileInfo winTrustFileInfo = null;
WinTrustData winTrustData = null;

try
{
// specify the WinVerifyTrust function/action that we want
var action = new Guid(WINTRUST_ACTION_GENERIC_VERIFY_V2);

// instantiate our WinTrustFileInfo and WinTrustData data structures
winTrustFileInfo = new WinTrustFileInfo(fileName);
winTrustData = new WinTrustData(winTrustFileInfo);

var result = WinVerifyTrust(INVALID_HANDLE_VALUE, action, winTrustData);
return (result == WinVerifyTrustResult.Success);
}
finally
{
// free the locally-held unmanaged memory in the data structures
winTrustFileInfo?.Dispose();
winTrustData?.Dispose();
}
}

//public static bool CheckFile(string filename)
//{
// // check digital signature
// var ret = WinTrust.VerifyEmbeddedSignature(filename);
// if (!ret) return false;

// // do some other checks - for example verify the subject
// var cert = new X509Certificate2(filename);
// return cert.Verify() && cert.Subject == "foo";
//}
}
}
31 changes: 31 additions & 0 deletions build/BuildTools.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33723.286
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArtifactBuilder", "ArtifactBuilder\ArtifactBuilder.csproj", "{2FB32877-EF0C-4382-8D3C-F653F3C26A3A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewRelic.NuGetHelper", "NewRelic.NuGetHelper\NewRelic.NuGetHelper.csproj", "{94BF8D27-2122-4573-AA79-90B977B40EF3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2FB32877-EF0C-4382-8D3C-F653F3C26A3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2FB32877-EF0C-4382-8D3C-F653F3C26A3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2FB32877-EF0C-4382-8D3C-F653F3C26A3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2FB32877-EF0C-4382-8D3C-F653F3C26A3A}.Release|Any CPU.Build.0 = Release|Any CPU
{94BF8D27-2122-4573-AA79-90B977B40EF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94BF8D27-2122-4573-AA79-90B977B40EF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94BF8D27-2122-4573-AA79-90B977B40EF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94BF8D27-2122-4573-AA79-90B977B40EF3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BA603325-9FCE-400E-9852-C8D3E39A16C6}
EndGlobalSection
EndGlobal