summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDr. David von Oheimb <David.von.Oheimb@siemens.com>2020-03-10 17:32:57 +0100
committerDr. David von Oheimb <David.von.Oheimb@siemens.com>2020-03-25 14:10:18 +0100
commit7e765f46a6b3a5b2fc48e10657bea7016e5c5e4b (patch)
tree10aa335bdb8955d13781f0139d49b3a30b7b5578
parentb4ba2b7ce0933bede5d3b59a5abbde8fa3de2228 (diff)
Chunk 9 of CMP contribution to OpenSSL: CMP client and related tests
Certificate Management Protocol (CMP, RFC 4210) extension to OpenSSL Also includes CRMF (RFC 4211) and HTTP transfer (RFC 6712). Adds the CMP and CRMF API to libcrypto and the "cmp" app to the CLI. Adds extensive documentation and tests. Reviewed-by: Matt Caswell <matt@openssl.org> Reviewed-by: David von Oheimb <david.von.oheimb@siemens.com> (Merged from https://github.com/openssl/openssl/pull/11300)
-rw-r--r--apps/cmp_mock_srv.c11
-rw-r--r--crypto/cmp/build.info2
-rw-r--r--crypto/cmp/cmp_client.c881
-rw-r--r--crypto/cmp/cmp_ctx.c42
-rw-r--r--crypto/cmp/cmp_err.c37
-rw-r--r--crypto/cmp/cmp_local.h16
-rw-r--r--crypto/cmp/cmp_msg.c3
-rw-r--r--crypto/cmp/cmp_server.c18
-rw-r--r--crypto/cmp/cmp_status.c2
-rw-r--r--crypto/cmp/cmp_util.c2
-rw-r--r--crypto/cmp/cmp_vfy.c39
-rw-r--r--crypto/err/openssl.txt22
-rw-r--r--doc/internal/man3/ossl_cmp_msg_check_received.pod86
-rw-r--r--doc/internal/man3/ossl_cmp_msg_create.pod3
-rw-r--r--doc/man3/OSSL_CMP_CTX_new.pod38
-rw-r--r--doc/man3/OSSL_CMP_SRV_CTX_new.pod3
-rw-r--r--doc/man3/OSSL_CMP_exec_IR_ses.pod172
-rw-r--r--doc/man3/OSSL_CMP_log_open.pod8
-rw-r--r--include/openssl/cmp.h37
-rw-r--r--include/openssl/cmp_util.h4
-rw-r--r--include/openssl/cmperr.h22
-rw-r--r--test/build.info6
-rw-r--r--test/cmp_client_test.c393
-rw-r--r--test/cmp_ctx_test.c6
-rw-r--r--test/cmp_msg_test.c4
-rw-r--r--test/cmp_testlib.c6
-rw-r--r--test/cmp_testlib.h2
-rw-r--r--test/cmp_vfy_test.c6
-rw-r--r--test/recipes/65-test_cmp_client.t27
-rw-r--r--test/recipes/65-test_cmp_client_data/client.crt13
-rw-r--r--test/recipes/65-test_cmp_client_data/client.csrbin0 -> 424 bytes
-rw-r--r--test/recipes/65-test_cmp_client_data/client.key7
-rw-r--r--test/recipes/65-test_cmp_client_data/server.crt17
-rw-r--r--test/recipes/65-test_cmp_client_data/server.key27
-rw-r--r--util/libcrypto.num10
-rw-r--r--util/missingcrypto.txt3
-rw-r--r--util/other.syms11
37 files changed, 1860 insertions, 126 deletions
diff --git a/apps/cmp_mock_srv.c b/apps/cmp_mock_srv.c
index 2e02104884..8ffe4ca5a8 100644
--- a/apps/cmp_mock_srv.c
+++ b/apps/cmp_mock_srv.c
@@ -187,7 +187,7 @@ static OSSL_CMP_PKISI *process_cert_request(OSSL_CMP_SRV_CTX *srv_ctx,
return NULL;
}
if (ctx->sendError) {
- CMPerr(0, CMP_R_ERROR_PROCESSING_MSG);
+ CMPerr(0, CMP_R_ERROR_PROCESSING_MESSAGE);
return NULL;
}
@@ -238,7 +238,7 @@ static OSSL_CMP_PKISI *process_rr(OSSL_CMP_SRV_CTX *srv_ctx,
return NULL;
}
if (ctx->sendError || ctx->certOut == NULL) {
- CMPerr(0, CMP_R_ERROR_PROCESSING_MSG);
+ CMPerr(0, CMP_R_ERROR_PROCESSING_MESSAGE);
return NULL;
}
@@ -264,7 +264,7 @@ static int process_genm(OSSL_CMP_SRV_CTX *srv_ctx,
return 0;
}
if (ctx->sendError) {
- CMPerr(0, CMP_R_ERROR_PROCESSING_MSG);
+ CMPerr(0, CMP_R_ERROR_PROCESSING_MESSAGE);
return 0;
}
@@ -306,6 +306,7 @@ static void process_error(OSSL_CMP_SRV_CTX *srv_ctx, const OSSL_CMP_MSG *error,
if (sk_ASN1_UTF8STRING_num(errorDetails) <= 0) {
BIO_printf(bio_err, "errorDetails absent\n");
} else {
+ /* TODO could use sk_ASN1_UTF8STRING2text() if exported */
BIO_printf(bio_err, "errorDetails: ");
for (i = 0; i < sk_ASN1_UTF8STRING_num(errorDetails); i++) {
if (i > 0)
@@ -332,7 +333,7 @@ static int process_certConf(OSSL_CMP_SRV_CTX *srv_ctx,
return 0;
}
if (ctx->sendError || ctx->certOut == NULL) {
- CMPerr(0, CMP_R_ERROR_PROCESSING_MSG);
+ CMPerr(0, CMP_R_ERROR_PROCESSING_MESSAGE);
return 0;
}
@@ -366,7 +367,7 @@ static int process_pollReq(OSSL_CMP_SRV_CTX *srv_ctx,
}
if (ctx->sendError || ctx->certReq == NULL) {
*certReq = NULL;
- CMPerr(0, CMP_R_ERROR_PROCESSING_MSG);
+ CMPerr(0, CMP_R_ERROR_PROCESSING_MESSAGE);
return 0;
}
diff --git a/crypto/cmp/build.info b/crypto/cmp/build.info
index 1667334e2a..d3fbae2452 100644
--- a/crypto/cmp/build.info
+++ b/crypto/cmp/build.info
@@ -1,4 +1,4 @@
LIBS=../../libcrypto
SOURCE[../../libcrypto]= cmp_asn.c cmp_ctx.c cmp_err.c cmp_util.c \
cmp_status.c cmp_hdr.c cmp_protect.c cmp_msg.c cmp_vfy.c \
- cmp_server.c
+ cmp_server.c cmp_client.c
diff --git a/crypto/cmp/cmp_client.c b/crypto/cmp/cmp_client.c
new file mode 100644
index 0000000000..394358c5e3
--- /dev/null
+++ b/crypto/cmp/cmp_client.c
@@ -0,0 +1,881 @@
+/*
+ * Copyright 2007-2019 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright Nokia 2007-2019
+ * Copyright Siemens AG 2015-2019
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include "cmp_local.h"
+#include "internal/cryptlib.h"
+
+/* explicit #includes not strictly needed since implied by the above: */
+#include <openssl/bio.h>
+#include <openssl/cmp.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/x509v3.h>
+
+#include "openssl/cmp_util.h"
+
+#define IS_CREP(t) ((t) == OSSL_CMP_PKIBODY_IP || (t) == OSSL_CMP_PKIBODY_CP \
+ || (t) == OSSL_CMP_PKIBODY_KUP)
+
+/*-
+ * Evaluate whether there's an exception (violating the standard) configured for
+ * handling negative responses without protection or with invalid protection.
+ * Returns 1 on acceptance, 0 on rejection, or -1 on (internal) error.
+ */
+static int unprotected_exception(const OSSL_CMP_CTX *ctx,
+ const OSSL_CMP_MSG *rep,
+ int invalid_protection,
+ int expected_type /* ignored here */)
+{
+ int rcvd_type = ossl_cmp_msg_get_bodytype(rep /* may be NULL */);
+ const char *msg_type = NULL;
+
+ if (!ossl_assert(ctx != NULL && rep != NULL))
+ return -1;
+
+ if (!ctx->unprotectedErrors)
+ return 0;
+
+ switch (rcvd_type) {
+ case OSSL_CMP_PKIBODY_ERROR:
+ msg_type = "error response";
+ break;
+ case OSSL_CMP_PKIBODY_RP:
+ {
+ OSSL_CMP_PKISI *si =
+ ossl_cmp_revrepcontent_get_pkisi(rep->body->value.rp,
+ OSSL_CMP_REVREQSID);
+
+ if (si == NULL)
+ return -1;
+ if (ossl_cmp_pkisi_get_status(si) == OSSL_CMP_PKISTATUS_rejection)
+ msg_type = "revocation response message with rejection status";
+ break;
+ }
+ case OSSL_CMP_PKIBODY_PKICONF:
+ msg_type = "PKI Confirmation message";
+ break;
+ default:
+ if (IS_CREP(rcvd_type)) {
+ OSSL_CMP_CERTREPMESSAGE *crepmsg = rep->body->value.ip;
+ OSSL_CMP_CERTRESPONSE *crep =
+ ossl_cmp_certrepmessage_get0_certresponse(crepmsg,
+ -1 /* any rid */);
+
+ if (sk_OSSL_CMP_CERTRESPONSE_num(crepmsg->response) > 1)
+ return -1;
+ /* TODO: handle potentially multiple CertResponses in CertRepMsg */
+ if (crep == NULL)
+ return -1;
+ if (ossl_cmp_pkisi_get_status(crep->status)
+ == OSSL_CMP_PKISTATUS_rejection)
+ msg_type = "CertRepMessage with rejection status";
+ }
+ }
+ if (msg_type == NULL)
+ return 0;
+ ossl_cmp_log2(WARN, ctx, "ignoring %s protection of %s",
+ invalid_protection ? "invalid" : "missing", msg_type);
+ return 1;
+}
+
+
+/* Save error info from PKIStatusInfo field of a certresponse into ctx */
+static int save_statusInfo(OSSL_CMP_CTX *ctx, OSSL_CMP_PKISI *si)
+{
+ int i;
+ OSSL_CMP_PKIFREETEXT *ss;
+
+ if (!ossl_assert(ctx != NULL && si != NULL))
+ return 0;
+
+ if ((ctx->status = ossl_cmp_pkisi_get_status(si)) < 0)
+ return 0;
+
+ ctx->failInfoCode = 0;
+ if (si->failInfo != NULL) {
+ for (i = 0; i <= OSSL_CMP_PKIFAILUREINFO_MAX; i++) {
+ if (ASN1_BIT_STRING_get_bit(si->failInfo, i))
+ ctx->failInfoCode |= (1 << i);
+ }
+ }
+
+ if (!ossl_cmp_ctx_set0_statusString(ctx, sk_ASN1_UTF8STRING_new_null())
+ || (ctx->statusString == NULL))
+ return 0;
+
+ ss = si->statusString; /* may be NULL */
+ for (i = 0; i < sk_ASN1_UTF8STRING_num(ss); i++) {
+ ASN1_UTF8STRING *str = sk_ASN1_UTF8STRING_value(ss, i);
+
+ if (!sk_ASN1_UTF8STRING_push(ctx->statusString, ASN1_STRING_dup(str)))
+ return 0;
+ }
+ return 1;
+}
+
+/*-
+ * Perform the generic aspects of sending a request and receiving a response.
+ * Returns 1 on success and provides the received PKIMESSAGE in *rep.
+ * Returns 0 on error.
+ * Regardless of success, caller is responsible for freeing *rep (unless NULL).
+ */
+static int send_receive_check(OSSL_CMP_CTX *ctx, const OSSL_CMP_MSG *req,
+ OSSL_CMP_MSG **rep, int expected_type)
+{
+ const char *req_type_str =
+ ossl_cmp_bodytype_to_string(ossl_cmp_msg_get_bodytype(req));
+ const char *expected_type_str = ossl_cmp_bodytype_to_string(expected_type);
+ int msg_timeout;
+ int bt;
+ time_t now = time(NULL);
+ int time_left;
+ OSSL_CMP_transfer_cb_t transfer_cb = ctx->transfer_cb;
+
+ if (transfer_cb == NULL)
+ transfer_cb = NULL; /* TODO: will be OSSL_CMP_MSG_http_perform of chunk 10 */
+
+ *rep = NULL;
+ msg_timeout = ctx->msg_timeout; /* backup original value */
+ if ((IS_CREP(expected_type) || expected_type == OSSL_CMP_PKIBODY_POLLREP)
+ && ctx->total_timeout > 0 /* timeout is not infinite */) {
+ if (now >= ctx->end_time) {
+ CMPerr(0, CMP_R_TOTAL_TIMEOUT);
+ return 0;
+ }
+ if (!ossl_assert(ctx->end_time - time(NULL) < INT_MAX)) {
+ /* cannot really happen due to the assignment in do_certreq_seq() */
+ CMPerr(0, CMP_R_INVALID_ARGS);
+ return 0;
+ }
+ time_left = (int)(ctx->end_time - now);
+ if (ctx->msg_timeout == 0 || time_left < ctx->msg_timeout)
+ ctx->msg_timeout = time_left;
+ }
+
+ /* should print error queue since transfer_cb may call ERR_clear_error() */
+ OSSL_CMP_CTX_print_errors(ctx);
+
+ ossl_cmp_log1(INFO, ctx, "sending %s", req_type_str);
+
+ *rep = (*transfer_cb)(ctx, req);
+ ctx->msg_timeout = msg_timeout; /* restore original value */
+
+ if (*rep == NULL) {
+ CMPerr(0, CMP_R_TRANSFER_ERROR); /* or receiving response */
+ ERR_add_error_data(1, req_type_str);
+ ERR_add_error_data(2, ", expected response: ", expected_type_str);
+ return 0;
+ }
+
+ bt = ossl_cmp_msg_get_bodytype(*rep);
+ /*
+ * The body type in the 'bt' variable is not yet verified.
+ * Still we use this preliminary value already for a progress report because
+ * the following msg verification may also produce log entries and may fail.
+ */
+ ossl_cmp_log1(INFO, ctx, "received %s", ossl_cmp_bodytype_to_string(bt));
+
+ if ((bt = ossl_cmp_msg_check_received(ctx, *rep, unprotected_exception,
+ expected_type)) < 0)
+ return 0;
+
+ if (bt == expected_type
+ /* as an answer to polling, there could be IP/CP/KUP: */
+ || (IS_CREP(bt) && expected_type == OSSL_CMP_PKIBODY_POLLREP))
+ return 1;
+
+ /* received message type is not one of the expected ones (e.g., error) */
+ CMPerr(0, bt == OSSL_CMP_PKIBODY_ERROR ? CMP_R_RECEIVED_ERROR :
+ CMP_R_UNEXPECTED_PKIBODY); /* in next line for mkerr.pl */
+
+ if (bt != OSSL_CMP_PKIBODY_ERROR) {
+ ERR_add_error_data(3, "message type is '",
+ ossl_cmp_bodytype_to_string(bt), "'");
+ } else {
+ OSSL_CMP_ERRORMSGCONTENT *emc = (*rep)->body->value.error;
+ OSSL_CMP_PKISI *si = emc->pKIStatusInfo;
+ char buf[OSSL_CMP_PKISI_BUFLEN];
+
+ if (save_statusInfo(ctx, si)
+ && OSSL_CMP_CTX_snprint_PKIStatus(ctx, buf, sizeof(buf)) != NULL)
+ ERR_add_error_data(1, buf);
+ if (emc->errorCode != NULL
+ && BIO_snprintf(buf, sizeof(buf), "; errorCode: %ld",
+ ASN1_INTEGER_get(emc->errorCode)) > 0)
+ ERR_add_error_data(1, buf);
+ if (emc->errorDetails != NULL) {
+ char *text = sk_ASN1_UTF8STRING2text(emc->errorDetails, ", ",
+ OSSL_CMP_PKISI_BUFLEN - 1);
+
+ if (text != NULL)
+ ERR_add_error_data(2, "; errorDetails: ", text);
+ OPENSSL_free(text);
+ }
+ if (ctx->status != OSSL_CMP_PKISTATUS_rejection) {
+ CMPerr(0, CMP_R_UNEXPECTED_PKISTATUS);
+ if (ctx->status == OSSL_CMP_PKISTATUS_waiting)
+ ctx->status = OSSL_CMP_PKISTATUS_rejection;
+ }
+ }
+ return 0;
+}
+
+/*-
+ * When a 'waiting' PKIStatus has been received, this function is used to
+ * poll, which should yield a pollRep or finally a CertRepMessage in ip/cp/kup.
+ * On receiving a pollRep, which includes a checkAfter value, it return this
+ * value if sleep == 0, else it sleeps as long as indicated and retries.
+ *
+ * A transaction timeout is enabled if ctx->total_timeout is > 0.
+ * In this case polling will continue until the timeout is reached and then
+ * polling is done a last time even if this is before the "checkAfter" time.
+ *
+ * Returns -1 on receiving pollRep if sleep == 0, setting the checkAfter value.
+ * Returns 1 on success and provides the received PKIMESSAGE in *rep.
+ * In this case the caller is responsible for freeing *rep.
+ * Returns 0 on error (which includes the case that timeout has been reached).
+ */
+static int poll_for_response(OSSL_CMP_CTX *ctx, int sleep, int rid,
+ OSSL_CMP_MSG **rep, int *checkAfter)
+{
+ OSSL_CMP_MSG *preq = NULL;
+ OSSL_CMP_MSG *prep = NULL;
+
+ ossl_cmp_info(ctx,
+ "received 'waiting' PKIStatus, starting to poll for response");
+ *rep = NULL;
+ for (;;) {
+ /* TODO: handle potentially multiple poll requests per message */
+ if ((preq = ossl_cmp_pollReq_new(ctx, rid)) == NULL)
+ goto err;
+
+ if (!send_receive_check(ctx, preq, &prep, OSSL_CMP_PKIBODY_POLLREP))
+ goto err;
+
+ /* handle potential pollRep */
+ if (ossl_cmp_msg_get_bodytype(prep) == OSSL_CMP_PKIBODY_POLLREP) {
+ OSSL_CMP_POLLREPCONTENT *prc = prep->body->value.pollRep;
+ OSSL_CMP_POLLREP *pollRep = NULL;
+ int64_t check_after;
+ char str[OSSL_CMP_PKISI_BUFLEN];
+ int len;
+
+ /* TODO: handle potentially multiple elements in pollRep */
+ if (sk_OSSL_CMP_POLLREP_num(prc) > 1) {
+ CMPerr(0, CMP_R_MULTIPLE_RESPONSES_NOT_SUPPORTED);
+ goto err;
+ }
+ pollRep = ossl_cmp_pollrepcontent_get0_pollrep(prc, rid);
+ if (pollRep == NULL)
+ goto err;
+
+ if (!ASN1_INTEGER_get_int64(&check_after, pollRep->checkAfter)) {
+ CMPerr(0, CMP_R_BAD_CHECKAFTER_IN_POLLREP);
+ goto err;
+ }
+ if (check_after < 0 || (uint64_t)check_after
+ > (sleep ? ULONG_MAX / 1000 : INT_MAX)) {
+ CMPerr(0, CMP_R_CHECKAFTER_OUT_OF_RANGE);
+ if (BIO_snprintf(str, OSSL_CMP_PKISI_BUFLEN, "value = %ld",
+ check_after) >= 0)
+ ERR_add_error_data(1, str);
+ goto err;
+ }
+ if (ctx->total_timeout > 0) { /* timeout is not infinite */
+ const int exp = 5; /* expected max time per msg round trip */
+ int64_t time_left = (int64_t)(ctx->end_time - exp - time(NULL));
+
+ if (time_left <= 0) {
+ CMPerr(0, CMP_R_TOTAL_TIMEOUT);
+ goto err;
+ }
+ if (time_left < check_after)
+ check_after = time_left;
+ /* poll one last time just when timeout was reached */
+ }
+
+ if (pollRep->reason == NULL
+ || (len = BIO_snprintf(str, OSSL_CMP_PKISI_BUFLEN,
+ " with reason = '")) < 0) {
+ *str = '\0';
+ } else {
+ char *text = sk_ASN1_UTF8STRING2text(pollRep->reason, ", ",
+ sizeof(str) - len - 2);
+
+ if (text == NULL
+ || BIO_snprintf(str + len, sizeof(str) - len,
+ "%s'", text) < 0)
+ *str = '\0';
+ OPENSSL_free(text);
+ }
+ ossl_cmp_log2(INFO, ctx,
+ "received polling response%s; checkAfter = %ld seconds",
+ str, check_after);
+
+ OSSL_CMP_MSG_free(preq);
+ preq = NULL;
+ OSSL_CMP_MSG_free(prep);
+ prep = NULL;
+ if (sleep) {
+ ossl_sleep((unsigned long)(1000 * check_after));
+ } else {
+ if (checkAfter != NULL)
+ *checkAfter = (int)check_after;
+ return -1; /* exits the loop */
+ }
+ } else {
+ ossl_cmp_info(ctx, "received ip/cp/kup after polling");
+ /* any other body type has been rejected by send_receive_check() */
+ break;
+ }
+ }
+ if (prep == NULL)
+ goto err;
+
+ OSSL_CMP_MSG_free(preq);
+ *rep = prep;
+
+ return 1;
+ err:
+ OSSL_CMP_MSG_free(preq);
+ OSSL_CMP_MSG_free(prep);
+ return 0;
+}
+
+/* Send certConf for IR, CR or KUR sequences and check response */
+int ossl_cmp_exchange_certConf(OSSL_CMP_CTX *ctx, int fail_info,
+ const char *txt)
+{
+ OSSL_CMP_MSG *certConf;
+ OSSL_CMP_MSG *PKIconf = NULL;
+ int res = 0;
+
+ /* OSSL_CMP_certConf_new() also checks if all necessary options are set */
+ if ((certConf = ossl_cmp_certConf_new(ctx, fail_info, txt)) == NULL)
+ goto err;
+
+ res = send_receive_check(ctx, certConf, &PKIconf, OSSL_CMP_PKIBODY_PKICONF);
+
+ err:
+ OSSL_CMP_MSG_free(certConf);
+ OSSL_CMP_MSG_free(PKIconf);
+ return res;
+}
+
+/* Send given error and check response */
+int ossl_cmp_exchange_error(OSSL_CMP_CTX *ctx, int status, int fail_info,
+ const char *txt, int errorCode, const char *details)
+{
+ OSSL_CMP_MSG *error = NULL;
+ OSSL_CMP_PKISI *si = NULL;
+ OSSL_CMP_MSG *PKIconf = NULL;
+ int res = 0;
+
+ if ((si = OSSL_CMP_STATUSINFO_new(status, fail_info, txt)) == NULL)
+ goto err;
+ /* ossl_cmp_error_new() also checks if all necessary options are set */
+ if ((error = ossl_cmp_error_new(ctx, si, errorCode, details, 0)) == NULL)
+ goto err;
+
+ res = send_receive_check(ctx, error, &PKIconf, OSSL_CMP_PKIBODY_PKICONF);
+
+ err:
+ OSSL_CMP_MSG_free(error);
+ OSSL_CMP_PKISI_free(si);
+ OSSL_CMP_MSG_free(PKIconf);
+ return res;
+}
+
+/*-
+ * Retrieve a copy of the certificate, if any, from the given CertResponse.
+ * Take into account PKIStatusInfo of CertResponse in ctx, report it on error.
+ * Returns NULL if not found or on error.
+ */
+static X509 *get1_cert_status(OSSL_CMP_CTX *ctx, int bodytype,
+ OSSL_CMP_CERTRESPONSE *crep)
+{
+ char buf[OSSL_CMP_PKISI_BUFLEN];
+ X509 *crt = NULL;
+ EVP_PKEY *privkey;
+
+ if (!ossl_assert(ctx != NULL && crep != NULL))
+ return NULL;
+
+ privkey = OSSL_CMP_CTX_get0_newPkey(ctx, 1);
+ switch (ossl_cmp_pkisi_get_status(crep->status)) {
+ case OSSL_CMP_PKISTATUS_waiting:
+ ossl_cmp_err(ctx,
+ "received \"waiting\" status for cert when actually aiming to extract cert");
+ CMPerr(0, CMP_R_ENCOUNTERED_WAITING);
+ goto err;
+ case OSSL_CMP_PKISTATUS_grantedWithMods:
+ ossl_cmp_warn(ctx, "received \"grantedWithMods\" for certificate");
+ crt = ossl_cmp_certresponse_get1_certificate(privkey, crep);
+ break;
+ case OSSL_CMP_PKISTATUS_accepted:
+ crt = ossl_cmp_certresponse_get1_certificate(privkey, crep);
+ break;
+ /* get all information in case of a rejection before going to error */
+ case OSSL_CMP_PKISTATUS_rejection:
+ ossl_cmp_err(ctx, "received \"rejection\" status rather than cert");
+ CMPerr(0, CMP_R_REQUEST_REJECTED_BY_SERVER);
+ goto err;
+ case OSSL_CMP_PKISTATUS_revocationWarning:
+ ossl_cmp_warn(ctx,
+ "received \"revocationWarning\" - a revocation of the cert is imminent");
+ crt = ossl_cmp_certresponse_get1_certificate(privkey, crep);
+ break;
+ case OSSL_CMP_PKISTATUS_revocationNotification:
+ ossl_cmp_warn(ctx,
+ "received \"revocationNotification\" - a revocation of the cert has occurred");
+ crt = ossl_cmp_certresponse_get1_certificate(privkey, crep);
+ break;
+ case OSSL_CMP_PKISTATUS_keyUpdateWarning:
+ if (bodytype != OSSL_CMP_PKIBODY_KUR) {
+ CMPerr(0, CMP_R_ENCOUNTERED_KEYUPDATEWARNING);
+ goto err;
+ }
+ crt = ossl_cmp_certresponse_get1_certificate(privkey, crep);
+ break;
+ default:
+ ossl_cmp_log1(ERROR, ctx,
+ "received unsupported PKIStatus %d for certificate",
+ ctx->status);
+ CMPerr(0, CMP_R_UNKNOWN_PKISTATUS);
+ goto err;
+ }
+ if (crt == NULL) /* according to PKIStatus, we can expect a cert */
+ CMPerr(0, CMP_R_CERTIFICATE_NOT_FOUND);
+
+ return crt;
+
+ err:
+ if (OSSL_CMP_CTX_snprint_PKIStatus(ctx, buf, sizeof(buf)) != NULL)
+ ERR_add_error_data(1, buf);
+ return NULL;
+}
+
+/*-
+ * Callback fn validating that the new certificate can be verified, using
+ * ctx->certConf_cb_arg, which has been initialized using opt_out_trusted, and
+ * ctx->untrusted_certs, which at this point already contains ctx->extraCertsIn.
+ * Returns 0 on acceptance, else a bit field reflecting PKIFailureInfo.
+ * Quoting from RFC 4210 section 5.1. Overall PKI Message:
+ * The extraCerts field can contain certificates that may be useful to
+ * the recipient. For example, this can be used by a CA or RA to
+ * present an end entity with certificates that it needs to verify its
+ * own new certificate (if, for example, the CA that issued the end
+ * entity's certificate is not a root CA for the end entity). Note that
+ * this field does not necessarily contain a certification path; the
+ * recipient may have to sort, select from, or otherwise process the
+ * extra certificates in order to use them.
+ * Note: While often handy, there is no hard requirement by CMP that
+ * an EE must be able to validate the certificates it gets enrolled.
+ */
+int OSSL_CMP_certConf_cb(OSSL_CMP_CTX *ctx, X509 *cert, int fail_info,
+ const char **text)
+{
+ X509_STORE *out_trusted = OSSL_CMP_CTX_get_certConf_cb_arg(ctx);
+ (void)text; /* make (artificial) use of var to prevent compiler warning */
+
+ if (fail_info != 0) /* accept any error flagged by CMP core library */
+ return fail_info;
+
+ if (out_trusted != NULL
+ && !OSSL_CMP_validate_cert_path(ctx, out_trusted, cert))
+ fail_info = 1 << OSSL_CMP_PKIFAILUREINFO_incorrectData;
+
+ return fail_info;
+}
+
+/*-
+ * Perform the generic handling of certificate responses for IR/CR/KUR/P10CR.
+ * Returns -1 on receiving pollRep if sleep == 0, setting the checkAfter value.
+ * Returns 1 on success and provides the received PKIMESSAGE in *resp.
+ * Returns 0 on error (which includes the case that timeout has been reached).
+ * Regardless of success, caller is responsible for freeing *resp (unless NULL).
+ */
+static int cert_response(OSSL_CMP_CTX *ctx, int sleep, int rid,
+ OSSL_CMP_MSG **resp, int *checkAfter,
+ int req_type, int expected_type)
+{
+ EVP_PKEY *rkey = OSSL_CMP_CTX_get0_newPkey(ctx /* may be NULL */, 0);
+ int fail_info = 0; /* no failure */
+ const char *txt = NULL;
+ OSSL_CMP_CERTREPMESSAGE *crepmsg;
+ OSSL_CMP_CERTRESPONSE *crep;
+ X509 *cert;
+ char *subj = NULL;
+ int ret = 1;
+
+ retry:
+ crepmsg = (*resp)->body->value.ip; /* same for cp and kup */
+ if (sk_OSSL_CMP_CERTRESPONSE_num(crepmsg->response) > 1) {
+ CMPerr(0, CMP_R_MULTIPLE_RESPONSES_NOT_SUPPORTED);
+ return 0;
+ }
+ /* TODO: handle potentially multiple CertResponses in CertRepMsg */
+ crep = ossl_cmp_certrepmessage_get0_certresponse(crepmsg, rid);
+ if (crep == NULL)
+ return 0;
+ if (!save_statusInfo(ctx, crep->status))
+ return 0;
+ if (rid == -1) {
+ /* for OSSL_CMP_PKIBODY_P10CR learn CertReqId from response */
+ rid = ossl_cmp_asn1_get_int(crep->certReqId);
+ if (rid == -1) {
+ CMPerr(0, CMP_R_BAD_REQUEST_ID);
+ return 0;
+ }
+ }
+
+ if (ossl_cmp_pkisi_get_status(crep->status) == OSSL_CMP_PKISTATUS_waiting) {
+ OSSL_CMP_MSG_free(*resp);
+ *resp = NULL;
+ if ((ret = poll_for_response(ctx, sleep, rid, resp, checkAfter)) != 0) {
+ if (ret == -1) /* at this point implies sleep == 0 */
+ return ret; /* waiting */
+ goto retry; /* got ip/cp/kup, which may still indicate 'waiting' */
+ } else {
+ CMPerr(0, CMP_R_POLLING_FAILED);
+ return 0;
+ }
+ }
+
+ cert = get1_cert_status(ctx, (*resp)->body->type, crep);
+ if (cert == NULL) {
+ ERR_add_error_data(1, "; cannot extract certificate from response");
+ return 0;
+ }
+ if (!ossl_cmp_ctx_set0_newCert(ctx, cert))
+ return 0;
+
+ /*
+ * if the CMP server returned certificates in the caPubs field, copy them
+ * to the context so that they can be retrieved if necessary
+ */
+ if (crepmsg->caPubs != NULL
+ && !ossl_cmp_ctx_set1_caPubs(ctx, crepmsg->caPubs))
+ return 0;
+
+ /* copy received extraCerts to ctx->extraCertsIn so they can be retrieved */
+ if (!ossl_cmp_ctx_set1_extraCertsIn(ctx, (*resp)->extraCerts))
+ return 0;
+
+ subj = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
+ if (rkey != NULL
+ /* X509_check_private_key() also works if rkey is just public key */
+ && !(X509_check_private_key(ctx->newCert, rkey))) {
+ fail_info = 1 << OSSL_CMP_PKIFAILUREINFO_incorrectData;
+ txt = "public key in new certificate does not match our enrollment key";
+ /*-
+ * not callling (void)ossl_cmp_exchange_error(ctx,
+ * OSSL_CMP_PKISTATUS_rejection, fail_info, txt)
+ * not throwing CMP_R_CERTIFICATE_NOT_ACCEPTED with txt
+ * not returning 0
+ * since we better leave this for any ctx->certConf_cb to decide
+ */
+ }
+
+ /*
+ * Execute the certification checking callback function possibly set in ctx,
+ * which can determine whether to accept a newly enrolled certificate.
+ * It may overrule the pre-decision reflected in 'fail_info' and '*txt'.
+ */
+ if (ctx->certConf_cb
+ && (fail_info = ctx->certConf_cb(ctx, ctx->newCert,
+ fail_info, &txt)) != 0) {
+ if (txt == NULL)
+ txt = "CMP client application did not accept it";
+ }
+ if (fail_info != 0) /* immediately log error before any certConf exchange */
+ ossl_cmp_log1(ERROR, ctx,
+ "rejecting newly enrolled cert with subject: %s", subj);
+
+ /*
+ * TODO: better move certConf exchange to do_certreq_seq() such that
+ * also more low-level errors with CertReqMessages get reported to server
+ */
+ if (!ctx->disableConfirm
+ && !ossl_cmp_hdr_has_implicitConfirm((*resp)->header)) {
+ if (!ossl_cmp_exchange_certConf(ctx, fail_info, txt))
+ ret = 0;
+ }
+
+ /* not throwing failure earlier as transfer_cb may call ERR_clear_error() */
+ if (fail_info != 0) {
+ CMPerr(0, CMP_R_CERTIFICATE_NOT_ACCEPTED);
+ ERR_add_error_data(2, "rejecting newly enrolled cert with subject: ",
+ subj);
+ if (txt != NULL)
+