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

remove unnecessary Result-wrapping in crypto.rs #474

Closed

Conversation

cosmicexplorer
Copy link
Contributor

@cosmicexplorer cosmicexplorer commented Jun 27, 2022

Broken out from #287.

  1. Separate CTR and CBC encryption methods in ctr and cbc submodules.
  2. Define pub const sizes for array lengths.
  3. Require fixed-size arrays to en/decryption methods to remove EncryptionError and one case of DecryptionError.
  4. Rename methods to aes_256_* and aes_256_ctr_hmac_* for consistency.

TODO

@cosmicexplorer cosmicexplorer force-pushed the document-crypto-module branch 3 times, most recently from 9e79b45 to 78efa92 Compare June 27, 2022 08:12
@cosmicexplorer cosmicexplorer marked this pull request as draft June 27, 2022 08:40
@cosmicexplorer cosmicexplorer marked this pull request as ready for review June 27, 2022 08:41
@cosmicexplorer cosmicexplorer marked this pull request as draft June 27, 2022 08:41
@cosmicexplorer cosmicexplorer marked this pull request as ready for review July 18, 2022 22:37
cosmicexplorer added a commit to cosmicexplorer/libsignal that referenced this pull request Jul 19, 2022
@cosmicexplorer cosmicexplorer force-pushed the document-crypto-module branch from 78efa92 to 8f29bf9 Compare July 19, 2022 00:02
cosmicexplorer added a commit to cosmicexplorer/libsignal that referenced this pull request Jul 19, 2022
cosmicexplorer added a commit to cosmicexplorer/libsignal that referenced this pull request Jul 19, 2022
@jrose-signal
Copy link
Contributor

These aren't pub for a reason: if someone wants to use these operations, they should get them directly from the RustCrypto crates. These are pretty much just convenience wrappers because of how they're used within the protocol. (The hardcoded "first ten bytes as a MAC" is something that a generalized library probably wouldn't hardcode.)

I think you're right about EncryptionError, though. Someone has to check that these keys are the right length, but it can happen a level up. And then I think DecryptionError collapses down to a struct instead of a one-case enum (not that it matters so much for an implementation detail).

cosmicexplorer added a commit to cosmicexplorer/libsignal that referenced this pull request Sep 9, 2022
cosmicexplorer added a commit to cosmicexplorer/libsignal that referenced this pull request Sep 9, 2022
cosmicexplorer added a commit to cosmicexplorer/libsignal that referenced this pull request Sep 9, 2022
@cosmicexplorer cosmicexplorer changed the title add docstrings and doctests to crypto.rs remove unnecessary Result-wrapping in crypto.rs Sep 9, 2022
@cosmicexplorer
Copy link
Contributor Author

cosmicexplorer commented Sep 9, 2022

These are pretty much just convenience wrappers because of how they're used within the protocol.

Ok, so in light of this I've changed it to pub(crate) mod crypto; in lib.rs, removed the doctests, and re-focused this PR on just removing unnecessary Results by enforcing fixed-size slices where possible.

However, on top of that, I've also kept in:

  • the cbc and ctr module organization,
  • some docstrings for parts that I thought weren't immediately clear,
  • new en/decryption test cases using Rng::gen() (vs the existing test cases which only check a single literal hex key).

I can revert any of that as desired.

Comment on lines +19 to +26
/// bad ciphertext: {0}
///
/// Either the input is malformed, or the MAC doesn't match on decryption.
///
/// These cases should not be distinguished; message corruption can cause either problem.
#[derive(Debug, Display, Error)]
#[ignore_extra_doc_attributes]
pub struct DecryptionError(pub &'static str);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: For some reason displaydoc on structs weirds me out; maybe it's because enum cases often get abbreviated docs anyway in a way that types don't. Mind switching to thiserror-style for the Display representation instead?

BadCiphertext(&'static str),
}
// TODO: Could be nice to have a type-safe library for manipulating units of bytes safely.
const BITS_PER_BYTE: usize = std::mem::size_of::<u8>() * 8;
Copy link
Contributor

Choose a reason for hiding this comment

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

Rust guarantees that size_of::<u8>() is 1, so I think it's fine to just write 8 here. (The named constant still makes sense though.)

Ok(ctext)
}
/// Use AES-256 in CTR mode.
pub mod ctr {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if I really see the point of this. Yes, there's a little more organization, but "CTR" and "CBR" don't imply "AES-256" by themselves, and they're all just convenience wrappers for the session implementation in any case.

}
/// Length in bytes of the [`Hmac`] key used for [`aes_256_ctr_hmac_encrypt`] and
/// [`aes_256_ctr_hmac_decrypt`].
pub const MAC_KEY_LENGTH: usize = 80 / BITS_PER_BYTE;
Copy link
Contributor

Choose a reason for hiding this comment

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

The MAC key isn't 80 bits; only the MAC itself is. The key can in theory be any length and in practice we use a 32-byte key.

Comment on lines +108 to +112
let key: [u8; AES_256_KEY_SIZE] =
hex::decode("603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4")
.expect("valid hex")
.try_into()
.expect("correct array size");
Copy link
Contributor

Choose a reason for hiding this comment

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

The hex_literal crate used elsewhere in the workspace can check these conditions at compile time; if you're adding more tests it might be a good time to switch over (incrementally).

@@ -24,7 +24,7 @@

mod address;
mod consts;
mod crypto;
pub(crate) mod crypto;
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't need to be changed, right?

&sender_eph_keys.mac_key,
)
.expect("just generated these keys, they should be correct");
array_ref![&sender_eph_keys.mac_key, 0, crypto::ctr::MAC_KEY_LENGTH],
Copy link
Contributor

Choose a reason for hiding this comment

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

…and this is where the MAC key is incorrectly truncated, and our lack of "decrypting this existing message with this session state" tests has come back to bite us.

Comment on lines -604 to -613
Err(crypto::DecryptionError::BadKeyOrIv) => {
log::warn!(
"{} session state corrupt for {}",
current_or_previous,
remote_address,
);
return Err(SignalProtocolError::InvalidSessionStructure(
"invalid receiver chain message keys",
));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is the change that convinced me this is a good idea: at first I thought "what happens if those fields are corrupt"? But they can't be, because we already check that when getting the message keys out of the session state, and that wasn't in the type system anywhere.

log::error!(
"incoming sender key state corrupt for {}, distribution ID {}, chain ID {}",
sender,
"outgoing sender key state corrupt for distribution ID {}",
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy/paste error: "incoming" got switched for "outgoing" here and below.

Comment on lines +43 to +58
let cipher_key: [u8; crypto::AES_256_KEY_SIZE] =
message_keys.cipher_key().try_into().map_err(|_| {
log::error!(
"outgoing sender key state corrupt for distribution ID {}",
distribution_id,
);
SignalProtocolError::InvalidSenderKeySession { distribution_id }
})?;
let iv: [u8; crypto::AES_NONCE_SIZE] = message_keys.iv().try_into().map_err(|_| {
log::error!(
"outgoing sender key state corrupt for distribution ID {}",
distribution_id,
);
SignalProtocolError::InvalidSenderKeySession { distribution_id }
})?;
let ciphertext = crypto::cbc::aes_256_cbc_encrypt(plaintext, &cipher_key, &iv);
Copy link
Contributor

Choose a reason for hiding this comment

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

This duplication is a little annoying, but I can't see a great way to clean it up. Do you think it's worth putting that error generation into a local helper?

@stale
Copy link

stale bot commented Feb 4, 2023

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Feb 4, 2023
@stale
Copy link

stale bot commented Feb 11, 2023

This issue has been closed due to inactivity.

@stale stale bot closed this Feb 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging this pull request may close these issues.

2 participants