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

[PM-15096] Implement xchacha20-poly1305 cipher functions #150

Merged
merged 11 commits into from
Mar 25, 2025

Conversation

quexten
Copy link
Contributor

@quexten quexten commented Feb 7, 2025

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-15096

📔 Objective

Implements xchacha20-poly1305 (https://libsodium.gitbook.io/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction) as a new encryption type.

Further, this introduces a dependency patch override (to https://github.com/bitwarden/rustcrypto-formats.git at revision 2b27c63034217dd126bbf5ed874da51b84f8c705) for rustcrypto formats, until this gets updated upstream to fix a type incompatibility when building on WASM and with chacha as a dependency.

These functions are unused until a follow-up PR (soon).

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation
    team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed
    issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@quexten quexten marked this pull request as draft February 7, 2025 13:31
@quexten quexten changed the title Change cipher to plain xchachapoly1305 Implement xchachapoly1305 cipher Feb 7, 2025
@quexten quexten changed the title Implement xchachapoly1305 cipher [PM-15096] Implement xchachapoly1305 cipher Feb 7, 2025
Copy link
Contributor

github-actions bot commented Feb 7, 2025

Logo
Checkmarx One – Scan Summary & Details67334bca-c752-4f20-a22c-d48b6c362b37

Great job, no security vulnerabilities found in this Pull Request

Copy link

codecov bot commented Feb 7, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 66.31%. Comparing base (aa9ed2a) to head (747e7a7).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #150      +/-   ##
==========================================
+ Coverage   66.08%   66.31%   +0.22%     
==========================================
  Files         198      199       +1     
  Lines       15505    15609     +104     
==========================================
+ Hits        10247    10351     +104     
  Misses       5258     5258              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@quexten quexten changed the base branch from km/17370/refactor-sdk-symmetric-keys to km/new-key-format February 12, 2025 12:28
@quexten quexten changed the title [PM-15096] Implement xchachapoly1305 cipher [PM-15096] Implement xchacha20-poly1305 cipher Feb 12, 2025
@quexten quexten force-pushed the km/pm-15096/xchacha20-poly1305 branch from 7f33323 to 34f25de Compare February 19, 2025 12:23
@quexten quexten changed the base branch from km/new-key-format to main March 7, 2025 10:36
@quexten quexten force-pushed the km/pm-15096/xchacha20-poly1305 branch from bff507b to 72bcf39 Compare March 7, 2025 10:44
@quexten quexten marked this pull request as ready for review March 7, 2025 11:17
@quexten quexten changed the title [PM-15096] Implement xchacha20-poly1305 cipher [PM-15096] Implement xchacha20-poly1305 cipher functions Mar 7, 2025
@quexten quexten requested review from dani-garcia and MGibson1 March 7, 2025 11:18
generic-array = { version = ">=0.14.7, <1.0", features = ["zeroize"] }
hkdf = ">=0.12.3, <0.13"
hmac = ">=0.12.1, <0.13"
num-bigint = ">=0.4, <0.5"
num-traits = ">=0.2.15, <0.3"
pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false }
poly1305 = { version = "0.8.0", features = ["zeroize"] }
Copy link
Member

Choose a reason for hiding this comment

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

This crate doesn't seem to be used at the moment, I assume it's used in your other PR with the key-commiting implementation? If so we can probably remove it for now.

Copy link
Contributor Author

@quexten quexten Mar 7, 2025

Choose a reason for hiding this comment

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

Weird, for chacha the CI pipeline complained about an unused dependency, for poly1305 it did not.

use crate::CryptoError;

#[allow(unused)]
pub(crate) fn generate_nonce() -> GenericArray<u8, U24> {
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 function only meant to be used for tests? If so, it should be moved inside mod tests {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, initially I thought we needed the IV to be included in the authenticated data, but I checked the spec of the format we want to follow for the new keys (COSE https://datatracker.ietf.org/doc/html/rfc8152), and the IV/nonce does not need to be authenticated, so it could just be generated during encrypt. We do however need to serialize it onto the encstring. So What would the recommendation here be, return a tuple / struct of Nonce + Ciphertext, and generate the nonce in the encrypt function?

Copy link
Member

@dani-garcia dani-garcia Mar 7, 2025

Choose a reason for hiding this comment

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

That's more or less what we do for aes now yeah, so it seems reasonable to do the same for chacha

pub(crate) fn encrypt_aes256_hmac(
    data_dec: &[u8],
    mac_key: &GenericArray<u8, U32>,
    key: &GenericArray<u8, U32>,

) -> Result<([u8; 16], [u8; 32], Vec<u8>)> {
    /// IV is generated and returned with the encrypted data and the MAC as a tuple
    Ok((iv, mac, data))
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated!

use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, XChaCha20Poly1305};
use generic_array::{typenum::U24, GenericArray};

/**
Copy link
Member

Choose a reason for hiding this comment

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

Small nit, but I would move this comment to the top of the file and use //! like on the other one. As it is right now, we're technically adding a doc comment to use crate::CryptoError

@@ -91,6 +91,7 @@ pub use wordlist::EFF_LONG_WORD_LIST;
mod store;
pub use store::{KeyStore, KeyStoreContext};
mod traits;
mod xchacha20;
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 more of an idea for the future, but it would be nice to think about moving the aes, rsa and the new xchacha20 modules into their own separate low level algorithms module so they're not all exposed in the root.


#[allow(unused)]
pub(crate) fn encrypt_xchacha20_poly1305(
nonce: &[u8; 24],
Copy link
Member

Choose a reason for hiding this comment

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

On the aes module we generate the IV internally to ensure we always generate a random one each time for encryption, assuming that the nonce works the same in chacha20, have you considered not exposing this as a parameter for encryption and just generating it internally?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned for the nonce generating function, initially I thought the IV needed to be authenticated, but this is not the case, so I'll move this to generate during encrypt.

associated_data: &[u8],
) -> Result<Vec<u8>, CryptoError> {
// This buffer contains the plaintext, that will be encrypted in-place
let mut buffer = Vec::from(plaintext_secret_data);
Copy link
Member

Choose a reason for hiding this comment

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

Minor nit: In decrypt_xchacha20_poly1305 we're using slice.to_vec(), while here we're using Vec::from(slice). We should try to be consistent and use one or the other.

associated_data,
&mut buffer,
)
.map_err(|_| CryptoError::InvalidKey)?;
Copy link
Member

Choose a reason for hiding this comment

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

I think for aes and rsa we use the CryptoError::KeyDecrypt error when decrypting. If so we should do the same here as well

associated_data,
&mut buffer,
)
.map_err(|_| CryptoError::InvalidKey)?;
Copy link
Member

Choose a reason for hiding this comment

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

What's the error case for chacha20 encryption? Key and nonce sizes are checked at compile time, and as far as I know, chacha20 doesn't have any specific key format requirements.

If the only error case is the buffer not having enough space, I think we should be fine calling .expect("") and not returning a Result.

@quexten quexten requested a review from dani-garcia March 24, 2025 14:49
Copy link
Member

@MGibson1 MGibson1 left a comment

Choose a reason for hiding this comment

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

My only note is that I kind of want to see the interface one level up, as well.

I say this because we could design certain aspects of the AAD set into this level. I'm not set definitively in either direction, but there's an elegance to tying the cipherText to a key and cipher type at this level, rather than in the Encryptable

@quexten
Copy link
Contributor Author

quexten commented Mar 25, 2025

@MGibson1 Let's revisit this on the cose PR. I do at this point believe that COSE is better suited though, if we want a structured format to add our own additional data.

@quexten quexten requested a review from a team as a code owner March 25, 2025 10:07
@quexten quexten requested a review from justindbaur March 25, 2025 10:07
@quexten quexten merged commit ae3c08d into main Mar 25, 2025
36 checks passed
@quexten quexten deleted the km/pm-15096/xchacha20-poly1305 branch March 25, 2025 10:10
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