Skip to main content

fido_authenticator/
ctap1.rs

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