Skip to content

Commit

Permalink
Add the ability to forward a base64 encoded cert in the xfcc header. (#…
Browse files Browse the repository at this point in the history
…2307)

Signed-off-by: William Thurston <[email protected]>
  • Loading branch information
jhspaybar authored and mattklein123 committed Jan 31, 2018
1 parent c360fad commit d89e2ed
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 44 deletions.
28 changes: 17 additions & 11 deletions include/envoy/ssl/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,45 @@ class Connection {
virtual ~Connection() {}

/**
* @return whether the peer certificate is presented.
* @return bool whether the peer certificate is presented.
**/
virtual bool peerCertificatePresented() const PURE;

/**
* @return the URI in the SAN feld of the local certificate. Returns "" if there is no local
* certificate, or no SAN field, or no URI.
* @return std::string the URI in the SAN feld of the local certificate. Returns "" if there is no
* local certificate, or no SAN field, or no URI.
**/
virtual std::string uriSanLocalCertificate() PURE;

/**
* @return the subject field of the local certificate in RFC 2253 format. Returns "" if there is
* no local certificate, or no subject.
* @return std::string the subject field of the local certificate in RFC 2253 format. Returns ""
* if there is no local certificate, or no subject.
**/
virtual std::string subjectLocalCertificate() const PURE;

/**
* @return the SHA256 digest of the peer certificate. Returns "" if there is no peer certificate
* which can happen in TLS (non mTLS) connections.
* @return std::string the SHA256 digest of the peer certificate. Returns "" if there is no peer
* certificate which can happen in TLS (non mTLS) connections.
*/
virtual std::string sha256PeerCertificateDigest() PURE;

/**
* @return the subject field of the peer certificate in RFC 2253 format. Returns "" if there is
* no peer certificate, or no subject.
* @return std::string the subject field of the peer certificate in RFC 2253 format. Returns "" if
* there is no peer certificate, or no subject.
**/
virtual std::string subjectPeerCertificate() const PURE;

/**
* @return the URI in the SAN field of the peer certificate. Returns "" if there is no peer
* certificate, or no SAN field, or no URI.
* @return std::string the URI in the SAN field of the peer certificate. Returns "" if there is no
* peer certificate, or no SAN field, or no URI.
**/
virtual std::string uriSanPeerCertificate() PURE;

/**
* @return std::string the URL-encoded PEM-encoded representation of the peer certificate. Returns
* "" if there is no peer certificate or encoding fails.
**/
virtual std::string urlEncodedPemEncodedPeerCertificate() const PURE;
};

} // namespace Ssl
Expand Down
2 changes: 1 addition & 1 deletion source/common/http/conn_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ enum class ForwardClientCertType {
* Configuration for the fields of the client cert, used for populating the current client cert
* information to the next hop.
*/
enum class ClientCertDetailsType { Subject, SAN };
enum class ClientCertDetailsType { Cert, Subject, SAN };

/**
* Abstract configuration for the connection manager.
Expand Down
17 changes: 13 additions & 4 deletions source/common/http/conn_manager_utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,13 @@ void ConnectionManagerUtility::mutateXfccRequestHeader(Http::HeaderMap& request_
// the XFCC header.
if (config.forwardClientCert() == Http::ForwardClientCertType::AppendForward ||
config.forwardClientCert() == Http::ForwardClientCertType::SanitizeSet) {
if (!connection.ssl()->uriSanLocalCertificate().empty()) {
client_cert_details.push_back("By=" + connection.ssl()->uriSanLocalCertificate());
const std::string uri_san_local_cert = connection.ssl()->uriSanLocalCertificate();
if (!uri_san_local_cert.empty()) {
client_cert_details.push_back("By=" + uri_san_local_cert);
}
if (!connection.ssl()->sha256PeerCertificateDigest().empty()) {
client_cert_details.push_back("Hash=" + connection.ssl()->sha256PeerCertificateDigest());
const std::string cert_digest = connection.ssl()->sha256PeerCertificateDigest();
if (!cert_digest.empty()) {
client_cert_details.push_back("Hash=" + cert_digest);
}
for (const auto& detail : config.setCurrentClientCertDetails()) {
switch (detail) {
Expand All @@ -205,6 +207,13 @@ void ConnectionManagerUtility::mutateXfccRequestHeader(Http::HeaderMap& request_
// Currently, we only support a single SAN field with URI type.
// The "SAN" key still exists even if the SAN is empty.
client_cert_details.push_back("SAN=" + connection.ssl()->uriSanPeerCertificate());
break;
case Http::ClientCertDetailsType::Cert:
const std::string peer_cert = connection.ssl()->urlEncodedPemEncodedPeerCertificate();
if (!peer_cert.empty()) {
client_cert_details.push_back("Cert=\"" + peer_cert + "\"");
}
break;
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions source/common/ssl/ssl_socket.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "common/common/hex.h"
#include "common/http/headers.h"

#include "absl/strings/str_replace.h"
#include "openssl/err.h"
#include "openssl/x509v3.h"

Expand Down Expand Up @@ -227,6 +228,26 @@ std::string SslSocket::sha256PeerCertificateDigest() {
return Hex::encode(computed_hash);
}

// TODO: Cache this result and possibly other methods in this class
std::string SslSocket::urlEncodedPemEncodedPeerCertificate() const {
bssl::UniquePtr<X509> cert(SSL_get_peer_certificate(ssl_.get()));
if (!cert) {
return "";
}

bssl::UniquePtr<BIO> buf(BIO_new(BIO_s_mem()));
RELEASE_ASSERT(buf != nullptr);
RELEASE_ASSERT(PEM_write_bio_X509(buf.get(), cert.get()) == 1);
const uint8_t* output;
size_t length;
RELEASE_ASSERT(BIO_mem_contents(buf.get(), &output, &length) == 1);
std::string pem = std::string(reinterpret_cast<const char*>(output), length);
// URL encoding shortcut
absl::StrReplaceAll({{"\n", "%0A"}, {" ", "%20"}, {"+", "%2B"}, {"/", "%2F"}, {"=", "%3D"}},
&pem);
return pem;
}

std::string SslSocket::uriSanPeerCertificate() {
bssl::UniquePtr<X509> cert(SSL_get_peer_certificate(ssl_.get()));
if (!cert) {
Expand Down
1 change: 1 addition & 0 deletions source/common/ssl/ssl_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class SslSocket : public Network::TransportSocket,
std::string subjectPeerCertificate() const override;
std::string subjectLocalCertificate() const override;
std::string uriSanPeerCertificate() override;
std::string urlEncodedPemEncodedPeerCertificate() const override;

// Network::TransportSocket
void setTransportSocketCallbacks(Network::TransportSocketCallbacks& callbacks) override;
Expand Down
3 changes: 3 additions & 0 deletions source/server/config/network/http_connection_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig(
if (PROTOBUF_GET_WRAPPED_OR_DEFAULT(set_current_client_cert_details, san, false)) {
set_current_client_cert_details_.push_back(Http::ClientCertDetailsType::SAN);
}
if (set_current_client_cert_details.cert()) {
set_current_client_cert_details_.push_back(Http::ClientCertDetailsType::Cert);
}

if (config.has_add_user_agent() && config.add_user_agent().value()) {
user_agent_.value(context_.localInfo().clusterName());
Expand Down
22 changes: 13 additions & 9 deletions test/common/http/conn_manager_utility_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -485,14 +485,16 @@ TEST_F(ConnectionManagerUtilityTest, MtlsForwardOnlyClientCert) {
TEST_F(ConnectionManagerUtilityTest, MtlsAppendForwardClientCert) {
NiceMock<Ssl::MockConnection> ssl;
ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(true));
EXPECT_CALL(ssl, uriSanLocalCertificate()).Times(2).WillRepeatedly(Return("test://foo.com/be"));
EXPECT_CALL(ssl, sha256PeerCertificateDigest()).Times(2).WillRepeatedly(Return("abcdefg"));
EXPECT_CALL(ssl, uriSanLocalCertificate()).WillOnce(Return("test://foo.com/be"));
EXPECT_CALL(ssl, sha256PeerCertificateDigest()).WillOnce(Return("abcdefg"));
EXPECT_CALL(ssl, uriSanPeerCertificate()).WillOnce(Return("test://foo.com/fe"));
EXPECT_CALL(ssl, urlEncodedPemEncodedPeerCertificate()).WillOnce(Return("%3D%3Dabc%0Ade%3D"));
ON_CALL(connection_, ssl()).WillByDefault(Return(&ssl));
ON_CALL(config_, forwardClientCert())
.WillByDefault(Return(Http::ForwardClientCertType::AppendForward));
std::vector<Http::ClientCertDetailsType> details = std::vector<Http::ClientCertDetailsType>();
details.push_back(Http::ClientCertDetailsType::SAN);
details.push_back(Http::ClientCertDetailsType::Cert);
ON_CALL(config_, setCurrentClientCertDetails()).WillByDefault(ReturnRef(details));
TestHeaderMapImpl headers{
{"x-forwarded-client-cert", "By=test://foo.com/fe;SAN=test://bar.com/be"}};
Expand All @@ -501,7 +503,7 @@ TEST_F(ConnectionManagerUtilityTest, MtlsAppendForwardClientCert) {
callMutateRequestHeaders(headers, Protocol::Http2));
EXPECT_TRUE(headers.has("x-forwarded-client-cert"));
EXPECT_EQ("By=test://foo.com/fe;SAN=test://bar.com/be,By=test://foo.com/"
"be;Hash=abcdefg;SAN=test://foo.com/fe",
"be;Hash=abcdefg;SAN=test://foo.com/fe;Cert=\"%3D%3Dabc%0Ade%3D\"",
headers.get_("x-forwarded-client-cert"));
}

Expand All @@ -513,7 +515,7 @@ TEST_F(ConnectionManagerUtilityTest, MtlsAppendForwardClientCertLocalSanEmpty) {
NiceMock<Ssl::MockConnection> ssl;
ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(true));
EXPECT_CALL(ssl, uriSanLocalCertificate()).WillOnce(Return(""));
EXPECT_CALL(ssl, sha256PeerCertificateDigest()).Times(2).WillRepeatedly(Return("abcdefg"));
EXPECT_CALL(ssl, sha256PeerCertificateDigest()).WillOnce(Return("abcdefg"));
EXPECT_CALL(ssl, uriSanPeerCertificate()).WillOnce(Return("test://foo.com/fe"));
ON_CALL(connection_, ssl()).WillByDefault(Return(&ssl));
ON_CALL(config_, forwardClientCert())
Expand All @@ -539,17 +541,19 @@ TEST_F(ConnectionManagerUtilityTest, MtlsAppendForwardClientCertLocalSanEmpty) {
TEST_F(ConnectionManagerUtilityTest, MtlsSanitizeSetClientCert) {
NiceMock<Ssl::MockConnection> ssl;
ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(true));
EXPECT_CALL(ssl, uriSanLocalCertificate()).Times(2).WillRepeatedly(Return("test://foo.com/be"));
EXPECT_CALL(ssl, sha256PeerCertificateDigest()).Times(2).WillRepeatedly(Return("abcdefg"));
EXPECT_CALL(ssl, uriSanLocalCertificate()).WillOnce(Return("test://foo.com/be"));
EXPECT_CALL(ssl, sha256PeerCertificateDigest()).WillOnce(Return("abcdefg"));
EXPECT_CALL(ssl, subjectPeerCertificate())
.WillOnce(Return("/C=US/ST=CA/L=San Francisco/OU=Lyft/CN=test.lyft.com"));
EXPECT_CALL(ssl, uriSanPeerCertificate()).WillOnce(Return("test://foo.com/fe"));
EXPECT_CALL(ssl, urlEncodedPemEncodedPeerCertificate()).WillOnce(Return("abcde="));
ON_CALL(connection_, ssl()).WillByDefault(Return(&ssl));
ON_CALL(config_, forwardClientCert())
.WillByDefault(Return(Http::ForwardClientCertType::SanitizeSet));
std::vector<Http::ClientCertDetailsType> details = std::vector<Http::ClientCertDetailsType>();
details.push_back(Http::ClientCertDetailsType::Subject);
details.push_back(Http::ClientCertDetailsType::SAN);
details.push_back(Http::ClientCertDetailsType::Cert);
ON_CALL(config_, setCurrentClientCertDetails()).WillByDefault(ReturnRef(details));
TestHeaderMapImpl headers{
{"x-forwarded-client-cert", "By=test://foo.com/fe;SAN=test://bar.com/be"}};
Expand All @@ -558,7 +562,7 @@ TEST_F(ConnectionManagerUtilityTest, MtlsSanitizeSetClientCert) {
callMutateRequestHeaders(headers, Protocol::Http2));
EXPECT_TRUE(headers.has("x-forwarded-client-cert"));
EXPECT_EQ("By=test://foo.com/be;Hash=abcdefg;Subject=\"/C=US/ST=CA/L=San "
"Francisco/OU=Lyft/CN=test.lyft.com\";SAN=test://foo.com/fe",
"Francisco/OU=Lyft/CN=test.lyft.com\";SAN=test://foo.com/fe;Cert=\"abcde=\"",
headers.get_("x-forwarded-client-cert"));
}

Expand All @@ -569,8 +573,8 @@ TEST_F(ConnectionManagerUtilityTest, MtlsSanitizeSetClientCert) {
TEST_F(ConnectionManagerUtilityTest, MtlsSanitizeSetClientCertPeerSanEmpty) {
NiceMock<Ssl::MockConnection> ssl;
ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(true));
EXPECT_CALL(ssl, uriSanLocalCertificate()).Times(2).WillRepeatedly(Return("test://foo.com/be"));
EXPECT_CALL(ssl, sha256PeerCertificateDigest()).Times(2).WillRepeatedly(Return("abcdefg"));
EXPECT_CALL(ssl, uriSanLocalCertificate()).WillOnce(Return("test://foo.com/be"));
EXPECT_CALL(ssl, sha256PeerCertificateDigest()).WillOnce(Return("abcdefg"));
EXPECT_CALL(ssl, subjectPeerCertificate())
.WillOnce(Return("/C=US/ST=CA/L=San Francisco/OU=Lyft/CN=test.lyft.com"));
EXPECT_CALL(ssl, uriSanPeerCertificate()).WillOnce(Return(""));
Expand Down
Loading

0 comments on commit d89e2ed

Please sign in to comment.