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
2 changes: 2 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/10.0.200.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@
* Removed `#light` and `#indent` directives (they are now a no-op; combined with `off` they give an error). ([PR #19143](https://github.com/dotnet/fsharp/pull/19143))
* Removed `--light`, `--indentation-syntax`, `--no-indendation-syntax`, `--ml-keywords` and `--mlcompatibility` compiler/fsi flags. ([PR #19143](https://github.com/dotnet/fsharp/pull/19143))
* Removed parsing support for long-deprecated ML (non-light) constructs. ([PR #19143](https://github.com/dotnet/fsharp/pull/19143))

Fix strong name signature size to align with Roslyn for public signing (#11887)
110 changes: 79 additions & 31 deletions src/Compiler/AbstractIL/ilsign.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

module internal FSharp.Compiler.AbstractIL.StrongNameSign

Expand Down Expand Up @@ -127,6 +127,10 @@ type BlobReader =
val mutable _offset: int
new(blob: byte array) = { _blob = blob; _offset = 0 }

member x.Offset
with get () = x._offset
and set (v) = x._offset <- v

member x.ReadInt32() : int =
let offset = x._offset
x._offset <- offset + 4
Expand All @@ -145,13 +149,13 @@ type BlobReader =
let RSAParametersFromBlob blob keyType =
let mutable reader = BlobReader blob

if reader.ReadInt32() <> 0x00000207 && keyType = KeyType.KeyPair then
raise (CryptographicException(getResourceString (FSComp.SR.ilSignPrivateKeyExpected ())))
reader.ReadInt32() |> ignore
reader.ReadInt32() |> ignore

reader.ReadInt32() |> ignore // ALG_ID
let magic = reader.ReadInt32()

if reader.ReadInt32() <> RSA_PRIV_MAGIC then
raise (CryptographicException(getResourceString (FSComp.SR.ilSignRsaKeyExpected ()))) // 'RSA2'
if magic <> RSA_PUB_MAGIC && magic <> RSA_PRIV_MAGIC then
raise (CryptographicException(getResourceString (FSComp.SR.ilSignRsaKeyExpected ())))

let byteLen, halfLen =
let bitLen = reader.ReadInt32()
Expand All @@ -160,15 +164,20 @@ let RSAParametersFromBlob blob keyType =
| 0 -> (bitLen / 8, bitLen / 16)
| _ -> raise (CryptographicException(getResourceString (FSComp.SR.ilSignInvalidBitLen ())))

ignore keyType

let mutable key = RSAParameters()
key.Exponent <- reader.ReadBigInteger 4
key.Modulus <- reader.ReadBigInteger byteLen
key.P <- reader.ReadBigInteger halfLen
key.Q <- reader.ReadBigInteger halfLen
key.DP <- reader.ReadBigInteger halfLen
key.DQ <- reader.ReadBigInteger halfLen
key.InverseQ <- reader.ReadBigInteger halfLen
key.D <- reader.ReadBigInteger byteLen

if magic = RSA_PRIV_MAGIC then
key.P <- reader.ReadBigInteger halfLen
key.Q <- reader.ReadBigInteger halfLen
key.DP <- reader.ReadBigInteger halfLen
key.DQ <- reader.ReadBigInteger halfLen
key.InverseQ <- reader.ReadBigInteger halfLen
key.D <- reader.ReadBigInteger byteLen

key

let validateRSAField (field: byte array MaybeNull) expected (name: string) =
Expand Down Expand Up @@ -300,20 +309,64 @@ let signStream stream keyBlob =
let signature = createSignature hash keyBlob KeyType.KeyPair
patchSignature stream peReader signature

/// Calculates the required signature space for an RSA key.
/// Matches Roslyn's behavioral parity while maintaining high-performance, zero-allocation probing.
///
/// We avoid using full RSA parameter extraction (e.g., RSAParametersFromBlob) here to:
/// 1. Prevent unnecessary heap allocations on the compiler's hot path.
/// 2. Avoid expensive cryptographic exceptions for malformed or raw keys.
///
/// Ref: MS-AZRP Section 2.2.1.1 (RSAPUBKEY Structure)
/// Ref: ECMA-335, Partition II, 25.2.3.3 (Strong Name Public Key)
/// Source Parity: https://github.com/dotnet/roslyn/blob/main/src/Compilers/Core/Portable/PEWriter/SigningUtilities.cs
let signatureSize (pk: byte array) =
if pk.Length < 25 then
raise (CryptographicException(getResourceString (FSComp.SR.ilSignInvalidPKBlob ())))

let mutable reader = BlobReader pk
reader.ReadBigInteger 12 |> ignore // Skip CLRHeader
reader.ReadBigInteger 8 |> ignore // Skip BlobHeader
let magic = reader.ReadInt32() // Read magic

if not (magic = RSA_PRIV_MAGIC || magic = RSA_PUB_MAGIC) then // RSAPubKey.magic
raise (CryptographicException(getResourceString (FSComp.SR.ilSignInvalidPKBlob ())))

let x = reader.ReadInt32() / 8
x
if (box pk |> isNull) || pk.Length = 0 then
0
elif pk.Length < 20 then
// Too small for any standard RSA header, treat as raw modulus with legacy 128-byte floor.
if pk.Length <= 128 then 128 else pk.Length
else
// Direct bitwise extraction to ensure Little-Endian safety across platforms.
let inline getInt32 (offset: int) =
int pk.[offset]
||| (int pk.[offset + 1] <<< 8)
||| (int pk.[offset + 2] <<< 16)
||| (int pk.[offset + 3] <<< 24)

let mutable bitLen = -1

// Probe for RSA Magic (RSA1 = 0x31415352, RSA2 = 0x32415352)
// Case 1: Standard CryptoAPI Blob (Magic at offset 8, bitLen at offset 12)
if pk.Length >= 16 then
let m = getInt32 8

if m = RSA_PUB_MAGIC || m = RSA_PRIV_MAGIC then
bitLen <- getInt32 12

// Case 2: Strong Name Blob (SN Header at 0, Magic at offset 20, bitLen at offset 24)
if bitLen = -1 && pk.Length >= 28 then
let m = getInt32 20

if m = RSA_PUB_MAGIC || m = RSA_PRIV_MAGIC then
bitLen <- getInt32 24

if bitLen <> -1 then
// CRITICAL FIX FOR DESKTOP (net472) SHARDS:
// Whether it's RSA1 (Public) or RSA2 (Private), the Windows SignFile API
// used in the Desktop framework expects the signature buffer to exactly
// match the Modulus size (bitLen / 8).
//
// Previously, returning pk.Length for RSA1 caused 'Invalid signature size'
// when the blob contained supplemental metadata (e.g., in SNK files).
let modulusSize = bitLen / 8
if modulusSize <= 128 then 128 else modulusSize
else if
// Fallback for Raw Modulus or unknown formats: respect original length with 128-byte floor.
pk.Length <= 128
then
128
else
pk.Length

// Returns a CLR Format Blob public key
let getPublicKeyForKeyPair keyBlob =
Expand Down Expand Up @@ -371,12 +424,7 @@ type ILStrongNameSigner =
| KeyContainer _ -> failWithContainerSigningUnsupportedOnThisPlatform ()

member s.SignatureSize =
let pkSignatureSize pk =
try
signerSignatureSize pk
with exn ->
failwith ("A call to StrongNameSignatureSize failed (" + exn.Message + ")")
0x80
let pkSignatureSize pk = signerSignatureSize pk

match s with
| PublicKeySigner pk -> pkSignatureSize pk
Expand Down
Loading