1use crate::security::{
2 DigitalSignature, SignatureType, SignatureValidity, ValidationResult, ValidationStatus,
3};
4
5#[derive(Debug, Clone, Default)]
6pub struct EtsiValidationOptions {
7 pub require_dss_for_pades: bool,
8}
9
10pub fn validate_etsi_profiles(
11 signatures: &[DigitalSignature],
12 has_dss: bool,
13 options: EtsiValidationOptions,
14) -> Vec<ValidationResult> {
15 let mut results = Vec::new();
16
17 let mut cades_found = 0usize;
18 let mut pades_found = 0usize;
19 let mut rfc3161_found = 0usize;
20
21 for sig in signatures {
22 match sig.signature_type {
23 SignatureType::EtsiCadEsDetached => {
24 cades_found += 1;
25 results.push(result_for_signature(sig, "ETSI:CAdES"));
26 }
27 SignatureType::EtsiRfc3161 => {
28 rfc3161_found += 1;
29 results.push(result_for_timestamp(sig));
30 }
31 SignatureType::AdbePkcs7Detached
32 | SignatureType::AdbePkcs7Sha1
33 | SignatureType::AdbeX509RsaSha1 => {
34 pades_found += 1;
36 }
37 }
38 }
39
40 if pades_found > 0 {
41 let status = if options.require_dss_for_pades && !has_dss {
42 ValidationStatus::Fail
43 } else if !has_dss {
44 ValidationStatus::Warning
45 } else {
46 ValidationStatus::Pass
47 };
48 results.push(ValidationResult {
49 check_type: "ETSI:PAdES-LTV".to_string(),
50 status,
51 message: if has_dss {
52 format!(
53 "PAdES signatures detected: {} with DSS present",
54 pades_found
55 )
56 } else {
57 format!("PAdES signatures detected: {} without DSS", pades_found)
58 },
59 });
60 }
61
62 if cades_found == 0 && rfc3161_found == 0 && pades_found == 0 {
63 results.push(ValidationResult {
64 check_type: "ETSI:Profiles".to_string(),
65 status: ValidationStatus::Warning,
66 message: "No ETSI/PAdES signatures detected".to_string(),
67 });
68 }
69
70 results
71}
72
73fn result_for_signature(sig: &DigitalSignature, label: &str) -> ValidationResult {
74 match &sig.validity {
75 SignatureValidity::Valid => ValidationResult {
76 check_type: label.to_string(),
77 status: ValidationStatus::Pass,
78 message: format!("{} signature valid", label),
79 },
80 SignatureValidity::Invalid(msg) => ValidationResult {
81 check_type: label.to_string(),
82 status: ValidationStatus::Fail,
83 message: format!("{} signature invalid: {}", label, msg),
84 },
85 SignatureValidity::Unknown(msg) => ValidationResult {
86 check_type: label.to_string(),
87 status: ValidationStatus::Warning,
88 message: format!("{} signature unknown: {}", label, msg),
89 },
90 }
91}
92
93fn result_for_timestamp(sig: &DigitalSignature) -> ValidationResult {
94 if let Some(ts) = &sig.timestamp {
95 let status = if ts.signature_valid {
96 ValidationStatus::Pass
97 } else {
98 ValidationStatus::Fail
99 };
100 return ValidationResult {
101 check_type: "ETSI:RFC3161".to_string(),
102 status,
103 message: if ts.signature_valid {
104 "RFC3161 timestamp valid".to_string()
105 } else {
106 "RFC3161 timestamp invalid".to_string()
107 },
108 };
109 }
110
111 ValidationResult {
112 check_type: "ETSI:RFC3161".to_string(),
113 status: ValidationStatus::Warning,
114 message: "RFC3161 timestamp not present".to_string(),
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::security::{DigitalSignature, SignatureType, SignatureValidity, TimestampDetails};
122
123 fn base_sig(signature_type: SignatureType) -> DigitalSignature {
124 DigitalSignature {
125 field_name: "Sig1".to_string(),
126 signature_type,
127 signer: None,
128 signing_time: None,
129 certificate_info: None,
130 validity: SignatureValidity::Valid,
131 location: None,
132 reason: None,
133 contact_info: None,
134 timestamp: None,
135 }
136 }
137
138 #[test]
139 fn validate_pades_requires_dss() {
140 let sigs = vec![base_sig(SignatureType::AdbePkcs7Detached)];
141 let res = validate_etsi_profiles(
142 &sigs,
143 false,
144 EtsiValidationOptions {
145 require_dss_for_pades: true,
146 },
147 );
148 assert!(res.iter().any(|r| r.status == ValidationStatus::Fail));
149 }
150
151 #[test]
152 fn validate_cades_and_rfc3161() {
153 let mut sig = base_sig(SignatureType::EtsiRfc3161);
154 sig.timestamp = Some(TimestampDetails {
155 time: None,
156 policy_oid: None,
157 hash_algorithm: None,
158 signature_valid: true,
159 tsa_chain_valid: None,
160 tsa_pin_valid: None,
161 tsa_revocation_events: Vec::new(),
162 });
163 let sigs = vec![sig, base_sig(SignatureType::EtsiCadEsDetached)];
164 let res = validate_etsi_profiles(&sigs, true, EtsiValidationOptions::default());
165 assert!(res.iter().any(|r| r.check_type == "ETSI:CAdES"));
166 assert!(res.iter().any(|r| r.check_type == "ETSI:RFC3161"));
167 }
168}