use std::{borrow::Cow, sync::LazyLock};
use cfg_if::cfg_if;
#[cfg(not(target_env = "sgx"))]
use lexe_byte_array::ByteArray;
#[cfg(not(target_env = "sgx"))]
use lexe_hex::hex;
use lexe_std::array::{self, ArrayExt};
use ring::{
aead::{
AES_256_GCM, Aad, BoundKey, Nonce, NonceSequence, OpeningKey,
SealingKey, UnboundKey,
},
hkdf::{self, HKDF_SHA256},
};
use sgx_isa::{Keyname, Keypolicy};
use crate::{
platform as enclave,
types::{
Error, MachineId, Measurement, MinCpusvn, Sealed, attributes,
miscselect, xfrm,
},
};
pub fn report() -> &'static sgx_isa::Report {
static SELF_REPORT: LazyLock<sgx_isa::Report> = LazyLock::new(|| {
cfg_if! {
if #[cfg(target_env = "sgx")] {
sgx_isa::Report::for_self()
} else {
sgx_isa::Report {
cpusvn: MinCpusvn::CURRENT.to_array(),
miscselect: miscselect::LEXE_FLAGS,
_reserved1: [0; 28],
attributes: sgx_isa::Attributes {
flags: attributes::LEXE_FLAGS_PROD,
xfrm: xfrm::LEXE_FLAGS,
},
mrenclave: enclave::measurement().to_array(),
_reserved2: [0; 32],
mrsigner: enclave::signer().to_array(),
_reserved3: [0; 96],
isvprodid: 0u16,
isvsvn: 0u16,
_reserved4: [0; 60],
reportdata: [0; 64],
keyid: MACHINE_ID_KEY_ID,
mac: [0; 16],
}
}
}
});
&SELF_REPORT
}
pub fn measurement() -> Measurement {
cfg_if! {
if #[cfg(target_env = "sgx")] {
Measurement::new(enclave::report().mrenclave)
} else {
match option_env!("DEV_MEASUREMENT") {
Some(hex) => Measurement::new(hex::decode_const(hex.as_bytes())),
None => Measurement::MOCK_ENCLAVE,
}
}
}
}
pub fn signer() -> Measurement {
cfg_if! {
if #[cfg(target_env = "sgx")] {
Measurement::new(enclave::report().mrsigner)
} else {
Measurement::MOCK_SIGNER
}
}
}
pub fn machine_id() -> MachineId {
cfg_if! {
if #[cfg(target_env = "sgx")] {
use sgx_isa::{Keyname, Keypolicy};
let keyid = MACHINE_ID_KEY_ID;
let keypolicy = Keypolicy::MRSIGNER;
let attribute_mask: u64 = 0;
let xfrm_mask: u64 = 0;
let misc_mask: u32 = 0;
let isvsvn = 0;
let cpusvn = MinCpusvn::CURRENT;
let keyrequest = sgx_isa::Keyrequest {
keyname: Keyname::Seal as _,
keypolicy,
isvsvn,
cpusvn: cpusvn.to_array(),
attributemask: [attribute_mask, xfrm_mask],
miscmask: misc_mask,
keyid,
..Default::default()
};
let bytes =
keyrequest.egetkey().expect("Failed to get machine id");
MachineId::new(bytes)
} else {
MachineId::MOCK
}
}
}
pub fn seal(
random_keyid: [u8; 32],
label: &[u8],
data: Cow<'_, [u8]>,
) -> Result<Sealed<'static>, Error> {
cfg_if! {
if #[cfg(target_env = "sgx")] {
let keyrequest = LxKeyRequest::new_sealing_request(random_keyid);
let mut sealing_key = keyrequest.derive_sealing_key(label)?;
let mut ciphertext = data.into_owned();
let tag = sealing_key
.seal_in_place_separate_tag(Aad::empty(), &mut ciphertext)
.map_err(|_| Error::SealInputTooLarge)?;
ciphertext.extend_from_slice(tag.as_ref());
Ok(Sealed {
keyrequest: keyrequest.as_bytes().to_vec().into(),
ciphertext: Cow::Owned(ciphertext),
})
} else {
let keyrequest = MockKeyRequest::new_sealing_request(random_keyid);
let key = keyrequest.derive_key(label);
let mut ciphertext = data.into_owned();
let nonce = Nonce::assume_unique_for_key([0u8; 12]);
key
.seal_in_place_append_tag(nonce, Aad::empty(), &mut ciphertext)
.map_err(|_| Error::SealInputTooLarge)?;
Ok(Sealed {
keyrequest: keyrequest.as_bytes().to_vec().into(),
ciphertext: Cow::Owned(ciphertext),
})
}
}
}
pub fn unseal(sealed: Sealed<'_>, label: &[u8]) -> Result<Vec<u8>, Error> {
cfg_if! {
if #[cfg(target_env = "sgx")] {
if sealed.ciphertext.len() < Sealed::TAG_LEN {
return Err(Error::UnsealInputTooSmall);
}
let keyrequest =
LxKeyRequest::try_from_bytes(&sealed.keyrequest)?;
let mut unsealing_key = keyrequest.derive_unsealing_key(label)?;
let mut ciphertext = sealed.ciphertext.into_owned();
let plaintext_ref = unsealing_key
.open_in_place(Aad::empty(), &mut ciphertext)
.map_err(|_| Error::UnsealDecryptionError)?;
let plaintext_len = plaintext_ref.len();
ciphertext.truncate(plaintext_len);
Ok(ciphertext)
} else {
let keyrequest =
MockKeyRequest::try_from_bytes(&sealed.keyrequest)?;
let key = keyrequest.derive_key(label);
let nonce = Nonce::assume_unique_for_key([0u8; 12]);
let mut ciphertext = sealed.ciphertext.into_owned();
let plaintext_ref = key
.open_in_place(nonce, Aad::empty(), &mut ciphertext)
.map_err(|_| Error::UnsealDecryptionError)?;
let plaintext_len = plaintext_ref.len();
ciphertext.truncate(plaintext_len);
Ok(ciphertext)
}
}
}
#[cfg_attr(not(target_env = "sgx"), allow(dead_code))]
struct LxKeyRequest(sgx_isa::Keyrequest);
#[cfg(not(target_env = "sgx"))] struct MockKeyRequest {
keyid: [u8; 32],
}
struct OnlyOnce(Option<Nonce>);
const MACHINE_ID_KEY_ID: [u8; 32] = *b"~~~~ LEXE MACHINE ID KEY ID ~~~~";
lexe_std::const_assert_usize_eq!(
LxKeyRequest::TRUNCATED_SIZE,
LxKeyRequest::KEYREQUEST_RESERVED_BYTES_START
);
#[cfg_attr(not(target_env = "sgx"), allow(dead_code))]
impl LxKeyRequest {
const UNPADDED_SIZE: usize = sgx_isa::Keyrequest::UNPADDED_SIZE;
const TRUNCATED_SIZE: usize = 76;
#[allow(dead_code)] const KEYREQUEST_RESERVED_BYTES_START: usize = {
let value = std::mem::MaybeUninit::<sgx_isa::Keyrequest>::uninit();
let base_ptr: *const sgx_isa::Keyrequest = value.as_ptr();
let field_ptr = unsafe { std::ptr::addr_of!((*base_ptr)._reserved2) };
unsafe {
(field_ptr as *const u8).offset_from(base_ptr as *const u8) as usize
}
};
fn new_sealing_request(random_keyid: [u8; 32]) -> Self {
let attribute_mask: u64 = attributes::LEXE_MASK.bits();
let xfrm_mask: u64 = xfrm::LEXE_MASK;
let misc_mask: u32 = miscselect::LEXE_MASK.bits();
let isvsvn = 0;
let cpusvn = MinCpusvn::CURRENT;
Self(sgx_isa::Keyrequest {
keyname: Keyname::Seal as _,
keypolicy: Keypolicy::MRENCLAVE,
isvsvn,
cpusvn: cpusvn.to_array(),
attributemask: [attribute_mask, xfrm_mask],
miscmask: misc_mask,
keyid: random_keyid,
..Default::default()
})
}
fn as_bytes(&self) -> &[u8] {
let bytes: &[u8] = self.0.as_ref();
&bytes[0..Self::TRUNCATED_SIZE]
}
fn try_from_bytes(bytes: &[u8]) -> Result<Self, Error> {
let valid_size = Self::TRUNCATED_SIZE..=Self::UNPADDED_SIZE;
if !valid_size.contains(&bytes.len()) {
return Err(Error::InvalidKeyRequestLength);
}
let mut buf = [0u8; Self::UNPADDED_SIZE];
buf[..bytes.len()].copy_from_slice(bytes);
Ok(Self(
sgx_isa::Keyrequest::try_copy_from(&buf)
.expect("Should never fail"),
))
}
fn single_use_nonce(&self) -> OnlyOnce {
let (_, nonce) = self.0.keyid.rsplit_array_ref_stable::<12>();
OnlyOnce::new(Nonce::assume_unique_for_key(*nonce))
}
fn derive_sealing_key(
&self,
label: &[u8],
) -> Result<SealingKey<OnlyOnce>, Error> {
let key_material = self.derive_key_material()?;
Ok(SealingKey::new(
Self::derive_aesgcm_key(&key_material, label),
self.single_use_nonce(),
))
}
fn derive_unsealing_key(
&self,
label: &[u8],
) -> Result<OpeningKey<OnlyOnce>, Error> {
let key_material = self.derive_key_material()?;
Ok(OpeningKey::new(
Self::derive_aesgcm_key(&key_material, label),
self.single_use_nonce(),
))
}
fn derive_key_material(&self) -> Result<[u8; 16], Error> {
cfg_if! {
if #[cfg(target_env = "sgx")] {
Ok(self.0.egetkey()?)
} else {
unimplemented!()
}
}
}
fn derive_aesgcm_key(key_material: &[u8; 16], label: &[u8]) -> UnboundKey {
const HKDF_SALT: [u8; 32] = array::pad(*b"LEXE-REALM::SgxSealing");
UnboundKey::from(
hkdf::Salt::new(HKDF_SHA256, &HKDF_SALT)
.extract(key_material.as_slice())
.expand(&[label], &AES_256_GCM)
.expect("Failed to derive sealing key from key material"),
)
}
}
#[cfg(not(target_env = "sgx"))] impl MockKeyRequest {
fn new_sealing_request(random_keyid: [u8; 32]) -> Self {
Self {
keyid: random_keyid,
}
}
fn as_bytes(&self) -> &[u8] {
self.keyid.as_slice()
}
fn try_from_bytes(bytes: &[u8]) -> Result<Self, Error> {
let keyid = <[u8; 32]>::try_from(bytes)
.map_err(|_| Error::InvalidKeyRequestLength)?;
Ok(Self { keyid })
}
fn derive_key(&self, label: &[u8]) -> ring::aead::LessSafeKey {
ring::aead::LessSafeKey::new(UnboundKey::from(
hkdf::Salt::new(HKDF_SHA256, &[0x42; 32])
.extract(self.keyid.as_slice())
.expand(&[label], &AES_256_GCM)
.expect("Failed to derive sealing key from key material"),
))
}
}
impl OnlyOnce {
fn new(nonce: Nonce) -> Self {
Self(Some(nonce))
}
}
impl NonceSequence for OnlyOnce {
fn advance(&mut self) -> Result<Nonce, ring::error::Unspecified> {
Ok(self
.0
.take()
.expect("sealed / unseal more than once with the same key"))
}
}
#[cfg(test)]
mod test {
use proptest::{arbitrary::any, prop_assume, proptest, strategy::Strategy};
use ring::aead::AES_256_GCM;
use super::*;
#[test]
fn test_measurement_consistent() {
let m1 = enclave::measurement();
let m2 = enclave::measurement();
assert_eq!(m1, m2);
}
#[test]
fn test_machine_id_consistent() {
let m1 = enclave::machine_id();
let m2 = enclave::machine_id();
assert_eq!(m1, m2);
}
#[test]
fn test_sealing_roundtrip_basic() {
let random_keyid = [0x69; 32];
let sealed =
enclave::seal(random_keyid, b"", b"".as_slice().into()).unwrap();
let unsealed = enclave::unseal(sealed, b"").unwrap();
assert_eq!(&unsealed, b"");
let sealed = enclave::seal(
random_keyid,
b"cool label",
b"cool data".as_slice().into(),
)
.unwrap();
let unsealed = enclave::unseal(sealed, b"cool label").unwrap();
assert_eq!(&unsealed, b"cool data");
}
#[test]
fn test_sealing_roundtrip_proptest() {
let arb_label = any::<Vec<u8>>();
let arb_data = any::<Vec<u8>>();
proptest!(|(
random_keyid in any::<[u8; 32]>(),
label in arb_label,
data in arb_data,
)| {
let sealed = enclave::seal(random_keyid, &label, data.clone().into()).unwrap();
let unsealed = enclave::unseal(sealed, &label).unwrap();
assert_eq!(&data, &unsealed);
});
}
#[test]
fn test_sealing_detects_ciphertext_change() {
let arb_label = any::<Vec<u8>>();
let arb_data = any::<Vec<u8>>();
let arb_mutation = any::<Vec<u8>>()
.prop_filter("can't be empty or all zeroes", |m| {
!m.is_empty() && !m.iter().all(|x| x == &0u8)
});
proptest!(|(
random_keyid in any::<[u8; 32]>(),
label in arb_label,
data in arb_data,
mutation in arb_mutation,
)| {
let sealed = enclave::seal(random_keyid, &label, data.into()).unwrap();
let keyrequest = sealed.keyrequest;
let ciphertext_original = sealed.ciphertext.into_owned();
let mut ciphertext = ciphertext_original.clone();
for (c, m) in ciphertext.iter_mut().zip(mutation.iter()) {
*c ^= m;
}
prop_assume!(ciphertext != ciphertext_original);
let sealed = Sealed {
keyrequest,
ciphertext: ciphertext.into(),
};
enclave::unseal(sealed, &label).unwrap_err();
});
}
#[test]
fn test_constants() {
assert_eq!(AES_256_GCM.tag_len(), Sealed::TAG_LEN);
}
}