--- ./,pam_krb5.h Sun Sep 3 21:31:47 2006 +++ ./pam_krb5.h Thu Oct 5 16:45:34 2006 @@ -52,6 +52,10 @@ * user (currently only error messages from password changing). */ int quiet; + + int try_pkinit; /* try to use PKINIT if there is a smartcard */ + int use_pkinit; /* Only try PKINIT not password */ + char *pk_user; /* parameter to pass to PKINIT */ }; /* Stores a simple list of credentials. */ @@ -96,6 +100,15 @@ * are verified by checking them against the local system key. */ int pamk5_password_auth(struct context *, struct pam_args *, + char *in_tkt_service, struct credlist **); + +/* + * Same as above, but use PKINIT. Since some smartcard readers can read the pin + * directly from the reader, the pin must be provided by the prompter and + * not via a password + */ + +int pamk5_pkinit_auth(struct context *, struct pam_args *, char *in_tkt_service, struct credlist **); /* Generic prompting function to get information from the user. */ --- ./,support.c Sun Sep 3 21:31:47 2006 +++ ./support.c Thu Oct 5 17:07:05 2006 @@ -22,6 +22,7 @@ #include #include +#include #include "pam_krb5.h" /* @@ -161,6 +162,133 @@ fail: fclose(k5login); return PAM_AUTH_ERR; +} + +/* + * attempt to use a smartcard with PKINIT. This only works today with Heimdal + * Return PAM_CRED_UNAVAIL if there is no card in the reader or no reader. + */ + +int +pamk5_pkinit_auth(struct context *ctx, struct pam_args *args, + char *in_tkt_service, struct credlist **credlist) +{ + krb5_get_init_creds_opt *opts; + krb5_creds creds; + krb5_verify_init_creds_opt verify_opts; + int retval, success; + + +pamk5_debug(ctx, args, "pamk5_pkinit_auth enter"); + + /* Bail if we should be ignoring this user. */ + if (pamk5_should_ignore(ctx, args, ctx->name)) { + retval = PAM_SERVICE_ERR; + goto done; + } + + pamk5_credlist_new(ctx, credlist); + memset(&creds, 0, sizeof(krb5_creds)); + + /* Set ticket options. */ + krb5_get_init_creds_opt_alloc(ctx->context, &opts); + if (opts == NULL) { + retval = PAM_SERVICE_ERR; + goto done; + } +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_DEFAULT_FLAGS + krb5_get_init_creds_opt_set_default_flags(ctx->context, "pam", + args->realm_data, opts); +#endif + if (args->forwardable) + krb5_get_init_creds_opt_set_forwardable(opts, 1); + if (args->renew_lifetime != NULL) { + krb5_deltat rlife; + int ret; + + ret = krb5_string_to_deltat(args->renew_lifetime, &rlife); + if (ret != 0 || rlife == 0) { + pamk5_error(ctx, "bad renew_lifetime value: %s", + pamk5_compat_get_err_text(ctx->context, ret)); + retval = PAM_SERVICE_ERR; + goto done; + } + krb5_get_init_creds_opt_set_renew_life(opts, rlife); + } + + /* Fill in the principal to authenticate as. */ + retval = krb5_parse_name(ctx->context, ctx->name, &ctx->princ); + if (retval != 0) { + pamk5_debug_krb5(ctx, args, "krb5_parse_name", retval); + retval = PAM_SERVICE_ERR; + goto done; + } + + /* Get a bGT */ + pamk5_debug(ctx, args, "pk_user %s",args->pk_user?args->pk_user:"(NULL)"); + retval = krb5_get_init_creds_opt_set_pkinit(ctx->context, + opts, ctx->princ, args->pk_user, + NULL, NULL, NULL, + 0, pamk5_pam_prompter, ctx->pamh, NULL); +pamk5_debug(ctx, args, " krb5_get_init_creds_opt_set_pkinit %d",retval); + if (retval == 0) + retval = krb5_get_init_creds_password(ctx->context, + &creds, ctx->princ, NULL, pamk5_pam_prompter, + ctx->pamh, 0, in_tkt_service, opts); + +pamk5_debug(ctx, args, "retval=%d",retval); + success = (retval == 0) ? PAM_SUCCESS : PAM_AUTH_ERR; + + if (success == PAM_SUCCESS) { + retval = pamk5_credlist_append(ctx, credlist, creds); + if (retval != PAM_SUCCESS) + goto done; + } + + /* + * Last step. Verify the obtained TGT by obtaining and checking a service + * ticket. This is required to verify that no one is spoofing the KDC, + * but requires read access to a keytab with an appropriate key. By + * default, the Kerberos library will silently succeed if no verification + * keys are available, but the user can change this by setting + * verify_ap_req_nofail in [libdefaults] in /etc/krb5.conf. + * + * Don't do this if we're authenticating for password changes. We can't + * get a service ticket from a kadmin/changepw ticket and the user + * probably isn't going to have access to a keytab to check KDC spoofing + * anyway. + */ + if (retval == 0 && in_tkt_service == NULL) { + krb5_verify_init_creds_opt_init(&verify_opts); + retval = krb5_verify_init_creds(ctx->context, &creds, NULL, NULL, + &ctx->cache, &verify_opts); + if (retval != 0) { + pamk5_error(ctx, "credential verification failed: %s", + pamk5_compat_get_err_text(ctx->context, retval)); + retval = PAM_AUTH_ERR; + goto done; + } + } + + /* If we failed, return the appropriate PAM error code. */ + if (retval != 0) { + pamk5_debug_krb5(ctx, args, "krb5_get_init_creds_password", retval); + if (retval == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN) + retval = PAM_USER_UNKNOWN; + else if (retval == KRB5_KDC_UNREACH) + retval = PAM_AUTHINFO_UNAVAIL; + else if (retval == HX509_PKCS11_NO_TOKEN || retval == HX509_PKCS11_NO_SLOT) + retval = PAM_CRED_UNAVAIL; + else + retval = PAM_AUTH_ERR; + goto done; + } + retval = PAM_SUCCESS; + +done: + if (opts) + krb5_get_init_creds_opt_free(opts); + return retval; } --- ./,pam_krb5_auth.c Sun Sep 3 21:31:47 2006 +++ ./pam_krb5_auth.c Thu Oct 5 16:42:53 2006 @@ -100,6 +100,7 @@ int pamret = PAM_SERVICE_ERR; char cache_name[] = "/tmp/krb5cc_pam_XXXXXX"; int ccfd; + int authenticated = 0; args = pamk5_args_parse(NULL, flags, argc, argv); ENTRY(ctx, args, flags); @@ -116,9 +117,25 @@ } /* Do the actual authentication. */ + + if (args->use_pkinit) { + pamret = pamk5_pkinit_auth(ctx, args, NULL, &clist); + if (pamret != PAM_SUCCESS) + goto done; + } + else if (args->try_pkinit) { + pamret = pamk5_pkinit_auth(ctx, args, NULL, &clist); + if (pamret != PAM_SUCCESS && pamret != PAM_CRED_UNAVAIL) + goto done; /* OK to fall through if no smartcard need better return code */ + authenticated = 1; + } + + if (authenticated = 0) { pamret = pamk5_password_auth(ctx, args, NULL, &clist); if (pamret != PAM_SUCCESS) goto done; + authenticated = 1; + } /* Check .k5login. */ pamret = pamk5_validate_auth(ctx, args); --- ./,options.c Sun Sep 3 21:31:47 2006 +++ ./options.c Thu Oct 5 16:15:46 2006 @@ -154,6 +154,14 @@ args->use_authtok = 1; else if (strcmp(argv[i], "use_first_pass") == 0) args->use_first_pass = 1; + else if (strcmp(argv[i], "try_pkinit") == 0) + args->try_pkinit = 1; + else if (strcmp(argv[i], "use_pkinit") == 0) + args->use_pkinit = 1; + else if (strncmp(argv[i], "pk_user=", 8) == 0) { + args->pk_user = strdup(&argv[i][strlen("pk_user=")]); + } + } if (flags & PAM_SILENT) @@ -177,6 +185,8 @@ free(args->realm); if (args->renew_lifetime != NULL) free(args->renew_lifetime); + if (args->pk_user !=NULL) + free(args->pk_user); pamk5_compat_free_realm(args); free(args); }