auths_sdk/domains/credentials/
authenticate.rs1use auths_core::storage::keychain::KeyAlias;
11use auths_crypto::RingCryptoProvider;
12use auths_rp::{
13 Audience, ChallengeError, ChallengeStore, Denied, NONCE_LEN, Nonce, VerifiedPrincipal,
14 WireError, WirePresentation,
15};
16use auths_verifier::{
17 PresentationBinding, PresentationVerdict, VerifierWitnessPolicy, verify_presentation,
18};
19use chrono::{DateTime, Utc};
20
21use auths_id::keri::types::Prefix;
22use auths_id::policy::context_from_credential;
23
24use crate::context::AuthsContext;
25use crate::domains::credentials::error::CredentialError;
26use crate::domains::credentials::present_inputs::load_presentation_inputs;
27use crate::domains::org::error::OrgError;
28use crate::domains::org::policy::{evaluate_with_org_policy, load_org_policy};
29
30#[derive(Debug, thiserror::Error)]
32pub enum PresentationAuthError {
33 #[error("wire: {0}")]
35 Wire(WireError),
36 #[error("nonce must be 32 bytes")]
38 NonceLength,
39 #[error("challenge: {0}")]
41 Challenge(ChallengeError),
42 #[error("resolve: {0}")]
44 Resolve(CredentialError),
45 #[error("denied: {0}")]
47 Denied(Denied),
48 #[error("org policy denied: {reason}")]
51 PolicyDenied {
52 reason: String,
54 },
55 #[error("org policy could not be evaluated: {0}")]
58 Policy(OrgError),
59}
60
61impl PresentationAuthError {
62 pub fn http_status(&self) -> u16 {
65 match self {
66 PresentationAuthError::Wire(_) | PresentationAuthError::NonceLength => 400,
67 PresentationAuthError::Denied(denied) => denied.http_status(),
68 PresentationAuthError::PolicyDenied { .. } | PresentationAuthError::Policy(_) => 403,
69 PresentationAuthError::Challenge(_) | PresentationAuthError::Resolve(_) => 401,
70 }
71 }
72}
73
74pub async fn authenticate_presentation(
97 ctx: &AuthsContext,
98 issuer_alias: &KeyAlias,
99 challenges: &dyn ChallengeStore,
100 config_audience: &Audience,
101 wire: WirePresentation,
102 now: DateTime<Utc>,
103) -> Result<VerifiedPrincipal, PresentationAuthError> {
104 let (envelope, presented_audience) = wire.parse().map_err(PresentationAuthError::Wire)?;
105
106 let nonce = binding_nonce(&envelope.binding)?;
107 let expected = challenges
108 .consume(&presented_audience, &nonce, now)
109 .map_err(PresentationAuthError::Challenge)?;
110
111 let inputs = load_presentation_inputs(ctx, issuer_alias, &envelope.credential_said)
112 .map_err(PresentationAuthError::Resolve)?;
113
114 let verdict = verify_presentation(
115 &envelope,
116 &inputs.signed,
117 &inputs.issuer_kel,
118 &inputs.tel,
119 &inputs.receipts,
120 VerifierWitnessPolicy::Warn,
121 &inputs.subject_kel,
122 &inputs.subject_delegator_kel,
123 config_audience.as_str(),
124 Some(expected.as_bytes()),
125 now,
126 &RingCryptoProvider,
127 )
128 .await;
129
130 enforce_issuer_policy(ctx, &verdict, now)?;
131
132 VerifiedPrincipal::from_verdict(verdict).map_err(PresentationAuthError::Denied)
133}
134
135fn enforce_issuer_policy(
144 ctx: &AuthsContext,
145 verdict: &PresentationVerdict,
146 now: DateTime<Utc>,
147) -> Result<(), PresentationAuthError> {
148 let PresentationVerdict::Valid {
149 issuer, subject, ..
150 } = verdict
151 else {
152 return Ok(()); };
154 let issuer_prefix = Prefix::new_unchecked(
155 issuer
156 .as_str()
157 .strip_prefix("did:keri:")
158 .unwrap_or(issuer.as_str())
159 .to_string(),
160 );
161
162 let Some(policy) =
163 load_org_policy(ctx, &issuer_prefix).map_err(PresentationAuthError::Policy)?
164 else {
165 return Ok(()); };
167
168 let eval_ctx = context_from_credential(verdict, now)
169 .map_err(|e| PresentationAuthError::Policy(OrgError::InvalidDid(e.to_string())))?;
170 let decision = evaluate_with_org_policy(&policy, &eval_ctx);
171 crate::audit::emit_policy_decision(ctx, "request", subject.as_str(), &decision);
173 if decision.is_allowed() {
174 Ok(())
175 } else {
176 Err(PresentationAuthError::PolicyDenied {
177 reason: format!("{} [{}]", decision.message, decision.reason),
178 })
179 }
180}
181
182fn binding_nonce(binding: &PresentationBinding) -> Result<Nonce, PresentationAuthError> {
184 let bytes = match binding {
185 PresentationBinding::Challenge { nonce } => nonce.as_slice(),
186 PresentationBinding::Ttl { nonce, .. } => nonce.as_slice(),
187 };
188 let arr: [u8; NONCE_LEN] = bytes
189 .try_into()
190 .map_err(|_| PresentationAuthError::NonceLength)?;
191 Ok(Nonce::from_bytes(arr))
192}