Revert "sign: Implement GPG signing backend"

This reverts commit 0efaef2da9.
This commit is contained in:
Evan Mesterhazy 2024-02-20 11:46:39 -05:00 committed by GitHub
parent 2e62162a76
commit 5995f0cc42
3 changed files with 4 additions and 247 deletions

View file

@ -1,232 +0,0 @@
// Copyright 2023 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![allow(missing_docs)]
use std::ffi::{OsStr, OsString};
use std::fmt::Debug;
use std::io::Write;
use std::process::{Command, ExitStatus, Stdio};
use std::str;
use thiserror::Error;
use crate::signing::{SigStatus, SignError, SigningBackend, Verification};
// Search for one of the:
// [GNUPG:] GOODSIG <long keyid> <primary uid..>
// [GNUPG:] EXPKEYSIG <long keyid> <primary uid..>
// [GNUPG:] NO_PUBKEY <long keyid>
// [GNUPG:] BADSIG <long keyid> <primary uid..>
// in the output from --status-fd=1
// Assume signature is invalid if none of the above was found
fn parse_gpg_verify_output(
output: &[u8],
allow_expired_keys: bool,
) -> Result<Verification, SignError> {
output
.split(|&b| b == b'\n')
.filter_map(|line| line.strip_prefix(b"[GNUPG:] "))
.find_map(|line| {
let mut parts = line.splitn(3, |&b| b == b' ').fuse();
let status = match parts.next()? {
b"GOODSIG" => SigStatus::Good,
b"EXPKEYSIG" => {
if allow_expired_keys {
SigStatus::Good
} else {
SigStatus::Bad
}
}
b"NO_PUBKEY" => SigStatus::Unknown,
b"BADSIG" => SigStatus::Bad,
_ => return None,
};
let key = parts
.next()
.and_then(|bs| str::from_utf8(bs).ok())
.map(|value| value.trim().to_owned());
let display = parts
.next()
.and_then(|bs| str::from_utf8(bs).ok())
.map(|value| value.trim().to_owned());
Some(Verification::new(status, key, display))
})
.ok_or(SignError::InvalidSignatureFormat)
}
#[derive(Debug)]
pub struct GpgBackend {
program: OsString,
allow_expired_keys: bool,
extra_args: Vec<OsString>,
}
#[derive(Debug, Error)]
pub enum GpgError {
#[error("GPG failed with exit status {exit_status}:\n{stderr}")]
Command {
exit_status: ExitStatus,
stderr: String,
},
#[error("Failed to run GPG")]
Io(#[from] std::io::Error),
}
impl From<GpgError> for SignError {
fn from(e: GpgError) -> Self {
SignError::Backend(Box::new(e))
}
}
impl GpgBackend {
pub fn new(program: OsString, allow_expired_keys: bool) -> Self {
Self {
program,
allow_expired_keys,
extra_args: vec![],
}
}
/// Primarily intended for testing
pub fn with_extra_args(mut self, args: &[OsString]) -> Self {
self.extra_args.extend_from_slice(args);
self
}
pub fn from_config(config: &config::Config) -> Self {
Self::new(
config
.get_string("signing.backends.gpg.program")
.unwrap_or_else(|_| "gpg2".into())
.into(),
config
.get_bool("signing.backends.gpg.allow-expired-keys")
.unwrap_or(false),
)
}
fn run(&self, input: &[u8], args: &[&OsStr], check: bool) -> Result<Vec<u8>, GpgError> {
let process = Command::new(&self.program)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(if check { Stdio::piped() } else { Stdio::null() })
.args(&self.extra_args)
.args(args)
.spawn()?;
process.stdin.as_ref().unwrap().write_all(input)?;
let output = process.wait_with_output()?;
if check && !output.status.success() {
Err(GpgError::Command {
exit_status: output.status,
stderr: String::from_utf8_lossy(&output.stderr).trim_end().into(),
})
} else {
Ok(output.stdout)
}
}
}
impl SigningBackend for GpgBackend {
fn name(&self) -> &str {
"gpg"
}
fn can_read(&self, signature: &[u8]) -> bool {
signature.starts_with(b"-----BEGIN PGP SIGNATURE-----")
}
fn sign(&self, data: &[u8], key: Option<&str>) -> Result<Vec<u8>, SignError> {
Ok(match key {
Some(key) => self.run(data, &["-abu".as_ref(), key.as_ref()], true)?,
None => self.run(data, &["-ab".as_ref()], true)?,
})
}
fn verify(&self, data: &[u8], signature: &[u8]) -> Result<Verification, SignError> {
let mut signature_file = tempfile::Builder::new()
.prefix(".jj-gpg-sig-tmp-")
.tempfile()
.map_err(GpgError::Io)?;
signature_file.write_all(signature).map_err(GpgError::Io)?;
signature_file.flush().map_err(GpgError::Io)?;
let sig_path = signature_file.into_temp_path();
let output = self.run(
data,
&[
"--status-fd=1".as_ref(),
"--verify".as_ref(),
// the only reason we have those .as_refs transmuting to &OsStr everywhere
sig_path.as_os_str(),
"-".as_ref(),
],
false,
)?;
parse_gpg_verify_output(&output, self.allow_expired_keys)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gpg_verify_invalid_signature_format() {
use assert_matches::assert_matches;
assert_matches!(
parse_gpg_verify_output(b"", true),
Err(SignError::InvalidSignatureFormat)
);
}
#[test]
fn gpg_verify_bad_signature() {
assert_eq!(
parse_gpg_verify_output(b"[GNUPG:] BADSIG 123 456", true).unwrap(),
Verification::new(SigStatus::Bad, Some("123".into()), Some("456".into()))
);
}
#[test]
fn gpg_verify_unknown_signature() {
assert_eq!(
parse_gpg_verify_output(b"[GNUPG:] NO_PUBKEY 123", true).unwrap(),
Verification::new(SigStatus::Unknown, Some("123".into()), None)
);
}
#[test]
fn gpg_verify_good_signature() {
assert_eq!(
parse_gpg_verify_output(b"[GNUPG:] GOODSIG 123 456", true).unwrap(),
Verification::new(SigStatus::Good, Some("123".into()), Some("456".into()))
);
}
#[test]
fn gpg_verify_expired_signature() {
assert_eq!(
parse_gpg_verify_output(b"[GNUPG:] EXPKEYSIG 123 456", true).unwrap(),
Verification::new(SigStatus::Good, Some("123".into()), Some("456".into()))
);
assert_eq!(
parse_gpg_verify_output(b"[GNUPG:] EXPKEYSIG 123 456", false).unwrap(),
Verification::new(SigStatus::Bad, Some("123".into()), Some("456".into()))
);
}
}

View file

@ -43,7 +43,6 @@ pub mod fsmonitor;
pub mod git; pub mod git;
pub mod git_backend; pub mod git_backend;
pub mod gitignore; pub mod gitignore;
pub mod gpg_signing;
pub mod hex_util; pub mod hex_util;
pub mod id_prefix; pub mod id_prefix;
pub mod index; pub mod index;

View file

@ -22,7 +22,6 @@ use std::sync::RwLock;
use thiserror::Error; use thiserror::Error;
use crate::backend::CommitId; use crate::backend::CommitId;
use crate::gpg_signing::GpgBackend;
use crate::settings::UserSettings; use crate::settings::UserSettings;
/// A status of the signature, part of the [Verification] type. /// A status of the signature, part of the [Verification] type.
@ -61,15 +60,6 @@ impl Verification {
display: None, display: None,
} }
} }
/// Create a new verification
pub fn new(status: SigStatus, key: Option<String>, display: Option<String>) -> Self {
Self {
status,
key,
display,
}
}
} }
/// The backend for signing and verifying cryptographic signatures. /// The backend for signing and verifying cryptographic signatures.
@ -160,10 +150,10 @@ impl Signer {
/// Creates a signer based on user settings. Uses all known backends, and /// Creates a signer based on user settings. Uses all known backends, and
/// chooses one of them to be used for signing depending on the config. /// chooses one of them to be used for signing depending on the config.
pub fn from_settings(settings: &UserSettings) -> Result<Self, SignInitError> { pub fn from_settings(settings: &UserSettings) -> Result<Self, SignInitError> {
let mut backends = vec![ let mut backends: Vec<Box<dyn SigningBackend>> = vec![
Box::new(GpgBackend::from_config(settings.config())) as Box<dyn SigningBackend>, // Box::new(GpgBackend::from_settings(settings)?),
// Box::new(SshBackend::from_settings(settings)?) as Box<dyn SigningBackend>, // Box::new(SshBackend::from_settings(settings)?),
// Box::new(X509Backend::from_settings(settings)?) as Box<dyn SigningBackend>, // Box::new(X509Backend::from_settings(settings)?),
]; ];
let main_backend = settings let main_backend = settings