diff options
-rw-r--r-- | apps/cmp.c | 40 | ||||
-rw-r--r-- | apps/include/http_server.h | 15 | ||||
-rw-r--r-- | apps/lib/http_server.c | 100 | ||||
-rw-r--r-- | apps/ocsp.c | 13 | ||||
-rw-r--r-- | crypto/cmp/cmp_ctx.c | 17 | ||||
-rw-r--r-- | crypto/cmp/cmp_http.c | 38 | ||||
-rw-r--r-- | crypto/cmp/cmp_local.h | 2 | ||||
-rw-r--r-- | crypto/err/openssl.txt | 2 | ||||
-rw-r--r-- | crypto/http/http_client.c | 470 | ||||
-rw-r--r-- | crypto/http/http_err.c | 4 | ||||
-rw-r--r-- | crypto/ocsp/ocsp_http.c | 20 | ||||
-rw-r--r-- | doc/man3/OSSL_CMP_CTX_new.pod | 13 | ||||
-rw-r--r-- | doc/man3/OSSL_CMP_SRV_CTX_new.pod | 4 | ||||
-rw-r--r-- | doc/man3/OSSL_HTTP_REQ_CTX.pod | 13 | ||||
-rw-r--r-- | doc/man3/OSSL_HTTP_transfer.pod | 34 | ||||
-rw-r--r-- | include/openssl/http.h | 11 | ||||
-rw-r--r-- | test/http_test.c | 183 | ||||
-rw-r--r-- | test/recipes/80-test_cmp_http_data/test_connection.csv | 92 |
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 |