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
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ The `--help` or `sign --help` option provides more detail about each parameter.
* `--skip-signed` [short: `-s`, required: no]: If a file is already signed it will be skipped, rather than replacing the existing
signature.

* `--append-signature` [short: `-as`, required: no]: If '.exe' or '.dll' file is already signed it will append the signature, just sign otherwise.
It has no effect when used with `--skip-signed` or not '.exe' and not '.dll' files.

### Advanced

* `--page-hashing` [short: `-ph`, required: no]: Causes the Authenticode signing process to generate hashes of pages for verifying when
Expand Down Expand Up @@ -148,7 +151,3 @@ a status code according to the complete signing operations.
## Requirements

Windows 10 or Windows Server 2016 is required.

## Current Limitations

Dual signing is not supported. This appears to be a limitation of the API used.
22 changes: 15 additions & 7 deletions src/AzureSign.Core/AuthenticodeKeyVaultSigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,25 @@ static char[] NullTerminate(ReadOnlySpan<char> str)

if (appendSignature)
{
if (Environment.OSVersion.Version < _win11Version)
if (!path.EndsWith(".dll".AsSpan(), StringComparison.InvariantCultureIgnoreCase) &&
!path.EndsWith(".exe".AsSpan(), StringComparison.InvariantCultureIgnoreCase))
{
// SignerSignEx3 silently succeeds with append on Windows 10 but does not actually append, so throw an error if we are not on Windows 11 or later.
throw new PlatformNotSupportedException("Appending signatures requires Windows 11 or later.");
logger?.LogWarning("SIG_APPEND is not supported for this file extention and will be ignored.");
}
if (_timeStampConfiguration.Type == TimeStampType.Authenticode)
else
{
// E_INVALIDARG is expected from SignerSignEx3, no need to override this error, log warning for troubleshooting
logger?.LogWarning("If you set the dwTimestampFlags parameter to SIGNER_TIMESTAMP_AUTHENTICODE, you cannot set the dwFlags parameter to SIG_APPEND.");
if (Environment.OSVersion.Version < _win11Version)
{
// SignerSignEx3 silently succeeds with append on Windows 10 but does not actually append, so throw an error if we are not on Windows 11 or later.
throw new PlatformNotSupportedException("Appending signatures requires Windows 11 or later.");
}
if (_timeStampConfiguration.Type == TimeStampType.Authenticode)
{
// E_INVALIDARG is expected from SignerSignEx3, no need to override this error, log warning for troubleshooting
logger?.LogWarning("If you set the dwTimestampFlags parameter to SIGNER_TIMESTAMP_AUTHENTICODE, you cannot set the dwFlags parameter to SIG_APPEND.");
}
flags |= SignerSignEx3Flags.SIG_APPEND;
}
flags |= SignerSignEx3Flags.SIG_APPEND;
}

SignerSignTimeStampFlags timeStampFlags;
Expand Down
85 changes: 85 additions & 0 deletions test/AzureSign.Core.Tests/AuthenticodeKeyVaultSignerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,98 @@ public void ShouldSignExeWithRSASigningCertificates_Sha256FileDigest_WithTimesta
}
}

[Theory]
[MemberData(nameof(RsaCertificates))]
public void ShouldSignPS1WithRSASigningCertificates_Sha1FileDigest(string certificate)
{
var signingCert = new X509Certificate2(certificate, "test", X509KeyStorageFlags.EphemeralKeySet);
var signer = new AuthenticodeKeyVaultSigner(signingCert.GetRSAPrivateKey(), signingCert, HashAlgorithmName.SHA1, TimeStampConfiguration.None);
var fileToSign = GetPS1FileToSign();
var result = signer.SignFile(fileToSign, null, null, null);
Assert.Equal(0, result);
result = signer.SignFile(fileToSign, null, null, null, appendSignature: true);
Assert.Equal(0, result);
result = signer.SignFile(fileToSign, null, null, null, appendSignature: false);
Assert.Equal(0, result);
}

[Theory]
[MemberData(nameof(RsaCertificates))]
public void ShouldSignPS1WithRSASigningCertificates_Sha256FileDigest(string certificate)
{
var signingCert = new X509Certificate2(certificate, "test", X509KeyStorageFlags.EphemeralKeySet);
var signer = new AuthenticodeKeyVaultSigner(signingCert.GetRSAPrivateKey(), signingCert, HashAlgorithmName.SHA256, TimeStampConfiguration.None);
var fileToSign = GetPS1FileToSign();
var result = signer.SignFile(fileToSign, null, null, null);
Assert.Equal(0, result);
result = signer.SignFile(fileToSign, null, null, null, appendSignature: true);
Assert.Equal(0, result);
result = signer.SignFile(fileToSign, null, null, null, appendSignature: false);
Assert.Equal(0, result);
}


[Theory]
[MemberData(nameof(ECDsaCertificates))]
public void ShouldSignPS1WithECDsaSigningCertificates_Sha256FileDigest(string certificate)
{
var signingCert = new X509Certificate2(certificate, "test", X509KeyStorageFlags.EphemeralKeySet);
var signer = new AuthenticodeKeyVaultSigner(signingCert.GetECDsaPrivateKey(), signingCert, HashAlgorithmName.SHA256, TimeStampConfiguration.None);
var fileToSign = GetPS1FileToSign();
var result = signer.SignFile(fileToSign, null, null, null);
Assert.Equal(0, result);
result = signer.SignFile(fileToSign, null, null, null, appendSignature: true);
Assert.Equal(0, result);
result = signer.SignFile(fileToSign, null, null, null, appendSignature: false);
Assert.Equal(0, result);
}

[Theory]
[MemberData(nameof(ECDsaCertificates))]
public void ShouldSignPS1WithECDsaSigningCertificates_Sha256FileDigest_WithTimestamps(string certificate)
{
var signingCert = new X509Certificate2(certificate, "test", X509KeyStorageFlags.EphemeralKeySet);
var timestampConfig = new TimeStampConfiguration("http://timestamp.digicert.com", HashAlgorithmName.SHA256, TimeStampType.RFC3161);
var signer = new AuthenticodeKeyVaultSigner(signingCert.GetECDsaPrivateKey(), signingCert, HashAlgorithmName.SHA256, timestampConfig);
var fileToSign = GetPS1FileToSign();
var result = signer.SignFile(fileToSign, null, null, null);
Assert.Equal(0, result);
result = signer.SignFile(fileToSign, null, null, null, appendSignature: true);
Assert.Equal(0, result);
result = signer.SignFile(fileToSign, null, null, null, appendSignature: false);
Assert.Equal(0, result);
}


[Theory]
[MemberData(nameof(RsaCertificates))]
public void ShouldSignPS1WithRSASigningCertificates_Sha256FileDigest_WithTimestamps(string certificate)
{
var signingCert = new X509Certificate2(certificate, "test", X509KeyStorageFlags.EphemeralKeySet);
var timestampConfig = new TimeStampConfiguration("http://timestamp.digicert.com", HashAlgorithmName.SHA256, TimeStampType.RFC3161);
var signer = new AuthenticodeKeyVaultSigner(signingCert.GetRSAPrivateKey(), signingCert, HashAlgorithmName.SHA256, timestampConfig);
var fileToSign = GetPS1FileToSign();
var result = signer.SignFile(fileToSign, null, null, null);
Assert.Equal(0, result);
result = signer.SignFile(fileToSign, null, null, null, appendSignature: true);
Assert.Equal(0, result);
result = signer.SignFile(fileToSign, null, null, null, appendSignature: false);
Assert.Equal(0, result);
}
private string GetFileToSign()
{
var guid = Guid.NewGuid();
var path = Path.Combine(_scratchDirectory.FullName, $"{guid}.exe");
File.Copy("signtarget.exe", path);
return path;
}
private string GetPS1FileToSign()
{
var guid = Guid.NewGuid();
var path = Path.Combine(_scratchDirectory.FullName, $"{guid}.ps1");
File.WriteAllText(path, "Write-Host \"Hello, World!\"");
return path;
}

public void Dispose()
{
Expand Down