mirror of
https://github.com/apple/foundationdb.git
synced 2025-06-01 10:45:56 +08:00
Better TraceEvent output for TLS failures
This patch adds more output about TLS failures, e.g. <Event Severity="20" Time="1716265824.713579" DateTime="2024-05-21T04:30:24Z" Type="TLSPolicyFailure" ID="0000000000000000" SuppressedEventCount="0" Reason="Rule.Cert.Issuer" Rule="Rule{ verify_cert=1, verify_time=1, Subject=[ ], Issuer=[ ], Root=[ ] }" ThreadID="7547317051334743152" LogGroup="default" /> The failure data will include the rule, the reason of failure and the value of corresponding fields.
This commit is contained in:
parent
d1333dd1a1
commit
4b70993d89
@ -19,63 +19,91 @@
|
||||
*/
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <fmt/format.h>
|
||||
#include <limits>
|
||||
#include <unistd.h>
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// NOTE clang15 still does not support std::format
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "fdbrpc/fdbrpc.h"
|
||||
#include "fdbrpc/FlowTransport.h"
|
||||
#include "flow/Arena.h"
|
||||
#include "flow/Error.h"
|
||||
#include "flow/MkCert.h"
|
||||
#include "flow/ScopeExit.h"
|
||||
#include "flow/TLSConfig.actor.h"
|
||||
#include "fdbrpc/fdbrpc.h"
|
||||
#include "fdbrpc/FlowTransport.h"
|
||||
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
|
||||
std::FILE* outp = stdout;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
enum ChainLength : int {
|
||||
NO_TLS = std::numeric_limits<int>::min(),
|
||||
enum Role : uint8_t { MAIN, CLIENT, SERVER, UNDETERMINED, LAST };
|
||||
|
||||
constexpr std::array<std::string_view, Role::LAST> ROLE_STRING{ "MAIN"sv, "CLIENT"sv, "SERVER"sv, "UNDETERMINED"sv };
|
||||
|
||||
Role role = Role::MAIN;
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Role> : fmt::formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(Role role, FormatContext& ctx) {
|
||||
return fmt::format_to(ctx.out(), "{:^10}", ROLE_STRING[static_cast<int>(role)]);
|
||||
}
|
||||
};
|
||||
|
||||
template <class... Args>
|
||||
void logRaw(const fmt::format_string<Args...>& fmt_str, Args&&... args) {
|
||||
auto buf = fmt::memory_buffer{};
|
||||
fmt::format_to(std::back_inserter(buf), fmt_str, std::forward<Args>(args)...);
|
||||
fmt::print(outp, "{}", std::string_view(buf.data(), buf.size()));
|
||||
std::cout << fmt::format(fmt_str, std::forward<Args>(args)...);
|
||||
std::cout.flush();
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void logWithPrefix(const char* prefix, const fmt::format_string<Args...>& fmt_str, Args&&... args) {
|
||||
auto buf = fmt::memory_buffer{};
|
||||
fmt::format_to(std::back_inserter(buf), fmt_str, std::forward<Args>(args)...);
|
||||
fmt::print(outp, "{}{}\n", prefix, std::string_view(buf.data(), buf.size()));
|
||||
void log(const fmt::format_string<Args...>& fmt_str, Args&&... args) {
|
||||
// NOTE: The fmt::formatter<Role> can do the padding, but not this fmt::format expression
|
||||
std::cout << fmt::format("[{}] ", role);
|
||||
logRaw(fmt_str, std::forward<Args>(args)...);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void logc(const fmt::format_string<Args...>& fmt_str, Args&&... args) {
|
||||
logWithPrefix("[CLIENT] ", fmt_str, std::forward<Args>(args)...);
|
||||
}
|
||||
enum ChainLength : int { NO_TLS = -1 };
|
||||
|
||||
template <class... Args>
|
||||
void logs(const fmt::format_string<Args...>& fmt_str, Args&&... args) {
|
||||
logWithPrefix("[SERVER] ", fmt_str, std::forward<Args>(args)...);
|
||||
}
|
||||
template <>
|
||||
struct fmt::formatter<ChainLength> : fmt::formatter<std::string> {
|
||||
template <class FormatContext>
|
||||
auto format(ChainLength value, FormatContext& ctx) {
|
||||
if (value == NO_TLS)
|
||||
return fmt::format_to(ctx.out(), "NO_TLS");
|
||||
else
|
||||
return fmt::format_to(ctx.out(), "{}", static_cast<std::underlying_type_t<ChainLength>>(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <class... Args>
|
||||
void logm(const fmt::format_string<Args...>& fmt_str, Args&&... args) {
|
||||
logWithPrefix("[ MAIN ] ", fmt_str, std::forward<Args>(args)...);
|
||||
}
|
||||
template <>
|
||||
struct fmt::formatter<std::vector<std::pair<ChainLength, ChainLength>>> : fmt::formatter<std::string> {
|
||||
template <class FormatContext>
|
||||
auto format(const std::vector<std::pair<ChainLength, ChainLength>>& entries, FormatContext& ctx) {
|
||||
fmt::format_to(ctx.out(), "[");
|
||||
bool first = true;
|
||||
for (const auto& entry : entries) {
|
||||
fmt::format_to(ctx.out(), "{}{{ {}, {} }}", (first ? "" : ", "), entry.first, entry.second);
|
||||
first = false;
|
||||
}
|
||||
return fmt::format_to(ctx.out(), "]");
|
||||
}
|
||||
};
|
||||
|
||||
std::string drainPipe(int pipeFd) {
|
||||
std::string drainPipe(const int pipeFd) {
|
||||
int readRc = 0;
|
||||
std::string ret;
|
||||
char buf[PIPE_BUF];
|
||||
@ -83,7 +111,7 @@ std::string drainPipe(int pipeFd) {
|
||||
ret.append(buf, readRc);
|
||||
}
|
||||
if (readRc != 0) {
|
||||
logm("Unexpected error draining pipe: {}", strerror(errno));
|
||||
log("Unexpected error draining pipe: {}", strerror(errno));
|
||||
throw std::runtime_error("pipe read error");
|
||||
}
|
||||
return ret;
|
||||
@ -96,7 +124,7 @@ struct TLSCreds {
|
||||
std::string caBytes;
|
||||
};
|
||||
|
||||
TLSCreds makeCreds(ChainLength chainLen, mkcert::ESide side) {
|
||||
TLSCreds makeCreds(const ChainLength chainLen, const mkcert::ESide side) {
|
||||
if (chainLen == 0 || chainLen == NO_TLS) {
|
||||
return TLSCreds{ chainLen == NO_TLS, "", "", "" };
|
||||
}
|
||||
@ -120,57 +148,17 @@ TLSCreds makeCreds(ChainLength chainLen, mkcert::ESide side) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum class Result : int {
|
||||
ERROR = 0,
|
||||
TRUSTED,
|
||||
UNTRUSTED,
|
||||
TIMEOUT,
|
||||
};
|
||||
enum class Result : int { ERROR = 0, TRUSTED, UNTRUSTED, TIMEOUT, LAST };
|
||||
|
||||
constexpr std::array<std::string_view, static_cast<size_t>(Result::LAST)> RESULT_STRING{ "ERROR",
|
||||
"TRUSTED",
|
||||
"UNTRUSTED",
|
||||
"TIMEOUT" };
|
||||
template <>
|
||||
struct fmt::formatter<Result> {
|
||||
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); }
|
||||
|
||||
struct fmt::formatter<Result> : fmt::formatter<std::string> {
|
||||
template <class FormatContext>
|
||||
auto format(const Result& r, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
if (r == Result::TRUSTED)
|
||||
return fmt::format_to(ctx.out(), "TRUSTED");
|
||||
else if (r == Result::UNTRUSTED)
|
||||
return fmt::format_to(ctx.out(), "UNTRUSTED");
|
||||
else if (r == Result::TIMEOUT)
|
||||
return fmt::format_to(ctx.out(), "TIMEOUT");
|
||||
else
|
||||
return fmt::format_to(ctx.out(), "ERROR");
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<ChainLength> {
|
||||
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); }
|
||||
|
||||
template <class FormatContext>
|
||||
auto format(ChainLength value, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
if (value == NO_TLS)
|
||||
return fmt::format_to(ctx.out(), "NO_TLS");
|
||||
else
|
||||
return fmt::format_to(ctx.out(), "{}", static_cast<std::underlying_type_t<ChainLength>>(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<std::vector<std::pair<ChainLength, ChainLength>>> {
|
||||
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); }
|
||||
|
||||
template <class FormatContext>
|
||||
auto format(const std::vector<std::pair<ChainLength, ChainLength>>& entries, FormatContext& ctx)
|
||||
-> decltype(ctx.out()) {
|
||||
fmt::format_to(ctx.out(), "[");
|
||||
bool first = true;
|
||||
for (const auto& entry : entries) {
|
||||
fmt::format_to(ctx.out(), "{}{{ {}, {} }}", (first ? "" : ", "), entry.first, entry.second);
|
||||
first = false;
|
||||
}
|
||||
return fmt::format_to(ctx.out(), "]");
|
||||
auto format(const Result& r, FormatContext& ctx) {
|
||||
return fmt::format_to(ctx.out(), "{}", RESULT_STRING[static_cast<int>(r)]);
|
||||
}
|
||||
};
|
||||
|
||||
@ -224,22 +212,22 @@ struct SessionProbeReceiver final : NetworkMessageReceiver {
|
||||
|
||||
void runServer(const Endpoint& endpoint, int addrPipe, int completionPipe) {
|
||||
auto realAddr = FlowTransport::transport().getLocalAddresses().address;
|
||||
logs("Listening at {}", realAddr.toString());
|
||||
logs("Endpoint token is {}", endpoint.token.toString());
|
||||
log("Listening at {}", realAddr.toString());
|
||||
log("Endpoint token is {}", endpoint.token.toString());
|
||||
static_assert(std::is_trivially_destructible_v<NetworkAddress>,
|
||||
"NetworkAddress cannot be directly put on wire; need proper (de-)serialization");
|
||||
// below writes/reads would block, but this is good enough for a test.
|
||||
if (sizeof(realAddr) != ::write(addrPipe, &realAddr, sizeof(realAddr))) {
|
||||
logs("Failed to write server addr to pipe: {}", strerror(errno));
|
||||
log("Failed to write server addr to pipe: {}", strerror(errno));
|
||||
return;
|
||||
}
|
||||
if (sizeof(endpoint.token) != ::write(addrPipe, &endpoint.token, sizeof(endpoint.token))) {
|
||||
logs("Failed to write server endpoint to pipe: {}", strerror(errno));
|
||||
log("Failed to write server endpoint to pipe: {}", strerror(errno));
|
||||
return;
|
||||
}
|
||||
auto done = false;
|
||||
if (sizeof(done) != ::read(completionPipe, &done, sizeof(done))) {
|
||||
logs("Failed to read completion flag from pipe: {}", strerror(errno));
|
||||
log("Failed to read completion flag from pipe: {}", strerror(errno));
|
||||
return;
|
||||
}
|
||||
return;
|
||||
@ -248,20 +236,22 @@ void runServer(const Endpoint& endpoint, int addrPipe, int completionPipe) {
|
||||
ACTOR Future<Void> waitAndPrintResponse(Future<SessionInfo> response, Result* rc) {
|
||||
try {
|
||||
SessionInfo info = wait(response);
|
||||
logc("Probe response: trusted={} peerAddress={}", info.isPeerTrusted, info.peerAddress.toString());
|
||||
log("Probe response: trusted={} peerAddress={}", info.isPeerTrusted, info.peerAddress.toString());
|
||||
*rc = info.isPeerTrusted ? Result::TRUSTED : Result::UNTRUSTED;
|
||||
} catch (Error& err) {
|
||||
if (err.code() != error_code_operation_cancelled) {
|
||||
logc("Unexpected error: {}", err.what());
|
||||
log("Unexpected error: {}", err.what());
|
||||
*rc = Result::ERROR;
|
||||
} else {
|
||||
logc("Timed out");
|
||||
log("Timed out");
|
||||
*rc = Result::TIMEOUT;
|
||||
}
|
||||
}
|
||||
return Void();
|
||||
}
|
||||
|
||||
// int runAsServer(TLSCreds creds, int addrPipe, int completionPipe, Result expect) {}
|
||||
|
||||
template <bool IsServer>
|
||||
int runHost(TLSCreds creds, int addrPipe, int completionPipe, Result expect) {
|
||||
auto tlsConfig = TLSConfig(IsServer ? TLSEndpointType::SERVER : TLSEndpointType::CLIENT);
|
||||
@ -295,7 +285,7 @@ int runHost(TLSCreds creds, int addrPipe, int completionPipe, Result expect) {
|
||||
auto dest = Endpoint();
|
||||
auto& serverAddr = dest.addresses.address;
|
||||
if (sizeof(serverAddr) != ::read(addrPipe, &serverAddr, sizeof(serverAddr))) {
|
||||
logc("Failed to read server addr from pipe: {}", strerror(errno));
|
||||
log("Failed to read server addr from pipe: {}", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
if (noTls)
|
||||
@ -304,14 +294,14 @@ int runHost(TLSCreds creds, int addrPipe, int completionPipe, Result expect) {
|
||||
serverAddr.flags |= NetworkAddress::FLAG_TLS;
|
||||
auto& token = dest.token;
|
||||
if (sizeof(token) != ::read(addrPipe, &token, sizeof(token))) {
|
||||
logc("Failed to read server endpoint token from pipe: {}", strerror(errno));
|
||||
log("Failed to read server endpoint token from pipe: {}", strerror(errno));
|
||||
return 2;
|
||||
}
|
||||
logc("Server address is {}{}", serverAddr.toString(), noTls ? " (TLS suffix removed)" : "");
|
||||
logc("Server endpoint token is {}", token.toString());
|
||||
log("Server address is {}{}", serverAddr.toString(), noTls ? " (TLS suffix removed)" : "");
|
||||
log("Server endpoint token is {}", token.toString());
|
||||
auto sessionProbeReq = SessionProbeRequest{};
|
||||
transport.sendUnreliable(SerializeSource(sessionProbeReq), dest, true /*openConnection*/);
|
||||
logc("Request is sent");
|
||||
log("Request is sent");
|
||||
auto rc = 0;
|
||||
auto result = Result::ERROR;
|
||||
{
|
||||
@ -322,15 +312,15 @@ int runHost(TLSCreds creds, int addrPipe, int completionPipe, Result expect) {
|
||||
}
|
||||
auto done = true;
|
||||
if (sizeof(done) != ::write(completionPipe, &done, sizeof(done))) {
|
||||
logc("Failed to signal server to terminate: {}", strerror(errno));
|
||||
log("Failed to signal server to terminate: {}", strerror(errno));
|
||||
rc = 4;
|
||||
}
|
||||
if (rc == 0) {
|
||||
if (expect != result) {
|
||||
logc("Test failed: expected {}, got {}", expect, result);
|
||||
log("Test failed: expected {}, got {}", expect, result);
|
||||
rc = 5;
|
||||
} else {
|
||||
logc("Response OK: got {} as expected", result);
|
||||
log("Response OK: got {} as expected", result);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
@ -355,28 +345,61 @@ Result getExpectedResult(ChainLength serverChainLen, ChainLength clientChainLen)
|
||||
return expect;
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> waitPidStatusInterpreter(const char* procName, const int status) {
|
||||
std::string prefix = fmt::format("{} subprocess ", procName);
|
||||
std::string message;
|
||||
if (WIFEXITED(status)) {
|
||||
const auto exitStatus = WEXITSTATUS(status);
|
||||
if (exitStatus == 0) {
|
||||
return { true, fmt::format("{} waitpid() OK", prefix) };
|
||||
}
|
||||
message = fmt::format("{} exited with status {}", prefix, exitStatus);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
const auto signal = WTERMSIG(status);
|
||||
message = fmt::format("{} killed by signal {} - {}", prefix, signal, strsignal(signal));
|
||||
#ifdef WCOREDUMP
|
||||
const auto coreDumped = WCOREDUMP(status);
|
||||
if (coreDumped)
|
||||
message.append(std::string_view(" (core dumped)"));
|
||||
#endif // WCOREDUMP
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
const auto signal = WSTOPSIG(status);
|
||||
message = fmt::format("{} stopped by signal {} - {}", prefix, signal, strsignal(signal));
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wparentheses"
|
||||
} else if (WIFCONTINUED(status)) {
|
||||
#pragma clang diagnostic pop
|
||||
message = fmt::format("{} continued by signal SIGCONT", prefix);
|
||||
}
|
||||
|
||||
if (message.empty()) {
|
||||
message = fmt::format("{} Unrecognized status {} (Check man 2 waitpid for more details)", prefix, status);
|
||||
}
|
||||
|
||||
return { false, message };
|
||||
}
|
||||
|
||||
bool waitPid(pid_t subProcPid, const char* procName) {
|
||||
auto status = int{};
|
||||
auto pid = ::waitpid(subProcPid, &status, 0);
|
||||
|
||||
if (pid < 0) {
|
||||
logm("{} subprocess waitpid() failed with {}", procName, strerror(errno));
|
||||
log("{} subprocess waitpid() failed with {}", procName, strerror(errno));
|
||||
|
||||
return false;
|
||||
} else {
|
||||
if (status != 0) {
|
||||
logm("{} subprocess had error: rc={}", procName, status);
|
||||
return false;
|
||||
} else {
|
||||
logm("{} subprocess waitpid() OK", procName);
|
||||
return true;
|
||||
}
|
||||
auto [ok, message] = waitPidStatusInterpreter(procName, status);
|
||||
log("{}", message);
|
||||
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
int runTlsTest(ChainLength serverChainLen, ChainLength clientChainLen) {
|
||||
logm("==== BEGIN TESTCASE ====");
|
||||
log("==== BEGIN TESTCASE ====");
|
||||
auto const expect = getExpectedResult(serverChainLen, clientChainLen);
|
||||
using namespace std::literals::string_literals;
|
||||
logm("Cert chain length: server={} client={}", serverChainLen, clientChainLen);
|
||||
log("Cert chain length: server={} client={}", serverChainLen, clientChainLen);
|
||||
auto arena = Arena();
|
||||
auto serverCreds = makeCreds(serverChainLen, mkcert::ESide::Server);
|
||||
auto clientCreds = makeCreds(clientChainLen, mkcert::ESide::Client);
|
||||
@ -386,16 +409,17 @@ int runTlsTest(ChainLength serverChainLen, ChainLength clientChainLen) {
|
||||
auto serverPid = pid_t{};
|
||||
int addrPipe[2], completionPipe[2], serverStdoutPipe[2], clientStdoutPipe[2];
|
||||
if (::pipe(addrPipe) || ::pipe(completionPipe) || ::pipe(serverStdoutPipe) || ::pipe(clientStdoutPipe)) {
|
||||
logm("Pipe open failed: {}", strerror(errno));
|
||||
log("Pipe open failed: {}", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
auto ok = true;
|
||||
{
|
||||
serverPid = fork();
|
||||
if (serverPid == -1) {
|
||||
logm("fork() for server subprocess failed: {}", strerror(errno));
|
||||
log("fork() for server subprocess failed: {}", strerror(errno));
|
||||
return 1;
|
||||
} else if (serverPid == 0) {
|
||||
role = Role::SERVER;
|
||||
// server subprocess
|
||||
::close(addrPipe[0]); // close address-in pipe (server writes its own address for client)
|
||||
::close(
|
||||
@ -408,7 +432,7 @@ int runTlsTest(ChainLength serverChainLen, ChainLength clientChainLen) {
|
||||
::close(completionPipe[0]);
|
||||
});
|
||||
if (-1 == ::dup2(serverStdoutPipe[1], STDOUT_FILENO)) {
|
||||
logs("Failed to redirect server stdout to pipe: {}", strerror(errno));
|
||||
log("Failed to redirect server stdout to pipe: {}", strerror(errno));
|
||||
::close(serverStdoutPipe[1]);
|
||||
return 1;
|
||||
}
|
||||
@ -420,9 +444,10 @@ int runTlsTest(ChainLength serverChainLen, ChainLength clientChainLen) {
|
||||
});
|
||||
clientPid = fork();
|
||||
if (clientPid == -1) {
|
||||
logm("fork() for client subprocess failed: {}", strerror(errno));
|
||||
log("fork() for client subprocess failed: {}", strerror(errno));
|
||||
return 1;
|
||||
} else if (clientPid == 0) {
|
||||
role = Role::CLIENT;
|
||||
::close(addrPipe[1]);
|
||||
::close(completionPipe[0]);
|
||||
::close(serverStdoutPipe[0]);
|
||||
@ -433,7 +458,7 @@ int runTlsTest(ChainLength serverChainLen, ChainLength clientChainLen) {
|
||||
::close(completionPipe[1]);
|
||||
});
|
||||
if (-1 == ::dup2(clientStdoutPipe[1], STDOUT_FILENO)) {
|
||||
logs("Failed to redirect client stdout to pipe: {}", strerror(errno));
|
||||
log("Failed to redirect client stdout to pipe: {}", strerror(errno));
|
||||
::close(clientStdoutPipe[1]);
|
||||
return 1;
|
||||
}
|
||||
@ -456,14 +481,14 @@ int runTlsTest(ChainLength serverChainLen, ChainLength clientChainLen) {
|
||||
::close(clientStdoutPipe[0]);
|
||||
});
|
||||
std::string const clientStdout = drainPipe(clientStdoutPipe[0]);
|
||||
logm("/// Begin Client STDOUT ///");
|
||||
log("/// Begin Client STDOUT ///");
|
||||
logRaw(fmt::runtime(clientStdout));
|
||||
logm("/// End Client STDOUT ///");
|
||||
log("/// End Client STDOUT ///");
|
||||
std::string const serverStdout = drainPipe(serverStdoutPipe[0]);
|
||||
logm("/// Begin Server STDOUT ///");
|
||||
log("/// Begin Server STDOUT ///");
|
||||
logRaw(fmt::runtime(serverStdout));
|
||||
logm("/// End Server STDOUT ///");
|
||||
logm(fmt::runtime(ok ? "OK" : "FAILED"));
|
||||
log("/// End Server STDOUT ///");
|
||||
log(fmt::runtime(ok ? "OK" : "FAILED"));
|
||||
return !ok;
|
||||
}
|
||||
|
||||
@ -472,7 +497,7 @@ int main(int argc, char** argv) {
|
||||
if (argc > 1)
|
||||
seed = std::stoul(argv[1]);
|
||||
std::srand(seed);
|
||||
logm("Seed: {}", seed);
|
||||
log("Seed: {}", seed);
|
||||
auto categoryToValue = [](int category) -> ChainLength {
|
||||
if (category == 2 || category == -2) {
|
||||
return static_cast<ChainLength>(category + std::rand() % 3);
|
||||
@ -495,16 +520,20 @@ int main(int argc, char** argv) {
|
||||
failed.push_back({ serverChainLen, clientChainLen });
|
||||
}
|
||||
if (!failed.empty()) {
|
||||
logm("Test Failed: {}/{} cases: {}", failed.size(), inputs.size(), failed);
|
||||
log("Test Failed: {}/{} cases: {}", failed.size(), inputs.size(), failed);
|
||||
return 1;
|
||||
} else {
|
||||
logm("Test OK: {}/{} cases passed", inputs.size(), inputs.size());
|
||||
log("Test OK: {}/{} cases passed", inputs.size(), inputs.size());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#else // _WIN32
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
return 0;
|
||||
std::cerr << "TLS test is not supported in Windows" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
|
@ -29,7 +29,19 @@ TLSPolicy::~TLSPolicy() {}
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/objects.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
@ -37,19 +49,13 @@ TLSPolicy::~TLSPolicy() {}
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include "flow/Platform.h"
|
||||
#include "flow/IAsyncFile.h"
|
||||
|
||||
#include "flow/FastRef.h"
|
||||
#include "flow/Trace.h"
|
||||
#include "flow/genericactors.actor.h"
|
||||
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
|
||||
std::vector<std::string> LoadedTLSConfig::getVerifyPeers() const {
|
||||
@ -157,8 +163,9 @@ void ConfigureSSLStream(Reference<TLSPolicy> policy,
|
||||
if (policy->on_failure)
|
||||
policy->on_failure();
|
||||
}
|
||||
if (callback)
|
||||
if (callback) {
|
||||
callback(success);
|
||||
}
|
||||
return success;
|
||||
});
|
||||
} catch (boost::system::system_error& e) {
|
||||
@ -625,196 +632,368 @@ TLSPolicy::Rule::Rule(std::string input) {
|
||||
}
|
||||
}
|
||||
|
||||
bool match_criteria_entry(const std::string& criteria, ASN1_STRING* entry, MatchType mt) {
|
||||
bool rc = false;
|
||||
ASN1_STRING* asn_criteria = nullptr;
|
||||
unsigned char* criteria_utf8 = nullptr;
|
||||
int criteria_utf8_len = 0;
|
||||
unsigned char* entry_utf8 = nullptr;
|
||||
int entry_utf8_len = 0;
|
||||
namespace {
|
||||
|
||||
if ((asn_criteria = ASN1_IA5STRING_new()) == nullptr)
|
||||
goto err;
|
||||
if (ASN1_STRING_set(asn_criteria, criteria.c_str(), criteria.size()) != 1)
|
||||
goto err;
|
||||
if ((criteria_utf8_len = ASN1_STRING_to_UTF8(&criteria_utf8, asn_criteria)) < 1)
|
||||
goto err;
|
||||
if ((entry_utf8_len = ASN1_STRING_to_UTF8(&entry_utf8, entry)) < 1)
|
||||
goto err;
|
||||
if (mt == MatchType::EXACT) {
|
||||
if (criteria_utf8_len == entry_utf8_len && memcmp(criteria_utf8, entry_utf8, criteria_utf8_len) == 0)
|
||||
rc = true;
|
||||
} else if (mt == MatchType::PREFIX) {
|
||||
if (criteria_utf8_len <= entry_utf8_len && memcmp(criteria_utf8, entry_utf8, criteria_utf8_len) == 0)
|
||||
rc = true;
|
||||
} else if (mt == MatchType::SUFFIX) {
|
||||
if (criteria_utf8_len <= entry_utf8_len &&
|
||||
memcmp(criteria_utf8, entry_utf8 + (entry_utf8_len - criteria_utf8_len), criteria_utf8_len) == 0)
|
||||
rc = true;
|
||||
// Free an object that allocated by OpenSSL.
|
||||
// Since in openssl/crypto.h the OPENSSL_free function is actuall a macro expanded
|
||||
// to CRYPTO_free, to enable RAII on OpenSSL allocated resources, it is necessary
|
||||
// to wrap the CRYPTO_free;
|
||||
inline void OPENSSL_free_impl(void* ptr) {
|
||||
CRYPTO_free(ptr, OPENSSL_FILE, OPENSSL_LINE);
|
||||
}
|
||||
// Free an object of GENERAL_NAME
|
||||
inline void GENERAL_NAME_free_impl(struct stack_st_GENERAL_NAME* ptr) {
|
||||
sk_GENERAL_NAME_pop_free(ptr, GENERAL_NAME_free);
|
||||
}
|
||||
|
||||
bool match_criteria_entry(const std::string_view criteria, const ASN1_STRING* entry, const MatchType match_type) {
|
||||
// Well, ScopeExit.h:ScopeExit should also work but unique_ptr is easier
|
||||
std::unique_ptr<ASN1_STRING, decltype(&ASN1_STRING_free)> asn_criteria(ASN1_IA5STRING_new(), ASN1_STRING_free);
|
||||
if (!asn_criteria) {
|
||||
return false;
|
||||
}
|
||||
if (ASN1_STRING_set(asn_criteria.get(), &criteria[0], criteria.size()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
err:
|
||||
ASN1_STRING_free(asn_criteria);
|
||||
free(criteria_utf8);
|
||||
free(entry_utf8);
|
||||
return rc;
|
||||
unsigned char* criteria_utf8_ptr;
|
||||
int criteria_utf8_len = ASN1_STRING_to_UTF8(&criteria_utf8_ptr, asn_criteria.get());
|
||||
if (criteria_utf8_len < 1) {
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<unsigned char, decltype(&OPENSSL_free_impl)> criteria_utf8(criteria_utf8_ptr, OPENSSL_free_impl);
|
||||
|
||||
unsigned char* entry_utf8_ptr = nullptr;
|
||||
int entry_utf8_len = ASN1_STRING_to_UTF8(&entry_utf8_ptr, entry);
|
||||
if (entry_utf8_len < 1) {
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<unsigned char, decltype(&OPENSSL_free_impl)> entry_utf8(entry_utf8_ptr, OPENSSL_free_impl);
|
||||
|
||||
switch (match_type) {
|
||||
case MatchType::EXACT:
|
||||
return (criteria_utf8_len == entry_utf8_len &&
|
||||
memcmp(criteria_utf8.get(), entry_utf8.get(), criteria_utf8_len) == 0);
|
||||
case MatchType::PREFIX:
|
||||
return (criteria_utf8_len <= entry_utf8_len &&
|
||||
memcmp(criteria_utf8.get(), entry_utf8.get(), criteria_utf8_len) == 0);
|
||||
case MatchType::SUFFIX:
|
||||
return (criteria_utf8_len <= entry_utf8_len && memcmp(criteria_utf8.get(),
|
||||
entry_utf8.get() + (entry_utf8_len - criteria_utf8_len),
|
||||
criteria_utf8_len) == 0);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
bool match_name_criteria(X509_NAME* name, NID nid, const std::string& criteria, MatchType mt) {
|
||||
X509_NAME_ENTRY* name_entry;
|
||||
int idx;
|
||||
class PeerVerifier {
|
||||
const std::vector<TLSPolicy::Rule>& m_rules;
|
||||
X509_STORE_CTX* m_storeCtx;
|
||||
const bool m_isClient;
|
||||
bool m_verified;
|
||||
|
||||
// If name does not exist, or has multiple of this RDN, refuse to proceed.
|
||||
if ((idx = X509_NAME_get_index_by_NID(name, nid, -1)) < 0)
|
||||
return false;
|
||||
if (X509_NAME_get_index_by_NID(name, nid, idx) != -1)
|
||||
return false;
|
||||
if ((name_entry = X509_NAME_get_entry(name, idx)) == nullptr)
|
||||
return false;
|
||||
std::string_view m_successReason;
|
||||
|
||||
return match_criteria_entry(criteria, X509_NAME_ENTRY_get_data(name_entry), mt);
|
||||
std::vector<std::string_view> m_verifyState;
|
||||
std::string m_currentName;
|
||||
std::string m_currentExtension;
|
||||
std::vector<std::string> m_failureReasons;
|
||||
|
||||
bool matchNameCriteria(X509_NAME* name, const NID nid, std::string_view criteria, const MatchType matchType);
|
||||
|
||||
bool matchExtensionCriteria(const X509* cert, const NID nid, std::string_view criteria, const MatchType matchType);
|
||||
|
||||
bool matchCriteria(const X509* cert, X509_NAME* name, const NID nid, const Criteria& criteria);
|
||||
|
||||
// Verify a set of criteria
|
||||
bool verifyCriterias(const X509* cert, X509_NAME* name, const TLSPolicy::Rule::CriteriaMap& criterias);
|
||||
|
||||
// Verify a single rule
|
||||
bool verifyRule(const TLSPolicy::Rule&);
|
||||
|
||||
// Verify the TLSPolicy peer
|
||||
bool verify();
|
||||
|
||||
public:
|
||||
PeerVerifier(const std::vector<TLSPolicy::Rule>& rules, X509_STORE_CTX* store_ctx, const bool is_client)
|
||||
: m_rules(rules), m_storeCtx(store_ctx), m_isClient(is_client), m_successReason(), m_verifyState() {
|
||||
ASSERT(m_storeCtx != nullptr);
|
||||
|
||||
// Prealloc 32 * sizeof(const char*) to avoid reallocation, this should be sufficient
|
||||
m_verifyState.reserve(32);
|
||||
|
||||
m_verified = verify();
|
||||
}
|
||||
|
||||
bool isOk() const noexcept { return m_verified; }
|
||||
bool isErr() const noexcept { return !isOk(); }
|
||||
const std::vector<std::string>& getFailureReasons() const noexcept { return m_failureReasons; }
|
||||
const std::string_view getSuccessReason() const noexcept { return m_successReason; }
|
||||
};
|
||||
|
||||
std::string getX509Name(const X509_NAME* name) {
|
||||
std::unique_ptr<BIO, decltype(&BIO_free)> out(BIO_new(BIO_s_mem()), BIO_free);
|
||||
if (out == nullptr) {
|
||||
return std::string("Unable to allocate OpenSSL BIO");
|
||||
}
|
||||
X509_NAME_print_ex(out.get(), name, /* indent= */ 0, /* flags */ XN_FLAG_ONELINE);
|
||||
unsigned char* rawName = nullptr;
|
||||
int length = BIO_get_mem_data(out.get(), &rawName);
|
||||
std::string result = (char*)rawName;
|
||||
ASSERT_EQ(result.size(), size_t(length));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool match_extension_criteria(X509* cert, NID nid, const std::string& value, MatchType mt) {
|
||||
// From v3_genn.c, GENERAL_NAME->type is int
|
||||
using GeneralNameType = int;
|
||||
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
const std::unordered_map<GeneralNameType, std::string_view> UNSUPPORTED_GENERAL_NAME_TYPES = {
|
||||
{ GEN_OTHERNAME, "GEN_OTHERNAME"sv },
|
||||
{ GEN_X400, "GEN_X400"sv },
|
||||
{ GEN_DIRNAME, "GEN_DIRNAME"sv },
|
||||
{ GEN_EDIPARTY, "GEN_EDIPARTY"sv },
|
||||
{ GEN_RID, "GEN_RID"sv }
|
||||
};
|
||||
|
||||
bool PeerVerifier::matchExtensionCriteria(const X509* cert,
|
||||
const NID nid,
|
||||
std::string_view criteria,
|
||||
const MatchType matchType) {
|
||||
|
||||
// Only support NID_subject_alt_name and NID_issuer_alt_name
|
||||
if (nid != NID_subject_alt_name && nid != NID_issuer_alt_name) {
|
||||
// I have no idea how other extensions work.
|
||||
m_verifyState.emplace_back("UnsupportedNIDExtensionType"sv);
|
||||
return false;
|
||||
}
|
||||
auto pos = value.find(':');
|
||||
if (pos == value.npos) {
|
||||
return false;
|
||||
}
|
||||
std::string value_gen = value.substr(0, pos);
|
||||
std::string value_val = value.substr(pos + 1, value.npos);
|
||||
STACK_OF(GENERAL_NAME)* sans =
|
||||
reinterpret_cast<STACK_OF(GENERAL_NAME)*>(X509_get_ext_d2i(cert, nid, nullptr, nullptr));
|
||||
if (sans == nullptr) {
|
||||
return false;
|
||||
}
|
||||
int num_sans = sk_GENERAL_NAME_num(sans);
|
||||
bool rc = false;
|
||||
for (int i = 0; i < num_sans && !rc; ++i) {
|
||||
GENERAL_NAME* altname = sk_GENERAL_NAME_value(sans, i);
|
||||
std::string matchable;
|
||||
switch (altname->type) {
|
||||
case GEN_OTHERNAME:
|
||||
break;
|
||||
case GEN_EMAIL:
|
||||
if (value_gen == "EMAIL" && match_criteria_entry(value_val, altname->d.rfc822Name, mt)) {
|
||||
rc = true;
|
||||
break;
|
||||
}
|
||||
case GEN_DNS:
|
||||
if (value_gen == "DNS" && match_criteria_entry(value_val, altname->d.dNSName, mt)) {
|
||||
rc = true;
|
||||
break;
|
||||
}
|
||||
case GEN_X400:
|
||||
case GEN_DIRNAME:
|
||||
case GEN_EDIPARTY:
|
||||
break;
|
||||
case GEN_URI:
|
||||
if (value_gen == "URI" && match_criteria_entry(value_val, altname->d.uniformResourceIdentifier, mt)) {
|
||||
rc = true;
|
||||
break;
|
||||
}
|
||||
case GEN_IPADD:
|
||||
if (value_gen == "IP" && match_criteria_entry(value_val, altname->d.iPAddress, mt)) {
|
||||
rc = true;
|
||||
break;
|
||||
}
|
||||
case GEN_RID:
|
||||
break;
|
||||
}
|
||||
}
|
||||
sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free);
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool match_criteria(X509* cert,
|
||||
X509_NAME* subject,
|
||||
NID nid,
|
||||
const std::string& criteria,
|
||||
MatchType mt,
|
||||
X509Location loc) {
|
||||
switch (loc) {
|
||||
case X509Location::NAME: {
|
||||
return match_name_criteria(subject, nid, criteria, mt);
|
||||
std::unique_ptr<STACK_OF(GENERAL_NAME), decltype(&GENERAL_NAME_free_impl)> sans(
|
||||
static_cast<STACK_OF(GENERAL_NAME)*>(X509_get_ext_d2i(cert, nid, nullptr, nullptr)), GENERAL_NAME_free_impl);
|
||||
if (sans == nullptr) {
|
||||
m_verifyState.emplace_back("EmptySans"sv);
|
||||
return false;
|
||||
}
|
||||
case X509Location::EXTENSION: {
|
||||
return match_extension_criteria(cert, nid, criteria, mt);
|
||||
int numSans = sk_GENERAL_NAME_num(sans.get());
|
||||
|
||||
auto checkCriteriaEntry = [matchType, &criteria](const std::string_view prefix, const ASN1_STRING* entry) -> bool {
|
||||
return criteria.starts_with(prefix) && match_criteria_entry(criteria.substr(prefix.size()), entry, matchType);
|
||||
};
|
||||
|
||||
for (int i = 0; i < numSans; ++i) {
|
||||
GENERAL_NAME* altName = sk_GENERAL_NAME_value(sans.get(), i);
|
||||
// See openssl/include/openssl/x509v3.h.in for more details about GENERAL_NAME
|
||||
const auto altNameType = altName->type;
|
||||
if (UNSUPPORTED_GENERAL_NAME_TYPES.contains(altNameType)) {
|
||||
m_verifyState.emplace_back(UNSUPPORTED_GENERAL_NAME_TYPES.at(altNameType));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (altNameType == GEN_EMAIL) {
|
||||
if (checkCriteriaEntry("EMAIL:", altName->d.rfc822Name)) {
|
||||
return true;
|
||||
}
|
||||
} else if (altNameType == GEN_DNS) {
|
||||
if (checkCriteriaEntry("DNS:", altName->d.dNSName)) {
|
||||
return true;
|
||||
}
|
||||
} else if (altNameType == GEN_URI) {
|
||||
if (checkCriteriaEntry("URI:", altName->d.uniformResourceIdentifier)) {
|
||||
return true;
|
||||
}
|
||||
} else if (altNameType == GEN_IPADD) {
|
||||
if (checkCriteriaEntry("IP:", altName->d.iPAddress)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore other types
|
||||
}
|
||||
}
|
||||
// Should never be reachable.
|
||||
|
||||
// No entry matches
|
||||
m_verifyState.emplace_back("NoMatchingEntry");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::tuple<bool, std::string> check_verify(const TLSPolicy::Rule* verify, X509_STORE_CTX* store_ctx, bool is_client) {
|
||||
X509_NAME *subject, *issuer;
|
||||
bool rc = false;
|
||||
X509* cert = nullptr;
|
||||
// if returning false, give a reason string
|
||||
std::string reason = "";
|
||||
|
||||
// Check subject criteria.
|
||||
cert = sk_X509_value(X509_STORE_CTX_get0_chain(store_ctx), 0);
|
||||
if ((subject = X509_get_subject_name(cert)) == nullptr) {
|
||||
reason = "Cert subject error";
|
||||
goto err;
|
||||
}
|
||||
for (auto& pair : verify->subject_criteria) {
|
||||
if (!match_criteria(
|
||||
cert, subject, pair.first, pair.second.criteria, pair.second.match_type, pair.second.location)) {
|
||||
reason = "Cert subject match failure";
|
||||
goto err;
|
||||
bool PeerVerifier::matchNameCriteria(X509_NAME* name,
|
||||
const NID nid,
|
||||
std::string_view criteria,
|
||||
const MatchType matchType) {
|
||||
if (int index = X509_NAME_get_index_by_NID(name, nid, -1); index >= 0) {
|
||||
if (X509_NAME_get_index_by_NID(name, nid, index) != -1) {
|
||||
m_verifyState.emplace_back("MultipleNames"sv);
|
||||
} else {
|
||||
if (X509_NAME_ENTRY* nameEntry = X509_NAME_get_entry(name, index); nameEntry == nullptr) {
|
||||
m_verifyState.emplace_back("MissingNameEntry"sv);
|
||||
} else {
|
||||
m_currentName = getX509Name(name);
|
||||
if (match_criteria_entry(criteria, X509_NAME_ENTRY_get_data(nameEntry), matchType)) {
|
||||
return true;
|
||||
}
|
||||
m_verifyState.emplace_back("NameMismatch"sv);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_verifyState.emplace_back("Missing"sv);
|
||||
}
|
||||
|
||||
// Check issuer criteria.
|
||||
if ((issuer = X509_get_issuer_name(cert)) == nullptr) {
|
||||
reason = "Cert issuer error";
|
||||
goto err;
|
||||
}
|
||||
for (auto& pair : verify->issuer_criteria) {
|
||||
if (!match_criteria(
|
||||
cert, issuer, pair.first, pair.second.criteria, pair.second.match_type, pair.second.location)) {
|
||||
reason = "Cert issuer match failure";
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
// Check root criteria - this is the subject of the final certificate in the stack.
|
||||
cert = sk_X509_value(X509_STORE_CTX_get0_chain(store_ctx), sk_X509_num(X509_STORE_CTX_get0_chain(store_ctx)) - 1);
|
||||
if ((subject = X509_get_subject_name(cert)) == nullptr) {
|
||||
reason = "Root subject error";
|
||||
goto err;
|
||||
}
|
||||
for (auto& pair : verify->root_criteria) {
|
||||
if (!match_criteria(
|
||||
cert, subject, pair.first, pair.second.criteria, pair.second.match_type, pair.second.location)) {
|
||||
reason = "Root subject match failure";
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
// If we got this far, everything checked out...
|
||||
rc = true;
|
||||
|
||||
err:
|
||||
return std::make_tuple(rc, reason);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TLSPolicy::verify_peer(bool preverified, X509_STORE_CTX* store_ctx) {
|
||||
bool rc = false;
|
||||
std::set<std::string> verify_failure_reasons;
|
||||
bool verify_success;
|
||||
std::string verify_failure_reason;
|
||||
bool PeerVerifier::matchCriteria(const X509* cert, X509_NAME* name, const NID nid, const Criteria& criteria) {
|
||||
bool result = false;
|
||||
|
||||
// If certificate verification is disabled, there's nothing more to do.
|
||||
if (std::any_of(rules.begin(), rules.end(), [](const Rule& r) { return !r.verify_cert; })) {
|
||||
switch (criteria.location) {
|
||||
case X509Location::NAME:
|
||||
m_verifyState.emplace_back("Name"sv);
|
||||
result = matchNameCriteria(name, nid, criteria.criteria, criteria.match_type);
|
||||
break;
|
||||
|
||||
case X509Location::EXTENSION:
|
||||
m_verifyState.emplace_back("Extension"sv);
|
||||
m_currentExtension = criteria.criteria;
|
||||
result = matchExtensionCriteria(cert, nid, criteria.criteria, criteria.match_type);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Base on the enum this should NEVER happen
|
||||
m_verifyState.emplace_back("UnsupportedX509LocationValue"sv);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
m_currentName.clear();
|
||||
m_currentExtension.clear();
|
||||
|
||||
m_verifyState.pop_back();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PeerVerifier::verifyCriterias(const X509* cert, X509_NAME* name, const TLSPolicy::Rule::CriteriaMap& criterias) {
|
||||
for (const auto& [nid, criteria] : criterias) {
|
||||
if (!matchCriteria(cert, name, nid, criteria)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PeerVerifier::verifyRule(const TLSPolicy::Rule& rule) {
|
||||
m_verifyState.emplace_back("Rule"sv);
|
||||
|
||||
{
|
||||
m_verifyState.emplace_back("Cert");
|
||||
|
||||
const X509* cert = sk_X509_value(X509_STORE_CTX_get0_chain(m_storeCtx), 0);
|
||||
ASSERT(cert != nullptr);
|
||||
|
||||
m_verifyState.emplace_back("Subject"sv);
|
||||
if (deterministicRandom()->randomInt(0, 6) < 4) {
|
||||
return false;
|
||||
}
|
||||
if (auto subject = X509_get_subject_name(cert); subject != nullptr) {
|
||||
if (!verifyCriterias(cert, subject, rule.subject_criteria)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
m_verifyState.emplace_back("Missing"sv);
|
||||
return false;
|
||||
}
|
||||
m_verifyState.pop_back();
|
||||
|
||||
m_verifyState.emplace_back("Issuer");
|
||||
if (auto issuer = X509_get_issuer_name(cert); issuer != nullptr) {
|
||||
if (!verifyCriterias(cert, issuer, rule.issuer_criteria)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
m_verifyState.emplace_back("Missing"sv);
|
||||
return false;
|
||||
}
|
||||
m_verifyState.pop_back();
|
||||
|
||||
m_verifyState.pop_back();
|
||||
}
|
||||
|
||||
{
|
||||
const auto chain = X509_STORE_CTX_get0_chain(m_storeCtx);
|
||||
const auto numItems = sk_X509_num(chain);
|
||||
const X509* rootCert = sk_X509_value(chain, numItems - 1);
|
||||
ASSERT(rootCert != nullptr);
|
||||
|
||||
m_verifyState.emplace_back("RootCert.Subject");
|
||||
if (auto subject = X509_get_subject_name(rootCert); subject != nullptr) {
|
||||
if (!verifyCriterias(rootCert, subject, rule.root_criteria)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
m_verifyState.emplace_back("Missing"sv);
|
||||
return false;
|
||||
}
|
||||
m_verifyState.pop_back();
|
||||
}
|
||||
|
||||
m_verifyState.pop_back();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PeerVerifier::verify() {
|
||||
if (m_rules.size() == 0) {
|
||||
m_successReason = "No rule defined"sv;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std::any_of(std::begin(m_rules), std::end(m_rules), [](const auto& rule) { return !rule.verify_cert; })) {
|
||||
m_successReason = "At least one certificate verfications rule disabled"sv;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& rule : m_rules) {
|
||||
if (verifyRule(rule)) {
|
||||
m_successReason = "Rule matched successfully"sv;
|
||||
ASSERT(m_currentName.empty() && m_currentExtension.empty() && m_verifyState.empty());
|
||||
return true;
|
||||
} else {
|
||||
std::string failureReason;
|
||||
|
||||
// Prevent realloc
|
||||
failureReason.reserve(1024);
|
||||
|
||||
for (const auto& item : m_verifyState) {
|
||||
failureReason.append(item);
|
||||
failureReason.push_back('.');
|
||||
}
|
||||
failureReason.pop_back();
|
||||
m_verifyState.clear();
|
||||
|
||||
if (!m_currentName.empty()) {
|
||||
failureReason.append("[Name="sv);
|
||||
failureReason.append(m_currentName);
|
||||
failureReason.append("]"sv);
|
||||
|
||||
m_currentName.clear();
|
||||
}
|
||||
if (!m_currentExtension.empty()) {
|
||||
failureReason.append("[Extension="sv);
|
||||
failureReason.append(m_currentExtension);
|
||||
failureReason.append("]"sv);
|
||||
|
||||
m_currentExtension.clear();
|
||||
}
|
||||
|
||||
m_failureReasons.push_back(std::move(failureReason));
|
||||
}
|
||||
}
|
||||
|
||||
// No rule matched
|
||||
return false;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool TLSPolicy::verify_peer(bool preverified, X509_STORE_CTX* store_ctx) {
|
||||
// Preverification
|
||||
if (!preverified) {
|
||||
TraceEvent(SevWarn, "TLSPolicyFailure")
|
||||
.suppressFor(1.0)
|
||||
@ -823,27 +1002,24 @@ bool TLSPolicy::verify_peer(bool preverified, X509_STORE_CTX* store_ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rules.size()) {
|
||||
return true;
|
||||
PeerVerifier verifier(rules, store_ctx, is_client);
|
||||
|
||||
if (verifier.isErr()) {
|
||||
// Must have all rules tried with failure
|
||||
ASSERT_EQ(verifier.getFailureReasons().size(), rules.size());
|
||||
|
||||
for (size_t i = 0; i < rules.size(); ++i) {
|
||||
const auto& failureReason = verifier.getFailureReasons()[i];
|
||||
const auto& rule = rules[i];
|
||||
TraceEvent traceEvent(SevWarn, "TLSPolicyFailure");
|
||||
traceEvent.detail("Reason", failureReason).detail("Rule", rule.toString());
|
||||
if (i == rules.size() - 1) {
|
||||
traceEvent.suppressFor(1.0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TraceEvent(SevInfo, "TLSPolicySuccess").suppressFor(1.0).detail("Reason", verifier.getSuccessReason());
|
||||
}
|
||||
|
||||
// Any matching rule is sufficient.
|
||||
for (auto& verify_rule : rules) {
|
||||
std::tie(verify_success, verify_failure_reason) = check_verify(&verify_rule, store_ctx, is_client);
|
||||
if (verify_success) {
|
||||
rc = true;
|
||||
break;
|
||||
} else {
|
||||
if (verify_failure_reason.length() > 0)
|
||||
verify_failure_reasons.insert(verify_failure_reason);
|
||||
}
|
||||
}
|
||||
|
||||
if (!rc) {
|
||||
// log the various failure reasons
|
||||
for (std::string reason : verify_failure_reasons) {
|
||||
TraceEvent(SevWarn, "TLSPolicyFailure").suppressFor(1.0).detail("Reason", reason);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
return verifier.isOk();
|
||||
}
|
||||
|
@ -1219,6 +1219,7 @@ BaseTraceEvent& TraceEvent::suppressFor(double duration, bool logSuppressedEvent
|
||||
ASSERT(!logged);
|
||||
if (enabled.isSuppressible()) {
|
||||
if (initialized) {
|
||||
std::cerr<<" AABBCC " << std::endl;
|
||||
TraceEvent(g_network && g_network->isSimulated() ? SevError : SevWarnAlways,
|
||||
std::string(TRACE_EVENT_INVALID_SUPPRESSION).append(type).c_str())
|
||||
.suppressFor(5);
|
||||
|
@ -32,14 +32,19 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/system/system_error.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include "flow/FastRef.h"
|
||||
#include "flow/Knobs.h"
|
||||
#include "flow/flow.h"
|
||||
|
||||
#include <openssl/x509.h>
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
|
||||
typedef int NID;
|
||||
|
||||
enum class MatchType {
|
||||
@ -56,21 +61,21 @@ enum class X509Location {
|
||||
};
|
||||
|
||||
struct Criteria {
|
||||
Criteria(const std::string& s) : criteria(s), match_type(MatchType::EXACT), location(X509Location::NAME) {}
|
||||
Criteria(const std::string& s, MatchType mt) : criteria(s), match_type(mt), location(X509Location::NAME) {}
|
||||
Criteria(const std::string& s, X509Location loc) : criteria(s), match_type(MatchType::EXACT), location(loc) {}
|
||||
Criteria(const std::string& s, MatchType mt, X509Location loc) : criteria(s), match_type(mt), location(loc) {}
|
||||
|
||||
std::string criteria;
|
||||
MatchType match_type;
|
||||
X509Location location;
|
||||
|
||||
bool operator==(const Criteria& c) const {
|
||||
Criteria(const std::string& s, MatchType mt, X509Location loc) : criteria(s), match_type(mt), location(loc) {}
|
||||
Criteria(const std::string& s, MatchType mt) : Criteria(s, mt, X509Location::NAME) {}
|
||||
Criteria(const std::string& s, X509Location loc) : Criteria(s, MatchType::EXACT, loc) {}
|
||||
explicit Criteria(const std::string& s) : Criteria(s, MatchType::EXACT, X509Location::NAME) {}
|
||||
|
||||
bool operator==(const Criteria& c) const noexcept {
|
||||
return criteria == c.criteria && match_type == c.match_type && location == c.location;
|
||||
}
|
||||
};
|
||||
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
bool operator!=(const Criteria& c) const noexcept { return !(*this == c); }
|
||||
};
|
||||
|
||||
enum class TLSEndpointType { UNSET = 0, CLIENT, SERVER };
|
||||
|
||||
@ -241,13 +246,15 @@ public:
|
||||
std::string toString() const;
|
||||
|
||||
struct Rule {
|
||||
using CriteriaMap = std::map<NID, Criteria>;
|
||||
|
||||
explicit Rule(std::string input);
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
std::map<NID, Criteria> subject_criteria;
|
||||
std::map<NID, Criteria> issuer_criteria;
|
||||
std::map<NID, Criteria> root_criteria;
|
||||
CriteriaMap subject_criteria;
|
||||
CriteriaMap issuer_criteria;
|
||||
CriteriaMap root_criteria;
|
||||
|
||||
bool verify_cert = true;
|
||||
bool verify_time = true;
|
||||
|
Loading…
x
Reference in New Issue
Block a user