Skip to main content

reallyme_crypto/secret_material/
policy.rs

1// SPDX-FileCopyrightText: 2026 ReallyMe LLC
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use super::{
6    BufferOwner, DestructionPolicy, ExportPolicy, OutputMaterial, RetentionPolicy,
7    SecretSensitivity, ZeroizationPolicy,
8};
9use crypto_core::RngOutputKind;
10
11/// Semantic operation whose entry point binds a secret-material policy.
12///
13/// Discriminants are explicit because downstream policy tooling may persist or
14/// compare the numeric form. New operations must use a previously unused value
15/// so an additive release cannot silently reinterpret an established operation.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum SecretMaterialOperation {
19    /// Authenticated encryption.
20    AeadSeal = 0,
21    /// Authenticated decryption.
22    AeadOpen = 1,
23    /// Constant-time comparison.
24    ConstantTimeCompare = 2,
25    /// Cryptographic hashing.
26    Hash = 3,
27    /// Message authentication tag generation.
28    MacAuthenticate = 4,
29    /// Message authentication tag verification.
30    MacVerify = 5,
31    /// Password or key derivation.
32    KeyDerivation = 6,
33    /// Symmetric key wrapping.
34    KeyWrap = 7,
35    /// Symmetric key unwrapping.
36    KeyUnwrap = 8,
37    /// Signature keypair generation.
38    SignatureKeyGeneration = 9,
39    /// Signature keypair derivation.
40    SignatureKeyDerivation = 10,
41    /// Signature creation.
42    SignatureSign = 11,
43    /// Signature verification.
44    SignatureVerify = 12,
45    /// Public signature representation validation or normalization.
46    SignatureEncoding = 31,
47    /// Key-agreement keypair generation.
48    KeyAgreementKeyGeneration = 13,
49    /// Key-agreement keypair derivation.
50    KeyAgreementKeyDerivation = 14,
51    /// Shared-secret derivation.
52    KeyAgreementSharedSecret = 15,
53    /// KEM keypair generation.
54    KemKeyGeneration = 16,
55    /// KEM keypair derivation.
56    KemKeyDerivation = 17,
57    /// KEM encapsulation.
58    KemEncapsulate = 18,
59    /// KEM decapsulation.
60    KemDecapsulate = 19,
61    /// HPKE keypair generation.
62    HpkeKeyGeneration = 20,
63    /// HPKE keypair derivation.
64    HpkeKeyDerivation = 21,
65    /// Establishment of a live, non-exportable HPKE sender context.
66    HpkeSenderSetup = 22,
67    /// Establishment of a live, non-exportable HPKE receiver context.
68    HpkeReceiverSetup = 23,
69    /// HPKE sealing.
70    HpkeSeal = 24,
71    /// HPKE opening.
72    HpkeOpen = 25,
73    /// HPKE exporter-secret derivation.
74    HpkeExport = 26,
75    /// Fill caller-owned storage with cryptographically secure random bytes.
76    RandomFill = 27,
77    /// Generate an AEAD nonce.
78    RandomNonce = 28,
79    /// Generate an Argon2 salt.
80    RandomSalt = 29,
81    /// Public-key encoding or decoding.
82    PublicKeyEncoding = 30,
83}
84
85/// Ownership, retention, export, and destruction rules for one buffer.
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87#[non_exhaustive]
88pub struct MaterialPolicy {
89    /// Layer responsible for the buffer.
90    pub owner: BufferOwner,
91    /// Sensitivity of its contents.
92    pub sensitivity: SecretSensitivity,
93    /// Maximum intended lifetime.
94    pub retention: RetentionPolicy,
95    /// Required zeroization behavior.
96    pub zeroization: ZeroizationPolicy,
97    /// Whether and how it may cross the boundary.
98    pub export: ExportPolicy,
99    /// Required destruction behavior.
100    pub destruction: DestructionPolicy,
101}
102
103/// Complete policy bound to a semantic operation entry point.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105#[non_exhaustive]
106pub struct OperationSecretMaterialPolicy {
107    /// Caller-supplied or decoded operation input.
108    pub input: MaterialPolicy,
109    /// Semantic operation result before adapter encoding.
110    pub output: MaterialPolicy,
111    /// Encoded operation request bytes.
112    pub request_wire: MaterialPolicy,
113    /// Encoded operation response bytes.
114    pub response_wire: MaterialPolicy,
115    /// Security-relevant class of the semantic result.
116    pub output_material: OutputMaterial,
117}
118
119/// Return the reviewed material policy for one semantic operation.
120#[must_use]
121pub const fn operation_secret_material_policy(
122    operation: SecretMaterialOperation,
123) -> OperationSecretMaterialPolicy {
124    match operation {
125        SecretMaterialOperation::AeadSeal | SecretMaterialOperation::KeyWrap => {
126            secret_input_sensitive_output(OutputMaterial::SensitivePublic)
127        }
128        SecretMaterialOperation::AeadOpen
129        | SecretMaterialOperation::KeyUnwrap
130        | SecretMaterialOperation::KeyDerivation
131        | SecretMaterialOperation::KeyAgreementSharedSecret
132        | SecretMaterialOperation::KemDecapsulate
133        | SecretMaterialOperation::HpkeOpen
134        | SecretMaterialOperation::HpkeExport => secret_output(OutputMaterial::Secret),
135        SecretMaterialOperation::RandomFill => caller_owned_secret_output(),
136        SecretMaterialOperation::RandomNonce => public_operation(),
137        SecretMaterialOperation::RandomSalt => {
138            public_input_sensitive_output(OutputMaterial::SensitivePublic)
139        }
140        SecretMaterialOperation::SignatureKeyGeneration
141        | SecretMaterialOperation::KeyAgreementKeyGeneration
142        | SecretMaterialOperation::KemKeyGeneration
143        | SecretMaterialOperation::KemEncapsulate
144        | SecretMaterialOperation::HpkeKeyGeneration => {
145            public_input_secret_output(OutputMaterial::Mixed)
146        }
147        SecretMaterialOperation::SignatureKeyDerivation
148        | SecretMaterialOperation::KeyAgreementKeyDerivation
149        | SecretMaterialOperation::KemKeyDerivation
150        | SecretMaterialOperation::HpkeKeyDerivation => secret_output(OutputMaterial::Mixed),
151        SecretMaterialOperation::HpkeSenderSetup | SecretMaterialOperation::HpkeReceiverSetup => {
152            secret_input_non_exportable_output(OutputMaterial::Mixed)
153        }
154        SecretMaterialOperation::HpkeSeal => {
155            secret_input_sensitive_output(OutputMaterial::SensitivePublic)
156        }
157        SecretMaterialOperation::Hash
158        | SecretMaterialOperation::ConstantTimeCompare
159        | SecretMaterialOperation::MacAuthenticate
160        | SecretMaterialOperation::MacVerify
161        | SecretMaterialOperation::SignatureSign => {
162            secret_input_public_output(OutputMaterial::Public)
163        }
164        SecretMaterialOperation::SignatureVerify
165        | SecretMaterialOperation::SignatureEncoding
166        | SecretMaterialOperation::PublicKeyEncoding => public_operation(),
167    }
168}
169
170/// Return the reviewed caller-owned output policy for one random purpose.
171#[must_use]
172pub const fn random_fill_secret_material_policy(
173    kind: RngOutputKind,
174) -> OperationSecretMaterialPolicy {
175    match kind {
176        RngOutputKind::AeadNonce12 => caller_owned_public_output(),
177        RngOutputKind::Argon2Salt16 | RngOutputKind::Argon2Salt32 => {
178            caller_owned_sensitive_output()
179        }
180        RngOutputKind::Generic
181        | RngOutputKind::Aes256GcmKey
182        | RngOutputKind::MlKem1024Seed
183        | RngOutputKind::MlDsa87Seed
184        | RngOutputKind::Ed25519Seed
185        | RngOutputKind::SlhDsaSha2_128sSeed => caller_owned_secret_output(),
186        _ => caller_owned_secret_output(),
187    }
188}
189
190/// Bind a reviewed policy in production code without retaining runtime data.
191///
192/// Keeping this call in every semantic entry point makes policy omissions
193/// mechanically searchable and prevents tests from being the only evidence.
194#[inline]
195pub(crate) const fn bind_operation_policy(
196    operation: SecretMaterialOperation,
197) -> OperationSecretMaterialPolicy {
198    operation_secret_material_policy(operation)
199}
200
201#[inline]
202#[cfg(feature = "csprng")]
203pub(crate) const fn bind_random_fill_policy(kind: RngOutputKind) -> OperationSecretMaterialPolicy {
204    random_fill_secret_material_policy(kind)
205}
206
207const fn secret_input_public_output(
208    output_material: OutputMaterial,
209) -> OperationSecretMaterialPolicy {
210    operation_policy(
211        borrowed_secret(),
212        public_result(),
213        secret_wire(),
214        public_wire(),
215        output_material,
216    )
217}
218
219const fn secret_input_sensitive_output(
220    output_material: OutputMaterial,
221) -> OperationSecretMaterialPolicy {
222    operation_policy(
223        borrowed_secret(),
224        sensitive_result(),
225        secret_wire(),
226        sensitive_wire(),
227        output_material,
228    )
229}
230
231const fn secret_output(output_material: OutputMaterial) -> OperationSecretMaterialPolicy {
232    operation_policy(
233        borrowed_secret(),
234        owned_secret(),
235        secret_wire(),
236        secret_wire(),
237        output_material,
238    )
239}
240
241const fn secret_input_non_exportable_output(
242    output_material: OutputMaterial,
243) -> OperationSecretMaterialPolicy {
244    operation_policy(
245        borrowed_secret(),
246        MaterialPolicy {
247            owner: BufferOwner::OperationLayer,
248            sensitivity: SecretSensitivity::Secret,
249            retention: RetentionPolicy::ResultLifetime,
250            zeroization: ZeroizationPolicy::OwnerZeroizesOnDrop,
251            export: ExportPolicy::NonExportable,
252            destruction: DestructionPolicy::ZeroizeOnDrop,
253        },
254        public_wire(),
255        public_wire(),
256        output_material,
257    )
258}
259
260const fn public_input_secret_output(
261    output_material: OutputMaterial,
262) -> OperationSecretMaterialPolicy {
263    operation_policy(
264        borrowed_public(),
265        owned_secret(),
266        public_wire(),
267        secret_wire(),
268        output_material,
269    )
270}
271
272const fn public_input_sensitive_output(
273    output_material: OutputMaterial,
274) -> OperationSecretMaterialPolicy {
275    operation_policy(
276        borrowed_public(),
277        sensitive_result(),
278        public_wire(),
279        sensitive_wire(),
280        output_material,
281    )
282}
283
284const fn caller_owned_secret_output() -> OperationSecretMaterialPolicy {
285    operation_policy(
286        borrowed_public(),
287        MaterialPolicy {
288            owner: BufferOwner::Caller,
289            sensitivity: SecretSensitivity::Secret,
290            retention: RetentionPolicy::ResultLifetime,
291            zeroization: ZeroizationPolicy::CallerRetainsResponsibility,
292            export: ExportPolicy::SecretOwnerRequired,
293            destruction: DestructionPolicy::CallerControlled,
294        },
295        public_wire(),
296        secret_wire(),
297        OutputMaterial::Secret,
298    )
299}
300
301const fn caller_owned_sensitive_output() -> OperationSecretMaterialPolicy {
302    operation_policy(
303        borrowed_public(),
304        MaterialPolicy {
305            owner: BufferOwner::Caller,
306            sensitivity: SecretSensitivity::Sensitive,
307            retention: RetentionPolicy::ResultLifetime,
308            zeroization: ZeroizationPolicy::OwnerZeroizesOnDrop,
309            export: ExportPolicy::Public,
310            destruction: DestructionPolicy::ZeroizeOnDrop,
311        },
312        public_wire(),
313        sensitive_wire(),
314        OutputMaterial::SensitivePublic,
315    )
316}
317
318const fn caller_owned_public_output() -> OperationSecretMaterialPolicy {
319    operation_policy(
320        borrowed_public(),
321        MaterialPolicy {
322            owner: BufferOwner::Caller,
323            sensitivity: SecretSensitivity::Public,
324            retention: RetentionPolicy::ResultLifetime,
325            zeroization: ZeroizationPolicy::NotRequired,
326            export: ExportPolicy::Public,
327            destruction: DestructionPolicy::NoneRequired,
328        },
329        public_wire(),
330        public_wire(),
331        OutputMaterial::Public,
332    )
333}
334
335const fn public_operation() -> OperationSecretMaterialPolicy {
336    operation_policy(
337        borrowed_public(),
338        public_result(),
339        public_wire(),
340        public_wire(),
341        OutputMaterial::Public,
342    )
343}
344
345const fn operation_policy(
346    input: MaterialPolicy,
347    output: MaterialPolicy,
348    request_wire: MaterialPolicy,
349    response_wire: MaterialPolicy,
350    output_material: OutputMaterial,
351) -> OperationSecretMaterialPolicy {
352    OperationSecretMaterialPolicy {
353        input,
354        output,
355        request_wire,
356        response_wire,
357        output_material,
358    }
359}
360
361const fn borrowed_secret() -> MaterialPolicy {
362    MaterialPolicy {
363        owner: BufferOwner::Caller,
364        sensitivity: SecretSensitivity::Secret,
365        retention: RetentionPolicy::BorrowedForCall,
366        zeroization: ZeroizationPolicy::CallerRetainsResponsibility,
367        export: ExportPolicy::SecretOwnerRequired,
368        destruction: DestructionPolicy::CallerControlled,
369    }
370}
371
372const fn borrowed_public() -> MaterialPolicy {
373    MaterialPolicy {
374        owner: BufferOwner::Caller,
375        sensitivity: SecretSensitivity::Public,
376        retention: RetentionPolicy::BorrowedForCall,
377        zeroization: ZeroizationPolicy::NotRequired,
378        export: ExportPolicy::Public,
379        destruction: DestructionPolicy::NoneRequired,
380    }
381}
382
383const fn owned_secret() -> MaterialPolicy {
384    MaterialPolicy {
385        owner: BufferOwner::OperationLayer,
386        sensitivity: SecretSensitivity::Secret,
387        retention: RetentionPolicy::ResultLifetime,
388        zeroization: ZeroizationPolicy::OwnerZeroizesOnDrop,
389        export: ExportPolicy::SecretOwnerRequired,
390        destruction: DestructionPolicy::ZeroizeOnDrop,
391    }
392}
393
394const fn public_result() -> MaterialPolicy {
395    MaterialPolicy {
396        owner: BufferOwner::OperationLayer,
397        sensitivity: SecretSensitivity::Public,
398        retention: RetentionPolicy::ResultLifetime,
399        zeroization: ZeroizationPolicy::NotRequired,
400        export: ExportPolicy::Public,
401        destruction: DestructionPolicy::NoneRequired,
402    }
403}
404
405const fn sensitive_result() -> MaterialPolicy {
406    MaterialPolicy {
407        owner: BufferOwner::OperationLayer,
408        sensitivity: SecretSensitivity::Sensitive,
409        retention: RetentionPolicy::ResultLifetime,
410        zeroization: ZeroizationPolicy::NotRequired,
411        export: ExportPolicy::Public,
412        destruction: DestructionPolicy::NoneRequired,
413    }
414}
415
416const fn secret_wire() -> MaterialPolicy {
417    MaterialPolicy {
418        owner: BufferOwner::OperationContract,
419        sensitivity: SecretSensitivity::Secret,
420        retention: RetentionPolicy::OperationTemporary,
421        zeroization: ZeroizationPolicy::OwnerZeroizesOnDrop,
422        export: ExportPolicy::SecretOwnerRequired,
423        destruction: DestructionPolicy::ZeroizeOnDrop,
424    }
425}
426
427const fn public_wire() -> MaterialPolicy {
428    MaterialPolicy {
429        owner: BufferOwner::OperationContract,
430        sensitivity: SecretSensitivity::Public,
431        retention: RetentionPolicy::OperationTemporary,
432        zeroization: ZeroizationPolicy::NotRequired,
433        export: ExportPolicy::Public,
434        destruction: DestructionPolicy::NoneRequired,
435    }
436}
437
438const fn sensitive_wire() -> MaterialPolicy {
439    MaterialPolicy {
440        owner: BufferOwner::OperationContract,
441        sensitivity: SecretSensitivity::Sensitive,
442        retention: RetentionPolicy::OperationTemporary,
443        zeroization: ZeroizationPolicy::NotRequired,
444        export: ExportPolicy::Public,
445        destruction: DestructionPolicy::NoneRequired,
446    }
447}