From 6b6477287d5249523893e97467b1d18353ec3dfe Mon Sep 17 00:00:00 2001 From: Grega Kespret Date: Sun, 24 May 2026 21:12:42 -0700 Subject: [PATCH] Provide RFC 3394 default IV explicitly for AES key wrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit openssl 0.10.80 enforces that any cipher with a defined IV must have one supplied explicitly — passing `None` panics at runtime with "an IV is required for this cipher" inside `Crypter::new` / `decrypt` (see openssl-rs/symm.rs around the IV-required check). The two call sites here are in the RFC 6637 OpenPGP key-wrap helpers and both go through AES Key Wrap (RFC 3394). RFC 3394 §2.2.3.1 defines the "default initial value" for the wrap function as the constant `A6A6A6A6A6A6A6A6`, used when no Alternative Initial Value is supplied. Pass that constant explicitly so the cipher contract is satisfied without changing the wire-level behaviour (the byte stream produced/consumed on the network is identical to what older openssl versions did when `None` was passed). Reproduces with the export-findmy tool against any CloudKit zone that returns PCS-encrypted records (e.g. the FindMy BeaconStore zone) when built against current openssl crate releases. Co-Authored-By: Claude Opus 4.7 --- src/util.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.rs b/src/util.rs index f646234..3c302f2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1588,7 +1588,7 @@ pub fn rfc6637_wrap_key(public_key: &CompactECKey, key: &[u8], *i = padding_count as u8; } - let mut c = Crypter::new(Cipher::from_nid(Nid::ID_AES128_WRAP).unwrap(), Mode::Encrypt, &aes_key[..16], None)?; + let mut c = Crypter::new(Cipher::from_nid(Nid::ID_AES128_WRAP).unwrap(), Mode::Encrypt, &aes_key[..16], Some(&[0xa6u8; 8]))?; let mut out = vec![0u8; message.len() + 16]; let mut count = c.update(&message, &mut out)?; @@ -1618,7 +1618,7 @@ pub fn rfc6637_unwrap_key(private_key: &CompactECKey, wrapped_key: &[u8 // RFC6637 KDF let hash = rfc6637_kdf(fingerprint, &secret); - let unwrapped = decrypt(Cipher::from_nid(Nid::ID_AES128_WRAP).unwrap(), &hash[..16], None, &unpacked.wrapped)?; + let unwrapped = decrypt(Cipher::from_nid(Nid::ID_AES128_WRAP).unwrap(), &hash[..16], Some(&[0xa6u8; 8]), &unpacked.wrapped)?; let padding_len = *unwrapped.last().unwrap() as usize; for i in 0..padding_len {