Skip to content

Commit

Permalink
lua: Add API to expose TLS OIDs for extensions
Browse files Browse the repository at this point in the history
This change adds two new methods (i.e. oidsPeerCertificate() and
oidsLocalCertificate()) to SSL connection object API, so as to make
ASN.1 OID cert info accessible from lua API.

Extension developer could then leverage the OID information to further
identify cert type for advanced use cases.

Risk Level: low
Testing: utility unit and lua filter integration test
Docs Changes: lua filter doc updated for new apis
Release Notes: included

Fixes envoyproxy#14801

Signed-off-by: Qiu Yu <qiuyu@apple.com>
  • Loading branch information
unicell committed Sep 3, 2024
1 parent dccb77a commit 6fba99b
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 0 deletions.
4 changes: 4 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -273,5 +273,9 @@ new_features:
change: |
Added :ref:`external_auth_provider <envoy_v3_api_msg_extensions.filters.network.redis_proxy.v3.RedisProxy>` to support
external authentication for redis proxy.
- area: lua
change: |
Added two new methods ``oidsPeerCertificate()`` and ``oidsLocalCertificate()`` to SSL
connection object API :ref:`SSL connection info object <config_http_filters_lua_ssl_socket_info>`.
deprecated:
20 changes: 20 additions & 0 deletions docs/root/configuration/http/http_filters/lua_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,26 @@ dnsSansLocalCertificate()
Returns the DNS entries (as a table) in the SAN field of the local certificate. Returns an empty
table if there is no local certificate, or no SAN field, or no DNS SAN entries.

oidsPeerCertificate()
^^^^^^^^^^^^^^^^^^^^^

.. code-block:: lua
downstreamSslConnection:oidsPeerCertificate()
Returns the OIDs (ASN.1 Object Identifiers) of the peer certificate. Returns an empty table if
there is no peer certificate or no OIDs.

oidsLocalCertificate()
^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: lua
downstreamSslConnection:oidsLocalCertificate()
Returns the OIDs (ASN.1 Object Identifiers) of the peer certificate. Returns an empty table if
there is no peer certificate or no OIDs.

validFromPeerCertificate()
^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
12 changes: 12 additions & 0 deletions envoy/ssl/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@ class ConnectionInfo {
**/
virtual absl::Span<const std::string> ipSansLocalCertificate() const PURE;

/**
* @return absl::Span<const std::string> the OID entries of the peer certificate extensions.
* Returns {} if there is no peer certificate, or no extensions.
**/
virtual absl::Span<const std::string> oidsPeerCertificate() const PURE;

/**
* @return absl::Span<const std::string> the OID entries of the local certificate extensions.
* Returns {} if there is no local certificate, or no extensions.
**/
virtual absl::Span<const std::string> oidsLocalCertificate() 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
28 changes: 28 additions & 0 deletions source/common/tls/connection_info_impl_base.cc
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,34 @@ absl::Span<const std::string> ConnectionInfoImplBase::ipSansPeerCertificate() co
return cached_ip_san_peer_certificate_;
}

absl::Span<const std::string> ConnectionInfoImplBase::oidsPeerCertificate() const {
if (!cached_oid_peer_certificate_.empty()) {
return cached_oid_peer_certificate_;
}

bssl::UniquePtr<X509> cert(SSL_get_peer_certificate(ssl()));
if (!cert) {
ASSERT(cached_oid_peer_certificate_.empty());
return cached_oid_peer_certificate_;
}
cached_oid_peer_certificate_ = Utility::getCertificateExtensionOids(*cert);
return cached_oid_peer_certificate_;
}

absl::Span<const std::string> ConnectionInfoImplBase::oidsLocalCertificate() const {
if (!cached_oid_local_certificate_.empty()) {
return cached_oid_local_certificate_;
}

bssl::UniquePtr<X509> cert(SSL_get_peer_certificate(ssl()));
if (!cert) {
ASSERT(cached_oid_local_certificate_.empty());
return cached_oid_local_certificate_;
}
cached_oid_local_certificate_ = Utility::getCertificateExtensionOids(*cert);
return cached_oid_local_certificate_;
}

uint16_t ConnectionInfoImplBase::ciphersuiteId() const {
const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl());
if (cipher == nullptr) {
Expand Down
4 changes: 4 additions & 0 deletions source/common/tls/connection_info_impl_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class ConnectionInfoImplBase : public Ssl::ConnectionInfo {
absl::Span<const std::string> dnsSansLocalCertificate() const override;
absl::Span<const std::string> ipSansPeerCertificate() const override;
absl::Span<const std::string> ipSansLocalCertificate() const override;
absl::Span<const std::string> oidsPeerCertificate() const override;
absl::Span<const std::string> oidsLocalCertificate() const override;
absl::optional<SystemTime> validFromPeerCertificate() const override;
absl::optional<SystemTime> expirationPeerCertificate() const override;
const std::string& sessionId() const override;
Expand Down Expand Up @@ -66,6 +68,8 @@ class ConnectionInfoImplBase : public Ssl::ConnectionInfo {
mutable std::vector<std::string> cached_dns_san_local_certificate_;
mutable std::vector<std::string> cached_ip_san_peer_certificate_;
mutable std::vector<std::string> cached_ip_san_local_certificate_;
mutable std::vector<std::string> cached_oid_peer_certificate_;
mutable std::vector<std::string> cached_oid_local_certificate_;
mutable std::string cached_session_id_;
mutable std::string cached_tls_version_;
mutable std::string alpn_;
Expand Down
25 changes: 25 additions & 0 deletions source/common/tls/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,31 @@ absl::optional<uint32_t> Utility::getDaysUntilExpiration(const X509* cert,
return absl::nullopt;
}

std::vector<std::string> Utility::getCertificateExtensionOids(X509& cert) {
std::vector<std::string> extension_oids;

int count = X509_get_ext_count(&cert);
for (int pos = 0; pos < count; pos++) {
X509_EXTENSION* extension = X509_get_ext(&cert, pos);
if (extension == nullptr) {
continue;
}

ASN1_OBJECT* obj = X509_EXTENSION_get_object(extension);
if (extension == nullptr) {
continue;
}

char oid[256];
int len = OBJ_obj2txt(oid, sizeof(oid), obj, 1); // Use 1 for always_return_oid
if (len <= 0) {
continue;
}
extension_oids.push_back(oid);
}
return extension_oids;
}

absl::string_view Utility::getCertificateExtensionValue(X509& cert,
absl::string_view extension_name) {
bssl::UniquePtr<ASN1_OBJECT> oid(
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 @@ -88,6 +88,13 @@ std::string getIssuerFromCertificate(X509& cert);
*/
std::string getSubjectFromCertificate(X509& cert);

/**
* Retrieves the extension OIDs from certificate.
* @param cert the certificate
* @return std::vector returns the string list of ASN.1 object identifiers.
*/
std::vector<std::string> getCertificateExtensionOids(X509& cert);

/**
* Retrieves the value of a specific X509 extension from the cert, if present.
* @param cert the certificate.
Expand Down
10 changes: 10 additions & 0 deletions source/extensions/filters/common/lua/wrappers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,16 @@ int SslConnectionWrapper::luaDnsSansLocalCertificate(lua_State* state) {
return 1;
}

int SslConnectionWrapper::luaOidsPeerCertificate(lua_State* state) {
createLuaTableFromStringList(state, connection_info_.oidsPeerCertificate());
return 1;
}

int SslConnectionWrapper::luaOidsLocalCertificate(lua_State* state) {
createLuaTableFromStringList(state, connection_info_.oidsLocalCertificate());
return 1;
}

int SslConnectionWrapper::luaValidFromPeerCertificate(lua_State* state) {
lua_pushinteger(state, timestampInSeconds(connection_info_.validFromPeerCertificate()));
return 1;
Expand Down
14 changes: 14 additions & 0 deletions source/extensions/filters/common/lua/wrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ class SslConnectionWrapper : public BaseLuaObject<SslConnectionWrapper> {
{"subjectLocalCertificate", static_luaSubjectLocalCertificate},
{"dnsSansPeerCertificate", static_luaDnsSansPeerCertificate},
{"dnsSansLocalCertificate", static_luaDnsSansLocalCertificate},
{"oidsPeerCertificate", static_luaOidsPeerCertificate},
{"oidsLocalCertificate", static_luaOidsLocalCertificate},
{"validFromPeerCertificate", static_luaValidFromPeerCertificate},
{"expirationPeerCertificate", static_luaExpirationPeerCertificate},
{"sessionId", static_luaSessionId},
Expand Down Expand Up @@ -223,6 +225,18 @@ class SslConnectionWrapper : public BaseLuaObject<SslConnectionWrapper> {
*/
DECLARE_LUA_FUNCTION(SslConnectionWrapper, luaDnsSansLocalCertificate);

/**
* Returns the OIDs (ASN.1 Object Identifiers) of the peer certificate. Returns an empty table if
* there is no peer certificate or no OIDs.
*/
DECLARE_LUA_FUNCTION(SslConnectionWrapper, luaOidsPeerCertificate);

/**
* Returns the OIDs (ASN.1 Object Identifiers) of the peer certificate. Returns an empty table if
* there is no peer certificate or no OIDs.
*/
DECLARE_LUA_FUNCTION(SslConnectionWrapper, luaOidsLocalCertificate);

/**
* Returns the timestamp-since-epoch (in seconds) that the peer certificate was issued and should
* be considered valid from. Returns empty string if there is no peer certificate.
Expand Down
27 changes: 27 additions & 0 deletions test/common/tls/utility_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,33 @@ TEST(UtilityTest, GetLastCryptoError) {
EXPECT_FALSE(Utility::getLastCryptoError().has_value());
}

TEST(UtilityTest, TestGetCertificateExtensionOids) {
const std::string test_data_path = "{{ test_rundir }}/test/common/tls/test_data/";
const std::vector<std::pair<std::string, int>> test_set = {
{"unittest_cert.pem", 1},
{"san_wildcard_dns_cert.pem", 6},
{"extensions_cert.pem", 7},
};

for (const auto& test_case : test_set) {
bssl::UniquePtr<X509> cert =
readCertFromFile(TestEnvironment::substitute(test_data_path + test_case.first));
const auto& extension_oids = Utility::getCertificateExtensionOids(*cert);
EXPECT_EQ(test_case.second, extension_oids.size());
}

bssl::UniquePtr<X509> cert =
readCertFromFile(TestEnvironment::substitute(test_data_path + "extensions_cert.pem"));
// clang-format off
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"};
// clang-format on
const auto& extension_oids = Utility::getCertificateExtensionOids(*cert);
EXPECT_THAT(extension_oids, testing::UnorderedElementsAreArray(expected_oids));
}

TEST(UtilityTest, TestGetCertificationExtensionValue) {
bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
"{{ test_rundir }}/test/common/tls/test_data/extensions_cert.pem"));
Expand Down
11 changes: 11 additions & 0 deletions test/extensions/filters/http/lua/lua_filter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2293,6 +2293,9 @@ TEST_F(LuaHttpFilterTest, InspectStreamInfoDowstreamSslConnection) {
request_handle:logTrace(table.concat(request_handle:streamInfo():downstreamSslConnection():dnsSansPeerCertificate(), ","))
request_handle:logTrace(table.concat(request_handle:streamInfo():downstreamSslConnection():dnsSansLocalCertificate(), ","))
request_handle:logTrace(table.concat(request_handle:streamInfo():downstreamSslConnection():oidsPeerCertificate(), ","))
request_handle:logTrace(table.concat(request_handle:streamInfo():downstreamSslConnection():oidsLocalCertificate(), ","))
request_handle:logTrace(request_handle:streamInfo():downstreamSslConnection():ciphersuiteId())
request_handle:logTrace(request_handle:streamInfo():downstreamSslConnection():validFromPeerCertificate())
Expand Down Expand Up @@ -2345,6 +2348,14 @@ TEST_F(LuaHttpFilterTest, InspectStreamInfoDowstreamSslConnection) {
EXPECT_CALL(*filter_,
scriptLog(spdlog::level::trace, StrEq("local-dns-sans-1,local-dns-sans-2")));

const std::vector<std::string> peer_oids{"2.5.29.14", "1.2.840.113635.100"};
EXPECT_CALL(*connection_info, oidsPeerCertificate()).WillOnce(Return(peer_oids));
EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("2.5.29.14,1.2.840.113635.100")));

const std::vector<std::string> local_oids{"2.5.29.14", "2.5.29.15", "2.5.29.19"};
EXPECT_CALL(*connection_info, oidsLocalCertificate()).WillOnce(Return(local_oids));
EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("2.5.29.14,2.5.29.15,2.5.29.19")));

const std::string subject_local = "subject-local";
EXPECT_CALL(*connection_info, subjectLocalCertificate()).WillOnce(ReturnRef(subject_local));
EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq(subject_local)));
Expand Down
2 changes: 2 additions & 0 deletions test/mocks/ssl/mocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class MockConnectionInfo : public ConnectionInfo {
MOCK_METHOD(absl::Span<const std::string>, dnsSansLocalCertificate, (), (const));
MOCK_METHOD(absl::Span<const std::string>, ipSansPeerCertificate, (), (const));
MOCK_METHOD(absl::Span<const std::string>, ipSansLocalCertificate, (), (const));
MOCK_METHOD(absl::Span<const std::string>, oidsPeerCertificate, (), (const));
MOCK_METHOD(absl::Span<const std::string>, oidsLocalCertificate, (), (const));
MOCK_METHOD(absl::optional<SystemTime>, validFromPeerCertificate, (), (const));
MOCK_METHOD(absl::optional<SystemTime>, expirationPeerCertificate, (), (const));
MOCK_METHOD(const std::string&, sessionId, (), (const));
Expand Down

0 comments on commit 6fba99b

Please sign in to comment.