Understanding TLS arragement for FTP project: ftp-srv in nodejs

Viewed 18

I am trying to understand how TLS can be set up accurately for my FTP project (typescript, nodejs) based on this API: ftp-srv

The documentation there is very basic. In one of the related github issues of the project, the author points to his source code / test example - here

In that example is a section:

  describe('#EXPLICIT', function () {
    before(() => {
      return server.close()
      .then(() => Promise.all([
        readFile(`${process.cwd()}/test/cert/server.key`),
        readFile(`${process.cwd()}/test/cert/server.crt`),
        readFile(`${process.cwd()}/test/cert/server.csr`)
      ]))
      .then(([key, cert, ca]) => startServer({
        url: 'ftp://127.0.0.1:8881',
        tls: {key, cert, ca}
      }))
      .then(() => {
        return connectClient({
          secure: true,
          secureOptions: {
            rejectUnauthorized: false,
            checkServerIdentity: () => undefined
          }
        });
      });
    });

From this example I can read that a programmer can create references to 3 crypto files:

 - ../server.key

 - ../server.crt

 - ../server.csr

than, to read these files and direct their output to a tls object e.g:

const tls: SecureContextOptions = {
  key:    <-- readFile('server.key'),
  cert:   <-- readFile('server.crt'),
  ca:     <-- readFile('server.csr'),
}

If this is right (correct me if wrong), I need the understanding of what the 3 files represent from the prospective of TLS.

TLS for NodeJS documentation is pretty clear - here

Following this documentation we create 5 (five) SSL crypto files:

file 1:  ryans-key.pem    <--- by command:  openssl genrsa -out ryans-key.pem 2048
file 2:  ryans-csr.pem    <--- by command:  openssl req -new -sha256 -key ryans-key.pem -out ryans-csr.pem
file 3:  ryans-cert.pem   <--- by command:  openssl x509 -req -in ryans-csr.pem -signkey ryans-key.pem -out ryans-cert.pem
file 4:  ca-cert.pem      <--- by command:  cat ryans-cert.pem > ca-cert.pem
file 5:  ryans.pfx        <--- by command:  openssl pkcs12 -export -in ryans-cert.pem -inkey ryans-key.pem -certfile ca-cert.pem -out ryans.pfx

Now, which of these five crypto files go to which property of the 'tls' object? Below here is my assumption and would ask some of you for correction:

const tls: SecureContextOptions = {
  key:    <-- readFile('server.key'),    =  <-- ryans-key.pem   ?
  cert:   <-- readFile('server.crt'),    =  <-- ryans-cert.pem  (or ca-cert.pem)  ?
  ca:     <-- readFile('server.csr'),    =  <-- ryans-csr.pem   ?
}

If other steps are required, please explain.

Further, related to this arrangement, which of the five SSL crypto files do I supply to the FTP client, so both, the server and the client are properly configured for a secure FTP communication?


I am assuming that this arrangement above, of the crypto files pointing to the properties of the 'tls' object for '#EXPLICIT' connections is practically the same for '#IMPLICIT' connections as well. Please correct me again, if not.


Last portion of this configuration is selection of the initial URL header. The ftp-srv documentations offers:

  • ftp - Plain FTP
  • ftps - Implicit FTP over TLS

for example:

"ftp://0.0.0.0:21"   or
"ftps://0.0.0.0:21"

It confuses me on how to programmatically organize the selection between:

  • pure FTP - no security/TLS involved and,
  • FTP with TLS via implicit mode and,
  • FTP with TLS via explicit mode
1 Answers

Here is what I figured out and is working.

Few understandings 1st.

  • The FTP server is based on node.js and API: ftp-srv
  • The FTP client is FileZilla

The standard config (non TLS) works OK on both client and server as per their documentations.

TLS:

ftp-srv has two modes:

  • ftp - explicit - recommended (is the same as without TLS)
  • ftps - implicit - (appears to be going out of fashion)

The TLS - explicit mode - config:

Generate crypto files: See here

openssl genrsa -out ryans-key.pem 2048
openssl req -new -sha256 -key ryans-key.pem -out ryans-csr.pem
openssl x509 -req -in ryans-csr.pem -signkey ryans-key.pem -out ryans-cert.pem
cat ryans-cert.pem > ca-cert.pem

In your node.js app (the example is for typescript):

import * as path from 'path';
import { promises as fsAsync, readFileSync } from "fs";
import { FtpServerOptions, FtpSrv } from 'ftp-srv';


configureTls(): Promise<SecureContextOptions> {

    const CRYPTO_FILES_DIR = // provide your dir where the crypto files are located
    
    const path_key:  string = path.join(CRYPTO_FILES_DIR , 'ryans-key.pem');
    const path_cert: string = path.join(CRYPTO_FILES_DIR , 'ryans-cert.pem');
    const path_ca:   string = path.join(CRYPTO_FILES_DIR , 'ca-cert.pem');
    
    
    const key:  string = await fsAsync.readFile(path_key,  'utf8');
    const cert: string = await fsAsync.readFile(path_cert, 'utf8');
    const ca:   string = await fsAsync.readFile(path_ca,   'utf8');
    
    
    const tls_ConfigOptions: SecureContextOptions = {
        key:  key,
        cert: cert,
        ca:   ca,
    }
    
    return tls_ConfigOptions;
}

                /**
                 * Start the server and listen for requests / connections
                 */
async startFtpServer() {

                // make this configurable ...
    const url: string = 'ftp://127.0.0.1:21'
    
    const ftpSrvCfg: FtpServerOptions = {
        url:      url,
        pasv_url: pasv_url,
        greeting: greeting,
        ...,
        tls = await this.configureTls()
    }
    
    ...
                // create the server instance with all its configs
    const ftpServer: FtpSrv = new FtpSrv(ftpSrvCfg);

                // FTP server is event driven.
                // Here we register the events we need or want to use.
    this.onUserLogin(ftpServer);
    this.onUserDisconnect(ftpServer);
    this.onClientError(ftpServer);

                // start listening for requests
    ftpServer.listen().then( () => {
                // your actions when the server starts ...
        ...
    }


                /**
                 * Occurs on each (user's) login attempt
                 */
    private onUserLogin(ftpServer: FtpSrv) {

        ftpServer.on('login',
                   async ({ connection, username, password }, resolve, reject) => { 
          ....
        }
    }

 



}

In the client - FileZilla:

  • Click menu - Edit > Settings (Settings box will appear)
  • Choose: Connection > SFTP
  • Click: Add key file, and navigate to the private key crypto file: ../ryans-key.pem
  • Click: OK

Start the server and try to connect. eg: ftp://127.0.0.1:21

Should work.

Related