webauthn_authenticator_rs/ctap2/commands/
config.rs

1use serde::Serialize;
2use serde_cbor_2::Value;
3
4use self::CBORCommand;
5use super::*;
6
7/// `authenticatorConfig` request type.
8///
9/// See [ConfigSubCommand] and [ConfigRequest::new] for details on how to
10/// construct a new [ConfigRequest].
11///
12/// This has no response type.
13///
14/// Reference: <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorConfig>
15#[derive(Serialize, Debug, Clone, Default, PartialEq, Eq)]
16#[serde(into = "BTreeMap<u32, Value>")]
17pub struct ConfigRequest {
18    /// Action being requested
19    sub_command: u8,
20    sub_command_params: Option<BTreeMap<Value, Value>>,
21    /// PIN / UV protocol version chosen by the platform
22    pin_uv_protocol: Option<u32>,
23    /// Output of calling "Authenticate" on some context specific to [Self::sub_command]
24    pin_uv_auth_param: Option<Vec<u8>>,
25}
26
27impl CBORCommand for ConfigRequest {
28    const CMD: u8 = 0x0d;
29    type Response = NoResponse;
30}
31
32/// Subcommands for [ConfigRequest].
33#[derive(Debug, Clone, PartialEq, Eq, Default)]
34pub enum ConfigSubCommand {
35    #[default]
36    Unknown,
37    /// Enables the [enterprise attestation] feature.
38    ///
39    /// [enterprise attestation]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#enable-enterprise-attestation
40    EnableEnterpriseAttestation,
41    /// Toggles the [always require user verification] feature.
42    ///
43    /// [always require user verification]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#toggle-alwaysUv
44    ToggleAlwaysUv,
45    /// Sets a [minimum PIN length] policy.
46    ///
47    /// See [SetMinPinLengthParams] for further details.
48    ///
49    /// [minimum PIN length]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#setMinPINLength
50    SetMinPinLength(SetMinPinLengthParams),
51    // VendorPrototype,
52}
53
54/// Parameters for setting minimum PIN length in a [ConfigRequest].
55#[derive(Serialize, Debug, Clone, Default, PartialEq, Eq)]
56#[serde(into = "BTreeMap<Value, Value>")]
57pub struct SetMinPinLengthParams {
58    /// Minimum PIN length, in Unicode code points.
59    pub new_min_pin_length: Option<u32>,
60    /// Relying Party IDs which are allowed to request this information via the
61    /// `minPinLength` extension.
62    pub min_pin_length_rpids: Vec<String>,
63    /// If set to `true`, invalidates the authenticator's existing PIN, and
64    /// forces the PIN to be changed before it can be used again.
65    pub force_change_pin: Option<bool>,
66}
67
68impl From<&ConfigSubCommand> for u8 {
69    fn from(c: &ConfigSubCommand) -> Self {
70        use ConfigSubCommand::*;
71        match c {
72            Unknown => 0x00,
73            EnableEnterpriseAttestation => 0x01,
74            ToggleAlwaysUv => 0x02,
75            SetMinPinLength(_) => 0x03,
76            // VendorPrototype => 0xff,
77        }
78    }
79}
80
81impl From<ConfigSubCommand> for Option<BTreeMap<Value, Value>> {
82    fn from(c: ConfigSubCommand) -> Self {
83        use ConfigSubCommand::*;
84        match c {
85            SetMinPinLength(p) => Some(p.into()),
86            Unknown => None,
87            EnableEnterpriseAttestation => None,
88            ToggleAlwaysUv => None,
89            // VendorPrototype => unimplemented!(),
90        }
91    }
92}
93
94impl ConfigSubCommand {
95    pub fn prf(&self) -> Vec<u8> {
96        // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#prfValues
97        let sub_command = self.into();
98        let sub_command_params: Option<BTreeMap<Value, Value>> = self.to_owned().into();
99
100        let mut o = vec![0xff; 32];
101        o.push(ConfigRequest::CMD);
102        o.push(sub_command);
103        if let Some(p) = sub_command_params
104            .as_ref()
105            .and_then(|p| serde_cbor_2::to_vec(p).ok())
106        {
107            o.extend_from_slice(p.as_slice())
108        }
109
110        o
111    }
112}
113
114impl ConfigRequest {
115    pub fn new(
116        s: ConfigSubCommand,
117        pin_uv_protocol: Option<u32>,
118        pin_uv_auth_param: Option<Vec<u8>>,
119    ) -> Self {
120        let sub_command = (&s).into();
121        let sub_command_params = s.into();
122
123        Self {
124            sub_command,
125            sub_command_params,
126            pin_uv_protocol,
127            pin_uv_auth_param,
128        }
129    }
130}
131
132impl From<ConfigRequest> for BTreeMap<u32, Value> {
133    fn from(value: ConfigRequest) -> Self {
134        let ConfigRequest {
135            sub_command,
136            sub_command_params,
137            pin_uv_protocol,
138            pin_uv_auth_param,
139        } = value;
140
141        let mut keys = BTreeMap::new();
142        keys.insert(0x01, Value::Integer(sub_command.into()));
143
144        if let Some(v) = sub_command_params {
145            keys.insert(0x02, Value::Map(v));
146        }
147
148        if let Some(v) = pin_uv_protocol {
149            keys.insert(0x03, Value::Integer(v.to_owned().into()));
150        }
151        if let Some(v) = pin_uv_auth_param {
152            keys.insert(0x04, Value::Bytes(v));
153        }
154
155        keys
156    }
157}
158
159impl From<SetMinPinLengthParams> for BTreeMap<Value, Value> {
160    fn from(value: SetMinPinLengthParams) -> Self {
161        let SetMinPinLengthParams {
162            new_min_pin_length,
163            min_pin_length_rpids,
164            force_change_pin,
165        } = value;
166
167        let mut keys = BTreeMap::new();
168
169        if let Some(v) = new_min_pin_length {
170            keys.insert(Value::Integer(0x01), Value::Integer(v.to_owned().into()));
171        }
172
173        if !min_pin_length_rpids.is_empty() {
174            keys.insert(
175                Value::Integer(0x02),
176                Value::Array(
177                    min_pin_length_rpids
178                        .iter()
179                        .map(|v| Value::Text(v.clone()))
180                        .collect(),
181                ),
182            );
183        }
184
185        if let Some(v) = force_change_pin {
186            keys.insert(Value::Integer(0x03), Value::Bool(v.to_owned()));
187        }
188
189        keys
190    }
191}