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
4 changes: 4 additions & 0 deletions lib/src/webcrypto/webcrypto.ecdh.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
67 changes: 67 additions & 0 deletions test/crypto_subtle_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,73 @@ void main() {
);
});
});
group('ECDH deriveBits', () {
test('P-256 allows maximum deriveBits length', () async {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests should decidedly not live in test/crypto_subtle_test.dart --- this tests the subtle.window.crypto.subtle wrapper we have.

I suggest we put them in lib/src/testing/ecdh/derive_bits.dart or test/ecdh_derive_bits_test.dart (they won't be included in integration tests, but maybe that's okay -- we can always move them later, and getting coverage is probably better).


I don't mind these tests, but they could also be made much simpler.

final _cases = [
  (
    name: 'P-256',
    curve: EllipticCurve.p256,
    maxBits: 256,
  ),
  ...
];

void main() {
  for (final c in _cases) {
    test('${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,
      );

      expect(secret.length, equals(c.maxBits / 8));
    });

    test('${c.name} rejects deriveBits larger than maximum', () async {
      final aliceKeyPair = await EcdhPrivateKey.generateKey(c.curve);
      final bobKeyPair = await EcdhPrivateKey.generateKey(c.curve);

      expect(
        aliceKeyPair.privateKey.deriveBits(c.maxBits + 1, bobKeyPair.publicKey),
        throwsA(anyOf(isA<subtle.JSDomException>(), isA<Error>())), 
      );
    });
  }
}

final aliceKeyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p256);
final bobKeyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p256);

final secret = await aliceKeyPair.privateKey.deriveBits(
256,
bobKeyPair.publicKey,
);

expect(secret.length, equals(32));
});

test('P-256 rejects deriveBits larger than maximum', () async {
final aliceKeyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p256);
final bobKeyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p256);

expect(
aliceKeyPair.privateKey.deriveBits(257, bobKeyPair.publicKey),
throwsA(anyOf(isA<subtle.JSDomException>(), isA<Error>())),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this implies a different kind of issue.

I think we are supposed to catch JSDomException and make it into an Exception or an Error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear that is probably an orthogonal issue to this PR and should be fixed separately.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since it’s kind of a separate thing, I will leave it out of this PR for now so this doesn’t get messy. I can open a new issue for the JSDomException part if needed.

);
});

test('P-384 allows maximum deriveBits length', () async {
final aliceKeyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p384);
final bobKeyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p384);

final secret = await aliceKeyPair.privateKey.deriveBits(
384,
bobKeyPair.publicKey,
);

expect(secret.length, equals(48));
});

test('P-384 rejects deriveBits larger than maximum', () async {
final aliceKeyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p384);
final bobKeyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p384);

expect(
aliceKeyPair.privateKey.deriveBits(385, bobKeyPair.publicKey),
throwsA(anyOf(isA<subtle.JSDomException>(), isA<Error>())),
);
});

test('P-521 allows maximum deriveBits length', () async {
final aliceKeyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p521);
final bobKeyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p521);

final secret = await aliceKeyPair.privateKey.deriveBits(
528,
bobKeyPair.publicKey,
);

expect(secret.length, equals(66));
});

test('P-521 rejects deriveBits larger than maximum', () async {
final aliceKeyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p521);
final bobKeyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p521);

expect(
aliceKeyPair.privateKey.deriveBits(529, bobKeyPair.publicKey),
throwsA(anyOf(isA<subtle.JSDomException>(), isA<Error>())),
);
});
});
}

extension on JSArray<JSString> {
Expand Down
Loading