[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

pam_krb5 with PKINIT from Heimdal and MIT




So while waiting for the pam_krb5-2.4 source to show up on
http://www.eyrie.org, I am attaching the Heimdal pkinit mods for
pam-krb5-2.3 as a point of discussion. I would hope the MIT
and CITI people would comment on their pkinit API.

I have this running on Ubuntu Edgy, with opensc-0.11.1 using the
Heimdal 20061002 snapshot.  I am using a PIV smart card with a Windows
compatible cert, and W2k3 as the KDC.

  o the pam_krb5 is the one place the Heimdal and MIT pkinit APIs come
    in contact. I have not seen what the CITI people have proposed. Depending
    on commonality of the APIs, parts of this code may need to be moved
    to compat_heimdal.c

  o The code depends on the hx509_err.h, as it tests for two error codes.
    i.e. no card readers found and no card in any reader. I sent a bug report
    to the Heimdal list on this today, suggesting that these should be KRB5_
    error codes and should be in the MIT code as well.

  o Three new pam options are added:
       use_pkinit - only do pkinit authentication, don't do password
       try_pkinit - try for pkinit, and if no card or reader, try password.
       pk_user=   - the pk_user parameter as passed to the pkinit code.
             for example: pk_user=PKCS11:/usr/lib/opensc-pkcs11.so
             With a smart card this is always the same.

  o The thinking is if the user puts in a smart card, try and use it.
    If no card is present use passwords as before. If they put in a card
    and it fails, don't fall back to password, make them take the card
    out first.

  o The user is expected to type in a username to PAM, unlike Windows
    that expects the UPN to be in the certificate. Future certificates
    should not be expected to contain realm specific information.

  o I chose to copy the pamk5_password_auth routine and rename it
    pamk5_pkinit_auth, and cut out what was not needed. There is a lot
    of duplicate code between the two. This was the easy way, as
    I have not seen the MIT pkinit API.

  o The Heimdal code uses the  krb5_get_init_creds_opt_alloc to
    allocate the opts structure, rather then having the structure
    defined on the stack. In any case the same opts can not be used
    for pkinit and if it fails for password. A separately initialized
    opts structure in needed.

  o Since the Heimdal default it to compile in pkinit, or at least
    a stub for it, this pkinit code can be compiled into pam_krb5
    by default. I would hope the MIT code would do something similar.

  o To compile pam_krb5-2.3 with this patch, you also need the mods I
    sent in as a bug yesterday #391276, that you have fixed in 2.4.

> 
> Matthijs, provided that I didn't mess something up again, this should fix
> the compilation problems with Heimdal, including the couple of warnings
> that were showing up in the buildd logs.
> 
> BTw, Bug#269457 against libpam-heimdal was also fixed in pam-krb5 2.0.
> 

-- 

  Douglas E. Engert  <DEEngert@anl.gov>
  Argonne National Laboratory
  9700 South Cass Avenue
  Argonne, Illinois  60439
  (630) 252-5444
--- ./,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 <sys/stat.h>
 #include <unistd.h>
 
+#include <hx509_err.h>
 #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);
     }