diff --git a/.github/workflows/ocb3.yml b/.github/workflows/ocb3.yml new file mode 100644 index 00000000..5c47c546 --- /dev/null +++ b/.github/workflows/ocb3.yml @@ -0,0 +1,68 @@ +name: ocb3 + +on: + pull_request: + paths: + - ".github/workflows/ocb3.yml" + - "ocb3/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: ocb3 + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.60.0 # MSRV + - stable + target: + - armv7a-none-eabi + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + include: + # 32-bit Linux + - target: i686-unknown-linux-gnu + rust: 1.60.0 # MSRV + deps: sudo apt update && sudo apt install gcc-multilib + - target: i686-unknown-linux-gnu + rust: stable + deps: sudo apt update && sudo apt install gcc-multilib + + # 64-bit Linux + - target: x86_64-unknown-linux-gnu + rust: 1.60.0 # MSRV + - target: x86_64-unknown-linux-gnu + rust: stable + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: ${{ matrix.deps }} + - run: cargo test --target ${{ matrix.target }} --release + - run: cargo test --target ${{ matrix.target }} --release --features stream,std,zeroize + - run: cargo test --target ${{ matrix.target }} --release --all-features + - run: cargo build --target ${{ matrix.target }} --benches diff --git a/Cargo.lock b/Cargo.lock index 53df2da8..4a9a7d39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,9 +99,9 @@ dependencies = [ [[package]] name = "atomic-polyfill" -version = "0.1.11" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" dependencies = [ "critical-section", ] @@ -294,9 +294,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -324,9 +324,9 @@ dependencies = [ [[package]] name = "heapless" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" dependencies = [ "atomic-polyfill", "hash32", @@ -380,9 +380,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.149" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "lock_api" @@ -418,11 +418,24 @@ dependencies = [ "subtle", ] +[[package]] +name = "ocb3" +version = "0.1.0" +dependencies = [ + "aead", + "aes", + "cipher 0.4.4", + "ctr", + "hex-literal 0.3.4", + "subtle", + "zeroize", +] + [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "pmac" @@ -466,18 +479,18 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -508,9 +521,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "spin" @@ -535,9 +548,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.38" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index ff932711..fb4be484 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,6 @@ members = [ "deoxys", "eax", "mgm", + "ocb3", ] resolver = "2" diff --git a/ocb3/CHANGELOG.md b/ocb3/CHANGELOG.md new file mode 100644 index 00000000..f0c2c862 --- /dev/null +++ b/ocb3/CHANGELOG.md @@ -0,0 +1,10 @@ +# 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.1.0 (2023-XX-XX) + +- Initial release diff --git a/ocb3/Cargo.toml b/ocb3/Cargo.toml new file mode 100644 index 00000000..2217f3e1 --- /dev/null +++ b/ocb3/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "ocb3" +version = "0.1.0" +description = """ +Pure Rust implementation of the OCB3 +Authenticated Encryption with Associated Data (AEAD) Cipher +""" +authors = ["RustCrypto Developers"] +edition = "2021" +license = "Apache-2.0 OR MIT" +readme = "README.md" +documentation = "https://docs.rs/ocb3" +repository = "https://github.com/RustCrypto/AEADs" +keywords = ["aead", "encryption", "ocb", "ocb3"] +categories = ["cryptography", "no-std"] +rust-version = "1.60" + +[dependencies] +aead = { version = "0.5", default-features = false } +cipher = "0.4" +ctr = "0.9" +subtle = { version = "2", default-features = false } +zeroize = { version = "1", optional = true, default-features = false } + +[dev-dependencies] +aead = { version = "0.5", features = ["dev"], default-features = false } +aes = { version = "0.8", default-features = false } +hex-literal = "0.4" + +[features] +default = ["alloc", "getrandom"] +std = ["aead/std", "alloc"] +alloc = ["aead/alloc"] +arrayvec = ["aead/arrayvec"] +getrandom = ["aead/getrandom", "rand_core"] +heapless = ["aead/heapless"] +rand_core = ["aead/rand_core"] +stream = ["aead/stream"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/ocb3/LICENSE-APACHE b/ocb3/LICENSE-APACHE new file mode 100644 index 00000000..78173fa2 --- /dev/null +++ b/ocb3/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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. diff --git a/ocb3/LICENSE-MIT b/ocb3/LICENSE-MIT new file mode 100644 index 00000000..50c61807 --- /dev/null +++ b/ocb3/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2023 The RustCrypto Project 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. diff --git a/ocb3/README.md b/ocb3/README.md new file mode 100644 index 00000000..0040aee1 --- /dev/null +++ b/ocb3/README.md @@ -0,0 +1,73 @@ +# RustCrypto: OCB3 + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +Pure Rust implementation of **OCB3** ([RFC 7253][rfc7253])[Authenticated Encryption with Associated Data (AEAD)][aead] cipher. + +[Documentation][docs-link] + +## Example + +```rust +use aes::Aes128; +use ocb3::{ + aead::{Aead, AeadCore, KeyInit, OsRng, generic_array::GenericArray}, + consts::U12, + AesOcb3, +}; + +type Aes128Ocb3 = AesOcb3; + +let key = Aes128::generate_key(&mut OsRng); +let cipher = Aes128Ocb3::new(&key); +let nonce = Aes128Ocb3::generate_nonce(&mut OsRng); +let ciphertext = cipher.encrypt(&nonce, b"plaintext message".as_ref()).unwrap(); +let plaintext = cipher.decrypt(&nonce, ciphertext.as_ref()).unwrap(); + +assert_eq!(&plaintext, b"plaintext message"); +``` + + +## Security Notes + +No security audits of this crate have ever been performed, and it has not been thoroughly assessed to ensure its operation is constant-time on common CPU architectures. + +USE AT YOUR OWN RISK! + +## 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://buildstats.info/crate/ocb3 +[crate-link]: https://crates.io/crates/ocb3 +[docs-image]: https://docs.rs/ocb3/badge.svg +[docs-link]: https://docs.rs/ocb3/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260038-AEADs +[build-image]: https://github.com/RustCrypto/AEADs/workflows/ocb3/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/AEADs/actions + +[//]: # (general links) + +[rfc7253]: https://datatracker.ietf.org/doc/rfc7253/ +[aead]: https://en.wikipedia.org/wiki/Authenticated_encryption diff --git a/ocb3/src/lib.rs b/ocb3/src/lib.rs new file mode 100644 index 00000000..c5be97e1 --- /dev/null +++ b/ocb3/src/lib.rs @@ -0,0 +1,588 @@ +#![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg" +)] +#![deny(unsafe_code)] +#![warn(missing_docs, rust_2018_idioms)] + +use core::marker::PhantomData; + +pub use aead::{ + self, generic_array::GenericArray, AeadCore, AeadInPlace, Error, KeyInit, KeySizeUser, +}; +use cipher::{ + consts::{U0, U12, U16}, + BlockDecrypt, BlockEncrypt, BlockSizeUser, +}; +use subtle::ConstantTimeEq; + +/// Constants used, reexported for convenience. +pub mod consts { + pub use cipher::consts::{U0, U12, U16}; +} + +mod util; + +use crate::util::{double, inplace_xor, ntz, Block}; + +/// Number of L values to be precomputed. Precomputing m values, allows +/// processing inputs of length up to 2^m blocks (2^m * 16 bytes) without +/// needing to calculate L values at runtime. +/// +/// By setting this to 32, we can process inputs of length up to 1 terabyte. +#[cfg(target_pointer_width = "64")] +const L_TABLE_SIZE: usize = 32; + +/// Number of L values to be precomputed. Precomputing m values, allows +/// processing inputs of length up to 2^m blocks (2^m * 16 bytes) without +/// needing to calculate L values at runtime. +#[cfg(target_pointer_width = "32")] +const L_TABLE_SIZE: usize = 16; + +/// Max associated data. +pub const A_MAX: usize = 1 << (L_TABLE_SIZE + 4); +/// Max plaintext. +pub const P_MAX: usize = 1 << (L_TABLE_SIZE + 4); +/// Max ciphertext. +pub const C_MAX: usize = 1 << (L_TABLE_SIZE + 4); + +/// OCB3 nonce +pub type Nonce = GenericArray; + +/// OCB3 tag +pub type Tag = GenericArray; + +/// Trait implemented for valid tag sizes +pub trait TagSize: private::SealedTagSize {} +impl TagSize for T {} +/// Trait implemented for valid nonce sizes +pub trait NonceSize: private::SealedNonceSize {} +impl NonceSize for T {} + +// Adapted from https://github.com/rustcrypto/AEADs/blob/2209bcaa9edc65e9a60498e7ece5b50e66f32ebf/aes-gcm/src/lib.rs#L143-L157 +mod private { + use aead::generic_array::ArrayLength; + use cipher::{consts, Unsigned}; + + // Sealed traits stop other crates from implementing any traits that use it. + pub trait SealedTagSize: ArrayLength + Unsigned {} + pub trait SealedNonceSize: ArrayLength + Unsigned {} + + // Tags are <= 128 bits + impl SealedTagSize for consts::U8 {} + impl SealedTagSize for consts::U12 {} + impl SealedTagSize for consts::U16 {} + + // Nonces are <= 120 bits + impl SealedNonceSize for consts::U12 {} +} + +/// AES-OCB3: generic over an AES implementation, nonce size, and tag size. +/// +/// WARNING: Unless absolutely necessary, prefer the aliases Aes128Ocb3 and +/// Aes256Ocb3. +#[derive(Clone)] +pub struct AesOcb3 +where + NonceSize: self::NonceSize, + TagSize: self::TagSize, +{ + cipher: Aes, + nonce_size: PhantomData, + tag_size: PhantomData, + // precomputed key-dependent variables + ll_star: Block, + ll_dollar: Block, + // list of pre-computed L values + ll: [Block; L_TABLE_SIZE], +} + +/// Output of the HASH function defined in https://www.rfc-editor.org/rfc/rfc7253.html#section-4.1 +type SumSize = U16; +type Sum = GenericArray; + +impl KeySizeUser for AesOcb3 +where + Aes: KeySizeUser, + TagSize: self::TagSize, + NonceSize: self::NonceSize, +{ + type KeySize = Aes::KeySize; +} + +impl KeyInit for AesOcb3 +where + Aes: BlockSizeUser + BlockEncrypt + KeyInit + BlockDecrypt, + TagSize: self::TagSize, + NonceSize: self::NonceSize, +{ + fn new(key: &aead::Key) -> Self { + Aes::new(key).into() + } +} + +impl AeadCore for AesOcb3 +where + NonceSize: self::NonceSize, + TagSize: self::TagSize, +{ + type NonceSize = NonceSize; + type TagSize = TagSize; + type CiphertextOverhead = U0; +} + +impl From for AesOcb3 +where + Aes: BlockSizeUser + BlockEncrypt + BlockDecrypt, + TagSize: self::TagSize, + NonceSize: self::NonceSize, +{ + fn from(cipher: Aes) -> Self { + let (ll_star, ll_dollar, ll) = key_dependent_variables(&cipher); + + Self { + cipher, + nonce_size: PhantomData, + tag_size: PhantomData, + ll_star, + ll_dollar, + ll, + } + } +} + +/// Computes key-dependent variables defined in +/// https://www.rfc-editor.org/rfc/rfc7253.html#section-4.1 +fn key_dependent_variables + BlockEncrypt>( + cipher: &Aes, +) -> (Block, Block, [Block; L_TABLE_SIZE]) { + let mut zeros = [0u8; 16]; + let ll_star = Block::from_mut_slice(&mut zeros); + cipher.encrypt_block(ll_star); + let ll_dollar = double(ll_star); + + let mut ll = [Block::default(); L_TABLE_SIZE]; + let mut ll_i = ll_dollar; + #[allow(clippy::needless_range_loop)] + for i in 0..L_TABLE_SIZE { + ll_i = double(&ll_i); + ll[i] = ll_i + } + (*ll_star, ll_dollar, ll) +} + +impl AeadInPlace for AesOcb3 +where + Aes: BlockSizeUser + BlockEncrypt + BlockDecrypt, + TagSize: self::TagSize, + NonceSize: self::NonceSize, +{ + fn encrypt_in_place_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> aead::Result> { + if (buffer.len() > P_MAX) || (associated_data.len() > A_MAX) { + unimplemented!() + } + + // First, try to process many blocks at once. + let (processed_bytes, mut offset_i, mut checksum_i) = self.wide_encrypt(nonce, buffer); + + let mut i = (processed_bytes / 16) + 1; + + // Then, process the remaining blocks. + for p_i in buffer[processed_bytes..].chunks_exact_mut(16) { + let p_i = Block::from_mut_slice(p_i); + // offset_i = offset_{i-1} xor L_{ntz(i)} + inplace_xor(&mut offset_i, &self.ll[ntz(i)]); + // checksum_i = checksum_{i-1} xor p_i + inplace_xor(&mut checksum_i, p_i); + // c_i = offset_i xor ENCIPHER(K, p_i xor offset_i) + let c_i = p_i; + inplace_xor(c_i, &offset_i); + self.cipher.encrypt_block(c_i); + inplace_xor(c_i, &offset_i); + + i += 1; + } + + // Process any partial blocks. + if (buffer.len() % 16) != 0 { + let processed_bytes = (i - 1) * 16; + let remaining_bytes = buffer.len() - processed_bytes; + + // offset_* = offset_m xor L_* + inplace_xor(&mut offset_i, &self.ll_star); + // Pad = ENCIPHER(K, offset_*) + let mut pad = Block::default(); + inplace_xor(&mut pad, &offset_i); + self.cipher.encrypt_block(&mut pad); + // checksum_* = checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) + let checksum_rhs = &mut [0u8; 16]; + checksum_rhs[..remaining_bytes].copy_from_slice(&buffer[processed_bytes..]); + checksum_rhs[remaining_bytes] = 0b1000_0000; + inplace_xor(&mut checksum_i, Block::from_slice(checksum_rhs)); + // C_* = P_* xor Pad[1..bitlen(P_*)] + let p_star = &mut buffer[processed_bytes..]; + let pad = &mut pad[..p_star.len()]; + for (aa, bb) in p_star.iter_mut().zip(pad) { + *aa ^= *bb; + } + } + + let tag = self.compute_tag(associated_data, &mut checksum_i, &offset_i); + + Ok(tag) + } + + fn decrypt_in_place_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &aead::Tag, + ) -> aead::Result<()> { + let expected_tag = self.decrypt_in_place_return_tag(nonce, associated_data, buffer); + if expected_tag.ct_eq(tag).into() { + Ok(()) + } else { + Err(Error) + } + } +} + +impl AesOcb3 +where + Aes: BlockSizeUser + BlockEncrypt + BlockDecrypt, + TagSize: self::TagSize, + NonceSize: self::NonceSize, +{ + /// Decrypts in place and returns expected tag. + pub(crate) fn decrypt_in_place_return_tag( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> aead::Tag { + if (buffer.len() > C_MAX) || (associated_data.len() > A_MAX) { + unimplemented!() + } + + // First, try to process many blocks at once. + let (processed_bytes, mut offset_i, mut checksum_i) = self.wide_decrypt(nonce, buffer); + + let mut i = (processed_bytes / 16) + 1; + + // Then, process the remaining blocks. + for c_i in buffer[processed_bytes..].chunks_exact_mut(16) { + let c_i = Block::from_mut_slice(c_i); + // offset_i = offset_{i-1} xor L_{ntz(i)} + inplace_xor(&mut offset_i, &self.ll[ntz(i)]); + // p_i = offset_i xor DECIPHER(K, c_i xor offset_i) + let p_i = c_i; + inplace_xor(p_i, &offset_i); + self.cipher.decrypt_block(p_i); + inplace_xor(p_i, &offset_i); + // checksum_i = checksum_{i-1} xor p_i + inplace_xor(&mut checksum_i, p_i); + + i += 1; + } + + // Process any partial blocks. + if (buffer.len() % 16) != 0 { + let processed_bytes = (i - 1) * 16; + let remaining_bytes = buffer.len() - processed_bytes; + + // offset_* = offset_m xor L_* + inplace_xor(&mut offset_i, &self.ll_star); + // Pad = ENCIPHER(K, offset_*) + let mut pad = Block::default(); + inplace_xor(&mut pad, &offset_i); + self.cipher.encrypt_block(&mut pad); + // P_* = C_* xor Pad[1..bitlen(C_*)] + let c_star = &mut buffer[processed_bytes..]; + let pad = &mut pad[..c_star.len()]; + for (aa, bb) in c_star.iter_mut().zip(pad) { + *aa ^= *bb; + } + // checksum_* = checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) + let checksum_rhs = &mut [0u8; 16]; + checksum_rhs[..remaining_bytes].copy_from_slice(&buffer[processed_bytes..]); + checksum_rhs[remaining_bytes] = 0b1000_0000; + inplace_xor(&mut checksum_i, Block::from_slice(checksum_rhs)); + } + + self.compute_tag(associated_data, &mut checksum_i, &offset_i) + } + + /// Encrypts plaintext in groups of two. + /// + /// Adapted from https://www.cs.ucdavis.edu/~rogaway/ocb/news/code/ocb.c + fn wide_encrypt(&self, nonce: &Nonce, buffer: &mut [u8]) -> (usize, Block, Block) { + const WIDTH: usize = 2; + let split_into_blocks = crate::util::split_into_two_blocks; + + let mut i = 1; + + let mut offset_i = [Block::default(); WIDTH]; + offset_i[offset_i.len() - 1] = initial_offset(&self.cipher, nonce, TagSize::to_u32()); + let mut checksum_i = Block::default(); + for wide_blocks in buffer.chunks_exact_mut(16 * WIDTH) { + let p_i = split_into_blocks(wide_blocks); + + // checksum_i = checksum_{i-1} xor p_i + for p_ij in &p_i { + inplace_xor(&mut checksum_i, p_ij); + } + + // offset_i = offset_{i-1} xor L_{ntz(i)} + offset_i[0] = offset_i[offset_i.len() - 1]; + inplace_xor(&mut offset_i[0], &self.ll[ntz(i)]); + for j in 1..p_i.len() { + offset_i[j] = offset_i[j - 1]; + inplace_xor(&mut offset_i[j], &self.ll[ntz(i + j)]); + } + + // c_i = offset_i xor ENCIPHER(K, p_i xor offset_i) + for j in 0..p_i.len() { + inplace_xor(p_i[j], &offset_i[j]); + self.cipher.encrypt_block(p_i[j]); + inplace_xor(p_i[j], &offset_i[j]) + } + + i += WIDTH; + } + + let processed_bytes = (buffer.len() / (WIDTH * 16)) * (WIDTH * 16); + + (processed_bytes, offset_i[offset_i.len() - 1], checksum_i) + } + + /// Decrypts plaintext in groups of two. + /// + /// Adapted from https://www.cs.ucdavis.edu/~rogaway/ocb/news/code/ocb.c + fn wide_decrypt(&self, nonce: &Nonce, buffer: &mut [u8]) -> (usize, Block, Block) { + const WIDTH: usize = 2; + let split_into_blocks = crate::util::split_into_two_blocks; + + let mut i = 1; + + let mut offset_i = [Block::default(); WIDTH]; + offset_i[offset_i.len() - 1] = initial_offset(&self.cipher, nonce, TagSize::to_u32()); + let mut checksum_i = Block::default(); + for wide_blocks in buffer.chunks_exact_mut(16 * WIDTH) { + let c_i = split_into_blocks(wide_blocks); + + // offset_i = offset_{i-1} xor L_{ntz(i)} + offset_i[0] = offset_i[offset_i.len() - 1]; + inplace_xor(&mut offset_i[0], &self.ll[ntz(i)]); + for j in 1..c_i.len() { + offset_i[j] = offset_i[j - 1]; + inplace_xor(&mut offset_i[j], &self.ll[ntz(i + j)]); + } + + // p_i = offset_i xor DECIPHER(K, c_i xor offset_i) + // checksum_i = checksum_{i-1} xor p_i + for j in 0..c_i.len() { + inplace_xor(c_i[j], &offset_i[j]); + self.cipher.decrypt_block(c_i[j]); + inplace_xor(c_i[j], &offset_i[j]); + inplace_xor(&mut checksum_i, c_i[j]); + } + + i += WIDTH; + } + + let processed_bytes = (buffer.len() / (WIDTH * 16)) * (WIDTH * 16); + + (processed_bytes, offset_i[offset_i.len() - 1], checksum_i) + } +} + +/// Computes nonce-dependent variables as defined +/// in https://www.rfc-editor.org/rfc/rfc7253.html#section-4.2 +/// +/// Assumes a 96-bit nonce and 128-bit tag. +fn nonce_dependent_variables< + Aes: BlockSizeUser + BlockEncrypt, + NonceSize: self::NonceSize, +>( + cipher: &Aes, + nn: &Nonce, + tag_len: u32, +) -> (usize, [u8; 24]) { + let mut nonce = [0u8; 16]; + nonce[4..16].copy_from_slice(nn.as_slice()); + let mut nonce = u128::from_be_bytes(nonce); + // Nonce = num2str(TAGLEN mod 128,7) || zeros(120-bitlen(N)) || 1 || N + nonce |= 1 << 96; + match tag_len { + 16 => {} + x if x < 16 => { + nonce |= (u128::from(tag_len) * 8) << (128 - 7); + } + _ => unreachable!(), + } + + // Separate the last 6 bits into `bottom`, and the rest into `top`. + let bottom = usize::try_from(nonce & 0b111111).unwrap(); + let top = nonce & !0b111111; + + let mut ktop = Block::from(top.to_be_bytes()); + cipher.encrypt_block(&mut ktop); + let ktop = ktop.as_mut_slice(); + + // stretch = Ktop || (Ktop[1..64] xor Ktop[9..72]) + let mut stretch = [0u8; 24]; + stretch[..16].copy_from_slice(ktop); + for i in 0..8 { + ktop[i] ^= ktop[i + 1]; + } + stretch[16..].copy_from_slice(&ktop[..8]); + + (bottom, stretch) +} + +/// Computes the initial offset as defined +/// in https://www.rfc-editor.org/rfc/rfc7253.html#section-4.2 +/// +/// Assumes a 96-bit nonce and 128-bit tag. +fn initial_offset< + Aes: BlockSizeUser + BlockEncrypt, + NonceSize: self::NonceSize, +>( + cipher: &Aes, + nn: &Nonce, + tag_size: u32, +) -> Block { + let (bottom, stretch) = nonce_dependent_variables(cipher, nn, tag_size); + let stretch_low = u128::from_be_bytes((&stretch[..16]).try_into().unwrap()); + let stretch_hi = u64::from_be_bytes((&stretch[16..24]).try_into().unwrap()); + let stretch_hi = u128::from(stretch_hi); + + // offset_0 = stretch[1+bottom..128+bottom] + let offset = (stretch_low << bottom) | (stretch_hi >> (64 - bottom)); + offset.to_be_bytes().into() +} + +impl AesOcb3 +where + Aes: BlockSizeUser + BlockEncrypt, + TagSize: self::TagSize, + NonceSize: self::NonceSize, +{ + /// Computes HASH function defined in https://www.rfc-editor.org/rfc/rfc7253.html#section-4.1 + fn hash(&self, associated_data: &[u8]) -> Sum { + let mut offset_i = Block::default(); + let mut sum_i = Block::default(); + + let mut i = 1; + for a_i in associated_data.chunks_exact(16) { + // offset_i = offset_{i-1} xor L_{ntz(i)} + inplace_xor(&mut offset_i, &self.ll[ntz(i)]); + // Sum_i = Sum_{i-1} xor ENCIPHER(K, A_i xor offset_i) + let mut a_i = *Block::from_slice(a_i); + inplace_xor(&mut a_i, &offset_i); + self.cipher.encrypt_block(&mut a_i); + inplace_xor(&mut sum_i, &a_i); + + i += 1; + } + + // Process any partial blocks. + if (associated_data.len() % 16) != 0 { + let processed_bytes = (i - 1) * 16; + let remaining_bytes = associated_data.len() - processed_bytes; + + // offset_* = offset_m xor L_* + inplace_xor(&mut offset_i, &self.ll_star); + // CipherInput = (A_* || 1 || zeros(127-bitlen(A_*))) xor offset_* + let cipher_input = &mut [0u8; 16]; + cipher_input[..remaining_bytes].copy_from_slice(&associated_data[processed_bytes..]); + cipher_input[remaining_bytes] = 0b1000_0000; + let cipher_input = Block::from_mut_slice(cipher_input); + inplace_xor(cipher_input, &offset_i); + // Sum = Sum_m xor ENCIPHER(K, CipherInput) + self.cipher.encrypt_block(cipher_input); + inplace_xor(&mut sum_i, cipher_input); + } + + sum_i + } + + fn compute_tag( + &self, + associated_data: &[u8], + checksum_m: &mut Block, + offset_m: &Block, + ) -> Tag { + // Tag = ENCIPHER(K, checksum_m xor offset_m xor L_$) xor HASH(K,A) + let full_tag = checksum_m; + inplace_xor(full_tag, offset_m); + inplace_xor(full_tag, &self.ll_dollar); + self.cipher.encrypt_block(full_tag); + inplace_xor(full_tag, &self.hash(associated_data)); + + // truncate the tag to the required length + Tag::clone_from_slice(&full_tag[..TagSize::to_usize()]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn double_basic_test() { + let zero = Block::from(hex!("00000000000000000000000000000000")); + assert_eq!(zero, double(&zero)); + let one = Block::from(hex!("00000000000000000000000000000001")); + let two = Block::from(hex!("00000000000000000000000000000002")); + assert_eq!(two, double(&one)); + } + + #[test] + fn rfc7253_key_dependent_constants() { + // Test vector from page 17 of https://www.rfc-editor.org/rfc/rfc7253.html + let key = hex!("000102030405060708090A0B0C0D0E0F"); + let expected_ll_star = Block::from(hex!("C6A13B37878F5B826F4F8162A1C8D879")); + let expected_ll_dollar = Block::from(hex!("8D42766F0F1EB704DE9F02C54391B075")); + let expected_ll0 = Block::from(hex!("1A84ECDE1E3D6E09BD3E058A8723606D")); + let expected_ll1 = Block::from(hex!("3509D9BC3C7ADC137A7C0B150E46C0DA")); + + let cipher = aes::Aes128::new(GenericArray::from_slice(&key)); + let (ll_star, ll_dollar, ll) = key_dependent_variables(&cipher); + + assert_eq!(ll_star, expected_ll_star); + assert_eq!(ll_dollar, expected_ll_dollar); + assert_eq!(ll[0], expected_ll0); + assert_eq!(ll[1], expected_ll1); + } + + #[test] + fn rfc7253_nonce_dependent_constants() { + // Test vector from page 17 of https://www.rfc-editor.org/rfc/rfc7253.html + let key = hex!("000102030405060708090A0B0C0D0E0F"); + let nonce = hex!("BBAA9988776655443322110F"); + let expected_bottom = usize::try_from(15).unwrap(); + let expected_stretch = hex!("9862B0FDEE4E2DD56DBA6433F0125AA2FAD24D13A063F8B8"); + let expected_offset_0 = Block::from(hex!("587EF72716EAB6DD3219F8092D517D69")); + + const TAGLEN: u32 = 16; + + let cipher = aes::Aes128::new(GenericArray::from_slice(&key)); + let (bottom, stretch) = nonce_dependent_variables(&cipher, &Nonce::from(nonce), TAGLEN); + let offset_0 = initial_offset(&cipher, &Nonce::from(nonce), TAGLEN); + + assert_eq!(bottom, expected_bottom); + assert_eq!(stretch, expected_stretch); + assert_eq!(offset_0, expected_offset_0); + } +} diff --git a/ocb3/src/util.rs b/ocb3/src/util.rs new file mode 100644 index 00000000..67574f1e --- /dev/null +++ b/ocb3/src/util.rs @@ -0,0 +1,44 @@ +use aead::generic_array::{typenum::U16, ArrayLength, GenericArray}; + +const BLOCK_SIZE: usize = 16; +pub(crate) type Block = GenericArray; + +#[inline] +pub(crate) fn inplace_xor(a: &mut GenericArray, b: &GenericArray) +where + U: ArrayLength, + T: core::ops::BitXor + Copy, +{ + for (aa, bb) in a.as_mut_slice().iter_mut().zip(b.as_slice()) { + *aa = *aa ^ *bb; + } +} + +/// Doubles a block, in GF(2^128). +/// +/// Adapted from https://github.com/RustCrypto/universal-hashes/blob/9b0ac5d1/polyval/src/mulx.rs#L5-L18 +#[inline] +pub(crate) fn double(block: &Block) -> Block { + let mut v = u128::from_be_bytes((*block).into()); + let v_hi = v >> 127; + + // If v_hi = 0, return (v << 1) + // If v_hi = 1, return (v << 1) xor (0b0...010000111) + v <<= 1; + v ^= v_hi ^ (v_hi << 1) ^ (v_hi << 2) ^ (v_hi << 7); + v.to_be_bytes().into() +} + +/// Counts the number of non-trailing zeros in the binary representation. +/// +/// Defined in https://www.rfc-editor.org/rfc/rfc7253.html#section-2 +#[inline] +pub(crate) fn ntz(n: usize) -> usize { + n.trailing_zeros().try_into().unwrap() +} + +#[inline] +pub(crate) fn split_into_two_blocks(two_blocks: &mut [u8]) -> [&mut Block; 2] { + let (b0, b1) = two_blocks.split_at_mut(BLOCK_SIZE); + [b0.into(), b1.into()] +} diff --git a/ocb3/tests/data/rfc7253_ocb_aes.blb b/ocb3/tests/data/rfc7253_ocb_aes.blb new file mode 100644 index 00000000..3f02fdee Binary files /dev/null and b/ocb3/tests/data/rfc7253_ocb_aes.blb differ diff --git a/ocb3/tests/kats.rs b/ocb3/tests/kats.rs new file mode 100644 index 00000000..21d94fdb --- /dev/null +++ b/ocb3/tests/kats.rs @@ -0,0 +1,108 @@ +#![allow(non_snake_case)] + +use aead::{ + consts::{U12, U8}, + AeadInPlace, KeyInit, +}; +use aes::{Aes128, Aes192, Aes256}; +use hex_literal::hex; +use ocb3::{AesOcb3, GenericArray}; + +// Test vectors from https://www.rfc-editor.org/rfc/rfc7253.html#appendix-A +aead::new_test!(rfc7253_ocb_aes, "rfc7253_ocb_aes", Aes128Ocb3); + +fn num2str96(num: usize) -> [u8; 12] { + let num: u32 = num.try_into().unwrap(); + let mut out = [0u8; 12]; + out[8..12].copy_from_slice(&num.to_be_bytes()); + out +} + +/// Test vectors from Page 18 of https://www.rfc-editor.org/rfc/rfc7253.html#appendix-A +macro_rules! rfc7253_wider_variety { + ($ocb:tt, $keylen:tt, $taglen:expr, $expected:expr) => { + let mut key_bytes = vec![0u8; $keylen]; + key_bytes[$keylen - 1] = 8 * $taglen; // taglen in bytes + + let key = GenericArray::from_slice(key_bytes.as_slice()); + let ocb = $ocb::new(key); + + let mut ciphertext = Vec::new(); + + for i in 0..128 { + // S = zeros(8i) + let S = vec![0u8; i]; + + // N = num2str(3i+1,96) + // C = C || OCB-ENCRYPT(K,N,S,S) + let N = num2str96(3 * i + 1); + let mut buffer = S.clone(); + let tag = ocb + .encrypt_in_place_detached(N.as_slice().into(), &S, &mut buffer) + .unwrap(); + ciphertext.append(&mut buffer); + ciphertext.append(&mut tag.as_slice().to_vec()); + + // N = num2str(3i+2,96) + // C = C || OCB-ENCRYPT(K,N,,S) + let N = num2str96(3 * i + 2); + let mut buffer = S.clone(); + let tag = ocb + .encrypt_in_place_detached(N.as_slice().into(), &[], &mut buffer) + .unwrap(); + ciphertext.append(&mut buffer); + ciphertext.append(&mut tag.as_slice().to_vec()); + + // N = num2str(3i+3,96) + // C = C || OCB-ENCRYPT(K,N,S,) + let N = num2str96(3 * i + 3); + let tag = ocb + .encrypt_in_place_detached(N.as_slice().into(), &S, &mut []) + .unwrap(); + ciphertext.append(&mut tag.as_slice().to_vec()); + } + if $taglen == 16 { + assert_eq!(ciphertext.len(), 22_400); + } else if $taglen == 12 { + assert_eq!(ciphertext.len(), 20_864); + } else if $taglen == 8 { + assert_eq!(ciphertext.len(), 19_328); + } else { + unreachable!(); + } + + // N = num2str(385,96) + // Output : OCB-ENCRYPT(K,N,C,) + let N = num2str96(385); + let tag = ocb + .encrypt_in_place_detached(N.as_slice().into(), &ciphertext, &mut []) + .unwrap(); + + assert_eq!(tag.as_slice(), hex!($expected)) + }; +} + +// More types for testing +type Aes192Ocb3 = AesOcb3; +type Aes128Ocb3Tag96 = AesOcb3; +type Aes192Ocb3Tag96 = AesOcb3; +type Aes256Ocb3Tag96 = AesOcb3; +type Aes128Ocb3Tag64 = AesOcb3; +type Aes192Ocb3Tag64 = AesOcb3; +type Aes256Ocb3Tag64 = AesOcb3; +type Aes128Ocb3 = AesOcb3; +type Aes256Ocb3 = AesOcb3; + +/// Test vectors from Page 18 of https://www.rfc-editor.org/rfc/rfc7253.html#appendix-A +#[test] +fn rfc7253_more_sample_results() { + rfc7253_wider_variety!(Aes128Ocb3, 16, 16, "67E944D23256C5E0B6C61FA22FDF1EA2"); + rfc7253_wider_variety!(Aes192Ocb3, 24, 16, "F673F2C3E7174AAE7BAE986CA9F29E17"); + rfc7253_wider_variety!(Aes256Ocb3, 32, 16, "D90EB8E9C977C88B79DD793D7FFA161C"); + rfc7253_wider_variety!(Aes128Ocb3Tag96, 16, 12, "77A3D8E73589158D25D01209"); + rfc7253_wider_variety!(Aes192Ocb3Tag96, 24, 12, "05D56EAD2752C86BE6932C5E"); + rfc7253_wider_variety!(Aes256Ocb3Tag96, 32, 12, "5458359AC23B0CBA9E6330DD"); + rfc7253_wider_variety!(Aes128Ocb3Tag64, 16, 8, "192C9B7BD90BA06A"); + rfc7253_wider_variety!(Aes192Ocb3Tag64, 24, 8, "0066BC6E0EF34E24"); + rfc7253_wider_variety!(Aes256Ocb3Tag64, 32, 8, "7D4EA5D445501CBE"); +}