@@ -85,12 +85,19 @@ impl SystemScardContext {
8585
8686 #[ cfg( not( target_os = "windows" ) ) ]
8787 if _with_cache {
88- let ( container_name , auth_cert , auth_cert_der ) = if let ( Ok ( auth_cert ) , Ok ( container_name) ) =
88+ let scard_logon_params = if let ( Ok ( certificate ) , Ok ( container_name) ) =
8989 ( winscard:: env:: auth_cert_from_env ( ) , winscard:: env:: container_name ( ) )
9090 {
91- let auth_cert_der = auth_cert . to_der ( ) ? ;
91+ use crate :: winscard :: piv :: SlotId ;
9292
93- ( container_name, auth_cert, auth_cert_der)
93+ let certificate_der = certificate. to_der ( ) ?;
94+
95+ ScardLogonParams {
96+ container_name,
97+ certificate,
98+ certificate_der,
99+ slot : SlotId :: PivAuthentication ,
100+ }
94101 } else {
95102 use std:: path:: Path ;
96103
@@ -102,23 +109,29 @@ impl SystemScardContext {
102109 try_extract_container_name_and_certificate ( Path :: new ( & pkcs11_module_path) ) ?
103110 } ;
104111
105- scard_context. cache = init_scard_cache ( & container_name , auth_cert , & auth_cert_der ) ?;
112+ scard_context. cache = init_scard_cache ( & scard_logon_params ) ?;
106113 }
107114
108115 Ok ( scard_context)
109116 }
110117}
111118
119+ #[ cfg( not( target_os = "windows" ) ) ]
120+ struct ScardLogonParams {
121+ container_name : String ,
122+ certificate : picky:: x509:: Cert ,
123+ certificate_der : Vec < u8 > ,
124+ slot : crate :: winscard:: piv:: SlotId ,
125+ }
126+
112127#[ cfg( not( target_os = "windows" ) ) ]
113128#[ instrument( err) ]
114- pub fn try_extract_container_name_and_certificate (
115- pkcs11_module : & std:: path:: Path ,
116- ) -> Result < ( String , picky:: x509:: Cert , Vec < u8 > ) , Error > {
129+ fn try_extract_container_name_and_certificate ( pkcs11_module : & std:: path:: Path ) -> Result < ScardLogonParams , Error > {
117130 use cryptoki:: context:: { CInitializeArgs , Pkcs11 } ;
118131 use cryptoki:: object:: { Attribute , AttributeType , CertificateType , ObjectClass } ;
119132 use picky:: x509:: Cert ;
120133
121- use crate :: winscard:: piv:: try_get_piv_container_name;
134+ use crate :: winscard:: piv:: { try_get_piv_container_name, SlotId } ;
122135
123136 macro_rules! try_execute {
124137 ( $x: expr, $msg: literal) => { {
@@ -177,6 +190,7 @@ pub fn try_extract_container_name_and_certificate(
177190 } ;
178191
179192 let mut container_name = None ;
193+ let mut slot = None ;
180194 // We found a suitable certificate for smart card logon on the device.
181195 // Next, we check if the inserted device is a PIV smart card. If so, we will attempt
182196 // to extract the container name using raw APDU commands.
@@ -192,6 +206,10 @@ pub fn try_extract_container_name_and_certificate(
192206 try_get_piv_container_name( reader_name, & label) ,
193207 "failed to construct a container name"
194208 ) ) ;
209+ slot = Some ( try_execute ! (
210+ SlotId :: from_certificate_label( & label) ,
211+ "failed to determine a slot id"
212+ ) ) ;
195213
196214 if container_name. is_some ( ) {
197215 break ;
@@ -202,12 +220,21 @@ pub fn try_extract_container_name_and_certificate(
202220 continue ;
203221 } ;
204222
223+ let Some ( slot) = slot else {
224+ continue ;
225+ } ;
226+
205227 let certificate = try_execute ! (
206228 Cert :: from_der( & certificate_der) ,
207229 "failed to parse smart card certificate"
208230 ) ;
209231
210- return Ok ( ( container_name, certificate, certificate_der) ) ;
232+ return Ok ( ScardLogonParams {
233+ container_name,
234+ certificate,
235+ certificate_der,
236+ slot,
237+ } ) ;
211238 }
212239
213240 Err ( Error :: new (
@@ -253,11 +280,14 @@ fn validate_certificate(certificate: &[u8]) -> Result<(), Error> {
253280}
254281
255282#[ cfg( not( target_os = "windows" ) ) ]
256- fn init_scard_cache (
257- container_name : & str ,
258- auth_cert : picky:: x509:: Cert ,
259- auth_cert_der : & [ u8 ] ,
260- ) -> WinScardResult < BTreeMap < String , Vec < u8 > > > {
283+ fn init_scard_cache ( scard_logon_params : & ScardLogonParams ) -> WinScardResult < BTreeMap < String , Vec < u8 > > > {
284+ let ScardLogonParams {
285+ container_name,
286+ certificate,
287+ certificate_der,
288+ slot,
289+ } = scard_logon_params;
290+
261291 use picky_asn1_x509:: { PublicKey , SubjectPublicKeyInfo } ;
262292
263293 // https://github.com/selfrender/Windows-Server-2003/blob/5c6fe3db626b63a384230a1aa6b92ac416b0765f/ds/security/csps/wfsccsp/inc/basecsp.h#L86-L93
@@ -388,7 +418,7 @@ fn init_scard_cache(
388418 value. extend_from_slice ( b"RSA1" ) ; // magic = RSA1
389419 value. extend_from_slice ( & 2048_u32 . to_le_bytes ( ) ) ; // bitlen = 2048
390420
391- let public_key = auth_cert . public_key ( ) ;
421+ let public_key = certificate . public_key ( ) ;
392422 let public_key: & SubjectPublicKeyInfo = public_key. as_ref ( ) ;
393423 let ( modulus, public_exponent) = match & public_key. subject_public_key {
394424 PublicKey :: Rsa ( rsa) => (
@@ -423,9 +453,9 @@ fn init_scard_cache(
423453 // unkown flags
424454 value. extend_from_slice ( & [ 0 , 0 , 0 , 0 , 0 , 0 ] ) ;
425455
426- value. extend_from_slice ( & ( u16:: try_from ( auth_cert_der . len ( ) ) ?. to_le_bytes ( ) ) ) ; // uncompressed certificate data len
456+ value. extend_from_slice ( & ( u16:: try_from ( certificate_der . len ( ) ) ?. to_le_bytes ( ) ) ) ; // uncompressed certificate data len
427457 value. extend_from_slice ( & [ 0x00 , 0x00 ] ) ; // flags that specify that the certificate is not compressed
428- value. extend_from_slice ( auth_cert_der ) ;
458+ value. extend_from_slice ( certificate_der ) ;
429459
430460 value
431461 } ) ;
@@ -443,6 +473,93 @@ fn init_scard_cache(
443473 value
444474 } ) ;
445475
476+ //= YubiKey-related cache items =//
477+
478+ /// The KeySpec property identifies how a key that is generated or retrieved using the Microsoft CryptoAPI (CAPI)
479+ /// from a Microsoft legacy Cryptographic Storage Provider (CSP) can be used.
480+ ///
481+ /// We need the [KeySpec] value to build scard cache items correctly.
482+ #[ repr( u8 ) ]
483+ enum KeySpec {
484+ /// RSA key that can be used for signing and decryption.
485+ AtKeyExchange = 1 ,
486+ /// RSA signature only key.
487+ AtSignature = 2 ,
488+ }
489+
490+ cache. insert ( "ykmd_cardcf" . to_owned ( ) , CACHE_ITEM_HEADER . to_vec ( ) ) ;
491+
492+ cache. insert ( "ykmd_lastupdate" . to_owned ( ) , {
493+ use std:: time:: { SystemTime , UNIX_EPOCH } ;
494+
495+ let now = SystemTime :: now ( ) ;
496+ let epoch_seconds = now. duration_since ( UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
497+
498+ epoch_seconds. to_le_bytes ( ) . to_vec ( )
499+ } ) ;
500+
501+ cache. insert ( "ykmd_cmap" . to_owned ( ) , {
502+ use ffi_types:: WChar ;
503+ use sha2:: Digest ;
504+
505+ use crate :: winscard:: piv:: SlotId ;
506+
507+ // This cache item represents the `ykpiv_container` object:
508+ //
509+ // https://github.com/Yubico/yubico-piv-tool/blob/6444d40d73f4d4c029c9739938edf57ede23e6f1/lib/ykpiv.h#L268-L277
510+ // ```C
511+ // typedef struct _ykpiv_container {
512+ // wchar_t name[40];
513+ // uint8_t slot;
514+ // uint8_t key_spec;
515+ // uint16_t key_size_bits;
516+ // uint8_t flags;
517+ // uint8_t pin_id;
518+ // uint8_t associated_echd_container;
519+ // uint8_t cert_fingerprint[20];
520+ // } ykpiv_container;
521+ // ```
522+
523+ let mut container_name_encoded = container_name
524+ . encode_utf16 ( )
525+ . chain ( core:: iter:: once ( 0 ) )
526+ . flat_map ( |v| v. to_le_bytes ( ) )
527+ . collect :: < Vec < _ > > ( ) ;
528+ let container_name_bytes_len = size_of :: < WChar > ( ) * MAX_CONTAINER_NAME_LEN ;
529+ debug_assert_eq ! ( container_name_bytes_len, 80 ) ;
530+
531+ container_name_encoded. resize ( container_name_bytes_len, 0 ) ;
532+ debug ! ( ?container_name_encoded) ;
533+
534+ let mut value = container_name_encoded;
535+ value. push ( slot. as_u8 ( ) ) ; // Slot.
536+
537+ // The KeySpec value is not encoded inside certificate. We determine it by looking at the certificate slot id.
538+ let key_spec = match slot {
539+ SlotId :: PivAuthentication => KeySpec :: AtKeyExchange ,
540+ SlotId :: DigitalSignature => KeySpec :: AtSignature ,
541+ SlotId :: KeyManagement => KeySpec :: AtKeyExchange ,
542+ SlotId :: CardAuthentication => KeySpec :: AtKeyExchange ,
543+ } ;
544+ value. push ( key_spec as u8 ) ;
545+
546+ value. extend_from_slice ( & [ 0x00 , 0x08 ] ) ; // KeySize.
547+ value. push ( 0x03 ) ; // Flags.
548+ value. push ( 0x01 ) ; // PIN id.
549+ value. push ( 0xff ) ; // Associated ECHD container (none).
550+
551+ // Discovered experimentally:
552+ // The `cert_fingerprint` field contains the first 20 bytes of SHA256 cert hash (instead of SHA1 cert hash).
553+ let cert_fingerprint = sha2:: Sha256 :: digest ( certificate_der) ;
554+ value. extend_from_slice ( & cert_fingerprint. as_slice ( ) [ 0 ..20 ] ) ;
555+
556+ // The value length must be the correct size. The YubiKey Smart Card Minidriver checks it.
557+ // The `0x6b` was extracted from the `ykmd.dll` and is also equal to the `ykpiv_container` size.
558+ debug_assert_eq ! ( value. len( ) , 0x6b ) ;
559+
560+ value
561+ } ) ;
562+
446563 Ok ( cache)
447564}
448565
0 commit comments