1use std::path::{Path, PathBuf};
4
5use a3s_box_core::error::{BoxError, Result};
6use tokio::io::{AsyncReadExt, AsyncWriteExt};
7use tokio::net::UnixStream;
8
9use crate::tee::attestation::{AttestationReport, AttestationRequest};
10
11#[derive(Debug)]
16pub struct AttestationClient {
17 socket_path: PathBuf,
18}
19
20impl AttestationClient {
21 pub async fn connect(socket_path: &Path) -> Result<Self> {
23 let _stream = UnixStream::connect(socket_path).await.map_err(|e| {
24 BoxError::AttestationError(format!(
25 "Failed to connect to agent at {}: {}",
26 socket_path.display(),
27 e,
28 ))
29 })?;
30
31 Ok(Self {
32 socket_path: socket_path.to_path_buf(),
33 })
34 }
35
36 pub fn socket_path(&self) -> &Path {
38 &self.socket_path
39 }
40
41 pub async fn get_report(&self, request: &AttestationRequest) -> Result<AttestationReport> {
54 let body = serde_json::to_string(request).map_err(|e| {
55 BoxError::AttestationError(format!("Failed to serialize attestation request: {}", e))
56 })?;
57
58 let http_request = format!(
59 "POST /attest HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
60 body.len(),
61 body,
62 );
63
64 let mut stream = UnixStream::connect(&self.socket_path).await.map_err(|e| {
65 BoxError::AttestationError(format!(
66 "Attestation connection failed to {}: {}",
67 self.socket_path.display(),
68 e,
69 ))
70 })?;
71
72 stream
73 .write_all(http_request.as_bytes())
74 .await
75 .map_err(|e| {
76 BoxError::AttestationError(format!("Attestation request write failed: {}", e))
77 })?;
78
79 let mut response = Vec::with_capacity(8192);
81 let mut buf = vec![0u8; 8192];
82 loop {
83 let n = stream.read(&mut buf).await.map_err(|e| {
84 BoxError::AttestationError(format!("Attestation response read failed: {}", e))
85 })?;
86 if n == 0 {
87 break;
88 }
89 response.extend_from_slice(&buf[..n]);
90 if response.len() > 1024 * 1024 {
92 break;
93 }
94 }
95
96 let response_str = String::from_utf8_lossy(&response);
97
98 let body_str = response_str
100 .find("\r\n\r\n")
101 .map(|pos| &response_str[pos + 4..])
102 .ok_or_else(|| {
103 BoxError::AttestationError(
104 "Malformed attestation response: no HTTP body".to_string(),
105 )
106 })?;
107
108 if !response_str.starts_with("HTTP/1.1 200") && !response_str.starts_with("HTTP/1.0 200") {
110 return Err(BoxError::AttestationError(format!(
111 "Attestation request failed: {}",
112 body_str.chars().take(200).collect::<String>(),
113 )));
114 }
115
116 let report: AttestationReport = serde_json::from_str(body_str).map_err(|e| {
117 BoxError::AttestationError(format!("Failed to parse attestation response: {}", e))
118 })?;
119
120 Ok(report)
121 }
122}
123
124async fn connect_ratls(
129 socket_path: &Path,
130 policy: crate::tee::AttestationPolicy,
131 allow_simulated: bool,
132) -> Result<tokio_rustls::client::TlsStream<UnixStream>> {
133 let client_config = crate::tee::ratls::create_client_config(policy, allow_simulated)?;
134 let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(client_config));
135
136 let stream = UnixStream::connect(socket_path).await.map_err(|e| {
137 BoxError::AttestationError(format!(
138 "Failed to connect to RA-TLS server at {}: {}",
139 socket_path.display(),
140 e,
141 ))
142 })?;
143
144 let server_name = rustls::pki_types::ServerName::try_from("localhost")
145 .map_err(|e| BoxError::AttestationError(format!("Invalid server name: {}", e)))?;
146
147 connector
148 .connect(server_name, stream)
149 .await
150 .map_err(|e| BoxError::AttestationError(format!("RA-TLS handshake failed: {}", e)))
151}
152
153#[derive(Debug)]
162pub struct RaTlsAttestationClient {
163 socket_path: PathBuf,
164}
165
166#[derive(Debug, Clone)]
168pub(crate) struct RaTlsAttestationEvidence {
169 pub(crate) report: AttestationReport,
170 #[cfg_attr(not(target_os = "linux"), allow(dead_code))]
173 pub(crate) certificate_der: Vec<u8>,
174}
175
176impl RaTlsAttestationClient {
177 pub fn new(socket_path: &Path) -> Self {
179 Self {
180 socket_path: socket_path.to_path_buf(),
181 }
182 }
183
184 pub fn socket_path(&self) -> &Path {
186 &self.socket_path
187 }
188
189 pub async fn verify(
199 &self,
200 policy: crate::tee::AttestationPolicy,
201 allow_simulated: bool,
202 ) -> Result<crate::tee::VerificationResult> {
203 use a3s_box_core::tee::{AttestRequest, AttestRoute};
204
205 let mut tls_stream = connect_ratls(&self.socket_path, policy, allow_simulated).await?;
206
207 let req = AttestRequest {
209 route: AttestRoute::Status,
210 payload: serde_json::Value::Null,
211 };
212 let payload = serde_json::to_vec(&req).map_err(|e| {
213 BoxError::AttestationError(format!("Failed to serialize status request: {}", e))
214 })?;
215 write_tls_frame(&mut tls_stream, 0x01, &payload).await?;
216
217 let _response = read_tls_frame(&mut tls_stream).await?;
219
220 let (_, tls_conn) = tls_stream.get_ref();
222 let peer_certs = tls_conn.peer_certificates();
223
224 if let Some(certs) = peer_certs {
225 if let Some(cert) = certs.first() {
226 let report = crate::tee::ratls::extract_report_from_cert(cert.as_ref())?;
227 let nonce = if report.report.len() >= 0x90 {
228 &report.report[0x50..0x90]
229 } else {
230 &[]
231 };
232 return crate::tee::verify_attestation(
233 &report,
234 nonce,
235 &crate::tee::AttestationPolicy::default(),
236 allow_simulated,
237 );
238 }
239 }
240
241 Ok(crate::tee::VerificationResult {
244 verified: true,
245 platform: crate::tee::PlatformInfo::default(),
246 policy_result: crate::tee::PolicyResult {
247 passed: true,
248 violations: vec![],
249 },
250 signature_valid: true,
251 cert_chain_valid: true,
252 nonce_valid: true,
253 report_age_valid: true,
254 failures: vec![],
255 })
256 }
257
258 pub async fn fetch_report(&self, allow_simulated: bool) -> Result<AttestationReport> {
265 self.fetch_report_with_policy(crate::tee::AttestationPolicy::default(), allow_simulated)
266 .await
267 }
268
269 pub async fn fetch_report_with_policy(
271 &self,
272 policy: crate::tee::AttestationPolicy,
273 allow_simulated: bool,
274 ) -> Result<AttestationReport> {
275 Ok(self
276 .fetch_evidence_with_policy(policy, allow_simulated)
277 .await?
278 .report)
279 }
280
281 pub(crate) async fn fetch_evidence_with_policy(
284 &self,
285 policy: crate::tee::AttestationPolicy,
286 allow_simulated: bool,
287 ) -> Result<RaTlsAttestationEvidence> {
288 use a3s_box_core::tee::{AttestRequest, AttestRoute};
289
290 let mut tls_stream = connect_ratls(&self.socket_path, policy, allow_simulated).await?;
291
292 let req = AttestRequest {
295 route: AttestRoute::Status,
296 payload: serde_json::Value::Null,
297 };
298 let payload = serde_json::to_vec(&req).map_err(|e| {
299 BoxError::AttestationError(format!("Failed to serialize status request: {}", e))
300 })?;
301 write_tls_frame(&mut tls_stream, 0x01, &payload).await?;
302 let _ = read_tls_frame(&mut tls_stream).await?;
303
304 let (_, tls_conn) = tls_stream.get_ref();
305 let cert = tls_conn
306 .peer_certificates()
307 .and_then(|certs| certs.first())
308 .ok_or_else(|| {
309 BoxError::AttestationError(
310 "RA-TLS server presented no certificate to extract a report from".to_string(),
311 )
312 })?;
313 let certificate_der = cert.as_ref().to_vec();
314 let report = crate::tee::ratls::extract_report_from_cert(&certificate_der)?;
315 Ok(RaTlsAttestationEvidence {
316 report,
317 certificate_der,
318 })
319 }
320}
321
322#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
324pub struct SecretEntry {
325 pub name: String,
327 pub value: String,
329 #[serde(default = "default_true")]
331 pub set_env: bool,
332}
333
334fn default_true() -> bool {
335 true
336}
337
338#[derive(Debug, Clone, serde::Deserialize)]
340pub struct SecretInjectionResult {
341 pub injected: usize,
343 #[serde(default)]
345 pub errors: Vec<String>,
346}
347
348#[derive(Debug)]
355pub struct SecretInjector {
356 socket_path: PathBuf,
357}
358
359impl SecretInjector {
360 pub fn new(socket_path: &Path) -> Self {
362 Self {
363 socket_path: socket_path.to_path_buf(),
364 }
365 }
366
367 pub async fn inject(
379 &self,
380 secrets: &[SecretEntry],
381 policy: crate::tee::AttestationPolicy,
382 allow_simulated: bool,
383 ) -> Result<SecretInjectionResult> {
384 use a3s_box_core::tee::{AttestRequest, AttestRoute};
385
386 if secrets.is_empty() {
387 return Ok(SecretInjectionResult {
388 injected: 0,
389 errors: vec![],
390 });
391 }
392
393 let mut tls_stream = connect_ratls(&self.socket_path, policy, allow_simulated).await?;
395
396 let req = AttestRequest {
398 route: AttestRoute::Secrets,
399 payload: serde_json::json!({ "secrets": secrets }),
400 };
401 let payload = serde_json::to_vec(&req).map_err(|e| {
402 BoxError::AttestationError(format!("Failed to serialize secrets request: {}", e))
403 })?;
404 write_tls_frame(&mut tls_stream, 0x01, &payload).await?;
405
406 let (frame_type, response_data) = read_tls_frame(&mut tls_stream).await?;
408
409 if frame_type == 0x04 {
410 let msg = String::from_utf8_lossy(&response_data);
411 return Err(BoxError::AttestationError(format!(
412 "Secret injection failed: {}",
413 msg,
414 )));
415 }
416
417 let result: SecretInjectionResult =
418 serde_json::from_slice(&response_data).map_err(|e| {
419 BoxError::AttestationError(format!("Failed to parse injection response: {}", e))
420 })?;
421
422 Ok(result)
423 }
424}
425
426#[derive(Debug, Clone, serde::Deserialize)]
428pub struct SealResult {
429 pub blob: String,
431 pub policy: String,
433 pub context: String,
435}
436
437#[derive(Debug, Clone, serde::Deserialize)]
439pub struct UnsealResult {
440 pub data: String,
442}
443
444#[derive(Debug)]
451pub struct SealClient {
452 socket_path: PathBuf,
453}
454
455impl SealClient {
456 pub fn new(socket_path: &Path) -> Self {
458 Self {
459 socket_path: socket_path.to_path_buf(),
460 }
461 }
462
463 pub async fn seal(
477 &self,
478 data: &[u8],
479 context: &str,
480 policy: &str,
481 attestation_policy: crate::tee::AttestationPolicy,
482 allow_simulated: bool,
483 ) -> Result<SealResult> {
484 use a3s_box_core::tee::{AttestRequest, AttestRoute};
485 use base64::Engine;
486
487 let mut tls_stream =
488 connect_ratls(&self.socket_path, attestation_policy, allow_simulated).await?;
489
490 let req = AttestRequest {
491 route: AttestRoute::Seal,
492 payload: serde_json::json!({
493 "data": base64::engine::general_purpose::STANDARD.encode(data),
494 "context": context,
495 "policy": policy,
496 }),
497 };
498 let payload = serde_json::to_vec(&req).map_err(|e| {
499 BoxError::AttestationError(format!("Failed to serialize seal request: {}", e))
500 })?;
501 write_tls_frame(&mut tls_stream, 0x01, &payload).await?;
502
503 let (frame_type, response_data) = read_tls_frame(&mut tls_stream).await?;
504
505 if frame_type == 0x04 {
506 let msg = String::from_utf8_lossy(&response_data);
507 return Err(BoxError::AttestationError(format!(
508 "Seal request failed: {}",
509 msg,
510 )));
511 }
512
513 let result: SealResult = serde_json::from_slice(&response_data).map_err(|e| {
514 BoxError::AttestationError(format!("Failed to parse seal response: {}", e))
515 })?;
516
517 Ok(result)
518 }
519
520 pub async fn unseal(
534 &self,
535 blob: &str,
536 context: &str,
537 policy: &str,
538 attestation_policy: crate::tee::AttestationPolicy,
539 allow_simulated: bool,
540 ) -> Result<Vec<u8>> {
541 use a3s_box_core::tee::{AttestRequest, AttestRoute};
542 use base64::Engine;
543
544 let mut tls_stream =
545 connect_ratls(&self.socket_path, attestation_policy, allow_simulated).await?;
546
547 let req = AttestRequest {
548 route: AttestRoute::Unseal,
549 payload: serde_json::json!({
550 "blob": blob,
551 "context": context,
552 "policy": policy,
553 }),
554 };
555 let payload = serde_json::to_vec(&req).map_err(|e| {
556 BoxError::AttestationError(format!("Failed to serialize unseal request: {}", e))
557 })?;
558 write_tls_frame(&mut tls_stream, 0x01, &payload).await?;
559
560 let (frame_type, response_data) = read_tls_frame(&mut tls_stream).await?;
561
562 if frame_type == 0x04 {
563 let msg = String::from_utf8_lossy(&response_data);
564 return Err(BoxError::AttestationError(format!(
565 "Unseal request failed: {}",
566 msg,
567 )));
568 }
569
570 let result: UnsealResult = serde_json::from_slice(&response_data).map_err(|e| {
571 BoxError::AttestationError(format!("Failed to parse unseal response: {}", e))
572 })?;
573
574 let plaintext = base64::engine::general_purpose::STANDARD
575 .decode(&result.data)
576 .map_err(|e| {
577 BoxError::AttestationError(format!("Failed to decode unsealed data: {}", e))
578 })?;
579
580 Ok(plaintext)
581 }
582}
583
584async fn write_tls_frame<S>(stream: &mut S, frame_type: u8, payload: &[u8]) -> Result<()>
591where
592 S: tokio::io::AsyncWriteExt + Unpin,
593{
594 let len = payload.len() as u32;
595 let mut header = [0u8; 5];
596 header[0] = frame_type;
597 header[1..5].copy_from_slice(&len.to_be_bytes());
598 stream
599 .write_all(&header)
600 .await
601 .map_err(|e| BoxError::AttestationError(format!("TLS frame header write failed: {}", e)))?;
602 if !payload.is_empty() {
603 stream.write_all(payload).await.map_err(|e| {
604 BoxError::AttestationError(format!("TLS frame payload write failed: {}", e))
605 })?;
606 }
607 Ok(())
608}
609
610async fn read_tls_frame<S>(stream: &mut S) -> Result<(u8, Vec<u8>)>
613where
614 S: tokio::io::AsyncReadExt + Unpin,
615{
616 let mut header = [0u8; 5];
617 match stream.read_exact(&mut header).await {
618 Ok(_) => {}
619 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
620 tracing::debug!("RA-TLS peer closed without sending response frame");
621 return Ok((0x01, Vec::new()));
622 }
623 Err(e) => {
624 return Err(BoxError::AttestationError(format!(
625 "TLS frame header read failed: {}",
626 e
627 )));
628 }
629 }
630 let frame_type = header[0];
631 let len = u32::from_be_bytes([header[1], header[2], header[3], header[4]]) as usize;
632 let mut payload = vec![0u8; len];
633 if len > 0 {
634 stream.read_exact(&mut payload).await.map_err(|e| {
635 BoxError::AttestationError(format!("TLS frame payload read failed: {}", e))
636 })?;
637 }
638 Ok((frame_type, payload))
639}
640
641#[cfg(test)]
642mod tests {
643 use super::*;
644 use crate::tee::attestation::{CertificateChain, PlatformInfo};
645 use tokio::io::{AsyncReadExt, AsyncWriteExt};
646 use tokio::net::UnixListener;
647
648 fn bind_test_listener(path: &Path) -> Option<UnixListener> {
649 match UnixListener::bind(path) {
650 Ok(listener) => Some(listener),
651 Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
652 eprintln!(
653 "skipping Unix socket test; sandbox denied bind at {}: {}",
654 path.display(),
655 e
656 );
657 None
658 }
659 Err(e) => panic!("failed to bind test socket {}: {}", path.display(), e),
660 }
661 }
662
663 fn test_report() -> AttestationReport {
664 AttestationReport {
665 report: vec![1, 2, 3, 4],
666 cert_chain: CertificateChain::default(),
667 platform: PlatformInfo::default(),
668 }
669 }
670
671 async fn spawn_attestation_http_server(listener: UnixListener, response: Vec<u8>) -> Vec<u8> {
672 let (mut stream, _) = listener.accept().await.unwrap();
673 let mut request = Vec::new();
674 let mut buf = [0u8; 1024];
675 loop {
676 let n = stream.read(&mut buf).await.unwrap();
677 if n == 0 {
678 break;
679 }
680 request.extend_from_slice(&buf[..n]);
681 if request.windows(4).any(|w| w == b"\r\n\r\n") {
682 break;
683 }
684 }
685 stream.write_all(&response).await.unwrap();
686 request
687 }
688
689 #[tokio::test]
690 async fn test_attestation_connect_nonexistent_socket() {
691 let result =
692 AttestationClient::connect(Path::new("/tmp/nonexistent-a3s-attest-test.sock")).await;
693 assert!(result.is_err());
694 let err = result.unwrap_err();
695 assert!(matches!(err, BoxError::AttestationError(_)));
696 }
697
698 #[tokio::test]
699 async fn test_attestation_connect_and_socket_path() {
700 let tmp = tempfile::TempDir::new().unwrap();
701 let sock_path = tmp.path().join("attest.sock");
702 let Some(_listener) = bind_test_listener(&sock_path) else {
703 return;
704 };
705
706 let client = AttestationClient::connect(&sock_path).await.unwrap();
707 assert_eq!(client.socket_path(), sock_path);
708 }
709
710 #[tokio::test]
711 async fn test_attestation_get_report_parses_success_response() {
712 let tmp = tempfile::TempDir::new().unwrap();
713 let sock_path = tmp.path().join("attest_success.sock");
714 let Some(listener) = bind_test_listener(&sock_path) else {
715 return;
716 };
717
718 let body = serde_json::to_vec(&test_report()).unwrap();
719 let response = format!(
720 "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
721 body.len(),
722 String::from_utf8(body).unwrap()
723 )
724 .into_bytes();
725 let server = tokio::spawn(spawn_attestation_http_server(listener, response));
726
727 let client = AttestationClient {
728 socket_path: sock_path,
729 };
730 let report = client
731 .get_report(&AttestationRequest {
732 nonce: vec![9, 8, 7],
733 user_data: None,
734 })
735 .await
736 .unwrap();
737
738 assert_eq!(report.report, vec![1, 2, 3, 4]);
739 let request = server.await.unwrap();
740 let request = String::from_utf8_lossy(&request);
741 assert!(request.starts_with("POST /attest HTTP/1.1\r\n"));
742 assert!(request.contains("Content-Type: application/json\r\n"));
743 assert!(request.contains("Content-Length:"));
744 }
745
746 #[tokio::test]
747 async fn test_attestation_get_report_surfaces_http_error_body() {
748 let tmp = tempfile::TempDir::new().unwrap();
749 let sock_path = tmp.path().join("attest_error.sock");
750 let Some(listener) = bind_test_listener(&sock_path) else {
751 return;
752 };
753
754 let response =
755 b"HTTP/1.1 500 Internal Server Error\r\nContent-Length: 12\r\n\r\nbad hardware"
756 .to_vec();
757 let server = tokio::spawn(spawn_attestation_http_server(listener, response));
758
759 let client = AttestationClient {
760 socket_path: sock_path,
761 };
762 let err = client
763 .get_report(&AttestationRequest {
764 nonce: vec![],
765 user_data: None,
766 })
767 .await
768 .unwrap_err();
769
770 assert!(matches!(err, BoxError::AttestationError(_)));
771 assert!(err.to_string().contains("bad hardware"));
772 let _ = server.await.unwrap();
773 }
774
775 #[tokio::test]
776 async fn test_attestation_get_report_rejects_malformed_and_invalid_json() {
777 for (name, response, expected) in [
778 (
779 "no_body",
780 b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n".to_vec(),
781 "no HTTP body",
782 ),
783 (
784 "bad_json",
785 b"HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\nnot-json".to_vec(),
786 "Failed to parse attestation response",
787 ),
788 ] {
789 let tmp = tempfile::TempDir::new().unwrap();
790 let sock_path = tmp.path().join(format!("{name}.sock"));
791 let Some(listener) = bind_test_listener(&sock_path) else {
792 return;
793 };
794 let server = tokio::spawn(spawn_attestation_http_server(listener, response));
795
796 let client = AttestationClient {
797 socket_path: sock_path,
798 };
799 let err = client
800 .get_report(&AttestationRequest {
801 nonce: vec![],
802 user_data: None,
803 })
804 .await
805 .unwrap_err();
806
807 assert!(err.to_string().contains(expected), "{err}");
808 let _ = server.await.unwrap();
809 }
810 }
811
812 #[tokio::test]
813 async fn test_tls_frame_write_and_read_roundtrip() {
814 let (mut client, mut server) = tokio::io::duplex(64);
815
816 let writer = tokio::spawn(async move {
817 write_tls_frame(&mut client, 0x03, b"payload")
818 .await
819 .unwrap();
820 write_tls_frame(&mut client, 0x02, b"").await.unwrap();
821 });
822
823 let (frame_type, payload) = read_tls_frame(&mut server).await.unwrap();
824 assert_eq!(frame_type, 0x03);
825 assert_eq!(payload, b"payload");
826
827 let (frame_type, payload) = read_tls_frame(&mut server).await.unwrap();
828 assert_eq!(frame_type, 0x02);
829 assert!(payload.is_empty());
830
831 writer.await.unwrap();
832 }
833
834 #[tokio::test]
835 async fn test_tls_frame_read_unexpected_eof_is_empty_response() {
836 let (client, mut server) = tokio::io::duplex(8);
837 drop(client);
838
839 let (frame_type, payload) = read_tls_frame(&mut server).await.unwrap();
840 assert_eq!(frame_type, 0x01);
841 assert!(payload.is_empty());
842 }
843
844 #[tokio::test]
845 async fn test_tls_frame_read_truncated_payload_errors() {
846 let (mut client, mut server) = tokio::io::duplex(16);
847 client
848 .write_all(&[0x01, 0, 0, 0, 5, b'a', b'b'])
849 .await
850 .unwrap();
851 drop(client);
852
853 let err = read_tls_frame(&mut server).await.unwrap_err();
854 assert!(matches!(err, BoxError::AttestationError(_)));
855 assert!(err.to_string().contains("payload read failed"));
856 }
857
858 #[tokio::test]
859 async fn test_secret_injector_empty_secrets_returns_without_connecting() {
860 let injector = SecretInjector::new(Path::new("/tmp/nonexistent-empty-secrets.sock"));
861 let result = injector
862 .inject(&[], crate::tee::AttestationPolicy::default(), false)
863 .await
864 .unwrap();
865
866 assert_eq!(result.injected, 0);
867 assert!(result.errors.is_empty());
868 }
869
870 #[test]
871 fn test_attestation_client_constructors_and_serde_defaults() {
872 let path = Path::new("/tmp/a3s-attest.sock");
873 assert_eq!(RaTlsAttestationClient::new(path).socket_path(), path);
874 assert_eq!(SecretInjector::new(path).socket_path, path);
875 assert_eq!(SealClient::new(path).socket_path, path);
876
877 let secret: SecretEntry =
878 serde_json::from_str(r#"{"name":"TOKEN","value":"secret"}"#).unwrap();
879 assert!(secret.set_env);
880
881 let result: SecretInjectionResult = serde_json::from_str(r#"{"injected":2}"#).unwrap();
882 assert_eq!(result.injected, 2);
883 assert!(result.errors.is_empty());
884
885 let seal: SealResult =
886 serde_json::from_str(r#"{"blob":"abc","policy":"ChipOnly","context":"ctx"}"#).unwrap();
887 assert_eq!(seal.blob, "abc");
888 assert_eq!(seal.policy, "ChipOnly");
889 assert_eq!(seal.context, "ctx");
890
891 let unseal: UnsealResult = serde_json::from_str(r#"{"data":"c2VjcmV0"}"#).unwrap();
892 assert_eq!(unseal.data, "c2VjcmV0");
893 }
894}