Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
TheBestOrNothing opened this issue May 31, 2021 · 6 comments

Comments

@TheBestOrNothing
Copy link

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
  1. Implement server-side Auth in 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();
  });
}
  1. Implement client-side Auth in 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')));
  1. Try it out!
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 , 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
Copy link
Member

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
Copy link
Author

TheBestOrNothing commented Jun 3, 2021

@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:

  • The curves name and TLS version is important parameter in the openssl command line. So where grpc node to assign these two parameters?
  • Is there any other clue to fix the problem? Thanks.

@murgatroid99
Copy link
Member

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
Copy link
Author

TheBestOrNothing commented Jun 9, 2021

@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);
  1. 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();
});
  1. Try it out
./gen.sh
node server.js //in one shell
node client.js //in anther shell
  1. 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
Copy link
Member

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
Copy link
Author

TheBestOrNothing commented Jun 9, 2021

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 -(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants