| 
 | 1 | +// Copyright 2025 Contributors to the Parsec project.  | 
 | 2 | +// SPDX-License-Identifier: Apache-2.0  | 
 | 3 | + | 
 | 4 | +use core::{  | 
 | 5 | +    marker::PhantomData,  | 
 | 6 | +    ops::{Add, Mul},  | 
 | 7 | +};  | 
 | 8 | + | 
 | 9 | +use cfb_mode::cipher::{AsyncStreamCipher, BlockCipherEncrypt};  | 
 | 10 | +use digest::{  | 
 | 11 | +    array::ArraySize,  | 
 | 12 | +    consts::{B1, U8},  | 
 | 13 | +    crypto_common::{Iv, KeyIvInit, KeySizeUser, WeakKeyError},  | 
 | 14 | +    typenum::{  | 
 | 15 | +        operator_aliases::{Add1, Sum},  | 
 | 16 | +        Unsigned,  | 
 | 17 | +    },  | 
 | 18 | +    Digest, DynDigest, FixedOutputReset, Key, KeyInit, Mac, OutputSizeUser,  | 
 | 19 | +};  | 
 | 20 | +use ecdsa::elliptic_curve::{  | 
 | 21 | +    ecdh::{EphemeralSecret, SharedSecret},  | 
 | 22 | +    sec1::{Coordinates, FromEncodedPoint, ModulusSize, ToEncodedPoint},  | 
 | 23 | +    AffinePoint, Curve, CurveArithmetic, FieldBytesSize, PublicKey,  | 
 | 24 | +};  | 
 | 25 | +use hmac::{EagerHash, Hmac};  | 
 | 26 | +use log::error;  | 
 | 27 | +use rand::{thread_rng, Rng};  | 
 | 28 | +use rsa::{Oaep, RsaPublicKey};  | 
 | 29 | + | 
 | 30 | +use crate::{  | 
 | 31 | +    error::{Error, Result, WrapperErrorKind},  | 
 | 32 | +    structures::{EncryptedSecret, IdObject, Name},  | 
 | 33 | +    utils::kdf::{self},  | 
 | 34 | +};  | 
 | 35 | + | 
 | 36 | +type WeakResult<T> = core::result::Result<T, WeakKeyError>;  | 
 | 37 | + | 
 | 38 | +// [`TpmHmac`] intends to code for the key expected for hmac  | 
 | 39 | +// in the KDFa and KDFe derivations. There are no standard sizes for hmac keys really,  | 
 | 40 | +// upstream RustCrypto considers it to be [BlockSize], but TPM specification  | 
 | 41 | +// has a different opinion on the matter, and expect the key to the output  | 
 | 42 | +// bit size of the hash algorithm used.  | 
 | 43 | +//  | 
 | 44 | +// See https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=202  | 
 | 45 | +// section 24.5 HMAC:  | 
 | 46 | +//   bits the number of bits in the digest produced by ekNameAlg  | 
 | 47 | +//  | 
 | 48 | +// [BlockSize]: https://docs.rs/hmac/0.12.1/hmac/struct.HmacCore.html#impl-KeySizeUser-for-HmacCore%3CD%3E  | 
 | 49 | +struct TpmHmac<H>(PhantomData<H>);  | 
 | 50 | + | 
 | 51 | +impl<H> KeySizeUser for TpmHmac<H>  | 
 | 52 | +where  | 
 | 53 | +    H: OutputSizeUser,  | 
 | 54 | +{  | 
 | 55 | +    type KeySize = H::OutputSize;  | 
 | 56 | +}  | 
 | 57 | + | 
 | 58 | +pub fn make_credential_ecc<C, EkHash, EkCipher>(  | 
 | 59 | +    ek_public: PublicKey<C>,  | 
 | 60 | +    secret: &[u8],  | 
 | 61 | +    key_name: Name,  | 
 | 62 | +) -> Result<(IdObject, EncryptedSecret)>  | 
 | 63 | +where  | 
 | 64 | +    C: Curve + CurveArithmetic,  | 
 | 65 | + | 
 | 66 | +    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,  | 
 | 67 | +    FieldBytesSize<C>: ModulusSize,  | 
 | 68 | + | 
 | 69 | +    <FieldBytesSize<C> as Add>::Output: Add<FieldBytesSize<C>>,  | 
 | 70 | +    Sum<FieldBytesSize<C>, FieldBytesSize<C>>: ArraySize,  | 
 | 71 | +    Sum<FieldBytesSize<C>, FieldBytesSize<C>>: Add<U8>,  | 
 | 72 | +    Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U8>: Add<B1>,  | 
 | 73 | +    Add1<Sum<Sum<FieldBytesSize<C>, FieldBytesSize<C>>, U8>>: ArraySize,  | 
 | 74 | + | 
 | 75 | +    EkHash: Digest + EagerHash + FixedOutputReset,  | 
 | 76 | +    <EkHash as OutputSizeUser>::OutputSize: Mul<U8>,  | 
 | 77 | +    <<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,  | 
 | 78 | +    <<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,  | 
 | 79 | +    <<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,  | 
 | 80 | + | 
 | 81 | +    EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,  | 
 | 82 | +    <EkCipher as KeySizeUser>::KeySize: Mul<U8>,  | 
 | 83 | +    <<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,  | 
 | 84 | +{  | 
 | 85 | +    let mut rng = thread_rng();  | 
 | 86 | + | 
 | 87 | +    loop {  | 
 | 88 | +        // See Table 22 - Key Generation for the various labels used here after:  | 
 | 89 | +        // https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=183  | 
 | 90 | + | 
 | 91 | +        // C.6.4. ECC Secret Sharing for Credentials  | 
 | 92 | +        // https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=311  | 
 | 93 | +        let local = EphemeralSecret::<C>::random(&mut rng);  | 
 | 94 | + | 
 | 95 | +        let ecdh_secret: SharedSecret<C> = local.diffie_hellman(&ek_public);  | 
 | 96 | +        let local_public = local.public_key();  | 
 | 97 | +        drop(local);  | 
 | 98 | + | 
 | 99 | +        let seed = kdf::kdfe::<kdf::Identity, EkHash, C, TpmHmac<EkHash>>(  | 
 | 100 | +            &ecdh_secret,  | 
 | 101 | +            &local_public,  | 
 | 102 | +            &ek_public,  | 
 | 103 | +        )?;  | 
 | 104 | +        drop(ecdh_secret);  | 
 | 105 | + | 
 | 106 | +        // The local ECDH pair is used as "encrypted seed"  | 
 | 107 | +        let encoded_point = local_public.to_encoded_point(false);  | 
 | 108 | +        let Coordinates::Uncompressed {  | 
 | 109 | +            x: point_x,  | 
 | 110 | +            y: point_y,  | 
 | 111 | +        } = encoded_point.coordinates()  | 
 | 112 | +        else {  | 
 | 113 | +            // NOTE: The only way this could trigger would be for the local key to be identity.  | 
 | 114 | +            error!("Couldn't compute coordinates for the local public key");  | 
 | 115 | +            return Err(Error::local_error(WrapperErrorKind::InvalidParam));  | 
 | 116 | +        };  | 
 | 117 | +        let encrypted_seed = {  | 
 | 118 | +            let mut out = vec![];  | 
 | 119 | +            out.extend_from_slice(&FieldBytesSize::<C>::U16.to_be_bytes()[..]);  | 
 | 120 | +            out.extend_from_slice(&point_x);  | 
 | 121 | +            out.extend_from_slice(&FieldBytesSize::<C>::U16.to_be_bytes()[..]);  | 
 | 122 | +            out.extend_from_slice(&point_y);  | 
 | 123 | +            out  | 
 | 124 | +        };  | 
 | 125 | +        let encrypted_secret = EncryptedSecret::from_bytes(&encrypted_seed)?;  | 
 | 126 | + | 
 | 127 | +        match secret_to_credential::<EkHash, EkCipher>(seed, secret, &key_name)? {  | 
 | 128 | +            Ok(id_object) => return Ok((id_object, encrypted_secret)),  | 
 | 129 | +            Err(WeakKeyError) => {  | 
 | 130 | +                // 11.4.10.4 Rejection of weak keys  | 
 | 131 | +                // https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82  | 
 | 132 | + | 
 | 133 | +                // The Key was considered weak, and we should re-run the creation of the encrypted  | 
 | 134 | +                // secret.  | 
 | 135 | +                continue;  | 
 | 136 | +            }  | 
 | 137 | +        }  | 
 | 138 | +    }  | 
 | 139 | +}  | 
 | 140 | + | 
 | 141 | +pub fn make_credential_rsa<EkHash, EkCipher>(  | 
 | 142 | +    ek_public: &RsaPublicKey,  | 
 | 143 | +    secret: &[u8],  | 
 | 144 | +    key_name: Name,  | 
 | 145 | +) -> Result<(IdObject, EncryptedSecret)>  | 
 | 146 | +where  | 
 | 147 | +    EkHash: Digest + DynDigest + Send + Sync + 'static,  | 
 | 148 | +    EkHash: EagerHash + FixedOutputReset,  | 
 | 149 | +    <EkHash as OutputSizeUser>::OutputSize: Mul<U8>,  | 
 | 150 | +    <<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,  | 
 | 151 | +    <<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,  | 
 | 152 | +    <<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,  | 
 | 153 | + | 
 | 154 | +    EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,  | 
 | 155 | +    <EkCipher as KeySizeUser>::KeySize: Mul<U8>,  | 
 | 156 | +    <<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,  | 
 | 157 | +{  | 
 | 158 | +    let mut rng = thread_rng();  | 
 | 159 | + | 
 | 160 | +    loop {  | 
 | 161 | +        // See Table 22 - Key Generation for the various labels used here after:  | 
 | 162 | +        // https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=183  | 
 | 163 | + | 
 | 164 | +        // B.10.4 RSA Secret Sharing for Credentials  | 
 | 165 | +        // https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=302  | 
 | 166 | +        let random_seed = {  | 
 | 167 | +            let mut out = Key::<TpmHmac<EkHash>>::default();  | 
 | 168 | +            rng.try_fill(out.as_mut_slice()).map_err(|e| {  | 
 | 169 | +                error!("RNG error: {e}");  | 
 | 170 | +                Error::local_error(WrapperErrorKind::InternalError)  | 
 | 171 | +            })?;  | 
 | 172 | +            out  | 
 | 173 | +        };  | 
 | 174 | + | 
 | 175 | +        // The random seed is then encrypted with RSA-OAEP  | 
 | 176 | +        //  | 
 | 177 | +        // B.4 RSAES_OAEP  | 
 | 178 | +        // https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=297  | 
 | 179 | +        //  | 
 | 180 | +        // The label is a byte-stream whose last byte must be zero  | 
 | 181 | +        //  | 
 | 182 | +        // B.10.4. RSA Secret Sharing for Credentials  | 
 | 183 | +        // https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=302  | 
 | 184 | +        //  | 
 | 185 | +        // The label is going to be "IDENTITY" for secret sharing.  | 
 | 186 | +        let encrypted_seed = {  | 
 | 187 | +            let padding = Oaep::new_with_label::<EkHash, _>(b"IDENTITY\0".to_vec());  | 
 | 188 | +            let enc_data = ek_public  | 
 | 189 | +                .encrypt(&mut rng, padding, &random_seed[..])  | 
 | 190 | +                .map_err(|e| {  | 
 | 191 | +                    error!("RSA OAEP encryption error: {e}");  | 
 | 192 | +                    Error::local_error(WrapperErrorKind::InternalError)  | 
 | 193 | +                })?;  | 
 | 194 | +            enc_data  | 
 | 195 | +        };  | 
 | 196 | +        let encrypted_secret = EncryptedSecret::from_bytes(&encrypted_seed)?;  | 
 | 197 | + | 
 | 198 | +        match secret_to_credential::<EkHash, EkCipher>(random_seed, secret, &key_name)? {  | 
 | 199 | +            Ok(id_object) => return Ok((id_object, encrypted_secret)),  | 
 | 200 | +            Err(WeakKeyError) => {  | 
 | 201 | +                // 11.4.10.4 Rejection of weak keys  | 
 | 202 | +                // https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82  | 
 | 203 | + | 
 | 204 | +                // The Key was considered weak, and we should re-run the creation of the encrypted  | 
 | 205 | +                // secret.  | 
 | 206 | +                continue;  | 
 | 207 | +            }  | 
 | 208 | +        }  | 
 | 209 | +    }  | 
 | 210 | +}  | 
 | 211 | + | 
 | 212 | +fn secret_to_credential<EkHash, EkCipher>(  | 
 | 213 | +    seed: Key<TpmHmac<EkHash>>,  | 
 | 214 | +    secret: &[u8],  | 
 | 215 | +    key_name: &Name,  | 
 | 216 | +) -> Result<WeakResult<IdObject>>  | 
 | 217 | +where  | 
 | 218 | +    EkHash: Digest + EagerHash + FixedOutputReset,  | 
 | 219 | +    <EkHash as OutputSizeUser>::OutputSize: Mul<U8>,  | 
 | 220 | +    <<EkHash as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,  | 
 | 221 | +    <<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize: ArraySize + Mul<U8>,  | 
 | 222 | +    <<<EkHash as EagerHash>::Core as OutputSizeUser>::OutputSize as Mul<U8>>::Output: Unsigned,  | 
 | 223 | + | 
 | 224 | +    EkCipher: KeySizeUser + BlockCipherEncrypt + KeyInit,  | 
 | 225 | +    <EkCipher as KeySizeUser>::KeySize: Mul<U8>,  | 
 | 226 | +    <<EkCipher as KeySizeUser>::KeySize as Mul<U8>>::Output: ArraySize,  | 
 | 227 | +{  | 
 | 228 | +    // Prepare the sensitive data  | 
 | 229 | +    // this will be then encrypted using AES-CFB (size of the symmetric key depends on the EK).  | 
 | 230 | +    let mut sensitive_data = {  | 
 | 231 | +        let mut out = vec![];  | 
 | 232 | +        out.extend_from_slice(  | 
 | 233 | +            &u16::try_from(secret.len())  | 
 | 234 | +                .map_err(|_| {  | 
 | 235 | +                    error!("secret may only be 2^16 bytes long");  | 
 | 236 | +                    Error::local_error(WrapperErrorKind::WrongParamSize)  | 
 | 237 | +                })?  | 
 | 238 | +                .to_be_bytes()[..],  | 
 | 239 | +        );  | 
 | 240 | +        out.extend_from_slice(secret);  | 
 | 241 | +        out  | 
 | 242 | +    };  | 
 | 243 | + | 
 | 244 | +    // We'll now encrypt the sensitive data, and hmac the result of the encryption  | 
 | 245 | +    // https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=201  | 
 | 246 | +    // See 24.4 Symmetric Encryption  | 
 | 247 | +    let sym_key = kdf::kdfa::<EkHash, kdf::Storage, EkCipher>(&seed, key_name.value(), &[])?;  | 
 | 248 | + | 
 | 249 | +    if EkCipher::weak_key_test(&sym_key).is_ok() {  | 
 | 250 | +        // 11.4.10.4 Rejection of weak keys  | 
 | 251 | +        // https://trustedcomputinggroup.org/wp-content/uploads/TPM-2.0-1.83-Part-1-Architecture.pdf#page=82  | 
 | 252 | +        // The Key was considered weak, and we should re-run the creation of the encrypted  | 
 | 253 | +        // secret.  | 
 | 254 | + | 
 | 255 | +        return Ok(Err(WeakKeyError));  | 
 | 256 | +    }  | 
 | 257 | + | 
 | 258 | +    let iv: Iv<cfb_mode::Encryptor<EkCipher>> = Default::default();  | 
 | 259 | + | 
 | 260 | +    cfb_mode::Encryptor::<EkCipher>::new(&sym_key.into(), &iv.into()).encrypt(&mut sensitive_data);  | 
 | 261 | + | 
 | 262 | +    // See 24.5 HMAC  | 
 | 263 | +    let hmac_key = kdf::kdfa::<EkHash, kdf::Integrity, TpmHmac<EkHash>>(&seed, &[], &[])?;  | 
 | 264 | +    let mut hmac = Hmac::<EkHash>::new_from_slice(&hmac_key).map_err(|e| {  | 
 | 265 | +        error!("HMAC initialization error: {e}");  | 
 | 266 | +        Error::local_error(WrapperErrorKind::WrongParamSize)  | 
 | 267 | +    })?;  | 
 | 268 | +    Mac::update(&mut hmac, &sensitive_data);  | 
 | 269 | +    Mac::update(&mut hmac, key_name.value());  | 
 | 270 | +    let hmac = hmac.finalize();  | 
 | 271 | + | 
 | 272 | +    // We'll now serialize the object and get everything through the door.  | 
 | 273 | +    let mut out = vec![];  | 
 | 274 | +    out.extend_from_slice(  | 
 | 275 | +        &u16::try_from(hmac.into_bytes().len())  | 
 | 276 | +            .map_err(|_| {  | 
 | 277 | +                // NOTE: this shouldn't ever trigger ... but ...  | 
 | 278 | +                error!("HMAC output may only be 2^16 bytes long");  | 
 | 279 | +                Error::local_error(WrapperErrorKind::WrongParamSize)  | 
 | 280 | +            })?  | 
 | 281 | +            .to_be_bytes()[..],  | 
 | 282 | +    );  | 
 | 283 | +    out.extend_from_slice(&hmac.into_bytes());  | 
 | 284 | +    out.extend_from_slice(&sensitive_data);  | 
 | 285 | + | 
 | 286 | +    IdObject::from_bytes(&out).map(Ok)  | 
 | 287 | +}  | 
0 commit comments