voa_config/file/technology/
openpgp.rs1use std::collections::HashSet;
10
11use garde::Validate;
12use serde::{Deserialize, Serialize};
13
14use crate::{
15 Error,
16 common::ordered_option_set,
17 config::technology::openpgp::{
18 DomainName,
19 NumCertifications,
20 NumDataSignatures,
21 OpenpgpFingerprint,
22 TrustAmountFlow,
23 TrustAmountPartial,
24 TrustAmountRoot,
25 },
26};
27
28#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
30pub struct ConfigPlainMode {
31 #[serde(
33 skip_serializing_if = "Option::is_none",
34 serialize_with = "ordered_option_set",
35 default
36 )]
37 identity_domain_matches: Option<HashSet<DomainName>>,
38
39 #[serde(
41 skip_serializing_if = "Option::is_none",
42 serialize_with = "ordered_option_set",
43 default
44 )]
45 fingerprint_matches: Option<HashSet<OpenpgpFingerprint>>,
46}
47
48impl ConfigPlainMode {
49 #[allow(dead_code)]
52 pub(crate) fn new(
53 identity_domain_matches: Option<HashSet<DomainName>>,
54 fingerprint_matches: Option<HashSet<OpenpgpFingerprint>>,
55 ) -> Self {
56 Self {
57 identity_domain_matches,
58 fingerprint_matches,
59 }
60 }
61
62 pub fn identity_domain_matches(&self) -> Option<&HashSet<DomainName>> {
64 self.identity_domain_matches.as_ref()
65 }
66
67 pub fn fingerprint_matches(&self) -> Option<&HashSet<OpenpgpFingerprint>> {
69 self.fingerprint_matches.as_ref()
70 }
71}
72
73fn validate_required_certifications(
80 trust_anchor_fingerprint_matches: Option<&HashSet<OpenpgpFingerprint>>,
81) -> impl FnOnce(&Option<NumCertifications>, &()) -> garde::Result + '_ {
82 move |num_certifications, _| {
83 let num_certifications = if let Some(num_certifications) = num_certifications {
84 num_certifications.get().get()
85 } else {
86 return Ok(());
87 };
88 let fingerprints =
89 if let Some(trust_anchor_fingerprint_matches) = trust_anchor_fingerprint_matches {
90 trust_anchor_fingerprint_matches
91 } else {
92 return Ok(());
93 };
94 let num_fingerprints = fingerprints.len();
95
96 if num_fingerprints > 0 && num_certifications > num_fingerprints {
97 return Err(garde::Error::new(format!(
98 "is {num_certifications}, but must be <= {num_fingerprints}, as the \"trust anchor\" verification mode only pins {num_fingerprints} trust anchor fingerprint(s): {}",
99 fingerprints
100 .iter()
101 .map(ToString::to_string)
102 .collect::<Vec<_>>()
103 .join(", ")
104 )));
105 }
106
107 Ok(())
108 }
109}
110
111#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Validate)]
117pub struct ConfigTrustAnchorMode {
118 #[serde(skip_serializing_if = "Option::is_none")]
121 #[garde(custom(validate_required_certifications(self.trust_anchor_fingerprint_matches.as_ref())))]
122 required_certifications: Option<NumCertifications>,
123
124 #[serde(
126 skip_serializing_if = "Option::is_none",
127 serialize_with = "ordered_option_set",
128 default
129 )]
130 #[garde(skip)]
131 artifact_verifier_identity_domain_matches: Option<HashSet<DomainName>>,
132
133 #[serde(
135 skip_serializing_if = "Option::is_none",
136 serialize_with = "ordered_option_set",
137 default
138 )]
139 #[garde(skip)]
140 trust_anchor_fingerprint_matches: Option<HashSet<OpenpgpFingerprint>>,
141}
142
143impl ConfigTrustAnchorMode {
144 #[allow(dead_code)]
152 pub fn new(
153 required_certifications: Option<NumCertifications>,
154 artifact_verifier_identity_domain_matches: Option<HashSet<DomainName>>,
155 trust_anchor_fingerprint_matches: Option<HashSet<OpenpgpFingerprint>>,
156 ) -> Result<Self, Error> {
157 let mode = Self {
158 required_certifications,
159 artifact_verifier_identity_domain_matches,
160 trust_anchor_fingerprint_matches,
161 };
162 mode.validate().map_err(|source| Error::Validation {
163 context: "creating a trust anchor verification".to_string(),
164 source,
165 })?;
166
167 Ok(mode)
168 }
169
170 pub fn required_certifications(&self) -> Option<NumCertifications> {
172 self.required_certifications
173 }
174
175 pub fn artifact_verifier_identity_domain_matches(&self) -> Option<&HashSet<DomainName>> {
177 self.artifact_verifier_identity_domain_matches.as_ref()
178 }
179
180 pub fn trust_anchor_fingerprint_matches(&self) -> Option<&HashSet<OpenpgpFingerprint>> {
182 self.trust_anchor_fingerprint_matches.as_ref()
183 }
184}
185
186#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
188pub(crate) struct ConfigWebOfTrustRoot {
189 fingerprint: OpenpgpFingerprint,
191
192 amount: Option<TrustAmountRoot>,
194}
195
196impl ConfigWebOfTrustRoot {
197 #[allow(dead_code)]
200 pub(crate) fn new(fingerprint: OpenpgpFingerprint, amount: Option<TrustAmountRoot>) -> Self {
201 Self {
202 fingerprint,
203 amount,
204 }
205 }
206
207 pub fn fingerprint(&self) -> &OpenpgpFingerprint {
209 &self.fingerprint
210 }
211
212 pub fn amount(&self) -> Option<TrustAmountRoot> {
214 self.amount
215 }
216}
217
218#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
220pub struct ConfigWebOfTrustMode {
221 flow_amount: Option<TrustAmountFlow>,
223
224 partial_amount: Option<TrustAmountPartial>,
226
227 #[serde(
239 skip_serializing_if = "Option::is_none",
240 serialize_with = "ordered_option_set",
241 default
242 )]
243 roots: Option<HashSet<ConfigWebOfTrustRoot>>,
244
245 #[serde(
247 skip_serializing_if = "Option::is_none",
248 serialize_with = "ordered_option_set",
249 default
250 )]
251 artifact_verifier_identity_domain_matches: Option<HashSet<DomainName>>,
252}
253
254impl ConfigWebOfTrustMode {
255 pub(crate) fn new(
257 flow_amount: Option<TrustAmountFlow>,
258 partial_amount: Option<TrustAmountPartial>,
259 roots: Option<HashSet<ConfigWebOfTrustRoot>>,
260 artifact_verifier_identity_domain_matches: Option<HashSet<DomainName>>,
261 ) -> Self {
262 Self {
263 flow_amount,
264 partial_amount,
265 roots,
266 artifact_verifier_identity_domain_matches,
267 }
268 }
269
270 pub fn flow_amount(&self) -> Option<TrustAmountFlow> {
272 self.flow_amount
273 }
274
275 pub fn partial_amount(&self) -> Option<TrustAmountPartial> {
277 self.partial_amount
278 }
279
280 pub(crate) fn roots(&self) -> Option<&HashSet<ConfigWebOfTrustRoot>> {
282 self.roots.as_ref()
283 }
284
285 pub fn artifact_verifier_identity_domain_matches(&self) -> Option<&HashSet<DomainName>> {
287 self.artifact_verifier_identity_domain_matches.as_ref()
288 }
289}
290
291impl Default for ConfigWebOfTrustMode {
292 fn default() -> Self {
293 Self::new(None, None, None, None)
294 }
295}
296
297#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Validate)]
303#[serde(rename_all = "snake_case")]
304pub(crate) enum ConfigVerificationMethod {
305 Plain(#[garde(skip)] ConfigPlainMode),
307
308 TrustAnchor(#[garde(dive)] ConfigTrustAnchorMode),
310
311 WebOfTrust(#[garde(skip)] ConfigWebOfTrustMode),
313}
314
315impl Default for ConfigVerificationMethod {
316 fn default() -> Self {
317 Self::TrustAnchor(ConfigTrustAnchorMode::default())
318 }
319}
320
321fn validate_num_data_signatures(
328 verification_method: &ConfigVerificationMethod,
329) -> impl FnOnce(&Option<NumDataSignatures>, &()) -> garde::Result + '_ {
330 move |num_data_signatures, _| {
331 let num_data_signatures = if let Some(num_data_signatures) = num_data_signatures {
332 num_data_signatures.get().get()
333 } else {
334 return Ok(());
335 };
336
337 if let ConfigVerificationMethod::Plain(mode) = verification_method {
338 let Some(fingerprint_matches) = mode.fingerprint_matches() else {
339 return Ok(());
340 };
341 let num_fingerprints = fingerprint_matches.len();
342 if num_fingerprints > 0 && num_data_signatures > num_fingerprints {
343 return Err(garde::Error::new(format!(
344 "is {num_data_signatures}, but must be <= {num_fingerprints}, as the plain verification mode only pins {num_fingerprints} artifact verifier fingerprint(s): {}",
345 fingerprint_matches
346 .iter()
347 .map(ToString::to_string)
348 .collect::<Vec<_>>()
349 .join(", ")
350 )));
351 }
352 }
353 Ok(())
354 }
355}
356
357#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Validate)]
363pub(crate) struct ConfigOpenpgpSettings {
364 #[serde(skip_serializing_if = "Option::is_none")]
366 #[garde(custom(validate_num_data_signatures(&self.verification_method)))]
367 num_data_signatures: Option<NumDataSignatures>,
368
369 #[garde(dive)]
371 verification_method: ConfigVerificationMethod,
372}
373
374impl ConfigOpenpgpSettings {
375 #[allow(dead_code)]
382 pub(crate) fn new(
383 num_data_signatures: Option<NumDataSignatures>,
384 verification_method: ConfigVerificationMethod,
385 ) -> Result<Self, Error> {
386 let settings = Self {
387 num_data_signatures,
388 verification_method,
389 };
390
391 settings.validate().map_err(|source| Error::Validation {
392 context: "creating OpenPGP settings from file".to_string(),
393 source,
394 })?;
395
396 Ok(settings)
397 }
398
399 pub fn num_data_signatures(&self) -> Option<NumDataSignatures> {
401 self.num_data_signatures
402 }
403
404 pub fn config_verification_method(&self) -> &ConfigVerificationMethod {
406 &self.verification_method
407 }
408}