The branch master has been updated via d96486dc809b5d134055785bfa6d707195d95534 (commit) via 6e477a60e42978f63623ad64d8e28e7a3e5f2e28 (commit) via d7fcee3b3b5fae674f107c736f8d53610212ce4e (commit) from 8d6481f532ab8c502de2ad17e09f688abb675a71 (commit)
- Log ----------------------------------------------------------------- commit d96486dc809b5d134055785bfa6d707195d95534 Author: Dr. David von Oheimb <david.von.ohe...@siemens.com> Date: Fri Sep 4 08:11:41 2020 +0200 apps/cmp.c: Allow default HTTP path (aka CMP alias) given with -server option Reviewed-by: Shane Lontis <shane.lon...@oracle.com> (Merged from https://github.com/openssl/openssl/pull/12786) commit 6e477a60e42978f63623ad64d8e28e7a3e5f2e28 Author: Dr. David von Oheimb <david.von.ohe...@siemens.com> Date: Fri Sep 4 08:05:46 2020 +0200 apps/cmp.c: Use enhanced OSSL_HTTP_parse_url(), removing parse_addr() and atoint() Reviewed-by: Shane Lontis <shane.lon...@oracle.com> (Merged from https://github.com/openssl/openssl/pull/12786) commit d7fcee3b3b5fae674f107c736f8d53610212ce4e Author: Dr. David von Oheimb <david.von.ohe...@siemens.com> Date: Thu Sep 3 13:32:56 2020 +0200 OSSL_HTTP_parse_url(): add optional port number return parameter and strengthen documentation Reviewed-by: Shane Lontis <shane.lon...@oracle.com> (Merged from https://github.com/openssl/openssl/pull/12786) ----------------------------------------------------------------------- Summary of changes: apps/cmp.c | 93 ++++---------- apps/lib/apps.c | 2 +- apps/ocsp.c | 2 +- apps/s_server.c | 4 +- crypto/err/openssl.txt | 3 + crypto/http/http_client.c | 3 +- crypto/http/http_err.c | 5 + crypto/http/http_lib.c | 102 ++++++++++------ doc/man1/openssl-cmp.pod.in | 7 +- doc/man3/OSSL_HTTP_transfer.pod | 136 +++++++++++---------- include/openssl/http.h | 2 +- include/openssl/httperr.h | 4 + test/http_test.c | 72 +++++++++++ .../81-test_cmp_cli_data/test_connection.csv | 3 + 14 files changed, 260 insertions(+), 178 deletions(-) diff --git a/apps/cmp.c b/apps/cmp.c index 9846e7a9c2..dd49142309 100644 --- a/apps/cmp.c +++ b/apps/cmp.c @@ -74,11 +74,10 @@ typedef enum { /* message transfer */ static char *opt_server = NULL; -static char server_port_s[32] = { '\0' }; -static int server_port = 0; +static char server_port[32] = { '\0' }; static char *opt_proxy = NULL; static char *opt_no_proxy = NULL; -static char *opt_path = "/"; +static char *opt_path = NULL; static int opt_msg_timeout = -1; static int opt_total_timeout = -1; @@ -334,9 +333,9 @@ const OPTIONS cmp_options[] = { OPT_SECTION("Message transfer"), {"server", OPT_SERVER, 's', - "[http[s]://]address[:port] of CMP server. Default port 80 or 443."}, + "[http[s]://]address[:port][/path] of CMP server. Default port 80 or 443."}, {OPT_MORE_STR, 0, 0, - "The address may be a DNS name or an IP address"}, + "address may be a DNS name or an IP address; path can be overridden by -path"}, {"proxy", OPT_PROXY, 's', "[http[s]://]address[:port][/path] of HTTP(S) proxy to use; path is ignored"}, {"no_proxy", OPT_NO_PROXY, 's', @@ -344,7 +343,7 @@ const OPTIONS cmp_options[] = { {OPT_MORE_STR, 0, 0, "Default from environment variable 'no_proxy', else 'NO_PROXY', else none"}, {"path", OPT_PATH, 's', - "HTTP path (aka CMP alias) at the CMP server. Default \"/\""}, + "HTTP path (aka CMP alias) at the CMP server. Default from -server, else \"/\""}, {"msg_timeout", OPT_MSG_TIMEOUT, 'n', "Timeout per CMP message round trip (or 0 for none). Default 120 seconds"}, {"total_timeout", OPT_TOTAL_TIMEOUT, 'n', @@ -889,49 +888,6 @@ static OSSL_CMP_MSG *read_write_req_resp(OSSL_CMP_CTX *ctx, return res; } -/* - * parse string as integer value, not allowing trailing garbage, see also - * https://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html - * - * returns integer value, or INT_MIN on error - */ -static int atoint(const char *str) -{ - char *tailptr; - long res = strtol(str, &tailptr, 10); - - if ((*tailptr != '\0') || (res < INT_MIN) || (res > INT_MAX)) - return INT_MIN; - else - return (int)res; -} - -static int parse_addr(char **opt_string, int port, const char *name) -{ - char *port_string; - - if (strncasecmp(*opt_string, OSSL_HTTP_PREFIX, - strlen(OSSL_HTTP_PREFIX)) == 0) { - *opt_string += strlen(OSSL_HTTP_PREFIX); - } else if (strncasecmp(*opt_string, OSSL_HTTPS_PREFIX, - strlen(OSSL_HTTPS_PREFIX)) == 0) { - *opt_string += strlen(OSSL_HTTPS_PREFIX); - if (port == 0) - port = 443; /* == integer value of OSSL_HTTPS_PORT */ - } - - if ((port_string = strrchr(*opt_string, ':')) == NULL) - return port; /* using default */ - *(port_string++) = '\0'; - port = atoint(port_string); - if ((port <= 0) || (port > 65535)) { - CMP_err2("invalid %s port '%s' given, sane range 1-65535", - name, port_string); - return -1; - } - return port; -} - static int set1_store_parameters(X509_STORE *ts) { if (ts == NULL) @@ -1896,33 +1852,36 @@ static int handle_opt_geninfo(OSSL_CMP_CTX *ctx) static int setup_client_ctx(OSSL_CMP_CTX *ctx, ENGINE *engine) { int ret = 0; + char *server = NULL, *port = NULL, *path = NULL, *used_path; + int portnum, ssl; char server_buf[200] = { '\0' }; char proxy_buf[200] = { '\0' }; char *proxy_host = NULL; char *proxy_port_str = NULL; if (opt_server == NULL) { - CMP_err("missing server address[:port]"); + CMP_err("missing -server option"); + goto err; + } + if (!OSSL_HTTP_parse_url(opt_server, &server, &port, &portnum, &path, &ssl)) goto err; - } else if ((server_port = - parse_addr(&opt_server, server_port, "server")) < 0) { + if (ssl && !opt_tls_used) { + CMP_err("missing -tls_used option since -server URL indicates https"); goto err; } - if (server_port != 0) - BIO_snprintf(server_port_s, sizeof(server_port_s), "%d", server_port); - if (!OSSL_CMP_CTX_set1_server(ctx, opt_server) - || !OSSL_CMP_CTX_set_serverPort(ctx, server_port) - || !OSSL_CMP_CTX_set1_serverPath(ctx, opt_path)) + strncpy(server_port, port, sizeof(server_port)); + used_path = opt_path != NULL ? opt_path : path; + if (!OSSL_CMP_CTX_set1_server(ctx, server) + || !OSSL_CMP_CTX_set_serverPort(ctx, portnum) + || !OSSL_CMP_CTX_set1_serverPath(ctx, used_path)) goto oom; if (opt_proxy != NULL && !OSSL_CMP_CTX_set1_proxy(ctx, opt_proxy)) goto oom; if (opt_no_proxy != NULL && !OSSL_CMP_CTX_set1_no_proxy(ctx, opt_no_proxy)) goto oom; - (void)BIO_snprintf(server_buf, sizeof(server_buf), "http%s://%s%s%s/%s", - opt_tls_used ? "s" : "", opt_server, - server_port == 0 ? "" : ":", server_port_s, - opt_path == NULL ? "" : - opt_path[0] == '/' ? opt_path + 1 : opt_path); + (void)BIO_snprintf(server_buf, sizeof(server_buf), "http%s://%s:%s/%s", + opt_tls_used ? "s" : "", server, port, + *used_path == '/' ? used_path + 1 : used_path); if (opt_proxy != NULL) (void)BIO_snprintf(proxy_buf, sizeof(proxy_buf), " via %s", opt_proxy); @@ -2023,7 +1982,7 @@ static int setup_client_ctx(OSSL_CMP_CTX *ctx, ENGINE *engine) (void)OSSL_CMP_CTX_set_http_cb_arg(ctx, info); /* info will be freed along with CMP ctx */ info->server = opt_server; - info->port = server_port_s; + info->port = server_port; info->use_proxy = opt_proxy != NULL; info->timeout = OSSL_CMP_CTX_get_option(ctx, OSSL_CMP_OPT_MSG_TIMEOUT); info->ssl_ctx = setup_ssl_ctx(ctx, engine); @@ -2053,6 +2012,9 @@ static int setup_client_ctx(OSSL_CMP_CTX *ctx, ENGINE *engine) ret = 1; err: + OPENSSL_free(server); + OPENSSL_free(port); + OPENSSL_free(path); OPENSSL_free(proxy_host); OPENSSL_free(proxy_port_str); return ret; @@ -2875,11 +2837,6 @@ int cmp_main(int argc, char **argv) } opt_server = mock_server; opt_proxy = "API"; - } else { - if (opt_server == NULL) { - CMP_err("missing -server option"); - goto err; - } } if (!setup_client_ctx(cmp_ctx, engine)) { diff --git a/apps/lib/apps.c b/apps/lib/apps.c index 150df997b8..342c364aa4 100644 --- a/apps/lib/apps.c +++ b/apps/lib/apps.c @@ -2066,7 +2066,7 @@ ASN1_VALUE *app_http_get_asn1(const char *url, const char *proxy, return NULL; } - if (!OSSL_HTTP_parse_url(url, &server, &port, NULL /* ppath */, &use_ssl)) + if (!OSSL_HTTP_parse_url(url, &server, &port, NULL, NULL, &use_ssl)) return NULL; if (use_ssl && ssl_ctx == NULL) { HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); diff --git a/apps/ocsp.c b/apps/ocsp.c index 8fb605e6fe..0aca4b7622 100644 --- a/apps/ocsp.c +++ b/apps/ocsp.c @@ -275,7 +275,7 @@ int ocsp_main(int argc, char **argv) OPENSSL_free(tpath); thost = tport = tpath = NULL; if (!OSSL_HTTP_parse_url(opt_arg(), - &host, &port, &path, &use_ssl)) { + &host, &port, NULL, &path, &use_ssl)) { BIO_printf(bio_err, "%s Error parsing URL\n", prog); goto end; } diff --git a/apps/s_server.c b/apps/s_server.c index b936ff4226..4c2e5b27f7 100644 --- a/apps/s_server.c +++ b/apps/s_server.c @@ -535,7 +535,7 @@ static int get_ocsp_resp_from_responder(SSL *s, tlsextstatusctx *srctx, aia = X509_get1_ocsp(x); if (aia != NULL) { if (!OSSL_HTTP_parse_url(sk_OPENSSL_STRING_value(aia, 0), - &host, &port, &path, &use_ssl)) { + &host, &port, NULL, &path, &use_ssl)) { BIO_puts(bio_err, "cert_status: can't parse AIA URL\n"); goto err; } @@ -1405,7 +1405,7 @@ int s_server_main(int argc, char *argv[]) #ifndef OPENSSL_NO_OCSP s_tlsextstatus = 1; if (!OSSL_HTTP_parse_url(opt_arg(), - &tlscstatp.host, &tlscstatp.port, + &tlscstatp.host, &tlscstatp.port, NULL, &tlscstatp.path, &tlscstatp.use_ssl)) { BIO_printf(bio_err, "Error parsing URL\n"); goto end; diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt index 7c37a3e56e..256ec35588 100644 --- a/crypto/err/openssl.txt +++ b/crypto/err/openssl.txt @@ -2637,6 +2637,9 @@ 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_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 +HTTP_R_INVALID_URL_PREFIX:124:invalid url prefix HTTP_R_MAX_RESP_LEN_EXCEEDED:117:max resp len exceeded HTTP_R_MISSING_ASN1_ENCODING:110:missing asn1 encoding HTTP_R_MISSING_CONTENT_TYPE:121:missing content type diff --git a/crypto/http/http_client.c b/crypto/http/http_client.c index 3e1be1f569..5a78d67ca4 100644 --- a/crypto/http/http_client.c +++ b/crypto/http/http_client.c @@ -1005,7 +1005,8 @@ BIO *OSSL_HTTP_get(const char *url, const char *proxy, const char *no_proxy, return NULL; for (;;) { - if (!OSSL_HTTP_parse_url(current_url, &host, &port, &path, &use_ssl)) + if (!OSSL_HTTP_parse_url(current_url, &host, &port, NULL /* port_num */, + &path, &use_ssl)) break; new_rpath: diff --git a/crypto/http/http_err.c b/crypto/http/http_err.c index 7b6f295170..13779fac84 100644 --- a/crypto/http/http_err.c +++ b/crypto/http/http_err.c @@ -26,6 +26,11 @@ static const ERR_STRING_DATA HTTP_str_reasons[] = { {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_ERROR_SENDING), "error sending"}, {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_INCONSISTENT_CONTENT_LENGTH), "inconsistent content length"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_INVALID_PORT_NUMBER), + "invalid port number"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_INVALID_URL_PATH), "invalid url path"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_INVALID_URL_PREFIX), + "invalid url prefix"}, {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_MAX_RESP_LEN_EXCEEDED), "max resp len exceeded"}, {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_MISSING_ASN1_ENCODING), diff --git a/crypto/http/http_lib.c b/crypto/http/http_lib.c index 5da5b1e724..19b964e613 100644 --- a/crypto/http/http_lib.c +++ b/crypto/http/http_lib.c @@ -21,19 +21,12 @@ */ int OSSL_HTTP_parse_url(const char *url, char **phost, char **pport, - char **ppath, int *pssl) + int *pport_num, char **ppath, int *pssl) { char *p, *buf; - char *host; - const char *port = OSSL_HTTP_PORT; - size_t https_len = strlen(OSSL_HTTPS_NAME); - - if (!ossl_assert(https_len >= strlen(OSSL_HTTP_NAME))) - return 0; - if (url == NULL) { - HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); - return 0; - } + char *host, *host_end; + const char *path, *port = OSSL_HTTP_PORT; + long portnum = 80; if (phost != NULL) *phost = NULL; @@ -44,59 +37,90 @@ int OSSL_HTTP_parse_url(const char *url, char **phost, char **pport, if (pssl != NULL) *pssl = 0; + if (url == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + /* dup the buffer since we are going to mess with it */ if ((buf = OPENSSL_strdup(url)) == NULL) goto err; - /* Check for initial colon */ - p = strchr(buf, ':'); - if (p == NULL || (size_t)(p - buf) > https_len) { + /* check for optional prefix "http[s]://" */ + p = strstr(buf, "://"); + if (p == NULL) { p = buf; } else { - *(p++) = '\0'; - + *p = '\0'; /* p points to end of scheme name */ if (strcmp(buf, OSSL_HTTPS_NAME) == 0) { if (pssl != NULL) *pssl = 1; port = OSSL_HTTPS_PORT; + portnum = 443; } else if (strcmp(buf, OSSL_HTTP_NAME) != 0) { - goto parse_err; + HTTPerr(0, HTTP_R_INVALID_URL_PREFIX); + goto err; } - - /* Check for double slash */ - if ((p[0] != '/') || (p[1] != '/')) - goto parse_err; - p += 2; + p += 3; } host = p; - /* Check for trailing part of path */ - p = strchr(p, '/'); - if (ppath != NULL && (*ppath = OPENSSL_strdup(p == NULL ? "/" : p)) == NULL) - goto err; - if (p != NULL) - *p = '\0'; /* Set start of path to 0 so hostname[:port] is valid */ - - p = host; + /* parse host name/address as far as needed here */ if (host[0] == '[') { - /* ipv6 literal */ + /* ipv6 literal, which may include ':' */ host++; - p = strchr(host, ']'); - if (p == NULL) + host_end = strchr(host, ']'); + if (host_end == NULL) goto parse_err; - *p = '\0'; - p++; + *host_end++ = '\0'; + } else { + host_end = strchr(host, ':'); /* look for start of optional port */ + if (host_end == NULL) + host_end = strchr(host, '/'); /* look for start of optional path */ + if (host_end == NULL) + /* the remaining string is just the hostname */ + host_end = host + strlen(host); } - /* Look for optional ':' for port number */ - if ((p = strchr(p, ':'))) { - *p = '\0'; - port = p + 1; + /* parse optional port specification starting with ':' */ + p = host_end; + if (*p == ':') { + port = ++p; + if (pport_num == NULL) { + p = strchr(port, '/'); + if (p == NULL) + p = p + strlen(port); + } else { /* make sure a numerical port value is given */ + portnum = strtol(port, &p, 10); + if (p == port || (*p != '\0' && *p != '/')) + goto parse_err; + if (portnum <= 0 || portnum >= 65536) { + HTTPerr(0, HTTP_R_INVALID_PORT_NUMBER); + goto err; + } + } + } + *host_end = '\0'; + *p = '\0'; /* terminate port string */ + + /* check for optional path at end of url starting with '/' */ + path = url + (p - buf); + /* cannot use p + 1 because *p is '\0' and path must start with '/' */ + if (*path == '\0') { + path = "/"; + } else if (*path != '/') { + HTTPerr(0, HTTP_R_INVALID_URL_PATH); + goto parse_err; } + if (phost != NULL && (*phost = OPENSSL_strdup(host)) == NULL) goto err; if (pport != NULL && (*pport = OPENSSL_strdup(port)) == NULL) goto err; + if (pport_num != NULL) + *pport_num = (int)portnum; + if (ppath != NULL && (*ppath = OPENSSL_strdup(path)) == NULL) + goto err; OPENSSL_free(buf); return 1; diff --git a/doc/man1/openssl-cmp.pod.in b/doc/man1/openssl-cmp.pod.in index 3dc193cd4d..46c5059d84 100644 --- a/doc/man1/openssl-cmp.pod.in +++ b/doc/man1/openssl-cmp.pod.in @@ -12,7 +12,7 @@ B<openssl> B<cmp> [B<-config> I<filename>] [B<-section> I<names>] -[B<-server> I<address[:port]>] +[B<-server> I<[http[s]://]address[:port][/path]>] [B<-proxy> I<[http[s]://]address[:port][/path]>] [B<-no_proxy> I<addresses>] [B<-path> I<remote_path>] @@ -431,11 +431,12 @@ Reason numbers defined in RFC 5280 are: =over 4 -=item B<-server> I<[http[s]://]address[:port]> +=item B<-server> I<[http[s]://]address[:port][/path]> The IP address or DNS hostname and optionally port (defaulting to 80 or 443) of the CMP server to connect to using HTTP(S) transport. The optional I<http://> or I<https://> prefix is ignored. +If a path is included it provides the default value for the B<-path> option. =item B<-proxy> I<[http[s]://]address[:port][/path]> @@ -454,7 +455,7 @@ Default is from the environment variable C<no_proxy> if set, else C<NO_PROXY>. =item B<-path> I<remote_path> HTTP path at the CMP server (aka CMP alias) to use for POST requests. -Defaults to I</>. +Defaults to any path given with B<-server>, else C<"/">. =item B<-msg_timeout> I<seconds> diff --git a/doc/man3/OSSL_HTTP_transfer.pod b/doc/man3/OSSL_HTTP_transfer.pod index 34794c313c..f78d96be1f 100644 --- a/doc/man3/OSSL_HTTP_transfer.pod +++ b/doc/man3/OSSL_HTTP_transfer.pod @@ -55,36 +55,36 @@ OSSL_HTTP_parse_url const char *proxyuser, const char *proxypass, int timeout, BIO *bio_err, const char *prog); int OSSL_HTTP_parse_url(const char *url, char **phost, char **pport, - char **ppath, int *pssl); + int *pport_num, char **ppath, int *pssl); =head1 DESCRIPTION -OSSL_HTTP_get() uses HTTP GET to obtain data (of any type) from the given B<url> +OSSL_HTTP_get() uses HTTP GET to obtain data (of any type) from the given I<url> and returns it as a memory BIO. OSSL_HTTP_get_asn1() uses HTTP GET to obtain an ASN.1-encoded value -(e.g., an X.509 certificate) with the expected structure specified by B<it> -(e.g., I<ASN1_ITEM_rptr(X509)>) from the given B<url> +(e.g., an X.509 certificate) with the expected structure specified by I<it> +(e.g., I<ASN1_ITEM_rptr(X509)>) from the given I<url> and returns it on success as a pointer to I<ASN1_VALUE>. -OSSL_HTTP_post_asn1() uses the HTTP POST method to send a request B<req> -with the ASN.1 structure defined in B<req_it> and the given B<content_type> to -the given B<server> and optional B<port> and B<path>. -If B<use_ssl> is nonzero a TLS connection is requested and the B<bio_update_fn> +OSSL_HTTP_post_asn1() uses the HTTP POST method to send a request I<req> +with the ASN.1 structure defined in I<req_it> and the given I<content_type> to +the given I<server> and optional I<port> and I<path>. +If I<use_ssl> is nonzero a TLS connection is requested and the I<bio_update_fn> parameter, described below, must be provided. -The optional list B<headers> may contain additional custom HTTP header lines. -The expected structure of the response is specified by B<rsp_it>. +The optional list I<headers> may contain additional custom HTTP header lines. +The expected structure of the response is specified by I<rsp_it>. On success it returns the response as a pointer to B<ASN1_VALUE>. OSSL_HTTP_transfer() exchanges any form of HTTP request and response. It implements the core of the functions described above. -If B<path> parameter is NULL it defaults to "/". -If B<use_ssl> is nonzero a TLS connection is requested -and the B<bio_update_fn> parameter, described below, must be provided. -If B<req_mem> is NULL it uses the HTTP GET method, else it uses HTTP POST to -send a request with the contents of the memory BIO and optional B<content_type>. -The optional list B<headers> may contain additional custom HTTP header lines. -If B<req_mem> is NULL (i.e., the HTTP method is GET) and B<redirection_url> +If I<path> parameter is NULL it defaults to "/". +If I<use_ssl> is nonzero a TLS connection is requested +and the I<bio_update_fn> parameter, described below, must be provided. +If I<req_mem> is NULL it uses the HTTP GET method, else it uses HTTP POST to +send a request with the contents of the memory BIO and optional I<content_type>. +The optional list I<headers> may contain additional custom HTTP header lines. +If I<req_mem> is NULL (i.e., the HTTP method is GET) and I<redirection_url> is not NULL the latter pointer is used to provide any new location that the server may return with HTTP code 301 (MOVED_PERMANENTLY) or 302 (FOUND). In this case the caller is responsible for deallocating this URL with @@ -93,71 +93,71 @@ L<OPENSSL_free(3)>. The above functions have the following parameters in common. Typically the OpenSSL build supports sockets -and the B<bio> and B<rbio> parameters are both NULL. +and the I<bio> and I<rbio> parameters are both NULL. In this case the client creates a network BIO internally -for connecting to the given B<server> -at the specified B<port> (if any, defaulting to 80 for HTTP or 443 for HTTPS), -optionally via a B<proxy> (respecting B<no_proxy>) as described below. +for connecting to the given I<server> +at the specified I<port> (if any, defaulting to 80 for HTTP or 443 for HTTPS), +optionally via a I<proxy> (respecting I<no_proxy>) as described below. Then the client uses this internal BIO for exchanging the request and response. -If B<bio> is given and B<rbio> is NULL then the client uses this B<bio> instead. -If both B<bio> and B<rbio> are given (which may be memory BIOs for instance) +If I<bio> is given and I<rbio> is NULL then the client uses this I<bio> instead. +If both I<bio> and I<rbio> are given (which may be memory BIOs for instance) then no explicit connection is attempted, -B<bio> is used for writing the request, and B<rbio> for reading the response. -As soon as the client has flushed B<bio> the server must be ready to provide -a response or indicate a waiting condition via B<rbio>. +I<bio> is used for writing the request, and I<rbio> for reading the response. +As soon as the client has flushed I<bio> the server must be ready to provide +a response or indicate a waiting condition via I<rbio>. -The optional B<proxy> parameter can be used to set the address of the an +The optional I<proxy> parameter can be used to set the address of the an HTTP(S) proxy to use (unless overridden by "no_proxy" settings). -If TLS is not used this defaults to the environment variable B<http_proxy> -if set, else B<HTTP_PROXY>. -If B<use_ssl> != 0 it defaults to B<https_proxy> if set, else B<HTTPS_PROXY>. +If TLS is not used this defaults to the environment variable C<http_proxy> +if set, else C<HTTP_PROXY>. +If I<use_ssl> != 0 it defaults to C<https_proxy> if set, else C<HTTPS_PROXY>. An empty proxy string specifies not to use a proxy. -Else the format is I<[http[s]://]address[:port][/path]>, +Else the format is C<[http[s]://]address[:port][/path]>, where any path given is ignored. The default proxy port number is 80, or 443 in case "https:" is given. -The HTTP client functions connect via the given proxy unless the B<server> -is found in the optional list B<no_proxy> of proxy hostnames (if not NULL; -default is the environment variable B<no_proxy> if set, else B<NO_PROXY>). +The HTTP client functions connect via the given proxy unless the I<server> +is found in the optional list I<no_proxy> of proxy hostnames (if not NULL; +default is the environment variable C<no_proxy> if set, else C<NO_PROXY>). Proxying plain HTTP is supported directly, while using a proxy for HTTPS connections requires a suitable callback function -such as B<OSSL_HTTP_proxy_connect()>, described below. +such as OSSL_HTTP_proxy_connect(), described below. -The B<maxline> parameter specifies the response header maximum line length, +The I<maxline> parameter specifies the response header maximum line length, where 0 indicates the default value, which currently is 4k. -The B<max_resp_len> parameter specifies the maximum response length, +The I<max_resp_len> parameter specifies the maximum response length, where 0 indicates the default value, which currently is 100k. An ASN.1-encoded response is expected by OSSL_HTTP_get_asn1() and OSSL_HTTP_post_asn1(), while for OSSL_HTTP_get() or OSSL_HTTP_transfer() -this is only the case if the B<expect_asn1> parameter is nonzero. +this is only the case if the I<expect_asn1> parameter is nonzero. If the response header contains one or more "Content-Length" header lines and/or an ASN.1-encoded response is expected, which should include a total length, the length indications received are checked for consistency and for not exceeding the maximum response length. -If the parameter B<expected_content_type> (or B<expected_ct>, respectively) +If the parameter I<expected_content_type> (or I<expected_ct>, respectively) is not NULL then the HTTP client checks that the given content type string is included in the HTTP header of the response and returns an error if not. -If the B<timeout> parameter is > 0 this indicates the maximum number of seconds +If the I<timeout> parameter is > 0 this indicates the maximum number of seconds to wait until the transfer is complete. A value of 0 enables waiting indefinitely, while a value < 0 immediately leads to a timeout condition. -The optional parameter B<bio_update_fn> with its optional argument B<arg> may +The optional parameter I<bio_update_fn> with its optional argument I<arg> may be used to modify the connection BIO used by the HTTP client (and cannot be -used when both B<bio> and B<rbio> are given). -B<bio_update_fn> is a BIO connect/disconnect callback function with prototype +used when both I<bio> and I<rbio> are given). +I<bio_update_fn> is a BIO connect/disconnect callback function with prototype BIO *(*OSSL_HTTP_bio_cb_t)(BIO *bio, void *arg, int connect, int detail) -The callback may modify the HTTP BIO provided in the B<bio> argument, -whereby it may make use of a custom defined argument B<arg>, +The callback may modify the HTTP BIO provided in the I<bio> argument, +whereby it may make use of a custom defined argument I<arg>, which may for instance refer to an I<SSL_CTX> structure. During connection establishment, just after calling BIO_do_connect_retry(), -the function is invoked with the B<connect> argument being 1 and the B<detail> +the function is invoked with the I<connect> argument being 1 and the I<detail> argument being 1 if HTTPS is requested, i.e., SSL/TLS should be enabled. -On disconnect B<connect> is 0 and B<detail> is 1 if no error occurred, else 0. +On disconnect I<connect> is 0 and I<detail> is 1 if no error occurred, else 0. For instance, on connect the function may prepend a TLS BIO to implement HTTPS; after disconnect it may do some diagnostic output and/or specific cleanup. The function should return NULL to indicate failure. @@ -180,31 +180,39 @@ After disconnect the modified BIO will be deallocated using BIO_free_all(). OSSL_HTTP_proxy_connect() may be used by an above BIO connect callback function to set up an SSL/TLS connection via an HTTPS proxy. -It promotes the given BIO B<bio> representing a connection +It promotes the given BIO I<bio> representing a connection pre-established with a TLS proxy using the HTTP CONNECT method, -optionally using proxy client credentials B<proxyuser> and B<proxypass>, -to connect with TLS protection ultimately to B<server> and B<port>. -If the B<port> argument is NULL or the empty string it defaults to "443". -The B<timeout> parameter is used as described above. +optionally using proxy client credentials I<proxyuser> and I<proxypass>, +to connect with TLS protection ultimately to I<server> and I<port>. +If the I<port> argument is NULL or the empty string it defaults to "443". +The I<timeout> parameter is used as described above. Since this function is typically called by applications such as -L<openssl-s_client(1)> it uses the B<bio_err> and B<prog> parameters (unless +L<openssl-s_client(1)> it uses the I<bio_err> and I<prog> parameters (unless NULL) to print additional diagnostic information in a user-oriented way. -OSSL_HTTP_parse_url() parses its input string B<url> as a URL and splits it up -into host, port and path components and a flag whether it begins with 'https'. -The host component may be a DNS name or an IPv4 or an IPv6 address. +OSSL_HTTP_parse_url() parses its input string I<url> as a URL +of the form C<[http[s]://]address[:port][/path]> and splits it up into host, +port, and path components and a flag indicating whether it begins with 'https'. +The host component may be a DNS name or an IP address +where IPv6 addresses should be enclosed in square brackets C<[> and C<]>. The port component is optional and defaults to "443" for HTTPS, else "80". +If the I<pport_num> argument is NULL the port specification +can be in mnemonic form such as "http" like with L<BIO_set_conn_port(3)>, else +it must be in numerical form and its integer value is assigned to B<*pport_num>. The path component is also optional and defaults to "/". -As far as the result pointer arguments are not NULL it assigns via -them copies of the respective string components. -The strings returned this way must be deallocated by the caller using -L<OPENSSL_free(3)> unless they are NULL, which is their default value on error. +On success the function assigns via each non-NULL result pointer argument +I<phost>, I<pport>, I<pport_num>, I<ppath>, and I<pssl> +the respective url component. +On error, B<*phost>, B<*pport>, and B<*ppath> are assigned to NULL, +else they are guaranteed to contain non-NULL string pointers. +It is the reponsibility of the caller to free them using L<OPENSSL_free(3)>. +A string returned via B<*ppath> is guaranteed to begin with a C</> character. =head1 NOTES The names of the environment variables used by this implementation: -B<http_proxy>, B<HTTP_PROXY>, B<https_proxy>, B<HTTPS_PROXY>, B<no_proxy>, and -B<NO_PROXY>, have been chosen for maximal compatibility with +C<http_proxy>, C<HTTP_PROXY>, C<https_proxy>, C<HTTPS_PROXY>, C<no_proxy>, and +C<NO_PROXY>, have been chosen for maximal compatibility with other HTTP client implementations such as wget, curl, and git. =head1 RETURN VALUES @@ -216,6 +224,10 @@ Error conditions include connection/transfer timeout, parse errors, etc. OSSL_HTTP_proxy_connect() and OSSL_HTTP_parse_url() return 1 on success, 0 on error. +=head1 SEE ALSO + +L<BIO_set_conn_port(3)> + =head1 HISTORY OSSL_HTTP_get(), OSSL_HTTP_get_asn1(), OSSL_HTTP_post_asn1(), diff --git a/include/openssl/http.h b/include/openssl/http.h index 45c8f11d7b..2c9ce9d86e 100644 --- a/include/openssl/http.h +++ b/include/openssl/http.h @@ -74,7 +74,7 @@ int OSSL_HTTP_proxy_connect(BIO *bio, const char *server, const char *port, int timeout, BIO *bio_err, const char *prog); int OSSL_HTTP_parse_url(const char *url, char **phost, char **pport, - char **ppath, int *pssl); + int *pport_num, char **ppath, int *pssl); # ifdef __cplusplus } diff --git a/include/openssl/httperr.h b/include/openssl/httperr.h index e4acb1df8c..7747643bfa 100644 --- a/include/openssl/httperr.h +++ b/include/openssl/httperr.h @@ -10,6 +10,7 @@ #ifndef OPENSSL_HTTPERR_H # define OPENSSL_HTTPERR_H +# pragma once # include <openssl/opensslconf.h> # include <openssl/symhacks.h> @@ -37,6 +38,9 @@ int ERR_load_HTTP_strings(void); # define HTTP_R_ERROR_RECEIVING 103 # define HTTP_R_ERROR_SENDING 102 # define HTTP_R_INCONSISTENT_CONTENT_LENGTH 120 +# define HTTP_R_INVALID_PORT_NUMBER 123 +# define HTTP_R_INVALID_URL_PATH 125 +# define HTTP_R_INVALID_URL_PREFIX 124 # define HTTP_R_MAX_RESP_LEN_EXCEEDED 117 # define HTTP_R_MISSING_ASN1_ENCODING 110 # define HTTP_R_MISSING_CONTENT_TYPE 121 diff --git a/test/http_test.c b/test/http_test.c index f0b12a7dd0..f073dcd7ff 100644 --- a/test/http_test.c +++ b/test/http_test.c @@ -151,6 +151,72 @@ static int test_http_x509(int do_get) return res; } +static int test_http_url_ok(const char *url, const char *exp_host, int exp_ssl) +{ + char *host, *port, *path; + int num, ssl; + int res; + + res = TEST_true(OSSL_HTTP_parse_url(url, &host, &port, &num, &path, &ssl)) + && TEST_str_eq(host, exp_host) + && TEST_str_eq(port, "65535") + && TEST_int_eq(num, 65535) + && TEST_str_eq(path, "/pkix") + && TEST_int_eq(ssl, exp_ssl); + OPENSSL_free(host); + OPENSSL_free(port); + OPENSSL_free(path); + return res; +} + +static int test_http_url_dns(void) +{ + return test_http_url_ok("server:65535/pkix", "server", 0); +} + +static int test_http_url_ipv4(void) +{ + return test_http_url_ok("https://1.2.3.4:65535/pkix", "1.2.3.4", 1); +} + +static int test_http_url_ipv6(void) +{ + return test_http_url_ok("http://[FF01::101]:65535/pkix", "FF01::101", 0); +} + +static int test_http_url_invalid(const char *url) +{ + char *host = "1", *port = "1", *path = "1"; + int num = 1, ssl = 1; + int res; + + res = TEST_false(OSSL_HTTP_parse_url(url, &host, &port, &num, &path, &ssl)) + && TEST_ptr_null(host) + && TEST_ptr_null(port) + && TEST_ptr_null(path); + if (!res) { + OPENSSL_free(host); + OPENSSL_free(port); + OPENSSL_free(path); + } + return res; +} + +static int test_http_url_invalid_prefix(void) +{ + return test_http_url_invalid("htttps://1.2.3.4:65535/pkix"); +} + +static int test_http_url_invalid_port(void) +{ + return test_http_url_invalid("https://1.2.3.4:65536/pkix"); +} + +static int test_http_url_invalid_path(void) +{ + return test_http_url_invalid("https://[FF01::101]pkix"); +} + static int test_http_get_x509(void) { return test_http_x509(1); @@ -177,6 +243,12 @@ int setup_tests(void) if (!TEST_ptr((x509 = load_pem_cert(test_get_argument(0))))) return 1; + ADD_TEST(test_http_url_dns); + ADD_TEST(test_http_url_ipv4); + ADD_TEST(test_http_url_ipv6); + ADD_TEST(test_http_url_invalid_prefix); + ADD_TEST(test_http_url_invalid_port); + ADD_TEST(test_http_url_invalid_path); ADD_TEST(test_http_get_x509); ADD_TEST(test_http_post_x509); return 1; diff --git a/test/recipes/81-test_cmp_cli_data/test_connection.csv b/test/recipes/81-test_cmp_cli_data/test_connection.csv index 7e4775afec..5d1700fa21 100644 --- a/test/recipes/81-test_cmp_cli_data/test_connection.csv +++ b/test/recipes/81-test_cmp_cli_data/test_connection.csv @@ -22,6 +22,9 @@ TBD,server IP address with TLS port, -section,, -server,_SERVER_IP:_SERVER_TLS,, 1,proxy default port, -section,, -server,_SERVER_HOST:_SERVER_PORT, -proxy,127.0.0.1,,,BLANK,,BLANK,,BLANK,,BLANK, -no_proxy,nonmatch.com,-msg_timeout,1 1,proxy missing argument, -section,, -server,_SERVER_HOST:_SERVER_PORT, -proxy,,,,BLANK,,BLANK,,BLANK,,BLANK, -no_proxy,nonmatch.com ,,,,,,,,,,,,,,,,,,,,,,,,, +0,path explicit, -section,, -server,_SERVER_HOST:_SERVER_PORT,,, -path,_SERVER_PATH,BLANK,,BLANK,,BLANK,,BLANK, +0,path overrides -server path, -section,, -server,_SERVER_HOST:_SERVER_PORT/ignored,,, -path,_SERVER_PATH,BLANK,,BLANK,,BLANK,,BLANK, +0,path default -server path, -section,, -server,_SERVER_HOST:_SERVER_PORT/_SERVER_PATH,,, -path,"""",BLANK,,BLANK,,BLANK,,BLANK, 1,path missing argument, -section,,,,,, -path,,BLANK,,BLANK,,BLANK,,BLANK, 1,path wrong, -section,,,,,, -path,/publicweb/cmp/example,BLANK,,BLANK,,BLANK,,BLANK, 0,path with additional '/'s fine according to RFC 3986, -section,,,,,, -path,/_SERVER_PATH////,BLANK,,BLANK,,BLANK,,BLANK