use std::sync::atomic::{AtomicU16, AtomicU8, Ordering};
use std::sync::Arc;
use arc_swap::ArcSwap;
use opcua_nodes::DefaultTypeTree;
use tracing::{debug, error, warn};
use crate::authenticator::{user_pass_security_policy_id, Password};
use crate::diagnostics::{ServerDiagnostics, ServerDiagnosticsSummary};
use crate::node_manager::TypeTreeForUser;
use opcua_core::comms::url::{hostname_from_url, url_matches_except_host};
use opcua_core::handle::AtomicHandle;
use opcua_core::sync::RwLock;
use opcua_crypto::{
legacy_decrypt_secret, verify_x509_identity_token, PrivateKey, SecurityPolicy, X509,
};
use opcua_types::{
profiles, status_code::StatusCode, ActivateSessionRequest, AnonymousIdentityToken,
ApplicationDescription, ApplicationType, EndpointDescription, RegisteredServer,
ServerState as ServerStateType, SignatureData, UserNameIdentityToken, UserTokenType,
X509IdentityToken,
};
use opcua_types::{
ByteString, ContextOwned, DateTime, DecodingOptions, Error, ExtensionObject,
IssuedIdentityToken, LocalizedText, MessageSecurityMode, NamespaceMap, TypeLoader,
TypeLoaderCollection, UAString,
};
use crate::config::{ServerConfig, ServerEndpoint};
use super::authenticator::{AuthManager, UserToken};
use super::identity_token::{IdentityToken, POLICY_ID_ANONYMOUS, POLICY_ID_X509};
use super::{OperationalLimits, ServerCapabilities, ANONYMOUS_USER_TOKEN_ID};
pub struct ServerInfo {
pub application_uri: UAString,
pub product_uri: UAString,
pub application_name: LocalizedText,
pub start_time: ArcSwap<DateTime>,
pub servers: Vec<String>,
pub config: Arc<ServerConfig>,
pub server_certificate: Option<X509>,
pub server_pkey: Option<PrivateKey>,
pub(crate) operational_limits: OperationalLimits,
pub state: ArcSwap<ServerStateType>,
pub send_buffer_size: usize,
pub receive_buffer_size: usize,
pub authenticator: Arc<dyn AuthManager>,
pub type_tree: Arc<RwLock<DefaultTypeTree>>,
pub type_tree_getter: Arc<dyn TypeTreeForUser>,
pub subscription_id_handle: AtomicHandle,
pub monitored_item_id_handle: AtomicHandle,
pub secure_channel_id_handle: Arc<AtomicHandle>,
pub capabilities: ServerCapabilities,
pub service_level: Arc<AtomicU8>,
pub port: AtomicU16,
pub type_loaders: RwLock<TypeLoaderCollection>,
pub diagnostics: ServerDiagnostics,
}
impl ServerInfo {
pub fn endpoints(
&self,
endpoint_url: &UAString,
transport_profile_uris: &Option<Vec<UAString>>,
) -> Option<Vec<EndpointDescription>> {
debug!(
"Endpoints requested, transport profile uris {:?}",
transport_profile_uris
);
if let Some(ref transport_profile_uris) = *transport_profile_uris {
if !transport_profile_uris.is_empty() {
let found_binary_transport = transport_profile_uris.iter().any(|profile_uri| {
profile_uri.as_ref() == profiles::TRANSPORT_PROFILE_URI_BINARY
});
if !found_binary_transport {
error!(
"Client wants to connect with a non binary transport {:#?}",
transport_profile_uris
);
return None;
}
}
}
if let Ok(hostname) = hostname_from_url(endpoint_url.as_ref()) {
if !hostname.eq_ignore_ascii_case(&self.config.tcp_config.host) {
debug!("Endpoint url \"{}\" hostname supplied by caller does not match server's hostname \"{}\"", endpoint_url, &self.config.tcp_config.host);
}
let endpoints = self
.config
.endpoints
.values()
.map(|e| self.new_endpoint_description(e, true))
.collect();
Some(endpoints)
} else {
warn!(
"Endpoint url \"{}\" is unrecognized, using default",
endpoint_url
);
if let Some(e) = self.config.default_endpoint() {
Some(vec![self.new_endpoint_description(e, true)])
} else {
Some(vec![])
}
}
}
pub fn endpoint_exists(
&self,
endpoint_url: &str,
security_policy: SecurityPolicy,
security_mode: MessageSecurityMode,
) -> bool {
self.config
.find_endpoint(
endpoint_url,
&self.base_endpoint(),
security_policy,
security_mode,
)
.is_some()
}
pub fn new_endpoint_descriptions(
&self,
endpoint_url: &str,
) -> Option<Vec<EndpointDescription>> {
debug!("find_endpoint, url = {}", endpoint_url);
let base_endpoint_url = self.base_endpoint();
let endpoints: Vec<EndpointDescription> = self
.config
.endpoints
.iter()
.filter(|&(_, e)| {
url_matches_except_host(&e.endpoint_url(&base_endpoint_url), endpoint_url)
})
.map(|(_, e)| self.new_endpoint_description(e, false))
.collect();
if endpoints.is_empty() {
None
} else {
Some(endpoints)
}
}
fn new_endpoint_description(
&self,
endpoint: &ServerEndpoint,
all_fields: bool,
) -> EndpointDescription {
let base_endpoint_url = self.base_endpoint();
let user_identity_tokens = self.authenticator.user_token_policies(endpoint);
let (server, server_certificate) = if all_fields {
(
ApplicationDescription {
application_uri: self.application_uri.clone(),
product_uri: self.product_uri.clone(),
application_name: self.application_name.clone(),
application_type: self.application_type(),
gateway_server_uri: self.gateway_server_uri(),
discovery_profile_uri: UAString::null(),
discovery_urls: self.discovery_urls(),
},
self.server_certificate_as_byte_string(),
)
} else {
(
ApplicationDescription {
application_uri: self.application_uri.clone(),
product_uri: UAString::null(),
application_name: LocalizedText::null(),
application_type: self.application_type(),
gateway_server_uri: self.gateway_server_uri(),
discovery_profile_uri: UAString::null(),
discovery_urls: self.discovery_urls(),
},
ByteString::null(),
)
};
EndpointDescription {
endpoint_url: endpoint.endpoint_url(&base_endpoint_url).into(),
server,
server_certificate,
security_mode: endpoint.message_security_mode(),
security_policy_uri: UAString::from(endpoint.security_policy().to_uri()),
user_identity_tokens: Some(user_identity_tokens),
transport_profile_uri: UAString::from(profiles::TRANSPORT_PROFILE_URI_BINARY),
security_level: endpoint.security_level,
}
}
pub fn discovery_urls(&self) -> Option<Vec<UAString>> {
if self.config.discovery_urls.is_empty() {
None
} else {
Some(
self.config
.discovery_urls
.iter()
.map(UAString::from)
.collect(),
)
}
}
pub fn application_type(&self) -> ApplicationType {
ApplicationType::Server
}
pub fn gateway_server_uri(&self) -> UAString {
UAString::null()
}
pub fn state(&self) -> ServerStateType {
**self.state.load()
}
pub fn is_running(&self) -> bool {
self.state() == ServerStateType::Running
}
pub fn base_endpoint(&self) -> String {
format!(
"opc.tcp://{}:{}",
self.config.tcp_config.host,
self.port.load(Ordering::Relaxed)
)
}
pub fn server_certificate_as_byte_string(&self) -> ByteString {
if let Some(ref server_certificate) = self.server_certificate {
server_certificate.as_byte_string()
} else {
ByteString::null()
}
}
pub fn registered_server(&self) -> RegisteredServer {
let server_uri = self.application_uri.clone();
let product_uri = self.product_uri.clone();
let gateway_server_uri = self.gateway_server_uri();
let discovery_urls = self.discovery_urls();
let server_type = self.application_type();
let is_online = self.is_running();
let server_names = Some(vec![self.application_name.clone()]);
RegisteredServer {
server_uri,
product_uri,
server_names,
server_type,
gateway_server_uri,
discovery_urls,
semaphore_file_path: UAString::null(),
is_online,
}
}
pub async fn authenticate_endpoint(
&self,
request: &ActivateSessionRequest,
endpoint_url: &str,
security_policy: SecurityPolicy,
security_mode: MessageSecurityMode,
user_identity_token: ExtensionObject,
server_nonce: &ByteString,
) -> Result<UserToken, Error> {
if let Some(endpoint) = self.config.find_endpoint(
endpoint_url,
&self.base_endpoint(),
security_policy,
security_mode,
) {
match IdentityToken::new(user_identity_token) {
IdentityToken::None => {
error!("User identity token type unsupported");
Err(Error::new(
StatusCode::BadIdentityTokenInvalid,
"User identity token type unsupported",
))
}
IdentityToken::Anonymous(token) => {
self.authenticate_anonymous_token(endpoint, &token).await
}
IdentityToken::UserName(token) => {
self.authenticate_username_identity_token(
endpoint,
&token,
&self.server_pkey,
server_nonce,
)
.await
}
IdentityToken::X509(token) => {
self.authenticate_x509_identity_token(
endpoint,
&token,
&request.user_token_signature,
&self.server_certificate,
server_nonce,
)
.await
}
IdentityToken::IssuedToken(token) => {
self.authenticate_issued_identity_token(
endpoint,
&token,
&self.server_pkey,
server_nonce,
)
.await
}
IdentityToken::Invalid(o) => Err(Error::new(
StatusCode::BadIdentityTokenInvalid,
format!(
"User identity token type {} is unsupported",
o.body.map(|b| b.type_name()).unwrap_or("None")
),
)),
}
} else {
Err(Error::new(StatusCode::BadIdentityTokenRejected, format!(
"Cannot find endpoint that matches path \"{endpoint_url}\", security policy {security_policy:?}, and security mode {security_mode:?}"
)))
}
}
pub fn decoding_options(&self) -> DecodingOptions {
self.config.decoding_options()
}
async fn authenticate_anonymous_token(
&self,
endpoint: &ServerEndpoint,
token: &AnonymousIdentityToken,
) -> Result<UserToken, Error> {
if token.policy_id.as_ref() != POLICY_ID_ANONYMOUS {
return Err(Error::new(
StatusCode::BadIdentityTokenInvalid,
format!(
"Token doesn't possess the correct policy id. Got {}, expected {}",
token.policy_id.as_ref(),
POLICY_ID_ANONYMOUS
),
));
}
self.authenticator
.authenticate_anonymous_token(endpoint)
.await?;
Ok(UserToken(ANONYMOUS_USER_TOKEN_ID.to_string()))
}
async fn authenticate_username_identity_token(
&self,
endpoint: &ServerEndpoint,
token: &UserNameIdentityToken,
server_key: &Option<PrivateKey>,
server_nonce: &ByteString,
) -> Result<UserToken, Error> {
if !self.authenticator.supports_user_pass(endpoint) {
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
"Endpoint doesn't support username password tokens",
))
} else if token.policy_id != user_pass_security_policy_id(endpoint) {
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
"Token doesn't possess the correct policy id",
))
} else if token.user_name.is_empty() {
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
"User identify token supplied no username",
))
} else {
debug!(
"policy id = {}, encryption algorithm = {}",
token.policy_id.as_ref(),
token.encryption_algorithm.as_ref()
);
let token_password = if !token.encryption_algorithm.is_empty() {
if let Some(ref server_key) = server_key {
let decrypted =
legacy_decrypt_secret(token, server_nonce.as_ref(), server_key)?;
String::from_utf8(decrypted.value.unwrap_or_default()).map_err(|e| {
Error::new(
StatusCode::BadIdentityTokenInvalid,
format!("Failed to decode identity token to string: {e}"),
)
})?
} else {
error!("Identity token password is encrypted but no server private key was supplied");
return Err(Error::new(
StatusCode::BadIdentityTokenInvalid,
"Failed to decrypt identity token password",
));
}
} else {
token.plaintext_password()?
};
self.authenticator
.authenticate_username_identity_token(
endpoint,
token.user_name.as_ref(),
&Password::new(token_password),
)
.await
}
}
async fn authenticate_x509_identity_token(
&self,
endpoint: &ServerEndpoint,
token: &X509IdentityToken,
user_token_signature: &SignatureData,
server_certificate: &Option<X509>,
server_nonce: &ByteString,
) -> Result<UserToken, Error> {
if !self.authenticator.supports_x509(endpoint) {
error!("Endpoint doesn't support x509 tokens");
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
"Endpoint doesn't support x509 tokens",
))
} else if token.policy_id.as_ref() != POLICY_ID_X509 {
error!("Token doesn't possess the correct policy id");
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
"Token doesn't possess the correct policy id",
))
} else {
match server_certificate {
Some(ref server_certificate) => {
let user_identity_tokens = self.authenticator.user_token_policies(endpoint);
let security_policy = user_identity_tokens
.iter()
.find(|t| t.token_type == UserTokenType::Certificate)
.map(|t| SecurityPolicy::from_uri(t.security_policy_uri.as_ref()))
.unwrap_or_else(|| endpoint.security_policy());
match security_policy {
SecurityPolicy::Unknown | SecurityPolicy::None => Err(Error::new(
StatusCode::BadIdentityTokenInvalid,
"Bad security policy",
)),
security_policy => {
verify_x509_identity_token(
token,
user_token_signature,
security_policy,
server_certificate,
server_nonce.as_ref(),
)
}
}
}
None => Err(Error::new(
StatusCode::BadIdentityTokenInvalid,
"Server certificate missing, cannot validate X509 tokens",
)),
}?;
let signing_cert = X509::from_byte_string(&token.certificate_data)?;
let signing_thumbprint = signing_cert.thumbprint();
self.authenticator
.authenticate_x509_identity_token(endpoint, &signing_thumbprint)
.await
}
}
async fn authenticate_issued_identity_token(
&self,
endpoint: &ServerEndpoint,
token: &IssuedIdentityToken,
server_key: &Option<PrivateKey>,
server_nonce: &ByteString,
) -> Result<UserToken, Error> {
if !self.authenticator.supports_issued_token(endpoint) {
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
"Endpoint doesn't support issued tokens",
))
} else if token.policy_id != user_pass_security_policy_id(endpoint) {
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
"Token doesn't possess the correct policy id",
))
} else {
debug!(
"policy id = {}, encryption algorithm = {}",
token.policy_id.as_ref(),
token.encryption_algorithm.as_ref()
);
let decrypted_token = if !token.encryption_algorithm.is_empty() {
if let Some(ref server_key) = server_key {
legacy_decrypt_secret(token, server_nonce.as_ref(), server_key)?
} else {
error!("Identity token password is encrypted but no server private key was supplied");
return Err(Error::new(
StatusCode::BadIdentityTokenInvalid,
"Failed to decrypt identity token issued token",
));
}
} else {
token.token_data.clone()
};
self.authenticator
.authenticate_issued_identity_token(endpoint, &decrypted_token)
.await
}
}
pub(crate) fn initial_encoding_context(&self) -> ContextOwned {
ContextOwned::new(
NamespaceMap::new(),
self.type_loaders.read().clone(),
self.decoding_options(),
)
}
pub fn add_type_loader(&self, type_loader: Arc<dyn TypeLoader>) {
self.type_loaders.write().add(type_loader);
}
pub fn summary(&self) -> &ServerDiagnosticsSummary {
&self.diagnostics.summary
}
}