#![cfg(feature = "scard")]
use std::borrow::Cow;
use std::fmt;
#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;
#[cfg(not(target_arch = "wasm32"))]
use cryptoki::context::{CInitializeArgs, Pkcs11};
#[cfg(not(target_arch = "wasm32"))]
use cryptoki::mechanism::Mechanism;
#[cfg(not(target_arch = "wasm32"))]
use cryptoki::object::{Attribute, KeyType, ObjectClass};
#[cfg(not(target_arch = "wasm32"))]
use cryptoki::session::UserType;
#[cfg(not(target_arch = "wasm32"))]
use cryptoki::types::AuthPin;
use picky::key::PrivateKey;
use winscard::SmartCard as PivSmartCard;
use crate::{Error, ErrorKind, Result, Secret, SmartCardIdentity, SmartCardType};
pub(crate) enum SmartCardApi {
PivEmulated(Box<PivSmartCard<'static>>),
#[cfg(not(target_arch = "wasm32"))]
Pkcs11 {
pkcs11_module: Pkcs11,
reader_name: String,
},
#[cfg(target_os = "windows")]
Windows {
container_name: String,
},
}
impl fmt::Debug for SmartCardApi {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PivEmulated { .. } => f.write_str("SmartCardApi::PivEmulated"),
#[cfg(not(target_arch = "wasm32"))]
Self::Pkcs11 { .. } => f.write_str("SmartCardApi::Pkcs11"),
#[cfg(target_os = "windows")]
Self::Windows { .. } => f.write_str("SmartCardApi::Windows"),
}
}
}
#[derive(Debug)]
pub(crate) struct SmartCard {
smart_card_type: SmartCardApi,
pin: Secret<Vec<u8>>,
}
impl SmartCard {
pub(crate) fn from_credentials(credentials: &SmartCardIdentity) -> Result<Self> {
let SmartCardIdentity {
username: _,
certificate,
reader_name,
card_name: _,
container_name: _container_name,
csp_name: _,
pin: user_pin,
private_key,
scard_type,
} = credentials;
let user_pin = user_pin.clone();
match scard_type {
SmartCardType::Emulated { scard_pin } => {
let private_key = private_key
.as_ref()
.ok_or(Error::new(
ErrorKind::IncompleteCredentials,
"emulated smart card private key is missing",
))?
.as_ref()
.clone();
Self::new_emulated(
Cow::Owned(reader_name.clone()),
scard_pin.as_ref().to_vec(),
user_pin,
private_key,
picky_asn1_der::to_vec(certificate)?,
)
}
#[cfg(not(target_arch = "wasm32"))]
SmartCardType::SystemProvided { pkcs11_module_path } => {
Self::new_system_provided(pkcs11_module_path, user_pin, reader_name.clone())
}
#[cfg(target_os = "windows")]
SmartCardType::WindowsNative => Self::new_windows_native(
user_pin,
_container_name
.as_ref()
.ok_or_else(|| Error::new(ErrorKind::NoCredentials, "container name is not provided"))?
.to_owned(),
),
}
}
fn new_emulated(
reader_name: Cow<'static, str>,
scard_pin: Vec<u8>,
user_pin: Secret<Vec<u8>>,
private_key: PrivateKey,
auth_cert_der: Vec<u8>,
) -> Result<Self> {
let scard = PivSmartCard::new(reader_name, scard_pin, auth_cert_der, private_key)?;
Ok(Self {
smart_card_type: SmartCardApi::PivEmulated(Box::new(scard)),
pin: user_pin,
})
}
#[cfg(target_os = "windows")]
fn new_windows_native(user_pin: Secret<Vec<u8>>, container_name: String) -> Result<Self> {
Ok(Self {
smart_card_type: SmartCardApi::Windows { container_name },
pin: user_pin,
})
}
#[cfg(not(target_arch = "wasm32"))]
fn new_system_provided(pkcs11_module_path: &Path, user_pin: Secret<Vec<u8>>, reader_name: String) -> Result<Self> {
let pkcs11 = Pkcs11::new(pkcs11_module_path)?;
pkcs11.initialize(CInitializeArgs::OsThreads)?;
Ok(Self {
smart_card_type: SmartCardApi::Pkcs11 {
pkcs11_module: pkcs11,
reader_name,
},
pin: user_pin,
})
}
pub(crate) fn sign(&mut self, digest: Vec<u8>) -> Result<Vec<u8>> {
match &mut self.smart_card_type {
SmartCardApi::PivEmulated(scard) => {
scard.verify_pin(self.pin.as_ref())?;
Ok(scard.sign_hashed(&encode_digest(digest)?)?)
}
#[cfg(not(target_arch = "wasm32"))]
SmartCardApi::Pkcs11 {
pkcs11_module,
reader_name,
} => {
let slot = 's: {
for slot in pkcs11_module.get_slots_with_token()? {
let slot_info = pkcs11_module.get_slot_info(slot)?;
if slot_info.slot_description() == reader_name {
break 's slot;
}
}
return Err(Error::new(
ErrorKind::NoCredentials,
format!("provided reader name ({reader_name}) does not match any smart card slots"),
));
};
let session = pkcs11_module.open_ro_session(slot)?;
let pin = String::from_utf8(self.pin.as_ref().to_vec())?;
let pin = AuthPin::new(pin);
session.login(UserType::User, Some(&pin))?;
let objects = session.find_objects(&[
Attribute::Class(ObjectClass::PRIVATE_KEY),
Attribute::KeyType(KeyType::RSA),
])?;
let data_to_sign = encode_digest(digest)?;
for private_key in objects {
if let Ok(signature) = session.sign(&Mechanism::RsaPkcs, private_key, &data_to_sign) {
return Ok(signature);
}
}
Err(Error::new(
ErrorKind::NoCredentials,
format!(
"the selected PKCS11 slot ({reader_name}) does not have a suitable private key for data signing"
),
))
}
#[cfg(target_os = "windows")]
SmartCardApi::Windows { container_name } => sign_data_win_api(container_name, self.pin.as_ref(), &digest),
}
}
}
fn encode_digest(digest: Vec<u8>) -> Result<Vec<u8>> {
use picky_asn1::wrapper::OctetStringAsn1;
use picky_asn1_x509::{AlgorithmIdentifier, DigestInfo};
let digest_info = DigestInfo {
oid: AlgorithmIdentifier::new_sha1(),
digest: OctetStringAsn1::from(digest),
};
Ok(picky_asn1_der::to_vec(&digest_info)?)
}
#[cfg(target_os = "windows")]
fn sign_data_win_api(container_name: &str, pin: &[u8], data_to_sign: &[u8]) -> Result<Vec<u8>> {
use std::ptr;
use windows::Win32::Security::Cryptography::{
BCRYPT_PKCS1_PADDING_INFO, BCRYPT_SHA1_ALGORITHM, CERT_KEY_SPEC, MS_SMART_CARD_KEY_STORAGE_PROVIDER,
NCRYPT_FLAGS, NCRYPT_PAD_PKCS1_FLAG, NCRYPT_PIN_PROPERTY, NCRYPT_SILENT_FLAG, NCryptOpenKey,
NCryptOpenStorageProvider, NCryptSetProperty, NCryptSignHash,
};
use windows::core::{Owned, PCWSTR};
use crate::utils::{str_to_w_buff, string_to_utf16};
let mut provider = Owned::default();
unsafe { NCryptOpenStorageProvider(&mut *provider, MS_SMART_CARD_KEY_STORAGE_PROVIDER, 0) }.map_err(|err| {
Error::new(
ErrorKind::InternalError,
format!(
"failed to open smart card CNG key storage provider: {} ({:x})",
err.message(),
err.code().0
),
)
})?;
let container_name = str_to_w_buff(container_name);
let container_name = PCWSTR::from_raw(container_name.as_ptr());
let mut key = Owned::default();
unsafe {
NCryptOpenKey(
*provider,
&mut *key,
container_name,
CERT_KEY_SPEC(0),
NCRYPT_SILENT_FLAG,
)
}
.map_err(|err| {
Error::new(
ErrorKind::InternalError,
format!("failed to open smart card key: {} ({:x})", err.message(), err.code().0),
)
})?;
let mut pin = string_to_utf16(std::str::from_utf8(pin)?);
pin.extend_from_slice(&[0, 0]);
if let Err(err) = unsafe { NCryptSetProperty((*key).into(), NCRYPT_PIN_PROPERTY, pin.as_ref(), NCRYPT_FLAGS(0)) } {
warn!(
"Failed to set smart card PIN code: {} ({:x}) - this may cause issues with signing data.",
err.message(),
err.code().0
);
}
let mut signature_len = 0;
let padding_info = BCRYPT_PKCS1_PADDING_INFO {
pszAlgId: BCRYPT_SHA1_ALGORITHM,
};
unsafe {
NCryptSignHash(
*key,
Some(ptr::from_ref(&padding_info).cast()),
data_to_sign,
None,
&mut signature_len,
NCRYPT_PAD_PKCS1_FLAG,
)
}
.map_err(|err| {
Error::new(
ErrorKind::InternalError,
format!("failed to get signature length: {} ({:x})", err.message(), err.code().0),
)
})?;
let mut signature = vec![0_u8; usize::try_from(signature_len)?];
unsafe {
NCryptSignHash(
*key,
Some(ptr::from_ref(&padding_info).cast()),
data_to_sign,
Some(&mut signature),
&mut signature_len,
NCRYPT_PAD_PKCS1_FLAG,
)
}
.map_err(|err| {
Error::new(
ErrorKind::InternalError,
format!("failed to sign data: {} ({:x})", err.message(), err.code().0),
)
})?;
Ok(signature)
}