google / google/webcrypto.dart
bug: browser HKDF and PBKDF2 deriveBits wrap out-of-range lengths
- 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
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