use std::path::PathBuf;
use anyhow::Context;
use cryptoki::{
context::{CInitializeArgs, CInitializeFlags, Pkcs11},
object::{Attribute, AttributeType, ObjectClass},
types::AuthPin,
};
use openssl::{nid::Nid, x509};
use sqlx::SqliteConnection;
use crate::{protocol::KeyAlgorithm, server::db};
pub async fn import_pkcs11_token(
conn: &mut SqliteConnection,
module: PathBuf,
slot: Option<u64>,
token_user_pin: AuthPin,
) -> anyhow::Result<db::Pkcs11Token> {
let pkcs11 = Pkcs11::new(&module).context("Failed to load the PKCS#11 module specified.")?;
pkcs11
.initialize(CInitializeArgs::new(CInitializeFlags::OS_LOCKING_OK))
.context("Failed to initialized the PKCS#11 module")?;
let result = import_pkcs11_token_private(&pkcs11, conn, module, slot, token_user_pin).await;
pkcs11.finalize()?;
result
}
async fn import_pkcs11_token_private(
pkcs11: &Pkcs11,
conn: &mut SqliteConnection,
module: PathBuf,
slot_id: Option<u64>,
token_user_pin: AuthPin,
) -> anyhow::Result<db::Pkcs11Token> {
let slot = if let Some(slot_id) = slot_id {
pkcs11
.get_slots_with_token()?
.into_iter()
.find(|s| s.id() == slot_id)
.ok_or_else(|| {
anyhow::anyhow!("The provided token didn't have a slot with id {}", slot_id)
})?
} else {
pkcs11
.get_slots_with_token()?
.pop()
.ok_or_else(|| anyhow::anyhow!("The provided token didn't have any slots"))?
};
let token_info = pkcs11
.get_token_info(slot)
.context("Unable to read token information")?;
let session = pkcs11
.open_ro_session(slot)
.context("Unable to open a read-only session with the token")?;
session
.login(cryptoki::session::UserType::User, Some(&token_user_pin))
.context("Failed to login to the token with the provided user PIN")?;
let manufacturer_id = if token_info.manufacturer_id().is_empty() {
None
} else {
Some(token_info.manufacturer_id().to_string())
};
let model = if token_info.model().is_empty() {
None
} else {
Some(token_info.model().to_string())
};
let label = if token_info.label().is_empty() {
return Err(anyhow::anyhow!("PKCS #11 token needs to have a label"));
} else {
token_info.label().to_string()
};
let serial_number = if token_info.serial_number().is_empty() {
return Err(anyhow::anyhow!(
"PKCS #11 token needs to have a serial_number"
));
} else {
token_info.serial_number().to_string()
};
let token = db::Pkcs11Token::create(
conn,
module,
label,
manufacturer_id,
model,
serial_number,
None,
)
.await?;
struct TokenKey {
label: String,
key_type: cryptoki::object::KeyType,
public_key_der: Option<Vec<u8>>,
x509_certificate_pem: Option<(String, String)>,
}
let mut token_keys: std::collections::HashMap<Vec<u8>, TokenKey> =
std::collections::HashMap::new();
let private_key_attributes = [
AttributeType::Id,
AttributeType::Label,
AttributeType::KeyType,
AttributeType::Class,
];
for object in session
.iter_objects(&[Attribute::Class(ObjectClass::PRIVATE_KEY)])
.context("Failed to search private key objects")?
{
let object = object?;
let attributes = session
.get_attributes(object, &private_key_attributes)
.context("Failed to query private key attributes")?;
let mut key_id = None;
let mut label = None;
let mut key_type = None;
for attr in attributes {
match attr {
Attribute::Id(id) => key_id = Some(id),
Attribute::Label(l) => {
label = String::from_utf8(l).ok();
}
Attribute::KeyType(kt) => key_type = Some(kt),
_ => {}
}
}
if let (Some(id), Some(label), Some(key_type)) = (key_id, label, key_type) {
token_keys.insert(
id,
TokenKey {
label,
key_type,
public_key_der: None,
x509_certificate_pem: None,
},
);
}
}
let public_key_attributes = [AttributeType::Id, AttributeType::PublicKeyInfo];
for object in session
.iter_objects(&[Attribute::Class(ObjectClass::PUBLIC_KEY)])
.context("Failed to search public key objects")?
{
let object = object?;
let attributes = session
.get_attributes(object, &public_key_attributes)
.context("Failed to query public key attributes")?;
let mut key_id = None;
let mut public_key_info = None;
for attr in attributes {
match attr {
Attribute::Id(id) => key_id = Some(id),
Attribute::PublicKeyInfo(der) => public_key_info = Some(der),
_ => {}
}
}
if let (Some(id), Some(der)) = (key_id, public_key_info)
&& let Some(entry) = token_keys.get_mut(&id)
{
entry.public_key_der = Some(der);
}
}
let certificate_attributes = [AttributeType::Id, AttributeType::Value];
for object in session
.iter_objects(&[Attribute::Class(ObjectClass::CERTIFICATE)])
.context("Failed to search certificate objects")?
{
let object = object?;
let attributes = session
.get_attributes(object, &certificate_attributes)
.context("Failed to query certificate attributes")?;
let mut key_id = None;
let mut cert_der = None;
for attr in attributes {
match attr {
Attribute::Id(id) => key_id = Some(id),
Attribute::Value(der) => cert_der = Some(der),
_ => {}
}
}
if let (Some(id), Some(der)) = (key_id, cert_der)
&& let Some(entry) = token_keys.get_mut(&id)
&& let Ok(cert) = x509::X509::from_der(&der)
{
let serial_number = cert
.serial_number()
.to_bn()
.and_then(|bn| bn.to_hex_str())
.map(|s| s.to_uppercase())
.ok();
let pem = cert
.to_pem()
.ok()
.and_then(|pem_bytes| String::from_utf8(pem_bytes).ok());
if let (Some(sn), Some(pem)) = (serial_number, pem) {
entry.x509_certificate_pem = Some((sn, pem));
}
}
}
for (key_id, key_info) in &token_keys {
if let Some(public_key) = key_info
.public_key_der
.as_deref()
.map(openssl::pkey::PKey::public_key_from_der)
{
let public_key = public_key?;
let key_algorithm = match key_info.key_type {
cryptoki::object::KeyType::EC => {
let ecc_key = public_key.ec_key()?;
if ecc_key.group().curve_name() == Some(Nid::X9_62_PRIME256V1) {
KeyAlgorithm::P256
} else {
tracing::warn!(
label = key_info.label,
"Found unsupported ECC key; skipping"
);
continue;
}
}
cryptoki::object::KeyType::RSA => {
let _ = public_key.rsa()?;
match public_key.bits() {
4096 => KeyAlgorithm::Rsa4K,
2048 => KeyAlgorithm::Rsa2K,
other => {
tracing::warn!(
label = key_info.label,
"Found unsupported RSA key of size {}",
other
);
continue;
}
}
}
unsupported => {
tracing::warn!(
label = key_info.label,
"Found unsupported key type {:?}",
unsupported
);
continue;
}
};
let pubkey_pem = String::from_utf8(public_key.public_key_to_pem()?)?;
let handle = hex::encode_upper(openssl::hash::hash(
openssl::hash::MessageDigest::sha256(),
&public_key.public_key_to_der()?,
)?);
let key = db::Key::create(
conn,
&key_info.label,
&handle,
key_algorithm,
None,
&pubkey_pem,
Some(&token),
Some(key_id.clone()),
)
.await?;
if let Some((serial_number, pem_cert)) = key_info.x509_certificate_pem.clone() {
db::PublicKeyMaterial::create(
conn,
&key,
serial_number,
db::PublicKeyMaterialType::X509,
pem_cert,
)
.await?;
}
}
}
Ok(token)
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use super::*;
use crate::server::crypto::test_utils::setup_hsm;
#[tokio::test]
async fn import_pkcs11_keys() -> Result<()> {
let hsm = setup_hsm()?;
let db_pool = db::pool("sqlite::memory:", false).await?;
db::migrate(&db_pool).await?;
let mut conn = db_pool.begin().await?;
super::import_pkcs11_token(
&mut conn,
PathBuf::from("/usr/lib64/pkcs11/libkryoptic_pkcs11.so"),
None,
hsm.user_pin.clone(),
)
.await?;
let keys = db::Key::list(&mut conn).await?;
assert_eq!(keys.len(), 2);
for key in keys {
if key.key_algorithm == KeyAlgorithm::Rsa4K {
let certs =
db::PublicKeyMaterial::list(&mut conn, &key, db::PublicKeyMaterialType::X509)
.await?;
assert_eq!(certs.len(), 1);
} else {
let certs =
db::PublicKeyMaterial::list(&mut conn, &key, db::PublicKeyMaterialType::X509)
.await?;
assert_eq!(certs.len(), 0);
}
}
Ok(())
}
}