From 385fcd1f55c86d75bb9f27f0d1c9840c7b2de6df Mon Sep 17 00:00:00 2001 From: Matt Broadstone Date: Wed, 4 Jul 2018 10:48:10 -0400 Subject: [PATCH] feat(serverPrincipalDetails): add server pricipal details method --- lib/kerberos.js | 95 +++++++++++++----------------------------- src/kerberos.cc | 44 +++++++++++++++++++ src/kerberos.h | 1 + src/kerberos_gss.cc | 4 +- test/kerberos_tests.js | 9 ++++ 5 files changed, 87 insertions(+), 66 deletions(-) diff --git a/lib/kerberos.js b/lib/kerberos.js index da2fe24..4368ec2 100644 --- a/lib/kerberos.js +++ b/lib/kerberos.js @@ -36,6 +36,24 @@ function validateParameter(parameter, spec) { } } +/** + * This function returns the service principal for the server given a service + * type and hostname. + * + * Details are looked up via the `/etc/keytab` file. + * + * @param {string} service The Kerberos service type for the server. + * @param {string} hostname The hostname of the server. + * @param {function} callback + */ +function serverPrincipalDetails(service, hostname, callback) { + validateParameter(service, { name: 'service', type: 'string' }); + validateParameter(hostname, { name: 'options', type: 'string' }); + validateParameter(callback, { name: 'callback', type: 'function' }); + + kerberos.serverPrincipalDetails(service, hostname, callback); +} + /** * The callback format for inserts * @callback authGSSClientInitCallback @@ -54,7 +72,7 @@ function validateParameter(parameter, spec) { * @param {string} [options.principal] Optional string containing the client principal in the form 'user@realm' (e.g. 'jdoe@example.com'). * @param {number} [options.gssFlags] Optional integer used to set GSS flags. (e.g. GSS_C_DELEG_FLAG|GSS_C_MUTUAL_FLAG|GSS_C_SEQUENCE_FLAG will allow for forwarding credentials to the remote host) * @param {number} [options.mechOID] Optional GSS mech OID. Defaults to None (GSS_C_NO_OID). Other possible values are GSS_MECH_OID_KRB5, GSS_MECH_OID_SPNEGO. - * @param {authGSSClientInitCallback} callback The operation callback + * @param {initializeClientCallback} callback The operation callback */ function initializeClient(service, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); @@ -74,7 +92,7 @@ function initializeClient(service, options, callback) { * are complete. * * @param {string} service A string containing the service principal in the form 'type@fqdn' (e.g. 'imap@mail.apple.com'). - * @param {function} callback + * @param {initializeServerCallback} callback */ function initializeServer(service, callback) { validateParameter(service, { name: 'service', type: 'string' }); @@ -83,105 +101,50 @@ function initializeServer(service, callback) { kerberos.initializeServer(service, callback); } -/** - * Destroys the context for GSSAPI client-side authentication. - * - * @param {KerberosContext} context The context object returned from `authGSSClientInit` - * @param {function} callback - */ -// function authGSSClientClean(context, callback) { -// validateParameter(context, { name: 'context', type: 'object' }); -// validateParameter(callback, { name: 'callback', type: 'function' }); - -// kerberos.authGSSClientClean(context, callback); -// } - /** * Processes a single GSSAPI client-side step using the supplied server data. * - * @param {KerberosContext} context The context object returned from `authGSSClientInit` + * @memberof KerberosClient * @param {string} challenge A string containing the base64-encoded server data (which may be empty for the first step) * @param {function} callback Returns a result code, or an error if one was encountered */ -// function authGSSClientStep(context, challenge, callback) { -// validateParameter(context, { name: 'context', type: 'object' }); -// validateParameter(challenge, { name: 'challenge', type: 'string' }); -// validateParameter(callback, { name: 'callback', type: 'function' }); - -// kerberos.authGSSClientStep(context, challenge, callback); -// } /** * Perform the client side GSSAPI unwrap step * - * @param {KerberosContext} context The context object returned from `authGSSClientInit` + * @memberof KerberosClient * @param {string} challenge A string containing the base64-encoded server data * @param {function} callback */ -// function authGSSClientUnwrap(context, challenge, callback) { -// validateParameter(context, { name: 'context', type: 'object' }); -// validateParameter(challenge, { name: 'challenge', type: 'string' }); -// validateParameter(callback, { name: 'callback', type: 'function' }); - -// kerberos.authGSSClientUnwrap(context, challenge, callback); -// } /** * Perform the client side GSSAPI wrap step. * - * @param {KerberosContext} context The context object returned from `authGSSClientInit` + * @memberof KerberosClient * @param {string} challenge The result of the `authGSSClientResponse` after the `authGSSClientUnwrap` * @param {object} [options] Optional settings * @param {string} [options.user] The user to authorize * @param {function} callback */ -// function authGSSClientWrap(context, challenge, options, callback) { -// if (typeof options === 'function') (callback = options), (options = {}); -// options = options || {}; - -// validateParameter(context, { name: 'context', type: 'object' }); -// validateParameter(challenge, { name: 'challenge', type: 'string' }); -// validateParameter(options, { name: 'options', type: 'object' }); -// validateParameter(callback, { name: 'callback', type: 'function' }); - -// kerberos.authGSSClientWrap(context, challenge, options, callback); -// } - -/** - * Destroys the context for GSSAPI server-side authentication. - * - * @param {KerberosContext} context The context object returned from `authGSSServerInit` - * @param {function} callback - */ -// function authGSSServerClean(context, callback) { -// validateParameter(context, { name: 'context', type: 'object' }); -// validateParameter(callback, { name: 'callback', type: 'function' }); - -// kerberos.authGSSServerClean(context, callback); -// } /** * Processes a single GSSAPI server-side step using the supplied client data. * - * @param {KerberosContext} context The context object returned from `authGSSServerInit` + * @memberof KerberosServer * @param {string} challenge A string containing the base64-encoded client data * @param {function} callback */ -// function authGSSServerStep(context, challenge, callback) { -// validateParameter(context, { name: 'context', type: 'object' }); -// validateParameter(challenge, { name: 'challenge', type: 'string' }); -// validateParameter(callback, { name: 'callback', type: 'function' }); - -// kerberos.authGSSServerStep(context, challenge, callback); -// } module.exports = { initializeClient, initializeServer, + serverPrincipalDetails, - // Constants + // result codes AUTH_GSS_CONTINUE, AUTH_GSS_COMPLETE, + + // gss flags GSS_C_DELEG_FLAG, GSS_C_MUTUAL_FLAG, GSS_C_REPLAY_FLAG, @@ -192,6 +155,8 @@ module.exports = { GSS_C_PROT_READY_FLAG, GSS_C_TRANS_FLAG, GSS_C_NO_OID, + + // mechanism OIDs GSS_MECH_OID_KRB5, GSS_MECH_OID_SPNEGO }; diff --git a/src/kerberos.cc b/src/kerberos.cc index 9e3bb9e..b403098 100644 --- a/src/kerberos.cc +++ b/src/kerberos.cc @@ -115,6 +115,48 @@ NAN_METHOD(InitializeServer) { AsyncQueueWorker(new InitializeServerWorker(service, callback)); } +class ServerPrincipalDetailsWorker : public Nan::AsyncWorker { + public: + ServerPrincipalDetailsWorker(std::string service, std::string hostname, Nan::Callback *callback) + : AsyncWorker(callback, "kerberos:ServerPrincipalDetails"), + _service(service), + _hostname(hostname) + {} + + virtual void Execute() { + std::unique_ptr result( + server_principal_details(_service.c_str(), _hostname.c_str())); + + if (result->code == AUTH_GSS_ERROR) { + SetErrorMessage(result->message); + return; + } + + _details = std::string(result->data); + } + + protected: + virtual void HandleOKCallback() { + Nan::HandleScope scope; + v8::Local argv[] = { Nan::Null(), Nan::New(_details).ToLocalChecked() }; + callback->Call(2, argv, async_resource); + } + + private: + std::string _service; + std::string _hostname; + std::string _details; + +}; + +NAN_METHOD(ServerPrincipalDetails) { + std::string service(*Nan::Utf8String(info[0])); + std::string hostname(*Nan::Utf8String(info[1])); + Nan::Callback* callback = new Nan::Callback(Nan::To(info[2]).ToLocalChecked()); + + AsyncQueueWorker(new ServerPrincipalDetailsWorker(service, hostname, callback)); +} + NAN_MODULE_INIT(Init) { // Custom types KerberosClient::Init(target); @@ -124,6 +166,8 @@ NAN_MODULE_INIT(Init) { Nan::GetFunction(Nan::New(InitializeClient)).ToLocalChecked()); Nan::Set(target, Nan::New("initializeServer").ToLocalChecked(), Nan::GetFunction(Nan::New(InitializeServer)).ToLocalChecked()); + Nan::Set(target, Nan::New("serverPrincipalDetails").ToLocalChecked(), + Nan::GetFunction(Nan::New(ServerPrincipalDetails)).ToLocalChecked()); } NODE_MODULE(kerberos, Init) diff --git a/src/kerberos.h b/src/kerberos.h index 8a5257e..413adf2 100644 --- a/src/kerberos.h +++ b/src/kerberos.h @@ -4,6 +4,7 @@ #include +NAN_METHOD(ServerPrincipalDetails); NAN_METHOD(InitializeClient); NAN_METHOD(InitializeServer); diff --git a/src/kerberos_gss.cc b/src/kerberos_gss.cc index b60211e..213348d 100644 --- a/src/kerberos_gss.cc +++ b/src/kerberos_gss.cc @@ -110,11 +110,13 @@ gss_result* server_principal_details(const char* service, const char* hostname) } if (details == NULL) + { result = gss_error_result_with_message_and_code("Principal not found in keytab", -1); + } else { result = gss_success_result(AUTH_GSS_COMPLETE); - result->message = details; + result->data = details; } end: if (cursor) diff --git a/test/kerberos_tests.js b/test/kerberos_tests.js index 5416960..8fce5f7 100644 --- a/test/kerberos_tests.js +++ b/test/kerberos_tests.js @@ -14,6 +14,15 @@ const hostname = process.env.KERBEROS_HOSTNAME || 'hostname.example.com'; const port = process.env.KERBEROS_PORT || '80'; describe('Kerberos', function() { + it('should lookup principal details on a server', function(done) { + const expected = `HTTP/${hostname}@${realm.toUpperCase()}`; + kerberos.serverPrincipalDetails('HTTP', hostname, (err, details) => { + expect(err).to.not.exist; + expect(details).to.equal(expected); + done(); + }); + }); + it('should authenticate against a kerberos server using GSSAPI', function(done) { const service = `HTTP@${hostname}`;