credibil_vc/openid/verifier.rs
1//! # `OpenID` for Verifiable Presentations (`OpenID4VP`)
2
3use std::collections::HashMap;
4use std::fmt;
5use std::fmt::Debug;
6use std::future::Future;
7use std::io::Cursor;
8
9use anyhow::anyhow;
10use base64ct::{Base64, Encoding};
11use credibil_did::DidResolver;
12pub use credibil_infosec::Signer;
13use qrcode::QrCode;
14use serde::de::{self, Deserializer, Visitor};
15use serde::ser::{SerializeMap, Serializer};
16use serde::{Deserialize, Serialize};
17
18use super::oauth::{OAuthClient, OAuthServer};
19use super::provider::{Result, StateStore};
20use crate::core::{urlencode, Kind};
21use crate::dif_exch::{InputDescriptor, PresentationDefinition, PresentationSubmission};
22use crate::w3c_vc::model::VerifiablePresentation;
23
24/// Verifier Provider trait.
25pub trait Provider: Metadata + StateStore + Signer + DidResolver + Clone {}
26
27/// The `Metadata` trait is used by implementers to provide `Verifier` (client)
28/// metadata to the library.
29pub trait Metadata: Send + Sync {
30 /// Verifier (Client) metadata for the specified verifier.
31 fn verifier(&self, verifier_id: &str) -> impl Future<Output = Result<Verifier>> + Send;
32
33 /// Wallet (Authorization Server) metadata.
34 fn wallet(&self, wallet_id: &str) -> impl Future<Output = Result<Wallet>> + Send;
35
36 /// Used by OAuth 2.0 clients to dynamically register with the authorization
37 /// server.
38 fn register(&self, verifier: &Verifier) -> impl Future<Output = Result<Verifier>> + Send;
39}
40
41/// The Request Object Request is created by the Verifier to generate an
42/// Authorization Request Object.
43#[derive(Clone, Debug, Default, Deserialize, Serialize)]
44#[serde(default)]
45pub struct CreateRequestRequest {
46 #[allow(rustdoc::bare_urls)]
47 /// The Verifier ID. It MUST be a valid URI. For example,
48 /// `"https://credibil.io"` or `"did:ion:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg"`.
49 pub client_id: String,
50
51 /// The reason the Verifier is requesting the Verifiable Presentation.
52 pub purpose: String,
53
54 /// Input Descriptors describing the information required from the
55 /// Holder.
56 pub input_descriptors: Vec<InputDescriptor>,
57
58 /// The Verifier can specify whether Authorization Requests and Responses
59 /// are to be passed between endpoints on the same device or across devices
60 pub device_flow: DeviceFlow,
61
62 /// The Client ID
63 pub client_id_scheme: String,
64}
65
66/// Used to specify whether Authorization Requests and Responses are to be
67/// passed between endpoints on the same device or across devices
68#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
69pub enum DeviceFlow {
70 /// With the cross-device flow the Verifier renders the Authorization
71 /// Request as a QR Code which the User scans with the Wallet. In
72 /// response, the Verifiable Presentations are sent to a URL controlled
73 /// by the Verifier using HTTPS POST.
74 ///
75 /// To initiate this flow, the Verifier specifies a Response Type of
76 /// "`vp_token`" and a Response Mode of "`direct_post`" in the Request
77 /// Object.
78 ///
79 /// In order to keep the size of the QR Code small and be able to sign and
80 /// optionally encrypt the Request Object, the Authorization Request only
81 /// contains a Request URI which the wallet uses to retrieve the actual
82 /// Authorization Request data.
83 ///
84 /// It is RECOMMENDED that Response Mode "`direct_post`" and `request_uri`
85 /// are used for cross-device flows, as Authorization Request size might
86 /// be large and may not fit in a QR code.
87 #[default]
88 CrossDevice,
89
90 /// The same-device flow uses HTTP redirects to pass Authorization Request
91 /// and Response between Verifier and Wallet. Verifiable Presentations
92 /// are returned to the Verifier in the fragment part of the redirect
93 /// URI, when the Response Mode is "`fragment`".
94 SameDevice,
95}
96
97/// The response to the originator of the Request Object Request.
98// TODO: Should this be an enum?
99#[derive(Clone, Debug, Default, Deserialize, Serialize)]
100pub struct CreateRequestResponse {
101 /// The generated Authorization Request Object, ready to send to the Wallet.
102 #[serde(skip_serializing_if = "Option::is_none")]
103 pub request_object: Option<RequestObject>,
104
105 /// A URI pointing to a location where the Authorization Request Object can
106 /// be retrieved by the Wallet.
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub request_uri: Option<String>,
109}
110
111impl CreateRequestResponse {
112 /// Convenience method to convert the `CreateRequestResponse` to a QR code.
113 ///
114 /// If the `request_object` is set, the method will generate a QR code for
115 /// that in favour of the `request_uri`.
116 ///
117 /// TODO: Revisit the logic to determine default type if this struct is made
118 /// an enum.
119 ///
120 /// # Errors
121 /// Returns an error if the neither the `request_object` nor `request_uri` is
122 /// set or the respective field cannot be represented as a base64-encoded PNG
123 /// image of a QR code.
124 pub fn to_qrcode(&self, endpoint: Option<&str>) -> anyhow::Result<String> {
125 if let Some(req_obj) = &self.request_object {
126 let Some(endpoint) = endpoint else {
127 return Err(anyhow!("no endpoint provided for object-type response"));
128 };
129 req_obj.to_qrcode(endpoint)
130 } else {
131 let Some(request_uri) = &self.request_uri else {
132 return Err(anyhow!("response has no request object or request uri"));
133 };
134 // generate qr code
135 let qr_code =
136 QrCode::new(request_uri).map_err(|e| anyhow!("Failed to create QR code: {e}"))?;
137
138 // write image to buffer
139 let img_buf = qr_code.render::<image::Luma<u8>>().build();
140 let mut buffer: Vec<u8> = Vec::new();
141 let mut writer = Cursor::new(&mut buffer);
142 img_buf
143 .write_to(&mut writer, image::ImageFormat::Png)
144 .map_err(|e| anyhow!("Failed to create QR code: {e}"))?;
145
146 // base64 encode image
147 Ok(format!("data:image/png;base64,{}", Base64::encode_string(buffer.as_slice())))
148 }
149 }
150}
151
152/// The Authorization Request follows the definition given in [RFC6749].
153///
154/// The Verifier may send an Authorization Request as Request Object by value or
155/// by reference as defined in JWT-Secured Authorization Request (JAR)
156/// [RFC9101].
157///
158/// [RFC6749]: (https://www.rfc-editor.org/rfc/rfc6749.html)
159/// [RFC9101]:https://www.rfc-editor.org/rfc/rfc9101
160#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
161pub struct RequestObject {
162 /// The type of response expected from the Wallet (as Authorization Server).
163 ///
164 /// If Response Type is:
165 /// - "`vp_token`": a VP Token is returned in an Authorization Response.
166 /// - "`vp_token id_token`" AND the `scope` parameter contains "`openid`":
167 /// a VP Token and a Self-Issued ID Token are returned in an
168 /// Authorization Response.
169 /// - "`code`": a VP Token is returned in a Token Response.
170 ///
171 /// The default Response Mode is "fragment": response parameters are encoded
172 /// in the fragment added to the `redirect_uri` when redirecting back to the
173 /// Verifier.
174 pub response_type: ResponseType,
175
176 /// The Verifier ID. MUST be a valid URI.
177 pub client_id: String,
178
179 /// The URI to redirect to the Verifier's redirection endpoint as
180 /// established during client registration.
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub redirect_uri: Option<String>,
183
184 /// While the `response_type` parameter informs the Authorization Server
185 /// (Wallet) of the desired authorization flow, the `response_mode`
186 /// parameter informs it of the mechanism to use when returning an
187 /// Authorization Response.
188 ///
189 /// A Response Mode of "`direct_post`" allows the Wallet to send the
190 /// Authorization Response to an endpoint controlled by the Verifier as
191 /// an HTTPS POST request.
192 ///
193 /// If not set, the default value is "`fragment`".
194 ///
195 /// Response parameters are returned using the
196 /// "application/x-www-form-urlencoded" content type. The flow can end
197 /// with an HTTPS POST request from the Wallet to the Verifier, or it
198 /// can end with a redirect that follows the HTTPS POST request,
199 /// if the Verifier responds with a redirect URI to the Wallet.
200 ///
201 /// Response Mode "`direct_post.jwt`" causes the Wallet to send the
202 /// Authorization Response as an HTTPS POST request (as for
203 /// "`direct_post`") except the Wallet sets a `response` parameter to a
204 /// JWT containing the Authorization Response. See [JARM] for more
205 /// detail.
206 ///
207 /// [JARM]: (https://openid.net/specs/oauth-v2-jarm-final.html)
208 #[serde(skip_serializing_if = "Option::is_none")]
209 pub response_mode: Option<String>,
210
211 /// OPTIONAL. MUST be set when the Response Mode "`direct_post`" is used.
212 ///
213 /// The URI to which the Wallet MUST send the Authorization Response using
214 /// an HTTPS POST request as defined by the Response Mode
215 /// "`direct_post`".
216 ///
217 /// When `response_uri` is set, `redirect_uri` MUST NOT be set. If set when
218 /// Response Mode is "`direct_post`", the Wallet MUST return an
219 /// "`invalid_request`" error.
220 ///
221 /// Note: If the Client Identifier scheme `redirect_uri` is used in
222 /// conjunction with the Response Mode "`direct_post`", and the
223 /// `response_uri` parameter is present, the `client_id` value MUST be
224 /// equal to the `response_uri` value.
225 #[serde(skip_serializing_if = "Option::is_none")]
226 pub response_uri: Option<String>,
227
228 /// The Wallet MAY allow Verifiers to request presentation of Verifiable
229 /// Credentials by utilizing a pre-defined scope value. Defined in
230 /// [RFC6749].
231 ///
232 /// [RFC6749]: (https://www.rfc-editor.org/rfc/rfc6749.html)
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub scope: Option<String>,
235
236 /// The nonce is used to securely bind the requested Verifiable
237 /// Presentation(s) provided by the Wallet to the particular
238 /// transaction. Returned in the VP's Proof.challenge parameter.
239 pub nonce: String,
240
241 /// State is used to maintain state between the Authorization Request and
242 /// subsequent callback from the Wallet ('Authorization Server').
243 #[serde(skip_serializing_if = "Option::is_none")]
244 pub state: Option<String>,
245
246 /// The Presentation Definition
247 pub presentation_definition: Kind<PresentationDefinition>,
248
249 /// The `client_id_scheme` is used to specify how the Wallet should to
250 /// obtain and validate Verifier metadata. The following values indicate
251 /// how the Wallet should interpret the value of the `client_id`
252 /// parameter.
253 ///
254 /// - If not set, the Wallet MUST behave as specified in [RFC6749].
255 /// - If the same Client Identifier is used with different Client Identifier
256 /// schemes, those occurences MUST be treated as different Verifiers. The
257 /// Verifier needs to determine which Client Identifier schemes the Wallet
258 /// supports prior to sending the Authorization Request in order to choose
259 /// a supported scheme.
260 ///
261 /// [RFC6749]: (https://www.rfc-editor.org/rfc/rfc6749.html)
262 /// [RFC5280]: (https://www.rfc-editor.org/rfc/rfc5280)
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub client_id_scheme: Option<ClientIdScheme>,
265
266 /// Client Metadata contains Verifier metadata values.
267 pub client_metadata: Verifier,
268}
269
270/// The type of response expected from the Wallet (as Authorization Server).
271#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
272pub enum ResponseType {
273 /// A VP Token is returned in an Authorization Response
274 #[default]
275 #[serde(rename = "vp_token")]
276 VpToken,
277
278 /// A VP Token and a Self-Issued ID Token are returned in an Authorization
279 /// Response (if `scope` is set to "openid").
280 #[serde(rename = "vp_token id_token")]
281 VpTokenIdToken,
282
283 /// A VP Token is returned in a Token Response
284 #[serde(rename = "code")]
285 Code,
286}
287
288/// The type of response expected from the Wallet (as Authorization Server).
289#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
290pub enum ClientIdScheme {
291 /// The Client Identifier is the redirect URI (or response URI).
292 /// The Authorization Request MUST NOT be signed, the Verifier MAY omit the
293 /// `redirect_uri` parameter, and all Verifier metadata parameters MUST be
294 /// passed using the `client_metadata` parameter.
295 /// If used in conjunction with the Response Mode "`direct_post`", and the
296 /// `response_uri` parameter is present, the `client_id` value MUST be equal
297 /// to the `response_uri` value.
298 #[default]
299 #[serde(rename = "redirect_uri")]
300 RedirectUri,
301
302 /// The Client Identifier is a DID.
303 /// The request MUST be signed with a private key associated with the DID. A
304 /// public key to verify the signature MUST be obtained from the
305 /// `verificationMethod` property of a DID Document. Since DID Document may
306 /// include multiple public keys, a particular public key used to sign the
307 /// request in question MUST be identified by the `kid` in the JOSE Header.
308 /// To obtain the DID Document, the Wallet MUST use DID Resolution defined
309 /// by the DID method used by the Verifier. All Verifier metadata other than
310 /// the public key MUST be obtained from the `client_metadata` parameter.
311 #[serde(rename = "did")]
312 Did,
313 // ---------------------------------------------------------------------
314 // Unsupported schemes
315 // ---------------------------------------------------------------------
316 // /// The Verifier authenticates using a JWT.
317 // /// The Client Identifier MUST equal the `sub` claim value in the Verifier
318 // /// attestation JWT. The request MUST be signed with the private key corresponding
319 // /// to the public key in the `cnf` claim in the Verifier attestation JWT. This
320 // /// serves as proof of possesion of this key. The Verifier attestation JWT MUST be
321 // /// added to the `jwt` JOSE Header of the request object. The Wallet
322 // /// MUST validate the signature on the Verifier attestation JWT. The `iss` claim
323 // /// of the Verifier Attestation JWT MUST identify a party the Wallet trusts
324 // /// for issuing Verifier Attestation JWTs. If the Wallet cannot establish trust,
325 // /// it MUST refuse the request. If the issuer of the Verifier Attestation JWT
326 // /// adds a `redirect_uris` claim to the attestation, the Wallet MUST ensure the
327 // /// `redirect_uri` request parameter value exactly matches one of the `redirect_uris`
328 // /// claim entries. All Verifier metadata other than the public key MUST be
329 // /// obtained from the `client_metadata`.
330 // #[serde(rename = "verifier_attestation")]
331 // VerifierAttestation,
332
333 // /// The Client Identifier is already known to the Wallet.
334 // /// This value represents the [RFC6749] default behavior, i.e. the Client
335 // /// Identifier needs to be known to the Wallet in advance of the Authorization
336 // /// Request. Verifier metadata is obtained from metadata endpoint
337 // /// [RFC7591] or out-of-band an mechanism.
338 // #[serde(rename = "pre-registered")]
339 // PreRegistered,
340
341 // /// The Client Identifier is an OpenID.Federation Entity ID.
342 // /// OpenID.Federation processing rules are followed, OpenID.Federation automatic
343 // /// registration is used, the request may contain a `trust_chain` parameter, the
344 // /// Wallet only obtains Verifier metadata from Entity Statement(s),
345 // /// `client_metadata`.
346 // #[serde(rename = "entity_id")]
347 // EntityId,
348
349 // /// The Client Identifier is a DNS name.
350 // /// The DNS name MUST match a dNSName Subject Alternative Name (SAN) [RFC5280]
351 // /// entry in the leaf certificate passed with the request.
352 // #[serde(rename = "x509_san_dns")]
353 // X509SanDns,
354
355 // /// The Client Identifier is a URI.
356 // /// The URI MUST match a uniformResourceIdentifier Subject Alternative Name (SAN)
357 // /// [RFC5280] entry in the leaf certificate passed with the request.
358 // #[serde(rename = "x509_san_uri")]
359 // X509SanUri,
360}
361
362// /// The type of Presentation Definition returned by the `RequestObject`:
363// either an object /// or a URI.
364// #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
365// #[allow(clippy::module_name_repetitions)]
366// pub enum PresentationDefinitionType {
367// /// A Presentation Definition object embedded in the `RequestObject`.
368// #[serde(rename = "presentation_definition")]
369// Object(PresentationDefinition),
370
371// /// A URI pointing to where a Presentation Definition object can be
372// /// retrieved. This parameter MUST be set when neither
373// /// `presentation_definition` nor a Presentation Definition scope value
374// /// are set.
375// #[serde(rename = "presentation_definition_uri")]
376// Uri(String),
377// }
378
379// impl Default for PresentationDefinitionType {
380// fn default() -> Self {
381// Self::Object(PresentationDefinition::default())
382// }
383// }
384
385impl RequestObject {
386 /// Generate qrcode for Request Object.
387 /// Use the `endpoint` parameter to specify the Wallet's endpoint using deep
388 /// link or direct call format.
389 ///
390 /// For example,
391 ///
392 /// ```http
393 /// openid-vc://?request_uri=
394 /// or GET https://holder.wallet.io/authorize?
395 /// ```
396 ///
397 /// # Errors
398 ///
399 /// Returns an `Error::ServerError` error if the Request Object cannot be
400 /// serialized.
401 pub fn to_qrcode(&self, endpoint: &str) -> Result<String> {
402 let qs =
403 self.to_querystring().map_err(|e| anyhow!("Failed to generate querystring: {e}"))?;
404
405 // generate qr code
406 let qr_code = QrCode::new(format!("{endpoint}{qs}"))
407 .map_err(|e| anyhow!("Failed to create QR code: {e}"))?;
408
409 // write image to buffer
410 let img_buf = qr_code.render::<image::Luma<u8>>().build();
411 let mut buffer: Vec<u8> = Vec::new();
412 let mut writer = Cursor::new(&mut buffer);
413 img_buf
414 .write_to(&mut writer, image::ImageFormat::Png)
415 .map_err(|e| anyhow!("Failed to create QR code: {e}"))?;
416
417 // base64 encode image
418 Ok(format!("data:image/png;base64,{}", Base64::encode_string(buffer.as_slice())))
419 }
420
421 /// Generate a query string for the Request Object.
422 ///
423 /// # Errors
424 ///
425 /// Returns an `Error::ServerError` error if the Request Object cannot be
426 /// serialized.
427 pub fn to_querystring(&self) -> Result<String> {
428 urlencode::to_string(self).map_err(|e| anyhow!("issue creating query string: {e}"))
429 }
430}
431
432/// The Request Object Request is used (indirectly) by the Wallet to retrieve a
433/// previously generated Authorization Request Object.
434///
435/// The Wallet is sent a `request_uri` containing a unique URL pointing to the
436/// Request Object. The URI has the form `client_id/request/state_key`.
437#[derive(Clone, Debug, Default, Deserialize, Serialize)]
438#[serde(default)]
439pub struct RequestObjectRequest {
440 /// The ID of the Verifier to retrieve the Authorization Request Object for.
441 #[serde(default)]
442 pub client_id: String,
443
444 /// The unique identifier of the the previously generated Request Object.
445 pub id: String,
446}
447
448/// The Request Object Response returns a previously generated Authorization
449/// Request Object.
450#[derive(Clone, Debug, Default, PartialEq, Eq)]
451pub struct RequestObjectResponse {
452 /// The Authorization Request Object generated by the `request` endpoint
453 /// either as an object or serialised to a JWT.
454 pub request_object: RequestObjectType,
455}
456
457/// The type of Authorization Request Object returned in the `RequestObject`:
458/// either an object or a JWT.
459#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
460#[allow(clippy::large_enum_variant)]
461pub enum RequestObjectType {
462 /// The repsonse contains an Authorization Request Object objet.
463 #[serde(rename = "request_object")]
464 Object(RequestObject),
465
466 /// The response contains an Authorization Request Object encoded as a JWT.
467 #[serde(rename = "jwt")]
468 Jwt(String),
469}
470
471impl Default for RequestObjectType {
472 fn default() -> Self {
473 Self::Object(RequestObject::default())
474 }
475}
476
477/// Serialize to 'unwrapped' JWT if Request Object is JWT (`jwt parameter is
478/// set`).
479impl Serialize for RequestObjectResponse {
480 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
481 where
482 S: Serializer,
483 {
484 match &self.request_object {
485 RequestObjectType::Object(_) => {
486 let mut map = serializer.serialize_map(Some(1))?;
487 map.serialize_entry("request_object", &self.request_object)?;
488 map.end()
489 }
490 RequestObjectType::Jwt(jwt) => jwt.serialize(serializer),
491 }
492 }
493}
494
495/// Deserialize from JSON or 'unwrapped' JWT if Request Object is JWT.
496impl<'de> Deserialize<'de> for RequestObjectResponse {
497 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
498 where
499 D: Deserializer<'de>,
500 {
501 struct VisitorImpl;
502
503 impl<'de> Visitor<'de> for VisitorImpl {
504 type Value = RequestObjectResponse;
505
506 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
507 formatter.write_str("a RequestObjectResponse or JWT")
508 }
509
510 fn visit_str<E>(self, value: &str) -> Result<RequestObjectResponse, E>
511 where
512 E: de::Error,
513 {
514 Ok(RequestObjectResponse {
515 request_object: RequestObjectType::Jwt(value.to_string()),
516 })
517 }
518
519 fn visit_map<A>(self, mut map: A) -> Result<RequestObjectResponse, A::Error>
520 where
521 A: de::MapAccess<'de>,
522 {
523 let mut resp = RequestObjectResponse::default();
524
525 while let Some(key) = map.next_key::<String>()? {
526 match key.as_str() {
527 "request_object" => {
528 resp.request_object = RequestObjectType::Object(map.next_value()?);
529 }
530 "jwt" => resp.request_object = RequestObjectType::Jwt(map.next_value()?),
531 _ => {
532 return Err(de::Error::unknown_field(&key, &["request_object", "jwt"]));
533 }
534 }
535 }
536
537 Ok(resp)
538 }
539 }
540
541 deserializer.deserialize_any(VisitorImpl)
542 }
543}
544
545/// Authorization Response request object is used by Wallets to send a VP Token
546/// and Presentation Submission to the Verifier who initiated the verification.
547#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
548pub struct ResponseRequest {
549 /// One or more Verifiable Presentations represented as base64url encoded
550 /// strings and/or JSON objects. The VP format determines the encoding.
551 /// The encoding follows the same format-based rules as for Credential
552 /// issuance (Appendix E of the [OpenID4VCI] specification).
553 ///
554 /// When a single Verifiable Presentation is returned, array syntax MUST NOT
555 /// be used.
556 ///
557 /// [OpenID4VCI]: (https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html)
558 #[serde(skip_serializing_if = "Option::is_none")]
559 pub vp_token: Option<Vec<Kind<VerifiablePresentation>>>,
560
561 /// The `presentation_submission` element as defined in
562 /// [DIF.PresentationExchange]. It contains mappings between the
563 /// requested Verifiable Credentials and where to find them within the
564 /// returned VP Token.
565 #[serde(skip_serializing_if = "Option::is_none")]
566 pub presentation_submission: Option<PresentationSubmission>,
567
568 /// The client state value from the Authorization Request.
569 #[serde(skip_serializing_if = "Option::is_none")]
570 pub state: Option<String>,
571}
572
573impl ResponseRequest {
574 /// Create a `HashMap` representation of the `ResponseRequest` suitable for
575 /// use in an HTML form post.
576 ///
577 /// # Errors
578 /// Will return an error if any nested objects cannot be serialized and
579 /// URL-encoded.
580 pub fn form_encode(&self) -> anyhow::Result<HashMap<String, String>> {
581 let mut map = HashMap::new();
582 if let Some(vp_token) = &self.vp_token {
583 let as_json = serde_json::to_string(vp_token)?;
584 map.insert("vp_token".into(), urlencoding::encode(&as_json).to_string());
585 }
586 if let Some(presentation_submission) = &self.presentation_submission {
587 let as_json = serde_json::to_string(presentation_submission)?;
588 map.insert("presentation_submission".into(), urlencoding::encode(&as_json).to_string());
589 }
590 if let Some(state) = &self.state {
591 map.insert("state".into(), state.into());
592 }
593 Ok(map)
594 }
595
596 /// Create a `ResponseRequest` from a `HashMap` representation.
597 ///
598 /// Suitable for
599 /// use in a verifier's response endpoint that receives a form post before
600 /// passing the `ResponseRequest` to the `response` handler.
601 ///
602 /// # Errors
603 /// Will return an error if any nested objects cannot be deserialized from
604 /// URL-encoded JSON strings.
605 pub fn form_decode(map: &HashMap<String, String>) -> anyhow::Result<Self> {
606 let mut req = Self::default();
607 if let Some(vp_token) = map.get("vp_token") {
608 let decoded = urlencoding::decode(vp_token)?;
609 let vp_token: Vec<Kind<VerifiablePresentation>> = serde_json::from_str(&decoded)?;
610 req.vp_token = Some(vp_token);
611 }
612 if let Some(presentation_submission) = map.get("presentation_submission") {
613 let decoded = urlencoding::decode(presentation_submission)?;
614 let presentation_submission: PresentationSubmission = serde_json::from_str(&decoded)?;
615 req.presentation_submission = Some(presentation_submission);
616 }
617 if let Some(state) = map.get("state") {
618 req.state = Some(state.to_string());
619 }
620 Ok(req)
621 }
622}
623
624/// Authorization Response response object is used to return a `redirect_uri` to
625/// the Wallet following successful processing of the presentation submission.
626#[derive(Debug, Deserialize, Serialize)]
627pub struct ResponseResponse {
628 /// When the redirect parameter is used the Wallet MUST send the User Agent
629 /// to the provided URI. The redirect URI allows the Verifier to
630 /// continue the interaction with the End-User on the device where the
631 /// Wallet resides after the Wallet has sent the Authorization Response.
632 /// It especially enables the Verifier to prevent session fixation
633 /// attacks.
634 ///
635 /// The URI — an absolute URI — is chosen by the Verifier. It MUST include a
636 /// fresh, cryptographically random number to ensure only the receiver
637 /// of the redirect can fetch and process the Authorization Response.
638 /// The number could be added as a path component or a parameter to the
639 /// URL. It is RECOMMENDED to use a cryptographic random value of 128
640 /// bits or more.
641 ///
642 /// # Example
643 ///
644 /// ```http
645 /// redirect_uri": "https://client.example.org/cb#response_code=091535f699ea575c7937fa5f0f454aee"
646 /// ```
647 /// If the response does not contain a parameter, the Wallet is not required
648 /// to perform any further steps.
649 pub redirect_uri: Option<String>,
650
651 /// A cryptographically random number with sufficient entropy used to link
652 /// the Authorization Response to the Authorization Request. The
653 /// `response_code` is returned to the Verifier when the Wallet follows
654 /// the redirect in the `redirect_uri` parameter.
655 pub response_code: Option<String>,
656}
657
658/// Request to retrieve the Verifier's client metadata.
659#[derive(Clone, Debug, Default, Deserialize)]
660pub struct MetadataRequest {
661 /// The Verifier's Client Identifier for which the configuration is to be
662 /// returned.
663 #[serde(default)]
664 pub client_id: String,
665}
666
667/// Response containing the Verifier's client metadata.
668#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
669pub struct MetadataResponse {
670 /// The Client metadata for the specified Verifier.
671 #[serde(flatten)]
672 pub client: Verifier,
673}
674
675/// Used to define the format and proof types of Verifiable Presentations and
676/// Verifiable Credentials that a Verifier supports.
677///
678/// Deployments can extend the formats supported, provided Issuers, Holders and
679/// Verifiers all understand the new format.
680/// See <https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#alternative_credential_formats>
681#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
682pub struct VpFormat {
683 /// Algorithms supported by the format.
684 #[serde(skip_serializing_if = "Option::is_none")]
685 pub alg: Option<Vec<String>>,
686
687 /// Proof types supported by the format.
688 #[serde(skip_serializing_if = "Option::is_none")]
689 pub proof_type: Option<Vec<String>>,
690}
691
692/// OAuth 2 client metadata used for registering clients of the issuance and
693/// wallet authorization servers.
694///
695/// In the case of Issuance, the Wallet is the Client and the Issuer is the
696/// Authorization Server.
697///
698/// In the case of Presentation, the Wallet is the Authorization Server and the
699/// Verifier is the Client.
700#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
701pub struct Verifier {
702 /// OAuth 2.0 Client
703 #[serde(flatten)]
704 pub oauth: OAuthClient,
705
706 /// An object defining the formats and proof types of Verifiable
707 /// Presentations and Verifiable Credentials that a Verifier supports.
708 /// For specific values that can be used.
709 ///
710 /// # Example
711 ///
712 /// ```json
713 /// "jwt_vc_json": {
714 /// "proof_type": [
715 /// "JsonWebSignature2020"
716 /// ]
717 /// }
718 /// ```
719 #[serde(skip_serializing_if = "Option::is_none")]
720 pub vp_formats: Option<HashMap<Format, VpFormat>>,
721}
722
723/// The `OpenID4VCI` specification defines commonly used [Credential Format
724/// Profiles] to support. The profiles define Credential format specific
725/// parameters or claims used to support a particular format.
726///
727/// [Credential Format Profiles]: (https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-format-profiles)
728#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
729pub enum Format {
730 /// W3C Verifiable Credential.
731 #[serde(rename = "jwt_vp_json")]
732 JwtVpJson,
733}
734
735/// OAuth 2.0 Authorization Server metadata.
736/// See RFC 8414 - Authorization Server Metadata
737#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
738pub struct Wallet {
739 /// OAuth 2.0 Server
740 #[serde(flatten)]
741 pub oauth: OAuthServer,
742
743 /// Specifies whether the Wallet supports the transfer of
744 /// `presentation_definition` by reference, with true indicating support.
745 /// If omitted, the default value is true.
746 pub presentation_definition_uri_supported: bool,
747
748 /// A list of key value pairs, where the key identifies a Credential format
749 /// supported by the Wallet.
750 pub vp_formats_supported: Option<HashMap<String, VpFormat>>,
751}
752
753#[cfg(test)]
754mod tests {
755 use insta::assert_yaml_snapshot as assert_snapshot;
756
757 use super::*;
758 use crate::dif_exch::{DescriptorMap, PathNested};
759
760 #[test]
761 fn response_request_form_encode() {
762 let request = ResponseRequest {
763 vp_token: Some(vec![Kind::String("eyJ.etc".into())]),
764 presentation_submission: Some(PresentationSubmission {
765 id: "07b0d07c-f51e-4909-a1ab-d35e2cef20b0".into(),
766 definition_id: "4b93b6aa-2157-4458-80ff-ffcefa3ff3b0".into(),
767 descriptor_map: vec![DescriptorMap {
768 id: "employment".into(),
769 format: "jwt_vc_json".into(),
770 path: "$".into(),
771 path_nested: PathNested {
772 format: "jwt_vc_json".into(),
773 path: "$.verifiableCredential[0]".into(),
774 },
775 }],
776 }),
777 state: Some("Z2VVKkglOWt-MkNDbX5VN05RRFI4ZkZeT01ZelEzQG8".into()),
778 };
779 let map = request.form_encode().expect("should condense to hashmap");
780 assert_snapshot!("response_request_form_encoded", &map, {
781 "." => insta::sorted_redaction(),
782 });
783 let req = ResponseRequest::form_decode(&map).expect("should expand from hashmap");
784 assert_snapshot!("response_request_form_decoded", &req);
785 }
786}