From 2043b6f511216627a67c3263fe40ec92441bdff5 Mon Sep 17 00:00:00 2001 From: Harshita Yadav Date: Mon, 13 Jul 2026 20:10:24 +0530 Subject: [PATCH] fix(derive-bits): support zero-length results --- lib/src/impl_ffi/impl_ffi.ecdh.dart | 4 +- lib/src/impl_ffi/impl_ffi.pbkdf2.dart | 8 ++-- .../regression/derive_bits_zero_length.dart | 42 +++++++++++++++++++ lib/src/testing/testing.dart | 2 + 4 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 lib/src/testing/regression/derive_bits_zero_length.dart diff --git a/lib/src/impl_ffi/impl_ffi.ecdh.dart b/lib/src/impl_ffi/impl_ffi.ecdh.dart index 06cbf53c1..f7a3c0eae 100644 --- a/lib/src/impl_ffi/impl_ffi.ecdh.dart +++ b/lib/src/impl_ffi/impl_ffi.ecdh.dart @@ -111,8 +111,8 @@ final class _EcdhPrivateKeyImpl implements EcdhPrivateKeyImpl { 'custom implementations of EcdhPublicKey is not supported', ); } - if (length <= 0) { - throw ArgumentError.value(length, 'length', 'must be positive'); + if (length < 0) { + throw ArgumentError.value(length, 'length', 'must be non-negative'); } return _Scope.async((scope) async { diff --git a/lib/src/impl_ffi/impl_ffi.pbkdf2.dart b/lib/src/impl_ffi/impl_ffi.pbkdf2.dart index 0046cfc0e..25dd53933 100644 --- a/lib/src/impl_ffi/impl_ffi.pbkdf2.dart +++ b/lib/src/impl_ffi/impl_ffi.pbkdf2.dart @@ -60,16 +60,14 @@ final class _Pbkdf2SecretKeyImpl implements Pbkdf2SecretKeyImpl { 'The length for PBKDF2 must be a multiple of 8 bits', ); } - if (length == 0) { - throw operationError( - 'A length of zero is not allowed Pbkdf2SecretKey.deriveBits', - ); - } if (iterations <= 0) { throw operationError( 'Iterations <= 0 is not allowed for Pbkdf2SecretKey.deriveBits', ); } + if (length == 0) { + return Uint8List(0); + } final lengthInBytes = length ~/ 8; diff --git a/lib/src/testing/regression/derive_bits_zero_length.dart b/lib/src/testing/regression/derive_bits_zero_length.dart new file mode 100644 index 000000000..2da6d3139 --- /dev/null +++ b/lib/src/testing/regression/derive_bits_zero_length.dart @@ -0,0 +1,42 @@ +// 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 'dart:typed_data'; + +import 'package:webcrypto/webcrypto.dart'; +import '../utils/utils.dart'; + +List<({String name, Future Function() test})> tests() => [ + ( + name: 'ECDH derives an empty secret when length is zero', + test: () async { + final alice = await EcdhPrivateKey.generateKey(EllipticCurve.p256); + final bob = await EcdhPrivateKey.generateKey(EllipticCurve.p256); + + final secret = await alice.privateKey.deriveBits(0, bob.publicKey); + + check(secret.isEmpty, 'Expected an empty ECDH secret'); + }, + ), + ( + name: 'PBKDF2 derives an empty secret when length is zero', + test: () async { + final key = await Pbkdf2SecretKey.importRawKey(Uint8List(16)); + + final secret = await key.deriveBits(0, Hash.sha256, Uint8List(16), 1); + + check(secret.isEmpty, 'Expected an empty PBKDF2 secret'); + }, + ), +]; diff --git a/lib/src/testing/testing.dart b/lib/src/testing/testing.dart index 3c7b4fd26..075a73520 100644 --- a/lib/src/testing/testing.dart +++ b/lib/src/testing/testing.dart @@ -30,6 +30,7 @@ import 'webcrypto/rsassapkcs1v15.dart' as rsassapkcs1v15; // Other test files, that don't use TestRunner import 'webcrypto/random.dart' as random; 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; /// Test runners from all test files except `digest.dart` and @@ -60,6 +61,7 @@ void runAllTests( ...random.tests(), ...digest.tests(), ...issue_60_trailing_bytes.tests(), + ...derive_bits_zero_length.tests(), ]; for (final (:name, :test) in allTests) {