Skip to content

Commit 3414577

Browse files
Alex Snastmeta-codesync[bot]
authored andcommitted
Add verifyContext to CertificateIdentityVerifier
Summary: This diff adds a new `verifyContext()` method to the `CertificateIdentityVerifier` interface that is called during OpenSSL's certificate verification callback for each certificate in the chain. This allows implementers to inspect and verify the entire certificate chain by accessing the `X509_STORE_CTX` directly. **Key Changes:** - Added virtual `verifyContext(bool preverifyOk, X509_STORE_CTX& ctx)` method to `CertificateIdentityVerifier` with a default pass-through implementation - `verifyContext()` is called **for each certificate in the chain** (root → intermediate(s) → leaf) during the SSL handshake - Modified `AsyncSSLSocket::sslVerifyCallback()` to call `verifyContext()` after `handshakeVer()` and before `verifyLeaf()` - `verifyContext()` updates the verification state without short-circuiting - unlike `handshakeVer()`, it allows processing to continue even when changing the result - If `verifyContext()` returns `false`, verification fails and `verifyLeaf()` will NOT be called - If `verifyContext()` returns `true`, verification succeeds and `verifyLeaf()` may be called for the leaf certificate at depth 0 - Updated `MockCertificateIdentityVerifier` to include `MOCK_METHOD` for the new method - Updated existing tests to mock `verifyContext()` calls and verify proper call ordering - Comprehensively updated documentation to accurately describe the verification flow and `verifyContext()` behavior **Why This Change:** This provides more flexibility for custom certificate verification by allowing access to the full certificate chain context. Some verification scenarios require: - Inspecting the entire chain (not just the leaf certificate) - Accessing OpenSSL's `X509_STORE_CTX` for advanced validation - Performing verification at different chain depths - Overriding OpenSSL's verification decisions (can rescue failed OpenSSL verifications or fail successful ones) - Short-circuiting verification early based on chain-level policies The method is marked `noexcept` and allows the verification flow to continue, enabling `verifyLeaf()` to be called for additional leaf certificate validation even when `verifyContext()` changes the result. **Verification Flow:** 1. OpenSSL performs internal certificate chain validation 2. HandshakeCB's `handshakeVer()` is invoked (if registered) - Short-circuits if it changes the result 3. CertificateIdentityVerifier's `verifyContext()` is invoked (if registered) - Called once for each cert in chain - Updates verification state without short-circuiting - Can override OpenSSL's decision in either direction 4. CertificateIdentityVerifier's `verifyLeaf()` is invoked (if registered) - Called only for leaf certificate at depth 0 - Only if all previous steps succeeded (preverifyOk is true) Reviewed By: yfeldblum Differential Revision: D87812821 fbshipit-source-id: a1ba21bb643c497c9da67f6e7deda75a05d8da0c
1 parent 8c3e1ad commit 3414577

File tree

5 files changed

+232
-20
lines changed

5 files changed

+232
-20
lines changed

third-party/folly/src/folly/io/async/AsyncSSLSocket.cpp

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,24 +1996,36 @@ int AsyncSSLSocket::sslVerifyCallback(
19961996

19971997
if (self->handshakeCallback_) {
19981998
int callbackOk =
1999-
(self->handshakeCallback_->handshakeVer(self, preverifyOk, x509Ctx))
1999+
self->handshakeCallback_->handshakeVer(self, preverifyOk, x509Ctx)
20002000
? 1
20012001
: 0;
20022002

20032003
if (preverifyOk != callbackOk) {
20042004
// HandshakeCB overwrites result from OpenSSL. One way or another, do not
2005-
// call CertificateIdentityVerifier.
2005+
// call verifyLeaf.
20062006
return callbackOk;
20072007
}
20082008
}
20092009

2010+
// verifyContext can override the OpenSSL verification result. Unlike
2011+
// handshakeVer, it doesn't return early - allowing verifyLeaf to be called
2012+
// for the leaf certificate even if verifyContext changes the result.
2013+
if (self->certificateIdentityVerifier_) {
2014+
preverifyOk =
2015+
self->certificateIdentityVerifier_->verifyContext(preverifyOk, *x509Ctx)
2016+
? 1
2017+
: 0;
2018+
}
2019+
20102020
if (!preverifyOk) {
2011-
// OpenSSL verification failure, no need to call CertificateIdentityVerifier
2021+
// Verification failed (either OpenSSL or verifyContext), no need to call
2022+
// verifyLeaf.
20122023
return 0;
20132024
}
20142025

2015-
// only invoke the CertificateIdentityVerifier for the leaf certificate and
2016-
// only if OpenSSL's preverify and the HandshakeCB's handshakeVer succeeded
2026+
// only invoke the verifyLeaf callback for the leaf certificate and
2027+
// only if all previous verification steps succeeded (OpenSSL, handshakeVer,
2028+
// and verifyContext)
20172029

20182030
int currentDepth = X509_STORE_CTX_get_error_depth(x509Ctx);
20192031
if (currentDepth != 0 || self->certificateIdentityVerifier_ == nullptr) {

third-party/folly/src/folly/io/async/AsyncSSLSocket.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,12 @@ class AsyncSSLSocket : public AsyncSocket {
198198
* Struct to consolidate constructor arguments.
199199
*/
200200
struct Options {
201-
// If this verifier is set, it's used during the TLS handshake. It will be
202-
// invoked to verify the peer's end-entity leaf certificate after OpenSSL's
203-
// chain validation and after calling the HandshakeCB's handshakeVer() and
204-
// only if these are successful.
201+
// If this verifier is set, it's used during the TLS handshake. First,
202+
// verifyContext() is called during OpenSSL's certificate verification
203+
// callback for each certificate in the chain, after HandshakeCB's
204+
// handshakeVer() if set. Then, verifyLeaf() is invoked to verify the
205+
// peer's end-entity leaf certificate, but only if OpenSSL's chain
206+
// validation, handshakeVer(), and verifyContext() all succeeded.
205207
std::shared_ptr<CertificateIdentityVerifier> verifier;
206208
bool deferSecurityNegotiation{};
207209
bool isServer{};

third-party/folly/src/folly/io/async/CertificateIdentityVerifier.h

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,27 @@
1616

1717
#pragma once
1818

19-
#include <folly/Try.h>
20-
#include <folly/Unit.h>
19+
#include <memory>
20+
2121
#include <folly/io/async/AsyncTransportCertificate.h>
22+
#include <folly/portability/OpenSSL.h>
2223

2324
namespace folly {
2425

2526
/**
2627
* CertificateIdentityVerifier implementations are used during TLS handshakes to
27-
* extract and verify end-entity certificate identities. AsyncSSLSocket first
28-
* performs OpenSSL certificate chain validation and then invokes any registered
29-
* HandshakeCB's handshakeVer() method. Only if both of these succeed, it then
30-
* calls the verifyLeaf method to further verify a peer's certificate.
28+
* extract and verify end-entity certificate identities.
29+
*
30+
* During TLS handshake, AsyncSSLSocket performs verification in this order:
31+
* 1. OpenSSL performs internal certificate chain validation
32+
* 2. HandshakeCB's handshakeVer() is invoked (if registered)
33+
* 3. CertificateIdentityVerifier's verifyContext() is invoked (if registered)
34+
* - Called once for EACH certificate in the chain (root → intermediate →
35+
* leaf)
36+
* - Provides access to the full X509_STORE_CTX for chain inspection
37+
* 4. CertificateIdentityVerifier's verifyLeaf() is invoked (if registered)
38+
* - Called ONLY for the leaf certificate at depth 0
39+
* - Only if all previous verification steps succeeded
3140
*
3241
* TLS connections must pass all these checks in order for an AsyncSSLSocket's
3342
* registered HandshakeCB to receive handshakeSuc().
@@ -39,6 +48,50 @@ class CertificateIdentityVerifier {
3948
public:
4049
virtual ~CertificateIdentityVerifier() = default;
4150

51+
/**
52+
* AsyncSSLSocket calls verifyContext() during the OpenSSL certificate
53+
* verification callback for EACH certificate in the chain (typically
54+
* root → intermediate(s) → leaf).
55+
*
56+
* This method allows custom validation logic beyond OpenSSL's internal
57+
* consistency checks. The returned value becomes the new verification state:
58+
* - If this method returns false, verification fails and verifyLeaf() will
59+
* NOT be called
60+
* - If this method returns true, verification succeeds (and verifyLeaf()
61+
* may be called for the leaf certificate at depth 0)
62+
*
63+
* This method can override OpenSSL's verification result. For example:
64+
* - If OpenSSL fails (preverifyOk=false) but verifyContext returns true,
65+
* verification succeeds and verifyLeaf() may be called
66+
* - If OpenSSL succeeds (preverifyOk=true) but verifyContext returns false,
67+
* verification fails and verifyLeaf() will NOT be called
68+
*
69+
* Unlike HandshakeCB::handshakeVer(), this method does not short-circuit
70+
* when it changes the result - it updates the verification state and allows
71+
* processing to continue.
72+
*
73+
* The default implementation returns preverifyOk unchanged (pass-through),
74+
* deferring to OpenSSL's verification decision.
75+
*
76+
* IMPORTANT: This method is called multiple times per handshake - once for
77+
* each certificate in the chain. Use X509_STORE_CTX_get_error_depth(ctx)
78+
* to determine the depth of the certificate being verified.
79+
*
80+
* See the passages on verify_callback in SSL_CTX_set_verify(3) for more
81+
* details.
82+
*
83+
* @param preverifyOk Result of OpenSSL's internal verification checks
84+
* @param ctx OpenSSL verification context containing the certificate chain
85+
* and verification state. Use
86+
* X509_STORE_CTX_get_current_cert(&ctx) to access the certificate being
87+
* verified at this depth.
88+
* @return true if the certificate context is valid, false otherwise
89+
*/
90+
virtual bool verifyContext(
91+
bool preverifyOk, [[maybe_unused]] X509_STORE_CTX& ctx) const noexcept {
92+
return preverifyOk;
93+
}
94+
4295
/**
4396
* AsyncSSLSocket calls verifyLeaf() during a TLS handshake after chain
4497
* verification, only if certificate verification is required/requested,

third-party/folly/src/folly/io/async/test/AsyncSSLSocketTest.cpp

Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,12 +1485,18 @@ TEST(AsyncSSLSocketTest, SSLCertificateIdentityVerifierReturns) {
14851485
std::shared_ptr<MockCertificateIdentityVerifier> verifier =
14861486
std::make_shared<MockCertificateIdentityVerifier>();
14871487

1488+
// expecting context verification to be called first
1489+
auto&& verifyContext =
1490+
EXPECT_CALL(*verifier, verifyContext(true, _))
1491+
.Times(AtLeast(1))
1492+
.WillRepeatedly(Return(true));
14881493
// expecting to only verify once, with the leaf certificate
14891494
// (kTestCert)
14901495
EXPECT_CALL(
14911496
*verifier,
14921497
verifyLeaf(Property(
14931498
&AsyncTransportCertificate::getIdentity, StrEq("Asox Company"))))
1499+
.After(verifyContext)
14941500
.WillOnce(Return(ByMove(
14951501
std::make_unique<folly::ssl::BasicTransportCertificate>(
14961502
"Asox Company", readCertFromFile(kTestCert)))));
@@ -1542,11 +1548,18 @@ TEST(AsyncSSLSocketTest, SSLCertificateIdentityVerifierFailsToConnect) {
15421548
// Throw an exception on verification failure
15431549
CertificateIdentityVerifierException failed{"a failed test reason"};
15441550

1551+
// expecting context verification to be called first
1552+
auto&& verifyContext =
1553+
EXPECT_CALL(*verifier, verifyContext(true, _))
1554+
.Times(AtLeast(1))
1555+
.WillRepeatedly(Return(true));
1556+
15451557
// expecting to only verify once, with the leaf certificate (kTestCert)
15461558
EXPECT_CALL(
15471559
*verifier,
15481560
verifyLeaf(Property(
15491561
&AsyncTransportCertificate::getIdentity, StrEq("Asox Company"))))
1562+
.After(verifyContext)
15501563
.WillOnce(Throw(failed));
15511564

15521565
AsyncSSLSocket::Options opts;
@@ -1562,6 +1575,119 @@ TEST(AsyncSSLSocketTest, SSLCertificateIdentityVerifierFailsToConnect) {
15621575
socket->close();
15631576
}
15641577

1578+
/**
1579+
* Verify that the client fails to connect during handshake when
1580+
* CertificateIdentityVerifier::verifyContext returns false,
1581+
* and that verifyLeaf is not invoked when verifyContext fails.
1582+
*/
1583+
TEST(AsyncSSLSocketTest, SSLCertificateIdentityVerifierContextFailure) {
1584+
EventBase eventBase;
1585+
auto clientCtx = std::make_shared<folly::SSLContext>();
1586+
auto serverCtx = std::make_shared<folly::SSLContext>();
1587+
getctx(clientCtx, serverCtx);
1588+
// the client socket will default to USE_CTX, so set VERIFY here
1589+
clientCtx->setVerificationOption(SSLContext::SSLVerifyPeerEnum::VERIFY);
1590+
// load root certificate
1591+
clientCtx->loadTrustedCertificates(find_resource(kTestCA).c_str());
1592+
1593+
// prepare a basic server (callbacks have a few EXPECTS to fulfill)
1594+
ReadCallback readCallback(nullptr);
1595+
// expects a failed handshake
1596+
HandshakeCallback handshakeCallback(
1597+
&readCallback, HandshakeCallback::ExpectType::EXPECT_ERROR);
1598+
SSLServerAcceptCallback acceptCallback(&handshakeCallback);
1599+
TestSSLServer server(&acceptCallback, serverCtx);
1600+
1601+
std::shared_ptr<StrictMock<MockCertificateIdentityVerifier>> verifier =
1602+
std::make_shared<StrictMock<MockCertificateIdentityVerifier>>();
1603+
1604+
// verifyContext should be called and return false to fail verification
1605+
EXPECT_CALL(*verifier, verifyContext(true, _)).WillOnce(Return(false));
1606+
1607+
// verifyLeaf should NOT be called since verifyContext failed
1608+
EXPECT_CALL(*verifier, verifyLeaf).Times(0);
1609+
1610+
AsyncSSLSocket::Options opts;
1611+
opts.verifier = std::move(verifier);
1612+
1613+
// connect to server and handshake
1614+
AsyncSSLSocket::UniquePtr socket(
1615+
new AsyncSSLSocket(clientCtx, &eventBase, std::move(opts)));
1616+
socket->connect(nullptr, server.getAddress(), 0);
1617+
1618+
eventBase.loop();
1619+
1620+
socket->close();
1621+
}
1622+
1623+
/**
1624+
* Verify that verifyContext has access to the X509_STORE_CTX
1625+
* and can inspect the certificate chain.
1626+
*/
1627+
TEST(AsyncSSLSocketTest, SSLCertificateIdentityVerifierContextInspectsChain) {
1628+
EventBase eventBase;
1629+
auto clientCtx = std::make_shared<folly::SSLContext>();
1630+
auto serverCtx = std::make_shared<folly::SSLContext>();
1631+
getctx(clientCtx, serverCtx);
1632+
// the client socket will default to USE_CTX, so set VERIFY here
1633+
clientCtx->setVerificationOption(SSLContext::SSLVerifyPeerEnum::VERIFY);
1634+
// load root certificate
1635+
clientCtx->loadTrustedCertificates(find_resource(kTestCA).c_str());
1636+
1637+
// prepare a basic server (callbacks have a few EXPECTS to fulfill)
1638+
ReadCallback readCallback(nullptr);
1639+
// expects successful handshake
1640+
HandshakeCallback handshakeCallback(&readCallback);
1641+
SSLServerAcceptCallback acceptCallback(&handshakeCallback);
1642+
TestSSLServer server(&acceptCallback, serverCtx);
1643+
1644+
std::shared_ptr<MockCertificateIdentityVerifier> verifier =
1645+
std::make_shared<MockCertificateIdentityVerifier>();
1646+
1647+
// verifyContext should be called and have access to X509_STORE_CTX
1648+
auto&& verifyContext =
1649+
EXPECT_CALL(*verifier, verifyContext(true, _))
1650+
.Times(AtLeast(1))
1651+
.WillRepeatedly(WithArg<1>([](X509_STORE_CTX& ctx) {
1652+
// Verify we can access the cert chain
1653+
X509* cert = X509_STORE_CTX_get_current_cert(&ctx);
1654+
EXPECT_NE(cert, nullptr);
1655+
1656+
// Verify we can get the chain depth
1657+
int depth = X509_STORE_CTX_get_error_depth(&ctx);
1658+
EXPECT_GE(depth, 0);
1659+
1660+
return true;
1661+
}));
1662+
1663+
// expecting to verify leaf certificate after context verification
1664+
EXPECT_CALL(
1665+
*verifier,
1666+
verifyLeaf(Property(
1667+
&AsyncTransportCertificate::getIdentity, StrEq("Asox Company"))))
1668+
.After(verifyContext)
1669+
.WillOnce(Return(ByMove(
1670+
std::make_unique<folly::ssl::BasicTransportCertificate>(
1671+
"Asox Company", readCertFromFile(kTestCert)))));
1672+
1673+
AsyncSSLSocket::Options opts;
1674+
opts.verifier = std::move(verifier);
1675+
1676+
// connect to server and handshake
1677+
AsyncSSLSocket::UniquePtr socket(
1678+
new AsyncSSLSocket(clientCtx, &eventBase, std::move(opts)));
1679+
socket->connect(nullptr, server.getAddress(), 0);
1680+
1681+
// write to satisfy server ReadCallback EXPECTs
1682+
std::array<uint8_t, 128> buf;
1683+
memset(buf.data(), 'a', buf.size());
1684+
socket->write(nullptr, buf.data(), buf.size());
1685+
1686+
eventBase.loop();
1687+
1688+
socket->close();
1689+
}
1690+
15651691
/**
15661692
* Verify that the client's CertificateIdentityVerifier is not invoked if
15671693
* OpenSSL's verification fails. (With no HandshakeCB.)
@@ -1583,9 +1709,10 @@ TEST(AsyncSSLSocketTest, SSLCertificateIdentityVerifierNotInvokedX509Failure) {
15831709
SSLServerAcceptCallback acceptCallback(&handshakeCallback);
15841710
TestSSLServer server(&acceptCallback, serverCtx);
15851711

1586-
// should not get called
1712+
// do not override verification result
15871713
std::shared_ptr<StrictMock<MockCertificateIdentityVerifier>> verifier =
15881714
std::make_shared<StrictMock<MockCertificateIdentityVerifier>>();
1715+
EXPECT_CALL(*verifier, verifyContext(false, _)).WillOnce(Return(false));
15891716

15901717
AsyncSSLSocket::Options opts;
15911718
opts.verifier = std::move(verifier);
@@ -1622,9 +1749,12 @@ TEST(
16221749
AsyncSocket::UniquePtr rawClient(new AsyncSocket(&eventBase, fds[0]));
16231750
AsyncSocket::UniquePtr rawServer(new AsyncSocket(&eventBase, fds[1]));
16241751

1625-
// should not be invoked
1752+
// should be invoked to only verify context
16261753
std::shared_ptr<StrictMock<MockCertificateIdentityVerifier>> verifier =
16271754
std::make_shared<StrictMock<MockCertificateIdentityVerifier>>();
1755+
EXPECT_CALL(*verifier, verifyContext)
1756+
.Times(AtLeast(1))
1757+
.WillRepeatedly(Return(true));
16281758

16291759
AsyncSSLSocket::Options clientOpts;
16301760
clientOpts.verifier = verifier;
@@ -1645,14 +1775,14 @@ TEST(
16451775
// to be considered as unsuccessful
16461776
EXPECT_CALL(clientHandshakeCB, handshakeVerImpl(clientSock.get(), true, _))
16471777
.Times(AtLeast(1))
1648-
.WillRepeatedly(Invoke([&](auto&&, bool preverifyOk, auto&& ctx) {
1778+
.WillRepeatedly([&](auto&&, bool preverifyOk, auto&& ctx) {
16491779
auto currentDepth = X509_STORE_CTX_get_error_depth(ctx);
16501780
if (currentDepth == 0) {
16511781
EXPECT_TRUE(preverifyOk);
16521782
return false;
16531783
}
16541784
return preverifyOk;
1655-
}));
1785+
});
16561786

16571787
// failure callback to verify handshake failed
16581788
EXPECT_CALL(clientHandshakeCB, handshakeErrImpl(clientSock.get(), _));
@@ -1695,19 +1825,29 @@ TEST(AsyncSSLSocketTest, SSLCertificateIdentityVerifierSucceedsOnServer) {
16951825
// client and server verifiers should verify only once each
16961826
std::shared_ptr<MockCertificateIdentityVerifier> clientVerifier =
16971827
std::make_shared<MockCertificateIdentityVerifier>();
1828+
auto&& clientVerifyContext =
1829+
EXPECT_CALL(*clientVerifier, verifyContext(true, _))
1830+
.Times(AtLeast(1))
1831+
.WillRepeatedly(Return(true));
16981832
EXPECT_CALL(
16991833
*clientVerifier,
17001834
verifyLeaf(Property(
17011835
&AsyncTransportCertificate::getIdentity, StrEq("Asox Company"))))
1836+
.After(clientVerifyContext)
17021837
.WillOnce(Return(ByMove(
17031838
std::make_unique<folly::ssl::BasicTransportCertificate>(
17041839
"Asox Company", readCertFromFile(kTestCert)))));
17051840
std::shared_ptr<StrictMock<MockCertificateIdentityVerifier>> serverVerifier =
17061841
std::make_shared<StrictMock<MockCertificateIdentityVerifier>>();
1842+
auto&& serverVerifyContext =
1843+
EXPECT_CALL(*serverVerifier, verifyContext(true, _))
1844+
.Times(AtLeast(1))
1845+
.WillRepeatedly(Return(true));
17071846
EXPECT_CALL(
17081847
*serverVerifier,
17091848
verifyLeaf(Property(
17101849
&AsyncTransportCertificate::getIdentity, StrEq("Asox Company"))))
1850+
.After(serverVerifyContext)
17111851
.WillOnce(Return(ByMove(
17121852
std::make_unique<folly::ssl::BasicTransportCertificate>(
17131853
"Asox Company", readCertFromFile(kTestCert)))));

third-party/folly/src/folly/io/async/test/AsyncSSLSocketTest.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,11 +495,16 @@ class EmptyReadCallback : public ReadCallback {
495495

496496
class MockCertificateIdentityVerifier : public CertificateIdentityVerifier {
497497
public:
498+
MOCK_METHOD(
499+
bool,
500+
verifyContext,
501+
(bool, X509_STORE_CTX&),
502+
(const, noexcept, override));
498503
MOCK_METHOD(
499504
std::unique_ptr<AsyncTransportCertificate>,
500505
verifyLeaf,
501506
(const AsyncTransportCertificate&),
502-
(const));
507+
(const, override));
503508
};
504509

505510
class MockHandshakeCB : public AsyncSSLSocket::HandshakeCB {

0 commit comments

Comments
 (0)