use super::DialogId;
use crate::sip::headers::auth::{Algorithm, AuthQop, Qop};
use crate::sip::prelude::{HasHeaders, HeadersExt, ToTypedHeader};
use crate::sip::typed::{Authorization, ProxyAuthorization};
use crate::sip::DigestGenerator;
use crate::sip::{Header, Method, Param, Response, StatusCode};
use crate::transaction::key::{TransactionKey, TransactionRole};
use crate::transaction::transaction::Transaction;
use crate::transaction::{make_via_branch, random_text, CNONCE_LEN};
use crate::Result;
#[derive(Clone)]
pub struct Credential {
pub username: String,
pub password: String,
pub realm: Option<String>,
}
pub async fn handle_client_authenticate(
new_seq: u32,
tx: &Transaction,
resp: Response,
cred: &Credential,
) -> Result<Transaction> {
let code = resp.status_code.clone();
let use_proxy = matches!(code, StatusCode::ProxyAuthenticationRequired);
let www_header = resp.www_authenticate_header();
let proxy_header = crate::sip_header_opt!(resp.headers().iter(), Header::ProxyAuthenticate);
let header = if use_proxy {
proxy_header
.cloned()
.map(Header::ProxyAuthenticate)
.or_else(|| www_header.cloned().map(Header::WwwAuthenticate))
} else {
www_header
.cloned()
.map(Header::WwwAuthenticate)
.or_else(|| proxy_header.cloned().map(Header::ProxyAuthenticate))
};
let header = match header {
Some(h) => h,
None => {
return Err(crate::Error::DialogError(
"missing proxy/www authenticate".to_string(),
DialogId::try_from(tx)?,
resp.status_code.clone(),
));
}
};
let mut new_req = tx.original.clone();
new_req.cseq_header_mut()?.mut_seq(new_seq)?;
let challenge: crate::sip::typed::WwwAuthenticate = match &header {
Header::WwwAuthenticate(h) => h.typed()?,
Header::ProxyAuthenticate(h) => {
let t = h.typed()?;
crate::sip::typed::WwwAuthenticate {
scheme: t.scheme,
realm: t.realm,
domain: t.domain,
nonce: t.nonce,
opaque: t.opaque,
stale: t.stale,
algorithm: t.algorithm,
qop: t.qop,
charset: t.charset,
}
}
_ => unreachable!(),
};
let cnonce = random_text(CNONCE_LEN);
let auth_qop = match challenge.qop {
Some(Qop::Auth) => Some(AuthQop::Auth { cnonce, nc: 1 }),
Some(Qop::AuthInt) => Some(AuthQop::AuthInt { cnonce, nc: 1 }),
_ => None,
};
let algorithm = challenge
.algorithm
.unwrap_or(crate::sip::headers::auth::Algorithm::Md5);
let response = DigestGenerator {
username: cred.username.as_str(),
password: cred.password.as_str(),
algorithm,
nonce: challenge.nonce.as_str(),
method: &tx.original.method,
qop: auth_qop.as_ref(),
uri: &tx.original.uri,
realm: challenge.realm.as_str(),
}
.compute();
let auth = Authorization {
scheme: challenge.scheme,
username: cred.username.clone(),
realm: challenge.realm,
nonce: challenge.nonce,
uri: tx.original.uri.clone(),
response,
algorithm: Some(algorithm),
opaque: challenge.opaque,
qop: auth_qop,
};
let mut via_header = tx.original.top_via_header()?.typed()?;
let params = &mut via_header.params;
params.retain(|p| !matches!(p, crate::sip::Param::Branch(_)));
params.push(make_via_branch());
if !params.iter().any(|p| matches!(p, Param::Rport(_))) {
params.push(Param::Rport(None));
}
new_req.headers_mut().unique_push(via_header.into());
new_req.headers_mut().retain(|h| {
!matches!(
h,
Header::ProxyAuthenticate(_)
| Header::Authorization(_)
| Header::WwwAuthenticate(_)
| Header::ProxyAuthorization(_)
)
});
match header {
Header::WwwAuthenticate(_) => {
new_req.headers_mut().unique_push(auth.into());
}
Header::ProxyAuthenticate(_) => {
new_req.headers_mut().unique_push(
ProxyAuthorization {
scheme: auth.scheme,
username: auth.username,
realm: auth.realm,
nonce: auth.nonce,
uri: auth.uri,
response: auth.response,
algorithm: auth.algorithm,
opaque: auth.opaque,
qop: auth.qop,
}
.into(),
);
}
_ => unreachable!(),
}
let key = TransactionKey::from_request(&new_req, TransactionRole::Client)?;
let mut new_tx = Transaction::new_client(
key,
new_req,
tx.endpoint_inner.clone(),
tx.connection.clone(),
);
new_tx.destination = tx.destination.clone();
Ok(new_tx)
}
fn hash_value(algorithm: Algorithm, value: &str) -> String {
use md5::Md5;
use sha2::{Digest, Sha256, Sha512};
match algorithm {
Algorithm::Md5 | Algorithm::Md5Sess => {
let mut hasher = Md5::new();
hasher.update(value.as_bytes());
encode_lower_hex(hasher.finalize())
}
Algorithm::Sha256 | Algorithm::Sha256Sess => {
let mut hasher = Sha256::new();
hasher.update(value.as_bytes());
encode_lower_hex(hasher.finalize())
}
Algorithm::Sha512 | Algorithm::Sha512Sess => {
let mut hasher = Sha512::new();
hasher.update(value.as_bytes());
encode_lower_hex(hasher.finalize())
}
}
}
fn encode_lower_hex(bytes: impl AsRef<[u8]>) -> String {
bytes
.as_ref()
.iter()
.map(|byte| format!("{:02x}", byte))
.collect()
}
pub fn compute_digest(
username: &str,
password: &str,
realm: &str,
nonce: &str,
method: &Method,
uri_raw: &str,
algorithm: Algorithm,
qop: Option<&AuthQop>,
) -> String {
let ha1 = hash_value(algorithm, &format!("{}:{}:{}", username, realm, password));
let ha2 = match qop {
None | Some(AuthQop::Auth { .. }) => {
hash_value(algorithm, &format!("{}:{}", method, uri_raw))
}
_ => hash_value(
algorithm,
&format!("{}:{}:d41d8cd98f00b204e9800998ecf8427e", method, uri_raw),
),
};
let value = match qop {
Some(AuthQop::Auth { cnonce, nc }) => {
format!("{}:{}:{:08}:{}:{}:{}", ha1, nonce, nc, cnonce, "auth", ha2)
}
Some(AuthQop::AuthInt { cnonce, nc }) => {
format!(
"{}:{}:{:08}:{}:{}:{}",
ha1, nonce, nc, cnonce, "auth-int", ha2
)
}
None => format!("{}:{}:{}", ha1, nonce, ha2),
};
hash_value(algorithm, &value)
}
pub fn extract_digest_uri_raw(header_value: &str) -> Option<String> {
use crate::sip::headers::typed::tokenizers::AuthTokenizer;
use crate::sip::headers::typed::Tokenize;
let tokenizer = AuthTokenizer::tokenize(header_value).ok()?;
tokenizer
.params
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case("uri"))
.map(|(_, value)| value.to_string())
}
pub fn verify_digest(
auth: &Authorization,
password: &str,
method: &Method,
raw_header_value: &str,
) -> bool {
let algorithm = auth.algorithm.unwrap_or(Algorithm::Md5);
let uri_str = match extract_digest_uri_raw(raw_header_value) {
Some(uri) => uri,
None => {
auth.uri.to_string()
}
};
let expected = compute_digest(
&auth.username,
password,
&auth.realm,
&auth.nonce,
method,
&uri_str,
algorithm,
auth.qop.as_ref(),
);
expected == auth.response
}