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")