Skip to content
Permalink
Browse files

feat(checkPassword): add implementation for checking krb5 passwords

  • Loading branch information
mbroadst committed Jul 4, 2018
1 parent 385fcd1 commit 60f476e80d0c0c95c51b771e59ca1af4fba19a8b
Showing with 172 additions and 2 deletions.
  1. +35 −0 lib/kerberos.js
  2. +43 −1 src/kerberos.cc
  3. +1 −0 src/kerberos.h
  4. +88 −1 src/kerberos_gss.cc
  5. +5 −0 src/kerberos_gss.h
@@ -36,6 +36,40 @@ function validateParameter(parameter, spec) {
}
}

/**
* This function provides a simple way to verify that a user name and password
* match those normally used for Kerberos authentication.
* It does this by checking that the supplied user name and password can be
* used to get a ticket for the supplied service.
* If the user name does not contain a realm, then the default realm supplied
* is used.
*
* For this to work properly the Kerberos must be configured properly on this
* machine.
* That will likely mean ensuring that the edu.mit.Kerberos preference file
* has the correct realms and KDCs listed.
*
* IMPORTANT: This method is vulnerable to KDC spoofing attacks and it should
* only used for testing. Do not use this in any production system - your
* security could be compromised if you do.
*
* @param {string} username The Kerberos user name. If no realm is supplied, then the `defaultRealm` will be used.
* @param {string} password The password for the user.
* @param {string} service The Kerberos service to check access for.
* @param {string} [defaultRealm] The default realm to use if one is not supplied in the user argument.
* @param {function} callback
*/
function checkPassword(username, password, service, defaultRealm, callback) {
if (typeof defaultRealm === 'function') (callback = defaultRealm), (defaultRealm = null);
validateParameter(username, { name: 'service', type: 'string' });
validateParameter(password, { name: 'options', type: 'string' });
validateParameter(service, { name: 'options', type: 'string' });
validateParameter(defaultRealm, { name: 'options', type: 'string' });
validateParameter(callback, { name: 'callback', type: 'function' });

kerberos.checkPassword(username, password, service, defaultRealm, callback);
}

/**
* This function returns the service principal for the server given a service
* type and hostname.
@@ -139,6 +173,7 @@ module.exports = {
initializeClient,
initializeServer,
serverPrincipalDetails,
checkPassword,

// result codes
AUTH_GSS_CONTINUE,
@@ -146,7 +146,6 @@ class ServerPrincipalDetailsWorker : public Nan::AsyncWorker {
std::string _service;
std::string _hostname;
std::string _details;

};

NAN_METHOD(ServerPrincipalDetails) {
@@ -157,6 +156,47 @@ NAN_METHOD(ServerPrincipalDetails) {
AsyncQueueWorker(new ServerPrincipalDetailsWorker(service, hostname, callback));
}

class CheckPasswordWorker : public Nan::AsyncWorker {
public:
CheckPasswordWorker(std::string username,
std::string password,
std::string service,
std::string defaultRealm,
Nan::Callback *callback)
: AsyncWorker(callback, "kerberos:CheckPassword"),
_username(username),
_password(password),
_service(service),
_defaultRealm(defaultRealm)
{}

virtual void Execute() {
std::unique_ptr<gss_result, FreeDeleter> result(
authenticate_user_krb5pwd(_username.c_str(), _password.c_str(), _service.c_str(), _defaultRealm.c_str()));

if (result->code == AUTH_GSS_ERROR) {
SetErrorMessage(result->message);
return;
}
}

private:
std::string _username;
std::string _password;
std::string _service;
std::string _defaultRealm;
};

NAN_METHOD(CheckPassword) {
std::string username(*Nan::Utf8String(info[0]));
std::string password(*Nan::Utf8String(info[1]));
std::string service(*Nan::Utf8String(info[2]));
std::string defaultRealm(*Nan::Utf8String(info[3]));
Nan::Callback* callback = new Nan::Callback(Nan::To<v8::Function>(info[4]).ToLocalChecked());

AsyncQueueWorker(new CheckPasswordWorker(username, password, service, defaultRealm, callback));
}

NAN_MODULE_INIT(Init) {
// Custom types
KerberosClient::Init(target);
@@ -168,6 +208,8 @@ NAN_MODULE_INIT(Init) {
Nan::GetFunction(Nan::New<FunctionTemplate>(InitializeServer)).ToLocalChecked());
Nan::Set(target, Nan::New("serverPrincipalDetails").ToLocalChecked(),
Nan::GetFunction(Nan::New<FunctionTemplate>(ServerPrincipalDetails)).ToLocalChecked());
Nan::Set(target, Nan::New("checkPassword").ToLocalChecked(),
Nan::GetFunction(Nan::New<FunctionTemplate>(CheckPassword)).ToLocalChecked());
}

NODE_MODULE(kerberos, Init)
@@ -7,5 +7,6 @@
NAN_METHOD(ServerPrincipalDetails);
NAN_METHOD(InitializeClient);
NAN_METHOD(InitializeServer);
NAN_METHOD(CheckPassword);

#endif
@@ -668,6 +668,93 @@ gss_result* authenticate_gss_server_step(gss_server_state *state, const char *ch
return ret;
}

gss_result* authenticate_user_krb5pwd(
const char *user, const char *pswd, const char *service,
const char *default_realm
) {
krb5_context kcontext = NULL;
krb5_error_code code;
krb5_principal client = NULL;
krb5_principal server = NULL;
gss_result* result = NULL;
int ret = 0;
char *name = NULL;
char *p = NULL;

code = krb5_init_context(&kcontext);
if (code) {
result = gss_error_result_with_message_and_code("Cannot initialize Kerberos5 context", code);
return result;
}

ret = krb5_parse_name (kcontext, service, &server);
if (ret) {
result = gss_error_result_with_message_and_code(krb5_get_err_text(kcontext, ret), ret);
goto end;
}

code = krb5_unparse_name(kcontext, server, &name);
if (code) {
result = gss_error_result_with_message_and_code(krb5_get_err_text(kcontext, code), code);
goto end;
}

free(name);
name = NULL;
name = (char *)malloc(256);
if (name == NULL) {
result = gss_error_result_with_message("Ran out of memory allocating name");
goto end;
}

p = strchr((char *)user, '@');
if (p == NULL) {
snprintf(name, 256, "%s@%s", user, default_realm);
} else {
snprintf(name, 256, "%s", user);
}

code = krb5_parse_name(kcontext, name, &client);
if (code) {
result = gss_error_result_with_message_and_code(krb5_get_err_text(kcontext, code), code);
goto end;
}

// verify krb5 user
krb5_creds creds;
krb5_get_init_creds_opt gic_options;
krb5_error_code verifyRet;

memset(&creds, 0, sizeof(creds));
krb5_get_init_creds_opt_init(&gic_options);
verifyRet = krb5_get_init_creds_password(
kcontext, &creds, client, (char *)pswd,
NULL, NULL, 0, NULL, &gic_options
);
if (verifyRet) {
result = gss_error_result_with_message_and_code(krb5_get_err_text(kcontext, verifyRet), verifyRet);
krb5_free_cred_contents(kcontext, &creds);
goto end;
}

krb5_free_cred_contents(kcontext, &creds);
result = gss_success_result(1);

end:
if (name) {
free(name);
}
if (client) {
krb5_free_principal(kcontext, client);
}
if (server) {
krb5_free_principal(kcontext, server);
}
krb5_free_context(kcontext);

return result;
}

static gss_result* gss_success_result(int ret)
{
gss_result* result = (gss_result *) malloc(sizeof(gss_result));
@@ -723,7 +810,7 @@ static gss_result* gss_error_result_with_message(const char* message)
{
gss_result* result = (gss_result *) malloc(sizeof(gss_result));
result->code = AUTH_GSS_ERROR;
result->message = strdup("Ran out of memory decoding challenge");
result->message = strdup(message);
return result;
}

@@ -73,3 +73,8 @@ gss_result* authenticate_gss_client_wrap(gss_client_state* state, const char* ch
gss_result* authenticate_gss_server_init(const char* service, gss_server_state* state);
int authenticate_gss_server_clean(gss_server_state* state);
gss_result* authenticate_gss_server_step(gss_server_state* state, const char* challenge);

gss_result* authenticate_user_krb5pwd(
const char *user, const char *pswd, const char *service,
const char *default_realm
);

0 comments on commit 60f476e

Please sign in to comment.
You can’t perform that action at this time.