grpc / grpc-node

gRPC for Node.js
https://grpc.io
Apache License 2.0
4.4k stars 625 forks source link

grpc node mutual auth failed when uses secp256k1 with the ECDSA algorithm #1802

Open TheBestOrNothing opened 3 years ago

TheBestOrNothing commented 3 years ago

Problem description

I've been using a self generated RSA Certificate Authority to sign my server and clients certificates and so far grpc node mutual authentication worked fine.

Following the same why, I'm trying to use secp256k1 with the ECDSA algorithm, assmuming that the key of BTC or ETH will be used grpc authentication directly. Unfortunately I cannot get right output.

Reproduction steps

All the following contents and steps can been found from the repository

  1. To generate server and client's EC secp256k1 private key and TLS certificates with gen.sh.
    
    # 1. Generate EC private key and self-signed certificate
    openssl ecparam -genkey -out ca.key -name secp256k1 
    openssl req -x509 -new -key ca.key -out ca.cert -subj "/C=FR/ST=Occitanie/L=Toulouse/O=Tech School/OU=Education/CN=*.techschool.guru/emailAddress=root.guru@gmail.com"

echo "CA's self-signed certificate" openssl x509 -in ca.cert -noout -text

2. Generate web server's private key and certificate signing request (EC)

openssl ecparam -genkey -out server.key -name secp256k1 openssl req -key server.key -new -out server.req -subj "/C=FR/ST=Ile de France/L=Paris/O=PC Book/OU=Computer/CN=*.pcbook.com/emailAddress=server@gmail.com"

3. Use CA's private key to sign web server's CSR and get back the signed certificate

openssl x509 -req -in server.req -days 60 -CA ca.cert -CAkey ca.key -CAcreateserial -out server.cert -extfile server.ext

echo "Server's signed certificate" openssl x509 -in server.cert -noout -text

4. Generate client's private key and certificate signing request (EC)

openssl ecparam -genkey -out client.key -name secp256k1 openssl req -key client.key -new -out client.req -subj "/C=FR/ST=Alsace/L=Strasbourg/O=PC Client/OU=Computer/CN=*.client.com/emailAddress=client@gmail.com"

5. Use CA's private key to sign client's CSR and get back the signed certificate

openssl x509 -req -in client.req -days 60 -CA ca.cert -CAkey ca.key -CAcreateserial -out client.cert -extfile client.ext

echo "Client's signed certificate" openssl x509 -in client.cert -noout -text

6. To verify the server certificate aginst by root CA

echo "server's certificate verification" openssl verify -show_chain -CAfile ca.cert server.cert

7. To verify the client certificate aginst by root CA.

echo "client's certificate verification" openssl verify -show_chain -CAfile ca.cert client.cert

2. Implement server-side Auth in [greeter_server.js](https://github.com/vulnsystem/OpenssLabs/blob/main/grpc-auth-secp256k1/greeter_server.js)

function main() { var server = new grpc.Server();

server.addService(hello_proto.Greeter.service, {sayHello: sayHello}); server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createSsl( fs.readFileSync('./credentials/ca.cert'), [{ cert_chain: fs.readFileSync('./credentials/server.cert'), private_key: fs.readFileSync('./credentials/server.key') }], true ), () => { server.start(); }); }

3. Implement client-side Auth in [greeter_client.js](https://github.com/vulnsystem/OpenssLabs/blob/main/grpc-auth-secp256k1/greeter_client.js)

var client = new hello_proto.Greeter(target, grpc.credentials.createSsl(fs.readFileSync('./credentials/ca.cert'), fs.readFileSync('./credentials/client.key'), fs.readFileSync('./credentials/client.cert')));

4. Try it out!
```shell
npm install 
node greeter_server.js
node greeter_client.js
  1. The server started successfully and Error output from client is:
    
    /grpc-auth-secp256k1/greeter_client.js:57
    console.log('Greeting:', response.message);                                    ^

TypeError: Cannot read property 'message' of undefined at Object.callback (grpc-auth-secp256k1/greeter_client.js:57:39) at Object.onReceiveStatus (grpc-auth-secp256k1/node_modules/@grpc/grpc-js/build/src/client.js:176:36) at Object.onReceiveStatus (grpc-auth-secp256k1/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:342:141) at Object.onReceiveStatus OpenssLabs/grpc-auth-secp256k1/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:305:181) at grpc-auth-secp256k1/node_modules/@grpc/grpc-js/build/src/call-stream.js:124:78 at processTicksAndRejections (internal/process/task_queues.js:75:11)



The grpc node mutual authentication is successful with [RSA Certificate](https://github.com/vulnsystem/OpenssLabs/tree/main/grpc-client-auth) , but failed with **secp256k1**.

### Environment
 - OS [Ubuntu 18.04 amd64]
 - Node version [v14.16.1]
 - Node installation method [nvm]
 - Package name and version [@grpc/grpc-js : 1.1.0]
 - Authentication mode: SSL/TLS
 - Signature algorithm: ecdsa-withsha256
 - Key generate type: **secp256k1**
 - Source code: dynamic_codegen from grpc node examples

### Additional context
No

### Question
1. How to show the grpc TLS handshake informaiton like openssl debug?
2. Is there any clue to fix the problem?
murgatroid99 commented 3 years ago

If you set the environment variable NODE_DEBUG=tls, you should get some debug logs for the TLS handshake. Also, it would probably help to modify the greeter client to output the error that the call ends with.

TheBestOrNothing commented 3 years ago

@murgatroid99 Thanks for your clue.

After set the environment variable NODE_DEBUG=tls, the following error info showed, there is no detailed handshake information.

  TLS 10914: client _init handle? true
  TLS 10914: client initRead handle? true buffered? false
  TLS 10914: client _start handle? true connecting? false requestOCSP? false
  Error: 14 UNAVAILABLE: No connection established
  .....
  code: 14,
  details: 'No connection established',
  metadata: Metadata { internalRepr: Map(0) {}, options: {} 

But it is successful to mutual auth with secp256k1 by OpenSSL.

Server side:

openssl s_server -accept 20000 -cert server.cert -key server.key  -debug -tlsextdebug -curves secp256k1 -tls1_2

Client side:

openssl s_client -showcerts -connect localhost:20000  -CAfile ca.cert  -cert client.cert -key client.key -curves secp256k1 -tls1_2

At last, mutual auth handshake successfully with right CIPHER and both side communicate well.

CIPHER is ECDHE-ECDSA-AES256-GCM-SHA384
Supported Elliptic Groups: secp256k1
Shared Elliptic groups: secp256k1
Secure Renegotiation IS supported

Note: The parameter -curves secp256k1 -tls1_2 are key to pass auth.

Question:

murgatroid99 commented 3 years ago

OK, that cipher is in Node's default cipher suite and Node is supposed to automatically select the correct curve by default, so that part should work. The issue might be with the -tls1_2 option. Are you passing that because your certificate is incompatible with TLS v1.3? My quick testing shows that Node 14 sets a default minimum TLS version of 1.2 and a default maximum version of 1.3, so if the certificate is incompatible with TLS 1.3, that could be the problem.

TheBestOrNothing commented 3 years ago

@murgatroid99 It is failed to mutual auth with TLS 1.3 according to the issue. But I am not stop trying -) I have successfully created a node server and client with TLS 1.2. All the code can been found in the repository.

  1. To generate server and client's EC secp256k1 private key and TLS certificates with gen.sh.
  2. Create node server
    
    var tls = require('tls'),
    fs = require('fs'),
    msg = [
            ".-..-..-.  .-.   .-. .--. .---. .-.   .---. .-.",
            ": :; :: :  : :.-.: :: ,. :: .; :: :   : .  :: :",
            ":    :: :  : :: :: :: :: ::   .': :   : :: :: :",
            ": :: :: :  : `' `' ;: :; :: :.`.: :__ : :; ::_;",
            ":_;:_;:_;   `.,`.,' `.__.':_;:_;:___.':___.':_;"
          ].join("\n").cyan;

var options = { ca: fs.readFileSync('./credentials/ca.cert'), key: fs.readFileSync('./credentials/server.key'), cert: fs.readFileSync('./credentials/server.cert'), ecdhCurve: 'secp256k1', maxVersion: 'TLSv1.2', ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384' };

tls.createServer(options, function (s) { s.write(msg+"\n"); s.pipe(s); }).listen(8000);

3. Access server with client

var tls = require('tls'), fs = require('fs');

var options = { ca: fs.readFileSync('./credentials/ca.cert'), key: fs.readFileSync('./credentials/client.key'), cert: fs.readFileSync('./credentials/client.cert'), ecdhCurve: 'secp256k1', maxVersion: 'TLSv1.2', ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384' };

var conn = tls.connect(8000, 'localhost', options, function() { if (conn.authorized) { console.log("Connection authorized by a Certificate Authority."); } else { console.log("Connection not authorized: " + conn.authorizationError) } });

// Send a friendly message conn.write("I am the client sending you a message.");

conn.on("data", function (data) { console.log('Receive:' + data.toString()); conn.end(); });

conn.on('close', function() { console.log("Connection closed"); });

conn.on('error', function(error) { console.error(error); conn.destroy(); });

4. Try it out

./gen.sh node server.js //in one shell node client.js //in anther shell

5. Output of client

Connection authorized by a Certificate Authority. Receive:undefined Receive:I am the client sending you a message. Connection closed


The server and client handshake successfully with dedicated ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384'.
So as to grpc node, could we assign the TLS options (showed in the server and client code)  to grpc node?
murgatroid99 commented 3 years ago

That cipher is in the default cipher suite. Did you try without explicitly setting the ciphers? And it's supposed to automatically choose the curve. Did you try without explicitly setting the ecdhCurve option?

Also, the responses on the issue you linked say that TLSv3 intentionally does not support that cipher because it is obsolete. Maybe you'd be better off just using a cipher that is still supported.

TheBestOrNothing commented 3 years ago

That cipher is in the default cipher suite. Did you try without explicitly setting the ciphers? And it's supposed to automatically choose the curve. Did you try without explicitly setting the ecdhCurve option?

It is OK without explicitly setting the ecdhCurve option. Server and client works well without assigning the ciphers ECDHE-ECDSA-AES256-GCM-SHA384.

Also, the responses on the issue you linked say that TLSv3 intentionally does not support that cipher because it is obsolete. Maybe you'd be better off just using a cipher that is still supported.

The reason is secp256k1 is not recommended in TLSv3, not referenced in RFC 8446, and defined in RFC 8422. Here is detailed discription. In TLS Supported Groups, the secp256k1 is not recommended -(