mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
20230629 tpm keygen ... again (#1793)
This commit is contained in:
parent
3e4c8f6241
commit
0425122ba3
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2571,6 +2571,7 @@ dependencies = [
|
|||
name = "kanidm_unix_int"
|
||||
version = "1.1.0-beta.13-dev"
|
||||
dependencies = [
|
||||
"base64urlsafedata",
|
||||
"bytes",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
|
|
|
@ -64,6 +64,7 @@ const ARGON2_MAX_P_COST: u32 = 1;
|
|||
#[derive(Clone, Debug)]
|
||||
pub enum CryptoError {
|
||||
Tpm2,
|
||||
Tpm2PublicBuilder,
|
||||
Tpm2FeatureMissing,
|
||||
Tpm2InputExceeded,
|
||||
Tpm2ContextMissing,
|
||||
|
@ -1167,67 +1168,6 @@ impl Password {
|
|||
| Kdf::NT_MD4(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tpm")]
|
||||
pub fn prepare_tpm_key(tpm_ctx: &mut TpmContext) -> Result<TpmHandle, CryptoError> {
|
||||
use tss_esapi::{
|
||||
attributes::ObjectAttributesBuilder,
|
||||
interface_types::{
|
||||
algorithm::{HashingAlgorithm, PublicAlgorithm},
|
||||
resource_handles::Hierarchy,
|
||||
},
|
||||
structures::{Digest, KeyedHashScheme, PublicBuilder, PublicKeyedHashParameters},
|
||||
};
|
||||
|
||||
// We generate a digest, which is really some unique small amount of data that
|
||||
// we save into the key context that we are going to save/load. This allows us
|
||||
// to have unique hmac keys compared to other users of the same tpm.
|
||||
|
||||
let digest = tpm_ctx
|
||||
.get_random(16)
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||
CryptoError::Tpm2
|
||||
})
|
||||
.and_then(|rand| {
|
||||
Digest::try_from(rand).map_err(|e| {
|
||||
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||
CryptoError::Tpm2
|
||||
})
|
||||
})?;
|
||||
|
||||
let object_attributes = ObjectAttributesBuilder::new()
|
||||
.with_sign_encrypt(true)
|
||||
.with_sensitive_data_origin(true)
|
||||
.with_user_with_auth(true)
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||
CryptoError::Tpm2
|
||||
})?;
|
||||
|
||||
let key_pub = PublicBuilder::new()
|
||||
.with_public_algorithm(PublicAlgorithm::KeyedHash)
|
||||
.with_name_hashing_algorithm(HashingAlgorithm::Sha256)
|
||||
.with_object_attributes(object_attributes)
|
||||
.with_keyed_hash_parameters(PublicKeyedHashParameters::new(
|
||||
KeyedHashScheme::HMAC_SHA_256,
|
||||
))
|
||||
.with_keyed_hash_unique_identifier(digest)
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||
CryptoError::Tpm2
|
||||
})?;
|
||||
|
||||
tpm_ctx
|
||||
.create_primary(Hierarchy::Owner, key_pub, None, None, None, None)
|
||||
.map(|key| key.key_handle.into())
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||
CryptoError::Tpm2
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tpm")]
|
||||
|
@ -1447,7 +1387,7 @@ mod tests {
|
|||
.expect("Failed to create Context");
|
||||
|
||||
let key = context
|
||||
.execute_with_nullauth_session(|ctx| Password::prepare_tpm_key(ctx))
|
||||
.execute_with_nullauth_session(|ctx| prepare_tpm_key(ctx))
|
||||
.unwrap();
|
||||
|
||||
let p = CryptoPolicy::minimum();
|
||||
|
@ -1504,4 +1444,65 @@ mod tests {
|
|||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "tpm")]
|
||||
pub fn prepare_tpm_key(tpm_ctx: &mut TpmContext) -> Result<TpmHandle, CryptoError> {
|
||||
use tss_esapi::{
|
||||
attributes::ObjectAttributesBuilder,
|
||||
interface_types::{
|
||||
algorithm::{HashingAlgorithm, PublicAlgorithm},
|
||||
resource_handles::Hierarchy,
|
||||
},
|
||||
structures::{Digest, KeyedHashScheme, PublicBuilder, PublicKeyedHashParameters},
|
||||
};
|
||||
|
||||
// We generate a digest, which is really some unique small amount of data that
|
||||
// we save into the key context that we are going to save/load. This allows us
|
||||
// to have unique hmac keys compared to other users of the same tpm.
|
||||
|
||||
let digest = tpm_ctx
|
||||
.get_random(16)
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||
CryptoError::Tpm2
|
||||
})
|
||||
.and_then(|rand| {
|
||||
Digest::try_from(rand).map_err(|e| {
|
||||
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||
CryptoError::Tpm2
|
||||
})
|
||||
})?;
|
||||
|
||||
let object_attributes = ObjectAttributesBuilder::new()
|
||||
.with_sign_encrypt(true)
|
||||
.with_sensitive_data_origin(true)
|
||||
.with_user_with_auth(true)
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||
CryptoError::Tpm2
|
||||
})?;
|
||||
|
||||
let key_pub = PublicBuilder::new()
|
||||
.with_public_algorithm(PublicAlgorithm::KeyedHash)
|
||||
.with_name_hashing_algorithm(HashingAlgorithm::Sha256)
|
||||
.with_object_attributes(object_attributes)
|
||||
.with_keyed_hash_parameters(PublicKeyedHashParameters::new(
|
||||
KeyedHashScheme::HMAC_SHA_256,
|
||||
))
|
||||
.with_keyed_hash_unique_identifier(digest)
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||
CryptoError::Tpm2
|
||||
})?;
|
||||
|
||||
tpm_ctx
|
||||
.create_primary(Hierarchy::Owner, key_pub, None, None, None, None)
|
||||
.map(|key| key.key_handle.into())
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||
CryptoError::Tpm2
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ name = "kanidm_unix_common"
|
|||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
base64urlsafedata = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
csv = { workspace = true }
|
||||
|
|
|
@ -782,13 +782,159 @@ pub(crate) mod tpm {
|
|||
use rusqlite::{Connection, OptionalExtension};
|
||||
|
||||
use kanidm_lib_crypto::{CryptoError, CryptoPolicy, Password, TpmError};
|
||||
use tss_esapi::{utils::TpmsContext, Context, TctiNameConf};
|
||||
use tss_esapi::{Context, TctiNameConf};
|
||||
|
||||
use tss_esapi::{
|
||||
attributes::ObjectAttributesBuilder,
|
||||
handles::KeyHandle,
|
||||
interface_types::{
|
||||
algorithm::{HashingAlgorithm, PublicAlgorithm},
|
||||
resource_handles::Hierarchy,
|
||||
},
|
||||
structures::{
|
||||
Digest, KeyedHashScheme, Private, Public, PublicBuilder, PublicKeyedHashParameters,
|
||||
SymmetricCipherParameters, SymmetricDefinitionObject,
|
||||
},
|
||||
traits::{Marshall, UnMarshall},
|
||||
};
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use base64urlsafedata::Base64UrlSafeData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct BinaryLoadableKey {
|
||||
private: Base64UrlSafeData,
|
||||
public: Base64UrlSafeData,
|
||||
}
|
||||
|
||||
impl Into<BinaryLoadableKey> for LoadableKey {
|
||||
fn into(self) -> BinaryLoadableKey {
|
||||
BinaryLoadableKey {
|
||||
private: self.private.as_slice().to_owned().into(),
|
||||
public: self.public.marshall().unwrap().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<BinaryLoadableKey> for LoadableKey {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: BinaryLoadableKey) -> Result<Self, Self::Error> {
|
||||
let private = Private::try_from(value.private.0).map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to restore tpm hmac key");
|
||||
"private try from"
|
||||
})?;
|
||||
|
||||
let public = Public::unmarshall(value.public.0.as_slice()).map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to restore tpm hmac key");
|
||||
"public unmarshall"
|
||||
})?;
|
||||
|
||||
Ok(LoadableKey { public, private })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(try_from = "BinaryLoadableKey", into = "BinaryLoadableKey")]
|
||||
struct LoadableKey {
|
||||
private: Private,
|
||||
public: Public,
|
||||
}
|
||||
|
||||
pub struct TpmConfig {
|
||||
tcti: TctiNameConf,
|
||||
ctx: TpmsContext,
|
||||
private: Private,
|
||||
public: Public,
|
||||
}
|
||||
|
||||
// First, setup the primary key we will be using. Remember tpm Primary keys
|
||||
// are the same provided the *same* public parameters are used and the tpm
|
||||
// seed hasn't been reset.
|
||||
fn get_primary_key_public() -> Result<Public, ()> {
|
||||
let object_attributes = ObjectAttributesBuilder::new()
|
||||
.with_fixed_tpm(true)
|
||||
.with_fixed_parent(true)
|
||||
.with_st_clear(false)
|
||||
.with_sensitive_data_origin(true)
|
||||
.with_user_with_auth(true)
|
||||
.with_decrypt(true)
|
||||
// .with_sign_encrypt(true)
|
||||
.with_restricted(true)
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to create tpm primary key attributes");
|
||||
})?;
|
||||
|
||||
PublicBuilder::new()
|
||||
.with_public_algorithm(PublicAlgorithm::SymCipher)
|
||||
.with_name_hashing_algorithm(HashingAlgorithm::Sha256)
|
||||
.with_object_attributes(object_attributes)
|
||||
.with_symmetric_cipher_parameters(SymmetricCipherParameters::new(
|
||||
SymmetricDefinitionObject::AES_128_CFB,
|
||||
))
|
||||
.with_symmetric_cipher_unique_identifier(Digest::default())
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to create tpm primary key public");
|
||||
})
|
||||
}
|
||||
|
||||
fn new_hmac_public() -> Result<Public, ()> {
|
||||
let object_attributes = ObjectAttributesBuilder::new()
|
||||
.with_fixed_tpm(true)
|
||||
.with_fixed_parent(true)
|
||||
.with_st_clear(false)
|
||||
.with_sensitive_data_origin(true)
|
||||
.with_user_with_auth(true)
|
||||
.with_sign_encrypt(true)
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to create tpm hmac key attributes");
|
||||
})?;
|
||||
|
||||
PublicBuilder::new()
|
||||
.with_public_algorithm(PublicAlgorithm::KeyedHash)
|
||||
.with_name_hashing_algorithm(HashingAlgorithm::Sha256)
|
||||
.with_object_attributes(object_attributes)
|
||||
.with_keyed_hash_parameters(PublicKeyedHashParameters::new(
|
||||
KeyedHashScheme::HMAC_SHA_256,
|
||||
))
|
||||
.with_keyed_hash_unique_identifier(Digest::default())
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to create tpm hmac key public");
|
||||
})
|
||||
}
|
||||
|
||||
fn setup_keys(ctx: &mut Context, tpm_conf: &TpmConfig) -> Result<KeyHandle, CryptoError> {
|
||||
// Given our public and private parts, setup our keys for usage.
|
||||
let primary_pub = get_primary_key_public().map_err(|_| CryptoError::Tpm2PublicBuilder)?;
|
||||
trace!(?primary_pub);
|
||||
let primary_key = ctx
|
||||
.create_primary(Hierarchy::Owner, primary_pub, None, None, None, None)
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to load tpm context");
|
||||
<TpmError as Into<CryptoError>>::into(e)
|
||||
})?;
|
||||
|
||||
/*
|
||||
let hmac_pub = new_hmac_public()
|
||||
.map_err(|_| CryptoError::Tpm2PublicBuilder )
|
||||
?;
|
||||
trace!(?hmac_pub);
|
||||
*/
|
||||
|
||||
ctx.load(
|
||||
primary_key.key_handle.clone(),
|
||||
tpm_conf.private.clone(),
|
||||
tpm_conf.public.clone(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to load tpm context");
|
||||
<TpmError as Into<CryptoError>>::into(e)
|
||||
})
|
||||
}
|
||||
|
||||
impl Db {
|
||||
|
@ -801,6 +947,19 @@ pub(crate) mod tpm {
|
|||
error!(tpm_err = ?e, "Failed to create tpm context");
|
||||
})?;
|
||||
|
||||
// Create the primary object that will contain our key.
|
||||
let primary_pub = get_primary_key_public()?;
|
||||
let primary_key = context
|
||||
.execute_with_nullauth_session(|ctx| {
|
||||
ctx.create_primary(Hierarchy::Owner, primary_pub, None, None, None, None)
|
||||
})
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to create tpm primary key");
|
||||
})?;
|
||||
|
||||
// Now we know we can establish a correct primary key, lets get any previously saved
|
||||
// hmac keys.
|
||||
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS config_t (
|
||||
key TEXT PRIMARY KEY,
|
||||
|
@ -828,46 +987,66 @@ pub(crate) mod tpm {
|
|||
|
||||
trace!(ctx_data_present = %ctx_data.is_some());
|
||||
|
||||
let ex_ctx = if let Some(ctx_data) = ctx_data {
|
||||
let ex_private = if let Some(ctx_data) = ctx_data {
|
||||
// Test loading, blank it out if it fails.
|
||||
serde_json::from_slice(ctx_data.as_slice()).map_err(|e| {
|
||||
serde_json::from_slice(ctx_data.as_slice())
|
||||
.map_err(|e| {
|
||||
warn!("json error -> {:?}", e);
|
||||
})
|
||||
// On error, becomes none and we do nothing else.
|
||||
.ok()
|
||||
.and_then(|maybe_ctx: TpmsContext| {
|
||||
// can it load?
|
||||
.and_then(|loadable: LoadableKey| {
|
||||
// test if it can load?
|
||||
context
|
||||
.execute_with_nullauth_session(|ctx| ctx.context_load(maybe_ctx.clone()))
|
||||
.execute_with_nullauth_session(|ctx| {
|
||||
ctx.load(
|
||||
primary_key.key_handle.clone(),
|
||||
loadable.private.clone(),
|
||||
loadable.public.clone(),
|
||||
)
|
||||
// We have to flush the handle here to not overfill tpm context memory.
|
||||
// We'll be reloading the key later anyway.
|
||||
.and_then(|kh| ctx.flush_context(kh.into()))
|
||||
})
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to load tpm context");
|
||||
error!(tpm_err = ?e, "Failed to load tpm hmac key");
|
||||
})
|
||||
.ok()
|
||||
// It loaded, so sub in our context.
|
||||
.map(|_handle| maybe_ctx)
|
||||
// It loaded, so sub in our Private data.
|
||||
.map(|_hmac_handle| loadable)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let ctx = if let Some(existing_ctx) = ex_ctx {
|
||||
existing_ctx
|
||||
let loadable = if let Some(existing) = ex_private {
|
||||
existing
|
||||
} else {
|
||||
// Need to regenerate for some reason
|
||||
info!("Creating new tpm ctx key");
|
||||
// Need to regenerate the private key for some reason
|
||||
info!("Creating new hmac key");
|
||||
let hmac_pub = new_hmac_public()?;
|
||||
context
|
||||
.execute_with_nullauth_session(|ctx| {
|
||||
let key = Password::prepare_tpm_key(ctx)?;
|
||||
|
||||
ctx.context_save(key.into()).map_err(|e| e.into())
|
||||
ctx.create(
|
||||
primary_key.key_handle.clone(),
|
||||
hmac_pub,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|key| LoadableKey {
|
||||
public: key.out_public,
|
||||
private: key.out_private,
|
||||
})
|
||||
})
|
||||
.map_err(|e: CryptoError| {
|
||||
error!(tpm_err = ?e, "Failed to create tpm key");
|
||||
.map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to create tpm hmac key");
|
||||
})?
|
||||
};
|
||||
|
||||
// Serialise it out.
|
||||
let data = serde_json::to_vec(&ctx).map_err(|e| {
|
||||
let data = serde_json::to_vec(&loadable).map_err(|e| {
|
||||
error!("json error -> {:?}", e);
|
||||
})?;
|
||||
|
||||
|
@ -883,9 +1062,17 @@ pub(crate) mod tpm {
|
|||
})
|
||||
.map(|_| ())?;
|
||||
|
||||
//
|
||||
|
||||
info!("tpm binding configured");
|
||||
|
||||
Ok(TpmConfig { tcti, ctx })
|
||||
let LoadableKey { private, public } = loadable;
|
||||
|
||||
Ok(TpmConfig {
|
||||
tcti,
|
||||
private,
|
||||
public,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tpm_new(
|
||||
|
@ -899,12 +1086,8 @@ pub(crate) mod tpm {
|
|||
|
||||
context
|
||||
.execute_with_nullauth_session(|ctx| {
|
||||
let key = ctx.context_load(tpm_conf.ctx.clone()).map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to load tpm context");
|
||||
<TpmError as Into<CryptoError>>::into(e)
|
||||
})?;
|
||||
|
||||
Password::new_argon2id_tpm(policy, cred, ctx, key)
|
||||
let key = setup_keys(ctx, tpm_conf)?;
|
||||
Password::new_argon2id_tpm(policy, cred, ctx, key.into())
|
||||
})
|
||||
.map_err(|e: CryptoError| {
|
||||
error!(tpm_err = ?e, "Failed to create tpm bound password");
|
||||
|
@ -918,12 +1101,8 @@ pub(crate) mod tpm {
|
|||
|
||||
context
|
||||
.execute_with_nullauth_session(|ctx| {
|
||||
let key = ctx.context_load(tpm_conf.ctx.clone()).map_err(|e| {
|
||||
error!(tpm_err = ?e, "Failed to load tpm context");
|
||||
<TpmError as Into<CryptoError>>::into(e)
|
||||
})?;
|
||||
|
||||
pw.verify_ctx(cred, Some((ctx, key)))
|
||||
let key = setup_keys(ctx, tpm_conf)?;
|
||||
pw.verify_ctx(cred, Some((ctx, key.into())))
|
||||
})
|
||||
.map_err(|e: CryptoError| {
|
||||
error!(tpm_err = ?e, "Failed to create tpm bound password");
|
||||
|
|
Loading…
Reference in a new issue