Skip to content

Commit

Permalink
More docs (#50)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jonasfj authored Oct 24, 2022
1 parent 0d65501 commit d629f58
Show file tree
Hide file tree
Showing 9 changed files with 1,386 additions and 105 deletions.
2 changes: 2 additions & 0 deletions lib/src/testing/webcrypto/pbkdf2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ void main() {
runner.runTests();
}

// TODO: Augments tests with test vectors from: https://datatracker.ietf.org/doc/html/rfc6070

// Allow single quotes for hardcoded testData written as JSON:
// ignore_for_file: prefer_single_quotes
final _testData = [
Expand Down
6 changes: 6 additions & 0 deletions lib/src/webcrypto/webcrypto.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

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

import 'package:meta/meta.dart';
Expand Down
2 changes: 1 addition & 1 deletion lib/src/webcrypto/webcrypto.digest.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ part of webcrypto;
/// For a guidance on choice of hash function see
/// [NIST SP 800-57 Part 1 Rev 5][1].
///
/// **WARNING:** Custom implementations of this class cannot be passed to
/// Notice custom implementations of this class cannot be passed to
/// to other methods in this library.
///
/// [1]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-57pt1r5.pdf
Expand Down
62 changes: 62 additions & 0 deletions lib/src/webcrypto/webcrypto.hkdf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,76 @@

part of webcrypto;

/// HKDF secret key (or password) for key derivation.
///
/// An [HkdfSecretKey] instance holds a secret key for key derivation using
/// the _HMAC-based Key Derivation Function_ specified in [RFC 5869][1] using
/// a [Hash] function specified in the [deriveBits] method.
///
/// A [HkdfSecretKey] can be imported using [importRawKey].
///
/// {@template HkdfSecretKey:example}
/// **Example**
/// ```
/// import 'dart:convert' show utf8, base64;
/// import 'package:webcrypto/webcrypto.dart';
///
/// // Provide a password to be used for key derivation
/// final key = await HkdfSecretKey.importRawKey(utf8.decode(
/// 'my-password-in-plain-text',
/// ));
///
/// // Derive a key from password
/// final derivedKey = await HkdfSecretKey.deriveBits(
/// 256, // number of bits to derive.
/// Hash.sha256,
/// utf8.decode('unique salt'),
/// utf8.decode('creating derivedKey in example'),
/// );
///
/// // Print the derived key, this could also be used as basis for other new
/// // symmetric cryptographic keys.
/// print(base64.encode(derivedKey));
/// ```
/// {@endtemplate}
///
/// [1]: https://tools.ietf.org/html/rfc5869
// TODO: It might be wise to use a random salt, then suggest that the non-secret
// salt is stored or exchanged...
@sealed
abstract class HkdfSecretKey {
HkdfSecretKey._(); // keep the constructor private.

/// Import [HkdfSecretKey] from raw [keyData].
///
/// Creates a [HkdfSecretKey] for key derivation using [keyData].
///
/// {@macro HkdfSecretKey:example}
static Future<HkdfSecretKey> importRawKey(List<int> keyData) {
return impl.hkdfSecretKey_importRawKey(keyData);
}

/// Derive key from [salt], [info] and password specified as `keyData` in
/// [importRawKey].
///
/// The [length] of the key to be derived must be specified in bits as a
/// multiple of 8.
///
/// Using sufficiently large random [salt] makes hard for an adversary to
/// precompute the most likely keys using a dictionary of common passwords.
/// The [salt] also serves make the same password have yield different keys.
/// For details on [salt] see [RFC 5869 section 3.1][1].
///
/// The [info] serves to bind the derived key to an application specific
/// context. For example, if the same `keyData` is used to derive keys for
/// different use cases, then using a different [info] for each purpose
/// ensures that the derived keys are different.
/// For details on [info] see [RFC 5869 section 3.2][2].
///
/// {@macro HkdfSecretKey:example}
///
/// [1]: https://www.rfc-editor.org/rfc/rfc5869#section-3.1
/// [2]: https://www.rfc-editor.org/rfc/rfc5869#section-3.2
Future<Uint8List> deriveBits(
int length,
Hash hash,
Expand Down
112 changes: 84 additions & 28 deletions lib/src/webcrypto/webcrypto.hmac.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ part of webcrypto;
/// [Hash], which can be used to create and verify HMAC signatures as
/// specified in [FIPS PUB 180-4][1].
///
/// Instances of [HmacSecretKey] can be imported using
/// [HmacSecretKey.importRawKey] or generated using [HmacSecretKey.generateKey].
/// Instances of [HmacSecretKey] can be imported from:
/// * Raw bytes using [HmacSecretKey.importRawKey], and,
/// * [JWK] format using [HmacSecretKey.importJsonWebKey].
///
/// A random key can also be generated using [HmacSecretKey.generateKey].
///
/// [1]: https://doi.org/10.6028/NIST.FIPS.180-4
@sealed
Expand All @@ -33,7 +36,7 @@ abstract class HmacSecretKey {
/// Creates an [HmacSecretKey] using [keyData] as secret key, and running
/// HMAC with given [hash] algorithm.
///
/// If given [length] specifies the length of the key, this must be not be
/// If given, [length] specifies the length of the key, this must be not be
/// less than number of bits in [keyData] - 7. The [length] only allows
/// cutting bits of the last byte in [keyData]. In practice this is the same
/// as zero'ing the last bits in [keyData].
Expand All @@ -44,7 +47,7 @@ abstract class HmacSecretKey {
/// import 'package:webcrypto/webcrypto.dart';
///
/// final key = await HmacSecretKey.importRawKey(
/// utf8.encode('a-secret-key'), // don't use string in practice
/// base64.decode('WzIxLDg0LDEwMCw5OSwxMCwxMDUsMjIsODAsMTkwLDExNiwyMDMsMjQ5XQ=='),
/// Hash.sha256,
/// );
/// ```
Expand All @@ -71,12 +74,49 @@ abstract class HmacSecretKey {
return impl.hmacSecretKey_importRawKey(keyData, hash, length: length);
}

/// Import [HmacSecretKey] from [JWK][1].
/// Import [HmacSecretKey] from [JSON Web Key][1].
///
/// {@macro importJsonWebKey:jwk}
///
/// JSON Web Keys imported using [HmacSecretKey.importJsonWebKey] must
/// have `"kty": "oct"`, and the [hash] given must match the hash algorithm
/// implied by the `"alg"` property of the imported [jwk].
///
/// For importing a JWK with:
/// * `"alg": "HS1"` use [Hash.sha1] (**SHA-1 is weak**),
/// * `"alg": "HS256"` use [Hash.sha256],
/// * `"alg": "HS384"` use [Hash.sha384], and,
/// * `"alg": "HS512"` use [Hash.sha512].
///
/// If specified the `"use"` property of the imported [jwk] must be
/// `"use": "sig"`.
///
/// {@macro importJsonWebKey:throws-FormatException-if-jwk}
///
/// **Example**
/// ```dart
/// import 'package:webcrypto/webcrypto.dart';
/// import 'dart:convert' show jsonEncode, jsonDecode;
///
/// // JSON Web Key as a string containing JSON.
/// final jwk = '{"kty": "oct", "alg": "HS256", "k": ...}';
///
/// // Import private key from decoded JSON.
/// final privateKey = await HmacSecretKey.importJsonWebKey(
/// jsonDecode(jwk),
/// Hash.sha256, // Must match the hash used the JWK key "alg"
/// );
///
/// TODO: finish documentation.
/// // Export the key (print it in same format as it was given).
/// Map<String, dynamic> keyData = await privateKey.exportJsonWebKey();
/// print(jsonEncode(keyData));
/// ```
///
/// [1]: https://tools.ietf.org/html/rfc7517
static Future<HmacSecretKey> importJsonWebKey(
// TODO: Determine if the "alg" property can be omitted, and update documentation accordingly
// also make tests covering cases where "alg" is omitted.
// TODO: Determine if there is any restrictions on "use" and "key_ops".
Map<String, dynamic> jwk,
// TODO: Discuss if hash parameter is really necessary, it's in the JWK.
// Presumably webcrypto requires as a sanity check. Notice, that this
Expand Down Expand Up @@ -150,11 +190,14 @@ abstract class HmacSecretKey {
/// print(base64.encode(signature));
/// ```
///
/// **Warning**, this method should **not** be used for **validating**
/// other signatures by generating a new signature and then comparing the two.
/// While this technically works, you application might be vulnerable to
/// timing attacks. To validate signatures use [verifyBytes()], this method
/// computes a signature and does a fixed-time comparison.
/// {@template HMAC-sign:do-not-validate-using-sign}
/// This method should not be used for **validating** other signatures by
/// generating a new signature and then comparing the two signatures.
/// While this technically works, your application might be vulnerable to
/// timing attacks. To validate signatures use [verifyBytes] or [verifyStream]
/// instead, these methods computes a signature and does a
/// fixed-time comparison.
/// {@template}
Future<Uint8List> signBytes(List<int> data);

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

/// Verify the HMAC [signature] of given [data].
///
/// This computes an HMAC signature of the [data] in the same manner
/// as [signBytes()] and conducts a fixed-time comparison against [signature],
/// as [signBytes] and conducts a fixed-time comparison against [signature],
/// returning `true` if the two signatures are equal.
///
/// Notice that it's possible to compute a signature for [data] using
/// [signBytes()] and then simply compare the two signatures. This is strongly
/// discouraged as it is easy to introduce side-channels opening your
/// application to timing attacks. Use this method to verify signatures.
/// {@template HMAC-verify:do-not-validate-using-sign}
/// It is possible to compute a signature for [data] using
/// [signBytes] or [signStream] and then simply compare the two signatures.
/// This is strongly discouraged as it is easy to introduce side-channels
/// opening your application to timing attacks.
/// Use [verifyBytes] or [verifyStream] to verify signatures.
/// {@endtemplate}
///
/// **Example**
/// ```dart
Expand All @@ -224,13 +266,10 @@ abstract class HmacSecretKey {
/// Verify the HMAC [signature] of given [data] stream.
///
/// This computes an HMAC signature of the [data] stream in the same manner
/// as [signBytes()] and conducts a fixed-time comparison against [signature],
/// as [signStream] and conducts a fixed-time comparison against [signature],
/// returning `true` if the two signatures are equal.
///
/// Notice that it's possible to compute a signature for [data] using
/// [signBytes()] and then simply compare the two signatures. This is strongly
/// discouraged as it is easy to introduce side-channels opening your
/// application to timing attacks. Use this method to verify signatures.
/// {@macro HMAC-verify:do-not-validate-using-sign}
///
/// **Example**
/// ```dart
Expand Down Expand Up @@ -278,9 +317,26 @@ abstract class HmacSecretKey {
/// ```
Future<Uint8List> exportRawKey();

/// Export [HmacSecretKey] from [JWK][1].
/// Export [HmacSecretKey] from [JSON Web Key][1].
///
/// {@macro exportJsonWebKey:returns}
///
/// **Example**
/// ```dart
/// import 'package:webcrypto/webcrypto.dart';
/// import 'dart:convert' show jsonEncode;
///
/// // Generate a new random HMAC secret key.
/// final key = await HmacSecretKey.generate(Hash.sha256);
///
/// // Export the private key.
/// final jwk = await key.exportJsonWebKey();
///
/// TODO: finish documentation.
/// // The Map returned by `exportJsonWebKey()` can be converted to JSON with
/// // `jsonEncode` from `dart:convert`, this will print something like:
/// // {"kty": "oct", "alg": "HS256", "k": ...}
/// print(jsonEncode(jwk));
/// ```
///
/// [1]: https://tools.ietf.org/html/rfc7517
Future<Map<String, dynamic>> exportJsonWebKey();
Expand Down
63 changes: 63 additions & 0 deletions lib/src/webcrypto/webcrypto.pbkdf2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,77 @@

part of webcrypto;

/// PBKDF2 secret key (or password) for key derivation.
///
/// An [Pbkdf2SecretKey] instance holds a secret key for key derivation using
/// _PKCS#5 password-based key derivation function version 2_ as specified in
/// [RFC 8018][1] using HMAC as pseudo-random function. The HMAC will used the
/// [Hash] algorithm given in [deriveBits].
///
/// A [Pbkdf2SecretKey] can be imported using [importRawKey].
///
/// {@template Pbkdf2SecretKey:example}
/// **Example**
/// ```
/// import 'dart:convert' show utf8, base64;
/// import 'package:webcrypto/webcrypto.dart';
///
/// // Provide a password to be used for key derivation
/// final key = await Pbkdf2SecretKey.importRawKey(utf8.decode(
/// 'my-password-in-plain-text',
/// ));
///
/// // Derive a key from password
/// final derivedKey = await Pbkdf2SecretKey.deriveBits(
/// 256, // number of bits to derive.
/// Hash.sha256,
/// utf8.decode('unique salt'),
/// 100000,
/// );
///
/// // Print the derived key, this could also be used as basis for other new
/// // symmetric cryptographic keys.
/// print(base64.encode(derivedKey));
/// ```
/// {@endtemplate}
///
/// [1]: https://tools.ietf.org/html/rfc8018
// TODO: Rewrite all RFC links to use https://www.rfc-editor.org/rfc/rfcXXXX
@sealed
abstract class Pbkdf2SecretKey {
Pbkdf2SecretKey._(); // keep the constructor private.

/// Import [Pbkdf2SecretKey] from raw [keyData].
///
/// Creates a [Pbkdf2SecretKey] for key derivation using [keyData].
///
/// {@macro Pbkdf2SecretKey:example}
static Future<Pbkdf2SecretKey> importRawKey(List<int> keyData) {
return impl.pbkdf2SecretKey_importRawKey(keyData);
}

/// Derive key from [salt] and password specified as `keyData` in
/// [importRawKey].
///
/// The [length] of the key to be derived must be specified in bits as a
/// multiple of 8.
///
/// The key derivation will used HMAC with given [hash] as the
/// _pseudo-random function_.
///
/// Using sufficiently large random [salt] makes hard for an adversary to
/// precompute the most likely keys using a dictionary of common passwords.
/// The [salt] also serves make the same password have yield different keys.
/// For details on [salt] see [RFC 8018 section 4.1][1].
///
/// A higher [iterations] count will increase the cost to an adversary doing
/// an exhaustive search for the derived key, but it will also make the
/// key derivation operation slower. For details on [iterations] see
/// [RFC 8018 section 4.2][1].
///
/// {@macro Pbkdf2SecretKey:example}
///
/// [1]: https://tools.ietf.org/html/rfc8018
Future<Uint8List> deriveBits(
int length,
Hash hash,
Expand Down
Loading

0 comments on commit d629f58

Please sign in to comment.