chore: split code out into modules
This commit is contained in:
parent
33abf7c09a
commit
b36efc13a3
4 changed files with 217 additions and 124 deletions
155
src/main.rs
155
src/main.rs
|
@ -1,125 +1,26 @@
|
|||
use std::{fs::write, path::PathBuf, time::Duration};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clap::Parser;
|
||||
|
||||
use serde::Deserialize;
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use sequoia_openpgp::{
|
||||
cert::CertBuilder,
|
||||
packet::signature::SignatureBuilder,
|
||||
serialize::SerializeInto,
|
||||
types::{KeyFlags, SignatureType},
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Spec {
|
||||
primary: KeyConfig,
|
||||
subs: Vec<KeyConfig>,
|
||||
user_ids: Vec<UserIdConfig>,
|
||||
#[serde(flatten)]
|
||||
expiry: Expiry,
|
||||
}
|
||||
use spec::KeyFlag;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct KeyConfig {
|
||||
flags: Vec<KeyFlag>,
|
||||
cipher_suite: sequoia_openpgp::cert::CipherSuite,
|
||||
#[serde(flatten)]
|
||||
expiry: Expiry,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UserIdConfig {
|
||||
value: String,
|
||||
#[serde(default)]
|
||||
notation: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Expiry {
|
||||
#[serde(with = "humantime_serde::option", default)]
|
||||
validity_period: Option<Duration>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum KeyFlag {
|
||||
Certify,
|
||||
Sign,
|
||||
EncryptForTransport,
|
||||
EncryptAtRest,
|
||||
SplitKey,
|
||||
Authenticate,
|
||||
GroupKey,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
/// Path to the directory it should operate on. Defaults to the current working directory.
|
||||
#[arg(value_name = "DIR")]
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
struct Paths {
|
||||
spec: PathBuf,
|
||||
secret: PathBuf,
|
||||
public: PathBuf,
|
||||
rev: PathBuf,
|
||||
}
|
||||
|
||||
impl Paths {
|
||||
fn new(base: PathBuf) -> Paths {
|
||||
let mut spec = base.clone();
|
||||
spec.push("spec.yml");
|
||||
let mut secret = base.clone();
|
||||
secret.push("secret.asc");
|
||||
let mut public = base.clone();
|
||||
public.push("public.asc");
|
||||
let mut rev = base;
|
||||
rev.push("rev.asc");
|
||||
Paths {
|
||||
spec,
|
||||
secret,
|
||||
public,
|
||||
rev,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyFlag {
|
||||
fn apply(&self, flags: sequoia_openpgp::types::KeyFlags) -> sequoia_openpgp::types::KeyFlags {
|
||||
match self {
|
||||
Self::Certify => flags.set_certification(),
|
||||
Self::Sign => flags.set_signing(),
|
||||
Self::EncryptForTransport => flags.set_transport_encryption(),
|
||||
Self::EncryptAtRest => flags.set_storage_encryption(),
|
||||
Self::SplitKey => flags.set_split_key(),
|
||||
Self::Authenticate => flags.set_authentication(),
|
||||
Self::GroupKey => flags.set_group_key(),
|
||||
}
|
||||
}
|
||||
}
|
||||
mod paths;
|
||||
mod setup;
|
||||
mod spec;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
let base_dir = cli
|
||||
.path
|
||||
.ok_or_else(|| anyhow!("No path specified in CLI parameters."))
|
||||
.or_else(|_| std::env::current_dir())
|
||||
.context(
|
||||
"Couldn't get current dir from env, and no path was specified in CLI parameters either",
|
||||
)?;
|
||||
let paths = Paths::new(base_dir);
|
||||
let spec_string = std::fs::read_to_string(&paths.spec)?;
|
||||
let spec: Spec = serde_yaml::from_str(&spec_string)?;
|
||||
let mut primary_key_flags = KeyFlags::empty();
|
||||
for flag in spec.primary.flags {
|
||||
primary_key_flags = flag.apply(primary_key_flags);
|
||||
}
|
||||
let (paths, spec) = crate::setup::setup().context("Failed to setup application")?;
|
||||
let mut builder = CertBuilder::new()
|
||||
.set_primary_key_flags(primary_key_flags)
|
||||
.set_primary_key_flags(
|
||||
spec.primary
|
||||
.flags
|
||||
.iter()
|
||||
.fold(KeyFlags::empty(), KeyFlag::fold),
|
||||
)
|
||||
.set_validity_period(
|
||||
spec.primary
|
||||
.expiry
|
||||
|
@ -128,12 +29,8 @@ fn main() -> Result<()> {
|
|||
)
|
||||
.set_cipher_suite(spec.primary.cipher_suite);
|
||||
for sub_key in spec.subs {
|
||||
let mut sub_key_flags = KeyFlags::empty();
|
||||
for flag in sub_key.flags {
|
||||
sub_key_flags = flag.apply(sub_key_flags);
|
||||
}
|
||||
builder = builder.add_subkey_with(
|
||||
sub_key_flags,
|
||||
sub_key.flags.iter().fold(KeyFlags::empty(), KeyFlag::fold),
|
||||
sub_key
|
||||
.expiry
|
||||
.validity_period
|
||||
|
@ -145,16 +42,26 @@ fn main() -> Result<()> {
|
|||
for user_id in spec.user_ids {
|
||||
let mut sig_builder = SignatureBuilder::new(SignatureType::PositiveCertification);
|
||||
for (key, value) in user_id.notation {
|
||||
sig_builder = sig_builder.add_notation(key, value, None, false)?;
|
||||
sig_builder = sig_builder
|
||||
.add_notation(key, value, None, false)
|
||||
.context(format!(
|
||||
"Failed to add notation to signature builder for {}",
|
||||
&user_id.value
|
||||
))?;
|
||||
}
|
||||
builder = builder.add_userid_with(user_id.value, sig_builder)?;
|
||||
builder = builder
|
||||
.add_userid_with(user_id.value.clone(), sig_builder)
|
||||
.context(format!(
|
||||
"Failed to add user ID {} to certificate builder",
|
||||
&user_id.value
|
||||
))?;
|
||||
}
|
||||
|
||||
let (cert, rev) = builder.generate()?;
|
||||
|
||||
write(&paths.public, cert.armored().to_vec()?)?;
|
||||
write(&paths.secret, cert.as_tsk().armored().to_vec()?)?;
|
||||
write(&paths.rev, cert.insert_packets(rev)?.armored().to_vec()?)?;
|
||||
|
||||
let (cert, rev) = builder
|
||||
.generate()
|
||||
.context("Failed to generate certificate!")?;
|
||||
paths
|
||||
.write(cert, rev)
|
||||
.context("Failed to store certificate!")?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
70
src/paths.rs
Normal file
70
src/paths.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use sequoia_openpgp::{packet::Signature, serialize::SerializeInto, Cert};
|
||||
|
||||
pub(crate) struct Paths {
|
||||
pub(crate) spec: PathBuf,
|
||||
pub(crate) secret: PathBuf,
|
||||
pub(crate) public: PathBuf,
|
||||
pub(crate) rev: PathBuf,
|
||||
}
|
||||
|
||||
impl Paths {
|
||||
pub(crate) fn new(base: PathBuf) -> Paths {
|
||||
let mut spec = base.clone();
|
||||
spec.push("spec.yml");
|
||||
let mut secret = base.clone();
|
||||
secret.push("secret.asc");
|
||||
let mut public = base.clone();
|
||||
public.push("public.asc");
|
||||
let mut rev = base;
|
||||
rev.push("rev.asc");
|
||||
Paths {
|
||||
spec,
|
||||
secret,
|
||||
public,
|
||||
rev,
|
||||
}
|
||||
}
|
||||
pub(crate) fn write(&self, cert: Cert, rev: Signature) -> Result<()> {
|
||||
self.write_part(Self::public, cert.armored())
|
||||
.context("Failed to write public certificate to file")?;
|
||||
self.write_part(Self::secret, cert.as_tsk().armored())
|
||||
.context("Failed to certificate including private keys to file")?;
|
||||
self.write_part(
|
||||
Self::rev,
|
||||
cert.insert_packets(rev)
|
||||
.context("Failed to write revocation signature packet into certificate")?
|
||||
.armored(),
|
||||
)
|
||||
.context("Failed to write revoked certificate to file")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_part<F, S>(&self, path_selector: F, data: S) -> Result<()>
|
||||
where
|
||||
F: Fn(&Self) -> &Path,
|
||||
S: SerializeInto,
|
||||
{
|
||||
std::fs::write(
|
||||
path_selector(self),
|
||||
SerializeInto::to_vec(&data)
|
||||
.context("Failed to serialize certificate into byte vec")?,
|
||||
)
|
||||
.context("Failed to write serialized certificate into file")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn public(&self) -> &Path {
|
||||
self.public.as_path()
|
||||
}
|
||||
|
||||
fn secret(&self) -> &Path {
|
||||
self.secret.as_path()
|
||||
}
|
||||
|
||||
fn rev(&self) -> &Path {
|
||||
self.rev.as_path()
|
||||
}
|
||||
}
|
31
src/setup.rs
Normal file
31
src/setup.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clap::Parser;
|
||||
|
||||
use crate::{paths::Paths, spec::Spec};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub(crate) struct Cli {
|
||||
/// Path to the directory it should operate on. Defaults to the current working directory.
|
||||
#[arg(value_name = "DIR")]
|
||||
pub(crate) path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub(crate) fn setup() -> Result<(Paths, Spec)> {
|
||||
let cli = Cli::parse();
|
||||
let base_dir = cli
|
||||
.path
|
||||
.ok_or_else(|| anyhow!("No path specified in CLI parameters."))
|
||||
.or_else(|_| std::env::current_dir())
|
||||
.context(
|
||||
"Couldn't get current dir from env, and no path was specified in CLI parameters either",
|
||||
)?;
|
||||
let paths = Paths::new(base_dir);
|
||||
let spec_string =
|
||||
std::fs::read_to_string(&paths.spec).context("Failed to read spec into string")?;
|
||||
let spec: Spec =
|
||||
serde_yaml::from_str(&spec_string).context("Failed to parse spec from string")?;
|
||||
Ok((paths, spec))
|
||||
}
|
85
src/spec.rs
Normal file
85
src/spec.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
/// Specification for how the OpenPGP certificate is supposed to look like
|
||||
pub(crate) struct Spec {
|
||||
/// KeySpec for the primary key
|
||||
pub(crate) primary: KeySpec,
|
||||
/// List of KeySpec elements for the sub keys
|
||||
pub(crate) subs: Vec<KeySpec>,
|
||||
/// List of UserIdSpec elements for the user ids
|
||||
pub(crate) user_ids: Vec<UserIdSpec>,
|
||||
#[serde(flatten)]
|
||||
/// Expiry information
|
||||
pub(crate) expiry: Expiry,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
/// Specification for how the (sub) key is supposed to be configured
|
||||
pub(crate) struct KeySpec {
|
||||
/// List of flags to set for the key, detailing what for and how the key can be used.
|
||||
pub(crate) flags: Vec<KeyFlag>,
|
||||
/// Which kind of cryptography the key is going to use
|
||||
pub(crate) cipher_suite: sequoia_openpgp::cert::CipherSuite,
|
||||
#[serde(flatten)]
|
||||
/// Expiry information
|
||||
pub(crate) expiry: Expiry,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
/// Specification on how a user ID is supposed to look
|
||||
pub(crate) struct UserIdSpec {
|
||||
/// The string value of the user ID itself
|
||||
pub(crate) value: String,
|
||||
#[serde(default)]
|
||||
/// A list of notation keys and values to add to the binding signature of the user ID.
|
||||
pub(crate) notation: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
/// Expiry spec, including right now only a validity period
|
||||
pub(crate) struct Expiry {
|
||||
#[serde(with = "humantime_serde::option", default)]
|
||||
/// Validity period, how long a key is supposed to be usable for, starting with the date it was
|
||||
/// created
|
||||
pub(crate) validity_period: Option<Duration>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
/// Flags that can be set for keys
|
||||
pub(crate) enum KeyFlag {
|
||||
/// Key can certify
|
||||
Certify,
|
||||
/// Key can sign
|
||||
Sign,
|
||||
/// Key can be used for transport encryption
|
||||
EncryptForTransport,
|
||||
/// Key can be used for encrypting data at rest
|
||||
EncryptAtRest,
|
||||
/// Key is split by a secret-sharing mechanism
|
||||
SplitKey,
|
||||
/// Key can be used for authentication
|
||||
Authenticate,
|
||||
/// Key is in the possession of more than one person
|
||||
GroupKey,
|
||||
}
|
||||
|
||||
impl KeyFlag {
|
||||
pub(crate) fn fold(
|
||||
flags: sequoia_openpgp::types::KeyFlags,
|
||||
flag: &Self,
|
||||
) -> sequoia_openpgp::types::KeyFlags {
|
||||
match flag {
|
||||
Self::Certify => flags.set_certification(),
|
||||
Self::Sign => flags.set_signing(),
|
||||
Self::EncryptForTransport => flags.set_transport_encryption(),
|
||||
Self::EncryptAtRest => flags.set_storage_encryption(),
|
||||
Self::SplitKey => flags.set_split_key(),
|
||||
Self::Authenticate => flags.set_authentication(),
|
||||
Self::GroupKey => flags.set_group_key(),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue