Add a password cache to Sq.

- `sq cert line` manually manages a password cache.

  - Move the password cache to `Sq` so the functionality can be used
    elsewhere.
This commit is contained in:
Neal H. Walfield 2024-06-03 14:47:31 +02:00
parent 1a33500c26
commit a2c482e4f3
No known key found for this signature in database
GPG Key ID: 6863C9AD5B4D22D3
3 changed files with 52 additions and 47 deletions

View File

@ -13,7 +13,6 @@ use sequoia_openpgp as openpgp;
use openpgp::Result;
use openpgp::armor;
use openpgp::cert::prelude::*;
use openpgp::crypto::Password;
use openpgp::crypto::Signer;
use openpgp::parse::Parse;
use openpgp::packet::prelude::*;
@ -39,7 +38,6 @@ use crate::{
fn update_cert_revocation(sq: &Sq,
cert: &Cert, rev: &Signature,
passwords: &mut Vec<Password>,
reference_time: &SystemTime)
-> Result<Signature>
{
@ -49,10 +47,7 @@ fn update_cert_revocation(sq: &Sq,
let pk = ka.key();
// Derive a signer.
let (mut signer, password) = sq.get_signer(&ka, Some(&passwords))?;
if let Some(password) = password {
passwords.push(password);
}
let (mut signer, _password) = sq.get_signer(&ka)?;
let sig = SignatureBuilder::from(rev.clone())
.set_signature_creation_time(reference_time.clone())?
@ -73,7 +68,6 @@ const GOOD_HASHES: &[ HashAlgorithm ] = &[
// ua is using a weak policy.
fn update_user_id_binding(sq: &Sq,
ua: &ValidUserIDAmalgamation,
passwords: &mut Vec<Password>,
reference_time: &SystemTime)
-> Result<Signature>
{
@ -81,10 +75,7 @@ fn update_user_id_binding(sq: &Sq,
let pk = ka.key();
// Derive a signer.
let (mut signer, password) = sq.get_signer(&ka, Some(&passwords))?;
if let Some(password) = password {
passwords.push(password);
}
let (mut signer, _password) = sq.get_signer(&ka)?;
let sym = &[
SymmetricAlgorithm::AES128,
@ -144,7 +135,6 @@ fn update_user_id_binding(sq: &Sq,
// ka is using a weak policy.
fn update_subkey_binding<P>(sq: &Sq,
ka: &ValidSubordinateKeyAmalgamation<P>,
passwords: &mut Vec<Password>,
reference_time: &SystemTime)
-> Result<Signature>
where P: key::KeyParts + Clone
@ -153,10 +143,7 @@ fn update_subkey_binding<P>(sq: &Sq,
let pk = primary.key();
// Derive a signer.
let (mut signer, password) = sq.get_signer(&primary, Some(&passwords))?;
if let Some(password) = password {
passwords.push(password);
}
let (mut signer, _password) = sq.get_signer(&primary)?;
// Update the signature.
let sig = ka.binding_signature();
@ -174,10 +161,7 @@ fn update_subkey_binding<P>(sq: &Sq,
.nth(0)
{
// Derive a signer.
let (mut subkey_signer, password) = sq.get_signer(&ka, Some(&passwords))?;
if let Some(password) = password {
passwords.push(password);
}
let (mut subkey_signer, _password) = sq.get_signer(&ka)?;
let backsig = SignatureBuilder::from(backsig.clone())
.set_signature_creation_time(reference_time.clone())?
@ -254,8 +238,6 @@ pub fn lint(sq: Sq, mut args: Command) -> Result<()> {
let reference_time = sq.time;
let mut passwords = Vec::new();
let mut out = if args.output.is_some() || ! args.cert_file.is_empty() {
let output = if let Some(output) = args.output {
output
@ -464,8 +446,7 @@ pub fn lint(sq: Sq, mut args: Command) -> Result<()> {
rev.digest_prefix()[1]);
if args.fix {
match update_cert_revocation(
&sq, &cert, rev, &mut passwords,
&reference_time)
&sq, &cert, rev, &reference_time)
{
Ok(sig) => {
updates.push(sig);
@ -588,7 +569,7 @@ pub fn lint(sq: Sq, mut args: Command) -> Result<()> {
}
if args.fix {
match update_user_id_binding(
&sq, &ua, &mut passwords, &reference_time)
&sq, &ua, &reference_time)
{
Ok(sig) => {
updates.push(sig);
@ -641,8 +622,7 @@ pub fn lint(sq: Sq, mut args: Command) -> Result<()> {
}
if args.fix {
match update_subkey_binding(
&sq, &ka, &mut passwords,
&reference_time)
&sq, &ka, &reference_time)
{
Ok(sig) => updates.push(sig),
Err(err) => {
@ -730,7 +710,7 @@ pub fn lint(sq: Sq, mut args: Command) -> Result<()> {
}
if args.fix {
match update_subkey_binding(
&sq, &ka, &mut passwords, &reference_time)
&sq, &ka, &reference_time)
{
Ok(sig) => updates.push(sig),
Err(err) => {

View File

@ -372,6 +372,7 @@ fn main() -> Result<()> {
no_key_store: c.no_key_store,
key_store_path: c.key_store.clone(),
key_store: OnceCell::new(),
password_cache: Default::default(),
};
commands::dispatch(sq, c)

View File

@ -98,6 +98,15 @@ pub struct Sq<'store, 'rstore>
pub no_key_store: bool,
pub key_store_path: Option<PathBuf>,
pub key_store: OnceCell<Mutex<keystore::Keystore>>,
/// A password cache. When encountering a locked key, we first
/// consult the password cache. The passwords are only tried if
/// it is safe. That is, the passwords are only tried if we are
/// sure that the key is not protected by a retry counter. If the
/// password cache doesn't contain the correct password, or the
/// key is protected by a retry counter, the user is prompted to
/// unlock the key. The correct password is added to the cache.
pub password_cache: Mutex<Vec<Password>>,
}
impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
@ -1000,24 +1009,34 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
}
}
/// Caches a password.
pub fn cache_password(&self, password: Password) {
let mut cache = self.password_cache.lock().unwrap();
if ! cache.contains(&password) {
cache.push(password);
}
}
/// Returns the cached passwords.
pub fn cached_passwords(&self) -> impl Iterator<Item=Password> {
self.password_cache.lock().unwrap().clone().into_iter()
}
/// Gets a signer for the specified key.
///
/// If `ka` includes secret key material, that is preferred.
/// Otherwise, we look for the key on the key store.
///
/// If the key is locked, we unlocked. `passwords` is a list of
/// passwords to try. The passwords are only tried if it is safe.
/// That is, the passwords are only tried if we are sure that the
/// key is not protected by a retry counter. If `passwords`
/// doesn't contain the correct password, or the key is protected
/// by a retry counter, the user is prompted to unlock the key.
pub fn get_signer<P, R, R2>(&self, ka: &KeyAmalgamation<P, R, R2>,
passwords: Option<&[Password]>)
/// If the key is locked, we try to unlock it. If the key isn't
/// protected by a retry counter, then the password cache is
/// tried. Otherwise, or if that fails, the user is prompted to
/// unlock the key. The correct password is added to the password
/// cache.
pub fn get_signer<P, R, R2>(&self, ka: &KeyAmalgamation<P, R, R2>)
-> Result<(Box<dyn crypto::Signer + Send + Sync>, Option<Password>)>
where P: key::KeyParts + Clone, R: key::KeyRole + Clone
{
let passwords = passwords.unwrap_or(&[]);
let try_tsk = |ka: &KeyAmalgamation<_, _, R2>|
-> Result<(_, _)>
{
@ -1025,8 +1044,8 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
let (unencrypted, password) = match secret {
SecretKeyMaterial::Encrypted(ref e) => {
// try passwords from already existing keys
match passwords.iter().find_map(|password| {
e.decrypt(ka.pk_algo(), password).ok()
match self.cached_passwords().find_map(|password| {
e.decrypt(ka.pk_algo(), &password).ok()
.map(|key| (key, password.clone()))
}) {
Some((unencrypted, password)) =>
@ -1036,11 +1055,13 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
&format!("key {}/{}",
ka.cert().keyid(),
ka.keyid()))?;
(
e.decrypt(ka.pk_algo(), &password)
.map_err(|_| anyhow!("Incorrect password."))?,
Some(password),
)
let key = e.decrypt(ka.pk_algo(), &password)
.map_err(|_| anyhow!("Incorrect password."))?;
self.cache_password(password.clone());
(key, Some(password))
}
}
}
@ -1094,7 +1115,10 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
}
match key.unlock(p.clone()) {
Ok(()) => break Some(p),
Ok(()) => {
self.cache_password(p.clone());
break Some(p)
}
Err(err) => {
eprintln!("Failed to unlock key: {}", err);
}
@ -1187,7 +1211,7 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
continue;
}
if let Ok((key, password)) = self.get_signer(&ka, None) {
if let Ok((key, password)) = self.get_signer(&ka) {
keys.push((key, password));
continue 'next_cert;
}