google/webcrypto.dart

Explore wrap/unwrap/derive-key and capabilities

Opened this issue · 0 comments

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 the unwrap 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 supports wrap/unwrap operations, but not encrypt/decrypt. But JWK does not work will with AES-KW, see w3c/webcrypto#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> {}