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