Skip to main content

sipx_ua/
identity.rs

1//! RFC 8224 authentication and verification services.
2//!
3//! **Supported** (`S-34`): outbound and inbound call policies select these services through
4//! `sipx-call`, which constrains their public shape. sipx remains pre-1.0, so Supported does not
5//! mean frozen; breaking changes receive a migration note.
6//!
7//! Network retrieval and trust remain caller-owned. This module only orders policy decisions,
8//! supplies a bounded cache, and maps verification failures to their specified SIP statuses.
9
10use std::collections::{HashMap, VecDeque};
11
12use bytes::Bytes;
13use sipx_sip::headers::Date;
14use sipx_sip::identity::{
15    CanonicalIdentity, Es256SigningKey, Es256VerifyingKey, IdentityError, IdentityHeader,
16    date_from_timestamp, date_timestamp, passport_issued_at, request_identities, sign_passport,
17    verify_passport,
18};
19use sipx_sip::{Header, HeaderName, Request, Uri};
20use thiserror::Error;
21
22/// Maximum accepted absolute clock skew.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct Freshness {
25    seconds: u64,
26}
27
28impl Freshness {
29    /// A caller-selected freshness window.
30    #[must_use]
31    pub const fn from_seconds(seconds: u64) -> Self {
32        Self { seconds }
33    }
34
35    fn accepts(self, left: i64, right: i64) -> bool {
36        left.abs_diff(right) <= self.seconds
37    }
38}
39
40impl Default for Freshness {
41    fn default() -> Self {
42        Self::from_seconds(60)
43    }
44}
45
46/// Local policy deciding which originating identities may be asserted.
47pub trait Authority {
48    /// Whether this authentication service is authoritative for `origin`.
49    fn authorizes(&self, origin: &CanonicalIdentity) -> bool;
50}
51
52impl<F> Authority for F
53where
54    F: Fn(&CanonicalIdentity) -> bool,
55{
56    fn authorizes(&self, origin: &CanonicalIdentity) -> bool {
57        self(origin)
58    }
59}
60
61/// An application-owned signing credential.
62pub struct SigningCredential {
63    key: Es256SigningKey,
64    info: String,
65    not_before: i64,
66    not_after: i64,
67}
68
69impl std::fmt::Debug for SigningCredential {
70    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        formatter
72            .debug_struct("SigningCredential")
73            .field("info", &self.info)
74            .field("not_before", &self.not_before)
75            .field("not_after", &self.not_after)
76            .field("key", &"[REDACTED]")
77            .finish()
78    }
79}
80
81impl SigningCredential {
82    /// Build an `ES256` credential from an unencrypted `PKCS #8` PEM key.
83    pub fn from_pkcs8_pem(
84        pem: &str,
85        info: impl Into<String>,
86        not_before: i64,
87        not_after: i64,
88    ) -> Result<Self, AuthenticationError> {
89        let info = info.into();
90        validate_info(&info)?;
91        if not_before > not_after {
92            return Err(AuthenticationError::Credential);
93        }
94        Ok(Self {
95            key: Es256SigningKey::from_pkcs8_pem(pem)
96                .map_err(|_| AuthenticationError::Credential)?,
97            info,
98            not_before,
99            not_after,
100        })
101    }
102
103    /// The corresponding public verification key.
104    #[must_use]
105    pub fn verifying_key(&self) -> Es256VerifyingKey {
106        self.key.verifying_key()
107    }
108
109    fn valid_at(&self, timestamp: i64) -> bool {
110        (self.not_before..=self.not_after).contains(&timestamp)
111    }
112}
113
114fn validate_info(info: &str) -> Result<(), AuthenticationError> {
115    Uri::parse(Bytes::copy_from_slice(info.as_bytes()))
116        .map(|_| ())
117        .map_err(|_| AuthenticationError::Credential)
118}
119
120/// Authentication-service failure before an `Identity` field could be added.
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
122#[non_exhaustive]
123pub enum AuthenticationError {
124    /// From or To did not contain a canonical identity.
125    #[error("the request does not carry usable From and To identities")]
126    Identity,
127    /// Local policy does not authorize this service to assert the From identity.
128    #[error("the authentication service is not authoritative for the origin")]
129    NotAuthoritative,
130    /// Date is malformed or outside the freshness policy.
131    #[error("the request carries a stale or invalid Date")]
132    StaleDate,
133    /// The signing credential is malformed or outside its validity interval.
134    #[error("the signing credential is not valid for this request")]
135    Credential,
136    /// The finished header could not be built.
137    #[error("the Identity header could not be built")]
138    Build,
139}
140
141/// RFC 8224 §6.1 authentication service.
142#[derive(Debug)]
143pub struct AuthenticationService<A> {
144    authority: A,
145    credential: SigningCredential,
146    freshness: Freshness,
147}
148
149impl<A: Authority> AuthenticationService<A> {
150    /// Use RFC 8224's recommended 60-second freshness policy.
151    #[must_use]
152    pub fn new(authority: A, credential: SigningCredential) -> Self {
153        Self {
154            authority,
155            credential,
156            freshness: Freshness::default(),
157        }
158    }
159
160    /// Replace the freshness policy.
161    #[must_use]
162    pub const fn with_freshness(mut self, freshness: Freshness) -> Self {
163        self.freshness = freshness;
164        self
165    }
166
167    /// Add a full baseline `PASSporT` `Identity` header.
168    ///
169    /// `now` is Unix time supplied by the caller; the service reads no clock.
170    pub fn sign(&self, request: &mut Request, now: i64) -> Result<(), AuthenticationError> {
171        let (origin, destination) =
172            request_identities(request).map_err(|_| AuthenticationError::Identity)?;
173        if !self.authority.authorizes(&origin) {
174            return Err(AuthenticationError::NotAuthoritative);
175        }
176
177        let date_count = request.headers.get_all(&HeaderName::Date).count();
178        if date_count > 1 {
179            return Err(AuthenticationError::StaleDate);
180        }
181        let existing_date = request.headers.typed::<Date>();
182        let (date, issued_at, add_date) = match existing_date {
183            None => (
184                date_from_timestamp(now).map_err(|_| AuthenticationError::StaleDate)?,
185                now,
186                true,
187            ),
188            Some(Ok(date)) => {
189                let timestamp =
190                    date_timestamp(&date).map_err(|_| AuthenticationError::StaleDate)?;
191                if !self.freshness.accepts(now, timestamp) {
192                    return Err(AuthenticationError::StaleDate);
193                }
194                (date, timestamp, false)
195            }
196            Some(Err(_)) => return Err(AuthenticationError::StaleDate),
197        };
198        if !self.credential.valid_at(now) || !self.credential.valid_at(issued_at) {
199            return Err(AuthenticationError::Credential);
200        }
201
202        let identity = sign_passport(
203            &self.credential.key,
204            &origin,
205            &destination,
206            issued_at,
207            &self.credential.info,
208        );
209        let header = Header::build(HeaderName::Identity, identity.to_bytes())
210            .map_err(|_| AuthenticationError::Build)?;
211        if add_date {
212            request.headers.push(
213                Header::build(HeaderName::Date, Bytes::from(date.0))
214                    .map_err(|_| AuthenticationError::Build)?,
215            );
216        }
217        request.headers.push(header);
218        Ok(())
219    }
220}
221
222/// A verification credential already interpreted and trusted by caller policy.
223#[derive(Debug, Clone)]
224pub struct VerificationCredential {
225    key: Es256VerifyingKey,
226    not_before: i64,
227    not_after: i64,
228}
229
230impl VerificationCredential {
231    /// Build a credential from a `SubjectPublicKeyInfo` PEM public key.
232    pub fn from_public_key_pem(
233        pem: &str,
234        not_before: i64,
235        not_after: i64,
236    ) -> Result<Self, IdentityError> {
237        Self::new(
238            Es256VerifyingKey::from_public_key_pem(pem)?,
239            not_before,
240            not_after,
241        )
242    }
243
244    /// Build a credential from an already parsed `ES256` public key.
245    pub fn new(
246        key: Es256VerifyingKey,
247        not_before: i64,
248        not_after: i64,
249    ) -> Result<Self, IdentityError> {
250        if not_before > not_after {
251            return Err(IdentityError::Credential);
252        }
253        Ok(Self {
254            key,
255            not_before,
256            not_after,
257        })
258    }
259
260    fn valid_at(&self, timestamp: i64) -> bool {
261        (self.not_before..=self.not_after).contains(&timestamp)
262    }
263}
264
265/// Caller-owned credential acquisition outcome.
266#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
267#[non_exhaustive]
268pub enum CredentialError {
269    /// The `info` resource could not be acquired.
270    #[error("credential unavailable")]
271    Unavailable,
272    /// A resource was acquired but its scheme, trust, or key is unsupported.
273    #[error("credential unsupported")]
274    Unsupported,
275}
276
277/// Caller-supplied `info` URI acquisition and authority policy.
278pub trait CredentialFetcher {
279    /// Acquire and validate the credential named by the exact `info` URI at caller-supplied time.
280    ///
281    /// `at` lets bounded caches discard an expired key without reading a clock themselves.
282    fn fetch(&mut self, info: &str, at: i64) -> Result<VerificationCredential, CredentialError>;
283
284    /// Whether this credential is trusted and authoritative for `origin`.
285    fn authorizes(&self, credential: &VerificationCredential, origin: &CanonicalIdentity) -> bool;
286}
287
288/// A bounded successful-credential cache in front of a caller's fetcher.
289#[derive(Debug)]
290pub struct CachedCredentials<F> {
291    fetcher: F,
292    capacity: usize,
293    entries: HashMap<String, VerificationCredential>,
294    order: VecDeque<String>,
295}
296
297impl<F> CachedCredentials<F> {
298    /// Cache up to `capacity` exact `info` URIs. Zero disables retention.
299    #[must_use]
300    pub fn new(fetcher: F, capacity: usize) -> Self {
301        Self {
302            fetcher,
303            capacity,
304            entries: HashMap::new(),
305            order: VecDeque::new(),
306        }
307    }
308
309    /// Recover the caller's fetcher.
310    pub fn into_inner(self) -> F {
311        self.fetcher
312    }
313}
314
315impl<F: CredentialFetcher> CredentialFetcher for CachedCredentials<F> {
316    fn fetch(&mut self, info: &str, at: i64) -> Result<VerificationCredential, CredentialError> {
317        if let Some(credential) = self.entries.get(info) {
318            if credential.valid_at(at) {
319                return Ok(credential.clone());
320            }
321            self.entries.remove(info);
322            self.order.retain(|cached| cached != info);
323        }
324        let credential = self.fetcher.fetch(info, at)?;
325        if self.capacity > 0 {
326            while self.entries.len() >= self.capacity {
327                let Some(oldest) = self.order.pop_front() else {
328                    break;
329                };
330                self.entries.remove(&oldest);
331            }
332            self.entries.insert(info.to_owned(), credential.clone());
333            self.order.push_back(info.to_owned());
334        }
335        Ok(credential)
336    }
337
338    fn authorizes(&self, credential: &VerificationCredential, origin: &CanonicalIdentity) -> bool {
339        self.fetcher.authorizes(credential, origin)
340    }
341}
342
343/// Successful verification state.
344#[derive(Debug, Clone, PartialEq, Eq)]
345pub enum Verification {
346    /// One trusted header verified.
347    Verified {
348        /// SIP-derived originating identity.
349        origin: CanonicalIdentity,
350        /// Exact credential-reference URI used.
351        info: String,
352    },
353    /// Local policy did not require identity and no usable header was present.
354    Unverified,
355}
356
357/// Verification-service failure with its RFC 8224 §6.2.2 status.
358#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
359#[non_exhaustive]
360pub enum VerificationFailure {
361    /// No usable baseline `Identity` was present when policy required one.
362    #[error("428 Use Identity Header")]
363    MissingIdentity,
364    /// No credential could be acquired.
365    #[error("436 Bad Identity Info")]
366    BadIdentityInfo,
367    /// An acquired credential is unsupported or untrusted.
368    #[error("437 Unsupported Credential")]
369    UnsupportedCredential,
370    /// Date or `iat` is outside freshness policy.
371    #[error("403 Stale Date")]
372    StaleDate,
373    /// No supported `PASSporT` had valid claims and signature.
374    #[error("438 Invalid Identity Header")]
375    InvalidIdentity,
376}
377
378impl VerificationFailure {
379    /// SIP response status assigned by RFC 8224 §6.2.2.
380    #[must_use]
381    pub const fn status(self) -> u16 {
382        match self {
383            Self::MissingIdentity => 428,
384            Self::BadIdentityInfo => 436,
385            Self::UnsupportedCredential => 437,
386            Self::StaleDate => 403,
387            Self::InvalidIdentity => 438,
388        }
389    }
390
391    /// SIP reason phrase paired with [`Self::status`].
392    #[must_use]
393    pub const fn reason(self) -> &'static str {
394        match self {
395            Self::MissingIdentity => "Use Identity Header",
396            Self::BadIdentityInfo => "Bad Identity Info",
397            Self::UnsupportedCredential => "Unsupported Credential",
398            Self::StaleDate => "Stale Date",
399            Self::InvalidIdentity => "Invalid Identity Header",
400        }
401    }
402}
403
404/// RFC 8224 §6.2 verification service.
405#[derive(Debug)]
406pub struct VerificationService<S> {
407    source: S,
408    freshness: Freshness,
409}
410
411impl<F: CredentialFetcher> VerificationService<CachedCredentials<F>> {
412    /// Use a bounded 64-entry cache and the default 60-second freshness policy.
413    #[must_use]
414    pub fn new(fetcher: F) -> Self {
415        Self {
416            source: CachedCredentials::new(fetcher, 64),
417            freshness: Freshness::default(),
418        }
419    }
420}
421
422impl<S: CredentialFetcher> VerificationService<S> {
423    /// Use a caller-constructed credential source or cache.
424    #[must_use]
425    pub fn with_source(source: S) -> Self {
426        Self {
427            source,
428            freshness: Freshness::default(),
429        }
430    }
431
432    /// Replace the freshness policy.
433    #[must_use]
434    pub const fn with_freshness(mut self, freshness: Freshness) -> Self {
435        self.freshness = freshness;
436        self
437    }
438
439    /// Recover the credential source, including cache and caller fetcher state.
440    pub fn into_source(self) -> S {
441        self.source
442    }
443
444    /// Verify `Identity` rows in wire order using caller-supplied Unix time.
445    pub fn verify(
446        &mut self,
447        request: &Request,
448        now: i64,
449        required: bool,
450    ) -> Result<Verification, VerificationFailure> {
451        let rows: Vec<_> = request.headers.typed_all::<IdentityHeader>().collect();
452        if rows.is_empty() {
453            return if required {
454                Err(VerificationFailure::MissingIdentity)
455            } else {
456                Ok(Verification::Unverified)
457            };
458        }
459
460        let mut best = None;
461        let mut usable = false;
462        for row in rows {
463            let Ok(header) = row else {
464                promote(&mut best, VerificationFailure::InvalidIdentity);
465                continue;
466            };
467            // Step 1: an unsupported mandatory extension makes this row unusable, with no fetch.
468            if header.passport_type.is_some() {
469                continue;
470            }
471            usable = true;
472            if header.algorithm != "ES256" {
473                promote(&mut best, VerificationFailure::InvalidIdentity);
474                continue;
475            }
476
477            // Step 2: derive identities from SIP, never from untrusted token claims.
478            let Ok((origin, destination)) = request_identities(request) else {
479                promote(&mut best, VerificationFailure::InvalidIdentity);
480                continue;
481            };
482
483            // Step 3: all dereferencing is delegated to the caller/cache.
484            let credential = match self.source.fetch(&header.info, now) {
485                Ok(credential) => credential,
486                Err(CredentialError::Unavailable) => {
487                    promote(&mut best, VerificationFailure::BadIdentityInfo);
488                    continue;
489                }
490                Err(CredentialError::Unsupported) => {
491                    promote(&mut best, VerificationFailure::UnsupportedCredential);
492                    continue;
493                }
494            };
495            if !self.source.authorizes(&credential, &origin) {
496                promote(&mut best, VerificationFailure::UnsupportedCredential);
497                continue;
498            }
499
500            // Step 4: Date freshness and credential validity.
501            if request.headers.get_all(&HeaderName::Date).count() != 1 {
502                promote(&mut best, VerificationFailure::InvalidIdentity);
503                continue;
504            }
505            let Some(Ok(date)) = request.headers.typed::<Date>() else {
506                promote(&mut best, VerificationFailure::InvalidIdentity);
507                continue;
508            };
509            let Ok(date) = date_timestamp(&date) else {
510                promote(&mut best, VerificationFailure::InvalidIdentity);
511                continue;
512            };
513            let issued_at = match passport_issued_at(&header) {
514                Ok(Some(issued_at)) => issued_at,
515                Ok(None) => date,
516                Err(_) => {
517                    promote(&mut best, VerificationFailure::InvalidIdentity);
518                    continue;
519                }
520            };
521            if !self.freshness.accepts(now, date) || !self.freshness.accepts(now, issued_at) {
522                promote(&mut best, VerificationFailure::StaleDate);
523                continue;
524            }
525            if !credential.valid_at(date) || !credential.valid_at(now) {
526                promote(&mut best, VerificationFailure::UnsupportedCredential);
527                continue;
528            }
529
530            // Step 5: exact deterministic claims followed by ES256.
531            // A full PASSporT's `iat` must describe the SIP Date, not merely be independently
532            // fresh. Passing the Date here makes the exact-claims check reject a signed token
533            // whose otherwise-fresh `iat` disagrees with the request it is authenticating.
534            if verify_passport(&header, &credential.key, &origin, &destination, date).is_ok() {
535                return Ok(Verification::Verified {
536                    origin,
537                    info: header.info,
538                });
539            }
540            promote(&mut best, VerificationFailure::InvalidIdentity);
541        }
542
543        if usable {
544            Err(best.unwrap_or(VerificationFailure::InvalidIdentity))
545        } else if required {
546            // An unsupported mandatory `ppt` behaves like no usable Identity (428), but a row
547            // that was present and malformed is an invalid Identity (438).
548            Err(best.unwrap_or(VerificationFailure::MissingIdentity))
549        } else {
550            Ok(Verification::Unverified)
551        }
552    }
553}
554
555fn promote(best: &mut Option<VerificationFailure>, candidate: VerificationFailure) {
556    let rank = |failure: VerificationFailure| match failure {
557        VerificationFailure::MissingIdentity => 0,
558        VerificationFailure::BadIdentityInfo => 1,
559        VerificationFailure::UnsupportedCredential => 2,
560        VerificationFailure::StaleDate => 3,
561        VerificationFailure::InvalidIdentity => 4,
562    };
563    if best.is_none_or(|current| rank(candidate) > rank(current)) {
564        *best = Some(candidate);
565    }
566}
567
568#[cfg(test)]
569#[allow(
570    clippy::unwrap_used,
571    clippy::expect_used,
572    clippy::panic,
573    clippy::indexing_slicing
574)]
575mod tests {
576    use super::*;
577    use sipx_sip::Method;
578    use sipx_sip::build::RequestBuilder;
579
580    const PRIVATE: &str = "-----BEGIN PRIVATE KEY-----\n\
581MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgi7q2TZvN9VDFg8Vy\n\
582qCP06bETrR2v8MRvr89rn4i+UAahRANCAAQWfaj1HUETpoNCrOtp9KA8o0V79IuW\n\
583ARKt9C1cFPkyd3FBP4SeiNZxQhDrD0tdBHls3/wFe8++K2FrPyQF9vuh\n\
584-----END PRIVATE KEY-----";
585
586    fn request() -> Request {
587        RequestBuilder::new(
588            Method::Invite,
589            Uri::parse(Bytes::from_static(b"sip:alice@example.com")).unwrap(),
590        )
591        .header(
592            HeaderName::From,
593            Bytes::from_static(b"<sip:+12155551212@example.com;user=phone>;tag=x"),
594        )
595        .unwrap()
596        .header(
597            HeaderName::To,
598            Bytes::from_static(b"<sip:alice@example.com>"),
599        )
600        .unwrap()
601        .build()
602    }
603
604    fn credential() -> SigningCredential {
605        SigningCredential::from_pkcs8_pem(
606            PRIVATE,
607            "https://cert.example.org/passport.cer",
608            i64::MIN,
609            i64::MAX,
610        )
611        .unwrap()
612    }
613
614    #[test]
615    fn signing_adds_date_and_identity_only_after_authority() {
616        let mut denied = request();
617        let service = AuthenticationService::new(|_: &CanonicalIdentity| false, credential());
618        assert_eq!(
619            service.sign(&mut denied, 1_471_375_418),
620            Err(AuthenticationError::NotAuthoritative)
621        );
622        assert!(denied.headers.get(&HeaderName::Date).is_none());
623        assert!(denied.headers.get(&HeaderName::Identity).is_none());
624
625        let mut allowed = request();
626        let service = AuthenticationService::new(|_: &CanonicalIdentity| true, credential());
627        service.sign(&mut allowed, 1_471_375_418).unwrap();
628        assert!(
629            allowed
630                .headers
631                .typed::<Date>()
632                .is_some_and(|date| date.is_ok())
633        );
634        assert!(
635            allowed
636                .headers
637                .typed::<IdentityHeader>()
638                .is_some_and(|identity| identity.is_ok())
639        );
640    }
641
642    #[derive(Debug)]
643    struct CountingFetcher {
644        calls: usize,
645        key: Es256VerifyingKey,
646        result: Option<CredentialError>,
647    }
648
649    impl CredentialFetcher for CountingFetcher {
650        fn fetch(
651            &mut self,
652            _info: &str,
653            _at: i64,
654        ) -> Result<VerificationCredential, CredentialError> {
655            self.calls += 1;
656            if let Some(error) = self.result {
657                return Err(error);
658            }
659            VerificationCredential::new(self.key.clone(), i64::MIN, i64::MAX)
660                .map_err(|_| CredentialError::Unsupported)
661        }
662
663        fn authorizes(
664            &self,
665            _credential: &VerificationCredential,
666            _origin: &CanonicalIdentity,
667        ) -> bool {
668            true
669        }
670    }
671
672    fn signed() -> (Request, Es256VerifyingKey) {
673        let credential = credential();
674        let key = credential.verifying_key();
675        let service = AuthenticationService::new(|_: &CanonicalIdentity| true, credential);
676        let mut request = request();
677        service.sign(&mut request, 1_471_375_418).unwrap();
678        (request, key)
679    }
680
681    #[test]
682    fn unsupported_ppt_is_428_and_does_not_fetch() {
683        let (mut request, key) = signed();
684        let parsed = request.headers.typed::<IdentityHeader>().unwrap().unwrap();
685        request.headers.remove_all(&HeaderName::Identity);
686        let mut unsupported = parsed;
687        unsupported.passport_type = Some("unknown".to_owned());
688        request
689            .headers
690            .push(Header::build(HeaderName::Identity, unsupported.to_bytes()).unwrap());
691        let fetcher = CountingFetcher {
692            calls: 0,
693            key,
694            result: None,
695        };
696        let mut verifier = VerificationService::new(fetcher);
697        assert_eq!(
698            verifier.verify(&request, 1_471_375_418, true),
699            Err(VerificationFailure::MissingIdentity)
700        );
701        assert_eq!(verifier.into_source().into_inner().calls, 0);
702    }
703
704    #[test]
705    fn acquisition_failures_keep_their_distinct_statuses() {
706        let (request, key) = signed();
707        for (error, status) in [
708            (CredentialError::Unavailable, 436),
709            (CredentialError::Unsupported, 437),
710        ] {
711            let fetcher = CountingFetcher {
712                calls: 0,
713                key: key.clone(),
714                result: Some(error),
715            };
716            let mut verifier = VerificationService::new(fetcher);
717            assert_eq!(
718                verifier
719                    .verify(&request, 1_471_375_418, true)
720                    .unwrap_err()
721                    .status(),
722                status
723            );
724        }
725    }
726
727    #[test]
728    fn stale_date_is_403_after_credential_acquisition() {
729        let (request, key) = signed();
730        let fetcher = CountingFetcher {
731            calls: 0,
732            key,
733            result: None,
734        };
735        let mut verifier = VerificationService::new(fetcher);
736        let failure = verifier.verify(&request, 1_471_375_479, true).unwrap_err();
737        assert_eq!(failure.status(), 403);
738        assert_eq!(verifier.into_source().into_inner().calls, 1);
739    }
740
741    #[test]
742    fn successful_credentials_are_cached_by_exact_info_uri() {
743        let (request, key) = signed();
744        let fetcher = CountingFetcher {
745            calls: 0,
746            key,
747            result: None,
748        };
749        let mut verifier = VerificationService::new(fetcher);
750        verifier.verify(&request, 1_471_375_418, true).unwrap();
751        verifier.verify(&request, 1_471_375_418, true).unwrap();
752        assert_eq!(verifier.into_source().into_inner().calls, 1);
753    }
754
755    #[test]
756    fn cached_credentials_are_bounded_and_evict_by_exact_info_uri() {
757        let (_, key) = signed();
758        let fetcher = CountingFetcher {
759            calls: 0,
760            key,
761            result: None,
762        };
763        let mut cache = CachedCredentials::new(fetcher, 1);
764        cache.fetch("https://cert.example.org/one", 1).unwrap();
765        cache.fetch("https://cert.example.org/one", 1).unwrap();
766        cache.fetch("https://cert.example.org/two", 1).unwrap();
767        cache.fetch("https://cert.example.org/one", 1).unwrap();
768        assert_eq!(cache.entries.len(), 1);
769        assert_eq!(cache.into_inner().calls, 3);
770    }
771
772    #[test]
773    fn an_expired_cached_credential_is_refetched_at_the_same_uri() {
774        #[derive(Debug)]
775        struct RotatingFetcher {
776            calls: usize,
777            key: Es256VerifyingKey,
778        }
779
780        impl CredentialFetcher for RotatingFetcher {
781            fn fetch(
782                &mut self,
783                _info: &str,
784                _at: i64,
785            ) -> Result<VerificationCredential, CredentialError> {
786                self.calls += 1;
787                let not_after = if self.calls == 1 { 10 } else { i64::MAX };
788                VerificationCredential::new(self.key.clone(), i64::MIN, not_after)
789                    .map_err(|_| CredentialError::Unsupported)
790            }
791
792            fn authorizes(
793                &self,
794                _credential: &VerificationCredential,
795                _origin: &CanonicalIdentity,
796            ) -> bool {
797                true
798            }
799        }
800
801        let (_, key) = signed();
802        let fetcher = RotatingFetcher { calls: 0, key };
803        let mut cache = CachedCredentials::new(fetcher, 1);
804        cache.fetch("https://cert.example.org/one", 10).unwrap();
805        cache.fetch("https://cert.example.org/one", 11).unwrap();
806        assert_eq!(cache.entries.len(), 1);
807        assert_eq!(cache.into_inner().calls, 2);
808    }
809
810    #[test]
811    fn malformed_identity_is_438_when_identity_is_required() {
812        let (_, key) = signed();
813        let mut request = request();
814        request.headers.push(
815            Header::build(HeaderName::Identity, Bytes::from_static(b"not-a-passport")).unwrap(),
816        );
817        let fetcher = CountingFetcher {
818            calls: 0,
819            key,
820            result: None,
821        };
822        let mut verifier = VerificationService::new(fetcher);
823        assert_eq!(
824            verifier.verify(&request, 1_471_375_418, true),
825            Err(VerificationFailure::InvalidIdentity)
826        );
827        assert_eq!(verifier.into_source().into_inner().calls, 0);
828    }
829
830    #[test]
831    fn full_passport_iat_must_equal_the_sip_date() {
832        let (mut request, key) = signed();
833        request.headers.remove_all(&HeaderName::Date);
834        let different_but_fresh = date_from_timestamp(1_471_375_419).unwrap();
835        request
836            .headers
837            .push(Header::build(HeaderName::Date, Bytes::from(different_but_fresh.0)).unwrap());
838        let fetcher = CountingFetcher {
839            calls: 0,
840            key,
841            result: None,
842        };
843        let mut verifier = VerificationService::new(fetcher);
844        assert_eq!(
845            verifier.verify(&request, 1_471_375_419, true),
846            Err(VerificationFailure::InvalidIdentity)
847        );
848    }
849}