ntex-rs / ntex-amqp

AMQP 1.0 Server framework
Apache License 2.0
66 stars 15 forks source link

ConnectError::Sasl #21

Closed calebroelenshowest closed 2 years ago

calebroelenshowest commented 2 years ago

On trying to connect with Azure Iot hub, I get the Sasl exception: SaslCode::Sys What does this error code mean?

fafhrd91 commented 2 years ago

according to spec it is Connection authentication failed due to a system error.

you can enable trace logging, it may give more info

calebroelenshowest commented 2 years ago

Thx for the quick response. Enabling tracing did not really show much more, so I added a few more traces. -> The Sasl Outcome seems to return the SASL sys error. Location: connector.rs, line 400-410 image This is probably a misconfiguration on my part. So one more question: image If the URL azure iot hub requests is of the following format: -> 'amqps://username:sas_token@hostname/operation' How do you translate it in all the params. Im just not sure about what the 'authz_id' param means.

One last note, is that I'm using an older version, because ntex-amqp doesn't build the v6 version.

image

Thx in advance for any replies :)

Update: I will be messaging Azure over this issue, I will post the solution if I find it.

calebroelenshowest commented 2 years ago

Posted an issue on Azure: Link

fafhrd91 commented 2 years ago

authn_id should be set to key name password is key itself,

        let auth = ntex_amqp::client::SaslAuth {
            authz_id: ByteString::from_static(""),
            authn_id: key_name,
            password: key,
        };

        let mut c = ntex_amqp::client::Connector();
        c.hostname(hostname);
        c.connect_sasl(hostname, auth)
calebroelenshowest commented 2 years ago

Thx for the reply. I have tried a few options, and will now wait for a reply on Azure & Discord. I will post the solution here if I find the solution. This more a problem with Azures unclear docs :(. I will keep the issue open for now so I can post the solution later.

Thx for your time, have a good day 👍 .

calebroelenshowest commented 2 years ago

I just found the solution to the problem, so here is the solution (I was quite stupid actually :p)

The problem is a combination of Azure and my code. The url_encode is not needed. Also, Access keys generated on Azure use the wrong signature. I found this out by sniffing through the python code.

This is the final code, which can successfully connect 👍

You can generate the correct SAS token with this: ` pub mod azureiothub { use std::fmt::Error; use std::ops::Add; use chrono::Duration; use hmac::{Hmac, Mac, NewMac}; use log::trace; use sha2::Sha256; use base64;

// Azure Iot Hub
pub struct Client{
    // The azure client
}
impl Client{
    // The client methods
}

pub enum GenSasTokenException{
    DECODEBASE64,
    INVALIDKEYLENGTH,
}

pub fn hostname(iot_hub_name: String) -> String{
    format!("{}.azure-devices.net", iot_hub_name)
}

pub fn generate_sas_token(key: String, device_id: String, hub_name: String, validity_days: i64) -> Result<String, GenSasTokenException>{
    // Generate a VALID SAS token
    // Recoded from the rust azure SDK (mqtt) and the generate_sas_token function (Python SDK)
    let b64_key = base64::decode(&key);
    if b64_key.is_err(){
        // Failed to decode
        trace!("Failed to decode base64 key.");
        return Err(GenSasTokenException::DECODEBASE64)
    }
    // Continue
    // Is the key the right length?
    let check_hmac = Hmac::<Sha256>::new_from_slice(&b64_key.unwrap());
    if check_hmac.is_err(){
        // Failed the length
        trace!("The length of the key is invalid.");
        return Err(GenSasTokenException::INVALIDKEYLENGTH)
    }
    // Continue
    // Create the expire date.
    let mut time_now = chrono::offset::Utc::now();
    // Add the amount of days
    let mut add_days = Duration::days(validity_days);
    let mut time_now_plus_day = time_now.add(add_days);
    // Now lets generate the token.
    let expiry_timestamp = time_now_plus_day.timestamp();
    let hub_url = format!("{}.azure-devices.net%2Fdevices%2F{}", hub_name, device_id);
    //  Coded to_sign
    let to_sign = format!("{}\n{}", &hub_url, &expiry_timestamp);
    println!("{}", to_sign);
    // Safe to unwrap --> Check before
    let unwrapped_key = base64::decode(&key).unwrap();
    // Start generation
    let mut mac  = Hmac::<Sha256>::new_from_slice(&unwrapped_key).unwrap();
    // SIGN GEN
    mac.update(to_sign.as_bytes());
    let mac_result = mac.finalize();
    let signature = base64::encode(mac_result.into_bytes());
    let pairs = &vec![("sig", signature)];
    let token_result = serde_urlencoded::to_string(pairs).unwrap();
    // Build up token signature
    let full_token = format!(
        "SharedAccessSignature sr={}.azure-devices.net%2Fdevices%2F{}&{}&se={}&skn={}",
        hub_name,
        device_id,
        token_result,
        expiry_timestamp,
        device_id
    );
    return Ok(full_token)
}

}

mod test { use std::process::exit; use crate::azureiothub::generate_sas_token;

#[test]
fn gen_token(){
    let token = generate_sas_token(String::from("PrimairyKeyHere"),
                       String::from("devicename"),
                       String::from("hubname"), 365);
    if token.is_err(){
        println!("Failed");
        exit(1);
    }
    else {
        println!("Token: {}", token.unwrap_or(String::from("")));
    }
}

}`

The correct code to connect is this :

`let auth = ntex_amqp::client::SaslAuth { authz_id: ByteString::from_static(""), authn_id: authenticator_id.into(), password: password.into() };

let mut driver = client::Connector::new()
    .connector(RustlsConnector::new(Arc::new(tls_config)));
driver.hostname(hostname);
let result = driver.connect_sasl(address, auth).await;

` This code will work for Azure Iot Hub, with TLS 1.2