QuorumDMS / ftp-srv

📮 Modern FTP Server
MIT License
387 stars 116 forks source link

Live renewal of the TLS options #331

Open bartbutenaers opened 2 years ago

bartbutenaers commented 2 years ago

Dear,

When working with FTPS, a keypair (private key and certificate) are being passed to the server constructor. Which means you need to restart the server every time the certificate is being renewed.

I use LetsEncrypt certificates for my home automation, which will expire every 3 months. Which means I have to renew the certificate every 3 months, and as a result I need to restart my FTP server every 3 months. Restarting my FTP server only to replace a certificate is not what I want. Because if one of my IP cams is uploading a video recording at that moment, that recording would be lost.

In NodeJs version 11, the setSecureContext function has been added to solve this. By calling this function you can instruct the server to use updated TLS options.

A bit of explanation:

  1. I start my FTP server with the old keypair via the TLS options:

    let options = {};
    options.tls = {
      url: "ftps://my_host_name:7021",
      key: fs.readFileSync("\path\to\old\key.pem"),
      cert: fs.readFileSync("\path\to\old\cert.pem")
    }
    let myFtpServer = new FtpServer(options);
  2. Now I can easily determine which certificate is being used by the FTP server (during the TLS handshake phase), by using this test code to connect to my FTP server:

    var testOptions = {
      host: "my_host_name",
      port: 7021,
      // Skip certificate validation, because we only want to do the handshake without failure on an invalid certificate
      checkServerIdentity: () => undefined,
      rejectUnauthorized: false
    }
    var tlsSocket = tls.connect(options, function () {
      let certificate = tlsSocket.getPeerCertificate();
      let validToDate = certificate.valid_to;
      ...
    })

    Which shows that my old (expired) certificate is being used:

    image

  3. Now I tell the server to start using my new certificate (using the new function from this pull request):

    let newTlsOptions = {
      key: fs.readFileSync("\path\to\new\key.pem"),
      cert: fs.readFileSync("\path\to\new\cert.pem")
    }
    myFtpServer.renewTlsOptions(newTlsOptions);
  4. When I repeat the test from step 2 again, now indeed I see that my new certificate is used by the server already:

    image

Note that this new certificate will only be used for new connections. For existing connections the old certificate will still being used, since the handshake phase is already finished. So there is no impact on existing connections!

Don't hesitate to ask for extra information or updates!

Bart

bartbutenaers commented 2 years ago

BTW I have now also added a section to the readme page:

image

matt-forster commented 2 years ago

I like it, and you didn't make any assumptions about how the user of the library should call it - awesome.

I don't see the readme update, does it need to be pushed to this branch still?

bartbutenaers commented 2 years ago

@matt-forster, Glad to hear that you like this proposal! Seems I forgot this morning to push my changes, before I left for my daily job. It should be visible now...