blob: 04a77e80b411a08481cd4436bbb93c78c2423691 [file]
// Licensed under the Apache-2.0 license
//! RustCrypto Backend
//!
//! Implements [`OneShot<A>`] for all supported algorithms using RustCrypto crates.
//! This backend is software-only and works on host, QEMU, and target.
//!
//! # Example
//!
//! ```ignore
//! use crypto_api::backend::{CryptoInput, OneShot, Sha256};
//! use crypto_backend_rustcrypto::RustCryptoBackend;
//!
//! let backend = RustCryptoBackend::new();
//! let input = CryptoInput::Digest { data: b"hello world" };
//! let mut output = [0u8; 32];
//! let len = backend.compute(&input, &mut output).unwrap();
//! ```
#![no_std]
use crypto_api::backend::{
BackendError, CryptoInput, OneShot, Streaming,
Sha256, Sha384, Sha512,
HmacSha256, HmacSha384, HmacSha512,
Aes256GcmEncrypt, Aes256GcmDecrypt,
GetRandomBytes,
};
#[cfg(feature = "ecdsa")]
use crypto_api::backend::{EcdsaP256Sign, EcdsaP256Verify, EcdsaP384Sign, EcdsaP384Verify};
use rand_chacha::ChaCha20Rng;
use rand_core::{RngCore, SeedableRng};
use sha2::Digest as Sha2Digest;
use hmac::{Hmac, Mac};
use aes_gcm::{Aes256Gcm, KeyInit, Nonce as GcmNonce, aead::AeadInPlace};
#[cfg(feature = "ecdsa")]
use p256::ecdsa::{
SigningKey as P256SigningKey,
VerifyingKey as P256VerifyingKey,
Signature as P256Signature,
signature::{Signer, Verifier},
};
#[cfg(feature = "ecdsa")]
use p384::ecdsa::{
SigningKey as P384SigningKey,
VerifyingKey as P384VerifyingKey,
Signature as P384Signature,
};
// ---------------------------------------------------------------------------
// Backend type
// ---------------------------------------------------------------------------
/// RustCrypto-based software backend.
///
/// This is a zero-sized type — stateless, cheap to copy, can be freely cloned.
/// All state is local to each operation.
#[derive(Clone, Copy, Default, Debug)]
pub struct RustCryptoBackend;
impl RustCryptoBackend {
/// Create a new RustCrypto backend instance.
#[must_use]
pub const fn new() -> Self {
Self
}
}
// ---------------------------------------------------------------------------
// Digest implementations (SHA-2)
// ---------------------------------------------------------------------------
/// Generic digest helper — reduces code duplication across SHA variants.
fn do_digest<D: Sha2Digest>(data: &[u8], output: &mut [u8]) -> Result<usize, BackendError> {
let mut hasher = D::new();
hasher.update(data);
let result = hasher.finalize();
let size = result.len();
if output.len() < size {
return Err(BackendError::BufferTooSmall);
}
output[..size].copy_from_slice(&result);
Ok(size)
}
impl OneShot<Sha256> for RustCryptoBackend {
fn compute(&self, input: &CryptoInput<'_>, output: &mut [u8]) -> Result<usize, BackendError> {
let CryptoInput::Digest { data } = input else {
return Err(BackendError::InvalidOperation);
};
do_digest::<sha2::Sha256>(data, output)
}
}
impl OneShot<Sha384> for RustCryptoBackend {
fn compute(&self, input: &CryptoInput<'_>, output: &mut [u8]) -> Result<usize, BackendError> {
let CryptoInput::Digest { data } = input else {
return Err(BackendError::InvalidOperation);
};
do_digest::<sha2::Sha384>(data, output)
}
}
impl OneShot<Sha512> for RustCryptoBackend {
fn compute(&self, input: &CryptoInput<'_>, output: &mut [u8]) -> Result<usize, BackendError> {
let CryptoInput::Digest { data } = input else {
return Err(BackendError::InvalidOperation);
};
do_digest::<sha2::Sha512>(data, output)
}
}
// ---------------------------------------------------------------------------
// MAC implementations (HMAC-SHA-2)
// ---------------------------------------------------------------------------
type HmacSha2_256 = Hmac<sha2::Sha256>;
type HmacSha2_384 = Hmac<sha2::Sha384>;
type HmacSha2_512 = Hmac<sha2::Sha512>;
impl OneShot<HmacSha256> for RustCryptoBackend {
fn compute(&self, input: &CryptoInput<'_>, output: &mut [u8]) -> Result<usize, BackendError> {
let CryptoInput::Mac { key, data } = input else {
return Err(BackendError::InvalidOperation);
};
let mut mac = <HmacSha2_256 as Mac>::new_from_slice(key)
.map_err(|_| BackendError::InvalidKeyLength)?;
mac.update(data);
let result = mac.finalize().into_bytes();
if output.len() < 32 {
return Err(BackendError::BufferTooSmall);
}
output[..32].copy_from_slice(&result);
Ok(32)
}
}
impl OneShot<HmacSha384> for RustCryptoBackend {
fn compute(&self, input: &CryptoInput<'_>, output: &mut [u8]) -> Result<usize, BackendError> {
let CryptoInput::Mac { key, data } = input else {
return Err(BackendError::InvalidOperation);
};
let mut mac = <HmacSha2_384 as Mac>::new_from_slice(key)
.map_err(|_| BackendError::InvalidKeyLength)?;
mac.update(data);
let result = mac.finalize().into_bytes();
if output.len() < 48 {
return Err(BackendError::BufferTooSmall);
}
output[..48].copy_from_slice(&result);
Ok(48)
}
}
impl OneShot<HmacSha512> for RustCryptoBackend {
fn compute(&self, input: &CryptoInput<'_>, output: &mut [u8]) -> Result<usize, BackendError> {
let CryptoInput::Mac { key, data } = input else {
return Err(BackendError::InvalidOperation);
};
let mut mac = <HmacSha2_512 as Mac>::new_from_slice(key)
.map_err(|_| BackendError::InvalidKeyLength)?;
mac.update(data);
let result = mac.finalize().into_bytes();
if output.len() < 64 {
return Err(BackendError::BufferTooSmall);
}
output[..64].copy_from_slice(&result);
Ok(64)
}
}
// ---------------------------------------------------------------------------
// AEAD implementations (AES-256-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, BackendError> {
let CryptoInput::Aead { key, nonce, data } = input else {
return Err(BackendError::InvalidOperation);
};
if key.len() != AES_KEY_SIZE {
return Err(BackendError::InvalidKeyLength);
}
if nonce.len() != GCM_NONCE_SIZE {
return Err(BackendError::InvalidNonceLength);
}
let output_len = data.len() + GCM_TAG_SIZE;
if output.len() < output_len {
return Err(BackendError::BufferTooSmall);
}
let key_array: [u8; 32] = (*key).try_into()
.map_err(|_| BackendError::InvalidKeyLength)?;
let cipher = Aes256Gcm::new(&key_array.into());
let gcm_nonce = GcmNonce::from_slice(nonce);
// Copy plaintext to output buffer for in-place encryption
output[..data.len()].copy_from_slice(data);
// Encrypt in place, get the tag
let tag = cipher
.encrypt_in_place_detached(gcm_nonce, &[], &mut output[..data.len()])
.map_err(|_| BackendError::InternalError)?;
// Append tag after ciphertext
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, BackendError> {
let CryptoInput::Aead { key, nonce, data } = input else {
return Err(BackendError::InvalidOperation);
};
if key.len() != AES_KEY_SIZE {
return Err(BackendError::InvalidKeyLength);
}
if nonce.len() != GCM_NONCE_SIZE {
return Err(BackendError::InvalidNonceLength);
}
if data.len() < GCM_TAG_SIZE {
return Err(BackendError::InvalidDataLength);
}
let ciphertext_len = data.len() - GCM_TAG_SIZE;
if output.len() < ciphertext_len {
return Err(BackendError::BufferTooSmall);
}
let key_array: [u8; 32] = (*key).try_into()
.map_err(|_| BackendError::InvalidKeyLength)?;
let cipher = Aes256Gcm::new(&key_array.into());
let gcm_nonce = GcmNonce::from_slice(nonce);
// Split ciphertext and tag
let ciphertext = &data[..ciphertext_len];
let tag = &data[ciphertext_len..];
// Copy ciphertext to output for in-place decryption
output[..ciphertext_len].copy_from_slice(ciphertext);
// Decrypt in place
cipher
.decrypt_in_place_detached(gcm_nonce, &[], &mut output[..ciphertext_len], tag.into())
.map_err(|_| BackendError::AuthenticationFailed)?;
Ok(ciphertext_len)
}
}
// ---------------------------------------------------------------------------
// ECDSA implementations (P-256, P-384)
// ---------------------------------------------------------------------------
#[cfg(feature = "ecdsa")]
impl OneShot<EcdsaP256Sign> for RustCryptoBackend {
fn compute(&self, input: &CryptoInput<'_>, output: &mut [u8]) -> Result<usize, BackendError> {
let CryptoInput::Sign { private_key, message } = input else {
return Err(BackendError::InvalidOperation);
};
if private_key.len() != 32 {
return Err(BackendError::InvalidKeyLength);
}
if output.len() < 64 {
return Err(BackendError::BufferTooSmall);
}
let key = P256SigningKey::from_slice(private_key)
.map_err(|_| BackendError::InvalidKeyLength)?;
let sig: P256Signature = key.sign(message);
output[..64].copy_from_slice(&sig.to_bytes());
Ok(64)
}
}
#[cfg(feature = "ecdsa")]
impl OneShot<EcdsaP256Verify> for RustCryptoBackend {
fn compute(&self, input: &CryptoInput<'_>, output: &mut [u8]) -> Result<usize, BackendError> {
let CryptoInput::Verify { public_key, message, signature } = input else {
return Err(BackendError::InvalidOperation);
};
if signature.len() != 64 {
return Err(BackendError::VerificationFailed);
}
if output.is_empty() {
return Err(BackendError::BufferTooSmall);
}
let key = P256VerifyingKey::from_sec1_bytes(public_key)
.map_err(|_| BackendError::InvalidKeyLength)?;
let sig = P256Signature::from_slice(signature)
.map_err(|_| BackendError::VerificationFailed)?;
match key.verify(message, &sig) {
Ok(()) => {
output[0] = 1; // verified
Ok(1)
}
Err(_) => {
output[0] = 0; // failed
Ok(1)
}
}
}
}
#[cfg(feature = "ecdsa")]
impl OneShot<EcdsaP384Sign> for RustCryptoBackend {
fn compute(&self, input: &CryptoInput<'_>, output: &mut [u8]) -> Result<usize, BackendError> {
let CryptoInput::Sign { private_key, message } = input else {
return Err(BackendError::InvalidOperation);
};
if private_key.len() != 48 {
return Err(BackendError::InvalidKeyLength);
}
if output.len() < 96 {
return Err(BackendError::BufferTooSmall);
}
let key = P384SigningKey::from_slice(private_key)
.map_err(|_| BackendError::InvalidKeyLength)?;
let sig: P384Signature = Signer::sign(&key, message);
output[..96].copy_from_slice(&sig.to_bytes());
Ok(96)
}
}
#[cfg(feature = "ecdsa")]
impl OneShot<EcdsaP384Verify> for RustCryptoBackend {
fn compute(&self, input: &CryptoInput<'_>, output: &mut [u8]) -> Result<usize, BackendError> {
let CryptoInput::Verify { public_key, message, signature } = input else {
return Err(BackendError::InvalidOperation);
};
if signature.len() != 96 {
return Err(BackendError::VerificationFailed);
}
if output.is_empty() {
return Err(BackendError::BufferTooSmall);
}
let key = P384VerifyingKey::from_sec1_bytes(public_key)
.map_err(|_| BackendError::InvalidKeyLength)?;
let sig = P384Signature::from_slice(signature)
.map_err(|_| BackendError::VerificationFailed)?;
match Verifier::verify(&key, message, &sig) {
Ok(()) => {
output[0] = 1;
Ok(1)
}
Err(_) => {
output[0] = 0;
Ok(1)
}
}
}
}
// ---------------------------------------------------------------------------
// RNG implementation
// ---------------------------------------------------------------------------
impl OneShot<GetRandomBytes> for RustCryptoBackend {
fn compute(&self, input: &CryptoInput<'_>, output: &mut [u8]) -> Result<usize, BackendError> {
let CryptoInput::Rng { length } = input else {
return Err(BackendError::InvalidOperation);
};
if *length == 0 {
return Err(BackendError::InvalidDataLength);
}
if *length > output.len() {
return Err(BackendError::BufferTooSmall);
}
// Use ChaCha20Rng with a fixed seed for MVP
// TODO: Integrate with hardware RNG when available
// Fixed seed derived from "OpenPRoT-SPDM-RNG-Seed-v1" SHA-256
let seed: [u8; 32] = [
0x3c, 0xf8, 0x91, 0x42, 0x7e, 0x5a, 0x1f, 0xd3,
0x9b, 0x2e, 0x8c, 0x67, 0xa4, 0x0f, 0xb5, 0x28,
0x71, 0xc6, 0x3d, 0x94, 0xe2, 0x58, 0x0b, 0xfa,
0x6d, 0xb1, 0x45, 0xc9, 0x87, 0x3a, 0xf6, 0x29,
];
let mut rng = ChaCha20Rng::from_seed(seed);
rng.fill_bytes(&mut output[..*length]);
Ok(*length)
}
}
// ---------------------------------------------------------------------------
// Streaming implementations (SHA-2)
// ---------------------------------------------------------------------------
/// SHA-256 streaming session state.
pub struct Sha256Session(sha2::Sha256);
/// SHA-384 streaming session state.
pub struct Sha384Session(sha2::Sha384);
/// SHA-512 streaming session state.
pub struct Sha512Session(sha2::Sha512);
impl Streaming<Sha256> for RustCryptoBackend {
type Session = Sha256Session;
fn begin(&mut self) -> Result<Self::Session, BackendError> {
Ok(Sha256Session(sha2::Sha256::new()))
}
fn feed(&mut self, session: &mut Self::Session, data: &[u8]) -> Result<(), BackendError> {
session.0.update(data);
Ok(())
}
fn finish(&mut self, session: Self::Session, output: &mut [u8]) -> Result<usize, BackendError> {
if output.len() < 32 {
return Err(BackendError::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
}
}
impl Streaming<Sha384> for RustCryptoBackend {
type Session = Sha384Session;
fn begin(&mut self) -> Result<Self::Session, BackendError> {
Ok(Sha384Session(sha2::Sha384::new()))
}
fn feed(&mut self, session: &mut Self::Session, data: &[u8]) -> Result<(), BackendError> {
session.0.update(data);
Ok(())
}
fn finish(&mut self, session: Self::Session, output: &mut [u8]) -> Result<usize, BackendError> {
if output.len() < 48 {
return Err(BackendError::BufferTooSmall);
}
let result = session.0.finalize();
output[..48].copy_from_slice(&result);
Ok(48)
}
fn cancel(&mut self, _session: Self::Session) {}
}
impl Streaming<Sha512> for RustCryptoBackend {
type Session = Sha512Session;
fn begin(&mut self) -> Result<Self::Session, BackendError> {
Ok(Sha512Session(sha2::Sha512::new()))
}
fn feed(&mut self, session: &mut Self::Session, data: &[u8]) -> Result<(), BackendError> {
session.0.update(data);
Ok(())
}
fn finish(&mut self, session: Self::Session, output: &mut [u8]) -> Result<usize, BackendError> {
if output.len() < 64 {
return Err(BackendError::BufferTooSmall);
}
let result = session.0.finalize();
output[..64].copy_from_slice(&result);
Ok(64)
}
fn cancel(&mut self, _session: Self::Session) {}
}