Compare commits

...

7 Commits

Author SHA1 Message Date
Ondřej Hruška fa3503e19a Bump version 12 months ago
Wolfgang Silbermayr 49d214bfd6 Upgrade dependencies 12 months ago
Ondřej Hruška 2827c9bbe5
optimize Method 3 years ago
Ondřej Hruška bae4c13597
improvements around HttpMethod, more tests 3 years ago
Ondřej Hruška a1f3c6ebfb Merge branch 'pr-method-lifetime' of slamb/digest_auth_rs into master 3 years ago
Scott Lamb 2e61615da0 allow non-'static HTTP method names 3 years ago
Ondřej Hruška 750294cd97 add changelog 3 years ago
  1. 15
      CHANGELOG.md
  2. 15
      Cargo.toml
  3. 2
      README.md
  4. 4
      src/digest.rs
  5. 296
      src/enums.rs
  6. 2
      src/error.rs
  7. 39
      src/lib.rs

@ -0,0 +1,15 @@
# 0.3.1
- Update deps
# 0.3.0
- Added lifetime parameter to `HttpMethod`
- Changed `HttpMethod` to `HttpMethod<'a>(Cow<'a, str>)`
- Added unit tests
- Added support for the `http` crate's `Method` struct (optional feature)
# 0.2.4
- Update dependencies

@ -1,6 +1,6 @@
[package]
name = "digest_auth"
version = "0.2.4"
version = "0.3.1"
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
edition = "2018"
description = "Implementation of the Digest Auth algorithm as defined in IETF RFC 2069, 2617, and 7616, intended for HTTP clients"
@ -17,6 +17,13 @@ license = "MIT"
[dependencies]
rand = "0.8"
hex = "0.4"
sha2 = "0.9"
md-5 = "0.9"
digest = "0.9"
sha2 = "0.10"
md-5 = "0.10"
digest = "0.10"
[dependencies.http]
version = "0.2.4"
optional = true
[features]
default = []

@ -2,3 +2,5 @@ Rust implementation of Digest Auth hashing algorithms,
as defined in IETF RFC 2069, 2617, and 7616.
This crate provides the authentication header parsing and generation code.
Please see the docs and tests for examples.

@ -161,7 +161,7 @@ pub struct AuthContext<'a> {
/// May be left out if not using auth-int
pub body: Option<Cow<'a, [u8]>>,
/// HTTP method used (defaults to GET)
pub method: HttpMethod,
pub method: HttpMethod<'a>,
/// Spoofed client nonce (use only for tests; a random nonce is generated automatically)
pub cnonce: Option<Cow<'a, str>>,
}
@ -202,7 +202,7 @@ impl<'a> AuthContext<'a> {
password: PW,
uri: UR,
body: Option<BD>,
method: HttpMethod,
method: HttpMethod<'a>,
) -> Self
where
UN: Into<Cow<'a, str>>,

@ -1,3 +1,5 @@
#![allow(clippy::upper_case_acronyms)]
use crate::{Error, Error::*, Result};
use std::fmt;
use std::fmt::{Display, Formatter};
@ -5,7 +7,8 @@ use std::str::FromStr;
use digest::{Digest, DynDigest};
use md5::Md5;
use sha2::{Sha256, Sha512Trunc256};
use sha2::{Sha256, Sha512_256};
use std::borrow::Cow;
/// Algorithm type
#[derive(Debug, PartialEq, Clone, Copy)]
@ -34,7 +37,7 @@ impl Algorithm {
let mut hash: Box<dyn DynDigest> = match self.algo {
AlgorithmType::MD5 => Box::new(Md5::new()),
AlgorithmType::SHA2_256 => Box::new(Sha256::new()),
AlgorithmType::SHA2_512_256 => Box::new(Sha512Trunc256::new()),
AlgorithmType::SHA2_512_256 => Box::new(Sha512_256::new()),
};
hash.update(bytes);
@ -127,10 +130,9 @@ pub enum QopAlgo<'a> {
}
// casting back...
impl<'a> Into<Option<Qop>> for QopAlgo<'a> {
/// Convert to ?Qop
fn into(self) -> Option<Qop> {
match self {
impl<'a> From<QopAlgo<'a>> for Option<Qop> {
fn from(algo: QopAlgo<'a>) -> Self {
match algo {
QopAlgo::NONE => None,
QopAlgo::AUTH => Some(Qop::AUTH),
QopAlgo::AUTH_INT(_) => Some(Qop::AUTH_INT),
@ -167,28 +169,282 @@ impl Display for Charset {
}
/// HTTP method (used when generating the response hash for some Qop options)
#[derive(Debug)]
pub enum HttpMethod {
GET,
POST,
HEAD,
OTHER(&'static str),
#[derive(Debug, PartialEq, Clone)]
pub struct HttpMethod<'a>(pub Cow<'a, str>);
// Well-known methods are provided as convenient associated constants
impl<'a> HttpMethod<'a> {
pub const GET : Self = HttpMethod(Cow::Borrowed("GET"));
pub const POST : Self = HttpMethod(Cow::Borrowed("POST"));
pub const PUT : Self = HttpMethod(Cow::Borrowed("PUT"));
pub const DELETE : Self = HttpMethod(Cow::Borrowed("DELETE"));
pub const HEAD : Self = HttpMethod(Cow::Borrowed("HEAD"));
pub const OPTIONS : Self = HttpMethod(Cow::Borrowed("OPTIONS"));
pub const CONNECT : Self = HttpMethod(Cow::Borrowed("CONNECT"));
pub const PATCH : Self = HttpMethod(Cow::Borrowed("PATCH"));
pub const TRACE : Self = HttpMethod(Cow::Borrowed("TRACE"));
}
impl Default for HttpMethod {
impl<'a> Default for HttpMethod<'a> {
fn default() -> Self {
HttpMethod::GET
}
}
impl Display for HttpMethod {
impl<'a> Display for HttpMethod<'a> {
/// Convert to uppercase string
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(match self {
HttpMethod::GET => "GET",
HttpMethod::POST => "POST",
HttpMethod::HEAD => "HEAD",
HttpMethod::OTHER(s) => s,
})
f.write_str(&self.0)
}
}
impl<'a> From<&'a str> for HttpMethod<'a> {
fn from(s: &'a str) -> Self {
Self(s.into())
}
}
impl<'a> From<&'a [u8]> for HttpMethod<'a> {
fn from(s: &'a [u8]) -> Self {
Self(String::from_utf8_lossy(s).into())
}
}
impl<'a> From<String> for HttpMethod<'a> {
fn from(s: String) -> Self {
Self(s.into())
}
}
impl<'a> From<Cow<'a, str>> for HttpMethod<'a> {
fn from(s: Cow<'a, str>) -> Self {
Self(s)
}
}
#[cfg(feature = "http")]
impl From<http::Method> for HttpMethod<'static> {
fn from(method: http::Method) -> Self {
match method.as_str() {
// Avoid cloning when possible
"GET" => Self::GET,
"POST" => Self::POST,
"PUT" => Self::PUT,
"DELETE" => Self::DELETE,
"HEAD" => Self::HEAD,
"OPTIONS" => Self::OPTIONS,
"CONNECT" => Self::CONNECT,
"PATCH" => Self::PATCH,
"TRACE" => Self::TRACE,
// Clone custom strings. This is inefficient, but the inner string is private
other => Self(other.to_owned().into())
}
}
}
#[cfg(feature = "http")]
impl<'a> From<&'a http::Method> for HttpMethod<'a> {
fn from(method: &'a http::Method) -> HttpMethod<'a> {
Self(method.as_str().into())
}
}
#[cfg(test)]
mod test {
use crate::error::Error::{BadCharset, BadQop, UnknownAlgorithm};
use crate::{Algorithm, AlgorithmType, Charset, HttpMethod, Qop, QopAlgo};
use std::borrow::Cow;
use std::str::FromStr;
#[test]
fn test_algorithm_type() {
// String parsing
assert_eq!(
Ok(Algorithm::new(AlgorithmType::MD5, false)),
Algorithm::from_str("MD5")
);
assert_eq!(
Ok(Algorithm::new(AlgorithmType::MD5, true)),
Algorithm::from_str("MD5-sess")
);
assert_eq!(
Ok(Algorithm::new(AlgorithmType::SHA2_256, false)),
Algorithm::from_str("SHA-256")
);
assert_eq!(
Ok(Algorithm::new(AlgorithmType::SHA2_256, true)),
Algorithm::from_str("SHA-256-sess")
);
assert_eq!(
Ok(Algorithm::new(AlgorithmType::SHA2_512_256, false)),
Algorithm::from_str("SHA-512-256")
);
assert_eq!(
Ok(Algorithm::new(AlgorithmType::SHA2_512_256, true)),
Algorithm::from_str("SHA-512-256-sess")
);
assert_eq!(
Err(UnknownAlgorithm("OTHER_ALGORITHM".to_string())),
Algorithm::from_str("OTHER_ALGORITHM")
);
// String building
assert_eq!(
"MD5".to_string(),
Algorithm::new(AlgorithmType::MD5, false).to_string()
);
assert_eq!(
"MD5-sess".to_string(),
Algorithm::new(AlgorithmType::MD5, true).to_string()
);
assert_eq!(
"SHA-256".to_string(),
Algorithm::new(AlgorithmType::SHA2_256, false).to_string()
);
assert_eq!(
"SHA-256-sess".to_string(),
Algorithm::new(AlgorithmType::SHA2_256, true).to_string()
);
assert_eq!(
"SHA-512-256".to_string(),
Algorithm::new(AlgorithmType::SHA2_512_256, false).to_string()
);
assert_eq!(
"SHA-512-256-sess".to_string(),
Algorithm::new(AlgorithmType::SHA2_512_256, true).to_string()
);
// Default
assert_eq!(
Algorithm::new(AlgorithmType::MD5, false),
Default::default()
);
// Hash calculation
assert_eq!(
"e2fc714c4727ee9395f324cd2e7f331f".to_string(),
Algorithm::new(AlgorithmType::MD5, false).hash("abcd".as_bytes())
);
assert_eq!(
"e2fc714c4727ee9395f324cd2e7f331f".to_string(),
Algorithm::new(AlgorithmType::MD5, false).hash_str("abcd")
);
assert_eq!(
"88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589".to_string(),
Algorithm::new(AlgorithmType::SHA2_256, false).hash("abcd".as_bytes())
);
assert_eq!(
"d2891c7978be0e24948f37caa415b87cb5cbe2b26b7bad9dc6391b8a6f6ddcc9".to_string(),
Algorithm::new(AlgorithmType::SHA2_512_256, false).hash("abcd".as_bytes())
);
}
#[test]
fn test_qop() {
assert_eq!(Ok(Qop::AUTH), Qop::from_str("auth"));
assert_eq!(Ok(Qop::AUTH_INT), Qop::from_str("auth-int"));
assert_eq!(Err(BadQop("banana".to_string())), Qop::from_str("banana"));
assert_eq!("auth".to_string(), Qop::AUTH.to_string());
assert_eq!("auth-int".to_string(), Qop::AUTH_INT.to_string());
}
#[test]
fn test_qop_algo() {
assert_eq!(Option::<Qop>::None, QopAlgo::NONE.into());
assert_eq!(Some(Qop::AUTH), QopAlgo::AUTH.into());
assert_eq!(
Some(Qop::AUTH_INT),
QopAlgo::AUTH_INT("foo".as_bytes()).into()
);
}
#[test]
fn test_charset() {
assert_eq!(Ok(Charset::UTF8), Charset::from_str("UTF-8"));
assert_eq!(Err(BadCharset("ASCII".into())), Charset::from_str("ASCII"));
assert_eq!("UTF-8".to_string(), Charset::UTF8.to_string());
assert_eq!("ASCII".to_string(), Charset::ASCII.to_string());
}
#[test]
fn test_http_method() {
// Well known 'static
assert_eq!(HttpMethod::GET, "GET".into());
assert_eq!(HttpMethod::POST, "POST".into());
assert_eq!(HttpMethod::PUT, "PUT".into());
assert_eq!(HttpMethod::DELETE, "DELETE".into());
assert_eq!(HttpMethod::HEAD, "HEAD".into());
assert_eq!(HttpMethod::OPTIONS, "OPTIONS".into());
assert_eq!(HttpMethod::CONNECT, "CONNECT".into());
assert_eq!(HttpMethod::PATCH, "PATCH".into());
assert_eq!(HttpMethod::TRACE, "TRACE".into());
// As bytes
assert_eq!(HttpMethod::GET, "GET".as_bytes().into());
assert_eq!(
HttpMethod(Cow::Borrowed("ěščř")),
"ěščř".as_bytes().into()
);
assert_eq!(
HttpMethod(Cow::Owned("AB<EFBFBD>".to_string())), // Lossy conversion
(&[65u8, 66, 156][..]).into()
);
// Well known String
assert_eq!(HttpMethod::GET, String::from("GET").into());
// Custom String
assert_eq!(
HttpMethod(Cow::Borrowed("NonsenseMethod")),
"NonsenseMethod".into()
);
assert_eq!(
HttpMethod(Cow::Owned("NonsenseMethod".to_string())),
"NonsenseMethod".to_string().into()
);
// Custom Cow
assert_eq!(HttpMethod::HEAD, Cow::Borrowed("HEAD").into());
assert_eq!(
HttpMethod(Cow::Borrowed("NonsenseMethod")),
Cow::Borrowed("NonsenseMethod").into()
);
// to string
assert_eq!("GET".to_string(), HttpMethod::GET.to_string());
assert_eq!("POST".to_string(), HttpMethod::POST.to_string());
assert_eq!("PUT".to_string(), HttpMethod::PUT.to_string());
assert_eq!("DELETE".to_string(), HttpMethod::DELETE.to_string());
assert_eq!("HEAD".to_string(), HttpMethod::HEAD.to_string());
assert_eq!("OPTIONS".to_string(), HttpMethod::OPTIONS.to_string());
assert_eq!("CONNECT".to_string(), HttpMethod::CONNECT.to_string());
assert_eq!("PATCH".to_string(), HttpMethod::PATCH.to_string());
assert_eq!("TRACE".to_string(), HttpMethod::TRACE.to_string());
assert_eq!(
"NonsenseMethod".to_string(),
HttpMethod(Cow::Borrowed("NonsenseMethod")).to_string()
);
assert_eq!(
"NonsenseMethod".to_string(),
HttpMethod(Cow::Owned("NonsenseMethod".to_string())).to_string()
);
}
#[cfg(feature = "http")]
#[test]
fn test_http_crate() {
assert_eq!(HttpMethod::GET, http::Method::GET.clone().into());
assert_eq!(
HttpMethod(Cow::Owned("BANANA".to_string())),
http::Method::from_str("BANANA").unwrap().into()
);
assert_eq!(HttpMethod::GET, (&http::Method::GET).into());
let x = http::Method::from_str("BANANA").unwrap();
assert_eq!(
HttpMethod(Cow::Borrowed("BANANA")),
(&x).into()
);
}
}

@ -1,7 +1,7 @@
use std::fmt::{self, Display, Formatter};
use std::result;
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum Error {
BadCharset(String),
UnknownAlgorithm(String),

@ -62,9 +62,13 @@ pub fn parse(www_authorize: &str) -> Result<WwwAuthenticateHeader> {
WwwAuthenticateHeader::parse(www_authorize)
}
#[test]
fn test_parse_respond() {
let src = r#"
#[cfg(test)]
mod test {
use crate::{AuthContext, Error};
#[test]
fn test_parse_respond() {
let src = r#"
Digest
realm="http-auth@example.org",
qop="auth, auth-int",
@ -73,17 +77,17 @@ fn test_parse_respond() {
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
"#;
let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html");
context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ");
let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html");
context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ");
let mut prompt = crate::parse(src).unwrap();
let answer = prompt.respond(&context).unwrap();
let mut prompt = crate::parse(src).unwrap();
let answer = prompt.respond(&context).unwrap();
let str = answer.to_string().replace(", ", ",\n ");
let str = answer.to_string().replace(", ", ",\n "); // This is only for easier reading
assert_eq!(
str,
r#"
assert_eq!(
str,
r#"
Digest username="Mufasa",
realm="http-auth@example.org",
nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
@ -95,11 +99,12 @@ Digest username="Mufasa",
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS",
algorithm=MD5
"#
.trim()
);
}
.trim()
);
}
#[test]
fn test_cast_error() {
let _m: Box<dyn std::error::Error> = Error::UnknownAlgorithm("Uhhh".into()).into();
#[test]
fn test_cast_error() {
let _m: Box<dyn std::error::Error> = Error::UnknownAlgorithm("Uhhh".into()).into();
}
}

Loading…
Cancel
Save