Skip to content

Commit

Permalink
Create credentials
Browse files Browse the repository at this point in the history
Signed-off-by: Arthur Gautier <arthur.gautier@arista.com>
  • Loading branch information
baloo committed Feb 13, 2025
1 parent e7352e7 commit 75825af
Show file tree
Hide file tree
Showing 7 changed files with 791 additions and 3 deletions.
18 changes: 18 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,22 @@ p192 = { git = "https://github.com/RustCrypto/elliptic-curves.git" }
p224 = { git = "https://github.com/RustCrypto/elliptic-curves.git" }
sm2 = { git = "https://github.com/RustCrypto/elliptic-curves.git" }

# https://github.com/RustCrypto/KDFs/pull/108
concat-kdf = { git = "https://github.com/RustCrypto/KDFs.git" }

cfb-mode = { git = "https://github.com/RustCrypto/block-modes.git" }

# https://github.com/RustCrypto/RSA/pull/467
rsa = { git = "https://github.com/RustCrypto/RSA.git" }

# https://github.com/RustCrypto/traits/issues/1738
# https://github.com/RustCrypto/traits/pull/1742
# Pending release of crypto-common 0.2.0-rc.2
# Pending release of digest 0.11.0-rc.0
crypto-common = { git = "https://github.com/RustCrypto/traits.git" }
digest = { git = "https://github.com/RustCrypto/traits.git" }

# https://github.com/RustCrypto/block-ciphers/pull/465
aes = { git = "https://github.com/RustCrypto/block-ciphers.git" }
# Not actually in the graph, but a consumer should use the weak key detection there.
# des = { git = "https://github.com/RustCrypto/block-ciphers.git" }
15 changes: 12 additions & 3 deletions tss-esapi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ regex = "1.3.9"
zeroize = { version = "1.5.7", features = ["zeroize_derive"] }
tss-esapi-sys = { path = "../tss-esapi-sys", version = "0.5.0" }
x509-cert = { version = "0.3.0-pre.0", optional = true }
aes = { version = "0.9.0-pre.2", optional = true }
cfb-mode = { version = "0.9.0-pre", optional = true }
ecdsa = { version = "0.17.0-pre.9", features = ["der", "hazmat", "arithmetic", "verifying"], optional = true }
elliptic-curve = { version = "0.14.0-rc.1", optional = true, features = ["alloc", "pkcs8"] }
hmac = { version = "0.13.0-pre.4", optional = true }
p192 = { version = "0.14.0-pre", optional = true }
p224 = { version = "0.14.0-pre", optional = true }
p256 = { version = "0.14.0-pre.2", optional = true }
Expand All @@ -50,15 +53,21 @@ sha3 = { version = "0.11.0-pre.4", optional = true }
sm2 = { version = "0.14.0-pre", optional = true }
sm3 = { version = "0.5.0-pre.4", optional = true }
digest = { version = "0.11.0-pre.9", optional = true }
kbkdf = { version = "0.0.1", optional = true }
concat-kdf = { version = "0.2.0-pre", optional = true }
signature = { version = "2.3.0-pre.4", features = ["std"], optional = true}
cfg-if = "1.0.0"
strum = { version = "0.26.3", optional = true }
strum_macros = { version = "0.26.4", optional = true }
paste = "1.0.14"
getrandom = "0.2.11"
rand = "0.8"

[dev-dependencies]
aes = "0.9.0-pre.2"
env_logger = "0.11.5"
hex-literal = "0.4.1"
rsa = { version = "0.10.0-pre.3" }
serde_json = "^1.0.108"
sha2 = { version = "0.11.0-pre.4", features = ["oid"] }
tss-esapi = { path = ".", features = [
Expand All @@ -67,6 +76,7 @@ tss-esapi = { path = ".", features = [
"abstraction",
"rustcrypto-full",
] }
p256 = { version = "0.14.0-pre.2", features = ["ecdh"] }
x509-cert = { version = "0.3.0-pre.0", features = ["builder"] }

[build-dependencies]
Expand All @@ -78,6 +88,5 @@ generate-bindings = ["tss-esapi-sys/generate-bindings"]
abstraction = ["rustcrypto"]
integration-tests = ["strum", "strum_macros"]

rustcrypto = ["digest", "ecdsa", "elliptic-curve", "pkcs8", "signature", "x509-cert"]
rustcrypto-full = ["rustcrypto", "p192", "p224", "p256", "p384", "p521", "rsa", "sha1", "sha2", "sha3", "sm2", "sm3"]

rustcrypto = ["cfb-mode", "concat-kdf", "digest", "ecdsa", "elliptic-curve", "pkcs8", "signature", "x509-cert"]
rustcrypto-full = ["rustcrypto", "aes", "p192", "p224", "p256", "p384", "p521", "rsa", "sha1", "sha2", "sha3", "sm2", "sm3"]
287 changes: 287 additions & 0 deletions tss-esapi/src/utils/credential.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
// Copyright 2025 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

use core::{
marker::PhantomData,
ops::{Add, Mul},
};

use cfb_mode::cipher::{AsyncStreamCipher, BlockCipherEncrypt};
use digest::{
array::ArraySize,
consts::{B1, U8},
crypto_common::{Iv, KeyIvInit, KeySizeUser, WeakKeyError},
typenum::{
operator_aliases::{Add1, Sum},
Unsigned,
},
Digest, DynDigest, FixedOutputReset, Key, KeyInit, Mac, OutputSizeUser,
};
use ecdsa::elliptic_curve::{
ecdh::{EphemeralSecret, SharedSecret},
sec1::{Coordinates, FromEncodedPoint, ModulusSize, ToEncodedPoint},
AffinePoint, Curve, CurveArithmetic, FieldBytesSize, PublicKey,
};
use hmac::{EagerHash, Hmac};
use log::error;
use rand::{thread_rng, Rng};
use rsa::{Oaep, RsaPublicKey};

use crate::{
error::{Error, Result, WrapperErrorKind},
structures::{EncryptedSecret, IdObject, Name},
utils::kdf::{self},
};

type WeakResult<T> = core::result::Result<T, WeakKeyError>;

// [`TpmHmac`] intends to code for the key expected for hmac
// in the KDFa and KDFe derivations. There are no standard sizes for hmac keys really,
// upstream RustCrypto considers it to be [BlockSize], but TPM specification
// has a different opinion on the matter, and expect the key to the output
// bit size of the hash algorithm used.
//
// See https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=202
// section 24.5 HMAC:
// bits the number of bits in the digest produced by ekNameAlg
//
// [BlockSize]: https://docs.rs/hmac/0.12.1/hmac/struct.HmacCore.html#impl-KeySizeUser-for-HmacCore%3CD%3E
struct TpmHmac<H>(PhantomData<H>);

impl<H> KeySizeUser for TpmHmac<H>
where
H: OutputSizeUser,
{
type KeySize = H::OutputSize;
}

pub fn make_credential_ecc<C, EkHash, EkCipher>(
ek_public: PublicKey<C>,
secret: &[u8],
key_name: Name,
) -> Result<(IdObject, EncryptedSecret)>
where
C: Curve + CurveArithmetic,

AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
FieldBytesSize<C>: ModulusSize,

<FieldBytesSize<C> as Add>::Output: Add<FieldBytesSize<C>>,
Sum<FieldBytesSize<C>, FieldBytesSize<C>>: ArraySize,
Sum<FieldBytesSize<C>, FieldBytesSize<C>>: Add<U8>,
Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U8>: Add<B1>,
Add1<Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U8>>: ArraySize,

EkHash: Digest + EagerHash + FixedOutputReset,
<EkHash as OutputSizeUser>::OutputSize: Mul<U8>,
<<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,
<<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,

EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,
<EkCipher as KeySizeUser>::KeySize: Mul<U8>,
<<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,
{
let mut rng = thread_rng();

loop {
// See Table 22 - Key Generation for the various labels used here after:
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=183

// C.6.4. ECC Secret Sharing for Credentials
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=311
let local = EphemeralSecret::<C>::random(&mut rng);

let ecdh_secret: SharedSecret<C> = local.diffie_hellman(&ek_public);
let local_public = local.public_key();
drop(local);

let seed = kdf::kdfe::<kdf::Identity, EkHash, C, TpmHmac<EkHash>>(
&ecdh_secret,
&local_public,
&ek_public,
)?;
drop(ecdh_secret);

// The local ECDH pair is used as "encrypted seed"
let encoded_point = local_public.to_encoded_point(false);
let Coordinates::Uncompressed {
x: point_x,
y: point_y,
} = encoded_point.coordinates()
else {
// NOTE: The only way this could trigger would be for the local key to be identity.
error!("Couldn't compute coordinates for the local public key");
return Err(Error::local_error(WrapperErrorKind::InvalidParam));
};
let encrypted_seed = {
let mut out = vec![];
out.extend_from_slice(&FieldBytesSize::<C>::U16.to_be_bytes()[..]);
out.extend_from_slice(point_x);
out.extend_from_slice(&FieldBytesSize::<C>::U16.to_be_bytes()[..]);
out.extend_from_slice(point_y);
out
};
let encrypted_secret = EncryptedSecret::from_bytes(&encrypted_seed)?;

match secret_to_credential::<EkHash, EkCipher>(seed, secret, &key_name)? {
Ok(id_object) => return Ok((id_object, encrypted_secret)),
Err(WeakKeyError) => {
// 11.4.10.4 Rejection of weak keys
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82

// The Key was considered weak, and we should re-run the creation of the encrypted
// secret.
continue;
}
}
}
}

pub fn make_credential_rsa<EkHash, EkCipher>(
ek_public: &RsaPublicKey,
secret: &[u8],
key_name: Name,
) -> Result<(IdObject, EncryptedSecret)>
where
EkHash: Digest + DynDigest + Send + Sync + 'static,
EkHash: EagerHash + FixedOutputReset,
<EkHash as OutputSizeUser>::OutputSize: Mul<U8>,
<<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,
<<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,

EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,
<EkCipher as KeySizeUser>::KeySize: Mul<U8>,
<<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,
{
let mut rng = thread_rng();

loop {
// See Table 22 - Key Generation for the various labels used here after:
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=183

// B.10.4 RSA Secret Sharing for Credentials
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=302
let random_seed = {
let mut out = Key::<TpmHmac<EkHash>>::default();
rng.try_fill(out.as_mut_slice()).map_err(|e| {
error!("RNG error: {e}");
Error::local_error(WrapperErrorKind::InternalError)
})?;
out
};

// The random seed is then encrypted with RSA-OAEP
//
// B.4 RSAES_OAEP
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=297
//
// The label is a byte-stream whose last byte must be zero
//
// B.10.4. RSA Secret Sharing for Credentials
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=302
//
// The label is going to be "IDENTITY" for secret sharing.
let encrypted_seed = {
let padding = Oaep::new_with_label::<EkHash, _>(b"IDENTITY\0".to_vec());
ek_public
.encrypt(&mut rng, padding, &random_seed[..])
.map_err(|e| {
error!("RSA OAEP encryption error: {e}");
Error::local_error(WrapperErrorKind::InternalError)
})?
};
let encrypted_secret = EncryptedSecret::from_bytes(&encrypted_seed)?;

match secret_to_credential::<EkHash, EkCipher>(random_seed, secret, &key_name)? {
Ok(id_object) => return Ok((id_object, encrypted_secret)),
Err(WeakKeyError) => {
// 11.4.10.4 Rejection of weak keys
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82

// The Key was considered weak, and we should re-run the creation of the encrypted
// secret.
continue;
}
}
}
}

fn secret_to_credential<EkHash, EkCipher>(
seed: Key<TpmHmac<EkHash>>,
secret: &[u8],
key_name: &Name,
) -> Result<WeakResult<IdObject>>
where
EkHash: Digest + EagerHash + FixedOutputReset,
<EkHash as OutputSizeUser>::OutputSize: Mul<U8>,
<<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,
<<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,

EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,
<EkCipher as KeySizeUser>::KeySize: Mul<U8>,
<<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,
{
// Prepare the sensitive data
// this will be then encrypted using AES-CFB (size of the symmetric key depends on the EK).
// NOTE(security): no need to zeroize it, content is rewritten in place with the encrypted version
let mut sensitive_data = {
let mut out = vec![];
out.extend_from_slice(
&u16::try_from(secret.len())
.map_err(|_| {
error!("secret may only be 2^16 bytes long");
Error::local_error(WrapperErrorKind::WrongParamSize)
})?
.to_be_bytes()[..],
);
out.extend_from_slice(secret);
out
};

// We'll now encrypt the sensitive data, and hmac the result of the encryption
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=201
// See 24.4 Symmetric Encryption
let sym_key = kdf::kdfa::<EkHash, kdf::Storage, EkCipher>(&seed, key_name.value(), &[])?;

if EkCipher::weak_key_test(&sym_key).is_ok() {
// 11.4.10.4 Rejection of weak keys
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82
// The Key was considered weak, and we should re-run the creation of the encrypted
// secret.

return Ok(Err(WeakKeyError));
}

let iv: Iv<cfb_mode::Encryptor<EkCipher>> = Default::default();

cfb_mode::Encryptor::<EkCipher>::new(&sym_key, &iv).encrypt(&mut sensitive_data);

// See 24.5 HMAC
let hmac_key = kdf::kdfa::<EkHash, kdf::Integrity, TpmHmac<EkHash>>(&seed, &[], &[])?;
let mut hmac = Hmac::<EkHash>::new_from_slice(&hmac_key).map_err(|e| {
error!("HMAC initialization error: {e}");
Error::local_error(WrapperErrorKind::WrongParamSize)
})?;
Mac::update(&mut hmac, &sensitive_data);
Mac::update(&mut hmac, key_name.value());
let hmac = hmac.finalize();

// We'll now serialize the object and get everything through the door.
let mut out = vec![];
out.extend_from_slice(
&u16::try_from(hmac.into_bytes().len())
.map_err(|_| {
// NOTE: this shouldn't ever trigger ... but ...
error!("HMAC output may only be 2^16 bytes long");
Error::local_error(WrapperErrorKind::WrongParamSize)
})?
.to_be_bytes()[..],
);
out.extend_from_slice(&hmac.into_bytes());
out.extend_from_slice(&sensitive_data);

IdObject::from_bytes(&out).map(Ok)
}
Loading

0 comments on commit 75825af

Please sign in to comment.