Skip to content

Conversation

sujitnayak
Copy link
Contributor

@sujitnayak sujitnayak commented Sep 18, 2025

Fixes #

Context

ClickOnce does not support signing it payload with SHA384 and SHA512 certs.

Changes Made

Added support for signing with SHA384 and SHA512 cert instead of defaulting to SHA256 for .NET 4.X apps.

P.S.
Targeting my msbuild fork for this PR until the ClickOnce runtime is ready to support installing such apps.

Testing

Ongoing

Notes

@Copilot Copilot AI review requested due to automatic review settings September 18, 2025 19:46
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds support for SHA384 and SHA512 signing algorithms for ClickOnce applications, expanding beyond the previous SHA256-only support for .NET 4.x apps.

  • Added SHA384 and SHA512 signature description classes for cryptographic operations
  • Refactored existing code to support multiple hash algorithms instead of hardcoding SHA256
  • Updated manifest signing logic to dynamically select appropriate hash algorithms based on certificate properties

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/Tasks/ManifestUtil/mansign2.cs Main implementation with new ManifestSignatureMethods class and updated signing logic to support SHA384/SHA512
src/Tasks/ManifestUtil/SecurityUtil.cs Updated method names and logic to handle SHA256+ algorithms, including signtool command generation
src/Tasks/ManifestUtil/RSAPKCS1SHA384SignatureDescription.cs New signature description class for SHA384 RSA PKCS#1 signatures
src/Tasks/ManifestUtil/RSAPKCS1SHA512SignatureDescription.cs New signature description class for SHA512 RSA PKCS#1 signatures

byte[] hash = sha2.ComputeHash(exc.GetOutput() as MemoryStream);
if (hash == null)
#if FEATURE_CRYPTOGRAPHIC_FACTORY_ALGORITHM_NAMES
using (var algorithm = algorithmFactory("System.Security.Cryptography.SHA256CryptoServiceProvider"))
Copy link
Preview

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

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

The hardcoded SHA256CryptoServiceProvider string is incorrect when the certificate uses SHA384 or SHA512. The algorithm factory parameter should match the actual hash algorithm from the certificate, not always SHA256.

Copilot uses AI. Check for mistakes.

Comment on lines +283 to +307
internal static string GetHashAlgorithmName(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return "sha256"; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return "sha384"; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return "sha512"; }
return string.Empty;
}

internal static string GetHashAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return Sha256SignatureMethodUri; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return Sha384SignatureMethodUri; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return Sha512SignatureMethodUri; }
return string.Empty;
}

internal static string GetDigestAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return Sha256DigestMethod; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return Sha384DigestMethod; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return Sha512DigestMethod; }
return string.Empty;
Copy link
Preview

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

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

The repetitive if-statement pattern for algorithm name mapping should be replaced with a dictionary lookup for better maintainability and performance.

Suggested change
internal static string GetHashAlgorithmName(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return "sha256"; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return "sha384"; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return "sha512"; }
return string.Empty;
}
internal static string GetHashAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return Sha256SignatureMethodUri; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return Sha384SignatureMethodUri; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return Sha512SignatureMethodUri; }
return string.Empty;
}
internal static string GetDigestAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return Sha256DigestMethod; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return Sha384DigestMethod; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return Sha512DigestMethod; }
return string.Empty;
// Mapping dictionaries for algorithm name, signature method URI, and digest method URI
private static readonly Dictionary<string, string> HashAlgorithmNameMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "sha256RSA", "sha256" },
{ "sha384RSA", "sha384" },
{ "sha512RSA", "sha512" }
};
private static readonly Dictionary<string, string> HashAlgorithmUriMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "sha256RSA", Sha256SignatureMethodUri },
{ "sha384RSA", Sha384SignatureMethodUri },
{ "sha512RSA", Sha512SignatureMethodUri }
};
private static readonly Dictionary<string, string> DigestAlgorithmUriMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "sha256RSA", Sha256DigestMethod },
{ "sha384RSA", Sha384DigestMethod },
{ "sha512RSA", Sha512DigestMethod }
};
internal static string GetHashAlgorithmName(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
return HashAlgorithmNameMap.TryGetValue(oid.FriendlyName, out var name) ? name : string.Empty;
}
internal static string GetHashAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
return HashAlgorithmUriMap.TryGetValue(oid.FriendlyName, out var uri) ? uri : string.Empty;
}
internal static string GetDigestAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
return DigestAlgorithmUriMap.TryGetValue(oid.FriendlyName, out var uri) ? uri : string.Empty;

Copilot uses AI. Check for mistakes.

Comment on lines +283 to +307
internal static string GetHashAlgorithmName(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return "sha256"; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return "sha384"; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return "sha512"; }
return string.Empty;
}

internal static string GetHashAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return Sha256SignatureMethodUri; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return Sha384SignatureMethodUri; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return Sha512SignatureMethodUri; }
return string.Empty;
}

internal static string GetDigestAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return Sha256DigestMethod; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return Sha384DigestMethod; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return Sha512DigestMethod; }
return string.Empty;
Copy link
Preview

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

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

Similar to GetHashAlgorithmName, this repetitive pattern should use a dictionary lookup instead of multiple if-statements for better maintainability.

Suggested change
internal static string GetHashAlgorithmName(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return "sha256"; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return "sha384"; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return "sha512"; }
return string.Empty;
}
internal static string GetHashAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return Sha256SignatureMethodUri; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return Sha384SignatureMethodUri; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return Sha512SignatureMethodUri; }
return string.Empty;
}
internal static string GetDigestAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return Sha256DigestMethod; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return Sha384DigestMethod; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return Sha512DigestMethod; }
return string.Empty;
// Dictionaries for mapping Oid.FriendlyName to algorithm names/URIs/digests
private static readonly Dictionary<string, string> HashAlgorithmNameMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "sha256RSA", "sha256" },
{ "sha384RSA", "sha384" },
{ "sha512RSA", "sha512" }
};
private static readonly Dictionary<string, string> HashAlgorithmUriMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "sha256RSA", Sha256SignatureMethodUri },
{ "sha384RSA", Sha384SignatureMethodUri },
{ "sha512RSA", Sha512SignatureMethodUri }
};
private static readonly Dictionary<string, string> DigestAlgorithmUriMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "sha256RSA", Sha256DigestMethod },
{ "sha384RSA", Sha384DigestMethod },
{ "sha512RSA", Sha512DigestMethod }
};
internal static string GetHashAlgorithmName(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
return HashAlgorithmNameMap.TryGetValue(oid.FriendlyName, out var name) ? name : string.Empty;
}
internal static string GetHashAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
return HashAlgorithmUriMap.TryGetValue(oid.FriendlyName, out var uri) ? uri : string.Empty;
}
internal static string GetDigestAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
return DigestAlgorithmUriMap.TryGetValue(oid.FriendlyName, out var uri) ? uri : string.Empty;

Copilot uses AI. Check for mistakes.

Comment on lines +283 to +307
internal static string GetHashAlgorithmName(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return "sha256"; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return "sha384"; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return "sha512"; }
return string.Empty;
}

internal static string GetHashAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return Sha256SignatureMethodUri; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return Sha384SignatureMethodUri; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return Sha512SignatureMethodUri; }
return string.Empty;
}

internal static string GetDigestAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return Sha256DigestMethod; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return Sha384DigestMethod; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return Sha512DigestMethod; }
return string.Empty;
Copy link
Preview

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

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

This method also follows the same repetitive pattern and should be consolidated with a dictionary approach for consistency with the other mapping methods.

Suggested change
internal static string GetHashAlgorithmName(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return "sha256"; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return "sha384"; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return "sha512"; }
return string.Empty;
}
internal static string GetHashAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return Sha256SignatureMethodUri; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return Sha384SignatureMethodUri; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return Sha512SignatureMethodUri; }
return string.Empty;
}
internal static string GetDigestAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
if (string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase)) { return Sha256DigestMethod; }
if (string.Equals(oid.FriendlyName, "sha384RSA", StringComparison.OrdinalIgnoreCase)) { return Sha384DigestMethod; }
if (string.Equals(oid.FriendlyName, "sha512RSA", StringComparison.OrdinalIgnoreCase)) { return Sha512DigestMethod; }
return string.Empty;
// Dictionaries for mapping Oid.FriendlyName to algorithm values
private static readonly Dictionary<string, string> HashAlgorithmNameMap =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "sha256RSA", "sha256" },
{ "sha384RSA", "sha384" },
{ "sha512RSA", "sha512" }
};
private static readonly Dictionary<string, string> HashAlgorithmUriMap =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "sha256RSA", Sha256SignatureMethodUri },
{ "sha384RSA", Sha384SignatureMethodUri },
{ "sha512RSA", Sha512SignatureMethodUri }
};
private static readonly Dictionary<string, string> DigestAlgorithmUriMap =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "sha256RSA", Sha256DigestMethod },
{ "sha384RSA", Sha384DigestMethod },
{ "sha512RSA", Sha512DigestMethod }
};
internal static string GetHashAlgorithmName(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
return HashAlgorithmNameMap.TryGetValue(oid.FriendlyName, out var name) ? name : string.Empty;
}
internal static string GetHashAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
return HashAlgorithmUriMap.TryGetValue(oid.FriendlyName, out var uri) ? uri : string.Empty;
}
internal static string GetDigestAlgorithmUri(X509Certificate2 cert)
{
Oid oid = cert.SignatureAlgorithm;
return DigestAlgorithmUriMap.TryGetValue(oid.FriendlyName, out var uri) ? uri : string.Empty;

Copilot uses AI. Check for mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants