Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signature check #10

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
695 changes: 671 additions & 24 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::SignatureCheckDisabled => "",
};
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),
/// Signature is Disabled
SignatureCheckDisabled,
}

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::SignatureCheckDisabled => write!(
f,
"Signature check disabled. To enable it, 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::SignatureCheckDisabled);
}
};
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(
"Siganture 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(
"Siganture verification failed".to_string(),
))
}

#[derive(Debug, Clone, PartialEq, Eq)]
/// Hasher implements the used hashing algorithm to calculate the hashsum
pub enum Hasher {
Expand Down
2 changes: 2 additions & 0 deletions rust/nasl-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Options:


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 enable the signature check, 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::SignatureCheckDisabled => "",
VerifyError::BadSignature(_) => "",
};
Self {
filename: filename.to_string(),
Expand Down
22 changes: 21 additions & 1 deletion rust/nasl-cli/src/feed_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,30 @@ where
// 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);

match updater.verify_signature() {
Ok(_) => tracing::info!("Signature check succsessful"),
Err(feed::VerifyError::SignatureCheckDisabled) => {
tracing::warn!("Signature check disabled")
}
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())),
});
}
}

for s in updater {
let s = s?;
tracing::trace!("updated {s}");
Expand Down
10 changes: 10 additions & 0 deletions rust/nasl-syntax/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ where
pub trait Loader {
/// Resolves the given key to nasl code
fn load(&self, key: &str) -> Result<String, LoadError>;
/// Return the root plugins folder
fn root_path(&self) -> Result<String, LoadError>;
}

/// Returns given key as BufReader
Expand All @@ -70,6 +72,9 @@ impl Loader for NoOpLoader {
fn load(&self, _: &str) -> Result<String, LoadError> {
Ok(String::default())
}
fn root_path(&self) -> Result<String, LoadError> {
Ok(String::default())
}
}

impl Default for Box<dyn Loader> {
Expand Down Expand Up @@ -149,4 +154,9 @@ where
// unfortunately nasl is still in iso-8859-1
load_non_utf8_path(path.as_path())
}
/// Return the root path of the plugins directory
fn root_path(&self) -> Result<String, LoadError> {
let path = self.root.as_ref().to_str().unwrap_or_default().to_string();
Ok(path)
}
}