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}