use anyhow::Context as _;
use clap::ArgMatches;
use itertools::Itertools;
use std::time::{SystemTime, Duration};
use crate::openpgp::KeyHandle;
use crate::openpgp::Packet;
use crate::openpgp::Result;
use crate::openpgp::armor::{Writer, Kind};
use crate::openpgp::cert::prelude::*;
use crate::openpgp::packet::prelude::*;
use crate::openpgp::packet::signature::subpacket::SubpacketTag;
use crate::openpgp::parse::Parse;
use crate::openpgp::policy::Policy;
use crate::openpgp::serialize::Serialize;
use crate::openpgp::types::KeyFlags;
use crate::openpgp::types::SignatureType;
use crate::{
open_or_stdin,
};
use crate::Config;
use crate::create_or_stdout;
use crate::SECONDS_IN_YEAR;
use crate::parse_duration;
use crate::decrypt_key;
pub fn generate(m: &ArgMatches, force: bool) -> Result<()> {
let mut builder = CertBuilder::new();
match m.values_of("userid") {
Some(uids) => for uid in uids {
builder = builder.add_userid(uid);
},
None => {
eprintln!("No user ID given, using direct key signature");
}
}
match (m.value_of("expires"), m.value_of("expires-in")) {
(None, None) => builder = builder.set_validity_period(
Some(Duration::new(3 * SECONDS_IN_YEAR, 0))),
(Some(t), None) if t == "never" =>
builder = builder.set_validity_period(None),
(Some(t), None) => {
let now = builder.creation_time()
.unwrap_or_else(std::time::SystemTime::now);
let expiration = SystemTime::from(
crate::parse_iso8601(t, chrono::NaiveTime::from_hms(0, 0, 0))?);
let validity = expiration.duration_since(now)?;
builder = builder.set_creation_time(now)
.set_validity_period(validity);
},
(None, Some(d)) if d == "never" =>
builder = builder.set_validity_period(None),
(None, Some(d)) => {
let d = parse_duration(d)?;
builder = builder.set_validity_period(Some(d));
},
(Some(_), Some(_)) => unreachable!("conflicting args"),
}
match m.value_of("cipher-suite") {
Some("rsa3k") => {
builder = builder.set_cipher_suite(CipherSuite::RSA3k);
}
Some("rsa4k") => {
builder = builder.set_cipher_suite(CipherSuite::RSA4k);
}
Some("cv25519") => {
builder = builder.set_cipher_suite(CipherSuite::Cv25519);
}
Some(ref cs) => {
return Err(anyhow::anyhow!("Unknown cipher suite '{}'", cs));
}
None => panic!("argument has a default value"),
}
match (m.is_present("can-sign"), m.is_present("cannot-sign")) {
(false, false) | (true, false) => {
builder = builder.add_signing_subkey();
}
(false, true) => { }
(true, true) => {
return Err(
anyhow::anyhow!("Conflicting arguments --can-sign and --cannot-sign"));
}
}
match (m.value_of("can-encrypt"), m.is_present("cannot-encrypt")) {
(Some("universal"), false) | (None, false) => {
builder = builder.add_subkey(KeyFlags::empty()
.set_transport_encryption()
.set_storage_encryption(),
None,
None);
}
(Some("storage"), false) => {
builder = builder.add_storage_encryption_subkey();
}
(Some("transport"), false) => {
builder = builder.add_transport_encryption_subkey();
}
(None, true) => { }
(Some(_), true) => {
return Err(
anyhow::anyhow!("Conflicting arguments --can-encrypt and \
--cannot-encrypt"));
}
(Some(ref cap), false) => {
return Err(
anyhow::anyhow!("Unknown encryption capability '{}'", cap));
}
}
if m.is_present("with-password") {
let p0 = rpassword::read_password_from_tty(Some(
"Enter password to protect the key: "))?.into();
let p1 = rpassword::read_password_from_tty(Some(
"Repeat the password once more: "))?.into();
if p0 == p1 {
builder = builder.set_password(Some(p0));
} else {
return Err(anyhow::anyhow!("Passwords do not match."));
}
}
let (cert, rev) = builder.generate()?;
if m.is_present("export") {
let (key_path, rev_path) =
match (m.value_of("export"), m.value_of("rev-cert")) {
(Some("-"), Some("-")) =>
("-".to_string(), "-".to_string()),
(Some("-"), Some(ref rp)) =>
("-".to_string(), rp.to_string()),
(Some("-"), None) =>
return Err(
anyhow::anyhow!("Missing arguments: --rev-cert is mandatory \
if --export is '-'.")),
(Some(ref kp), None) =>
(kp.to_string(), format!("{}.rev", kp)),
(Some(ref kp), Some("-")) =>
(kp.to_string(), "-".to_string()),
(Some(ref kp), Some(ref rp)) =>
(kp.to_string(), rp.to_string()),
_ =>
return Err(
anyhow::anyhow!("Conflicting arguments --rev-cert and \
--export")),
};
let headers = cert.armor_headers();
{
let headers: Vec<_> = headers.iter()
.map(|value| ("Comment", value.as_str()))
.collect();
let w = create_or_stdout(Some(&key_path), force)?;
let mut w = Writer::with_headers(w, Kind::SecretKey, headers)?;
cert.as_tsk().serialize(&mut w)?;
w.finalize()?;
}
{
let mut headers: Vec<_> = headers.iter()
.map(|value| ("Comment", value.as_str()))
.collect();
headers.insert(0, ("Comment", "Revocation certificate for"));
let w = create_or_stdout(Some(&rev_path), force)?;
let mut w = Writer::with_headers(w, Kind::Signature, headers)?;
Packet::Signature(rev).serialize(&mut w)?;
w.finalize()?;
}
} else {
return Err(
anyhow::anyhow!("Saving generated key to the store isn't implemented \
yet."));
}
Ok(())
}
pub fn adopt(config: Config, m: &ArgMatches, p: &dyn Policy) -> Result<()> {
let input = open_or_stdin(m.value_of("certificate"))?;
let cert = Cert::from_reader(input)?;
let mut wanted: Vec<(KeyHandle,
Option<(Key<key::PublicParts, key::SubordinateRole>,
SignatureBuilder)>)>
= vec![];
for id in m.values_of("key").unwrap_or_default() {
let h = keyhandle_from_str(&id)?;
if keyhandle_is_invalid(&h) {
return Err(anyhow::anyhow!(
"Invalid Fingerprint or KeyID ('{:?}')", id));
}
wanted.push((h, None));
}
let null_policy = &crate::openpgp::policy::NullPolicy::new();
let adoptee_policy = if m.values_of("allow-broken-crypto").is_some() {
null_policy
} else {
p
};
for keyring in m.values_of("keyring").unwrap_or_default() {
for cert in CertParser::from_file(keyring)
.context(format!("Parsing: {}", keyring))?
{
let cert = cert.context(format!("Parsing {}", keyring))?;
let vc = match cert.with_policy(adoptee_policy, None) {
Ok(vc) => vc,
Err(err) => {
eprintln!("Ignoring {} from '{}': {}",
cert.keyid().to_hex(), keyring, err);
continue;
}
};
for key in vc.keys() {
for (id, ref mut keyo) in wanted.iter_mut() {
if id.aliases(key.key_handle()) {
match keyo {
Some((_, _)) =>
(),
None => {
let sig = key.binding_signature();
let builder: SignatureBuilder = match sig.typ() {
SignatureType::SubkeyBinding =>
sig.clone().into(),
SignatureType::DirectKey
| SignatureType::PositiveCertification
| SignatureType::CasualCertification
| SignatureType::PersonaCertification
| SignatureType::GenericCertification =>
{
let kf = sig.key_flags()
.context("Missing required \
subpacket, KeyFlags")?;
SignatureBuilder::new(
SignatureType::SubkeyBinding)
.set_key_flags(kf)?
},
_ => panic!("Unsupported binding \
signature: {:?}",
sig),
};
*keyo = Some(
(key.key().clone().role_into_subordinate(),
builder));
}
}
}
}
}
}
}
let missing: Vec<&KeyHandle> = wanted
.iter()
.filter_map(|(id, keyo)| {
match keyo {
Some(_) => None,
None => Some(id),
}
})
.collect();
if missing.len() > 0 {
return Err(anyhow::anyhow!(
"Keys not found: {}",
missing.iter().map(|&h| format!("{:X}", h)).join(", ")));
}
let passwords = &mut Vec::new();
let pk = cert.primary_key().key();
let mut pk_signer =
decrypt_key(
pk.clone().parts_into_secret()?,
passwords)?
.into_keypair()?;
let mut packets: Vec<Packet> = vec![];
for (_, ka) in wanted.into_iter() {
let (key, builder) = ka.expect("Checked for missing keys above.");
let mut builder = builder;
let need_backsig = builder.key_flags()
.map(|kf| kf.for_signing() || kf.for_certification())
.expect("Missing keyflags");
if need_backsig {
let mut subkey_signer
= decrypt_key(
key.clone().parts_into_secret()?,
passwords)?
.into_keypair()?;
let backsig = builder.embedded_signatures()
.filter(|backsig| {
(*backsig).clone().verify_primary_key_binding(
&cert.primary_key(),
&key).is_ok()
})
.nth(0)
.map(|sig| SignatureBuilder::from(sig.clone()))
.unwrap_or_else(|| {
SignatureBuilder::new(SignatureType::PrimaryKeyBinding)
})
.sign_primary_key_binding(&mut subkey_signer, pk, &key)?;
builder = builder.set_embedded_signature(backsig)?;
} else {
builder = builder.modify_hashed_area(|mut a| {
a.remove_all(SubpacketTag::EmbeddedSignature);
Ok(a)
})?;
}
let mut sig = builder.sign_subkey_binding(&mut pk_signer, pk, &key)?;
assert!(sig.verify_subkey_binding(pk_signer.public(), pk, &key)
.is_ok());
packets.push(key.into());
packets.push(sig.into());
}
let cert = cert.clone().insert_packets(packets.clone())?;
let mut message = crate::create_or_stdout_pgp(
m.value_of("output"), config.force,
m.is_present("binary"), sequoia_openpgp::armor::Kind::SecretKey)?;
cert.as_tsk().serialize(&mut message)?;
message.finalize()?;
let vc = cert.with_policy(p, None).expect("still valid");
for pair in packets[..].chunks(2) {
let newkey: &Key<key::PublicParts, key::UnspecifiedRole> = match pair[0] {
Packet::PublicKey(ref k) => k.into(),
Packet::PublicSubkey(ref k) => k.into(),
Packet::SecretKey(ref k) => k.into(),
Packet::SecretSubkey(ref k) => k.into(),
ref p => panic!("Expected a key, got: {:?}", p),
};
let newsig: &Signature = match pair[1] {
Packet::Signature(ref s) => s,
ref p => panic!("Expected a sig, got: {:?}", p),
};
let mut found = false;
for key in vc.keys() {
if key.fingerprint() == newkey.fingerprint() {
for sig in key.self_signatures() {
if sig == newsig {
found = true;
break;
}
}
}
}
assert!(found, "Subkey: {:?}\nSignature: {:?}", newkey, newsig);
}
Ok(())
}
pub fn attest_certifications(config: Config, m: &ArgMatches, _p: &dyn Policy)
-> Result<()> {
use sequoia_openpgp::{
crypto::hash::{Hash, Digest},
packet::signature::subpacket::*,
types::HashAlgorithm,
};
#[allow(non_upper_case_globals)]
const SignatureType__AttestedKey: SignatureType =
SignatureType::Unknown(0x16);
#[allow(non_upper_case_globals)]
const SubpacketTag__AttestedCertifications: SubpacketTag =
SubpacketTag::Unknown(37);
let all = ! m.is_present("none");
let hash_algo = HashAlgorithm::default();
let digest_size = hash_algo.context()?.digest_size();
let reserve_area_space = 256; let digests_per_sig = ((1usize << 16) - reserve_area_space) / digest_size;
let input = open_or_stdin(m.value_of("key"))?;
let key = Cert::from_reader(input)?;
let key = Cert::from_packets(
key.into_packets().filter(|p| match p {
Packet::Signature(s) if s.typ() == SignatureType__AttestedKey =>
false,
_ => true,
}))?;
let mut passwords = Vec::new();
let pk = key.primary_key().key();
let mut pk_signer =
decrypt_key(
pk.clone().parts_into_secret()?,
&mut passwords)?
.into_keypair()?;
let mut attestation_signatures = Vec::new();
for uid in key.userids() {
let mut attestations = Vec::new();
if all {
for certification in uid.certifications() {
let mut h = hash_algo.context()?;
hash_for_confirmation(certification, &mut h);
attestations.push(h.into_digest()?);
}
}
attestations.sort();
let t = std::time::SystemTime::now();
let mut hash = hash_algo.context()?;
key.primary_key().hash(&mut hash);
uid.hash(&mut hash);
for digests in attestations.chunks(digests_per_sig) {
let mut body = Vec::with_capacity(digest_size * digests.len());
digests.iter().for_each(|d| body.extend(d));
attestation_signatures.push(
SignatureBuilder::new(SignatureType__AttestedKey)
.set_signature_creation_time(t)?
.modify_hashed_area(|mut a| {
a.add(Subpacket::new(
SubpacketValue::Unknown {
tag: SubpacketTag__AttestedCertifications,
body,
},
true)?)?;
Ok(a)
})?
.sign_hash(&mut pk_signer, hash.clone())?);
}
}
for ua in key.user_attributes() {
let mut attestations = Vec::new();
if all {
for certification in ua.certifications() {
let mut h = hash_algo.context()?;
hash_for_confirmation(certification, &mut h);
attestations.push(h.into_digest()?);
}
}
attestations.sort();
let t = std::time::SystemTime::now();
let mut hash = hash_algo.context()?;
key.primary_key().hash(&mut hash);
ua.hash(&mut hash);
for digests in attestations.chunks(digests_per_sig) {
let mut body = Vec::with_capacity(digest_size * digests.len());
digests.iter().for_each(|d| body.extend(d));
attestation_signatures.push(
SignatureBuilder::new(SignatureType__AttestedKey)
.set_signature_creation_time(t)?
.modify_hashed_area(|mut a| {
a.add(Subpacket::new(
SubpacketValue::Unknown {
tag: SubpacketTag__AttestedCertifications,
body,
},
true)?)?;
Ok(a)
})?
.sign_hash(&mut pk_signer, hash.clone())?);
}
}
let key = key.insert_packets(attestation_signatures)?;
let mut message = crate::create_or_stdout_pgp(
m.value_of("output"), config.force, m.is_present("binary"),
sequoia_openpgp::armor::Kind::SecretKey)?;
key.as_tsk().serialize(&mut message)?;
message.finalize()?;
Ok(())
}
fn keyhandle_from_str(s: &str) -> Result<KeyHandle> {
use sequoia_openpgp::{Fingerprint, KeyID};
let bytes = &sequoia_openpgp::fmt::hex::decode_pretty(s)?[..];
match Fingerprint::from_bytes(bytes) {
fpr @ Fingerprint::Invalid(_) => {
match KeyID::from_bytes(bytes) {
KeyID::Invalid(_) => Ok(fpr.into()),
kid => Ok(kid.into()),
}
}
fpr => Ok(fpr.into()),
}
}
fn keyhandle_is_invalid(h: &KeyHandle) -> bool {
use sequoia_openpgp::{Fingerprint, KeyID};
match h {
KeyHandle::Fingerprint(Fingerprint::Invalid(_)) => true,
KeyHandle::KeyID(KeyID::Invalid(_)) => true,
_ => false,
}
}
use sequoia_openpgp::{crypto::hash::Digest, packet::Signature};
pub fn hash_for_confirmation(sig: &Signature, hash: &mut dyn Digest) {
use sequoia_openpgp::serialize::{Marshal, MarshalInto};
let mut body = Vec::new();
body.push(sig.version());
body.push(sig.typ().into());
body.push(sig.pk_algo().into());
body.push(sig.hash_algo().into());
let l = sig.hashed_area().serialized_len()
.min(std::u16::MAX as usize);
body.extend(&(l as u16).to_be_bytes());
let _ = sig.hashed_area().serialize(&mut body);
body.extend(&[0, 0]);
body.extend(sig.digest_prefix());
let _ = sig.mpis().serialize(&mut body);
hash.update(&[0x88]);
hash.update(&(body.len() as u32).to_be_bytes());
hash.update(&body);
}