Skip to content

Commit

Permalink
Hashes revocation: ability to add content revocation
Browse files Browse the repository at this point in the history
  • Loading branch information
amanjeev committed May 21, 2024
1 parent 9da6adb commit 84db7b4
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 15 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/criticaltrust/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ pub enum Error {
aws_smithy_runtime_api::client::orchestrator::HttpResponse,
>,
),
#[error("failed verification while converting signature to string")]
SignatureConversionFailure,
}
2 changes: 1 addition & 1 deletion crates/criticaltrust/src/keys/pair_aws_kms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ mod tests {
let signature = keypair.sign(&payload).expect("failed to sign");
keypair
.public()
.verify(KeyRole::Root, &payload, &signature)
.verify(KeyRole::Root, &payload, &signature, None)
.expect("failed to verify");
}

Expand Down
52 changes: 41 additions & 11 deletions crates/criticaltrust/src/keys/public.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use super::newtypes::SignatureBytes;
use crate::keys::newtypes::{PayloadBytes, PublicKeyBytes};
use crate::keys::KeyAlgorithm;
use crate::manifests::RevocationInfo;
use crate::sha256::hash_sha256;
use crate::signatures::{PublicKeysRepository, Signable};
use crate::signatures::{PublicKeysRepository, Signable, SignedPayload};
use crate::Error;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
Expand Down Expand Up @@ -31,7 +32,30 @@ impl PublicKey {
role: KeyRole,
payload: &PayloadBytes<'_>,
signature: &SignatureBytes<'_>,
signed_revocation_info: Option<SignedPayload<RevocationInfo>>,
) -> Result<(), Error> {
// We need to check if there is revoked content. If the following checks pass, then bail out
// early with an error.
// 1. Check if the expiration date has passed.
// 2. Check whether the `signature` is inside the vector
// `RevocationInfo.revoked_content_sha256`.
if let Some(revoked_hashes) = signed_revocation_info {
let verified_revoked_content = revoked_hashes.get_verified(self)?;

if OffsetDateTime::now_utc() > verified_revoked_content.expires_at {
return Err(Error::VerificationFailed);
}

let signature_as_string = String::from_utf8(signature.as_bytes().to_vec())
.map_err(|_err| Error::SignatureConversionFailure)?;
if verified_revoked_content
.revoked_content_sha256
.contains(&signature_as_string)
{
return Err(Error::VerificationFailed);
}
}

if role != self.role || role == KeyRole::Unknown {
return Err(Error::VerificationFailed);
}
Expand Down Expand Up @@ -83,6 +107,8 @@ pub enum KeyRole {
Packages,
/// `redirects` key role, used to sign dynamic server redirects.
Redirects,
/// `revocation` key role, used to sign revoked content hashes.
Revocation,
/// `root` key role, used to sign other keys.
Root,
#[serde(other)]
Expand Down Expand Up @@ -116,7 +142,7 @@ mod tests {

assert!(key
.public()
.verify(KeyRole::Root, &SAMPLE_PAYLOAD, &signature)
.verify(KeyRole::Root, &SAMPLE_PAYLOAD, &signature, None)
.is_ok())
}

Expand All @@ -127,7 +153,7 @@ mod tests {

assert!(key
.public()
.verify(KeyRole::Root, &SAMPLE_PAYLOAD, &signature)
.verify(KeyRole::Root, &SAMPLE_PAYLOAD, &signature, None)
.is_ok());
}

Expand All @@ -138,7 +164,7 @@ mod tests {

assert!(matches!(
key.public()
.verify(KeyRole::Packages, &SAMPLE_PAYLOAD, &signature),
.verify(KeyRole::Packages, &SAMPLE_PAYLOAD, &signature, None),
Err(Error::VerificationFailed)
));
}
Expand All @@ -150,7 +176,7 @@ mod tests {

assert!(matches!(
key.public()
.verify(KeyRole::Unknown, &SAMPLE_PAYLOAD, &signature),
.verify(KeyRole::Unknown, &SAMPLE_PAYLOAD, &signature, None),
Err(Error::VerificationFailed)
));
}
Expand All @@ -162,7 +188,7 @@ mod tests {

assert!(matches!(
key.public()
.verify(KeyRole::Root, &SAMPLE_PAYLOAD, &signature),
.verify(KeyRole::Root, &SAMPLE_PAYLOAD, &signature, None),
Err(Error::VerificationFailed)
));
}
Expand All @@ -179,7 +205,8 @@ mod tests {
key.public().verify(
KeyRole::Root,
&SAMPLE_PAYLOAD,
&SignatureBytes::owned(bad_signature)
&SignatureBytes::owned(bad_signature),
None
),
Err(Error::VerificationFailed)
));
Expand All @@ -194,7 +221,8 @@ mod tests {
key.public().verify(
KeyRole::Root,
&PayloadBytes::borrowed("Hello world!".as_bytes()),
&signature
&signature,
None
),
Err(Error::VerificationFailed)
));
Expand All @@ -208,7 +236,8 @@ mod tests {
key.public().verify(
KeyRole::Root,
&SAMPLE_PAYLOAD,
&SignatureBytes::borrowed(&[])
&SignatureBytes::borrowed(&[]),
None
),
Err(Error::VerificationFailed)
));
Expand All @@ -223,7 +252,7 @@ mod tests {

assert!(matches!(
key2.public()
.verify(KeyRole::Root, &SAMPLE_PAYLOAD, &signature),
.verify(KeyRole::Root, &SAMPLE_PAYLOAD, &signature, None),
Err(Error::VerificationFailed)
));
}
Expand All @@ -237,7 +266,7 @@ mod tests {
public.algorithm = KeyAlgorithm::Unknown;

assert!(matches!(
public.verify(KeyRole::Root, &SAMPLE_PAYLOAD, &signature),
public.verify(KeyRole::Root, &SAMPLE_PAYLOAD, &signature, None),
Err(Error::UnsupportedKey)
));
}
Expand Down Expand Up @@ -293,6 +322,7 @@ mod tests {
KeyRole::Root,
&SAMPLE_PAYLOAD,
&SignatureBytes::owned(base64_decode(SAMPLE_SIGNATURE).unwrap()),
None,
)
.unwrap();
}
Expand Down
17 changes: 16 additions & 1 deletion crates/criticaltrust/src/manifests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::keys::{KeyRole, PublicKey};
use crate::signatures::{Signable, SignedPayload};
use serde::de::Error as _;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;

/// Typed representation of a manifest version number.
///
Expand Down Expand Up @@ -151,12 +152,26 @@ pub struct PackageFile {
pub needs_proxy: bool,
}

// Revocations

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct RevocationInfo {
pub revoked_content_sha256: Vec<String>,
pub expires_at: OffsetDateTime,
}

impl Signable for RevocationInfo {
const SIGNED_BY_ROLE: KeyRole = KeyRole::Revocation;
}

// Keys

#[derive(Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct KeysManifest {
pub version: ManifestVersion<1>,
pub keys: Vec<SignedPayload<PublicKey>>,
pub revoked_signatures: Option<SignedPayload<RevocationInfo>>,
}

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion crates/criticaltrust/src/signatures/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ fn verify_signature<T: Signable>(
None => continue,
};

match key.verify(T::SIGNED_BY_ROLE, &signed, &signature.signature) {
match key.verify(T::SIGNED_BY_ROLE, &signed, &signature.signature, None) {
Ok(()) => {}
Err(Error::VerificationFailed) => continue,
Err(other) => return Err(other),
Expand Down
1 change: 1 addition & 0 deletions crates/mock-download-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ criticaltrust = { path = "../criticaltrust" }
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
tiny_http = { version = "0.12.0", default-features = false, features = ["rustls"] }
time = { version = "0.3.7", features = ["std", "serde", "serde-well-known"] }
1 change: 1 addition & 0 deletions crates/mock-download-server/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ fn handle_v1_keys(data: &Data) -> Result<Resp, Resp> {
Ok(Resp::json(&criticaltrust::manifests::KeysManifest {
version: ManifestVersion,
keys: data.keys.clone(),
revoked_signatures: data.revoked_signatures.clone(),
}))
}

Expand Down
11 changes: 10 additions & 1 deletion crates/mock-download-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ mod server;

pub use crate::server::MockServer;
use criticaltrust::keys::PublicKey;
use criticaltrust::manifests::ReleaseManifest;
use criticaltrust::manifests::{ReleaseManifest, RevocationInfo};
use criticaltrust::signatures::SignedPayload;
use serde::Serialize;
use std::borrow::Cow;
use std::collections::HashMap;
use time::OffsetDateTime;

#[derive(Serialize, Clone)]
#[serde(rename_all = "kebab-case")]
Expand All @@ -20,6 +21,7 @@ pub struct AuthenticationToken {
pub struct Data {
pub tokens: HashMap<String, AuthenticationToken>,
pub keys: Vec<SignedPayload<PublicKey>>,
pub revoked_signatures: Option<SignedPayload<RevocationInfo>>,
pub release_manifests: HashMap<(String, String), ReleaseManifest>,
}

Expand All @@ -28,6 +30,13 @@ pub fn new() -> Builder {
data: Data {
tokens: HashMap::new(),
keys: Vec::new(),
revoked_signatures: Some(
SignedPayload::new(&RevocationInfo {
revoked_content_sha256: Vec::new(),
expires_at: OffsetDateTime::now_utc(),
})
.unwrap(),
),
release_manifests: HashMap::new(),
},
}
Expand Down

0 comments on commit 84db7b4

Please sign in to comment.