this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Include recipient DID in HKDF info string for domain separation

The HKDF info string now includes the recipient DID alongside the
schema version and algorithm name, binding each derived wrapping key
to its intended recipient. Format: opake-v1-x25519-hkdf-a256kw-{did}

+16 -8
+5 -4
crates/opake-core/src/crypto/key_wrapping.rs
··· 11 11 use crate::records::WrappedKey; 12 12 13 13 /// Derive a 256-bit wrapping key from an ECDH shared secret via HKDF-SHA256. 14 - fn derive_wrapping_key(shared_secret: &[u8; 32]) -> Result<[u8; 32], Error> { 14 + /// The recipient DID is included in the info string for domain separation. 15 + fn derive_wrapping_key(shared_secret: &[u8; 32], recipient_did: &str) -> Result<[u8; 32], Error> { 15 16 use hkdf::Hkdf; 16 17 use sha2::Sha256; 17 18 18 19 let hkdf = Hkdf::<Sha256>::new(None, shared_secret); 19 20 let mut wrapping_key = [0u8; 32]; 20 - hkdf.expand(&hkdf_info(), &mut wrapping_key) 21 + hkdf.expand(&hkdf_info(recipient_did), &mut wrapping_key) 21 22 .map_err(|_| Error::KeyWrap("HKDF expand failed".into()))?; 22 23 Ok(wrapping_key) 23 24 } ··· 38 39 let recipient_public_key = PublicKey::from(*recipient_public_key); 39 40 let shared_secret = ephemeral_secret.diffie_hellman(&recipient_public_key); 40 41 41 - let wrapping_key = derive_wrapping_key(shared_secret.as_bytes())?; 42 + let wrapping_key = derive_wrapping_key(shared_secret.as_bytes(), recipient_did)?; 42 43 let kek = KekAes256::new((&wrapping_key).into()); 43 44 let wrapped = kek 44 45 .wrap_vec(&content_key.0) ··· 82 83 let secret = StaticSecret::from(*private_key); 83 84 let shared_secret = secret.diffie_hellman(&ephemeral_public_key); 84 85 85 - let wrapping_key = derive_wrapping_key(shared_secret.as_bytes())?; 86 + let wrapping_key = derive_wrapping_key(shared_secret.as_bytes(), &wrapped.did)?; 86 87 let kek = KekAes256::new((&wrapping_key).into()); 87 88 let content_key_bytes = kek 88 89 .unwrap_vec(wrapped_key_bytes)
+11 -4
crates/opake-core/src/crypto/mod.rs
··· 1 1 // Client-side encryption primitives. 2 2 // 3 + // NOTE TO EDITORS: 4 + // Opake uses a dual-documentation system. If you modify the cryptographic 5 + // primitives, key wrapping schemes, or security model in this file, you 6 + // MUST also update the corresponding MDX content in `web/src/content/` 7 + // to prevent documentation drift. 8 + // 3 9 // This module handles AES-256-GCM content encryption and asymmetric key 4 10 // wrapping (x25519-hkdf-a256kw). It intentionally has no I/O — it takes 5 11 // bytes in and returns bytes out. The calling layer (CLI or WASM) handles ··· 113 119 pub nonce: [u8; AES_GCM_NONCE_LEN], 114 120 } 115 121 116 - /// HKDF info string for domain separation — includes schema version so a 117 - /// version bump produces different derived keys from the same shared secret. 118 - fn hkdf_info() -> Vec<u8> { 119 - format!("opake-key-wrap-v{SCHEMA_VERSION}").into_bytes() 122 + /// HKDF info string for domain separation — includes schema version and 123 + /// recipient DID so a version bump or different recipient produces different 124 + /// derived keys from the same shared secret. 125 + fn hkdf_info(recipient_did: &str) -> Vec<u8> { 126 + format!("opake-v{SCHEMA_VERSION}-{WRAP_ALGO}-{recipient_did}").into_bytes() 120 127 } 121 128 122 129 #[cfg(test)]