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