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(); }); });