blob: c136b884c6ffafd8e27865e471e0538cb01cb46b [file]
// Licensed under the Apache-2.0 license
//! Crypto Client Library
//!
//! Provides an ergonomic API for applications to call the crypto server over IPC.
//! The server implements cryptographic operations using RustCrypto crates.
//!
//! # Supported Algorithms
//!
//! | Category | Algorithm | Output |
//! |----------|-----------|--------|
//! | Hash | SHA-256, SHA-384, SHA-512 | 32/48/64 bytes |
//! | MAC | HMAC-SHA256, HMAC-SHA384, HMAC-SHA512 | 32/48/64 bytes |
//! | AEAD | AES-256-GCM | 16-byte tag |
//! | Signature | ECDSA P-256, P-384 (feature-gated) | 64/96 bytes |
//!
//! # Quick Start
//!
//! ```ignore
//! use crypto_client::CryptoClient;
//!
//! let crypto = CryptoClient::new(handle::CRYPTO);
//!
//! // Hash — returns fixed-size array directly
//! let hash: [u8; 32] = crypto.sha256(b"hello world")?;
//!
//! // HMAC — returns authentication tag
//! let tag: [u8; 32] = crypto.hmac_sha256(key, data)?;
//!
//! // AEAD — seal encrypts + authenticates, open decrypts + verifies
//! let ct_len = crypto.aes256_gcm_seal(&key, &nonce, plaintext, &mut ct)?;
//! let pt_len = crypto.aes256_gcm_open(&key, &nonce, &ct[..ct_len], &mut pt)?;
//!
//! // ECDSA (requires "ecdsa" feature)
//! let sig: [u8; 64] = crypto.ecdsa_p256_sign(&private_key, message)?;
//! crypto.ecdsa_p256_verify(&public_key, message, &sig)?; // Ok(()) = valid
//! ```
//!
//! # Error Handling
//!
//! All operations return `Result<T, ClientError>`. The [`ClientError`] type
//! distinguishes IPC failures from cryptographic errors and implements
//! [`Display`](core::fmt::Display) for logging.
//!
//! # Free Functions
//!
//! For compatibility, free functions are also exported (e.g., `sha256(handle, data)`).
//! Prefer [`CryptoClient`] for new code.
#![no_std]
use crypto_api::{
CryptoError, CryptoOp, CryptoRequestHeader, CryptoResponseHeader,
MAX_PAYLOAD_SIZE,
};
use userspace::syscall;
use userspace::time::Instant;
/// Maximum buffer size for requests/responses
const MAX_BUF_SIZE: usize = 1024;
/// Error type for crypto client operations.
///
/// Distinguishes between IPC-level failures and cryptographic errors,
/// enabling appropriate error handling strategies.
///
/// # Variants
///
/// - [`IpcError`](Self::IpcError) — Channel communication failed (retry may help)
/// - [`ServerError`](Self::ServerError) — Cryptographic operation failed (check inputs)
/// - [`InvalidResponse`](Self::InvalidResponse) — Protocol mismatch (likely a bug)
/// - [`BufferTooSmall`](Self::BufferTooSmall) — Output buffer insufficient
/// - [`VerificationFailed`](Self::VerificationFailed) — Signature or MAC invalid
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClientError {
/// IPC syscall failed (channel closed, timeout, etc.)
IpcError(pw_status::Error),
/// Server returned an error (invalid key, bad nonce, etc.)
ServerError(CryptoError),
/// Response was malformed (internal error)
InvalidResponse,
/// Buffer too small for the requested operation
BufferTooSmall,
/// Signature or authentication tag verification failed
VerificationFailed,
}
impl From<pw_status::Error> for ClientError {
fn from(e: pw_status::Error) -> Self {
ClientError::IpcError(e)
}
}
impl core::fmt::Display for ClientError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::IpcError(e) => write!(f, "IPC: {:?}", e),
Self::ServerError(e) => write!(f, "server: {:?}", e),
Self::InvalidResponse => write!(f, "malformed response"),
Self::BufferTooSmall => write!(f, "buffer too small"),
Self::VerificationFailed => write!(f, "verification failed"),
}
}
}
// ---------------------------------------------------------------------------
// CryptoClient — typed handle to the crypto server
// ---------------------------------------------------------------------------
/// Typed handle to the crypto server IPC channel.
///
/// `CryptoClient` is the primary interface for cryptographic operations.
/// It wraps an IPC channel handle and provides ergonomic methods for
/// hashing, HMAC, AEAD, and digital signatures.
///
/// # Construction
///
/// Create a client using the channel handle from your application's generated
/// handle module:
///
/// ```ignore
/// use crypto_client::CryptoClient;
/// use app_my_app::handle;
///
/// let crypto = CryptoClient::new(handle::CRYPTO);
/// ```
///
/// # Thread Safety
///
/// `CryptoClient` is `Send` and `Sync`. The underlying IPC channel handles
/// concurrent requests correctly, but operations are serialized by the server.
///
/// # Performance
///
/// Zero-cost abstraction — the struct is a single `u32` that the compiler
/// inlines away. Each method performs one blocking IPC round-trip.
pub struct CryptoClient {
handle: u32,
}
impl CryptoClient {
/// Bind to the crypto server channel.
///
/// `handle` is the IPC channel handle from the app's generated handle module
/// (e.g., `handle::CRYPTO`).
pub const fn new(handle: u32) -> Self {
Self { handle }
}
// -- Digest operations --------------------------------------------------
/// Compute SHA-256 hash of the input data.
///
/// Returns the 32-byte digest directly as a fixed-size array.
///
/// # Example
///
/// ```ignore
/// let hash = crypto.sha256(b"hello world")?;
/// assert_eq!(hash.len(), 32);
/// ```
///
/// # Errors
///
/// Returns [`ClientError::BufferTooSmall`] if `data` exceeds the maximum
/// payload size (~900 bytes).
pub fn sha256(&self, data: &[u8]) -> Result<[u8; 32], ClientError> {
self.hash_op(CryptoOp::Sha256Hash, data)
}
/// Compute SHA-384 hash of the input data.
///
/// Returns the 48-byte digest directly as a fixed-size array.
pub fn sha384(&self, data: &[u8]) -> Result<[u8; 48], ClientError> {
self.hash_op(CryptoOp::Sha384Hash, data)
}
/// Compute SHA-512 hash of the input data.
///
/// Returns the 64-byte digest directly as a fixed-size array.
pub fn sha512(&self, data: &[u8]) -> Result<[u8; 64], ClientError> {
self.hash_op(CryptoOp::Sha512Hash, data)
}
// -- Streaming digest operations ----------------------------------------
/// Begin a streaming SHA-256 hash computation.
///
/// Use this when the data to hash is too large for a single IPC buffer
/// (>~900 bytes) or arrives in chunks. Returns a session that accumulates
/// data via [`Sha256Session::update`] and produces the final hash via
/// [`Sha256Session::finalize`].
///
/// # Example
///
/// ```ignore
/// let mut session = crypto.sha256_begin()?;
/// session.update(chunk1)?;
/// session.update(chunk2)?;
/// let hash: [u8; 32] = session.finalize()?;
/// ```
///
/// # Session Semantics
///
/// Only one streaming session can be active at a time per server instance.
/// Starting a new session while one is active returns `SessionBusy`.
pub fn sha256_begin(&self) -> Result<Sha256Session, ClientError> {
self.begin_hash_session(CryptoOp::Sha256Begin, Sha256Session { handle: self.handle })
}
/// Begin a streaming SHA-384 hash computation.
pub fn sha384_begin(&self) -> Result<Sha384Session, ClientError> {
self.begin_hash_session(CryptoOp::Sha384Begin, Sha384Session { handle: self.handle })
}
/// Begin a streaming SHA-512 hash computation.
pub fn sha512_begin(&self) -> Result<Sha512Session, ClientError> {
self.begin_hash_session(CryptoOp::Sha512Begin, Sha512Session { handle: self.handle })
}
fn begin_hash_session<S>(&self, op: CryptoOp, session: S) -> Result<S, ClientError> {
let mut request = [0u8; MAX_BUF_SIZE];
let mut response = [0u8; MAX_BUF_SIZE];
let header = CryptoRequestHeader::new(op, 0, 0, 0);
let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
request[..CryptoRequestHeader::SIZE].copy_from_slice(header_bytes);
let response_len = syscall::channel_transact(
self.handle,
&request[..CryptoRequestHeader::SIZE],
&mut response,
Instant::MAX,
)?;
// Check for success (empty body)
if response_len < CryptoResponseHeader::SIZE {
return Err(ClientError::InvalidResponse);
}
let header_bytes = &response[..CryptoResponseHeader::SIZE];
let Some(resp_header) = zerocopy::Ref::<_, CryptoResponseHeader>::from_bytes(header_bytes).ok() else {
return Err(ClientError::InvalidResponse);
};
if !resp_header.is_success() {
return Err(ClientError::ServerError(resp_header.error_code()));
}
Ok(session)
}
// -- HMAC operations ----------------------------------------------------
/// Compute HMAC-SHA256 authentication tag.
///
/// Returns the 32-byte tag directly. HMAC provides both integrity and
/// authenticity — use the same key to verify.
///
/// # Arguments
///
/// * `key` — Secret key (any length, but ≥32 bytes recommended)
/// * `data` — Message to authenticate
///
/// # Example
///
/// ```ignore
/// let key = b"my-secret-key-32-bytes-long!!!!!";
/// let tag = crypto.hmac_sha256(key, b"message")?;
///
/// // Verify by recomputing
/// let tag2 = crypto.hmac_sha256(key, b"message")?;
/// assert_eq!(tag, tag2);
/// ```
pub fn hmac_sha256(&self, key: &[u8], data: &[u8]) -> Result<[u8; 32], ClientError> {
self.hmac_op(CryptoOp::HmacSha256, key, data)
}
/// Compute HMAC-SHA384 authentication tag. Returns the 48-byte tag.
pub fn hmac_sha384(&self, key: &[u8], data: &[u8]) -> Result<[u8; 48], ClientError> {
self.hmac_op(CryptoOp::HmacSha384, key, data)
}
/// Compute HMAC-SHA512. Returns the 64-byte tag.
pub fn hmac_sha512(&self, key: &[u8], data: &[u8]) -> Result<[u8; 64], ClientError> {
self.hmac_op(CryptoOp::HmacSha512, key, data)
}
// -- AEAD operations (seal/open) ----------------------------------------
/// AES-256-GCM authenticated encryption (seal).
///
/// Encrypts `plaintext` and appends a 16-byte authentication tag.
/// The combination provides both confidentiality and integrity.
///
/// # Arguments
///
/// * `key` — 256-bit (32-byte) AES key
/// * `nonce` — 96-bit (12-byte) nonce; **must be unique per key**
/// * `plaintext` — Data to encrypt
/// * `out` — Output buffer; must be at least `plaintext.len() + 16` bytes
///
/// # Returns
///
/// Number of bytes written to `out` (ciphertext length + 16-byte tag).
///
/// # Example
///
/// ```ignore
/// let key = [0u8; 32]; // Use a real key!
/// let nonce = [0u8; 12]; // Must be unique per encryption
/// let plaintext = b"secret message";
///
/// let mut ciphertext = [0u8; 128];
/// let ct_len = crypto.aes256_gcm_seal(&key, &nonce, plaintext, &mut ciphertext)?;
/// // ciphertext[..ct_len] contains encrypted data + tag
/// ```
///
/// # Security
///
/// Never reuse a nonce with the same key. Nonce reuse completely breaks
/// AES-GCM security, allowing tag forgery and plaintext recovery.
pub fn aes256_gcm_seal(
&self,
key: &[u8; 32],
nonce: &[u8; 12],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, ClientError> {
self.cipher_op(CryptoOp::Aes256GcmEncrypt, key, nonce, plaintext, out)
}
/// AES-256-GCM authenticated decryption (open).
///
/// Decrypts and verifies ciphertext produced by [`aes256_gcm_seal`](Self::aes256_gcm_seal).
/// The ciphertext must include the 16-byte authentication tag.
///
/// # Arguments
///
/// * `key` — Same 256-bit key used for sealing
/// * `nonce` — Same 96-bit nonce used for sealing
/// * `ciphertext` — Data from `seal` (encrypted data + tag)
/// * `out` — Output buffer; must be at least `ciphertext.len() - 16` bytes
///
/// # Returns
///
/// Number of bytes written to `out` (plaintext length).
///
/// # Errors
///
/// Returns [`ClientError::ServerError`] with `AuthenticationFailed` if
/// the tag is invalid (tampered data, wrong key, or wrong nonce).
///
/// # Example
///
/// ```ignore
/// let mut plaintext = [0u8; 128];
/// let pt_len = crypto.aes256_gcm_open(&key, &nonce, &ciphertext[..ct_len], &mut plaintext)?;
/// assert_eq!(&plaintext[..pt_len], b"secret message");
/// ```
pub fn aes256_gcm_open(
&self,
key: &[u8; 32],
nonce: &[u8; 12],
ciphertext: &[u8],
out: &mut [u8],
) -> Result<usize, ClientError> {
self.cipher_op(CryptoOp::Aes256GcmDecrypt, key, nonce, ciphertext, out)
}
// -- RNG operations -----------------------------------------------------
/// Generate cryptographically secure random bytes.
///
/// Fills the provided buffer with random data from a CSPRNG
/// (ChaCha20) seeded with system entropy.
///
/// # Arguments
///
/// * `buf` — Output buffer to fill with random bytes
///
/// # Example
///
/// ```ignore
/// let mut nonce = [0u8; 32];
/// crypto.get_random_bytes(&mut nonce)?;
/// ```
///
/// # Errors
///
/// Returns [`ClientError::BufferTooSmall`] if buffer exceeds max payload size.
pub fn get_random_bytes(&self, buf: &mut [u8]) -> Result<(), ClientError> {
let len = buf.len();
if len == 0 {
return Ok(());
}
if len > MAX_PAYLOAD_SIZE {
return Err(ClientError::BufferTooSmall);
}
let mut request = [0u8; MAX_BUF_SIZE];
let mut response = [0u8; MAX_BUF_SIZE];
// Encode length in data_len field, no payload needed
let header = CryptoRequestHeader::new(CryptoOp::GetRandomBytes, 0, 0, len as u16);
let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
request[..CryptoRequestHeader::SIZE].copy_from_slice(header_bytes);
let response_len = syscall::channel_transact(
self.handle,
&request[..CryptoRequestHeader::SIZE],
&mut response,
Instant::MAX,
)?;
// Parse variable-length response
parse_variable_response(&response[..response_len], buf)?;
Ok(())
}
// -- ECDSA operations ---------------------------------------------------
/// Sign a message with ECDSA P-256 (secp256r1).
///
/// Returns the 64-byte signature (r || s, each 32 bytes).
/// Uses RFC 6979 deterministic signatures with SHA-256 internally.
///
/// # Arguments
///
/// * `private_key` — 32-byte scalar (SEC1 format)
/// * `message` — Message to sign (hashed internally)
///
/// # Example
///
/// ```ignore
/// let sig = crypto.ecdsa_p256_sign(&private_key, b"message to sign")?;
/// assert_eq!(sig.len(), 64);
/// ```
#[cfg(feature = "ecdsa")]
pub fn ecdsa_p256_sign(
&self,
private_key: &[u8; 32],
message: &[u8],
) -> Result<[u8; 64], ClientError> {
self.sign_op(CryptoOp::EcdsaP256Sign, private_key, message)
}
/// Verify an ECDSA P-256 signature.
///
/// # Arguments
///
/// * `public_key` — Uncompressed SEC1 point (65 bytes, starting with 0x04)
/// * `message` — Original message (hashed internally)
/// * `signature` — 64-byte signature from [`ecdsa_p256_sign`](Self::ecdsa_p256_sign)
///
/// # Returns
///
/// - `Ok(())` — Signature is valid
/// - `Err(VerificationFailed)` — Signature is invalid
///
/// # Example
///
/// ```ignore
/// // Verify — Ok(()) means valid
/// crypto.ecdsa_p256_verify(&public_key, message, &signature)?;
///
/// // Check for invalid signature
/// if crypto.ecdsa_p256_verify(&public_key, wrong_message, &signature).is_err() {
/// // Signature invalid for this message
/// }
/// ```
#[cfg(feature = "ecdsa")]
pub fn ecdsa_p256_verify(
&self,
public_key: &[u8],
message: &[u8],
signature: &[u8; 64],
) -> Result<(), ClientError> {
self.verify_op(CryptoOp::EcdsaP256Verify, public_key, message, signature)
}
/// Sign a message with ECDSA P-384. Returns the 96-byte signature.
#[cfg(feature = "ecdsa")]
pub fn ecdsa_p384_sign(
&self,
private_key: &[u8; 48],
message: &[u8],
) -> Result<[u8; 96], ClientError> {
self.sign_op(CryptoOp::EcdsaP384Sign, private_key, message)
}
/// Verify an ECDSA P-384 signature.
///
/// Returns `Ok(())` if the signature is valid.
/// Returns `Err(VerificationFailed)` if the signature is invalid.
#[cfg(feature = "ecdsa")]
pub fn ecdsa_p384_verify(
&self,
public_key: &[u8],
message: &[u8],
signature: &[u8; 96],
) -> Result<(), ClientError> {
self.verify_op(CryptoOp::EcdsaP384Verify, public_key, message, signature)
}
// -- Internal implementation --------------------------------------------
fn hash_op<const N: usize>(
&self,
op: CryptoOp,
data: &[u8],
) -> Result<[u8; N], ClientError> {
if data.len() > MAX_PAYLOAD_SIZE {
return Err(ClientError::BufferTooSmall);
}
let mut request = [0u8; MAX_BUF_SIZE];
let mut response = [0u8; MAX_BUF_SIZE];
let header = CryptoRequestHeader::new(op, 0, 0, data.len() as u16);
let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
request[..CryptoRequestHeader::SIZE].copy_from_slice(header_bytes);
request[CryptoRequestHeader::SIZE..CryptoRequestHeader::SIZE + data.len()]
.copy_from_slice(data);
let request_len = CryptoRequestHeader::SIZE + data.len();
let response_len = syscall::channel_transact(
self.handle,
&request[..request_len],
&mut response,
Instant::MAX,
)?;
parse_fixed_response(&response[..response_len])
}
fn hmac_op<const N: usize>(
&self,
op: CryptoOp,
key: &[u8],
data: &[u8],
) -> Result<[u8; N], ClientError> {
if key.len() + data.len() > MAX_PAYLOAD_SIZE {
return Err(ClientError::BufferTooSmall);
}
let mut request = [0u8; MAX_BUF_SIZE];
let mut response = [0u8; MAX_BUF_SIZE];
let header = CryptoRequestHeader::new(op, key.len() as u16, 0, data.len() as u16);
let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
request[..CryptoRequestHeader::SIZE].copy_from_slice(header_bytes);
let mut offset = CryptoRequestHeader::SIZE;
request[offset..offset + key.len()].copy_from_slice(key);
offset += key.len();
request[offset..offset + data.len()].copy_from_slice(data);
offset += data.len();
let response_len = syscall::channel_transact(
self.handle,
&request[..offset],
&mut response,
Instant::MAX,
)?;
parse_fixed_response(&response[..response_len])
}
fn cipher_op(
&self,
op: CryptoOp,
key: &[u8; 32],
nonce: &[u8; 12],
input: &[u8],
output: &mut [u8],
) -> Result<usize, ClientError> {
if 32 + 12 + input.len() > MAX_PAYLOAD_SIZE {
return Err(ClientError::BufferTooSmall);
}
let mut request = [0u8; MAX_BUF_SIZE];
let mut response = [0u8; MAX_BUF_SIZE];
let header = CryptoRequestHeader::new(op, 32, 12, input.len() as u16);
let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
request[..CryptoRequestHeader::SIZE].copy_from_slice(header_bytes);
let mut offset = CryptoRequestHeader::SIZE;
request[offset..offset + 32].copy_from_slice(key);
offset += 32;
request[offset..offset + 12].copy_from_slice(nonce);
offset += 12;
request[offset..offset + input.len()].copy_from_slice(input);
offset += input.len();
let response_len = syscall::channel_transact(
self.handle,
&request[..offset],
&mut response,
Instant::MAX,
)?;
parse_variable_response(&response[..response_len], output)
}
#[cfg(feature = "ecdsa")]
fn sign_op<const N: usize>(
&self,
op: CryptoOp,
private_key: &[u8],
message: &[u8],
) -> Result<[u8; N], ClientError> {
let key_len = private_key.len();
if message.len() + key_len > MAX_PAYLOAD_SIZE {
return Err(ClientError::BufferTooSmall);
}
let mut request = [0u8; MAX_BUF_SIZE];
let mut response = [0u8; MAX_BUF_SIZE];
let header = CryptoRequestHeader::new(
op,
key_len as u16,
0,
message.len() as u16,
);
let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
request[..CryptoRequestHeader::SIZE].copy_from_slice(header_bytes);
let mut offset = CryptoRequestHeader::SIZE;
request[offset..offset + key_len].copy_from_slice(private_key);
offset += key_len;
request[offset..offset + message.len()].copy_from_slice(message);
offset += message.len();
let response_len = syscall::channel_transact(
self.handle,
&request[..offset],
&mut response,
Instant::MAX,
)?;
parse_fixed_response(&response[..response_len])
}
#[cfg(feature = "ecdsa")]
fn verify_op(
&self,
op: CryptoOp,
public_key: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), ClientError> {
let sig_len = signature.len();
if message.len() + public_key.len() + sig_len > MAX_PAYLOAD_SIZE {
return Err(ClientError::BufferTooSmall);
}
let mut request = [0u8; MAX_BUF_SIZE];
let mut response = [0u8; MAX_BUF_SIZE];
// key=pubkey, nonce=signature, data=message
let header = CryptoRequestHeader::new(
op,
public_key.len() as u16,
sig_len as u16,
message.len() as u16,
);
let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
request[..CryptoRequestHeader::SIZE].copy_from_slice(header_bytes);
let mut offset = CryptoRequestHeader::SIZE;
request[offset..offset + public_key.len()].copy_from_slice(public_key);
offset += public_key.len();
request[offset..offset + sig_len].copy_from_slice(signature);
offset += sig_len;
request[offset..offset + message.len()].copy_from_slice(message);
offset += message.len();
let response_len = syscall::channel_transact(
self.handle,
&request[..offset],
&mut response,
Instant::MAX,
)?;
// Server returns success with empty body for valid, or error for invalid
parse_verify_response(&response[..response_len])
}
}
// ---------------------------------------------------------------------------
// Response parsing helpers
// ---------------------------------------------------------------------------
/// Parse a response with a fixed-size result, returning it by value.
fn parse_fixed_response<const N: usize>(
response: &[u8],
) -> Result<[u8; N], ClientError> {
if response.len() < CryptoResponseHeader::SIZE {
return Err(ClientError::InvalidResponse);
}
let header_bytes = &response[..CryptoResponseHeader::SIZE];
let Some(header) = zerocopy::Ref::<_, CryptoResponseHeader>::from_bytes(header_bytes).ok() else {
return Err(ClientError::InvalidResponse);
};
let header: &CryptoResponseHeader = &*header;
if !header.is_success() {
return Err(ClientError::ServerError(header.error_code()));
}
let result_len = header.result_length();
if result_len != N {
return Err(ClientError::InvalidResponse);
}
let mut output = [0u8; N];
output.copy_from_slice(
&response[CryptoResponseHeader::SIZE..CryptoResponseHeader::SIZE + N],
);
Ok(output)
}
/// Parse a response with variable-size result into a caller-provided buffer.
fn parse_variable_response(
response: &[u8],
output: &mut [u8],
) -> Result<usize, ClientError> {
if response.len() < CryptoResponseHeader::SIZE {
return Err(ClientError::InvalidResponse);
}
let header_bytes = &response[..CryptoResponseHeader::SIZE];
let Some(header) = zerocopy::Ref::<_, CryptoResponseHeader>::from_bytes(header_bytes).ok() else {
return Err(ClientError::InvalidResponse);
};
let header: &CryptoResponseHeader = &*header;
if !header.is_success() {
return Err(ClientError::ServerError(header.error_code()));
}
let result_len = header.result_length();
if result_len > output.len() {
return Err(ClientError::BufferTooSmall);
}
output[..result_len].copy_from_slice(
&response[CryptoResponseHeader::SIZE..CryptoResponseHeader::SIZE + result_len],
);
Ok(result_len)
}
/// Parse a verify response: success with empty body = valid, error = invalid/failed.
#[cfg(feature = "ecdsa")]
fn parse_verify_response(response: &[u8]) -> Result<(), ClientError> {
if response.len() < CryptoResponseHeader::SIZE {
return Err(ClientError::InvalidResponse);
}
let header_bytes = &response[..CryptoResponseHeader::SIZE];
let Some(header) = zerocopy::Ref::<_, CryptoResponseHeader>::from_bytes(header_bytes).ok() else {
return Err(ClientError::InvalidResponse);
};
let header: &CryptoResponseHeader = &*header;
if !header.is_success() {
let err = header.error_code();
if err == CryptoError::VerificationFailed {
return Err(ClientError::VerificationFailed);
}
return Err(ClientError::ServerError(err));
}
Ok(())
}
// ---------------------------------------------------------------------------
// Streaming hash session types
// ---------------------------------------------------------------------------
/// Active SHA-256 streaming session.
///
/// Created by [`CryptoClient::sha256_begin`]. Call [`update`](Self::update)
/// to feed data, then [`finalize`](Self::finalize) to get the final hash.
pub struct Sha256Session {
handle: u32,
}
impl Sha256Session {
/// Feed data into the hash computation.
///
/// Can be called multiple times. Each chunk is accumulated server-side.
pub fn update(&mut self, data: &[u8]) -> Result<(), ClientError> {
streaming_update(self.handle, CryptoOp::Sha256Update, data)
}
/// Finalize the hash and return the 32-byte digest.
///
/// Consumes the session. The server clears its internal state.
pub fn finalize(self) -> Result<[u8; 32], ClientError> {
streaming_finish(self.handle, CryptoOp::Sha256Finish)
}
}
/// Active SHA-384 streaming session.
pub struct Sha384Session {
handle: u32,
}
impl Sha384Session {
/// Feed data into the hash computation.
pub fn update(&mut self, data: &[u8]) -> Result<(), ClientError> {
streaming_update(self.handle, CryptoOp::Sha384Update, data)
}
/// Finalize the hash and return the 48-byte digest.
pub fn finalize(self) -> Result<[u8; 48], ClientError> {
streaming_finish(self.handle, CryptoOp::Sha384Finish)
}
}
/// Active SHA-512 streaming session.
pub struct Sha512Session {
handle: u32,
}
impl Sha512Session {
/// Feed data into the hash computation.
pub fn update(&mut self, data: &[u8]) -> Result<(), ClientError> {
streaming_update(self.handle, CryptoOp::Sha512Update, data)
}
/// Finalize the hash and return the 64-byte digest.
pub fn finalize(self) -> Result<[u8; 64], ClientError> {
streaming_finish(self.handle, CryptoOp::Sha512Finish)
}
}
/// Internal: send data to an active streaming session.
fn streaming_update(handle: u32, op: CryptoOp, data: &[u8]) -> Result<(), ClientError> {
if data.len() > MAX_PAYLOAD_SIZE {
return Err(ClientError::BufferTooSmall);
}
let mut request = [0u8; MAX_BUF_SIZE];
let mut response = [0u8; MAX_BUF_SIZE];
let header = CryptoRequestHeader::new(op, 0, 0, data.len() as u16);
let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
request[..CryptoRequestHeader::SIZE].copy_from_slice(header_bytes);
request[CryptoRequestHeader::SIZE..CryptoRequestHeader::SIZE + data.len()]
.copy_from_slice(data);
let request_len = CryptoRequestHeader::SIZE + data.len();
let response_len = syscall::channel_transact(
handle,
&request[..request_len],
&mut response,
Instant::MAX,
)?;
// Check for success
if response_len < CryptoResponseHeader::SIZE {
return Err(ClientError::InvalidResponse);
}
let header_bytes = &response[..CryptoResponseHeader::SIZE];
let Some(resp_header) = zerocopy::Ref::<_, CryptoResponseHeader>::from_bytes(header_bytes).ok() else {
return Err(ClientError::InvalidResponse);
};
if !resp_header.is_success() {
return Err(ClientError::ServerError(resp_header.error_code()));
}
Ok(())
}
/// Internal: finalize a streaming session and return the hash.
fn streaming_finish<const N: usize>(handle: u32, op: CryptoOp) -> Result<[u8; N], ClientError> {
let mut request = [0u8; MAX_BUF_SIZE];
let mut response = [0u8; MAX_BUF_SIZE];
let header = CryptoRequestHeader::new(op, 0, 0, 0);
let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
request[..CryptoRequestHeader::SIZE].copy_from_slice(header_bytes);
let response_len = syscall::channel_transact(
handle,
&request[..CryptoRequestHeader::SIZE],
&mut response,
Instant::MAX,
)?;
parse_fixed_response(&response[..response_len])
}
// ---------------------------------------------------------------------------
// Free function wrappers (backward compatibility)
// ---------------------------------------------------------------------------
/// Convenience: compute SHA-256 without constructing a client.
pub fn sha256(handle: u32, data: &[u8]) -> Result<[u8; 32], ClientError> {
CryptoClient::new(handle).sha256(data)
}
/// Convenience: compute SHA-384 without constructing a client.
pub fn sha384(handle: u32, data: &[u8]) -> Result<[u8; 48], ClientError> {
CryptoClient::new(handle).sha384(data)
}
/// Convenience: compute SHA-512 without constructing a client.
pub fn sha512(handle: u32, data: &[u8]) -> Result<[u8; 64], ClientError> {
CryptoClient::new(handle).sha512(data)
}
/// Convenience: compute HMAC-SHA256 without constructing a client.
pub fn hmac_sha256(handle: u32, key: &[u8], data: &[u8]) -> Result<[u8; 32], ClientError> {
CryptoClient::new(handle).hmac_sha256(key, data)
}
/// Convenience: compute HMAC-SHA384 without constructing a client.
pub fn hmac_sha384(handle: u32, key: &[u8], data: &[u8]) -> Result<[u8; 48], ClientError> {
CryptoClient::new(handle).hmac_sha384(key, data)
}
/// Convenience: compute HMAC-SHA512 without constructing a client.
pub fn hmac_sha512(handle: u32, key: &[u8], data: &[u8]) -> Result<[u8; 64], ClientError> {
CryptoClient::new(handle).hmac_sha512(key, data)
}
/// Convenience: AES-256-GCM seal without constructing a client.
pub fn aes256_gcm_seal(
handle: u32,
key: &[u8; 32],
nonce: &[u8; 12],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, ClientError> {
CryptoClient::new(handle).aes256_gcm_seal(key, nonce, plaintext, out)
}
/// Convenience: AES-256-GCM open without constructing a client.
pub fn aes256_gcm_open(
handle: u32,
key: &[u8; 32],
nonce: &[u8; 12],
ciphertext: &[u8],
out: &mut [u8],
) -> Result<usize, ClientError> {
CryptoClient::new(handle).aes256_gcm_open(key, nonce, ciphertext, out)
}
/// Convenience: get random bytes without constructing a client.
pub fn get_random_bytes(handle: u32, buf: &mut [u8]) -> Result<(), ClientError> {
CryptoClient::new(handle).get_random_bytes(buf)
}
/// Convenience: ECDSA P-256 sign without constructing a client.
#[cfg(feature = "ecdsa")]
pub fn ecdsa_p256_sign(
handle: u32,
private_key: &[u8; 32],
message: &[u8],
) -> Result<[u8; 64], ClientError> {
CryptoClient::new(handle).ecdsa_p256_sign(private_key, message)
}
/// Convenience: ECDSA P-256 verify without constructing a client.
#[cfg(feature = "ecdsa")]
pub fn ecdsa_p256_verify(
handle: u32,
public_key: &[u8],
message: &[u8],
signature: &[u8; 64],
) -> Result<(), ClientError> {
CryptoClient::new(handle).ecdsa_p256_verify(public_key, message, signature)
}
/// Convenience: ECDSA P-384 sign without constructing a client.
#[cfg(feature = "ecdsa")]
pub fn ecdsa_p384_sign(
handle: u32,
private_key: &[u8; 48],
message: &[u8],
) -> Result<[u8; 96], ClientError> {
CryptoClient::new(handle).ecdsa_p384_sign(private_key, message)
}
/// Convenience: ECDSA P-384 verify without constructing a client.
#[cfg(feature = "ecdsa")]
pub fn ecdsa_p384_verify(
handle: u32,
public_key: &[u8],
message: &[u8],
signature: &[u8; 96],
) -> Result<(), ClientError> {
CryptoClient::new(handle).ecdsa_p384_verify(public_key, message, signature)
}