|
| 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 | +} |
0 commit comments