Skip to content

Commit

Permalink
Fixes #26082: Use sequoia in rudder-package (#6094)
Browse files Browse the repository at this point in the history
  • Loading branch information
amousset authored Dec 17, 2024
1 parent 3c5e534 commit 73149b5
Show file tree
Hide file tree
Showing 10 changed files with 417 additions and 114 deletions.
324 changes: 321 additions & 3 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ pipeline {
sh script: 'typos', label: 'check webapp api doc typos'
}
dir('relay') {
sh script: 'typos --exclude "*.log" --exclude "*.license" --exclude "*.asc" --exclude "*.pem" --exclude "*.cert" --exclude "*.priv" --exclude "*.pub" --exclude "*.signed" --exclude "*.log" --exclude "*.json"', label: 'check relayd typos'
sh script: 'typos --exclude "*.log" --exclude "*.gpg" --exclude "*.license" --exclude "*.asc" --exclude "*.pem" --exclude "*.cert" --exclude "*.priv" --exclude "*.pub" --exclude "*.signed" --exclude "*.log" --exclude "*.json"', label: 'check relayd typos'
}
}
}
Expand Down
47 changes: 0 additions & 47 deletions policies/techniques/anssi_bp_028/technique.yml

This file was deleted.

2 changes: 0 additions & 2 deletions policies/techniques/anssi_bp_028/test.yml

This file was deleted.

2 changes: 1 addition & 1 deletion relay/sources/rudder-package/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ tar = "0.4.40"
tempfile = "3.8.0"
# Compile dev and release with trace logs enabled
tracing = { version = "0.1", features = ["max_level_trace", "release_max_level_trace"] }
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
which = "7"
flate2 = "1.0.28"
cli-table = "0.4.7"
sequoia-openpgp = "1"
spinners = "4.1.1"
rudder_cli = { path = "../../../policies/rudder-cli" }
itertools = "0.13"
Expand Down
2 changes: 2 additions & 0 deletions relay/sources/rudder-package/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ RUN ./user.sh $USER_ID
COPY ci/rust.sh .
RUN ./rust.sh

RUN apt-get update && apt-get install -y nettle-dev clang

ENV RUSTC_WRAPPER="sccache"
2 changes: 1 addition & 1 deletion relay/sources/rudder-package/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub fn run_inner(args: Args) -> Result<()> {
debug!("Parsed configuration: {cfg:?}");

// Now initialize all common data structures
let verifier = SignatureVerifier::new(PathBuf::from(SIGNATURE_KEYRING_PATH));
let verifier = SignatureVerifier::new(PathBuf::from(SIGNATURE_KEYRING_PATH))?;
let repo = Repository::new(&cfg, verifier)?;
let webapp_version = RudderVersion::from_path(RUDDER_VERSION_PATH)?;
let mut webapp = Webapp::new(PathBuf::from(WEBAPP_XML_PATH), webapp_version);
Expand Down
6 changes: 4 additions & 2 deletions relay/sources/rudder-package/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ mod tests {
#[test]
fn it_downloads_unverified_files() {
let config = Configuration::parse("").unwrap();
let verifier = SignatureVerifier::new(PathBuf::from("tools/rudder_plugins_key.gpg"));
let verifier =
SignatureVerifier::new(PathBuf::from("tools/rudder_plugins_key.gpg")).unwrap();
let repo = Repository::new(&config, verifier).unwrap();
let dst = NamedTempFile::new().unwrap();
repo.download_unsafe("../rpm/rudder_rpm_key.pub", dst.path())
Expand All @@ -207,7 +208,8 @@ mod tests {
#[test]
fn it_downloads_verified_files() {
let config = Configuration::parse("").unwrap();
let verifier = SignatureVerifier::new(PathBuf::from("tools/rudder_plugins_key.gpg"));
let verifier =
SignatureVerifier::new(PathBuf::from("tools/rudder_plugins_key.gpg")).unwrap();
let repo = Repository::new(&config, verifier).unwrap();
let dst = NamedTempFile::new().unwrap();
repo.download(
Expand Down
144 changes: 87 additions & 57 deletions relay/sources/rudder-package/src/signature.rs
Original file line number Diff line number Diff line change
@@ -1,73 +1,97 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 Normation SAS

//! Here we use gpgv:
//!
//! * It is a bit lighter than the full gpg implementation, with a way simpler CLI, but still provided
//! everywhere.
//! * sequoia is to low-level for our goals, we don't want to have to deal with gpg internals.
//! * Other Rust implementation do not seem mature enough.
const GPGV_BIN: &str = "/usr/bin/gpgv";

use anyhow::{bail, Result};
use openpgp::parse::{stream::*, Parse};
use openpgp::policy::StandardPolicy;
use openpgp::{Cert, KeyHandle};
use regex::Regex;
use sequoia_openpgp as openpgp;
use sequoia_openpgp::cert::CertParser;
use sha2::{Digest, Sha512};
use std::fs::read;
use std::{
fs::{read_to_string, File},
io,
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
process::Command,
str,
};

use anyhow::{bail, Result};
use regex::Regex;
use sha2::{Digest, Sha512};

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum VerificationSuccess {
ValidSignature,
ValidSignatureAndHash,
}

use crate::cmd::CmdOutput;

#[derive(Clone, PartialEq, Eq, Debug)]
#[derive(Clone, Debug)]
pub struct SignatureVerifier {
keyring: PathBuf,
/// The configured certificates.
keyring: Vec<Cert>,
}

impl SignatureVerifier {
pub fn new(keyring: PathBuf) -> Self {
Self { keyring }
#[derive(Clone, Debug)]
struct Helper<'a> {
certs: &'a [Cert],
}

impl VerificationHelper for Helper<'_> {
fn get_certs(&mut self, ids: &[KeyHandle]) -> openpgp::Result<Vec<Cert>> {
for id in ids {
for cert in self.certs {
if cert.key_handle().aliases(id) {
return Ok(vec![cert.clone()]);
}
}
}
bail!("Could not find key")
}

fn verify(&self, signature: &Path, data: &Path) -> Result<VerificationSuccess> {
let mut gpgv = Command::new(GPGV_BIN);
let cmd = gpgv
.arg("--keyring")
.arg(&self.keyring)
.arg("--")
.arg(signature)
.arg(data);
let gpg_output = match CmdOutput::new(cmd) {
Ok(out) => out,
Err(e) => {
bail!(
"Could not check signature using gpgv, most likely because it is not installed:\n{}",
e
);
fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> {
// Per `DetachedVerifierBuilder` docs:
//
// > `VerificationHelper::check` will be called with a `MessageStructure` containing exactly
// > one layer, a signature group.

if structure.len() != 1 {
bail!("Unexpected number of layers");
}

match structure[0] {
MessageLayer::Encryption { .. } => bail!("Unexpected encryption layer"),
MessageLayer::Compression { .. } => bail!("Unexpected compression layer"),
MessageLayer::SignatureGroup { ref results } => {
if !results.iter().any(|r| r.is_ok()) {
bail!("No valid signature found");
}
}
};
if gpg_output.output.status.success() {
Ok(VerificationSuccess::ValidSignature)
} else {
bail!(
"Invalid signature file '{}' for '{}', gpgv failed with:\n{}",
signature.display(),
data.display(),
String::from_utf8_lossy(&gpg_output.output.stderr)
)
}

Ok(())
}
}

impl SignatureVerifier {
pub fn new(keyring: PathBuf) -> Result<Self> {
let cert_parser = CertParser::from_file(&keyring)?;
let keyring: Vec<Cert> = cert_parser.collect::<Result<Vec<Cert>>>()?;
Ok(Self { keyring })
}

/// Verify data against a detached signature using sequoia
fn verify_sq(&self, signature: &[u8], data: &[u8]) -> Result<VerificationSuccess> {
let policy = StandardPolicy::new();
// Use current time
let time = None;
let helper = Helper {
certs: &self.keyring,
};

let mut verifier =
DetachedVerifierBuilder::from_bytes(signature)?.with_policy(&policy, time, helper)?;
verifier.verify_bytes(data)?;

Ok(VerificationSuccess::ValidSignature)
}

/// Returns hexadecimal encoded sha512 hash of the file
Expand Down Expand Up @@ -100,9 +124,12 @@ impl SignatureVerifier {
hash_sign_file: &Path,
hash_file: &Path,
) -> Result<VerificationSuccess> {
let v = self.verify(hash_sign_file, hash_file)?;
assert_eq!(v, VerificationSuccess::ValidSignature);
// Read files, hash file contains UTF-8 text.
let hash_r = read_to_string(hash_file)?;
let sign_r = read(hash_sign_file)?;

let v = self.verify_sq(&sign_r, hash_r.as_bytes())?;
assert_eq!(v, VerificationSuccess::ValidSignature);
let file_hash = Self::file_sha512(file)?;
let file_name = String::from_utf8_lossy(file.file_name().unwrap().as_bytes());

Expand Down Expand Up @@ -130,25 +157,28 @@ mod tests {
use super::*;

#[test]
fn it_verifies_signature() {
let verifier = SignatureVerifier::new(PathBuf::from("tools/rudder_plugins_key.gpg"));
fn it_verifies_signature_with_sequoia() {
let verifier =
SignatureVerifier::new(PathBuf::from("tools/rudder_plugins_key.gpg")).unwrap();

assert!(verifier
.verify(
Path::new("tests/signature/SHA512SUMS.asc"),
Path::new("tests/signature/SHA512SUMS")
.verify_sq(
&read(Path::new("tests/signature/SHA512SUMS.asc")).unwrap(),
&read(Path::new("tests/signature/SHA512SUMS")).unwrap()
)
.is_ok());
assert!(verifier
.verify(
Path::new("tests/signature/SHA512SUMS.asc"),
Path::new("tests/signature/SHA512SUMS.wrong")
.verify_sq(
&read(Path::new("tests/signature/SHA512SUMS.asc")).unwrap(),
&read(Path::new("tests/signature/SHA512SUMS.wrong")).unwrap()
)
.is_err());
}

#[test]
fn it_verifies_files() {
let verifier = SignatureVerifier::new(PathBuf::from("tools/rudder_plugins_key.gpg"));
let verifier =
SignatureVerifier::new(PathBuf::from("tools/rudder_plugins_key.gpg")).unwrap();
assert!(verifier
.verify_file(
Path::new("tests/signature/rudder-plugin-zabbix-8.0.3-2.1.rpkg"),
Expand Down
Binary file modified relay/sources/rudder-package/tools/rudder_plugins_key.gpg
Binary file not shown.

0 comments on commit 73149b5

Please sign in to comment.