blob: 5cf9aa6c99867261574cd72107f0434dee167975 [file]
/** @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 "swoc/swoc_file.h"
#include "api/LifecycleAPIHooks.h"
#include "P_SSLConfig.h"
#include <utility>
#include <openssl/evp.h>
namespace
{
DbgCtl dbg_ctl_ssl_secret{"ssl_secret"};
DbgCtl dbg_ctl_ssl_secret_err{"ssl_secret_err"};
} // end anonymous namespace
static void
get_hash_str(const std::string &input, char hash_str[EVP_MAX_MD_SIZE * 2], unsigned int *hash_len)
{
unsigned char hash[EVP_MAX_MD_SIZE];
EVP_Digest(input.c_str(), input.length(), hash, hash_len, EVP_md5(), nullptr);
for (unsigned int i = 0; i < *hash_len; i++) {
hash_str[i * 2] = hash[i] >> 4;
hash_str[i * 2] += hash_str[i * 2] < 10 ? '0' : 'a' - 10;
hash_str[i * 2 + 1] = hash[i] & 0x0F;
hash_str[i * 2 + 1] += hash_str[i * 2 + 1] < 10 ? '0' : 'a' - 10;
}
*hash_len = *hash_len * 2;
}
// NOTE: The secret_map_mutex should not be held by the caller of this
// function. The implementation of this function may call a plugin's
// TS_EVENT_SSL_SECRET handler which in turn may grab a lock for
// secret_map_mutex via a TSSslSecretSet call. These events will result in a
// deadlock.
void
SSLSecret::loadSecret(const std::string &name1, const std::string &name2, std::string &data1, std::string &data2)
{
// Call the load secret hooks
//
class APIHook *curHook = g_lifecycle_hooks->get(TS_LIFECYCLE_SSL_SECRET_HOOK);
TSSecretID secret_name;
secret_name.cert_name = name1.data();
secret_name.cert_name_len = name1.size();
secret_name.key_name = name2.data();
secret_name.key_name_len = name2.size();
while (curHook) {
curHook->blocking_invoke(TS_EVENT_SSL_SECRET, &secret_name);
curHook = curHook->next();
}
data1 = this->getSecret(name1);
data2 = name2.empty() ? std::string{} : this->getSecret(name2);
if (data1.empty() || (!name2.empty() && data2.empty())) {
// If none of them loaded it, assume it is a file
data1 = loadFile(name1);
setSecret(name1, data1);
if (!name2.empty()) {
data2 = loadFile(name2);
setSecret(name2, data2);
}
}
}
std::string
SSLSecret::loadFile(const std::string &name)
{
Dbg(dbg_ctl_ssl_secret, "SSLSecret::loadFile(%s)", name.c_str());
std::error_code error;
std::string data = swoc::file::load(swoc::file::path(name), error);
if (error) {
Dbg(dbg_ctl_ssl_secret_err, "SSLSecret::loadFile(%s) failed error code=%d message=%s", name.c_str(), error.value(),
error.message().c_str());
// Loading file failed
Dbg(dbg_ctl_ssl_secret, "Loading file: %s failed ", name.c_str());
return std::string{};
}
if (dbg_ctl_ssl_secret.on()) {
char hash_str[EVP_MAX_MD_SIZE * 2];
unsigned int hash_len;
get_hash_str(data, hash_str, &hash_len);
DbgPrint(dbg_ctl_ssl_secret, "Secret hash: %.*s", hash_len, hash_str);
DbgPrint(dbg_ctl_ssl_secret, "Secret data: %.50s", data.c_str());
}
if (SSLConfigParams::load_ssl_file_cb) {
SSLConfigParams::load_ssl_file_cb(name.c_str());
}
return data;
}
void
SSLSecret::setSecret(const std::string &name, std::string_view data)
{
std::scoped_lock lock(secret_map_mutex);
secret_map[name] = std::string{data};
// The full secret data can be sensitive. Print only the first 50 bytes.
Dbg(dbg_ctl_ssl_secret, "Set secret for %s to %.*s", name.c_str(), int(data.size() > 50 ? 50 : data.size()), data.data());
}
void
SSLSecret::invalidateSecret(const std::string &name)
{
std::scoped_lock lock(secret_map_mutex);
if (secret_map.erase(name) > 0) {
Dbg(dbg_ctl_ssl_secret, "Invalidated cached secret for %s", name.c_str());
}
}
std::string
SSLSecret::getSecret(const std::string &name) const
{
std::scoped_lock lock(secret_map_mutex);
auto iter = secret_map.find(name);
if (secret_map.end() == iter) {
Dbg(dbg_ctl_ssl_secret, "Get secret for %s: not found", name.c_str());
return std::string{};
}
if (iter->second.empty()) {
Dbg(dbg_ctl_ssl_secret, "Get secret for %s: empty", name.c_str());
return std::string{};
}
// The full secret data can be sensitive. Print only the first 50 bytes.
if (dbg_ctl_ssl_secret.on()) {
char hash_str[EVP_MAX_MD_SIZE * 2];
unsigned int hash_len;
get_hash_str(iter->second, hash_str, &hash_len);
DbgPrint(dbg_ctl_ssl_secret, "Get secret for %s: hash=%.*s %.50s", name.c_str(), hash_len, hash_str, iter->second.c_str());
}
return iter->second;
}
void
SSLSecret::getOrLoadSecret(const std::string &name1, const std::string &name2, std::string &data1, std::string &data2)
{
Dbg(dbg_ctl_ssl_secret, "lookup up secrets for %s and %s", name1.c_str(), name2.c_str());
{
std::scoped_lock lock(secret_map_mutex);
std::string *const data1ptr = &(secret_map[name1]);
std::string *const data2ptr = [&]() -> std::string * {
if (name2.empty()) {
data2.clear();
return &data2;
}
return &(secret_map[name2]);
}();
data1 = *data1ptr;
data2 = *data2ptr;
}
// If we can't find either secret, load them both again
if (data1.empty() || (!name2.empty() && data2.empty())) {
std::string data1tmp;
std::string data2tmp;
this->loadSecret(name1, name2, data1tmp, data2tmp);
data1 = std::move(data1tmp);
data2 = std::move(data2tmp);
}
}