diff --git a/README.md b/README.md index 938187a..74dce58 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. diff --git a/src/AzureSign.Core/AuthenticodeKeyVaultSigner.cs b/src/AzureSign.Core/AuthenticodeKeyVaultSigner.cs index 0a2b0a5..bb94f85 100644 --- a/src/AzureSign.Core/AuthenticodeKeyVaultSigner.cs +++ b/src/AzureSign.Core/AuthenticodeKeyVaultSigner.cs @@ -101,17 +101,25 @@ static char[] NullTerminate(ReadOnlySpan 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; diff --git a/test/AzureSign.Core.Tests/AuthenticodeKeyVaultSignerTests.cs b/test/AzureSign.Core.Tests/AuthenticodeKeyVaultSignerTests.cs index df5bbb4..aa5d7cf 100644 --- a/test/AzureSign.Core.Tests/AuthenticodeKeyVaultSignerTests.cs +++ b/test/AzureSign.Core.Tests/AuthenticodeKeyVaultSignerTests.cs @@ -112,6 +112,84 @@ 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(); @@ -119,6 +197,13 @@ private string GetFileToSign() 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() {