From 0c84b6ec76e529ea4a0a859a79240ec34ebf0c9a Mon Sep 17 00:00:00 2001 From: Harshita Yadav Date: Wed, 1 Jul 2026 00:02:34 +0530 Subject: [PATCH 1/2] docs: document ECDH deriveBits limits --- lib/src/testing/ecdh/derive_bits.dart | 70 +++++++++++++++++++++++++++ lib/src/testing/testing.dart | 2 + lib/src/webcrypto/webcrypto.ecdh.dart | 4 ++ 3 files changed, 76 insertions(+) create mode 100644 lib/src/testing/ecdh/derive_bits.dart diff --git a/lib/src/testing/ecdh/derive_bits.dart b/lib/src/testing/ecdh/derive_bits.dart new file mode 100644 index 000000000..e472f50fd --- /dev/null +++ b/lib/src/testing/ecdh/derive_bits.dart @@ -0,0 +1,70 @@ +// 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'; +import '../utils/detected_runtime.dart'; +import '../utils/utils.dart'; + +final _cases = [ + (name: 'P-256', curve: EllipticCurve.p256, maxBits: 256), + (name: 'P-384', curve: EllipticCurve.p384, maxBits: 384), + (name: 'P-521', curve: EllipticCurve.p521, maxBits: 528), +]; + +void main() => tests().runTests(); + +List<({String name, Future Function() test})> tests() { + final tests = <({String name, Future Function() test})>[]; + void test(String name, Future Function() fn) => + tests.add((name: name, test: fn)); + + for (final c in _cases) { + if (detectedRuntime == 'safari' && c.curve == EllipticCurve.p521) { + continue; + } + + test('ECDH: ${c.name} allows maximum deriveBits length', () async { + final aliceKeyPair = await EcdhPrivateKey.generateKey(c.curve); + final bobKeyPair = await EcdhPrivateKey.generateKey(c.curve); + + final secret = await aliceKeyPair.privateKey.deriveBits( + c.maxBits, + bobKeyPair.publicKey, + ); + + check(secret.length == c.maxBits ~/ 8, 'secret length mismatch'); + }); + + test('ECDH: ${c.name} rejects deriveBits larger than maximum', () async { + final aliceKeyPair = await EcdhPrivateKey.generateKey(c.curve); + final bobKeyPair = await EcdhPrivateKey.generateKey(c.curve); + + var threw = false; + try { + await aliceKeyPair.privateKey.deriveBits( + c.maxBits + 8, + bobKeyPair.publicKey, + ); + } on OperationError { + threw = true; + } + check( + threw, + 'Should throw OperationError for deriveBits larger than maximum', + ); + }); + } + + return tests; +} diff --git a/lib/src/testing/testing.dart b/lib/src/testing/testing.dart index 3c7b4fd26..6241fe9f1 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 'ecdh/derive_bits.dart' as ecdh_derive_bits; import 'regression/issue_60_trailing_bytes.dart' as issue_60_trailing_bytes; /// Test runners from all test files except `digest.dart` and @@ -59,6 +60,7 @@ void runAllTests( for (final r in _testRunners) ...r.tests(), ...random.tests(), ...digest.tests(), + ...ecdh_derive_bits.tests(), ...issue_60_trailing_bytes.tests(), ]; diff --git a/lib/src/webcrypto/webcrypto.ecdh.dart b/lib/src/webcrypto/webcrypto.ecdh.dart index 064dbb17e..8840d05aa 100644 --- a/lib/src/webcrypto/webcrypto.ecdh.dart +++ b/lib/src/webcrypto/webcrypto.ecdh.dart @@ -206,6 +206,10 @@ final class EcdhPrivateKey { /// two parties. /// /// [length] specifies the length of the derived secret in bits. + /// The maximum allowed [length] is determined by the elliptic curve: + /// * [EllipticCurve.p256] can derive up to 256 bits. + /// * [EllipticCurve.p384] can derive up to 384 bits. + /// * [EllipticCurve.p521] can derive up to 528 bits. /// [publicKey] is [EcdhPublicKey] from the other party's ECDH key pair. /// /// Returns a [Uint8List] containing the derived shared secret. From ffb25b74d9d2c0c36d211562ad9c821cc2e3b970 Mon Sep 17 00:00:00 2001 From: Harshita Yadav Date: Fri, 24 Jul 2026 21:53:55 +0530 Subject: [PATCH 2/2] test(ecdh): cover deriveBits boundary lengths --- lib/src/testing/ecdh/derive_bits.dart | 2 +- lib/src/testing/webcrypto/ecdh.dart | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/src/testing/ecdh/derive_bits.dart b/lib/src/testing/ecdh/derive_bits.dart index e472f50fd..144b38a7c 100644 --- a/lib/src/testing/ecdh/derive_bits.dart +++ b/lib/src/testing/ecdh/derive_bits.dart @@ -53,7 +53,7 @@ List<({String name, Future Function() test})> tests() { var threw = false; try { await aliceKeyPair.privateKey.deriveBits( - c.maxBits + 8, + c.maxBits + 1, bobKeyPair.publicKey, ); } on OperationError { diff --git a/lib/src/testing/webcrypto/ecdh.dart b/lib/src/testing/webcrypto/ecdh.dart index d8202aa0b..b54d0fa2e 100644 --- a/lib/src/testing/webcrypto/ecdh.dart +++ b/lib/src/testing/webcrypto/ecdh.dart @@ -66,7 +66,8 @@ void main() async { generateKeyParams: {'curve': curveToJson(EllipticCurve.p256)}, importKeyParams: {'curve': curveToJson(EllipticCurve.p256)}, deriveParams: {}, - maxDeriveLength: 32, + minDeriveLength: 256, + maxDeriveLength: 256, ); // P-384: up to 384 bits (48 bytes) @@ -74,7 +75,8 @@ void main() async { generateKeyParams: {'curve': curveToJson(EllipticCurve.p384)}, importKeyParams: {'curve': curveToJson(EllipticCurve.p384)}, deriveParams: {}, - maxDeriveLength: 48, + minDeriveLength: 384, + maxDeriveLength: 384, ); // P-521: up to 528 bits (66 bytes) @@ -82,7 +84,8 @@ void main() async { generateKeyParams: {'curve': curveToJson(EllipticCurve.p521)}, importKeyParams: {'curve': curveToJson(EllipticCurve.p521)}, deriveParams: {}, - maxDeriveLength: 66, + minDeriveLength: 528, + maxDeriveLength: 528, ); log('--------------------');