Introduction
I have a digital code signing logic in my application. I use custom signing tool which signs dll files and create .sig text files with signed hash values. This tool runs on .NET 6, gets assembly list, path to pfx certificate file and a passphrase for it.
Signing code part
var cryptoProvider = new RSACryptoServiceProvider();
var rsaParameters = GetRsaPrivateKey(certificate.FullName, passphrase);
cryptoProvider.ImportParameters(rsaParameters);
foreach (var assembly in assemblies)
{
var assemblyBytes = File.ReadAllBytes(assembly.FullName);
var signedBytes = cryptoProvider.SignData(
assemblyBytes,
HashAlgorithmName.SHA256);
var hash = Convert.ToBase64String(signedBytes);
var sigFile = new FileInfo(Path.ChangeExtension(assembly.FullName, "sig"));
File.WriteAllText(sigFile.FullName, hash);
}
private static RSAParameters GetRsaPrivateKey(string certificatePath, string certificatePassphrase)
{
var certificate = new X509Certificate2(
certificatePath,
certificatePassphrase,
X509KeyStorageFlags.Exportable);
var privateKey = certificate.GetRSAPrivateKey();
if (privateKey != null)
{
using var exportRewriter = RSA.Create();
exportRewriter.ImportEncryptedPkcs8PrivateKey(
certificatePassphrase,
privateKey.ExportEncryptedPkcs8PrivateKey(
certificatePassphrase,
new PbeParameters(
PbeEncryptionAlgorithm.Aes128Cbc,
HashAlgorithmName.SHA256,
1)), out _);
return exportRewriter.ExportParameters(true);
}
After creating .sig file my console tool verifies digital signature importing only public key from pfx certificate
Verifying code signing part
var cryptoProvider = new RSACryptoServiceProvider();
var rsaParameters = GetRsaPublicKey(certificate.FullName, passphrase);
cryptoProvider.ImportParameters(rsaParameters);
foreach (var assembly in assemblies)
{
try
{
var sigFile = new FileInfo(Path.ChangeExtension(assembly.FullName, "sig"));
var assemblyBytes = File.ReadAllBytes(assembly.FullName);
var assemblySignedBytes = Convert.FromBase64String(File.ReadAllText(sigFile.FullName));
var verificationResult = cryptoProvider.VerifyData(
assemblyBytes,
HashAlgorithmName.SHA256,
assemblySignedBytes);
if (verificationResult)
{
Console.WriteLine($"Verification for assembly: {assembly} is successful.{Environment.NewLine}");
}
else
{
throw new SignToolException($"Verification for assembly: {assembly} " +
$"with sig file: {sigFile} is failed.");
}
}
catch
{
Console.WriteLine($"Assembly: {assembly} verification failed.{Environment.NewLine}");
throw;
}
}
private static RSAParameters GetRsaPublicKey(string certificatePath, string certificatePassPhrase)
{
var certificate = new X509Certificate2(
certificatePath,
certificatePassPhrase);
var publicKey = certificate.GetRSAPublicKey();
if (publicKey != null)
{
return publicKey.ExportParameters(false);
}
throw new SignToolException(
"Can't get public key from certificate." +
" Please check your certificate file and a passphrase");
}
Everything works great on .NET 6 I don't have any problems.
Also I have .NET 4.8 application which must verify signed dll's by reading signed hash value from corresponding .sig files and verify digital signature.
.NET 4.8 application digital signature verification part
var publicKeyBytes = GetPublicKeyFromResource(PublicKeyResourceKey);
CryptoServiceProvider = new RSACryptoServiceProvider();
CryptoServiceProvider.ImportCspBlob(publicKeyBytes);
public static bool VerifyAssemblyCodeSign(Assembly assembly)
{
var assemblyPath = new UriBuilder(assembly.CodeBase).Path;
var assemblySigLocation = Path.ChangeExtension(assemblyPath, SignatureFileExtension);
if (File.Exists(assemblySigLocation))
{
var assemblyBytes = File.ReadAllBytes(assemblyPath);
var assemblySignedBytes = Convert.FromBase64String(File.ReadAllText(assemblySigLocation));
return CryptoServiceProvider.VerifyData(
assemblyBytes,
assemblySignedBytes,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
}
return false;
}
private static byte[] GetPublicKeyFromResource(string resourceKey)
{
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceKey))
using (var memoryStream = new MemoryStream())
{
// ReSharper disable once PossibleNullReferenceException
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
This code reads exported from pfx certificate public key in binary format and imports it to CryptoServiceProvider
.
For exporting public key from pfx certificate I use this command
openssl rsa -in my.key -passin pass:mySecret -RSAPublicKey_out -outform "MS PUBLICKEYBLOB" -out my_rsa.pem
Problem
Code represented above works great on my local machine (Windows 11) and on my build server (Windows Server 2019 with latest patches) but it doesn't work on other Windows Server machines and on some other Windows machines. VerifyAssemblyCodeSign
method returns false without any other output.
I've tried different ways to import/export public key, tried RSACng
object, but it didn't help. I have no clue why it works on random Windows machines. I'll appreciate any help/suggestion.