Summary
Native HMAC verification can use a different signature from the one supplied when verifyBytes or verifyStream is called. The native verifyStream implementation awaits signStream(data) before it copies and compares the caller's mutable signature list. The browser backend copies the signature before invoking Web Crypto.
Reproduction
import 'package:webcrypto/webcrypto.dart';
Future<void> main() async {
final key = await HmacSecretKey.importRawKey(
List<int>.filled(32, 7),
Hash.sha256,
);
final data = List<int>.filled(50000, 3);
final signature = await key.signBytes(data);
signature[0] ^= 1; // Invalid at invocation.
final pending = key.verifyBytes(signature, data);
signature[0] ^= 1; // Restore the valid bytes while verification is pending.
print(await pending);
}
On the native VM backend this prints true; on Chrome it prints false. Reversing the mutation makes native reject a signature that was valid when verification began, while Chrome accepts it. A focused two-case regression test passed on Chrome and failed in both cases on the native VM.
Expected behavior
Verification should use the signature value supplied at invocation, consistently across backends. This requires mutation of the caller-owned list while verification is pending; it does not enable forgery without that mutation.
Root cause and suggested fix
_HmacSecretKeyImpl.verifyBytes delegates to verifyStream. In the native verifyStream, scope.dataAsPointer(signature) runs after await signStream(data), so it copies the list's later contents. Snapshot the signature before the first await, and add regressions for both mutation directions through the public API.
Environment
- Upstream
master: c0f9a6b
- Native Dart VM backend on macOS arm64; Chrome browser backend
Related: #419 concerns a mutable AES-CTR counter and has a separate implementation path.
Summary
Native HMAC verification can use a different signature from the one supplied when
verifyBytesorverifyStreamis called. The nativeverifyStreamimplementation awaitssignStream(data)before it copies and compares the caller's mutablesignaturelist. The browser backend copies the signature before invoking Web Crypto.Reproduction
On the native VM backend this prints
true; on Chrome it printsfalse. Reversing the mutation makes native reject a signature that was valid when verification began, while Chrome accepts it. A focused two-case regression test passed on Chrome and failed in both cases on the native VM.Expected behavior
Verification should use the signature value supplied at invocation, consistently across backends. This requires mutation of the caller-owned list while verification is pending; it does not enable forgery without that mutation.
Root cause and suggested fix
_HmacSecretKeyImpl.verifyBytesdelegates toverifyStream. In the nativeverifyStream,scope.dataAsPointer(signature)runs afterawait signStream(data), so it copies the list's later contents. Snapshot the signature before the firstawait, and add regressions for both mutation directions through the public API.Environment
master:c0f9a6bRelated: #419 concerns a mutable AES-CTR counter and has a separate implementation path.