Httplistener with HTTPS support
I did a bunch of homework and got this working. The steps to add SSL support for an .NET HttpListener are:
Update C# application code to include the
https
prefix. Example:String[] prefixes = { "http://*:8089/","https://*:8443/" };
That's it from the code aspect.
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 certificatemakecert -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.
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.
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();
Create self-signed certificate:*
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
)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.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:- From an administrative command prompt on the target machine, run
mmc
- Press Ctrl + M, or Click File > Add/Remove Snap-in
- Choose
Certificates
, and click Add > - In the dialog that appears, Choose
Computer Account
, and click Next - Choose
Local Computer
. Click Finish, then Okay
- From an administrative command prompt on the target machine, run
Import the certificate (
pfx
) into the Windows Certificate Store on the target machine- In the
mmc
window previously opened, drill down to Certificates (Local Computer) > Personal - Right-click on
Personal
, then click on All Tasks -> Import... - 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
orAll Files
in order to find it - 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
- On the last screen, choose
Place all certificates in the following store
. Verify that it saysPersonal
, then click Finish - Repeat the import procedure above for the
Trusted Root Certification Authorities
certificates section.
- In the
Create the port associations for your application. On Windows Vista and later, use
netsh
, as I did. (For Windows XP and earlier, usehttpcfg
)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 port8443
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: Thenetsh
mechanism is evidently a COM interface, judging from this question and its answers)
* Microsoft has redirected the SSL Binding link from here to there.
- The
Start up your web-server, and you're good to go!
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.
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.