1
votes

I'm using a self signed certificate to secure a websocket connection using Fleck. On the Fleck website it says:

Enabling secure connections requires two things: using the scheme wss instead of ws, and pointing Fleck to an x509 certificate containing a public and private key

I'm using Powershell to generate a CA then using the CA to sign a certificate and store it in the local machine store, no problem. However, when I retrieve the certificate in my application, the certificate doesn't include the private key and the websocket connection fails.

When I look at the certificate properties of a working certificate in Visual Studio and compare it with a failing certificate I see an exception being thrown on the PrivateKey portion of the failing certificate

Working Certificate Working Certificate

Failing Certificate Failing Certificate

I have been looking at this for hours and I can't figure out how to generate the correct certificate in Powershell. This is what I'm using to generate the certificate:

$rootcert = New-SelfSignedCertificate -CertStoreLocation cert:\CurrentUser\My -DnsName "Test CA" -KeyAlgorithm RSA -KeyUsage CertSign -HashAlgorithm SHA512 -KeyLength 4096 -NotAfter (Get-Date).AddYears(2) -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -KeyExportPolicy Exportable -KeyProtection None

Export-Certificate -Cert $rootcert -FilePath C:\Users\default\Desktop\TestRootCA.cer

Import-Certificate -FilePath C:\Users\default\Desktop\TestRootCA.cer -CertStoreLocation Cert:\LocalMachine\Root

$rootca = Get-ChildItem  cert:\CurrentUser\my | Where-Object {$_.Issuer.Contains("Test")}

New-SelfSignedCertificate -CertStoreLocation cert:\LocalMachine\My -Signer $rootca -FriendlyName "TEST" -Subject "TestTransactions" -KeyAlgorithm RSA -KeyLength 2048 -KeyUsage KeyEncipherment, DigitalSignature -HashAlgorithm SHA512 -TextExtension @("2.5.29.17={text}DNS=localhost&IPAddress=127.0.0.1","2.5.29.37={text}1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1") -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider"

I thought I had found the answer when I stumbled across this StackExchange post. One of the answers mentioned changing the provider for the certificate but that solve the problem for me. How can I generate an appropriate certificate using Powershell?

-- Edit --

When I attempt to access the private key the error message is: Keyset does not exist

and the stacktrace:

   at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
   at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
   at System.Security.Cryptography.RSACryptoServiceProvider.GetKeyPair()
   at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 dwKeySize, CspParameters parameters, Boolean useDefaultKeySize)
   at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey()
   at TWCP.Services.CertificateService.GetCertificateFromStore() in H:\Visual Studio Projects\TWCP\Services\CertificateService.cs:line 74
   at TWCP.ViewModels.MainWindowViewModel.Startup() in H:\Visual Studio Projects\TWCP\ViewModels\MainWindowViewModel.cs:line 139
   at TWCP.ViewModels.MainWindowViewModel.<get_StartupCommand>b__54_0() in H:\Visual Studio Projects\TWCP\ViewModels\MainWindowViewModel.cs:line 122
   at TWCP.Helpers.DelegateCommand.Execute(Object parameter) in H:\Visual Studio Projects\TWCP\Helpers\DelegateCommand.cs:line 20
   at System.Windows.Interactivity.InvokeCommandAction.Invoke(Object parameter)
   at System.Windows.Interactivity.TriggerBase.InvokeActions(Object parameter)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root, RoutedEvent routedEvent)
   at System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(Object root)
   at MS.Internal.LoadedOrUnloadedOperation.DoWork()
   at System.Windows.Media.MediaContext.FireLoadedPendingCallbacks()
   at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
   at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
   at System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)
   at System.Windows.Interop.HwndTarget.OnResize()
   at System.Windows.Interop.HwndTarget.HandleMessage(WindowMessage msg, IntPtr wparam, IntPtr lparam)
   at System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
1
Please post the full CryptographicException shown in the second screenshot (message + error code + stack trace) - Mathias R. Jessen
The definition quoted at beginning is wrong or at least seriously miswritten: a certificate NEVER contains a private key, since a certificate is a public information. It contains a PUBLIC key. Now you have PKIX container formats that allow you to store both a certificate and a private key in the same file, but those are 2 separate elements, one is not inside the other. "PFX" is indeed a shorthand for PKCS#12 which is one of those container formats. See en.wikipedia.org/wiki/PKCS_12 - Patrick Mevzek

1 Answers

1
votes

I've had good luck generating a self-signed certificate exported to PFX format, then importing that as needed with the chosen password. If this fits your scenario, here's my script:

$password = Read-Host -AsSecureString -Prompt "Choose password for generated certificate file"

$storeCert = New-SelfSignedCertificate -DnsName <YOUR_HOST> -FriendlyName <YOUR_NAME> -CertStoreLocation Cert:\CurrentUser\My

$storeCert | Export-PfxCertificate -FilePath <PATH> -Password $password | Out-Null
$storeCert | Remove-Item