Compare commits

..
21 Commits
Author SHA1 Message Date
yhirose 00afaaed6a Release v0.60.1 2026-10-07 23:40:36 -04:00
yhirose e803f5e413 Let the SSL chain policy alone judge the Windows chain (#2618)
verify_cert_with_windows_schannel() rejected a chain whenever
TrustStatus.dwErrorStatus was non-zero, before
CertVerifyCertificateChainPolicy() ran. The
CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS flag passed to that
policy check was therefore dead code: a certificate without revocation
information, or one whose CRL could not be fetched, failed with
CERT_TRUST_REVOCATION_STATUS_UNKNOWN.

Drop the pre-check so the SSL chain policy is the only judge.
Revocation checking becomes best-effort: a revoked certificate and
every other chain error are still rejected, while an undetermined
revocation status is accepted.

On a rejected chain, ssl_backend_error() now holds the policy status,
such as CERT_E_UNTRUSTEDROOT, instead of the trust status bit mask.
2026-10-07 23:19:36 -04:00
Michael Weitzel 57b8aca6da Avoid name clash with class WebSocketClient by explicit qualification (#2616)
Adds an explicit namespace qualification and a forward declaration for
class WebSocketClient. This change avoids a name clash on code bases
containing unrelated WebSocketClient classes.
2026-10-07 22:38:50 -04:00
KBSandyoudie006 213029685a Serve the whole body for a suffix range longer than it (#2615)
RFC 9110 14.1.2: if the representation is shorter than the suffix-length,
the entire representation is used. range_error computed a negative first
byte position for bytes=-8 on a 7-byte body and answered 416. Clamp it
at 0, as #711 did before the range handling was reworked.

Co-authored-by: youdie006 <youdie006@users.noreply.github.com>
2026-10-07 22:20:53 -04:00
yhirose 17eeb18feb Release v0.60.0 2026-10-06 22:57:42 -04:00
metsw24-maxandyhirose edc9760005 use the SAN type tag in Mbed TLS verify_hostname and get_cert_sans (#2614)
* use the SAN type tag in Mbed TLS verify_hostname and get_cert_sans

Mbed TLS keeps a subjectAltName entry's GeneralName tag in buf.tag and the bare value in buf.p / buf.len. verify_hostname ignored the tag, so a dNSName whose bytes equal an address authenticated that IP host, and an iPAddress or rfc822Name was matched as a DNS pattern. get_cert_sans looked for the tag inside the value, so it reported no entries for an ordinary certificate, or part of a dNSName as an entry of its own.

* Shorten the SAN type comments

---------

Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
2026-10-05 21:13:50 -04:00
yhirose 6d1049462d Ignore a subprotocol the WebSocket client did not offer
A SubProtocolSelector could return a value outside the client's
Sec-WebSocket-Protocol list, and the server sent it back as is. The
server now treats such a value as no selection.
2026-10-04 18:34:21 -04:00
metsw24-maxandyhirose 4fcbc08f2d reject an unoffered subprotocol in read_websocket_upgrade_response (#2595)
* reject an unoffered subprotocol in the ws client handshake

* test the subprotocol check through WebSocketClient so the split build compiles

* Trim comments in the subprotocol check

---------

Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
2026-10-04 18:21:39 -04:00
Jordan Woyak d59f3e438c Remove semantically redundant closed_ checks in ping thread. (#2613) 2026-10-04 09:39:24 -04:00
yhirose 09fa6820fe Fix 303 redirect request bodies and Location path decoding (Fix #2606, #2607)
A 303 response turns the follow-up request into a GET, but only the buffered body and headers were cleared. A content provider (sized or chunked) stayed on the request, so the original payload was sent again, and for a chunked provider the unframed chunks also broke the keep-alive connection.

The redirect also percent-decoded the Location path before sending it. That turned %23, %3F and %25 into a fragment, a query delimiter and a different octet, so the client requested a different resource than the one named, and made set_path_encode(false) fail on any Location containing %20. The path is now sent as given.
2026-10-03 19:06:45 -04:00
yhirose 438319cfcb Fix WebSocket pings being sent early on spurious wakeups
The heartbeat thread waited on ping_cv_ without a predicate, so a
spurious wakeup ended the wait early and sent a ping before
ping_interval_sec_ had elapsed. With max_missed_pongs enabled, the
early ping also counted toward the pong timeout.

Pass a predicate to wait_for so that the wait only ends when the
interval elapses or the connection is closed.

Reported in #2612.
2026-10-03 17:47:30 -04:00
yhirose eda9a10bfe Fix SSE client not clearing Last-Event-ID on an empty id field (Fix #2611)
An event with an empty id field must reset the last event ID, so that
no Last-Event-ID header is sent on reconnect. run_event_loop only
updated last_event_id_ when the id was non-empty, so it could not tell
an empty id field from an event with no id field, and kept sending the
stale ID.

Track whether an id field was seen with a has_id flag, as has_data
does for the data field, and add a regression test.
2026-10-03 17:29:33 -04:00
yhirose cf3693cb5c Release v0.59.0 2026-10-02 22:06:39 -04:00
yhirose 4fd9ae8f42 Serve pipelined requests without waiting for the keep-alive timeout
A client may pipeline its requests (RFC 9112 9.3.2). The server read the
following request(s) into a per-request SocketStream buffer, discarded them
with the stream after the first response, and then waited in keep_alive()
for socket data that never came, closing the connection after the
keep-alive timeout. Over TLS the bytes stayed decrypted in the TLS library,
where keep_alive() could not see them either.

Create one stream per connection and serve a request that is already
buffered (Stream::is_readable()) without waiting in keep_alive().

Keeping the buffer means an extra CRLF that some clients send after a
request body is now parsed as the next request line, which answered 400
and closed the connection. Ignore one empty line before the request-line,
as RFC 9112 2.2 recommends.

Fixes #2599
2026-10-02 21:47:18 -04:00
yhirose 3d40dfc727 Fix SSE parsing of CRLF field names and data-less events
A field line without a colon kept the \r of a CRLF line ending in its
name, so "data\r\n" was not recognized. Strip the \r once per line
before parsing instead of from each value.

An event without a data field was neither dispatched nor cleared, so
its event type leaked into the next event and its id only reached
last_event_id after a later event was dispatched. Reset the message on
every blank line and record the id even when nothing is dispatched.

The parsing tests exercised a copy of parse_sse_line that had drifted
from the real one. Run them through SSEClient against a local server
instead, and add regression tests for the cases above.
2026-10-02 21:38:39 -04:00
metsw24-max dd71728110 escape quoted-string auth-params in make_digest_authentication_header (#2597) 2026-10-02 21:29:35 -04:00
DosX 7255a7e979 Preserve empty SSE data fields (#2594)
SSE events may contain an empty data field, and a data field without a colon also has an empty value. Track whether a data field was seen separately from the accumulated payload so empty events are dispatched and leading empty lines are preserved. Add an integration regression test for both forms.
2026-10-02 21:17:47 -04:00
yhirose 8a3abfb597 Extract parse_int_in_range from parse_port
parse_port and the NO_PROXY CIDR prefix parsing each repeated the same
from_chars call, full-consumption check and range check. Move that into
a parse_int_in_range helper and use it in both places.
2026-10-02 21:15:14 -04:00
KBS 10aadd57f7 Reject trailing characters in URL port numbers (#2593)
parse_port checked only the error code of from_chars, which stops at
the first non-digit, so http://host:80abc was accepted as port 80, and
a redirect Location with such a port was followed. RFC 3986 defines
port as *DIGIT. Require the whole string to be consumed, as #2590 does
for quality values.
2026-10-02 21:07:34 -04:00
yhirose 43863e1f67 Make CryptoAPI the only chain verifier when Windows verification is on (#2604)
Every backend loads the Windows ROOT and CA stores, but Windows adds a
root to them only when CryptoAPI needs it to build a chain. On a machine
that had not needed a root yet, the backend rejected the chain before
the CryptoAPI check ran, so Windows never fetched the root (for example
OpenSSL error 20 for accounts.spotify.com under Starfield Root G2).

With Windows verification enabled, the backend's chain verdict is no
longer used. Mbed TLS and wolfSSL skip chain verification during the
handshake, and the post-handshake verify result is ignored. The
CryptoAPI check becomes mandatory: a leaf that cannot be encoded now
fails the connection instead of skipping the check, and the chain must
allow server authentication, which the backends used to check.

A server certificate verifier works on the backend's chain verification,
so when one is set the backend still decides and CryptoAPI only adds its
own check, as before.

ClientCertMissing now disables hostname verification: cert.pem does not
match HOST, and on Windows the hostname check fails before the chain
check.

Refs #2596
2026-10-02 20:48:47 -04:00
yhirose 0db1df7cf2 Pass the server's intermediates to Windows certificate verification (#2602)
CryptoAPI got only the leaf, so it fetched an issuer from the leaf's AIA
URL instead of using the intermediates the server sent. For
accounts.spotify.com that issuer chains to Certainly Root R1, which
Windows does not trust, while the server's own chain ends at Starfield
Root G2.

Add tls::get_peer_certs(), which returns the certificates the peer sent
in the same way get_ca_certs() returns the CA certificates, for every
backend. The CryptoAPI check puts them into a memory store that it
passes to CertGetCertificateChain(), skipping any certificate that
cannot be added. wolfSSL keeps the received chain only when built with
SESSION_CERTS; without it, CryptoAPI still gets the leaf alone.

Refs #2596
2026-10-02 20:48:10 -04:00
8 changed files with 1179 additions and 424 deletions

No files matched your search

+1 -1
View File
@@ -119,7 +119,7 @@ using SubProtocolSelector =
std::function<std::string(const std::vector<std::string> &protocols)>;
```
The `SubProtocolSelector` receives the list of subprotocols proposed by the client (from the `Sec-WebSocket-Protocol` header) and returns the selected one. Return an empty string to decline all proposed subprotocols.
The `SubProtocolSelector` receives the list of subprotocols proposed by the client (from the `Sec-WebSocket-Protocol` header) and returns the selected one. Return an empty string to decline all proposed subprotocols. A returned value that the client did not propose is ignored.
### WebSocket (Server-side)
+1 -1
View File
@@ -230,7 +230,7 @@ cpp-httplib automatically integrates with the OS certificate store on macOS and
| Platform | Behavior | Disable (compile time) |
| :------- | :------- | :--------------------- |
| macOS | Loads system certs from Keychain (link `CoreFoundation` and `Security` with `-framework`). Requires Apple Clang; GCC is not supported for this feature. | `CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES` |
| Windows | Verifies certs via CryptoAPI (`CertGetCertificateChain` / `CertVerifyCertificateChainPolicy`) with revocation checking | `CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE` |
| Windows | Verifies the certificate chain with CryptoAPI (`CertGetCertificateChain` / `CertVerifyCertificateChainPolicy`) instead of the TLS backend. Revocation checking is best-effort: a revoked server certificate is rejected, while one whose revocation status cannot be determined is accepted. Windows fetches missing roots and intermediates on demand. With a custom CA, the TLS backend verifies the chain instead; with `set_server_certificate_verifier()`, both do. | `CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE` |
On Windows, verification can also be disabled at runtime:
+1 -1
View File
@@ -4,7 +4,7 @@ langs = ["en", "ja"]
[site]
title = "cpp-httplib"
version = "0.58.0"
version = "0.60.1"
hostname = "https://yhirose.github.io"
base_path = "/cpp-httplib"
footer_message = "© 2026 Yuji Hirose. All rights reserved."
+335 -189
View File
@@ -8,8 +8,8 @@
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.58.0"
#define CPPHTTPLIB_VERSION_NUM "0x003a00"
#define CPPHTTPLIB_VERSION "0.60.1"
#define CPPHTTPLIB_VERSION_NUM "0x003c01"
#ifdef _WIN32
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
@@ -831,14 +831,21 @@ inline from_chars_result<double> from_chars(const char *first, const char *last,
return {p, std::errc{}};
}
inline bool parse_port(const char *s, size_t len, int &port) {
inline bool parse_int_in_range(const char *s, size_t len, int lo, int hi,
int &out) {
int val = 0;
auto r = from_chars(s, s + len, val);
if (r.ec != std::errc{} || val < 1 || val > 65535) { return false; }
port = val;
if (r.ec != std::errc{} || r.ptr != s + len || val < lo || val > hi) {
return false;
}
out = val;
return true;
}
inline bool parse_port(const char *s, size_t len, int &port) {
return parse_int_in_range(s, len, 1, 65535, port);
}
inline bool parse_port(const std::string &s, int &port) {
return parse_port(s.data(), s.size(), port);
}
@@ -3442,6 +3449,9 @@ private:
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
bool enable_windows_cert_verification_ = true;
// Like ca_cert_store_set_, tracks what ctx_ cannot report back: whether
// set_server_certificate_verifier() installed a verifier.
bool server_certificate_verifier_set_ = false;
#endif
friend class ClientImpl;
@@ -4366,7 +4376,8 @@ public:
void stop();
private:
bool parse_sse_line(const std::string &line, SSEMessage &msg, int &retry_ms);
bool parse_sse_line(const std::string &line, SSEMessage &msg, int &retry_ms,
bool &has_data, bool &has_id);
void run_event_loop();
void dispatch_event(const SSEMessage &msg);
bool should_reconnect(int count) const;
@@ -4401,6 +4412,8 @@ private:
namespace ws {
class WebSocketClient;
enum class Opcode : uint8_t {
Continuation = 0x0,
Text = 0x1,
@@ -4502,7 +4515,7 @@ public:
private:
friend class httplib::Server;
friend class WebSocketClient;
friend class httplib::ws::WebSocketClient;
WebSocket(
Stream &strm, const Request &req, bool is_server,
@@ -4876,30 +4889,24 @@ inline void SSEClient::stop() {
}
inline bool SSEClient::parse_sse_line(const std::string &line, SSEMessage &msg,
int &retry_ms) {
int &retry_ms, bool &has_data,
bool &has_id) {
// Blank line signals end of event
if (line.empty() || line == "\r") { return true; }
if (line.empty()) { return true; }
// Lines starting with ':' are comments (ignored)
if (!line.empty() && line[0] == ':') { return false; }
if (line[0] == ':') { return false; }
// Find the colon separator
auto colon_pos = line.find(':');
if (colon_pos == std::string::npos) {
// Line with no colon is treated as field name with empty value
return false;
}
auto field = line.substr(0, colon_pos);
std::string value;
// Value starts after colon, skip optional single space
if (colon_pos + 1 < line.size()) {
if (colon_pos != std::string::npos && colon_pos + 1 < line.size()) {
auto value_start = colon_pos + 1;
if (line[value_start] == ' ') { value_start++; }
value = line.substr(value_start);
// Remove trailing \r if present
if (!value.empty() && value.back() == '\r') { value.pop_back(); }
}
// Handle known fields
@@ -4907,11 +4914,13 @@ inline bool SSEClient::parse_sse_line(const std::string &line, SSEMessage &msg,
msg.event = value;
} else if (field == "data") {
// Multiple data lines are concatenated with newlines
if (!msg.data.empty()) { msg.data += "\n"; }
if (has_data) { msg.data += "\n"; }
msg.data += value;
has_data = true;
} else if (field == "id") {
// Empty id is valid (clears the last event ID)
msg.id = value;
has_id = true;
} else if (field == "retry") {
// Parse retry interval in milliseconds
// Per the SSE spec, a value that is not all ASCII digits is ignored.
@@ -4982,6 +4991,8 @@ inline void SSEClient::run_event_loop() {
// Event receiving loop
std::string buffer;
SSEMessage current_msg;
auto has_data = false;
auto has_id = false;
while (running_.load() && result.next()) {
buffer.append(result.data(), result.size());
@@ -4995,18 +5006,26 @@ inline void SSEClient::run_event_loop() {
auto line = buffer.substr(line_start, newline_pos - line_start);
line_start = newline_pos + 1;
// Strip the \r of a CRLF line ending so that every field, including
// one without a colon, sees the same line
if (!line.empty() && line.back() == '\r') { line.pop_back(); }
// Parse the line and check if event is complete
auto event_complete =
parse_sse_line(line, current_msg, reconnect_interval_ms_);
auto event_complete = parse_sse_line(
line, current_msg, reconnect_interval_ms_, has_data, has_id);
if (event_complete && !current_msg.data.empty()) {
// Update last_event_id for reconnection
if (!current_msg.id.empty()) { last_event_id_ = current_msg.id; }
if (event_complete) {
// Update last_event_id for reconnection, even for an event that
// has no data. An empty id clears it.
if (has_id) { last_event_id_ = current_msg.id; }
// Dispatch event to appropriate handler
dispatch_event(current_msg);
// An event without a data field is not dispatched
if (has_data) { dispatch_event(current_msg); }
// Reset the message for the next event either way
current_msg.clear();
has_data = false;
has_id = false;
}
}
@@ -5120,6 +5139,9 @@ bool is_peer_closed(session_t session, socket_t sock);
// Certificate verification
cert_t get_peer_cert(const_session_t session);
// The certificates the peer sent, leaf first. Free each with free_cert(), and
// do not use them after free_session(), as with get_peer_cert().
size_t get_peer_certs(const_session_t session, std::vector<cert_t> &certs);
void free_cert(cert_t cert);
bool verify_hostname(cert_t cert, const char *hostname);
uint64_t hostname_mismatch_code();
@@ -6608,18 +6630,30 @@ inline bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
return false;
}
template <typename T>
inline bool
process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
size_t keep_alive_max_count,
time_t keep_alive_timeout_sec, T callback) {
// `has_buffered_request` reports whether the connection's stream already holds
// bytes of the next request. A client may pipeline its requests (RFC 9112
// 9.3.2), so reading one request can pull the start of the next one into the
// stream's buffer; that request must be served without waiting for the socket
// to become readable again, since its bytes are no longer on the socket.
// `callback` is told whether keep_alive() has just seen the socket go readable.
template <typename P, typename T>
inline bool process_server_socket_core(const std::atomic<socket_t> &svr_sock,
socket_t sock,
size_t keep_alive_max_count,
time_t keep_alive_timeout_sec,
P has_buffered_request, T callback) {
assert(keep_alive_max_count > 0);
auto ret = false;
auto count = keep_alive_max_count;
while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) {
while (count > 0) {
auto socket_readable = false;
if (!has_buffered_request()) {
if (!keep_alive(svr_sock, sock, keep_alive_timeout_sec)) { break; }
socket_readable = true;
}
auto close_connection = count == 1;
auto connection_closed = false;
ret = callback(close_connection, connection_closed);
ret = callback(socket_readable, close_connection, connection_closed);
if (!ret || connection_closed) { break; }
count--;
}
@@ -6633,14 +6667,16 @@ process_server_socket(const std::atomic<socket_t> &svr_sock, socket_t sock,
time_t keep_alive_timeout_sec, time_t read_timeout_sec,
time_t read_timeout_usec, time_t write_timeout_sec,
time_t write_timeout_usec, T callback) {
// One stream per connection: its read buffer can already hold the start of
// the next, pipelined request.
SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
write_timeout_sec, write_timeout_usec);
return process_server_socket_core(
svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
[&](bool close_connection, bool &connection_closed) {
SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
write_timeout_sec, write_timeout_usec);
// process_server_socket_core() only gets here once keep_alive() has
// seen the socket go readable.
strm.set_readable_hint();
[&]() { return strm.is_readable(); },
[&](bool socket_readable, bool close_connection,
bool &connection_closed) {
if (socket_readable) { strm.set_readable_hint(); }
return callback(strm, close_connection, connection_closed);
});
}
@@ -8238,9 +8274,11 @@ struct WebSocketUpgradeResponse {
std::string selected_subprotocol;
};
inline bool read_websocket_upgrade_response(Stream &strm,
const std::string &expected_accept,
WebSocketUpgradeResponse &upgrade) {
inline bool
read_websocket_upgrade_response(Stream &strm,
const std::string &expected_accept,
const std::string &offered_subprotocols,
WebSocketUpgradeResponse &upgrade) {
// Read status line
const auto bufsiz = 2048;
char buf[bufsiz];
@@ -8297,6 +8335,22 @@ inline bool read_websocket_upgrade_response(Stream &strm,
upgrade.selected_subprotocol = proto_it->second;
}
// Verify the subprotocol is one the client offered (RFC 6455 4.1)
if (!upgrade.selected_subprotocol.empty()) {
auto was_offered = false;
split(offered_subprotocols.data(),
offered_subprotocols.data() + offered_subprotocols.size(), ',',
[&](const char *b, const char *e) {
if (std::string(b, e) == upgrade.selected_subprotocol) {
was_offered = true;
}
});
if (!was_offered) {
upgrade.error = Error::WebSocketHandshake;
return false;
}
}
return true;
}
@@ -8942,6 +8996,9 @@ inline bool redirect(T &cli, Request &req, Response &res,
new_req.method = "GET";
new_req.body.clear();
new_req.headers.clear();
new_req.content_length_ = 0;
new_req.content_provider_ = nullptr;
new_req.is_chunked_content_provider_ = false;
}
Response new_res;
@@ -9855,8 +9912,10 @@ inline bool range_error(Request &req, Response &res) {
last_pos = content_len;
}
// RFC 9110 14.1.2: a suffix-length longer than the representation
// selects the entire representation.
if (first_pos == -1) {
first_pos = content_len - last_pos;
first_pos = (std::max)(static_cast<ssize_t>(0), content_len - last_pos);
last_pos = content_len - 1;
}
@@ -10099,6 +10158,19 @@ inline std::string unescape_quoted_pairs(const std::string &s) {
return out;
}
// Inverse of unescape_quoted_pairs: prepares a value to sit inside a
// quoted-string. RFC 9110 §5.6.4 requires a literal '\' or '"' to be sent as a
// quoted-pair, so the recipient recovers the original value.
inline std::string escape_quoted_pairs(const std::string &s) {
std::string out;
out.reserve(s.size());
for (auto c : s) {
if (c == '\\' || c == '"') { out += '\\'; }
out += c;
}
return out;
}
inline bool parse_www_authenticate(const Response &res,
std::map<std::string, std::string> &auth,
bool is_proxy) {
@@ -10278,7 +10350,10 @@ inline bool perform_websocket_handshake(Stream &strm, Request &req,
// Verify 101 response and Sec-WebSocket-Accept header
auto expected_accept = websocket_accept_key(client_key);
return read_websocket_upgrade_response(strm, expected_accept, upgrade);
auto offered_subprotocols =
get_combined_header_value(req.headers, "Sec-WebSocket-Protocol");
return read_websocket_upgrade_response(strm, expected_accept,
offered_subprotocols, upgrade);
}
inline bool is_ip_address(const std::string &host) {
@@ -10555,13 +10630,16 @@ inline bool process_server_socket_ssl(
socket_t sock, size_t keep_alive_max_count, time_t keep_alive_timeout_sec,
time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
time_t write_timeout_usec, T callback) {
// See process_server_socket(). The TLS library keeps already decrypted bytes
// of a pipelined request, which keep_alive() cannot see on the socket.
SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
write_timeout_sec, write_timeout_usec);
return process_server_socket_core(
svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
[&](bool close_connection, bool &connection_closed) {
SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
write_timeout_sec, write_timeout_usec);
// See the non-TLS path in process_server_socket().
strm.set_readable_hint();
[&]() { return strm.is_readable(); },
[&](bool socket_readable, bool close_connection,
bool &connection_closed) {
if (socket_readable) { strm.set_readable_hint(); }
return callback(strm, close_connection, connection_closed);
});
}
@@ -10602,7 +10680,13 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
}
std::string algo = "MD5";
if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
if (auth.find("algorithm") != auth.end()) {
// algorithm is an unquoted token (RFC 7616 §3.4). A server value that is
// not a token would otherwise be emitted verbatim and could carry commas
// or quotes that inject further auth-params into the header below.
const auto &a = auth.at("algorithm");
if (fields::is_token(a)) { algo = a; }
}
std::string response;
{
@@ -10625,14 +10709,23 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
auto field = "Digest username=\"" + username + "\", realm=\"" +
auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
"\", uri=\"" + req.path + "\", algorithm=" + algo +
(qop.empty() ? ", response=\""
: ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
cnonce + "\", response=\"") +
response + "\"" +
(opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
// Every value placed inside a quoted-string is escaped so a '"' in it cannot
// close the string early. realm, nonce and opaque come straight from the
// server's challenge (parse_www_authenticate() already de-escaped them), so
// without this a crafted challenge injects extra auth-params into the header.
auto field =
"Digest username=\"" + detail::escape_quoted_pairs(username) +
"\", realm=\"" + detail::escape_quoted_pairs(auth.at("realm")) +
"\", nonce=\"" + detail::escape_quoted_pairs(auth.at("nonce")) +
"\", uri=\"" + detail::escape_quoted_pairs(req.path) +
"\", algorithm=" + algo +
(qop.empty() ? ", response=\""
: ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + cnonce +
"\", response=\"") +
response + "\"" +
(opaque.empty()
? ""
: ", opaque=\"" + detail::escape_quoted_pairs(opaque) + "\"");
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
return std::make_pair(key, field);
@@ -10697,7 +10790,7 @@ inline bool match_hostname(const std::string &pattern,
#ifdef _WIN32
// Verify certificate using Windows CertGetCertificateChain API.
// This provides real-time certificate validation with Windows Update
// integration, independent of the TLS backend (OpenSSL or MbedTLS).
// integration, independent of the TLS backend.
inline bool verify_cert_with_windows_schannel(
const std::vector<unsigned char> &der_cert, const std::string &hostname,
bool verify_hostname, uint64_t &out_error, tls::const_session_t session) {
@@ -10718,28 +10811,37 @@ inline bool verify_cert_with_windows_schannel(
auto cert_guard =
scope_exit([&] { CertFreeCertificateContext(cert_context); });
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
// Add the intermediates the server sent. Without them CryptoAPI follows the
// leaf's AIA URL, which may lead to an issuer under an untrusted root.
// Give CryptoAPI the certificates the server sent. Without them it follows
// the leaf's AIA URL, which may lead to an issuer under an untrusted root.
std::vector<tls::cert_t> peer_certs;
tls::get_peer_certs(session, peer_certs);
auto store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, nullptr);
auto store_guard = scope_exit([&] { CertCloseStore(store, 0); });
auto sk = SSL_get_peer_cert_chain(static_cast<const SSL *>(session));
for (int i = 1; sk && i < sk_X509_num(sk); i++) {
auto store_guard = scope_exit([&] {
for (auto cert : peer_certs) {
tls::free_cert(cert);
}
if (store) { CertCloseStore(store, 0); }
});
for (auto cert : peer_certs) {
std::vector<unsigned char> der;
tls::get_cert_der(sk_X509_value(sk, i), der);
CertAddEncodedCertificateToStore(store, X509_ASN_ENCODING, der.data(),
static_cast<DWORD>(der.size()),
CERT_STORE_ADD_USE_EXISTING, nullptr);
if (store && tls::get_cert_der(cert, der)) {
CertAddEncodedCertificateToStore(store, X509_ASN_ENCODING, der.data(),
static_cast<DWORD>(der.size()),
CERT_STORE_ADD_USE_EXISTING, nullptr);
}
}
#else
(void)session;
auto store = cert_context->hCertStore;
#endif
// Setup chain parameters
CERT_CHAIN_PARA chain_para = {};
chain_para.cbSize = sizeof(chain_para);
// Require the server authentication usage along the chain, which also
// rejects roots that Windows trusts only for other purposes.
LPSTR server_auth = const_cast<LPSTR>(szOID_PKIX_KP_SERVER_AUTH);
chain_para.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
chain_para.RequestedUsage.Usage.cUsageIdentifier = 1;
chain_para.RequestedUsage.Usage.rgpszUsageIdentifier = &server_auth;
// Build certificate chain with revocation checking
PCCERT_CHAIN_CONTEXT chain_context = nullptr;
auto chain_result = CertGetCertificateChain(
@@ -10756,12 +10858,6 @@ inline bool verify_cert_with_windows_schannel(
auto chain_guard =
scope_exit([&] { CertFreeCertificateChain(chain_context); });
// Check if chain has errors
if (chain_context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) {
out_error = chain_context->TrustStatus.dwErrorStatus;
return false;
}
// Verify SSL policy
SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para = {};
extra_policy_para.cbSize = sizeof(extra_policy_para);
@@ -10851,6 +10947,9 @@ struct ClientTlsSessionOptions {
// The caller decides whether Schannel has anything to say about this
// connection; see SSLClient::initialize_ssl().
bool windows_cert_verification = false;
// A server certificate verifier works on the backend's chain verification,
// so the backend keeps deciding and Schannel only adds its own check.
bool server_certificate_verifier_set = false;
#endif
};
@@ -10885,12 +10984,24 @@ inline bool setup_client_tls_session(
return fail(Error::SSLConnection, 0, 0);
}
// With Windows verification on and no server certificate verifier set,
// Schannel is the only chain verifier. The backend's trust store is a
// snapshot of the Windows stores that lacks the roots Windows fetches on
// demand, so the backend's verdict is not used.
auto windows_verifies_chain = false;
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
windows_verifies_chain = options.windows_cert_verification &&
!options.server_certificate_verifier_set;
#endif
#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
// Mbed TLS and wolfSSL need the verification mode set explicitly; OpenSSL
// uses SSL_VERIFY_NONE and does all verification post-handshake. Chain
// verification happens during the handshake even for IP hosts; the
// certificate identity is verified post-handshake via verify_hostname().
set_verify_client(ctx, server_certificate_verification);
// uses SSL_VERIFY_NONE and does all verification post-handshake. Unless
// Schannel verifies the chain instead, chain verification happens during
// the handshake even for IP hosts; the certificate identity is verified
// post-handshake via verify_hostname().
set_verify_client(ctx,
server_certificate_verification && !windows_verifies_chain);
#endif
{
@@ -10935,10 +11046,12 @@ inline bool setup_client_tls_session(
if (verification_status == SSLVerifierResponse::NoDecisionMade &&
server_certificate_verification) {
auto verify_result = get_verify_result(session);
if (verify_result != 0) {
return fail(Error::SSLServerVerification, 0,
static_cast<uint64_t>(verify_result));
if (!windows_verifies_chain) {
auto verify_result = get_verify_result(session);
if (verify_result != 0) {
return fail(Error::SSLServerVerification, 0,
static_cast<uint64_t>(verify_result));
}
}
auto server_cert = get_peer_cert(session);
@@ -10958,18 +11071,17 @@ inline bool setup_client_tls_session(
}
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
// Additional Windows Schannel verification.
// This provides real-time certificate validation with Windows Update
// integration, working with both OpenSSL and MbedTLS backends.
// Windows Schannel verification, which lets Windows fetch missing roots
// and intermediates on demand. It must not be skipped: unless a server
// certificate verifier is set, it is the only chain check.
if (options.windows_cert_verification) {
std::vector<unsigned char> der;
if (get_cert_der(server_cert, der)) {
uint64_t wincrypt_error = 0;
if (!verify_cert_with_windows_schannel(
der, host, options.server_hostname_verification, wincrypt_error,
session)) {
return fail(Error::SSLServerVerification, 0, wincrypt_error);
}
uint64_t wincrypt_error = 0;
if (!get_cert_der(server_cert, der) ||
!verify_cert_with_windows_schannel(
der, host, options.server_hostname_verification, wincrypt_error,
session)) {
return fail(Error::SSLServerVerification, 0, wincrypt_error);
}
}
#endif
@@ -12437,14 +12549,10 @@ inline bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out) {
struct in_addr v4;
if (inet_pton(AF_INET, addr_part.c_str(), &v4) == 1) {
int prefix = 32;
if (!prefix_part.empty()) {
auto r = from_chars(prefix_part.data(),
prefix_part.data() + prefix_part.size(), prefix);
if (r.ec != std::errc{} ||
r.ptr != prefix_part.data() + prefix_part.size()) {
return false;
}
if (prefix < 0 || prefix > 32) { return false; }
if (!prefix_part.empty() &&
!parse_int_in_range(prefix_part.data(), prefix_part.size(), 0, 32,
prefix)) {
return false;
}
out.kind = NoProxyKind::IPv4Cidr;
std::memcpy(out.net.data(), &v4, sizeof(v4));
@@ -12456,14 +12564,10 @@ inline bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out) {
struct in6_addr v6;
if (inet_pton(AF_INET6, addr_part.c_str(), &v6) == 1) {
int prefix = 128;
if (!prefix_part.empty()) {
auto r = from_chars(prefix_part.data(),
prefix_part.data() + prefix_part.size(), prefix);
if (r.ec != std::errc{} ||
r.ptr != prefix_part.data() + prefix_part.size()) {
return false;
}
if (prefix < 0 || prefix > 128) { return false; }
if (!prefix_part.empty() &&
!parse_int_in_range(prefix_part.data(), prefix_part.size(), 0, 128,
prefix)) {
return false;
}
out.kind = NoProxyKind::IPv6Cidr;
std::memcpy(out.net.data(), &v6, sizeof(v6));
@@ -14386,6 +14490,13 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
// Connection has been closed on client
if (!line_reader.getline()) { return false; }
// RFC 9112 2.2: ignore an empty line received before the request-line. Some
// clients send an extra CRLF after a request body, which would otherwise be
// parsed as the next request on a persistent connection.
if (strcmp(line_reader.ptr(), "\r\n") == 0 && !line_reader.getline()) {
return false;
}
Request req;
req.start_time_ = std::chrono::steady_clock::now();
req.remote_addr = remote_addr;
@@ -14579,6 +14690,12 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
protocols.emplace_back(b, e);
});
selected_subprotocol = entry.sub_protocol_selector(protocols);
// Ignore a selection the client did not offer (RFC 6455 4.2.2)
if (std::find(protocols.begin(), protocols.end(),
selected_subprotocol) == protocols.end()) {
selected_subprotocol.clear();
}
}
}
@@ -15649,7 +15766,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
if (next_host.empty()) { next_host = host_; }
if (next_path.empty()) { next_path = "/"; }
auto path = decode_path_component(next_path) + next_query;
auto path = std::move(next_path) + next_query;
// Same host redirect - use current client
if (next_scheme == scheme && next_host == host_ && next_port == port_) {
@@ -15904,9 +16021,9 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
// Write request line and headers
if (detail::write_request_line(bstrm, req.method, path_with_query) < 0) {
// A rejected method (not a token, e.g. carrying CR/LF) or target (e.g.
// CR/LF smuggled in via a decoded redirect Location under
// set_path_encode(false)) must fail the request cleanly instead of
// emitting a request-line-less, header-injecting request.
// CR/LF in a caller-supplied path under set_path_encode(false)) must
// fail the request cleanly instead of emitting a request-line-less,
// header-injecting request.
error = Error::Write;
rejected_locally = true;
output_error_log(error, &req);
@@ -18510,6 +18627,9 @@ inline void SSLClient::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
inline void
SSLClient::set_server_certificate_verifier(tls::VerifyCallback verifier) {
if (!ctx_) { return; }
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
server_certificate_verifier_set_ = static_cast<bool>(verifier);
#endif
tls::set_verify_callback(ctx_, verifier);
}
@@ -18571,6 +18691,9 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
enable_windows_cert_verification_ &&
system_ca_mode_ != SystemCAMode::Disabled && ca_cert_file_path_.empty() &&
ca_cert_dir_path_.empty() && ca_cert_pem_.empty() && !ca_cert_store_set_;
// Only a verifier set through set_server_certificate_verifier() is seen
// here, not one installed with tls::set_verify_callback() directly.
options.server_certificate_verifier_set = server_certificate_verifier_set_;
#endif
tls::session_t session = nullptr;
@@ -19540,6 +19663,24 @@ inline cert_t get_peer_cert(const_session_t session) {
static_cast<SSL *>(const_cast<void *>(session))));
}
inline size_t get_peer_certs(const_session_t session,
std::vector<cert_t> &certs) {
certs.clear();
if (!session) { return 0; }
auto ssl = static_cast<const SSL *>(session);
// On the server side, the chain leaves out the peer's own certificate
if (SSL_is_server(ssl)) {
if (auto leaf = get_peer_cert(session)) { certs.push_back(leaf); }
}
auto sk = SSL_get_peer_cert_chain(ssl);
for (int i = 0; sk && i < sk_X509_num(sk); i++) {
auto x509 = sk_X509_value(sk, i);
X509_up_ref(x509);
certs.push_back(static_cast<cert_t>(x509));
}
return certs.size();
}
inline void free_cert(cert_t cert) {
if (cert) { X509_free(static_cast<X509 *>(cert)); }
}
@@ -20884,6 +21025,18 @@ inline cert_t get_peer_cert(const_session_t session) {
return const_cast<mbedtls_x509_crt *>(cert);
}
inline size_t get_peer_certs(const_session_t session,
std::vector<cert_t> &certs) {
certs.clear();
// Mbed TLS parses the whole received chain into a list headed by the peer
// certificate, owned by the session like get_peer_cert()'s result
for (auto crt = static_cast<mbedtls_x509_crt *>(get_peer_cert(session));
crt && crt->raw.len > 0; crt = crt->next) {
certs.push_back(static_cast<cert_t>(crt));
}
return certs.size();
}
inline void free_cert(cert_t cert) {
// Mbed TLS: peer certificate is owned by the SSL context.
// No-op here, but callers should still call this for cross-backend
@@ -20901,29 +21054,24 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
auto ip_len = impl::parse_ip_address(host_str, ip_bytes);
auto is_ip = ip_len > 0;
// Check Subject Alternative Names (SAN)
// In Mbed TLS 3.x, subject_alt_names contains raw values without ASN.1 tags
// - DNS names: raw string bytes
// - IP addresses: raw IP bytes (4 for IPv4, 16 for IPv6)
// Check Subject Alternative Names (SAN). Mbed TLS keeps the GeneralName type
// in buf.tag and the raw value in buf.p / buf.len.
const mbedtls_x509_sequence *san = &mcert->subject_alt_names;
while (san != nullptr && san->buf.p != nullptr && san->buf.len > 0) {
const unsigned char *p = san->buf.p;
size_t len = san->buf.len;
auto san_type = san->buf.tag & MBEDTLS_ASN1_TAG_VALUE_MASK;
if (is_ip) {
// For an IP host, only a matching iPAddress SAN of the same family
// (4 bytes for IPv4, 16 bytes for IPv6) may authenticate it.
if (len == ip_len && memcmp(p, ip_bytes, ip_len) == 0) { return true; }
} else {
// Check if this SAN is a DNS name (printable ASCII string)
bool is_dns = len > 0;
for (size_t i = 0; i < len && is_dns; i++) {
if (p[i] < 32 || p[i] > 126) { is_dns = false; }
}
if (is_dns) {
std::string san_name(reinterpret_cast<const char *>(p), len);
if (detail::match_hostname(san_name, host_str)) { return true; }
if (san_type == MBEDTLS_X509_SAN_IP_ADDRESS && len == ip_len &&
memcmp(p, ip_bytes, ip_len) == 0) {
return true;
}
} else if (san_type == MBEDTLS_X509_SAN_DNS_NAME) {
std::string san_name(reinterpret_cast<const char *>(p), len);
if (detail::match_hostname(san_name, host_str)) { return true; }
}
san = san->next;
}
@@ -21002,65 +21150,45 @@ inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
const mbedtls_x509_sequence *cur = &x509->subject_alt_names;
while (cur != nullptr) {
if (cur->buf.len > 0) {
// Mbed TLS stores SAN as ASN.1 sequences
// The tag byte indicates the type
const unsigned char *p = cur->buf.p;
size_t len = cur->buf.len;
size_t value_len = cur->buf.len;
// First byte is the tag
unsigned char tag = *p;
p++;
len--;
// Parse length (simple single-byte length assumed)
if (len > 0 && *p < 0x80) {
size_t value_len = *p;
p++;
len--;
if (value_len <= len) {
SanEntry entry;
// ASN.1 context tags for GeneralName
switch (tag & 0x1F) {
case 2: // dNSName
entry.type = SanType::DNS;
entry.value =
std::string(reinterpret_cast<const char *>(p), value_len);
break;
case 7: // iPAddress
entry.type = SanType::IP;
if (value_len == 4) {
// IPv4
char buf[16];
snprintf(buf, sizeof(buf), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
entry.value = buf;
} else if (value_len == 16) {
// IPv6
char buf[64];
snprintf(buf, sizeof(buf),
"%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
"%02x%02x:%02x%02x:%02x%02x:%02x%02x",
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8],
p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
entry.value = buf;
}
break;
case 1: // rfc822Name (email)
entry.type = SanType::EMAIL;
entry.value =
std::string(reinterpret_cast<const char *>(p), value_len);
break;
case 6: // uniformResourceIdentifier
entry.type = SanType::URI;
entry.value =
std::string(reinterpret_cast<const char *>(p), value_len);
break;
default: entry.type = SanType::OTHER; break;
}
if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
SanEntry entry;
switch (cur->buf.tag & MBEDTLS_ASN1_TAG_VALUE_MASK) {
case MBEDTLS_X509_SAN_DNS_NAME:
entry.type = SanType::DNS;
entry.value = std::string(reinterpret_cast<const char *>(p), value_len);
break;
case MBEDTLS_X509_SAN_IP_ADDRESS:
entry.type = SanType::IP;
if (value_len == 4) {
// IPv4
char buf[16];
snprintf(buf, sizeof(buf), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
entry.value = buf;
} else if (value_len == 16) {
// IPv6
char buf[64];
snprintf(buf, sizeof(buf),
"%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
"%02x%02x:%02x%02x:%02x%02x:%02x%02x",
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9],
p[10], p[11], p[12], p[13], p[14], p[15]);
entry.value = buf;
}
break;
case MBEDTLS_X509_SAN_RFC822_NAME:
entry.type = SanType::EMAIL;
entry.value = std::string(reinterpret_cast<const char *>(p), value_len);
break;
case MBEDTLS_X509_SAN_UNIFORM_RESOURCE_IDENTIFIER:
entry.type = SanType::URI;
entry.value = std::string(reinterpret_cast<const char *>(p), value_len);
break;
default: entry.type = SanType::OTHER; break;
}
if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
}
cur = cur->next;
}
@@ -22041,6 +22169,24 @@ inline cert_t get_peer_cert(const_session_t session) {
return static_cast<cert_t>(cert);
}
inline size_t get_peer_certs(const_session_t session,
std::vector<cert_t> &certs) {
certs.clear();
if (!session) { return 0; }
// wolfSSL keeps the received chain only when built with SESSION_CERTS
#ifdef SESSION_CERTS
auto wsession =
static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
auto chain = wolfSSL_get_peer_chain(wsession->ssl);
auto count = chain ? wolfSSL_get_chain_count(chain) : 0;
for (int i = 0; i < count; i++) {
auto x509 = wolfSSL_get_chain_X509(chain, i);
if (x509) { certs.push_back(static_cast<cert_t>(x509)); }
}
#endif
return certs.size();
}
inline void free_cert(cert_t cert) {
if (cert) { wolfSSL_X509_free(static_cast<WOLFSSL_X509 *>(cert)); }
}
@@ -22684,9 +22830,9 @@ inline void WebSocket::start_heartbeat() {
if (ping_interval_sec_ == 0) { return; }
ping_thread_ = std::thread([this]() {
std::unique_lock<std::mutex> lock(ping_mutex_);
while (!closed_) {
ping_cv_.wait_for(lock, std::chrono::seconds(ping_interval_sec_));
if (closed_) { break; }
// The predicate keeps a spurious wakeup from sending a ping early
while (!ping_cv_.wait_for(lock, std::chrono::seconds(ping_interval_sec_),
[this]() { return closed_.load(); })) {
// If the peer has failed to respond to the previous pings, give up.
// RFC 6455 does not define a pong-timeout mechanism; this is an
// opt-in liveness check controlled by max_missed_pongs_.
+7
View File
@@ -146,6 +146,13 @@ if(HTTPLIB_IS_USING_OPENSSL)
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
# cert_san_types.pem: the bytes of each SAN read as the other type:
# DNS:a.zz is 97.46.122.122, IP:42.46.122.122 is "*.zz".
execute_process(
COMMAND ${OPENSSL_COMMAND} req -x509 -key key.pem -sha256 -days 3650 -nodes -subj /CN=san-types -addext subjectAltName=DNS:a.zz,IP:42.46.122.122 -out cert_san_types.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
endif()
add_subdirectory(fuzzing)
+4
View File
@@ -33,3 +33,7 @@ openssl req -x509 -key key.pem -sha256 -days 3650 -nodes -subj "/CN=127.0.0.1" -
# different address. The SAN address must match; the CN address
# must be ignored.
openssl req -x509 -key key.pem -sha256 -days 3650 -nodes -subj "/CN=::1" -addext "subjectAltName=IP:2001:db8::1" -out cert_ipv6.pem
# cert_san_types.pem: the bytes of each SAN read as the other type:
# DNS:a.zz is 97.46.122.122, IP:42.46.122.122 is "*.zz".
openssl req -x509 -key key.pem -sha256 -days 3650 -nodes -subj "/CN=san-types" -addext "subjectAltName=DNS:a.zz,IP:42.46.122.122" -out cert_san_types.pem
+11 -1
View File
@@ -137,6 +137,15 @@ cert_ipv6_pem = custom_target(
command: [openssl, 'req', '-x509', '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-subj', '/CN=::1', '-addext', 'subjectAltName=IP:2001:db8::1', '-out', '@OUTPUT@']
)
# cert_san_types.pem: the bytes of each SAN read as the other type: DNS:a.zz is
# 97.46.122.122, IP:42.46.122.122 is "*.zz".
cert_san_types_pem = custom_target(
'cert_san_types_pem',
input: key_pem,
output: 'cert_san_types.pem',
command: [openssl, 'req', '-x509', '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-subj', '/CN=san-types', '-addext', 'subjectAltName=DNS:a.zz,IP:42.46.122.122', '-out', '@OUTPUT@']
)
# Copy test files to the build directory
configure_file(input: 'ca-bundle.crt', output: 'ca-bundle.crt', copy: true)
configure_file(input: 'image.jpg', output: 'image.jpg', copy: true)
@@ -178,7 +187,8 @@ test(
client_encrypted_pbes1_key_pem,
client_encrypted_cert_pem,
cert_ip_cn_pem,
cert_ipv6_pem
cert_ipv6_pem,
cert_san_types_pem
],
workdir: meson.current_build_dir(),
timeout: 300
+819 -231
View File
File diff suppressed because it is too large. Load diff