Skip to content

Commit ce34f79

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

File tree

7 files changed

+781
-3
lines changed

7 files changed

+781
-3
lines changed

Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ p384 = { git = "https://github.com/baloo/elliptic-curves.git", branch = "baloo/g
1010
p521 = { git = "https://github.com/baloo/elliptic-curves.git", branch = "baloo/group/try-from-rng" }
1111
sm2 = { git = "https://github.com/baloo/elliptic-curves.git", branch = "baloo/group/try-from-rng" }
1212

13+
# https://github.com/RustCrypto/KDFs/pull/108
14+
concat-kdf = { git = "https://github.com/RustCrypto/KDFs.git" }
15+
16+
cfb-mode = { git = "https://github.com/RustCrypto/block-modes.git" }
17+
18+
# https://github.com/RustCrypto/RSA/pull/467
1319
rsa = { git = "https://github.com/RustCrypto/RSA.git" }
20+
1421
crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint.git" }
1522
crypto-primes = { git = "https://github.com/entropyxyz/crypto-primes.git" }
1623

@@ -21,9 +28,12 @@ x509-cert = { git = "https://github.com/baloo/formats.git", branch = "baloo/reun
2128

2229
elliptic-curve = { git = "https://github.com/RustCrypto/traits.git" }
2330
signature = { git = "https://github.com/RustCrypto/traits.git" }
31+
#crypto-common = { git = "https://github.com/RustCrypto/traits.git" }
32+
#digest = { git = "https://github.com/RustCrypto/traits.git" }
2433

2534
hmac = { git = "https://github.com/RustCrypto/MACs.git" }
2635

2736
ff = { git = "https://github.com/zkcrypto/ff.git", branch = "release-0.14.0" }
2837
group = { git = "https://github.com/baloo/group.git", branch = "baloo/try_from_rng" }
2938

39+
aes = { git = "https://github.com/RustCrypto/block-ciphers.git" }

tss-esapi/Cargo.toml

Lines changed: 13 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-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.5", 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.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 = "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,8 +88,8 @@ 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", "pkcs8", "signature", "x509-cert"]
92+
rustcrypto-full = ["rustcrypto", "aes", "p192", "p224", "p256", "p384", "p521", "rsa", "sha1", "sha2", "sha3", "sm2", "sm3"]
93+
8494
sha1 = ["dep:sha1", "rsa?/sha1"]
8595
sha2 = ["dep:sha2", "rsa?/sha2"]

tss-esapi/src/utils/credential.rs

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
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::{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 = 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 = 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.fill(out.as_mut_slice());
169+
out
170+
};
171+
172+
// The random seed is then encrypted with RSA-OAEP
173+
//
174+
// B.4 RSAES_OAEP
175+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=297
176+
//
177+
// The label is a byte-stream whose last byte must be zero
178+
//
179+
// B.10.4. RSA Secret Sharing for Credentials
180+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=302
181+
//
182+
// The label is going to be "IDENTITY" for secret sharing.
183+
let encrypted_seed = {
184+
let padding = Oaep::new_with_label::<EkHash, _>(b"IDENTITY\0".to_vec());
185+
ek_public
186+
.encrypt(&mut rng, padding, &random_seed[..])
187+
.map_err(|e| {
188+
error!("RSA OAEP encryption error: {e}");
189+
Error::local_error(WrapperErrorKind::InternalError)
190+
})?
191+
};
192+
let encrypted_secret = EncryptedSecret::from_bytes(&encrypted_seed)?;
193+
194+
match secret_to_credential::<EkHash, EkCipher>(random_seed, secret, &key_name)? {
195+
Ok(id_object) => return Ok((id_object, encrypted_secret)),
196+
Err(WeakKeyError) => {
197+
// 11.4.10.4 Rejection of weak keys
198+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82
199+
200+
// The Key was considered weak, and we should re-run the creation of the encrypted
201+
// secret.
202+
continue;
203+
}
204+
}
205+
}
206+
}
207+
208+
fn secret_to_credential<EkHash, EkCipher>(
209+
seed: Key<TpmHmac<EkHash>>,
210+
secret: &[u8],
211+
key_name: &Name,
212+
) -> Result<WeakResult<IdObject>>
213+
where
214+
EkHash: Digest + EagerHash + FixedOutputReset,
215+
<EkHash as OutputSizeUser>::OutputSize: Mul<U8>,
216+
<<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
217+
<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,
218+
<<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
219+
220+
EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,
221+
<EkCipher as KeySizeUser>::KeySize: Mul<U8>,
222+
<<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,
223+
{
224+
// Prepare the sensitive data
225+
// this will be then encrypted using AES-CFB (size of the symmetric key depends on the EK).
226+
// NOTE(security): no need to zeroize it, content is rewritten in place with the encrypted version
227+
let mut sensitive_data = {
228+
let mut out = vec![];
229+
out.extend_from_slice(
230+
&u16::try_from(secret.len())
231+
.map_err(|_| {
232+
error!("secret may only be 2^16 bytes long");
233+
Error::local_error(WrapperErrorKind::WrongParamSize)
234+
})?
235+
.to_be_bytes()[..],
236+
);
237+
out.extend_from_slice(secret);
238+
out
239+
};
240+
241+
// We'll now encrypt the sensitive data, and hmac the result of the encryption
242+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=201
243+
// See 24.4 Symmetric Encryption
244+
let sym_key = kdf::kdfa::<EkHash, kdf::Storage, EkCipher>(&seed, key_name.value(), &[])?;
245+
246+
if EkCipher::weak_key_test(&sym_key).is_ok() {
247+
// 11.4.10.4 Rejection of weak keys
248+
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82
249+
// The Key was considered weak, and we should re-run the creation of the encrypted
250+
// secret.
251+
252+
return Ok(Err(WeakKeyError));
253+
}
254+
255+
let iv: Iv<cfb_mode::Encryptor<EkCipher>> = Default::default();
256+
257+
cfb_mode::Encryptor::<EkCipher>::new(&sym_key, &iv).encrypt(&mut sensitive_data);
258+
259+
// See 24.5 HMAC
260+
let hmac_key = kdf::kdfa::<EkHash, kdf::Integrity, TpmHmac<EkHash>>(&seed, &[], &[])?;
261+
let mut hmac = Hmac::<EkHash>::new_from_slice(&hmac_key).map_err(|e| {
262+
error!("HMAC initialization error: {e}");
263+
Error::local_error(WrapperErrorKind::WrongParamSize)
264+
})?;
265+
Mac::update(&mut hmac, &sensitive_data);
266+
Mac::update(&mut hmac, key_name.value());
267+
let hmac = hmac.finalize();
268+
269+
// We'll now serialize the object and get everything through the door.
270+
let mut out = vec![];
271+
out.extend_from_slice(
272+
&u16::try_from(hmac.into_bytes().len())
273+
.map_err(|_| {
274+
// NOTE: this shouldn't ever trigger ... but ...
275+
error!("HMAC output may only be 2^16 bytes long");
276+
Error::local_error(WrapperErrorKind::WrongParamSize)
277+
})?
278+
.to_be_bytes()[..],
279+
);
280+
out.extend_from_slice(&hmac.into_bytes());
281+
out.extend_from_slice(&sensitive_data);
282+
283+
IdObject::from_bytes(&out).map(Ok)
284+
}

0 commit comments

Comments
 (0)