webcrypto.dart
webcrypto.dart copied to clipboard
Explore wrap/unwrap/derive-key and capabilities
The web cryptography specification the CryptoKey
interface have the following attributes not currently supported in this package:
-
usages: 'encrypt' | 'decrypt' | 'sign' | 'verify' | 'deriveKey' | 'deriveBits' | 'wrapKey' | 'unwrapKey'
-
extractable: true | false
These capabilities limits what a key object can be used for. In a Dart API trying to export a key with extractable: false
would probably throw a StateError
.
The web crypto spec also have the following operations not currently supported in this package:
-
deriveKey
-- equivalent to derive bits and import key. -
wrapKey
-- equivalent to exporting the key and encrypting it. -
unwrapKey
-- equivalent to decrypting and importing key.
If we wish to add support for capabilities, then we need to also support wrap/unwrap/derive-key and vice-versus.
Support for capabilities (usages
/extractable
) and wrap/unwrap/derive-key operations are mutually dependent.
Summary:
- A key exported in JWK format encodes capabilities (
usages
/extractable
), thus, unwrapping in the browser enforces capabilities. - Supporting capabilities (
usages
/extractable
) without supporting all operations that can be encoded is weird.
When exporting a key in JWK format the extractable
bit and the usages
are encoded in the exported JSON Web Key.
If we unwrap an encrypted JWK key using window.crypto
in the browser, then whatever capabilities was encoded in the encrypted JWK is what the decrypted+imported CryptoKey
will have. And the browser won't give us an opportunity to increase the capabilities of the unwrapped key. So we abstractions that wrap CryptoKey
in Dart must support capabilities, if we wish to support the unwrapKey
operation.
This could be worked around by:
- Using
decrypt
+import
instead of theunwrap
operation.- This would a bit of a hack in the browser, and reduce the security features expected by the user in the browser environment.
- It would not work for
AES-KW
which supportswrap
/unwrap
operations, but notencrypt
/decrypt
. But JWK does not work will withAES-KW
, see https://github.com/w3c/webcrypto/issues/187
- Not supporting wrap/unwrap for JWK formatted keys.
A possible API design for this could be something along the lines of what is illustrated here:
final hmacKey = await HmacSecretKey.generate(Hash.sha256);
final wrappedKeyAsBytes = await secretKey.wrapKey(
// this is a WrapKeyOptions -- just a way type key + format, ensuring you
// can't use a format not supported for a given key.
// Also weird to have a format enum, not used in import/export methods!
hmacKey.wrapRawKeyOptions(),
// options for encryptBytes, differs depending on secretKey type!
iv,
);
final wrappedKeyAsBytes = await secretKey.wrapKey(
hmacKey.wrapJsonWebKeyOptions(),
iv,
);
final hmacKey = await secretKey.unwrapKey(
// This is a UnwrapKeyOptions -- which is format + import options
HmacSecretKey.unwrapJsonWebKeyOptions(Hash.sha256),
wrappedKeyAsBytes,
// options for decryptBytes, differs depending on secretKey type
iv,
);
final hmacKey = await secretKey.unwrapKey(
HmacSecretKey.unwrapRawKeyOptions(Hash.sha256),
wrappedKeyAsBytes,
iv,
);
final hmacKey = await hkdfKey.deriveKey(
// This must be a DeriveKeyOptions<T>, and then deriveKey returns Future<T>
// This allows us to limit what keys can be derived, webcrypto can only derive
// HMAC, AES-CBC, AES-CTR, AES-GCM and AES-KW.
HmacSecretKey.deriveKeyOptions(Hash.sha256),
salt,
info,
);
@sealed
abstract class WrapKeyOptions {}
@sealed
abstract class UnwrapKeyOptions<T> {}
@sealed
abstract class DeriveKeyOptions<T> {}