1use std::collections::BTreeMap;
2
3use aes_gcm::{
4 Aes256Gcm, Nonce,
5 aead::{Aead, KeyInit},
6};
7use base64::{Engine, engine::general_purpose::STANDARD as BASE64_STANDARD};
8use ed25519_dalek::{
9 Signature as Ed25519Signature, Signer as _, SigningKey as Ed25519SigningKey, Verifier as _,
10 VerifyingKey as Ed25519VerifyingKey,
11};
12use rand::RngCore;
13use serde::{Deserialize, Serialize};
14use sha2::{Digest, Sha256};
15use zeroize::{Zeroize, ZeroizeOnDrop};
16
17use crate::{
18 Client, SdkError,
19 models::{
20 ArtifactProfile, EvidenceEventType, KeyAccessOperation, KeyTransportGuidance,
21 KeyTransportMode, ProtectionOperation, RequestContext, ResourceDescriptor,
22 SdkArtifactRegisterRequest, SdkArtifactRegisterResponse, SdkBootstrapResponse,
23 SdkEvidenceIngestRequest, SdkEvidenceIngestResponse, SdkKeyAccessPlanRequest,
24 SdkKeyAccessPlanResponse, SdkPolicyResolveRequest, SdkPolicyResolveResponse,
25 SdkProtectionPlanRequest, SdkProtectionPlanResponse, WorkloadDescriptor,
26 },
27};
28
29const LOCAL_ENVELOPE_KEY_REFERENCE: &str = "local-symmetric-key";
30const LOCAL_TDF_KEY_REFERENCE: &str = "local-tdf-key";
31const LOCAL_DETACHED_SIGNATURE_KEY_REFERENCE: &str = "local-detached-signature-key";
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub struct LocalProtectionRequest {
35 pub workload: WorkloadDescriptor,
36 pub resource: ResourceDescriptor,
37 pub preferred_artifact_profile: Option<ArtifactProfile>,
38 pub purpose: Option<String>,
39 #[serde(default)]
40 pub labels: Vec<String>,
41 #[serde(default)]
42 pub attributes: BTreeMap<String, String>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
46pub struct LocalAttributeEdit {
47 #[serde(default)]
48 pub set: BTreeMap<String, String>,
49 #[serde(default)]
50 pub remove: Vec<String>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54pub struct LocalContentBinding {
55 pub tenant_id: String,
56 pub content_digest: String,
57 pub content_size_bytes: u64,
58 pub raw_cid: String,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
62pub struct LocalArtifactBinding {
63 #[serde(default = "default_binding_version")]
64 pub version: u8,
65 pub tenant_id: String,
66 pub raw_cid: String,
67 pub content_digest: String,
68 pub content_size_bytes: u64,
69 pub workload: WorkloadDescriptor,
70 pub resource: ResourceDescriptor,
71 pub purpose: Option<String>,
72 #[serde(default)]
73 pub labels: Vec<String>,
74 #[serde(default)]
75 pub attributes: BTreeMap<String, String>,
76 #[serde(default)]
77 pub binding_targets: Vec<String>,
78 #[serde(default)]
79 pub binding_hash: String,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
83pub struct PreparedLocalProtection {
84 pub caller: RequestContext,
85 pub content_binding: LocalContentBinding,
86 pub artifact_binding: LocalArtifactBinding,
87 pub bootstrap: SdkBootstrapResponse,
88 pub policy_resolution: SdkPolicyResolveResponse,
89 pub protection_plan: SdkProtectionPlanResponse,
90}
91
92impl PreparedLocalProtection {
93 pub fn resolved_artifact_profile(&self) -> ArtifactProfile {
94 self.protection_plan.execution.artifact_profile
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
99pub struct LocalSymmetricKey([u8; 32]);
100
101impl LocalSymmetricKey {
102 pub fn new(bytes: [u8; 32]) -> Self {
103 Self(bytes)
104 }
105
106 fn as_bytes(&self) -> &[u8; 32] {
107 &self.0
108 }
109}
110
111impl From<[u8; 32]> for LocalSymmetricKey {
112 fn from(value: [u8; 32]) -> Self {
113 Self::new(value)
114 }
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
118pub struct ManagedSymmetricKeyReference {
119 pub key_reference: String,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
121 pub provider_name: Option<String>,
122}
123
124impl ManagedSymmetricKeyReference {
125 pub fn new(key_reference: impl Into<String>) -> Self {
126 Self {
127 key_reference: key_reference.into(),
128 provider_name: None,
129 }
130 }
131
132 pub fn with_provider(
133 provider_name: impl Into<String>,
134 key_reference: impl Into<String>,
135 ) -> Self {
136 Self {
137 key_reference: key_reference.into(),
138 provider_name: Some(provider_name.into()),
139 }
140 }
141
142 pub fn key_reference(&self) -> &str {
143 &self.key_reference
144 }
145
146 pub fn provider_name(&self) -> Option<&str> {
147 self.provider_name.as_deref()
148 }
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub enum LocalSymmetricKeySource {
153 Inline(LocalSymmetricKey),
154 ManagedReference(ManagedSymmetricKeyReference),
155}
156
157impl LocalSymmetricKeySource {
158 pub fn inline(key: impl Into<LocalSymmetricKey>) -> Self {
159 Self::Inline(key.into())
160 }
161
162 pub fn managed_reference(key_reference: impl Into<String>) -> Self {
163 Self::ManagedReference(ManagedSymmetricKeyReference::new(key_reference))
164 }
165
166 pub fn managed_reference_with_provider(
167 provider_name: impl Into<String>,
168 key_reference: impl Into<String>,
169 ) -> Self {
170 Self::ManagedReference(ManagedSymmetricKeyReference::with_provider(
171 provider_name,
172 key_reference,
173 ))
174 }
175
176 pub fn key_reference<'a>(&'a self, default_key_reference: &'a str) -> &'a str {
177 match self {
178 Self::Inline(_) => default_key_reference,
179 Self::ManagedReference(key_reference) => key_reference.key_reference(),
180 }
181 }
182
183 pub fn provider_name(&self) -> Option<&str> {
184 match self {
185 Self::Inline(_) => None,
186 Self::ManagedReference(key_reference) => key_reference.provider_name(),
187 }
188 }
189}
190
191impl From<LocalSymmetricKey> for LocalSymmetricKeySource {
192 fn from(value: LocalSymmetricKey) -> Self {
193 Self::Inline(value)
194 }
195}
196
197impl From<[u8; 32]> for LocalSymmetricKeySource {
198 fn from(value: [u8; 32]) -> Self {
199 Self::Inline(LocalSymmetricKey::from(value))
200 }
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
204pub struct LocalSigningKey([u8; 32]);
205
206impl LocalSigningKey {
207 pub fn new(bytes: [u8; 32]) -> Self {
208 Self(bytes)
209 }
210
211 fn as_bytes(&self) -> &[u8; 32] {
212 &self.0
213 }
214
215 pub fn verifying_key(&self) -> LocalVerifyingKey {
216 let signing_key = Ed25519SigningKey::from_bytes(self.as_bytes());
217 LocalVerifyingKey::from(signing_key.verifying_key().to_bytes())
218 }
219}
220
221impl From<[u8; 32]> for LocalSigningKey {
222 fn from(value: [u8; 32]) -> Self {
223 Self::new(value)
224 }
225}
226
227#[derive(Debug, Clone, PartialEq, Eq)]
228pub struct LocalVerifyingKey([u8; 32]);
229
230impl LocalVerifyingKey {
231 pub fn new(bytes: [u8; 32]) -> Self {
232 Self(bytes)
233 }
234
235 fn as_bytes(&self) -> &[u8; 32] {
236 &self.0
237 }
238}
239
240impl From<[u8; 32]> for LocalVerifyingKey {
241 fn from(value: [u8; 32]) -> Self {
242 Self::new(value)
243 }
244}
245
246#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
247#[serde(rename_all = "snake_case")]
248pub enum LocalEnvelopeAlgorithm {
249 Aes256Gcm,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
253pub struct LocalEnvelopeArtifact {
254 pub version: u8,
255 pub artifact_profile: ArtifactProfile,
256 pub algorithm: LocalEnvelopeAlgorithm,
257 pub tenant_id: String,
258 pub raw_cid: String,
259 pub content_digest: String,
260 pub content_size_bytes: u64,
261 pub workload: WorkloadDescriptor,
262 pub resource: ResourceDescriptor,
263 pub purpose: Option<String>,
264 pub labels: Vec<String>,
265 pub attributes: BTreeMap<String, String>,
266 #[serde(default)]
267 pub binding_targets: Vec<String>,
268 #[serde(default)]
269 pub binding_hash: String,
270 pub nonce_b64: String,
271 pub aad_hash: String,
272 pub ciphertext_b64: String,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq)]
276pub struct ProtectedEnvelopeArtifact {
277 pub envelope: LocalEnvelopeArtifact,
278 pub artifact_bytes: Vec<u8>,
279 pub artifact_digest: String,
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
283pub struct EnvelopeProtectionResult {
284 pub prepared: PreparedLocalProtection,
285 pub key_access_plan: SdkKeyAccessPlanResponse,
286 pub artifact: ProtectedEnvelopeArtifact,
287 pub artifact_registration: SdkArtifactRegisterResponse,
288 pub evidence: SdkEvidenceIngestResponse,
289}
290
291#[derive(Debug, Clone, PartialEq, Eq)]
292pub struct EnvelopeAccessResult {
293 pub artifact: LocalEnvelopeArtifact,
294 pub artifact_digest: String,
295 pub policy_resolution: SdkPolicyResolveResponse,
296 pub key_access_plan: SdkKeyAccessPlanResponse,
297 pub plaintext: Vec<u8>,
298 pub evidence: SdkEvidenceIngestResponse,
299}
300
301#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
302#[serde(rename_all = "snake_case")]
303pub enum LocalTdfAlgorithm {
304 Aes256Gcm,
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
308pub struct LocalTdfManifest {
309 pub workload: WorkloadDescriptor,
310 pub resource: ResourceDescriptor,
311 pub purpose: Option<String>,
312 #[serde(default)]
313 pub labels: Vec<String>,
314 #[serde(default)]
315 pub attributes: BTreeMap<String, String>,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
319pub struct LocalTdfArtifact {
320 pub version: u8,
321 #[serde(default = "default_meta_version")]
322 pub meta_version: u64,
323 pub artifact_profile: ArtifactProfile,
324 pub algorithm: LocalTdfAlgorithm,
325 pub tenant_id: String,
326 pub raw_cid: String,
327 pub content_digest: String,
328 pub content_size_bytes: u64,
329 pub manifest_digest: String,
330 #[serde(default)]
331 pub binding_targets: Vec<String>,
332 #[serde(default)]
333 pub binding_hash: String,
334 #[serde(default, skip_serializing_if = "Option::is_none")]
335 pub policy_context: Option<LocalTdfManifest>,
336 pub manifest_nonce_b64: String,
337 pub manifest_ciphertext_b64: String,
338 pub payload_nonce_b64: String,
339 pub payload_ciphertext_b64: String,
340 pub aad_hash: String,
341}
342
343#[derive(Debug, Clone, PartialEq, Eq)]
344pub struct ProtectedTdfArtifact {
345 pub tdf: LocalTdfArtifact,
346 pub artifact_bytes: Vec<u8>,
347 pub artifact_digest: String,
348}
349
350#[derive(Debug, Clone, PartialEq, Eq)]
351pub struct TdfProtectionResult {
352 pub prepared: PreparedLocalProtection,
353 pub key_access_plan: SdkKeyAccessPlanResponse,
354 pub artifact: ProtectedTdfArtifact,
355 pub artifact_registration: SdkArtifactRegisterResponse,
356 pub evidence: SdkEvidenceIngestResponse,
357}
358
359#[derive(Debug, Clone, PartialEq, Eq)]
360pub struct TdfAccessResult {
361 pub artifact: LocalTdfArtifact,
362 pub manifest: LocalTdfManifest,
363 pub artifact_digest: String,
364 pub policy_resolution: SdkPolicyResolveResponse,
365 pub key_access_plan: SdkKeyAccessPlanResponse,
366 pub plaintext: Vec<u8>,
367 pub evidence: SdkEvidenceIngestResponse,
368}
369
370#[derive(Debug, Clone, PartialEq, Eq)]
371pub struct EnvelopeRewrapResult {
372 pub content_binding: LocalContentBinding,
373 pub policy_resolution: SdkPolicyResolveResponse,
374 pub protection_plan: SdkProtectionPlanResponse,
375 pub key_access_plan: SdkKeyAccessPlanResponse,
376 pub original_artifact_digest: String,
377 pub artifact: ProtectedEnvelopeArtifact,
378 pub artifact_registration: SdkArtifactRegisterResponse,
379 pub evidence: SdkEvidenceIngestResponse,
380}
381
382#[derive(Debug, Clone, PartialEq, Eq)]
383pub struct TdfRewrapResult {
384 pub content_binding: LocalContentBinding,
385 pub manifest: LocalTdfManifest,
386 pub policy_resolution: SdkPolicyResolveResponse,
387 pub protection_plan: SdkProtectionPlanResponse,
388 pub key_access_plan: SdkKeyAccessPlanResponse,
389 pub original_artifact_digest: String,
390 pub artifact: ProtectedTdfArtifact,
391 pub artifact_registration: SdkArtifactRegisterResponse,
392 pub evidence: SdkEvidenceIngestResponse,
393}
394
395#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
396#[serde(rename_all = "snake_case")]
397pub enum LocalDetachedSignatureAlgorithm {
398 Ed25519,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
402pub struct LocalDetachedSignatureArtifact {
403 pub version: u8,
404 pub artifact_profile: ArtifactProfile,
405 pub algorithm: LocalDetachedSignatureAlgorithm,
406 pub tenant_id: String,
407 pub raw_cid: String,
408 pub content_digest: String,
409 pub content_size_bytes: u64,
410 pub workload: WorkloadDescriptor,
411 pub resource: ResourceDescriptor,
412 pub purpose: Option<String>,
413 #[serde(default)]
414 pub labels: Vec<String>,
415 #[serde(default)]
416 pub attributes: BTreeMap<String, String>,
417 #[serde(default)]
418 pub binding_targets: Vec<String>,
419 pub signer_key_id: String,
420 pub signer_public_key_b64: String,
421 #[serde(default)]
422 pub binding_hash: String,
423 pub signature_b64: String,
424}
425
426#[derive(Debug, Clone, PartialEq, Eq)]
427pub struct ProtectedDetachedSignatureArtifact {
428 pub detached_signature: LocalDetachedSignatureArtifact,
429 pub artifact_bytes: Vec<u8>,
430 pub artifact_digest: String,
431}
432
433#[derive(Debug, Clone, PartialEq, Eq)]
434pub struct DetachedSignatureSignResult {
435 pub prepared: PreparedLocalProtection,
436 pub key_access_plan: SdkKeyAccessPlanResponse,
437 pub artifact: ProtectedDetachedSignatureArtifact,
438 pub artifact_registration: SdkArtifactRegisterResponse,
439 pub evidence: SdkEvidenceIngestResponse,
440}
441
442#[derive(Debug, Clone, PartialEq, Eq)]
443pub struct DetachedSignatureVerifyResult {
444 pub artifact: LocalDetachedSignatureArtifact,
445 pub artifact_digest: String,
446 pub policy_resolution: SdkPolicyResolveResponse,
447 pub key_access_plan: SdkKeyAccessPlanResponse,
448 pub content_binding: LocalContentBinding,
449 pub evidence: SdkEvidenceIngestResponse,
450}
451
452#[derive(Debug, Clone, Serialize)]
453struct EnvelopeAadBinding<'a> {
454 tenant_id: &'a str,
455 raw_cid: &'a str,
456 content_digest: &'a str,
457 artifact_profile: ArtifactProfile,
458 workload: &'a WorkloadDescriptor,
459 resource: &'a ResourceDescriptor,
460 purpose: &'a Option<String>,
461 labels: &'a [String],
462 attributes: &'a BTreeMap<String, String>,
463 binding_targets: &'a [String],
464 binding_hash: &'a str,
465}
466
467#[derive(Debug, Clone, Serialize)]
468struct TdfAadBinding<'a> {
469 tenant_id: &'a str,
470 raw_cid: &'a str,
471 content_digest: &'a str,
472 content_size_bytes: u64,
473 manifest_digest: &'a str,
474 artifact_profile: ArtifactProfile,
475 binding_hash: &'a str,
476}
477
478#[derive(Debug, Clone, Serialize)]
479struct LocalArtifactBindingPayload<'a> {
480 version: u8,
481 tenant_id: &'a str,
482 raw_cid: &'a str,
483 content_digest: &'a str,
484 content_size_bytes: u64,
485 workload: &'a WorkloadDescriptor,
486 resource: &'a ResourceDescriptor,
487 purpose: &'a Option<String>,
488 labels: &'a [String],
489 attributes: &'a BTreeMap<String, String>,
490 binding_targets: &'a [String],
491}
492
493impl Client {
494 pub fn prepare_local_protection(
495 &self,
496 content: &[u8],
497 request: LocalProtectionRequest,
498 ) -> Result<PreparedLocalProtection, SdkError> {
499 let digest = sha256_prefixed(content);
500 let content_size_bytes = u64::try_from(content.len()).map_err(|_| {
501 SdkError::InvalidInput("content length exceeds supported u64 range".to_string())
502 })?;
503
504 let bootstrap = self.bootstrap()?;
505 ensure_bootstrap_supports_local_protection(&bootstrap, request.preferred_artifact_profile)?;
506
507 let policy_resolution = self.policy_resolve(&SdkPolicyResolveRequest {
508 operation: ProtectionOperation::Protect,
509 workload: request.workload.clone(),
510 resource: request.resource.clone(),
511 content_digest: Some(digest.clone()),
512 content_size_bytes: Some(content_size_bytes),
513 purpose: request.purpose.clone(),
514 labels: request.labels.clone(),
515 attributes: request.attributes.clone(),
516 })?;
517 ensure_policy_resolution_supports_local_protection(&policy_resolution)?;
518
519 let protection_plan = self.protection_plan(&SdkProtectionPlanRequest {
520 operation: ProtectionOperation::Protect,
521 workload: request.workload.clone(),
522 resource: request.resource.clone(),
523 preferred_artifact_profile: request.preferred_artifact_profile,
524 content_digest: Some(digest.clone()),
525 content_size_bytes: Some(content_size_bytes),
526 purpose: request.purpose.clone(),
527 labels: request.labels.clone(),
528 attributes: request.attributes.clone(),
529 })?;
530 ensure_protection_plan_supports_local_protection(&protection_plan)?;
531
532 let caller = protection_plan.caller.clone();
533 let content_binding = LocalContentBinding {
534 tenant_id: caller.tenant_id.clone(),
535 content_digest: digest.clone(),
536 content_size_bytes,
537 raw_cid: digest,
538 };
539 let artifact_binding = build_local_artifact_binding(
540 &content_binding,
541 &request,
542 &policy_resolution.handling.bind_policy_to,
543 )?;
544
545 Ok(PreparedLocalProtection {
546 caller,
547 content_binding,
548 artifact_binding,
549 bootstrap,
550 policy_resolution,
551 protection_plan,
552 })
553 }
554
555 pub fn generate_cid_binding(
556 &self,
557 content: &[u8],
558 request: LocalProtectionRequest,
559 ) -> Result<LocalArtifactBinding, SdkError> {
560 Ok(self
561 .prepare_local_protection(content, request)?
562 .artifact_binding)
563 }
564
565 pub fn protect_bytes_with_envelope(
566 &self,
567 key: &LocalSymmetricKey,
568 plaintext: &[u8],
569 request: LocalProtectionRequest,
570 ) -> Result<EnvelopeProtectionResult, SdkError> {
571 self.protect_bytes_with_envelope_using_key_source(
572 &LocalSymmetricKeySource::from(key.clone()),
573 plaintext,
574 request,
575 )
576 }
577
578 pub fn protect_bytes_with_envelope_using_key_source(
579 &self,
580 key_source: &LocalSymmetricKeySource,
581 plaintext: &[u8],
582 mut request: LocalProtectionRequest,
583 ) -> Result<EnvelopeProtectionResult, SdkError> {
584 request.preferred_artifact_profile = Some(ArtifactProfile::Envelope);
585 let prepared = self.prepare_local_protection(plaintext, request.clone())?;
586
587 let key_access_plan = self.key_access_plan(&SdkKeyAccessPlanRequest {
588 operation: KeyAccessOperation::Wrap,
589 workload: request.workload.clone(),
590 resource: request.resource.clone(),
591 artifact_profile: Some(ArtifactProfile::Envelope),
592 key_reference: Some(
593 key_source
594 .key_reference(LOCAL_ENVELOPE_KEY_REFERENCE)
595 .to_string(),
596 ),
597 content_digest: Some(prepared.content_binding.content_digest.clone()),
598 purpose: request.purpose.clone(),
599 labels: request.labels.clone(),
600 attributes: request.attributes.clone(),
601 })?;
602 ensure_key_access_plan_supports_local_crypto(
603 &key_access_plan,
604 KeyAccessOperation::Wrap,
605 ArtifactProfile::Envelope,
606 )?;
607
608 let resolved_key = resolve_symmetric_key_for_runtime(
609 self,
610 key_source,
611 key_access_plan.execution.key_transport.as_ref(),
612 LOCAL_ENVELOPE_KEY_REFERENCE,
613 "local envelope protection",
614 )?;
615
616 let envelope =
617 encrypt_envelope_artifact(&resolved_key.key, plaintext, &prepared, &request)?;
618 let artifact_bytes = serde_json::to_vec(&envelope).map_err(|error| {
619 SdkError::Serialization(format!(
620 "failed to serialize local envelope artifact: {error}"
621 ))
622 })?;
623 let artifact_digest = sha256_prefixed(&artifact_bytes);
624
625 let artifact_registration = self.artifact_register(&SdkArtifactRegisterRequest {
626 operation: ProtectionOperation::Protect,
627 workload: request.workload.clone(),
628 resource: request.resource.clone(),
629 artifact_profile: ArtifactProfile::Envelope,
630 artifact_digest: artifact_digest.clone(),
631 artifact_locator: None,
632 decision_id: None,
633 key_reference: Some(resolved_key.key_reference),
634 purpose: request.purpose.clone(),
635 labels: request.labels.clone(),
636 attributes: request.attributes.clone(),
637 })?;
638 ensure_artifact_registration_supports_local_only(&artifact_registration)?;
639
640 let evidence = self.evidence(&SdkEvidenceIngestRequest {
641 event_type: EvidenceEventType::Protect,
642 workload: request.workload,
643 resource: request.resource,
644 artifact_profile: Some(ArtifactProfile::Envelope),
645 artifact_digest: Some(artifact_digest.clone()),
646 decision_id: None,
647 outcome: Some("success".to_string()),
648 occurred_at: None,
649 purpose: request.purpose,
650 labels: request.labels,
651 attributes: request.attributes,
652 })?;
653 ensure_evidence_ingestion_supports_local_only(&evidence)?;
654
655 Ok(EnvelopeProtectionResult {
656 prepared,
657 key_access_plan,
658 artifact: ProtectedEnvelopeArtifact {
659 envelope,
660 artifact_bytes,
661 artifact_digest,
662 },
663 artifact_registration,
664 evidence,
665 })
666 }
667
668 pub fn access_bytes_with_envelope(
669 &self,
670 key: &LocalSymmetricKey,
671 artifact_bytes: &[u8],
672 ) -> Result<EnvelopeAccessResult, SdkError> {
673 self.access_bytes_with_envelope_using_key_source(
674 &LocalSymmetricKeySource::from(key.clone()),
675 artifact_bytes,
676 )
677 }
678
679 pub fn access_bytes_with_envelope_using_key_source(
680 &self,
681 key_source: &LocalSymmetricKeySource,
682 artifact_bytes: &[u8],
683 ) -> Result<EnvelopeAccessResult, SdkError> {
684 let artifact: LocalEnvelopeArtifact =
685 serde_json::from_slice(artifact_bytes).map_err(|error| {
686 SdkError::Serialization(format!(
687 "failed to decode local envelope artifact: {error}"
688 ))
689 })?;
690 ensure_local_envelope_artifact_valid(&artifact)?;
691
692 let artifact_digest = sha256_prefixed(artifact_bytes);
693
694 let policy_resolution = self.policy_resolve(&SdkPolicyResolveRequest {
695 operation: ProtectionOperation::Access,
696 workload: artifact.workload.clone(),
697 resource: artifact.resource.clone(),
698 content_digest: Some(artifact.content_digest.clone()),
699 content_size_bytes: Some(artifact.content_size_bytes),
700 purpose: artifact.purpose.clone(),
701 labels: artifact.labels.clone(),
702 attributes: artifact.attributes.clone(),
703 })?;
704 ensure_access_policy_resolution_supports_local_access(&policy_resolution)?;
705
706 let key_access_plan = self.key_access_plan(&SdkKeyAccessPlanRequest {
707 operation: KeyAccessOperation::Unwrap,
708 workload: artifact.workload.clone(),
709 resource: artifact.resource.clone(),
710 artifact_profile: Some(ArtifactProfile::Envelope),
711 key_reference: Some(
712 key_source
713 .key_reference(LOCAL_ENVELOPE_KEY_REFERENCE)
714 .to_string(),
715 ),
716 content_digest: Some(artifact.content_digest.clone()),
717 purpose: artifact.purpose.clone(),
718 labels: artifact.labels.clone(),
719 attributes: artifact.attributes.clone(),
720 })?;
721 ensure_key_access_plan_supports_local_crypto(
722 &key_access_plan,
723 KeyAccessOperation::Unwrap,
724 ArtifactProfile::Envelope,
725 )?;
726
727 let resolved_key = resolve_symmetric_key_for_runtime(
728 self,
729 key_source,
730 key_access_plan.execution.key_transport.as_ref(),
731 LOCAL_ENVELOPE_KEY_REFERENCE,
732 "local envelope access",
733 )?;
734
735 let plaintext = decrypt_envelope_artifact(&resolved_key.key, &artifact)?;
736 let decrypted_digest = sha256_prefixed(&plaintext);
737 if decrypted_digest != artifact.content_digest {
738 return Err(SdkError::InvalidInput(
739 "local envelope decrypted bytes do not match the embedded content digest"
740 .to_string(),
741 ));
742 }
743
744 let decrypted_size = u64::try_from(plaintext.len()).map_err(|_| {
745 SdkError::InvalidInput(
746 "decrypted content length exceeds supported u64 range".to_string(),
747 )
748 })?;
749 if decrypted_size != artifact.content_size_bytes {
750 return Err(SdkError::InvalidInput(
751 "local envelope decrypted bytes do not match the embedded content size".to_string(),
752 ));
753 }
754
755 let evidence = self.evidence(&SdkEvidenceIngestRequest {
756 event_type: EvidenceEventType::Access,
757 workload: artifact.workload.clone(),
758 resource: artifact.resource.clone(),
759 artifact_profile: Some(ArtifactProfile::Envelope),
760 artifact_digest: Some(artifact_digest.clone()),
761 decision_id: None,
762 outcome: Some("success".to_string()),
763 occurred_at: None,
764 purpose: artifact.purpose.clone(),
765 labels: artifact.labels.clone(),
766 attributes: artifact.attributes.clone(),
767 })?;
768 ensure_evidence_ingestion_supports_local_only(&evidence)?;
769
770 Ok(EnvelopeAccessResult {
771 artifact,
772 artifact_digest,
773 policy_resolution,
774 key_access_plan,
775 plaintext,
776 evidence,
777 })
778 }
779
780 pub fn rewrap_bytes_with_envelope(
781 &self,
782 current_key: &LocalSymmetricKey,
783 new_key: &LocalSymmetricKey,
784 artifact_bytes: &[u8],
785 ) -> Result<EnvelopeRewrapResult, SdkError> {
786 self.rewrap_bytes_with_envelope_using_key_sources(
787 &LocalSymmetricKeySource::from(current_key.clone()),
788 &LocalSymmetricKeySource::from(new_key.clone()),
789 artifact_bytes,
790 )
791 }
792
793 pub fn rewrap_bytes_with_envelope_using_key_sources(
794 &self,
795 current_key_source: &LocalSymmetricKeySource,
796 new_key_source: &LocalSymmetricKeySource,
797 artifact_bytes: &[u8],
798 ) -> Result<EnvelopeRewrapResult, SdkError> {
799 let artifact: LocalEnvelopeArtifact =
800 serde_json::from_slice(artifact_bytes).map_err(|error| {
801 SdkError::Serialization(format!(
802 "failed to decode local envelope artifact: {error}"
803 ))
804 })?;
805 ensure_local_envelope_artifact_valid(&artifact)?;
806
807 let original_artifact_digest = sha256_prefixed(artifact_bytes);
808 let bootstrap = self.bootstrap()?;
809 ensure_bootstrap_supports_local_rewrap(&bootstrap, ArtifactProfile::Envelope)?;
810
811 let policy_resolution = self.policy_resolve(&SdkPolicyResolveRequest {
812 operation: ProtectionOperation::Rewrap,
813 workload: artifact.workload.clone(),
814 resource: artifact.resource.clone(),
815 content_digest: Some(artifact.content_digest.clone()),
816 content_size_bytes: Some(artifact.content_size_bytes),
817 purpose: artifact.purpose.clone(),
818 labels: artifact.labels.clone(),
819 attributes: artifact.attributes.clone(),
820 })?;
821 ensure_rewrap_policy_resolution_supports_local_only(&policy_resolution)?;
822
823 let protection_plan = self.protection_plan(&SdkProtectionPlanRequest {
824 operation: ProtectionOperation::Rewrap,
825 workload: artifact.workload.clone(),
826 resource: artifact.resource.clone(),
827 preferred_artifact_profile: Some(ArtifactProfile::Envelope),
828 content_digest: Some(artifact.content_digest.clone()),
829 content_size_bytes: Some(artifact.content_size_bytes),
830 purpose: artifact.purpose.clone(),
831 labels: artifact.labels.clone(),
832 attributes: artifact.attributes.clone(),
833 })?;
834 ensure_rewrap_protection_plan_supports_local_only(&protection_plan)?;
835
836 let key_access_plan = self.key_access_plan(&SdkKeyAccessPlanRequest {
837 operation: KeyAccessOperation::Rewrap,
838 workload: artifact.workload.clone(),
839 resource: artifact.resource.clone(),
840 artifact_profile: Some(ArtifactProfile::Envelope),
841 key_reference: Some(
842 current_key_source
843 .key_reference(LOCAL_ENVELOPE_KEY_REFERENCE)
844 .to_string(),
845 ),
846 content_digest: Some(artifact.content_digest.clone()),
847 purpose: artifact.purpose.clone(),
848 labels: artifact.labels.clone(),
849 attributes: artifact.attributes.clone(),
850 })?;
851 ensure_key_access_plan_supports_local_crypto(
852 &key_access_plan,
853 KeyAccessOperation::Rewrap,
854 ArtifactProfile::Envelope,
855 )?;
856
857 let current_key = resolve_symmetric_key_for_runtime(
858 self,
859 current_key_source,
860 key_access_plan.execution.key_transport.as_ref(),
861 LOCAL_ENVELOPE_KEY_REFERENCE,
862 "local envelope rewrap",
863 )?;
864 let new_key = resolve_symmetric_key_for_runtime(
865 self,
866 new_key_source,
867 key_access_plan.execution.key_transport.as_ref(),
868 LOCAL_ENVELOPE_KEY_REFERENCE,
869 "local envelope rewrap",
870 )?;
871
872 let plaintext = decrypt_envelope_artifact(¤t_key.key, &artifact)?;
873 let decrypted_digest = sha256_prefixed(&plaintext);
874 if decrypted_digest != artifact.content_digest {
875 return Err(SdkError::InvalidInput(
876 "local envelope decrypted bytes do not match the embedded content digest"
877 .to_string(),
878 ));
879 }
880
881 let decrypted_size = u64::try_from(plaintext.len()).map_err(|_| {
882 SdkError::InvalidInput(
883 "decrypted content length exceeds supported u64 range".to_string(),
884 )
885 })?;
886 if decrypted_size != artifact.content_size_bytes {
887 return Err(SdkError::InvalidInput(
888 "local envelope decrypted bytes do not match the embedded content size".to_string(),
889 ));
890 }
891
892 let content_binding = LocalContentBinding {
893 tenant_id: artifact.tenant_id.clone(),
894 content_digest: artifact.content_digest.clone(),
895 content_size_bytes: artifact.content_size_bytes,
896 raw_cid: artifact.raw_cid.clone(),
897 };
898 let request = LocalProtectionRequest {
899 workload: artifact.workload.clone(),
900 resource: artifact.resource.clone(),
901 preferred_artifact_profile: Some(ArtifactProfile::Envelope),
902 purpose: artifact.purpose.clone(),
903 labels: artifact.labels.clone(),
904 attributes: artifact.attributes.clone(),
905 };
906 let prepared = PreparedLocalProtection {
907 caller: protection_plan.caller.clone(),
908 content_binding: content_binding.clone(),
909 artifact_binding: build_local_artifact_binding(
910 &content_binding,
911 &request,
912 &policy_resolution.handling.bind_policy_to,
913 )?,
914 bootstrap,
915 policy_resolution: policy_resolution.clone(),
916 protection_plan: protection_plan.clone(),
917 };
918
919 let envelope = encrypt_envelope_artifact(&new_key.key, &plaintext, &prepared, &request)?;
920 let new_artifact_bytes = serde_json::to_vec(&envelope).map_err(|error| {
921 SdkError::Serialization(format!(
922 "failed to serialize local envelope artifact: {error}"
923 ))
924 })?;
925 let artifact_digest = sha256_prefixed(&new_artifact_bytes);
926
927 let artifact_registration = self.artifact_register(&SdkArtifactRegisterRequest {
928 operation: ProtectionOperation::Rewrap,
929 workload: artifact.workload.clone(),
930 resource: artifact.resource.clone(),
931 artifact_profile: ArtifactProfile::Envelope,
932 artifact_digest: artifact_digest.clone(),
933 artifact_locator: None,
934 decision_id: None,
935 key_reference: Some(new_key.key_reference),
936 purpose: artifact.purpose.clone(),
937 labels: artifact.labels.clone(),
938 attributes: artifact.attributes.clone(),
939 })?;
940 ensure_artifact_registration_supports_local_only(&artifact_registration)?;
941
942 let evidence = self.evidence(&SdkEvidenceIngestRequest {
943 event_type: EvidenceEventType::Rewrap,
944 workload: artifact.workload.clone(),
945 resource: artifact.resource.clone(),
946 artifact_profile: Some(ArtifactProfile::Envelope),
947 artifact_digest: Some(artifact_digest.clone()),
948 decision_id: None,
949 outcome: Some("success".to_string()),
950 occurred_at: None,
951 purpose: artifact.purpose.clone(),
952 labels: artifact.labels.clone(),
953 attributes: artifact.attributes.clone(),
954 })?;
955 ensure_evidence_ingestion_supports_local_only(&evidence)?;
956
957 Ok(EnvelopeRewrapResult {
958 content_binding,
959 policy_resolution,
960 protection_plan,
961 key_access_plan,
962 original_artifact_digest,
963 artifact: ProtectedEnvelopeArtifact {
964 envelope,
965 artifact_bytes: new_artifact_bytes,
966 artifact_digest,
967 },
968 artifact_registration,
969 evidence,
970 })
971 }
972
973 pub fn protect_bytes_with_tdf(
974 &self,
975 key: &LocalSymmetricKey,
976 plaintext: &[u8],
977 request: LocalProtectionRequest,
978 ) -> Result<TdfProtectionResult, SdkError> {
979 self.protect_bytes_with_tdf_using_key_source(
980 &LocalSymmetricKeySource::from(key.clone()),
981 plaintext,
982 request,
983 )
984 }
985
986 pub fn protect_bytes_with_tdf_using_key_source(
987 &self,
988 key_source: &LocalSymmetricKeySource,
989 plaintext: &[u8],
990 mut request: LocalProtectionRequest,
991 ) -> Result<TdfProtectionResult, SdkError> {
992 request.preferred_artifact_profile = Some(ArtifactProfile::Tdf);
993 let prepared = self.prepare_local_protection(plaintext, request.clone())?;
994
995 let key_access_plan = self.key_access_plan(&SdkKeyAccessPlanRequest {
996 operation: KeyAccessOperation::Wrap,
997 workload: request.workload.clone(),
998 resource: request.resource.clone(),
999 artifact_profile: Some(ArtifactProfile::Tdf),
1000 key_reference: Some(
1001 key_source
1002 .key_reference(LOCAL_TDF_KEY_REFERENCE)
1003 .to_string(),
1004 ),
1005 content_digest: Some(prepared.content_binding.content_digest.clone()),
1006 purpose: request.purpose.clone(),
1007 labels: request.labels.clone(),
1008 attributes: request.attributes.clone(),
1009 })?;
1010 ensure_key_access_plan_supports_local_crypto(
1011 &key_access_plan,
1012 KeyAccessOperation::Wrap,
1013 ArtifactProfile::Tdf,
1014 )?;
1015
1016 let resolved_key = resolve_symmetric_key_for_runtime(
1017 self,
1018 key_source,
1019 key_access_plan.execution.key_transport.as_ref(),
1020 LOCAL_TDF_KEY_REFERENCE,
1021 "local TDF protection",
1022 )?;
1023
1024 let tdf = encrypt_tdf_artifact(&resolved_key.key, plaintext, &prepared, &request, 1)?;
1025 let artifact_bytes = serde_json::to_vec(&tdf).map_err(|error| {
1026 SdkError::Serialization(format!("failed to serialize local TDF artifact: {error}"))
1027 })?;
1028 let artifact_digest = sha256_prefixed(&artifact_bytes);
1029
1030 let artifact_registration = self.artifact_register(&SdkArtifactRegisterRequest {
1031 operation: ProtectionOperation::Protect,
1032 workload: request.workload.clone(),
1033 resource: request.resource.clone(),
1034 artifact_profile: ArtifactProfile::Tdf,
1035 artifact_digest: artifact_digest.clone(),
1036 artifact_locator: None,
1037 decision_id: None,
1038 key_reference: Some(resolved_key.key_reference),
1039 purpose: request.purpose.clone(),
1040 labels: request.labels.clone(),
1041 attributes: request.attributes.clone(),
1042 })?;
1043 ensure_artifact_registration_supports_local_only(&artifact_registration)?;
1044
1045 let evidence = self.evidence(&SdkEvidenceIngestRequest {
1046 event_type: EvidenceEventType::Protect,
1047 workload: request.workload,
1048 resource: request.resource,
1049 artifact_profile: Some(ArtifactProfile::Tdf),
1050 artifact_digest: Some(artifact_digest.clone()),
1051 decision_id: None,
1052 outcome: Some("success".to_string()),
1053 occurred_at: None,
1054 purpose: request.purpose,
1055 labels: request.labels,
1056 attributes: request.attributes,
1057 })?;
1058 ensure_evidence_ingestion_supports_local_only(&evidence)?;
1059
1060 Ok(TdfProtectionResult {
1061 prepared,
1062 key_access_plan,
1063 artifact: ProtectedTdfArtifact {
1064 tdf,
1065 artifact_bytes,
1066 artifact_digest,
1067 },
1068 artifact_registration,
1069 evidence,
1070 })
1071 }
1072
1073 pub fn access_bytes_with_tdf(
1074 &self,
1075 key: &LocalSymmetricKey,
1076 artifact_bytes: &[u8],
1077 ) -> Result<TdfAccessResult, SdkError> {
1078 self.access_bytes_with_tdf_using_key_source(
1079 &LocalSymmetricKeySource::from(key.clone()),
1080 artifact_bytes,
1081 )
1082 }
1083
1084 pub fn access_bytes_with_tdf_using_key_source(
1085 &self,
1086 key_source: &LocalSymmetricKeySource,
1087 artifact_bytes: &[u8],
1088 ) -> Result<TdfAccessResult, SdkError> {
1089 let artifact: LocalTdfArtifact =
1090 serde_json::from_slice(artifact_bytes).map_err(|error| {
1091 SdkError::Serialization(format!("failed to decode local TDF artifact: {error}"))
1092 })?;
1093 ensure_local_tdf_artifact_valid(&artifact)?;
1094
1095 let artifact_digest = sha256_prefixed(artifact_bytes);
1096 let (manifest, resolved_key) = match artifact.policy_context.as_ref() {
1097 Some(policy_context) => {
1098 ensure_local_tdf_binding_matches_manifest(&artifact, policy_context)?;
1099 let resolved = resolve_tdf_artifact_key_for_operation(
1100 self,
1101 key_source,
1102 &artifact,
1103 policy_context,
1104 ProtectionOperation::Access,
1105 KeyAccessOperation::Unwrap,
1106 "local TDF access",
1107 )?;
1108 let manifest = decrypt_tdf_manifest(&resolved.key_material.key, &artifact)?;
1109 ensure_local_tdf_binding_matches_manifest(&artifact, &manifest)?;
1110 ensure_local_tdf_policy_context_matches_manifest(policy_context, &manifest)?;
1111 (manifest, resolved)
1112 }
1113 None => {
1114 let key = require_inline_symmetric_key_without_transport_guidance(
1115 key_source,
1116 "local TDF access",
1117 )?;
1118 let manifest = decrypt_tdf_manifest(key, &artifact)?;
1119 ensure_local_tdf_binding_matches_manifest(&artifact, &manifest)?;
1120 let resolved = resolve_tdf_artifact_key_for_operation(
1121 self,
1122 key_source,
1123 &artifact,
1124 &manifest,
1125 ProtectionOperation::Access,
1126 KeyAccessOperation::Unwrap,
1127 "local TDF access",
1128 )?;
1129 (manifest, resolved)
1130 }
1131 };
1132
1133 let plaintext = decrypt_tdf_payload(&resolved_key.key_material.key, &artifact)?;
1134 let decrypted_digest = sha256_prefixed(&plaintext);
1135 if decrypted_digest != artifact.content_digest {
1136 return Err(SdkError::InvalidInput(
1137 "local TDF decrypted bytes do not match the embedded content digest".to_string(),
1138 ));
1139 }
1140
1141 let decrypted_size = u64::try_from(plaintext.len()).map_err(|_| {
1142 SdkError::InvalidInput(
1143 "decrypted content length exceeds supported u64 range".to_string(),
1144 )
1145 })?;
1146 if decrypted_size != artifact.content_size_bytes {
1147 return Err(SdkError::InvalidInput(
1148 "local TDF decrypted bytes do not match the embedded content size".to_string(),
1149 ));
1150 }
1151
1152 let evidence = self.evidence(&SdkEvidenceIngestRequest {
1153 event_type: EvidenceEventType::Access,
1154 workload: manifest.workload.clone(),
1155 resource: manifest.resource.clone(),
1156 artifact_profile: Some(ArtifactProfile::Tdf),
1157 artifact_digest: Some(artifact_digest.clone()),
1158 decision_id: None,
1159 outcome: Some("success".to_string()),
1160 occurred_at: None,
1161 purpose: manifest.purpose.clone(),
1162 labels: manifest.labels.clone(),
1163 attributes: manifest.attributes.clone(),
1164 })?;
1165 ensure_evidence_ingestion_supports_local_only(&evidence)?;
1166
1167 Ok(TdfAccessResult {
1168 artifact,
1169 manifest,
1170 artifact_digest,
1171 policy_resolution: resolved_key.policy_resolution,
1172 key_access_plan: resolved_key.key_access_plan,
1173 plaintext,
1174 evidence,
1175 })
1176 }
1177
1178 pub fn rewrap_bytes_with_tdf(
1179 &self,
1180 current_key: &LocalSymmetricKey,
1181 new_key: &LocalSymmetricKey,
1182 artifact_bytes: &[u8],
1183 ) -> Result<TdfRewrapResult, SdkError> {
1184 self.rewrap_bytes_with_tdf_using_key_sources(
1185 &LocalSymmetricKeySource::from(current_key.clone()),
1186 &LocalSymmetricKeySource::from(new_key.clone()),
1187 artifact_bytes,
1188 )
1189 }
1190
1191 pub fn rewrap_bytes_with_tdf_using_key_sources(
1192 &self,
1193 current_key_source: &LocalSymmetricKeySource,
1194 new_key_source: &LocalSymmetricKeySource,
1195 artifact_bytes: &[u8],
1196 ) -> Result<TdfRewrapResult, SdkError> {
1197 let artifact: LocalTdfArtifact =
1198 serde_json::from_slice(artifact_bytes).map_err(|error| {
1199 SdkError::Serialization(format!("failed to decode local TDF artifact: {error}"))
1200 })?;
1201 ensure_local_tdf_artifact_valid(&artifact)?;
1202
1203 let manifest = match artifact.policy_context.as_ref() {
1204 Some(policy_context) => {
1205 ensure_local_tdf_binding_matches_manifest(&artifact, policy_context)?;
1206 let manifest = match current_key_source {
1207 LocalSymmetricKeySource::Inline(key) => decrypt_tdf_manifest(key, &artifact)?,
1208 LocalSymmetricKeySource::ManagedReference(_) => {
1209 let resolved = resolve_tdf_artifact_key_for_operation(
1210 self,
1211 current_key_source,
1212 &artifact,
1213 policy_context,
1214 ProtectionOperation::Rewrap,
1215 KeyAccessOperation::Rewrap,
1216 "local TDF rewrap",
1217 )?;
1218 decrypt_tdf_manifest(&resolved.key_material.key, &artifact)?
1219 }
1220 };
1221 ensure_local_tdf_binding_matches_manifest(&artifact, &manifest)?;
1222 ensure_local_tdf_policy_context_matches_manifest(policy_context, &manifest)?;
1223 manifest
1224 }
1225 None => {
1226 let current_key = require_inline_symmetric_key_for_local_runtime(
1227 current_key_source,
1228 None,
1229 "local TDF rewrap",
1230 )?;
1231 let manifest = decrypt_tdf_manifest(current_key, &artifact)?;
1232 ensure_local_tdf_binding_matches_manifest(&artifact, &manifest)?;
1233 manifest
1234 }
1235 };
1236 let meta_version = artifact.meta_version;
1237
1238 rewrite_tdf_artifact(
1239 self,
1240 current_key_source,
1241 new_key_source,
1242 artifact_bytes,
1243 artifact,
1244 manifest,
1245 meta_version,
1246 )
1247 }
1248
1249 pub fn set_tdf_attributes(
1250 &self,
1251 key: &LocalSymmetricKey,
1252 artifact_bytes: &[u8],
1253 attributes: BTreeMap<String, String>,
1254 ) -> Result<TdfRewrapResult, SdkError> {
1255 self.set_tdf_attributes_using_key_source(
1256 &LocalSymmetricKeySource::from(key.clone()),
1257 artifact_bytes,
1258 attributes,
1259 )
1260 }
1261
1262 pub fn set_tdf_attributes_using_key_source(
1263 &self,
1264 key_source: &LocalSymmetricKeySource,
1265 artifact_bytes: &[u8],
1266 attributes: BTreeMap<String, String>,
1267 ) -> Result<TdfRewrapResult, SdkError> {
1268 let artifact: LocalTdfArtifact =
1269 serde_json::from_slice(artifact_bytes).map_err(|error| {
1270 SdkError::Serialization(format!("failed to decode local TDF artifact: {error}"))
1271 })?;
1272 ensure_local_tdf_artifact_valid(&artifact)?;
1273
1274 let mut manifest = match artifact.policy_context.as_ref() {
1275 Some(policy_context) => {
1276 ensure_local_tdf_binding_matches_manifest(&artifact, policy_context)?;
1277 let manifest = match key_source {
1278 LocalSymmetricKeySource::Inline(key) => decrypt_tdf_manifest(key, &artifact)?,
1279 LocalSymmetricKeySource::ManagedReference(_) => {
1280 let resolved = resolve_tdf_artifact_key_for_operation(
1281 self,
1282 key_source,
1283 &artifact,
1284 policy_context,
1285 ProtectionOperation::Rewrap,
1286 KeyAccessOperation::Rewrap,
1287 "local TDF attribute rewrite",
1288 )?;
1289 decrypt_tdf_manifest(&resolved.key_material.key, &artifact)?
1290 }
1291 };
1292 ensure_local_tdf_binding_matches_manifest(&artifact, &manifest)?;
1293 ensure_local_tdf_policy_context_matches_manifest(policy_context, &manifest)?;
1294 manifest
1295 }
1296 None => {
1297 let key = require_inline_symmetric_key_without_transport_guidance(
1298 key_source,
1299 "local TDF attribute rewrite",
1300 )?;
1301 let manifest = decrypt_tdf_manifest(key, &artifact)?;
1302 ensure_local_tdf_binding_matches_manifest(&artifact, &manifest)?;
1303 manifest
1304 }
1305 };
1306 manifest.attributes = attributes;
1307 let meta_version = artifact.meta_version.saturating_add(1);
1308
1309 rewrite_tdf_artifact(
1310 self,
1311 key_source,
1312 key_source,
1313 artifact_bytes,
1314 artifact,
1315 manifest,
1316 meta_version,
1317 )
1318 }
1319
1320 pub fn edit_tdf_attributes(
1321 &self,
1322 key: &LocalSymmetricKey,
1323 artifact_bytes: &[u8],
1324 edit: LocalAttributeEdit,
1325 ) -> Result<TdfRewrapResult, SdkError> {
1326 self.edit_tdf_attributes_using_key_source(
1327 &LocalSymmetricKeySource::from(key.clone()),
1328 artifact_bytes,
1329 edit,
1330 )
1331 }
1332
1333 pub fn edit_tdf_attributes_using_key_source(
1334 &self,
1335 key_source: &LocalSymmetricKeySource,
1336 artifact_bytes: &[u8],
1337 edit: LocalAttributeEdit,
1338 ) -> Result<TdfRewrapResult, SdkError> {
1339 let artifact: LocalTdfArtifact =
1340 serde_json::from_slice(artifact_bytes).map_err(|error| {
1341 SdkError::Serialization(format!("failed to decode local TDF artifact: {error}"))
1342 })?;
1343 ensure_local_tdf_artifact_valid(&artifact)?;
1344
1345 let mut manifest = match artifact.policy_context.as_ref() {
1346 Some(policy_context) => {
1347 ensure_local_tdf_binding_matches_manifest(&artifact, policy_context)?;
1348 let manifest = match key_source {
1349 LocalSymmetricKeySource::Inline(key) => decrypt_tdf_manifest(key, &artifact)?,
1350 LocalSymmetricKeySource::ManagedReference(_) => {
1351 let resolved = resolve_tdf_artifact_key_for_operation(
1352 self,
1353 key_source,
1354 &artifact,
1355 policy_context,
1356 ProtectionOperation::Rewrap,
1357 KeyAccessOperation::Rewrap,
1358 "local TDF attribute rewrite",
1359 )?;
1360 decrypt_tdf_manifest(&resolved.key_material.key, &artifact)?
1361 }
1362 };
1363 ensure_local_tdf_binding_matches_manifest(&artifact, &manifest)?;
1364 ensure_local_tdf_policy_context_matches_manifest(policy_context, &manifest)?;
1365 manifest
1366 }
1367 None => {
1368 let key = require_inline_symmetric_key_without_transport_guidance(
1369 key_source,
1370 "local TDF attribute rewrite",
1371 )?;
1372 let manifest = decrypt_tdf_manifest(key, &artifact)?;
1373 ensure_local_tdf_binding_matches_manifest(&artifact, &manifest)?;
1374 manifest
1375 }
1376 };
1377 apply_attribute_edit(&mut manifest.attributes, &edit);
1378 let meta_version = artifact.meta_version.saturating_add(1);
1379
1380 rewrite_tdf_artifact(
1381 self,
1382 key_source,
1383 key_source,
1384 artifact_bytes,
1385 artifact,
1386 manifest,
1387 meta_version,
1388 )
1389 }
1390
1391 pub fn sign_bytes_with_detached_signature(
1392 &self,
1393 signing_key: &LocalSigningKey,
1394 content: &[u8],
1395 mut request: LocalProtectionRequest,
1396 ) -> Result<DetachedSignatureSignResult, SdkError> {
1397 request.preferred_artifact_profile = Some(ArtifactProfile::DetachedSignature);
1398 let prepared = self.prepare_local_protection(content, request.clone())?;
1399
1400 let key_access_plan = self.key_access_plan(&SdkKeyAccessPlanRequest {
1401 operation: KeyAccessOperation::Wrap,
1402 workload: request.workload.clone(),
1403 resource: request.resource.clone(),
1404 artifact_profile: Some(ArtifactProfile::DetachedSignature),
1405 key_reference: Some(LOCAL_DETACHED_SIGNATURE_KEY_REFERENCE.to_string()),
1406 content_digest: Some(prepared.content_binding.content_digest.clone()),
1407 purpose: request.purpose.clone(),
1408 labels: request.labels.clone(),
1409 attributes: request.attributes.clone(),
1410 })?;
1411 ensure_key_access_plan_supports_local_crypto(
1412 &key_access_plan,
1413 KeyAccessOperation::Wrap,
1414 ArtifactProfile::DetachedSignature,
1415 )?;
1416
1417 let detached_signature =
1418 sign_detached_signature_artifact(signing_key, &prepared, &request)?;
1419 let artifact_bytes = serde_json::to_vec(&detached_signature).map_err(|error| {
1420 SdkError::Serialization(format!(
1421 "failed to serialize local detached signature artifact: {error}"
1422 ))
1423 })?;
1424 let artifact_digest = sha256_prefixed(&artifact_bytes);
1425
1426 let artifact_registration = self.artifact_register(&SdkArtifactRegisterRequest {
1427 operation: ProtectionOperation::Protect,
1428 workload: request.workload.clone(),
1429 resource: request.resource.clone(),
1430 artifact_profile: ArtifactProfile::DetachedSignature,
1431 artifact_digest: artifact_digest.clone(),
1432 artifact_locator: None,
1433 decision_id: None,
1434 key_reference: Some(LOCAL_DETACHED_SIGNATURE_KEY_REFERENCE.to_string()),
1435 purpose: request.purpose.clone(),
1436 labels: request.labels.clone(),
1437 attributes: request.attributes.clone(),
1438 })?;
1439 ensure_artifact_registration_supports_local_only(&artifact_registration)?;
1440
1441 let evidence = self.evidence(&SdkEvidenceIngestRequest {
1442 event_type: EvidenceEventType::Protect,
1443 workload: request.workload,
1444 resource: request.resource,
1445 artifact_profile: Some(ArtifactProfile::DetachedSignature),
1446 artifact_digest: Some(artifact_digest.clone()),
1447 decision_id: None,
1448 outcome: Some("success".to_string()),
1449 occurred_at: None,
1450 purpose: request.purpose,
1451 labels: request.labels,
1452 attributes: request.attributes,
1453 })?;
1454 ensure_evidence_ingestion_supports_local_only(&evidence)?;
1455
1456 Ok(DetachedSignatureSignResult {
1457 prepared,
1458 key_access_plan,
1459 artifact: ProtectedDetachedSignatureArtifact {
1460 detached_signature,
1461 artifact_bytes,
1462 artifact_digest,
1463 },
1464 artifact_registration,
1465 evidence,
1466 })
1467 }
1468
1469 pub fn verify_bytes_with_detached_signature(
1470 &self,
1471 verifying_key: &LocalVerifyingKey,
1472 content: &[u8],
1473 artifact_bytes: &[u8],
1474 ) -> Result<DetachedSignatureVerifyResult, SdkError> {
1475 let artifact: LocalDetachedSignatureArtifact = serde_json::from_slice(artifact_bytes)
1476 .map_err(|error| {
1477 SdkError::Serialization(format!(
1478 "failed to decode local detached signature artifact: {error}"
1479 ))
1480 })?;
1481 ensure_local_detached_signature_artifact_valid(&artifact)?;
1482
1483 let artifact_digest = sha256_prefixed(artifact_bytes);
1484 let content_digest = sha256_prefixed(content);
1485 if content_digest != artifact.content_digest {
1486 return Err(SdkError::InvalidInput(
1487 "detached signature content digest does not match the provided content".to_string(),
1488 ));
1489 }
1490
1491 let content_size_bytes = u64::try_from(content.len()).map_err(|_| {
1492 SdkError::InvalidInput("content length exceeds supported u64 range".to_string())
1493 })?;
1494 if content_size_bytes != artifact.content_size_bytes {
1495 return Err(SdkError::InvalidInput(
1496 "detached signature content size does not match the provided content".to_string(),
1497 ));
1498 }
1499
1500 let content_binding = LocalContentBinding {
1501 tenant_id: artifact.tenant_id.clone(),
1502 content_digest: artifact.content_digest.clone(),
1503 content_size_bytes: artifact.content_size_bytes,
1504 raw_cid: artifact.raw_cid.clone(),
1505 };
1506
1507 let policy_resolution = self.policy_resolve(&SdkPolicyResolveRequest {
1508 operation: ProtectionOperation::Access,
1509 workload: artifact.workload.clone(),
1510 resource: artifact.resource.clone(),
1511 content_digest: Some(artifact.content_digest.clone()),
1512 content_size_bytes: Some(artifact.content_size_bytes),
1513 purpose: artifact.purpose.clone(),
1514 labels: artifact.labels.clone(),
1515 attributes: artifact.attributes.clone(),
1516 })?;
1517 ensure_access_policy_resolution_supports_local_access(&policy_resolution)?;
1518
1519 let key_access_plan = self.key_access_plan(&SdkKeyAccessPlanRequest {
1520 operation: KeyAccessOperation::Unwrap,
1521 workload: artifact.workload.clone(),
1522 resource: artifact.resource.clone(),
1523 artifact_profile: Some(ArtifactProfile::DetachedSignature),
1524 key_reference: Some(LOCAL_DETACHED_SIGNATURE_KEY_REFERENCE.to_string()),
1525 content_digest: Some(artifact.content_digest.clone()),
1526 purpose: artifact.purpose.clone(),
1527 labels: artifact.labels.clone(),
1528 attributes: artifact.attributes.clone(),
1529 })?;
1530 ensure_key_access_plan_supports_local_crypto(
1531 &key_access_plan,
1532 KeyAccessOperation::Unwrap,
1533 ArtifactProfile::DetachedSignature,
1534 )?;
1535
1536 let expected_public_key_b64 = BASE64_STANDARD.encode(verifying_key.as_bytes());
1537 if expected_public_key_b64 != artifact.signer_public_key_b64 {
1538 return Err(SdkError::InvalidInput(
1539 "provided verifying key does not match the detached signature artifact signer"
1540 .to_string(),
1541 ));
1542 }
1543
1544 let expected_signer_key_id = signer_key_id_from_public_key(verifying_key.as_bytes());
1545 if expected_signer_key_id != artifact.signer_key_id {
1546 return Err(SdkError::InvalidInput(
1547 "provided verifying key id does not match the detached signature artifact signer"
1548 .to_string(),
1549 ));
1550 }
1551
1552 verify_detached_signature_artifact(verifying_key, &artifact, &content_binding)?;
1553
1554 let evidence = self.evidence(&SdkEvidenceIngestRequest {
1555 event_type: EvidenceEventType::Access,
1556 workload: artifact.workload.clone(),
1557 resource: artifact.resource.clone(),
1558 artifact_profile: Some(ArtifactProfile::DetachedSignature),
1559 artifact_digest: Some(artifact_digest.clone()),
1560 decision_id: None,
1561 outcome: Some("success".to_string()),
1562 occurred_at: None,
1563 purpose: artifact.purpose.clone(),
1564 labels: artifact.labels.clone(),
1565 attributes: artifact.attributes.clone(),
1566 })?;
1567 ensure_evidence_ingestion_supports_local_only(&evidence)?;
1568
1569 Ok(DetachedSignatureVerifyResult {
1570 artifact,
1571 artifact_digest,
1572 policy_resolution,
1573 key_access_plan,
1574 content_binding,
1575 evidence,
1576 })
1577 }
1578}
1579
1580fn sha256_prefixed(content: &[u8]) -> String {
1581 let digest = Sha256::digest(content);
1582 format!("sha256:{}", hex::encode(digest))
1583}
1584
1585fn default_binding_version() -> u8 {
1586 1
1587}
1588
1589fn default_meta_version() -> u64 {
1590 1
1591}
1592
1593fn ensure_bootstrap_supports_local_protection(
1594 bootstrap: &SdkBootstrapResponse,
1595 requested_profile: Option<ArtifactProfile>,
1596) -> Result<(), SdkError> {
1597 if bootstrap.plaintext_to_platform {
1598 return Err(SdkError::InvalidInput(
1599 "bootstrap indicates plaintext may be transported to the platform; refusing local-only workflow"
1600 .to_string(),
1601 ));
1602 }
1603
1604 if bootstrap.enforcement_model != "embedded_local_library" {
1605 return Err(SdkError::InvalidInput(format!(
1606 "bootstrap enforcement model {:?} is incompatible with embedded local protection",
1607 bootstrap.enforcement_model
1608 )));
1609 }
1610
1611 if !bootstrap
1612 .supported_operations
1613 .contains(&ProtectionOperation::Protect)
1614 {
1615 return Err(SdkError::InvalidInput(
1616 "bootstrap does not advertise protect support for local workflows".to_string(),
1617 ));
1618 }
1619
1620 if let Some(profile) = requested_profile
1621 && !bootstrap.supported_artifact_profiles.is_empty()
1622 && !bootstrap.supported_artifact_profiles.contains(&profile)
1623 {
1624 return Err(SdkError::InvalidInput(format!(
1625 "requested artifact profile {:?} is not advertised by bootstrap",
1626 profile
1627 )));
1628 }
1629
1630 Ok(())
1631}
1632
1633fn ensure_policy_resolution_supports_local_protection(
1634 policy_resolution: &SdkPolicyResolveResponse,
1635) -> Result<(), SdkError> {
1636 if !policy_resolution.decision.allow {
1637 return Err(SdkError::InvalidInput(
1638 "policy resolution denied the protect operation".to_string(),
1639 ));
1640 }
1641
1642 if !policy_resolution.handling.protect_locally {
1643 return Err(SdkError::InvalidInput(
1644 "policy resolution did not require local enforcement".to_string(),
1645 ));
1646 }
1647
1648 if policy_resolution.handling.plaintext_transport != "forbidden_by_default" {
1649 return Err(SdkError::InvalidInput(format!(
1650 "policy resolution returned unsupported plaintext transport mode {:?}",
1651 policy_resolution.handling.plaintext_transport
1652 )));
1653 }
1654
1655 Ok(())
1656}
1657
1658fn ensure_access_policy_resolution_supports_local_access(
1659 policy_resolution: &SdkPolicyResolveResponse,
1660) -> Result<(), SdkError> {
1661 if !policy_resolution.decision.allow {
1662 return Err(SdkError::InvalidInput(
1663 "policy resolution denied the access operation".to_string(),
1664 ));
1665 }
1666
1667 if !policy_resolution.handling.protect_locally {
1668 return Err(SdkError::InvalidInput(
1669 "policy resolution did not preserve local enforcement for access".to_string(),
1670 ));
1671 }
1672
1673 if policy_resolution.handling.plaintext_transport != "forbidden_by_default" {
1674 return Err(SdkError::InvalidInput(format!(
1675 "policy resolution returned unsupported plaintext transport mode {:?}",
1676 policy_resolution.handling.plaintext_transport
1677 )));
1678 }
1679
1680 Ok(())
1681}
1682
1683fn ensure_rewrap_policy_resolution_supports_local_only(
1684 policy_resolution: &SdkPolicyResolveResponse,
1685) -> Result<(), SdkError> {
1686 if !policy_resolution.decision.allow {
1687 return Err(SdkError::InvalidInput(
1688 "policy resolution denied the rewrap operation".to_string(),
1689 ));
1690 }
1691
1692 if !policy_resolution.handling.protect_locally {
1693 return Err(SdkError::InvalidInput(
1694 "policy resolution did not preserve local enforcement for rewrap".to_string(),
1695 ));
1696 }
1697
1698 if policy_resolution.handling.plaintext_transport != "forbidden_by_default" {
1699 return Err(SdkError::InvalidInput(format!(
1700 "policy resolution returned unsupported plaintext transport mode {:?}",
1701 policy_resolution.handling.plaintext_transport
1702 )));
1703 }
1704
1705 Ok(())
1706}
1707
1708fn ensure_protection_plan_supports_local_protection(
1709 protection_plan: &SdkProtectionPlanResponse,
1710) -> Result<(), SdkError> {
1711 if !protection_plan.decision.allow {
1712 return Err(SdkError::InvalidInput(
1713 "protection plan denied the protect operation".to_string(),
1714 ));
1715 }
1716
1717 if !protection_plan.execution.protect_locally {
1718 return Err(SdkError::InvalidInput(
1719 "protection plan did not require local enforcement".to_string(),
1720 ));
1721 }
1722
1723 if protection_plan.execution.send_plaintext_to_platform {
1724 return Err(SdkError::InvalidInput(
1725 "protection plan requested plaintext transport to the platform; refusing local-only workflow"
1726 .to_string(),
1727 ));
1728 }
1729
1730 if protection_plan.decision.plaintext_transport != "forbidden_by_default" {
1731 return Err(SdkError::InvalidInput(format!(
1732 "protection plan returned unsupported plaintext transport mode {:?}",
1733 protection_plan.decision.plaintext_transport
1734 )));
1735 }
1736
1737 ensure_key_transport_guidance_is_fail_closed(protection_plan.execution.key_transport.as_ref())?;
1738
1739 Ok(())
1740}
1741
1742fn ensure_rewrap_protection_plan_supports_local_only(
1743 protection_plan: &SdkProtectionPlanResponse,
1744) -> Result<(), SdkError> {
1745 if !protection_plan.decision.allow {
1746 return Err(SdkError::InvalidInput(
1747 "protection plan denied the rewrap operation".to_string(),
1748 ));
1749 }
1750
1751 if !protection_plan.execution.protect_locally {
1752 return Err(SdkError::InvalidInput(
1753 "protection plan did not preserve local enforcement for rewrap".to_string(),
1754 ));
1755 }
1756
1757 if protection_plan.execution.send_plaintext_to_platform {
1758 return Err(SdkError::InvalidInput(
1759 "protection plan requested plaintext transport to the platform; refusing local-only rewrap workflow"
1760 .to_string(),
1761 ));
1762 }
1763
1764 if protection_plan.decision.plaintext_transport != "forbidden_by_default" {
1765 return Err(SdkError::InvalidInput(format!(
1766 "protection plan returned unsupported plaintext transport mode {:?}",
1767 protection_plan.decision.plaintext_transport
1768 )));
1769 }
1770
1771 ensure_key_transport_guidance_is_fail_closed(protection_plan.execution.key_transport.as_ref())?;
1772
1773 Ok(())
1774}
1775
1776fn ensure_bootstrap_supports_local_rewrap(
1777 bootstrap: &SdkBootstrapResponse,
1778 requested_profile: ArtifactProfile,
1779) -> Result<(), SdkError> {
1780 if bootstrap.plaintext_to_platform {
1781 return Err(SdkError::InvalidInput(
1782 "bootstrap indicates plaintext may be transported to the platform; refusing local-only rewrap workflow"
1783 .to_string(),
1784 ));
1785 }
1786
1787 if bootstrap.enforcement_model != "embedded_local_library" {
1788 return Err(SdkError::InvalidInput(format!(
1789 "bootstrap enforcement model {:?} is incompatible with embedded local rewrap",
1790 bootstrap.enforcement_model
1791 )));
1792 }
1793
1794 if !bootstrap
1795 .supported_operations
1796 .contains(&ProtectionOperation::Rewrap)
1797 {
1798 return Err(SdkError::InvalidInput(
1799 "bootstrap does not advertise rewrap support for local workflows".to_string(),
1800 ));
1801 }
1802
1803 if !bootstrap.supported_artifact_profiles.is_empty()
1804 && !bootstrap
1805 .supported_artifact_profiles
1806 .contains(&requested_profile)
1807 {
1808 return Err(SdkError::InvalidInput(format!(
1809 "requested artifact profile {:?} is not advertised by bootstrap",
1810 requested_profile
1811 )));
1812 }
1813
1814 Ok(())
1815}
1816
1817fn ensure_key_access_plan_supports_local_crypto(
1818 key_access_plan: &SdkKeyAccessPlanResponse,
1819 expected_operation: KeyAccessOperation,
1820 expected_profile: ArtifactProfile,
1821) -> Result<(), SdkError> {
1822 if !key_access_plan.decision.allow {
1823 return Err(SdkError::InvalidInput(
1824 "key access plan denied the local cryptographic operation".to_string(),
1825 ));
1826 }
1827
1828 if key_access_plan.decision.operation != expected_operation {
1829 return Err(SdkError::InvalidInput(format!(
1830 "key access plan returned unexpected operation {:?}",
1831 key_access_plan.decision.operation
1832 )));
1833 }
1834
1835 if !key_access_plan.execution.local_cryptographic_operation {
1836 return Err(SdkError::InvalidInput(
1837 "key access plan did not allow local cryptographic execution".to_string(),
1838 ));
1839 }
1840
1841 if key_access_plan.execution.send_plaintext_to_platform {
1842 return Err(SdkError::InvalidInput(
1843 "key access plan requested plaintext transport to the platform".to_string(),
1844 ));
1845 }
1846
1847 if key_access_plan.execution.artifact_profile != expected_profile {
1848 return Err(SdkError::InvalidInput(format!(
1849 "key access plan returned unexpected artifact profile {:?}",
1850 key_access_plan.execution.artifact_profile
1851 )));
1852 }
1853
1854 ensure_key_transport_guidance_is_fail_closed(key_access_plan.execution.key_transport.as_ref())?;
1855
1856 Ok(())
1857}
1858
1859fn ensure_key_transport_guidance_is_fail_closed(
1860 key_transport: Option<&KeyTransportGuidance>,
1861) -> Result<(), SdkError> {
1862 if let Some(key_transport) = key_transport
1863 && !key_transport.raw_key_delivery_forbidden
1864 {
1865 return Err(SdkError::InvalidInput(
1866 "key transport guidance permitted raw key delivery; refusing local-only workflow"
1867 .to_string(),
1868 ));
1869 }
1870
1871 Ok(())
1872}
1873
1874fn ensure_key_transport_guidance_supports_local_only(
1875 key_transport: Option<&KeyTransportGuidance>,
1876) -> Result<(), SdkError> {
1877 ensure_key_transport_guidance_is_fail_closed(key_transport)?;
1878
1879 if let Some(key_transport) = key_transport
1880 && key_transport.mode != KeyTransportMode::LocalProvided
1881 {
1882 return Err(SdkError::InvalidInput(format!(
1883 "key transport mode {} requires provider-backed orchestration; current local runtime only supports local_provided",
1884 key_transport_mode_name(key_transport.mode)
1885 )));
1886 }
1887
1888 Ok(())
1889}
1890
1891struct ResolvedSymmetricKeyMaterial {
1892 key: LocalSymmetricKey,
1893 key_reference: String,
1894}
1895
1896struct ResolvedTdfArtifactKey {
1897 policy_resolution: SdkPolicyResolveResponse,
1898 key_access_plan: SdkKeyAccessPlanResponse,
1899 key_material: ResolvedSymmetricKeyMaterial,
1900}
1901
1902fn resolve_symmetric_key_for_runtime(
1903 client: &Client,
1904 key_source: &LocalSymmetricKeySource,
1905 key_transport: Option<&KeyTransportGuidance>,
1906 default_key_reference: &str,
1907 context: &str,
1908) -> Result<ResolvedSymmetricKeyMaterial, SdkError> {
1909 ensure_key_transport_guidance_is_fail_closed(key_transport)?;
1910
1911 let transport_mode = key_transport
1912 .map(|guidance| guidance.mode)
1913 .unwrap_or(KeyTransportMode::LocalProvided);
1914
1915 match transport_mode {
1916 KeyTransportMode::LocalProvided => match key_source {
1917 LocalSymmetricKeySource::Inline(key) => Ok(ResolvedSymmetricKeyMaterial {
1918 key: key.clone(),
1919 key_reference: default_key_reference.to_string(),
1920 }),
1921 LocalSymmetricKeySource::ManagedReference(key_reference) => {
1922 Err(SdkError::InvalidInput(format!(
1923 "{context} requires an inline symmetric key when key_transport.mode is local_provided; received managed key reference {:?}",
1924 key_reference.key_reference()
1925 )))
1926 }
1927 },
1928 KeyTransportMode::WrappedKeyReference
1929 | KeyTransportMode::AuthorizedKeyRelease
1930 | KeyTransportMode::KemEncapsulatedCek => match key_source {
1931 LocalSymmetricKeySource::Inline(_) => Err(SdkError::InvalidInput(format!(
1932 "{context} requires a managed key reference when key_transport.mode is {}",
1933 key_transport_mode_name(transport_mode)
1934 ))),
1935 LocalSymmetricKeySource::ManagedReference(key_reference) => {
1936 let provider = client
1937 .managed_symmetric_key_provider_registry
1938 .resolve(key_reference.provider_name())?;
1939
1940 if !provider.capabilities().supports(transport_mode) {
1941 return Err(SdkError::InvalidInput(format!(
1942 "managed symmetric key provider {:?} does not support key_transport.mode {}",
1943 provider.provider_name(),
1944 key_transport_mode_name(transport_mode)
1945 )));
1946 }
1947
1948 Ok(ResolvedSymmetricKeyMaterial {
1949 key: provider.resolve_key(key_reference)?,
1950 key_reference: key_reference.key_reference().to_string(),
1951 })
1952 }
1953 },
1954 }
1955}
1956
1957fn resolve_tdf_artifact_key_for_operation(
1958 client: &Client,
1959 key_source: &LocalSymmetricKeySource,
1960 artifact: &LocalTdfArtifact,
1961 manifest: &LocalTdfManifest,
1962 policy_operation: ProtectionOperation,
1963 key_operation: KeyAccessOperation,
1964 context: &str,
1965) -> Result<ResolvedTdfArtifactKey, SdkError> {
1966 let policy_resolution = client.policy_resolve(&SdkPolicyResolveRequest {
1967 operation: policy_operation,
1968 workload: manifest.workload.clone(),
1969 resource: manifest.resource.clone(),
1970 content_digest: Some(artifact.content_digest.clone()),
1971 content_size_bytes: Some(artifact.content_size_bytes),
1972 purpose: manifest.purpose.clone(),
1973 labels: manifest.labels.clone(),
1974 attributes: manifest.attributes.clone(),
1975 })?;
1976
1977 match policy_operation {
1978 ProtectionOperation::Access => {
1979 ensure_access_policy_resolution_supports_local_access(&policy_resolution)?;
1980 }
1981 ProtectionOperation::Rewrap => {
1982 ensure_rewrap_policy_resolution_supports_local_only(&policy_resolution)?;
1983 }
1984 ProtectionOperation::Protect => {
1985 ensure_policy_resolution_supports_local_protection(&policy_resolution)?;
1986 }
1987 }
1988
1989 let key_access_plan = client.key_access_plan(&SdkKeyAccessPlanRequest {
1990 operation: key_operation,
1991 workload: manifest.workload.clone(),
1992 resource: manifest.resource.clone(),
1993 artifact_profile: Some(ArtifactProfile::Tdf),
1994 key_reference: Some(
1995 key_source
1996 .key_reference(LOCAL_TDF_KEY_REFERENCE)
1997 .to_string(),
1998 ),
1999 content_digest: Some(artifact.content_digest.clone()),
2000 purpose: manifest.purpose.clone(),
2001 labels: manifest.labels.clone(),
2002 attributes: manifest.attributes.clone(),
2003 })?;
2004 ensure_key_access_plan_supports_local_crypto(
2005 &key_access_plan,
2006 key_operation,
2007 ArtifactProfile::Tdf,
2008 )?;
2009
2010 let key_material = resolve_symmetric_key_for_runtime(
2011 client,
2012 key_source,
2013 key_access_plan.execution.key_transport.as_ref(),
2014 LOCAL_TDF_KEY_REFERENCE,
2015 context,
2016 )?;
2017
2018 Ok(ResolvedTdfArtifactKey {
2019 policy_resolution,
2020 key_access_plan,
2021 key_material,
2022 })
2023}
2024
2025fn require_inline_symmetric_key_for_local_runtime<'a>(
2026 key_source: &'a LocalSymmetricKeySource,
2027 key_transport: Option<&KeyTransportGuidance>,
2028 context: &str,
2029) -> Result<&'a LocalSymmetricKey, SdkError> {
2030 ensure_key_transport_guidance_supports_local_only(key_transport)?;
2031
2032 match key_source {
2033 LocalSymmetricKeySource::Inline(key) => Ok(key),
2034 LocalSymmetricKeySource::ManagedReference(key_reference) => {
2035 Err(SdkError::InvalidInput(format!(
2036 "{context} requested managed key reference {key_reference:?}, but provider-backed symmetric key execution is not implemented yet"
2037 )))
2038 }
2039 }
2040}
2041
2042fn require_inline_symmetric_key_without_transport_guidance<'a>(
2043 key_source: &'a LocalSymmetricKeySource,
2044 context: &str,
2045) -> Result<&'a LocalSymmetricKey, SdkError> {
2046 match key_source {
2047 LocalSymmetricKeySource::Inline(key) => Ok(key),
2048 LocalSymmetricKeySource::ManagedReference(key_reference) => {
2049 Err(SdkError::InvalidInput(format!(
2050 "{context} requested managed key reference {:?}, but this workflow still requires inline symmetric key material before runtime key_transport guidance is available",
2051 key_reference.key_reference()
2052 )))
2053 }
2054 }
2055}
2056
2057fn key_transport_mode_name(mode: KeyTransportMode) -> &'static str {
2058 match mode {
2059 KeyTransportMode::LocalProvided => "local_provided",
2060 KeyTransportMode::WrappedKeyReference => "wrapped_key_reference",
2061 KeyTransportMode::AuthorizedKeyRelease => "authorized_key_release",
2062 KeyTransportMode::KemEncapsulatedCek => "kem_encapsulated_cek",
2063 }
2064}
2065
2066fn ensure_artifact_registration_supports_local_only(
2067 artifact_registration: &SdkArtifactRegisterResponse,
2068) -> Result<(), SdkError> {
2069 if !artifact_registration.registration.accepted {
2070 return Err(SdkError::InvalidInput(
2071 "artifact registration was not accepted".to_string(),
2072 ));
2073 }
2074
2075 if artifact_registration
2076 .registration
2077 .send_plaintext_to_platform
2078 {
2079 return Err(SdkError::InvalidInput(
2080 "artifact registration requested plaintext transport to the platform".to_string(),
2081 ));
2082 }
2083
2084 Ok(())
2085}
2086
2087fn ensure_evidence_ingestion_supports_local_only(
2088 evidence: &SdkEvidenceIngestResponse,
2089) -> Result<(), SdkError> {
2090 if !evidence.ingestion.accepted {
2091 return Err(SdkError::InvalidInput(
2092 "evidence ingestion was not accepted".to_string(),
2093 ));
2094 }
2095
2096 if evidence.ingestion.plaintext_transport != "forbidden_by_default" {
2097 return Err(SdkError::InvalidInput(format!(
2098 "evidence ingestion returned unsupported plaintext transport mode {:?}",
2099 evidence.ingestion.plaintext_transport
2100 )));
2101 }
2102
2103 Ok(())
2104}
2105
2106fn ensure_local_envelope_artifact_valid(artifact: &LocalEnvelopeArtifact) -> Result<(), SdkError> {
2107 if artifact.version != 1 {
2108 return Err(SdkError::InvalidInput(format!(
2109 "unsupported local envelope artifact version {}",
2110 artifact.version
2111 )));
2112 }
2113
2114 if artifact.artifact_profile != ArtifactProfile::Envelope {
2115 return Err(SdkError::InvalidInput(format!(
2116 "unsupported artifact profile {:?} for local envelope access",
2117 artifact.artifact_profile
2118 )));
2119 }
2120
2121 Ok(())
2122}
2123
2124fn ensure_local_envelope_binding_matches_artifact(
2125 artifact: &LocalEnvelopeArtifact,
2126) -> Result<(), SdkError> {
2127 let binding = local_artifact_binding_from_envelope_artifact(artifact)?;
2128 if binding.binding_hash != artifact.binding_hash {
2129 return Err(SdkError::InvalidInput(
2130 "local envelope binding hash does not match the embedded metadata binding".to_string(),
2131 ));
2132 }
2133
2134 Ok(())
2135}
2136
2137fn ensure_local_tdf_artifact_valid(artifact: &LocalTdfArtifact) -> Result<(), SdkError> {
2138 if artifact.version != 1 {
2139 return Err(SdkError::InvalidInput(format!(
2140 "unsupported local TDF artifact version {}",
2141 artifact.version
2142 )));
2143 }
2144
2145 if artifact.artifact_profile != ArtifactProfile::Tdf {
2146 return Err(SdkError::InvalidInput(format!(
2147 "unsupported artifact profile {:?} for local TDF access",
2148 artifact.artifact_profile
2149 )));
2150 }
2151
2152 Ok(())
2153}
2154
2155fn ensure_local_tdf_binding_matches_manifest(
2156 artifact: &LocalTdfArtifact,
2157 manifest: &LocalTdfManifest,
2158) -> Result<(), SdkError> {
2159 let binding = local_artifact_binding_from_tdf_artifact(artifact, manifest)?;
2160 if binding.binding_hash != artifact.binding_hash {
2161 return Err(SdkError::InvalidInput(
2162 "local TDF binding hash does not match the embedded metadata binding".to_string(),
2163 ));
2164 }
2165
2166 Ok(())
2167}
2168
2169fn ensure_local_tdf_policy_context_matches_manifest(
2170 policy_context: &LocalTdfManifest,
2171 manifest: &LocalTdfManifest,
2172) -> Result<(), SdkError> {
2173 if policy_context != manifest {
2174 return Err(SdkError::InvalidInput(
2175 "local TDF policy context does not match the decrypted manifest".to_string(),
2176 ));
2177 }
2178
2179 Ok(())
2180}
2181
2182fn ensure_local_detached_signature_artifact_valid(
2183 artifact: &LocalDetachedSignatureArtifact,
2184) -> Result<(), SdkError> {
2185 if artifact.version != 1 {
2186 return Err(SdkError::InvalidInput(format!(
2187 "unsupported local detached signature artifact version {}",
2188 artifact.version
2189 )));
2190 }
2191
2192 if artifact.artifact_profile != ArtifactProfile::DetachedSignature {
2193 return Err(SdkError::InvalidInput(format!(
2194 "unsupported artifact profile {:?} for detached signature verification",
2195 artifact.artifact_profile
2196 )));
2197 }
2198
2199 Ok(())
2200}
2201
2202fn apply_attribute_edit(attributes: &mut BTreeMap<String, String>, edit: &LocalAttributeEdit) {
2203 for key in &edit.remove {
2204 attributes.remove(key);
2205 }
2206
2207 for (key, value) in &edit.set {
2208 attributes.insert(key.clone(), value.clone());
2209 }
2210}
2211
2212fn rewrite_tdf_artifact(
2213 client: &Client,
2214 current_key_source: &LocalSymmetricKeySource,
2215 new_key_source: &LocalSymmetricKeySource,
2216 artifact_bytes: &[u8],
2217 artifact: LocalTdfArtifact,
2218 manifest: LocalTdfManifest,
2219 meta_version: u64,
2220) -> Result<TdfRewrapResult, SdkError> {
2221 let original_artifact_digest = sha256_prefixed(artifact_bytes);
2222
2223 let bootstrap = client.bootstrap()?;
2224 ensure_bootstrap_supports_local_rewrap(&bootstrap, ArtifactProfile::Tdf)?;
2225
2226 let policy_resolution = client.policy_resolve(&SdkPolicyResolveRequest {
2227 operation: ProtectionOperation::Rewrap,
2228 workload: manifest.workload.clone(),
2229 resource: manifest.resource.clone(),
2230 content_digest: Some(artifact.content_digest.clone()),
2231 content_size_bytes: Some(artifact.content_size_bytes),
2232 purpose: manifest.purpose.clone(),
2233 labels: manifest.labels.clone(),
2234 attributes: manifest.attributes.clone(),
2235 })?;
2236 ensure_rewrap_policy_resolution_supports_local_only(&policy_resolution)?;
2237
2238 let protection_plan = client.protection_plan(&SdkProtectionPlanRequest {
2239 operation: ProtectionOperation::Rewrap,
2240 workload: manifest.workload.clone(),
2241 resource: manifest.resource.clone(),
2242 preferred_artifact_profile: Some(ArtifactProfile::Tdf),
2243 content_digest: Some(artifact.content_digest.clone()),
2244 content_size_bytes: Some(artifact.content_size_bytes),
2245 purpose: manifest.purpose.clone(),
2246 labels: manifest.labels.clone(),
2247 attributes: manifest.attributes.clone(),
2248 })?;
2249 ensure_rewrap_protection_plan_supports_local_only(&protection_plan)?;
2250
2251 let key_access_plan = client.key_access_plan(&SdkKeyAccessPlanRequest {
2252 operation: KeyAccessOperation::Rewrap,
2253 workload: manifest.workload.clone(),
2254 resource: manifest.resource.clone(),
2255 artifact_profile: Some(ArtifactProfile::Tdf),
2256 key_reference: Some(
2257 current_key_source
2258 .key_reference(LOCAL_TDF_KEY_REFERENCE)
2259 .to_string(),
2260 ),
2261 content_digest: Some(artifact.content_digest.clone()),
2262 purpose: manifest.purpose.clone(),
2263 labels: manifest.labels.clone(),
2264 attributes: manifest.attributes.clone(),
2265 })?;
2266 ensure_key_access_plan_supports_local_crypto(
2267 &key_access_plan,
2268 KeyAccessOperation::Rewrap,
2269 ArtifactProfile::Tdf,
2270 )?;
2271
2272 let current_key = resolve_symmetric_key_for_runtime(
2273 client,
2274 current_key_source,
2275 key_access_plan.execution.key_transport.as_ref(),
2276 LOCAL_TDF_KEY_REFERENCE,
2277 "local TDF rewrap",
2278 )?;
2279 let new_key = resolve_symmetric_key_for_runtime(
2280 client,
2281 new_key_source,
2282 key_access_plan.execution.key_transport.as_ref(),
2283 LOCAL_TDF_KEY_REFERENCE,
2284 "local TDF rewrap",
2285 )?;
2286
2287 let plaintext = decrypt_tdf_payload(¤t_key.key, &artifact)?;
2288 let decrypted_digest = sha256_prefixed(&plaintext);
2289 if decrypted_digest != artifact.content_digest {
2290 return Err(SdkError::InvalidInput(
2291 "local TDF decrypted bytes do not match the embedded content digest".to_string(),
2292 ));
2293 }
2294
2295 let decrypted_size = u64::try_from(plaintext.len()).map_err(|_| {
2296 SdkError::InvalidInput("decrypted content length exceeds supported u64 range".to_string())
2297 })?;
2298 if decrypted_size != artifact.content_size_bytes {
2299 return Err(SdkError::InvalidInput(
2300 "local TDF decrypted bytes do not match the embedded content size".to_string(),
2301 ));
2302 }
2303
2304 let content_binding = LocalContentBinding {
2305 tenant_id: artifact.tenant_id.clone(),
2306 content_digest: artifact.content_digest.clone(),
2307 content_size_bytes: artifact.content_size_bytes,
2308 raw_cid: artifact.raw_cid.clone(),
2309 };
2310 let request = LocalProtectionRequest {
2311 workload: manifest.workload.clone(),
2312 resource: manifest.resource.clone(),
2313 preferred_artifact_profile: Some(ArtifactProfile::Tdf),
2314 purpose: manifest.purpose.clone(),
2315 labels: manifest.labels.clone(),
2316 attributes: manifest.attributes.clone(),
2317 };
2318 let prepared = PreparedLocalProtection {
2319 caller: protection_plan.caller.clone(),
2320 content_binding: content_binding.clone(),
2321 artifact_binding: build_local_artifact_binding(
2322 &content_binding,
2323 &request,
2324 &policy_resolution.handling.bind_policy_to,
2325 )?,
2326 bootstrap,
2327 policy_resolution: policy_resolution.clone(),
2328 protection_plan: protection_plan.clone(),
2329 };
2330
2331 let tdf = encrypt_tdf_artifact(&new_key.key, &plaintext, &prepared, &request, meta_version)?;
2332 let new_artifact_bytes = serde_json::to_vec(&tdf).map_err(|error| {
2333 SdkError::Serialization(format!("failed to serialize local TDF artifact: {error}"))
2334 })?;
2335 let artifact_digest = sha256_prefixed(&new_artifact_bytes);
2336
2337 let artifact_registration = client.artifact_register(&SdkArtifactRegisterRequest {
2338 operation: ProtectionOperation::Rewrap,
2339 workload: manifest.workload.clone(),
2340 resource: manifest.resource.clone(),
2341 artifact_profile: ArtifactProfile::Tdf,
2342 artifact_digest: artifact_digest.clone(),
2343 artifact_locator: None,
2344 decision_id: None,
2345 key_reference: Some(new_key.key_reference),
2346 purpose: manifest.purpose.clone(),
2347 labels: manifest.labels.clone(),
2348 attributes: manifest.attributes.clone(),
2349 })?;
2350 ensure_artifact_registration_supports_local_only(&artifact_registration)?;
2351
2352 let evidence = client.evidence(&SdkEvidenceIngestRequest {
2353 event_type: EvidenceEventType::Rewrap,
2354 workload: manifest.workload.clone(),
2355 resource: manifest.resource.clone(),
2356 artifact_profile: Some(ArtifactProfile::Tdf),
2357 artifact_digest: Some(artifact_digest.clone()),
2358 decision_id: None,
2359 outcome: Some("success".to_string()),
2360 occurred_at: None,
2361 purpose: manifest.purpose.clone(),
2362 labels: manifest.labels.clone(),
2363 attributes: manifest.attributes.clone(),
2364 })?;
2365 ensure_evidence_ingestion_supports_local_only(&evidence)?;
2366
2367 Ok(TdfRewrapResult {
2368 content_binding,
2369 manifest,
2370 policy_resolution,
2371 protection_plan,
2372 key_access_plan,
2373 original_artifact_digest,
2374 artifact: ProtectedTdfArtifact {
2375 tdf,
2376 artifact_bytes: new_artifact_bytes,
2377 artifact_digest,
2378 },
2379 artifact_registration,
2380 evidence,
2381 })
2382}
2383
2384fn encrypt_envelope_artifact(
2385 key: &LocalSymmetricKey,
2386 plaintext: &[u8],
2387 prepared: &PreparedLocalProtection,
2388 request: &LocalProtectionRequest,
2389) -> Result<LocalEnvelopeArtifact, SdkError> {
2390 let binding_hash = prepared.artifact_binding.binding_hash.clone();
2391 let aad = envelope_aad_bytes(&EnvelopeAadBinding {
2392 tenant_id: &prepared.content_binding.tenant_id,
2393 raw_cid: &prepared.content_binding.raw_cid,
2394 content_digest: &prepared.content_binding.content_digest,
2395 artifact_profile: ArtifactProfile::Envelope,
2396 workload: &request.workload,
2397 resource: &request.resource,
2398 purpose: &request.purpose,
2399 labels: &request.labels,
2400 attributes: &request.attributes,
2401 binding_targets: &prepared.artifact_binding.binding_targets,
2402 binding_hash: &binding_hash,
2403 })?;
2404 let aad_hash = sha256_prefixed(&aad);
2405
2406 let mut nonce_bytes = [0u8; 12];
2407 rand::rngs::OsRng.fill_bytes(&mut nonce_bytes);
2408 let nonce = Nonce::from_slice(&nonce_bytes);
2409
2410 let cipher = Aes256Gcm::new_from_slice(key.as_bytes()).map_err(|error| {
2411 SdkError::InvalidInput(format!("failed to initialize envelope cipher: {error}"))
2412 })?;
2413 let ciphertext = cipher
2414 .encrypt(
2415 nonce,
2416 aes_gcm::aead::Payload {
2417 msg: plaintext,
2418 aad: &aad,
2419 },
2420 )
2421 .map_err(|error| SdkError::Server(format!("local envelope encryption failed: {error}")))?;
2422
2423 Ok(LocalEnvelopeArtifact {
2424 version: 1,
2425 artifact_profile: ArtifactProfile::Envelope,
2426 algorithm: LocalEnvelopeAlgorithm::Aes256Gcm,
2427 tenant_id: prepared.content_binding.tenant_id.clone(),
2428 raw_cid: prepared.content_binding.raw_cid.clone(),
2429 content_digest: prepared.content_binding.content_digest.clone(),
2430 content_size_bytes: prepared.content_binding.content_size_bytes,
2431 workload: request.workload.clone(),
2432 resource: request.resource.clone(),
2433 purpose: request.purpose.clone(),
2434 labels: request.labels.clone(),
2435 attributes: request.attributes.clone(),
2436 binding_targets: prepared.artifact_binding.binding_targets.clone(),
2437 binding_hash,
2438 nonce_b64: BASE64_STANDARD.encode(nonce_bytes),
2439 aad_hash,
2440 ciphertext_b64: BASE64_STANDARD.encode(ciphertext),
2441 })
2442}
2443
2444fn decrypt_envelope_artifact(
2445 key: &LocalSymmetricKey,
2446 artifact: &LocalEnvelopeArtifact,
2447) -> Result<Vec<u8>, SdkError> {
2448 let aad = envelope_aad_bytes(&EnvelopeAadBinding {
2449 tenant_id: &artifact.tenant_id,
2450 raw_cid: &artifact.raw_cid,
2451 content_digest: &artifact.content_digest,
2452 artifact_profile: ArtifactProfile::Envelope,
2453 workload: &artifact.workload,
2454 resource: &artifact.resource,
2455 purpose: &artifact.purpose,
2456 labels: &artifact.labels,
2457 attributes: &artifact.attributes,
2458 binding_targets: &artifact.binding_targets,
2459 binding_hash: &artifact.binding_hash,
2460 })?;
2461
2462 let expected_aad_hash = sha256_prefixed(&aad);
2463 if expected_aad_hash != artifact.aad_hash {
2464 return Err(SdkError::InvalidInput(
2465 "local envelope artifact AAD hash does not match the embedded metadata binding"
2466 .to_string(),
2467 ));
2468 }
2469
2470 ensure_local_envelope_binding_matches_artifact(artifact)?;
2471
2472 let nonce_bytes = BASE64_STANDARD
2473 .decode(&artifact.nonce_b64)
2474 .map_err(|error| {
2475 SdkError::Serialization(format!("failed to decode local envelope nonce: {error}"))
2476 })?;
2477 let ciphertext = BASE64_STANDARD
2478 .decode(&artifact.ciphertext_b64)
2479 .map_err(|error| {
2480 SdkError::Serialization(format!(
2481 "failed to decode local envelope ciphertext: {error}"
2482 ))
2483 })?;
2484 let nonce_array: [u8; 12] = nonce_bytes.try_into().map_err(|_| {
2485 SdkError::Serialization("local envelope nonce must be 12 bytes".to_string())
2486 })?;
2487
2488 let cipher = Aes256Gcm::new_from_slice(key.as_bytes()).map_err(|error| {
2489 SdkError::InvalidInput(format!("failed to initialize envelope cipher: {error}"))
2490 })?;
2491 cipher
2492 .decrypt(
2493 Nonce::from_slice(&nonce_array),
2494 aes_gcm::aead::Payload {
2495 msg: &ciphertext,
2496 aad: &aad,
2497 },
2498 )
2499 .map_err(|error| SdkError::Server(format!("local envelope decryption failed: {error}")))
2500}
2501
2502fn encrypt_tdf_artifact(
2503 key: &LocalSymmetricKey,
2504 plaintext: &[u8],
2505 prepared: &PreparedLocalProtection,
2506 request: &LocalProtectionRequest,
2507 meta_version: u64,
2508) -> Result<LocalTdfArtifact, SdkError> {
2509 let manifest = LocalTdfManifest {
2510 workload: request.workload.clone(),
2511 resource: request.resource.clone(),
2512 purpose: request.purpose.clone(),
2513 labels: request.labels.clone(),
2514 attributes: request.attributes.clone(),
2515 };
2516 let manifest_bytes = serde_json::to_vec(&manifest).map_err(|error| {
2517 SdkError::Serialization(format!("failed to serialize local TDF manifest: {error}"))
2518 })?;
2519 let manifest_digest = sha256_prefixed(&manifest_bytes);
2520 let binding_hash = prepared.artifact_binding.binding_hash.clone();
2521 let aad = tdf_aad_bytes(
2522 &prepared.content_binding.tenant_id,
2523 &prepared.content_binding.raw_cid,
2524 &prepared.content_binding.content_digest,
2525 prepared.content_binding.content_size_bytes,
2526 &manifest_digest,
2527 &binding_hash,
2528 )?;
2529 let aad_hash = sha256_prefixed(&aad);
2530
2531 let cipher = Aes256Gcm::new_from_slice(key.as_bytes()).map_err(|error| {
2532 SdkError::InvalidInput(format!("failed to initialize TDF cipher: {error}"))
2533 })?;
2534
2535 let mut manifest_nonce_bytes = [0u8; 12];
2536 rand::rngs::OsRng.fill_bytes(&mut manifest_nonce_bytes);
2537 let manifest_nonce = Nonce::from_slice(&manifest_nonce_bytes);
2538 let manifest_ciphertext = cipher
2539 .encrypt(
2540 manifest_nonce,
2541 aes_gcm::aead::Payload {
2542 msg: &manifest_bytes,
2543 aad: &aad,
2544 },
2545 )
2546 .map_err(|error| {
2547 SdkError::Server(format!("local TDF manifest encryption failed: {error}"))
2548 })?;
2549
2550 let mut payload_nonce_bytes = [0u8; 12];
2551 rand::rngs::OsRng.fill_bytes(&mut payload_nonce_bytes);
2552 let payload_nonce = Nonce::from_slice(&payload_nonce_bytes);
2553 let payload_ciphertext = cipher
2554 .encrypt(
2555 payload_nonce,
2556 aes_gcm::aead::Payload {
2557 msg: plaintext,
2558 aad: &aad,
2559 },
2560 )
2561 .map_err(|error| {
2562 SdkError::Server(format!("local TDF payload encryption failed: {error}"))
2563 })?;
2564
2565 Ok(LocalTdfArtifact {
2566 version: 1,
2567 meta_version,
2568 artifact_profile: ArtifactProfile::Tdf,
2569 algorithm: LocalTdfAlgorithm::Aes256Gcm,
2570 tenant_id: prepared.content_binding.tenant_id.clone(),
2571 raw_cid: prepared.content_binding.raw_cid.clone(),
2572 content_digest: prepared.content_binding.content_digest.clone(),
2573 content_size_bytes: prepared.content_binding.content_size_bytes,
2574 manifest_digest,
2575 binding_targets: prepared.artifact_binding.binding_targets.clone(),
2576 binding_hash,
2577 policy_context: Some(manifest),
2578 manifest_nonce_b64: BASE64_STANDARD.encode(manifest_nonce_bytes),
2579 manifest_ciphertext_b64: BASE64_STANDARD.encode(manifest_ciphertext),
2580 payload_nonce_b64: BASE64_STANDARD.encode(payload_nonce_bytes),
2581 payload_ciphertext_b64: BASE64_STANDARD.encode(payload_ciphertext),
2582 aad_hash,
2583 })
2584}
2585
2586fn decrypt_tdf_manifest(
2587 key: &LocalSymmetricKey,
2588 artifact: &LocalTdfArtifact,
2589) -> Result<LocalTdfManifest, SdkError> {
2590 let aad = tdf_aad_bytes(
2591 &artifact.tenant_id,
2592 &artifact.raw_cid,
2593 &artifact.content_digest,
2594 artifact.content_size_bytes,
2595 &artifact.manifest_digest,
2596 &artifact.binding_hash,
2597 )?;
2598 let expected_aad_hash = sha256_prefixed(&aad);
2599 if expected_aad_hash != artifact.aad_hash {
2600 return Err(SdkError::InvalidInput(
2601 "local TDF artifact AAD hash does not match the embedded metadata binding".to_string(),
2602 ));
2603 }
2604
2605 let nonce_bytes = BASE64_STANDARD
2606 .decode(&artifact.manifest_nonce_b64)
2607 .map_err(|error| {
2608 SdkError::Serialization(format!(
2609 "failed to decode local TDF manifest nonce: {error}"
2610 ))
2611 })?;
2612 let ciphertext = BASE64_STANDARD
2613 .decode(&artifact.manifest_ciphertext_b64)
2614 .map_err(|error| {
2615 SdkError::Serialization(format!(
2616 "failed to decode local TDF manifest ciphertext: {error}"
2617 ))
2618 })?;
2619 let nonce_array: [u8; 12] = nonce_bytes.try_into().map_err(|_| {
2620 SdkError::Serialization("local TDF manifest nonce must be 12 bytes".to_string())
2621 })?;
2622
2623 let cipher = Aes256Gcm::new_from_slice(key.as_bytes()).map_err(|error| {
2624 SdkError::InvalidInput(format!("failed to initialize TDF cipher: {error}"))
2625 })?;
2626 let manifest_bytes = cipher
2627 .decrypt(
2628 Nonce::from_slice(&nonce_array),
2629 aes_gcm::aead::Payload {
2630 msg: &ciphertext,
2631 aad: &aad,
2632 },
2633 )
2634 .map_err(|error| {
2635 SdkError::Server(format!("local TDF manifest decryption failed: {error}"))
2636 })?;
2637
2638 let manifest_digest = sha256_prefixed(&manifest_bytes);
2639 if manifest_digest != artifact.manifest_digest {
2640 return Err(SdkError::InvalidInput(
2641 "local TDF manifest digest does not match the embedded metadata binding".to_string(),
2642 ));
2643 }
2644
2645 serde_json::from_slice(&manifest_bytes).map_err(|error| {
2646 SdkError::Serialization(format!("failed to decode local TDF manifest: {error}"))
2647 })
2648}
2649
2650fn decrypt_tdf_payload(
2651 key: &LocalSymmetricKey,
2652 artifact: &LocalTdfArtifact,
2653) -> Result<Vec<u8>, SdkError> {
2654 let aad = tdf_aad_bytes(
2655 &artifact.tenant_id,
2656 &artifact.raw_cid,
2657 &artifact.content_digest,
2658 artifact.content_size_bytes,
2659 &artifact.manifest_digest,
2660 &artifact.binding_hash,
2661 )?;
2662 let expected_aad_hash = sha256_prefixed(&aad);
2663 if expected_aad_hash != artifact.aad_hash {
2664 return Err(SdkError::InvalidInput(
2665 "local TDF artifact AAD hash does not match the embedded metadata binding".to_string(),
2666 ));
2667 }
2668
2669 let nonce_bytes = BASE64_STANDARD
2670 .decode(&artifact.payload_nonce_b64)
2671 .map_err(|error| {
2672 SdkError::Serialization(format!("failed to decode local TDF payload nonce: {error}"))
2673 })?;
2674 let ciphertext = BASE64_STANDARD
2675 .decode(&artifact.payload_ciphertext_b64)
2676 .map_err(|error| {
2677 SdkError::Serialization(format!(
2678 "failed to decode local TDF payload ciphertext: {error}"
2679 ))
2680 })?;
2681 let nonce_array: [u8; 12] = nonce_bytes.try_into().map_err(|_| {
2682 SdkError::Serialization("local TDF payload nonce must be 12 bytes".to_string())
2683 })?;
2684
2685 let cipher = Aes256Gcm::new_from_slice(key.as_bytes()).map_err(|error| {
2686 SdkError::InvalidInput(format!("failed to initialize TDF cipher: {error}"))
2687 })?;
2688 cipher
2689 .decrypt(
2690 Nonce::from_slice(&nonce_array),
2691 aes_gcm::aead::Payload {
2692 msg: &ciphertext,
2693 aad: &aad,
2694 },
2695 )
2696 .map_err(|error| SdkError::Server(format!("local TDF payload decryption failed: {error}")))
2697}
2698
2699fn sign_detached_signature_artifact(
2700 signing_key: &LocalSigningKey,
2701 prepared: &PreparedLocalProtection,
2702 request: &LocalProtectionRequest,
2703) -> Result<LocalDetachedSignatureArtifact, SdkError> {
2704 let binding_bytes = local_artifact_binding_bytes(&prepared.artifact_binding)?;
2705 let binding_hash = prepared.artifact_binding.binding_hash.clone();
2706
2707 let signer = Ed25519SigningKey::from_bytes(signing_key.as_bytes());
2708 let verifying_key = signer.verifying_key();
2709 let signature = signer.sign(&binding_bytes);
2710 let public_key_bytes = verifying_key.to_bytes();
2711
2712 Ok(LocalDetachedSignatureArtifact {
2713 version: 1,
2714 artifact_profile: ArtifactProfile::DetachedSignature,
2715 algorithm: LocalDetachedSignatureAlgorithm::Ed25519,
2716 tenant_id: prepared.content_binding.tenant_id.clone(),
2717 raw_cid: prepared.content_binding.raw_cid.clone(),
2718 content_digest: prepared.content_binding.content_digest.clone(),
2719 content_size_bytes: prepared.content_binding.content_size_bytes,
2720 workload: request.workload.clone(),
2721 resource: request.resource.clone(),
2722 purpose: request.purpose.clone(),
2723 labels: request.labels.clone(),
2724 attributes: request.attributes.clone(),
2725 binding_targets: prepared.artifact_binding.binding_targets.clone(),
2726 signer_key_id: signer_key_id_from_public_key(&public_key_bytes),
2727 signer_public_key_b64: BASE64_STANDARD.encode(public_key_bytes),
2728 binding_hash,
2729 signature_b64: BASE64_STANDARD.encode(signature.to_bytes()),
2730 })
2731}
2732
2733fn verify_detached_signature_artifact(
2734 verifying_key: &LocalVerifyingKey,
2735 artifact: &LocalDetachedSignatureArtifact,
2736 content_binding: &LocalContentBinding,
2737) -> Result<(), SdkError> {
2738 let binding =
2739 local_artifact_binding_from_detached_signature_artifact(artifact, content_binding)?;
2740 let binding_bytes = local_artifact_binding_bytes(&binding)?;
2741 let expected_binding_hash = binding.binding_hash;
2742 if expected_binding_hash != artifact.binding_hash {
2743 return Err(SdkError::InvalidInput(
2744 "detached signature binding hash does not match the embedded metadata binding"
2745 .to_string(),
2746 ));
2747 }
2748
2749 let signature_bytes = BASE64_STANDARD
2750 .decode(&artifact.signature_b64)
2751 .map_err(|error| {
2752 SdkError::Serialization(format!(
2753 "failed to decode detached signature bytes: {error}"
2754 ))
2755 })?;
2756 let signature = Ed25519Signature::from_slice(&signature_bytes).map_err(|error| {
2757 SdkError::Serialization(format!("invalid detached signature bytes: {error}"))
2758 })?;
2759
2760 let verifying_key =
2761 Ed25519VerifyingKey::from_bytes(verifying_key.as_bytes()).map_err(|error| {
2762 SdkError::InvalidInput(format!("invalid Ed25519 verifying key: {error}"))
2763 })?;
2764 verifying_key
2765 .verify(&binding_bytes, &signature)
2766 .map_err(|error| {
2767 SdkError::InvalidInput(format!("detached signature verification failed: {error}"))
2768 })
2769}
2770
2771fn envelope_aad_bytes(binding: &EnvelopeAadBinding<'_>) -> Result<Vec<u8>, SdkError> {
2772 serde_json::to_vec(binding).map_err(|error| {
2773 SdkError::Serialization(format!("failed to serialize envelope AAD binding: {error}"))
2774 })
2775}
2776
2777fn tdf_aad_bytes(
2778 tenant_id: &str,
2779 raw_cid: &str,
2780 content_digest: &str,
2781 content_size_bytes: u64,
2782 manifest_digest: &str,
2783 binding_hash: &str,
2784) -> Result<Vec<u8>, SdkError> {
2785 serde_json::to_vec(&TdfAadBinding {
2786 tenant_id,
2787 raw_cid,
2788 content_digest,
2789 content_size_bytes,
2790 manifest_digest,
2791 artifact_profile: ArtifactProfile::Tdf,
2792 binding_hash,
2793 })
2794 .map_err(|error| {
2795 SdkError::Serialization(format!("failed to serialize TDF AAD binding: {error}"))
2796 })
2797}
2798
2799fn build_local_artifact_binding(
2800 content_binding: &LocalContentBinding,
2801 request: &LocalProtectionRequest,
2802 binding_targets: &[String],
2803) -> Result<LocalArtifactBinding, SdkError> {
2804 let mut artifact_binding = LocalArtifactBinding {
2805 version: default_binding_version(),
2806 tenant_id: content_binding.tenant_id.clone(),
2807 raw_cid: content_binding.raw_cid.clone(),
2808 content_digest: content_binding.content_digest.clone(),
2809 content_size_bytes: content_binding.content_size_bytes,
2810 workload: request.workload.clone(),
2811 resource: request.resource.clone(),
2812 purpose: request.purpose.clone(),
2813 labels: request.labels.clone(),
2814 attributes: request.attributes.clone(),
2815 binding_targets: binding_targets.to_vec(),
2816 binding_hash: String::new(),
2817 };
2818 artifact_binding.binding_hash =
2819 sha256_prefixed(&local_artifact_binding_bytes(&artifact_binding)?);
2820 Ok(artifact_binding)
2821}
2822
2823fn local_artifact_binding_bytes(binding: &LocalArtifactBinding) -> Result<Vec<u8>, SdkError> {
2824 serde_json::to_vec(&LocalArtifactBindingPayload {
2825 version: binding.version,
2826 tenant_id: &binding.tenant_id,
2827 raw_cid: &binding.raw_cid,
2828 content_digest: &binding.content_digest,
2829 content_size_bytes: binding.content_size_bytes,
2830 workload: &binding.workload,
2831 resource: &binding.resource,
2832 purpose: &binding.purpose,
2833 labels: &binding.labels,
2834 attributes: &binding.attributes,
2835 binding_targets: &binding.binding_targets,
2836 })
2837 .map_err(|error| {
2838 SdkError::Serialization(format!(
2839 "failed to serialize local artifact binding: {error}"
2840 ))
2841 })
2842}
2843
2844fn local_artifact_binding_from_envelope_artifact(
2845 artifact: &LocalEnvelopeArtifact,
2846) -> Result<LocalArtifactBinding, SdkError> {
2847 build_local_artifact_binding(
2848 &LocalContentBinding {
2849 tenant_id: artifact.tenant_id.clone(),
2850 content_digest: artifact.content_digest.clone(),
2851 content_size_bytes: artifact.content_size_bytes,
2852 raw_cid: artifact.raw_cid.clone(),
2853 },
2854 &LocalProtectionRequest {
2855 workload: artifact.workload.clone(),
2856 resource: artifact.resource.clone(),
2857 preferred_artifact_profile: Some(ArtifactProfile::Envelope),
2858 purpose: artifact.purpose.clone(),
2859 labels: artifact.labels.clone(),
2860 attributes: artifact.attributes.clone(),
2861 },
2862 &artifact.binding_targets,
2863 )
2864}
2865
2866fn local_artifact_binding_from_tdf_artifact(
2867 artifact: &LocalTdfArtifact,
2868 manifest: &LocalTdfManifest,
2869) -> Result<LocalArtifactBinding, SdkError> {
2870 build_local_artifact_binding(
2871 &LocalContentBinding {
2872 tenant_id: artifact.tenant_id.clone(),
2873 content_digest: artifact.content_digest.clone(),
2874 content_size_bytes: artifact.content_size_bytes,
2875 raw_cid: artifact.raw_cid.clone(),
2876 },
2877 &LocalProtectionRequest {
2878 workload: manifest.workload.clone(),
2879 resource: manifest.resource.clone(),
2880 preferred_artifact_profile: Some(ArtifactProfile::Tdf),
2881 purpose: manifest.purpose.clone(),
2882 labels: manifest.labels.clone(),
2883 attributes: manifest.attributes.clone(),
2884 },
2885 &artifact.binding_targets,
2886 )
2887}
2888
2889fn local_artifact_binding_from_detached_signature_artifact(
2890 artifact: &LocalDetachedSignatureArtifact,
2891 content_binding: &LocalContentBinding,
2892) -> Result<LocalArtifactBinding, SdkError> {
2893 build_local_artifact_binding(
2894 content_binding,
2895 &LocalProtectionRequest {
2896 workload: artifact.workload.clone(),
2897 resource: artifact.resource.clone(),
2898 preferred_artifact_profile: Some(ArtifactProfile::DetachedSignature),
2899 purpose: artifact.purpose.clone(),
2900 labels: artifact.labels.clone(),
2901 attributes: artifact.attributes.clone(),
2902 },
2903 &artifact.binding_targets,
2904 )
2905}
2906
2907fn signer_key_id_from_public_key(public_key_bytes: &[u8]) -> String {
2908 sha256_prefixed(public_key_bytes)
2909}