diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f15ad65d83e..b42106737a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,7 +141,9 @@ jobs: args: cargo-udeps - name: Run cargo udeps - run: cargo udeps --workspace --all-targets + # we only use openssl when the openssl-benchmarks feature is enabled. + # openssl is a dev-dependency so it can't be optional. + run: cargo udeps --workspace --all-targets --features openssl-benchmarks env: RUSTC_WRAPPER: "" diff --git a/Cargo.toml b/Cargo.toml index 5d0f69e8989..1577b22e1bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "aws-lc-sys", "aws-lc-fips-sys" ] +resolver = "2" [profile.bench] lto = true diff --git a/aws-lc-rs/Cargo.toml b/aws-lc-rs/Cargo.toml index f0fcbb8905b..b58c39ed9a9 100644 --- a/aws-lc-rs/Cargo.toml +++ b/aws-lc-rs/Cargo.toml @@ -28,6 +28,7 @@ default = ["aws-lc-sys", "alloc", "ring-io", "ring-sig-verify"] ring-io = ["dep:untrusted"] ring-sig-verify = ["dep:untrusted"] ring-benchmarks = [] +openssl-benchmarks = [] bindgen = ["aws-lc-sys?/bindgen", "aws-lc-fips-sys?/bindgen"] asan = ["aws-lc-sys?/asan", "aws-lc-fips-sys?/asan"] @@ -46,11 +47,13 @@ mirai-annotations = "1.12.0" [dev-dependencies] paste = "1.0" -criterion = { version = "0.5.0", features = ["csv_output"]} +criterion = { version = "0.5.0", features = ["csv_output"] } ring = "0.16" regex = "1.6.0" lazy_static = "1.4.0" -clap = {version = "4.1.8", features = ["derive"]} +clap = { version = "4.1.8", features = ["derive"] } +openssl = { version = "0.10.52", features = ["vendored"] } +hex = "0.4.3" [[bench]] name = "aead_benchmark" @@ -91,3 +94,7 @@ harness = false [[bench]] name = "agreement_benchmark" harness = false + +[[bench]] +name = "cipher_benchmark" +harness = false diff --git a/aws-lc-rs/benches/cipher_benchmark.rs b/aws-lc-rs/benches/cipher_benchmark.rs new file mode 100644 index 00000000000..d8a3bdeff4a --- /dev/null +++ b/aws-lc-rs/benches/cipher_benchmark.rs @@ -0,0 +1,158 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +use aws_lc_rs::cipher::{ + CipherContext, DecryptingKey, EncryptingKey, OperatingMode, PaddedBlockDecryptingKey, + PaddedBlockEncryptingKey, UnboundCipherKey, AES_128, AES_256, +}; +use aws_lc_rs::{test, test_file}; +use criterion::{criterion_group, criterion_main, Criterion}; + +macro_rules! openssl_bench { + ($group:ident, $openssl: expr, $key:ident, $iv:ident, $data:ident) => {{ + #[cfg(feature = "openssl-benchmarks")] + $group.bench_function("OpenSSL", |b| { + use openssl::symm::Cipher; + b.iter(|| { + use openssl::symm::{decrypt, encrypt}; + let data = encrypt($openssl, &$key, Some(&$iv), &$data).unwrap(); + let _ = decrypt($openssl, &$key, Some(&$iv), data.as_ref()).unwrap(); + }) + }); + }}; +} + +macro_rules! benchmark_padded { + ($fn:ident, $test:literal, $file:literal, $awslc:expr, $mode:expr, $openssl:expr) => { + fn $fn(c: &mut Criterion) { + test::run(test_file!($file), |_section, test_case| { + let key_bytes = test_case.consume_bytes("KEY"); + let iv = test_case.consume_bytes("IV"); + let data = test_case.consume_bytes("IN"); + + let mut group = c.benchmark_group(format!("{}-{}-bytes", $test, data.len())); + + group.bench_function("AWS-LC", |b| { + b.iter(|| { + let key = UnboundCipherKey::new($awslc, &key_bytes).unwrap(); + let iv: CipherContext = + CipherContext::Iv128(iv.as_slice().try_into().unwrap()); + + let encrypt_key = match $mode { + OperatingMode::CBC => PaddedBlockEncryptingKey::cbc_pkcs7(key), + _ => unreachable!(), + } + .unwrap(); + + let mut in_out = Vec::from(data.as_slice()); + let context = encrypt_key.less_safe_encrypt(&mut in_out, iv).unwrap(); + + let key = UnboundCipherKey::new($awslc, &key_bytes).unwrap(); + + let decrypt_key = match $mode { + OperatingMode::CBC => PaddedBlockDecryptingKey::cbc_pkcs7(key), + _ => unreachable!(), + } + .unwrap(); + + let _ = decrypt_key.decrypt(&mut in_out, context).unwrap(); + }) + }); + + openssl_bench!(group, $openssl, key_bytes, iv, data); + + Ok(()) + }); + } + }; +} + +macro_rules! benchmark_unpadded { + ($fn:ident, $test:literal, $file:literal, $awslc:expr, $mode:expr, $openssl:expr) => { + fn $fn(c: &mut Criterion) { + test::run(test_file!($file), |_section, test_case| { + let key_bytes = test_case.consume_bytes("KEY"); + let iv = test_case.consume_bytes("IV"); + let data = test_case.consume_bytes("IN"); + + let mut group = c.benchmark_group(format!("{}-{}-bytes", $test, data.len())); + + group.bench_function("AWS-LC", |b| { + b.iter(|| { + let key = UnboundCipherKey::new($awslc, &key_bytes).unwrap(); + let iv: CipherContext = + CipherContext::Iv128(iv.as_slice().try_into().unwrap()); + + let encrypt_key = match $mode { + OperatingMode::CTR => EncryptingKey::ctr(key), + _ => unreachable!(), + } + .unwrap(); + + let mut in_out = Vec::from(data.as_slice()); + let context = encrypt_key.less_safe_encrypt(&mut in_out, iv).unwrap(); + + let key = UnboundCipherKey::new($awslc, &key_bytes).unwrap(); + + let decrypt_key = match $mode { + OperatingMode::CTR => DecryptingKey::ctr(key), + _ => unreachable!(), + } + .unwrap(); + + let _ = decrypt_key.decrypt(&mut in_out, context).unwrap(); + }) + }); + + openssl_bench!(group, $openssl, key_bytes, iv, data); + + Ok(()) + }); + } + }; +} + +benchmark_unpadded!( + test_aes_128_ctr, + "AES-128-CTR", + "data/cipher_aes_128_ctr.txt", + &AES_128, + OperatingMode::CTR, + Cipher::aes_128_ctr() +); + +benchmark_unpadded!( + test_aes_256_ctr, + "AES-256-CTR", + "data/cipher_aes_256_ctr.txt", + &AES_256, + OperatingMode::CTR, + Cipher::aes_256_ctr() +); + +benchmark_padded!( + test_aes_128_cbc, + "AES-128-CBC", + "data/cipher_aes_128_cbc.txt", + &AES_128, + OperatingMode::CBC, + Cipher::aes_128_cbc() +); + +benchmark_padded!( + test_aes_256_cbc, + "AES-256-CBC", + "data/cipher_aes_256_cbc.txt", + &AES_256, + OperatingMode::CBC, + Cipher::aes_256_cbc() +); + +criterion_group!( + benches, + test_aes_128_ctr, + test_aes_128_cbc, + test_aes_256_ctr, + test_aes_256_cbc +); +criterion_main!(benches); diff --git a/aws-lc-rs/benches/data/cipher_aes_128_cbc.txt b/aws-lc-rs/benches/data/cipher_aes_128_cbc.txt new file mode 100644 index 00000000000..f43a97b7d57 --- /dev/null +++ b/aws-lc-rs/benches/data/cipher_aes_128_cbc.txt @@ -0,0 +1,14 @@ +# 1 block +KEY = d1ae485dbe0d98ae4eff24ae075a5d28 +IV = a7cbf70ede88eb876887c9e7ed60a108 +IN = 6d00bced19251c9d4c9a8ee7ead11881 + +# 16 blocks +KEY = 78f094594b7ee4194bc2baed0562856e +IV = 5591c315865d3b312292be1c8b3babdf +IN = 98b4eae2434eb203583d837f90b17d92c5aaf51cb6d160566a691d1e254067e1a038416723ab31036775e560d6c9d692a1d083dc66b348f1a47fd69c5a890044345d9c0acad8db746280b42fdd17cd07a2c7df684d979d06eb41df6b5dabf1ff6d64ad54462966dfbb1dc8d3d19085b9f9b85f2892ddce92f0b4da4fd9d97d60d9ef171f27a895bc2e00aaa7532a1230536998f246e005688e698eb7edecf05bfda05b93ac63f7eabff0296d9442a0a0c3942985b86e2b5f3a38df65be6fc8ee690ecadab6acc3b75bc4580f54101bcedf6c131081faab8c3e8a322e252260dada51e63dfe470d0d0199d2e9c2f50a77a48d382e3986c26d0db8915ef2e25e28 + +# 256 blocks +KEY = 4da3482c72e5eec455e919054118103a +IV = a4a809574a1a9575b4256cc704a53e46 +IN = a606b6054c433bf7b052aa96b5438ced6675050987c7f9036bb9bc261e5c422c05b155c85bf319f31cef15370f8adb6b574484bfa61cbc203b2875c9d86fae358ac3a59db1230f0e614d68df10ad53077da9b609996f7fdcca9ccc3ca32c1474e563cbb6c97d8113b5f6591f72ac42ac9cdf76d4660a29b5d9b2c8157bd83ce02eb895a8b300a61f886c5d4ca7d07a7166cbd0b9d2ad818e0b1e6ae86ce17b15b67a468446268bf23f61f4fab478d1dee9443ba82b981b5f133561a3019da21b86d848530e6bfaa874d8866cab995ab7a944acac71bb1b7e398dd94a7d69f25f692a2824dcb2354e846d561d4a13d89cd69cd148e4e14d4169da0334b17664b7910f3ad5ab71ba45549fcfdad84da05795b15d6256c99110a5585584230b343ed08990a8b3033da1a07bc47de455548456b4b95f78761482811cbdf39ea35231eebf1f0792667302a269ac534d4a52b1b1927c245b566c9181002db0f6eaf22293196713e514311a658dc616e2ec4f9d8790368e627cf0b741be2a346700ae5456496464162e496ebbc19801059c9c53da709cc74687ecd5fa3d3ef740642f8740a630ff65f9b7b391c6def821e0f01e3c70503846e15e1d53204978a7d85a4eb8c3b654e3d3a7719b8661396235a3455a52de6f3e29c3333dd430eb166ad835d26cfa3be41bd3c3cafdd2bf1956447bbec205cf254f906569fd8bcf5ebaa3d3445f21938b3314e132fbbbb796a3e9abd393277650d5061833e12d6f009baf6543e0bab99be09b39975caff634c5988263e95e1861231b54228d93b3709ea17e29f0614d77da6578b9807b0e8f603f81417ddfe64936b5a337bf8e6055658d58a8680d07d9439fd453274d58dcd4cf6a5eb6a21e96dbdec9d534ee803f3ad830e58d18836ea51964098acd9af4bade9c473387288ce7ef9a73b843e272904e1bd1629e941be8590bb0c254202f93035a9d1708214a5fa0c66cce0f7db2084f63c06de42aab893a587af9b232d2dbdbbc1c85e7005845ad6de9b04ec002743392a4a6282132da9209bd5df5adf4358ac914f83186a58386629c6b1e3032ce2be4454ba7cbff15bc340fd14a51da3b54cd35ea1593b2cabd32004f45f5ae4ec6c7ed79d3d9c512d7299ccf4466958bf379211dffb90b2d8f3737dcf358f659880184707cacbff2184c39134d874fc85c21aabe6fab711f18170b0e5fd38838e19c64b77a6709612f4cd8ada37d31b5ec01347e3b7f777d0a4eb0b7f70094f6856b5bee20118cbd982d6fbf90831b73a4eeabade1243cc52a440f735daf53a6eb12d354faba5938e7e41c6c9d79bbb63d44e1b766659cf7f20d9b2b45fe66ca8fc294f191a0e5ecf7c189afab57687bc599534d5bdcc001c145d0a852e0954d5318e76350dea1afc84a274faad3eb24cafb70e5073fe214a83c8251be1a9e106d86149f569a9ef9aa1f2d473bcb06072a6cb994f400175c788b431f44f7e70f0d7563a0850111f9835b3553d138436e60e2f45a1d147bc6c111447faa79001b05b70f11919ddb1a5681a74c8e08aa892d5a1af635305e18d67e116d26662ca92072be5b9a85cd9da0b97d47f2019b12daa6a5b03f18569857a42e139a5c59cb8b876b0a6965383b4b067d2eb9d3d382b5f96c05d1747eeb39c42815f028698bb94b45fbf501104bb5127ac9213c505460e8598a099bdf2ba9f6c7afca91d34125d642a1fc9746b685ea3adcb7ad49b9ceafda3cef3f0254a28c4000611780946c5de2255af12da37e983c2b41b92ddffd59de694aa79ddc18f49f56ec43bc138c21095d9b6cd82607679a386902957c240c8e4d9196030c2798685bc78b4abbcb823eaeed99995b009bd45c2d667c7652f0d65cf781a1ae5d7348412436f78410fd0b6caf9a80caeb3e9ca2a11345399b37111cd0c579cd24ddf86652634ad6cb1b2fda3e1a194f695e2d1c3247cee2b8a88e6d373d3ac6d29b594306b749ae74b0507e4c70e29536e0a6dc5b5e85c25936117b3e6d0eee31c6685b2e45d152f74de49f82820338653dd2bd43224bfe5f0c94247a1c8e13a03bc0b9eaec114789ca4e4939fd267d261377cafe12e5adadb5df05be0555c11a78b38e454bcbf5dfe263aade057224c0aa7632a4a4ca17f9c97d983d261204a798dcf651d60e0b93f3b4337aa06c793c812fa088ebc9b07df4ca79a3105a33d3b3706c468f258c2a6b7b4a2f4fa1d589eb795c4a0b325b963cbc99eb14a23b5657f9717a6608631bbff1b94337d0c66fe7cdb391476d7c14180b538d83df38086640ff48b1e3f4c7048cef1970df7c43af5c9d586d0cdef4cf51f96aa4539b7d9608261863949e57781b6148b4892a1f42e0169e1486428a8ef5bdb0bf1a00e27c79ae2740e601c54bf5e7c9c96f82a7df1f437ea1d423170617c02751959cb25bbe310ef28ee26718580f85a454e2fb35215b3c52422f69109549c49aa3e4b4e126f2a8a38cc79c4aee2e900cc5073d1a590ada05cdd4a84c6c56247fedb47efe1b8aa33bce4e616623d7e1b250c242e6cd763250b1623db116fce701f46ddb7c9d63bf3ddb173961ae354194c3d2674a228ae977f7de78f7517e0e5612272b6dfc627dfa01f7cc0f4d6c3b5d591bb3c80f77eb23e9d2385666efe0c7fc98ce3fa02c24bc6209bd6db3ba5976d443ff106a41827b156a37a7afc62c916972a410a0ad7cb33188e7db331ec7fb51bdc1fb5537aedc0d1476e1d71adf060439f57a1b83a5af191b0e12ca533b0348e997dce20798fdd2d01c5424c7d606b435c8141578210447d6dec63b06739722a138f0fd2bcdf6cc455b175b07fe0e96c7003662ad71b047884205ce1a83e9a96b9acfa2d837a1b17b4ad865b778646517a33a9515b09b7659f932d7cc660eb5bc39805ed8b1831d72365d772f7875259da722a18274010483df24868c8347cc1dcc67d25fa148a355c0bcdeac84f5aad0af892b24df2928059508c98003ed734ab6eeaeac67060e992c2c1f991247439b514ca3ebe038a4adf04d5d6514e17bbd9ae58c160eaa2a8606d1f9e4dc6e03f48c2c94230f602cfb2c5b285d5ac0d00bdd8392302cfce89fca520e2c91f6b870028a2850b8da2ef86c1088a61a3780bc0f3eca4b28bf84842e455c320b256c26c2b89a03cb9e73821a1014008a175793d35f60641c972352bd3851ec88246960a18672a68dc062d97009f3d34e194c32c124b22e966b63d199e102508b6e23e9d7b0ea38e5562a4198688162219bd70a1b4eb2f77077e6d1b5cc4556a7b9825cd4528e9f96fb7fc65b247baabe903c9de016642f5cd71de47f9c605e985973a3bb0699f54406e66606836d9d7c8b707c8964b59a0f963e0d5f6cc23c05f52b49118d8fcb4e488381104435583ce71583b285c02e4ceff41c81a7b9639d4d0d40dc1f4fca3ff58406426a1aa9a026bd258b6e0f103f1a0850d289b3126afaf17cdaaf5ace15c735363155f4b1c869d6421d015ad499bc9081891686542fb9ddb6f58c3c69e23ace91d75685e93161bf80ff3cb4f3dc946d94b4cece9ececbc9a20975543c7f358346a028d774c811d72ae68da1fad92e6cc6aa497c6365f2003f8e4754faafe1a7b48e626e5613ffe880c404ad0a8104186ccc5c4392f94316f437db2d0bfe4c68394ce764377e810c8a3c9b938c8f4b7727fd1d067f3b38490663ca3183c453cbe760cb0a92d236dd153f51f45b2193437b3340f8a6a44ff30dfbb7e3a57dec7cd80b3288797147e95bfb821c84a7401595f0521c20337a9e9e31714b0b4b7c4708604db81ae0b5755b6c04491d81023ac7f6a32b8f92e46cc339922f381dd6e475d0a43c03707fd2b5796e272d2977569e1ecf43fdc98a41dcd017e10abd0b9ce75ca0fcf3d01b777c765dc6763cfee650802e1c50f04f9995b0b742100fcb6bb955e27974607877207c48df18ae3dc424900c30ac2ac84c7c89594211a3117d53f68c39f7456eb51cace912c7c592580e087bffd86d6c80e89d9d605bbbc29f4197b46136d04ca4d78415b308ec3800867b6abff0bc610a75295c3833e6eb6cd7de5b40e21d083228a9eb0293809061ab63810d035904f75575bcc3c229e37c6fbdde651f3a77bb99998ac6234cfdfd04b6e0e90082387798fff8497de30e66183cd545ac1964857ee1bd277d8594fec991ca9429abc0393c307187dd41e3eb9bffce0b6882c85de6d68b2638b1061c534effd25f7bc702ba2e109fa78ff5e45e308e2e45df1b9846fe3694ee7e2a7c1cb82b9d2b0f0b2e86c69acad5b98d30df79444445eb1143605110219ebf6f203626632d426b1583f83101ba1abcabe233b5409c1b48f20e6453a66a6cf15361679ae99aac1971559ecf58896f5c950ac6b7482784872665d3ce563c89dec404332dc32c19eb69093d556341d7ec2beac4a70ee6a07023b0e8ca7dd945199d46acf05c476b78fe4883b9a193294b3dbf5b0b8d143d7a7682d016ad4e6741eef91de74565c2c57030d86c598b271041cdb8bb5125cf43f9d64f83774f3b4032c0accf6c2787a4599011d4b9ba848b50fb47da918f6800de5055b3a566fb858dbd2f9d6db758d801ce2494ce22ec824e74300b030014ba3c54f0b8544d737e7761f9c2aced8114e5a92827928e9c000dd4353b92fc61f32d172b9133a5c3386adb3030c159b252b01163b2093830f7487d095baf7aacb2f135aee40e04d866d07c97ff273af7251cb6e8aedeca0f5e06f9776464ff8a0bf95aa0033c597f3cb4144ebd2b6dc5b1114e544cd22d445b4cb504fdfd7a392e5c300eb9c463c027f5e48e8e81dd819ada10ce2aa1c3c37c70ad5970289c3204274bf8762611041bf9c5b6864bbf5fc8d31b0772626bfe1b1f5f634bdaf36f1652d5687a69b21e4010b3e5d82f31c63e07904b104b87809d7e2f52f58c3008ae8fa98fae65e8e7609671e89a3e1bb4a0eab52b5e541863bc2fa850b855f4ecc7e08e5a9293bfb48ba6f03f5042816234b304c6f2af3df3029123f44ea52e99d2b59f3211a16096aa03cd8e7fcdc277665f32cac3f023a1468cd2aea4e8863c63dc0af27b3a42a071f4e16970ee06c1fc287df2401e0f2d719ab8e217efeb114c2c0bf18c41933f898245395e77961815ed05e9366d20b2083a5b681ff823609b1b67d1cdcb9ab11b5e80b971ade516186b4d1fe4e4528a35d790e96d857bf78658eda334140ed19f5599b474dbc7f3934bf5459dbdfff6b201a7cff6ddef2d621b30e6e5ba532a1dd3b8b68540f94b32e9ac7b40ef06804147e31e2d7c5c3344a2fcf9ccd4726d4484f05966a4ddbc6ee717d9d71b1f33afc7afa5fdf2234297c4b8db3e9f4de2a884bf91a534066f4ad988d6c498a09abd95b5ffbafe5119953878bc6611f56fa26662802ee6dcd759a17bd9b0c4391ff7ad8eccf7bfba5a97450d179df34116b5c7c85a6bbd6eb61b596227c968e3118640954e17fb01455bfee4051d4b6ee2c01c3f5fe4a4acf965c94f5aa9a699349f4ebec88ebfe1deeb8bbc512f2d11609c52eede49ca6313b192110230d0b58624d1d05a28248bd29acedf348c67a3c686892a1b2f167731e164eebad6f3a3216af54c201daca26a6027b0e7d3c05bf00f26091b451e17489f1c05b475158a14071d0103c692f8fb6c4621077fbe591da55f648a35fa359e993499fdb135c466e6994ab954fc773186de49b20bef7a1b6cb8954d49e22ba1d27103677faca2f9f8b19b92dd4169b564ddb7ce90fc66acc113418ff84d36382f3fe01a92fe54c54093d0386b67 diff --git a/aws-lc-rs/benches/data/cipher_aes_128_ctr.txt b/aws-lc-rs/benches/data/cipher_aes_128_ctr.txt new file mode 100644 index 00000000000..f43a97b7d57 --- /dev/null +++ b/aws-lc-rs/benches/data/cipher_aes_128_ctr.txt @@ -0,0 +1,14 @@ +# 1 block +KEY = d1ae485dbe0d98ae4eff24ae075a5d28 +IV = a7cbf70ede88eb876887c9e7ed60a108 +IN = 6d00bced19251c9d4c9a8ee7ead11881 + +# 16 blocks +KEY = 78f094594b7ee4194bc2baed0562856e +IV = 5591c315865d3b312292be1c8b3babdf +IN = 98b4eae2434eb203583d837f90b17d92c5aaf51cb6d160566a691d1e254067e1a038416723ab31036775e560d6c9d692a1d083dc66b348f1a47fd69c5a890044345d9c0acad8db746280b42fdd17cd07a2c7df684d979d06eb41df6b5dabf1ff6d64ad54462966dfbb1dc8d3d19085b9f9b85f2892ddce92f0b4da4fd9d97d60d9ef171f27a895bc2e00aaa7532a1230536998f246e005688e698eb7edecf05bfda05b93ac63f7eabff0296d9442a0a0c3942985b86e2b5f3a38df65be6fc8ee690ecadab6acc3b75bc4580f54101bcedf6c131081faab8c3e8a322e252260dada51e63dfe470d0d0199d2e9c2f50a77a48d382e3986c26d0db8915ef2e25e28 + +# 256 blocks +KEY = 4da3482c72e5eec455e919054118103a +IV = a4a809574a1a9575b4256cc704a53e46 +IN = a606b6054c433bf7b052aa96b5438ced6675050987c7f9036bb9bc261e5c422c05b155c85bf319f31cef15370f8adb6b574484bfa61cbc203b2875c9d86fae358ac3a59db1230f0e614d68df10ad53077da9b609996f7fdcca9ccc3ca32c1474e563cbb6c97d8113b5f6591f72ac42ac9cdf76d4660a29b5d9b2c8157bd83ce02eb895a8b300a61f886c5d4ca7d07a7166cbd0b9d2ad818e0b1e6ae86ce17b15b67a468446268bf23f61f4fab478d1dee9443ba82b981b5f133561a3019da21b86d848530e6bfaa874d8866cab995ab7a944acac71bb1b7e398dd94a7d69f25f692a2824dcb2354e846d561d4a13d89cd69cd148e4e14d4169da0334b17664b7910f3ad5ab71ba45549fcfdad84da05795b15d6256c99110a5585584230b343ed08990a8b3033da1a07bc47de455548456b4b95f78761482811cbdf39ea35231eebf1f0792667302a269ac534d4a52b1b1927c245b566c9181002db0f6eaf22293196713e514311a658dc616e2ec4f9d8790368e627cf0b741be2a346700ae5456496464162e496ebbc19801059c9c53da709cc74687ecd5fa3d3ef740642f8740a630ff65f9b7b391c6def821e0f01e3c70503846e15e1d53204978a7d85a4eb8c3b654e3d3a7719b8661396235a3455a52de6f3e29c3333dd430eb166ad835d26cfa3be41bd3c3cafdd2bf1956447bbec205cf254f906569fd8bcf5ebaa3d3445f21938b3314e132fbbbb796a3e9abd393277650d5061833e12d6f009baf6543e0bab99be09b39975caff634c5988263e95e1861231b54228d93b3709ea17e29f0614d77da6578b9807b0e8f603f81417ddfe64936b5a337bf8e6055658d58a8680d07d9439fd453274d58dcd4cf6a5eb6a21e96dbdec9d534ee803f3ad830e58d18836ea51964098acd9af4bade9c473387288ce7ef9a73b843e272904e1bd1629e941be8590bb0c254202f93035a9d1708214a5fa0c66cce0f7db2084f63c06de42aab893a587af9b232d2dbdbbc1c85e7005845ad6de9b04ec002743392a4a6282132da9209bd5df5adf4358ac914f83186a58386629c6b1e3032ce2be4454ba7cbff15bc340fd14a51da3b54cd35ea1593b2cabd32004f45f5ae4ec6c7ed79d3d9c512d7299ccf4466958bf379211dffb90b2d8f3737dcf358f659880184707cacbff2184c39134d874fc85c21aabe6fab711f18170b0e5fd38838e19c64b77a6709612f4cd8ada37d31b5ec01347e3b7f777d0a4eb0b7f70094f6856b5bee20118cbd982d6fbf90831b73a4eeabade1243cc52a440f735daf53a6eb12d354faba5938e7e41c6c9d79bbb63d44e1b766659cf7f20d9b2b45fe66ca8fc294f191a0e5ecf7c189afab57687bc599534d5bdcc001c145d0a852e0954d5318e76350dea1afc84a274faad3eb24cafb70e5073fe214a83c8251be1a9e106d86149f569a9ef9aa1f2d473bcb06072a6cb994f400175c788b431f44f7e70f0d7563a0850111f9835b3553d138436e60e2f45a1d147bc6c111447faa79001b05b70f11919ddb1a5681a74c8e08aa892d5a1af635305e18d67e116d26662ca92072be5b9a85cd9da0b97d47f2019b12daa6a5b03f18569857a42e139a5c59cb8b876b0a6965383b4b067d2eb9d3d382b5f96c05d1747eeb39c42815f028698bb94b45fbf501104bb5127ac9213c505460e8598a099bdf2ba9f6c7afca91d34125d642a1fc9746b685ea3adcb7ad49b9ceafda3cef3f0254a28c4000611780946c5de2255af12da37e983c2b41b92ddffd59de694aa79ddc18f49f56ec43bc138c21095d9b6cd82607679a386902957c240c8e4d9196030c2798685bc78b4abbcb823eaeed99995b009bd45c2d667c7652f0d65cf781a1ae5d7348412436f78410fd0b6caf9a80caeb3e9ca2a11345399b37111cd0c579cd24ddf86652634ad6cb1b2fda3e1a194f695e2d1c3247cee2b8a88e6d373d3ac6d29b594306b749ae74b0507e4c70e29536e0a6dc5b5e85c25936117b3e6d0eee31c6685b2e45d152f74de49f82820338653dd2bd43224bfe5f0c94247a1c8e13a03bc0b9eaec114789ca4e4939fd267d261377cafe12e5adadb5df05be0555c11a78b38e454bcbf5dfe263aade057224c0aa7632a4a4ca17f9c97d983d261204a798dcf651d60e0b93f3b4337aa06c793c812fa088ebc9b07df4ca79a3105a33d3b3706c468f258c2a6b7b4a2f4fa1d589eb795c4a0b325b963cbc99eb14a23b5657f9717a6608631bbff1b94337d0c66fe7cdb391476d7c14180b538d83df38086640ff48b1e3f4c7048cef1970df7c43af5c9d586d0cdef4cf51f96aa4539b7d9608261863949e57781b6148b4892a1f42e0169e1486428a8ef5bdb0bf1a00e27c79ae2740e601c54bf5e7c9c96f82a7df1f437ea1d423170617c02751959cb25bbe310ef28ee26718580f85a454e2fb35215b3c52422f69109549c49aa3e4b4e126f2a8a38cc79c4aee2e900cc5073d1a590ada05cdd4a84c6c56247fedb47efe1b8aa33bce4e616623d7e1b250c242e6cd763250b1623db116fce701f46ddb7c9d63bf3ddb173961ae354194c3d2674a228ae977f7de78f7517e0e5612272b6dfc627dfa01f7cc0f4d6c3b5d591bb3c80f77eb23e9d2385666efe0c7fc98ce3fa02c24bc6209bd6db3ba5976d443ff106a41827b156a37a7afc62c916972a410a0ad7cb33188e7db331ec7fb51bdc1fb5537aedc0d1476e1d71adf060439f57a1b83a5af191b0e12ca533b0348e997dce20798fdd2d01c5424c7d606b435c8141578210447d6dec63b06739722a138f0fd2bcdf6cc455b175b07fe0e96c7003662ad71b047884205ce1a83e9a96b9acfa2d837a1b17b4ad865b778646517a33a9515b09b7659f932d7cc660eb5bc39805ed8b1831d72365d772f7875259da722a18274010483df24868c8347cc1dcc67d25fa148a355c0bcdeac84f5aad0af892b24df2928059508c98003ed734ab6eeaeac67060e992c2c1f991247439b514ca3ebe038a4adf04d5d6514e17bbd9ae58c160eaa2a8606d1f9e4dc6e03f48c2c94230f602cfb2c5b285d5ac0d00bdd8392302cfce89fca520e2c91f6b870028a2850b8da2ef86c1088a61a3780bc0f3eca4b28bf84842e455c320b256c26c2b89a03cb9e73821a1014008a175793d35f60641c972352bd3851ec88246960a18672a68dc062d97009f3d34e194c32c124b22e966b63d199e102508b6e23e9d7b0ea38e5562a4198688162219bd70a1b4eb2f77077e6d1b5cc4556a7b9825cd4528e9f96fb7fc65b247baabe903c9de016642f5cd71de47f9c605e985973a3bb0699f54406e66606836d9d7c8b707c8964b59a0f963e0d5f6cc23c05f52b49118d8fcb4e488381104435583ce71583b285c02e4ceff41c81a7b9639d4d0d40dc1f4fca3ff58406426a1aa9a026bd258b6e0f103f1a0850d289b3126afaf17cdaaf5ace15c735363155f4b1c869d6421d015ad499bc9081891686542fb9ddb6f58c3c69e23ace91d75685e93161bf80ff3cb4f3dc946d94b4cece9ececbc9a20975543c7f358346a028d774c811d72ae68da1fad92e6cc6aa497c6365f2003f8e4754faafe1a7b48e626e5613ffe880c404ad0a8104186ccc5c4392f94316f437db2d0bfe4c68394ce764377e810c8a3c9b938c8f4b7727fd1d067f3b38490663ca3183c453cbe760cb0a92d236dd153f51f45b2193437b3340f8a6a44ff30dfbb7e3a57dec7cd80b3288797147e95bfb821c84a7401595f0521c20337a9e9e31714b0b4b7c4708604db81ae0b5755b6c04491d81023ac7f6a32b8f92e46cc339922f381dd6e475d0a43c03707fd2b5796e272d2977569e1ecf43fdc98a41dcd017e10abd0b9ce75ca0fcf3d01b777c765dc6763cfee650802e1c50f04f9995b0b742100fcb6bb955e27974607877207c48df18ae3dc424900c30ac2ac84c7c89594211a3117d53f68c39f7456eb51cace912c7c592580e087bffd86d6c80e89d9d605bbbc29f4197b46136d04ca4d78415b308ec3800867b6abff0bc610a75295c3833e6eb6cd7de5b40e21d083228a9eb0293809061ab63810d035904f75575bcc3c229e37c6fbdde651f3a77bb99998ac6234cfdfd04b6e0e90082387798fff8497de30e66183cd545ac1964857ee1bd277d8594fec991ca9429abc0393c307187dd41e3eb9bffce0b6882c85de6d68b2638b1061c534effd25f7bc702ba2e109fa78ff5e45e308e2e45df1b9846fe3694ee7e2a7c1cb82b9d2b0f0b2e86c69acad5b98d30df79444445eb1143605110219ebf6f203626632d426b1583f83101ba1abcabe233b5409c1b48f20e6453a66a6cf15361679ae99aac1971559ecf58896f5c950ac6b7482784872665d3ce563c89dec404332dc32c19eb69093d556341d7ec2beac4a70ee6a07023b0e8ca7dd945199d46acf05c476b78fe4883b9a193294b3dbf5b0b8d143d7a7682d016ad4e6741eef91de74565c2c57030d86c598b271041cdb8bb5125cf43f9d64f83774f3b4032c0accf6c2787a4599011d4b9ba848b50fb47da918f6800de5055b3a566fb858dbd2f9d6db758d801ce2494ce22ec824e74300b030014ba3c54f0b8544d737e7761f9c2aced8114e5a92827928e9c000dd4353b92fc61f32d172b9133a5c3386adb3030c159b252b01163b2093830f7487d095baf7aacb2f135aee40e04d866d07c97ff273af7251cb6e8aedeca0f5e06f9776464ff8a0bf95aa0033c597f3cb4144ebd2b6dc5b1114e544cd22d445b4cb504fdfd7a392e5c300eb9c463c027f5e48e8e81dd819ada10ce2aa1c3c37c70ad5970289c3204274bf8762611041bf9c5b6864bbf5fc8d31b0772626bfe1b1f5f634bdaf36f1652d5687a69b21e4010b3e5d82f31c63e07904b104b87809d7e2f52f58c3008ae8fa98fae65e8e7609671e89a3e1bb4a0eab52b5e541863bc2fa850b855f4ecc7e08e5a9293bfb48ba6f03f5042816234b304c6f2af3df3029123f44ea52e99d2b59f3211a16096aa03cd8e7fcdc277665f32cac3f023a1468cd2aea4e8863c63dc0af27b3a42a071f4e16970ee06c1fc287df2401e0f2d719ab8e217efeb114c2c0bf18c41933f898245395e77961815ed05e9366d20b2083a5b681ff823609b1b67d1cdcb9ab11b5e80b971ade516186b4d1fe4e4528a35d790e96d857bf78658eda334140ed19f5599b474dbc7f3934bf5459dbdfff6b201a7cff6ddef2d621b30e6e5ba532a1dd3b8b68540f94b32e9ac7b40ef06804147e31e2d7c5c3344a2fcf9ccd4726d4484f05966a4ddbc6ee717d9d71b1f33afc7afa5fdf2234297c4b8db3e9f4de2a884bf91a534066f4ad988d6c498a09abd95b5ffbafe5119953878bc6611f56fa26662802ee6dcd759a17bd9b0c4391ff7ad8eccf7bfba5a97450d179df34116b5c7c85a6bbd6eb61b596227c968e3118640954e17fb01455bfee4051d4b6ee2c01c3f5fe4a4acf965c94f5aa9a699349f4ebec88ebfe1deeb8bbc512f2d11609c52eede49ca6313b192110230d0b58624d1d05a28248bd29acedf348c67a3c686892a1b2f167731e164eebad6f3a3216af54c201daca26a6027b0e7d3c05bf00f26091b451e17489f1c05b475158a14071d0103c692f8fb6c4621077fbe591da55f648a35fa359e993499fdb135c466e6994ab954fc773186de49b20bef7a1b6cb8954d49e22ba1d27103677faca2f9f8b19b92dd4169b564ddb7ce90fc66acc113418ff84d36382f3fe01a92fe54c54093d0386b67 diff --git a/aws-lc-rs/benches/data/cipher_aes_256_cbc.txt b/aws-lc-rs/benches/data/cipher_aes_256_cbc.txt new file mode 100644 index 00000000000..f0aa7c4330b --- /dev/null +++ b/aws-lc-rs/benches/data/cipher_aes_256_cbc.txt @@ -0,0 +1,14 @@ +# 1 block +KEY = 26b8020ed590d535b26d294de8f379ef8db9d969da6945866974594ad24f2652 +IV = 2981adfc2b36df9d806ccc231902d49e +IN = 6d00bced19251c9d4c9a8ee7ead11881 + +# 16 blocks +KEY = e5d74245cb21bf24deb0dc2fca02ada3c7494ae9ebc148b3d4e3b66c32e286b1 +IV = e4d591e0745efeef8cc9ce3c743a7227 +IN = 98b4eae2434eb203583d837f90b17d92c5aaf51cb6d160566a691d1e254067e1a038416723ab31036775e560d6c9d692a1d083dc66b348f1a47fd69c5a890044345d9c0acad8db746280b42fdd17cd07a2c7df684d979d06eb41df6b5dabf1ff6d64ad54462966dfbb1dc8d3d19085b9f9b85f2892ddce92f0b4da4fd9d97d60d9ef171f27a895bc2e00aaa7532a1230536998f246e005688e698eb7edecf05bfda05b93ac63f7eabff0296d9442a0a0c3942985b86e2b5f3a38df65be6fc8ee690ecadab6acc3b75bc4580f54101bcedf6c131081faab8c3e8a322e252260dada51e63dfe470d0d0199d2e9c2f50a77a48d382e3986c26d0db8915ef2e25e28 + +# 256 blocks +KEY = 55cafb5ac2edbbe3fdffee32cc9d5ed954dcaf80585671086404b275d7472c15 +IV = c76cb076894620ca19466a4b9f512082 +IN = a606b6054c433bf7b052aa96b5438ced6675050987c7f9036bb9bc261e5c422c05b155c85bf319f31cef15370f8adb6b574484bfa61cbc203b2875c9d86fae358ac3a59db1230f0e614d68df10ad53077da9b609996f7fdcca9ccc3ca32c1474e563cbb6c97d8113b5f6591f72ac42ac9cdf76d4660a29b5d9b2c8157bd83ce02eb895a8b300a61f886c5d4ca7d07a7166cbd0b9d2ad818e0b1e6ae86ce17b15b67a468446268bf23f61f4fab478d1dee9443ba82b981b5f133561a3019da21b86d848530e6bfaa874d8866cab995ab7a944acac71bb1b7e398dd94a7d69f25f692a2824dcb2354e846d561d4a13d89cd69cd148e4e14d4169da0334b17664b7910f3ad5ab71ba45549fcfdad84da05795b15d6256c99110a5585584230b343ed08990a8b3033da1a07bc47de455548456b4b95f78761482811cbdf39ea35231eebf1f0792667302a269ac534d4a52b1b1927c245b566c9181002db0f6eaf22293196713e514311a658dc616e2ec4f9d8790368e627cf0b741be2a346700ae5456496464162e496ebbc19801059c9c53da709cc74687ecd5fa3d3ef740642f8740a630ff65f9b7b391c6def821e0f01e3c70503846e15e1d53204978a7d85a4eb8c3b654e3d3a7719b8661396235a3455a52de6f3e29c3333dd430eb166ad835d26cfa3be41bd3c3cafdd2bf1956447bbec205cf254f906569fd8bcf5ebaa3d3445f21938b3314e132fbbbb796a3e9abd393277650d5061833e12d6f009baf6543e0bab99be09b39975caff634c5988263e95e1861231b54228d93b3709ea17e29f0614d77da6578b9807b0e8f603f81417ddfe64936b5a337bf8e6055658d58a8680d07d9439fd453274d58dcd4cf6a5eb6a21e96dbdec9d534ee803f3ad830e58d18836ea51964098acd9af4bade9c473387288ce7ef9a73b843e272904e1bd1629e941be8590bb0c254202f93035a9d1708214a5fa0c66cce0f7db2084f63c06de42aab893a587af9b232d2dbdbbc1c85e7005845ad6de9b04ec002743392a4a6282132da9209bd5df5adf4358ac914f83186a58386629c6b1e3032ce2be4454ba7cbff15bc340fd14a51da3b54cd35ea1593b2cabd32004f45f5ae4ec6c7ed79d3d9c512d7299ccf4466958bf379211dffb90b2d8f3737dcf358f659880184707cacbff2184c39134d874fc85c21aabe6fab711f18170b0e5fd38838e19c64b77a6709612f4cd8ada37d31b5ec01347e3b7f777d0a4eb0b7f70094f6856b5bee20118cbd982d6fbf90831b73a4eeabade1243cc52a440f735daf53a6eb12d354faba5938e7e41c6c9d79bbb63d44e1b766659cf7f20d9b2b45fe66ca8fc294f191a0e5ecf7c189afab57687bc599534d5bdcc001c145d0a852e0954d5318e76350dea1afc84a274faad3eb24cafb70e5073fe214a83c8251be1a9e106d86149f569a9ef9aa1f2d473bcb06072a6cb994f400175c788b431f44f7e70f0d7563a0850111f9835b3553d138436e60e2f45a1d147bc6c111447faa79001b05b70f11919ddb1a5681a74c8e08aa892d5a1af635305e18d67e116d26662ca92072be5b9a85cd9da0b97d47f2019b12daa6a5b03f18569857a42e139a5c59cb8b876b0a6965383b4b067d2eb9d3d382b5f96c05d1747eeb39c42815f028698bb94b45fbf501104bb5127ac9213c505460e8598a099bdf2ba9f6c7afca91d34125d642a1fc9746b685ea3adcb7ad49b9ceafda3cef3f0254a28c4000611780946c5de2255af12da37e983c2b41b92ddffd59de694aa79ddc18f49f56ec43bc138c21095d9b6cd82607679a386902957c240c8e4d9196030c2798685bc78b4abbcb823eaeed99995b009bd45c2d667c7652f0d65cf781a1ae5d7348412436f78410fd0b6caf9a80caeb3e9ca2a11345399b37111cd0c579cd24ddf86652634ad6cb1b2fda3e1a194f695e2d1c3247cee2b8a88e6d373d3ac6d29b594306b749ae74b0507e4c70e29536e0a6dc5b5e85c25936117b3e6d0eee31c6685b2e45d152f74de49f82820338653dd2bd43224bfe5f0c94247a1c8e13a03bc0b9eaec114789ca4e4939fd267d261377cafe12e5adadb5df05be0555c11a78b38e454bcbf5dfe263aade057224c0aa7632a4a4ca17f9c97d983d261204a798dcf651d60e0b93f3b4337aa06c793c812fa088ebc9b07df4ca79a3105a33d3b3706c468f258c2a6b7b4a2f4fa1d589eb795c4a0b325b963cbc99eb14a23b5657f9717a6608631bbff1b94337d0c66fe7cdb391476d7c14180b538d83df38086640ff48b1e3f4c7048cef1970df7c43af5c9d586d0cdef4cf51f96aa4539b7d9608261863949e57781b6148b4892a1f42e0169e1486428a8ef5bdb0bf1a00e27c79ae2740e601c54bf5e7c9c96f82a7df1f437ea1d423170617c02751959cb25bbe310ef28ee26718580f85a454e2fb35215b3c52422f69109549c49aa3e4b4e126f2a8a38cc79c4aee2e900cc5073d1a590ada05cdd4a84c6c56247fedb47efe1b8aa33bce4e616623d7e1b250c242e6cd763250b1623db116fce701f46ddb7c9d63bf3ddb173961ae354194c3d2674a228ae977f7de78f7517e0e5612272b6dfc627dfa01f7cc0f4d6c3b5d591bb3c80f77eb23e9d2385666efe0c7fc98ce3fa02c24bc6209bd6db3ba5976d443ff106a41827b156a37a7afc62c916972a410a0ad7cb33188e7db331ec7fb51bdc1fb5537aedc0d1476e1d71adf060439f57a1b83a5af191b0e12ca533b0348e997dce20798fdd2d01c5424c7d606b435c8141578210447d6dec63b06739722a138f0fd2bcdf6cc455b175b07fe0e96c7003662ad71b047884205ce1a83e9a96b9acfa2d837a1b17b4ad865b778646517a33a9515b09b7659f932d7cc660eb5bc39805ed8b1831d72365d772f7875259da722a18274010483df24868c8347cc1dcc67d25fa148a355c0bcdeac84f5aad0af892b24df2928059508c98003ed734ab6eeaeac67060e992c2c1f991247439b514ca3ebe038a4adf04d5d6514e17bbd9ae58c160eaa2a8606d1f9e4dc6e03f48c2c94230f602cfb2c5b285d5ac0d00bdd8392302cfce89fca520e2c91f6b870028a2850b8da2ef86c1088a61a3780bc0f3eca4b28bf84842e455c320b256c26c2b89a03cb9e73821a1014008a175793d35f60641c972352bd3851ec88246960a18672a68dc062d97009f3d34e194c32c124b22e966b63d199e102508b6e23e9d7b0ea38e5562a4198688162219bd70a1b4eb2f77077e6d1b5cc4556a7b9825cd4528e9f96fb7fc65b247baabe903c9de016642f5cd71de47f9c605e985973a3bb0699f54406e66606836d9d7c8b707c8964b59a0f963e0d5f6cc23c05f52b49118d8fcb4e488381104435583ce71583b285c02e4ceff41c81a7b9639d4d0d40dc1f4fca3ff58406426a1aa9a026bd258b6e0f103f1a0850d289b3126afaf17cdaaf5ace15c735363155f4b1c869d6421d015ad499bc9081891686542fb9ddb6f58c3c69e23ace91d75685e93161bf80ff3cb4f3dc946d94b4cece9ececbc9a20975543c7f358346a028d774c811d72ae68da1fad92e6cc6aa497c6365f2003f8e4754faafe1a7b48e626e5613ffe880c404ad0a8104186ccc5c4392f94316f437db2d0bfe4c68394ce764377e810c8a3c9b938c8f4b7727fd1d067f3b38490663ca3183c453cbe760cb0a92d236dd153f51f45b2193437b3340f8a6a44ff30dfbb7e3a57dec7cd80b3288797147e95bfb821c84a7401595f0521c20337a9e9e31714b0b4b7c4708604db81ae0b5755b6c04491d81023ac7f6a32b8f92e46cc339922f381dd6e475d0a43c03707fd2b5796e272d2977569e1ecf43fdc98a41dcd017e10abd0b9ce75ca0fcf3d01b777c765dc6763cfee650802e1c50f04f9995b0b742100fcb6bb955e27974607877207c48df18ae3dc424900c30ac2ac84c7c89594211a3117d53f68c39f7456eb51cace912c7c592580e087bffd86d6c80e89d9d605bbbc29f4197b46136d04ca4d78415b308ec3800867b6abff0bc610a75295c3833e6eb6cd7de5b40e21d083228a9eb0293809061ab63810d035904f75575bcc3c229e37c6fbdde651f3a77bb99998ac6234cfdfd04b6e0e90082387798fff8497de30e66183cd545ac1964857ee1bd277d8594fec991ca9429abc0393c307187dd41e3eb9bffce0b6882c85de6d68b2638b1061c534effd25f7bc702ba2e109fa78ff5e45e308e2e45df1b9846fe3694ee7e2a7c1cb82b9d2b0f0b2e86c69acad5b98d30df79444445eb1143605110219ebf6f203626632d426b1583f83101ba1abcabe233b5409c1b48f20e6453a66a6cf15361679ae99aac1971559ecf58896f5c950ac6b7482784872665d3ce563c89dec404332dc32c19eb69093d556341d7ec2beac4a70ee6a07023b0e8ca7dd945199d46acf05c476b78fe4883b9a193294b3dbf5b0b8d143d7a7682d016ad4e6741eef91de74565c2c57030d86c598b271041cdb8bb5125cf43f9d64f83774f3b4032c0accf6c2787a4599011d4b9ba848b50fb47da918f6800de5055b3a566fb858dbd2f9d6db758d801ce2494ce22ec824e74300b030014ba3c54f0b8544d737e7761f9c2aced8114e5a92827928e9c000dd4353b92fc61f32d172b9133a5c3386adb3030c159b252b01163b2093830f7487d095baf7aacb2f135aee40e04d866d07c97ff273af7251cb6e8aedeca0f5e06f9776464ff8a0bf95aa0033c597f3cb4144ebd2b6dc5b1114e544cd22d445b4cb504fdfd7a392e5c300eb9c463c027f5e48e8e81dd819ada10ce2aa1c3c37c70ad5970289c3204274bf8762611041bf9c5b6864bbf5fc8d31b0772626bfe1b1f5f634bdaf36f1652d5687a69b21e4010b3e5d82f31c63e07904b104b87809d7e2f52f58c3008ae8fa98fae65e8e7609671e89a3e1bb4a0eab52b5e541863bc2fa850b855f4ecc7e08e5a9293bfb48ba6f03f5042816234b304c6f2af3df3029123f44ea52e99d2b59f3211a16096aa03cd8e7fcdc277665f32cac3f023a1468cd2aea4e8863c63dc0af27b3a42a071f4e16970ee06c1fc287df2401e0f2d719ab8e217efeb114c2c0bf18c41933f898245395e77961815ed05e9366d20b2083a5b681ff823609b1b67d1cdcb9ab11b5e80b971ade516186b4d1fe4e4528a35d790e96d857bf78658eda334140ed19f5599b474dbc7f3934bf5459dbdfff6b201a7cff6ddef2d621b30e6e5ba532a1dd3b8b68540f94b32e9ac7b40ef06804147e31e2d7c5c3344a2fcf9ccd4726d4484f05966a4ddbc6ee717d9d71b1f33afc7afa5fdf2234297c4b8db3e9f4de2a884bf91a534066f4ad988d6c498a09abd95b5ffbafe5119953878bc6611f56fa26662802ee6dcd759a17bd9b0c4391ff7ad8eccf7bfba5a97450d179df34116b5c7c85a6bbd6eb61b596227c968e3118640954e17fb01455bfee4051d4b6ee2c01c3f5fe4a4acf965c94f5aa9a699349f4ebec88ebfe1deeb8bbc512f2d11609c52eede49ca6313b192110230d0b58624d1d05a28248bd29acedf348c67a3c686892a1b2f167731e164eebad6f3a3216af54c201daca26a6027b0e7d3c05bf00f26091b451e17489f1c05b475158a14071d0103c692f8fb6c4621077fbe591da55f648a35fa359e993499fdb135c466e6994ab954fc773186de49b20bef7a1b6cb8954d49e22ba1d27103677faca2f9f8b19b92dd4169b564ddb7ce90fc66acc113418ff84d36382f3fe01a92fe54c54093d0386b67 diff --git a/aws-lc-rs/benches/data/cipher_aes_256_ctr.txt b/aws-lc-rs/benches/data/cipher_aes_256_ctr.txt new file mode 100644 index 00000000000..f0aa7c4330b --- /dev/null +++ b/aws-lc-rs/benches/data/cipher_aes_256_ctr.txt @@ -0,0 +1,14 @@ +# 1 block +KEY = 26b8020ed590d535b26d294de8f379ef8db9d969da6945866974594ad24f2652 +IV = 2981adfc2b36df9d806ccc231902d49e +IN = 6d00bced19251c9d4c9a8ee7ead11881 + +# 16 blocks +KEY = e5d74245cb21bf24deb0dc2fca02ada3c7494ae9ebc148b3d4e3b66c32e286b1 +IV = e4d591e0745efeef8cc9ce3c743a7227 +IN = 98b4eae2434eb203583d837f90b17d92c5aaf51cb6d160566a691d1e254067e1a038416723ab31036775e560d6c9d692a1d083dc66b348f1a47fd69c5a890044345d9c0acad8db746280b42fdd17cd07a2c7df684d979d06eb41df6b5dabf1ff6d64ad54462966dfbb1dc8d3d19085b9f9b85f2892ddce92f0b4da4fd9d97d60d9ef171f27a895bc2e00aaa7532a1230536998f246e005688e698eb7edecf05bfda05b93ac63f7eabff0296d9442a0a0c3942985b86e2b5f3a38df65be6fc8ee690ecadab6acc3b75bc4580f54101bcedf6c131081faab8c3e8a322e252260dada51e63dfe470d0d0199d2e9c2f50a77a48d382e3986c26d0db8915ef2e25e28 + +# 256 blocks +KEY = 55cafb5ac2edbbe3fdffee32cc9d5ed954dcaf80585671086404b275d7472c15 +IV = c76cb076894620ca19466a4b9f512082 +IN = a606b6054c433bf7b052aa96b5438ced6675050987c7f9036bb9bc261e5c422c05b155c85bf319f31cef15370f8adb6b574484bfa61cbc203b2875c9d86fae358ac3a59db1230f0e614d68df10ad53077da9b609996f7fdcca9ccc3ca32c1474e563cbb6c97d8113b5f6591f72ac42ac9cdf76d4660a29b5d9b2c8157bd83ce02eb895a8b300a61f886c5d4ca7d07a7166cbd0b9d2ad818e0b1e6ae86ce17b15b67a468446268bf23f61f4fab478d1dee9443ba82b981b5f133561a3019da21b86d848530e6bfaa874d8866cab995ab7a944acac71bb1b7e398dd94a7d69f25f692a2824dcb2354e846d561d4a13d89cd69cd148e4e14d4169da0334b17664b7910f3ad5ab71ba45549fcfdad84da05795b15d6256c99110a5585584230b343ed08990a8b3033da1a07bc47de455548456b4b95f78761482811cbdf39ea35231eebf1f0792667302a269ac534d4a52b1b1927c245b566c9181002db0f6eaf22293196713e514311a658dc616e2ec4f9d8790368e627cf0b741be2a346700ae5456496464162e496ebbc19801059c9c53da709cc74687ecd5fa3d3ef740642f8740a630ff65f9b7b391c6def821e0f01e3c70503846e15e1d53204978a7d85a4eb8c3b654e3d3a7719b8661396235a3455a52de6f3e29c3333dd430eb166ad835d26cfa3be41bd3c3cafdd2bf1956447bbec205cf254f906569fd8bcf5ebaa3d3445f21938b3314e132fbbbb796a3e9abd393277650d5061833e12d6f009baf6543e0bab99be09b39975caff634c5988263e95e1861231b54228d93b3709ea17e29f0614d77da6578b9807b0e8f603f81417ddfe64936b5a337bf8e6055658d58a8680d07d9439fd453274d58dcd4cf6a5eb6a21e96dbdec9d534ee803f3ad830e58d18836ea51964098acd9af4bade9c473387288ce7ef9a73b843e272904e1bd1629e941be8590bb0c254202f93035a9d1708214a5fa0c66cce0f7db2084f63c06de42aab893a587af9b232d2dbdbbc1c85e7005845ad6de9b04ec002743392a4a6282132da9209bd5df5adf4358ac914f83186a58386629c6b1e3032ce2be4454ba7cbff15bc340fd14a51da3b54cd35ea1593b2cabd32004f45f5ae4ec6c7ed79d3d9c512d7299ccf4466958bf379211dffb90b2d8f3737dcf358f659880184707cacbff2184c39134d874fc85c21aabe6fab711f18170b0e5fd38838e19c64b77a6709612f4cd8ada37d31b5ec01347e3b7f777d0a4eb0b7f70094f6856b5bee20118cbd982d6fbf90831b73a4eeabade1243cc52a440f735daf53a6eb12d354faba5938e7e41c6c9d79bbb63d44e1b766659cf7f20d9b2b45fe66ca8fc294f191a0e5ecf7c189afab57687bc599534d5bdcc001c145d0a852e0954d5318e76350dea1afc84a274faad3eb24cafb70e5073fe214a83c8251be1a9e106d86149f569a9ef9aa1f2d473bcb06072a6cb994f400175c788b431f44f7e70f0d7563a0850111f9835b3553d138436e60e2f45a1d147bc6c111447faa79001b05b70f11919ddb1a5681a74c8e08aa892d5a1af635305e18d67e116d26662ca92072be5b9a85cd9da0b97d47f2019b12daa6a5b03f18569857a42e139a5c59cb8b876b0a6965383b4b067d2eb9d3d382b5f96c05d1747eeb39c42815f028698bb94b45fbf501104bb5127ac9213c505460e8598a099bdf2ba9f6c7afca91d34125d642a1fc9746b685ea3adcb7ad49b9ceafda3cef3f0254a28c4000611780946c5de2255af12da37e983c2b41b92ddffd59de694aa79ddc18f49f56ec43bc138c21095d9b6cd82607679a386902957c240c8e4d9196030c2798685bc78b4abbcb823eaeed99995b009bd45c2d667c7652f0d65cf781a1ae5d7348412436f78410fd0b6caf9a80caeb3e9ca2a11345399b37111cd0c579cd24ddf86652634ad6cb1b2fda3e1a194f695e2d1c3247cee2b8a88e6d373d3ac6d29b594306b749ae74b0507e4c70e29536e0a6dc5b5e85c25936117b3e6d0eee31c6685b2e45d152f74de49f82820338653dd2bd43224bfe5f0c94247a1c8e13a03bc0b9eaec114789ca4e4939fd267d261377cafe12e5adadb5df05be0555c11a78b38e454bcbf5dfe263aade057224c0aa7632a4a4ca17f9c97d983d261204a798dcf651d60e0b93f3b4337aa06c793c812fa088ebc9b07df4ca79a3105a33d3b3706c468f258c2a6b7b4a2f4fa1d589eb795c4a0b325b963cbc99eb14a23b5657f9717a6608631bbff1b94337d0c66fe7cdb391476d7c14180b538d83df38086640ff48b1e3f4c7048cef1970df7c43af5c9d586d0cdef4cf51f96aa4539b7d9608261863949e57781b6148b4892a1f42e0169e1486428a8ef5bdb0bf1a00e27c79ae2740e601c54bf5e7c9c96f82a7df1f437ea1d423170617c02751959cb25bbe310ef28ee26718580f85a454e2fb35215b3c52422f69109549c49aa3e4b4e126f2a8a38cc79c4aee2e900cc5073d1a590ada05cdd4a84c6c56247fedb47efe1b8aa33bce4e616623d7e1b250c242e6cd763250b1623db116fce701f46ddb7c9d63bf3ddb173961ae354194c3d2674a228ae977f7de78f7517e0e5612272b6dfc627dfa01f7cc0f4d6c3b5d591bb3c80f77eb23e9d2385666efe0c7fc98ce3fa02c24bc6209bd6db3ba5976d443ff106a41827b156a37a7afc62c916972a410a0ad7cb33188e7db331ec7fb51bdc1fb5537aedc0d1476e1d71adf060439f57a1b83a5af191b0e12ca533b0348e997dce20798fdd2d01c5424c7d606b435c8141578210447d6dec63b06739722a138f0fd2bcdf6cc455b175b07fe0e96c7003662ad71b047884205ce1a83e9a96b9acfa2d837a1b17b4ad865b778646517a33a9515b09b7659f932d7cc660eb5bc39805ed8b1831d72365d772f7875259da722a18274010483df24868c8347cc1dcc67d25fa148a355c0bcdeac84f5aad0af892b24df2928059508c98003ed734ab6eeaeac67060e992c2c1f991247439b514ca3ebe038a4adf04d5d6514e17bbd9ae58c160eaa2a8606d1f9e4dc6e03f48c2c94230f602cfb2c5b285d5ac0d00bdd8392302cfce89fca520e2c91f6b870028a2850b8da2ef86c1088a61a3780bc0f3eca4b28bf84842e455c320b256c26c2b89a03cb9e73821a1014008a175793d35f60641c972352bd3851ec88246960a18672a68dc062d97009f3d34e194c32c124b22e966b63d199e102508b6e23e9d7b0ea38e5562a4198688162219bd70a1b4eb2f77077e6d1b5cc4556a7b9825cd4528e9f96fb7fc65b247baabe903c9de016642f5cd71de47f9c605e985973a3bb0699f54406e66606836d9d7c8b707c8964b59a0f963e0d5f6cc23c05f52b49118d8fcb4e488381104435583ce71583b285c02e4ceff41c81a7b9639d4d0d40dc1f4fca3ff58406426a1aa9a026bd258b6e0f103f1a0850d289b3126afaf17cdaaf5ace15c735363155f4b1c869d6421d015ad499bc9081891686542fb9ddb6f58c3c69e23ace91d75685e93161bf80ff3cb4f3dc946d94b4cece9ececbc9a20975543c7f358346a028d774c811d72ae68da1fad92e6cc6aa497c6365f2003f8e4754faafe1a7b48e626e5613ffe880c404ad0a8104186ccc5c4392f94316f437db2d0bfe4c68394ce764377e810c8a3c9b938c8f4b7727fd1d067f3b38490663ca3183c453cbe760cb0a92d236dd153f51f45b2193437b3340f8a6a44ff30dfbb7e3a57dec7cd80b3288797147e95bfb821c84a7401595f0521c20337a9e9e31714b0b4b7c4708604db81ae0b5755b6c04491d81023ac7f6a32b8f92e46cc339922f381dd6e475d0a43c03707fd2b5796e272d2977569e1ecf43fdc98a41dcd017e10abd0b9ce75ca0fcf3d01b777c765dc6763cfee650802e1c50f04f9995b0b742100fcb6bb955e27974607877207c48df18ae3dc424900c30ac2ac84c7c89594211a3117d53f68c39f7456eb51cace912c7c592580e087bffd86d6c80e89d9d605bbbc29f4197b46136d04ca4d78415b308ec3800867b6abff0bc610a75295c3833e6eb6cd7de5b40e21d083228a9eb0293809061ab63810d035904f75575bcc3c229e37c6fbdde651f3a77bb99998ac6234cfdfd04b6e0e90082387798fff8497de30e66183cd545ac1964857ee1bd277d8594fec991ca9429abc0393c307187dd41e3eb9bffce0b6882c85de6d68b2638b1061c534effd25f7bc702ba2e109fa78ff5e45e308e2e45df1b9846fe3694ee7e2a7c1cb82b9d2b0f0b2e86c69acad5b98d30df79444445eb1143605110219ebf6f203626632d426b1583f83101ba1abcabe233b5409c1b48f20e6453a66a6cf15361679ae99aac1971559ecf58896f5c950ac6b7482784872665d3ce563c89dec404332dc32c19eb69093d556341d7ec2beac4a70ee6a07023b0e8ca7dd945199d46acf05c476b78fe4883b9a193294b3dbf5b0b8d143d7a7682d016ad4e6741eef91de74565c2c57030d86c598b271041cdb8bb5125cf43f9d64f83774f3b4032c0accf6c2787a4599011d4b9ba848b50fb47da918f6800de5055b3a566fb858dbd2f9d6db758d801ce2494ce22ec824e74300b030014ba3c54f0b8544d737e7761f9c2aced8114e5a92827928e9c000dd4353b92fc61f32d172b9133a5c3386adb3030c159b252b01163b2093830f7487d095baf7aacb2f135aee40e04d866d07c97ff273af7251cb6e8aedeca0f5e06f9776464ff8a0bf95aa0033c597f3cb4144ebd2b6dc5b1114e544cd22d445b4cb504fdfd7a392e5c300eb9c463c027f5e48e8e81dd819ada10ce2aa1c3c37c70ad5970289c3204274bf8762611041bf9c5b6864bbf5fc8d31b0772626bfe1b1f5f634bdaf36f1652d5687a69b21e4010b3e5d82f31c63e07904b104b87809d7e2f52f58c3008ae8fa98fae65e8e7609671e89a3e1bb4a0eab52b5e541863bc2fa850b855f4ecc7e08e5a9293bfb48ba6f03f5042816234b304c6f2af3df3029123f44ea52e99d2b59f3211a16096aa03cd8e7fcdc277665f32cac3f023a1468cd2aea4e8863c63dc0af27b3a42a071f4e16970ee06c1fc287df2401e0f2d719ab8e217efeb114c2c0bf18c41933f898245395e77961815ed05e9366d20b2083a5b681ff823609b1b67d1cdcb9ab11b5e80b971ade516186b4d1fe4e4528a35d790e96d857bf78658eda334140ed19f5599b474dbc7f3934bf5459dbdfff6b201a7cff6ddef2d621b30e6e5ba532a1dd3b8b68540f94b32e9ac7b40ef06804147e31e2d7c5c3344a2fcf9ccd4726d4484f05966a4ddbc6ee717d9d71b1f33afc7afa5fdf2234297c4b8db3e9f4de2a884bf91a534066f4ad988d6c498a09abd95b5ffbafe5119953878bc6611f56fa26662802ee6dcd759a17bd9b0c4391ff7ad8eccf7bfba5a97450d179df34116b5c7c85a6bbd6eb61b596227c968e3118640954e17fb01455bfee4051d4b6ee2c01c3f5fe4a4acf965c94f5aa9a699349f4ebec88ebfe1deeb8bbc512f2d11609c52eede49ca6313b192110230d0b58624d1d05a28248bd29acedf348c67a3c686892a1b2f167731e164eebad6f3a3216af54c201daca26a6027b0e7d3c05bf00f26091b451e17489f1c05b475158a14071d0103c692f8fb6c4621077fbe591da55f648a35fa359e993499fdb135c466e6994ab954fc773186de49b20bef7a1b6cb8954d49e22ba1d27103677faca2f9f8b19b92dd4169b564ddb7ce90fc66acc113418ff84d36382f3fe01a92fe54c54093d0386b67 diff --git a/aws-lc-rs/examples/cipher.rs b/aws-lc-rs/examples/cipher.rs new file mode 100644 index 00000000000..b13bfc09a14 --- /dev/null +++ b/aws-lc-rs/examples/cipher.rs @@ -0,0 +1,232 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +//! cipher - Perform symmetric cipher encryption/decryption on utf8 plaintext. +//! +//! *cipher* is an example program demonstrating the `aws_lc_rs::cipher` API for *aws-lc-rs*. +//! It demonstrates CTR & CBC mode encryption using AES 128 or 256 bit keys. +//! +//! The program can be run from the command line using cargo: +//! ```sh +//! $ cargo run --example cipher -- --mode ctr encrypt "Hello World" +//! key: b331133eb742497c67ced9520c9a7de3 +//! iv: 4e967c7b799e0670431888e2e959e154 +//! ciphertext: 88bcbd8d1656d60de739c5 +//! +//! $ cargo run --example cipher -- --mode ctr --key b331133eb742497c67ced9520c9a7de3 decrypt --iv 4e967c7b799e0670431888e2e959e154 88bcbd8d1656d60de739c5 +//! Hello World +//! +//! $ cargo run --example cipher -- --mode cbc encrypt "Hello World" +//! key: 6489d8ce0c4facf18b872705a05d5ee4 +//! iv: 5cd56fb752830ec2459889226c5431bd +//! ciphertext: 6311c14e8104730be124ce1e57e51fe3 +//! +//! $ cargo run --example cipher -- --mode cbc --key 6489d8ce0c4facf18b872705a05d5ee4 decrypt --iv 5cd56fb752830ec2459889226c5431bd 6311c14e8104730be124ce1e57e51fe3 +//! Hello World +//! ``` +use aws_lc_rs::cipher::{AES_128_KEY_LEN, AES_256_KEY_LEN, AES_CBC_IV_LEN, AES_CTR_IV_LEN}; +use aws_lc_rs::{ + cipher::{ + CipherContext, DecryptingKey, EncryptingKey, PaddedBlockDecryptingKey, + PaddedBlockEncryptingKey, UnboundCipherKey, AES_128, AES_256, + }, + iv::FixedLength, +}; +use clap::{Parser, Subcommand, ValueEnum}; + +#[derive(Parser)] +#[command(author, version, name = "cipher")] +struct Cli { + #[arg( + short, + long, + help = "AES 128 or 256 bit key in hex, if not provided defaults to 128" + )] + key: Option, + + #[arg(short, long, value_enum, help = "AES cipher mode")] + mode: Mode, + + #[command(subcommand)] + command: Commands, +} + +#[derive(ValueEnum, Clone, Copy)] +enum Mode { + Ctr, + Cbc, +} + +#[derive(Subcommand)] +enum Commands { + Encrypt { + #[arg(short, long, help = "Initalization Vector (IV)")] + iv: Option, + plaintext: String, + }, + Decrypt { + #[arg(short, long, help = "Initalization Vector (IV)")] + iv: String, + ciphertext: String, + }, +} + +fn main() -> Result<(), &'static str> { + let cli = Cli::parse(); + + let key = if let Some(key) = cli.key { + match hex::decode(key) { + Ok(v) => v, + Err(..) => { + return Err("invalid key"); + } + } + } else { + let mut v = vec![0u8; AES_128_KEY_LEN]; + aws_lc_rs::rand::fill(v.as_mut_slice()).map_err(|_| "failed to generate key")?; + v + }; + + match (cli.command, cli.mode) { + (Commands::Encrypt { iv, plaintext }, Mode::Ctr) => aes_ctr_encrypt(&key, iv, plaintext), + (Commands::Encrypt { iv, plaintext }, Mode::Cbc) => aes_cbc_encrypt(&key, iv, plaintext), + (Commands::Decrypt { iv, ciphertext }, Mode::Ctr) => aes_ctr_decrypt(&key, iv, ciphertext), + (Commands::Decrypt { iv, ciphertext }, Mode::Cbc) => aes_cbc_decrypt(&key, iv, ciphertext), + }?; + + Ok(()) +} + +fn aes_ctr_encrypt(key: &[u8], iv: Option, plaintext: String) -> Result<(), &'static str> { + let hex_key = hex::encode(key); + let key = new_unbound_key(key)?; + + let key = EncryptingKey::ctr(key).map_err(|_| "failed to initalized aes encryption")?; + + let mut ciphertext = Vec::from(plaintext); + + let context = match iv { + Some(iv) => { + let context = { + let v = hex::decode(iv).map_err(|_| "invalid iv")?; + let v: FixedLength = + v.as_slice().try_into().map_err(|_| "invalid iv")?; + CipherContext::Iv128(v) + }; + key.less_safe_encrypt(ciphertext.as_mut(), context) + } + None => key.encrypt(ciphertext.as_mut()), + } + .map_err(|_| "failed to encrypt plaintext")?; + + let iv: &[u8] = (&context) + .try_into() + .map_err(|_| "unexpected encryption context")?; + + let ciphertext = hex::encode(ciphertext.as_slice()); + + println!("key: {hex_key}"); + println!("iv: {}", hex::encode(iv)); + println!("ciphertext: {ciphertext}"); + + Ok(()) +} + +fn aes_ctr_decrypt(key: &[u8], iv: String, ciphertext: String) -> Result<(), &'static str> { + let key = new_unbound_key(key)?; + let iv = { + let v = hex::decode(iv).map_err(|_| "invalid iv")?; + let v: FixedLength = v.as_slice().try_into().map_err(|_| "invalid iv")?; + v + }; + + let key = DecryptingKey::ctr(key).map_err(|_| "failed to initalized aes decryption")?; + + let mut ciphertext = + hex::decode(ciphertext).map_err(|_| "ciphertext is not valid hex encoding")?; + + let plaintext = key + .decrypt(ciphertext.as_mut(), CipherContext::Iv128(iv)) + .map_err(|_| "failed to decrypt ciphertext")?; + + let plaintext = + String::from_utf8(plaintext.into()).map_err(|_| "decrypted text was not a utf8 string")?; + + println!("{plaintext}"); + + Ok(()) +} + +fn aes_cbc_encrypt(key: &[u8], iv: Option, plaintext: String) -> Result<(), &'static str> { + let hex_key = hex::encode(key); + let key = new_unbound_key(key)?; + + let key = PaddedBlockEncryptingKey::cbc_pkcs7(key) + .map_err(|_| "failed to initalized aes encryption")?; + + let mut ciphertext = Vec::from(plaintext); + + let context = match iv { + Some(iv) => { + let context = { + let v = hex::decode(iv).map_err(|_| "invalid iv")?; + let v: FixedLength = + v.as_slice().try_into().map_err(|_| "invalid iv")?; + CipherContext::Iv128(v) + }; + key.less_safe_encrypt(&mut ciphertext, context) + } + None => key.encrypt(&mut ciphertext), + } + .map_err(|_| "failed to initalized aes encryption")?; + + let iv: &[u8] = (&context) + .try_into() + .map_err(|_| "unexpected encryption context")?; + + let ciphertext = hex::encode(ciphertext.as_slice()); + + println!("key: {hex_key}"); + println!("iv: {}", hex::encode(iv)); + println!("ciphertext: {ciphertext}"); + + Ok(()) +} + +fn aes_cbc_decrypt(key: &[u8], iv: String, ciphertext: String) -> Result<(), &'static str> { + let key = new_unbound_key(key)?; + let iv = { + let v = hex::decode(iv).map_err(|_| "invalid iv")?; + let v: FixedLength = v.as_slice().try_into().map_err(|_| "invalid iv")?; + v + }; + + let key = PaddedBlockDecryptingKey::cbc_pkcs7(key) + .map_err(|_| "failed to initalized aes decryption")?; + + let mut ciphertext = + hex::decode(ciphertext).map_err(|_| "ciphertext is not valid hex encoding")?; + + let plaintext = key + .decrypt(ciphertext.as_mut(), CipherContext::Iv128(iv)) + .map_err(|_| "failed to decrypt ciphertext")?; + + let plaintext = + String::from_utf8(plaintext.into()).map_err(|_| "decrypted text was not a utf8 string")?; + + println!("{plaintext}"); + + Ok(()) +} + +fn new_unbound_key(key: &[u8]) -> Result { + let alg = match key.len() { + AES_128_KEY_LEN => &AES_128, + AES_256_KEY_LEN => &AES_256, + _ => { + return Err("invalid aes key length"); + } + }; + + UnboundCipherKey::new(alg, key).map_err(|_| "failed to construct aes key") +} diff --git a/aws-lc-rs/src/aead.rs b/aws-lc-rs/src/aead.rs index 2abae43e51b..afb26f4aa6a 100644 --- a/aws-lc-rs/src/aead.rs +++ b/aws-lc-rs/src/aead.rs @@ -824,7 +824,7 @@ pub(crate) fn aead_open_combined( #[cfg(test)] mod tests { use super::*; - use crate::test::from_hex; + use crate::{iv::FixedLength, test::from_hex}; #[test] fn test_aes_128() { @@ -845,25 +845,33 @@ mod tests { #[allow(deprecated)] less_safe_key // Test coverage for `seal_in_place`, which calls `seal_in_place_append_tag`. - .seal_in_place(Nonce(nonce), Aad::empty(), &mut in_out) + .seal_in_place(Nonce(FixedLength::from(nonce)), Aad::empty(), &mut in_out) .unwrap(); let mut in_out_clone = in_out.clone(); let nonce: [u8; NONCE_LEN] = og_nonce.as_slice().try_into().unwrap(); assert!(less_safe_key - .open_in_place(Nonce(nonce), Aad::from("test"), &mut in_out_clone) + .open_in_place( + Nonce(FixedLength::from(nonce)), + Aad::from("test"), + &mut in_out_clone + ) .is_err()); let mut in_out_clone = in_out.clone(); let mut nonce: [u8; NONCE_LEN] = og_nonce.as_slice().try_into().unwrap(); nonce[0] = 0; assert!(less_safe_key - .open_in_place(Nonce(nonce), Aad::empty(), &mut in_out_clone) + .open_in_place( + Nonce(FixedLength::from(nonce)), + Aad::empty(), + &mut in_out_clone + ) .is_err()); let nonce: [u8; NONCE_LEN] = og_nonce.as_slice().try_into().unwrap(); less_safe_key - .open_in_place(Nonce(nonce), Aad::empty(), &mut in_out) + .open_in_place(Nonce(FixedLength::from(nonce)), Aad::empty(), &mut in_out) .unwrap(); assert_eq!(plaintext, in_out[..plaintext.len()]); diff --git a/aws-lc-rs/src/aead/chacha20_poly1305_openssh.rs b/aws-lc-rs/src/aead/chacha20_poly1305_openssh.rs index d6cf266650c..ad7673d022f 100644 --- a/aws-lc-rs/src/aead/chacha20_poly1305_openssh.rs +++ b/aws-lc-rs/src/aead/chacha20_poly1305_openssh.rs @@ -23,6 +23,7 @@ use super::{poly1305, Nonce, Tag}; use crate::cipher::block::BLOCK_LEN; use crate::cipher::chacha::{self, ChaCha20Key}; +use crate::iv::FixedLength; use crate::{constant_time, endian::BigEndian, error}; use core::convert::TryInto; @@ -55,7 +56,7 @@ impl SealingKey { tag_out: &mut [u8; TAG_LEN], ) { let nonce = make_nonce(sequence_number); - let poly_key = derive_poly1305_key(&self.key.k_2, Nonce(nonce.0)); + let poly_key = derive_poly1305_key(&self.key.k_2, Nonce(FixedLength::from(nonce.as_ref()))); { let (len_in_out, data_and_padding_in_out) = @@ -130,7 +131,7 @@ impl OpeningKey { // We must verify the tag before decrypting so that // `ciphertext_in_plaintext_out` is unmodified if verification fails. // This is beyond what we guarantee. - let poly_key = derive_poly1305_key(&self.key.k_2, Nonce(nonce.0)); + let poly_key = derive_poly1305_key(&self.key.k_2, Nonce(FixedLength::from(nonce.as_ref()))); verify(poly_key, ciphertext_in_plaintext_out, tag)?; let plaintext_in_ciphertext_out = &mut ciphertext_in_plaintext_out[PACKET_LENGTH_LEN..]; diff --git a/aws-lc-rs/src/aead/nonce.rs b/aws-lc-rs/src/aead/nonce.rs index 525ca21b8a2..0ef3b4365d5 100644 --- a/aws-lc-rs/src/aead/nonce.rs +++ b/aws-lc-rs/src/aead/nonce.rs @@ -5,7 +5,7 @@ use crate::endian::{ArrayEncoding, BigEndian, Encoding}; use crate::error; -use std::convert::TryInto; +use crate::iv::FixedLength; use std::mem::transmute_copy; /// A nonce for a single AEAD opening or sealing operation. @@ -14,7 +14,7 @@ use std::mem::transmute_copy; /// /// `Nonce` intentionally doesn't implement `Clone` to ensure that each one is /// consumed at most once. -pub struct Nonce(pub(crate) [u8; NONCE_LEN]); +pub struct Nonce(pub(crate) FixedLength); impl Nonce { /// Constructs a `Nonce` with the given value, assuming that the value is @@ -25,8 +25,7 @@ impl Nonce { /// `error::Unspecified` when byte slice length is not `NONCE_LEN` #[inline] pub fn try_assume_unique_for_key(value: &[u8]) -> Result { - let value: &[u8; NONCE_LEN] = value.try_into()?; - Ok(Self::assume_unique_for_key(*value)) + Ok(Self(FixedLength::::try_from(value)?)) } /// Constructs a `Nonce` with the given value, assuming that the value is @@ -34,21 +33,21 @@ impl Nonce { #[inline] #[must_use] pub fn assume_unique_for_key(value: [u8; NONCE_LEN]) -> Self { - Self(value) + Self(FixedLength::::from(value)) } } impl AsRef<[u8; NONCE_LEN]> for Nonce { #[inline] fn as_ref(&self) -> &[u8; NONCE_LEN] { - &self.0 + self.0.as_ref() } } impl From<&[u8; NONCE_LEN]> for Nonce { #[inline] fn from(bytes: &[u8; NONCE_LEN]) -> Self { - Nonce(bytes.to_owned()) + Self(FixedLength::from(bytes)) } } @@ -57,7 +56,7 @@ impl From<&[u32; NONCE_LEN / 4]> for Nonce { fn from(values: &[u32; NONCE_LEN / 4]) -> Self { unsafe { let bytes: [u8; NONCE_LEN] = transmute_copy(values); - Nonce(bytes) + Nonce(FixedLength::from(bytes)) } } } @@ -66,7 +65,7 @@ impl From> for Nonce { #[inline] fn from(number: BigEndian) -> Self { let nonce = [BigEndian::ZERO, BigEndian::ZERO, number]; - Nonce(*(nonce.as_byte_array())) + Nonce(FixedLength::from(*(nonce.as_byte_array()))) } } @@ -76,7 +75,7 @@ impl From<&[u8; IV_LEN]> for Nonce { fn from(bytes: &[u8; IV_LEN]) -> Self { let mut nonce_bytes = [0u8; NONCE_LEN]; nonce_bytes.copy_from_slice(&bytes[0..NONCE_LEN]); - Nonce(nonce_bytes) + Nonce(FixedLength::from(nonce_bytes)) } } diff --git a/aws-lc-rs/src/aead/nonce_sequence/counter32.rs b/aws-lc-rs/src/aead/nonce_sequence/counter32.rs index c7d8ac248b2..8f04f4fb765 100644 --- a/aws-lc-rs/src/aead/nonce_sequence/counter32.rs +++ b/aws-lc-rs/src/aead/nonce_sequence/counter32.rs @@ -3,6 +3,7 @@ use crate::aead::{Nonce, NonceSequence, NONCE_LEN}; use crate::error::Unspecified; +use crate::iv::FixedLength; /// `Counter32` is an implementation of the `NonceSequence` trait. /// The internal state of a `Counter32` is a 32-bit unsigned counter that @@ -118,7 +119,7 @@ impl NonceSequence for Counter32 { nonce_bytes[..8].copy_from_slice(&self.identifier); nonce_bytes[8..].copy_from_slice(&counter_bytes); self.counter = self.counter.wrapping_add(1); - Ok(Nonce(nonce_bytes)) + Ok(Nonce(FixedLength::from(nonce_bytes))) } } @@ -134,7 +135,8 @@ mod tests { .counter(7) .build(); assert_eq!(0, cns.generated()); - let nonce = cns.advance().unwrap().0; + let nonce = cns.advance().unwrap(); + let nonce = nonce.as_ref(); assert_eq!(8, cns.counter()); assert_eq!( [0xA1, 0xB2, 0xC3, 0xD4, 0xA2, 0xB3, 0xC4, 0xD5], @@ -144,9 +146,10 @@ mod tests { assert_eq!(1, cns.generated()); assert_eq!( nonce, - [0xA1, 0xB2, 0xC3, 0xD4, 0xA2, 0xB3, 0xC4, 0xD5, 0, 0, 0, 7] + &[0xA1, 0xB2, 0xC3, 0xD4, 0xA2, 0xB3, 0xC4, 0xD5, 0, 0, 0, 7] ); - let nonce = cns.advance().unwrap().0; + let nonce = cns.advance().unwrap(); + let nonce = nonce.as_ref(); assert_eq!(2, cns.generated()); assert_eq!(9, cns.counter()); assert_eq!( @@ -155,17 +158,19 @@ mod tests { ); assert_eq!( nonce, - [0xA1, 0xB2, 0xC3, 0xD4, 0xA2, 0xB3, 0xC4, 0xD5, 0, 0, 0, 8] + &[0xA1, 0xB2, 0xC3, 0xD4, 0xA2, 0xB3, 0xC4, 0xD5, 0, 0, 0, 8] ); } #[test] fn test_counter32() { let mut cns = Counter32Builder::new().counter(0x_4CB0_16EA_u32).build(); - let nonce = cns.advance().unwrap().0; - assert_eq!(nonce, [0, 0, 0, 0, 0, 0, 0, 0, 0x4C, 0xB0, 0x16, 0xEA]); - let nonce = cns.advance().unwrap().0; - assert_eq!(nonce, [0, 0, 0, 0, 0, 0, 0, 0, 0x4C, 0xB0, 0x16, 0xEB]); + let nonce = cns.advance().unwrap(); + let nonce = nonce.as_ref(); + assert_eq!(nonce, &[0, 0, 0, 0, 0, 0, 0, 0, 0x4C, 0xB0, 0x16, 0xEA]); + let nonce = cns.advance().unwrap(); + let nonce = nonce.as_ref(); + assert_eq!(nonce, &[0, 0, 0, 0, 0, 0, 0, 0, 0x4C, 0xB0, 0x16, 0xEB]); } #[test] @@ -174,10 +179,12 @@ mod tests { .counter(0x_6A_u32) .identifier(0x_7B_u64.to_be_bytes()) .build(); - let nonce = cns.advance().unwrap().0; - assert_eq!(nonce, [0, 0, 0, 0, 0, 0, 0, 0x7B, 0, 0, 0, 0x6A]); - let nonce = cns.advance().unwrap().0; - assert_eq!(nonce, [0, 0, 0, 0, 0, 0, 0, 0x7B, 0, 0, 0, 0x6B]); + let nonce = cns.advance().unwrap(); + let nonce = nonce.as_ref(); + assert_eq!(nonce, &[0, 0, 0, 0, 0, 0, 0, 0x7B, 0, 0, 0, 0x6A]); + let nonce = cns.advance().unwrap(); + let nonce = nonce.as_ref(); + assert_eq!(nonce, &[0, 0, 0, 0, 0, 0, 0, 0x7B, 0, 0, 0, 0x6B]); } #[test] @@ -185,7 +192,7 @@ mod tests { let mut cns = Counter32Builder::new().limit(1).build(); assert_eq!(1, cns.limit()); assert_eq!(0, cns.generated()); - let _nonce = cns.advance().unwrap().0; + let _nonce = cns.advance().unwrap(); assert_eq!(1, cns.generated()); assert!(cns.advance().is_err()); } diff --git a/aws-lc-rs/src/aead/nonce_sequence/counter64.rs b/aws-lc-rs/src/aead/nonce_sequence/counter64.rs index 8e294101606..0ebcb74dfe3 100644 --- a/aws-lc-rs/src/aead/nonce_sequence/counter64.rs +++ b/aws-lc-rs/src/aead/nonce_sequence/counter64.rs @@ -3,6 +3,7 @@ use crate::aead::{Nonce, NonceSequence, NONCE_LEN}; use crate::error::Unspecified; +use crate::iv::FixedLength; /// `Counter64` is an implementation of the `NonceSequence` trait. /// The internal state of a `Counter64` is a 64-bit unsigned counter that @@ -116,7 +117,7 @@ impl NonceSequence for Counter64 { nonce_bytes[..4].copy_from_slice(&self.identifier); nonce_bytes[4..].copy_from_slice(&bytes); self.counter = self.counter.wrapping_add(1); - Ok(Nonce(nonce_bytes)) + Ok(Nonce(FixedLength::from(nonce_bytes))) } } @@ -132,17 +133,19 @@ mod tests { .counter(7) .build(); assert_eq!(0, cns.generated()); - let nonce = cns.advance().unwrap().0; + let nonce = cns.advance().unwrap(); + let nonce = nonce.as_ref(); assert_eq!(8, cns.counter()); assert_eq!([0xA1, 0xB2, 0xC3, 0xD4], cns.identifier()); assert_eq!(u64::MAX, cns.limit()); assert_eq!(1, cns.generated()); - assert_eq!(nonce, [0xA1, 0xB2, 0xC3, 0xD4, 0, 0, 0, 0, 0, 0, 0, 7]); - let nonce = cns.advance().unwrap().0; + assert_eq!(nonce, &[0xA1, 0xB2, 0xC3, 0xD4, 0, 0, 0, 0, 0, 0, 0, 7]); + let nonce = cns.advance().unwrap(); + let nonce = nonce.as_ref(); assert_eq!(2, cns.generated()); assert_eq!(9, cns.counter()); assert_eq!([0xA1, 0xB2, 0xC3, 0xD4], cns.identifier()); - assert_eq!(nonce, [0xA1, 0xB2, 0xC3, 0xD4, 0, 0, 0, 0, 0, 0, 0, 8]); + assert_eq!(nonce, &[0xA1, 0xB2, 0xC3, 0xD4, 0, 0, 0, 0, 0, 0, 0, 8]); } #[test] @@ -150,10 +153,12 @@ mod tests { let mut cns = Counter64Builder::new() .counter(0x0002_4CB0_16EA_u64) .build(); - let nonce = cns.advance().unwrap().0; - assert_eq!(nonce, [0, 0, 0, 0, 0, 0, 0, 0x02, 0x4C, 0xB0, 0x16, 0xEA]); - let nonce = cns.advance().unwrap().0; - assert_eq!(nonce, [0, 0, 0, 0, 0, 0, 0, 0x02, 0x4C, 0xB0, 0x16, 0xEB]); + let nonce = cns.advance().unwrap(); + let nonce = nonce.as_ref(); + assert_eq!(nonce, &[0, 0, 0, 0, 0, 0, 0, 0x02, 0x4C, 0xB0, 0x16, 0xEA]); + let nonce = cns.advance().unwrap(); + let nonce = nonce.as_ref(); + assert_eq!(nonce, &[0, 0, 0, 0, 0, 0, 0, 0x02, 0x4C, 0xB0, 0x16, 0xEB]); } #[test] @@ -162,10 +167,12 @@ mod tests { .counter(0x_6A_u64) .identifier(0x_7B_u32.to_be_bytes()) .build(); - let nonce = cns.advance().unwrap().0; - assert_eq!(nonce, [0, 0, 0, 0x7B, 0, 0, 0, 0, 0, 0, 0, 0x6A]); - let nonce = cns.advance().unwrap().0; - assert_eq!(nonce, [0, 0, 0, 0x7B, 0, 0, 0, 0, 0, 0, 0, 0x6B]); + let nonce = cns.advance().unwrap(); + let nonce = nonce.as_ref(); + assert_eq!(nonce, &[0, 0, 0, 0x7B, 0, 0, 0, 0, 0, 0, 0, 0x6A]); + let nonce = cns.advance().unwrap(); + let nonce = nonce.as_ref(); + assert_eq!(nonce, &[0, 0, 0, 0x7B, 0, 0, 0, 0, 0, 0, 0, 0x6B]); } #[test] @@ -173,7 +180,7 @@ mod tests { let mut cns = Counter64Builder::new().limit(1).build(); assert_eq!(1, cns.limit()); assert_eq!(0, cns.generated()); - let _nonce = cns.advance().unwrap().0; + let _nonce = cns.advance().unwrap(); assert_eq!(1, cns.generated()); assert!(cns.advance().is_err()); } diff --git a/aws-lc-rs/src/aead/quic.rs b/aws-lc-rs/src/aead/quic.rs index c3da145f3e0..fa510673af8 100644 --- a/aws-lc-rs/src/aead/quic.rs +++ b/aws-lc-rs/src/aead/quic.rs @@ -8,8 +8,9 @@ //! See draft-ietf-quic-tls. use crate::cipher::aes::encrypt_block_aes; +use crate::cipher::block; use crate::cipher::chacha::encrypt_block_chacha20; -use crate::cipher::{block, SymmetricCipherKey}; +use crate::cipher::key::SymmetricCipherKey; use crate::hkdf::KeyType; use crate::{derive_debug_via_id, error, hkdf}; use core::convert::TryFrom; @@ -150,10 +151,10 @@ fn cipher_new_mask( let block = block::Block::from(&sample); let encrypted_block = match cipher_key { - SymmetricCipherKey::Aes128(.., aes_key) | SymmetricCipherKey::Aes256(.., aes_key) => { - encrypt_block_aes(aes_key, block) + SymmetricCipherKey::Aes128 { enc_key, .. } | SymmetricCipherKey::Aes256 { enc_key, .. } => { + encrypt_block_aes(enc_key, block) } - SymmetricCipherKey::ChaCha20(key_bytes) => { + SymmetricCipherKey::ChaCha20 { raw_key } => { let plaintext = block.as_ref(); let counter_bytes: &[u8; 4] = plaintext[0..=3] .try_into() @@ -164,7 +165,7 @@ fn cipher_new_mask( let input = block::Block::zero(); unsafe { let counter = std::mem::transmute::<[u8; 4], u32>(*counter_bytes).to_le(); - encrypt_block_chacha20(key_bytes, input, nonce, counter)? + encrypt_block_chacha20(raw_key, input, nonce, counter)? } } }; diff --git a/aws-lc-rs/src/cipher.rs b/aws-lc-rs/src/cipher.rs index 0d74c56d7da..4a30eb18969 100644 --- a/aws-lc-rs/src/cipher.rs +++ b/aws-lc-rs/src/cipher.rs @@ -3,143 +3,1238 @@ // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC +//! Block and Stream Ciphers for Encryption and Decryption. +//! +//! # 🛑 Read Before Using +//! +//! This module provides access to block and stream cipher algorithms. +//! The modes provided here only provide confidentiality, but **do not** +//! provide integrity or authentication verification of ciphertext. +//! +//! These algorithms are provided solely for applications requring them +//! in order to maintain backwards compatability in legacy applications. +//! +//! If you are developing new applications requring data encryption see +//! the algorithms provided in [`aead`](crate::aead). +//! +//! # Examples +//! +//! ## AES-128 CBC Mode Encryption +//! +//! ```rust +//! # use std::error::Error; +//! # +//! # fn main() -> Result<(), Box> { +//! use std::io::Read; +//! use aws_lc_rs::cipher::{ +//! PaddedBlockDecryptingKey, PaddedBlockEncryptingKey, UnboundCipherKey, AES_128, +//! }; +//! +//! let original_message = "This is a secret message!".as_bytes(); +//! let mut in_out_buffer = Vec::from(original_message); +//! +//! let key_bytes: &[u8] = &[ +//! 0xff, 0x0b, 0xe5, 0x84, 0x64, 0x0b, 0x00, 0xc8, 0x90, 0x7a, 0x4b, 0xbf, 0x82, 0x7c, 0xb6, +//! 0xd1, +//! ]; +//! +//! let key = UnboundCipherKey::new(&AES_128, key_bytes)?; +//! let mut encrypting_key = PaddedBlockEncryptingKey::cbc_pkcs7(key)?; +//! let context = encrypting_key.encrypt(&mut in_out_buffer)?; +//! +//! let key = UnboundCipherKey::new(&AES_128, key_bytes)?; +//! let mut decrypting_key = PaddedBlockDecryptingKey::cbc_pkcs7(key)?; +//! let plaintext = decrypting_key.decrypt(&mut in_out_buffer, context)?; +//! assert_eq!(original_message, plaintext); +//! # +//! # +//! # Ok(()) +//! # } +//! ``` +//! +//! ## AES-128 CTR Mode Encryption +//! +//! ```rust +//! # use std::error::Error; +//! # +//! # fn main() -> Result<(), Box> { +//! use aws_lc_rs::cipher::{DecryptingKey, EncryptingKey, UnboundCipherKey, AES_128}; +//! +//! let original_message = "This is a secret message!".as_bytes(); +//! let mut in_out_buffer = Vec::from(original_message); +//! +//! let key_bytes: &[u8] = &[ +//! 0xff, 0x0b, 0xe5, 0x84, 0x64, 0x0b, 0x00, 0xc8, 0x90, 0x7a, 0x4b, 0xbf, 0x82, 0x7c, 0xb6, +//! 0xd1, +//! ]; +//! +//! let key = UnboundCipherKey::new(&AES_128, key_bytes)?; +//! let mut encrypting_key = EncryptingKey::ctr(key)?; +//! let context = encrypting_key.encrypt(&mut in_out_buffer)?; +//! +//! let key = UnboundCipherKey::new(&AES_128, key_bytes)?; +//! let mut decrypting_key = DecryptingKey::ctr(key)?; +//! let plaintext = decrypting_key.decrypt(&mut in_out_buffer, context)?; +//! assert_eq!(original_message, plaintext); +//! # +//! # Ok(()) +//! # } +//! ``` +//! + +#![allow(clippy::module_name_repetitions)] + pub(crate) mod aes; pub(crate) mod block; pub(crate) mod chacha; +pub(crate) mod key; -use crate::cipher::aes::encrypt_block_aes; -use crate::cipher::block::Block; -use crate::cipher::chacha::ChaCha20Key; use crate::error::Unspecified; -use aws_lc::{AES_set_encrypt_key, AES_KEY}; -use std::mem::{size_of, transmute, MaybeUninit}; -use std::os::raw::c_uint; -use std::ptr; +use crate::hkdf; +use crate::hkdf::KeyType; +use crate::iv::FixedLength; +use aws_lc::{AES_cbc_encrypt, AES_ctr128_encrypt, AES_DECRYPT, AES_ENCRYPT, AES_KEY}; +use key::SymmetricCipherKey; +use std::fmt::Debug; +use std::mem::MaybeUninit; use zeroize::Zeroize; -pub(crate) enum SymmetricCipherKey { - Aes128(AES_KEY), - Aes256(AES_KEY), - ChaCha20(ChaCha20Key), +/// The cipher block padding strategy. +#[non_exhaustive] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum PaddingStrategy { + /// PKCS#7 Padding. ([See RFC 5652](https://datatracker.ietf.org/doc/html/rfc5652#section-6.3)) + PKCS7, +} + +impl PaddingStrategy { + fn add_padding(self, block_len: usize, in_out: &mut InOut) -> Result<(), Unspecified> + where + InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>, + { + match self { + PaddingStrategy::PKCS7 => { + let mut padding_buffer = [0u8; MAX_CIPHER_BLOCK_LEN]; + + let in_out_len = in_out.as_mut().len(); + // This implements PKCS#7 padding scheme, used by aws-lc if we were using EVP_CIPHER API's + let remainder = in_out_len % block_len; + let padding_size = block_len - remainder; + let v: u8 = padding_size.try_into().map_err(|_| Unspecified)?; + padding_buffer.fill(v); + // Possible heap allocation here :( + in_out.extend(padding_buffer[0..padding_size].iter()); + } + } + Ok(()) + } + + fn remove_padding(self, block_len: usize, in_out: &mut [u8]) -> Result<&mut [u8], Unspecified> { + match self { + PaddingStrategy::PKCS7 => { + let block_size: u8 = block_len.try_into().map_err(|_| Unspecified)?; + + if in_out.is_empty() || in_out.len() < block_len { + return Err(Unspecified); + } + + let padding: u8 = in_out[in_out.len() - 1]; + if padding == 0 || padding > block_size { + return Err(Unspecified); + } + + for item in in_out.iter().skip(in_out.len() - padding as usize) { + if *item != padding { + return Err(Unspecified); + } + } + + let final_len = in_out.len() - padding as usize; + Ok(&mut in_out[0..final_len]) + } + } + } +} + +/// The number of bytes in an AES 128-bit key +pub const AES_128_KEY_LEN: usize = 16; + +/// The number of bytes in an AES 256-bit key +pub const AES_256_KEY_LEN: usize = 32; + +const MAX_CIPHER_KEY_LEN: usize = AES_256_KEY_LEN; + +/// The number of bytes for an AES-CBC initialization vector (IV) +pub const AES_CBC_IV_LEN: usize = 16; + +/// The number of bytes for an AES-CTR initialization vector (IV) +pub const AES_CTR_IV_LEN: usize = 16; +const AES_BLOCK_LEN: usize = 16; + +const MAX_CIPHER_BLOCK_LEN: usize = AES_BLOCK_LEN; + +const IV_LEN_128_BIT: usize = 16; + +/// The cipher operating mode. +#[non_exhaustive] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum OperatingMode { + /// Cipher block chaining (CBC) mode. + CBC, + + /// Counter (CTR) mode. + CTR, } -unsafe impl Send for SymmetricCipherKey {} -// The AES_KEY value is only used as a `*const AES_KEY` in calls to `AES_encrypt`. -unsafe impl Sync for SymmetricCipherKey {} +/// The contextual data used to encrypted/decrypt data. +/// +/// # Examples +/// +/// ## Constructing a `CipherContext` for decryption. +/// +/// ```rust +/// # use std::error::Error; +/// # fn main() -> Result<(), Box> { +/// use aws_lc_rs::cipher::{CipherContext, DecryptingKey, UnboundCipherKey, AES_128}; +/// use aws_lc_rs::iv::FixedLength; +/// +/// let context = CipherContext::Iv128(FixedLength::<16>::from(&[ +/// 0x8d, 0xdb, 0x7d, 0xf1, 0x56, 0xf5, 0x1c, 0xde, 0x63, 0xe3, 0x4a, 0x34, 0xb0, 0xdf, 0x28, +/// 0xf0, +/// ])); +/// +/// let ciphertext: &[u8] = &[ +/// 0x79, 0x8c, 0x04, 0x58, 0xcf, 0x98, 0xb1, 0xe9, 0x97, 0x6b, 0xa1, 0xce, +/// ]; +/// +/// let mut in_out_buffer = Vec::from(ciphertext); +/// +/// let key = UnboundCipherKey::new( +/// &AES_128, +/// &[ +/// 0x5b, 0xfc, 0xe7, 0x5e, 0x57, 0xc5, 0x4d, 0xda, 0x2d, 0xd4, 0x7e, 0x07, 0x0a, 0xef, +/// 0x43, 0x29, +/// ], +/// )?; +/// let mut decrypting_key = DecryptingKey::ctr(key)?; +/// let plaintext = decrypting_key.decrypt(&mut in_out_buffer, context)?; +/// assert_eq!("Hello World!".as_bytes(), plaintext); +/// +/// # Ok(()) +/// # } +/// ``` +/// +/// ## Getting an immutable reference to an IV slice. +/// +/// `CipherContext` implements `TryFrom<&CipherContext>` for `&[u8]` allowing immutable references +/// to IV bytes returned from cipher encryption operations. +/// +/// ```rust +/// # use std::error::Error; +/// # fn main() -> Result<(), Box> { +/// use aws_lc_rs::cipher::CipherContext; +/// # use aws_lc_rs::cipher::{EncryptingKey, UnboundCipherKey, AES_128}; +/// # let original_message = "Hello World!".as_bytes(); +/// # let mut in_out_buffer = Vec::from(original_message); +/// # let key_bytes: &[u8] = &[ +/// # 0x68, 0xf9, 0x46, 0x1a, 0xde, 0x8d, 0x35, 0x38, 0x7b, 0x50, 0xcc, 0x9a, 0x36, 0x64, 0xf8, +/// # 0x9d, +/// # ]; +/// # let key = UnboundCipherKey::new(&AES_128, key_bytes)?; +/// # let mut encrypting_key = EncryptingKey::ctr(key)?; +/// # +/// let context: CipherContext = encrypting_key.encrypt(&mut in_out_buffer)?; +/// let iv_bytes: &[u8] = (&context).try_into()?; +/// assert_eq!(16, iv_bytes.len()); +/// # +/// # Ok(()) +/// # } +/// ``` +/// +/// +#[non_exhaustive] +pub enum CipherContext { + /// A 128-bit Initialization Vector. + Iv128(FixedLength), + + /// No input to the cipher mode. + None, +} + +impl<'a> TryFrom<&'a CipherContext> for &'a [u8] { + type Error = Unspecified; + + fn try_from(value: &'a CipherContext) -> Result { + match value { + CipherContext::Iv128(iv) => Ok(iv.as_ref()), + CipherContext::None => Err(Unspecified), + } + } +} -impl Drop for SymmetricCipherKey { - fn drop(&mut self) { - // Aes128Key, Aes256Key and ChaCha20Key implement Drop separately. +impl Debug for CipherContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - SymmetricCipherKey::Aes128(aes_key) | SymmetricCipherKey::Aes256(aes_key) => unsafe { - #[allow(clippy::transmute_ptr_to_ptr)] - let value: &mut [u8; size_of::()] = transmute(aes_key); - value.zeroize(); + Self::Iv128(_) => write!(f, "Iv128"), + Self::None => write!(f, "None"), + } + } +} + +#[non_exhaustive] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +/// Cipher algorithm identifier. +pub enum AlgorithmId { + /// AES 128-bit + Aes128, + + /// AES 256-bit + Aes256, +} + +/// A cipher algorithm. +#[derive(Debug, PartialEq, Eq)] +pub struct Algorithm { + id: AlgorithmId, + key_len: usize, + block_len: usize, +} + +/// AES 128-bit cipher +pub static AES_128: Algorithm = Algorithm { + id: AlgorithmId::Aes128, + key_len: AES_128_KEY_LEN, + block_len: AES_BLOCK_LEN, +}; + +/// AES 256-bit cipher +pub static AES_256: Algorithm = Algorithm { + id: AlgorithmId::Aes256, + key_len: AES_256_KEY_LEN, + block_len: AES_BLOCK_LEN, +}; + +impl Algorithm { + fn id(&self) -> &AlgorithmId { + &self.id + } + + const fn block_len(&self) -> usize { + self.block_len + } + + fn new_cipher_context(&self, mode: OperatingMode) -> Result { + match self.id { + AlgorithmId::Aes128 | AlgorithmId::Aes256 => match mode { + OperatingMode::CBC | OperatingMode::CTR => { + Ok(CipherContext::Iv128(FixedLength::new()?)) + } + }, + } + } + + fn is_valid_cipher_context(&self, mode: OperatingMode, input: &CipherContext) -> bool { + match self.id { + AlgorithmId::Aes128 | AlgorithmId::Aes256 => match mode { + OperatingMode::CBC | OperatingMode::CTR => { + matches!(input, CipherContext::Iv128(_)) + } }, - SymmetricCipherKey::ChaCha20(_) => {} } } } -impl SymmetricCipherKey { - pub(crate) fn aes128(key_bytes: &[u8]) -> Result { - if key_bytes.len() != 16 { +#[allow(clippy::missing_fields_in_debug)] +impl Debug for UnboundCipherKey { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + f.debug_struct("UnboundCipherKey") + .field("algorithm", &self.algorithm) + .finish() + } +} + +impl From> for UnboundCipherKey { + fn from(okm: hkdf::Okm<&'static Algorithm>) -> Self { + let mut key_bytes = [0; MAX_CIPHER_KEY_LEN]; + let key_bytes = &mut key_bytes[..okm.len().key_len]; + let algorithm = *okm.len(); + okm.fill(key_bytes).unwrap(); + Self::new(algorithm, key_bytes).unwrap() + } +} + +impl KeyType for &'static Algorithm { + fn len(&self) -> usize { + self.key_len + } +} + +/// A key bound to a particular cipher algorithm. +pub struct UnboundCipherKey { + algorithm: &'static Algorithm, + key: SymmetricCipherKey, +} + +impl UnboundCipherKey { + /// Constructs an [`UnboundCipherKey`]. + /// + /// # Errors + /// + /// * [`Unspecified`] if `key_bytes.len()` does not match the + /// length required by `algorithm`. + /// + pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result { + let key = match algorithm.id() { + AlgorithmId::Aes128 => SymmetricCipherKey::aes128(key_bytes), + AlgorithmId::Aes256 => SymmetricCipherKey::aes256(key_bytes), + }?; + Ok(UnboundCipherKey { algorithm, key }) + } + + #[inline] + #[must_use] + /// Returns the algorithm associated with this key. + pub fn algorithm(&self) -> &'static Algorithm { + self.algorithm + } +} + +/// A cipher encryption key that performs block padding. +pub struct PaddedBlockEncryptingKey { + key: UnboundCipherKey, + mode: OperatingMode, + padding: PaddingStrategy, +} + +impl PaddedBlockEncryptingKey { + /// Constructs a new `PaddedBlockEncryptingKey` cipher with chaining block cipher (CBC) mode. + /// Plaintext data is padded following the PKCS#7 scheme. + /// + /// # Errors + /// * [`Unspecified`]: Returned if there is an error cosntructing a `PaddedBlockEncryptingKey`. + /// + pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result { + PaddedBlockEncryptingKey::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7) + } + + #[allow(clippy::unnecessary_wraps)] + fn new( + key: UnboundCipherKey, + mode: OperatingMode, + padding: PaddingStrategy, + ) -> Result { + Ok(PaddedBlockEncryptingKey { key, mode, padding }) + } + + /// Returns the cipher algorithm. + #[must_use] + pub fn algorithm(&self) -> &Algorithm { + self.key.algorithm() + } + + /// Returns the cipher operating mode. + #[must_use] + pub fn mode(&self) -> OperatingMode { + self.mode + } + + /// Pads and encrypts data provided in `in_out` in-place. + /// Returns a references to the encryted data. + /// + /// # Errors + /// * [`Unspecified`]: Returned if encryption fails. + /// + pub fn encrypt(&self, in_out: &mut InOut) -> Result + where + InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>, + { + let context = self.key.algorithm.new_cipher_context(self.mode)?; + self.less_safe_encrypt(in_out, context) + } + + /// Pads and encrypts data provided in `in_out` in-place. + /// Returns a references to the encryted data. + /// + /// # Errors + /// * [`Unspecified`]: Returned if encryption fails. + /// + pub fn less_safe_encrypt( + &self, + in_out: &mut InOut, + context: CipherContext, + ) -> Result + where + InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>, + { + if !self + .key + .algorithm() + .is_valid_cipher_context(self.mode, &context) + { return Err(Unspecified); } - unsafe { - let mut aes_key = MaybeUninit::::uninit(); - #[allow(clippy::cast_possible_truncation)] - if 0 != AES_set_encrypt_key( - key_bytes.as_ptr(), - (key_bytes.len() * 8) as c_uint, - aes_key.as_mut_ptr(), - ) { - return Err(Unspecified); - } - let aes_key = aes_key.assume_init(); + self.padding + .add_padding(self.algorithm().block_len(), in_out)?; + encrypt(&self.key, self.mode, in_out.as_mut(), context) + } +} + +impl Debug for PaddedBlockEncryptingKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PaddedBlockEncryptingKey") + .field("key", &self.key) + .field("mode", &self.mode) + .field("padding", &self.padding) + .finish() + } +} + +/// A cipher decryption key that performs block padding. +pub struct PaddedBlockDecryptingKey { + key: UnboundCipherKey, + mode: OperatingMode, + padding: PaddingStrategy, +} + +impl PaddedBlockDecryptingKey { + /// Constructs a new `PaddedBlockDecryptingKey` cipher with chaining block cipher (CBC) mode. + /// Decrypted data is unpadded following the PKCS#7 scheme. + /// + /// # Errors + /// * [`Unspecified`]: Returned if there is an error constructing the `PaddedBlockDecryptingKey`. + /// + pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result { + PaddedBlockDecryptingKey::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7) + } + + #[allow(clippy::unnecessary_wraps)] + fn new( + key: UnboundCipherKey, + mode: OperatingMode, + padding: PaddingStrategy, + ) -> Result { + Ok(PaddedBlockDecryptingKey { key, mode, padding }) + } - let mut kb = MaybeUninit::<[u8; 16]>::uninit(); - ptr::copy_nonoverlapping(key_bytes.as_ptr(), kb.as_mut_ptr().cast(), 16); - Ok(SymmetricCipherKey::Aes128(aes_key)) + /// Returns the cipher algorithm. + #[must_use] + pub fn algorithm(&self) -> &Algorithm { + self.key.algorithm() + } + + /// Returns the cipher operating mode. + #[must_use] + pub fn mode(&self) -> OperatingMode { + self.mode + } + + /// Decrypts and unpads data provided in `in_out` in-place. + /// Returns a references to the decrypted data. + /// + /// # Errors + /// * [`Unspecified`]: Returned if decryption fails. + /// + pub fn decrypt<'in_out>( + &self, + in_out: &'in_out mut [u8], + context: CipherContext, + ) -> Result<&'in_out mut [u8], Unspecified> { + if !self + .key + .algorithm() + .is_valid_cipher_context(self.mode, &context) + { + return Err(Unspecified); } + + let block_len = self.algorithm().block_len(); + let padding = self.padding; + let mut in_out = decrypt(&self.key, self.mode, in_out, context)?; + in_out = padding.remove_padding(block_len, in_out)?; + Ok(in_out) + } +} + +impl Debug for PaddedBlockDecryptingKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PaddedBlockDecryptingKey") + .field("key", &self.key) + .field("mode", &self.mode) + .field("padding", &self.padding) + .finish() + } +} + +/// A cipher encryption key that does not perform block padding. +pub struct EncryptingKey { + key: UnboundCipherKey, + mode: OperatingMode, +} + +impl EncryptingKey { + /// Constructs an `EncryptingKey` operating in counter (CTR) mode using the provided key. + /// + /// # Errors + /// * [`Unspecified`]: Returned if there is an error constructing the `EncryptingKey`. + /// + pub fn ctr(key: UnboundCipherKey) -> Result { + EncryptingKey::new(key, OperatingMode::CTR) + } + + #[allow(clippy::unnecessary_wraps)] + fn new(key: UnboundCipherKey, mode: OperatingMode) -> Result { + Ok(EncryptingKey { key, mode }) } - pub(crate) fn aes256(key_bytes: &[u8]) -> Result { - if key_bytes.len() != 32 { + /// Returns the cipher algorithm. + #[must_use] + pub fn algorithm(&self) -> &Algorithm { + self.key.algorithm() + } + + /// Returns the cipher operating mode. + #[must_use] + pub fn mode(&self) -> OperatingMode { + self.mode + } + + /// Encrypts the data provided in `in_out` in-place. + /// Returns a references to the decrypted data. + /// + /// # Errors + /// * [`Unspecified`]: Returned if cipher mode requires input to be a multiple of the block length, + /// and `in_out.len()` is not. Otherwise returned if encryption fails. + /// + pub fn encrypt(&self, in_out: &mut [u8]) -> Result { + let context = self.key.algorithm.new_cipher_context(self.mode)?; + self.less_safe_encrypt(in_out, context) + } + + /// Encrypts the data provided in `in_out` in-place using the provided `CipherContext`. + /// Returns a references to the decrypted data. + /// + /// # Errors + /// * [`Unspecified`]: Returned if cipher mode requires input to be a multiple of the block length, + /// and `in_out.len()` is not. Otherwise returned if encryption fails. + /// + pub fn less_safe_encrypt( + &self, + in_out: &mut [u8], + context: CipherContext, + ) -> Result { + if !self + .key + .algorithm() + .is_valid_cipher_context(self.mode, &context) + { return Err(Unspecified); } - unsafe { - let mut aes_key = MaybeUninit::::uninit(); - #[allow(clippy::cast_possible_truncation)] - if 0 != AES_set_encrypt_key( - key_bytes.as_ptr(), - (key_bytes.len() * 8) as c_uint, - aes_key.as_mut_ptr(), - ) { + encrypt(&self.key, self.mode, in_out, context) + } +} + +impl Debug for EncryptingKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EncryptingKey") + .field("key", &self.key) + .field("mode", &self.mode) + .finish() + } +} + +/// A cipher decryption key that does not perform block padding. +pub struct DecryptingKey { + key: UnboundCipherKey, + mode: OperatingMode, +} + +impl DecryptingKey { + /// Constructs a cipher decrypting key operating in counter (CTR) mode using the provided key and context. + /// + /// # Errors + /// * [`Unspecified`]: Returned if there is an error during decryption. + /// + pub fn ctr(key: UnboundCipherKey) -> Result { + DecryptingKey::new(key, OperatingMode::CTR) + } + + #[allow(clippy::unnecessary_wraps)] + fn new(key: UnboundCipherKey, mode: OperatingMode) -> Result { + Ok(DecryptingKey { key, mode }) + } + + /// Returns the cipher algorithm. + #[must_use] + pub fn algorithm(&self) -> &Algorithm { + self.key.algorithm() + } + + /// Returns the cipher operating mode. + #[must_use] + pub fn mode(&self) -> OperatingMode { + self.mode + } + + /// Decrypts the data provided in `in_out` in-place. + /// Returns a references to the decrypted data. + /// + /// # Errors + /// * [`Unspecified`]: Returned if cipher mode requires input to be a multiple of the block length, + /// and `in_out.len()` is not. Also returned if decryption fails. + /// + pub fn decrypt<'in_out>( + &self, + in_out: &'in_out mut [u8], + context: CipherContext, + ) -> Result<&'in_out mut [u8], Unspecified> { + decrypt(&self.key, self.mode, in_out, context) + } +} + +impl Debug for DecryptingKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DecryptingKey") + .field("key", &self.key) + .field("mode", &self.mode) + .finish() + } +} + +fn encrypt( + key: &UnboundCipherKey, + mode: OperatingMode, + in_out: &mut [u8], + context: CipherContext, +) -> Result { + let block_len = key.algorithm().block_len(); + + match mode { + OperatingMode::CTR => {} + _ => { + if (in_out.len() % block_len) != 0 { return Err(Unspecified); } - let aes_key = aes_key.assume_init(); - - let mut kb = MaybeUninit::<[u8; 32]>::uninit(); - ptr::copy_nonoverlapping(key_bytes.as_ptr(), kb.as_mut_ptr().cast(), 32); - Ok(SymmetricCipherKey::Aes256(aes_key)) } } - pub(crate) fn chacha20(key_bytes: &[u8]) -> Result { - if key_bytes.len() != 32 { - return Err(Unspecified); - } - let mut kb = MaybeUninit::<[u8; 32]>::uninit(); - unsafe { - ptr::copy_nonoverlapping(key_bytes.as_ptr(), kb.as_mut_ptr().cast(), 32); - Ok(SymmetricCipherKey::ChaCha20(ChaCha20Key(kb.assume_init()))) + match mode { + OperatingMode::CBC => match key.algorithm().id() { + AlgorithmId::Aes128 | AlgorithmId::Aes256 => encrypt_aes_cbc_mode(key, context, in_out), + }, + OperatingMode::CTR => match key.algorithm().id() { + AlgorithmId::Aes128 | AlgorithmId::Aes256 => encrypt_aes_ctr_mode(key, context, in_out), + }, + } +} + +fn decrypt<'in_out>( + key: &UnboundCipherKey, + mode: OperatingMode, + in_out: &'in_out mut [u8], + context: CipherContext, +) -> Result<&'in_out mut [u8], Unspecified> { + let block_len = key.algorithm().block_len(); + + match mode { + OperatingMode::CTR => {} + _ => { + if (in_out.len() % block_len) != 0 { + return Err(Unspecified); + } } } - #[allow(dead_code)] - #[inline] - pub(crate) fn encrypt_block(&self, block: Block) -> Block { - match self { - SymmetricCipherKey::Aes128(.., aes_key) | SymmetricCipherKey::Aes256(.., aes_key) => { - encrypt_block_aes(aes_key, block) + match mode { + OperatingMode::CBC => match key.algorithm().id() { + AlgorithmId::Aes128 | AlgorithmId::Aes256 => { + decrypt_aes_cbc_mode(key, context, in_out).map(|_| in_out) + } + }, + OperatingMode::CTR => match key.algorithm().id() { + AlgorithmId::Aes128 | AlgorithmId::Aes256 => { + decrypt_aes_ctr_mode(key, context, in_out).map(|_| in_out) } - SymmetricCipherKey::ChaCha20(..) => panic!("Unsupported algorithm!"), + }, + } +} + +fn encrypt_aes_ctr_mode( + key: &UnboundCipherKey, + context: CipherContext, + in_out: &mut [u8], +) -> Result { + #[allow(clippy::match_wildcard_for_single_variants)] + let key = match &key.key { + SymmetricCipherKey::Aes128 { enc_key, .. } | SymmetricCipherKey::Aes256 { enc_key, .. } => { + enc_key } + _ => return Err(Unspecified), + }; + + let mut iv = { + let mut iv = [0u8; AES_CTR_IV_LEN]; + iv.copy_from_slice((&context).try_into()?); + iv + }; + + let mut buffer = [0u8; AES_BLOCK_LEN]; + + aes_ctr128_encrypt(key, &mut iv, &mut buffer, in_out); + iv.zeroize(); + + Ok(context) +} + +fn decrypt_aes_ctr_mode( + key: &UnboundCipherKey, + context: CipherContext, + in_out: &mut [u8], +) -> Result { + // it's the same in CTR, just providing a nice named wrapper to match + encrypt_aes_ctr_mode(key, context, in_out) +} + +fn encrypt_aes_cbc_mode( + key: &UnboundCipherKey, + context: CipherContext, + in_out: &mut [u8], +) -> Result { + #[allow(clippy::match_wildcard_for_single_variants)] + let key = match &key.key { + SymmetricCipherKey::Aes128 { enc_key, .. } | SymmetricCipherKey::Aes256 { enc_key, .. } => { + enc_key + } + _ => return Err(Unspecified), + }; + + let mut iv = { + let mut iv = [0u8; AES_CBC_IV_LEN]; + iv.copy_from_slice((&context).try_into()?); + iv + }; + + aes_cbc_encrypt(key, &mut iv, in_out); + iv.zeroize(); + + Ok(context) +} + +fn decrypt_aes_cbc_mode( + key: &UnboundCipherKey, + context: CipherContext, + in_out: &mut [u8], +) -> Result { + #[allow(clippy::match_wildcard_for_single_variants)] + let key = match &key.key { + SymmetricCipherKey::Aes128 { dec_key, .. } | SymmetricCipherKey::Aes256 { dec_key, .. } => { + dec_key + } + _ => return Err(Unspecified), + }; + + let mut iv = { + let mut iv = [0u8; AES_CBC_IV_LEN]; + iv.copy_from_slice((&context).try_into()?); + iv + }; + + aes_cbc_decrypt(key, &mut iv, in_out); + iv.zeroize(); + + Ok(context) +} + +fn aes_ctr128_encrypt(key: &AES_KEY, iv: &mut [u8], block_buffer: &mut [u8], in_out: &mut [u8]) { + let mut num = MaybeUninit::::new(0); + + unsafe { + AES_ctr128_encrypt( + in_out.as_ptr(), + in_out.as_mut_ptr(), + in_out.len(), + key, + iv.as_mut_ptr(), + block_buffer.as_mut_ptr(), + num.as_mut_ptr(), + ); + }; + + Zeroize::zeroize(block_buffer); +} + +fn aes_cbc_encrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) { + unsafe { + AES_cbc_encrypt( + in_out.as_ptr(), + in_out.as_mut_ptr(), + in_out.len(), + key, + iv.as_mut_ptr(), + AES_ENCRYPT, + ); + } +} + +fn aes_cbc_decrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) { + unsafe { + AES_cbc_encrypt( + in_out.as_ptr(), + in_out.as_mut_ptr(), + in_out.len(), + key, + iv.as_mut_ptr(), + AES_DECRYPT, + ); } } #[cfg(test)] mod tests { use super::*; - use crate::cipher::block::BLOCK_LEN; use crate::test::from_hex; #[test] - fn test_encrypt_block_aes_128() { + fn test_debug() { + { + let aes_128_key_bytes = from_hex("000102030405060708090a0b0c0d0e0f").unwrap(); + let cipher_key = UnboundCipherKey::new(&AES_128, aes_128_key_bytes.as_slice()).unwrap(); + assert_eq!("UnboundCipherKey { algorithm: Algorithm { id: Aes128, key_len: 16, block_len: 16 } }", format!("{cipher_key:?}")); + } + + { + let aes_256_key_bytes = + from_hex("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f") + .unwrap(); + let cipher_key = UnboundCipherKey::new(&AES_256, aes_256_key_bytes.as_slice()).unwrap(); + assert_eq!("UnboundCipherKey { algorithm: Algorithm { id: Aes256, key_len: 32, block_len: 16 } }", format!("{cipher_key:?}")); + } + + { + let key_bytes = &[0u8; 16]; + let key = PaddedBlockEncryptingKey::cbc_pkcs7( + UnboundCipherKey::new(&AES_128, key_bytes).unwrap(), + ) + .unwrap(); + assert_eq!("PaddedBlockEncryptingKey { key: UnboundCipherKey { algorithm: Algorithm { id: Aes128, key_len: 16, block_len: 16 } }, mode: CBC, padding: PKCS7 }", format!("{key:?}")); + let mut data = vec![0u8; 16]; + let context = key.encrypt(&mut data).unwrap(); + assert_eq!("Iv128", format!("{context:?}")); + let key = PaddedBlockDecryptingKey::cbc_pkcs7( + UnboundCipherKey::new(&AES_128, key_bytes).unwrap(), + ) + .unwrap(); + assert_eq!("PaddedBlockDecryptingKey { key: UnboundCipherKey { algorithm: Algorithm { id: Aes128, key_len: 16, block_len: 16 } }, mode: CBC, padding: PKCS7 }", format!("{key:?}")); + } + + { + let key_bytes = &[0u8; 16]; + let key = + EncryptingKey::ctr(UnboundCipherKey::new(&AES_128, key_bytes).unwrap()).unwrap(); + assert_eq!("EncryptingKey { key: UnboundCipherKey { algorithm: Algorithm { id: Aes128, key_len: 16, block_len: 16 } }, mode: CTR }", format!("{key:?}")); + let mut data = vec![0u8; 16]; + let context = key.encrypt(&mut data).unwrap(); + assert_eq!("Iv128", format!("{context:?}")); + let key = + DecryptingKey::ctr(UnboundCipherKey::new(&AES_128, key_bytes).unwrap()).unwrap(); + assert_eq!("DecryptingKey { key: UnboundCipherKey { algorithm: Algorithm { id: Aes128, key_len: 16, block_len: 16 } }, mode: CTR }", format!("{key:?}")); + } + } + + fn helper_test_cipher_n_bytes( + key: &[u8], + alg: &'static Algorithm, + mode: OperatingMode, + n: usize, + ) { + let mut input: Vec = Vec::with_capacity(n); + for i in 0..n { + let byte: u8 = i.try_into().unwrap(); + input.push(byte); + } + + let cipher_key = UnboundCipherKey::new(alg, key).unwrap(); + let encrypting_key = EncryptingKey::new(cipher_key, mode).unwrap(); + + let mut in_out = input.clone(); + let decrypt_iv = encrypting_key.encrypt(&mut in_out).unwrap(); + + if n > 5 { + // There's no more than a 1 in 2^48 chance that this will fail randomly + assert_ne!(input.as_slice(), in_out); + } + + let cipher_key2 = UnboundCipherKey::new(alg, key).unwrap(); + let decrypting_key = DecryptingKey::new(cipher_key2, mode).unwrap(); + + let plaintext = decrypting_key.decrypt(&mut in_out, decrypt_iv).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + + fn helper_test_padded_cipher_n_bytes( + key: &[u8], + alg: &'static Algorithm, + mode: OperatingMode, + padding: PaddingStrategy, + n: usize, + ) { + let mut input: Vec = Vec::with_capacity(n); + for i in 0..n { + let byte: u8 = i.try_into().unwrap(); + input.push(byte); + } + + let cipher_key = UnboundCipherKey::new(alg, key).unwrap(); + let encrypting_key = PaddedBlockEncryptingKey::new(cipher_key, mode, padding).unwrap(); + + let mut in_out = input.clone(); + let decrypt_iv = encrypting_key.encrypt(&mut in_out).unwrap(); + + if n > 5 { + // There's no more than a 1 in 2^48 chance that this will fail randomly + assert_ne!(input.as_slice(), in_out); + } + + let cipher_key2 = UnboundCipherKey::new(alg, key).unwrap(); + let decrypting_key = PaddedBlockDecryptingKey::new(cipher_key2, mode, padding).unwrap(); + + let plaintext = decrypting_key.decrypt(&mut in_out, decrypt_iv).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + + #[test] + fn test_aes_128_cbc() { let key = from_hex("000102030405060708090a0b0c0d0e0f").unwrap(); - let input = from_hex("00112233445566778899aabbccddeeff").unwrap(); - let expected_result = from_hex("69c4e0d86a7b0430d8cdb78070b4c55a").unwrap(); - let input_block: [u8; BLOCK_LEN] = <[u8; BLOCK_LEN]>::try_from(input).unwrap(); + for i in 0..=50 { + helper_test_padded_cipher_n_bytes( + key.as_slice(), + &AES_128, + OperatingMode::CBC, + PaddingStrategy::PKCS7, + i, + ); + } + } - let aes128 = SymmetricCipherKey::aes128(key.as_slice()).unwrap(); - let result = aes128.encrypt_block(Block::from(&input_block)); + #[test] + fn test_aes_256_cbc() { + let key = + from_hex("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f").unwrap(); + for i in 0..=50 { + helper_test_padded_cipher_n_bytes( + key.as_slice(), + &AES_256, + OperatingMode::CBC, + PaddingStrategy::PKCS7, + i, + ); + } + } - assert_eq!(expected_result.as_slice(), result.as_ref()); + #[test] + fn test_aes_128_ctr() { + let key = from_hex("000102030405060708090a0b0c0d0e0f").unwrap(); + for i in 0..=50 { + helper_test_cipher_n_bytes(key.as_slice(), &AES_128, OperatingMode::CTR, i); + } } #[test] - fn test_encrypt_block_aes_256() { + fn test_aes_256_ctr() { let key = - from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(); - let input = from_hex("00112233445566778899aabbccddeeff").unwrap(); - let expected_result = from_hex("8ea2b7ca516745bfeafc49904b496089").unwrap(); - let input_block: [u8; BLOCK_LEN] = <[u8; BLOCK_LEN]>::try_from(input).unwrap(); + from_hex("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f").unwrap(); + for i in 0..=50 { + helper_test_cipher_n_bytes(key.as_slice(), &AES_256, OperatingMode::CTR, i); + } + } - let aes128 = SymmetricCipherKey::aes256(key.as_slice()).unwrap(); - let result = aes128.encrypt_block(Block::from(&input_block)); + macro_rules! padded_cipher_kat { + ($name:ident, $alg:expr, $mode:expr, $padding:expr, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let expected_ciphertext = from_hex($ciphertext).unwrap(); + let mut iv = from_hex($iv).unwrap(); + let iv = { + let slice = iv.as_mut_slice(); + let mut iv = [0u8; $iv.len() / 2]; + { + let x = iv.as_mut_slice(); + x.copy_from_slice(slice); + } + iv + }; - assert_eq!(expected_result.as_slice(), result.as_ref()); + let dc = CipherContext::Iv128(FixedLength::from(iv)); + + let alg = $alg; + + let unbound_key = UnboundCipherKey::new(alg, &key).unwrap(); + + let encrypting_key = + PaddedBlockEncryptingKey::new(unbound_key, $mode, $padding).unwrap(); + + let mut in_out = input.clone(); + + let context = encrypting_key.less_safe_encrypt(&mut in_out, dc).unwrap(); + + assert_eq!(expected_ciphertext, in_out); + + let unbound_key2 = UnboundCipherKey::new(alg, &key).unwrap(); + let decrypting_key = + PaddedBlockDecryptingKey::new(unbound_key2, $mode, $padding).unwrap(); + + let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + }; } + + macro_rules! cipher_kat { + ($name:ident, $alg:expr, $mode:expr, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let expected_ciphertext = from_hex($ciphertext).unwrap(); + let mut iv = from_hex($iv).unwrap(); + let iv = { + let slice = iv.as_mut_slice(); + let mut iv = [0u8; $iv.len() / 2]; + { + let x = iv.as_mut_slice(); + x.copy_from_slice(slice); + } + iv + }; + + let dc = CipherContext::Iv128(FixedLength::from(iv)); + + let alg = $alg; + + let unbound_key = UnboundCipherKey::new(alg, &key).unwrap(); + + let encrypting_key = EncryptingKey::new(unbound_key, $mode).unwrap(); + + let mut in_out = input.clone(); + + let context = encrypting_key.less_safe_encrypt(&mut in_out, dc).unwrap(); + + assert_eq!(expected_ciphertext, in_out); + + let unbound_key2 = UnboundCipherKey::new(alg, &key).unwrap(); + let decrypting_key = DecryptingKey::new(unbound_key2, $mode).unwrap(); + + let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + }; + } + + padded_cipher_kat!( + test_iv_aes_128_cbc_16_bytes, + &AES_128, + OperatingMode::CBC, + PaddingStrategy::PKCS7, + "000102030405060708090a0b0c0d0e0f", + "00000000000000000000000000000000", + "00112233445566778899aabbccddeeff", + "69c4e0d86a7b0430d8cdb78070b4c55a9e978e6d16b086570ef794ef97984232" + ); + + padded_cipher_kat!( + test_iv_aes_256_cbc_15_bytes, + &AES_256, + OperatingMode::CBC, + PaddingStrategy::PKCS7, + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "00000000000000000000000000000000", + "00112233445566778899aabbccddee", + "2ddfb635a651a43f582997966840ca0c" + ); + + cipher_kat!( + test_iv_aes_128_ctr_16_bytes, + &AES_128, + OperatingMode::CTR, + "000102030405060708090a0b0c0d0e0f", + "00000000000000000000000000000000", + "00112233445566778899aabbccddeeff", + "c6b01904c3da3df5e7d62bd96d153686" + ); + + cipher_kat!( + test_iv_aes_256_ctr_15_bytes, + &AES_256, + OperatingMode::CTR, + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "00000000000000000000000000000000", + "00112233445566778899aabbccddee", + "f28122856e1cf9a7216a30d111f399" + ); + + cipher_kat!( + test_openssl_aes_128_ctr_15_bytes, + &AES_128, + OperatingMode::CTR, + "244828580821c1652582c76e34d299f5", + "093145d5af233f46072a5eb5adc11aa1", + "3ee38cec171e6cf466bf0df98aa0e1", + "bd7d928f60e3422d96b3f8cd614eb2" + ); + + cipher_kat!( + test_openssl_aes_256_ctr_15_bytes, + &AES_256, + OperatingMode::CTR, + "0857db8240ea459bdf660b4cced66d1f2d3734ff2de7b81e92740e65e7cc6a1d", + "f028ecb053f801102d11fccc9d303a27", + "eca7285d19f3c20e295378460e8729", + "b5098e5e788de6ac2f2098eb2fc6f8" + ); + + padded_cipher_kat!( + test_openssl_aes_128_cbc_15_bytes, + &AES_128, + OperatingMode::CBC, + PaddingStrategy::PKCS7, + "053304bb3899e1d99db9d29343ea782d", + "b5313560244a4822c46c2a0c9d0cf7fd", + "a3e4c990356c01f320043c3d8d6f43", + "ad96993f248bd6a29760ec7ccda95ee1" + ); + + padded_cipher_kat!( + test_openssl_aes_128_cbc_16_bytes, + &AES_128, + OperatingMode::CBC, + PaddingStrategy::PKCS7, + "95af71f1c63e4a1d0b0b1a27fb978283", + "89e40797dca70197ff87d3dbb0ef2802", + "aece7b5e3c3df1ffc9802d2dfe296dc7", + "301b5dab49fb11e919d0d39970d06739301919743304f23f3cbc67d28564b25b" + ); + + padded_cipher_kat!( + test_openssl_aes_256_cbc_15_bytes, + &AES_256, + OperatingMode::CBC, + PaddingStrategy::PKCS7, + "d369e03e9752784917cc7bac1db7399598d9555e691861d9dd7b3292a693ef57", + "1399bb66b2f6ad99a7f064140eaaa885", + "7385f5784b85bf0a97768ddd896d6d", + "4351082bac9b4593ae8848cc9dfb5a01" + ); + + padded_cipher_kat!( + test_openssl_aes_256_cbc_16_bytes, + &AES_256, + OperatingMode::CBC, + PaddingStrategy::PKCS7, + "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41", + "24f6076548fb9d93c8f7ed9f6e661ef9", + "a39c1fdf77ea3e1f18178c0ec237c70a", + "f1af484830a149ee0387b854d65fe87ca0e62efc1c8e6909d4b9ab8666470453" + ); } diff --git a/aws-lc-rs/src/cipher/key.rs b/aws-lc-rs/src/cipher/key.rs new file mode 100644 index 00000000000..41125417ae3 --- /dev/null +++ b/aws-lc-rs/src/cipher/key.rs @@ -0,0 +1,172 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +use crate::cipher::aes::encrypt_block_aes; +use crate::cipher::block::Block; +use crate::cipher::chacha::ChaCha20Key; +use crate::cipher::{AES_128_KEY_LEN, AES_256_KEY_LEN}; +use crate::error::Unspecified; +use aws_lc::{AES_set_decrypt_key, AES_set_encrypt_key, AES_KEY}; +use core::ptr::copy_nonoverlapping; +use std::mem::{size_of, MaybeUninit}; +use std::os::raw::c_uint; +use zeroize::Zeroize; + +pub(crate) enum SymmetricCipherKey { + Aes128 { enc_key: AES_KEY, dec_key: AES_KEY }, + Aes256 { enc_key: AES_KEY, dec_key: AES_KEY }, + ChaCha20 { raw_key: ChaCha20Key }, +} + +unsafe impl Send for SymmetricCipherKey {} + +// The AES_KEY value is only used as a `*const AES_KEY` in calls to `AES_encrypt`. +unsafe impl Sync for SymmetricCipherKey {} + +impl Drop for SymmetricCipherKey { + fn drop(&mut self) { + // Aes128Key, Aes256Key and ChaCha20Key implement Drop separately. + match self { + SymmetricCipherKey::Aes128 { enc_key, dec_key } + | SymmetricCipherKey::Aes256 { enc_key, dec_key } => unsafe { + let enc_bytes: &mut [u8; size_of::()] = (enc_key as *mut AES_KEY) + .cast::<[u8; size_of::()]>() + .as_mut() + .unwrap(); + enc_bytes.zeroize(); + let dec_bytes: &mut [u8; size_of::()] = (dec_key as *mut AES_KEY) + .cast::<[u8; size_of::()]>() + .as_mut() + .unwrap(); + dec_bytes.zeroize(); + }, + SymmetricCipherKey::ChaCha20 { .. } => {} + } + } +} + +impl SymmetricCipherKey { + pub(crate) fn aes128(key_bytes: &[u8]) -> Result { + if key_bytes.len() != AES_128_KEY_LEN { + return Err(Unspecified); + } + + unsafe { + let mut enc_key = MaybeUninit::::uninit(); + #[allow(clippy::cast_possible_truncation)] + if 0 != AES_set_encrypt_key( + key_bytes.as_ptr(), + (key_bytes.len() * 8) as c_uint, + enc_key.as_mut_ptr(), + ) { + return Err(Unspecified); + } + let enc_key = enc_key.assume_init(); + + let mut dec_key = MaybeUninit::::uninit(); + #[allow(clippy::cast_possible_truncation)] + if 0 != AES_set_decrypt_key( + key_bytes.as_ptr(), + (key_bytes.len() * 8) as c_uint, + dec_key.as_mut_ptr(), + ) { + return Err(Unspecified); + } + let dec_key = dec_key.assume_init(); + + let mut kb = MaybeUninit::<[u8; AES_128_KEY_LEN]>::uninit(); + copy_nonoverlapping(key_bytes.as_ptr(), kb.as_mut_ptr().cast(), AES_128_KEY_LEN); + Ok(SymmetricCipherKey::Aes128 { enc_key, dec_key }) + } + } + + pub(crate) fn aes256(key_bytes: &[u8]) -> Result { + if key_bytes.len() != AES_256_KEY_LEN { + return Err(Unspecified); + } + unsafe { + let mut enc_key = MaybeUninit::::uninit(); + #[allow(clippy::cast_possible_truncation)] + if 0 != AES_set_encrypt_key( + key_bytes.as_ptr(), + (key_bytes.len() * 8) as c_uint, + enc_key.as_mut_ptr(), + ) { + return Err(Unspecified); + } + let enc_key = enc_key.assume_init(); + + let mut dec_key = MaybeUninit::::uninit(); + #[allow(clippy::cast_possible_truncation)] + if 0 != AES_set_decrypt_key( + key_bytes.as_ptr(), + (key_bytes.len() * 8) as c_uint, + dec_key.as_mut_ptr(), + ) { + return Err(Unspecified); + } + let dec_key = dec_key.assume_init(); + + let mut kb = MaybeUninit::<[u8; AES_256_KEY_LEN]>::uninit(); + copy_nonoverlapping(key_bytes.as_ptr(), kb.as_mut_ptr().cast(), AES_256_KEY_LEN); + Ok(SymmetricCipherKey::Aes256 { enc_key, dec_key }) + } + } + + pub(crate) fn chacha20(key_bytes: &[u8]) -> Result { + if key_bytes.len() != 32 { + return Err(Unspecified); + } + let mut kb = MaybeUninit::<[u8; 32]>::uninit(); + unsafe { + copy_nonoverlapping(key_bytes.as_ptr(), kb.as_mut_ptr().cast(), 32); + Ok(SymmetricCipherKey::ChaCha20 { + raw_key: ChaCha20Key(kb.assume_init()), + }) + } + } + + #[allow(dead_code)] + #[inline] + pub(crate) fn encrypt_block(&self, block: Block) -> Block { + match self { + SymmetricCipherKey::Aes128 { enc_key, .. } + | SymmetricCipherKey::Aes256 { enc_key, .. } => encrypt_block_aes(enc_key, block), + SymmetricCipherKey::ChaCha20 { .. } => panic!("Unsupported algorithm!"), + } + } +} + +#[cfg(test)] +mod tests { + use crate::cipher::block::{Block, BLOCK_LEN}; + use crate::cipher::key::SymmetricCipherKey; + use crate::test::from_hex; + + #[test] + fn test_encrypt_block_aes_128() { + let key = from_hex("000102030405060708090a0b0c0d0e0f").unwrap(); + let input = from_hex("00112233445566778899aabbccddeeff").unwrap(); + let expected_result = from_hex("69c4e0d86a7b0430d8cdb78070b4c55a").unwrap(); + let input_block: [u8; BLOCK_LEN] = <[u8; BLOCK_LEN]>::try_from(input).unwrap(); + + let aes128 = SymmetricCipherKey::aes128(key.as_slice()).unwrap(); + let result = aes128.encrypt_block(Block::from(&input_block)); + + assert_eq!(expected_result.as_slice(), result.as_ref()); + } + + #[test] + fn test_encrypt_block_aes_256() { + let key = + from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(); + let input = from_hex("00112233445566778899aabbccddeeff").unwrap(); + let expected_result = from_hex("8ea2b7ca516745bfeafc49904b496089").unwrap(); + let input_block: [u8; BLOCK_LEN] = <[u8; BLOCK_LEN]>::try_from(input).unwrap(); + + let aes128 = SymmetricCipherKey::aes256(key.as_slice()).unwrap(); + let result = aes128.encrypt_block(Block::from(&input_block)); + + assert_eq!(expected_result.as_slice(), result.as_ref()); + } +} diff --git a/aws-lc-rs/src/iv.rs b/aws-lc-rs/src/iv.rs new file mode 100644 index 00000000000..f2a11335efd --- /dev/null +++ b/aws-lc-rs/src/iv.rs @@ -0,0 +1,98 @@ +// Copyright 2018 Brian Smith. +// SPDX-License-Identifier: ISC +// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC +#![allow(dead_code)] + +//! Initialization Vector (IV) cryptographic primitives + +use crate::error::Unspecified; +use crate::rand; +use zeroize::Zeroize; + +/// An initialization vector that must be unique for the lifetime of the associated key +/// it is used with. +pub struct FixedLength([u8; L]); + +impl FixedLength { + /// Returns the size of the iv in bytes. + #[allow(clippy::must_use_candidate)] + pub fn size(&self) -> usize { + L + } + + /// Constructs a new [`FixedLength`] from pseudo-random bytes. + /// + /// # Errors + /// + /// * [`Unspecified`]: Returned if there is a failure generating `L` bytes. + /// + pub fn new() -> Result { + let mut iv_bytes = [0u8; L]; + rand::fill(&mut iv_bytes)?; + Ok(Self(iv_bytes)) + } +} + +impl Drop for FixedLength { + fn drop(&mut self) { + self.0.zeroize(); + } +} + +impl AsRef<[u8; L]> for FixedLength { + #[inline] + fn as_ref(&self) -> &[u8; L] { + &self.0 + } +} + +impl From<&[u8; L]> for FixedLength { + #[inline] + fn from(bytes: &[u8; L]) -> Self { + FixedLength(bytes.to_owned()) + } +} + +impl From<[u8; L]> for FixedLength { + #[inline] + fn from(bytes: [u8; L]) -> Self { + FixedLength(bytes) + } +} + +impl TryFrom<&[u8]> for FixedLength { + type Error = Unspecified; + + fn try_from(value: &[u8]) -> Result { + let value: &[u8; L] = value.try_into()?; + Ok(Self::from(*value)) + } +} + +impl TryFrom> for [u8; L] { + type Error = Unspecified; + + fn try_from(value: FixedLength) -> Result { + Ok(value.0) + } +} + +#[cfg(test)] +mod tests { + use crate::iv::FixedLength; + + #[test] + fn test_size() { + let fixed = FixedLength::from([0u8; 16]); + assert_eq!(16, fixed.size()); + + let array = [0u8; 12]; + let fixed = FixedLength::<12>::try_from(array.as_slice()).unwrap(); + assert_eq!(12, fixed.size()); + + assert!(FixedLength::<16>::try_from(array.as_slice()).is_err()); + + assert!(TryInto::<[u8; 12]>::try_into(fixed).is_ok()); + } +} diff --git a/aws-lc-rs/src/lib.rs b/aws-lc-rs/src/lib.rs index bb63d203b7b..43b3a5aaa57 100644 --- a/aws-lc-rs/src/lib.rs +++ b/aws-lc-rs/src/lib.rs @@ -83,7 +83,7 @@ //! //! #### - bindgen #### //! Causes `aws-lc-sys` or `aws-lc-fips-sys` to generates fresh bindings for AWS-LC instead of using -//! the pre-generated bindings. This feature require `libclang` to be installed. See the +//! the pre-generated bindings. This feature requires `libclang` to be installed. See the //! [requirements](https://rust-lang.github.io/rust-bindgen/requirements.html) //! for [rust-bindgen](https://github.com/rust-lang/rust-bindgen) //! @@ -137,21 +137,18 @@ pub mod rand; pub mod signature; pub mod test; -mod cipher; - -mod rsa; - -mod debug; - -mod endian; - mod bn; mod cbb; mod cbs; +pub mod cipher; +mod debug; mod ec; mod ed25519; +mod endian; mod evp_pkey; +pub mod iv; mod ptr; +mod rsa; use aws_lc::{ CRYPTO_library_init, ERR_error_string, ERR_get_error, FIPS_mode, ERR_GET_FUNC, ERR_GET_LIB, diff --git a/aws-lc-rs/tests/cipher_test.rs b/aws-lc-rs/tests/cipher_test.rs new file mode 100644 index 00000000000..61f29318ca4 --- /dev/null +++ b/aws-lc-rs/tests/cipher_test.rs @@ -0,0 +1,339 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +use aws_lc_rs::cipher::{ + CipherContext, DecryptingKey, EncryptingKey, OperatingMode, PaddedBlockDecryptingKey, + PaddedBlockEncryptingKey, UnboundCipherKey, AES_128, AES_256, +}; +use aws_lc_rs::iv::FixedLength; +use aws_lc_rs::test::from_hex; + +macro_rules! padded_cipher_kat { + ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let expected_ciphertext = from_hex($ciphertext).unwrap(); + + let iv = from_hex($iv).unwrap(); + let fixed_iv = FixedLength::try_from(iv.as_slice()).unwrap(); + let context = CipherContext::Iv128(fixed_iv); + + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + + let encrypting_key = PaddedBlockEncryptingKey::$constructor(unbound_key).unwrap(); + assert_eq!($mode, encrypting_key.mode()); + assert_eq!($alg, encrypting_key.algorithm()); + let mut in_out = input.clone(); + let context = encrypting_key + .less_safe_encrypt(&mut in_out, context) + .unwrap(); + assert_eq!(expected_ciphertext.as_slice(), in_out.as_slice()); + + let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap(); + let decrypting_key = PaddedBlockDecryptingKey::$constructor(unbound_key2).unwrap(); + assert_eq!($mode, decrypting_key.mode()); + assert_eq!($alg, decrypting_key.algorithm()); + let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + }; +} + +macro_rules! cipher_kat { + ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let expected_ciphertext = from_hex($ciphertext).unwrap(); + + let iv = from_hex($iv).unwrap(); + let fixed_iv = FixedLength::try_from(iv.as_slice()).unwrap(); + let context = CipherContext::Iv128(fixed_iv); + + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + + let encrypting_key = EncryptingKey::$constructor(unbound_key).unwrap(); + assert_eq!($mode, encrypting_key.mode()); + assert_eq!($alg, encrypting_key.algorithm()); + let mut in_out = input.clone(); + let context = encrypting_key + .less_safe_encrypt(in_out.as_mut_slice(), context) + .unwrap(); + assert_eq!(expected_ciphertext.as_slice(), in_out); + + let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap(); + let decrypting_key = DecryptingKey::$constructor(unbound_key2).unwrap(); + assert_eq!($mode, decrypting_key.mode()); + assert_eq!($alg, decrypting_key.algorithm()); + let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + }; +} + +macro_rules! padded_cipher_rt { + ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $key:literal, $plaintext:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + + let encrypting_key = PaddedBlockEncryptingKey::$constructor(unbound_key).unwrap(); + assert_eq!($mode, encrypting_key.mode()); + assert_eq!($alg, encrypting_key.algorithm()); + let mut in_out = input.clone(); + let context = encrypting_key.encrypt(&mut in_out).unwrap(); + + let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap(); + let decrypting_key = PaddedBlockDecryptingKey::$constructor(unbound_key2).unwrap(); + assert_eq!($mode, decrypting_key.mode()); + assert_eq!($alg, decrypting_key.algorithm()); + let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + }; +} + +macro_rules! cipher_rt { + ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $key:literal, $plaintext:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + + let encrypting_key = EncryptingKey::$constructor(unbound_key).unwrap(); + assert_eq!($mode, encrypting_key.mode()); + assert_eq!($alg, encrypting_key.algorithm()); + let mut in_out = input.clone(); + let context = encrypting_key.encrypt(in_out.as_mut_slice()).unwrap(); + + let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap(); + let decrypting_key = DecryptingKey::$constructor(unbound_key2).unwrap(); + assert_eq!($mode, decrypting_key.mode()); + assert_eq!($alg, decrypting_key.algorithm()); + let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + }; +} + +padded_cipher_kat!( + test_kat_aes_128_cbc_16_bytes, + &AES_128, + OperatingMode::CBC, + cbc_pkcs7, + "000102030405060708090a0b0c0d0e0f", + "00000000000000000000000000000000", + "00112233445566778899aabbccddeeff", + "69c4e0d86a7b0430d8cdb78070b4c55a9e978e6d16b086570ef794ef97984232" +); + +padded_cipher_kat!( + test_kat_aes_256_cbc_15_bytes, + &AES_256, + OperatingMode::CBC, + cbc_pkcs7, + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "00000000000000000000000000000000", + "00112233445566778899aabbccddee", + "2ddfb635a651a43f582997966840ca0c" +); + +cipher_kat!( + test_kat_aes_128_ctr_16_bytes, + &AES_128, + OperatingMode::CTR, + ctr, + "000102030405060708090a0b0c0d0e0f", + "00000000000000000000000000000000", + "00112233445566778899aabbccddeeff", + "c6b01904c3da3df5e7d62bd96d153686" +); + +cipher_kat!( + test_kat_aes_256_ctr_15_bytes, + &AES_256, + OperatingMode::CTR, + ctr, + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "00000000000000000000000000000000", + "00112233445566778899aabbccddee", + "f28122856e1cf9a7216a30d111f399" +); + +cipher_kat!( + test_kat_aes_128_ctr_15_bytes, + &AES_128, + OperatingMode::CTR, + ctr, + "244828580821c1652582c76e34d299f5", + "093145d5af233f46072a5eb5adc11aa1", + "3ee38cec171e6cf466bf0df98aa0e1", + "bd7d928f60e3422d96b3f8cd614eb2" +); + +cipher_kat!( + test_kat_aes_256_ctr_15_bytes_2, + &AES_256, + OperatingMode::CTR, + ctr, + "0857db8240ea459bdf660b4cced66d1f2d3734ff2de7b81e92740e65e7cc6a1d", + "f028ecb053f801102d11fccc9d303a27", + "eca7285d19f3c20e295378460e8729", + "b5098e5e788de6ac2f2098eb2fc6f8" +); + +padded_cipher_kat!( + test_kat_aes_128_cbc_15_bytes, + &AES_128, + OperatingMode::CBC, + cbc_pkcs7, + "053304bb3899e1d99db9d29343ea782d", + "b5313560244a4822c46c2a0c9d0cf7fd", + "a3e4c990356c01f320043c3d8d6f43", + "ad96993f248bd6a29760ec7ccda95ee1" +); + +padded_cipher_kat!( + test_kat_aes_128_cbc_16_bytes_2, + &AES_128, + OperatingMode::CBC, + cbc_pkcs7, + "95af71f1c63e4a1d0b0b1a27fb978283", + "89e40797dca70197ff87d3dbb0ef2802", + "aece7b5e3c3df1ffc9802d2dfe296dc7", + "301b5dab49fb11e919d0d39970d06739301919743304f23f3cbc67d28564b25b" +); + +padded_cipher_kat!( + test_kat_aes_256_cbc_16_bytes, + &AES_256, + OperatingMode::CBC, + cbc_pkcs7, + "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41", + "24f6076548fb9d93c8f7ed9f6e661ef9", + "a39c1fdf77ea3e1f18178c0ec237c70a", + "f1af484830a149ee0387b854d65fe87ca0e62efc1c8e6909d4b9ab8666470453" +); + +padded_cipher_rt!( + test_rt_aes_128_cbc_16_bytes, + &AES_128, + OperatingMode::CBC, + cbc_pkcs7, + "000102030405060708090a0b0c0d0e0f", + "00112233445566778899aabbccddeeff" +); + +padded_cipher_rt!( + test_rt_aes_256_cbc_15_bytes, + &AES_256, + OperatingMode::CBC, + cbc_pkcs7, + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "00112233445566778899aabbccddee" +); + +cipher_rt!( + test_rt_aes_128_ctr_16_bytes, + &AES_128, + OperatingMode::CTR, + ctr, + "000102030405060708090a0b0c0d0e0f", + "00112233445566778899aabbccddeeff" +); + +cipher_rt!( + test_rt_aes_128_ctr_17_bytes, + &AES_128, + OperatingMode::CTR, + ctr, + "000102030405060708090a0b0c0d0e0f", + "00112233445566778899aabbccddeeff01" +); + +cipher_rt!( + test_rt_aes_256_ctr_15_bytes, + &AES_256, + OperatingMode::CTR, + ctr, + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "00112233445566778899aabbccddee" +); + +cipher_rt!( + test_rt_aes_128_ctr_15_bytes, + &AES_128, + OperatingMode::CTR, + ctr, + "244828580821c1652582c76e34d299f5", + "3ee38cec171e6cf466bf0df98aa0e1" +); + +cipher_rt!( + test_rt_aes_256_ctr_15_bytes_2, + &AES_256, + OperatingMode::CTR, + ctr, + "0857db8240ea459bdf660b4cced66d1f2d3734ff2de7b81e92740e65e7cc6a1d", + "eca7285d19f3c20e295378460e8729" +); + +cipher_rt!( + test_rt_aes_256_ctr_17_bytes, + &AES_256, + OperatingMode::CTR, + ctr, + "0857db8240ea459bdf660b4cced66d1f2d3734ff2de7b81e92740e65e7cc6a1d", + "eca7285d19f3c20e295378460e872934" +); + +padded_cipher_rt!( + test_rt_aes_128_cbc_15_bytes, + &AES_128, + OperatingMode::CBC, + cbc_pkcs7, + "053304bb3899e1d99db9d29343ea782d", + "a3e4c990356c01f320043c3d8d6f43" +); + +padded_cipher_rt!( + test_rt_aes_128_cbc_16_bytes_2, + &AES_128, + OperatingMode::CBC, + cbc_pkcs7, + "95af71f1c63e4a1d0b0b1a27fb978283", + "aece7b5e3c3df1ffc9802d2dfe296dc7" +); + +padded_cipher_rt!( + test_rt_128_cbc_17_bytes, + &AES_128, + OperatingMode::CBC, + cbc_pkcs7, + "95af71f1c63e4a1d0b0b1a27fb978283", + "aece7b5e3c3df1ffc9802d2dfe296dc734" +); + +padded_cipher_rt!( + test_rt_aes_256_cbc_16_bytes, + &AES_256, + OperatingMode::CBC, + cbc_pkcs7, + "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41", + "a39c1fdf77ea3e1f18178c0ec237c70a" +); + +padded_cipher_rt!( + test_rt_aes_256_cbc_17_bytes, + &AES_256, + OperatingMode::CBC, + cbc_pkcs7, + "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41", + "a39c1fdf77ea3e1f18178c0ec237c70a34" +); diff --git a/aws-lc-rs/tests/hkdf_test.rs b/aws-lc-rs/tests/hkdf_test.rs index c85dd7294dc..e9eb1df82c8 100644 --- a/aws-lc-rs/tests/hkdf_test.rs +++ b/aws-lc-rs/tests/hkdf_test.rs @@ -3,7 +3,7 @@ // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC -use aws_lc_rs::{aead, digest, error, hkdf, hmac, test, test_file}; +use aws_lc_rs::{aead, cipher, digest, error, hkdf, hmac, test, test_file}; #[test] fn hkdf_tests() { @@ -122,6 +122,11 @@ fn hkdf_key_types() { let okm = prk.expand(&[b"info"], aead_alg).unwrap(); let _aead_prk_key = aead::UnboundKey::from(okm); } + + for cipher_alg in [&cipher::AES_256, &cipher::AES_128] { + let okm = prk.expand(&[b"info"], cipher_alg).unwrap(); + let _aes_prk_key = cipher::UnboundCipherKey::from(okm); + } } }