1use std::{
4 collections::{BTreeMap, HashSet},
5 fmt::Debug,
6 io::stdin,
7 path::{Path, PathBuf},
8};
9
10use log::info;
11use voa_config::{TechnologySettings, VoaConfig};
12use voa_core::{
13 LoadPathList,
14 Verifier,
15 VerifierWriter,
16 Voa,
17 identifiers::{Context, Os, Purpose, Technology},
18};
19use voa_openpgp::{
20 ModelBasedVerifier,
21 OpenPgpImport,
22 OpenpgpCert,
23 OpenpgpSignature,
24 OpenpgpSignatureCheck,
25 VoaOpenpgp,
26 import::destructured::load_from_dir,
27};
28
29use crate::{
30 Error,
31 utils::{DirOrFile, DirOrFileType, RegularFile},
32};
33
34pub fn get_writable_load_path(runtime: bool) -> Result<PathBuf, Error> {
56 let load_path_list = LoadPathList::from_effective_user();
57
58 let filter = voa_core::LoadPathFilter {
59 ephemeral: runtime,
60 writable: true,
61 };
62 let load_path = load_path_list
63 .filter(&filter)
64 .first()
65 .cloned()
66 .ok_or(Error::NoLoadPath)?;
67
68 Ok(load_path.path.clone())
69}
70
71pub fn load_verifier(
120 input: Option<DirOrFile>,
121 technology: Technology,
122) -> Result<impl VerifierWriter + Debug, Error> {
123 match technology {
124 Technology::Openpgp => Ok(if let Some(path) = input {
125 match path.typ {
126 DirOrFileType::Dir => load_from_dir(&path)?,
127 DirOrFileType::File => OpenPgpImport::from_file(&path)?,
128 }
129 } else {
130 OpenPgpImport::from_reader(stdin())?
131 }),
132 technology => Err(Error::UnsupportedTechnology { technology }),
133 }
134}
135
136pub fn write_verifier_to_hierarchy(
180 verifier: impl VerifierWriter,
181 base_path: impl AsRef<Path>,
182 os: Os,
183 purpose: Purpose,
184 context: Option<Context>,
185) -> Result<(), Error> {
186 let base_path = base_path.as_ref();
187 info!("Writing verifier to VOA base path: {base_path:?}");
188 verifier.write_to_hierarchy(base_path, os, purpose, context)?;
189 Ok(())
190}
191
192pub fn search_verifiers(
194 os: Os,
195 purpose: Purpose,
196 context: Option<Context>,
197 technology: Option<Technology>,
198) -> Result<BTreeMap<PathBuf, Vec<Verifier>>, Error> {
199 let context = if let Some(context) = context {
200 context
201 } else {
202 Context::Default
203 };
204 let technology = if let Some(technology) = technology {
205 technology
206 } else {
207 Technology::Openpgp
208 };
209
210 let voa = Voa::new();
211 let verifiers = voa.lookup(os, purpose, context, technology);
212
213 Ok(verifiers)
214}
215
216pub fn read_openpgp_signatures(
222 signatures: &HashSet<RegularFile>,
223) -> Result<Vec<OpenpgpSignature>, Error> {
224 let mut openpgp_sigs = Vec::new();
225 for path in signatures {
226 info!("Reading {:?} as OpenPGP signature", path.as_ref());
227 openpgp_sigs.push(OpenpgpSignature::from_file(path).map_err(Error::VoaOpenPgp)?)
228 }
229
230 Ok(openpgp_sigs)
231}
232
233pub fn read_openpgp_verifiers(os: Os, purpose: Purpose, context: Context) -> Vec<OpenpgpCert> {
235 info!(
236 "Reading all OpenPGP certificates matching os={os}, purpose={purpose}, context={context}"
237 );
238 let voa = VoaOpenpgp::new();
239
240 voa.lookup(os, purpose, context)
241}
242
243pub fn openpgp_verify<'a>(
249 model_verifier: &'a ModelBasedVerifier,
250 signatures: &'a [OpenpgpSignature],
251 file: &RegularFile,
252) -> Result<Vec<OpenpgpSignatureCheck<'a>>, Error> {
253 model_verifier
254 .verify_file_with_signatures(file, signatures)
255 .map_err(Error::VoaOpenPgp)
256}
257
258pub fn get_voa_config() -> VoaConfig {
262 VoaConfig::load()
263}
264
265#[derive(Debug)]
267pub struct PurposeAndContext {
268 purpose: Purpose,
269 context: Context,
270}
271
272impl PurposeAndContext {
273 pub fn new(purpose: Option<Purpose>, context: Option<Context>) -> Option<Self> {
278 let purpose = purpose?;
279 let context = context.unwrap_or_default();
280
281 Some(Self { purpose, context })
282 }
283
284 pub fn purpose(&self) -> &Purpose {
286 &self.purpose
287 }
288
289 pub fn context(&self) -> &Context {
291 &self.context
292 }
293}
294
295pub fn get_technology_settings<'a>(
297 config: &'a VoaConfig,
298 os: &Os,
299 purpose_and_context: Option<&PurposeAndContext>,
300) -> &'a TechnologySettings {
301 if let Some(purpose_and_context) = purpose_and_context {
302 config.settings_for_context_or_default(
303 os,
304 purpose_and_context.purpose(),
305 purpose_and_context.context(),
306 )
307 } else {
308 config.settings_for_os_or_default(os)
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use libc::geteuid;
315 use rstest::rstest;
316 use testresult::TestResult;
317
318 use super::*;
319
320 #[rstest]
321 #[case::runtime_dir(true)]
322 #[case::config_dir(false)]
323 fn get_writable_load_path_succeeds(#[case] runtime: bool) -> TestResult {
324 let load_path = get_writable_load_path(runtime)?;
325
326 let euid = unsafe { geteuid() };
327
328 eprintln!("Load path: {load_path:?}");
329 if runtime {
330 assert!(load_path.starts_with("/run"))
331 } else if euid < 1000 {
332 assert_eq!(load_path, PathBuf::from("/etc/voa"))
333 } else {
334 assert!(load_path.ends_with(".config/voa"))
335 }
336
337 Ok(())
338 }
339
340 #[test]
341 fn load_verifier_fails_on_unsupported_technology() -> TestResult {
342 let result = load_verifier(None, Technology::Custom("foo".parse()?));
343 match result {
344 Err(Error::UnsupportedTechnology { .. }) => {}
345 Err(error) => panic!("Did not raise Error::UnsupportedTechnology but {error}"),
346 Ok(verifier) => {
347 panic!("Is expected to fail, but succeeded to load verifier: {verifier:?}")
348 }
349 }
350
351 Ok(())
352 }
353
354 #[rstest]
355 #[case::purpose_and_context_is_none(
356 "example".parse()?,
357 PurposeAndContext::new(None, None),
358 )]
359 #[case::purpose_and_context_purpose_is_none(
360 "example".parse()?,
361 PurposeAndContext::new(None, Some("context".parse()?)),
362 )]
363 #[case::purpose_and_context_context_is_none(
364 "example".parse()?,
365 PurposeAndContext::new(Some("purpose".parse()?), None),
366 )]
367 #[case::purpose_and_context_is_some(
368 "example".parse()?,
369 PurposeAndContext::new(Some("purpose".parse()?), Some("context".parse()?)),
370 )]
371 fn get_technology_settings_os_or_context(
372 #[case] os: Os,
373 #[case] purpose_and_context: Option<PurposeAndContext>,
374 ) -> TestResult {
375 let config = get_voa_config();
376 let settings = get_technology_settings(&config, &os, purpose_and_context.as_ref());
377
378 println!("{settings}");
379 Ok(())
380 }
381}