• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / webcrypto.dart / 25232696112

01 May 2026 08:53PM UTC coverage: 90.337% (-0.1%) from 90.474%
25232696112

push

github

web-flow
ci: trim browser matrix and cache Android AVD (#266)

3730 of 4129 relevant lines covered (90.34%)

1.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.6
/lib/src/testing/utils/testrunner.dart
1
// Copyright 2020 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
import 'dart:convert';
16
import 'dart:math';
17
import 'dart:async';
18

19
import 'package:meta/meta.dart';
20
import 'package:webcrypto/webcrypto.dart';
21
import 'detected_runtime.dart';
22
import 'ffibonacci_chunked_stream.dart';
23
import 'utils.dart';
24
import 'lipsum.dart';
25
import 'err_stack_stub.dart' if (dart.library.ffi) 'err_stack_ffi.dart';
26

27
// Export utilities necessary for implementing a `TestRunner`.
28
export 'utils.dart' show hashFromJson, curveFromJson;
29

30
List<int>? _optionalBase64Decode(dynamic data) =>
1✔
31
    data == null ? null : base64.decode(data as String);
1✔
32

33
Map<String, dynamic>? _optionalStringMapDecode(dynamic data) =>
1✔
34
    data == null ? null : (data as Map).cast<String, dynamic>();
1✔
35

36
String? _optionalBase64Encode(List<int>? data) =>
1✔
37
    data == null ? null : base64.encode(data);
1✔
38

39
@sealed
40
class _TestCase {
41
  final String name;
42

43
  // Obtain a keyPair from import or key generation
44
  final Map<String, dynamic>? generateKeyParams;
45
  final List<int>? privateRawKeyData;
46
  final List<int>? privatePkcs8KeyData;
47
  final Map<String, dynamic>? privateJsonWebKeyData;
48
  final List<int>? publicRawKeyData;
49
  final List<int>? publicSpkiKeyData;
50
  final Map<String, dynamic>? publicJsonWebKeyData;
51

52
  // Plaintext to be signed, (always required)
53
  final List<int>? plaintext;
54
  // Signature to be verified (invalid, if generateKeyParams != null)
55
  final List<int>? signature;
56
  // Ciphertext of plaintext (invalid, if generateKeyParams != null)
57
  final List<int>? ciphertext;
58
  // Bits derived using deriveBits (invalid, if generateKeyParams != null)
59
  final List<int>? derivedBits;
60
  // Number of bits to derive.
61
  final int? derivedLength;
62

63
  // Parameters for key import (always required)
64
  final Map<String, dynamic>? importKeyParams;
65

66
  // Parameters for sign/verify (required, if there is a signature)
67
  final Map<String, dynamic>? signVerifyParams;
68

69
  // Parameters for encrypt/decrypt (required, if there is a ciphertext)
70
  final Map<String, dynamic>? encryptDecryptParams;
71

72
  // Parameters for deriveBits (required, if there is a derivedBits)
73
  final Map<String, dynamic>? deriveParams;
74

75
  _TestCase(
1✔
76
    this.name, {
77
    this.generateKeyParams,
78
    this.privateRawKeyData,
79
    this.privatePkcs8KeyData,
80
    this.privateJsonWebKeyData,
81
    this.publicRawKeyData,
82
    this.publicSpkiKeyData,
83
    this.publicJsonWebKeyData,
84
    this.plaintext,
85
    this.signature,
86
    this.ciphertext,
87
    this.derivedBits,
88
    this.derivedLength,
89
    this.importKeyParams,
90
    this.signVerifyParams,
91
    this.encryptDecryptParams,
92
    this.deriveParams,
93
  });
94

95
  factory _TestCase.fromJson(Map json) {
1✔
96
    return _TestCase(
1✔
97
      json['name'] as String,
1✔
98
      generateKeyParams: _optionalStringMapDecode(json['generateKeyParams']),
2✔
99
      privateRawKeyData: _optionalBase64Decode(json['privateRawKeyData']),
2✔
100
      privatePkcs8KeyData: _optionalBase64Decode(json['privatePkcs8KeyData']),
2✔
101
      privateJsonWebKeyData: _optionalStringMapDecode(
1✔
102
        json['privateJsonWebKeyData'],
1✔
103
      ),
104
      publicRawKeyData: _optionalBase64Decode(json['publicRawKeyData']),
2✔
105
      publicSpkiKeyData: _optionalBase64Decode(json['publicSpkiKeyData']),
2✔
106
      publicJsonWebKeyData: _optionalStringMapDecode(
1✔
107
        json['publicJsonWebKeyData'],
1✔
108
      ),
109
      plaintext: _optionalBase64Decode(json['plaintext']),
2✔
110
      signature: _optionalBase64Decode(json['signature']),
2✔
111
      ciphertext: _optionalBase64Decode(json['ciphertext']),
2✔
112
      derivedBits: _optionalBase64Decode(json['derivedBits']),
2✔
113
      derivedLength: json['derivedLength'] as int?,
1✔
114
      importKeyParams: _optionalStringMapDecode(json['importKeyParams']),
2✔
115
      signVerifyParams: _optionalStringMapDecode(json['signVerifyParams']),
2✔
116
      encryptDecryptParams: _optionalStringMapDecode(
1✔
117
        json['encryptDecryptParams'],
1✔
118
      ),
119
      deriveParams: _optionalStringMapDecode(json['deriveParams']),
2✔
120
    );
121
  }
122

123
  Map<String, dynamic> toJson() {
1✔
124
    return {
1✔
125
      'name': name,
1✔
126
      'generateKeyParams': generateKeyParams,
1✔
127
      'privateRawKeyData': _optionalBase64Encode(privateRawKeyData),
2✔
128
      'privatePkcs8KeyData': _optionalBase64Encode(privatePkcs8KeyData),
2✔
129
      'privateJsonWebKeyData': privateJsonWebKeyData,
1✔
130
      'publicRawKeyData': _optionalBase64Encode(publicRawKeyData),
2✔
131
      'publicSpkiKeyData': _optionalBase64Encode(publicSpkiKeyData),
2✔
132
      'publicJsonWebKeyData': publicJsonWebKeyData,
1✔
133
      'plaintext': _optionalBase64Encode(plaintext),
2✔
134
      'signature': _optionalBase64Encode(signature),
2✔
135
      'ciphertext': _optionalBase64Encode(ciphertext),
2✔
136
      'derivedBits': _optionalBase64Encode(derivedBits),
2✔
137
      'derivedLength': derivedLength,
1✔
138
      'importKeyParams': importKeyParams,
1✔
139
      'signVerifyParams': signVerifyParams,
1✔
140
      'encryptDecryptParams': encryptDecryptParams,
1✔
141
      'deriveParams': deriveParams,
1✔
142
    }..removeWhere((_, v) => v == null);
2✔
143
  }
144
}
145

146
/// Function for importing pkcs8, spki, or raw key.
147
typedef ImportKeyFn<T> =
148
    Future<T> Function(List<int> keyData, Map<String, dynamic> keyImportParams);
149

150
/// Function for exporting pkcs8, spki or raw key.
151
typedef ExportKeyFn<T> = Future<List<int>> Function(T key);
152

153
/// Function for importing JWK key.
154
typedef ImportJsonWebKeyKeyFn<T> =
155
    Future<T> Function(
156
      Map<String, dynamic> jsonWebKeyData,
157
      Map<String, dynamic> keyImportParams,
158
    );
159

160
/// Function for exporting JWK key.
161
typedef ExportJsonWebKeyKeyFn<T> = Future<Map<String, dynamic>> Function(T key);
162

163
/// Function for generating a key.
164
typedef GenerateKeyFn<T> =
165
    Future<T> Function(Map<String, dynamic> generateKeyPairParams);
166

167
/// Function for signing [data] using [key].
168
typedef SignBytesFn<T> =
169
    Future<List<int>> Function(
170
      T key,
171
      List<int> data,
172
      Map<String, dynamic> signParams,
173
    );
174

175
/// Function for signing [data] using [key].
176
typedef SignStreamFn<T> =
177
    Future<List<int>> Function(
178
      T key,
179
      Stream<List<int>> data,
180
      Map<String, dynamic> signParams,
181
    );
182

183
/// Function for verifying [data] using [key].
184
typedef VerifyBytesFn<T> =
185
    Future<bool> Function(
186
      T key,
187
      List<int> signature,
188
      List<int> data,
189
      Map<String, dynamic> verifyParams,
190
    );
191

192
/// Function for verifying [data] using [key].
193
typedef VerifyStreamFn<T> =
194
    Future<bool> Function(
195
      T key,
196
      List<int> signature,
197
      Stream<List<int>> data,
198
      Map<String, dynamic> verifyParams,
199
    );
200

201
/// Function for encrypting or a function for decrypting [data] using [key].
202
typedef EncryptOrDecryptBytesFn<T> =
203
    Future<List<int>> Function(
204
      T key,
205
      List<int> data,
206
      Map<String, dynamic> encryptOrDecryptParams,
207
    );
208

209
/// Function for encrypting or a function for decrypting [data] using [key].
210
typedef EncryptOrDecryptStreamFn<T> =
211
    Stream<List<int>> Function(
212
      T key,
213
      Stream<List<int>> data,
214
      Map<String, dynamic> encryptOrDecryptParams,
215
    );
216

217
typedef DeriveBitsFn<T> =
218
    Future<List<int>> Function(
219
      T keyOrKeyPair,
220
      int length,
221
      Map<String, dynamic> deriveParams,
222
    );
223

224
@sealed
225
class TestRunner<PrivateKey, PublicKey> {
226
  final String algorithm;
227

228
  /// True, if private is a secret key and there is no public key.
229
  final bool _isSymmetric;
230

231
  final ImportKeyFn<PrivateKey>? _importPrivateRawKey;
232
  final ExportKeyFn<PrivateKey>? _exportPrivateRawKey;
233
  final ImportKeyFn<PrivateKey>? _importPrivatePkcs8Key;
234
  final ExportKeyFn<PrivateKey>? _exportPrivatePkcs8Key;
235
  final ImportJsonWebKeyKeyFn<PrivateKey>? _importPrivateJsonWebKey;
236
  final ExportJsonWebKeyKeyFn<PrivateKey>? _exportPrivateJsonWebKey;
237

238
  final ImportKeyFn<PublicKey>? _importPublicRawKey;
239
  final ExportKeyFn<PublicKey>? _exportPublicRawKey;
240
  final ImportKeyFn<PublicKey>? _importPublicSpkiKey;
241
  final ExportKeyFn<PublicKey>? _exportPublicSpkiKey;
242
  final ImportJsonWebKeyKeyFn<PublicKey>? _importPublicJsonWebKey;
243
  final ExportJsonWebKeyKeyFn<PublicKey>? _exportPublicJsonWebKey;
244

245
  final GenerateKeyFn<KeyPair<PrivateKey, PublicKey>> _generateKeyPair;
246
  final SignBytesFn<PrivateKey>? _signBytes;
247
  final SignStreamFn<PrivateKey>? _signStream;
248
  final VerifyBytesFn<PublicKey>? _verifyBytes;
249
  final VerifyStreamFn<PublicKey>? _verifyStream;
250
  final EncryptOrDecryptBytesFn<PublicKey>? _encryptBytes;
251
  final EncryptOrDecryptStreamFn<PublicKey>? _encryptStream;
252
  final EncryptOrDecryptBytesFn<PrivateKey>? _decryptBytes;
253
  final EncryptOrDecryptStreamFn<PrivateKey>? _decryptStream;
254
  final DeriveBitsFn<KeyPair<PrivateKey, PublicKey>>? _deriveBits;
255

256
  final List<Map<dynamic, dynamic>> _testData;
257

258
  TestRunner._({
1✔
259
    required bool isSymmetric,
260
    required this.algorithm,
261
    ImportKeyFn<PrivateKey>? importPrivateRawKey,
262
    ExportKeyFn<PrivateKey>? exportPrivateRawKey,
263
    ImportKeyFn<PrivateKey>? importPrivatePkcs8Key,
264
    ExportKeyFn<PrivateKey>? exportPrivatePkcs8Key,
265
    ImportJsonWebKeyKeyFn<PrivateKey>? importPrivateJsonWebKey,
266
    ExportJsonWebKeyKeyFn<PrivateKey>? exportPrivateJsonWebKey,
267
    ImportKeyFn<PublicKey>? importPublicRawKey,
268
    ExportKeyFn<PublicKey>? exportPublicRawKey,
269
    ImportKeyFn<PublicKey>? importPublicSpkiKey,
270
    ExportKeyFn<PublicKey>? exportPublicSpkiKey,
271
    ImportJsonWebKeyKeyFn<PublicKey>? importPublicJsonWebKey,
272
    ExportJsonWebKeyKeyFn<PublicKey>? exportPublicJsonWebKey,
273
    required GenerateKeyFn<KeyPair<PrivateKey, PublicKey>> generateKeyPair,
274
    SignBytesFn<PrivateKey>? signBytes,
275
    SignStreamFn<PrivateKey>? signStream,
276
    VerifyBytesFn<PublicKey>? verifyBytes,
277
    VerifyStreamFn<PublicKey>? verifyStream,
278
    EncryptOrDecryptBytesFn<PublicKey>? encryptBytes,
279
    EncryptOrDecryptStreamFn<PublicKey>? encryptStream,
280
    EncryptOrDecryptBytesFn<PrivateKey>? decryptBytes,
281
    EncryptOrDecryptStreamFn<PrivateKey>? decryptStream,
282
    DeriveBitsFn<KeyPair<PrivateKey, PublicKey>>? deriveBits,
283
    Iterable<Map<dynamic, dynamic>>? testData,
284
  }) : _isSymmetric = isSymmetric,
285
       _importPrivateRawKey = importPrivateRawKey,
286
       _exportPrivateRawKey = exportPrivateRawKey,
287
       _importPrivatePkcs8Key = importPrivatePkcs8Key,
288
       _exportPrivatePkcs8Key = exportPrivatePkcs8Key,
289
       _importPrivateJsonWebKey = importPrivateJsonWebKey,
290
       _exportPrivateJsonWebKey = exportPrivateJsonWebKey,
291
       _importPublicRawKey = importPublicRawKey,
292
       _exportPublicRawKey = exportPublicRawKey,
293
       _importPublicSpkiKey = importPublicSpkiKey,
294
       _exportPublicSpkiKey = exportPublicSpkiKey,
295
       _importPublicJsonWebKey = importPublicJsonWebKey,
296
       _exportPublicJsonWebKey = exportPublicJsonWebKey,
297
       _generateKeyPair = generateKeyPair,
298
       _signBytes = signBytes,
299
       _signStream = signStream,
300
       _verifyBytes = verifyBytes,
301
       _verifyStream = verifyStream,
302
       _encryptBytes = encryptBytes,
303
       _encryptStream = encryptStream,
304
       _decryptBytes = decryptBytes,
305
       _decryptStream = decryptStream,
306
       _deriveBits = deriveBits,
307
       _testData = List.from(testData ?? <Map<dynamic, dynamic>>[]) {
1✔
308
    _validate();
1✔
309
  }
310

311
  /// Create [TestRunner] for an asymmetric primitive.
312
  static TestRunner<PrivateKey, PublicKey> asymmetric<PrivateKey, PublicKey>({
1✔
313
    required String algorithm,
314
    ImportKeyFn<PrivateKey>? importPrivateRawKey,
315
    ExportKeyFn<PrivateKey>? exportPrivateRawKey,
316
    ImportKeyFn<PrivateKey>? importPrivatePkcs8Key,
317
    ExportKeyFn<PrivateKey>? exportPrivatePkcs8Key,
318
    ImportJsonWebKeyKeyFn<PrivateKey>? importPrivateJsonWebKey,
319
    ExportJsonWebKeyKeyFn<PrivateKey>? exportPrivateJsonWebKey,
320
    ImportKeyFn<PublicKey>? importPublicRawKey,
321
    ExportKeyFn<PublicKey>? exportPublicRawKey,
322
    ImportKeyFn<PublicKey>? importPublicSpkiKey,
323
    ExportKeyFn<PublicKey>? exportPublicSpkiKey,
324
    ImportJsonWebKeyKeyFn<PublicKey>? importPublicJsonWebKey,
325
    ExportJsonWebKeyKeyFn<PublicKey>? exportPublicJsonWebKey,
326
    required GenerateKeyFn<KeyPair<PrivateKey, PublicKey>> generateKeyPair,
327
    SignBytesFn<PrivateKey>? signBytes,
328
    SignStreamFn<PrivateKey>? signStream,
329
    VerifyBytesFn<PublicKey>? verifyBytes,
330
    VerifyStreamFn<PublicKey>? verifyStream,
331
    EncryptOrDecryptBytesFn<PublicKey>? encryptBytes,
332
    EncryptOrDecryptStreamFn<PublicKey>? encryptStream,
333
    EncryptOrDecryptBytesFn<PrivateKey>? decryptBytes,
334
    EncryptOrDecryptStreamFn<PrivateKey>? decryptStream,
335
    DeriveBitsFn<KeyPair<PrivateKey, PublicKey>>? deriveBits,
336
    Iterable<Map<dynamic, dynamic>>? testData,
337
  }) {
338
    return TestRunner._(
1✔
339
      isSymmetric: false,
340
      algorithm: algorithm,
341
      importPrivateRawKey: importPrivateRawKey,
342
      exportPrivateRawKey: exportPrivateRawKey,
343
      importPrivatePkcs8Key: importPrivatePkcs8Key,
344
      exportPrivatePkcs8Key: exportPrivatePkcs8Key,
345
      importPrivateJsonWebKey: importPrivateJsonWebKey,
346
      exportPrivateJsonWebKey: exportPrivateJsonWebKey,
347
      importPublicRawKey: importPublicRawKey,
348
      exportPublicRawKey: exportPublicRawKey,
349
      importPublicSpkiKey: importPublicSpkiKey,
350
      exportPublicSpkiKey: exportPublicSpkiKey,
351
      importPublicJsonWebKey: importPublicJsonWebKey,
352
      exportPublicJsonWebKey: exportPublicJsonWebKey,
353
      generateKeyPair: generateKeyPair,
354
      signBytes: signBytes,
355
      signStream: signStream,
356
      verifyBytes: verifyBytes,
357
      verifyStream: verifyStream,
358
      encryptBytes: encryptBytes,
359
      encryptStream: encryptStream,
360
      decryptBytes: decryptBytes,
361
      decryptStream: decryptStream,
362
      deriveBits: deriveBits,
363
      testData: testData,
364
    );
365
  }
366

367
  /// Create [TestRunner] for an symmetric primitive.
368
  ///
369
  /// This just creates a [TestRunner] where public and private key have the
370
  /// same type. This may give rise to a few unnecessary test cases as
371
  /// import/export of public and private key
372
  static TestRunner<PrivateKey, PrivateKey> symmetric<PrivateKey>({
1✔
373
    required String algorithm,
374
    ImportKeyFn<PrivateKey>? importPrivateRawKey,
375
    ExportKeyFn<PrivateKey>? exportPrivateRawKey,
376
    ImportKeyFn<PrivateKey>? importPrivatePkcs8Key,
377
    ExportKeyFn<PrivateKey>? exportPrivatePkcs8Key,
378
    ImportJsonWebKeyKeyFn<PrivateKey>? importPrivateJsonWebKey,
379
    ExportJsonWebKeyKeyFn<PrivateKey>? exportPrivateJsonWebKey,
380
    required GenerateKeyFn<PrivateKey> generateKey,
381
    SignBytesFn<PrivateKey>? signBytes,
382
    SignStreamFn<PrivateKey>? signStream,
383
    VerifyBytesFn<PrivateKey>? verifyBytes,
384
    VerifyStreamFn<PrivateKey>? verifyStream,
385
    EncryptOrDecryptBytesFn<PrivateKey>? encryptBytes,
386
    EncryptOrDecryptStreamFn<PrivateKey>? encryptStream,
387
    EncryptOrDecryptBytesFn<PrivateKey>? decryptBytes,
388
    EncryptOrDecryptStreamFn<PrivateKey>? decryptStream,
389
    DeriveBitsFn<PrivateKey>? deriveBits,
390
    Iterable<Map<dynamic, dynamic>>? testData,
391
  }) {
392
    return TestRunner._(
1✔
393
      isSymmetric: true,
394
      algorithm: algorithm,
395
      importPrivateRawKey: importPrivateRawKey,
396
      exportPrivateRawKey: exportPrivateRawKey,
397
      importPrivatePkcs8Key: importPrivatePkcs8Key,
398
      exportPrivatePkcs8Key: exportPrivatePkcs8Key,
399
      importPrivateJsonWebKey: importPrivateJsonWebKey,
400
      exportPrivateJsonWebKey: exportPrivateJsonWebKey,
401
      generateKeyPair: (params) async {
×
402
        final k = await generateKey(params);
×
403
        return (privateKey: k, publicKey: k);
404
      },
405
      signBytes: signBytes,
406
      signStream: signStream,
407
      verifyBytes: verifyBytes,
408
      verifyStream: verifyStream,
409
      encryptBytes: encryptBytes,
410
      encryptStream: encryptStream,
411
      decryptBytes: decryptBytes,
412
      decryptStream: decryptStream,
413
      deriveBits: deriveBits == null
414
          ? null
415
          : (
1✔
416
              KeyPair<PrivateKey, PrivateKey> pair,
417
              int length,
418
              Map<String, dynamic> deriveParams,
419
            ) async {
420
              final a = await deriveBits(pair.privateKey, length, deriveParams);
1✔
421
              final b = await deriveBits(pair.publicKey, length, deriveParams);
1✔
422
              check(equalBytes(a, b), 'expected both keys to derive the same');
2✔
423
              return a;
424
            },
425
      testData: testData,
426
    );
427
  }
428

429
  void _validate() {
1✔
430
    // Check that we have verify if we have sign
431
    check((_signBytes != null) == (_verifyBytes != null));
4✔
432
    check((_signStream != null) == (_verifyStream != null));
4✔
433
    // If we can sign streams, we should also be able to sign bytes
434
    if (_signStream != null) {
1✔
435
      check(_signBytes != null);
2✔
436
    }
437

438
    // Check that we have decrypt if we have encrypt
439
    check((_encryptBytes != null) == (_decryptBytes != null));
4✔
440
    check((_encryptStream != null) == (_decryptStream != null));
4✔
441
    // If we can encrypt streams, we should also be able to encrypt bytes
442
    if (_encryptStream != null) {
1✔
443
      check(_encryptBytes != null);
2✔
444
    }
445

446
    // Must have one priate key import format.
447
    check(
1✔
448
      _importPrivateRawKey != null ||
1✔
449
          _importPrivatePkcs8Key != null ||
1✔
450
          _importPrivateJsonWebKey != null,
×
451
    );
452

453
    if (_isSymmetric) {
1✔
454
      // if symmetric we have no methods for importing public keys
455
      check(_importPublicRawKey == null);
2✔
456
      check(_importPublicSpkiKey == null);
2✔
457
      check(_importPublicJsonWebKey == null);
2✔
458
    } else {
459
      // Must have one public key import format.
460
      check(
1✔
461
        _importPublicRawKey != null ||
1✔
462
            _importPublicSpkiKey != null ||
1✔
463
            _importPublicJsonWebKey != null,
×
464
      );
465
    }
466

467
    // Export-only and import-only formats do not make sense
468
    check((_importPrivateRawKey != null) == (_exportPrivateRawKey != null));
4✔
469
    check((_importPrivatePkcs8Key != null) == (_exportPrivatePkcs8Key != null));
4✔
470
    check(
1✔
471
      (_importPrivateJsonWebKey != null) == (_exportPrivateJsonWebKey != null),
3✔
472
    );
473
    check((_importPublicRawKey != null) == (_exportPublicRawKey != null));
4✔
474
    check((_importPublicSpkiKey != null) == (_exportPublicSpkiKey != null));
4✔
475
    check(
1✔
476
      (_importPublicJsonWebKey != null) == (_exportPublicJsonWebKey != null),
3✔
477
    );
478

479
    // Check all test cases
480
    for (final data in _testData) {
2✔
481
      final c = _TestCase.fromJson(data);
1✔
482
      try {
483
        _validateTestCase(this, c);
1✔
484
      } catch (e) {
485
        log('Invalid test case: $c');
×
486
        rethrow;
487
      }
488
    }
489
  }
490

491
  Future<Map<String, dynamic>> generate({
×
492
    required Map<String, dynamic> generateKeyParams,
493
    required Map<String, dynamic> importKeyParams,
494
    Map<String, dynamic> signVerifyParams = const {},
495
    Map<String, dynamic> encryptDecryptParams = const {},
496
    Map<String, dynamic> deriveParams = const {},
497
    String plaintextTemplate = libsum,
498
    int minPlaintext = 8,
499
    int maxPlaintext = libsum.length,
500
    int minDeriveLength = 4,
501
    int maxDeriveLength = 512,
502
  }) async {
503
    check(minPlaintext <= maxPlaintext);
×
504
    check(maxPlaintext <= plaintextTemplate.length);
×
505
    final ts = DateTime.now().toIso8601String().split('.').first; // drop secs
×
506
    final name = 'generated at $ts';
×
507

508
    log('generating key-pair');
×
509
    final pair = await _generateKeyPair(generateKeyParams);
×
510
    final privateKey = pair.privateKey;
511
    final publicKey = pair.publicKey;
512
    check(privateKey != null);
×
513
    check(publicKey != null);
×
514

515
    List<int>? plaintext;
516
    if (_signBytes != null || _encryptBytes != null) {
×
517
      log('picking plaintext');
×
518
      final rng = Random.secure();
×
519
      final N = rng.nextInt(maxPlaintext - minPlaintext) + minPlaintext;
×
520
      final offset = rng.nextInt(plaintextTemplate.length - N);
×
521
      plaintext = utf8.encode(plaintextTemplate.substring(offset, offset + N));
×
522
    }
523

524
    List<int>? signature;
525
    final signBytes = _signBytes;
×
526
    if (signBytes != null) {
527
      log('creating signature');
×
528
      signature = await signBytes(
×
529
        pair.privateKey,
530
        plaintext!,
531
        signVerifyParams,
532
      );
533
    }
534

535
    List<int>? ciphertext;
536
    final encryptBytes = _encryptBytes;
×
537
    if (encryptBytes != null) {
538
      log('creating ciphertext');
×
539
      ciphertext = await encryptBytes(
×
540
        pair.publicKey,
541
        plaintext!,
542
        encryptDecryptParams,
543
      );
544
    }
545

546
    int? derivedLength;
547
    List<int>? derivedBits;
548
    final deriveBits = _deriveBits;
×
549
    if (deriveBits != null) {
550
      log('picking derivedLength');
×
551
      final rng = Random.secure();
×
552
      derivedLength = maxDeriveLength == minDeriveLength
×
553
          ? maxDeriveLength
554
          : rng.nextInt(maxDeriveLength - minDeriveLength) + minDeriveLength;
×
555

556
      log('creating derivedBits');
×
557
      derivedBits = await deriveBits(pair, derivedLength, deriveParams);
×
558
    }
559

560
    Future<T?> optionalCall<S, T>(Future<T> Function(S)? fn, S v) async =>
×
561
        fn != null ? await fn(v) : null;
×
562
    final c = _TestCase(
×
563
      name,
564
      generateKeyParams: null, // omit generateKeyParams
565
      privateRawKeyData: await optionalCall(_exportPrivateRawKey, privateKey),
×
566
      privatePkcs8KeyData: await optionalCall(
×
567
        _exportPrivatePkcs8Key,
×
568
        privateKey,
569
      ),
570
      privateJsonWebKeyData: await optionalCall(
×
571
        _exportPrivateJsonWebKey,
×
572
        privateKey,
573
      ),
574
      publicRawKeyData: await optionalCall(_exportPublicRawKey, publicKey),
×
575
      publicSpkiKeyData: await optionalCall(_exportPublicSpkiKey, publicKey),
×
576
      publicJsonWebKeyData: await optionalCall(
×
577
        _exportPublicJsonWebKey,
×
578
        publicKey,
579
      ),
580
      plaintext: plaintext,
581
      signature: signature,
582
      ciphertext: ciphertext,
583
      derivedBits: derivedBits,
584
      importKeyParams: importKeyParams,
585
      signVerifyParams: signVerifyParams,
586
      encryptDecryptParams: encryptDecryptParams,
587
      derivedLength: derivedLength,
588
      deriveParams: deriveParams,
589
    );
590

591
    // Log the generated test case. This makes it easy to copy/paste the test
592
    // case into test files.
593
    final json = const JsonEncoder.withIndent(
594
      '  ',
595
    ).convert(c.toJson()).replaceAll('\n', '\n| ');
×
596
    log('| $json');
×
597

598
    return c.toJson();
×
599
  }
600

601
  /// Get test cases from [testData].
602
  ///
603
  /// If no [testData] is given the `testData` given when the [TestRunner] was
604
  /// created will be used.
605
  ///
606
  /// Returns a list of tuples with test name and test function.
607
  List<({String name, Future<void> Function() test})> tests({
1✔
608
    Iterable<Map<dynamic, dynamic>>? testData,
609
  }) {
610
    final tests = <({String name, Future<void> Function() test})>[];
1✔
611
    testData ??= _testData;
1✔
612
    for (final data in testData) {
2✔
613
      final c = _TestCase.fromJson(data);
1✔
614

615
      _runTests(this, c, (String name, FutureOr<void> Function() fn) {
2✔
616
        tests.add((
1✔
617
          // Prefix test names
618
          name: '$algorithm: ${c.name} -- $name',
3✔
619
          test: () async {
1✔
620
            // Check BoringSSL error stack if running with dart:ffi
621
            await checkErrorStack(fn);
1✔
622
          },
623
        ));
624
      });
625
    }
626
    return tests;
627
  }
628
}
629

630
/// Wraps a [test] function such that calls must always be ordered, and that
631
/// any subsequent tests fails, if a previous test has failed.
632
void Function(String name, FutureOr Function() fn) _withTestDependency(
1✔
633
  void Function(String name, FutureOr Function() fn) test,
634
) {
635
  var count = 0;
636
  var next = 0;
637
  return (String name, FutureOr Function() fn) {
1✔
638
    final order = count++;
1✔
639
    test(name, () async {
2✔
640
      if (next != order) {
1✔
641
        check(false, 'Test dependency failed.');
×
642
      }
643
      await fn();
1✔
644
      next++;
1✔
645
    });
646
  };
647
}
648

649
/// Validate that the test case [c] is compatible with TestRunner [r].
650
void _validateTestCase<PrivateKey, PublicKey>(
1✔
651
  TestRunner<PrivateKey, PublicKey> r,
652
  _TestCase c,
653
) {
654
  final hasPrivateKey =
655
      c.privateRawKeyData != null ||
1✔
656
      c.privatePkcs8KeyData != null ||
1✔
657
      c.privateJsonWebKeyData != null;
1✔
658
  final hasPublicKey =
659
      c.publicRawKeyData != null ||
1✔
660
      c.publicSpkiKeyData != null ||
1✔
661
      c.publicJsonWebKeyData != null;
1✔
662

663
  // Test that we have keys to import or generate some.
664
  if (r._isSymmetric) {
1✔
665
    check(!hasPublicKey);
1✔
666
    check(
1✔
667
      c.generateKeyParams != null || hasPrivateKey,
1✔
668
      'A key must be generated or imported',
669
    );
670
  } else {
671
    check(
1✔
672
      c.generateKeyParams != null || (hasPrivateKey && hasPublicKey),
1✔
673
      'A key-pair must be generated or imported',
674
    );
675
  }
676

677
  check(
1✔
678
    c.generateKeyParams == null ||
1✔
679
        (c.signature == null && c.ciphertext == null && c.derivedBits == null),
3✔
680
    'Cannot verify signature/ciphertext/derivedBits for a generated key-pair',
681
  );
682
  check(
1✔
683
    c.plaintext != null || (r._signBytes == null && r._encryptBytes == null),
3✔
684
    'Cannot sign/encrypt without plaintext',
685
  );
686
  check(c.importKeyParams != null);
2✔
687
  check((c.signVerifyParams != null) == (r._signBytes != null));
4✔
688
  check((c.encryptDecryptParams != null) == (r._encryptBytes != null));
4✔
689
  check((c.deriveParams != null) == (r._deriveBits != null));
4✔
690
  if (c.signature != null) {
1✔
691
    check(r._signBytes != null);
2✔
692
  }
693
  if (c.ciphertext != null) {
1✔
694
    check(r._encryptBytes != null);
2✔
695
  }
696
  if (c.derivedBits != null) {
1✔
697
    check(r._deriveBits != null);
2✔
698
  }
699
  if (r._deriveBits != null) {
1✔
700
    check(c.derivedLength != null);
2✔
701
  }
702

703
  // Check that data matches the methods we have in the runner.
704
  check(r._importPrivateRawKey != null || c.privateRawKeyData == null);
3✔
705
  check(r._importPrivatePkcs8Key != null || c.privatePkcs8KeyData == null);
3✔
706
  check(r._importPrivateJsonWebKey != null || c.privateJsonWebKeyData == null);
3✔
707
  check(r._importPublicRawKey != null || c.publicRawKeyData == null);
3✔
708
  check(r._importPublicSpkiKey != null || c.publicSpkiKeyData == null);
3✔
709
  check(r._importPublicJsonWebKey != null || c.publicJsonWebKeyData == null);
3✔
710
}
711

712
void _runTests<PrivateKey, PublicKey>(
1✔
713
  TestRunner<PrivateKey, PublicKey> r,
714
  _TestCase c,
715
  void Function(String name, FutureOr Function() fn) test,
716
) {
717
  test = _withTestDependency(test);
1✔
718

719
  test('validate test case', () => _validateTestCase(r, c));
3✔
720

721
  try {
722
    _validateTestCase(r, c);
1✔
723
  } catch (_) {
724
    // Don't register additional tests if the test-case is invalid!
725
    return;
726
  }
727

728
  //------------------------------ Import or generate key-pair for testing
729

730
  // Store publicKey and privateKey for use in later tests.
731
  // If [_isSymmetric] is true, we still import the public and assign the
732
  // private key to the public key.
733
  PublicKey? publicKey;
734
  PrivateKey? privateKey;
735

736
  if (c.generateKeyParams != null) {
1✔
737
    test('generateKeyPair()', () async {
2✔
738
      final pair = await r._generateKeyPair(c.generateKeyParams!);
3✔
739
      check(pair.privateKey != null);
1✔
740
      check(pair.publicKey != null);
1✔
741
      publicKey = pair.publicKey;
742
      privateKey = pair.privateKey;
743
    });
744
  } else {
745
    test('import key-pair', () async {
2✔
746
      // Get a privateKey
747
      if (c.privateRawKeyData != null) {
1✔
748
        privateKey = await r._importPrivateRawKey!(
2✔
749
          c.privateRawKeyData!,
1✔
750
          c.importKeyParams!,
1✔
751
        );
752
        check(privateKey != null);
1✔
753
      } else if (c.privatePkcs8KeyData != null) {
1✔
754
        privateKey = await r._importPrivatePkcs8Key!(
2✔
755
          c.privatePkcs8KeyData!,
1✔
756
          c.importKeyParams!,
1✔
757
        );
758
        check(privateKey != null);
1✔
759
      } else if (c.privateJsonWebKeyData != null) {
1✔
760
        privateKey = await r._importPrivateJsonWebKey!(
2✔
761
          c.privateJsonWebKeyData!,
1✔
762
          c.importKeyParams!,
1✔
763
        );
764
        check(privateKey != null);
1✔
765
      } else {
766
        check(false, 'missing private key for importing');
×
767
      }
768

769
      // Get a publicKey
770
      if (r._isSymmetric) {
1✔
771
        // If symmetric algorithm we just use the private key.
772
        publicKey = privateKey as PublicKey;
773
      } else if (c.publicRawKeyData != null) {
1✔
774
        publicKey = await r._importPublicRawKey!(
2✔
775
          c.publicRawKeyData!,
1✔
776
          c.importKeyParams!,
1✔
777
        );
778
        check(publicKey != null);
1✔
779
      } else if (c.publicSpkiKeyData != null) {
1✔
780
        publicKey = await r._importPublicSpkiKey!(
2✔
781
          c.publicSpkiKeyData!,
1✔
782
          c.importKeyParams!,
1✔
783
        );
784
        check(publicKey != null);
1✔
785
      } else if (c.publicJsonWebKeyData != null) {
×
786
        publicKey = await r._importPublicJsonWebKey!(
×
787
          c.publicJsonWebKeyData!,
×
788
          c.importKeyParams!,
×
789
        );
790
        check(publicKey != null);
×
791
      } else {
792
        check(false, 'missing public key for importing');
×
793
      }
794
    });
795
  }
796

797
  //------------------------------ Create a signature for testing
798

799
  // Ensure that we have a signature for use in later test cases
800
  List<int>? signature;
801

802
  if (r._signBytes != null) {
1✔
803
    if (c.signature != null) {
1✔
804
      signature = c.signature;
1✔
805
    } else {
806
      test('create signature', () async {
2✔
807
        signature = await r._signBytes(
2✔
808
          privateKey as PrivateKey,
809
          c.plaintext!,
1✔
810
          c.signVerifyParams!,
1✔
811
        );
812
      });
813
    }
814

815
    test('verify signature', () async {
2✔
816
      check(
1✔
817
        await r._verifyBytes!(
2✔
818
          publicKey as PublicKey,
819
          signature!,
820
          c.plaintext!,
1✔
821
          c.signVerifyParams!,
1✔
822
        ),
823
        'failed to verify signature',
824
      );
825
    });
826
  }
827

828
  //------------------------------ Create a ciphertext for testing
829
  List<int>? ciphertext;
830

831
  if (r._encryptBytes != null) {
1✔
832
    if (c.ciphertext != null) {
1✔
833
      ciphertext = c.ciphertext!;
1✔
834
    } else {
835
      test('create ciphertext', () async {
2✔
836
        ciphertext = await r._encryptBytes(
2✔
837
          publicKey as PublicKey,
838
          c.plaintext!,
1✔
839
          c.encryptDecryptParams!,
1✔
840
        );
841
      });
842
    }
843

844
    test('decrypt ciphertext', () async {
2✔
845
      final text = await r._decryptBytes!(
2✔
846
        privateKey as PrivateKey,
847
        ciphertext!,
848
        c.encryptDecryptParams!,
1✔
849
      );
850
      check(equalBytes(text, c.plaintext!), 'failed to decrypt ciphertext');
3✔
851
    });
852
  }
853

854
  //------------------------------ Create derivedBits for testing
855

856
  // Ensure that we have derivedBits for use in later test cases
857
  List<int>? derivedBits;
858

859
  if (r._deriveBits != null) {
1✔
860
    if (c.derivedBits != null) {
1✔
861
      derivedBits = c.derivedBits!;
1✔
862
    } else {
863
      test('create derivedBits', () async {
×
864
        derivedBits = await r._deriveBits(
×
865
          (
866
            privateKey: privateKey as PrivateKey,
867
            publicKey: publicKey as PublicKey,
868
          ),
869
          c.derivedLength!,
×
870
          c.deriveParams!,
×
871
        );
872
      });
873
    }
874

875
    test('validated derivedBits', () async {
2✔
876
      final derived = await r._deriveBits(
2✔
877
        (
878
          privateKey: privateKey as PrivateKey,
879
          publicKey: publicKey as PublicKey,
880
        ),
881
        c.derivedLength!,
1✔
882
        c.deriveParams!,
1✔
883
      );
884
      check(
1✔
885
        equalBytes(derived, derivedBits!),
1✔
886
        'failed to derivedBits are not consistent',
887
      );
888
    });
889
  }
890

891
  //------------------------------ Utilities for testing
892

893
  //// Utility function to verify [sig] using [key].
894
  Future<void> checkVerifyBytes(PublicKey key, List<int>? sig) async {
1✔
895
    check(sig != null, 'signature cannot be null');
1✔
896
    check(
1✔
897
      await r._verifyBytes!(key, sig!, c.plaintext!, c.signVerifyParams!),
4✔
898
      'failed to verify signature',
899
    );
900
    check(
1✔
901
      !await r._verifyBytes(
2✔
902
        key,
903
        flipFirstBits(sig),
1✔
904
        c.plaintext!,
1✔
905
        c.signVerifyParams!,
1✔
906
      ),
907
      'verified an invalid signature',
908
    );
909
    if (c.plaintext!.isNotEmpty) {
2✔
910
      check(
1✔
911
        !await r._verifyBytes(
2✔
912
          key,
913
          sig,
914
          flipFirstBits(c.plaintext!),
2✔
915
          c.signVerifyParams!,
1✔
916
        ),
917
        'verified an invalid message',
918
      );
919
    }
920
  }
921

922
  /// Utility function to decrypt [ctext] using [key].
923
  Future<void> checkDecryptBytes(PrivateKey key, List<int> ctext) async {
1✔
924
    final text = await r._decryptBytes!(key, ctext, c.encryptDecryptParams!);
3✔
925
    check(equalBytes(text, c.plaintext!), 'failed to decrypt ciphertext');
3✔
926

927
    if (ctext.isNotEmpty) {
1✔
928
      // If ciphertext is mangled some primitives like AES-GCM must throw
929
      // others may return garbled plaintext.
930
      try {
931
        final invalidText = await r._decryptBytes(
2✔
932
          key,
933
          flipFirstBits(ctext),
1✔
934
          c.encryptDecryptParams!,
1✔
935
        );
936
        check(
1✔
937
          !equalBytes(invalidText, c.plaintext!),
2✔
938
          'decrypted an invalid ciphertext',
939
        );
940
      } on OperationError catch (e) {
1✔
941
        check(e.toString() != '', 'expected some explanation');
3✔
942
      }
943
    }
944
  }
945

946
  /// Check if [signature] is sane.
947
  Future<void> checkSignature(List<int>? signature) async {
1✔
948
    check(signature != null, 'signature is null');
1✔
949
    check(signature!.isNotEmpty, 'signature is empty');
2✔
950
    await checkVerifyBytes(publicKey as PublicKey, signature);
1✔
951
  }
952

953
  /// Check if [ciphertext] is sane.
954
  Future<void> checkCipherText(List<int>? ctext) async {
1✔
955
    check(ctext != null, 'ciphtertext is null');
1✔
956
    check(ctext!.isNotEmpty, 'ciphtertext is empty');
2✔
957
    await checkDecryptBytes(privateKey as PrivateKey, ctext);
1✔
958
  }
959

960
  /// Check if [derived] is correct.
961
  void checkDerivedBits(List<int>? derived) async {
1✔
962
    check(derived != null, 'derivedBits is null');
1✔
963
    check(derived!.isNotEmpty, 'derivedBits is empty');
2✔
964
    check(equalBytes(derived, derivedBits!), 'derivedBits is not consistent');
2✔
965
  }
966

967
  /// Check if [publicKey] is sane.
968
  Future<void> checkPublicKey(PublicKey? publicKey) async {
1✔
969
    check(publicKey != null, 'publicKey is null');
1✔
970
    if (r._signBytes != null) {
1✔
971
      await checkVerifyBytes(publicKey as PublicKey, signature);
1✔
972
    }
973
    if (r._encryptBytes != null) {
1✔
974
      final ctext = await r._encryptBytes(
2✔
975
        publicKey as PublicKey,
976
        c.plaintext!,
1✔
977
        c.encryptDecryptParams!,
1✔
978
      );
979
      await checkCipherText(ctext);
1✔
980
    }
981
    if (r._deriveBits != null) {
1✔
982
      final derived = await r._deriveBits(
2✔
983
        (
984
          privateKey: privateKey as PrivateKey,
985
          publicKey: publicKey as PublicKey,
986
        ),
987
        c.derivedLength!,
1✔
988
        c.deriveParams!,
1✔
989
      );
990
      checkDerivedBits(derived);
1✔
991
    }
992
  }
993

994
  /// Check if [privateKey] is sane.
995
  Future<void> checkPrivateKey(PrivateKey? privateKey) async {
1✔
996
    check(privateKey != null, 'privateKey is null');
1✔
997
    if (r._signBytes != null) {
1✔
998
      final sig = await r._signBytes(
2✔
999
        privateKey as PrivateKey,
1000
        c.plaintext!,
1✔
1001
        c.signVerifyParams!,
1✔
1002
      );
1003
      await checkSignature(sig);
1✔
1004
    }
1005
    if (r._encryptBytes != null) {
1✔
1006
      await checkDecryptBytes(privateKey as PrivateKey, ciphertext!);
1✔
1007
    }
1008
    if (r._deriveBits != null) {
1✔
1009
      final derived = await r._deriveBits(
2✔
1010
        (
1011
          privateKey: privateKey as PrivateKey,
1012
          publicKey: publicKey as PublicKey,
1013
        ),
1014
        c.derivedLength!,
1✔
1015
        c.deriveParams!,
1✔
1016
      );
1017
      checkDerivedBits(derived);
1✔
1018
    }
1019
  }
1020

1021
  //------------------------------ Test import public key
1022

1023
  if (c.publicRawKeyData != null) {
1✔
1024
    assert(!r._isSymmetric && r._importPublicRawKey != null);
3✔
1025

1026
    test('importPublicRawKey()', () async {
2✔
1027
      final key = await r._importPublicRawKey!(
2✔
1028
        c.publicRawKeyData!,
1✔
1029
        c.importKeyParams!,
1✔
1030
      );
1031
      await checkPublicKey(key);
1✔
1032
    });
1033
  }
1034

1035
  if (c.publicSpkiKeyData != null) {
1✔
1036
    assert(!r._isSymmetric && r._importPublicSpkiKey != null);
3✔
1037

1038
    test('importPublicSpkiKey()', () async {
2✔
1039
      final key = await r._importPublicSpkiKey!(
2✔
1040
        c.publicSpkiKeyData!,
1✔
1041
        c.importKeyParams!,
1✔
1042
      );
1043
      await checkPublicKey(key);
1✔
1044
    });
1045
  }
1046

1047
  if (c.publicJsonWebKeyData != null) {
1✔
1048
    assert(!r._isSymmetric && r._importPublicJsonWebKey != null);
3✔
1049

1050
    test('importPublicJsonWebKey()', () async {
2✔
1051
      final key = await r._importPublicJsonWebKey!(
2✔
1052
        c.publicJsonWebKeyData!,
1✔
1053
        c.importKeyParams!,
1✔
1054
      );
1055
      await checkPublicKey(key);
1✔
1056
    });
1057
  }
1058

1059
  //------------------------------ Test import private key
1060

1061
  if (c.privateRawKeyData != null) {
1✔
1062
    test('importPrivateRawKey()', () async {
2✔
1063
      final key = await r._importPrivateRawKey!(
2✔
1064
        c.privateRawKeyData!,
1✔
1065
        c.importKeyParams!,
1✔
1066
      );
1067
      await checkPrivateKey(key);
1✔
1068
    });
1069
  }
1070

1071
  if (c.privatePkcs8KeyData != null) {
1✔
1072
    test('importPrivatePkcs8Key()', () async {
2✔
1073
      final key = await r._importPrivatePkcs8Key!(
2✔
1074
        c.privatePkcs8KeyData!,
1✔
1075
        c.importKeyParams!,
1✔
1076
      );
1077
      await checkPrivateKey(key);
1✔
1078
    });
1079
  }
1080

1081
  if (c.privateJsonWebKeyData != null) {
1✔
1082
    test('importPrivateJsonWebKey()', () async {
2✔
1083
      final key = await r._importPrivateJsonWebKey!(
2✔
1084
        c.privateJsonWebKeyData!,
1✔
1085
        c.importKeyParams!,
1✔
1086
      );
1087
      await checkPrivateKey(key);
1✔
1088
    });
1089
  }
1090

1091
  //------------------------------ Test signing
1092

1093
  if (r._signBytes != null) {
1✔
1094
    test('signBytes(plaintext)', () async {
2✔
1095
      final sig = await r._signBytes(
2✔
1096
        privateKey as PrivateKey,
1097
        c.plaintext!,
1✔
1098
        c.signVerifyParams!,
1✔
1099
      );
1100
      await checkSignature(sig);
1✔
1101
    });
1102
  }
1103

1104
  if (r._signStream != null) {
1✔
1105
    test('signStream(plaintext)', () async {
2✔
1106
      final sig = await r._signStream(
2✔
1107
        privateKey as PrivateKey,
1108
        Stream.value(c.plaintext!),
2✔
1109
        c.signVerifyParams!,
1✔
1110
      );
1111
      await checkSignature(sig);
1✔
1112
    });
1113

1114
    test('signStream(fibChunked(plaintext))', () async {
2✔
1115
      final sig = await r._signStream(
2✔
1116
        privateKey as PrivateKey,
1117
        fibonacciChunkedStream(c.plaintext!),
2✔
1118
        c.signVerifyParams!,
1✔
1119
      );
1120
      await checkSignature(sig);
1✔
1121
    });
1122
  }
1123

1124
  //------------------------------ Test verification
1125

1126
  if (r._verifyBytes != null) {
1✔
1127
    test('verifyBytes(signature, plaintext)', () async {
2✔
1128
      check(
1✔
1129
        await r._verifyBytes(
2✔
1130
          publicKey as PublicKey,
1131
          signature!,
1132
          c.plaintext!,
1✔
1133
          c.signVerifyParams!,
1✔
1134
        ),
1135
        'failed to verify signature',
1136
      );
1137

1138
      check(
1✔
1139
        !await r._verifyBytes(
2✔
1140
          publicKey as PublicKey,
1141
          flipFirstBits(signature!),
1✔
1142
          c.plaintext!,
1✔
1143
          c.signVerifyParams!,
1✔
1144
        ),
1145
        'verified an invalid signature',
1146
      );
1147

1148
      if (c.plaintext!.isNotEmpty) {
2✔
1149
        check(
1✔
1150
          !await r._verifyBytes(
2✔
1151
            publicKey as PublicKey,
1152
            signature!,
1153
            flipFirstBits(c.plaintext!),
2✔
1154
            c.signVerifyParams!,
1✔
1155
          ),
1156
          'verified an invalid message',
1157
        );
1158
      }
1159
    });
1160
  }
1161

1162
  if (r._verifyStream != null) {
1✔
1163
    test('verifyStream(signature, Stream.value(plaintext))', () async {
2✔
1164
      check(
1✔
1165
        await r._verifyStream(
2✔
1166
          publicKey as PublicKey,
1167
          signature!,
1168
          Stream.value(c.plaintext!),
2✔
1169
          c.signVerifyParams!,
1✔
1170
        ),
1171
        'failed to verify signature',
1172
      );
1173

1174
      check(
1✔
1175
        !await r._verifyStream(
2✔
1176
          publicKey as PublicKey,
1177
          flipFirstBits(signature!),
1✔
1178
          Stream.value(c.plaintext!),
2✔
1179
          c.signVerifyParams!,
1✔
1180
        ),
1181
        'verified an invalid signature',
1182
      );
1183

1184
      if (c.plaintext!.isNotEmpty) {
2✔
1185
        check(
1✔
1186
          !await r._verifyStream(
2✔
1187
            publicKey as PublicKey,
1188
            signature!,
1189
            Stream.value(flipFirstBits(c.plaintext!)),
3✔
1190
            c.signVerifyParams!,
1✔
1191
          ),
1192
          'verified an invalid message',
1193
        );
1194
      }
1195
    });
1196

1197
    test('verifyStream(signature, fibChunkedStream(plaintext))', () async {
2✔
1198
      check(
1✔
1199
        await r._verifyStream(
2✔
1200
          publicKey as PublicKey,
1201
          signature!,
1202
          fibonacciChunkedStream(c.plaintext!),
2✔
1203
          c.signVerifyParams!,
1✔
1204
        ),
1205
        'failed to verify signature',
1206
      );
1207

1208
      check(
1✔
1209
        !await r._verifyStream(
2✔
1210
          publicKey as PublicKey,
1211
          flipFirstBits(signature!),
1✔
1212
          fibonacciChunkedStream(c.plaintext!),
2✔
1213
          c.signVerifyParams!,
1✔
1214
        ),
1215
        'verified an invalid signature',
1216
      );
1217

1218
      if (c.plaintext!.isNotEmpty) {
2✔
1219
        check(
1✔
1220
          !await r._verifyStream(
2✔
1221
            publicKey as PublicKey,
1222
            signature!,
1223
            fibonacciChunkedStream(flipFirstBits(c.plaintext!)),
3✔
1224
            c.signVerifyParams!,
1✔
1225
          ),
1226
          'verified an invalid message',
1227
        );
1228
      }
1229
    });
1230
  }
1231

1232
  //------------------------------ Test encryption
1233

1234
  if (r._encryptBytes != null) {
1✔
1235
    test('encryptBytes(plaintext)', () async {
2✔
1236
      final ctext = await r._encryptBytes(
2✔
1237
        publicKey as PublicKey,
1238
        c.plaintext!,
1✔
1239
        c.encryptDecryptParams!,
1✔
1240
      );
1241
      await checkCipherText(ctext);
1✔
1242
    });
1243
  }
1244

1245
  if (r._encryptStream != null) {
1✔
1246
    test('encryptStream(plaintext)', () async {
2✔
1247
      final ctext = await bufferStream(
1✔
1248
        r._encryptStream(
2✔
1249
          publicKey as PublicKey,
1250
          Stream.value(c.plaintext!),
2✔
1251
          c.encryptDecryptParams!,
1✔
1252
        ),
1253
      );
1254
      await checkCipherText(ctext);
1✔
1255
    });
1256

1257
    test('encryptStream(fibChunked(plaintext))', () async {
2✔
1258
      final ctext = await bufferStream(
1✔
1259
        r._encryptStream(
2✔
1260
          publicKey as PublicKey,
1261
          fibonacciChunkedStream(c.plaintext!),
2✔
1262
          c.encryptDecryptParams!,
1✔
1263
        ),
1264
      );
1265
      await checkCipherText(ctext);
1✔
1266
    });
1267
  }
1268

1269
  //------------------------------ Test decryption
1270

1271
  if (r._decryptBytes != null) {
1✔
1272
    test('decryptBytes(plaintext)', () async {
2✔
1273
      final text = await r._decryptBytes(
2✔
1274
        privateKey as PrivateKey,
1275
        ciphertext!,
1276
        c.encryptDecryptParams!,
1✔
1277
      );
1278
      check(equalBytes(text, c.plaintext!), 'failed to decrypt signature');
3✔
1279

1280
      if (ciphertext!.isNotEmpty) {
1✔
1281
        // If ciphertext is mangled some primitives like AES-GCM must throw
1282
        // others may return garbled plaintext.
1283
        try {
1284
          final text2 = await r._decryptBytes(
2✔
1285
            privateKey as PrivateKey,
1286
            flipFirstBits(ciphertext!),
1✔
1287
            c.encryptDecryptParams!,
1✔
1288
          );
1289
          check(
1✔
1290
            !equalBytes(text2, c.plaintext!),
2✔
1291
            'decrypted an invalid ciphertext correctly',
1292
          );
1293
        } on OperationError catch (e) {
1✔
1294
          check(e.toString() != '', 'expected some explanation');
3✔
1295
        }
1296
      }
1297
    });
1298
  }
1299

1300
  if (r._decryptStream != null) {
1✔
1301
    test('decryptStream(Stream.value(ciphertext))', () async {
2✔
1302
      final text = await bufferStream(
1✔
1303
        r._decryptStream(
2✔
1304
          privateKey as PrivateKey,
1305
          Stream.value(ciphertext!),
1✔
1306
          c.encryptDecryptParams!,
1✔
1307
        ),
1308
      );
1309
      check(equalBytes(text, c.plaintext!), 'failed to decrypt signature');
3✔
1310

1311
      if (ciphertext!.isNotEmpty) {
1✔
1312
        // If ciphertext is mangled some primitives like AES-GCM must throw
1313
        // others may return garbled plaintext.
1314
        try {
1315
          final text2 = await bufferStream(
1✔
1316
            r._decryptStream(
2✔
1317
              privateKey as PrivateKey,
1318
              Stream.value(flipFirstBits(ciphertext!)),
2✔
1319
              c.encryptDecryptParams!,
1✔
1320
            ),
1321
          );
1322
          check(
1✔
1323
            !equalBytes(text2, c.plaintext!),
2✔
1324
            'decrypted an invalid ciphertext correctly',
1325
          );
1326
        } on OperationError catch (e) {
1✔
1327
          check(e.toString() != '', 'expected some explanation');
3✔
1328
        }
1329
      }
1330
    });
1331

1332
    test('decryptStream(fibChunkedStream(ciphertext))', () async {
2✔
1333
      final text = await bufferStream(
1✔
1334
        r._decryptStream(
2✔
1335
          privateKey as PrivateKey,
1336
          fibonacciChunkedStream(ciphertext!),
1✔
1337
          c.encryptDecryptParams!,
1✔
1338
        ),
1339
      );
1340
      check(equalBytes(text, c.plaintext!), 'failed to decrypt signature');
3✔
1341

1342
      if (ciphertext!.isNotEmpty) {
1✔
1343
        // If ciphertext is mangled some primitives like AES-GCM must throw
1344
        // others may return garbled plaintext.
1345
        try {
1346
          final text2 = await bufferStream(
1✔
1347
            r._decryptStream(
2✔
1348
              privateKey as PrivateKey,
1349
              fibonacciChunkedStream(flipFirstBits(ciphertext!)),
2✔
1350
              c.encryptDecryptParams!,
1✔
1351
            ),
1352
          );
1353
          check(
1✔
1354
            !equalBytes(text2, c.plaintext!),
2✔
1355
            'decrypted an invalid ciphertext correctly',
1356
          );
1357
        } on OperationError catch (e) {
1✔
1358
          check(e.toString() != '', 'expected some explanation');
3✔
1359
        }
1360
      }
1361
    });
1362
  }
1363

1364
  //------------------------------ Test derivedBits
1365
  if (r._deriveBits != null) {
1✔
1366
    test('deriveBits', () async {
2✔
1367
      final derived = await r._deriveBits(
2✔
1368
        (
1369
          privateKey: privateKey as PrivateKey,
1370
          publicKey: publicKey as PublicKey,
1371
        ),
1372
        c.derivedLength!,
1✔
1373
        c.deriveParams!,
1✔
1374
      );
1375
      checkDerivedBits(derived);
1✔
1376
    });
1377
  }
1378

1379
  //------------------------------ export/import private key
1380
  if (r._exportPrivateRawKey != null) {
1✔
1381
    test('export/import raw private key', () async {
2✔
1382
      final keyData = await r._exportPrivateRawKey(privateKey as PrivateKey);
2✔
1383
      check(keyData.isNotEmpty, 'exported key is empty');
2✔
1384

1385
      final key = await r._importPrivateRawKey!(keyData, c.importKeyParams!);
3✔
1386
      await checkPrivateKey(key);
1✔
1387
    });
1388
  }
1389

1390
  if (r._exportPrivatePkcs8Key != null) {
1✔
1391
    test('export/import pkcs8 private key', () async {
2✔
1392
      final keyData = await r._exportPrivatePkcs8Key(privateKey as PrivateKey);
2✔
1393
      check(keyData.isNotEmpty, 'exported key is empty');
2✔
1394

1395
      final key = await r._importPrivatePkcs8Key!(keyData, c.importKeyParams!);
3✔
1396
      await checkPrivateKey(key);
1✔
1397
    });
1398
  }
1399

1400
  if (r._exportPrivateJsonWebKey != null) {
1✔
1401
    test('export/import jwk private key', () async {
2✔
1402
      final jwk = await r._exportPrivateJsonWebKey(privateKey as PrivateKey);
2✔
1403
      check(jwk.isNotEmpty, 'exported key is empty');
2✔
1404

1405
      final key = await r._importPrivateJsonWebKey!(jwk, c.importKeyParams!);
3✔
1406
      await checkPrivateKey(key);
1✔
1407
    });
1408
  }
1409

1410
  //------------------------------ export/import public key
1411

1412
  if (r._exportPublicRawKey != null) {
1✔
1413
    assert(!r._isSymmetric && r._importPublicRawKey != null);
3✔
1414

1415
    test('export/import raw public key', () async {
2✔
1416
      final keyData = await r._exportPublicRawKey(publicKey as PublicKey);
2✔
1417
      check(keyData.isNotEmpty, 'exported key is empty');
2✔
1418

1419
      final key = await r._importPublicRawKey!(keyData, c.importKeyParams!);
3✔
1420
      await checkPublicKey(key);
1✔
1421
    });
1422
  }
1423

1424
  if (r._exportPublicSpkiKey != null) {
1✔
1425
    assert(!r._isSymmetric && r._importPublicSpkiKey != null);
3✔
1426

1427
    test('export/import pkcs8 public key', () async {
2✔
1428
      final keyData = await r._exportPublicSpkiKey(publicKey as PublicKey);
2✔
1429
      check(keyData.isNotEmpty, 'exported key is empty');
2✔
1430

1431
      final key = await r._importPublicSpkiKey!(keyData, c.importKeyParams!);
3✔
1432
      await checkPublicKey(key);
1✔
1433
    });
1434
  }
1435

1436
  if (r._exportPublicJsonWebKey != null) {
1✔
1437
    assert(!r._isSymmetric && r._importPublicJsonWebKey != null);
3✔
1438

1439
    test('export/import jwk public key', () async {
2✔
1440
      final jwk = await r._exportPublicJsonWebKey(publicKey as PublicKey);
2✔
1441
      check(jwk.isNotEmpty, 'exported key is empty');
2✔
1442

1443
      final key = await r._importPublicJsonWebKey!(jwk, c.importKeyParams!);
3✔
1444
      await checkPublicKey(key);
1✔
1445
    });
1446
  }
1447

1448
  //------------------------------ validate the generated test case
1449

1450
  if (c.generateKeyParams != null) {
1✔
1451
    test('validate generated test case', () async {
2✔
1452
      Future<T?> optionalCall<S, T>(Future<T> Function(S)? fn, S v) async =>
1✔
1453
          fn != null ? await fn(v) : null;
1✔
1454
      final date = DateTime.now()
1✔
1455
          .toIso8601String()
1✔
1456
          .split('T')
1✔
1457
          .first; // drop time
1✔
1458
      final generated = _TestCase(
1✔
1459
        '${c.name} generated on $detectedRuntime at $date',
3✔
1460
        generateKeyParams: null, // omit generateKeyParams
1461
        privateRawKeyData: await optionalCall(
1✔
1462
          r._exportPrivateRawKey,
1✔
1463
          privateKey as PrivateKey,
1464
        ),
1465
        privatePkcs8KeyData: await optionalCall(
1✔
1466
          r._exportPrivatePkcs8Key,
1✔
1467
          privateKey as PrivateKey,
1468
        ),
1469
        privateJsonWebKeyData: await optionalCall(
1✔
1470
          r._exportPrivateJsonWebKey,
1✔
1471
          privateKey as PrivateKey,
1472
        ),
1473
        publicRawKeyData: await optionalCall(
1✔
1474
          r._exportPublicRawKey,
1✔
1475
          publicKey as PublicKey,
1476
        ),
1477
        publicSpkiKeyData: await optionalCall(
1✔
1478
          r._exportPublicSpkiKey,
1✔
1479
          publicKey as PublicKey,
1480
        ),
1481
        publicJsonWebKeyData: await optionalCall(
1✔
1482
          r._exportPublicJsonWebKey,
1✔
1483
          publicKey as PublicKey,
1484
        ),
1485
        plaintext: c.plaintext,
1✔
1486
        signature: signature,
1487
        ciphertext: ciphertext,
1488
        derivedBits: derivedBits,
1489
        importKeyParams: c.importKeyParams,
1✔
1490
        signVerifyParams: c.signVerifyParams,
1✔
1491
        encryptDecryptParams: c.encryptDecryptParams,
1✔
1492
        derivedLength: c.derivedLength,
1✔
1493
        deriveParams: c.deriveParams,
1✔
1494
      );
1495

1496
      // Validate that the generated test case is sane.
1497
      _validateTestCase(r, generated);
1✔
1498

1499
      // Log the generated test case. This makes it easy to copy/paste the test
1500
      // case into test files.
1501
      dump(generated.toJson());
2✔
1502
    });
1503
  }
1504
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc