20230629 tpm keygen ... again (#1793)

This commit is contained in:
Firstyear 2023-06-30 12:41:32 +10:00 committed by GitHub
parent 3e4c8f6241
commit 0425122ba3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 277 additions and 95 deletions

1
Cargo.lock generated
View file

@ -2571,6 +2571,7 @@ dependencies = [
name = "kanidm_unix_int"
version = "1.1.0-beta.13-dev"
dependencies = [
"base64urlsafedata",
"bytes",
"clap",
"clap_complete",

View file

@ -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
})
}
}

View file

@ -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 }

View file

@ -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");