diff --git a/digest/CHANGELOG.md b/digest/CHANGELOG.md index 9d0759017..7952c1899 100644 --- a/digest/CHANGELOG.md +++ b/digest/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.11.4 (UNRELEASED) +### Fixed +- `dev` MAC test helpers check that a tag with its first or last byte changed is rejected, and `initialized_mac_test` also verifies inputs shorter than 2 bytes. + ## 0.11.3 (2026-04-03) ### Added - `dev::initialized_mac_test` function ([#2367]) diff --git a/digest/src/dev/mac.rs b/digest/src/dev/mac.rs index 9a3581285..6edd2462d 100644 --- a/digest/src/dev/mac.rs +++ b/digest/src/dev/mac.rs @@ -1,4 +1,4 @@ -use crate::{FixedOutputReset, Mac, common::KeyInit}; +use crate::{FixedOutputReset, Mac, MacError, Output, common::KeyInit}; /// Tag truncation side used in MAC tests #[derive(Clone, Copy, Debug)] @@ -71,7 +71,14 @@ pub fn initialized_mac_test( } } - Ok(()) + // test verification over the whole message, including empty inputs + let mut mac = mac0.clone(); + mac.update(input); + if verify(mac, tag, trunc_side).is_err() { + return Err("whole message verification"); + } + + check_wrong_tags(&mac0, input, tag, trunc_side) } /// Run resettable MAC test @@ -123,6 +130,42 @@ pub fn reset_mac_test( } } + check_wrong_tags(&mac0, input, tag, trunc_side) +} + +/// Verify `tag` with the method that matches `trunc_side`. +fn verify(mac: M, tag: &[u8], trunc_side: MacTruncSide) -> Result<(), MacError> { + match trunc_side { + MacTruncSide::Left => mac.verify_truncated_left(tag), + MacTruncSide::Right => mac.verify_truncated_right(tag), + MacTruncSide::None => mac.verify_slice(tag), + } +} + +/// Check that `tag` with its first or its last byte changed is rejected. +fn check_wrong_tags( + mac0: &M, + input: &[u8], + tag: &[u8], + trunc_side: MacTruncSide, +) -> Result<(), &'static str> { + let n = tag.len(); + if n == 0 { + return Ok(()); + } + // The callers have already compared `tag` with the output, so `n` is at most the output size. + let mut buf = Output::::default(); + let wrong_tag = &mut buf[..n]; + wrong_tag.copy_from_slice(tag); + for i in [0, n - 1] { + wrong_tag[i] ^= 1; + let mut mac = mac0.clone(); + Mac::update(&mut mac, input); + if verify(mac, wrong_tag, trunc_side).is_ok() { + return Err("wrong tag accepted"); + } + wrong_tag[i] ^= 1; + } Ok(()) }