Skip to content

Commit 75b7750

Browse files
committed
WIP: Create credentials
1 parent 06ceec2 commit 75b7750

File tree

7 files changed

+465
-1
lines changed

7 files changed

+465
-1
lines changed

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@ 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/baloo/KDFs.git", branch = "baloo/kbkdf/pre-releases" }
12+
concat-kdf = { git = "https://github.com/RustCrypto/KDFs.git" }
13+
14+
cfb-mode = { git = "https://github.com/RustCrypto/block-modes.git" }

tss-esapi/Cargo.toml

+9-1
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ 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+
cfb-mode = { version = "0.9.0-pre", optional = true }
3839
ecdsa = { version = "0.17.0-pre.9", features = ["der", "hazmat", "arithmetic", "verifying"], optional = true }
3940
elliptic-curve = { version = "0.14.0-rc.1", optional = true, features = ["alloc", "pkcs8"] }
41+
hmac = { version = "0.13.0-pre.4", optional = true }
4042
p192 = { version = "0.14.0-pre", optional = true }
4143
p224 = { version = "0.14.0-pre", optional = true }
4244
p256 = { version = "0.14.0-pre.2", optional = true }
@@ -48,16 +50,21 @@ sha2 = { version = "0.11.0-pre.4", optional = true }
4850
sha3 = { version = "0.11.0-pre.4", optional = true }
4951
sm2 = { version = "0.14.0-pre", optional = true }
5052
sm3 = { version = "0.5.0-pre.4", optional = true }
53+
kbkdf = { version = "0.1.0" }
54+
concat-kdf = { version = "0.2.0-pre" }
5155
digest = "0.11.0-pre.9"
5256
signature = { version = "2.3.0-pre.4", features = ["std"], optional = true}
5357
cfg-if = "1.0.0"
5458
strum = { version = "0.26.3", optional = true }
5559
strum_macros = { version = "0.26.4", optional = true }
5660
paste = "1.0.14"
5761
getrandom = "0.2.11"
62+
rand = "0.8"
63+
aes = "0.9.0-pre.2"
5864

5965
[dev-dependencies]
6066
env_logger = "0.11.5"
67+
hex-literal = "0.4.1"
6168
serde_json = "^1.0.108"
6269
sha2 = { version = "0.11.0-pre.4", features = ["oid"] }
6370
tss-esapi = { path = ".", features = [
@@ -66,6 +73,7 @@ tss-esapi = { path = ".", features = [
6673
"abstraction",
6774
"rustcrypto-full",
6875
] }
76+
p256 = { version = "0.14.0-pre.2", features = ["ecdh"] }
6977
x509-cert = { version = "0.3.0-pre.0", features = ["builder"] }
7078

7179
[build-dependencies]
@@ -77,6 +85,6 @@ generate-bindings = ["tss-esapi-sys/generate-bindings"]
7785
abstraction = ["rustcrypto"]
7886
integration-tests = ["strum", "strum_macros"]
7987

80-
rustcrypto = ["ecdsa", "elliptic-curve", "signature", "x509-cert"]
88+
rustcrypto = ["cfb-mode", "ecdsa", "elliptic-curve", "hmac", "signature", "x509-cert"]
8189
rustcrypto-full = ["rustcrypto", "p192", "p224", "p256", "p384", "p521", "rsa", "sha1", "sha2", "sha3", "sm2", "sm3"]
8290

tss-esapi/src/utils/credential.rs

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2019 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use aes::cipher::AsyncStreamCipher;
5+
use digest::{
6+
array::ArraySize,
7+
consts::U9,
8+
crypto_common::{Iv, KeyIvInit},
9+
typenum::operator_aliases::Sum,
10+
KeyInit, Mac,
11+
};
12+
use ecdsa::elliptic_curve::{
13+
ecdh::{EphemeralSecret, SharedSecret},
14+
sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint},
15+
AffinePoint, Curve, CurveArithmetic, FieldBytesSize, PublicKey,
16+
};
17+
use hmac::Hmac;
18+
use rand::thread_rng;
19+
use sha2::Sha256;
20+
use std::ops::Add;
21+
22+
use crate::{
23+
structures::{EncryptedSecret, IdObject, Name},
24+
utils::kdf::{self},
25+
};
26+
27+
pub fn make_credential_ecc<C>(
28+
ek_public: PublicKey<C>,
29+
secret: &[u8],
30+
key_name: Name,
31+
) -> (IdObject, EncryptedSecret)
32+
where
33+
C: Curve + CurveArithmetic,
34+
35+
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
36+
FieldBytesSize<C>: ModulusSize,
37+
38+
<FieldBytesSize<C> as Add>::Output: Add<FieldBytesSize<C>>,
39+
Sum<FieldBytesSize<C>, FieldBytesSize<C>>: ArraySize,
40+
Sum<FieldBytesSize<C>, FieldBytesSize<C>>: Add<U9>,
41+
Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U9>: ArraySize,
42+
{
43+
let mut rng = thread_rng();
44+
45+
let local = EphemeralSecret::<C>::random(&mut rng);
46+
47+
let ecdh_secret: SharedSecret<C> = local.diffie_hellman(&ek_public);
48+
49+
let _ = key_name;
50+
51+
type HmacSha256 = Hmac<Sha256>;
52+
53+
let seed = kdf::kdfe::<kdf::Identity, Sha256, C, aes::Aes256>(
54+
&ecdh_secret,
55+
&local.public_key(),
56+
&ek_public,
57+
);
58+
drop(ecdh_secret);
59+
60+
// The local ECDH pair is used as "encrypted seed"
61+
let encrypted_seed = {
62+
let mut out = vec![];
63+
out.extend_from_slice(&32u16.to_be_bytes()[..]);
64+
out.extend_from_slice(&local.public_key().to_encoded_point(false).x().unwrap());
65+
out.extend_from_slice(&32u16.to_be_bytes()[..]);
66+
out.extend_from_slice(&local.public_key().to_encoded_point(false).y().unwrap());
67+
out
68+
};
69+
70+
let mut sensitive_data = {
71+
let mut out = vec![];
72+
out.extend_from_slice(&u16::try_from(secret.len()).unwrap().to_be_bytes()[..]);
73+
out.extend_from_slice(secret);
74+
out
75+
};
76+
77+
let sym_key = kdf::kdfa::<Sha256, kdf::Storage, aes::Aes128>(&seed, key_name.value(), &[]);
78+
println!("----");
79+
let hmac_key = kdf::kdfa::<Sha256, kdf::Integrity, aes::Aes256>(&seed, &[], &[]);
80+
type Aes128CfbEnc = cfb_mode::Encryptor<aes::Aes128>;
81+
let iv: Iv<Aes128CfbEnc> = Default::default();
82+
83+
Aes128CfbEnc::new(&sym_key.into(), &iv.into()).encrypt(&mut sensitive_data);
84+
85+
let mut hmac = HmacSha256::new_from_slice(&hmac_key).unwrap();
86+
hmac.update(&sensitive_data);
87+
hmac.update(key_name.value());
88+
let hmac = hmac.finalize();
89+
90+
let mut out = vec![];
91+
out.extend_from_slice(
92+
&u16::try_from(hmac.into_bytes().len())
93+
.unwrap()
94+
.to_be_bytes()[..],
95+
);
96+
out.extend_from_slice(&hmac.into_bytes());
97+
out.extend_from_slice(&sensitive_data);
98+
99+
(
100+
IdObject::from_bytes(&out).unwrap(),
101+
EncryptedSecret::from_bytes(&encrypted_seed).unwrap(),
102+
)
103+
}

tss-esapi/src/utils/kdf.rs

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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 digest::{
7+
array::{Array, ArraySize},
8+
consts::{U10, U4, U7, U8, U9},
9+
crypto_common::KeySizeUser,
10+
typenum::{operator_aliases::Sum, Unsigned},
11+
Digest, FixedOutputReset, Key, OutputSizeUser,
12+
};
13+
use ecdsa::elliptic_curve::{
14+
ecdh::SharedSecret,
15+
sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint},
16+
AffinePoint, Curve, CurveArithmetic, FieldBytesSize, PublicKey,
17+
};
18+
use hmac::{EagerHash, Hmac};
19+
use kbkdf::{Counter, Kbkdf};
20+
21+
// Note: until generic_const_expr stabilize, we will have to carry a const parameter on the trait,
22+
// once that's stable, we should be able to do `const LABEL: [u8; Self::LabelSize]`
23+
// Until then, the prefered implementation would be using `impl_kdf_usage` macro, as it should be
24+
// misuse-resistant.
25+
pub trait KdfUsage {
26+
type LabelSize: Unsigned;
27+
const LABEL: &'static [u8];
28+
}
29+
30+
macro_rules! impl_kdf_usage {
31+
($usage:ty, $size: ty, $value: expr) => {
32+
impl KdfUsage for $usage {
33+
type LabelSize = $size;
34+
const LABEL: &'static [u8] = {
35+
let _: [u8; <$size>::USIZE] = *$value;
36+
$value
37+
};
38+
}
39+
};
40+
}
41+
42+
#[derive(Copy, Clone, Debug)]
43+
pub struct Secret;
44+
impl_kdf_usage!(Secret, U7, b"SECRET\0");
45+
46+
#[derive(Copy, Clone, Debug)]
47+
pub struct Context;
48+
impl_kdf_usage!(Context, U8, b"CONTEXT\0");
49+
50+
#[derive(Copy, Clone, Debug)]
51+
pub struct Obfuscate;
52+
impl_kdf_usage!(Obfuscate, U10, b"OBFUSCATE\0");
53+
54+
#[derive(Copy, Clone, Debug)]
55+
pub struct Storage;
56+
impl_kdf_usage!(Storage, U8, b"STORAGE\0");
57+
58+
#[derive(Copy, Clone, Debug)]
59+
pub struct Integrity;
60+
impl_kdf_usage!(Integrity, U10, b"INTEGRITY\0");
61+
62+
#[derive(Copy, Clone, Debug)]
63+
pub struct Commit;
64+
impl_kdf_usage!(Commit, U7, b"COMMIT\0");
65+
66+
#[derive(Copy, Clone, Debug)]
67+
pub struct Cfb;
68+
impl_kdf_usage!(Cfb, U4, b"CFB\0");
69+
70+
#[derive(Copy, Clone, Debug)]
71+
pub struct Xor;
72+
impl_kdf_usage!(Xor, U4, b"XOR\0");
73+
74+
#[derive(Copy, Clone, Debug)]
75+
pub struct Session;
76+
impl_kdf_usage!(Session, U8, b"SESSION\0");
77+
78+
#[derive(Copy, Clone, Debug)]
79+
pub struct Identity;
80+
impl_kdf_usage!(Identity, U9, b"IDENTITY\0");
81+
82+
type LabelAndUAndV<N, C> = Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, N>;
83+
84+
pub fn kdfa<H, L, K>(key: &[u8], context_u: &[u8], context_v: &[u8]) -> Key<K>
85+
where
86+
L: KdfUsage,
87+
88+
H: Digest + FixedOutputReset + EagerHash,
89+
K: KeySizeUser,
90+
91+
K::KeySize: ArraySize + Mul<U8>,
92+
<K::KeySize as Mul<U8>>::Output: Unsigned,
93+
94+
<<H as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,
95+
<<<H as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,
96+
{
97+
let mut context = Vec::with_capacity(context_u.len() + context_v.len());
98+
context.extend_from_slice(context_u);
99+
context.extend_from_slice(context_v);
100+
101+
let kdf = Counter::<Hmac<H>, K>::default();
102+
kdf.derive(key, true, false, L::LABEL, &context).unwrap()
103+
}
104+
105+
pub fn kdfe<U, H, C, K>(
106+
z: &SharedSecret<C>,
107+
party_u_info: &PublicKey<C>,
108+
party_v_info: &PublicKey<C>,
109+
) -> Key<K>
110+
// TODO: return error
111+
where
112+
U: KdfUsage,
113+
114+
H: Digest + FixedOutputReset,
115+
C: Curve + CurveArithmetic,
116+
K: KeySizeUser,
117+
118+
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
119+
FieldBytesSize<C>: ModulusSize,
120+
121+
<FieldBytesSize<C> as Add>::Output: Add<FieldBytesSize<C>>,
122+
Sum<FieldBytesSize<C>, FieldBytesSize<C>>: Add<U::LabelSize>,
123+
Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U::LabelSize>: ArraySize,
124+
{
125+
let mut key = Key::<K>::default();
126+
127+
let mut other_info = Array::<u8, LabelAndUAndV<U::LabelSize, C>>::default();
128+
other_info[..U::LabelSize::USIZE].copy_from_slice(&U::LABEL);
129+
130+
// TODO: convert that to affine point, then grab the X from there instead.
131+
other_info[U::LabelSize::USIZE..U::LabelSize::USIZE + FieldBytesSize::<C>::USIZE]
132+
.copy_from_slice(&party_u_info.to_encoded_point(false).x().unwrap());
133+
other_info[U::LabelSize::USIZE + FieldBytesSize::<C>::USIZE..]
134+
.copy_from_slice(&party_v_info.to_encoded_point(false).x().unwrap());
135+
136+
concat_kdf::derive_key_into::<H>(z.raw_secret_bytes(), &other_info, &mut key).unwrap();
137+
138+
key
139+
}
140+
141+
#[cfg(test)]
142+
mod tests {
143+
use super::*;
144+
145+
use aes::Aes256;
146+
use hex_literal::hex;
147+
use sha2::Sha256;
148+
149+
#[test]
150+
fn test_kdfe() {
151+
struct Vector<const S: usize, const K: usize, const E: usize> {
152+
shared_secret: [u8; S],
153+
local_key: [u8; K],
154+
remote_key: [u8; K],
155+
expected: [u8; E],
156+
}
157+
158+
// Test vectors here were manually generated from tpm2-pytss
159+
static TEST_VECTORS_SHA256: [Vector<
160+
{ FieldBytesSize::<p256::NistP256>::USIZE },
161+
{ <FieldBytesSize<p256::NistP256> as ModulusSize>::CompressedPointSize::USIZE },
162+
32,
163+
>; 2] = [
164+
Vector {
165+
shared_secret: hex!(
166+
"c75afb6f49c941ef194b232d7615769f5152d20de5dee19a991067f337dd65bc"
167+
),
168+
local_key: hex!(
169+
"031ba4030de068a2f07919c42ef6b19f302884f35f45e7d4e4bb90ffbb0bd9d099"
170+
),
171+
remote_key: hex!(
172+
"038f2b219a29c2ff9ba69cedff2d08d33a5dbca3da6bc8af8acd3ff6f5ec4dfbef"
173+
),
174+
expected: hex!("e3a0079db19724f9b76101e9364c4a149cea3501336abc3b603f94b22b6309a5"),
175+
},
176+
Vector {
177+
shared_secret: hex!(
178+
"a90a1c095155428500ed19e87c0df078df3dd2e66a0e3bbe664ba9ff62113b4a"
179+
),
180+
local_key: hex!(
181+
"03e9c7d6a853ba6176b65ec2f328bdea25f61c4e1b23a4e1c08e1da8c723381a04"
182+
),
183+
remote_key: hex!(
184+
"036ccf059628d3cdf8e1b4c4ba6d14696ba51cc8d4a96df4016f0b214782d5cee6"
185+
),
186+
expected: hex!("865f8093e2c4b801dc8c236eeb2806c7b1c51c2cb04101c035f7f2511ea0aeda"),
187+
},
188+
];
189+
190+
for v in &TEST_VECTORS_SHA256 {
191+
let out = kdfe::<Identity, Sha256, p256::NistP256, Aes256>(
192+
&SharedSecret::from(Array::from(v.shared_secret)),
193+
&PublicKey::try_from(Array::from(v.local_key)).unwrap(),
194+
&PublicKey::try_from(Array::from(v.remote_key)).unwrap(),
195+
);
196+
assert_eq!(out, v.expected);
197+
}
198+
}
199+
200+
#[test]
201+
fn test_kdfa() {
202+
struct Vector {
203+
key: &'static [u8],
204+
context_u: &'static [u8],
205+
context_v: &'static [u8],
206+
expected: &'static [u8],
207+
}
208+
209+
static TEST_VECTORS_SHA256: [Vector; 1] = [Vector {
210+
key: &hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
211+
context_u: b"",
212+
context_v: &hex!("0506070809"),
213+
expected: &hex!("de275f7f5cfeaac226b30d42377903b34705f178730d96400ccafb736e3d28a4"),
214+
}];
215+
216+
for v in &TEST_VECTORS_SHA256 {
217+
let out = kdfa::<Sha256, Storage, Aes256>(&v.key, &v.context_u, &v.context_v);
218+
assert_eq!(out.as_slice(), v.expected);
219+
}
220+
}
221+
}

tss-esapi/src/utils/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ use crate::{Context, Error, Result, WrapperErrorKind};
2323
use std::convert::TryFrom;
2424
use zeroize::Zeroize;
2525

26+
mod credential;
27+
pub mod kdf;
28+
29+
pub use self::credential::make_credential_ecc;
30+
2631
/// Create the [Public] structure for a restricted decryption key.
2732
///
2833
/// * `symmetric` - Cipher to be used for decrypting children of the key

0 commit comments

Comments
 (0)