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

Hashes revocation #3

Closed
wants to merge 10 commits into from
Closed

Conversation

amanjeev
Copy link
Member

@amanjeev amanjeev commented May 21, 2024

Adds the ability to check for revoked content which are served as signed hashes.

Ticket: https://ferroussystems.clickup.com/t/86947z6fp

@amanjeev amanjeev self-assigned this May 21, 2024
@amanjeev amanjeev force-pushed the amanjeev/hashes-hashes-we-all-revoke-down branch 2 times, most recently from 912efed to 84db7b4 Compare May 21, 2024 19:13
@amanjeev amanjeev marked this pull request as ready for review May 22, 2024 01:32
@amanjeev amanjeev force-pushed the amanjeev/hashes-hashes-we-all-revoke-down branch from 023caa2 to 5dd5500 Compare May 22, 2024 03:18
crates/criticaltrust/src/manifests.rs Outdated Show resolved Hide resolved
crates/criticaltrust/src/keys/public.rs Outdated Show resolved Hide resolved
crates/criticaltrust/src/keys/public.rs Outdated Show resolved Hide resolved
crates/criticaltrust/src/keys/public.rs Outdated Show resolved Hide resolved
@@ -106,7 +106,7 @@ fn verify_signature<T: Signable>(
None => continue,
};

match key.verify(T::SIGNED_BY_ROLE, &signed, &signature.signature) {
match key.verify_payload(T::SIGNED_BY_ROLE, &signed, &signature.signature) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check the revocation in most cases when decoding a signed payload. The two only cases where we can't realistically do that is (a) when decoding the public keys (as at that point we haven't decoded the revocation key) and (b) when decoding the revocation payload.

To have a hard-to-misuse interface, I'd add something like this:

pub trait NoRevocationsCheck {}

impl<T: Signable + NoRevocationsCheck> SignedPayload<T> {
    pub fn get_verified_without_checking_revocations(...) -> ... {}
}

Then we can implement NoRevocationsCheck just on the payloads where we have to avoid checking revocations.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pietroalbini I did add the trait but I could not achieve everything, including calling verify_signature standalone function. Given the deadline is cutting close, I created a ticket for 1.1 https://ferroussystems.clickup.com/t/8694mfy8z for myself.

@amanjeev amanjeev force-pushed the amanjeev/hashes-hashes-we-all-revoke-down branch from ba103a7 to fbf9cde Compare May 22, 2024 14:04
@amanjeev amanjeev force-pushed the amanjeev/hashes-hashes-we-all-revoke-down branch from fbf9cde to 7733b97 Compare May 22, 2024 15:51
Reviewed-by: Pietro Albini <pietro.albini@ferrous-systems.com>

Ticket: https://ferroussystems.clickup.com/t/86947z6fp
@@ -109,6 +109,7 @@ mod tests {
// "localstack", a local replica of AWS services meant for testing.

#[test]
#[ignore = "This test will be enabled in a later release."]
fn test_roundtrip() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pietroalbini I have no idea why yet, but this test is super flaky.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just started breaking???

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the failure you're referring to?

$ cargo test -p criticaltrust --features aws-kms roundtrip
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.08s
     Running unittests src/lib.rs (target/debug/deps/criticaltrust-6080a18de3f40575)

running 1 test
test keys::pair_aws_kms::tests::test_roundtrip ... FAILED

failures:

---- keys::pair_aws_kms::tests::test_roundtrip stdout ----
running "docker" "pull" "localstack/localstack:2.2.0"
finished running "docker" "pull" "localstack/localstack:2.2.0"
running "docker" "create" "--name" "criticaltrust-localstack-3090418271525868829" "-p" "4566" "localstack/localstack:2.2.0"
finished running "docker" "create" "--name" "criticaltrust-localstack-3090418271525868829" "-p" "4566" "localstack/localstack:2.2.0"
running "docker" "start" "criticaltrust-localstack-3090418271525868829"
finished running "docker" "start" "criticaltrust-localstack-3090418271525868829"
running "docker" "port" "criticaltrust-localstack-3090418271525868829" "4566/tcp"
finished running "docker" "port" "criticaltrust-localstack-3090418271525868829" "4566/tcp"
thread 'keys::pair_aws_kms::tests::test_roundtrip' panicked at crates/criticaltrust/src/keys/pair_aws_kms.rs:214:18:
failed to create kms key: DispatchFailure(DispatchFailure { source: ConnectorError { kind: Io, source: hyper::Error(Canceled, hyper::Error(IncompleteMessage)), connection: Unknown } })
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
running "docker" "stop" "criticaltrust-localstack-3090418271525868829" "-t" "10"
finished running "docker" "stop" "criticaltrust-localstack-3090418271525868829" "-t" "10"
running "docker" "rm" "criticaltrust-localstack-3090418271525868829"
finished running "docker" "rm" "criticaltrust-localstack-3090418271525868829"


failures:
    keys::pair_aws_kms::tests::test_roundtrip

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 88 filtered out; finished in 4.19s

error: test failed, to rerun pass `-p criticaltrust --lib`

This seems to break on main too. :(

amanjeev added 2 commits May 22, 2024 16:01
…tory for EphemeralKeyPair

Reviewed-by: Pietro Albini <pietro.albini@ferrous-systems.com>

Ticket: https://ferroussystems.clickup.com/t/86947z6fp
Checking for payload's SHA256 is the requirement, not the base64 of the signature.

Reviewed-by: Ana Hobden <ana.hobden@ferrous-systems.com>

Ticket: https://ferroussystems.clickup.com/t/86947z6fp
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;

/// Expiration of `RevocationInfo` should be at least this many number of days out from now.
const MAX_REVOCATION_INFO_EXPIRATION_DURATION: i64 = 90;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this ok with the current expiration of the payload of 2024-10-01?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Days from today to Oct 1, 2024 are about 130. So, this seems fine to me.

/// Make sure verification of `PublicKey` type does no checks for revocations.
///
/// This is done because when decoding the public keys, we haven't yet decoded the revocation key.
impl NoRevocationCheck for SignedPayload<PublicKey> {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
impl NoRevocationCheck for SignedPayload<PublicKey> {}
impl NoRevocationCheck for PublicKey {}

If we implement it for SignedPayload<PublicKey>:

  • The trait bound of impl<T: NoRevocationCheck> SignedPayload<T> will not work.
  • It won't be possible to implement NoRevocationCheck outside CriticalTrust due to the orphan rule (as both NoRevocationCheck and SignedPayload are defined in CriticalTrust).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since, the get_verified method is on SignedPayload, you won't be able to restrict that method if you do impl NoRevocationCheck for PublicKey {}. IIUC, that was your idea, to make this API more stringent so harder to abuse. There is no get_verified on PublicKey, as an example.

I actually started with what you suggested and learned that, that does not work. as in one could still call .get_verified on PublicKey.

/// Make sure verification of `RevocationInfo` type does no checks for revocations.
///
/// If we did, then this would be a circular logic. We say no to such logic.
impl NoRevocationCheck for SignedPayload<RevocationInfo> {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the other NoRevocationCheck impl.

@@ -106,7 +133,11 @@ fn verify_signature<T: Signable>(
None => continue,
};

match key.verify(T::SIGNED_BY_ROLE, &signed, &signature.signature) {
match key.verify_without_checking_revocations(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need to make sure this verifies revocations if called by get_verified or into_verified.

Comment on lines +33 to +37
revoked_signatures: SignedPayload::new(&RevocationInfo {
revoked_content_sha256: Vec::new(),
expires_at: OffsetDateTime::now_utc(),
})
.unwrap(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct: it expires immediately, and it's not signed. It will cause CriticalUp tests to fail.

let key = generate(KeyRole::Root, None);
let signature = key.sign(&SAMPLE_REVOKED_PAYLOAD).unwrap();

let payload_sha256 = String::from_utf8(SAMPLE_REVOKED_PAYLOAD.as_bytes().to_vec()).unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the sha256?

let payload_sha256 = String::from_utf8(SAMPLE_REVOKED_PAYLOAD.as_bytes().to_vec()).unwrap();
let signed_revocation_info = SignedPayload::new(&RevocationInfo {
revoked_content_sha256: vec![payload_sha256],
expires_at: days_diff(10000).unwrap(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is failing due to the expires_at date, not due to the content being revoked.

let verified_revoked_content = signed_revocation_info.get_verified(self)?;
let expiration_in_days =
(verified_revoked_content.expires_at - OffsetDateTime::now_utc()).whole_days();
if expiration_in_days < MAX_REVOCATION_INFO_EXPIRATION_DURATION {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit confused by this check. The check doesn't seem to actually check the revocation date, only that the period between the expiration and now is less than 90 days.

I would make two separate checks:

  • expires_at < now() + 90 days
  • expires_at > now()

@amanjeev amanjeev marked this pull request as draft May 23, 2024 16:08
@amanjeev
Copy link
Member Author

A new PR will be opened at a later stage using reviews from this PR.

@amanjeev amanjeev closed this May 27, 2024
@amanjeev amanjeev deleted the amanjeev/hashes-hashes-we-all-revoke-down branch August 30, 2024 01:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants