From 2cdbf9e620093ad4298af88e2d53d9c4cbd8c2cf Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 17 Sep 2023 12:35:10 -0500 Subject: [PATCH] Convert CMAC to Rust --- .../hazmat/backends/openssl/backend.py | 5 - .../hazmat/backends/openssl/cmac.py | 89 ----------- .../bindings/_rust/openssl/__init__.pyi | 2 + .../hazmat/bindings/_rust/openssl/cmac.pyi | 18 +++ src/cryptography/hazmat/primitives/cmac.py | 61 +------- src/rust/build.rs | 6 + src/rust/cryptography-openssl/src/cmac.rs | 71 +++++++++ src/rust/cryptography-openssl/src/hmac.rs | 4 +- src/rust/cryptography-openssl/src/lib.rs | 1 + src/rust/src/backend/ciphers.rs | 148 ++++++++++++++++++ src/rust/src/backend/cmac.rs | 105 +++++++++++++ src/rust/src/backend/mod.rs | 3 + src/rust/src/types.rs | 50 ++++++ tests/hazmat/backends/test_openssl.py | 7 - tests/hazmat/primitives/test_cmac.py | 16 +- 15 files changed, 423 insertions(+), 163 deletions(-) delete mode 100644 src/cryptography/hazmat/backends/openssl/cmac.py create mode 100644 src/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi create mode 100644 src/rust/cryptography-openssl/src/cmac.rs create mode 100644 src/rust/src/backend/ciphers.rs create mode 100644 src/rust/src/backend/cmac.rs diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 52b536908dec..7852e0d27245 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -13,7 +13,6 @@ from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.backends.openssl import aead from cryptography.hazmat.backends.openssl.ciphers import _CipherContext -from cryptography.hazmat.backends.openssl.cmac import _CMACContext from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.bindings.openssl import binding from cryptography.hazmat.primitives import hashes, serialization @@ -31,7 +30,6 @@ PublicKeyTypes, ) from cryptography.hazmat.primitives.ciphers import ( - BlockCipherAlgorithm, CipherAlgorithm, ) from cryptography.hazmat.primitives.ciphers.algorithms import ( @@ -571,9 +569,6 @@ def cmac_algorithm_supported(self, algorithm) -> bool: algorithm, CBC(b"\x00" * algorithm.block_size) ) - def create_cmac_ctx(self, algorithm: BlockCipherAlgorithm) -> _CMACContext: - return _CMACContext(self, algorithm) - def load_pem_private_key( self, data: bytes, diff --git a/src/cryptography/hazmat/backends/openssl/cmac.py b/src/cryptography/hazmat/backends/openssl/cmac.py deleted file mode 100644 index bdd7fec611d1..000000000000 --- a/src/cryptography/hazmat/backends/openssl/cmac.py +++ /dev/null @@ -1,89 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -import typing - -from cryptography.exceptions import ( - InvalidSignature, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.primitives import constant_time -from cryptography.hazmat.primitives.ciphers.modes import CBC - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - from cryptography.hazmat.primitives import ciphers - - -class _CMACContext: - def __init__( - self, - backend: Backend, - algorithm: ciphers.BlockCipherAlgorithm, - ctx=None, - ) -> None: - if not backend.cmac_algorithm_supported(algorithm): - raise UnsupportedAlgorithm( - "This backend does not support CMAC.", - _Reasons.UNSUPPORTED_CIPHER, - ) - - self._backend = backend - self._key = algorithm.key - self._algorithm = algorithm - self._output_length = algorithm.block_size // 8 - - if ctx is None: - registry = self._backend._cipher_registry - adapter = registry[type(algorithm), CBC] - - evp_cipher = adapter(self._backend, algorithm, CBC) - - ctx = self._backend._lib.CMAC_CTX_new() - - self._backend.openssl_assert(ctx != self._backend._ffi.NULL) - ctx = self._backend._ffi.gc(ctx, self._backend._lib.CMAC_CTX_free) - - key_ptr = self._backend._ffi.from_buffer(self._key) - res = self._backend._lib.CMAC_Init( - ctx, - key_ptr, - len(self._key), - evp_cipher, - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(res == 1) - - self._ctx = ctx - - def update(self, data: bytes) -> None: - res = self._backend._lib.CMAC_Update(self._ctx, data, len(data)) - self._backend.openssl_assert(res == 1) - - def finalize(self) -> bytes: - buf = self._backend._ffi.new("unsigned char[]", self._output_length) - length = self._backend._ffi.new("size_t *", self._output_length) - res = self._backend._lib.CMAC_Final(self._ctx, buf, length) - self._backend.openssl_assert(res == 1) - - self._ctx = None - - return self._backend._ffi.buffer(buf)[:] - - def copy(self) -> _CMACContext: - copied_ctx = self._backend._lib.CMAC_CTX_new() - copied_ctx = self._backend._ffi.gc( - copied_ctx, self._backend._lib.CMAC_CTX_free - ) - res = self._backend._lib.CMAC_CTX_copy(copied_ctx, self._ctx) - self._backend.openssl_assert(res == 1) - return _CMACContext(self._backend, self._algorithm, ctx=copied_ctx) - - def verify(self, signature: bytes) -> None: - digest = self.finalize() - if not constant_time.bytes_eq(digest, signature): - raise InvalidSignature("Signature did not match digest.") diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi index 21c860265867..e95bc15457ae 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi @@ -6,6 +6,7 @@ import typing from cryptography.hazmat.bindings._rust.openssl import ( aead, + cmac, dh, dsa, ec, @@ -24,6 +25,7 @@ __all__ = [ "openssl_version", "raise_openssl_error", "aead", + "cmac", "dh", "dsa", "ec", diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi new file mode 100644 index 000000000000..9c03508bc89b --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi @@ -0,0 +1,18 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import ciphers + +class CMAC: + def __init__( + self, + algorithm: ciphers.BlockCipherAlgorithm, + backend: typing.Any = None, + ) -> None: ... + def update(self, data: bytes) -> None: ... + def finalize(self) -> bytes: ... + def verify(self, signature: bytes) -> None: ... + def copy(self) -> CMAC: ... diff --git a/src/cryptography/hazmat/primitives/cmac.py b/src/cryptography/hazmat/primitives/cmac.py index 1a8a622c6953..2c67ce2206e4 100644 --- a/src/cryptography/hazmat/primitives/cmac.py +++ b/src/cryptography/hazmat/primitives/cmac.py @@ -4,62 +4,7 @@ from __future__ import annotations -import typing +from cryptography.hazmat.bindings._rust import openssl as rust_openssl -from cryptography import utils -from cryptography.exceptions import AlreadyFinalized -from cryptography.hazmat.primitives import ciphers - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.cmac import _CMACContext - - -class CMAC: - _ctx: _CMACContext | None - _algorithm: ciphers.BlockCipherAlgorithm - - def __init__( - self, - algorithm: ciphers.BlockCipherAlgorithm, - backend: typing.Any = None, - ctx: _CMACContext | None = None, - ) -> None: - if not isinstance(algorithm, ciphers.BlockCipherAlgorithm): - raise TypeError("Expected instance of BlockCipherAlgorithm.") - self._algorithm = algorithm - - if ctx is None: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - self._ctx = ossl.create_cmac_ctx(self._algorithm) - else: - self._ctx = ctx - - def update(self, data: bytes) -> None: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - - utils._check_bytes("data", data) - self._ctx.update(data) - - def finalize(self) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - digest = self._ctx.finalize() - self._ctx = None - return digest - - def verify(self, signature: bytes) -> None: - utils._check_bytes("signature", signature) - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - - ctx, self._ctx = self._ctx, None - ctx.verify(signature) - - def copy(self) -> CMAC: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - return CMAC(self._algorithm, ctx=self._ctx.copy()) +__all__ = ["CMAC"] +CMAC = rust_openssl.cmac.CMAC diff --git a/src/rust/build.rs b/src/rust/build.rs index a0b4566a753c..87c074b42e23 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -21,4 +21,10 @@ fn main() { if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); } + + if let Ok(vars) = env::var("DEP_OPENSSL_CONF") { + for var in vars.split(',') { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_OSSLCONF=\"{}\"", var); + } + } } diff --git a/src/rust/cryptography-openssl/src/cmac.rs b/src/rust/cryptography-openssl/src/cmac.rs new file mode 100644 index 000000000000..5215b88358d4 --- /dev/null +++ b/src/rust/cryptography-openssl/src/cmac.rs @@ -0,0 +1,71 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use crate::hmac::DigestBytes; +use crate::{cvt, cvt_p, OpenSSLResult}; +use foreign_types_shared::{ForeignType, ForeignTypeRef}; +use std::ptr; + +foreign_types::foreign_type! { + type CType = ffi::CMAC_CTX; + fn drop = ffi::CMAC_CTX_free; + + pub struct Cmac; + pub struct CmacRef; +} + +// SAFETY: It's safe to have `&` references from multiple threads. +unsafe impl Sync for Cmac {} +// SAFETY: It's safe to move the `Cmac` from one thread to another. +unsafe impl Send for Cmac {} + +impl Cmac { + pub fn new(key: &[u8], cipher: &openssl::symm::Cipher) -> OpenSSLResult { + // SAFETY: All FFI conditions are handled. + unsafe { + let ctx = Cmac::from_ptr(cvt_p(ffi::CMAC_CTX_new())?); + cvt(ffi::CMAC_Init( + ctx.as_ptr(), + key.as_ptr().cast(), + key.len(), + cipher.as_ptr(), + ptr::null_mut(), + ))?; + Ok(ctx) + } + } +} + +impl CmacRef { + pub fn update(&mut self, data: &[u8]) -> OpenSSLResult<()> { + // SAFETY: All FFI conditions are handled. + unsafe { + cvt(ffi::CMAC_Update( + self.as_ptr(), + data.as_ptr().cast(), + data.len(), + ))?; + } + Ok(()) + } + + pub fn finish(&mut self) -> OpenSSLResult { + let mut buf = [0; ffi::EVP_MAX_MD_SIZE as usize]; + let mut len = ffi::EVP_MAX_MD_SIZE as usize; + // SAFETY: All FFI conditions are handled. + unsafe { + cvt(ffi::CMAC_Final(self.as_ptr(), buf.as_mut_ptr(), &mut len))?; + } + Ok(DigestBytes { buf, len }) + } + + pub fn copy(&self) -> OpenSSLResult { + // SAFETY: All FFI conditions are handled. + unsafe { + let h = Cmac::from_ptr(cvt_p(ffi::CMAC_CTX_new())?); + cvt(ffi::CMAC_CTX_copy(h.as_ptr(), self.as_ptr()))?; + Ok(h) + } + } +} diff --git a/src/rust/cryptography-openssl/src/hmac.rs b/src/rust/cryptography-openssl/src/hmac.rs index d2c14431853b..282efa79bd60 100644 --- a/src/rust/cryptography-openssl/src/hmac.rs +++ b/src/rust/cryptography-openssl/src/hmac.rs @@ -71,8 +71,8 @@ impl HmacRef { } pub struct DigestBytes { - buf: [u8; ffi::EVP_MAX_MD_SIZE as usize], - len: usize, + pub(crate) buf: [u8; ffi::EVP_MAX_MD_SIZE as usize], + pub(crate) len: usize, } impl std::ops::Deref for DigestBytes { diff --git a/src/rust/cryptography-openssl/src/lib.rs b/src/rust/cryptography-openssl/src/lib.rs index 7d2ab1bc7d8c..41938246fc5d 100644 --- a/src/rust/cryptography-openssl/src/lib.rs +++ b/src/rust/cryptography-openssl/src/lib.rs @@ -4,6 +4,7 @@ #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] +pub mod cmac; pub mod fips; pub mod hmac; #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] diff --git a/src/rust/src/backend/ciphers.rs b/src/rust/src/backend/ciphers.rs new file mode 100644 index 000000000000..5182843c2439 --- /dev/null +++ b/src/rust/src/backend/ciphers.rs @@ -0,0 +1,148 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use crate::error::CryptographyResult; +use crate::types; +use openssl::symm::Cipher; +use std::collections::HashMap; + +struct RegistryKey { + algorithm: pyo3::PyObject, + mode: pyo3::PyObject, + key_size: Option, + + algorithm_hash: isize, + mode_hash: isize, +} + +impl RegistryKey { + fn new( + py: pyo3::Python<'_>, + algorithm: pyo3::PyObject, + mode: pyo3::PyObject, + key_size: Option, + ) -> CryptographyResult { + Ok(Self { + algorithm: algorithm.clone_ref(py), + mode: mode.clone_ref(py), + key_size, + algorithm_hash: algorithm.as_ref(py).hash()?, + mode_hash: mode.as_ref(py).hash()?, + }) + } +} + +impl PartialEq for RegistryKey { + fn eq(&self, other: &RegistryKey) -> bool { + self.algorithm.is(&other.algorithm) + && self.mode.is(&other.mode) + && (self.key_size == other.key_size + || self.key_size.is_none() + || other.key_size.is_none()) + } +} + +impl Eq for RegistryKey {} + +impl std::hash::Hash for RegistryKey { + fn hash(&self, state: &mut H) { + self.algorithm_hash.hash(state); + self.mode_hash.hash(state); + } +} + +fn add_cipher( + py: pyo3::Python<'_>, + m: &mut HashMap, + algorithm: &pyo3::PyAny, + mode: &pyo3::PyAny, + key_size: Option, + cipher: openssl::symm::Cipher, +) -> CryptographyResult<()> { + m.insert( + RegistryKey::new(py, algorithm.into(), mode.into(), key_size)?, + cipher, + ); + + Ok(()) +} + +fn get_cipher_registry( + py: pyo3::Python<'_>, +) -> CryptographyResult<&HashMap> { + static REGISTRY: pyo3::once_cell::GILOnceCell> = + pyo3::once_cell::GILOnceCell::new(); + + REGISTRY.get_or_try_init(py, || { + let mut r = HashMap::new(); + let m = &mut r; + + let aes = types::AES.get(py)?; + let aes128 = types::AES128.get(py)?; + let aes256 = types::AES256.get(py)?; + let triple_des = types::TRIPLE_DES.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] + let camellia = types::CAMELLIA.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_BF"))] + let blowfish = types::BLOWFISH.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAST"))] + let cast5 = types::CAST5.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] + let idea = types::IDEA.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SM4"))] + let sm4 = types::SM4.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SEED"))] + let seed = types::SEED.get(py)?; + + let cbc = types::CBC.get(py)?; + + add_cipher(py, m, aes, cbc, Some(128), Cipher::aes_128_cbc())?; + add_cipher(py, m, aes, cbc, Some(192), Cipher::aes_192_cbc())?; + add_cipher(py, m, aes, cbc, Some(256), Cipher::aes_256_cbc())?; + + add_cipher(py, m, aes128, cbc, Some(128), Cipher::aes_128_cbc())?; + add_cipher(py, m, aes256, cbc, Some(256), Cipher::aes_256_cbc())?; + + add_cipher(py, m, triple_des, cbc, Some(192), Cipher::des_ede3_cbc())?; + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] + add_cipher(py, m, camellia, cbc, Some(128), Cipher::camellia_128_cbc())?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] + add_cipher(py, m, camellia, cbc, Some(192), Cipher::camellia_192_cbc())?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] + add_cipher(py, m, camellia, cbc, Some(256), Cipher::camellia_256_cbc())?; + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SM4"))] + add_cipher(py, m, sm4, cbc, Some(128), Cipher::sm4_cbc())?; + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SEED"))] + add_cipher(py, m, seed, cbc, Some(128), Cipher::seed_cbc())?; + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_BF"))] + add_cipher(py, m, blowfish, cbc, None, Cipher::bf_cbc())?; + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAST"))] + add_cipher(py, m, cast5, cbc, None, Cipher::cast5_cbc())?; + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] + add_cipher(py, m, idea, cbc, Some(128), Cipher::idea_cbc())?; + + Ok(r) + }) +} + +pub(crate) fn get_cipher( + py: pyo3::Python<'_>, + algorithm: &pyo3::PyAny, + mode_cls: &pyo3::PyAny, +) -> CryptographyResult> { + let registry = get_cipher_registry(py)?; + + let key_size = algorithm + .getattr(pyo3::intern!(py, "key_size"))? + .extract()?; + let key = RegistryKey::new(py, algorithm.get_type().into(), mode_cls.into(), key_size)?; + + Ok(registry.get(&key).cloned()) +} diff --git a/src/rust/src/backend/cmac.rs b/src/rust/src/backend/cmac.rs new file mode 100644 index 000000000000..283812b9038b --- /dev/null +++ b/src/rust/src/backend/cmac.rs @@ -0,0 +1,105 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use crate::backend::ciphers; +use crate::backend::hashes::already_finalized_error; +use crate::buf::CffiBuf; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::{exceptions, types}; + +#[pyo3::prelude::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.cmac", + name = "CMAC" +)] +struct Cmac { + ctx: Option, +} + +impl Cmac { + fn get_ctx(&self) -> CryptographyResult<&cryptography_openssl::cmac::Cmac> { + if let Some(ctx) = self.ctx.as_ref() { + return Ok(ctx); + }; + Err(already_finalized_error()) + } + + fn get_mut_ctx(&mut self) -> CryptographyResult<&mut cryptography_openssl::cmac::Cmac> { + if let Some(ctx) = self.ctx.as_mut() { + return Ok(ctx); + } + Err(already_finalized_error()) + } +} + +#[pyo3::pymethods] +impl Cmac { + #[new] + fn new( + py: pyo3::Python<'_>, + algorithm: &pyo3::PyAny, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + if !algorithm.is_instance(types::BLOCK_CIPHER_ALGORITHM.get(py)?)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Expected instance of BlockCipherAlgorithm.", + ), + )); + } + + let cipher = ciphers::get_cipher(py, algorithm, types::CBC.get(py)?)?.ok_or_else(|| { + exceptions::UnsupportedAlgorithm::new_err(( + "CMAC is not supported with this algorithm", + exceptions::Reasons::UNSUPPORTED_CIPHER, + )) + })?; + + let key = algorithm + .getattr(pyo3::intern!(py, "key"))? + .extract::>()?; + let ctx = cryptography_openssl::cmac::Cmac::new(key.as_bytes(), &cipher)?; + Ok(Cmac { ctx: Some(ctx) }) + } + + fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { + self.get_mut_ctx()?.update(data.as_bytes())?; + Ok(()) + } + + fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let data = self.get_mut_ctx()?.finish()?; + self.ctx = None; + Ok(pyo3::types::PyBytes::new(py, &data)) + } + + fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> { + let actual = self.finalize(py)?.as_bytes(); + if actual.len() != signature.len() || !openssl::memcmp::eq(actual, signature) { + return Err(CryptographyError::from( + exceptions::InvalidSignature::new_err("Signature did not match digest."), + )); + } + + Ok(()) + } + + fn copy(&self) -> CryptographyResult { + Ok(Cmac { + ctx: Some(self.get_ctx()?.copy()?), + }) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "cmac")?; + + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/mod.rs b/src/rust/src/backend/mod.rs index 4251bacfbaf3..3b32bed6bbbf 100644 --- a/src/rust/src/backend/mod.rs +++ b/src/rust/src/backend/mod.rs @@ -3,6 +3,8 @@ // for complete details. pub(crate) mod aead; +pub(crate) mod ciphers; +pub(crate) mod cmac; pub(crate) mod dh; pub(crate) mod dsa; pub(crate) mod ec; @@ -21,6 +23,7 @@ pub(crate) mod x448; pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { module.add_submodule(aead::create_module(module.py())?)?; + module.add_submodule(cmac::create_module(module.py())?)?; module.add_submodule(dh::create_module(module.py())?)?; module.add_submodule(dsa::create_module(module.py())?)?; module.add_submodule(ec::create_module(module.py())?)?; diff --git a/src/rust/src/types.rs b/src/rust/src/types.rs index 1e1dca93a19e..1ee030a40f9b 100644 --- a/src/rust/src/types.rs +++ b/src/rust/src/types.rs @@ -486,6 +486,56 @@ pub static DSA_PRIVATE_NUMBERS: LazyPyImport = LazyPyImport::new( pub static EXTRACT_BUFFER_LENGTH: LazyPyImport = LazyPyImport::new("cryptography.utils", &["_extract_buffer_length"]); +pub static BLOCK_CIPHER_ALGORITHM: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers", + &["BlockCipherAlgorithm"], +); + +pub static TRIPLE_DES: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["TripleDES"], +); +pub static AES: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["AES"], +); +pub static AES128: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["AES128"], +); +pub static AES256: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["AES256"], +); +pub static SM4: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["SM4"], +); +pub static SEED: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["_SEEDInternal"], +); +pub static CAMELLIA: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["Camellia"], +); +pub static BLOWFISH: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["_BlowfishInternal"], +); +pub static CAST5: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["_CAST5Internal"], +); +#[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] +pub static IDEA: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["_IDEAInternal"], +); + +pub static CBC: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["CBC"]); + #[cfg(test)] mod tests { use super::LazyPyImport; diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index a47470b9a243..5b33d76ef245 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -19,7 +19,6 @@ from ...doubles import ( DummyAsymmetricPadding, - DummyBlockCipherAlgorithm, DummyCipherAlgorithm, DummyHashAlgorithm, DummyMode, @@ -251,12 +250,6 @@ def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self, rsa_key_2048): ) -class TestOpenSSLCMAC: - def test_unsupported_cipher(self): - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): - backend.create_cmac_ctx(DummyBlockCipherAlgorithm(b"bad")) - - class TestOpenSSLSerializationWithOpenSSL: def test_pem_password_cb(self): userdata = backend._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") diff --git a/tests/hazmat/primitives/test_cmac.py b/tests/hazmat/primitives/test_cmac.py index c9e7fdd88fa1..18ba898e7a85 100644 --- a/tests/hazmat/primitives/test_cmac.py +++ b/tests/hazmat/primitives/test_cmac.py @@ -7,7 +7,11 @@ import pytest -from cryptography.exceptions import AlreadyFinalized, InvalidSignature +from cryptography.exceptions import ( + AlreadyFinalized, + InvalidSignature, + _Reasons, +) from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, @@ -15,7 +19,12 @@ ) from cryptography.hazmat.primitives.cmac import CMAC -from ...utils import load_nist_vectors, load_vectors_from_file +from ...doubles import DummyBlockCipherAlgorithm +from ...utils import ( + load_nist_vectors, + load_vectors_from_file, + raises_unsupported_algorithm, +) vectors_aes128 = load_vectors_from_file( "CMAC/nist-800-38b-aes128.txt", load_nist_vectors @@ -136,6 +145,9 @@ def test_invalid_algorithm(self, backend): with pytest.raises(TypeError): CMAC(ARC4(key), backend) # type: ignore[arg-type] + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + CMAC(DummyBlockCipherAlgorithm(b"bad"), backend) + @pytest.mark.supported( only_if=lambda backend: backend.cmac_algorithm_supported( AES(fake_key)