summaryrefslogtreecommitdiffstats
path: root/tool/src/commands
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2020-10-14 11:43:23 +0200
committerJustus Winter <justus@sequoia-pgp.org>2020-10-14 12:16:15 +0200
commit653960de3dfe19c9b46daad38efbc4a49a7676f2 (patch)
treedd1c0c5963667dd06b717e69a0dd40df0a94cd68 /tool/src/commands
parent2079449be080b4da3cdc8c6f9972f4ac48e58aaf (diff)
sq: Rename module to 'sequoia-sq'.
Diffstat (limited to 'tool/src/commands')
-rw-r--r--tool/src/commands/decrypt.rs354
-rw-r--r--tool/src/commands/dump.rs946
-rw-r--r--tool/src/commands/inspect.rs412
-rw-r--r--tool/src/commands/key.rs248
-rw-r--r--tool/src/commands/mod.rs515
-rw-r--r--tool/src/commands/sign.rs347
6 files changed, 0 insertions, 2822 deletions
diff --git a/tool/src/commands/decrypt.rs b/tool/src/commands/decrypt.rs
deleted file mode 100644
index 07adabe6..00000000
--- a/tool/src/commands/decrypt.rs
+++ /dev/null
@@ -1,354 +0,0 @@
-use crossterm::terminal;
-use anyhow::Context as _;
-use std::collections::HashMap;
-use std::io;
-use rpassword;
-
-use sequoia_openpgp as openpgp;
-use sequoia_core::Context;
-use crate::openpgp::types::SymmetricAlgorithm;
-use crate::openpgp::fmt::hex;
-use crate::openpgp::crypto::{self, SessionKey};
-use crate::openpgp::{Fingerprint, Cert, KeyID, Result};
-use crate::openpgp::packet;
-use crate::openpgp::packet::prelude::*;
-use crate::openpgp::parse::{
- Parse,
- PacketParser,
- PacketParserResult,
-};
-use crate::openpgp::parse::stream::{
- VerificationHelper, DecryptionHelper, DecryptorBuilder, MessageStructure,
-};
-use crate::openpgp::policy::Policy;
-use sequoia_store as store;
-
-use super::{dump::PacketDumper, VHelper};
-
-struct Helper<'a> {
- vhelper: VHelper<'a>,
- secret_keys:
- HashMap<KeyID, Key<key::SecretParts, key::UnspecifiedRole>>,
- key_identities: HashMap<KeyID, Fingerprint>,
- key_hints: HashMap<KeyID, String>,
- dump_session_key: bool,
- dumper: Option<PacketDumper>,
-}
-
-impl<'a> Helper<'a> {
- fn new(ctx: &'a Context, policy: &'a dyn Policy,
- mapping: &'a mut store::Mapping,
- signatures: usize, certs: Vec<Cert>, secrets: Vec<Cert>,
- dump_session_key: bool, dump: bool)
- -> Self
- {
- let mut keys = HashMap::new();
- let mut identities: HashMap<KeyID, Fingerprint> = HashMap::new();
- let mut hints: HashMap<KeyID, String> = HashMap::new();
- for tsk in secrets {
- let hint = match tsk.with_policy(policy, None)
- .and_then(|valid_cert| valid_cert.primary_userid()).ok()
- {
- Some(uid) => format!("{} ({})", uid.userid(),
- KeyID::from(tsk.fingerprint())),
- None => format!("{}", KeyID::from(tsk.fingerprint())),
- };
-
- for ka in tsk.keys()
- // XXX: Should use the message's creation time that we do not know.
- .with_policy(policy, None)
- .for_transport_encryption().for_storage_encryption()
- .secret()
- {
- let id: KeyID = ka.key().fingerprint().into();
- keys.insert(id.clone(), ka.key().clone().into());
- identities.insert(id.clone(), tsk.fingerprint());
- hints.insert(id, hint.clone());
- }
- }
-
- Helper {
- vhelper: VHelper::new(ctx, mapping, signatures, certs),
- secret_keys: keys,
- key_identities: identities,
- key_hints: hints,
- dump_session_key: dump_session_key,
- dumper: if dump {
- let width = terminal::size().ok().map(|(cols, _)| cols as usize)
- .unwrap_or(80);
- Some(PacketDumper::new(width, false))
- } else {
- None
- },
- }
- }
-
- /// Tries to decrypt the given PKESK packet with `keypair` and try
- /// to decrypt the packet parser using `decrypt`.
- fn try_decrypt<D>(&self, pkesk: &PKESK,
- sym_algo: Option<SymmetricAlgorithm>,
- keypair: &mut dyn crypto::Decryptor,
- decrypt: &mut D)
- -> Option<Option<Fingerprint>>
- where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool
- {
- let keyid = keypair.public().fingerprint().into();
- match pkesk.decrypt(keypair, sym_algo)
- .and_then(|(algo, sk)| {
- if decrypt(algo, &sk) { Some(sk) } else { None }
- })
- {
- Some(sk) => {
- if self.dump_session_key {
- eprintln!("Session key: {}", hex::encode(&sk));
- }
- Some(self.key_identities.get(&keyid).map(|fp| fp.clone()))
- },
- None => None,
- }
- }
-}
-
-impl<'a> VerificationHelper for Helper<'a> {
- fn inspect(&mut self, pp: &PacketParser) -> Result<()> {
- if let Some(dumper) = self.dumper.as_mut() {
- dumper.packet(&mut io::stderr(),
- pp.recursion_depth() as usize,
- pp.header().clone(), pp.packet.clone(),
- pp.map().map(|m| m.clone()), None)?;
- }
- Ok(())
- }
-
- fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> Result<Vec<Cert>> {
- self.vhelper.get_certs(ids)
- }
- fn check(&mut self, structure: MessageStructure) -> Result<()> {
- self.vhelper.check(structure)
- }
-}
-
-impl<'a> DecryptionHelper for Helper<'a> {
- fn decrypt<D>(&mut self, pkesks: &[PKESK], skesks: &[SKESK],
- sym_algo: Option<SymmetricAlgorithm>,
- mut decrypt: D) -> openpgp::Result<Option<Fingerprint>>
- where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool
- {
- // First, we try those keys that we can use without prompting
- // for a password.
- for pkesk in pkesks {
- let keyid = pkesk.recipient();
- if let Some(key) = self.secret_keys.get(&keyid) {
- if ! key.secret().is_encrypted() {
- if let Some(fp) = key.clone().into_keypair().ok()
- .and_then(|mut k|
- self.try_decrypt(pkesk, sym_algo, &mut k, &mut decrypt))
- {
- return Ok(fp);
- }
- }
- }
- }
-
- // Second, we try those keys that are encrypted.
- for pkesk in pkesks {
- // Don't ask the user to decrypt a key if we don't support
- // the algorithm.
- if ! pkesk.pk_algo().is_supported() {
- continue;
- }
-
- let keyid = pkesk.recipient();
- if let Some(key) = self.secret_keys.get_mut(&keyid) {
- let mut keypair = loop {
- if ! key.secret().is_encrypted() {
- break key.clone().into_keypair().unwrap();
- }
-
- let p = rpassword::read_password_from_tty(Some(
- &format!(
- "Enter password to decrypt key {}: ",
- self.key_hints.get(&keyid).unwrap())))?.into();
-
- let algo = key.pk_algo();
- if let Some(()) =
- key.secret_mut().decrypt_in_place(algo, &p).ok() {
- break key.clone().into_keypair().unwrap()
- } else {
- eprintln!("Bad password.");
- }
- };
-
- if let Some(fp) =
- self.try_decrypt(pkesk, sym_algo, &mut keypair,
- &mut decrypt)
- {
- return Ok(fp);
- }
- }
- }
-
- // Third, we try to decrypt PKESK packets with wildcard
- // recipients using those keys that we can use without
- // prompting for a password.
- for pkesk in pkesks.iter().filter(|p| p.recipient().is_wildcard()) {
- for key in self.secret_keys.values() {
- if ! key.secret().is_encrypted() {
- if let Some(fp) = key.clone().into_keypair().ok()
- .and_then(|mut k|
- self.try_decrypt(pkesk, sym_algo, &mut k, &mut decrypt))
- {
- return Ok(fp);
- }
- }
- }
- }
-
- // Fourth, we try to decrypt PKESK packets with wildcard
- // recipients using those keys that are encrypted.
- for pkesk in pkesks.iter().filter(|p| p.recipient().is_wildcard()) {
- // Don't ask the user to decrypt a key if we don't support
- // the algorithm.
- if ! pkesk.pk_algo().is_supported() {
- continue;
- }
-
- // To appease the borrow checker, iterate over the
- // hashmap, awkwardly.
- for keyid in self.secret_keys.keys().cloned().collect::<Vec<_>>()
- {
- let mut keypair = loop {
- let key = self.secret_keys.get_mut(&keyid).unwrap(); // Yuck
-
- if ! key.secret().is_encrypted() {
- break key.clone().into_keypair().unwrap();
- }
-
- let p = rpassword::read_password_from_tty(Some(
- &format!(
- "Enter password to decrypt key {}: ",
- self.key_hints.get(&keyid).unwrap())))?.into();
-
- let algo = key.pk_algo();
- if let Some(()) =
- key.secret_mut().decrypt_in_place(algo, &p).ok() {
- break key.clone().into_keypair().unwrap()
- } else {
- eprintln!("Bad password.");
- }
- };
-
- if let Some(fp) =
- self.try_decrypt(pkesk, sym_algo, &mut keypair,
- &mut decrypt)
- {
- return Ok(fp);
- }
- }
- }
-
- if skesks.is_empty() {
- return
- Err(anyhow::anyhow!("No key to decrypt message"));
- }
-
- // Finally, try to decrypt using the SKESKs.
- loop {
- let password =
- rpassword::read_password_from_tty(Some(
- "Enter password to decrypt message: "))?.into();
-
- for skesk in skesks {
- if let Some(sk) = skesk.decrypt(&password).ok()
- .and_then(|(algo, sk)| { if decrypt(algo, &sk) { Some(sk) } else { None }})
- {
- if self.dump_session_key {
- eprintln!("Session key: {}", hex::encode(&sk));
- }
- return Ok(None);
- }
- }
-
- eprintln!("Bad password.");
- }
- }
-}
-
-pub fn decrypt(ctx: &Context, policy: &dyn Policy, mapping: &mut store::Mapping,
- input: &mut dyn io::Read, output: &mut dyn io::Write,
- signatures: usize, certs: Vec<Cert>, secrets: Vec<Cert>,
- dump_session_key: bool,
- dump: bool, hex: bool)
- -> Result<()> {
- let helper = Helper::new(ctx, policy, mapping, signatures, certs, secrets,
- dump_session_key, dump || hex);
- let mut decryptor = DecryptorBuilder::from_reader(input)?
- .mapping(hex)
- .with_policy(policy, None, helper)
- .context("Decryption failed")?;
-
- io::copy(&mut decryptor, output).context("Decryption failed")?;
-
- let helper = decryptor.into_helper();
- if let Some(dumper) = helper.dumper.as_ref() {
- dumper.flush(&mut io::stderr())?;
- }
- helper.vhelper.print_status();
- return Ok(());
-}
-
-pub fn decrypt_unwrap(ctx: &Context, policy: &dyn Policy,
- mapping: &mut store::Mapping,
- input: &mut dyn io::Read, output: &mut dyn io::Write,
- secrets: Vec<Cert>, dump_session_key: bool)
- -> Result<()>
-{
- let mut helper = Helper::new(ctx, policy, mapping, 0, Vec::new(), secrets,
- dump_session_key, false);
-
- let mut ppr = PacketParser::from_reader(input)?;
-
- let mut pkesks: Vec<packet::PKESK> = Vec::new();
- let mut skesks: Vec<packet::SKESK> = Vec::new();
- while let PacketParserResult::Some(mut pp) = ppr {
- let sym_algo_hint = if let Packet::AED(ref aed) = pp.packet {
- Some(aed.symmetric_algo())
- } else {
- None
- };
-
- match pp.packet {
- Packet::SEIP(_) | Packet::AED(_) => {
- {
- let decrypt = |algo, secret: &SessionKey| {
- pp.decrypt(algo, secret).is_ok()
- };
- helper.decrypt(&pkesks[..], &skesks[..], sym_algo_hint,
- decrypt)?;
- }
- if pp.encrypted() {
- return Err(
- openpgp::Error::MissingSessionKey(
- "No session key".into()).into());
- }
-
- io::copy(&mut pp, output)?;
- return Ok(());
- },
- Packet::MDC(ref mdc) => if ! mdc.valid() {
- return Err(openpgp::Error::ManipulatedMessage.into());
- },
- _ => (),
- }
-
- let (p, ppr_tmp) = pp.recurse()?;
- match p {
- Packet::PKESK(pkesk) => pkesks.push(pkesk),
- Packet::SKESK(skesk) => skesks.push(skesk),
- _ => (),
- }
- ppr = ppr_tmp;
- }
-
- Ok(())
-}
diff --git a/tool/src/commands/dump.rs b/tool/src/commands/dump.rs
deleted file mode 100644
index 6f35f56d..00000000
--- a/tool/src/commands/dump.rs
+++ /dev/null
@@ -1,946 +0,0 @@
-use std::io::{self, Read};
-
-use sequoia_openpgp as openpgp;
-use self::openpgp::types::{Duration, Timestamp, SymmetricAlgorithm};
-use self::openpgp::fmt::hex;
-use self::openpgp::crypto::mpi;
-use self::openpgp::{Packet, Result};
-use self::openpgp::packet::prelude::*;
-use self::openpgp::packet::header::CTB;
-use self::openpgp::packet::{Header, header::BodyLength, Signature};
-use self::openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue};
-use self::openpgp::crypto::{SessionKey, S2K};
-use self::openpgp::parse::{map::Map, Parse, PacketParserResult};
-
-#[derive(Debug)]
-pub enum Kind {
- Message {
- encrypted: bool,
- },
- Keyring,
- Cert,
- Unknown,
-}
-
-/// Converts sequoia_openpgp types for rendering.
-pub trait Convert<T> {
- /// Performs the conversion.
- fn convert(self) -> T;
-}
-
-impl Convert<chrono::Duration> for std::time::Duration {
- fn convert(self) -> chrono::Duration {
- chrono::Duration::seconds(self.as_secs() as i64)
- }
-}
-
-impl Convert<chrono::Duration> for Duration {
- fn convert(self) -> chrono::Duration {
- chrono::Duration::seconds(self.as_secs() as i64)
- }
-}
-
-impl Convert<chrono::DateTime<chrono::offset::Utc>> for std::time::SystemTime {
- fn convert(self) -> chrono::DateTime<chrono::offset::Utc> {
- chrono::DateTime::<chrono::offset::Utc>::from(self)
- }
-}
-
-impl Convert<chrono::DateTime<chrono::offset::Utc>> for Timestamp {
- fn convert(self) -> chrono::DateTime<chrono::offset::Utc> {
- std::time::SystemTime::from(self).convert()
- }
-}
-
-pub fn dump<W>(input: &mut dyn io::Read, output: &mut dyn io::Write,
- mpis: bool, hex: bool, sk: Option<&SessionKey>,
- width: W)
- -> Result<Kind>
- where W: Into<Option<usize>>
-{
- let mut ppr
- = self::openpgp::parse::PacketParserBuilder::from_reader(input)?
- .map(hex).build()?;
- let mut message_encrypted = false;
- let width = width.into().unwrap_or(80);
- let mut dumper = PacketDumper::new(width, mpis);
-
- while let PacketParserResult::Some(mut pp) = ppr {
- let additional_fields = match pp.packet {
- Packet::Literal(_) => {
- let mut prefix = vec![0; 40];
- let n = pp.read(&mut prefix)?;
- Some(vec![
- format!("Content: {:?}{}",
- String::from_utf8_lossy(&prefix[..n]),
- if n == prefix.len() { "..." } else { "" }),
- ])
- },
- Packet::SEIP(_) if sk.is_none() => {
- message_encrypted = true;
- Some(vec!["No session key supplied".into()])
- }
- Packet::SEIP(_) if sk.is_some() => {
- message_encrypted = true;
- let sk = sk.as_ref().unwrap();
- let mut decrypted_with = None;
- for algo in 1..20 {
- let algo = SymmetricAlgorithm::from(algo);
- if let Ok(size) = algo.key_size() {
- if size != sk.len() { continue; }
- } else {
- continue;
- }
-
- if let Ok(_) = pp.decrypt(algo, sk) {
- decrypted_with = Some(algo);
- break;
- }
- }
- let mut fields = Vec::new();
- fields.push(format!("Session key: {}", hex::encode(sk)));
- if let Some(algo) = decrypted_with {
- fields.push(format!("Symmetric algo: {}", algo));
- fields.push("Decryption successful".into());
- } else {
- fields.push("Decryption failed".into());
- }
- Some(fields)
- },
- Packet::AED(_) if sk.is_none() => {
- message_encrypted = true;
- Some(vec!["No session key supplied".into()])
- }
- Packet::AED(_) if sk.is_some() => {
- message_encrypted = true;
- let sk = sk.as_ref().unwrap();
- let algo = if let Packet::AED(ref aed) = pp.packet {
- aed.symmetric_algo()
- } else {
- unreachable!()
- };
-
- let _ = pp.decrypt(algo, sk);
-
- let mut fields = Vec::new();
- fields.push(format!("Session key: {}", hex::encode(sk)));
- if pp.encrypted() {
- fields.push("Decryption failed".into());
- } else {
- fields.push("Decryption successful".into());
- }
- Some(fields)
- },
- _ => None,
- };
-
- let header = pp.header().clone();
- let map = pp.take_map();
-
- let recursion_depth = pp.recursion_depth();
- let packet = pp.packet.clone();
-
- dumper.packet(output, recursion_depth as usize,
- header, packet, map, additional_fields)?;
-
- let (_, ppr_) = match pp.recurse() {
- Ok(v) => Ok(v),
- Err(e) => {
- let _ = dumper.flush(output);
- Err(e)
- },
- }?;
- ppr = ppr_;
- }
-
- dumper.flush(output)?;
-
- if let PacketParserResult::EOF(eof) = ppr {
- if eof.is_message().is_ok() {
- Ok(Kind::Message {
- encrypted: message_encrypted,
- })
- } else if eof.is_cert().is_ok() {
- Ok(Kind::Cert)
- } else if eof.is_keyring().is_ok() {
- Ok(Kind::Keyring)
- } else {
- Ok(Kind::Unknown)
- }
- } else {
- unreachable!()
- }
-}
-
-struct Node {
- header: Header,
- packet: Packet,
- map: Option<Map>,
- additional_fields: Option<Vec<String>>,
- children: Vec<Node>,
-}
-
-impl Node {
- fn new(header: Header, packet: Packet, map: Option<Map>,
- additional_fields: Option<Vec<String>>) -> Self {
- Node {
- header: header,
- packet: packet,
- map: map,
- additional_fields: additional_fields,
- children: Vec::new(),
- }
- }
-
- fn append(&mut self, depth: usize, node: Node) {
- if depth == 0 {
- self.children.push(node);
- } else {
- self.children.iter_mut().last().unwrap().append(depth - 1, node);
- }
- }
-}
-
-pub struct PacketDumper {
- width: usize,
- mpis: bool,
- root: Option<Node>,
-}
-
-impl PacketDumper {
- pub fn new(width: usize, mpis: bool) -> Self {
- PacketDumper {
- width: width,
- mpis: mpis,
- root: None,
- }
- }
-
- pub fn packet(&mut self, output: &mut dyn io::Write, depth: usize,
- header: Header, p: Packet, map: Option<Map>,
- additional_fields: Option<Vec<String>>)
- -> Result<()> {
- let node = Node::new(header, p, map, additional_fields);
- if self.root.is_none() {
- assert_eq!(depth, 0);
- self.root = Some(node);
- } else {
- if depth == 0 {
- let root = self.root.take().unwrap();
- self.dump_tree(output, "", &root)?;
- self.root = Some(node);
- } else {
- self.root.as_mut().unwrap().append(depth - 1, node);
- }
- }
- Ok(())
- }
-
- pub fn flush(&self, output: &mut dyn io::Write) -> Result<()> {
- if let Some(root) = self.root.as_ref() {
- self.dump_tree(output, "", &root)?;
- }
- Ok(())
- }
-
- fn dump_tree(&self, output: &mut dyn io::Write, indent: &str, node: &Node)
- -> Result<()> {
- let indent_node =
- format!("{}{} ", indent,
- if node.children.is_empty() { " " } else { "│" });
- self.dump_packet(output, &indent_node, Some(&node.header), &node.packet,
- node.map.as_ref(), node.additional_fields.as_ref())?;
- if node.children.is_empty() {
- return Ok(());
- }
-
- let last = node.children.len() - 1;
- for (i, child) in node.children.iter().enumerate() {
- let is_last = i == last;
- write!(output, "{}{}── ", indent,
- if is_last { "└" } else { "├" })?;
- let indent_child =
- format!("{}{} ", indent,
- if is_last { " " } else { "│" });
- self.dump_tree(output, &indent_child, child)?;
- }
- Ok(())
- }
-
- fn dump_packet(&self, output: &mut dyn io::Write, i: &str,
- header: Option<&Header>, p: &Packet, map: Option<&Map>,
- additional_fields: Option<&Vec<String>>)
- -> Result<()> {
- use self::openpgp::Packet::*;
-
- if let Some(tag) = p.kind() {
- write!(output, "{}", tag)?;
- } else {
- write!(output, "Unknown or Unsupported Packet")?;
- }
-
- if let Some(h) = header {
- write!(output, ", {} CTB, {}{}",
- if let CTB::Old(_) = h.ctb() { "old" } else { "new" },
- if let Some(map) = map {
- format!("{} header bytes + ",
- map.iter().take(2).map(|f| f.as_bytes().len())
- .sum::<usize>())
- } else {
- // XXX: Mapping is disabled. No can do for
- // now. Once we save the header in
- // packet::Common, we can use this instead of
- // relying on the map.
- "".into()
- },
- match h.length() {
- BodyLength::Full(n) =>
- format!("{} bytes", n),
- BodyLength::Partial(n) =>
- format!("partial length, {} bytes in first chunk", n),
- BodyLength::Indeterminate =>
- "indeterminate length".into(),
- })?;
- }
- writeln!(output)?;
-
- fn dump_key<P, R>(pd: &PacketDumper,
- output: &mut dyn io::Write, i: &str,
- k: &Key<P, R>)
- -> Result<()>
- where P: key::KeyParts,
- R: key::KeyRole,
- {
- writeln!(output, "{} Version: {}", i, k.version())?;
- writeln!(output, "{} Creation time: {}", i,
- k.creation_time().convert())?;
- writeln!(output, "{} Pk algo: {}", i, k.pk_algo())?;
- if let Some(bits) = k.mpis().bits() {
- writeln!(output, "{} Pk size: {} bits", i, bits)?;
- }
- writeln!(output, "{} Fingerprint: {}", i, k.fingerprint())?;
- writeln!(output, "{} KeyID: {}", i, k.keyid())?;
- if pd.mpis {
- writeln!(output, "{}", i)?;
- writeln!(output, "{} Public Key:", i)?;
-
- let ii = format!("{} ", i);
- match k.mpis() {
- mpi::PublicKey::RSA { e, n } =>
- pd.dump_mpis(output, &ii,
- &[e.value(), n.value()],
- &["e", "n"])?,
- mpi::PublicKey::DSA { p, q, g, y } =>
- pd.dump_mpis(output, &ii,
- &[p.value(), q.value(), g.value(),
- y.value()],
- &["p", "q", "g", "y"])?,
- mpi::PublicKey::ElGamal { p, g, y } =>
- pd.dump_mpis(output, &ii,
- &[p.value(), g.value(), y.value()],
- &["p", "g", "y"])?,
- mpi::PublicKey::EdDSA { curve, q } => {
- writeln!(output, "{} Curve: {}", ii, curve)?;
- pd.dump_mpis(output, &ii, &[q.value()], &["q"])?;
- },
- mpi::PublicKey::ECDSA { curve, q } => {
- writeln!(output, "{} Curve: {}", ii, curve)?;
- pd.dump_mpis(output, &ii, &[q.value()], &["q"])?;
- },
- mpi::PublicKey::ECDH { curve, q, hash, sym } => {
- writeln!(output, "{} Curve: {}", ii, curve)?;
- writeln!(output, "{} Hash algo: {}", ii, hash)?;
- writeln!(output, "{} Symmetric algo: {}", ii,
- sym)?;
- pd.dump_mpis(output, &ii, &[q.value()], &["q"])?;
- },
- mpi::PublicKey::Unknown { mpis, rest } => {
- let keys: Vec<String> =
- (0..mpis.len()).map(
- |i| format!("mpi{}", i)).collect();
- pd.dump_mpis(
- output, &ii,
- &mpis.iter().map(|m| {
- m.valu