Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 206e793

Browse files
committedJun 9, 2025·
Create credentials
Signed-off-by: Arthur Gautier <[email protected]>
1 parent c7c0b24 commit 206e793

File tree

8 files changed

+924
-16
lines changed

8 files changed

+924
-16
lines changed
 

‎Cargo.lock

Lines changed: 141 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ p224 = { git = "https://github.com/RustCrypto/elliptic-curves.git" }
99
sm2 = { git = "https://github.com/RustCrypto/elliptic-curves.git" }
1010

1111
x509-cert = { git = "https://github.com/RustCrypto/formats.git" }
12+
13+
concat-kdf = { git = "https://github.com/RustCrypto/KDFs.git" }

‎tss-esapi/Cargo.toml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,11 @@ regex = "1.3.9"
3535
zeroize = { version = "1.5.7", features = ["zeroize_derive"] }
3636
tss-esapi-sys = { path = "../tss-esapi-sys", version = "0.5.0" }
3737
x509-cert = { version = "0.3.0-pre.0", optional = true }
38+
aes = { version = "0.9.0-rc.0", optional = true }
39+
cfb-mode = { version = "0.9.0-rc.0", optional = true }
3840
ecdsa = { version = "0.17.0-rc.0", features = ["der", "hazmat", "arithmetic", "verifying"], optional = true }
3941
elliptic-curve = { version = "0.14.0-rc.5", optional = true, features = ["alloc", "pkcs8"] }
42+
hmac = { version = "0.13.0-rc.0", optional = true }
4043
p192 = { version = "0.14.0-pre", optional = true }
4144
p224 = { version = "0.14.0-pre", optional = true }
4245
p256 = { version = "0.14.0-pre.5", optional = true }
@@ -51,14 +54,20 @@ sm2 = { version = "0.14.0-pre", optional = true }
5154
sm3 = { version = "0.5.0-pre.5", optional = true }
5255
digest = { version = "0.11.0-rc.0", optional = true }
5356
signature = { version = "3.0.0-rc.0", features = ["alloc", "digest"], optional = true}
57+
kbkdf = { version = "0.0.1", optional = true }
58+
concat-kdf = { version = "0.2.0-pre", optional = true }
5459
cfg-if = "1.0.0"
5560
strum = { version = "0.26.3", optional = true }
5661
strum_macros = { version = "0.26.4", optional = true }
5762
paste = "1.0.14"
5863
getrandom = "0.3"
64+
rand = "0.9"
5965

6066
[dev-dependencies]
67+
aes = "0.9.0-pre.2"
6168
env_logger = "0.11.5"
69+
hex-literal = "1"
70+
rsa = { version = "0.10.0-pre.3" }
6271
serde_json = "^1.0.108"
6372
sha2 = { version = "0.11.0-pre.5", features = ["oid"] }
6473
tss-esapi = { path = ".", features = [
@@ -67,6 +76,7 @@ tss-esapi = { path = ".", features = [
6776
"abstraction",
6877
"rustcrypto-full",
6978
] }
79+
p256 = { version = "0.14.0-pre.2", features = ["ecdh"] }
7080
x509-cert = { version = "0.3.0-pre.0", features = ["builder"] }
7181

7282
[build-dependencies]
@@ -78,8 +88,9 @@ generate-bindings = ["tss-esapi-sys/generate-bindings"]
7888
abstraction = ["rustcrypto"]
7989
integration-tests = ["strum", "strum_macros"]
8090

81-
rustcrypto = ["digest", "ecdsa", "elliptic-curve", "pkcs8", "signature", "x509-cert"]
82-
rustcrypto-full = ["rustcrypto", "p192", "p224", "p256", "p384", "p521", "rsa", "sha1", "sha2", "sha3", "sm2", "sm3"]
83-
91+
rustcrypto = ["cfb-mode", "concat-kdf", "digest", "ecdsa", "elliptic-curve/ecdh", "hmac", "kbkdf", "pkcs8", "signature", "x509-cert"]
92+
rustcrypto-full = ["rustcrypto", "aes", "p192", "p224", "p256", "p384", "p521", "rsa", "sha1", "sha2", "sha3", "sm2", "sm3"]
93+
94+
rsa = ["dep:rsa", "kbkdf"]
8495
sha1 = ["dep:sha1", "rsa?/sha1"]
8596
sha2 = ["dep:sha2", "rsa?/sha2"]

‎tss-esapi/src/utils/credential.rs

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
// Copyright 2025 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use core::{
5+
marker::PhantomData,
6+
ops::{Add, Mul},
7+
};
8+
9+
use cfb_mode::cipher::{AsyncStreamCipher, BlockCipherEncrypt};
10+
use digest::{
11+
array::ArraySize,
12+
consts::{B1, U8},
13+
crypto_common::{Iv, KeyIvInit, KeySizeUser, WeakKeyError},
14+
typenum::{
15+
operator_aliases::{Add1, Sum},
16+
Unsigned,
17+
},
18+
Digest, FixedOutputReset, Key, KeyInit, Mac, OutputSizeUser,
19+
};
20+
use ecdsa::elliptic_curve::{
21+
ecdh::{EphemeralSecret, SharedSecret},
22+
sec1::{Coordinates, FromEncodedPoint, ModulusSize, ToEncodedPoint},
23+
AffinePoint, Curve, CurveArithmetic, FieldBytesSize, PublicKey,
24+
};
25+
use hmac::{EagerHash, Hmac};
26+
use log::error;
27+
use rand::rng;
28+
29+
#[cfg(feature = "rsa")]
30+
use {
31+
digest::DynDigest,
32+
rand::Rng,
33+
rsa::{Oaep, RsaPublicKey},
34+
};
35+
36+
use crate::{
37+
error::{Error, Result, WrapperErrorKind},
38+
structures::{EncryptedSecret, IdObject, Name},
39+
utils::kdf::{self},
40+
};
41+
42+
type WeakResult<T> = core::result::Result<T, WeakKeyError>;
43+
44+
// [`TpmHmac`] intends to code for the key expected for hmac
45+
// in the KDFa and KDFe derivations. There are no standard sizes for hmac keys really,
46+
// upstream RustCrypto considers it to be [BlockSize], but TPM specification
47+
// has a different opinion on the matter, and expect the key to the output
48+
// bit size of the hash algorithm used.
49+
//
50+
// See https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=202
51+
// section 24.5 HMAC:
52+
// bits the number of bits in the digest produced by ekNameAlg
53+
//
54+
// [BlockSize]: https://docs.rs/hmac/0.12.1/hmac/struct.HmacCore.html#impl-KeySizeUser-for-HmacCore%3CD%3E
55+
struct TpmHmac<H>(PhantomData<H>);
56+
57+
impl<H> KeySizeUser for TpmHmac<H>
58+
where
59+
H: OutputSizeUser,
60+
{
61+
type KeySize = H::OutputSize;
62+
}
63+
64+
pub fn make_credential_ecc<C, EkHash, EkCipher>(
65+
ek_public: PublicKey<C>,
66+
secret: &[u8],
67+
key_name: Name,
68+
) -> Result<(IdObject, EncryptedSecret)>
69+
where
70+
C: Curve + CurveArithmetic,
71+
72+
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
73+
FieldBytesSize<C>: ModulusSize,
74+
75+
<FieldBytesSize<C> as Add>::Output: Add<FieldBytesSize<C>>,
76+
Sum<FieldBytesSize<C>, FieldBytesSize<C>>: ArraySize,
77+
Sum<FieldBytesSize<C>, FieldBytesSize<C>>: Add<U8>,
78+
Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U8>: Add<B1>,
79+
Add1<Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U8>>: ArraySize,
80+
81+
EkHash: Digest + EagerHash + FixedOutputReset,
82+
<EkHash as OutputSizeUser>::OutputSize: Mul<U8>,
83+
<<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
84+
<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,
85+
<<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
86+
87+
EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,
88+
<EkCipher as KeySizeUser>::KeySize: Mul<U8>,
89+
<<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,
90+
{
91+
let mut rng = rng();
92+
93+
loop {
94+
// See Table 22 - Key Generation for the various labels used here after:
95+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=183
96+
97+
// C.6.4. ECC Secret Sharing for Credentials
98+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=311
99+
let local = EphemeralSecret::<C>::random(&mut rng);
100+
101+
let ecdh_secret: SharedSecret<C> = local.diffie_hellman(&ek_public);
102+
let local_public = local.public_key();
103+
drop(local);
104+
105+
let seed = kdf::kdfe::<kdf::Identity, EkHash, C, TpmHmac<EkHash>>(
106+
&ecdh_secret,
107+
&local_public,
108+
&ek_public,
109+
)?;
110+
drop(ecdh_secret);
111+
112+
// The local ECDH pair is used as "encrypted seed"
113+
let encoded_point = local_public.to_encoded_point(false);
114+
let Coordinates::Uncompressed {
115+
x: point_x,
116+
y: point_y,
117+
} = encoded_point.coordinates()
118+
else {
119+
// NOTE: The only way this could trigger would be for the local key to be identity.
120+
error!("Couldn't compute coordinates for the local public key");
121+
return Err(Error::local_error(WrapperErrorKind::InvalidParam));
122+
};
123+
let encrypted_seed = {
124+
let mut out = vec![];
125+
out.extend_from_slice(&FieldBytesSize::<C>::U16.to_be_bytes()[..]);
126+
out.extend_from_slice(point_x);
127+
out.extend_from_slice(&FieldBytesSize::<C>::U16.to_be_bytes()[..]);
128+
out.extend_from_slice(point_y);
129+
out
130+
};
131+
let encrypted_secret = EncryptedSecret::from_bytes(&encrypted_seed)?;
132+
133+
match secret_to_credential::<EkHash, EkCipher>(seed, secret, &key_name)? {
134+
Ok(id_object) => return Ok((id_object, encrypted_secret)),
135+
Err(WeakKeyError) => {
136+
// 11.4.10.4 Rejection of weak keys
137+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82
138+
139+
// The Key was considered weak, and we should re-run the creation of the encrypted
140+
// secret.
141+
continue;
142+
}
143+
}
144+
}
145+
}
146+
147+
#[cfg(feature = "rsa")]
148+
pub fn make_credential_rsa<EkHash, EkCipher>(
149+
ek_public: &RsaPublicKey,
150+
secret: &[u8],
151+
key_name: Name,
152+
) -> Result<(IdObject, EncryptedSecret)>
153+
where
154+
EkHash: Digest + DynDigest + Send + Sync + 'static,
155+
EkHash: EagerHash + FixedOutputReset,
156+
<EkHash as OutputSizeUser>::OutputSize: Mul<U8>,
157+
<<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
158+
<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,
159+
<<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
160+
161+
EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,
162+
<EkCipher as KeySizeUser>::KeySize: Mul<U8>,
163+
<<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,
164+
{
165+
let mut rng = rng();
166+
167+
loop {
168+
// See Table 22 - Key Generation for the various labels used here after:
169+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=183
170+
171+
// B.10.4 RSA Secret Sharing for Credentials
172+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=302
173+
let random_seed = {
174+
let mut out = Key::<TpmHmac<EkHash>>::default();
175+
rng.fill(out.as_mut_slice());
176+
out
177+
};
178+
179+
// The random seed is then encrypted with RSA-OAEP
180+
//
181+
// B.4 RSAES_OAEP
182+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=297
183+
//
184+
// The label is a byte-stream whose last byte must be zero
185+
//
186+
// B.10.4. RSA Secret Sharing for Credentials
187+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=302
188+
//
189+
// The label is going to be "IDENTITY" for secret sharing.
190+
let encrypted_seed = {
191+
let padding = Oaep::new_with_label::<EkHash, _>(b"IDENTITY\0".to_vec());
192+
ek_public
193+
.encrypt(&mut rng, padding, &random_seed[..])
194+
.map_err(|e| {
195+
error!("RSA OAEP encryption error: {e}");
196+
Error::local_error(WrapperErrorKind::InternalError)
197+
})?
198+
};
199+
let encrypted_secret = EncryptedSecret::from_bytes(&encrypted_seed)?;
200+
201+
match secret_to_credential::<EkHash, EkCipher>(random_seed, secret, &key_name)? {
202+
Ok(id_object) => return Ok((id_object, encrypted_secret)),
203+
Err(WeakKeyError) => {
204+
// 11.4.10.4 Rejection of weak keys
205+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82
206+
207+
// The Key was considered weak, and we should re-run the creation of the encrypted
208+
// secret.
209+
continue;
210+
}
211+
}
212+
}
213+
}
214+
215+
fn secret_to_credential<EkHash, EkCipher>(
216+
seed: Key<TpmHmac<EkHash>>,
217+
secret: &[u8],
218+
key_name: &Name,
219+
) -> Result<WeakResult<IdObject>>
220+
where
221+
EkHash: Digest + EagerHash + FixedOutputReset,
222+
<EkHash as OutputSizeUser>::OutputSize: Mul<U8>,
223+
<<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
224+
<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,
225+
<<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
226+
227+
EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,
228+
<EkCipher as KeySizeUser>::KeySize: Mul<U8>,
229+
<<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,
230+
{
231+
// Prepare the sensitive data
232+
// this will be then encrypted using AES-CFB (size of the symmetric key depends on the EK).
233+
// NOTE(security): no need to zeroize it, content is rewritten in place with the encrypted version
234+
let mut sensitive_data = {
235+
let mut out = vec![];
236+
out.extend_from_slice(
237+
&u16::try_from(secret.len())
238+
.map_err(|_| {
239+
error!("secret may only be 2^16 bytes long");
240+
Error::local_error(WrapperErrorKind::WrongParamSize)
241+
})?
242+
.to_be_bytes()[..],
243+
);
244+
out.extend_from_slice(secret);
245+
out
246+
};
247+
248+
// We'll now encrypt the sensitive data, and hmac the result of the encryption
249+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=201
250+
// See 24.4 Symmetric Encryption
251+
let sym_key = kdf::kdfa::<EkHash, kdf::Storage, EkCipher>(&seed, key_name.value(), &[])?;
252+
253+
if EkCipher::weak_key_test(&sym_key).is_ok() {
254+
// 11.4.10.4 Rejection of weak keys
255+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82
256+
// The Key was considered weak, and we should re-run the creation of the encrypted
257+
// secret.
258+
259+
return Ok(Err(WeakKeyError));
260+
}
261+
262+
let iv: Iv<cfb_mode::Encryptor<EkCipher>> = Default::default();
263+
264+
cfb_mode::Encryptor::<EkCipher>::new(&sym_key, &iv).encrypt(&mut sensitive_data);
265+
266+
// See 24.5 HMAC
267+
let hmac_key = kdf::kdfa::<EkHash, kdf::Integrity, TpmHmac<EkHash>>(&seed, &[], &[])?;
268+
let mut hmac = Hmac::<EkHash>::new_from_slice(&hmac_key).map_err(|e| {
269+
error!("HMAC initialization error: {e}");
270+
Error::local_error(WrapperErrorKind::WrongParamSize)
271+
})?;
272+
Mac::update(&mut hmac, &sensitive_data);
273+
Mac::update(&mut hmac, key_name.value());
274+
let hmac = hmac.finalize();
275+
276+
// We'll now serialize the object and get everything through the door.
277+
let mut out = vec![];
278+
out.extend_from_slice(
279+
&u16::try_from(hmac.into_bytes().len())
280+
.map_err(|_| {
281+
// NOTE: this shouldn't ever trigger ... but ...
282+
error!("HMAC output may only be 2^16 bytes long");
283+
Error::local_error(WrapperErrorKind::WrongParamSize)
284+
})?
285+
.to_be_bytes()[..],
286+
);
287+
out.extend_from_slice(&hmac.into_bytes());
288+
out.extend_from_slice(&sensitive_data);
289+
290+
IdObject::from_bytes(&out).map(Ok)
291+
}

‎tss-esapi/src/utils/kdf.rs

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
// Copyright 2025 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use core::ops::{Add, Mul};
5+
6+
use digest::{
7+
array::{Array, ArraySize},
8+
consts::{B1, U3, U6, U7, U8, U9},
9+
crypto_common::KeySizeUser,
10+
typenum::{
11+
operator_aliases::{Add1, Sum},
12+
Unsigned,
13+
},
14+
Digest, FixedOutputReset, Key, OutputSizeUser,
15+
};
16+
use ecdsa::elliptic_curve::{
17+
ecdh::SharedSecret,
18+
point::AffineCoordinates,
19+
sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint},
20+
AffinePoint, Curve, CurveArithmetic, FieldBytesSize, PublicKey,
21+
};
22+
use hmac::{EagerHash, Hmac};
23+
use kbkdf::{Counter, Kbkdf, Params};
24+
use log::error;
25+
26+
use crate::{Error, Result, WrapperErrorKind};
27+
28+
/// Label to be applied when deriving a key with either [`kdfa`] or [`kdfe`]
29+
// Note: until generic_const_expr stabilize, we will have to carry a const parameter on the trait,
30+
// once that's stable, we should be able to do `const LABEL: [u8; Self::LabelSize]`
31+
// Until then, the preferred implementation would be using `impl_kdf_label` macro, as it should be
32+
// misuse-resistant.
33+
pub trait KdfLabel {
34+
type LabelSize: Unsigned;
35+
const LABEL: &'static [u8];
36+
}
37+
38+
macro_rules! impl_kdf_label {
39+
($usage:ty, $size: ty, $value: expr) => {
40+
impl KdfLabel for $usage {
41+
type LabelSize = $size;
42+
const LABEL: &'static [u8] = {
43+
// This is only to make sure at compile-time the label has the correct size
44+
let _: [u8; <$size>::USIZE] = *$value;
45+
$value
46+
};
47+
}
48+
};
49+
}
50+
51+
#[derive(Copy, Clone, Debug)]
52+
pub struct Secret;
53+
impl_kdf_label!(Secret, U6, b"SECRET");
54+
55+
#[derive(Copy, Clone, Debug)]
56+
pub struct Context;
57+
impl_kdf_label!(Context, U7, b"CONTEXT");
58+
59+
#[derive(Copy, Clone, Debug)]
60+
pub struct Obfuscate;
61+
impl_kdf_label!(Obfuscate, U9, b"OBFUSCATE");
62+
63+
#[derive(Copy, Clone, Debug)]
64+
pub struct Storage;
65+
impl_kdf_label!(Storage, U7, b"STORAGE");
66+
67+
#[derive(Copy, Clone, Debug)]
68+
pub struct Integrity;
69+
impl_kdf_label!(Integrity, U9, b"INTEGRITY");
70+
71+
#[derive(Copy, Clone, Debug)]
72+
pub struct Commit;
73+
impl_kdf_label!(Commit, U6, b"COMMIT");
74+
75+
#[derive(Copy, Clone, Debug)]
76+
pub struct Cfb;
77+
impl_kdf_label!(Cfb, U3, b"CFB");
78+
79+
#[derive(Copy, Clone, Debug)]
80+
pub struct Xor;
81+
impl_kdf_label!(Xor, U3, b"XOR");
82+
83+
#[derive(Copy, Clone, Debug)]
84+
pub struct Session;
85+
impl_kdf_label!(Session, U7, b"SESSION");
86+
87+
#[derive(Copy, Clone, Debug)]
88+
pub struct Identity;
89+
impl_kdf_label!(Identity, U8, b"IDENTITY");
90+
91+
type LabelAndUAndV<N, C> = Add1<Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, N>>;
92+
93+
pub fn kdfa<H, L, K>(key: &[u8], context_u: &[u8], context_v: &[u8]) -> Result<Key<K>>
94+
where
95+
L: KdfLabel,
96+
97+
H: Digest + FixedOutputReset + EagerHash,
98+
K: KeySizeUser,
99+
100+
K::KeySize: ArraySize + Mul<U8>,
101+
<K::KeySize as Mul<U8>>::Output: Unsigned,
102+
103+
<<H as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,
104+
<<<H as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
105+
{
106+
let mut context = Vec::with_capacity(context_u.len() + context_v.len());
107+
context.extend_from_slice(context_u);
108+
context.extend_from_slice(context_v);
109+
110+
let kdf = Counter::<Hmac<H>, K>::default();
111+
kdf.derive(
112+
Params::builder(key)
113+
.with_label(L::LABEL)
114+
.with_context(&context)
115+
.build(),
116+
)
117+
.map_err(|e| {
118+
error!("KDFa derivation error: {e}");
119+
Error::local_error(WrapperErrorKind::InternalError)
120+
})
121+
}
122+
123+
pub fn kdfe<L, H, C, K>(
124+
z: &SharedSecret<C>,
125+
party_u_info: &PublicKey<C>,
126+
party_v_info: &PublicKey<C>,
127+
) -> Result<Key<K>>
128+
where
129+
L: KdfLabel,
130+
131+
H: Digest + FixedOutputReset,
132+
C: Curve + CurveArithmetic,
133+
K: KeySizeUser,
134+
135+
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
136+
FieldBytesSize<C>: ModulusSize,
137+
138+
<FieldBytesSize<C> as Add>::Output: Add<FieldBytesSize<C>>,
139+
Sum<FieldBytesSize<C>, FieldBytesSize<C>>: Add<L::LabelSize>,
140+
Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, L::LabelSize>: Add<B1>,
141+
Add1<Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, L::LabelSize>>: ArraySize,
142+
{
143+
let mut key = Key::<K>::default();
144+
145+
let mut other_info = Array::<u8, LabelAndUAndV<L::LabelSize, C>>::default();
146+
other_info[..L::LabelSize::USIZE].copy_from_slice(L::LABEL);
147+
other_info[L::LabelSize::USIZE] = 0;
148+
149+
other_info[L::LabelSize::USIZE + 1..L::LabelSize::USIZE + 1 + FieldBytesSize::<C>::USIZE]
150+
.copy_from_slice(&party_u_info.as_affine().x());
151+
other_info[L::LabelSize::USIZE + 1 + FieldBytesSize::<C>::USIZE..]
152+
.copy_from_slice(&party_v_info.as_affine().x());
153+
154+
concat_kdf::derive_key_into::<H>(z.raw_secret_bytes(), &other_info, &mut key).map_err(|e| {
155+
error!("KDFe derivation error: {e}");
156+
Error::local_error(WrapperErrorKind::InternalError)
157+
})?;
158+
159+
Ok(key)
160+
}
161+
162+
#[cfg(test)]
163+
mod tests {
164+
use super::*;
165+
166+
use aes::Aes256;
167+
use hex_literal::hex;
168+
use sha2::Sha256;
169+
170+
#[test]
171+
fn test_kdfe() {
172+
struct Vector<const S: usize, const K: usize, const E: usize> {
173+
shared_secret: [u8; S],
174+
local_key: [u8; K],
175+
remote_key: [u8; K],
176+
expected: [u8; E],
177+
}
178+
179+
// Test vectors here were manually generated from tpm2-pytss
180+
static TEST_VECTORS_SHA256: [Vector<
181+
{ FieldBytesSize::<p256::NistP256>::USIZE },
182+
{ <FieldBytesSize<p256::NistP256> as ModulusSize>::CompressedPointSize::USIZE },
183+
32,
184+
>; 2] = [
185+
Vector {
186+
shared_secret: hex!(
187+
"c75afb6f49c941ef194b232d7615769f5152d20de5dee19a991067f337dd65bc"
188+
),
189+
local_key: hex!(
190+
"031ba4030de068a2f07919c42ef6b19f302884f35f45e7d4e4bb90ffbb0bd9d099"
191+
),
192+
remote_key: hex!(
193+
"038f2b219a29c2ff9ba69cedff2d08d33a5dbca3da6bc8af8acd3ff6f5ec4dfbef"
194+
),
195+
expected: hex!("e3a0079db19724f9b76101e9364c4a149cea3501336abc3b603f94b22b6309a5"),
196+
},
197+
Vector {
198+
shared_secret: hex!(
199+
"a90a1c095155428500ed19e87c0df078df3dd2e66a0e3bbe664ba9ff62113b4a"
200+
),
201+
local_key: hex!(
202+
"03e9c7d6a853ba6176b65ec2f328bdea25f61c4e1b23a4e1c08e1da8c723381a04"
203+
),
204+
remote_key: hex!(
205+
"036ccf059628d3cdf8e1b4c4ba6d14696ba51cc8d4a96df4016f0b214782d5cee6"
206+
),
207+
expected: hex!("865f8093e2c4b801dc8c236eeb2806c7b1c51c2cb04101c035f7f2511ea0aeda"),
208+
},
209+
];
210+
211+
for v in &TEST_VECTORS_SHA256 {
212+
let out = kdfe::<Identity, Sha256, p256::NistP256, Aes256>(
213+
&SharedSecret::from(Array::from(v.shared_secret)),
214+
&PublicKey::try_from(Array::from(v.local_key)).unwrap(),
215+
&PublicKey::try_from(Array::from(v.remote_key)).unwrap(),
216+
)
217+
.unwrap();
218+
assert_eq!(out, v.expected);
219+
}
220+
}
221+
222+
#[test]
223+
fn test_kdfa() {
224+
struct Vector {
225+
key: &'static [u8],
226+
context_u: &'static [u8],
227+
context_v: &'static [u8],
228+
expected: &'static [u8],
229+
}
230+
231+
static TEST_VECTORS_SHA256: [Vector; 1] = [Vector {
232+
key: &hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
233+
context_u: b"",
234+
context_v: &hex!("0506070809"),
235+
expected: &hex!("de275f7f5cfeaac226b30d42377903b34705f178730d96400ccafb736e3d28a4"),
236+
}];
237+
238+
for v in &TEST_VECTORS_SHA256 {
239+
let out = kdfa::<Sha256, Storage, Aes256>(v.key, v.context_u, v.context_v).unwrap();
240+
assert_eq!(out.as_slice(), v.expected);
241+
}
242+
}
243+
}

‎tss-esapi/src/utils/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ use crate::{Context, Error, Result, WrapperErrorKind};
2323
use std::convert::TryFrom;
2424
use zeroize::Zeroize;
2525

26+
#[cfg(feature = "rustcrypto")]
27+
mod credential;
28+
#[cfg(feature = "rustcrypto")]
29+
pub mod kdf;
30+
31+
#[cfg(feature = "rustcrypto")]
32+
pub use self::credential::make_credential_ecc;
33+
#[cfg(all(feature = "rustcrypto", feature = "rsa"))]
34+
pub use self::credential::make_credential_rsa;
35+
2636
/// Create the [Public] structure for a restricted decryption key.
2737
///
2838
/// * `symmetric` - Cipher to be used for decrypting children of the key
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// Copyright 2025 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use tss_esapi::{
5+
abstraction::{ak, ek, AsymmetricAlgorithmSelection},
6+
attributes::SessionAttributesBuilder,
7+
constants::SessionType,
8+
handles::AuthHandle,
9+
interface_types::{
10+
algorithm::{HashingAlgorithm, SignatureSchemeAlgorithm},
11+
ecc::EccCurve,
12+
key_bits::RsaKeyBits,
13+
session_handles::PolicySession,
14+
},
15+
structures::{Digest, SymmetricDefinition},
16+
utils,
17+
};
18+
19+
use elliptic_curve::PublicKey;
20+
use rsa::RsaPublicKey;
21+
22+
use crate::common::create_ctx_without_session;
23+
24+
#[test]
25+
fn test_credential_ecc() {
26+
let mut context = create_ctx_without_session();
27+
28+
let ek_ecc = ek::create_ek_object(
29+
&mut context,
30+
AsymmetricAlgorithmSelection::Ecc(EccCurve::NistP256),
31+
None,
32+
)
33+
.unwrap();
34+
35+
let (ek_pub, _, _) = context.read_public(ek_ecc).unwrap();
36+
37+
let ak_res = ak::create_ak(
38+
&mut context,
39+
ek_ecc,
40+
HashingAlgorithm::Sha384,
41+
AsymmetricAlgorithmSelection::Ecc(EccCurve::NistP384),
42+
SignatureSchemeAlgorithm::EcDsa,
43+
None,
44+
None,
45+
)
46+
.unwrap();
47+
48+
let ak_ecc = ak::load_ak(
49+
&mut context,
50+
ek_ecc,
51+
None,
52+
ak_res.out_private,
53+
ak_res.out_public,
54+
)
55+
.unwrap();
56+
57+
let (_, key_name, _) = context.read_public(ak_ecc).unwrap();
58+
let cred = vec![1, 2, 3, 4, 5];
59+
let expected = Digest::try_from(vec![1, 2, 3, 4, 5]).unwrap();
60+
61+
let (credential_blob, secret) = utils::make_credential_ecc::<_, sha2::Sha256, aes::Aes128>(
62+
PublicKey::<p256::NistP256>::try_from(&ek_pub).unwrap(),
63+
&cred,
64+
key_name,
65+
)
66+
.expect("Create credential");
67+
68+
let (session_attributes, session_attributes_mask) = SessionAttributesBuilder::new().build();
69+
let session_1 = context
70+
.start_auth_session(
71+
None,
72+
None,
73+
None,
74+
SessionType::Hmac,
75+
SymmetricDefinition::AES_256_CFB,
76+
HashingAlgorithm::Sha256,
77+
)
78+
.expect("Failed to call start_auth_session")
79+
.expect("Failed invalid session value");
80+
context
81+
.tr_sess_set_attributes(session_1, session_attributes, session_attributes_mask)
82+
.unwrap();
83+
84+
let session_2 = context
85+
.start_auth_session(
86+
None,
87+
None,
88+
None,
89+
SessionType::Policy,
90+
SymmetricDefinition::AES_256_CFB,
91+
HashingAlgorithm::Sha256,
92+
)
93+
.expect("Failed to call start_auth_session")
94+
.expect("Failed invalid session value");
95+
context
96+
.tr_sess_set_attributes(session_2, session_attributes, session_attributes_mask)
97+
.expect("Failed to call tr_sess_set_attributes");
98+
99+
let _ = context
100+
.execute_with_session(Some(session_1), |ctx| {
101+
ctx.policy_secret(
102+
PolicySession::try_from(session_2)
103+
.expect("Failed to convert auth session to policy session"),
104+
AuthHandle::Endorsement,
105+
Default::default(),
106+
Default::default(),
107+
Default::default(),
108+
None,
109+
)
110+
})
111+
.unwrap();
112+
113+
context.set_sessions((Some(session_1), Some(session_2), None));
114+
let decrypted = context
115+
.activate_credential(ak_ecc, ek_ecc, credential_blob, secret)
116+
.unwrap();
117+
118+
assert_eq!(expected, decrypted);
119+
120+
context.flush_context(ek_ecc.into()).unwrap();
121+
context.flush_context(ak_ecc.into()).unwrap();
122+
}
123+
124+
#[test]
125+
fn test_credential_rsa() {
126+
let mut context = create_ctx_without_session();
127+
128+
let ek_rsa = ek::create_ek_object(
129+
&mut context,
130+
AsymmetricAlgorithmSelection::Rsa(RsaKeyBits::Rsa2048),
131+
None,
132+
)
133+
.unwrap();
134+
135+
let (ek_pub, _, _) = context.read_public(ek_rsa).unwrap();
136+
137+
let ak_res = ak::create_ak(
138+
&mut context,
139+
ek_rsa,
140+
HashingAlgorithm::Sha256,
141+
AsymmetricAlgorithmSelection::Rsa(RsaKeyBits::Rsa2048),
142+
SignatureSchemeAlgorithm::RsaPss,
143+
None,
144+
None,
145+
)
146+
.unwrap();
147+
148+
let ak_rsa = ak::load_ak(
149+
&mut context,
150+
ek_rsa,
151+
None,
152+
ak_res.out_private,
153+
ak_res.out_public,
154+
)
155+
.unwrap();
156+
157+
let (_, key_name, _) = context.read_public(ak_rsa).unwrap();
158+
let cred = vec![1, 2, 3, 4, 5];
159+
let expected = Digest::try_from(vec![1, 2, 3, 4, 5]).unwrap();
160+
161+
let (credential_blob, secret) = utils::make_credential_rsa::<sha2::Sha256, aes::Aes128>(
162+
&RsaPublicKey::try_from(&ek_pub).unwrap(),
163+
&cred,
164+
key_name,
165+
)
166+
.expect("Create credential");
167+
168+
let (session_attributes, session_attributes_mask) = SessionAttributesBuilder::new().build();
169+
let session_1 = context
170+
.start_auth_session(
171+
None,
172+
None,
173+
None,
174+
SessionType::Hmac,
175+
SymmetricDefinition::AES_256_CFB,
176+
HashingAlgorithm::Sha256,
177+
)
178+
.expect("Failed to call start_auth_session")
179+
.expect("Failed invalid session value");
180+
context
181+
.tr_sess_set_attributes(session_1, session_attributes, session_attributes_mask)
182+
.unwrap();
183+
184+
let session_2 = context
185+
.start_auth_session(
186+
None,
187+
None,
188+
None,
189+
SessionType::Policy,
190+
SymmetricDefinition::AES_256_CFB,
191+
HashingAlgorithm::Sha256,
192+
)
193+
.expect("Failed to call start_auth_session")
194+
.expect("Failed invalid session value");
195+
context
196+
.tr_sess_set_attributes(session_2, session_attributes, session_attributes_mask)
197+
.expect("Failed to call tr_sess_set_attributes");
198+
199+
let _ = context
200+
.execute_with_session(Some(session_1), |ctx| {
201+
ctx.policy_secret(
202+
PolicySession::try_from(session_2)
203+
.expect("Failed to convert auth session to policy session"),
204+
AuthHandle::Endorsement,
205+
Default::default(),
206+
Default::default(),
207+
Default::default(),
208+
None,
209+
)
210+
})
211+
.unwrap();
212+
213+
context.set_sessions((Some(session_1), Some(session_2), None));
214+
let decrypted = context
215+
.activate_credential(ak_rsa, ek_rsa, credential_blob, secret)
216+
.unwrap();
217+
218+
assert_eq!(expected, decrypted);
219+
220+
context.flush_context(ek_rsa.into()).unwrap();
221+
context.flush_context(ak_rsa.into()).unwrap();
222+
}

‎tss-esapi/tests/integration_tests/abstraction_tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2021 Contributors to the Parsec project.
22
// SPDX-License-Identifier: Apache-2.0
33
mod ak_tests;
4+
mod credential_tests;
45
mod ek_tests;
56
mod no_tpm;
67
mod nv_tests;

0 commit comments

Comments
 (0)
Please sign in to comment.