#[cfg(feature = "test-utils")]
use std::sync::LazyLock;
use lexe_byte_array::ByteArray;
use crate::types::Measurement;
pub fn version() -> Option<&'static str> {
cfg_if::cfg_if! {
if #[cfg(feature = "test-utils")] {
DEV_IDENTITY.version()
} else {
None
}
}
}
#[cfg(not(target_env = "sgx"))]
pub(crate) fn measurement() -> Option<Measurement> {
cfg_if::cfg_if! {
if #[cfg(feature = "test-utils")] {
DEV_IDENTITY.measurement()
} else {
None
}
}
}
#[cfg(feature = "test-utils")]
static DEV_IDENTITY: LazyLock<Identity> =
LazyLock::new(Identity::read_patch_slot);
pub struct Identity([u8; Self::LEN]);
impl Identity {
pub const LEN: usize = 64;
pub const PLACEHOLDER: Identity = Identity(
*b"\xffLEXE_DEV_ENCLAVE_IDENTITY_PATCH_SLOT_REPLACE_ME__0123456789ABCD",
);
pub const SECTION_ELF: &'static str = ".lexe_dev_id";
pub const SECTION_MACHO: &'static str = "__lexe_dev_id";
const MEASUREMENT_OFFSET: usize = 32;
const VERSION_MAX_LEN: usize = Self::MEASUREMENT_OFFSET - 1;
const VERSION_OFFSET: usize = 1;
pub fn encode(version: &str, measurement: Measurement) -> Self {
assert!(!version.is_empty(), "Dev version is empty");
let max_len = Self::VERSION_MAX_LEN;
assert!(
version.len() <= max_len,
"Dev version exceeds {max_len} bytes: {version}"
);
let mut encoded = [0; Self::LEN];
encoded[0] = version.len() as u8;
encoded[Self::VERSION_OFFSET..][..version.len()]
.copy_from_slice(version.as_bytes());
encoded[Self::MEASUREMENT_OFFSET..]
.copy_from_slice(measurement.as_slice());
Self(encoded)
}
pub fn as_bytes(&self) -> &[u8; Self::LEN] {
&self.0
}
}
#[cfg(any(test, feature = "test-utils"))]
impl Identity {
const UNPATCHED_VERSION_LEN: u8 = 0xff;
fn version(&self) -> Option<&str> {
self.is_patched().then(|| {
let version_len = usize::from(self.0[0]);
assert!(
(1..=Self::VERSION_MAX_LEN).contains(&version_len),
"Invalid patched dev version length: {version_len}"
);
std::str::from_utf8(&self.0[Self::VERSION_OFFSET..][..version_len])
.expect("Patched dev version is not UTF-8")
})
}
#[cfg(any(test, not(target_env = "sgx")))]
fn measurement(&self) -> Option<Measurement> {
self.is_patched().then(|| {
let measurement: [u8; 32] =
self.0[Self::MEASUREMENT_OFFSET..].try_into().unwrap();
Measurement::new(measurement)
})
}
fn is_patched(&self) -> bool {
self.0[0] != Self::UNPATCHED_VERSION_LEN
}
#[cfg(feature = "test-utils")] #[inline(never)]
fn read_patch_slot() -> Self {
#[used]
#[cfg_attr(
not(target_vendor = "apple"),
unsafe(link_section = ".lexe_dev_id")
)]
#[cfg_attr(
target_vendor = "apple",
unsafe(link_section = "__DATA,__lexe_dev_id")
)]
static mut PATCH_SLOT: [u8; Identity::LEN] = Identity::PLACEHOLDER.0;
Self(unsafe { std::ptr::read(&raw mut PATCH_SLOT) })
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn encode_roundtrip() {
let version = "0.0.0-dev.1";
let measurement = Measurement::new([42; 32]);
let id = Identity::encode(version, measurement);
assert_eq!(id.version(), Some(version));
assert_eq!(id.measurement(), Some(measurement));
}
#[test]
fn unpatched_placeholder() {
assert!(Identity::PLACEHOLDER.version().is_none());
assert!(Identity::PLACEHOLDER.measurement().is_none());
}
#[cfg(feature = "test-utils")]
#[test]
fn unpatched_slot() {
assert!(version().is_none());
}
}