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:
parent
1a33500c26
commit
a2c482e4f3
@ -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) => {
|
||||
|
@ -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)
|
||||
|
62
src/sq.rs
62
src/sq.rs
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user