Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fixed QUIC connection info to expose peer certificate details (digests, SANs, subject, issuer,
serial numbers, PEM encoding and validity dates). Previously all peer certificate accessors on
QUIC connections returned empty values, so for example access log and header formatters for the
peer certificate of upstream HTTP/3 connections produced empty output.
1 change: 1 addition & 0 deletions source/common/quic/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ envoy_cc_library(

envoy_cc_library(
name = "quic_ssl_connection_info_lib",
srcs = envoy_select_enable_http3(["quic_ssl_connection_info.cc"]),
hdrs = envoy_select_enable_http3(["quic_ssl_connection_info.h"]),
external_deps = ["ssl"],
deps = envoy_select_enable_http3([
Expand Down
53 changes: 53 additions & 0 deletions source/common/quic/quic_ssl_connection_info.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include "source/common/quic/quic_ssl_connection_info.h"

#include "openssl/ssl.h"
#include "openssl/x509.h"

namespace Envoy {
namespace Quic {

bssl::UniquePtr<X509> QuicSslConnectionInfo::peerCertificate() const {
STACK_OF(X509)* chain = peerCertificateChain();
if (chain == nullptr || sk_X509_num(chain) == 0) {
return nullptr;
}
return bssl::UpRef(sk_X509_value(chain, 0));
}

STACK_OF(X509)* QuicSslConnectionInfo::peerCertificateChain() const {
if (peer_cert_chain_ != nullptr) {
return peer_cert_chain_.get();
}
// The chain may legitimately not be available yet if queried before the handshake delivered the
// peer certificates; in that case the conversion is retried on the next query.
const STACK_OF(CRYPTO_BUFFER)* certs = SSL_get0_peer_certificates(ssl());
if (certs == nullptr || sk_CRYPTO_BUFFER_num(certs) == 0) {
return nullptr;
}
bssl::UniquePtr<STACK_OF(X509)> chain(sk_X509_new_null());
for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(certs); i++) {
const CRYPTO_BUFFER* buffer = sk_CRYPTO_BUFFER_value(certs, i);
const uint8_t* data = CRYPTO_BUFFER_data(buffer);
bssl::UniquePtr<X509> cert(d2i_X509(nullptr, &data, CRYPTO_BUFFER_len(buffer)));
if (cert == nullptr || !bssl::PushToStack(chain.get(), std::move(cert))) {
// A certificate the TLS stack accepted should always be parseable; treat a malformed chain
// as not presented rather than exposing a partial chain.
return nullptr;
}
}
peer_cert_chain_ = std::move(chain);
return peer_cert_chain_.get();
}

X509* QuicSslConnectionInfo::validatedPeerIssuer() const {
// The whole presented chain is validated for QUIC connections, so the leaf's direct issuer is
// the second element of the chain when present.
STACK_OF(X509)* chain = peerCertificateChain();
if (chain == nullptr || sk_X509_num(chain) < 2) {
return nullptr;
}
return sk_X509_value(chain, 1);
}

} // namespace Quic
} // namespace Envoy
44 changes: 20 additions & 24 deletions source/common/quic/quic_ssl_connection_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,40 +23,36 @@ class QuicSslConnectionInfo : public Extensions::TransportSockets::Tls::Connecti
return session_.GetCryptoStream()->GetSsl();
}

// Extensions::TransportSockets::Tls::ConnectionInfoImplBase
// TODO(#23809) populate those field once we support mutual TLS.
bool peerCertificatePresented() const override { return false; }
const std::string& sha256PeerCertificateDigest() const override { return EMPTY_STRING; }
const std::string& sha1PeerCertificateDigest() const override { return EMPTY_STRING; }
absl::Span<const std::string> uriSanPeerCertificate() const override { return {}; }
const std::string& serialNumberPeerCertificate() const override { return EMPTY_STRING; }
const std::string& issuerPeerCertificate() const override { return EMPTY_STRING; }
const std::string& sha256PeerCertificateIssuerDigest() const override { return EMPTY_STRING; }
const std::string& serialNumberPeerCertificateIssuer() const override { return EMPTY_STRING; }
const std::string& subjectPeerCertificate() const override { return EMPTY_STRING; }
Ssl::ParsedX509NameOptConstRef parsedSubjectPeerCertificate() const override {
return std::nullopt;
}
const std::string& urlEncodedPemEncodedPeerCertificate() const override { return EMPTY_STRING; }
const std::string& pemEncodedPeerCertificate() const override { return EMPTY_STRING; }
const std::string& urlEncodedPemEncodedPeerCertificateChain() const override {
return EMPTY_STRING;
}
absl::Span<const std::string> pemEncodedPeerCertificateChain() const override { return {}; }
absl::Span<const std::string> dnsSansPeerCertificate() const override { return {}; }
std::optional<SystemTime> validFromPeerCertificate() const override { return std::nullopt; }
std::optional<SystemTime> expirationPeerCertificate() const override { return std::nullopt; }
// QUIC SSL object doesn't cache local certs after the handshake.
X509* validatedPeerIssuer() const override;

// QUIC SSL object doesn't cache local certs after the handshake, and the X509-based local
// certificate getters are not usable on its CRYPTO_BUFFER-based SSL object.
// TODO(danzh) cache these fields during cert chain retrieval.
const std::string& subjectLocalCertificate() const override { return EMPTY_STRING; }
absl::Span<const std::string> uriSanLocalCertificate() const override { return {}; }
absl::Span<const std::string> dnsSansLocalCertificate() const override { return {}; }
absl::Span<const std::string> ipSansLocalCertificate() const override { return {}; }
absl::Span<const std::string> emailSansLocalCertificate() const override { return {}; }
absl::Span<const std::string> othernameSansLocalCertificate() const override { return {}; }
absl::Span<const std::string> oidsLocalCertificate() const override { return {}; }

void onCertValidated() { cert_validated_ = true; };

protected:
// Extensions::TransportSockets::Tls::ConnectionInfoImplBase
// QUIC's SSL object uses the CRYPTO_BUFFER-based method, so the default X509-based peer
// certificate getters are not usable on it. Convert the CRYPTO_BUFFER peer chain to X509 once
// and serve the base class accessors from the converted chain.
bssl::UniquePtr<X509> peerCertificate() const override;
STACK_OF(X509)* peerCertificateChain() const override;

private:
quic::QuicSession& session_;
bool cert_validated_{false};
// The peer certificate chain converted from the CRYPTO_BUFFER-based SSL object, lazily created
// and cached. Null if conversion hasn't happened, was queried before the handshake delivered
// the peer chain, or failed.
mutable bssl::UniquePtr<STACK_OF(X509)> peer_cert_chain_;
};

} // namespace Quic
Expand Down
Loading