mirror of
https://github.com/grpc/grpc-node.git
synced 2025-12-08 18:23:54 +00:00
Merge pull request #476 from murgatroid99/verify_callback_backport
Backport "Add checkServerIdentity callback"
This commit is contained in:
commit
e6f07b43fc
@ -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,44 @@ ChannelCredentials::~ChannelCredentials() {
|
||||
grpc_channel_credentials_release(wrapped_credentials);
|
||||
}
|
||||
|
||||
static int verify_peer_callback_wrapper(const char* servername, const char* cert, void* userdata) {
|
||||
Nan::HandleScope scope;
|
||||
Nan::TryCatch try_catch;
|
||||
Nan::Callback *callback = (Nan::Callback*)userdata;
|
||||
|
||||
const unsigned argc = 2;
|
||||
Local<Value> argv[argc];
|
||||
if (servername == NULL) {
|
||||
argv[0] = Nan::Null();
|
||||
} else {
|
||||
argv[0] = Nan::New<v8::String>(servername).ToLocalChecked();
|
||||
}
|
||||
if (cert == NULL) {
|
||||
argv[1] = Nan::Null();
|
||||
} else {
|
||||
argv[1] = Nan::New<v8::String>(cert).ToLocalChecked();
|
||||
}
|
||||
|
||||
Local<Value> 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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void verify_peer_callback_destruct(void *userdata) {
|
||||
Nan::Callback *callback = (Nan::Callback*)userdata;
|
||||
delete callback;
|
||||
}
|
||||
|
||||
void ChannelCredentials::Init(Local<Object> exports) {
|
||||
HandleScope scope;
|
||||
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
|
||||
@ -148,9 +188,31 @@ 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[3]->IsUndefined()) {
|
||||
if (!info[3]->IsObject()) {
|
||||
return Nan::ThrowTypeError("createSsl's fourth argument must be an object");
|
||||
}
|
||||
Local<Object> object = info[3]->ToObject();
|
||||
|
||||
Local<Value> 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<Function>::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;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
33
packages/grpc-native-core/index.d.ts
vendored
33
packages/grpc-native-core/index.d.ts
vendored
@ -794,6 +794,36 @@ declare module "grpc" {
|
||||
ERROR,
|
||||
}
|
||||
|
||||
/**
|
||||
* A certificate as received by the checkServerIdentity callback.
|
||||
*/
|
||||
export interface Certificate {
|
||||
/**
|
||||
* The raw certificate in DER form.
|
||||
*/
|
||||
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: Certificate) => Error | undefined;
|
||||
|
||||
/**
|
||||
* 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 +858,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
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -76,9 +76,35 @@ 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.
|
||||
* 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,9 +112,30 @@ 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 {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;
|
||||
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
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var forge = require('node-forge');
|
||||
|
||||
var grpc = require('..');
|
||||
|
||||
@ -128,6 +129,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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -260,6 +280,58 @@ 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');
|
||||
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
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('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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user