Skip to content
Open
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
41 changes: 40 additions & 1 deletion Cargo.lock

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

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ assert_cmd = "2.0.17"
async-bb8-diesel = "0.2"
async-recursion = "1.1.1"
async-trait = "0.1.89"
attest-data = { git = "https://github.com/oxidecomputer/dice-util", branch = "generic-nonce" }
attest-mock = { git = "https://github.com/oxidecomputer/dice-util", rev = "10952e8d9599b735b85d480af3560a11700e5b64" }
atomicwrites = "0.4.4"
authz-macros = { path = "nexus/authz-macros" }
Expand Down Expand Up @@ -524,6 +525,7 @@ http-body-util = "0.1.3"
http-range = "0.1.5"
httpmock = "0.8.0-alpha.1"
httptest = "0.16.3"
hubpack = "0.1"
hubtools = { git = "https://github.com/oxidecomputer/hubtools.git", rev = "2b1ef9b38d75563ea800baa3b17327eec17b1b7a" }
humantime = "2.2.0"
hyper = "1.6.0"
Expand Down Expand Up @@ -555,7 +557,7 @@ jiff = "0.2.15"
key-manager = { path = "key-manager" }
kstat-rs = "0.2.4"
libc = "0.2.174"
libipcc = { git = "https://github.com/oxidecomputer/ipcc-rs", rev = "524eb8f125003dff50b9703900c6b323f00f9e1b" }
libipcc = { git = "https://github.com/oxidecomputer/ipcc-rs", rev = "7cdf2ab9c8d9e9267a8b366aa780c6c26f9a5ecf" }
libfalcon = { git = "https://github.com/oxidecomputer/falcon", branch = "main" }
libnvme = { git = "https://github.com/oxidecomputer/libnvme", rev = "dd5bb221d327a1bc9287961718c3c10d6bd37da0" }
linear-map = "1.2.0"
Expand Down Expand Up @@ -825,6 +827,7 @@ wicket = { path = "wicket" }
wicket-common = { path = "wicket-common" }
wicketd-api = { path = "wicketd-api" }
wicketd-client = { path = "clients/wicketd-client" }
x509-cert = { version = "0.2.5", default-features = false }
xshell = "0.2.7"
zerocopy = "0.8.26"
zeroize = { version = "1.8.1", features = ["zeroize_derive", "std"] }
Expand Down
7 changes: 7 additions & 0 deletions clients/sled-agent-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ progenitor::generate_api!(
"oxnet" = "0.1.0",
},
replace = {
Attestation = sled_agent_types_versions::latest::rot::Attestation,
Baseboard = sled_agent_types_versions::latest::inventory::Baseboard,
BaseboardId = sled_hardware_types::BaseboardId,
ByteCount = omicron_common::api::external::ByteCount,
CertificateChain = sled_agent_types_versions::latest::rot::CertificateChain,
CommitRequest = trust_quorum_types::messages::CommitRequest,
CommitStatus = trust_quorum_types::status::CommitStatus,
CoordinatorStatus = trust_quorum_types::status::CoordinatorStatus,
Expand All @@ -71,9 +73,12 @@ progenitor::generate_api!(
InventoryZpool = sled_agent_types_versions::latest::inventory::InventoryZpool,
LrtqUpgradeMsg = trust_quorum_types::messages::LrtqUpgradeMsg,
MacAddr = omicron_common::api::external::MacAddr,
Measurement = sled_agent_types_versions::latest::rot::Measurement,
MeasurementLog = sled_agent_types_versions::latest::rot::MeasurementLog,
MupdateOverrideBootInventory = sled_agent_types_versions::latest::inventory::MupdateOverrideBootInventory,
Name = omicron_common::api::external::Name,
NetworkInterface = omicron_common::api::internal::shared::NetworkInterface,
Nonce = sled_agent_types_versions::latest::rot::Nonce,
OmicronPhysicalDiskConfig = omicron_common::disk::OmicronPhysicalDiskConfig,
OmicronPhysicalDisksConfig = omicron_common::disk::OmicronPhysicalDisksConfig,
OmicronSledConfig = sled_agent_types_versions::latest::inventory::OmicronSledConfig,
Expand All @@ -89,9 +94,11 @@ progenitor::generate_api!(
ResolvedVpcFirewallRule = omicron_common::api::internal::shared::ResolvedVpcFirewallRule,
ResolvedVpcRoute = omicron_common::api::internal::shared::ResolvedVpcRoute,
ResolvedVpcRouteSet = omicron_common::api::internal::shared::ResolvedVpcRouteSet,
Rot = sled_agent_types_versions::latest::rot::Rot,
RouterId = omicron_common::api::internal::shared::RouterId,
RouterTarget = omicron_common::api::internal::shared::RouterTarget,
RouterVersion = omicron_common::api::internal::shared::RouterVersion,
Sha3_256Digest = sled_agent_types_versions::latest::rot::Sha3_256Digest,
SledRole = sled_agent_types_versions::latest::inventory::SledRole,
SourceNatConfigGeneric = omicron_common::api::internal::shared::SourceNatConfigGeneric,
SwitchLocation = omicron_common::api::external::SwitchLocation,
Expand Down
3 changes: 3 additions & 0 deletions ipcc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ license = "MPL-2.0"
workspace = true

[dependencies]
attest-data.workspace = true
ciborium.workspace = true
hubpack.workspace = true
omicron-common.workspace = true
omicron-uuid-kinds.workspace = true
serde.workspace = true
tufaceous-artifact.workspace = true
thiserror.workspace = true
omicron-workspace-hack.workspace = true
libipcc.workspace = true
x509-cert.workspace = true

[dev-dependencies]
omicron-common = { workspace = true, features = ["testing"] }
Expand Down
139 changes: 132 additions & 7 deletions ipcc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,31 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

// Copyright 2023 Oxide Computer Company
// Copyright 2026 Oxide Computer Company

//! An interface to libipcc (inter-processor communications channel) which
//! currently supports looking up values stored in the SP by key. These
//! values are variously static, passed from the control plane to the SP
//! (through MGS) or set from userland via libipcc.
//! currently supports looking up values stored in the SP by key as well as RoT
//! attestation operations.
//! The looked up SP values are variously static, passed from the control plane
//! to the SP (through MGS) or set from userland via libipcc.

use libipcc::{IpccError, IpccHandle};
use attest_data::messages::{HostToRotCommand, MAX_REQUEST_SIZE, RotToHost};
use attest_data::{Attestation, Log, Nonce32};
use libipcc::IPCC_MAX_DATA_SIZE;
use omicron_uuid_kinds::MupdateUuid;
use serde::Deserialize;
use serde::Serialize;
use thiserror::Error;
use tufaceous_artifact::ArtifactHash;

pub use libipcc::IpccError;

#[cfg(test)]
use proptest::arbitrary::any;
#[cfg(test)]
use proptest::strategy::Strategy;
use x509_cert::PkiPath;
use x509_cert::der::{self, Decode, Reader};

/// Supported keys.
///
Expand Down Expand Up @@ -134,6 +141,18 @@ pub enum InstallinatorImageIdError {
DeserializationFailed(String),
}

#[derive(Debug, Error)]
pub enum AttestError {
#[error(transparent)]
Ipcc(#[from] IpccError),
#[error("failed to send ipcc message to RoT: {0}")]
HostToRot(#[from] attest_data::messages::HostToRotError),
#[error("deserializing {0:?} failed: {1}")]
Deserialize(RotToHost, String),
#[error(transparent)]
Certificate(#[from] x509_cert::der::Error),
}

/// These are the IPCC keys we can look up.
/// NB: These keys match the definitions found in libipcc (RFD 316) and should
/// match the values in `[ipcc::Key]` one-to-one.
Expand All @@ -151,13 +170,13 @@ enum IpccKey {
/// Interface to the inter-processor communications channel.
/// For more information see rfd 316.
pub struct Ipcc {
handle: IpccHandle,
handle: libipcc::IpccHandle,
}

impl Ipcc {
/// Creates a new `Ipcc` instance.
pub fn new() -> Result<Self, IpccError> {
let handle = IpccHandle::new()?;
let handle = libipcc::IpccHandle::new()?;
Ok(Self { handle })
}

Expand All @@ -173,6 +192,112 @@ impl Ipcc {
.map_err(InstallinatorImageIdError::DeserializationFailed)?;
Ok(id)
}

pub fn get_measurement_log(&self) -> Result<Log, AttestError> {
let mut rot_message = vec![0; MAX_REQUEST_SIZE];
let mut rot_resp = vec![0; IPCC_MAX_DATA_SIZE];
// Serializing is infallible
let rot_req = match attest_data::messages::serialize(
&mut rot_message,
&HostToRotCommand::GetMeasurementLog,
|_| 0,
) {
Ok(len) => &rot_message[..len],
Err(err) => unreachable!(
"failed to serialize GetMeasurementLog command: {err}"
),
};
let resp_len = self.handle.rot_request(rot_req, &mut rot_resp)?;
let data = attest_data::messages::parse_response(
&rot_resp[..resp_len],
RotToHost::RotMeasurementLog,
)?;
let log = match hubpack::deserialize(data) {
Ok((log, _)) => log,
Err(err) => {
return Err(AttestError::Deserialize(
RotToHost::RotMeasurementLog,
format!("{err}"),
));
}
};
Ok(log)
}

pub fn get_certificates(&self) -> Result<PkiPath, AttestError> {
let mut rot_message = vec![0; MAX_REQUEST_SIZE];
let mut rot_resp = vec![0; IPCC_MAX_DATA_SIZE];
// Serializing is infallible
let rot_req = match attest_data::messages::serialize(
&mut rot_message,
&HostToRotCommand::GetCertificates,
|_| 0,
) {
Ok(len) => &rot_message[..len],
Err(err) => unreachable!(
"failed to serialize GetCertificates command: {err}"
),
};
let resp_len = self.handle.rot_request(rot_req, &mut rot_resp)?;
let data = attest_data::messages::parse_response(
&rot_resp[..resp_len],
RotToHost::RotCertificates,
)?;

// The returned payload is the DER encoded certificate chain itself
// which we decode into a more usable `PkiPath`
let mut certs = PkiPath::new();
assert!(data.len() < u32::from(der::Length::MAX) as usize);
let mut reader = der::SliceReader::new(data).unwrap();
while !reader.is_finished() {
let cert = reader
.tlv_bytes()
.and_then(|bytes| x509_cert::Certificate::from_der(bytes))
.map_err(|err| {
AttestError::Deserialize(
RotToHost::RotCertificates,
format!("[{}] {err}", certs.len()),
)
})?;
certs.push(cert);
}

Ok(certs)
}

pub fn attest(&self, nonce: Nonce32) -> Result<Attestation, AttestError> {
let mut rot_message = vec![0; MAX_REQUEST_SIZE];
let mut rot_resp = vec![0; IPCC_MAX_DATA_SIZE];
// Serializing is infallible
let rot_req = match attest_data::messages::serialize(
&mut rot_message,
&HostToRotCommand::Attest,
|buf| {
buf[..Nonce32::LENGTH].copy_from_slice(nonce.as_ref());
Nonce32::LENGTH
},
) {
Ok(len) => &rot_message[..len],
Err(err) => {
unreachable!("failed to serialize Attest command: {err}")
}
};
let resp_len = self.handle.rot_request(rot_req, &mut rot_resp)?;
let data = attest_data::messages::parse_response(
&rot_resp[..resp_len],
RotToHost::RotAttestation,
)?;
let attestation = match hubpack::deserialize(data) {
Ok((attestation, _)) => attestation,
Err(err) => {
return Err(AttestError::Deserialize(
RotToHost::RotAttestation,
format!("{err}"),
));
}
};
Ok(attestation)
}
}

#[cfg(test)]
Expand Down
Loading
Loading