Skip to content

Commit fe9fdd5

Browse files
committed
Create credentials
Signed-off-by: Arthur Gautier <[email protected]>
1 parent e7352e7 commit fe9fdd5

File tree

7 files changed

+792
-4
lines changed

7 files changed

+792
-4
lines changed

Cargo.toml

+19-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,22 @@ p192 = { git = "https://github.com/RustCrypto/elliptic-curves.git" }
77
p224 = { git = "https://github.com/RustCrypto/elliptic-curves.git" }
88
sm2 = { git = "https://github.com/RustCrypto/elliptic-curves.git" }
99

10-
rsa = { git = "https://github.com/RustCrypto/RSA.git" }
10+
# https://github.com/RustCrypto/KDFs/pull/108
11+
concat-kdf = { git = "https://github.com/RustCrypto/KDFs.git" }
12+
13+
cfb-mode = { git = "https://github.com/RustCrypto/block-modes.git" }
14+
15+
# https://github.com/RustCrypto/RSA/pull/467
16+
rsa = { git = "https://github.com/baloo/RSA.git", branch = "baloo/oaep/non-string-label" }
17+
18+
# https://github.com/RustCrypto/traits/issues/1738
19+
# https://github.com/RustCrypto/traits/pull/1742
20+
# Pending release of crypto-common 0.2.0-rc.2
21+
# Pending release of digest 0.11.0-rc.0
22+
crypto-common = { git = "https://github.com/RustCrypto/traits.git" }
23+
digest = { git = "https://github.com/RustCrypto/traits.git" }
24+
25+
# https://github.com/RustCrypto/block-ciphers/pull/465
26+
aes = { git = "https://github.com/RustCrypto/block-ciphers.git" }
27+
# Not actually in the graph, but a consumer should use the weak key detection there.
28+
# des = { git = "https://github.com/RustCrypto/block-ciphers.git" }

tss-esapi/Cargo.toml

+12-3
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-pre.2", optional = true }
39+
cfb-mode = { version = "0.9.0-pre", optional = true }
3840
ecdsa = { version = "0.17.0-pre.9", features = ["der", "hazmat", "arithmetic", "verifying"], optional = true }
3941
elliptic-curve = { version = "0.14.0-rc.1", optional = true, features = ["alloc", "pkcs8"] }
42+
hmac = { version = "0.13.0-pre.4", 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.2", optional = true }
@@ -50,15 +53,21 @@ sha3 = { version = "0.11.0-pre.4", optional = true }
5053
sm2 = { version = "0.14.0-pre", optional = true }
5154
sm3 = { version = "0.5.0-pre.4", optional = true }
5255
digest = { version = "0.11.0-pre.9", optional = true }
56+
kbkdf = { version = "0.0.1", optional = true }
57+
concat-kdf = { version = "0.2.0-pre", optional = true }
5358
signature = { version = "2.3.0-pre.4", features = ["std"], 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.2.11"
64+
rand = "0.8"
5965

6066
[dev-dependencies]
67+
aes = "0.9.0-pre.2"
6168
env_logger = "0.11.5"
69+
hex-literal = "0.4.1"
70+
rsa = { version = "0.10.0-pre.3" }
6271
serde_json = "^1.0.108"
6372
sha2 = { version = "0.11.0-pre.4", 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,6 +88,5 @@ 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", "pkcs8", "signature", "x509-cert"]
92+
rustcrypto-full = ["rustcrypto", "aes", "p192", "p224", "p256", "p384", "p521", "rsa", "sha1", "sha2", "sha3", "sm2", "sm3"]

tss-esapi/src/utils/credential.rs

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

0 commit comments

Comments
 (0)