Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/hawk/src/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 31 kB image not shown  

Quelle  request.rs

  Sprache: Rust
 

use crate::b64;
use crate::bewit::Bewit;
use crate::credentials::{Credentials, Key};
use crate::error::*;
use crate::header::Header;
use crate::mac::{Mac, MacType};
use crate::response::ResponseBuilder;
use base64::Engine;
use log::debug;
use std::borrow::Cow;
use std::str;
use std::str::FromStr;
use std::time::{Duration, SystemTime};
use url::{Position, Url};

/// Request represents a single HTTP request.
///
/// The structure is created using (RequestBuilder)[struct.RequestBuilder.html]. Most uses of this
/// library will hold several of the fields in this structure fixed. Cloning the structure with
/// these fields applied is a convenient way to avoid repeating those fields. Most fields are
/// references, since in common use the values already exist and will outlive the request.
///
/// A request can be used on the client, to generate a header or a bewit, or on the server, to
/// validate the same.
///
/// # Examples
///
/// ```
/// use hawk::RequestBuilder;
/// let bldr = RequestBuilder::new("GET", "mysite.com", 443, "/");
/// let request1 = bldr.clone().method("POST").path("/api/user").request();
/// let request2 = bldr.path("/api/users").request();
/// ```
///
/// See the documentation in the crate root for examples of creating and validating headers.
#[derive(Debug, Clone)]
pub struct Request<'a> {
    method: &'a str,
    host: &'a str,
    port: u16,
    path: Cow<'a, str>,
    hash: Option<&'a [u8]>,
    ext: Option<&'a str>,
    app: Option<&'a str>,
    dlg: Option<&'a str>,
}

impl<'a> Request<'a> {
    /// Create a new Header for this request, inventing a new nonce and setting the
    /// timestamp to the current time.
    pub fn make_header(&self, credentials: &Credentials) -> Result<Header> {
        let nonce = random_string(10)?;
        self.make_header_full(credentials, SystemTime::now(), nonce)
    }

    /// Similar to `make_header`, but allowing specification of the timestamp
    /// and nonce.
    pub fn make_header_full<S>(
        &self,
        credentials: &Credentials,
        ts: SystemTime,
        nonce: S,
    ) -> Result<Header>
    where
        S: Into<String>,
    {
        let nonce = nonce.into();
        let mac = Mac::new(
            MacType::Header,
            &credentials.key,
            ts,
            &nonce,
            self.method,
            self.host,
            self.port,
            self.path.as_ref(),
            self.hash,
            self.ext,
        )?;
        Header::new(
            Some(credentials.id.clone()),
            Some(ts),
            Some(nonce),
            Some(mac),
            self.ext.map(|v| v.to_string()),
            self.hash.map(|v| v.to_vec()),
            self.app.map(|v| v.to_string()),
            self.dlg.map(|v| v.to_string()),
        )
    }

    /// Make a "bewit" that can be attached to a URL to authenticate GET access.
    ///
    /// The ttl gives the time for which this bewit is valid, starting now.
    pub fn make_bewit(&self, credentials: &'a Credentials, exp: SystemTime) -> Result<Bewit<'a>> {
        // note that this includes `method` and `hash` even though they must always be GET and None
        // for bewits.  If they aren't, then the bewit just won't validate -- no need to catch
        // that now
        let mac = Mac::new(
            MacType::Bewit,
            &credentials.key,
            exp,
            "",
            self.method,
            self.host,
            self.port,
            self.path.as_ref(),
            self.hash,
            self.ext,
        )?;
        let bewit = Bewit::new(&credentials.id, exp, mac, self.ext);
        Ok(bewit)
    }

    /// Variant of `make_bewit` that takes a Duration (starting from now)
    /// instead of a SystemTime, provided for convenience.
    pub fn make_bewit_with_ttl(
        &self,
        credentials: &'a Credentials,
        ttl: Duration,
    ) -> Result<Bewit<'a>> {
        let exp = SystemTime::now() + ttl;
        self.make_bewit(credentials, exp)
    }

    /// Validate the given header.  This validates that the `mac` field matches that calculated
    /// using the other header fields and the given request information.
    ///
    /// The header's timestamp is verified to be within `ts_skew` of the current time.  If any of
    /// the required header fields are missing, the method will return false.
    ///
    /// It is up to the caller to examine the header's `id` field and supply the corresponding key.
    ///
    /// If desired, it is up to the caller to validate that `nonce` has not been used before.
    ///
    /// If a hash has been supplied, then the header must contain a matching hash. Note that this
    /// hash must be calculated based on the request body, not copied from the request header!
    pub fn validate_header(&self, header: &Header, key: &Key, ts_skew: Duration) -> bool {
        // extract required fields, returning early if they are not present
        let ts = match header.ts {
            Some(ts) => ts,
            None => {
                debug!("missing timestamp from header");
                return false;
            }
        };
        let nonce = match header.nonce {
            Some(ref nonce) => nonce,
            None => {
                debug!("missing nonce from header");
                return false;
            }
        };
        let header_mac = match header.mac {
            Some(ref mac) => mac,
            None => {
                debug!("missing mac from header");
                return false;
            }
        };
        let header_hash = header.hash.as_ref().map(|hash| &hash[..]);
        let header_ext = header.ext.as_ref().map(|ext| &ext[..]);

        // first verify the MAC
        match Mac::new(
            MacType::Header,
            key,
            ts,
            nonce,
            self.method,
            self.host,
            self.port,
            self.path.as_ref(),
            header_hash,
            header_ext,
        ) {
            Ok(calculated_mac) => {
                if &calculated_mac != header_mac {
                    debug!("calculated mac doesn't match header");
                    return false;
                }
            }
            Err(e) => {
                debug!("unexpected mac error: {:?}", e);
                return false;
            }
        };

        // ..then the hashes
        if let Some(local_hash) = self.hash {
            if let Some(server_hash) = header_hash {
                if local_hash != server_hash {
                    debug!("server hash doesn't match header");
                    return false;
                }
            } else {
                debug!("missing hash from header");
                return false;
            }
        }

        // ..then the timestamp
        let now = SystemTime::now();
        let skew = if now > ts {
            now.duration_since(ts).unwrap()
        } else {
            ts.duration_since(now).unwrap()
        };
        if skew > ts_skew {
            debug!(
                "bad timestamp skew, timestamp too old? detected skew: {:?}, ts_skew: {:?}",
                &skew, &ts_skew
            );
            return false;
        }

        true
    }

    /// Validate the given bewit matches this request.
    ///
    /// It is up to the caller to consult the Bewit's `id` and look up the
    /// corresponding key.
    ///
    /// Nonces and hashes do not apply when using bewits.
    pub fn validate_bewit(&self, bewit: &Bewit, key: &Key) -> bool {
        let calculated_mac = Mac::new(
            MacType::Bewit,
            key,
            bewit.exp(),
            "",
            self.method,
            self.host,
            self.port,
            self.path.as_ref(),
            self.hash,
            match bewit.ext() {
                Some(e) => Some(e),
                None => None,
            },
        );
        let calculated_mac = match calculated_mac {
            Ok(m) => m,
            Err(_) => {
                return false;
            }
        };

        if bewit.mac() != &calculated_mac {
            return false;
        }

        let now = SystemTime::now();
        if bewit.exp() < now {
            return false;
        }

        true
    }

    /// Get a Response instance for a response to this request.  This is a convenience
    /// wrapper around `Response::from_request_header`.
    pub fn make_response_builder(&'a self, req_header: &'a Header) -> ResponseBuilder<'a> {
        ResponseBuilder::from_request_header(
            req_header,
            self.method,
            self.host,
            self.port,
            self.path.as_ref(),
        )
    }
}

#[derive(Debug, Clone)]
pub struct RequestBuilder<'a>(Request<'a>);

impl<'a> RequestBuilder<'a> {
    /// Create a new request with the given method, host, port, and path.
    pub fn new(method: &'a str, host: &'a str, port: u16, path: & style='color:blue'>'a str) -> Self {
        RequestBuilder(Request {
            method,
            host,
            port,
            path: Cow::Borrowed(path),
            hash: None,
            ext: None,
            app: None,
            dlg: None,
        })
    }

    /// Create a new request with the host, port, and path determined from the URL.
    pub fn from_url(method: &'a str, url: &'a Url) -> Result<Self> {
        let (host, port, path) = RequestBuilder::parse_url(url)?;
        Ok(RequestBuilder(Request {
            method,
            host,
            port,
            path: Cow::Borrowed(path),
            hash: None,
            ext: None,
            app: None,
            dlg: None,
        }))
    }

    /// Set the request method. This should be a capitalized string.
    pub fn method(mut self, method: &'a str) -> Self {
        self.0.method = method;
        self
    }

    /// Set the URL path for the request.
    pub fn path(mut self, path: &'a str) -> Self {
        self.0.path = Cow::Borrowed(path);
        self
    }

    /// Set the URL hostname for the request
    pub fn host(mut self, host: &'a str) -> Self {
        self.0.host = host;
        self
    }

    /// Set the URL port for the request
    pub fn port(mut self, port: u16) -> Self {
        self.0.port = port;
        self
    }

    /// Set the hostname, port, and path for the request, from a string URL.
    pub fn url(self, url: &'a Url) -> Result<Self> {
        let (host, port, path) = RequestBuilder::parse_url(url)?;
        Ok(self.path(path).host(host).port(port))
    }

    /// Set the content hash for the request
    pub fn hash<H: Into<Option<&'a [u8]>>>(mut self, hash: H) -> Self {
        self.0.hash = hash.into();
        self
    }

    /// Set the `ext` Hawk property for the request
    pub fn ext<S: Into<Option<&'a str>>>(mut self, ext: S) -> Self {
        self.0.ext = ext.into();
        self
    }

    /// Set the `app` Hawk property for the request
    pub fn app<S: Into<Option<&'a str>>>(mut self, app: S) -> Self {
        self.0.app = app.into();
        self
    }

    /// Set the `dlg` Hawk property for the request
    pub fn dlg<S: Into<Option<&'a str>>>(mut self, dlg: S) -> Self {
        self.0.dlg = dlg.into();
        self
    }

    /// Get the request from this builder
    pub fn request(self) -> Request<'a> {
        self.0
    }

    /// Extract the `bewit` query parameter, if any, from the path, and return it in the output
    /// parameter, returning a modified RequestBuilder omitting the `bewit=..` query parameter.  If
    /// no bewit is present, or if an error is returned, the output parameter is reset to None.
    ///
    /// The path manipulation is tested to correspond to that preformed by the hueniverse/hawk
    /// implementation-specification
    pub fn extract_bewit(mut self, bewit: &mut Option<Bewit<'a>>) -> Result<Self> {
        const PREFIX: &str = "bewit=";
        *bewit = None;

        if let Some(query_index) = self.0.path.find('?') {
            let (bewit_components, components): (Vec<&str>, Vec<&str>) = self.0.path
                [query_index + 1..]
                .split('&')
                .partition(|comp| comp.starts_with(PREFIX));

            if bewit_components.len() == 1 {
                let bewit_str = bewit_components[0];
                *bewit = Some(Bewit::from_str(&bewit_str[PREFIX.len()..])?);

                // update the path to omit the bewit=... segment
                let new_path = if !components.is_empty() {
                    format!("{}{}", &self.0.path[..=query_index], components.join("&"))
                } else {
                    // no query left, so return the remaining path, omitting the '?'
                    self.0.path[..query_index].to_string()
                };
                self.0.path = Cow::Owned(new_path);
                Ok(self)
            } else if bewit_components.is_empty() {
                Ok(self)
            } else {
                Err(InvalidBewit::Multiple.into())
            }
        } else {
            Ok(self)
        }
    }

    fn parse_url(url: &'a Url) -> Result<(&'a str, u16, &'color:blue'>'a str)> {
        let host = url
            .host_str()
            .ok_or_else(|| Error::InvalidUrl(format!("url {url} has no host")))?;
        let port = url
            .port_or_known_default()
            .ok_or_else(|| Error::InvalidUrl(format!("url {url} has no port")))?;
        let path = &url[Position::BeforePath..];
        Ok((host, port, path))
    }
}

/// Create a random string with `bytes` bytes of entropy.  The string
/// is base64-encoded. so it will be longer than bytes characters.
fn random_string(bytes: usize) -> Result<String> {
    let mut bytes = vec![0u8; bytes];
    crate::crypto::rand_bytes(&mut bytes)?;
    Ok(b64::BEWIT_ENGINE.encode(&bytes))
}

#[cfg(all(test, any(feature = "use_ring", feature = "use_openssl")))]
mod test {
    use super::*;
    use crate::credentials::{Credentials, Key};
    use crate::header::Header;
    use std::str::FromStr;
    use std::time::{Duration, SystemTime, UNIX_EPOCH};
    use url::Url;

    // this is a header from a real request using the JS Hawk library, to
    // https://pulse.taskcluster.net:443/v1/namespaces with credentials "me" / "tok"
    const REAL_HEADER: &str = "id=\"me\", ts=\"1491183061\", nonce=\"RVnYzW\", \
                                       mac=\"1kqRT9EoxiZ9AA/ayOCXB+AcjfK/BoJ+n7z0gfvZotQ=\"";
    const BEWIT_STR: &str =
        "bWVcMTM1MzgzMjgzNFxmaXk0ZTV3QmRhcEROeEhIZUExOE5yU3JVMVUzaVM2NmdtMFhqVEpwWXlVPVw";

    // this is used as the initial bewit when calling extract_bewit, to verify that it is
    // not allowing the original value of the parameter to remain in place.
    const INITIAL_BEWIT_STR: &str =
        "T0ggTk9FU1wxMzUzODMyODM0XGZpeTRlNXdCZGFwRE54SEhlQTE4TnJTclUxVTNpUzY2Z20wWGpUSnBZeVU9XCZtdXQgYmV3aXQgbm90IHJlc2V0IQ";

    #[test]
    fn test_empty() {
        let req = RequestBuilder::new("GET""site"80"/").request();
        assert_eq!(req.method, "GET");
        assert_eq!(req.host, "site");
        assert_eq!(req.port, 80);
        assert_eq!(req.path, "/");
        assert_eq!(req.hash, None);
        assert_eq!(req.ext, None);
        assert_eq!(req.app, None);
        assert_eq!(req.dlg, None);
    }

    #[test]
    fn test_builder() {
        let hash = vec![0u8];
        let req = RequestBuilder::new("GET""example.com"443"/foo")
            .hash(Some(&hash[..]))
            .ext("ext")
            .app("app")
            .dlg("dlg")
            .request();

        assert_eq!(req.method, "GET");
        assert_eq!(req.path, "/foo");
        assert_eq!(req.host, "example.com");
        assert_eq!(req.port, 443);
        assert_eq!(req.hash, Some(&hash[..]));
        assert_eq!(req.ext, Some("ext"));
        assert_eq!(req.app, Some("app"));
        assert_eq!(req.dlg, Some("dlg"));
    }

    #[test]
    fn test_builder_clone() {
        let rb = RequestBuilder::new("GET""site"443"/foo");
        let req = rb.clone().request();
        let req2 = rb.path("/bar").request();

        assert_eq!(req.method, "GET");
        assert_eq!(req.path, "/foo");
        assert_eq!(req2.method, "GET");
        assert_eq!(req2.path, "/bar");
    }

    #[test]
    fn test_url_builder() {
        let url = Url::parse("https://example.com/foo").unwrap();
        let req = RequestBuilder::from_url("GET", &url).unwrap().request();

        assert_eq!(req.path, "/foo");
        assert_eq!(req.host, "example.com");
        assert_eq!(req.port, 443); // default for https
    }

    #[test]
    fn test_url_builder_with_query() {
        let url = Url::parse("https://example.com/foo?foo=bar").unwrap();
        let bldr = RequestBuilder::from_url("GET", &url).unwrap();

        let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
        let bldr = bldr.extract_bewit(&mut bewit).unwrap();
        assert_eq!(bewit, None);

        let req = bldr.request();

        assert_eq!(req.path, "/foo?foo=bar");
        assert_eq!(req.host, "example.com");
        assert_eq!(req.port, 443); // default for https
    }

    #[test]
    fn test_url_builder_with_encodable_chars() {
        let url = Url::parse("https://example.com/ñoo?foo=año").unwrap();
        let bldr = RequestBuilder::from_url("GET", &url).unwrap();

        let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
        let bldr = bldr.extract_bewit(&mut bewit).unwrap();
        assert_eq!(bewit, None);

        let req = bldr.request();

        assert_eq!(req.path, "/%C3%B1oo?foo=a%C3%B1o");
        assert_eq!(req.host, "example.com");
        assert_eq!(req.port, 443); // default for https
    }

    #[test]
    fn test_url_builder_with_empty_query() {
        let url = Url::parse("https://example.com/foo?").unwrap();
        let bldr = RequestBuilder::from_url("GET", &url).unwrap();

        let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
        let bldr = bldr.extract_bewit(&mut bewit).unwrap();
        assert_eq!(bewit, None);

        let req = bldr.request();

        assert_eq!(req.path, "/foo?");
        assert_eq!(req.host, "example.com");
        assert_eq!(req.port, 443); // default for https
    }

    #[test]
    fn test_url_builder_with_bewit_alone() {
        let url = Url::parse(&format!("https://example.com/foo?bewit={BEWIT_STR}")).unwrap();
        let bldr = RequestBuilder::from_url("GET", &url).unwrap();

        let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
        let bldr = bldr.extract_bewit(&mut bewit).unwrap();
        assert_eq!(bewit, Some(Bewit::from_str(BEWIT_STR).unwrap()));

        let req = bldr.request();

        assert_eq!(req.path, "/foo"); // NOTE: strips the `?`
        assert_eq!(req.host, "example.com");
        assert_eq!(req.port, 443); // default for https
    }

    #[test]
    fn test_url_builder_with_bewit_first() {
        let url = Url::parse(&format!("https://example.com/foo?bewit={BEWIT_STR}&a=1")).unwrap();
        let bldr = RequestBuilder::from_url("GET", &url).unwrap();

        let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
        let bldr = bldr.extract_bewit(&mut bewit).unwrap();
        assert_eq!(bewit, Some(Bewit::from_str(BEWIT_STR).unwrap()));

        let req = bldr.request();

        assert_eq!(req.path, "/foo?a=1");
        assert_eq!(req.host, "example.com");
        assert_eq!(req.port, 443); // default for https
    }

    #[test]
    fn test_url_builder_with_bewit_multiple() {
        let url = Url::parse(&format!(
            "https://example.com/foo?bewit={BEWIT_STR}&bewit={BEWIT_STR}"
        ))
        .unwrap();
        let bldr = RequestBuilder::from_url("GET", &url).unwrap();

        let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
        assert!(bldr.extract_bewit(&mut bewit).is_err());
        assert_eq!(bewit, None);
    }

    #[test]
    fn test_url_builder_with_bewit_invalid() {
        let url = Url::parse("https://example.com/foo?bewit=1234").unwrap();
        let bldr = RequestBuilder::from_url("GET", &url).unwrap();

        let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
        assert!(bldr.extract_bewit(&mut bewit).is_err());
        assert_eq!(bewit, None);
    }

    #[test]
    fn test_url_builder_with_bewit_last() {
        let url = Url::parse(&format!("https://example.com/foo?a=1&bewit={BEWIT_STR}")).unwrap();
        let bldr = RequestBuilder::from_url("GET", &url).unwrap();

        let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
        let bldr = bldr.extract_bewit(&mut bewit).unwrap();
        assert_eq!(bewit, Some(Bewit::from_str(BEWIT_STR).unwrap()));

        let req = bldr.request();

        assert_eq!(req.path, "/foo?a=1");
        assert_eq!(req.host, "example.com");
        assert_eq!(req.port, 443); // default for https
    }

    #[test]
    fn test_url_builder_with_bewit_middle() {
        let url = Url::parse(&format!(
            "https://example.com/foo?a=1&bewit={BEWIT_STR}&b=2"
        ))
        .unwrap();
        let bldr = RequestBuilder::from_url("GET", &url).unwrap();

        let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
        let bldr = bldr.extract_bewit(&mut bewit).unwrap();
        assert_eq!(bewit, Some(Bewit::from_str(BEWIT_STR).unwrap()));

        let req = bldr.request();

        assert_eq!(req.path, "/foo?a=1&b=2");
        assert_eq!(req.host, "example.com");
        assert_eq!(req.port, 443); // default for https
    }

    #[test]
    fn test_url_builder_with_bewit_percent_encoding() {
        // Note that this *over*-encodes things.  Perfectly legal, but the kind
        // of thing that incautious libraries can sometimes fail to reproduce,
        // causing Hawk validation failures
        let url = Url::parse(&format!(
            "https://example.com/foo?%66oo=1&bewit={BEWIT_STR}&%62ar=2"
        ))
        .unwrap();
        let bldr = RequestBuilder::from_url("GET", &url).unwrap();

        let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
        let bldr = bldr.extract_bewit(&mut bewit).unwrap();
        assert_eq!(bewit, Some(Bewit::from_str(BEWIT_STR).unwrap()));

        let req = bldr.request();

        assert_eq!(req.path, "/foo?%66oo=1&%62ar=2");
        assert_eq!(req.host, "example.com");
        assert_eq!(req.port, 443); // default for https
    }

    #[test]
    fn test_url_builder_with_xxxbewit() {
        // check that we're not doing a simple string search for "bewit=.."
        let url = Url::parse(&format!(
            "https://example.com/foo?a=1&xxxbewit={BEWIT_STR}&b=2"
        ))
        .unwrap();
        let bldr = RequestBuilder::from_url("GET", &url).unwrap();

        let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
        let bldr = bldr.extract_bewit(&mut bewit).unwrap();
        assert_eq!(bewit, None);

        let req = bldr.request();

        assert_eq!(req.path, format!("/foo?a=1&xxxbewit={BEWIT_STR}&b=2"));
        assert_eq!(req.host, "example.com");
        assert_eq!(req.port, 443); // default for https
    }

    #[test]
    fn test_url_builder_with_username_password() {
        let url = Url::parse("https://a:b@example.com/foo?x=y").unwrap();
        let bldr = RequestBuilder::from_url("GET", &url).unwrap();

        let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
        let bldr = bldr.extract_bewit(&mut bewit).unwrap();
        assert_eq!(bewit, None);

        let req = bldr.request();

        assert_eq!(req.path, "/foo?x=y");
        assert_eq!(req.host, "example.com");
        assert_eq!(req.port, 443); // default for https
    }

    #[test]
    fn test_make_header_full() {
        let req = RequestBuilder::new("GET""example.com"443"/foo").request();
        let credentials = Credentials {
            id: "me".to_string(),
            key: Key::new(vec![99u8; 32], crate::SHA256).unwrap(),
        };
        let header = req
            .make_header_full(&credentials, UNIX_EPOCH + Duration::new(1000100), "nonny")
            .unwrap();
        assert_eq!(
            header,
            Header {
                id: Some("me".to_string()),
                ts: Some(UNIX_EPOCH + Duration::new(1000100)),
                nonce: Some("nonny".to_string()),
                mac: Some(Mac::from(vec![
                    1224725319524718510713325061134200351189448,
                    1752371086071422446641172917233140
                ])),
                ext: None,
                hash: None,
                app: None,
                dlg: None,
            }
        );
    }

    #[test]
    fn test_make_header_full_with_optional_fields() {
        let hash = vec![0u8];
        let req = RequestBuilder::new("GET""example.com"443"/foo")
            .hash(Some(&hash[..]))
            .ext("ext")
            .app("app")
            .dlg("dlg")
            .request();
        let credentials = Credentials {
            id: "me".to_string(),
            key: Key::new(vec![99u8; 32], crate::SHA256).unwrap(),
        };
        let header = req
            .make_header_full(&credentials, UNIX_EPOCH + Duration::new(1000100), "nonny")
            .unwrap();
        assert_eq!(
            header,
            Header {
                id: Some("me".to_string()),
                ts: Some(UNIX_EPOCH + Duration::new(1000100)),
                nonce: Some("nonny".to_string()),
                mac: Some(Mac::from(vec![
                    721232432141458112954183902213619214620853216,
                    13814594175204217877162025010144133162
                ])),
                ext: Some("ext".to_string()),
                hash: Some(hash.clone()),
                app: Some("app".to_string()),
                dlg: Some("dlg".to_string()),
            }
        );
    }

    #[test]
    fn test_validate_matches_generated() {
        let req = RequestBuilder::new("GET""example.com"443"/foo").request();
        let credentials = Credentials {
            id: "me".to_string(),
            key: Key::new(vec![99u8; 32], crate::SHA256).unwrap(),
        };
        let header = req
            .make_header_full(&credentials, SystemTime::now(), "nonny")
            .unwrap();
        assert!(req.validate_header(&header, &credentials.key, Duration::from_secs(60)));
    }

    // Well, close enough.
    const ONE_YEAR_IN_SECS: u64 = 365 * 24 * 60 * 60;

    #[test]
    fn test_validate_real_request() {
        let header = Header::from_str(REAL_HEADER).unwrap();
        let credentials = Credentials {
            id: "me".to_string(),
            key: Key::new("tok"crate::SHA256).unwrap(),
        };
        let req =
            RequestBuilder::new("GET""pulse.taskcluster.net"443"/v1/namespaces").request();
        // allow 1000 years skew, since this was a real request that
        // happened back in 2017, when life was simple and carefree
        assert!(req.validate_header(
            &header,
            &credentials.key,
            Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
        ));
    }

    #[test]
    fn test_validate_real_request_bad_creds() {
        let header = Header::from_str(REAL_HEADER).unwrap();
        let credentials = Credentials {
            id: "me".to_string(),
            key: Key::new("WRONG"crate::SHA256).unwrap(),
        };
        let req =
            RequestBuilder::new("GET""pulse.taskcluster.net"443"/v1/namespaces").request();
        assert!(!req.validate_header(
            &header,
            &credentials.key,
            Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
        ));
    }

    #[test]
    fn test_validate_real_request_bad_req_info() {
        let header = Header::from_str(REAL_HEADER).unwrap();
        let credentials = Credentials {
            id: "me".to_string(),
            key: Key::new("tok"crate::SHA256).unwrap(),
        };
        let req = RequestBuilder::new("GET""pulse.taskcluster.net"443"WRONG PATH").request();
        assert!(!req.validate_header(
            &header,
            &credentials.key,
            Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
        ));
    }

    fn make_header_without_hash() -> Header {
        Header::new(
            Some("dh37fgj492je"),
            Some(UNIX_EPOCH + Duration::new(13538322340)),
            Some("j4h3g2"),
            Some(Mac::from(vec![
                161105122110248621291931482062391932194613722151,
                1701351148168145182151651451681142375235,
            ])),
            None,
            None,
            None,
            None,
        )
        .unwrap()
    }

    fn make_header_with_hash() -> Header {
        Header::new(
            Some("dh37fgj492je"),
            Some(UNIX_EPOCH + Duration::new(13538322340)),
            Some("j4h3g2"),
            Some(Mac::from(vec![
                1895315524420315025523813514418693618918421150,
                210226619315417218142250254193123132131195,
            ])),
            None,
            Some(vec![1234]),
            None,
            None,
        )
        .unwrap()
    }

    #[test]
    fn test_validate_no_hash() {
        let header = make_header_without_hash();
        let req = RequestBuilder::new(""""0"").request();
        assert!(req.validate_header(
            &header,
            &Key::new("tok"crate::SHA256).unwrap(),
            Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
        ));
    }

    #[test]
    fn test_validate_hash_in_header() {
        let header = make_header_with_hash();
        let req = RequestBuilder::new(""""0"").request();
        assert!(req.validate_header(
            &header,
            &Key::new("tok"crate::SHA256).unwrap(),
            Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
        ));
    }

    #[test]
    fn test_validate_hash_required_but_not_given() {
        let header = make_header_without_hash();
        let hash = vec![1234];
        let req = RequestBuilder::new(""""0"")
            .hash(Some(&hash[..]))
            .request();
        assert!(!req.validate_header(
            &header,
            &Key::new("tok"crate::SHA256).unwrap(),
            Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
        ));
    }

    #[test]
    fn test_validate_hash_validated() {
        let header = make_header_with_hash();
        let hash = vec![1234];
        let req = RequestBuilder::new(""""0"")
            .hash(Some(&hash[..]))
            .request();
        assert!(req.validate_header(
            &header,
            &Key::new("tok"crate::SHA256).unwrap(),
            Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
        ));

        // ..but supplying the wrong hash will cause validation to fail
        let hash = vec![99999999];
        let req = RequestBuilder::new(""""0"")
            .hash(Some(&hash[..]))
            .request();
        assert!(!req.validate_header(
            &header,
            &Key::new("tok"crate::SHA256).unwrap(),
            Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
        ));
    }

    fn round_trip_bewit(req: Request, ts: SystemTime, expected: bool) {
        let credentials = Credentials {
            id: "me".to_string(),
            key: Key::new("tok"crate::SHA256).unwrap(),
        };

        let bewit = req.make_bewit(&credentials, ts).unwrap();

        // convert to a string and back
        let bewit = bewit.to_str();
        let bewit = Bewit::from_str(&bewit).unwrap();

        // and validate it maches the original request
        assert_eq!(req.validate_bewit(&bewit, &credentials.key), expected);
    }

    #[test]
    fn test_validate_bewit() {
        let req = RequestBuilder::new("GET""foo.com"443"/x/y/z").request();
        round_trip_bewit(req, SystemTime::now() + Duration::from_secs(10 * 60), true);
    }

    #[test]
    fn test_validate_bewit_ext() {
        let req = RequestBuilder::new("GET""foo.com"443"/x/y/z")
            .ext("abcd")
            .request();
        round_trip_bewit(req, SystemTime::now() + Duration::from_secs(10 * 60), true);
    }

    #[test]
    fn test_validate_bewit_expired() {
        let req = RequestBuilder::new("GET""foo.com"443"/x/y/z").request();
        round_trip_bewit(req, SystemTime::now() - Duration::from_secs(10 * 60), false);
    }
}

Messung V0.5 in Prozent
C=90 H=93 G=91

¤ Dauer der Verarbeitung: 0.13 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.