Skip to content

Commit

Permalink
Add: Verify signature of sha256sums file (#1505)
Browse files Browse the repository at this point in the history
* Add: Verify signature of sha256sums file

* Update README files

* Use own fork of libssh-rs, which unpins the openssl-sys version from libssh-rs

* Fix typos

* fix tests

* Since I got an answer from the libssh-rs developer, now the dependency issue is solved and we can use the crate again

* Add: add 'signature-check' command line option to nasl-cli

This allows to enable the feed signature check.

* Add: add possibility to enable signature check to openvasd

This allows to enable the feed signature check before getting the OIDs. This can be performed via the signature-check cmd-line opiton
or via the config file.

* update readme
  • Loading branch information
jjnicola authored Oct 20, 2023
1 parent 34d380a commit 0899d3f
Show file tree
Hide file tree
Showing 21 changed files with 1,041 additions and 151 deletions.
856 changes: 725 additions & 131 deletions rust/Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions rust/feed/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ nasl-interpreter = { path = "../nasl-interpreter" }
storage = { path = "../storage" }
sha2 = "0.10.6"
hex = "0.4.3"
sequoia-ipc = "0.30.1"
sequoia-openpgp = { version ="1.16.1", default-features = false, features = ["crypto-openssl"] }
anyhow = "1.0.75"
tracing = "0.1.37"
1 change: 1 addition & 0 deletions rust/feed/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ FEED_NAME = "short name of the feed";
## Verify

[Implements](./src/verify/mod.rs) a [HashSumNameLoader](./src/verify/mod.rs#L93) that loads the filenames defined in the sha256sums and verifies the corresponding hashsum.
Also, implements a [signature verifier](./src/verify/mod.rs#L163) for checking the signature of the sha256sums file.

### Example

Expand Down
2 changes: 2 additions & 0 deletions rust/feed/src/update/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ impl From<verify::Error> for Error {
actual: _,
key,
} => key,
crate::VerifyError::BadSignature(e) => e,
crate::VerifyError::MissingKeyring => "",
};
Self {
key: fin.to_string(),
Expand Down
7 changes: 6 additions & 1 deletion rust/feed/src/update/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use nasl_interpreter::{
};
use storage::{nvt::NVTField, Dispatcher, NoOpRetriever};

use crate::verify;
use crate::verify::{self, signature_check};

pub use self::error::ErrorKind;

Expand Down Expand Up @@ -160,6 +160,11 @@ where
}
Err(ErrorKind::MissingExit(key.as_ref().into()))
}
/// Perform a signature check of the sha256sums file
pub fn verify_signature(&self) -> Result<(), verify::Error> {
let path = self.loader.root_path().unwrap();
signature_check(&path)
}
}

impl<S, L, V, K> Iterator for Update<S, L, V, K>
Expand Down
151 changes: 151 additions & 0 deletions rust/feed/src/verify/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,25 @@

use std::{
fmt::Display,
fs::File,
io::{self, BufRead, BufReader, Read},
};

use hex::encode;
use nasl_interpreter::{AsBufReader, LoadError};
use sha2::{Digest, Sha256};

use openpgp::{
parse::stream::{
DetachedVerifierBuilder, GoodChecksum, MessageLayer, MessageStructure, VerificationHelper,
},
parse::Parse,
policy::StandardPolicy,
Cert, KeyHandle,
};
use sequoia_ipc::keybox::{Keybox, KeyboxRecord};
use sequoia_openpgp as openpgp;

#[derive(Debug, Clone, PartialEq, Eq)]
/// Defines error cases that can happen while verifying
pub enum Error {
Expand All @@ -35,6 +47,10 @@ pub enum Error {
/// The key of the file
key: String,
},
/// When signature check of the hashsumfile fails
BadSignature(String),
/// Missingkeyring
MissingKeyring,
}

impl From<LoadError> for Error {
Expand All @@ -53,10 +69,145 @@ impl Display for Error {
actual,
key,
} => write!(f, "{key} hash {actual} is not as expected ({expected})."),
Error::BadSignature(e) => write!(f, "{e}"),
Error::MissingKeyring => write!(
f,
"Signature check is enabled but there is no keyring. Set the GNUPGHOME environment variable"
),
}
}
}

struct VHelper {
keyring: String,
not_before: Option<std::time::SystemTime>,
not_after: std::time::SystemTime,
}

impl VHelper {
fn new(keyring: String) -> Self {
Self {
keyring,
not_before: None,
not_after: std::time::SystemTime::now(),
}
}
}

impl VerificationHelper for VHelper {
fn get_certs(&mut self, _ids: &[KeyHandle]) -> openpgp::Result<Vec<Cert>> {
let file = File::open(self.keyring.as_str())?;
let kbx = Keybox::from_reader(file)?;

let certs = kbx
// Keep only records which were parsed successfully.
.filter_map(|kbx_record| kbx_record.ok())
// Map the OpenPGP records to the contained certs.
.filter_map(|kbx_record| match kbx_record {
KeyboxRecord::OpenPGP(r) => Some(r.cert()),
_ => None,
})
.collect::<openpgp::Result<Vec<Cert>>>()?;
Ok(certs)
}

fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> {
for layer in structure.into_iter() {
match layer {
MessageLayer::SignatureGroup { results } => {
for result in results {
match result {
Ok(GoodChecksum { sig, ka, .. }) => {
match (
sig.signature_creation_time(),
self.not_before,
self.not_after,
) {
(None, _, _) => {
eprintln!("Malformed signature:");
}
(Some(t), Some(not_before), not_after) => {
if t < not_before {
eprintln!(
"Signature by {:X} was created before \
the --not-before date.",
ka.key().fingerprint()
);
} else if t > not_after {
eprintln!(
"Signature by {:X} was created after \
the --not-after date.",
ka.key().fingerprint()
);
}
}
(Some(t), None, not_after) => {
if t > not_after {
eprintln!(
"Signature by {:X} was created after \
the --not-after date.",
ka.key().fingerprint()
);
}
}
};
}
Err(e) => return Err(anyhow::Error::msg(e.to_string())),
}
}
}
MessageLayer::Compression { .. } => (),
_ => unreachable!(),
}
}
Ok(())
}
}

pub fn signature_check(feed_path: &str) -> Result<(), Error> {
let mut gnupghome = match std::env::var("GNUPGHOME") {
Ok(v) => v,
Err(_) => {
return Err(Error::MissingKeyring);
}
};
gnupghome.push_str("/pubring.kbx");

let helper = VHelper::new(gnupghome);

let mut sign_path = feed_path.to_owned();
sign_path.push_str("/sha256sums.asc");
let mut sig_file = File::open(sign_path).unwrap();
let mut signature = Vec::new();
let _ = sig_file.read_to_end(&mut signature);

let mut data_path = feed_path.to_owned();
data_path.push_str("/sha256sums");
let mut data_file = File::open(data_path).unwrap();
let mut data = Vec::new();
let _ = data_file.read_to_end(&mut data);

let v = match DetachedVerifierBuilder::from_bytes(&signature[..]) {
Ok(v) => v,
Err(_) => {
return Err(Error::BadSignature(
"Signature verification failed".to_string(),
));
}
};

let p = &StandardPolicy::new();
if let Ok(mut verifier) = v.with_policy(p, None, helper) {
match verifier.verify_bytes(data) {
Ok(_) => return Ok(()),
Err(e) => return Err(Error::BadSignature(e.to_string())),
}
};
Err(Error::BadSignature(
"Signature verification failed".to_string(),
))
}

#[derive(Debug, Clone, PartialEq, Eq)]
/// Hasher implements the used hashing algorithm to calculate the hashsum
pub enum Hasher {
Expand Down
3 changes: 1 addition & 2 deletions rust/nasl-builtin-ssh/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ edition = "2021"
[dependencies]
nasl-builtin-utils = {path = "../nasl-builtin-utils"}
nasl-syntax = {path = "../nasl-syntax"}
# requires libssh > 3.0.0 to be installed
libssh-rs = {git = "https://github.com/wez/libssh-rs.git", branch = "main"}
libssh-rs = {version = "~0.2", features = ["vendored-openssl", "vendored"]}
storage = {path = "../storage"}

[dev-dependencies]
Expand Down
4 changes: 3 additions & 1 deletion rust/nasl-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ Usage `nasl-cli feed update [OPTIONS]`
Options:
- `-p`, `--path <FILE>`: Path to the feed.
- `-r`, `--redis <VALUE>`: Redis url. Must either start `unix://` or `redis://`.

- `-x`, `--signature-check`: Enable NASL signature check.

On `feed update` it will first read the `sha256sums` file within the feed directory and verify each file with the corresponding sha256sums. When the hash is correct it will execute each mentioned `*.nasl` script within that dir with `description = 1`.
Optionally, it is possible to perform a signature verification of the sha256sums file before uploading. To perform the signature check, also the environment variable `GNUPGHOME` must be set with the gnupg home directory, where the `pubring.kbx` file is stored.

#### transform
Runs nasl scripts in description mode and returns it as a json array into stdout.
Expand All @@ -73,6 +74,7 @@ Options:


On `feed transform` it will first read the `sha256sums` file within the feed directory and verify each file with the corresponding sha256sums. When the hash is correct it will execute each mentioned `*.nasl` script within that dir with `description = 1`.
Optionally, it is possible to perform a signature verification of the sha256sums file before the transformation. To enable the signature check, the environment variable `GNUPGHOME` must be set with the gnupg home directory, where the `pubring.kbx` file is stored.

It will produce a json array in stdout in the format described within [json-storage](../json-storage/README.md).

Expand Down
2 changes: 2 additions & 0 deletions rust/nasl-cli/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ impl From<VerifyError> for CliError {
actual: _,
key,
} => key,
VerifyError::MissingKeyring => "Signature check enabled but missing keyring. Set GNUPGHOME environment variable.",
VerifyError::BadSignature(_) => "Bad signature",
};
Self {
filename: filename.to_string(),
Expand Down
31 changes: 29 additions & 2 deletions rust/nasl-cli/src/feed_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,45 @@ use storage::Dispatcher;

use crate::CliError;

pub fn run<S>(storage: S, path: PathBuf) -> Result<(), CliError>
pub fn run<S>(storage: S, path: PathBuf, signature_check: bool) -> Result<(), CliError>
where
S: Sync + Send + Dispatcher<String>,
{
tracing::debug!("description run syntax in {path:?}.");
// needed to strip the root path so that we can build a relative path
// e.g. 2006/something.nasl
let loader = FSPluginLoader::new(path);

let verifier = feed::HashSumNameLoader::sha256(&loader)?;
let updater = feed::Update::init("1", 5, loader.clone(), storage, verifier);

if signature_check {
match updater.verify_signature() {
Ok(_) => tracing::info!("Signature check succsessful"),
Err(feed::VerifyError::MissingKeyring) => {
tracing::warn!("Signature check enabled but missing keyring");
return Err(feed::VerifyError::MissingKeyring.into());
}
Err(feed::VerifyError::BadSignature(e)) => {
tracing::warn!("{}", e);
return Err(CliError {
filename: feed::Hasher::Sha256.sum_file().to_string(),
kind: crate::CliErrorKind::LoadError(nasl_syntax::LoadError::Dirty(e)),
});
}
Err(e) => {
tracing::warn!("Unexpected error during signature verification: {e}");
return Err(CliError {
filename: feed::Hasher::Sha256.sum_file().to_string(),
kind: crate::CliErrorKind::LoadError(nasl_syntax::LoadError::Dirty(
e.to_string(),
)),
});
}
}
} else {
tracing::warn!("Signature check disabled");
}

for s in updater {
let s = s?;
tracing::trace!("updated {s}");
Expand Down
Loading

0 comments on commit 0899d3f

Please sign in to comment.