///|
pub struct Credentials {
priv user : String
priv password : String
}
///|
pub fn Credentials::new(
user : String,
password : String,
) -> Credentials raise RtspError {
require(
user.length() > 0 && user.length() <= 256 && password.length() <= 1024,
"credential length",
)
require(
clean(user) && clean(password) && !user.contains(":"),
"credential characters",
)
{ user, password, }
}
///|
pub struct Authenticator {
priv credentials : Credentials
priv mut challenge : String
priv mut count : Int32
priv cnonce : String
}
///|
/// Caller supplies an unpredictable cnonce for production Digest qop=auth.
pub fn Authenticator::new(
credentials : Credentials,
cnonce : String,
) -> Authenticator raise RtspError {
require(
cnonce.length() >= 8 && cnonce.length() <= 128 && clean(cnonce),
"cnonce",
)
{ credentials, challenge: "", count: 0, cnonce, }
}
///|
pub fn Authenticator::challenge(
self : Authenticator,
header : String,
) -> Unit raise RtspError {
require(
header.has_prefix("Basic ") || header.has_prefix("Digest "),
"authentication scheme",
)
let offset = if header.has_prefix("Basic ") { 6 } else { 7 }
let params = parse_parameters(header[offset:].to_owned())
if offset == 7 {
require(
params.contains("realm") && params.contains("nonce"),
"Digest realm and nonce",
)
require(
params.get("algorithm").unwrap_or("MD5") == "MD5",
"Digest algorithm unsupported",
)
if params.get("qop") is Some(qop) {
require(
qop.split(",").any(x => trim(x.to_owned()) == "auth"),
"Digest qop unsupported",
)
}
}
if header != self.challenge {
self.count = 0
}
self.challenge = header
}
///|
fn md5_hex(s : String) -> String {
let digest = @md5.Digest::new()
for b in octets(s) {
digest.write(b)
}
digest.check_sum()
}
///|
fn hex_count(n : Int32) -> String {
let digits = "0123456789abcdef"
let out = StringBuilder()
for i = 7; i >= 0; i = i - 1 {
let j = (n >> (i * 4)) & 15
out.write_string(digits[j:j + 1].to_owned())
}
out.to_string()
}
///|
pub fn Authenticator::authorization(
self : Authenticator,
verb : String,
uri : String,
) -> String raise RtspError {
require(self.challenge != "", "missing challenge")
let user = self.credentials.user
let password = self.credentials.password
if self.challenge.has_prefix("Basic ") {
return "Basic " + @base64.encode(octets(user + ":" + password))
}
let p = parse_parameters(self.challenge[7:].to_owned())
let realm = p["realm"]
let nonce = p["nonce"]
require(self.count < 2147483647, "Digest nonce count exhausted")
self.count += 1
let nc = hex_count(self.count)
let has_qop = p.contains("qop")
let middle = if has_qop {
nonce + ":" + nc + ":" + self.cnonce + ":auth"
} else {
nonce
}
let response = md5_hex(
md5_hex(user + ":" + realm + ":" + password) +
":" +
middle +
":" +
md5_hex(verb + ":" + uri),
)
let mut result = "Digest username=" +
quoted(user) +
", realm=" +
quoted(realm) +
", nonce=" +
quoted(nonce) +
", uri=" +
quoted(uri) +
", response=" +
quoted(response) +
", algorithm=MD5"
if has_qop {
result += ", qop=auth, nc=" + nc + ", cnonce=" + quoted(self.cnonce)
}
if p.get("opaque") is Some(v) {
result += ", opaque=" + quoted(v)
}
result
}