AEAD decryption-in-place traits with additional tag processing, created for committing AEAD wrappers
Add AEAD decryption-in-place traits that allow additional processing on the expected tag before comparison to the received tag. This will allow me to implement the full CTX construction and possibly other similar constructions (see https://github.com/RustCrypto/AEADs/pull/564) instead of only the encryption direction.
Exposing the expected tag in this way may increase the chances of misuse. I tried to file off as many potential sharp edges as possible (e.g. by leaving the final tag comparison up to the implementer of the trait and by using Fn instead of FnMut or FnOnce to make copying the expected tag out of the closure harder), but do leave feedback if there are safer ways of allowing this functionality.
I'm still confused why these are necessary. The Aead and AeadInPlace traits already provide access to the original tag as part of the original message.
With a breaking change we can split the *_detached methods of AeadInPlace into their own trait which would make the latter cleaner for this particular use case. Of all of the APIs, they are the only ones which don't expose the tag, but if we filed them under their own trait you could simply not implement them and implement AeadInPlace instead.
Under the CTX construction (which is the motivation for this PR), the original (inner) tag is not available.
Pseudocode for CTX (crossposted from the Zulip chat) is as follows:
fn ctx_encrypt<AEAD, Digest>(key, ptxt, aad) -> (ctxt, outer_tag) {
let nonce = AEAD::generate_nonce();
let (ctxt, inner_tag) = AEAD::encrypt(key, ptxt, aad);
let digest_obj = Digest::new();
digest_obj.update(key);
digest_obj.update(nonce);
digest_obj.update(aad);
digest_obj.update(inner_tag);
let outer_tag = digest_obj.finalize();
(ctxt, outer_tag)
}
fn ctx_decrypt<AEAD, Digest>(key, nonce, ctxt, aad, outer_tag) -> Result<ptxt, ()> {
let (ptxt, expected_inner_tag) = AEAD::decrypt_with_expected_tag(key, nonce, ctxt, aad);
let digest_obj = Digest::new();
digest_obj.update(key);
digest_obj.update(nonce);
digest_obj.update(aad);
digest_obj.update(expected_inner_tag);
let expected_outer_tag = digest_obj.finalize();
if expected_outer_tag.ct_eq(tag) {
Ok(ptxt)
} else {
Err(())
}
}
The CTX construction wraps an AEAD and replaces the original (inner) tag with a hash that is computed over the AEAD's inputs. Only the hash (outer tag) gets sent as the MAC, but CTX decryption requires the original (inner) tag in order to check the received (outer) tag. The original (inner) tag cannot be recovered from the transmitted hash (outer tag), and as far as I can tell, there is no way to retrieve the expected inner tag during decryption using the current interfaces. Sending both tags has potential problems, which is why I created a CTXish-HMAC construction in the other PR to enable that to be done safely.
But if you have a generic implementation of committing AEADs, what is the purpose of the trait? Abstracting over that and potential future constructions?
The generic implementation of CTX in the other PR is incomplete because the decryption stage of many of the wrapping constructions, including CTX, require access to more of the wrapped AEAD's internal state than is provided by a decryption method that returns the equivalent of Result<ptxt, Error>. This PR would give me access to the extra internal state (namely, the wrapped AEAD's computed tag, before the equality check) that would let me implement the decryption direction of CTX as well (along with potential future constructions that also need it.)
Aha, that makes sense. Thanks.