Skip to content

Commit 44ce6ba

Browse files
committed
WIP: Create credentials
1 parent 06ceec2 commit 44ce6ba

File tree

7 files changed

+474
-1
lines changed

7 files changed

+474
-1
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
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

Lines changed: 9 additions & 1 deletion
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

Lines changed: 103 additions & 0 deletions
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

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
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(
103+
key,
104+
true,
105+
false, // TODO(baloo):
106+
// https://github.com/tpm2-software/tpm2-pytss/blob/a3e64878f622e23a0313144bed055c78b0f3f8d5/src/tpm2_pytss/internal/crypto.py#L344
107+
// is wrong
108+
L::LABEL,
109+
&context,
110+
)
111+
.unwrap()
112+
}
113+
114+
pub fn kdfe<U, H, C, K>(
115+
z: &SharedSecret<C>,
116+
party_u_info: &PublicKey<C>,
117+
party_v_info: &PublicKey<C>,
118+
) -> Key<K>
119+
// TODO: return error
120+
where
121+
U: KdfUsage,
122+
123+
H: Digest + FixedOutputReset,
124+
C: Curve + CurveArithmetic,
125+
K: KeySizeUser,
126+
127+
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
128+
FieldBytesSize<C>: ModulusSize,
129+
130+
<FieldBytesSize<C> as Add>::Output: Add<FieldBytesSize<C>>,
131+
Sum<FieldBytesSize<C>, FieldBytesSize<C>>: Add<U::LabelSize>,
132+
Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U::LabelSize>: ArraySize,
133+
{
134+
let mut key = Key::<K>::default();
135+
136+
let mut other_info = Array::<u8, LabelAndUAndV<U::LabelSize, C>>::default();
137+
other_info[..U::LabelSize::USIZE].copy_from_slice(&U::LABEL);
138+
139+
// TODO: convert that to affine point, then grab the X from there instead.
140+
other_info[U::LabelSize::USIZE..U::LabelSize::USIZE + FieldBytesSize::<C>::USIZE]
141+
.copy_from_slice(&party_u_info.to_encoded_point(false).x().unwrap());
142+
other_info[U::LabelSize::USIZE + FieldBytesSize::<C>::USIZE..]
143+
.copy_from_slice(&party_v_info.to_encoded_point(false).x().unwrap());
144+
145+
concat_kdf::derive_key_into::<H>(z.raw_secret_bytes(), &other_info, &mut key).unwrap();
146+
147+
key
148+
}
149+
150+
#[cfg(test)]
151+
mod tests {
152+
use super::*;
153+
154+
use aes::Aes256;
155+
use hex_literal::hex;
156+
use sha2::Sha256;
157+
158+
#[test]
159+
fn test_kdfe() {
160+
struct Vector<const S: usize, const K: usize, const E: usize> {
161+
shared_secret: [u8; S],
162+
local_key: [u8; K],
163+
remote_key: [u8; K],
164+
expected: [u8; E],
165+
}
166+
167+
// Test vectors here were manually generated from tpm2-pytss
168+
static TEST_VECTORS_SHA256: [Vector<
169+
{ FieldBytesSize::<p256::NistP256>::USIZE },
170+
{ <FieldBytesSize<p256::NistP256> as ModulusSize>::CompressedPointSize::USIZE },
171+
32,
172+
>; 2] = [
173+
Vector {
174+
shared_secret: hex!(
175+
"c75afb6f49c941ef194b232d7615769f5152d20de5dee19a991067f337dd65bc"
176+
),
177+
local_key: hex!(
178+
"031ba4030de068a2f07919c42ef6b19f302884f35f45e7d4e4bb90ffbb0bd9d099"
179+
),
180+
remote_key: hex!(
181+
"038f2b219a29c2ff9ba69cedff2d08d33a5dbca3da6bc8af8acd3ff6f5ec4dfbef"
182+
),
183+
expected: hex!("e3a0079db19724f9b76101e9364c4a149cea3501336abc3b603f94b22b6309a5"),
184+
},
185+
Vector {
186+
shared_secret: hex!(
187+
"a90a1c095155428500ed19e87c0df078df3dd2e66a0e3bbe664ba9ff62113b4a"
188+
),
189+
local_key: hex!(
190+
"03e9c7d6a853ba6176b65ec2f328bdea25f61c4e1b23a4e1c08e1da8c723381a04"
191+
),
192+
remote_key: hex!(
193+
"036ccf059628d3cdf8e1b4c4ba6d14696ba51cc8d4a96df4016f0b214782d5cee6"
194+
),
195+
expected: hex!("865f8093e2c4b801dc8c236eeb2806c7b1c51c2cb04101c035f7f2511ea0aeda"),
196+
},
197+
];
198+
199+
for v in &TEST_VECTORS_SHA256 {
200+
let out = kdfe::<Identity, Sha256, p256::NistP256, Aes256>(
201+
&SharedSecret::from(Array::from(v.shared_secret)),
202+
&PublicKey::try_from(Array::from(v.local_key)).unwrap(),
203+
&PublicKey::try_from(Array::from(v.remote_key)).unwrap(),
204+
);
205+
assert_eq!(out, v.expected);
206+
}
207+
}
208+
209+
#[test]
210+
fn test_kdfa() {
211+
struct Vector {
212+
key: &'static [u8],
213+
context_u: &'static [u8],
214+
context_v: &'static [u8],
215+
expected: &'static [u8],
216+
}
217+
218+
static TEST_VECTORS_SHA256: [Vector; 1] = [Vector {
219+
key: &hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
220+
context_u: b"",
221+
context_v: &hex!("0506070809"),
222+
expected: &hex!("de275f7f5cfeaac226b30d42377903b34705f178730d96400ccafb736e3d28a4"),
223+
}];
224+
225+
for v in &TEST_VECTORS_SHA256 {
226+
let out = kdfa::<Sha256, Storage, Aes256>(&v.key, &v.context_u, &v.context_v);
227+
assert_eq!(out.as_slice(), v.expected);
228+
}
229+
}
230+
}

0 commit comments

Comments
 (0)