stunnel as HTTPS proxy with Let’s encrypt certificate on Windows

Note to readers: it’s the first time I’ve ever used stunnel or let’s encrypt, so I don’t really know what I’m doing.

I needed to put https interface over my http-only server (running on Windows). One of the recommended options I’ve found was using stunnel (stunnel.org).

Here’s a minimal config stunnel.config:

[MyService]
accept = 443
connect = 127.0.0.1:80
cert = stunnel.pem

The config accepts traffic from any host on 443 port (default HTTPS port) and redirects it to localhost:80.

To set up an encrypted connection, you need a certificate. This config uses stunnel.pem (PEM file format). Stunnel can (and does during installation) generate a self-signed one. But it will cause your browser to complain:

You could add that certificate to trusted certificates on your client machine. But this doesn’t scale. The proper way of getting rid of this warning is using a certificate signed by Certificate Authority.

The easiest way of getting it (for free, otherwise they’re not cheap) would be using letsencrypt.org. The cerificate would only be valid for 90 days, and needs to be renewed after that. To simplify creation and renewal, there are numerous clients and scripts. I went with ‘Certify SSL Manager’, because it’s one of the few that works on Windows. I did not compare it to other clients.

Using Certify SSL Manager

The interface is fairly simple. I had two notable issues with it:

1. DNS challenge to confirm that you own your domain

Let’s encrypt needs to confirm that you own a domain for which you’re issuing a certificate. One of the ways of doing that is DNS challenge. And if none of the builtin APIs work for the challenge, there’s a manual way of doing it. I used google domains, which needed manual verification. The challenge consists of adding a TXT DNS record requested by CertifyTheWeb app

[INF] Attempting Domain Validation: yourdomain.com
[INF] Registering and Validating yourdomain.com 
[INF] Performing automated challenge responses (yourdomain.com)
[INF] DNS: Creating TXT Record '_acme-challenge.yourdomain.com' with value 'AAAAAAAAAAAAAA-aaaaaaaaaaaaaaaaa-AAAAA', in Zone Id '' using API provider '(Update DNS Manually)'
[INF] DNS: (Update DNS Manually) :: Please login to your DNS control panel for the domain 'yourdomain.com' and create a new TXT record named: 
_acme-challenge.yourdomain.com 
with the value:
AAAAAAAAAAAAAA-aaaaaaaaaaaaaaaaa-AAAAA
[INF] Requesting Validation from Let's Encrypt: yourdomain.com

After you add the record, you need to go and press ‘Request Certificate’ again. However, when I added the record, it did not work. I then retried requesting certificate, it failed again while asking for another record. Turned out (duh), take time to propagate. So after you add the record, you need to wait till it becomes visible before pressing ‘Request Certificate’.

You can use ‘nslookup -type=txt _acme-challenge.yourdomain.com‘ and such to check the record is available.

2. How do you feed the certificate into stunnel?

Option 1: Using Certify The Web’s deployment tasks

Disable default deployment, as we won’t need it:

Certify The Web's UI to disable deployment

Then, on Tasks tab, add 3 tasks:

  1. Stop stunnel service
  2. Export certificate
  3. Start stunnel service
Certify The Web's UI with all 3 deployment tasks added

Stopping and starting service tasks should be self-explanatory (assuming you set it up as a service).

For “export certificate” task, select “PEM – Full Certificate Chain”, and of course specify the file path from where stunnel is going to load the certificate.

I did not change the “Authentication” field for either of the tasks.

Certify The Web's task parameters for 'export certificate' task

This should do the trick. Next time you run certificate update, stunnel will stop, certificate file will be updated and service is going to be started again.

Perhaps, this can be done more efficiently without stopping the service, I’m not sure if it needs to be restarted to reload the certificate, but that’s good enough for me.

Option 2: Store certificate in Windows’ certificate store (by default), convert and feed into stunnel manually

Stunnel is supposed to work with certificate store directly. You need to set engine = capi global option and engineId = capi per-service option. Server loaded the config, but my browsers refused to connect with errors like SSL_ERROR_NO_CYPHER_OVERLAP which means that the browser is expecting ciphers that stunnel (openssl, really) doesn’t provide. So this approach is not working out.

You need to export the certificate. And to start the process, find “Manage Computer Certificates” in windows control panel. Find your certificate in Personal section:

Then I tried exporting the certificate into pem file. I was getting some strange errors about keys while stunnel tried loading the config.

It was hard to find anything about this particular problem, but I finally found one here: https://serverfault.com/questions/926435/how-do-i-get-a-valid-ssl-public-certificate-from-windows-certificate-for-st

Turns out, I needed to export certificate + key, then put them into one pem file, and use that instead.

Before that, make sure your certificate does have the key (has this small key on its icon):

Then export key+certificate (you’ll need to create a password for your private key on the next step, and then use this key to open the key with openssl or stunnel):

After exporting key+certificate, use openssl to convert that to pem format. Something along these lines:

C:\PathToOpenSsl\openssl.exe pkcs12 -in fileGeneratedByWindows -out certAndKeyForStunnel.pem

You can also add -nodes flag to avoid entering password every time stunnel starts. Note: this is less secure, as if someone gets access to the certificate file, he can then use it to impersonate you, as the password is not needed.

I made a little bat file to simplify the process:

@echo off
IF %1.==. GOTO No1
IF %2.==. GOTO No2
"C:\Program Files (x86)\stunnel\bin\openssl.exe" pkcs12 -in %1 -out %2 -nodes
GOTO End1

:No1
ECHO No param 1 (source certificate)
GOTO End1

:No2
ECHO No param 2 (target certificate)
GOTO End1

:End1

After doing all that, I got my new certificate .pem file, used it in stunnel’s config, and was able to communicate with my server over https without the security warning.