fido_authenticator/
ctap1.rs

1//! The `ctap_types::ctap1::Authenticator` implementation.
2
3use ctap_types::{
4    ctap1::{authenticate, register, Authenticator, ControlByte, Error, Result},
5    heapless_bytes::Bytes,
6};
7use serde_bytes::ByteArray;
8
9use trussed_core::{
10    syscall,
11    types::{KeySerialization, Location, Mechanism, SignatureSerialization},
12};
13
14use crate::{
15    constants,
16    credential::{self, Credential, Key, StrippedCredential},
17    SigningAlgorithm, TrussedRequirements, UserPresence,
18};
19
20type Commitment = Bytes<324>;
21
22/// Implement `ctap1::Authenticator` for our Authenticator.
23///
24/// ## References
25/// The "proposed standard" of U2F V1.2 applies to CTAP1.
26/// - [Message formats](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html)
27/// - [App ID](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-appid-and-facets-v1.2-ps-20170411.html)
28impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenticator<UP, T> {
29    /// Register a new credential, this always uses P-256 keys.
30    ///
31    /// Note that attestation is mandatory in CTAP1/U2F, so if the state
32    /// is not provisioned with a key/cert, this method will fail.
33    /// <https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#registration-request-message---u2f_register>
34    ///
35    /// Also note that CTAP1 credentials should be assertable over CTAP2. I believe this is
36    /// currently not the case.
37    fn register(&mut self, reg: &register::Request) -> Result<register::Response> {
38        self.up
39            .user_present(&mut self.trussed, constants::U2F_UP_TIMEOUT)
40            .map_err(|_| Error::ConditionsOfUseNotSatisfied)?;
41
42        // Generate a new P256 key pair.
43        let private_key = syscall!(self.trussed.generate_p256_private_key(Location::Volatile)).key;
44        let public_key = syscall!(self
45            .trussed
46            .derive_p256_public_key(private_key, Location::Volatile))
47        .key;
48
49        let serialized_cose_public_key = syscall!(self
50            .trussed
51            .serialize_p256_key(public_key, KeySerialization::EcdhEsHkdf256))
52        .serialized_key;
53        syscall!(self.trussed.delete(public_key));
54        let cose_key: cosey::EcdhEsHkdf256PublicKey =
55            cbor_smol::cbor_deserialize(&serialized_cose_public_key).unwrap();
56
57        let wrapping_key = self
58            .state
59            .persistent
60            .key_wrapping_key(&mut self.trussed)
61            .map_err(|_| Error::UnspecifiedCheckingError)?;
62        // debug!("wrapping u2f private key");
63
64        let wrapped_key =
65            syscall!(self
66                .trussed
67                .wrap_key_chacha8poly1305(wrapping_key, private_key, &[], None))
68            .wrapped_key;
69        // debug!("wrapped_key = {:?}", &wrapped_key);
70
71        syscall!(self.trussed.delete(private_key));
72
73        let key = Key::WrappedKey(
74            wrapped_key
75                .to_bytes()
76                .map_err(|_| Error::UnspecifiedCheckingError)?,
77        );
78        let nonce = ByteArray::new(self.nonce());
79
80        let credential = StrippedCredential {
81            ctap: credential::CtapVersion::U2fV2,
82            creation_time: self
83                .state
84                .persistent
85                .timestamp(&mut self.trussed)
86                .map_err(|_| Error::NotEnoughMemory)?,
87            use_counter: true,
88            algorithm: SigningAlgorithm::P256 as i32,
89            key,
90            nonce,
91            hmac_secret: None,
92            cred_protect: None,
93            large_blob_key: None,
94            third_party_payment: None,
95        };
96
97        // info!("made credential {:?}", &credential);
98
99        // 12.b generate credential ID { = AEAD(Serialize(Credential)) }
100        let kek = self
101            .state
102            .persistent
103            .key_encryption_key(&mut self.trussed)
104            .map_err(|_| Error::NotEnoughMemory)?;
105        let credential_id = credential
106            .id(&mut self.trussed, kek, reg.app_id)
107            .map_err(|_| Error::NotEnoughMemory)?;
108
109        let mut commitment = Commitment::new();
110
111        commitment.push(0).unwrap(); // reserve byte
112        commitment.extend_from_slice(reg.app_id).unwrap();
113        commitment.extend_from_slice(reg.challenge).unwrap();
114
115        commitment.extend_from_slice(&credential_id.0).unwrap();
116
117        commitment.push(0x04).unwrap(); // public key uncompressed byte
118        commitment.extend_from_slice(&cose_key.x).unwrap();
119        commitment.extend_from_slice(&cose_key.y).unwrap();
120
121        let attestation = self.state.identity.attestation(&mut self.trussed);
122
123        let (signature, cert) = match attestation {
124            (Some((key, cert)), _aaguid) => {
125                info!("aaguid: {}", hex_str!(&_aaguid));
126                (
127                    syscall!(self.trussed.sign(
128                        Mechanism::P256,
129                        key,
130                        &commitment,
131                        SignatureSerialization::Asn1Der
132                    ))
133                    .signature
134                    .to_bytes()
135                    .unwrap(),
136                    cert,
137                )
138            }
139            _ => {
140                info!("Not provisioned with attestation key!");
141                return Err(Error::KeyReferenceNotFound);
142            }
143        };
144
145        Ok(register::Response::new(
146            0x05,
147            &cose_key,
148            credential_id.0,
149            signature,
150            cert,
151        ))
152    }
153
154    fn authenticate(&mut self, auth: &authenticate::Request) -> Result<authenticate::Response> {
155        let cred = Credential::try_from_bytes(self, auth.app_id, auth.key_handle);
156
157        let user_presence_byte = match auth.control_byte {
158            ControlByte::CheckOnly => {
159                // if the control byte is set to 0x07 by the FIDO Client,
160                // the U2F token is supposed to simply check whether the
161                // provided key handle was originally created by this token
162                return if cred.is_ok() {
163                    Err(Error::ConditionsOfUseNotSatisfied)
164                } else {
165                    Err(Error::IncorrectDataParameter)
166                };
167            }
168            ControlByte::EnforceUserPresenceAndSign => {
169                if !self.skip_up_check() {
170                    self.up
171                        .user_present(&mut self.trussed, constants::U2F_UP_TIMEOUT)
172                        .map_err(|_| Error::ConditionsOfUseNotSatisfied)?;
173                }
174                0x01
175            }
176            ControlByte::DontEnforceUserPresenceAndSign => 0x00,
177        };
178
179        let cred = cred.map_err(|_| Error::IncorrectDataParameter)?;
180
181        let key = match cred.key() {
182            Key::WrappedKey(bytes) => {
183                let wrapping_key = self
184                    .state
185                    .persistent
186                    .key_wrapping_key(&mut self.trussed)
187                    .map_err(|_| Error::IncorrectDataParameter)?;
188                let key_result = syscall!(self.trussed.unwrap_key_chacha8poly1305(
189                    wrapping_key,
190                    bytes,
191                    &[],
192                    Location::Volatile,
193                ))
194                .key;
195                match key_result {
196                    Some(key) => {
197                        info!("loaded u2f key!");
198                        key
199                    }
200                    None => {
201                        info!("issue with unwrapping credential id key");
202                        return Err(Error::IncorrectDataParameter);
203                    }
204                }
205            }
206            _ => return Err(Error::IncorrectDataParameter),
207        };
208
209        if cred.algorithm() != -7 {
210            info!("Unexpected mechanism for u2f");
211            return Err(Error::IncorrectDataParameter);
212        }
213
214        let sig_count = self
215            .state
216            .persistent
217            .timestamp(&mut self.trussed)
218            .map_err(|_| Error::UnspecifiedNonpersistentExecutionError)?;
219
220        let mut commitment = Commitment::new();
221
222        commitment.extend_from_slice(auth.app_id).unwrap();
223        commitment.push(user_presence_byte).unwrap();
224        commitment
225            .extend_from_slice(&sig_count.to_be_bytes())
226            .unwrap();
227        commitment.extend_from_slice(auth.challenge).unwrap();
228
229        let signature = syscall!(self.trussed.sign(
230            Mechanism::P256,
231            key,
232            &commitment,
233            SignatureSerialization::Asn1Der
234        ))
235        .signature
236        .to_bytes()
237        .unwrap();
238
239        Ok(authenticate::Response {
240            user_presence: user_presence_byte,
241            count: sig_count,
242            signature,
243        })
244    }
245}