| /** @file |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| #include "P_OCSPStapling.h" |
| |
| #include <memory> |
| #include <mutex> |
| #include <vector> |
| |
| #include <openssl/ssl.h> |
| #include <openssl/x509v3.h> |
| #include <openssl/asn1.h> |
| #include <openssl/asn1t.h> |
| |
| #include "tscore/ink_config.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" |
| #include "SSLStats.h" |
| #include "TLSCertCompression.h" |
| #include "proxy/FetchSM.h" |
| #include "tsutil/Bravo.h" |
| |
| // Macros for ASN1 and the code in TS_OCSP_* functions were borrowed from OpenSSL 3.1.0 (a92271e03a8d0dee507b6f1e7f49512568b2c7ad), |
| // and were modified to make them compilable with BoringSSL and C++ compiler. |
| |
| // Maximum OCSP stapling response size. |
| // This should be the response for a single certificate and will typically include the responder certificate chain, |
| // 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, false> FetchSMAllocator; |
| |
| // clang-format off |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wunused-function" |
| #ifndef GENERAL_NAME_it |
| #if HAVE_GENERAL_NAME_IN_BSSL_NAMESPACE |
| namespace bssl { |
| #endif |
| DECLARE_ASN1_ITEM(GENERAL_NAME) |
| #if HAVE_GENERAL_NAME_IN_BSSL_NAMESPACE |
| } |
| #endif |
| #endif |
| // RFC 6960 |
| using TS_OCSP_CERTID = struct ocsp_cert_id { |
| X509_ALGOR *hashAlgorithm; |
| ASN1_OCTET_STRING *issuerNameHash; |
| ASN1_OCTET_STRING *issuerKeyHash; |
| ASN1_INTEGER *serialNumber; |
| }; |
| DECLARE_ASN1_FUNCTIONS(TS_OCSP_CERTID) |
| ASN1_SEQUENCE(TS_OCSP_CERTID) = { |
| ASN1_SIMPLE(TS_OCSP_CERTID, hashAlgorithm, X509_ALGOR), |
| ASN1_SIMPLE(TS_OCSP_CERTID, issuerNameHash, ASN1_OCTET_STRING), |
| ASN1_SIMPLE(TS_OCSP_CERTID, issuerKeyHash, ASN1_OCTET_STRING), |
| ASN1_SIMPLE(TS_OCSP_CERTID, serialNumber, ASN1_INTEGER) |
| } ASN1_SEQUENCE_END(TS_OCSP_CERTID) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_CERTID) |
| TS_OCSP_CERTID * TS_OCSP_CERTID_dup(const TS_OCSP_CERTID *x) \ |
| { |
| return static_cast<TS_OCSP_CERTID *>(ASN1_item_dup(ASN1_ITEM_rptr(TS_OCSP_CERTID), const_cast<TS_OCSP_CERTID *>(x))); |
| } |
| |
| using TS_OCSP_ONEREQ = struct ocsp_one_request_st { |
| TS_OCSP_CERTID *reqCert; |
| STACK_OF(X509_EXTENSION) *singleRequestExtensions; |
| }; |
| DECLARE_ASN1_FUNCTIONS(TS_OCSP_ONEREQ) |
| ASN1_SEQUENCE(TS_OCSP_ONEREQ) = { |
| ASN1_SIMPLE(TS_OCSP_ONEREQ, reqCert, TS_OCSP_CERTID), |
| ASN1_EXP_SEQUENCE_OF_OPT(TS_OCSP_ONEREQ, singleRequestExtensions, X509_EXTENSION, 0) |
| } ASN1_SEQUENCE_END(TS_OCSP_ONEREQ) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_ONEREQ) |
| |
| using TS_OCSP_REQINFO = struct ocsp_req_info_st { |
| ASN1_INTEGER *version; |
| GENERAL_NAME *requestorName; |
| STACK_OF(TS_OCSP_ONEREQ) *requestList; |
| STACK_OF(X509_EXTENSION) *requestExtensions; |
| }; |
| DECLARE_ASN1_FUNCTIONS(TS_OCSP_REQINFO) |
| ASN1_SEQUENCE(TS_OCSP_REQINFO) = { |
| ASN1_EXP_OPT(TS_OCSP_REQINFO, version, ASN1_INTEGER, 0), |
| #if HAVE_GENERAL_NAME_IN_BSSL_NAMESPACE |
| ASN1_EXP_OPT(TS_OCSP_REQINFO, requestorName, bssl::GENERAL_NAME, 1), |
| #else |
| ASN1_EXP_OPT(TS_OCSP_REQINFO, requestorName, GENERAL_NAME, 1), |
| #endif |
| ASN1_SEQUENCE_OF(TS_OCSP_REQINFO, requestList, TS_OCSP_ONEREQ), |
| ASN1_EXP_SEQUENCE_OF_OPT(TS_OCSP_REQINFO, requestExtensions, X509_EXTENSION, 2) |
| } ASN1_SEQUENCE_END(TS_OCSP_REQINFO) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_REQINFO) |
| |
| using TS_OCSP_SIGNATURE = struct ocsp_signature_st { |
| X509_ALGOR *signatureAlgorithm; |
| ASN1_BIT_STRING *signature; |
| STACK_OF(X509) *certs; |
| }; |
| ASN1_SEQUENCE(TS_OCSP_SIGNATURE) = { |
| ASN1_SIMPLE(TS_OCSP_SIGNATURE, signatureAlgorithm, X509_ALGOR), |
| ASN1_SIMPLE(TS_OCSP_SIGNATURE, signature, ASN1_BIT_STRING), |
| ASN1_EXP_SEQUENCE_OF_OPT(TS_OCSP_SIGNATURE, certs, X509, 0) |
| } ASN1_SEQUENCE_END(TS_OCSP_SIGNATURE) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_SIGNATURE) |
| |
| using TS_OCSP_REQUEST = struct ocsp_req { |
| TS_OCSP_REQINFO *tbsRequest; |
| TS_OCSP_SIGNATURE *optionalSignature; |
| }; |
| DECLARE_ASN1_FUNCTIONS(TS_OCSP_REQUEST) |
| ASN1_SEQUENCE(TS_OCSP_REQUEST) = { |
| ASN1_SIMPLE(TS_OCSP_REQUEST, tbsRequest, TS_OCSP_REQINFO), |
| ASN1_EXP_OPT(TS_OCSP_REQUEST, optionalSignature, TS_OCSP_SIGNATURE, 0) |
| } ASN1_SEQUENCE_END(TS_OCSP_REQUEST) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_REQUEST) |
| |
| using TS_OCSP_RESPBYTES = struct ocsp_resp_bytes_st { |
| ASN1_OBJECT *responseType; |
| ASN1_OCTET_STRING *response; |
| }; |
| DECLARE_ASN1_FUNCTIONS(TS_OCSP_RESPBYTES) |
| ASN1_SEQUENCE(TS_OCSP_RESPBYTES) = { |
| ASN1_SIMPLE(TS_OCSP_RESPBYTES, responseType, ASN1_OBJECT), |
| ASN1_SIMPLE(TS_OCSP_RESPBYTES, response, ASN1_OCTET_STRING) |
| } ASN1_SEQUENCE_END(TS_OCSP_RESPBYTES) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_RESPBYTES) |
| |
| using TS_OCSP_RESPONSE = struct ocsp_resp { |
| ASN1_ENUMERATED *responseStatus; |
| TS_OCSP_RESPBYTES *responseBytes; |
| }; |
| DECLARE_ASN1_FUNCTIONS(TS_OCSP_RESPONSE) |
| ASN1_SEQUENCE(TS_OCSP_RESPONSE) = { |
| ASN1_SIMPLE(TS_OCSP_RESPONSE, responseStatus, ASN1_ENUMERATED), |
| ASN1_EXP_OPT(TS_OCSP_RESPONSE, responseBytes, TS_OCSP_RESPBYTES, 0) |
| } ASN1_SEQUENCE_END(TS_OCSP_RESPONSE) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_RESPONSE) |
| |
| using TS_OCSP_RESPID = struct ocsp_responder_id_st { |
| int type; |
| union { |
| X509_NAME *byName; |
| ASN1_OCTET_STRING *byKey; |
| } value; |
| }; |
| DECLARE_ASN1_FUNCTIONS(TS_OCSP_RESPID) |
| ASN1_CHOICE(TS_OCSP_RESPID) = { |
| ASN1_EXP(TS_OCSP_RESPID, value.byName, X509_NAME, 1), |
| ASN1_EXP(TS_OCSP_RESPID, value.byKey, ASN1_OCTET_STRING, 2) |
| } ASN1_CHOICE_END(TS_OCSP_RESPID) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_RESPID) |
| |
| using TS_OCSP_REVOKEDINFO = struct ocsp_revoked_info_st { |
| ASN1_GENERALIZEDTIME *revocationTime; |
| ASN1_ENUMERATED *revocationReason; |
| }; |
| DECLARE_ASN1_FUNCTIONS(TS_OCSP_REVOKEDINFO) |
| ASN1_SEQUENCE(TS_OCSP_REVOKEDINFO) = { |
| ASN1_SIMPLE(TS_OCSP_REVOKEDINFO, revocationTime, ASN1_GENERALIZEDTIME), |
| ASN1_EXP_OPT(TS_OCSP_REVOKEDINFO, revocationReason, ASN1_ENUMERATED, 0) |
| } ASN1_SEQUENCE_END(TS_OCSP_REVOKEDINFO) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_REVOKEDINFO) |
| |
| using TS_OCSP_CERTSTATUS = struct ocsp_cert_status_st { |
| int type; |
| union { |
| ASN1_NULL *good; |
| TS_OCSP_REVOKEDINFO *revoked; |
| ASN1_NULL *unknown; |
| } value; |
| }; |
| DECLARE_ASN1_FUNCTIONS(TS_OCSP_CERTSTATUS) |
| ASN1_CHOICE(TS_OCSP_CERTSTATUS) = { |
| ASN1_IMP(TS_OCSP_CERTSTATUS, value.good, ASN1_NULL, 0), |
| ASN1_IMP(TS_OCSP_CERTSTATUS, value.revoked, TS_OCSP_REVOKEDINFO, 1), |
| ASN1_IMP(TS_OCSP_CERTSTATUS, value.unknown, ASN1_NULL, 2) |
| } ASN1_CHOICE_END(TS_OCSP_CERTSTATUS) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_CERTSTATUS) |
| |
| using TS_OCSP_SINGLERESP = struct ocsp_single_response_st { |
| TS_OCSP_CERTID *certId; |
| TS_OCSP_CERTSTATUS *certStatus; |
| ASN1_GENERALIZEDTIME *thisUpdate; |
| ASN1_GENERALIZEDTIME *nextUpdate; |
| STACK_OF(X509_EXTENSION) *singleExtensions; |
| }; |
| DECLARE_ASN1_FUNCTIONS(TS_OCSP_SINGLERESP) |
| ASN1_SEQUENCE(TS_OCSP_SINGLERESP) = { |
| ASN1_SIMPLE(TS_OCSP_SINGLERESP, certId, TS_OCSP_CERTID), |
| ASN1_SIMPLE(TS_OCSP_SINGLERESP, certStatus, TS_OCSP_CERTSTATUS), |
| ASN1_SIMPLE(TS_OCSP_SINGLERESP, thisUpdate, ASN1_GENERALIZEDTIME), |
| ASN1_EXP_OPT(TS_OCSP_SINGLERESP, nextUpdate, ASN1_GENERALIZEDTIME, 0), |
| ASN1_EXP_SEQUENCE_OF_OPT(TS_OCSP_SINGLERESP, singleExtensions, X509_EXTENSION, 1) |
| } ASN1_SEQUENCE_END(TS_OCSP_SINGLERESP) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_SINGLERESP) |
| |
| using TS_OCSP_RESPDATA = struct ocsp_response_data_st { |
| ASN1_INTEGER *version; |
| TS_OCSP_RESPID *responderId; |
| ASN1_GENERALIZEDTIME *producedAt; |
| STACK_OF(TS_OCSP_SINGLERESP) *responses; |
| STACK_OF(X509_EXTENSION) *responseExtensions; |
| }; |
| DECLARE_ASN1_FUNCTIONS(TS_OCSP_RESPDATA) |
| ASN1_SEQUENCE(TS_OCSP_RESPDATA) = { |
| ASN1_EXP_OPT(TS_OCSP_RESPDATA, version, ASN1_INTEGER, 0), |
| ASN1_SIMPLE(TS_OCSP_RESPDATA, responderId, TS_OCSP_RESPID), |
| ASN1_SIMPLE(TS_OCSP_RESPDATA, producedAt, ASN1_GENERALIZEDTIME), |
| ASN1_SEQUENCE_OF(TS_OCSP_RESPDATA, responses, TS_OCSP_SINGLERESP), |
| ASN1_EXP_SEQUENCE_OF_OPT(TS_OCSP_RESPDATA, responseExtensions, X509_EXTENSION, 1) |
| } ASN1_SEQUENCE_END(TS_OCSP_RESPDATA) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_RESPDATA) |
| |
| using TS_OCSP_BASICRESP = struct ocsp_basic_response_st { |
| TS_OCSP_RESPDATA *tbsResponseData; |
| X509_ALGOR *signatureAlgorithm; |
| ASN1_BIT_STRING *signature; |
| STACK_OF(X509) *certs; |
| }; |
| DECLARE_ASN1_FUNCTIONS(TS_OCSP_BASICRESP) |
| ASN1_SEQUENCE(TS_OCSP_BASICRESP) = { |
| ASN1_SIMPLE(TS_OCSP_BASICRESP, tbsResponseData, TS_OCSP_RESPDATA), |
| ASN1_SIMPLE(TS_OCSP_BASICRESP, signatureAlgorithm, X509_ALGOR), |
| ASN1_SIMPLE(TS_OCSP_BASICRESP, signature, ASN1_BIT_STRING), |
| ASN1_EXP_SEQUENCE_OF_OPT(TS_OCSP_BASICRESP, certs, X509, 0) |
| } ASN1_SEQUENCE_END(TS_OCSP_BASICRESP) |
| IMPLEMENT_ASN1_FUNCTIONS(TS_OCSP_BASICRESP) |
| |
| DEFINE_STACK_OF(TS_OCSP_ONEREQ) |
| DEFINE_STACK_OF(TS_OCSP_SINGLERESP) |
| |
| ; // This is to satisfy clang-format |
| // clang-format on |
| |
| namespace |
| { |
| |
| constexpr int TS_OCSP_RESPONSE_STATUS_SUCCESSFUL = 0; |
| // constexpr int TS_OCSP_RESPONSE_STATUS_MALFORMEDREQUEST = 1; |
| // constexpr int TS_OCSP_RESPONSE_STATUS_INTERNALERROR = 2; |
| // constexpr int TS_OCSP_RESPONSE_STATUS_TRYLATER = 3; |
| // 4 is not defined on the spec |
| // constexpr int TS_OCSP_RESPONSE_STATUS_SIGREQUIRED = 5; |
| // constexpr int TS_OCSP_RESPONSE_STATUS_UNAUTHORIZED = 6; |
| constexpr int TS_OCSP_CERTSTATUS_GOOD = 0; |
| constexpr int TS_OCSP_CERTSTATUS_REVOKED = 1; |
| constexpr int TS_OCSP_CERTSTATUS_UNKNOWN = 2; |
| #pragma GCC diagnostic pop |
| |
| // End of definitions from RFC 6960 |
| |
| namespace |
| { |
| DbgCtl dbg_ctl_ssl_ocsp{"ssl_ocsp"}; |
| |
| } // end anonymous namespace |
| |
| // Cached info stored in SSL_CTX ex_info |
| struct certinfo { |
| unsigned char idx[20] = {}; // Index in session cache SHA1 hash of certificate |
| TS_OCSP_CERTID *cid = nullptr; // Certificate ID for OCSP requests or nullptr if ID cannot be determined |
| char *uri = nullptr; // Responder details |
| char *certname = nullptr; |
| char *user_agent = nullptr; |
| const bool is_prefetched; |
| |
| // OCSP response data, protected by resp_mutex. |
| // Readers take a shared lock; the updater takes an exclusive lock. |
| unsigned char resp_der[MAX_STAPLING_DER] = {}; |
| unsigned int resp_derlen = 0; |
| bool is_expire = true; |
| time_t expire_time = 0; |
| mutable ts::bravo::shared_mutex resp_mutex; |
| |
| explicit certinfo(bool is_prefetched) : is_prefetched(is_prefetched) {} |
| ~certinfo() |
| { |
| if (cid) { |
| TS_OCSP_CERTID_free(cid); |
| } |
| if (uri) { |
| OPENSSL_free(uri); |
| } |
| ats_free(certname); |
| ats_free(user_agent); |
| } |
| |
| certinfo(const certinfo &) = delete; |
| certinfo &operator=(const certinfo &) = delete; |
| certinfo(certinfo &&) = delete; |
| certinfo &operator=(certinfo &&) = delete; |
| }; |
| |
| class HTTPRequest : public Continuation |
| { |
| public: |
| static constexpr int MAX_RESP_LEN = 100 * 1024; |
| |
| HTTPRequest() |
| { |
| mutex = new_ProxyMutex(); |
| SET_HANDLER(&HTTPRequest::event_handler); |
| } |
| |
| ~HTTPRequest() override |
| { |
| this->_fsm->ext_destroy(); |
| OPENSSL_free(this->_req_body); |
| } |
| |
| int |
| event_handler(int event, Event * /* e ATS_UNUSED */) |
| { |
| if (event == TS_EVENT_IMMEDIATE) { |
| this->fetch(); |
| } else { |
| if (event == TS_FETCH_EVENT_EXT_BODY_DONE) { |
| this->set_done(); |
| } else if (event == TS_EVENT_ERROR) { |
| this->set_error(); |
| } |
| } |
| |
| return 0; |
| } |
| |
| void |
| set_request_line(bool use_get, const char *uri) |
| { |
| struct sockaddr_in sin; |
| memset(&sin, 0, sizeof(sin)); |
| sin.sin_family = AF_INET; |
| sin.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| sin.sin_port = 65535; |
| |
| this->_fsm = FetchSMAllocator.alloc(); |
| this->_fsm->ext_init(this, use_get ? "GET" : "POST", uri, "HTTP/1.1", reinterpret_cast<sockaddr *>(&sin), |
| TS_FETCH_FLAGS_SKIP_REMAP); |
| } |
| |
| int |
| set_body(const char *content_type, const ASN1_ITEM *it, const ASN1_VALUE *req) |
| { |
| this->_req_body = nullptr; |
| |
| if (req != nullptr) { |
| // const_cast is needed for OpenSSL 1.1.1 |
| this->_req_body_len = ASN1_item_i2d(const_cast<ASN1_VALUE *>(req), &this->_req_body, it); |
| if (this->_req_body_len == -1) { |
| return 0; |
| } |
| } |
| this->add_header("Content-Type", content_type); |
| char req_body_len_str[10]; |
| int req_body_len_str_len; |
| req_body_len_str_len = ink_fast_itoa(this->_req_body_len, req_body_len_str, sizeof(req_body_len_str)); |
| this->add_header("Content-Length", 14, req_body_len_str, req_body_len_str_len); |
| |
| return 1; |
| } |
| |
| void |
| add_header(const char *name, int name_len, const char *value, int value_len) |
| { |
| this->_fsm->ext_add_header(name, name_len, value, value_len); |
| } |
| |
| void |
| add_header(const char *name, const char *value) |
| { |
| this->add_header(name, strlen(name), value, strlen(value)); |
| } |
| |
| bool |
| is_initiated() |
| { |
| SCOPED_MUTEX_LOCK(lock, mutex, this_ethread()); |
| return this->_result != INT_MAX; |
| } |
| |
| bool |
| is_done() |
| { |
| SCOPED_MUTEX_LOCK(lock, mutex, this_ethread()); |
| return this->_result != 0 && this->_result != INT_MAX; |
| } |
| |
| bool |
| is_success() |
| { |
| SCOPED_MUTEX_LOCK(lock, mutex, this_ethread()); |
| return this->_result == 1; |
| } |
| |
| unsigned char * |
| get_response_body(int *len) |
| { |
| SCOPED_MUTEX_LOCK(lock, mutex, this_ethread()); |
| char *buf = static_cast<char *>(ats_malloc(MAX_RESP_LEN)); |
| *len = this->_fsm->ext_read_data(buf, MAX_RESP_LEN); |
| return reinterpret_cast<unsigned char *>(buf); |
| } |
| |
| private: |
| FetchSM *_fsm = nullptr; |
| unsigned char *_req_body = nullptr; |
| int _req_body_len = 0; |
| int _result = INT_MAX; |
| |
| void |
| fetch() |
| { |
| SCOPED_MUTEX_LOCK(lock, mutex, this_ethread()); |
| this->_result = 0; |
| this->_fsm->ext_launch(); |
| this->_fsm->ext_write_data(this->_req_body, this->_req_body_len); |
| } |
| |
| void |
| set_done() |
| { |
| SCOPED_MUTEX_LOCK(lock, mutex, this_ethread()); |
| this->_result = 1; |
| } |
| |
| void |
| set_error() |
| { |
| SCOPED_MUTEX_LOCK(lock, mutex, this_ethread()); |
| this->_result = -1; |
| } |
| }; |
| |
| TS_OCSP_CERTID * |
| TS_OCSP_cert_id_new(const EVP_MD *dgst, const X509_NAME *issuerName, const ASN1_BIT_STRING *issuerKey, |
| const ASN1_INTEGER *serialNumber) |
| { |
| int nid; |
| unsigned int i; |
| X509_ALGOR *alg; |
| TS_OCSP_CERTID *cid = nullptr; |
| unsigned char md[EVP_MAX_MD_SIZE]; |
| |
| if ((cid = TS_OCSP_CERTID_new()) == nullptr) { |
| goto err; |
| } |
| |
| alg = cid->hashAlgorithm; |
| ASN1_OBJECT_free(alg->algorithm); |
| if ((nid = EVP_MD_type(dgst)) == NID_undef) { |
| Dbg(dbg_ctl_ssl_ocsp, "Unknown NID"); |
| goto err; |
| } |
| if ((alg->algorithm = OBJ_nid2obj(nid)) == nullptr) { |
| goto err; |
| } |
| if ((alg->parameter = ASN1_TYPE_new()) == nullptr) { |
| goto err; |
| } |
| alg->parameter->type = V_ASN1_NULL; |
| |
| if (!X509_NAME_digest(issuerName, dgst, md, &i)) { |
| goto digerr; |
| } |
| if (!(ASN1_OCTET_STRING_set(cid->issuerNameHash, md, i))) { |
| goto err; |
| } |
| |
| /* Calculate the issuerKey hash, excluding tag and length */ |
| if (!EVP_Digest(ASN1_STRING_get0_data(issuerKey), ASN1_STRING_length(issuerKey), md, &i, dgst, nullptr)) { |
| goto err; |
| } |
| |
| if (!(ASN1_OCTET_STRING_set(cid->issuerKeyHash, md, i))) { |
| goto err; |
| } |
| |
| if (serialNumber) { |
| if (ASN1_STRING_copy(cid->serialNumber, serialNumber) == 0) { |
| goto err; |
| } |
| } |
| return cid; |
| digerr: |
| Dbg(dbg_ctl_ssl_ocsp, "Digest error"); |
| err: |
| TS_OCSP_CERTID_free(cid); |
| return nullptr; |
| } |
| |
| TS_OCSP_CERTID * |
| TS_OCSP_cert_to_id(const EVP_MD *dgst, const X509 *subject, const X509 *issuer) |
| { |
| const X509_NAME *iname; |
| const ASN1_INTEGER *serial; |
| const ASN1_BIT_STRING *ikey; |
| |
| if (!dgst) { |
| dgst = EVP_sha1(); |
| } |
| if (subject) { |
| iname = X509_get_issuer_name(subject); |
| serial = X509_get0_serialNumber(subject); |
| } else { |
| iname = X509_get_subject_name(issuer); |
| serial = nullptr; |
| } |
| ikey = X509_get0_pubkey_bitstr(issuer); |
| return TS_OCSP_cert_id_new(dgst, iname, ikey, serial); |
| } |
| |
| int |
| TS_OCSP_check_validity(ASN1_GENERALIZEDTIME *thisupd, ASN1_GENERALIZEDTIME *nextupd, long nsec, long maxsec) |
| { |
| int ret = 1; |
| time_t t_now, t_tmp; |
| |
| time(&t_now); |
| /* Check thisUpdate is valid and not more than nsec in the future */ |
| if (!ASN1_GENERALIZEDTIME_check(thisupd)) { |
| Dbg(dbg_ctl_ssl_ocsp, "Error in thisUpdate field"); |
| ret = 0; |
| } else { |
| t_tmp = t_now + nsec; |
| if (X509_cmp_time(thisupd, &t_tmp) > 0) { |
| Dbg(dbg_ctl_ssl_ocsp, "Status not yet valid"); |
| ret = 0; |
| } |
| |
| /* |
| * If maxsec specified check thisUpdate is not more than maxsec in |
| * the past |
| */ |
| if (maxsec >= 0) { |
| t_tmp = t_now - maxsec; |
| if (X509_cmp_time(thisupd, &t_tmp) < 0) { |
| Dbg(dbg_ctl_ssl_ocsp, "Status too old"); |
| ret = 0; |
| } |
| } |
| } |
| |
| if (nextupd == nullptr) { |
| return ret; |
| } |
| |
| /* Check nextUpdate is valid and not more than nsec in the past */ |
| if (!ASN1_GENERALIZEDTIME_check(nextupd)) { |
| Dbg(dbg_ctl_ssl_ocsp, "Error in nextUpdate field"); |
| ret = 0; |
| } else { |
| t_tmp = t_now - nsec; |
| if (X509_cmp_time(nextupd, &t_tmp) < 0) { |
| Dbg(dbg_ctl_ssl_ocsp, "Status expired"); |
| ret = 0; |
| } |
| } |
| |
| /* Also don't allow nextUpdate to precede thisUpdate */ |
| if (ASN1_STRING_cmp(nextupd, thisupd) < 0) { |
| Dbg(dbg_ctl_ssl_ocsp, "nextUpdate precedes thisUpdate"); |
| ret = 0; |
| } |
| |
| return ret; |
| } |
| |
| TS_OCSP_ONEREQ * |
| TS_OCSP_request_add0_id(TS_OCSP_REQUEST *req, TS_OCSP_CERTID *cid) |
| { |
| TS_OCSP_ONEREQ *one = nullptr; |
| |
| if ((one = TS_OCSP_ONEREQ_new()) == nullptr) { |
| return nullptr; |
| } |
| TS_OCSP_CERTID_free(one->reqCert); |
| one->reqCert = cid; |
| if (req && !sk_TS_OCSP_ONEREQ_push(req->tbsRequest->requestList, one)) { |
| one->reqCert = nullptr; /* do not free on error */ |
| TS_OCSP_ONEREQ_free(one); |
| return nullptr; |
| } |
| return one; |
| } |
| |
| TS_OCSP_BASICRESP * |
| TS_OCSP_response_get1_basic(TS_OCSP_RESPONSE *resp) |
| { |
| TS_OCSP_RESPBYTES *rb = resp->responseBytes; |
| |
| if (rb == nullptr) { |
| Dbg(dbg_ctl_ssl_ocsp, "No response data"); |
| return nullptr; |
| } |
| if (OBJ_obj2nid(rb->responseType) != NID_id_pkix_OCSP_basic) { |
| Dbg(dbg_ctl_ssl_ocsp, "Not basic response"); |
| return nullptr; |
| } |
| |
| return static_cast<TS_OCSP_BASICRESP *>(ASN1_item_unpack(rb->response, ASN1_ITEM_rptr(TS_OCSP_BASICRESP))); |
| } |
| |
| int |
| TS_OCSP_id_issuer_cmp(const TS_OCSP_CERTID *a, const TS_OCSP_CERTID *b) |
| { |
| int ret; |
| ret = OBJ_cmp(a->hashAlgorithm->algorithm, b->hashAlgorithm->algorithm); |
| if (ret) { |
| return ret; |
| } |
| ret = ASN1_OCTET_STRING_cmp(a->issuerNameHash, b->issuerNameHash); |
| if (ret) { |
| return ret; |
| } |
| return ASN1_OCTET_STRING_cmp(a->issuerKeyHash, b->issuerKeyHash); |
| } |
| |
| int |
| TS_OCSP_id_cmp(const TS_OCSP_CERTID *a, const TS_OCSP_CERTID *b) |
| { |
| int ret; |
| ret = TS_OCSP_id_issuer_cmp(a, b); |
| if (ret) { |
| return ret; |
| } |
| return ASN1_INTEGER_cmp(a->serialNumber, b->serialNumber); |
| } |
| |
| int |
| TS_OCSP_resp_find(TS_OCSP_BASICRESP *bs, TS_OCSP_CERTID *id, int last) |
| { |
| int i; |
| STACK_OF(TS_OCSP_SINGLERESP) * sresp; |
| TS_OCSP_SINGLERESP *single; |
| |
| if (bs == nullptr) { |
| return -1; |
| } |
| if (last < 0) { |
| last = 0; |
| } else { |
| last++; |
| } |
| sresp = bs->tbsResponseData->responses; |
| for (i = last; i < static_cast<int>(sk_TS_OCSP_SINGLERESP_num(sresp)); i++) { |
| single = sk_TS_OCSP_SINGLERESP_value(sresp, i); |
| if (!TS_OCSP_id_cmp(id, single->certId)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| TS_OCSP_SINGLERESP * |
| TS_OCSP_resp_get0(TS_OCSP_BASICRESP *bs, int idx) |
| { |
| if (bs == nullptr) { |
| return nullptr; |
| } |
| return sk_TS_OCSP_SINGLERESP_value(bs->tbsResponseData->responses, idx); |
| } |
| |
| int |
| TS_OCSP_single_get0_status(TS_OCSP_SINGLERESP *single, int *reason, ASN1_GENERALIZEDTIME **revtime, ASN1_GENERALIZEDTIME **thisupd, |
| ASN1_GENERALIZEDTIME **nextupd) |
| { |
| int ret; |
| TS_OCSP_CERTSTATUS *cst; |
| |
| if (single == nullptr) { |
| return -1; |
| } |
| cst = single->certStatus; |
| ret = cst->type; |
| if (ret == TS_OCSP_CERTSTATUS_REVOKED) { |
| TS_OCSP_REVOKEDINFO *rev = cst->value.revoked; |
| |
| if (revtime) { |
| *revtime = rev->revocationTime; |
| } |
| if (reason) { |
| if (rev->revocationReason) { |
| *reason = ASN1_ENUMERATED_get(rev->revocationReason); |
| } else { |
| *reason = -1; |
| } |
| } |
| } |
| if (thisupd != nullptr) { |
| *thisupd = single->thisUpdate; |
| } |
| if (nextupd != nullptr) { |
| *nextupd = single->nextUpdate; |
| } |
| return ret; |
| } |
| |
| int |
| TS_OCSP_resp_find_status(TS_OCSP_BASICRESP *bs, TS_OCSP_CERTID *id, int *status, int *reason, ASN1_GENERALIZEDTIME **revtime, |
| ASN1_GENERALIZEDTIME **thisupd, ASN1_GENERALIZEDTIME **nextupd) |
| { |
| int i = TS_OCSP_resp_find(bs, id, -1); |
| TS_OCSP_SINGLERESP *single; |
| |
| /* Maybe check for multiple responses and give an error? */ |
| if (i < 0) { |
| return 0; |
| } |
| single = TS_OCSP_resp_get0(bs, i); |
| i = TS_OCSP_single_get0_status(single, reason, revtime, thisupd, nextupd); |
| if (i < 0) { |
| return 0; |
| } |
| if (status != nullptr) { |
| *status = i; |
| } |
| return 1; |
| } |
| |
| } // End of namespace |
| |
| /* |
| * In the case of multiple certificates associated with a SSL_CTX, we must store a map |
| * of cached responses |
| */ |
| using certinfo_map = std::map<X509 *, std::unique_ptr<certinfo>>; |
| |
| void |
| certinfo_map_free(void * /*parent*/, void *ptr, CRYPTO_EX_DATA * /*ad*/, int /*idx*/, long /*argl*/, void * /*argp*/) |
| { |
| certinfo_map *map = static_cast<certinfo_map *>(ptr); |
| |
| if (!map) { |
| return; |
| } |
| |
| #ifdef OPENSSL_IS_BORINGSSL |
| // The map owns a reference on each X509 key under BoringSSL; the certinfo |
| // values are freed by their unique_ptr destructors when the map is deleted. |
| for (auto &iter : *map) { |
| X509_free(iter.first); |
| } |
| #endif |
| delete map; |
| } |
| |
| static int ssl_stapling_index = -1; |
| |
| void |
| ssl_stapling_ex_init() |
| { |
| if (ssl_stapling_index != -1) { |
| return; |
| } |
| ssl_stapling_index = SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, certinfo_map_free); |
| } |
| |
| static X509 * |
| stapling_get_issuer(SSL_CTX *ssl_ctx, X509 *x) |
| { |
| X509 *issuer = nullptr; |
| X509_STORE *st = SSL_CTX_get_cert_store(ssl_ctx); |
| STACK_OF(X509) *extra_certs = nullptr; |
| X509_STORE_CTX *inctx = X509_STORE_CTX_new(); |
| |
| if (inctx == nullptr) { |
| return nullptr; |
| } |
| |
| #ifdef SSL_CTX_select_current_cert |
| if (!SSL_CTX_select_current_cert(ssl_ctx, x)) { |
| Warning("OCSP: could not select current certificate chain %p", x); |
| } |
| #endif |
| |
| if (X509_STORE_CTX_init(inctx, st, nullptr, nullptr) == 0) { |
| goto end; |
| } |
| |
| #ifdef SSL_CTX_get_extra_chain_certs |
| SSL_CTX_get_extra_chain_certs(ssl_ctx, &extra_certs); |
| #else |
| extra_certs = ssl_ctx->extra_certs; |
| #endif |
| |
| if (sk_X509_num(extra_certs) == 0) { |
| goto end; |
| } |
| |
| for (int i = 0; i < static_cast<int>(sk_X509_num(extra_certs)); i++) { |
| issuer = sk_X509_value(extra_certs, i); |
| if (X509_check_issued(issuer, x) == X509_V_OK) { |
| X509_up_ref(issuer); |
| goto end; |
| } |
| } |
| |
| if (X509_STORE_CTX_get1_issuer(&issuer, inctx, x) <= 0) { |
| issuer = nullptr; |
| } |
| |
| end: |
| X509_STORE_CTX_free(inctx); |
| |
| return issuer; |
| } |
| |
| static bool |
| stapling_cache_response(TS_OCSP_RESPONSE *rsp, certinfo *cinf) |
| { |
| unsigned char resp_der[MAX_STAPLING_DER]; |
| unsigned char *p; |
| unsigned int resp_derlen; |
| |
| p = resp_der; |
| resp_derlen = i2d_TS_OCSP_RESPONSE(rsp, &p); |
| |
| if (resp_derlen == 0) { |
| Error("stapling_cache_response: cannot decode OCSP response for %s", cinf->certname); |
| return false; |
| } |
| |
| if (resp_derlen > MAX_STAPLING_DER) { |
| Error("stapling_cache_response: OCSP response too big (%u bytes) for %s", resp_derlen, cinf->certname); |
| return false; |
| } |
| |
| { |
| std::lock_guard<ts::bravo::shared_mutex> lock(cinf->resp_mutex); |
| memcpy(cinf->resp_der, resp_der, resp_derlen); |
| cinf->resp_derlen = resp_derlen; |
| cinf->is_expire = false; |
| cinf->expire_time = time(nullptr) + SSLConfigParams::ssl_ocsp_cache_timeout; |
| } |
| |
| Dbg(dbg_ctl_ssl_ocsp, "stapling_cache_response: success to cache response"); |
| return true; |
| } |
| |
| bool |
| ssl_stapling_init_cert(SSL_CTX *ctx, X509 *cert, const char *certname, const char *rsp_file) |
| { |
| scoped_X509 issuer; |
| STACK_OF(OPENSSL_STRING) *aia = nullptr; |
| TS_OCSP_RESPONSE *rsp = nullptr; |
| |
| if (!cert) { |
| Error("null cert passed in for %s", certname); |
| return false; |
| } |
| |
| certinfo_map *map = static_cast<certinfo_map *>(SSL_CTX_get_ex_data(ctx, ssl_stapling_index)); |
| if (map && map->find(cert) != map->end()) { |
| Note("certificate already initialized for %s", certname); |
| return false; |
| } |
| |
| bool map_is_new = false; |
| if (!map) { |
| map = new certinfo_map; |
| map_is_new = true; |
| } |
| auto cinf_ptr = std::make_unique<certinfo>(rsp_file != nullptr); |
| certinfo *cinf = cinf_ptr.get(); |
| |
| // Initialize certinfo |
| cinf->certname = ats_strdup(certname); |
| if (SSLConfigParams::ssl_ocsp_user_agent != nullptr) { |
| cinf->user_agent = ats_strdup(SSLConfigParams::ssl_ocsp_user_agent); |
| } |
| if (cinf->is_prefetched) { |
| Dbg(dbg_ctl_ssl_ocsp, "using OCSP prefetched response file %s", rsp_file); |
| FILE *fp = fopen(rsp_file, "r"); |
| if (fp) { |
| long rsp_buf_len = -1; |
| |
| if (fseek(fp, 0, SEEK_END) == 0) { |
| rsp_buf_len = ftell(fp); |
| } |
| |
| if (rsp_buf_len > 0 && fseek(fp, 0, SEEK_SET) == 0) { |
| std::vector<unsigned char> rsp_buf(rsp_buf_len); |
| auto read_len = fread(rsp_buf.data(), 1, rsp_buf.size(), fp); |
| |
| if (read_len == static_cast<size_t>(rsp_buf_len)) { |
| const unsigned char *p = rsp_buf.data(); |
| rsp = d2i_TS_OCSP_RESPONSE(nullptr, &p, rsp_buf_len); |
| } else { |
| Error("stapling_refresh_response: failed to read prefetched response file: %s", rsp_file); |
| } |
| } else { |
| Error("stapling_refresh_response: failed to check the size of prefetched response file: %s", rsp_file); |
| } |
| fclose(fp); |
| } |
| |
| if (!fp || !rsp) { |
| Note("cannot get prefetched response for %s from %s", certname, rsp_file); |
| goto err; |
| } |
| |
| if (!stapling_cache_response(rsp, cinf)) { |
| Error("stapling_refresh_response: can not cache response"); |
| goto err; |
| } else { |
| Dbg(dbg_ctl_ssl_ocsp, "stapling_refresh_response: successful refresh OCSP response"); |
| TS_OCSP_RESPONSE_free(rsp); |
| rsp = nullptr; |
| } |
| } |
| |
| issuer.reset(stapling_get_issuer(ctx, cert)); |
| if (issuer.get() == nullptr) { |
| Note("cannot get issuer certificate from %s", certname); |
| goto err; |
| } |
| |
| cinf->cid = TS_OCSP_cert_to_id(nullptr, cert, issuer.get()); |
| if (!cinf->cid) { |
| goto err; |
| } |
| |
| X509_digest(cert, EVP_sha1(), cinf->idx, nullptr); |
| |
| aia = X509_get1_ocsp(cert); |
| if (aia) { |
| cinf->uri = sk_OPENSSL_STRING_pop(aia); |
| X509_email_free(aia); |
| } |
| |
| if (!cinf->uri) { |
| Note("no OCSP responder URI for %s", certname); |
| goto err; |
| } |
| |
| #ifdef OPENSSL_IS_BORINGSSL |
| X509_up_ref(cert); |
| #endif |
| |
| map->emplace(cert, std::move(cinf_ptr)); |
| SSL_CTX_set_ex_data(ctx, ssl_stapling_index, map); |
| |
| Note("successfully initialized stapling for %s into SSL_CTX: %p uri=%s", certname, ctx, cinf->uri); |
| return true; |
| |
| err: |
| // cinf_ptr destructor handles certinfo cleanup. |
| // Only tear down the map if this call created it; an existing map is still |
| // owned by the SSL_CTX. |
| if (map_is_new) { |
| delete map; |
| } |
| |
| if (rsp) { |
| TS_OCSP_RESPONSE_free(rsp); |
| } |
| |
| return false; |
| } |
| |
| static certinfo_map * |
| stapling_get_cert_info(SSL_CTX *ctx) |
| { |
| certinfo_map *map; |
| |
| // Only return the map if it contains at least one element with a valid entry |
| map = static_cast<certinfo_map *>(SSL_CTX_get_ex_data(ctx, ssl_stapling_index)); |
| if (map && !map->empty() && map->begin()->second && map->begin()->second->cid) { |
| return map; |
| } |
| |
| return nullptr; |
| } |
| |
| static int |
| stapling_check_response(certinfo *cinf, TS_OCSP_RESPONSE *rsp) |
| { |
| int status = TS_OCSP_CERTSTATUS_UNKNOWN, reason; |
| TS_OCSP_BASICRESP *bs = nullptr; |
| ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; |
| int response_status = ASN1_ENUMERATED_get(rsp->responseStatus); |
| |
| // Check to see if response is an error. |
| // If so we automatically accept it because it would have expired from the cache if it was time to retry. |
| if (response_status != TS_OCSP_RESPONSE_STATUS_SUCCESSFUL) { |
| return SSL_TLSEXT_ERR_NOACK; |
| } |
| |
| bs = TS_OCSP_response_get1_basic(rsp); |
| if (bs == nullptr) { |
| // If we can't parse response just pass it back to client |
| Error("stapling_check_response: cannot parse response for %s", cinf->certname); |
| return SSL_TLSEXT_ERR_OK; |
| } |
| if (!TS_OCSP_resp_find_status(bs, cinf->cid, &status, &reason, &rev, &thisupd, &nextupd)) { |
| // If ID not present just pass it back to client |
| Error("stapling_check_response: certificate ID not present in response for %s", cinf->certname); |
| } else { |
| if (!TS_OCSP_check_validity(thisupd, nextupd, 300, -1)) { |
| // The check is just for logging and pass the response back to client anyway |
| Error("stapling_check_response: status in response for %s is not valid already/yet", cinf->certname); |
| } |
| } |
| |
| switch (status) { |
| case TS_OCSP_CERTSTATUS_GOOD: |
| break; |
| case TS_OCSP_CERTSTATUS_REVOKED: |
| Metrics::Counter::increment(ssl_rsb.ocsp_revoked_cert); |
| break; |
| case TS_OCSP_CERTSTATUS_UNKNOWN: |
| Metrics::Counter::increment(ssl_rsb.ocsp_unknown_cert); |
| break; |
| default: |
| break; |
| } |
| |
| TS_OCSP_BASICRESP_free(bs); |
| |
| return SSL_TLSEXT_ERR_OK; |
| } |
| |
| static TS_OCSP_RESPONSE * |
| 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; |
| |
| start = ink_get_hrtime(); |
| end = ink_hrtime_add(start, ink_hrtime_from_sec(req_timeout)); |
| |
| HTTPRequest httpreq; |
| |
| httpreq.set_request_line(use_get, uri); |
| |
| // Host header |
| const char *host = strstr(uri, "://") + 3; |
| const char *slash = strchr(host, '/'); |
| if (slash == nullptr) { |
| slash = host + strlen(host); |
| } |
| int host_len = slash - host; |
| httpreq.add_header("Host", 4, host, host_len); |
| |
| // User-Agent header |
| if (user_agent != nullptr) { |
| httpreq.add_header("User-Agent", user_agent); |
| } |
| |
| // Content-Type, Content-Length, Request Body |
| if (!use_get) { |
| if (httpreq.set_body("application/ocsp-request", ASN1_ITEM_rptr(TS_OCSP_REQUEST), reinterpret_cast<const ASN1_VALUE *>(req)) != |
| 1) { |
| Error("failed to make a request for OCSP server; uri=%s", uri); |
| return nullptr; |
| } |
| } |
| |
| // Send request |
| Event *e = eventProcessor.schedule_imm(&httpreq, ET_NET); |
| |
| // Wait until the request completes |
| do { |
| ink_hrtime_sleep(HRTIME_MSECONDS(1)); |
| } while (!httpreq.is_done() && (ink_get_hrtime() < end)); |
| |
| if (!httpreq.is_done()) { |
| Error("OCSP request was timed out; uri=%s", uri); |
| if (!httpreq.is_initiated()) { |
| Dbg(dbg_ctl_ssl_ocsp, "Request is not initiated yet. Cancelling the event."); |
| e->cancel(&httpreq); |
| } |
| } |
| |
| if (httpreq.is_success()) { |
| // Parse the response |
| int len; |
| unsigned char *res = httpreq.get_response_body(&len); |
| const unsigned char *p = res; |
| resp = reinterpret_cast<TS_OCSP_RESPONSE *>(ASN1_item_d2i(nullptr, &p, len, ASN1_ITEM_rptr(TS_OCSP_RESPONSE))); |
| |
| if (resp) { |
| ats_free(res); |
| return resp; |
| } |
| |
| if (len < 5) { |
| Error("failed to parse a response from OCSP server; uri=%s len=%d", uri, len); |
| } else { |
| Error("failed to parse a response from OCSP server; uri=%s len=%d data=%02x%02x%02x%02x%02x...", uri, len, res[0], res[1], |
| res[2], res[3], res[4]); |
| } |
| ats_free(res); |
| return nullptr; |
| } |
| |
| Error("failed to get a response from OCSP server; uri=%s", uri); |
| 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; |
| } |
| Dbg(dbg_ctl_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); |
| Dbg(dbg_ctl_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; |
| } |
| Dbg(dbg_ctl_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); |
| Dbg(dbg_ctl_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; |
| } |
| |
| Dbg(dbg_ctl_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] != '/') { |
| written = ink_strlcat(url->end(), "/", url->write_avail()); |
| url->fill(written); |
| } |
| |
| written = ink_strlcat(url->end(), ocsp_escaped, url->write_avail()); |
| url->fill(written); |
| Dbg(dbg_ctl_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; |
| IOBufferBlock *url_for_get = nullptr; |
| const char *url = nullptr; // Final URL to use |
| bool use_get = false; |
| |
| *prsp = nullptr; |
| |
| req = TS_OCSP_REQUEST_new(); |
| if (!req) { |
| goto err; |
| } |
| id = TS_OCSP_CERTID_dup(cinf->cid); |
| if (!id) { |
| goto err; |
| } |
| if (!TS_OCSP_request_add0_id(req, id)) { |
| // add0 only takes ownership of id on success. |
| TS_OCSP_CERTID_free(id); |
| goto err; |
| } |
| |
| 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; |
| } |
| |
| Dbg(dbg_ctl_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; |
| } |
| |
| response_status = ASN1_ENUMERATED_get((*prsp)->responseStatus); |
| if (response_status == TS_OCSP_RESPONSE_STATUS_SUCCESSFUL) { |
| Dbg(dbg_ctl_ssl_ocsp, "stapling_refresh_response: query response received"); |
| stapling_check_response(cinf, *prsp); |
| } else { |
| 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)) { |
| Error("stapling_refresh_response: can not cache response"); |
| } else { |
| Dbg(dbg_ctl_ssl_ocsp, "stapling_refresh_response: successfully refreshed OCSP response"); |
| } |
| goto done; |
| |
| err: |
| rv = false; |
| Error("stapling_refresh_response: failed to refresh OCSP response"); |
| |
| done: |
| if (req) { |
| TS_OCSP_REQUEST_free(req); |
| } |
| if (*prsp) { |
| TS_OCSP_RESPONSE_free(*prsp); |
| } |
| if (url_for_get) { |
| url_for_get->free(); |
| } |
| return rv; |
| } |
| |
| OCSPStatus |
| ocsp_update() |
| { |
| if (!FetchSM::is_initialized()) { |
| Dbg(dbg_ctl_ssl_ocsp, "FetchSM is not yet initialized. Skipping OCSP update."); |
| return OCSPStatus::OCSP_FETCHSM_NOT_INITIALIZED; |
| } |
| shared_SSL_CTX ctx; |
| TS_OCSP_RESPONSE *resp = nullptr; |
| time_t current_time; |
| |
| Dbg(dbg_ctl_ssl_ocsp, "OCSP refresh started"); |
| |
| SSLCertificateConfig::scoped_config certLookup; |
| |
| Dbg(dbg_ctl_ssl_ocsp, "updating OCSP data"); |
| #ifdef HAVE_NATIVE_DUAL_CERT_SUPPORT |
| const SSLCertContextType ctxTypes[] = {SSLCertContextType::GENERIC}; |
| #else |
| const SSLCertContextType ctxTypes[] = {SSLCertContextType::RSA, SSLCertContextType::EC}; |
| #endif |
| for (const auto &ctxType : ctxTypes) { |
| const unsigned ctxCount = certLookup ? certLookup->count(ctxType) : 0; |
| for (unsigned i = 0; i < ctxCount; i++) { |
| SSLCertContext *cc = certLookup->get(i, ctxType); |
| if (cc) { |
| ctx = cc->getCtx(); |
| if (ctx) { |
| certinfo *cinf = nullptr; |
| certinfo_map *map = stapling_get_cert_info(ctx.get()); |
| if (map) { |
| // Walk over all certs associated with this CTX |
| for (auto &iter : *map) { |
| cinf = iter.second.get(); |
| current_time = time(nullptr); |
| bool needs_refresh; |
| { |
| ts::bravo::shared_lock<ts::bravo::shared_mutex> lock(cinf->resp_mutex); |
| needs_refresh = cinf->resp_derlen == 0 || cinf->is_expire || cinf->expire_time < current_time; |
| } |
| if (needs_refresh) { |
| if (stapling_refresh_response(cinf, &resp)) { |
| Dbg(dbg_ctl_ssl_ocsp, "Successfully refreshed OCSP for %s certificate. url=%s", cinf->certname, cinf->uri); |
| Metrics::Counter::increment(ssl_rsb.ocsp_refreshed_cert); |
| cert_compress_invalidate_or_recompress(ctx.get()); |
| } else { |
| Error("Failed to refresh OCSP for %s certificate. url=%s", cinf->certname, cinf->uri); |
| Metrics::Counter::increment(ssl_rsb.ocsp_refresh_cert_failure); |
| cert_compress_invalidate_or_recompress(ctx.get()); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| Dbg(dbg_ctl_ssl_ocsp, "OCSP refresh finished"); |
| return OCSPStatus::OCSP_OK; |
| } |
| |
| // RFC 6066 Section-8: Certificate Status Request |
| int |
| ssl_callback_ocsp_stapling(SSL *ssl, void *) |
| { |
| // Assume SSL_get_SSL_CTX() is the same as reaching into the ssl structure |
| // Using the official call, to avoid leaking internal openssl knowledge |
| // originally was, cinf = stapling_get_cert_info(ssl->ctx); |
| certinfo_map *map = stapling_get_cert_info(SSL_get_SSL_CTX(ssl)); |
| if (map == nullptr) { |
| Dbg(dbg_ctl_ssl_ocsp, "ssl_callback_ocsp_stapling: failed to get certificate map"); |
| return SSL_TLSEXT_ERR_NOACK; |
| } |
| |
| if (map->empty()) { |
| Dbg(dbg_ctl_ssl_ocsp, "ssl_callback_ocsp_stapling: certificate map empty"); |
| return SSL_TLSEXT_ERR_NOACK; |
| } |
| |
| certinfo *cinf = nullptr; |
| |
| #ifndef HAVE_NATIVE_DUAL_CERT_SUPPORT |
| // Fast path: without native dual-cert support each SSL_CTX holds exactly one |
| // certificate, so a single map entry must be the negotiated cert. This skips |
| // the SSL_get_certificate() lookup and the X509_cmp() DER re-parse below. |
| // Not safe under HAVE_NATIVE_DUAL_CERT_SUPPORT: a dual-cert CTX where only one |
| // cert has OCSP info also yields map->size()==1, but the negotiated cert may |
| // be the other one. |
| if (map->size() == 1) { |
| cinf = map->begin()->second.get(); |
| } else |
| #endif |
| { |
| // Fetch the specific certificate used in this negotiation |
| X509 *cert = SSL_get_certificate(ssl); |
| if (!cert) { |
| Error("ssl_callback_ocsp_stapling: failed to get certificate"); |
| return SSL_TLSEXT_ERR_NOACK; |
| } |
| |
| #if HAVE_NATIVE_DUAL_CERT_SUPPORT |
| certinfo_map::iterator iter = map->find(cert); |
| if (iter != map->end()) { |
| cinf = iter->second.get(); |
| } |
| #else |
| for (certinfo_map::iterator iter = map->begin(); iter != map->end(); ++iter) { |
| X509 *key = iter->first; |
| if (key == nullptr) { |
| continue; |
| } |
| |
| if (X509_cmp(key, cert) == 0) { |
| cinf = iter->second.get(); |
| break; |
| } |
| } |
| #endif |
| } |
| |
| if (cinf == nullptr) { |
| Error("ssl_callback_ocsp_stapling: failed to get certificate information for ssl=%p", ssl); |
| return SSL_TLSEXT_ERR_NOACK; |
| } |
| |
| #ifdef OPENSSL_IS_BORINGSSL |
| int set_ok; |
| { |
| ts::bravo::shared_lock<ts::bravo::shared_mutex> lock(cinf->resp_mutex); |
| |
| time_t current_time = time(nullptr); |
| if (cinf->resp_derlen == 0 || cinf->is_expire || (cinf->expire_time < current_time && !cinf->is_prefetched)) { |
| SiteThrottledError("ssl_callback_ocsp_stapling: failed to get certificate status for %s", cinf->certname); |
| return SSL_TLSEXT_ERR_NOACK; |
| } |
| |
| // SSL_set_ocsp_response copies the response, so hand it the cached buffer directly. |
| set_ok = SSL_set_ocsp_response(ssl, cinf->resp_der, cinf->resp_derlen); |
| } |
| #else |
| unsigned char *p = nullptr; |
| unsigned int resp_capacity = 0; |
| unsigned int resp_derlen; |
| |
| while (true) { |
| unsigned int required_capacity; |
| bool is_response_available; |
| { |
| ts::bravo::shared_lock<ts::bravo::shared_mutex> lock(cinf->resp_mutex); |
| |
| time_t current_time = time(nullptr); |
| is_response_available = |
| cinf->resp_derlen != 0 && !cinf->is_expire && (cinf->expire_time >= current_time || cinf->is_prefetched); |
| |
| if (is_response_available) { |
| resp_derlen = cinf->resp_derlen; |
| if (resp_derlen <= resp_capacity) { |
| memcpy(p, cinf->resp_der, resp_derlen); |
| break; |
| } |
| required_capacity = resp_derlen; |
| } |
| } |
| |
| if (!is_response_available) { |
| OPENSSL_free(p); |
| SiteThrottledError("ssl_callback_ocsp_stapling: failed to get certificate status for %s", cinf->certname); |
| return SSL_TLSEXT_ERR_NOACK; |
| } |
| |
| unsigned char *new_p = static_cast<unsigned char *>(OPENSSL_malloc(required_capacity)); |
| if (new_p == nullptr) { |
| OPENSSL_free(p); |
| Dbg(dbg_ctl_ssl_ocsp, "ssl_callback_ocsp_stapling: failed to allocate memory for %s", cinf->certname); |
| return SSL_TLSEXT_ERR_NOACK; |
| } |
| OPENSSL_free(p); |
| p = new_p; |
| resp_capacity = required_capacity; |
| } |
| |
| // Takes ownership of p and frees it on success; on failure it does not. |
| int set_ok = SSL_set_tlsext_status_ocsp_resp(ssl, p, resp_derlen); |
| if (set_ok == 0) { |
| OPENSSL_free(p); |
| } |
| #endif |
| |
| if (set_ok == 0) { |
| return SSL_TLSEXT_ERR_NOACK; |
| } |
| |
| Dbg(dbg_ctl_ssl_ocsp, "ssl_callback_ocsp_stapling: successfully got certificate status for %s", cinf->certname); |
| Dbg(dbg_ctl_ssl_ocsp, "is_prefetched:%d uri:%s", cinf->is_prefetched, cinf->uri); |
| return SSL_TLSEXT_ERR_OK; |
| } |