Skip to main content

opcua/crypto/
mod.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2022 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 crate::types::{service_types::SignatureData, status_code::StatusCode, ByteString, UAString};
12pub use {
13    aeskey::*, certificate_store::*, hash::*, pkey::*, security_policy::*, thumbprint::*,
14    user_identity::*, x509::*,
15};
16
17#[cfg(test)]
18mod tests;
19
20pub mod aeskey;
21pub mod certificate_store;
22pub mod hash;
23pub mod pkey;
24pub mod random;
25pub mod security_policy;
26pub mod thumbprint;
27pub mod user_identity;
28pub mod x509;
29
30// Size of a SHA1 hash value in bytes
31pub const SHA1_SIZE: usize = 20;
32// Size of a SHA256 hash value bytes
33pub const SHA256_SIZE: usize = 32;
34
35/// These are algorithms that are used by various policies or external to this file
36pub(crate) mod algorithms {
37    // Symmetric encryption algorithm AES128-CBC
38    //pub const ENC_AES128_CBC: &str = "http://www.w3.org/2001/04/xmlenc#aes128-cbc";
39
40    // Symmetric encryption algorithm AES256-CBC
41    //pub const ENC_AES256_CBC: &str = "http://www.w3.org/2001/04/xmlenc#aes256-cbc";
42
43    /// Asymmetric encryption algorithm RSA15
44    pub const ENC_RSA_15: &str = "http://www.w3.org/2001/04/xmlenc#rsa-1_5";
45
46    /// Asymmetric encryption algorithm RSA-OAEP
47    pub const ENC_RSA_OAEP: &str = "http://www.w3.org/2001/04/xmlenc#rsa-oaep";
48
49    /// Asymmetrric encrypttion
50    pub const ENC_RSA_OAEP_SHA256: &str = "http://opcfoundation.org/UA/security/rsa-oaep-sha2-256";
51
52    // Asymmetric encryption algorithm RSA-OAEP-MGF1P
53    //pub const ENC_RSA_OAEP_MGF1P: &str = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p";
54
55    /// SymmetricSignatureAlgorithm – HmacSha1 – (http://www.w3.org/2000/09/xmldsig#hmac-sha1).
56    pub const DSIG_HMAC_SHA1: &str = "http://www.w3.org/2000/09/xmldsig#hmac-sha1";
57
58    /// SymmetricSignatureAlgorithm – HmacSha256 – (http://www.w3.org/2000/09/xmldsig#hmac-sha256).
59    pub const DSIG_HMAC_SHA256: &str = "http://www.w3.org/2000/09/xmldsig#hmac-sha256";
60
61    /// Asymmetric digital signature algorithm using RSA-SHA1
62    pub const DSIG_RSA_SHA1: &str = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
63
64    /// Asymmetric digital signature algorithm using RSA-SHA256
65    pub const DSIG_RSA_SHA256: &str = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
66
67    /// Asymmetric digital signature algorithm using RSA-PSS_SHA2-256
68    pub const DSIG_RSA_PSS_SHA2_256: &str = "http://opcfoundation.org/UA/security/rsa-pss-sha2-256";
69
70    // Key derivation algorithm P_SHA1
71    //pub const KEY_P_SHA1: &str = "http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha1";
72
73    // Key derivation algorithm P_SHA256
74    //pub const KEY_P_SHA256: &str = "http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha256";
75}
76
77fn concat_data_and_nonce(data: &[u8], nonce: &[u8]) -> Vec<u8> {
78    let mut buffer: Vec<u8> = Vec::with_capacity(data.len() + nonce.len());
79    buffer.extend_from_slice(data);
80    buffer.extend_from_slice(nonce);
81    buffer
82}
83
84/// Creates a `SignatureData` object by signing the supplied certificate and nonce with a pkey
85pub fn create_signature_data(
86    signing_key: &PrivateKey,
87    security_policy: SecurityPolicy,
88    contained_cert: &ByteString,
89    nonce: &ByteString,
90) -> Result<SignatureData, StatusCode> {
91    // TODO this function should be refactored to return an error if the contained cert or nonce is incorrect, not a blank signature. That
92    //  very much depends on reading the spec to see what should happen if its not possible to create a signature, e.g. because
93    //  policy is None.
94
95    let (algorithm, signature) = if contained_cert.is_null() || nonce.is_null() {
96        (UAString::null(), ByteString::null())
97    } else {
98        let data = concat_data_and_nonce(contained_cert.as_ref(), nonce.as_ref());
99        // Sign the bytes and return the algorithm, signature
100        match security_policy {
101            SecurityPolicy::None => (UAString::null(), ByteString::null()),
102            SecurityPolicy::Unknown => {
103                error!(
104                    "An unknown security policy was passed to create_signature_data and rejected"
105                );
106                (UAString::null(), ByteString::null())
107            }
108            security_policy => {
109                let signing_key_size = signing_key.size();
110                let mut signature = vec![0u8; signing_key_size];
111                let _ = security_policy.asymmetric_sign(signing_key, &data, &mut signature)?;
112                (
113                    UAString::from(security_policy.asymmetric_signature_algorithm()),
114                    ByteString::from(&signature),
115                )
116            }
117        }
118    };
119    let signature_data = SignatureData {
120        algorithm,
121        signature,
122    };
123    trace!("Creating signature contained_cert = {:?}", signature_data);
124    Ok(signature_data)
125}
126
127/// Verifies that the supplied signature data was produced by the signing cert. The contained cert and nonce are supplied so
128/// the signature can be verified against the expected data.
129pub fn verify_signature_data(
130    signature: &SignatureData,
131    security_policy: SecurityPolicy,
132    signing_cert: &X509,
133    contained_cert: &X509,
134    contained_nonce: &[u8],
135) -> StatusCode {
136    if let Ok(verification_key) = signing_cert.public_key() {
137        // This is the data that the should have been signed
138        let contained_cert = contained_cert.as_byte_string();
139        let data = concat_data_and_nonce(contained_cert.as_ref(), contained_nonce);
140
141        // Verify the signature
142        let result = security_policy.asymmetric_verify_signature(
143            &verification_key,
144            &data,
145            signature.signature.as_ref(),
146            None,
147        );
148        match result {
149            Ok(_) => StatusCode::Good,
150            Err(result) => {
151                error!(
152                    "Client signature verification failed, status code = {}",
153                    result
154                );
155                result
156            }
157        }
158    } else {
159        error!(
160            "Signature verification failed, signing certificate has no public key to verify with"
161        );
162        StatusCode::BadUnexpectedError
163    }
164}
165
166#[derive(Debug)]
167pub struct HostnameError;
168
169impl fmt::Display for HostnameError {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        write!(f, "HostnameError")
172    }
173}
174
175impl std::error::Error for HostnameError {}
176
177/// Returns this computer's hostname
178pub fn hostname() -> Result<String, HostnameError> {
179    use gethostname::gethostname;
180    gethostname().into_string().map_err(|_| HostnameError {})
181}