diff --git a/core/src/crypto/mod.rs b/core/src/crypto/mod.rs index f60bdf20a..326d9353b 100644 --- a/core/src/crypto/mod.rs +++ b/core/src/crypto/mod.rs @@ -105,11 +105,11 @@ pub fn create_signature_data(signing_key: &PrivateKey, security_policy: Security /// Verifies that the supplied signature data was produced by the signing cert. The contained cert and nonce are supplied so /// the signature can be verified against the expected data. -pub fn verify_signature_data(signature: &SignatureData, security_policy: SecurityPolicy, signing_cert: &X509, contained_cert: &X509, contained_nonce: &ByteString) -> StatusCode { +pub fn verify_signature_data(signature: &SignatureData, security_policy: SecurityPolicy, signing_cert: &X509, contained_cert: &X509, contained_nonce: &[u8]) -> StatusCode { if let Ok(verification_key) = signing_cert.public_key() { // This is the data that the should have been signed let contained_cert = contained_cert.as_byte_string(); - let data = concat_data_and_nonce(contained_cert.as_ref(), contained_nonce.as_ref()); + let data = concat_data_and_nonce(contained_cert.as_ref(), contained_nonce); // Verify the signature let result = security_policy.asymmetric_verify_signature(&verification_key, &data, signature.signature.as_ref(), None); diff --git a/core/src/crypto/user_identity.rs b/core/src/crypto/user_identity.rs index e6e1a8c84..8394bbc07 100644 --- a/core/src/crypto/user_identity.rs +++ b/core/src/crypto/user_identity.rs @@ -14,6 +14,7 @@ use opcua_types::{ }; use super::{X509, PrivateKey, KeySize, SecurityPolicy, RsaPadding}; +use opcua_types::service_types::{X509IdentityToken, SignatureData}; /// Create a filled in UserNameIdentityToken by using the supplied channel security policy, user token policy, nonce, cert, user name and password. pub fn make_user_name_identity_token(channel_security_policy: SecurityPolicy, user_token_policy: &UserTokenPolicy, nonce: &[u8], cert: Option, user: &str, pass: &str) -> Result { @@ -100,7 +101,7 @@ pub fn decrypt_user_identity_token_password(user_identity_token: &UserNameIdenti /// Encrypt a client side user's password using the server nonce and cert. This is described in table 176 /// OPC UA part 4. This function is prefixed "legacy" because 1.04 describes another way of encrypting passwords. -pub fn legacy_password_encrypt(password: &str, server_nonce: &[u8], server_cert: &X509, padding: RsaPadding) -> Result { +pub(crate) fn legacy_password_encrypt(password: &str, server_nonce: &[u8], server_cert: &X509, padding: RsaPadding) -> Result { // Message format is size, password, nonce let plaintext_size = 4 + password.len() + server_nonce.len(); let mut src = Cursor::new(vec![0u8; plaintext_size]); @@ -124,7 +125,7 @@ pub fn legacy_password_encrypt(password: &str, server_nonce: &[u8], server_cert: /// Decrypt the client's password using the server's nonce and private key. This function is prefixed /// "legacy" because 1.04 describes another way of encrypting passwords. -pub fn legacy_password_decrypt(secret: &ByteString, server_nonce: &[u8], server_key: &PrivateKey, padding: RsaPadding) -> Result { +pub(crate) fn legacy_password_decrypt(secret: &ByteString, server_nonce: &[u8], server_key: &PrivateKey, padding: RsaPadding) -> Result { if secret.is_null() { Err(StatusCode::BadDecodingError) } else { @@ -154,3 +155,24 @@ pub fn legacy_password_decrypt(secret: &ByteString, server_nonce: &[u8], server_ } } } + +pub fn verify_x509_identity_token(token: &X509IdentityToken, user_token_signature: &SignatureData, security_policy: SecurityPolicy, server_cert: &X509, server_nonce: &[u8]) -> Result<(), StatusCode> { + // Since it is not obvious at all from the spec what the user token signature is supposed to be, I looked + // at the internet for answers + // + // https://stackoverflow.com/questions/46683342/securing-opensecurechannel-messages-and-x509identitytoken + // https://forum.prosysopc.com/forum/opc-ua/clarification-on-opensecurechannel-messages-and-x509identitytoken-specifications/ + // + // These suggest that the signature is produced by appending the server nonce to the server certificate + // and signing with the user certificate's private key. + // + // More or less like the standard handshake between client and server but with the identity cert. + + let signing_cert = super::x509::X509::from_byte_string(&token.certificate_data)?; + let result = super::verify_signature_data(user_token_signature, security_policy, &signing_cert, server_cert, server_nonce); + if result.is_good() { + Ok(()) + } else { + Err(result) + } +} \ No newline at end of file diff --git a/server/src/services/session.rs b/server/src/services/session.rs index b7a2b5682..d485e4a69 100644 --- a/server/src/services/session.rs +++ b/server/src/services/session.rs @@ -206,7 +206,7 @@ impl SessionService { let secure_channel = trace_read_lock_unwrap!(session.secure_channel); secure_channel.security_policy() }; - crypto::verify_signature_data(client_signature, security_policy, client_certificate, server_certificate, &session.session_nonce) + crypto::verify_signature_data(client_signature, security_policy, client_certificate, server_certificate, &session.session_nonce.as_ref()) } else { error!("Client signature verification failed, server has no server certificate"); StatusCode::BadUnexpectedError diff --git a/server/src/state.rs b/server/src/state.rs index 141d78279..242f774c7 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -11,7 +11,7 @@ use opcua_types::{ profiles, service_types::{ ApplicationDescription, RegisteredServer, ApplicationType, EndpointDescription, - UserNameIdentityToken, UserTokenPolicy, UserTokenType, X509IdentityToken, + UserNameIdentityToken, UserTokenPolicy, UserTokenType, X509IdentityToken, SignatureData, ServerState as ServerStateType, }, status_code::StatusCode, @@ -283,7 +283,7 @@ impl ServerState { let decoding_limits = config.decoding_limits(); if let Some(endpoint) = config.find_endpoint(endpoint_url, security_policy, security_mode) { // Now validate the user identity token - if user_identity_token.is_null() || user_identity_token.is_empty() { + if user_identity_token.is_empty() { // Empty tokens are treated as anonymous Self::authenticate_anonymous_token(endpoint) } else if let Ok(object_id) = user_identity_token.node_id.as_object_id() { @@ -295,9 +295,8 @@ impl ServerState { } ObjectId::UserNameIdentityToken_Encoding_DefaultBinary => { // Username / password - let result = user_identity_token.decode_inner::(&decoding_limits); - if let Ok(token) = result { - self.authenticate_username_identity_token(&config, endpoint, &token, server_nonce, &self.server_pkey) + if let Ok(token) = user_identity_token.decode_inner::(&decoding_limits) { + self.authenticate_username_identity_token(&config, endpoint, &token, &self.server_pkey, server_nonce) } else { // Garbage in the extension object error!("User name identity token could not be decoded"); @@ -305,11 +304,14 @@ impl ServerState { } } ObjectId::X509IdentityToken_Encoding_DefaultBinary => { - // X509 certs could be recognized here - let result = user_identity_token.decode_inner::(&decoding_limits); - if let Ok(_) = result { - error!("X509 identity token type is not supported"); - Err(StatusCode::BadIdentityTokenRejected) + // X509 certs + if let Ok(token) = user_identity_token.decode_inner::(&decoding_limits) { + // TODO get this from activate session + let user_token_signature = SignatureData { + algorithm: UAString::null(), + signature: ByteString::null() + }; + self.authenticate_x509_identity_token(&token, &user_token_signature, &self.server_certificate, server_nonce) } else { // Garbage in the extension object error!("X509 identity token could not be decoded"); @@ -347,8 +349,22 @@ impl ServerState { } } + fn authenticate_x509_identity_token(&self, token: &X509IdentityToken, user_token_signature: &SignatureData, server_certificate: &Option, server_nonce: &ByteString) -> Result<(), StatusCode> { + match server_certificate { + Some(ref server_certificate) => { + // TODO + let security_policy = SecurityPolicy::Basic128Rsa15; + user_identity::verify_x509_identity_token(token, user_token_signature, security_policy, server_certificate, server_nonce.as_ref()) + } + None => { + Err(StatusCode::BadIdentityTokenInvalid) + } + } + } + + /// Authenticates the username identity token with the supplied endpoint - fn authenticate_username_identity_token(&self, config: &ServerConfig, endpoint: &ServerEndpoint, token: &UserNameIdentityToken, server_nonce: &ByteString, server_key: &Option) -> Result<(), StatusCode> { + fn authenticate_username_identity_token(&self, config: &ServerConfig, endpoint: &ServerEndpoint, token: &UserNameIdentityToken, server_key: &Option, server_nonce: &ByteString) -> Result<(), StatusCode> { // The policy_id should be used to determine the algorithm for encoding passwords etc. if token.user_name.is_null() { error!("User identify token supplies no user name"); diff --git a/types/src/extension_object.rs b/types/src/extension_object.rs index c226f778d..6f8057910 100644 --- a/types/src/extension_object.rs +++ b/types/src/extension_object.rs @@ -108,7 +108,7 @@ impl ExtensionObject { /// Tests for empty body. pub fn is_empty(&self) -> bool { - match self.body { + self.is_null() || match self.body { ExtensionObjectEncoding::None => true, _ => false }