summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/cmp.c40
-rw-r--r--apps/include/http_server.h15
-rw-r--r--apps/lib/http_server.c100
-rw-r--r--apps/ocsp.c13
-rw-r--r--crypto/cmp/cmp_ctx.c17
-rw-r--r--crypto/cmp/cmp_http.c38
-rw-r--r--crypto/cmp/cmp_local.h2
-rw-r--r--crypto/err/openssl.txt2
-rw-r--r--crypto/http/http_client.c470
-rw-r--r--crypto/http/http_err.c4
-rw-r--r--crypto/ocsp/ocsp_http.c20
-rw-r--r--doc/man3/OSSL_CMP_CTX_new.pod13
-rw-r--r--doc/man3/OSSL_CMP_SRV_CTX_new.pod4
-rw-r--r--doc/man3/OSSL_HTTP_REQ_CTX.pod13
-rw-r--r--doc/man3/OSSL_HTTP_transfer.pod34
-rw-r--r--include/openssl/http.h11
-rw-r--r--test/http_test.c183
-rw-r--r--test/recipes/80-test_cmp_http_data/test_connection.csv92
18 files changed, 706 insertions, 365 deletions
diff --git a/apps/cmp.c b/apps/cmp.c
index d0d18c69ca..303eff10c0 100644
--- a/apps/cmp.c
+++ b/apps/cmp.c
@@ -72,6 +72,7 @@ static char *opt_path = NULL;
static char *opt_proxy = NULL;
static char *opt_no_proxy = NULL;
static char *opt_recipient = NULL;
+static int opt_keep_alive = 1;
static int opt_msg_timeout = -1;
static int opt_total_timeout = -1;
@@ -205,7 +206,7 @@ typedef enum OPTION_choice {
OPT_SERVER, OPT_PATH, OPT_PROXY, OPT_NO_PROXY,
OPT_RECIPIENT,
- OPT_MSG_TIMEOUT, OPT_TOTAL_TIMEOUT,
+ OPT_KEEP_ALIVE, OPT_MSG_TIMEOUT, OPT_TOTAL_TIMEOUT,
OPT_TRUSTED, OPT_UNTRUSTED, OPT_SRVCERT,
OPT_EXPECT_SENDER,
@@ -344,8 +345,10 @@ const OPTIONS cmp_options[] = {
"Default from environment variable 'no_proxy', else 'NO_PROXY', else none"},
{"recipient", OPT_RECIPIENT, 's',
"DN of CA. Default: subject of -srvcert, -issuer, issuer of -oldcert or -cert"},
+ {"keep_alive", OPT_KEEP_ALIVE, 'N',
+ "Persistent HTTP connections. 0: no, 1 (the default): request, 2: require"},
{"msg_timeout", OPT_MSG_TIMEOUT, 'N',
- "Timeout per CMP message round trip (or 0 for none). Default 120 seconds"},
+ "Number of seconds allowed per CMP message round trip, or 0 for infinite"},
{"total_timeout", OPT_TOTAL_TIMEOUT, 'N',
"Overall time an enrollment incl. polling may take. Default 0 = infinite"},
@@ -530,7 +533,7 @@ static varref cmp_vars[] = { /* must be in same order as enumerated above! */
{&opt_oldcert}, {(char **)&opt_revreason},
{&opt_server}, {&opt_path}, {&opt_proxy}, {&opt_no_proxy},
- {&opt_recipient},
+ {&opt_recipient}, {(char **)&opt_keep_alive},
{(char **)&opt_msg_timeout}, {(char **)&opt_total_timeout},
{&opt_trusted}, {&opt_untrusted}, {&opt_srvcert},
@@ -1817,6 +1820,15 @@ static int setup_client_ctx(OSSL_CMP_CTX *ctx, ENGINE *engine)
if (!setup_verification_ctx(ctx))
goto err;
+ if (opt_keep_alive != 1)
+ (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_KEEP_ALIVE,
+ opt_keep_alive);
+ if (opt_total_timeout > 0 && opt_msg_timeout > 0
+ && opt_total_timeout < opt_msg_timeout) {
+ CMP_err2("-total_timeout argument = %d must not be < %d (-msg_timeout)",
+ opt_total_timeout, opt_msg_timeout);
+ goto err;
+ }
if (opt_msg_timeout >= 0) /* must do this before setup_ssl_ctx() */
(void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_MSG_TIMEOUT,
opt_msg_timeout);
@@ -2232,6 +2244,13 @@ static int get_opts(int argc, char **argv)
case OPT_RECIPIENT:
opt_recipient = opt_str();
break;
+ case OPT_KEEP_ALIVE:
+ opt_keep_alive = opt_int_arg();
+ if (opt_keep_alive > 2) {
+ CMP_err("-keep_alive argument must be 0, 1, or 2");
+ goto opthelp;
+ }
+ break;
case OPT_MSG_TIMEOUT:
opt_msg_timeout = opt_int_arg();
break;
@@ -2668,6 +2687,7 @@ int cmp_main(int argc, char **argv)
#else
BIO *acbio;
BIO *cbio = NULL;
+ int keep_alive = 0;
int msgs = 0;
int retry = 1;
@@ -2680,7 +2700,8 @@ int cmp_main(int argc, char **argv)
ret = http_server_get_asn1_req(ASN1_ITEM_rptr(OSSL_CMP_MSG),
(ASN1_VALUE **)&req, &path,
- &cbio, acbio, prog, 0, 0);
+ &cbio, acbio, &keep_alive,
+ prog, opt_port, 0, 0);
if (ret == 0) { /* no request yet */
if (retry) {
sleep(1);
@@ -2712,7 +2733,8 @@ int cmp_main(int argc, char **argv)
500, "Internal Server Error");
break; /* treated as fatal error */
}
- ret = http_server_send_asn1_resp(cbio, "application/pkixcmp",
+ ret = http_server_send_asn1_resp(cbio, keep_alive,
+ "application/pkixcmp",
ASN1_ITEM_rptr(OSSL_CMP_MSG),
(const ASN1_VALUE *)resp);
OSSL_CMP_MSG_free(resp);
@@ -2724,8 +2746,12 @@ int cmp_main(int argc, char **argv)
(void)OSSL_CMP_CTX_set1_transactionID(srv_cmp_ctx, NULL);
(void)OSSL_CMP_CTX_set1_senderNonce(srv_cmp_ctx, NULL);
}
- BIO_free_all(cbio);
- cbio = NULL;
+ if (!ret || !keep_alive
+ || OSSL_CMP_CTX_get_status(srv_cmp_ctx) == -1
+ /* transaction closed by OSSL_CMP_CTX_server_perform() */) {
+ BIO_free_all(cbio);
+ cbio = NULL;
+ }
}
BIO_free_all(cbio);
BIO_free_all(acbio);
diff --git a/apps/include/http_server.h b/apps/include/http_server.h
index 9520eb4dac..ed3f597fbd 100644
--- a/apps/include/http_server.h
+++ b/apps/include/http_server.h
@@ -67,10 +67,12 @@ BIO *http_server_init_bio(const char *prog, const char *port);
* Accept an ASN.1-formatted HTTP request
* it: the expected request ASN.1 type
* preq: pointer to variable where to place the parsed request
- * pcbio: pointer to variable where to place the BIO for sending the response to
* ppath: pointer to variable where to place the request path, or NULL
+ * pcbio: pointer to variable where to place the BIO for sending the response to
* acbio: the listening bio (typically as returned by http_server_init_bio())
- * prog: the name of the current app
+ * found_keep_alive: for returning flag if client requests persistent connection
+ * prog: the name of the current app, for diagnostics only
+ * port: the local port listening to, for diagnostics only
* accept_get: whether to accept GET requests (in addition to POST requests)
* timeout: connection timeout (in seconds), or 0 for none/infinite
* returns 0 in case caller should retry, then *preq == *ppath == *pcbio == NULL
@@ -83,19 +85,22 @@ BIO *http_server_init_bio(const char *prog, const char *port);
*/
int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
char **ppath, BIO **pcbio, BIO *acbio,
- const char *prog, int accept_get, int timeout);
+ int *found_keep_alive,
+ const char *prog, const char *port,
+ int accept_get, int timeout);
/*-
* Send an ASN.1-formatted HTTP response
* cbio: destination BIO (typically as returned by http_server_get_asn1_req())
* note: cbio should not do an encoding that changes the output length
+ * keep_alive: grant persistent connnection
* content_type: string identifying the type of the response
* it: the response ASN.1 type
- * valit: the response ASN.1 type
* resp: the response to send
* returns 1 on success, 0 on failure
*/
-int http_server_send_asn1_resp(BIO *cbio, const char *content_type,
+int http_server_send_asn1_resp(BIO *cbio, int keep_alive,
+ const char *content_type,
const ASN1_ITEM *it, const ASN1_VALUE *resp);
/*-
diff --git a/apps/lib/http_server.c b/apps/lib/http_server.c
index 0bdc4bc316..691e5c9056 100644
--- a/apps/lib/http_server.c
+++ b/apps/lib/http_server.c
@@ -31,7 +31,14 @@
#endif
static int verbosity = LOG_INFO;
+
+#define HTTP_PREFIX "HTTP/"
+#define HTTP_VERSION_PATT "1." /* allow 1.x */
+#define HTTP_PREFIX_VERSION HTTP_PREFIX""HTTP_VERSION_PATT
+#define HTTP_1_0 HTTP_PREFIX_VERSION"0" /* "HTTP/1.0" */
+
#ifdef HTTP_DAEMON
+
int multi = 0; /* run multiple responder processes */
int acfd = (int) INVALID_SOCKET;
@@ -262,11 +269,15 @@ static int urldecode(char *p)
return (int)(out - save);
}
+/* if *pcbio != NULL, continue given connected session, else accept new */
+/* if found_keep_alive != NULL, return this way connection persistence state */
int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
char **ppath, BIO **pcbio, BIO *acbio,
- const char *prog, int accept_get, int timeout)
+ int *found_keep_alive,
+ const char *prog, const char *port,
+ int accept_get, int timeout)
{
- BIO *cbio = NULL, *getbio = NULL, *b64 = NULL;
+ BIO *cbio = *pcbio, *getbio = NULL, *b64 = NULL;
int len;
char reqbuf[2048], inbuf[2048];
char *meth, *url, *end;
@@ -276,14 +287,18 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
*preq = NULL;
if (ppath != NULL)
*ppath = NULL;
- *pcbio = NULL;
- log_message(prog, LOG_DEBUG, "Awaiting next request...");
-/* Connection loss before accept() is routine, ignore silently */
- if (BIO_do_accept(acbio) <= 0)
- return ret;
+ if (cbio == NULL) {
+ log_message(prog, LOG_DEBUG,
+ "Awaiting new connection on port %s...", port);
+ if (BIO_do_accept(acbio) <= 0)
+ /* Connection loss before accept() is routine, ignore silently */
+ return ret;
- *pcbio = cbio = BIO_pop(acbio);
+ *pcbio = cbio = BIO_pop(acbio);
+ } else {
+ log_message(prog, LOG_DEBUG, "Awaiting next request...");
+ }
if (cbio == NULL) {
/* Cannot call http_server_send_status(cbio, ...) */
ret = -1;
@@ -316,6 +331,9 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
url = meth + 3;
if ((accept_get && strncmp(meth, "GET ", 4) == 0)
|| (url++, strncmp(meth, "POST ", 5) == 0)) {
+ static const char http_version_str[] = " "HTTP_PREFIX_VERSION;
+ static const size_t http_version_str_len = sizeof(http_version_str) - 1;
+
/* Expecting (GET|POST) {sp} /URL {sp} HTTP/1.x */
*(url++) = '\0';
while (*url == ' ')
@@ -333,7 +351,7 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
for (end = url; *end != '\0'; end++)
if (*end == ' ')
break;
- if (strncmp(end, " HTTP/1.", 7) != 0) {
+ if (strncmp(end, http_version_str, http_version_str_len) != 0) {
log_message(prog, LOG_WARNING,
"Invalid %s -- bad HTTP/version string: %s",
meth, end + 1);
@@ -341,6 +359,9 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
goto out;
}
*end = '\0';
+ /* above HTTP 1.0, connection persistence is the default */
+ if (found_keep_alive != NULL)
+ *found_keep_alive = end[http_version_str_len] > '0';
/*-
* Skip "GET / HTTP..." requests often used by load-balancers.
@@ -373,7 +394,8 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
}
} else {
log_message(prog, LOG_WARNING,
- "HTTP request does not start with GET/POST: %s", reqbuf);
+ "HTTP request does not begin with %sPOST: %s",
+ accept_get ? "GET or " : "", reqbuf);
/* TODO provide better diagnosis in case client tries TLS */
(void)http_server_send_status(cbio, 400, "Bad Request");
goto out;
@@ -388,15 +410,50 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
/* Read and skip past the headers. */
for (;;) {
+ char *key, *value, *line_end = NULL;
+
len = BIO_gets(cbio, inbuf, sizeof(inbuf));
if (len <= 0) {
- log_message(prog, LOG_WARNING,
- "Error skipping remaining HTTP headers");
+ log_message(prog, LOG_WARNING, "Error reading HTTP header");
(void)http_server_send_status(cbio, 400, "Bad Request");
goto out;
}
- if ((inbuf[0] == '\r') || (inbuf[0] == '\n'))
+
+ if (inbuf[0] == '\r' || inbuf[0] == '\n')
break;
+
+ key = inbuf;
+ value = strchr(key, ':');
+ if (value != NULL) {
+ *(value++) = '\0';
+ while (*value == ' ')
+ value++;
+ line_end = strchr(value, '\r');
+ if (line_end == NULL)
+ line_end = strchr(value, '\n');
+ if (line_end != NULL)
+ *line_end = '\0';
+ } else {
+ log_message(prog, LOG_WARNING,
+ "Error parsing HTTP header: missing ':'");
+ (void)http_server_send_status(cbio, 400, "Bad Request");
+ goto out;
+ }
+ if (value != NULL && line_end != NULL) {
+ /* https://tools.ietf.org/html/rfc7230#section-6.3 Persistence */
+ if (found_keep_alive != NULL && strcasecmp(key, "Connection") == 0) {
+ if (strcasecmp(value, "keep-alive") == 0)
+ *found_keep_alive = 1;
+ if (strcasecmp(value, "close") == 0)
+ *found_keep_alive = 0;
+ }
+ } else {
+ log_message(prog, LOG_WARNING,
+ "Error parsing HTTP header: missing end of line");
+ (void)http_server_send_status(cbio, 400, "Bad Request");
+ goto out;
+ }
+
}
# ifdef HTTP_DAEMON
@@ -408,7 +465,8 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
/* Try to read and parse request */
req = ASN1_item_d2i_bio(it, getbio != NULL ? getbio : cbio, NULL);
if (req == NULL) {
- log_message(prog, LOG_WARNING, "Error parsing DER-encoded request content");
+ log_message(prog, LOG_WARNING,
+ "Error parsing DER-encoded request content");
(void)http_server_send_status(cbio, 400, "Bad Request");
} else if (ppath != NULL && (*ppath = OPENSSL_strdup(url)) == NULL) {
log_message(prog, LOG_ERR,
@@ -441,11 +499,15 @@ int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq,
}
/* assumes that cbio does not do an encoding that changes the output length */
-int http_server_send_asn1_resp(BIO *cbio, const char *content_type,
+int http_server_send_asn1_resp(BIO *cbio, int keep_alive,
+ const char *content_type,
const ASN1_ITEM *it, const ASN1_VALUE *resp)
{
- int ret = BIO_printf(cbio, "HTTP/1.0 200 OK\r\nContent-type: %s\r\n"
- "Content-Length: %d\r\n\r\n", content_type,
+ int ret = BIO_printf(cbio, HTTP_1_0" 200 OK\r\n%s"
+ "Content-type: %s\r\n"
+ "Content-Length: %d\r\n\r\n",
+ keep_alive ? "Connection: keep-alive\r\n" : "",
+ content_type,
ASN1_item_i2d(resp, NULL, it)) > 0
&& ASN1_item_i2d_bio(it, cbio, resp) > 0;
@@ -455,7 +517,9 @@ int http_server_send_asn1_resp(BIO *cbio, const char *content_type,
int http_server_send_status(BIO *cbio, int status, const char *reason)
{
- int ret = BIO_printf(cbio, "HTTP/1.0 %d %s\r\n\r\n", status, reason) > 0;
+ int ret = BIO_printf(cbio, HTTP_1_0" %d %s\r\n\r\n",
+ /* This implicitly cancels keep-alive */
+ status, reason) > 0;
(void)BIO_flush(cbio);
return ret;
diff --git a/apps/ocsp.c b/apps/ocsp.c
index 355b4127c8..694855fe09 100644
--- a/apps/ocsp.c
+++ b/apps/ocsp.c
@@ -76,7 +76,7 @@ static void make_ocsp_response(BIO *err, OCSP_RESPONSE **resp, OCSP_REQUEST *req
static char **lookup_serial(CA_DB *db, ASN1_INTEGER *ser);
static int do_responder(OCSP_REQUEST **preq, BIO **pcbio, BIO *acbio,
- int timeout);
+ const char *port, int timeout);
static int send_ocsp_response(BIO *cbio, const OCSP_RESPONSE *resp);
static char *prog;
@@ -631,7 +631,7 @@ redo_accept:
#endif
req = NULL;
- res = do_responder(&req, &cbio, acbio, req_timeout);
+ res = do_responder(&req, &cbio, acbio, port, req_timeout);
if (res == 0)
goto redo_accept;
@@ -1162,12 +1162,13 @@ static char **lookup_serial(CA_DB *db, ASN1_INTEGER *ser)
}
static int do_responder(OCSP_REQUEST **preq, BIO **pcbio, BIO *acbio,
- int timeout)
+ const char *port, int timeout)
{
#ifndef OPENSSL_NO_SOCK
return http_server_get_asn1_req(ASN1_ITEM_rptr(OCSP_REQUEST),
(ASN1_VALUE **)preq, NULL, pcbio, acbio,
- prog, 1 /* accept_get */, timeout);
+ NULL /* found_keep_alive */,
+ prog, port, 1 /* accept_get */, timeout);
#else
BIO_printf(bio_err,
"Error getting OCSP request - sockets not supported\n");
@@ -1179,7 +1180,9 @@ static int do_responder(OCSP_REQUEST **preq, BIO **pcbio, BIO *acbio,
static int send_ocsp_response(BIO *cbio, const OCSP_RESPONSE *resp)
{
#ifndef OPENSSL_NO_SOCK
- return http_server_send_asn1_resp(cbio, "application/ocsp-response",
+ return http_server_send_asn1_resp(cbio,
+ 0 /* no keep-alive */,
+ "application/ocsp-response",
ASN1_ITEM_rptr(OCSP_RESPONSE),
(const ASN1_VALUE *)resp);
#else
diff --git a/crypto/cmp/cmp_ctx.c b/crypto/cmp/cmp_ctx.c
index 7e7af63b4a..a09432597b 100644
--- a/crypto/cmp/cmp_ctx.c
+++ b/crypto/cmp/cmp_ctx.c
@@ -115,7 +115,8 @@ OSSL_CMP_CTX *OSSL_CMP_CTX_new(OSSL_LIB_CTX *libctx, const char *propq)
ctx->status = -1;
ctx->failInfoCode = -1;
- ctx->msg_timeout = 2 * 60;
+ ctx->keep_alive = 1;
+ ctx->msg_timeout = -1;
if ((ctx->untrusted = sk_X509_new_null()) == NULL)
goto oom;
@@ -149,6 +150,11 @@ int OSSL_CMP_CTX_reinit(OSSL_CMP_CTX *ctx)
return 0;
}
+ if (ctx->http_ctx != NULL) {
+ (void)OSSL_HTTP_close(ctx->http_ctx, 1);
+ ossl_cmp_debug(ctx, "disconnected from CMP server");
+ ctx->http_ctx = NULL;
+ }
ctx->status = -1;
ctx->failInfoCode = -1;
@@ -169,6 +175,10 @@ void OSSL_CMP_CTX_free(OSSL_CMP_CTX *ctx)
if (ctx == NULL)
return;
+ if (ctx->http_ctx != NULL) {
+ (void)OSSL_HTTP_close(ctx->http_ctx, 1);
+ ossl_cmp_debug(ctx, "disconnected from CMP server");
+ }
OPENSSL_free(ctx->serverPath);
OPENSSL_free(ctx->server);
OPENSSL_free(ctx->proxy);
@@ -1041,6 +1051,9 @@ int OSSL_CMP_CTX_set_option(OSSL_CMP_CTX *ctx, int opt, int val)
case OSSL_CMP_OPT_MAC_ALGNID:
ctx->pbm_mac = val;
break;
+ case OSSL_CMP_OPT_KEEP_ALIVE:
+ ctx->keep_alive = val;
+ break;
case OSSL_CMP_OPT_MSG_TIMEOUT:
ctx->msg_timeout = val;
break;
@@ -1105,6 +1118,8 @@ int OSSL_CMP_CTX_get_option(const OSSL_CMP_CTX *ctx, int opt)
return EVP_MD_type(ctx->pbm_owf);
case OSSL_CMP_OPT_MAC_ALGNID:
return ctx->pbm_mac;
+ case OSSL_CMP_OPT_KEEP_ALIVE:
+ return ctx->keep_alive;
case OSSL_CMP_OPT_MSG_TIMEOUT:
return ctx->msg_timeout;
case OSSL_CMP_OPT_TOTAL_TIMEOUT:
diff --git a/crypto/cmp/cmp_http.c b/crypto/cmp/cmp_http.c
index a358622feb..600955efce 100644
--- a/crypto/cmp/cmp_http.c
+++ b/crypto/cmp/cmp_http.c
@@ -28,6 +28,19 @@
#include <openssl/cmp.h>
#include <openssl/err.h>
+static int keep_alive(int keep_alive, int body_type)
+{
+ if (keep_alive != 0
+ /* Ask for persistent connection only if may need more round trips */
+ && body_type != OSSL_CMP_PKIBODY_IR
+ && body_type != OSSL_CMP_PKIBODY_CR
+ && body_type != OSSL_CMP_PKIBODY_P10CR
+ && body_type != OSSL_CMP_PKIBODY_KUR
+ && body_type != OSSL_CMP_PKIBODY_POLLREQ)
+ keep_alive = 0;
+ return keep_alive;
+}
+
/*
* Send the PKIMessage req and on success return the response, else NULL.
* Any previous error queue entries will likely be removed by ERR_clear_error().
@@ -55,11 +68,12 @@ OSSL_CMP_MSG *OSSL_CMP_MSG_http_perform(OSSL_CMP_CTX *ctx,
if (ctx->serverPort != 0)
BIO_snprintf(server_port, sizeof(server_port), "%d", ctx->serverPort);
-
tls_used = OSSL_CMP_CTX_get_http_cb_arg(ctx) != NULL;
- ossl_cmp_log2(DEBUG, ctx, "connecting to CMP server %s%s",
- ctx->server, tls_used ? " using TLS" : "");
- rsp = OSSL_HTTP_transfer(NULL, ctx->server, server_port,
+ if (ctx->http_ctx == NULL)
+ ossl_cmp_log3(DEBUG, ctx, "connecting to CMP server %s:%s%s",
+ ctx->server, server_port, tls_used ? " using TLS" : "");
+
+ rsp = OSSL_HTTP_transfer(&ctx->http_ctx, ctx->server, server_port,
ctx->serverPath, tls_used,
ctx->proxy, ctx->no_proxy,
NULL /* bio */, NULL /* rbio */,
@@ -67,12 +81,22 @@ OSSL_CMP_MSG *OSSL_CMP_MSG_http_perform(OSSL_CMP_CTX *ctx,
0 /* buf_size */, headers,
content_type_pkix, req_mem,
content_type_pkix, 1 /* expect_asn1 */,
- HTTP_DEFAULT_MAX_RESP_LEN,
- ctx->msg_timeout, 0 /* keep_alive */);
+ OSSL_HTTP_DEFAULT_MAX_RESP_LEN,
+ ctx->msg_timeout,
+ keep_alive(ctx->keep_alive, req->body->type));
BIO_free(req_mem);
res = (OSSL_CMP_MSG *)ASN1_item_d2i_bio(it, rsp, NULL);
BIO_free(rsp);
- ossl_cmp_debug(ctx, "disconnected from CMP server");
+
+ if (ctx->http_ctx == NULL)
+ ossl_cmp_debug(ctx, "disconnected from CMP server");
+ /*
+ * Note that on normal successful end of the transaction the connection
+ * is not closed at this level, but this will be done by the CMP client
+ * application via OSSL_CMP_CTX_free() or OSSL_CMP_CTX_reinit().
+ */
+ if (res != NULL)
+ ossl_cmp_debug(ctx, "finished reading response from CMP server");
err:
sk_CONF_VALUE_pop_free(headers, X509V3_conf_free);
return res;
diff --git a/crypto/cmp/cmp_local.h b/crypto/cmp/cmp_local.h
index b2a3382079..eee609937b 100644
--- a/crypto/cmp/cmp_local.h
+++ b/crypto/cmp/cmp_local.h
@@ -40,11 +40,13 @@ struct ossl_cmp_ctx_st {
OSSL_CMP_transfer_cb_t transfer_cb; /* default: OSSL_CMP_MSG_http_perform */
void *transfer_cb_arg; /* allows to store optional argument to cb */
/* HTTP-based transfer */
+ OSSL_HTTP_REQ_CTX *http_ctx;
char *serverPath;
char *server;
int serverPort;
char *proxy;
char *no_proxy;
+ int keep_alive; /* persistent connection: 0=no, 1=prefer, 2=require */
int msg_timeout; /* max seconds to wait for each CMP message round trip */
int total_timeout; /* max number of seconds an enrollment may take, incl. */
/* attempts polling for a response if a 'waiting' PKIStatus is received */
diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt
index 9ad6757857..0bbdd886ce 100644
--- a/crypto/err/openssl.txt
+++ b/crypto/err/openssl.txt
@@ -760,6 +760,7 @@ HTTP_R_ERROR_PARSING_URL:101:error parsing url
HTTP_R_ERROR_RECEIVING:103:error receiving
HTTP_R_ERROR_SENDING:102:error sending
HTTP_R_FAILED_READING_DATA:128:failed reading data
+HTTP_R_HEADER_PARSE_ERROR:126:header parse error
HTTP_R_INCONSISTENT_CONTENT_LENGTH:120:inconsistent content length
HTTP_R_INVALID_PORT_NUMBER:123:invalid port number
HTTP_R_INVALID_URL_PATH:125:invalid url path
@@ -774,6 +775,7 @@ HTTP_R_REDIRECTION_FROM_HTTPS_TO_HTTP:112:redirection from https to http
HTTP_R_REDIRECTION_NOT_ENABLED:116:redirection not enabled
HTTP_R_RESPONSE_LINE_TOO_LONG:113:response line too long
HTTP_R_RESPONSE_PARSE_ERROR:104:response parse error
+HTTP_R_SERVER_CANCELED_CONNECTION:127:server canceled connection
HTTP_R_SOCK_NOT_SUPPORTED:122:sock not supported
HTTP_R_STATUS_CODE_UNSUPPORTED:114:status code unsupported
HTTP_R_TLS_NOT_ENABLED:107:tls not enabled
diff --git a/crypto/http/http_client.c b/crypto/http/http_client.c
index 9f41a31bf7..f46cc2714f 100644
--- a/crypto/http/http_client.c
+++ b/crypto/http/http_client.c
@@ -27,15 +27,17 @@
#define HTTP_PREFIX "HTTP/"
#define HTTP_VERSION_PATT "1." /* allow 1.x */
-#define HTTP_VERSION_STR_LEN 3
-#define HTTP_LINE1_MINLEN ((int)strlen(HTTP_PREFIX HTTP_VERSION_PATT "x 200\n"))
+#define HTTP_PREFIX_VERSION HTTP_PREFIX""HTTP_VERSION_PATT
+#define HTTP_1_0 HTTP_PREFIX_VERSION"0" /* "HTTP/1.0" */
+#define HTTP_VERSION_PATT_LEN strlen(HTTP_PREFIX_VERSION)
+#define HTTP_VERSION_STR_LEN (HTTP_VERSION_PATT_LEN + 1)
+#define HTTP_LINE1_MINLEN ((int)strlen(HTTP_PREFIX_VERSION "x 200\n"))
#define HTTP_VERSION_MAX_REDIRECTIONS 50
#define HTTP_STATUS_CODE_OK 200
#define HTTP_STATUS_CODE_MOVED_PERMANENTLY 301
#define HTTP_STATUS_CODE_FOUND 302
-
/* Stateful HTTP request code, supporting blocking and non-blocking I/O */
/* Opaque HTTP request status structure */
@@ -44,19 +46,26 @@ struct ossl_http_req_ctx_st {
int state; /* Current I/O state */
unsigned char *readbuf; /* Buffer for reading response by line */
int readbuflen; /* Buffer length, equals buf_size */
- BIO *wbio; /* BIO to send request to */
- BIO *rbio; /* BIO to read response from */
- BIO *mem; /* Memory BIO response is built into */
- int method_POST; /* HTTP method is "POST" (else "GET") */
- char *expected_ct; /* expected Content-Type, or NULL */
- int expect_asn1; /* response must be ASN.1-encoded */
- long len_to_send; /* number of bytes in request still to send */
- unsigned long resp_len; /* length of response */
+ int free_wbio; /* wbio allocated internally, free with ctx */
+ BIO *wbio; /* BIO to write/send request to */
+ BIO *rbio; /* BIO to read/receive response from */
+ OSSL_HTTP_bio_cb_t upd_fn; /* Optional BIO update callback used for TLS */
+ void *upd_arg; /* Optional arg for update callback function */
+ int use_ssl; /* Use HTTPS */
+ char *proxy; /* Optional proxy name or URI */
+ char *server; /* Optional server host name */
+ char *port; /* Optional server port */
+ BIO *mem; /* Memory BIO holding request and response */
+ int method_POST; /* HTTP method is POST (else GET) */
+ char *expected_ct; /* Optional expected Content-Type */
+ int expect_asn1; /* Response must be ASN.1-encoded */
+ long len_to_send; /* Number of bytes in request still to send */
+ unsigned long resp_len; /* Length of response */
size_t max_resp_len; /* Maximum length of response */
int keep_alive; /* Persistent conn. 0=no, 1=prefer, 2=require */
time_t max_time; /* Maximum end time of current transfer, or 0 */
time_t max_total_time; /* Maximum end time of total transfer, or 0 */
- char *redirection_url; /* Location given with HTTP status 301/302 */
+ char *redirection_url; /* Location obtained from HTTP status 301/302 */
};
/* HTTP states */
@@ -74,6 +83,8 @@ struct ossl_http_req_ctx_st {
#define OHS_DONE (8 | OHS_NOREAD) /* Completed */
#define OHS_HTTP_HEADER (9 | OHS_NOREAD) /* Headers set, w/o final \r\n */
+/* Low-level HTTP API implementation */
+
OSSL_HTTP_REQ_CTX *OSSL_HTTP_REQ_CTX_new(BIO *wbio, BIO *rbio, int buf_size)
{
OSSL_HTTP_REQ_CTX *rctx;
@@ -94,7 +105,6 @@ OSSL_HTTP_REQ_CTX *OSSL_HTTP_REQ_CTX_new(BIO *wbio, BIO *rbio, int buf_size)
OPENSSL_free(rctx);
return NULL;
}
- rctx->resp_len = 0;
rctx->max_resp_len = HTTP_DEFAULT_MAX_RESP_LEN;
/* everything else is 0, e.g. rctx->len_to_send, or NULL, e.g. rctx->mem */
return rctx;
@@ -104,8 +114,19 @@ void OSSL_HTTP_REQ_CTX_free(OSSL_HTTP_REQ_CTX *rctx)
{
if (rctx == NULL)
return;
+ /*
+ * Use BIO_free_all() because bio_update_fn may prepend or append to cbio.
+ * This also frees any (e.g., SSL/TLS) BIOs linked with bio and,
+ * like BIO_reset(bio), calls SSL_shutdown() to notify/alert the peer.
+ */
+ if (rctx->free_wbio)
+ BIO_free_all(rctx->wbio);
+ /* do not free rctx->rbio */
BIO_free(rctx->mem); /* this may indirectly call ERR_clear_error() */
OPENSSL_free(rctx->readbuf);
+ OPENSSL_free(rctx->proxy);
+ OPENSSL_free(rctx->server);
+ OPENSSL_free(rctx->port);
OPENSSL_free(rctx->expected_ct);
OPENSSL_free(rctx);
}
@@ -175,8 +196,9 @@ int OSSL_HTTP_REQ_CTX_set_request_line(OSSL_HTTP_REQ_CTX *rctx, int method_POST,
if (path[0] != '/' && BIO_printf(rctx->mem, "/") <= 0)
return 0;
- if (BIO_printf(rctx->mem, "%s "HTTP_PREFIX"1.0\r\n", path) <= 0)
+ if (BIO_printf(rctx->mem, "%s "HTTP_1_0"\r\n", path) <= 0)
return 0;
+ rctx->resp_len = 0;
rctx->state = OHS_HTTP_HEADER;
return 1;
}
@@ -201,10 +223,7 @@ int OSSL_HTTP_REQ_CTX_add1_header(OSSL_HTTP_REQ_CTX *rctx,
if (BIO_puts(rctx->mem, value) <= 0)
return 0;
}
- if (BIO_write(rctx->mem, "\r\n", 2) != 2)
- return 0;
- rctx->state = OHS_HTTP_HEADER;
- return 1;
+ return BIO_write(rctx->mem, "\r\n", 2) == 2;
}
int OSSL_HTTP_REQ_CTX_set_expected(OSSL_HTTP_REQ_CTX *rctx,
@@ -216,7 +235,7 @@ int OSSL_HTTP_REQ_CTX_set_expected(OSSL_HTTP_REQ_CTX *rctx,
return 0;
}
if (keep_alive != 0
- && rctx->state != OHS_ERROR && rctx->state != OHS_HEADERS) {
+ && rctx->state != OHS_ERROR && rctx->state != OHS_HTTP_HEADER) {
/* Cannot anymore set keep-alive in request header */
ERR_raise(ERR_LIB_HTTP, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
return 0;
@@ -243,11 +262,19 @@ static int ossl_http_req_ctx_set_content(OSSL_HTTP_REQ_CTX *rctx,
const unsigned char *req;
long req_len;
- if (rctx == NULL || req_mem == NULL) {
+ if (rctx == NULL || (req_mem == NULL && content_type != NULL)) {
ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
- if (rctx->mem == NULL || !rctx->method_POST) {
+
+ if (rctx->keep_alive != 0
+ && !OSSL_HTTP_REQ_CTX_add1_header(rctx, "Connection", "keep-alive"))
+ return 0;
+ rctx->state = OHS_WRITE_INIT;
+
+ if (req_mem == NULL)
+ return 1;
+ if (!rctx->method_POST) {
ERR_raise(ERR_LIB_HTTP, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
return 0;
}
@@ -258,7 +285,6 @@ static int ossl_http_req_ctx_set_content(OSSL_HTTP_REQ_CTX *rctx,
if ((req_len = BIO_get_mem_data(req_mem, &req)) <= 0)
return 0;
- rctx->state = OHS_WRITE_INIT;
return BIO_printf(rctx->mem, "Content-Length: %ld\r\n\r\n", req_len) > 0
&& BIO_write(rctx->mem, req, req_len) == (int)req_len;
@@ -302,41 +328,38 @@ static int OSSL_HTTP_REQ_CTX_add1_headers(OSSL_HTTP_REQ_CTX *rctx,
return 1;
}
-/*-
- * Create OSSL_HTTP_REQ_CTX structure using the values provided.
- * If !use_http_proxy then the 'server' and 'port' parameters are ignored.
- * If req_mem == NULL then use GET and ignore content_type, else POST.
- */
-static OSSL_HTTP_REQ_CTX
-*ossl_http_req_ctx_new(BIO *wbio, BIO *rbio, int use_http_proxy,
- const char *server, const char *port,
- const char *path,
- const STACK_OF(CONF_VALUE) *headers,
- const char *content_type, BIO *req_mem,
- int buf_size, int timeout,
- const char *expected_ct, int expect_asn1)
+/* Create OSSL_HTTP_REQ_CTX structure using the values provided. */
+static OSSL_HTTP_REQ_CTX *http_req_ctx_new(int free_wbio, BIO *wbio, BIO *rbio,
+ OSSL_HTTP_bio_cb_t bio_update_fn,
+ void *arg, int use_ssl,
+ const char *proxy,
+ const char *server, const char *port,
+ int buf_size, unsigned long max_len,
+ int overall_timeout)
{
- OSSL_HTTP_REQ_CTX *rctx;
+ OSSL_HTTP_REQ_CTX *rctx = OSSL_HTTP_REQ_CTX_new(wbio, rbio, buf_size);
- if (use_http_proxy && (server == NULL || port == NULL)) {
- ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER);
- return NULL;
- }
- /* remaining parameters are checked indirectly by the functions called */
-
- if ((rctx = OSSL_HTTP_REQ_CTX_new(wbio, rbio, buf_size))
- == NULL)
+ if (rctx == NULL)
return NULL;
- if (OSSL_HTTP_REQ_CTX_set_request_line(rctx, req_mem != NULL,
- use_http_proxy ? server : NULL, port,
- path)
- && OSSL_HTTP_REQ_CTX_set_expected(rc