• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / OpenSK / 4342532062

pending completion
4342532062

push

github

GitHub
AAGUID customization (#600)

32 of 32 new or added lines in 6 files covered. (100.0%)

14267 of 16290 relevant lines covered (87.58%)

797225.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.75
/src/api/customization.rs
1
// Copyright 2022 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
//! This file contains all customizable constants.
16
//!
17
//! If you adapt them, make sure to run the tests before flashing the firmware.
18
//! Our deploy script enforces the invariants.
19

20
use crate::ctap::data_formats::{CredentialProtectionPolicy, EnterpriseAttestationMode};
21
use alloc::string::String;
22
use alloc::vec::Vec;
23

24
pub const AAGUID_LENGTH: usize = 16;
25

26
pub trait Customization {
27
    /// Authenticator Attestation Global Unique Identifier
28
    fn aaguid(&self) -> &'static [u8; AAGUID_LENGTH];
29

30
    // ###########################################################################
31
    // Constants for adjusting privacy and protection levels.
32
    // ###########################################################################
33

34
    /// Removes support for PIN protocol v1.
35
    ///
36
    /// We support PIN protocol v2, "intended to aid FIPS certification".
37
    /// To certify, you might want to remove support for v1 using this customization.
38
    fn allows_pin_protocol_v1(&self) -> bool;
39

40
    /// Changes the default level for the credProtect extension.
41
    ///
42
    /// You can change this value to one of the following for more privacy:
43
    /// - CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList
44
    /// - CredentialProtectionPolicy::UserVerificationRequired
45
    ///
46
    /// UserVerificationOptionalWithCredentialIdList
47
    /// Resident credentials are discoverable with
48
    /// - an allowList,
49
    /// - an excludeList,
50
    /// - user verification.
51
    ///
52
    /// UserVerificationRequired
53
    /// Resident credentials are discoverable with user verification only.
54
    ///
55
    /// This can improve privacy, but can make usage less comfortable.
56
    fn default_cred_protect(&self) -> Option<CredentialProtectionPolicy>;
57

58
    /// Sets the initial minimum PIN length in code points.
59
    ///
60
    /// # Invariant
61
    ///
62
    /// - The minimum PIN length must be at least 4.
63
    /// - The minimum PIN length must be at most 63.
64
    /// - default_min_pin_length_rp_ids() must be non-empty if max_rp_ids_length() is 0.
65
    ///
66
    /// Requiring longer PINs can help establish trust between users and relying
67
    /// parties. It makes user verification harder to break, but less convenient.
68
    /// NIST recommends at least 6-digit PINs in section 5.1.9.1:
69
    /// https://pages.nist.gov/800-63-3/sp800-63b.html
70
    ///
71
    /// Reset reverts the minimum PIN length to this DEFAULT_MIN_PIN_LENGTH.
72
    fn default_min_pin_length(&self) -> u8;
73

74
    /// Lists relying parties that can read the minimum PIN length.
75
    ///
76
    /// # Invariant
77
    ///
78
    /// - default_min_pin_length_rp_ids() must be non-empty if max_rp_ids_length() is 0
79
    ///
80
    /// Only the RP IDs listed in default_min_pin_length_rp_ids are allowed to read
81
    /// the minimum PIN length with the minPinLength extension.
82
    fn default_min_pin_length_rp_ids(&self) -> Vec<String>;
83

84
    /// Enforces the alwaysUv option.
85
    ///
86
    /// When setting to true, commands require a PIN.
87
    /// Also, alwaysUv can not be disabled by commands.
88
    ///
89
    /// A certification (additional to FIDO Alliance's) might require enforcing
90
    /// alwaysUv. Otherwise, users should have the choice to configure alwaysUv.
91
    /// Calling toggleAlwaysUv is preferred over enforcing alwaysUv here.
92
    fn enforce_always_uv(&self) -> bool;
93

94
    /// Allows usage of enterprise attestation.
95
    ///
96
    /// # Invariant
97
    ///
98
    /// - Enterprise and batch attestation can not both be active.
99
    /// - If the mode is VendorFacilitated, enterprise_attestation_mode() must be non-empty.
100
    ///
101
    /// For privacy reasons, it is disabled by default. You can choose between:
102
    /// - EnterpriseAttestationMode::VendorFacilitated
103
    /// - EnterpriseAttestationMode::PlatformManaged
104
    ///
105
    /// VendorFacilitated
106
    /// Enterprise attestation is restricted to enterprise_attestation_mode(). Add your
107
    /// enterprises domain, e.g. "example.com", to the list below.
108
    ///
109
    /// PlatformManaged
110
    /// All relying parties can request an enterprise attestation. The authenticator
111
    /// trusts the platform to filter requests.
112
    ///
113
    /// To enable the feature, send the subcommand enableEnterpriseAttestation in
114
    /// AuthenticatorConfig. An enterprise might want to customize the type of
115
    /// attestation that is used. OpenSK defaults to batch attestation. Configuring
116
    /// individual certificates then makes authenticators identifiable.
117
    ///
118
    /// OpenSK prevents activating batch and enterprise attestation together. The
119
    /// current implementation uses the same key material at the moment, and these
120
    /// two modes have conflicting privacy guarantees.
121
    /// If you implement your own enterprise attestation mechanism, and you want
122
    /// batch attestation at the same time, proceed carefully and remove the
123
    /// assertion.
124
    fn enterprise_attestation_mode(&self) -> Option<EnterpriseAttestationMode>;
125

126
    /// Lists relying party IDs that can perform enterprise attestation.
127
    ///
128
    /// # Invariant
129
    ///
130
    /// - If the mode is VendorFacilitated, enterprise_attestation_mode() must be non-empty.
131
    ///
132
    /// This list is only considered if enterprise attestation is used.
133
    #[cfg(feature = "std")]
134
    fn enterprise_rp_id_list(&self) -> Vec<String>;
135

136
    /// Returns whether the rp_id is contained in enterprise_rp_id_list().
137
    fn is_enterprise_rp_id(&self, rp_id: &str) -> bool;
138

139
    /// Maximum message size send for CTAP commands.
140
    ///
141
    /// The maximum value is 7609, as HID packets can not encode longer messages.
142
    /// 1024 is the default mentioned in the authenticatorLargeBlobs commands.
143
    /// Larger values are preferred, as that allows more parameters in commands.
144
    /// If long commands are too unreliable on your hardware, consider decreasing
145
    /// this value.
146
    fn max_msg_size(&self) -> usize;
147

148
    /// Sets the number of consecutive failed PINs before blocking interaction.
149
    ///
150
    /// # Invariant
151
    ///
152
    /// - CTAP2.0: Maximum PIN retries must be 8.
153
    /// - CTAP2.1: Maximum PIN retries must be 8 at most.
154
    ///
155
    /// The fail retry counter is reset after entering the correct PIN.
156
    fn max_pin_retries(&self) -> u8;
157

158
    /// Enables or disables basic attestation for FIDO2.
159
    ///
160
    /// # Invariant
161
    ///
162
    /// - Enterprise and batch attestation can not both be active (see above).
163
    ///
164
    /// The basic attestation uses the signing key configured with a vendor command
165
    /// as a batch key. If you turn batch attestation on, be aware that it is your
166
    /// responsibility to safely generate and store the key material. Also, the
167
    /// batches must have size of at least 100k authenticators before using new key
168
    /// material.
169
    /// U2F is unaffected by this setting.
170
    ///
171
    /// https://www.w3.org/TR/webauthn/#attestation
172
    fn use_batch_attestation(&self) -> bool;
173

174
    /// Enables or disables signature counters.
175
    ///
176
    /// The signature counter is currently implemented as a global counter.
177
    /// The specification strongly suggests to have per-credential counters.
178
    /// Implementing those means you can't have an infinite amount of server-side
179
    /// credentials anymore. Also, since counters need frequent writes on the
180
    /// persistent storage, we might need a flash friendly implementation. This
181
    /// solution is a compromise to be compatible with U2F and not wasting storage.
182
    ///
183
    /// https://www.w3.org/TR/webauthn/#signature-counter
184
    fn use_signature_counter(&self) -> bool;
185

186
    // ###########################################################################
187
    // Constants for performance optimization or adapting to different hardware.
188
    //
189
    // Those constants may be modified before compilation to tune the behavior of
190
    // the key.
191
    // ###########################################################################
192

193
    /// Sets the maximum blob size stored with the credBlob extension.
194
    ///
195
    /// # Invariant
196
    ///
197
    /// - The length must be at least 32.
198
    /// - OpenSK puts a limit that the length must be at most 64, as it needs to
199
    ///   be persisted in the credential ID.
200
    fn max_cred_blob_length(&self) -> usize;
201

202
    /// Limits the number of considered entries in credential lists.
203
    ///
204
    /// # Invariant
205
    ///
206
    /// - This value, if present, must be at least 1 (more is preferred).
207
    ///
208
    /// Depending on your memory, you can use Some(n) to limit request sizes in
209
    /// MakeCredential and GetAssertion. This affects allowList and excludeList.
210
    fn max_credential_count_in_list(&self) -> Option<usize>;
211

212
    /// Limits the size of largeBlobs the authenticator stores.
213
    ///
214
    /// # Invariant
215
    ///
216
    /// - The allowed size must be at least 1024.
217
    /// - The array must fit into the shards reserved in storage/key.rs.
218
    fn max_large_blob_array_size(&self) -> usize;
219

220
    /// Limits the number of RP IDs that can change the minimum PIN length.
221
    ///
222
    /// # Invariant
223
    ///
224
    /// - If this value is 0, default_min_pin_length_rp_ids() must be non-empty.
225
    ///
226
    /// You can use this constant to have an upper limit in storage requirements.
227
    /// This might be useful if you want to more reliably predict the remaining
228
    /// storage. Stored string can still be of arbitrary length though, until RP ID
229
    /// truncation is implemented.
230
    /// Outside of memory considerations, you can set this value to 0 if only RP IDs
231
    /// in default_min_pin_length_rp_ids() should be allowed to change the minimum
232
    /// PIN length.
233
    fn max_rp_ids_length(&self) -> usize;
234

235
    /// Sets the number of resident keys you can store.
236
    ///
237
    /// # Invariant
238
    ///
239
    /// - The storage key CREDENTIALS must fit at least this number of credentials.
240
    ///
241
    /// Limiting the number of resident keys permits to ensure a minimum number of
242
    /// counter increments.
243
    /// Let:
244
    /// - P the number of pages (NUM_PAGES in the board definition)
245
    /// - K the maximum number of resident keys (max_supported_resident_keys())
246
    /// - S the maximum size of a resident key (about 500)
247
    /// - C the number of erase cycles (10000)
248
    /// - I the minimum number of counter increments
249
    ///
250
    /// We have: I = (P * 4084 - 5107 - K * S) / 8 * C
251
    ///
252
    /// With P=20 and K=150, we have I=2M which is enough for 500 increments per day
253
    /// for 10 years.
254
    fn max_supported_resident_keys(&self) -> usize;
255
}
256

257
#[derive(Clone)]
2✔
258
pub struct CustomizationImpl {
259
    pub aaguid: &'static [u8; AAGUID_LENGTH],
260
    pub allows_pin_protocol_v1: bool,
261
    pub default_cred_protect: Option<CredentialProtectionPolicy>,
262
    pub default_min_pin_length: u8,
263
    pub default_min_pin_length_rp_ids: &'static [&'static str],
264
    pub enforce_always_uv: bool,
265
    pub enterprise_attestation_mode: Option<EnterpriseAttestationMode>,
266
    pub enterprise_rp_id_list: &'static [&'static str],
267
    pub max_msg_size: usize,
268
    pub max_pin_retries: u8,
269
    pub use_batch_attestation: bool,
270
    pub use_signature_counter: bool,
271
    pub max_cred_blob_length: usize,
272
    pub max_credential_count_in_list: Option<usize>,
273
    pub max_large_blob_array_size: usize,
274
    pub max_rp_ids_length: usize,
275
    pub max_supported_resident_keys: usize,
276
}
277

278
pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
279
    aaguid: &[0; AAGUID_LENGTH],
280
    allows_pin_protocol_v1: true,
281
    default_cred_protect: None,
282
    default_min_pin_length: 4,
283
    default_min_pin_length_rp_ids: &[],
284
    enforce_always_uv: false,
285
    enterprise_attestation_mode: None,
286
    enterprise_rp_id_list: &[],
287
    max_msg_size: 7609,
288
    max_pin_retries: 8,
289
    use_batch_attestation: false,
290
    use_signature_counter: true,
291
    max_cred_blob_length: 32,
292
    max_credential_count_in_list: None,
293
    max_large_blob_array_size: 2048,
294
    max_rp_ids_length: 8,
295
    max_supported_resident_keys: 150,
296
};
297

298
impl Customization for CustomizationImpl {
299
    fn aaguid(&self) -> &'static [u8; AAGUID_LENGTH] {
×
300
        self.aaguid
×
301
    }
×
302

303
    fn allows_pin_protocol_v1(&self) -> bool {
×
304
        self.allows_pin_protocol_v1
×
305
    }
×
306

307
    fn default_cred_protect(&self) -> Option<CredentialProtectionPolicy> {
×
308
        self.default_cred_protect
×
309
    }
×
310

311
    fn default_min_pin_length(&self) -> u8 {
4✔
312
        self.default_min_pin_length
4✔
313
    }
4✔
314

315
    fn default_min_pin_length_rp_ids(&self) -> Vec<String> {
×
316
        self.default_min_pin_length_rp_ids
×
317
            .iter()
×
318
            .map(|s| String::from(*s))
×
319
            .collect()
×
320
    }
×
321

322
    fn enforce_always_uv(&self) -> bool {
×
323
        self.enforce_always_uv
×
324
    }
×
325

326
    fn enterprise_attestation_mode(&self) -> Option<EnterpriseAttestationMode> {
4✔
327
        self.enterprise_attestation_mode
4✔
328
    }
4✔
329

330
    #[cfg(feature = "std")]
331
    fn enterprise_rp_id_list(&self) -> Vec<String> {
2✔
332
        self.enterprise_rp_id_list
2✔
333
            .iter()
2✔
334
            .map(|s| String::from(*s))
2✔
335
            .collect()
2✔
336
    }
2✔
337

338
    fn is_enterprise_rp_id(&self, rp_id: &str) -> bool {
×
339
        self.enterprise_rp_id_list.contains(&rp_id)
×
340
    }
×
341

342
    fn max_msg_size(&self) -> usize {
4✔
343
        self.max_msg_size
4✔
344
    }
4✔
345

346
    fn max_pin_retries(&self) -> u8 {
2✔
347
        self.max_pin_retries
2✔
348
    }
2✔
349

350
    fn use_batch_attestation(&self) -> bool {
2✔
351
        self.use_batch_attestation
2✔
352
    }
2✔
353

354
    fn use_signature_counter(&self) -> bool {
×
355
        self.use_signature_counter
×
356
    }
×
357

358
    fn max_cred_blob_length(&self) -> usize {
4✔
359
        self.max_cred_blob_length
4✔
360
    }
4✔
361

362
    fn max_credential_count_in_list(&self) -> Option<usize> {
2✔
363
        self.max_credential_count_in_list
2✔
364
    }
2✔
365

366
    fn max_large_blob_array_size(&self) -> usize {
2✔
367
        self.max_large_blob_array_size
2✔
368
    }
2✔
369

370
    fn max_rp_ids_length(&self) -> usize {
2✔
371
        self.max_rp_ids_length
2✔
372
    }
2✔
373

374
    fn max_supported_resident_keys(&self) -> usize {
×
375
        self.max_supported_resident_keys
×
376
    }
×
377
}
378

379
#[cfg(feature = "std")]
380
pub fn is_valid(customization: &impl Customization) -> bool {
6✔
381
    // Two invariants are currently tested in different files:
382
    // - storage.rs: if max_large_blob_array_size() fits the shards
383
    // - storage/key.rs: if max_supported_resident_keys() fits CREDENTIALS
384

385
    // Max message size must be between 1024 and 7609.
386
    if customization.max_msg_size() < 1024 || customization.max_msg_size() > 7609 {
6✔
387
        return false;
×
388
    }
6✔
389

390
    // Default min pin length must be between 4 and 63.
391
    if customization.default_min_pin_length() < 4 || customization.default_min_pin_length() > 63 {
6✔
392
        return false;
×
393
    }
6✔
394

395
    // OpenSK prevents activating batch and enterprise attestation together. The
396
    // current implementation uses the same key material at the moment, and these
397
    // two modes have conflicting privacy guarantees.
398
    if customization.use_batch_attestation()
6✔
399
        && customization.enterprise_attestation_mode().is_some()
×
400
    {
401
        return false;
×
402
    }
6✔
403

404
    // enterprise_rp_id_list() should be non-empty in vendor facilitated mode.
405
    if matches!(
406
        customization.enterprise_attestation_mode(),
6✔
407
        Some(EnterpriseAttestationMode::VendorFacilitated)
2✔
408
    ) && customization.enterprise_rp_id_list().is_empty()
×
409
    {
410
        return false;
×
411
    }
6✔
412

413
    // enterprise_rp_id_list() should be empty without an enterprise attestation mode.
414
    if customization.enterprise_attestation_mode().is_none()
6✔
415
        && !customization.enterprise_rp_id_list().is_empty()
4✔
416
    {
417
        return false;
×
418
    }
6✔
419

6✔
420
    // Max pin retries must be less or equal than 8.
6✔
421
    if customization.max_pin_retries() > 8 {
6✔
422
        return false;
×
423
    }
6✔
424

425
    // Max cred blob length should be at least 32, and at most 64.
426
    if customization.max_cred_blob_length() < 32 || customization.max_cred_blob_length() > 64 {
6✔
427
        return false;
×
428
    }
6✔
429

430
    // Max credential count in list should be positive if exists.
431
    if let Some(count) = customization.max_credential_count_in_list() {
6✔
432
        if count < 1 {
×
433
            return false;
×
434
        }
×
435
    }
6✔
436

437
    // Max large blob array size should not be less than 1024.
438
    if customization.max_large_blob_array_size() < 1024 {
6✔
439
        return false;
×
440
    }
6✔
441

442
    // Default min pin length rp ids must be non-empty if max rp ids length is 0.
443
    if customization.max_rp_ids_length() == 0
6✔
444
        && customization.default_min_pin_length_rp_ids().is_empty()
×
445
    {
446
        return false;
×
447
    }
6✔
448

6✔
449
    true
6✔
450
}
6✔
451

452
#[cfg(test)]
453
mod test {
454
    use super::*;
455

456
    #[test]
457
    fn test_invariants() {
3✔
458
        assert!(is_valid(&DEFAULT_CUSTOMIZATION));
2✔
459
    }
2✔
460
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc