Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
100 changes: 100 additions & 0 deletions CODE_SIGNING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Code Signing Configuration

## Overview

AsyncAwaitBestPractices supports Windows Authenticode code signing to ensure compatibility with Windows 11 Smart App Control. This feature helps prevent the security warnings that occur when Smart App Control is enabled.

## How It Works

### Build Process
- Code signing is automatically enabled during Release builds when running on Windows
- Signing occurs after the build completes for each target framework
- The signing process is conditional and won't break builds when certificates are not available

### Requirements
- Valid Windows code signing certificate (`.pfx` file)
- `signtool.exe` available in PATH (included with Windows SDK)
- Windows build environment (signing is skipped on other platforms)

## Configuration

### Environment Variables
Set these environment variables to enable code signing:

```bash
WINDOWS_CODESIGN_CERTIFICATE=path/to/your/certificate.pfx
WINDOWS_CODESIGN_PASSWORD=your-certificate-password
```

### MSBuild Properties
Alternatively, you can set these MSBuild properties:

```xml
<PropertyGroup>
<CodeSigningCertificatePath>path/to/your/certificate.pfx</CodeSigningCertificatePath>
<CodeSigningCertificatePassword>your-certificate-password</CodeSigningCertificatePassword>
<CodeSigningTimestampServer>http://timestamp.digicert.com</CodeSigningTimestampServer>
</PropertyGroup>
```

## Azure DevOps Pipeline

The Azure DevOps pipeline is configured to automatically sign assemblies when:
1. A secure file named by `WINDOWS_CODESIGN_CERTIFICATE_NAME` variable is available
2. The `WINDOWS_CODESIGN_PASSWORD` variable is set
3. The build is running on Windows

### Pipeline Setup
1. Upload your code signing certificate as a secure file in Azure DevOps
2. Set the `WINDOWS_CODESIGN_CERTIFICATE_NAME` variable to the name of your secure file
3. Set the `WINDOWS_CODESIGN_PASSWORD` variable as a secret variable

## Certificate Requirements

### For Windows 11 Smart App Control
- Certificate must be issued by a trusted Certificate Authority
- Certificate must be valid for code signing
- Certificate should have timestamping enabled for long-term validity

### Recommended Certificate Authorities
Copy link
Preview

Copilot AI Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The recommended Certificate Authorities section should include more context about why these specific CAs are recommended for Windows code signing and Smart App Control compatibility.

Suggested change
### Recommended Certificate Authorities
### Recommended Certificate Authorities
The following Certificate Authorities are recommended because they are widely trusted for Windows code signing and are known to meet the requirements for Smart App Control compatibility. These CAs provide robust support for timestamping, ensuring long-term validity of signed code, and have a strong reputation for reliability:

Copilot uses AI. Check for mistakes.

- DigiCert
- Sectigo (formerly Comodo)
- GlobalSign
- Entrust

## Troubleshooting

### Common Issues
- **Certificate not found**: Ensure the certificate path is correct and the file exists
- **Invalid password**: Verify the certificate password is correct
- **Timestamp server unavailable**: The build will retry timestamping, or you can change the timestamp server URL
- **signtool not found**: Install Windows SDK or ensure signtool.exe is in your PATH

### Local Development
Code signing is disabled by default for local development. To test signing locally:
1. Obtain a code signing certificate
2. Set the required environment variables
3. Build in Release configuration on Windows

## Security Considerations

- **Never commit certificates to source control**
- **Use secure storage for certificates and passwords**
- **Regularly update certificates before expiration**
- **Use separate certificates for different environments if needed**

## Verification

### Checking if an Assembly is Signed
You can verify if an assembly is signed using:

```powershell
# PowerShell
Get-AuthenticodeSignature "AsyncAwaitBestPractices.dll"

# Command Prompt
signtool verify /pa "AsyncAwaitBestPractices.dll"
```

### Properties Dialog
Right-click the DLL file and select "Properties" β†’ "Digital Signatures" tab to view signature information.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ Available on NuGet: https://www.nuget.org/packages/AsyncAwaitBestPractices/
- `AsyncValueCommand<TExecute, TCanExecute> : IAsyncValueCommand<TExecute, TCanExecute>`
- [Usage instructions](#asyncawaitbestpracticesmvvm-2)

## Security & Code Signing

AsyncAwaitBestPractices supports **Windows Authenticode code signing** for compatibility with Windows 11 Smart App Control. This ensures that applications using the library won't be blocked by enhanced security features.

- **Automatic signing** in CI/CD pipeline when certificates are available
- **Conditional signing** - doesn't break local development
- **Platform detection** - only signs on Windows
- **Secure certificate storage** using Azure DevOps secure files

For detailed setup instructions, see [CODE_SIGNING.md](CODE_SIGNING.md).

## Setup

### AsyncAwaitBestPractices
Expand Down
14 changes: 14 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,29 @@ jobs:
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
failIfCoverageEmpty: true

# Download code signing certificate
- task: DownloadSecureFile@1
displayName: 'Download Windows Code Signing Certificate'
condition: and(eq(variables['Agent.OS'], 'Windows_NT'), ne(variables['WINDOWS_CODESIGN_CERTIFICATE_NAME'], ''), ne(variables['WINDOWS_CODESIGN_PASSWORD'], '')) # Only run this step on Windows and when certificate and password are available
inputs:
secureFile: $(WINDOWS_CODESIGN_CERTIFICATE_NAME)
name: codesigncert

- task: CmdLine@2
displayName: 'Pack AsyncAwaitBestPractices NuGet'
inputs:
script: 'dotnet pack $(PathToAsyncAwaitBestPracticesCsproj) -c Release -p:PackageVersion=$(NugetPackageVersion)'
env:
WINDOWS_CODESIGN_CERTIFICATE: $(codesigncert.secureFilePath)
WINDOWS_CODESIGN_PASSWORD: $(WINDOWS_CODESIGN_PASSWORD)

- task: CmdLine@2
displayName: 'Pack AsyncAwaitBestPractices.MVVM NuGet'
inputs:
script: 'dotnet pack $(PathToAsyncAwaitBestPracticesMVVMCsproj) -c Release -p:PackageVersion=$(NugetPackageVersion)'
env:
WINDOWS_CODESIGN_CERTIFICATE: $(codesigncert.secureFilePath)
WINDOWS_CODESIGN_PASSWORD: $(WINDOWS_CODESIGN_PASSWORD)

# check vulnerabilities
- powershell: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
<Configurations>Debug;Release</Configurations>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<GenerateDocumentationFile>true</GenerateDocumentationFile>

<!-- Code signing configuration for Windows Authenticode -->
<CodeSigningCertificatePath Condition="'$(CodeSigningCertificatePath)' == ''">$(WINDOWS_CODESIGN_CERTIFICATE)</CodeSigningCertificatePath>
<CodeSigningCertificatePassword Condition="'$(CodeSigningCertificatePassword)' == ''">$(WINDOWS_CODESIGN_PASSWORD)</CodeSigningCertificatePassword>
<CodeSigningTimestampServer Condition="'$(CodeSigningTimestampServer)' == ''">http://timestamp.digicert.com</CodeSigningTimestampServer>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)'=='Debug' ">
<!-- Manage TargetFrameworks for development (Debug Mode) -->
Expand All @@ -71,4 +76,12 @@
<ItemGroup>
<PackageReference Update="NETStandard.Library" PrivateAssets="all"/>
</ItemGroup>

<!-- Code signing target -->
<Target Name="SignAssembly" AfterTargets="AfterBuild"
Condition="'$(Configuration)' == 'Release' And '$(CodeSigningCertificatePath)' != '' And Exists('$(CodeSigningCertificatePath)') And $([MSBuild]::IsOSPlatform('windows'))">
<Message Text="Code signing $(TargetPath) with certificate $(CodeSigningCertificatePath)" Importance="high" />
<Exec Command="signtool sign /f &quot;$(CodeSigningCertificatePath)&quot; /t &quot;$(CodeSigningTimestampServer)&quot; /v &quot;$(TargetPath)&quot;"
ContinueOnError="false" />
</Target>
</Project>
13 changes: 13 additions & 0 deletions src/AsyncAwaitBestPractices/AsyncAwaitBestPractices.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@
<Configurations>Debug;Release</Configurations>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<GenerateDocumentationFile>true</GenerateDocumentationFile>

<!-- Code signing configuration for Windows Authenticode -->
<CodeSigningCertificatePath Condition="'$(CodeSigningCertificatePath)' == ''">$(WINDOWS_CODESIGN_CERTIFICATE)</CodeSigningCertificatePath>
<CodeSigningCertificatePassword Condition="'$(CodeSigningCertificatePassword)' == ''">$(WINDOWS_CODESIGN_PASSWORD)</CodeSigningCertificatePassword>
<CodeSigningTimestampServer Condition="'$(CodeSigningTimestampServer)' == ''">http://timestamp.digicert.com</CodeSigningTimestampServer>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)'=='Debug' ">
<!-- Manage TargetFrameworks for development (Debug Mode) -->
Expand Down Expand Up @@ -82,4 +87,12 @@
<ItemGroup Condition=" '$(Configuration)'=='Release' ">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
</ItemGroup>

<!-- Code signing target -->
<Target Name="SignAssembly" AfterTargets="AfterBuild"
Condition="'$(Configuration)' == 'Release' And '$(CodeSigningCertificatePath)' != '' And Exists('$(CodeSigningCertificatePath)') And $([MSBuild]::IsOSPlatform('windows'))">
<Message Text="Code signing $(TargetPath) with certificate $(CodeSigningCertificatePath)" Importance="high" />
<Exec Command="signtool sign /f &quot;$(CodeSigningCertificatePath)&quot; /t &quot;$(CodeSigningTimestampServer)&quot; /v &quot;$(TargetPath)&quot;"
ContinueOnError="false" />
</Target>
</Project>