Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/src/impl_ffi/impl_ffi.pbkdf2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ final class _Pbkdf2SecretKeyImpl implements Pbkdf2SecretKeyImpl {
'Iterations <= 0 is not allowed for Pbkdf2SecretKey.deriveBits',
);
}
_checkUnsignedLong(iterations, 'iterations');
if (length == 0) {
return Uint8List(0);
}
Expand Down
2 changes: 2 additions & 0 deletions lib/src/impl_ffi/impl_ffi.rsapss.dart
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ final class _RsaPssPrivateKeyImpl implements RsaPssPrivateKeyImpl {
'must be a positive integer',
);
}
_checkInt32(saltLength, 'saltLength');

return _signStream(
_key,
Expand Down Expand Up @@ -219,6 +220,7 @@ final class _RsaPssPublicKeyImpl implements RsaPssPublicKeyImpl {
'must be a positive integer',
);
}
_checkInt32(saltLength, 'saltLength');

return _verifyStream(
_key,
Expand Down
14 changes: 14 additions & 0 deletions lib/src/impl_ffi/impl_ffi.utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ void _checkData(bool condition, {String? message, String? fallback}) {
void _checkDataIsOne(int retval, {String? message, String? fallback}) =>
_checkData(retval == 1, message: message, fallback: fallback);

/// Validate that [value] fits Web IDL's `unsigned long` type.
void _checkUnsignedLong(int value, String name) {
if (value < 0 || value > 0xffffffff) {
throw ArgumentError.value(value, name, 'must be between 0 and 4294967295');
}
}

/// Validate that [value] can be passed to a signed 32-bit native parameter.
void _checkInt32(int value, String name) {
if (value < -0x80000000 || value > 0x7fffffff) {
throw ArgumentError.value(value, name, 'must fit a signed 32-bit integer');
}
}

/// Extract latest error on this thread as [String] and clear the error queue
/// for this thread.
///
Expand Down
82 changes: 82 additions & 0 deletions lib/src/testing/regression/uint32_parameter_overflow.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import 'package:webcrypto/webcrypto.dart';

const _isFfi = bool.fromEnvironment('dart.library.ffi');
const _maxUnsignedLong = 0xffffffff;

List<({String name, Future<void> Function() test})> tests() => !_isFfi
? const []
: [
(
name: 'PBKDF2 rejects iteration counts above unsigned long',
test: () async {
final key = await Pbkdf2SecretKey.importRawKey([1, 2, 3, 4]);

for (final iterations in [
_maxUnsignedLong + 1,
_maxUnsignedLong + 2,
]) {
await _expectArgumentError(
() =>
key.deriveBits(256, Hash.sha256, [5, 6, 7, 8], iterations),
'Expected PBKDF2 iterations $iterations to be rejected',
);
}
},
),
(
name: 'RSA-PSS rejects salt lengths that narrow at the FFI boundary',
test: () async {
final pair = await RsaPssPrivateKey.generateKey(
1024,
BigInt.from(65537),
Hash.sha256,
);
final message = [1, 2, 3, 4];
final signature = await pair.privateKey.signBytes(message, 0);

for (final saltLength in [
0x80000000,
_maxUnsignedLong,
_maxUnsignedLong + 1,
_maxUnsignedLong + 2,
]) {
await _expectArgumentError(
() => pair.privateKey.signBytes(message, saltLength),
'Expected RSA-PSS signing salt length $saltLength to be rejected',
);

await _expectArgumentError(
() =>
pair.publicKey.verifyBytes(signature, message, saltLength),
'Expected RSA-PSS verification salt length $saltLength to be rejected',
);
}
},
),
];

Future<void> _expectArgumentError(
Future<Object?> Function() callback,
String message,
) async {
try {
await callback();
} on ArgumentError {
return;
}
throw AssertionError(message);
}
2 changes: 2 additions & 0 deletions lib/src/testing/testing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import 'webcrypto/digest.dart' as digest;
import 'regression/derive_bits_zero_length.dart' as derive_bits_zero_length;
import 'regression/issue_60_trailing_bytes.dart' as issue_60_trailing_bytes;
import 'regression/rsa_oaep_sha1_jwk_alg.dart' as rsa_oaep_sha1_jwk_alg;
import 'regression/uint32_parameter_overflow.dart' as uint32_parameter_overflow;

/// Test runners from all test files except `digest.dart` and
/// `random.dart`, which do not use [TestRunner].
Expand Down Expand Up @@ -64,6 +65,7 @@ void runAllTests(
...issue_60_trailing_bytes.tests(),
...derive_bits_zero_length.tests(),
...rsa_oaep_sha1_jwk_alg.tests(),
...uint32_parameter_overflow.tests(),
];

for (final (:name, :test) in allTests) {
Expand Down
1 change: 1 addition & 0 deletions lib/src/webcrypto/webcrypto.pbkdf2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ final class Pbkdf2SecretKey {
/// an exhaustive search for the derived key, but it will also make the
/// key derivation operation slower. For details on [iterations] see
/// [RFC 8018 section 4.2][1].
/// The [iterations] count must be between `1` and `2^32 - 1`.
///
/// {@macro Pbkdf2SecretKey:example}
///
Expand Down
2 changes: 2 additions & 0 deletions lib/src/webcrypto/webcrypto.rsapss.dart
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ final class RsaPssPrivateKey {
/// The [saltLength] is typically zero or length of the [Hash], for a
/// discussion of appropriate values for [saltLength] see
/// [RFC 3447 Section 9.1, Notes 4][1].
/// The [saltLength] must be between `0` and `2^32 - 1`.
///
/// When running in Safari/WebKit on Mac the [saltLength] is restricted to
/// `0 <= saltLength <= hashLength` as the underlying cryptography libraries
Expand Down Expand Up @@ -324,6 +325,7 @@ final class RsaPssPrivateKey {
/// The [saltLength] is typically zero or length of the [Hash], for a
/// discussion of appropriate values for [saltLength] see
/// [RFC 3447 Section 9.1, Notes 4][1].
/// The [saltLength] must be between `0` and `2^32 - 1`.
///
/// When running in Safari/WebKit on Mac the [saltLength] is restricted to
/// `0 <= saltLength <= hashLength` as the underlying cryptography libraries
Expand Down