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::Result;
|
||||||
use openpgp::armor;
|
use openpgp::armor;
|
||||||
use openpgp::cert::prelude::*;
|
use openpgp::cert::prelude::*;
|
||||||
use openpgp::crypto::Password;
|
|
||||||
use openpgp::crypto::Signer;
|
use openpgp::crypto::Signer;
|
||||||
use openpgp::parse::Parse;
|
use openpgp::parse::Parse;
|
||||||
use openpgp::packet::prelude::*;
|
use openpgp::packet::prelude::*;
|
||||||
@ -39,7 +38,6 @@ use crate::{
|
|||||||
|
|
||||||
fn update_cert_revocation(sq: &Sq,
|
fn update_cert_revocation(sq: &Sq,
|
||||||
cert: &Cert, rev: &Signature,
|
cert: &Cert, rev: &Signature,
|
||||||
passwords: &mut Vec<Password>,
|
|
||||||
reference_time: &SystemTime)
|
reference_time: &SystemTime)
|
||||||
-> Result<Signature>
|
-> Result<Signature>
|
||||||
{
|
{
|
||||||
@ -49,10 +47,7 @@ fn update_cert_revocation(sq: &Sq,
|
|||||||
let pk = ka.key();
|
let pk = ka.key();
|
||||||
|
|
||||||
// Derive a signer.
|
// Derive a signer.
|
||||||
let (mut signer, password) = sq.get_signer(&ka, Some(&passwords))?;
|
let (mut signer, _password) = sq.get_signer(&ka)?;
|
||||||
if let Some(password) = password {
|
|
||||||
passwords.push(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
let sig = SignatureBuilder::from(rev.clone())
|
let sig = SignatureBuilder::from(rev.clone())
|
||||||
.set_signature_creation_time(reference_time.clone())?
|
.set_signature_creation_time(reference_time.clone())?
|
||||||
@ -73,7 +68,6 @@ const GOOD_HASHES: &[ HashAlgorithm ] = &[
|
|||||||
// ua is using a weak policy.
|
// ua is using a weak policy.
|
||||||
fn update_user_id_binding(sq: &Sq,
|
fn update_user_id_binding(sq: &Sq,
|
||||||
ua: &ValidUserIDAmalgamation,
|
ua: &ValidUserIDAmalgamation,
|
||||||
passwords: &mut Vec<Password>,
|
|
||||||
reference_time: &SystemTime)
|
reference_time: &SystemTime)
|
||||||
-> Result<Signature>
|
-> Result<Signature>
|
||||||
{
|
{
|
||||||
@ -81,10 +75,7 @@ fn update_user_id_binding(sq: &Sq,
|
|||||||
let pk = ka.key();
|
let pk = ka.key();
|
||||||
|
|
||||||
// Derive a signer.
|
// Derive a signer.
|
||||||
let (mut signer, password) = sq.get_signer(&ka, Some(&passwords))?;
|
let (mut signer, _password) = sq.get_signer(&ka)?;
|
||||||
if let Some(password) = password {
|
|
||||||
passwords.push(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
let sym = &[
|
let sym = &[
|
||||||
SymmetricAlgorithm::AES128,
|
SymmetricAlgorithm::AES128,
|
||||||
@ -144,7 +135,6 @@ fn update_user_id_binding(sq: &Sq,
|
|||||||
// ka is using a weak policy.
|
// ka is using a weak policy.
|
||||||
fn update_subkey_binding<P>(sq: &Sq,
|
fn update_subkey_binding<P>(sq: &Sq,
|
||||||
ka: &ValidSubordinateKeyAmalgamation<P>,
|
ka: &ValidSubordinateKeyAmalgamation<P>,
|
||||||
passwords: &mut Vec<Password>,
|
|
||||||
reference_time: &SystemTime)
|
reference_time: &SystemTime)
|
||||||
-> Result<Signature>
|
-> Result<Signature>
|
||||||
where P: key::KeyParts + Clone
|
where P: key::KeyParts + Clone
|
||||||
@ -153,10 +143,7 @@ fn update_subkey_binding<P>(sq: &Sq,
|
|||||||
let pk = primary.key();
|
let pk = primary.key();
|
||||||
|
|
||||||
// Derive a signer.
|
// Derive a signer.
|
||||||
let (mut signer, password) = sq.get_signer(&primary, Some(&passwords))?;
|
let (mut signer, _password) = sq.get_signer(&primary)?;
|
||||||
if let Some(password) = password {
|
|
||||||
passwords.push(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the signature.
|
// Update the signature.
|
||||||
let sig = ka.binding_signature();
|
let sig = ka.binding_signature();
|
||||||
@ -174,10 +161,7 @@ fn update_subkey_binding<P>(sq: &Sq,
|
|||||||
.nth(0)
|
.nth(0)
|
||||||
{
|
{
|
||||||
// Derive a signer.
|
// Derive a signer.
|
||||||
let (mut subkey_signer, password) = sq.get_signer(&ka, Some(&passwords))?;
|
let (mut subkey_signer, _password) = sq.get_signer(&ka)?;
|
||||||
if let Some(password) = password {
|
|
||||||
passwords.push(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
let backsig = SignatureBuilder::from(backsig.clone())
|
let backsig = SignatureBuilder::from(backsig.clone())
|
||||||
.set_signature_creation_time(reference_time.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 reference_time = sq.time;
|
||||||
|
|
||||||
let mut passwords = Vec::new();
|
|
||||||
|
|
||||||
let mut out = if args.output.is_some() || ! args.cert_file.is_empty() {
|
let mut out = if args.output.is_some() || ! args.cert_file.is_empty() {
|
||||||
let output = if let Some(output) = args.output {
|
let output = if let Some(output) = args.output {
|
||||||
output
|
output
|
||||||
@ -464,8 +446,7 @@ pub fn lint(sq: Sq, mut args: Command) -> Result<()> {
|
|||||||
rev.digest_prefix()[1]);
|
rev.digest_prefix()[1]);
|
||||||
if args.fix {
|
if args.fix {
|
||||||
match update_cert_revocation(
|
match update_cert_revocation(
|
||||||
&sq, &cert, rev, &mut passwords,
|
&sq, &cert, rev, &reference_time)
|
||||||
&reference_time)
|
|
||||||
{
|
{
|
||||||
Ok(sig) => {
|
Ok(sig) => {
|
||||||
updates.push(sig);
|
updates.push(sig);
|
||||||
@ -588,7 +569,7 @@ pub fn lint(sq: Sq, mut args: Command) -> Result<()> {
|
|||||||
}
|
}
|
||||||
if args.fix {
|
if args.fix {
|
||||||
match update_user_id_binding(
|
match update_user_id_binding(
|
||||||
&sq, &ua, &mut passwords, &reference_time)
|
&sq, &ua, &reference_time)
|
||||||
{
|
{
|
||||||
Ok(sig) => {
|
Ok(sig) => {
|
||||||
updates.push(sig);
|
updates.push(sig);
|
||||||
@ -641,8 +622,7 @@ pub fn lint(sq: Sq, mut args: Command) -> Result<()> {
|
|||||||
}
|
}
|
||||||
if args.fix {
|
if args.fix {
|
||||||
match update_subkey_binding(
|
match update_subkey_binding(
|
||||||
&sq, &ka, &mut passwords,
|
&sq, &ka, &reference_time)
|
||||||
&reference_time)
|
|
||||||
{
|
{
|
||||||
Ok(sig) => updates.push(sig),
|
Ok(sig) => updates.push(sig),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -730,7 +710,7 @@ pub fn lint(sq: Sq, mut args: Command) -> Result<()> {
|
|||||||
}
|
}
|
||||||
if args.fix {
|
if args.fix {
|
||||||
match update_subkey_binding(
|
match update_subkey_binding(
|
||||||
&sq, &ka, &mut passwords, &reference_time)
|
&sq, &ka, &reference_time)
|
||||||
{
|
{
|
||||||
Ok(sig) => updates.push(sig),
|
Ok(sig) => updates.push(sig),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -372,6 +372,7 @@ fn main() -> Result<()> {
|
|||||||
no_key_store: c.no_key_store,
|
no_key_store: c.no_key_store,
|
||||||
key_store_path: c.key_store.clone(),
|
key_store_path: c.key_store.clone(),
|
||||||
key_store: OnceCell::new(),
|
key_store: OnceCell::new(),
|
||||||
|
password_cache: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
commands::dispatch(sq, c)
|
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 no_key_store: bool,
|
||||||
pub key_store_path: Option<PathBuf>,
|
pub key_store_path: Option<PathBuf>,
|
||||||
pub key_store: OnceCell<Mutex<keystore::Keystore>>,
|
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> {
|
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.
|
/// Gets a signer for the specified key.
|
||||||
///
|
///
|
||||||
/// If `ka` includes secret key material, that is preferred.
|
/// If `ka` includes secret key material, that is preferred.
|
||||||
/// Otherwise, we look for the key on the key store.
|
/// Otherwise, we look for the key on the key store.
|
||||||
///
|
///
|
||||||
/// If the key is locked, we unlocked. `passwords` is a list of
|
/// If the key is locked, we try to unlock it. If the key isn't
|
||||||
/// passwords to try. The passwords are only tried if it is safe.
|
/// protected by a retry counter, then the password cache is
|
||||||
/// That is, the passwords are only tried if we are sure that the
|
/// tried. Otherwise, or if that fails, the user is prompted to
|
||||||
/// key is not protected by a retry counter. If `passwords`
|
/// unlock the key. The correct password is added to the password
|
||||||
/// doesn't contain the correct password, or the key is protected
|
/// cache.
|
||||||
/// 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>)
|
||||||
pub fn get_signer<P, R, R2>(&self, ka: &KeyAmalgamation<P, R, R2>,
|
|
||||||
passwords: Option<&[Password]>)
|
|
||||||
-> Result<(Box<dyn crypto::Signer + Send + Sync>, Option<Password>)>
|
-> Result<(Box<dyn crypto::Signer + Send + Sync>, Option<Password>)>
|
||||||
where P: key::KeyParts + Clone, R: key::KeyRole + Clone
|
where P: key::KeyParts + Clone, R: key::KeyRole + Clone
|
||||||
{
|
{
|
||||||
let passwords = passwords.unwrap_or(&[]);
|
|
||||||
|
|
||||||
let try_tsk = |ka: &KeyAmalgamation<_, _, R2>|
|
let try_tsk = |ka: &KeyAmalgamation<_, _, R2>|
|
||||||
-> Result<(_, _)>
|
-> Result<(_, _)>
|
||||||
{
|
{
|
||||||
@ -1025,8 +1044,8 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
|
|||||||
let (unencrypted, password) = match secret {
|
let (unencrypted, password) = match secret {
|
||||||
SecretKeyMaterial::Encrypted(ref e) => {
|
SecretKeyMaterial::Encrypted(ref e) => {
|
||||||
// try passwords from already existing keys
|
// try passwords from already existing keys
|
||||||
match passwords.iter().find_map(|password| {
|
match self.cached_passwords().find_map(|password| {
|
||||||
e.decrypt(ka.pk_algo(), password).ok()
|
e.decrypt(ka.pk_algo(), &password).ok()
|
||||||
.map(|key| (key, password.clone()))
|
.map(|key| (key, password.clone()))
|
||||||
}) {
|
}) {
|
||||||
Some((unencrypted, password)) =>
|
Some((unencrypted, password)) =>
|
||||||
@ -1036,11 +1055,13 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
|
|||||||
&format!("key {}/{}",
|
&format!("key {}/{}",
|
||||||
ka.cert().keyid(),
|
ka.cert().keyid(),
|
||||||
ka.keyid()))?;
|
ka.keyid()))?;
|
||||||
(
|
|
||||||
e.decrypt(ka.pk_algo(), &password)
|
let key = e.decrypt(ka.pk_algo(), &password)
|
||||||
.map_err(|_| anyhow!("Incorrect password."))?,
|
.map_err(|_| anyhow!("Incorrect password."))?;
|
||||||
Some(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()) {
|
match key.unlock(p.clone()) {
|
||||||
Ok(()) => break Some(p),
|
Ok(()) => {
|
||||||
|
self.cache_password(p.clone());
|
||||||
|
break Some(p)
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Failed to unlock key: {}", err);
|
eprintln!("Failed to unlock key: {}", err);
|
||||||
}
|
}
|
||||||
@ -1187,7 +1211,7 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok((key, password)) = self.get_signer(&ka, None) {
|
if let Ok((key, password)) = self.get_signer(&ka) {
|
||||||
keys.push((key, password));
|
keys.push((key, password));
|
||||||
continue 'next_cert;
|
continue 'next_cert;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user