mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-07-12 22:21:53 +00:00
Add SCRAM client extensions support
The SCRAM RFC describes extensions that can be used to add extra data into the protocol. This commit adds support for the client scram mechanism to insert extension data into the client messages at the locations specified by the SCRAM RFC. Kafka utilizes these extensions when authenticating delegation tokens over scram. Since I am writing a kafka client I would like access to these extensions so I can support delegation tokens. I've only added them to Scram::new not Mechanism::from_credentials since they do not apply to other mechanisms. For my purposes this is fine since I only need to work with scram. However it would be limiting for other use cases, so I'm quite happy to add the extension fields into Credentials if that was desired. For now I've left it out since the fields would be scram exclusive and everything else in Credentials is currently generic.
This commit is contained in:
parent
4853776010
commit
8bdd19b0ff
1 changed files with 80 additions and 0 deletions
|
@ -27,6 +27,8 @@ pub struct Scram<S: ScramProvider> {
|
|||
name_plus: String,
|
||||
username: String,
|
||||
password: Password,
|
||||
client_first_extensions: String,
|
||||
client_final_extensions: String,
|
||||
client_nonce: String,
|
||||
state: ScramState,
|
||||
channel_binding: ChannelBinding,
|
||||
|
@ -49,6 +51,8 @@ impl<S: ScramProvider> Scram<S> {
|
|||
name_plus: format!("SCRAM-{}-PLUS", S::name()),
|
||||
username: username.into(),
|
||||
password: password.into(),
|
||||
client_first_extensions: String::new(),
|
||||
client_final_extensions: String::new(),
|
||||
client_nonce: generate_nonce()?,
|
||||
state: ScramState::Init,
|
||||
channel_binding,
|
||||
|
@ -56,6 +60,22 @@ impl<S: ScramProvider> Scram<S> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Sets extension data to be inserted into the client's first message.
|
||||
/// Extension data must be in the format of a comma seperated list of SCRAM extensions to be used e.g. `foo=true,bar=baz`
|
||||
/// If not called, no extensions will be used for the clients first message.
|
||||
pub fn with_first_extensions(mut self, extensions: String) -> Self {
|
||||
self.client_first_extensions = extensions;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets extension data to be inserted into the client's final message.
|
||||
/// Extension data must be in the format of a comma seperated list of SCRAM extensions to be used e.g. `foo=true,bar=baz`
|
||||
/// If not called, no extensions will be used for the clients final message.
|
||||
pub fn with_final_extensions(mut self, extensions: String) -> Self {
|
||||
self.client_final_extensions = extensions;
|
||||
self
|
||||
}
|
||||
|
||||
// Used for testing.
|
||||
#[doc(hidden)]
|
||||
#[cfg(test)]
|
||||
|
@ -69,6 +89,8 @@ impl<S: ScramProvider> Scram<S> {
|
|||
name_plus: format!("SCRAM-{}-PLUS", S::name()),
|
||||
username: username.into(),
|
||||
password: password.into(),
|
||||
client_first_extensions: String::new(),
|
||||
client_final_extensions: String::new(),
|
||||
client_nonce: nonce,
|
||||
state: ScramState::Init,
|
||||
channel_binding: ChannelBinding::None,
|
||||
|
@ -107,6 +129,10 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
|
|||
bare.extend(self.username.bytes());
|
||||
bare.extend(b",r=");
|
||||
bare.extend(self.client_nonce.bytes());
|
||||
if !self.client_first_extensions.is_empty() {
|
||||
bare.extend(b",");
|
||||
bare.extend(self.client_first_extensions.bytes());
|
||||
}
|
||||
let mut data = Vec::new();
|
||||
data.extend(&gs2_header);
|
||||
data.extend(&bare);
|
||||
|
@ -142,6 +168,10 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
|
|||
client_final_message_bare.extend(Base64.encode(&cb_data).bytes());
|
||||
client_final_message_bare.extend(b",r=");
|
||||
client_final_message_bare.extend(server_nonce.bytes());
|
||||
if !self.client_final_extensions.is_empty() {
|
||||
client_final_message_bare.extend(b",");
|
||||
client_final_message_bare.extend(self.client_final_extensions.bytes());
|
||||
}
|
||||
let salted_password = S::derive(&self.password, &salt, iterations)?;
|
||||
let client_key = S::hmac(b"Client Key", &salted_password)?;
|
||||
let server_key = S::hmac(b"Server Key", &salted_password)?;
|
||||
|
@ -247,4 +277,54 @@ mod tests {
|
|||
); // again, depends on ordering…
|
||||
mechanism.success(&server_final[..]).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scram_kafka_token_delegation_works() {
|
||||
// credentials and raw messages taken from a real kafka SCRAM token delegation authentication
|
||||
let username = "6Lbb79aSTs-mDWUPc64D9Q";
|
||||
let password = "O574x+7mB0B8R9Yt8DqwWbIzBgEm3lUE+fy7VWdvCwcLvGvwJK9GM4y0Qaz/MxiIxDHEnxDfSuB13uycXiUqyg==";
|
||||
let client_nonce = "o6wj2xqdu0fxe4nmnukkj076m";
|
||||
let client_init = b"n,,n=6Lbb79aSTs-mDWUPc64D9Q,r=o6wj2xqdu0fxe4nmnukkj076m,tokenauth=true";
|
||||
let server_init = b"r=o6wj2xqdu0fxe4nmnukkj076m1eut816hvmsycqw2qzyn14zxvr,s=MWVtNWw1Mzc1MnFianNoYWhqMjhyYzVzZHM=,i=4096";
|
||||
let client_final = b"c=biws,r=o6wj2xqdu0fxe4nmnukkj076m1eut816hvmsycqw2qzyn14zxvr,p=qVfqg28hDgroc6pal4qCF+8hO1/wiB84o7snGRDZKuE=";
|
||||
let server_final = b"v=2ZSkAlHEUj6WehcizLhQRiiVGn+VDVtmAqj1v/IPa28=";
|
||||
let mut mechanism =
|
||||
Scram::<Sha256>::new_with_nonce(username, password, client_nonce.to_owned())
|
||||
.with_first_extensions("tokenauth=true".to_owned());
|
||||
let init = mechanism.initial();
|
||||
assert_eq!(
|
||||
std::str::from_utf8(&init).unwrap(),
|
||||
std::str::from_utf8(client_init).unwrap()
|
||||
); // depends on ordering…
|
||||
let resp = mechanism.response(server_init).unwrap();
|
||||
assert_eq!(
|
||||
std::str::from_utf8(&resp).unwrap(),
|
||||
std::str::from_utf8(client_final).unwrap()
|
||||
); // again, depends on ordering…
|
||||
mechanism.success(server_final).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scram_final_extension_works() {
|
||||
let username = "some_user";
|
||||
let password = "a_password";
|
||||
let client_nonce = "client_nonce";
|
||||
let client_init = b"n,,n=some_user,r=client_nonce";
|
||||
let server_init =
|
||||
b"r=client_nonceserver_nonce,s=MWVtNWw1Mzc1MnFianNoYWhqMjhyYzVzZHM=,i=4096";
|
||||
let client_final = b"c=biws,r=client_nonceserver_nonce,foo=true,p=T9XQLmykBv74DzbaCtX90/ElJYJU2XWM/jHmHJ+BI/w=";
|
||||
let mut mechanism =
|
||||
Scram::<Sha256>::new_with_nonce(username, password, client_nonce.to_owned())
|
||||
.with_final_extensions("foo=true".to_owned());
|
||||
let init = mechanism.initial();
|
||||
assert_eq!(
|
||||
std::str::from_utf8(&init).unwrap(),
|
||||
std::str::from_utf8(client_init).unwrap()
|
||||
); // depends on ordering…
|
||||
let resp = mechanism.response(server_init).unwrap();
|
||||
assert_eq!(
|
||||
std::str::from_utf8(&resp).unwrap(),
|
||||
std::str::from_utf8(client_final).unwrap()
|
||||
); // again, depends on ordering…
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue