google / google/webcrypto.dart

bug: browser AES-CTR exposes inconsistent errors for invalid counter bit lengths

Open
#392 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Dart
Stars
116
Forks
110
Avg merge
6d 8h
Merged PRs (30d)
9

Description

## Summary

The browser implementation of `AesCtrSecretKey.encryptBytes` and `decryptBytes` forwards `length` to `SubtleCrypto` without validating the AES-CTR counter length range first.

The native backend validates that `length` is between 1 and 128 and throws `ArgumentError`. In browsers, invalid values are instead handled at the Web IDL or Web Crypto boundary. Values outside the Web IDL `octet` range can expose a raw JavaScript `TypeError`, while other invalid AES-CTR lengths produce `OperationError`.

This makes the public API behavior dependent on the selected backend.

## Reproduction

```dart
import 'package:webcrypto/webcrypto.dart';

Future main() async {
final key = await AesCtrSecretKey.importRawKey(List.filled(16, 0));
final counter = List.filled(16, 0);

await key.encryptBytes([1, 2, 3], counter, -1);
}
```

### Native result

```text
Invalid argument (length): must be between 1 and 128: -1
```

### Chrome result

```text
TypeError: Failed to execute 'encrypt' on 'SubtleCrypto':
AesCtrParams: length: Outside of numeric range
```

The browser result occurs under both dart2js and dart2wasm.

## Expected behavior

AES-CTR `length` values outside `1..128` should be rejected consistently with `ArgumentError` before entering browser interop, matching the native backend.

Both encryption and decryption should have the same behavior.

## Cause

Web Crypto defines `AesCtrParams.length` as an `[EnforceRange] octet`. The browser backend currently passes the Dart integer directly to `SubtleCrypto`, while the native backend performs an explicit `1..128` validation.

## Suggested fix

- Validate `1 <= length <= 128` in the browser AES-CTR implementation before calling `SubtleCrypto`.
- Apply the validation to encryption and decryption.
- Add shared regression coverage for `-1`, `0`, `129`, and `256`.
- Run the regression on native, Chrome/dart2js, and Chrome/dart2wasm.

Contributor guide

Open the contributing guide

Research direction

Find the browser implementation of AesCtrSecretKey.encryptBytes and decryptBytes, then compare its length handling with the native backend. Add shared regression coverage for -1, 0, 129, and 256, and run it on native, Chrome/dart2js, and Chrome/dart2wasm to verify consistent ArgumentError behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
dart
Domain
cryptography, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.