Skip to content

Commit

Permalink
Start work on x509 identity token
Browse files Browse the repository at this point in the history
  • Loading branch information
locka99 committed May 1, 2019
1 parent a8e4e5b commit c8ba120
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 17 deletions.
4 changes: 2 additions & 2 deletions core/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
26 changes: 24 additions & 2 deletions core/src/crypto/user_identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<X509>, user: &str, pass: &str) -> Result<UserNameIdentityToken, StatusCode> {
Expand Down Expand Up @@ -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<ByteString, StatusCode> {
pub(crate) fn legacy_password_encrypt(password: &str, server_nonce: &[u8], server_cert: &X509, padding: RsaPadding) -> Result<ByteString, StatusCode> {
// Message format is size, password, nonce
let plaintext_size = 4 + password.len() + server_nonce.len();
let mut src = Cursor::new(vec![0u8; plaintext_size]);
Expand All @@ -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<String, StatusCode> {
pub(crate) fn legacy_password_decrypt(secret: &ByteString, server_nonce: &[u8], server_key: &PrivateKey, padding: RsaPadding) -> Result<String, StatusCode> {
if secret.is_null() {
Err(StatusCode::BadDecodingError)
} else {
Expand Down Expand Up @@ -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)
}
}
2 changes: 1 addition & 1 deletion server/src/services/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 27 additions & 11 deletions server/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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() {
Expand All @@ -295,21 +295,23 @@ impl ServerState {
}
ObjectId::UserNameIdentityToken_Encoding_DefaultBinary => {
// Username / password
let result = user_identity_token.decode_inner::<UserNameIdentityToken>(&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::<UserNameIdentityToken>(&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");
Err(StatusCode::BadIdentityTokenInvalid)
}
}
ObjectId::X509IdentityToken_Encoding_DefaultBinary => {
// X509 certs could be recognized here
let result = user_identity_token.decode_inner::<X509IdentityToken>(&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::<X509IdentityToken>(&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");
Expand Down Expand Up @@ -347,8 +349,22 @@ impl ServerState {
}
}

fn authenticate_x509_identity_token(&self, token: &X509IdentityToken, user_token_signature: &SignatureData, server_certificate: &Option<X509>, 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<PrivateKey>) -> Result<(), StatusCode> {
fn authenticate_username_identity_token(&self, config: &ServerConfig, endpoint: &ServerEndpoint, token: &UserNameIdentityToken, server_key: &Option<PrivateKey>, 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");
Expand Down
2 changes: 1 addition & 1 deletion types/src/extension_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit c8ba120

Please sign in to comment.