use chrono::DateTime;
use chrono::Utc;
use anyhow::Context;
use sequoia_openpgp as openpgp;
use openpgp::armor::Kind;
use openpgp::armor::Writer;
use openpgp::cert::CertBuilder;
use openpgp::serialize::Serialize;
use openpgp::types::KeyFlags;
use openpgp::Packet;
use openpgp::packet::UserID;
use openpgp::Result;
use crate::common::password;
use crate::common::userid::{lint_userids, lint_names, lint_emails};
use crate::Sq;
use crate::cli::{
self,
types::{
Expiration,
FileOrStdout,
TrustAmount,
},
};
use crate::commands::inspect::inspect;
use crate::common::key::certify_generated;
use crate::output::import::ImportStatus;
pub fn generate(
mut sq: Sq,
mut command: cli::key::generate::Command,
) -> Result<()> {
if command.output.as_ref().map(|s| s.is_stdout()).unwrap_or(false)
&& command.rev_cert.as_ref().map(|s| s.is_stdout()).unwrap_or(false)
{
return Err(anyhow::anyhow!(
"--output and --rev-cert must not both be stdout"));
}
if command.output.is_none() {
sq.cert_store_or_else()?;
sq.key_store_or_else()?;
}
assert!(command.own_key ^ command.shared_key);
let key_flags_template = if command.own_key {
KeyFlags::empty()
} else {
KeyFlags::empty().set_group_key()
};
let mut builder = CertBuilder::new()
.set_profile(command.profile.into())?;
lint_names(&command.names)?;
for n in &command.names {
command.userid.push(UserID::from(n.as_str()));
}
lint_emails(&command.emails)?;
for n in &command.emails {
command.userid.push(UserID::from_address(None, None, n)?);
}
if command.userid.is_empty() {
weprintln!("No user ID given, using direct key signature");
} else {
if ! command.allow_non_canonical_userids {
lint_userids(&command.userid)?;
}
for uid in &command.userid {
builder = builder.add_userid(uid.clone());
}
}
builder = builder.set_creation_time(sq.time);
builder = builder.set_validity_period(
command
.expiration
.as_duration(DateTime::<Utc>::from(sq.time))?
);
builder = builder.set_cipher_suite(
sq.config.cipher_suite(&command.cipher_suite,
command.cipher_suite_source));
let _profile = sq.config.key_generate_profile(
&command.profile, command.profile_source);
builder = builder.set_primary_key_flags(
key_flags_template.clone().set_certification());
match (command.can_sign, command.cannot_sign) {
(false, false) | (true, false) => {
builder = builder.add_subkey(
key_flags_template.clone().set_signing(),
None, None);
}
(false, true) => { }
(true, true) => {
return Err(anyhow::anyhow!(
"Conflicting arguments --can-sign and --cannot-sign"
));
}
}
match (command.can_authenticate, command.cannot_authenticate) {
(false, false) | (true, false) => {
builder = builder.add_subkey(
key_flags_template.clone().set_authentication(),
None, None);
}
(false, true) => { }
(true, true) => {
return Err(anyhow::anyhow!(
"Conflicting arguments --can-authenticate and \
--cannot-authenticate"
));
}
}
use cli::types::EncryptPurpose::*;
match (command.can_encrypt, command.cannot_encrypt) {
(Some(Universal), false) | (None, false) => {
builder = builder.add_subkey(
key_flags_template.clone()
.set_transport_encryption()
.set_storage_encryption(),
None, None);
}
(Some(Storage), false) => {
builder = builder.add_subkey(
key_flags_template.clone().set_storage_encryption(),
None, None);
}
(Some(Transport), false) => {
builder = builder.add_subkey(
key_flags_template.clone().set_transport_encryption(),
None, None);
}
(None, true) => { }
(Some(_), true) => {
return Err(anyhow::anyhow!(
"Conflicting arguments --can-encrypt and \
--cannot-encrypt"
));
}
}
if let Some(password_file) = command.new_password_file {
let password = std::fs::read(&password_file)
.with_context(|| {
format!("Reading {}", password_file.display())
})?;
builder = builder.set_password(Some(password.into()));
} else if ! command.without_password {
builder = builder.set_password(
password::prompt_for_new_or_none(&sq, "key")?);
}
let on_keystore = command.output.is_none();
let gen = || {
builder.generate()
};
let (mut cert, rev);
let rev_path = if let Some(rev_cert) = command.rev_cert {
(cert, rev) = gen()?;
rev_cert
} else if on_keystore {
if let Some(home) = &sq.home {
let dir = home.data_dir(sequoia_directories::Component::Other(
"revocation-certificates".into()));
std::fs::create_dir_all(&dir)
.with_context(|| {
format!("While creating {}", dir.display())
})?;
(cert, rev) = gen()?;
FileOrStdout::new(
Some(dir.join(format!("{}-revocation.pgp",
cert.fingerprint()))))
} else {
return Err(anyhow::anyhow!(
"Missing arguments: --rev-cert is mandatory if --home=none is \
given."
));
}
} else {
return Err(anyhow::anyhow!(
"Missing arguments: --rev-cert is mandatory if --output is \
given."
));
};
let headers = cert.armor_headers();
{
let mut headers: Vec<_> = headers
.iter()
.map(|value| ("Comment", value.as_str()))
.collect();
headers.insert(0, ("Comment", "Revocation certificate for"));
let w = rev_path.create_safe(&sq)?;
let mut w = Writer::with_headers(w, Kind::PublicKey, headers)?;
Packet::from(cert.primary_key().key().clone()).serialize(&mut w)?;
Packet::Signature(rev).serialize(&mut w)?;
w.finalize()?;
}
{
let headers: Vec<_> = headers
.iter()
.map(|value| ("Comment", value.as_str()))
.collect();
match command.output {
Some(ref output_file) => {
let w = output_file.clone().for_secrets()
.create_safe(&sq)?;
let mut w = Writer::with_headers(w, Kind::SecretKey, headers)?;
cert.as_tsk().serialize(&mut w)?;
w.finalize()?;
}
None => {
let have_userids = cert.userids().next().is_some();
if have_userids {
cert = certify_generated(&mut sq, &cert)?;
}
match sq.import_key(cert.clone(), &mut Default::default())
.map(|(key_status, _cert_status)| key_status)
{
Ok(ImportStatus::New) => { }
Ok(ImportStatus::Unchanged) => {
panic!(
"The new key is identical to an existing one; this \
should never happen");
}
Ok(ImportStatus::Updated) => {
panic!(
"The new key collides with an existing one; this \
should never happen")
}
Err(err) => {
return Err(anyhow::anyhow!(
"Failed saving to the store: {}", err))
}
}
let trust_root = sq.local_trust_root()?;
let trust_root = trust_root.to_cert()?;
if command.own_key && have_userids {
crate::common::pki::certify::certify(
&mut std::io::stderr(),
&sq,
false, &trust_root,
&cert,
&command.userid.into_iter().map(Into::into)
.collect::<Vec<_>>(),
true, &[(TrustAmount::Full, Expiration::Never)],
u8::MAX, &[][..], &[][..], true, false, &[][..], None, false, )?;
} else if command.shared_key && have_userids {
crate::common::pki::certify::certify(
&mut std::io::stderr(),
&sq,
false, &trust_root,
&cert,
&command.userid.into_iter().map(Into::into)
.collect::<Vec<_>>(),
true, &[(TrustAmount::Full, Expiration::Never)],
0, &[][..], &[][..], true, false, &[][..], None, false, )?;
}
}
}
}
{
let mut bytes = Vec::new();
cert.as_tsk().serialize(&mut bytes)
.expect("serializing to a vector is infallible");
if let Err(err) = inspect(
&mut sq,
buffered_reader::Memory::with_cookie(&bytes, Default::default()),
command.output
.as_ref()
.and_then(|output| {
output.path().map(|p| p.display().to_string())
})
.as_deref(),
&mut (Box::new(std::io::stderr()) as Box<dyn std::io::Write + Send + Sync>),
false, false)
{
weprintln!("Failed to display key: {}", err);
weprintln!("This is probably a bug in sq, please report it to \
https://gitlab.com/sequoia-pgp/sequoia-sq/-/issues/new .");
}
}
if on_keystore && command.own_key {
sq.hint(format_args!("Because you supplied the `--own-key` flag, \
the user IDs on this key have been marked as \
authenticated, and this key has been marked \
as a fully trusted introducer. \
If that was a mistake, you can undo that \
with:"))
.sq().arg("pki").arg("link").arg("retract")
.arg_value("--cert", cert.fingerprint())
.arg("--all")
.done();
}
if on_keystore && command.shared_key {
sq.hint(format_args!("The user IDs on the key have been marked as \
authenticated. \
If that was a mistake, you can undo that \
with:"))
.sq().arg("pki").arg("link").arg("retract")
.arg_value("--cert", cert.fingerprint())
.arg("--all")
.done();
}
if on_keystore {
sq.hint(format_args!("You can export your certificate as follows:"))
.sq().arg("cert").arg("export")
.arg_value("--cert", cert.fingerprint())
.done();
sq.hint(format_args!("Once you are happy you can upload it to public \
directories using:"))
.sq().arg("network").arg("keyserver").arg("publish")
.arg_value("--cert", cert.fingerprint())
.done();
} else {
let mut shown = false;
if let Some(ref output) = command.output {
if let Some(output_path) = output.path() {
sq.hint(format_args!("You can extract the certificate from the \
generated key by running:"))
.sq().arg("key").arg("delete")
.arg_value("--cert-file", output_path.display())
.arg_value("--output", output_path.with_extension("cert").display())
.done();
shown = true;
}
}
if ! shown {
sq.hint(format_args!("You can extract the certificate from the \
generated key using:"))
.sq().arg("key").arg("delete").done();
}
}
Ok(())
}