HttpListener class with HTTPS support
Asked Answered
M

6

81

There seems to be a lot of confusing, sometimes conflicting, information with regards to making a .NET HttpListener HTTPS capable. My understanding is as follows:

  • One's C# code needs an https prefix (for example, https://*:8443) in order for the listener to understand that it needs to service SSL requests at this port.

  • The actual SSL handshake happens under the covers and is handled by http.sys (buried somewhere on the Windows machine). The C# code doesn't have to explicitly manage the SSL handshake, because it happens under the covers.

  • One needs to have a "X.509 trusted certificate" on the httpListener machine, and somehow that certificate needs to be bound to port 8443 (in this example).

Is my understanding above correct? If not, please educate me.

Regarding X.509 certificates, my understanding is:

  • Use makecert to create an X.509 certificate. This certificate gets stored in the personal store and needs to get moved over to the Trusted Store (this is where the HTTP listener will look). It seems I can use certMgr to perform the move, or I can use mmc to effect the move. It seems there is more than one X.509 certificate format (DER, Base64, pks, pswd protected, pks private, etc.)... Is there a preferred format I should use?

Once I get the certificate into the trusted store, I need to bind it to the TCP port. I am on Windows 7: should I be using httpcfg or netsh?

Milagro answered 9/7, 2012 at 21:19 Comment(0)
M
97

I did a bunch of homework and got this working. The steps to add SSL support for an .NET HttpListener are:

  1. Update C# application code to include the https prefix. Example:

    String[] prefixes = { "http://*:8089/","https://*:8443/" };
    

    That's it from the code aspect.

  2. For the certificate side of things, using the Windows SDK command console or Visual Studio Professional command console

    • Use makecert.exe to create a certificate authority. Example:

      makecert -n "CN=vMargeCA" -r -sv vMargeCA.pvk vMargeCA.cer
      
    • Use makecert.exe to create an SSL certificate

      makecert -sk vMargeSignedByCA -iv vMargeCA.pvk -n "CN=vMargeSignedByCA" -ic vMargeCA.cer vMargeSignedByCA.cer -sr localmachine -ss My
      
    • Use MMC GUI to install CA in Trusted Authority store

    • Use MMC GUI to install an SSL certificate in Personal store
    • Bind certificate to IP address:port and application. Example:

      netsh http add sslcert ipport=0.0.0.0:8443 certhash=585947f104b5bce53239f02d1c6fed06832f47dc appid={df8c8073-5a4b-4810-b469-5975a9c95230}
      

      The certhash is the thumbprint from your SSL certificate. You can find this using mmc. The appid is found in Visual Studio...usually in assembly.cs, look for the GUID value.

There may be other ways to accomplish the above, but this worked for me.

Milagro answered 12/7, 2012 at 17:55 Comment(12)
Huh, I tried do to everything according to these hints but I cannot get through the last step - it says that some param is not valid...Hazlett
I've noticed when I copy-and-paste into the command line that sometimes a '?' appears between "certhash=" and the actual key. Double-check you input.Basketwork
Any way to chain a root CA cert to an intermediate certificate?Charyl
@WalterKelt Your answer helped me greatly, and filled in almost all of the blanks in the existing documentation. However, there were a few I had to fill in myself, thus I posted my step-by-step process as an answer. Cheers!Teufert
@Moby04 Were you, by chance, using PowerShell? See my answer for a workaround.Teufert
excuse me @WalterKelt, how do we got appid?on Bind certificate to ip:port and application sectionPostobit
@WalterKelt just a guess, but probably the Guid in your AssemblyInfo file in the properties folder of the executable projectGerrigerrie
In my case there was no need to use mmc. Just right-click and select Install Certificate, then double click to open certificate and get certificate hash.Bernardinabernardine
What about certificate from Let's Encrypt? Do you use some cli on Windows for getting their certificate?Zaporozhye
This is very old, but I am facing a similar problem now. Last steps consist of adding the certificate to the local machine, but, how to do it for an application that is distributed to other people?Shin
This only allows one to add one SSL certificate to Port 443...Anstice
Thanks Walter, solved my https connection refused issue, but if anyone facing some error like parameter invalid: the appid should come before the certhash. At least for me it was a problem. FYI @TomaszKaplonskyTelford
T
47

Here are the steps, in detail, that I followed to set up a stand-alone server on Windows, using OpenSSL to create the self-signed certificate for a C# HTTPListener application. It includes plenty of links, in case you want to do further research.

  1. Create a stand-alone server in .NET via HttpListener:

     var prefixes = {"http://localhost:8080/app/root", "https://localhost:8443/app/root"};
     var listener = new HttpListener();
     foreach (string s in prefixes)
         listener.Prefixes.Add(s);
     listener.Start();
    
  2. Create self-signed certificate:*

  3. openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365, which will prompt you for the value of each of the certificate's fields on the command line. For the common name, type the domain name (e.g. localhost)

  4. openssl pkcs12 -inkey bob_key.pem -in bob_cert.cert -export -out bob_pfx.pfx, so that it can be imported with its key on the target machine.

*For an alternative using makecert, see Walter's own answer.

  1. Open Certificate Manager for the Local Machine. When you run certmgr.msc, it opens the Certificate Manager for the current user, which is not what we want here. Instead, run certlm.msc [Thanks, @Arkane], and if that doesn't work, then:

    1. From an administrative command prompt on the target machine, run mmc
    2. Press Ctrl + M, or Click File > Add/Remove Snap-in
    3. Choose Certificates, and click Add >
    4. In the dialog that appears, Choose Computer Account, and click Next
    5. Choose Local Computer. Click Finish, then Okay
  2. Import the certificate (pfx) into the Windows Certificate Store on the target machine

  3. In the mmc window previously opened, drill down to Certificates (Local Computer) > Personal

  4. Right-click on Personal, then click on All Tasks -> Import...

  5. In the 2nd screen of the dialog that appears, find and import your certificate. You'll have to change the file-type filter to Personal Information Exchange or All Files in order to find it

  6. On the next screen, enter the password you chose in step 2.1, and pay close attention to the first check box. This determines how securely your certificate is stored, and also how convenient it is to use

  7. On the last screen, choose Place all certificates in the following store. Verify that it says Personal, then click Finish

  8. Repeat the import procedure above for the Trusted Root Certification Authorities certificates section.

  9. Create the port associations for your application. On Windows Vista and later, use netsh, as I did. (For Windows XP and earlier, use httpcfg)

  • From the administrative command line, type the following to set up the SSL binding* to your app, and the appropriate port. NB: This command is easy to get wrong, because (in PowerShell) the braces need to be escaped. The following PowerShell command will work:

        netsh http add sslcert ipport=0.0.0.0:8443 `
            certhash=110000000000003ed9cd0c315bbb6dc1c08da5e6 `
            appid=`{00112233-4455-6677-8899-AABBCCDDEEFF`}
    

    For cmd.exe, the following should be used instead:

        netsh http add sslcert ipport=0.0.0.0:8443 certhash=110000000000003ed9cd0c315bbb6dc1c08da5e6 appid={00112233-4455-6677-8899-AABBCCDDEEFF}
    
    • The ipport parameter will cause the SSL certificate to bind to the port 8443 on every network interface; to bind to a specific interface (only), choose the IP address associated with that network interface.
    • The certhash is simply the certificate thumbprint, with spaces removed
    • The appid is the GUID stored in the Assembly Info of your application. (Sidenote: The netsh mechanism is evidently a COM interface, judging from this question and its answers)

    * Microsoft has redirected the SSL Binding link from here to there.

  1. Start up your web-server, and you're good to go!
Teufert answered 24/11, 2015 at 22:23 Comment(9)
@Jez IIRC, I didn't have an issue with using just the Trusted Root Certificate Authorities cert store. Is there something special about your setup which requires the cert to be in the Personal store as well?Teufert
When I ran the netsh command on the machine on which I'd installed the cert, I got the error "SSL Certificate add failed, Error 1312 - A specified logon session does not exist. It may already have been terminated." Looking at this answer to this question, it looks like the certificate is meant to be in the Personal store to install it through netsh (it talks about running certutil with my rather than root): https://mcmap.net/q/66377/-ssl-certificate-add-failed-when-binding-to-portManolete
On my Windows the command for generating the .pfx file hangs using Git-(Bash)-for-Windows. As a solution, just add winpty before the command according to openssl-hangs-during-pkcs12-export.Voncile
@Voncile Good tip! Unfortunately, it's really hard (in general) to predict which commands will work better with winpty versus without.Teufert
@jpaugh, I followed all steps you have given above, but I am getting error as "The certificate is not trusted because it is self-signed." Error code: MOZILLA_PKIX_ERROR_SELF_SIGNED_CERTOldfangled
@spr That's expected, because the certificate is self-signed. Navigating to a site with a self-signed certificate should be the exception rather than the rule, so your browser is right to warn you. Once you've verified that the cert fingerprint matches yours, you can tell the browser to make an exception for it. (Not sure why I didn't see your comment earlier, but perhaps others will benefit from it.)Teufert
Thanks for your time, but I myself forgot when did I comment it, but I have upvoted your answer, so I guess your answer worked for me, thanks:)Oldfangled
Just a note for future readers looking for their projects GUID, with Visual studio some projects no longer have an assembly info file. If that is the case, your project's GUID should be in your solution file. just open your .sln file in notepad and look for something like {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}Blowgun
For step 3, I think you can use certlm.msc to open the certificates manager for the local machine. (It works on W10 and Windows Server 2016 - not sure about other versions)Georg
C
7

We can import the certificates using PowerShell and C# (no manual steps required).

For details, see: https://blog.davidchristiansen.com/2016/09/howto-create-self-signed-certificates-with-powershell/

I'm using this code:

/// <summary>
/// Create and install a self-signed certificate for HTTPS use
/// </summary>
private static void CreateInstallCert(int expDate, string password, string issuedBy)
{
    // Create/install certificate
    using (var powerShell = System.Management.Automation.PowerShell.Create())
    {
        var notAfter = DateTime.Now.AddYears(expDate).ToLongDateString();
        var assemPath = Assembly.GetCallingAssembly().Location;
        var fileInfo = new FileInfo(assemPath);
        var saveDir = Path.Combine(fileInfo.Directory.FullName, "CertDir");
        if (!Directory.Exists(saveDir))
        {
            Directory.CreateDirectory(saveDir);
        }

        // This adds certificate to Personal and Intermediate Certification Authority
        var rootAuthorityName = "My-RootAuthority";
        var rootFriendlyName = "My Root Authority";
        var rootAuthorityScript =
            $"$rootAuthority = New-SelfSignedCertificate" +
            $" -DnsName '{rootAuthorityName}'" +
            $" -NotAfter '{notAfter}'" +
            $" -CertStoreLocation cert:\\LocalMachine\\My" +
            $" -FriendlyName '{rootFriendlyName}'" +
            $" -KeyUsage DigitalSignature,CertSign";
        powerShell.AddScript(rootAuthorityScript);

        // Export CRT file
        var rootAuthorityCrtPath = Path.Combine(saveDir, "MyRootAuthority.crt");
        var exportAuthorityCrtScript =
            $"$rootAuthorityPath = 'cert:\\localMachine\\my\\' + $rootAuthority.thumbprint;" +
            $"Export-Certificate" +
            $" -Cert $rootAuthorityPath" +
            $" -FilePath {rootAuthorityCrtPath}";
        powerShell.AddScript(exportAuthorityCrtScript);

        // Export PFX file
        var rootAuthorityPfxPath = Path.Combine(saveDir, "MyRootAuthority.pfx");
        var exportAuthorityPfxScript =
            $"$pwd = ConvertTo-SecureString -String '{password}' -Force -AsPlainText;" +
            $"Export-PfxCertificate" +
            $" -Cert $rootAuthorityPath" +
            $" -FilePath '{rootAuthorityPfxPath}'" +
            $" -Password $pwd";
        powerShell.AddScript(exportAuthorityPfxScript);

        // Create the self-signed certificate, signed using the above certificate
        var gatewayAuthorityName = "My-Service";
        var gatewayFriendlyName = "My Service";
        var gatewayAuthorityScript =
            $"$rootcert = ( Get-ChildItem -Path $rootAuthorityPath );" +
            $"$gatewayCert = New-SelfSignedCertificate" +
            $" -DnsName '{gatewayAuthorityName}'" +
            $" -NotAfter '{notAfter}'" +
            $" -certstorelocation cert:\\localmachine\\my" +
            $" -Signer $rootcert" +
            $" -FriendlyName '{gatewayFriendlyName}'" +
            $" -KeyUsage KeyEncipherment,DigitalSignature";
        powerShell.AddScript(gatewayAuthorityScript);

        // Export new certificate public key as a CRT file
        var myGatewayCrtPath = Path.Combine(saveDir, "MyGatewayAuthority.crt");
        var exportCrtScript =
            $"$gatewayCertPath = 'cert:\\localMachine\\my\\' + $gatewayCert.thumbprint;" +
            $"Export-Certificate" +
            $" -Cert $gatewayCertPath" +
            $" -FilePath {myGatewayCrtPath}";
        powerShell.AddScript(exportCrtScript);

        // Export the new certificate as a PFX file
        var myGatewayPfxPath = Path.Combine(saveDir, "MyGatewayAuthority.pfx");
        var exportPfxScript =
            $"Export-PfxCertificate" +
            $" -Cert $gatewayCertPath" +
            $" -FilePath {myGatewayPfxPath}" +
            $" -Password $pwd"; // Use the previous password
        powerShell.AddScript(exportPfxScript);

        powerShell.Invoke();
    }
}

Requires PowerShell 4 or higher.

Coz answered 14/4, 2019 at 17:48 Comment(1)
the Path.Combine and System.Management are missing in my VSFatten
R
7

The following command generates self-signed certificate for localhost for 10 years, imports it to the local computer storage and displays Thumbprint (certhash) in the output:

powershell -Command "New-SelfSignedCertificate -DnsName localhost -CertStoreLocation cert:\LocalMachine\My -NotAfter (Get-Date).AddYears(10)"

Then you can copy Thumbprint from output and attach the certificate to localhost:443 using netsh.exe, for example:

netsh http add sslcert ipport=localhost:443 certhash=110000000000003ed9cd0c315bbb6dc1c08da5e6 appid={00112233-4455-6677-8899-AABBCCDDEEFF}

Works on Windows 8 or higher. Requires administrator rights.

Ropeway answered 17/11, 2020 at 12:34 Comment(3)
This sounds very efficient and simple. Question - What is appid? - where do I get it from?Physic
@GoguCelMare this answer could help you #537673Cholecystotomy
i found i had to use 0.0.0.0 as the hostname instead of localhost, otherwise, works beautifully (y)Mccloud
E
1

As making your own self-signed certificates in the answers did not work for me and as the question specifically calls for making a .NET HTTPListener HTTPS capable and asks for any tips/advice, I want to share my approach.

You need a hostname, something like www.made-up.com which needs to point to your WAN IP address (e.g. ask your host provider for instructions) and forward its port, e.g. 443, to your local machine. Don't forget to open that inbound 443 port in your firewall of your local machine.

I used https://letsencrypt.org/. On Windows this not as easy as on Linux, because there isn't any official certbot ACME client for windows. However, you can use https://github.com/Lone-Coder/letsencrypt-win-simple, of which there are also binaries around. However "Currently only IIS is supported". But you can easily trick it to create a certificate on your computer such that you can approach your HTTP listener the SSL way:

  1. Install IIS (via Windows Features on/of), create a website within IIS and assign the hostname. Also make a secure (443 port) website of it.
  2. Run the letsencrypt-win-simple EXE file (I used version 1.9.1). Answer the questions to let it generate the certificate.
  3. After that you can stop de IIS server.

I believe you have to take note of the refresh task generated, since I am not sure it will succeed after a few months (you probably have to start IIS again for the certificate to be renewed).

Extinct answered 27/12, 2016 at 20:39 Comment(1)
certbot works with cygwin and IIS, if you're able to add the "." -> "text/html" mime type. Do "pip3 install certbot", then mount the web root into a standard posix path, then run "certbot certonly". Not the smoothest, but it does work.Ornament
O
0

Here are simple steps for getting it working (tested in c#7.0 console application, vs2022, win10)

Using HTTPListener with HTTPS

  • VS: c# httplistener project, add listener.Prefixes.Add($"https://192.168.1.xxx:4443/"); *or some other ip/port
  • Browser: Test connecting into it, error: "Secure Connection Failed : Error code: PR_CONNECT_RESET_ERROR"
  • PowerShell (as administrator) to get certhash using snippet below:
$certs = Get-ChildItem -Path Cert:\LocalMachine\My
foreach ($cert in $certs) {
    $thumbprint = $cert.Thumbprint
    $appid = [System.Guid]::NewGuid()
    Write-Host "Certificate certhash: $thumbprint"
    #Write-Host "Application ID (appid): $appid"
}
  • Pick one of those (2nd one is usually the correct?)
  • "appid" is from your vs studio *.sln solution file, SolutionGuid =
  • CommandPrompt (as administrator): netsh http add sslcert ipport=0.0.0.0:4443 certhash=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx appid={xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
  • Browser test reload: https://192.168.1.xxx:4443
  • Browser: Warning about insecure cert (ERR_CERT_AUTHORITY_INVALID), press advanced, Accept and Continue
  • Done!

OPTIONAL

  • Open firewall port for incoming TCP 4443 (if you want other computers from LAN to connect into your webserver)

UNINSTALLING

  • CommandPrompt (as administrator): netsh http delete sslcert ipport=0.0.0.0:4443

TROUBLESHOOTING

"SSL Certificate add failed, Error: 1312 A specified logon session does not exist. It may already have been terminated."

Wrong certhash with netsh command. Try other ones from the returned list.

"The parameter is incorrect."

Don't use ticks or quotes for appid='{...}' in netsh command

"Secure Connection Failed: Error code: Certificate type not approved for application., SEC_ERROR_INADEQUATE_CERT_TYPE"

Used wrong appid taken from powershell list, use another one.

Resources

Link to gist https://gist.github.com/unitycoder/ec217d20eecc2dfaf8d316acd8c3c5c5

Osteoma answered 2/11, 2023 at 10:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.