TLS Server
Creating a TLS server for outstation instances is exactly the same process as a TCP server,
except that an extra TlsServerConfig
and an AuthorizationHandler
is required. For more details about TLS
support and the configuration options, check the TLS general information page.
tip
A server mode of operation is also supported which does not require the client certificate to contain the role extension.
The example only demonstrate the Server.CreateTlsWithAuthz
method, but there is a Server.CreateTls
which does NOT take an AuthorizationHandler
parameter
and allows an authenticated client to perform any Modbus operation.
Examples
Certificate chain configuration
- Rust
- C
- C++
- Java
- C#
let handler =
SimpleHandler::new(vec![false; 10], vec![false; 10], vec![0; 10], vec![0; 10]).wrap();
// map unit ids to a handler for processing requests
let map = ServerHandlerMap::single(UnitId::new(1), handler.clone());
let tls_config = TlsServerConfig::new(
Path::new("./certs/ca_chain/ca_cert.pem"),
Path::new("./certs/ca_chain/server_cert.pem"),
Path::new("./certs/ca_chain/server_key.pem"),
None, // no password
MinTlsVersion::V1_2,
CertificateMode::AuthorityBased,
)?;
let server = rodbus::server::spawn_tls_server_task_with_authz(
1,
"127.0.0.1:802".parse()?,
map,
ReadOnlyAuthorizationHandler::create(),
tls_config,
AddressFilter::Any,
DecodeLevel::default(),
)
.await?;
rodbus_authorization_t auth_read(uint8_t unit_id, rodbus_address_range_t range, const char* role, void* ctx)
{
return RODBUS_AUTHORIZATION_ALLOW;
}
rodbus_authorization_t auth_single_write(uint8_t unit_id, uint16_t idx, const char *role, void *ctx)
{
return RODBUS_AUTHORIZATION_DENY;
}
rodbus_authorization_t auth_multiple_writes(uint8_t unit_id, rodbus_address_range_t range, const char *role, void *ctx)
{
return RODBUS_AUTHORIZATION_DENY;
}
rodbus_authorization_handler_t auth_handler = {
.read_coils = auth_read,
.read_discrete_inputs = auth_read,
.read_holding_registers = auth_read,
.read_input_registers = auth_read,
.write_single_coil = auth_single_write,
.write_single_register = auth_single_write,
.write_multiple_coils = auth_multiple_writes,
.write_multiple_registers = auth_multiple_writes,
};
rodbus_write_handler_t write_handler = {
.write_single_coil = on_write_single_coil,
.write_single_register = on_write_single_register,
.write_multiple_coils = on_write_multiple_coils,
.write_multiple_registers = on_write_multiple_registers,
};
rodbus_device_map_t* map = rodbus_device_map_create();
rodbus_device_map_add_endpoint(map,
1, // Unit ID
write_handler, // Handler for write requests
(rodbus_database_callback_t){.callback = configure_db } // Callback for the initial state of the database
);
rodbus_tls_server_config_t tls_config = rodbus_tls_server_config_init(
"./certs/ca_chain/ca_cert.pem",
"./certs/ca_chain/server_cert.pem",
"./certs/ca_chain/server_key.pem",
"" // no password
);
rodbus_server_t* server = NULL;
rodbus_device_map_t* map = build_device_map();
rodbus_authorization_handler_t auth_handler = get_auth_handler();
rodbus_decode_level_t decode_level = rodbus_decode_level_nothing();
rodbus_address_filter_t* filter = rodbus_address_filter_any();
rodbus_param_error_t err = rodbus_server_create_tls_with_authz(runtime, "127.0.0.1", 802, filter, 100, map, tls_config, auth_handler, decode_level, &server);
rodbus_address_filter_destroy(filter);
rodbus_device_map_destroy(map);
if (err) {
printf("Unable to initialize server: %s\n", rodbus_param_error_to_string(err));
return -1;
}
// check error
class AuthorizationHandler : public rodbus::AuthorizationHandler
{
rodbus::Authorization read_coils(uint8_t unit_id, const rodbus::AddressRange& range, const char* role) override
{
return rodbus::Authorization::allow;
}
rodbus::Authorization read_discrete_inputs(uint8_t unit_id, const rodbus::AddressRange &range, const char *role) override
{
return rodbus::Authorization::allow;
}
rodbus::Authorization read_holding_registers(uint8_t unit_id, const rodbus::AddressRange &range, const char *role) override
{
return rodbus::Authorization::allow;
}
rodbus::Authorization read_input_registers(uint8_t unit_id, const rodbus::AddressRange &range, const char *role) override
{
return rodbus::Authorization::allow;
}
rodbus::Authorization write_single_coil(uint8_t unit_id, uint16_t idx, const char *role) override
{
return rodbus::Authorization::deny;
}
rodbus::Authorization write_single_register(uint8_t unit_id, uint16_t idx, const char *role) override
{
return rodbus::Authorization::deny;
}
rodbus::Authorization write_multiple_coils(uint8_t unit_id, const rodbus::AddressRange &range, const char *role) override
{
return rodbus::Authorization::deny;
}
rodbus::Authorization write_multiple_registers(uint8_t unit_id, const rodbus::AddressRange &range, const char *role) override
{
return rodbus::Authorization::deny;
}
};
auto device_map = rodbus::DeviceMap();
auto init_transaction = rodbus::functional::database_callback([](rodbus::Database& db) {
for (uint16_t i = 0; i < 10; ++i)
{
db.add_coil(i, false);
db.add_discrete_input(i, false);
db.add_holding_register(i, 0);
db.add_input_register(i, 0);
}
});
device_map.add_endpoint(1, std::make_unique<WriterHandler>(), init_transaction);
auto tls_config = rodbus::TlsServerConfig(
"./certs/ca_chain/ca_cert.pem",
"./certs/ca_chain/server_cert.pem",
"./certs/ca_chain/server_key.pem",
"" // no password
);
auto filter = rodbus::AddressFilter::any();
auto server = rodbus::Server::create_tls_with_authz(runtime, "127.0.0.1", 802, filter, 100, device_map, tls_config, std::make_unique<AuthorizationHandler>(), rodbus::DecodeLevel::nothing());
class TestAuthorizationHandler implements AuthorizationHandler
{
public Authorization readCoils(UByte unitId, AddressRange range, String role) {
return Authorization.ALLOW;
}
public Authorization readDiscreteInputs(UByte unitId, AddressRange range, String role) {
return Authorization.ALLOW;
}
public Authorization readHoldingRegisters(UByte unitId, AddressRange range, String role) {
return Authorization.ALLOW;
}
public Authorization readInputRegisters(UByte unitId, AddressRange range, String role) {
return Authorization.ALLOW;
}
public Authorization writeSingleCoil(UByte unitId, UShort idx, String role) {
return Authorization.ALLOW;
}
public Authorization writeSingleRegister(UByte unitId, UShort idx, String role) {
return Authorization.DENY;
}
public Authorization writeMultipleCoils(UByte unitId, AddressRange range, String role) {
return Authorization.DENY;
}
public Authorization writeMultipleRegisters(UByte unitId, AddressRange range, String role) {
return Authorization.DENY;
}
}
TlsServerConfig tlsConfig = new TlsServerConfig(
"./certs/ca_chain/ca_cert.pem",
"./certs/ca_chain/server_cert.pem",
"./certs/ca_chain/server_key.pem",
"" // no password
);
Server server = Server.createTlsWithAuthz(runtime, "127.0.0.1", ushort(802), AddressFilter.any(), ushort(10), map, tlsConfig, new TestAuthorizationHandler(), DecodeLevel.nothing());
class AuthorizationHandler : IAuthorizationHandler
{
public Authorization ReadCoils(byte unitId, AddressRange range, string role)
{
return Authorization.Allow;
}
public Authorization ReadDiscreteInputs(byte unitId, AddressRange range, string role)
{
return Authorization.Allow;
}
public Authorization ReadHoldingRegisters(byte unitId, AddressRange range, string role)
{
return Authorization.Allow;
}
public Authorization ReadInputRegisters(byte unitId, AddressRange range, string role)
{
return Authorization.Allow;
}
public Authorization WriteSingleCoil(byte unitId, ushort idx, string role)
{
return Authorization.Deny;
}
public Authorization WriteSingleRegister(byte unitId, ushort idx, string role)
{
return Authorization.Deny;
}
public Authorization WriteMultipleCoils(byte unitId, AddressRange range, string role)
{
return Authorization.Deny;
}
public Authorization WriteMultipleRegisters(byte unitId, AddressRange range, string role)
{
return Authorization.Deny;
}
}
var tlsConfig = new TlsServerConfig(
"./certs/ca_chain/ca_cert.pem",
"./certs/ca_chain/server_cert.pem",
"./certs/ca_chain/server_key.pem",
"" // no password
);
var server = Server.CreateTlsWithAuthz(runtime, "127.0.0.1", 802, AddressFilter.Any(), 10, map, tlsConfig, new AuthorizationHandler(), DecodeLevel.Nothing());
Self-signed certificate configuration
- Rust
- C
- C++
- Java
- C#
let handler =
SimpleHandler::new(vec![false; 10], vec![false; 10], vec![0; 10], vec![0; 10]).wrap();
// map unit ids to a handler for processing requests
let map = ServerHandlerMap::single(UnitId::new(1), handler.clone());
let tls_config = TlsServerConfig::new(
Path::new("./certs/self_signed/entity1_cert.pem"),
Path::new("./certs/self_signed/entity2_cert.pem"),
Path::new("./certs/self_signed/entity2_key.pem"),
None, // no password
MinTlsVersion::V1_2,
CertificateMode::SelfSigned,
)?;
let server = rodbus::server::spawn_tls_server_task_with_authz(
1,
"127.0.0.1:802".parse()?,
map,
ReadOnlyAuthorizationHandler::create(),
tls_config,
AddressFilter::Any,
DecodeLevel::default(),
)
.await?;
rodbus_authorization_t auth_read(uint8_t unit_id, rodbus_address_range_t range, const char* role, void* ctx)
{
return RODBUS_AUTHORIZATION_ALLOW;
}
rodbus_authorization_t auth_single_write(uint8_t unit_id, uint16_t idx, const char *role, void *ctx)
{
return RODBUS_AUTHORIZATION_DENY;
}
rodbus_authorization_t auth_multiple_writes(uint8_t unit_id, rodbus_address_range_t range, const char *role, void *ctx)
{
return RODBUS_AUTHORIZATION_DENY;
}
rodbus_authorization_handler_t auth_handler = {
.read_coils = auth_read,
.read_discrete_inputs = auth_read,
.read_holding_registers = auth_read,
.read_input_registers = auth_read,
.write_single_coil = auth_single_write,
.write_single_register = auth_single_write,
.write_multiple_coils = auth_multiple_writes,
.write_multiple_registers = auth_multiple_writes,
};
rodbus_write_handler_t write_handler = {
.write_single_coil = on_write_single_coil,
.write_single_register = on_write_single_register,
.write_multiple_coils = on_write_multiple_coils,
.write_multiple_registers = on_write_multiple_registers,
};
rodbus_device_map_t* map = rodbus_device_map_create();
rodbus_device_map_add_endpoint(map,
1, // Unit ID
write_handler, // Handler for write requests
(rodbus_database_callback_t){.callback = configure_db } // Callback for the initial state of the database
);
rodbus_tls_server_config_t tls_config = rodbus_tls_server_config_init(
"./certs/self_signed/entity1_cert.pem",
"./certs/self_signed/entity2_cert.pem",
"./certs/self_signed/entity2_key.pem",
"" // no password
);
tls_config.certificate_mode = RODBUS_CERTIFICATE_MODE_SELF_SIGNED;
rodbus_server_t* server = NULL;
rodbus_device_map_t* map = build_device_map();
rodbus_authorization_handler_t auth_handler = get_auth_handler();
rodbus_decode_level_t decode_level = rodbus_decode_level_nothing();
rodbus_address_filter_t* filter = rodbus_address_filter_any();
rodbus_param_error_t err = rodbus_server_create_tls_with_authz(runtime, "127.0.0.1", 802, filter, 100, map, tls_config, auth_handler, decode_level, &server);
rodbus_address_filter_destroy(filter);
rodbus_device_map_destroy(map);
if (err) {
printf("Unable to initialize server: %s\n", rodbus_param_error_to_string(err));
return -1;
}
// check error
class AuthorizationHandler : public rodbus::AuthorizationHandler
{
rodbus::Authorization read_coils(uint8_t unit_id, const rodbus::AddressRange& range, const char* role) override
{
return rodbus::Authorization::allow;
}
rodbus::Authorization read_discrete_inputs(uint8_t unit_id, const rodbus::AddressRange &range, const char *role) override
{
return rodbus::Authorization::allow;
}
rodbus::Authorization read_holding_registers(uint8_t unit_id, const rodbus::AddressRange &range, const char *role) override
{
return rodbus::Authorization::allow;
}
rodbus::Authorization read_input_registers(uint8_t unit_id, const rodbus::AddressRange &range, const char *role) override
{
return rodbus::Authorization::allow;
}
rodbus::Authorization write_single_coil(uint8_t unit_id, uint16_t idx, const char *role) override
{
return rodbus::Authorization::deny;
}
rodbus::Authorization write_single_register(uint8_t unit_id, uint16_t idx, const char *role) override
{
return rodbus::Authorization::deny;
}
rodbus::Authorization write_multiple_coils(uint8_t unit_id, const rodbus::AddressRange &range, const char *role) override
{
return rodbus::Authorization::deny;
}
rodbus::Authorization write_multiple_registers(uint8_t unit_id, const rodbus::AddressRange &range, const char *role) override
{
return rodbus::Authorization::deny;
}
};
auto device_map = rodbus::DeviceMap();
auto init_transaction = rodbus::functional::database_callback([](rodbus::Database& db) {
for (uint16_t i = 0; i < 10; ++i)
{
db.add_coil(i, false);
db.add_discrete_input(i, false);
db.add_holding_register(i, 0);
db.add_input_register(i, 0);
}
});
device_map.add_endpoint(1, std::make_unique<WriterHandler>(), init_transaction);
auto tls_config = rodbus::TlsServerConfig(
"./certs/self_signed/entity1_cert.pem",
"./certs/self_signed/entity2_cert.pem",
"./certs/self_signed/entity2_key.pem",
"" // no password
);
tls_config.certificate_mode = rodbus::CertificateMode::self_signed;
auto filter = rodbus::AddressFilter::any();
auto server = rodbus::Server::create_tls_with_authz(runtime, "127.0.0.1", 802, filter, 100, device_map, tls_config, std::make_unique<AuthorizationHandler>(), rodbus::DecodeLevel::nothing());
class TestAuthorizationHandler implements AuthorizationHandler
{
public Authorization readCoils(UByte unitId, AddressRange range, String role) {
return Authorization.ALLOW;
}
public Authorization readDiscreteInputs(UByte unitId, AddressRange range, String role) {
return Authorization.ALLOW;
}
public Authorization readHoldingRegisters(UByte unitId, AddressRange range, String role) {
return Authorization.ALLOW;
}
public Authorization readInputRegisters(UByte unitId, AddressRange range, String role) {
return Authorization.ALLOW;
}
public Authorization writeSingleCoil(UByte unitId, UShort idx, String role) {
return Authorization.ALLOW;
}
public Authorization writeSingleRegister(UByte unitId, UShort idx, String role) {
return Authorization.DENY;
}
public Authorization writeMultipleCoils(UByte unitId, AddressRange range, String role) {
return Authorization.DENY;
}
public Authorization writeMultipleRegisters(UByte unitId, AddressRange range, String role) {
return Authorization.DENY;
}
}
TlsServerConfig tlsConfig = new TlsServerConfig(
"./certs/self_signed/entity1_cert.pem",
"./certs/self_signed/entity2_cert.pem",
"./certs/self_signed/entity2_key.pem",
"" // no password
).withCertificateMode(CertificateMode.SELF_SIGNED);
Server server = Server.createTlsWithAuthz(runtime, "127.0.0.1", ushort(802), AddressFilter.any(), ushort(10), map, tlsConfig, new TestAuthorizationHandler(), DecodeLevel.nothing());
class AuthorizationHandler : IAuthorizationHandler
{
public Authorization ReadCoils(byte unitId, AddressRange range, string role)
{
return Authorization.Allow;
}
public Authorization ReadDiscreteInputs(byte unitId, AddressRange range, string role)
{
return Authorization.Allow;
}
public Authorization ReadHoldingRegisters(byte unitId, AddressRange range, string role)
{
return Authorization.Allow;
}
public Authorization ReadInputRegisters(byte unitId, AddressRange range, string role)
{
return Authorization.Allow;
}
public Authorization WriteSingleCoil(byte unitId, ushort idx, string role)
{
return Authorization.Deny;
}
public Authorization WriteSingleRegister(byte unitId, ushort idx, string role)
{
return Authorization.Deny;
}
public Authorization WriteMultipleCoils(byte unitId, AddressRange range, string role)
{
return Authorization.Deny;
}
public Authorization WriteMultipleRegisters(byte unitId, AddressRange range, string role)
{
return Authorization.Deny;
}
}
var tlsConfig = new TlsServerConfig(
"./certs/self_signed/entity1.pem",
"./certs/self_signed/entity2_cert.pem",
"./certs/self_signed/entity2_key.pem",
"" // no password
).WithCertificateMode(CertificateMode.SelfSigned);
var server = Server.CreateTlsWithAuthz(runtime, "127.0.0.1", 802, AddressFilter.Any(), 10, map, tlsConfig, new AuthorizationHandler(), DecodeLevel.Nothing());