Skip to main content

opcua_crypto/
lib.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2024 Adam Lock
4
5//! Crypto related functionality. It is used for establishing
6//! trust between a client and server via certificate exchange and validation. It also used for
7//! encrypting / decrypting messages and signing messages.
8
9use std::fmt;
10
11use opcua_types::{
12    status_code::StatusCode, ByteString, EncodingResult, Error, SignatureData, UAString,
13};
14use tracing::{error, trace};
15
16pub use crate::aes::{AesKey, KeySize, PKey, PrivateKey, PublicKey};
17pub use crate::x509::{X509Data, X509Error, X509};
18pub use certificate_store::CertificateStore;
19pub use policy::{AesDerivedKeys, PaddingInfo};
20pub use security_policy::SecurityPolicy;
21pub use thumbprint::Thumbprint;
22pub use user_identity::{
23    legacy_decrypt_secret, legacy_encrypt_secret, verify_x509_identity_token,
24    LegacyEncryptedSecret, LegacySecret,
25};
26
27#[cfg(test)]
28mod tests;
29
30mod certificate_store;
31mod hash;
32pub mod random;
33mod security_policy;
34mod thumbprint;
35mod user_identity;
36mod x509;
37
38mod policy;
39
40mod aes;
41
42/// Size of a SHA1 hash value in bytes
43pub const SHA1_SIZE: usize = 20;
44/// Size of a SHA256 hash value bytes
45pub const SHA256_SIZE: usize = 32;
46
47/// These are algorithms that are used by various policies or external to this file
48pub(crate) mod algorithms {
49    /// Symmetric encryption algorithm AES128-CBC
50    #[allow(unused)]
51    pub(crate) const ENC_AES128_CBC: &str = "http://www.w3.org/2001/04/xmlenc#aes128-cbc";
52
53    /// Symmetric encryption algorithm AES256-CBC
54    #[allow(unused)]
55    pub(crate) const ENC_AES256_CBC: &str = "http://www.w3.org/2001/04/xmlenc#aes256-cbc";
56
57    /// Asymmetric encryption algorithm RSA15
58    pub(crate) const ENC_RSA_15: &str = "http://www.w3.org/2001/04/xmlenc#rsa-1_5";
59
60    /// Asymmetric encryption algorithm RSA-OAEP
61    pub(crate) const ENC_RSA_OAEP: &str = "http://www.w3.org/2001/04/xmlenc#rsa-oaep";
62
63    /// Asymmetrric encrypttion
64    pub(crate) const ENC_RSA_OAEP_SHA256: &str =
65        "http://opcfoundation.org/UA/security/rsa-oaep-sha2-256";
66
67    // Asymmetric encryption algorithm RSA-OAEP-MGF1P
68    //pub const ENC_RSA_OAEP_MGF1P: &str = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p";
69
70    /// SymmetricSignatureAlgorithm – HmacSha1 – (http://www.w3.org/2000/09/xmldsig#hmac-sha1).
71    #[allow(unused)]
72    pub(crate) const DSIG_HMAC_SHA1: &str = "http://www.w3.org/2000/09/xmldsig#hmac-sha1";
73
74    /// SymmetricSignatureAlgorithm – HmacSha256 – (http://www.w3.org/2000/09/xmldsig#hmac-sha256).
75    #[allow(unused)]
76    pub(crate) const DSIG_HMAC_SHA256: &str = "http://www.w3.org/2000/09/xmldsig#hmac-sha256";
77
78    /// Asymmetric digital signature algorithm using RSA-SHA1
79    pub(crate) const DSIG_RSA_SHA1: &str = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
80
81    /// Asymmetric digital signature algorithm using RSA-SHA256
82    pub(crate) const DSIG_RSA_SHA256: &str = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
83
84    /// Asymmetric digital signature algorithm using RSA-PSS_SHA2-256
85    pub(crate) const DSIG_RSA_PSS_SHA2_256: &str =
86        "http://opcfoundation.org/UA/security/rsa-pss-sha2-256";
87
88    // Key derivation algorithm P_SHA1
89    //pub const KEY_P_SHA1: &str = "http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha1";
90
91    // Key derivation algorithm P_SHA256
92    //pub const KEY_P_SHA256: &str = "http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha256";
93}
94
95fn concat_data_and_nonce(data: &[u8], nonce: &[u8]) -> Vec<u8> {
96    let mut buffer: Vec<u8> = Vec::with_capacity(data.len() + nonce.len());
97    buffer.extend_from_slice(data);
98    buffer.extend_from_slice(nonce);
99    buffer
100}
101
102/// Creates a `SignatureData` object by signing the supplied certificate and nonce with a pkey
103pub fn create_signature_data(
104    signing_key: &PrivateKey,
105    security_policy: SecurityPolicy,
106    contained_cert: &ByteString,
107    nonce: &ByteString,
108) -> Result<SignatureData, StatusCode> {
109    let (algorithm, signature) = match security_policy {
110        SecurityPolicy::Unknown => return Err(StatusCode::BadSecurityPolicyRejected),
111        SecurityPolicy::None => (UAString::null(), ByteString::null()),
112        security_policy => {
113            if contained_cert.is_null_or_empty() {
114                error!("Null certificate passed to create_signature_data");
115                return Err(StatusCode::BadCertificateInvalid);
116            }
117            if nonce.is_null_or_empty() {
118                error!("Null nonce passed to create_signature_data");
119                return Err(StatusCode::BadNonceInvalid);
120            }
121
122            let data = concat_data_and_nonce(contained_cert.as_ref(), nonce.as_ref());
123            let signing_key_size = signing_key.size();
124            let mut signature = vec![0u8; signing_key_size];
125            let _ = security_policy.asymmetric_sign(signing_key, &data, &mut signature)?;
126            (
127                UAString::from(security_policy.asymmetric_signature_algorithm()),
128                ByteString::from(&signature),
129            )
130        }
131    };
132
133    let signature_data = SignatureData {
134        algorithm,
135        signature,
136    };
137    trace!("Creating signature contained_cert = {:?}", signature_data);
138    Ok(signature_data)
139}
140
141/// Verifies that the supplied signature data was produced by the signing cert. The contained cert and nonce are supplied so
142/// the signature can be verified against the expected data.
143pub fn verify_signature_data(
144    signature: &SignatureData,
145    security_policy: SecurityPolicy,
146    signing_cert: &X509,
147    contained_cert: &X509,
148    contained_nonce: &[u8],
149) -> EncodingResult<()> {
150    if let Ok(verification_key) = signing_cert.public_key() {
151        // This is the data that the should have been signed
152        let contained_cert = contained_cert.as_byte_string();
153        let data = concat_data_and_nonce(contained_cert.as_ref(), contained_nonce);
154
155        // Verify the signature
156        security_policy.asymmetric_verify_signature(
157            &verification_key,
158            &data,
159            signature.signature.as_ref(),
160        )?;
161        Ok(())
162    } else {
163        Err(Error::new(
164            StatusCode::BadUnexpectedError,
165            "Signature verification failed, signing certificate has no public key to verify with",
166        ))
167    }
168}
169
170#[derive(Debug)]
171/// Error resolving computer hostname.
172pub struct HostnameError;
173
174impl fmt::Display for HostnameError {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        write!(f, "HostnameError")
177    }
178}
179
180impl std::error::Error for HostnameError {}
181
182/// Returns this computer's hostname
183pub fn hostname() -> Result<String, HostnameError> {
184    use gethostname::gethostname;
185    gethostname().into_string().map_err(|_| HostnameError {})
186}
187
188// Turns hex string to array bytes. Function was extracted & adapted from the deprecated
189// crate rustc-serialize. Function panics if the string is invalid.
190//
191// https://github.com/rust-lang-deprecated/rustc-serialize/blob/master/src/hex.rs
192#[cfg(test)]
193fn from_hex(v: &str) -> Vec<u8> {
194    // This may be an overestimate if there is any whitespace
195    let mut b = Vec::with_capacity(v.len() / 2);
196    let mut modulus = 0;
197    let mut buf = 0;
198
199    for (idx, byte) in v.bytes().enumerate() {
200        buf <<= 4;
201
202        match byte {
203            b'A'..=b'F' => buf |= byte - b'A' + 10,
204            b'a'..=b'f' => buf |= byte - b'a' + 10,
205            b'0'..=b'9' => buf |= byte - b'0',
206            b' ' | b'\r' | b'\n' | b'\t' => {
207                buf >>= 4;
208                continue;
209            }
210            _ => {
211                let ch = v[idx..].chars().next().unwrap();
212                panic!("Invalid hex character {ch} at {idx}");
213            }
214        }
215
216        modulus += 1;
217        if modulus == 2 {
218            modulus = 0;
219            b.push(buf);
220        }
221    }
222
223    match modulus {
224        0 => b.into_iter().collect(),
225        _ => panic!("Invalid hex length"),
226    }
227}