Skip to main content

opcua_crypto/
user_identity.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2024 Adam Lock
4
5//! Functions related to encrypting / decrypting passwords in a UserNameIdentityToken.
6//!
7//! The code here determines how or if to encrypt the password depending on the security policy
8//! and user token policy.
9
10use std::io::{Cursor, Write};
11use std::str::FromStr;
12
13use opcua_types::{
14    encoding::{read_u32, write_u32},
15    status_code::StatusCode,
16    ByteString, UAString,
17    {SignatureData, UserNameIdentityToken, UserTokenPolicy, X509IdentityToken},
18};
19use opcua_types::{Error, IssuedIdentityToken, MessageSecurityMode};
20use tracing::{error, warn};
21
22use crate::policy::aes::{AesAsymmetricEncryptionAlgorithm, OaepSha1, OaepSha256, Pkcs1v15};
23
24use super::{PrivateKey, SecurityPolicy, X509};
25
26/// Trait for a type with a secret encrypted with legacy secret encryption.
27pub trait LegacySecret {
28    /// The raw encrypted secret.
29    fn raw_secret(&self) -> &ByteString;
30    /// The encryption algorithm used to encrypt the secret.
31    fn encryption_algorithm(&self) -> &UAString;
32}
33
34impl LegacySecret for UserNameIdentityToken {
35    fn raw_secret(&self) -> &ByteString {
36        &self.password
37    }
38
39    fn encryption_algorithm(&self) -> &UAString {
40        &self.encryption_algorithm
41    }
42}
43
44impl LegacySecret for IssuedIdentityToken {
45    fn raw_secret(&self) -> &ByteString {
46        &self.token_data
47    }
48
49    fn encryption_algorithm(&self) -> &UAString {
50        &self.encryption_algorithm
51    }
52}
53
54impl LegacySecret for LegacyEncryptedSecret {
55    fn raw_secret(&self) -> &ByteString {
56        &self.secret
57    }
58
59    fn encryption_algorithm(&self) -> &UAString {
60        &self.encryption_algorithm
61    }
62}
63
64/// Decrypt a legacy secret using the server's nonce and private key.
65pub fn legacy_decrypt_secret(
66    secret: &impl LegacySecret,
67    server_nonce: &[u8],
68    server_key: &PrivateKey,
69) -> Result<ByteString, Error> {
70    if secret.encryption_algorithm().is_empty() {
71        Ok(secret.raw_secret().clone())
72    } else {
73        // Determine the padding from the algorithm.
74        let encryption_algorithm = secret.encryption_algorithm().as_ref();
75        match encryption_algorithm {
76            super::algorithms::ENC_RSA_15 => {
77                legacy_secret_decrypt::<Pkcs1v15>(secret.raw_secret(), server_nonce, server_key)
78            }
79            super::algorithms::ENC_RSA_OAEP => {
80                legacy_secret_decrypt::<OaepSha1>(secret.raw_secret(), server_nonce, server_key)
81            }
82            super::algorithms::ENC_RSA_OAEP_SHA256 => {
83                legacy_secret_decrypt::<OaepSha256>(secret.raw_secret(), server_nonce, server_key)
84            }
85            r => {
86                error!("decrypt_user_identity_token_password has rejected unsupported user identity encryption algorithm \"{}\"", encryption_algorithm);
87                Err(Error::new(
88                    StatusCode::BadIdentityTokenInvalid,
89                    format!("Identity token rejected, unsupported encryption algorithm {r}"),
90                ))
91            }
92        }
93    }
94}
95
96/// A generic legacy encrypted secret.
97pub struct LegacyEncryptedSecret {
98    /// The user token policy the encrypted secret conforms to.
99    pub policy: UAString,
100    /// The encrypted secret.
101    pub secret: ByteString,
102    /// The encryption algorithm used to encrypt the secret.
103    pub encryption_algorithm: UAString,
104}
105
106enum EncryptionMode {
107    None,
108    AsymmetricFor(SecurityPolicy),
109}
110
111/// Encrypt a client side user's password using the server nonce and cert.
112/// This is described in part 4, 7.41 of the OPC-UA standard.
113pub fn legacy_encrypt_secret(
114    channel_security_policy: SecurityPolicy,
115    channel_security_mode: MessageSecurityMode,
116    user_token_policy: &UserTokenPolicy,
117    nonce: &[u8],
118    cert: &Option<X509>,
119    secret_to_encrypt: &[u8],
120) -> Result<LegacyEncryptedSecret, Error> {
121    let token_security_policy = if user_token_policy.security_policy_uri.is_empty() {
122        None
123    } else {
124        Some(SecurityPolicy::from_str(user_token_policy.security_policy_uri.as_ref()).unwrap())
125    };
126
127    // This is an implementation of Table 193 in OPC-UA Part 4, 7.41
128    let encryption_mode = match (
129        channel_security_policy,
130        channel_security_mode,
131        token_security_policy,
132    ) {
133        // Check for unknown security policies
134        (_, _, Some(SecurityPolicy::Unknown)) | (SecurityPolicy::Unknown, _, _) => {
135            // Unknown security policy is not allowed
136            return Err(Error::new(
137                StatusCode::BadSecurityPolicyRejected,
138                "Unknown user token security policy",
139            ));
140        }
141
142        // Table implementation begins here
143        (SecurityPolicy::None, MessageSecurityMode::None, Some(SecurityPolicy::None) | None) => {
144            EncryptionMode::None
145        }
146        (SecurityPolicy::None, MessageSecurityMode::None, Some(p)) => {
147            EncryptionMode::AsymmetricFor(p)
148        }
149        (p, MessageSecurityMode::Sign | MessageSecurityMode::SignAndEncrypt, None) => {
150            EncryptionMode::AsymmetricFor(p)
151        }
152        (_, MessageSecurityMode::SignAndEncrypt, Some(SecurityPolicy::None)) => {
153            EncryptionMode::None
154        }
155        (_, MessageSecurityMode::Sign, Some(SecurityPolicy::None)) => {
156            return Err(Error::new(
157                StatusCode::BadSecurityPolicyRejected,
158                "User token policy security policy is None but message security mode is Sign",
159            ))
160        }
161        (_, MessageSecurityMode::Sign | MessageSecurityMode::SignAndEncrypt, Some(p)) => {
162            EncryptionMode::AsymmetricFor(p)
163        }
164        // Check for invalid message security modes
165        (_, MessageSecurityMode::None | MessageSecurityMode::Invalid, _) => {
166            return Err(Error::new(
167                StatusCode::BadSecurityChecksFailed,
168                "Invalid message security mode",
169            ));
170        }
171    };
172
173    match encryption_mode {
174        EncryptionMode::None => {
175            if matches!(channel_security_policy, SecurityPolicy::None)
176                || matches!(
177                    channel_security_mode,
178                    MessageSecurityMode::None | MessageSecurityMode::Sign
179                )
180            {
181                warn!("A user identity's password is being sent over the network in plain text. This could be a serious security issue");
182            }
183            Ok(LegacyEncryptedSecret {
184                secret: ByteString::from(secret_to_encrypt),
185                encryption_algorithm: UAString::null(),
186                policy: user_token_policy.policy_id.clone(),
187            })
188        }
189        EncryptionMode::AsymmetricFor(security_policy) => {
190            let password = legacy_secret_encrypt(
191                secret_to_encrypt,
192                nonce,
193                cert.as_ref().unwrap(),
194                security_policy,
195            )?;
196
197            Ok(LegacyEncryptedSecret {
198                secret: password,
199                encryption_algorithm: UAString::from(
200                    security_policy
201                        .asymmetric_encryption_algorithm()
202                        .ok_or_else(|| {
203                            Error::new(
204                                StatusCode::BadSecurityPolicyRejected,
205                                "Security policy does not support asymmetric encryption",
206                            )
207                        })?,
208                ),
209                policy: user_token_policy.policy_id.clone(),
210            })
211        }
212    }
213}
214
215/// Encrypt a client side user's password using the server nonce and cert. This is described in table 176
216/// OPC UA part 4. This function is prefixed "legacy" because 1.04 describes another way of encrypting passwords.
217pub(crate) fn legacy_secret_encrypt(
218    password: &[u8],
219    server_nonce: &[u8],
220    server_cert: &X509,
221    policy: SecurityPolicy,
222) -> Result<ByteString, Error> {
223    // Message format is size, password, nonce
224    let plaintext_size = 4 + password.len() + server_nonce.len();
225    let mut src = Cursor::new(vec![0u8; plaintext_size]);
226
227    // Write the length of the data to be encrypted excluding the length itself)
228    write_u32(&mut src, (plaintext_size - 4) as u32)?;
229    src.write(password).map_err(Error::decoding)?;
230    src.write(server_nonce).map_err(Error::decoding)?;
231
232    // Encrypt the data with the public key from the server's certificate
233    let public_key = server_cert.public_key()?;
234
235    let cipher_size = policy.calculate_cipher_text_size(plaintext_size, &public_key);
236    let mut dst = vec![0u8; cipher_size];
237    let actual_size = policy
238        .asymmetric_encrypt(&public_key, &src.into_inner(), &mut dst)
239        .map_err(Error::encoding)?;
240
241    assert_eq!(actual_size, cipher_size);
242
243    Ok(ByteString::from(dst))
244}
245
246/// Decrypt the client's password using the server's nonce and private key. This function is prefixed
247/// "legacy" because 1.04 describes another way of encrypting passwords.
248pub(crate) fn legacy_secret_decrypt<T: AesAsymmetricEncryptionAlgorithm>(
249    secret: &ByteString,
250    server_nonce: &[u8],
251    server_key: &PrivateKey,
252) -> Result<ByteString, Error> {
253    if secret.is_null_or_empty() {
254        Err(Error::decoding("Missing server secret"))
255    } else {
256        // Decrypt the message
257        let src = secret.value.as_ref().unwrap();
258        let mut dst = vec![0u8; src.len()];
259        let mut actual_size = server_key
260            .private_decrypt::<T>(src, &mut dst)
261            .map_err(Error::decoding)?;
262
263        let mut dst = Cursor::new(dst);
264        let plaintext_size = read_u32(&mut dst)? as usize;
265
266        /* Remove padding
267         *
268         * 7.36.2.2 Legacy Encrypted Token Secret Format: A Client should not add any
269         * padding after the secret. If a Client adds padding then all bytes shall
270         * be zero. A Server shall check for padding added by Clients and ensure
271         * that all padding bytes are zeros.
272         *
273         */
274        let mut dst = dst.into_inner();
275        if actual_size > plaintext_size + 4 {
276            let padding_bytes = &dst[plaintext_size + 4..];
277            /*
278             * If the Encrypted Token Secret contains padding, the padding must be
279             * zeroes according to the 1.04.1 specification errata, chapter 3.
280             */
281            if !padding_bytes.iter().all(|&x| x == 0) {
282                return Err(Error::decoding(
283                    "Non-zero padding bytes in decrypted password",
284                ));
285            } else {
286                dst.truncate(plaintext_size + 4);
287                actual_size = dst.len();
288            }
289        }
290
291        if plaintext_size + 4 != actual_size {
292            Err(Error::decoding("Invalid plaintext size"))
293        } else {
294            let nonce_len = server_nonce.len();
295            let nonce_begin = actual_size - nonce_len;
296            let nonce = &dst[nonce_begin..(nonce_begin + nonce_len)];
297            if nonce != server_nonce {
298                Err(Error::decoding("Invalid nonce"))
299            } else {
300                let password = &dst[4..nonce_begin];
301                Ok(ByteString::from(password))
302            }
303        }
304    }
305}
306
307/// Verify that the X509 identity token supplied to a server contains a valid signature.
308pub fn verify_x509_identity_token(
309    token: &X509IdentityToken,
310    user_token_signature: &SignatureData,
311    security_policy: SecurityPolicy,
312    server_cert: &X509,
313    server_nonce: &[u8],
314) -> Result<(), Error> {
315    // Since it is not obvious at all from the spec what the user token signature is supposed to be, I looked
316    // at the internet for clues:
317    //
318    // https://stackoverflow.com/questions/46683342/securing-opensecurechannel-messages-and-x509identitytoken
319    // https://forum.prosysopc.com/forum/opc-ua/clarification-on-opensecurechannel-messages-and-x509identitytoken-specifications/
320    //
321    // These suggest that the signature is produced by appending the server nonce to the server certificate
322    // and signing with the user certificate's private key.
323    //
324    // This is the same as the standard handshake between client and server but using the identity cert. It would have been nice
325    // if the spec actually said this.
326
327    let signing_cert = super::x509::X509::from_byte_string(&token.certificate_data)?;
328    super::verify_signature_data(
329        user_token_signature,
330        security_policy,
331        &signing_cert,
332        server_cert,
333        server_nonce,
334    )
335}