static gss_client_response *create_krb5_ccache(gss_server_state *state, krb5_context kcontext, krb5_principal princ, krb5_ccache *ccache) { char *ccname = NULL; int fd; krb5_error_code problem; krb5_ccache tmp_ccache = NULL; gss_client_response *error = NULL; // TODO: mod_auth_kerb used a temp file under /run/httpd/krbcache. what can we do? ccname = strdup("FILE:/tmp/krb5cc_nodekerberos_XXXXXX"); if (!ccname) die1("Memory allocation failed"); fd = mkstemp(ccname + strlen("FILE:")); if (fd < 0) { error = other_error("mkstemp() failed: %s", strerror(errno)); goto end; } close(fd); problem = krb5_cc_resolve(kcontext, ccname, &tmp_ccache); if (problem) { error = krb5_ctx_error(kcontext, problem); goto end; } problem = krb5_cc_initialize(kcontext, tmp_ccache, princ); if (problem) { error = krb5_ctx_error(kcontext, problem); goto end; } state->delegated_credentials_cache = strdup(ccname); // TODO: how/when to cleanup the creds cache file? // TODO: how to expose the credentials expiration time? *ccache = tmp_ccache; tmp_ccache = NULL; end: if (tmp_ccache) krb5_cc_destroy(kcontext, tmp_ccache); if (ccname && error) unlink(ccname); if (ccname) free(ccname); return error; }
static gss_client_response *store_gss_creds(gss_server_state *state) { OM_uint32 maj_stat, min_stat; krb5_principal princ = NULL; krb5_ccache ccache = NULL; krb5_error_code problem; krb5_context context; gss_client_response *response = NULL; problem = krb5_init_context(&context); if (problem) { response = other_error("No auth_data value in request from client"); return response; } problem = krb5_parse_name(context, state->username, &princ); if (problem) { response = krb5_ctx_error(context, problem); goto end; } if ((response = create_krb5_ccache(state, context, princ, &ccache))) { goto end; } maj_stat = gss_krb5_copy_ccache(&min_stat, state->client_creds, ccache); if (GSS_ERROR(maj_stat)) { response = gss_error(__func__, "gss_krb5_copy_ccache", maj_stat, min_stat); response->return_code = AUTH_GSS_ERROR; goto end; } krb5_cc_close(context, ccache); ccache = NULL; response = calloc(1, sizeof(gss_client_response)); if(response == NULL) die1("Memory allocation failed"); // TODO: something other than AUTH_GSS_COMPLETE? response->return_code = AUTH_GSS_COMPLETE; end: if (princ) krb5_free_principal(context, princ); if (ccache) krb5_cc_destroy(context, ccache); krb5_free_context(context); return response; }
static gss_client_response *init_gss_creds(const char *credential_cache, gss_cred_id_t *cred) { OM_uint32 maj_stat; OM_uint32 min_stat; krb5_context context; krb5_error_code problem; gss_client_response *response = NULL; krb5_ccache ccache = NULL; *cred = GSS_C_NO_CREDENTIAL; if (credential_cache == NULL || strlen(credential_cache) == 0) { return NULL; } problem = krb5_init_context(&context); if (problem) { return other_error("unable to initialize krb5 context (%d)", (int)problem); } problem = krb5_cc_resolve(context, credential_cache, &ccache); if (problem) { response = krb5_ctx_error(context, problem); goto done; } maj_stat = gss_krb5_import_cred(&min_stat, ccache, NULL, NULL, cred); if (GSS_ERROR(maj_stat)) { response = gss_error(__func__, "gss_krb5_import_cred", maj_stat, min_stat); response->return_code = AUTH_GSS_ERROR; } done: if (response && ccache) { krb5_cc_close(context, ccache); } krb5_free_context(context); return response; }
/* * The username/password have been verified, now we must verify the * response came from a valid KDC by getting a ticket for our own * service that we can verify using our keytab. * * Based on mod_auth_kerb */ static gss_client_response *verify_krb5_kdc(krb5_context context, krb5_creds *creds, const char *service) { krb5_error_code problem; krb5_keytab keytab = NULL; krb5_ccache tmp_ccache = NULL; krb5_principal server_princ = NULL; krb5_creds *new_creds = NULL; krb5_auth_context auth_context = NULL; krb5_data req; gss_client_response *response = NULL; memset(&req, 0, sizeof(req)); problem = krb5_kt_default (context, &keytab); if (problem) { response = krb5_ctx_error(context, problem); goto out; } problem = krb5_cc_new_unique(context, "MEMORY", NULL, &tmp_ccache); if (problem) { response = krb5_ctx_error(context, problem); goto out; } problem = krb5_cc_initialize(context, tmp_ccache, creds->client); if (problem) { response = krb5_ctx_error(context, problem); goto out; } problem = krb5_cc_store_cred(context, tmp_ccache, creds); if (problem) { response = krb5_ctx_error(context, problem); goto out; } problem = krb5_parse_name(context, service, &server_princ); if (problem) { response = krb5_ctx_error(context, problem); goto out; } /* * creds->server is (almost always?) krbtgt service, and server_princ is not. * In which case retrieve a service ticket for server_princ service from KDC */ if (!krb5_principal_compare(context, server_princ, creds->server)) { krb5_creds match_cred; memset (&match_cred, 0, sizeof(match_cred)); match_cred.client = creds->client; match_cred.server = server_princ; problem = krb5_get_credentials(context, 0, tmp_ccache, &match_cred, &new_creds); if (problem) { response = krb5_ctx_error(context, problem); goto out; } creds = new_creds; } problem = krb5_mk_req_extended(context, &auth_context, 0, NULL, creds, &req); if (problem) { response = krb5_ctx_error(context, problem); goto out; } krb5_auth_con_free(context, auth_context); auth_context = NULL; problem = krb5_auth_con_init(context, &auth_context); if (problem) { response = krb5_ctx_error(context, problem); goto out; } /* disable replay cache checks */ krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE); problem = krb5_rd_req(context, &auth_context, &req, server_princ, keytab, 0, NULL); if (problem) { response = krb5_ctx_error(context, problem); goto out; } out: krb5_free_data_contents(context, &req); if (auth_context) { krb5_auth_con_free(context, auth_context); } if (new_creds) { krb5_free_creds(context, new_creds); } if (server_princ) { krb5_free_principal(context, server_princ); } if (tmp_ccache) { krb5_cc_destroy (context, tmp_ccache); } if (keytab) { krb5_kt_close (context, keytab); } return response; }
/* * username, password: Credentials to validate. Null not allowed * service: Service principal (e.g. HTTP/somehost.example.org) of key * stored in default keytab which will be used to verify KDC. * Empty string (*not* NULL) will bypass KDC verification * return: response->return_code will be * -1 (AUTH_GSS_ERROR) for error, see response->message * 0 for auth fail * 1 for auth ok */ gss_client_response *authenticate_user_krb5_password(const char *username, const char *password, const char *service) { krb5_context context = NULL; krb5_error_code problem; krb5_principal user_principal = NULL; krb5_get_init_creds_opt *opt = NULL; krb5_creds creds; bool auth_ok = false; gss_client_response *response = NULL; if (username == NULL || password == NULL || service == NULL) { return other_error("username, password and service must all be non-null"); } memset(&creds, 0, sizeof(creds)); problem = krb5_init_context(&context); if (problem) { // can't call krb5_ctx_error without a context... response = other_error("unable to initialize krb5 context (%d)", (int)problem); goto out; } problem = krb5_parse_name(context, username, &user_principal); if (problem) { response = krb5_ctx_error(context, problem); goto out; } problem = krb5_get_init_creds_opt_alloc(context, &opt); if (problem) { response = krb5_ctx_error(context, problem); goto out; } problem = krb5_get_init_creds_password(context, &creds, user_principal, (char *)password, NULL, NULL, 0, NULL, opt); switch (problem) { case 0: auth_ok = true; break; case KRB5KDC_ERR_PREAUTH_FAILED: case KRB5KRB_AP_ERR_BAD_INTEGRITY: case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: /* "expected" error */ auth_ok = false; break; default: /* unexpected error */ response = krb5_ctx_error(context, problem); break; } if (auth_ok && strlen(service) > 0) { response = verify_krb5_kdc(context, &creds, service); } out: krb5_free_cred_contents(context, &creds); if (opt != NULL) { krb5_get_init_creds_opt_free(context, opt); } if (user_principal != NULL) { krb5_free_principal(context, user_principal); } if (context != NULL) { krb5_free_context(context); } if (response == NULL) { response = calloc(1, sizeof(gss_client_response)); if(response == NULL) die1("Memory allocation failed"); response->return_code = auth_ok ? 1 : 0; } return response; }