diff options
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) { + /* http status code indicated redirect but there was no Location */ + HTTPerr(0, HTTP_R_MISSING_REDIRECT_LOCATION); + return 0; + } + + if (!rctx->expect_asn1) { + rctx->state = OHS_CONTENT; + goto content; + } + + rctx->state = OHS_ASN1_HEADER; + + /* Fall thru */ + case OHS_ASN1_HEADER: + /* + * Now reading ASN1 header: can read at least 2 bytes which is enough + * for ASN1 SEQUENCE header and either length field or at least the + * length of the length field. + */ + n = BIO_get_mem_data(rctx->mem, &p); + if (n < 2) + goto next_io; + + /* Check it is an ASN1 SEQUENCE */ + if (*p++ != (V_ASN1_SEQUENCE | V_ASN1_CONSTRUCTED)) { + HTTPerr(0, HTTP_R_MISSING_ASN1_ENCODING); + return 0; + } + + /* Check out length field */ + if ((*p & 0x80) != 0) { + /* + * If MSB set on initial length octet we can now always read 6 + * octets: make sure we have them. + */ + if (n < 6) + goto next_io; + n = *p & 0x7F; + /* Not NDEF or excessive length */ + if (n == 0 || (n > 4)) { + HTTPerr(0, HTTP_R_ERROR_PARSING_ASN1_LENGTH); + return 0; + } + p++; + resp_len = 0; + for (i = 0; i < n; i++) { + resp_len <<= 8; + resp_len |= *p++; + } + resp_len += n + 2; + } else { + resp_len = *p + 2; + } + if (!check_set_resp_len(rctx, resp_len)) + return 0; + + content: + rctx->state = OHS_CONTENT; + + /* Fall thru */ + case OHS_CONTENT: + default: + n = BIO_get_mem_data(rctx->mem, NULL); + if (n < (long)rctx->resp_len /* may be 0 if no Content-Type or ASN.1 */) + goto next_io; + + rctx->state = OHS_DONE; + return 1; + } +} + +#ifndef OPENSSL_NO_SOCK + +/* set up a new connection BIO, to HTTP server or to HTTP(S) proxy if given */ +static BIO *HTTP_new_bio(const char *server, const char *server_port, + const char *proxy, const char *proxy_port) +{ + const char *host = server; + const char *port = server_port; + BIO *cbio; + + if (server == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + + if (proxy != NULL) { + host = proxy; + port = proxy_port; + } + cbio = BIO_new_connect(host); + if (cbio == NULL) + goto end; + if (port != NULL) + (void)BIO_set_conn_port(cbio, port); + + end: + return cbio; +} + +static ASN1_VALUE *BIO_mem_d2i(BIO *mem, const ASN1_ITEM *it) +{ + const unsigned char *p; + long len = BIO_get_mem_data(mem, &p); + ASN1_VALUE *resp = ASN1_item_d2i(NULL, &p, len, it); + + if (resp == NULL) + HTTPerr(0, HTTP_R_SERVER_RESPONSE_PARSE_ERROR); + return resp; +} + +static BIO *OSSL_HTTP_REQ_CTX_transfer(OSSL_HTTP_REQ_CTX *rctx) +{ + int sending = 1; + int rv; + + if (rctx == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + + for (;;) { + rv = OSSL_HTTP_REQ_CTX_nbio(rctx); + if (rv != -1) + break; + /* BIO_should_retry was true */ + sending = 0; + /* will not actually wait if rctx->max_time == 0 */ + if (BIO_wait(rctx->rbio, rctx->max_time) <= 0) + return NULL; + } + + if (rv == 0) { + if (rctx->redirection_url == NULL) { /* an error occurred */ + if (sending && (rctx->state & OHS_NOREAD) != 0) + HTTPerr(0, HTTP_R_ERROR_SENDING); + else + HTTPerr(0, HTTP_R_ERROR_RECEIVING); + } + return NULL; + } + if (!BIO_up_ref(rctx->mem)) + return NULL; + return rctx->mem; +} + +/* Exchange ASN.1-encoded request and response via HTTP on (non-)blocking BIO */ +ASN1_VALUE *OSSL_HTTP_REQ_CTX_sendreq_d2i(OSSL_HTTP_REQ_CTX *rctx, + const ASN1_ITEM *it) +{ + if (rctx == NULL || it == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + return BIO_mem_d2i(OSSL_HTTP_REQ_CTX_transfer(rctx), it); +} + +static int update_timeout(int timeout, time_t start_time) +{ + long elapsed_time; + + if (timeout == 0) + return 0; + elapsed_time = (long)(time(NULL) - start_time); /* this might overflow */ + return timeout <= elapsed_time ? -1 : timeout - elapsed_time; +} + +/*- + * Exchange HTTP request and response with the given server. + * If req_mem == NULL then use GET and ignore content_type, else POST. + * The redirection_url output (freed by caller) parameter is used only for GET. + * + * Typically the bio and rbio parameters are NULL and a network BIO is created + * internally for connecting to the given server and port, optionally via a + * proxy and its port, and is then used for exchanging the request and response. + * If bio is given and rbio is NULL then this BIO is used instead. + * If both bio and rbio are given (which may be memory BIOs for instance) + * then no explicit connection is attempted, + * bio is used for writing the request, and rbio for reading the response. + * + * bio_update_fn is an optional BIO connect/disconnect callback function, + * which has the prototype + * BIO *(*OSSL_HTTP_bio_cb_t) (BIO *bio, void *arg, int conn, int detail); + * The callback may modify the HTTP BIO provided in the bio argument, + * whereby it may make use of any custom defined argument 'arg'. + * During connection establishment, just after BIO_connect_retry(), + * the callback function is invoked with the 'conn' argument being 1 + * 'detail' indicating whether a HTTPS (i.e., TLS) connection is requested. + * On disconnect 'conn' is 0 and 'detail' indicates that no error occurred. + * For instance, on connect the funct may prepend a TLS BIO to implement HTTPS; + * after disconnect it may do some error diagnostics and/or specific cleanup. + * The function should return NULL to indicate failure. + * After disconnect the modified BIO will be deallocated using BIO_free_all(). + */ +BIO *OSSL_HTTP_transfer(const char *server, const char *port, const char *path, + int use_ssl, const char *proxy, const char *proxy_port, + BIO *bio, BIO *rbio, + OSSL_HTTP_bio_cb_t bio_update_fn, void *arg, + 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_ct, int expect_asn1, + char **redirection_url) +{ + time_t start_time = timeout > 0 ? time(NULL) : 0; + BIO *cbio; /* = bio if present, used as connection BIO if rbio is NULL */ + OSSL_HTTP_REQ_CTX *rctx; + BIO *resp = NULL; + + if (redirection_url != NULL) + *redirection_url = NULL; /* do this beforehand to prevent dbl free */ + + if (use_ssl && bio_update_fn == NULL) { + HTTPerr(0, HTTP_R_TLS_NOT_ENABLED); + return NULL; + } + if (rbio != NULL && (bio == NULL || bio_update_fn != NULL)) { + HTTPerr(0, ERR_R_PASSED_INVALID_ARGUMENT); + return NULL; + } + /* remaining parameters are checked indirectly by the functions called */ + + if (bio != NULL) + cbio = bio; + else if ((cbio = HTTP_new_bio(server, port, proxy, proxy_port)) == NULL) + return NULL; + + (void)ERR_set_mark(); /* prepare removing any spurious libssl errors */ + if (rbio == NULL && BIO_connect_retry(cbio, timeout) <= 0) + goto end; + /* now timeout is guaranteed to be >= 0 */ + + /* callback can be used to wrap or prepend TLS session */ + if (bio_update_fn != NULL) { + BIO *orig_bio = cbio; + cbio = (*bio_update_fn)(cbio, arg, 1 /* connect */, use_ssl); + if (cbio == NULL) { + cbio = orig_bio; + goto end; + } + } + + rctx = HTTP_REQ_CTX_new(cbio, rbio != NULL ? rbio : cbio, + !use_ssl && proxy != NULL, server, port, path, + headers, content_type, req_mem, maxline, + max_resp_len, update_timeout(timeout, start_time), + expected_ct, expect_asn1); + if (rctx == NULL) + goto end; + + resp = OSSL_HTTP_REQ_CTX_transfer(rctx); + if (resp == NULL) { + if (rctx->redirection_url != NULL) { + if (redirection_url == NULL) + HTTPerr(0, HTTP_R_REDIRECTION_NOT_ENABLED); + else + /* may be NULL if out of memory: */ + *redirection_url = OPENSSL_strdup(rctx->redirection_url); + } else { + char buf[200]; + unsigned long err = ERR_peek_error(); + int lib = ERR_GET_LIB(err); + int reason = ERR_GET_REASON(err); + + if (lib == ERR_LIB_SSL || lib == ERR_LIB_HTTP + || (lib == ERR_LIB_BIO && reason == BIO_R_CONNECT_TIMEOUT) + || (lib == ERR_LIB_BIO && reason == BIO_R_CONNECT_ERROR) + || (lib == ERR_LIB_CMP + && reason == CMP_R_POTENTIALLY_INVALID_CERTIFICATE)) { + BIO_snprintf(buf, 200, "server=%s:%s", server, port); + ERR_add_error_data(1, buf); + if (err == 0) { + BIO_snprintf(buf, 200, "server has disconnected%s", + use_ssl ? " violating the protocol" : + ", likely because it requires the use of TLS"); + ERR_add_error_data(1, buf); + } + } + } + } + OSSL_HTTP_REQ_CTX_free(rctx); + + /* callback can be used to clean up TLS session */ + if (bio_update_fn != NULL + && (*bio_update_fn)(cbio, arg, 0, resp != NULL) == NULL) { + BIO_free(resp); + resp = NULL; + } + + end: + /* + * 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 (bio == NULL) /* cbio was not provided by caller */ + BIO_free_all(cbio); + + if (resp != NULL) + /* remove any spurious error queue entries by ssl_add_cert_chain() */ + (void)ERR_pop_to_mark(); + else + (void)ERR_clear_last_mark(); + + return resp; +} + +static int redirection_ok(int n_redir, const char *old_url, const char *new_url) +{ + static const char https[] = "https:"; + int https_len = 6; /* strlen(https) */ + + if (n_redir >= HTTP_VERSION_MAX_REDIRECTIONS) { + HTTPerr(0, HTTP_R_TOO_MANY_REDIRECTIONS); + return 0; + } + if (*new_url == '/') /* redirection to same server => same protocol */ + return 1; + if (strncmp(old_url, https, https_len) == 0 && + strncmp(new_url, https, https_len) != 0) { + HTTPerr(0, HTTP_R_REDIRECTION_FROM_HTTPS_TO_HTTP); + return 0; + } + return 1; +} + +/* Get data via HTTP from server at given URL, potentially with redirection */ +BIO *OSSL_HTTP_get(const char *url, const char *proxy, const char *proxy_port, + BIO *bio, BIO *rbio, + OSSL_HTTP_bio_cb_t bio_update_fn, void *arg, + const STACK_OF(CONF_VALUE) *headers, + int maxline, unsigned long max_resp_len, int timeout, + const char *expected_content_type, int expect_asn1) +{ + time_t start_time = timeout > 0 ? time(NULL) : 0; + char *current_url, *redirection_url; + int n_redirs = 0; + char *host; + char *port; + char *path; + int use_ssl; + BIO *resp = NULL; + + if (url == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + if ((current_url = OPENSSL_strdup(url)) == NULL) + return NULL; + + for (;;) { + if (!OSSL_HTTP_parse_url(current_url, &host, &port, &path, &use_ssl)) + break; + + new_rpath: + resp = OSSL_HTTP_transfer(host, port, path, use_ssl, proxy, proxy_port, + bio, rbio, + bio_update_fn, arg, headers, NULL, NULL, + maxline, max_resp_len, + update_timeout(timeout, start_time), + expected_content_type, expect_asn1, + &redirection_url); + OPENSSL_free(path); + if (resp == NULL && redirection_url != NULL) { + if (redirection_ok(++n_redirs, current_url, redirection_url)) { + (void)BIO_reset(bio); + OPENSSL_free(current_url); + current_url = redirection_url; + if (*redirection_url == '/') { /* redirection to same server */ + path = OPENSSL_strdup(redirection_url); + goto new_rpath; + } + OPENSSL_free(host); + OPENSSL_free(port); + continue; + } + OPENSSL_free(redirection_url); + } + OPENSSL_free(host); + OPENSSL_free(port); + break; + } + OPENSSL_free(current_url); + return resp; +} + +/* Get ASN.1-encoded data via HTTP from server at given URL */ +ASN1_VALUE *OSSL_HTTP_get_asn1(const char *url, + const char *proxy, const char *proxy_port, + BIO *bio, BIO *rbio, + OSSL_HTTP_bio_cb_t bio_update_fn, void *arg, + const STACK_OF(CONF_VALUE) *headers, + int maxline, unsigned long max_resp_len, + int timeout, const char *expected_content_type, + const ASN1_ITEM *it) +{ + BIO *mem; + ASN1_VALUE *resp = NULL; + + if (url == NULL || it == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + if ((mem = OSSL_HTTP_get(url, proxy, proxy_port, bio, rbio, bio_update_fn, + arg, headers, maxline, max_resp_len, timeout, + expected_content_type, 1 /* expect_asn1 */)) + != NULL) + resp = BIO_mem_d2i(mem, it); + BIO_free(mem); + return resp; +} + +/* Post ASN.1-encoded request via HTTP to server return ASN.1 response */ +ASN1_VALUE *OSSL_HTTP_post_asn1(const char *server, const char *port, + const char *path, int use_ssl, + const char *proxy, const char *proxy_port, + BIO *bio, BIO *rbio, + OSSL_HTTP_bio_cb_t bio_update_fn, void *arg, + const STACK_OF(CONF_VALUE) *headers, + const char *content_type, + ASN1_VALUE *req, const ASN1_ITEM *req_it, + int maxline, unsigned long max_resp_len, + int timeout, const char *expected_ct, + const ASN1_ITEM *rsp_it) +{ + BIO *req_mem; + BIO *res_mem; + ASN1_VALUE *resp = NULL; + + if (req == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + /* remaining parameters are checked indirectly */ + + req_mem = HTTP_asn1_item2bio(req_it, req); + res_mem = OSSL_HTTP_transfer(server, port, path, use_ssl, proxy, proxy_port, + bio, rbio, + bio_update_fn, arg, headers, content_type, + req_mem /* may be NULL */, maxline, + max_resp_len, timeout, + expected_ct, 1 /* expect_asn1 */, NULL); + BIO_free(req_mem); + if (res_mem != NULL) + resp = BIO_mem_d2i(res_mem, rsp_it); + BIO_free(res_mem); + return resp; +} + +/* BASE64 encoder used for encoding basic proxy authentication credentials */ +static char *base64encode(const void *buf, size_t len) +{ + int i; + size_t outl; + char *out; + + /* Calculate size of encoded data */ + outl = (len / 3); + if (len % 3 > 0) + outl++; + outl <<= 2; + out = OPENSSL_malloc(outl + 1); + if (out == NULL) + return 0; + + i = EVP_EncodeBlock((unsigned char *)out, buf, len); + if (!ossl_assert(0 <= i && (size_t)i <= outl)) { + OPENSSL_free(out); + return NULL; + } + return out; +} + +/* + * Promote the given connection BIO using the CONNECT method for a TLS proxy. + * This is typically called by an app, so bio_err and prog are used unless NULL + * to print additional diagnostic information in a user-oriented way. + */ +int OSSL_HTTP_proxy_connect(BIO *bio, const char *server, const char *port, + const char *proxyuser, const char *proxypass, + int timeout, BIO *bio_err, const char *prog) +{ +# undef BUF_SIZE +# define BUF_SIZE (8 * 1024) + char *mbuf = OPENSSL_malloc(BUF_SIZE); + char *mbufp; + int read_len = 0; + int rv; + int ret = 0; + BIO *fbio = BIO_new(BIO_f_buffer()); + time_t max_time = timeout > 0 ? time(NULL) + timeout : 0; + + if (bio == NULL || server == NULL || port == NULL + || (bio_err != NULL && prog == NULL)) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + goto end; + } + + if (mbuf == NULL || fbio == NULL) { + BIO_printf(bio_err /* may be NULL */, "%s: out of memory", prog); + goto end; + } + BIO_push(fbio, bio); + + BIO_printf(fbio, "CONNECT %s:%s "HTTP_PREFIX"1.0\r\n", server, port); + + /* + * Workaround for broken proxies which would otherwise close + * the connection when entering tunnel mode (e.g., Squid 2.6) + */ + BIO_printf(fbio, "Proxy-Connection: Keep-Alive\r\n"); + + /* Support for basic (base64) proxy authentication */ + if (proxyuser != NULL) { + size_t len = strlen(proxyuser) + 1; + char *proxyauth, *proxyauthenc = NULL; + + if (proxypass != NULL) + len += strlen(proxypass); + proxyauth = OPENSSL_malloc(len + 1); + if (proxyauth == NULL) + goto end; + if (BIO_snprintf(proxyauth, len + 1, "%s:%s", proxyuser, + proxypass != NULL ? proxypass : "") != (int)len) + goto proxy_end; + proxyauthenc = base64encode(proxyauth, len); + if (proxyauthenc != NULL) { + BIO_printf(fbio, "Proxy-Authorization: Basic %s\r\n", proxyauthenc); + OPENSSL_clear_free(proxyauthenc, strlen(proxyauthenc)); + } + proxy_end: + OPENSSL_clear_free(proxyauth, len); + if (proxyauthenc == NULL) + goto end; + } + + /* Terminate the HTTP CONNECT request */ + BIO_printf(fbio, "\r\n"); + + for (;;) { + if (BIO_flush(fbio) != 0) + break; + /* potentially needs to be retried if BIO is non-blocking */ + if (!BIO_should_retry(fbio)) + break; + } + + for (;;) { + /* will not actually wait if timeout == 0 */ + rv = BIO_wait(fbio, max_time); + if (rv <= 0) { + BIO_printf(bio_err, "%s: HTTP CONNECT %s\n", prog, + rv == 0 ? "timed out" : "failed waiting for data"); + goto end; + } + + /*- + * The first line is the HTTP response. + * According to RFC 7230, it is formatted exactly like this: + * HTTP/d.d ddd Reason text\r\n + */ + read_len = BIO_gets(fbio, mbuf, BUF_SIZE); + /* the BIO may not block, so we must wait for the 1st line to come in */ + if (read_len < HTTP_LINE1_MINLEN) + continue; + + /* RFC 7231 4.3.6: any 2xx status code is valid */ + if (strncmp(mbuf, HTTP_PREFIX, strlen(HTTP_PREFIX)) != 0) { + HTTPerr(0, HTTP_R_SERVER_RESPONSE_PARSE_ERROR); + BIO_printf(bio_err, "%s: HTTP CONNECT failed, non-HTTP response\n", + prog); + /* Wrong protocol, not even HTTP, so stop reading headers */ + goto end; + } + mbufp = mbuf + strlen(HTTP_PREFIX); + if (strncmp(mbufp, HTTP_VERSION_PATT, strlen(HTTP_VERSION_PATT)) != 0) { + HTTPerr(0, HTTP_R_SERVER_SENT_WRONG_HTTP_VERSION); + BIO_printf(bio_err, + "%s: HTTP CONNECT failed, bad HTTP version %.*s\n", + prog, HTTP_VERSION_STR_LEN, mbufp); + goto end; + } + mbufp += HTTP_VERSION_STR_LEN; + if (strncmp(mbufp, " 2", strlen(" 2")) != 0) { + mbufp += 1; + /* chop any trailing whitespace */ + while (read_len > 0 && ossl_isspace(mbuf[read_len - 1])) + read_len--; + mbuf[read_len] = '\0'; + HTTPerr(0, HTTP_R_CONNECT_FAILURE); + ERR_add_error_data(2, "Reason=", mbufp); + BIO_printf(bio_err, "%s: HTTP CONNECT failed, Reason=%s\n", + prog, mbufp); + goto end; + } + ret = 1; + break; + } + + /* Read past all following headers */ + do { + /* + * TODO: This does not necessarily catch the case when the full + * HTTP response came in in more than a single TCP message. + */ + read_len = BIO_gets(fbio, mbuf, BUF_SIZE); + } while (read_len > 2); + + end: + if (fbio != NULL) { + (void)BIO_flush(fbio); + BIO_pop(fbio); + BIO_free(fbio); + } + OPENSSL_free(mbuf); + return ret; +# undef BUF_SIZE +} + +#endif /* !defined(OPENSSL_NO_SOCK) */ diff --git a/crypto/http/http_err.c b/crypto/http/http_err.c new file mode 100644 index 0000000000..8618539365 --- /dev/null +++ b/crypto/http/http_err.c @@ -0,0 +1,67 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * 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 <openssl/err.h> +#include <openssl/httperr.h> + +#ifndef OPENSSL_NO_ERR + +static const ERR_STRING_DATA HTTP_str_reasons[] = { + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_ASN1_LEN_EXCEEDS_MAX_RESP_LEN), + "asn1 len exceeds max resp len"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_CONNECT_FAILURE), "connect failure"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_ERROR_PARSING_ASN1_LENGTH), + "error parsing asn1 length"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_ERROR_PARSING_CONTENT_LENGTH), + "error parsing content length"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_ERROR_PARSING_URL), "error parsing url"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_ERROR_RECEIVING), "error receiving"}, + {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_MAX_RESP_LEN_EXCEEDED), + "max resp len exceeded"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_MISSING_ASN1_ENCODING), + "missing asn1 encoding"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_MISSING_CONTENT_TYPE), + "missing content type"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_MISSING_REDIRECT_LOCATION), + "missing redirect location"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_REDIRECTION_FROM_HTTPS_TO_HTTP), + "redirection from https to http"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_REDIRECTION_NOT_ENABLED), + "redirection not enabled"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_RESPONSE_LINE_TOO_LONG), + "response line too long"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_SERVER_RESPONSE_PARSE_ERROR), + "server response parse error"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_SERVER_SENT_ERROR), "server sent error"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_SERVER_SENT_WRONG_HTTP_VERSION), + "server sent wrong http version"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_STATUS_CODE_UNSUPPORTED), + "status code unsupported"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_TLS_NOT_ENABLED), "tls not enabled"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_TOO_MANY_REDIRECTIONS), + "too many redirections"}, + {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_UNEXPECTED_CONTENT_TYPE), + "unexpected content type"}, + {0, NULL} +}; + +#endif + +int ERR_load_HTTP_strings(void) +{ +#ifndef OPENSSL_NO_ERR + if (ERR_reason_error_string(HTTP_str_reasons[0].error) == NULL) + ERR_load_strings_const(HTTP_str_reasons); +#endif + return 1; +} diff --git a/crypto/http/http_lib.c b/crypto/http/http_lib.c new file mode 100644 index 0000000000..1d7ad0422a --- /dev/null +++ b/crypto/http/http_lib.c @@ -0,0 +1,116 @@ +/* + * Copyright 2001-2020 The OpenSSL Project Authors. All Rights Reserved. + * + * 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 <openssl/http.h> +#include <openssl/httperr.h> +#include <openssl/err.h> +#include <string.h> + +/* + * Parse a URL and split it up into host, port and path components and + * whether it indicates SSL/TLS. Return 1 on success, 0 on error. + */ + +int OSSL_HTTP_parse_url(const char *url, char **phost, char **pport, + char **ppath, int *pssl) +{ + char *p, *buf; + char *host; + char *port = "80"; + + if (url == NULL) { + HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + if (phost != NULL) + *phost = NULL; + if (pport != NULL) + *pport = NULL; + if (ppath != NULL) + *ppath = NULL; + if (pssl != NULL) + *pssl = 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 || p - buf > 5 /* strlen("https") */) { + p = buf; + } else { + *(p++) = '\0'; + + if (strcmp(buf, "https") == 0) { + if (pssl != NULL) + *pssl = 1; + port = "443"; + } else if (strcmp(buf, "http") != 0) { + goto parse_err; + } + + /* Check for double slash */ + if ((p[0] != '/') || (p[1] != '/')) + goto parse_err; + p += 2; + } + 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; + if (host[0] == '[') { + /* ipv6 literal */ + host++; + p = strchr(host, ']'); + if (p == NULL) + goto parse_err; + *p = '\0'; + p++; + } + + /* Look for optional ':' for port number */ + if ((p = strchr(p, ':'))) { + *p = '\0'; + port = p + 1; + } + if (phost != NULL && (*phost = OPENSSL_strdup(host)) == NULL) + goto err; + if (pport != NULL && (*pport = OPENSSL_strdup(port)) == NULL) + goto err; + + OPENSSL_free(buf); + return 1; + + parse_err: + HTTPerr(0, HTTP_R_ERROR_PARSING_URL); + + err: + if (ppath != NULL) { + OPENSSL_free(*ppath); + *ppath = NULL; + } + if (pport != NULL) { + OPENSSL_free(*pport); + *pport = NULL; + } + if (phost != NULL) { + OPENSSL_free(*phost); + *phost = NULL; + } + OPENSSL_free(buf); + return 0; +} diff --git a/crypto/http/http_local.h b/crypto/http/http_local.h new file mode 100644 index 0000000000..33457f1e09 --- /dev/null +++ b/crypto/http/http_local.h @@ -0,0 +1,51 @@ +/* + * Copyright 2007-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 + */ + +#ifndef OSSL_CRYPTO_HTTP_LOCAL_H +# define OSSL_CRYPTO_HTTP_LOCAL_H + +# include <openssl/ocsp.h> + +/* name aliases for legacy names with name prefix "OCSP_" */ +typedef OCSP_REQ_CTX OSSL_HTTP_REQ_CTX; +/* functions meanwhile only used internally */ +# define OSSL_HTTP_REQ_CTX_new OCSP_REQ_CTX_new +# define OSSL_HTTP_REQ_CTX_free OCSP_REQ_CTX_free +# define OSSL_HTTP_REQ_CTX_header OCSP_REQ_CTX_http +# define OSSL_HTTP_REQ_CTX_add1_header OCSP_REQ_CTX_add1_header +# define OSSL_HTTP_REQ_CTX_i2d OCSP_REQ_CTX_i2d +# define OSSL_HTTP_REQ_CTX_nbio OCSP_REQ_CTX_nbio +# ifndef OPENSSL_NO_SOCK +# define OSSL_HTTP_REQ_CTX_sendreq_d2i OCSP_REQ_CTX_nbio_d2i +# endif +/* functions that are meanwhile unused */ +# define OSSL_HTTP_REQ_CTX_get0_mem_bio OCSP_REQ_CTX_get0_mem_bio /* undoc'd */ +# define OSSL_HTTP_REQ_CTX_set_max_response_length OCSP_set_max_response_length + +BIO *HTTP_asn1_item2bio(const ASN1_ITEM *it, ASN1_VALUE *val); +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); +ASN1_VALUE *HTTP_sendreq_bio(BIO *bio, OSSL_HTTP_bio_cb_t bio_update_fn, + void *arg, const char *server, const char *port, + const char *path, int use_ssl, int use_proxy, + const STACK_OF(CONF_VALUE) *headers, + const char *content_type, + ASN1_VALUE *req, const ASN1_ITEM *req_it, + int maxline, unsigned long max_resp_len, + int timeout, const ASN1_ITEM *rsp_it); + +#endif /* !defined OSSL_CRYPTO_HTTP_LOCAL_H */ diff --git a/crypto/ocsp/build.info b/crypto/ocsp/build.info index 0902caae3d..79a59004af 100644 --- a/crypto/ocsp/build.info +++ b/crypto/ocsp/build.info @@ -1,4 +1,4 @@ LIBS=../../libcrypto SOURCE[../../libcrypto]=\ - ocsp_asn.c ocsp_ext.c ocsp_ht.c ocsp_lib.c ocsp_cl.c \ + ocsp_asn.c ocsp_ext.c ocsp_http.c ocsp_lib.c ocsp_cl.c \ ocsp_srv.c ocsp_prn.c ocsp_vfy.c ocsp_err.c v3_ocsp.c diff --git a/crypto/ocsp/ocsp_err.c b/crypto/ocsp/ocsp_err.c index 6e2152b3b2..e271780909 100644 --- a/crypto/ocsp/ocsp_err.c +++ b/crypto/ocsp/ocsp_err.c @@ -21,7 +21,6 @@ static const ERR_STRING_DATA OCSP_str_reasons[] = { "error in nextupdate field"}, {ERR_PACK(ERR_LIB_OCSP, 0, OCSP_R_ERROR_IN_THISUPDATE_FIELD), "error in thisupdate field"}, - {ERR_PACK(ERR_LIB_OCSP, 0, OCSP_R_ERROR_PARSING_URL), "error parsing url"}, {ERR_PACK(ERR_LIB_OCSP, 0, OCSP_R_MISSING_OCSPSIGNING_USAGE), "missing ocspsigning usage"}, {ERR_PACK(ERR_LIB_OCSP, 0, OCSP_R_NEXTUPDATE_BEFORE_THISUPDATE), @@ -41,10 +40,6 @@ static const ERR_STRING_DATA OCSP_str_reasons[] = { "response contains no revocation data"}, {ERR_PACK(ERR_LIB_OCSP, 0, OCSP_R_ROOT_CA_NOT_TRUSTED), "root ca not trusted"}, - {ERR_PACK(ERR_LIB_OCSP, 0, OCSP_R_SERVER_RESPONSE_ERROR), - "server response error"}, - {ERR_PACK(ERR_LIB_OCSP, 0, OCSP_R_SERVER_RESPONSE_PARSE_ERROR), - "server response parse error"}, {ERR_PACK(ERR_LIB_OCSP, 0, OCSP_R_SIGNATURE_FAILURE), "signature failure"}, {ERR_PACK(ERR_LIB_OCSP, 0, OCSP_R_SIGNER_CERTIFICATE_NOT_FOUND), "signer certificate not found"}, diff --git a/crypto/ocsp/ocsp_ht.c b/crypto/ocsp/ocsp_ht.c deleted file mode 100644 index fa147f3b16..0000000000 --- a/crypto/ocsp/ocsp_ht.c +++ /dev/null @@ -1,502 +0,0 @@ -/* - * Copyright 2001-2017 The OpenSSL Project Authors. All Rights Reserved. - * - * 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/ocsp.h> -#include <openssl/err.h> -#include <openssl/buffer.h> - -/* Stateful OCSP request code, supporting non-blocking I/O */ - -/* Opaque OCSP request status structure */ - -struct ocsp_req_ctx_st { - int state; /* Current I/O state */ - unsigned char *iobuf; /* Line buffer */ - int iobuflen; /* Line buffer length */ - BIO *io; /* BIO to perform I/O with */ - BIO *mem; /* Memory BIO response is built into */ - unsigned long asn1_len; /* ASN1 length of response */ - unsigned long max_resp_len; /* Maximum length of response */ -}; - -#define OCSP_MAX_RESP_LENGTH (100 * 1024) -#define OCSP_MAX_LINE_LEN 4096; - -/* OCSP states */ - -/* If set no reading should be performed */ -#define OHS_NOREAD 0x1000 -/* Error condition */ -#define OHS_ERROR (0 | OHS_NOREAD) -/* First line being read */ -#define OHS_FIRSTLINE 1 -/* MIME headers being read */ -#define OHS_HEADERS 2 -/* OCSP initial header (tag + length) being read */ -#define OHS_ASN1_HEADER 3 -/* OCSP content octets being read */ -#define OHS_ASN1_CONTENT 4 -/* First call: ready to start I/O */ -#define OHS_ASN1_WRITE_INIT (5 | OHS_NOREAD) -/* Request being sent */ -#define OHS_ASN1_WRITE (6 | OHS_NOREAD) -/* Request being flushed */ -#define OHS_ASN1_FLUSH (7 | OHS_NOREAD) -/* Completed */ -#define OHS_DONE (8 | OHS_NOREAD) -/* Headers set, no final \r\n included */ -#define OHS_HTTP_HEADER (9 | OHS_NOREAD) - -static int parse_http_line1(char *line); - -OCSP_REQ_CTX *OCSP_REQ_CTX_new(BIO *io, int maxline) -{ - OCSP_REQ_CTX *rctx = OPENSSL_zalloc(sizeof(*rctx)); - - if (rctx == NULL) - return NULL; - rctx->state = OHS_ERROR; - rctx->max_resp_len = OCSP_MAX_RESP_LENGTH; - rctx->mem = BIO_new(BIO_s_mem()); - rctx->io = io; - if (maxline > 0) - rctx->iobuflen = maxline; - else - rctx->iobuflen = OCSP_MAX_LINE_LEN; - rctx->iobuf = OPENSSL_malloc(rctx->iobuflen); - if (rctx->iobuf == NULL || rctx->mem == NULL) { - OCSP_REQ_CTX_free(rctx); - return NULL; - } - return rctx; -} - -void OCSP_REQ_CTX_free(OCSP_REQ_CTX *rctx) -{ - if (!rctx) - return; - BIO_free(rctx->mem); - OPENSSL_free(rctx->iobuf); - OPENSSL_free(rctx); -} - -BIO *OCSP_REQ_CTX_get0_mem_bio(OCSP_REQ_CTX *rctx) -{ - return rctx->mem; -} - -void OCSP_set_max_response_length(OCSP_REQ_CTX *rctx, unsigned long len) -{ - if (len == 0) - rctx->max_resp_len = OCSP_MAX_RESP_LENGTH; - else - rctx->max_resp_len = len; -} - -int OCSP_REQ_CTX_i2d(OCSP_REQ_CTX *rctx, const ASN1_ITEM *it, ASN1_VALUE *val) -{ - static const char req_hdr[] = - "Content-Type: application/ocsp-request\r\n" - "Content-Length: %d\r\n\r\n"; - int reqlen = ASN1_item_i2d(val, NULL, it); - if (BIO_printf(rctx->mem, req_hdr, reqlen) <= 0) - return 0; - if (ASN1_item_i2d_bio(it, rctx->mem, val) <= 0) - return 0; - rctx->state = OHS_ASN1_WRITE_INIT; - return 1; -} - -int OCSP_REQ_CTX_nbio_d2i(OCSP_REQ_CTX *rctx, - ASN1_VALUE **pval, const ASN1_ITEM *it) -{ - int rv, len; - const unsigned char *p; - - rv = OCSP_REQ_CTX_nbio(rctx); - if (rv != 1) - return rv; - - len = BIO_get_mem_data(rctx->mem, &p); - *pval = ASN1_item_d2i(NULL, &p, len, it); - if (*pval == NULL) { - rctx->state = OHS_ERROR; - return 0; - } - return 1; -} - -int OCSP_REQ_CTX_http(OCSP_REQ_CTX *rctx, const char *op, const char *path) -{ - static const char http_hdr[] = "%s %s HTTP/1.0\r\n"; - - if (path == NULL) - path = "/"; - - if (BIO_printf(rctx->mem, http_hdr, op, path) <= 0) - return 0; - rctx->state = OHS_HTTP_HEADER; - return 1; -} - -int OCSP_REQ_CTX_set1_req(OCSP_REQ_CTX *rctx, OCSP_REQUEST *req) -{ - return OCSP_REQ_CTX_i2d(rctx, ASN1_ITEM_rptr(OCSP_REQUEST), - (ASN1_VALUE *)req); -} - -int OCSP_REQ_CTX_add1_header(OCSP_REQ_CTX *rctx, - const char *name, const char *value) -{ - if (!name) - return 0; - if (BIO_puts(rctx->mem, name) <= 0) - return 0; - if (value) { - 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; -} - -OCSP_REQ_CTX *OCSP_sendreq_new(BIO *io, const char *path, OCSP_REQUEST *req, - int maxline) -{ - - OCSP_REQ_CTX *rctx = NULL; - rctx = OCSP_REQ_CTX_new(io, maxline); - if (rctx == NULL) - return NULL; - - if (!OCSP_REQ_CTX_http(rctx, "POST", path)) - goto err; - - if (req && !OCSP_REQ_CTX_set1_req(rctx, req)) - goto err; - - return rctx; - - err: - OCSP_REQ_CTX_free(rctx); - return NULL; -} - -/* - * Parse the HTTP response. This will look 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 *p, *q, *r; - /* Skip to first white space (passed protocol info) */ - - for (p = line; *p && !ossl_isspace(*p); p++) - continue; - if (*p == '\0') { - OCSPerr(OCSP_F_PARSE_HTTP_LINE1, OCSP_R_SERVER_RESPONSE_PARSE_ERROR); - return 0; - } - - /* Skip past white space to start of response code */ - while (*p && ossl_isspace(*p)) - p++; - - if (*p == '\0') { - OCSPerr(OCSP_F_PARSE_HTTP_LINE1, OCSP_R_SERVER_RESPONSE_PARSE_ERROR); - return 0; - } - - /* Find end of response code: first whitespace after start of code */ - for (q = p; *q && !ossl_isspace(*q); q++) - continue; - - if (*q == '\0') { - OCSPerr(OCSP_F_PARSE_HTTP_LINE1, OCSP_R_SERVER_RESPONSE_PARSE_ERROR); - return 0; - } - - /* Set end of response code and start of message */ - *q++ = 0; - - /* Attempt to parse numeric code */ - retcode = strtoul(p, &r, 10); - - if (*r) - return 0; - - /* Skip over any leading white space in message */ - while (*q && ossl_isspace(*q)) - q++; - - if (*q) { - /* - * Finally zap any trailing white space in message (include CRLF) - */ - - /* We know q has a non white space character so this is OK */ - for (r = q + strlen(q) - 1; ossl_isspace(*r); r--) - *r = 0; - } - if (retcode != 200) { - OCSPerr(OCSP_F_PARSE_HTTP_LINE1, OCSP_R_SERVER_RESPONSE_ERROR); - if (*q == '\0') - ERR_add_error_data(2, "Code=", p); - else - ERR_add_error_data(4, "Code=", p, ",Reason=", q); - return 0; - } - - return 1; - -} - -int OCSP_REQ_CTX_nbio(OCSP_REQ_CTX *rctx) -{ - int i, n; - const unsigned char *p; - next_io: - if (!(rctx->state & OHS_NOREAD)) { - n = BIO_read(rctx->io, rctx->iobuf, rctx->iobuflen); - - if (n <= 0) { - if (BIO_should_retry(rctx->io)) - 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_ASN1_WRITE_INIT; - - /* fall thru */ - case OHS_ASN1_WRITE_INIT: - rctx->asn1_len = BIO_get_mem_data(rctx->mem, NULL); - rctx->state = OHS_ASN1_WRITE; - - /* fall thru */ - case OHS_ASN1_WRITE: - n = BIO_get_mem_data(rctx->mem, &p); - - i = BIO_write(rctx->io, p + (n - rctx->asn1_len), rctx->asn1_len); - - if (i <= 0) { - if (BIO_should_retry(rctx->io)) - return -1; - rctx->state = OHS_ERROR; - return 0; - } - - rctx->asn1_len -= i; - - if (rctx->asn1_len > 0) - goto next_io; - - rctx->state = OHS_ASN1_FLUSH; - - (void)BIO_reset(rctx->mem); - - /* fall thru */ - case OHS_ASN1_FLUSH: - - i = BIO_flush(rctx->io); - - if (i > 0) { - rctx->state = OHS_FIRSTLINE; - goto next_io; - } - - if (BIO_should_retry(rctx->io)) - return -1; - - rctx->state = OHS_ERROR; - return 0; - - case OHS_ERROR: - return 0; - - case OHS_FIRSTLINE: - case OHS_HEADERS: - - /* Attempt to read a line in */ - - next_line: - /* - * Due to &%^*$" memory BIO behaviour 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)) { - 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) { - rctx->state = OHS_ERROR; - return 0; - } - - /* First line */ - if (rctx->state == OHS_FIRSTLINE) { - if (parse_http_line1((char *)rctx->iobuf)) { - rctx->state = OHS_HEADERS; - goto next_line; - } else { - rctx->state = OHS_ERROR; - return 0; - } - } else { - /* Look for blank line: end of headers */ - for (p = rctx->iobuf; *p; p++) { - if ((*p != '\r') && (*p != '\n')) - break; - } - if (*p) - goto next_line; - - rctx->state = OHS_ASN1_HEADER; - - } - - /* Fall thru */ - - case OHS_ASN1_HEADER: - /* - * Now reading ASN1 header: can read at least 2 bytes which is enough - * for ASN1 SEQUENCE header and either length field or at least the - * length of the length field. - */ - n = BIO_get_mem_data(rctx->mem, &p); - if (n < 2) - goto next_io; - - /* Check it is an ASN1 SEQUENCE */ - if (*p++ != (V_ASN1_SEQUENCE | V_ASN1_CONSTRUCTED)) { - rctx->state = OHS_ERROR; - return 0; - } - - /* Check out length field */ - if (*p & 0x80) { - /* - * If MSB set on initial length octet we can now always read 6 - * octets: make sure we have them. - */ - if (n < 6) - goto next_io; - n = *p & 0x7F; - /* Not NDEF or excessive length */ - if (!n || (n > 4)) { - rctx->state = OHS_ERROR; - return 0; - } - p++; - rctx->asn1_len = 0; - for (i = 0; i < n; i++) { - rctx->asn1_len <<= 8; - rctx->asn1_len |= *p++; - } - - if (rctx->asn1_len > rctx->max_resp_len) { - rctx->state = OHS_ERROR; - return 0; - } - - rctx->asn1_len += n + 2; - } else - rctx->asn1_len = *p + 2; - - rctx->state = OHS_ASN1_CONTENT; - - /* Fall thru */ - - case OHS_ASN1_CONTENT: - n = BIO_get_mem_data(rctx->mem, NULL); - if (n < (int)rctx->asn1_len) - goto next_io; - - rctx->state = OHS_DONE; - return 1; - - case OHS_DONE: - return 1; - - } - - return 0; - -} - -int OCSP_sendreq_nbio(OCSP_RESPONSE **presp, OCSP_REQ_CTX *rctx) -{ - return OCSP_REQ_CTX_nbio_d2i(rctx, - (ASN1_VALUE **)presp, - ASN1_ITEM_rptr(OCSP_RESPONSE)); -} - -/* Blocking OCSP request handler: now a special case of non-blocking I/O */ - -OCSP_RESPONSE *OCSP_sendreq_bio(BIO *b, const char *path, OCSP_REQUEST *req) -{ - OCSP_RESPONSE *resp = NULL; - OCSP_REQ_CTX *ctx; - int rv; - - ctx = OCSP_sendreq_new(b, path, req, -1); - - if (ctx == NULL) - return NULL; - - do { - rv = OCSP_sendreq_nbio(&resp, ctx); - } while ((rv == -1) && BIO_should_retry(b)); - - OCSP_REQ_CTX_free(ctx); - - if (rv) - return resp; - - return NULL; -} diff --git a/crypto/ocsp/ocsp_http.c b/crypto/ocsp/ocsp_http.c new file mode 100644 index 0000000000..39277c1bba --- /dev/null +++ b/crypto/ocsp/ocsp_http.c @@ -0,0 +1,65 @@ +/* + * Copyright 2001-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * 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 <openssl/ocsp.h> +#include <openssl/http.h> +#include "../http/http_local.h" + +#ifndef OPENSSL_NO_OCSP + +int OCSP_REQ_CTX_set1_req(OCSP_REQ_CTX *rctx, const OCSP_REQUEST *req) +{ + return OCSP_REQ_CTX_i2d(rctx, "application/ocsp-request", + ASN1_ITEM_rptr(OCSP_REQUEST), (ASN1_VALUE *)req); +} + +OCSP_REQ_CTX *OCSP_sendreq_new(BIO *io, const char *path, OCSP_REQUEST *req, + int maxline) +{ + BIO *req_mem = HTTP_asn1_item2bio(ASN1_ITEM_rptr(OCSP_REQUEST), + (ASN1_VALUE *)req); + OCSP_REQ_CTX *res = + HTTP_REQ_CTX_new(io, io, 0 /* no HTTP proxy used */, NULL, NULL, path, + NULL /* headers */, "application/ocsp-request", + req_mem /* may be NULL */, + maxline, 0 /* default max_resp_len */, + 0 /* no timeout, blocking indefinite */, NULL, + 1 /* expect_asn1 */); + BIO_free(req_mem); + return res; +} + +# ifndef OPENSSL_NO_SOCK +int OCSP_sendreq_nbio(OCSP_RESPONSE **presp, OCSP_REQ_CTX *rctx) +{ + *presp = (OCSP_RESPONSE *) + OCSP_REQ_CTX_nbio_d2i(rctx, ASN1_ITEM_rptr(OCSP_RESPONSE)); + return *presp != NULL; +} + +OCSP_RESPONSE *OCSP_sendreq_bio(BIO *b, const char *path, OCSP_REQUEST *req) +{ + OCSP_RESPONSE *resp = NULL; + OCSP_REQ_CTX *ctx; + int rv; + + ctx = OCSP_sendreq_new(b, path, req, -1 /* default max resp line length */); + if (ctx == NULL) + return NULL; + + rv = OCSP_sendreq_nbio(&resp, ctx); + + /* this indirectly calls ERR_clear_error(): */ + OCSP_REQ_CTX_free(ctx); + + return rv == 1 ? resp : NULL; +} +# endif /* !defined(OPENSSL_NO_SOCK) */ + +#endif /* !defined(OPENSSL_NO_OCSP) */ diff --git a/crypto/ocsp/ocsp_lib.c b/crypto/ocsp/ocsp_lib.c index a027062ccf..797ac289d4 100644 --- a/crypto/ocsp/ocsp_lib.c +++ b/crypto/ocsp/ocsp_lib.c @@ -109,111 +109,4 @@ int OCSP_id_cmp(const OCSP_CERTID *a, const OCSP_CERTID *b) return ASN1_INTEGER_cmp(&a->serialNumber, &b->serialNumber); } -/* - * Parse a URL and split it up into host, port and path components and - * whether it is SSL. - */ - -int OCSP_parse_url(const char *url, char **phost, char **pport, char **ppath, - int *pssl) -{ - char *p, *buf; - - char *host, *port; - - *phost = NULL; - *pport = NULL; - *ppath = NULL; - - /* dup the buffer since we are going to mess with it */ - buf = OPENSSL_strdup(url); - if (!buf) - goto mem_err; - - /* Check for initial colon */ - p = strchr(buf, ':'); - if (p == NULL) - goto parse_err; - - *(p++) = '\0'; - - if (strcmp(buf, "http") == 0) { - *pssl = 0; - port = "80"; - } else if (strcmp(buf, "https") == 0) { - *pssl = 1; - port = "443"; - } else - goto parse_err; - - /* Check for double slash */ - if ((p[0] != '/') || (p[1] != '/')) - goto parse_err; - - p += 2; - - host = p; - - /* Check for trailing part of path */ - p = strchr(p, '/'); - if (p == NULL) - *ppath = OPENSSL_strdup("/"); - else { - *ppath = OPENSSL_strdup(p); - /* Set start of path to 0 so hostname is valid */ - *p = '\0'; - } - - if (*ppath == NULL) - goto mem_err; - - p = host; - if (host[0] == '[') { - /* ipv6 literal */ - host++; - p = strchr(host, ']'); - if (p == NULL) - goto parse_err; - *p = '\0'; - p++; - } - - /* Look for optional ':' for port number */ - if ((p = strchr(p, ':'))) { - *p = 0; - port = p + 1; - } - - *pport = OPENSSL_strdup(port); - if (*pport == NULL) - goto mem_err; - - *phost = OPENSSL_strdup(host); - - if (*phost == NULL) - goto mem_err; - - OPENSSL_free(buf); - - return 1; - - mem_err: - OCSPerr(OCSP_F_OCSP_PARSE_URL, ERR_R_MALLOC_FAILURE); - goto err; - - parse_err: - OCSPerr(OCSP_F_OCSP_PARSE_URL, OCSP_R_ERROR_PARSING_URL); - - err: - OPENSSL_free(buf); - OPENSSL_free(*ppath); - *ppath = NULL; - OPENSSL_free(*pport); - *pport = NULL; - OPENSSL_free(*phost); - *phost = NULL; - return 0; - -} - IMPLEMENT_ASN1_DUP_FUNCTION(OCSP_CERTID) diff --git a/crypto/x509/x_all.c b/crypto/x509/x_all.c index 89940a0cc9..6a6748bad4 100644 --- a/crypto/x509/x_all.c +++ b/crypto/x509/x_all.c @@ -20,7 +20,7 @@ #include <openssl/evp.h> #include <openssl/x509.h> #include "crypto/x509.h" -#include <openssl/ocsp.h> +#include <openssl/http.h> #include <openssl/rsa.h> #include <openssl/dsa.h> #include <openssl/x509v3.h> @@ -123,11 +123,21 @@ int X509_sign_ctx(X509 *x, EVP_MD_CTX *ctx) &x->sig_alg, &x->signature, &x->cert_info, ctx); } -#ifndef OPENSSL_NO_OCSP -int X509_http_nbio(OCSP_REQ_CTX *rctx, X509 **pcert) +#if !defined(OPENSSL_NO_SOCK) +static ASN1_VALUE *simple_get_asn1(const char *url, BIO *bio, BIO *rbio, + int timeout, const ASN1_ITEM *it) { - return OCSP_REQ_CTX_nbio_d2i(rctx, - (ASN1_VALUE **)pcert, ASN1_ITEM_rptr(X509)); + return OSSL_HTTP_get_asn1(url, NULL, NULL /* no proxy and port */, bio, + rbio, NULL /* no callback for SSL/TLS */, NULL, + NULL /* headers */, 1024 /* maxline */, + 0 /* max_resp_len */, timeout, + NULL /* expected_content_type */, it); +} + +X509 *X509_load_http(const char *url, BIO *bio, BIO *rbio, int timeout) +{ + return (X509 *)simple_get_asn1(url, bio, rbio, timeout, + ASN1_ITEM_rptr(X509)); } #endif @@ -159,12 +169,11 @@ int X509_CRL_sign_ctx(X509_CRL *x, EVP_MD_CTX *ctx) &x->crl, ctx); } -#ifndef OPENSSL_NO_OCSP -int X509_CRL_http_nbio(OCSP_REQ_CTX *rctx, X509_CRL **pcrl) +#if !defined(OPENSSL_NO_SOCK) +X509_CRL *X509_CRL_load_http(const char *url, BIO *bio, BIO *rbio, int timeout) { - return OCSP_REQ_CTX_nbio_d2i(rctx, - (ASN1_VALUE **)pcrl, - ASN1_ITEM_rptr(X509_CRL)); + return (X509_CRL *)simple_get_asn1(url, bio, rbio, timeout, + ASN1_ITEM_rptr(X509_CRL)); } #endif |