1 /*
2  * COPYRIGHT (C) 2007
3  * THE REGENTS OF THE UNIVERSITY OF MICHIGAN
4  * ALL RIGHTS RESERVED
5  *
6  * Permission is granted to use, copy, create derivative works
7  * and redistribute this software and such derivative works
8  * for any purpose, so long as the name of The University of
9  * Michigan is not used in any advertising or publicity
10  * pertaining to the use of distribution of this software
11  * without specific, written prior authorization.  If the
12  * above copyright notice or any other identification of the
13  * University of Michigan is included in any copy of any
14  * portion of this software, then the disclaimer below must
15  * also be included.
16  *
17  * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
18  * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
19  * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
20  * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
21  * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
22  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
23  * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
24  * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
25  * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
26  * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
27  * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGES.
29  */
30 
31 #include <errno.h>
32 #include <string.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <regex.h>
37 #include <krb5.h>
38 #include "pkinit.h"
39 
40 typedef struct _pkinit_cert_info pkinit_cert_info;
41 
42 typedef enum {
43     kw_undefined = 0,
44     kw_subject = 1,
45     kw_issuer = 2,
46     kw_san = 3,
47     kw_eku = 4,
48     kw_ku = 5
49 } keyword_type;
50 
51 static char *
52 keyword2string(unsigned int kw)
53 {
54     /* Solaris Kerberos: removed "break"s (lint) */
55     switch(kw) {
56     case kw_undefined: return "NONE";
57     case kw_subject: return "SUBJECT";
58     case kw_issuer: return "ISSUER";
59     case kw_san: return "SAN";
60     case kw_eku: return "EKU";
61     case kw_ku: return "KU";
62     default: return "INVALID";
63     }
64 }
65 typedef enum {
66     relation_none = 0,
67     relation_and = 1,
68     relation_or = 2
69 } relation_type;
70 
71 static char *
72 relation2string(unsigned int rel)
73 {
74     /* Solaris Kerberos: removed "break"s (lint) */
75     switch(rel) {
76     case relation_none: return "NONE";
77     case relation_and: return "AND";
78     case relation_or: return "OR";
79     default: return "INVALID";
80     }
81 }
82 
83 typedef enum {
84     kwvaltype_undefined = 0,
85     kwvaltype_regexp = 1,
86     kwvaltype_list = 2
87 } kw_value_type;
88 
89 static char *
90 kwval2string(unsigned int kwval)
91 {
92     /* Solaris Kerberos: removed "break"s (lint) */
93     switch(kwval) {
94     case kwvaltype_undefined: return "NONE";
95     case kwvaltype_regexp: return "REGEXP";
96     case kwvaltype_list: return "LIST";
97     default: return "INVALID";
98     }
99 }
100 
101 struct keyword_desc {
102     const char *value;
103     size_t length;
104     keyword_type kwtype;
105     kw_value_type kwvaltype;
106 } matching_keywords[] = {
107     { "<KU>",	    4, kw_ku, kwvaltype_list },
108     { "<EKU>",	    5, kw_eku, kwvaltype_list },
109     { "<SAN>",	    5, kw_san, kwvaltype_regexp },
110     { "<ISSUER>",   8, kw_issuer, kwvaltype_regexp },
111     { "<SUBJECT>",  9, kw_subject, kwvaltype_regexp },
112     { NULL, 0, kw_undefined, kwvaltype_undefined},
113 };
114 
115 struct ku_desc {
116     const char *value;
117     size_t length;
118     unsigned int bitval;
119 };
120 
121 struct ku_desc ku_keywords[] = {
122     { "digitalSignature",   16, PKINIT_KU_DIGITALSIGNATURE },
123     { "keyEncipherment",    15, PKINIT_KU_KEYENCIPHERMENT },
124     { NULL, 0, 0 },
125 };
126 
127 struct ku_desc  eku_keywords[] = {
128     { "pkinit",		    6,  PKINIT_EKU_PKINIT },
129     { "msScLogin",	    9,  PKINIT_EKU_MSSCLOGIN },
130     { "clientAuth",	    10, PKINIT_EKU_CLIENTAUTH },
131     { "emailProtection",    15, PKINIT_EKU_EMAILPROTECTION },
132     { NULL, 0, 0 },
133 };
134 
135 /* Rule component */
136 typedef struct _rule_component {
137     struct _rule_component *next;
138     keyword_type kw_type;
139     kw_value_type kwval_type;
140     regex_t regexp;	    /* Compiled regular expression */
141     char *regsrc;	    /* The regular expression source (for debugging) */
142     unsigned int ku_bits;
143     unsigned int eku_bits;
144 } rule_component;
145 
146 /* Set rule components */
147 typedef struct _rule_set {
148     relation_type relation;
149     int num_crs;
150     rule_component *crs;
151 } rule_set;
152 
153 /* ARGSUSED */
154 static krb5_error_code
155 free_rule_component(krb5_context context,
156 		    rule_component *rc)
157 {
158     if (rc == NULL)
159 	return 0;
160 
161     if (rc->kwval_type == kwvaltype_regexp) {
162 	if (rc->regsrc)
163 	    free(rc->regsrc);
164 	regfree(&rc->regexp);
165     }
166     free(rc);
167     return 0;
168 }
169 
170 static krb5_error_code
171 free_rule_set(krb5_context context,
172 	      rule_set *rs)
173 {
174     rule_component *rc, *trc;
175 
176     if (rs == NULL)
177 	return 0;
178     for (rc = rs->crs; rc != NULL;) {
179 	trc = rc->next;
180 	/* Solaris Kerberos */
181 	(void) free_rule_component(context, rc);
182 	rc = trc;
183     }
184     free(rs);
185     return 0;
186 }
187 
188 /* ARGSUSED */
189 static krb5_error_code
190 parse_list_value(krb5_context context,
191 		 keyword_type type,
192 		 char *value,
193 		 rule_component *rc)
194 {
195     krb5_error_code retval;
196     char *comma;
197     struct ku_desc *ku = NULL;
198     int found;
199     size_t len;
200     unsigned int *bitptr;
201 
202 
203     if (value == NULL || value[0] == '\0') {
204 	pkiDebug("%s: Missing or empty value for list keyword type %d\n",
205 		 __FUNCTION__, type);
206 	retval = EINVAL;
207 	goto out;
208     }
209 
210     if (type == kw_eku) {
211 	bitptr = &rc->eku_bits;
212     } else if (type == kw_ku) {
213 	bitptr = &rc->ku_bits;
214     } else {
215 	pkiDebug("%s: Unknown list keyword type %d\n", __FUNCTION__, type);
216 	retval = EINVAL;
217 	goto out;
218     }
219 
220     do {
221 	found = 0;
222 	comma = strchr(value, ',');
223 	if (comma != NULL)
224 	    len = comma - value;
225 	else
226 	    len = strlen(value);
227 
228 	if (type == kw_eku) {
229 	    ku = eku_keywords;
230 	} else if (type == kw_ku) {
231 	    ku = ku_keywords;
232 	}
233 
234 	for (; ku->value != NULL; ku++) {
235 	    if (strncasecmp(value, ku->value, len) == 0) {
236 		*bitptr |= ku->bitval;
237 		found = 1;
238 		pkiDebug("%s: Found value '%s', bitfield is now 0x%x\n",
239 			 __FUNCTION__, ku->value, *bitptr);
240 		break;
241 	    }
242 	}
243 	if (found) {
244 	    value += ku->length;
245 	    if (*value == ',')
246 		value += 1;
247 	} else {
248 	    pkiDebug("%s: Urecognized value '%s'\n", __FUNCTION__, value);
249 	    retval = EINVAL;
250 	    goto out;
251 	}
252     } while (found && *value != '\0');
253 
254     retval = 0;
255 out:
256     pkiDebug("%s: returning %d\n", __FUNCTION__, retval);
257     return retval;
258 }
259 
260 static krb5_error_code
261 parse_rule_component(krb5_context context,
262 		     const char **rule,
263 		     int *remaining,
264 		     rule_component **ret_rule)
265 {
266     krb5_error_code retval;
267     rule_component *rc = NULL;
268     keyword_type kw_type;
269     kw_value_type kwval_type;
270     char err_buf[128];
271     int ret;
272     struct keyword_desc *kw, *nextkw;
273     char *nk;
274     int found_next_kw = 0;
275     char *value = NULL;
276     size_t len;
277 
278     for (kw = matching_keywords; kw->value != NULL; kw++) {
279 	if (strncmp(*rule, kw->value, kw->length) == 0) {
280 	    kw_type = kw->kwtype;
281 	    kwval_type = kw->kwvaltype;
282 	    *rule += kw->length;
283 	    *remaining -= kw->length;
284 	    break;
285 	}
286     }
287     if (kw->value == NULL) {
288 	pkiDebug("%s: Missing or invalid keyword in rule '%s'\n",
289 		 __FUNCTION__, *rule);
290 	retval = ENOENT;
291 	goto out;
292     }
293 
294     pkiDebug("%s: found keyword '%s'\n", __FUNCTION__, kw->value);
295 
296     rc = calloc(1, sizeof(*rc));
297     if (rc == NULL) {
298 	retval = ENOMEM;
299 	goto out;
300     }
301     rc->next = NULL;
302     rc->kw_type = kw_type;
303     rc->kwval_type = kwval_type;
304 
305     /*
306      * Before procesing the value for this keyword,
307      * (compiling the regular expression or processing the list)
308      * we need to find the end of it.  That means parsing for the
309      * beginning of the next keyword (or the end of the rule).
310      */
311     nk = strchr(*rule, '<');
312     while (nk != NULL) {
313 	/* Possibly another keyword, check it out */
314 	for (nextkw = matching_keywords; nextkw->value != NULL; nextkw++) {
315 	    if (strncmp(nk, nextkw->value, nextkw->length) == 0) {
316 		/* Found a keyword, nk points to the beginning */
317 		found_next_kw = 1;
318 		break;	/* Need to break out of the while! */
319 	    }
320 	}
321 	if (!found_next_kw)
322 	    nk = strchr(nk+1, '<');	/* keep looking */
323 	else
324 	    break;
325     }
326 
327     if (nk != NULL && found_next_kw)
328 	len = (nk - *rule);
329     else
330 	len = (*remaining);
331 
332     if (len == 0) {
333 	pkiDebug("%s: Missing value for keyword '%s'\n",
334 		 __FUNCTION__, kw->value);
335 	retval = EINVAL;
336 	goto out;
337     }
338 
339     value = calloc(1, len+1);
340     if (value == NULL) {
341 	retval = ENOMEM;
342 	goto out;
343     }
344     (void) memcpy(value, *rule, len);
345     *remaining -= len;
346     *rule += len;
347     pkiDebug("%s: found value '%s'\n", __FUNCTION__, value);
348 
349     if (kw->kwvaltype == kwvaltype_regexp) {
350 	ret = regcomp(&rc->regexp, value, REG_EXTENDED);
351 	if (ret) {
352 	    (void) regerror(ret, &rc->regexp, err_buf, sizeof(err_buf));
353 	    pkiDebug("%s: Error compiling reg-exp '%s': %s\n",
354 		     __FUNCTION__, value, err_buf);
355 	    retval = ret;
356 	    goto out;
357 	}
358 	rc->regsrc = strdup(value);
359 	if (rc->regsrc == NULL) {
360 	    retval = ENOMEM;
361 	    goto out;
362 	}
363     } else if (kw->kwvaltype == kwvaltype_list) {
364 	retval = parse_list_value(context, rc->kw_type, value, rc);
365 	if (retval) {
366 	    pkiDebug("%s: Error %d, parsing list values for keyword %s\n",
367 		     __FUNCTION__, retval, kw->value);
368 	    goto out;
369 	}
370     }
371 
372     *ret_rule = rc;
373     retval = 0;
374 out:
375     if (value != NULL)
376 	free(value);
377     if (retval && rc != NULL)
378 	(void) free_rule_component(context, rc);
379     pkiDebug("%s: returning %d\n", __FUNCTION__, retval);
380     return retval;
381 }
382 
383 /* ARGSUSED */
384 static krb5_error_code
385 parse_rule_set(krb5_context context,
386 	       const char *rule_in,
387 	       rule_set **out_rs)
388 {
389     const char *rule;
390     /* Solaris Kerberos */
391     int remaining;
392     krb5_error_code ret, retval;
393     rule_component *rc = NULL, *trc;
394     rule_set *rs;
395 
396 
397     if (rule_in == NULL)
398 	return EINVAL;
399     rule = rule_in;
400     /* Solaris Kerberos */
401     remaining = strlen(rule);
402 
403     rs = calloc(1, sizeof(*rs));
404     if (rs == NULL) {
405 	retval = ENOMEM;
406 	goto cleanup;
407     }
408 
409     rs->relation = relation_none;
410     if (remaining > 1) {
411 	if (rule[0] == '&' && rule[1] == '&') {
412 	    rs->relation = relation_and;
413 	    rule += 2;
414 	    remaining -= 2;
415 	} else if (rule_in[0] == '|' && rule_in[1] == '|') {
416 	    rs->relation = relation_or;
417 	    rule +=2;
418 	    remaining -= 2;
419 	}
420     }
421     rs->num_crs = 0;
422     while (remaining > 0) {
423 	if (rs->relation == relation_none && rs->num_crs > 1) {
424 	    pkiDebug("%s: Assuming AND relation for multiple components in rule '%s'\n",
425 		     __FUNCTION__, rule_in);
426 	    rs->relation = relation_and;
427 	}
428 	ret = parse_rule_component(context, &rule, &remaining, &rc);
429 	if (ret) {
430 	    retval = ret;
431 	    goto cleanup;
432 	}
433 	pkiDebug("%s: After parse_rule_component, remaining %d, rule '%s'\n",
434 		 __FUNCTION__, remaining, rule);
435 	rs->num_crs++;
436 
437 	/*
438 	 * Chain the new component on the end (order matters since
439 	 * we can short-circuit an OR or an AND relation if an
440 	 * earlier check passes
441 	 */
442 	for (trc = rs->crs; trc != NULL && trc->next != NULL; trc = trc->next);
443 	if (trc == NULL)
444 	    rs->crs = rc;
445 	else {
446 	    trc->next = rc;
447 	}
448     }
449 
450     *out_rs = rs;
451 
452     retval = 0;
453 cleanup:
454     if (retval && rs != NULL) {
455 	(void) free_rule_set(context, rs);
456     }
457     pkiDebug("%s: returning %d\n", __FUNCTION__, retval);
458     return retval;
459 }
460 
461 /* ARGSUSED */
462 static int
463 regexp_match(krb5_context context, rule_component *rc, char *value)
464 {
465     int code;
466 
467     pkiDebug("%s: checking %s rule '%s' with value '%s'\n",
468 	     __FUNCTION__, keyword2string(rc->kw_type), rc->regsrc, value);
469 
470     code = regexec(&rc->regexp, value, 0, NULL, 0);
471 
472     pkiDebug("%s: the result is%s a match\n", __FUNCTION__,
473 	     code == REG_NOMATCH ? " NOT" : "");
474 
475     return (code == 0 ? 1: 0);
476 }
477 
478 static int
479 component_match(krb5_context context,
480 		rule_component *rc,
481 		pkinit_cert_matching_data *md)
482 {
483     int match = 0;
484     int i;
485     krb5_principal p;
486     char *princ_string;
487 
488     switch (rc->kwval_type) {
489     case kwvaltype_regexp:
490 	switch (rc->kw_type) {
491 	case kw_subject:
492 	    match = regexp_match(context, rc, md->subject_dn);
493 	    break;
494 	case kw_issuer:
495 	    match = regexp_match(context, rc, md->issuer_dn);
496 	    break;
497 	case kw_san:
498 	    if (md->sans == NULL)
499 		break;
500 	    for (i = 0, p = md->sans[i]; p != NULL; p = md->sans[++i]) {
501 		krb5_unparse_name(context, p, &princ_string);
502 		match = regexp_match(context, rc, princ_string);
503 		krb5_free_unparsed_name(context, princ_string);
504 		if (match)
505 		    break;
506 	    }
507 	    break;
508 	default:
509 	    pkiDebug("%s: keyword %s, keyword value %s mismatch\n",
510 		     __FUNCTION__, keyword2string(rc->kw_type),
511 		     kwval2string(kwvaltype_regexp));
512 	    break;
513 	}
514 	break;
515     case kwvaltype_list:
516 	switch(rc->kw_type) {
517 	case kw_eku:
518 	    pkiDebug("%s: checking %s: rule 0x%08x, cert 0x%08x\n",
519 		     __FUNCTION__, keyword2string(rc->kw_type),
520 		     rc->eku_bits, md->eku_bits);
521 	    if ((rc->eku_bits & md->eku_bits) == rc->eku_bits)
522 		match = 1;
523 	    break;
524 	case kw_ku:
525 	    pkiDebug("%s: checking %s: rule 0x%08x, cert 0x%08x\n",
526 		     __FUNCTION__, keyword2string(rc->kw_type),
527 		     rc->ku_bits, md->ku_bits);
528 	    if ((rc->ku_bits & md->ku_bits) == rc->ku_bits)
529 		match = 1;
530 	    break;
531 	default:
532 	    pkiDebug("%s: keyword %s, keyword value %s mismatch\n",
533 		     __FUNCTION__, keyword2string(rc->kw_type),
534 		     kwval2string(kwvaltype_regexp));
535 	    break;
536 	}
537 	break;
538     default:
539 	pkiDebug("%s: unknown keyword value type %d\n",
540 		 __FUNCTION__, rc->kwval_type);
541 	break;
542     }
543     pkiDebug("%s: returning match = %d\n", __FUNCTION__, match);
544     return match;
545 }
546 /*
547  * Returns match_found == 1 only if exactly one certificate matches
548  * the given rule
549  */
550 /* ARGSUSED */
551 static krb5_error_code
552 check_all_certs(krb5_context context,
553 		pkinit_plg_crypto_context plg_cryptoctx,
554 		pkinit_req_crypto_context req_cryptoctx,
555 		pkinit_identity_crypto_context id_cryptoctx,
556 		krb5_principal princ,
557 		rule_set *rs,	/* rule to check */
558 		pkinit_cert_matching_data **matchdata,
559 		int *match_found,
560 		pkinit_cert_matching_data **matching_cert)
561 {
562     krb5_error_code retval;
563     pkinit_cert_matching_data *md;
564     int i;
565     int comp_match = 0;
566     int total_cert_matches = 0;
567     rule_component *rc;
568     int certs_checked = 0;
569     pkinit_cert_matching_data *save_match = NULL;
570 
571     if (match_found == NULL || matching_cert == NULL)
572 	return EINVAL;
573 
574     *matching_cert = NULL;
575     *match_found = 0;
576 
577     pkiDebug("%s: matching rule relation is %s with %d components\n",
578 	     __FUNCTION__, relation2string(rs->relation), rs->num_crs);
579 
580     /*
581      * Loop through all the certs available and count
582      * how many match the rule
583      */
584     for (i = 0, md = matchdata[i]; md != NULL; md = matchdata[++i]) {
585 	pkiDebug("%s: subject: '%s'\n", __FUNCTION__, md->subject_dn);
586 #if 0
587 	pkiDebug("%s: issuer:  '%s'\n", __FUNCTION__, md->subject_dn);
588 	for (j = 0, p = md->sans[j]; p != NULL; p = md->sans[++j]) {
589 	    char *san_string;
590 	    krb5_unparse_name(context, p, &san_string);
591 	    pkiDebug("%s: san: '%s'\n", __FUNCTION__, san_string);
592 	    krb5_free_unparsed_name(context, san_string);
593 	}
594 #endif
595 	certs_checked++;
596 	for (rc = rs->crs; rc != NULL; rc = rc->next) {
597 	    comp_match = component_match(context, rc, md);
598 	    if (comp_match) {
599 		pkiDebug("%s: match for keyword type %s\n",
600 			 __FUNCTION__, keyword2string(rc->kw_type));
601 	    }
602 	    if (comp_match && rs->relation == relation_or) {
603 		pkiDebug("%s: cert matches rule (OR relation)\n",
604 			 __FUNCTION__);
605 		total_cert_matches++;
606 		save_match = md;
607 		goto nextcert;
608 	    }
609 	    if (!comp_match && rs->relation == relation_and) {
610 		pkiDebug("%s: cert does not match rule (AND relation)\n",
611 			 __FUNCTION__);
612 		goto nextcert;
613 	    }
614 	}
615 	if (rc == NULL && comp_match) {
616 	    pkiDebug("%s: cert matches rule (AND relation)\n", __FUNCTION__);
617 	    total_cert_matches++;
618 	    save_match = md;
619 	}
620 nextcert:
621 	continue;
622     }
623     pkiDebug("%s: After checking %d certs, we found %d matches\n",
624 	     __FUNCTION__, certs_checked, total_cert_matches);
625     if (total_cert_matches == 1) {
626 	*match_found = 1;
627 	*matching_cert = save_match;
628     }
629 
630     retval = 0;
631 
632     pkiDebug("%s: returning %d, match_found %d\n",
633 	     __FUNCTION__, retval, *match_found);
634     return retval;
635 }
636 
637 static krb5_error_code
638 free_all_cert_matching_data(krb5_context context,
639 			    pkinit_cert_matching_data **matchdata)
640 {
641     krb5_error_code retval;
642     pkinit_cert_matching_data *md;
643     int i;
644 
645     if (matchdata == NULL)
646 	return EINVAL;
647 
648     for (i = 0, md = matchdata[i]; md != NULL; md = matchdata[++i]) {
649 	pkinit_cert_handle ch = md->ch;
650 	retval = crypto_cert_free_matching_data(context, md);
651 	if (retval) {
652 	    pkiDebug("%s: crypto_cert_free_matching_data error %d, %s\n",
653 		     __FUNCTION__, retval, error_message(retval));
654 	    goto cleanup;
655 	}
656 	retval = crypto_cert_release(context, ch);
657 	if (retval) {
658 	    pkiDebug("%s: crypto_cert_release error %d, %s\n",
659 		     __FUNCTION__, retval, error_message(retval));
660 	    goto cleanup;
661 	}
662     }
663     free(matchdata);
664     retval = 0;
665 
666 cleanup:
667     return retval;
668 }
669 
670 static krb5_error_code
671 obtain_all_cert_matching_data(krb5_context context,
672 			      pkinit_plg_crypto_context plg_cryptoctx,
673 			      pkinit_req_crypto_context req_cryptoctx,
674 			      pkinit_identity_crypto_context id_cryptoctx,
675 			      pkinit_cert_matching_data ***all_matching_data)
676 {
677     krb5_error_code retval;
678     int i, cert_count;
679     pkinit_cert_iter_handle ih = NULL;
680     pkinit_cert_handle ch;
681     pkinit_cert_matching_data **matchdata = NULL;
682 
683     retval = crypto_cert_get_count(context, plg_cryptoctx, req_cryptoctx,
684 				   id_cryptoctx, &cert_count);
685     if (retval) {
686 	pkiDebug("%s: crypto_cert_get_count error %d, %s\n",
687 		 __FUNCTION__, retval, error_message(retval));
688 	goto cleanup;
689     }
690 
691     pkiDebug("%s: crypto_cert_get_count says there are %d certs\n",
692 	      __FUNCTION__, cert_count);
693 
694     matchdata = calloc((size_t)cert_count + 1, sizeof(*matchdata));
695     if (matchdata == NULL)
696 	return ENOMEM;
697 
698     retval = crypto_cert_iteration_begin(context, plg_cryptoctx, req_cryptoctx,
699 					 id_cryptoctx, &ih);
700     if (retval) {
701 	pkiDebug("%s: crypto_cert_iteration_begin returned %d, %s\n",
702 		 __FUNCTION__, retval, error_message(retval));
703 	goto cleanup;
704     }
705 
706     for (i = 0; i < cert_count; i++) {
707 	retval = crypto_cert_iteration_next(context, ih, &ch);
708 	if (retval) {
709 	    if (retval == PKINIT_ITER_NO_MORE)
710 		pkiDebug("%s: We thought there were %d certs, but "
711 			 "crypto_cert_iteration_next stopped after %d?\n",
712 			 __FUNCTION__, cert_count, i);
713 	    else
714 		pkiDebug("%s: crypto_cert_iteration_next error %d, %s\n",
715 			 __FUNCTION__, retval, error_message(retval));
716 	    goto cleanup;
717 	}
718 
719 	retval = crypto_cert_get_matching_data(context, ch, &matchdata[i]);
720 	if (retval) {
721 	    pkiDebug("%s: crypto_cert_get_matching_data error %d, %s\n",
722 		     __FUNCTION__, retval, error_message(retval));
723 	    goto cleanup;
724 	}
725 
726     }
727 
728     *all_matching_data = matchdata;
729     retval = 0;
730 cleanup:
731     if (ih != NULL)
732 	/* Solaris Kerberos */
733 	(void) crypto_cert_iteration_end(context, ih);
734     if (retval) {
735 	if (matchdata != NULL)
736 	    (void) free_all_cert_matching_data(context, matchdata);
737     }
738     pkiDebug("%s: returning %d, certinfo %p\n",
739 	     __FUNCTION__, retval, *all_matching_data);
740     return retval;
741 }
742 
743 krb5_error_code
744 pkinit_cert_matching(krb5_context context,
745 		     pkinit_plg_crypto_context plg_cryptoctx,
746 		     pkinit_req_crypto_context req_cryptoctx,
747 		     pkinit_identity_crypto_context id_cryptoctx,
748 		     krb5_principal princ)
749 {
750 
751     krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED;
752     int x;
753     char **rules = NULL;
754     rule_set *rs = NULL;
755     int match_found = 0;
756     pkinit_cert_matching_data **matchdata = NULL;
757     pkinit_cert_matching_data *the_matching_cert = NULL;
758 
759     /* If no matching rules, select the default cert and we're done */
760     (void) pkinit_libdefault_strings(context, krb5_princ_realm(context, princ),
761 			      "pkinit_cert_match", &rules);
762     if (rules == NULL) {
763 	pkiDebug("%s: no matching rules found in config file\n", __FUNCTION__);
764 	retval = crypto_cert_select_default(context, plg_cryptoctx,
765 					    req_cryptoctx, id_cryptoctx);
766 	goto cleanup;
767     }
768 
769     /* parse each rule line one at a time and check all the certs against it */
770     for (x = 0; rules[x] != NULL; x++) {
771 	pkiDebug("%s: Processing rule '%s'\n", __FUNCTION__, rules[x]);
772 
773 	/* Free rules from previous time through... */
774 	if (rs != NULL) {
775 	    (void) free_rule_set(context, rs);
776 	    rs = NULL;
777 	}
778 	retval = parse_rule_set(context, rules[x], &rs);
779 	if (retval) {
780 	    if (retval == EINVAL) {
781 		pkiDebug("%s: Ignoring invalid rule pkinit_cert_match = '%s'\n",
782 			 __FUNCTION__, rules[x]);
783 		continue;
784 	    }
785 	    goto cleanup;
786 	}
787 
788 	/*
789 	 * Optimize so that we do not get cert info unless we have
790 	 * valid rules to check.  Once obtained, keep it around
791 	 * until we are done.
792 	 */
793 	if (matchdata == NULL) {
794 	    retval = obtain_all_cert_matching_data(context, plg_cryptoctx,
795 						   req_cryptoctx, id_cryptoctx,
796 						   &matchdata);
797 	    if (retval || matchdata == NULL) {
798 		pkiDebug("%s: Error %d obtaining certificate information\n",
799 			 __FUNCTION__, retval);
800 		retval = ENOENT;
801 		goto cleanup;
802 	    }
803 	}
804 
805 	retval = check_all_certs(context, plg_cryptoctx, req_cryptoctx,
806 				 id_cryptoctx, princ, rs, matchdata,
807 				 &match_found, &the_matching_cert);
808 	if (retval) {
809 	    pkiDebug("%s: Error %d, checking certs against rule '%s'\n",
810 		     __FUNCTION__, retval, rules[x]);
811 	    goto cleanup;
812 	}
813 	if (match_found) {
814 	    pkiDebug("%s: We have an exact match with rule '%s'\n",
815 		     __FUNCTION__, rules[x]);
816 	    break;
817 	}
818     }
819 
820     if (match_found && the_matching_cert != NULL) {
821 	pkiDebug("%s: Selecting the matching cert!\n", __FUNCTION__);
822 	retval = crypto_cert_select(context, the_matching_cert);
823 	if (retval) {
824 	    pkiDebug("%s: crypto_cert_select error %d, %s\n",
825 		     __FUNCTION__, retval, error_message(retval));
826 	    goto cleanup;
827 	}
828     } else {
829 	retval = ENOENT;    /* XXX */
830 	goto cleanup;
831     }
832 
833     retval = 0;
834 cleanup:
835     if (rules != NULL)
836 	profile_free_list(rules);
837     if (rs != NULL)
838 	/* Solaris Kerberos */
839 	(void) free_rule_set(context, rs);
840     if (matchdata != NULL)
841 	(void) free_all_cert_matching_data(context, matchdata);
842     return retval;
843 }
844