diff --git a/src/Compiler/AbstractIL/ilsign.fs b/src/Compiler/AbstractIL/ilsign.fs
index aef49313f9f..36d5b2d3563 100644
--- a/src/Compiler/AbstractIL/ilsign.fs
+++ b/src/Compiler/AbstractIL/ilsign.fs
@@ -331,6 +331,13 @@ let getPublicKeyForKeyPair keyBlob =
let rsaParameters = rsa.ExportParameters false
toCLRKeyBlob rsaParameters CALG_RSA_KEYX
+// Detect whether a byte array is a raw CAPI PRIVATEKEYBLOB (full key pair).
+// A raw key pair blob starts with bType=0x07 (PRIVATEKEYBLOB), bVersion=0x02.
+let isKeyPairBlob (blob: byte array) =
+ blob.Length > 8
+ && int blob.[0] = PRIVATEKEYBLOB
+ && int blob.[1] = BLOBHEADER_CURRENT_BVERSION
+
// Key signing
type keyContainerName = string
type keyPair = byte array
@@ -375,7 +382,12 @@ type ILStrongNameSigner =
| PublicKeySigner pk -> pk
| PublicKeyOptionsSigner pko ->
let pk, _ = pko
- pk
+ // If the blob is a full key pair (PRIVATEKEYBLOB), extract the public key
+ // to avoid embedding private key material in the assembly.
+ if isKeyPairBlob pk then
+ signerGetPublicKeyForKeyPair pk
+ else
+ pk
| KeyPair kp -> signerGetPublicKeyForKeyPair kp
| KeyContainer _ -> failWithContainerSigningUnsupportedOnThisPlatform ()
diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/misc/PublicSign.fs b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/misc/PublicSign.fs
index 5a4a75d6369..a753fecac3f 100644
--- a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/misc/PublicSign.fs
+++ b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/misc/PublicSign.fs
@@ -72,11 +72,54 @@ let x = 42
found <- true
found
- // Verify that the compiled DLL contains RSA magic bytes, confirming the public key blob was embedded
- let hasRSAMagic: bool =
- containsRSAMagic dllBytes rsa1Magic || containsRSAMagic dllBytes rsa2Magic
-
+ // Verify that the compiled DLL contains RSA1 (public key) magic and NOT RSA2 (private key) magic
+ let hasRSA1: bool = containsRSAMagic dllBytes rsa1Magic
+ let hasRSA2: bool = containsRSAMagic dllBytes rsa2Magic
+
Assert.True(
- hasRSAMagic,
- "Compiled DLL should contain RSA magic bytes (RSA1 or RSA2) indicating public key blob was embedded by compiler with --publicsign"
+ hasRSA1,
+ "Compiled DLL should contain RSA1 magic bytes indicating a public key blob was embedded"
+ )
+
+ Assert.False(
+ hasRSA2,
+ "Compiled DLL must NOT contain RSA2 magic bytes — private key material must not be embedded in the assembly"
)
+
+ ///
+ /// Tests that --publicsign with a full key pair (.snk) produces an assembly with a valid
+ /// public key blob that can be loaded by AssemblyName without throwing SecurityException.
+ /// This is the regression test for https://github.com/dotnet/fsharp/issues/19441.
+ ///
+ []
+ let ``--publicsign with full key pair produces valid public key blob`` () =
+ let source =
+ """
+module TestModule
+let x = 42
+"""
+
+ let snkPath: string =
+ Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "..", "..", "fsharp", "core", "signedtests", "sha1full.snk")
+
+ let result =
+ source
+ |> FSharp
+ |> asLibrary
+ |> withFileName "PublicSignValidKey.fs"
+ |> withOptions ["--publicsign+"; sprintf "--keyfile:%s" snkPath]
+ |> compile
+
+ result |> shouldSucceed |> ignore
+
+ let outputDll: string =
+ match result.OutputPath with
+ | Some path -> path
+ | None -> failwith "Compilation did not produce an output DLL"
+
+ // Loading the assembly name should not throw SecurityException for invalid public key
+ let assemblyName = System.Reflection.AssemblyName.GetAssemblyName(outputDll)
+ let publicKeyToken = assemblyName.GetPublicKeyToken()
+
+ Assert.NotNull(publicKeyToken)
+ Assert.True(publicKeyToken.Length > 0, "PublicKeyToken should be non-empty for a public-signed assembly")