summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2023-07-17 16:47:14 +0200
committerJustus Winter <justus@sequoia-pgp.org>2023-07-25 11:44:27 +0200
commit64a5636b6926092015c60b109e6fe51500abc163 (patch)
tree3c2142c31460832f3071cf850a66606b58e3561d
parent4fef5e28650b287d0c43f944b2f864f7d33aa8a9 (diff)
ipc: Implement loopback password entry.
-rw-r--r--ipc/src/gnupg.rs79
-rw-r--r--ipc/tests/gpg-agent.rs34
2 files changed, 83 insertions, 30 deletions
diff --git a/ipc/src/gnupg.rs b/ipc/src/gnupg.rs
index 3a070d04..ca833040 100644
--- a/ipc/src/gnupg.rs
+++ b/ipc/src/gnupg.rs
@@ -351,6 +351,10 @@ impl Agent {
self.send_simple(option).await?;
}
+ if key.password.is_some() {
+ self.send_simple("OPTION pinentry-mode=loopback").await?;
+ }
+
let grip = Keygrip::of(key.public.mpis())?;
self.send_simple(format!("SIGKEY {}", grip)).await?;
self.send_simple(
@@ -369,8 +373,15 @@ impl Agent {
| assuan::Response::Comment { .. }
| assuan::Response::Status { .. } =>
(), // Ignore.
- assuan::Response::Inquire { .. } =>
- acknowledge_inquiry(self).await?,
+ assuan::Response::Inquire { keyword, .. } =>
+ match (keyword.as_str(), &key.password) {
+ ("PASSPHRASE", Some(p)) => {
+ p.map(|p| self.data(p))?;
+ // Dummy read to send the data.
+ self.next().await;
+ },
+ _ => acknowledge_inquiry(self).await?,
+ },
assuan::Response::Error { ref message, .. } =>
return assuan::operation_failed(self, message).await,
assuan::Response::Data { ref partial } =>
@@ -392,41 +403,38 @@ impl Agent {
self.send_simple(option).await?;
}
+ if key.password.is_some() {
+ self.send_simple("OPTION pinentry-mode=loopback").await?;
+ }
+
let grip = Keygrip::of(key.public.mpis())?;
self.send_simple(format!("SETKEY {}", grip)).await?;
self.send_simple(format!("SETKEYDESC {}",
assuan::escape(&key.password_prompt))).await?;
self.send("PKDECRYPT")?;
- while let Some(r) = self.next().await {
- match r? {
- assuan::Response::Inquire { ref keyword, .. } =>
- if keyword == "CIPHERTEXT" {
- // What we expect.
- } else {
- acknowledge_inquiry(self).await?;
- },
- assuan::Response::Comment { .. }
- | assuan::Response::Status { .. } =>
- (), // Ignore.
- assuan::Response::Error { ref message, .. } =>
- return assuan::operation_failed(self, message).await,
- r =>
- return assuan::protocol_error(&r),
- }
- }
-
- let mut buf = Vec::new();
- Sexp::try_from(ciphertext)?.serialize(&mut buf)?;
- self.data(&buf)?;
let mut padding = true;
let mut data = Vec::new();
while let Some(r) = self.next().await {
match r? {
- assuan::Response::Ok { .. }
- | assuan::Response::Comment { .. } =>
+ assuan::Response::Ok { .. } |
+ assuan::Response::Comment { .. } =>
(), // Ignore.
- assuan::Response::Inquire { .. } =>
- acknowledge_inquiry(self).await?,
+ assuan::Response::Inquire { ref keyword, .. } =>
+ match (keyword.as_str(), &key.password) {
+ ("PASSPHRASE", Some(p)) => {
+ p.map(|p| self.data(p))?;
+ // Dummy read to send the data.
+ self.next().await;
+ },
+ ("CIPHERTEXT", _) => {
+ let mut buf = Vec::new();
+ Sexp::try_from(ciphertext)?.serialize(&mut buf)?;
+ self.data(&buf)?;
+ // Dummy read to send the data.
+ self.next().await;
+ },
+ _ => acknowledge_inquiry(self).await?,
+ },
assuan::Response::Status { ref keyword, ref message } =>
if keyword == "PADDING" {
padding = message != "0";
@@ -504,6 +512,7 @@ impl Agent {
pub struct KeyPair {
public: Key<key::PublicParts, key::UnspecifiedRole>,
agent_socket: PathBuf,
+ password: Option<crypto::Password>,
password_prompt: String,
}
@@ -518,6 +527,7 @@ impl KeyPair {
where R: key::KeyRole
{
Ok(KeyPair {
+ password: None,
password_prompt: format!(
"Please enter the passphrase to \
unlock the OpenPGP secret key:\n\
@@ -587,6 +597,21 @@ impl KeyPair {
self.with_password_prompt(prompt)
}
+ /// Supplies a password to unlock the secret key.
+ ///
+ /// This will be used when the secret key operation is performed,
+ /// e.g. when signing or decrypting a message.
+ ///
+ /// Note: This is the equivalent of GnuPG's
+ /// `--pinentry-mode=loopback` and requires explicit opt-in in the
+ /// gpg-agent configuration using the `allow-loopback-pinentry`
+ /// option. If this is not enabled in the agent, the secret key
+ /// operation will fail. It is likely only useful during testing.
+ pub fn with_password(mut self, password: crypto::Password) -> Self {
+ self.password = Some(password);
+ self
+ }
+
/// Changes the password prompt.
///
/// Use this function to give more context to the user when she is
diff --git a/ipc/tests/gpg-agent.rs b/ipc/tests/gpg-agent.rs
index 331cc298..e5c7858a 100644
--- a/ipc/tests/gpg-agent.rs
+++ b/ipc/tests/gpg-agent.rs
@@ -36,6 +36,10 @@ macro_rules! make_context {
return Ok(());
},
};
+
+ std::fs::write(ctx.homedir().unwrap().join("gpg-agent.conf"),
+ "allow-loopback-pinentry\n").unwrap();
+
match ctx.start("gpg-agent") {
Ok(_) => (),
Err(e) => {
@@ -71,6 +75,7 @@ async fn help() -> openpgp::Result<()> {
}
const MESSAGE: &str = "дружба";
+const PASSWORD: &str = "streng geheim";
fn gpg_import(ctx: &Context, what: &[u8]) -> openpgp::Result<()> {
use std::process::{Command, Stdio};
@@ -80,6 +85,7 @@ fn gpg_import(ctx: &Context, what: &[u8]) -> openpgp::Result<()> {
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.arg("--homedir").arg(ctx.homedir().unwrap())
+ .arg("--batch")
.arg("--import")
.spawn()
.context("failed to start gpg")?;
@@ -126,22 +132,28 @@ fn sign() -> openpgp::Result<()> {
let ctx = make_context!();
for cs in &[RSA2k, Cv25519, P521] {
+ for password in vec![None, Some(PASSWORD.into())] {
let (cert, _) = CertBuilder::new()
.set_cipher_suite(*cs)
.add_userid("someone@example.org")
.add_signing_subkey()
+ .set_password(password.clone())
.generate().unwrap();
let mut buf = Vec::new();
cert.as_tsk().serialize(&mut buf).unwrap();
gpg_import(&ctx, &buf)?;
- let keypair = KeyPair::new(
+ let mut keypair = KeyPair::new(
&ctx,
cert.keys().with_policy(p, None).alive().revoked(false)
.for_signing().take(1).next().unwrap().key())
.unwrap();
+ if let Some(p) = password.clone() {
+ keypair = keypair.with_password(p);
+ }
+
let mut message = Vec::new();
{
// Start streaming an OpenPGP message.
@@ -222,6 +234,7 @@ fn sign() -> openpgp::Result<()> {
Err(anyhow::anyhow!("Signature verification failed"))
}
}
+ }
}
Ok(())
}
@@ -252,12 +265,14 @@ fn decrypt(also_try_explicit_async: bool) -> openpgp::Result<()> {
.generate()?;
for cs in &[RSA2k, Cv25519, P521] {
+ for password in vec![None, Some(PASSWORD.into())] {
let ctx = make_context!();
let (cert, _) = CertBuilder::new()
.set_cipher_suite(*cs)
.add_userid("someone@example.org")
.add_transport_encryption_subkey()
+ .set_password(password.clone())
.generate().unwrap();
let mut buf = Vec::new();
@@ -320,11 +335,15 @@ fn decrypt(also_try_explicit_async: bool) -> openpgp::Result<()> {
.is_err());
// Now try "our" key.
- let keypair = KeyPair::new(
+ let mut keypair = KeyPair::new(
&ctx,
cert.keys().with_policy(p, None)
.for_storage_encryption().for_transport_encryption()
.take(1).next().unwrap().key())?;
+ if let Some(p) = password.clone() {
+ keypair = keypair.with_password(p);
+ }
+
assert!(rt.block_on(agent.decrypt(&keypair, pkesk_0.esk()))
.is_ok());
@@ -334,7 +353,10 @@ fn decrypt(also_try_explicit_async: bool) -> openpgp::Result<()> {
// Make a helper that that feeds the recipient's secret key to the
// decryptor.
- let helper = Helper { policy: p, ctx: &ctx, cert: &cert, other: &other};
+ let helper = Helper {
+ policy: p, ctx: &ctx, cert: &cert, other: &other,
+ password: &password,
+ };
// Now, create a decryptor with a helper using the given Certs.
let mut decryptor = DecryptorBuilder::from_bytes(&message).unwrap()
@@ -350,6 +372,7 @@ fn decrypt(also_try_explicit_async: bool) -> openpgp::Result<()> {
ctx: &'a Context,
cert: &'a openpgp::Cert,
other: &'a openpgp::Cert,
+ password: &'a Option<openpgp::crypto::Password>,
}
impl<'a> VerificationHelper for Helper<'a> {
@@ -396,6 +419,10 @@ fn decrypt(also_try_explicit_async: bool) -> openpgp::Result<()> {
.take(1).next().unwrap().key())
.unwrap();
+ if let Some(p) = self.password.clone() {
+ keypair = keypair.with_password(p);
+ }
+
for pkesk in pkesks {
if *pkesk.recipient() != keypair.public().keyid() {
continue;
@@ -412,6 +439,7 @@ fn decrypt(also_try_explicit_async: bool) -> openpgp::Result<()> {
Ok(None)
}
}
+ }
}
Ok(())
}