Skip to content

Commit

Permalink
feat: add support for ChaCha20/Poly1305 to COSE module
Browse files Browse the repository at this point in the history
  • Loading branch information
pulsastrix committed Aug 27, 2024
1 parent 66acdf6 commit 91d0419
Show file tree
Hide file tree
Showing 16 changed files with 418 additions and 72 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ exclude = [
default = ["std", "openssl"]
std = ["serde/std", "ciborium/std", "serde_bytes/std", "erased-serde/std", "derive_builder/std"]
rustcrypto = ["rustcrypto-aes-gcm", "rustcrypto-aes-kw", "rustcrypto-ecdsa", "rustcrypto-hmac"]
rustcrypto-encrypt = ["rustcrypto-aes-gcm", "rustcrypto-aes-ccm"]
rustcrypto-encrypt = ["rustcrypto-aes-gcm", "rustcrypto-aes-ccm", "rustcrypto-chacha20-poly1305"]
rustcrypto-sign = ["rustcrypto-ecdsa"]
rustcrypto-key-distribution = ["rustcrypto-aes-kw"]
rustcrypto-mac = ["rustcrypto-hmac"]
rustcrypto-aes-gcm = ["dep:aes-gcm", "dep:typenum", "dep:aead", "dep:aes"]
rustcrypto-aes-ccm = ["dep:ccm", "dep:typenum", "dep:aead", "dep:aes"]
rustcrypto-chacha20-poly1305 = ["dep:chacha20poly1305", "dep:typenum", "dep:aead"]
rustcrypto-aes-kw = ["dep:aes-kw", "dep:aes", "dep:typenum", "dep:crypto-common"]
rustcrypto-ecdsa = ["dep:ecdsa", "dep:p256", "dep:p384", "dep:digest", "dep:sha2", "dep:elliptic-curve"]
rustcrypto-hmac = ["dep:hmac", "dep:digest", "dep:sha2"]
Expand All @@ -43,6 +44,7 @@ openssl = { version = "^0.10", optional = true }
lazy_static = "1.4.0"
aes-gcm = { version = "0.10.3", optional = true, default-features = false, features = ["alloc", "aes"] }
ccm = { version = "0.5.0", optional = true, default-features = false, features = ["alloc"] }
chacha20poly1305 = { version = "0.10.1", optional = true, default-features = false, features = ["alloc"] }
typenum = { version = "1.17.0", optional = true, default-features = false, features = ["const-generics"] }
crypto-common = { version = "0.1.6", optional = true, default-features = false }
aead = { version = "0.5.2", optional = true, default-features = false }
Expand Down
79 changes: 73 additions & 6 deletions src/token/cose/crypto_impl/openssl/encrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

use crate::error::CoseCipherError;
use crate::token::cose::crypto_impl::openssl::OpensslContext;
use crate::token::cose::util::{aes_ccm_algorithm_tag_len, AES_GCM_TAG_LEN};
use crate::token::cose::util::symmetric_algorithm_tag_len;
use crate::token::cose::{crypto_impl, CoseSymmetricKey, EncryptCryptoBackend};
use alloc::vec::Vec;
use coset::iana;
Expand All @@ -27,6 +27,7 @@ impl EncryptCryptoBackend for OpensslContext {
iv: &[u8],
) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
let cipher = crypto_impl::openssl::algorithm_to_cipher(algorithm)?;
let tag_len = symmetric_algorithm_tag_len(algorithm)?;
let mut ctx = CipherCtx::new()?;
// So, apparently OpenSSL requires a very specific order of operations which differs
// slightly for AES-GCM and AES-CCM in order to work.
Expand All @@ -51,7 +52,7 @@ impl EncryptCryptoBackend for OpensslContext {
// 6. Then, we can finish the operation.
ctx.cipher_final_vec(&mut ciphertext)?;
let ciphertext_len = ciphertext.len();
ciphertext.resize(ciphertext_len + AES_GCM_TAG_LEN, 0u8);
ciphertext.resize(ciphertext_len + tag_len, 0u8);
ctx.tag(&mut ciphertext[ciphertext_len..])?;
Ok(ciphertext)
}
Expand All @@ -65,8 +66,9 @@ impl EncryptCryptoBackend for OpensslContext {
iv: &[u8],
) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
let cipher = crypto_impl::openssl::algorithm_to_cipher(algorithm)?;
let auth_tag = &ciphertext_with_tag[(ciphertext_with_tag.len() - AES_GCM_TAG_LEN)..];
let ciphertext = &ciphertext_with_tag[..(ciphertext_with_tag.len() - AES_GCM_TAG_LEN)];
let tag_len = symmetric_algorithm_tag_len(algorithm)?;
let auth_tag = &ciphertext_with_tag[(ciphertext_with_tag.len() - tag_len)..];
let ciphertext = &ciphertext_with_tag[..(ciphertext_with_tag.len() - tag_len)];

let mut ctx = CipherCtx::new()?;
// Refer to https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Decryption_using_GCM_mode
Expand Down Expand Up @@ -103,7 +105,7 @@ impl EncryptCryptoBackend for OpensslContext {
iv: &[u8],
) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
let cipher = crypto_impl::openssl::algorithm_to_cipher(algorithm)?;
let tag_len = aes_ccm_algorithm_tag_len(algorithm)?;
let tag_len = symmetric_algorithm_tag_len(algorithm)?;
let mut ctx = CipherCtx::new()?;
// Refer to https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Encryption_using_CCM_mode
// for reference.
Expand Down Expand Up @@ -140,7 +142,7 @@ impl EncryptCryptoBackend for OpensslContext {
iv: &[u8],
) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
let cipher = crypto_impl::openssl::algorithm_to_cipher(algorithm)?;
let tag_len = aes_ccm_algorithm_tag_len(algorithm)?;
let tag_len = symmetric_algorithm_tag_len(algorithm)?;
let auth_tag = &ciphertext_with_tag[(ciphertext_with_tag.len() - tag_len)..];
let ciphertext = &ciphertext_with_tag[..(ciphertext_with_tag.len() - tag_len)];

Expand Down Expand Up @@ -169,4 +171,69 @@ impl EncryptCryptoBackend for OpensslContext {

Ok(plaintext)
}

fn encrypt_chacha20_poly1305(
&mut self,
key: CoseSymmetricKey<'_, Self::Error>,
plaintext: &[u8],
aad: &[u8],
iv: &[u8],
) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
let cipher = openssl::cipher::Cipher::chacha20_poly1305();
let tag_len = symmetric_algorithm_tag_len(iana::Algorithm::ChaCha20Poly1305)?;
let mut ctx = CipherCtx::new()?;
// Refer to https://docs.openssl.org/1.1.1/man3/EVP_EncryptInit/#chacha20-poly1305 for
// reference.
// 1. First, we set the cipher.
ctx.encrypt_init(Some(cipher), None, None)?;
// 2. We *must* set the IV length _before_ setting the IV.
ctx.set_iv_length(iv.len())?;
// 3. Now we can set key and IV.
ctx.encrypt_init(None, Some(key.k), Some(iv))?;
let mut ciphertext = vec![];
// 4. Then, we set the AAD before setting the plaintext.
ctx.cipher_update(aad, None)?;
// 5. Finally, we provide the plaintext.
ctx.cipher_update_vec(plaintext, &mut ciphertext)?;
// 6. Then, we can finish the operation.
ctx.cipher_final_vec(&mut ciphertext)?;
let ciphertext_len = ciphertext.len();
ciphertext.resize(ciphertext_len + tag_len, 0u8);
ctx.tag(&mut ciphertext[ciphertext_len..])?;
Ok(ciphertext)
}

fn decrypt_chacha20_poly1305(
&mut self,
key: CoseSymmetricKey<'_, Self::Error>,
ciphertext_with_tag: &[u8],
aad: &[u8],
iv: &[u8],
) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
let cipher = openssl::cipher::Cipher::chacha20_poly1305();
let tag_len = symmetric_algorithm_tag_len(iana::Algorithm::ChaCha20Poly1305)?;
let auth_tag = &ciphertext_with_tag[(ciphertext_with_tag.len() - tag_len)..];
let ciphertext = &ciphertext_with_tag[..(ciphertext_with_tag.len() - tag_len)];

let mut ctx = CipherCtx::new()?;
// Refer to https://docs.openssl.org/1.1.1/man3/EVP_EncryptInit/#chacha20-poly1305 for
// reference.
// 1. First, we set the cipher.
ctx.decrypt_init(Some(cipher), None, None)?;
// 2. We *must* set the tag and IV length _before_ setting key and IV.
ctx.set_iv_length(iv.len())?;
ctx.set_tag(auth_tag)?;
// 3. Now we can set key and IV.
ctx.decrypt_init(None, Some(key.k), Some(iv))?;
// 4. Then, we set the AAD before setting the ciphertext.
ctx.cipher_update(aad, None)?;
// 5. Finally, we provide the ciphertext for decryption.
let mut plaintext = vec![0; ciphertext.len()];
let plaintext_len = ctx.cipher_update(ciphertext, Some(&mut plaintext))?;
plaintext.truncate(plaintext_len);
// No call to cipher_final() here, I guess?
// The official examples in the OpenSSL wiki don't finalize, so we won't either.

Ok(plaintext)
}
}
1 change: 1 addition & 0 deletions src/token/cose/crypto_impl/rustcrypto/encrypt/aead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::error::CoseCipherError;
use crate::token::cose::{CoseSymmetricKey, CryptoBackend};

use super::RustCryptoContext;
use alloc::vec::Vec;

impl<RNG: RngCore + CryptoRng> RustCryptoContext<RNG> {
/// Perform an AEAD encryption operation on `plaintext` and the additional authenticated
Expand Down
27 changes: 27 additions & 0 deletions src/token/cose/crypto_impl/rustcrypto/encrypt/chacha_poly.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::error::CoseCipherError;
use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext;
use crate::token::cose::{CoseSymmetricKey, CryptoBackend};
use alloc::vec::Vec;
use chacha20poly1305::ChaCha20Poly1305;
use rand::CryptoRng;
use rand::RngCore;

impl<RNG: RngCore + CryptoRng> RustCryptoContext<RNG> {
pub(super) fn encrypt_chacha20_poly1305(
key: &CoseSymmetricKey<'_, <Self as CryptoBackend>::Error>,
plaintext: &[u8],
aad: &[u8],
iv: &[u8],
) -> Result<Vec<u8>, CoseCipherError<<Self as CryptoBackend>::Error>> {
Self::encrypt_aead::<ChaCha20Poly1305>(key, plaintext, aad, iv)
}

pub(super) fn decrypt_chacha20_poly1305(
key: &CoseSymmetricKey<'_, <Self as CryptoBackend>::Error>,
ciphertext_with_tag: &[u8],
aad: &[u8],
iv: &[u8],
) -> Result<Vec<u8>, CoseCipherError<<Self as CryptoBackend>::Error>> {
Self::decrypt_aead::<ChaCha20Poly1305>(key, ciphertext_with_tag, aad, iv)
}
}
32 changes: 31 additions & 1 deletion src/token/cose/crypto_impl/rustcrypto/encrypt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,22 @@ use rand::{CryptoRng, RngCore};
use crate::error::CoseCipherError;
use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext;
use crate::token::cose::{CoseSymmetricKey, EncryptCryptoBackend};
use alloc::vec::Vec;

#[cfg(feature = "rustcrypto-aes-gcm")]
mod aes_gcm;

#[cfg(feature = "rustcrypto-aes-ccm")]
mod aes_ccm;

#[cfg(any(feature = "rustcrypto-aes-gcm", feature = "rustcrypto-aes-ccm"))]
#[cfg(feature = "rustcrypto-chacha20-poly1305")]
mod chacha_poly;

#[cfg(any(
feature = "rustcrypto-aes-gcm",
feature = "rustcrypto-aes-ccm",
feature = "rustcrypto-chacha20-poly1305"
))]
mod aead;

impl<RNG: RngCore + CryptoRng> EncryptCryptoBackend for RustCryptoContext<RNG> {
Expand Down Expand Up @@ -72,4 +80,26 @@ impl<RNG: RngCore + CryptoRng> EncryptCryptoBackend for RustCryptoContext<RNG> {
) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
Self::decrypt_aes_ccm(algorithm, &key, ciphertext_with_tag, aad, iv)
}

#[cfg(feature = "rustcrypto-chacha20-poly1305")]
fn encrypt_chacha20_poly1305(
&mut self,
key: CoseSymmetricKey<'_, Self::Error>,
plaintext: &[u8],
aad: &[u8],
iv: &[u8],
) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
Self::encrypt_chacha20_poly1305(&key, plaintext, aad, iv)
}

#[cfg(feature = "rustcrypto-chacha20-poly1305")]
fn decrypt_chacha20_poly1305(
&mut self,
key: CoseSymmetricKey<'_, Self::Error>,
ciphertext_with_tag: &[u8],
aad: &[u8],
iv: &[u8],
) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
Self::decrypt_chacha20_poly1305(&key, ciphertext_with_tag, aad, iv)
}
}
1 change: 1 addition & 0 deletions src/token/cose/crypto_impl/rustcrypto/mac/hmac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use sha2::{Sha256, Sha384, Sha512};
use crate::error::CoseCipherError;
use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext;
use crate::token::cose::{CoseSymmetricKey, CryptoBackend};
use alloc::vec::Vec;

impl<RNG: RngCore + CryptoRng> RustCryptoContext<RNG> {
/// Compute the HMAC of `payload` using the given `key` with the HMAC function
Expand Down
1 change: 1 addition & 0 deletions src/token/cose/crypto_impl/rustcrypto/mac/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::token::cose::crypto_impl::rustcrypto::CoseCipherError;
use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext;
use crate::token::cose::CoseSymmetricKey;
use crate::token::cose::MacCryptoBackend;
use alloc::vec::Vec;
use coset::iana;
use rand::{CryptoRng, RngCore};

Expand Down
58 changes: 57 additions & 1 deletion src/token/cose/encrypted/encrypt/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ use crate::token::cose::{util::determine_header_param, CryptoBackend};
#[cfg(feature = "openssl")]
use crate::token::cose::test_helper::openssl_ctx;
#[cfg(all(
any(feature = "rustcrypto-aes-gcm", feature = "rustcrypto-aes-ccm"),
any(
feature = "rustcrypto-aes-gcm",
feature = "rustcrypto-aes-ccm",
feature = "rustcrypto-chacha20-poly1305"
),
feature = "rustcrypto-aes-kw"
))]
use crate::token::cose::test_helper::rustcrypto_ctx;
Expand Down Expand Up @@ -317,6 +321,42 @@ fn cose_examples_aes_ccm_self_signed<B: EncryptCryptoBackend + KeyDistributionCr
perform_cose_self_signed_test::<CoseEncrypt, B>(test_path, backend);
}

#[rstest]
#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))]
#[cfg_attr(
all(
feature = "rustcrypto-aes-kw",
feature = "rustcrypto-chacha20-poly1305"
),
case::rustcrypto(rustcrypto_ctx())
)]
fn cose_examples_chacha20_poly1305_reference_output<
B: EncryptCryptoBackend + KeyDistributionCryptoBackend,
>(
#[files("tests/cose_examples/chacha-poly-examples/chacha-poly-0[0-9].json")] test_path: PathBuf,
#[case] backend: B,
) {
perform_cose_reference_output_test::<CoseEncrypt, B>(test_path, backend);
}

#[rstest]
#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))]
#[cfg_attr(
all(
feature = "rustcrypto-aes-kw",
feature = "rustcrypto-chacha20-poly1305"
),
case::rustcrypto(rustcrypto_ctx())
)]
fn cose_examples_chacha20_poly1305_self_signed<
B: EncryptCryptoBackend + KeyDistributionCryptoBackend,
>(
#[files("tests/cose_examples/chacha-poly-examples/chacha-poly-0[0-9].json")] test_path: PathBuf,
#[case] backend: B,
) {
perform_cose_self_signed_test::<CoseEncrypt, B>(test_path, backend);
}

#[rstest]
#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))]
#[cfg_attr(
Expand Down Expand Up @@ -355,3 +395,19 @@ fn aes_ccm_tests<B: EncryptCryptoBackend + KeyDistributionCryptoBackend>(
) {
perform_cose_self_signed_test::<CoseEncrypt, B>(test_path, backend);
}

#[rstest]
#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))]
#[cfg_attr(
all(
feature = "rustcrypto-aes-kw",
feature = "rustcrypto-chacha20-poly1305"
),
case::rustcrypto(rustcrypto_ctx())
)]
fn chacha20_poly1305_tests<B: EncryptCryptoBackend + KeyDistributionCryptoBackend>(
#[files("tests/dcaf_cose_examples/chacha-poly/*.json")] test_path: PathBuf,
#[case] backend: B,
) {
perform_cose_self_signed_test::<CoseEncrypt, B>(test_path, backend);
}
32 changes: 31 additions & 1 deletion src/token/cose/encrypted/encrypt0/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ use crate::token::cose::CryptoBackend;

#[cfg(feature = "openssl")]
use crate::token::cose::test_helper::openssl_ctx;
#[cfg(any(feature = "rustcrypto-aes-gcm", feature = "rustcrypto-aes-ccm"))]
#[cfg(any(
feature = "rustcrypto-aes-gcm",
feature = "rustcrypto-aes-ccm",
feature = "rustcrypto-chacha20-poly1305"
))]
use crate::token::cose::test_helper::rustcrypto_ctx;

impl<B: CryptoBackend + EncryptCryptoBackend> CoseStructTestHelper<B> for CoseEncrypt0 {
Expand Down Expand Up @@ -188,3 +192,29 @@ fn cose_examples_aes_ccm_encrypt0_self_signed<B: EncryptCryptoBackend>(
) {
perform_cose_self_signed_test::<CoseEncrypt0, B>(test_path, backend);
}

#[rstest]
#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))]
#[cfg_attr(
feature = "rustcrypto-chacha20-poly1305",
case::rustcrypto(rustcrypto_ctx())
)]
fn cose_examples_chacha20_poly1305_reference_output<B: EncryptCryptoBackend>(
#[files("tests/cose_examples/chacha-poly-examples/chacha-poly-enc-*.json")] test_path: PathBuf,
#[case] backend: B,
) {
perform_cose_reference_output_test::<CoseEncrypt0, B>(test_path, backend);
}

#[rstest]
#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))]
#[cfg_attr(
feature = "rustcrypto-chacha20-poly1305",
case::rustcrypto(rustcrypto_ctx())
)]
fn cose_examples_chacha20_poly1305_self_signed<B: EncryptCryptoBackend>(
#[files("tests/cose_examples/chacha-poly-examples/chacha-poly-enc-*.json")] test_path: PathBuf,
#[case] backend: B,
) {
perform_cose_self_signed_test::<CoseEncrypt0, B>(test_path, backend);
}
Loading

0 comments on commit 91d0419

Please sign in to comment.