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