diff --git a/wit/README.md b/wit/README.md new file mode 100644 index 0000000..02a5ede --- /dev/null +++ b/wit/README.md @@ -0,0 +1,37 @@ +## Made changes + +### Automatic + +| WITX Type / Construct | Target WIT Construct | Transpilation Details & Notes | +| --------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| **`TypeRef::Name`** | `Type::named(...)` | Map using kebab-case identifier. | +| **`BuiltinType::Char`** | `Type::Char` | Translated directly. | +| **`BuiltinType::U8 / U16 / U32 / U64`** | `Type::U8` to `Type::U64` | Translated directly. | +| **`BuiltinType::S8 / S16 / S32 / S64`** | `Type::S8` to `Type::S64` | Translated directly (Signed integer). | +| **`BuiltinType::F32 / F64`** | `Type::F32` / `Type::F64` | Translated directly (Floats). | +| **`Type::List(Type::Char)`** | `Type::String` | Special promotion applied (`list` -> `string`). | +| **`Type::List(T)`** | `Type::list` | Mapped to a WIT list. | +| **`Type::Variant` (Option style)** | `Type::option` | Recognized via `general_as_option`. | +| **`Type::Variant` (Expected style)** | `Type::result` | Recognized via `general_as_expected` (handles ok/err variations). | +| **`Type::Variant` (Enum style)** | `TypeDef::variant(...)` | Custom map to variant cases. | +| **`Type::Record` (Tuple style)** | `Type::tuple(...)` | Evaluated when `record.is_tuple()` is true. | +| **`Type::Record` (Bitflags)** | `TypeDef::flags(...)` | Evaluated when `record.bitflags_repr().is_some()`. | +| **`Type::Record` (Standard)** | `TypeDef::record(...)` | Maps fields to a named structure. | +| **`Type::Handle`** | `Type::named(...)` | Resolves to the name of the resource handle. | +| **`witx_module.constants()`** | `TypeDef::resource(...)` | **Workaround**: Grouped by type and modeled as a resource containing static-like `ResourceFunc::method` calls tagged with a `TODO` comment. | +| **`witx_module.resources()`** | `TypeDef::resource(...)` | Map to an isolated empty WIT resource type block. | +| **`witx_module.funcs()`** | `StandaloneFunc` | Maps parameters and the single return output. | +| **`Type::Pointer`** | **Not Supported** | ❌ Fails with `TranspileError::UnsupportedType`. Raw memory addresses are disallowed; recommend `list` or `resource`. | +| **`Type::ConstPointer`** | **Not Supported** | ❌ Fails with `TranspileError::UnsupportedType`. Raw memory addresses are disallowed; recommend `list` or `resource`. | + +### Manual + +| WITX Type / Construct | Target WIT Construct | Transpilation Details & Notes | +| ------------------------------------------------------------------ | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`Pointer + len` (Output buffer)** | `list` | For `symmetric-state-encrypt`, `symmetric-state-encrypt-detached`, `symmetric-state-decrypt`, and `symmetric-state-decrypt-detached`; the ability to encrypt in-place had to be removed. | +| **`ConstPointer + len` (Input buffer)** | `list` | For `symmetric-state-encrypt`, `symmetric-state-encrypt-detached`, `symmetric-state-decrypt`, and `symmetric-state-decrypt-detached`; the ability to encrypt in-place had to be removed. | +| **Version constants** | `variant` | Mostly due to the lack of constants, with a variant being more suitable to display the same information than implicit values. | +| **`Variant`** without data | `enum` | There is nothing stopping the transpiler from handling this case as well. I just didn't notice and fixing it by hand was easier. | +| `type ... = handle` | `resource` | This required manually adding `borrow<>` where required. | +| **Imports** | `use wasi-ephemeral-crypto-common.{}` | This was done manually and verified using: | +| **`array-output-pull` / `symmetric-tag-pull` parameter ownership** | `borrow` (WITX host closes as side-effect) | `T` (owned; Component Model destroys on function return) | In WITX the host explicitly closes the handle inside `pull`, enforcing single-use. WIT `borrow` prevents the host from doing so. Taking ownership restores the invariant. The regression is minimal: WITX allowed retrying `pull` with a larger buffer on `Overflow`, but the WIT version already returns `list` (host-allocated), making `Overflow` impossible and retries unnecessary. | diff --git a/wit/batch/wasi_ephemeral_crypto_signatures_batch.wit b/wit/batch/wasi_ephemeral_crypto_signatures_batch.wit new file mode 100644 index 0000000..5bfc81a --- /dev/null +++ b/wit/batch/wasi_ephemeral_crypto_signatures_batch.wit @@ -0,0 +1,100 @@ +package wasi:crypto@0.11.0; + +interface wasi-ephemeral-crypto-signatures-batch { + use wasi-ephemeral-crypto-common.{array-output, crypto-errno, signature-verification-state, signature, signature-state}; + + /// The result of a signature sign operation. A pair of the signature and an error code. + type signature-sign-result = tuple; + + /// A list of signature_sign results. + type signature-results = list; + + /// A tuple of a signature verification state and the signature to verify. + /// + /// Used for grouping signature verification state to be verified with the signature to verify. + /// Used with batch_signature_state_verify(). + type signature-verification-input = tuple, borrow>; + + type signature-verification-results = list; + + /// Compute a batch of signatures. + /// + /// This is a batch version of the signature_state_sign operation and is an extension of the wasi_ephemeral_crypto_signatures module. + /// + /// The batch operation returns an error code of type $crypto_errno that + /// indicates if the batch was processed or if the batch could not be + /// processed. + /// + /// Batch processing error codes: + /// - `success`: Batch was processed. The status of each operation is indicated in the results list. + /// - `not_implemented`: Batch functionality is not supported. + /// - `unsupported_feature`: Inconsistent operations within the batch, e.g. not all operations in the batch use the same algorithm. + /// + /// If the batch was processed, the result of each operation in the batch + /// is a pair of a $crypto_errno error code and a signature. The error code + /// indicates if that operation succeeded or failed. The signature is only + /// valid if the error code indicates success. + /// + /// Example usage: + /// + /// ```rust + /// let kp_handle = keypair_import(AlgorithmType::Signatures, "Ed25519", encoded, KeypairEncoding::Raw)?; + /// + /// let mut state_handles = Vec::new(); + /// + /// let state_handle = signature_state_open(kp_handle)?; + /// signature_state_update(state_handle, b"message part 1")?; + /// signature_state_update(state_handle, b"message part 2")?; + /// state_handles.push(state_handle); + /// + /// let state_handle = signature_state_open(kp_handle)?; + /// signature_state_update(state_handle, b"message part 1")?; + /// signature_state_update(state_handle, b"message part 2")?; + /// state_handles.push(state_handle); + /// + /// let sig_handles = batch_signature_state_sign(state_handles)?; + /// + /// let raw_sig1 = signature_export(sig_handle[0], SignatureEncoding::Raw)?; + /// let raw_sig2 = signature_export(sig_handle[1], SignatureEncoding::Raw)?; + /// ``` + batch-signature-state-sign: func(states: list>) -> result; + + /// Verify a batch of signatures. + /// + /// This is a batch version of the signature_state_verify operation and is + /// an extension of the wasi_ephemeral_crypto_signatures module. + /// + /// The batch operation returns an error code of type $crypto_errno that + /// indicates if the batch was processed (`success`) or if the batch could + /// not be processed. + /// + /// Batch processing failure cases are: + /// - `not_implemented`: Batch functionality is not supported. + /// - `unsupported_feature`: Inconsistent operations within the batch, e.g. not all operations in the batch use the same algorithm. + /// + /// If the batch was processed, a list of verification results is produced. + /// Each entry in the input list has a corresponding error state returned + /// in the verification results list to indicate if the verification + /// succeeded or encountered an error. + /// + /// Example usage: + /// + /// ```rust + /// let kp_handle = keypair_import(AlgorithmType::Signatures, "Ed25519", encoded, KeypairEncoding::Raw)?; + /// + /// let mut batch = Vec::new(); + /// + /// let state_handle = signature_verification_state_open(kp_handle)?; + /// signature_verification_state_update(state_handle, b"message part 1")?; + /// signature_verification_state_update(state_handle, b"message part 2")?; + /// state_handles.push((state_handle, signature1)); + /// + /// let state_handle = signature_verification_state_open(kp_handle)?; + /// signature_verification_state_update(state_handle, b"message part 1")?; + /// signature_verification_state_update(state_handle, b"message part 2")?; + /// state_handles.push((state_handle, signature2)); + /// + /// let results = batch_signature_state_verify(state_handles)?; + /// ``` + batch-signature-state-verify: func(states: list) -> result; +} diff --git a/wit/batch/wasi_ephemeral_crypto_symmetric_batch.wit b/wit/batch/wasi_ephemeral_crypto_symmetric_batch.wit new file mode 100644 index 0000000..ea02186 --- /dev/null +++ b/wit/batch/wasi_ephemeral_crypto_symmetric_batch.wit @@ -0,0 +1,226 @@ +package wasi:crypto@0.11.0; + +/// Symmetric Batch Operations +interface wasi-ephemeral-crypto-symmetric-batch { + use wasi-ephemeral-crypto-common.{size, crypto-errno, symmetric-tag, symmetric-state}; + + type output = list; + + /// A non-mutable data buffer + type data = list; + + /// A raw tag buffer + type raw-tag = list; + + /// Tuple representing results and output buffers produced by an encrypt/decrypt operation. + type crypt-result = tuple; + + /// A list of results from the individual encrypt/decrypt operations within a batch operation. + type batch-crypt-results = list; + + /// Tuple representing results and size produced by a detached encrypt operation. + type encrypt-detached-result = tuple; + + /// A list of results from the individual encrypt/decrypt operations within a batch operation. + type batch-encrypt-detached-results = list; + + /// Tuple representing results and size produced by a squeeze operation. + type squeeze-result = tuple; + + /// A list of results from squeeze operation within a batch operation. + type batch-squeeze-results = list; + + /// Tuple representing results and tag and error produced by a detached squeeze operation. + type squeeze-detached-result = tuple; + + /// A list of results from the individual detached squeeze operations within a batch operation. + type batch-squeeze-detached-results = list; + + /// Batch of operations to squeeze bytes from a batch of states. + /// + /// This is a batch version of the $symmetric_state_squeeze operation. + /// + /// Each entry in the batch corresponds to an individual squeeze operation. + /// The parameters associated with each operation are grouped into a tuple. + /// + /// The batch operation returns an error code of type $crypto_errno that + /// indicates if the batch was processed or if the batch could not be + /// processed. + /// + /// Batch processing error codes: + /// - `success`: Batch was processed. The status of each operation is indicated in the results list. + /// - `not_implemented`: Batch functionality is not supported. + /// - `unsupported_feature`: Inconsistent operations within the batch, e.g. not all operations in the batch use the same algorithm. + /// + /// If the batch was processed, the result is a list of $crypto_errno error + /// codes that represent the status of the operation in the input list at + /// the same list offset. + /// + batch-symmetric-state-squeeze: func(batch: list>) -> result; + + /// Batch of operations to compute and return a tag for all the data + /// injected into the state so far. + /// + /// This is a batch version of the $symmetric_state_squeeze_tag operation. + /// + /// Each entry in the batch corresponds to an individual squeeze_tag + /// operation. The parameters associated with each operation are grouped + /// into a tuple. + /// + /// The batch operation returns an error code of type $crypto_errno that + /// indicates if the batch was processed or if the batch could not be + /// processed. + /// + /// Batch processing error codes: + /// - `success`: Batch was processed. The status of each operation is indicated in the results list. + /// - `not_implemented`: Batch functionality is not supported. + /// - `unsupported_feature`: Inconsistent operations within the batch, e.g. not all operations in the batch use the same algorithm. + /// + /// If the batch was processed, the result is a list of tuples, with each + /// list entry corresponding to the operation in the input list at the same + /// list offset. Each tuple contains a $crypto_errno error code and a tag. + /// The error code represents the status of the operation and the tag is the + /// tag generated from the squeeze operation. The tag is only valid if the + /// tuple's error code is `success`. + /// + batch-symmetric-state-squeeze-tag: func(states: list>) -> result; + + /// Perform a batch of symmetric encrypt operations. + /// + /// This is a batch version of the symmetric_state_encrypt operation. + /// + /// Each entry in the batch corresponds to an individual encrypt operation. + /// The parameters associated with each encrypt operation are grouped into a + /// tuple. + /// + /// The batch operation returns an error code of type $crypto_errno that + /// indicates if the batch was processed or if the batch could not be + /// processed. + /// + /// Batch processing error codes: + /// - `success`: Batch was processed. The status of each operation is indicated in the results list. + /// - `not_implemented`: Batch functionality is not supported. + /// - `unsupported_feature`: Inconsistent operations within the batch, e.g. not all operations in the batch use the same algorithm. + /// + /// If the batch was processed, the result is a list of tuples, with each + /// list entry corresponding to the operation in the input list at the same + /// list offset. + /// Each tuple contains a size and a $crypto_errno error code. + /// The error code represents the status of the operation and the size is + /// the actual size of the ciphertext and the tag in the output buffer. The + /// size value is only valid if the tuple's error code is `success`. + /// + /// Example usage: + /// + /// ```rust + /// let mut batch = Vec::new(); + /// + /// let state_handle = ctx.symmetric_state_open("AES-256-GCM", Some(key_handle1), Some(options_handle1))?; + /// let mut ciphertext = vec![0u8; message.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; + /// batch.push((batch, state_handle, ciphertext, ciphertext.len(), message, message.len())); + /// + /// let state_handle = ctx.symmetric_state_open("AES-256-GCM", Some(key_handle2), Some(options_handle2))?; + /// let mut ciphertext = vec![0u8; message2.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; + /// batch.push((batch, state_handle, ciphertext, ciphertext.len(), message2, message2.len())); + /// + /// let results = ctx.batch_symmetric_state_encrypt(batch)?; + /// ``` + batch-symmetric-state-encrypt: func(batch: list, data>>) -> result; + + /// Perform a batch of symmetric encrypt operations with detached tags. + /// + /// This is a batch version of the symmetric_state_encrypt_detached + /// operation. + /// + /// Each entry in the batch corresponds to an individual encrypt operation. + /// The parameters associated with each encrypt operation are grouped into a + /// tuple. + /// + /// The batch operation returns an error code of type $crypto_errno that + /// indicates if the batch was processed or if the batch could not be + /// processed. + /// + /// Batch processing error codes: + /// - `success`: Batch was processed. The status of each operation is indicated in the results list. + /// - `not_implemented`: Batch functionality is not supported. + /// - `unsupported_feature`: Inconsistent operations within the batch, e.g. not all operations in the batch use the same algorithm. + /// + /// If the batch was processed, the result is a list of tuples, with each + /// list entry corresponding to the operation in the input list at the same + /// list offset. + /// Each tuple contains a tag and a $crypto_errno error code. + /// The error code represents the status of the operation and the tag is + /// the tag generated by the operation. The tag is only valid if the tuple's + /// error code is `success`. + /// + /// Example usage: + /// + /// ```rust + /// let mut batch = Vec::new(); + /// + /// let state_handle = ctx.symmetric_state_open("AES-256-GCM", Some(key_handle1), Some(options_handle1))?; + /// let mut ciphertext = vec![0u8; message.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; + /// batch.push((batch, state_handle, ciphertext, ciphertext.len(), message, message.len())); + /// + /// let state_handle = ctx.symmetric_state_open("AES-256-GCM", Some(key_handle2), Some(options_handle2))?; + /// let mut ciphertext = vec![0u8; message2.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; + /// batch.push((batch, state_handle, ciphertext, ciphertext.len(), message2, message2.len())); + /// + /// let results = ctx.batch_symmetric_state_encrypt_detached(batch)?; + /// ``` + batch-symmetric-state-encrypt-detached: func(batch: list, data>>) -> result; + + /// Perform a batch of symmetric decrypt operations. + /// + /// This is a batch version of the symmetric_state_decrypt operation. + /// + /// Each entry in the batch corresponds to an individual decrypt operation. + /// The parameters associated with each decrypt operation are grouped into a + /// tuple. + /// + /// The batch operation returns an error code of type $crypto_errno that + /// indicates if the batch was processed or if the batch could not be + /// processed. + /// + /// Batch processing error codes: + /// - `success`: Batch was processed. The status of each operation is indicated in the results list. + /// - `not_implemented`: Batch functionality is not supported. + /// - `unsupported_feature`: Inconsistent operations within the batch, e.g. not all operations in the batch use the same algorithm. + /// + /// If the batch was processed, the result is a list of tuples, with each + /// list entry corresponding to the operation in the input list at the same + /// list offset. + /// Each tuple contains a size and a $crypto_errno error code. + /// The error code represents the status of the operation and the size is + /// the actual size of the decrypted data in the output buffer. The size + /// value is only valid if the tuple's error code is `success`. + /// + batch-symmetric-state-decrypt: func(batch: list, data>>) -> result; + + /// Perform a batch of symmetric decrypt operations with detached tags. + /// + /// This is a batch version of the symmetric_state_decrypt_detached operation. + /// + /// Each entry in the batch corresponds to an individual decrypt operation. + /// The parameters associated with each decrypt operation are grouped into a + /// tuple. + /// + /// The batch operation returns an error code of type $crypto_errno that + /// indicates if the batch was processed or if the batch could not be + /// processed. + /// + /// Batch processing error codes: + /// - `success`: Batch was processed. The status of each operation is indicated in the results list. + /// - `not_implemented`: Batch functionality is not supported. + /// - `unsupported_feature`: Inconsistent operations within the batch, e.g. not all operations in the batch use the same algorithm. + /// + /// If the batch was processed, the result is a list of tuples, with each + /// list entry corresponding to the operation in the input list at the same + /// list offset. + /// Each tuple contains a size and a $crypto_errno error code. + /// The error code represents the status of the operation and the size is + /// the actual size of the decrypted data in the output buffer. The size + /// value is only valid if the tuple's error code is `success`. + /// + batch-symmetric-state-decrypt-detached: func(batch: list, data, raw-tag>>) -> result; +} diff --git a/wit/wasi_ephemeral_crypto_asymmetric_common.wit b/wit/wasi_ephemeral_crypto_asymmetric_common.wit new file mode 100644 index 0000000..1e6c855 --- /dev/null +++ b/wit/wasi_ephemeral_crypto_asymmetric_common.wit @@ -0,0 +1,185 @@ +package wasi:crypto@0.11.0; + +interface wasi-ephemeral-crypto-asymmetric-common { + use wasi-ephemeral-crypto-common.{algorithm-type, options, keypair, crypto-errno, size, keypair-encoding, secrets-manager, version, publickey, secretkey, array-output, publickey-encoding, secretkey-encoding}; + + type kp-id = list; + + /// Generate a new key pair. + /// + /// Internally, a key pair stores the supplied algorithm and optional parameters. + /// + /// Trying to use that key pair with different parameters will throw an `invalid_key` error. + /// + /// This function may return `$crypto_errno.unsupported_feature` if key generation is not supported by the host for the chosen algorithm. + /// + /// The function may also return `unsupported_algorithm` if the algorithm is not supported by the host. + /// + /// Finally, if generating that type of key pair is an expensive operation, the function may return `in_progress`. + /// In that case, the guest should retry with the same parameters until the function completes. + /// + /// Example usage: + /// + /// ```rust + /// let kp_handle = ctx.keypair_generate(AlgorithmType::Signatures, "RSA_PKCS1_2048_SHA256", None)?; + /// ``` + keypair-generate: func(algorithm-type: algorithm-type, algorithm: string, options: option>) -> result; + + /// Import a key pair. + /// + /// This function creates a `keypair` object from existing material. + /// + /// It may return `unsupported_algorithm` if the encoding scheme is not supported, or `invalid_key` if the key cannot be decoded. + /// + /// The function may also return `unsupported_algorithm` if the algorithm is not supported by the host. + /// + /// Example usage: + /// + /// ```rust + /// let kp_handle = ctx.keypair_import(AlgorithmType::Signatures, "RSA_PKCS1_2048_SHA256", KeypairEncoding::PKCS8)?; + /// ``` + keypair-import: func(algorithm-type: algorithm-type, algorithm: string, encoded: list, encoding: keypair-encoding) -> result; + + /// __(optional)__ + /// Generate a new managed key pair. + /// + /// The key pair is generated and stored by the secrets management facilities. + /// + /// It may be used through its identifier, but the host may not allow it to be exported. + /// + /// The function returns the `unsupported_feature` error code if secrets management facilities are not supported by the host, + /// or `unsupported_algorithm` if a key cannot be created for the chosen algorithm. + /// + /// The function may also return `unsupported_algorithm` if the algorithm is not supported by the host. + /// + /// This is also an optional import, meaning that the function may not even exist. + keypair-generate-managed: func(secrets-manager: borrow, algorithm-type: algorithm-type, algorithm: string, options: option>) -> result; + + /// __(optional)__ + /// Store a key pair into the secrets manager. + /// + /// On success, the function returns the key pair identifier + keypair-store-managed: func(secrets-manager: borrow, kp: borrow) -> result; + + /// __(optional)__ + /// Replace a managed key pair. + /// + /// This function creates a new version of a managed key pair, by replacing `$kp_old` with `$kp_new`. + /// + /// It does several things: + /// + /// - The key identifier for `$kp_new` is set to the one of `$kp_old`. + /// - A new, unique version identifier is assigned to `$kp_new`. This version will be equivalent to using `$version_latest` until the key is replaced. + /// - The `$kp_old` handle is closed. + /// + /// Both keys must share the same algorithm and have compatible parameters. If this is not the case, `incompatible_keys` is returned. + /// + /// The function may also return the `unsupported_feature` error code if secrets management facilities are not supported by the host, + /// or if keys cannot be rotated. + /// + /// Finally, `prohibited_operation` can be returned if `$kp_new` wasn't created by the secrets manager, and the secrets manager prohibits imported keys. + /// + /// If the operation succeeded, the new version is returned. + /// + /// This is an optional import, meaning that the function may not even exist. + keypair-replace-managed: func(secrets-manager: borrow, kp-old: keypair, kp-new: borrow) -> result; + + /// __(optional)__ + /// Return the key pair identifier and version of a managed key pair. + /// + /// If the key pair is not managed, `unsupported_feature` is returned instead. + /// + /// This is an optional import, meaning that the function may not even exist. + keypair-id: func(kp: borrow) -> result, crypto-errno>; + + /// __(optional)__ + /// Return a managed key pair from a key identifier. + /// + /// `kp_version` can be set to `version_latest` to retrieve the most recent version of a key pair. + /// + /// If no key pair matching the provided information is found, `not_found` is returned instead. + /// + /// This is an optional import, meaning that the function may not even exist. + keypair-from-id: func(secrets-manager: borrow, kp-id: kp-id, kp-version: version) -> result; + + /// Create a key pair from a public key and a secret key. + keypair-from-pk-and-sk: func(publickey: borrow, secretkey: borrow) -> result; + + /// Export a key pair as the given encoding format. + /// + /// May return `prohibited_operation` if this operation is denied or `unsupported_encoding` if the encoding is not supported. + keypair-export: func(kp: borrow, encoding: keypair-encoding) -> result; + + /// Get the public key of a key pair. + keypair-publickey: func(kp: borrow) -> result; + + /// Get the secret key of a key pair. + keypair-secretkey: func(kp: borrow) -> result; + + /// Destroy a key pair. + /// + /// The host will automatically wipe traces of the secret key from memory. + /// + /// If this is a managed key, the key will not be removed from persistent storage, and can be reconstructed later using the key identifier. + keypair-close: func(kp: keypair) -> result<_, crypto-errno>; + + /// Import a public key. + /// + /// The function may return `unsupported_encoding` if importing from the given format is not implemented or incompatible with the key type. + /// + /// It may also return `invalid_key` if the key doesn't appear to match the supplied algorithm. + /// + /// Finally, the function may return `unsupported_algorithm` if the algorithm is not supported by the host. + /// + /// Example usage: + /// + /// ```rust + /// let pk_handle = ctx.publickey_import(AlgorithmType::Signatures, encoded, PublicKeyEncoding::Sec)?; + /// ``` + publickey-import: func(algorithm-type: algorithm-type, algorithm: string, encoded: list, encoding: publickey-encoding) -> result; + + /// Export a public key as the given encoding format. + /// + /// May return `unsupported_encoding` if the encoding is not supported. + publickey-export: func(pk: borrow, encoding: publickey-encoding) -> result; + + /// Check that a public key is valid and in canonical form. + /// + /// This function may perform stricter checks than those made during importation at the expense of additional CPU cycles. + /// + /// The function returns `invalid_key` if the public key didn't pass the checks. + publickey-verify: func(pk: borrow) -> result<_, crypto-errno>; + + /// Compute the public key for a secret key. + publickey-from-secretkey: func(sk: borrow) -> result; + + /// Destroy a public key. + /// + /// Objects are reference counted. It is safe to close an object immediately after the last function needing it is called. + publickey-close: func(pk: publickey) -> result<_, crypto-errno>; + + /// Import a secret key. + /// + /// The function may return `unsupported_encoding` if importing from the given format is not implemented or incompatible with the key type. + /// + /// It may also return `invalid_key` if the key doesn't appear to match the supplied algorithm. + /// + /// Finally, the function may return `unsupported_algorithm` if the algorithm is not supported by the host. + /// + /// Example usage: + /// + /// ```rust + /// let pk_handle = ctx.secretkey_import(AlgorithmType::KX, encoded, SecretKeyEncoding::Raw)?; + /// ``` + secretkey-import: func(algorithm-type: algorithm-type, algorithm: string, encoded: list, encoding: secretkey-encoding) -> result; + + /// Export a secret key as the given encoding format. + /// + /// May return `unsupported_encoding` if the encoding is not supported. + secretkey-export: func(sk: borrow, encoding: secretkey-encoding) -> result; + + /// Destroy a secret key. + /// + /// Objects are reference counted. It is safe to close an object immediately after the last function needing it is called. + secretkey-close: func(sk: secretkey) -> result<_, crypto-errno>; +} diff --git a/wit/wasi_ephemeral_crypto_common.wit b/wit/wasi_ephemeral_crypto_common.wit new file mode 100644 index 0000000..f3dcb44 --- /dev/null +++ b/wit/wasi_ephemeral_crypto_common.wit @@ -0,0 +1,408 @@ +package wasi:crypto@0.11.0; + +interface wasi-ephemeral-crypto-common { + type key-id = list; + + /// Error codes. + variant crypto-errno { + /// Operation succeeded. + success, + + /// An error occurred during a conversion from a host type to a guest type. + /// + /// Only an internal bug can throw this error. + guest-error, + + /// The requested operation is valid, but not implemented by the host. + not-implemented, + + /// The requested feature is not supported by the chosen algorithm. + unsupported-feature, + + /// The requested operation is valid, but was administratively prohibited. + prohibited-operation, + + /// Unsupported encoding for an import or export operation. + unsupported-encoding, + + /// The requested algorithm is not supported by the host. + unsupported-algorithm, + + /// The requested option is not supported by the currently selected algorithm. + unsupported-option, + + /// An invalid or incompatible key was supplied. + /// + /// The key may not be valid, or was generated for a different algorithm or parameters set. + invalid-key, + + /// The currently selected algorithm doesn't support the requested output length. + /// + /// This error is thrown by non-extensible hash functions, when requesting an output size larger than they produce out of a single block. + invalid-length, + + /// A signature or authentication tag verification failed. + verification-failed, + + /// A secure random numbers generator is not available. + /// + /// The requested operation requires random numbers, but the host cannot securely generate them at the moment. + rng-error, + + /// An error was returned by the underlying cryptography library. + /// + /// The host may be running out of memory, parameters may be incompatible with the chosen implementation of an algorithm or another unexpected error may have happened. + /// + /// Ideally, the specification should provide enough details and guidance to make this error impossible to ever be thrown. + /// + /// Realistically, the WASI crypto module cannot possibly cover all possible error types implementations can return, especially since some of these may be language-specific. + /// This error can thus be thrown when other error types are not suitable, and when the original error comes from the cryptographic primitives themselves and not from the WASI module. + algorithm-failure, + + /// The supplied signature is invalid, or incompatible with the chosen algorithm. + invalid-signature, + + /// An attempt was made to close a handle that was already closed. + closed, + + /// A function was called with an unassigned handle, a closed handle, or handle of an unexpected type. + invalid-handle, + + /// The host needs to copy data to a guest-allocated buffer, but that buffer is too small. + overflow, + + /// An internal error occurred. + /// + /// This error is reserved to internal consistency checks, and must only be sent if the internal state of the host remains safe after an inconsistency was detected. + internal-error, + + /// Too many handles are currently open, and a new one cannot be created. + /// + /// Implementations are free to represent handles as they want, and to enforce limits to limit resources usage. + too-many-handles, + + /// A key was provided, but the chosen algorithm doesn't support keys. + /// + /// This is returned by symmetric operations. + /// + /// Many hash functions, in particular, do not support keys without being used in particular constructions. + /// Blindly ignoring a key provided by mistake while trying to open a context for such as function could cause serious security vulnerabilities. + /// + /// These functions must refuse to create the context and return this error instead. + key-not-supported, + + /// A key is required for the chosen algorithm, but none was given. + key-required, + + /// The provided authentication tag is invalid or incompatible with the current algorithm. + /// + /// This error is returned by decryption functions and tag verification functions. + /// + /// Unlike `verification_failed`, this error code is returned when the tag cannot possibly verify for any input. + invalid-tag, + + /// The requested operation is incompatible with the current scheme. + /// + /// For example, the `symmetric_state_encrypt()` function cannot complete if the selected construction is a key derivation function. + /// This error code will be returned instead. + invalid-operation, + + /// A nonce is required. + /// + /// Most encryption schemes require a nonce. + /// + /// In the absence of a nonce, the WASI cryptography module can automatically generate one, if that can be done safely. The nonce can be retrieved later with the `symmetric_state_option_get()` function using the `nonce` parameter. + /// If automatically generating a nonce cannot be done safely, the module never falls back to an insecure option and requests an explicit nonce by throwing that error. + nonce-required, + + /// The provided nonce doesn't have a correct size for the given cipher. + invalid-nonce, + + /// The named option was not set. + /// + /// The caller tried to read the value of an option that was not set. + /// This error is used to make the distinction between an empty option, and an option that was not set and left to its default value. + option-not-set, + + /// A key or key pair matching the requested identifier cannot be found using the supplied information. + /// + /// This error is returned by a secrets manager via the `keypair_from_id()` function. + not-found, + + /// The algorithm requires parameters that haven't been set. + /// + /// Non-generic options are required and must be given by building an `options` set and giving that object to functions instantiating that algorithm. + parameters-missing, + + /// A requested computation is not done yet, and additional calls to the function are required. + /// + /// Some functions, such as functions generating key pairs and password stretching functions, can take a long time to complete. + /// + /// In order to avoid a host call to be blocked for too long, these functions can return prematurely, requiring additional calls with the same parameters until they complete. + in-progress, + + /// Multiple keys have been provided, but they do not share the same type. + /// + /// This error is returned when trying to build a key pair from a public key and a secret key that were created for different and incompatible algorithms. + incompatible-keys, + + /// A managed key or secret expired and cannot be used any more. + expired, + } + /// Encoding to use for importing or exporting a key pair. + enum keypair-encoding { + /// Raw bytes. + raw, + /// PKCS8/DER encoding. + pkcs8, + /// PEM encoding. + pem, + /// Implementation-defined encoding. + local, + } + + /// Encoding to use for importing or exporting a public key. + enum publickey-encoding { + /// Raw bytes. + raw, + /// PKCS8/DER encoding. + pkcs8, + /// PEM encoding. + pem, + /// SEC-1 encoding. + sec, + /// Implementation-defined encoding. + local, + } + + /// Encoding to use for importing or exporting a secret key. + enum secretkey-encoding { + /// Raw bytes. + raw, + /// PKCS8/DER encoding. + pkcs8, + /// PEM encoding. + pem, + /// SEC encoding. + sec, + /// Implementation-defined encoding. + local, + } + + /// Encoding to use for importing or exporting a signature. + enum signature-encoding { + /// Raw bytes. + raw, + /// DER encoding. + der, + } + + /// An algorithm category. + enum algorithm-type { + signatures, + symmetric, + key-exchange, + } + + /// Version of a managed key. + variant version { + /// Key doesn't support versioning. + unspecified, + /// Use the latest version of a key. + latest, + /// Perform an operation over all versions of a key. + all, + /// Specific version specifier + specified(u64) + } + + /// Size of a value. + type size = u32; + + /// A UNIX timestamp, in seconds since 01/01/1970. + type timestamp = u64; + + /// A 64-bit value + type %u64 = u64; + + /// Handle for functions returning output whose size may be large or not known in advance. + /// + /// An `array_output` object contains a host-allocated byte array. + /// + /// A guest can get the size of that array after a function returns in order to then allocate a buffer of the correct size. + /// In addition, the content of such an object can be consumed by a guest in a streaming fashion. + /// + /// An `array_output` handle is automatically closed after its full content has been consumed. + resource array-output {} + + /// A set of options. + /// + /// This type is used to set non-default parameters. + /// + /// The exact set of allowed options depends on the algorithm being used. + resource options {} + + /// A handle to the optional secrets management facilities offered by a host. + /// + /// This is used to generate, retrieve and invalidate managed keys. + resource secrets-manager {} + + /// A key pair. + resource keypair {} + + /// A state to absorb data to be signed. + /// + /// After a signature has been computed or verified, the state remains valid for further operations. + /// + /// A subsequent signature would sign all the data accumulated since the creation of the state object. + resource signature-state {} + + /// A signature. + resource signature {} + + /// A public key, for key exchange and signature verification. + resource publickey {} + + /// A secret key, for key exchange mechanisms. + resource secretkey {} + + /// A state to absorb signed data to be verified. + resource signature-verification-state {} + + /// A state to perform symmetric operations. + /// + /// The state is not reset nor invalidated after an option has been performed. + /// Incremental updates and sessions are thus supported. + resource symmetric-state {} + + /// A symmetric key. + /// + /// The key can be imported from raw bytes, or can be a reference to a managed key. + /// + /// If it was imported, the host will wipe it from memory as soon as the handle is closed. + resource symmetric-key {} + + /// An authentication tag. + /// + /// This is an object returned by functions computing authentication tags. + /// + /// A tag can be compared against another tag (directly supplied as raw bytes) in constant time with the `symmetric_tag_verify()` function. + /// + /// This object type can't be directly created from raw bytes. They are only returned by functions computing MACs. + /// + /// The host is responsible for securely wiping them from memory on close. + resource symmetric-tag {} + + /// Options index, only required by the Interface Types translation layer. + enum opt-options-u { + some, + none, + } + + /// An optional options set. + /// + /// This union simulates an `Option` type to make the `options` parameter of some functions optional. + @deprecated(version = 0.11.0) + @since(version = 0.11.0) + type opt-options = option; + + /// Symmetric key index, only required by the Interface Types translation layer. + enum opt-symmetric-key-u { + some, + none, + } + + /// An optional symmetric key. + /// + /// This union simulates an `Option` type to make the `symmetric_key` parameter of some functions optional. + @deprecated(version = 0.11.0) + @since(version = 0.11.0) + type opt-symmetric-key = option; + + /// Create a new object to set non-default options. + /// + /// Example usage: + /// + /// ```rust + /// let options_handle = options_open(AlgorithmType::Symmetric)?; + /// options_set(options_handle, "context", context)?; + /// options_set_u64(options_handle, "threads", 4)?; + /// let state = symmetric_state_open("BLAKE3", None, Some(options_handle))?; + /// options_close(options_handle)?; + /// ``` + options-open: func(algorithm-type: algorithm-type) -> result; + + /// Destroy an options object. + options-close: func(options: options) -> result<_, crypto-errno>; + + /// Set or update an option. + /// + /// This is used to set algorithm-specific parameters, but also to provide credentials for the secrets management facilities, if required. + /// + /// This function may return `unsupported_option` if an option that doesn't exist for any implemented algorithms is specified. + options-set: func(options: borrow, name: string, value: list) -> result<_, crypto-errno>; + + /// Set or update an integer option. + /// + /// This is used to set algorithm-specific parameters. + /// + /// This function may return `unsupported_option` if an option that doesn't exist for any implemented algorithms is specified. + options-set-u64: func(options: borrow, name: string, value: u64) -> result<_, crypto-errno>; + + /// Set or update buffer that the host can use or return data into. + /// + /// This is for example used to set the scratch buffer required by memory-hard functions. + /// + /// NOTE: In WITX, this was a raw pointer into guest linear memory, ensuring the allocation counted against the guest's memory budget. + /// This implementation using `list` does not have this guarantee and serves as a stopgap till [caller-supplied buffers](https://wasi.dev/roadmap) arrive in future WASI 0.3.x release. + /// + /// This function may return `unsupported_option` if an option that doesn't exist for any implemented algorithms is specified. + @deprecated(version = 0.11.0) + @since(version = 0.11.0) + options-set-guest-buffer: func(options: borrow, name: string, buffer: list) -> result<_, crypto-errno>; + + /// Return the length of an `array_output` object. + /// + /// This allows a guest to allocate a buffer of the correct size in order to copy the output of a function returning this object type. + array-output-len: func(array-output: borrow) -> result; + + /// Copy the content of an `array_output` object into an application-allocated buffer. + /// + /// The function returns the copied bytes. The handle is consumed and becomes invalid after this call. + /// + /// Example usage: + /// + /// ```rust + /// let len = array_output_len(output_handle)?; + /// let out = array_output_pull(output_handle)?; + /// ``` + array-output-pull: func(array-output: array-output) -> result, crypto-errno>; + + /// __(optional)__ + /// Create a context to use a secrets manager. + /// + /// The set of required and supported options is defined by the host. + /// + /// The function returns the `unsupported_feature` error code if secrets management facilities are not supported by the host. + /// This is also an optional import, meaning that the function may not even exist. + secrets-manager-open: func(options: option>) -> result; + + /// __(optional)__ + /// Destroy a secrets manager context. + /// + /// The function returns the `unsupported_feature` error code if secrets management facilities are not supported by the host. + /// This is also an optional import, meaning that the function may not even exist. + secrets-manager-close: func(secrets-manager: secrets-manager) -> result<_, crypto-errno>; + + /// __(optional)__ + /// Invalidate a managed key or key pair given an identifier and a version. + /// + /// This asks the secrets manager to delete or revoke a stored key, a specific version of a key. + /// + /// `key_version` can be set to a version number, to `version.latest` to invalidate the current version, or to `version.all` to invalidate all versions of a key. + /// + /// The function returns `unsupported_feature` if this operation is not supported by the host, and `not_found` if the identifier and version don't match any existing key. + /// + /// This is an optional import, meaning that the function may not even exist. + secrets-manager-invalidate: func(secrets-manager: borrow, key-id: key-id, key-version: version) -> result<_, crypto-errno>; +} diff --git a/wit/wasi_ephemeral_crypto_external_secrets.wit b/wit/wasi_ephemeral_crypto_external_secrets.wit new file mode 100644 index 0000000..11c8b0e --- /dev/null +++ b/wit/wasi_ephemeral_crypto_external_secrets.wit @@ -0,0 +1,71 @@ +package wasi:crypto@0.11.0; + +/// External secrets storage. +/// +/// External secrets are binary blobs, that can represent external API tokens or anything that is not meant to be consumed by the wasi-crypto APIs. +/// These secrets can be securely stored, and then retrieved using an identifier. +/// +/// Alternatively, the secrets manager can encrypt them, and applications will supply the ciphertext get the original secret back. +/// +/// The whole interface is optional. +/// +/// __(optional)__ +interface wasi-ephemeral-crypto-external-secrets { + use wasi-ephemeral-crypto-common.{secrets-manager, size, timestamp, crypto-errno, version, array-output}; + + type secret-id = list; + + /// Store an external secret into the secrets manager. + /// + /// `expiration` is the expiration date of the secret as a UNIX timestamp, in seconds. + /// An expiration date is mandatory. + /// + /// On success, the secret identifier is returned. + /// + /// If this function is not supported by the host the `$unsupported_feature` error is returned. + external-secret-store: func(secrets-manager: borrow, secret: list, expiration: timestamp) -> result; + + /// Replace a managed external secret with a new version. + /// + /// `expiration` is the expiration date of the secret as a UNIX timestamp, in seconds. + /// An expiration date is mandatory. + /// + /// On success, a new version is created and returned. + /// + /// If this function is not supported by the host the `$unsupported_feature` error is returned. + external-secret-replace: func(secrets-manager: borrow, secret: list, expiration: timestamp) -> result, crypto-errno>; + + /// Get a copy of an external secret given an identifier and version. + /// + /// `secret_version` can be set to a version number, or to `version.latest` to retrieve the most recent version of a secret. + /// + /// On success, a copy of the secret is returned. + /// + /// The function returns `$unsupported_feature` if this operation is not supported by the host, and `not_found` if the identifier and version don't match any existing secret. + external-secret-from-id: func(secrets-manager: borrow, secret-id: secret-id, secret-version: version) -> result; + + /// Invalidate an external secret given an identifier and a version. + /// + /// This asks the secrets manager to delete or revoke a stored secret, a specific version of a secret. + /// + /// `secret_version` can be set to a version number, or to `version.latest` to invalidate the current version, or to `version.all` to invalidate all versions of a secret. + /// + /// The function returns `$unsupported_feature` if this operation is not supported by the host, and `not_found` if the identifier and version don't match any existing secret. + external-secret-invalidate: func(secrets-manager: borrow, secret-id: secret-id, secret-version: version) -> result<_, crypto-errno>; + + /// Encrypt an external secret. + /// + /// Applications don't have access to the encryption key, and the secrets manager is free to choose any suitable algorithm. + /// + /// However, the returned ciphertext must include and authenticate both the secret and the expiration date. + /// + /// On success, the ciphertext is returned. + external-secret-encapsulate: func(secrets-manager: borrow, secret: list, expiration: timestamp) -> result; + + /// Decrypt an external secret previously encrypted by the secrets manager. + /// + /// Returns the original secret if the ciphertext is valid. + /// Returns `$expired` if the current date is past the stored expiration date. + /// Returns `$verification_failed` if the ciphertext format is invalid or if its authentication tag couldn't be verified. + external-secret-decapsulate: func(secrets-manager: borrow, encrypted-secret: list) -> result; +} diff --git a/wit/wasi_ephemeral_crypto_kx.wit b/wit/wasi_ephemeral_crypto_kx.wit new file mode 100644 index 0000000..cf08ab9 --- /dev/null +++ b/wit/wasi_ephemeral_crypto_kx.wit @@ -0,0 +1,41 @@ +package wasi:crypto@0.11.0; + +interface wasi-ephemeral-crypto-kx { + use wasi-ephemeral-crypto-common.{keypair, publickey, secretkey, array-output, crypto-errno, size}; + + /// `$kx_keypair` is just an alias for `$keypair` + /// + /// However, bindings may want to define a specialized type `kx_keypair` as a super class of `keypair`. + type kx-keypair = keypair; + + /// `$kx_publickey` is just an alias for `$publickey` + /// + /// However, bindings may want to define a specialized type `kx_publickey` as a super class of `publickey`, with additional methods such as `dh`. + type kx-publickey = publickey; + + /// `$kx_secretkey` is just an alias for `$secretkey` + /// + /// However, bindings may want to define a specialized type `kx_secretkey` as a super class of `secretkey`, with additional methods such as `dh`. + type kx-secretkey = secretkey; + + /// Perform a simple Diffie-Hellman key exchange. + /// + /// Both keys must be of the same type, or else the `$crypto_errno.incompatible_keys` error is returned. + /// The algorithm also has to support this kind of key exchange. If this is not the case, the `$crypto_errno.invalid_operation` error is returned. + /// + /// Otherwise, a raw shared key is returned, and can be imported as a symmetric key. + kx-dh: func(pk: borrow, sk: borrow) -> result; + + /// Create a shared secret and encrypt it for the given public key. + /// + /// This operation is only compatible with specific algorithms. + /// If a selected algorithm doesn't support it, `$crypto_errno.invalid_operation` is returned. + /// + /// On success, both the shared secret and its encrypted version are returned. + kx-encapsulate: func(pk: borrow) -> result, crypto-errno>; + + /// Decapsulate an encapsulated secret created with `kx_encapsulate` + /// + /// Return the secret, or `$crypto_errno.verification_failed` on error. + kx-decapsulate: func(sk: borrow, encapsulated-secret: list) -> result; +} diff --git a/wit/wasi_ephemeral_crypto_signatures.wit b/wit/wasi_ephemeral_crypto_signatures.wit new file mode 100644 index 0000000..2914c27 --- /dev/null +++ b/wit/wasi_ephemeral_crypto_signatures.wit @@ -0,0 +1,118 @@ +package wasi:crypto@0.11.0; + +interface wasi-ephemeral-crypto-signatures { + use wasi-ephemeral-crypto-common.{keypair, publickey, secretkey, signature, signature-encoding, array-output, crypto-errno, size, signature-state, signature-verification-state}; + + /// `$signature_keypair` is just an alias for `$keypair` + /// + /// However, bindings may want to define a specialized type `signature_keypair` as a super class of `keypair`, with additional methods such as `sign`. + type signature-keypair = keypair; + + /// `$signature_publickey` is just an alias for `$publickey` + /// + /// However, bindings may want to define a specialized type `signature_publickey` as a super class of `publickey`, with additional methods such as `verify`. + type signature-publickey = publickey; + + /// `$signature_secretkey` is just an alias for `$secretkey` + /// + /// However, bindings may want to define a specialized type `signature_secretkey` as a super class of `secretkey`. + type signature-secretkey = secretkey; + + /// Export a signature. + /// + /// This function exports a signature object using the specified encoding. + /// + /// May return `unsupported_encoding` if the signature cannot be encoded into the given format. + signature-export: func(signature: borrow, encoding: signature-encoding) -> result; + + /// Create a signature object. + /// + /// This object can be used along with a public key to verify an existing signature. + /// + /// It may return `invalid_signature` if the signature is invalid or incompatible with the specified algorithm, as well as `unsupported_encoding` if the encoding is not compatible with the signature type. + /// + /// The function may also return `unsupported_algorithm` if the algorithm is not supported by the host. + /// + /// Example usage: + /// + /// ```rust + /// let signature_handle = ctx.signature_import("ECDSA_P256_SHA256", SignatureEncoding::DER, encoded)?; + /// ``` + signature-import: func(algorithm: string, encoded: list, encoding: signature-encoding) -> result; + + /// Create a new state to collect data to compute a signature on. + /// + /// This function allows data to be signed to be supplied in a streaming fashion. + /// + /// The state is not closed and can be used after a signature has been computed, allowing incremental updates by calling `signature_state_update()` again afterwards. + /// + /// Example usage - signature creation + /// + /// ```rust + /// let kp_handle = ctx.keypair_import(AlgorithmType::Signatures, "Ed25519ph", keypair, KeypairEncoding::Raw)?; + /// let state_handle = ctx.signature_state_open(kp_handle)?; + /// ctx.signature_state_update(state_handle, b"message part 1")?; + /// ctx.signature_state_update(state_handle, b"message part 2")?; + /// let sig_handle = ctx.signature_state_sign(state_handle)?; + /// let raw_sig = ctx.signature_export(sig_handle, SignatureEncoding::Raw)?; + /// ``` + signature-state-open: func(kp: borrow) -> result; + + /// Absorb data into the signature state. + /// + /// This function may return `unsupported_feature` if the selected algorithm doesn't support incremental updates, + /// or if `signature-state-sign` has already been called on this state (e.g. pure Ed25519, which does not support + /// re-feeding data after a signature has been produced). + signature-state-update: func(state: borrow, input: list) -> result<_, crypto-errno>; + + /// Compute a signature for all the data collected up to that point. + /// + /// The function can be called multiple times for incremental signing. + signature-state-sign: func(state: borrow) -> result; + + /// Destroy a signature state. + /// + /// Objects are reference counted. It is safe to close an object immediately after the last function needing it is called. + /// + /// Note that closing a signature state doesn't close or invalidate the key pair object, that can be reused for further signatures. + signature-state-close: func(state: signature-state) -> result<_, crypto-errno>; + + /// Create a new state to collect data to verify a signature on. + /// + /// This is the verification counterpart of `signature_state`. + /// + /// Data can be injected using `signature_verification_state_update()`, and the state is not closed after a verification, allowing incremental verification. + /// + /// Example usage - signature verification: + /// + /// ```rust + /// let pk_handle = ctx.publickey_import(AlgorithmType::Signatures, "ECDSA_P256_SHA256", encoded_pk, PublicKeyEncoding::Sec)?; + /// let signature_handle = ctx.signature_import("ECDSA_P256_SHA256", encoded_sig, SignatureEncoding::Der)?; + /// let state_handle = ctx.signature_verification_state_open(pk_handle)?; + /// ctx.signature_verification_state_update(state_handle, "message")?; + /// ctx.signature_verification_state_verify(state_handle, signature_handle)?; + /// ``` + signature-verification-state-open: func(kp: borrow) -> result; + + /// Absorb data into the signature verification state. + /// + /// This function may return `unsupported_feature` if the selected algorithm doesn't support incremental updates. + signature-verification-state-update: func(state: borrow, input: list) -> result<_, crypto-errno>; + + /// Check that the given signature verifies for the data collected up to that point. + /// + /// The state is not closed and can absorb more data to allow for incremental verification. + /// + /// The function returns `invalid_signature` if the signature doesn't appear to be valid. + signature-verification-state-verify: func(state: borrow, signature: signature) -> result<_, crypto-errno>; + + /// Destroy a signature verification state. + /// + /// Objects are reference counted. It is safe to close an object immediately after the last function needing it is called. + /// + /// Note that closing a signature state doesn't close or invalidate the public key object, that can be reused for further verifications. + signature-verification-state-close: func(state: signature-verification-state) -> result<_, crypto-errno>; + + /// Destroy a signature. + signature-close: func(signature: signature) -> result<_, crypto-errno>; +} diff --git a/wit/wasi_ephemeral_crypto_symmetric.wit b/wit/wasi_ephemeral_crypto_symmetric.wit new file mode 100644 index 0000000..dda6ff3 --- /dev/null +++ b/wit/wasi_ephemeral_crypto_symmetric.wit @@ -0,0 +1,456 @@ +package wasi:crypto@0.11.0; + +interface wasi-ephemeral-crypto-symmetric { + use wasi-ephemeral-crypto-common.{options, symmetric-key, crypto-errno, size, array-output, secrets-manager, version, symmetric-state, %u64, symmetric-tag}; + + // Would have liked to keep this `symmetric-key-id`, but that overlaps with the function name + type stored-key-id = list; + + /// Generate a new symmetric key for a given algorithm. + /// + /// `options` can be `None` to use the default parameters, or an algorithm-specific set of parameters to override. + /// + /// This function may return `unsupported_feature` if key generation is not supported by the host for the chosen algorithm, or `unsupported_algorithm` if the algorithm is not supported by the host. + symmetric-key-generate: func(algorithm: string, options: option>) -> result; + + /// Create a symmetric key from raw material. + /// + /// The algorithm is internally stored along with the key, and trying to use the key with an operation expecting a different algorithm will return `invalid_key`. + /// + /// The function may also return `unsupported_algorithm` if the algorithm is not supported by the host. + symmetric-key-import: func(algorithm: string, raw: list) -> result; + + /// Export a symmetric key as raw material. + /// + /// This is mainly useful to export a managed key. + /// + /// May return `prohibited_operation` if this operation is denied. + symmetric-key-export: func(symmetric-key: borrow) -> result; + + /// Destroy a symmetric key. + /// + /// Objects are reference counted. It is safe to close an object immediately after the last function needing it is called. + symmetric-key-close: func(symmetric-key: symmetric-key) -> result<_, crypto-errno>; + + /// __(optional)__ + /// Generate a new managed symmetric key. + /// + /// The key is generated and stored by the secrets management facilities. + /// + /// It may be used through its identifier, but the host may not allow it to be exported. + /// + /// The function returns the `unsupported_feature` error code if secrets management facilities are not supported by the host, + /// or `unsupported_algorithm` if a key cannot be created for the chosen algorithm. + /// + /// The function may also return `unsupported_algorithm` if the algorithm is not supported by the host. + /// + /// This is also an optional import, meaning that the function may not even exist. + symmetric-key-generate-managed: func(secrets-manager: borrow, algorithm: string, options: option>) -> result; + + /// __(optional)__ + /// Store a symmetric key into the secrets manager. + /// + /// On success, the function returns the key identifier. + /// + /// This is an optional import, meaning that the function may not even exist. + symmetric-key-store-managed: func(secrets-manager: borrow, symmetric-key: borrow) -> result; + + /// __(optional)__ + /// Replace a managed symmetric key. + /// + /// This function creates a new version of a managed symmetric key, by replacing `$kp_old` with `$kp_new`. + /// + /// It does several things: + /// + /// - The key identifier for `$symmetric_key_new` is set to the one of `$symmetric_key_old`. + /// - A new, unique version identifier is assigned to `$kp_new`. This version will be equivalent to using `$version_latest` until the key is replaced. + /// - The `$symmetric_key_old` handle is closed. + /// + /// Both keys must share the same algorithm and have compatible parameters. If this is not the case, `incompatible_keys` is returned. + /// + /// The function may also return the `unsupported_feature` error code if secrets management facilities are not supported by the host, + /// or if keys cannot be rotated. + /// + /// Finally, `prohibited_operation` can be returned if `$symmetric_key_new` wasn't created by the secrets manager, and the secrets manager prohibits imported keys. + /// + /// If the operation succeeded, the new version is returned. + /// + /// This is an optional import, meaning that the function may not even exist. + symmetric-key-replace-managed: func(secrets-manager: borrow, symmetric-key-old: symmetric-key, symmetric-key-new: borrow) -> result; + + /// __(optional)__ + /// Return the key identifier and version of a managed symmetric key. + /// + /// If the key is not managed, `unsupported_feature` is returned instead. + /// + /// This is an optional import, meaning that the function may not even exist. + symmetric-key-id: func(symmetric-key: borrow) -> result, crypto-errno>; + + /// __(optional)__ + /// Return a managed symmetric key from a key identifier. + /// + /// `kp_version` can be set to `version_latest` to retrieve the most recent version of a symmetric key. + /// + /// If no key matching the provided information is found, `not_found` is returned instead. + /// + /// This is an optional import, meaning that the function may not even exist. + symmetric-key-from-id: func(secrets-manager: borrow, symmetric-key-id: stored-key-id, symmetric-key-version: version) -> result; + + /// Create a new state to absorb and produce data using symmetric operations. + /// + /// The state remains valid after every operation in order to support incremental updates. + /// + /// The function has two optional parameters: a key and an options set. + /// + /// It will fail with a `key_not_supported` error code if a key was provided but the chosen algorithm doesn't natively support keying. + /// + /// On the other hand, if a key is required, but was not provided, a `key_required` error will be thrown. + /// + /// Some algorithms may require additional parameters. They have to be supplied as an options set: + /// + /// ```rust + /// let options_handle = ctx.options_open()?; + /// ctx.options_set("context", b"My application")?; + /// ctx.options_set_u64("fanout", 16)?; + /// let state_handle = ctx.symmetric_state_open("BLAKE2b-512", None, Some(options_handle))?; + /// ``` + /// + /// If some parameters are mandatory but were not set, the `parameters_missing` error code will be returned. + /// + /// A notable exception is the `nonce` parameter, that is common to most AEAD constructions. + /// + /// If a nonce is required but was not supplied: + /// + /// - If it is safe to do so, the host will automatically generate a nonce. This is true for nonces that are large enough to be randomly generated, or if the host is able to maintain a global counter. + /// - If not, the function will fail and return the dedicated `nonce_required` error code. + /// + /// A nonce that was automatically generated can be retrieved after the function returns with `symmetric_state_get(state_handle, "nonce")`. + /// + /// **Sample usage patterns:** + /// + /// - **Hashing** + /// + /// ```rust + /// let mut out = [0u8; 64]; + /// let state_handle = ctx.symmetric_state_open("SHAKE-128", None, None)?; + /// ctx.symmetric_state_absorb(state_handle, b"data")?; + /// ctx.symmetric_state_absorb(state_handle, b"more_data")?; + /// ctx.symmetric_state_squeeze(state_handle, &mut out)?; + /// ``` + /// + /// - **MAC** + /// + /// ```rust + /// let mut raw_tag = [0u8; 64]; + /// let key_handle = ctx.symmetric_key_import("HMAC/SHA-512", b"key")?; + /// let state_handle = ctx.symmetric_state_open("HMAC/SHA-512", Some(key_handle), None)?; + /// ctx.symmetric_state_absorb(state_handle, b"data")?; + /// ctx.symmetric_state_absorb(state_handle, b"more_data")?; + /// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; + /// ctx.symmetric_tag_pull(computed_tag_handle, &mut raw_tag)?; + /// ``` + /// + /// Verification: + /// + /// ```rust + /// let state_handle = ctx.symmetric_state_open("HMAC/SHA-512", Some(key_handle), None)?; + /// ctx.symmetric_state_absorb(state_handle, b"data")?; + /// ctx.symmetric_state_absorb(state_handle, b"more_data")?; + /// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; + /// ctx.symmetric_tag_verify(computed_tag_handle, expected_raw_tag)?; + /// ``` + /// + /// - **Tuple hashing** + /// + /// ```rust + /// let mut out = [0u8; 64]; + /// let state_handle = ctx.symmetric_state_open("TupleHashXOF256", None, None)?; + /// ctx.symmetric_state_absorb(state_handle, b"value 1")?; + /// ctx.symmetric_state_absorb(state_handle, b"value 2")?; + /// ctx.symmetric_state_absorb(state_handle, b"value 3")?; + /// ctx.symmetric_state_squeeze(state_handle, &mut out)?; + /// ``` + /// Unlike MACs and regular hash functions, inputs are domain separated instead of being concatenated. + /// + /// - **Key derivation using extract-and-expand** + /// + /// Extract: + /// + /// ```rust + /// let mut prk = vec![0u8; 64]; + /// let key_handle = ctx.symmetric_key_import("HKDF-EXTRACT/SHA-512", b"key")?; + /// let state_handle = ctx.symmetric_state_open("HKDF-EXTRACT/SHA-512", Some(key_handle), None)?; + /// ctx.symmetric_state_absorb(state_handle, b"salt")?; + /// let prk_handle = ctx.symmetric_state_squeeze_key(state_handle, "HKDF-EXPAND/SHA-512")?; + /// ``` + /// + /// Expand: + /// + /// ```rust + /// let mut subkey = vec![0u8; 32]; + /// let state_handle = ctx.symmetric_state_open("HKDF-EXPAND/SHA-512", Some(prk_handle), None)?; + /// ctx.symmetric_state_absorb(state_handle, b"info")?; + /// ctx.symmetric_state_squeeze(state_handle, &mut subkey)?; + /// ``` + /// + /// - **Key derivation using a XOF** + /// + /// ```rust + /// let mut subkey1 = vec![0u8; 32]; + /// let mut subkey2 = vec![0u8; 32]; + /// let key_handle = ctx.symmetric_key_import("BLAKE3", b"key")?; + /// let state_handle = ctx.symmetric_state_open("BLAKE3", Some(key_handle), None)?; + /// ctx.symmetric_absorb(state_handle, b"context")?; + /// ctx.squeeze(state_handle, &mut subkey1)?; + /// ctx.squeeze(state_handle, &mut subkey2)?; + /// ``` + /// + /// - **Password hashing** + /// + /// ```rust + /// let mut memory = vec![0u8; 1_000_000_000]; + /// let options_handle = ctx.symmetric_options_open()?; + /// ctx.symmetric_options_set_guest_buffer(options_handle, "memory", &mut memory)?; + /// ctx.symmetric_options_set_u64(options_handle, "opslimit", 5)?; + /// ctx.symmetric_options_set_u64(options_handle, "parallelism", 8)?; + /// + /// let state_handle = ctx.symmetric_state_open("ARGON2-ID-13", None, Some(options))?; + /// ctx.symmetric_state_absorb(state_handle, b"password")?; + /// + /// let pw_str_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; + /// let mut pw_str = vec![0u8; ctx.symmetric_tag_len(pw_str_handle)?]; + /// ctx.symmetric_tag_pull(pw_str_handle, &mut pw_str)?; + /// ``` + /// + /// - **AEAD encryption with an explicit nonce** + /// + /// ```rust + /// let key_handle = ctx.symmetric_key_generate("AES-256-GCM", None)?; + /// let message = b"test"; + /// + /// let options_handle = ctx.symmetric_options_open()?; + /// ctx.symmetric_options_set(options_handle, "nonce", nonce)?; + /// + /// let state_handle = ctx.symmetric_state_open("AES-256-GCM", Some(key_handle), Some(options_handle))?; + /// let mut ciphertext = vec![0u8; message.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; + /// ctx.symmetric_state_absorb(state_handle, "additional data")?; + /// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, message)?; + /// ``` + /// + /// - **AEAD encryption with automatic nonce generation** + /// + /// ```rust + /// let key_handle = ctx.symmetric_key_generate("AES-256-GCM-SIV", None)?; + /// let message = b"test"; + /// let mut nonce = [0u8; 24]; + /// + /// let state_handle = ctx.symmetric_state_open("AES-256-GCM-SIV", Some(key_handle), None)?; + /// + /// let nonce = ctx.symmetric_state_options_get(state_handle, "nonce")?; + /// + /// let mut ciphertext = vec![0u8; message.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; + /// ctx.symmetric_state_absorb(state_handle, "additional data")?; + /// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, message)?; + /// ``` + /// + /// - **Session authenticated modes** + /// + /// ```rust + /// let mut out = [0u8; 16]; + /// let mut out2 = [0u8; 16]; + /// let mut ciphertext = [0u8; 20]; + /// let key_handle = ctx.symmetric_key_generate("Xoodyak-128", None)?; + /// let state_handle = ctx.symmetric_state_open("Xoodyak-128", Some(key_handle), None)?; + /// ctx.symmetric_state_absorb(state_handle, b"data")?; + /// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, b"abcd")?; + /// ctx.symmetric_state_absorb(state_handle, b"more data")?; + /// ctx.symmetric_state_squeeze(state_handle, &mut out)?; + /// ctx.symmetric_state_squeeze(state_handle, &mut out2)?; + /// ctx.symmetric_state_ratchet(state_handle)?; + /// ctx.symmetric_state_absorb(state_handle, b"more data")?; + /// let next_key_handle = ctx.symmetric_state_squeeze_key(state_handle, "Xoodyak-128")?; + /// // ... + /// ``` + symmetric-state-open: func(algorithm: string, key: option>, options: option>) -> result; + + /// Retrieve a parameter from the current state. + /// + /// In particular, `symmetric_state_options_get("nonce")` can be used to get a nonce that as automatically generated. + /// + /// The function may return `options_not_set` if an option was not set, which is different from an empty value. + /// + /// It may also return `unsupported_option` if the option doesn't exist for the chosen algorithm. + symmetric-state-options-get: func(state: borrow, name: string) -> result, crypto-errno>; + + /// Retrieve an integer parameter from the current state. + /// + /// The function may return `options_not_set` if an option was not set. + /// + /// It may also return `unsupported_option` if the option doesn't exist for the chosen algorithm. + symmetric-state-options-get-u64: func(state: borrow, name: string) -> result<%u64, crypto-errno>; + + /// Clone a symmetric state. + /// + /// The function clones the internal state, assigns a new handle to it and returns the new handle. + symmetric-state-clone: func(state: borrow) -> result; + + /// Destroy a symmetric state. + /// + /// Objects are reference counted. It is safe to close an object immediately after the last function needing it is called. + symmetric-state-close: func(state: symmetric-state) -> result<_, crypto-errno>; + + /// Absorb data into the state. + /// + /// - **Hash functions:** adds data to be hashed. + /// - **MAC functions:** adds data to be authenticated. + /// - **Tuplehash-like constructions:** adds a new tuple to the state. + /// - **Key derivation functions:** adds to the IKM or to the subkey information. + /// - **AEAD constructions:** adds additional data to be authenticated. + /// - **Stateful hash objects, permutation-based constructions:** absorbs. + /// + /// If the chosen algorithm doesn't accept input data, the `invalid_operation` error code is returned. + /// + /// If too much data has been fed for the algorithm, `overflow` may be thrown. + symmetric-state-absorb: func(state: borrow, data: list) -> result<_, crypto-errno>; + + /// Squeeze bytes from the state. + /// + /// - **Hash functions:** this tries to output an `out_len` bytes digest from the absorbed data. The hash function output will be truncated if necessary. If the requested size is too large, the `invalid_len` error code is returned. + /// - **Key derivation functions:** : outputs an arbitrary-long derived key. + /// - **RNGs, DRBGs, stream ciphers:**: outputs arbitrary-long data. + /// - **Stateful hash objects, permutation-based constructions:** squeeze. + /// + /// Other kinds of algorithms may return `invalid_operation` instead. + /// + /// For password-stretching functions, the function may return `in_progress`. + /// In that case, the guest should retry with the same parameters until the function completes. + symmetric-state-squeeze: func(state: borrow) -> result, crypto-errno>; + + /// Compute and return a tag for all the data injected into the state so far. + /// + /// - **MAC functions**: returns a tag authenticating the absorbed data. + /// - **Tuplehash-like constructions:** returns a tag authenticating all the absorbed tuples. + /// - **Password-hashing functions:** returns a standard string containing all the required parameters for password verification. + /// + /// Other kinds of algorithms may return `invalid_operation` instead. + /// + /// For password-stretching functions, the function may return `in_progress`. + /// In that case, the guest should retry with the same parameters until the function completes. + symmetric-state-squeeze-tag: func(state: borrow) -> result; + + /// Use the current state to produce a key for a target algorithm. + /// + /// For extract-then-expand constructions, this returns the PRK. + /// For session-based authentication encryption, this returns a key that can be used to resume a session without storing a nonce. + /// + /// `invalid_operation` is returned for algorithms not supporting this operation. + symmetric-state-squeeze-key: func(state: borrow, alg-str: string) -> result; + + /// Return the maximum length of an authentication tag for the current algorithm. + /// + /// This allows guests to compute the size required to store a ciphertext along with its authentication tag. + /// + /// The returned length may include the encryption mode's padding requirements in addition to the actual tag. + /// + /// For an encryption operation, the size of the output buffer should be `input_len + symmetric_state_max_tag_len()`. + /// + /// For a decryption operation, the size of the buffer that will store the decrypted data must be `ciphertext_len - symmetric_state_max_tag_len()`. + symmetric-state-max-tag-len: func(state: borrow) -> result; + + /// Encrypt data with an attached tag. + /// + /// - **Stream cipher:** adds the input to the stream cipher output. `out_len` and `data_len` can be equal, as no authentication tags will be added. + /// - **AEAD:** encrypts `data` into `out`, including the authentication tag to the output. Additional data must have been previously absorbed using `symmetric_state_absorb()`. The `symmetric_state_max_tag_len()` function can be used to retrieve the overhead of adding the tag, as well as padding if necessary. + /// - **SHOE, Xoodyak, Strobe:** encrypts data, squeezes a tag and appends it to the output. + /// + /// The function returns the ciphertext along with the tag. + /// + /// `invalid_operation` is returned for algorithms not supporting encryption. + symmetric-state-encrypt: func(state: borrow, data: list) -> result, crypto-errno>; + + /// Encrypt data, with a detached tag. + /// + /// - **Stream cipher:** returns `invalid_operation` since stream ciphers do not include authentication tags. + /// - **AEAD:** encrypts `data` into `out` and returns the tag separately. Additional data must have been previously absorbed using `symmetric_state_absorb()`. The output and input buffers must be of the same length. + /// - **SHOE, Xoodyak, Strobe:** encrypts data and squeezes a tag. + /// + /// The function returns the ciphertext and tag. + /// + /// `invalid_operation` is returned for algorithms not supporting encryption. + symmetric-state-encrypt-detached: func(state: borrow, data: list) -> result, symmetric-tag>, crypto-errno>; + + /// - **Stream cipher:** adds the input to the stream cipher output. `out_len` and `data_len` can be equal, as no authentication tags will be added. + /// - **AEAD:** decrypts `data` into `out`. Additional data must have been previously absorbed using `symmetric_state_absorb()`. + /// - **SHOE, Xoodyak, Strobe:** decrypts data, squeezes a tag and verify that it matches the one that was appended to the ciphertext. + /// + /// `out-len` is the expected length of the plaintext. It must equal `data.len() - tag_len`, where + /// `tag_len` is the length of the authentication tag appended to the ciphertext. This parameter is + /// required because the tag length is algorithm-specific and may vary within the same algorithm + /// (e.g. AES-GCM supports tag lengths of 4–16 bytes), so the host cannot determine the split point + /// without explicit guidance from the caller. + /// + /// The function returns the decrypted message. + /// + /// `invalid_tag` is returned if the tag didn't verify. + /// + /// `invalid_operation` is returned for algorithms not supporting encryption. + symmetric-state-decrypt: func(state: borrow, data: list, out-len: size) -> result, crypto-errno>; + + /// - **Stream cipher:** returns `invalid_operation` since stream ciphers do not include authentication tags. + /// - **AEAD:** decrypts `data` into `out`. Additional data must have been previously absorbed using `symmetric_state_absorb()`. + /// - **SHOE, Xoodyak, Strobe:** decrypts data, squeezes a tag and verify that it matches the expected one. + /// + /// `raw_tag` is the expected tag, as raw bytes. + /// + /// The function returns the decrypted message. + /// + /// `invalid_tag` is returned if the tag verification failed. + /// + /// `invalid_operation` is returned for algorithms not supporting encryption. + symmetric-state-decrypt-detached: func(state: borrow, data: list, raw-tag: list) -> result, crypto-errno>; + + /// Make it impossible to recover the previous state. + /// + /// This operation is supported by some systems keeping a rolling state over an entire session, for forward security. + /// + /// `invalid_operation` is returned for algorithms not supporting ratcheting. + symmetric-state-ratchet: func(state: borrow) -> result<_, crypto-errno>; + + /// Return the length of an authentication tag. + /// + /// This function can be used by a guest to allocate the correct buffer size to copy a computed authentication tag. + symmetric-tag-len: func(symmetric-tag: borrow) -> result; + + /// Copy an authentication tag into a guest-allocated buffer. + /// + /// The handle is consumed and becomes invalid after this call. + /// + /// Example usage: + /// + /// ```rust + /// let raw_tag = ctx.symmetric_tag_pull(raw_tag_handle)?; + /// ``` + symmetric-tag-pull: func(symmetric-tag: symmetric-tag) -> result, crypto-errno>; + + /// Verify that a computed authentication tag matches the expected value, in constant-time. + /// + /// The expected tag must be provided as a raw byte string. + /// + /// The function returns `invalid_tag` if the tags don't match. + /// + /// Example usage: + /// + /// ```rust + /// let key_handle = ctx.symmetric_key_import("HMAC/SHA-256", b"key")?; + /// let state_handle = ctx.symmetric_state_open("HMAC/SHA-256", Some(key_handle), None)?; + /// ctx.symmetric_state_absorb(state_handle, b"data")?; + /// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; + /// ctx.symmetric_tag_verify(computed_tag_handle, expected_raw_tag)?; + /// ``` + symmetric-tag-verify: func(symmetric-tag: borrow, expected-raw-tag: list) -> result<_, crypto-errno>; + + /// Explicitly destroy an unused authentication tag. + /// + /// Objects are reference counted. It is safe to close an object immediately after the last function needing it is called. + symmetric-tag-close: func(symmetric-tag: symmetric-tag) -> result<_, crypto-errno>; +} diff --git a/wit/world.wit b/wit/world.wit new file mode 100644 index 0000000..d0af565 --- /dev/null +++ b/wit/world.wit @@ -0,0 +1,13 @@ +package wasi:crypto@0.11.0; + +world imports { + import wasi-ephemeral-crypto-asymmetric-common; + import wasi-ephemeral-crypto-common; + import wasi-ephemeral-crypto-external-secrets; + import wasi-ephemeral-crypto-kx; + import wasi-ephemeral-crypto-signatures; + import wasi-ephemeral-crypto-symmetric; + // import wasi-ephemeral-crypto-signatures-batch; + // import wasi-ephemeral-crypto-symmetric-batch; + } +