plugins/experimental/sslheaders/sslheaders.cc (271 lines of code) (raw):

/* * 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 "sslheaders.h" #include "tscore/ink_memory.h" #include <getopt.h> #include <openssl/ssl.h> #include <openssl/x509.h> namespace sslheaders_ns { DbgCtl dbg_ctl{PLUGIN_NAME}; } static void SslHdrExpand(SSL *, const SslHdrInstance::expansion_list &, TSMBuffer, TSMLoc); static int SslHdrExpandRequestHook(TSCont cont, TSEvent event, void *edata) { const SslHdrInstance *hdr; TSHttpTxn txn; TSMBuffer mbuf; TSMLoc mhdr; txn = static_cast<TSHttpTxn>(edata); hdr = static_cast<const SslHdrInstance *>(TSContDataGet(cont)); TSVConn vconn = TSHttpSsnClientVConnGet(TSHttpTxnSsnGet(txn)); TSSslConnection ssl = TSVConnSslConnectionGet(vconn); switch (event) { case TS_EVENT_HTTP_READ_REQUEST_HDR: if (TSHttpTxnClientReqGet(txn, &mbuf, &mhdr) != TS_SUCCESS) { goto done; } break; case TS_EVENT_HTTP_SEND_REQUEST_HDR: if (TSHttpTxnServerReqGet(txn, &mbuf, &mhdr) != TS_SUCCESS) { goto done; } // If we are only attaching to the client request, NULL the SSL context in order to // nuke the SSL headers from the server request. if (hdr->attach == SSL_HEADERS_ATTACH_CLIENT) { ssl = nullptr; } break; default: goto done; } SslHdrExpand(reinterpret_cast<SSL *>(ssl), hdr->expansions, mbuf, mhdr); TSHandleMLocRelease(mbuf, TS_NULL_MLOC, mhdr); done: TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); return TS_EVENT_NONE; } static void SslHdrRemoveHeader(TSMBuffer mbuf, TSMLoc mhdr, const std::string &name) { TSMLoc field; TSMLoc next; field = TSMimeHdrFieldFind(mbuf, mhdr, name.c_str(), name.size()); for (; field != TS_NULL_MLOC; field = next) { next = TSMimeHdrFieldNextDup(mbuf, mhdr, field); TSMimeHdrFieldDestroy(mbuf, mhdr, field); TSHandleMLocRelease(mbuf, mhdr, field); } } static void SslHdrSetHeader(TSMBuffer mbuf, TSMLoc mhdr, const std::string &name, BIO *value) { TSMLoc field; long vlen; char *vptr; vlen = BIO_get_mem_data(value, &vptr); SslHdrDebug("SSL header '%s'", name.c_str()); field = TSMimeHdrFieldFind(mbuf, mhdr, name.c_str(), name.size()); if (field == TS_NULL_MLOC) { TSMimeHdrFieldCreateNamed(mbuf, mhdr, name.c_str(), name.size(), &field); TSMimeHdrFieldValueStringSet(mbuf, mhdr, field, -1, vptr, vlen); TSMimeHdrFieldAppend(mbuf, mhdr, field); TSHandleMLocRelease(mbuf, mhdr, field); } else { TSMLoc next; // Overwrite the first value. TSMimeHdrFieldValueStringSet(mbuf, mhdr, field, -1, vptr, vlen); next = TSMimeHdrFieldNextDup(mbuf, mhdr, field); TSHandleMLocRelease(mbuf, mhdr, field); for (field = next; field != TS_NULL_MLOC; field = next) { next = TSMimeHdrFieldNextDup(mbuf, mhdr, field); TSMimeHdrFieldDestroy(mbuf, mhdr, field); TSHandleMLocRelease(mbuf, mhdr, field); } } } namespace { template <bool IsClient> class WrapX509 { public: WrapX509(SSL *ssl) : _ssl(ssl), _x509(_nonNullInvalidValue()) {} X509 * get() { if (_x509 == _nonNullInvalidValue()) { _set(); } return _x509; } ~WrapX509() { if (IsClient && (_x509 != _nonNullInvalidValue()) && (_x509 != nullptr)) { X509_free(_x509); } } private: SSL *_ssl; X509 *_x509; // The address of this object can not be a valid X509 structure address. X509 * _nonNullInvalidValue() const { return reinterpret_cast<X509 *>(const_cast<WrapX509 *>(this)); } void _set() { #ifdef OPENSSL_IS_OPENSSL3 _x509 = (IsClient ? SSL_get1_peer_certificate : SSL_get_certificate)(_ssl); #else _x509 = (IsClient ? SSL_get_peer_certificate : SSL_get_certificate)(_ssl); #endif } }; } // end anonymous namespace // Process SSL header expansions. If this is not an SSL connection, then we need to delete the SSL headers // so that malicious clients cannot inject bogus information. Otherwise, we populate the header with the // expanded value. If the value expands to something empty, we nuke the header. static void SslHdrExpand(SSL *ssl, const SslHdrInstance::expansion_list &expansions, TSMBuffer mbuf, TSMLoc mhdr) { if (ssl == nullptr) { for (const auto &expansion : expansions) { SslHdrRemoveHeader(mbuf, mhdr, expansion.name); } } else { WrapX509<true> clientX509(ssl); WrapX509<false> serverX509(ssl); X509 *x509; BIO *exp = BIO_new(BIO_s_mem()); for (const auto &expansion : expansions) { switch (expansion.scope) { case SSL_HEADERS_SCOPE_CLIENT: x509 = clientX509.get(); if (x509 == nullptr) { SslHdrRemoveHeader(mbuf, mhdr, expansion.name); continue; } break; case SSL_HEADERS_SCOPE_SERVER: x509 = serverX509.get(); if (x509 == nullptr) { continue; } break; default: continue; } SslHdrExpandX509Field(exp, x509, expansion.field); if (BIO_pending(exp)) { SslHdrSetHeader(mbuf, mhdr, expansion.name, exp); } else { SslHdrRemoveHeader(mbuf, mhdr, expansion.name); } } BIO_free(exp); } } static SslHdrInstance * SslHdrParseOptions(int argc, const char **argv) { static const struct option longopt[] = { {const_cast<char *>("attach"), required_argument, nullptr, 'a'}, {nullptr, 0, nullptr, 0 }, }; std::unique_ptr<SslHdrInstance> hdr(new SslHdrInstance()); for (;;) { int opt; opt = getopt_long(argc, const_cast<char *const *>(argv), "", longopt, nullptr); switch (opt) { case 'a': if (strcmp(optarg, "client") == 0) { hdr->attach = SSL_HEADERS_ATTACH_CLIENT; } else if (strcmp(optarg, "server") == 0) { hdr->attach = SSL_HEADERS_ATTACH_SERVER; } else if (strcmp(optarg, "both") == 0) { hdr->attach = SSL_HEADERS_ATTACH_BOTH; } else { TSError("[%s] Invalid attach option '%s'", PLUGIN_NAME, optarg); return nullptr; } break; } if (opt == -1) { break; } } // Pick up the remaining options as SSL header expansions. hdr->expansions.resize(argc - optind); for (int i = optind; i < argc; ++i) { if (!SslHdrParseExpansion(argv[i], hdr->expansions[i - optind])) { // If we fail, the expansion parsing logs the error. return nullptr; } } return hdr.release(); } void TSPluginInit(int argc, const char *argv[]) { TSPluginRegistrationInfo info; SslHdrInstance *hdr; info.plugin_name = (char *)"sslheaders"; info.vendor_name = (char *)"Apache Software Foundation"; info.support_email = (char *)"dev@trafficserver.apache.org"; if (TSPluginRegister(&info) != TS_SUCCESS) { SslHdrError("plugin registration failed"); } hdr = SslHdrParseOptions(argc, static_cast<const char **>(argv)); if (hdr) { switch (hdr->attach) { case SSL_HEADERS_ATTACH_SERVER: TSHttpHookAdd(TS_HTTP_SEND_REQUEST_HDR_HOOK, hdr->cont); break; case SSL_HEADERS_ATTACH_BOTH: /* fallthrough */ case SSL_HEADERS_ATTACH_CLIENT: TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, hdr->cont); TSHttpHookAdd(TS_HTTP_SEND_REQUEST_HDR_HOOK, hdr->cont); break; } } } TSReturnCode TSRemapInit(TSRemapInterface * /* api */, char * /* err */, int /* errsz */) { return TS_SUCCESS; } TSReturnCode TSRemapNewInstance(int argc, char *argv[], void **instance, char * /* err */, int /* errsz */) { SslHdrInstance *hdr; // The first two arguments are the "from" and "to" URL string. We need to // skip them, but we also require that there be an option to masquerade as // argv[0], so we increment the argument indexes by 1 rather than by 2. --argc; ++argv; hdr = SslHdrParseOptions(argc, (const char **)argv); if (hdr) { *instance = hdr; return TS_SUCCESS; } return TS_ERROR; } void TSRemapDeleteInstance(void *instance) { SslHdrInstance *hdr = static_cast<SslHdrInstance *>(instance); delete hdr; } TSRemapStatus TSRemapDoRemap(void *instance, TSHttpTxn txn, TSRemapRequestInfo * /* rri */) { SslHdrInstance *hdr = static_cast<SslHdrInstance *>(instance); switch (hdr->attach) { case SSL_HEADERS_ATTACH_SERVER: TSHttpTxnHookAdd(txn, TS_HTTP_SEND_REQUEST_HDR_HOOK, hdr->cont); break; case SSL_HEADERS_ATTACH_BOTH: /* fallthrough */ case SSL_HEADERS_ATTACH_CLIENT: TSHttpTxnHookAdd(txn, TS_HTTP_READ_REQUEST_HDR_HOOK, hdr->cont); TSHttpTxnHookAdd(txn, TS_HTTP_SEND_REQUEST_HDR_HOOK, hdr->cont); break; } return TSREMAP_NO_REMAP; } SslHdrInstance::SslHdrInstance() : expansions(), cont(TSContCreate(SslHdrExpandRequestHook, nullptr)) { TSContDataSet(cont, this); } SslHdrInstance::~SslHdrInstance() { TSContDestroy(cont); }