diff options
author | Dr. David von Oheimb <David.von.Oheimb@siemens.com> | 2019-10-30 23:39:35 +0100 |
---|---|---|
committer | Dr. David von Oheimb <David.von.Oheimb@siemens.com> | 2020-02-10 16:49:37 +0100 |
commit | 29f178bddfdbd11218fbcba0b8060297696968e3 (patch) | |
tree | a44efcd919c122d9c6ff38c61b14676b002aa010 /crypto | |
parent | bcbb30afe2ef51c7affaaa7ce4db67e26e7ff6b7 (diff) |
Generalize the HTTP client so far implemented mostly in crypto/ocsp/ocsp_ht.c
The new client has become an independent libcrpyto module in crypto/http/ and
* can handle any types of requests and responses (ASN.1-encoded and plain)
* does not include potentially busy loops when waiting for responses but
* makes use of a new timeout mechanism integrated with socket-based BIO
* supports the use of HTTP proxies and TLS, including HTTPS over proxies
* supports HTTP redirection via codes 301 and 302 for GET requests
* returns more useful diagnostics in various error situations
Also adapts - and strongly simplifies - hitherto uses of HTTP in crypto/ocsp/,
crypto/x509/x_all.c, apps/lib/apps.c, and apps/{ocsp,s_client,s_server}.c
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/10667)
Diffstat (limited to 'crypto')
-rw-r--r-- | crypto/build.info | 2 | ||||
-rw-r--r-- | crypto/cmp/cmp_ctx.c | 2 | ||||
-rw-r--r-- | crypto/cmp/cmp_err.c | 2 | ||||
-rw-r--r-- | crypto/cmp/cmp_local.h | 2 | ||||
-rw-r--r-- | crypto/err/err.c | 1 | ||||
-rw-r--r-- | crypto/err/err_all.c | 2 | ||||
-rw-r--r-- | crypto/err/openssl.ec | 1 | ||||
-rw-r--r-- | crypto/err/openssl.txt | 28 | ||||
-rw-r--r-- | crypto/http/build.info | 2 | ||||
-rw-r--r-- | crypto/http/http_client.c | 1238 | ||||
-rw-r--r-- | crypto/http/http_err.c | 67 | ||||
-rw-r--r-- | crypto/http/http_lib.c | 116 | ||||
-rw-r--r-- | crypto/http/http_local.h | 51 | ||||
-rw-r--r-- | crypto/ocsp/build.info | 2 | ||||
-rw-r--r-- | crypto/ocsp/ocsp_err.c | 5 | ||||
-rw-r--r-- | crypto/ocsp/ocsp_ht.c | 502 | ||||
-rw-r--r-- | crypto/ocsp/ocsp_http.c | 65 | ||||
-rw-r--r-- | crypto/ocsp/ocsp_lib.c | 107 | ||||
-rw-r--r-- | crypto/x509/x_all.c | 29 |
19 files changed, 1591 insertions, 633 deletions
diff --git a/crypto/build.info b/crypto/build.info index 6906c54db2..b21cf3f45a 100644 --- a/crypto/build.info +++ b/crypto/build.info @@ -5,7 +5,7 @@ SUBDIRS=objects buffer bio stack lhash rand evp asn1 pem x509 conf \ md2 md4 md5 sha mdc2 hmac ripemd whrlpool poly1305 \ siphash sm3 des aes rc2 rc4 rc5 idea aria bf cast camellia \ seed sm4 chacha modes bn ec rsa dsa dh sm2 dso engine \ - err comp ocsp cms ts srp cmac ct async ess crmf cmp serializer \ + err comp http ocsp cms ts srp cmac ct async ess crmf cmp serializer \ ffc LIBS=../libcrypto diff --git a/crypto/cmp/cmp_ctx.c b/crypto/cmp/cmp_ctx.c index 0bd12f47ac..fc89ea6bc8 100644 --- a/crypto/cmp/cmp_ctx.c +++ b/crypto/cmp/cmp_ctx.c @@ -819,7 +819,7 @@ int OSSL_CMP_CTX_set_proxyPort(OSSL_CMP_CTX *ctx, int port) * sets the http connect/disconnect callback function to be used for HTTP(S) * returns 1 on success, 0 on error */ -int OSSL_CMP_CTX_set_http_cb(OSSL_CMP_CTX *ctx, OSSL_cmp_http_cb_t cb) +int OSSL_CMP_CTX_set_http_cb(OSSL_CMP_CTX *ctx, OSSL_HTTP_bio_cb_t cb) { if (ctx == NULL) { CMPerr(0, CMP_R_NULL_ARGUMENT); diff --git a/crypto/cmp/cmp_err.c b/crypto/cmp/cmp_err.c index 8b4a6ca708..a6d59f9fc4 100644 --- a/crypto/cmp/cmp_err.c +++ b/crypto/cmp/cmp_err.c @@ -67,6 +67,8 @@ static const ERR_STRING_DATA CMP_str_reasons[] = { {ERR_PACK(ERR_LIB_CMP, 0, CMP_R_NULL_ARGUMENT), "null argument"}, {ERR_PACK(ERR_LIB_CMP, 0, CMP_R_PKISTATUSINFO_NOT_FOUND), "pkistatusinfo not found"}, + {ERR_PACK(ERR_LIB_CMP, 0, CMP_R_POTENTIALLY_INVALID_CERTIFICATE), + "potentially invalid certificate"}, {ERR_PACK(ERR_LIB_CMP, 0, CMP_R_UNEXPECTED_PKIBODY), "unexpected pkibody"}, {ERR_PACK(ERR_LIB_CMP, 0, CMP_R_UNKNOWN_ALGORITHM_ID), "unknown algorithm id"}, diff --git a/crypto/cmp/cmp_local.h b/crypto/cmp/cmp_local.h index b7ab6454b5..f705cb24be 100644 --- a/crypto/cmp/cmp_local.h +++ b/crypto/cmp/cmp_local.h @@ -44,7 +44,7 @@ struct ossl_cmp_ctx_st { int totaltimeout; /* maximum number seconds an enrollment may take, incl. */ /* attempts polling for a response if a 'waiting' PKIStatus is received */ time_t end_time; /* session start time + totaltimeout */ - OSSL_cmp_http_cb_t http_cb; + OSSL_HTTP_bio_cb_t http_cb; void *http_cb_arg; /* allows to store optional argument to cb */ /* server authentication */ diff --git a/crypto/err/err.c b/crypto/err/err.c index e77cfe83cf..efc6273350 100644 --- a/crypto/err/err.c +++ b/crypto/err/err.c @@ -76,6 +76,7 @@ static ERR_STRING_DATA ERR_str_libraries[] = { {ERR_PACK(ERR_LIB_ESS, 0, 0), "ESS routines"}, {ERR_PACK(ERR_LIB_PROV, 0, 0), "Provider routines"}, {ERR_PACK(ERR_LIB_OSSL_SERIALIZER, 0, 0), "SERIALIZER routines"}, + {ERR_PACK(ERR_LIB_HTTP, 0, 0), "HTTP routines"}, {0, NULL}, }; diff --git a/crypto/err/err_all.c b/crypto/err/err_all.c index 13bef4a7a8..49d4e3616d 100644 --- a/crypto/err/err_all.c +++ b/crypto/err/err_all.c @@ -30,6 +30,7 @@ #include "internal/dso.h" #include <openssl/engineerr.h> #include <openssl/uierr.h> +#include <openssl/httperr.h> #include <openssl/ocsperr.h> #include <openssl/err.h> #include <openssl/tserr.h> @@ -85,6 +86,7 @@ int err_load_crypto_strings_int(void) # ifndef OPENSSL_NO_ENGINE ERR_load_ENGINE_strings() == 0 || # endif + ERR_load_HTTP_strings() == 0 || # ifndef OPENSSL_NO_OCSP ERR_load_OCSP_strings() == 0 || # endif diff --git a/crypto/err/openssl.ec b/crypto/err/openssl.ec index 485c0c89ce..1ec7bb1162 100644 --- a/crypto/err/openssl.ec +++ b/crypto/err/openssl.ec @@ -41,6 +41,7 @@ L ESS include/openssl/ess.h crypto/ess/ess_err.c L PROP include/internal/property.h crypto/property/property_err.c L PROV providers/common/include/prov/providercommon.h providers/common/provider_err.c L OSSL_SERIALIZER include/openssl/serializer.h crypto/serializer/serializer_err.c +L HTTP include/openssl/http.h crypto/http/http_err.c # additional header files to be scanned for function names L NONE include/openssl/x509_vfy.h NONE diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt index 84a8adc52c..a663bd2858 100644 --- a/crypto/err/openssl.txt +++ b/crypto/err/openssl.txt @@ -940,11 +940,9 @@ OCSP_F_OCSP_CHECK_IDS:107:ocsp_check_ids OCSP_F_OCSP_CHECK_ISSUER:108:ocsp_check_issuer OCSP_F_OCSP_CHECK_VALIDITY:115:OCSP_check_validity OCSP_F_OCSP_MATCH_ISSUERID:109:ocsp_match_issuerid -OCSP_F_OCSP_PARSE_URL:114:OCSP_parse_url OCSP_F_OCSP_REQUEST_SIGN:110:OCSP_request_sign OCSP_F_OCSP_REQUEST_VERIFY:116:OCSP_request_verify OCSP_F_OCSP_RESPONSE_GET1_BASIC:111:OCSP_response_get1_basic -OCSP_F_PARSE_HTTP_LINE1:118:parse_http_line1 OSSL_STORE_F_FILE_CTRL:129:file_ctrl OSSL_STORE_F_FILE_FIND:138:file_find OSSL_STORE_F_FILE_GET_PASS:118:file_get_pass @@ -2100,6 +2098,7 @@ CMP_R_MULTIPLE_SAN_SOURCES:102:multiple san sources CMP_R_NO_STDIO:194:no stdio CMP_R_NULL_ARGUMENT:103:null argument CMP_R_PKISTATUSINFO_NOT_FOUND:132:pkistatusinfo not found +CMP_R_POTENTIALLY_INVALID_CERTIFICATE:139:potentially invalid certificate CMP_R_UNEXPECTED_PKIBODY:133:unexpected pkibody CMP_R_UNKNOWN_ALGORITHM_ID:134:unknown algorithm id CMP_R_UNKNOWN_CERT_TYPE:135:unknown cert type @@ -2527,6 +2526,28 @@ EVP_R_WRAP_MODE_NOT_ALLOWED:170:wrap mode not allowed EVP_R_WRONG_FINAL_BLOCK_LENGTH:109:wrong final block length EVP_R_XTS_DATA_UNIT_IS_TOO_LARGE:191:xts data unit is too large EVP_R_XTS_DUPLICATED_KEYS:192:xts duplicated keys +HTTP_R_ASN1_LEN_EXCEEDS_MAX_RESP_LEN:108:asn1 len exceeds max resp len +HTTP_R_CONNECT_FAILURE:100:connect failure +HTTP_R_ERROR_PARSING_ASN1_LENGTH:109:error parsing asn1 length +HTTP_R_ERROR_PARSING_CONTENT_LENGTH:119:error parsing content length +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_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 +HTTP_R_MISSING_REDIRECT_LOCATION:111:missing redirect location +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_SERVER_RESPONSE_PARSE_ERROR:104:server response parse error +HTTP_R_SERVER_SENT_ERROR:105:server sent error +HTTP_R_SERVER_SENT_WRONG_HTTP_VERSION:106:server sent wrong http version +HTTP_R_STATUS_CODE_UNSUPPORTED:114:status code unsupported +HTTP_R_TLS_NOT_ENABLED:107:tls not enabled +HTTP_R_TOO_MANY_REDIRECTIONS:115:too many redirections +HTTP_R_UNEXPECTED_CONTENT_TYPE:118:unexpected content type KDF_R_BAD_ENCODING:122:bad encoding KDF_R_BAD_LENGTH:123:bad length KDF_R_BOTH_MODE_AND_MODE_INT:127:both mode and mode int @@ -2561,7 +2582,6 @@ OCSP_R_CERTIFICATE_VERIFY_ERROR:101:certificate verify error OCSP_R_DIGEST_ERR:102:digest err OCSP_R_ERROR_IN_NEXTUPDATE_FIELD:122:error in nextupdate field OCSP_R_ERROR_IN_THISUPDATE_FIELD:123:error in thisupdate field -OCSP_R_ERROR_PARSING_URL:121:error parsing url OCSP_R_MISSING_OCSPSIGNING_USAGE:103:missing ocspsigning usage OCSP_R_NEXTUPDATE_BEFORE_THISUPDATE:124:nextupdate before thisupdate OCSP_R_NOT_BASIC_RESPONSE:104:not basic response @@ -2575,8 +2595,6 @@ OCSP_R_REQUEST_NOT_SIGNED:128:request not signed OCSP_R_RESPONSE_CONTAINS_NO_REVOCATION_DATA:111:\ response contains no revocation data OCSP_R_ROOT_CA_NOT_TRUSTED:112:root ca not trusted -OCSP_R_SERVER_RESPONSE_ERROR:114:server response error -OCSP_R_SERVER_RESPONSE_PARSE_ERROR:115:server response parse error OCSP_R_SIGNATURE_FAILURE:117:signature failure OCSP_R_SIGNER_CERTIFICATE_NOT_FOUND:118:signer certificate not found OCSP_R_STATUS_EXPIRED:125:status expired diff --git a/crypto/http/build.info b/crypto/http/build.info new file mode 100644 index 0000000000..b4626b13de --- /dev/null +++ b/crypto/http/build.info @@ -0,0 +1,2 @@ +LIBS=../../libcrypto +SOURCE[../../libcrypto]=http_client.c http_err.c http_lib.c diff --git a/crypto/http/http_client.c b/crypto/http/http_client.c new file mode 100644 index 0000000000..424b4c3922 --- /dev/null +++ b/crypto/http/http_client.c @@ -0,0 +1,1238 @@ +/* + * Copyright 2001-2020 The OpenSSL Project Authors. All Rights Reserved. + * Copyright Siemens AG 2018-2020 + * + * 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 "e_os.h" +#include <stdio.h> +#include <stdlib.h> +#include "crypto/ctype.h" +#include <string.h> +#include <openssl/asn1.h> +#include <openssl/evp.h> +#include <openssl/err.h> +#include <openssl/httperr.h> +#include <openssl/cmperr.h> +#include <openssl/buffer.h> +#include <openssl/http.h> +#include "internal/sockets.h" +#include "internal/cryptlib.h" + +#include "http_local.h" + +#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_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 */ + +struct ossl_http_req_ctx_st { + int state; /* Current I/O state */ + unsigned char *iobuf; /* Line buffer */ + int iobuflen; /* Line buffer length */ + BIO *wbio; /* BIO to send request to */ + BIO *rbio; /* BIO to read response from */ + BIO *mem; /* Memory BIO response is built into */ + int method_GET; /* HTTP method "GET" or "POST" */ + const char *expected_ct; /* expected Content-Type, or NULL */ + int expect_asn1; /* response must be ASN.1-encoded */ + unsigned long resp_len; /* length of response */ + unsigned long max_resp_len; /* Maximum length of response */ + time_t max_time; /* Maximum end time of the transfer, or 0 */ + char *redirection_url; /* Location given with HTTP status 301/302 */ +}; + +#define HTTP_DEFAULT_MAX_LINE_LENGTH (4 * 1024) +#define HTTP_DEFAULT_MAX_RESP_LEN (100 * 1024) + +/* HTTP states */ + +#define OHS_NOREAD 0x1000 /* If set no reading should be performed */ +#define OHS_ERROR (0 | OHS_NOREAD) /* Error condition */ +#define OHS_FIRSTLINE 1 /* First line being read */ +#define OHS_REDIRECT 0xa /* Looking for redirection location */ +#define OHS_HEADERS 2 /* MIME headers being read */ +#define OHS_ASN1_HEADER 3 /* HTTP initial header (tag+length) being read */ +#define OHS_CONTENT 4 /* HTTP content octets being read */ +#define OHS_WRITE_INIT (5 | OHS_NOREAD) /* 1st call: ready to start I/O */ +#define OHS_WRITE (6 | OHS_NOREAD) /* Request being sent */ +#define OHS_FLUSH (7 | OHS_NOREAD) /* Request being flushed */ +#define OHS_DONE (8 | OHS_NOREAD) /* Completed */ +#define OHS_HTTP_HEADER (9 | OHS_NOREAD) /* Headers set, w/o final \r\n */ + +OSSL_HTTP_REQ_CTX *OSSL_HTTP_REQ_CTX_new(BIO *wbio, BIO *rbio, + int method_GET, int maxline, + unsigned long max_resp_len, + int timeout, + const char *expected_content_type, + int expect_asn1) +{ + OSSL_HTTP_REQ_CTX *rctx; + + if (wbio == NULL || rbio == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + + if ((rctx = OPENSSL_zalloc(sizeof(*rctx))) == NULL) + return NULL; + rctx->state = OHS_ERROR; + rctx->iobuflen = maxline > 0 ? maxline : HTTP_DEFAULT_MAX_LINE_LENGTH; + rctx->iobuf = OPENSSL_malloc(rctx->iobuflen); + rctx->wbio = wbio; + rctx->rbio = rbio; + rctx->mem = BIO_new(BIO_s_mem()); + if (rctx->iobuf == NULL || rctx->mem == NULL) { + OSSL_HTTP_REQ_CTX_free(rctx); + return NULL; + } + rctx->method_GET = method_GET; + rctx->expected_ct = expected_content_type; + rctx->expect_asn1 = expect_asn1; + rctx->resp_len = 0; + OSSL_HTTP_REQ_CTX_set_max_response_length(rctx, max_resp_len); + rctx->max_time = timeout > 0 ? time(NULL) + timeout : 0; + return rctx; +} + +void OSSL_HTTP_REQ_CTX_free(OSSL_HTTP_REQ_CTX *rctx) +{ + if (rctx == NULL) + return; + BIO_free(rctx->mem); /* this may indirectly call ERR_clear_error() */ + OPENSSL_free(rctx->iobuf); + OPENSSL_free(rctx); +} + +BIO *OSSL_HTTP_REQ_CTX_get0_mem_bio(OSSL_HTTP_REQ_CTX *rctx) +{ + if (rctx == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + return rctx->mem; +} + +void OSSL_HTTP_REQ_CTX_set_max_response_length(OSSL_HTTP_REQ_CTX *rctx, + unsigned long len) +{ + if (rctx == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return; + } + rctx->max_resp_len = len != 0 ? len : HTTP_DEFAULT_MAX_RESP_LEN; +} + +/* + * Create HTTP header using given op and path (or "/" in case path is NULL). + * Server name (and port) must be given if and only if plain HTTP proxy is used. + */ +int OSSL_HTTP_REQ_CTX_header(OSSL_HTTP_REQ_CTX *rctx, const char *server, + const char *port, const char *path) +{ + if (rctx == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + if (BIO_printf(rctx->mem, "%s ", rctx->method_GET ? "GET" : "POST") <= 0) + return 0; + + if (server != NULL) { /* HTTP (but not HTTPS) proxy is used */ + /* + * Section 5.1.2 of RFC 1945 states that the absoluteURI form is only + * allowed when using a proxy + */ + if (BIO_printf(rctx->mem, "http://%s", server) <= 0) + return 0; + if (port != NULL && BIO_printf(rctx->mem, ":%s", port) <= 0) + return 0; + } + + /* Make sure path includes a forward slash */ + if (path == NULL) + path = "/"; + if (path[0] != '/' && BIO_printf(rctx->mem, "/") <= 0) + return 0; + + if (BIO_printf(rctx->mem, "%s "HTTP_PREFIX"1.0\r\n", path) <= 0) + return 0; + rctx->state = OHS_HTTP_HEADER; + return 1; +} + +int OSSL_HTTP_REQ_CTX_add1_header(OSSL_HTTP_REQ_CTX *rctx, + const char *name, const char *value) +{ + if (rctx == NULL || name == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + if (BIO_puts(rctx->mem, name) <= 0) + return 0; + if (value != NULL) { + if (BIO_write(rctx->mem, ": ", 2) != 2) + return 0; + 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; +} + +static int OSSL_HTTP_REQ_CTX_content(OSSL_HTTP_REQ_CTX *rctx, + const char *content_type, BIO *req_mem) +{ + const unsigned char *req; + long req_len; + + if (rctx == NULL || req_mem == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + if (content_type != NULL + && BIO_printf(rctx->mem, "Content-Type: %s\r\n", content_type) <= 0) + return 0; + + 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; +} + +BIO *HTTP_asn1_item2bio(const ASN1_ITEM *it, ASN1_VALUE *val) +{ + BIO *res; + + if (it == NULL || val == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + + if ((res = BIO_new(BIO_s_mem())) == NULL) + return NULL; + if (ASN1_item_i2d_bio(it, res, val) <= 0) { + BIO_free(res); + res = NULL; + } + return res; +} + +int OSSL_HTTP_REQ_CTX_i2d(OSSL_HTTP_REQ_CTX *rctx, const char *content_type, + const ASN1_ITEM *it, ASN1_VALUE *req) +{ + BIO *mem; + int res; + + if (rctx == NULL || it == NULL || req == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + res = (mem = HTTP_asn1_item2bio(it, req)) != NULL + && OSSL_HTTP_REQ_CTX_content(rctx, content_type, mem); + BIO_free(mem); + return res; +} + +static int OSSL_HTTP_REQ_CTX_add1_headers(OSSL_HTTP_REQ_CTX *rctx, + const STACK_OF(CONF_VALUE) *headers, + const char *host) +{ + int i; + int add_host = 1; + CONF_VALUE *hdr; + + for (i = 0; i < sk_CONF_VALUE_num(headers); i++) { + hdr = sk_CONF_VALUE_value(headers, i); + if (add_host && strcasecmp("host", hdr->name) == 0) + add_host = 0; + if (!OSSL_HTTP_REQ_CTX_add1_header(rctx, hdr->name, hdr->value)) + return 0; + } + + if (add_host && !OSSL_HTTP_REQ_CTX_add1_header(rctx, "Host", host)) + return 0; + 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. + */ +OSSL_HTTP_REQ_CTX *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 maxline, unsigned long max_resp_len, + int timeout, + const char *expected_content_type, + int expect_asn1) +{ + OSSL_HTTP_REQ_CTX *rctx; + + if (use_http_proxy && (server == NULL || port == NULL)) { + HTTPerr(0, 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, req_mem == NULL, maxline, + max_resp_len, timeout, + expected_content_type, expect_asn1)) + == NULL) + return NULL; + + if (OSSL_HTTP_REQ_CTX_header(rctx, use_http_proxy ? server : NULL, + port, path) + && OSSL_HTTP_REQ_CTX_add1_headers(rctx, headers, server) + && (req_mem == NULL + || OSSL_HTTP_REQ_CTX_content(rctx, content_type, req_mem))) + return rctx; + + OSSL_HTTP_REQ_CTX_free(rctx); + return NULL; +} + +/* + * Parse first HTTP response line. This should be like this: "HTTP/1.0 200 OK". + * We need to obtain the numeric code and (optional) informational message. + */ + +static int parse_http_line1(char *line) +{ + int retcode; + char *code, *reason, *end; + + /* Skip to first whitespace (past protocol info) */ + for (code = line; *code != '\0' && !ossl_isspace(*code); code++) + continue; + if (*code == '\0') { + HTTPerr(0, HTTP_R_SERVER_RESPONSE_PARSE_ERROR); + return 0; + } + + /* Skip past whitespace to start of response code */ + while (*code != '\0' && ossl_isspace(*code)) + code++; + + if (*code == '\0') { + HTTPerr(0, HTTP_R_SERVER_RESPONSE_PARSE_ERROR); + return 0; + } + + /* Find end of response code: first whitespace after start of code */ + for (reason = code; *reason != '\0' && !ossl_isspace(*reason); reason++) + continue; + + if (*reason == '\0') { + HTTPerr(0, HTTP_R_SERVER_RESPONSE_PARSE_ERROR); + return 0; + } + + /* Set end of response code and start of message */ + *reason++ = '\0'; + + /* Attempt to parse numeric code */ + retcode = strtoul(code, &end, 10); + + if (*end != '\0') + return 0; + + /* Skip over any leading whitespace in message */ + while (*reason != '\0' && ossl_isspace(*reason)) + reason++; + + if (*reason != '\0') { + /* + * Finally zap any trailing whitespace in message (include CRLF) + */ + + /* chop any trailing whitespace from reason */ + /* We know reason has a non-whitespace character so this is OK */ + for (end = reason + strlen(reason) - 1; ossl_isspace(*end); end--) + *end = '\0'; + } + + switch (retcode) { + case HTTP_STATUS_CODE_OK: + case HTTP_STATUS_CODE_MOVED_PERMANENTLY: + case HTTP_STATUS_CODE_FOUND: + return retcode; + default: + if (retcode < 400) + HTTPerr(0, HTTP_R_STATUS_CODE_UNSUPPORTED); + else + HTTPerr(0, HTTP_R_SERVER_SENT_ERROR); + if (*reason == '\0') + ERR_add_error_data(2, "Code=", code); + else + ERR_add_error_data(4, "Code=", code, ",Reason=", reason); + return 0; + } +} + +static int check_set_resp_len(OSSL_HTTP_REQ_CTX *rctx, unsigned long len) +{ + const char *tag = NULL; + unsigned long val = 0; + + if (len > rctx->max_resp_len) { + HTTPerr(0, HTTP_R_MAX_RESP_LEN_EXCEEDED); + tag = ",max="; + val = rctx->max_resp_len; + } + if (rctx->resp_len != 0 && rctx->resp_len != len) { + HTTPerr(0, HTTP_R_INCONSISTENT_CONTENT_LENGTH); + tag = ",before="; + val = rctx->resp_len; + } + if (tag != NULL) { + char len_str[32]; + char str[32]; + + BIO_snprintf(len_str, sizeof(len_str), "%lu", len); + BIO_snprintf(str, sizeof(str), "%lu", val); + ERR_add_error_data(4, "length=", len_str, tag, str); + return 0; + } + rctx->resp_len = len; + return 1; +} + +/* + * Try exchanging request and response via HTTP on (non-)blocking BIO in rctx. + * Returns 1 on success, 0 on error or redirection, -1 on BIO_should_retry. + */ +int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx) +{ + int i; + long n, n_to_send = 0; + unsigned long resp_len; + const unsigned char *p; + char *key, *value, *line_end = NULL; + + if (rctx == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + rctx->redirection_url = NULL; + next_io: + if ((rctx->state & OHS_NOREAD) == 0) { + n = BIO_read(rctx->rbio, rctx->iobuf, rctx->iobuflen); + if (n <= 0) { + if (BIO_should_retry(rctx->rbio)) + return -1; + return 0; + } + + /* Write data to memory BIO */ + if (BIO_write(rctx->mem, rctx->iobuf, n) != n) + return 0; + } + + switch (rctx->state) { + case OHS_HTTP_HEADER: + /* Last operation was adding headers: need a final \r\n */ + if (BIO_write(rctx->mem, "\r\n", 2) != 2) { + rctx->state = OHS_ERROR; + return 0; + } + rctx->state = OHS_WRITE_INIT; + + /* fall thru */ + case OHS_WRITE_INIT: + n_to_send = BIO_get_mem_data(rctx->mem, NULL); + rctx->state = OHS_WRITE; + + /* fall thru */ + case OHS_WRITE: + n = BIO_get_mem_data(rctx->mem, &p); + + i = BIO_write(rctx->wbio, p + (n - n_to_send), n_to_send); + + if (i <= 0) { + if (BIO_should_retry(rctx->wbio)) + return -1; + rctx->state = OHS_ERROR; + return 0; + } + + n_to_send -= i; + + if (n_to_send > 0) + goto next_io; + + rctx->state = OHS_FLUSH; + + (void)BIO_reset(rctx->mem); + + /* fall thru */ + case OHS_FLUSH: + + i = BIO_flush(rctx->wbio); + + if (i > 0) { + rctx->state = OHS_FIRSTLINE; + goto next_io; + } + + if (BIO_should_retry(rctx->wbio)) + return -1; + + rctx->state = OHS_ERROR; + return 0; + + case OHS_ERROR: + return 0; + + case OHS_FIRSTLINE: + case OHS_HEADERS: + case OHS_REDIRECT: + + /* Attempt to read a line in */ + next_line: + /* + * Due to strange memory BIO behavior with BIO_gets we have to check + * there's a complete line in there before calling BIO_gets or we'll + * just get a partial read. + */ + n = BIO_get_mem_data(rctx->mem, &p); + if (n <= 0 || memchr(p, '\n', n) == 0) { + if (n >= rctx->iobuflen) { + rctx->state = OHS_ERROR; + return 0; + } + goto next_io; + } + n = BIO_gets(rctx->mem, (char *)rctx->iobuf, rctx->iobuflen); + + if (n <= 0) { + if (BIO_should_retry(rctx->mem)) + goto next_io; + rctx->state = OHS_ERROR; + return 0; + } + + /* Don't allow excessive lines */ + if (n == rctx->iobuflen) { + HTTPerr(0, HTTP_R_RESPONSE_LINE_TOO_LONG); + rctx->state = OHS_ERROR; + return 0; + } + + /* First line */ + if (rctx->state == OHS_FIRSTLINE) { + switch (parse_http_line1((char *)rctx->iobuf)) { + case HTTP_STATUS_CODE_OK: + rctx->state = OHS_HEADERS; + goto next_line; + case HTTP_STATUS_CODE_MOVED_PERMANENTLY: + case HTTP_STATUS_CODE_FOUND: /* i.e., moved temporarily */ + if (rctx->method_GET) { + rctx->state = OHS_REDIRECT; + goto next_line; + } + HTTPerr(0, HTTP_R_REDIRECTION_NOT_ENABLED); + /* redirection is not supported/recommended for POST */ + /* fall through */ + default: + rctx->state = OHS_ERROR; + return 0; + } + } + key = (char *)rctx->iobuf; + value = strchr(key, ':'); + if (value != NULL) { + *(value++) = '\0'; + while (ossl_isspace(*value)) + value++; + line_end = strchr(value, '\r'); + if (line_end == NULL) + line_end = strchr(value, '\n'); + if (line_end != NULL) + *line_end = '\0'; + } + if (value != NULL && line_end != NULL) { + if (rctx->state == OHS_REDIRECT && strcmp(key, "Location") == 0) { + rctx->redirection_url = value; + return 0; + } + if (rctx->expected_ct != NULL && strcmp(key, "Content-Type") == 0) { + if (strcmp(rctx->expected_ct, value) != 0) { + HTTPerr(0, HTTP_R_UNEXPECTED_CONTENT_TYPE); + ERR_add_error_data(4, "expected=", rctx->expected_ct, + ",actual=", value); + return 0; + } + rctx->expected_ct = NULL; /* content-type has been found */ + } + if (strcmp(key, "Content-Length") == 0) { + resp_len = strtoul(value, &line_end, 10); + if (line_end == value || *line_end != '\0') { + HTTPerr(0, HTTP_R_ERROR_PARSING_CONTENT_LENGTH); + ERR_add_error_data(2, "input=", value); + return 0; + } + if (!check_set_resp_len(rctx, resp_len)) + return 0; + } + } + + /* Look for blank line: end of headers */ + for (p = rctx->iobuf; *p != '\0' ; p++) { + if (*p != '\r' && *p != '\n') + break; + } + if (*p != '\0') /* not end of headers */ + goto next_line; + + if (rctx->expected_ct != NULL) { + HTTPerr(0, HTTP_R_MISSING_CONTENT_TYPE); + ERR_add_error_data(2, "expected=", rctx->expected_ct); + return 0; + } + if (rctx->state == OHS_REDIRECT) { |