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 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 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 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 }; 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, 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, interaction_type_used: InteractionFlow,
164 initial_callback_url: &str,
165 flow_type: FlowType,
166 ) -> String {
167 let separator: &str = "|";
168
169 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#[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 rp_challenge: String,
204 signature_algorithm: SignatureAlgorithm,
205 signature_algorithm_parameters: SignatureRequestAlgorithmParameters,
206 },
207 #[serde(rename_all = "camelCase")]
208 RAW_DIGEST_SIGNATURE {
209 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 pub fn get_digest(&self) -> Option<String> {
238 match self {
239 SignatureProtocolParameters::RAW_DIGEST_SIGNATURE { digest, .. } => {
240 Some(digest.clone())
241 }
242 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 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#[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 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 #[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#[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 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 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 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 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 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#[cfg(test)]
568mod tests {
569 use super::*;
570 use base64::engine::general_purpose::STANDARD;
571
572 #[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]
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 = "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";
655
656 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}