Crypto Service Refactor — Implementation Plan

Date: February 14, 2026
Status: Approved
Reference: crypto-service-design-review.md


Overview

This plan implements the proposed architecture from the design review:

  • Algorithm marker types with compile-time dispatch
  • OneShot<A> trait for backend abstraction
  • Generic CryptoServer<B> that works with any backend
  • Streaming sessions via Streaming<A> trait

Design Decision: Why Not Reuse RustCryptoController?

The existing platform/impls/rustcrypto/src/controller.rs (640 lines) implements the openprot-hal-blocking traits. We considered reusing it but chose not to for long-term maintainability:

Current Architecture (God Trait)

// hal/blocking/src/digest.rs
pub trait DigestInit<A> { 
    type Context: DigestOp; 
    fn init(self, algo: A) -> Result<Self::Context, Self::Error>;
}

// platform/impls/rustcrypto
impl DigestInit<Sha2_256> for RustCryptoController { ... }
impl DigestInit<Sha2_384> for RustCryptoController { ... }
impl DigestInit<Sha2_512> for RustCryptoController { ... }
impl DigestOp for DigestContext256 { ... }
impl DigestOp for DigestContext384 { ... }
impl DigestOp for DigestContext512 { ... }
// 6 impls just for digest, 6 more for MAC = 12+ total

Problem: Adding SHA3-384 requires:

  1. Modify DigestInit trait (or add new marker type)
  2. Add DigestContext3_384 in RustCryptoController
  3. Add impl for HACE backend
  4. Add impl for any test mock

Proposed Architecture (Composable Traits)

// crypto-traits/src/lib.rs
pub trait OneShot<A: Algorithm> {
    fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError>;
}

// backend-rustcrypto/src/lib.rs
impl OneShot<Sha256> for RustCryptoBackend { ... }
impl OneShot<Sha384> for RustCryptoBackend { ... }
impl OneShot<Sha3_384> for RustCryptoBackend { ... }  // ← Adding algorithm = adding impl

// backend-hace/src/lib.rs (HACE only supports SHA-2)
impl OneShot<Sha256> for HaceBackend { ... }
impl OneShot<Sha384> for HaceBackend { ... }
// No Sha3_384 impl needed if HACE doesn't support it

Benefit: Adding SHA3-384 requires:

  1. Add pub struct Sha3_384; impl Algorithm for Sha3_384 { ... } to traits
  2. Add impl OneShot<Sha3_384> for RustCryptoBackend { ... } to backend
  3. Done — no other files touched

Impact on Algorithm Roadmap

The SPDM spec requires 10-15 additional algorithms. Each algorithm addition:

ApproachFiles ChangedRisk
Current God trait3-4 per algorithmHigh (touches shared traits)
Composable traits1-2 per algorithmLow (additive only)

Decision: Take the upfront refactor cost now for clean extensibility later. The existing RustCryptoController code will be referenced for implementation details but not directly reused.


Architectural Insight: Trait Layering (HAL vs Service)

During implementation review, we identified an important layering distinction:

┌─────────────────────────────────────────────────────────────────┐
│                        Crypto Client                            │
│  (services/crypto/client)                                       │
└───────────────────────────┬─────────────────────────────────────┘
                            │ IPC (channel_call)
┌───────────────────────────▼─────────────────────────────────────┐
│                        Crypto Server                            │
│  (services/crypto/server)                                       │
│                                                                 │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │              Service Layer Traits                         │  │
│  │  OneShot<A>    — one-shot operations via &self            │  │
│  │  Streaming<A>  — session-based via session handle         │  │
│  │  (services/crypto/api/src/backend.rs)                     │  │
│  └───────────────────────────────────────────────────────────┘  │
│                            │                                    │
│                            │ impl                               │
│                            ▼                                    │
│  ┌──────────────────┐  ┌──────────────────┐                     │
│  │ RustCryptoBackend│  │   HaceBackend    │ ... (future)        │
│  │ (software)       │  │ (hardware accel) │                     │
│  └────────┬─────────┘  └────────┬─────────┘                     │
│           │                     │                               │
└───────────┼─────────────────────┼───────────────────────────────┘
            │                     │
            │ (may use)           │ (uses)
            ▼                     ▼
┌───────────────────────────────────────────────────────────────────┐
│                        HAL Layer Traits                           │
│  owned::DigestInit  — typestate pattern (moves self)              │
│  owned::DigestOp    — resource recovery via fn cancel(self)       │
│  (hal/blocking/src/digest.rs)                                     │
└───────────────────────────────────────────────────────────────────┘
            │
            │ impl
            ▼
┌───────────────────────────────────────────────────────────────────┐
│  RustCryptoController │ HaceController │ ... (platform impls)     │
│  (platform/impls/)    │                │                          │
└───────────────────────────────────────────────────────────────────┘

Key Distinction

LayerLocationPurposePattern
Serviceservices/crypto/api/src/backend.rsAbstract IPC protocol&self + session handle
HALhal/blocking/src/digest.rsAbstract hardware controllersTypestate (moves self)

Why Two Layers?

  1. Service Layer: Designed for IPC semantics where state lives across multiple requests. The server owns a StreamingSession and clients reference it via session ID.

  2. HAL Layer: Designed for baremetal use where resource recovery is critical. The typestate pattern ensures hardware controllers are properly released on success, failure, or cancellation.

Relationship

  • The HAL layer is used by platform implementations (both baremetal and server backends)
  • The Service layer is used by the crypto server to abstract over different backends
  • A HaceBackend would wrap a HaceController (HAL) to implement OneShot<A> (Service)

Architectural Insight: Decoupling Resource Recovery

The HAL traits in hal/blocking/src/digest.rs bundle two concerns:

  1. Operation semantics: init/update/finalize flow
  2. Resource recovery: typestate pattern ensuring controller is returned
// Current HAL: both concerns bundled
pub mod owned {
    pub trait DigestOp {
        type Output;
        type Controller;
        type Error;
        
        fn update(self, data: &[u8]) -> Result<Self, Self::Error>;
        fn finalize(self) -> Result<(Self::Output, Self::Controller), Self::Error>;
        fn cancel(self) -> Self::Controller;  // ← Resource recovery
    }
}

Why This Matters for Service Layer

For the crypto server:

  • Sessions are already managed via session handles (IDs in a table)
  • Resource recovery is handled at the session table level, not per-operation
  • The typestate overhead provides no benefit for software backends

Proposed Decoupling (Future Work)

Decouple into two orthogonal patterns:

// 1. Core operation trait (minimal, &mut self)
pub trait DigestOp {
    type Output;
    type Error;
    
    fn update(&mut self, data: &[u8]) -> Result<(), Self::Error>;
    fn finalize(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>;
}

// 2. Optional wrapper for resource recovery (typestate)
pub struct Owned<T, C> {
    op: T,
    controller: C,
}

impl<T: DigestOp, C> Owned<T, C> {
    pub fn update(self, data: &[u8]) -> Result<Self, (C, T::Error)> { ... }
    pub fn finalize(self) -> Result<(T::Output, C), (C, T::Error)> { ... }
    pub fn cancel(self) -> C { ... }  // ← Resource recovery here
}

Benefits

ConcernBaremetalServer
Operation semanticsUses core DigestOpUses core DigestOp
Resource recoveryWraps in Owned<T, C>Not needed (session table)

This decoupling is not required for the current refactor—the Service layer traits are already designed without typestate. However, this insight informs future HAL evolution.


Threading Model Note

Pigweed userspace processes are single-threaded event loops. The crypto server:

  • Handles requests sequentially within a single thread
  • Cannot spawn multiple threads to handle concurrent requests
  • For true parallelism, would need multiple separate server processes

This simplifies session management—no need for thread-safe session tables or locks. The single-threaded model also means &mut self on Streaming<A> methods is safe without additional synchronization.


Phase 1: Create crypto-traits Crate

Goal: Define algorithm marker types and backend traits in a minimal, dependency-free crate.

1.1 Create directory structure

services/crypto/traits/
├── Cargo.toml
├── BUILD.bazel
└── src/
    └── lib.rs

1.2 Create Cargo.toml

[package]
name = "crypto-traits"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"

[dependencies]
# Intentionally minimal — no_std, no dependencies

1.3 Create src/lib.rs

Contents:

#![no_std]

//! Crypto traits for backend abstraction.
//!
//! This crate defines:
//! - Algorithm marker types (Sha256, HmacSha256, etc.)
//! - OneShot<A> trait for one-shot crypto operations
//! - Streaming<A> trait for session-based streaming
//! - CryptoInput enum for semantically typed inputs

// --- Algorithm trait ---
pub trait Algorithm {
    const OUTPUT_SIZE: usize;
    const OP_CODE: u8;
}

// --- Digest algorithms ---
pub struct Sha256;
impl Algorithm for Sha256 { const OUTPUT_SIZE: usize = 32; const OP_CODE: u8 = 0x01; }

pub struct Sha384;
impl Algorithm for Sha384 { const OUTPUT_SIZE: usize = 48; const OP_CODE: u8 = 0x02; }

pub struct Sha512;
impl Algorithm for Sha512 { const OUTPUT_SIZE: usize = 64; const OP_CODE: u8 = 0x03; }

// --- MAC algorithms ---
pub struct HmacSha256;
impl Algorithm for HmacSha256 { const OUTPUT_SIZE: usize = 32; const OP_CODE: u8 = 0x10; }

pub struct HmacSha384;
impl Algorithm for HmacSha384 { const OUTPUT_SIZE: usize = 48; const OP_CODE: u8 = 0x11; }

pub struct HmacSha512;
impl Algorithm for HmacSha512 { const OUTPUT_SIZE: usize = 64; const OP_CODE: u8 = 0x12; }

// --- AEAD algorithms ---
pub struct Aes256GcmEncrypt;
impl Algorithm for Aes256GcmEncrypt { const OUTPUT_SIZE: usize = 0; const OP_CODE: u8 = 0x20; }

pub struct Aes256GcmDecrypt;
impl Algorithm for Aes256GcmDecrypt { const OUTPUT_SIZE: usize = 0; const OP_CODE: u8 = 0x21; }

// --- Signature algorithms (gated) ---
#[cfg(feature = "ecdsa")]
pub struct EcdsaP256Sign;
#[cfg(feature = "ecdsa")]
impl Algorithm for EcdsaP256Sign { const OUTPUT_SIZE: usize = 64; const OP_CODE: u8 = 0x40; }

#[cfg(feature = "ecdsa")]
pub struct EcdsaP256Verify;
#[cfg(feature = "ecdsa")]
impl Algorithm for EcdsaP256Verify { const OUTPUT_SIZE: usize = 1; const OP_CODE: u8 = 0x41; }

#[cfg(feature = "ecdsa")]
pub struct EcdsaP384Sign;
#[cfg(feature = "ecdsa")]
impl Algorithm for EcdsaP384Sign { const OUTPUT_SIZE: usize = 96; const OP_CODE: u8 = 0x42; }

#[cfg(feature = "ecdsa")]
pub struct EcdsaP384Verify;
#[cfg(feature = "ecdsa")]
impl Algorithm for EcdsaP384Verify { const OUTPUT_SIZE: usize = 1; const OP_CODE: u8 = 0x43; }

// --- Structured input types ---
pub enum CryptoInput<'a> {
    Digest { data: &'a [u8] },
    Mac { key: &'a [u8], data: &'a [u8] },
    Aead { key: &'a [u8], nonce: &'a [u8], data: &'a [u8] },
    Sign { private_key: &'a [u8], message: &'a [u8] },
    Verify { public_key: &'a [u8], message: &'a [u8], signature: &'a [u8] },
}

// --- Error type ---
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CryptoError {
    InvalidOperation,
    InvalidKeyLength,
    InvalidNonceLength,
    InvalidDataLength,
    InvalidSignature,
    BufferTooSmall,
    AuthenticationFailed,
    HardwareFailure,
}

// --- OneShot trait ---
pub trait OneShot<A: Algorithm> {
    fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError>;
}

// --- Streaming trait ---
pub trait Streaming<A: Algorithm> {
    type Session;
    
    fn begin(&mut self) -> Result<Self::Session, CryptoError>;
    fn update(&mut self, session: &mut Self::Session, data: &[u8]) -> Result<(), CryptoError>;
    fn finalize(&mut self, session: Self::Session, output: &mut [u8]) -> Result<usize, CryptoError>;
    fn cancel(&mut self, session: Self::Session);
}

1.4 Create BUILD.bazel

load("@rules_rust//rust:defs.bzl", "rust_library")

rust_library(
    name = "crypto-traits",
    srcs = ["src/lib.rs"],
    crate_features = select({
        "//target:ecdsa": ["ecdsa"],
        "//conditions:default": [],
    }),
    visibility = ["//visibility:public"],
)

1.5 Verification

bazel build //services/crypto/traits:crypto-traits

Phase 2: Create backend-rustcrypto Crate

Goal: Implement OneShot<A> for all algorithms using RustCrypto.

2.1 Create directory structure

services/crypto/backend-rustcrypto/
├── Cargo.toml
├── BUILD.bazel
└── src/
    └── lib.rs

2.2 Create Cargo.toml

[package]
name = "crypto-backend-rustcrypto"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"

[features]
default = []
ecdsa = ["crypto-traits/ecdsa", "p256", "p384"]

[dependencies]
crypto-traits = { path = "../traits" }

# RustCrypto
sha2 = { version = "0.10", default-features = false }
hmac = { version = "0.12", default-features = false }
aes-gcm = { version = "0.10", default-features = false, features = ["aes"] }

# ECDSA (optional)
p256 = { version = "0.13", default-features = false, features = ["ecdsa"], optional = true }
p384 = { version = "0.13", default-features = false, features = ["ecdsa"], optional = true }

2.3 Create src/lib.rs

#![no_std]

use crypto_traits::{
    Algorithm, CryptoError, CryptoInput, OneShot,
    Sha256, Sha384, Sha512,
    HmacSha256, HmacSha384, HmacSha512,
    Aes256GcmEncrypt, Aes256GcmDecrypt,
};

#[cfg(feature = "ecdsa")]
use crypto_traits::{EcdsaP256Sign, EcdsaP256Verify, EcdsaP384Sign, EcdsaP384Verify};

use sha2::Digest as Sha2Digest;
use hmac::{Hmac, Mac};
use aes_gcm::{Aes256Gcm, KeyInit, Nonce as GcmNonce, aead::AeadInPlace};

/// RustCrypto-based backend. Stateless — can be freely copied.
#[derive(Clone, Copy, Default)]
pub struct RustCryptoBackend;

impl RustCryptoBackend {
    pub const fn new() -> Self {
        Self
    }
}

// --- Generic digest helper ---
fn do_digest<D: Sha2Digest>(data: &[u8], output: &mut [u8]) -> Result<usize, CryptoError> {
    let mut hasher = D::new();
    hasher.update(data);
    let result = hasher.finalize();
    let size = result.len();
    if output.len() < size {
        return Err(CryptoError::BufferTooSmall);
    }
    output[..size].copy_from_slice(&result);
    Ok(size)
}

impl OneShot<Sha256> for RustCryptoBackend {
    fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> {
        let CryptoInput::Digest { data } = input else { return Err(CryptoError::InvalidOperation) };
        do_digest::<sha2::Sha256>(data, output)
    }
}

impl OneShot<Sha384> for RustCryptoBackend {
    fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> {
        let CryptoInput::Digest { data } = input else { return Err(CryptoError::InvalidOperation) };
        do_digest::<sha2::Sha384>(data, output)
    }
}

impl OneShot<Sha512> for RustCryptoBackend {
    fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> {
        let CryptoInput::Digest { data } = input else { return Err(CryptoError::InvalidOperation) };
        do_digest::<sha2::Sha512>(data, output)
    }
}

// --- Generic HMAC helper ---
fn do_hmac<D>(key: &[u8], data: &[u8], output: &mut [u8]) -> Result<usize, CryptoError>
where
    D: sha2::Digest + hmac::digest::core_api::BlockSizeUser + Clone,
    Hmac<D>: Mac,
{
    let mut mac = <Hmac<D> as Mac>::new_from_slice(key)
        .map_err(|_| CryptoError::InvalidKeyLength)?;
    mac.update(data);
    let result = mac.finalize().into_bytes();
    let size = result.len();
    if output.len() < size {
        return Err(CryptoError::BufferTooSmall);
    }
    output[..size].copy_from_slice(&result);
    Ok(size)
}

impl OneShot<HmacSha256> for RustCryptoBackend {
    fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> {
        let CryptoInput::Mac { key, data } = input else { return Err(CryptoError::InvalidOperation) };
        do_hmac::<sha2::Sha256>(key, data, output)
    }
}

impl OneShot<HmacSha384> for RustCryptoBackend {
    fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> {
        let CryptoInput::Mac { key, data } = input else { return Err(CryptoError::InvalidOperation) };
        do_hmac::<sha2::Sha384>(key, data, output)
    }
}

impl OneShot<HmacSha512> for RustCryptoBackend {
    fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> {
        let CryptoInput::Mac { key, data } = input else { return Err(CryptoError::InvalidOperation) };
        do_hmac::<sha2::Sha512>(key, data, output)
    }
}

// --- AES-GCM ---
const AES_KEY_SIZE: usize = 32;
const GCM_NONCE_SIZE: usize = 12;
const GCM_TAG_SIZE: usize = 16;

impl OneShot<Aes256GcmEncrypt> for RustCryptoBackend {
    fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> {
        let CryptoInput::Aead { key, nonce, data } = input else { 
            return Err(CryptoError::InvalidOperation) 
        };
        if key.len() != AES_KEY_SIZE {
            return Err(CryptoError::InvalidKeyLength);
        }
        if nonce.len() != GCM_NONCE_SIZE {
            return Err(CryptoError::InvalidNonceLength);
        }
        let output_len = data.len() + GCM_TAG_SIZE;
        if output.len() < output_len {
            return Err(CryptoError::BufferTooSmall);
        }
        
        let key_array: [u8; 32] = key.try_into().map_err(|_| CryptoError::InvalidKeyLength)?;
        let cipher = Aes256Gcm::new(&key_array.into());
        let gcm_nonce = GcmNonce::from_slice(nonce);
        
        output[..data.len()].copy_from_slice(data);
        let tag = cipher.encrypt_in_place_detached(gcm_nonce, &[], &mut output[..data.len()])
            .map_err(|_| CryptoError::HardwareFailure)?;
        output[data.len()..output_len].copy_from_slice(&tag);
        
        Ok(output_len)
    }
}

impl OneShot<Aes256GcmDecrypt> for RustCryptoBackend {
    fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> {
        let CryptoInput::Aead { key, nonce, data } = input else { 
            return Err(CryptoError::InvalidOperation) 
        };
        if key.len() != AES_KEY_SIZE {
            return Err(CryptoError::InvalidKeyLength);
        }
        if nonce.len() != GCM_NONCE_SIZE {
            return Err(CryptoError::InvalidNonceLength);
        }
        if data.len() < GCM_TAG_SIZE {
            return Err(CryptoError::InvalidDataLength);
        }
        
        let ciphertext_len = data.len() - GCM_TAG_SIZE;
        if output.len() < ciphertext_len {
            return Err(CryptoError::BufferTooSmall);
        }
        
        let key_array: [u8; 32] = key.try_into().map_err(|_| CryptoError::InvalidKeyLength)?;
        let cipher = Aes256Gcm::new(&key_array.into());
        let gcm_nonce = GcmNonce::from_slice(nonce);
        
        let ciphertext = &data[..ciphertext_len];
        let tag = &data[ciphertext_len..];
        
        output[..ciphertext_len].copy_from_slice(ciphertext);
        cipher.decrypt_in_place_detached(
            gcm_nonce,
            &[],
            &mut output[..ciphertext_len],
            tag.into(),
        ).map_err(|_| CryptoError::AuthenticationFailed)?;
        
        Ok(ciphertext_len)
    }
}

// --- ECDSA (feature-gated) ---
#[cfg(feature = "ecdsa")]
mod ecdsa_impl {
    use super::*;
    use p256::ecdsa::{SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey, Signature as P256Signature, signature::{Signer, Verifier}};
    use p384::ecdsa::{SigningKey as P384SigningKey, VerifyingKey as P384VerifyingKey, Signature as P384Signature};
    
    impl OneShot<EcdsaP256Sign> for RustCryptoBackend {
        fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> {
            let CryptoInput::Sign { private_key, message } = input else {
                return Err(CryptoError::InvalidOperation);
            };
            if private_key.len() != 32 {
                return Err(CryptoError::InvalidKeyLength);
            }
            if output.len() < 64 {
                return Err(CryptoError::BufferTooSmall);
            }
            
            let key = P256SigningKey::from_slice(private_key)
                .map_err(|_| CryptoError::InvalidKeyLength)?;
            let sig: P256Signature = key.sign(message);
            output[..64].copy_from_slice(&sig.to_bytes());
            Ok(64)
        }
    }
    
    impl OneShot<EcdsaP256Verify> for RustCryptoBackend {
        fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> {
            let CryptoInput::Verify { public_key, message, signature } = input else {
                return Err(CryptoError::InvalidOperation);
            };
            if signature.len() != 64 {
                return Err(CryptoError::InvalidSignature);
            }
            if output.is_empty() {
                return Err(CryptoError::BufferTooSmall);
            }
            
            let key = P256VerifyingKey::from_sec1_bytes(public_key)
                .map_err(|_| CryptoError::InvalidKeyLength)?;
            let sig = P256Signature::from_slice(signature)
                .map_err(|_| CryptoError::InvalidSignature)?;
            
            match key.verify(message, &sig) {
                Ok(()) => {
                    output[0] = 1; // verified
                    Ok(1)
                }
                Err(_) => {
                    output[0] = 0; // failed
                    Ok(1)
                }
            }
        }
    }
    
    impl OneShot<EcdsaP384Sign> for RustCryptoBackend {
        fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> {
            let CryptoInput::Sign { private_key, message } = input else {
                return Err(CryptoError::InvalidOperation);
            };
            if private_key.len() != 48 {
                return Err(CryptoError::InvalidKeyLength);
            }
            if output.len() < 96 {
                return Err(CryptoError::BufferTooSmall);
            }
            
            let key = P384SigningKey::from_slice(private_key)
                .map_err(|_| CryptoError::InvalidKeyLength)?;
            let sig: P384Signature = Signer::sign(&key, message);
            output[..96].copy_from_slice(&sig.to_bytes());
            Ok(96)
        }
    }
    
    impl OneShot<EcdsaP384Verify> for RustCryptoBackend {
        fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> {
            let CryptoInput::Verify { public_key, message, signature } = input else {
                return Err(CryptoError::InvalidOperation);
            };
            if signature.len() != 96 {
                return Err(CryptoError::InvalidSignature);
            }
            if output.is_empty() {
                return Err(CryptoError::BufferTooSmall);
            }
            
            let key = P384VerifyingKey::from_sec1_bytes(public_key)
                .map_err(|_| CryptoError::InvalidKeyLength)?;
            let sig = P384Signature::from_slice(signature)
                .map_err(|_| CryptoError::InvalidSignature)?;
            
            match Verifier::verify(&key, message, &sig) {
                Ok(()) => {
                    output[0] = 1;
                    Ok(1)
                }
                Err(_) => {
                    output[0] = 0;
                    Ok(1)
                }
            }
        }
    }
}

2.4 Verification

bazel build //services/crypto/backend-rustcrypto:crypto-backend-rustcrypto

Phase 3: Rewrite Server with CryptoServer<B>

Goal: Replace the 496-line server with a ~150-line generic implementation.

3.1 Modify services/crypto/server/Cargo.toml

Replace direct RustCrypto dependencies with trait crate:

[dependencies]
crypto-api = { path = "../api" }
crypto-traits = { path = "../traits" }
crypto-backend-rustcrypto = { path = "../backend-rustcrypto" }
zerocopy = { version = "0.8", features = ["derive"] }
# Remove: sha2, hmac, aes-gcm, p256, p384

3.2 Rewrite services/crypto/server/src/main.rs

New structure:

#![no_main]
#![no_std]

use crypto_api::{CryptoError as WireError, CryptoOp, CryptoRequestHeader, CryptoResponseHeader};
use crypto_traits::{
    Algorithm, CryptoError, CryptoInput, OneShot, Streaming,
    Sha256, Sha384, Sha512,
    HmacSha256, HmacSha384, HmacSha512,
    Aes256GcmEncrypt, Aes256GcmDecrypt,
};
#[cfg(feature = "ecdsa")]
use crypto_traits::{EcdsaP256Sign, EcdsaP256Verify, EcdsaP384Sign, EcdsaP384Verify};

use crypto_backend_rustcrypto::RustCryptoBackend;
use pw_status::Result;
use userspace::entry;
use userspace::syscall::{self, Signals};
use userspace::time::Instant;
use app_crypto_server::handle;

const MAX_REQUEST_SIZE: usize = 1024;
const MAX_RESPONSE_SIZE: usize = 1024;

// Type alias for backend — change this one line to swap backends
type Backend = RustCryptoBackend;

/// Generic crypto server.
struct CryptoServer<B> {
    backend: B,
    // Streaming session state (single session at a time)
    session: StreamingSession,
}

enum StreamingSession {
    None,
    Sha256(Sha256State),
    Sha384(Sha384State),
    Sha512(Sha512State),
}

// Session state types (backend-specific, but hidden behind Streaming<A> trait)
struct Sha256State { /* backend session handle */ }
struct Sha384State { /* backend session handle */ }
struct Sha512State { /* backend session handle */ }

impl<B> CryptoServer<B>
where
    B: OneShot<Sha256> + OneShot<Sha384> + OneShot<Sha512>
     + OneShot<HmacSha256> + OneShot<HmacSha384> + OneShot<HmacSha512>
     + OneShot<Aes256GcmEncrypt> + OneShot<Aes256GcmDecrypt>
     #[cfg(feature = "ecdsa")]
     + OneShot<EcdsaP256Sign> + OneShot<EcdsaP256Verify>
     + OneShot<EcdsaP384Sign> + OneShot<EcdsaP384Verify>
{
    fn new(backend: B) -> Self {
        Self {
            backend,
            session: StreamingSession::None,
        }
    }

    fn dispatch(&self, request: &[u8], response: &mut [u8]) -> usize {
        // Parse header
        if request.len() < CryptoRequestHeader::SIZE {
            return encode_error(response, WireError::InvalidDataLength);
        }
        
        let header_bytes = &request[..CryptoRequestHeader::SIZE];
        let Some(header) = zerocopy::Ref::<_, CryptoRequestHeader>::from_bytes(header_bytes).ok() else {
            return encode_error(response, WireError::InvalidDataLength);
        };
        let header: &CryptoRequestHeader = &*header;
        
        let op = match header.operation() {
            Ok(op) => op,
            Err(e) => return encode_error(response, e),
        };
        
        // Extract fields
        let payload = &request[CryptoRequestHeader::SIZE..];
        let key_len = header.key_length();
        let nonce_len = header.nonce_length();
        let data_len = header.data_length();
        
        if payload.len() < key_len + nonce_len + data_len {
            return encode_error(response, WireError::InvalidDataLength);
        }
        
        let key = &payload[..key_len];
        let nonce = &payload[key_len..key_len + nonce_len];
        let data = &payload[key_len + nonce_len..key_len + nonce_len + data_len];
        
        // Build semantic input
        let input = Self::build_input(op, key, nonce, data);
        
        // Dispatch by op code
        match op {
            CryptoOp::Sha256Hash => self.run::<Sha256>(&input, response),
            CryptoOp::Sha384Hash => self.run::<Sha384>(&input, response),
            CryptoOp::Sha512Hash => self.run::<Sha512>(&input, response),
            CryptoOp::HmacSha256 => self.run::<HmacSha256>(&input, response),
            CryptoOp::HmacSha384 => self.run::<HmacSha384>(&input, response),
            CryptoOp::HmacSha512 => self.run::<HmacSha512>(&input, response),
            CryptoOp::Aes256GcmEncrypt => self.run::<Aes256GcmEncrypt>(&input, response),
            CryptoOp::Aes256GcmDecrypt => self.run::<Aes256GcmDecrypt>(&input, response),
            #[cfg(feature = "ecdsa")]
            CryptoOp::EcdsaP256Sign => self.run::<EcdsaP256Sign>(&input, response),
            #[cfg(feature = "ecdsa")]
            CryptoOp::EcdsaP256Verify => self.run::<EcdsaP256Verify>(&input, response),
            #[cfg(feature = "ecdsa")]
            CryptoOp::EcdsaP384Sign => self.run::<EcdsaP384Sign>(&input, response),
            #[cfg(feature = "ecdsa")]
            CryptoOp::EcdsaP384Verify => self.run::<EcdsaP384Verify>(&input, response),
            // Streaming ops (Phase 4)
            CryptoOp::Sha256Begin | CryptoOp::Sha256Update | CryptoOp::Sha256Finish |
            CryptoOp::Sha384Begin | CryptoOp::Sha384Update | CryptoOp::Sha384Finish |
            CryptoOp::Sha512Begin | CryptoOp::Sha512Update | CryptoOp::Sha512Finish => {
                encode_error(response, WireError::InvalidOperation) // TODO Phase 4
            }
            #[cfg(not(feature = "ecdsa"))]
            CryptoOp::EcdsaP256Sign | CryptoOp::EcdsaP256Verify |
            CryptoOp::EcdsaP384Sign | CryptoOp::EcdsaP384Verify => {
                encode_error(response, WireError::InvalidOperation)
            }
        }
    }

    fn build_input<'a>(op: CryptoOp, key: &'a [u8], nonce: &'a [u8], data: &'a [u8]) -> CryptoInput<'a> {
        match op {
            CryptoOp::Sha256Hash | CryptoOp::Sha384Hash | CryptoOp::Sha512Hash => 
                CryptoInput::Digest { data },
            CryptoOp::HmacSha256 | CryptoOp::HmacSha384 | CryptoOp::HmacSha512 => 
                CryptoInput::Mac { key, data },
            CryptoOp::Aes256GcmEncrypt | CryptoOp::Aes256GcmDecrypt => 
                CryptoInput::Aead { key, nonce, data },
            CryptoOp::EcdsaP256Sign | CryptoOp::EcdsaP384Sign => 
                CryptoInput::Sign { private_key: key, message: data },
            CryptoOp::EcdsaP256Verify | CryptoOp::EcdsaP384Verify => 
                CryptoInput::Verify { public_key: key, message: data, signature: nonce },
            _ => CryptoInput::Digest { data: &[] }, // streaming handled separately
        }
    }

    fn run<A: Algorithm>(&self, input: &CryptoInput, response: &mut [u8]) -> usize
    where
        B: OneShot<A>,
    {
        let result_start = CryptoResponseHeader::SIZE;
        match self.backend.compute(input, &mut response[result_start..]) {
            Ok(len) => encode_success(response, len),
            Err(e) => encode_error(response, map_error(e)),
        }
    }
}

fn encode_error(response: &mut [u8], err: WireError) -> usize {
    let header = CryptoResponseHeader::error(err);
    let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
    response[..CryptoResponseHeader::SIZE].copy_from_slice(header_bytes);
    CryptoResponseHeader::SIZE
}

fn encode_success(response: &mut [u8], result_len: usize) -> usize {
    let header = CryptoResponseHeader::success(result_len as u16);
    let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
    response[..CryptoResponseHeader::SIZE].copy_from_slice(header_bytes);
    CryptoResponseHeader::SIZE + result_len
}

fn map_error(e: CryptoError) -> WireError {
    match e {
        CryptoError::InvalidOperation => WireError::InvalidOperation,
        CryptoError::InvalidKeyLength => WireError::InvalidKeyLength,
        CryptoError::InvalidNonceLength => WireError::InvalidNonceLength,
        CryptoError::InvalidDataLength => WireError::InvalidDataLength,
        CryptoError::InvalidSignature => WireError::InvalidSignature,
        CryptoError::BufferTooSmall => WireError::BufferTooSmall,
        CryptoError::AuthenticationFailed => WireError::AuthenticationFailed,
        CryptoError::HardwareFailure => WireError::HardwareFailure,
    }
}

#[entry]
fn main() -> ! {
    match crypto_server_loop() {
        Ok(()) => unreachable!(),
        Err(e) => {
            pw_log::error!("Crypto server error: {:?}", e);
            loop { cortex_m::asm::wfi(); }
        }
    }
}

fn crypto_server_loop() -> Result<()> {
    pw_log::info!("Crypto server starting");
    
    let server = CryptoServer::new(RustCryptoBackend::new());
    let mut request_buf = [0u8; MAX_REQUEST_SIZE];
    let mut response_buf = [0u8; MAX_RESPONSE_SIZE];

    loop {
        syscall::object_wait(handle::CRYPTO, Signals::READABLE, Instant::MAX)?;
        let len = syscall::channel_read(handle::CRYPTO, 0, &mut request_buf)?;
        let response_len = server.dispatch(&request_buf[..len], &mut response_buf);
        syscall::channel_respond(handle::CRYPTO, &response_buf[..response_len])?;
    }
}

3.3 Verification

bazel test --config=virt_ast1060_evb //target/ast1060/crypto:crypto_test --test_output=all

All existing tests must pass unchanged.


Phase 4: Add Streaming Support via Streaming<A> Trait

Goal: Implement streaming hash using the trait-based design.

4.1 Add to crypto-traits/src/lib.rs

Already defined in Phase 1. Add session state types.

4.2 Add to backend-rustcrypto/src/lib.rs

use sha2::{Sha256 as Sha2_256, Sha384 as Sha2_384, Sha512 as Sha2_512};

pub struct Sha256Session(Sha2_256);
pub struct Sha384Session(Sha2_384);
pub struct Sha512Session(Sha2_512);

impl Streaming<Sha256> for RustCryptoBackend {
    type Session = Sha256Session;
    
    fn begin(&mut self) -> Result<Self::Session, CryptoError> {
        Ok(Sha256Session(Sha2_256::new()))
    }
    
    fn update(&mut self, session: &mut Self::Session, data: &[u8]) -> Result<(), CryptoError> {
        session.0.update(data);
        Ok(())
    }
    
    fn finalize(&mut self, session: Self::Session, output: &mut [u8]) -> Result<usize, CryptoError> {
        if output.len() < 32 {
            return Err(CryptoError::BufferTooSmall);
        }
        let result = session.0.finalize();
        output[..32].copy_from_slice(&result);
        Ok(32)
    }
    
    fn cancel(&mut self, _session: Self::Session) {
        // Session dropped, nothing to clean up for software impl
    }
}

// Similar for Sha384, Sha512

4.3 Update server dispatch

Add streaming op handling in CryptoServer::dispatch().

4.4 Verification

bazel test --config=virt_ast1060_evb //target/ast1060/crypto:crypto_test --test_output=all

Streaming test (test_sha256_streaming) must pass.


Phase 5: HACE Backend (Future)

Goal: Add hardware-accelerated backend for AST1060.

5.1 Create backend-hace crate

services/crypto/backend-hace/
├── Cargo.toml
├── BUILD.bazel
└── src/
    └── lib.rs

5.2 Implement OneShot<A> and Streaming<A>

Wrap the HaceController from aspeed-rust to implement the traits.

5.3 Feature-gate in server

#[cfg(feature = "hace")]
type Backend = HaceBackend;

#[cfg(not(feature = "hace"))]
type Backend = RustCryptoBackend;

Task Summary

PhaseTaskNew FilesModifiedLOCEffort
1Create crypto-traits crate30~1500.5 day
2Create backend-rustcrypto crate30~3001 day
3Rewrite server with CryptoServer<B>02~150 (was 496)1 day
4Add streaming via Streaming<A>03~1000.5 day
5HACE backend (future)31~3003 days
Total96~1000~6 days

What Happens to Existing Code

ComponentAction
hal/blocking/src/digest.rsKeep — still used by non-crypto-server code
hal/blocking/src/mac.rsKeep — still used by non-crypto-server code
platform/impls/rustcrypto/src/controller.rsReference only — mine for implementation patterns
services/crypto/server/src/main.rsRewrite — becomes generic CryptoServer<B>
services/crypto/api/Keep — wire protocol unchanged
services/crypto/client/Keep — client API unchanged
services/crypto/tests/Keep — tests pass without modification

Code Organization After Refactor

services/crypto/
├── traits/                          # NEW: Algorithm markers + OneShot/Streaming traits
│   └── src/lib.rs                   #      ~150 lines, no dependencies
├── backend-rustcrypto/              # NEW: OneShot<A> impls using RustCrypto
│   └── src/lib.rs                   #      ~300 lines
├── backend-hace/                    # FUTURE: OneShot<A> impls using HACE
│   └── src/lib.rs                   #      ~300 lines
├── api/                             # UNCHANGED: Wire protocol
│   └── src/protocol.rs
├── server/                          # REWRITTEN: Generic CryptoServer<B>
│   └── src/main.rs                  #      ~150 lines (was 496)
├── client/                          # UNCHANGED: Client library
│   └── src/lib.rs
└── tests/                           # UNCHANGED: Integration tests
    └── src/main.rs

Verification Checkpoints

After each phase:

  1. Build: bazel build //services/crypto/...
  2. Lint: bazel build //services/crypto/... --aspects=@aspect_rules_lint//lint:lint.bzl%clippy_lints
  3. Test: bazel test --config=virt_ast1060_evb //target/ast1060/crypto:crypto_test --test_output=all
  4. Commit: One commit per phase with descriptive message

Rollback Plan

Each phase is independently deployable:

  • Phase 1-2 are additive (no existing code changes)
  • Phase 3 is the critical rewrite — keep old server as main.rs.bak until tests pass
  • Phase 4-5 are additive features

Decision Record

Date: February 13, 2026

Decision: Implement new composable OneShot<A> / Streaming<A> traits rather than reusing existing openprot-hal-blocking + RustCryptoController.

Rationale:

  1. Current architecture uses “God trait” pattern requiring all backends to implement all algorithms
  2. Adding new algorithms touches shared trait definitions, violating Open-Closed Principle
  3. SPDM spec requires 10-15 additional algorithms; current approach would be 3-4 files per algorithm
  4. Composable traits allow adding algorithms in 1-2 files with no modifications to existing code
  5. HAL traits bundle resource recovery (typestate) with operation semantics—unnecessary for server
  6. Service layer traits use session handles, better fit for IPC-based state management

Tradeoff: ~6 days upfront work vs. ongoing maintenance cost for 2+ years of algorithm additions.

Accepted by: User (February 13, 2026)

Updated: February 14, 2026 — Added architectural insights on trait layering and resource recovery decoupling


Ready to execute.