use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use std::str::FromStr;
use opcua_core::{
comms::url::url_matches_except_host,
config::Config,
};
use opcua_crypto::{CertificateStore, SecurityPolicy, Thumbprint};
use opcua_types::{
constants as opcua_types_constants, DecodingLimits, MessageSecurityMode,
service_types::ApplicationType,
UAString,
};
use crate::constants;
pub const ANONYMOUS_USER_TOKEN_ID: &str = "ANONYMOUS";
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct TcpConfig {
pub hello_timeout: u32,
pub host: String,
pub port: u16,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct ServerUserToken {
pub user: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub pass: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub x509: Option<String>,
#[serde(skip)]
pub thumbprint: Option<Thumbprint>,
}
impl ServerUserToken {
pub fn user_pass<T>(user: T, pass: T) -> Self where T: Into<String> {
ServerUserToken {
user: user.into(),
pass: Some(pass.into()),
x509: None,
thumbprint: None,
}
}
pub fn x509<T>(user: T, cert_path: &PathBuf) -> Self where T: Into<String> {
ServerUserToken {
user: user.into(),
pass: None,
x509: Some(cert_path.to_string_lossy().to_string()),
thumbprint: None,
}
}
pub fn read_thumbprint(&mut self) {
if self.is_x509() && self.thumbprint.is_none() {
if let Some(ref x509_path) = self.x509 {
let path = PathBuf::from(x509_path);
if let Ok(x509) = CertificateStore::read_cert(&path) {
self.thumbprint = Some(x509.thumbprint());
}
}
}
}
pub fn is_valid(&self, id: &str) -> bool {
let mut valid = true;
if id == ANONYMOUS_USER_TOKEN_ID {
error!("User token {} is invalid because id is a reserved value, use another value.", id);
valid = false;
}
if self.user.is_empty() {
error!("User token {} has an empty user name.", id);
valid = false;
}
if self.pass.is_some() && self.x509.is_some() {
error!("User token {} holds a password and certificate info - it cannot be both.", id);
valid = false;
} else if self.pass.is_none() && self.x509.is_none() {
error!("User token {} fails to provide a password or certificate info.", id);
valid = false;
}
valid
}
pub fn is_user_pass(&self) -> bool {
self.x509.is_none()
}
pub fn is_x509(&self) -> bool {
self.x509.is_some()
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct ServerLimits {
pub clients_can_modify_address_space: bool,
pub max_subscriptions: u32,
pub max_monitored_items_per_sub: u32,
pub max_array_length: u32,
pub max_string_length: u32,
pub max_byte_string_length: u32,
pub min_sampling_interval: f64,
pub min_publishing_interval: f64,
}
impl Default for ServerLimits {
fn default() -> Self {
Self {
max_array_length: opcua_types_constants::MAX_ARRAY_LENGTH as u32,
max_string_length: opcua_types_constants::MAX_STRING_LENGTH as u32,
max_byte_string_length: opcua_types_constants::MAX_BYTE_STRING_LENGTH as u32,
max_subscriptions: constants::DEFAULT_MAX_SUBSCRIPTIONS,
max_monitored_items_per_sub: constants::DEFAULT_MAX_MONITORED_ITEMS_PER_SUB,
clients_can_modify_address_space: false,
min_sampling_interval: constants::MIN_SAMPLING_INTERVAL,
min_publishing_interval: constants::MIN_PUBLISHING_INTERVAL,
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct ServerEndpoint {
pub path: String,
pub security_policy: String,
pub security_mode: String,
pub security_level: u8,
pub password_security_policy: Option<String>,
pub user_token_ids: BTreeSet<String>,
}
impl<'a> From<(&'a str, SecurityPolicy, MessageSecurityMode, &'a [&'a str])> for ServerEndpoint {
fn from(v: (&'a str, SecurityPolicy, MessageSecurityMode, &'a [&'a str])) -> ServerEndpoint {
ServerEndpoint {
path: v.0.into(),
security_policy: v.1.to_string(),
security_mode: v.2.to_string(),
security_level: Self::security_level(v.1, v.2),
password_security_policy: None,
user_token_ids: v.3.iter().map(|id| id.to_string()).collect(),
}
}
}
impl ServerEndpoint {
pub fn new<T>(path: T, security_policy: SecurityPolicy, security_mode: MessageSecurityMode, user_token_ids: &[String]) -> Self where T: Into<String> {
ServerEndpoint {
path: path.into(),
security_policy: security_policy.to_string(),
security_mode: security_mode.to_string(),
security_level: Self::security_level(security_policy, security_mode),
password_security_policy: None,
user_token_ids: user_token_ids.iter().map(|id| id.clone()).collect(),
}
}
fn security_level(security_policy: SecurityPolicy, security_mode: MessageSecurityMode) -> u8 {
let security_level = match security_policy {
SecurityPolicy::Basic128Rsa15 => 1,
SecurityPolicy::Aes128Sha256RsaOaep => 2,
SecurityPolicy::Basic256 => 3,
SecurityPolicy::Basic256Sha256 => 4,
SecurityPolicy::Aes256Sha256RsaPss => 5,
_ => 0
};
if security_mode == MessageSecurityMode::SignAndEncrypt {
security_level + 10
} else {
security_level
}
}
pub fn new_none<T>(path: T, user_token_ids: &[String]) -> Self where T: Into<String> {
Self::new(path, SecurityPolicy::None, MessageSecurityMode::None, user_token_ids)
}
pub fn new_basic128rsa15_sign<T>(path: T, user_token_ids: &[String]) -> Self where T: Into<String> {
Self::new(path, SecurityPolicy::Basic128Rsa15, MessageSecurityMode::Sign, user_token_ids)
}
pub fn new_basic128rsa15_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self where T: Into<String> {
Self::new(path, SecurityPolicy::Basic128Rsa15, MessageSecurityMode::SignAndEncrypt, user_token_ids)
}
pub fn new_basic256_sign<T>(path: T, user_token_ids: &[String]) -> Self where T: Into<String> {
Self::new(path, SecurityPolicy::Basic256, MessageSecurityMode::Sign, user_token_ids)
}
pub fn new_basic256_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self where T: Into<String> {
Self::new(path, SecurityPolicy::Basic256, MessageSecurityMode::SignAndEncrypt, user_token_ids)
}
pub fn new_basic256sha256_sign<T>(path: T, user_token_ids: &[String]) -> Self where T: Into<String> {
Self::new(path, SecurityPolicy::Basic256Sha256, MessageSecurityMode::Sign, user_token_ids)
}
pub fn new_basic256sha256_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self where T: Into<String> {
Self::new(path, SecurityPolicy::Basic256Sha256, MessageSecurityMode::SignAndEncrypt, user_token_ids)
}
pub fn new_aes128_sha256_rsaoaep_sign<T>(path: T, user_token_ids: &[String]) -> Self where T: Into<String> {
Self::new(path, SecurityPolicy::Aes128Sha256RsaOaep, MessageSecurityMode::Sign, user_token_ids)
}
pub fn new_aes128_sha256_rsaoaep_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self where T: Into<String> {
Self::new(path, SecurityPolicy::Aes128Sha256RsaOaep, MessageSecurityMode::SignAndEncrypt, user_token_ids)
}
pub fn new_aes256_sha256_rsapss_sign<T>(path: T, user_token_ids: &[String]) -> Self where T: Into<String> {
Self::new(path, SecurityPolicy::Aes256Sha256RsaPss, MessageSecurityMode::Sign, user_token_ids)
}
pub fn new_aes256_sha256_rsapss_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self where T: Into<String> {
Self::new(path, SecurityPolicy::Aes256Sha256RsaPss, MessageSecurityMode::SignAndEncrypt, user_token_ids)
}
pub fn is_valid(&self, id: &str, user_tokens: &BTreeMap<String, ServerUserToken>) -> bool {
let mut valid = true;
for id in &self.user_token_ids {
if id == ANONYMOUS_USER_TOKEN_ID {
continue;
}
if !user_tokens.contains_key(id) {
error!("Cannot find user token with id {}", id);
valid = false;
}
}
if let Some(ref password_security_policy) = self.password_security_policy {
let password_security_policy = SecurityPolicy::from_str(password_security_policy).unwrap();
if password_security_policy == SecurityPolicy::Unknown {
error!("Endpoint {} is invalid. Password security policy \"{}\" is invalid. Valid values are None, Basic128Rsa15, Basic256, Basic256Sha256", id, password_security_policy);
valid = false;
}
}
let security_policy = SecurityPolicy::from_str(&self.security_policy).unwrap();
let security_mode = MessageSecurityMode::from(self.security_mode.as_ref());
if security_policy == SecurityPolicy::Unknown {
error!("Endpoint {} is invalid. Security policy \"{}\" is invalid. Valid values are None, Basic128Rsa15, Basic256, Basic256Sha256, Aes128Sha256RsaOaep, Aes256Sha256RsaPss,", id, self.security_policy);
valid = false;
} else if security_mode == MessageSecurityMode::Invalid {
error!("Endpoint {} is invalid. Security mode \"{}\" is invalid. Valid values are None, Sign, SignAndEncrypt", id, self.security_mode);
valid = false;
} else if (security_policy == SecurityPolicy::None && security_mode != MessageSecurityMode::None) ||
(security_policy != SecurityPolicy::None && security_mode == MessageSecurityMode::None) {
error!("Endpoint {} is invalid. Security policy and security mode must both contain None or neither of them should (1).", id);
valid = false;
} else if security_policy != SecurityPolicy::None && security_mode == MessageSecurityMode::None {
error!("Endpoint {} is invalid. Security policy and security mode must both contain None or neither of them should (2).", id);
valid = false;
}
valid
}
pub fn security_policy(&self) -> SecurityPolicy {
SecurityPolicy::from_str(&self.security_policy).unwrap()
}
pub fn message_security_mode(&self) -> MessageSecurityMode {
MessageSecurityMode::from(self.security_mode.as_ref())
}
pub fn endpoint_url(&self, base_endpoint: &str) -> String {
format!("{}{}", base_endpoint, self.path)
}
pub fn password_security_policy(&self) -> SecurityPolicy {
let mut password_security_policy = self.security_policy();
if let Some(ref security_policy) = self.password_security_policy {
match SecurityPolicy::from_str(security_policy).unwrap() {
SecurityPolicy::Unknown => {
panic!("Password security policy {} is unrecognized", security_policy);
}
security_policy => {
password_security_policy = security_policy;
}
}
}
password_security_policy
}
pub fn supports_anonymous(&self) -> bool {
self.supports_user_token_id(ANONYMOUS_USER_TOKEN_ID)
}
pub fn supports_user_pass(&self, server_tokens: &BTreeMap<String, ServerUserToken>) -> bool {
for user_token_id in &self.user_token_ids {
if user_token_id != ANONYMOUS_USER_TOKEN_ID {
if let Some(user_token) = server_tokens.get(user_token_id) {
if user_token.is_user_pass() {
return true;
}
}
}
}
false
}
pub fn supports_x509(&self, server_tokens: &BTreeMap<String, ServerUserToken>) -> bool {
for user_token_id in &self.user_token_ids {
if user_token_id != ANONYMOUS_USER_TOKEN_ID {
if let Some(user_token) = server_tokens.get(user_token_id) {
if user_token.is_x509() {
return true;
}
}
}
}
false
}
pub fn supports_user_token_id(&self, id: &str) -> bool {
self.user_token_ids.contains(id)
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct ServerConfig {
pub application_name: String,
pub application_uri: String,
pub product_uri: String,
pub pki_dir: PathBuf,
pub create_sample_keypair: bool,
pub trust_client_certs: bool,
pub discovery_server_url: Option<String>,
pub tcp_config: TcpConfig,
pub limits: ServerLimits,
pub locale_ids: Vec<String>,
pub user_tokens: BTreeMap<String, ServerUserToken>,
pub discovery_urls: Vec<String>,
pub default_endpoint: Option<String>,
pub endpoints: BTreeMap<String, ServerEndpoint>,
}
impl Config for ServerConfig {
fn is_valid(&self) -> bool {
let mut valid = true;
if self.application_name.is_empty() {
warn!("No application was set");
}
if self.application_uri.is_empty() {
warn!("No application uri was set");
}
if self.product_uri.is_empty() {
warn!("No product uri was set");
}
if self.endpoints.is_empty() {
error!("Server configuration is invalid. It defines no endpoints");
valid = false;
}
for (id, endpoint) in &self.endpoints {
if !endpoint.is_valid(&id, &self.user_tokens) {
valid = false;
}
}
if let Some(ref default_endpoint) = self.default_endpoint {
if !self.endpoints.contains_key(default_endpoint) {
valid = false;
}
}
for (id, user_token) in &self.user_tokens {
if !user_token.is_valid(&id) {
valid = false;
}
}
if self.limits.max_array_length == 0 {
error!("Server configuration is invalid. Max array length is invalid");
valid = false;
}
if self.limits.max_string_length == 0 {
error!("Server configuration is invalid. Max string length is invalid");
valid = false;
}
if self.limits.max_byte_string_length == 0 {
error!("Server configuration is invalid. Max byte string length is invalid");
valid = false;
}
if self.discovery_urls.is_empty() {
error!("Server configuration is invalid. Discovery urls not set");
valid = false;
}
valid
}
fn application_name(&self) -> UAString { UAString::from(&self.application_name) }
fn application_uri(&self) -> UAString { UAString::from(&self.application_uri) }
fn product_uri(&self) -> UAString { UAString::from(&self.product_uri) }
fn application_type(&self) -> ApplicationType { ApplicationType::Server }
fn discovery_urls(&self) -> Option<Vec<UAString>> {
let discovery_urls: Vec<UAString> = self.discovery_urls.iter().map(|v| UAString::from(v)).collect();
Some(discovery_urls)
}
}
impl Default for ServerConfig {
fn default() -> Self {
let pki_dir = PathBuf::from("./pki");
ServerConfig {
application_name: String::new(),
application_uri: String::new(),
product_uri: String::new(),
pki_dir,
create_sample_keypair: false,
trust_client_certs: false,
discovery_server_url: None,
tcp_config: TcpConfig {
host: "127.0.0.1".to_string(),
port: constants::DEFAULT_RUST_OPC_UA_SERVER_PORT,
hello_timeout: constants::DEFAULT_HELLO_TIMEOUT_SECONDS,
},
limits: ServerLimits::default(),
user_tokens: BTreeMap::new(),
locale_ids: vec!["en".to_string()],
discovery_urls: Vec::new(),
default_endpoint: None,
endpoints: BTreeMap::new(),
}
}
}
impl ServerConfig {
pub fn new<T>(application_name: T, user_tokens: BTreeMap<String, ServerUserToken>, endpoints: BTreeMap<String, ServerEndpoint>) -> Self where T: Into<String> {
let host = "127.0.0.1".to_string();
let port = constants::DEFAULT_RUST_OPC_UA_SERVER_PORT;
let application_name = application_name.into();
let application_uri = format!("urn:{}", application_name);
let product_uri = format!("urn:{}", application_name);
let pki_dir = PathBuf::from("./pki");
let discovery_server_url = Some(constants::DEFAULT_DISCOVERY_SERVER_URL.to_string());
let discovery_urls = vec![format!("opc.tcp://{}:{}/", host, port)];
let locale_ids = vec!["en".to_string()];
ServerConfig {
application_name,
application_uri,
product_uri,
pki_dir,
create_sample_keypair: false,
trust_client_certs: false,
discovery_server_url,
tcp_config: TcpConfig {
host,
port,
hello_timeout: constants::DEFAULT_HELLO_TIMEOUT_SECONDS,
},
limits: ServerLimits::default(),
locale_ids,
user_tokens,
discovery_urls,
default_endpoint: None,
endpoints,
}
}
pub fn decoding_limits(&self) -> DecodingLimits {
DecodingLimits {
max_chunk_size: 0,
max_string_length: self.limits.max_string_length as usize,
max_byte_string_length: self.limits.max_byte_string_length as usize,
max_array_length: self.limits.max_array_length as usize,
}
}
pub fn add_endpoint(&mut self, id: &str, endpoint: ServerEndpoint) {
self.endpoints.insert(id.to_string(), endpoint);
}
pub fn read_x509_thumbprints(&mut self) {
self.user_tokens.iter_mut().for_each(|(_, token)| token.read_thumbprint());
}
pub fn base_endpoint_url(&self) -> String {
format!("opc.tcp://{}:{}", self.tcp_config.host, self.tcp_config.port)
}
pub fn default_endpoint(&self) -> Option<&ServerEndpoint> {
if let Some(ref default_endpoint) = self.default_endpoint {
self.endpoints.get(default_endpoint)
} else {
None
}
}
pub fn find_endpoint(&self, endpoint_url: &str, security_policy: SecurityPolicy, security_mode: MessageSecurityMode) -> Option<&ServerEndpoint> {
let base_endpoint_url = self.base_endpoint_url();
let endpoint = self.endpoints.iter().find(|&(_, e)| {
if url_matches_except_host(&e.endpoint_url(&base_endpoint_url), endpoint_url) {
if e.security_policy() == security_policy && e.message_security_mode() == security_mode {
trace!("Found matching endpoint for url {} - {:?}", endpoint_url, e);
true
} else {
false
}
} else {
false
}
});
endpoint.map(|endpoint| endpoint.1)
}
}