This is done for error messages received if OSSL_CMP_OPT_NONMATCHED_ERROR_NONCES
is set or the respective -nonmatched_error_nonces CLI option is given.
Can be helpful when the server (or other peer) cannot provide a proper error message header,
for instance if was unable to parse the ASN.1 encoding of a request message.
Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jun 11 14:56:49 2026
(Merged from https://github.com/openssl/openssl/pull/29043)
*Daniel Kubec*
+ * Added `OSSL_CMP_OPT_NONMATCHED_ERROR_NONCES` option for `OSSL_CMP_CTX` and
+ a corresponding `-nonmatched_error_nonces` option for the `openssl cmp` command.
+
+ This work was sponsored by Siemens AG.
+
+ *David von Oheimb*
+
* Added support for RFC 8701 GREASE (Generate Random Extensions And Sustain
Extensibility). When `SSL_OP_GREASE` is set, the TLS client injects
reserved GREASE values into cipher suites, supported versions, supported
static char *opt_expect_sender = NULL;
static int opt_ignore_keyusage = 0;
static int opt_unprotected_errors = 0;
+static int opt_nonmatched_error_nonces = 0;
static int opt_ta_in_ip_extracerts = 0;
static int opt_no_cache_extracerts = 0;
static char *opt_srvcertout = NULL;
OPT_EXPECT_SENDER,
OPT_IGNORE_KEYUSAGE,
OPT_UNPROTECTED_ERRORS,
+ OPT_NONMATCHED_ERROR_NONCES,
OPT_TA_IN_IP_EXTRACERTS,
OPT_NO_CACHE_EXTRACERTS,
OPT_SRVCERTOUT,
"certificate responses (ip/cp/kup), revocation responses (rp), and PKIConf" },
{ OPT_MORE_STR, 0, 0,
"WARNING: This setting leads to behavior allowing violation of RFC 9810" },
+ { "nonmatched_error_nonces", OPT_NONMATCHED_ERROR_NONCES, '-',
+ "Accept missing or non-matching transactionID or recipNonce in error messages" },
{ "ta_in_ip_extracerts", OPT_TA_IN_IP_EXTRACERTS, '-',
"Permit using self-issued certificates from the extraCerts in an IP message" },
{ OPT_MORE_STR, 0, 0,
{ &opt_trusted }, { &opt_untrusted }, { &opt_srvcert },
{ &opt_expect_sender },
{ (char **)&opt_ignore_keyusage }, { (char **)&opt_unprotected_errors },
+ { (char **)&opt_nonmatched_error_nonces },
{ (char **)&opt_ta_in_ip_extracerts },
{ (char **)&opt_no_cache_extracerts },
{ &opt_srvcertout }, { &opt_extracertsout }, { &opt_cacertsout },
if (opt_unprotected_errors)
(void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_UNPROTECTED_ERRORS, 1);
+ if (opt_nonmatched_error_nonces)
+ (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_NONMATCHED_ERROR_NONCES, 1);
if (opt_ta_in_ip_extracerts) {
(void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_PERMIT_TA_IN_EXTRACERTS_FOR_IR, 1);
CMP_warn("permitting non-authenticated trust anchors in IP extracerts according to 3GPP TS 33.310");
case OPT_UNPROTECTED_ERRORS:
opt_unprotected_errors = 1;
break;
+ case OPT_NONMATCHED_ERROR_NONCES:
+ opt_nonmatched_error_nonces = 1;
+ break;
case OPT_TA_IN_IP_EXTRACERTS:
opt_ta_in_ip_extracerts = 1;
break;
case OSSL_CMP_OPT_UNPROTECTED_ERRORS:
ctx->unprotectedErrors = val;
break;
+ case OSSL_CMP_OPT_NONMATCHED_ERROR_NONCES:
+ ctx->nonmatchedErrorNonces = val;
+ break;
case OSSL_CMP_OPT_NO_CACHE_EXTRACERTS:
ctx->noCacheExtraCerts = val;
break;
return ctx->unprotectedSend;
case OSSL_CMP_OPT_UNPROTECTED_ERRORS:
return ctx->unprotectedErrors;
+ case OSSL_CMP_OPT_NONMATCHED_ERROR_NONCES:
+ return ctx->nonmatchedErrorNonces;
case OSSL_CMP_OPT_NO_CACHE_EXTRACERTS:
return ctx->noCacheExtraCerts;
case OSSL_CMP_OPT_VALIDITY_DAYS:
* certificate responses (ip/cp/kup), revocation responses (rp), and PKIConf
*/
int unprotectedErrors;
+ int nonmatchedErrorNonces; /* accept missing/wrong transactionID or recipNonce in error msgs */
int noCacheExtraCerts;
X509 *srvCert; /* certificate used to identify the server */
X509 *validatedSrvCert; /* caches any already validated sender cert */
return 0;
}
-static int check_transactionID_or_nonce(ASN1_OCTET_STRING *expected,
- ASN1_OCTET_STRING *actual, int reason)
+static int check_transactionID_or_nonce(OSSL_CMP_CTX *ctx, const ASN1_OCTET_STRING *expected,
+ const ASN1_OCTET_STRING *actual, int bodytype, int reason)
{
if (expected != NULL
&& (actual == NULL || ASN1_OCTET_STRING_cmp(expected, actual) != 0)) {
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
- char *expected_str, *actual_str;
+ char *expected_str, *actual_str, *expected_msg, *actual_msg;
+ const int strict = bodytype != OSSL_CMP_PKIBODY_ERROR || !ctx->nonmatchedErrorNonces;
+ int res = 0;
+ if (reason == 0) /* at end of polling, overall check is not yet complete */
+ return res;
expected_str = i2s_ASN1_OCTET_STRING(NULL, expected);
+ expected_msg = expected_str == NULL ? "?" : expected_str;
actual_str = actual == NULL ? NULL : i2s_ASN1_OCTET_STRING(NULL, actual);
- ERR_raise_data(ERR_LIB_CMP, reason,
- "expected = %s, actual = %s",
- expected_str == NULL ? "?" : expected_str,
- actual == NULL ? "(none)" : actual_str == NULL ? "?"
- : actual_str);
+ actual_msg = actual == NULL ? "(none)" : actual_str == NULL ? "?"
+ : actual_str;
+ if (strict) {
+ ERR_raise_data(ERR_LIB_CMP, reason, "expected = %s, actual = %s",
+ expected_msg, actual_msg);
+ } else {
+ ossl_cmp_log3(WARN, ctx, "ignoring missing or non-matching %s of error message, expected = %s, actual = %s",
+ reason == CMP_R_TRANSACTIONID_UNMATCHED ? "transactionID" : "recipNonce",
+ expected_msg, actual_msg);
+ res = 1;
+ }
OPENSSL_free(expected_str);
OPENSSL_free(actual_str);
- return 0;
+ return res;
#endif
}
return 1;
{
OSSL_CMP_PKIHEADER *hdr;
const X509_NAME *expected_sender;
+ int bodytype, end_of_polling;
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);
+ bodytype = OSSL_CMP_MSG_get_bodytype(msg);
/* If expected_sender is given, validate sender name of received msg */
expected_sender = ctx->expected_sender;
return 0;
}
}
+
+ /* Ignoring recipient */
/* Note: if recipient was NULL-DN it could be learned here if needed */
num_added = sk_X509_num(msg->extraCerts);
#endif
}
- if (OSSL_CMP_MSG_get_bodytype(msg) < 0) {
+ if (bodytype < 0) {
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
ERR_raise(ERR_LIB_CMP, CMP_R_PKIBODY_ERROR);
return 0;
}
/* compare received transactionID with the expected one in previous msg */
- if (!check_transactionID_or_nonce(ctx->transactionID, hdr->transactionID,
- CMP_R_TRANSACTIONID_UNMATCHED))
+ if (!check_transactionID_or_nonce(ctx, ctx->transactionID, hdr->transactionID,
+ bodytype, CMP_R_TRANSACTIONID_UNMATCHED))
return 0;
/*
- * enable clearing irrelevant errors
- * in attempts to validate recipient nonce in case of delayed delivery.
+ * Compare received nonce with the one we sent last.
+ * When we received the final response at the end of polling,
+ * we allow also the nonce that we sent earlier with the original request,
+ * as specified in RFC 9483 section 5.1.5.
*/
- (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();
+ end_of_polling = ctx->first_senderNonce != NULL && bodytype != OSSL_CMP_PKIBODY_POLLREP;
+ if (!check_transactionID_or_nonce(ctx, ctx->senderNonce, hdr->recipNonce,
+ bodytype, end_of_polling ? 0 : CMP_R_RECIPNONCE_UNMATCHED)) {
+ if (!end_of_polling
+ /* otherwise, compare received nonce with our sender nonce at poll start: */
+ || !check_transactionID_or_nonce(ctx, ctx->first_senderNonce, hdr->recipNonce,
+ bodytype, CMP_R_RECIPNONCE_UNMATCHED))
return 0;
- }
}
- (void)ERR_pop_to_mark();
/* if not yet present, learn transactionID */
if (ctx->transactionID == NULL
* the caPubs field may be directly trusted as a root CA
* certificate by the initiator.'
*/
- switch (OSSL_CMP_MSG_get_bodytype(msg)) {
+ switch (bodytype) {
case OSSL_CMP_PKIBODY_IP:
case OSSL_CMP_PKIBODY_CP:
case OSSL_CMP_PKIBODY_KUP:
[B<-expect_sender> I<name>]
[B<-ignore_keyusage>]
[B<-unprotected_errors>]
+[B<-nonmatched_error_nonces>]
[B<-ta_in_ip_extracerts>]
[B<-no_cache_extracerts>]
[B<-srvcertout> I<filename>]
=back
+=item B<-nonmatched_error_nonces>
+
+Accept missing or non-matching transactionID or recipNonce values in error messages.
+This can be helpful in case the server cannot provide a proper error message,
+for instance if it was unable to parse the ASN.1 encoding of a request message.
+
=item B<-ta_in_ip_extracerts>
This is a quirk option added to support 3GPP TS 33.310.
The B<-engine> option was removed in OpenSSL 4.0.
+The B<-nonmatched_error_nonces> option was added in OpenSSL 4.1.
+
=head1 COPYRIGHT
Copyright 2007-2026 The OpenSSL Project Authors. All Rights Reserved.
exclusively to allow interoperability with server implementations violating
RFC 9810.
+=item B<OSSL_CMP_OPT_NONMATCHED_ERROR_NONCES>
+
+Accept missing or non-matching transactionID or recipNonce in error messages.
+This can be helpful in case the server cannot provide a proper error message,
+for instance if it was unable to parse the ASN.1 encoding of a request message.
+
=item B<OSSL_CMP_OPT_IGNORE_KEYUSAGE>
Ignore key usage restrictions in the signer's certificate when
Support for central key generation, requested via B<OSSL_CRMF_POPO_NONE>,
was added in OpenSSL 3.5.
+
The B<OSSL_CMP_OPT_PERMIT_TA_IN_EXTRACERTS_FOR_IR> option was added in OpenSSL 4.0.
+The B<OSSL_CMP_OPT_NONMATCHED_ERROR_NONCES> option was added in OpenSSL 4.1.
+
=head1 COPYRIGHT
Copyright 2007-2025 The OpenSSL Project Authors. All Rights Reserved.
#define OSSL_CMP_OPT_IGNORE_KEYUSAGE 35
#define OSSL_CMP_OPT_PERMIT_TA_IN_EXTRACERTS_FOR_IR 36
#define OSSL_CMP_OPT_NO_CACHE_EXTRACERTS 37
+#define OSSL_CMP_OPT_NONMATCHED_ERROR_NONCES 38
int OSSL_CMP_CTX_set_option(OSSL_CMP_CTX *ctx, int opt, int val);
int OSSL_CMP_CTX_get_option(const OSSL_CMP_CTX *ctx, int opt);
/* CMP-specific callback for logging and outputting the error queue: */
static const char *ir_unprotected_f;
static const char *ir_rmprotection_f;
static const char *ip_waiting_f;
+static const char *error_protected_f;
static const char *instacert_f;
static const char *instaca_f;
static const char *ir_protected_0_extracerts;
static X509 *insta_cert = NULL, *instaca_cert = NULL;
static unsigned char rand_data[OSSL_CMP_TRANSACTIONID_LENGTH];
-static OSSL_CMP_MSG *ir_unprotected, *ir_rmprotection;
+static OSSL_CMP_MSG *ir_unprotected, *ir_rmprotection, *error_protected;
/* secret value used for IP_waitingStatus_PBM.der */
static const unsigned char sec_1[] = {
if (fixture->expected == 0) /* error expected already during above check */
return 1;
- return TEST_int_eq(0,
- ASN1_OCTET_STRING_cmp(ossl_cmp_hdr_get0_senderNonce(hdr),
- fixture->cmp_ctx->recipNonce))
+ if (OSSL_CMP_CTX_get_option(fixture->cmp_ctx, OSSL_CMP_OPT_NONMATCHED_ERROR_NONCES))
+ return 1;
+ return TEST_int_eq(0, ASN1_OCTET_STRING_cmp(ossl_cmp_hdr_get0_senderNonce(hdr), fixture->cmp_ctx->recipNonce))
&& TEST_int_eq(0,
ASN1_OCTET_STRING_cmp(tid,
fixture->cmp_ctx->transactionID));
static void setup_check_update(CMP_VFY_TEST_FIXTURE **fixture, int expected,
ossl_cmp_allow_unprotected_cb_t cb, int arg,
+ const OSSL_CMP_MSG *msg,
const unsigned char *trid_data,
const unsigned char *nonce_data)
{
(*fixture)->expected = expected;
(*fixture)->allow_unprotected_cb = cb;
(*fixture)->additional_arg = arg;
- (*fixture)->msg = OSSL_CMP_MSG_dup(ir_rmprotection);
+ (*fixture)->msg = OSSL_CMP_MSG_dup(msg);
if ((*fixture)->msg == NULL
|| (nonce_data != NULL
&& !ossl_cmp_asn1_octet_string_set1_bytes(&ctx->senderNonce,
static int test_msg_check_no_protection_no_cb(void)
{
SETUP_TEST_FIXTURE(CMP_VFY_TEST_FIXTURE, set_up);
- setup_check_update(&fixture, 0, NULL, 0, NULL, NULL);
+ setup_check_update(&fixture, 0, NULL, 0, ir_rmprotection, NULL, NULL);
EXECUTE_TEST(execute_msg_check_test, tear_down);
return result;
}
static int test_msg_check_no_protection_restrictive_cb(void)
{
SETUP_TEST_FIXTURE(CMP_VFY_TEST_FIXTURE, set_up);
- setup_check_update(&fixture, 0, allow_unprotected, 0, NULL, NULL);
+ setup_check_update(&fixture, 0, allow_unprotected, 0, ir_rmprotection, NULL, NULL);
EXECUTE_TEST(execute_msg_check_test, tear_down);
return result;
}
static int test_msg_check_no_protection_permissive_cb(void)
{
SETUP_TEST_FIXTURE(CMP_VFY_TEST_FIXTURE, set_up);
- setup_check_update(&fixture, 1, allow_unprotected, 1, NULL, NULL);
+ setup_check_update(&fixture, 1, allow_unprotected, 1, ir_rmprotection, NULL, NULL);
EXECUTE_TEST(execute_msg_check_test, tear_down);
return result;
}
};
SETUP_TEST_FIXTURE(CMP_VFY_TEST_FIXTURE, set_up);
- setup_check_update(&fixture, 1, allow_unprotected, 1, trans_id, NULL);
+ setup_check_update(&fixture, 1, allow_unprotected, 1, ir_rmprotection, trans_id, NULL);
EXECUTE_TEST(execute_msg_check_test, tear_down);
return result;
}
static int test_msg_check_transaction_id_bad(void)
{
SETUP_TEST_FIXTURE(CMP_VFY_TEST_FIXTURE, set_up);
- setup_check_update(&fixture, 0, allow_unprotected, 1, rand_data, NULL);
+ setup_check_update(&fixture, 0, allow_unprotected, 1, ir_rmprotection, rand_data, NULL);
EXECUTE_TEST(execute_msg_check_test, tear_down);
return result;
}
#endif
+static int test_msg_check_transaction_id_error(void)
+{
+ SETUP_TEST_FIXTURE(CMP_VFY_TEST_FIXTURE, set_up);
+
+ (void)OSSL_CMP_CTX_set_option(fixture->cmp_ctx, OSSL_CMP_OPT_NONMATCHED_ERROR_NONCES, 1);
+ setup_check_update(&fixture, 1, allow_unprotected, 1, error_protected, rand_data, NULL);
+ EXECUTE_TEST(execute_msg_check_test, tear_down);
+ return result;
+}
+
static int test_msg_check_recipient_nonce(void)
{
/* Recipient nonce belonging to CMP_IP_ir_rmprotection.der */
};
SETUP_TEST_FIXTURE(CMP_VFY_TEST_FIXTURE, set_up);
- setup_check_update(&fixture, 1, allow_unprotected, 1, NULL, rec_nonce);
+ setup_check_update(&fixture, 1, allow_unprotected, 1, ir_rmprotection, NULL, rec_nonce);
EXECUTE_TEST(execute_msg_check_test, tear_down);
return result;
}
static int test_msg_check_recipient_nonce_bad(void)
{
SETUP_TEST_FIXTURE(CMP_VFY_TEST_FIXTURE, set_up);
- setup_check_update(&fixture, 0, allow_unprotected, 1, NULL, rand_data);
+ setup_check_update(&fixture, 0, allow_unprotected, 1, ir_rmprotection, NULL, rand_data);
EXECUTE_TEST(execute_msg_check_test, tear_down);
return result;
}
#endif
+/* Transaction id belonging to error_protected.der not needed here: */
+/* 0x5D, 0x90, 0x14, 0xB4, 0xBA, 0xAD, 0x6F, 0xCC, 0x36, 0x7B, 0xB8, 0x09, 0xB6, 0x98, 0xBF, 0x21 */
+static int test_msg_check_recipient_nonce_error(void)
+{
+ SETUP_TEST_FIXTURE(CMP_VFY_TEST_FIXTURE, set_up);
+ (void)OSSL_CMP_CTX_set_option(fixture->cmp_ctx, OSSL_CMP_OPT_NONMATCHED_ERROR_NONCES, 1);
+ setup_check_update(&fixture, 1, allow_unprotected, 1, error_protected, NULL, rand_data);
+ EXECUTE_TEST(execute_msg_check_test, tear_down);
+ return result;
+}
+
void cleanup_tests(void)
{
X509_free(srvcert);
X509_free(instaca_cert);
OSSL_CMP_MSG_free(ir_unprotected);
OSSL_CMP_MSG_free(ir_rmprotection);
+ OSSL_CMP_MSG_free(error_protected);
OSSL_PROVIDER_unload(default_null_provider);
OSSL_PROVIDER_unload(provider);
OSSL_LIB_CTX_free(libctx);
"Root_CA.crt Intermediate_CA.crt " \
"CMP_IR_protected.der CMP_IR_unprotected.der " \
"IP_waitingStatus_PBM.der IR_rmprotection.der " \
+ "error_protected.der " \
"insta.cert.pem insta_ca.cert.pem " \
"IR_protected_0_extraCerts.der " \
"IR_protected_2_extraCerts.der module_name [module_conf_file]\n"
|| !TEST_ptr(ir_unprotected_f = test_get_argument(7))
|| !TEST_ptr(ip_waiting_f = test_get_argument(8))
|| !TEST_ptr(ir_rmprotection_f = test_get_argument(9))
- || !TEST_ptr(instacert_f = test_get_argument(10))
- || !TEST_ptr(instaca_f = test_get_argument(11))
- || !TEST_ptr(ir_protected_0_extracerts = test_get_argument(12))
- || !TEST_ptr(ir_protected_2_extracerts = test_get_argument(13))) {
+ || !TEST_ptr(error_protected_f = test_get_argument(10))
+ || !TEST_ptr(instacert_f = test_get_argument(11))
+ || !TEST_ptr(instaca_f = test_get_argument(12))
+ || !TEST_ptr(ir_protected_0_extracerts = test_get_argument(13))
+ || !TEST_ptr(ir_protected_2_extracerts = test_get_argument(14))) {
TEST_error("usage: cmp_vfy_test %s", USAGE);
return 0;
}
- if (!test_arg_libctx(&libctx, &default_null_provider, &provider, 14, USAGE))
+ if (!test_arg_libctx(&libctx, &default_null_provider, &provider, 15, USAGE))
return 0;
/* Load certificates for cert chain */
if (!TEST_int_eq(1, RAND_bytes(rand_data, OSSL_CMP_TRANSACTIONID_LENGTH)))
goto err;
if (!TEST_ptr(ir_unprotected = load_pkimsg(ir_unprotected_f, libctx))
- || !TEST_ptr(ir_rmprotection = load_pkimsg(ir_rmprotection_f,
- libctx)))
+ || !TEST_ptr(ir_rmprotection = load_pkimsg(ir_rmprotection_f, libctx))
+ || !TEST_ptr(error_protected = load_pkimsg(error_protected_f, libctx)))
goto err;
/* Message validation tests */
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
ADD_TEST(test_msg_check_transaction_id_bad);
#endif
+ ADD_TEST(test_msg_check_transaction_id_error);
ADD_TEST(test_msg_check_recipient_nonce);
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
ADD_TEST(test_msg_check_recipient_nonce_bad);
#endif
+ ADD_TEST(test_msg_check_recipient_nonce_error);
return 1;
data_file("IR_unprotected.der"),
data_file("IP_waitingStatus_PBM.der"),
data_file("IR_rmprotection.der"),
+ data_file("error_protected.der"),
data_file("insta.cert.pem"),
data_file("insta_ca.cert.pem"),
data_file("IR_protected_0_extraCerts.der"),