Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/admin-guide/files/records.yaml.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4254,6 +4254,14 @@ OCSP Stapling Configuration

Number of seconds before an OCSP response expires in the stapling cache.

.. ts:cv:: CONFIG proxy.config.ssl.ocsp.request_mode INT 0

Set the request method to prefer when querying OCSP responders. The default
is zero, or POST, and a value of 1 will cause ATS to attempt a GET request.
Because the length of the encoded request must be less than 255 bytes per RFC
6960, Appendix A, ATS will fall back to the POST request method when the
encoded size exceeds this limit.

.. ts:cv:: CONFIG proxy.config.ssl.ocsp.request_timeout INT 10
:units: seconds

Expand Down
1 change: 1 addition & 0 deletions doc/admin-guide/security/index.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ in :file:`records.yaml` file:

* :ts:cv:`proxy.config.ssl.ocsp.enabled`
* :ts:cv:`proxy.config.ssl.ocsp.cache_timeout`
* :ts:cv:`proxy.config.ssl.ocsp.request_mode`
* :ts:cv:`proxy.config.ssl.ocsp.request_timeout`
* :ts:cv:`proxy.config.ssl.ocsp.update_period`
* :ts:cv:`proxy.config.ssl.ocsp.response.path`
Expand Down
158 changes: 140 additions & 18 deletions iocore/net/OCSPStapling.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
#include <openssl/asn1t.h>

#include "tscore/ink_memory.h"
#include "tscore/Encoding.h"
#include "tscore/ink_base64.h"
#include "tscore/ink_string.h"
#include "P_Net.h"
#include "P_SSLConfig.h"
#include "P_SSLUtils.h"
Expand All @@ -41,6 +44,10 @@
// so 10K should be more than enough.
#define MAX_STAPLING_DER 10240

// maximum length allowed for the encoded OCSP GET request; per RFC 6960, Appendix A, the encoded request must be less than 255
// bytes
#define MAX_OCSP_GET_ENCODED_LENGTH 255 // maximum of 254 bytes + \0

extern ClassAllocator<FetchSM> FetchSMAllocator;

// clang-format off
Expand Down Expand Up @@ -299,7 +306,7 @@ class HTTPRequest : public Continuation
}

void
set_request_line(bool use_post, const char *uri)
set_request_line(bool use_get, const char *uri)
{
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
Expand All @@ -308,11 +315,8 @@ class HTTPRequest : public Continuation
sin.sin_port = 65535;

this->_fsm = FetchSMAllocator.alloc();
if (use_post) {
this->_fsm->ext_init(this, "POST", uri, "HTTP/1.1", reinterpret_cast<sockaddr *>(&sin), TS_FETCH_FLAGS_SKIP_REMAP);
} else {
this->_fsm->ext_init(this, "GET", uri, "HTTP/1.1", reinterpret_cast<sockaddr *>(&sin), TS_FETCH_FLAGS_SKIP_REMAP);
}
this->_fsm->ext_init(this, use_get ? "GET" : "POST", uri, "HTTP/1.1", reinterpret_cast<sockaddr *>(&sin),
TS_FETCH_FLAGS_SKIP_REMAP);
}

int
Expand Down Expand Up @@ -996,7 +1000,7 @@ stapling_check_response(certinfo *cinf, TS_OCSP_RESPONSE *rsp)
}

static TS_OCSP_RESPONSE *
query_responder(const char *uri, const char *user_agent, TS_OCSP_REQUEST *req, int req_timeout)
query_responder(const char *uri, const char *user_agent, TS_OCSP_REQUEST *req, int req_timeout, bool use_get)
{
ink_hrtime start, end;
TS_OCSP_RESPONSE *resp = nullptr;
Expand All @@ -1005,9 +1009,8 @@ query_responder(const char *uri, const char *user_agent, TS_OCSP_REQUEST *req, i
end = ink_hrtime_add(start, ink_hrtime_from_sec(req_timeout));

HTTPRequest httpreq;
bool use_post = true;

httpreq.set_request_line(use_post, uri);
httpreq.set_request_line(use_get, uri);

// Host header
const char *host = strstr(uri, "://") + 3;
Expand All @@ -1024,7 +1027,7 @@ query_responder(const char *uri, const char *user_agent, TS_OCSP_REQUEST *req, i
}

// Content-Type, Content-Length, Request Body
if (use_post) {
if (!use_get) {
if (httpreq.set_body("application/ocsp-request", ASN1_ITEM_rptr(TS_OCSP_REQUEST), (const ASN1_VALUE *)req) != 1) {
Error("failed to make a request for OCSP server; uri=%s", uri);
return nullptr;
Expand Down Expand Up @@ -1073,18 +1076,119 @@ query_responder(const char *uri, const char *user_agent, TS_OCSP_REQUEST *req, i
return nullptr;
}

// The default encoding table, per the RFC, does not encode any of the following: ,+/=
static const unsigned char encoding_map[32] = {
0xFF, 0xFF, 0xFF,
0xFF, // control
0xB4, // space " # %
0x19, // , + /
0x00, //
0x0E, // < > =
0x00, 0x00, //
0x00, //
0x1E, 0x80, // [ \ ] ^ `
0x00, 0x00, //
0x1F, // { | } ~ DEL
0x00, 0x00, 0x00,
0x00, // all non-ascii characters unmodified
0x00, 0x00, 0x00,
0x00, // .
0x00, 0x00, 0x00,
0x00, // .
0x00, 0x00, 0x00,
0x00 // .
};

static IOBufferBlock *
make_url_for_get(TS_OCSP_REQUEST *req, const char *base_url)
{
unsigned char *ocsp_der = nullptr;
int ocsp_der_len = -1;
char ocsp_encoded_der[MAX_OCSP_GET_ENCODED_LENGTH]; // Stores base64 encoded data
size_t ocsp_encoded_der_len = 0;
char ocsp_escaped[MAX_OCSP_GET_ENCODED_LENGTH];
int ocsp_escaped_len = -1;
IOBufferBlock *url = nullptr;

ocsp_der_len = i2d_TS_OCSP_REQUEST(req, &ocsp_der);

// ats_base64_encode does not insert newlines, which would need to be removed otherwise.
// When ats_base64_encode is false, the encoded length exceeds the length of our buffer,
// which is set to MAX_OCSP_GET_ENCODED_LENGTH; 255 bytes per RFC6960, Appendix A.
if (ocsp_der_len <= 0 || ocsp_der == nullptr) {
Error("stapling_refresh_response: unable to convert OCSP request to DER; falling back to POST; url=%s", base_url);
return nullptr;
}
Debug("ssl_ocsp", "converted OCSP request to DER; length=%d", ocsp_der_len);

if (ats_base64_encode(ocsp_der, ocsp_der_len, ocsp_encoded_der, MAX_OCSP_GET_ENCODED_LENGTH, &ocsp_encoded_der_len) == false ||
ocsp_encoded_der_len == 0) {
Error("stapling_refresh_response: unable to base64 encode OCSP DER; falling back to POST; url=%s", base_url);
OPENSSL_free(ocsp_der);
return nullptr;
}
OPENSSL_free(ocsp_der);
Debug("ssl_ocsp", "encoded DER with base64: %s", ocsp_encoded_der);

if (nullptr == Encoding::pure_escapify_url(nullptr, ocsp_encoded_der, ocsp_encoded_der_len, &ocsp_escaped_len, ocsp_escaped,
MAX_OCSP_GET_ENCODED_LENGTH, encoding_map)) {
Error("stapling_refresh_response: unable to escapify encoded url; falling back to POST; url=%s", base_url);
return nullptr;
}
Debug("ssl_ocsp", "escaped encoded path; %d bytes, %s", ocsp_escaped_len, ocsp_escaped);

size_t total_url_len =
sizeof(char) * (strlen(base_url) + 1 + ocsp_escaped_len + 1); // <base URL> + / + <encoded OCSP request> + \0
unsigned int buffer_idx = DEFAULT_SMALL_BUFFER_SIZE; // idx 2, aka BUFFER_SIZE_INDEX_512 should be enough in most cases
unsigned int buffer_size = BUFFER_SIZE_FOR_INDEX(buffer_idx);

// increase buffer index as necessary to fit the largest observed encoded_path_len
while (buffer_size < total_url_len && buffer_idx < MAX_BUFFER_SIZE_INDEX) {
buffer_size = BUFFER_SIZE_FOR_INDEX(++buffer_idx);
Debug("ssl_ocsp", "increased buffer index to %d", buffer_idx);
}

if (buffer_size < total_url_len) {
// this case should never occur; the largest index, 14, is 2MB
Error("Unable to identify a buffer index large enough to fit %zu bytes for the the request url; maximum index %d with size %d",
total_url_len, buffer_idx, buffer_size);
return nullptr;
}

Debug("ssl_ocsp", "creating new buffer block with index %d, size %d, to store %zu encoded bytes", buffer_idx, buffer_size,
total_url_len);
url = new_IOBufferBlock();
url->alloc(buffer_idx);

int written = ink_strlcpy(url->end(), base_url, url->write_avail());
url->fill(written);

// Append '/' if base_url does not end with it
if (url->buf()[url->size() - 1] != '/') {
strncat(url->end(), "/", 1);
url->fill(1);
}

written = ink_strlcat(url->end(), ocsp_escaped, url->write_avail());
url->fill(written);
Debug("ssl_ocsp", "appended encoded data to path: %s", url->buf());

return url;
}

static bool
stapling_refresh_response(certinfo *cinf, TS_OCSP_RESPONSE **prsp)
{
bool rv = true;
TS_OCSP_REQUEST *req = nullptr;
TS_OCSP_CERTID *id = nullptr;
int response_status = 0;
bool rv = true;
TS_OCSP_REQUEST *req = nullptr;
TS_OCSP_CERTID *id = nullptr;
int response_status = 0;
IOBufferBlock *url_for_get = nullptr;
const char *url = nullptr; // Final URL to use
bool use_get = false;

*prsp = nullptr;

Debug("ssl_ocsp", "stapling_refresh_response: querying responder; uri=%s", cinf->uri);

req = TS_OCSP_REQUEST_new();
if (!req) {
goto err;
Expand All @@ -1097,7 +1201,21 @@ stapling_refresh_response(certinfo *cinf, TS_OCSP_RESPONSE **prsp)
goto err;
}

*prsp = query_responder(cinf->uri, cinf->user_agent, req, SSLConfigParams::ssl_ocsp_request_timeout);
if (SSLConfigParams::ssl_ocsp_request_mode) { // True: GET, False: POST
url_for_get = make_url_for_get(req, cinf->uri);
if (url_for_get != nullptr) {
url = url_for_get->buf();
use_get = true;
}
}
if (url == nullptr) {
// GET request is disabled or the request is too large for GET request
url = cinf->uri;
}

Debug("ssl_ocsp", "stapling_refresh_response: querying responder; method=%s uri=%s", use_get ? "GET" : "POST", cinf->uri);

*prsp = query_responder(url, cinf->user_agent, req, SSLConfigParams::ssl_ocsp_request_timeout, use_get);
if (*prsp == nullptr) {
goto err;
}
Expand All @@ -1107,7 +1225,8 @@ stapling_refresh_response(certinfo *cinf, TS_OCSP_RESPONSE **prsp)
Debug("ssl_ocsp", "stapling_refresh_response: query response received");
stapling_check_response(cinf, *prsp);
} else {
Error("stapling_refresh_response: responder response error; uri=%s response_status=%d", cinf->uri, response_status);
Error("stapling_refresh_response: responder response error; uri=%s method=%s response_status=%d", cinf->uri,
use_get ? "GET" : "POST", response_status);
}

if (!stapling_cache_response(*prsp, cinf)) {
Expand All @@ -1128,6 +1247,9 @@ stapling_refresh_response(certinfo *cinf, TS_OCSP_RESPONSE **prsp)
if (*prsp) {
TS_OCSP_RESPONSE_free(*prsp);
}
if (url_for_get) {
url_for_get->free();
}
return rv;
}

Expand Down
1 change: 1 addition & 0 deletions iocore/net/P_SSLConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ struct SSLConfigParams : public ConfigInfo {

static bool ssl_ocsp_enabled;
static int ssl_ocsp_cache_timeout;
static bool ssl_ocsp_request_mode;
static int ssl_ocsp_request_timeout;
static int ssl_ocsp_update_period;
static int ssl_handshake_timeout_in;
Expand Down
2 changes: 2 additions & 0 deletions iocore/net/SSLConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ int SSLConfigParams::ssl_misc_max_iobuffer_size_index = 8;
bool SSLConfigParams::ssl_allow_client_renegotiation = false;
bool SSLConfigParams::ssl_ocsp_enabled = false;
int SSLConfigParams::ssl_ocsp_cache_timeout = 3600;
bool SSLConfigParams::ssl_ocsp_request_mode = false;
int SSLConfigParams::ssl_ocsp_request_timeout = 10;
int SSLConfigParams::ssl_ocsp_update_period = 60;
char *SSLConfigParams::ssl_ocsp_user_agent = nullptr;
Expand Down Expand Up @@ -469,6 +470,7 @@ SSLConfigParams::initialize()
// SSL OCSP Stapling configurations
REC_ReadConfigInt32(ssl_ocsp_enabled, "proxy.config.ssl.ocsp.enabled");
REC_EstablishStaticConfigInt32(ssl_ocsp_cache_timeout, "proxy.config.ssl.ocsp.cache_timeout");
REC_ReadConfigInt32(ssl_ocsp_request_mode, "proxy.config.ssl.ocsp.request_mode");
REC_EstablishStaticConfigInt32(ssl_ocsp_request_timeout, "proxy.config.ssl.ocsp.request_timeout");
REC_EstablishStaticConfigInt32(ssl_ocsp_update_period, "proxy.config.ssl.ocsp.update_period");
REC_ReadConfigStringAlloc(ssl_ocsp_response_path, "proxy.config.ssl.ocsp.response.path");
Expand Down
3 changes: 3 additions & 0 deletions src/records/RecordsConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,9 @@ static const RecordElement RecordsConfig[] =
// # Number of seconds before an OCSP response expires in the stapling cache. 3600s (1 hour) by default.
{RECT_CONFIG, "proxy.config.ssl.ocsp.cache_timeout", RECD_INT, "3600", RECU_DYNAMIC, RR_NULL, RECC_NULL, "^[0-9]+$", RECA_NULL}
,
// # Request method "mode" for queries to OCSP responders; 0 is POST, 1 is "prefer GET."
{RECT_CONFIG, "proxy.config.ssl.ocsp.request_mode", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
,
// # Timeout for queries to OCSP responders. 10s by default.
{RECT_CONFIG, "proxy.config.ssl.ocsp.request_timeout", RECD_INT, "10", RECU_DYNAMIC, RR_NULL, RECC_NULL, "^[0-9]+$", RECA_NULL}
,
Expand Down