Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/add-header-protection-masks-a3f4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rscrypto" = "minor"
---

Add narrow AES-128, AES-256, and ChaCha20 header-protection generators with allocation-free mask operations and distinct non-exportable key types.
7 changes: 5 additions & 2 deletions .config/benchmark-matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@
"aes-128-gcm": { "crate": "aead", "bench": "aead", "filter": "^aes-128-gcm/" },
"aegis-256": { "crate": "aead", "bench": "aead", "filter": "aegis-256" },
"ascon-aead128": { "crate": "aead", "bench": "aead", "filter": "ascon-aead128" },
"header-protection": { "crate": "aead", "bench": "aead", "filter": "header-protection" },
"aead-diag": { "crate": "aead", "bench": "aead_diag", "filter": "chacha20-poly1305/encrypt" }
},
"selectors": {
Expand Down Expand Up @@ -272,7 +273,8 @@
"aes-256-gcm",
"aes-128-gcm",
"aegis-256",
"ascon-aead128"
"ascon-aead128",
"header-protection"
],
"auth": [
"hmac-sha256",
Expand Down Expand Up @@ -305,7 +307,8 @@
"aes-256-gcm",
"aes-128-gcm",
"aegis-256",
"ascon-aead128"
"ascon-aead128",
"header-protection"
],
"aeaddiag": ["aead-diag"],
"chacha20poly1305diag": ["aead-diag"],
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ gungraun = "=0.19.4"
proptest = "^1.11.0"

# Oracles
aes = "0.9.2"
cshake = { version = "0.2.1", default-features = false }
crc = "3.4.0"
crc-fast = { version = "1.10.0", default-features = false, features = ["std"] }
Expand Down Expand Up @@ -263,6 +264,7 @@ rustcrypto-ml-kem = { package = "ml-kem", version = "0.3.2", default-features =
hmac = "0.13.0"
hkdf = "0.13.0"
chacha20poly1305 = "0.11.0"
chacha20 = "0.10.1"
aes-gcm = "0.11.0"
aes-gcm-siv = "0.12.0"
aegis = "0.9.15"
Expand Down
7 changes: 4 additions & 3 deletions THREAT_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Review the `ct_intended` candidate core before the rest of the repository:
5. ML-KEM secret-noise key generation, encapsulation coins, decapsulation
secret-key material, and implicit rejection.
6. AEAD authentication and failed-open cleanup.
7. MAC/tag verification, fixed-size owner comparison/declassification, and selected
7. Header-protection mask generation.
8. MAC/tag verification, fixed-size owner comparison/declassification, and selected
password-verification comparisons.

This order prioritizes secret-dependent computation; it does not remove public
Expand Down Expand Up @@ -72,7 +73,7 @@ claims remain limited to the release-evidenced configurations.
## Assets

1. Long-term secrets: private keys, passwords, master keys.
2. Session secrets: X25519 and ML-KEM shared secrets, AEAD keys, signing
2. Session secrets: X25519 and ML-KEM shared secrets, AEAD and header-protection keys, signing
nonces, blinding factors.
3. Intermediate secret state: key schedules, scalars, limbs, DRBG state,
sampler buffers.
Expand Down Expand Up @@ -115,7 +116,7 @@ Ordered by exposure to untrusted input:
| ----------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| Parsers | RSA DER/SPKI/PKCS#8 import, ECDSA DER signatures and SEC1 points, ML-KEM key and ciphertext parsing, PHC strings, hex | Memory safety, panics, accepting what should be rejected |
| Verification oracles | MAC `verify_tag`, AEAD open, signature `verify`, ML-KEM implicit rejection | Timing or error detail beyond the single failure bit |
| Secret-bearing compute | Sign, decrypt, decapsulate, derive; the release-evidenced subset of `ct.toml` | Timing leakage, incorrect arithmetic |
| Secret-bearing compute | Sign, decrypt, decapsulate, derive, generate header masks; the release-evidenced subset of `ct.toml` | Timing leakage, incorrect arithmetic |
| `unsafe` low-level code | SIMD/assembly kernels, raw buffer helpers, zeroization, and dispatch | Undefined behavior, divergence from the portable authority |
| Dispatch | `src/platform`, `src/backend` | Selecting a kernel the CPU cannot run, or one that produces wrong output |
| Compatibility operations | `hashes::legacy::WebSocketAcceptDigest::compute` | Capability expansion or treating broken SHA-1 collision resistance as authentication |
Expand Down
94 changes: 94 additions & 0 deletions benches/aead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1374,6 +1374,99 @@ fn ascon_aead128_decrypt(c: &mut Criterion) {
g.finish();
}

// Fixed-size header protection

fn header_protection(c: &mut Criterion) {
use aes::cipher::{Array, BlockCipherEncrypt as _, KeyInit as _};
use chacha20::cipher::{KeyIvInit as _, StreamCipherCore as _};
use rscrypto::aead::expert::header_protection::{
Aes128HeaderProtection, Aes128HeaderProtectionKey, Aes256HeaderProtection, Aes256HeaderProtectionKey,
ChaCha20HeaderProtection, ChaCha20HeaderProtectionKey,
};

let mut construction = c.benchmark_group("header-protection/construct");
construction.bench_function("rscrypto/aes128", |b| {
b.iter(|| {
let key = Aes128HeaderProtectionKey::from_bytes(black_box(KEY_16));
black_box(Aes128HeaderProtection::new(&key))
})
});
construction.bench_function("rustcrypto/aes128", |b| {
b.iter(|| aes::Aes128::new(black_box(&Array::from(KEY_16))))
});
construction.bench_function("rscrypto/aes256", |b| {
b.iter(|| {
let key = Aes256HeaderProtectionKey::from_bytes(black_box(KEY_32));
black_box(Aes256HeaderProtection::new(&key))
})
});
construction.bench_function("rustcrypto/aes256", |b| {
b.iter(|| aes::Aes256::new(black_box(&Array::from(KEY_32))))
});
construction.bench_function("rscrypto/chacha20", |b| {
b.iter(|| {
let key = ChaCha20HeaderProtectionKey::from_bytes(black_box(KEY_32));
black_box(ChaCha20HeaderProtection::new(&key))
})
});
construction.bench_function("rustcrypto/chacha20", |b| {
b.iter(|| {
chacha20::ChaChaCore::<chacha20::R20, chacha20::variants::Ietf>::new(
black_box(&KEY_32).into(),
black_box(&NONCE_12).into(),
)
})
});
construction.finish();

let sample = [0x07; 16];
let aes128_rs = Aes128HeaderProtection::new(&Aes128HeaderProtectionKey::from_bytes(KEY_16));
let aes256_rs = Aes256HeaderProtection::new(&Aes256HeaderProtectionKey::from_bytes(KEY_32));
let chacha_rs = ChaCha20HeaderProtection::new(&ChaCha20HeaderProtectionKey::from_bytes(KEY_32));
let aes128_rc = aes::Aes128::new(&Array::from(KEY_16));
let aes256_rc = aes::Aes256::new(&Array::from(KEY_32));

let mut mask = c.benchmark_group("header-protection/mask");
mask.bench_function("rscrypto/aes128", |b| {
b.iter(|| black_box(aes128_rs.mask(black_box(&sample))))
});
mask.bench_function("rustcrypto/aes128", |b| {
b.iter(|| {
let mut block = Array::from(*black_box(&sample));
aes128_rc.encrypt_block(&mut block);
black_box(<[u8; 5]>::try_from(&block[..5]).expect("five-byte prefix has fixed length"))
})
});
mask.bench_function("rscrypto/aes256", |b| {
b.iter(|| black_box(aes256_rs.mask(black_box(&sample))))
});
mask.bench_function("rustcrypto/aes256", |b| {
b.iter(|| {
let mut block = Array::from(*black_box(&sample));
aes256_rc.encrypt_block(&mut block);
black_box(<[u8; 5]>::try_from(&block[..5]).expect("five-byte prefix has fixed length"))
})
});
mask.bench_function("rscrypto/chacha20", |b| {
b.iter(|| black_box(chacha_rs.mask(black_box(&sample))))
});
mask.bench_function("rustcrypto/chacha20", |b| {
b.iter(|| {
let counter = u32::from_le_bytes(sample[..4].try_into().expect("counter prefix has fixed length"));
let nonce: [u8; 12] = sample[4..].try_into().expect("nonce suffix has fixed length");
let mut core = chacha20::ChaChaCore::<chacha20::R20, chacha20::variants::Ietf>::new(
black_box(&KEY_32).into(),
black_box(&nonce).into(),
);
core.set_block_pos(counter);
let mut block = chacha20::cipher::array::Array::<u8, chacha20::cipher::consts::U64>::default();
core.write_keystream_block(&mut block);
black_box(<[u8; 5]>::try_from(&block[..5]).expect("five-byte prefix has fixed length"))
})
});
mask.finish();
}

// Criterion harness

criterion_group!(
Expand All @@ -1394,5 +1487,6 @@ criterion_group!(
aegis256_decrypt,
ascon_aead128_encrypt,
ascon_aead128_decrypt,
header_protection,
);
criterion_main!(benches);
92 changes: 80 additions & 12 deletions ct.toml
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,33 @@ right_class = "random scalar"
samples = 20000
smoke_samples = 2000

[[dudect_case]]
name = "aes128_header_protection_fixed_vs_random_key"
primitive = "aead.symmetric_transform"
filter = "aes128_header_protection_fixed_vs_random_key"
left_class = "complete AES-128 header-protection operation with fixed secret key"
right_class = "complete AES-128 header-protection operation with random secret key"
samples = 20000
smoke_samples = 2000

[[dudect_case]]
name = "aes256_header_protection_fixed_vs_random_key"
primitive = "aead.symmetric_transform"
filter = "aes256_header_protection_fixed_vs_random_key"
left_class = "complete AES-256 header-protection operation with fixed secret key"
right_class = "complete AES-256 header-protection operation with random secret key"
samples = 20000
smoke_samples = 2000

[[dudect_case]]
name = "chacha20_header_protection_fixed_vs_random_key"
primitive = "aead.symmetric_transform"
filter = "chacha20_header_protection_fixed_vs_random_key"
left_class = "complete ChaCha20 header-protection operation with fixed secret key"
right_class = "complete ChaCha20 header-protection operation with random secret key"
samples = 20000
smoke_samples = 2000

[[dudect_case]]
name = "mlkem512_keygen_secret_noise_fixed_vs_random"
primitive = "kem.mlkem512"
Expand Down Expand Up @@ -1855,6 +1882,27 @@ variant = "AsconAead128"
dudect = ["ascon_aead128_fixed_vs_random_key_seal"]
binsec = ["aead.symmetric_transform.ascon_aead128_tag_portable.all"]

[[evidence_unit]]
id = "aead.symmetric_transform.aes128_header_protection"
primitive = "aead.symmetric_transform"
variant = "Aes128HeaderProtection"
dudect = ["aes128_header_protection_fixed_vs_random_key"]
binsec = ["aead.symmetric_transform.aes_round_portable.all"]

[[evidence_unit]]
id = "aead.symmetric_transform.aes256_header_protection"
primitive = "aead.symmetric_transform"
variant = "Aes256HeaderProtection"
dudect = ["aes256_header_protection_fixed_vs_random_key"]
binsec = ["aead.symmetric_transform.aes_round_portable.all"]

[[evidence_unit]]
id = "aead.symmetric_transform.chacha20_header_protection"
primitive = "aead.symmetric_transform"
variant = "ChaCha20HeaderProtection"
dudect = ["chacha20_header_protection_fixed_vs_random_key"]
binsec = ["aead.symmetric_transform.chacha20poly1305_seal.portable.all"]

[[evidence_unit]]
id = "rsa.private_ops.pkcs1v15_sign"
primitive = "rsa.private_ops"
Expand Down Expand Up @@ -2787,8 +2835,8 @@ name = "x86_64-unknown-linux-gnu"
group = "linux"
backend = "llvm"
linker = "platform-default-unpinned"
compiler_api_item_count = 2246
compiler_api_sha256 = "c3584d2a5516984b8acd3189c2d73f2aa4f7b33af63e2aec4cb31a0bb3b58a4d"
compiler_api_item_count = 2268
compiler_api_sha256 = "6e3f13819536b857ab38b6b77280db1ca7da49ca9ddc91bf964e4656f0ecea19"
claim = "ct-intended"
physical_timing = "required"
binsec = "required"
Expand All @@ -2799,8 +2847,8 @@ name = "aarch64-unknown-linux-gnu"
group = "linux"
backend = "llvm"
linker = "platform-default-unpinned"
compiler_api_item_count = 2240
compiler_api_sha256 = "c573e79dace25ef23c10cedca144416acefa9e360c48ffa8bea14636cc3dd9c3"
compiler_api_item_count = 2262
compiler_api_sha256 = "8e85d195e87964207eaf4a142b157eef0d8f8aea85b838ee6c172279b1fe23df"
claim = "ct-intended"
physical_timing = "required"
binsec = "required"
Expand Down Expand Up @@ -2859,8 +2907,8 @@ name = "aarch64-apple-darwin"
group = "macos"
backend = "llvm"
linker = "apple-ld-unpinned"
compiler_api_item_count = 2240
compiler_api_sha256 = "c573e79dace25ef23c10cedca144416acefa9e360c48ffa8bea14636cc3dd9c3"
compiler_api_item_count = 2262
compiler_api_sha256 = "8e85d195e87964207eaf4a142b157eef0d8f8aea85b838ee6c172279b1fe23df"
claim = "ct-intended"
physical_timing = "required"
binsec = "unsupported"
Expand All @@ -2884,8 +2932,8 @@ name = "s390x-unknown-linux-gnu"
group = "ibm"
backend = "llvm"
linker = "platform-default-unpinned"
compiler_api_item_count = 2239
compiler_api_sha256 = "44733fbae78fed04e9534deec1c367c411bc9f6004eceb52f5aaa6ef1ac41502"
compiler_api_item_count = 2261
compiler_api_sha256 = "daa3642fedbb5532ec987e192ef9f3f8e522237df89779dca933ea17920a3911"
claim = "ct-intended"
physical_timing = "required"
binsec = "unsupported"
Expand All @@ -2897,8 +2945,8 @@ name = "powerpc64le-unknown-linux-gnu"
group = "ibm"
backend = "llvm"
linker = "platform-default-unpinned"
compiler_api_item_count = 2239
compiler_api_sha256 = "b1ff2caddb915001a54731b9a0f53328c5541e9213c83da8e01b9a680eb53913"
compiler_api_item_count = 2261
compiler_api_sha256 = "9ea6ddaa217b1181d383fc4dd51690439005044199ae5af472d55a54d7775ea9"
claim = "ct-intended"
physical_timing = "required"
binsec = "unsupported"
Expand All @@ -2910,8 +2958,8 @@ name = "riscv64gc-unknown-linux-gnu"
group = "linux"
backend = "llvm"
linker = "platform-default-unpinned"
compiler_api_item_count = 2239
compiler_api_sha256 = "461621913477bbb623f12d4b07af646cd49e597027c24133294aeec9251aa34b"
compiler_api_item_count = 2261
compiler_api_sha256 = "cc0faa52fd40a16280c020505643c008b1bfbc0e6f7c3b9d05e333bbb7b87eb2"
claim = "ct-intended"
physical_timing = "required"
binsec = "unsupported"
Expand Down Expand Up @@ -3099,6 +3147,26 @@ evidence = [
]
limitation = "Release claims require exact target artifacts. Random-nonce helper timing includes the platform entropy source."

[[operation]]
id = "aead.header_protection"
api = [
"rscrypto::aead::expert::header_protection::{Aes128HeaderProtection,Aes256HeaderProtection,ChaCha20HeaderProtection}::{new,mask}",
]
features = ["aes-gcm for AES variants", "chacha20poly1305 for the ChaCha20 variant"]
targets = ["all-supported", "AES backend selected by public target capabilities"]
secret_inputs = ["header-protection key", "expanded AES schedule or retained ChaCha20 key", "full cipher output block"]
public_inputs = ["algorithm variant", "fixed-size ciphertext sample", "backend capabilities"]
variable_time_components = ["public backend dispatch"]
permitted_leakage = ["algorithm", "sample", "backend", "returned five-byte mask"]
claim = "ct-intended"
evidence = [
"primitive:aead.symmetric_transform",
"unit:aead.symmetric_transform.aes128_header_protection",
"unit:aead.symmetric_transform.aes256_header_protection",
"unit:aead.symmetric_transform.chacha20_header_protection",
]
limitation = "DudeCT covers complete key construction and mask generation. AES reuses the existing portable-round BINSEC leaf; ChaCha20 reuses the portable seal leaf. Exact release claims remain bound to target artifacts."

[[operation]]
id = "aead.open_and_authenticate"
api = [
Expand Down
1 change: 1 addition & 0 deletions docs/constant-time.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ evidence gate. This is intent, not a standalone public claim:
- MAC/tag verification and fixed-size equality owned by concrete key, secret,
tag, and keyed-output types.
- AEAD authentication and failed-open cleanup.
- AES and ChaCha20 header-protection mask generation with the derived key as secret and the fixed-size sample as public.
- X25519 scalar multiplication.
- ML-KEM-512/768/1024 key generation secret noise, encapsulation coins,
decapsulation secret-key material, implicit-rejection seed, and listed
Expand Down
4 changes: 2 additions & 2 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ rscrypto = { version = "0.8.1", features = ["full", "portable-only"] }
| `rsa` | `alloc`, `sha2` | RSA public/private keys, RSA signatures, OAEP, PKCS#1 v1.5, key generation |
| `x25519` | -- | X25519 key exchange |
| `ml-kem` | `sha3` | ML-KEM-512, ML-KEM-768, and ML-KEM-1024 key encapsulation |
| `aes-gcm` | -- | AES-128-GCM and AES-256-GCM |
| `aes-gcm` | -- | AES-128-GCM, AES-256-GCM, and expert AES header-protection mask generation |
| `aes-gcm-siv` | -- | AES-128-GCM-SIV and AES-256-GCM-SIV |
| `chacha20poly1305` | -- | ChaCha20-Poly1305 |
| `chacha20poly1305` | -- | ChaCha20-Poly1305 and expert ChaCha20 header-protection mask generation |
| `xchacha20poly1305` | -- | XChaCha20-Poly1305 |
| `aegis256` | -- | AEGIS-256 |
| `ascon-aead` | -- | Ascon-AEAD128 |
Expand Down
Loading