Skip to content

Commit 31926a7

Browse files
committed
Create credentials
Signed-off-by: Arthur Gautier <[email protected]>
1 parent 7f416f2 commit 31926a7

File tree

9 files changed

+1257
-38
lines changed

9 files changed

+1257
-38
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ p256 = { git = "https://github.com/RustCrypto/elliptic-curves.git" }
1010
p384 = { git = "https://github.com/RustCrypto/elliptic-curves.git" }
1111
p521 = { git = "https://github.com/RustCrypto/elliptic-curves.git" }
1212
sm2 = { git = "https://github.com/RustCrypto/elliptic-curves.git" }
13+
14+
concat-kdf = { git = "https://github.com/RustCrypto/KDFs.git" }
15+
16+
rsa = { git = "https://github.com/RustCrypto/RSA.git" }

tss-esapi/Cargo.toml

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,13 @@ num-derive = "0.4.2"
3232
num-traits = "0.2.12"
3333
hostname-validator = "1.1.0"
3434
regex = "1.3.9"
35-
zeroize = { version = "1.5.7", features = ["zeroize_derive"] }
35+
zeroize = { version = "1.8.2", features = ["zeroize_derive"] }
3636
tss-esapi-sys = { path = "../tss-esapi-sys", version = "0.5.0" }
3737
x509-cert = { version = "0.3.0-rc.1", optional = true }
38+
aes = { version = "0.9.0-rc.1", optional = true }
39+
cipher = { version = "0.5.0-rc.1", optional = true }
40+
cfb-mode = { version = "0.9.0-rc.1", optional = true }
41+
proc-concat-bytes = { version = "0.1.0", optional = true }
3842
ecdsa = { version = "0.17.0-rc.0", features = [
3943
"der",
4044
"hazmat",
@@ -45,6 +49,7 @@ elliptic-curve = { version = "0.14.0-rc.15", optional = true, features = [
4549
"alloc",
4650
"pkcs8",
4751
] }
52+
hmac = { version = "0.13.0-rc.0", optional = true }
4853
p192 = { version = "0.14.0-pre", optional = true }
4954
p224 = { version = "0.14.0-pre", optional = true }
5055
p256 = { version = "0.14.0-pre.11", optional = true }
@@ -62,14 +67,20 @@ signature = { version = "3.0.0-rc.0", features = [
6267
"alloc",
6368
"digest",
6469
], optional = true }
70+
kbkdf = { version = "0.0.1", optional = true }
71+
concat-kdf = { version = "0.2.0-pre", optional = true }
6572
cfg-if = "1.0.0"
6673
strum = { version = "0.26.3", optional = true }
6774
strum_macros = { version = "0.26.4", optional = true }
6875
paste = "1.0.14"
6976
getrandom = "0.3"
77+
rand = "0.9"
7078

7179
[dev-dependencies]
80+
aes = "0.9.0-pre.2"
7281
env_logger = "0.11.5"
82+
hex-literal = "1"
83+
rsa = { version = "0.10.0-pre.3" }
7384
serde_json = "^1.0.108"
7485
sha2 = { version = "0.11.0-rc.2", features = ["oid"] }
7586
tss-esapi = { path = ".", features = [
@@ -89,16 +100,24 @@ default = ["abstraction"]
89100
generate-bindings = ["tss-esapi-sys/generate-bindings"]
90101
abstraction = ["rustcrypto"]
91102
integration-tests = ["strum", "strum_macros"]
103+
92104
rustcrypto = [
105+
"cfb-mode",
106+
"cipher",
107+
"concat-kdf",
93108
"digest",
94109
"ecdsa",
95-
"elliptic-curve",
110+
"elliptic-curve/ecdh",
111+
"hmac",
112+
"kbkdf",
96113
"pkcs8",
114+
"proc-concat-bytes",
97115
"signature",
98116
"x509-cert",
99117
]
100118
rustcrypto-full = [
101119
"rustcrypto",
120+
"aes",
102121
"p192",
103122
"p224",
104123
"p256",
@@ -111,6 +130,8 @@ rustcrypto-full = [
111130
"sm2",
112131
"sm3",
113132
]
133+
134+
rsa = ["dep:rsa", "kbkdf"]
114135
sha1 = ["dep:sha1", "rsa?/sha1"]
115136
sha2 = ["dep:sha2", "rsa?/sha2"]
116137
bundled = ["tss-esapi-sys/bundled"]

tss-esapi/src/utils/credential.rs

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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+
sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint},
22+
AffinePoint, Curve, CurveArithmetic, FieldBytesSize, PublicKey,
23+
};
24+
use hmac::{EagerHash, Hmac};
25+
use log::error;
26+
use rand::rng;
27+
use zeroize::Zeroizing;
28+
29+
#[cfg(feature = "rsa")]
30+
use rsa::RsaPublicKey;
31+
32+
use crate::{
33+
error::{Error, Result, WrapperErrorKind},
34+
structures::{EncryptedSecret, IdObject, Name},
35+
utils::{kdf, secret_sharing},
36+
};
37+
38+
type WeakResult<T> = core::result::Result<T, WeakKeyError>;
39+
40+
// [`TpmHmac`] intends to code for the key expected for hmac
41+
// in the KDFa and KDFe derivations. There are no standard sizes for hmac keys really,
42+
// upstream RustCrypto considers it to be [BlockSize], but TPM specification
43+
// has a different opinion on the matter, and expect the key to the output
44+
// bit size of the hash algorithm used.
45+
//
46+
// See https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=202
47+
// section 24.5 HMAC:
48+
// bits the number of bits in the digest produced by ekNameAlg
49+
//
50+
// [BlockSize]: https://docs.rs/hmac/0.12.1/hmac/struct.HmacCore.html#impl-KeySizeUser-for-HmacCore%3CD%3E
51+
struct TpmHmac<H>(PhantomData<H>);
52+
53+
impl<H> KeySizeUser for TpmHmac<H>
54+
where
55+
H: OutputSizeUser,
56+
{
57+
type KeySize = H::OutputSize;
58+
}
59+
60+
/// [`make_credential_ecc`] creates a credential that will only be decrypted by the target
61+
/// elliptic-curve EK.
62+
///
63+
/// # Parameters
64+
///
65+
/// * `ek_public` is the EC Public key of the Endorsement Key,
66+
/// * `secret` is the serialization of the credential,
67+
/// * `name` will usually be the AK held on the TPM.
68+
pub fn make_credential_ecc<C, EkHash, EkCipher>(
69+
ek_public: PublicKey<C>,
70+
secret: &[u8],
71+
key_name: Name,
72+
) -> Result<(IdObject, EncryptedSecret)>
73+
where
74+
C: Curve + CurveArithmetic,
75+
76+
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
77+
FieldBytesSize<C>: ModulusSize,
78+
79+
<FieldBytesSize<C> as Add>::Output: Add<FieldBytesSize<C>>,
80+
Sum<FieldBytesSize<C>, FieldBytesSize<C>>: ArraySize,
81+
Sum<FieldBytesSize<C>, FieldBytesSize<C>>: Add<U8>,
82+
Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U8>: Add<B1>,
83+
Add1<Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U8>>: ArraySize,
84+
85+
EkHash: Digest + FixedOutputReset + EagerHash,
86+
<EkHash as OutputSizeUser>::OutputSize: Mul<U8>,
87+
<<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
88+
<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,
89+
<<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
90+
91+
EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,
92+
<EkCipher as KeySizeUser>::KeySize: Mul<U8>,
93+
<<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,
94+
{
95+
let mut rng = rng();
96+
97+
loop {
98+
let (seed, encrypted_secret) = secret_sharing::secret_sharing_ecc_curve::<
99+
_,
100+
kdf::Identity,
101+
C,
102+
TpmHmac<EkHash>,
103+
EkHash,
104+
>(&mut rng, &ek_public)?;
105+
106+
match secret_to_credential::<EkHash, EkCipher>(&seed, secret, &key_name)? {
107+
Ok(id_object) => return Ok((id_object, encrypted_secret)),
108+
Err(WeakKeyError) => {
109+
// 11.4.10.4 Rejection of weak keys
110+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82
111+
112+
// The Key was considered weak, and we should re-run the creation of the encrypted
113+
// secret.
114+
continue;
115+
}
116+
}
117+
}
118+
}
119+
120+
/// [`make_credential_rsa`] creates a credential that will only be decrypted by the target RSA EK.
121+
///
122+
/// # Parameters
123+
///
124+
/// * `ek_public` is the RSA Public key of the Endorsement Key,
125+
/// * `secret` is the serialization of the credential,
126+
/// * `name` will usually be the AK held on the TPM.
127+
#[cfg(feature = "rsa")]
128+
pub fn make_credential_rsa<EkHash, EkCipher>(
129+
ek_public: &RsaPublicKey,
130+
secret: &[u8],
131+
key_name: Name,
132+
) -> Result<(IdObject, EncryptedSecret)>
133+
where
134+
EkHash: Digest + EagerHash + FixedOutputReset,
135+
<EkHash as OutputSizeUser>::OutputSize: Mul<U8>,
136+
<<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
137+
<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,
138+
<<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
139+
140+
EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,
141+
<EkCipher as KeySizeUser>::KeySize: Mul<U8>,
142+
<<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,
143+
{
144+
let mut rng = rng();
145+
146+
loop {
147+
let (random_seed, encrypted_secret) =
148+
secret_sharing::secret_sharing_rsa::<_, kdf::Identity, TpmHmac<EkHash>, EkHash>(
149+
&mut rng, ek_public,
150+
)?;
151+
152+
match secret_to_credential::<EkHash, EkCipher>(&random_seed, secret, &key_name)? {
153+
Ok(id_object) => return Ok((id_object, encrypted_secret)),
154+
Err(WeakKeyError) => {
155+
// 11.4.10.4 Rejection of weak keys
156+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82
157+
158+
// The Key was considered weak, and we should re-run the creation of the encrypted
159+
// secret.
160+
continue;
161+
}
162+
}
163+
}
164+
}
165+
166+
fn secret_to_credential<EkHash, EkCipher>(
167+
seed: &Key<TpmHmac<EkHash>>,
168+
secret: &[u8],
169+
key_name: &Name,
170+
) -> Result<WeakResult<IdObject>>
171+
where
172+
EkHash: Digest + EagerHash + FixedOutputReset,
173+
<EkHash as OutputSizeUser>::OutputSize: Mul<U8>,
174+
<<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
175+
<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,
176+
<<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
177+
178+
EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,
179+
<EkCipher as KeySizeUser>::KeySize: Mul<U8>,
180+
<<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,
181+
{
182+
// Prepare the sensitive data
183+
// this will be then encrypted using AES-CFB (size of the symmetric key depends on the EK).
184+
let mut sensitive_data = {
185+
let mut out = Zeroizing::new(vec![]);
186+
out.extend_from_slice(
187+
&u16::try_from(secret.len())
188+
.map_err(|_| {
189+
error!("secret may only be 2^16 bytes long");
190+
Error::local_error(WrapperErrorKind::WrongParamSize)
191+
})?
192+
.to_be_bytes()[..],
193+
);
194+
out.extend_from_slice(secret);
195+
out
196+
};
197+
198+
// We'll now encrypt the sensitive data, and hmac the result of the encryption
199+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=201
200+
// See 24.4 Symmetric Encryption
201+
let sym_key = kdf::kdfa::<EkHash, kdf::Storage, EkCipher>(seed, key_name.value(), &[])?;
202+
203+
if EkCipher::weak_key_test(&sym_key).is_err() {
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+
// The Key was considered weak, and we should re-run the creation of the encrypted
207+
// secret.
208+
209+
return Ok(Err(WeakKeyError));
210+
}
211+
212+
let iv: Iv<cfb_mode::Encryptor<EkCipher>> = Default::default();
213+
214+
cfb_mode::Encryptor::<EkCipher>::new(&sym_key, &iv).encrypt(&mut sensitive_data);
215+
216+
// See 24.5 HMAC
217+
let hmac_key = kdf::kdfa::<EkHash, kdf::Integrity, TpmHmac<EkHash>>(seed, &[], &[])?;
218+
let mut hmac = Hmac::<EkHash>::new_from_slice(&hmac_key).map_err(|e| {
219+
error!("HMAC initialization error: {e}");
220+
Error::local_error(WrapperErrorKind::WrongParamSize)
221+
})?;
222+
Mac::update(&mut hmac, &sensitive_data);
223+
Mac::update(&mut hmac, key_name.value());
224+
let hmac = hmac.finalize();
225+
226+
// We'll now serialize the object and get everything through the door.
227+
let mut out = vec![];
228+
out.extend_from_slice(
229+
&u16::try_from(hmac.into_bytes().len())
230+
.map_err(|_| {
231+
// NOTE: this shouldn't ever trigger ... but ...
232+
error!("HMAC output may only be 2^16 bytes long");
233+
Error::local_error(WrapperErrorKind::WrongParamSize)
234+
})?
235+
.to_be_bytes()[..],
236+
);
237+
out.extend_from_slice(&hmac.into_bytes());
238+
out.extend_from_slice(&sensitive_data);
239+
240+
IdObject::from_bytes(&out).map(Ok)
241+
}

0 commit comments

Comments
 (0)