Fix adopting subkeys backed by the softkeys backend into softkeys.

- Fixes #333.
This commit is contained in:
Justus Winter 2024-09-27 17:51:52 +02:00
parent d4a6b09b45
commit b4158f40de
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386

View File

@ -20,6 +20,7 @@ use crate::cli;
use cli::types::EncryptPurpose;
use crate::cli::types::FileOrStdout;
use crate::cli::types::FileStdinOrKeyHandle;
use crate::common::password;
pub fn adopt(sq: Sq, mut command: cli::key::AdoptCommand) -> Result<()>
{
@ -199,7 +200,7 @@ pub fn adopt(sq: Sq, mut command: cli::key::AdoptCommand) -> Result<()>
// Add the keys and signatures to cert.
let mut packets: Vec<Packet> = vec![];
for (cert, key, mut builder) in wanted.into_iter() {
for (cert, mut key, mut builder) in wanted.into_iter() {
// Set key expiration.
if let Some(e) = &command.expiration {
builder = builder.set_key_expiration_time(&key, e.timestamp())?;
@ -214,6 +215,99 @@ pub fn adopt(sq: Sq, mut command: cli::key::AdoptCommand) -> Result<()>
key.fingerprint()));
};
// If the key we got from the certificate does not contain a
// secret (for example, because we got it from the cert
// store), try to get it from the key store. This will only
// succeed for the softkeys backend. But, for the softkeys
// backend it is necessary to actually get the secret key
// material, so that we have secret key material to import
// back into the softkeys backend.
//
// Note that keys backed by the softkeys backend can also
// adopt keys with material backed by hardware keys. In this
// case, we'll import the public key and binding signature
// into the cert store, and the change will not be reflected
// in the key store.
// If we have to prompt for a password in order to identify
// the right key, at least store the keypair so that we don't
// have to ask again.
let mut keypair = None;
if key.optional_secret().is_none() {
let ks = sq.key_store_or_else()?;
let mut ks = ks.lock().unwrap();
// Try to get secrets from the store.
let secrets = ks.find_key(key.key_handle())?.into_iter()
.filter_map(|mut k| k.export().ok()).collect::<Vec<_>>();
match secrets.len() {
0 => (),
1 => key = secrets.into_iter().next().unwrap().into(),
_ => if secrets.iter().all(|k| ! k.secret().is_encrypted()) {
// There is more than one variant of the secret
// key, and all of them are unlocked. Pick one.
let k = secrets.into_iter().next().unwrap();
keypair =
Some(k.clone().into_keypair()?);
key = k.into();
} else {
// There is more than one variant of the secret
// key. Prompt for a password to unlock one, so
// that we know which one the user wants. This is
// a bit annoying, but on the plus side we don't
// need to ask the user again to create the
// primary key binding signature.
let uid = sq.best_userid(&cert, true);
'password_loop: loop {
let p = password::prompt_to_unlock(&sq, &format!(
"{}/{}, {}",
cert.keyid(), key.keyid(), uid))?;
// Empty password given and a key without
// encryption? Pick it.
if p.map(|p| p.is_empty()) {
if let Some(k) = secrets.iter()
.find(|k| ! k.secret().is_encrypted())
{
keypair =
Some(k.clone().into_keypair()?);
key = k.clone().into();
break;
}
}
let mut err = None;
for k in &secrets {
match k.secret().clone().decrypt(key.pk_algo(), &p) {
Ok(decrypted) => {
// Keep the decrypted keypair.
keypair = Some({
let k = key.add_secret(decrypted).0;
k.clone().into_keypair()?
});
// Adopt the encrypted key.
key = k.clone().into();
break 'password_loop;
},
Err(e) => err = Some(e),
}
}
if p == "".into() {
wprintln!("Giving up.");
return Err(anyhow::anyhow!(
"Failed to unlock key: {}",
err.expect("must be set when we came here")));
}
}
},
}
}
// If we need a valid backsig, create it.
if key_flags.for_signing() || key_flags.for_certification() {
// Derive a signer.
@ -221,10 +315,14 @@ pub fn adopt(sq: Sq, mut command: cli::key::AdoptCommand) -> Result<()>
.next()
.expect("have key");
let mut subkey_signer = sq.get_signer(&ka)
.with_context(|| {
format!("Getting signer for {}", ka.fingerprint())
})?;
let mut subkey_signer = if let Some(k) = keypair {
Box::new(k)
} else {
sq.get_signer(&ka)
.with_context(|| {
format!("Getting signer for {}", ka.fingerprint())
})?
};
let backsig = builder
.embedded_signatures()