Skip to main content

rite_model/params/
crypto.rs

1//! Params for the key and data actions.
2
3use rite_sdk::{KeyAlgorithm, KeyUsages, SignAlgorithm, WrapScheme};
4use schemars::{JsonSchema, Schema, SchemaGenerator};
5use serde::{Deserialize, Serialize};
6
7/// Params for `generate_key` action.
8#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
9#[serde(deny_unknown_fields)]
10#[non_exhaustive]
11pub struct GenerateKeyParams {
12    /// Cryptographic algorithm (e.g., `"RSA-4096"`, `"ECDSA-P256"`).
13    #[serde(default = "default_algorithm")]
14    pub algorithm: KeyAlgorithm,
15    /// What the key is permitted to do, and whether it may leave the backend.
16    #[serde(default)]
17    pub policy: Option<KeyPolicyParams>,
18    /// Backend-specific slot hint.
19    #[serde(default)]
20    pub slot: Option<String>,
21}
22
23/// The `policy:` block, mirroring [`KeyPolicy`](rite_sdk::KeyPolicy) field
24/// for field.
25///
26/// Every field is optional and falls back to the restrictive choice: a
27/// persistent, sensitive, non-extractable key. The usages fall back to what the
28/// algorithm permits, by
29/// [`KeyPolicy::default_for`](rite_sdk::KeyPolicy::default_for), so a keypair
30/// may sign and verify and a symmetric key may wrap and unwrap. A ceremony that
31/// wraps a key it generated has to say `extractable: true`, because otherwise
32/// the key cannot leave the backend and the wrap step will refuse.
33///
34/// This is PKCS#11 vocabulary: what the token permits. The `KeyUsage`
35/// extension in a certificate is a different thing, settled by `profile:` on
36/// `issue_certificate`.
37#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
38#[serde(deny_unknown_fields)]
39#[non_exhaustive]
40pub struct KeyPolicyParams {
41    /// Key survives the session. `CKA_TOKEN`.
42    #[serde(default)]
43    pub persistent: Option<bool>,
44    /// Key material is never revealed in plaintext. `CKA_SENSITIVE`.
45    #[serde(default)]
46    pub sensitive: Option<bool>,
47    /// Key may be wrapped and leave the backend. `CKA_EXTRACTABLE`.
48    #[serde(default)]
49    pub extractable: Option<bool>,
50    /// Key may only be wrapped by a trusted wrapping key.
51    /// `CKA_WRAP_WITH_TRUSTED`.
52    #[serde(default)]
53    pub wrap_with_trusted_only: Option<bool>,
54    /// Operations the key is permitted to perform: `sign`, `verify`,
55    /// `encrypt`, `decrypt`, `wrap`, `unwrap`, `derive`.
56    #[serde(default)]
57    pub usages: Option<Vec<KeyUsage>>,
58}
59
60impl KeyPolicyParams {
61    /// Resolve the declared policy against a base.
62    ///
63    /// The base is the caller's to choose, because what a key may do when the
64    /// ceremony says nothing follows from its algorithm: see
65    /// [`KeyPolicy::default_for`](rite_sdk::KeyPolicy::default_for).
66    #[must_use]
67    pub fn resolve_from(&self, defaults: &rite_sdk::KeyPolicy) -> rite_sdk::KeyPolicy {
68        let usages = self.usages.as_ref().map_or(defaults.usages, |usages| {
69            usages
70                .iter()
71                .fold(KeyUsages::empty(), |all, usage| all | usage.0)
72        });
73        rite_sdk::KeyPolicy {
74            persistent: self.persistent.unwrap_or(defaults.persistent),
75            sensitive: self.sensitive.unwrap_or(defaults.sensitive),
76            extractable: self.extractable.unwrap_or(defaults.extractable),
77            wrap_with_trusted_only: self
78                .wrap_with_trusted_only
79                .unwrap_or(defaults.wrap_with_trusted_only),
80            usages,
81        }
82    }
83}
84
85/// One name under `policy: { usages: [...] }`, a PKCS#11 usage.
86///
87/// What the token permits the key to do. The X.509 `KeyUsage` extension is a
88/// different thing and comes from `profile:` on `issue_certificate`.
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
90#[serde(into = "String", try_from = "String")]
91pub struct KeyUsage(KeyUsages);
92
93impl KeyUsage {
94    /// The one usage this name stands for.
95    #[must_use]
96    pub fn usage(self) -> KeyUsages {
97        self.0
98    }
99}
100
101impl From<KeyUsage> for String {
102    fn from(usage: KeyUsage) -> String {
103        usage.0.describe()
104    }
105}
106
107impl TryFrom<String> for KeyUsage {
108    type Error = String;
109
110    fn try_from(name: String) -> Result<Self, Self::Error> {
111        KeyUsages::usage_named(&name).map(KeyUsage).ok_or_else(|| {
112            format!(
113                "unknown key usage '{name}'. Supported: {}",
114                KeyUsages::all_names().join(", ")
115            )
116        })
117    }
118}
119
120impl JsonSchema for KeyUsage {
121    fn schema_name() -> std::borrow::Cow<'static, str> {
122        "KeyUsage".into()
123    }
124
125    fn json_schema(_: &mut SchemaGenerator) -> Schema {
126        super::schema::names(KeyUsages::all_names().into_iter())
127    }
128}
129
130fn default_algorithm() -> KeyAlgorithm {
131    KeyAlgorithm::Rsa4096
132}
133
134impl Default for GenerateKeyParams {
135    fn default() -> Self {
136        Self {
137            algorithm: default_algorithm(),
138            policy: None,
139            slot: None,
140        }
141    }
142}
143
144/// Params for `wrap_key` action.
145#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
146#[serde(deny_unknown_fields)]
147#[non_exhaustive]
148pub struct WrapKeyParams {
149    /// The wrapping scheme, defaulting to `"CMS-AES-256-GCM"`.
150    ///
151    /// Which encapsulation a scheme takes still follows the recipient key and
152    /// is never an author's choice. What the author does choose is where the
153    /// blob is going: CMS carries its own algorithm identifiers and is what a
154    /// ceremony archives, while the raw mechanisms are what a cloud KMS import
155    /// accepts and describe nothing about themselves.
156    ///
157    /// A raw scheme is a weaker record: `rite verify` cannot re-derive its
158    /// algorithms from the artifact, and says so rather than reporting the
159    /// wrap as checked.
160    ///
161    /// The AES schemes wrap under a symmetric key the backend holds, so they
162    /// are reachable only from a step reading `wrapping_key:`. They name the
163    /// KEK size, because the RFC 3394 and RFC 5649 object identifiers are
164    /// per-size and the output carries neither.
165    #[serde(default)]
166    pub scheme: Option<WrapScheme>,
167    /// The recipient's expected fingerprint, `"sha256:<hex>"` over its SPKI
168    /// DER, for a step that reads `recipient:`.
169    ///
170    /// Declaring it is what turns the recorded recipient from "whatever key
171    /// was present" into a value the ceremony committed to in advance. On
172    /// mismatch the step fails: a key wrapped to the wrong recipient cannot be
173    /// unwrapped again.
174    //
175    // A value in any other shape can never equal what the runtime computes,
176    // which renders every fingerprint in lowercase, so the step would fail in
177    // the room, after the keys exist.
178    #[serde(default)]
179    #[schemars(
180        regex(pattern = r"^sha256:[0-9a-f]{64}$"),
181        extend(
182            "x-message" = "'expect_recipient' must be a fingerprint of the form \
183                'sha256:<64 lowercase hex digits>'"
184        )
185    )]
186    pub expect_recipient: Option<String>,
187}
188
189/// Params for `sign_data` action.
190#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
191#[serde(deny_unknown_fields)]
192#[non_exhaustive]
193pub struct SignDataParams {
194    /// Signature algorithm. Defaults to the one implied by the key.
195    ///
196    /// Worth setting for an RSA key, which can sign under either
197    /// `"RSA-PKCS1-SHA256"` (the default) or `"RSA-PSS-SHA256"`. Every other
198    /// key type admits exactly one algorithm, so naming it only restates the
199    /// key.
200    #[serde(default)]
201    pub algorithm: Option<SignAlgorithm>,
202    /// Optional display message.
203    #[serde(default)]
204    pub message: Option<String>,
205}
206
207/// Params for `verify_signature` action.
208#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
209#[serde(deny_unknown_fields)]
210#[non_exhaustive]
211pub struct VerifySignatureParams {
212    /// Signature algorithm. Defaults to the one implied by the public key.
213    ///
214    /// Required when checking an RSA-PSS signature, since an RSA key alone
215    /// does not say which scheme was used.
216    #[serde(default)]
217    pub algorithm: Option<SignAlgorithm>,
218    /// Optional display message.
219    #[serde(default)]
220    pub message: Option<String>,
221}
222
223/// The identity of a key a ceremony declares in advance, in whichever form
224/// the key has one.
225///
226/// A value in neither form can never equal what the runtime computes, so the
227/// mismatch is reported before the room rather than during it.
228const KEY_IDENTITY: &str = r"^(sha256:[0-9a-f]{64}|cmac-aes:[0-9a-f]{6})$";
229
230/// Params for `unwrap_key` action.
231#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
232#[serde(deny_unknown_fields)]
233#[non_exhaustive]
234pub struct UnwrapKeyParams {
235    /// What the ceremony is restoring, as a key algorithm name.
236    ///
237    /// Nothing travels with a wrapped key saying what it is, so a recovered
238    /// blob is read as whatever this names. Required to recover a symmetric
239    /// key, whose bytes are indistinguishable from any other bytes of the same
240    /// length. Optional for a keypair, which names itself once its DER parses,
241    /// and checked against what came out when given.
242    #[serde(default)]
243    pub algorithm: Option<KeyAlgorithm>,
244    /// The expected identity of the recovered key: `"sha256:<hex>"` over its
245    /// SPKI DER for a keypair, `"cmac-aes:<hex>"` for a symmetric key.
246    ///
247    /// Usually the value the origin ceremony's `generate_key` step
248    /// recorded. On mismatch the step fails rather than importing a key the
249    /// ceremony did not mean to restore.
250    #[serde(default)]
251    #[schemars(
252        regex(pattern = KEY_IDENTITY),
253        extend("x-message" = EXPECT_KEY_MESSAGE)
254    )]
255    pub expect_key: Option<String>,
256    /// Label for the unwrapped key.
257    #[serde(default = "unwrapped_key_label")]
258    pub label: String,
259    /// What the recovered key is permitted to do, and whether it may leave
260    /// the backend again.
261    ///
262    /// Nothing travels with a wrapped key that says what it may do, so the
263    /// receiving ceremony declares that. Without a `policy:` the key may sign,
264    /// verify and be wrapped again; a key restored to serve as a wrapping key
265    /// has to say `usages: [wrap, unwrap]`.
266    #[serde(default)]
267    pub policy: Option<KeyPolicyParams>,
268}
269
270const EXPECT_KEY_MESSAGE: &str = "'expect_key' must name the key either as \
271    'sha256:<64 lowercase hex digits>', the fingerprint of a public key, or as \
272    'cmac-aes:<6 lowercase hex digits>', the check value of a symmetric key";
273
274fn unwrapped_key_label() -> String {
275    "unwrapped-key".to_string()
276}
277
278impl Default for UnwrapKeyParams {
279    fn default() -> Self {
280        Self {
281            algorithm: None,
282            expect_key: None,
283            label: unwrapped_key_label(),
284            policy: None,
285        }
286    }
287}
288
289/// Params for `import_key` action.
290#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
291#[serde(deny_unknown_fields)]
292#[non_exhaustive]
293pub struct ImportKeyParams {
294    /// What the bytes are, as a key algorithm name.
295    ///
296    /// Required, because raw material says nothing about itself: a symmetric
297    /// algorithm reads the bytes as the key itself, every other one reads them
298    /// as PKCS#8 DER. `unwrap_key` can leave this out for a keypair because the
299    /// artifact it reads carries a description; an artifact of plain bytes
300    /// carries nothing.
301    pub algorithm: KeyAlgorithm,
302    /// The expected identity of the imported key: `"sha256:<hex>"` over its
303    /// SPKI DER for a keypair, `"cmac-aes:<hex>"` for a symmetric key.
304    ///
305    /// Optional, since a ceremony may be lifting material whose identity it
306    /// learns only afterwards. Where it is known it is the only evidence
307    /// available about material this ceremony did not itself produce.
308    #[serde(default)]
309    #[schemars(
310        regex(pattern = KEY_IDENTITY),
311        extend("x-message" = EXPECT_KEY_MESSAGE)
312    )]
313    pub expect_key: Option<String>,
314    /// Label for the imported key.
315    #[serde(default = "imported_key_label")]
316    pub label: String,
317    /// What the imported key is permitted to do, and whether it may leave the
318    /// backend again.
319    ///
320    /// Nothing travels with raw key material saying what it may do, so the
321    /// ceremony declares it, exactly as at `unwrap_key`.
322    #[serde(default)]
323    pub policy: Option<KeyPolicyParams>,
324}
325
326fn imported_key_label() -> String {
327    "imported-key".to_string()
328}
329
330/// Params for `encrypt_data` action.
331#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
332#[serde(deny_unknown_fields)]
333#[non_exhaustive]
334pub struct EncryptDataParams {
335    /// The container, defaulting to `"CMS-AES-256-GCM"`.
336    ///
337    /// The only value this action accepts today. It is written out rather than
338    /// implied because a second content cipher means a second container, and a
339    /// ceremony that names the one it used stays readable when there are two.
340    //
341    // Narrower than `wrap_key`'s vocabulary on purpose: the container is the
342    // only thing `encrypt_data` writes.
343    #[serde(default)]
344    #[schemars(schema_with = "content_schemes")]
345    pub scheme: Option<WrapScheme>,
346}
347
348/// The schemes that carry content, which are the ones `encrypt_data` writes.
349fn content_schemes(_: &mut SchemaGenerator) -> Schema {
350    super::schema::names(
351        WrapScheme::ALL
352            .iter()
353            .filter(|scheme| scheme.carries_content())
354            .map(|scheme| scheme.as_str()),
355    )
356}