Skip to main content

rite_model/params/
piv.rs

1//! Params for the PIV and `YubiKey` actions.
2
3use rite_sdk::SignAlgorithm;
4use schemars::{JsonSchema, Schema, SchemaGenerator};
5use serde::{Deserialize, Serialize};
6
7/// A PIV slot as a step names it: `9a`, `9c`, `9d`, `9e`, or a retired slot
8/// by its key reference in hex, `82` to `95`, optionally after `piv:`.
9//
10// The set `rite_piv::ops::slot_from_hint` reads, which this crate cannot
11// link; its tests hold the two together.
12pub const PIV_SLOT: &str = r"^(piv:)?(9[acde]|8[2-9a-fA-F]|9[0-5])$";
13
14const PIV_SLOT_MESSAGE: &str = "'slot' must be a PIV slot: 9a, 9c, 9d, 9e, or a retired slot \
15    from 82 to 95";
16
17/// The signature algorithms a PIV card performs. RSA-PSS is absent because
18/// PIV cards apply a raw RSA operation and the client-side PSS encoding is
19/// not implemented; Ed25519 and ML-DSA because no PIV card does them.
20pub const PIV_SIGN_ALGORITHMS: [SignAlgorithm; 3] = [
21    SignAlgorithm::EcdsaSha256,
22    SignAlgorithm::EcdsaSha384,
23    SignAlgorithm::RsaPkcs1Sha256,
24];
25
26/// Parameters for the `piv_read_certificate` action.
27#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
28#[serde(deny_unknown_fields)]
29#[non_exhaustive]
30pub struct PivReadCertificateParams {
31    /// PIV slot to read: `9a`, `9c`, `9d`, `9e`, or a retired slot key
32    /// reference in hex (`82`..`95`).
33    #[schemars(regex(pattern = PIV_SLOT), extend("x-message" = PIV_SLOT_MESSAGE))]
34    pub slot: String,
35    /// Optional display message.
36    #[serde(default)]
37    pub message: Option<String>,
38}
39
40/// Parameters for the `piv_sign` action.
41#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
42#[serde(deny_unknown_fields)]
43#[non_exhaustive]
44pub struct PivSignParams {
45    /// PIV slot containing the signing key.
46    #[serde(default = "default_sign_slot")]
47    #[schemars(regex(pattern = PIV_SLOT), extend("x-message" = PIV_SLOT_MESSAGE))]
48    pub slot: String,
49    /// Signing algorithm: `ECDSA-SHA256`, `ECDSA-SHA384`, `RSA-PKCS1-SHA256`.
50    #[serde(default = "default_sign_algorithm")]
51    #[schemars(schema_with = "piv_sign_algorithms")]
52    pub algorithm: SignAlgorithm,
53    /// Optional display message.
54    #[serde(default)]
55    pub message: Option<String>,
56}
57
58fn default_sign_slot() -> String {
59    "9c".to_string()
60}
61
62fn default_sign_algorithm() -> SignAlgorithm {
63    SignAlgorithm::EcdsaSha256
64}
65
66fn piv_sign_algorithms(_: &mut SchemaGenerator) -> Schema {
67    let mut schema = super::schema::names(PIV_SIGN_ALGORITHMS.iter().map(|a| a.as_str()));
68    schema.insert(
69        "default".to_string(),
70        default_sign_algorithm().as_str().into(),
71    );
72    schema
73}
74
75/// Parameters for the `yubikey_attest_slot` action.
76#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
77#[serde(deny_unknown_fields)]
78#[non_exhaustive]
79pub struct AttestSlotParams {
80    /// PIV slot to attest (e.g., `9a`, `9c`, `9d`, `9e`).
81    #[schemars(regex(pattern = PIV_SLOT), extend("x-message" = PIV_SLOT_MESSAGE))]
82    pub slot: String,
83    /// Custom message to display before the attestation step.
84    #[serde(default)]
85    pub message: Option<String>,
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn read_certificate_params_deserializes() {
94        let json = serde_json::json!({ "slot": "9a" });
95        let params: PivReadCertificateParams = serde_json::from_value(json).unwrap();
96        assert_eq!(params.slot, "9a");
97        assert!(params.message.is_none());
98    }
99
100    #[test]
101    fn read_certificate_params_rejects_missing_slot() {
102        let json = serde_json::json!({});
103        let result = serde_json::from_value::<PivReadCertificateParams>(json);
104        assert!(result.is_err());
105    }
106
107    #[test]
108    fn sign_params_defaults() {
109        let json = serde_json::json!({});
110        let params: PivSignParams = serde_json::from_value(json).unwrap();
111        assert_eq!(params.slot, "9c");
112        assert_eq!(params.algorithm, SignAlgorithm::EcdsaSha256);
113        assert!(params.message.is_none());
114    }
115
116    #[test]
117    fn sign_params_explicit_values() {
118        let json = serde_json::json!({
119            "slot": "9d",
120            "algorithm": "RSA-PKCS1-SHA256",
121            "message": "Sign the key"
122        });
123        let params: PivSignParams = serde_json::from_value(json).unwrap();
124        assert_eq!(params.slot, "9d");
125        assert_eq!(params.algorithm, SignAlgorithm::RsaPkcs1Sha256);
126        assert_eq!(params.message.as_deref(), Some("Sign the key"));
127    }
128}