Skip to main content

opcua_server/config/
endpoint.rs

1use std::{
2    collections::{BTreeMap, BTreeSet},
3    str::FromStr,
4};
5
6use serde::{Deserialize, Serialize};
7
8use opcua_crypto::SecurityPolicy;
9use opcua_types::MessageSecurityMode;
10
11use super::server::{ServerUserToken, ANONYMOUS_USER_TOKEN_ID};
12
13#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
14/// A configured server endpoint.
15pub struct ServerEndpoint {
16    /// Endpoint path
17    pub path: String,
18    /// Security policy
19    pub security_policy: String,
20    /// Security mode
21    pub security_mode: String,
22    /// Security level, higher being more secure
23    pub security_level: u8,
24    /// Password security policy when a client supplies a user name identity token
25    pub password_security_policy: Option<String>,
26    /// User tokens
27    pub user_token_ids: BTreeSet<String>,
28}
29
30#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Hash, Eq)]
31/// Unique ID of an endpoint.
32pub struct EndpointIdentifier {
33    /// Endpoint path
34    pub path: String,
35    /// Security policy
36    pub security_policy: String,
37    /// Security mode
38    pub security_mode: String,
39}
40
41impl From<&ServerEndpoint> for EndpointIdentifier {
42    fn from(value: &ServerEndpoint) -> Self {
43        Self {
44            path: value.path.clone(),
45            security_policy: value.security_policy.clone(),
46            security_mode: value.security_mode.clone(),
47        }
48    }
49}
50
51/// Convenience method to make an endpoint from a tuple
52impl<'a> From<(&'a str, SecurityPolicy, MessageSecurityMode, &'a [&'a str])> for ServerEndpoint {
53    fn from(v: (&'a str, SecurityPolicy, MessageSecurityMode, &'a [&'a str])) -> ServerEndpoint {
54        ServerEndpoint {
55            path: v.0.into(),
56            security_policy: v.1.to_string(),
57            security_mode: v.2.to_string(),
58            security_level: Self::security_level(v.1, v.2),
59            password_security_policy: None,
60            user_token_ids: v.3.iter().map(|id| id.to_string()).collect(),
61        }
62    }
63}
64
65impl ServerEndpoint {
66    /// Create a new server endpoint.
67    pub fn new<T>(
68        path: T,
69        security_policy: SecurityPolicy,
70        security_mode: MessageSecurityMode,
71        user_token_ids: &[String],
72    ) -> Self
73    where
74        T: Into<String>,
75    {
76        ServerEndpoint {
77            path: path.into(),
78            security_policy: security_policy.to_string(),
79            security_mode: security_mode.to_string(),
80            security_level: Self::security_level(security_policy, security_mode),
81            password_security_policy: None,
82            user_token_ids: user_token_ids.iter().cloned().collect(),
83        }
84    }
85
86    /// Recommends a security level for the supplied security policy
87    fn security_level(security_policy: SecurityPolicy, security_mode: MessageSecurityMode) -> u8 {
88        let security_level = match security_policy {
89            SecurityPolicy::Basic128Rsa15 => 1,
90            SecurityPolicy::Aes128Sha256RsaOaep => 2,
91            SecurityPolicy::Basic256 => 3,
92            SecurityPolicy::Basic256Sha256 => 4,
93            SecurityPolicy::Aes256Sha256RsaPss => 5,
94            _ => 0,
95        };
96        if security_mode == MessageSecurityMode::SignAndEncrypt {
97            security_level + 10
98        } else {
99            security_level
100        }
101    }
102
103    /// Create a new unsecured server endpoint.
104    pub fn new_none<T>(path: T, user_token_ids: &[String]) -> Self
105    where
106        T: Into<String>,
107    {
108        Self::new(
109            path,
110            SecurityPolicy::None,
111            MessageSecurityMode::None,
112            user_token_ids,
113        )
114    }
115
116    #[deprecated]
117    /// Create a new server endpoint with Basic128 signature.
118    ///
119    /// # Warning
120    ///
121    /// This security mode is deprecated in the OPC-UA standard for being insecure.
122    pub fn new_basic128rsa15_sign<T>(path: T, user_token_ids: &[String]) -> Self
123    where
124        T: Into<String>,
125    {
126        Self::new(
127            path,
128            SecurityPolicy::Basic128Rsa15,
129            MessageSecurityMode::Sign,
130            user_token_ids,
131        )
132    }
133
134    #[deprecated]
135    /// Create a new server endpoint with Basic128 encryption.
136    ///
137    /// # Warning
138    ///
139    /// This security mode is deprecated in the OPC-UA standard for being insecure.
140    pub fn new_basic128rsa15_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self
141    where
142        T: Into<String>,
143    {
144        Self::new(
145            path,
146            SecurityPolicy::Basic128Rsa15,
147            MessageSecurityMode::SignAndEncrypt,
148            user_token_ids,
149        )
150    }
151
152    #[deprecated]
153    /// Create a new server endpoint with Basic256 signature.
154    ///
155    /// # Warning
156    ///
157    /// This security mode is deprecated in the OPC-UA standard for being insecure.
158    pub fn new_basic256_sign<T>(path: T, user_token_ids: &[String]) -> Self
159    where
160        T: Into<String>,
161    {
162        Self::new(
163            path,
164            SecurityPolicy::Basic256,
165            MessageSecurityMode::Sign,
166            user_token_ids,
167        )
168    }
169
170    #[deprecated]
171    /// Create a new server endpoint with Basic256 encryption.
172    ///
173    /// # Warning
174    ///
175    /// This security mode is deprecated in the OPC-UA standard for being insecure.
176    pub fn new_basic256_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self
177    where
178        T: Into<String>,
179    {
180        Self::new(
181            path,
182            SecurityPolicy::Basic256,
183            MessageSecurityMode::SignAndEncrypt,
184            user_token_ids,
185        )
186    }
187
188    #[deprecated]
189    /// Create a new server endpoint with Basic256/Sha256 signing.
190    ///
191    /// # Warning
192    ///
193    /// This security mode is deprecated in the OPC-UA standard for being insecure.
194    pub fn new_basic256sha256_sign<T>(path: T, user_token_ids: &[String]) -> Self
195    where
196        T: Into<String>,
197    {
198        Self::new(
199            path,
200            SecurityPolicy::Basic256Sha256,
201            MessageSecurityMode::Sign,
202            user_token_ids,
203        )
204    }
205
206    /// Create a new server endpoint with Basic256/Sha256 encryption.
207    ///
208    /// # Warning
209    ///
210    /// This security mode is deprecated in the OPC-UA standard for being insecure.
211    pub fn new_basic256sha256_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self
212    where
213        T: Into<String>,
214    {
215        Self::new(
216            path,
217            SecurityPolicy::Basic256Sha256,
218            MessageSecurityMode::SignAndEncrypt,
219            user_token_ids,
220        )
221    }
222
223    /// Create a new server endpoint with AES128/SHA256 RSA-OAEP signing.
224    pub fn new_aes128_sha256_rsaoaep_sign<T>(path: T, user_token_ids: &[String]) -> Self
225    where
226        T: Into<String>,
227    {
228        Self::new(
229            path,
230            SecurityPolicy::Aes128Sha256RsaOaep,
231            MessageSecurityMode::Sign,
232            user_token_ids,
233        )
234    }
235
236    /// Create a new server endpoint with AES128/SHA256 RSA-OAEP encryption.
237    pub fn new_aes128_sha256_rsaoaep_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self
238    where
239        T: Into<String>,
240    {
241        Self::new(
242            path,
243            SecurityPolicy::Aes128Sha256RsaOaep,
244            MessageSecurityMode::SignAndEncrypt,
245            user_token_ids,
246        )
247    }
248
249    /// Create a new server endpoint with AES128/SHA256 RSA-PSS signing.
250    pub fn new_aes256_sha256_rsapss_sign<T>(path: T, user_token_ids: &[String]) -> Self
251    where
252        T: Into<String>,
253    {
254        Self::new(
255            path,
256            SecurityPolicy::Aes256Sha256RsaPss,
257            MessageSecurityMode::Sign,
258            user_token_ids,
259        )
260    }
261
262    /// Create a new server endpoint with AES128/SHA256 RSA-PSS encryption.
263    pub fn new_aes256_sha256_rsapss_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self
264    where
265        T: Into<String>,
266    {
267        Self::new(
268            path,
269            SecurityPolicy::Aes256Sha256RsaPss,
270            MessageSecurityMode::SignAndEncrypt,
271            user_token_ids,
272        )
273    }
274
275    /// Validate the endpoint and return a list of validation errors.
276    pub fn validate(
277        &self,
278        id: &str,
279        user_tokens: &BTreeMap<String, ServerUserToken>,
280    ) -> Result<(), Vec<String>> {
281        let mut errors = Vec::new();
282
283        // Validate that the user token ids exist
284        for id in &self.user_token_ids {
285            // Skip anonymous
286            if id == ANONYMOUS_USER_TOKEN_ID {
287                continue;
288            }
289            if !user_tokens.contains_key(id) {
290                errors.push(format!("Cannot find user token with id {id}"));
291            }
292        }
293
294        if let Some(ref password_security_policy) = self.password_security_policy {
295            let password_security_policy =
296                SecurityPolicy::from_str(password_security_policy).unwrap();
297            if password_security_policy == SecurityPolicy::Unknown {
298                errors.push(format!("Endpoint {id} is invalid. Password security policy \"{password_security_policy}\" is invalid. Valid values are None, Basic128Rsa15, Basic256, Basic256Sha256"));
299            }
300        }
301
302        // Validate the security policy and mode
303        let security_policy = SecurityPolicy::from_str(&self.security_policy).unwrap();
304        let security_mode = MessageSecurityMode::from(self.security_mode.as_ref());
305        if security_policy == SecurityPolicy::Unknown {
306            errors.push(format!("Endpoint {} is invalid. Security policy \"{}\" is invalid. Valid values are None, Basic128Rsa15, Basic256, Basic256Sha256, Aes128Sha256RsaOaep, Aes256Sha256RsaPss,", id, self.security_policy));
307        } else if security_mode == MessageSecurityMode::Invalid {
308            errors.push(format!("Endpoint {} is invalid. Security mode \"{}\" is invalid. Valid values are None, Sign, SignAndEncrypt", id, self.security_mode));
309        } else if (security_policy == SecurityPolicy::None
310            && security_mode != MessageSecurityMode::None)
311            || (security_policy != SecurityPolicy::None
312                && security_mode == MessageSecurityMode::None)
313        {
314            errors.push(format!("Endpoint {id} is invalid. Security policy and security mode must both contain None or neither of them should (1)."));
315        } else if security_policy != SecurityPolicy::None
316            && security_mode == MessageSecurityMode::None
317        {
318            errors.push(format!("Endpoint {id} is invalid. Security policy and security mode must both contain None or neither of them should (2)."));
319        }
320
321        if errors.is_empty() {
322            Ok(())
323        } else {
324            Err(errors)
325        }
326    }
327
328    /// Get the security policy of this endpoint.
329    pub fn security_policy(&self) -> SecurityPolicy {
330        SecurityPolicy::from_str(&self.security_policy).unwrap()
331    }
332
333    /// Get the message security mode of this endpoint.
334    pub fn message_security_mode(&self) -> MessageSecurityMode {
335        MessageSecurityMode::from(self.security_mode.as_ref())
336    }
337
338    /// Get the URL of this endpoint, with `base_endpoint` as root.
339    pub fn endpoint_url(&self, base_endpoint: &str) -> String {
340        format!("{}{}", base_endpoint, self.path)
341    }
342
343    /// Returns the effective password security policy for the endpoint. This is the explicitly set password
344    /// security policy, or just the regular security policy.
345    pub fn password_security_policy(&self) -> SecurityPolicy {
346        let mut password_security_policy = self.security_policy();
347        if let Some(ref security_policy) = self.password_security_policy {
348            match SecurityPolicy::from_str(security_policy).unwrap() {
349                SecurityPolicy::Unknown => {
350                    panic!("Password security policy {security_policy} is unrecognized");
351                }
352                security_policy => {
353                    password_security_policy = security_policy;
354                }
355            }
356        }
357        password_security_policy
358    }
359}