use crate::errors::SdkError;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SealMode {
MrEnclave,
MrSigner,
}
pub fn seal_to_file(data: &[u8], path: &Path, mode: SealMode) -> Result<(), SdkError> {
if is_enclave_mode() {
seal_sgx(data, path, mode)
} else {
seal_dev(data, path, mode)
}
}
pub fn unseal_from_file(path: &Path, mode: SealMode) -> Result<Vec<u8>, SdkError> {
if is_enclave_mode() {
unseal_sgx(path, mode)
} else {
unseal_dev(path, mode)
}
}
fn is_enclave_mode() -> bool {
std::env::var("GUARANTEE_ENCLAVE")
.map(|v| v == "1")
.unwrap_or(false)
}
fn seal_dev(data: &[u8], path: &Path, mode: SealMode) -> Result<(), SdkError> {
let header: &[u8] = match mode {
SealMode::MrEnclave => b"SEAL_MRENCLAVE_DEV\n",
SealMode::MrSigner => b"SEAL_MRSIGNER_DEV\n",
};
let mut sealed = header.to_vec();
sealed.extend_from_slice(data);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| SdkError::SealError(format!("Create dir: {e}")))?;
}
std::fs::write(path, &sealed)
.map_err(|e| SdkError::SealError(format!("Write sealed: {e}")))?;
tracing::debug!(path = %path.display(), ?mode, "Sealed data (dev mode)");
Ok(())
}
fn unseal_dev(path: &Path, mode: SealMode) -> Result<Vec<u8>, SdkError> {
let sealed = std::fs::read(path)
.map_err(|e| SdkError::SealError(format!("Read sealed: {e}")))?;
let expected_header: &[u8] = match mode {
SealMode::MrEnclave => b"SEAL_MRENCLAVE_DEV\n",
SealMode::MrSigner => b"SEAL_MRSIGNER_DEV\n",
};
if !sealed.starts_with(expected_header) {
return Err(SdkError::SealError(format!(
"Seal mode mismatch: expected {:?}",
mode
)));
}
let data = sealed[expected_header.len()..].to_vec();
tracing::debug!(path = %path.display(), ?mode, "Unsealed data (dev mode)");
Ok(data)
}
fn seal_sgx(data: &[u8], path: &Path, mode: SealMode) -> Result<(), SdkError> {
tracing::warn!("SGX sealing not yet implemented -- using dev mode fallback");
seal_dev(data, path, mode)
}
fn unseal_sgx(path: &Path, mode: SealMode) -> Result<Vec<u8>, SdkError> {
tracing::warn!("SGX unsealing not yet implemented -- using dev mode fallback");
unseal_dev(path, mode)
}
pub fn sign_with_enclave_key(
signing_key: &ed25519_dalek::SigningKey,
body: &[u8],
request_id: &str,
) -> crate::AttestationHeader {
use base64::Engine;
use ed25519_dalek::Signer;
use sha2::{Digest, Sha256};
use crate::response::hex_encode;
let timestamp_ms = chrono::Utc::now().timestamp_millis() as u64;
let mut hasher = Sha256::new();
hasher.update(body);
hasher.update(timestamp_ms.to_be_bytes());
hasher.update(request_id.as_bytes());
let payload_hash: [u8; 32] = hasher.finalize().into();
let signature = signing_key.sign(&payload_hash);
crate::AttestationHeader {
version: 1,
signature_b64: base64::engine::general_purpose::STANDARD.encode(signature.to_bytes()),
payload_hash_hex: hex_encode(&payload_hash),
timestamp_ms,
public_key_hex: hex_encode(signing_key.verifying_key().as_bytes()),
}
}
pub mod signing_key_serde {
use ed25519_dalek::SigningKey;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(key: &SigningKey, s: S) -> Result<S::Ok, S::Error> {
key.to_bytes().serialize(s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<SigningKey, D::Error> {
let bytes = <[u8; 32]>::deserialize(d)?;
Ok(SigningKey::from_bytes(&bytes))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seal_unseal_roundtrip_mrenclave() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("test.sealed");
let data = b"hello enclave";
seal_to_file(data, &path, SealMode::MrEnclave).expect("seal");
let recovered = unseal_from_file(&path, SealMode::MrEnclave).expect("unseal");
assert_eq!(recovered, data);
}
#[test]
fn seal_unseal_roundtrip_mrsigner() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("test.sealed");
let data = b"hello signer";
seal_to_file(data, &path, SealMode::MrSigner).expect("seal");
let recovered = unseal_from_file(&path, SealMode::MrSigner).expect("unseal");
assert_eq!(recovered, data);
}
#[test]
fn unseal_wrong_mode_fails() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("test.sealed");
let data = b"secret";
seal_to_file(data, &path, SealMode::MrEnclave).expect("seal");
let result = unseal_from_file(&path, SealMode::MrSigner);
assert!(result.is_err());
}
#[test]
fn unseal_missing_file_fails() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("nonexistent.sealed");
let result = unseal_from_file(&path, SealMode::MrEnclave);
assert!(result.is_err());
}
#[test]
fn signing_key_serde_roundtrip() {
use rand::rngs::OsRng;
let key = ed25519_dalek::SigningKey::generate(&mut OsRng);
let serialized = serde_json::to_vec(&SigningKeyWrapper { key: key.clone() })
.expect("serialize");
let deserialized: SigningKeyWrapper =
serde_json::from_slice(&serialized).expect("deserialize");
assert_eq!(key.to_bytes(), deserialized.key.to_bytes());
}
#[derive(serde::Serialize, serde::Deserialize)]
struct SigningKeyWrapper {
#[serde(with = "super::signing_key_serde")]
key: ed25519_dalek::SigningKey,
}
}