Skip to content

Commit 3671522

Browse files
committed
WIP: Create credentials
Signed-off-by: Arthur Gautier <[email protected]>
1 parent 7e73a1e commit 3671522

File tree

7 files changed

+473
-1
lines changed

7 files changed

+473
-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

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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::{B1, U8},
8+
crypto_common::{Iv, KeyIvInit},
9+
typenum::operator_aliases::{Add1, 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<U8>,
41+
Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U8>: Add<B1>,
42+
Add1<Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U8>>: ArraySize,
43+
{
44+
let mut rng = thread_rng();
45+
46+
let local = EphemeralSecret::<C>::random(&mut rng);
47+
48+
let ecdh_secret: SharedSecret<C> = local.diffie_hellman(&ek_public);
49+
50+
let _ = key_name;
51+
52+
type HmacSha256 = Hmac<Sha256>;
53+
54+
let seed = kdf::kdfe::<kdf::Identity, Sha256, C, aes::Aes256>(
55+
&ecdh_secret,
56+
&local.public_key(),
57+
&ek_public,
58+
);
59+
drop(ecdh_secret);
60+
61+
// The local ECDH pair is used as "encrypted seed"
62+
let encrypted_seed = {
63+
let mut out = vec![];
64+
out.extend_from_slice(&32u16.to_be_bytes()[..]);
65+
out.extend_from_slice(&local.public_key().to_encoded_point(false).x().unwrap());
66+
out.extend_from_slice(&32u16.to_be_bytes()[..]);
67+
out.extend_from_slice(&local.public_key().to_encoded_point(false).y().unwrap());
68+
out
69+
};
70+
71+
let mut sensitive_data = {
72+
let mut out = vec![];
73+
out.extend_from_slice(&u16::try_from(secret.len()).unwrap().to_be_bytes()[..]);
74+
out.extend_from_slice(secret);
75+
out
76+
};
77+
78+
let sym_key = kdf::kdfa::<Sha256, kdf::Storage, aes::Aes128>(&seed, key_name.value(), &[]);
79+
println!("----");
80+
let hmac_key = kdf::kdfa::<Sha256, kdf::Integrity, aes::Aes256>(&seed, &[], &[]);
81+
type Aes128CfbEnc = cfb_mode::Encryptor<aes::Aes128>;
82+
let iv: Iv<Aes128CfbEnc> = Default::default();
83+
84+
Aes128CfbEnc::new(&sym_key.into(), &iv.into()).encrypt(&mut sensitive_data);
85+
86+
let mut hmac = HmacSha256::new_from_slice(&hmac_key).unwrap();
87+
hmac.update(&sensitive_data);
88+
hmac.update(key_name.value());
89+
let hmac = hmac.finalize();
90+
91+
let mut out = vec![];
92+
out.extend_from_slice(
93+
&u16::try_from(hmac.into_bytes().len())
94+
.unwrap()
95+
.to_be_bytes()[..],
96+
);
97+
out.extend_from_slice(&hmac.into_bytes());
98+
out.extend_from_slice(&sensitive_data);
99+
100+
(
101+
IdObject::from_bytes(&out).unwrap(),
102+
EncryptedSecret::from_bytes(&encrypted_seed).unwrap(),
103+
)
104+
}

tss-esapi/src/utils/kdf.rs

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

0 commit comments

Comments
 (0)