Skip to content

Commit bd76c9e

Browse files
committed
Add HttpClientConfig::extraCaFile to trust extra CA certificates
SSL_CERT_FILE replaces the default store, so trusting a private CA or a TLS-inspecting proxy meant building a combined bundle by hand. The new field names a PEM file whose certificates are added to the default store. It applies to the target connection and to the connection to an https:// proxy. A file that cannot be read or holds no certificate fails the connection and the message names the path; it is not skipped. proxy_tunnel takes the path as a trailing default argument and TlsSocket gains set_extra_ca_file. With the field empty nothing changes.
1 parent 2cc7310 commit bd76c9e

8 files changed

Lines changed: 477 additions & 6 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ jobs:
352352
- name: The hermetic tests
353353
run: |
354354
set -o pipefail
355-
for t in test_framing test_tls_verify test_pool test_proxy test_cancel; do
355+
for t in test_framing test_tls_verify test_pool test_proxy test_cancel test_extra_ca; do
356356
mcpp test "$t" 2>&1 | tee "$t.log"
357357
grep -q "^$t \.\.\. ok" "$t.log"
358358
done

‎CHANGELOG.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Extra CA certificates
6+
7+
`HttpClientConfig::extraCaFile` is a path to a PEM file whose certificates are
8+
trusted in addition to the default store, which `SSL_CERT_FILE` can only replace.
9+
It is for a private CA or a proxy that re-signs TLS.
10+
11+
* It applies wherever a trust store is built: the target, and the connection to
12+
an `https://` proxy.
13+
* A file that cannot be read or holds no certificate fails the connection, and
14+
`statusText` names the file.
15+
* `proxy_tunnel` takes the path as a trailing default argument, and `TlsSocket`
16+
gains `set_extra_ca_file`. Nothing changes while the field is empty.
17+
318
## 0.3.4
419

520
A request in flight can be abandoned from another thread. Everything is added

‎README.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,24 @@ includes in that file, or use libc++ 23 or libstdc++.
8686
| `maxRedirects` | 10 | 0 disables redirect following |
8787
| `maxResponseBodyBytes` | 64 MiB | the most `send()` will hold in memory; does not bound `download_to_file` or `send_stream` |
8888
| `retryOnStaleConnection` | true | resend once when a pooled connection turns out to have been closed while idle |
89+
| `extraCaFile` | empty | path of a PEM file of CA certificates to trust in addition to the default store, see below |
8990
| `proxy` | none | proxy URL, see below |
9091

92+
### Extra CA certificates
93+
94+
A private CA, or a proxy that re-signs TLS, is not in the default store, and
95+
`SSL_CERT_FILE` replaces that store instead of adding to it. Set `extraCaFile`
96+
to a PEM file and its certificates are trusted as well as the default ones:
97+
98+
```cpp
99+
cfg.extraCaFile = "/etc/corp/root-ca.pem";
100+
```
101+
102+
It applies to the connection to an `https://` proxy as well as to the target. If
103+
the file cannot be read or holds no certificate, the connection fails and
104+
`statusText` names the file. The file adds to a default store and does not stand
105+
in for one: with none found, the connection fails as described under `verifySsl`.
106+
91107
### Proxies
92108

93109
Set `proxy` to a URL and every request is tunnelled through it with `CONNECT`:

‎src/http.cppm‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,13 @@ export struct HttpClientConfig {
107107
// endpoints, and restricting it there would make the retry inapplicable to
108108
// nearly every request it exists to rescue. Set false to opt out.
109109
bool retryOnStaleConnection { true };
110+
111+
// A PEM file of CA certificates to trust in addition to the default store
112+
// (`SSL_CERT_FILE`, or else the system roots), for a private CA or a proxy that
113+
// re-signs TLS. It applies to the connection to an https:// proxy as well.
114+
// A file that cannot be read or holds no certificate fails the connection,
115+
// with the path in `statusText`. Empty means none.
116+
std::string extraCaFile;
110117
};
111118

112119
// Progress callback for streaming downloads: (totalBytes, downloadedBytes)
@@ -1042,10 +1049,12 @@ private:
10421049
// connection failed: a proxy that refused the tunnel says so in its own words.
10431050
bool open_connection(TlsSocket& sock, const ParsedUrl& parsed, std::string& error,
10441051
std::stop_token stop) {
1052+
sock.set_extra_ca_file(config_.extraCaFile);
10451053
if (config_.proxy.has_value()) {
10461054
auto tunnel = proxy_tunnel(parse_proxy_url(config_.proxy.value()),
10471055
parsed.host, parsed.port,
1048-
config_.connectTimeoutMs, config_.verifySsl, stop);
1056+
config_.connectTimeoutMs, config_.verifySsl, stop,
1057+
config_.extraCaFile);
10491058
if (!tunnel.ok()) {
10501059
error = std::move(tunnel.error);
10511060
return false;

‎src/proxy.cppm‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -347,12 +347,13 @@ export struct ProxyTunnel {
347347
}
348348
};
349349

350-
// `verifySsl` is for the connection to an https:// proxy, and is the same
351-
// setting that governs the connection to the target.
350+
// `verifySsl` and `extraCaFile` are for the connection to an https:// proxy, and
351+
// are the same settings that govern the connection to the target.
352352
export ProxyTunnel proxy_tunnel(const ProxyConfig& proxy,
353353
std::string_view targetHost, int targetPort,
354354
int timeoutMs, bool verifySsl = true,
355-
std::stop_token stop = {}) {
355+
std::stop_token stop = {},
356+
std::string_view extraCaFile = {}) {
356357
ProxyTunnel tunnel;
357358
tunnel.socket.set_stop(stop);
358359
const std::string where = proxy.host + ":" + std::to_string(proxy.port);
@@ -369,6 +370,7 @@ export ProxyTunnel proxy_tunnel(const ProxyConfig& proxy,
369370
} else if (proxy.scheme == "https") {
370371
auto tls = std::make_unique<TlsSocket>();
371372
tls->set_stop(stop);
373+
tls->set_extra_ca_file(std::string(extraCaFile));
372374
if (!tls->connect(proxy.host.c_str(), proxy.port, timeoutMs, verifySsl)) {
373375
// The session says why when the TCP connection was up, which is
374376
// where a proxy certificate that does not verify is refused.

‎src/tls.cppm‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ public:
165165
: socket_(std::move(other.socket_))
166166
, lower_(std::move(other.lower_))
167167
, state_(std::move(other.state_))
168-
, error_(std::move(other.error_)) {
168+
, error_(std::move(other.error_))
169+
, extraCaFile_(std::move(other.extraCaFile_)) {
169170
// Re-bind BIO to point to our socket_ (not the moved-from one)
170171
bind_bio();
171172
}
@@ -178,6 +179,7 @@ public:
178179
lower_ = std::move(other.lower_);
179180
state_ = std::move(other.state_);
180181
error_ = std::move(other.error_);
182+
extraCaFile_ = std::move(other.extraCaFile_);
181183
// Re-bind BIO to point to our socket_
182184
bind_bio();
183185
}
@@ -206,6 +208,10 @@ public:
206208
socket_.set_stop(std::move(stop));
207209
}
208210

211+
// A PEM file of roots to trust in addition to the default store. Call before
212+
// connect(); setup fails if the file cannot be read or holds no certificate.
213+
void set_extra_ca_file(std::string path) { extraCaFile_ = std::move(path); }
214+
209215
// Connect over an already-established Socket (e.g. a proxy tunnel).
210216
// Takes ownership of the socket and performs TLS handshake on top of it.
211217
bool connect_over(Socket&& socket, const char* host, bool verifySsl) {
@@ -323,6 +329,7 @@ private:
323329
std::unique_ptr<TlsSocket> lower_;
324330
std::unique_ptr<TlsState> state_;
325331
std::string error_;
332+
std::string extraCaFile_;
326333

327334
// The session beneath this one, when there is one, is closed as well: a
328335
// failed handshake to the target leaves nothing of the tunnel open.
@@ -411,6 +418,24 @@ private:
411418
if (ret < 0) {
412419
return fail("cannot parse the CA certificate bundle: " + mbedtls_message(ret));
413420
}
421+
}
422+
// Roots the caller adds to the above. A file that cannot be used is an
423+
// error, not something to skip: the caller named it because a server
424+
// needs it, and skipping would surface later as a verification failure
425+
// that does not mention the file.
426+
if (!extraCaFile_.empty()) {
427+
std::ifstream in(extraCaFile_, std::ios::binary);
428+
if (!in) return fail("cannot read extra CA file '" + extraCaFile_ + "'");
429+
std::string extra((std::istreambuf_iterator<char>(in)), {});
430+
ret = mbedtls_x509_crt_parse(
431+
&state_->ca_cert,
432+
reinterpret_cast<const unsigned char*>(extra.c_str()), extra.size() + 1);
433+
if (ret < 0) {
434+
return fail("cannot parse extra CA file '" + extraCaFile_ + "': "
435+
+ mbedtls_message(ret));
436+
}
437+
}
438+
if (!ca_pem.empty() || !extraCaFile_.empty()) {
414439
mbedtls_ssl_conf_ca_chain(&state_->conf, &state_->ca_cert, nullptr);
415440
}
416441

‎tests/extra_ca_certs.hpp‎

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Two private CAs, each with one server certificate (CN=localhost, SAN localhost
2+
// and 127.0.0.1), for test_extra_ca.cpp. Valid for a century. The keys are here
3+
// on purpose: they authenticate nothing and must never be used for anything.
4+
//
5+
// Made with openssl: a self-signed CA with basicConstraints CA:TRUE and
6+
// keyUsage keyCertSign, and a leaf signed by it whose extensions are
7+
// basicConstraints CA:FALSE, extendedKeyUsage serverAuth and
8+
// subjectAltName DNS:localhost,IP:127.0.0.1.
9+
#pragma once
10+
11+
namespace extra_ca_test {
12+
13+
inline constexpr const char* kCa1Pem = R"PEM(-----BEGIN CERTIFICATE-----
14+
MIIDLzCCAhegAwIBAgIUG2Zap6LiJwyHlkOt1Ko7G28l+DcwDQYJKoZIhvcNAQEL
15+
BQAwHjEcMBoGA1UEAwwTdGlueWh0dHBzIHRlc3QgQ0EgMTAgFw0yNjEwMDkwODQ3
16+
MzNaGA8yMTI2MDkxNTA4NDczM1owHjEcMBoGA1UEAwwTdGlueWh0dHBzIHRlc3Qg
17+
Q0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALCFaWIMBSe8OolK
18+
5ExxlX3LbNgg3XlOLxa604zHI0nLWFhKJalkticwwMaX4gcJQdETCXADq+V8qj48
19+
014c3uUC7xlb+INULhe2ocThipOSwhh4E1Vyv8ywNhgVEw8r+Lgl2ExaSIGzcx4O
20+
2tWuOvzQHhe6JG8G5YRWVk5ydimhFGY6VdGzUPfHAID+3JEqr6RUeCct4Ehx2zbg
21+
mkn7ZBu0PHtP2fjrc5KnVwXWDH91MbSfJ1WQvPq5kPbSpeeLoWvDznzpUjg+9aTB
22+
Dbyxu+/5YVFy5jWWEpgOKakyKnfsQ6XTqzz6dvogAjjh3UWTHe01osluXqY8tjls
23+
gapw+N8CAwEAAaNjMGEwHQYDVR0OBBYEFP3PSVqEY/jqpWJmAzftRY3zFZPtMB8G
24+
A1UdIwQYMBaAFP3PSVqEY/jqpWJmAzftRY3zFZPtMA8GA1UdEwEB/wQFMAMBAf8w
25+
DgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQAhMIwH6LSKiywbxaFK
26+
RlUIqgThCIP9uUOZ0m/6jjgMwSJhU0IZ4COCewFVPEGepeqhNjDXHD9Y0EHbxDi5
27+
cCsk2fSz9xfsjBniI+xETlxGLoCrNDAmNEfPv8mT9UnNKG7KqLghQiHpMbx47895
28+
wTbr1OldFAlU8usR9pOigRc6j5cmf9b1Tu/piUTFsy5xoANPt/HRg3o/w0mfR8c5
29+
bHQ3LaqcQO+uBpKAy9guHpszKosOmbTX6AB5JBSyo70u3jx0PHgQuOJTEeKP1PBJ
30+
51EyNShztp7bn+Rz0OLsMk2h6AMns7HB2m5gEIq/ELlaWDYruqiYFmGpqcm7deVd
31+
v6DH
32+
-----END CERTIFICATE-----
33+
)PEM";
34+
35+
inline constexpr const char* kLeaf1Pem = R"PEM(-----BEGIN CERTIFICATE-----
36+
MIIDTzCCAjegAwIBAgIUO+WtedkpuiIwKSiJ20nYcEG4pNQwDQYJKoZIhvcNAQEL
37+
BQAwHjEcMBoGA1UEAwwTdGlueWh0dHBzIHRlc3QgQ0EgMTAgFw0yNjEwMDkwODQ3
38+
MzNaGA8yMTI2MDkxNTA4NDczM1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjAN
39+
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj3gXKIHuAZ2h0DeMqxZX2ImgZJan
40+
arOOw8vKg8yqyfgAeEtenmH42Hv4DAPhHVycq9DyXutiXVxQ+v1y7ABfG9RG2Z0u
41+
Ueen2TqNDz5F6CrlUeG/Fgk3OVOWVeglcJpd8vqiVzrgRRtc4+uREGlu2YPusgSX
42+
rbVL+6VARdEfypluaStOt1kJkQlLUxa5A7OpsXKIDLRnOKSHCyx1CCJNy3jwPmqz
43+
vng6M/FYns/73TxqSZ8Wi7CRdCpNWjlwfJ0zunjeYdrGUchgVpbyWedexZzKwLMO
44+
NFnjY20qPmN0ZIcXSg+IfZJcUKSD6t+kWZAOtcFwrO6xi7SWFQwNyskW3wIDAQAB
45+
o4GMMIGJMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUF
46+
BwMBMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATAdBgNVHQ4EFgQUNtDg1vH4
47+
jNeYzFRvdHWYhhdO6WEwHwYDVR0jBBgwFoAU/c9JWoRj+OqlYmYDN+1FjfMVk+0w
48+
DQYJKoZIhvcNAQELBQADggEBAEUwKddb7KxhgZ5a41u78wRANsfkXHJEXX6l2IWL
49+
cOAE9PU2LFj4muABzp7g6WA5rOF5Dt34E3PSB8EQIMTY6SBA9V6Y80/8uBlsF+Er
50+
Labg6ABrvswpGPn7sc65m8uDUYdG5jm0P2lure9mV8kdiYURk0/eOIpdCp8CcuFx
51+
iG1LsAMz9h0WAgbkfIzigDGx9TkvqTZNSfzvvObgGmeRfo++pLMEjifug9NBRHuN
52+
dMiQ4NBvRv+HvJHGSAD1fpm8QvEiMk7spf+PyZbJZP34YZlbJfZJXvBxnTnJY6rD
53+
amz7EBggI4kRiDGG5vxgyH2bYe9uzw01jb02QJyUvJqvOv4=
54+
-----END CERTIFICATE-----
55+
)PEM";
56+
57+
inline constexpr const char* kLeaf1KeyPem = R"PEM(-----BEGIN PRIVATE KEY-----
58+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCPeBcoge4BnaHQ
59+
N4yrFlfYiaBklqdqs47Dy8qDzKrJ+AB4S16eYfjYe/gMA+EdXJyr0PJe62JdXFD6
60+
/XLsAF8b1EbZnS5R56fZOo0PPkXoKuVR4b8WCTc5U5ZV6CVwml3y+qJXOuBFG1zj
61+
65EQaW7Zg+6yBJettUv7pUBF0R/KmW5pK063WQmRCUtTFrkDs6mxcogMtGc4pIcL
62+
LHUIIk3LePA+arO+eDoz8Viez/vdPGpJnxaLsJF0Kk1aOXB8nTO6eN5h2sZRyGBW
63+
lvJZ517FnMrAsw40WeNjbSo+Y3RkhxdKD4h9klxQpIPq36RZkA61wXCs7rGLtJYV
64+
DA3KyRbfAgMBAAECggEAI/sGM86CFj2mO0XzLWUpGtFJPHDcA9KMV2Zgn7VQj952
65+
n7SnQiln80MD7l96sZZsZFq+rjdyL9/bH1c43x8oux39ts3ItBnY6o3Ymp4Pgi5f
66+
3K008x3Uj7f62Aikexz7uthIpn/JhwtZvH9znh4TAtInptWoBJSXPcEv4VKVGoYI
67+
HglTL1/TvIkyo2hwdLBqOuUF5PioHktG8MZH2ILqFJTfB5bWmOPXDMAEuZD9pkQK
68+
LfyD9AntTRKVxRO/sXjHy27IXSKbDoZq4kqqLkxk0BGJpf4BnxxjZUWoqbQ7MZHs
69+
MS26/fcpMWSJygYT8vMkAQYrGjrEkKQSvABFTmkegQKBgQDASz/4vpa+qPf9soX8
70+
n9+XXSPBeyNeNKpl2p0b+7h8skRIdi/gmttSiC9g/L2fuI2rpoikii1qxQ1XN+wA
71+
LOcOrL5p/RJdGKAyu8n8cRSGFkhkOO5s2zLsSdXUHesVqWc8keI37DoJkySYEsiC
72+
WI0MZNNK01cUFttD+nEqxgCicQKBgQC+/+3u+aWF1rumheoUV9pRk65F0jJRnTAQ
73+
bfiRE7jWhs/LSYpJ1EOgc0OYaRnJPwKYuck4/Z7K3beNKPH3jPJ+89UvfRjHKE/3
74+
o89yACWZATh2XMjvZUUUmvn+Om+yMBxWhy1vyTJU+XhFVSIROofqg5xH3F3hMC6P
75+
WIyJctVWTwKBgDUdE+BrFEw5c7Y5d1Td++5dJaEtmAlPVrmndrnh/4VB4CZ7rqHF
76+
ZEsZnqyVYvMZENiWuStplz3ki9jJc4Bg5rlg8X9pDYd1Y9pfkF0QBvE6emhkaUbc
77+
DObDRjK+yM5E6mUKjoeP0kAOyo7OsV9/ggYoW0xRr722yn2uUU/VPRuhAoGBALu/
78+
TX5cTdWa98g/2JH4rbUEqwkdDrNlBQsRXoYVUdsml3mnp1TdX0CILQjTPzKwSngg
79+
H+lyfLXDbhtHBVqmZPRf8M4GdOXH/ZtanZ7dABg/t+W5XRbdgCM6F8VMYeFRI3n2
80+
1LYOmvgmZZAZmCxBUbs3dda5ilrCBvKHQ1YU2BpHAoGAGZVbkJ0lnCoXf3tyMBCZ
81+
MliDj7Q6fSGd22ZAoYy/XGLmZUMi/ZK+PkdzVwT6CDwJY07vBDwUIj5qBHlmaHGS
82+
qC7o3bmhdtC4R+eN4Gb6mdKB3wCdrRhx2HTBGDf4aoPwGtUw+kUdlve1L+Rwsdfc
83+
zCIifQbNtFiGohWrskdamio=
84+
-----END PRIVATE KEY-----
85+
)PEM";
86+
87+
inline constexpr const char* kCa2Pem = R"PEM(-----BEGIN CERTIFICATE-----
88+
MIIDLzCCAhegAwIBAgIUe6yBH1jY6Ogs0HBiaUEsnbZlB54wDQYJKoZIhvcNAQEL
89+
BQAwHjEcMBoGA1UEAwwTdGlueWh0dHBzIHRlc3QgQ0EgMjAgFw0yNjEwMDkwODQ3
90+
MzNaGA8yMTI2MDkxNTA4NDczM1owHjEcMBoGA1UEAwwTdGlueWh0dHBzIHRlc3Qg
91+
Q0EgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJXfKHI6jLsG/yLn
92+
UqcDb+PBuw4+C6eaYUwgJNJ5VNubXZpjg1JJxqBqOYFKJ69J5OULqBD2A3DWCAKh
93+
vh6FOHeSdXlvwnZQN0fLwtnYcmL90XJaDrUwaFn/NrceqsgBo+pNXHcVb6OYwxQr
94+
Np9/3kta6vbWp9cC8D6vCkmp2ehrz2UfBo4m6oh1Re2ATEBn0H2OD41LKyQslpF4
95+
CFkm2rIdkTsqlGtdoBkE+JyBX+eoZAlnRBRC5NxJDLEtDlxoPlbVRP/YgPeazv1g
96+
tMgUvBhmC5AROU6oEpiXiGxas/3m3av/21shsq0iz+b+1QOF5gt9MdOEm2mxrxfF
97+
hkf0pH0CAwEAAaNjMGEwHQYDVR0OBBYEFMV3Z2Qg+uzCT8vmKJV84SBhwzQQMB8G
98+
A1UdIwQYMBaAFMV3Z2Qg+uzCT8vmKJV84SBhwzQQMA8GA1UdEwEB/wQFMAMBAf8w
99+
DgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQBmve5C71LtGh24Sq3Q
100+
kNsEROTQYwjbI1W1tEC5C/bqeUFLl4YxyzIHyGFiNSZ7aaaOSRrMEY2aCG2f7am0
101+
Rckygp6fFaTTAfWnsgH7fVxvBEwywNlLDaAU/co20cnFa1MfUO1MfstF56lqllgX
102+
NHE/PAOCV/ZcO3DAmJ1lWbsJcXQKplbNlZIeEBwqJs2i3xpiJHxhckg95wyq2vnX
103+
qtCBiGlK7LBawlHLAbYEXf4LTDAJvb5OFfb8vIJN5pn7PKvMhhMtp4i0r0nDgLkR
104+
nP25ABxH8uw8KV7rCzp6i0LDSMRAU6pbs/C8kvMSv8BmF2FFK4OX4+29aIPH/utP
105+
x/4p
106+
-----END CERTIFICATE-----
107+
)PEM";
108+
109+
inline constexpr const char* kLeaf2Pem = R"PEM(-----BEGIN CERTIFICATE-----
110+
MIIDTzCCAjegAwIBAgIUBQEev9nx9U9d1N63ZONvDn/2u9owDQYJKoZIhvcNAQEL
111+
BQAwHjEcMBoGA1UEAwwTdGlueWh0dHBzIHRlc3QgQ0EgMjAgFw0yNjEwMDkwODQ3
112+
MzNaGA8yMTI2MDkxNTA4NDczM1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjAN
113+
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4piCGItiClSpVhm3Rw2h/OTfgyE4
114+
vrEXqkEcHB7Oq4z23KeluS9r6eM/6i0ezRUTCI71Q6UBQ/n7mQUPb5ePHZygVqL+
115+
QEYGiO6xH2q3kB3oDzNixaPYUCg0u7WLCbv4E6E8ngqcpMSBTxTH/Be3AB1j0I9a
116+
AH3xmVbY1E2V79uYhRVc7cQ0mNfXkj1K4G0AaPKuiwwVKnE7/z/BwA4vqWjH9jsV
117+
s1JFhjPpydBY73VWEVE0Ou1ntBNoKGilsVJBs4lf4P8pu9hBS8H0PCAk784kVY8n
118+
0Y1G2xU3fSash6HjljjKtWg+mudbu2edVG8pkv13JYxJNNIUCXLvRH6m4QIDAQAB
119+
o4GMMIGJMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUF
120+
BwMBMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATAdBgNVHQ4EFgQUSfHOwhMo
121+
5rrgK8aDsQ4K7lvGJ5IwHwYDVR0jBBgwFoAUxXdnZCD67MJPy+YolXzhIGHDNBAw
122+
DQYJKoZIhvcNAQELBQADggEBAIQ2kQBTG7wSFAa/fGup6cph+pV2ZVikoQUN0C9U
123+
29uJaGALXNhx5RUDduPZP0i1Q9chYh55HcCT/CtbVlkO3X3BwLmgvqJel0O0nDnR
124+
bIA90Ccs0wOYBY1bkWqcjqALJlddjeeerYdV3PulAp5uklf5ZWxgxl6XSucRW1fH
125+
wvtBbQ7TirbYos0nZqAF4pxpMq4KLfWa7eAVMThL6ImiQyOYZsblMxZqAOPh8RIU
126+
lmZG8/79aHwWyQdCC7p91dD5bCtOwrn1VZvTgmrzp8+TQnzqGuKAlUZEJXp/OF+a
127+
qana9iMkkKQyE7y+DGZz09xKrvlh8l7kxJMES6ynN/XjITg=
128+
-----END CERTIFICATE-----
129+
)PEM";
130+
131+
inline constexpr const char* kLeaf2KeyPem = R"PEM(-----BEGIN PRIVATE KEY-----
132+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDimIIYi2IKVKlW
133+
GbdHDaH85N+DITi+sReqQRwcHs6rjPbcp6W5L2vp4z/qLR7NFRMIjvVDpQFD+fuZ
134+
BQ9vl48dnKBWov5ARgaI7rEfareQHegPM2LFo9hQKDS7tYsJu/gToTyeCpykxIFP
135+
FMf8F7cAHWPQj1oAffGZVtjUTZXv25iFFVztxDSY19eSPUrgbQBo8q6LDBUqcTv/
136+
P8HADi+paMf2OxWzUkWGM+nJ0FjvdVYRUTQ67We0E2goaKWxUkGziV/g/ym72EFL
137+
wfQ8ICTvziRVjyfRjUbbFTd9JqyHoeOWOMq1aD6a51u7Z51UbymS/XcljEk00hQJ
138+
cu9EfqbhAgMBAAECggEAFmUu1HiwKae47cjQX64mAK3pNldZ+aTcmyfgvJnB4Nwn
139+
iOLr4uvgLUdXyZAIj4nh7fJdJMCh6aNDHdpNboDK9QkGItHssZMKMc3vhRShzFVc
140+
WmZAgJqjNjbnEVdNxNpGWEBTEqanWTSPuC5sXorEfDcrZpFIeqx37KkT+JWreD9r
141+
DEsHVL51wGwD7KC06qv/IUbQXrfUtwJouof0X3iF/1bSmBTi3gVvyRpBaECRAZab
142+
bOktlfKP/9FquA7KegY0HaaByXC073lj7pl3H9AmnYihXfTHSnxRMLq31IhpWzIN
143+
Bbx/+vaVWgt/lWdedAVg8wVqlen4PSTstKYpq9WrHQKBgQD0SeekTgwjHfixaGm8
144+
7BoYHrszO32ua7BPAi8zXjbJi2SwFqyLlsw52FaYMMFxWRCarhtOKzUR0AnfB+Tp
145+
HUsNlg5IOzeBXz26Care6Ymmx0jc1ZTrNMNoSTRMhZ4Cq16pD/mXSFNRQaBxP1Uy
146+
ZTKzbz/UMZ6dbw+wY0lRp5gABQKBgQDtdXZHyPWns5OY34IYkO/cX3FY7K6BqqQz
147+
8ciULN1v8bK32J8NXuuHRpbl+gJQ9ZwSzTyUBEW2EJArSmiVQBWLfITOv+Ubb34a
148+
7RmiMO7ectqDsydpXyNr5GVvmJSN7ua/Bgse71ZEER+5T+Wk1IFrmc+dgWD7p1wX
149+
DPoRGVruLQKBgGxA++extmsxcu2bKZBQyFd79tUDPfS3QBAjFaubyoeRv/LQeXH6
150+
R1Iy7lzO7ko+f78r/Gmtd/0GxfkR1H0BQCsrHfUgWL556C6y7geOUjOYCE/kTfPV
151+
E+r41bY7WAQy0OX2cFJ7+H//pwvIVwbYFZmyaQStYG6/sqIlvW3z6MXJAoGAFNv1
152+
qPapRbMLqykvJ3NL7sLflR0tcyyHQhziLsoGJbhIp8e6qRUGgZqc1NTycSQyMqCe
153+
yq3ZZdXspDDkIQBBJECq9fJ7cL6JiFn9n3bG9PmH4nvRYXs/BFJK6tOvcQxkbrFc
154+
Nrp1jIMok0HGfYVBXS9kwoRD/bdE6q10Jh5hE30CgYEA1cUVunt7NYrY2NV+jF+1
155+
kp9Imom/F0ao4wowMmrcJTm1zgw4BehYaRoE7BVOxoq4Rc7SBgSlvb8aoy7EXuu7
156+
eieh8GE4WeiFGcDtCdpS82s/3IOeHwHo9I7wFa16r1jy/ooygVPM3RB8q7KOmfP+
157+
Hmwo4XQ7djlELruiEfglIt4=
158+
-----END PRIVATE KEY-----
159+
)PEM";
160+
161+
} // namespace extra_ca_test

0 commit comments

Comments
 (0)