OpenSSL TLS#
The aes70::openssl helpers (header aes70/openssl.hpp) wrap OpenSSL so AES70
traffic runs over TLS. They support TLS-PSK (psk_context) and certificate TLS
(cert_context), including mutual TLS (mTLS). OpenSSL is not included with
libaes70; you supply and link it. See Third-party libraries.
Prerequisites#
- OpenSSL 1.1.1 or later (TLS 1.3 support).
- Link
OpenSSL::SSLandOpenSSL::Crypto(or equivalent) with your device/controller targets. - A platform TCP integration (e.g. libuv) as for plaintext AES70.
Plaintext read chunk#
ssl_session decrypts into a scratch buffer of size AES70_OPENSSL_READ_CHUNK_SIZE
(default 16 KiB). Override at compile time if needed. AES70 PDUs are usually
much smaller than this.
Layering#
Connections are layered as:
encrypt < io < decrypt < Connection
- Device: wrap the AES70 port with
aes70::openssl::port<…, Context>. - Controller: wrap an IO
connector_factorywithaes70::openssl::connector_factory<IoFactory, Connection, Context>.
Context defaults to psk_context so existing PSK call sites stay short.
Context contract#
A TLS context must provide:
SSL_CTX *ssl_ctx()bool tls_is_server() const
It must be copyable/movable enough to live in port and in reconnecting
controller factories (both psk_context and cert_context use a shared impl).
There is no formal C++20 concept; the requirement is duck-typed.
TLS-PSK#
Configure a shared key via psk_config (key material lives in key_storage),
then build a server or client context:
aes70::openssl::psk_config cfg;
cfg.key.assign(32, 0x42); // demo only — use real key material
cfg.min_version = TLS1_2_VERSION;
cfg.max_version = TLS1_3_VERSION;
auto server_tls = aes70::openssl::psk_context::make_server(cfg);
auto client_tls = aes70::openssl::psk_context::make_client(cfg);
Device#
using Port = aes70::openssl::port<aes70::port<aes70::dynamic_device::device>>;
libuv::tcp::port<Port> tcp(
portno, "0.0.0.0", uv_default_loop(), std::move(server_tls), dev);
Controller#
auto factory =
aes70::openssl::connector_factory<libuv::tcp::connector_factory>::create(
client_tls, host, port);
auto sub = aes70::controller::observe_connection(
factory,
[](aes70::controller::device remote) {
// return subscriptions for this connection
return aes70::controller::subscription{};
},
1000u);
Full working code: examples/openssl/psk.
Certificate TLS#
cert_config takes DER (binary) material in memory — not file paths:
certificate— leaf cert (std::vector<uint8_t>)private_key— matching key (key_storage, wiped on destruction)ca_certificates— trusted CAs (list of DER blobs)
Use the helpers in aes70/openssl/pem.hpp when you have PEM files:
load_pem_certificate_file/pem_decode_certificateload_pem_private_key_file/pem_decode_private_keyload_pem_certificates_file/pem_decode_certificates(CA bundles)
| Mode | Server | Client |
|---|---|---|
| Server cert only | certificate + private_key, verify_peer=false |
ca_certificates, verify_peer=true |
| mTLS | cert + key + ca_certificates, verify_peer=true |
cert + key + ca_certificates, verify_peer=true |
Server cert only (device)#
aes70::openssl::cert_config cfg;
cfg.certificate = aes70::openssl::load_pem_certificate_file("server.crt");
cfg.private_key = aes70::openssl::load_pem_private_key_file("server.key");
cfg.verify_peer = false;
auto tls = aes70::openssl::cert_context::make_server(cfg);
using Port = aes70::openssl::port<aes70::port<aes70::dynamic_device::device>,
aes70::openssl::cert_context>;
libuv::tcp::port<Port> tcp(
portno, "0.0.0.0", uv_default_loop(), std::move(tls), dev);
Server cert only (controller)#
aes70::openssl::cert_config cfg;
cfg.ca_certificates =
aes70::openssl::load_pem_certificates_file("ca.crt");
cfg.verify_peer = true;
auto client_tls = aes70::openssl::cert_context::make_client(cfg);
using Factory = aes70::openssl::connector_factory<
libuv::tcp::connector_factory, aes70::controller::connection,
aes70::openssl::cert_context>;
auto sub = aes70::controller::observe_connection(
Factory::create(client_tls, host, port),
[](aes70::controller::device remote) {
return aes70::controller::subscription{};
},
1000u);
Demo PEMs: examples/openssl/certs/gen_certs.sh. Examples under
examples/openssl/ (server_cert, mtls).
Errors#
Three channels matter on the controller side:
- IO factory
on_error— transport failures (connect refused, socket errors). Access viaopenssl::connector_factory::io_factory().on_error(...). - OpenSSL factory
on_error— SSL-layer failures (SSL_get_error/ERR_peek_last_error), e.g. handshake / alert failures. observe_connectionon_failure— connect failed or the connection closed before OCP.1 was ready (nullptrin the failure callback). Set viaobserve_connection_config::on_failure.
Keepalive and auto-reconnect behavior are the same as for plaintext
observe_connection; see Connecting to Devices.
Examples#
| Example | Mode | Default port |
|---|---|---|
examples/openssl/psk |
TLS-PSK | 65123 |
examples/openssl/server_cert |
Server cert only | 65124 |
examples/openssl/mtls |
mTLS | 65125 |
See also examples/openssl/README.md.