aes-kw-0.2.1/.cargo_vcs_info.json0000644000000001440000000000100122220ustar { "git": { "sha1": "97a92bd503d686aaa367616789d74179b50e352b" }, "path_in_vcs": "aes-kw" }aes-kw-0.2.1/CHANGELOG.md000064400000000000000000000011060072674642500126520ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## 0.2.1 (2022-04-20) ### Changed - Use `encrypt_with_backend`/`decrypt_with_backend` methods ([#19]) [#19]: https://github.com/RustCrypto/key-wraps/pull/19 ## 0.2.0 (2022-02-10) ### Changed - Bump `aes` dependency to v0.8 ([#14]) [#14]: https://github.com/RustCrypto/key-wraps/pull/14 ## 0.1.0 (2022-01-06) - Initial release aes-kw-0.2.1/Cargo.toml0000644000000022500000000000100102200ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.56" name = "aes-kw" version = "0.2.1" authors = ["RustCrypto Developers"] description = "NIST 800-38F AES Key Wrap (KW) and Key Wrap with Padding (KWP) modes" homepage = "https://github.com/RustCrypto/key-wraps/" readme = "README.md" keywords = [ "crypto", "AES-KW", "KW", "AES-KWP", "KWP", ] categories = [ "cryptography", "no-std", ] license = "MIT OR Apache-2.0" repository = "https://github.com/RustCrypto/key-wraps/tree/aes-kw" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [dependencies.aes] version = "0.8.1" [dev-dependencies.hex-literal] version = "0.3" [features] alloc = [] std = ["alloc"] aes-kw-0.2.1/Cargo.toml.orig000064400000000000000000000012300072674642500137260ustar 00000000000000[package] name = "aes-kw" version = "0.2.1" description = "NIST 800-38F AES Key Wrap (KW) and Key Wrap with Padding (KWP) modes" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" homepage = "https://github.com/RustCrypto/key-wraps/" repository = "https://github.com/RustCrypto/key-wraps/tree/aes-kw" keywords = ["crypto", "AES-KW", "KW", "AES-KWP", "KWP"] categories = ["cryptography", "no-std"] readme = "README.md" edition = "2021" rust-version = "1.56" [dependencies] aes = "0.8.1" [dev-dependencies] hex-literal = "0.3" [features] alloc = [] std = ["alloc"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] aes-kw-0.2.1/LICENSE-APACHE000064400000000000000000000251400072674642500127710ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.aes-kw-0.2.1/LICENSE-MIT000064400000000000000000000020560072674642500125020ustar 00000000000000Copyright (c) 2021-2022 RustCrypto Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. aes-kw-0.2.1/README.md000064400000000000000000000125340072674642500123270ustar 00000000000000# RustCrypto: AES Key Wrap Algorithm [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] ![Apache2/MIT licensed][license-image] ![Rust Version][rustc-image] [![Build Status][build-image]][build-link] Pure Rust implementation of the [NIST AES-KW Key Wrap] and [NIST AES-KWP Key Wrap with Padding] modes also described in [RFC3394] and [RFC5649]. ## About RFC3394 § 2 describes AES-KW as follows: > The AES key wrap algorithm is designed to wrap or encrypt key data. > The key wrap operates on blocks of 64 bits. Before being wrapped, > the key data is parsed into n blocks of 64 bits. > > The only restriction the key wrap algorithm places on n is that n be > at least two. (For key data with length less than or equal to 64 > bits, the constant field used in this specification and the key data > form a single 128-bit codebook input making this key wrap > unnecessary.) The key wrap algorithm accommodates all supported AES > key sizes. However, other cryptographic values often need to be > wrapped. One such value is the seed of the random number generator > for DSS. This seed value requires n to be greater than four. > Undoubtedly other values require this type of protection. Therefore, > no upper bound is imposed on n. > > The AES key wrap can be configured to use any of the three key sizes > supported by the AES codebook. The choice of a key size affects the > overall security provided by the key wrap, but it does not alter the > description of the key wrap algorithm. Therefore, in the description > that follows, the key wrap is described generically; no key size is > specified for the KEK. RFC5649 § 1 describes AES-KWP as follows: > This document specifies an extension of the Advanced Encryption > Standard (AES) Key Wrap algorithm \[AES-KW1, AES-KW2\]. Without this > extension, the input to the AES Key Wrap algorithm, called the key > data, must be a sequence of two or more 64-bit blocks. > > The AES Key Wrap with Padding algorithm can be used to wrap a key of > any practical size with an AES key. The AES key-encryption key (KEK) > must be 128, 192, or 256 bits. The input key data may be as short as > one octet, which will result in an output of two 64-bit blocks (or 16 > octets). Although the AES Key Wrap algorithm does not place a > maximum bound on the size of the key data that can be wrapped, this > extension does so. The use of a 32-bit fixed field to carry the > octet length of the key data bounds the size of the input at 2^32 > octets. Most systems will have other factors that limit the > practical size of key data to much less than 2^32 octets. # Usage The most common way to use AES-KW is as follows: you provide the Key Wrapping Key and the key-to-be-wrapped, then wrap it, or provide a wrapped-key and unwrap it. ```rust # fn main() -> Result<(), Box> { # #[cfg(feature = "std")] # { use aes_kw::Kek; use hex_literal::hex; let kek = Kek::from(hex!("000102030405060708090A0B0C0D0E0F")); let input_key = hex!("00112233445566778899AABBCCDDEEFF"); let wrapped_key = kek.wrap_vec(&input_key)?; assert_eq!(wrapped_key, hex!("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5")); let unwrapped_key = kek.unwrap_vec(&wrapped_key)?; assert_eq!(unwrapped_key, input_key); # } # Ok(()) # } ``` Alternatively, AES-KWP can be used to wrap keys which are not a multiple of 8 bytes long. ```rust # fn main() -> Result<(), Box> { # #[cfg(feature = "std")] # { use aes_kw::Kek; use hex_literal::hex; let kek = Kek::from(hex!("5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8")); let input_key = hex!("c37b7e6492584340bed12207808941155068f738"); let wrapped_key = kek.wrap_with_padding_vec(&input_key)?; assert_eq!(wrapped_key, hex!("138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a")); let unwrapped_key = kek.unwrap_with_padding_vec(&wrapped_key)?; assert_eq!(unwrapped_key, input_key); # } # Ok(()) # } ``` Implemented for 128/192/256bit keys. ## Minimum Supported Rust Version This crate requires **Rust 1.56** at a minimum. We may change the MSRV in the future, but it will be accompanied by a minor version bump. ## License Licensed under either of: - [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - [MIT license](http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [//]: # (badges) [crate-image]: https://img.shields.io/crates/v/aes-kw.svg [crate-link]: https://crates.io/crates/aes-kw [docs-image]: https://docs.rs/aes-kw/badge.svg [docs-link]: https://docs.rs/aes-kw/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg [rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg [build-image]: https://github.com/RustCrypto/key-wraps/actions/workflows/aes-kw.yml/badge.svg [build-link]: https://github.com/RustCrypto/key-wraps/actions/workflows/aes-kw.yml [//]: # (links) [NIST AES-KW Key Wrap]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38F.pdf [NIST AES-KWP Key Wrap with Padding]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38F.pdf [RFC3394]: https://datatracker.ietf.org/doc/html/rfc3394 [RFC5649]: https://datatracker.ietf.org/doc/html/rfc5649 aes-kw-0.2.1/src/error.rs000064400000000000000000000024430072674642500133340ustar 00000000000000use core::fmt; /// Result type with the `aes-kw` crate's [`Error`]. pub type Result = core::result::Result; /// Errors emitted from the wrap and unwrap operations. #[derive(Debug)] pub enum Error { /// Input data length invalid. InvalidDataSize, /// Invalid KEK size. InvalidKekSize { /// KEK size provided in bytes (expected 8, 12, or 24). size: usize, }, /// Output buffer size invalid. InvalidOutputSize { /// Expected size in bytes. expected: usize, }, /// Integrity check did not pass. IntegrityCheckFailed, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::InvalidDataSize => write!(f, "data must be a multiple of 64 bits for AES-KW and less than 2^32 bytes for AES-KWP"), Error::InvalidKekSize { size } => { write!(f, "invalid AES KEK size: {}", size) } Error::InvalidOutputSize { expected } => { write!(f, "invalid output buffer size: expected {}", expected) } Error::IntegrityCheckFailed => { write!(f, "integrity check failed") } } } } #[cfg(feature = "std")] impl std::error::Error for Error {} aes-kw-0.2.1/src/lib.rs000064400000000000000000000352530072674642500127560ustar 00000000000000#![no_std] #![doc = include_str!("../README.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" )] #![cfg_attr(docsrs, feature(doc_cfg))] #![forbid(unsafe_code)] #![warn(missing_docs, rust_2018_idioms)] #[cfg(feature = "alloc")] #[macro_use] extern crate alloc; #[cfg(feature = "std")] extern crate std; mod error; pub use error::{Error, Result}; use aes::cipher::{ generic_array::GenericArray, typenum::{Unsigned, U16, U24, U32}, Block, BlockBackend, BlockCipher, BlockClosure, BlockDecrypt, BlockEncrypt, BlockSizeUser, KeyInit, }; #[cfg(feature = "alloc")] use alloc::vec::Vec; /// Size of an AES "semiblock" in bytes. /// /// From NIST SP 800-38F § 4.1: /// /// > semiblock: given a block cipher, a bit string whose length is half of the /// > block size. pub const SEMIBLOCK_SIZE: usize = 8; /// Maximum length of the AES-KWP input data (2^32 bytes). pub const KWP_MAX_LEN: usize = u32::MAX as usize; /// Size of an AES-KW and AES-KWP initialization vector in bytes. pub const IV_LEN: usize = SEMIBLOCK_SIZE; /// Default Initial Value for AES-KW as defined in RFC3394 § 2.2.3.1. /// /// /// /// ```text /// The default initial value (IV) is defined to be the hexadecimal /// constant: /// /// A[0] = IV = A6A6A6A6A6A6A6A6 /// /// The use of a constant as the IV supports a strong integrity check on /// the key data during the period that it is wrapped. If unwrapping /// produces A[0] = A6A6A6A6A6A6A6A6, then the chance that the key data /// is corrupt is 2^-64. If unwrapping produces A[0] any other value, /// then the unwrap must return an error and not return any key data. /// ``` pub const IV: [u8; IV_LEN] = [0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6]; /// Alternative Initial Value constant prefix for AES-KWP as defined in /// RFC3394 § 3. /// /// /// /// ```text /// The Alternative Initial Value (AIV) required by this specification is // a 32-bit constant concatenated to a 32-bit MLI. The constant is (in // hexadecimal) A65959A6 and occupies the high-order half of the AIV. /// ``` pub const KWP_IV_PREFIX: [u8; IV_LEN / 2] = [0xA6, 0x59, 0x59, 0xA6]; /// A Key-Encrypting-Key (KEK) that can be used to wrap and unwrap other /// keys. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Kek where Aes: KeyInit + BlockCipher + BlockSizeUser + BlockEncrypt + BlockDecrypt, { /// Initialized cipher cipher: Aes, } /// AES-128 KEK pub type KekAes128 = Kek; /// AES-192 KEK pub type KekAes192 = Kek; /// AES-256 KEK pub type KekAes256 = Kek; impl From> for KekAes128 { fn from(kek: GenericArray) -> Self { Kek::new(&kek) } } impl From> for KekAes192 { fn from(kek: GenericArray) -> Self { Kek::new(&kek) } } impl From> for KekAes256 { fn from(kek: GenericArray) -> Self { Kek::new(&kek) } } impl From<[u8; 16]> for KekAes128 { fn from(kek: [u8; 16]) -> Self { Kek::new(&kek.into()) } } impl From<[u8; 24]> for KekAes192 { fn from(kek: [u8; 24]) -> Self { Kek::new(&kek.into()) } } impl From<[u8; 32]> for KekAes256 { fn from(kek: [u8; 32]) -> Self { Kek::new(&kek.into()) } } impl TryFrom<&[u8]> for Kek where Aes: KeyInit + BlockCipher + BlockSizeUser + BlockEncrypt + BlockDecrypt, { type Error = Error; fn try_from(value: &[u8]) -> Result { if value.len() == Aes::KeySize::to_usize() { Ok(Kek::new(GenericArray::from_slice(value))) } else { Err(Error::InvalidKekSize { size: value.len() }) } } } impl Kek where Aes: KeyInit + BlockCipher + BlockSizeUser + BlockEncrypt + BlockDecrypt, { /// Constructs a new Kek based on the appropriate raw key material. pub fn new(key: &GenericArray) -> Self { let cipher = Aes::new(key); Kek { cipher } } /// AES Key Wrap, as defined in RFC 3394. /// /// The `out` buffer will be overwritten, and must be exactly [`IV_LEN`] /// bytes (i.e. 8 bytes) longer than the length of `data`. pub fn wrap(&self, data: &[u8], out: &mut [u8]) -> Result<()> { if data.len() % SEMIBLOCK_SIZE != 0 { return Err(Error::InvalidDataSize); } if out.len() != data.len() + IV_LEN { return Err(Error::InvalidOutputSize { expected: data.len() + IV_LEN, }); } // 0) Prepare inputs // number of 64 bit blocks in the input data let n = data.len() / 8; // 1) Initialize variables // Set A to the IV let block = &mut Block::>::default(); block[..IV_LEN].copy_from_slice(&IV); // 2) Calculate intermediate values out[IV_LEN..].copy_from_slice(data); self.cipher.encrypt_with_backend(WCtx { n, block, out }); // 3) Output the results out[..IV_LEN].copy_from_slice(&block[..IV_LEN]); Ok(()) } /// Computes [`Self::wrap`], allocating a [`Vec`] for the return value. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn wrap_vec(&self, data: &[u8]) -> Result> { let mut out = vec![0u8; data.len() + IV_LEN]; self.wrap(data, &mut out)?; Ok(out) } /// AES Key Unwrap, as defined in RFC 3394. /// /// The `out` buffer will be overwritten, and must be exactly [`IV_LEN`] /// bytes (i.e. 8 bytes) shorter than the length of `data`. pub fn unwrap(&self, data: &[u8], out: &mut [u8]) -> Result<()> { if data.len() % SEMIBLOCK_SIZE != 0 { return Err(Error::InvalidDataSize); } // 0) Prepare inputs let n = (data.len() / SEMIBLOCK_SIZE) .checked_sub(1) .ok_or(Error::InvalidDataSize)?; if out.len() != n * SEMIBLOCK_SIZE { return Err(Error::InvalidOutputSize { expected: n * SEMIBLOCK_SIZE, }); } // 1) Initialize variables let block = &mut Block::>::default(); block[..IV_LEN].copy_from_slice(&data[..IV_LEN]); // for i = 1 to n: R[i] = C[i] out.copy_from_slice(&data[IV_LEN..]); // 2) Calculate intermediate values self.cipher .decrypt_with_backend(WInverseCtx { n, block, out }); // 3) Output the results if block[..IV_LEN] == IV[..] { Ok(()) } else { Err(Error::IntegrityCheckFailed) } } /// Computes [`Self::unwrap`], allocating a [`Vec`] for the return value. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn unwrap_vec(&self, data: &[u8]) -> Result> { let out_len = data .len() .checked_sub(IV_LEN) .ok_or(Error::InvalidDataSize)?; let mut out = vec![0u8; out_len]; self.unwrap(data, &mut out)?; Ok(out) } /// AES Key Wrap with Padding, as defined in RFC 5649. /// /// /// The `out` buffer will be overwritten, and must be the smallest /// multiple of [`SEMIBLOCK_SIZE`] (i.e. 8) which is at least [`IV_LEN`] /// bytes (i.e. 8 bytes) longer than the length of `data`. pub fn wrap_with_padding(&self, data: &[u8], out: &mut [u8]) -> Result<()> { if data.len() > KWP_MAX_LEN { return Err(Error::InvalidDataSize); } // 0) Prepare inputs // number of 64 bit blocks in the input data (padded) let n = (data.len() + SEMIBLOCK_SIZE - 1) / SEMIBLOCK_SIZE; if out.len() != n * SEMIBLOCK_SIZE + IV_LEN { return Err(Error::InvalidOutputSize { expected: n * SEMIBLOCK_SIZE + IV_LEN, }); } // 32-bit MLI equal to the number of bytes in the input data, big endian let mli = (data.len() as u32).to_be_bytes(); // 2) Wrapping // 2.1) Initialize variables // Set A to the AIV let block = &mut Block::>::default(); block[..IV_LEN / 2].copy_from_slice(&KWP_IV_PREFIX); block[IV_LEN / 2..IV_LEN].copy_from_slice(&mli); // If n is 1, the plaintext is encrypted as a single AES block if n == 1 { // 1) Append padding // GenericArrays should be zero by default, but zeroize again to be sure for i in data.len()..n * SEMIBLOCK_SIZE { block[IV_LEN + i] = 0; } block[IV_LEN..IV_LEN + data.len()].copy_from_slice(data); self.cipher.encrypt_block(block); out.copy_from_slice(block); } else { // 1) Append padding // Don't trust the caller to provide a zeroized out buffer, zeroize again to be sure for i in data.len()..n * SEMIBLOCK_SIZE { out[IV_LEN + i] = 0; } // 2.2) Calculate intermediate values out[IV_LEN..IV_LEN + data.len()].copy_from_slice(data); self.cipher.encrypt_with_backend(WCtx { n, block, out }); // 2.3) Output the results out[..IV_LEN].copy_from_slice(&block[..IV_LEN]); } Ok(()) } /// Computes [`Self::wrap`], allocating a [`Vec`] for the return value. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn wrap_with_padding_vec(&self, data: &[u8]) -> Result> { let n = (data.len() + SEMIBLOCK_SIZE - 1) / SEMIBLOCK_SIZE; let mut out = vec![0u8; n * SEMIBLOCK_SIZE + IV_LEN]; self.wrap_with_padding(data, &mut out)?; Ok(out) } /// AES Key Wrap with Padding, as defined in RFC 5649. /// /// The `out` buffer will be overwritten, and must be exactly [`IV_LEN`] /// bytes (i.e. 8 bytes) shorter than the length of `data`. /// This method returns a slice of `out`, truncated to the appropriate /// length by removing the padding. pub fn unwrap_with_padding<'a>(&self, data: &[u8], out: &'a mut [u8]) -> Result<&'a [u8]> { if data.len() % SEMIBLOCK_SIZE != 0 { return Err(Error::InvalidDataSize); } // 0) Prepare inputs let n = (data.len() / SEMIBLOCK_SIZE) .checked_sub(1) .ok_or(Error::InvalidDataSize)?; if out.len() != n * SEMIBLOCK_SIZE { return Err(Error::InvalidOutputSize { expected: n * SEMIBLOCK_SIZE, }); } // 1) Key unwrapping // 1.1) Initialize variables let block = &mut Block::>::default(); // If n is 1, the plaintext is encrypted as a single AES block if n == 1 { block.copy_from_slice(data); self.cipher.decrypt_block(block); out.copy_from_slice(&block[IV_LEN..]); } else { block[..IV_LEN].copy_from_slice(&data[..IV_LEN]); // for i = 1 to n: R[i] = C[i] out.copy_from_slice(&data[IV_LEN..]); // 1.2) Calculate intermediate values self.cipher .decrypt_with_backend(WInverseCtx { n, block, out }); } // 2) AIV verification // Checks as defined in RFC5649 § 3 if block[..IV_LEN / 2] != KWP_IV_PREFIX { return Err(Error::IntegrityCheckFailed); } let mli = u32::from_be_bytes(block[IV_LEN / 2..IV_LEN].try_into().unwrap()) as usize; if !(SEMIBLOCK_SIZE * (n - 1) < mli && mli <= SEMIBLOCK_SIZE * n) { return Err(Error::IntegrityCheckFailed); } let b = SEMIBLOCK_SIZE * n - mli; if !out.iter().rev().take(b).all(|&x| x == 0) { return Err(Error::IntegrityCheckFailed); } // 3) Output the results Ok(&out[..mli]) } /// Computes [`Self::unwrap`], allocating a [`Vec`] for the return value. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn unwrap_with_padding_vec(&self, data: &[u8]) -> Result> { let out_len = data .len() .checked_sub(IV_LEN) .ok_or(Error::InvalidDataSize)?; let mut out = vec![0u8; out_len]; let out_len = self.unwrap_with_padding(data, &mut out)?.len(); out.truncate(out_len); Ok(out) } } struct WCtx<'a> { n: usize, block: &'a mut Block, out: &'a mut [u8], } impl<'a> BlockSizeUser for WCtx<'a> { type BlockSize = U16; } /// Very similar to the W(S) function defined by NIST in SP 800-38F, /// Section 6.1 impl<'a> BlockClosure for WCtx<'a> { #[inline(always)] fn call>(self, backend: &mut B) { for j in 0..=5 { for (i, chunk) in self.out.chunks_mut(SEMIBLOCK_SIZE).skip(1).enumerate() { // A | R[i] self.block[IV_LEN..].copy_from_slice(chunk); // B = AES(K, ..) backend.proc_block(self.block.into()); // A = MSB(64, B) ^ t let t = (self.n * j + (i + 1)) as u64; for (ai, ti) in self.block[..IV_LEN].iter_mut().zip(&t.to_be_bytes()) { *ai ^= ti; } // R[i] = LSB(64, B) chunk.copy_from_slice(&self.block[IV_LEN..]); } } } } struct WInverseCtx<'a> { n: usize, block: &'a mut Block, out: &'a mut [u8], } impl<'a> BlockSizeUser for WInverseCtx<'a> { type BlockSize = U16; } /// Very similar to the W^-1(S) function defined by NIST in SP 800-38F, /// Section 6.1 impl<'a> BlockClosure for WInverseCtx<'a> { #[inline(always)] fn call>(self, backend: &mut B) { for j in (0..=5).rev() { for (i, chunk) in self.out.chunks_mut(SEMIBLOCK_SIZE).enumerate().rev() { // A ^ t let t = (self.n * j + (i + 1)) as u64; for (ai, ti) in self.block[..IV_LEN].iter_mut().zip(&t.to_be_bytes()) { *ai ^= ti; } // (A ^ t) | R[i] self.block[IV_LEN..].copy_from_slice(chunk); // B = AES-1(K, ..) backend.proc_block(self.block.into()); // A = MSB(64, B) // already set // R[i] = LSB(64, B) chunk.copy_from_slice(&self.block[IV_LEN..]); } } } } aes-kw-0.2.1/tests/kw_tests.rs000064400000000000000000000115600072674642500144210ustar 00000000000000use aes_kw::Error; use aes_kw::Kek; use hex_literal::hex; use std::assert_eq; #[cfg(test)] mod tests { use super::*; macro_rules! test_aes_kw { ($name:ident, $kek_typ:ty, $kek:expr, $input:expr, $output:expr) => { #[test] fn $name() { let kek = Kek::<$kek_typ>::from($kek); let mut wrapped = [0u8; $output.len()]; kek.wrap(&$input, &mut wrapped).unwrap(); let mut unwrapped = [0u8; $input.len()]; kek.unwrap(&wrapped, &mut unwrapped).unwrap(); assert_eq!($output, wrapped, "failed wrap"); assert_eq!($input, unwrapped, "failed unwrap"); } }; } test_aes_kw!( wrap_unwrap_128_key_128_kek, aes::Aes128, hex!("000102030405060708090A0B0C0D0E0F"), hex!("00112233445566778899AABBCCDDEEFF"), hex!("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5") ); test_aes_kw!( wrap_unwrap_128_key_192_kek, aes::Aes192, hex!("000102030405060708090A0B0C0D0E0F1011121314151617"), hex!("00112233445566778899AABBCCDDEEFF"), hex!("96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D") ); test_aes_kw!( wrap_unwrap_128_key_256_kek, aes::Aes256, hex!("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"), hex!("00112233445566778899AABBCCDDEEFF"), hex!("64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7") ); test_aes_kw!( wrap_unwrap_192_key_192_kek, aes::Aes192, hex!("000102030405060708090A0B0C0D0E0F1011121314151617"), hex!("00112233445566778899AABBCCDDEEFF0001020304050607"), hex!("031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2") ); test_aes_kw!( wrap_unwrap_192_key_256_kek, aes::Aes256, hex!("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"), hex!("00112233445566778899AABBCCDDEEFF0001020304050607"), hex!("A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1") ); test_aes_kw!( wrap_unwrap_256_key_256_kek, aes::Aes256, hex!("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"), hex!("00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F"), hex!("28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21") ); #[test] fn error_invalid_data_size() { let kek = hex!("000102030405060708090A0B0C0D0E0F"); let input = hex!("00112233445566778899AABBCCDDEE"); let output = hex!("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CF"); let kek = Kek::::from(kek); let mut wrapped = [0u8; 24]; let result = kek.wrap(&input, &mut wrapped); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), Error::InvalidDataSize)); let mut unwrapped = [0u8; 16]; let result = kek.unwrap(&output, &mut unwrapped); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), Error::InvalidDataSize)); let mut unwrapped = [0u8; 0]; let result = kek.unwrap(&[], &mut unwrapped); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), Error::InvalidDataSize)); } #[test] fn error_invalid_kek_size() { let kek = hex!("000102030405060708090A0B0C0D0E"); let result = Kek::::try_from(&kek[..]); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), Error::InvalidKekSize { size: 15 } )); } #[test] fn error_invalid_output_size() { let kek = hex!("000102030405060708090A0B0C0D0E0F"); let input = hex!("00112233445566778899AABBCCDDEEFF"); let output = hex!("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5"); let kek = Kek::::from(kek); let mut wrapped = [0u8; 23]; let result = kek.wrap(&input, &mut wrapped); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), Error::InvalidOutputSize { expected: 24 } )); let mut unwrapped = [0u8; 15]; let result = kek.unwrap(&output, &mut unwrapped); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), Error::InvalidOutputSize { expected: 16 } )); } #[test] fn error_integrity_check_failed() { let kek = hex!("000102030405060708090A0B0C0D0E0F"); let output = hex!("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE6"); let kek = Kek::::from(kek); let mut unwrapped = [0u8; 16]; let result = kek.unwrap(&output, &mut unwrapped); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), Error::IntegrityCheckFailed)); } } aes-kw-0.2.1/tests/kwp_tests.rs000064400000000000000000000217100072674642500145770ustar 00000000000000use aes_kw::Error; use aes_kw::Kek; use aes_kw::IV_LEN; use hex_literal::hex; use std::assert_eq; #[cfg(test)] mod tests { use super::*; macro_rules! test_aes_kwp { ($name:ident, $kek_typ:ty, $kek:expr, $input:expr, $output:expr) => { #[test] fn $name() { let kek = Kek::<$kek_typ>::from($kek); let mut wrapped = [0u8; $output.len()]; kek.wrap_with_padding(&$input, &mut wrapped).unwrap(); let mut unwrapped = [0u8; $output.len() - IV_LEN]; let unwrapped_slice = kek.unwrap_with_padding(&wrapped, &mut unwrapped).unwrap(); assert_eq!($output, wrapped, "failed wrap"); assert_eq!($input, unwrapped_slice, "failed unwrap"); } }; } test_aes_kwp!( wrap_unwrap_160_key_192_kek, aes::Aes192, hex!("5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8"), hex!("c37b7e6492584340bed12207808941155068f738"), hex!("138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a") ); test_aes_kwp!( wrap_unwrap_56_key_192_kek, aes::Aes192, hex!("5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8"), hex!("466f7250617369"), hex!("afbeb0f07dfbf5419200f2ccb50bb24f") ); // These test vectors were obtained using NIST's ACVP system test_aes_kwp!( wrap_unwrap_24_key_128_kek, aes::Aes128, hex!("AF83AE6624FC006DA13B3C37B8A5933B"), hex!("13126A"), hex!("A661F530339C9F344FA4755AD4CC3558") ); test_aes_kwp!( wrap_unwrap_24_key_192_kek, aes::Aes192, hex!("BA0CFC260103DDD629FA8826982F5547D245F5AB0711F10F"), hex!("C01990"), hex!("91E3B5E73A25EC91E91D337D0485B960") ); test_aes_kwp!( wrap_unwrap_24_key_256_kek, aes::Aes256, hex!("6D60C0D0941CF3750B864C6F1FA580AE074C00EDEB386F9FC299178A70FCCCD1"), hex!("6B54A0"), hex!("24255140B4A9F8A9E35B9DA2BFA0E0C3") ); test_aes_kwp!( wrap_unwrap_64_key_128_kek, aes::Aes128, hex!("D19C43011C2A0242A38BD58B8D76456D"), hex!("4202C90D7298CB4B"), hex!("65BEFAEAACBB4620D1A5D64E7B57A760") ); test_aes_kwp!( wrap_unwrap_64_key_192_kek, aes::Aes192, hex!("D65980B811B696A44AFB3DE6DDCA07910FAB2A4C898B51AF"), hex!("E63D206E6321CBCA"), hex!("7F3B9764D9B28AA7D2E4EDA430AFBA21") ); test_aes_kwp!( wrap_unwrap_64_key_256_kek, aes::Aes256, hex!("EB950B844B97145A594B7F91AA81844045874AAA46DB522CF91144F63A6FED37"), hex!("A4CE3F7D7C49B11A"), hex!("F5939D472407E28EE6D7269FA75DAC88") ); test_aes_kwp!( wrap_unwrap_128_key_128_kek, aes::Aes128, hex!("EBEE1B9211AADEFD06D258605F7134FB"), hex!("4029F7DA4F8C29E4BB951A6F9D7F5305"), hex!("634194EACA80D77A21D11DD3E739DC5AA3FECA2CE0990507") ); test_aes_kwp!( wrap_unwrap_128_key_192_kek, aes::Aes192, hex!("029194F464DCF06C0E7CA8F05927874A3AC4AA93262459FC"), hex!("D45E4B35D47F2F559EE2B78D71E73C23"), hex!("2519D224F9CAB21C69ED5758F41BEB4D145FC68A3387BADF") ); test_aes_kwp!( wrap_unwrap_128_key_256_kek, aes::Aes256, hex!("314A549913256A71C6348EAAB9B85EFC755FE736568F0DBC9F6F8BC3CA3D12EE"), hex!("3B700E9682275D8DBE61CA7C1EC900E8"), hex!("70C684C49112AD8B8C3E13B99992127B58DCB9B59CE5C3FD") ); test_aes_kwp!( wrap_unwrap_144_key_128_kek, aes::Aes128, hex!("83696B21D199C224415370F2C9857E67"), hex!("8D6220459626A496036389DF998B45029CE7"), hex!("C255C96564C96F0A381A8A8091389D654357AB826C9F1ACF16EA8E1DB2F820E9") ); test_aes_kwp!( wrap_unwrap_144_key_192_kek, aes::Aes192, hex!("2F65E32F3BC3F0F3EA7E74E86ED66162A7447E723D30E72F"), hex!("CB4BE52BAB46B64322FFFFF30D1A39D17359"), hex!("4C27BAE9E7A7814B78946A6F06902A14C51DA65344524EAA645BE30F14C400D5") ); test_aes_kwp!( wrap_unwrap_144_key_256_kek, aes::Aes256, hex!("F2882A99E67FD1F0E024D2E973EE55BF2AE94D6798BC3B3A7EF94BFC9197A7F6"), hex!("13CDD6837C4C40FDE0B9EC150093713771AC"), hex!("D096D3702EA4252DA0D36666D01F1F450BCD26C87814A8041F8EEFD229EC4828") ); #[test] fn padding_cleared() { let kek = hex!("5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8"); let input = hex!("c37b7e6492584340bed12207808941155068f738"); let output = hex!("138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a"); let kek = Kek::::from(kek); let mut wrapped = [0u8; 32]; // The following positions should be zeroized by wrap (padding). wrapped[20] = 0xFF; wrapped[21] = 0xFF; wrapped[22] = 0xFF; wrapped[23] = 0xFF; kek.wrap_with_padding(&input, &mut wrapped).unwrap(); assert_eq!(output, wrapped, "failed wrap"); } #[cfg(feature = "alloc")] #[test] fn unwrap_with_padding_vec_truncated() { let kek = hex!("5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8"); let input = hex!("c37b7e6492584340bed12207808941155068f738"); let output = hex!("138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a"); let kek = Kek::::from(kek); let unwrapped = kek.unwrap_with_padding_vec(&output).unwrap(); assert_eq!(input.to_vec(), unwrapped, "failed unwrap"); } #[test] fn error_invalid_data_size() { let kek = hex!("EBEE1B9211AADEFD06D258605F7134FB"); let output = hex!("634194EACA80D77A21D11DD3E739DC5AA3FECA2CE09905"); let kek = Kek::::from(kek); let mut unwrapped = [0u8; 16]; let result = kek.unwrap_with_padding(&output, &mut unwrapped); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), Error::InvalidDataSize)); let mut unwrapped = [0u8; 0]; let result = kek.unwrap_with_padding(&[], &mut unwrapped); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), Error::InvalidDataSize)); } #[test] fn error_invalid_kek_size() { let kek = hex!("EBEE1B9211AADEFD06D258605F7134"); let result = Kek::::try_from(&kek[..]); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), Error::InvalidKekSize { size: 15 } )); } #[test] fn error_invalid_output_size() { let kek = hex!("EBEE1B9211AADEFD06D258605F7134FB"); let input = hex!("4029F7DA4F8C29E4BB951A6F9D7F5305"); let output = hex!("634194EACA80D77A21D11DD3E739DC5AA3FECA2CE0990507"); let kek = Kek::::from(kek); let mut wrapped = [0u8; 23]; let result = kek.wrap_with_padding(&input, &mut wrapped); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), Error::InvalidOutputSize { expected: 24 } )); let mut unwrapped = [0u8; 15]; let result = kek.unwrap_with_padding(&output, &mut unwrapped); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), Error::InvalidOutputSize { expected: 16 } )); // Make sure we also test the padded case let kek = hex!("AF83AE6624FC006DA13B3C37B8A5933B"); let input = hex!("13126A"); let output = hex!("A661F530339C9F344FA4755AD4CC3558"); let kek = Kek::::from(kek); let mut wrapped = [0u8; 11]; let result = kek.wrap_with_padding(&input, &mut wrapped); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), Error::InvalidOutputSize { expected: 16 } )); let mut unwrapped = [0u8; 3]; let result = kek.unwrap_with_padding(&output, &mut unwrapped); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), Error::InvalidOutputSize { expected: 8 } )); } #[test] fn error_integrity_check_failed() { let kek = hex!("EBEE1B9211AADEFD06D258605F7134FB"); let output = hex!("634194EACA80D77A21D11DD3E739DC5AA3FECA2CE0990508"); let kek = Kek::::from(kek); let mut unwrapped = [0u8; 16]; let result = kek.unwrap_with_padding(&output, &mut unwrapped); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), Error::IntegrityCheckFailed)); // Make sure we also test the padded case let kek = hex!("AF83AE6624FC006DA13B3C37B8A5933B"); let output = hex!("A661F530339C9F344FA4755AD4CC3559"); let kek = Kek::::from(kek); let mut unwrapped = [0u8; 8]; let result = kek.unwrap_with_padding(&output, &mut unwrapped); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), Error::IntegrityCheckFailed)); } }