Skip to main content

smart_id_rust_client/models/
signature.rs

1use crate::error::Result;
2use crate::error::SmartIdClientError;
3use crate::models::common::SchemeName;
4use crate::models::interaction::InteractionFlow;
5use base64::prelude::BASE64_STANDARD;
6use base64::Engine;
7use der::Decode;
8use rand::rngs::OsRng;
9use rand_chacha::rand_core::RngCore;
10use rsa;
11use rsa::traits::SignatureScheme;
12use rsa::{pkcs1, Pss};
13use serde::{Deserialize, Serialize};
14use sha2::{Digest, Sha256, Sha384, Sha512};
15use strum_macros::{AsRefStr, Display};
16use x509_parser::certificate::X509Certificate;
17use x509_parser::nom::AsBytes;
18use x509_parser::prelude::FromDer;
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, AsRefStr, Display)]
21#[allow(non_camel_case_types)]
22#[non_exhaustive]
23pub enum SignatureProtocol {
24    #[default]
25    ACSP_V2,
26    RAW_DIGEST_SIGNATURE,
27}
28
29impl SignatureAlgorithm {
30    pub(crate) fn validate_signature(
31        &self,
32        public_key: &[u8],
33        digest: &[u8],
34        signature: &[u8],
35        hashing_algorithm: HashingAlgorithm,
36        salt_length: u32,
37    ) -> Result<()> {
38        // region Workaround for rsa crate limitations
39
40        // First we decode using the pkcs::RsaPublicKey because the rsa::RsaPublicKey::from_der_x functions do not support modulus sizes over 4096 bits.
41        // After, we create a rsa::RsaPublicKey with the rsa::RsaPublicKey::new_with_max_size function, which allows us to create a public key with any modulus size.
42        // This is a workaround for the rsa crate's limitations.
43        let pkcs1_public_key = pkcs1::RsaPublicKey::from_der(public_key).map_err(|e| {
44            SmartIdClientError::InvalidResponseSignature(format!(
45                "Failed to parse public key: {}",
46                e
47            ))
48        })?;
49
50        let modulus = num_bigint_dig::BigUint::from_bytes_be(pkcs1_public_key.modulus.as_bytes());
51        let public_exponent =
52            num_bigint_dig::BigUint::from_bytes_be(pkcs1_public_key.public_exponent.as_bytes());
53
54        // endregion Workaround for rsa crate limitations
55
56        let rsa_public_key = rsa::RsaPublicKey::new_with_max_size(modulus, public_exponent, 10000)
57            .map_err(|e| {
58                SmartIdClientError::InvalidResponseSignature(format!(
59                    "Failed to create RSA public key: {}",
60                    e
61                ))
62            })?;
63
64        // Create PSS verifier with SHA-256
65        let verifier = match hashing_algorithm {
66            HashingAlgorithm::sha_256 => Pss::new_with_salt::<Sha256>(salt_length as usize),
67            HashingAlgorithm::sha_384 => Pss::new_with_salt::<Sha384>(salt_length as usize),
68            HashingAlgorithm::sha_512 => Pss::new_with_salt::<Sha512>(salt_length as usize),
69            HashingAlgorithm::sha3_256 => {
70                Pss::new_with_salt::<sha3::Sha3_256>(salt_length as usize)
71            }
72            HashingAlgorithm::sha3_384 => {
73                Pss::new_with_salt::<sha3::Sha3_384>(salt_length as usize)
74            }
75            HashingAlgorithm::sha3_512 => {
76                Pss::new_with_salt::<sha3::Sha3_512>(salt_length as usize)
77            }
78        }; // 32-byte salt
79
80        // Verify signature
81        match verifier.verify(&rsa_public_key, digest, signature) {
82            Ok(_) => Ok(()),
83            Err(e) => Err(SmartIdClientError::InvalidResponseSignature(format!(
84                "Failed to verify signature: {}",
85                e
86            ))),
87        }
88    }
89
90    pub(crate) fn build_acsp_v2_digest(
91        scheme_name: SchemeName,
92        signature_protocol: SignatureProtocol,
93        server_random: &str,
94        rp_challenge: &str,
95        user_challenge: &str,
96        relying_party_name_base64: &str,
97        brokered_rp_name_base64: &str,
98        interactions_base64: &str, // Base64 encoded interactions
99        interaction_type_used: InteractionFlow,
100        initial_callback_url: &str,
101        flow_type: FlowType,
102        hash_algorithm: HashingAlgorithm,
103    ) -> Vec<u8> {
104        let acsp_v2_payload = SignatureAlgorithm::build_acsp_v2_payload(
105            scheme_name,
106            signature_protocol,
107            server_random,
108            rp_challenge,
109            user_challenge,
110            relying_party_name_base64,
111            brokered_rp_name_base64,
112            interactions_base64,
113            interaction_type_used,
114            initial_callback_url,
115            flow_type,
116        );
117
118        let digest = match hash_algorithm {
119            HashingAlgorithm::sha_256 => {
120                let mut hasher = Sha256::new();
121                hasher.update(acsp_v2_payload.as_bytes());
122                hasher.finalize().to_vec()
123            }
124            HashingAlgorithm::sha_384 => {
125                let mut hasher = Sha384::new();
126                hasher.update(acsp_v2_payload.as_bytes());
127                hasher.finalize().to_vec()
128            }
129            HashingAlgorithm::sha_512 => {
130                let mut hasher = Sha512::new();
131                hasher.update(acsp_v2_payload.as_bytes());
132                hasher.finalize().to_vec()
133            }
134            HashingAlgorithm::sha3_256 => {
135                let mut hasher = sha3::Sha3_256::new();
136                hasher.update(acsp_v2_payload.as_bytes());
137                hasher.finalize().to_vec()
138            }
139            HashingAlgorithm::sha3_384 => {
140                let mut hasher = sha3::Sha3_384::new();
141                hasher.update(acsp_v2_payload.as_bytes());
142                hasher.finalize().to_vec()
143            }
144            HashingAlgorithm::sha3_512 => {
145                let mut hasher = sha3::Sha3_512::new();
146                hasher.update(acsp_v2_payload.as_bytes());
147                hasher.finalize().to_vec()
148            }
149        };
150
151        digest.to_vec()
152    }
153
154    pub(crate) fn build_acsp_v2_payload(
155        scheme_name: SchemeName,
156        signature_protocol: SignatureProtocol,
157        server_random: &str,
158        rp_challenge: &str,
159        user_challenge: &str,
160        relying_party_name_base64: &str,
161        brokered_rp_name_base64: &str,
162        interactions_base64: &str, // Base64 encoded interactions
163        interaction_type_used: InteractionFlow,
164        initial_callback_url: &str,
165        flow_type: FlowType,
166    ) -> String {
167        let separator: &str = "|";
168
169        // We take the base64 encoded interactions, hash it, and then encode it again
170        let interactions_hash = Sha256::digest(interactions_base64.as_bytes());
171        let interactions_base64 =
172            &base64::engine::general_purpose::STANDARD.encode(interactions_hash);
173
174        let acsp_v2_payload_parts: [&str; 11] = [
175            scheme_name.as_ref(),
176            signature_protocol.as_ref(),
177            server_random,
178            rp_challenge,
179            user_challenge,
180            relying_party_name_base64,
181            brokered_rp_name_base64,
182            interactions_base64,
183            interaction_type_used.as_ref(),
184            initial_callback_url,
185            flow_type.as_ref(),
186        ];
187
188        let acsp_v2_payload: String = acsp_v2_payload_parts.join(separator);
189
190        acsp_v2_payload
191    }
192}
193
194// Region SignatureRequestParameters
195#[allow(non_camel_case_types)]
196#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
197#[serde(untagged)]
198#[non_exhaustive]
199pub enum SignatureProtocolParameters {
200    #[serde(rename_all = "camelCase")]
201    ACSP_V2 {
202        // A random value which is calculated by generating random bits with size in the range of 32 bytes …64 bytes and applying Base64 encoding (according to rfc4648).
203        rp_challenge: String,
204        signature_algorithm: SignatureAlgorithm,
205        signature_algorithm_parameters: SignatureRequestAlgorithmParameters,
206    },
207    #[serde(rename_all = "camelCase")]
208    RAW_DIGEST_SIGNATURE {
209        // Base64 encoded digest to be signed (RFC 4648).
210        digest: String,
211        signature_algorithm: SignatureAlgorithm,
212        signature_algorithm_parameters: SignatureRequestAlgorithmParameters,
213    },
214}
215
216impl SignatureProtocolParameters {
217    pub fn new_acsp_v2(
218        signature_algorithm: SignatureAlgorithm,
219        hash_algorithm: HashingAlgorithm,
220    ) -> SignatureProtocolParameters {
221        SignatureProtocolParameters::ACSP_V2 {
222            rp_challenge: Self::generate_rp_challenge(),
223            signature_algorithm,
224            signature_algorithm_parameters: SignatureRequestAlgorithmParameters { hash_algorithm },
225        }
226    }
227
228    pub(crate) fn get_rp_challenge(&self) -> Option<String> {
229        match self {
230            SignatureProtocolParameters::ACSP_V2 { rp_challenge, .. } => Some(rp_challenge.clone()),
231            _ => None,
232        }
233    }
234
235    // Get the digest from the request parameters this.
236    // This is only possible for RAW_DIGEST_SIGNATURE requests, as ACSP_V2 requests require a server random from the response to build the digest (auth)
237    pub fn get_digest(&self) -> Option<String> {
238        match self {
239            SignatureProtocolParameters::RAW_DIGEST_SIGNATURE { digest, .. } => {
240                Some(digest.clone())
241            }
242            // ACSP_V2 requests require a server random from the response to build the digest (auth)
243            // Use SessionConfig::get_digest if you need to build the digest for ACSP_V2 requests.
244            SignatureProtocolParameters::ACSP_V2 { .. } => None,
245        }
246    }
247
248    pub(crate) fn get_hashing_algorithm(&self) -> HashingAlgorithm {
249        match self {
250            SignatureProtocolParameters::ACSP_V2 {
251                signature_algorithm_parameters,
252                ..
253            } => signature_algorithm_parameters.hash_algorithm.clone(),
254            SignatureProtocolParameters::RAW_DIGEST_SIGNATURE {
255                signature_algorithm_parameters,
256                ..
257            } => signature_algorithm_parameters.hash_algorithm.clone(),
258        }
259    }
260
261    // Generates random bits with size in the range of 32 bytes …64 bytes and applies Base64 encoding.
262    fn generate_rp_challenge() -> String {
263        let mut rp_challenge_bytes: [u8; 64] = [0u8; 64];
264        OsRng.fill_bytes(&mut rp_challenge_bytes);
265        base64::engine::general_purpose::STANDARD.encode(rp_challenge_bytes)
266    }
267}
268
269// endregion
270
271// Region SignatureResponse
272
273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
274#[allow(non_camel_case_types)]
275#[serde(untagged)]
276#[non_exhaustive]
277pub enum ResponseSignature {
278    #[serde(rename_all = "camelCase")]
279    ACSP_V2 {
280        value: String,
281        // A random value of 24 or more characters from Base64 alphabet, which is generated at RP API service side.
282        // There are not any guarantees that the returned value length is the same in each call of the RP API.
283        server_random: String,
284        user_challenge: String,
285        flow_type: FlowType,
286        signature_algorithm: SignatureAlgorithm,
287        signature_algorithm_parameters: Option<SignatureResponseAlgorithmParameters>,
288    },
289
290    #[serde(rename_all = "camelCase")]
291    RAW_DIGEST_SIGNATURE {
292        value: String,
293        signature_algorithm: SignatureAlgorithm,
294        signature_algorithm_parameters: Option<SignatureResponseAlgorithmParameters>,
295        flow_type: FlowType,
296    },
297
298    // The certificate choice returns this mostly empty variant, it does not actually contain a signature.
299    #[serde(rename_all = "camelCase")]
300    CERTIFICATE_CHOICE_NO_SIGNATURE { flow_type: FlowType },
301}
302
303#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
304#[serde(rename_all = "camelCase")]
305pub struct SignatureResponseAlgorithmParameters {
306    pub hash_algorithm: HashingAlgorithm,
307    pub mask_gen_algorithm: MaskGenAlgorithm,
308    pub salt_length: u32,
309    pub trailer_field: String,
310}
311
312#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
313#[serde(rename_all = "camelCase")]
314pub struct SignatureRequestAlgorithmParameters {
315    pub hash_algorithm: HashingAlgorithm,
316}
317#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
318#[serde(rename_all = "camelCase")]
319pub struct MaskGenAlgorithm {
320    pub algorithm: MaskGenAlgorithmType,
321    pub parameters: MaskGenAlgorithmParameters,
322}
323
324#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
325#[serde(rename_all = "kebab-case")]
326#[allow(non_camel_case_types)]
327pub enum MaskGenAlgorithmType {
328    id_mgf1,
329}
330
331#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
332#[serde(rename_all = "camelCase")]
333pub struct MaskGenAlgorithmParameters {
334    pub hash_algorithm: HashingAlgorithm,
335}
336
337// Based on the list here https://github.com/SK-EID/smart-id-java-client/blob/bc6157305aea08186e56e6484390ffa8065f5b65/src/main/java/ee/sk/smartid/HashAlgorithm.java#L1-L102
338#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
339#[serde(rename_all = "SCREAMING-KEBAB-CASE")]
340#[allow(non_camel_case_types)]
341pub enum HashingAlgorithm {
342    sha_256,
343    sha_384,
344    sha_512,
345    sha3_256,
346    sha3_384,
347    sha3_512,
348}
349
350#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, AsRefStr)]
351pub enum FlowType {
352    QR,
353    App2App,
354    Web2App,
355    Notification,
356}
357
358#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
359#[serde(rename_all = "kebab-case")]
360pub enum SignatureAlgorithm {
361    RsassaPss,
362}
363
364impl ResponseSignature {
365    pub(crate) fn validate_raw_digest(
366        &self,
367        digest: String,
368        cert: String,
369        hashing_algorithm: HashingAlgorithm,
370        salt_length: u32,
371    ) -> Result<()> {
372        match self {
373            ResponseSignature::RAW_DIGEST_SIGNATURE {
374                value,
375                signature_algorithm,
376                ..
377            } => {
378                let decoded_cert = BASE64_STANDARD.decode(&cert).map_err(|e| {
379                    SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
380                        "Could not decode base64 certificate: {:?}",
381                        e
382                    ))
383                })?;
384
385                let (_, parsed_cert) =
386                    X509Certificate::from_der(decoded_cert.as_slice()).map_err(|e| {
387                        SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
388                            "Failed to parse certificate: {:?}",
389                            e
390                        ))
391                    })?;
392
393                let public_key = parsed_cert.public_key().clone().subject_public_key;
394
395                let digest = BASE64_STANDARD
396                    .decode(digest)
397                    .expect("Failed to decode base64 digest");
398
399                let signature = BASE64_STANDARD
400                    .decode(value)
401                    .expect("Failed to decode base64 signature");
402
403                signature_algorithm.validate_signature(
404                    public_key.as_ref(),
405                    digest.as_slice(),
406                    signature.as_slice(),
407                    hashing_algorithm,
408                    salt_length,
409                )
410            }
411            _ => Err(SmartIdClientError::InvalidSignatureProtocal(
412                "Expected RAW_DIGEST_SIGNATURE signature protocol",
413            )),
414        }
415    }
416
417    pub(crate) fn validate_acsp_v2(
418        &self,
419        scheme_name: SchemeName,
420        signature_protocol: SignatureProtocol,
421        rp_challenge: String,
422        cert: String,
423        relying_party_name: String,
424        brokered_rp_name: Option<String>,
425        interactions: String,
426        interaction_type_used: InteractionFlow,
427        initial_callback_url: Option<String>,
428        hashing_algorithm: HashingAlgorithm,
429    ) -> Result<()> {
430        match self {
431            ResponseSignature::ACSP_V2 {
432                value,
433                server_random,
434                user_challenge,
435                flow_type,
436                signature_algorithm,
437                signature_algorithm_parameters,
438            } => {
439                // server_random validation as specified in the Smart-ID API documentation
440                if server_random.len() < 24 {
441                    return Err(SmartIdClientError::InvalidResponseSignature(
442                        "server_random length is less than 24 characters".to_string(),
443                    ));
444                }
445
446                if BASE64_STANDARD.decode(server_random).is_err() {
447                    return Err(SmartIdClientError::InvalidResponseSignature(
448                        "server_random contains invalid Base64 characters".to_string(),
449                    ));
450                }
451
452                // println!("cert: {}", cert);
453                // signature validation
454                let decoded_cert = BASE64_STANDARD.decode(&cert).map_err(|e| {
455                    SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
456                        "Could not decode base64 certificate: {:?}",
457                        e
458                    ))
459                })?;
460
461                let (_, parsed_cert) =
462                    X509Certificate::from_der(decoded_cert.as_slice()).map_err(|e| {
463                        SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
464                            "Failed to parse certificate: {:?}",
465                            e
466                        ))
467                    })?;
468
469                let public_key = parsed_cert.public_key().clone().subject_public_key.data;
470
471                let digest = SignatureAlgorithm::build_acsp_v2_digest(
472                    scheme_name,
473                    signature_protocol,
474                    server_random,
475                    &rp_challenge,
476                    user_challenge,
477                    &BASE64_STANDARD.encode(relying_party_name),
478                    &BASE64_STANDARD.encode(brokered_rp_name.unwrap_or("".to_string())),
479                    &interactions,
480                    interaction_type_used,
481                    &initial_callback_url.unwrap_or("".to_string()),
482                    flow_type.clone(),
483                    hashing_algorithm,
484                );
485
486                let signature = BASE64_STANDARD
487                    .decode(value)
488                    .expect("Failed to decode base64 signature");
489
490                let signature_algorithm_parameters = signature_algorithm_parameters.clone().ok_or(
491                    SmartIdClientError::InvalidResponseSignature(
492                        "Missing signature algorithm parameters".to_string(),
493                    ),
494                )?;
495
496                signature_algorithm.validate_signature(
497                    public_key.as_ref(),
498                    digest.as_bytes(),
499                    signature.as_bytes(),
500                    signature_algorithm_parameters.hash_algorithm,
501                    signature_algorithm_parameters.salt_length,
502                )
503            }
504            _ => Err(SmartIdClientError::InvalidSignatureProtocal(
505                "Expected ACSP_V2 signature protocol",
506            )),
507        }
508    }
509
510    pub fn get_value(&self) -> String {
511        match self {
512            ResponseSignature::ACSP_V2 { value, .. } => value.clone(),
513            ResponseSignature::RAW_DIGEST_SIGNATURE { value, .. } => value.clone(),
514            ResponseSignature::CERTIFICATE_CHOICE_NO_SIGNATURE { .. } => {
515                // This variant does not contain a signature value, so we return an empty string.
516                String::new()
517            }
518        }
519    }
520
521    pub fn get_signature_algorithm(&self) -> SignatureAlgorithm {
522        match self {
523            ResponseSignature::ACSP_V2 {
524                signature_algorithm,
525                ..
526            } => signature_algorithm.clone(),
527            ResponseSignature::RAW_DIGEST_SIGNATURE {
528                signature_algorithm,
529                ..
530            } => signature_algorithm.clone(),
531            ResponseSignature::CERTIFICATE_CHOICE_NO_SIGNATURE { .. } => {
532                // This variant does not contain a signature algorithm, so we return a default value.
533                SignatureAlgorithm::RsassaPss
534            }
535        }
536    }
537
538    pub fn get_signature_algorithm_parameters(
539        &self,
540    ) -> Option<SignatureResponseAlgorithmParameters> {
541        match self {
542            ResponseSignature::ACSP_V2 {
543                signature_algorithm_parameters,
544                ..
545            } => signature_algorithm_parameters.clone(),
546            ResponseSignature::RAW_DIGEST_SIGNATURE {
547                signature_algorithm_parameters,
548                ..
549            } => signature_algorithm_parameters.clone(),
550            ResponseSignature::CERTIFICATE_CHOICE_NO_SIGNATURE { .. } => {
551                // This variant does not contain signature algorithm parameters, so we return None.
552                None
553            }
554        }
555    }
556
557    pub fn get_flow_type(&self) -> FlowType {
558        match self {
559            ResponseSignature::ACSP_V2 { flow_type, .. } => flow_type.clone(),
560            ResponseSignature::RAW_DIGEST_SIGNATURE { flow_type, .. } => flow_type.clone(),
561            ResponseSignature::CERTIFICATE_CHOICE_NO_SIGNATURE { flow_type } => flow_type.clone(),
562        }
563    }
564}
565// endregion
566
567#[cfg(test)]
568mod tests {
569    use super::*;
570    use base64::engine::general_purpose::STANDARD;
571
572    // Based on documentation https://sk-eid.github.io/smart-id-documentation/rp-api/signature_protocols.html#acsp_v2_digest_calculation
573    #[test]
574    fn test_create_acsp_v2_digest_device_link_web2app_payload_and_hashing() {
575        let scheme_name = SchemeName::smart_id;
576        let signature_protocol = SignatureProtocol::ACSP_V2;
577        let server_random = "MTlop6EXCrQ6FOErcKjxUhbV";
578        let rp_challenge = "GYS+yoah6emAcVDNIajwSs6UB/M95XrDxMzXBUkwQJ9YFDipXXzGpPc7raWcuc2+TEoRc7WvIZ/7dU/iRXenYg==";
579        let user_challenge = "GnsWXXEjTCKR89fj9uo5u5ReBZ9JR7_pezLAI5jMS00";
580        let relying_party_name_base_64 = "QmFuayAxMjM=";
581        let brokered_rp_name_base_64 = "RXhhbXBsZSBSUA==";
582        let interactions_base_64 = "W3sidHlwZSI6ImNvbmZpcm1hdGlvbk1lc3NhZ2UiLCJkaXNwbGF5VGV4dDIwMCI6IkxvbmdlciBkZXNjcmlwdGlvbiBvZiB0aGUgdHJhbnNhY3Rpb24gY29udGV4dCJ9LHsidHlwZSI6ImRpc3BsYXlUZXh0QW5kUElOIiwiZGlzcGxheVRleHQ2MCI6IlNob3J0IGRlc2NyaXB0aW9uIG9mIHRoZSB0cmFuc2FjdGlvbiBjb250ZXh0In1d";
583        let interaction_type_used = InteractionFlow::ConfirmationMessage;
584        let flow_type = FlowType::Web2App;
585        let initial_callback_url =
586            "https://rp.example.com/callback-url?value=RrKjjT4aggzu27YBddX1bQ";
587
588        let digest = SignatureAlgorithm::build_acsp_v2_digest(
589            scheme_name,
590            signature_protocol,
591            server_random,
592            &rp_challenge,
593            user_challenge,
594            relying_party_name_base_64,
595            &brokered_rp_name_base_64,
596            &interactions_base_64,
597            interaction_type_used,
598            &initial_callback_url,
599            flow_type.clone(),
600            HashingAlgorithm::sha_512,
601        );
602
603        let digest_base64 = STANDARD.encode(digest);
604
605        assert_eq!(digest_base64, "ForpWzIGtGPvivuCWiDXv1U01qBnaf7ob2wjGEtRKpYO/atx7707vsG3o2jdTuezTHJvUvM2V9TKEAIhor+nng==");
606    }
607
608    // Test based on demo interaction with api
609    #[test]
610    #[ignore]
611    fn test_create_acsp_v2_digest_notification_verify() {
612        let scheme_name = SchemeName::smart_id_demo;
613        let signature_protocol = SignatureProtocol::ACSP_V2;
614        let server_random = "DnrScjtnPA0foJmEF1D6jSXe";
615        let rp_challenge = "gTcw8L1/UHy2iCVqgMx5XSwcs1PAlWjFsJxlSSYPbI031o6FekaFm/BAaORnMEl3mJ1AsBj91Vod+GsYPaIhdw==";
616        let user_challenge = "talMTYBAd1qaHph7dR_YsO4LlzqZ-0f5HxDPqSN2Tuo";
617        let relying_party_name_base_64 = STANDARD.encode("RELYING_PARTY_NAME");
618        let brokered_rp_name_base_64 = "";
619        let interactions_base_64 = "W3sidHlwZSI6ImNvbmZpcm1hdGlvbk1lc3NhZ2UiLCJkaXNwbGF5VGV4dDIwMCI6IkxvbmdlciBkZXNjcmlwdGlvbiBvZiB0aGUgdHJhbnNhY3Rpb24gY29udGV4dCJ9LHsidHlwZSI6ImRpc3BsYXlUZXh0QW5kUElOIiwiZGlzcGxheVRleHQ2MCI6IlNob3J0IGRlc2NyaXB0aW9uIG9mIHRoZSB0cmFuc2FjdGlvbiBjb250ZXh0In1d";
620        let interaction_type_used = InteractionFlow::ConfirmationMessage;
621        let flow_type = FlowType::QR;
622        let initial_callback_url = "";
623        let hashing_algorithm = HashingAlgorithm::sha_512;
624        let signature_value = "a3U2Yq6Ffk7oWrCjHkglgHy4PX0yk9+9jgX94hT9NvHK8BgSuGsSsYVaWaBcAtSI/kddIbOW5RzII/NDaqCC5nbHDs86G0KRiFKE4o/MzsQdLGHOncSrdQlrvOdQZriQUHDlCza54pJaX+5wqx63NBGFIC4aSo0U21qIHVoj+CLtdeVIB21btgTy3JZtLtbGmsDoEFVZ5+kBinc+L8JoJqo48C8H4w2JjJf+uhZ80WYvspTXTU2C8NyYBEbqUksLp4/SsNu6jcCNG4hlI5I2es9mQ3FQtLnVtPhPKovw/ZtFIBcy5gMet3FH2QJAjQAZ5XwTIwgvI7wqTBBDo1g+KWwUX6XO4wAevqW8hgXZ5HbO+mtQ98YHKeNZuCkHaB2zSeRsimNiKkXkN+uWUa5lVUawbUzJvO9UX1RGnRfUQNxrtR/Kt7OHCSAUjwQegncQAk4+akss/HL7sJB5XG+HIk0dwX+lV7x0heE+k9kDFW6oUltUZZT1yxEqlmmaaHDVTukjJt1eRgevZWi9ahnu382crHXL5DsFP1vBGrg5qDP8NbkmBq8rX5Z0N4D8/wLi14f0aTiaAlVPbFwLjzD5a540N+NN/596+PMH8BLwT1x4PAWtrMDr6pw3zTBXHlk7p8XXUJtsMofuVwJ9w7X6IC5U6+DfUUC573+scCez6qSx/3w4uEXYgRSdQFPR2N12MHu9Hpob4KYUzEsSRlcLMAXBBzP9zz1z3w6uTQanjJ+S6IEFo3hwYfP08BAwO3pUk+3Bo1Y3Ir1bakZ98iwmo+8L9G3EtZN4JivLy8Rl9Rwbjtvfadtvg2ZeikBmSNj2Bj6FIKVNYwm5F1r2XnpLoGBADLfJ+pbZvEcShRkHyJywESesElLtqsai/Jb/VVioOZ8czDUVdZccCwayo74Gfu2vtI6jLj1W+eeS/j7xa8UJScwiFjNbRMr+KxQWwnoLjb02ZhB/pkG5AP9m79XEJ7fMTIG89AZ4Zu2yv2P1nn5uiF7AoS37uKMFrFo/wdT5";
625        let signature_algorithm_parameters = SignatureResponseAlgorithmParameters {
626            hash_algorithm: HashingAlgorithm::sha_512,
627            mask_gen_algorithm: MaskGenAlgorithm {
628                algorithm: MaskGenAlgorithmType::id_mgf1,
629                parameters: MaskGenAlgorithmParameters {
630                    hash_algorithm: HashingAlgorithm::sha_512,
631                },
632            },
633            salt_length: 64,
634            trailer_field: "0xbc".to_string(),
635        };
636
637        let digest = SignatureAlgorithm::build_acsp_v2_digest(
638            scheme_name.clone(),
639            signature_protocol.clone(),
640            server_random,
641            &rp_challenge,
642            user_challenge,
643            &relying_party_name_base_64,
644            &brokered_rp_name_base_64,
645            &interactions_base_64,
646            interaction_type_used.clone(),
647            &initial_callback_url,
648            flow_type.clone(),
649            hashing_algorithm.clone(),
650        );
651
652        println!("Digest: {:?}", digest);
653
654        let signing_cert = "MIIGjTCCBhOgAwIBAgIQYzybyxQYpGgacL+sOF2CmTAKBggqhkjOPQQDAzBxMSwwKgYDVQQDDCNURVNUIG9mIFNLIElEIFNvbHV0aW9ucyBFSUQtUSAyMDI0RTEXMBUGA1UEYQwOTlRSRUUtMTA3NDcwMTMxGzAZBgNVBAoMElNLIElEIFNvbHV0aW9ucyBBUzELMAkGA1UEBhMCRUUwHhcNMjUwMTIwMTAzNDQ0WhcNMjgwMTIwMTAzNDQzWjBnMQswCQYDVQQGEwJCRTEYMBYGA1UEAwwPREUgTCdBUkFHTyxKT0VZMRMwEQYDVQQEDApERSBMJ0FSQUdPMQ0wCwYDVQQqDARKT0VZMRowGAYDVQQFExFQTk9CRS05ODAyMTI3MzExMTCCAyEwDQYJKoZIhvcNAQEBBQADggMOADCCAwkCggMAeQBuKgMynZGaWNIkNua/VCJayr49UpMhmcB7JvCJualAw4vpC6pje7uqHCrO8u8S6HcFyoPVYCdIkzctDuaqhQ3AQ1KjIjQYjn4gICscn24afX5nH1+CGm4kj7txGGjtKRfMelAh+mQ0nhBVjfXFn3Lh2EeUE0RJ81k1yUA2QCBNyh2/Uh6fwcyIgiW8Jt0CGSk9+S7J81+h1kb4/LycdIqlKu8blMdXwQ+DezPlBTP9ixIKMVfHUpznqgX3gp7scT8SR97ZdRMC4SwxXFuz93DLdSS17ITGdN5ZbLforqmJoeHfD1z8eo4O+UW50yBK5NafZoRjL36WlOtMNK0eWmYF7vEVxIT6n4MZFFoBmo3NQ7V1kTj6BmvMZB2mhaDUI6G+MDmcL5HG9LLtP6jPstgV4LlyPIyGnTmoeXa0miZK14Cd7ggjXnKPNhuJlZNDZ6IPO1y/Bfud4rC9dXHy+F/3EULVAwfLe9OoaqG6/TCdEnAQbjpdxj2hD1rGI3pz56wrUA7fCKsOLYTGt2qhUCTco38pdXeYVUfsZHAIXyLE5D33hEIN28Ia4ngwenWIXu3g96uTSvBP1LwHvZLV7hDBQWoHqKAKOvHSeLsaH+z4o4fQKIUee2en3BgqZFsc3I4VJt19frY7lDTNmaDqDon7+ldLXylosr0DzHvjwCsrXXC3ujMQjc227enpWbcB67nqqyYSoBgcTB9KQ/kT86CS8uEI47Fjd+u8rSYtXp066Liro+hO1QLW+a8nNgvhE+pOapQZeopfkMMZVks76SRE7IrHMVCzGIA/OcmEggjTS/F+gM6NqA3BnnBgYAJnEd/Ru8Rv0YjNiZ/KkgYpUaPPTgyLM02OAN/TdUSgTtnLykhbgoSZOfmrdBmOzvpzPAB7O38ixyfbVnGAELalA7ZPoZYIy5l0Qaw8qiOIcJZsagqE99eRThme5qDic1orEbio6VwLFqzoITMNwmIGsaO35ZZaqzsYtDcPo2Oxm2V5urJARt+pNBbKsJHhtzrTAgMBAAGjggHLMIIBxzAJBgNVHRMEAjAAMB8GA1UdIwQYMBaAFLAkFxmI42b4zShYZXtNFNiSZk9rMHAGCCsGAQUFBwEBBGQwYjAzBggrBgEFBQcwAoYnaHR0cDovL2Muc2suZWUvVEVTVF9FSUQtUV8yMDI0RS5kZXIuY3J0MCsGCCsGAQUFBzABhh9odHRwOi8vYWlhLmRlbW8uc2suZWUvZWlkcTIwMjRlMDAGA1UdEQQpMCekJTAjMSEwHwYDVQQDDBhQTk9CRS05ODAyMTI3MzExMS1XSlM5LVEweAYDVR0gBHEwbzBjBgkrBgEEAc4fEQIwVjBUBggrBgEFBQcCARZIaHR0cHM6Ly93d3cuc2tpZHNvbHV0aW9ucy5ldS9yZXNvdXJjZXMvY2VydGlmaWNhdGlvbi1wcmFjdGljZS1zdGF0ZW1lbnQvMAgGBgQAj3oBAjAWBgNVHSUEDzANBgsrBgEEAYPmYgUHADA0BgNVHR8ELTArMCmgJ6AlhiNodHRwOi8vYy5zay5lZS90ZXN0X2VpZC1xXzIwMjRlLmNybDAdBgNVHQ4EFgQUTQW2XZCVfA5ry8zkUnNeJx8YCicwDgYDVR0PAQH/BAQDAgeAMAoGCCqGSM49BAMDA2gAMGUCMB1al3sALnREaeupWA+z1CrwxD1BkFwa27kMI0mQcgonayQlgUhza/ob84GG2+XmDQIxAM5BFuai6p5QLbre+UKGJmRAyl2m3M0OubyfrTkAXh1ClCdhav/jYeoVMIpUZHrAmQ==";
655
656        // signature validation
657        let decoded_cert = BASE64_STANDARD
658            .decode(&signing_cert)
659            .map_err(|e| {
660                SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
661                    "Could not decode base64 certificate: {:?}",
662                    e
663                ))
664            })
665            .unwrap();
666
667        let (_, parsed_cert) = X509Certificate::from_der(decoded_cert.as_slice())
668            .map_err(|e| {
669                SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
670                    "Failed to parse certificate: {:?}",
671                    e
672                ))
673            })
674            .unwrap();
675
676        let public_key = parsed_cert.public_key().clone().subject_public_key.data;
677
678        let signature = STANDARD
679            .decode(signature_value)
680            .expect("Failed to decode base64 signature");
681
682        let result_digest = SignatureAlgorithm::RsassaPss.validate_signature(
683            public_key.as_ref(),
684            digest.as_bytes(),
685            signature.as_bytes(),
686            signature_algorithm_parameters.hash_algorithm.clone(),
687            signature_algorithm_parameters.salt_length,
688        );
689        println!("{:?}", result_digest);
690
691        println!("Result Digest: {:?}", result_digest);
692
693        assert!(result_digest.is_ok());
694    }
695
696    #[test]
697    fn test_new_acsp_v2() {
698        let signature_algorithm = SignatureAlgorithm::RsassaPss;
699        let params = SignatureProtocolParameters::new_acsp_v2(
700            signature_algorithm.clone(),
701            HashingAlgorithm::sha_256,
702        );
703
704        if let SignatureProtocolParameters::ACSP_V2 {
705            rp_challenge,
706            signature_algorithm: alg,
707            signature_algorithm_parameters: _,
708        } = params
709        {
710            assert!(!rp_challenge.is_empty(), "rp challenge should not be empty");
711            assert_eq!(alg, signature_algorithm, "Signature algorithm should match");
712        } else {
713            panic!("Expected SignatureRequestParameters::ACSP_V2 variant");
714        }
715    }
716
717    #[test]
718    fn test_get_rp_challenge() {
719        let signature_algorithm = SignatureAlgorithm::RsassaPss;
720        let params = SignatureProtocolParameters::new_acsp_v2(
721            signature_algorithm,
722            HashingAlgorithm::sha_256,
723        );
724
725        let rp_challenge = params.get_rp_challenge();
726        assert!(rp_challenge.is_some(), "rp challenge should be Some");
727        assert!(
728            !rp_challenge.unwrap().is_empty(),
729            "rp challenge should not be empty"
730        );
731    }
732}