polymorph_webcrypto_guest/lib.rs
1//! Guest-side bindings and ergonomic helpers for the `polymorph:webcrypto`
2//! interfaces.
3//!
4//! This crate is the intended way for Rust guest components to *consume*
5//! `polymorph:webcrypto`: it binds the whole import surface once (the
6//! [`bindings`] module) and wraps the key resources in newtypes whose
7//! operations take a [`DataSource`] — a byte slice, an owned buffer, or a
8//! component-model stream — so callers need none of the stream plumbing the
9//! interfaces are defined in terms of.
10//!
11//! Most consumers need **no `polymorph:webcrypto` WIT at all**: link this crate
12//! and call it, and the componentized binary imports exactly the interfaces
13//! it uses (unused imports are stripped). Only list the imports in your own
14//! world — remapping them onto this crate's [`bindings`] modules with
15//! wit-bindgen's `with:` option — if your own interfaces name these types or
16//! external tooling validates your world's shape. Do **not** bind the same
17//! interfaces with a second `generate!` without that remapping: the two
18//! expansions would produce distinct, unconvertible resource types, and the
19//! newtypes here wrap only this crate's generation.
20//!
21//! # Cargo features
22//!
23//! - `bytes`: `DataSource::from_buf` feeds an operation from any
24//! `bytes::Buf`, chunk by chunk.
25//! - `futures-io`: `DataSource::from_reader` feeds an operation from any
26//! `futures_io::AsyncRead`; read failures surface as [`Error::Read`].
27//!
28//! # Contract notes carried over from the WIT
29//!
30//! - **The wrappers hide streams, not the closure rule.** An operation's
31//! input stream ends no later than the operation completes, and only a
32//! failing operation may end it early; these helpers feed the source and
33//! await the result concurrently, reporting the operation's error over
34//! the feed's fate, so that contract is invisible here. Callers with
35//! needs beyond [`DataSource`] use the
36//! [`bindings`] resources directly with wit-bindgen's own stream
37//! primitives ([`wit_stream::new`], `StreamWriter::write_all`,
38//! [`StreamReader::collect`]).
39//! - **Writer drop ends the message.** A stream's producer failing midway
40//! is indistinguishable from it finishing (the ABI carries no verdict at
41//! end-of-stream). Buffer-backed [`DataSource`]s own their whole input, so
42//! this only concerns stream-backed sources; see
43//! [`DataSource`]'s truncating-producer warning.
44//! - **Implementations may bound input sizes.** Hosts enforce buffering
45//! limits as recoverable [`Error::Other`] values (see the WIT
46//! `types.error` docs); nothing here retries or special-cases them.
47//! - **Nonces are the caller's problem on `aead`.** [`Aead::seal`] leaves
48//! nonce uniqueness per key entirely to you, and nonce reuse under one
49//! key defeats the algorithm's guarantees.
50
51#![deny(missing_docs)]
52
53use std::borrow::Cow;
54use std::fmt;
55
56use wit_bindgen::StreamWriter;
57
58/// Re-export of the `wit-bindgen` crate this crate's bindings were generated
59/// with, so consumers can name its runtime types (streams, futures) without
60/// depending on — and version-matching — `wit-bindgen` themselves.
61pub use wit_bindgen;
62/// The component-model byte-stream reader, as returned by [`Aead::seal`] and
63/// friends and accepted by [`DataSource`].
64pub use wit_bindgen::StreamReader;
65
66mod generated {
67 #![allow(missing_docs)]
68 // One mutually exclusive expansion per cargo-feature combination
69 // rather than one parameterized invocation: `generate!`'s `features`
70 // list is static, and the arms must stay option-for-option identical
71 // apart from it. This scales as 2^n in the gated cargo features — at
72 // n where this stops being tolerable, the bindings move to a build
73 // script that computes the flag list (tracked with the SDK's other
74 // cargo-feature debt in #85).
75 #[cfg(all(
76 feature = "sha1-checked",
77 feature = "rsa-sign",
78 feature = "rsa-oaep-decrypt"
79 ))]
80 wit_bindgen::generate!({
81 path: "wit",
82 features: ["sha1-checked", "rsa-sign", "rsa-oaep-decrypt"],
83 world: "imports",
84 generate_all,
85 pub_export_macro: false,
86 });
87 #[cfg(all(
88 feature = "sha1-checked",
89 feature = "rsa-sign",
90 not(feature = "rsa-oaep-decrypt")
91 ))]
92 wit_bindgen::generate!({
93 path: "wit",
94 features: ["sha1-checked", "rsa-sign"],
95 world: "imports",
96 generate_all,
97 pub_export_macro: false,
98 });
99 #[cfg(all(
100 feature = "sha1-checked",
101 not(feature = "rsa-sign"),
102 feature = "rsa-oaep-decrypt"
103 ))]
104 wit_bindgen::generate!({
105 path: "wit",
106 features: ["sha1-checked", "rsa-oaep-decrypt"],
107 world: "imports",
108 generate_all,
109 pub_export_macro: false,
110 });
111 #[cfg(all(
112 feature = "sha1-checked",
113 not(feature = "rsa-sign"),
114 not(feature = "rsa-oaep-decrypt")
115 ))]
116 wit_bindgen::generate!({
117 path: "wit",
118 features: ["sha1-checked"],
119 world: "imports",
120 generate_all,
121 pub_export_macro: false,
122 });
123 #[cfg(all(
124 not(feature = "sha1-checked"),
125 feature = "rsa-sign",
126 feature = "rsa-oaep-decrypt"
127 ))]
128 wit_bindgen::generate!({
129 path: "wit",
130 features: ["rsa-sign", "rsa-oaep-decrypt"],
131 world: "imports",
132 generate_all,
133 pub_export_macro: false,
134 });
135 #[cfg(all(
136 not(feature = "sha1-checked"),
137 feature = "rsa-sign",
138 not(feature = "rsa-oaep-decrypt")
139 ))]
140 wit_bindgen::generate!({
141 path: "wit",
142 features: ["rsa-sign"],
143 world: "imports",
144 generate_all,
145 pub_export_macro: false,
146 });
147 #[cfg(all(
148 not(feature = "sha1-checked"),
149 not(feature = "rsa-sign"),
150 feature = "rsa-oaep-decrypt"
151 ))]
152 wit_bindgen::generate!({
153 path: "wit",
154 features: ["rsa-oaep-decrypt"],
155 world: "imports",
156 generate_all,
157 pub_export_macro: false,
158 });
159 #[cfg(all(
160 not(feature = "sha1-checked"),
161 not(feature = "rsa-sign"),
162 not(feature = "rsa-oaep-decrypt")
163 ))]
164 wit_bindgen::generate!({
165 path: "wit",
166 world: "imports",
167 generate_all,
168 pub_export_macro: false,
169 });
170}
171
172/// The generated bindings for the full `polymorph:webcrypto` import surface.
173///
174/// The newtype wrappers cover the common cases; these are the escape hatch
175/// for callers driving the streams themselves and for passing resources
176/// through a consumer's own interfaces (via [`Mac::into_raw`] and friends).
177pub mod bindings {
178 // `aes`, `rsa`, and `sha2` are here for their *types*: they define
179 // `aes-variant`, `rsa-variant`, and `sha2-variant`, which the minting
180 // interfaces only alias, and rustdoc renders an alias into a private
181 // module as an empty enum.
182 #[cfg(feature = "rsa-oaep-decrypt")]
183 pub use super::generated::polymorph::webcrypto::rsa_oaep_decrypt;
184 #[cfg(feature = "sha1-checked")]
185 pub use super::generated::polymorph::webcrypto::sha1_checked;
186 pub use super::generated::polymorph::webcrypto::{
187 aead, aes, aes_cbc, aes_ctr, aes_gcm, aes_kw, cipher, derivation, digest, ecdh, ecdsa_sign,
188 ecdsa_verify, ed25519_sign, ed25519_verify, hkdf, hkdf_sha1, hkdf_sha2, hmac_sha1,
189 hmac_sha2, key_agreement, key_wrap, mac, pbkdf2, pbkdf2_sha1, pbkdf2_sha2,
190 public_encryption, rsa, rsa_oaep_encrypt, rsa_pss_verify, rsassa_pkcs1_v15_verify, sha2,
191 signature, types, wrapping, x25519,
192 };
193 #[cfg(feature = "rsa-sign")]
194 pub use super::generated::polymorph::webcrypto::{rsa_pss_sign, rsassa_pkcs1_v15_sign};
195}
196
197pub use generated::wit_stream;
198
199// --- error ---------------------------------------------------------------------
200
201/// Errors surfaced by key creation and cryptographic operations.
202///
203/// Mirrors the WIT `types.error` variant (see the doc comments in
204/// `wit/webcrypto.wit` for the full contracts), plus [`Error::Read`] for
205/// failures of a caller-supplied [`DataSource`] producer. Misuse of the API
206/// is unrepresentable by construction — operations are one-shot calls on
207/// immutable key resources — and so has no variant here.
208///
209/// `#[non_exhaustive]`: this enum carries [`Error::Read`] in addition to the
210/// WIT cases, so it grows independently of the package's own rule that a new
211/// `types.error` case is semver-major. The `From` conversion below is
212/// exhaustive over the WIT variant, so a case added there is a compile error
213/// here rather than a silent fallthrough.
214#[derive(Debug)]
215#[non_exhaustive]
216pub enum Error {
217 /// The supplied key material is invalid for the algorithm (for example,
218 /// a wrong-length raw key, or one rejected by an implementation's
219 /// key-length policy). The string is human-readable.
220 InvalidKey(String),
221 /// The supplied nonce is invalid for the algorithm (for example, a
222 /// wrong-length AES-GCM nonce). The string is human-readable.
223 InvalidNonce(String),
224 /// Verification failed: the MAC tag, the signature, or the ciphertext or
225 /// its associated data did not verify under the key. Deliberately
226 /// carries no detail, so implementations cannot leak *why* verification
227 /// failed.
228 AuthenticationFailed,
229 /// The key was created with `extractable` false, so its material cannot
230 /// be exported.
231 NotExtractable,
232 /// The request was well-formed, but the implementation does not serve
233 /// the requested algorithm parameters. The string is human-readable.
234 Unsupported(String),
235 /// The key does not permit the requested operation: it was minted (or
236 /// arrived from a platform keystore) with the operation's usage
237 /// disabled. The string names the refused operation.
238 NotPermitted(String),
239 /// An implementation-specific operational failure (an external keystore
240 /// that cannot complete the operation, an input exceeding a buffering
241 /// limit, …). The string is human-readable.
242 Other(String),
243 /// A named condition outside the WIT `error` variant's closed set,
244 /// identified by the (`origin`, `name`) pair — the only branchable
245 /// identity; `message` is human-readable prose, never contract.
246 /// Handle an unrecognized pair as [`Error::Other`]. Known pairs have
247 /// constants in [`extension`].
248 Extension(bindings::types::ExtensionError),
249 /// A caller-supplied [`DataSource`] producer failed while being fed into
250 /// the operation (see `DataSource::from_reader`). The operation's own
251 /// result is discarded: it was computed over a truncated input.
252 Read(std::io::Error),
253 /// The operation succeeded without accepting the whole source: the
254 /// provider closed the stream's read end early and then reported
255 /// success. The stream-closure rule permits ending the input early
256 /// only when the operation fails (a failing operation's own error is
257 /// what these wrappers report), so this indicates a defective
258 /// provider, not a condition to retry.
259 ShortWrite,
260}
261
262/// The known extension-error conditions, as (`origin`, `name`) constants
263/// for matching against [`Error::Extension`].
264pub mod extension {
265 /// The `origin` of conditions the `polymorph:webcrypto` package defines.
266 pub const ORIGIN: &str = "polymorph:webcrypto";
267 /// `sha1-checked`'s collision condition: a rejecting digest's input
268 /// carried a SHA-1 collision attack pattern.
269 pub const COLLISION_DETECTED: &str = "collision-detected";
270 /// `public-encryption`'s plaintext-bound condition: the plaintext (or
271 /// wrapped serialization) exceeds the key's bound — the signal to
272 /// switch to hybrid wrapping.
273 pub const MESSAGE_TOO_LONG: &str = "message-too-long";
274}
275
276impl From<bindings::types::Error> for Error {
277 fn from(error: bindings::types::Error) -> Self {
278 use bindings::types::Error as Raw;
279 match error {
280 Raw::InvalidKey(detail) => Error::InvalidKey(detail),
281 Raw::InvalidNonce(detail) => Error::InvalidNonce(detail),
282 Raw::AuthenticationFailed => Error::AuthenticationFailed,
283 Raw::NotExtractable => Error::NotExtractable,
284 Raw::Unsupported(detail) => Error::Unsupported(detail),
285 Raw::NotPermitted(detail) => Error::NotPermitted(detail),
286 Raw::Other(detail) => Error::Other(detail),
287 Raw::Extension(ext) => Error::Extension(ext),
288 }
289 }
290}
291
292/// Renders the WIT cases case-name-first — `invalid-key: <detail>` — plus
293/// the [`Error::Read`] case this type adds.
294impl fmt::Display for Error {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 match self {
297 Error::InvalidKey(detail) => write!(f, "invalid-key: {detail}"),
298 Error::InvalidNonce(detail) => write!(f, "invalid-nonce: {detail}"),
299 Error::AuthenticationFailed => write!(f, "authentication-failed"),
300 Error::NotExtractable => write!(f, "not-extractable"),
301 Error::Unsupported(detail) => write!(f, "unsupported: {detail}"),
302 Error::NotPermitted(detail) => write!(f, "not-permitted: {detail}"),
303 Error::Other(detail) => write!(f, "other: {detail}"),
304 Error::Extension(ext) => write!(
305 f,
306 "extension({origin}, {name}): {message}",
307 origin = ext.origin,
308 name = ext.name,
309 message = ext.message,
310 ),
311 Error::Read(error) => write!(f, "data source read failed: {error}"),
312 Error::ShortWrite => write!(
313 f,
314 "short write: the operation stopped accepting input before the \
315 source was fully written"
316 ),
317 }
318 }
319}
320
321impl std::error::Error for Error {
322 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
323 match self {
324 Error::Read(error) => Some(error),
325 _ => None,
326 }
327 }
328}
329
330// --- data sources ----------------------------------------------------------------
331
332/// The input to a wrapped operation: anything this crate knows how to feed
333/// into a WIT `stream<u8>`.
334///
335/// Operation methods take `impl Into<DataSource<'_>>`, so byte slices, owned
336/// buffers, and streams received from other components all work directly:
337///
338/// - `&[u8]`, `&[u8; N]`, `Vec<u8>`, `&Vec<u8>`, [`Cow<'a, [u8]>`](Cow) —
339/// buffered sources. Owned data is moved and written whole, never copied;
340/// borrowed data is fed chunk by chunk through one reusable buffer, so a
341/// large input is never duplicated whole (the ABI's per-chunk copy into
342/// an owned buffer is unavoidable).
343/// - [`StreamReader<u8>`] — passed through to the operation as-is, without
344/// buffering.
345/// - `DataSource::from_buf` (feature `bytes`) — fed chunk by chunk.
346/// - `DataSource::from_reader` (feature `futures-io`) — pumped
347/// incrementally; read failures surface as [`Error::Read`].
348///
349/// # Warning: truncating producers
350///
351/// Dropping a stream's writer is its only end-of-input signal and carries no
352/// verdict, so a producer that fails midway is indistinguishable *on the
353/// wire* from one that finished — the operation correctly computes over the
354/// delivered prefix. Buffer-backed sources own their whole input and are
355/// immune. For a [`StreamReader<u8>`] fed by another component, convey
356/// completeness in-band (e.g. length framing) or discard the result on
357/// producer failure. A `DataSource::from_reader` source is handled for
358/// you: its failure is observed locally and reported as [`Error::Read`]
359/// instead of the operation's result.
360pub struct DataSource<'a>(Inner<'a>);
361
362enum Inner<'a> {
363 Bytes(Cow<'a, [u8]>),
364 Stream(StreamReader<u8>),
365 #[cfg(feature = "bytes")]
366 Buf(Box<dyn ::bytes::Buf + 'a>),
367 #[cfg(feature = "futures-io")]
368 Reader(std::pin::Pin<Box<dyn futures_io::AsyncRead + 'a>>),
369}
370
371impl<'a> From<&'a [u8]> for DataSource<'a> {
372 fn from(data: &'a [u8]) -> Self {
373 Self(Inner::Bytes(Cow::Borrowed(data)))
374 }
375}
376
377impl<'a, const N: usize> From<&'a [u8; N]> for DataSource<'a> {
378 fn from(data: &'a [u8; N]) -> Self {
379 Self(Inner::Bytes(Cow::Borrowed(data)))
380 }
381}
382
383impl From<Vec<u8>> for DataSource<'_> {
384 fn from(data: Vec<u8>) -> Self {
385 Self(Inner::Bytes(Cow::Owned(data)))
386 }
387}
388
389impl<'a> From<&'a Vec<u8>> for DataSource<'a> {
390 fn from(data: &'a Vec<u8>) -> Self {
391 Self(Inner::Bytes(Cow::Borrowed(data)))
392 }
393}
394
395impl<'a> From<Cow<'a, [u8]>> for DataSource<'a> {
396 fn from(data: Cow<'a, [u8]>) -> Self {
397 Self(Inner::Bytes(data))
398 }
399}
400
401impl From<StreamReader<u8>> for DataSource<'_> {
402 fn from(stream: StreamReader<u8>) -> Self {
403 Self(Inner::Stream(stream))
404 }
405}
406
407impl<'a> DataSource<'a> {
408 /// A source that feeds the operation from `buf`, chunk by chunk.
409 ///
410 /// `Buf` is infallible, so this source cannot produce [`Error::Read`].
411 #[cfg(feature = "bytes")]
412 pub fn from_buf(buf: impl ::bytes::Buf + 'a) -> Self {
413 Self(Inner::Buf(Box::new(buf)))
414 }
415
416 /// A source that pumps the operation's input from `reader` until
417 /// end-of-file.
418 ///
419 /// A read failure aborts the feed and the operation reports
420 /// [`Error::Read`] — never the result computed over the truncated
421 /// prefix.
422 #[cfg(feature = "futures-io")]
423 pub fn from_reader(reader: impl futures_io::AsyncRead + 'a) -> Self {
424 Self(Inner::Reader(Box::pin(reader)))
425 }
426}
427
428// --- operation plumbing ---------------------------------------------------------
429
430/// The chunk size the incremental feeders copy through their reusable
431/// scratch buffer, bounding a feed's extra memory to one chunk.
432const FEED_CHUNK: usize = 8192;
433
434/// A feeder's outcome, distinct from its *failure* ([`Error::Read`]): a
435/// rejected write is not itself an error — the closure rule permits a
436/// failing operation to stop reading — so its meaning depends on the
437/// operation's result.
438#[must_use]
439enum Feed {
440 /// Every byte was written.
441 Complete,
442 /// The operation stopped accepting input partway.
443 Rejected,
444}
445
446impl Feed {
447 /// The success-path requirement: a completed operation promises it
448 /// consumed the whole input, so rejection under success is the defect
449 /// [`Error::ShortWrite`] names.
450 fn require_complete(self) -> Result<(), Error> {
451 match self {
452 Feed::Complete => Ok(()),
453 Feed::Rejected => Err(Error::ShortWrite),
454 }
455 }
456}
457
458impl Inner<'_> {
459 /// Feed this source into `tx`, then drop the writer to end the stream.
460 /// The error is always [`Error::Read`] — the only way a feed *fails*;
461 /// the operation rejecting input is an outcome, not an error.
462 async fn feed(self, mut tx: StreamWriter<u8>) -> Result<Feed, Error> {
463 match self {
464 // Pass-through sources never reach the feeder.
465 Inner::Stream(_) => unreachable!("stream sources are passed through"),
466 Inner::Bytes(Cow::Owned(data)) => {
467 let leftover = tx.write_all(data).await;
468 if leftover.is_empty() {
469 Ok(Feed::Complete)
470 } else {
471 Ok(Feed::Rejected)
472 }
473 }
474 // A borrowed buffer is never duplicated whole: it is fed in
475 // chunks through one reusable allocation (`write_all` returns
476 // its argument's allocation, emptied on success), so the feed
477 // costs one chunk of extra memory and the ABI's unavoidable
478 // per-chunk copy.
479 Inner::Bytes(Cow::Borrowed(data)) => {
480 let mut scratch = Vec::new();
481 for chunk in data.chunks(FEED_CHUNK) {
482 scratch.extend_from_slice(chunk);
483 scratch = tx.write_all(scratch).await;
484 if !scratch.is_empty() {
485 return Ok(Feed::Rejected);
486 }
487 }
488 Ok(Feed::Complete)
489 }
490 #[cfg(feature = "bytes")]
491 Inner::Buf(mut buf) => {
492 use ::bytes::Buf as _;
493 // As for borrowed bytes: one reusable scratch buffer, one
494 // copy per source-native chunk.
495 let mut scratch = Vec::new();
496 while buf.has_remaining() {
497 let chunk = buf.chunk();
498 let n = chunk.len();
499 scratch.extend_from_slice(chunk);
500 buf.advance(n);
501 scratch = tx.write_all(scratch).await;
502 if !scratch.is_empty() {
503 return Ok(Feed::Rejected);
504 }
505 }
506 Ok(Feed::Complete)
507 }
508 #[cfg(feature = "futures-io")]
509 Inner::Reader(mut reader) => {
510 // As for borrowed bytes: one reusable scratch buffer, one
511 // copy per chunk (out of the read buffer `poll_read`
512 // requires).
513 let mut chunk = [0u8; FEED_CHUNK];
514 let mut scratch = Vec::new();
515 loop {
516 let n = std::future::poll_fn(|cx| reader.as_mut().poll_read(cx, &mut chunk))
517 .await
518 .map_err(Error::Read)?;
519 if n == 0 {
520 return Ok(Feed::Complete);
521 }
522 scratch.extend_from_slice(&chunk[..n]);
523 scratch = tx.write_all(scratch).await;
524 if !scratch.is_empty() {
525 return Ok(Feed::Rejected);
526 }
527 }
528 }
529 }
530 }
531}
532
533/// Run the operation built by `op` over `source`: pass a stream source
534/// through directly, or mint a stream pair and feed the source concurrently
535/// with the operation (per the closure rule, the feed settles no later
536/// than the operation). The joined outcome resolves one precedence rule
537/// per statement: a source failure outranks everything (the operation only
538/// saw a truncated input); then the operation's result is authoritative;
539/// and a completed operation must have consumed the whole input.
540async fn run_sourced<T, F>(
541 source: DataSource<'_>,
542 op: impl FnOnce(StreamReader<u8>) -> F,
543) -> Result<T, Error>
544where
545 F: std::future::Future<Output = Result<T, bindings::types::Error>>,
546{
547 match source.0 {
548 Inner::Stream(rx) => op(rx).await.map_err(Error::from),
549 inner => {
550 let (tx, rx) = wit_stream::new();
551 let (result, fed) = futures::join!(op(rx), inner.feed(tx));
552 let fed = fed?;
553 let value = result?;
554 fed.require_complete()?;
555 Ok(value)
556 }
557 }
558}
559
560/// A pending `seal`, returned by [`Aead::seal`] and
561/// [`CipherKey::encrypt`].
562///
563/// Nothing runs until this is polled: the operation starts on the first
564/// `await`, so a `Seal` that is dropped unused never calls the
565/// implementation.
566///
567/// Awaiting it yields the whole sealed message. It is a [`Future`] rather
568/// than an `async fn`'s anonymous one so that it drops straight into
569/// [`futures::join!`], which is the shape the package's making-progress rule
570/// asks callers for: several operations in flight, all of them making
571/// progress.
572///
573/// `seal` is the one operation in the package whose result may arrive before
574/// its input is consumed — the WIT permits producing the sealed message
575/// incrementally — so the collect runs *concurrently* with the feed. Awaiting
576/// the operation first and reading the stream afterwards would deadlock
577/// against a provider that does so.
578#[must_use = "a Seal does nothing until it is awaited"]
579pub struct Seal<'a> {
580 state: SealState<'a>,
581}
582
583type LocalBoxFuture<'a, T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + 'a>>;
584
585/// Starts the operation over the readable end of a freshly minted stream.
586type StartSeal<'a> = Box<
587 dyn FnOnce(
588 StreamReader<u8>,
589 ) -> LocalBoxFuture<'a, Result<StreamReader<u8>, bindings::types::Error>>
590 + 'a,
591>;
592
593enum SealState<'a> {
594 Ready(DataSource<'a>, StartSeal<'a>),
595 Running(LocalBoxFuture<'a, Result<Vec<u8>, Error>>),
596 Done,
597}
598
599impl<'a> Seal<'a> {
600 fn new(source: DataSource<'a>, start: StartSeal<'a>) -> Self {
601 Self {
602 state: SealState::Ready(source, start),
603 }
604 }
605}
606
607impl std::future::Future for Seal<'_> {
608 type Output = Result<Vec<u8>, Error>;
609
610 fn poll(
611 self: std::pin::Pin<&mut Self>,
612 cx: &mut std::task::Context<'_>,
613 ) -> std::task::Poll<Self::Output> {
614 // `Seal` is `Unpin`: every field is a `Box` or a `Pin<Box<_>>`.
615 let this = self.get_mut();
616 if let SealState::Ready(..) = this.state {
617 let SealState::Ready(source, start) =
618 std::mem::replace(&mut this.state, SealState::Done)
619 else {
620 unreachable!("just matched Ready")
621 };
622 this.state = SealState::Running(seal_and_collect(source, start));
623 }
624 match &mut this.state {
625 SealState::Running(running) => running.as_mut().poll(cx),
626 SealState::Done => panic!("Seal polled after completion"),
627 SealState::Ready(..) => unreachable!("started above"),
628 }
629 }
630}
631
632/// Feed the source and collect the sealed message concurrently.
633fn seal_and_collect<'a>(
634 source: DataSource<'a>,
635 start: StartSeal<'a>,
636) -> LocalBoxFuture<'a, Result<Vec<u8>, Error>> {
637 Box::pin(async move {
638 match source.0 {
639 // A caller-supplied stream is fed by whoever owns its writer, so
640 // there is nothing to run concurrently here.
641 Inner::Stream(rx) => {
642 let sealed = start(rx).await.map_err(Error::from)?;
643 Ok(sealed.collect().await)
644 }
645 inner => {
646 let (tx, rx) = wit_stream::new();
647 let sealed = async {
648 let stream = start(rx).await.map_err(Error::from)?;
649 Ok::<_, Error>(stream.collect().await)
650 };
651 let (result, fed) = futures::join!(sealed, inner.feed(tx));
652 let fed = fed?;
653 let value = result?;
654 fed.require_complete()?;
655 Ok(value)
656 }
657 }
658 })
659}
660
661// --- key options ----------------------------------------------------------------
662
663/// Mint-time policy for a [`Mac`] key: the plain-data counterpart of the
664/// WIT `mac.mac-key-options` resource, which the minting functions
665/// construct from it per call.
666///
667/// Follows the package-wide options contract: the default grants nothing,
668/// every field is opt-in, and a mint with no usage enabled fails
669/// [`Error::NotPermitted`].
670#[derive(Clone, Copy, Debug, Default)]
671pub struct MacKeyOptions {
672 /// Whether the minted key may `sign`.
673 pub sign: bool,
674 /// Whether the minted key may `verify`.
675 pub verify: bool,
676 /// Whether the minted key's material may be exported.
677 pub extractable: bool,
678}
679
680impl MacKeyOptions {
681 /// The WIT options resource carrying this policy.
682 pub(crate) fn lower(self) -> bindings::mac::MacKeyOptions {
683 let options = bindings::mac::MacKeyOptions::new();
684 options.can_sign(self.sign);
685 options.can_verify(self.verify);
686 options.extractable(self.extractable);
687 options
688 }
689}
690
691/// Mint-time policy for an [`Aead`] key. See [`MacKeyOptions`] for the
692/// options contract.
693#[derive(Clone, Copy, Debug, Default)]
694pub struct AeadKeyOptions {
695 /// Whether the minted key may `seal`.
696 pub seal: bool,
697 /// Whether the minted key may `open`.
698 pub open: bool,
699 /// Whether the minted key may `wrap` key material.
700 pub wrap: bool,
701 /// Whether the minted key may `unwrap` key material.
702 pub unwrap: bool,
703 /// Whether the minted key's material may be exported.
704 pub extractable: bool,
705}
706
707impl AeadKeyOptions {
708 /// The WIT options resource carrying this policy.
709 pub(crate) fn lower(self) -> bindings::aead::AeadKeyOptions {
710 let options = bindings::aead::AeadKeyOptions::new();
711 options.can_seal(self.seal);
712 options.can_open(self.open);
713 options.can_wrap(self.wrap);
714 options.can_unwrap(self.unwrap);
715 options.extractable(self.extractable);
716 options
717 }
718}
719
720/// Mint-time policy for a [`CipherKey`] key. See [`MacKeyOptions`] for the
721/// options contract.
722#[derive(Clone, Copy, Debug, Default)]
723pub struct CipherKeyOptions {
724 /// Whether the minted key may `encrypt`.
725 pub encrypt: bool,
726 /// Whether the minted key may `decrypt`.
727 pub decrypt: bool,
728 /// Whether the minted key may `wrap` key material.
729 pub wrap: bool,
730 /// Whether the minted key may `unwrap` key material.
731 pub unwrap: bool,
732 /// Whether the minted key's material may be exported.
733 pub extractable: bool,
734}
735
736impl CipherKeyOptions {
737 /// The WIT options resource carrying this policy.
738 pub(crate) fn lower(self) -> bindings::cipher::CipherKeyOptions {
739 let options = bindings::cipher::CipherKeyOptions::new();
740 options.can_encrypt(self.encrypt);
741 options.can_decrypt(self.decrypt);
742 options.can_wrap(self.wrap);
743 options.can_unwrap(self.unwrap);
744 options.extractable(self.extractable);
745 options
746 }
747}
748
749/// Mint-time policy for a [`KwKey`]. See [`MacKeyOptions`] for the options
750/// contract.
751#[derive(Clone, Copy, Debug, Default)]
752pub struct KwKeyOptions {
753 /// Whether the minted key may `wrap` key material.
754 pub wrap: bool,
755 /// Whether the minted key may `unwrap` key material.
756 pub unwrap: bool,
757 /// Whether the minted key's material may be exported.
758 pub extractable: bool,
759}
760
761impl KwKeyOptions {
762 /// The WIT options resource carrying this policy.
763 pub(crate) fn lower(self) -> bindings::key_wrap::KwKeyOptions {
764 let options = bindings::key_wrap::KwKeyOptions::new();
765 options.can_wrap(self.wrap);
766 options.can_unwrap(self.unwrap);
767 options.extractable(self.extractable);
768 options
769 }
770}
771
772/// Mint-time policy for a [`SigningKey`]. See [`MacKeyOptions`] for the
773/// options contract; `sign` is the sole usage, so it must be enabled for a
774/// mint to succeed.
775#[derive(Clone, Copy, Debug, Default)]
776pub struct SigningKeyOptions {
777 /// Whether the minted key may `sign`.
778 pub sign: bool,
779 /// Whether the minted key's material may be exported
780 /// ([`SigningKey::export_key_jwk`], [`SigningKey::export_key_pkcs8`],
781 /// and the wrap inputs).
782 pub extractable: bool,
783}
784
785impl SigningKeyOptions {
786 /// The WIT options resource carrying this policy.
787 pub(crate) fn lower(self) -> bindings::signature::SigningKeyOptions {
788 let options = bindings::signature::SigningKeyOptions::new();
789 options.can_sign(self.sign);
790 options.extractable(self.extractable);
791 options
792 }
793}
794
795/// Mint-time policy for a derivation base secret ([`Ikm`] or [`Password`]).
796/// See [`MacKeyOptions`] for the options contract. The grants are copied
797/// onto every [`DeriveInput`] built on the secret; parameterization
798/// neither grants nor revokes.
799#[derive(Clone, Copy, Debug, Default)]
800pub struct DeriveOptions {
801 /// Whether inputs built on this secret may yield raw bits through
802 /// [`DeriveInput::derive_bits`] — and, because an exportable key is
803 /// bits disclosure by other means, whether they may mint
804 /// *extractable* keys.
805 pub derive_bits: bool,
806 /// Whether inputs built on this secret may mint keys through the
807 /// target interfaces' `derive_key` (e.g. [`hmac_sha2::derive_key`]).
808 pub derive_key: bool,
809}
810
811impl DeriveOptions {
812 /// The WIT options resource carrying this policy.
813 pub(crate) fn lower(self) -> bindings::derivation::DeriveOptions {
814 let options = bindings::derivation::DeriveOptions::new();
815 options.can_derive_bits(self.derive_bits);
816 options.can_derive_key(self.derive_key);
817 options
818 }
819}
820
821/// Mint-time policy for an [`AgreementSecretKey`]. See [`MacKeyOptions`]
822/// for the options contract; the derive grants are copied onto every
823/// [`DeriveInput`] the key [`agree`](AgreementSecretKey::agree)s
824/// (WebCrypto's model: derive usages live on the secret key).
825#[derive(Clone, Copy, Debug, Default)]
826pub struct AgreementKeyOptions {
827 /// Whether inputs agreed by the key may yield raw bits. See
828 /// [`DeriveOptions::derive_bits`].
829 pub derive_bits: bool,
830 /// Whether inputs agreed by the key may mint keys. See
831 /// [`DeriveOptions::derive_key`].
832 pub derive_key: bool,
833 /// Whether the secret key's material may be exported.
834 pub extractable: bool,
835}
836
837impl AgreementKeyOptions {
838 /// The WIT options resource carrying this policy.
839 pub(crate) fn lower(self) -> bindings::key_agreement::AgreementKeyOptions {
840 let options = bindings::key_agreement::AgreementKeyOptions::new();
841 options.can_derive_bits(self.derive_bits);
842 options.can_derive_key(self.derive_key);
843 options.extractable(self.extractable);
844 options
845 }
846}
847
848/// Mint-time policy for a [`DecryptionKey`]. See [`MacKeyOptions`] for the
849/// options contract. The two grants separate disclosure from minting:
850/// `decrypt` returns plaintext to the caller, while `unwrap` mints keys
851/// whose material the caller never sees, so a key granted only `unwrap`
852/// cannot leak what it transports.
853#[derive(Clone, Copy, Debug, Default)]
854pub struct DecryptionKeyOptions {
855 /// Whether the minted key may [`decrypt`](DecryptionKey::decrypt).
856 pub decrypt: bool,
857 /// Whether the minted key may [`unwrap`](DecryptionKey::unwrap).
858 pub unwrap: bool,
859 /// Whether the minted key's material may be exported.
860 pub extractable: bool,
861}
862
863impl DecryptionKeyOptions {
864 /// The WIT options resource carrying this policy.
865 #[cfg_attr(not(feature = "rsa-oaep-decrypt"), allow(dead_code))]
866 pub(crate) fn lower(self) -> bindings::public_encryption::DecryptionKeyOptions {
867 let options = bindings::public_encryption::DecryptionKeyOptions::new();
868 options.can_decrypt(self.decrypt);
869 options.can_unwrap(self.unwrap);
870 options.extractable(self.extractable);
871 options
872 }
873}
874
875// --- newtypes ------------------------------------------------------------------
876
877/// Generate the shared newtype plumbing: constructors, raw accessors, and
878/// `From` in both directions.
879macro_rules! newtype_common {
880 ($name:ident, $raw:ty, $doc_res:literal) => {
881 impl $name {
882 #[doc = concat!("Wrap a raw `", $doc_res, "` resource.")]
883 pub fn from_raw(raw: $raw) -> Self {
884 Self(raw)
885 }
886
887 #[doc = concat!("Borrow the raw `", $doc_res, "` resource.")]
888 pub fn as_raw(&self) -> &$raw {
889 &self.0
890 }
891
892 #[doc = concat!("Unwrap into the raw `", $doc_res, "` resource.")]
893 pub fn into_raw(self) -> $raw {
894 self.0
895 }
896 }
897
898 impl From<$raw> for $name {
899 fn from(raw: $raw) -> Self {
900 Self(raw)
901 }
902 }
903
904 impl std::fmt::Debug for $name {
905 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
906 f.debug_tuple(stringify!($name)).field(&self.0).finish()
907 }
908 }
909 };
910}
911
912/// A `mac.mac-key`: a message-authentication-code key, bound to one
913/// algorithm at creation.
914pub struct Mac(bindings::mac::MacKey);
915newtype_common!(Mac, bindings::mac::MacKey, "mac-key");
916
917impl Mac {
918 /// Compute the authentication tag over `data`.
919 ///
920 /// Fails only for operational reasons ([`Error::Other`], or
921 /// [`Error::Read`] for a failing `DataSource::from_reader` source) —
922 /// never for misuse, which is unrepresentable.
923 pub async fn sign(&self, data: impl Into<DataSource<'_>>) -> Result<Vec<u8>, Error> {
924 run_sourced(data.into(), |rx| self.0.sign(rx)).await
925 }
926
927 /// Verify `tag` over `data`, in constant time.
928 ///
929 /// Fails closed with [`Error::AuthenticationFailed`] if the tag does not
930 /// verify — deliberately a `Result` rather than a `bool`: an ignored
931 /// boolean fails open, a dropped `Result` does not.
932 pub async fn verify(
933 &self,
934 data: impl Into<DataSource<'_>>,
935 tag: impl Into<Cow<'_, [u8]>>,
936 ) -> Result<(), Error> {
937 let tag = tag.into().into_owned();
938 run_sourced(data.into(), |rx| self.0.verify(rx, tag)).await
939 }
940
941 /// The name of the key's algorithm family, e.g. `"HMAC"` — WebCrypto's
942 /// `KeyAlgorithm.name`, spelled as the [W3C Web Cryptography API
943 /// algorithm registry](https://www.w3.org/TR/WebCryptoAPI/#algorithm-overview)
944 /// spells it.
945 pub fn algorithm_name(&self) -> String {
946 self.0.algorithm_name()
947 }
948
949 /// The registry name of the digest the algorithm is parameterized over,
950 /// e.g. `"SHA-256"` for HMAC-SHA-256 (WebCrypto's
951 /// `HmacKeyAlgorithm.hash`, spelled per the same registry as
952 /// [`algorithm_name`](Self::algorithm_name)). `None` for MAC algorithms
953 /// not built on a digest.
954 pub fn algorithm_hash(&self) -> Option<String> {
955 self.0.algorithm_hash()
956 }
957
958 /// The key length in bits (WebCrypto's `HmacKeyAlgorithm.length`: the
959 /// length of the key material).
960 pub fn algorithm_length(&self) -> u32 {
961 self.0.algorithm_length()
962 }
963
964 /// Whether [`export_key_raw`](Self::export_key_raw) may return the key
965 /// material.
966 ///
967 /// Asking is not the same as exporting: interrogating extractability
968 /// through [`export_key_raw`](Self::export_key_raw) alone would hand you the
969 /// material whenever the answer is yes.
970 pub fn extractable(&self) -> bool {
971 self.0.extractable()
972 }
973
974 /// Whether the key permits [`sign`](Self::sign) — the usage recorded
975 /// at mint. A refused operation fails [`Error::NotPermitted`].
976 pub fn can_sign(&self) -> bool {
977 self.0.can_sign()
978 }
979
980 /// Whether the key permits [`verify`](Self::verify). See
981 /// [`can_sign`](Self::can_sign).
982 pub fn can_verify(&self) -> bool {
983 self.0.can_verify()
984 }
985
986 /// The raw key material; fails with [`Error::NotExtractable`] unless the
987 /// key was minted extractable. Extractability is an API property, not a
988 /// physical one: the guarantee is that components holding only the
989 /// handle cannot obtain the material through this API.
990 pub async fn export_key_raw(&self) -> Result<Vec<u8>, Error> {
991 self.0.export_key_raw().await.map_err(Error::from)
992 }
993
994 /// The key as an RFC 7517 `oct` JSON Web Key (JSON text), behind the
995 /// same extractability gate as [`export_key_raw`](Self::export_key_raw).
996 pub async fn export_key_jwk(&self) -> Result<String, Error> {
997 self.0.export_key_jwk().await.map_err(Error::from)
998 }
999
1000 /// This key's raw material as a [`WrapInput`], behind the same
1001 /// extractability gate as [`export_key_raw`](Self::export_key_raw).
1002 pub async fn to_wrap_input_raw(&self) -> Result<WrapInput, Error> {
1003 self.0
1004 .to_wrap_input_raw()
1005 .await
1006 .map(WrapInput::from_raw)
1007 .map_err(Error::from)
1008 }
1009
1010 /// The JWK serialization as a [`WrapInput`], behind the same gate.
1011 pub async fn to_wrap_input_jwk(&self) -> Result<WrapInput, Error> {
1012 self.0
1013 .to_wrap_input_jwk()
1014 .await
1015 .map(WrapInput::from_raw)
1016 .map_err(Error::from)
1017 }
1018}
1019
1020/// An `aead.aead-key`: caller-nonce authenticated encryption with
1021/// associated data.
1022///
1023/// Nonce reuse under one key is catastrophic, and this type's
1024/// [`seal`](Self::seal) leaves nonce uniqueness entirely to you: use a
1025/// deterministic per-key uniqueness scheme (such as a counter).
1026pub struct Aead(bindings::aead::AeadKey);
1027newtype_common!(Aead, bindings::aead::AeadKey, "aead-key");
1028
1029impl Aead {
1030 /// Encrypt and authenticate `plaintext` under `nonce` and `aad`,
1031 /// yielding the ciphertext followed by the authentication tag.
1032 ///
1033 /// Returns a [`Seal`], which starts the operation when awaited.
1034 ///
1035 /// **The caller is responsible for nonce uniqueness per key.** Reusing a
1036 /// nonce under one key defeats the algorithm's confidentiality and
1037 /// authenticity guarantees.
1038 pub fn seal<'a>(
1039 &'a self,
1040 nonce: impl Into<Cow<'a, [u8]>>,
1041 aad: impl Into<Cow<'a, [u8]>>,
1042 plaintext: impl Into<DataSource<'a>>,
1043 ) -> Seal<'a> {
1044 let (nonce, aad) = (nonce.into().into_owned(), aad.into().into_owned());
1045 Seal::new(
1046 plaintext.into(),
1047 Box::new(move |rx| Box::pin(self.0.seal(nonce, aad, None, rx))),
1048 )
1049 }
1050
1051 /// [`seal`](Self::seal) with an explicit tag size in bytes, for
1052 /// algorithms whose tag size is a per-call parameter (AES-GCM's set is
1053 /// 4, 8, 12, 13, 14, 15, or 16; other algorithms fix 16). Short tags
1054 /// weaken the forgery bound; prefer [`seal`](Self::seal), which uses
1055 /// the algorithm default ([`tag_size`](Self::tag_size)).
1056 pub fn seal_with_tag_size<'a>(
1057 &'a self,
1058 nonce: impl Into<Cow<'a, [u8]>>,
1059 aad: impl Into<Cow<'a, [u8]>>,
1060 tag_size: u8,
1061 plaintext: impl Into<DataSource<'a>>,
1062 ) -> Seal<'a> {
1063 let (nonce, aad) = (nonce.into().into_owned(), aad.into().into_owned());
1064 Seal::new(
1065 plaintext.into(),
1066 Box::new(move |rx| Box::pin(self.0.seal(nonce, aad, Some(tag_size), rx))),
1067 )
1068 }
1069
1070 /// Decrypt and verify `ciphertext` (ciphertext followed by tag, as
1071 /// produced by [`seal`](Self::seal)) under `nonce` and `aad`.
1072 ///
1073 /// The stream is handed back only after the whole input is consumed and
1074 /// the tag verified: `Ok` *is* the authentication statement, and
1075 /// unverified plaintext is never observable. Fails closed with
1076 /// [`Error::AuthenticationFailed`] if verification fails.
1077 pub async fn open(
1078 &self,
1079 nonce: impl Into<Cow<'_, [u8]>>,
1080 aad: impl Into<Cow<'_, [u8]>>,
1081 ciphertext: impl Into<DataSource<'_>>,
1082 ) -> Result<StreamReader<u8>, Error> {
1083 let (nonce, aad) = (nonce.into().into_owned(), aad.into().into_owned());
1084 run_sourced(ciphertext.into(), |rx| self.0.open(nonce, aad, None, rx)).await
1085 }
1086
1087 /// [`open`](Self::open) with an explicit tag size in bytes (the size
1088 /// the message was sealed with — see
1089 /// [`seal_with_tag_size`](Self::seal_with_tag_size)).
1090 pub async fn open_with_tag_size(
1091 &self,
1092 nonce: impl Into<Cow<'_, [u8]>>,
1093 aad: impl Into<Cow<'_, [u8]>>,
1094 tag_size: u8,
1095 ciphertext: impl Into<DataSource<'_>>,
1096 ) -> Result<StreamReader<u8>, Error> {
1097 let (nonce, aad) = (nonce.into().into_owned(), aad.into().into_owned());
1098 run_sourced(ciphertext.into(), |rx| {
1099 self.0.open(nonce, aad, Some(tag_size), rx)
1100 })
1101 .await
1102 }
1103
1104 /// The name of the key's algorithm family, e.g. `"AES-GCM"` —
1105 /// WebCrypto's `KeyAlgorithm.name`, spelled as the [W3C Web Cryptography
1106 /// API algorithm registry](https://www.w3.org/TR/WebCryptoAPI/#algorithm-overview)
1107 /// spells it.
1108 pub fn algorithm_name(&self) -> String {
1109 self.0.algorithm_name()
1110 }
1111
1112 /// The key length in bits, e.g. `256` for AES-256-GCM (WebCrypto's
1113 /// `AesKeyAlgorithm.length`).
1114 pub fn algorithm_length(&self) -> u32 {
1115 self.0.algorithm_length()
1116 }
1117
1118 /// The algorithm's standard nonce size in bytes, e.g. `12` for AES-GCM
1119 /// — always accepted by [`seal`](Self::seal)/[`open`](Self::open).
1120 /// Whether other lengths are accepted is the algorithm's contract
1121 /// (AES-GCM accepts 12 to 128 bytes inclusive).
1122 pub fn nonce_size(&self) -> u32 {
1123 self.0.nonce_size()
1124 }
1125
1126 /// The size in bytes of the tag trailing the ciphertext, e.g. `16` —
1127 /// for framing arithmetic (sealed length = plaintext length +
1128 /// `tag_size`).
1129 pub fn tag_size(&self) -> u32 {
1130 self.0.tag_size()
1131 }
1132
1133 /// Whether [`export_key_raw`](Self::export_key_raw) may return the key material
1134 /// (see [`Mac::extractable`]).
1135 pub fn extractable(&self) -> bool {
1136 self.0.extractable()
1137 }
1138
1139 /// Whether the key permits [`seal`](Self::seal) — the usage recorded
1140 /// at mint. A refused operation fails [`Error::NotPermitted`].
1141 pub fn can_seal(&self) -> bool {
1142 self.0.can_seal()
1143 }
1144
1145 /// Whether the key permits [`open`](Self::open). See
1146 /// [`can_seal`](Self::can_seal).
1147 pub fn can_open(&self) -> bool {
1148 self.0.can_open()
1149 }
1150
1151 /// Whether the key permits [`wrap`](Self::wrap).
1152 pub fn can_wrap(&self) -> bool {
1153 self.0.can_wrap()
1154 }
1155
1156 /// Whether the key permits [`unwrap`](Self::unwrap). See
1157 /// [`can_wrap`](Self::can_wrap).
1158 pub fn can_unwrap(&self) -> bool {
1159 self.0.can_unwrap()
1160 }
1161
1162 /// Encrypt and authenticate serialized key material under `nonce`
1163 /// with `aad`, exactly as `seal` encrypts a message (the WIT
1164 /// `aead-key.wrap` contract). Consumes the [`WrapInput`].
1165 pub async fn wrap(
1166 &self,
1167 nonce: impl Into<Vec<u8>>,
1168 aad: impl Into<Vec<u8>>,
1169 tag_size: Option<u8>,
1170 input: WrapInput,
1171 ) -> Result<Vec<u8>, Error> {
1172 self.0
1173 .wrap(nonce.into(), aad.into(), tag_size, input.into_raw())
1174 .await
1175 .map_err(Error::from)
1176 }
1177
1178 /// Decrypt and verify wrapped key material into an [`UnwrapInput`]
1179 /// for a typed unwrap mint (the WIT `aead-key.unwrap` contract).
1180 pub async fn unwrap(
1181 &self,
1182 nonce: impl Into<Vec<u8>>,
1183 aad: impl Into<Vec<u8>>,
1184 tag_size: Option<u8>,
1185 wrapped: impl Into<Vec<u8>>,
1186 ) -> Result<UnwrapInput, Error> {
1187 self.0
1188 .unwrap(nonce.into(), aad.into(), tag_size, wrapped.into())
1189 .await
1190 .map(UnwrapInput::from_raw)
1191 .map_err(Error::from)
1192 }
1193
1194 /// This key's raw material as a [`WrapInput`], behind the same
1195 /// extractability gate as [`export_key_raw`](Self::export_key_raw).
1196 pub async fn to_wrap_input_raw(&self) -> Result<WrapInput, Error> {
1197 self.0
1198 .to_wrap_input_raw()
1199 .await
1200 .map(WrapInput::from_raw)
1201 .map_err(Error::from)
1202 }
1203
1204 /// The JWK serialization as a [`WrapInput`], behind the same gate.
1205 pub async fn to_wrap_input_jwk(&self) -> Result<WrapInput, Error> {
1206 self.0
1207 .to_wrap_input_jwk()
1208 .await
1209 .map(WrapInput::from_raw)
1210 .map_err(Error::from)
1211 }
1212
1213 /// The raw key material; fails with [`Error::NotExtractable`] unless the
1214 /// key was minted extractable (an API property, not a physical one —
1215 /// see [`Mac::export_key_raw`]).
1216 pub async fn export_key_raw(&self) -> Result<Vec<u8>, Error> {
1217 self.0.export_key_raw().await.map_err(Error::from)
1218 }
1219
1220 /// The key as an RFC 7517 `oct` JSON Web Key (JSON text), behind the
1221 /// same extractability gate as [`export_key_raw`](Self::export_key_raw).
1222 /// Algorithms with no registered JWK form fail [`Error::Unsupported`].
1223 pub async fn export_key_jwk(&self) -> Result<String, Error> {
1224 self.0.export_key_jwk().await.map_err(Error::from)
1225 }
1226}
1227
1228/// A `digest.digest`: a reusable, algorithm-bound hash.
1229///
1230/// A digest authenticates nothing by itself: to check untrusted data
1231/// against a known digest, compare [`compute`](Self::compute)'s result
1232/// with a constant-time byte comparison; when authenticity is needed, use
1233/// a [`Mac`].
1234pub struct Digest(bindings::digest::Digest);
1235newtype_common!(Digest, bindings::digest::Digest, "digest");
1236
1237impl Digest {
1238 /// Digest `data`. The resource is reusable; the result is
1239 /// chunking-invariant.
1240 pub async fn compute(&self, data: impl Into<DataSource<'_>>) -> Result<Vec<u8>, Error> {
1241 run_sourced(data.into(), |rx| self.0.compute(rx)).await
1242 }
1243
1244 /// The name of the algorithm this resource is bound to, e.g.
1245 /// `"SHA-256"` — spelled as the [W3C Web Cryptography API algorithm
1246 /// registry](https://www.w3.org/TR/WebCryptoAPI/#algorithm-overview)
1247 /// (and `crypto.subtle.digest`) spells it.
1248 pub fn algorithm_name(&self) -> String {
1249 self.0.algorithm_name()
1250 }
1251}
1252
1253/// A `signature.verifying-key`: public-key signature verification.
1254/// Secret-free — a component holding only this key provably cannot sign.
1255pub struct VerifyingKey(bindings::signature::VerifyingKey);
1256newtype_common!(
1257 VerifyingKey,
1258 bindings::signature::VerifyingKey,
1259 "verifying-key"
1260);
1261
1262impl VerifyingKey {
1263 /// Verify `sig` over `data`.
1264 ///
1265 /// Fails closed with [`Error::AuthenticationFailed`] if the signature
1266 /// does not verify — deliberately a `Result` rather than a `bool`: an
1267 /// ignored boolean fails open, a dropped `Result` does not. The precise
1268 /// verification criterion (which degenerate keys and signatures must be
1269 /// rejected) is defined by the key's minting interface, exactly like
1270 /// the wire format.
1271 pub async fn verify(
1272 &self,
1273 data: impl Into<DataSource<'_>>,
1274 sig: impl Into<Cow<'_, [u8]>>,
1275 ) -> Result<(), Error> {
1276 let sig = sig.into().into_owned();
1277 run_sourced(data.into(), |rx| self.0.verify(rx, sig)).await
1278 }
1279
1280 /// The name of the key's algorithm family, e.g. `"Ed25519"` or
1281 /// `"ECDSA"` — WebCrypto's `KeyAlgorithm.name`, spelled as the [W3C Web
1282 /// Cryptography API algorithm
1283 /// registry](https://www.w3.org/TR/WebCryptoAPI/#algorithm-overview)
1284 /// spells it.
1285 pub fn algorithm_name(&self) -> String {
1286 self.0.algorithm_name()
1287 }
1288
1289 /// The registry name of the curve for curve-parameterized algorithms,
1290 /// e.g. `"P-256"` (WebCrypto's `EcKeyAlgorithm.namedCurve`). `None` for
1291 /// Ed25519, whose curve is implied by the name.
1292 pub fn algorithm_curve(&self) -> Option<String> {
1293 self.0.algorithm_curve()
1294 }
1295
1296 /// The registry name of the digest bound at mint, e.g. `"SHA-256"`.
1297 /// `None` for Ed25519: RFC 8032 fixes SHA-512 internally, so it is not
1298 /// a parameter.
1299 pub fn algorithm_hash(&self) -> Option<String> {
1300 self.0.algorithm_hash()
1301 }
1302
1303 /// The key's length in bits for algorithms parameterized by one — the
1304 /// RSA modulus length (WebCrypto's `RsaKeyAlgorithm.modulusLength`).
1305 /// `None` for Ed25519 and ECDSA, whose key size is fixed by the
1306 /// algorithm or curve.
1307 pub fn algorithm_length(&self) -> Option<u32> {
1308 self.0.algorithm_length()
1309 }
1310
1311 /// The RSA public exponent's big-endian bytes (WebCrypto's
1312 /// `RsaKeyAlgorithm.publicExponent`; `[1, 0, 1]` for 65537). `None`
1313 /// for Ed25519 and ECDSA, which have no such parameter.
1314 pub fn algorithm_public_exponent(&self) -> Option<Vec<u8>> {
1315 self.0.algorithm_public_exponent()
1316 }
1317
1318 /// The public key material, in the minting interface's documented
1319 /// public format.
1320 ///
1321 /// There is no extractability gate on this key, so this never fails
1322 /// with [`Error::NotExtractable`]. It can still fail with
1323 /// [`Error::Other`]: a provider may hold the key as a handle it can
1324 /// *use* but not *read*, so verifying succeeds while recovering the
1325 /// encoding does not.
1326 pub async fn export_key_raw(&self) -> Result<Vec<u8>, Error> {
1327 self.0.export_key_raw().await.map_err(Error::from)
1328 }
1329
1330 /// The public key as an X.509 SubjectPublicKeyInfo (DER), with the
1331 /// same fallibility as [`export_key_raw`](Self::export_key_raw).
1332 pub async fn export_key_spki(&self) -> Result<Vec<u8>, Error> {
1333 self.0.export_key_spki().await.map_err(Error::from)
1334 }
1335
1336 /// The public key as a JWK (JSON text — an RFC 8037 OKP public key
1337 /// for Ed25519, an EC public key for ECDSA), with the same
1338 /// fallibility as [`export_key_raw`](Self::export_key_raw).
1339 pub async fn export_key_jwk(&self) -> Result<String, Error> {
1340 self.0.export_key_jwk().await.map_err(Error::from)
1341 }
1342}
1343
1344/// A `signature.signing-key`: private-key signing.
1345pub struct SigningKey(bindings::signature::SigningKey);
1346newtype_common!(SigningKey, bindings::signature::SigningKey, "signing-key");
1347
1348impl SigningKey {
1349 /// Sign `data`, returning the signature in the minting interface's
1350 /// documented wire format.
1351 ///
1352 /// Fails only for operational reasons ([`Error::Other`], or
1353 /// [`Error::Read`] for a failing `DataSource::from_reader` source) —
1354 /// never for misuse, which is unrepresentable.
1355 pub async fn sign(&self, data: impl Into<DataSource<'_>>) -> Result<Vec<u8>, Error> {
1356 run_sourced(data.into(), |rx| self.0.sign(rx)).await
1357 }
1358
1359 /// See [`VerifyingKey::algorithm_name`].
1360 pub fn algorithm_name(&self) -> String {
1361 self.0.algorithm_name()
1362 }
1363
1364 /// See [`VerifyingKey::algorithm_curve`].
1365 pub fn algorithm_curve(&self) -> Option<String> {
1366 self.0.algorithm_curve()
1367 }
1368
1369 /// See [`VerifyingKey::algorithm_hash`].
1370 pub fn algorithm_hash(&self) -> Option<String> {
1371 self.0.algorithm_hash()
1372 }
1373
1374 /// See [`VerifyingKey::algorithm_length`].
1375 pub fn algorithm_length(&self) -> Option<u32> {
1376 self.0.algorithm_length()
1377 }
1378
1379 /// See [`VerifyingKey::algorithm_public_exponent`].
1380 pub fn algorithm_public_exponent(&self) -> Option<Vec<u8>> {
1381 self.0.algorithm_public_exponent()
1382 }
1383
1384 /// Whether the private key material may be exported by
1385 /// [`export_key_jwk`](Self::export_key_jwk) /
1386 /// [`export_key_pkcs8`](Self::export_key_pkcs8) and the wrap inputs —
1387 /// mint-time recorded policy, which platform-backed key storage also
1388 /// honors.
1389 ///
1390 /// Asking is not the same as exporting: interrogating extractability
1391 /// through an export alone would hand you the material whenever the
1392 /// answer is yes.
1393 pub fn extractable(&self) -> bool {
1394 self.0.extractable()
1395 }
1396
1397 /// Whether the key permits [`sign`](Self::sign) — the usage recorded
1398 /// at mint (or carried by a platform keystore key). A refused
1399 /// operation fails [`Error::NotPermitted`].
1400 pub fn can_sign(&self) -> bool {
1401 self.0.can_sign()
1402 }
1403
1404 /// The private key as a JWK (JSON text — an RFC 8037 OKP private key
1405 /// for Ed25519, an EC private key for ECDSA); fails with
1406 /// [`Error::NotExtractable`] unless the key was minted extractable.
1407 /// Extractability is an API property, not a physical one: the
1408 /// guarantee is that components holding only the handle cannot obtain
1409 /// the material through this API.
1410 pub async fn export_key_jwk(&self) -> Result<String, Error> {
1411 self.0.export_key_jwk().await.map_err(Error::from)
1412 }
1413
1414 /// The private key as a PKCS#8 PrivateKeyInfo (DER), behind the same
1415 /// extractability gate as [`export_key_jwk`](Self::export_key_jwk).
1416 pub async fn export_key_pkcs8(&self) -> Result<Vec<u8>, Error> {
1417 self.0.export_key_pkcs8().await.map_err(Error::from)
1418 }
1419
1420 /// The private-key JWK serialization as a [`WrapInput`] for wrapping
1421 /// under another key — the material transits neither caller. Behind
1422 /// the same extractability gate as
1423 /// [`export_key_jwk`](Self::export_key_jwk).
1424 pub async fn to_wrap_input_jwk(&self) -> Result<WrapInput, Error> {
1425 self.0
1426 .to_wrap_input_jwk()
1427 .await
1428 .map(WrapInput::from_raw)
1429 .map_err(Error::from)
1430 }
1431
1432 /// The PKCS#8 serialization as a [`WrapInput`], behind the same gate.
1433 pub async fn to_wrap_input_pkcs8(&self) -> Result<WrapInput, Error> {
1434 self.0
1435 .to_wrap_input_pkcs8()
1436 .await
1437 .map(WrapInput::from_raw)
1438 .map_err(Error::from)
1439 }
1440}
1441
1442/// A `public-encryption.encryption-key`: the public half of asymmetric
1443/// encryption — encryption and wrapping, secret-free to hold.
1444///
1445/// Operations take and return whole byte buffers rather than
1446/// [`DataSource`]s: the plaintext is bounded by the key (for RSA-OAEP, the
1447/// modulus length minus the padding overhead), so there is nothing
1448/// unbounded to stream. Encryption is randomized — encrypting one
1449/// plaintext twice yields different ciphertexts, and both decrypt.
1450pub struct EncryptionKey(bindings::public_encryption::EncryptionKey);
1451newtype_common!(
1452 EncryptionKey,
1453 bindings::public_encryption::EncryptionKey,
1454 "encryption-key"
1455);
1456
1457impl EncryptionKey {
1458 /// Encrypt a plaintext bounded by the key. `label` is optional context
1459 /// bound into the padding: decryption succeeds only under the same
1460 /// label (WebCrypto's `RsaOaepParams.label`). A plaintext above the
1461 /// key's bound fails [`Error::Extension`] (origin `"polymorph:webcrypto"`,
1462 /// name `"message-too-long"`; see [`crate::extension`]) — the signal to
1463 /// switch to hybrid wrapping: encrypt a symmetric key, wrap the payload
1464 /// under it.
1465 pub async fn encrypt(
1466 &self,
1467 label: Option<&[u8]>,
1468 plaintext: impl Into<Vec<u8>>,
1469 ) -> Result<Vec<u8>, Error> {
1470 self.0
1471 .encrypt(label.map(<[u8]>::to_vec), plaintext.into())
1472 .await
1473 .map_err(Error::from)
1474 }
1475
1476 /// Wrap key material serialized as a [`WrapInput`]: the material
1477 /// transits neither caller. The serialized form must fit the key's
1478 /// bound — symmetric-key JWKs do, private-key serializations generally
1479 /// do not — else the same `"message-too-long"` extension condition as
1480 /// [`encrypt`](Self::encrypt). Consumes the [`WrapInput`].
1481 pub async fn wrap(&self, label: Option<&[u8]>, input: WrapInput) -> Result<Vec<u8>, Error> {
1482 self.0
1483 .wrap(label.map(<[u8]>::to_vec), input.into_raw())
1484 .await
1485 .map_err(Error::from)
1486 }
1487
1488 /// The registry name of the key's algorithm family, e.g. `"RSA-OAEP"`
1489 /// — WebCrypto's `KeyAlgorithm.name`.
1490 pub fn algorithm_name(&self) -> String {
1491 self.0.algorithm_name()
1492 }
1493
1494 /// The registry name of the digest bound at mint, e.g. `"SHA-256"`.
1495 pub fn algorithm_hash(&self) -> Option<String> {
1496 self.0.algorithm_hash()
1497 }
1498
1499 /// The key's length in bits for algorithms parameterized by one — the
1500 /// RSA modulus length (WebCrypto's `RsaKeyAlgorithm.modulusLength`).
1501 pub fn algorithm_length(&self) -> Option<u32> {
1502 self.0.algorithm_length()
1503 }
1504
1505 /// The public exponent's big-endian bytes (WebCrypto's
1506 /// `RsaKeyAlgorithm.publicExponent`; `[1, 0, 1]` for 65537).
1507 pub fn algorithm_public_exponent(&self) -> Option<Vec<u8>> {
1508 self.0.algorithm_public_exponent()
1509 }
1510
1511 /// The public key material, in the minting interface's documented
1512 /// public format. Algorithms without a raw public form (the RSA
1513 /// family) fail [`Error::Unsupported`].
1514 ///
1515 /// There is no extractability gate on public material, so this never
1516 /// fails [`Error::NotExtractable`] — but it can fail [`Error::Other`]:
1517 /// a provider may hold the key as a handle it can use but not read
1518 /// (see [`VerifyingKey::export_key_raw`]).
1519 pub async fn export_key_raw(&self) -> Result<Vec<u8>, Error> {
1520 self.0.export_key_raw().await.map_err(Error::from)
1521 }
1522
1523 /// The public key as an X.509 SubjectPublicKeyInfo (DER), with the
1524 /// same handle-not-bytes fallibility as
1525 /// [`export_key_raw`](Self::export_key_raw).
1526 pub async fn export_key_spki(&self) -> Result<Vec<u8>, Error> {
1527 self.0.export_key_spki().await.map_err(Error::from)
1528 }
1529
1530 /// The public key as a JWK (JSON text), with the same fallibility as
1531 /// [`export_key_raw`](Self::export_key_raw).
1532 pub async fn export_key_jwk(&self) -> Result<String, Error> {
1533 self.0.export_key_jwk().await.map_err(Error::from)
1534 }
1535}
1536
1537/// A `public-encryption.decryption-key`: the private half of asymmetric
1538/// encryption. [`decrypt`](Self::decrypt) and [`unwrap`](Self::unwrap) are
1539/// one-shot calls on the immutable key, over whole byte buffers (see
1540/// [`EncryptionKey`] for why nothing streams here).
1541pub struct DecryptionKey(bindings::public_encryption::DecryptionKey);
1542newtype_common!(
1543 DecryptionKey,
1544 bindings::public_encryption::DecryptionKey,
1545 "decryption-key"
1546);
1547
1548impl DecryptionKey {
1549 /// Decrypt a ciphertext produced by the matching public key under the
1550 /// same `label`. Fails [`Error::NotPermitted`] without the
1551 /// [`decrypt`](DecryptionKeyOptions::decrypt) grant; every decryption
1552 /// failure is the one detail-free [`Error::AuthenticationFailed`] — a
1553 /// wrong-length ciphertext, damaged padding, and a mismatched label
1554 /// are indistinguishable, as RFC 8017 requires.
1555 pub async fn decrypt(
1556 &self,
1557 label: Option<&[u8]>,
1558 ciphertext: impl Into<Vec<u8>>,
1559 ) -> Result<Vec<u8>, Error> {
1560 self.0
1561 .decrypt(label.map(<[u8]>::to_vec), ciphertext.into())
1562 .await
1563 .map_err(Error::from)
1564 }
1565
1566 /// Decrypt a wrapped key into an [`UnwrapInput`] for a typed unwrap
1567 /// mint: the material never reaches the caller. Fails
1568 /// [`Error::NotPermitted`] without the
1569 /// [`unwrap`](DecryptionKeyOptions::unwrap) grant; failures are
1570 /// otherwise as [`decrypt`](Self::decrypt).
1571 pub async fn unwrap(
1572 &self,
1573 label: Option<&[u8]>,
1574 ciphertext: impl Into<Vec<u8>>,
1575 ) -> Result<UnwrapInput, Error> {
1576 self.0
1577 .unwrap(label.map(<[u8]>::to_vec), ciphertext.into())
1578 .await
1579 .map(UnwrapInput::from_raw)
1580 .map_err(Error::from)
1581 }
1582
1583 /// See [`EncryptionKey::algorithm_name`].
1584 pub fn algorithm_name(&self) -> String {
1585 self.0.algorithm_name()
1586 }
1587
1588 /// See [`EncryptionKey::algorithm_hash`].
1589 pub fn algorithm_hash(&self) -> Option<String> {
1590 self.0.algorithm_hash()
1591 }
1592
1593 /// See [`EncryptionKey::algorithm_length`].
1594 pub fn algorithm_length(&self) -> Option<u32> {
1595 self.0.algorithm_length()
1596 }
1597
1598 /// See [`EncryptionKey::algorithm_public_exponent`].
1599 pub fn algorithm_public_exponent(&self) -> Option<Vec<u8>> {
1600 self.0.algorithm_public_exponent()
1601 }
1602
1603 /// Whether the key permits [`decrypt`](Self::decrypt) — the usage
1604 /// recorded at mint. A refused operation fails
1605 /// [`Error::NotPermitted`].
1606 pub fn can_decrypt(&self) -> bool {
1607 self.0.can_decrypt()
1608 }
1609
1610 /// Whether the key permits [`unwrap`](Self::unwrap). See
1611 /// [`can_decrypt`](Self::can_decrypt).
1612 pub fn can_unwrap(&self) -> bool {
1613 self.0.can_unwrap()
1614 }
1615
1616 /// Whether the export functions may return this key's material (see
1617 /// [`Mac::extractable`]).
1618 pub fn extractable(&self) -> bool {
1619 self.0.extractable()
1620 }
1621
1622 /// The private key as a JWK (JSON text); fails
1623 /// [`Error::NotExtractable`] unless the key was minted extractable.
1624 pub async fn export_key_jwk(&self) -> Result<String, Error> {
1625 self.0.export_key_jwk().await.map_err(Error::from)
1626 }
1627
1628 /// The private key as a PKCS#8 PrivateKeyInfo (DER), behind the same
1629 /// extractability gate as [`export_key_jwk`](Self::export_key_jwk).
1630 pub async fn export_key_pkcs8(&self) -> Result<Vec<u8>, Error> {
1631 self.0.export_key_pkcs8().await.map_err(Error::from)
1632 }
1633
1634 /// The private-key JWK serialization as a [`WrapInput`], for wrapping
1635 /// under another key. Behind the same extractability gate as
1636 /// [`export_key_jwk`](Self::export_key_jwk).
1637 pub async fn to_wrap_input_jwk(&self) -> Result<WrapInput, Error> {
1638 self.0
1639 .to_wrap_input_jwk()
1640 .await
1641 .map(WrapInput::from_raw)
1642 .map_err(Error::from)
1643 }
1644
1645 /// The PKCS#8 serialization as a [`WrapInput`], behind the same gate.
1646 pub async fn to_wrap_input_pkcs8(&self) -> Result<WrapInput, Error> {
1647 self.0
1648 .to_wrap_input_pkcs8()
1649 .await
1650 .map(WrapInput::from_raw)
1651 .map_err(Error::from)
1652 }
1653}
1654
1655// --- key & digest creation -------------------------------------------------------
1656
1657/// An unauthenticated-cipher key (AES-CBC or AES-CTR), minted by
1658/// [`aes_cbc`] / [`aes_ctr`]. **Nothing this key does authenticates**:
1659/// ciphertext is malleable and a successful [`decrypt`](Self::decrypt) is
1660/// not evidence the input is untampered. Default to [`Aead`]; use this
1661/// kind only where an existing format fixes the mode. See the WIT
1662/// `cipher` interface for the full contract.
1663pub struct CipherKey(bindings::cipher::CipherKey);
1664newtype_common!(CipherKey, bindings::cipher::CipherKey, "cipher-key");
1665
1666impl CipherKey {
1667 /// Encrypt `plaintext` under `iv` (for AES-CTR, the initial counter
1668 /// block plus the counter width in bits; AES-CBC callers pass `None`).
1669 /// The caller owns the IV discipline — see the minting interface's
1670 /// Security notes.
1671 pub fn encrypt<'a>(
1672 &'a self,
1673 iv: impl Into<Cow<'a, [u8]>>,
1674 counter_length: Option<u8>,
1675 plaintext: impl Into<DataSource<'a>>,
1676 ) -> Seal<'a> {
1677 let iv = iv.into().into_owned();
1678 Seal::new(
1679 plaintext.into(),
1680 Box::new(move |rx| Box::pin(self.0.encrypt(iv, counter_length, rx))),
1681 )
1682 }
1683
1684 /// Decrypt `ciphertext` under `iv`. The plaintext is unauthenticated:
1685 /// treat it as attacker-influenced data even on success. Malformed
1686 /// input fails [`Error::Other`], deliberately uniform across
1687 /// conditions.
1688 pub async fn decrypt(
1689 &self,
1690 iv: impl Into<Cow<'_, [u8]>>,
1691 counter_length: Option<u8>,
1692 ciphertext: impl Into<DataSource<'_>>,
1693 ) -> Result<StreamReader<u8>, Error> {
1694 let iv = iv.into().into_owned();
1695 run_sourced(ciphertext.into(), |rx| {
1696 self.0.decrypt(iv, counter_length, rx)
1697 })
1698 .await
1699 }
1700
1701 /// The name of the key's algorithm family, e.g. `"AES-CBC"`.
1702 pub fn algorithm_name(&self) -> String {
1703 self.0.algorithm_name()
1704 }
1705
1706 /// The key length in bits.
1707 pub fn algorithm_length(&self) -> u32 {
1708 self.0.algorithm_length()
1709 }
1710
1711 /// The algorithm's IV size in bytes (`16` for the AES modes).
1712 pub fn iv_size(&self) -> u32 {
1713 self.0.iv_size()
1714 }
1715
1716 /// Whether [`export_key_raw`](Self::export_key_raw) may return the key
1717 /// material (see [`Mac::extractable`]).
1718 pub fn extractable(&self) -> bool {
1719 self.0.extractable()
1720 }
1721
1722 /// Whether the key permits [`encrypt`](Self::encrypt).
1723 pub fn can_encrypt(&self) -> bool {
1724 self.0.can_encrypt()
1725 }
1726
1727 /// Whether the key permits [`decrypt`](Self::decrypt).
1728 pub fn can_decrypt(&self) -> bool {
1729 self.0.can_decrypt()
1730 }
1731
1732 /// Whether the key permits [`wrap`](Self::wrap).
1733 pub fn can_wrap(&self) -> bool {
1734 self.0.can_wrap()
1735 }
1736
1737 /// Whether the key permits [`unwrap`](Self::unwrap). See
1738 /// [`can_wrap`](Self::can_wrap).
1739 pub fn can_unwrap(&self) -> bool {
1740 self.0.can_unwrap()
1741 }
1742
1743 /// Encrypt serialized key material under `iv`, exactly as `encrypt`
1744 /// encrypts a message (the WIT `cipher-key.wrap` contract; nothing
1745 /// here authenticates). Consumes the [`WrapInput`].
1746 pub async fn wrap(
1747 &self,
1748 iv: impl Into<Vec<u8>>,
1749 counter_length: Option<u8>,
1750 input: WrapInput,
1751 ) -> Result<Vec<u8>, Error> {
1752 self.0
1753 .wrap(iv.into(), counter_length, input.into_raw())
1754 .await
1755 .map_err(Error::from)
1756 }
1757
1758 /// Decrypt wrapped key material into an [`UnwrapInput`] for a typed
1759 /// unwrap mint (the WIT `cipher-key.unwrap` contract; the result is
1760 /// unauthenticated).
1761 pub async fn unwrap(
1762 &self,
1763 iv: impl Into<Vec<u8>>,
1764 counter_length: Option<u8>,
1765 wrapped: impl Into<Vec<u8>>,
1766 ) -> Result<UnwrapInput, Error> {
1767 self.0
1768 .unwrap(iv.into(), counter_length, wrapped.into())
1769 .await
1770 .map(UnwrapInput::from_raw)
1771 .map_err(Error::from)
1772 }
1773
1774 /// This key's raw material as a [`WrapInput`], behind the same
1775 /// extractability gate as [`export_key_raw`](Self::export_key_raw).
1776 pub async fn to_wrap_input_raw(&self) -> Result<WrapInput, Error> {
1777 self.0
1778 .to_wrap_input_raw()
1779 .await
1780 .map(WrapInput::from_raw)
1781 .map_err(Error::from)
1782 }
1783
1784 /// The JWK serialization as a [`WrapInput`], behind the same gate.
1785 pub async fn to_wrap_input_jwk(&self) -> Result<WrapInput, Error> {
1786 self.0
1787 .to_wrap_input_jwk()
1788 .await
1789 .map(WrapInput::from_raw)
1790 .map_err(Error::from)
1791 }
1792
1793 /// The raw key material, behind the extractability gate.
1794 pub async fn export_key_raw(&self) -> Result<Vec<u8>, Error> {
1795 Ok(self.0.export_key_raw().await?)
1796 }
1797
1798 /// The key as an `oct` JWK, behind the same gate.
1799 pub async fn export_key_jwk(&self) -> Result<String, Error> {
1800 Ok(self.0.export_key_jwk().await?)
1801 }
1802}
1803
1804// --- derivation ------------------------------------------------------------------
1805
1806/// An `hkdf.ikm`: imported input keying material for HKDF, minted by
1807/// [`hkdf::import_ikm`]. Never readable back through the API under any
1808/// grant; the grants recorded at import are copied onto every
1809/// [`DeriveInput`] built on it (via [`hkdf_sha2::prepare`] and friends).
1810pub struct Ikm(bindings::hkdf::Ikm);
1811newtype_common!(Ikm, bindings::hkdf::Ikm, "ikm");
1812
1813impl Ikm {
1814 /// Whether inputs built on this material may yield raw bits. See
1815 /// [`DeriveOptions::derive_bits`].
1816 pub fn can_derive_bits(&self) -> bool {
1817 self.0.can_derive_bits()
1818 }
1819
1820 /// Whether inputs built on this material may mint keys. See
1821 /// [`DeriveOptions::derive_key`].
1822 pub fn can_derive_key(&self) -> bool {
1823 self.0.can_derive_key()
1824 }
1825}
1826
1827/// A `pbkdf2.password`: an imported password, minted by
1828/// [`pbkdf2::import_password`]. Never readable back through the API under
1829/// any grant; the grants recorded at import are copied onto every
1830/// [`DeriveInput`] built on it (via [`pbkdf2_sha2::prepare`] and friends).
1831pub struct Password(bindings::pbkdf2::Password);
1832newtype_common!(Password, bindings::pbkdf2::Password, "password");
1833
1834impl Password {
1835 /// Whether inputs built on this password may yield raw bits. See
1836 /// [`DeriveOptions::derive_bits`].
1837 pub fn can_derive_bits(&self) -> bool {
1838 self.0.can_derive_bits()
1839 }
1840
1841 /// Whether inputs built on this password may mint keys. See
1842 /// [`DeriveOptions::derive_key`].
1843 pub fn can_derive_key(&self) -> bool {
1844 self.0.can_derive_key()
1845 }
1846}
1847
1848/// A `derivation.derive-input`: a fully parameterized derivation — base
1849/// secret plus every parameter, minted by the `prepare` functions
1850/// ([`hkdf_sha2::prepare`], [`pbkdf2_sha2::prepare`], …) and by
1851/// [`AgreementSecretKey::agree`].
1852///
1853/// Consume it through [`derive_bits`](Self::derive_bits) or hand it to a
1854/// target interface's `derive_key` (e.g. [`hmac_sha2::derive_key`],
1855/// [`aes_gcm::derive_key`]); both may run any number of times. While an
1856/// input is live it may hold derivation state of its own (for HKDF, the
1857/// PRK), so a base secret's sensitivity extends to the inputs built on it.
1858pub struct DeriveInput(bindings::derivation::DeriveInput);
1859newtype_common!(
1860 DeriveInput,
1861 bindings::derivation::DeriveInput,
1862 "derive-input"
1863);
1864
1865impl DeriveInput {
1866 /// The derived bits.
1867 ///
1868 /// `length` is in bits — WebCrypto's denomination for this parameter —
1869 /// and must be a multiple of 8 (none of the package's implementations
1870 /// serve sub-byte outputs). `None` means the source's natural output
1871 /// length: an agreement's full shared secret (32 bytes for X25519).
1872 /// KDF sources have none — their output length is a caller choice —
1873 /// so `None` fails [`Error::Other`] there, matching the platform's
1874 /// own null-length behavior.
1875 ///
1876 /// Fails [`Error::NotPermitted`] without the
1877 /// [`derive_bits`](DeriveOptions::derive_bits) grant.
1878 pub async fn derive_bits(&self, length: Option<u32>) -> Result<Vec<u8>, Error> {
1879 self.0.derive_bits(length).await.map_err(Error::from)
1880 }
1881
1882 /// Whether [`derive_bits`](Self::derive_bits) (and minting
1883 /// *extractable* keys) is permitted — the grant copied from the base
1884 /// secret. A refused operation fails [`Error::NotPermitted`].
1885 pub fn can_derive_bits(&self) -> bool {
1886 self.0.can_derive_bits()
1887 }
1888
1889 /// Whether the target interfaces' `derive_key` is permitted. See
1890 /// [`can_derive_bits`](Self::can_derive_bits).
1891 pub fn can_derive_key(&self) -> bool {
1892 self.0.can_derive_key()
1893 }
1894}
1895
1896/// A `key-agreement.public-key`: the exchangeable half of an agreement
1897/// keypair, minted by [`x25519`]'s imports and
1898/// [`generate_key`](x25519::generate_key). Secret-free.
1899pub struct AgreementPublicKey(bindings::key_agreement::PublicKey);
1900newtype_common!(
1901 AgreementPublicKey,
1902 bindings::key_agreement::PublicKey,
1903 "public-key"
1904);
1905
1906impl AgreementPublicKey {
1907 /// The name of the key's algorithm family, e.g. `"X25519"` —
1908 /// WebCrypto's `KeyAlgorithm.name`.
1909 pub fn algorithm_name(&self) -> String {
1910 self.0.algorithm_name()
1911 }
1912
1913 /// The public key material, in the minting interface's documented
1914 /// public format (32 bytes for X25519).
1915 ///
1916 /// There is no extractability gate on public material, so this never
1917 /// fails [`Error::NotExtractable`] — but it can fail [`Error::Other`]:
1918 /// a provider may hold the key as a handle it can use but not read
1919 /// (see [`VerifyingKey::export_key_raw`]).
1920 pub async fn export_key_raw(&self) -> Result<Vec<u8>, Error> {
1921 self.0.export_key_raw().await.map_err(Error::from)
1922 }
1923
1924 /// The public key as a JWK (an RFC 8037 OKP public key for X25519),
1925 /// with the same handle-not-bytes fallibility as
1926 /// [`export_key_raw`](Self::export_key_raw).
1927 pub async fn export_key_jwk(&self) -> Result<String, Error> {
1928 self.0.export_key_jwk().await.map_err(Error::from)
1929 }
1930
1931 /// The public key as an X.509 SubjectPublicKeyInfo (DER), with the
1932 /// same handle-not-bytes fallibility as
1933 /// [`export_key_raw`](Self::export_key_raw).
1934 pub async fn export_key_spki(&self) -> Result<Vec<u8>, Error> {
1935 self.0.export_key_spki().await.map_err(Error::from)
1936 }
1937}
1938
1939/// A `key-agreement.secret-key`: the private half of an agreement keypair.
1940/// [`agree`](Self::agree) is one-shot on the immutable key; the derivation
1941/// state lives in the [`DeriveInput`] it returns.
1942pub struct AgreementSecretKey(bindings::key_agreement::SecretKey);
1943newtype_common!(
1944 AgreementSecretKey,
1945 bindings::key_agreement::SecretKey,
1946 "secret-key"
1947);
1948
1949impl AgreementSecretKey {
1950 /// The shared secret with `peer`, as a [`DeriveInput`] whose grants
1951 /// are copied from this key's mint options.
1952 ///
1953 /// The returned input has a *natural* output length — the agreement's
1954 /// full shared secret — so `derive_bits(None)` returns the whole
1955 /// secret and [`hkdf_sha2::prepare_from`] accepts it as IKM.
1956 ///
1957 /// Fails [`Error::InvalidKey`] if the shared secret is the all-zero
1958 /// value (a small-order `peer`; the mandatory contributory check,
1959 /// performed in constant time) or if `peer` is bound to a different
1960 /// algorithm than this key.
1961 pub async fn agree(&self, peer: &AgreementPublicKey) -> Result<DeriveInput, Error> {
1962 Ok(DeriveInput::from_raw(self.0.agree(&peer.0).await?))
1963 }
1964
1965 /// See [`AgreementPublicKey::algorithm_name`].
1966 pub fn algorithm_name(&self) -> String {
1967 self.0.algorithm_name()
1968 }
1969
1970 /// Whether inputs agreed by this key may yield raw bits. See
1971 /// [`DeriveOptions::derive_bits`].
1972 pub fn can_derive_bits(&self) -> bool {
1973 self.0.can_derive_bits()
1974 }
1975
1976 /// Whether inputs agreed by this key may mint keys. See
1977 /// [`DeriveOptions::derive_key`].
1978 pub fn can_derive_key(&self) -> bool {
1979 self.0.can_derive_key()
1980 }
1981
1982 /// Whether the export functions may return this key's material (see
1983 /// [`Mac::extractable`]).
1984 pub fn extractable(&self) -> bool {
1985 self.0.extractable()
1986 }
1987
1988 /// The secret key as a JWK (an RFC 8037 OKP private key for X25519);
1989 /// fails [`Error::NotExtractable`] unless the key was minted
1990 /// extractable.
1991 pub async fn export_key_jwk(&self) -> Result<String, Error> {
1992 self.0.export_key_jwk().await.map_err(Error::from)
1993 }
1994
1995 /// The secret key as a PKCS#8 PrivateKeyInfo (DER), behind the same
1996 /// extractability gate as [`export_key_jwk`](Self::export_key_jwk).
1997 pub async fn export_key_pkcs8(&self) -> Result<Vec<u8>, Error> {
1998 self.0.export_key_pkcs8().await.map_err(Error::from)
1999 }
2000}
2001
2002/// A `wrapping.wrap-input`: one key's serialized material awaiting
2003/// encryption under a wrapping key. Single-use — the consuming wrap
2004/// operation takes it by value, on failure as on success.
2005pub struct WrapInput(bindings::wrapping::WrapInput);
2006newtype_common!(WrapInput, bindings::wrapping::WrapInput, "wrap-input");
2007
2008/// A `wrapping.unwrap-input`: decrypted key material awaiting a typed
2009/// unwrap mint. Single-use, like [`WrapInput`].
2010pub struct UnwrapInput(bindings::wrapping::UnwrapInput);
2011newtype_common!(UnwrapInput, bindings::wrapping::UnwrapInput, "unwrap-input");
2012
2013/// A `key-wrap.kw-key`: a dedicated key-wrapping key (AES-KW), bound to
2014/// its algorithm at creation.
2015pub struct KwKey(bindings::key_wrap::KwKey);
2016newtype_common!(KwKey, bindings::key_wrap::KwKey, "kw-key");
2017
2018impl KwKey {
2019 /// Encrypt serialized key material (the WIT `kw-key.wrap` contract:
2020 /// deterministic; JWK-formatted input is space-padded to a multiple
2021 /// of 8; the input domain is a multiple of 8 bytes, at least 16).
2022 /// Consumes the [`WrapInput`].
2023 pub async fn wrap(&self, input: WrapInput) -> Result<Vec<u8>, Error> {
2024 self.0.wrap(input.into_raw()).await.map_err(Error::from)
2025 }
2026
2027 /// Decrypt and integrity-check wrapped key material into an
2028 /// [`UnwrapInput`] for a typed unwrap mint. Every integrity failure —
2029 /// including input outside the wrapped-form domain — fails
2030 /// [`Error::AuthenticationFailed`].
2031 pub async fn unwrap(&self, wrapped: impl Into<Vec<u8>>) -> Result<UnwrapInput, Error> {
2032 self.0
2033 .unwrap(wrapped.into())
2034 .await
2035 .map(UnwrapInput::from_raw)
2036 .map_err(Error::from)
2037 }
2038
2039 /// The registry algorithm name, `"AES-KW"`.
2040 pub fn algorithm_name(&self) -> String {
2041 self.0.algorithm_name()
2042 }
2043
2044 /// The key length in bits.
2045 pub fn algorithm_length(&self) -> u32 {
2046 self.0.algorithm_length()
2047 }
2048
2049 /// Whether the key material may be exported.
2050 pub fn extractable(&self) -> bool {
2051 self.0.extractable()
2052 }
2053
2054 /// Whether the key permits [`wrap`](Self::wrap).
2055 pub fn can_wrap(&self) -> bool {
2056 self.0.can_wrap()
2057 }
2058
2059 /// Whether the key permits [`unwrap`](Self::unwrap).
2060 pub fn can_unwrap(&self) -> bool {
2061 self.0.can_unwrap()
2062 }
2063
2064 /// This key's raw material as a [`WrapInput`], behind the same
2065 /// extractability gate as [`export_key_raw`](Self::export_key_raw).
2066 pub async fn to_wrap_input_raw(&self) -> Result<WrapInput, Error> {
2067 self.0
2068 .to_wrap_input_raw()
2069 .await
2070 .map(WrapInput::from_raw)
2071 .map_err(Error::from)
2072 }
2073
2074 /// The JWK serialization as a [`WrapInput`], behind the same gate.
2075 pub async fn to_wrap_input_jwk(&self) -> Result<WrapInput, Error> {
2076 self.0
2077 .to_wrap_input_jwk()
2078 .await
2079 .map(WrapInput::from_raw)
2080 .map_err(Error::from)
2081 }
2082
2083 /// The raw key material, behind the extractability gate.
2084 pub async fn export_key_raw(&self) -> Result<Vec<u8>, Error> {
2085 self.0.export_key_raw().await.map_err(Error::from)
2086 }
2087
2088 /// The key as an `oct` JWK, behind the same gate.
2089 pub async fn export_key_jwk(&self) -> Result<String, Error> {
2090 self.0.export_key_jwk().await.map_err(Error::from)
2091 }
2092}
2093
2094pub mod aes_cbc;
2095pub mod aes_ctr;
2096pub mod aes_gcm;
2097pub mod aes_kw;
2098pub mod ecdh;
2099pub mod ecdsa;
2100pub mod ed25519;
2101pub mod hkdf;
2102pub mod hkdf_sha1;
2103pub mod hkdf_sha2;
2104pub mod hmac_sha1;
2105pub mod hmac_sha2;
2106pub mod pbkdf2;
2107pub mod pbkdf2_sha1;
2108pub mod pbkdf2_sha2;
2109pub mod rsa_oaep;
2110pub mod rsa_pss;
2111pub mod rsassa_pkcs1_v15;
2112#[cfg(feature = "sha1-checked")]
2113pub mod sha1_checked;
2114pub mod sha2;
2115pub mod x25519;
2116
2117#[cfg(test)]
2118mod tests {
2119 use super::{Error, Feed};
2120
2121 fn read_error() -> Error {
2122 Error::Read(std::io::Error::other("reader failed"))
2123 }
2124
2125 /// Rejection maps to [`Error::ShortWrite`] only through the
2126 /// success-path requirement; completion satisfies it. (The rest of
2127 /// the precedence is statement order at the join sites: the feed's
2128 /// `?` — always a read failure — before the operation's, before this
2129 /// requirement.)
2130 #[test]
2131 fn rejection_is_short_write_only_by_requirement() {
2132 assert!(matches!(
2133 Feed::Rejected.require_complete(),
2134 Err(Error::ShortWrite)
2135 ));
2136 assert!(Feed::Complete.require_complete().is_ok());
2137 }
2138
2139 /// Every WIT error case maps onto its own variant — and the match in
2140 /// `From` is exhaustive, so a case added to the WIT is a compile error
2141 /// here rather than a silent fallthrough.
2142 #[test]
2143 fn wit_errors_map_onto_their_variants() {
2144 use super::bindings::types::{Error as Raw, ExtensionError};
2145 assert!(matches!(
2146 Error::from(Raw::InvalidKey("k".into())),
2147 Error::InvalidKey(_)
2148 ));
2149 assert!(matches!(
2150 Error::from(Raw::InvalidNonce("n".into())),
2151 Error::InvalidNonce(_)
2152 ));
2153 assert!(matches!(
2154 Error::from(Raw::AuthenticationFailed),
2155 Error::AuthenticationFailed
2156 ));
2157 assert!(matches!(
2158 Error::from(Raw::NotExtractable),
2159 Error::NotExtractable
2160 ));
2161 assert!(matches!(
2162 Error::from(Raw::Unsupported("u".into())),
2163 Error::Unsupported(_)
2164 ));
2165 assert!(matches!(
2166 Error::from(Raw::NotPermitted("p".into())),
2167 Error::NotPermitted(_)
2168 ));
2169 assert!(matches!(
2170 Error::from(Raw::Other("o".into())),
2171 Error::Other(_)
2172 ));
2173 assert!(matches!(
2174 Error::from(Raw::Extension(ExtensionError {
2175 origin: "polymorph:webcrypto".into(),
2176 name: "collision-detected".into(),
2177 message: "m".into(),
2178 })),
2179 Error::Extension(_)
2180 ));
2181 }
2182
2183 /// Every `Display` rendering identifies its condition: the WIT-mirrored
2184 /// variants by case name, the SDK-local ones by prose.
2185 #[test]
2186 fn display_identifies_every_condition() {
2187 let renders = [
2188 (Error::InvalidKey("k".into()), "invalid-key: k"),
2189 (Error::InvalidNonce("n".into()), "invalid-nonce: n"),
2190 (Error::AuthenticationFailed, "authentication-failed"),
2191 (Error::NotExtractable, "not-extractable"),
2192 (Error::Unsupported("u".into()), "unsupported: u"),
2193 (Error::NotPermitted("p".into()), "not-permitted: p"),
2194 (Error::Other("o".into()), "other: o"),
2195 ];
2196 for (error, expected) in renders {
2197 assert_eq!(error.to_string(), expected);
2198 }
2199 assert!(read_error().to_string().contains("reader failed"));
2200 assert!(Error::ShortWrite.to_string().starts_with("short write"));
2201 }
2202
2203 /// The registry (`wit/extension-conditions.json`) is the authoritative
2204 /// spelling of the package's extension-condition pairs: the
2205 /// [`crate::extension`] constants consumers match against must cover it
2206 /// exactly, in both directions.
2207 #[test]
2208 fn extension_constants_match_the_registry() {
2209 let registry: serde_json::Value = serde_json::from_str(include_str!(concat!(
2210 env!("CARGO_MANIFEST_DIR"),
2211 "/../../wit/extension-conditions.json"
2212 )))
2213 .expect("wit/extension-conditions.json parses");
2214 let registered: std::collections::BTreeSet<(&str, &str)> = registry["conditions"]
2215 .as_array()
2216 .expect("registry has a conditions array")
2217 .iter()
2218 .map(|condition| {
2219 (
2220 condition["origin"].as_str().expect("condition origin"),
2221 condition["name"].as_str().expect("condition name"),
2222 )
2223 })
2224 .collect();
2225 let constants = std::collections::BTreeSet::from([
2226 (
2227 crate::extension::ORIGIN,
2228 crate::extension::COLLISION_DETECTED,
2229 ),
2230 (crate::extension::ORIGIN, crate::extension::MESSAGE_TOO_LONG),
2231 ]);
2232 assert_eq!(constants, registered);
2233 }
2234}