From 515f4bfa9ed2f98be2670143d34b2e1356eb7089 Mon Sep 17 00:00:00 2001 From: arabull <22479725+arabull@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:54:25 -0400 Subject: [PATCH] feat(NODE-6333): Allow callers to specify the 'protect' flag (#198) Co-authored-by: Michael Johns Co-authored-by: Bailey Pearson Co-authored-by: Durran Jordan Co-authored-by: Bailey Pearson --- README.md | 1 + lib/kerberos.js | 1 + src/kerberos.cc | 11 +++++++++ src/kerberos.h | 2 ++ src/unix/kerberos_unix.cc | 3 +-- src/win32/kerberos_win32.cc | 2 +- test/kerberos_tests.js | 47 +++++++++++++++++++++++++++++++++++++ 7 files changed, 64 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 321dad0c..8c1fb361 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,7 @@ Processes a single kerberos client-side step using the supplied server challenge | challenge | string | The response returned after calling `unwrap` | | [options] | object | Optional settings | | [options.user] | string | The user to authorize | +| [options.protect] | boolean | Indicates if the wrap should request message confidentiality | | [callback] | function | | Perform the client side kerberos wrap step. diff --git a/lib/kerberos.js b/lib/kerberos.js index 09f8425f..f203d891 100644 --- a/lib/kerberos.js +++ b/lib/kerberos.js @@ -52,6 +52,7 @@ KerberosClient.prototype.step = defineOperation(KerberosClient.prototype.step, [ * @param {string} challenge The response returned after calling `unwrap` * @param {object} [options] Optional settings * @param {string} [options.user] The user to authorize + * @param {boolean} [options.protect] Indicates if the wrap should request message confidentiality * @param {function} [callback] * @return {Promise} returns Promise if no callback passed */ diff --git a/src/kerberos.cc b/src/kerberos.cc index 7a760423..a79dfdd4 100644 --- a/src/kerberos.cc +++ b/src/kerberos.cc @@ -37,6 +37,17 @@ std::string ToStringWithNonStringAsEmpty(Napi::Value value) { return value.As(); } +int KerberosClient::ParseWrapOptionsProtect(const Napi::Object& options) { + if (!options.Has("protect")) return 0; + + if (!options.Get("protect").IsBoolean()) { + throw TypeError::New(options.Env(), "options.protect must be a boolean."); + } + + bool protect = options.Get("protect").As(); + return protect ? 1 : 0; +} + Function KerberosClient::Init(Napi::Env env) { return DefineClass(env, diff --git a/src/kerberos.h b/src/kerberos.h index 7f1c2096..deec6e33 100644 --- a/src/kerberos.h +++ b/src/kerberos.h @@ -55,6 +55,8 @@ class KerberosClient : public Napi::ObjectWrap { void UnwrapData(const Napi::CallbackInfo& info); void WrapData(const Napi::CallbackInfo& info); + int ParseWrapOptionsProtect(const Napi::Object& options); + private: friend class Napi::ObjectWrap; explicit KerberosClient(const Napi::CallbackInfo& info); diff --git a/src/unix/kerberos_unix.cc b/src/unix/kerberos_unix.cc index 57a67ae8..fafa937b 100644 --- a/src/unix/kerberos_unix.cc +++ b/src/unix/kerberos_unix.cc @@ -74,8 +74,7 @@ void KerberosClient::WrapData(const CallbackInfo& info) { Object options = info[1].ToObject(); Function callback = info[2].As(); std::string user = ToStringWithNonStringAsEmpty(options["user"]); - - int protect = 0; // NOTE: this should be an option + int protect = ParseWrapOptionsProtect(options); KerberosWorker::Run(callback, "kerberos:ClientWrap", [=](KerberosWorker::SetOnFinishedHandler onFinished) { gss_result result = authenticate_gss_client_wrap( diff --git a/src/win32/kerberos_win32.cc b/src/win32/kerberos_win32.cc index aa653d43..21977799 100644 --- a/src/win32/kerberos_win32.cc +++ b/src/win32/kerberos_win32.cc @@ -92,7 +92,7 @@ void KerberosClient::WrapData(const CallbackInfo& info) { Object options = info[1].ToObject(); Function callback = info[2].As(); std::string user = ToStringWithNonStringAsEmpty(options["user"]); - int protect = 0; // NOTE: this should be an option + int protect = ParseWrapOptionsProtect(options); if (isStringTooLong(user)) { throw Error::New(info.Env(), "User name is too long"); diff --git a/test/kerberos_tests.js b/test/kerberos_tests.js index 18263bff..4be9de67 100644 --- a/test/kerberos_tests.js +++ b/test/kerberos_tests.js @@ -4,6 +4,7 @@ const request = require('request'); const chai = require('chai'); const expect = chai.expect; const os = require('os'); +const { test } = require('mocha'); chai.use(require('chai-string')); // environment variables @@ -122,4 +123,50 @@ describe('Kerberos', function () { }); }); }); + + describe('Client.wrap()', function () { + async function establishConext() { + const service = `HTTP@${hostname}`; + client = await kerberos.initializeClient(service, {}); + server = await kerberos.initializeServer(service); + const clientResponse = await client.step(''); + const serverResponse = await server.step(clientResponse); + await client.step(serverResponse); + expect(client.contextComplete).to.be.true; + return { client, server }; + } + + let client; + let server; + + before(establishConext); + describe('options.protect', function () { + context('valid values for `protect`', function () { + test('no options provided', async function () { + await client.wrap('challenge'); + }); + + test('options provided (protect omitted)', async function () { + await client.wrap('challenge', {}); + }); + + test('protect = false', async function () { + await client.wrap('challenge', { protect: false }); + }); + + test('protect = true', async function () { + await client.wrap('challenge', { protect: true }); + }); + }); + + context('when set to an invalid value', function () { + it('throws a TypeError', async function () { + const error = await client.wrap('challenge', { protect: 'non-boolean' }).catch(e => e); + expect(error) + .to.be.instanceOf(TypeError) + .to.match(/options.protect must be a boolean/); + }); + }); + }); + }); });