/* Provenance: openssl/openssl ossl_cmp_msg_check_update (fixed). * repo: openssl/openssl * commit: a7e5a6eea8fd3ccca6b6fbba031a5fbf8a3d93b4 * parent: 1e8c398db67404babd3e5af999bb6bd86f720c76 * commit_url: https://github.com/openssl/openssl/commit/a7e5a6eea8fd3ccca6b6fbba031a5fbf8a3d93b4 * cve: CVE-2026-63073 * license: Apache-2.0 * function: ossl_cmp_msg_check_update * relpath: crypto/cmp/cmp_vfy.c */ int ossl_cmp_msg_check_update(OSSL_CMP_CTX *ctx, const OSSL_CMP_MSG *msg, ossl_cmp_allow_unprotected_cb_t cb, int cb_arg) { OSSL_CMP_PKIHEADER *hdr; const X509_NAME *expected_sender; int num_untrusted, num_added, res; if (!ossl_assert(ctx != NULL && msg != NULL && msg->header != NULL)) return 0; hdr = OSSL_CMP_MSG_get0_header(msg); /* If expected_sender is given, validate sender name of received msg */ expected_sender = ctx->expected_sender; if (expected_sender == NULL && ctx->srvCert != NULL) expected_sender = X509_get_subject_name(ctx->srvCert); if (expected_sender != NULL) { const X509_NAME *actual_sender; char *str; if (hdr->sender->type != GEN_DIRNAME) { ERR_raise(ERR_LIB_CMP, CMP_R_SENDER_GENERALNAME_TYPE_NOT_SUPPORTED); return 0; } actual_sender = hdr->sender->d.directoryName; /* * Compare actual sender name of response with expected sender name. * Mitigates risk of accepting misused PBM secret or * misused certificate of an unauthorized entity of a trusted hierarchy. */ if (!check_name(ctx, 0, "sender DN field", actual_sender, "expected sender", expected_sender)) { str = X509_NAME_oneline(actual_sender, NULL, 0); ERR_raise_data(ERR_LIB_CMP, CMP_R_UNEXPECTED_SENDER, "%s", str != NULL ? str : ""); OPENSSL_free(str); return 0; } } /* Note: if recipient was NULL-DN it could be learned here if needed */ num_added = sk_X509_num(msg->extraCerts); if (num_added > 10) ossl_cmp_log1(WARN, ctx, "received CMP message contains %d extraCerts", num_added); /* * Store any provided extraCerts in ctx for use in OSSL_CMP_validate_msg() * and for future use, such that they are available to ctx->certConf_cb and * the peer does not need to send them again in the same transaction. * Note that it does not help validating the message before storing the * extraCerts because they do not belong to the protected msg part anyway. * The extraCerts are prepended. Allows simple removal if they shall not be * cached. Also they get used first, which is likely good for efficiency. */ num_untrusted = ctx->untrusted == NULL ? 0 : sk_X509_num(ctx->untrusted); res = ossl_x509_add_certs_new(&ctx->untrusted, msg->extraCerts, /* this allows self-signed certs */ X509_ADD_FLAG_UP_REF | X509_ADD_FLAG_NO_DUP | X509_ADD_FLAG_PREPEND); num_added = (ctx->untrusted == NULL ? 0 : sk_X509_num(ctx->untrusted)) - num_untrusted; if (!res) { while (num_added-- > 0) X509_free(sk_X509_shift(ctx->untrusted)); return 0; } if (hdr->protectionAlg != NULL) res = OSSL_CMP_validate_msg(ctx, msg) /* explicitly permitted exceptions for invalid protection: */ || (cb != NULL && (*cb)(ctx, msg, 1, cb_arg) > 0); else /* explicitly permitted exceptions for missing protection: */ res = cb != NULL && (*cb)(ctx, msg, 0, cb_arg) > 0; #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION res = 1; /* support more aggressive fuzzing by letting invalid msg pass */ #endif /* * remove extraCerts again if not caching * or if we failed validation above, lest a remote user * starts sending us lots of certificates in invalid messages * leading to a DOS from unbounded certificate stack growth */ if (ctx->noCacheExtraCerts || res != 1) while (num_added-- > 0) X509_free(sk_X509_shift(ctx->untrusted)); if (!res) { if (hdr->protectionAlg != NULL) ERR_raise(ERR_LIB_CMP, CMP_R_ERROR_VALIDATING_PROTECTION); else ERR_raise(ERR_LIB_CMP, CMP_R_MISSING_PROTECTION); return 0; } /* check CMP version number in header */ if (ossl_cmp_hdr_get_pvno(hdr) != OSSL_CMP_PVNO_2 && ossl_cmp_hdr_get_pvno(hdr) != OSSL_CMP_PVNO_3) { #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION ERR_raise(ERR_LIB_CMP, CMP_R_UNEXPECTED_PVNO); return 0; #endif } if (OSSL_CMP_MSG_get_bodytype(msg) < 0) { #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION ERR_raise(ERR_LIB_CMP, CMP_R_PKIBODY_ERROR); return 0; #endif } /* compare received transactionID with the expected one in previous msg */ if (!check_transactionID_or_nonce(ctx->transactionID, hdr->transactionID, CMP_R_TRANSACTIONID_UNMATCHED)) return 0; /* * enable clearing irrelevant errors * in attempts to validate recipient nonce in case of delayed delivery. */ (void)ERR_set_mark(); /* compare received nonce with the one we sent */ if (!check_transactionID_or_nonce(ctx->senderNonce, hdr->recipNonce, CMP_R_RECIPNONCE_UNMATCHED)) { /* check if we are polling and received final response */ if (ctx->first_senderNonce == NULL || OSSL_CMP_MSG_get_bodytype(msg) == OSSL_CMP_PKIBODY_POLLREP /* compare received nonce with our sender nonce at poll start */ || !check_transactionID_or_nonce(ctx->first_senderNonce, hdr->recipNonce, CMP_R_RECIPNONCE_UNMATCHED)) { (void)ERR_clear_last_mark(); return 0; } } (void)ERR_pop_to_mark(); /* if not yet present, learn transactionID */ if (ctx->transactionID == NULL && !OSSL_CMP_CTX_set1_transactionID(ctx, hdr->transactionID)) return 0; /* * RFC 4210 section 5.1.1 states: the recipNonce is copied from * the senderNonce of the previous message in the transaction. * --> Store for setting in next message */ if (!ossl_cmp_ctx_set1_recipNonce(ctx, hdr->senderNonce)) return 0; if (ossl_cmp_hdr_get_protection_nid(hdr) == NID_id_PasswordBasedMAC) { /* * RFC 4210, 5.3.2: 'Note that if the PKI Message Protection is * "shared secret information", then any certificate transported in * the caPubs field may be directly trusted as a root CA * certificate by the initiator.' */ switch (OSSL_CMP_MSG_get_bodytype(msg)) { case OSSL_CMP_PKIBODY_IP: case OSSL_CMP_PKIBODY_CP: case OSSL_CMP_PKIBODY_KUP: case OSSL_CMP_PKIBODY_CCP: if (ctx->trusted != NULL) { STACK_OF(X509) *certs = msg->body->value.ip->caPubs; /* value.ip is same for cp, kup, and ccp */ if (!ossl_cmp_X509_STORE_add1_certs(ctx->trusted, certs, 0)) /* adds both self-issued and not self-issued certs */ return 0; } break; default: break; } } return 1; }