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

Changes to PKINIT



Hi Love, and *

please find enclosed a patch containing some adaptations to the Heimdal
PKINIT implementation. It's a single "monster" patch (sorry for not splitting
it into multiple parts, but it changes still the same pieces of code).  It
allows for following main features:

1. Support for proxy certificates so that clients can use proxies to receive
   a kerberos ticket. Proxy certicates are widely used in the Grid world and
   are defined by RFC3820, and this support allows the grid users to easily
   get tickets (when accessing kerberos-secured resources). The support
   requires either the Globus toolkit or openssl 0.9.7g

2. Support for CRLs checking, so the parties (KDC and clients) can detect if
   the peer's certificate got revoked and refuse it.

3. The KDC can reload the PKI config files on SIGHUP (CRLs and
   pki-mapping). It's sort of hack but I don't like restarting the KDC upon
   each update of CRLs or adding or removal of a new user.

4. The KDC checks the lifetime of the client's cert and don't issue a ticket
   that would have longer lifetime than the certificate (important especialy
   when the short-time proxy certificate are used). BTW, does anyone know if
   this is handled by the PKINIT draft? (perhaps I could ask the the IETF
   mailing list).

I hope you'll find the modifications useful and possibly add them to the main
code.

cheers

Daniel
Index: lib/krb5/context.c
===================================================================
RCS file: /cvs/meta/heimdal/lib/krb5/context.c,v
retrieving revision 1.1.1.1.6.7
diff -u -r1.1.1.1.6.7 context.c
--- lib/krb5/context.c	21 Jun 2005 06:19:08 -0000	1.1.1.1.6.7
+++ lib/krb5/context.c	22 Aug 2005 08:08:23 -0000
@@ -182,6 +182,8 @@
     INIT_FIELD(context, bool, srv_lookup, context->srv_lookup, "dns_lookup_kdc");
     INIT_FIELD(context, int, large_msg_size, 6000, "large_message_size");
     context->default_cc_name = NULL;
+
+    INIT_FIELD(context, bool, enable_proxy_certificates, FALSE, "enable_proxy_certificates");
     return 0;
 }
 
@@ -236,6 +238,11 @@
     krb5_kt_register (p, &krb5_srvtab_fkt_ops);
     krb5_kt_register (p, &krb5_any_ops);
 
+#ifdef GLOBUS
+    globus_module_activate(GLOBUS_GSI_CALLBACK_MODULE);
+    globus_module_activate(GLOBUS_GSI_CERT_UTILS_MODULE);
+#endif
+
 out:
     if(ret) {
 	krb5_free_context(p);
@@ -268,6 +275,10 @@
     }
     memset(context, 0, sizeof(*context));
     free(context);
+#if GLOBUS
+    globus_module_deactivate(GLOBUS_GSI_CERT_UTILS_MODULE);
+    globus_module_deactivate(GLOBUS_GSI_CALLBACK_MODULE);
+#endif
 }
 
 krb5_error_code KRB5_LIB_FUNCTION
Index: lib/krb5/krb5-private.h
===================================================================
RCS file: /cvs/meta/heimdal/lib/krb5/krb5-private.h,v
retrieving revision 1.1.1.1.6.5
retrieving revision 1.5
diff -u -r1.1.1.1.6.5 -r1.5
--- lib/krb5/krb5-private.h	21 Jun 2005 06:19:09 -0000	1.1.1.1.6.5
+++ lib/krb5/krb5-private.h	22 Jul 2005 07:12:11 -0000	1.5
@@ -268,6 +268,11 @@
 	struct krb5_pk_identity */*id*/,
 	krb5_data */*sd_data*/);
 
+void KRB5_LIB_FUNCTION
+_krb5_pk_free_openssl_id (
+	krb5_context /*context*/,
+	struct krb5_pk_identity */*id*/);
+
 krb5_error_code KRB5_LIB_FUNCTION
 _krb5_pk_load_openssl_id (
 	krb5_context /*context*/,
Index: lib/krb5/krb5.h
===================================================================
RCS file: /cvs/meta/heimdal/lib/krb5/krb5.h,v
retrieving revision 1.1.1.1.6.7
diff -u -r1.1.1.1.6.7 krb5.h
--- lib/krb5/krb5.h	21 Jun 2005 06:19:09 -0000	1.1.1.1.6.7
+++ lib/krb5/krb5.h	22 Aug 2005 08:08:23 -0000
@@ -443,6 +443,7 @@
     int pkinit_flags;
     void *mutex;			/* protects error_string/error_buf */
     int large_msg_size;
+    krb5_boolean enable_proxy_certificates;
 } krb5_context_data;
 
 enum {
Index: lib/krb5/krb5_locl.h
===================================================================
RCS file: /cvs/meta/heimdal/lib/krb5/krb5_locl.h,v
retrieving revision 1.1.1.1.6.6
diff -u -r1.1.1.1.6.6 krb5_locl.h
--- lib/krb5/krb5_locl.h	21 Jun 2005 06:19:09 -0000	1.1.1.1.6.6
+++ lib/krb5/krb5_locl.h	22 Aug 2005 08:08:23 -0000
@@ -127,6 +127,10 @@
 #include <door.h>
 #endif
 
+#if GLOBUS
+#include <globus_gsi_callback.h>
+#endif
+
 #include <roken.h>
 #include <parse_time.h>
 #include <base64.h>
Index: lib/krb5/pkinit.c
===================================================================
RCS file: /cvs/meta/heimdal/lib/krb5/pkinit.c,v
retrieving revision 1.1.4.3
diff -u -r1.1.4.3 pkinit.c
--- lib/krb5/pkinit.c	21 Jun 2005 06:19:09 -0000	1.1.4.3
+++ lib/krb5/pkinit.c	22 Aug 2005 08:08:23 -0000
@@ -105,6 +105,7 @@
 
 struct krb5_pk_cert {
     X509 *cert;
+    STACK_OF(X509) *chain;
 };
 
 struct krb5_pk_init_ctx_data {
@@ -118,6 +119,8 @@
 {
     if (cert->cert)
 	X509_free(cert->cert);
+    if (cert->chain)
+       sk_X509_pop_free(cert->chain, X509_free);
     free(cert);
 }
 
@@ -997,13 +1000,17 @@
 			 struct krb5_pk_identity *id,
 			 const SignerIdentifier *client,
 			 STACK_OF(X509) *chain,
-			 X509 **client_cert)
+			 X509 **client_cert,
+			 STACK_OF(X509) **client_chain)
 {
     X509_STORE *cert_store = NULL;
     X509_STORE_CTX *store_ctx = NULL;
     X509 *cert = NULL;
     int i;
     int ret;
+#ifdef GLOBUS
+    globus_gsi_callback_data_t gsi_cb_data = NULL;
+#endif
 
     ret = KRB5_KDC_ERROR_CLIENT_NAME_MISMATCH;
     for (i = 0; i < sk_X509_num(chain); i++) {
@@ -1026,6 +1033,9 @@
 			      ERR_error_string(ERR_get_error(), NULL));
     }
 
+    for (i = 0; i < sk_X509_CRL_num(id->crls); i++)
+       X509_STORE_add_crl(cert_store, sk_X509_CRL_value(id->crls, i));
+
     store_ctx = X509_STORE_CTX_new();
     if (store_ctx == NULL) {
 	ret = ENOMEM;
@@ -1034,10 +1044,36 @@
 			      ERR_error_string(ERR_get_error(), NULL));
 	goto end;
     }
+
    
     X509_STORE_CTX_init(store_ctx, cert_store, cert, chain);
     X509_STORE_CTX_trusted_stack(store_ctx, id->trusted_certs);
+    X509_STORE_CTX_set_flags (store_ctx, X509_V_FLAG_CRL_CHECK);
+#if OPENSSL_VERSION_NUMBER >= 0x0090707fL
+    /* RFC3820-compliant proxy certificates are supported by openssl as of 0.9.7g */
+    if (context->enable_proxy_certificates)
+       X509_STORE_CTX_set_flags(store_ctx, X509_V_FLAG_ALLOW_PROXY_CERTS);
+#endif
+
+#if GLOBUS
+    if (context->enable_proxy_certificates) {
+       int gsi_cb_data_index;
+
+       store_ctx->check_issued = globus_gsi_callback_check_issued;
+       X509_STORE_CTX_set_verify_cb(store_ctx, globus_gsi_callback_create_proxy_callback);
+       globus_gsi_callback_data_init(&gsi_cb_data);
+       globus_gsi_callback_get_X509_STORE_callback_data_index(&gsi_cb_data_index);
+       X509_STORE_CTX_set_ex_data(store_ctx, gsi_cb_data_index, (void *)gsi_cb_data);
+    }
+#endif
+
+
     X509_verify_cert(store_ctx);
+
+#ifdef GLOBUS
+    if (gsi_cb_data)
+       globus_gsi_callback_data_destroy(gsi_cb_data);
+#endif
     /* the last checked certificate is in store_ctx->current_cert */
     krb5_clear_error_string(context);
     switch(store_ctx->error) {
@@ -1094,6 +1130,8 @@
 
     if (client_cert && cert)
 	*client_cert = X509_dup(cert);
+    if (client_chain)
+       *client_chain = X509_STORE_CTX_get1_chain(store_ctx);
 
  end:
     if (cert_store)
@@ -1105,7 +1143,7 @@
 
 static int
 cert_to_X509(krb5_context context, CertificateSetReal *set,
-	     STACK_OF(X509_CRL) **certs)
+	     STACK_OF(X509) **certs)
 {
     krb5_error_code ret;
     int i;
@@ -1190,6 +1228,7 @@
     CertificateSetReal set;
     EVP_MD_CTX md;
     X509 *cert;
+    STACK_OF(X509) *client_chain;
     SignedData sd;
     size_t size;
     
@@ -1245,7 +1284,8 @@
     ret = pk_verify_chain_standard(context, id,
 				   &signer_info->sid,
 				   certificates,
-				   &cert);
+				   &cert,
+				   &client_chain);
     sk_X509_free(certificates);
     if (ret)
 	goto out;
@@ -1253,6 +1293,7 @@
     if (signer_info->signature.length == 0) {
 	free_SignedData(&sd);
 	X509_free(cert);
+	sk_X509_pop_free(client_chain, X509_free);
 	krb5_set_error_string(context, "PKINIT: signature missing from"
 			      "pkinit response");
 	return KRB5_KDC_ERR_INVALID_SIG; 
@@ -1272,6 +1313,7 @@
 	evp_type = EVP_sha1();
     else {
 	X509_free(cert);
+	sk_X509_pop_free(client_chain, X509_free);
 	krb5_set_error_string(context, "PKINIT: The requested digest "
 			      "algorithm is not supported");
 	ret = KRB5_KDC_ERR_INVALID_SIG;
@@ -1288,6 +1330,7 @@
 			  public_key);
     if (ret != 1) {
 	X509_free(cert);
+	sk_X509_pop_free(client_chain, X509_free);
 	krb5_set_error_string(context, "PKINIT: signature didn't verify: %s",
 			      ERR_error_string(ERR_get_error(), NULL));
 	ret = KRB5_KDC_ERR_INVALID_SIG;
@@ -1316,6 +1359,7 @@
 	goto out;
     }
     (*signer)->cert = cert;
+    (*signer)->chain = client_chain;
 
  out:
     free_SignedData(&sd);
@@ -2306,11 +2350,12 @@
 			 char *password)
 {
     STACK_OF(X509) *trusted_certs = NULL;
+    STACK_OF(X509_CRLS) *crls = NULL;
     struct krb5_pk_identity *id = NULL;
     krb5_error_code ret;
     struct dirent *file;
     char *dirname = NULL;
-    DIR *dir;
+    DIR *dir = NULL;
     FILE *f;
     krb5_error_code (*load_pair)(krb5_context, 
 				 char *, 
@@ -2395,13 +2440,15 @@
     }
 
     trusted_certs = sk_X509_new_null();
+    crls = sk_X509_CRL_new_null();
     while ((file = readdir(dir)) != NULL) {
 	X509 *cert;
+	X509_CRL *crl;
 	char *filename;
 
 	/*
-	 * Assume the certificate filenames constist of hashed subject
-	 * name followed by suffix ".0"
+	 * Assume the certificate filenames consist of hashed subject
+	 * name followed by suffix ".0" 
 	 */
 
 	if (strlen(file->d_name) == 10 && strcmp(&file->d_name[8],".0") == 0) {
@@ -2417,7 +2464,6 @@
 		krb5_set_error_string(context, "PKINIT: open %s: %s",
 				      filename, strerror(ret));
 		free(filename);
-		closedir(dir);
 		goto out;
 	    }
 	    cert = PEM_read_X509(f, NULL, NULL, NULL);
@@ -2428,8 +2474,44 @@
 	    }
 	    free(filename);
 	}
+
+	/* Also try loading CRLs available, i.e. filenames of the form:
+	 *    <hash>.r[0-9]+
+	 */
+	if (strlen(file->d_name) >= 11 && strstr(file->d_name, ".r") != NULL) {
+	   asprintf(&filename, "%s/%s", dirname, file->d_name);
+	   if (filename == NULL) {
+	      ret = ENOMEM;
+	      krb5_set_error_string(context, "malloc: out or memory");
+	      goto out;
+	   }
+	   
+	   f = fopen(filename, "r");
+	   if (f == NULL) {
+	      ret = errno;
+	      krb5_set_error_string(context, "PKINIT: error reading CRL %s: %s",
+		    		    filename, strerror(ret));
+	      free(filename);
+	      goto out;
+	   }
+
+	   crl = PEM_read_X509_CRL(f, NULL, NULL, NULL);
+	   fclose(f);
+	   if (crl == NULL) {
+	      ret = -1; /* XXX */
+	      krb5_set_error_string(context, "PKINIT: error loading CRL %s: %s",
+		    		    filename,
+				    ERR_error_string(ERR_get_error(), NULL));
+	      free(filename);
+	      goto out;
+	   }
+	   sk_X509_CRL_push(crls, crl);
+	   free(filename);
+	}
+
     }
     closedir(dir);
+    dir = NULL;
 
     if (sk_X509_num(trusted_certs) == 0) {
 	krb5_set_error_string(context,
@@ -2440,6 +2522,7 @@
     }
 
     id->trusted_certs = trusted_certs;
+    id->crls = crls;
 
     *ret_id = id;
 
@@ -2457,6 +2540,8 @@
 	    EVP_PKEY_free(id->private_key);
 	free(id);
     }
+    if (dir)
+       closedir(dir);
 
     return ret;
 }
@@ -2581,3 +2666,25 @@
     return EINVAL;
 #endif
 }
+
+void KRB5_LIB_FUNCTION
+_krb5_pk_free_openssl_id(krb5_context context,
+      			 struct krb5_pk_identity *id)
+{
+   if (id == NULL)
+      return;
+
+   if (id->private_key)
+      EVP_PKEY_free(id->private_key);
+   if (id->cert)
+      sk_X509_pop_free(id->cert, X509_free);
+   if (id->trusted_certs)
+      sk_X509_pop_free(id->trusted_certs, X509_free);
+   if (id->crls)
+      sk_X509_pop_free(id->crls, X509_CRL_free);
+   if (id->engine) {
+      ENGINE_finish(id->engine);
+      ENGINE_free(id->engine);
+   }
+   free(id);
+}
Index: kdc/connect.c
===================================================================
RCS file: /cvs/meta/heimdal/kdc/connect.c,v
retrieving revision 1.1.1.1.6.7
diff -u -r1.1.1.1.6.7 connect.c
--- kdc/connect.c	21 Jun 2005 06:19:05 -0000	1.1.1.1.6.7
+++ kdc/connect.c	22 Aug 2005 08:08:23 -0000
@@ -771,6 +771,32 @@
     }
 }
 
+static int
+reload_pki_config(krb5_context context)
+{
+   const char *user_id, *x509_anchors;
+   int ret;
+
+   kdc_log(5, "Reloading PKI mappings and CRLs");
+
+   user_id = krb5_config_get_string(context, NULL,
+	 			    "kdc",
+				    "pki-identity",
+				    NULL);
+   if (user_id == NULL)
+      krb5_errx(context, 1, "no identity for pkinit");
+
+   x509_anchors = krb5_config_get_string(context, NULL,
+	 				 "kdc",
+					 "pki-anchors",
+					 NULL);
+   if (x509_anchors == NULL)
+      krb5_errx(context, 1, "no X509 anchors for pkinit");
+
+   pk_initialize(user_id, x509_anchors);
+   return 0;
+}
+
 void
 loop(void)
 {
@@ -787,6 +813,12 @@
 	int max_fd = 0;
 	int i;
 
+	if (reload_flag) {
+	   if (enable_pkinit)
+	      reload_pki_config(context);
+	   reload_flag = 0;
+	}
+
 	FD_ZERO(&fds);
 	for(i = 0; i < ndescr; i++) {
 	    if(d[i].s >= 0){
Index: kdc/kdc_locl.h
===================================================================
RCS file: /cvs/meta/heimdal/kdc/kdc_locl.h,v
retrieving revision 1.1.1.1.4.7
diff -u -r1.1.1.1.4.7 kdc_locl.h
--- kdc/kdc_locl.h	21 Jun 2005 06:19:05 -0000	1.1.1.1.4.7
+++ kdc/kdc_locl.h	22 Aug 2005 08:08:23 -0000
@@ -44,6 +44,7 @@
 
 extern int require_preauth;
 extern sig_atomic_t exit_flag;
+extern sig_atomic_t reload_flag;
 extern size_t max_request;
 extern time_t kdc_warn_pwexpire;
 extern struct dbinfo {
@@ -142,6 +143,8 @@
 krb5_error_code do_kaserver (unsigned char*, size_t, krb5_data*, const char*, 
 			     struct sockaddr_in*);
 
+void fix_time(time_t **t);
+
 
 
 #endif /* __KDC_LOCL_H__ */
Index: kdc/kerberos5.c
===================================================================
RCS file: /cvs/meta/heimdal/kdc/kerberos5.c,v
retrieving revision 1.1.1.1.4.7
diff -u -r1.1.1.1.4.7 kerberos5.c
--- kdc/kerberos5.c	21 Jun 2005 06:19:05 -0000	1.1.1.1.4.7
+++ kdc/kerberos5.c	22 Aug 2005 08:08:23 -0000
@@ -37,7 +37,7 @@
 
 #define MAX_TIME ((time_t)((1U << 31) - 1))
 
-static void
+void
 fix_time(time_t **t)
 {
     if(*t == NULL){
Index: kdc/main.c
===================================================================
RCS file: /cvs/meta/heimdal/kdc/main.c,v
retrieving revision 1.1.1.1.6.6
retrieving revision 1.6
diff -u -r1.1.1.1.6.6 -r1.6
--- kdc/main.c	21 Jun 2005 06:19:05 -0000	1.1.1.1.6.6
+++ kdc/main.c	27 Jul 2005 10:58:32 -0000	1.6
@@ -40,5 +40,6 @@
 
 sig_atomic_t exit_flag = 0;
+sig_atomic_t reload_flag = 0;
 krb5_context context;
 
 extern int detach_from_console;
@@ -49,6 +50,12 @@
     exit_flag = sig;
 }
 
+static RETSIGTYPE
+sigreload(int sig)
+{
+   reload_flag = sig;
+}
+
 int
 main(int argc, char **argv)
 {
@@ -102,10 +109,14 @@
 
 	sa.sa_handler = SIG_IGN;
 	sigaction(SIGPIPE, &sa, NULL);
+
+	sa.sa_handler = sigreload;
+	sigaction(SIGHUP, &sa, NULL);
     }
 #else
     signal(SIGINT, sigterm);
     signal(SIGTERM, sigterm);
     signal(SIGXCPU, sigterm);
     signal(SIGPIPE, SIG_IGN);
+    signal(SIGHUP, sigreload);
 #endif
Index: kdc/pkinit.c
===================================================================
RCS file: /cvs/meta/heimdal/kdc/pkinit.c,v
retrieving revision 1.1.4.3
diff -u -r1.1.4.3 pkinit.c
--- kdc/pkinit.c	21 Jun 2005 06:19:05 -0000	1.1.4.3
+++ kdc/pkinit.c	22 Aug 2005 08:08:23 -0000
@@ -64,6 +64,7 @@
 /* XXX copied from lib/krb5/pkinit.c */
 struct krb5_pk_cert {
     X509 *cert;
+    STACK_OF(X509) *chain;
 };
 
 enum pkinit_type {
@@ -112,8 +113,8 @@
   }									\
 }
 
-static struct krb5_pk_identity *kdc_identity;
-static struct pk_principal_mapping principal_mappings;
+static struct krb5_pk_identity *kdc_identity = NULL;
+static struct pk_principal_mapping principal_mappings = {0, NULL};
 
 /*
  *
@@ -1213,9 +1214,9 @@
 	memset(&rep, 0, sizeof(rep));
 
 	if (client_params->dh == NULL) {
-	    rep.element = choice_PA_PK_AS_REP_encKeyPack;
 	    ContentInfo info;
 
+	    rep.element = choice_PA_PK_AS_REP_encKeyPack;
 	    krb5_generate_random_keyblock(context, enctype, 
 					  &client_params->reply_key);
 	    ret = pk_mk_pa_reply_enckey(context,
@@ -1317,9 +1318,9 @@
 	    krb5_set_error_string(context, "DH -25 not implemented");
 	    ret = KRB5KRB_ERR_GENERIC;
 	} else {
-	    rep.element = choice_PA_PK_AS_REP_encKeyPack;
 	    ContentInfo info;
 
+	    rep.element = choice_PA_PK_AS_REP_encKeyPack;
 	    krb5_generate_random_keyblock(context, enctype, 
 					  &client_params->reply_key);
 	    ret = pk_mk_pa_reply_enckey(context,
@@ -1378,7 +1379,7 @@
 
 static int
 pk_principal_from_X509(krb5_context context, 
-		       struct krb5_pk_cert *client_cert, 
+		       X509 *client_cert, 
 		       krb5_principal *principal)
 {
     krb5_error_code ret;
@@ -1391,7 +1392,7 @@
 
     obj = OBJ_txt2obj("1.3.6.1.5.2.2",1);
 	
-    gens = X509_get_ext_d2i(client_cert->cert, NID_subject_alt_name, 
+    gens = X509_get_ext_d2i(client_cert, NID_subject_alt_name, 
 			    NULL, NULL);
     if (gens == NULL)
 	return 1;
@@ -1434,6 +1435,113 @@
 
 /* XXX match with issuer too ? */
 
+static krb5_error_code
+decode_utctime(const ASN1_UTCTIME *utc, time_t *time)
+{
+   struct tm tm;
+   int i = utc->length;
+
+   if ((i < 11) || (i > 17)) {
+      return -1; /* XXX */
+   }
+
+   tm.tm_isdst = 0;
+   tm.tm_year = (utc->data[0]-'0')*10+(utc->data[1]-'0');
+   if (tm.tm_year < 70)
+      tm.tm_year+=100;
+
+   tm.tm_mon	= (utc->data[2]-'0') * 10 + (utc->data[3]-'0') - 1;
+   tm.tm_mday	= (utc->data[4]-'0') * 10 + (utc->data[5]-'0');
+   tm.tm_hour	= (utc->data[6]-'0') * 10 + (utc->data[7]-'0');
+   tm.tm_min	= (utc->data[8]-'0') * 10 + (utc->data[9]-'0');
+   tm.tm_sec	= (utc->data[10]-'0') * 10 + (utc->data[11]-'0');
+
+   tzset();
+
+   *time = mktime(&tm) - timezone;
+   return 0;
+}
+
+/* This call is inspired by the Globus code */
+static krb5_boolean
+is_proxy(X509 *cert)
+{
+   BASIC_CONSTRAINTS *x509v3_bc = NULL;
+   X509_NAME *subject = NULL;
+   X509_NAME_ENTRY *ne = NULL;
+   krb5_boolean result;
+   int index, critical, pci_oid;
+
+#if GLOBUS
+   globus_result_t res;
+   globus_gsi_cert_utils_cert_type_t cert_type;
+
+   res = globus_gsi_cert_utils_get_cert_type(cert, &cert_type);
+   if (res != GLOBUS_SUCCESS)
+      return FALSE;
+
+   return GLOBUS_GSI_CERT_UTILS_IS_PROXY(cert_type) &&
+          GLOBUS_GSI_CERT_UTILS_IS_IMPERSONATION_PROXY(cert_type);
+#endif
+
+   result = FALSE;
+
+   OBJ_create("1.3.6.1.5.5.7.1.14","PROXYCERTINFO","Proxy Certificate Info Extension");
+   pci_oid  = OBJ_txt2nid("PROXYCERTINFO");
+
+   x509v3_bc = X509_get_ext_d2i(cert, NID_basic_constraints, &critical, &index);
+   if (x509v3_bc && x509v3_bc->ca)
+      /* CA certificates aren't proxies */
+      goto end;
+
+   subject = X509_get_subject_name(cert);
+
+   /* Proxies must end with a CN field, so let's look at the tail */
+   ne = X509_NAME_get_entry(subject, X509_NAME_entry_count(subject) - 1);
+   if (ne == NULL)
+      goto end;
+
+   if (OBJ_cmp(ne->object, OBJ_nid2obj(NID_commonName)) != 0)
+      goto end;
+
+   index = X509_get_ext_by_NID(cert, pci_oid, -1);
+   if (index == -1)
+      goto end;
+
+   /* XXX I'm affraid it's not sufficient to find the correct extension to be
+    * sure that the cert is a proxy. We should also decode and check the
+    * policy embedded in the extension. Anyway, let's go on for now.*/
+
+   /* Should we also check that the proxy was signed by the same user? I think
+    * this step can be omitted since the chain already passed the verification
+    * procedure, which should check this. */
+
+   result = TRUE;
+
+end:
+   if (x509v3_bc)
+      BASIC_CONSTRAINTS_free(x509v3_bc);
+
+   return result;
+}
+
+static krb5_error_code
+get_base_cert(krb5_context context,
+      	     pk_client_params *client_params,
+	     X509 **base_cert)
+{
+   int depth;
+   STACK_OF(X509) *chain = client_params->certificate->chain;
+
+   for (depth = 0; depth < sk_X509_num(chain); depth++) {
+      if (is_proxy(sk_X509_value(chain, depth)) == FALSE)
+	 break;
+   }
+
+   *base_cert = sk_X509_value(chain, depth);
+   return 0;
+}
+
 krb5_error_code
 pk_check_client(krb5_context context,
                 krb5_principal client_princ,
@@ -1444,14 +1552,28 @@
     struct krb5_pk_cert *client_cert = client_params->certificate;
     krb5_principal cert_princ;
     X509_NAME *name;
+    X509 *base_cert;
     char *subject = NULL;
     krb5_error_code ret;
     krb5_boolean b;
     int i;
+    time_t notAfter;
+    krb5_boolean found = FALSE;
 
     *subject_name = NULL;
 
-    name = X509_get_subject_name(client_cert->cert);
+    base_cert = client_cert->cert;
+    if (context->enable_proxy_certificates) {
+       /* The proxy certificates concept (RFC3820) allows users to issue a
+	* brand-new certificates and enlarge their current certificate chain
+	* with new certs (proxies). Here we're trying to find the original
+	* certificate issued to the user by a CA */ 
+       ret = get_base_cert(context, client_params, &base_cert);
+       if (ret)
+	  return ret;
+    }
+
+    name = X509_get_subject_name(base_cert);
     if (name == NULL) {
 	krb5_set_error_string(context, "PKINIT can't get subject name");
 	return ENOMEM;
@@ -1469,7 +1591,7 @@
     OPENSSL_free(subject);
 
     if (enable_pkinit_princ_in_cert) {
-	ret = pk_principal_from_X509(context, client_cert, &cert_princ);
+	ret = pk_principal_from_X509(context, base_cert, &cert_princ);
 	if (ret == 0) {
 	    b = krb5_principal_compare(context, client_princ, cert_princ);
 	    krb5_free_principal(context, cert_princ);
@@ -1486,12 +1608,24 @@
 	    continue;
 	if (strcmp(principal_mappings.val[i].subject, *subject_name) != 0)
 	    continue;
-	return 0;
+	found = TRUE;
+	break;
     }
-    free(*subject_name);
-    *subject_name = NULL;
-    krb5_set_error_string(context, "PKINIT no matching principals");
-    return KRB5_KDC_ERROR_CLIENT_NAME_MISMATCH;
+
+    if (found == FALSE) {
+       free(*subject_name);
+       *subject_name = NULL;
+       krb5_set_error_string(context, "PKINIT no matching principals");
+       return KRB5_KDC_ERROR_CLIENT_NAME_MISMATCH;
+    }
+
+    /* ensure that we won't issue a ticket that would be valid longer than the
+     * user's certificate */
+    decode_utctime(X509_get_notAfter(client_cert->cert), &notAfter);
+    fix_time(&client->max_life);
+    *client->max_life = min(notAfter - kdc_time, *client->max_life);
+
+    return 0;
 }
 
 static krb5_error_code
@@ -1533,8 +1667,22 @@
     unsigned long lineno = 0;
     FILE *f;
 
-    principal_mappings.len = 0;
-    principal_mappings.val = NULL;
+    if (kdc_identity) {
+       _krb5_pk_free_openssl_id(context, kdc_identity);
+       kdc_identity = NULL;
+    }
+
+    if (principal_mappings.len > 0) {
+       int i;
+
+       for (i = 0; i < principal_mappings.len; i++) {
+	  krb5_free_principal(context, principal_mappings.val[i].principal);
+	  free(principal_mappings.val[i].subject);
+       }
+       free (principal_mappings.val);
+       principal_mappings.len = 0;
+       principal_mappings.val = NULL;
+    }
 
     ret = _krb5_pk_load_openssl_id(context,
 				   &kdc_identity,