Skip to main content

smart_id_rust_client/client/
smart_id_client.rs

1use crate::client::reqwest_generic::{get, post};
2use crate::config::SmartIDConfig;
3use crate::error::Result;
4use crate::error::SmartIdClientError;
5use crate::error::SmartIdClientError::NoSessionException;
6use crate::models::api::authentication_session::{
7    AuthenticationDeviceLinkRequest, AuthenticationDeviceLinkResponse,
8    AuthenticationNotificationRequest, AuthenticationNotificationResponse,
9};
10use crate::models::api::certificate_choice_session::{CertificateChoiceDeviceLinkRequest, CertificateChoiceDeviceLinkResponse, CertificateChoiceNotificationRequest, CertificateChoiceNotificationResponse, SigningCertificate, SigningCertificateRequest, SigningCertificateResponse, SigningCertificateResponseState};
11use crate::models::api::session_status::{
12    SessionCertificate, SessionResponse, SessionState, SessionStatusResponse,
13};
14use crate::models::api::signature_session::{
15    SignatureDeviceLinkRequest, SignatureDeviceLinkResponse, SignatureNotificationLinkedRequest,
16    SignatureNotificationLinkedResponse, SignatureNotificationRequest,
17    SignatureNotificationResponse,
18};
19use crate::models::common::{SessionConfig, VCCode};
20use crate::models::device_link::DeviceLink::{CrossDeviceLink, SameDeviceLink};
21use crate::models::device_link::{DeviceLinkType, SessionType};
22use crate::models::signature::FlowType;
23use crate::models::user_identity::UserIdentity;
24use crate::utils::demo_certificates::{demo_intermediate_certificates, demo_root_certificates};
25use crate::utils::production_certificates::{
26    production_intermediate_certificates, production_root_certificates,
27};
28use crate::utils::sec_x509::verify_certificate;
29use std::sync::{Arc, Mutex};
30use tracing::debug;
31
32// region: Path definitions
33// Copied from https://github.com/SK-EID/smart-id-java-client/blob/81e48f519bf882db8584a344b161db378b959093/src/main/java/ee/sk/smartid/v3/rest/SmartIdRestConnector.java#L79
34const SESSION_STATUS_URI: &str = "/session";
35const NOTIFICATION_CERTIFICATE_CHOICE_WITH_SEMANTIC_IDENTIFIER_PATH: &str = "/signature/certificate-choice/notification/etsi";
36#[allow(dead_code)]
37const NOTIFICATION_CERTIFICATE_CHOICE_WITH_DOCUMENT_NUMBER_PATH: &str = "/signature/certificate-choice/notification/document";
38const ANONYMOUSE_DEVICE_LINK_CERTIFICATE_CHOICE_PATH: &str = "/signature/certificate-choice/device-link/anonymous";
39const SIGNING_CERTIFICATE_WITH_DOCUMENT_NUMBER_PATH: &str = "/signature/certificate";
40
41const DEVICE_LINK_SIGNATURE_WITH_SEMANTIC_IDENTIFIER_PATH: &str = "/signature/device-link/etsi";
42const DEVICE_LINK_SIGNATURE_WITH_DOCUMENT_NUMBER_PATH: &str = "/signature/device-link/document";
43const NOTIFICATION_SIGNATURE_WITH_SEMANTIC_IDENTIFIER_PATH: &str = "/signature/notification/etsi";
44const NOTIFICATION_SIGNATURE_WITH_DOCUMENT_NUMBER_PATH: &str = "/signature/notification/document";
45const NOTIFICATION_SIGNATURE_WITH_DOCUMENT_NUMBER_LINKED_PATH: &str = "/signature/notification/linked";
46
47const ANONYMOUS_DEVICE_LINK_AUTHENTICATION_PATH: &str = "/authentication/device-link/anonymous";
48const DEVICE_LINK_AUTHENTICATION_WITH_SEMANTIC_IDENTIFIER_PATH: &str =
49    "/authentication/device-link/etsi";
50const DEVICE_LINK_AUTHENTICATION_WITH_DOCUMENT_NUMBER_PATH: &str =
51    "/authentication/device-link/document";
52#[allow(dead_code)]
53const NOTIFICATION_AUTHENTICATION_WITH_SEMANTIC_IDENTIFIER_PATH: &str =
54    "/authentication/notification/etsi";
55#[allow(dead_code)]
56const NOTIFICATION_AUTHENTICATION_WITH_DOCUMENT_NUMBER_PATH: &str =
57    "/authentication/notification/document";
58
59// Currently only support for 1.0
60const DEVICE_LINK_VERSION: &str = "1.0";
61
62// endregion: Path definitions
63
64/// Smart ID Client
65///
66/// This struct provides methods to interact with the Smart ID service, including starting authentication,
67/// certificate choice, and signature sessions using device links. It also includes methods to generate
68/// device links, retrieve session status, and validate session responses.
69///
70/// The client maintains session state and authenticated user identity to ensure the correct user is signing
71/// and to validate session responses.
72#[derive(Debug)]
73pub struct SmartIdClient {
74    pub cfg: SmartIDConfig,
75    /// This tracks session state and is used to make subsequent requests
76    /// For example to generate QR codes or to poll for session status
77    pub(crate) session_config: Arc<Mutex<Option<SessionConfig>>>,
78    /// Is checked against returned certificates to ensure the correct user is signing
79    pub(crate) authenticated_identity: Arc<Mutex<Option<UserIdentity>>>,
80    /// List of root certificates used to validate the smart id certificates. If not provided, only the default root certificates will be used.
81    /// If you are using an older version of this library, you will need to provide the latest root certificates yourself.
82    pub(crate) root_certificates: Vec<String>,
83    /// List of intermediate certificates used to validate the smart id certificates. If not provided, only the default intermediate certificates will be used.
84    /// If you are using an older version of this library, you will need to provide the latest intermediate certificates yourself.
85    pub(crate) intermediate_certificates: Vec<String>,
86}
87
88impl SmartIdClient {
89    /// Creates a new SmartIdClient instance with the given configuration.
90    ///
91    /// # Arguments
92    ///
93    /// * `cfg` - A reference to the SmartIDConfig.
94    /// * `user_identity` - An optional UserIdentity. This will be compared with the certificate subject to ensure the correct user is signing. If not provided, the UserIdentity will be set from the certificate during the first successful authentication.
95    /// * `root_certificates` - A vector of base64 der encoded root certificates (not bundles), this is used to validate the smart id certificate chain. If not provided, only the default root certificates will be used. If you are using an older version of this library, you will need to provide the latest root certificates yourself.
96    /// * `intermediate_certificates` - A vector of base64 der encoded intermediate certificates (not bundles), this is used to validate the smart id certificate chain. If not provided, only the default intermediate certificates will be used. If you are using an older version of this library, you will need to provide the latest intermediate certificates yourself
97    ///
98    /// # Returns
99    ///
100    /// A new instance of SmartIdClient.
101    pub fn new(
102        cfg: &SmartIDConfig,
103        user_identity: Option<UserIdentity>,
104        root_certificates: Vec<String>,
105        intermediate_certificates: Vec<String>,
106    ) -> Self {
107        SmartIdClient {
108            cfg: cfg.clone(),
109            session_config: Arc::new(Mutex::new(None)),
110            authenticated_identity: Arc::new(Mutex::new(user_identity)),
111            root_certificates,
112            intermediate_certificates,
113        }
114    }
115
116    /// Creates a new SmartIdClient instance with the given session configuration.
117    /// This should not be used to start a new session!
118    /// This should be used when you need to cache the session configuration in a serialized form between requests.
119    ///
120    /// Example Use Case:
121    /// After starting an authentication session, you can cache the session_configuration (serialized).
122    /// Then, when you receive a request for session status, you rebuild the client. After you cache the session_configuration again.
123    /// Then, when you receive a request for a Device Link, you can rebuild the client from the session_configuration.
124    ///
125    /// # Arguments
126    ///
127    /// * `cfg` - A reference to the SmartIDConfig.
128    /// * `session_config` - The session configuration from a previous session.
129    /// * `user_identity` - An optional UserIdentity. This will be compared with the certificate subject to ensure the correct user is signing. If not provided, the UserIdentity will be set from the certificate during the first successful authentication.
130    /// * `root_certificates` - A vector of root certificates, this is used to validate the smart id certificate chain. If not provided, only the default root certificates will be used. If you are using an older version of this library, you will need to provide the latest root certificates yourself.
131    /// * `intermediate_certificates` - A vector of intermediate certificates, this is used to validate the smart id certificate chain. If not provided, only the default intermediate certificates will be used. If you are using an older version of this library, you will need to provide the latest intermediate certificates yourself
132    ///
133    /// # Returns
134    ///
135    /// A new instance of SmartIdClient.
136    pub fn from_session(
137        cfg: &SmartIDConfig,
138        session_config: SessionConfig,
139        user_identity: Option<UserIdentity>,
140        root_certificates: Vec<String>,
141        intermediate_certificates: Vec<String>,
142    ) -> Self {
143        SmartIdClient {
144            cfg: cfg.clone(),
145            session_config: Arc::new(Mutex::new(Some(session_config))),
146            authenticated_identity: Arc::new(Mutex::new(user_identity)),
147            root_certificates,
148            intermediate_certificates,
149        }
150    }
151
152    // region: Session Status
153
154    /// Retrieves the session status with a specified timeout.
155    /// The session must first be started with one of the start session methods.
156    ///
157    /// # Arguments
158    ///
159    /// * `timeoutMs` - Timeout in milliseconds.  The upper bound of timeout: 120000, minimum 1000.
160    ///
161    /// # Returns
162    ///
163    /// A Result containing the SessionStatus or an error.
164    ///
165    /// # Errors
166    ///
167    /// This function will return an error if:
168    /// - The session is not found or not running.
169    /// - The session status request fails.
170    /// - The session did not complete within the specified timeout.
171    /// - The session response endResult is not OK.
172    /// - The session response is missing a certificate.
173    /// - The session response is missing a signature.
174    /// - The session response certificate is invalid.
175    /// - The session response signature is invalid.
176    pub async fn get_session_status(&self) -> Result<SessionStatusResponse> {
177        let session_config = self.get_session()?;
178
179        let path = format!(
180            "{}{}/{}?timeoutMs={}",
181            self.cfg.api_url(),
182            SESSION_STATUS_URI,
183            session_config.session_id(),
184            self.cfg.long_polling_timeout,
185        );
186
187        let session_response =
188            get::<SessionResponse>(path.as_str(), Some(self.cfg.long_polling_timeout + 100))
189                .await?; // Add 100ms to allow SmartId to respond with a long polling timeout error instead of reqwest creating a connection error
190
191        let session_status = session_response.into_result()?;
192
193        match session_status.state {
194            SessionState::COMPLETE => {
195                self.validate_session_status(session_status.clone(), session_config)?;
196                Ok(session_status)
197            }
198            SessionState::RUNNING => {
199                Err(SmartIdClientError::StatusRequestLongPollingTimeoutException)
200            }
201        }
202    }
203
204    // endregion: Session Status
205
206    // region: Authentication
207
208    /// Starts an authentication session using a device link.
209    /// Use the create device link methods to generate the device link to send to the user to continue the authentication process.
210    /// Use the get_session_status method to poll for the result.
211    ///
212    /// # Arguments
213    ///
214    /// * `authentication_request` - The authentication request.
215    ///
216    /// # Returns
217    ///
218    /// A Result indicating success or failure.
219    pub async fn start_authentication_device_link_anonymous_session(
220        &self,
221        authentication_request: AuthenticationDeviceLinkRequest,
222    ) -> Result<()> {
223        self.clear_session();
224
225        let path = format!(
226            "{}{}",
227            self.cfg.api_url(),
228            ANONYMOUS_DEVICE_LINK_AUTHENTICATION_PATH,
229        );
230
231        let authentication_response =
232            post::<AuthenticationDeviceLinkRequest, AuthenticationDeviceLinkResponse>(
233                path.as_str(),
234                &authentication_request,
235                self.cfg.client_request_timeout,
236            )
237            .await?;
238
239        let session = authentication_response.into_result()?;
240
241        self.set_session(SessionConfig::from_authentication_device_link_response(
242            session,
243            authentication_request,
244            &self.cfg.scheme_name,
245        )?)
246    }
247
248    /// Starts an authentication session with a document using a device link.
249    /// Use the create device link methods to generate the device link to send to the user to continue the authentication process.
250    /// Use the get_session_status method to poll for the result.
251    ///
252    /// # Arguments
253    ///
254    /// * `authentication_request` - The authentication request.
255    /// * `document_number` - The document number.
256    ///
257    /// # Returns
258    ///
259    /// A Result indicating success or failure.
260    pub async fn start_authentication_device_link_document_session(
261        &self,
262        authentication_request: AuthenticationDeviceLinkRequest,
263        document_number: String,
264    ) -> Result<()> {
265        self.clear_session();
266
267        let path = format!(
268            "{}{}/{}",
269            self.cfg.api_url(),
270            DEVICE_LINK_AUTHENTICATION_WITH_DOCUMENT_NUMBER_PATH,
271            document_number,
272        );
273
274        let authentication_response =
275            post::<AuthenticationDeviceLinkRequest, AuthenticationDeviceLinkResponse>(
276                path.as_str(),
277                &authentication_request,
278                self.cfg.client_request_timeout,
279            )
280            .await?;
281
282        let session = authentication_response.into_result()?;
283
284        self.set_session(SessionConfig::from_authentication_device_link_response(
285            session,
286            authentication_request,
287            &self.cfg.scheme_name,
288        )?)
289    }
290
291    /// Starts an authentication session with an etsi using a device link.
292    /// Use the create device link methods to generate the device link to send to the user to continue the authentication process.
293    /// Use the get_session_status method to poll for the result.
294    ///
295    /// # Arguments
296    ///
297    /// * `authentication_request` - The authentication request.
298    /// * `etsi` - The ETSI semantic identifier.
299    ///
300    /// # Returns
301    ///
302    /// A Result indicating success or failure.
303    pub async fn start_authentication_device_link_etsi_session(
304        &self,
305        authentication_request: AuthenticationDeviceLinkRequest,
306        etsi: String,
307    ) -> Result<()> {
308        self.clear_session();
309
310        let path = format!(
311            "{}{}/{}",
312            self.cfg.api_url(),
313            DEVICE_LINK_AUTHENTICATION_WITH_SEMANTIC_IDENTIFIER_PATH,
314            etsi,
315        );
316
317        let authentication_response =
318            post::<AuthenticationDeviceLinkRequest, AuthenticationDeviceLinkResponse>(
319                path.as_str(),
320                &authentication_request,
321                self.cfg.client_request_timeout,
322            )
323            .await?;
324
325        let session = authentication_response.into_result()?;
326
327        self.set_session(SessionConfig::from_authentication_device_link_response(
328            session,
329            authentication_request,
330            &self.cfg.scheme_name,
331        )?)
332    }
333
334    /// Starts an authentication session using a notification.
335    /// Use the `get_session_status` method to poll for the result.
336    ///
337    /// # Arguments
338    ///
339    /// * `authentication_request` - The authentication request.
340    /// * `etsi` - The ETSI identifier of the user.
341    ///
342    /// # Returns
343    ///
344    /// A `Result` containing the verification code the user will see on screen.
345    pub async fn start_authentication_notification_etsi_session(
346        &self,
347        authentication_request: AuthenticationNotificationRequest,
348        etsi: String,
349    ) -> Result<VCCode> {
350        self.clear_session();
351
352        let path = format!(
353            "{}{}/{}",
354            self.cfg.api_url(),
355            NOTIFICATION_AUTHENTICATION_WITH_SEMANTIC_IDENTIFIER_PATH,
356            etsi,
357        );
358
359        let authentication_response =
360            post::<AuthenticationNotificationRequest, AuthenticationNotificationResponse>(
361                path.as_str(),
362                &authentication_request,
363                self.cfg.client_request_timeout,
364            )
365            .await?;
366
367        let session = authentication_response.into_result()?;
368
369        let session_config = SessionConfig::from_authentication_notification_response(
370            session.clone(),
371            authentication_request,
372            &self.cfg.scheme_name,
373        )?;
374
375        let vc_code = session_config.calculate_vc_code();
376
377        self.set_session(session_config)?;
378
379        vc_code
380    }
381
382    /// Starts an authentication session using a notification.
383    /// Use the `get_session_status` method to poll for the result.
384    ///
385    /// # Arguments
386    ///
387    /// * `authentication_request` - The authentication request.
388    /// * `document_number` - The document number.
389    ///
390    /// # Returns
391    ///
392    /// A `Result` containing the verification code the user will see on screen.
393    pub async fn start_authentication_notification_document_session(
394        &self,
395        authentication_request: AuthenticationNotificationRequest,
396        document_number: String,
397    ) -> Result<VCCode> {
398        self.clear_session();
399
400        let path = format!(
401            "{}{}/{}",
402            self.cfg.api_url(),
403            NOTIFICATION_AUTHENTICATION_WITH_DOCUMENT_NUMBER_PATH,
404            document_number,
405        );
406
407        let authentication_response =
408            post::<AuthenticationNotificationRequest, AuthenticationNotificationResponse>(
409                path.as_str(),
410                &authentication_request,
411                self.cfg.client_request_timeout,
412            )
413            .await?;
414
415        let session = authentication_response.into_result()?;
416
417        let session_config = SessionConfig::from_authentication_notification_response(
418            session.clone(),
419            authentication_request,
420            &self.cfg.scheme_name,
421        )?;
422
423        let vc_code = session_config.calculate_vc_code();
424
425        self.set_session(session_config)?;
426
427        vc_code
428    }
429
430    // endregion: Authentication
431
432    // region: Signature
433
434    /// Starts a signature session using a device link and an ETSI identifier.
435    /// Use the create device link methods to generate the device link to send to the user to continue the signature process.
436    /// Use the get_session_status method to poll for the result.
437    ///
438    /// # Arguments
439    ///
440    /// * `signature_request` - The signature request.
441    /// * `etsi` - The ETSI identifier.
442    ///
443    /// # Returns
444    ///
445    /// A Result indicating success or failure.
446    pub async fn start_signature_device_link_etsi_session(
447        &self,
448        signature_request: SignatureDeviceLinkRequest,
449        etsi: String,
450    ) -> Result<()> {
451        self.clear_session();
452
453        let path = format!(
454            "{}{}/{}",
455            self.cfg.api_url(),
456            DEVICE_LINK_SIGNATURE_WITH_SEMANTIC_IDENTIFIER_PATH,
457            etsi,
458        );
459
460        let signature_response = post::<SignatureDeviceLinkRequest, SignatureDeviceLinkResponse>(
461            path.as_str(),
462            &signature_request,
463            self.cfg.client_request_timeout,
464        )
465        .await?;
466
467        let session = signature_response.into_result()?;
468
469        self.set_session(SessionConfig::from_signature_device_link_request_response(
470            session,
471            signature_request,
472            &self.cfg.scheme_name,
473        )?)
474    }
475
476    /// Starts a signature session using a device link and a document number.
477    /// Use the create device link methods to generate the device link to send to the user to continue the signature process.
478    /// Use the get_session_status method to poll for the result.
479    ///
480    /// # Arguments
481    ///
482    /// * `signature_request` - The signature request.
483    /// * `document_number` - The document number.
484    ///
485    /// # Returns
486    ///
487    /// A Result indicating success or failure.
488    pub async fn start_signature_device_link_document_session(
489        &self,
490        signature_request: SignatureDeviceLinkRequest,
491        document_number: String,
492    ) -> Result<()> {
493        self.clear_session();
494
495        let path = format!(
496            "{}{}/{}",
497            self.cfg.api_url(),
498            DEVICE_LINK_SIGNATURE_WITH_DOCUMENT_NUMBER_PATH,
499            document_number,
500        );
501
502        let signature_response = post::<SignatureDeviceLinkRequest, SignatureDeviceLinkResponse>(
503            path.as_str(),
504            &signature_request,
505            self.cfg.client_request_timeout,
506        )
507        .await?;
508
509        let session = signature_response.into_result()?;
510
511        self.set_session(SessionConfig::from_signature_device_link_request_response(
512            session,
513            signature_request,
514            &self.cfg.scheme_name,
515        )?)
516    }
517
518    /// Starts a signature session using a notification.
519    /// Use the `get_session_status` method to poll for the result.
520    ///
521    /// # Arguments
522    ///
523    /// * `signature_request` - The signature request.
524    /// * `etsi` - The ETSI identifier.
525    ///
526    /// # Returns
527    ///
528    /// A `Result` containing the verification code the user will see on screen.
529    pub async fn start_signature_notification_etsi_session(
530        &self,
531        signature_request: SignatureNotificationRequest,
532        etsi: String,
533    ) -> Result<VCCode> {
534        self.clear_session();
535
536        let path = format!(
537            "{}{}/{}",
538            self.cfg.api_url(),
539            NOTIFICATION_SIGNATURE_WITH_SEMANTIC_IDENTIFIER_PATH,
540            etsi,
541        );
542
543        let signature_response =
544            post::<SignatureNotificationRequest, SignatureNotificationResponse>(
545                path.as_str(),
546                &signature_request,
547                self.cfg.client_request_timeout,
548            )
549            .await?;
550
551        let session = signature_response.into_result()?;
552
553        self.set_session(SessionConfig::from_signature_notification_response(
554            session.clone(),
555            signature_request,
556            &self.cfg.scheme_name,
557        )?)?;
558
559        Ok(session.vc)
560    }
561
562    /// Starts a signature session using a notification.
563    /// Use the `get_session_status` method to poll for the result.
564    ///
565    /// # Arguments
566    ///
567    /// * `signature_request` - The signature request.
568    /// * `document_number` - The document number.
569    ///
570    /// # Returns
571    ///
572    /// A `Result` containing the verification code the user will see on screen.
573    pub async fn start_signature_notification_document_session(
574        &self,
575        signature_request: SignatureNotificationRequest,
576        document_number: String,
577    ) -> Result<VCCode> {
578        self.clear_session();
579
580        let path = format!(
581            "{}{}/{}",
582            self.cfg.api_url(),
583            NOTIFICATION_SIGNATURE_WITH_DOCUMENT_NUMBER_PATH,
584            document_number,
585        );
586
587        let signature_response =
588            post::<SignatureNotificationRequest, SignatureNotificationResponse>(
589                path.as_str(),
590                &signature_request,
591                self.cfg.client_request_timeout,
592            )
593            .await?;
594
595        let session = signature_response.into_result()?;
596
597        self.set_session(SessionConfig::from_signature_notification_response(
598            session.clone(),
599            signature_request,
600            &self.cfg.scheme_name,
601        )?)?;
602
603        Ok(session.vc)
604    }
605
606    /// Starts a linked signature session using a notification.
607    /// This is the same as the start_signature_notification_document_session method, but can be linked to a previous certificate choice session.
608    /// Use the `get_session_status` method to poll for the result.
609    ///
610    /// # Arguments
611    ///
612    /// * `signature_request` - The signature request.
613    /// * `document_number` - The document number.
614    /// # Returns
615    ///
616    /// A Result indicating success or failure.
617    pub async fn start_signature_notification_document_linked_session(
618        &self,
619        signature_request: SignatureNotificationLinkedRequest,
620        document_number: String,
621    ) -> Result<()> {
622        self.clear_session();
623
624        let path = format!(
625            "{}{}/{}",
626            self.cfg.api_url(),
627            NOTIFICATION_SIGNATURE_WITH_DOCUMENT_NUMBER_LINKED_PATH,
628            document_number,
629        );
630
631        let signature_response =
632            post::<SignatureNotificationLinkedRequest, SignatureNotificationLinkedResponse>(
633                path.as_str(),
634                &signature_request,
635                self.cfg.client_request_timeout,
636            )
637            .await?;
638
639        let session = signature_response.into_result()?;
640
641        self.set_session(SessionConfig::from_signature_notification_linked_response(
642            session.clone(),
643            signature_request,
644            &self.cfg.scheme_name,
645        )?)?;
646
647        Ok(())
648    }
649
650    // endregion: Signature
651
652    // region: Certificate Choice
653
654    /// Starts a certificate choice session using a notification and an ETSI identifier.
655    /// Use the get_session_status method to poll for the result.
656    ///
657    /// # Arguments
658    ///
659    /// * `certificate_choice_request` - The certificate choice request.
660    /// * `etsi` - The ETSI identifier.
661    ///
662    /// # Returns
663    ///
664    /// A Result indicating success or failure.
665    pub async fn start_certificate_choice_notification_etsi_session(
666        &self,
667        certificate_choice_request: CertificateChoiceNotificationRequest,
668        etsi: String,
669    ) -> Result<()> {
670        self.clear_session();
671
672        let path = format!(
673            "{}{}/{}",
674            self.cfg.api_url(),
675            NOTIFICATION_CERTIFICATE_CHOICE_WITH_SEMANTIC_IDENTIFIER_PATH,
676            etsi,
677        );
678
679        let certificate_choice_response =
680            post::<CertificateChoiceNotificationRequest, CertificateChoiceNotificationResponse>(
681                path.as_str(),
682                &certificate_choice_request,
683                self.cfg.client_request_timeout,
684            )
685            .await?;
686
687        let session = certificate_choice_response.into_result()?;
688
689        self.set_session(
690            SessionConfig::from_certificate_choice_notification_response(
691                session,
692                certificate_choice_request,
693                &self.cfg.scheme_name,
694            ),
695        )
696    }
697
698    /// Starts an anonymous certificate choice session using a ge link
699    /// Use the get_session_status method to poll for the result.
700    /// This should be proceeded by a signature session.
701    ///
702    /// # Arguments
703    ///
704    /// * `certificate_choice_request` - The certificate choice request.
705    ///
706    /// # Returns
707    ///
708    /// A Result indicating success or failure.
709    pub async fn start_certificate_choice_anonymous_session(
710        &self,
711        certificate_choice_request: CertificateChoiceDeviceLinkRequest,
712    ) -> Result<()> {
713        self.clear_session();
714
715        let path = format!(
716            "{}{}",
717            self.cfg.api_url(),
718            ANONYMOUSE_DEVICE_LINK_CERTIFICATE_CHOICE_PATH,
719        );
720
721        let certificate_choice_response =
722            post::<CertificateChoiceDeviceLinkRequest, CertificateChoiceDeviceLinkResponse>(
723                path.as_str(),
724                &certificate_choice_request,
725                self.cfg.client_request_timeout,
726            )
727                .await?;
728
729        let session = certificate_choice_response.into_result()?;
730
731        self.set_session(
732            SessionConfig::from_certificate_choice_device_link_response(
733                session,
734                certificate_choice_request,
735                &self.cfg.scheme_name,
736            ),
737        )
738    }
739
740    /// Get the signing certificate of the requested document number.
741    /// If the document number has been previously aquired via the certificate choice session or authentication session, this can be used to get the signing certificate.
742    /// This does not require a session.
743    ///
744    /// # Arguments
745    /// * `document_number` - The document number.
746    /// * `signing_certificate_request` - The signing certificate request.
747    ///
748    /// # Returns
749    /// A `Result` containing a SigningCertificateResult or an error.
750    pub async fn get_signing_certificate(
751        &self,
752        document_number: String,
753        signing_certificate_request: SigningCertificateRequest,
754    ) -> Result<SigningCertificate> {
755        self.clear_session();
756        let path = format!(
757            "{}{}/{}",
758            self.cfg.api_url(),
759            SIGNING_CERTIFICATE_WITH_DOCUMENT_NUMBER_PATH,
760            document_number,
761        );
762
763        let certificate_choice_response =
764            post::<SigningCertificateRequest, SigningCertificateResponse>(
765                path.as_str(),
766                &signing_certificate_request,
767                self.cfg.client_request_timeout,
768            )
769            .await?;
770
771        match certificate_choice_response {
772            SigningCertificateResponse::Success(signing_certificate_result) => {
773                match signing_certificate_result.state {
774                    SigningCertificateResponseState::OK => Ok(signing_certificate_result.cert),
775                    SigningCertificateResponseState::DOCUMENT_UNUSABLE => {
776                        Err(SmartIdClientError::GetSigningCertificateException(
777                            "Document is unusable".to_string(),
778                        ))
779                    }
780                }
781            }
782            SigningCertificateResponse::Error(e) => Err(
783                SmartIdClientError::GetSigningCertificateException(e.error_type),
784            ),
785        }
786    }
787
788    // endregion: Certificate Choice
789
790    // region: Device Link
791
792    /// Generates a device link for the current session.
793    /// The link will redirect the device to the Smart-ID app.
794    /// The link must be refreshed every 1 second.
795    ///
796    /// # Arguments
797    ///
798    /// * `device_link_type` - This can be a QR, Web2App or App2App link.
799    /// * `language_code` - The language code (3-letter ISO 639-2 code).
800    ///
801    /// # Returns
802    ///
803    /// A `Result` containing the generated device link as a `String` or an error.
804    ///
805    /// # Errors
806    ///
807    /// This function will return an error if:
808    /// - There is no running session.
809    /// - The session type is `CertificateChoice`.
810    pub fn generate_device_link(
811        &self,
812        device_link_type: DeviceLinkType,
813        language_code: &str,
814    ) -> Result<String> {
815        let session: SessionConfig = self.get_session()?;
816
817        match session {
818            SessionConfig::AuthenticationDeviceLink {
819                session_secret,
820                session_token,
821                device_link_base,
822                relying_party_name,
823                initial_callback_url,
824                signature_protocol,
825                interactions,
826                rp_challenge,
827                session_start_time,
828                ..
829            } => {
830                let device_link = match device_link_type {
831                    DeviceLinkType::Web2App | DeviceLinkType::App2App => {
832                        if let Some(initial_callback_url) = initial_callback_url {
833                            SameDeviceLink {
834                                device_link_base,
835                                device_link_type,
836                                session_token,
837                                session_type: SessionType::auth,
838                                version: DEVICE_LINK_VERSION.to_string(),
839                                language_code: language_code.to_string(),
840                                session_secret,
841                                scheme_name: self.cfg.scheme_name.clone(),
842                                signature_protocol: Some(signature_protocol),
843                                rp_challenge_or_digest: rp_challenge,
844                                relying_party_name,
845                                brokered_rp_name: "".to_string(),
846                                interactions,
847                                initial_callback_url,
848                            }
849                        } else {
850                            return Err(SmartIdClientError::GenerateDeviceLinkException(
851                                "Initial callback URL is required for Web2App or App2App device links",
852                            ));
853                        }
854                    }
855                    DeviceLinkType::QR => {
856                        CrossDeviceLink {
857                            device_link_base,
858                            device_link_type,
859                            session_start_time,
860                            session_token,
861                            session_type: SessionType::auth,
862                            version: DEVICE_LINK_VERSION.to_string(),
863                            language_code: language_code.to_string(),
864                            session_secret,
865                            scheme_name: self.cfg.scheme_name.clone(),
866                            signature_protocol: Some(signature_protocol),
867                            rp_challenge_or_digest: rp_challenge,
868                            relying_party_name,
869                            brokered_rp_name: "".to_string(),
870                            interactions,
871                            initial_callback_url,
872                        }
873                    }
874                };
875                Ok(device_link.generate_device_link())
876            }
877            SessionConfig::SignatureDeviceLink {
878                session_secret,
879                session_token,
880                device_link_base,
881                relying_party_name,
882                initial_callback_url,
883                signature_protocol,
884                interactions,
885                digest,
886                session_start_time,
887                ..
888            } => {
889                let device_link = match device_link_type {
890                    DeviceLinkType::Web2App | DeviceLinkType::App2App => {
891                        if let Some(initial_callback_url) = initial_callback_url {
892                            SameDeviceLink {
893                                device_link_base,
894                                device_link_type,
895                                session_token,
896                                session_type: SessionType::sign,
897                                version: DEVICE_LINK_VERSION.to_string(),
898                                language_code: language_code.to_string(),
899                                session_secret,
900                                scheme_name: self.cfg.scheme_name.clone(),
901                                signature_protocol: Some(signature_protocol),
902                                rp_challenge_or_digest: digest,
903                                relying_party_name,
904                                brokered_rp_name: "".to_string(),
905                                interactions,
906                                initial_callback_url,
907                            }
908                        } else {
909                            return Err(SmartIdClientError::GenerateDeviceLinkException(
910                                "Initial callback URL is required for Web2App or App2App device links",
911                            ));
912                        }
913                    }
914                    DeviceLinkType::QR => {
915                        CrossDeviceLink {
916                            device_link_base,
917                            device_link_type,
918                            session_start_time,
919                            session_token,
920                            session_type: SessionType::sign,
921                            version: DEVICE_LINK_VERSION.to_string(),
922                            language_code: language_code.to_string(),
923                            session_secret,
924                            scheme_name: self.cfg.scheme_name.clone(),
925                            signature_protocol: Some(signature_protocol),
926                            rp_challenge_or_digest: digest,
927                            relying_party_name,
928                            brokered_rp_name: "".to_string(),
929                            interactions,
930                            initial_callback_url,
931                        }
932                    }
933                };
934                Ok(device_link.generate_device_link())
935            }
936            SessionConfig::CertificateChoiceDeviceLink {
937                session_token,
938                session_secret,
939                device_link_base,
940                relying_party_name,
941                initial_callback_url,
942                session_start_time,
943                ..
944            } => {
945                let device_link = match device_link_type {
946                    DeviceLinkType::Web2App | DeviceLinkType::App2App => {
947                        if let Some(initial_callback_url) = initial_callback_url {
948                            SameDeviceLink {
949                                device_link_base,
950                                device_link_type,
951                                session_token,
952                                session_type: SessionType::cert,
953                                version: DEVICE_LINK_VERSION.to_string(),
954                                language_code: language_code.to_string(),
955                                session_secret,
956                                scheme_name: self.cfg.scheme_name.clone(),
957                                signature_protocol: None,
958                                rp_challenge_or_digest: "".to_string(),
959                                relying_party_name,
960                                brokered_rp_name: "".to_string(),
961                                interactions: "".to_string(),
962                                initial_callback_url,
963                            }
964                        } else {
965                            return Err(SmartIdClientError::GenerateDeviceLinkException(
966                                "Initial callback URL is required for Web2App or App2App device links",
967                            ));
968                        }
969                    }
970                    DeviceLinkType::QR => {
971                        CrossDeviceLink {
972                            device_link_base,
973                            device_link_type,
974                            session_start_time,
975                            session_token,
976                            session_type: SessionType::cert,
977                            version: DEVICE_LINK_VERSION.to_string(),
978                            language_code: language_code.to_string(),
979                            session_secret,
980                            scheme_name: self.cfg.scheme_name.clone(),
981                            signature_protocol: None,
982                            rp_challenge_or_digest: "".to_string(),
983                            relying_party_name,
984                            brokered_rp_name: "".to_string(),
985                            interactions: "".to_string(),
986                            initial_callback_url,
987                        }
988                    }
989                };
990                Ok(device_link.generate_device_link())
991            }
992            _ => {
993                Err(SmartIdClientError::GenerateDeviceLinkException(
994                    "Can only generate device links for authentication or signature device link sessions",
995                ))
996            }
997
998        }
999    }
1000
1001    // endregion: Device Link
1002
1003    // region: Validation
1004
1005    /// Validates the session status and ensures that the session has completed successfully.
1006    ///
1007    /// If the session is running returns Ok(()).
1008    ///
1009    /// If the session is complete, this function performs several checks to validate the session status:
1010    /// - Ensures that the session result is present.
1011    /// - Validates the certificate chain and checks for expiration.
1012    /// - Verifies the identity of the authenticated person using the subject field or subjectAltName extension of the X.509 certificate.
1013    /// - Checks that the certificate level is high enough.
1014    /// - Validates the signature using the public key from the certificate.
1015    /// - Checks the session result is OK.
1016    ///
1017    /// # Arguments
1018    ///
1019    /// * `session_status` - The status of the session to be validated.
1020    /// * `session_config` - The configuration of the session.
1021    /// * `user_identity` - The subject of the certificate.
1022    ///
1023    /// # Returns
1024    ///
1025    /// A `Result` indicating success or failure. If the validation is successful, it returns `Ok(())`.
1026    /// If any validation step fails, it returns an appropriate `SmartIdClientError`.
1027    ///
1028    /// # Errors
1029    ///
1030    /// This function will return an error if:
1031    /// - The session result is missing.
1032    /// - The certificate is missing or invalid.
1033    /// - The signature is missing or invalid.
1034    /// - The session did not complete successfully.
1035    /// - The session result is not OK.
1036    /// - The provided identity does not match the certificate.
1037    fn validate_session_status(
1038        &self,
1039        session_status: SessionStatusResponse,
1040        session_config: SessionConfig,
1041    ) -> Result<()> {
1042        match session_status.result.clone() {
1043            Some(session_result) => {
1044                // Check the result is OK
1045                session_result.end_result.is_ok()?;
1046
1047                // Validate the certificate is present (Required for OK status)
1048                let cert = session_status
1049                    .cert
1050                    .clone()
1051                    .ok_or(SmartIdClientError::SessionResponseMissingCertificate)?;
1052
1053                // Verify the certificate chain
1054                self.verify_certificate(cert.value.clone())?;
1055
1056                // Check certificate level is high enough
1057                if &cert.certificate_level < session_config.requested_certificate_level() {
1058                    Err(
1059                        SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
1060                            "Certificate level is not high enough: {:?} < {:?}",
1061                            cert.certificate_level,
1062                            session_config.requested_certificate_level()
1063                        )),
1064                    )?
1065                };
1066
1067                // Validate signature is correct
1068                self.validate_signature(session_config, session_status, cert.clone())?;
1069
1070                // Check that the identity matches the certificate
1071                if let Some(user_identity) = self.get_user_identity()? {
1072                    user_identity.identity_matches_certificate(cert.value)?
1073                }
1074
1075                Ok(())
1076            }
1077            None => match session_status.state {
1078                SessionState::RUNNING => Ok(()),
1079                SessionState::COMPLETE => {
1080                    Err(SmartIdClientError::AuthenticationSessionCompletedWithoutResult)
1081                }
1082            },
1083        }
1084    }
1085
1086    /// Verifies a certificate chain using the root and intermediate certificates.
1087    ///
1088    /// This is done automatically when validating the session response.
1089    /// You only need to call this method if you want to validate a certificate that has not just been returned from a session.
1090    /// Or if you want to get the certificate chain (Example: For PAdES-L/LTA signatures)
1091    ///
1092    /// # Arguments
1093    /// * `cert` - The base64 der encoded certificate to be validated.
1094    /// # Returns
1095    /// A valid certificate chain.
1096    pub fn verify_certificate(&self, cert: String) -> Result<Vec<String>> {
1097        if self.cfg.is_demo() {
1098            let mut root_certs = demo_root_certificates();
1099            root_certs.extend(self.root_certificates.clone());
1100            let mut intermediate_certs = demo_intermediate_certificates();
1101            intermediate_certs.extend(self.intermediate_certificates.clone());
1102            verify_certificate(&cert, intermediate_certs, root_certs)
1103        } else {
1104            let mut root_certs = production_root_certificates();
1105            root_certs.extend(self.root_certificates.clone());
1106            let mut intermediate_certs = production_intermediate_certificates();
1107            intermediate_certs.extend(self.intermediate_certificates.clone());
1108            verify_certificate(
1109                &cert,
1110                production_root_certificates(),
1111                production_intermediate_certificates(),
1112            )
1113        }
1114    }
1115
1116    fn validate_signature(
1117        &self,
1118        session_config: SessionConfig,
1119        session_status_response: SessionStatusResponse,
1120        cert: SessionCertificate,
1121    ) -> Result<()> {
1122        match session_config {
1123            SessionConfig::AuthenticationDeviceLink {
1124                relying_party_name,
1125                initial_callback_url,
1126                interactions,
1127                rp_challenge,
1128                scheme_name,
1129                signature_protocol,
1130                signature_protocol_parameters,
1131                ..
1132            } => {
1133                let signature = session_status_response
1134                    .signature
1135                    .ok_or(SmartIdClientError::SessionResponseMissingSignature)?;
1136
1137                if signature.get_flow_type() == FlowType::App2App
1138                    || signature.get_flow_type() == FlowType::Web2App
1139                {
1140                    debug!("When the user goes to the callback a secret is appended to the URL, this is needed to verify the signature for these flows");
1141                    return Ok(());
1142                }
1143
1144                let used_interaction_type = session_status_response
1145                    .interaction_type_used
1146                    .ok_or(SmartIdClientError::SessionResponseMissingInteractionType)?;
1147
1148                signature.validate_acsp_v2(
1149                    scheme_name,
1150                    signature_protocol,
1151                    rp_challenge,
1152                    cert.value.clone(),
1153                    relying_party_name,
1154                    None,
1155                    interactions,
1156                    used_interaction_type,
1157                    initial_callback_url,
1158                    signature_protocol_parameters.get_hashing_algorithm(),
1159                )?;
1160
1161                // If no user identity is set, set it from the certificate
1162                // This happens during all anonymous sessions
1163                if self.get_user_identity()?.is_none() {
1164                    self.set_user_identity(UserIdentity::from_certificate(cert.value.clone())?)?
1165                };
1166
1167                Ok(())
1168            }
1169            SessionConfig::AuthenticationNotification {
1170                relying_party_name,
1171                interactions,
1172                rp_challenge,
1173                scheme_name,
1174                signature_protocol,
1175                signature_protocol_parameters,
1176                ..
1177            } => {
1178                let signature = session_status_response
1179                    .signature
1180                    .ok_or(SmartIdClientError::SessionResponseMissingSignature)?;
1181
1182                let used_interaction_type = session_status_response
1183                    .interaction_type_used
1184                    .ok_or(SmartIdClientError::SessionResponseMissingInteractionType)?;
1185
1186                signature.validate_acsp_v2(
1187                    scheme_name,
1188                    signature_protocol,
1189                    rp_challenge,
1190                    cert.value.clone(),
1191                    relying_party_name,
1192                    None,
1193                    interactions,
1194                    used_interaction_type,
1195                    None,
1196                    signature_protocol_parameters.get_hashing_algorithm(),
1197                )?;
1198
1199                // If no user identity is set, set it from the certificate
1200                // This happens during all anonymous sessions
1201                if self.get_user_identity()?.is_none() {
1202                    self.set_user_identity(UserIdentity::from_certificate(cert.value.clone())?)?
1203                };
1204
1205                Ok(())
1206            }
1207            SessionConfig::SignatureDeviceLink {
1208                digest,
1209                signature_protocol_parameters,
1210                ..
1211            } => {
1212                let signature = session_status_response
1213                    .signature
1214                    .ok_or(SmartIdClientError::SessionResponseMissingSignature)?;
1215
1216                let parameters = signature
1217                    .get_signature_algorithm_parameters()
1218                    .ok_or(SmartIdClientError::SessionResponseMissingSignature)?;
1219
1220                // TODO: CHeck this with prod
1221                if self.cfg.is_demo() {
1222                    return Ok(());
1223                }
1224
1225                signature.validate_raw_digest(
1226                    digest,
1227                    cert.value.clone(),
1228                    signature_protocol_parameters.get_hashing_algorithm(),
1229                    parameters.salt_length,
1230                )
1231            }
1232            SessionConfig::SignatureNotification {
1233                digest,
1234                signature_protocol_parameters,
1235                ..
1236            } => {
1237                let signature = session_status_response
1238                    .signature
1239                    .ok_or(SmartIdClientError::SessionResponseMissingSignature)?;
1240
1241                let parameters = signature
1242                    .get_signature_algorithm_parameters()
1243                    .ok_or(SmartIdClientError::SessionResponseMissingSignature)?;
1244
1245                // TODO: CHeck this with prod
1246                if self.cfg.is_demo() {
1247                    return Ok(());
1248                }
1249
1250                signature.validate_raw_digest(
1251                    digest,
1252                    cert.value.clone(),
1253                    signature_protocol_parameters.get_hashing_algorithm(),
1254                    parameters.salt_length,
1255                )
1256            }
1257            _ => {
1258                debug!("Signature validation only needed for device link authentication and signature sessions");
1259                Ok(())
1260            }
1261        }
1262    }
1263
1264    // endregion: Validation
1265
1266    // region: Utility functions
1267
1268    /// Resets the current session by clearing the session configuration and the authenticated user identity.
1269    ///
1270    /// If a different user wants to log in you must call this method to clear the current session identity.
1271    pub fn reset_session(&self) {
1272        self.clear_session();
1273        self.clear_user_identity();
1274    }
1275
1276    pub fn get_session(&self) -> Result<SessionConfig> {
1277        match self.session_config.lock() {
1278            Ok(guard) => match guard.clone() {
1279                Some(s) => Ok(s),
1280                None => {
1281                    debug!("Can't get session there is no running session");
1282                    Err(NoSessionException)
1283                }
1284            },
1285            Err(e) => {
1286                debug!("Failed to lock session config: {:?}", e);
1287                Err(SmartIdClientError::GetSessionException)
1288            }
1289        }
1290    }
1291
1292    fn set_session(&self, session: SessionConfig) -> Result<()> {
1293        match self.session_config.lock() {
1294            Ok(mut guard) => {
1295                *guard = Some(session);
1296                Ok(())
1297            }
1298            Err(e) => {
1299                debug!("Failed to lock session config: {:?}", e);
1300                Err(SmartIdClientError::SetSessionException)
1301            }
1302        }
1303    }
1304
1305    fn clear_session(&self) {
1306        match self.session_config.lock() {
1307            Ok(mut guard) => {
1308                *guard = None;
1309            }
1310            Err(e) => {
1311                debug!("Failed to lock session config: {:?}", e);
1312            }
1313        }
1314    }
1315
1316    pub fn get_user_identity(&self) -> Result<Option<UserIdentity>> {
1317        match self.authenticated_identity.lock() {
1318            Ok(guard) => match guard.clone() {
1319                Some(s) => Ok(Some(s)),
1320                None => Ok(None),
1321            },
1322            Err(e) => {
1323                debug!("Failed to lock authenticated identity: {:?}", e);
1324                Err(SmartIdClientError::GetUserIdentityException)
1325            }
1326        }
1327    }
1328
1329    fn set_user_identity(&self, user_identity: UserIdentity) -> Result<()> {
1330        match self.authenticated_identity.lock() {
1331            Ok(mut guard) => {
1332                *guard = Some(user_identity);
1333                Ok(())
1334            }
1335            Err(e) => {
1336                debug!("Failed to lock authenticated identity: {:?}", e);
1337                Err(SmartIdClientError::SetUserIdentityException)
1338            }
1339        }
1340    }
1341
1342    #[allow(dead_code)]
1343    fn clear_user_identity(&self) {
1344        match self.authenticated_identity.lock() {
1345            Ok(mut guard) => {
1346                *guard = None;
1347            }
1348            Err(e) => {
1349                debug!("Failed to lock authenticated identity: {:?}", e);
1350            }
1351        }
1352    }
1353
1354    // endregion: Utility functions
1355}