sopass 0.5.0

command line password manager using SOP
Documentation
//! Manage certificates in a store.

use std::path::PathBuf;

use clap::Parser;
use log::info;

use crate::{
    sop::Certificate,
    store::{EncryptedStore, EncryptedStoreError, StoreError},
    Config, LeafCommand,
};

/// List all certificate names, one per line.
#[derive(Debug, Parser)]
pub struct ListCerts {}

impl LeafCommand for ListCerts {
    type Error = CertError;

    fn run(&self, config: &Config) -> Result<(), CertError> {
        let store = EncryptedStore::new(config.sop(), config.store()).read_store()?;
        info!("loaded store OK");
        for name in store.cert_names() {
            println!("{name}");
        }
        Ok(())
    }
}

/// Add a certificate to the store.
#[derive(Debug, Parser)]
pub struct AddCert {
    /// Name of certificate.
    #[clap(long)]
    name: String,

    /// File where certificate is stored.
    #[clap(long)]
    cert: PathBuf,
}

impl LeafCommand for AddCert {
    type Error = CertError;

    fn run(&self, config: &Config) -> Result<(), CertError> {
        let encrypted = EncryptedStore::new(config.sop(), config.store());
        let mut store = encrypted.read_store()?;
        let cert =
            std::fs::read(&self.cert).map_err(|err| CertError::ReadCert(self.cert.clone(), err))?;
        let cert = Certificate::new(cert);
        store.push_cert(&self.name, &cert);
        encrypted.write_store(&store)?;

        Ok(())
    }
}

/// Show a named certificate.
#[derive(Debug, Parser)]
pub struct ShowCert {
    /// Name of certificate.
    name: String,
}

impl LeafCommand for ShowCert {
    type Error = CertError;

    fn run(&self, config: &Config) -> Result<(), CertError> {
        let encrypted = EncryptedStore::new(config.sop(), config.store());
        let store = encrypted.read_store()?;
        info!("get value for {}", self.name);
        if let Some(value) = store.get(&self.name) {
            println!("{}", String::from_utf8_lossy(value));
        } else {
            return Err(CertError::NoSuchCert(self.name.clone()));
        }

        Ok(())
    }
}

/// Remove a named certificate.
#[derive(Debug, Parser)]
pub struct RemoveCert {
    /// Name of certificate.
    name: String,
}

impl LeafCommand for RemoveCert {
    type Error = CertError;

    fn run(&self, config: &Config) -> Result<(), CertError> {
        info!("remove certificate {}", self.name);
        let encrypted = EncryptedStore::new(config.sop(), config.store());
        let mut store = encrypted.read_store()?;
        store.remove_cert(&self.name)?;
        encrypted.write_store(&store)?;

        Ok(())
    }
}

/// Rename a certificate.
#[derive(Debug, Parser)]
pub struct RenameCert {
    /// Old name of certificate.
    old: String,

    /// New name of certificate.
    new: String,
}

impl LeafCommand for RenameCert {
    type Error = CertError;

    fn run(&self, config: &Config) -> Result<(), CertError> {
        info!("rename {} to {}", self.old, self.new);
        let encrypted = EncryptedStore::new(config.sop(), config.store());
        let mut store = encrypted.read_store()?;
        info!("rename in memory");
        store.rename(&self.old, &self.new)?;
        info!("rename in memory OK");
        encrypted.write_store(&store)?;

        Ok(())
    }
}

/// Possible errors for managing certificates in a store.
#[derive(Debug, thiserror::Error)]
pub enum CertError {
    /// Can't read certificate.
    #[error("failed to read certificate from file {0}")]
    ReadCert(PathBuf, #[source] std::io::Error),

    /// No such certificate.
    #[error("there is not certificate named {0}")]
    NoSuchCert(String),

    /// Error from handling encrypted store.
    #[error(transparent)]
    Encrypted(#[from] EncryptedStoreError),

    /// Error handling cleartext store.
    #[error(transparent)]
    Store(#[from] StoreError),
}