chore: split code out into modules

This commit is contained in:
Jan Christian Grünhage 2023-05-30 23:15:22 +02:00
parent 33abf7c09a
commit b36efc13a3
Signed by: jcgruenhage
GPG key ID: EEC1170CE56FA2ED
4 changed files with 217 additions and 124 deletions

View file

@ -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
View 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
View 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
View 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(),
}
}
}