diff --git a/.changes/make-cryptographic-boundaries-mi-fba1.md b/.changes/make-cryptographic-boundaries-mi-fba1.md new file mode 100644 index 00000000..ad52d513 --- /dev/null +++ b/.changes/make-cryptographic-boundaries-mi-fba1.md @@ -0,0 +1,5 @@ +--- +"rscrypto" = "minor" +--- + +Make cryptographic boundaries misuse-resistant: keyed BLAKE2 uses validated borrowed key types and variable outputs fail with typed errors, normal AEAD sealing owns nonce issuance while caller nonces require an expert import, entropy and platform override failures no longer panic, and diagnostic or dangerous capabilities no longer clutter the crate root. diff --git a/benches/aead.rs b/benches/aead.rs index c0dd20fd..8b40ff6c 100644 --- a/benches/aead.rs +++ b/benches/aead.rs @@ -11,6 +11,7 @@ use core::hint::black_box; use aes_gcm::aead::{AeadInOut as _, KeyInit as _}; use aes_gcm_siv::aead::{AeadInPlace as _, KeyInit as _}; use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; +use rscrypto::aead::expert::AeadWithNonce; #[cfg(all( any(unix, windows), diff --git a/benches/auth.rs b/benches/auth.rs index 49ec5f24..2ac8849d 100644 --- a/benches/auth.rs +++ b/benches/auth.rs @@ -1042,7 +1042,7 @@ fn ecdsa_p384_sign(c: &mut Criterion) { #[cfg(all(feature = "diag", feature = "ecdsa-p384"))] fn ecdsa_p384_internal(c: &mut Criterion) { - use rscrypto::{ + use rscrypto::auth::{ diag_ecdsa_p384_basepoint_blinded_limb_digest, diag_ecdsa_p384_basepoint_r_limb_digest, diag_ecdsa_p384_final_multiply_limb_digest, diag_ecdsa_p384_nonce_inverse_limb_digest, diag_ecdsa_p384_nonce_reduce_limb_digest, diag_ecdsa_p384_order_mul_fixed_r_limb_digest, @@ -1281,7 +1281,7 @@ fn ed25519_verify(c: &mut Criterion) { #[cfg(feature = "diag")] fn ed25519_verify_phase(c: &mut Criterion) { - use rscrypto::{ + use rscrypto::auth::{ diag_ed25519_verify_challenge_reduce_digest, diag_ed25519_verify_portable_double_scalar_digest, diag_ed25519_verify_public_decode_digest, diag_ed25519_verify_r_decode_digest, diag_ed25519_verify_scalars, }; @@ -1339,7 +1339,7 @@ fn ed25519_verify_phase(c: &mut Criterion) { ))] g.bench_function(BenchmarkId::new("aarch64-asm-double-scalar", len), |b| { b.iter(|| { - black_box(rscrypto::diag_ed25519_verify_aarch64_asm_double_scalar_digest( + black_box(rscrypto::auth::diag_ed25519_verify_aarch64_asm_double_scalar_digest( black_box(&scalars.s_canonical), black_box(&scalars.neg_challenge), black_box(&scalars.public_key), diff --git a/benches/blake2.rs b/benches/blake2.rs index 87463c4c..7e851d9e 100644 --- a/benches/blake2.rs +++ b/benches/blake2.rs @@ -11,7 +11,9 @@ use blake2::{ use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use digest::typenum::{U16, U32, U64}; use hmac::{Mac as _, digest::KeyInit}; -use rscrypto::{Blake2b256, Blake2b512, Blake2bParams, Blake2s128, Blake2s256, Blake2sParams, Digest}; +use rscrypto::{ + Blake2b256, Blake2b512, Blake2bKey, Blake2bParams, Blake2s128, Blake2s256, Blake2sKey, Blake2sParams, Digest, +}; type RustCryptoBlake2bMac256 = Blake2bMac; type RustCryptoBlake2bMac512 = Blake2bMac; @@ -84,6 +86,8 @@ fn host_overhead(c: &mut Criterion) { let inputs = tiny_inputs(); let key_b = [0x42u8; 64]; let key_s = [0x24u8; 32]; + let key_b_typed = Blake2bKey::new(black_box(&key_b[..32])).unwrap(); + let key_s_typed = Blake2sKey::new(black_box(&key_s)).unwrap(); let mut oneshot = c.benchmark_group("blake2/host-overhead"); for (len, data) in &inputs { @@ -110,7 +114,7 @@ fn host_overhead(c: &mut Criterion) { common::set_throughput(&mut keyed, *len); keyed.bench_with_input(BenchmarkId::new("rscrypto/blake2b256", len), data, |b, d| { - b.iter(|| black_box(Blake2b256::keyed_digest(black_box(&key_b[..32]), black_box(d)))) + b.iter(|| black_box(Blake2b256::keyed_digest(key_b_typed, black_box(d)))) }); keyed.bench_with_input(BenchmarkId::new("rustcrypto/blake2b256", len), data, |b, d| { b.iter(|| { @@ -121,7 +125,7 @@ fn host_overhead(c: &mut Criterion) { }); keyed.bench_with_input(BenchmarkId::new("rscrypto/blake2s256", len), data, |b, d| { - b.iter(|| black_box(Blake2s256::keyed_digest(black_box(&key_s), black_box(d)))) + b.iter(|| black_box(Blake2s256::keyed_digest(key_s_typed, black_box(d)))) }); keyed.bench_with_input(BenchmarkId::new("rustcrypto/blake2s256", len), data, |b, d| { b.iter(|| { @@ -176,13 +180,17 @@ fn keyed(c: &mut Criterion) { let inputs = common::comp_sizes(); let key_b = [0x42u8; 64]; let key_s = [0x24u8; 32]; + let key_b_256 = Blake2bKey::new(black_box(&key_b[..32])).unwrap(); + let key_b_512 = Blake2bKey::new(black_box(&key_b)).unwrap(); + let key_s_128 = Blake2sKey::new(black_box(&key_s[..16])).unwrap(); + let key_s_256 = Blake2sKey::new(black_box(&key_s)).unwrap(); let mut g = c.benchmark_group("blake2/keyed"); for (len, data) in &inputs { common::set_throughput(&mut g, *len); g.bench_with_input(BenchmarkId::new("rscrypto/blake2b256", len), data, |b, d| { - b.iter(|| black_box(Blake2b256::keyed_digest(black_box(&key_b[..32]), black_box(d)))) + b.iter(|| black_box(Blake2b256::keyed_digest(key_b_256, black_box(d)))) }); g.bench_with_input(BenchmarkId::new("rustcrypto/blake2b256", len), data, |b, d| { b.iter(|| { @@ -200,7 +208,7 @@ fn keyed(c: &mut Criterion) { }); g.bench_with_input(BenchmarkId::new("rscrypto/blake2b512", len), data, |b, d| { - b.iter(|| black_box(Blake2b512::keyed_digest(black_box(&key_b), black_box(d)))) + b.iter(|| black_box(Blake2b512::keyed_digest(key_b_512, black_box(d)))) }); g.bench_with_input(BenchmarkId::new("rustcrypto/blake2b512", len), data, |b, d| { b.iter(|| { @@ -218,7 +226,7 @@ fn keyed(c: &mut Criterion) { }); g.bench_with_input(BenchmarkId::new("rscrypto/blake2s128", len), data, |b, d| { - b.iter(|| black_box(Blake2s128::keyed_digest(black_box(&key_s[..16]), black_box(d)))) + b.iter(|| black_box(Blake2s128::keyed_digest(key_s_128, black_box(d)))) }); g.bench_with_input(BenchmarkId::new("rustcrypto/blake2s128", len), data, |b, d| { b.iter(|| { @@ -229,7 +237,7 @@ fn keyed(c: &mut Criterion) { }); g.bench_with_input(BenchmarkId::new("rscrypto/blake2s256", len), data, |b, d| { - b.iter(|| black_box(Blake2s256::keyed_digest(black_box(&key_s), black_box(d)))) + b.iter(|| black_box(Blake2s256::keyed_digest(key_s_256, black_box(d)))) }); g.bench_with_input(BenchmarkId::new("rustcrypto/blake2s256", len), data, |b, d| { b.iter(|| { @@ -332,8 +340,8 @@ fn params(c: &mut Criterion) { b.iter(|| { black_box( Blake2bParams::new() - .salt(black_box(&salt_b)) - .personal(black_box(&personal_b)) + .salt(black_box(salt_b)) + .personal(black_box(personal_b)) .hash_256(black_box(d)), ) }) @@ -350,8 +358,8 @@ fn params(c: &mut Criterion) { b.iter(|| { black_box( Blake2sParams::new() - .salt(black_box(&salt_s)) - .personal(black_box(&personal_s)) + .salt(black_box(salt_s)) + .personal(black_box(personal_s)) .hash_256(black_box(d)), ) }) diff --git a/docs/features.md b/docs/features.md index 467ff69c..05826c4f 100644 --- a/docs/features.md +++ b/docs/features.md @@ -106,7 +106,7 @@ rscrypto = { version = "0.7.8", features = ["full", "portable-only"] } | Feature | Effect | |---|---| -| `getrandom` | Enables OS-RNG constructors such as `random()` / `try_random()`, `RapidRandomState::try_new()`, canonical Argon2id/scrypt password-record generation, `try_generate()` for Ed25519/X25519/ECDSA, `Poly1305OneTimeKey::try_generate()`, ML-KEM `try_generate_keypair()` / `try_encapsulate()`, AEAD random sealing helpers, RSA key generation, signing salt/blinding, encryption randomness, and private-operation blinding. Password-record salts are intentionally OS-owned; other APIs retain caller-supplied byte-filling closures where deterministic tests or constrained integrations need them. Deterministic ECDSA signing does not use OS randomness. RSA key generation uses OS entropy to seed its key-generation HMAC_DRBG; no separate DRBG feature is required. | +| `getrandom` | Enables fallible OS-RNG constructors such as `try_random()` / `try_generate()`, `RapidRandomState::try_new()`, canonical Argon2id/scrypt password-record generation, ML-KEM `try_generate_keypair()` / `try_encapsulate()`, AEAD random sealing helpers, RSA key generation, signing salt/blinding, encryption randomness, and private-operation blinding. Password-record salts are intentionally OS-owned; other APIs retain caller-supplied byte-filling closures where deterministic tests or constrained integrations need them. Deterministic ECDSA signing does not use OS randomness. RSA key generation uses OS entropy to seed its key-generation HMAC_DRBG; no separate DRBG feature is required. | | `serde` | Serde for non-secret byte wrappers (nonces, tags, public keys, signatures). | | `serde-secrets` | Serde for secret-key and shared-secret bytes. Implies `serde`. Use only for controlled key-material storage, not logs or DTOs. | | `parallel` | Rayon-backed BLAKE3 and Argon2 lane parallelism. Requires `std`, `blake3`, `argon2`. | diff --git a/docs/migration/README.md b/docs/migration/README.md index 3f0f402c..6bd77533 100644 --- a/docs/migration/README.md +++ b/docs/migration/README.md @@ -1,6 +1,7 @@ # Migration Guides -This index covers 35 migration guides for individual crates and larger stacks. +This index covers 36 migration guides for API revisions, individual crates, +and larger stacks. Each guide identifies dependency, import, call-site, behavior, and unsupported surface changes. @@ -9,6 +10,9 @@ If you are evaluating `rscrypto`, start with the crate you already use. The guide states whether the mapped surface is compatible and which upstream APIs must remain. +For projects upgrading rscrypto itself, start with +[`misuse-resistant API boundaries`](api-boundaries.md). + ## Checksums | From | To | Status | diff --git a/docs/migration/RustCrypto/aes-gcm-siv.md b/docs/migration/RustCrypto/aes-gcm-siv.md index 75137971..5db5b6c7 100644 --- a/docs/migration/RustCrypto/aes-gcm-siv.md +++ b/docs/migration/RustCrypto/aes-gcm-siv.md @@ -10,7 +10,7 @@ Evidence: `tests/aes128gcmsiv_oracle.rs`, `tests/aes256gcmsiv_oracle.rs`, and `t | | Before (`aes-gcm-siv` 0.11.x) | After (`rscrypto` 0.7.8) | |---|---|---| | Cargo dep | `aes-gcm-siv = "0.11"` | `rscrypto = { version = "0.7.8", features = ["aes-gcm-siv"] }` | -| Import | `use aes_gcm_siv::{Aes256GcmSiv, Key, Nonce, KeyInit, aead::{Aead, Payload}};` | `use rscrypto::{Aead, Aes256GcmSiv, Aes256GcmSivKey, aead::Nonce96};` | +| Import | `use aes_gcm_siv::{Aes256GcmSiv, Key, Nonce, KeyInit, aead::{Aead, Payload}};` | `use rscrypto::{Aead, Aes256GcmSiv, Aes256GcmSivKey, aead::{Nonce96, expert::AeadWithNonce}};` | | Encrypt | `cipher.encrypt(nonce, Payload { msg, aad })?` | `cipher.encrypt(&nonce, aad, msg, &mut out)?` | ## Cargo.toml @@ -56,7 +56,10 @@ let ct = cipher.encrypt(nonce, Payload { msg: plaintext, aad }).unwrap(); ```rust // After -use rscrypto::{Aead, Aes256GcmSiv, Aes256GcmSivKey, aead::Nonce96}; +use rscrypto::{ + Aead, Aes256GcmSiv, Aes256GcmSivKey, + aead::{Nonce96, expert::AeadWithNonce}, +}; let key = Aes256GcmSivKey::from_bytes([0u8; 32]); let cipher = Aes256GcmSiv::new(&key); @@ -65,6 +68,10 @@ let mut ct = vec![0u8; plaintext.len() + 16]; cipher.encrypt(&nonce, aad, plaintext, &mut ct)?; ``` +The expert trait import makes preservation of the upstream caller-nonce +protocol explicit. Prefer `seal_random` when the protocol does not already +define nonce derivation. + ### Combined decrypt ```rust diff --git a/docs/migration/RustCrypto/aes-gcm.md b/docs/migration/RustCrypto/aes-gcm.md index db215886..bf77f172 100644 --- a/docs/migration/RustCrypto/aes-gcm.md +++ b/docs/migration/RustCrypto/aes-gcm.md @@ -12,7 +12,7 @@ Evidence: `tests/aes128gcm_oracle.rs`, `tests/aes256gcm_oracle.rs`, and `tests/a | | Before (`aes-gcm` 0.11.x) | After (`rscrypto` 0.7.8) | |---|---|---| | Cargo dep | `aes-gcm = "0.11"` | `rscrypto = { version = "0.7.8", features = ["aes-gcm"] }` | -| Import | `use aes_gcm::{Aes256Gcm, Key, Nonce, KeyInit, aead::{Aead, Payload}};` | `use rscrypto::{Aead, Aes256Gcm, Aes256GcmKey, aead::Nonce96};` | +| Import | `use aes_gcm::{Aes256Gcm, Key, Nonce, KeyInit, aead::{Aead, Payload}};` | `use rscrypto::{Aead, Aes256Gcm, Aes256GcmKey, aead::{Nonce96, expert::AeadWithNonce}};` | | Encrypt | `cipher.encrypt(nonce, Payload { msg, aad })?` (returns `Vec`) | `cipher.encrypt(&nonce, aad, msg, &mut out)?` (writes into caller buffer) | ## Cargo.toml @@ -59,7 +59,10 @@ let ct = cipher.encrypt(nonce, Payload { msg: plaintext, aad }).unwrap(); ```rust // After -use rscrypto::{Aead, Aes256Gcm, Aes256GcmKey, aead::Nonce96}; +use rscrypto::{ + Aead, Aes256Gcm, Aes256GcmKey, + aead::{Nonce96, expert::AeadWithNonce}, +}; let key = Aes256GcmKey::from_bytes([0u8; 32]); let cipher = Aes256Gcm::new(&key); @@ -70,6 +73,9 @@ cipher.encrypt(&nonce, aad, plaintext, &mut ct)?; ``` The output layout is identical (`[ciphertext || tag]`), so on-the-wire compatibility is preserved. The shape change is who owns the buffer: `aes-gcm` allocates a `Vec`, rscrypto writes into a buffer you pre-sized. +The expert trait import is required because this migration preserves the +upstream caller-supplied nonce. New protocols should use `seal_random` or +`NonceCounter` so nonce issuance is not a normal call-site choice. ### Combined decrypt @@ -107,7 +113,7 @@ let tag = cipher.encrypt_in_place(&nonce, aad, &mut buffer)?; // buffer is now the ciphertext; tag: Aes256GcmTag (Copy). ``` -`encrypt_in_place` is the canonical name in rscrypto; `encrypt_in_place_detached` is also available as an alias matching RustCrypto's naming. +Both names require the explicit `AeadWithNonce` import. ### Detached (in-place) decrypt diff --git a/docs/migration/RustCrypto/ascon-aead.md b/docs/migration/RustCrypto/ascon-aead.md index 23ebd5ef..a407de1d 100644 --- a/docs/migration/RustCrypto/ascon-aead.md +++ b/docs/migration/RustCrypto/ascon-aead.md @@ -10,7 +10,7 @@ Evidence: `tests/ascon_aead_oracle.rs`. | | Before (`ascon-aead` 0.6.x) | After (`rscrypto` 0.7.8) | |---|---|---| | Cargo dep | `ascon-aead = "0.6"` | `rscrypto = { version = "0.7.8", features = ["ascon-aead"] }` | -| Import | `use ascon_aead::{AsconAead128, Key, Nonce, aead::{Aead, KeyInit, Payload}};` | `use rscrypto::{Aead, AsconAead128, AsconAead128Key, aead::Nonce128};` | +| Import | `use ascon_aead::{AsconAead128, Key, Nonce, aead::{Aead, KeyInit, Payload}};` | `use rscrypto::{Aead, AsconAead128, AsconAead128Key, aead::{Nonce128, expert::AeadWithNonce}};` | | Encrypt | `cipher.encrypt(nonce, Payload { msg, aad })?` | `cipher.encrypt(&nonce, aad, msg, &mut out)?` | ## Cargo.toml @@ -52,7 +52,10 @@ let ct = cipher.encrypt(nonce, Payload { msg: plaintext, aad }).unwrap(); ```rust // After -use rscrypto::{Aead, AsconAead128, AsconAead128Key, aead::Nonce128}; +use rscrypto::{ + Aead, AsconAead128, AsconAead128Key, + aead::{Nonce128, expert::AeadWithNonce}, +}; let key = AsconAead128Key::from_bytes([0u8; 16]); let cipher = AsconAead128::new(&key); @@ -61,6 +64,9 @@ let mut ct = vec![0u8; plaintext.len() + 16]; cipher.encrypt(&nonce, aad, plaintext, &mut ct)?; ``` +The expert trait import preserves an existing caller-nonce protocol. Prefer +`seal_random` when the protocol does not already define nonce derivation. + ### Combined decrypt ```rust diff --git a/docs/migration/RustCrypto/blake2.md b/docs/migration/RustCrypto/blake2.md index f6b0af4f..740ccb12 100644 --- a/docs/migration/RustCrypto/blake2.md +++ b/docs/migration/RustCrypto/blake2.md @@ -77,7 +77,9 @@ use rscrypto::{Blake2b256, Digest}; let out: [u8; 32] = Blake2b256::digest(b"123456789"); ``` -For runtime-variable output, use `Blake2b::digest_into(out_len, data, &mut buf)`: see the `Blake2b` rustdoc. +For runtime-variable output, size the destination and call +`Blake2b::digest_into(data, &mut buf)?`. The buffer length selects the output +length and invalid empty or oversized buffers return `Blake2Error`. ### Streaming @@ -113,17 +115,29 @@ let tag = mac.finalize().into_bytes(); // GenericArray ```rust // After -use rscrypto::Blake2b512; +use rscrypto::{Blake2b512, Blake2bKey}; let key = [0x42u8; 32]; -let tag: [u8; 64] = Blake2b512::keyed_digest(&key, b"message"); +let key = Blake2bKey::new(&key)?; +let tag: [u8; 64] = Blake2b512::keyed_digest(key, b"message"); ``` -For streaming keyed mode, use `Blake2b512::new_keyed(&key)`. The MAC type and the hash type are unified: no separate `Blake2bMac` import. +For streaming keyed mode, use `Blake2b512::new_keyed(key)`. `Blake2bKey::new` +rejects empty or oversized keys at the caller boundary; the borrowed validated +key can then be reused without allocation or copying. Omitting the keyed +constructor selects unkeyed hashing. The MAC type and the hash type are +unified: no separate `Blake2bMac` import. ## Notes - **Generic constants gone.** `Blake2b` and `Blake2s` style generics are replaced with named convenience types per output length. `generic-array` is no longer in your dependency tree if rscrypto is your only consumer. -- **MAC unification.** RustCrypto separates `Blake2bMac` from `Blake2b` because the MAC and the hash use different parameter blocks. rscrypto exposes both modes from the same type via `keyed_digest` / `new_keyed`. Personalisation, salt, and tree-hashing parameters are reachable through `Blake2bParams` / `Blake2sParams`. +- **MAC unification.** RustCrypto separates `Blake2bMac` from `Blake2b` + because the MAC and the hash use different parameter blocks. rscrypto + exposes both modes from the same type via `keyed_digest` / `new_keyed`. + Personalization and salt are available through `Blake2bParams` / + `Blake2sParams`; tree hashing is not exposed. +- **Exact parameter fields.** `Blake2bParams` accepts `[u8; 16]` salt and + personalization fields; `Blake2sParams` accepts `[u8; 8]`. Pad deliberately + before the call when a protocol defines a shorter value. - **`finalize` consumes vs. borrows.** Same as `sha2` / `sha3`: drop `.clone()`. - **`Output` → `[u8; N]`.** Same as `sha2` / `sha3`. - **No generic keyed-output comparison.** rscrypto's Blake2 keyed digest diff --git a/docs/migration/RustCrypto/chacha20poly1305.md b/docs/migration/RustCrypto/chacha20poly1305.md index 8b2ee32b..5e7138da 100644 --- a/docs/migration/RustCrypto/chacha20poly1305.md +++ b/docs/migration/RustCrypto/chacha20poly1305.md @@ -10,7 +10,7 @@ Evidence: `tests/chacha20poly1305.rs`, `tests/xchacha20poly1305.rs`, and `tests/ | | Before (`chacha20poly1305` 0.11.x) | After (`rscrypto` 0.7.8) | |---|---|---| | Cargo dep | `chacha20poly1305 = "0.11"` | `rscrypto = { version = "0.7.8", features = ["chacha20poly1305", "xchacha20poly1305"] }` | -| Import | `use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce, KeyInit, aead::{Aead, Payload}};` | `use rscrypto::{Aead, ChaCha20Poly1305, ChaCha20Poly1305Key, aead::Nonce96};` | +| Import | `use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce, KeyInit, aead::{Aead, Payload}};` | `use rscrypto::{Aead, ChaCha20Poly1305, ChaCha20Poly1305Key, aead::{Nonce96, expert::AeadWithNonce}};` | | Encrypt | `cipher.encrypt(nonce, Payload { msg, aad })?` | `cipher.encrypt(&nonce, aad, msg, &mut out)?` | Drop `xchacha20poly1305` from the feature list if you don't use the 192-bit-nonce variant. @@ -54,7 +54,10 @@ let ct = cipher.encrypt(nonce, Payload { msg: plaintext, aad }).unwrap(); ```rust // After -use rscrypto::{Aead, ChaCha20Poly1305, ChaCha20Poly1305Key, aead::Nonce96}; +use rscrypto::{ + Aead, ChaCha20Poly1305, ChaCha20Poly1305Key, + aead::{Nonce96, expert::AeadWithNonce}, +}; let key = ChaCha20Poly1305Key::from_bytes([0u8; 32]); let cipher = ChaCha20Poly1305::new(&key); @@ -78,7 +81,10 @@ let ct = cipher.encrypt(nonce, Payload { msg: plaintext, aad }).unwrap(); ```rust // After -use rscrypto::{Aead, XChaCha20Poly1305, XChaCha20Poly1305Key, aead::Nonce192}; +use rscrypto::{ + Aead, XChaCha20Poly1305, XChaCha20Poly1305Key, + aead::{Nonce192, expert::AeadWithNonce}, +}; let key = XChaCha20Poly1305Key::from_bytes([0u8; 32]); let cipher = XChaCha20Poly1305::new(&key); @@ -88,6 +94,8 @@ cipher.encrypt(&nonce, aad, plaintext, &mut ct)?; ``` The XChaCha variant uses `Nonce192` (24 bytes). That is the only structural change from the IETF-nonce variant. +The expert trait import is required because these examples preserve an +existing caller-nonce protocol. Prefer `seal_random` for new protocols. ### Decrypt + tamper-detection @@ -120,7 +128,9 @@ cipher.decrypt_in_place(&nonce, aad, &mut buffer, &tag)?; the deployment must still define a message limit and acceptable error probability. - **No `Payload`.** Same simplification as `aes-gcm.md`: positional `aad` and `msg`/`buffer` args. -- **`AeadInPlace` import not needed.** rscrypto exposes both combined and in-place shapes through the single `Aead` trait. +- **Explicit-nonce sealing is expert-only.** Import `AeadWithNonce` for + protocols that already prove nonce uniqueness. Decryption and fresh-random + sealing remain on `Aead`. - **Failed-open buffer semantics change.** RustCrypto keeps the in-place buffer unchanged on error. rscrypto clears it on authentication failure. Combined rscrypto decrypt also clears its output buffer on authentication failure. diff --git a/docs/migration/aegis.md b/docs/migration/aegis.md index 5c875cf4..edcd36ff 100644 --- a/docs/migration/aegis.md +++ b/docs/migration/aegis.md @@ -10,7 +10,7 @@ Evidence: `tests/aegis256_oracle.rs` and `tests/aead_wycheproof.rs`. | | Before (`aegis` 0.9.x) | After (`rscrypto` 0.7.8) | |---|---|---| | Cargo dep | `aegis = "0.9"` | `rscrypto = { version = "0.7.8", features = ["aegis256"] }` | -| Import | `use aegis::aegis256::Aegis256;` | `use rscrypto::{Aead, Aegis256, Aegis256Key, aead::Nonce256};` | +| Import | `use aegis::aegis256::Aegis256;` | `use rscrypto::{Aead, Aegis256, Aegis256Key, aead::{Nonce256, expert::AeadWithNonce}};` | | Encrypt | `Aegis256::<16>::new(&key, &nonce).encrypt(msg, aad) -> (Vec, [u8; 16])` | `cipher.encrypt(&nonce, aad, msg, &mut out)?` | ## Cargo.toml @@ -52,7 +52,10 @@ let (ciphertext, tag) = cipher.encrypt(plaintext, aad); // separate (Vec ```rust // After -use rscrypto::{Aead, Aegis256, Aegis256Key, aead::Nonce256}; +use rscrypto::{ + Aead, Aegis256, Aegis256Key, + aead::{Nonce256, expert::AeadWithNonce}, +}; let key = Aegis256Key::from_bytes([0u8; 32]); let cipher = Aegis256::new(&key); // key at construction @@ -61,6 +64,9 @@ let mut ct = vec![0u8; plaintext.len() + 16]; cipher.encrypt(&nonce, aad, plaintext, &mut ct)?; // appended tag ``` +The expert trait import is deliberate: this recipe preserves the upstream +caller-supplied nonce. Prefer `seal_random` for new protocols. + Three structural differences: | `aegis` | rscrypto | diff --git a/docs/migration/api-boundaries.md b/docs/migration/api-boundaries.md new file mode 100644 index 00000000..162ee7de --- /dev/null +++ b/docs/migration/api-boundaries.md @@ -0,0 +1,78 @@ +# Migration: misuse-resistant API boundaries + +This release makes invalid lengths and caller-controlled expert operations +explicit. Cryptographic kernels, output bytes, buffer layouts, and dispatch +behavior are unchanged. + +## BLAKE2 + +Keyed BLAKE2 accepts a validated borrowed key: + +```rust +use rscrypto::{Blake2b256, Blake2bKey}; + +let key = Blake2bKey::new(b"key")?; +let tag = Blake2b256::keyed_digest(key, b"message"); +let mut hasher = Blake2b256::new_keyed(key); +# Ok::<(), rscrypto::Blake2Error>(()) +``` + +`Blake2bKey::new` and `Blake2sKey::new` reject empty or oversized keys at the +caller boundary. The borrowed key can then be reused without allocation or +copying. Omit the keyed method to select unkeyed hashing. + +Variable-output BLAKE2b derives its output length from the destination: + +```rust +use rscrypto::Blake2b; + +let mut out = [0u8; 24]; +Blake2b::digest_into(b"message", &mut out)?; +# Ok::<(), rscrypto::Blake2Error>(()) +``` + +`Blake2bParams` uses exact `[u8; 16]` salt and personalization fields; +`Blake2sParams` uses `[u8; 8]`. Pad deliberately before the call when a +protocol defines a shorter value. + +## Random generation + +Panicking `random()` constructors were removed. Use `try_random()` or the +type-specific `try_generate()` method and propagate entropy failures. + +## AEAD nonces + +Normal `Aead` sealing generates a fresh OS nonce. AES-GCM also supports the +allocation-free `NonceCounter` stream. Caller-supplied nonce sealing is +available only after an explicit expert import: + +```rust +use rscrypto::{ + Aes256Gcm, Aes256GcmKey, + aead::{Nonce96, expert::AeadWithNonce}, +}; + +let cipher = Aes256Gcm::new(&Aes256GcmKey::from_bytes([0x11; 32])); +let nonce = Nonce96::from_bytes([0x22; 12]); +let mut out = [0u8; 20]; +cipher.encrypt(&nonce, b"aad", b"data", &mut out)?; +# Ok::<(), rscrypto::aead::SealError>(()) +``` + +Use this extension only when a protocol or persistent counter already proves +nonce uniqueness for the key. + +## Expert and diagnostic paths + +- The implementation module `platform::detect` is private. Use `platform::get`, + `platform::caps`, `platform::arch`, or `platform::caps_static` for normal + detection. +- Detection overrides moved to `platform::expert`; use + `try_set_override(Some(value))` to set and `try_set_override(None)` to clear. + Uncached detection is `platform::expert::detect_uncached`. +- Explicit secret formatting is named `expert::DisplaySecret`. +- Diagnostic hooks remain under their owning modules such as `auth`, `aead`, + and `hashes`; they are no longer re-exported from the crate root. + +These are namespace changes only. They add no allocation, dynamic dispatch, +registry, lock, or extra cryptographic work. diff --git a/docs/migration/aws-lc-rs.md b/docs/migration/aws-lc-rs.md index 295a71bf..b27e3f48 100644 --- a/docs/migration/aws-lc-rs.md +++ b/docs/migration/aws-lc-rs.md @@ -135,7 +135,10 @@ key.seal_in_place_append_tag( ```rust // After -use rscrypto::{Aes256Gcm, Aes256GcmKey, aead::Nonce96}; +use rscrypto::{ + Aes256Gcm, Aes256GcmKey, + aead::{Nonce96, expert::AeadWithNonce}, +}; let cipher = Aes256Gcm::new(&Aes256GcmKey::from_bytes(*key_bytes)); let nonce = Nonce96::from_bytes(*nonce_bytes); diff --git a/docs/migration/dryoc.md b/docs/migration/dryoc.md index 7c0e13a2..62d2f6bd 100644 --- a/docs/migration/dryoc.md +++ b/docs/migration/dryoc.md @@ -52,8 +52,9 @@ dryoc::classic::crypto_generichash::crypto_generichash(&mut tag, data, Some(key) ```rust // After -use rscrypto::Blake2b256; +use rscrypto::{Blake2b256, Blake2bKey}; +let key = Blake2bKey::new(key)?; let tag = Blake2b256::keyed_digest(key, data); ``` diff --git a/docs/migration/ring.md b/docs/migration/ring.md index 83d77667..570e6d33 100644 --- a/docs/migration/ring.md +++ b/docs/migration/ring.md @@ -130,7 +130,10 @@ key.seal_in_place_append_tag( ```rust // After -use rscrypto::{Aes256Gcm, Aes256GcmKey, aead::Nonce96}; +use rscrypto::{ + Aes256Gcm, Aes256GcmKey, + aead::{Nonce96, expert::AeadWithNonce}, +}; let cipher = Aes256Gcm::new(&Aes256GcmKey::from_bytes(*key_bytes)); let nonce = Nonce96::from_bytes(*nonce_bytes); diff --git a/docs/secret-lifecycle.md b/docs/secret-lifecycle.md index 4c5e9e5e..0bfc23e4 100644 --- a/docs/secret-lifecycle.md +++ b/docs/secret-lifecycle.md @@ -99,7 +99,7 @@ standard error-source chain; callers can still recover it by explicitly matching the public `Random` variant. Other reviewed errors contain only discriminants, public sizes, or opaque verification failures. -`DisplaySecret` is the deliberate exception: constructing it explicitly opts +`expert::DisplaySecret` is the deliberate exception: constructing it explicitly opts into rendering borrowed secret bytes. Feature-gated diagnostic functions may return their declared result bytes, but they do not implicitly format owning keys, seeds, nonce material, intermediate state, or unmasked shares through diff --git a/docs/secret-ownership.md b/docs/secret-ownership.md index 43636dca..c73853b2 100644 --- a/docs/secret-ownership.md +++ b/docs/secret-ownership.md @@ -33,7 +33,7 @@ capability for permanent retention. |---|---|---|---|---|---| | `SecretBytes` | Neither | Masked | Consuming `expose()` returns a plain array | Inline | Fixed-size integration boundary whose owned source is cleared on extraction | | `SecretVec` | Neither | Masked | Consuming `into_unprotected_vec()` returns an ordinary allocation | Heap `Vec` | Variable-size RSA private-key export and explicit transfer to APIs that cannot borrow | -| `DisplaySecret<'a>` | Neither | Intentionally prints bytes through both `Display` and `Debug` | Hex formatting only | Borrowed | Explicit opt-in escape hatch for integrations that must render a key; never use it in logs | +| `expert::DisplaySecret<'a>` | Neither | Intentionally prints bytes through both `Display` and `Debug` | Hex formatting only | Borrowed | Explicit opt-in escape hatch for integrations that must render a key; never use it in logs | | AEAD `*Key` types | Explicit duplicate; no `Clone` or `Copy` | Masked | `SecretBytes` export, hex opt-in, and `serde-secrets` | Inline | Lets an owned cipher context coexist with a caller-retained typed key while making the extra lifetime visible | | AEAD cipher contexts | Neither | Masked | None | Inline, except boxed RISC-V fixslice AES schedules | Reusable expanded key and authentication subkey state without exposing a generic duplication path | | ECDSA P-256/P-384 secret keys and keypairs | Explicit duplicate; no `Clone` or `Copy` | Secret keys are masked; keypairs show only the public half | `SecretBytes` export and hex opt-in; no Serde | Inline | Caller-controlled key/keypair duplication for independent signing owners | @@ -91,7 +91,7 @@ and ordinary serialization therefore do not duplicate a confidential key. - `serde` alone covers protocol-visible values. `serde-secrets` is the explicit opt-in for AEAD keys, Ed25519/X25519 secrets, and ML-KEM decapsulation/shared secrets. ECDSA and RSA private keys do not implement Serde. -- `DisplaySecret` is the only generic formatting wrapper that intentionally +- `expert::DisplaySecret` is the only generic formatting wrapper that intentionally makes secret bytes visible through `Debug`; constructing it is the disclosure decision. - Heap-backed secret state is confined to `SecretVec`, RSA private-key import, diff --git a/docs/types.md b/docs/types.md index 426f9553..2cbb2ce1 100644 --- a/docs/types.md +++ b/docs/types.md @@ -77,6 +77,8 @@ XOF readers: `Shake128XofReader`, `Shake256XofReader`, Aliases: `hashes::crypto::AsconXof128` and `hashes::crypto::AsconXof128Reader`. `hashes::io::{DigestReader, DigestWriter}` provides `std::io` adapters. +`Blake2bKey` and `Blake2sKey` make caller-facing key validation explicit while +borrowing key bytes without allocation or copying. ## Fast Hashes @@ -192,9 +194,12 @@ Nonce types: `Nonce96` (12B), `Nonce128` (16B), `Nonce192` (24B), `Nonce256` (32 AEAD support types: `SealError`, `OpenError`, `AeadBufferError`, `NonceCounter`, `NonceCounterExhausted`, and `NonceCounterSealError`. -Caller-buffer and detached APIs are allocation-free. With `alloc`, every AEAD -also has `encrypt_to_vec` and `decrypt_to_vec`; with `alloc` + `getrandom`, it -also has `seal_random_to_vec`. +Fresh-random sealing and decryption live on `Aead`; deterministic AES-GCM +sealing lives on `NonceCounter`. Protocols that already prove nonce uniqueness +must explicitly import `aead::expert::AeadWithNonce` for caller-nonce +`encrypt`, `encrypt_in_place`, and `encrypt_to_vec`. The caller-buffer and +detached forms remain allocation-free. With `alloc`, decryption has +`decrypt_to_vec`; with `alloc` + `getrandom`, sealing has `seal_random_to_vec`. ## Error Types @@ -208,6 +213,7 @@ also has `seal_random_to_vec`. | `NonceCounterSealError` | AES-GCM nonce counter is exhausted or sealing fails | Rotate the key before counter reuse, or correct the sealing input | | `HkdfOutputLengthError` | HKDF expand exceeds max | Request less output | | `Pbkdf2Error` | PBKDF2 parameter validation | Adjust iterations / output length | +| `Blake2Error` | Invalid BLAKE2 key or variable output length, or a mismatched streaming output buffer | Correct the public key/output length | | `Argon2Error` | Argon2 configuration, input, entropy, or resource failure | Fix the profile/input or restore resources | | `ScryptError` | scrypt configuration, entropy, or resource failure | Fix N/r/p or restore resources | | `X25519Error` | Low-order DH point | Reject peer key | @@ -220,7 +226,7 @@ also has `seal_random_to_vec`. | `RsaProtocolAlgorithmError` | Unsupported/confused COSE/TLS/X.509 RSA selector | Reject algorithm mapping | | `AsconCxofCustomizationError` | Customization > 256 bytes | Shorten string | | `InvalidHexError` | Hex decode failure | Fix input | -| `platform::OverrideError` | Override after detection init | Set before first call | +| `platform::expert::OverrideError` | Invalid, unsupported, or late detection override | Configure through `platform::expert::try_set_override` before first detection | ## Platform And Dispatch @@ -232,14 +238,14 @@ also has `seal_random_to_vec`. | `platform::Description` | Zero-allocation display wrapper for detected platform facts | | `platform::DispatchInfo` | Shared dispatch metadata used by introspection modules | | `platform::KernelIntrospect` | Trait for algorithms that can report selected kernels by input length | -| `platform::OverrideError` | Detection override failure | +| `platform::expert::OverrideError` | Expert detection override failure | ## Utility | Item | Purpose | |------|---------| | `ct::zeroize` | Volatile source-level overwrite plus compiler fence; see `secret-lifecycle.md` for the evidence boundary | -| `DisplaySecret` | Opt-in hex display for secret keys | +| `expert::DisplaySecret` | Explicitly dangerous hex display for secret keys | | `SecretBytes` | Fixed-size secret owner that overwrites its owned bytes on drop | | `SecretVec` | Variable-length secret owner that overwrites initialized storage on drop; ordinary extraction requires `into_unprotected_vec()` | diff --git a/fuzz/support/src/lib.rs b/fuzz/support/src/lib.rs index a974a903..ab1ff4f4 100644 --- a/fuzz/support/src/lib.rs +++ b/fuzz/support/src/lib.rs @@ -5,6 +5,8 @@ use std::{fs, path::Path}; +#[cfg(feature = "aead")] +use rscrypto::aead::expert::AeadWithNonce; #[cfg(feature = "aead")] use rscrypto::traits::Aead; use rscrypto::traits::{Checksum, ChecksumCombine, Digest, Mac}; diff --git a/fuzz/target_impls/aead_aegis256.rs b/fuzz/target_impls/aead_aegis256.rs index 1a4d6704..af12a9c0 100644 --- a/fuzz/target_impls/aead_aegis256.rs +++ b/fuzz/target_impls/aead_aegis256.rs @@ -1,4 +1,7 @@ -use rscrypto::{Aegis256, Aegis256Key, aead::Nonce256}; +use rscrypto::{ + Aegis256, Aegis256Key, + aead::{Nonce256, expert::AeadWithNonce}, +}; use rscrypto_fuzz::{FuzzInput, assert_aead_forgery, assert_aead_roundtrip, some_or_return}; pub fn run(data: &[u8]) { diff --git a/fuzz/target_impls/aead_nonce_counter.rs b/fuzz/target_impls/aead_nonce_counter.rs index f8f9b705..cd681fa2 100644 --- a/fuzz/target_impls/aead_nonce_counter.rs +++ b/fuzz/target_impls/aead_nonce_counter.rs @@ -1,4 +1,7 @@ -use rscrypto::{Aes256Gcm, Aes256GcmKey, aead::NonceCounter}; +use rscrypto::{ + Aes256Gcm, Aes256GcmKey, + aead::{NonceCounter, expert::AeadWithNonce}, +}; use rscrypto_fuzz::{FuzzInput, some_or_return, split_at_ratio}; // Bound the per-iteration nonce burst so the fuzz job stays fast even when diff --git a/fuzz/target_impls/hash_blake2b.rs b/fuzz/target_impls/hash_blake2b.rs index 4cfef055..78266587 100644 --- a/fuzz/target_impls/hash_blake2b.rs +++ b/fuzz/target_impls/hash_blake2b.rs @@ -1,7 +1,7 @@ use blake2::{Blake2b256 as OracleBlake2b256, Blake2b512 as OracleBlake2b512, Blake2bMac, Digest as _}; use digest::typenum::{U32, U64}; use hmac::{Mac as _, digest::KeyInit}; -use rscrypto::{Blake2b256, Blake2b512, Digest}; +use rscrypto::{Blake2b256, Blake2b512, Blake2bKey, Digest}; use rscrypto_fuzz::{FuzzInput, assert_digest_chunked, assert_digest_reset, some_or_return, split_at_ratio}; type OracleBlake2bMac256 = Blake2bMac; @@ -30,9 +30,10 @@ pub fn run(data: &[u8]) { let split_idx = split_at_ratio(data, key_ratio).0.len(); let key_len = split_idx.clamp(1, 64); let (key, msg) = data.split_at(key_len); + let typed_key = Blake2bKey::new(key).unwrap(); let (msg_a, msg_b) = split_at_ratio(msg, split); - let mut ours_256_stream = Blake2b256::new_keyed(key); + let mut ours_256_stream = Blake2b256::new_keyed(typed_key); ours_256_stream.update(msg_a); ours_256_stream.update(msg_b); let ours_256_keyed = ours_256_stream.finalize(); @@ -42,7 +43,7 @@ pub fn run(data: &[u8]) { let oracle_256_keyed = oracle_256_mac.finalize().into_bytes(); assert_eq!(&ours_256_keyed[..], &oracle_256_keyed[..], "blake2b256 keyed mismatch"); - let mut ours_512_stream = Blake2b512::new_keyed(key); + let mut ours_512_stream = Blake2b512::new_keyed(typed_key); ours_512_stream.update(msg_a); ours_512_stream.update(msg_b); let ours_512_keyed = ours_512_stream.finalize(); diff --git a/fuzz/target_impls/hash_blake2s.rs b/fuzz/target_impls/hash_blake2s.rs index 1ddd7f4e..8446d3c6 100644 --- a/fuzz/target_impls/hash_blake2s.rs +++ b/fuzz/target_impls/hash_blake2s.rs @@ -1,7 +1,7 @@ use blake2::{Blake2s128 as OracleBlake2s128, Blake2s256 as OracleBlake2s256, Blake2sMac, Digest as _}; use digest::typenum::{U16, U32}; use hmac::{Mac as _, digest::KeyInit}; -use rscrypto::{Blake2s128, Blake2s256, Digest}; +use rscrypto::{Blake2s128, Blake2s256, Blake2sKey, Digest}; use rscrypto_fuzz::{FuzzInput, assert_digest_chunked, assert_digest_reset, some_or_return, split_at_ratio}; type OracleBlake2sMac128 = Blake2sMac; @@ -30,9 +30,10 @@ pub fn run(data: &[u8]) { let split_idx = split_at_ratio(data, key_ratio).0.len(); let key_len = split_idx.clamp(1, 32); let (key, msg) = data.split_at(key_len); + let typed_key = Blake2sKey::new(key).unwrap(); let (msg_a, msg_b) = split_at_ratio(msg, split); - let mut ours_128_stream = Blake2s128::new_keyed(key); + let mut ours_128_stream = Blake2s128::new_keyed(typed_key); ours_128_stream.update(msg_a); ours_128_stream.update(msg_b); let ours_128_keyed = ours_128_stream.finalize(); @@ -42,7 +43,7 @@ pub fn run(data: &[u8]) { let oracle_128_keyed = oracle_128_mac.finalize().into_bytes(); assert_eq!(&ours_128_keyed[..], &oracle_128_keyed[..], "blake2s128 keyed mismatch"); - let mut ours_256_stream = Blake2s256::new_keyed(key); + let mut ours_256_stream = Blake2s256::new_keyed(typed_key); ours_256_stream.update(msg_a); ours_256_stream.update(msg_b); let ours_256_keyed = ours_256_stream.finalize(); diff --git a/src/aead/aegis256.rs b/src/aead/aegis256.rs index a47a615a..9bd3e574 100644 --- a/src/aead/aegis256.rs +++ b/src/aead/aegis256.rs @@ -378,12 +378,6 @@ impl Aegis256 { ::tag_from_slice(bytes) } - /// Encrypt `buffer` in place and return the detached authentication tag. - #[inline] - pub fn encrypt_in_place(&self, nonce: &Nonce256, aad: &[u8], buffer: &mut [u8]) -> Result { - ::encrypt_in_place(self, nonce, aad, buffer) - } - /// Decrypt `buffer` in place and verify the detached authentication tag. #[inline] pub fn decrypt_in_place( @@ -396,12 +390,6 @@ impl Aegis256 { ::decrypt_in_place(self, nonce, aad, buffer, tag) } - /// Encrypt `plaintext` into `out` as `ciphertext || tag`. - #[inline] - pub fn encrypt(&self, nonce: &Nonce256, aad: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result<(), SealError> { - ::encrypt(self, nonce, aad, plaintext, out) - } - /// Decrypt a combined `ciphertext || tag` into `out`. #[inline] pub fn decrypt( @@ -526,7 +514,13 @@ impl Aead for Aegis256 { Ok(Aegis256Tag::from_bytes(tag)) } - fn encrypt_in_place(&self, nonce: &Self::Nonce, aad: &[u8], buffer: &mut [u8]) -> Result { + fn __encrypt_in_place_with_nonce( + &self, + nonce: &Self::Nonce, + aad: &[u8], + buffer: &mut [u8], + _token: crate::traits::aead::SealToken, + ) -> Result { super::seal_bit_lengths(aad.len(), buffer.len())?; let key = self.key.as_bytes(); @@ -684,6 +678,7 @@ mod tests { use std::eprintln; use super::*; + use crate::aead::expert::AeadWithNonce; #[cfg(not(target_arch = "s390x"))] #[inline(always)] diff --git a/src/aead/aes128gcm.rs b/src/aead/aes128gcm.rs index 0c7c0cf6..ae947731 100644 --- a/src/aead/aes128gcm.rs +++ b/src/aead/aes128gcm.rs @@ -186,12 +186,6 @@ impl Aes128Gcm { } } - /// Encrypt `buffer` in place and return the detached authentication tag. - #[inline] - pub fn encrypt_in_place(&self, nonce: &Nonce96, aad: &[u8], buffer: &mut [u8]) -> Result { - ::encrypt_in_place(self, nonce, aad, buffer) - } - /// Decrypt `buffer` in place and verify the detached authentication tag. #[inline] pub fn decrypt_in_place( @@ -204,12 +198,6 @@ impl Aes128Gcm { ::decrypt_in_place(self, nonce, aad, buffer, tag) } - /// Encrypt `plaintext` into `out` as `ciphertext || tag`. - #[inline] - pub fn encrypt(&self, nonce: &Nonce96, aad: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result<(), SealError> { - ::encrypt(self, nonce, aad, plaintext, out) - } - /// Decrypt a combined `ciphertext || tag` into `out`. #[inline] pub fn decrypt( @@ -700,7 +688,13 @@ impl Aead for Aes128Gcm { Ok(Aes128GcmTag::from_bytes(tag)) } - fn encrypt_in_place(&self, nonce: &Self::Nonce, aad: &[u8], buffer: &mut [u8]) -> Result { + fn __encrypt_in_place_with_nonce( + &self, + nonce: &Self::Nonce, + aad: &[u8], + buffer: &mut [u8], + _token: crate::traits::aead::SealToken, + ) -> Result { super::seal_bounded_length_as_u64(buffer.len(), MAX_PLAINTEXT_LEN)?; let length_block = super::seal_bit_lengths(aad.len(), buffer.len())?.to_be_bits_block(); #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))] @@ -1073,6 +1067,7 @@ mod tests { use alloc::{vec, vec::Vec}; use super::*; + use crate::aead::expert::AeadWithNonce; // NIST SP 800-38D Test Case 1: AES-128-GCM, empty plaintext, empty AAD. // Key: 00000000000000000000000000000000 diff --git a/src/aead/aes128gcmsiv.rs b/src/aead/aes128gcmsiv.rs index 3c0ed5c0..459a2a2f 100644 --- a/src/aead/aes128gcmsiv.rs +++ b/src/aead/aes128gcmsiv.rs @@ -119,12 +119,6 @@ impl Aes128GcmSiv { ::tag_from_slice(bytes) } - /// Encrypt `buffer` in place and return the detached authentication tag. - #[inline] - pub fn encrypt_in_place(&self, nonce: &Nonce96, aad: &[u8], buffer: &mut [u8]) -> Result { - ::encrypt_in_place(self, nonce, aad, buffer) - } - /// Decrypt `buffer` in place and verify the detached authentication tag. #[inline] pub fn decrypt_in_place( @@ -137,12 +131,6 @@ impl Aes128GcmSiv { ::decrypt_in_place(self, nonce, aad, buffer, tag) } - /// Encrypt `plaintext` into `out` as `ciphertext || tag`. - #[inline] - pub fn encrypt(&self, nonce: &Nonce96, aad: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result<(), SealError> { - ::encrypt(self, nonce, aad, plaintext, out) - } - /// Decrypt a combined `ciphertext || tag` into `out`. #[inline] pub fn decrypt( @@ -1409,7 +1397,13 @@ impl Aead for Aes128GcmSiv { Ok(Aes128GcmSivTag::from_bytes(tag)) } - fn encrypt_in_place(&self, nonce: &Self::Nonce, aad: &[u8], buffer: &mut [u8]) -> Result { + fn __encrypt_in_place_with_nonce( + &self, + nonce: &Self::Nonce, + aad: &[u8], + buffer: &mut [u8], + _token: crate::traits::aead::SealToken, + ) -> Result { super::seal_bounded_length_as_u64(buffer.len(), MAX_PLAINTEXT_LEN)?; super::seal_bit_lengths(aad.len(), buffer.len())?; @@ -1598,6 +1592,7 @@ mod tests { use alloc::{vec, vec::Vec}; use super::*; + use crate::aead::expert::AeadWithNonce; /// RFC 8452 Appendix C.1, test 1: empty plaintext, empty AAD. #[test] diff --git a/src/aead/aes256gcm.rs b/src/aead/aes256gcm.rs index 418ccdec..28af183a 100644 --- a/src/aead/aes256gcm.rs +++ b/src/aead/aes256gcm.rs @@ -181,12 +181,6 @@ impl Aes256Gcm { } } - /// Encrypt `buffer` in place and return the detached authentication tag. - #[inline] - pub fn encrypt_in_place(&self, nonce: &Nonce96, aad: &[u8], buffer: &mut [u8]) -> Result { - ::encrypt_in_place(self, nonce, aad, buffer) - } - /// Decrypt `buffer` in place and verify the detached authentication tag. #[inline] pub fn decrypt_in_place( @@ -199,12 +193,6 @@ impl Aes256Gcm { ::decrypt_in_place(self, nonce, aad, buffer, tag) } - /// Encrypt `plaintext` into `out` as `ciphertext || tag`. - #[inline] - pub fn encrypt(&self, nonce: &Nonce96, aad: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result<(), SealError> { - ::encrypt(self, nonce, aad, plaintext, out) - } - /// Decrypt a combined `ciphertext || tag` into `out`. #[inline] pub fn decrypt( @@ -703,7 +691,13 @@ impl Aead for Aes256Gcm { Ok(Aes256GcmTag::from_bytes(tag)) } - fn encrypt_in_place(&self, nonce: &Self::Nonce, aad: &[u8], buffer: &mut [u8]) -> Result { + fn __encrypt_in_place_with_nonce( + &self, + nonce: &Self::Nonce, + aad: &[u8], + buffer: &mut [u8], + _token: crate::traits::aead::SealToken, + ) -> Result { super::seal_bounded_length_as_u64(buffer.len(), MAX_PLAINTEXT_LEN)?; let length_block = super::seal_bit_lengths(aad.len(), buffer.len())?.to_be_bits_block(); #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))] @@ -1076,6 +1070,7 @@ mod tests { use alloc::{vec, vec::Vec}; use super::*; + use crate::aead::expert::AeadWithNonce; // NIST SP 800-38D Test Case 13: AES-256-GCM, empty plaintext, empty AAD. // Key: 0000...00 (32 bytes) diff --git a/src/aead/aes256gcmsiv.rs b/src/aead/aes256gcmsiv.rs index 6373250e..87baa773 100644 --- a/src/aead/aes256gcmsiv.rs +++ b/src/aead/aes256gcmsiv.rs @@ -118,12 +118,6 @@ impl Aes256GcmSiv { ::tag_from_slice(bytes) } - /// Encrypt `buffer` in place and return the detached authentication tag. - #[inline] - pub fn encrypt_in_place(&self, nonce: &Nonce96, aad: &[u8], buffer: &mut [u8]) -> Result { - ::encrypt_in_place(self, nonce, aad, buffer) - } - /// Decrypt `buffer` in place and verify the detached authentication tag. #[inline] pub fn decrypt_in_place( @@ -136,12 +130,6 @@ impl Aes256GcmSiv { ::decrypt_in_place(self, nonce, aad, buffer, tag) } - /// Encrypt `plaintext` into `out` as `ciphertext || tag`. - #[inline] - pub fn encrypt(&self, nonce: &Nonce96, aad: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result<(), SealError> { - ::encrypt(self, nonce, aad, plaintext, out) - } - /// Decrypt a combined `ciphertext || tag` into `out`. #[inline] pub fn decrypt( @@ -1476,7 +1464,13 @@ impl Aead for Aes256GcmSiv { Ok(Aes256GcmSivTag::from_bytes(tag)) } - fn encrypt_in_place(&self, nonce: &Self::Nonce, aad: &[u8], buffer: &mut [u8]) -> Result { + fn __encrypt_in_place_with_nonce( + &self, + nonce: &Self::Nonce, + aad: &[u8], + buffer: &mut [u8], + _token: crate::traits::aead::SealToken, + ) -> Result { super::seal_bounded_length_as_u64(buffer.len(), MAX_PLAINTEXT_LEN)?; super::seal_bit_lengths(aad.len(), buffer.len())?; @@ -1665,6 +1659,7 @@ mod tests { use alloc::{vec, vec::Vec}; use super::*; + use crate::aead::expert::AeadWithNonce; /// RFC 8452 Appendix C.2, test case 1: empty plaintext, empty AAD. #[test] diff --git a/src/aead/ascon128.rs b/src/aead/ascon128.rs index 4eff73df..802b0368 100644 --- a/src/aead/ascon128.rs +++ b/src/aead/ascon128.rs @@ -149,17 +149,6 @@ impl AsconAead128 { ::tag_from_slice(bytes) } - /// Encrypt `buffer` in place and return the detached authentication tag. - #[inline] - pub fn encrypt_in_place( - &self, - nonce: &Nonce128, - aad: &[u8], - buffer: &mut [u8], - ) -> Result { - ::encrypt_in_place(self, nonce, aad, buffer) - } - /// Decrypt `buffer` in place and verify the detached authentication tag. #[inline] pub fn decrypt_in_place( @@ -172,12 +161,6 @@ impl AsconAead128 { ::decrypt_in_place(self, nonce, aad, buffer, tag) } - /// Encrypt `plaintext` into `out` as `ciphertext || tag`. - #[inline] - pub fn encrypt(&self, nonce: &Nonce128, aad: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result<(), SealError> { - ::encrypt(self, nonce, aad, plaintext, out) - } - /// Decrypt a combined `ciphertext || tag` into `out`. #[inline] pub fn decrypt( @@ -279,7 +262,13 @@ impl Aead for AsconAead128 { Ok(AsconAead128Tag::from_bytes(tag)) } - fn encrypt_in_place(&self, nonce: &Self::Nonce, aad: &[u8], buffer: &mut [u8]) -> Result { + fn __encrypt_in_place_with_nonce( + &self, + nonce: &Self::Nonce, + aad: &[u8], + buffer: &mut [u8], + _token: crate::traits::aead::SealToken, + ) -> Result { let mut s = self.initialize(nonce); Self::process_aad(&mut s, aad); @@ -385,6 +374,7 @@ mod tests { use ascon_aead::aead::{Aead as _, KeyInit, Payload, array::Array}; use super::*; + use crate::aead::expert::AeadWithNonce; fn assert_matches_oracle(key: [u8; 16], nonce: [u8; 16], aad: &[u8], plaintext: &[u8]) { let aead = AsconAead128::new(&AsconAead128Key::from_bytes(key)); diff --git a/src/aead/chacha20poly1305.rs b/src/aead/chacha20poly1305.rs index 0d7a93d2..ad96c186 100644 --- a/src/aead/chacha20poly1305.rs +++ b/src/aead/chacha20poly1305.rs @@ -139,17 +139,6 @@ impl ChaCha20Poly1305 { ::tag_from_slice(bytes) } - /// Encrypt `buffer` in place and return the detached authentication tag. - #[inline] - pub fn encrypt_in_place( - &self, - nonce: &Nonce96, - aad: &[u8], - buffer: &mut [u8], - ) -> Result { - ::encrypt_in_place(self, nonce, aad, buffer) - } - /// Decrypt `buffer` in place and verify the detached authentication tag. #[inline] pub fn decrypt_in_place( @@ -162,12 +151,6 @@ impl ChaCha20Poly1305 { ::decrypt_in_place(self, nonce, aad, buffer, tag) } - /// Encrypt `plaintext` into `out` as `ciphertext || tag`. - #[inline] - pub fn encrypt(&self, nonce: &Nonce96, aad: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result<(), SealError> { - ::encrypt(self, nonce, aad, plaintext, out) - } - /// Decrypt a combined `ciphertext || tag` into `out`. #[inline] pub fn decrypt( @@ -720,7 +703,13 @@ impl Aead for ChaCha20Poly1305 { Ok(ChaCha20Poly1305Tag::from_bytes(tag)) } - fn encrypt_in_place(&self, nonce: &Self::Nonce, aad: &[u8], buffer: &mut [u8]) -> Result { + fn __encrypt_in_place_with_nonce( + &self, + nonce: &Self::Nonce, + aad: &[u8], + buffer: &mut [u8], + _token: crate::traits::aead::SealToken, + ) -> Result { super::seal_bounded_length_as_u64(buffer.len(), MAX_PLAINTEXT_LEN)?; #[cfg(any(target_arch = "x86_64", all(target_arch = "powerpc64", target_endian = "little")))] @@ -809,6 +798,7 @@ mod tests { use alloc::vec::Vec; use super::*; + use crate::aead::expert::AeadWithNonce; #[test] fn round_trip() { diff --git a/src/aead/mod.rs b/src/aead/mod.rs index 00e5d0b2..df1a6f05 100644 --- a/src/aead/mod.rs +++ b/src/aead/mod.rs @@ -43,9 +43,9 @@ //! # API Conventions //! //! - Every cipher uses a typed `*Key`, typed nonce wrapper, and typed `*Tag`. -//! - Combined-buffer helpers use `encrypt` / `decrypt`. -//! - Allocating combined helpers use `encrypt_to_vec` / `decrypt_to_vec`. -//! - Detached helpers use `encrypt_in_place` / `decrypt_in_place`. +//! - Normal sealing uses fresh random nonces or [`NonceCounter`] where available. +//! - Decryption accepts the nonce transported with the ciphertext. +//! - Protocol-defined caller-nonce sealing requires an explicit [`expert::AeadWithNonce`] import. //! - All AEADs implement the shared [`Aead`] trait with the same constructor and operation names. //! //! # Error Conventions @@ -58,6 +58,13 @@ use core::fmt; pub use crate::traits::Aead; use crate::traits::VerificationError; +#[doc(hidden)] +pub use crate::traits::aead::SealToken as __SealToken; + +/// Explicit-nonce sealing for protocols that prove nonce uniqueness. +pub mod expert { + pub use crate::traits::aead::AeadWithNonce; +} #[cfg(feature = "aegis256")] mod aegis256; #[cfg(any( @@ -306,22 +313,6 @@ macro_rules! define_nonce_type { Self(bytes) } - /// Generate a random nonce using the operating system's CSPRNG. - /// - /// # Panics - /// - /// Panics if the platform entropy source is unavailable. - #[cfg(feature = "getrandom")] - #[cfg_attr(docsrs, doc(cfg(feature = "getrandom")))] - #[inline] - #[must_use] - pub fn random() -> Self { - match Self::try_random() { - Ok(value) => value, - Err(e) => panic!("getrandom failed: {e}"), - } - } - /// Try to generate a random nonce from the platform entropy source. /// /// # Errors diff --git a/src/aead/nonce_counter.rs b/src/aead/nonce_counter.rs index 39e5b2a0..af4dd16c 100644 --- a/src/aead/nonce_counter.rs +++ b/src/aead/nonce_counter.rs @@ -26,6 +26,7 @@ use core::{fmt, marker::PhantomData}; use super::{Aes128Gcm, Aes128GcmTag, Aes256Gcm, Aes256GcmTag, Nonce96, SealError}; +use crate::aead::expert::AeadWithNonce; const FIXED_PREFIX_LEN: usize = 4; const COUNTER_LEN: usize = 8; diff --git a/src/aead/xchacha20poly1305.rs b/src/aead/xchacha20poly1305.rs index f02f6e31..8e1cd870 100644 --- a/src/aead/xchacha20poly1305.rs +++ b/src/aead/xchacha20poly1305.rs @@ -95,17 +95,6 @@ impl XChaCha20Poly1305 { ::tag_from_slice(bytes) } - /// Encrypt `buffer` in place and return the detached authentication tag. - #[inline] - pub fn encrypt_in_place( - &self, - nonce: &Nonce192, - aad: &[u8], - buffer: &mut [u8], - ) -> Result { - ::encrypt_in_place(self, nonce, aad, buffer) - } - /// Decrypt `buffer` in place and verify the detached authentication tag. #[inline] pub fn decrypt_in_place( @@ -118,12 +107,6 @@ impl XChaCha20Poly1305 { ::decrypt_in_place(self, nonce, aad, buffer, tag) } - /// Encrypt `plaintext` into `out` as `ciphertext || tag`. - #[inline] - pub fn encrypt(&self, nonce: &Nonce192, aad: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result<(), SealError> { - ::encrypt(self, nonce, aad, plaintext, out) - } - /// Decrypt a combined `ciphertext || tag` into `out`. #[inline] pub fn decrypt( @@ -172,7 +155,13 @@ impl Aead for XChaCha20Poly1305 { Ok(XChaCha20Poly1305Tag::from_bytes(tag)) } - fn encrypt_in_place(&self, nonce: &Self::Nonce, aad: &[u8], buffer: &mut [u8]) -> Result { + fn __encrypt_in_place_with_nonce( + &self, + nonce: &Self::Nonce, + aad: &[u8], + buffer: &mut [u8], + _token: crate::traits::aead::SealToken, + ) -> Result { super::seal_bounded_length_as_u64(buffer.len(), MAX_PLAINTEXT_LEN)?; let (mut subkey, ietf_nonce) = self.derive_subkey_and_nonce(nonce); @@ -222,6 +211,7 @@ impl Aead for XChaCha20Poly1305 { #[cfg(test)] mod tests { use super::*; + use crate::aead::expert::AeadWithNonce; #[test] fn round_trip() { diff --git a/src/auth/argon2/mod.rs b/src/auth/argon2/mod.rs index 9f9e70f4..8616c0a6 100644 --- a/src/auth/argon2/mod.rs +++ b/src/auth/argon2/mod.rs @@ -871,13 +871,12 @@ fn h_prime(input_parts: &[&[u8]], out: &mut [u8]) { .to_le_bytes(); if out_len <= 64 { - let mut hasher = - Blake2b::new(u8::try_from(out_len).unwrap_or_else(|_| unreachable!("guarded by `out_len <= 64` above"))); + let mut hasher = Blake2b::new_validated(out_len); hasher.update(&len_le); for part in input_parts { hasher.update(part); } - hasher.finalize_into(out); + hasher.finalize_into_validated(out); return; } @@ -906,12 +905,9 @@ fn h_prime(input_parts: &[&[u8]], out: &mut [u8]) { // V_{r+1} = Blake2b(V_r, out_len − 32·r) let tail_off = r.strict_mul(32); let tail_len = out_len.strict_sub(tail_off); - let mut hasher = Blake2b::new( - u8::try_from(tail_len) - .unwrap_or_else(|_| unreachable!("tail_len ∈ (32, 64] by construction (out_len > 64, r = ⌈out_len/32⌉ - 2)")), - ); + let mut hasher = Blake2b::new_validated(tail_len); hasher.update(&v_prev); - hasher.finalize_into(&mut out[tail_off..]); + hasher.finalize_into_validated(&mut out[tail_off..]); ct::zeroize(&mut v_prev); } diff --git a/src/auth/mod.rs b/src/auth/mod.rs index 0466078f..26d1ab91 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -296,4 +296,6 @@ pub use scrypt::{ScryptPassword, ScryptVerificationLimits}; #[cfg(feature = "x25519")] pub use x25519::{X25519Error, X25519PublicKey, X25519SecretKey, X25519SharedSecret}; +#[cfg(all(feature = "diag", feature = "x25519"))] +pub use crate::backend::curve25519::diag_curve25519_conditional_swap; pub use crate::traits::Mac; diff --git a/src/hashes/crypto/blake2b/mod.rs b/src/hashes/crypto/blake2b/mod.rs index bf973464..b56fad26 100644 --- a/src/hashes/crypto/blake2b/mod.rs +++ b/src/hashes/crypto/blake2b/mod.rs @@ -24,11 +24,12 @@ //! ## Keyed Hashing //! //! ```rust -//! use rscrypto::{Blake2b256, Digest}; +//! use rscrypto::{Blake2b256, Blake2bKey, Digest}; //! -//! let key = b"secret-key-up-to-64-bytes"; +//! let key = Blake2bKey::new(b"secret-key-up-to-64-bytes")?; //! let tag = Blake2b256::keyed_digest(key, b"message"); //! assert_ne!(tag, Blake2b256::digest(b"message")); +//! # Ok::<(), rscrypto::Blake2Error>(()) //! ``` //! //! ## Variable Output Length @@ -38,12 +39,13 @@ //! //! // One-shot into a 48-byte buffer. //! let mut out = [0u8; 48]; -//! Blake2b::digest_into(48, b"hello", &mut out); +//! Blake2b::digest_into(b"hello", &mut out)?; //! assert_ne!(out, [0u8; 48]); //! //! // Streaming with a const-generic output array. //! let tag = Blake2b::digest_array::<20>(b"message"); //! assert_eq!(tag.len(), 20); +//! # Ok::<(), rscrypto::Blake2Error>(()) //! ``` pub(crate) mod kernels; @@ -60,12 +62,74 @@ use core::{fmt, mem::MaybeUninit}; use kernels::{Blake2bCounter, IV}; +use super::Blake2Error; use crate::traits::{Digest, ct}; const BLOCK_SIZE: usize = 128; const MAX_KEY_LEN: usize = 64; const MAX_OUTPUT_LEN: usize = 64; +/// Validated 1–64 byte Blake2b key. +/// +/// Construction validates the RFC 7693 key-length invariant. The wrapper then +/// borrows the key without allocation or copying. +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct Blake2bKey<'a>(&'a [u8]); + +impl<'a> Blake2bKey<'a> { + /// Validate and borrow a Blake2b key. + /// + /// # Errors + /// + /// Returns [`Blake2Error::InvalidKeyLength`] for an empty key or a key + /// longer than 64 bytes. + #[inline] + pub fn new(key: &'a [u8]) -> Result { + validate_key(key)?; + Ok(Self(key)) + } + + /// Borrow the validated key bytes. + #[must_use] + pub const fn as_bytes(self) -> &'a [u8] { + self.0 + } +} + +impl fmt::Debug for Blake2bKey<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Blake2bKey") + .field("length", &self.0.len()) + .finish_non_exhaustive() + } +} + +impl<'a> TryFrom<&'a [u8]> for Blake2bKey<'a> { + type Error = Blake2Error; + + #[inline] + fn try_from(key: &'a [u8]) -> Result { + Self::new(key) + } +} + +#[inline] +fn validate_key(key: &[u8]) -> Result<(), Blake2Error> { + if key.is_empty() || key.len() > MAX_KEY_LEN { + return Err(Blake2Error::InvalidKeyLength); + } + Ok(()) +} + +#[inline] +fn validate_output_len(output_len: usize) -> Result { + if !(1..=MAX_OUTPUT_LEN).contains(&output_len) { + return Err(Blake2Error::InvalidOutputLength); + } + Ok(output_len as u8) +} + #[cfg(any(test, feature = "diag"))] #[allow(dead_code)] #[inline] @@ -111,11 +175,6 @@ impl Core { } } - /// Create a new Blake2b state with output length `nn` and optional `key`. - /// - /// # Panics - /// - /// Panics if `nn` is 0 or > 64, or if `key` is longer than 64 bytes. #[inline] fn new(nn: u8, key: &[u8]) -> Self { Self::new_with_params(nn, key, &[0u8; SALT_LEN], &[0u8; PERSONAL_LEN]) @@ -123,10 +182,6 @@ impl Core { /// Create a new Blake2b state with output length `nn`, optional `key`, and /// spec-defined `salt` + `personal` parameter-block values (RFC 7693 §2.5). - /// - /// # Panics - /// - /// Panics if `nn` is 0 or > 64, or if `key` is longer than 64 bytes. #[allow(clippy::indexing_slicing)] fn new_with_params(nn: u8, key: &[u8], salt: &[u8; SALT_LEN], personal: &[u8; PERSONAL_LEN]) -> Self { assert!( @@ -594,32 +649,35 @@ impl Drop for Core { /// Builder for Blake2b hashers with optional key, salt, and personalization. /// -/// Implements the sequential-mode parameter block from RFC 7693 §2.5. Salt -/// (up to 16 bytes) and personalization (up to 16 bytes) are XORed into the -/// initial chaining value words `h[4..8]`, giving the same hasher with a -/// different domain. +/// Implements the sequential-mode parameter block from RFC 7693 §2.5. The +/// exact-size salt and personalization fields are XORed into the initial +/// chaining value words `h[4..8]`, giving the same hasher with a different +/// domain. /// -/// Empty key produces an unkeyed hasher; non-empty key produces a Blake2b-MAC. +/// Omitting [`key`](Self::key) produces an unkeyed hasher. /// /// # Examples /// /// ```rust -/// use rscrypto::Blake2bParams; +/// use rscrypto::{Blake2bKey, Blake2bParams}; +/// +/// let key = Blake2bKey::new(b"my-secret-key")?; /// /// let tag = Blake2bParams::new() -/// .key(b"my-secret-key") -/// .salt(b"random-salt-1234") -/// .personal(b"app-v1-tagging00") +/// .key(key) +/// .salt(*b"random-salt-1234") +/// .personal(*b"app-v1-tagging00") /// .hash_256(b"message"); /// assert_eq!(tag.len(), 32); /// /// // Same input + different personalization → different output. /// let other = Blake2bParams::new() -/// .key(b"my-secret-key") -/// .salt(b"random-salt-1234") -/// .personal(b"app-v2-tagging00") +/// .key(key) +/// .salt(*b"random-salt-1234") +/// .personal(*b"app-v2-tagging00") /// .hash_256(b"message"); /// assert_ne!(tag, other); +/// # Ok::<(), rscrypto::Blake2Error>(()) /// ``` pub struct Blake2bParams { key_buf: [u8; MAX_KEY_LEN], @@ -647,55 +705,34 @@ impl Blake2bParams { } } - /// Set the MAC key (0–64 bytes; empty disables keying). - /// - /// # Panics - /// - /// Panics if `key.len() > 64`. + /// Set a validated MAC key. Omit this method for unkeyed hashing. #[must_use] #[allow(clippy::indexing_slicing)] - pub fn key(mut self, key: &[u8]) -> Self { - assert!(key.len() <= MAX_KEY_LEN, "Blake2b key must be at most 64 bytes"); + pub fn key(mut self, key: Blake2bKey<'_>) -> Self { + let key = key.as_bytes(); self.key_buf = [0u8; MAX_KEY_LEN]; self.key_buf[..key.len()].copy_from_slice(key); self.key_len = key.len() as u8; self } - /// Set the salt (up to 16 bytes; shorter inputs are zero-padded per spec). - /// - /// # Panics - /// - /// Panics if `salt.len() > 16`. + /// Set the 16-byte RFC 7693 salt field. #[must_use] - #[allow(clippy::indexing_slicing)] - pub fn salt(mut self, salt: &[u8]) -> Self { - assert!(salt.len() <= SALT_LEN, "Blake2b salt must be at most 16 bytes"); - self.salt = [0u8; SALT_LEN]; - self.salt[..salt.len()].copy_from_slice(salt); + pub const fn salt(mut self, salt: [u8; SALT_LEN]) -> Self { + self.salt = salt; self } - /// Set the personalization tag (up to 16 bytes; shorter inputs are zero-padded). - /// - /// # Panics - /// - /// Panics if `personal.len() > 16`. + /// Set the 16-byte RFC 7693 personalization field. #[must_use] - #[allow(clippy::indexing_slicing)] - pub fn personal(mut self, personal: &[u8]) -> Self { - assert!( - personal.len() <= PERSONAL_LEN, - "Blake2b personalization must be at most 16 bytes" - ); - self.personal = [0u8; PERSONAL_LEN]; - self.personal[..personal.len()].copy_from_slice(personal); + pub const fn personal(mut self, personal: [u8; PERSONAL_LEN]) -> Self { + self.personal = personal; self } + #[allow(clippy::indexing_slicing)] fn key_slice(&self) -> &[u8] { - // key_len is set from a slice ≤ MAX_KEY_LEN in `key()`; fallback is unreachable. - self.key_buf.get(..self.key_len as usize).unwrap_or(&[]) + &self.key_buf[..usize::from(self.key_len)] } /// Build a streaming Blake2b-256 hasher initialized with these parameters. @@ -710,29 +747,33 @@ impl Blake2bParams { Blake2b512(Core::new_with_params(64, self.key_slice(), &self.salt, &self.personal)) } - /// Build a streaming variable-output Blake2b hasher (`output_len` bytes, 1..=64). + /// Build a streaming variable-output Blake2b hasher. /// - /// # Panics + /// # Errors /// - /// Panics if `output_len == 0` or `output_len > 64`. + /// Returns [`Blake2Error::InvalidOutputLength`] unless `output_len` is in + /// `1..=64`. /// /// # Examples /// /// ```rust /// use rscrypto::Blake2bParams; /// - /// let mut hasher = Blake2bParams::new().salt(b"domain-salt").build(48); + /// let mut hasher = Blake2bParams::new() + /// .salt(*b"domain-salt\0\0\0\0\0") + /// .build(48)?; /// hasher.update(b"hello "); /// hasher.update(b"world"); /// let mut out = [0u8; 48]; - /// hasher.finalize_into(&mut out); + /// hasher.finalize_into(&mut out)?; + /// # Ok::<(), rscrypto::Blake2Error>(()) /// ``` - #[must_use] - pub fn build(&self, output_len: u8) -> Blake2b { - Blake2b { + pub fn build(&self, output_len: usize) -> Result { + let output_len = validate_output_len(output_len)?; + Ok(Blake2b { core: Core::new_with_params(output_len, self.key_slice(), &self.salt, &self.personal), output_len, - } + }) } /// Compute a Blake2b-256 hash of `data` in one shot using these parameters. @@ -750,9 +791,10 @@ impl Blake2bParams { /// Compute a variable-output Blake2b hash of `data` in one shot using these /// parameters. The output length is taken from `out.len()`. /// - /// # Panics + /// # Errors /// - /// Panics if `out.len() == 0` or `out.len() > 64`. + /// Returns [`Blake2Error::InvalidOutputLength`] unless `out.len()` is in + /// `1..=64`. /// /// # Examples /// @@ -761,14 +803,15 @@ impl Blake2bParams { /// /// let mut out = [0u8; 24]; /// Blake2bParams::new() - /// .personal(b"app-v1") - /// .hash_into(b"msg", &mut out); + /// .personal(*b"app-v1\0\0\0\0\0\0\0\0\0\0") + /// .hash_into(b"msg", &mut out)?; /// assert_ne!(out, [0u8; 24]); + /// # Ok::<(), rscrypto::Blake2Error>(()) /// ``` - pub fn hash_into(&self, data: &[u8], out: &mut [u8]) { - let nn = out.len(); - assert!((1..=MAX_OUTPUT_LEN).contains(&nn), "Blake2b output length must be 1-64",); - oneshot_hash_into_with_params(nn as u8, self.key_slice(), &self.salt, &self.personal, data, out); + pub fn hash_into(&self, data: &[u8], out: &mut [u8]) -> Result<(), Blake2Error> { + let output_len = validate_output_len(out.len())?; + oneshot_hash_into_with_params(output_len, self.key_slice(), &self.salt, &self.personal, data, out); + Ok(()) } } @@ -831,24 +874,18 @@ impl Blake2b256 { } /// Create a keyed Blake2b-256 streaming hasher (Blake2b-MAC). - /// - /// # Panics - /// - /// Panics if `key` is empty or longer than 64 bytes. #[must_use] - pub fn new_keyed(key: &[u8]) -> Self { - assert!(!key.is_empty(), "use Blake2b256::new() for unkeyed hashing"); + pub fn new_keyed(key: Blake2bKey<'_>) -> Self { + let key = key.as_bytes(); + assert!(!key.is_empty(), "validated Blake2b key must not be empty"); Self(Core::new(32, key)) } /// Compute a keyed Blake2b-256 hash in one shot. - /// - /// # Panics - /// - /// Panics if `key` is empty or longer than 64 bytes. #[must_use] - pub fn keyed_digest(key: &[u8], data: &[u8]) -> [u8; 32] { - assert!(!key.is_empty(), "use Blake2b256::digest() for unkeyed hashing"); + pub fn keyed_digest(key: Blake2bKey<'_>, data: &[u8]) -> [u8; 32] { + let key = key.as_bytes(); + assert!(!key.is_empty(), "validated Blake2b key must not be empty"); oneshot_hash_array::<32>(32, key, data) } } @@ -982,24 +1019,18 @@ impl Blake2b512 { } /// Create a keyed Blake2b-512 streaming hasher (Blake2b-MAC). - /// - /// # Panics - /// - /// Panics if `key` is empty or longer than 64 bytes. #[must_use] - pub fn new_keyed(key: &[u8]) -> Self { - assert!(!key.is_empty(), "use Blake2b512::new() for unkeyed hashing"); + pub fn new_keyed(key: Blake2bKey<'_>) -> Self { + let key = key.as_bytes(); + assert!(!key.is_empty(), "validated Blake2b key must not be empty"); Self(Core::new(64, key)) } /// Compute a keyed Blake2b-512 hash in one shot. - /// - /// # Panics - /// - /// Panics if `key` is empty or longer than 64 bytes. #[must_use] - pub fn keyed_digest(key: &[u8], data: &[u8]) -> [u8; 64] { - assert!(!key.is_empty(), "use Blake2b512::digest() for unkeyed hashing"); + pub fn keyed_digest(key: Blake2bKey<'_>, data: &[u8]) -> [u8; 64] { + let key = key.as_bytes(); + assert!(!key.is_empty(), "validated Blake2b key must not be empty"); oneshot_hash_array::<64>(64, key, data) } } @@ -1070,26 +1101,21 @@ impl_std_io_write_for_digest!(Blake2b512); /// /// // One-shot with a 48-byte output. /// let mut out = [0u8; 48]; -/// Blake2b::digest_into(48, b"hello world", &mut out); +/// Blake2b::digest_into(b"hello world", &mut out)?; /// assert_ne!(out, [0u8; 48]); /// /// // Streaming with a 20-byte output. -/// let mut hasher = Blake2b::new(20); +/// let mut hasher = Blake2b::new(20)?; /// hasher.update(b"hello "); /// hasher.update(b"world"); /// let mut tag = [0u8; 20]; -/// hasher.finalize_into(&mut tag); +/// hasher.finalize_into(&mut tag)?; /// /// // Const-generic convenience. /// let tag2 = Blake2b::digest_array::<20>(b"hello world"); /// assert_eq!(tag, tag2); +/// # Ok::<(), rscrypto::Blake2Error>(()) /// ``` -/// -/// # Panics -/// -/// Constructors panic if `output_len` is `0` or greater than `64`. -/// `finalize_into` panics if the supplied slice length does not match the -/// hasher's configured output length. pub struct Blake2b { core: Core, output_len: u8, @@ -1109,30 +1135,32 @@ impl Blake2b { /// Create an unkeyed Blake2b hasher with the given output length. /// - /// # Panics + /// # Errors /// - /// Panics if `output_len == 0` or `output_len > 64`. - #[must_use] - pub fn new(output_len: u8) -> Self { - Self { + /// Returns [`Blake2Error::InvalidOutputLength`] unless `output_len` is in + /// `1..=64`. + pub fn new(output_len: usize) -> Result { + let output_len = validate_output_len(output_len)?; + Ok(Self { core: Core::new(output_len, &[]), output_len, - } + }) } /// Create a keyed Blake2b hasher (Blake2b-MAC) with the given output length. /// - /// # Panics + /// # Errors /// - /// Panics if `output_len == 0`, `output_len > 64`, `key` is empty, or - /// `key.len() > 64`. - #[must_use] - pub fn new_keyed(output_len: u8, key: &[u8]) -> Self { - assert!(!key.is_empty(), "use Blake2b::new() for unkeyed hashing"); - Self { + /// Returns [`Blake2Error::InvalidOutputLength`] unless `output_len` is in + /// `1..=64`. + pub fn new_keyed(output_len: usize, key: Blake2bKey<'_>) -> Result { + let output_len = validate_output_len(output_len)?; + let key = key.as_bytes(); + assert!(!key.is_empty(), "validated Blake2b key must not be empty"); + Ok(Self { core: Core::new(output_len, key), output_len, - } + }) } /// Output length this hasher will produce, in bytes. @@ -1150,16 +1178,16 @@ impl Blake2b { /// Write the hash into `out`. /// - /// # Panics + /// # Errors /// - /// Panics if `out.len() != self.output_size()`. - pub fn finalize_into(&self, out: &mut [u8]) { - assert_eq!( - out.len(), - self.output_len as usize, - "Blake2b::finalize_into output slice must be exactly the configured output length" - ); + /// Returns [`Blake2Error::OutputLengthMismatch`] unless `out.len()` matches + /// [`output_size`](Self::output_size). + pub fn finalize_into(&self, out: &mut [u8]) -> Result<(), Blake2Error> { + if out.len() != self.output_len as usize { + return Err(Blake2Error::OutputLengthMismatch); + } self.core.finalize_into(out); + Ok(()) } /// Reset to the initial state, preserving the configured output length and @@ -1169,46 +1197,41 @@ impl Blake2b { self.core.reset(); } - /// Compute an unkeyed Blake2b hash in one shot with `output_len` bytes - /// written to `out`. + /// Compute an unkeyed Blake2b hash in one shot. /// - /// # Panics + /// The output length is `out.len()`. /// - /// Panics if `output_len == 0`, `output_len > 64`, or - /// `out.len() != output_len as usize`. - pub fn digest_into(output_len: u8, data: &[u8], out: &mut [u8]) { - assert_eq!( - out.len(), - output_len as usize, - "Blake2b::digest_into output slice must be exactly output_len bytes" - ); + /// # Errors + /// + /// Returns [`Blake2Error::InvalidOutputLength`] unless `out.len()` is in + /// `1..=64`. + pub fn digest_into(data: &[u8], out: &mut [u8]) -> Result<(), Blake2Error> { + let output_len = validate_output_len(out.len())?; oneshot_hash_into(output_len, &[], data, out); + Ok(()) } - /// Compute a keyed Blake2b hash in one shot with `output_len` bytes - /// written to `out`. + /// Compute a keyed Blake2b hash in one shot. /// - /// # Panics + /// The output length is `out.len()`. /// - /// Panics if `output_len == 0`, `output_len > 64`, `key` is empty, - /// `key.len() > 64`, or `out.len() != output_len as usize`. - pub fn keyed_digest_into(output_len: u8, key: &[u8], data: &[u8], out: &mut [u8]) { - assert!(!key.is_empty(), "use Blake2b::digest_into() for unkeyed hashing"); - assert_eq!( - out.len(), - output_len as usize, - "Blake2b::keyed_digest_into output slice must be exactly output_len bytes" - ); + /// # Errors + /// + /// Returns [`Blake2Error::InvalidOutputLength`] unless `out.len()` is in + /// `1..=64`. + pub fn keyed_digest_into(key: Blake2bKey<'_>, data: &[u8], out: &mut [u8]) -> Result<(), Blake2Error> { + let output_len = validate_output_len(out.len())?; + let key = key.as_bytes(); + assert!(!key.is_empty(), "validated Blake2b key must not be empty"); oneshot_hash_into(output_len, key, data, out); + Ok(()) } /// Compute an unkeyed Blake2b hash in one shot, returning a fixed-size array. /// /// The output length `N` is enforced at monomorphization time. /// - /// # Panics - /// - /// Fails compilation (via inline const assertion) if `N == 0` or `N > 64`. + /// `N` must be in `1..=64`; invalid lengths fail during monomorphization. #[must_use] pub fn digest_array(data: &[u8]) -> [u8; N] { const { @@ -1219,18 +1242,38 @@ impl Blake2b { /// Compute a keyed Blake2b hash in one shot, returning a fixed-size array. /// - /// # Panics - /// - /// Fails compilation if `N == 0` or `N > 64`; panics at runtime if `key` - /// is empty or longer than 64 bytes. + /// `N` must be in `1..=64`; invalid lengths fail during monomorphization. #[must_use] - pub fn keyed_digest_array(key: &[u8], data: &[u8]) -> [u8; N] { + pub fn keyed_digest_array(key: Blake2bKey<'_>, data: &[u8]) -> [u8; N] { const { assert!(N >= 1 && N <= MAX_OUTPUT_LEN, "Blake2b output length N must be 1..=64"); } - assert!(!key.is_empty(), "use Blake2b::digest_array() for unkeyed hashing"); + let key = key.as_bytes(); + assert!(!key.is_empty(), "validated Blake2b key must not be empty"); oneshot_hash_array::(N as u8, key, data) } + + #[cfg(feature = "argon2")] + #[inline] + pub(crate) fn new_validated(output_len: usize) -> Self { + debug_assert!((1..=MAX_OUTPUT_LEN).contains(&output_len)); + let output_len = output_len as u8; + Self { + core: Core::new(output_len, &[]), + output_len, + } + } + + #[cfg(feature = "argon2")] + #[inline] + pub(crate) fn finalize_into_validated(&self, out: &mut [u8]) { + assert_eq!( + out.len(), + self.output_len as usize, + "validated Blake2b output length must match the configured length" + ); + self.core.finalize_into(out); + } } impl Drop for Blake2b { @@ -1255,6 +1298,10 @@ mod tests { type OracleBlake2b512 = OracleBlake2b; type OracleBlake2bMac256 = Blake2bMac; + fn validated_key(key: &[u8]) -> Blake2bKey<'_> { + Blake2bKey::new(key).unwrap() + } + fn oracle_hash_256(data: &[u8]) -> [u8; 32] { let mut h = OracleBlake2b256::new(); h.update(data); @@ -1395,7 +1442,7 @@ mod tests { hmac::Mac::update(&mut oracle, data); let expected: [u8; 32] = hmac::Mac::finalize(oracle).into_bytes().into(); - let actual = Blake2b256::keyed_digest(key, data); + let actual = Blake2b256::keyed_digest(validated_key(key), data); assert_eq!(actual, expected); } @@ -1406,7 +1453,7 @@ mod tests { hmac::Mac::update(&mut oracle, b""); let expected: [u8; 32] = hmac::Mac::finalize(oracle).into_bytes().into(); - let actual = Blake2b256::keyed_digest(key, b""); + let actual = Blake2b256::keyed_digest(validated_key(key), b""); assert_eq!(actual, expected); } @@ -1419,13 +1466,14 @@ mod tests { hmac::Mac::update(&mut oracle, data); let expected: [u8; 32] = hmac::Mac::finalize(oracle).into_bytes().into(); - let actual = Blake2b256::keyed_digest(key, data); + let actual = Blake2b256::keyed_digest(validated_key(key), data); assert_eq!(actual, expected); } #[test] fn blake2b256_keyed_streaming_reset() { let key = b"my-key"; + let key = validated_key(key); let tag1 = Blake2b256::keyed_digest(key, b"msg1"); let tag2 = Blake2b256::keyed_digest(key, b"msg2"); @@ -1441,7 +1489,7 @@ mod tests { #[test] fn keyed_differs_from_unkeyed() { let hash = Blake2b256::digest(b"hello"); - let tag = Blake2b256::keyed_digest(b"key", b"hello"); + let tag = Blake2b256::keyed_digest(validated_key(b"key"), b"hello"); assert_ne!(hash, tag); } @@ -1450,7 +1498,7 @@ mod tests { #[test] fn variable_output_1_byte() { let mut out = [0u8; 1]; - Blake2b::digest_into(1, b"test", &mut out); + Blake2b::digest_into(b"test", &mut out).unwrap(); assert_ne!(out, [0u8; 1]); } @@ -1458,13 +1506,13 @@ mod tests { fn variable_output_matches_fixed() { // 32-byte variable output should match Blake2b256 let mut var_out = [0u8; 32]; - Blake2b::digest_into(32, b"hello", &mut var_out); + Blake2b::digest_into(b"hello", &mut var_out).unwrap(); let fixed_out = Blake2b256::digest(b"hello"); assert_eq!(var_out, fixed_out); // 64-byte variable output should match Blake2b512 let mut var_out = [0u8; 64]; - Blake2b::digest_into(64, b"hello", &mut var_out); + Blake2b::digest_into(b"hello", &mut var_out).unwrap(); let fixed_out = Blake2b512::digest(b"hello"); assert_eq!(var_out, fixed_out); } @@ -1478,7 +1526,7 @@ mod tests { let expected_arr: [u8; $nn] = oracle.finalize().into(); let mut actual = [0u8; $nn]; - Blake2b::digest_into($nn, data, &mut actual); + Blake2b::digest_into(data, &mut actual).unwrap(); assert_eq!(actual, expected_arr, "Blake2b nn={} oracle mismatch", $nn); }}; } @@ -1516,22 +1564,22 @@ mod tests { fn variable_output_all_lengths_self_consistent() { // 1..=64 oneshot == streaming(single update) == streaming(chunked) let data: [u8; 200] = core::array::from_fn(|i| ((i * 31 + 17) & 0xff) as u8); - for nn in 1u8..=64 { - let mut oneshot = vec![0u8; nn as usize]; - Blake2b::digest_into(nn, &data, &mut oneshot); + for nn in 1usize..=64 { + let mut oneshot = vec![0u8; nn]; + Blake2b::digest_into(&data, &mut oneshot).unwrap(); - let mut h = Blake2b::new(nn); + let mut h = Blake2b::new(nn).unwrap(); h.update(&data); - let mut single_update = vec![0u8; nn as usize]; - h.finalize_into(&mut single_update); + let mut single_update = vec![0u8; nn]; + h.finalize_into(&mut single_update).unwrap(); assert_eq!(oneshot, single_update, "single-update nn={nn}"); - let mut h = Blake2b::new(nn); + let mut h = Blake2b::new(nn).unwrap(); for chunk in data.chunks(37) { h.update(chunk); } - let mut chunked = vec![0u8; nn as usize]; - h.finalize_into(&mut chunked); + let mut chunked = vec![0u8; nn]; + h.finalize_into(&mut chunked).unwrap(); assert_eq!(oneshot, chunked, "chunked nn={nn}"); } } @@ -1539,16 +1587,16 @@ mod tests { #[test] fn variable_output_streaming_matches_oneshot() { let data = [0x5Au8; 300]; - for nn in [1u8, 16, 32, 48, 63, 64] { - let mut expected = vec![0u8; nn as usize]; - Blake2b::digest_into(nn, &data, &mut expected); + for nn in [1usize, 16, 32, 48, 63, 64] { + let mut expected = vec![0u8; nn]; + Blake2b::digest_into(&data, &mut expected).unwrap(); - let mut h = Blake2b::new(nn); + let mut h = Blake2b::new(nn).unwrap(); for chunk in data.chunks(37) { h.update(chunk); } - let mut actual = vec![0u8; nn as usize]; - h.finalize_into(&mut actual); + let mut actual = vec![0u8; nn]; + h.finalize_into(&mut actual).unwrap(); assert_eq!(actual, expected, "streaming mismatch nn={nn}"); } } @@ -1560,11 +1608,12 @@ mod tests { let data: &[u8] = b"message"; let mut actual_32 = [0u8; 32]; - Blake2b::keyed_digest_into(32, key, data, &mut actual_32); + let key = validated_key(key); + Blake2b::keyed_digest_into(key, data, &mut actual_32).unwrap(); assert_eq!(actual_32, Blake2b256::keyed_digest(key, data)); let mut actual_64 = [0u8; 64]; - Blake2b::keyed_digest_into(64, key, data, &mut actual_64); + Blake2b::keyed_digest_into(key, data, &mut actual_64).unwrap(); assert_eq!(actual_64, Blake2b512::keyed_digest(key, data)); } @@ -1572,12 +1621,12 @@ mod tests { fn variable_output_keyed_differs_from_unkeyed() { let key = b"secret-key"; let data = b"message"; - for nn in [1u8, 16, 40, 48, 63] { - let mut keyed = vec![0u8; nn as usize]; - Blake2b::keyed_digest_into(nn, key, data, &mut keyed); + for nn in [1usize, 16, 40, 48, 63] { + let mut keyed = vec![0u8; nn]; + Blake2b::keyed_digest_into(validated_key(key), data, &mut keyed).unwrap(); - let mut unkeyed = vec![0u8; nn as usize]; - Blake2b::digest_into(nn, data, &mut unkeyed); + let mut unkeyed = vec![0u8; nn]; + Blake2b::digest_into(data, &mut unkeyed).unwrap(); assert_ne!(keyed, unkeyed, "keyed vs unkeyed nn={nn}"); } @@ -1587,29 +1636,30 @@ mod tests { fn variable_output_keyed_deterministic() { let key = b"secret-key"; let data = b"message"; - for nn in [1u8, 16, 40, 48, 63] { - let mut a = vec![0u8; nn as usize]; - let mut b = vec![0u8; nn as usize]; - Blake2b::keyed_digest_into(nn, key, data, &mut a); - Blake2b::keyed_digest_into(nn, key, data, &mut b); + for nn in [1usize, 16, 40, 48, 63] { + let mut a = vec![0u8; nn]; + let mut b = vec![0u8; nn]; + let key = validated_key(key); + Blake2b::keyed_digest_into(key, data, &mut a).unwrap(); + Blake2b::keyed_digest_into(key, data, &mut b).unwrap(); assert_eq!(a, b, "determinism nn={nn}"); } } #[test] fn variable_output_reset_preserves_length() { - let mut h = Blake2b::new(40); + let mut h = Blake2b::new(40).unwrap(); h.update(b"first"); let mut first = [0u8; 40]; - h.finalize_into(&mut first); + h.finalize_into(&mut first).unwrap(); h.reset(); h.update(b"second"); let mut second = [0u8; 40]; - h.finalize_into(&mut second); + h.finalize_into(&mut second).unwrap(); let mut expected = [0u8; 40]; - Blake2b::digest_into(40, b"second", &mut expected); + Blake2b::digest_into(b"second", &mut expected).unwrap(); assert_eq!(second, expected); assert_ne!(first, second); } @@ -1618,75 +1668,71 @@ mod tests { fn variable_output_digest_array_matches_into() { let arr = Blake2b::digest_array::<20>(b"hello world"); let mut expected = [0u8; 20]; - Blake2b::digest_into(20, b"hello world", &mut expected); + Blake2b::digest_into(b"hello world", &mut expected).unwrap(); assert_eq!(arr, expected); } #[test] fn params_build_variable_matches_oneshot() { - let params = Blake2bParams::new().salt(b"domain-salt").personal(b"app-v1"); + let params = Blake2bParams::new() + .salt(*b"domain-salt\0\0\0\0\0") + .personal(*b"app-v1\0\0\0\0\0\0\0\0\0\0"); let mut oneshot = [0u8; 24]; - params.hash_into(b"hello world", &mut oneshot); + params.hash_into(b"hello world", &mut oneshot).unwrap(); - let mut h = params.build(24); + let mut h = params.build(24).unwrap(); h.update(b"hello "); h.update(b"world"); let mut streamed = [0u8; 24]; - h.finalize_into(&mut streamed); + h.finalize_into(&mut streamed).unwrap(); assert_eq!(oneshot, streamed); } #[test] fn params_hash_into_matches_plain_when_defaults() { // Empty key/salt/personal + variable length should match direct Blake2b. - for nn in [1u8, 17, 32, 48, 64] { - let mut via_params = vec![0u8; nn as usize]; - Blake2bParams::new().hash_into(b"msg", &mut via_params); + for nn in [1usize, 17, 32, 48, 64] { + let mut via_params = vec![0u8; nn]; + Blake2bParams::new().hash_into(b"msg", &mut via_params).unwrap(); - let mut direct = vec![0u8; nn as usize]; - Blake2b::digest_into(nn, b"msg", &mut direct); + let mut direct = vec![0u8; nn]; + Blake2b::digest_into(b"msg", &mut direct).unwrap(); assert_eq!(via_params, direct); } } #[test] - #[should_panic(expected = "Blake2b output length must be 1-64")] fn params_hash_into_rejects_wrapping_output_length() { let mut out = vec![0u8; 256]; - Blake2bParams::new().hash_into(b"msg", &mut out); + assert_eq!( + Blake2bParams::new().hash_into(b"msg", &mut out), + Err(Blake2Error::InvalidOutputLength) + ); } #[test] - #[should_panic] - fn variable_output_zero_panics() { - let _ = Blake2b::new(0); + fn variable_output_zero_is_rejected() { + assert_eq!(Blake2b::new(0).unwrap_err(), Blake2Error::InvalidOutputLength); } #[test] - #[should_panic] - fn variable_output_over_64_panics() { - let _ = Blake2b::new(65); + fn variable_output_over_64_is_rejected() { + assert_eq!(Blake2b::new(65).unwrap_err(), Blake2Error::InvalidOutputLength); } #[test] - #[should_panic] - fn variable_output_mismatch_slice_panics() { - let mut out = [0u8; 20]; - Blake2b::digest_into(32, b"x", &mut out); + fn variable_output_empty_slice_is_rejected() { + assert_eq!( + Blake2b::digest_into(b"x", &mut []), + Err(Blake2Error::InvalidOutputLength) + ); } #[test] - #[should_panic] - fn variable_output_finalize_wrong_len_panics() { - let h = Blake2b::new(32); + fn variable_output_finalize_wrong_len_is_rejected() { + let h = Blake2b::new(32).unwrap(); let mut out = [0u8; 16]; - h.finalize_into(&mut out); - } - - #[test] - #[should_panic] - fn variable_output_keyed_empty_key_panics() { - let _ = Blake2b::new_keyed(32, b""); + assert_eq!(h.finalize_into(&mut out), Err(Blake2Error::OutputLengthMismatch)); } // ── Edge cases ──────────────────────────────────────────────────────── @@ -1719,15 +1765,13 @@ mod tests { } #[test] - #[should_panic] - fn keyed_empty_key_panics() { - let _ = Blake2b256::new_keyed(b""); + fn keyed_empty_key_is_rejected() { + assert_eq!(Blake2bKey::new(b"").unwrap_err(), Blake2Error::InvalidKeyLength); } #[test] - #[should_panic] - fn keyed_overlength_key_panics() { - let _ = Blake2b256::new_keyed(&[0u8; 65]); + fn keyed_overlength_key_is_rejected() { + assert_eq!(Blake2bKey::new(&[0u8; 65]).unwrap_err(), Blake2Error::InvalidKeyLength); } // ── Finalize is non-destructive ─────────────────────────────────────── @@ -1762,6 +1806,7 @@ mod tests { fn params_key_matches_keyed_digest() { // Empty salt + empty personal + key should equal keyed_digest. let key = b"secret-key"; + let key = validated_key(key); let plain = Blake2b256::keyed_digest(key, b"hello"); let via_params = Blake2bParams::new().key(key).hash_256(b"hello"); assert_eq!(plain, via_params); @@ -1769,8 +1814,8 @@ mod tests { #[test] fn params_salt_changes_output() { - let a = Blake2bParams::new().salt(b"salt-a").hash_256(b"msg"); - let b = Blake2bParams::new().salt(b"salt-b").hash_256(b"msg"); + let a = Blake2bParams::new().salt([b'a'; 16]).hash_256(b"msg"); + let b = Blake2bParams::new().salt([b'b'; 16]).hash_256(b"msg"); let plain = Blake2b256::digest(b"msg"); assert_ne!(a, b); assert_ne!(a, plain); @@ -1779,8 +1824,8 @@ mod tests { #[test] fn params_personal_changes_output() { - let a = Blake2bParams::new().personal(b"ctx-a").hash_256(b"msg"); - let b = Blake2bParams::new().personal(b"ctx-b").hash_256(b"msg"); + let a = Blake2bParams::new().personal([b'a'; 16]).hash_256(b"msg"); + let b = Blake2bParams::new().personal([b'b'; 16]).hash_256(b"msg"); let plain = Blake2b256::digest(b"msg"); assert_ne!(a, b); assert_ne!(a, plain); @@ -1792,12 +1837,12 @@ mod tests { // Swapping which field carries the same bytes must change the output, // proving salt and personal XOR into different IV words. let both_a = Blake2bParams::new() - .salt(b"AAAAAAAAAAAAAAAA") - .personal(b"BBBBBBBBBBBBBBBB") + .salt(*b"AAAAAAAAAAAAAAAA") + .personal(*b"BBBBBBBBBBBBBBBB") .hash_256(b"msg"); let swapped = Blake2bParams::new() - .salt(b"BBBBBBBBBBBBBBBB") - .personal(b"AAAAAAAAAAAAAAAA") + .salt(*b"BBBBBBBBBBBBBBBB") + .personal(*b"AAAAAAAAAAAAAAAA") .hash_256(b"msg"); assert_ne!(both_a, swapped); } @@ -1805,21 +1850,24 @@ mod tests { #[test] fn params_stable_under_repeat() { let a = Blake2bParams::new() - .key(b"k") - .salt(b"s") - .personal(b"p") + .key(validated_key(b"k")) + .salt([b's'; 16]) + .personal([b'p'; 16]) .hash_256(b"data"); let b = Blake2bParams::new() - .key(b"k") - .salt(b"s") - .personal(b"p") + .key(validated_key(b"k")) + .salt([b's'; 16]) + .personal([b'p'; 16]) .hash_256(b"data"); assert_eq!(a, b); } #[test] fn params_streaming_matches_oneshot() { - let params = Blake2bParams::new().key(b"k").salt(b"s").personal(b"p"); + let params = Blake2bParams::new() + .key(validated_key(b"k")) + .salt([b's'; 16]) + .personal([b'p'; 16]); let oneshot = params.hash_256(b"hello world"); let mut h = params.build_256(); @@ -1831,18 +1879,18 @@ mod tests { } #[test] - fn params_short_salt_is_zero_padded() { - // Short salt zero-padded should match explicit zero-padded 16-byte salt. - let short = Blake2bParams::new().salt(b"abc").hash_256(b"msg"); + fn params_exact_salt_is_preserved() { let mut padded = [0u8; 16]; padded[..3].copy_from_slice(b"abc"); - let full = Blake2bParams::new().salt(&padded).hash_256(b"msg"); - assert_eq!(short, full); + let with_salt = Blake2bParams::new().salt(padded).hash_256(b"msg"); + assert_ne!(with_salt, Blake2bParams::new().hash_256(b"msg")); } #[test] fn params_reset_preserves_salt_and_personal() { - let params = Blake2bParams::new().salt(b"salt").personal(b"personal"); + let params = Blake2bParams::new() + .salt(*b"salt\0\0\0\0\0\0\0\0\0\0\0\0") + .personal(*b"personal\0\0\0\0\0\0\0\0"); let mut h = params.build_256(); h.update(b"first"); let _ = h.finalize(); @@ -1855,24 +1903,6 @@ mod tests { assert_eq!(after_reset, expected); } - #[test] - #[should_panic] - fn params_overlong_salt_panics() { - let _ = Blake2bParams::new().salt(&[0u8; 17]); - } - - #[test] - #[should_panic] - fn params_overlong_personal_panics() { - let _ = Blake2bParams::new().personal(&[0u8; 17]); - } - - #[test] - #[should_panic] - fn params_overlong_key_panics() { - let _ = Blake2bParams::new().key(&[0u8; 65]); - } - // ── Per-kernel oracle tests ─────────────────────────────────────────── use super::kernels::{ diff --git a/src/hashes/crypto/blake2s/mod.rs b/src/hashes/crypto/blake2s/mod.rs index b7444e1c..010f16b4 100644 --- a/src/hashes/crypto/blake2s/mod.rs +++ b/src/hashes/crypto/blake2s/mod.rs @@ -22,11 +22,12 @@ //! ## Keyed Hashing //! //! ```rust -//! use rscrypto::{Blake2s256, Digest}; +//! use rscrypto::{Blake2s256, Blake2sKey, Digest}; //! -//! let key = b"secret-key-up-to-32-bytes"; +//! let key = Blake2sKey::new(b"secret-key-up-to-32-bytes")?; //! let tag = Blake2s256::keyed_digest(key, b"message"); //! assert_ne!(tag, Blake2s256::digest(b"message")); +//! # Ok::<(), rscrypto::Blake2Error>(()) //! ``` pub(crate) mod kernels; @@ -43,6 +44,7 @@ use core::{fmt, mem::MaybeUninit}; use kernels::IV; +use super::Blake2Error; use crate::traits::{Digest, ct}; const BLOCK_SIZE: usize = 64; @@ -62,6 +64,59 @@ const SALT_LEN: usize = 8; /// Spec-defined personalization size for Blake2s (RFC 7693 §2.5). const PERSONAL_LEN: usize = 8; +/// Validated 1–32 byte Blake2s key. +/// +/// Construction validates the RFC 7693 key-length invariant. The wrapper then +/// borrows the key without allocation or copying. +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct Blake2sKey<'a>(&'a [u8]); + +impl<'a> Blake2sKey<'a> { + /// Validate and borrow a Blake2s key. + /// + /// # Errors + /// + /// Returns [`Blake2Error::InvalidKeyLength`] for an empty key or a key + /// longer than 32 bytes. + #[inline] + pub fn new(key: &'a [u8]) -> Result { + validate_key(key)?; + Ok(Self(key)) + } + + /// Borrow the validated key bytes. + #[must_use] + pub const fn as_bytes(self) -> &'a [u8] { + self.0 + } +} + +impl fmt::Debug for Blake2sKey<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Blake2sKey") + .field("length", &self.0.len()) + .finish_non_exhaustive() + } +} + +impl<'a> TryFrom<&'a [u8]> for Blake2sKey<'a> { + type Error = Blake2Error; + + #[inline] + fn try_from(key: &'a [u8]) -> Result { + Self::new(key) + } +} + +#[inline] +fn validate_key(key: &[u8]) -> Result<(), Blake2Error> { + if key.is_empty() || key.len() > MAX_KEY_LEN { + return Err(Blake2Error::InvalidKeyLength); + } + Ok(()) +} + #[derive(Clone)] struct Core { h: [u32; 8], @@ -520,32 +575,35 @@ impl Drop for Core { /// Builder for Blake2s hashers with optional key, salt, and personalization. /// -/// Implements the sequential-mode parameter block from RFC 7693 §2.5. Salt -/// (up to 8 bytes) and personalization (up to 8 bytes) are XORed into the -/// initial chaining value words `h[4..8]`, giving the same hasher with a -/// different domain. +/// Implements the sequential-mode parameter block from RFC 7693 §2.5. The +/// exact-size salt and personalization fields are XORed into the initial +/// chaining value words `h[4..8]`, giving the same hasher with a different +/// domain. /// -/// Empty key produces an unkeyed hasher; non-empty key produces a Blake2s-MAC. +/// Omit [`key`](Self::key) for unkeyed hashing. /// /// # Examples /// /// ```rust -/// use rscrypto::Blake2sParams; +/// use rscrypto::{Blake2sKey, Blake2sParams}; +/// +/// let key = Blake2sKey::new(b"my-secret")?; /// /// let tag = Blake2sParams::new() -/// .key(b"my-secret") -/// .salt(b"salt-123") -/// .personal(b"appv1tag") +/// .key(key) +/// .salt(*b"salt-123") +/// .personal(*b"appv1tag") /// .hash_256(b"message"); /// assert_eq!(tag.len(), 32); /// /// // Same input + different personalization → different output. /// let other = Blake2sParams::new() -/// .key(b"my-secret") -/// .salt(b"salt-123") -/// .personal(b"appv2tag") +/// .key(key) +/// .salt(*b"salt-123") +/// .personal(*b"appv2tag") /// .hash_256(b"message"); /// assert_ne!(tag, other); +/// # Ok::<(), rscrypto::Blake2Error>(()) /// ``` pub struct Blake2sParams { key_buf: [u8; MAX_KEY_LEN], @@ -573,55 +631,34 @@ impl Blake2sParams { } } - /// Set the MAC key (0–32 bytes; empty disables keying). - /// - /// # Panics - /// - /// Panics if `key.len() > 32`. + /// Set a validated MAC key. Omit this method for unkeyed hashing. #[must_use] #[allow(clippy::indexing_slicing)] - pub fn key(mut self, key: &[u8]) -> Self { - assert!(key.len() <= MAX_KEY_LEN, "Blake2s key must be at most 32 bytes"); + pub fn key(mut self, key: Blake2sKey<'_>) -> Self { + let key = key.as_bytes(); self.key_buf = [0u8; MAX_KEY_LEN]; self.key_buf[..key.len()].copy_from_slice(key); self.key_len = key.len() as u8; self } - /// Set the salt (up to 8 bytes; shorter inputs are zero-padded per spec). - /// - /// # Panics - /// - /// Panics if `salt.len() > 8`. + /// Set the 8-byte RFC 7693 salt field. #[must_use] - #[allow(clippy::indexing_slicing)] - pub fn salt(mut self, salt: &[u8]) -> Self { - assert!(salt.len() <= SALT_LEN, "Blake2s salt must be at most 8 bytes"); - self.salt = [0u8; SALT_LEN]; - self.salt[..salt.len()].copy_from_slice(salt); + pub const fn salt(mut self, salt: [u8; SALT_LEN]) -> Self { + self.salt = salt; self } - /// Set the personalization tag (up to 8 bytes; shorter inputs are zero-padded). - /// - /// # Panics - /// - /// Panics if `personal.len() > 8`. + /// Set the 8-byte RFC 7693 personalization field. #[must_use] - #[allow(clippy::indexing_slicing)] - pub fn personal(mut self, personal: &[u8]) -> Self { - assert!( - personal.len() <= PERSONAL_LEN, - "Blake2s personalization must be at most 8 bytes" - ); - self.personal = [0u8; PERSONAL_LEN]; - self.personal[..personal.len()].copy_from_slice(personal); + pub const fn personal(mut self, personal: [u8; PERSONAL_LEN]) -> Self { + self.personal = personal; self } + #[allow(clippy::indexing_slicing)] fn key_slice(&self) -> &[u8] { - // key_len is set from a slice ≤ MAX_KEY_LEN in `key()`; fallback is unreachable. - self.key_buf.get(..self.key_len as usize).unwrap_or(&[]) + &self.key_buf[..usize::from(self.key_len)] } /// Build a streaming Blake2s-256 hasher initialized with these parameters. @@ -708,24 +745,18 @@ impl Blake2s256 { } /// Create a keyed Blake2s-256 streaming hasher. - /// - /// # Panics - /// - /// Panics if `key` is empty or longer than 32 bytes. #[must_use] - pub fn new_keyed(key: &[u8]) -> Self { - assert!(!key.is_empty(), "use Blake2s256::new() for unkeyed hashing"); + pub fn new_keyed(key: Blake2sKey<'_>) -> Self { + let key = key.as_bytes(); + assert!(!key.is_empty(), "validated Blake2s key must not be empty"); Self(Core::new(32, key)) } /// Compute a keyed Blake2s-256 hash in one shot. - /// - /// # Panics - /// - /// Panics if `key` is empty or longer than 32 bytes. #[must_use] - pub fn keyed_digest(key: &[u8], data: &[u8]) -> [u8; 32] { - assert!(!key.is_empty(), "use Blake2s256::digest() for unkeyed hashing"); + pub fn keyed_digest(key: Blake2sKey<'_>, data: &[u8]) -> [u8; 32] { + let key = key.as_bytes(); + assert!(!key.is_empty(), "validated Blake2s key must not be empty"); oneshot_hash_array::<32>(32, key, data) } @@ -833,20 +864,18 @@ impl Blake2s128 { } /// Create a keyed Blake2s-128 streaming hasher. - /// - /// # Panics - /// - /// Panics if `key` is empty or longer than 32 bytes. #[must_use] - pub fn new_keyed(key: &[u8]) -> Self { - assert!(!key.is_empty(), "use Blake2s128::new() for unkeyed hashing"); + pub fn new_keyed(key: Blake2sKey<'_>) -> Self { + let key = key.as_bytes(); + assert!(!key.is_empty(), "validated Blake2s key must not be empty"); Self(Core::new(16, key)) } /// Compute a keyed Blake2s-128 hash in one shot. #[must_use] - pub fn keyed_digest(key: &[u8], data: &[u8]) -> [u8; 16] { - assert!(!key.is_empty(), "use Blake2s128::digest() for unkeyed hashing"); + pub fn keyed_digest(key: Blake2sKey<'_>, data: &[u8]) -> [u8; 16] { + let key = key.as_bytes(); + assert!(!key.is_empty(), "validated Blake2s key must not be empty"); oneshot_hash_array::<16>(16, key, data) } @@ -925,6 +954,10 @@ mod tests { type OracleBlake2sMac128 = Blake2sMac; type OracleBlake2sMac256 = Blake2sMac; + fn validated_key(key: &[u8]) -> Blake2sKey<'_> { + Blake2sKey::new(key).unwrap() + } + const ORACLE_CASES: &[&[u8]] = &[ b"", b"a", @@ -1022,7 +1055,7 @@ mod tests { #[test] fn keyed_differs_from_unkeyed() { let hash = Blake2s256::digest(b"hello"); - let tag = Blake2s256::keyed_digest(b"key", b"hello"); + let tag = Blake2s256::keyed_digest(validated_key(b"key"), b"hello"); assert_ne!(hash, tag); } @@ -1035,7 +1068,7 @@ mod tests { oracle.update(data); let expected = oracle.finalize().into_bytes(); - let actual = Blake2s256::keyed_digest(key, data); + let actual = Blake2s256::keyed_digest(validated_key(key), data); assert_eq!(&actual[..], &expected[..]); } @@ -1048,7 +1081,7 @@ mod tests { oracle.update(data); let expected = oracle.finalize().into_bytes(); - let actual = Blake2s128::keyed_digest(key, data); + let actual = Blake2s128::keyed_digest(validated_key(key), data); assert_eq!(&actual[..], &expected[..]); } @@ -1061,7 +1094,7 @@ mod tests { oracle.update(data); let expected = oracle.finalize().into_bytes(); - let actual = Blake2s256::keyed_digest(key, data); + let actual = Blake2s256::keyed_digest(validated_key(key), data); assert_eq!(&actual[..], &expected[..]); } @@ -1100,15 +1133,13 @@ mod tests { } #[test] - #[should_panic] - fn keyed_empty_key_panics() { - let _ = Blake2s256::new_keyed(b""); + fn keyed_empty_key_is_rejected() { + assert_eq!(Blake2sKey::new(b"").unwrap_err(), Blake2Error::InvalidKeyLength); } #[test] - #[should_panic] - fn keyed_overlength_key_panics() { - let _ = Blake2s256::new_keyed(&[0u8; 33]); + fn keyed_overlength_key_is_rejected() { + assert_eq!(Blake2sKey::new(&[0u8; 33]).unwrap_err(), Blake2Error::InvalidKeyLength); } // ── Params (salt + personalization) ─────────────────────────────────── @@ -1130,6 +1161,7 @@ mod tests { #[test] fn params_key_matches_keyed_digest() { let key = b"secret"; + let key = validated_key(key); let plain = Blake2s256::keyed_digest(key, b"hello"); let via_params = Blake2sParams::new().key(key).hash_256(b"hello"); assert_eq!(plain, via_params); @@ -1137,8 +1169,8 @@ mod tests { #[test] fn params_salt_changes_output() { - let a = Blake2sParams::new().salt(b"salt-aaa").hash_256(b"msg"); - let b = Blake2sParams::new().salt(b"salt-bbb").hash_256(b"msg"); + let a = Blake2sParams::new().salt(*b"salt-aaa").hash_256(b"msg"); + let b = Blake2sParams::new().salt(*b"salt-bbb").hash_256(b"msg"); let plain = Blake2s256::digest(b"msg"); assert_ne!(a, b); assert_ne!(a, plain); @@ -1147,8 +1179,8 @@ mod tests { #[test] fn params_personal_changes_output() { - let a = Blake2sParams::new().personal(b"ctx-aaaa").hash_256(b"msg"); - let b = Blake2sParams::new().personal(b"ctx-bbbb").hash_256(b"msg"); + let a = Blake2sParams::new().personal(*b"ctx-aaaa").hash_256(b"msg"); + let b = Blake2sParams::new().personal(*b"ctx-bbbb").hash_256(b"msg"); let plain = Blake2s256::digest(b"msg"); assert_ne!(a, b); assert_ne!(a, plain); @@ -1158,12 +1190,12 @@ mod tests { #[test] fn params_salt_and_personal_are_independent() { let both_a = Blake2sParams::new() - .salt(b"AAAAAAAA") - .personal(b"BBBBBBBB") + .salt(*b"AAAAAAAA") + .personal(*b"BBBBBBBB") .hash_256(b"msg"); let swapped = Blake2sParams::new() - .salt(b"BBBBBBBB") - .personal(b"AAAAAAAA") + .salt(*b"BBBBBBBB") + .personal(*b"AAAAAAAA") .hash_256(b"msg"); assert_ne!(both_a, swapped); } @@ -1171,21 +1203,24 @@ mod tests { #[test] fn params_stable_under_repeat() { let a = Blake2sParams::new() - .key(b"k") - .salt(b"s") - .personal(b"p") + .key(validated_key(b"k")) + .salt([b's'; 8]) + .personal([b'p'; 8]) .hash_256(b"data"); let b = Blake2sParams::new() - .key(b"k") - .salt(b"s") - .personal(b"p") + .key(validated_key(b"k")) + .salt([b's'; 8]) + .personal([b'p'; 8]) .hash_256(b"data"); assert_eq!(a, b); } #[test] fn params_streaming_matches_oneshot() { - let params = Blake2sParams::new().key(b"k").salt(b"s").personal(b"p"); + let params = Blake2sParams::new() + .key(validated_key(b"k")) + .salt([b's'; 8]) + .personal([b'p'; 8]); let oneshot = params.hash_256(b"hello world"); let mut h = params.build_256(); @@ -1197,17 +1232,16 @@ mod tests { } #[test] - fn params_short_salt_is_zero_padded() { - let short = Blake2sParams::new().salt(b"ab").hash_256(b"msg"); + fn params_exact_salt_is_preserved() { let mut padded = [0u8; 8]; padded[..2].copy_from_slice(b"ab"); - let full = Blake2sParams::new().salt(&padded).hash_256(b"msg"); - assert_eq!(short, full); + let with_salt = Blake2sParams::new().salt(padded).hash_256(b"msg"); + assert_ne!(with_salt, Blake2sParams::new().hash_256(b"msg")); } #[test] fn params_reset_preserves_salt_and_personal() { - let params = Blake2sParams::new().salt(b"salty").personal(b"tagging"); + let params = Blake2sParams::new().salt(*b"salty\0\0\0").personal(*b"tagging\0"); let mut h = params.build_256(); h.update(b"first"); let _ = h.finalize(); @@ -1220,24 +1254,6 @@ mod tests { assert_eq!(after_reset, expected); } - #[test] - #[should_panic] - fn params_overlong_salt_panics() { - let _ = Blake2sParams::new().salt(&[0u8; 9]); - } - - #[test] - #[should_panic] - fn params_overlong_personal_panics() { - let _ = Blake2sParams::new().personal(&[0u8; 9]); - } - - #[test] - #[should_panic] - fn params_overlong_key_panics() { - let _ = Blake2sParams::new().key(&[0u8; 33]); - } - // ── Per-kernel oracle tests ─────────────────────────────────────────── fn assert_blake2s_kernel(id: Blake2sKernelId) { diff --git a/src/hashes/crypto/mod.rs b/src/hashes/crypto/mod.rs index 971cdcc4..2c02f9e2 100644 --- a/src/hashes/crypto/mod.rs +++ b/src/hashes/crypto/mod.rs @@ -4,6 +4,9 @@ #![cfg_attr(not(test), deny(clippy::expect_used))] #![cfg_attr(not(test), deny(clippy::indexing_slicing))] +#[cfg(any(feature = "blake2b", feature = "blake2s"))] +use core::fmt; + #[cfg(feature = "ascon-hash")] pub mod ascon; #[cfg(feature = "blake2b")] @@ -38,11 +41,11 @@ pub use ascon::{AsconCxof128, AsconCxof128Reader, AsconHash256, AsconXof, AsconX #[cfg(all(feature = "diag", feature = "blake2b"))] pub use blake2b::diag_blake2b256_keyed_digest_portable; #[cfg(feature = "blake2b")] -pub use blake2b::{Blake2b, Blake2b256, Blake2b512, Blake2bParams}; +pub use blake2b::{Blake2b, Blake2b256, Blake2b512, Blake2bKey, Blake2bParams}; #[cfg(all(feature = "diag", feature = "blake2s"))] pub use blake2s::diag_blake2s256_keyed_digest_portable; #[cfg(feature = "blake2s")] -pub use blake2s::{Blake2s128, Blake2s256, Blake2sParams}; +pub use blake2s::{Blake2s128, Blake2s256, Blake2sKey, Blake2sParams}; #[cfg(all(feature = "diag", feature = "blake3"))] pub use blake3::diag_blake3_keyed_digest_portable; #[cfg(feature = "blake3")] @@ -61,3 +64,30 @@ pub use sha384::Sha384; pub use sha512::Sha512; #[cfg(feature = "sha2")] pub use sha512_256::Sha512_256; + +/// Invalid BLAKE2 key, parameter, or output length. +#[cfg(any(feature = "blake2b", feature = "blake2s"))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum Blake2Error { + /// A keyed operation requires a non-empty key within the algorithm's limit. + InvalidKeyLength, + /// A variable BLAKE2b output must contain 1 to 64 bytes. + InvalidOutputLength, + /// The caller-provided output buffer does not match the configured length. + OutputLengthMismatch, +} + +#[cfg(any(feature = "blake2b", feature = "blake2s"))] +impl fmt::Display for Blake2Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidKeyLength => f.write_str("BLAKE2 key length is invalid"), + Self::InvalidOutputLength => f.write_str("BLAKE2 output length is invalid"), + Self::OutputLengthMismatch => f.write_str("BLAKE2 output buffer length does not match the configured length"), + } + } +} + +#[cfg(any(feature = "blake2b", feature = "blake2s"))] +impl core::error::Error for Blake2Error {} diff --git a/src/hex.rs b/src/hex.rs index 7dcfb518..48f0bef3 100644 --- a/src/hex.rs +++ b/src/hex.rs @@ -320,43 +320,12 @@ macro_rules! impl_serde_secret_bytes { ($type:ty) => {}; } -/// Generate a `random()` constructor that fills `Self::LENGTH` bytes from -/// the operating system CSPRNG via `getrandom`. -/// -/// Compatibility policy: keep `random()` / `try_random()` for the first -/// release that exposes newer key-generation names; decide any actual -/// deprecation only after that release. -/// -/// Invoke inside an `impl` block for a tuple-struct with `LENGTH` and -/// `from_bytes`. +/// Generate a fallible constructor that fills `Self::LENGTH` bytes from the +/// operating system CSPRNG via `getrandom`. macro_rules! impl_getrandom { () => { - /// Generate a random instance using the operating system's CSPRNG. - /// - /// Compatibility name: prefer `try_generate()` where the concrete key type - /// exposes it. This method remains supported in the release that introduces - /// the newer generation names. - /// - /// # Panics - /// - /// Panics if the platform entropy source is unavailable. - #[cfg(feature = "getrandom")] - #[cfg_attr(docsrs, doc(cfg(feature = "getrandom")))] - #[inline] - #[must_use] - pub fn random() -> Self { - match Self::try_random() { - Ok(value) => value, - Err(e) => panic!("getrandom failed: {e}"), - } - } - /// Try to generate a random instance from the platform entropy source. /// - /// Compatibility name: prefer `try_generate()` where the concrete key type - /// exposes it. This method remains supported in the release that introduces - /// the newer generation names. - /// /// # Errors /// /// Returns a getrandom error if the platform entropy source is unavailable. diff --git a/src/lib.rs b/src/lib.rs index e255d838..96456286 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,8 +257,8 @@ mod macros; // Internal modules (not published as separate crates) // `hex` is an internal utility module for public byte newtypes that expose // hex formatting, parsing, or explicit secret display. -// Public re-exports of `DisplaySecret` / `InvalidHexError` stay gated to the -// features that surface them in the public API. +// Public re-exports of `expert::DisplaySecret` / `InvalidHexError` stay gated +// to the features that surface them in the public API. #[cfg(any( feature = "aes-gcm", feature = "aes-gcm-siv", @@ -380,27 +380,10 @@ pub use aead::{XChaCha20Poly1305, XChaCha20Poly1305Key, XChaCha20Poly1305Tag}; pub use auth::HkdfOutputLengthError; #[cfg(all(feature = "phc-strings", any(feature = "argon2", feature = "scrypt")))] pub use auth::PasswordStatus; -#[cfg(all(feature = "diag", feature = "ed25519"))] -pub use auth::diag_ed25519_select_basepoint_cached_limb_digest; -#[cfg(all( - feature = "diag", - feature = "ed25519", - target_arch = "aarch64", - any(target_os = "macos", target_os = "linux"), - not(feature = "portable-only"), - not(miri) -))] -pub use auth::diag_ed25519_verify_aarch64_asm_double_scalar_digest; #[cfg(feature = "argon2")] pub use auth::{Argon2Context, Argon2Error, Argon2Params, Argon2d, Argon2i, Argon2id}; #[cfg(all(feature = "argon2", feature = "phc-strings"))] pub use auth::{Argon2VerificationLimits, Argon2idPassword}; -#[cfg(all(feature = "diag", feature = "ed25519"))] -pub use auth::{ - DiagEd25519VerifyScalars, diag_ed25519_verify_challenge_reduce_digest, - diag_ed25519_verify_portable_double_scalar_digest, diag_ed25519_verify_public_decode_digest, - diag_ed25519_verify_r_decode_digest, diag_ed25519_verify_scalars, -}; #[cfg(any(feature = "ecdsa-p256", feature = "ecdsa-p384"))] pub use auth::{EcdsaError, EcdsaKeyGenerationError}; #[cfg(feature = "ecdsa-p256")] @@ -446,65 +429,6 @@ pub use auth::{Scrypt, ScryptError, ScryptParams}; pub use auth::{ScryptPassword, ScryptVerificationLimits}; #[cfg(feature = "x25519")] pub use auth::{X25519Error, X25519PublicKey, X25519SecretKey, X25519SharedSecret}; -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] -pub use auth::{ - diag_ecdsa_p256_basepoint_blinded_limb_digest, diag_ecdsa_p256_final_multiply_limb_digest, - diag_ecdsa_p256_nonce_inverse_limb_digest, diag_ecdsa_p256_nonce_reduce_limb_digest, - diag_ecdsa_p256_order_mul_blinded_fixed_r_limb_digest, diag_ecdsa_p256_order_mul_fixed_r_limb_digest, - diag_ecdsa_p256_reduce_wide_order_limb_digest, diag_ecdsa_p256_scalar_finish_limb_digest, - diag_ecdsa_p256_select_signing_generator_affine_limb_digest, -}; -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] -pub use auth::{ - diag_ecdsa_p384_basepoint_blinded_limb_digest, diag_ecdsa_p384_basepoint_r_limb_digest, - diag_ecdsa_p384_final_multiply_limb_digest, diag_ecdsa_p384_nonce_inverse_limb_digest, - diag_ecdsa_p384_nonce_reduce_limb_digest, diag_ecdsa_p384_order_mul_fixed_r_limb_digest, - diag_ecdsa_p384_reduce_wide_order_limb_digest, diag_ecdsa_p384_scalar_finish_limb_digest, - diag_ecdsa_p384_select_signing_generator_affine_limb_digest, -}; -#[cfg(all(feature = "diag", feature = "ed25519", target_arch = "x86_64"))] -pub use auth::{ - diag_ed25519_select_basepoint_cached_avx2_limb_digest, diag_ed25519_select_basepoint_cached_ifma_limb_digest, -}; -#[cfg(all(feature = "diag", feature = "hkdf"))] -pub use auth::{diag_hkdf_sha256_derive_portable, diag_hkdf_sha384_derive_portable, diag_hkdf_sha512_derive_portable}; -#[cfg(all(feature = "diag", feature = "hmac"))] -pub use auth::{diag_hmac_sha256_verify_portable, diag_hmac_sha384_verify_portable, diag_hmac_sha512_verify_portable}; -#[cfg(all( - feature = "diag", - feature = "ml-kem", - target_arch = "aarch64", - any(target_os = "macos", target_os = "linux"), - not(miri), - not(feature = "portable-only") -))] -pub use auth::{ - diag_mlkem_aarch64_multiply_ntts_add_assign_asm_digest, diag_mlkem_aarch64_multiply_ntts_add_assign_asm_input_digest, - diag_mlkem768_aarch64_multiply_ntts_accumulate_asm_digest, - diag_mlkem768_aarch64_multiply_ntts_accumulate_asm_input_digest, - diag_mlkem1024_aarch64_multiply_ntts_accumulate_asm_digest, - diag_mlkem1024_aarch64_multiply_ntts_accumulate_asm_input_digest, -}; -#[cfg(all(feature = "diag", feature = "ml-kem"))] -pub use auth::{ - diag_mlkem_compress_decompress_values_digest, diag_mlkem_from_montgomery_product_domain_input_digest, - diag_mlkem_inverse_ntt_montgomery_product_add_assign_input_digest, - diag_mlkem_inverse_ntt_montgomery_product_input_digest, diag_mlkem_multiply_ntts_add_assign_input_digest, - diag_mlkem_ntt_input_digest, diag_mlkem_to_montgomery_product_domain_input_digest, - diag_mlkem512_keygen_secret_noise_digest, diag_mlkem512_multiply_ntts_accumulate_digest, - diag_mlkem512_multiply_ntts_accumulate_input_digest, diag_mlkem768_keygen_secret_noise_digest, - diag_mlkem768_multiply_ntts_accumulate_digest, diag_mlkem1024_keygen_secret_noise_digest, - diag_mlkem1024_multiply_ntts_accumulate_digest, diag_mlkem1024_multiply_ntts_accumulate_input_digest, -}; -#[cfg(all(feature = "diag", feature = "pbkdf2"))] -pub use auth::{diag_pbkdf2_sha256_verify_portable, diag_pbkdf2_sha512_verify_portable}; -#[cfg(all(feature = "rsa", feature = "diag"))] -pub use auth::{ - diag_rsa_import_pkcs8_private_key_der_stage, diag_rsa_private_select_window_power_4, - diag_rsa_validate_pkcs8_private_key_der, diag_rsa_validate_pkcs8_private_key_der_stage, -}; -#[cfg(all(feature = "diag", feature = "x25519"))] -pub use backend::curve25519::diag_curve25519_conditional_swap; #[cfg(feature = "crc24")] pub use checksum::Crc24OpenPgp; #[cfg(feature = "crc16")] @@ -513,15 +437,17 @@ pub use checksum::{Crc16Ccitt, Crc16Ibm}; pub use checksum::{Crc32, Crc32C}; #[cfg(feature = "crc64")] pub use checksum::{Crc64, Crc64Nvme}; +#[cfg(any(feature = "blake2b", feature = "blake2s"))] +pub use hashes::crypto::Blake2Error; // Hash re-exports. #[cfg(feature = "ascon-hash")] pub use hashes::crypto::ascon::AsconCxofCustomizationError; #[cfg(feature = "ascon-hash")] pub use hashes::crypto::{AsconCxof128, AsconCxof128Reader, AsconHash256, AsconXof, AsconXofReader}; #[cfg(feature = "blake2b")] -pub use hashes::crypto::{Blake2b, Blake2b256, Blake2b512, Blake2bParams}; +pub use hashes::crypto::{Blake2b, Blake2b256, Blake2b512, Blake2bKey, Blake2bParams}; #[cfg(feature = "blake2s")] -pub use hashes::crypto::{Blake2s128, Blake2s256, Blake2sParams}; +pub use hashes::crypto::{Blake2s128, Blake2s256, Blake2sKey, Blake2sParams}; #[cfg(feature = "blake3")] pub use hashes::crypto::{Blake3, Blake3KeyedHash, Blake3XofReader}; #[cfg(feature = "sha3")] @@ -551,7 +477,7 @@ pub use hashes::fast::{Xxh3_128Hasher, Xxh3BuildHasher, Xxh3Hasher}; feature = "ml-kem", feature = "x25519" ))] -pub use hex::{DisplaySecret, InvalidHexError}; +pub use hex::InvalidHexError; pub use secret::SecretBytes; #[cfg(feature = "alloc")] pub use secret::SecretVec; @@ -578,6 +504,26 @@ pub use traits::{Checksum, ChecksumCombine, Kem, Mac, TrySigner, TrySignerInto, ))] pub use traits::{Digest, FastHash, Xof}; +/// Explicitly dangerous capabilities that normal application code does not need. +/// +/// Their placement is an intentional opt-in, not an additional runtime layer. +pub mod expert { + #[cfg(any( + feature = "aes-gcm", + feature = "aes-gcm-siv", + feature = "chacha20poly1305", + feature = "xchacha20poly1305", + feature = "aegis256", + feature = "ascon-aead", + feature = "ecdsa-p256", + feature = "ecdsa-p384", + feature = "ed25519", + feature = "ml-kem", + feature = "x25519" + ))] + pub use crate::hex::DisplaySecret; +} + /// Trait-first imports for rscrypto user code. /// /// This prelude intentionally re-exports traits and common verification @@ -678,6 +624,10 @@ use rscrypto::platform_describe; use rscrypto::DigestReader; ``` +```compile_fail +use rscrypto::diag_hmac_sha256_verify_portable; +``` + ```rust use rscrypto::checksum::config::Crc32Config; use rscrypto::checksum::buffered::BufferedCrc32C; @@ -705,6 +655,59 @@ let _ = core::any::TypeId::of::(); "#] pub struct __RootSurfaceAudit; +#[cfg(all(doctest, feature = "full", feature = "getrandom"))] +#[doc(hidden)] +#[doc = r#" +```compile_fail +use rscrypto::DisplaySecret; +``` + +```compile_fail +use rscrypto::platform::OverrideError; +``` + +```compile_fail +use rscrypto::platform::try_set_override; +``` + +```compile_fail +use rscrypto::aead::{ChaCha20Poly1305Key, Nonce96}; + +let _ = ChaCha20Poly1305Key::random(); +let _ = Nonce96::random(); +``` + +```compile_fail +use rscrypto::{Aead, ChaCha20Poly1305, ChaCha20Poly1305Key, aead::Nonce96}; + +let cipher = ChaCha20Poly1305::new(&ChaCha20Poly1305Key::from_bytes([0u8; 32])); +let nonce = Nonce96::from_bytes([0u8; 12]); +let mut out = [0u8; 16]; +cipher.encrypt(&nonce, b"", b"", &mut out)?; +``` + +```compile_fail +let _ = rscrypto::aead::__SealToken(()); +``` + +```rust +use rscrypto::{ + Aead, ChaCha20Poly1305, ChaCha20Poly1305Key, + aead::{Nonce96, expert::AeadWithNonce}, +}; + +let cipher = ChaCha20Poly1305::new(&ChaCha20Poly1305Key::from_bytes([0u8; 32])); +let nonce = Nonce96::from_bytes([0u8; 12]); +let mut out = [0u8; 16]; +cipher.encrypt(&nonce, b"", b"", &mut out)?; +let display_key = ChaCha20Poly1305Key::from_bytes([0u8; 32]); +let _: rscrypto::expert::DisplaySecret<'_> = display_key.display_secret(); +let _: Option = None; +# Ok::<(), rscrypto::aead::SealError>(()) +``` +"#] +pub struct __MisuseResistantSurfaceAudit; + #[cfg(all(doctest, feature = "full"))] #[doc(hidden)] #[doc = r#" @@ -1234,7 +1237,7 @@ mod send_sync_assertions { assert_send_sync::(); assert_send_sync::(); assert_send_sync::(); - assert_send_sync::(); + assert_send_sync::(); assert_send_sync::(); } @@ -1358,14 +1361,14 @@ mod send_sync_assertions { assert_clone::(); assert_clone::(); assert_clone::(); - assert_clone::(); + assert_clone::(); assert_clone::(); assert_clone::(); assert_debug::(); assert_debug::(); assert_debug::(); - assert_debug::(); + assert_debug::(); assert_debug::(); assert_debug::(); } diff --git a/src/platform/detect.rs b/src/platform/detect.rs index 8d603008..9e360971 100644 --- a/src/platform/detect.rs +++ b/src/platform/detect.rs @@ -12,9 +12,10 @@ //! # Override Support //! //! ``` -//! use rscrypto::platform::Detected; -//! rscrypto::platform::set_override(Some(Detected::portable())); -//! rscrypto::platform::clear_override(); +//! use rscrypto::platform::{Detected, expert}; +//! expert::try_set_override(Some(Detected::portable()))?; +//! expert::try_set_override(None)?; +//! # Ok::<(), rscrypto::platform::expert::OverrideError>(()) //! ``` use crate::platform::caps::{Arch, Caps}; @@ -50,8 +51,8 @@ impl core::fmt::Display for OverrideError { /// - `caps`: Available CPU features (what instructions can run) /// - `arch`: Target architecture identifier /// -/// Use [`get()`] to obtain a cached instance, or [`detect_uncached()`] for -/// fresh detection (useful for testing/benchmarking). +/// Use [`crate::platform::get`] to obtain a cached instance, or +/// [`crate::platform::expert::detect_uncached`] for fresh detection. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Detected { /// CPU feature capabilities bitset. @@ -120,7 +121,7 @@ fn validate_override(value: Option) -> Result, Overri /// # Examples /// /// ``` -/// let det = rscrypto::platform::detect::get(); +/// let det = rscrypto::platform::get(); /// assert_eq!(det.arch, rscrypto::platform::Arch::current()); /// ``` #[inline] diff --git a/src/platform/detect/cache_override.rs b/src/platform/detect/cache_override.rs index a936f4e7..ec083fc1 100644 --- a/src/platform/detect/cache_override.rs +++ b/src/platform/detect/cache_override.rs @@ -6,27 +6,10 @@ use std::sync::OnceLock; #[cfg(all(feature = "std", not(miri)))] static STD_CACHE: OnceLock = OnceLock::new(); -/// Set detection override. -/// -/// Must be called **before** the first call to [`get()`]. After caching occurs, -/// updates are rejected. -/// -/// # Panics -/// -/// Panics if detection has already been initialized or overrides are unsupported -/// on the current target. Use [`try_set_override()`] for a fallible path. -#[cold] -#[track_caller] -pub fn set_override(value: Option) { - if let Err(err) = try_set_override(value) { - panic!("platform::set_override failed: {err}"); - } -} - /// Try to set detection override. /// -/// Contract: pre-init only. Once [`get()`] has initialized detection state, -/// this returns [`OverrideError::AlreadyInitialized`]. +/// Contract: pre-init only. Once [`crate::platform::get`] has initialized +/// detection state, this returns [`OverrideError::AlreadyInitialized`]. #[cold] pub fn try_set_override(value: Option) -> Result<(), OverrideError> { #[cfg(any(feature = "std", all(not(feature = "std"), target_has_atomic = "64")))] @@ -57,13 +40,6 @@ pub fn try_set_override(value: Option) -> Result<(), OverrideError> { } } -/// Clear detection override. -#[cold] -#[track_caller] -pub fn clear_override() { - set_override(None); -} - /// Check if an override is set. #[inline] #[must_use] diff --git a/src/platform/detect/compile_time.rs b/src/platform/detect/compile_time.rs index a0073924..84faac4c 100644 --- a/src/platform/detect/compile_time.rs +++ b/src/platform/detect/compile_time.rs @@ -16,7 +16,7 @@ /// # Examples /// /// ``` -/// use rscrypto::platform::detect::caps_static; +/// use rscrypto::platform::caps_static; /// /// // Evaluates at compile time—no runtime cost /// const CAPS: rscrypto::platform::Caps = caps_static(); @@ -41,7 +41,7 @@ /// ``` /// # #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))] /// # fn example() { -/// use rscrypto::platform::{caps::x86, detect::caps_static}; +/// use rscrypto::platform::{caps::x86, caps_static}; /// /// const CAPS: rscrypto::platform::Caps = caps_static(); /// // AVX-512 features are detected at compile time diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 6c9eac99..289ed7da 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -36,14 +36,14 @@ // Core modules pub mod caps; -pub mod detect; +pub(crate) mod detect; #[cfg(not(miri))] mod target_matrix; // Public API - Types pub use caps::{Arch, Caps}; -pub use detect::{Detected, OverrideError}; +pub use detect::Detected; // Architecture-specific feature constants are available via submodules: // - `caps::x86` - x86/x86_64 features (SSE, AVX, AVX-512, etc.) @@ -89,56 +89,12 @@ pub fn arch() -> Arch { detect::arch() } -/// Set detection override. +/// Explicit controls and uncached detection for tests and constrained runtimes. /// -/// Call this **before** any call to [`get()`] to bypass runtime detection. -/// Useful for bare-metal, embedded, or testing. -/// -/// # Panics -/// -/// Panics if detection has already been initialized or overrides are unsupported -/// on the current target. Use [`try_set_override()`] for a fallible path. -/// -/// # Examples -/// -/// ``` -/// use rscrypto::platform::Detected; -/// -/// rscrypto::platform::set_override(Some(Detected::portable())); -/// assert!(rscrypto::platform::has_override()); -/// rscrypto::platform::clear_override(); -/// ``` -#[inline] -pub fn set_override(value: Option) { - detect::set_override(value); -} - -/// Try to set detection override. -/// -/// Returns an explicit error if detection has already been initialized. -#[inline] -pub fn try_set_override(value: Option) -> Result<(), OverrideError> { - detect::try_set_override(value) -} - -/// Clear the detection override. -/// -/// Equivalent to `set_override(None)`. -/// -/// # Panics -/// -/// Panics under the same conditions as [`set_override()`]. Use -/// [`try_set_override(None)`](try_set_override) for a fallible path. -#[inline] -pub fn clear_override() { - detect::clear_override(); -} - -/// Check if an override is currently set. -#[inline] -#[must_use] -pub fn has_override() -> bool { - detect::has_override() +/// Normal callers should use [`get()`], [`caps()`], or [`arch()`]. Overrides +/// must be configured before the first cached detection. +pub mod expert { + pub use super::detect::{OverrideError, detect_uncached, has_override, try_set_override}; } /// Get compile-time known capabilities. @@ -146,7 +102,7 @@ pub fn has_override() -> bool { /// Returns capabilities that are known at compile time via `-C target-feature=...` /// or `-C target-cpu=native`. Use this for zero-overhead dispatch. /// -/// See [`detect::caps_static`] for details. +/// This is a compile-time constant and performs no runtime detection. #[inline(always)] #[must_use] pub const fn caps_static() -> Caps { diff --git a/src/traits/aead.rs b/src/traits/aead.rs index a1fdb56a..b81723fa 100644 --- a/src/traits/aead.rs +++ b/src/traits/aead.rs @@ -12,6 +12,17 @@ use core::fmt::Debug; use crate::aead::RandomSealError; use crate::aead::{AeadBufferError, AeadNonce, OpenError, SealError}; +/// Zero-sized capability proving that a call came through a crate-owned nonce +/// issuer or the explicit expert extension. +#[doc(hidden)] +pub struct SealToken(()); + +impl SealToken { + const fn new() -> Self { + Self(()) + } +} + /// Authenticated encryption with associated data. /// /// The in-place methods mutate the payload buffer and return or consume the tag @@ -68,6 +79,16 @@ pub trait Aead { /// [`TAG_SIZE`](Self::TAG_SIZE). fn tag_from_slice(bytes: &[u8]) -> Result; + /// Internal sealing primitive used by fresh-nonce and expert APIs. + #[doc(hidden)] + fn __encrypt_in_place_with_nonce( + &self, + nonce: &Self::Nonce, + aad: &[u8], + buffer: &mut [u8], + token: SealToken, + ) -> Result; + /// Encrypt `plaintext` into `out` as `ciphertext || tag` with a fresh random nonce. /// /// This is the default one-shot sealing API when the `getrandom` feature is @@ -84,7 +105,7 @@ pub trait Aead { #[inline] fn seal_random(&self, aad: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result { let nonce = Self::Nonce::try_random()?; - self.encrypt(&nonce, aad, plaintext, out)?; + AeadWithNonce::encrypt(self, &nonce, aad, plaintext, out)?; Ok(nonce) } @@ -129,20 +150,10 @@ pub trait Aead { #[inline] fn seal_random_in_place(&self, aad: &[u8], buffer: &mut [u8]) -> Result<(Self::Nonce, Self::Tag), RandomSealError> { let nonce = Self::Nonce::try_random()?; - let tag = self.encrypt_in_place(&nonce, aad, buffer)?; + let tag = self.__encrypt_in_place_with_nonce(&nonce, aad, buffer, SealToken::new())?; Ok((nonce, tag)) } - /// Encrypt `buffer` in place with a caller-supplied nonce and return the detached authentication - /// tag. - /// - /// Prefer `seal_random_in_place` when OS - /// randomness is available, or a dedicated nonce stream such as - /// `NonceCounter` for high-volume AES-GCM. - /// Supplying a nonce directly is for protocols that already define nonce - /// derivation and for test vectors. - fn encrypt_in_place(&self, nonce: &Self::Nonce, aad: &[u8], buffer: &mut [u8]) -> Result; - /// Decrypt `buffer` in place and verify the detached authentication tag. /// /// # Errors @@ -157,17 +168,6 @@ pub trait Aead { tag: &Self::Tag, ) -> Result<(), OpenError>; - /// Alias for [`encrypt_in_place`](Self::encrypt_in_place). - #[inline] - fn encrypt_in_place_detached( - &self, - nonce: &Self::Nonce, - aad: &[u8], - buffer: &mut [u8], - ) -> Result { - self.encrypt_in_place(nonce, aad, buffer) - } - /// Alias for [`decrypt_in_place`](Self::decrypt_in_place). #[inline] fn decrypt_in_place_detached( @@ -207,55 +207,48 @@ pub trait Aead { Ok(ciphertext_and_tag_len.strict_sub(Self::TAG_SIZE)) } - /// Encrypt `plaintext` into `out` as `ciphertext || tag` with a caller-supplied nonce. - /// - /// Prefer `seal_random` when OS randomness is - /// available, or a dedicated nonce stream such as - /// `NonceCounter` for high-volume AES-GCM. - /// Supplying a nonce directly is for protocols that already define nonce - /// derivation and for test vectors. + /// Decrypt a combined `ciphertext || tag` into `out`. /// /// # Errors /// - /// Returns [`SealError`] if `out.len()` does not match `plaintext.len() + - /// TAG_SIZE`, that addition overflows, or the input exceeds the algorithm's - /// supported length bound. + /// Returns [`OpenError`] if the input is malformed, `out` has the wrong + /// length, or authentication fails. #[inline] - fn encrypt(&self, nonce: &Self::Nonce, aad: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result<(), SealError> { - let expected = Self::ciphertext_len(plaintext.len()).map_err(SealError::from)?; - if out.len() != expected { - return Err(SealError::buffer()); + fn decrypt( + &self, + nonce: &Self::Nonce, + aad: &[u8], + ciphertext_and_tag: &[u8], + out: &mut [u8], + ) -> Result<(), OpenError> { + let plaintext_len = Self::plaintext_len(ciphertext_and_tag.len())?; + if out.len() != plaintext_len { + return Err(OpenError::buffer()); } - let (ciphertext, tag_out) = out.split_at_mut(plaintext.len()); - ciphertext.copy_from_slice(plaintext); - let tag = match self.encrypt_in_place(nonce, aad, ciphertext) { - Ok(tag) => tag, - Err(err) => { - super::ct::zeroize(out); - return Err(err); - } - }; - tag_out.copy_from_slice(tag.as_ref()); + let (ciphertext, tag_bytes) = ciphertext_and_tag.split_at(plaintext_len); + out.copy_from_slice(ciphertext); + let tag = Self::tag_from_slice(tag_bytes)?; + if let Err(e) = self.decrypt_in_place(nonce, aad, out, &tag) { + super::ct::zeroize(out); + return Err(e); + } Ok(()) } - /// Allocate `ciphertext || tag` and encrypt `plaintext` into it with a caller-supplied nonce. - /// - /// Prefer [`encrypt`](Self::encrypt) for allocation-free paths and - /// [`seal_random_to_vec`](Self::seal_random_to_vec) when the protocol does - /// not derive its own nonce. + /// Allocate plaintext output and decrypt a combined `ciphertext || tag`. /// /// # Errors /// - /// Returns [`SealError`] if length calculation fails or encryption fails. + /// Returns [`OpenError`] if the input is malformed or authentication fails. + /// Any allocated plaintext buffer is cleared before returning an error. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[inline] - fn encrypt_to_vec(&self, nonce: &Self::Nonce, aad: &[u8], plaintext: &[u8]) -> Result, SealError> { - let len = Self::ciphertext_len(plaintext.len()).map_err(SealError::from)?; + fn decrypt_to_vec(&self, nonce: &Self::Nonce, aad: &[u8], ciphertext_and_tag: &[u8]) -> Result, OpenError> { + let len = Self::plaintext_len(ciphertext_and_tag.len())?; let mut out = vec![0u8; len]; - match self.encrypt(nonce, aad, plaintext, &mut out) { + match self.decrypt(nonce, aad, ciphertext_and_tag, &mut out) { Ok(()) => Ok(out), Err(err) => { super::ct::zeroize(&mut out); @@ -263,49 +256,76 @@ pub trait Aead { } } } +} - /// Decrypt a combined `ciphertext || tag` into `out`. +/// Expert extension for protocols that define their own nonce derivation. +/// +/// Import this trait explicitly from `rscrypto::aead::expert` only when the +/// protocol, persistent state, or test vector guarantees nonce uniqueness. +/// Reusing a nonce with the same key can destroy confidentiality and +/// authenticity. +pub trait AeadWithNonce: Aead { + /// Encrypt `buffer` in place with a caller-supplied nonce. /// /// # Errors /// - /// Returns [`OpenError`] if the input is malformed, `out` has the wrong - /// length, or authentication fails. + /// Returns [`SealError`] if the input exceeds the algorithm's supported + /// length bound. #[inline] - fn decrypt( + fn encrypt_in_place(&self, nonce: &Self::Nonce, aad: &[u8], buffer: &mut [u8]) -> Result { + self.__encrypt_in_place_with_nonce(nonce, aad, buffer, SealToken::new()) + } + + /// Alias for [`encrypt_in_place`](Self::encrypt_in_place). + #[inline] + fn encrypt_in_place_detached( &self, nonce: &Self::Nonce, aad: &[u8], - ciphertext_and_tag: &[u8], - out: &mut [u8], - ) -> Result<(), OpenError> { - let plaintext_len = Self::plaintext_len(ciphertext_and_tag.len())?; - if out.len() != plaintext_len { - return Err(OpenError::buffer()); - } + buffer: &mut [u8], + ) -> Result { + self.encrypt_in_place(nonce, aad, buffer) + } - let (ciphertext, tag_bytes) = ciphertext_and_tag.split_at(plaintext_len); - out.copy_from_slice(ciphertext); - let tag = Self::tag_from_slice(tag_bytes)?; - if let Err(e) = self.decrypt_in_place(nonce, aad, out, &tag) { - super::ct::zeroize(out); - return Err(e); + /// Encrypt `plaintext` into `out` as `ciphertext || tag` with a caller-supplied nonce. + /// + /// # Errors + /// + /// Returns [`SealError`] if `out.len()` does not match `plaintext.len() + + /// TAG_SIZE`, that addition overflows, or the input exceeds the algorithm's + /// supported length bound. + #[inline] + fn encrypt(&self, nonce: &Self::Nonce, aad: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result<(), SealError> { + let expected = Self::ciphertext_len(plaintext.len()).map_err(SealError::from)?; + if out.len() != expected { + return Err(SealError::buffer()); } + + let (ciphertext, tag_out) = out.split_at_mut(plaintext.len()); + ciphertext.copy_from_slice(plaintext); + let tag = match self.encrypt_in_place(nonce, aad, ciphertext) { + Ok(tag) => tag, + Err(err) => { + super::ct::zeroize(out); + return Err(err); + } + }; + tag_out.copy_from_slice(tag.as_ref()); Ok(()) } - /// Allocate plaintext output and decrypt a combined `ciphertext || tag`. + /// Allocate `ciphertext || tag` and encrypt with a caller-supplied nonce. /// /// # Errors /// - /// Returns [`OpenError`] if the input is malformed or authentication fails. - /// Any allocated plaintext buffer is cleared before returning an error. + /// Returns [`SealError`] if length calculation fails or encryption fails. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[inline] - fn decrypt_to_vec(&self, nonce: &Self::Nonce, aad: &[u8], ciphertext_and_tag: &[u8]) -> Result, OpenError> { - let len = Self::plaintext_len(ciphertext_and_tag.len())?; + fn encrypt_to_vec(&self, nonce: &Self::Nonce, aad: &[u8], plaintext: &[u8]) -> Result, SealError> { + let len = Self::ciphertext_len(plaintext.len()).map_err(SealError::from)?; let mut out = vec![0u8; len]; - match self.decrypt(nonce, aad, ciphertext_and_tag, &mut out) { + match self.encrypt(nonce, aad, plaintext, &mut out) { Ok(()) => Ok(out), Err(err) => { super::ct::zeroize(&mut out); @@ -314,3 +334,5 @@ pub trait Aead { } } } + +impl AeadWithNonce for T {} diff --git a/src/traits/mod.rs b/src/traits/mod.rs index e8aa2a21..fcd56cd1 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -36,7 +36,7 @@ feature = "aegis256", feature = "ascon-aead" ))] -mod aead; +pub(crate) mod aead; mod checksum; pub mod ct; mod digest; diff --git a/tests/aead_foundations.rs b/tests/aead_foundations.rs index 74cc3da1..7e583af7 100644 --- a/tests/aead_foundations.rs +++ b/tests/aead_foundations.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "aead")] +use rscrypto::aead::expert::AeadWithNonce; #[cfg(all(feature = "aead", feature = "getrandom"))] use rscrypto::aead::{RandomSealError, SealError}; #[cfg(feature = "aead")] @@ -35,7 +37,13 @@ impl Aead for DirtyRejectingAead { bytes.try_into().map_err(|_| AeadBufferError::new()) } - fn encrypt_in_place(&self, _: &Self::Nonce, _: &[u8], _: &mut [u8]) -> Result { + fn __encrypt_in_place_with_nonce( + &self, + _: &Self::Nonce, + _: &[u8], + _: &mut [u8], + _: rscrypto::aead::__SealToken, + ) -> Result { Err(rscrypto::aead::SealError::buffer()) } diff --git a/tests/aead_wycheproof.rs b/tests/aead_wycheproof.rs index 9bc59a73..16db55de 100644 --- a/tests/aead_wycheproof.rs +++ b/tests/aead_wycheproof.rs @@ -5,7 +5,7 @@ use rscrypto::{ Aes128GcmTag, Aes256Gcm, Aes256GcmKey, Aes256GcmSiv, Aes256GcmSivKey, Aes256GcmSivTag, Aes256GcmTag, ChaCha20Poly1305, ChaCha20Poly1305Key, ChaCha20Poly1305Tag, XChaCha20Poly1305, XChaCha20Poly1305Key, XChaCha20Poly1305Tag, - aead::{Nonce96, Nonce192, Nonce256}, + aead::{Nonce96, Nonce192, Nonce256, expert::AeadWithNonce}, }; use serde_json::Value; diff --git a/tests/aegis256_oracle.rs b/tests/aegis256_oracle.rs index 7902cc46..afbd757a 100644 --- a/tests/aegis256_oracle.rs +++ b/tests/aegis256_oracle.rs @@ -14,7 +14,10 @@ #![cfg(feature = "aead")] use aegis::aegis256::Aegis256 as Oracle; -use rscrypto::{Aegis256, Aegis256Key, Aegis256Tag, aead::Nonce256}; +use rscrypto::{ + Aegis256, Aegis256Key, Aegis256Tag, + aead::{Nonce256, expert::AeadWithNonce}, +}; fn assert_matches_oracle(key_bytes: &[u8; 32], nonce_bytes: &[u8; 32], aad: &[u8], plaintext: &[u8]) { let key = Aegis256Key::from_bytes(*key_bytes); diff --git a/tests/aes128gcm_oracle.rs b/tests/aes128gcm_oracle.rs index 565964bf..e8b5378d 100644 --- a/tests/aes128gcm_oracle.rs +++ b/tests/aes128gcm_oracle.rs @@ -17,7 +17,10 @@ use aes_gcm::{ Aes128Gcm as Oracle, aead::{AeadInOut, KeyInit, array::Array}, }; -use rscrypto::{Aes128Gcm, Aes128GcmKey, Aes128GcmTag, aead::Nonce96}; +use rscrypto::{ + Aes128Gcm, Aes128GcmKey, Aes128GcmTag, + aead::{Nonce96, expert::AeadWithNonce}, +}; fn deterministic_bytes(seed: u8, len: usize) -> Vec { let mut out = Vec::with_capacity(len); diff --git a/tests/aes128gcmsiv_oracle.rs b/tests/aes128gcmsiv_oracle.rs index 04005fa6..c84db9f5 100644 --- a/tests/aes128gcmsiv_oracle.rs +++ b/tests/aes128gcmsiv_oracle.rs @@ -18,7 +18,10 @@ use aes_gcm_siv::{ Aes128GcmSiv as Oracle, KeyInit, aead::{AeadInPlace, generic_array::GenericArray}, }; -use rscrypto::{Aes128GcmSiv, Aes128GcmSivKey, Aes128GcmSivTag, aead::Nonce96}; +use rscrypto::{ + Aes128GcmSiv, Aes128GcmSivKey, Aes128GcmSivTag, + aead::{Nonce96, expert::AeadWithNonce}, +}; fn assert_matches_oracle(key_bytes: &[u8; 16], nonce_bytes: &[u8; 12], aad: &[u8], plaintext: &[u8]) { let key = Aes128GcmSivKey::from_bytes(*key_bytes); diff --git a/tests/aes256gcm_oracle.rs b/tests/aes256gcm_oracle.rs index c6b9080d..dedd58bf 100644 --- a/tests/aes256gcm_oracle.rs +++ b/tests/aes256gcm_oracle.rs @@ -17,7 +17,10 @@ use aes_gcm::{ Aes256Gcm as Oracle, aead::{AeadInOut, KeyInit, array::Array}, }; -use rscrypto::{Aes256Gcm, Aes256GcmKey, Aes256GcmTag, aead::Nonce96}; +use rscrypto::{ + Aes256Gcm, Aes256GcmKey, Aes256GcmTag, + aead::{Nonce96, expert::AeadWithNonce}, +}; fn deterministic_bytes(seed: u8, len: usize) -> Vec { let mut out = Vec::with_capacity(len); diff --git a/tests/aes256gcmsiv_oracle.rs b/tests/aes256gcmsiv_oracle.rs index dae3d44d..cdf3bb03 100644 --- a/tests/aes256gcmsiv_oracle.rs +++ b/tests/aes256gcmsiv_oracle.rs @@ -17,7 +17,10 @@ use aes_gcm_siv::{ Aes256GcmSiv as Oracle, KeyInit, aead::{AeadInPlace, generic_array::GenericArray}, }; -use rscrypto::{Aes256GcmSiv, Aes256GcmSivKey, Aes256GcmSivTag, aead::Nonce96}; +use rscrypto::{ + Aes256GcmSiv, Aes256GcmSivKey, Aes256GcmSivTag, + aead::{Nonce96, expert::AeadWithNonce}, +}; fn assert_matches_oracle(key_bytes: &[u8; 32], nonce_bytes: &[u8; 12], aad: &[u8], plaintext: &[u8]) { let key = Aes256GcmSivKey::from_bytes(*key_bytes); diff --git a/tests/aes_gcm_aarch64_asm_oracle.rs b/tests/aes_gcm_aarch64_asm_oracle.rs index 0102c48f..be3c0c86 100644 --- a/tests/aes_gcm_aarch64_asm_oracle.rs +++ b/tests/aes_gcm_aarch64_asm_oracle.rs @@ -9,7 +9,10 @@ use aes_gcm::{ Aes128Gcm as Aes128Oracle, Aes256Gcm as Aes256Oracle, aead::{AeadInOut, KeyInit, array::Array}, }; -use rscrypto::{Aes128Gcm, Aes128GcmKey, Aes256Gcm, Aes256GcmKey, aead::Nonce96}; +use rscrypto::{ + Aes128Gcm, Aes128GcmKey, Aes256Gcm, Aes256GcmKey, + aead::{Nonce96, expert::AeadWithNonce}, +}; fn deterministic_bytes(seed: u8, len: usize) -> Vec { let mut out = Vec::with_capacity(len); diff --git a/tests/api_consistency.rs b/tests/api_consistency.rs index 16f450c7..28c78d41 100644 --- a/tests/api_consistency.rs +++ b/tests/api_consistency.rs @@ -8,6 +8,8 @@ use rscrypto::EcdsaKeyGenerationError; #[cfg(any(feature = "hmac", feature = "hmac-sha3"))] use rscrypto::Mac; #[cfg(feature = "aead")] +use rscrypto::aead::expert::AeadWithNonce; +#[cfg(feature = "aead")] use rscrypto::{ Aead, Aegis256, Aegis256Key, Aes128Gcm, Aes128GcmKey, Aes128GcmSiv, Aes128GcmSivKey, Aes256Gcm, Aes256GcmKey, Aes256GcmSiv, Aes256GcmSivKey, AsconAead128, AsconAead128Key, ChaCha20Poly1305, ChaCha20Poly1305Key, diff --git a/tests/ascon_aead_oracle.rs b/tests/ascon_aead_oracle.rs index 80f7b6b7..fd3bea7e 100644 --- a/tests/ascon_aead_oracle.rs +++ b/tests/ascon_aead_oracle.rs @@ -21,7 +21,10 @@ use ascon_aead::{ AsconAead128 as Oracle, aead::{AeadInOut, KeyInit, array::Array}, }; -use rscrypto::{AsconAead128, AsconAead128Key, AsconAead128Tag, aead::Nonce128}; +use rscrypto::{ + AsconAead128, AsconAead128Key, AsconAead128Tag, + aead::{Nonce128, expert::AeadWithNonce}, +}; fn assert_matches_oracle(key_bytes: &[u8; 16], nonce_bytes: &[u8; 16], aad: &[u8], plaintext: &[u8]) { let key = AsconAead128Key::from_bytes(*key_bytes); diff --git a/tests/blake2_differential.rs b/tests/blake2_differential.rs index 7dc94633..547a02d3 100644 --- a/tests/blake2_differential.rs +++ b/tests/blake2_differential.rs @@ -7,7 +7,9 @@ use blake2::{ use digest::typenum::{U16, U32, U64}; use hmac::{Mac as _, digest::KeyInit}; use proptest::{prelude::*, test_runner::Config as ProptestConfig}; -use rscrypto::{Blake2b256, Blake2b512, Blake2bParams, Blake2s128, Blake2s256, Blake2sParams, Digest}; +use rscrypto::{ + Blake2b256, Blake2b512, Blake2bKey, Blake2bParams, Blake2s128, Blake2s256, Blake2sKey, Blake2sParams, Digest, +}; type OracleBlake2bMac256 = Blake2bMac; type OracleBlake2bMac512 = Blake2bMac; @@ -39,6 +41,7 @@ proptest! { ) { let (left, right) = split_at_ratio(&data, split); let key = &patterned_input(0x42, key_len); + let typed_key = Blake2bKey::new(key).unwrap(); let tail = patterned_input(0xA5, tail_len); let expected_256 = OracleBlake2b256::digest(&data); @@ -59,12 +62,18 @@ proptest! { let mut oracle_keyed_256 = OracleBlake2bMac256::new_from_slice(key).unwrap(); oracle_keyed_256.update(&data); let expected_keyed_256 = oracle_keyed_256.finalize().into_bytes(); - prop_assert_eq!(&Blake2b256::keyed_digest(key, &data)[..], &expected_keyed_256[..]); + prop_assert_eq!( + &Blake2b256::keyed_digest(typed_key, &data)[..], + &expected_keyed_256[..] + ); let mut oracle_keyed_512 = OracleBlake2bMac512::new_from_slice(key).unwrap(); oracle_keyed_512.update(&data); let expected_keyed_512 = oracle_keyed_512.finalize().into_bytes(); - prop_assert_eq!(&Blake2b512::keyed_digest(key, &data)[..], &expected_keyed_512[..]); + prop_assert_eq!( + &Blake2b512::keyed_digest(typed_key, &data)[..], + &expected_keyed_512[..] + ); let mut reset_256 = Blake2b256::new(); reset_256.update(&data); @@ -90,6 +99,7 @@ proptest! { ) { let (left, right) = split_at_ratio(&data, split); let key = &patterned_input(0x24, key_len); + let typed_key = Blake2sKey::new(key).unwrap(); let tail = patterned_input(0x5A, tail_len); let expected_128 = OracleBlake2s128::digest(&data); @@ -110,12 +120,18 @@ proptest! { let mut oracle_keyed_128 = OracleBlake2sMac128::new_from_slice(key).unwrap(); oracle_keyed_128.update(&data); let expected_keyed_128 = oracle_keyed_128.finalize().into_bytes(); - prop_assert_eq!(&Blake2s128::keyed_digest(key, &data)[..], &expected_keyed_128[..]); + prop_assert_eq!( + &Blake2s128::keyed_digest(typed_key, &data)[..], + &expected_keyed_128[..] + ); let mut oracle_keyed_256 = OracleBlake2sMac256::new_from_slice(key).unwrap(); oracle_keyed_256.update(&data); let expected_keyed_256 = oracle_keyed_256.finalize().into_bytes(); - prop_assert_eq!(&Blake2s256::keyed_digest(key, &data)[..], &expected_keyed_256[..]); + prop_assert_eq!( + &Blake2s256::keyed_digest(typed_key, &data)[..], + &expected_keyed_256[..] + ); let mut reset_128 = Blake2s128::new(); reset_128.update(&data); @@ -142,6 +158,10 @@ proptest! { let key = patterned_input(0x11, key_len); let salt = patterned_input(0x22, salt_len); let personal = patterned_input(0x33, personal_len); + let mut salt_field = [0u8; 16]; + salt_field[..salt.len()].copy_from_slice(&salt); + let mut personal_field = [0u8; 16]; + personal_field[..personal.len()].copy_from_slice(&personal); let key_opt: Option<&[u8]> = if key.is_empty() { None } else { Some(&key) }; @@ -151,19 +171,15 @@ proptest! { oracle_256.update(&data); let expected_256 = oracle_256.finalize().into_bytes(); - let ours_oneshot_256 = Blake2bParams::new() - .key(&key) - .salt(&salt) - .personal(&personal) - .hash_256(&data); + let mut params = Blake2bParams::new().salt(salt_field).personal(personal_field); + if !key.is_empty() { + params = params.key(Blake2bKey::new(&key).unwrap()); + } + let ours_oneshot_256 = params.hash_256(&data); prop_assert_eq!(&ours_oneshot_256[..], &expected_256[..]); // Streaming should match too. - let mut ours_stream_256 = Blake2bParams::new() - .key(&key) - .salt(&salt) - .personal(&personal) - .build_256(); + let mut ours_stream_256 = params.build_256(); ours_stream_256.update(&data); prop_assert_eq!(&ours_stream_256.finalize()[..], &expected_256[..]); @@ -171,11 +187,7 @@ proptest! { oracle_512.update(&data); let expected_512 = oracle_512.finalize().into_bytes(); - let ours_oneshot_512 = Blake2bParams::new() - .key(&key) - .salt(&salt) - .personal(&personal) - .hash_512(&data); + let ours_oneshot_512 = params.hash_512(&data); prop_assert_eq!(&ours_oneshot_512[..], &expected_512[..]); } @@ -189,6 +201,10 @@ proptest! { let key = patterned_input(0x44, key_len); let salt = patterned_input(0x55, salt_len); let personal = patterned_input(0x66, personal_len); + let mut salt_field = [0u8; 8]; + salt_field[..salt.len()].copy_from_slice(&salt); + let mut personal_field = [0u8; 8]; + personal_field[..personal.len()].copy_from_slice(&personal); let key_opt: Option<&[u8]> = if key.is_empty() { None } else { Some(&key) }; @@ -196,18 +212,14 @@ proptest! { oracle_256.update(&data); let expected_256 = oracle_256.finalize().into_bytes(); - let ours_oneshot_256 = Blake2sParams::new() - .key(&key) - .salt(&salt) - .personal(&personal) - .hash_256(&data); + let mut params = Blake2sParams::new().salt(salt_field).personal(personal_field); + if !key.is_empty() { + params = params.key(Blake2sKey::new(&key).unwrap()); + } + let ours_oneshot_256 = params.hash_256(&data); prop_assert_eq!(&ours_oneshot_256[..], &expected_256[..]); - let mut ours_stream_256 = Blake2sParams::new() - .key(&key) - .salt(&salt) - .personal(&personal) - .build_256(); + let mut ours_stream_256 = params.build_256(); ours_stream_256.update(&data); prop_assert_eq!(&ours_stream_256.finalize()[..], &expected_256[..]); @@ -215,11 +227,7 @@ proptest! { oracle_128.update(&data); let expected_128 = oracle_128.finalize().into_bytes(); - let ours_oneshot_128 = Blake2sParams::new() - .key(&key) - .salt(&salt) - .personal(&personal) - .hash_128(&data); + let ours_oneshot_128 = params.hash_128(&data); prop_assert_eq!(&ours_oneshot_128[..], &expected_128[..]); } } diff --git a/tests/blake2_official_vectors.rs b/tests/blake2_official_vectors.rs index 6925ad8e..8dcf140f 100644 --- a/tests/blake2_official_vectors.rs +++ b/tests/blake2_official_vectors.rs @@ -2,7 +2,7 @@ mod support; -use rscrypto::{Blake2b512, Blake2s256, Digest}; +use rscrypto::{Blake2b512, Blake2bKey, Blake2s256, Blake2sKey, Digest}; use support::blobby_compat::Blob3Iterator; fn run_blake2_vectors( @@ -48,14 +48,14 @@ fn blake2s_official_vectors() { if key.is_empty() { Blake2s256::digest(input) } else { - Blake2s256::keyed_digest(key, input) + Blake2s256::keyed_digest(Blake2sKey::new(key).unwrap(), input) } }, |input, key| { let mut hasher = if key.is_empty() { Blake2s256::new() } else { - Blake2s256::new_keyed(key) + Blake2s256::new_keyed(Blake2sKey::new(key).unwrap()) }; for chunk in input.chunks(11) { hasher.update(chunk); @@ -75,14 +75,14 @@ fn blake2b_official_vectors() { if key.is_empty() { Blake2b512::digest(input) } else { - Blake2b512::keyed_digest(key, input) + Blake2b512::keyed_digest(Blake2bKey::new(key).unwrap(), input) } }, |input, key| { let mut hasher = if key.is_empty() { Blake2b512::new() } else { - Blake2b512::new_keyed(key) + Blake2b512::new_keyed(Blake2bKey::new(key).unwrap()) }; for chunk in input.chunks(17) { hasher.update(chunk); diff --git a/tests/chacha20poly1305.rs b/tests/chacha20poly1305.rs index a528b6d9..ec8af3f0 100644 --- a/tests/chacha20poly1305.rs +++ b/tests/chacha20poly1305.rs @@ -4,7 +4,10 @@ use chacha20poly1305::{ ChaCha20Poly1305 as Oracle, KeyInit, aead::{Aead as _, AeadInOut, Payload, array::Array}, }; -use rscrypto::{ChaCha20Poly1305, ChaCha20Poly1305Key, ChaCha20Poly1305Tag, aead::Nonce96}; +use rscrypto::{ + ChaCha20Poly1305, ChaCha20Poly1305Key, ChaCha20Poly1305Tag, + aead::{Nonce96, expert::AeadWithNonce}, +}; mod common; use common::decode_hex_vec as decode_hex; diff --git a/tests/getrandom_smoke.rs b/tests/getrandom_smoke.rs index 36be4185..870c9147 100644 --- a/tests/getrandom_smoke.rs +++ b/tests/getrandom_smoke.rs @@ -1,4 +1,4 @@ -//! Smoke tests for `getrandom`-backed `random()` methods. +//! Smoke tests for `getrandom`-backed fallible constructors. //! //! Validates that the OS entropy source is reachable and that successive calls //! produce distinct output (ruling out an all-zero or constant return). @@ -10,16 +10,16 @@ macro_rules! random_smoke { ($name:ident, $ty:ty) => { #[test] fn $name() { - let a = <$ty>::random(); - let b = <$ty>::random(); + let a = <$ty>::try_random().unwrap(); + let b = <$ty>::try_random().unwrap(); // Must not be all-zero (overwhelmingly unlikely from a CSPRNG). - assert!(a.as_bytes().iter().any(|&b| b != 0), "random() returned all zeros"); + assert!(a.as_bytes().iter().any(|&b| b != 0), "try_random() returned all zeros"); // Two calls must differ (probability of collision: 2^-256 for 32-byte types). assert_ne!( a.as_bytes(), b.as_bytes(), - "two random() calls returned identical output" + "two try_random() calls returned identical output" ); } }; diff --git a/tests/migration_aws_lc_rs.rs b/tests/migration_aws_lc_rs.rs index 24b31fef..6c5d5254 100644 --- a/tests/migration_aws_lc_rs.rs +++ b/tests/migration_aws_lc_rs.rs @@ -24,7 +24,7 @@ use aws_lc_rs::{ use rscrypto::{ Aes256Gcm, Aes256GcmKey, ChaCha20Poly1305, ChaCha20Poly1305Key, Ed25519SecretKey, HkdfSha256, HmacSha256, Pbkdf2Sha256, RsaPkcs1v15Profile, RsaPssProfile, RsaPublicKey, Sha256, X25519PublicKey, X25519SecretKey, - aead::Nonce96, + aead::{Nonce96, expert::AeadWithNonce}, }; const DATA: &[u8] = b"migration equivalence data"; diff --git a/tests/migration_docs.rs b/tests/migration_docs.rs index 26bc2c2f..ca5fd13b 100644 --- a/tests/migration_docs.rs +++ b/tests/migration_docs.rs @@ -31,7 +31,7 @@ fn migration_docs_do_not_delegate_accuracy_to_a_validation_index() { let root = migration_root(); let guide_count = migration_guides(&root).len(); assert_eq!( - guide_count, 35, + guide_count, 36, "migration guide count drifted; update docs/migration/README.md" ); assert!( diff --git a/tests/migration_dryoc.rs b/tests/migration_dryoc.rs index 9b52f0ce..02332061 100644 --- a/tests/migration_dryoc.rs +++ b/tests/migration_dryoc.rs @@ -5,7 +5,7 @@ use dryoc::classic::{ crypto_generichash::crypto_generichash, crypto_sign::{crypto_sign_detached, crypto_sign_seed_keypair, crypto_sign_verify_detached}, }; -use rscrypto::{Blake2b256, Blake2b512, Ed25519SecretKey, X25519SecretKey}; +use rscrypto::{Blake2b256, Blake2b512, Blake2bKey, Ed25519SecretKey, X25519SecretKey}; const DATA: &[u8] = b"dryoc migration equivalence data"; const KEY_32: [u8; 32] = [0x42; 32]; @@ -23,11 +23,17 @@ fn test_dryoc_blake2b_migration_examples_are_byte_equivalent() { let mut dryoc_keyed_b256 = [0u8; 32]; crypto_generichash(&mut dryoc_keyed_b256, DATA, Some(&KEY_32)).unwrap(); - assert_eq!(Blake2b256::keyed_digest(&KEY_32, DATA), dryoc_keyed_b256); + assert_eq!( + Blake2b256::keyed_digest(Blake2bKey::new(&KEY_32).unwrap(), DATA), + dryoc_keyed_b256 + ); let mut dryoc_keyed_b512 = [0u8; 64]; crypto_generichash(&mut dryoc_keyed_b512, DATA, Some(&KEY_64)).unwrap(); - assert_eq!(Blake2b512::keyed_digest(&KEY_64, DATA), dryoc_keyed_b512); + assert_eq!( + Blake2b512::keyed_digest(Blake2bKey::new(&KEY_64).unwrap(), DATA), + dryoc_keyed_b512 + ); } #[test] diff --git a/tests/migration_ring.rs b/tests/migration_ring.rs index f5aef260..a2b51d44 100644 --- a/tests/migration_ring.rs +++ b/tests/migration_ring.rs @@ -14,7 +14,8 @@ use core::num::NonZeroU32; use ring::{aead as ring_aead, digest as ring_digest, hkdf as ring_hkdf, hmac as ring_hmac, pbkdf2 as ring_pbkdf2}; use rscrypto::{ Aes256Gcm, Aes256GcmKey, ChaCha20Poly1305, ChaCha20Poly1305Key, Ed25519SecretKey, HkdfSha256, HmacSha256, - Pbkdf2Sha256, RsaPkcs1v15Profile, RsaPssProfile, RsaPublicKey, Sha256, aead::Nonce96, + Pbkdf2Sha256, RsaPkcs1v15Profile, RsaPssProfile, RsaPublicKey, Sha256, + aead::{Nonce96, expert::AeadWithNonce}, }; const DATA: &[u8] = b"ring migration equivalence data"; diff --git a/tests/platform_override_race.rs b/tests/platform_override_race.rs index b4333e58..37f82b2b 100644 --- a/tests/platform_override_race.rs +++ b/tests/platform_override_race.rs @@ -1,6 +1,6 @@ #![cfg(feature = "std")] -use rscrypto::platform::{self, Detected}; +use rscrypto::platform::{Detected, expert}; #[test] fn concurrent_override_writers_are_serialized() { @@ -8,12 +8,12 @@ fn concurrent_override_writers_are_serialized() { for _ in 0..8 { scope.spawn(|| { for _ in 0..128 { - platform::try_set_override(Some(Detected::portable())).unwrap(); - platform::try_set_override(None).unwrap(); + expert::try_set_override(Some(Detected::portable())).unwrap(); + expert::try_set_override(None).unwrap(); } }); } }); - platform::try_set_override(None).unwrap(); + expert::try_set_override(None).unwrap(); } diff --git a/tests/platform_override_validation.rs b/tests/platform_override_validation.rs index 38d0b013..c4f82ab0 100644 --- a/tests/platform_override_validation.rs +++ b/tests/platform_override_validation.rs @@ -1,6 +1,9 @@ #![cfg(feature = "std")] -use rscrypto::platform::{self, Arch, Caps, Detected, OverrideError}; +use rscrypto::platform::{ + self, Arch, Caps, Detected, + expert::{self, OverrideError}, +}; #[test] fn safe_override_rejects_impossible_caps_and_still_allows_portable() { @@ -10,27 +13,27 @@ fn safe_override_rejects_impossible_caps_and_still_allows_portable() { }; assert_eq!( - platform::try_set_override(Some(invalid)), + expert::try_set_override(Some(invalid)), Err(OverrideError::InvalidCapabilities) ); let portable = Detected::portable(); - platform::try_set_override(Some(portable)).unwrap(); - assert!(platform::has_override()); + expert::try_set_override(Some(portable)).unwrap(); + assert!(expert::has_override()); assert_eq!(platform::get(), portable); #[cfg(not(miri))] { - assert_eq!(platform::try_set_override(None), Err(OverrideError::AlreadyInitialized)); + assert_eq!(expert::try_set_override(None), Err(OverrideError::AlreadyInitialized)); assert_eq!( - platform::try_set_override(Some(portable)), + expert::try_set_override(Some(portable)), Err(OverrideError::AlreadyInitialized) ); } #[cfg(miri)] { - platform::try_set_override(None).unwrap(); - assert!(!platform::has_override()); + expert::try_set_override(None).unwrap(); + assert!(!expert::has_override()); } } diff --git a/tests/root_surface.rs b/tests/root_surface.rs index aba946e2..112f09ae 100644 --- a/tests/root_surface.rs +++ b/tests/root_surface.rs @@ -4,6 +4,8 @@ use rscrypto::Aead; #[cfg(any(feature = "hmac", feature = "hmac-sha3", feature = "kmac"))] use rscrypto::Mac; +#[cfg(feature = "aead")] +use rscrypto::aead::expert::AeadWithNonce; #[cfg(all(feature = "aead", feature = "diag"))] use rscrypto::aead::introspect::{ DispatchInfo as AeadDispatchInfo, aegis256_backend, aes256gcm_backend, aes256gcmsiv_backend, ascon_aead128_backend, @@ -142,49 +144,7 @@ fn root_surface_aead_exports_compile() { } fn assert_aead_trait() {} - - #[derive(Clone)] - struct Marker; - - impl Aead for Marker { - const KEY_SIZE: usize = 32; - const NONCE_SIZE: usize = Nonce96::LENGTH; - const TAG_SIZE: usize = 16; - - type Key = [u8; 32]; - type Nonce = Nonce96; - type Tag = [u8; 16]; - - fn new(_key: &Self::Key) -> Self { - Self - } - - fn tag_from_slice(bytes: &[u8]) -> Result { - if bytes.len() != Self::TAG_SIZE { - return Err(AeadBufferError::new()); - } - - let mut tag = [0u8; Self::TAG_SIZE]; - tag.copy_from_slice(bytes); - Ok(tag) - } - - fn encrypt_in_place(&self, _nonce: &Self::Nonce, _aad: &[u8], _buffer: &mut [u8]) -> Result { - Ok([0u8; Self::TAG_SIZE]) - } - - fn decrypt_in_place( - &self, - _nonce: &Self::Nonce, - _aad: &[u8], - _buffer: &mut [u8], - _tag: &Self::Tag, - ) -> Result<(), OpenError> { - Ok(()) - } - } - - assert_aead_trait::(); + assert_aead_trait::(); let key = XChaCha20Poly1305Key::from_bytes([0x44; XChaCha20Poly1305::KEY_SIZE]); let cipher = XChaCha20Poly1305::new(&key); diff --git a/tests/secret_redaction.rs b/tests/secret_redaction.rs index 6b154ac8..b534070a 100644 --- a/tests/secret_redaction.rs +++ b/tests/secret_redaction.rs @@ -136,7 +136,7 @@ fn keyed_hash_debug_snapshots_are_redacted() { #[cfg(feature = "blake2s")] { - let params = rscrypto::Blake2sParams::new().key(&KEY); + let params = rscrypto::Blake2sParams::new().key(rscrypto::Blake2sKey::new(&KEY).unwrap()); assert_debug_snapshot( ¶ms, "Blake2sParams { key_len: 32, salt: [0, 0, 0, 0, 0, 0, 0, 0], personal: [0, 0, 0, 0, 0, 0, 0, 0] }", @@ -147,7 +147,7 @@ fn keyed_hash_debug_snapshots_are_redacted() { #[cfg(feature = "blake2b")] { - let params = rscrypto::Blake2bParams::new().key(&KEY); + let params = rscrypto::Blake2bParams::new().key(rscrypto::Blake2bKey::new(&KEY).unwrap()); assert_debug_snapshot( ¶ms, "Blake2bParams { key_len: 32, salt: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], personal: [0, 0, 0, 0, 0, \ @@ -155,7 +155,7 @@ fn keyed_hash_debug_snapshots_are_redacted() { ); assert_debug_snapshot(¶ms.build_256(), "Blake2b256 { .. }"); assert_debug_snapshot(¶ms.build_512(), "Blake2b512 { .. }"); - assert_debug_snapshot(¶ms.build(32), "Blake2b { output_len: 32, .. }"); + assert_debug_snapshot(¶ms.build(32).unwrap(), "Blake2b { output_len: 32, .. }"); } #[cfg(feature = "blake3")] diff --git a/tests/xchacha20poly1305.rs b/tests/xchacha20poly1305.rs index acbc5b13..816d16f7 100644 --- a/tests/xchacha20poly1305.rs +++ b/tests/xchacha20poly1305.rs @@ -4,7 +4,10 @@ use chacha20poly1305::{ KeyInit, XChaCha20Poly1305 as Oracle, aead::{Aead as _, AeadInOut, Payload, array::Array}, }; -use rscrypto::{XChaCha20Poly1305, XChaCha20Poly1305Key, XChaCha20Poly1305Tag, aead::Nonce192}; +use rscrypto::{ + XChaCha20Poly1305, XChaCha20Poly1305Key, XChaCha20Poly1305Tag, + aead::{Nonce192, expert::AeadWithNonce}, +}; mod common; use common::decode_hex_vec as decode_hex; diff --git a/tools/ct-binsec-harness/src/main.rs b/tools/ct-binsec-harness/src/main.rs index 395fac22..1a53594b 100644 --- a/tools/ct-binsec-harness/src/main.rs +++ b/tools/ct-binsec-harness/src/main.rs @@ -1,5 +1,6 @@ use core::ptr; +use rscrypto::aead::expert::AeadWithNonce; use rscrypto::{ Aegis256, Aegis256Key, Aes128Gcm, Aes128GcmKey, Aes128GcmSiv, Aes128GcmSivKey, Aes256Gcm, Aes256GcmKey, Aes256GcmSiv, Aes256GcmSivKey, AsconAead128, AsconAead128Key, Blake3KeyedHash, ChaCha20Poly1305, @@ -313,7 +314,7 @@ pub extern "C" fn ct_binsec_hmac_sha256_verify() -> ! { let key = unsafe { array_from_global(ptr::addr_of!(CT_BINSEC_KEY_32)) }; // SAFETY: These pointers reference fixed harness globals with static storage. let expected = unsafe { array_from_global(ptr::addr_of!(CT_BINSEC_TAG_32)) }; - let ok = rscrypto::diag_hmac_sha256_verify_portable(&key, &expected); + let ok = rscrypto::auth::diag_hmac_sha256_verify_portable(&key, &expected); ct_binsec_done(u8::from(ok.declassify())) } @@ -324,7 +325,7 @@ pub extern "C" fn ct_binsec_hmac_sha384_verify() -> ! { let key = unsafe { array_from_global(ptr::addr_of!(CT_BINSEC_KEY_48)) }; // SAFETY: These pointers reference fixed harness globals with static storage. let expected = unsafe { array_from_global(ptr::addr_of!(CT_BINSEC_TAG_48)) }; - let ok = rscrypto::diag_hmac_sha384_verify_portable(&key, &expected); + let ok = rscrypto::auth::diag_hmac_sha384_verify_portable(&key, &expected); ct_binsec_done(u8::from(ok.declassify())) } @@ -335,7 +336,7 @@ pub extern "C" fn ct_binsec_hmac_sha512_verify() -> ! { let key = unsafe { array_from_global(ptr::addr_of!(CT_BINSEC_KEY_64)) }; // SAFETY: These pointers reference fixed harness globals with static storage. let expected = unsafe { array_from_global(ptr::addr_of!(CT_BINSEC_TAG_64)) }; - let ok = rscrypto::diag_hmac_sha512_verify_portable(&key, &expected); + let ok = rscrypto::auth::diag_hmac_sha512_verify_portable(&key, &expected); ct_binsec_done(u8::from(ok.declassify())) } @@ -368,7 +369,7 @@ pub extern "C" fn ct_binsec_blake3_verify_keyed() -> ! { pub extern "C" fn ct_binsec_hkdf_sha256_derive() -> ! { // SAFETY: These pointers reference fixed harness globals with static storage. let ikm = unsafe { array_from_global(ptr::addr_of!(CT_BINSEC_IKM_32)) }; - let okm = rscrypto::diag_hkdf_sha256_derive_portable(&ikm); + let okm = rscrypto::auth::diag_hkdf_sha256_derive_portable(&ikm); let mut acc = 0u8; for byte in okm { acc ^= byte; @@ -381,7 +382,7 @@ pub extern "C" fn ct_binsec_hkdf_sha256_derive() -> ! { pub extern "C" fn ct_binsec_hkdf_sha384_derive() -> ! { // SAFETY: These pointers reference fixed harness globals with static storage. let ikm = unsafe { array_from_global(ptr::addr_of!(CT_BINSEC_IKM_48)) }; - let okm = rscrypto::diag_hkdf_sha384_derive_portable(&ikm); + let okm = rscrypto::auth::diag_hkdf_sha384_derive_portable(&ikm); let mut acc = 0u8; for byte in okm { acc ^= byte; @@ -396,7 +397,7 @@ pub extern "C" fn ct_binsec_pbkdf2_sha256_verify() -> ! { let password = unsafe { array_from_global(ptr::addr_of!(CT_BINSEC_PASSWORD_32)) }; // SAFETY: These pointers reference fixed harness globals with static storage. let expected = unsafe { array_from_global(ptr::addr_of!(CT_BINSEC_TAG_32)) }; - let ok = rscrypto::diag_pbkdf2_sha256_verify_portable(&password, &expected); + let ok = rscrypto::auth::diag_pbkdf2_sha256_verify_portable(&password, &expected); ct_binsec_done(u8::from(ok)) } @@ -407,7 +408,7 @@ pub extern "C" fn ct_binsec_pbkdf2_sha512_verify() -> ! { let password = unsafe { array_from_global(ptr::addr_of!(CT_BINSEC_PASSWORD_64)) }; // SAFETY: These pointers reference fixed harness globals with static storage. let expected = unsafe { array_from_global(ptr::addr_of!(CT_BINSEC_TAG_64)) }; - let ok = rscrypto::diag_pbkdf2_sha512_verify_portable(&password, &expected); + let ok = rscrypto::auth::diag_pbkdf2_sha512_verify_portable(&password, &expected); ct_binsec_done(u8::from(ok)) } @@ -705,7 +706,7 @@ pub extern "C" fn ct_binsec_curve25519_conditional_swap() -> ! { // SAFETY: This pointer references a fixed harness global with static storage. let swap = unsafe { ptr::read_volatile(ptr::addr_of!(CT_BINSEC_CURVE25519_SWAP)) }; - rscrypto::diag_curve25519_conditional_swap(&mut lhs, &mut rhs, swap); + rscrypto::auth::diag_curve25519_conditional_swap(&mut lhs, &mut rhs, swap); let mut acc = 0u64; for limb in lhs.into_iter().chain(rhs) { @@ -719,7 +720,7 @@ pub extern "C" fn ct_binsec_curve25519_conditional_swap() -> ! { pub extern "C" fn ct_binsec_ed25519_select_basepoint_cached() -> ! { // SAFETY: This pointer references a fixed harness global with static storage. let digit = unsafe { ptr::read_volatile(ptr::addr_of!(CT_BINSEC_ED25519_DIGIT)) }; - let limbs = rscrypto::diag_ed25519_select_basepoint_cached_limb_digest(digit); + let limbs = rscrypto::auth::diag_ed25519_select_basepoint_cached_limb_digest(digit); let mut acc = 0u64; for limb in limbs { @@ -733,7 +734,7 @@ pub extern "C" fn ct_binsec_ed25519_select_basepoint_cached() -> ! { pub extern "C" fn ct_binsec_ecdsa_p256_select_signing_generator_affine() -> ! { // SAFETY: This pointer references a fixed harness global with static storage. let digit = unsafe { ptr::read_volatile(ptr::addr_of!(CT_BINSEC_ECDSA_DIGIT)) }; - let limbs = rscrypto::diag_ecdsa_p256_select_signing_generator_affine_limb_digest(digit); + let limbs = rscrypto::auth::diag_ecdsa_p256_select_signing_generator_affine_limb_digest(digit); let mut acc = 0u64; for limb in limbs { @@ -747,7 +748,7 @@ pub extern "C" fn ct_binsec_ecdsa_p256_select_signing_generator_affine() -> ! { pub extern "C" fn ct_binsec_ecdsa_p384_select_signing_generator_affine() -> ! { // SAFETY: This pointer references a fixed harness global with static storage. let digit = unsafe { ptr::read_volatile(ptr::addr_of!(CT_BINSEC_ECDSA_DIGIT)) }; - let limbs = rscrypto::diag_ecdsa_p384_select_signing_generator_affine_limb_digest(digit); + let limbs = rscrypto::auth::diag_ecdsa_p384_select_signing_generator_affine_limb_digest(digit); let mut acc = 0u64; for limb in limbs { @@ -763,7 +764,7 @@ pub extern "C" fn ct_binsec_rsa_private_select_window_power_4() -> ! { let table = unsafe { limbs_from_global(ptr::addr_of!(CT_BINSEC_RSA_WINDOW_TABLE)) }; // SAFETY: This pointer references a fixed harness global with static storage. let window = unsafe { ptr::read_volatile(ptr::addr_of!(CT_BINSEC_RSA_WINDOW)) }; - let limbs = rscrypto::diag_rsa_private_select_window_power_4(&table, window); + let limbs = rscrypto::auth::diag_rsa_private_select_window_power_4(&table, window); let mut acc = 0u64; for limb in limbs { @@ -794,7 +795,7 @@ pub unsafe extern "C" fn ct_binsec_ed25519_select_basepoint_cached_avx2() -> ! { let digit = unsafe { ptr::read_volatile(ptr::addr_of!(CT_BINSEC_ED25519_DIGIT)) }; // SAFETY: This entrypoint is compiled with AVX2 enabled and is selected only // for the x86_64 AVX2 BINSEC kernel. - let limbs = unsafe { rscrypto::diag_ed25519_select_basepoint_cached_avx2_limb_digest(digit) }; + let limbs = unsafe { rscrypto::auth::diag_ed25519_select_basepoint_cached_avx2_limb_digest(digit) }; let mut acc = 0u64; for limb in limbs { @@ -812,7 +813,7 @@ pub unsafe extern "C" fn ct_binsec_ed25519_select_basepoint_cached_ifma() -> ! { let digit = unsafe { ptr::read_volatile(ptr::addr_of!(CT_BINSEC_ED25519_DIGIT)) }; // SAFETY: This entrypoint is compiled with AVX2, AVX-512 IFMA, and AVX-512 VL // enabled and is selected only for the x86_64 IFMA BINSEC kernel. - let limbs = unsafe { rscrypto::diag_ed25519_select_basepoint_cached_ifma_limb_digest(digit) }; + let limbs = unsafe { rscrypto::auth::diag_ed25519_select_basepoint_cached_ifma_limb_digest(digit) }; let mut acc = 0u64; for limb in limbs { diff --git a/tools/ct-dudect/src/main.rs b/tools/ct-dudect/src/main.rs index 996ca57c..6170f4e3 100644 --- a/tools/ct-dudect/src/main.rs +++ b/tools/ct-dudect/src/main.rs @@ -1,9 +1,10 @@ use dudect_bencher::{BenchRng, Class, CtRunner, ctbench_main_with_seeds, rand::RngExt}; +use rscrypto::aead::expert::AeadWithNonce; use rscrypto::{ Aegis256, Aegis256Key, Aes128Gcm, Aes128GcmKey, Aes128GcmSiv, Aes128GcmSivKey, Aes256Gcm, Aes256GcmKey, Aes256GcmSiv, Aes256GcmSivKey, Argon2Params, Argon2i, AsconAead128, AsconAead128Key, Blake2b256, Blake2b512, - Blake2s128, Blake2s256, Blake3, Blake3KeyedHash, ChaCha20Poly1305, ChaCha20Poly1305Key, EcdsaP256SecretKey, - EcdsaP384SecretKey, Ed25519Keypair, Ed25519SecretKey, HkdfSha256, + Blake2bKey, Blake2s128, Blake2s256, Blake2sKey, Blake3, Blake3KeyedHash, ChaCha20Poly1305, ChaCha20Poly1305Key, + EcdsaP256SecretKey, EcdsaP384SecretKey, Ed25519Keypair, Ed25519SecretKey, HkdfSha256, HkdfSha384, HmacSha256, HmacSha256Tag, HmacSha384, HmacSha384Tag, HmacSha512, HmacSha512Tag, Kmac256, MlKem512, MlKem512Ciphertext, MlKem512DecapsulationKey, MlKem768, MlKem768Ciphertext, MlKem768DecapsulationKey, MlKem1024, MlKem1024Ciphertext, MlKem1024DecapsulationKey, MlKemError, Pbkdf2Sha256, Pbkdf2Sha512, RsaOaepProfile, @@ -16,21 +17,22 @@ use rscrypto::{ diag_aes256gcm_ghash, diag_aes256gcm_tag_aes, diag_aes256gcmsiv_ctr32, diag_aes256gcmsiv_derive_keys, diag_aes256gcmsiv_raw_tag_aes, }, - auth::diag_rsa_private_component_validation_32, - diag_ecdsa_p256_basepoint_blinded_limb_digest, diag_ecdsa_p256_nonce_reduce_limb_digest, - diag_ecdsa_p256_nonce_inverse_limb_digest, diag_ecdsa_p256_order_mul_blinded_fixed_r_limb_digest, - diag_ecdsa_p256_order_mul_fixed_r_limb_digest, diag_ecdsa_p256_reduce_wide_order_limb_digest, - diag_ecdsa_p256_scalar_finish_limb_digest, diag_ecdsa_p256_final_multiply_limb_digest, - diag_ecdsa_p384_basepoint_blinded_limb_digest, - diag_ecdsa_p384_nonce_reduce_limb_digest, diag_ecdsa_p384_nonce_inverse_limb_digest, - diag_ecdsa_p384_order_mul_fixed_r_limb_digest, diag_ecdsa_p384_reduce_wide_order_limb_digest, - diag_ecdsa_p384_scalar_finish_limb_digest, diag_ecdsa_p384_final_multiply_limb_digest, - diag_mlkem512_keygen_secret_noise_digest, diag_mlkem768_keygen_secret_noise_digest, - diag_mlkem1024_keygen_secret_noise_digest, diag_mlkem1024_multiply_ntts_accumulate_input_digest, - diag_mlkem_from_montgomery_product_domain_input_digest, diag_mlkem_inverse_ntt_montgomery_product_input_digest, - diag_mlkem_multiply_ntts_add_assign_input_digest, diag_mlkem_ntt_input_digest, - diag_mlkem_to_montgomery_product_domain_input_digest, diag_rsa_import_pkcs8_private_key_der_stage, - diag_rsa_validate_pkcs8_private_key_der, diag_rsa_validate_pkcs8_private_key_der_stage, + auth::{ + diag_ecdsa_p256_basepoint_blinded_limb_digest, diag_ecdsa_p256_final_multiply_limb_digest, + diag_ecdsa_p256_nonce_inverse_limb_digest, diag_ecdsa_p256_nonce_reduce_limb_digest, + diag_ecdsa_p256_order_mul_blinded_fixed_r_limb_digest, diag_ecdsa_p256_order_mul_fixed_r_limb_digest, + diag_ecdsa_p256_reduce_wide_order_limb_digest, diag_ecdsa_p256_scalar_finish_limb_digest, + diag_ecdsa_p384_basepoint_blinded_limb_digest, diag_ecdsa_p384_final_multiply_limb_digest, + diag_ecdsa_p384_nonce_inverse_limb_digest, diag_ecdsa_p384_nonce_reduce_limb_digest, + diag_ecdsa_p384_order_mul_fixed_r_limb_digest, diag_ecdsa_p384_reduce_wide_order_limb_digest, + diag_ecdsa_p384_scalar_finish_limb_digest, diag_mlkem_from_montgomery_product_domain_input_digest, + diag_mlkem_inverse_ntt_montgomery_product_input_digest, diag_mlkem_multiply_ntts_add_assign_input_digest, + diag_mlkem_ntt_input_digest, diag_mlkem_to_montgomery_product_domain_input_digest, + diag_mlkem512_keygen_secret_noise_digest, diag_mlkem768_keygen_secret_noise_digest, + diag_mlkem1024_keygen_secret_noise_digest, diag_mlkem1024_multiply_ntts_accumulate_input_digest, + diag_rsa_import_pkcs8_private_key_der_stage, diag_rsa_private_component_validation_32, + diag_rsa_validate_pkcs8_private_key_der, diag_rsa_validate_pkcs8_private_key_der_stage, + }, traits::Kem as _, RsaEncryptionError, RsaPublicKeyPolicy, }; @@ -1899,7 +1901,7 @@ aead_fixed_vs_random_key_seal!( ); macro_rules! blake2_keyed_fixed_vs_random { - ($name:ident, $ty:ty) => { + ($name:ident, $ty:ty, $key_ty:ty) => { fn $name(runner: &mut CtRunner, rng: &mut BenchRng) { let mut inputs = Vec::with_capacity(samples()); for _ in 0..samples() { @@ -1913,16 +1915,17 @@ macro_rules! blake2_keyed_fixed_vs_random { } for (class, key) in inputs { - runner.run_one(class, || <$ty>::keyed_digest(&key, MESSAGE)[0]); + let key = <$key_ty>::new(&key).unwrap(); + runner.run_one(class, || <$ty>::keyed_digest(key, MESSAGE)[0]); } } }; } -blake2_keyed_fixed_vs_random!(blake2b256_keyed_fixed_vs_random_key, Blake2b256); -blake2_keyed_fixed_vs_random!(blake2b512_keyed_fixed_vs_random_key, Blake2b512); -blake2_keyed_fixed_vs_random!(blake2s128_keyed_fixed_vs_random_key, Blake2s128); -blake2_keyed_fixed_vs_random!(blake2s256_keyed_fixed_vs_random_key, Blake2s256); +blake2_keyed_fixed_vs_random!(blake2b256_keyed_fixed_vs_random_key, Blake2b256, Blake2bKey); +blake2_keyed_fixed_vs_random!(blake2b512_keyed_fixed_vs_random_key, Blake2b512, Blake2bKey); +blake2_keyed_fixed_vs_random!(blake2s128_keyed_fixed_vs_random_key, Blake2s128, Blake2sKey); +blake2_keyed_fixed_vs_random!(blake2s256_keyed_fixed_vs_random_key, Blake2s256, Blake2sKey); fn blake3_keyed_fixed_vs_random_key(runner: &mut CtRunner, rng: &mut BenchRng) { let mut inputs = Vec::with_capacity(samples()); diff --git a/tools/ct-harness/src/lib.rs b/tools/ct-harness/src/lib.rs index 9efe7b7f..c33df637 100644 --- a/tools/ct-harness/src/lib.rs +++ b/tools/ct-harness/src/lib.rs @@ -14,9 +14,10 @@ use std::format; use rscrypto::{ Aegis256, Aegis256Key, Aes128Gcm, Aes128GcmKey, Aes128GcmSiv, Aes128GcmSivKey, Aes256Gcm, Aes256GcmKey, Aes256GcmSiv, Aes256GcmSivKey, Argon2Params, Argon2d, Argon2i, Argon2id, AsconAead128, AsconAead128Key, Blake2b256, Blake2b512, - Blake2s128, Blake2s256, Blake3, Blake3KeyedHash, ChaCha20Poly1305, ChaCha20Poly1305Key, Crc32, EcdsaP256SecretKey, - EcdsaP384SecretKey, Ed25519PublicKey, Ed25519SecretKey, Ed25519Signature, HkdfSha256, HkdfSha384, HmacSha3_224Tag, - HmacSha256, HmacSha256Tag, HmacSha384, HmacSha384Tag, HmacSha512, HmacSha512Tag, Kmac256, MlKem512, + Blake2bKey, Blake2s128, Blake2s256, Blake2sKey, Blake3, Blake3KeyedHash, ChaCha20Poly1305, ChaCha20Poly1305Key, + Crc32, EcdsaP256SecretKey, EcdsaP384SecretKey, Ed25519PublicKey, Ed25519SecretKey, Ed25519Signature, HkdfSha256, + HkdfSha384, HmacSha3_224Tag, HmacSha256, HmacSha256Tag, HmacSha384, HmacSha384Tag, HmacSha512, HmacSha512Tag, + Kmac256, MlKem512, MlKem512Ciphertext, MlKem512DecapsulationKey, MlKem512EncapsulationKey, MlKem768, MlKem768Ciphertext, MlKem768DecapsulationKey, MlKem768EncapsulationKey, MlKem1024, MlKem1024Ciphertext, MlKem1024DecapsulationKey, MlKem1024EncapsulationKey, MlKemError, Pbkdf2Sha256, Pbkdf2Sha512, RsaOaepProfile, RsaPkcs1v15Profile, RsaPrivateKey, @@ -1434,7 +1435,7 @@ pub extern "C" fn ct_entry_rsa_private_key_pkcs8_roundtrip( } macro_rules! blake2_keyed_entry { - ($name:ident, $ty:ty, $out_len:literal) => { + ($name:ident, $ty:ty, $key_ty:ty, $out_len:literal) => { #[unsafe(no_mangle)] pub extern "C" fn $name(key: *const u8, key_len: usize, data: *const u8, data_len: usize, out: *mut u8) -> u8 { // SAFETY: FFI input pointers are validated by slice helpers. @@ -1446,6 +1447,9 @@ macro_rules! blake2_keyed_entry { return STATUS_ERR; }; + let Ok(key) = <$key_ty>::new(key) else { + return STATUS_ERR; + }; let digest = <$ty>::keyed_digest(key, data); // SAFETY: The output pointer must reference exactly `$out_len` writable bytes. if unsafe { write_array::<$out_len>(out, &digest) } { @@ -1457,10 +1461,10 @@ macro_rules! blake2_keyed_entry { }; } -blake2_keyed_entry!(ct_entry_blake2b256_keyed_digest, Blake2b256, 32); -blake2_keyed_entry!(ct_entry_blake2b512_keyed_digest, Blake2b512, 64); -blake2_keyed_entry!(ct_entry_blake2s128_keyed_digest, Blake2s128, 16); -blake2_keyed_entry!(ct_entry_blake2s256_keyed_digest, Blake2s256, 32); +blake2_keyed_entry!(ct_entry_blake2b256_keyed_digest, Blake2b256, Blake2bKey, 32); +blake2_keyed_entry!(ct_entry_blake2b512_keyed_digest, Blake2b512, Blake2bKey, 64); +blake2_keyed_entry!(ct_entry_blake2s128_keyed_digest, Blake2s128, Blake2sKey, 16); +blake2_keyed_entry!(ct_entry_blake2s256_keyed_digest, Blake2s256, Blake2sKey, 32); #[unsafe(no_mangle)] pub extern "C" fn ct_entry_blake3_keyed_digest(key: *const u8, data: *const u8, data_len: usize, out: *mut u8) -> u8 {