From ac7c1bc2e5ed626f807ee32581328121feca49f6 Mon Sep 17 00:00:00 2001 From: Ian Haken Date: Mon, 12 Mar 2018 22:11:33 -0700 Subject: [PATCH 01/11] Add checkServerIdentity callback. --- .../ext/channel_credentials.cc | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/packages/grpc-native-core/ext/channel_credentials.cc b/packages/grpc-native-core/ext/channel_credentials.cc index c334d5d0..d56780cf 100644 --- a/packages/grpc-native-core/ext/channel_credentials.cc +++ b/packages/grpc-native-core/ext/channel_credentials.cc @@ -38,6 +38,8 @@ using Nan::ObjectWrap; using Nan::Persistent; using Nan::Utf8String; +using v8::Array; +using v8::Context; using v8::Exception; using v8::External; using v8::Function; @@ -58,6 +60,43 @@ ChannelCredentials::~ChannelCredentials() { grpc_channel_credentials_release(wrapped_credentials); } +struct callback_md { + Nan::Callback *callback; +}; + +static int verify_peer_callback_wrapper(const char* servername, const char* cert, void* userdata) { + Nan::HandleScope scope; + Nan::TryCatch try_catch; + struct callback_md* md = (struct callback_md*)userdata; + + const unsigned argc = 2; + Local argv[argc]; + if (servername == NULL) { + argv[0] = Nan::Null(); + } else { + argv[0] = Nan::New(servername).ToLocalChecked(); + } + if (cert == NULL) { + argv[1] = Nan::Null(); + } else { + argv[1] = Nan::New(cert).ToLocalChecked(); + } + + md->callback->Call(argc, argv); + + if (try_catch.HasCaught()) { + return 1; + } + + return 0; +} + +static void verify_peer_callback_destruct(void *userdata) { + callback_md *md = (callback_md*)userdata; + delete md->callback; + delete md; +} + void ChannelCredentials::Init(Local exports) { HandleScope scope; Local tpl = Nan::New(New); @@ -148,9 +187,42 @@ NAN_METHOD(ChannelCredentials::CreateSsl) { "createSsl's second and third arguments must be" " provided or omitted together"); } + + verify_peer_options verify_options = {NULL, NULL, NULL}; + if (info.Length() >= 4 && info[3]->IsObject()) { + Local context = Nan::New(); + Local object = info[3]->ToObject(); + MaybeLocal maybe_props = object->GetOwnPropertyNames(context); + if (!maybe_props.IsEmpty()) { + Local props = maybe_props.ToLocalChecked(); + for(uint32_t i=0; i < props->Length(); i++) { + Local key = props->Get(i); + Local value = object->Get(key); + + if (key->IsString()) { + Nan::Utf8String keyStr(key->ToString()); + + if (strcmp("checkServerIdentity", (const char*)(*keyStr)) == 0) { + + if (!value->IsFunction()) { + return Nan::ThrowError("Value of checkServerIdentity must be a function."); + } + struct callback_md *md = new struct callback_md; + md->callback = new Callback(Local::Cast(value)); + verify_options.verify_peer_callback = verify_peer_callback_wrapper; + verify_options.verify_peer_callback_userdata = (void*)md; + verify_options.verify_peer_destruct = verify_peer_callback_destruct; + + } + } + // do stuff with key / value + } + } + } + grpc_channel_credentials *creds = grpc_ssl_credentials_create( root_certs.get(), private_key.isAssigned() ? &key_cert_pair : NULL, - NULL, NULL); + &verify_options, NULL); if (creds == NULL) { info.GetReturnValue().SetNull(); } else { From f368e5010260259a21cce9c6665dd71439cf6c25 Mon Sep 17 00:00:00 2001 From: Ian Haken Date: Wed, 20 Jun 2018 14:59:26 -0700 Subject: [PATCH 02/11] Regenerate project files and add test covering checkServerIdentity callback. --- .../grpc-native-core/test/credentials_test.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/grpc-native-core/test/credentials_test.js b/packages/grpc-native-core/test/credentials_test.js index 58dddb50..b3f922fa 100644 --- a/packages/grpc-native-core/test/credentials_test.js +++ b/packages/grpc-native-core/test/credentials_test.js @@ -260,6 +260,36 @@ describe('client credentials', function() { done(); }); }); + it('Verify callback receives correct arguments', function(done) { + var callback_host, callback_cert; + var client_ssl_creds = grpc.credentials.createSsl(ca_data, null, null, { + "checkServerIdentity": function(host, cert) { + callback_host = host; + callback_cert = cert; + } + }); + var client = new Client('localhost:' + port, client_ssl_creds, + client_options); + client.unary({}, function(err, data) { + assert.ifError(err); + assert.equal(callback_host, 'foo.test.google.fr'); + assert.equal(callback_cert, pem_data); + done(); + }); + }); + it('Verify callback exception causes connection failure', function(done) { + var client_ssl_creds = grpc.credentials.createSsl(ca_data, null, null, { + "checkServerIdentity": function(host, cert) { + throw "Verification callback exception"; + } + }); + var client = new Client('localhost:' + port, client_ssl_creds, + client_options); + client.unary({}, function(err, data) { + assert.ok(err, "Should have raised an error"); + done(); + }); + }); it('Should update metadata with SSL creds', function(done) { var metadataUpdater = function(service_url, callback) { var metadata = new grpc.Metadata(); From b6ad568c09e92c542be1f13686e6885ef61c3fdf Mon Sep 17 00:00:00 2001 From: Ian Haken Date: Wed, 20 Jun 2018 15:01:57 -0700 Subject: [PATCH 03/11] Add type assertion on createSsl's fourth argument. --- packages/grpc-native-core/ext/channel_credentials.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/grpc-native-core/ext/channel_credentials.cc b/packages/grpc-native-core/ext/channel_credentials.cc index d56780cf..64e3df46 100644 --- a/packages/grpc-native-core/ext/channel_credentials.cc +++ b/packages/grpc-native-core/ext/channel_credentials.cc @@ -189,7 +189,11 @@ NAN_METHOD(ChannelCredentials::CreateSsl) { } verify_peer_options verify_options = {NULL, NULL, NULL}; - if (info.Length() >= 4 && info[3]->IsObject()) { + if (!info[3]->IsUndefined()) { + if (!info[3]->IsObject()) { + return Nan::ThrowTypeError("createSsl's fourth argument must be an " + "object"); + } Local context = Nan::New(); Local object = info[3]->ToObject(); MaybeLocal maybe_props = object->GetOwnPropertyNames(context); From c70df88fc02e79881f45c3fab156f44e29da86ee Mon Sep 17 00:00:00 2001 From: Ian Haken Date: Wed, 20 Jun 2018 15:11:11 -0700 Subject: [PATCH 04/11] Simplify userdata being passed to checkServerIdentity callback. --- .../ext/channel_credentials.cc | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/grpc-native-core/ext/channel_credentials.cc b/packages/grpc-native-core/ext/channel_credentials.cc index 64e3df46..5aaa19b8 100644 --- a/packages/grpc-native-core/ext/channel_credentials.cc +++ b/packages/grpc-native-core/ext/channel_credentials.cc @@ -60,14 +60,10 @@ ChannelCredentials::~ChannelCredentials() { grpc_channel_credentials_release(wrapped_credentials); } -struct callback_md { - Nan::Callback *callback; -}; - static int verify_peer_callback_wrapper(const char* servername, const char* cert, void* userdata) { Nan::HandleScope scope; Nan::TryCatch try_catch; - struct callback_md* md = (struct callback_md*)userdata; + Nan::Callback *callback = (Nan::Callback*)userdata; const unsigned argc = 2; Local argv[argc]; @@ -82,7 +78,7 @@ static int verify_peer_callback_wrapper(const char* servername, const char* cert argv[1] = Nan::New(cert).ToLocalChecked(); } - md->callback->Call(argc, argv); + callback->Call(argc, argv); if (try_catch.HasCaught()) { return 1; @@ -92,9 +88,8 @@ static int verify_peer_callback_wrapper(const char* servername, const char* cert } static void verify_peer_callback_destruct(void *userdata) { - callback_md *md = (callback_md*)userdata; - delete md->callback; - delete md; + Nan::Callback *callback = (Nan::Callback*)userdata; + delete callback; } void ChannelCredentials::Init(Local exports) { @@ -211,10 +206,9 @@ NAN_METHOD(ChannelCredentials::CreateSsl) { if (!value->IsFunction()) { return Nan::ThrowError("Value of checkServerIdentity must be a function."); } - struct callback_md *md = new struct callback_md; - md->callback = new Callback(Local::Cast(value)); + Nan::Callback *callback = new Callback(Local::Cast(value)); verify_options.verify_peer_callback = verify_peer_callback_wrapper; - verify_options.verify_peer_callback_userdata = (void*)md; + verify_options.verify_peer_callback_userdata = (void*)callback; verify_options.verify_peer_destruct = verify_peer_callback_destruct; } From 03c5d98b1e8c0a7e525250f061a522555e114646 Mon Sep 17 00:00:00 2001 From: Ian Haken Date: Wed, 20 Jun 2018 16:13:08 -0700 Subject: [PATCH 05/11] Simplify getting checkServerIdentity out of the fourth createSsl argument. Add some tests asserting type-checking behavior. --- .../ext/channel_credentials.cc | 36 ++++++------------- .../grpc-native-core/test/credentials_test.js | 19 ++++++++++ 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/packages/grpc-native-core/ext/channel_credentials.cc b/packages/grpc-native-core/ext/channel_credentials.cc index 5aaa19b8..434b6e9b 100644 --- a/packages/grpc-native-core/ext/channel_credentials.cc +++ b/packages/grpc-native-core/ext/channel_credentials.cc @@ -186,35 +186,21 @@ NAN_METHOD(ChannelCredentials::CreateSsl) { verify_peer_options verify_options = {NULL, NULL, NULL}; if (!info[3]->IsUndefined()) { if (!info[3]->IsObject()) { - return Nan::ThrowTypeError("createSsl's fourth argument must be an " - "object"); + return Nan::ThrowTypeError("createSsl's fourth argument must be an object"); } - Local context = Nan::New(); Local object = info[3]->ToObject(); - MaybeLocal maybe_props = object->GetOwnPropertyNames(context); - if (!maybe_props.IsEmpty()) { - Local props = maybe_props.ToLocalChecked(); - for(uint32_t i=0; i < props->Length(); i++) { - Local key = props->Get(i); - Local value = object->Get(key); - if (key->IsString()) { - Nan::Utf8String keyStr(key->ToString()); - - if (strcmp("checkServerIdentity", (const char*)(*keyStr)) == 0) { - - if (!value->IsFunction()) { - return Nan::ThrowError("Value of checkServerIdentity must be a function."); - } - Nan::Callback *callback = new Callback(Local::Cast(value)); - verify_options.verify_peer_callback = verify_peer_callback_wrapper; - verify_options.verify_peer_callback_userdata = (void*)callback; - verify_options.verify_peer_destruct = verify_peer_callback_destruct; - - } - } - // do stuff with key / value + Local checkServerIdentityValue = Nan::Get(object, + Nan::New("checkServerIdentity").ToLocalChecked()).ToLocalChecked(); + if (!checkServerIdentityValue->IsUndefined()) { + if (!checkServerIdentityValue->IsFunction()) { + return Nan::ThrowTypeError("Value of checkServerIdentity must be a function."); } + Nan::Callback *callback = new Callback(Local::Cast( + checkServerIdentityValue)); + verify_options.verify_peer_callback = verify_peer_callback_wrapper; + verify_options.verify_peer_callback_userdata = (void*)callback; + verify_options.verify_peer_destruct = verify_peer_callback_destruct; } } diff --git a/packages/grpc-native-core/test/credentials_test.js b/packages/grpc-native-core/test/credentials_test.js index b3f922fa..542f93f3 100644 --- a/packages/grpc-native-core/test/credentials_test.js +++ b/packages/grpc-native-core/test/credentials_test.js @@ -128,6 +128,25 @@ describe('channel credentials', function() { grpc.credentials.createSsl(null, null, pem_data); }); }); + it('works if the fourth argument is an empty object', function() { + var creds; + assert.doesNotThrow(function() { + creds = grpc.credentials.createSsl(ca_data, null, null, {}); + }); + assert.notEqual(creds, null); + }); + it('fails if the fourth argument is a non-object value', function() { + assert.throws(function() { + grpc.credentials.createSsl(ca_data, null, null, 'test'); + }, TypeError); + }); + it('fails if the checkServerIdentity is a non-function', function() { + assert.throws(function() { + grpc.credentials.createSsl(ca_data, null, null, { + "checkServerIdentity": 'test' + }); + }, TypeError); + }); }); }); From a48629fa83baa9276095a0817b079642ebfe13e1 Mon Sep 17 00:00:00 2001 From: Ian Haken Date: Wed, 20 Jun 2018 18:14:38 -0700 Subject: [PATCH 06/11] Update credentials.js documentation for verify options and add verify options to typescript definition. --- packages/grpc-native-core/index.d.ts | 22 +++++++++++++++++++- packages/grpc-native-core/src/credentials.js | 15 ++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/grpc-native-core/index.d.ts b/packages/grpc-native-core/index.d.ts index a704694e..50e02e40 100644 --- a/packages/grpc-native-core/index.d.ts +++ b/packages/grpc-native-core/index.d.ts @@ -794,6 +794,25 @@ declare module "grpc" { ERROR, } + /** + * A callback that will receive the expected hostname and presented peer + * certificate as parameters. The callback should throw an error to + * indicate that the presented certificate is considered invalid. + */ + export type CheckServerIdentityCallback = (hostname: string, cert: string) => void; + + /** + * Additional peer verification options that can be set when creating + * SSL credentials. + */ + export interface VerifyOptions: { + /** + * If set, this callback will be invoked after the usual hostname verification + * has been performed on the peer certificate. + */ + checkServerIdentity?: CheckServerIdentityCallback; + } + /** * Credentials module * @@ -828,9 +847,10 @@ declare module "grpc" { * @param rootCerts The root certificate data * @param privateKey The client certificate private key, if applicable * @param certChain The client certificate cert chain, if applicable + * @param verifyOptions Additional peer verification options, if desired * @return The SSL Credentials object */ - createSsl(rootCerts?: Buffer, privateKey?: Buffer, certChain?: Buffer): ChannelCredentials; + createSsl(rootCerts?: Buffer, privateKey?: Buffer, certChain?: Buffer, verifyOptions?: VerifyOptions): ChannelCredentials; /** * Create a gRPC credentials object from a metadata generation function. This diff --git a/packages/grpc-native-core/src/credentials.js b/packages/grpc-native-core/src/credentials.js index a2312966..1c461d36 100644 --- a/packages/grpc-native-core/src/credentials.js +++ b/packages/grpc-native-core/src/credentials.js @@ -78,7 +78,8 @@ var _ = require('lodash'); /** * Create an SSL Credentials object. If using a client-side certificate, both - * the second and third arguments must be passed. + * the second and third arguments must be passed. Additional peer verification + * options can be passed in the fourth argument as described below. * @memberof grpc.credentials * @alias grpc.credentials.createSsl * @kind function @@ -86,6 +87,18 @@ var _ = require('lodash'); * @param {Buffer=} private_key The client certificate private key, if * applicable * @param {Buffer=} cert_chain The client certificate cert chain, if applicable + * @param {Object} verify_options Additional peer verification options. Can + * be undefined, in which case default behavior is preserved. + * Supported options are: "checkServerIdentity": (servername, cert) => {} + * The callback passed to checkServerIdentity will be invoked when the + * channel is opened in order to provide an opportunity to perform + * additional verification of the peer certificate as passed to the + * callback in the second parameter. The expected hostname is passed as + * the first parameter. If the callback considers the peer certificate + * invalid it should throw an error which will cause the handshake to + * be terminated. Note that supplying this callback does not disable + * the usual hostname verification which will also be performed on the + * certificate before this callback is invoked. * @return {grpc.credentials~ChannelCredentials} The SSL Credentials object */ exports.createSsl = ChannelCredentials.createSsl; From 1fd96966d754cabecbdff7ca6f3f3a81b949daf4 Mon Sep 17 00:00:00 2001 From: Ian Haken Date: Thu, 21 Jun 2018 14:59:32 -0700 Subject: [PATCH 07/11] Correct checkServerIdentity behavior to return a verification failure if an error is returned. Clean up documentation and add a test assertion on returned Error. --- .../grpc-native-core/ext/channel_credentials.cc | 8 +++++++- packages/grpc-native-core/index.d.ts | 7 ++++--- packages/grpc-native-core/src/credentials.js | 16 ++++------------ .../grpc-native-core/test/credentials_test.js | 13 +++++++++++++ 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/packages/grpc-native-core/ext/channel_credentials.cc b/packages/grpc-native-core/ext/channel_credentials.cc index 434b6e9b..6a2a1dde 100644 --- a/packages/grpc-native-core/ext/channel_credentials.cc +++ b/packages/grpc-native-core/ext/channel_credentials.cc @@ -78,9 +78,15 @@ static int verify_peer_callback_wrapper(const char* servername, const char* cert argv[1] = Nan::New(cert).ToLocalChecked(); } - callback->Call(argc, argv); + Local result = callback->Call(argc, argv); + // Catch any exception and return with a distinct status code which indicates this if (try_catch.HasCaught()) { + return 2; + } + + // If the result is an error, return a failure + if (result->IsNativeError()) { return 1; } diff --git a/packages/grpc-native-core/index.d.ts b/packages/grpc-native-core/index.d.ts index 50e02e40..bb3cd894 100644 --- a/packages/grpc-native-core/index.d.ts +++ b/packages/grpc-native-core/index.d.ts @@ -796,10 +796,11 @@ declare module "grpc" { /** * A callback that will receive the expected hostname and presented peer - * certificate as parameters. The callback should throw an error to - * indicate that the presented certificate is considered invalid. + * certificate as parameters. The callback should return an error to + * indicate that the presented certificate is considered invalid and + * otherwise returned undefined. */ - export type CheckServerIdentityCallback = (hostname: string, cert: string) => void; + export type CheckServerIdentityCallback = (hostname: string, cert: string) => Error | undefined; /** * Additional peer verification options that can be set when creating diff --git a/packages/grpc-native-core/src/credentials.js b/packages/grpc-native-core/src/credentials.js index 1c461d36..0fde6185 100644 --- a/packages/grpc-native-core/src/credentials.js +++ b/packages/grpc-native-core/src/credentials.js @@ -87,18 +87,10 @@ var _ = require('lodash'); * @param {Buffer=} private_key The client certificate private key, if * applicable * @param {Buffer=} cert_chain The client certificate cert chain, if applicable - * @param {Object} verify_options Additional peer verification options. Can - * be undefined, in which case default behavior is preserved. - * Supported options are: "checkServerIdentity": (servername, cert) => {} - * The callback passed to checkServerIdentity will be invoked when the - * channel is opened in order to provide an opportunity to perform - * additional verification of the peer certificate as passed to the - * callback in the second parameter. The expected hostname is passed as - * the first parameter. If the callback considers the peer certificate - * invalid it should throw an error which will cause the handshake to - * be terminated. Note that supplying this callback does not disable - * the usual hostname verification which will also be performed on the - * certificate before this callback is invoked. + * @param {Function} verify_options.checkServerIdentity Optional callback + * receiving the expected hostname and peer certificate for additional + * verification. The callback should return an Error if verification + * fails and otherwise return undefined. * @return {grpc.credentials~ChannelCredentials} The SSL Credentials object */ exports.createSsl = ChannelCredentials.createSsl; diff --git a/packages/grpc-native-core/test/credentials_test.js b/packages/grpc-native-core/test/credentials_test.js index 542f93f3..f3fc082f 100644 --- a/packages/grpc-native-core/test/credentials_test.js +++ b/packages/grpc-native-core/test/credentials_test.js @@ -309,6 +309,19 @@ describe('client credentials', function() { done(); }); }); + it('Verify callback returning an Error causes connection failure', function(done) { + var client_ssl_creds = grpc.credentials.createSsl(ca_data, null, null, { + "checkServerIdentity": function(host, cert) { + return new Error("Verification error"); + } + }); + var client = new Client('localhost:' + port, client_ssl_creds, + client_options); + client.unary({}, function(err, data) { + assert.ok(err, "Should have raised an error"); + done(); + }); + }); it('Should update metadata with SSL creds', function(done) { var metadataUpdater = function(service_url, callback) { var metadata = new grpc.Metadata(); From 0201c218a6299155b6f988a11a7a76235b40cd40 Mon Sep 17 00:00:00 2001 From: Ian Haken Date: Thu, 19 Jul 2018 12:29:57 -0700 Subject: [PATCH 08/11] Refactor checkServerIdentity callback to pass in cert as an object with raw DER buffer. --- packages/grpc-native-core/package.json | 1 + packages/grpc-native-core/src/credentials.js | 44 ++++++++++++++++++- .../grpc-native-core/test/credentials_test.js | 12 ++++- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/packages/grpc-native-core/package.json b/packages/grpc-native-core/package.json index 5d2a82d5..3e1e647c 100644 --- a/packages/grpc-native-core/package.json +++ b/packages/grpc-native-core/package.json @@ -44,6 +44,7 @@ "istanbul": "^0.4.4", "lodash": "^4.17.4", "minimist": "^1.1.0", + "node-forge": "^0.7.5", "poisson-process": "^0.2.1" }, "engines": { diff --git a/packages/grpc-native-core/src/credentials.js b/packages/grpc-native-core/src/credentials.js index 0fde6185..28b668b1 100644 --- a/packages/grpc-native-core/src/credentials.js +++ b/packages/grpc-native-core/src/credentials.js @@ -76,6 +76,31 @@ var _ = require('lodash'); * @see https://github.com/google/google-auth-library-nodejs */ +const PEM_CERT_HEADER = "-----BEGIN CERTIFICATE-----"; +const PEM_CERT_FOOTER = "-----END CERTIFICATE-----"; + +function wrapCheckServerIdentityCallback(callback) { + return function(hostname, cert) { + // Parse cert from pem to a version that matches the tls.checkServerIdentity + // format. + // https://nodejs.org/api/tls.html#tls_tls_checkserveridentity_hostname_cert + + var pemHeaderIndex = cert.indexOf(PEM_CERT_HEADER); + if (pemHeaderIndex === -1) { + return new Error("Unable to parse certificate PEM."); + } + cert = cert.substring(pemHeaderIndex); + var pemFooterIndex = cert.indexOf(PEM_CERT_FOOTER); + if (pemFooterIndex === -1) { + return new Error("Unable to parse certificate PEM."); + } + cert = cert.substring(PEM_CERT_HEADER.length, pemFooterIndex); + var rawBuffer = new Buffer(cert.replace("\n", "").replace(" ", ""), "base64"); + + return callback(hostname, { raw: rawBuffer }); + } +} + /** * Create an SSL Credentials object. If using a client-side certificate, both * the second and third arguments must be passed. Additional peer verification @@ -93,7 +118,24 @@ var _ = require('lodash'); * fails and otherwise return undefined. * @return {grpc.credentials~ChannelCredentials} The SSL Credentials object */ -exports.createSsl = ChannelCredentials.createSsl; +exports.createSsl = function(root_certs, private_key, cert_chain, verify_options) { + // The checkServerIdentity callback from gRPC core will receive the cert as a PEM. + // To better match the checkServerIdentity callback of Node, we wrap the callback + // to decode the PEM and populate a cert object. + if (verify_options && verify_options.checkServerIdentity) { + if (typeof verify_options.checkServerIdentity !== 'function') { + throw new TypeError("Value of checkServerIdentity must be a function."); + } + // Make a shallow clone of verify_options so our modification of the callback + // isn't reflected to the caller + var updated_verify_options = Object.assign({}, verify_options); + updated_verify_options.checkServerIdentity = wrapCheckServerIdentityCallback( + verify_options.checkServerIdentity); + arguments[3] = updated_verify_options; + } + return ChannelCredentials.createSsl.apply(this, arguments); +} + /** * @callback grpc.credentials~metadataCallback diff --git a/packages/grpc-native-core/test/credentials_test.js b/packages/grpc-native-core/test/credentials_test.js index f3fc082f..74d3edc1 100644 --- a/packages/grpc-native-core/test/credentials_test.js +++ b/packages/grpc-native-core/test/credentials_test.js @@ -21,6 +21,7 @@ var assert = require('assert'); var fs = require('fs'); var path = require('path'); +var forge = require('node-forge'); var grpc = require('..'); @@ -292,7 +293,16 @@ describe('client credentials', function() { client.unary({}, function(err, data) { assert.ifError(err); assert.equal(callback_host, 'foo.test.google.fr'); - assert.equal(callback_cert, pem_data); + + // The roundabout forge APIs for converting PEM to a node DER Buffer + var expected_der = new Buffer(forge.asn1.toDer( + forge.pki.certificateToAsn1(forge.pki.certificateFromPem(pem_data))) + .getBytes(), 'binary'); + + // Assert the buffers are equal by converting them to hex strings + assert.equal(callback_cert.raw.toString('hex'), expected_der.toString('hex')); + // Documented behavior of callback cert is that raw should be its only property + assert.equal(Object.keys(callback_cert).length, 1); done(); }); }); From 51c97b559d86071381f6bc51feef022a82c799b3 Mon Sep 17 00:00:00 2001 From: Ian Haken Date: Thu, 19 Jul 2018 14:05:10 -0700 Subject: [PATCH 09/11] Update typescript to properly reflect the format of the certificate received by the checkServerIdentity callback. --- packages/grpc-native-core/index.d.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/grpc-native-core/index.d.ts b/packages/grpc-native-core/index.d.ts index bb3cd894..4ffa5993 100644 --- a/packages/grpc-native-core/index.d.ts +++ b/packages/grpc-native-core/index.d.ts @@ -794,13 +794,17 @@ declare module "grpc" { ERROR, } + export interface Certificate { + raw: Buffer; + } + /** * A callback that will receive the expected hostname and presented peer * certificate as parameters. The callback should return an error to * indicate that the presented certificate is considered invalid and * otherwise returned undefined. */ - export type CheckServerIdentityCallback = (hostname: string, cert: string) => Error | undefined; + export type CheckServerIdentityCallback = (hostname: string, cert: Certificate) => Error | undefined; /** * Additional peer verification options that can be set when creating From 5f77bcda1fbdf5edf4490b3713771a0b0a8ccb18 Mon Sep 17 00:00:00 2001 From: Ian Haken Date: Thu, 19 Jul 2018 14:06:51 -0700 Subject: [PATCH 10/11] Add some missing descriptions to typescript. --- packages/grpc-native-core/index.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/grpc-native-core/index.d.ts b/packages/grpc-native-core/index.d.ts index 4ffa5993..ac11056e 100644 --- a/packages/grpc-native-core/index.d.ts +++ b/packages/grpc-native-core/index.d.ts @@ -794,7 +794,13 @@ declare module "grpc" { ERROR, } + /** + * A certificate as received by the checkServerIdentity callback. + */ export interface Certificate { + /** + * The raw certificate in DER form. + */ raw: Buffer; } From 5b57e43de54c4001a1b721a6d6b47875c15d7c50 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 6 Aug 2018 14:29:43 -0700 Subject: [PATCH 11/11] Update package.json template to match package.json --- packages/grpc-native-core/templates/package.json.template | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-native-core/templates/package.json.template b/packages/grpc-native-core/templates/package.json.template index a6d580a8..77bcfd13 100644 --- a/packages/grpc-native-core/templates/package.json.template +++ b/packages/grpc-native-core/templates/package.json.template @@ -46,6 +46,7 @@ "istanbul": "^0.4.4", "lodash": "^4.17.4", "minimist": "^1.1.0", + "node-forge": "^0.7.5", "poisson-process": "^0.2.1" }, "engines": {