B4J Library [server] LetsEncrypt SSL certificates

LetsEncrypt provides SSL certificates for free. The certificates are created and renewed using a tool named certbot. The process can be a bit confusing, especially if you want to automate it.
As B4J servers can run "forever" a solution that automates the steps and allows hot reloading of the renewed certificate is needed.
The attached class takes care of:
  1. Periodically calling certbot for a certificate creation or renewal. Certbot will not do anything if the existing certificate isn't close to its expiration date.
  2. Using OpenSSL + Java keytool to convert the certificate to a Java keystore that can be loaded by B4J server.
  3. Hot reloading of the new certificate.
Certbot creates a file under the B4J server www folder (through the file system) and then makes a http call to read and verify this file. This is done to verify the domain.

Requirements:
  1. Certbot needs to be installed: https://certbot.eff.org/
  2. OpenSSL needs to be installed. It is preinstalled on Mac and Linux.
    You can install Git which includes OpenSSL: https://gitforwindows.org/
  3. The server must be listening on port 80.
  4. On Windows the server must be started as an administrator. This is required by certbot. This is not required on Mac (not sure about Linux).
    Note that running B4J as an administrator will cause the running program to also run as admin.
Instructions:
  1. Add the module to the server project.
  2. The paths to CertBot and OpenSSL should be set in the module:
    B4X:
    'Mac
    Private Const CertBotExe As String = "/opt/homebrew/Cellar/certbot/2.9.0/bin/certbot"
    Private Const OpenSSLExe As String = "openssl"
    'Windows
    Private Const CertBotExe As String = "C:\Program Files\Certbot\bin\certbot.exe"
    Private Const OpenSSLExe As String = "c:\Program Files\Git\usr\bin\openssl.exe"
  3. In the main module the srvr variable needs to be made public and the following globals should be added, with the correct values:
    B4X:
    Public SslConfiguration As SslConfiguration
    Public KeystorePassword As String = "password here"
    Public Domain As String = "www.example.com"
    Public KeystoreFilename As String = "keystore.jks"
    The ConfigureSSL sub should be similar to:
    B4X:
    Private Sub ConfigureSSL (SslPort As Int)
        'example of SSL connector configuration
        SslConfiguration.Initialize 'this is the global variable
        SslConfiguration.SetKeyStorePath(File.DirApp, KeystoreFilename) 'path to keystore file
        SslConfiguration.KeyStorePassword = KeystorePassword
        SslConfiguration.KeyManagerPassword = KeystorePassword
        srvr.SetSslConfiguration(SslConfiguration, SslPort)
        'add filter to redirect all traffic from http to https
        'srvr.AddFilter("/*", "HttpsFilter", False)
    End Sub
  4. The LetsEncrypt class should be added with a background worker:
    B4X:
    srvr.AddBackgroundWorker("LetsEncrypt") 'before the server is started
Tips:
  1. The CertBot will create the files under <project>\Objects\certbot
  2. LetsEncrypt sets a limit on the number of certificates (for the same domain). The limit is easy to reach while testing after deleting the certificates. CertBot will not create a new certificate if not needed.
  3. You can comment the call to ConfigureSSL when running without a certificate. Once it was created, uncomment and restart the server.
  4. SSL connectors tutorial: https://www.b4x.com/android/forum/threads/server-ssl-connections.40130/#content
  5. I wasn't able to install CertBot on one of my Linux servers. Eventually I used a different client named getssl: https://github.com/srvrco/getssl
    For now I'm running it manually. The two commands that convert the generated certificate to a keystore:
    B4X:
    openssl pkcs12 -export -in fullchain.crt -inkey domain.com.key  -out serversout -passout pass:<password>
    keytool -importkeystore -deststorepass <password> -destkeypass <password> -destkeystore keystore.jks -srckeystore server.p12 -srcstoretype PKCS12 -srcstorepass <password> -alias b4j
  6. LetsEncrypt certificates are not recognized as valid certificates on Android 7- devices.
 

Attachments

  • LetsEncrypt.bas
    4.8 KB · Views: 223
Last edited:

aeric

Expert
Licensed User
Longtime User
Currently I create only one certificate in VPS using certbot and it is shared with apache and all B4J server apps.
With this module, does it mean that each B4J server has it's own certificate?
 

Sandman

Expert
Licensed User
Longtime User
For whatever it's worth, there are many alternatives to certbot, many which are considered more lightweight.

(I use Lego since forever for my Apache server and I never think about it, it renewes and does its thing rock solid.)
 

aeric

Expert
Licensed User
Longtime User
For whatever it's worth, there are many alternatives to certbot, many which are considered more lightweight.

(I use Lego since forever for my Apache server and I never think about it, it renewes and does its thing rock solid.)
I guess it maybe similar steps if you are replacing certbot with lego but the point here is we still need to hot reloading the new auto renewed certificates for B4J servers. So this module will take care for you automatically.
 

Sandman

Expert
Licensed User
Longtime User
I guess it maybe similar steps if you are replacing certbot with lego but the point here is we still need to hot reloading the new auto renewed certificates for B4J servers. So this module will take care for you automatically.
True. I just wanted to add that there are other clients, if someone wanted an alternative to certbot.

(I steered away from it because I felt it was just too invasive on my Linux server. I have no idea how it behaves on Windows servers, it might be just fine. And to be fair: It's very possible I'm overly grumpy, certbot absolutely is an appreciated client by the typical server admin, I just had problems understanding all the things it did. And I like to keep my servers lean so I can understand all (=most) of the things on it. Lego was so simple it was almost stupid. Perfect for me. :) )
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Currently I create only one certificate in VPS using certbot and it is shared with apache and all B4J server apps.
With this module, does it mean that each B4J server has it's own certificate?
If they are all using the same domain then you can share the keystore. Make one server create the certificate and the others should only check whether the file was modified and reload when needed.
 

mcqueccu

Well-Known Member
Licensed User
Longtime User
I currently use this website zerossl to generate certificate every 3 months for a shared hosting account

Using this method, does it give the necessary codes like
1. certificate
2. Private key and
3. Ca Bundle
that can be used on any shared hosting server which is not running java or b4j server?
 

JackKirk

Well-Known Member
Licensed User
Longtime User
I think I am about to display my profound ignorance once more...

I'm trying to implement this in the ABMaterial Mini Template example:

https://www.b4x.com/android/forum/t...ginners-update-2022-09-08.117237/#post-733213

I think I have been basically able to integrate it - had to make a few simple adjustments:

In Main/AppStart:
srvr.AddBackgroundWorker("LetsEncrypt") ====> Server.srvr.AddBackgroundWorker("LetsEncrypt")

In Main/ConfigureSSL:
srvr.SetSslConfiguration(SslConfiguration, SslPort) ====> Server.srvr.SetSslConfiguration(SslConfiguration, SslPort)

In LetsEncrypt/Initialize:
WWWFolder = Main.srvr.StaticFilesFolder ====> WWWFolder = Main.Server.srvr.StaticFilesFolder

But ConfigureSSL has me stumped - the above documentation of Erel's implies it is placed in Main, yet when you do that you get the warning "in not used (#12)".

Should it be somewhere else? if not what calls it?

Thanks...
 

hatzisn

Expert
Licensed User
Longtime User
Great stuff. While I was testing new solutions for my homelab and I was learning how the whole process works I came up in the solution of NginxProxyManager. I found everything I needed in this, for my homelab. It is a reverse proxy that runs in a docker container and what happens is that it exposes port 443 (SSL) or 80 outside and inside the homelab according to the domain name it diverts with single http the traffic to ip+port and thus all my B4J server apps while trying them. It takes care of Let's Encrypt SSLs renewal automatically without you needing to do anything. Recently before some months I came across a video that said that it has a security hole which later was patched and this security hole is accessible only when you expose the interface on-line. You could do at the time several tasks like password protect access to the exposed interface but I lost confidence in it. I got rid of it even though it is patched and is suposedely safe again.
 
Last edited:

hatzisn

Expert
Licensed User
Longtime User
OK, but what about ConfigureSSL? Where does it go? What calls it?

Before NginxProxyManager and Let's Encrypt I had tried buying an SSL and I used similar code in the server before server.Start. I suppose you call it yourself before server.Start providing the port your server listens to.
 
Top