Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resource: add PKCS11 resource back-end #533

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions kbs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ openssl = ["actix-web/openssl", "dep:openssl"]
# Use aliyun KMS as KBS backend
aliyun = ["kms/aliyun"]

# Use pkcs11 resource backend to store secrets in an HSM
pkcs11 = ["cryptoki"]

[dependencies]
actix-web.workspace = true
actix-web-httpauth.workspace = true
Expand All @@ -56,6 +59,7 @@ base64.workspace = true
cfg-if.workspace = true
clap = { workspace = true, features = ["derive", "env"] }
config.workspace = true
cryptoki = { version = "0.7.0", optional = true }
env_logger.workspace = true
jsonwebtoken = { workspace = true, default-features = false, optional = true }
jwt-simple.workspace = true
Expand Down
23 changes: 23 additions & 0 deletions kbs/src/resource/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use anyhow::*;
use serde::Deserialize;
use std::fmt;
use std::fs;
use std::path::Path;
use std::sync::Arc;
Expand All @@ -14,6 +15,9 @@ mod local_fs;
#[cfg(feature = "aliyun")]
mod aliyun_kms;

#[cfg(feature = "pkcs11")]
mod pkcs11;

/// Interface of a `Repository`.
#[async_trait::async_trait]
pub trait Repository {
Expand Down Expand Up @@ -48,13 +52,26 @@ impl ResourceDesc {
}
}

impl fmt::Display for ResourceDesc {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}/{}/{}",
self.repository_name, self.resource_type, self.resource_tag
)
}
}

#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "type")]
pub enum RepositoryConfig {
LocalFs(local_fs::LocalFsRepoDesc),

#[cfg(feature = "aliyun")]
Aliyun(aliyun_kms::AliyunKmsBackendConfig),

#[cfg(feature = "pkcs11")]
Pkcs11(pkcs11::Pkcs11Config),
}

impl RepositoryConfig {
Expand Down Expand Up @@ -83,6 +100,12 @@ impl RepositoryConfig {
let client = aliyun_kms::AliyunKmsBackend::new(config)?;
Ok(Arc::new(RwLock::new(client)) as Arc<RwLock<dyn Repository + Send + Sync>>)
}

#[cfg(feature = "pkcs11")]
Self::Pkcs11(config) => {
let client = pkcs11::Pkcs11Backend::new(config)?;
Ok(Arc::new(RwLock::new(client)) as Arc<RwLock<dyn Repository + Send + Sync>>)
}
}
}
}
Expand Down
106 changes: 106 additions & 0 deletions kbs/src/resource/pkcs11.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) 2024 by IBM.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

use anyhow::{bail, Result};
use cryptoki::context::{CInitializeArgs, Pkcs11};
use cryptoki::object::{Attribute, AttributeInfo, AttributeType, KeyType, ObjectClass};
use cryptoki::session::{Session, UserType};
use cryptoki::types::AuthPin;
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::Mutex;

use super::{Repository, ResourceDesc};

#[derive(Debug, Deserialize, Clone)]
pub struct Pkcs11Config {
/// Path to the Pkcs11 module
module: String,

/// The index of the slot to be used
/// If not provided, the first slot will be used.
slot_index: Option<u8>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want this to be a config item, String or i64 might be better?


/// The user pin for authenticating the session
pin: String,
}

pub struct Pkcs11Backend {
session: Arc<Mutex<Session>>,
}

#[async_trait::async_trait]
impl Repository for Pkcs11Backend {
async fn read_secret_resource(&self, resource_desc: ResourceDesc) -> Result<Vec<u8>> {
let session = self.session.lock().await;

// find object with matching label
let attributes = vec![Attribute::Label(Vec::from(resource_desc.to_string()))];
let objects = session.find_objects(&attributes)?;

if objects.is_empty() {
bail!(
"Could not find object with label {}",
resource_desc.to_string()
);
}
let object = objects[0];

// check that object has a readable value attribute
let value_attribute = vec![AttributeType::Value];
let attribute_map = session.get_attribute_info_map(object, value_attribute.clone())?;
let Some(AttributeInfo::Available(_size)) = attribute_map.get(&AttributeType::Value) else {
bail!("Key does not have value attribute available.");
};

// get the value
let value = &session.get_attributes(object, &value_attribute)?[0];
let Attribute::Value(resource_bytes) = value else {
bail!("Failed to get value.");
};

Ok(resource_bytes.clone())
}

async fn write_secret_resource(
&mut self,
resource_desc: ResourceDesc,
data: &[u8],
) -> Result<()> {
let mut attributes = vec![];
attributes.push(Attribute::Class(ObjectClass::SECRET_KEY));
attributes.push(Attribute::KeyType(KeyType::GENERIC_SECRET));
attributes.push(Attribute::Extractable(true));
attributes.push(Attribute::Private(true));

attributes.push(Attribute::Value(data.to_vec()));
attributes.push(Attribute::Label(Vec::from(resource_desc.to_string())));

let _object = self.session.lock().await.create_object(&attributes)?;

Ok(())
}
}

impl Pkcs11Backend {
pub fn new(config: &Pkcs11Config) -> Result<Self> {
// setup global context
let pkcs11 = Pkcs11::new(config.module.clone())?;
pkcs11.initialize(CInitializeArgs::OsThreads).unwrap();

// create session
let slots = pkcs11.get_slots_with_token()?;
let slot_index = usize::from(config.slot_index.unwrap_or(0));
if slot_index >= slots.len() {
bail!("Slot index out of range");
}

let session = pkcs11.open_rw_session(slots[slot_index])?;
session.login(UserType::User, Some(&AuthPin::new(config.pin.clone())))?;

Ok(Self {
session: Arc::new(Mutex::new(session)),
})
}
}
Loading