|
| 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 preferred 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