Skip to content

Commit c652a60

Browse files
committed
WIP: Create credentials
Signed-off-by: Arthur Gautier <[email protected]>
1 parent 995ca94 commit c652a60

File tree

7 files changed

+750
-2
lines changed

7 files changed

+750
-2
lines changed

Cargo.toml

+8
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,11 @@ 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+
# https://github.com/RustCrypto/KDFs/pull/108
11+
kbkdf = { git = "https://github.com/TheBestTvarynka/KDFs.git", branch = "feat/kbkdf" }
12+
concat-kdf = { git = "https://github.com/RustCrypto/KDFs.git" }
13+
14+
cfb-mode = { git = "https://github.com/RustCrypto/block-modes.git" }
15+
16+
# https://github.com/RustCrypto/RSA/pull/467
17+
rsa = { git = "https://github.com/baloo/RSA.git", branch = "baloo/oaep/non-string-label" }

tss-esapi/Cargo.toml

+12-2
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 }
@@ -48,16 +51,22 @@ sha2 = { version = "0.11.0-pre.4", optional = true }
4851
sha3 = { version = "0.11.0-pre.4", optional = true }
4952
sm2 = { version = "0.14.0-pre", optional = true }
5053
sm3 = { version = "0.5.0-pre.4", optional = true }
54+
kbkdf = { version = "0.1.0", optional = true }
55+
concat-kdf = { version = "0.2.0-pre", optional = true }
5156
digest = "0.11.0-pre.9"
5257
signature = { version = "2.3.0-pre.4", features = ["std"], optional = true}
5358
cfg-if = "1.0.0"
5459
strum = { version = "0.26.3", optional = true }
5560
strum_macros = { version = "0.26.4", optional = true }
5661
paste = "1.0.14"
5762
getrandom = "0.2.11"
63+
rand = "0.8"
5864

5965
[dev-dependencies]
66+
aes = "0.9.0-pre.2"
6067
env_logger = "0.11.5"
68+
hex-literal = "0.4.1"
69+
rsa = { version = "0.10.0-pre.3" }
6170
serde_json = "^1.0.108"
6271
sha2 = { version = "0.11.0-pre.4", features = ["oid"] }
6372
tss-esapi = { path = ".", features = [
@@ -66,6 +75,7 @@ tss-esapi = { path = ".", features = [
6675
"abstraction",
6776
"rustcrypto-full",
6877
] }
78+
p256 = { version = "0.14.0-pre.2", features = ["ecdh"] }
6979
x509-cert = { version = "0.3.0-pre.0", features = ["builder"] }
7080

7181
[build-dependencies]
@@ -77,6 +87,6 @@ generate-bindings = ["tss-esapi-sys/generate-bindings"]
7787
abstraction = ["rustcrypto"]
7888
integration-tests = ["strum", "strum_macros"]
7989

80-
rustcrypto = ["ecdsa", "elliptic-curve", "signature", "x509-cert"]
81-
rustcrypto-full = ["rustcrypto", "p192", "p224", "p256", "p384", "p521", "rsa", "sha1", "sha2", "sha3", "sm2", "sm3"]
90+
rustcrypto = ["cfb-mode", "concat-kdf", "ecdsa", "elliptic-curve", "elliptic-curve/ecdh", "hmac", "kbkdf", "signature", "x509-cert"]
91+
rustcrypto-full = ["rustcrypto", "aes", "p192", "p224", "p256", "p384", "p521", "rsa", "sha1", "sha2", "sha3", "sm2", "sm3"]
8292

tss-esapi/src/utils/credential.rs

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

0 commit comments

Comments
 (0)