google / google/webcrypto.dart

bug: browser HKDF and PBKDF2 deriveBits wrap out-of-range lengths

Open
#389 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 implementations of `HkdfSecretKey.deriveBits` and`Pbkdf2SecretKey.deriveBits` forward `length` to `SubtleCrypto.deriveBits` without first validating the Dart integer range.

Web IDL converts the value to an `unsigned long`. Consequently, some invalid negative or greater-than-32-bit values wrap modulo `2^32` and become valid small derivations.

For example, `-0xfffffff8` wraps to `8`, causing the browser backend to successfully derive one byte. The native backend rejects the same value with `ArgumentError`.

## Reproduction

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

Future main() async {
const length = -0xfffffff8; // Converts to 8 as an unsigned 32-bit value.

final pbkdf2 = await Pbkdf2SecretKey.importRawKey([1]);
final pbkdf2Result = await pbkdf2.deriveBits(
length,
Hash.sha256,
[2],
1,
);
print('PBKDF2: ${pbkdf2Result.length} bytes');

final hkdf = await HkdfSecretKey.importRawKey([1]);
final hkdfResult = await hkdf.deriveBits(
length,
Hash.sha256,
[2],
[3],
);
print('HKDF: ${hkdfResult.length} bytes');
}
```

### Native result

Both operations throw `ArgumentError`.

### Chrome result

Under both dart2js and dart2wasm, both operations succeed and return one byte.

## Expected behavior

`length` values outside the Web IDL `unsigned long` range (`0` through `2^32 - 1`) should be rejected consistently before browser numeric conversion or native output allocation. Valid values, including zero, should remain unchanged.

## Impact

An invalid caller-supplied output length can silently select a different, smaller derivation on web platforms.

The positive overflow case is also relevant to the native PBKDF2 backend: a value such as `0x100000008`, which wraps to 8 bits in the browser, reaches the native `length ~/ 8` allocation path as an output larger than 512 MiB.

## Cause

The public HKDF and PBKDF2 methods do not validate `length`.

The browser backend passes it directly to `SubtleCrypto.deriveBits`, where Web IDL conversion wraps out-of-range integers. The native implementations perform some algorithm-specific validation, but there is no shared cross-platform boundary.

## Suggested fix

- Validate `0 <= length <= 0xffffffff` at the shared public API boundary.
- Apply the validation to both HKDF and PBKDF2.
- Add shared regression tests for negative and greater-than-32-bit values.
- Run the regression tests on native, Chrome/dart2js, and Chrome/dart2wasm.

## Related issues

- #366 fixed the same class of Web IDL wrapping specifically for ECDH.
- #347 concerns PBKDF2 iteration counts and RSA-PSS salt lengths, not KDF output lengths.

Contributor guide

Open the contributing guide

Research direction

Start at the shared public HkdfSecretKey.deriveBits and Pbkdf2SecretKey.deriveBits methods, then review issue #366 for the related Web IDL range-validation fix. Add shared regression coverage for negative and greater-than-32-bit lengths while preserving zero, and run it on native, Chrome/dart2js, and Chrome/dart2wasm.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.