Skip to content

bug: native HMAC verification reads a mutated signature after verification starts #421

Description

@harrshita123

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions