Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 78 additions & 0 deletions api/src/mailbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ impl CommandId {
pub const CERTIFY_KEY_EXTENDED: Self = Self(0x434B4558); // "CKEX"
pub const CERTIFY_KEY_EXTENDED_ECC384: Self = Self(0x434B4558); // "CKEX"
pub const CERTIFY_KEY_EXTENDED_MLDSA87: Self = Self(0x434B584D); // "CKXM"
pub const CERTIFY_KEY_CHUNKS: Self = Self(0x434B4348); // "CKCH"

/// FIPS module commands.
/// The status command.
Expand Down Expand Up @@ -337,6 +338,7 @@ pub enum MailboxResp {
ProductionAuthDebugUnlockChallenge(ProductionAuthDebugUnlockChallenge),
GetPcrLog(GetPcrLogResp),
ReallocateDpeContextLimits(ReallocateDpeContextLimitsResp),
CertifyKeyChunks(CertifyKeyChunksResp),
}

pub const MAX_RESP_SIZE: usize = size_of::<MailboxResp>();
Expand Down Expand Up @@ -401,6 +403,7 @@ impl MailboxResp {
MailboxResp::ProductionAuthDebugUnlockChallenge(resp) => Ok(resp.as_bytes()),
MailboxResp::GetPcrLog(resp) => Ok(resp.as_bytes()),
MailboxResp::ReallocateDpeContextLimits(resp) => Ok(resp.as_bytes()),
MailboxResp::CertifyKeyChunks(resp) => Ok(resp.as_bytes()),
}
}

Expand Down Expand Up @@ -463,6 +466,7 @@ impl MailboxResp {
MailboxResp::ProductionAuthDebugUnlockChallenge(resp) => Ok(resp.as_mut_bytes()),
MailboxResp::GetPcrLog(resp) => Ok(resp.as_mut_bytes()),
MailboxResp::ReallocateDpeContextLimits(resp) => Ok(resp.as_mut_bytes()),
MailboxResp::CertifyKeyChunks(resp) => Ok(resp.as_mut_bytes()),
}
}

Expand Down Expand Up @@ -574,6 +578,7 @@ pub enum MailboxReq {
GetPcrLog(MailboxReqHeader),
FeProg(FeProgReq),
ReallocateDpeContextLimits(ReallocateDpeContextLimitsReq),
CertifyKeyChunks(CertifyKeyChunksReq),
}

pub const MAX_REQ_SIZE: usize = size_of::<MailboxReq>();
Expand Down Expand Up @@ -654,6 +659,7 @@ impl MailboxReq {
MailboxReq::GetPcrLog(req) => Ok(req.as_bytes()),
MailboxReq::FeProg(req) => Ok(req.as_bytes()),
MailboxReq::ReallocateDpeContextLimits(req) => Ok(req.as_bytes()),
MailboxReq::CertifyKeyChunks(req) => Ok(req.as_bytes()),
}
}

Expand Down Expand Up @@ -732,6 +738,7 @@ impl MailboxReq {
MailboxReq::GetPcrLog(req) => Ok(req.as_mut_bytes()),
MailboxReq::FeProg(req) => Ok(req.as_mut_bytes()),
MailboxReq::ReallocateDpeContextLimits(req) => Ok(req.as_mut_bytes()),
MailboxReq::CertifyKeyChunks(req) => Ok(req.as_mut_bytes()),
}
}

Expand Down Expand Up @@ -814,6 +821,7 @@ impl MailboxReq {
CommandId::PRODUCTION_AUTH_DEBUG_UNLOCK_TOKEN
}
MailboxReq::ReallocateDpeContextLimits(_) => CommandId::REALLOCATE_DPE_CONTEXT_LIMITS,
MailboxReq::CertifyKeyChunks(_) => CommandId::CERTIFY_KEY_CHUNKS,
}
}

Expand Down Expand Up @@ -1365,6 +1373,76 @@ impl CertifyKeyExtendedResp {
}
}

// CERTIFY_KEY_CHUNKS
#[repr(C)]
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout, PartialEq, Eq)]
pub struct CertifyKeyChunksReq {
pub hdr: MailboxReqHeader,
pub flags: CertifyKeyChunksFlags,
pub reserved: u32,
pub max_size: u32,
pub offset: u32,
pub certify_key_req: [u8; Self::CERTIFY_KEY_REQ_SIZE],
}

impl CertifyKeyChunksReq {
pub const CERTIFY_KEY_REQ_SIZE: usize = 72;
}

impl Request for CertifyKeyChunksReq {
const ID: CommandId = CommandId::CERTIFY_KEY_CHUNKS;
type Resp = CertifyKeyChunksResp;
}

#[repr(C)]
#[derive(
Debug, PartialEq, Eq, FromBytes, Immutable, KnownLayout, IntoBytes, Default, Copy, Clone,
)]
pub struct CertifyKeyChunksFlags(pub u32);

bitflags! {
impl CertifyKeyChunksFlags: u32 {
const USE_MLDSA = 1u32 << 31;
}
}

impl CertifyKeyChunksFlags {
pub fn use_mldsa(&self) -> bool {
self.contains(CertifyKeyChunksFlags::USE_MLDSA)
}
}

#[repr(C)]
#[derive(Debug, Default, IntoBytes, FromBytes, Immutable, KnownLayout, PartialEq, Eq)]
pub struct CertifyKeyChunksRespInfo {
pub hdr: MailboxRespHeader,
pub context_handle: [u8; 16],
pub chunk_len: u32,
pub remaining: u32,
}

#[repr(C)]
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout, PartialEq, Eq)]
pub struct CertifyKeyChunksResp {
pub info: CertifyKeyChunksRespInfo,
pub certify_key_resp: [u8; Self::MAX_CHUNK_SIZE],
}

impl CertifyKeyChunksResp {
pub const MAX_CHUNK_SIZE: usize = 15 * 1024;
}

impl Response for CertifyKeyChunksResp {}

impl Default for CertifyKeyChunksResp {
fn default() -> Self {
Self {
info: Default::default(),
certify_key_resp: [0u8; Self::MAX_CHUNK_SIZE],
}
}
}

// INVOKE_DPE_ECC384
#[repr(C)]
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout, PartialEq, Eq)]
Expand Down
19 changes: 8 additions & 11 deletions drivers/src/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,13 +384,10 @@ impl Aes {
let mut buffer = [0u8; AES_BLOCK_SIZE_BYTES];
buffer[..input.len()].copy_from_slice(input);

let ghash_state = if context.aad_len == 0 && context.buffer_len == 0 {
// Edge case where we have not actually done any AES operations,
// so the GHASH state should not be saved.
[0; 16]
} else {
self.save()?
};
// We've processed at least one block. The GHASH accumulator in
// the hardware reflects that work and must be saved so a subsequent
// update/final restores it.
let ghash_state = self.save()?;
self.zeroize_internal();
Ok((
written,
Expand Down Expand Up @@ -600,10 +597,10 @@ impl Aes {

wait_for_idle(&aes);

// if we haven't actually written any AAD or input, then
// we can skip the restore operation.
// This avoids some edge cases in the hardware.
if aad_len == 0 && len == 0 {
// Nothing to restore until either AAD or a full block of text has
// been processed by the hardware. Issuing GcmPhase::Restore with a
// zero GHASH state corrupts subsequent tag computation.
if aad_len == 0 && (len as usize) < AES_BLOCK_SIZE_BYTES {
return Ok(());
}

Expand Down
1 change: 1 addition & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ caliptra-gen-linker-scripts.workspace = true
cfg-if.workspace = true

[dev-dependencies]
anyhow.workspace = true
aes.workspace = true
aes-gcm.workspace = true
caliptra-api.workspace = true
Expand Down
38 changes: 38 additions & 0 deletions runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,44 @@ Command Code: `0x434B_4558` ("CKEX")
| fips\_status | u32 | Indicates if the command is FIPS approved or an error. |
| certify\_key\_resp | u8[2176] | Certify Key Response. |

### CERTIFY\_KEY\_CHUNKS

Invokes a DPE `CertifyKey` command (ECC-P384 or ML-DSA-87) and returns the response in chunks. This is useful when the DPE response (which contains a certificate or CSR) is larger than the mailbox size or larger than the caller can easily consume.

The handler re-executes the full DPE `CertifyKey` operation on every call and then returns the requested `[offset, offset+max_size)` window of the freshly generated response. Each call rotates the context handle, if needed. The new handle is returned in the per-chunk response header and must be used for the next call.

Command Code: `0x434B_4348` ("CKCH")

*Table: `CERTIFY_KEY_CHUNKS` input arguments*

| **Name** | **Type** | **Description** |
| ----------------- | -------- | ------------------------------------------------------------------------------------- |
| chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian. |
| flags | u32 | Flags. |
| reserved | u32 | Reserved. |
| max\_size | u32 | The maximum length of the chunk the caller wants to receive. If 0, defaults to 15360. |
| offset | u32 | Offset into the full CertifyKey response to read from. |
| certify\_key\_req | u8[72] | The serialized DPE `CertifyKey` command. |

*Table: `CERTIFY_KEY_CHUNKS` input flags*

| **Name** | **Offset** |
| --------- | ---------- |
| USE_MLDSA | 1 << 31 |

When `USE_MLDSA` is set, the command operates on the ML-DSA-87 DPE profile; otherwise, it operates on the ECC-P384 profile.

*Table: `CERTIFY_KEY_CHUNKS` output arguments*

| **Name** | **Type** | **Description** |
| ------------------ | --------- | -------------------------------------------------------------------------- |
| chksum | u32 | Checksum over other output arguments, computed by Caliptra. Little endian. |
| fips\_status | u32 | Indicates if the command is FIPS approved or an error. |
| context\_handle | u8[16] | The new DPE context handle returned by the `CertifyKey` command. |
| chunk\_len | u32 | The length of the chunk returned in `certify_key_resp`. |
| remaining | u32 | The number of bytes remaining in the full response after this chunk. |
| certify\_key\_resp | u8[15360] | The chunk of the DPE `CertifyKey` response. |

### SET_AUTH_MANIFEST

The SoC uses this command and `SET_IMAGE_METADTA` to program an image manifest for Manifest-Based Image Authorization to Caliptra. In response to these commands, the Caliptra Runtime will verify the manifest by authenticating the public keys and in turn using them to authenticate the IMC. On successful verification, the Runtime will store the IMEs into DCCM for future use.
Expand Down
155 changes: 155 additions & 0 deletions runtime/src/certify_key_chunks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*++

Licensed under the Apache-2.0 license.

File Name:

certify_key_chunks.rs

Abstract:

File contains CertifyKeyChunks mailbox command.

--*/

use crate::PauserPrivileges;
use crate::{invoke_dpe::invoke_dpe_cmd, CaliptraDpeProfile, Drivers};
use caliptra_api::mailbox::CertifyKeyChunksRespInfo;
use caliptra_common::mailbox_api::{CertifyKeyChunksReq, CertifyKeyChunksResp};
use caliptra_error::{CaliptraError, CaliptraResult};
use dpe::commands::{CertifyKeyCommand, CertifyKeyMldsa87Cmd, CertifyKeyP384Cmd, Command};
use dpe::response::{CertifyKeyMldsa87Resp, CertifyKeyP384Resp};
use memoffset::span_of;
use zerocopy::FromBytes;

pub struct CertifyKeyChunksCmd;
impl CertifyKeyChunksCmd {
#[inline(never)]
pub(crate) fn execute(
drivers: &mut Drivers,
cmd_args: &[u8],
mbox_resp: &mut [u8],
) -> CaliptraResult<usize> {
let chunk_cmd = CertifyKeyChunksReq::ref_from_bytes(cmd_args)
.map_err(|_| CaliptraError::RUNTIME_INSUFFICIENT_MEMORY)?;

let profile = if chunk_cmd.flags.use_mldsa() {
CaliptraDpeProfile::Mldsa87
} else {
CaliptraDpeProfile::Ecc384
};

let certify_key_cmd = match profile {
CaliptraDpeProfile::Ecc384 => CertifyKeyCommand::from(
CertifyKeyP384Cmd::ref_from_bytes(&chunk_cmd.certify_key_req[..]).or(Err(
CaliptraError::RUNTIME_DPE_COMMAND_DESERIALIZATION_FAILED,
))?,
),
CaliptraDpeProfile::Mldsa87 => CertifyKeyCommand::from(
CertifyKeyMldsa87Cmd::ref_from_bytes(&chunk_cmd.certify_key_req[..]).or(Err(
CaliptraError::RUNTIME_DPE_COMMAND_DESERIALIZATION_FAILED,
))?,
),
};

// Check if command can be executed
// PL1 cannot request X509
let caller_privilege_level = drivers.caller_privilege_level();
if certify_key_cmd.format() == CertifyKeyCommand::FORMAT_X509
&& caller_privilege_level != PauserPrivileges::PL0
{
return Err(CaliptraError::RUNTIME_INCORRECT_PAUSER_PRIVILEGE_LEVEL);
}

let (resp_info, certify_key_resp_bytes) =
CertifyKeyChunksRespInfo::mut_from_prefix(mbox_resp)
.map_err(|_| CaliptraError::RUNTIME_INSUFFICIENT_MEMORY)?;

let (min_resp_size, handle_range) = match profile {
CaliptraDpeProfile::Ecc384 => (
size_of::<CertifyKeyP384Resp>(),
span_of!(CertifyKeyP384Resp, new_context_handle..derived_pubkey_x),
),
CaliptraDpeProfile::Mldsa87 => (
size_of::<CertifyKeyMldsa87Resp>(),
span_of!(CertifyKeyMldsa87Resp, new_context_handle..pubkey),
),
};
Comment thread
parvathib marked this conversation as resolved.

if certify_key_resp_bytes.len() < min_resp_size {
return Err(CaliptraError::RUNTIME_INSUFFICIENT_MEMORY);
}

// Get the full CertifyKey response
let dpe_resp_len = invoke_dpe_cmd(
profile,
drivers,
&Command::from(&certify_key_cmd),
None,
None,
None,
certify_key_resp_bytes,
)
.map_err(|e| {
// If there is extended error info, populate CPTRA_FW_EXTENDED_ERROR_INFO
if let Some(ext_err) = e.get_error_detail() {
drivers.soc_ifc.set_fw_extended_error(ext_err);
}
CaliptraError::RUNTIME_CERTIFY_KEY_EXTENDED_FAILED
})?;

// If the offset is past the end of the response this will make it have nothing in the
// chunk, but the caller will still get the handle back
let offset = usize::min(chunk_cmd.offset as usize, dpe_resp_len);

// Copy the new handle to the response header
resp_info.context_handle = certify_key_resp_bytes[handle_range].try_into().unwrap();

// Copy the chunk of the response to the beginning of the response buffer
let max_chunk_size = CertifyKeyChunksResp::MAX_CHUNK_SIZE;
let max_chunk_size = if chunk_cmd.max_size > 0 {
usize::min(max_chunk_size, chunk_cmd.max_size as usize)
} else {
max_chunk_size
};
let total_remaining = dpe_resp_len.saturating_sub(offset);
let chunk_len = core::cmp::min(total_remaining, max_chunk_size);
copy_to_start(certify_key_resp_bytes, offset, chunk_len)?;

// Fill in the rest of the response info
resp_info.chunk_len = chunk_len as u32;
resp_info.remaining = dpe_resp_len
.saturating_sub(offset)
.saturating_sub(chunk_len) as u32;

Ok(core::mem::size_of::<CertifyKeyChunksRespInfo>() + chunk_len)
}
}

fn copy_to_start(slice: &mut [u8], src_offset: usize, len: usize) -> CaliptraResult<()> {
let slice_len = slice.len();

if src_offset > slice_len || len > slice_len {
return Err(CaliptraError::RUNTIME_INSUFFICIENT_MEMORY);
}

let src_end = src_offset
.checked_add(len)
.ok_or(CaliptraError::RUNTIME_INSUFFICIENT_MEMORY)?;
if src_end > slice_len {
return Err(CaliptraError::RUNTIME_INSUFFICIENT_MEMORY);
}

// Copy forwards (left to right) is always safe when copying to the start (dest = 0)
for i in 0..len {
let src_idx = src_offset + i;
let val = *slice
.get(src_idx)
.ok_or(CaliptraError::RUNTIME_INSUFFICIENT_MEMORY)?;
*slice
.get_mut(i)
.ok_or(CaliptraError::RUNTIME_INSUFFICIENT_MEMORY)? = val;
}

Ok(())
}
Loading
Loading