Add SSL/TLS Certificate section to documentation

Closes #2910
This commit is contained in:
Mohammad Nejati
2025-02-26 09:53:15 +00:00
committed by Mohammad Nejati
parent 0451018f25
commit 41c1abb402
12 changed files with 604 additions and 1 deletions

View File

@ -0,0 +1,217 @@
[/
Copyright (c) 2025 Mohammad Nejati
Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Official repository: https://github.com/boostorg/beast
]
[section SSL/TLS Certificate]
[/-----------------------------------------------------------------------------]
[heading Certificate Authority]
A Certificate Authority (CA) is a trusted entity that signs digital
certificates, enabling users to verify their authenticity. Rather than storing
every individual certificate for each server (which would be impractical due to
the sheer volume and frequent renewals), users can store a limited set of root
certificates to authenticate server certificates as needed.
Boost.Asio provides various methods for loading certificate authority
certificates:
* [@boost:/doc/html/boost_asio/reference/ssl__context/add_certificate_authority.html `net::ssl::context::add_certificate_authority`]
* [@boost:/doc/html/boost_asio/reference/ssl__context/add_verify_path.html `net::ssl::context::add_verify_path`]
* [@boost:/doc/html/boost_asio/reference/ssl__context/load_verify_file.html `net::ssl::context::load_verify_file`]
* [@boost:/doc/html/boost_asio/reference/ssl__context/set_default_verify_paths.html `net::ssl::context::set_default_verify_paths`]
It is important to set up peer verification so that the TLS/SSL handshake fails
if certificate verification is unsuccessful:
[snippet_core_6]
A client must also verify that the hostname or IP address in the certificate
matches the expected one. The
[@boost:/doc/html/boost_asio/reference/ssl__host_name_verification.html
`net::ssl::host_name_verification`] helper function object can perform this
verification according to the rules described in RFC 6125:
[snippet_core_7]
A server can also request and verify a client certificate to authenticate the
client:
[snippet_core_8]
[/-----------------------------------------------------------------------------]
[heading Server Certificate]
A Server Certificate is a digital certificate that confirms a server's identity
as the legitimate destination for a client. It contains a verifiable signature
that ensures it was issued by a trusted certificate authority (CA).
When a server certificate is issued by an intermediate certificate authority,
and the client lacks those intermediate certificates, the server should provide
all the relevant certificates to the client. This allows the client to verify
the final certificate in the chain against the root certificate.
The following Boost.Asio methods can be used for loading a certificate or a
certificate chain:
* [@boost:/doc/html/boost_asio/reference/ssl__context/use_certificate.html `net::ssl::context::use_certificate`]
* [@boost:/doc/html/boost_asio/reference/ssl__context/use_certificate_file.html `net::ssl::context::use_certificate_file`]
* [@boost:/doc/html/boost_asio/reference/ssl__context/use_certificate_chain.html `net::ssl::context::use_certificate_chain`]
* [@boost:/doc/html/boost_asio/reference/ssl__context/use_certificate_chain_file.html `net::ssl::context::use_certificate_chain_file`]
[/-----------------------------------------------------------------------------]
[heading Client Certificate]
A server can authenticate clients by requiring and verifying their certificates,
preventing access for those without a valid certificate and private key. The
server enforces this by modifying peer verification settings:
[snippet_core_8]
If used, the necessary CA certificates must be loaded into the server's SSL
context to enable verification of the client's certificate.
[/-----------------------------------------------------------------------------]
[heading Common Name and Subject Alternative Name]
The Subject Alternative Name (SAN) is an extension in X.509 certificates that
allows multiple domain names, subdomains, or IP addresses to be associated with
a single SSL/TLS certificate. Before that it was the Common Name field in the
certificate subject which could contain a single hostname.
[@https://datatracker.ietf.org/doc/html/rfc6125#appendix-B.2 RFC 6125]
recommends that if a certificate includes a SAN dNSName field, the client must
ignore the subject CN field. Some modern browsers, such as Google Chrome, check
only the SAN section in an SSL/TLS certificate and reject certificates that
contain only the CN field.
[/-----------------------------------------------------------------------------]
[heading Private Key]
The private key of a certificate is required during the SSL/TLS handshake to
prove that the certificate's provider is its rightful owner
The following Boost.Asio methods can be used for loading a private key:
* [@boost:/doc/html/boost_asio/reference/ssl__context/use_private_key.html `net::ssl::context::use_private_key`]
* [@boost:/doc/html/boost_asio/reference/ssl__context/use_private_key_file.html `net::ssl::context::use_private_key_file`]
* [@boost:/doc/html/boost_asio/reference/ssl__context/use_rsa_private_key.html `net::ssl::context::use_rsa_private_key`]
* [@boost:/doc/html/boost_asio/reference/ssl__context/use_rsa_private_key_file.html `net::ssl::context::use_rsa_private_key_file`]
If the private key is secured with a password, the
[@boost:/doc/html/boost_asio/reference/ssl__context/set_password_callback.html
net::ssl::context::set_password_callback] allows specifying a callable object to
retrieve the password.
[/-----------------------------------------------------------------------------]
[heading Self-Signed and Self-Issued Certificates]
A self-issued certificate is a certificate where the issuer and subject are the
same entity.
A self-signed certificate is a self-issued certificate in which the digital
signature can be verified using the public key within the certificate.
[warning
Installing an untrusted, self-issued, or self-signed CA certificate poses a
significant security risk, as there are no restrictions on the domains for
which it can issue certificates. This allows attackers to generate
fraudulent certificates for any public domain, enabling man-in-the-middle
attacks if they gain access to your network.
]
[/-----------------------------------------------------------------------------]
[heading Diffie-Hellman (DH) Parameters]
Diffie-Hellman (DH) key exchange is a cryptographic protocol that allows two
parties to securely establish a shared secret over an insecure communication
channel. The key exchange process involves both parties agreeing on a set of
parameters, known as Diffie-Hellman parameters, which include a large prime
number `p` and a generator `g`. Since generating these parameters is a
computationally expensive task, a user might prefer to provide a precomputed
value at startup.
The following Boost.Asio methods can be used for loading DH parameters:
* [@boost:/doc/html/boost_asio/reference/ssl__context/use_tmp_dh.html `net::ssl::context::use_tmp_dh`]
* [@boost:/doc/html/boost_asio/reference/ssl__context/use_tmp_dh_file.html `net::ssl::context::use_tmp_dh_file`]
If no DH parameter is provided, OpenSSL will refuse to perform any handshake
that uses DHE-based cipher suites but will still work with other cipher suites,
such as those based on ECDHE.
[/-----------------------------------------------------------------------------]
[heading A Self-Issued Certificate Example]
In the following example, we will generate a self-signed CA certificate and use
it to issue both server and client certificates.
* Generate a CA certificate:
```
openssl req -new -newkey rsa:4096 -keyout ca.key -x509 -out ca.crt -subj "/CN=localhost" -days 365
```
* Generate a Server CSR:
```
openssl req -new -newkey rsa:4096 -keyout server.key -out server.csr -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
```
* Sign the Server CSR using our CA:
```
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -copy_extensions copy -days 365 -out server.crt
```
* Generate a Client CSR:
```
openssl req -new -newkey rsa:4096 -keyout client.key -out client.csr -subj "/CN=client.1"
```
* Sign the Client CSR using our CA:
```
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -days 365 -out client.crt
```
* Generate a DH parameters file:
```
openssl dhparam -out dh4096.pem 4096
```
Server example: [path_link example/doc/ssl/server.cpp server.cpp]
Note that the server is configured in such a way that it requests and verifies
the client certificate. You can disable this by commenting out the related line
in the example.
You can test the server using this cURL command:
```
curl https://localhost:8080 --cacert ca.crt --cert client.crt --key client.key
```
Also, you can use the client example: [path_link example/doc/ssl/client.cpp client.cpp]
[endsect]

View File

@ -96,7 +96,8 @@ effect:
[include 5_buffers.qbk]
[include 6_files.qbk]
[include 7_composed.qbk]
[include 9_ssl-tls-shutdown.qbk]
[include 9_ssl_tls_certificate.qbk]
[include 10_ssl_tls_shutdown.qbk]
[endsect]
[section:config Configuration]

View File

@ -13,4 +13,6 @@ add_subdirectory(advanced)
add_subdirectory(http)
add_subdirectory(websocket)
add_subdirectory(doc)
add_subdirectory(echo-op)

View File

@ -30,5 +30,7 @@ build-project advanced ;
build-project http ;
build-project websocket ;
build-project doc ;
# legacy
build-project echo-op ;

View File

@ -0,0 +1,12 @@
#
# Copyright (c) 2025 Mohammad Nejati
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# Official repository: https://github.com/boostorg/beast
#
if (OPENSSL_FOUND)
add_subdirectory(ssl)
endif ()

10
example/doc/Jamfile Normal file
View File

@ -0,0 +1,10 @@
#
# Copyright (c) 2025 Mohammad Nejati
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# Official repository: https://github.com/boostorg/beast
#
build-project ssl ;

View File

@ -0,0 +1,44 @@
#
# Copyright (c) 2025 Mohammad Nejati
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# Official repository: https://github.com/boostorg/beast
#
# Client
add_executable(doc-ssl-client
Jamfile
client.cpp)
source_group("" FILES
Jamfile
client.cpp)
target_include_directories(doc-ssl-client
PRIVATE ${PROJECT_SOURCE_DIR})
target_link_libraries(doc-ssl-client
PRIVATE Boost::beast OpenSSL::SSL OpenSSL::Crypto)
set_target_properties(doc-ssl-client
PROPERTIES FOLDER "example-doc-ssl")
# Server
add_executable(doc-ssl-server
Jamfile
server.cpp)
source_group("" FILES
Jamfile
server.cpp)
target_include_directories(doc-ssl-server
PRIVATE ${PROJECT_SOURCE_DIR})
target_link_libraries(doc-ssl-server
PRIVATE Boost::beast OpenSSL::SSL OpenSSL::Crypto)
set_target_properties(doc-ssl-server
PROPERTIES FOLDER "example-doc-ssl")

20
example/doc/ssl/Jamfile Normal file
View File

@ -0,0 +1,20 @@
import ac ;
project
: requirements
[ ac.check-library /boost/beast/test//lib-asio-ssl : <library>/boost/beast/test//lib-asio-ssl/<link>static : <build>no ]
;
exe doc-ssl-client :
client.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
exe doc-ssl-server :
server.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

137
example/doc/ssl/client.cpp Normal file
View File

@ -0,0 +1,137 @@
//
// Copyright (c) 2025 Mohammad Nejati
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
namespace ssl = net::ssl;
void
print_exception(std::exception_ptr eptr)
{
if(eptr)
{
try
{
std::rethrow_exception(eptr);
}
catch(std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
}
net::awaitable<void>
request(ssl::context& ctx)
{
auto executor = co_await net::this_coro::executor;
net::ip::tcp::endpoint endpoint{ {}, 8080 };
ssl::stream<net::ip::tcp::socket> stream{ executor, ctx };
// Connect TCP socket
co_await stream.lowest_layer().async_connect(endpoint);
// Set Server Name Indication (SNI)
if(!SSL_set_tlsext_host_name(stream.native_handle(), "localhost"))
{
throw beast::system_error(
static_cast<int>(::ERR_get_error()),
net::error::get_ssl_category());
}
// Set a callback to verify that the hostname in the server
// certificate matches the expected value
stream.set_verify_callback(ssl::host_name_verification("localhost"));
// Perform the SSL handshake
co_await stream.async_handshake(ssl::stream_base::client);
// Write an HTTP GET request
http::request<http::empty_body> req{ http::verb::get, "/", 11 };
req.set(http::field::host, "localhost");
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
co_await http::async_write(stream, req);
// Read the response
beast::flat_buffer buf;
http::response<http::string_body> res;
co_await http::async_read(stream, buf, res);
// Print the response body
std::cout << res.body();
// Gracefully shutdown the SSL stream
auto [ec] = co_await stream.async_shutdown(net::as_tuple);
if(ec && ec != ssl::error::stream_truncated)
throw boost::system::system_error(ec);
}
int
main()
{
try
{
// The io_context is required for all I/O
net::io_context ioc;
// The SSL context is required, and holds certificates,
// configurations and session related data
ssl::context ctx{ ssl::context::sslv23 };
// https://docs.openssl.org/3.4/man3/SSL_CTX_set_options/
ctx.set_options(
ssl::context::no_sslv2 | ssl::context::default_workarounds |
ssl::context::single_dh_use);
// set up the peer verification mode so that the TLS/SSL handshake fails
// if the certificate verification is unsuccessful
ctx.set_verify_mode(ssl::verify_peer);
// The servers's certificate will be verified against this
// certificate authority.
ctx.load_verify_file("ca.crt");
// In a real application, the passphrase would be read from
// a secure place, such as a key vault.
ctx.set_password_callback([](auto, auto) { return "123456"; });
// Client certificate and private key (if server request for).
ctx.use_certificate_chain_file("client.crt");
ctx.use_private_key_file("client.key", ssl::context::pem);
net::co_spawn(ioc, request(ctx), print_exception);
ioc.run();
}
catch(std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
#else
int
main(int, char*[])
{
std::printf("awaitables require C++20\n");
return EXIT_FAILURE;
}
#endif

133
example/doc/ssl/server.cpp Normal file
View File

@ -0,0 +1,133 @@
//
// Copyright (c) 2025 Mohammad Nejati
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
namespace ssl = net::ssl;
void
print_exception(std::exception_ptr eptr)
{
if(eptr)
{
try
{
std::rethrow_exception(eptr);
}
catch(std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
}
net::awaitable<void>
handle_session(ssl::stream<net::ip::tcp::socket> stream)
{
// Perform the SSL handshake
co_await stream.async_handshake(ssl::stream_base::server);
// Read and discard a request
beast::flat_buffer buf;
http::request<http::empty_body> req;
co_await http::async_read(stream, buf, req);
// Write the response
http::response<http::string_body> res;
res.body() = "Hello!";
co_await http::async_write(stream, res);
// Gracefully shutdown the SSL stream
auto [ec] = co_await stream.async_shutdown(net::as_tuple);
if(ec && ec != ssl::error::stream_truncated)
throw boost::system::system_error(ec);
}
net::awaitable<void>
acceptor(ssl::context& ctx)
{
auto executor = co_await net::this_coro::executor;
net::ip::tcp::endpoint endpoint{ {}, 8080 };
net::ip::tcp::acceptor acceptor{ executor, endpoint };
for(;;)
{
net::co_spawn(
executor,
handle_session({ co_await acceptor.async_accept(), ctx }),
print_exception);
}
}
int
main()
{
try
{
// The io_context is required for all I/O
net::io_context ioc;
// The SSL context is required, and holds certificates,
// configurations and session related data
ssl::context ctx{ ssl::context::sslv23 };
// https://docs.openssl.org/3.4/man3/SSL_CTX_set_options/
ctx.set_options(
ssl::context::no_sslv2 | ssl::context::default_workarounds |
ssl::context::single_dh_use);
// Comment this line to disable client certificate request.
ctx.set_verify_mode(
ssl::verify_peer | ssl::verify_fail_if_no_peer_cert);
// The client's certificate will be verified against this
// certificate authority.
ctx.load_verify_file("ca.crt");
// In a real application, the passphrase would be read from
// a secure place, such as a key vault.
ctx.set_password_callback([](auto, auto) { return "123456"; });
// Server certificate and private key.
ctx.use_certificate_chain_file("server.crt");
ctx.use_private_key_file("server.key", ssl::context::pem);
// DH parameters for DHE-based cipher suites
ctx.use_tmp_dh_file("dh4096.pem");
net::co_spawn(ioc, acceptor(ctx), print_exception);
ioc.run();
}
catch(std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
#else
int
main(int, char*[])
{
std::printf("awaitables require C++20\n");
return EXIT_FAILURE;
}
#endif

View File

@ -136,4 +136,29 @@ if(ec != net::ssl::error::stream_truncated)
}
}
void ssl_tls()
{
net::io_context ioc;
net::ssl::context ctx(net::ssl::context::tlsv12);
net::ssl::stream<tcp_stream> stream(ioc, ctx);
//[snippet_core_6
// Verify the remote server's certificate
ctx.set_verify_mode(net::ssl::verify_peer);
//]
//[snippet_core_7
// Verify the remote server's Hostname or IP address
stream.set_verify_callback(
net::ssl::host_name_verification("host.name"));
//]
//[snippet_core_8
// Verify the remote client's certificate
ctx.set_verify_mode(
net::ssl::verify_peer |
net::ssl::verify_fail_if_no_peer_cert);
//]
}
} // doc_core_snippets