Skip to content

Commit 857dd28

Browse files
authored
feat: specify a custom CA file for TLS peer verification (#409)
This adds a new config builder option, `CustomCAFile` and associated C binding to the server and client SDKs. When specified, the SDK's streaming, polling, and event connections will verify its TLS peer based on the CAs found in this file. The custom file may be un-set by passing an empty string.
1 parent db0a9eb commit 857dd28

File tree

30 files changed

+264
-49
lines changed

30 files changed

+264
-49
lines changed

contract-tests/client-contract-tests/src/entity_manager.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ std::optional<std::string> EntityManager::create(ConfigParams const& in) {
134134
if (in.tls->skipVerifyPeer) {
135135
builder.SkipVerifyPeer(*in.tls->skipVerifyPeer);
136136
}
137+
if (in.tls->customCAFile) {
138+
builder.CustomCAFile(*in.tls->customCAFile);
139+
}
137140
config_builder.HttpProperties().Tls(std::move(builder));
138141
}
139142

contract-tests/client-contract-tests/src/main.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ int main(int argc, char* argv[]) {
4545
srv.add_capability("anonymous-redaction");
4646
srv.add_capability("tls:verify-peer");
4747
srv.add_capability("tls:skip-verify-peer");
48-
48+
srv.add_capability("tls:custom-ca");
49+
4950
net::signal_set signals{ioc, SIGINT, SIGTERM};
5051

5152
boost::asio::spawn(ioc.get_executor(), [&](auto yield) mutable {

contract-tests/data-model/include/data_model/data_model.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ struct adl_serializer<std::optional<T>> {
3131

3232
struct ConfigTLSParams {
3333
std::optional<bool> skipVerifyPeer;
34+
std::optional<std::string> customCAFile;
3435
};
36+
3537
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigTLSParams,
36-
skipVerifyPeer);
38+
skipVerifyPeer,
39+
customCAFile);
3740

3841
struct ConfigStreamingParams {
3942
std::optional<std::string> baseUri;

contract-tests/server-contract-tests/src/entity_manager.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ std::optional<std::string> EntityManager::create(ConfigParams const& in) {
125125
if (in.tls->skipVerifyPeer) {
126126
builder.SkipVerifyPeer(*in.tls->skipVerifyPeer);
127127
}
128+
if (in.tls->customCAFile) {
129+
builder.CustomCAFile(*in.tls->customCAFile);
130+
}
128131
config_builder.HttpProperties().Tls(std::move(builder));
129132
}
130133

contract-tests/server-contract-tests/src/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ int main(int argc, char* argv[]) {
4444
srv.add_capability("anonymous-redaction");
4545
srv.add_capability("tls:verify-peer");
4646
srv.add_capability("tls:skip-verify-peer");
47+
srv.add_capability("tls:custom-ca");
4748

4849
net::signal_set signals{ioc, SIGINT, SIGTERM};
4950

libs/client-sdk/include/launchdarkly/client_side/bindings/c/config/builder.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,26 @@ LDClientHttpPropertiesTlsBuilder_SkipVerifyPeer(
440440
LDClientHttpPropertiesTlsBuilder b,
441441
bool skip_verify_peer);
442442

443+
/**
444+
* Configures TLS peer certificate verification to use a custom
445+
* CA file.
446+
*
447+
* The parameter is a filepath pointing to a bundle of
448+
* one or more PEM-encoded x509 certificates comprising the root of trust for
449+
* the SDK's outbound connections.
450+
*
451+
* By default, the SDK uses the system's CA bundle. Passing the empty string
452+
* will unset any previously set path and revert to the system's CA bundle.
453+
*
454+
* @param b Client config builder. Must not be NULL.
455+
* @param custom_ca_file Filepath of the custom CA bundle, or empty string. Must
456+
* not be NULL.
457+
*/
458+
LD_EXPORT(void)
459+
LDClientHttpPropertiesTlsBuilder_CustomCAFile(
460+
LDClientHttpPropertiesTlsBuilder b,
461+
char const* custom_ca_file);
462+
443463
/**
444464
* Disables the default SDK logging.
445465
* @param b Client config builder. Must not be NULL.

libs/client-sdk/src/bindings/c/builder.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,16 @@ LDClientHttpPropertiesTlsBuilder_SkipVerifyPeer(
332332
TO_TLS_BUILDER(b)->SkipVerifyPeer(skip_verify_peer);
333333
}
334334

335+
LD_EXPORT(void)
336+
LDClientHttpPropertiesTlsBuilder_CustomCAFile(
337+
LDClientHttpPropertiesTlsBuilder b,
338+
char const* custom_ca_file) {
339+
LD_ASSERT_NOT_NULL(b);
340+
LD_ASSERT_NOT_NULL(custom_ca_file);
341+
342+
TO_TLS_BUILDER(b)->CustomCAFile(custom_ca_file);
343+
}
344+
335345
LD_EXPORT(LDClientHttpPropertiesTlsBuilder)
336346
LDClientHttpPropertiesTlsBuilder_New(void) {
337347
return FROM_TLS_BUILDER(new TlsBuilder());

libs/client-sdk/src/client_impl.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ ClientImpl::ClientImpl(Config in_cfg,
109109
eval_reasons_available_(config_.DataSourceConfig().with_reasons) {
110110
flag_manager_.LoadCache(context_);
111111

112+
if (auto custom_ca = http_properties_.Tls().CustomCAFile()) {
113+
LD_LOG(logger_, LogLevel::kInfo)
114+
<< "TLS peer verification configured with custom CA file: "
115+
<< *custom_ca;
116+
}
117+
if (http_properties_.Tls().PeerVerifyMode() ==
118+
config::shared::built::TlsOptions::VerifyMode::kVerifyNone) {
119+
LD_LOG(logger_, LogLevel::kInfo) << "TLS peer verification disabled";
120+
}
121+
112122
if (config_.Events().Enabled() && !config_.Offline()) {
113123
event_processor_ =
114124
std::make_unique<events::AsioEventProcessor<ClientSDK>>(

libs/client-sdk/src/data_sources/polling_data_source.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ PollingDataSource::PollingDataSource(
7474
status_manager_(status_manager),
7575
data_source_handler_(
7676
DataSourceEventHandler(context, handler, logger, status_manager_)),
77-
requester_(ioc, http_properties.Tls().PeerVerifyMode()),
77+
requester_(ioc, http_properties.Tls()),
7878
timer_(ioc),
7979
polling_interval_(
8080
std::get<
@@ -88,10 +88,6 @@ PollingDataSource::PollingDataSource(
8888
auto const& polling_config = std::get<
8989
config::shared::built::PollingConfig<config::shared::ClientSDK>>(
9090
data_source_config.method);
91-
if (http_properties.Tls().PeerVerifyMode() ==
92-
config::shared::built::TlsOptions::VerifyMode::kVerifyNone) {
93-
LD_LOG(logger_, LogLevel::kDebug) << "TLS peer verification disabled";
94-
}
9591
if (polling_interval_ < polling_config.min_polling_interval) {
9692
LD_LOG(logger_, LogLevel::kWarn)
9793
<< "Polling interval too frequent, defaulting to "

libs/client-sdk/src/data_sources/streaming_data_source.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ void StreamingDataSource::Start() {
132132
client_builder.skip_verify_peer(true);
133133
}
134134

135+
if (auto ca_file = http_config_.Tls().CustomCAFile()) {
136+
client_builder.custom_ca_file(*ca_file);
137+
}
138+
135139
auto weak_self = weak_from_this();
136140

137141
client_builder.receiver([weak_self](launchdarkly::sse::Event const& event) {

0 commit comments

Comments
 (0)