Skip to content

Commit

Permalink
Add device.cryptoki.enable,module_path,pin,serial config options
Browse files Browse the repository at this point in the history
Signed-off-by: Marcel Guzik <marcel.guzik@cumulocity.com>
  • Loading branch information
Bravo555 committed Feb 10, 2025
1 parent d4d0a01 commit 134cd6a
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 34 deletions.
14 changes: 11 additions & 3 deletions crates/common/certificate/src/parse_root_certificate.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use camino::Utf8PathBuf;
use rustls::pki_types::pem::PemObject as _;
use rustls::pki_types::CertificateDer;
use rustls::pki_types::PrivateKeyDer;
Expand Down Expand Up @@ -30,13 +31,14 @@ pub fn create_tls_config(
.with_client_auth_cert(cert_chain, pvt_key)?)
}

pub fn create_tls_config_piv(
pub fn create_tls_config_cryptoki(
root_certificates: impl AsRef<Path>,
piv_serial: Arc<str>,
cryptoki_config: CryptokiConfig,
) -> Result<ClientConfig, CertificateError> {
let root_cert_store = new_root_store(root_certificates.as_ref())?;

let resolver = Pkcs11Resolver::from_piv_serial(&piv_serial).expect("failed to create resolver");
let resolver =
Pkcs11Resolver::from_cryptoki_config(cryptoki_config).expect("failed to create resolver");

let config = ClientConfig::builder()
.with_root_certificates(root_cert_store)
Expand All @@ -45,6 +47,12 @@ pub fn create_tls_config_piv(
Ok(config)
}

pub struct CryptokiConfig {
pub module_path: Utf8PathBuf,
pub pin: Arc<str>,
pub serial: Option<Arc<str>>,
}

pub fn client_config_for_ca_certificates<P>(
root_certificates: impl IntoIterator<Item = P>,
) -> Result<ClientConfig, std::io::Error>
Expand Down
30 changes: 21 additions & 9 deletions crates/common/certificate/src/parse_root_certificate/pkcs11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use asn1_rs::ToDer;
use base64::Engine;

use rustls::ClientConfig;
use rustls::{crypto, ClientConfig};

// Only used when loading certs from file
use rustls::pki_types::pem::PemObject;
Expand All @@ -25,10 +25,13 @@ use rustls::{
sign::{CertifiedKey, Signer, SigningKey},
Error as RusTLSError, SignatureAlgorithm, SignatureScheme,
};
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use tracing::{debug, error, info, warn};
use tracing::{debug, error, info, trace, warn};
use x509_parser::prelude::{FromDer, X509Certificate};

use super::CryptokiConfig;

const PCKS11_MODULE_PATH: &str = "/usr/lib/x86_64-linux-gnu/libykcs11.so";
const PKCS11_PIN: &str = "123456";
const PKCS11_TOKENLABEL: &str = "YubiKey PIV #30590875";
Expand Down Expand Up @@ -162,7 +165,7 @@ impl Signer for PkcsSigner {
}

fn scheme(&self) -> SignatureScheme {
info!("Using Signature scheme: {:?}", self.scheme.as_str());
debug!("Using Signature scheme: {:?}", self.scheme.as_str());
self.scheme
}
}
Expand Down Expand Up @@ -271,16 +274,25 @@ fn get_certificate_der(
}

impl Pkcs11Resolver {
pub fn from_piv_serial(piv_serial: &str) -> anyhow::Result<Arc<Self>> {
pub fn from_cryptoki_config(cryptoki_config: CryptokiConfig) -> anyhow::Result<Arc<Self>> {
let CryptokiConfig {
module_path,
pin,
serial,
} = cryptoki_config;

// Alternative module: /opt/homebrew/lib/pkcs11/opensc-pkcs11.so
let pkcs11module = std::env::var("PKCS11_MODULE");
let pkcs11module = pkcs11module.as_deref().unwrap_or(PCKS11_MODULE_PATH);
let pkcs11client = Pkcs11::new(pkcs11module)?;
// let pkcs11module = std::env::var("PKCS11_MODULE");
// let pkcs11module = pkcs11module.as_deref().unwrap_or(PCKS11_MODULE_PATH);

debug!(%module_path, "Loading PKCS#11 module");
let pkcs11client = Pkcs11::new(module_path)?;
pkcs11client.initialize(CInitializeArgs::OsThreads)?;

debug!(%pin, "Attempting to login to PKCS#11 module");
let slot = pkcs11client.get_slots_with_token()?.remove(0);
let session = pkcs11client.open_ro_session(slot)?;
session.login(UserType::User, Some(&AuthPin::new("123456".into())))?;
session.login(UserType::User, Some(&AuthPin::new(pin.deref().into())))?;

// Debug
let search = vec![
Expand All @@ -306,7 +318,7 @@ impl Pkcs11Resolver {
// let my_signing_key = Arc::new(MySigningKey { pkcs11 });

let key_type = get_key_type(pkcs11.clone());
info!("Key Type: {:?}", key_type.to_string());
debug!("Key Type: {:?}", key_type.to_string());
let resolver = match key_type {
KeyType::EC => Arc::new(Pkcs11Resolver {
chain,
Expand Down
89 changes: 89 additions & 0 deletions crates/common/tedge_config/src/tedge_config_cli/cryptoki_opts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::sync::Arc;

use anyhow::bail;
use camino::Utf8PathBuf;
use certificate::parse_root_certificate::CryptokiConfig;

use crate::{TEdgeConfig, TEdgeConfigReaderDeviceCryptoki};

pub enum CryptokiOpts {
Disabled,

Enabled {
module_path: Utf8PathBuf,
pin: Arc<str>,
serial: Option<Arc<str>>,
},
}

impl TEdgeConfigReaderDeviceCryptoki {
pub fn opts(&self) -> Result<CryptokiOpts, anyhow::Error> {
if !self.enable {
return Ok(CryptokiOpts::Disabled);
}

Ok(CryptokiOpts::Enabled {
module_path: self.module_path.or_config_not_set()?.clone(),
pin: self.pin.clone(),
serial: self.serial.or_none().cloned(),
})
}

pub fn config(&self) -> Result<CryptokiConfig, anyhow::Error> {
if !self.enable {
bail!("disabled");
}

Ok(CryptokiConfig {
module_path: self.module_path.or_config_not_set().unwrap().clone(),
pin: self.pin.clone(),
serial: self.serial.or_none().cloned(),
})
}
}

impl TryFrom<CryptokiOpts> for CryptokiConfig {
type Error = anyhow::Error;

fn try_from(value: CryptokiOpts) -> Result<Self, Self::Error> {
let CryptokiOpts::Enabled {
module_path,
pin,
serial,
} = value
else {
// TODO(marcel): eliminate error cases!
anyhow::bail!("disabled");
};

Ok(CryptokiConfig {
module_path,
pin,
serial,
})
}
}

// impl TryFrom<&TEdgeConfigReaderDeviceCryptoki> for CryptokiOpts {
// type Error = anyhow::Error;

// fn try_from(reader: &TEdgeConfigReaderDeviceCryptoki) -> Result<Self, Self::Error> {
// if !reader.enable {
// return Ok(Self::Disabled);
// }

// Ok(Self::Enabled {
// module_path: reader.module_path.or_config_not_set()?.clone(),
// pin: reader.pin.clone(),
// serial: reader.serial.or_config_not_set()?.clone(),
// })
// }
// }

// impl TryFrom<&TEdgeConfig> for CryptokiOpts {
// type Error = anyhow::Error;

// fn try_from(tedge_config: &TEdgeConfig) -> Result<Self, Self::Error> {
// (&tedge_config.device.cryptoki).try_into()
// }
// }
2 changes: 2 additions & 0 deletions crates/common/tedge_config/src/tedge_config_cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ pub mod tedge_config_location;
mod figment;
pub mod models;
pub mod tedge_config;

pub mod cryptoki_opts;
27 changes: 23 additions & 4 deletions crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,29 @@ define_tedge_config! {
#[doku(as = "PathBuf")]
csr_path: Utf8PathBuf,

/// Use a certificate/key from a Personal Identity Verification (PIV) device of a given serial number. Overrides
/// `key_path`/`cert_path`/`csr_path` options.
#[tedge_config(example = "123456789")]
use_piv_serial: Arc<str>,
cryptoki: {
/// Use a Hardware Security Module for authenticating the MQTT connection with the cloud.
///
/// When set to true, `key_path` option is ignored as PKCS#11 module is used for signing.
#[tedge_config(default(value = false))]
enable: bool,

/// A path to the PKCS#11 module used for interaction with the HSM.
#[tedge_config(example = "/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so")]
#[doku(as = "PathBuf")]
module_path: Utf8PathBuf,

/// Use a certificate/key from a Personal Identity Verification (PIV) device of a given serial number. Overrides
/// `key_path`/`cert_path`/`csr_path` options.
#[tedge_config(example = "123456", default(value = "123456"))]
pin: Arc<str>,

/// A serial number of a Personal Identity Verification (PIV) device to be used.
///
/// Necessary if two or more modules are connected.
#[tedge_config(example = "123456789")]
serial: Arc<str>,
},

/// The default device type
#[tedge_config(example = "thin-edge.io", default(value = "thin-edge.io"))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ impl_append_remove_for_single_value!(
IpAddr,
u16,
Arc<str>,
Arc<Utf8Path>,
AutoFlag,
TopicPrefix,
SoftwareManagementApiFlag,
Expand Down
9 changes: 5 additions & 4 deletions crates/core/tedge/src/cli/connect/c8y_direct_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use anyhow::bail;
use anyhow::Context as _;
use certificate::parse_root_certificate::create_tls_config;
use certificate::parse_root_certificate::create_tls_config_without_client_cert;
use certificate::parse_root_certificate::CryptokiConfig;
use rumqttc::tokio_rustls::rustls::AlertDescription;
use rumqttc::tokio_rustls::rustls::CertificateError;
use rumqttc::tokio_rustls::rustls::Error;
Expand All @@ -29,7 +30,7 @@ pub fn create_device_with_direct_connection(
bridge_config: &BridgeConfig,
device_type: &str,
// TODO: put into general authentication struct
use_piv_serial: Option<Arc<str>>,
cryptoki_config: Option<CryptokiConfig>,
) -> anyhow::Result<()> {
const DEVICE_ALREADY_EXISTS: &[u8] = b"41,100,Device already existing";
const DEVICE_CREATE_ERROR_TOPIC: &str = "s/e";
Expand All @@ -55,10 +56,10 @@ pub fn create_device_with_direct_connection(
.expect("password must be set to use basic auth"),
);
create_tls_config_without_client_cert(&bridge_config.bridge_root_cert_path)?
} else if let Some(use_piv_serial) = use_piv_serial {
certificate::parse_root_certificate::create_tls_config_piv(
} else if let Some(cryptoki_config) = cryptoki_config {
certificate::parse_root_certificate::create_tls_config_cryptoki(
&bridge_config.bridge_root_cert_path,
use_piv_serial,
cryptoki_config,
)?
} else {
create_tls_config(
Expand Down
11 changes: 6 additions & 5 deletions crates/core/tedge/src/cli/connect/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use tedge_api::mqtt_topics::TopicIdError;
use tedge_api::service_health_topic;
use tedge_config::auth_method::AuthType;
use tedge_config::system_services::*;
use tedge_config::tedge_config_cli::cryptoki_opts::CryptokiOpts;
use tedge_config::TEdgeConfig;
use tedge_config::*;
use tedge_utils::paths::create_directories;
Expand Down Expand Up @@ -584,10 +585,10 @@ fn check_device_status_c8y(

mqtt_options.set_keep_alive(RESPONSE_TIMEOUT);

if let Ok(piv_serial) = tedge_config.device.use_piv_serial.or_config_not_set() {
let tls_config = certificate::parse_root_certificate::create_tls_config_piv(
if let Ok(cryptoki_config) = tedge_config.device.cryptoki.config() {
let tls_config = certificate::parse_root_certificate::create_tls_config_cryptoki(
&c8y_config.root_cert_path,
piv_serial.clone(),
cryptoki_config,
)?;
mqtt_options.set_transport(rumqttc::Transport::tls_with_config(tls_config.into()));
}
Expand Down Expand Up @@ -927,7 +928,7 @@ fn new_bridge(
bridge_config.validate(use_basic_auth)?;

// TODO: put in general auth config struct
let use_piv_serial = tedge_config.device.use_piv_serial.or_none().cloned();
let cryptoki_config = tedge_config.device.cryptoki.config().ok();

if bridge_config.cloud_name.eq("c8y") {
if offline_mode {
Expand All @@ -938,7 +939,7 @@ fn new_bridge(
use_basic_auth,
bridge_config,
device_type,
use_piv_serial,
cryptoki_config,
);
spinner.finish(res)?;
}
Expand Down
7 changes: 4 additions & 3 deletions crates/core/tedge_mapper/src/c8y/mapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@ use mqtt_channel::Config;
use std::borrow::Cow;
use tedge_api::entity::EntityExternalId;
use tedge_api::mqtt_topics::EntityTopicId;
use tedge_config::tedge_config_cli::cryptoki_opts::CryptokiOpts;
use tedge_config::ProfileName;
use tedge_config::TEdgeConfig;
use tedge_downloader_ext::DownloaderActor;
use tedge_file_system_ext::FsWatchActorBuilder;
use tedge_http_ext::HttpActor;
use tedge_mqtt_bridge::rumqttc::LastWill;
use tedge_mqtt_bridge::use_credentials;
use tedge_mqtt_bridge::use_cryptoki;
use tedge_mqtt_bridge::use_key_and_cert;
use tedge_mqtt_bridge::use_piv;
use tedge_mqtt_bridge::BridgeConfig;
use tedge_mqtt_bridge::MqttBridgeActorBuilder;
use tedge_mqtt_bridge::QoS;
Expand Down Expand Up @@ -154,8 +155,8 @@ impl TEdgeComponent for CumulocityMapper {
cloud_config.set_clean_session(true);

if use_certificate {
if let Some(piv_serial) = tedge_config.device.use_piv_serial.or_none() {
use_piv(&mut cloud_config, c8y_config)?;
if let Ok(cryptoki_config) = tedge_config.device.cryptoki.config() {
use_cryptoki(&mut cloud_config, c8y_config, cryptoki_config)?;
} else {
use_key_and_cert(&mut cloud_config, c8y_config)?;
}
Expand Down
12 changes: 9 additions & 3 deletions crates/extensions/tedge_mqtt_bridge/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use crate::topics::matches_ignore_dollar_prefix;
use crate::topics::TopicConverter;
use certificate::parse_root_certificate::create_tls_config;
use certificate::parse_root_certificate::create_tls_config_piv;
use certificate::parse_root_certificate::create_tls_config_cryptoki;
use certificate::parse_root_certificate::create_tls_config_without_client_cert;
use certificate::parse_root_certificate::CryptokiConfig;
use rumqttc::valid_filter;
use rumqttc::valid_topic;
use rumqttc::MqttOptions;
use rumqttc::Transport;
use std::borrow::Cow;
use std::path::Path;
use std::sync::Arc;
use tedge_config::CloudConfig;

pub fn use_key_and_cert(
Expand All @@ -24,8 +26,12 @@ pub fn use_key_and_cert(
Ok(())
}

pub fn use_piv(config: &mut MqttOptions, cloud_config: &dyn CloudConfig) -> anyhow::Result<()> {
let tls_config = create_tls_config_piv(cloud_config.root_cert_path(), "1234".into())?;
pub fn use_cryptoki(
config: &mut MqttOptions,
cloud_config: &dyn CloudConfig,
cryptoki_config: CryptokiConfig,
) -> anyhow::Result<()> {
let tls_config = create_tls_config_cryptoki(cloud_config.root_cert_path(), cryptoki_config)?;
config.set_transport(Transport::tls_with_config(tls_config.into()));
Ok(())
}
Expand Down
Loading

0 comments on commit 134cd6a

Please sign in to comment.