Skip to content

Commit 8fc89ac

Browse files
committed
ssl: add OID map properties for proxy-wasm
This change adds new methods to the SSL ConnectionInfo interface to expose certificate extension OID maps, which allow proxy-wasm filters to access certificate extension data. This implements the proposal from proxy-wasm/spec#89. The new properties are: - connection.oid_map_local_certificate (map) - connection.oid_map_peer_certificate (map) - upstream.oid_map_local_certificate (map) - upstream.oid_map_peer_certificate (map) Each property provides a map of OID strings to their values extracted from certificate extensions. Signed-off-by: Derek Brown <6845676+DerekTBrown@users.noreply.github.com>
1 parent d3e445e commit 8fc89ac

File tree

10 files changed

+299
-1
lines changed

10 files changed

+299
-1
lines changed

envoy/ssl/connection.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <map>
34
#include <memory>
45
#include <string>
56

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

195+
/**
196+
* @return const std::map<std::string, std::string>& the map of OID entries to their values
197+
* from peer certificate extensions. Returns empty map if there is no peer certificate,
198+
* or no extensions.
199+
**/
200+
virtual const std::map<std::string, std::string>& oidMapPeerCertificate() const PURE;
201+
202+
/**
203+
* @return const std::map<std::string, std::string>& the map of OID entries to their values
204+
* from local certificate extensions. Returns empty map if there is no local certificate,
205+
* or no extensions.
206+
**/
207+
virtual const std::map<std::string, std::string>& oidMapLocalCertificate() const PURE;
208+
194209
/**
195210
* @return absl::optional<SystemTime> the time that the peer certificate was issued and should be
196211
* considered valid from. Returns empty absl::optional if there is no peer certificate.

source/common/tls/connection_info_impl_base.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,28 @@ const std::string& ConnectionInfoImplBase::sessionId() const {
457457
});
458458
}
459459

460+
const std::map<std::string, std::string>& ConnectionInfoImplBase::oidMapPeerCertificate() const {
461+
return getCachedValueOrCreate<std::map<std::string, std::string>>(
462+
CachedValueTag::OidMapPeerCertificate, [](SSL* ssl) {
463+
bssl::UniquePtr<X509> cert(SSL_get_peer_certificate(ssl));
464+
if (!cert) {
465+
return std::map<std::string, std::string>{};
466+
}
467+
return Utility::getCertificateOidMap(*cert);
468+
});
469+
}
470+
471+
const std::map<std::string, std::string>& ConnectionInfoImplBase::oidMapLocalCertificate() const {
472+
return getCachedValueOrCreate<std::map<std::string, std::string>>(
473+
CachedValueTag::OidMapLocalCertificate, [](SSL* ssl) {
474+
X509* cert = SSL_get_certificate(ssl);
475+
if (!cert) {
476+
return std::map<std::string, std::string>{};
477+
}
478+
return Utility::getCertificateOidMap(*cert);
479+
});
480+
}
481+
460482
} // namespace Tls
461483
} // namespace TransportSockets
462484
} // namespace Extensions

source/common/tls/connection_info_impl_base.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class ConnectionInfoImplBase : public Ssl::ConnectionInfo {
4545
absl::Span<const std::string> othernameSansLocalCertificate() const override;
4646
absl::Span<const std::string> oidsPeerCertificate() const override;
4747
absl::Span<const std::string> oidsLocalCertificate() const override;
48+
const std::map<std::string, std::string>& oidMapPeerCertificate() const override;
49+
const std::map<std::string, std::string>& oidMapLocalCertificate() const override;
4850
absl::optional<SystemTime> validFromPeerCertificate() const override;
4951
absl::optional<SystemTime> expirationPeerCertificate() const override;
5052
const std::string& sessionId() const override;
@@ -88,6 +90,8 @@ class ConnectionInfoImplBase : public Ssl::ConnectionInfo {
8890
IpSansPeerCertificate,
8991
OidsPeerCertificate,
9092
OidsLocalCertificate,
93+
OidMapPeerCertificate,
94+
OidMapLocalCertificate,
9195
};
9296

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

source/common/tls/utility.cc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,35 @@ absl::string_view Utility::getCertificateExtensionValue(X509& cert,
493493
static_cast<absl::string_view::size_type>(octet_string_length)};
494494
}
495495

496+
std::map<std::string, std::string> Utility::getCertificateOidMap(X509& cert) {
497+
std::map<std::string, std::string> extension_map;
498+
499+
int count = X509_get_ext_count(&cert);
500+
for (int pos = 0; pos < count; pos++) {
501+
X509_EXTENSION* extension = X509_get_ext(&cert, pos);
502+
RELEASE_ASSERT(extension != nullptr, "");
503+
504+
char oid[MAX_OID_LENGTH];
505+
int obj_len = OBJ_obj2txt(oid, MAX_OID_LENGTH, X509_EXTENSION_get_object(extension),
506+
1 /* always_return_oid */);
507+
if (obj_len > 0 && obj_len < MAX_OID_LENGTH) {
508+
const ASN1_OCTET_STRING* octet_string = X509_EXTENSION_get_data(extension);
509+
RELEASE_ASSERT(octet_string != nullptr, "");
510+
511+
const unsigned char* octet_string_data = ASN1_STRING_get0_data(octet_string);
512+
const int octet_string_length = ASN1_STRING_length(octet_string);
513+
514+
std::string oid_str(oid);
515+
std::string value_str(reinterpret_cast<const char*>(octet_string_data),
516+
static_cast<size_t>(octet_string_length));
517+
518+
extension_map[oid_str] = value_str;
519+
}
520+
}
521+
522+
return extension_map;
523+
}
524+
496525
SystemTime Utility::getValidFrom(const X509& cert) {
497526
int days, seconds;
498527
int rc = ASN1_TIME_diff(&days, &seconds, &epochASN1Time(), X509_get0_notBefore(&cert));

source/common/tls/utility.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ std::vector<std::string> getCertificateExtensionOids(X509& cert);
116116
*/
117117
absl::string_view getCertificateExtensionValue(X509& cert, absl::string_view extension_name);
118118

119+
/**
120+
* Retrieves all OIDs and their values from a certificate's extensions as a map.
121+
* @param cert the certificate.
122+
* @return std::map<std::string, std::string> a map of OID strings to their extension values.
123+
*/
124+
std::map<std::string, std::string> getCertificateOidMap(X509& cert);
125+
119126
/**
120127
* Returns the seconds since unix epoch of the expiration time of this certificate.
121128
* @param cert the certificate

source/extensions/filters/common/expr/context.cc

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,45 @@
1010
#include "absl/strings/numbers.h"
1111
#include "absl/time/time.h"
1212

13+
#include "eval/public/cel_value.h"
14+
#include "eval/public/containers/container_backed_list_impl.h"
15+
1316
namespace Envoy {
1417
namespace Extensions {
1518
namespace Filters {
1619
namespace Common {
1720
namespace Expr {
1821

22+
// A simple constant map for CEL use
23+
class ConstMap : public google::api::expr::runtime::CelMap {
24+
public:
25+
using GetValueFunction = std::function<absl::optional<CelValue>(CelValue)>;
26+
using SizeFunction = std::function<int()>;
27+
using KeysFunction = std::function<std::vector<CelValue>()>;
28+
29+
ConstMap(GetValueFunction get_value, SizeFunction size_func, KeysFunction keys_func = nullptr)
30+
: get_value_(std::move(get_value)), size_func_(std::move(size_func)), keys_func_(std::move(keys_func)) {}
31+
32+
absl::optional<CelValue> operator[](CelValue key) const override { return get_value_(key); }
33+
int size() const override { return size_func_(); }
34+
bool empty() const override { return size() == 0; }
35+
36+
absl::StatusOr<const google::api::expr::runtime::CelList*> ListKeys() const override {
37+
// Create a container-backed list with keys from the map
38+
if (keys_func_ == nullptr) {
39+
static const google::api::expr::runtime::ContainerBackedListImpl empty_list({});
40+
return &empty_list;
41+
}
42+
auto keys = keys_func_();
43+
return new google::api::expr::runtime::ContainerBackedListImpl(std::move(keys));
44+
}
45+
46+
private:
47+
GetValueFunction get_value_;
48+
SizeFunction size_func_;
49+
KeysFunction keys_func_;
50+
};
51+
1952
Http::RegisterCustomInlineHeader<Http::CustomInlineHeaderRegistry::Type::RequestHeaders>
2053
referer_handle(Http::CustomHeaders::get().Referer);
2154

@@ -89,6 +122,66 @@ const SslExtractorsValues& SslExtractorsValues::get() {
89122
return {};
90123
}
91124
return CelValue::CreateString(&info.sha256PeerCertificateDigest());
125+
}},
126+
{OidMapLocalCertificate,
127+
[](const Ssl::ConnectionInfo& info) -> absl::optional<CelValue> {
128+
const auto& oid_map = info.oidMapLocalCertificate();
129+
if (oid_map.empty()) {
130+
return {};
131+
}
132+
// Create a map of OID strings to their values
133+
auto cel_map = std::make_unique<ConstMap>(
134+
[&oid_map](CelValue key) -> absl::optional<CelValue> {
135+
if (!key.IsString()) {
136+
return {};
137+
}
138+
auto str = std::string(key.StringOrDie().value());
139+
auto it = oid_map.find(str);
140+
if (it == oid_map.end()) {
141+
return {};
142+
}
143+
return CelValue::CreateStringView(it->second);
144+
},
145+
[&oid_map]() -> int { return oid_map.size(); },
146+
[&oid_map]() -> std::vector<CelValue> {
147+
std::vector<CelValue> keys;
148+
keys.reserve(oid_map.size());
149+
for (const auto& pair : oid_map) {
150+
keys.push_back(CelValue::CreateStringView(pair.first));
151+
}
152+
return keys;
153+
});
154+
return CelValue::CreateMap(cel_map.release());
155+
}},
156+
{OidMapPeerCertificate,
157+
[](const Ssl::ConnectionInfo& info) -> absl::optional<CelValue> {
158+
const auto& oid_map = info.oidMapPeerCertificate();
159+
if (oid_map.empty()) {
160+
return {};
161+
}
162+
// Create a map of OID strings to their values
163+
auto cel_map = std::make_unique<ConstMap>(
164+
[&oid_map](CelValue key) -> absl::optional<CelValue> {
165+
if (!key.IsString()) {
166+
return {};
167+
}
168+
auto str = std::string(key.StringOrDie().value());
169+
auto it = oid_map.find(str);
170+
if (it == oid_map.end()) {
171+
return {};
172+
}
173+
return CelValue::CreateStringView(it->second);
174+
},
175+
[&oid_map]() -> int { return oid_map.size(); },
176+
[&oid_map]() -> std::vector<CelValue> {
177+
std::vector<CelValue> keys;
178+
keys.reserve(oid_map.size());
179+
for (const auto& pair : oid_map) {
180+
keys.push_back(CelValue::CreateStringView(pair.first));
181+
}
182+
return keys;
183+
});
184+
return CelValue::CreateMap(cel_map.release());
92185
}}});
93186
}
94187

source/extensions/filters/common/expr/context.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ constexpr absl::string_view DNSSanLocalCertificate = "dns_san_local_certificate"
7171
constexpr absl::string_view DNSSanPeerCertificate = "dns_san_peer_certificate";
7272
constexpr absl::string_view SHA256PeerCertificateDigest = "sha256_peer_certificate_digest";
7373
constexpr absl::string_view DownstreamTransportFailureReason = "transport_failure_reason";
74+
constexpr absl::string_view OidMapLocalCertificate = "oid_map_local_certificate";
75+
constexpr absl::string_view OidMapPeerCertificate = "oid_map_peer_certificate";
7476

7577
// Source properties
7678
constexpr absl::string_view Source = "source";

test/common/tls/utility_test.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,34 @@ TEST(UtilityTest, TestGetCertificationExtensionValue) {
248248
EXPECT_EQ("", Utility::getCertificateExtensionValue(*cert, "foo"));
249249
}
250250

251+
TEST(UtilityTest, TestGetCertificateOidMap) {
252+
bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
253+
"{{ test_rundir }}/test/common/tls/test_data/extensions_cert.pem"));
254+
const auto& oid_map = Utility::getCertificateOidMap(*cert);
255+
256+
EXPECT_EQ(7, oid_map.size());
257+
258+
// Check that all expected OIDs are present
259+
std::vector<std::string> expected_oids{
260+
"2.5.29.14", "2.5.29.15", "2.5.29.19",
261+
"2.5.29.35", "2.5.29.37",
262+
"1.2.3.4.5.6.7.8", "1.2.3.4.5.6.7.9"};
263+
264+
for (const auto& oid : expected_oids) {
265+
EXPECT_NE(oid_map.find(oid), oid_map.end());
266+
}
267+
268+
// Check specific values for custom OIDs
269+
EXPECT_EQ("\xc\x9Something", oid_map.at("1.2.3.4.5.6.7.8"));
270+
EXPECT_EQ("\x30\x3\x1\x1\xFF", oid_map.at("1.2.3.4.5.6.7.9"));
271+
272+
// Test with a certificate that has no extensions
273+
bssl::UniquePtr<X509> no_ext_cert = readCertFromFile(TestEnvironment::substitute(
274+
"{{ test_rundir }}/test/common/tls/test_data/no_extension_cert.pem"));
275+
const auto& empty_map = Utility::getCertificateOidMap(*no_ext_cert);
276+
EXPECT_TRUE(empty_map.empty());
277+
}
278+
251279
TEST(UtilityTest, SslErrorDescriptionTest) {
252280
const std::vector<std::pair<int, std::string>> test_set = {
253281
{SSL_ERROR_NONE, "NONE"},

test/extensions/filters/common/expr/context_test.cc

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,102 @@ TEST(Context, ConnectionAttributes) {
837837
EXPECT_TRUE(Protobuf::util::MessageDifferencer::Equals(*value.value().MessageOrDie(),
838838
upstream_locality));
839839
}
840+
841+
// Test OID map properties
842+
{
843+
// Create maps of OIDs to values
844+
std::map<std::string, std::string> local_oid_map = {
845+
{"1.2.840.113549.1.9.1", "test@example.com"},
846+
{"2.5.4.10", "Test Organization"}
847+
};
848+
849+
std::map<std::string, std::string> peer_oid_map = {
850+
{"1.2.840.113549.1.9.1", "peer@example.com"},
851+
{"2.5.4.11", "Test OU"}
852+
};
853+
854+
// Set up expectations for OID maps
855+
EXPECT_CALL(*downstream_ssl_info, oidMapLocalCertificate())
856+
.WillRepeatedly(ReturnRef(local_oid_map));
857+
EXPECT_CALL(*downstream_ssl_info, oidMapPeerCertificate())
858+
.WillRepeatedly(ReturnRef(peer_oid_map));
859+
EXPECT_CALL(*upstream_ssl_info, oidMapLocalCertificate())
860+
.WillRepeatedly(ReturnRef(local_oid_map));
861+
EXPECT_CALL(*upstream_ssl_info, oidMapPeerCertificate())
862+
.WillRepeatedly(ReturnRef(peer_oid_map));
863+
864+
// Test downstream local OID map
865+
{
866+
auto value = connection[CelValue::CreateStringView(OidMapLocalCertificate)];
867+
EXPECT_TRUE(value.has_value());
868+
ASSERT_TRUE(value.value().IsMap());
869+
auto& map = *value.value().MapOrDie();
870+
871+
auto email = map[CelValue::CreateStringView("1.2.840.113549.1.9.1")];
872+
EXPECT_TRUE(email.has_value());
873+
EXPECT_TRUE(email.value().IsString());
874+
EXPECT_EQ("test@example.com", email.value().StringOrDie().value());
875+
876+
auto org = map[CelValue::CreateStringView("2.5.4.10")];
877+
EXPECT_TRUE(org.has_value());
878+
EXPECT_TRUE(org.value().IsString());
879+
EXPECT_EQ("Test Organization", org.value().StringOrDie().value());
880+
}
881+
882+
// Test downstream peer OID map
883+
{
884+
auto value = connection[CelValue::CreateStringView(OidMapPeerCertificate)];
885+
EXPECT_TRUE(value.has_value());
886+
ASSERT_TRUE(value.value().IsMap());
887+
auto& map = *value.value().MapOrDie();
888+
889+
auto email = map[CelValue::CreateStringView("1.2.840.113549.1.9.1")];
890+
EXPECT_TRUE(email.has_value());
891+
EXPECT_TRUE(email.value().IsString());
892+
EXPECT_EQ("peer@example.com", email.value().StringOrDie().value());
893+
894+
auto ou = map[CelValue::CreateStringView("2.5.4.11")];
895+
EXPECT_TRUE(ou.has_value());
896+
EXPECT_TRUE(ou.value().IsString());
897+
EXPECT_EQ("Test OU", ou.value().StringOrDie().value());
898+
}
899+
900+
// Test upstream local OID map
901+
{
902+
auto value = upstream[CelValue::CreateStringView(OidMapLocalCertificate)];
903+
EXPECT_TRUE(value.has_value());
904+
ASSERT_TRUE(value.value().IsMap());
905+
auto& map = *value.value().MapOrDie();
906+
907+
auto email = map[CelValue::CreateStringView("1.2.840.113549.1.9.1")];
908+
EXPECT_TRUE(email.has_value());
909+
EXPECT_TRUE(email.value().IsString());
910+
EXPECT_EQ("test@example.com", email.value().StringOrDie().value());
911+
912+
auto org = map[CelValue::CreateStringView("2.5.4.10")];
913+
EXPECT_TRUE(org.has_value());
914+
EXPECT_TRUE(org.value().IsString());
915+
EXPECT_EQ("Test Organization", org.value().StringOrDie().value());
916+
}
917+
918+
// Test upstream peer OID map
919+
{
920+
auto value = upstream[CelValue::CreateStringView(OidMapPeerCertificate)];
921+
EXPECT_TRUE(value.has_value());
922+
ASSERT_TRUE(value.value().IsMap());
923+
auto& map = *value.value().MapOrDie();
924+
925+
auto email = map[CelValue::CreateStringView("1.2.840.113549.1.9.1")];
926+
EXPECT_TRUE(email.has_value());
927+
EXPECT_TRUE(email.value().IsString());
928+
EXPECT_EQ("peer@example.com", email.value().StringOrDie().value());
929+
930+
auto ou = map[CelValue::CreateStringView("2.5.4.11")];
931+
EXPECT_TRUE(ou.has_value());
932+
EXPECT_TRUE(ou.value().IsString());
933+
EXPECT_EQ("Test OU", ou.value().StringOrDie().value());
934+
}
935+
}
840936
}
841937

842938
TEST(Context, FilterStateAttributes) {

0 commit comments

Comments
 (0)