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