diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 714755a..250e202 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -52,6 +52,23 @@ jobs: with: command: test + test_all_features: + name: Test Suite (all features) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features + test_nostd: name: Test Suite (no_std) runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index e5d5750..edd6084 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,15 @@ exclude = [ [features] 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-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-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"] [dependencies] serde = { version = "1.0", default-features = false, features = ["derive"] } @@ -31,10 +40,28 @@ enumflags2 = { version = "^0.7", default-features = false } rand = { version = "^0.8", default-features = false } 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"] } +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 } +aes-kw = { version = "0.2.1", optional = true, default-features = false, features = ["alloc"] } +aes = { version = "0.8.4", optional = true, default-features = false } +hmac = { version = "0.12.1", optional = true, default-features = false } +digest = { version = "0.10.7", optional = true, default-features = false } +sha2 = { version = "0.10.8", optional = true, default-features = false } +elliptic-curve = { version = "0.13.8", default-features = false, optional = true } +ecdsa = { version = "0.16.9", optional = true, default-features = false, features = ["sha2"] } +p256 = { version = "0.13.2", optional = true, default-features = false, features = ["alloc", "ecdsa", "arithmetic"] } +p384 = { version = "0.13.0", optional = true, default-features = false, features = ["alloc", "ecdsa", "arithmetic"] } +# P-521 must implement DigestPrimitive in order to be usable in ECDSA, which was only recently added and is not released yet. +# p521 = { version = "0.14.0-pre.1", optional = true } [dev-dependencies] hex = { version = "^0.4", features = ["serde"] } base64 = "0.22.1" -rstest = "0.21.0" +rstest = "0.22.0" serde_json = "1.0.118" rand = { version = "^0.8", default-features = false, features = ["std_rng", "std"] } + +[build-dependencies] +cfg_aliases = "0.2.1" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..c2cc891 --- /dev/null +++ b/build.rs @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 The NAMIB Project Developers. + * Licensed under the Apache License, Version 2.0 or the MIT license + * , at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + * + * SPDX-License-Identifier: MIT OR Apache-2.0 + */ +use cfg_aliases::cfg_aliases; + +fn main() { + // Set up some aliases for conditional compilation (in order to avoid repetition). + cfg_aliases! { + rustcrypto_encrypt_base: { + any( + feature = "rustcrypto-aes-gcm" + ) + }, + rustcrypto_sign_base: { + any( + feature = "rustcrypto-ecdsa" + ) + }, + rustcrypto_key_distribution_base: { + any( + feature = "rustcrypto-aes-kw" + ) + }, + rustcrypto_mac_base: { + any( + feature = "rustcrypto-hmac" + ) + }, + rustcrypto_base: { + any( + rustcrypto_encrypt_base, + rustcrypto_sign_base, + rustcrypto_key_distribution_base, + rustcrypto_mac_base + ) + }, + } +} diff --git a/src/common/cbor_map/mod.rs b/src/common/cbor_map/mod.rs index d65dfba..21acff5 100644 --- a/src/common/cbor_map/mod.rs +++ b/src/common/cbor_map/mod.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022, 2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/common/cbor_values/mod.rs b/src/common/cbor_values/mod.rs index f758f27..701ff51 100644 --- a/src/common/cbor_values/mod.rs +++ b/src/common/cbor_values/mod.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022, 2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/common/cbor_values/tests.rs b/src/common/cbor_values/tests.rs index 8a72582..bd6a8f0 100644 --- a/src/common/cbor_values/tests.rs +++ b/src/common/cbor_values/tests.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022, 2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/common/mod.rs b/src/common/mod.rs index 3817f59..4e80823 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022, 2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/common/scope/mod.rs b/src/common/scope/mod.rs index b51948d..02b9736 100644 --- a/src/common/scope/mod.rs +++ b/src/common/scope/mod.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022, 2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/common/scope/tests.rs b/src/common/scope/tests.rs index 879ae3d..0058c6c 100644 --- a/src/common/scope/tests.rs +++ b/src/common/scope/tests.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022, 2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/common/test_helper.rs b/src/common/test_helper.rs index 0137a06..07a2c17 100644 --- a/src/common/test_helper.rs +++ b/src/common/test_helper.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022-2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/endpoints/creation_hint/mod.rs b/src/endpoints/creation_hint/mod.rs index 13a2a69..1b9dbbb 100644 --- a/src/endpoints/creation_hint/mod.rs +++ b/src/endpoints/creation_hint/mod.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022, 2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/endpoints/creation_hint/tests.rs b/src/endpoints/creation_hint/tests.rs index 2c5c9a5..dcab8f9 100644 --- a/src/endpoints/creation_hint/tests.rs +++ b/src/endpoints/creation_hint/tests.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022, 2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/endpoints/token_req/mod.rs b/src/endpoints/token_req/mod.rs index 42f9806..bf60781 100644 --- a/src/endpoints/token_req/mod.rs +++ b/src/endpoints/token_req/mod.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022, 2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/endpoints/token_req/tests.rs b/src/endpoints/token_req/tests.rs index 0a49a28..e5ebbdb 100644 --- a/src/endpoints/token_req/tests.rs +++ b/src/endpoints/token_req/tests.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022, 2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/error/mod.rs b/src/error/mod.rs index 5191e89..8405c8b 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022, 2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/lib.rs b/src/lib.rs index c268460..1520fc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The NAMIB Project Developers. + * Copyright (c) 2022-2024 The NAMIB Project Developers. * Licensed under the Apache License, Version 2.0 or the MIT license * , at your diff --git a/src/token/cose/crypto_impl/mod.rs b/src/token/cose/crypto_impl/mod.rs index 45ad159..a1c196b 100644 --- a/src/token/cose/crypto_impl/mod.rs +++ b/src/token/cose/crypto_impl/mod.rs @@ -18,3 +18,7 @@ /// Cryptographic backend based on the OpenSSL library (accessed using the `openssl` crate). #[cfg(feature = "openssl")] pub mod openssl; + +/// Cryptographic backend based on the RustCrypto collection of crates. +#[cfg(rustcrypto_base)] +pub mod rustcrypto; diff --git a/src/token/cose/crypto_impl/openssl.rs b/src/token/cose/crypto_impl/openssl.rs index 705e96b..5b7ad4b 100644 --- a/src/token/cose/crypto_impl/openssl.rs +++ b/src/token/cose/crypto_impl/openssl.rs @@ -76,6 +76,7 @@ impl From for CoseCipherError { /// /// Generic properties of this backend: /// - [ ] Can derive EC public key components if only the private component (d) is present. +/// - [ ] Can work with compressed EC public keys (EC keys using point compression) /// /// Algorithm support: /// - Signature Algorithms (for COSE_Sign and COSE_Sign1) diff --git a/src/token/cose/crypto_impl/rustcrypto/encrypt/aes_gcm.rs b/src/token/cose/crypto_impl/rustcrypto/encrypt/aes_gcm.rs new file mode 100644 index 0000000..7a01124 --- /dev/null +++ b/src/token/cose/crypto_impl/rustcrypto/encrypt/aes_gcm.rs @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024 The NAMIB Project Developers. + * Licensed under the Apache License, Version 2.0 or the MIT license + * , at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + * + * SPDX-License-Identifier: MIT OR Apache-2.0 + */ +use aead::{Aead, AeadCore, Key, KeyInit, Nonce, Payload}; +use aes::Aes192; +use aes_gcm::{Aes128Gcm, Aes256Gcm, AesGcm}; +use coset::{iana, Algorithm}; +use rand::CryptoRng; +use rand::RngCore; +use typenum::consts::U12; + +use crate::error::CoseCipherError; +use crate::token::cose::{CoseSymmetricKey, CryptoBackend}; + +use super::RustCryptoContext; + +impl RustCryptoContext { + /// Perform an AEAD encryption operation on `plaintext` and the additional authenticated + /// data `aad` using the given `iv` and `key`. + fn encrypt_aead( + key: &CoseSymmetricKey<'_, as CryptoBackend>::Error>, + plaintext: &[u8], + aad: &[u8], + iv: &[u8], + ) -> Result, CoseCipherError<::Error>> { + let aes_key = Key::::from_slice(key.k); + let cipher = AEAD::new(aes_key); + let nonce = Nonce::::from_slice(iv); + let payload = Payload { + msg: plaintext, + aad, + }; + cipher + .encrypt(nonce, payload) + .map_err(CoseCipherError::from) + } + + /// Perform an AEAD decryption operation on `ciphertext` and the additional authenticated + /// data `aad` using the given `iv` and `key`. + fn decrypt_aead( + key: &CoseSymmetricKey<'_, as CryptoBackend>::Error>, + ciphertext: &[u8], + aad: &[u8], + iv: &[u8], + ) -> Result, CoseCipherError<::Error>> { + let aes_key = Key::::from_slice(key.k); + let cipher = AEAD::new(aes_key); + let nonce = Nonce::::from_slice(iv); + let payload = Payload { + msg: ciphertext, + aad, + }; + cipher + .decrypt(nonce, payload) + .map_err(CoseCipherError::from) + } + + /// Perform an AES-GCM encryption operation on `plaintext` and the additional authenticated + /// data `aad` using the given `iv` and `key` with the given `algorithm` variant of AES-GCM. + pub(super) fn encrypt_aes_gcm( + algorithm: iana::Algorithm, + key: &CoseSymmetricKey<'_, ::Error>, + plaintext: &[u8], + aad: &[u8], + iv: &[u8], + ) -> Result, CoseCipherError<::Error>> { + match algorithm { + iana::Algorithm::A128GCM => Self::encrypt_aead::(key, plaintext, aad, iv), + iana::Algorithm::A192GCM => { + Self::encrypt_aead::>(key, plaintext, aad, iv) + } + iana::Algorithm::A256GCM => Self::encrypt_aead::(key, plaintext, aad, iv), + a => Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + a, + ))), + } + } + + /// Perform an AES-GCM decryption operation on `ciphertext` and the additional authenticated + /// data `aad` using the given `iv` and `key` with the given `algorithm` variant of AES-GCM. + pub(super) fn decrypt_aes_gcm( + algorithm: iana::Algorithm, + key: &CoseSymmetricKey<'_, ::Error>, + ciphertext_with_tag: &[u8], + aad: &[u8], + iv: &[u8], + ) -> Result, CoseCipherError<::Error>> { + match algorithm { + iana::Algorithm::A128GCM => { + Self::decrypt_aead::(key, ciphertext_with_tag, aad, iv) + } + iana::Algorithm::A192GCM => { + Self::decrypt_aead::>(key, ciphertext_with_tag, aad, iv) + } + iana::Algorithm::A256GCM => { + Self::decrypt_aead::(key, ciphertext_with_tag, aad, iv) + } + a => Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + a, + ))), + } + } +} diff --git a/src/token/cose/crypto_impl/rustcrypto/encrypt/mod.rs b/src/token/cose/crypto_impl/rustcrypto/encrypt/mod.rs new file mode 100644 index 0000000..daa2910 --- /dev/null +++ b/src/token/cose/crypto_impl/rustcrypto/encrypt/mod.rs @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 The NAMIB Project Developers. + * Licensed under the Apache License, Version 2.0 or the MIT license + * , at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + * + * SPDX-License-Identifier: MIT OR Apache-2.0 + */ +use coset::iana; +use rand::{CryptoRng, RngCore}; + +use crate::error::CoseCipherError; +use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext; +use crate::token::cose::{CoseSymmetricKey, EncryptCryptoBackend}; + +#[cfg(feature = "rustcrypto-aes-gcm")] +mod aes_gcm; + +impl EncryptCryptoBackend for RustCryptoContext { + #[cfg(feature = "rustcrypto-aes-gcm")] + fn encrypt_aes_gcm( + &mut self, + algorithm: iana::Algorithm, + key: CoseSymmetricKey<'_, Self::Error>, + plaintext: &[u8], + aad: &[u8], + iv: &[u8], + ) -> Result, CoseCipherError> { + Self::encrypt_aes_gcm(algorithm, &key, plaintext, aad, iv) + } + + #[cfg(feature = "rustcrypto-aes-gcm")] + fn decrypt_aes_gcm( + &mut self, + algorithm: iana::Algorithm, + key: CoseSymmetricKey<'_, Self::Error>, + ciphertext_with_tag: &[u8], + aad: &[u8], + iv: &[u8], + ) -> Result, CoseCipherError> { + Self::decrypt_aes_gcm(algorithm, &key, ciphertext_with_tag, aad, iv) + } +} diff --git a/src/token/cose/crypto_impl/rustcrypto/key_distribution/aes_key_wrap.rs b/src/token/cose/crypto_impl/rustcrypto/key_distribution/aes_key_wrap.rs new file mode 100644 index 0000000..c76866b --- /dev/null +++ b/src/token/cose/crypto_impl/rustcrypto/key_distribution/aes_key_wrap.rs @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2024 The NAMIB Project Developers. + * Licensed under the Apache License, Version 2.0 or the MIT license + * , at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + * + * SPDX-License-Identifier: MIT OR Apache-2.0 + */ +use aes::cipher::{BlockCipher, BlockDecrypt, BlockEncrypt, BlockSizeUser}; +use aes::{Aes128, Aes192, Aes256}; +use aes_kw::Kek; +use coset::{iana, Algorithm}; +use crypto_common::{Key, KeyInit}; +use rand::{CryptoRng, RngCore}; +use typenum::consts::U16; + +use crate::error::CoseCipherError; +use crate::token::cose::{CoseSymmetricKey, CryptoBackend}; + +use super::RustCryptoContext; + +impl RustCryptoContext { + /// Perform an AES key wrap operation on the key contained in `plaintext` which is wrapped + /// using the key encryption key `key` using the AES variant provided as `AES`. + fn aes_key_wrap_with_alg< + AES: KeyInit + BlockCipher + BlockSizeUser + BlockEncrypt + BlockDecrypt, + >( + key: &CoseSymmetricKey<'_, ::Error>, + plaintext: &[u8], + ) -> Result, CoseCipherError<::Error>> { + let key = Key::::from_slice(key.k); + let key_wrap = Kek::::new(key); + key_wrap.wrap_vec(plaintext).map_err(CoseCipherError::from) + } + + /// Perform an AES key unwrap operation on the key contained in `ciphertext` which is wrapped + /// using the key encryption key `key` using the AES variant provided as `AES`. + fn aes_key_unwrap_with_alg< + AES: KeyInit + BlockCipher + BlockSizeUser + BlockEncrypt + BlockDecrypt, + >( + key: &CoseSymmetricKey<'_, ::Error>, + ciphertext: &[u8], + ) -> Result, CoseCipherError<::Error>> { + let key = Key::::from_slice(key.k); + let key_wrap = Kek::::new(key); + key_wrap + .unwrap_vec(ciphertext) + .map_err(CoseCipherError::from) + } + + /// Perform an AES key wrap operation on the key contained in `plaintext` which is wrapped + /// using the key encryption key `key` using the AES variant specified for the given + /// `algorithm`. + pub(super) fn aes_key_wrap( + algorithm: iana::Algorithm, + key: &CoseSymmetricKey<'_, ::Error>, + plaintext: &[u8], + iv: &[u8], + ) -> Result, CoseCipherError<::Error>> { + if iv != aes_kw::IV { + // IV for AES key wrap is not set by user, but by dcaf-rs. + // This indicates some weird/unknown variation of an AES-KW algorithm, or something went + // really wrong. + return Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + algorithm, + ))); + } + match algorithm { + iana::Algorithm::A128KW => Self::aes_key_wrap_with_alg::(key, plaintext), + iana::Algorithm::A192KW => Self::aes_key_wrap_with_alg::(key, plaintext), + iana::Algorithm::A256KW => Self::aes_key_wrap_with_alg::(key, plaintext), + a => Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + a, + ))), + } + } + + /// Perform an AES key unwrap operation on the key contained in `ciphertext` which is wrapped + /// using the key encryption key `key` using the AES variant specified for the given + /// `algorithm`. + pub(super) fn aes_key_unwrap( + algorithm: iana::Algorithm, + key: &CoseSymmetricKey<'_, ::Error>, + ciphertext: &[u8], + iv: &[u8], + ) -> Result, CoseCipherError<::Error>> { + if iv != aes_kw::IV { + // IV for AES key wrap is not set by user, but by dcaf-rs. + // This indicates some weird/unknown variation of an AES-KW algorithm, or something went + // really wrong. + return Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + algorithm, + ))); + } + match algorithm { + iana::Algorithm::A128KW => Self::aes_key_unwrap_with_alg::(key, ciphertext), + iana::Algorithm::A192KW => Self::aes_key_unwrap_with_alg::(key, ciphertext), + iana::Algorithm::A256KW => Self::aes_key_unwrap_with_alg::(key, ciphertext), + a => Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + a, + ))), + } + } +} diff --git a/src/token/cose/crypto_impl/rustcrypto/key_distribution/mod.rs b/src/token/cose/crypto_impl/rustcrypto/key_distribution/mod.rs new file mode 100644 index 0000000..589f8b3 --- /dev/null +++ b/src/token/cose/crypto_impl/rustcrypto/key_distribution/mod.rs @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 The NAMIB Project Developers. + * Licensed under the Apache License, Version 2.0 or the MIT license + * , at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + * + * SPDX-License-Identifier: MIT OR Apache-2.0 + */ +use coset::iana; +use rand::{CryptoRng, RngCore}; + +use crate::error::CoseCipherError; +use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext; +use crate::token::cose::{CoseSymmetricKey, KeyDistributionCryptoBackend}; + +#[cfg(feature = "rustcrypto-aes-kw")] +mod aes_key_wrap; + +impl KeyDistributionCryptoBackend for RustCryptoContext { + #[cfg(feature = "rustcrypto-aes-kw")] + fn aes_key_wrap( + &mut self, + algorithm: iana::Algorithm, + key: CoseSymmetricKey<'_, Self::Error>, + plaintext: &[u8], + iv: &[u8], + ) -> Result, CoseCipherError> { + Self::aes_key_wrap(algorithm, &key, plaintext, iv) + } + + #[cfg(feature = "rustcrypto-aes-kw")] + fn aes_key_unwrap( + &mut self, + algorithm: iana::Algorithm, + key: CoseSymmetricKey<'_, Self::Error>, + ciphertext: &[u8], + iv: &[u8], + ) -> Result, CoseCipherError> { + Self::aes_key_unwrap(algorithm, &key, ciphertext, iv) + } +} diff --git a/src/token/cose/crypto_impl/rustcrypto/mac/hmac.rs b/src/token/cose/crypto_impl/rustcrypto/mac/hmac.rs new file mode 100644 index 0000000..3c58f99 --- /dev/null +++ b/src/token/cose/crypto_impl/rustcrypto/mac/hmac.rs @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 The NAMIB Project Developers. + * Licensed under the Apache License, Version 2.0 or the MIT license + * , at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + * + * SPDX-License-Identifier: MIT OR Apache-2.0 + */ +use coset::{iana, Algorithm}; +use digest::{FixedOutput, Mac, MacMarker, Update}; +use hmac::Hmac; +use rand::{CryptoRng, RngCore}; +use sha2::{Sha256, Sha384, Sha512}; + +use crate::error::CoseCipherError; +use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext; +use crate::token::cose::{CoseSymmetricKey, CryptoBackend}; + +impl RustCryptoContext { + /// Compute the HMAC of `payload` using the given `key` with the HMAC function + /// `MAC`. + fn compute_hmac_using_mac( + key: &CoseSymmetricKey<'_, ::Error>, + payload: &[u8], + ) -> Vec { + let key_size = key.k.len(); + let mut hmac_key = hmac::digest::Key::::default(); + hmac_key.as_mut_slice()[..key_size].copy_from_slice(key.k); + let mut hmac = MAC::new(&hmac_key); + hmac.update(payload); + hmac.finalize().into_bytes().to_vec() + } + + /// Verify the HMAC of `payload` using the given `key` with the HMAC function `MAC`. + fn verify_hmac_using_mac( + key: &CoseSymmetricKey<'_, ::Error>, + payload: &[u8], + tag: &[u8], + ) -> Result<(), CoseCipherError<::Error>> { + let key_size = key.k.len(); + let mut hmac_key = hmac::digest::Key::::default(); + hmac_key.as_mut_slice()[..key_size].copy_from_slice(key.k); + let mut hmac = MAC::new(&hmac_key); + hmac.update(payload); + hmac.verify_slice(tag).map_err(CoseCipherError::from) + } + + /// Compute the HMAC of `payload` using the given `key` with the HMAC function + /// specified in the `algorithm`. + pub(super) fn compute_hmac( + algorithm: iana::Algorithm, + key: &CoseSymmetricKey<'_, ::Error>, + payload: &[u8], + ) -> Result, CoseCipherError<::Error>> { + match algorithm { + iana::Algorithm::HMAC_256_256 => Ok( + Self::compute_hmac_using_mac::>(key, payload), + ), + iana::Algorithm::HMAC_384_384 => Ok( + Self::compute_hmac_using_mac::>(key, payload), + ), + iana::Algorithm::HMAC_512_512 => Ok( + Self::compute_hmac_using_mac::>(key, payload), + ), + a => Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + a, + ))), + } + } + + /// Verify the HMAC `tag` of `payload` using the given `key` with the HMAC + /// function specified in the `algorithm`. + pub(super) fn verify_hmac( + algorithm: iana::Algorithm, + key: &CoseSymmetricKey<'_, ::Error>, + tag: &[u8], + payload: &[u8], + ) -> Result<(), CoseCipherError<::Error>> { + match algorithm { + iana::Algorithm::HMAC_256_256 => { + Self::verify_hmac_using_mac::>(key, payload, tag) + } + iana::Algorithm::HMAC_384_384 => { + Self::verify_hmac_using_mac::>(key, payload, tag) + } + iana::Algorithm::HMAC_512_512 => { + Self::verify_hmac_using_mac::>(key, payload, tag) + } + a => Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + a, + ))), + } + } +} diff --git a/src/token/cose/crypto_impl/rustcrypto/mac/mod.rs b/src/token/cose/crypto_impl/rustcrypto/mac/mod.rs new file mode 100644 index 0000000..d0e3805 --- /dev/null +++ b/src/token/cose/crypto_impl/rustcrypto/mac/mod.rs @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 The NAMIB Project Developers. + * Licensed under the Apache License, Version 2.0 or the MIT license + * , at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + * + * SPDX-License-Identifier: MIT OR Apache-2.0 + */ +use coset::iana::Algorithm; + +use crate::error::CoseCipherError; +use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext; +use crate::token::cose::{CoseSymmetricKey, MacCryptoBackend}; +use rand::{CryptoRng, RngCore}; + +#[cfg(feature = "rustcrypto-hmac")] +mod hmac; + +impl MacCryptoBackend for RustCryptoContext { + #[cfg(feature = "rustcrypto-hmac")] + fn compute_hmac( + &mut self, + algorithm: Algorithm, + key: CoseSymmetricKey<'_, Self::Error>, + payload: &[u8], + ) -> Result, CoseCipherError> { + Self::compute_hmac(algorithm, &key, payload) + } + + #[cfg(feature = "rustcrypto-hmac")] + fn verify_hmac( + &mut self, + algorithm: Algorithm, + key: CoseSymmetricKey<'_, Self::Error>, + tag: &[u8], + payload: &[u8], + ) -> Result<(), CoseCipherError> { + Self::verify_hmac(algorithm, &key, tag, payload) + } +} diff --git a/src/token/cose/crypto_impl/rustcrypto/mod.rs b/src/token/cose/crypto_impl/rustcrypto/mod.rs new file mode 100644 index 0000000..7f7ffd4 --- /dev/null +++ b/src/token/cose/crypto_impl/rustcrypto/mod.rs @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2024 The NAMIB Project Developers. + * Licensed under the Apache License, Version 2.0 or the MIT license + * , at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + * + * SPDX-License-Identifier: MIT OR Apache-2.0 + */ +use rand::{CryptoRng, RngCore}; +use strum_macros::Display; + +use crate::error::CoseCipherError; +use crate::token::cose::CryptoBackend; + +#[cfg(rustcrypto_encrypt_base)] +mod encrypt; +#[cfg(rustcrypto_key_distribution_base)] +mod key_distribution; +#[cfg(rustcrypto_mac_base)] +mod mac; +#[cfg(rustcrypto_sign_base)] +mod sign; + +#[derive(Debug, Display)] +/// Errors that might be returned from the `RustCrypto` cryptographic backend. +pub enum CoseRustCryptoCipherError { + /// Error in AES key wrap. + #[cfg(feature = "rustcrypto-aes-kw")] + AesKwError(aes_kw::Error), + /// Provided parameter has invalid length. + #[cfg(any(feature = "rustcrypto-hmac", feature = "rustcrypto-sign"))] + InvalidLength(digest::InvalidLength), + /// Error regarding elliptic curve operations. + #[cfg(feature = "rustcrypto-ecdsa")] + EcError(elliptic_curve::Error), + /// Error in ECDSA operation. + #[cfg(feature = "rustcrypto-ecdsa")] + EcdsaError(ecdsa::Error), + /// Invalid elliptic curve point. + #[cfg(feature = "rustcrypto-ecdsa")] + InvalidPoint, +} + +#[cfg(feature = "rustcrypto-aes-kw")] +impl From for CoseRustCryptoCipherError { + fn from(value: aes_kw::Error) -> Self { + CoseRustCryptoCipherError::AesKwError(value) + } +} + +#[cfg(any(feature = "rustcrypto-hmac", feature = "rustcrypto-sign"))] +impl From for CoseRustCryptoCipherError { + fn from(value: digest::InvalidLength) -> Self { + CoseRustCryptoCipherError::InvalidLength(value) + } +} + +#[cfg(feature = "rustcrypto-ecdsa")] +impl From for CoseRustCryptoCipherError { + fn from(value: ecdsa::elliptic_curve::Error) -> Self { + CoseRustCryptoCipherError::EcError(value) + } +} + +#[cfg(feature = "rustcrypto-aes-kw")] +impl From for CoseCipherError { + fn from(value: aes_kw::Error) -> Self { + CoseCipherError::Other(CoseRustCryptoCipherError::from(value)) + } +} + +#[cfg(any(feature = "rustcrypto-hmac", feature = "rustcrypto-sign"))] +impl From for CoseCipherError { + fn from(value: digest::InvalidLength) -> Self { + CoseCipherError::Other(CoseRustCryptoCipherError::from(value)) + } +} + +#[cfg(feature = "rustcrypto-ecdsa")] +impl From for CoseCipherError { + fn from(value: ecdsa::elliptic_curve::Error) -> Self { + CoseCipherError::Other(CoseRustCryptoCipherError::EcError(value)) + } +} + +#[cfg(feature = "rustcrypto-hmac")] +impl From for CoseCipherError { + fn from(_value: digest::MacError) -> Self { + CoseCipherError::VerificationFailure + } +} + +#[cfg(feature = "rustcrypto-aes-gcm")] +impl From for CoseCipherError { + fn from(_value: aead::Error) -> Self { + CoseCipherError::VerificationFailure + } +} + +#[cfg(feature = "rustcrypto-ecdsa")] +impl From for CoseCipherError { + fn from(value: ecdsa::Error) -> Self { + CoseCipherError::Other(CoseRustCryptoCipherError::EcdsaError(value)) + } +} + +/// Context for the RustCrypto cryptographic backend +/// +/// Can be used as a [`CryptoBackend`] for COSE operations. +/// +/// Generic properties of this backend: +/// - [x] Can derive EC public key components if only the private component (d) is present. +/// - [x] Can work with compressed EC public keys (EC keys using point compression) +/// +/// Algorithm support: +/// - Signature Algorithms (for COSE_Sign and COSE_Sign1) +/// - [x] ECDSA +/// - [x] ES256 +/// - [x] ES384 +/// - [x] ES512 +/// - [ ] ES256K +/// - [ ] EdDSA +/// - Message Authentication Code Algorithms (for COSE_Mac and COSE_Mac0) +/// - [x] HMAC +/// - [ ] HMAC 256/64 +/// - [x] HMAC 256/256 +/// - [x] HMAC 384/384 +/// - [x] HMAC 512/512 +/// - [ ] AES-CBC-MAC +/// - [ ] AES-MAC 128/64 +/// - [ ] AES-MAC 256/64 +/// - [ ] AES-MAC 128/128 +/// - [ ] AES-MAC 256/128 +/// - Content Encryption Algorithms (for COSE_Encrypt and COSE_Encrypt0) +/// - [x] AES-GCM +/// - [x] A128GCM +/// - [x] A192GCM +/// - [x] A256GCM +/// - [ ] AES-CCM +/// - [ ] AES-CCM-16-64-128 +/// - [ ] AES-CCM-16-64-256 +/// - [ ] AES-CCM-64-64-128 +/// - [ ] AES-CCM-64-64-256 +/// - [ ] AES-CCM-16-128-128 +/// - [ ] AES-CCM-16-128-256 +/// - [ ] AES-CCM-64-128-128 +/// - [ ] AES-CCM-64-128-256 +/// - [ ] ChaCha20/Poly1305 +/// - Content Key Distribution Methods (for COSE_Recipients) +/// - Direct Encryption +/// - [ ] Direct Key with KDF +/// - [ ] direct+HKDF-SHA-256 +/// - [ ] direct+HKDF-SHA-512 +/// - [ ] direct+HKDF-AES-128 +/// - [ ] direct+HKDF-AES-256 +/// - Key Wrap +/// - [x] AES Key Wrap +/// - [x] A128KW +/// - [x] A192KW +/// - [x] A256KW +/// - Direct Key Agreement +/// - [ ] Direct ECDH +/// - [ ] ECDH-ES + HKDF-256 +/// - [ ] ECDH-ES + HKDF-512 +/// - [ ] ECDH-SS + HKDF-256 +/// - [ ] ECDH-SS + HKDF-512 +/// - Key Agreement with Key Wrap +/// - [ ] ECDH with Key Wrap +/// - [ ] ECDH-ES + A128KW +/// - [ ] ECDH-ES + A192KW +/// - [ ] ECDH-ES + A256KW +/// - [ ] ECDH-SS + A128KW +/// - [ ] ECDH-SS + A192KW +/// - [ ] ECDH-SS + A256KW +/// +/// Elliptic Curve support (for EC algorithms): +/// - ES256/ES384/ES512 [^1] +/// - [x] P-256 +/// - [x] P-384 +/// - [ ] P-521 [^2] +/// - ES256K +/// - [ ] secp256k1 +/// - EdDSA +/// - [ ] Ed448 +/// - [ ] Ed25519 +/// - ECDH +/// - [ ] X448 +/// - [ ] X25519 +/// +/// [^1]: RFC 9053, Section 2.1 suggests using ES256 only with curve P-256, ES384 with curve P-384 +/// and ES512 only with curve P-521. +/// [^2]: P-521 must implement DigestPrimitive in order to be usable in ECDSA. +/// This implementation was only recently added and is not released yet (p521 version 0.14.0 +/// is only a pre-release right now). +pub struct RustCryptoContext { + rng: RNG, +} + +impl RustCryptoContext { + /// Creates a new RustCrypto context for cryptographic COSE operations using the given random + /// number generator `rng`. + pub fn new(rng: RNG) -> RustCryptoContext { + RustCryptoContext { rng } + } +} + +impl CryptoBackend for RustCryptoContext { + type Error = CoseRustCryptoCipherError; + + fn generate_rand(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { + self.rng.fill_bytes(buf); + Ok(()) + } +} diff --git a/src/token/cose/crypto_impl/rustcrypto/sign/ecdsa.rs b/src/token/cose/crypto_impl/rustcrypto/sign/ecdsa.rs new file mode 100644 index 0000000..03a7218 --- /dev/null +++ b/src/token/cose/crypto_impl/rustcrypto/sign/ecdsa.rs @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2024 The NAMIB Project Developers. + * Licensed under the Apache License, Version 2.0 or the MIT license + * , at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + * + * SPDX-License-Identifier: MIT OR Apache-2.0 + */ +use core::ops::Add; + +use coset::{iana, Algorithm}; +use digest::const_oid::ObjectIdentifier; +use digest::Digest; +use ecdsa::elliptic_curve::generic_array::ArrayLength; +use ecdsa::hazmat::{DigestPrimitive, SignPrimitive, VerifyPrimitive}; +use ecdsa::signature::{DigestSigner, Verifier}; +use ecdsa::{ + PrimeCurve, RecoveryId, Signature, SignatureWithOid, SigningKey, VerifyingKey, + ECDSA_SHA256_OID, ECDSA_SHA384_OID, ECDSA_SHA512_OID, +}; +use elliptic_curve::{ + sec1::{EncodedPoint, FromEncodedPoint, ModulusSize, ToEncodedPoint}, + CurveArithmetic, PublicKey, SecretKey, +}; +use p256::NistP256; +use p384::NistP384; +use rand::{CryptoRng, RngCore}; +use sha2::{Sha256, Sha384, Sha512}; + +use crate::error::CoseCipherError; +use crate::token::cose::crypto_impl::rustcrypto::CoseRustCryptoCipherError; +use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext; +use crate::token::cose::{CoseEc2Key, CryptoBackend, EllipticCurve}; + +impl RustCryptoContext { + /// Perform an ECDSA signature operation with the ECDSA variant given in `algorithm` for the + /// given `payload` using the provided `key`. + pub(super) fn sign_ecdsa( + algorithm: iana::Algorithm, + key: &CoseEc2Key<'_, ::Error>, + payload: &[u8], + ) -> Result, CoseCipherError<::Error>> { + match algorithm { + iana::Algorithm::ES256 => Self::sign_ecdsa_with_digest::(key, payload), + iana::Algorithm::ES384 => Self::sign_ecdsa_with_digest::(key, payload), + iana::Algorithm::ES512 => Self::sign_ecdsa_with_digest::(key, payload), + a => Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + a, + ))), + } + } + + /// Perform an ECDSA verification operation with the ECDSA variant given in `algorithm` for the + /// given `payload` and `sig`nature using the provided `key`. + pub(super) fn verify_ecdsa( + algorithm: iana::Algorithm, + key: &CoseEc2Key<'_, ::Error>, + sig: &[u8], + payload: &[u8], + ) -> Result<(), CoseCipherError<::Error>> { + let oid = match algorithm { + iana::Algorithm::ES256 => ECDSA_SHA256_OID, + iana::Algorithm::ES384 => ECDSA_SHA384_OID, + iana::Algorithm::ES512 => ECDSA_SHA512_OID, + a => { + return Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + a, + ))) + } + }; + + match &key.crv { + EllipticCurve::Assigned(iana::EllipticCurve::P_256) => { + Self::verify_ecdsa_with_curve::(key, oid, sig, payload) + } + EllipticCurve::Assigned(iana::EllipticCurve::P_384) => { + Self::verify_ecdsa_with_curve::(key, oid, sig, payload) + } + // P-521 must implement DigestPrimitive in order to be usable in ECDSA, which was only + // recently added and is not released yet (will come with p521 0.14.0). + /*EllipticCurve::Assigned(iana::EllipticCurve::P_521) => { + Self::ecdsa_sign_with_curve::(key, payload) + }*/ + v => Err(CoseCipherError::UnsupportedCurve(v.clone())), + } + } + + /// Perform an ECDSA signature operation with the ECDSA hash function `D` for the + /// given `payload` using the provided `key`. + fn sign_ecdsa_with_digest( + key: &CoseEc2Key<'_, ::Error>, + payload: &[u8], + ) -> Result, CoseCipherError<::Error>> { + match &key.crv { + EllipticCurve::Assigned(iana::EllipticCurve::P_256) => { + Self::sign_ecdsa_with_digest_and_curve::(key, payload) + } + EllipticCurve::Assigned(iana::EllipticCurve::P_384) => { + Self::sign_ecdsa_with_digest_and_curve::(key, payload) + } + // P-521 must implement DigestPrimitive in order to be usable in ECDSA, which was only + // recently added and is not released yet (will come with p521 0.14.0). + /*EllipticCurve::Assigned(iana::EllipticCurve::P_521) => { + Self::ecdsa_sign_with_curve::(key, payload) + }*/ + v => Err(CoseCipherError::UnsupportedCurve(v.clone())), + } + } + + /// Perform an ECDSA signature operation with the ECDSA hash function `D` and curve `CRV` for + /// the given `payload` using the provided `key`. + fn sign_ecdsa_with_digest_and_curve< + D: Digest, + CRV: PrimeCurve + CurveArithmetic + DigestPrimitive, + >( + key: &CoseEc2Key<'_, ::Error>, + payload: &[u8], + ) -> Result, CoseCipherError<::Error>> + where + ::Scalar: SignPrimitive, + <::FieldBytesSize as Add>::Output: ArrayLength, + { + let digest = Digest::new_with_prefix(payload); + let sign_key = Self::cose_ec2_to_ec_private_key::(key)?; + let (signature, _recid) = as DigestSigner< + D, + (Signature, RecoveryId), + >>::sign_digest(&sign_key, digest); + Ok(signature.to_vec()) + } + + /// Perform an ECDSA verification operation with the ECDSA hash function given in `oid` for the + /// given `payload` and `sig`nature using the provided `key`. + fn verify_ecdsa_with_curve( + key: &CoseEc2Key<'_, ::Error>, + oid: ObjectIdentifier, + sig: &[u8], + payload: &[u8], + ) -> Result<(), CoseCipherError<::Error>> + where + ::AffinePoint: VerifyPrimitive, + <::FieldBytesSize as Add>::Output: ArrayLength, + ::FieldBytesSize: ModulusSize, + ::AffinePoint: FromEncodedPoint, + ::AffinePoint: ToEncodedPoint, + { + let sign_key = Self::cose_ec2_to_ec_public_key::(key)?; + let signature = SignatureWithOid::new(Signature::::from_slice(sig)?, oid)?; + as Verifier>>::verify( + &sign_key, payload, &signature, + ) + .map_err(CoseCipherError::from) + } + + /// Convert a public or private COSE EC2 key to its public key RustCrypto representation. + fn cose_ec2_to_ec_public_key( + key: &CoseEc2Key<'_, ::Error>, + ) -> Result, CoseCipherError<::Error>> + where + ::FieldBytesSize: ModulusSize, + ::AffinePoint: FromEncodedPoint, + ::AffinePoint: ToEncodedPoint, + { + if key.x.is_none() || (key.y.is_none() && key.sign.is_none()) { + // According to the contract provided by the calling COSE library, D must be set, so we + // can attempt to reconstruct the public key from the private key. + SecretKey::from_slice(key.d.expect( + "invalid EC2 key was provided, at least one of the key parameters must be set", + )) + .map(|sc| VerifyingKey::from(sc.public_key())) + .map_err(CoseCipherError::from) + } else { + // x must be Some here due to the previous condition. + let pubkey_coord = if let Some(y) = key.y { + EncodedPoint::::from_affine_coordinates(key.x.unwrap().into(), y.into(), false) + } else { + EncodedPoint::::from_affine_coordinates( + key.x.unwrap().into(), + u8::from(key.sign.unwrap()).to_be_bytes().as_slice().into(), + true, + ) + }; + let pubkey = PublicKey::from_encoded_point(&pubkey_coord); + if pubkey.is_some().into() { + Ok(VerifyingKey::from(pubkey.unwrap())) + } else { + Err(CoseCipherError::Other( + CoseRustCryptoCipherError::InvalidPoint, + )) + } + } + } + + /// Convert a private COSE EC2 key to its RustCrypto representation. + fn cose_ec2_to_ec_private_key( + key: &CoseEc2Key<'_, ::Error>, + ) -> Result, CoseCipherError<::Error>> + where + ::Scalar: SignPrimitive, + <::FieldBytesSize as Add>::Output: ArrayLength, + { + SecretKey::::from_slice( + key.d + .expect("invalid EC2 private key was provided, key parameter d must be set"), + ) + .map(SigningKey::::from) + .map_err(CoseCipherError::from) + } +} diff --git a/src/token/cose/crypto_impl/rustcrypto/sign/mod.rs b/src/token/cose/crypto_impl/rustcrypto/sign/mod.rs new file mode 100644 index 0000000..46b5aec --- /dev/null +++ b/src/token/cose/crypto_impl/rustcrypto/sign/mod.rs @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 The NAMIB Project Developers. + * Licensed under the Apache License, Version 2.0 or the MIT license + * , at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + * + * SPDX-License-Identifier: MIT OR Apache-2.0 + */ +use coset::iana; +use rand::{CryptoRng, RngCore}; + +use crate::error::CoseCipherError; +use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext; +use crate::token::cose::CoseEc2Key; +use crate::token::SignCryptoBackend; + +#[cfg(feature = "rustcrypto-ecdsa")] +mod ecdsa; + +impl SignCryptoBackend for RustCryptoContext { + #[cfg(feature = "rustcrypto-ecdsa")] + fn sign_ecdsa( + &mut self, + algorithm: iana::Algorithm, + key: &CoseEc2Key<'_, Self::Error>, + payload: &[u8], + ) -> Result, CoseCipherError> { + Self::sign_ecdsa(algorithm, key, payload) + } + + #[cfg(feature = "rustcrypto-ecdsa")] + fn verify_ecdsa( + &mut self, + algorithm: iana::Algorithm, + key: &CoseEc2Key<'_, Self::Error>, + sig: &[u8], + payload: &[u8], + ) -> Result<(), CoseCipherError> { + Self::verify_ecdsa(algorithm, key, sig, payload) + } +} diff --git a/src/token/cose/encrypted/encrypt/tests.rs b/src/token/cose/encrypted/encrypt/tests.rs index d24ba7e..f298980 100644 --- a/src/token/cose/encrypted/encrypt/tests.rs +++ b/src/token/cose/encrypted/encrypt/tests.rs @@ -17,7 +17,6 @@ use coset::{ }; use rstest::rstest; -use crate::token::cose::crypto_impl::openssl::OpensslContext; use crate::token::cose::encrypted::encrypt::{CoseEncryptBuilderExt, CoseEncryptExt}; use crate::token::cose::encrypted::EncryptCryptoBackend; use crate::token::cose::header_util::{determine_algorithm, HeaderBuilderExt}; @@ -29,6 +28,11 @@ use crate::token::cose::test_helper::{ }; use crate::token::cose::CryptoBackend; +#[cfg(feature = "openssl")] +use crate::token::cose::test_helper::openssl_ctx; +#[cfg(all(feature = "rustcrypto-aes-gcm", feature = "rustcrypto-aes-kw"))] +use crate::token::cose::test_helper::rustcrypto_ctx; + impl CoseStructTestHelper for CoseEncrypt { @@ -169,53 +173,83 @@ impl Cos } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr( + all(feature = "rustcrypto-aes-kw", feature = "rustcrypto-aes-gcm"), + case::rustcrypto(rustcrypto_ctx()) +)] fn cose_examples_enveloped_reference_output< B: EncryptCryptoBackend + KeyDistributionCryptoBackend, >( #[files("tests/cose_examples/enveloped-tests/env-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_reference_output_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr( + all(feature = "rustcrypto-aes-kw", feature = "rustcrypto-aes-gcm"), + case::rustcrypto(rustcrypto_ctx()) +)] fn cose_examples_enveloped_self_signed( #[files("tests/cose_examples/enveloped-tests/env-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_self_signed_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr( + all(feature = "rustcrypto-aes-kw", feature = "rustcrypto-aes-gcm"), + case::rustcrypto(rustcrypto_ctx()) +)] fn cose_examples_aes_wrap_reference_output< B: EncryptCryptoBackend + KeyDistributionCryptoBackend, >( #[files("tests/cose_examples/aes-wrap-examples/aes-wrap-*-0[45].json")] test_path: PathBuf, // The other tests use (as of now) unsupported algorithms - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_reference_output_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr( + all(feature = "rustcrypto-aes-kw", feature = "rustcrypto-aes-gcm"), + case::rustcrypto(rustcrypto_ctx()) +)] fn cose_examples_aes_wrap_self_signed( #[files("tests/cose_examples/aes-wrap-examples/aes-wrap-*-0[45].json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_self_signed_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr( + all(feature = "rustcrypto-aes-kw", feature = "rustcrypto-aes-gcm"), + case::rustcrypto(rustcrypto_ctx()) +)] fn aes_wrap_tests( #[files("tests/dcaf_cose_examples/aes-kw/*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_self_signed_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr( + all(feature = "rustcrypto-aes-kw", feature = "rustcrypto-aes-gcm"), + case::rustcrypto(rustcrypto_ctx()) +)] fn aes_gcm_tests( #[files("tests/dcaf_cose_examples/aes-gcm/*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_self_signed_test::(test_path, backend); } diff --git a/src/token/cose/encrypted/encrypt0/tests.rs b/src/token/cose/encrypted/encrypt0/tests.rs index f085206..27e6fe8 100644 --- a/src/token/cose/encrypted/encrypt0/tests.rs +++ b/src/token/cose/encrypted/encrypt0/tests.rs @@ -13,7 +13,6 @@ use std::path::PathBuf; use coset::{CoseEncrypt0, CoseEncrypt0Builder, CoseError, CoseKey, HeaderBuilder}; use rstest::rstest; -use crate::token::cose::crypto_impl::openssl::OpensslContext; use crate::token::cose::encrypted::encrypt0::{CoseEncrypt0BuilderExt, CoseEncrypt0Ext}; use crate::token::cose::encrypted::EncryptCryptoBackend; use crate::token::cose::header_util::HeaderBuilderExt; @@ -23,6 +22,11 @@ use crate::token::cose::test_helper::{ }; use crate::token::cose::CryptoBackend; +#[cfg(feature = "openssl")] +use crate::token::cose::test_helper::openssl_ctx; +#[cfg(feature = "rustcrypto-aes-gcm")] +use crate::token::cose::test_helper::rustcrypto_ctx; + impl CoseStructTestHelper for CoseEncrypt0 { fn from_test_case(case: &TestCase, backend: &mut B) -> Self { let encrypt0_cfg = case @@ -126,33 +130,41 @@ impl CoseStructTestHelper for CoseEn } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-aes-gcm", case::rustcrypto(rustcrypto_ctx()))] fn cose_examples_encrypted_encrypt0_reference_output( #[files("tests/cose_examples/encrypted-tests/enc-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_reference_output_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-aes-gcm", case::rustcrypto(rustcrypto_ctx()))] fn cose_examples_encrypted_encrypt0_self_signed( #[files("tests/cose_examples/encrypted-tests/enc-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_self_signed_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-aes-gcm", case::rustcrypto(rustcrypto_ctx()))] fn cose_examples_aes_gcm_encrypt0_reference_output( #[files("tests/cose_examples/aes-gcm-examples/aes-gcm-enc-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_reference_output_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-aes-gcm", case::rustcrypto(rustcrypto_ctx()))] fn cose_examples_aes_gcm_encrypt0_self_signed( #[files("tests/cose_examples/aes-gcm-examples/aes-gcm-enc-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_self_signed_test::(test_path, backend); } diff --git a/src/token/cose/encrypted/mod.rs b/src/token/cose/encrypted/mod.rs index 17ae51e..a98174c 100644 --- a/src/token/cose/encrypted/mod.rs +++ b/src/token/cose/encrypted/mod.rs @@ -81,6 +81,7 @@ pub trait EncryptCryptoBackend: CryptoBackend { /// For unknown algorithms or key curves, however, the implementation must not panic and return /// [`CoseCipherError::UnsupportedAlgorithm`] instead (in case new AES-GCM variants are ever /// defined). + #[allow(unused_variables)] fn encrypt_aes_gcm( &mut self, algorithm: iana::Algorithm, @@ -88,7 +89,11 @@ pub trait EncryptCryptoBackend: CryptoBackend { plaintext: &[u8], aad: &[u8], iv: &[u8], - ) -> Result, CoseCipherError>; + ) -> Result, CoseCipherError> { + Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + algorithm, + ))) + } /// Decrypts the given `payload` using the AES-GCM variant provided as `algorithm` and the given /// `key`. @@ -138,6 +143,7 @@ pub trait EncryptCryptoBackend: CryptoBackend { /// For unknown algorithms or key curves, however, the implementation must not panic and return /// [`CoseCipherError::UnsupportedAlgorithm`] instead (in case new AES-GCM variants are ever /// defined). + #[allow(unused_variables)] fn decrypt_aes_gcm( &mut self, algorithm: iana::Algorithm, @@ -145,7 +151,11 @@ pub trait EncryptCryptoBackend: CryptoBackend { ciphertext_with_tag: &[u8], aad: &[u8], iv: &[u8], - ) -> Result, CoseCipherError>; + ) -> Result, CoseCipherError> { + Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + algorithm, + ))) + } } /// Attempts to perform a COSE encryption operation for a [`CoseEncrypt`](coset::CoseEncrypt) or diff --git a/src/token/cose/maced/mac/tests.rs b/src/token/cose/maced/mac/tests.rs index 32102d1..c2fbc09 100644 --- a/src/token/cose/maced/mac/tests.rs +++ b/src/token/cose/maced/mac/tests.rs @@ -18,7 +18,6 @@ use coset::{ }; use rstest::rstest; -use crate::token::cose::crypto_impl::openssl::OpensslContext; use crate::token::cose::header_util::determine_algorithm; use crate::token::cose::key::CoseSymmetricKey; use crate::token::cose::maced::mac::{CoseMacBuilderExt, CoseMacExt}; @@ -31,6 +30,11 @@ use crate::token::cose::test_helper::{ }; use crate::token::cose::{test_helper, CryptoBackend}; +#[cfg(feature = "openssl")] +use crate::token::cose::test_helper::openssl_ctx; +#[cfg(all(feature = "rustcrypto-hmac", feature = "rustcrypto-aes-kw"))] +use crate::token::cose::test_helper::rustcrypto_ctx; + impl CoseStructTestHelper for CoseMac { @@ -150,25 +154,66 @@ impl CoseStr } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr( + all(feature = "rustcrypto-hmac", feature = "rustcrypto-aes-kw"), + case::rustcrypto(rustcrypto_ctx()) +)] fn cose_examples_mac_reference_output( #[files("tests/cose_examples/mac-tests/mac-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { test_helper::perform_cose_reference_output_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr( + all(feature = "rustcrypto-hmac", feature = "rustcrypto-aes-kw"), + case::rustcrypto(rustcrypto_ctx()) +)] fn cose_examples_mac_self_signed( #[files("tests/cose_examples/mac-tests/mac-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, +) { + test_helper::perform_cose_self_signed_test::(test_path, backend); +} + +#[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr( + all(feature = "rustcrypto-hmac", feature = "rustcrypto-aes-kw"), + case::rustcrypto(rustcrypto_ctx()) +)] +fn cose_examples_hmac_mac_reference_output( + #[files("tests/cose_examples/hmac-examples/HMac-0[0-4].json")] test_path: PathBuf, + #[case] backend: B, +) { + test_helper::perform_cose_reference_output_test::(test_path, backend); +} + +#[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr( + all(feature = "rustcrypto-hmac", feature = "rustcrypto-aes-kw"), + case::rustcrypto(rustcrypto_ctx()) +)] +fn cose_examples_hmac_mac_self_signed( + #[files("tests/cose_examples/hmac-examples/HMac-0[0-4].json")] test_path: PathBuf, + #[case] backend: B, ) { test_helper::perform_cose_self_signed_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr( + all(feature = "rustcrypto-hmac", feature = "rustcrypto-aes-kw"), + case::rustcrypto(rustcrypto_ctx()) +)] fn hmac_tests( #[files("tests/dcaf_cose_examples/hmac/*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { test_helper::perform_cose_self_signed_test::(test_path, backend); } diff --git a/src/token/cose/maced/mac0/tests.rs b/src/token/cose/maced/mac0/tests.rs index 58f8257..6a349cb 100644 --- a/src/token/cose/maced/mac0/tests.rs +++ b/src/token/cose/maced/mac0/tests.rs @@ -15,7 +15,6 @@ use coset::iana::Algorithm; use coset::{CoseError, CoseKey, CoseKeyBuilder, CoseMac0, CoseMac0Builder, Header}; use rstest::rstest; -use crate::token::cose::crypto_impl::openssl::OpensslContext; use crate::token::cose::header_util::determine_algorithm; use crate::token::cose::maced::mac0::{CoseMac0BuilderExt, CoseMac0Ext}; use crate::token::cose::maced::MacCryptoBackend; @@ -25,6 +24,11 @@ use crate::token::cose::test_helper::{ }; use crate::token::cose::{test_helper, CryptoBackend}; +#[cfg(feature = "openssl")] +use crate::token::cose::test_helper::openssl_ctx; +#[cfg(feature = "rustcrypto-hmac")] +use crate::token::cose::test_helper::rustcrypto_ctx; + impl CoseStructTestHelper for CoseMac0 { fn from_test_case(case: &TestCase, backend: &mut B) -> Self { let mac0_cfg = case @@ -137,17 +141,41 @@ impl CoseStructTestHelper for CoseMac0 { } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-hmac", case::rustcrypto(rustcrypto_ctx()))] fn cose_examples_mac0_reference_output( #[files("tests/cose_examples/mac0-tests/mac-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { test_helper::perform_cose_reference_output_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-hmac", case::rustcrypto(rustcrypto_ctx()))] fn cose_examples_mac0_self_signed( #[files("tests/cose_examples/mac0-tests/mac-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, +) { + test_helper::perform_cose_self_signed_test::(test_path, backend); +} + +#[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-hmac", case::rustcrypto(rustcrypto_ctx()))] +fn cose_examples_hmac_mac0_reference_output( + #[files("tests/cose_examples/hmac-examples/HMac-enc-0[0-4].json")] test_path: PathBuf, + #[case] backend: B, +) { + test_helper::perform_cose_reference_output_test::(test_path, backend); +} + +#[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-hmac", case::rustcrypto(rustcrypto_ctx()))] +fn cose_examples_hmac_mac0_self_signed( + #[files("tests/cose_examples/hmac-examples/HMac-enc-0[0-4].json")] test_path: PathBuf, + #[case] backend: B, ) { test_helper::perform_cose_self_signed_test::(test_path, backend); } diff --git a/src/token/cose/maced/mod.rs b/src/token/cose/maced/mod.rs index 67361a8..05efd78 100644 --- a/src/token/cose/maced/mod.rs +++ b/src/token/cose/maced/mod.rs @@ -69,12 +69,17 @@ pub trait MacCryptoBackend: CryptoBackend { /// For unknown algorithms or key curves, however, the implementation must not panic and return /// [`CoseCipherError::UnsupportedAlgorithm`] instead (in case new HMAC variants are ever /// defined). + #[allow(unused_variables)] fn compute_hmac( &mut self, algorithm: iana::Algorithm, key: CoseSymmetricKey<'_, Self::Error>, payload: &[u8], - ) -> Result, CoseCipherError>; + ) -> Result, CoseCipherError> { + Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + algorithm, + ))) + } /// Verifies the HMAC provided as `tag` for the given `payload` using the given `algorithm` and /// `key`. @@ -122,13 +127,18 @@ pub trait MacCryptoBackend: CryptoBackend { /// For unknown algorithms or key curves, however, the implementation must not panic and return /// [`CoseCipherError::UnsupportedAlgorithm`] instead (in case new HMAC variants are ever /// defined). + #[allow(unused_variables)] fn verify_hmac( &mut self, algorithm: iana::Algorithm, key: CoseSymmetricKey<'_, Self::Error>, tag: &[u8], payload: &[u8], - ) -> Result<(), CoseCipherError>; + ) -> Result<(), CoseCipherError> { + Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + algorithm, + ))) + } } /// Attempts to perform a COSE HMAC computation operation for a [`CoseMac`](coset::CoseMac) or @@ -156,7 +166,9 @@ fn try_compute( let parsed_key = CoseParsedKey::try_from(key)?; match alg { - iana::Algorithm::HMAC_256_256 => { + iana::Algorithm::HMAC_256_256 + | iana::Algorithm::HMAC_384_384 + | iana::Algorithm::HMAC_512_512 => { let symm_key = key::ensure_valid_hmac_key(alg, parsed_key)?; backend.compute_hmac(alg, symm_key, payload) } @@ -194,7 +206,9 @@ pub(crate) fn try_verify( let parsed_key = CoseParsedKey::try_from(key)?; match alg { - iana::Algorithm::HMAC_256_256 => { + iana::Algorithm::HMAC_256_256 + | iana::Algorithm::HMAC_384_384 + | iana::Algorithm::HMAC_512_512 => { let symm_key = key::ensure_valid_hmac_key(alg, parsed_key)?; (*backend.borrow_mut()).verify_hmac(alg, symm_key, tag, payload) } diff --git a/src/token/cose/recipient/mod.rs b/src/token/cose/recipient/mod.rs index 1fc0163..2e54b18 100644 --- a/src/token/cose/recipient/mod.rs +++ b/src/token/cose/recipient/mod.rs @@ -74,13 +74,18 @@ pub trait KeyDistributionCryptoBackend: CryptoBackend { /// For unknown algorithms or key curves, however, the implementation must not panic and return /// [`CoseCipherError::UnsupportedAlgorithm`] instead (in case new AES-GCM variants are ever /// defined). + #[allow(unused_variables)] fn aes_key_wrap( &mut self, algorithm: iana::Algorithm, key: CoseSymmetricKey<'_, Self::Error>, plaintext: &[u8], iv: &[u8], - ) -> Result, CoseCipherError>; + ) -> Result, CoseCipherError> { + Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + algorithm, + ))) + } /// Decrypts the given `ciphertext` using the AES key unwrap (RFC 3394) variant provided as /// `algorithm` and the given `key`. @@ -124,13 +129,18 @@ pub trait KeyDistributionCryptoBackend: CryptoBackend { /// For unknown algorithms or key curves, however, the implementation must not panic and return /// [`CoseCipherError::UnsupportedAlgorithm`] instead (in case new AES-GCM variants are ever /// defined). + #[allow(unused_variables)] fn aes_key_unwrap( &mut self, algorithm: iana::Algorithm, key: CoseSymmetricKey<'_, Self::Error>, ciphertext: &[u8], iv: &[u8], - ) -> Result, CoseCipherError>; + ) -> Result, CoseCipherError> { + Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + algorithm, + ))) + } } /// Internal structure that implements the key provider trait by creating depth-first search @@ -724,7 +734,7 @@ pub trait CoseRecipientExt { /// /// # Errors /// - /// If the COSE structure, selected [`CoseKey`](coset::CoseKey) or AAD (or any combination of those) are malformed + /// If the COSE structure, selected [`CoseKey`](CoseKey) or AAD (or any combination of those) are malformed /// or otherwise unsuitable for decryption, this function will return the most fitting /// [`CoseCipherError`] for the specific type of error. /// diff --git a/src/token/cose/signed/mod.rs b/src/token/cose/signed/mod.rs index 7449ff1..9379798 100644 --- a/src/token/cose/signed/mod.rs +++ b/src/token/cose/signed/mod.rs @@ -80,12 +80,17 @@ pub trait SignCryptoBackend: CryptoBackend { /// /// For unknown algorithms or key curves, however, the implementation must not panic and return /// [`CoseCipherError::UnsupportedAlgorithm`] instead (in case new ECDSA variants are defined). + #[allow(unused_variables)] fn sign_ecdsa( &mut self, algorithm: iana::Algorithm, key: &CoseEc2Key<'_, Self::Error>, payload: &[u8], - ) -> Result, CoseCipherError>; + ) -> Result, CoseCipherError> { + Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + algorithm, + ))) + } /// Verifies the `signature` using the given `key` and `payload` (plaintext) using ECDSA. /// @@ -148,13 +153,18 @@ pub trait SignCryptoBackend: CryptoBackend { /// /// For unknown algorithms or key curves, however, the implementation must not panic and return /// [`CoseCipherError::UnsupportedAlgorithm`] instead (in case new ECDSA variants are defined). + #[allow(unused_variables)] fn verify_ecdsa( &mut self, algorithm: iana::Algorithm, key: &CoseEc2Key<'_, Self::Error>, sig: &[u8], payload: &[u8], - ) -> Result<(), CoseCipherError>; + ) -> Result<(), CoseCipherError> { + Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned( + algorithm, + ))) + } } /// Attempts to perform a COSE signing operation for a [`CoseSign`](coset::CoseSign) or diff --git a/src/token/cose/signed/sign/tests.rs b/src/token/cose/signed/sign/tests.rs index 712cde2..5d4bf51 100644 --- a/src/token/cose/signed/sign/tests.rs +++ b/src/token/cose/signed/sign/tests.rs @@ -13,7 +13,6 @@ use std::path::PathBuf; use coset::{CoseError, CoseKey, CoseSign, CoseSignBuilder, CoseSignatureBuilder, Header}; use rstest::rstest; -use crate::token::cose::crypto_impl::openssl::OpensslContext; use crate::token::cose::recipient::KeyDistributionCryptoBackend; use crate::token::cose::signed::{CoseSignBuilderExt, CoseSignExt}; use crate::token::cose::test_helper::{ @@ -23,6 +22,11 @@ use crate::token::cose::test_helper::{ use crate::token::cose::CryptoBackend; use crate::token::cose::SignCryptoBackend; +#[cfg(feature = "openssl")] +use crate::token::cose::test_helper::openssl_ctx; +#[cfg(feature = "rustcrypto-ecdsa")] +use crate::token::cose::test_helper::rustcrypto_ctx; + impl CoseStructTestHelper for CoseSign { @@ -145,43 +149,103 @@ impl CoseSt } #[rstest] -fn cose_examples_ecdsa_sign_reference_output< +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] +fn cose_examples_ecdsa_p256_sign_reference_output< + B: SignCryptoBackend + KeyDistributionCryptoBackend, +>( + #[files("tests/cose_examples/ecdsa-examples/ecdsa-0[14].json")] test_path: PathBuf, + #[case] backend: B, +) { + perform_cose_reference_output_test::(test_path, backend); +} + +#[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] +fn cose_examples_ecdsa_p256_sign_self_signed< B: SignCryptoBackend + KeyDistributionCryptoBackend, >( - #[files("tests/cose_examples/ecdsa-examples/ecdsa-0*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[files("tests/cose_examples/ecdsa-examples/ecdsa-0[14].json")] test_path: PathBuf, + #[case] backend: B, +) { + perform_cose_self_signed_test::(test_path, backend); +} + +#[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] +fn cose_examples_ecdsa_p384_sign_reference_output< + B: SignCryptoBackend + KeyDistributionCryptoBackend, +>( + #[files("tests/cose_examples/ecdsa-examples/ecdsa-02.json")] test_path: PathBuf, + #[case] backend: B, ) { perform_cose_reference_output_test::(test_path, backend); } #[rstest] -fn cose_examples_ecdsa_sign_self_signed( - #[files("tests/cose_examples/ecdsa-examples/ecdsa-0*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] +fn cose_examples_ecdsa_p384_sign_self_signed< + B: SignCryptoBackend + KeyDistributionCryptoBackend, +>( + #[files("tests/cose_examples/ecdsa-examples/ecdsa-02.json")] test_path: PathBuf, + #[case] backend: B, +) { + perform_cose_self_signed_test::(test_path, backend); +} + +#[cfg(feature = "openssl")] +#[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +fn cose_examples_ecdsa_p521_sign_reference_output< + B: SignCryptoBackend + KeyDistributionCryptoBackend, +>( + #[files("tests/cose_examples/ecdsa-examples/ecdsa-03.json")] test_path: PathBuf, + #[case] backend: B, +) { + perform_cose_reference_output_test::(test_path, backend); +} + +#[cfg(feature = "openssl")] +#[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +fn cose_examples_ecdsa_p521_sign_self_signed< + B: SignCryptoBackend + KeyDistributionCryptoBackend, +>( + #[files("tests/cose_examples/ecdsa-examples/ecdsa-03.json")] test_path: PathBuf, + #[case] backend: B, ) { perform_cose_self_signed_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] fn cose_examples_sign_reference_output( #[files("tests/cose_examples/sign-tests/sign-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_reference_output_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] fn cose_examples_sign_self_signed( #[files("tests/cose_examples/sign-tests/sign-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_self_signed_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] fn ecdsa_tests( #[files("tests/dcaf_cose_examples/ecdsa/*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_self_signed_test::(test_path, backend); } diff --git a/src/token/cose/signed/sign1/tests.rs b/src/token/cose/signed/sign1/tests.rs index 7c9dfcd..6a081c8 100644 --- a/src/token/cose/signed/sign1/tests.rs +++ b/src/token/cose/signed/sign1/tests.rs @@ -13,7 +13,6 @@ use std::path::PathBuf; use coset::{CoseError, CoseKey, CoseSign1, CoseSign1Builder, Header}; use rstest::rstest; -use crate::token::cose::crypto_impl::openssl::OpensslContext; use crate::token::cose::signed::CoseSign1BuilderExt; use crate::token::cose::signed::CoseSign1Ext; use crate::token::cose::test_helper::{ @@ -23,6 +22,11 @@ use crate::token::cose::test_helper::{ use crate::token::cose::CryptoBackend; use crate::token::cose::SignCryptoBackend; +#[cfg(feature = "openssl")] +use crate::token::cose::test_helper::openssl_ctx; +#[cfg(feature = "rustcrypto-ecdsa")] +use crate::token::cose::test_helper::rustcrypto_ctx; + impl CoseStructTestHelper for CoseSign1 { fn from_test_case(case: &TestCase, backend: &mut B) -> Self { let sign1_cfg = case @@ -83,33 +87,81 @@ impl CoseStructTestHelper for CoseSign1 } #[rstest] -fn cose_examples_ecdsa_sign1_reference_output( - #[files("tests/cose_examples/ecdsa-examples/ecdsa-sig-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] +fn cose_examples_ecdsa_p256_sign1_reference_output( + #[files("tests/cose_examples/ecdsa-examples/ecdsa-sig-0[14].json")] test_path: PathBuf, + #[case] backend: B, +) { + perform_cose_reference_output_test::(test_path, backend); +} + +#[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] +fn cose_examples_ecdsa_p256_sign1_self_signed( + #[files("tests/cose_examples/ecdsa-examples/ecdsa-sig-0[14].json")] test_path: PathBuf, + #[case] backend: B, +) { + perform_cose_self_signed_test::(test_path, backend); +} + +#[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] +fn cose_examples_ecdsa_p384_sign1_reference_output( + #[files("tests/cose_examples/ecdsa-examples/ecdsa-sig-02.json")] test_path: PathBuf, + #[case] backend: B, +) { + perform_cose_reference_output_test::(test_path, backend); +} + +#[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] +fn cose_examples_ecdsa_p384_sign1_self_signed( + #[files("tests/cose_examples/ecdsa-examples/ecdsa-sig-02.json")] test_path: PathBuf, + #[case] backend: B, +) { + perform_cose_self_signed_test::(test_path, backend); +} + +#[cfg(feature = "openssl")] +#[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +fn cose_examples_ecdsa_p521_sign1_reference_output( + #[files("tests/cose_examples/ecdsa-examples/ecdsa-sig-03.json")] test_path: PathBuf, + #[case] backend: B, ) { perform_cose_reference_output_test::(test_path, backend); } +#[cfg(feature = "openssl")] #[rstest] -fn cose_examples_ecdsa_sign1_self_signed( - #[files("tests/cose_examples/ecdsa-examples/ecdsa-sig-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +fn cose_examples_ecdsa_p521_sign1_self_signed( + #[files("tests/cose_examples/ecdsa-examples/ecdsa-sig-03.json")] test_path: PathBuf, + #[case] backend: B, ) { perform_cose_self_signed_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] fn cose_examples_sign1_reference_output( #[files("tests/cose_examples/sign1-tests/sign-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_reference_output_test::(test_path, backend); } #[rstest] +#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))] +#[cfg_attr(feature = "rustcrypto-ecdsa", case::rustcrypto(rustcrypto_ctx()))] fn cose_examples_sign1_self_signed( #[files("tests/cose_examples/sign1-tests/sign-*.json")] test_path: PathBuf, - #[values(OpensslContext {})] backend: B, + #[case] backend: B, ) { perform_cose_self_signed_test::(test_path, backend); } diff --git a/src/token/cose/test_helper.rs b/src/token/cose/test_helper.rs index fb6462c..1d4c8cc 100644 --- a/src/token/cose/test_helper.rs +++ b/src/token/cose/test_helper.rs @@ -12,6 +12,10 @@ use core::fmt::Debug; use std::collections::HashMap; use std::path::PathBuf; +#[cfg(feature = "openssl")] +use crate::token::cose::crypto_impl::openssl::OpensslContext; +#[cfg(rustcrypto_base)] +use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext; use crate::token::cose::CryptoBackend; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::Engine; @@ -20,6 +24,9 @@ use coset::{ iana, Algorithm, AsCborValue, CborSerializable, CoseError, CoseKey, CoseKeyBuilder, CoseRecipientBuilder, Header, HeaderBuilder, Label, TaggedCborSerializable, }; +#[cfg(rustcrypto_base)] +use rand::rngs::ThreadRng; +use rstest::fixture; use serde::{de, Deserialize, Deserializer}; use serde_json::Value; @@ -573,3 +580,15 @@ impl CoseCipher for RngMockCipher { todo!() } }*/ + +#[cfg(feature = "openssl")] +#[fixture] +pub(crate) fn openssl_ctx() -> OpensslContext { + OpensslContext::new() +} + +#[cfg(rustcrypto_base)] +#[fixture] +pub(crate) fn rustcrypto_ctx() -> RustCryptoContext { + RustCryptoContext::new(rand::thread_rng()) +}