-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[ClickOnce] Add support for SHA384 and SHA512 signing for ClickOnce apps #12545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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")) |
There was a problem hiding this comment.
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.
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; |
There was a problem hiding this comment.
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.
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.
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; |
There was a problem hiding this comment.
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.
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.
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; |
There was a problem hiding this comment.
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.
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.
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