Skip to content

Commit d629f58

Browse files
authored
More docs (#50)
* Docs for RSASSA * Better docs * minor template usage * Add TODO for documentation of streaming limitations on web * Better docs * Documentation for RSA primitives * More deduplication with templates in documentation * Improve docs consistency * More deduplication using templates * docs for HMAC and Hash * Documentation for PBKDF2 * Document HKDF
1 parent 0d65501 commit d629f58

File tree

9 files changed

+1386
-105
lines changed

9 files changed

+1386
-105
lines changed

lib/src/testing/webcrypto/pbkdf2.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ void main() {
7676
runner.runTests();
7777
}
7878

79+
// TODO: Augments tests with test vectors from: https://datatracker.ietf.org/doc/html/rfc6070
80+
7981
// Allow single quotes for hardcoded testData written as JSON:
8082
// ignore_for_file: prefer_single_quotes
8183
final _testData = [

lib/src/webcrypto/webcrypto.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
/// TODO: Document that methods accepting / returning streams are NOT streaming
16+
/// when running in the browser. This is because the Web Cryptography API
17+
/// supported by browser do not support streaming. Hence, one should
18+
/// expect that the contents of these streams is buffered when operating
19+
/// in the browser.
20+
/// This could be documented for each method or at library level.
1521
library webcrypto;
1622

1723
import 'package:meta/meta.dart';

lib/src/webcrypto/webcrypto.digest.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ part of webcrypto;
2626
/// For a guidance on choice of hash function see
2727
/// [NIST SP 800-57 Part 1 Rev 5][1].
2828
///
29-
/// **WARNING:** Custom implementations of this class cannot be passed to
29+
/// Notice custom implementations of this class cannot be passed to
3030
/// to other methods in this library.
3131
///
3232
/// [1]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-57pt1r5.pdf

lib/src/webcrypto/webcrypto.hkdf.dart

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,76 @@
1414

1515
part of webcrypto;
1616

17+
/// HKDF secret key (or password) for key derivation.
18+
///
19+
/// An [HkdfSecretKey] instance holds a secret key for key derivation using
20+
/// the _HMAC-based Key Derivation Function_ specified in [RFC 5869][1] using
21+
/// a [Hash] function specified in the [deriveBits] method.
22+
///
23+
/// A [HkdfSecretKey] can be imported using [importRawKey].
24+
///
25+
/// {@template HkdfSecretKey:example}
26+
/// **Example**
27+
/// ```
28+
/// import 'dart:convert' show utf8, base64;
29+
/// import 'package:webcrypto/webcrypto.dart';
30+
///
31+
/// // Provide a password to be used for key derivation
32+
/// final key = await HkdfSecretKey.importRawKey(utf8.decode(
33+
/// 'my-password-in-plain-text',
34+
/// ));
35+
///
36+
/// // Derive a key from password
37+
/// final derivedKey = await HkdfSecretKey.deriveBits(
38+
/// 256, // number of bits to derive.
39+
/// Hash.sha256,
40+
/// utf8.decode('unique salt'),
41+
/// utf8.decode('creating derivedKey in example'),
42+
/// );
43+
///
44+
/// // Print the derived key, this could also be used as basis for other new
45+
/// // symmetric cryptographic keys.
46+
/// print(base64.encode(derivedKey));
47+
/// ```
48+
/// {@endtemplate}
49+
///
50+
/// [1]: https://tools.ietf.org/html/rfc5869
51+
// TODO: It might be wise to use a random salt, then suggest that the non-secret
52+
// salt is stored or exchanged...
1753
@sealed
1854
abstract class HkdfSecretKey {
1955
HkdfSecretKey._(); // keep the constructor private.
2056

57+
/// Import [HkdfSecretKey] from raw [keyData].
58+
///
59+
/// Creates a [HkdfSecretKey] for key derivation using [keyData].
60+
///
61+
/// {@macro HkdfSecretKey:example}
2162
static Future<HkdfSecretKey> importRawKey(List<int> keyData) {
2263
return impl.hkdfSecretKey_importRawKey(keyData);
2364
}
2465

66+
/// Derive key from [salt], [info] and password specified as `keyData` in
67+
/// [importRawKey].
68+
///
69+
/// The [length] of the key to be derived must be specified in bits as a
70+
/// multiple of 8.
71+
///
72+
/// Using sufficiently large random [salt] makes hard for an adversary to
73+
/// precompute the most likely keys using a dictionary of common passwords.
74+
/// The [salt] also serves make the same password have yield different keys.
75+
/// For details on [salt] see [RFC 5869 section 3.1][1].
76+
///
77+
/// The [info] serves to bind the derived key to an application specific
78+
/// context. For example, if the same `keyData` is used to derive keys for
79+
/// different use cases, then using a different [info] for each purpose
80+
/// ensures that the derived keys are different.
81+
/// For details on [info] see [RFC 5869 section 3.2][2].
82+
///
83+
/// {@macro HkdfSecretKey:example}
84+
///
85+
/// [1]: https://www.rfc-editor.org/rfc/rfc5869#section-3.1
86+
/// [2]: https://www.rfc-editor.org/rfc/rfc5869#section-3.2
2587
Future<Uint8List> deriveBits(
2688
int length,
2789
Hash hash,

lib/src/webcrypto/webcrypto.hmac.dart

Lines changed: 84 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ part of webcrypto;
2020
/// [Hash], which can be used to create and verify HMAC signatures as
2121
/// specified in [FIPS PUB 180-4][1].
2222
///
23-
/// Instances of [HmacSecretKey] can be imported using
24-
/// [HmacSecretKey.importRawKey] or generated using [HmacSecretKey.generateKey].
23+
/// Instances of [HmacSecretKey] can be imported from:
24+
/// * Raw bytes using [HmacSecretKey.importRawKey], and,
25+
/// * [JWK] format using [HmacSecretKey.importJsonWebKey].
26+
///
27+
/// A random key can also be generated using [HmacSecretKey.generateKey].
2528
///
2629
/// [1]: https://doi.org/10.6028/NIST.FIPS.180-4
2730
@sealed
@@ -33,7 +36,7 @@ abstract class HmacSecretKey {
3336
/// Creates an [HmacSecretKey] using [keyData] as secret key, and running
3437
/// HMAC with given [hash] algorithm.
3538
///
36-
/// If given [length] specifies the length of the key, this must be not be
39+
/// If given, [length] specifies the length of the key, this must be not be
3740
/// less than number of bits in [keyData] - 7. The [length] only allows
3841
/// cutting bits of the last byte in [keyData]. In practice this is the same
3942
/// as zero'ing the last bits in [keyData].
@@ -44,7 +47,7 @@ abstract class HmacSecretKey {
4447
/// import 'package:webcrypto/webcrypto.dart';
4548
///
4649
/// final key = await HmacSecretKey.importRawKey(
47-
/// utf8.encode('a-secret-key'), // don't use string in practice
50+
/// base64.decode('WzIxLDg0LDEwMCw5OSwxMCwxMDUsMjIsODAsMTkwLDExNiwyMDMsMjQ5XQ=='),
4851
/// Hash.sha256,
4952
/// );
5053
/// ```
@@ -71,12 +74,49 @@ abstract class HmacSecretKey {
7174
return impl.hmacSecretKey_importRawKey(keyData, hash, length: length);
7275
}
7376

74-
/// Import [HmacSecretKey] from [JWK][1].
77+
/// Import [HmacSecretKey] from [JSON Web Key][1].
78+
///
79+
/// {@macro importJsonWebKey:jwk}
80+
///
81+
/// JSON Web Keys imported using [HmacSecretKey.importJsonWebKey] must
82+
/// have `"kty": "oct"`, and the [hash] given must match the hash algorithm
83+
/// implied by the `"alg"` property of the imported [jwk].
84+
///
85+
/// For importing a JWK with:
86+
/// * `"alg": "HS1"` use [Hash.sha1] (**SHA-1 is weak**),
87+
/// * `"alg": "HS256"` use [Hash.sha256],
88+
/// * `"alg": "HS384"` use [Hash.sha384], and,
89+
/// * `"alg": "HS512"` use [Hash.sha512].
90+
///
91+
/// If specified the `"use"` property of the imported [jwk] must be
92+
/// `"use": "sig"`.
93+
///
94+
/// {@macro importJsonWebKey:throws-FormatException-if-jwk}
95+
///
96+
/// **Example**
97+
/// ```dart
98+
/// import 'package:webcrypto/webcrypto.dart';
99+
/// import 'dart:convert' show jsonEncode, jsonDecode;
100+
///
101+
/// // JSON Web Key as a string containing JSON.
102+
/// final jwk = '{"kty": "oct", "alg": "HS256", "k": ...}';
103+
///
104+
/// // Import private key from decoded JSON.
105+
/// final privateKey = await HmacSecretKey.importJsonWebKey(
106+
/// jsonDecode(jwk),
107+
/// Hash.sha256, // Must match the hash used the JWK key "alg"
108+
/// );
75109
///
76-
/// TODO: finish documentation.
110+
/// // Export the key (print it in same format as it was given).
111+
/// Map<String, dynamic> keyData = await privateKey.exportJsonWebKey();
112+
/// print(jsonEncode(keyData));
113+
/// ```
77114
///
78115
/// [1]: https://tools.ietf.org/html/rfc7517
79116
static Future<HmacSecretKey> importJsonWebKey(
117+
// TODO: Determine if the "alg" property can be omitted, and update documentation accordingly
118+
// also make tests covering cases where "alg" is omitted.
119+
// TODO: Determine if there is any restrictions on "use" and "key_ops".
80120
Map<String, dynamic> jwk,
81121
// TODO: Discuss if hash parameter is really necessary, it's in the JWK.
82122
// Presumably webcrypto requires as a sanity check. Notice, that this
@@ -150,11 +190,14 @@ abstract class HmacSecretKey {
150190
/// print(base64.encode(signature));
151191
/// ```
152192
///
153-
/// **Warning**, this method should **not** be used for **validating**
154-
/// other signatures by generating a new signature and then comparing the two.
155-
/// While this technically works, you application might be vulnerable to
156-
/// timing attacks. To validate signatures use [verifyBytes()], this method
157-
/// computes a signature and does a fixed-time comparison.
193+
/// {@template HMAC-sign:do-not-validate-using-sign}
194+
/// This method should not be used for **validating** other signatures by
195+
/// generating a new signature and then comparing the two signatures.
196+
/// While this technically works, your application might be vulnerable to
197+
/// timing attacks. To validate signatures use [verifyBytes] or [verifyStream]
198+
/// instead, these methods computes a signature and does a
199+
/// fixed-time comparison.
200+
/// {@template}
158201
Future<Uint8List> signBytes(List<int> data);
159202

160203
/// Compute an HMAC signature of given [data] stream.
@@ -181,23 +224,22 @@ abstract class HmacSecretKey {
181224
/// print(base64.encode(signature));
182225
/// ```
183226
///
184-
/// **Warning**, this method should **not** be used for **validating**
185-
/// other signatures by generating a new signature and then comparing the two.
186-
/// While this technically works, you application might be vulnerable to
187-
/// timing attacks. To validate signatures use [verifyStream()], this method
188-
/// computes a signature and does a fixed-time comparison.
227+
/// {@macro HMAC-sign:do-not-validate-using-sign}
189228
Future<Uint8List> signStream(Stream<List<int>> data);
190229

191230
/// Verify the HMAC [signature] of given [data].
192231
///
193232
/// This computes an HMAC signature of the [data] in the same manner
194-
/// as [signBytes()] and conducts a fixed-time comparison against [signature],
233+
/// as [signBytes] and conducts a fixed-time comparison against [signature],
195234
/// returning `true` if the two signatures are equal.
196235
///
197-
/// Notice that it's possible to compute a signature for [data] using
198-
/// [signBytes()] and then simply compare the two signatures. This is strongly
199-
/// discouraged as it is easy to introduce side-channels opening your
200-
/// application to timing attacks. Use this method to verify signatures.
236+
/// {@template HMAC-verify:do-not-validate-using-sign}
237+
/// It is possible to compute a signature for [data] using
238+
/// [signBytes] or [signStream] and then simply compare the two signatures.
239+
/// This is strongly discouraged as it is easy to introduce side-channels
240+
/// opening your application to timing attacks.
241+
/// Use [verifyBytes] or [verifyStream] to verify signatures.
242+
/// {@endtemplate}
201243
///
202244
/// **Example**
203245
/// ```dart
@@ -224,13 +266,10 @@ abstract class HmacSecretKey {
224266
/// Verify the HMAC [signature] of given [data] stream.
225267
///
226268
/// This computes an HMAC signature of the [data] stream in the same manner
227-
/// as [signBytes()] and conducts a fixed-time comparison against [signature],
269+
/// as [signStream] and conducts a fixed-time comparison against [signature],
228270
/// returning `true` if the two signatures are equal.
229271
///
230-
/// Notice that it's possible to compute a signature for [data] using
231-
/// [signBytes()] and then simply compare the two signatures. This is strongly
232-
/// discouraged as it is easy to introduce side-channels opening your
233-
/// application to timing attacks. Use this method to verify signatures.
272+
/// {@macro HMAC-verify:do-not-validate-using-sign}
234273
///
235274
/// **Example**
236275
/// ```dart
@@ -278,9 +317,26 @@ abstract class HmacSecretKey {
278317
/// ```
279318
Future<Uint8List> exportRawKey();
280319

281-
/// Export [HmacSecretKey] from [JWK][1].
320+
/// Export [HmacSecretKey] from [JSON Web Key][1].
321+
///
322+
/// {@macro exportJsonWebKey:returns}
323+
///
324+
/// **Example**
325+
/// ```dart
326+
/// import 'package:webcrypto/webcrypto.dart';
327+
/// import 'dart:convert' show jsonEncode;
328+
///
329+
/// // Generate a new random HMAC secret key.
330+
/// final key = await HmacSecretKey.generate(Hash.sha256);
331+
///
332+
/// // Export the private key.
333+
/// final jwk = await key.exportJsonWebKey();
282334
///
283-
/// TODO: finish documentation.
335+
/// // The Map returned by `exportJsonWebKey()` can be converted to JSON with
336+
/// // `jsonEncode` from `dart:convert`, this will print something like:
337+
/// // {"kty": "oct", "alg": "HS256", "k": ...}
338+
/// print(jsonEncode(jwk));
339+
/// ```
284340
///
285341
/// [1]: https://tools.ietf.org/html/rfc7517
286342
Future<Map<String, dynamic>> exportJsonWebKey();

lib/src/webcrypto/webcrypto.pbkdf2.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,77 @@
1414

1515
part of webcrypto;
1616

17+
/// PBKDF2 secret key (or password) for key derivation.
18+
///
19+
/// An [Pbkdf2SecretKey] instance holds a secret key for key derivation using
20+
/// _PKCS#5 password-based key derivation function version 2_ as specified in
21+
/// [RFC 8018][1] using HMAC as pseudo-random function. The HMAC will used the
22+
/// [Hash] algorithm given in [deriveBits].
23+
///
24+
/// A [Pbkdf2SecretKey] can be imported using [importRawKey].
25+
///
26+
/// {@template Pbkdf2SecretKey:example}
27+
/// **Example**
28+
/// ```
29+
/// import 'dart:convert' show utf8, base64;
30+
/// import 'package:webcrypto/webcrypto.dart';
31+
///
32+
/// // Provide a password to be used for key derivation
33+
/// final key = await Pbkdf2SecretKey.importRawKey(utf8.decode(
34+
/// 'my-password-in-plain-text',
35+
/// ));
36+
///
37+
/// // Derive a key from password
38+
/// final derivedKey = await Pbkdf2SecretKey.deriveBits(
39+
/// 256, // number of bits to derive.
40+
/// Hash.sha256,
41+
/// utf8.decode('unique salt'),
42+
/// 100000,
43+
/// );
44+
///
45+
/// // Print the derived key, this could also be used as basis for other new
46+
/// // symmetric cryptographic keys.
47+
/// print(base64.encode(derivedKey));
48+
/// ```
49+
/// {@endtemplate}
50+
///
51+
/// [1]: https://tools.ietf.org/html/rfc8018
52+
// TODO: Rewrite all RFC links to use https://www.rfc-editor.org/rfc/rfcXXXX
1753
@sealed
1854
abstract class Pbkdf2SecretKey {
1955
Pbkdf2SecretKey._(); // keep the constructor private.
2056

57+
/// Import [Pbkdf2SecretKey] from raw [keyData].
58+
///
59+
/// Creates a [Pbkdf2SecretKey] for key derivation using [keyData].
60+
///
61+
/// {@macro Pbkdf2SecretKey:example}
2162
static Future<Pbkdf2SecretKey> importRawKey(List<int> keyData) {
2263
return impl.pbkdf2SecretKey_importRawKey(keyData);
2364
}
2465

66+
/// Derive key from [salt] and password specified as `keyData` in
67+
/// [importRawKey].
68+
///
69+
/// The [length] of the key to be derived must be specified in bits as a
70+
/// multiple of 8.
71+
///
72+
/// The key derivation will used HMAC with given [hash] as the
73+
/// _pseudo-random function_.
74+
///
75+
/// Using sufficiently large random [salt] makes hard for an adversary to
76+
/// precompute the most likely keys using a dictionary of common passwords.
77+
/// The [salt] also serves make the same password have yield different keys.
78+
/// For details on [salt] see [RFC 8018 section 4.1][1].
79+
///
80+
/// A higher [iterations] count will increase the cost to an adversary doing
81+
/// an exhaustive search for the derived key, but it will also make the
82+
/// key derivation operation slower. For details on [iterations] see
83+
/// [RFC 8018 section 4.2][1].
84+
///
85+
/// {@macro Pbkdf2SecretKey:example}
86+
///
87+
/// [1]: https://tools.ietf.org/html/rfc8018
2588
Future<Uint8List> deriveBits(
2689
int length,
2790
Hash hash,

0 commit comments

Comments
 (0)