Skip to content

Commit 5238f04

Browse files
committed
Create credentials
Signed-off-by: Arthur Gautier <arthur.gautier@arista.com>
1 parent 2353bac commit 5238f04

File tree

9 files changed

+1280
-122
lines changed

9 files changed

+1280
-122
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[workspace]
22
resolver = "2"
33
members = ["tss-esapi", "tss-esapi-sys"]
4+
5+
[patch.crates-io]
6+
rsa = { git = "https://github.com/RustCrypto/RSA.git" }

tss-esapi/Cargo.toml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ regex = "1.3.9"
3535
zeroize = { version = "1.8.2", features = ["zeroize_derive"] }
3636
tss-esapi-sys = { path = "../tss-esapi-sys", version = "0.6.0-alpha.1" }
3737
x509-cert = { version = "0.3.0-rc.1", optional = true }
38+
aes = { version = "0.9.0-rc.2", optional = true }
39+
byte-strings = { version = "0.3.1", optional = true }
40+
cipher = { version = "0.5.0-rc.2", optional = true }
41+
cfb-mode = { version = "0.9.0-rc.1", optional = true }
3842
ecdsa = { version = "0.17.0-rc.8", features = [
3943
"algorithm",
4044
"der",
@@ -44,6 +48,7 @@ elliptic-curve = { version = "0.14.0-rc.15", optional = true, features = [
4448
"alloc",
4549
"pkcs8",
4650
] }
51+
hmac = { version = "0.13.0-rc.2", optional = true }
4752
p192 = { version = "0.14.0-rc.0", optional = true }
4853
p224 = { version = "0.14.0-rc.0", optional = true }
4954
p256 = { version = "0.14.0-rc.0", optional = true }
@@ -61,14 +66,20 @@ signature = { version = "=3.0.0-rc.4", features = [
6166
"alloc",
6267
"digest",
6368
], optional = true }
69+
kbkdf = { version = "0.0.1", optional = true }
70+
one-step-kdf = { version = "0.1.0-rc.0", optional = true }
6471
cfg-if = "1.0.0"
6572
strum = { version = "0.26.3", optional = true }
6673
strum_macros = { version = "0.26.4", optional = true }
6774
paste = "1.0.14"
6875
getrandom = "0.3"
76+
rand = "0.9"
6977

7078
[dev-dependencies]
79+
aes = "0.9.0-pre.2"
7180
env_logger = "0.11.5"
81+
hex-literal = "1"
82+
rsa = { version = "0.10.0-pre.3" }
7283
serde_json = "^1.0.108"
7384
sha2 = { version = "0.11.0-rc.2", features = ["oid"] }
7485
tss-esapi = { path = ".", features = [
@@ -88,16 +99,24 @@ default = ["abstraction"]
8899
generate-bindings = ["tss-esapi-sys/generate-bindings"]
89100
abstraction = ["rustcrypto"]
90101
integration-tests = ["strum", "strum_macros"]
102+
91103
rustcrypto = [
104+
"byte-strings",
105+
"cfb-mode",
106+
"cipher",
107+
"one-step-kdf",
92108
"digest",
93109
"ecdsa",
94-
"elliptic-curve",
110+
"elliptic-curve/ecdh",
111+
"hmac",
112+
"kbkdf",
95113
"pkcs8",
96114
"signature",
97115
"x509-cert",
98116
]
99117
rustcrypto-full = [
100118
"rustcrypto",
119+
"aes",
101120
"p192",
102121
"p224",
103122
"p256",
@@ -110,6 +129,8 @@ rustcrypto-full = [
110129
"sm2",
111130
"sm3",
112131
]
132+
133+
rsa = ["dep:rsa", "kbkdf"]
113134
sha1 = ["dep:sha1", "rsa?/sha1"]
114135
sha2 = ["dep:sha2", "rsa?/sha2"]
115136
bundled = ["tss-esapi-sys/bundled"]

tss-esapi/src/utils/credential.rs

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

0 commit comments

Comments
 (0)