title: Having trouble converting external Windows Crypto API calls from C# to F#
body: <p>I've got some working C# code to work with some Windows API calls (based on <a href="https://github.com/brianary/scripts/blob/master/Get-CertificatePath.ps1#L64" rel="nofollow noreferrer" title="Gets the physical path on disk of a certificate&#39;s private key.">my Get-CertificatePath.ps1 PowerShell script</a>), but the F# version fails. I'm sure I'm missing something, most likely an attribute, but I haven't been able to figure it out.</p>
  
  <p>Is there a way to get the F# version working?</p>
  
  <h2>keyname.csx</h2>
  
  <p>Run using <code>csi.exe</code> from the <code>microsoft-build-tools</code> Chocolatey package.</p>
  
  <pre class="lang-cs prettyprint-override"><code>using System;
  using System.Diagnostics.CodeAnalysis;
  using System.Runtime.ConstrainedExecution;
  using System.Runtime.InteropServices;
  using System.Security;
  using System.Security.Cryptography;
  using System.Security.Cryptography.X509Certificates;
  using System.Security.Permissions;
  using Microsoft.Win32.SafeHandles;
  
  public static class CryptoApi
  {
      [DllImport("crypt32.dll")]
      internal static extern SafeNCryptKeyHandle CertDuplicateCertificateContext(IntPtr certContext); // CERT_CONTEXT *
      [DllImport("crypt32.dll", SetLastError = true)]
      [return: MarshalAs(UnmanagedType.Bool)]
      internal static extern bool
          CryptAcquireCertificatePrivateKey(SafeNCryptKeyHandle pCert,
                                            uint dwFlags,
                                            IntPtr pvReserved, // void *
                                            [Out] out SafeNCryptKeyHandle phCryptProvOrNCryptKey,
                                            [Out] out int dwKeySpec,
                                            [Out, MarshalAs(UnmanagedType.Bool)] out bool pfCallerFreeProvOrNCryptKey);
      [SecurityCritical]
      [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
      public static string GetCngUniqueKeyContainerName(X509Certificate2 certificate)
      {
          SafeNCryptKeyHandle privateKey = null;
          int keySpec = 0;
          bool freeKey = true;
          CryptAcquireCertificatePrivateKey(CertDuplicateCertificateContext(certificate.Handle),
                                            0x00040000, // AcquireOnlyNCryptKeys
                                            IntPtr.Zero, out privateKey, out keySpec, out freeKey);
          return CngKey.Open(privateKey, CngKeyHandleOpenOptions.None).UniqueName;
      }
  }
  
  X509Certificate2 getMyCert(string subject)
  {
      using(var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
      {
          store.Open(OpenFlags.OpenExistingOnly);
          var certs = store.Certificates.Find(X509FindType.FindBySubjectName, subject, false);
          Console.WriteLine("found {0} certs", certs.Count);
          store.Close();
          return certs[0];
      }
  }
  
  var cert = getMyCert("localhost");
  Console.WriteLine("private key? {0}", cert.HasPrivateKey);
  Console.WriteLine("key name: {0}", CryptoApi.GetCngUniqueKeyContainerName(cert));
  </code></pre>
  
  <p>The output is:</p>
  
  <pre><code>found 1 certs
  private key? True
  key name: 32fd60███████████████████████████████████-████-████-████-████████████
  </code></pre>
  
  <h2>keyname.fsx</h2>
  
  <p>Run using <code>dotnet fsi</code> (version 3.1.100; Chocolatey package <code>dotnetcore</code> as of this posting).</p>
  
  <pre class="lang-ml prettyprint-override"><code>#load ".paket/load/netstandard2.1/System.Security.Cryptography.Cng.fsx"
  #load ".paket/load/netstandard2.1/System.Security.Cryptography.X509Certificates.fsx"
  
  open System
  open System.Diagnostics.CodeAnalysis
  open System.Runtime.ConstrainedExecution
  open System.Runtime.InteropServices
  open System.Security
  open System.Security.Cryptography
  open System.Security.Cryptography.X509Certificates
  open System.Security.Permissions
  open Microsoft.Win32.SafeHandles
  
  type CryptoApi () =
      [&lt;DllImport("crypt32.dll", CallingConvention = CallingConvention.Cdecl)&gt;]
      static extern SafeNCryptKeyHandle  internal CertDuplicateCertificateContext(IntPtr certContext) // CERT_CONTEXT *
      [&lt;DllImport("crypt32.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)&gt;]
      static extern [&lt;MarshalAs(UnmanagedType.Bool)&gt;] bool internal
          CryptAcquireCertificatePrivateKey(SafeNCryptKeyHandle  pCert,
                                            uint32 dwFlags,
                                            IntPtr pvReserved, // void *
                                            SafeNCryptKeyHandle&amp; phCryptProvOrNCryptKey,
                                            int&amp; dwKeySpec,
                                            [&lt;MarshalAs(UnmanagedType.Bool)&gt;] bool&amp; pfCallerFreeProvOrNCryptKey)
      [&lt;SecurityCritical&gt;]
      [&lt;SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)&gt;]
      static member GetCngUniqueKeyContainerName (certificate : X509Certificate2) =
          let mutable privateKey : SafeNCryptKeyHandle = null
          let mutable keySpec = 0
          let mutable freeKey = true
          CryptAcquireCertificatePrivateKey(CertDuplicateCertificateContext(certificate.Handle),
                                            0x00040000u, // AcquireOnlyNCryptKeys
                                            IntPtr.Zero, &amp;privateKey, &amp;keySpec, &amp;freeKey) |&gt; ignore
          CngKey.Open(privateKey, CngKeyHandleOpenOptions.None).UniqueName
  
  let getMyCert subject =
      use store = new X509Store(StoreName.My, StoreLocation.CurrentUser)
      store.Open(OpenFlags.OpenExistingOnly)
      let certs = store.Certificates.Find(X509FindType.FindBySubjectName, subject, false)
      printfn "found %d certs" certs.Count
      store.Close()
      certs.[0]
  
  let cert = getMyCert "localhost"
  printfn "private key? %b" cert.HasPrivateKey
  CryptoApi.GetCngUniqueKeyContainerName(cert) |&gt; printfn "%s"
  </code></pre>
  
  <p>The output is:</p>
  
  <pre><code>found 1 certs
  private key? true
  System.ArgumentNullException: SafeHandle cannot be null. (Parameter 'pHandle')
     at System.StubHelpers.StubHelpers.SafeHandleAddRef(SafeHandle pHandle, Boolean&amp; success)
     at System.StubHelpers.StubHelpers.AddToCleanupList(CleanupWorkListElement&amp; pCleanupWorkList, SafeHandle handle)
     at FSI_0003.CryptoApi.CryptAcquireCertificatePrivateKey(SafeNCryptKeyHandle pCert, UInt32 dwFlags, IntPtr pvReserved, SafeNCryptKeyHandle&amp; phCryptProvOrNCryptKey, Int32&amp; dwKeySpec, Boolean&amp; pfCallerFreeProvOrNCryptKey)
     at FSI_0003.CryptoApi.GetCngUniqueKeyContainerName(X509Certificate2 certificate)
     at &lt;StartupCode$FSI_0003&gt;.$FSI_0003.main@()
  Stopped due to error
  </code></pre>
  
  <h2>paket.dependencies</h2>
  
  <p>This is required for the .fsx file above to work, then run <code>paket install</code>.</p>
  
  <pre><code>generate_load_scripts: true
  source https://api.nuget.org/v3/index.json
  
  storage: none
  framework: netcore3.0, netstandard2.0, netstandard2.1
  nuget System.Security.Cryptography.Cng
  nuget System.Security.Cryptography.X509Certificates
  </code></pre>
  
  <p><strong>Edit:</strong> Initializing <code>let mutable privateKey = new SafeNCryptKeyHandle ()</code> seems to fix the issue.</p>