cryptoki 0.12.0

Rust-native wrapper around the PKCS #11 API
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
// Copyright 2021 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0
//! Object management functions

use crate::as_cptr;
use crate::context::Function;
use crate::error::{Error, Result, Rv, RvError};
use crate::object::{Attribute, AttributeInfo, AttributeType, ObjectHandle};
use crate::session::Session;
use cryptoki_sys::*;
use std::collections::HashMap;
use std::convert::TryInto;
use std::num::NonZeroUsize;

// Search 10 elements at a time
// Safety: the value provided (10) must be non-zero
const MAX_OBJECT_COUNT: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(10) };

/// Iterator over object handles, in an active session.
///
/// Used to iterate over the object handles returned by underlying calls to `C_FindObjects`.
/// The iterator is created by calling the `iter_objects` and `iter_objects_with_cache_size` methods on a `Session` object.
///
/// # Note
///
/// The iterator `new()` method will call `C_FindObjectsInit`. It means that until the iterator is dropped,
/// creating another iterator will result in an error (typically `RvError::OperationActive` ).
///
/// # Example
///
/// ```no_run
/// use cryptoki::context::{CInitializeArgs, CInitializeFlags};
/// use cryptoki::context::Pkcs11;
/// use cryptoki::error::Error;
/// use cryptoki::object::Attribute;
/// use cryptoki::object::AttributeType;
/// use cryptoki::session::UserType;
/// use cryptoki::types::AuthPin;
/// use std::env;
///
/// # fn main() -> testresult::TestResult {
/// # let pkcs11 = Pkcs11::new(
/// #    env::var("TEST_PKCS11_MODULE")
/// #        .unwrap_or_else(|_| "/usr/local/lib/libsofthsm2.so".to_string()),
/// # )?;
/// #
/// # pkcs11.initialize(CInitializeArgs::new(CInitializeFlags::OS_LOCKING_OK))?;
/// # let slot = pkcs11.get_slots_with_token()?.remove(0);
/// #
/// # let session = pkcs11.open_ro_session(slot).unwrap();
/// # session.login(UserType::User, Some(&AuthPin::new("fedcba".into())))?;
///
/// let token_object = vec![Attribute::Token(true)];
/// let wanted_attr = vec![AttributeType::Label];
///
/// for (idx, obj) in session.iter_objects(&token_object)?.enumerate() {
///     let obj = obj?; // handle potential error condition
///
///     let attributes = session.get_attributes(obj, &wanted_attr)?;
///
///     match attributes.first() {
///         Some(Attribute::Label(l)) => {
///             println!(
///                 "token object #{}: handle {}, label {}",
///                 idx,
///                 obj,
///                 String::from_utf8(l.to_vec())
///                     .unwrap_or_else(|_| "*** not valid utf8 ***".to_string())
///             );
///         }
///         _ => {
///             println!("token object #{}: handle {}, label not found", idx, obj);
///         }
///     }
/// }
/// # Ok(())
/// # }
///
/// ```
#[derive(Debug)]
pub struct ObjectHandleIterator<'a> {
    session: &'a Session,
    object_count: usize,
    index: usize,
    cache: Vec<CK_OBJECT_HANDLE>,
}

impl<'a> ObjectHandleIterator<'a> {
    /// Create a new iterator over object handles.
    ///
    /// # Arguments
    ///
    /// * `session` - The session to iterate over
    /// * `template` - The template to match objects against
    /// * `cache_size` - The number of objects to cache (type is [`NonZeroUsize`])
    ///
    /// # Returns
    ///
    /// This function will return a [`Result<ObjectHandleIterator>`] that can be used to iterate over the objects
    /// matching the template. The cache size corresponds to the size of the array provided to `C_FindObjects()`.
    ///
    /// # Errors
    ///
    /// This function will return an error if the call to `C_FindObjectsInit` fails.
    ///
    /// # Note
    ///
    /// The iterator `new()` method will call `C_FindObjectsInit`. It means that until the iterator is dropped,
    /// creating another iterator will result in an error (typically `RvError::OperationActive` ).
    ///
    fn new(
        session: &'a Session,
        mut template: Vec<CK_ATTRIBUTE>,
        cache_size: NonZeroUsize,
    ) -> Result<Self> {
        unsafe {
            Rv::from(get_pkcs11!(session.client(), C_FindObjectsInit)(
                session.handle(),
                template.as_mut_ptr(),
                template.len().try_into()?,
            ))
            .into_result(Function::FindObjectsInit)?;
        }

        let cache: Vec<CK_OBJECT_HANDLE> = vec![0; cache_size.get()];
        Ok(ObjectHandleIterator {
            session,
            object_count: cache_size.get(),
            index: cache_size.get(),
            cache,
        })
    }
}

// In this implementation, we use object_count to keep track of the number of objects
// returned by the last C_FindObjects call; the index is used to keep track of
// the next object in the cache to be returned. The size of cache is never changed.
// In order to enter the loop for the first time, we set object_count to cache_size
// and index to cache_size. That allows to jump directly to the C_FindObjects call
// and start filling the cache.

impl Iterator for ObjectHandleIterator<'_> {
    type Item = Result<ObjectHandle>;

    fn next(&mut self) -> Option<Self::Item> {
        // since the iterator is initialized with object_count and index both equal and > 0,
        // we are guaranteed to enter the loop at least once
        while self.object_count > 0 {
            // if index<object_count, we have items in the cache to return
            if self.index < self.object_count {
                self.index += 1;
                return Some(Ok(ObjectHandle::new(self.cache[self.index - 1])));
            } else {
                // reset counters and proceed to the next section
                self.index = 0;

                if self.object_count < self.cache.len() {
                    // if self.object_count is less than the cache size,
                    // it means our last call to C_FindObjects returned less than the cache size
                    // At this point, we have exhausted all objects in the cache
                    // and we can safely break the loop and return None
                    self.object_count = 0;
                    break;
                } else {
                    // reset the counter - C_FindObjects will adjust that value.
                    self.object_count = 0;
                }
            }

            let p11rv = match get_pkcs11_func!(self.session.client(), C_FindObjects) {
                Some(f) => unsafe {
                    f(
                        self.session.handle(),
                        self.cache.as_mut_ptr(),
                        self.cache.len() as CK_ULONG,
                        &mut self.object_count as *mut usize as CK_ULONG_PTR,
                    )
                },
                None => {
                    // C_FindObjects() is not implemented,, bark and return an error
                    log::error!("C_FindObjects() is not implemented on this library");
                    return Some(Err(Error::NullFunctionPointer) as Result<ObjectHandle>);
                }
            };

            if let Rv::Error(error) = Rv::from(p11rv) {
                return Some(
                    Err(Error::Pkcs11(error, Function::FindObjects)) as Result<ObjectHandle>
                );
            }
        }
        None
    }
}

impl Drop for ObjectHandleIterator<'_> {
    fn drop(&mut self) {
        if let Some(f) = get_pkcs11_func!(self.session.client(), C_FindObjectsFinal) {
            // swallow the return value, as we can't do anything about it,
            // but log the error
            if let Rv::Error(err) = Rv::from(unsafe { f(self.session.handle()) }) {
                log::error!("C_FindObjectsFinal() failed with error: {err:?}");
            }
        } else {
            // bark but pass if C_FindObjectsFinal() is not implemented
            log::error!("C_FindObjectsFinal() is not implemented on this library");
        }
    }
}

impl Session {
    /// Iterate over session objects matching a template.
    ///
    /// # Arguments
    ///
    /// * `template` - The template to match objects against
    ///
    /// # Returns
    ///
    /// This function will return a [`Result<ObjectHandleIterator>`] that can be used to iterate over the objects
    /// matching the template. Note that the cache size is managed internally and set to a default value (10)
    ///
    /// # See also
    ///
    /// * [`ObjectHandleIterator`] for more information on how to use the iterator
    /// * [`Session::iter_objects_with_cache_size`] for a way to specify the cache size
    #[inline(always)]
    pub fn iter_objects(&self, template: &[Attribute]) -> Result<ObjectHandleIterator<'_>> {
        self.iter_objects_with_cache_size(template, MAX_OBJECT_COUNT)
    }

    /// Iterate over session objects matching a template, with cache size
    ///
    /// # Arguments
    ///
    /// * `template` - The template to match objects against
    /// * `cache_size` - The number of objects to cache (type is [`NonZeroUsize`])
    ///
    /// # Returns
    ///
    /// This function will return a [`Result<ObjectHandleIterator>`] that can be used to iterate over the objects
    /// matching the template. The cache size corresponds to the size of the array provided to `C_FindObjects()`.
    ///
    /// # See also
    ///
    /// * [`ObjectHandleIterator`] for more information on how to use the iterator
    /// * [`Session::iter_objects`] for a simpler way to iterate over objects
    pub fn iter_objects_with_cache_size(
        &self,
        template: &[Attribute],
        cache_size: NonZeroUsize,
    ) -> Result<ObjectHandleIterator<'_>> {
        let template: Vec<CK_ATTRIBUTE> = template.iter().map(Into::into).collect();
        ObjectHandleIterator::new(self, template, cache_size)
    }

    /// Search for session objects matching a template
    ///
    /// # Arguments
    ///
    /// * `template` - A reference to [Attribute] of search parameters that will be used
    ///   to find objects.
    ///
    /// # Returns
    ///
    /// Upon success, a vector of [`ObjectHandle`] wrapped in a Result.
    /// Upon failure, the first error encountered.
    ///
    /// # Note
    ///
    /// It is a convenience method that will call [`Session::iter_objects`] and collect the results.
    ///
    /// # See also
    ///
    /// * [`Session::iter_objects`] for a way to specify the cache size
    ///
    /// # Example
    ///
    /// ```rust
    /// # fn main() -> testresult::TestResult {
    /// # use cryptoki::session::Session;
    /// # use cryptoki::context::{Pkcs11, CInitializeArgs, CInitializeFlags};
    /// # use cryptoki::object::{Attribute, AttributeType, CertificateType, ObjectClass, ObjectHandle};
    /// #
    /// # let mut client = Pkcs11::new(
    /// #    std::env::var("TEST_PKCS11_MODULE")
    /// #       .unwrap_or_else(|_| "/usr/local/lib/softhsm/libsofthsm2.so".to_string()),
    /// # )?;
    /// # client.initialize(CInitializeArgs::new(CInitializeFlags::OS_LOCKING_OK))?;
    /// #
    /// # // Use the first slot
    /// # let slot = client.get_all_slots()?[0];
    /// # let session = client.open_ro_session(slot)?;
    /// #
    /// // Get handles to all of the x509 certificates on the card
    /// let search = vec![Attribute::Class(ObjectClass::CERTIFICATE), Attribute::CertificateType(CertificateType::X_509)];
    /// for handle in session.find_objects(&search)? {
    ///     // each cert: get the "value" which will be the raw certificate data
    ///     for value in session.get_attributes(handle, &[AttributeType::Value])? {
    ///        if let Attribute::Value(value) = value {
    ///            println!("Certificate value: {value:?}");
    ///        }
    ///     }
    /// }
    /// # Ok(()) }
    /// ```
    ///
    #[inline(always)]
    pub fn find_objects(&self, template: &[Attribute]) -> Result<Vec<ObjectHandle>> {
        self.iter_objects(template)?.collect()
    }

    /// Create a new object
    pub fn create_object(&self, template: &[Attribute]) -> Result<ObjectHandle> {
        let mut template: Vec<CK_ATTRIBUTE> = template.iter().map(|attr| attr.into()).collect();
        let mut object_handle = 0;

        unsafe {
            Rv::from(get_pkcs11!(self.client(), C_CreateObject)(
                self.handle(),
                template.as_mut_ptr(),
                template.len().try_into()?,
                &mut object_handle as CK_OBJECT_HANDLE_PTR,
            ))
            .into_result(Function::CreateObject)?;
        }

        Ok(ObjectHandle::new(object_handle))
    }

    /// Destroy an object
    pub fn destroy_object(&self, object: ObjectHandle) -> Result<()> {
        unsafe {
            Rv::from(get_pkcs11!(self.client(), C_DestroyObject)(
                self.handle(),
                object.handle(),
            ))
            .into_result(Function::DestroyObject)
        }
    }

    /// Copy an object
    ///
    /// A template can be provided to change some attributes of the new object, when allowed.
    ///
    /// # Arguments
    ///
    /// * `object` - The [ObjectHandle] used to reference the object to copy
    /// * `template` - new values for any attributes of the object that can ordinarily be modified
    ///   check out [PKCS#11 documentation](https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.1/cs01/pkcs11-spec-v3.1-cs01.html#_Toc111203284) for details
    ///
    /// # Returns
    ///
    /// This function will return a new [ObjectHandle] that references the newly created object.
    ///
    pub fn copy_object(
        &self,
        object: ObjectHandle,
        template: &[Attribute],
    ) -> Result<ObjectHandle> {
        let mut template: Vec<CK_ATTRIBUTE> = template.iter().map(|attr| attr.into()).collect();
        let mut object_handle = 0;

        unsafe {
            Rv::from(get_pkcs11!(self.client(), C_CopyObject)(
                self.handle(),
                object.handle(),
                template.as_mut_ptr(),
                template.len().try_into()?,
                &mut object_handle as CK_OBJECT_HANDLE_PTR,
            ))
            .into_result(Function::CopyObject)?;
        }

        Ok(ObjectHandle::new(object_handle))
    }

    /// Get the attribute info of an object: if the attribute is present and its size.
    ///
    /// # Arguments
    ///
    /// * `object` - The [ObjectHandle] used to reference the object
    /// * `attributes` - The list of attributes to get the information of
    ///
    /// # Returns
    ///
    /// This function will return a Vector of [AttributeInfo] enums that will either contain
    /// the size of the requested attribute, [AttributeInfo::TypeInvalid] if the attribute is not a
    /// valid type for the object, or [AttributeInfo::Sensitive] if the requested attribute is
    /// sensitive and will not be returned to the user.
    ///
    /// The list of returned attributes is 1-to-1 matched with the provided vector of attribute
    /// types.  If you wish, you may create a hash table simply by:
    ///
    /// ```no_run
    /// use cryptoki::context::Pkcs11;
    /// use cryptoki::context::{CInitializeArgs, CInitializeFlags};
    /// use cryptoki::object::AttributeType;
    /// use cryptoki::session::UserType;
    /// use cryptoki::types::AuthPin;
    /// use std::collections::HashMap;
    /// use std::env;
    ///
    /// let mut pkcs11 = Pkcs11::new(
    ///         env::var("TEST_PKCS11_MODULE")
    ///             .unwrap_or_else(|_| "/usr/local/lib/softhsm/libsofthsm2.so".to_string()),
    ///     )
    ///     .unwrap();
    ///
    /// pkcs11.initialize(CInitializeArgs::new(CInitializeFlags::OS_LOCKING_OK)).unwrap();
    /// let slot = pkcs11.get_slots_with_token().unwrap().remove(0);
    ///
    /// let session = pkcs11.open_ro_session(slot).unwrap();
    /// session.login(UserType::User, Some(&AuthPin::new("fedcba123456".into())));
    ///
    /// let empty_attrib= vec![];
    /// if let Some(object) = session.find_objects(&empty_attrib).unwrap().first() {
    ///     let attribute_types = vec![
    ///         AttributeType::Token,
    ///         AttributeType::Private,
    ///         AttributeType::Modulus,
    ///         AttributeType::KeyType,
    ///         AttributeType::Verify,];
    ///
    ///     let attribute_info = session.get_attribute_info(*object, &attribute_types).unwrap();
    ///
    ///     let hash = attribute_types
    ///         .iter()
    ///         .zip(attribute_info.iter())
    ///         .collect::<HashMap<_, _>>();
    /// }
    /// ```
    ///
    /// Alternatively, you can call [Session::get_attribute_info_map], found below.
    pub fn get_attribute_info(
        &self,
        object: ObjectHandle,
        attributes: &[AttributeType],
    ) -> Result<Vec<AttributeInfo>> {
        let mut results = Vec::new();

        for attrib in attributes.iter() {
            let mut template: Vec<CK_ATTRIBUTE> = vec![CK_ATTRIBUTE {
                type_: (*attrib).into(),
                pValue: std::ptr::null_mut(),
                ulValueLen: 0,
            }];

            match unsafe {
                Rv::from(get_pkcs11!(self.client(), C_GetAttributeValue)(
                    self.handle(),
                    object.handle(),
                    template.as_mut_ptr(),
                    template.len().try_into()?,
                ))
            } {
                Rv::Ok => {
                    if template[0].ulValueLen == CK_UNAVAILABLE_INFORMATION {
                        results.push(AttributeInfo::Unavailable)
                    } else {
                        results.push(AttributeInfo::Available(template[0].ulValueLen.try_into()?))
                    }
                }
                Rv::Error(RvError::AttributeSensitive) => results.push(AttributeInfo::Sensitive),
                Rv::Error(RvError::AttributeTypeInvalid) => {
                    results.push(AttributeInfo::TypeInvalid)
                }
                rv => rv.into_result(Function::GetAttributeValue)?,
            }
        }
        Ok(results)
    }

    /// Get the attribute info of an object: if the attribute is present and its size.
    ///
    /// # Arguments
    ///
    /// * `object` - The [ObjectHandle] used to reference the object
    /// * `attributes` - The list of attributes to get the information of
    ///
    /// # Returns
    ///
    /// This function will return a HashMap of [AttributeType] and [AttributeInfo] enums that will
    /// either contain the size of the requested attribute, [AttributeInfo::TypeInvalid] if the
    /// attribute is not a valid type for the object, or [AttributeInfo::Sensitive] if the requested
    /// attribute is sensitive and will not be returned to the user.
    pub fn get_attribute_info_map(
        &self,
        object: ObjectHandle,
        attributes: &[AttributeType],
    ) -> Result<HashMap<AttributeType, AttributeInfo>> {
        let attrib_info = self.get_attribute_info(object, attributes)?;

        Ok(attributes
            .iter()
            .cloned()
            .zip(attrib_info.iter().cloned())
            .collect::<HashMap<_, _>>())
    }

    /// Get the attributes values of an object, filtering out unavailable ones.
    ///
    /// # Arguments
    ///
    /// * `object` - The [ObjectHandle] used to reference the object
    /// * `attributes` - The list of attribute types to retrieve
    ///
    /// # Returns
    ///
    /// A vector of [Attribute] containing the values of the available attributes.
    ///
    /// # Note
    ///
    /// This method follows PKCS#11 spec: in the first call, it provides pre-allocated buffers
    /// for attributes with a known fixed size, and NULL pointers for other attributes to query
    /// their size.
    ///
    /// After the first call:
    /// - Attributes that fit in pre-allocated buffers are ready (pValue != NULL, valid ulValueLen)
    /// - Attributes with NULL pValue but valid ulValueLen need a second fetch
    /// - Attributes with CK_UNAVAILABLE_INFORMATION are skipped
    ///
    /// In total, a maximum of 2 calls to C_GetAttributeValue are made.
    ///
    pub fn get_attributes(
        &self,
        object: ObjectHandle,
        attributes: &[AttributeType],
    ) -> Result<Vec<Attribute>> {
        // Step 1: Build pass1 template
        // - Pre-allocate buffers for attributes with known fixed size
        // - Use NULL pointers for all other attributes to query their size
        let mut buffers: Vec<Vec<u8>> = Vec::with_capacity(attributes.len());
        let mut template1: Vec<CK_ATTRIBUTE> = Vec::with_capacity(attributes.len());

        for attr_type in attributes.iter() {
            if let Some(size) = attr_type.fixed_size() {
                // We know the needed size, we allocate
                let buffer = vec![0u8; size];
                template1.push(CK_ATTRIBUTE {
                    type_: (*attr_type).into(),
                    pValue: as_cptr!(buffer),
                    ulValueLen: size as CK_ULONG,
                });
                buffers.push(buffer);
            } else {
                // This is a variable size, we set length to 0 and set the buffer ptr to NULL
                template1.push(CK_ATTRIBUTE {
                    type_: (*attr_type).into(),
                    pValue: std::ptr::null_mut(),
                    ulValueLen: 0,
                });
                buffers.push(Vec::new());
            }
        }

        // Step 2: Make pass1 call to C_GetAttributeValue
        let rv = unsafe {
            Rv::from(get_pkcs11!(self.client(), C_GetAttributeValue)(
                self.handle(),
                object.handle(),
                template1.as_mut_ptr(),
                template1.len().try_into()?,
            ))
        };

        match rv {
            Rv::Ok
            | Rv::Error(RvError::BufferTooSmall)
            | Rv::Error(RvError::AttributeSensitive)
            | Rv::Error(RvError::AttributeTypeInvalid) => {
                // acceptable - we'll inspect ulValueLen/pValue
            }
            _ => {
                rv.into_result(Function::GetAttributeValue)?;
            }
        }

        // Step 3: Categorize pass1 results into pass1 (already satisfied) and pass2 (need fetch)
        let mut pass1_indices: Vec<usize> = Vec::new();
        let mut pass2_indices: Vec<usize> = Vec::new();

        for (i, attr) in template1.iter().enumerate() {
            if attr.ulValueLen == CK_UNAVAILABLE_INFORMATION {
                // Skip unavailable attributes
                continue;
            } else if attr.pValue.is_null() {
                // NULL pointer - needs fetching in pass2
                // Note: even if ulValueLen is 0, we still need to fetch as 0 length attributes are legit
                pass2_indices.push(i);
            } else {
                // If buffer was pre-allocated but too small, need to fetch in pass2
                if attr.ulValueLen > buffers[i].len() as CK_ULONG {
                    pass2_indices.push(i);
                } else {
                    // Has data already - satisfied in pass1
                    pass1_indices.push(i);
                }
            }
        }

        // Step 4: Make pass2 call if needed for attributes that need fetching
        let pass2_template_and_indices: Option<(Vec<CK_ATTRIBUTE>, Vec<usize>)> =
            if pass2_indices.is_empty() {
                None
            } else {
                let mut template2: Vec<CK_ATTRIBUTE> = Vec::with_capacity(pass2_indices.len());

                for &idx in pass2_indices.iter() {
                    let size = template1[idx].ulValueLen as usize;
                    buffers[idx].resize(size, 0);

                    template2.push(CK_ATTRIBUTE {
                        type_: template1[idx].type_,
                        pValue: as_cptr!(buffers[idx]),
                        ulValueLen: buffers[idx].len() as CK_ULONG,
                    });
                }

                let rv = unsafe {
                    Rv::from(get_pkcs11!(self.client(), C_GetAttributeValue)(
                        self.handle(),
                        object.handle(),
                        template2.as_mut_ptr(),
                        template2.len().try_into()?,
                    ))
                };

                match rv {
                    Rv::Ok
                    | Rv::Error(RvError::AttributeSensitive)
                    | Rv::Error(RvError::AttributeTypeInvalid) => {
                        // acceptable
                    }
                    _ => {
                        rv.into_result(Function::GetAttributeValue)?;
                    }
                }

                // Add indices satisfied by pass2 into pass1_indices
                for (i, &idx) in pass2_indices.iter().enumerate() {
                    if template2[i].ulValueLen != CK_UNAVAILABLE_INFORMATION {
                        pass1_indices.push(idx);
                    }
                }

                Some((template2, pass2_indices))
            };

        // Step 5: Build result Vec<Attribute> preserving request order
        // Sort pass1_indices to preserve the original order from attributes[]
        pass1_indices.sort_unstable();

        let mut results = Vec::new();
        for &idx in pass1_indices.iter() {
            let attr = if let Some((ref template2, ref indices2)) = pass2_template_and_indices {
                if let Some(pos) = indices2.iter().position(|&i| i == idx) {
                    // attribute came from pass2
                    template2[pos].try_into()?
                } else {
                    // attribute came from pass1
                    template1[idx].try_into()?
                }
            } else {
                // Only pass1 happened
                template1[idx].try_into()?
            };

            results.push(attr);
        }

        Ok(results)
    }

    /// Sets the attributes of an object
    pub fn update_attributes(&self, object: ObjectHandle, template: &[Attribute]) -> Result<()> {
        let mut template: Vec<CK_ATTRIBUTE> = template.iter().map(|attr| attr.into()).collect();

        unsafe {
            Rv::from(get_pkcs11!(self.client(), C_SetAttributeValue)(
                self.handle(),
                object.handle(),
                template.as_mut_ptr(),
                template.len().try_into()?,
            ))
            .into_result(Function::SetAttributeValue)?;
        }

        Ok(())
    }
}