diff --git a/lib/kerberos.js b/lib/kerberos.js index 4368ec23..62099795 100644 --- a/lib/kerberos.js +++ b/lib/kerberos.js @@ -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, diff --git a/src/kerberos.cc b/src/kerberos.cc index b4030983..e5a4034a 100644 --- a/src/kerberos.cc +++ b/src/kerberos.cc @@ -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 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(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(InitializeServer)).ToLocalChecked()); Nan::Set(target, Nan::New("serverPrincipalDetails").ToLocalChecked(), Nan::GetFunction(Nan::New(ServerPrincipalDetails)).ToLocalChecked()); + Nan::Set(target, Nan::New("checkPassword").ToLocalChecked(), + Nan::GetFunction(Nan::New(CheckPassword)).ToLocalChecked()); } NODE_MODULE(kerberos, Init) diff --git a/src/kerberos.h b/src/kerberos.h index 413adf2f..e9054891 100644 --- a/src/kerberos.h +++ b/src/kerberos.h @@ -7,5 +7,6 @@ NAN_METHOD(ServerPrincipalDetails); NAN_METHOD(InitializeClient); NAN_METHOD(InitializeServer); +NAN_METHOD(CheckPassword); #endif diff --git a/src/kerberos_gss.cc b/src/kerberos_gss.cc index 213348d9..47f6cec5 100644 --- a/src/kerberos_gss.cc +++ b/src/kerberos_gss.cc @@ -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; } diff --git a/src/kerberos_gss.h b/src/kerberos_gss.h index 1257ceea..0b10d2ac 100644 --- a/src/kerberos_gss.h +++ b/src/kerberos_gss.h @@ -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 +);