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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions envoy/ssl/connection.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <map>
#include <memory>
#include <string>

Expand Down Expand Up @@ -191,6 +192,20 @@ class ConnectionInfo {
**/
virtual absl::Span<const std::string> oidsLocalCertificate() const PURE;

/**
* @return const std::map<std::string, std::string>& the map of OID entries to their values
* from peer certificate extensions. Returns empty map if there is no peer certificate,
* or no extensions.
**/
virtual const std::map<std::string, std::string>& oidMapPeerCertificate() const PURE;

/**
* @return const std::map<std::string, std::string>& the map of OID entries to their values
* from local certificate extensions. Returns empty map if there is no local certificate,
* or no extensions.
**/
virtual const std::map<std::string, std::string>& oidMapLocalCertificate() const PURE;

/**
* @return absl::optional<SystemTime> the time that the peer certificate was issued and should be
* considered valid from. Returns empty absl::optional if there is no peer certificate.
Expand Down
22 changes: 22 additions & 0 deletions source/common/tls/connection_info_impl_base.cc
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,28 @@ const std::string& ConnectionInfoImplBase::sessionId() const {
});
}

const std::map<std::string, std::string>& ConnectionInfoImplBase::oidMapPeerCertificate() const {
return getCachedValueOrCreate<std::map<std::string, std::string>>(
CachedValueTag::OidMapPeerCertificate, [](SSL* ssl) {
bssl::UniquePtr<X509> cert(SSL_get_peer_certificate(ssl));
if (!cert) {
return std::map<std::string, std::string>{};
}
return Utility::getCertificateOidMap(*cert);
});
}

const std::map<std::string, std::string>& ConnectionInfoImplBase::oidMapLocalCertificate() const {
return getCachedValueOrCreate<std::map<std::string, std::string>>(
CachedValueTag::OidMapLocalCertificate, [](SSL* ssl) {
X509* cert = SSL_get_certificate(ssl);
if (!cert) {
return std::map<std::string, std::string>{};
}
return Utility::getCertificateOidMap(*cert);
});
}

} // namespace Tls
} // namespace TransportSockets
} // namespace Extensions
Expand Down
6 changes: 5 additions & 1 deletion source/common/tls/connection_info_impl_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class ConnectionInfoImplBase : public Ssl::ConnectionInfo {
absl::Span<const std::string> othernameSansLocalCertificate() const override;
absl::Span<const std::string> oidsPeerCertificate() const override;
absl::Span<const std::string> oidsLocalCertificate() const override;
const std::map<std::string, std::string>& oidMapPeerCertificate() const override;
const std::map<std::string, std::string>& oidMapLocalCertificate() const override;
absl::optional<SystemTime> validFromPeerCertificate() const override;
absl::optional<SystemTime> expirationPeerCertificate() const override;
const std::string& sessionId() const override;
Expand Down Expand Up @@ -88,6 +90,8 @@ class ConnectionInfoImplBase : public Ssl::ConnectionInfo {
IpSansPeerCertificate,
OidsPeerCertificate,
OidsLocalCertificate,
Comment on lines 91 to 92
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: we could probably drop this in favor of the OidMapPeerCertificate/OidMapLocalCertificate implementation, and just iterate over keys when needed.

OidMapPeerCertificate,
OidMapLocalCertificate,
};

// Retrieve the given tag from the set of cached values, or create the value via the supplied
Expand All @@ -101,7 +105,7 @@ class ConnectionInfoImplBase : public Ssl::ConnectionInfo {
// table of cached values that are created on demand. Use a node_hash_map so that returned
// references are not invalidated when additional items are added.
using CachedValue = absl::variant<std::string, std::vector<std::string>, Ssl::ParsedX509NamePtr,
bssl::UniquePtr<GENERAL_NAMES>>;
bssl::UniquePtr<GENERAL_NAMES>, std::map<std::string, std::string>>;
mutable absl::node_hash_map<CachedValueTag, CachedValue> cached_values_;
};

Expand Down
29 changes: 29 additions & 0 deletions source/common/tls/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,35 @@ absl::string_view Utility::getCertificateExtensionValue(X509& cert,
static_cast<absl::string_view::size_type>(octet_string_length)};
}

std::map<std::string, std::string> Utility::getCertificateOidMap(X509& cert) {
std::map<std::string, std::string> extension_map;

int count = X509_get_ext_count(&cert);
for (int pos = 0; pos < count; pos++) {
X509_EXTENSION* extension = X509_get_ext(&cert, pos);
RELEASE_ASSERT(extension != nullptr, "");

char oid[MAX_OID_LENGTH];
int obj_len = OBJ_obj2txt(oid, MAX_OID_LENGTH, X509_EXTENSION_get_object(extension),
1 /* always_return_oid */);
if (obj_len > 0 && obj_len < MAX_OID_LENGTH) {
const ASN1_OCTET_STRING* octet_string = X509_EXTENSION_get_data(extension);
RELEASE_ASSERT(octet_string != nullptr, "");

const unsigned char* octet_string_data = ASN1_STRING_get0_data(octet_string);
const int octet_string_length = ASN1_STRING_length(octet_string);

std::string oid_str(oid);
std::string value_str(reinterpret_cast<const char*>(octet_string_data),
static_cast<size_t>(octet_string_length));

extension_map[oid_str] = value_str;
}
}

return extension_map;
}

SystemTime Utility::getValidFrom(const X509& cert) {
int days, seconds;
int rc = ASN1_TIME_diff(&days, &seconds, &epochASN1Time(), X509_get0_notBefore(&cert));
Expand Down
7 changes: 7 additions & 0 deletions source/common/tls/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ std::vector<std::string> getCertificateExtensionOids(X509& cert);
*/
absl::string_view getCertificateExtensionValue(X509& cert, absl::string_view extension_name);

/**
* Retrieves all OIDs and their values from a certificate's extensions as a map.
* @param cert the certificate.
* @return std::map<std::string, std::string> a map of OID strings to their extension values.
*/
std::map<std::string, std::string> getCertificateOidMap(X509& cert);

/**
* Returns the seconds since unix epoch of the expiration time of this certificate.
* @param cert the certificate
Expand Down
93 changes: 93 additions & 0 deletions source/extensions/filters/common/expr/context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,45 @@
#include "absl/strings/numbers.h"
#include "absl/time/time.h"

#include "eval/public/cel_value.h"
#include "eval/public/containers/container_backed_list_impl.h"

namespace Envoy {
namespace Extensions {
namespace Filters {
namespace Common {
namespace Expr {

// A simple constant map for CEL use
class ConstMap : public google::api::expr::runtime::CelMap {
public:
using GetValueFunction = std::function<absl::optional<CelValue>(CelValue)>;
using SizeFunction = std::function<int()>;
using KeysFunction = std::function<std::vector<CelValue>()>;

ConstMap(GetValueFunction get_value, SizeFunction size_func, KeysFunction keys_func = nullptr)
: get_value_(std::move(get_value)), size_func_(std::move(size_func)), keys_func_(std::move(keys_func)) {}

absl::optional<CelValue> operator[](CelValue key) const override { return get_value_(key); }
int size() const override { return size_func_(); }
bool empty() const override { return size() == 0; }

absl::StatusOr<const google::api::expr::runtime::CelList*> ListKeys() const override {
// Create a container-backed list with keys from the map
if (keys_func_ == nullptr) {
static const google::api::expr::runtime::ContainerBackedListImpl empty_list({});
return &empty_list;
}
auto keys = keys_func_();
return new google::api::expr::runtime::ContainerBackedListImpl(std::move(keys));
}

private:
GetValueFunction get_value_;
SizeFunction size_func_;
KeysFunction keys_func_;
};

Http::RegisterCustomInlineHeader<Http::CustomInlineHeaderRegistry::Type::RequestHeaders>
referer_handle(Http::CustomHeaders::get().Referer);

Expand Down Expand Up @@ -89,6 +122,66 @@ const SslExtractorsValues& SslExtractorsValues::get() {
return {};
}
return CelValue::CreateString(&info.sha256PeerCertificateDigest());
}},
{OidMapLocalCertificate,
[](const Ssl::ConnectionInfo& info) -> absl::optional<CelValue> {
const auto& oid_map = info.oidMapLocalCertificate();
if (oid_map.empty()) {
return {};
}
// Create a map of OID strings to their values
auto cel_map = std::make_unique<ConstMap>(
[&oid_map](CelValue key) -> absl::optional<CelValue> {
if (!key.IsString()) {
return {};
}
auto str = std::string(key.StringOrDie().value());
auto it = oid_map.find(str);
if (it == oid_map.end()) {
return {};
}
return CelValue::CreateStringView(it->second);
},
[&oid_map]() -> int { return oid_map.size(); },
[&oid_map]() -> std::vector<CelValue> {
std::vector<CelValue> keys;
keys.reserve(oid_map.size());
for (const auto& pair : oid_map) {
keys.push_back(CelValue::CreateStringView(pair.first));
}
return keys;
});
return CelValue::CreateMap(cel_map.release());
}},
{OidMapPeerCertificate,
[](const Ssl::ConnectionInfo& info) -> absl::optional<CelValue> {
const auto& oid_map = info.oidMapPeerCertificate();
if (oid_map.empty()) {
return {};
}
// Create a map of OID strings to their values
auto cel_map = std::make_unique<ConstMap>(
[&oid_map](CelValue key) -> absl::optional<CelValue> {
if (!key.IsString()) {
return {};
}
auto str = std::string(key.StringOrDie().value());
auto it = oid_map.find(str);
if (it == oid_map.end()) {
return {};
}
return CelValue::CreateStringView(it->second);
},
[&oid_map]() -> int { return oid_map.size(); },
[&oid_map]() -> std::vector<CelValue> {
std::vector<CelValue> keys;
keys.reserve(oid_map.size());
for (const auto& pair : oid_map) {
keys.push_back(CelValue::CreateStringView(pair.first));
}
return keys;
});
return CelValue::CreateMap(cel_map.release());
}}});
}

Expand Down
2 changes: 2 additions & 0 deletions source/extensions/filters/common/expr/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ constexpr absl::string_view DNSSanLocalCertificate = "dns_san_local_certificate"
constexpr absl::string_view DNSSanPeerCertificate = "dns_san_peer_certificate";
constexpr absl::string_view SHA256PeerCertificateDigest = "sha256_peer_certificate_digest";
constexpr absl::string_view DownstreamTransportFailureReason = "transport_failure_reason";
constexpr absl::string_view OidMapLocalCertificate = "oid_map_local_certificate";
constexpr absl::string_view OidMapPeerCertificate = "oid_map_peer_certificate";

// Source properties
constexpr absl::string_view Source = "source";
Expand Down
28 changes: 28 additions & 0 deletions test/common/tls/utility_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,34 @@ TEST(UtilityTest, TestGetCertificationExtensionValue) {
EXPECT_EQ("", Utility::getCertificateExtensionValue(*cert, "foo"));
}

TEST(UtilityTest, TestGetCertificateOidMap) {
bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
"{{ test_rundir }}/test/common/tls/test_data/extensions_cert.pem"));
const auto& oid_map = Utility::getCertificateOidMap(*cert);

EXPECT_EQ(7, oid_map.size());

// Check that all expected OIDs are present
std::vector<std::string> expected_oids{
"2.5.29.14", "2.5.29.15", "2.5.29.19",
"2.5.29.35", "2.5.29.37",
"1.2.3.4.5.6.7.8", "1.2.3.4.5.6.7.9"};

for (const auto& oid : expected_oids) {
EXPECT_NE(oid_map.find(oid), oid_map.end());
}

// Check specific values for custom OIDs
EXPECT_EQ("\xc\x9Something", oid_map.at("1.2.3.4.5.6.7.8"));
EXPECT_EQ("\x30\x3\x1\x1\xFF", oid_map.at("1.2.3.4.5.6.7.9"));

// Test with a certificate that has no extensions
bssl::UniquePtr<X509> no_ext_cert = readCertFromFile(TestEnvironment::substitute(
"{{ test_rundir }}/test/common/tls/test_data/no_extension_cert.pem"));
const auto& empty_map = Utility::getCertificateOidMap(*no_ext_cert);
EXPECT_TRUE(empty_map.empty());
}

TEST(UtilityTest, SslErrorDescriptionTest) {
const std::vector<std::pair<int, std::string>> test_set = {
{SSL_ERROR_NONE, "NONE"},
Expand Down
96 changes: 96 additions & 0 deletions test/extensions/filters/common/expr/context_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,102 @@ TEST(Context, ConnectionAttributes) {
EXPECT_TRUE(Protobuf::util::MessageDifferencer::Equals(*value.value().MessageOrDie(),
upstream_locality));
}

// Test OID map properties
{
// Create maps of OIDs to values
std::map<std::string, std::string> local_oid_map = {
{"1.2.840.113549.1.9.1", "[email protected]"},
{"2.5.4.10", "Test Organization"}
};

std::map<std::string, std::string> peer_oid_map = {
{"1.2.840.113549.1.9.1", "[email protected]"},
{"2.5.4.11", "Test OU"}
};

// Set up expectations for OID maps
EXPECT_CALL(*downstream_ssl_info, oidMapLocalCertificate())
.WillRepeatedly(ReturnRef(local_oid_map));
EXPECT_CALL(*downstream_ssl_info, oidMapPeerCertificate())
.WillRepeatedly(ReturnRef(peer_oid_map));
EXPECT_CALL(*upstream_ssl_info, oidMapLocalCertificate())
.WillRepeatedly(ReturnRef(local_oid_map));
EXPECT_CALL(*upstream_ssl_info, oidMapPeerCertificate())
.WillRepeatedly(ReturnRef(peer_oid_map));

// Test downstream local OID map
{
auto value = connection[CelValue::CreateStringView(OidMapLocalCertificate)];
EXPECT_TRUE(value.has_value());
ASSERT_TRUE(value.value().IsMap());
auto& map = *value.value().MapOrDie();

auto email = map[CelValue::CreateStringView("1.2.840.113549.1.9.1")];
EXPECT_TRUE(email.has_value());
EXPECT_TRUE(email.value().IsString());
EXPECT_EQ("[email protected]", email.value().StringOrDie().value());

auto org = map[CelValue::CreateStringView("2.5.4.10")];
EXPECT_TRUE(org.has_value());
EXPECT_TRUE(org.value().IsString());
EXPECT_EQ("Test Organization", org.value().StringOrDie().value());
}

// Test downstream peer OID map
{
auto value = connection[CelValue::CreateStringView(OidMapPeerCertificate)];
EXPECT_TRUE(value.has_value());
ASSERT_TRUE(value.value().IsMap());
auto& map = *value.value().MapOrDie();

auto email = map[CelValue::CreateStringView("1.2.840.113549.1.9.1")];
EXPECT_TRUE(email.has_value());
EXPECT_TRUE(email.value().IsString());
EXPECT_EQ("[email protected]", email.value().StringOrDie().value());

auto ou = map[CelValue::CreateStringView("2.5.4.11")];
EXPECT_TRUE(ou.has_value());
EXPECT_TRUE(ou.value().IsString());
EXPECT_EQ("Test OU", ou.value().StringOrDie().value());
}

// Test upstream local OID map
{
auto value = upstream[CelValue::CreateStringView(OidMapLocalCertificate)];
EXPECT_TRUE(value.has_value());
ASSERT_TRUE(value.value().IsMap());
auto& map = *value.value().MapOrDie();

auto email = map[CelValue::CreateStringView("1.2.840.113549.1.9.1")];
EXPECT_TRUE(email.has_value());
EXPECT_TRUE(email.value().IsString());
EXPECT_EQ("[email protected]", email.value().StringOrDie().value());

auto org = map[CelValue::CreateStringView("2.5.4.10")];
EXPECT_TRUE(org.has_value());
EXPECT_TRUE(org.value().IsString());
EXPECT_EQ("Test Organization", org.value().StringOrDie().value());
}

// Test upstream peer OID map
{
auto value = upstream[CelValue::CreateStringView(OidMapPeerCertificate)];
EXPECT_TRUE(value.has_value());
ASSERT_TRUE(value.value().IsMap());
auto& map = *value.value().MapOrDie();

auto email = map[CelValue::CreateStringView("1.2.840.113549.1.9.1")];
EXPECT_TRUE(email.has_value());
EXPECT_TRUE(email.value().IsString());
EXPECT_EQ("[email protected]", email.value().StringOrDie().value());

auto ou = map[CelValue::CreateStringView("2.5.4.11")];
EXPECT_TRUE(ou.has_value());
EXPECT_TRUE(ou.value().IsString());
EXPECT_EQ("Test OU", ou.value().StringOrDie().value());
}
}
}

TEST(Context, FilterStateAttributes) {
Expand Down
Loading