Skip to content

Commit

Permalink
Merge pull request #2 from ricaun-io/develop
Browse files Browse the repository at this point in the history
Version 1.0.1
  • Loading branch information
ricaun authored Sep 19, 2023
2 parents bec6e97 + d88e293 commit d73b87d
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 7 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [1.0.1] / 2023-09-19
- `Certificate` features `GetSignedFileSubject` and `GetSignedFileIssuer`

## [1.0.0] / 2023-09-19
- First Release


[vNext]: ../../compare/1.0.0...HEAD
[1.0.1]: ../../compare/1.0.0...1.0.1
[1.0.0]: ../../compare/1.0.0
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,26 @@ bool result = WinTrust.VerifyEmbeddedSignature(@"C:\Windows\explorer.exe");
```

### Certificate
Utility class to check if the file is signed.
Utility class to check the `Certificate` file is signed, subject and issuer.

```csharp
bool result = Certificate.IsSignedFile(@"C:\Windows\explorer.exe");
```

If you want to get the subject or issuer of the file, you can use the following methods:

``` csharp
string subject = Certificate.GetSignedFileSubject(@"C:\Windows\explorer.exe");
string issuer = Certificate.GetSignedFileIssuer(@"C:\Windows\explorer.exe");
```

If you want to get a specific field of the subject or issuer, you can use the following methods:

``` csharp
string communName = Certificate.GetSignedFileSubject(@"C:\Windows\explorer.exe", "cn"); // "Microsoft Windows"
string organization = Certificate.GetSignedFileIssuer(@"C:\Windows\explorer.exe", "o"); // "Microsoft Corporation"
```

### Dummy Certificate

The [signfile.pfx](ricaun.Security.WinTrust.Tests/signfile) is a dummy certificate created to sign the `ConsoleAppSignedNotTrusted.exe` file and test the `WinTrust.VerifyEmbeddedSignature` method.
Expand Down
2 changes: 1 addition & 1 deletion ricaun.Security.WinTrust.Tests/Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void VerifyEmbeddedSignature_ShouldBe(string fileName, bool isSignedTrust

[TestCase("C:\\Windows\\notepad.exe", false)]
[TestCase("C:\\Windows\\explorer.exe", true)]
public void VerifyEmbeddedSignaturePath_ShouldBe(string filePath, bool isSigned)
public void Windows_VerifyEmbeddedSignature_ShouldBe(string filePath, bool isSigned)
{
if (File.Exists(filePath) == false)
Assert.Ignore("File not found");
Expand Down
88 changes: 88 additions & 0 deletions ricaun.Security.WinTrust.Tests/Tests_Certificate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using NUnit.Framework;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace ricaun.Security.WinTrust.Tests
{
public class Tests_Certificate
{
string Directory => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

[TestCase("ConsoleAppSigned.exe", "Sectigo Public Code Signing CA R36")]
[TestCase("ConsoleAppSignedNotTrusted.exe", "signfile")]
public void GetSignedFileIssuerCode_ShouldBe(string fileName, string communName)
{
var filePath = Path.Combine(Directory, fileName);
var result = Certificate.GetSignedFileIssuer(filePath, "cn");
Assert.AreEqual(communName, result);
}

[TestCase("ConsoleAppSigned.exe", "ricaun")]
[TestCase("ConsoleAppSignedNotTrusted.exe", "signfile")]
public void GetSignedFileSubjectCode_ShouldBe(string fileName, string communName)
{
var filePath = Path.Combine(Directory, fileName);
var result = Certificate.GetSignedFileSubject(filePath, "cn");
Assert.AreEqual(communName, result);
}

[TestCase("ConsoleApp.exe", false)]
[TestCase("ConsoleAppSigned.exe", true)]
[TestCase("ConsoleAppSignedNotTrusted.exe", true)]
public void GetSignedFileIssuer_ShouldBe(string fileName, bool isNotEmpty)
{
var filePath = Path.Combine(Directory, fileName);
var result = Certificate.GetSignedFileIssuer(filePath);
Assert.AreEqual(isNotEmpty, !string.IsNullOrEmpty(result));
}

[TestCase("ConsoleApp.exe", false)]
[TestCase("ConsoleAppSigned.exe", true)]
[TestCase("ConsoleAppSignedNotTrusted.exe", true)]
public void GetSignedFileSubject_ShouldBe(string fileName, bool isNotEmpty)
{
var filePath = Path.Combine(Directory, fileName);
var result = Certificate.GetSignedFileSubject(filePath);
Assert.AreEqual(isNotEmpty, !string.IsNullOrEmpty(result));
}

[TestCase("C:\\Windows\\explorer.exe", "cn", "Microsoft Windows")]
[TestCase("C:\\Windows\\explorer.exe", "o", "Microsoft Corporation")]
public void Windows_GetSignedFileSubjectCode_ShouldBe(string filePath, string communName, string expected)
{
if (File.Exists(filePath) == false)
Assert.Ignore("File not found");

var result = Certificate.GetSignedFileSubject(filePath, communName);
Assert.AreEqual(expected, result);
}

[Test]
public void Internal_SplitAndGetStartWith()
{
var baseString = "CN=signfile, OU=signfile1, O=signfile2, L=signfile3, S=signfile4, C=signfile5";
var expectedValues = new Dictionary<string, string>() {
{"cn","signfile"},
{"ou","signfile1"},
{"o","signfile2"},
{"l","signfile3"},
{"s","signfile4"},
{"c","signfile5"},
};

foreach (var expectedValue in expectedValues)
{
var code = expectedValue.Key;
var expected = expectedValue.Value;

Assert.AreEqual(expected, Certificate.SplitAndGetStartWith(baseString, code));
Assert.AreEqual(expected, Certificate.SplitAndGetStartWith(baseString, code + '='), "= should be Ignore");
Assert.AreEqual(expected, Certificate.SplitAndGetStartWith(baseString, code.ToUpper()), "UpperCase be Ignore");
}

Assert.IsEmpty(Certificate.SplitAndGetStartWith(baseString, "a"));
Assert.IsEmpty(Certificate.SplitAndGetStartWith(baseString, "b"));
}
}
}
86 changes: 83 additions & 3 deletions ricaun.Security.WinTrust/Certificate.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,106 @@
namespace ricaun.Security.WinTrust
{
using System.Linq;
using System.Security.Cryptography.X509Certificates;

/// <summary>
/// Certificate
/// </summary>
public sealed class Certificate
{
/// <summary>
/// Try to <see cref="System.Security.Cryptography.X509Certificates.X509Certificate.CreateFromSignedFile"/> to check if the file is signed.
/// Try to <see cref="X509Certificate.CreateFromSignedFile"/> to check if the file is signed.
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public static bool IsSignedFile(string fileName)
{
try
{
System.Security.Cryptography.X509Certificates.X509Certificate.CreateFromSignedFile(fileName);
X509Certificate.CreateFromSignedFile(fileName);
return true;
}
catch
{
return false;
}
}

/// <summary>
/// Get SignedFileIssuer
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
/// <remarks>Return <see cref="string.Empty"/> if the <paramref name="fileName"/> is not signed.</remarks>
public static string GetSignedFileIssuer(string fileName)
{
if (IsSignedFile(fileName))
{
return X509Certificate.CreateFromSignedFile(fileName).Issuer;
}
return string.Empty;
}

/// <summary>
/// Get SignedFileSubject
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
/// <remarks>Return <see cref="string.Empty"/> if the <paramref name="fileName"/> is not signed.</remarks>
public static string GetSignedFileSubject(string fileName)
{
if (IsSignedFile(fileName))
{
return X509Certificate.CreateFromSignedFile(fileName).Subject;
}
return string.Empty;
}

/// <summary>
/// Get SignedFileIssuer with the <paramref name="code"/> start with.
/// </summary>
/// <param name="fileName"></param>
/// <param name="code"></param>
/// <remarks>Return <see cref="string.Empty"/> if the <paramref name="fileName"/> is not signed.</remarks>
public static string GetSignedFileIssuer(string fileName, string code)
{
var subject = GetSignedFileIssuer(fileName);

if (string.IsNullOrEmpty(subject)) return string.Empty;

return SplitAndGetStartWith(subject, code);
}

/// <summary>
/// Get SignedFileSubject with the <paramref name="code"/> start with.
/// </summary>
/// <param name="fileName"></param>
/// <param name="code"></param>
/// <remarks>Return <see cref="string.Empty"/> if the <paramref name="fileName"/> is not signed.</remarks>
public static string GetSignedFileSubject(string fileName, string code)
{
var subject = GetSignedFileSubject(fileName);

if (string.IsNullOrEmpty(subject)) return string.Empty;

return SplitAndGetStartWith(subject, code);
}

internal static string SplitAndGetStartWith(string subject, string codeStartWith)
{
const char ConstEqual = '=';
const char ConstSeparator = ',';

var texts = subject.Split(ConstSeparator)
.Select(s => s.Trim());

codeStartWith = codeStartWith.Trim().TrimEnd(ConstEqual) + ConstEqual;

if (texts.FirstOrDefault(t => t.StartsWith(codeStartWith, System.StringComparison.InvariantCultureIgnoreCase)) is string text)
{
return text.Remove(0, codeStartWith.Length);
}
return string.Empty;
}
}
}
}
2 changes: 1 addition & 1 deletion ricaun.Security.WinTrust/ricaun.Security.WinTrust.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

<PropertyGroup>
<PackageId>ricaun.Security.WinTrust</PackageId>
<Version>1.0.0</Version>
<Version>1.0.1</Version>
<ProjectGuid>{AC121D4A-F00B-4B94-AD1B-ED7665C4803A}</ProjectGuid>
</PropertyGroup>

Expand Down

0 comments on commit d73b87d

Please sign in to comment.