// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::sync::{Arc, Mutex}; use tracing::instrument; use x509_cert::der::Encode; use yubikey::piv::AlgorithmId; use yubikey::YubiKey; use crate::piv::slot::SlotObject; use crate::piv::util::get_algorithm_id_by_certificate; use native_pkcs11_traits::Certificate as P11Certificate; use native_pkcs11_traits::KeyAlgorithm as P11KeyAlgorithm; use native_pkcs11_traits::KeySearchOptions as P11KeySearchOptions; use native_pkcs11_traits::PrivateKey as P11PrivateKey; use native_pkcs11_traits::PublicKey as P11PublicKey; use native_pkcs11_traits::Result as P11Result; use native_pkcs11_traits::{Backend, KeyAlgorithm, KeySearchOptions}; #[derive(Debug, Default)] pub struct YubikeyPivBackend { cached_pin: Mutex>, yubikey: Mutex>, slot_objects: Mutex>>, } impl YubikeyPivBackend { pub fn new() -> Self { YubikeyPivBackend::default() } fn run_with_yubikey(&self, verify: bool, mut callback: F) -> P11Result<()> where F: FnMut(&mut YubiKey) -> P11Result<()>, { let mut yubikey = self.yubikey.lock().unwrap(); if yubikey.is_none() { *yubikey = Some(YubiKey::open()?); } let mut yk = yubikey.as_mut().unwrap(); if verify { let pin = self.prepare_pin()?; let verify_result = yk.verify_pin(pin.as_bytes()); if verify_result.is_err() { self.clear_pin(); } verify_result?; } callback(&mut yk) } fn clear_pin(&self) -> () { let mut cached_pin = self.cached_pin.lock().unwrap(); if cached_pin.is_some() { *cached_pin = None; } } fn prepare_pin(&self) -> P11Result { let mut cached_pin = self.cached_pin.lock().unwrap(); if cached_pin.is_none() { let pin = crate::piv::pinentry::get_pin()?; *cached_pin = Some(pin); } Ok(cached_pin.as_deref().unwrap().to_string()) } fn init_slot_objects(&self) -> P11Result<()> { let mut slot_objects_opt = self.slot_objects.lock().unwrap(); if slot_objects_opt.is_some() { return Ok(()); } let mut slot_objects = vec![]; self.run_with_yubikey(false, |yk| { let keys = yk.piv_keys()?; for key in keys { let slot_id = key.slot().to_string(); let certificate_der = key.certificate().cert.to_der()?; let public_key_der = key.certificate().cert.tbs_certificate.subject_public_key_info.to_der()?; let algorithm_id = get_algorithm_id_by_certificate(key.certificate())?; let algorithm = match algorithm_id { AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => KeyAlgorithm::Rsa, AlgorithmId::EccP256 | AlgorithmId::EccP384 => KeyAlgorithm::Ecc, }; // println!(">>> {} {:?} {}", &slot_id, algorithm_id, &key.certificate().cert.tbs_certificate.subject); // TODO remove let slot_object = SlotObject::new( algorithm, slot_id, certificate_der, public_key_der, ); slot_objects.push(slot_object); } Ok(()) })?; *slot_objects_opt = Some(slot_objects); Ok(()) } fn for_each_slot_objects(&self, mut callback: F) -> P11Result<()> where F: FnMut(&SlotObject) -> P11Result<()>, { let slot_objects = &self.slot_objects.lock().unwrap(); for slot_objects in slot_objects.iter() { for slot_object in slot_objects { callback(slot_object)?; } } Ok(()) } } impl Backend for YubikeyPivBackend { fn name(&self) -> String { "Yubikey PIV".into() } #[instrument] fn find_all_certificates( &self, ) -> P11Result>> { // println!("[find_all_certificates]"); self.init_slot_objects()?; let mut certs: Vec> = vec![]; self.for_each_slot_objects(|slot_object| { certs.push(slot_object.to_certificate()?); Ok(()) })?; Ok(certs) } #[instrument] fn find_private_key( &self, query: P11KeySearchOptions, ) -> P11Result>> { println!("[find_private_key]"); self.init_slot_objects()?; let mut private_key: Option> = None; self.for_each_slot_objects(|slot_object| { if private_key.is_none() { match &query { KeySearchOptions::Label(label) => { if &slot_object.label == label { private_key = Some(Arc::from(slot_object.to_private_key()?)); } } KeySearchOptions::PublicKeyHash(public_key_hash) => { if hex::encode(&slot_object.public_key_hash) == hex::encode(public_key_hash) { private_key = Some(Arc::from(slot_object.to_private_key()?)); } } } } Ok(()) })?; Ok(private_key) } #[instrument] fn find_public_key( &self, query: P11KeySearchOptions, ) -> P11Result>> { println!("[find_public_key]"); self.init_slot_objects()?; let mut public_key: Option> = None; self.for_each_slot_objects(|slot_object| { if public_key.is_none() { match &query { KeySearchOptions::Label(label) => { if &slot_object.label == label { public_key = Some(slot_object.to_public_key()?); } } KeySearchOptions::PublicKeyHash(public_key_hash) => { if hex::encode(&slot_object.public_key_hash) == hex::encode(public_key_hash) { public_key = Some(slot_object.to_public_key()?); } } } } Ok(()) })?; Ok(public_key) } fn find_all_private_keys( &self, ) -> P11Result>> { println!("[find_all_private_keys]"); self.init_slot_objects()?; let mut private_keys: Vec> = vec![]; self.for_each_slot_objects(|slot_object| { private_keys.push(Arc::from(slot_object.to_private_key()?)); Ok(()) })?; Ok(private_keys) } fn find_all_public_keys( &self, ) -> P11Result>> { println!("[find_all_public_keys]"); self.init_slot_objects()?; let mut public_keys: Vec> = vec![]; self.for_each_slot_objects(|slot_object| { public_keys.push(Arc::from(slot_object.to_public_key()?)); Ok(()) })?; Ok(public_keys) } #[instrument] fn generate_key( &self, _algorithm: P11KeyAlgorithm, _label: Option<&str>, ) -> P11Result> { Err("Generate key not supported, please use ykman, URL: https://hatter.in/ykman")? } }