1

I am trying to setup a server and some clients using TLS in node. I am using self-signed certificates on the clients and the server. The server runs ok, but when I try to connect a client I end up with the following error on the client side:

Error: DEPTH_ZERO_SELF_SIGNED_CERT

Cert Generation

CA cert:

    # Create key to sign our own certs. Act like as our own a CA.
    echo "SecuresPassphrase" > ./tls/passphrase.txt
    openssl ecparam -name secp521r1 -genkey -noout -out ./tls/certs/ca/ca.plain.key # Generate private key
    openssl pkcs8 -topk8 -passout file:./tls/passphrase.txt -in ./tls/certs/ca/ca.plain.key -out ./tls/certs/ca/ca.key   # Generate encrypted private key
    rm ./tls/certs/ca/ca.plain.key; # Remove unencrypted private key
    # Generate CA cert
    openssl req -config ./openssl/oid_file -passin file:./tls/passphrase.txt -new -x509 -days 365 -key ./tls/certs/ca/ca.key -out ./tls/certs/ca/ca.crt

Server Cert:

    openssl ecparam -name secp521r1 -genkey -noout -out ./tls/certs/servers/server.key # Generate server private key
    openssl req -config ./openssl/oid_file -new -key ./tls/certs/servers/server.key -out ./tls/certs/servers/server.csr # Generate signing request
    openssl x509 -passin file:./tls/passphrase.txt -req -days 365 -in ./tls/certs/servers/server.csr -CA ./tls/certs/ca/ca.crt -CAkey ./tls/certs/ca/ca.key -CAcreateserial -out ./tls/server.crt
    mv ./tls/server.crt ./tls/certs/servers/

Client cert:

Client's certs are created inside a bash loop, the variable ${name} contains the name of the client and changes each iteration.

    openssl ecparam -name secp521r1 -genkey -noout -out ./tls/certs/clients/${name}/client.key
    openssl req -config ./openssl/oid_file -new -key ./tls/certs/clients/${name}/client.key -out ./tls/certs/clients/${name}/client.csr
    openssl x509 -passin file:./tls/passphrase.txt -req -days 365 -in ./tls/certs/clients/${name}/client.csr -CA ./tls/certs/ca/ca.crt -CAkey ./tls/certs/ca/ca.key -CAcreateserial -out ./tls/client.crt
    mv ./tls/client.crt ./tls/certs/clients/${name}/

I am also trying to use Perfect Forward Secrecy by using ephemereal Diffie-Hellman interchange. The parameters, for clients and server, are created as openssl dhparam -outform PEM -out ./tls/params/servers/server/dhparams.pem 2048

Server's code:

return new Promise(resolve => {
            // Define parameters of TLS socket
            const options = {
                rejectUnauthorized: false,  
                requestCert: true,
                // Secure Context Parameters
                ca: [fs.readFileSync("tls/certs/ca/ca.crt")], 
                cert: fs.readFileSync("tls/certs/servers/server.crt"),
                key: fs.readFileSync("tls/certs/servers/server.key"),
                ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:!RC4:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!SRP:!CAMELLIA",
                dhparam: fs.readFileSync("tls/params/servers/server/dhparams.pem"),
                ecdhCurve: tls.DEFAULT_ECDH_CURVE,
                minVersion: "TLSv1.2"
            };
            // Iniciar servidor TLS
            this.SERVIDOR = tls.createServer(options, function (socket) {
                console.log("Server created."); 
            });

            this.SERVIDOR.listen(this.puerto, this.direccion, function () {
                console.log("Listening");
            });

            this.SERVIDOR.on("secureConnection", (socket) => this.handleRequest(socket));

            this.SERVIDOR.on("tlsClientError", (error) => console.log("Error client TLs. %s", error));
        });

Client's code:

return new Promise(resolve => {
            // Define parameters of TLS socket
            const options = {
                host: this.NODE,
                port: this.NODE_PORT,
                rejectUnauthorized: false,
                secureContext: tls.createSecureContext({
                    ca: [fs.readFileSync("tls/certs/ca/ca.crt")],
                    cert: fs.readFileSync("tls/certs/clients/" + this.NAME + "/client.crt"),
                    key: fs.readFileSync("tls/certs/clients/" + this.NAME + "/client.key"),
                    ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:!RC4:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!SRP:!CAMELLIA",
                    dhparam: fs.readFileSync("tls/params/clients/" + this.NAME + "/dhparams.pem"),
                    ecdhCurve: tls.DEFAULT_ECDH_CURVE, 
                    minVersion: "TLSv1.2"
                })
            };
            // Initialize TLS socket
            this.WEB_SOCKET = tls.connect(options, function () {
                // Check if TLS auth worked
                if (this.authorized) {
                    console.log("Connection authorized by a Cert. Authority ");
                } else {
                    console.log("Authorization not granted. Error: " + this.authorizationError);
                }
            });

What I have tried:

  • Set rejectUnauthorized to false.
  • Tried to run the code with NODE_TLS_REJECT_UNAUTHORIZED = "0";. It didn't work and I think is not a good option for production.
  • Checked similar questions on SO, this one it looks pretty similar to my problem. But the most upvoted answer is from 2014 and I couldn't find too much information about Distinguished name on the docs.
  • I checked the certs using openssl x509 -in *.cert -text and they looked good.

¿Am I wrongly generating the certs? Any help is appreciated. Thanks!

EDIT. 16/10/2019 There was a problem in the code, I didn't use resolve(); when the sockets were up. The same problem remains... BUT despite of getting an authorization error on the client, the server fires up the "secureConnection" event and messages are interchanged. ¿Does this makes sense? *Yes, it makes sense since the server accepts unauthorized connections. If I set the server to reject not certified connections the clients hung up *

WristMan
  • 337
  • 3
  • 11
  • 1
    Irrelevant to your Q, but your dhparams are useless. You enable only one (TLS1.2) ciphersuite, which does not use DHE; it does use ecdhCurve, but setting that to the default is the same as not setting it. Even if you enabled other ciphersuites, there are none that use DHE with an EC certificate (and static key). For TLS1.3, if your systems (all) support it, it is possible to use DHE with an EC cert, but only with standard groups, never ones you generate. And no TLS client (any version) ever uses its own ephemeral parameters. – dave_thompson_085 Nov 14 '19 at 12:04
  • @dave_thompson_085 Thanks a lot for the information. I had no idea. I'll make the changes. Any pointer or recommendation for where to read about these issues. I found openssl official docs a little bit vague sometimes. – WristMan Nov 15 '19 at 07:12
  • @dave_thompson_085 Regarding this part of your comment "And no TLS client (any version) ever uses its own ephemeral parameters.".I understand I shouldn't provide the dhparams attribute.But in nodejs official doc about the TLS package they state the following:"To use Perfect Forward Secrecy using DHE with the tls module,it is required to generate Diffie-Hellman parameters and specify them with the dhparam option to tls.createSecureContext().The following illustrates the use of the OpenSSL command-line interface to generate such parameters...". Should I provide provide my own eph. params or not? – WristMan Nov 19 '19 at 11:11
  • 1
    _If_ using DHE (which you aren't) and _not_ TLS1.3, then you do need to configure dhparam on the _server_ but not the _client_; their doc isn't clear about that. You don't technically _need_ to generate your own params, you can use ones from another _trustworthy_ source -- but in practice assessing such a source is usually harder than just generating new params. It does no _harm_ to provide them where they aren't needed, like client, just no good. ... – dave_thompson_085 Nov 22 '19 at 10:44
  • 1
    ... While I'm at it, the last para (in v11 up) is also wrong; it says 'all TLSv1.3 suites use ECDHE'. First in 1.3 keyexchange is no longer in the suite; second and more substantive 1.3 drops the former static-RSA keyexechange and forces PFS, but for new/nonresumed session it uses _either_ ECHDE or DHE (the latter with standardized groups instead of customizable ones, so no config needed). – dave_thompson_085 Nov 22 '19 at 10:48

1 Answers1

3

The problem was I was using the same configuration file (./openssl/oid_file) for all the certificates. Using different configuration files and different Alternative names solved this issue.

I ended with an "UNABLE_TO_VERIFY_LEAF_SIGNATURE" error. The certificates were properly generated but it didn't work. I couldn't find a working example of self-signed certificates in nodejs. Most of them simply deprecated the use of certificates by disabling SSL or accepting unathorized transactions, which is the opposite of what TLS is supposed to do.

Finally, I used this tool to generate the certificates: https://github.com/FiloSottile/mkcert . The best and simplest way to generate self-signed certificates in a testing environment. You only need to set the node variable NODE_EXTRA_CA_CERTS to point the root certificate:

process.env.NODE_EXTRA_CA_CERTS = [os.homedir() + "/.local/share/mkcert/rootCA.pem"];

-

WristMan
  • 337
  • 3
  • 11