mirror of
https://github.com/sadreck/Codecepticon
synced 2026-08-09 13:07:37 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0b3e7a61c | ||
|
|
e4920c1f21 | ||
|
|
b9bd043ce8 | ||
|
|
babb024473 | ||
|
|
7057e0b6c3 | ||
|
|
6358dd2afc | ||
|
|
ca43cd89bb | ||
|
|
67f5fe9aaa | ||
|
|
933a7f56ba | ||
|
|
daee9f05af |
@@ -0,0 +1,63 @@
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
env:
|
||||
SOLUTION_FILE_PATH: .
|
||||
|
||||
BUILD_CONFIGURATION: Release
|
||||
BUILD_OUTPUT_PATH: Build
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.CODECEPTICON_TOKEN }}
|
||||
|
||||
- name: Add MSBuild to PATH
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Restore NuGet packages
|
||||
working-directory: ${{env.GITHUB_WORKSPACE}}
|
||||
run: nuget restore ${{env.SOLUTION_FILE_PATH}}
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{env.GITHUB_WORKSPACE}}
|
||||
run: msbuild /m /p:Configuration=${{env.BUILD_CONFIGURATION}} /p:OutputPath=../${{env.BUILD_OUTPUT_PATH}} ${{env.SOLUTION_FILE_PATH}}
|
||||
|
||||
- name: Delete .config file
|
||||
run: Remove-Item -Path ${{env.BUILD_OUTPUT_PATH}}/Codecepticon.exe.config
|
||||
|
||||
- name: Set release filename
|
||||
run: echo "RELEASE_FILENAME=Codecepticon-${{ github.ref_name }}.zip" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Compress release
|
||||
run: Compress-Archive -Path ${{env.BUILD_OUTPUT_PATH}}/* -Destination ${{ env.RELEASE_FILENAME }}
|
||||
|
||||
- name: Get file hash
|
||||
run: |
|
||||
$hash = Get-FileHash -Algorithm SHA256 ${{ env.RELEASE_FILENAME }} | select -exp Hash
|
||||
echo "FILE_HASH=$hash" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: ${{ env.RELEASE_FILENAME }}
|
||||
name: Codecepticon-${{ github.ref_name }}
|
||||
body: |
|
||||
Archive SHA256: `${{ env.FILE_HASH }}`
|
||||
|
||||
For more details see `CHANGELOG.md`.
|
||||
token: ${{ secrets.CODECEPTICON_TOKEN }}
|
||||
@@ -1,5 +1,21 @@
|
||||
# Codecepticon Changelog
|
||||
|
||||
## v1.2.3
|
||||
|
||||
* `[Update]` Update copyright & links, added workflows to repo.
|
||||
|
||||
## v1.2.2
|
||||
|
||||
* `[Update]` Removed `BouncyCastle` dependency, now certificates are generated using native .NET functionality.
|
||||
|
||||
## v1.2.1
|
||||
|
||||
* `[New]` C#: Added support for renaming Structs.
|
||||
|
||||
## v1.2.0
|
||||
|
||||
* `[Update]` Removed the `signtool.exe` dependency and are now natively signing executables. The code was taken & customised from https://github.com/Danielku15/SigningServer, under MIT License - original author is Danielku15.
|
||||
|
||||
## v1.1.0
|
||||
|
||||
* `[New]` Module: Implement the `sign` module, to enable creating self-signed certificates and using any given certificate to sign an executable. This functionality is using `signtool.exe`.
|
||||
|
||||
@@ -4,42 +4,43 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>AnyCPU;x86;x64</Platforms>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<PackageId>Codecepticon</PackageId>
|
||||
<Title>Codecepticon</Title>
|
||||
<Version>1.1.0</Version>
|
||||
<Version>1.2.3</Version>
|
||||
<Authors>Pavel Tsakalidis</Authors>
|
||||
<Company>Accenture Security</Company>
|
||||
<Company></Company>
|
||||
<Product>Codecepticon</Product>
|
||||
<Description>Offensive Security Code Obfuscator</Description>
|
||||
<PackageProjectUrl>https://github.com/Accenture/Codecepticon</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/sadreck/Codecepticon</PackageProjectUrl>
|
||||
<AssemblyVersion></AssemblyVersion>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -56,7 +57,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Antlr4.Runtime.Standard" Version="4.9.2" />
|
||||
<PackageReference Include="BouncyCastle" Version="1.8.9" />
|
||||
<PackageReference Include="Microsoft.Build" Version="17.3.1" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="Microsoft.Build.Locator" Version="1.5.3" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
|
||||
@@ -64,7 +64,7 @@
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="3.9.0" />
|
||||
<PackageReference Include="Microsoft.PowerShell.5.1.ReferenceAssemblies" Version="1.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="6.0.0" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -8,8 +8,11 @@ Usage: Codecepticon.exe --module [csharp|cs] [OPTIONS]...
|
||||
--build Whether to build the project upon completion.
|
||||
--build-path [path] Path to a directory where the solution will be compiled to. Only works with --build.
|
||||
--profile [name] Name of an application-specific profile to use. Supported profiles are:
|
||||
- certify
|
||||
- rubeus
|
||||
- seatbelt
|
||||
- sharpchrome
|
||||
- sharpdpapi
|
||||
- sharphound
|
||||
- sharpview
|
||||
|
||||
@@ -22,6 +25,7 @@ Usage: Codecepticon.exe --module [csharp|cs] [OPTIONS]...
|
||||
- p Properties
|
||||
- a Parameters
|
||||
- v Variables
|
||||
- s Structs
|
||||
- o Command Line
|
||||
%%_SHARED_%%
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
Codecepticon v%%_VERSION_%% [ Accenture Security ]
|
||||
- For more information visit https://github.com/Accenture/Codecepticon
|
||||
Codecepticon v%%_VERSION_%% [ Pavel Tsakalidis ]
|
||||
- For more information visit https://github.com/sadreck/Codecepticon
|
||||
@@ -16,6 +16,8 @@ Usage: Codecepticon.exe --module sign [OPTIONS]...
|
||||
--overwrite When used with '--action cert' this will indicate whether to rewrite the target file
|
||||
if it already exists.
|
||||
--password Password for the pfx file (either to save or load, depending on the --action)
|
||||
--signtool Only used with '--action sign' to indicate the location of signtool.exe on the system.
|
||||
If this argument is not passed, Codecepticon will try to find it automatically.
|
||||
--path [executable] Location of the executable file to be signed.
|
||||
--path [executable] Location of the executable file to be signed.
|
||||
--algorithm When used with '--action sign', this argument will specify the signature algorithm.
|
||||
This can be one of: MD5, SHA1, SHA256, SHA384, SHA512.
|
||||
--timestamp When used with '--action sign', this is where the Timestamp Server is specified.
|
||||
For example 'http://timestamp.sectigo.com' or 'http://timestamp.digicert.com'.
|
||||
@@ -112,6 +112,7 @@ namespace Codecepticon.Modules.CSharp
|
||||
Logger.Verbose($"\tProperties:\t{DataCollector.AllProperties.Count}");
|
||||
Logger.Verbose($"\tParameters:\t{DataCollector.AllParameters.Count}");
|
||||
Logger.Verbose($"\tVariables:\t{DataCollector.AllVariables.Count}");
|
||||
Logger.Verbose($"\tStructs:\t{DataCollector.AllStructs.Count}");
|
||||
|
||||
Logger.Info("Generating mappings...");
|
||||
if (await GenerateMappings() == false)
|
||||
@@ -190,6 +191,7 @@ namespace Codecepticon.Modules.CSharp
|
||||
await DataCollector.CollectProperties(solution, project.Name, document.Name);
|
||||
await DataCollector.CollectVariables(solution, project.Name, document.Name);
|
||||
await DataCollector.CollectParameters(solution, project.Name, document.Name);
|
||||
await DataCollector.CollectStructs(solution, project.Name, document.Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,6 +322,21 @@ namespace Codecepticon.Modules.CSharp
|
||||
}
|
||||
}
|
||||
|
||||
if (CommandLineData.CSharp.Rename.Structs)
|
||||
{
|
||||
Logger.Verbose("Creating mappings for structs");
|
||||
foreach (string name in DataCollector.AllStructs)
|
||||
{
|
||||
newName = await GenerateName(CommandLineData.Global.NameGenerator, DataCollector.IsMappingUnique);
|
||||
if (newName.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DataCollector.Mapping.Structs.Add(name, newName);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -587,6 +604,28 @@ namespace Codecepticon.Modules.CSharp
|
||||
}
|
||||
}
|
||||
|
||||
if (CommandLineData.CSharp.Rename.Structs)
|
||||
{
|
||||
Logger.Info($"Renaming {DataCollector.Mapping.Structs.Count} structs...", CommandLineData.Global.Project.Debug);
|
||||
c = 0;
|
||||
foreach (Document document in project.Documents)
|
||||
{
|
||||
if (CommandLineData.Global.Project.Debug)
|
||||
{
|
||||
Logger.Debug($"Renaming structs in document {document.FilePath}");
|
||||
}
|
||||
else if (++c % step == 0)
|
||||
{
|
||||
Logger.Verbose(".", false, false);
|
||||
}
|
||||
solution = await dataRenamer.RenameStructs(solution, project.Name, document.Name);
|
||||
}
|
||||
if (!CommandLineData.Global.Project.Debug)
|
||||
{
|
||||
Logger.Info("", true, false);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Debug($"Setting ProjectGuid to {{{CommandLineData.Global.Project.Guid.ToString().ToUpper()}}}");
|
||||
VisualStudioManager.SetProjectConfiguration(solution, new Dictionary<string, string>
|
||||
{
|
||||
|
||||
@@ -129,6 +129,9 @@ namespace Codecepticon.Modules.CSharp.CommandLine
|
||||
case "o":
|
||||
CommandLineData.CSharp.Rename.CommandLine = true;
|
||||
break;
|
||||
case "s":
|
||||
CommandLineData.CSharp.Rename.Structs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -145,6 +148,7 @@ namespace Codecepticon.Modules.CSharp.CommandLine
|
||||
CommandLineData.CSharp.Rename.Variables = value;
|
||||
CommandLineData.CSharp.Rename.Parameters = value;
|
||||
CommandLineData.CSharp.Rename.CommandLine = value;
|
||||
CommandLineData.CSharp.Rename.Structs = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Codecepticon.Modules.CSharp
|
||||
public static List<string> AllProperties = new List<string>();
|
||||
public static List<string> AllVariables = new List<string>();
|
||||
public static List<string> AllParameters = new List<string>();
|
||||
public static List<string> AllStructs = new List<string>();
|
||||
|
||||
public struct CommandLine
|
||||
{
|
||||
@@ -41,6 +42,7 @@ namespace Codecepticon.Modules.CSharp
|
||||
public Dictionary<string, string> Properties;
|
||||
public Dictionary<string, string> Variables;
|
||||
public Dictionary<string, string> Parameters;
|
||||
public Dictionary<string, string> Structs;
|
||||
public Dictionary<string, CommandLine> CommandLine;
|
||||
}
|
||||
|
||||
@@ -52,7 +54,8 @@ namespace Codecepticon.Modules.CSharp
|
||||
Enums = new Dictionary<string, string>(),
|
||||
Properties = new Dictionary<string, string>(),
|
||||
Variables = new Dictionary<string, string>(),
|
||||
Parameters = new Dictionary<string, string>()
|
||||
Parameters = new Dictionary<string, string>(),
|
||||
Structs = new Dictionary<string, string>(),
|
||||
};
|
||||
|
||||
public static bool IsMappingUnique(string name)
|
||||
@@ -93,6 +96,12 @@ namespace Codecepticon.Modules.CSharp
|
||||
return false;
|
||||
}
|
||||
|
||||
item = Mapping.Structs.FirstOrDefault(s => s.Value == name).Key;
|
||||
if (item != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
item = Mapping.Parameters.FirstOrDefault(s => s.Value == name).Key;
|
||||
return item == null;
|
||||
}
|
||||
@@ -195,6 +204,24 @@ namespace Codecepticon.Modules.CSharp
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task CollectStructs(Solution solution, string projectName, string documentName)
|
||||
{
|
||||
Document document = VisualStudioManager.GetDocumentByName(solution, projectName, documentName);
|
||||
|
||||
SyntaxTree syntaxTree = await document.GetSyntaxTreeAsync();
|
||||
var structs = syntaxTree.GetRoot().DescendantNodes().OfType<StructDeclarationSyntax>();
|
||||
|
||||
foreach (var s in structs)
|
||||
{
|
||||
string name = s.Identifier.ToString();
|
||||
if (AllStructs.Contains(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
AllStructs.Add(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task CollectProperties(Solution solution, string projectName, string documentName)
|
||||
{
|
||||
Document document = VisualStudioManager.GetDocumentByName(solution, projectName, documentName);
|
||||
|
||||
@@ -190,5 +190,26 @@ namespace Codecepticon.Modules.CSharp
|
||||
|
||||
return solution;
|
||||
}
|
||||
|
||||
public async Task<Solution> RenameStructs(Solution solution, string projectName, string documentName)
|
||||
{
|
||||
Document document = VisualStudioManager.GetDocumentByName(solution, projectName, documentName);
|
||||
|
||||
SyntaxTree syntaxTree = await document.GetSyntaxTreeAsync();
|
||||
var structs = syntaxTree.GetRoot().DescendantNodes().OfType<StructDeclarationSyntax>();
|
||||
foreach (var s in structs)
|
||||
{
|
||||
string name = s.Identifier.ToString();
|
||||
if (!DataCollector.Mapping.Structs.ContainsKey(name))
|
||||
{
|
||||
Logger.Debug($"Struct does not exist in mapping: {name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
solution = await RenameCode<StructDeclarationSyntax>(solution, projectName, documentName, name, DataCollector.Mapping.Structs[name]);
|
||||
}
|
||||
|
||||
return solution;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Codecepticon.Modules.CSharp
|
||||
["properties"] = "",
|
||||
["variables"] = "",
|
||||
["parameters"] = "",
|
||||
["structs"] = "",
|
||||
["cmdline"] = "",
|
||||
};
|
||||
|
||||
@@ -61,6 +62,11 @@ namespace Codecepticon.Modules.CSharp
|
||||
data["parameters"] = ConcatData(DataCollector.Mapping.Parameters);
|
||||
}
|
||||
|
||||
if (CommandLineData.CSharp.Rename.Structs)
|
||||
{
|
||||
data["structs"] = ConcatData(DataCollector.Mapping.Structs);
|
||||
}
|
||||
|
||||
if (CommandLineData.CSharp.Rename.CommandLine)
|
||||
{
|
||||
data["cmdline"] = DataCollector.ConcatCommandLineData(DataCollector.Mapping.CommandLine);
|
||||
|
||||
@@ -67,6 +67,7 @@ namespace Codecepticon.CommandLine
|
||||
public bool Variables;
|
||||
public bool Parameters;
|
||||
public bool CommandLine;
|
||||
public bool Structs;
|
||||
}
|
||||
|
||||
public struct RewriteTemplateStruct
|
||||
@@ -155,7 +156,8 @@ namespace Codecepticon.CommandLine
|
||||
public struct SignSettings
|
||||
{
|
||||
public SignNewCertificate NewCertificate;
|
||||
public string SignTool;
|
||||
public string TimestampServer;
|
||||
public string SignatureAlgorithm;
|
||||
}
|
||||
|
||||
public struct RenameGeneratorStruct
|
||||
|
||||
@@ -1,101 +1,69 @@
|
||||
using Codecepticon.Utils;
|
||||
using Org.BouncyCastle.Asn1;
|
||||
using Org.BouncyCastle.Asn1.X509;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Generators;
|
||||
using Org.BouncyCastle.Crypto.Operators;
|
||||
using Org.BouncyCastle.Crypto.Prng;
|
||||
using Org.BouncyCastle.Math;
|
||||
using Org.BouncyCastle.Pkcs;
|
||||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.Utilities;
|
||||
using Org.BouncyCastle.X509;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace Codecepticon.Modules.Sign
|
||||
{
|
||||
class CertificateManager
|
||||
{
|
||||
private const string SignatureAlgorithm = "SHA256WithRSA";
|
||||
|
||||
private const int KeyLength = 2048;
|
||||
|
||||
public bool GenerateCertificate(string Subject, string Issuer, DateTime NotBefore, DateTime NotAfter, string Password, string PfxOutput)
|
||||
{
|
||||
// https://mcse.cloud/create-a-self-signed-certificate-with-bouncy-castle-and-c/
|
||||
Logger.Debug("Initialising random generators and certificate generators...");
|
||||
SecureRandom secureRandom = new SecureRandom(new CryptoApiRandomGenerator());
|
||||
// https://stackoverflow.com/a/48210587/2445959
|
||||
RSA keyPair = RSA.Create(KeyLength);
|
||||
|
||||
X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
|
||||
Logger.Verbose("Generating issuer certificate...");
|
||||
Logger.Verbose("Issuer is " + Issuer);
|
||||
X509Certificate2 certificateIssuer = GenerateIssuerCertificate(Issuer, NotBefore, NotAfter);
|
||||
|
||||
// Create and set serial number.
|
||||
Logger.Verbose("Setting up certificate properties...");
|
||||
BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), secureRandom);
|
||||
certificateGenerator.SetSerialNumber(serialNumber);
|
||||
Logger.Info("Generating signing certificate...");
|
||||
Logger.Verbose("Subject is " + Subject);
|
||||
CertificateRequest certRequest = new(Subject, keyPair, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||
certRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
|
||||
//certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid("1.3.6.1.5.5.7.3.8") }, true));
|
||||
certRequest.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(certRequest.PublicKey, false));
|
||||
X509Certificate2 cert = certRequest.Create(certificateIssuer, NotBefore, NotAfter, new byte[] { 1, 2, 3, 4 });
|
||||
|
||||
Logger.Debug("Creating Subject: " + Subject);
|
||||
X509Name subjectDN = new X509Name(true, Subject);
|
||||
certificateGenerator.SetSubjectDN(subjectDN);
|
||||
// Add the private key back to the certificate.
|
||||
X509Certificate2 certificate = cert.CopyWithPrivateKey(keyPair);
|
||||
|
||||
Logger.Debug("Creating Issuer: " + Issuer);
|
||||
X509Name issuerDN = new X509Name(true, Issuer);
|
||||
certificateGenerator.SetIssuerDN(issuerDN);
|
||||
|
||||
Logger.Debug("Setting NotBefore: " + NotBefore);
|
||||
certificateGenerator.SetNotBefore(NotBefore);
|
||||
Logger.Debug("Setting NotAfter: " + NotAfter);
|
||||
certificateGenerator.SetNotAfter(NotAfter);
|
||||
|
||||
KeyGenerationParameters keyGeneration = new KeyGenerationParameters(secureRandom, KeyLength);
|
||||
|
||||
// Create RSA key.
|
||||
Logger.Verbose("Generating RSA keypair...");
|
||||
RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();
|
||||
keyPairGenerator.Init(keyGeneration);
|
||||
AsymmetricCipherKeyPair keyPair = keyPairGenerator.GenerateKeyPair();
|
||||
|
||||
// Add the public/private keys to the certificate generator.
|
||||
Logger.Debug("Setting public key...");
|
||||
certificateGenerator.SetPublicKey(keyPair.Public);
|
||||
ISignatureFactory signatureFactory = new Asn1SignatureFactory(SignatureAlgorithm, keyPair.Private, secureRandom);
|
||||
X509Certificate certificate = certificateGenerator.Generate(signatureFactory);
|
||||
|
||||
Logger.Debug("Creating keystore...");
|
||||
Pkcs12Store keyStore = new Pkcs12Store();
|
||||
X509CertificateEntry certificateEntry = new X509CertificateEntry(certificate);
|
||||
keyStore.SetCertificateEntry(certificate.SubjectDN.ToString(), certificateEntry);
|
||||
keyStore.SetKeyEntry(certificate.SubjectDN.ToString(), new AsymmetricKeyEntry(keyPair.Private), new[] { certificateEntry });
|
||||
|
||||
// Convert to .NET Certificate.
|
||||
Logger.Debug("Converting to a .NET certificate...");
|
||||
MemoryStream stream = new MemoryStream();
|
||||
keyStore.Save(stream, Password.ToCharArray(), secureRandom);
|
||||
|
||||
System.Security.Cryptography.X509Certificates.X509Certificate2 netCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2(stream.ToArray(), Password, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.PersistKeySet | System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable);
|
||||
|
||||
Logger.Verbose("Writing certificate to " + PfxOutput);
|
||||
File.WriteAllBytes(PfxOutput, netCertificate.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pfx, Password));
|
||||
Logger.Info("Exporting certificate to file...");
|
||||
File.WriteAllBytes(PfxOutput, certificate.Export(X509ContentType.Pfx, Password));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private X509Certificate2 GenerateIssuerCertificate(string Issuer, DateTime NotBefore, DateTime NotAfter)
|
||||
{
|
||||
RSA keyPair = RSA.Create(KeyLength);
|
||||
|
||||
CertificateRequest issuerRequest = new(Issuer, keyPair, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||
issuerRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
|
||||
issuerRequest.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(issuerRequest.PublicKey, false));
|
||||
return issuerRequest.CreateSelfSigned(NotBefore, NotAfter);
|
||||
}
|
||||
|
||||
public bool CheckPfxPassword(string pfxFile, string password)
|
||||
{
|
||||
try
|
||||
{
|
||||
Pkcs12Store keyStore = new Pkcs12Store(File.OpenRead(pfxFile), password.ToCharArray());
|
||||
} catch (Exception e)
|
||||
X509Certificate2 certificate = new(pfxFile, password);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public System.Security.Cryptography.X509Certificates.X509Certificate GetCertificateFromFile(string signedFile)
|
||||
public X509Certificate GetCertificateFromFile(string signedFile)
|
||||
{
|
||||
return System.Security.Cryptography.X509Certificates.X509Certificate2.CreateFromSignedFile(signedFile);
|
||||
return X509Certificate.CreateFromSignedFile(signedFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ namespace Codecepticon.Modules.Sign.CommandLine
|
||||
{ "password", "" },
|
||||
{ "pfx-file", "" },
|
||||
{ "overwrite", "switch" },
|
||||
{ "signtool", "" }
|
||||
{ "algorithm", "" },
|
||||
{ "timestamp", "" }
|
||||
};
|
||||
MergeArguments();
|
||||
}
|
||||
@@ -67,10 +68,10 @@ namespace Codecepticon.Modules.Sign.CommandLine
|
||||
}
|
||||
break;
|
||||
case "password":
|
||||
CommandLineData.Sign.NewCertificate.Password= argument.Value;
|
||||
CommandLineData.Sign.NewCertificate.Password = argument.Value;
|
||||
break;
|
||||
case "pfx-file":
|
||||
CommandLineData.Sign.NewCertificate.PfxFile= argument.Value;
|
||||
CommandLineData.Sign.NewCertificate.PfxFile = argument.Value;
|
||||
break;
|
||||
case "overwrite":
|
||||
if (argument.Value.ToLower() != "false")
|
||||
@@ -78,8 +79,11 @@ namespace Codecepticon.Modules.Sign.CommandLine
|
||||
CommandLineData.Sign.NewCertificate.Overwrite = (argument.Value.Length > 0);
|
||||
}
|
||||
break;
|
||||
case "signtool":
|
||||
CommandLineData.Sign.SignTool = argument.Value;
|
||||
case "algorithm":
|
||||
CommandLineData.Sign.SignatureAlgorithm = argument.Value.ToUpper();
|
||||
break;
|
||||
case "timestamp":
|
||||
CommandLineData.Sign.TimestampServer = argument.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,16 +133,6 @@ namespace Codecepticon.Modules.Sign.CommandLine
|
||||
return false;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(CommandLineData.Sign.SignTool))
|
||||
{
|
||||
Logger.Info("SignTool path is empty - will try to find signtool.exe");
|
||||
}
|
||||
else if (!File.Exists(CommandLineData.Sign.SignTool))
|
||||
{
|
||||
Logger.Error("Path for signtool.exe does not exist: " + CommandLineData.Sign.SignTool);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate the PFX Password.
|
||||
if (!certificateManager.CheckPfxPassword(CommandLineData.Sign.NewCertificate.PfxFile, CommandLineData.Sign.NewCertificate.Password))
|
||||
{
|
||||
@@ -150,6 +140,22 @@ namespace Codecepticon.Modules.Sign.CommandLine
|
||||
return false;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(CommandLineData.Sign.SignatureAlgorithm))
|
||||
{
|
||||
Logger.Error("Signature Algorithm not set");
|
||||
return false;
|
||||
}
|
||||
else if (!IsValidSignatureAlgorithm(CommandLineData.Sign.SignatureAlgorithm))
|
||||
{
|
||||
Logger.Error("Invalid signature algorithm selected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(CommandLineData.Sign.TimestampServer))
|
||||
{
|
||||
CommandLineData.Sign.TimestampServer = ""; // Make sure it's not null.
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
Logger.Error("Invalid action: " + CommandLineData.Global.Action.ToString());
|
||||
@@ -158,6 +164,12 @@ namespace Codecepticon.Modules.Sign.CommandLine
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool IsValidSignatureAlgorithm(string algorithm)
|
||||
{
|
||||
List<string> validAlgorithms = new() { "MD5", "SHA1", "SHA256", "SHA384", "SHA512" };
|
||||
return validAlgorithms.Contains(algorithm);
|
||||
}
|
||||
|
||||
protected string FixDN(string dn)
|
||||
{
|
||||
// BouncyCastle does not recognise S=XXX within an X509Name, and it has to be in the form of ST=XXX.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Codecepticon.Modules.Sign.MsSign
|
||||
{
|
||||
public interface ISigningTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the format the signing tool offers to sign.
|
||||
/// </summary>
|
||||
string FormatName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of hash algorithms supported by this signing tool.
|
||||
/// </summary>
|
||||
IReadOnlyList<string> SupportedHashAlgorithms { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Performs the signing of the given file through the request.
|
||||
/// Might throw any exceptions describing the error during signing.
|
||||
/// </summary>
|
||||
/// <param name="signFileRequest">The request describing what to sign.</param>
|
||||
/// <param name="cancellationToken">A token to support cancellation.</param>
|
||||
/// <returns>The result of the signing operation.</returns>
|
||||
SignFileResponse SignFile(SignFileRequest signFileRequest);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the given file is signed.
|
||||
/// </summary>
|
||||
/// <param name="inputFileName">The path to the file on disk.</param>
|
||||
/// <param name="cancellationToken">A token to support cancellation.</param>
|
||||
/// <returns>true if the file is considered signed, otherwise false.</returns>
|
||||
/// <remarks>
|
||||
/// Some tools might only do a very basic check and not a full validation on whether
|
||||
/// all aspects of the signing are in place and valid.
|
||||
/// </remarks>
|
||||
bool IsFileSigned(string inputFileName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
using Codecepticon.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Codecepticon.Modules.Sign.MsSign
|
||||
{
|
||||
/*
|
||||
* This class was taken and customised from https://github.com/Danielku15/SigningServer, under MIT License.
|
||||
*/
|
||||
public class PortableExecutableSigningTool : ISigningTool
|
||||
{
|
||||
private static readonly Dictionary<string, (uint algId, string algOid, HashAlgorithmName algName)>
|
||||
PeSupportedHashAlgorithms =
|
||||
new(StringComparer
|
||||
.OrdinalIgnoreCase)
|
||||
{
|
||||
["SHA1"] = (Win32SigningAPI.CALG_SHA1, Win32SigningAPI.OID_OIWSEC_SHA1, HashAlgorithmName.SHA1),
|
||||
["MD5"] = (Win32SigningAPI.CALG_MD5, Win32SigningAPI.OID_RSA_MD5, HashAlgorithmName.MD5),
|
||||
["SHA256"] = (Win32SigningAPI.CALG_SHA_256, Win32SigningAPI.OID_OIWSEC_SHA256, HashAlgorithmName.SHA256),
|
||||
["SHA384"] = (Win32SigningAPI.CALG_SHA_384, Win32SigningAPI.OID_OIWSEC_SHA384, HashAlgorithmName.SHA384),
|
||||
["SHA512"] = (Win32SigningAPI.CALG_SHA_512, Win32SigningAPI.OID_OIWSEC_SHA512, HashAlgorithmName.SHA512)
|
||||
};
|
||||
|
||||
public virtual string FormatName => "Windows Portable Executables (PE)";
|
||||
|
||||
public virtual IReadOnlyList<string> SupportedHashAlgorithms => PeSupportedHashAlgorithms.Keys.ToArray();
|
||||
|
||||
public SignFileResponse SignFile(SignFileRequest signFileRequest)
|
||||
{
|
||||
var signFileResponse = new SignFileResponse();
|
||||
var successResult = SignFileResponseStatus.FileSigned;
|
||||
|
||||
if (IsFileSigned(signFileRequest.InputFilePath))
|
||||
{
|
||||
if (signFileRequest.OverwriteSignature)
|
||||
{
|
||||
Logger.Verbose($"File {signFileRequest.InputFilePath} is already signed, removing signature");
|
||||
UnsignFile(signFileRequest.InputFilePath);
|
||||
successResult = SignFileResponseStatus.FileResigned;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Verbose($"File {signFileRequest.InputFilePath} is already signed, abort signing");
|
||||
signFileResponse.Status = SignFileResponseStatus.FileAlreadySigned;
|
||||
return signFileResponse;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PeSupportedHashAlgorithms.TryGetValue(
|
||||
signFileRequest.HashAlgorithm ?? "", out var algId))
|
||||
{
|
||||
algId = PeSupportedHashAlgorithms["SHA256"];
|
||||
}
|
||||
|
||||
using var signerFileInfo = new UnmanagedStruct<Win32SigningAPI.SIGNER_FILE_INFO>(new Win32SigningAPI.SIGNER_FILE_INFO
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Win32SigningAPI.SIGNER_FILE_INFO>(),
|
||||
pwszFileName = signFileRequest.InputFilePath,
|
||||
hFile = IntPtr.Zero
|
||||
});
|
||||
using var dwIndex = new UnmanagedStruct<uint>(0);
|
||||
using var signerSubjectInfo = new UnmanagedStruct<Win32SigningAPI.SIGNER_SUBJECT_INFO>(
|
||||
new Win32SigningAPI.SIGNER_SUBJECT_INFO
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Win32SigningAPI.SIGNER_SUBJECT_INFO>(),
|
||||
pdwIndex = dwIndex.Pointer,
|
||||
dwSubjectChoice = Win32SigningAPI.SIGNER_SUBJECT_FILE,
|
||||
union = { pSignerFileInfo = signerFileInfo.Pointer }
|
||||
});
|
||||
using var signerCertStoreInfo = new UnmanagedStruct<Win32SigningAPI.SIGNER_CERT_STORE_INFO>(
|
||||
new Win32SigningAPI.SIGNER_CERT_STORE_INFO
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Win32SigningAPI.SIGNER_CERT_STORE_INFO>(),
|
||||
pSigningCert = signFileRequest.Certificate.Handle,
|
||||
dwCertPolicy = Win32SigningAPI.SIGNER_CERT_POLICY_CHAIN,
|
||||
hCertStore = IntPtr.Zero
|
||||
});
|
||||
using var signerCert = new UnmanagedStruct<Win32SigningAPI.SIGNER_CERT>(
|
||||
new Win32SigningAPI.SIGNER_CERT
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Win32SigningAPI.SIGNER_CERT>(),
|
||||
dwCertChoice = Win32SigningAPI.SIGNER_CERT_STORE,
|
||||
union = { pSpcChainInfo = signerCertStoreInfo.Pointer },
|
||||
hwnd = IntPtr.Zero
|
||||
});
|
||||
using var signerSignatureInfo = new UnmanagedStruct<Win32SigningAPI.SIGNER_SIGNATURE_INFO>(
|
||||
new Win32SigningAPI.SIGNER_SIGNATURE_INFO
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Win32SigningAPI.SIGNER_SIGNATURE_INFO>(),
|
||||
algidHash = algId.algId,
|
||||
dwAttrChoice = Win32SigningAPI.SIGNER_NO_ATTR,
|
||||
union = { pAttrAuthcode = IntPtr.Zero },
|
||||
psAuthenticated = IntPtr.Zero,
|
||||
psUnauthenticated = IntPtr.Zero
|
||||
});
|
||||
var (hr, tshr) = SignAndTimestamp(
|
||||
algId.algName,
|
||||
algId.algOid,
|
||||
signFileRequest.InputFilePath, signFileRequest.TimestampServer, signerSubjectInfo.Pointer,
|
||||
signerCert.Pointer,
|
||||
signerSignatureInfo.Pointer, signFileRequest.PrivateKey
|
||||
);
|
||||
|
||||
if (hr == Win32SigningAPI.S_OK && tshr == Win32SigningAPI.S_OK)
|
||||
{
|
||||
Logger.Verbose($"{signFileRequest.InputFilePath} successfully signed");
|
||||
signFileResponse.Status = successResult;
|
||||
signFileResponse.ResultFiles = new[]
|
||||
{
|
||||
new SignFileResponseFileInfo(signFileRequest.OriginalFileName, signFileRequest.InputFilePath)
|
||||
};
|
||||
}
|
||||
else if (hr != Win32SigningAPI.S_OK)
|
||||
{
|
||||
var exception = new Win32Exception(hr);
|
||||
signFileResponse.Status = SignFileResponseStatus.FileNotSignedError;
|
||||
signFileResponse.ErrorMessage = !string.IsNullOrEmpty(exception.Message)
|
||||
? exception.Message
|
||||
: $"signing file failed (0x{hr:x})";
|
||||
|
||||
if ((uint)hr == 0x8007000B)
|
||||
{
|
||||
signFileResponse.ErrorMessage =
|
||||
$"The appxmanifest does not contain the expected publisher. Expected: <Identity ... Publisher\"{signFileRequest.Certificate.SubjectName}\" .. />.";
|
||||
}
|
||||
|
||||
Logger.Error($"{signFileRequest.InputFilePath} signing failed {signFileResponse.ErrorMessage}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorText = new Win32Exception(tshr).Message;
|
||||
signFileResponse.Status = SignFileResponseStatus.FileNotSignedError;
|
||||
signFileResponse.ErrorMessage = !string.IsNullOrEmpty(errorText)
|
||||
? errorText
|
||||
: $"timestamping failed (0x{hr:x})";
|
||||
|
||||
Logger.Error($"{signFileRequest.InputFilePath} timestamping failed {signFileResponse.ErrorMessage}");
|
||||
}
|
||||
|
||||
return signFileResponse;
|
||||
}
|
||||
|
||||
public bool IsFileSigned(string inputFileName)
|
||||
{
|
||||
using var winTrustFileInfo = new UnmanagedStruct<Win32SigningAPI.WINTRUST_FILE_INFO>(
|
||||
new Win32SigningAPI.WINTRUST_FILE_INFO
|
||||
{
|
||||
cbStruct = (uint)Marshal.SizeOf<Win32SigningAPI.WINTRUST_FILE_INFO>(),
|
||||
pcwszFilePath = inputFileName,
|
||||
hFile = IntPtr.Zero,
|
||||
pgKnownSubject = IntPtr.Zero
|
||||
});
|
||||
var winTrustData = new Win32SigningAPI.WINTRUST_DATA
|
||||
{
|
||||
cbStruct = (uint)Marshal.SizeOf<Win32SigningAPI.WINTRUST_DATA>(),
|
||||
pPolicyCallbackData = IntPtr.Zero,
|
||||
pSIPClientData = IntPtr.Zero,
|
||||
dwUIChoice = Win32SigningAPI.WinTrustDataUIChoice.None,
|
||||
fdwRevocationChecks = Win32SigningAPI.WinTrustDataRevocationChecks.None,
|
||||
dwUnionChoice = Win32SigningAPI.WinTrustDataUnionChoice.File,
|
||||
dwStateAction = Win32SigningAPI.WinTrustDataStateAction.Verify,
|
||||
hWVTStateData = IntPtr.Zero,
|
||||
pwszURLReference = IntPtr.Zero,
|
||||
dwUIContext = 0,
|
||||
union = { pFile = winTrustFileInfo.Pointer }
|
||||
};
|
||||
|
||||
var actionId = new Guid(Win32SigningAPI.WINTRUST_ACTION_GENERIC_VERIFY_V2);
|
||||
var result = Win32SigningAPI.WinVerifyTrust(IntPtr.Zero, actionId, winTrustData);
|
||||
Logger.Debug($"WinVerifyTrust returned {result}");
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case Win32SigningAPI.WinVerifyTrustResult.Success:
|
||||
return true;
|
||||
case Win32SigningAPI.WinVerifyTrustResult.FileNotSigned:
|
||||
var dwLastError = (uint)Marshal.GetLastWin32Error();
|
||||
switch (dwLastError)
|
||||
{
|
||||
case (uint)Win32SigningAPI.WinVerifyTrustResult.FileNotSigned:
|
||||
return false;
|
||||
case (uint)Win32SigningAPI.WinVerifyTrustResult.SubjectFormUnknown:
|
||||
return true;
|
||||
case (uint)Win32SigningAPI.WinVerifyTrustResult.ProviderUnknown:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
case Win32SigningAPI.WinVerifyTrustResult.UntrustedRoot:
|
||||
return true;
|
||||
|
||||
case Win32SigningAPI.WinVerifyTrustResult.SubjectExplicitlyDistrusted:
|
||||
return true;
|
||||
|
||||
case Win32SigningAPI.WinVerifyTrustResult.SubjectNotTrusted:
|
||||
return true;
|
||||
|
||||
case Win32SigningAPI.WinVerifyTrustResult.LocalSecurityOption:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private protected virtual (int hr, int tshr) SignAndTimestamp(
|
||||
HashAlgorithmName hashAlgorithmName,
|
||||
string timestampHashOid,
|
||||
string inputFileName,
|
||||
string timestampServer,
|
||||
/*PSIGNER_SUBJECT_INFO*/IntPtr signerSubjectInfo,
|
||||
/*PSIGNER_CERT*/IntPtr signerCert,
|
||||
/*PSIGNER_SIGNATURE_INFO*/ IntPtr signerSignatureInfo,
|
||||
AsymmetricAlgorithm privateKey)
|
||||
{
|
||||
Logger.Debug($"Call signing of {inputFileName}");
|
||||
|
||||
int SignCallback(IntPtr pCertContext, IntPtr pvExtra, uint algId, byte[] pDigestToSign, uint dwDigestToSign,
|
||||
ref Win32SigningAPI.CRYPTOAPI_BLOB blob)
|
||||
{
|
||||
byte[] digest;
|
||||
try
|
||||
{
|
||||
switch (privateKey)
|
||||
{
|
||||
case DSA dsa:
|
||||
digest = dsa.CreateSignature(pDigestToSign);
|
||||
break;
|
||||
case ECDsa ecdsa:
|
||||
digest = ecdsa.SignHash(pDigestToSign);
|
||||
break;
|
||||
case RSA rsa:
|
||||
digest = rsa.SignHash(pDigestToSign, hashAlgorithmName, RSASignaturePadding.Pkcs1);
|
||||
break;
|
||||
default:
|
||||
return Win32SigningAPI.E_INVALIDARG;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var hr = e.HResult != 0 ? e.HResult : Win32SigningAPI.NTE_BAD_KEY;
|
||||
Logger.Error("Failed to sign data reporting: " + hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
var resultPtr = Marshal.AllocHGlobal(digest.Length);
|
||||
Marshal.Copy(digest, 0, resultPtr, digest.Length);
|
||||
blob.pbData = resultPtr;
|
||||
blob.cbData = (uint)digest.Length;
|
||||
return Win32SigningAPI.S_OK;
|
||||
}
|
||||
|
||||
Win32SigningAPI.SignCallback callbackDelegate = SignCallback;
|
||||
|
||||
using var unmanagedSignerParams = new UnmanagedStruct<Win32SigningAPI.SIGNER_SIGN_EX3_PARAMS>();
|
||||
using var unmanagedSignInfo = new UnmanagedStruct<Win32SigningAPI.SIGN_INFO>(new Win32SigningAPI.SIGN_INFO
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Win32SigningAPI.SIGN_INFO>(),
|
||||
callback = Marshal.GetFunctionPointerForDelegate(callbackDelegate),
|
||||
pvOpaque = IntPtr.Zero
|
||||
});
|
||||
var signerParams = new Win32SigningAPI.SIGNER_SIGN_EX3_PARAMS
|
||||
{
|
||||
dwFlags = Win32SigningAPI.SIGN_CALLBACK_UNDOCUMENTED,
|
||||
pSubjectInfo = signerSubjectInfo,
|
||||
pSigningCert = signerCert,
|
||||
pSignatureInfo = signerSignatureInfo,
|
||||
pProviderInfo = IntPtr.Zero,
|
||||
psRequest = IntPtr.Zero,
|
||||
pCryptoPolicy = IntPtr.Zero,
|
||||
pSignCallback = unmanagedSignInfo.Pointer
|
||||
};
|
||||
unmanagedSignerParams.Fill(signerParams);
|
||||
|
||||
var hr = Win32SigningAPI.SignerSignEx3(
|
||||
signerParams.dwFlags,
|
||||
signerParams.pSubjectInfo,
|
||||
signerParams.pSigningCert,
|
||||
signerParams.pSignatureInfo,
|
||||
signerParams.pProviderInfo,
|
||||
signerParams.dwTimestampFlags,
|
||||
signerParams.pszTimestampAlgorithmOid,
|
||||
signerParams.pwszTimestampURL,
|
||||
signerParams.psRequest,
|
||||
IntPtr.Zero,
|
||||
signerParams.pSignerContext,
|
||||
signerParams.pCryptoPolicy,
|
||||
signerParams.pSignCallback,
|
||||
signerParams.pReserved
|
||||
);
|
||||
|
||||
if (signerParams.pSignerContext != IntPtr.Zero)
|
||||
{
|
||||
var signerContext = new IntPtr();
|
||||
Marshal.PtrToStructure(signerParams.pSignerContext, signerContext);
|
||||
Win32SigningAPI.SignerFreeSignerContext(signerContext);
|
||||
}
|
||||
|
||||
var tshr = Win32SigningAPI.S_OK;
|
||||
if (hr == Win32SigningAPI.S_OK && !string.IsNullOrWhiteSpace(timestampServer))
|
||||
{
|
||||
Logger.Verbose($"Timestamping with url {timestampServer}");
|
||||
var timestampRetries = 5;
|
||||
do
|
||||
{
|
||||
tshr = timestampHashOid == Win32SigningAPI.OID_OIWSEC_SHA1
|
||||
? Win32SigningAPI.SignerTimeStamp(signerSubjectInfo, timestampServer)
|
||||
: Win32SigningAPI.SignerTimeStampEx2(
|
||||
Win32SigningAPI.SIGNER_TIMESTAMP_RFC3161,
|
||||
signerSubjectInfo,
|
||||
timestampServer,
|
||||
timestampHashOid,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero
|
||||
);
|
||||
if (tshr == Win32SigningAPI.S_OK)
|
||||
{
|
||||
Logger.Verbose("Timestamping succeeded");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error($"Timestamping failed with {tshr}, retries: {timestampRetries}");
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
} while (tshr != Win32SigningAPI.S_OK && (timestampRetries--) > 0);
|
||||
}
|
||||
|
||||
return (hr, tshr);
|
||||
}
|
||||
|
||||
public virtual void UnsignFile(string fileName)
|
||||
{
|
||||
using var file = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
|
||||
// TODO: remove multiple certificates here?
|
||||
if (Win32SigningAPI.ImageEnumerateCertificates(file.SafeFileHandle, Win32SigningAPI.CERT_SECTION_TYPE_ANY,
|
||||
out var dwNumCerts) &&
|
||||
dwNumCerts == 1)
|
||||
{
|
||||
Win32SigningAPI.ImageRemoveCertificate(file.SafeFileHandle, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Codecepticon.Modules.Sign.MsSign
|
||||
{
|
||||
public class SignFileRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the absolute path to the file being signed.
|
||||
/// </summary>
|
||||
public string InputFilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the certificate used during the signing operation.
|
||||
/// Typically embedded into the signed file (without private keys).
|
||||
/// </summary>
|
||||
public X509Certificate2 Certificate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the private key used for performing the signing operations.
|
||||
/// This key must match the <see cref="Certificate"/> to avoid corrupt signatures.
|
||||
/// </summary>
|
||||
public AsymmetricAlgorithm PrivateKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the original name of the file being signed. <see cref="InputFilePath"/>
|
||||
/// might point to a temporarily name while <see cref="OriginalFileName"/> is the name of
|
||||
/// the file as provided by the client. Might be used to generate auxiliary files.
|
||||
/// </summary>
|
||||
public string OriginalFileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamping server which should be used for timestamping the signatures.
|
||||
/// </summary>
|
||||
public string TimestampServer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the hash algorithm to be used for the signatures.
|
||||
/// </summary>
|
||||
public string HashAlgorithm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether any existing signatures should be overwritten.
|
||||
/// If this is not set, and a file is already signed, the signing operation will fail.
|
||||
/// </summary>
|
||||
public bool OverwriteSignature { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Codecepticon.Modules.Sign.MsSign
|
||||
{
|
||||
public enum SignFileResponseStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// File was successfully signed
|
||||
/// </summary>
|
||||
FileSigned,
|
||||
|
||||
/// <summary>
|
||||
/// Files was successfully signed, an existing signature was removed
|
||||
/// </summary>
|
||||
FileResigned,
|
||||
|
||||
/// <summary>
|
||||
/// The file was already signed and therefore signing was skipped.
|
||||
/// </summary>
|
||||
FileAlreadySigned,
|
||||
|
||||
/// <summary>
|
||||
/// The file was not signed because the given file format cannot be signed or is not supported.
|
||||
/// </summary>
|
||||
FileNotSignedUnsupportedFormat,
|
||||
|
||||
/// <summary>
|
||||
/// The file was not signed because an unexpected error happened.
|
||||
/// </summary>
|
||||
FileNotSignedError,
|
||||
|
||||
/// <summary>
|
||||
/// The file was not signed because the singing request was noth authorized.
|
||||
/// </summary>
|
||||
FileNotSignedUnauthorized
|
||||
}
|
||||
|
||||
public class SignFileResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The result status of the signing
|
||||
/// </summary>
|
||||
public SignFileResponseStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The detailed error message in case <see cref="Status"/> is set to <see cref="SignFileResponseStatus.FileNotSignedError"/>
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The result files consisting typically of the signed file.
|
||||
/// In some scenarios additional files might be provided (e.g. Android v4 idsig)
|
||||
/// </summary>
|
||||
public IList<SignFileResponseFileInfo> ResultFiles { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Codecepticon.Modules.Sign.MsSign
|
||||
{
|
||||
public class SignFileResponseFileInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the output file as it should be named on the client side.
|
||||
/// </summary>
|
||||
public string FileName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the disk holding the output file which should be sent to the client.
|
||||
/// </summary>
|
||||
public string OutputFilePath { get; }
|
||||
|
||||
public SignFileResponseFileInfo(string fileName, string outputFilePath)
|
||||
{
|
||||
FileName = fileName;
|
||||
OutputFilePath = outputFilePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Codecepticon.Modules.Sign.MsSign
|
||||
{
|
||||
internal sealed class UnmanagedStruct<T> : IDisposable
|
||||
where T : struct
|
||||
{
|
||||
public IntPtr Pointer { get; private set; }
|
||||
|
||||
public UnmanagedStruct()
|
||||
{
|
||||
Pointer = Marshal.AllocHGlobal(Marshal.SizeOf<T>());
|
||||
}
|
||||
|
||||
public void Fill(T value)
|
||||
{
|
||||
Marshal.StructureToPtr(value, Pointer, false);
|
||||
}
|
||||
|
||||
public UnmanagedStruct(T v) : this()
|
||||
{
|
||||
Marshal.StructureToPtr(v, Pointer, false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Pointer != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(Pointer);
|
||||
Pointer = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Codecepticon.Modules.Sign.MsSign
|
||||
{
|
||||
internal static class Win32SigningAPI
|
||||
{
|
||||
public const uint SIGN_CALLBACK_UNDOCUMENTED = 0x400;
|
||||
|
||||
public const string OID_OIWSEC_SHA1 = "1.3.14.3.2.26";
|
||||
public const string OID_RSA_MD5 = "1.2.840.113549.2.5";
|
||||
public const string OID_OIWSEC_SHA256 = "2.16.840.1.101.3.4.2.1";
|
||||
public const string OID_OIWSEC_SHA384 = "2.16.840.1.101.3.4.2.2";
|
||||
public const string OID_OIWSEC_SHA512 = "2.16.840.1.101.3.4.2.3";
|
||||
public const uint SIGNER_TIMESTAMP_RFC3161 = 2;
|
||||
|
||||
public const int S_OK = 0;
|
||||
|
||||
public const uint SIGNER_NO_ATTR = 0;
|
||||
public const uint SIGNER_CERT_STORE = 2;
|
||||
|
||||
public const uint SIGNER_CERT_POLICY_CHAIN = 2;
|
||||
|
||||
public const uint SIGNER_SUBJECT_FILE = 1;
|
||||
public const int E_INVALIDARG = unchecked((int)0x80070057);
|
||||
|
||||
public const string WINTRUST_ACTION_GENERIC_VERIFY_V2 = "{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}";
|
||||
|
||||
public const uint ALG_CLASS_HASH = (4 << 13);
|
||||
public const uint ALG_TYPE_ANY = (0);
|
||||
|
||||
public const uint ALG_SID_SHA1 = 4;
|
||||
public const uint ALG_SID_MD5 = 3;
|
||||
public const uint ALG_SID_SHA_256 = 12;
|
||||
public const uint ALG_SID_SHA_384 = 13;
|
||||
public const uint ALG_SID_SHA_512 = 14;
|
||||
|
||||
public const uint CALG_SHA1 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA1;
|
||||
public const uint CALG_MD5 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD5;
|
||||
public const uint CALG_SHA_256 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256;
|
||||
public const uint CALG_SHA_384 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_384;
|
||||
public const uint CALG_SHA_512 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_512;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct SIGNER_CERT
|
||||
{
|
||||
public uint cbSize;
|
||||
public uint dwCertChoice;
|
||||
public SIGNER_CERT_UNION union;
|
||||
public IntPtr hwnd;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct SIGNER_SIGNATURE_INFO
|
||||
{
|
||||
public uint cbSize;
|
||||
public uint algidHash;
|
||||
public uint dwAttrChoice;
|
||||
public SIGNER_SIGNATURE_INFO_UNION union;
|
||||
public /*PCRYPT_ATTRIBUTES*/ IntPtr psAuthenticated;
|
||||
public /*PCRYPT_ATTRIBUTES*/ IntPtr psUnauthenticated;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
|
||||
public struct SIGNER_SIGNATURE_INFO_UNION
|
||||
{
|
||||
[FieldOffset(0)] public /*PSIGNER_ATTR_AUTHCODE*/ IntPtr pAttrAuthcode;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SIGNER_CERT_STORE_INFO
|
||||
{
|
||||
public uint cbSize;
|
||||
public /*PCERT_CONTEXT*/ IntPtr pSigningCert;
|
||||
public uint dwCertPolicy;
|
||||
public IntPtr hCertStore;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
|
||||
public struct SIGNER_CERT_UNION
|
||||
{
|
||||
[FieldOffset(0)] public /*PSIGNER_CERT_STORE_INFO*/ IntPtr pSpcChainInfo;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
|
||||
public struct SIGNER_SUBJECT_INFO_UNION
|
||||
{
|
||||
[FieldOffset(0)] public /*PSIGNER_FILE_INFO*/ IntPtr pSignerFileInfo;
|
||||
// [FieldOffset(0)]
|
||||
// public SIGNER_BLOB_INFO* pSignerBlobInfo;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct SIGNER_FILE_INFO
|
||||
{
|
||||
public uint cbSize;
|
||||
public string pwszFileName;
|
||||
public IntPtr hFile;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct SIGNER_SUBJECT_INFO
|
||||
{
|
||||
public uint cbSize;
|
||||
public IntPtr pdwIndex;
|
||||
public uint dwSubjectChoice;
|
||||
public SIGNER_SUBJECT_INFO_UNION union;
|
||||
}
|
||||
|
||||
[DllImport("mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern int SignerSignEx3(
|
||||
[In] uint dwFlags,
|
||||
[In] /*PSIGNER_SUBJECT_INFO*/ IntPtr pSubjectInfo,
|
||||
[In] /*PSIGNER_CERT*/ IntPtr pSignerCert,
|
||||
[In] /*PSIGNER_SIGNATURE_INFO*/ IntPtr pSignatureInfo,
|
||||
[In, Optional] /*PSIGNER_PROVIDER_INFO*/ IntPtr pProviderInfo,
|
||||
[In, Optional] uint dwTimestampFlags,
|
||||
[In, Optional, MarshalAs(UnmanagedType.LPStr)]
|
||||
string pszAlgorithmOid,
|
||||
[In, Optional] string pwszTimestampURL,
|
||||
[In, Optional] /*PCRYPT_ATTRIBUTES*/ IntPtr psRequest,
|
||||
[In, Optional] IntPtr pSipData,
|
||||
[Out] /*PPSIGNER_CONTEXT*/IntPtr ppSignerContext,
|
||||
[In, Optional] IntPtr pCryptoPolicy,
|
||||
[In] /*SIGN_INFO*/IntPtr pSignInfo,
|
||||
[Optional] IntPtr pReserved
|
||||
);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SIGN_INFO
|
||||
{
|
||||
public uint cbSize;
|
||||
public IntPtr callback;
|
||||
public IntPtr pvOpaque;
|
||||
}
|
||||
|
||||
[DllImport("mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern int SignerFreeSignerContext(
|
||||
[In] /*PSIGNER_CONTEXT*/ IntPtr pSignerContext
|
||||
);
|
||||
|
||||
[DllImport("mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern int SignerTimeStamp(
|
||||
[In] /*PSIGNER_SUBJECT_INFO*/ IntPtr pSubjectInfo,
|
||||
[In] string pwszHttpTimeStamp,
|
||||
[In, Optional] /*PCRYPT_ATTRIBUTES*/ IntPtr psRequest,
|
||||
[In, Optional] IntPtr pSipData
|
||||
);
|
||||
|
||||
[DllImport("mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern int SignerTimeStampEx2(
|
||||
[In] uint dwFlags,
|
||||
[In] /*PSIGNER_SUBJECT_INFO*/ IntPtr pSubjectInfo,
|
||||
[In] string pwszHttpTimeStamp,
|
||||
[In, MarshalAs(UnmanagedType.LPStr)] string dwAlgId,
|
||||
[In, Optional] /*PCRYPT_ATTRIBUTES*/ IntPtr psRequest,
|
||||
[In, Optional] IntPtr pSipData,
|
||||
[Out] /*PPSIGNER_CONTEXT*/IntPtr ppSignerContext
|
||||
);
|
||||
|
||||
|
||||
public enum WinVerifyTrustResult : uint
|
||||
{
|
||||
Success = 0,
|
||||
ProviderUnknown = 0x800b0001, // Trust provider is not recognized on this system
|
||||
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
|
||||
|
||||
UntrustedRoot =
|
||||
0x800B0109, // CERT_E_UNTRUSTEDROOT - A certification chain processed correctly but terminated in a root certificate that is not trusted by the trust provider.
|
||||
|
||||
LocalSecurityOption =
|
||||
0x80092026 // CRYPT_E_SECURITY_SETTINGS
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct WINTRUST_DATA
|
||||
{
|
||||
public uint cbStruct;
|
||||
public IntPtr pPolicyCallbackData;
|
||||
public IntPtr pSIPClientData;
|
||||
public WinTrustDataUIChoice dwUIChoice;
|
||||
public WinTrustDataRevocationChecks fdwRevocationChecks;
|
||||
public WinTrustDataUnionChoice dwUnionChoice;
|
||||
public WINTRUST_DATA_UNION union;
|
||||
public WinTrustDataStateAction dwStateAction;
|
||||
public IntPtr hWVTStateData;
|
||||
public IntPtr pwszURLReference;
|
||||
public uint dwProvFlags;
|
||||
public uint dwUIContext;
|
||||
}
|
||||
|
||||
public enum WinTrustDataStateAction : uint
|
||||
{
|
||||
Verify = 0x00000001
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
|
||||
public struct WINTRUST_DATA_UNION
|
||||
{
|
||||
[FieldOffset(0)] public /*PWINTRUST_FILE_INFO*/ IntPtr pFile; // individual file
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct WINTRUST_FILE_INFO
|
||||
{
|
||||
public uint cbStruct;
|
||||
public string pcwszFilePath;
|
||||
public IntPtr hFile;
|
||||
public IntPtr pgKnownSubject;
|
||||
}
|
||||
|
||||
public enum WinTrustDataUIChoice : uint
|
||||
{
|
||||
None = 2
|
||||
}
|
||||
|
||||
public enum WinTrustDataRevocationChecks : uint
|
||||
{
|
||||
None = 0x00000000
|
||||
}
|
||||
|
||||
public enum WinTrustDataUnionChoice : uint
|
||||
{
|
||||
File = 1
|
||||
}
|
||||
|
||||
[DllImport("wintrust.dll", ExactSpelling = true, SetLastError = false, CharSet = CharSet.Unicode)]
|
||||
public static extern WinVerifyTrustResult WinVerifyTrust(
|
||||
[In] IntPtr hwnd,
|
||||
[In] [MarshalAs(UnmanagedType.LPStruct)]
|
||||
Guid pgActionID,
|
||||
[In] WINTRUST_DATA pWVTData
|
||||
);
|
||||
|
||||
[DllImport("imagehlp.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool ImageEnumerateCertificates(
|
||||
[In] SafeFileHandle FileHandle,
|
||||
[In] uint TypeFilter,
|
||||
[Out] out uint CertificateCount,
|
||||
[In, Out, Optional] uint[] Indices,
|
||||
[In, Optional] uint IndexCount
|
||||
);
|
||||
|
||||
public const uint CERT_SECTION_TYPE_ANY = 0xFF;
|
||||
|
||||
[DllImport("imagehlp.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool ImageRemoveCertificate(SafeFileHandle fileHandle, uint index);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct SIGNER_SIGN_EX3_PARAMS
|
||||
{
|
||||
public uint dwFlags;
|
||||
public /*PSIGNER_SUBJECT_INFO*/ IntPtr pSubjectInfo;
|
||||
public /*PSIGNER_CERT*/ IntPtr pSigningCert;
|
||||
public /*PSIGNER_SIGNATURE_INFO*/ IntPtr pSignatureInfo;
|
||||
public /*PSIGNER_PROVIDER_INFO*/ IntPtr pProviderInfo;
|
||||
public uint dwTimestampFlags;
|
||||
[MarshalAs(UnmanagedType.LPStr)] public string pszTimestampAlgorithmOid;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string pwszTimestampURL;
|
||||
public IntPtr psRequest;
|
||||
public /*PSIGN_INFO*/ IntPtr pSignCallback;
|
||||
public /*PPSIGNER_CONTEXT*/ IntPtr pSignerContext;
|
||||
public IntPtr pCryptoPolicy;
|
||||
public IntPtr pReserved;
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
||||
public delegate int SignCallback(
|
||||
[In, MarshalAs(UnmanagedType.SysInt)] IntPtr pCertContext,
|
||||
[In, MarshalAs(UnmanagedType.SysInt)] IntPtr pvExtra,
|
||||
[In, MarshalAs(UnmanagedType.U4)] uint algId,
|
||||
[In, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 4)]
|
||||
byte[] pDigestToSign,
|
||||
[In, MarshalAs(UnmanagedType.U4)] uint dwDigestToSign,
|
||||
[In, Out] ref CRYPTOAPI_BLOB blob
|
||||
);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct CRYPTOAPI_BLOB
|
||||
{
|
||||
public uint cbData;
|
||||
public IntPtr pbData;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct APPX_SIP_CLIENT_DATA
|
||||
{
|
||||
public /*PSIGNER_SIGN_EX2_PARAMS or PSIGNER_SIGN_EX3_PARAMS*/ IntPtr pSignerParams;
|
||||
public /*LPVOID*/ IntPtr pAppxSipState;
|
||||
}
|
||||
|
||||
public const int NTE_BAD_KEY = unchecked((int)0x80090003);
|
||||
public const int TRUST_E_SUBJECT_FORM_UNKNOWN = unchecked((int)0x800B0003);
|
||||
public const int TRUST_E_BAD_DIGEST = unchecked((int)0x80096010);
|
||||
public const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
|
||||
public const string szOID_OIWSEC_sha1 = "1.3.14.3.2.26";
|
||||
public const string szOID_NIST_sha256 = "2.16.840.1.101.3.4.2.1";
|
||||
|
||||
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern int SetDllDirectoryW(string strPathName);
|
||||
|
||||
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr LoadLibraryExW(string strFileName, IntPtr hFile, uint ulFlags);
|
||||
|
||||
[DllImport("Kernel32.dll", SetLastError = true)]
|
||||
public static extern bool FreeLibrary(IntPtr hModule);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct CRYPT_DATA_BLOB
|
||||
{
|
||||
internal uint cbData;
|
||||
internal IntPtr pbData;
|
||||
}
|
||||
|
||||
[DllImport("clr.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern int _AxlPublicKeyBlobToPublicKeyToken(
|
||||
[In] ref CRYPT_DATA_BLOB pCspPublicKeyBlob,
|
||||
[In, Out] ref IntPtr ppwszPublicKeyToken);
|
||||
|
||||
|
||||
[DllImport("clr.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern int _AxlGetIssuerPublicKeyHash(
|
||||
[In] IntPtr pCertContext,
|
||||
[In, Out] ref IntPtr ppwszPublicKeyHash);
|
||||
|
||||
[DllImport("clr.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern int CertTimestampAuthenticodeLicense(
|
||||
[In] ref CRYPT_DATA_BLOB pSignedLicenseBlob,
|
||||
[In] string pwszTimestampURI,
|
||||
[In, Out] ref CRYPT_DATA_BLOB pTimestampSignatureBlob);
|
||||
|
||||
[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool HeapFree(
|
||||
[In] IntPtr hHeap,
|
||||
[In] uint dwFlags,
|
||||
[In] IntPtr lpMem);
|
||||
|
||||
[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern IntPtr GetProcessHeap();
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct CRYPT_TIMESTAMP_PARA
|
||||
{
|
||||
public IntPtr pszTSAPolicyId;
|
||||
public bool fRequestCerts;
|
||||
public CRYPTOAPI_BLOB Nonce;
|
||||
public int cExtension;
|
||||
public IntPtr rgExtension;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct CRYPT_TIMESTAMP_CONTEXT
|
||||
{
|
||||
public uint cbEncoded;
|
||||
public IntPtr pbEncoded;
|
||||
public IntPtr pTimeStamp;
|
||||
}
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[DllImport("crypt32.dll", CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern void CryptMemFree(IntPtr pv);
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[DllImport("crypt32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
|
||||
public static extern bool CertFreeCertificateContext(IntPtr pCertContext);
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[DllImport("crypt32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
|
||||
public static extern bool CertCloseStore(IntPtr pCertContext, int dwFlags);
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[DllImport("crypt32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool CryptRetrieveTimeStamp(
|
||||
[In][MarshalAs(UnmanagedType.LPWStr)] string wszUrl,
|
||||
[In] uint dwRetrievalFlags,
|
||||
[In] int dwTimeout,
|
||||
[In][MarshalAs(UnmanagedType.LPStr)] string pszHashId,
|
||||
[In, Out] ref CRYPT_TIMESTAMP_PARA pPara,
|
||||
[In] byte[] pbData,
|
||||
[In] int cbData,
|
||||
[In, Out] ref IntPtr ppTsContext,
|
||||
[In, Out] ref IntPtr ppTsSigner,
|
||||
[In, Out] ref IntPtr phStore);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
using Codecepticon.CommandLine;
|
||||
using Codecepticon.Modules.Sign.MsSign;
|
||||
using Codecepticon.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -19,10 +21,6 @@ namespace Codecepticon.Modules.Sign
|
||||
GenerateCertificate();
|
||||
break;
|
||||
case CommandLineData.Action.Sign:
|
||||
if (!FindSignTool())
|
||||
{
|
||||
return;
|
||||
}
|
||||
SignExecutable();
|
||||
break;
|
||||
}
|
||||
@@ -50,39 +48,48 @@ namespace Codecepticon.Modules.Sign
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool FindSignTool()
|
||||
{
|
||||
if (String.IsNullOrEmpty(CommandLineData.Sign.SignTool))
|
||||
{
|
||||
Logger.Info("No signtool.exe specified, will look for it now...");
|
||||
SignToolManager signToolManager = new SignToolManager();
|
||||
CommandLineData.Sign.SignTool = signToolManager.Find();
|
||||
if (String.IsNullOrEmpty(CommandLineData.Sign.SignTool) || !File.Exists(CommandLineData.Sign.SignTool))
|
||||
{
|
||||
Logger.Error("Could not find signtool.exe");
|
||||
return false;
|
||||
}
|
||||
Logger.Info("Found signtool.exe: " + CommandLineData.Sign.SignTool);
|
||||
}
|
||||
return File.Exists(CommandLineData.Sign.SignTool);
|
||||
}
|
||||
|
||||
protected bool SignExecutable()
|
||||
{
|
||||
string stdOutput = "";
|
||||
string stdError = "";
|
||||
|
||||
Logger.Info("Signing executable...");
|
||||
SignToolManager signToolManager = new SignToolManager();
|
||||
bool result = signToolManager.SignExecutable(CommandLineData.Sign.SignTool, CommandLineData.Global.Project.Path, CommandLineData.Sign.NewCertificate.PfxFile, CommandLineData.Sign.NewCertificate.Password, ref stdOutput, ref stdError);
|
||||
if (!result)
|
||||
Logger.Info("Loading certificate...");
|
||||
|
||||
X509Certificate2 certificate;
|
||||
try
|
||||
{
|
||||
Logger.Error("There was an error while signing the file:");
|
||||
Logger.Error("", true, false);
|
||||
Logger.Error(stdError, true, false);
|
||||
certificate = new(CommandLineData.Sign.NewCertificate.PfxFile, CommandLineData.Sign.NewCertificate.Password);
|
||||
} catch (Exception e)
|
||||
{
|
||||
Logger.Error("Could not load PFX file: " + CommandLineData.Sign.NewCertificate.PfxFile);
|
||||
Logger.Error(e.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Info("Signing executable...");
|
||||
SignFileRequest request = new()
|
||||
{
|
||||
Certificate = certificate,
|
||||
PrivateKey = certificate.GetRSAPrivateKey(),
|
||||
OverwriteSignature = true,
|
||||
InputFilePath = CommandLineData.Global.Project.Path,
|
||||
HashAlgorithm = CommandLineData.Sign.SignatureAlgorithm,
|
||||
TimestampServer = CommandLineData.Sign.TimestampServer,
|
||||
};
|
||||
|
||||
PortableExecutableSigningTool signingTool = new();
|
||||
SignFileResponse response = signingTool.SignFile(request);
|
||||
if (response.Status != SignFileResponseStatus.FileSigned && response.Status != SignFileResponseStatus.FileResigned)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e)
|
||||
{
|
||||
Logger.Error("Could not sign executable");
|
||||
Logger.Error(e.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Logger.Success("Executable signed");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
"properties": "%properties%",
|
||||
"variables": "%variables%",
|
||||
"parameters": "%parameters%",
|
||||
"structs": "%structs%",
|
||||
"cmdline": "%cmdline%"
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -267,6 +267,12 @@
|
||||
<label class="form-check-label" for="csharp-rename-variables">Variables</label>
|
||||
</div>
|
||||
|
||||
<!-- Structs -->
|
||||
<div class="form-check form-switch">
|
||||
<input class="csharp-rename-item form-check-input" type="checkbox" data-argument="s" id="csharp-rename-structs">
|
||||
<label class="form-check-label" for="csharp-rename-structs">Structs</label>
|
||||
</div>
|
||||
|
||||
<!-- Command Line -->
|
||||
<div class="form-check form-switch">
|
||||
<input class="csharp-rename-item form-check-input" type="checkbox" data-argument="o" id="csharp-rename-commandline">
|
||||
@@ -486,11 +492,28 @@
|
||||
<input type="text" id="password" class="form-control" value="">
|
||||
</div>
|
||||
|
||||
<!-- Sign Tool Location -->
|
||||
<div class="mb-3 box-sign">
|
||||
<label for="signtool" class="form-label">Microsoft SignTool</label><small class="ms-2">(Location of signtool.exe on the system. Run 'cd C:\ && dir /b /s signtool.exe' to find)</small>
|
||||
<input type="text" id="signtool" class="form-control" value="" placeholder="Leave empty to auto-locate">
|
||||
<!-- Timestamp Server & Signature Algorithm -->
|
||||
<div class="row mb-3 box-sign">
|
||||
<div class="col">
|
||||
<div>
|
||||
<label for="timestamp" class="form-label">Timestamp Server</label>
|
||||
<input type="text" id="timestamp" class="form-control" value="" placeholder="http://timestamp.sectigo.com">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div>
|
||||
<label for="algorithm" class="form-label">Signature Algorithm</label><span class="ms-1 text-danger">*</span>
|
||||
<select class="form-select" id="algorithm">
|
||||
<option value="SHA256">SHA256</option>
|
||||
<option value="SHA384">SHA384</option>
|
||||
<option value="SHA512">SHA512</option>
|
||||
<option value="SHA1">SHA1</option>
|
||||
<option value="MD5">MD5</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -552,7 +575,8 @@
|
||||
certCopyFrom: function() { return this.element('#copy-from').value; },
|
||||
getDateTime: function(selector) { let value = this.element(selector).value.replace('T', ' '); if (value.length === 0) { return ''; }; let seconds = this.getRandomInt(0, 59); seconds = (seconds >= 10 ? seconds : '0' + seconds); return value + ':' + seconds; },
|
||||
getRandomInt: function(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); },
|
||||
signTool: function() { return this.element('#signtool').value; },
|
||||
getTimestamp: function() { return this.element('#timestamp').value; },
|
||||
getSignatureAlgorithm: function() { return this.element('#algorithm').value; },
|
||||
fileToSign: function() { return this.element('#executable').value; },
|
||||
|
||||
getRenameItems: function(module) {
|
||||
@@ -975,9 +999,12 @@
|
||||
output['path'] = PageData.quoteIfNeeded(PageData.fileToSign());
|
||||
}
|
||||
|
||||
// Sign tool.
|
||||
if (PageData.signTool().length > 0) {
|
||||
output['signtool'] = PageData.quoteIfNeeded(PageData.signTool());
|
||||
if (PageData.getTimestamp().length > 0) {
|
||||
output['timestamp'] = PageData.getTimestamp();
|
||||
}
|
||||
|
||||
if (PageData.getSignatureAlgorithm().length > 0) {
|
||||
output['algorithm'] = PageData.getSignatureAlgorithm();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Accenture Security
|
||||
Copyright (c) 2022 - 02/2024 Accenture Security
|
||||
Copyright (c) 03/2024 - Present - Pavel Tsakalidis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Codecepticon
|
||||
# Codecepticon 
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* [Functionality Deep Dive](docs/Functionality.md)
|
||||
* [Tips](docs/Tips.md)
|
||||
* [Known Issues](docs/KnownIssues.md)
|
||||
* [Sign Executables](#sign-executables)
|
||||
* [FAQ](#faq)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* [Contributions](#contributions)
|
||||
@@ -52,7 +53,7 @@ Codecepticon allows you to obfuscate and rewrite code, but also provides feature
|
||||
|
||||
### Open and Compile
|
||||
|
||||
Open Codecepticon, wait until all NuGet packages are downloaded and then build the solution.
|
||||
Open Codecepticon, wait until all NuGet packages are downloaded and then build the solution. You can also download a pre-compiled `Release`.
|
||||
|
||||
## Using Codecepticon
|
||||
|
||||
@@ -118,7 +119,7 @@ Therefore it is **critical** to always test your result in a local environment f
|
||||
|
||||
### Sign Executables
|
||||
|
||||
It is also possible to use Codecepticon to digitally sign executable files (your compiled output). Codecepticon can both generate a signing certificate for you, and also use it (or any other you may provide) to sign your target file. Please note that for this functionality you will need Microsoft's [SignTool](https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool) - although it should be automatically installed when you install Visual Studio 2022.
|
||||
It is also possible to use Codecepticon to digitally sign executable files (your compiled output). Codecepticon can both generate a signing certificate for you, and also use it (or any other you may provide) to sign your target file.
|
||||
|
||||
## FAQ
|
||||
|
||||
@@ -167,3 +168,4 @@ Whether it's a typo, a bug, or a new feature, Codecepticon is very open to contr
|
||||
* https://github.com/MagicMau/ProceduralNameGenerator
|
||||
* https://github.com/uwol/proleap-vb6-parser
|
||||
* https://github.com/dwyl/english-words
|
||||
* https://github.com/Danielku15/SigningServer
|
||||
@@ -173,6 +173,7 @@ The level of customisation supported by the C# module is:
|
||||
* Properties
|
||||
* Parameters
|
||||
* Variables
|
||||
* Structs
|
||||
* Command Line _(only for targets that have a pre-existing Profile - like SharpHound, Rubeus, etc)_
|
||||
|
||||
This means that it is possible to choose which identifiers should be obfuscated.
|
||||
|
||||
Reference in New Issue
Block a user