tpm2_crypto/
ecc.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2025 Opinsys Oy
3// Copyright (c) 2024-2025 Jarkko Sakkinen
4
5//! TPM 2.0 ECC curves and cryptographic operations.
6
7use crate::{Error, Hash, PublicKey, KDF_LABEL_DUPLICATE};
8use num_bigint::{BigUint, RandBigInt};
9use num_traits::ops::bytes::ToBytes;
10use openssl::{
11    bn::{BigNum, BigNumContext},
12    derive::Deriver,
13    ec::{EcGroup, EcKey, EcPoint},
14    nid::Nid,
15    pkey::{PKey, Private},
16};
17use rand::{CryptoRng, RngCore};
18use strum::{Display, EnumString};
19use tpm2_protocol::{
20    constant::TPM_MAX_COMMAND_SIZE,
21    data::{
22        Tpm2bDigest, Tpm2bEccParameter, Tpm2bEncryptedSecret, TpmAlgId, TpmEccCurve, TpmaObject,
23        TpmsEccParms, TpmsEccPoint, TpmsSchemeHash, TpmtEccScheme, TpmtKdfScheme, TpmtPublic,
24        TpmtSymDefObject, TpmuAsymScheme, TpmuPublicId, TpmuPublicParms,
25    },
26    TpmMarshal, TpmWriter,
27};
28
29/// TPM 2.0 ECC curves.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)]
31#[strum(serialize_all = "kebab-case")]
32pub enum EccCurve {
33    NistP192,
34    NistP224,
35    NistP256,
36    NistP384,
37    NistP521,
38    BnP256,
39    BnP638,
40    Sm2P256,
41    #[strum(serialize = "bp-p256-r1")]
42    BpP256R1,
43    #[strum(serialize = "bp-p384-r1")]
44    BpP384R1,
45    #[strum(serialize = "bp-p512-r1")]
46    BpP512R1,
47    Curve25519,
48    Curve448,
49    None,
50}
51
52impl From<TpmEccCurve> for EccCurve {
53    fn from(curve: TpmEccCurve) -> Self {
54        match curve {
55            TpmEccCurve::NistP192 => Self::NistP192,
56            TpmEccCurve::NistP224 => Self::NistP224,
57            TpmEccCurve::NistP256 => Self::NistP256,
58            TpmEccCurve::NistP384 => Self::NistP384,
59            TpmEccCurve::NistP521 => Self::NistP521,
60            TpmEccCurve::BnP256 => Self::BnP256,
61            TpmEccCurve::BnP638 => Self::BnP638,
62            TpmEccCurve::Sm2P256 => Self::Sm2P256,
63            TpmEccCurve::BpP256R1 => Self::BpP256R1,
64            TpmEccCurve::BpP384R1 => Self::BpP384R1,
65            TpmEccCurve::BpP512R1 => Self::BpP512R1,
66            TpmEccCurve::Curve25519 => Self::Curve25519,
67            TpmEccCurve::Curve448 => Self::Curve448,
68            TpmEccCurve::None => Self::None,
69        }
70    }
71}
72
73impl From<EccCurve> for TpmEccCurve {
74    fn from(curve: EccCurve) -> Self {
75        match curve {
76            EccCurve::NistP192 => Self::NistP192,
77            EccCurve::NistP224 => Self::NistP224,
78            EccCurve::NistP256 => Self::NistP256,
79            EccCurve::NistP384 => Self::NistP384,
80            EccCurve::NistP521 => Self::NistP521,
81            EccCurve::BnP256 => Self::BnP256,
82            EccCurve::BnP638 => Self::BnP638,
83            EccCurve::Sm2P256 => Self::Sm2P256,
84            EccCurve::BpP256R1 => Self::BpP256R1,
85            EccCurve::BpP384R1 => Self::BpP384R1,
86            EccCurve::BpP512R1 => Self::BpP512R1,
87            EccCurve::Curve25519 => Self::Curve25519,
88            EccCurve::Curve448 => Self::Curve448,
89            EccCurve::None => Self::None,
90        }
91    }
92}
93
94impl From<EccCurve> for Nid {
95    /// Maps a TPM ECC curve ID to an OpenSSL NID.
96    fn from(curve: EccCurve) -> Self {
97        match curve {
98            EccCurve::NistP192 => Nid::X9_62_PRIME192V1,
99            EccCurve::NistP224 => Nid::SECP224R1,
100            EccCurve::NistP256 => Nid::X9_62_PRIME256V1,
101            EccCurve::NistP384 => Nid::SECP384R1,
102            EccCurve::NistP521 => Nid::SECP521R1,
103            EccCurve::BpP256R1 => Nid::BRAINPOOL_P256R1,
104            EccCurve::BpP384R1 => Nid::BRAINPOOL_P384R1,
105            EccCurve::BpP512R1 => Nid::BRAINPOOL_P512R1,
106            EccCurve::Sm2P256 => Nid::SM2,
107            _ => Nid::UNDEF,
108        }
109    }
110}
111
112impl TryFrom<Nid> for EccCurve {
113    type Error = Error;
114
115    fn try_from(nid: Nid) -> Result<Self, Self::Error> {
116        match nid {
117            Nid::X9_62_PRIME192V1 => Ok(EccCurve::NistP192),
118            Nid::SECP224R1 => Ok(EccCurve::NistP224),
119            Nid::X9_62_PRIME256V1 => Ok(EccCurve::NistP256),
120            Nid::SECP384R1 => Ok(EccCurve::NistP384),
121            Nid::SECP521R1 => Ok(EccCurve::NistP521),
122            Nid::BRAINPOOL_P256R1 => Ok(EccCurve::BpP256R1),
123            Nid::BRAINPOOL_P384R1 => Ok(EccCurve::BpP384R1),
124            Nid::BRAINPOOL_P512R1 => Ok(EccCurve::BpP512R1),
125            Nid::SM2 => Ok(EccCurve::Sm2P256),
126            _ => Err(Error::InvalidEccCurve),
127        }
128    }
129}
130
131/// ECC public key parameters.
132#[derive(Debug, Clone)]
133pub struct EccPublicKey {
134    pub curve: EccCurve,
135    pub x: Tpm2bEccParameter,
136    pub y: Tpm2bEccParameter,
137}
138
139impl TryFrom<&TpmtPublic> for EccPublicKey {
140    type Error = Error;
141
142    fn try_from(public: &TpmtPublic) -> Result<Self, Self::Error> {
143        let params = match &public.parameters {
144            TpmuPublicParms::Ecc(params) => Ok(params),
145            _ => Err(Error::InvalidEccParameters),
146        }?;
147
148        let (x, y) = match &public.unique {
149            TpmuPublicId::Ecc(point) => Ok((point.x, point.y)),
150            _ => Err(Error::InvalidEccParameters),
151        }?;
152
153        Ok(Self {
154            curve: params.curve_id.into(),
155            x,
156            y,
157        })
158    }
159}
160
161impl TryFrom<&PKey<Private>> for EccPublicKey {
162    type Error = Error;
163
164    fn try_from(pkey: &PKey<Private>) -> Result<Self, Self::Error> {
165        let ec_key = pkey.ec_key().map_err(|_| Error::InvalidEccParameters)?;
166        let group = ec_key.group();
167        let nid = group.curve_name().ok_or(Error::InvalidEccParameters)?;
168        let curve = EccCurve::try_from(nid)?;
169
170        let mut ctx = BigNumContext::new().map_err(|_| Error::OutOfMemory)?;
171        let (x, y) = crate::tpm_make_point(ec_key.public_key(), group, &mut ctx)?;
172
173        Ok(Self { curve, x, y })
174    }
175}
176
177impl PublicKey for EccPublicKey {
178    fn from_der(bytes: &[u8]) -> Result<(Self, Vec<u8>), Error> {
179        let pkey = PKey::private_key_from_der(bytes).map_err(|_| Error::OperationFailed)?;
180        let public_key = EccPublicKey::try_from(&pkey)?;
181        let ec_key = pkey.ec_key().map_err(|_| Error::InvalidEccParameters)?;
182        let sensitive = ec_key.private_key().to_vec();
183        Ok((public_key, sensitive))
184    }
185
186    fn to_public(&self, hash_alg: TpmAlgId, symmetric: TpmtSymDefObject) -> TpmtPublic {
187        tpm2_protocol::data::TpmtPublic {
188            object_type: TpmAlgId::Ecc,
189            name_alg: hash_alg,
190            object_attributes: TpmaObject::USER_WITH_AUTH | TpmaObject::DECRYPT,
191            auth_policy: Tpm2bDigest::default(),
192            parameters: TpmuPublicParms::Ecc(TpmsEccParms {
193                symmetric,
194                scheme: TpmtEccScheme {
195                    scheme: TpmAlgId::Ecdh,
196                    details: TpmuAsymScheme::Hash(TpmsSchemeHash { hash_alg }),
197                },
198                curve_id: self.curve.into(),
199                kdf: TpmtKdfScheme::default(),
200            }),
201            unique: TpmuPublicId::Ecc(TpmsEccPoint {
202                x: self.x,
203                y: self.y,
204            }),
205        }
206    }
207
208    fn to_seed(
209        &self,
210        name_alg: Hash,
211        rng: &mut (impl RngCore + CryptoRng),
212    ) -> Result<(Vec<u8>, Tpm2bEncryptedSecret), Error> {
213        let (derived_seed, ephemeral_point) = self.ecdh(name_alg, rng)?;
214
215        let mut point_bytes_buf = [0u8; TPM_MAX_COMMAND_SIZE as usize];
216        let len = {
217            let mut writer = TpmWriter::new(&mut point_bytes_buf);
218            ephemeral_point
219                .marshal(&mut writer)
220                .map_err(|_| Error::OperationFailed)?;
221            writer.len()
222        };
223        let point_bytes = &point_bytes_buf[..len];
224
225        let secret = Tpm2bEncryptedSecret::try_from(point_bytes).map_err(|_| Error::OutOfMemory)?;
226
227        Ok((derived_seed, secret))
228    }
229}
230
231impl EccPublicKey {
232    /// Performs ECDH and derives a seed using `KDFe` key derivation function from
233    /// TCG TPM 2.0 Architecture specification.
234    ///
235    /// # Errors
236    ///
237    /// Returns [`InvalidEccCurve`](crate::Error::InvalidEccCurve)
238    /// when the curve is not supported.
239    /// Returns [`InvalidHash`](crate::Error::InvalidHash)
240    /// when the hash algorithm is not recognized.
241    /// Returns [`OperationFailed`](crate::Error::OperationFailed) when an internal
242    /// cryptographic operation fails.
243    /// Returns [`OutOfMemory`](crate::Error::OutOfMemory) when an allocation fails.
244    fn ecdh(
245        &self,
246        name_alg: Hash,
247        rng: &mut (impl RngCore + CryptoRng),
248    ) -> Result<(Vec<u8>, TpmsEccPoint), Error> {
249        let nid = self.curve.into();
250        if nid == Nid::UNDEF {
251            return Err(Error::InvalidEccCurve);
252        }
253        let group = EcGroup::from_curve_name(nid).map_err(|_| Error::OutOfMemory)?;
254        let mut ctx = BigNumContext::new().map_err(|_| Error::OutOfMemory)?;
255
256        let parent_x = BigNum::from_slice(self.x.as_ref()).map_err(|_| Error::OutOfMemory)?;
257        let parent_y = BigNum::from_slice(self.y.as_ref()).map_err(|_| Error::OutOfMemory)?;
258        let parent_key = EcKey::from_public_key_affine_coordinates(&group, &parent_x, &parent_y)
259            .map_err(|_| Error::OperationFailed)?;
260        let parent_public_key =
261            PKey::from_ec_key(parent_key).map_err(|_| Error::OperationFailed)?;
262
263        let mut order = BigNum::new().map_err(|_| Error::OutOfMemory)?;
264        group
265            .order(&mut order, &mut ctx)
266            .map_err(|_| Error::OperationFailed)?;
267        let order_uint = BigUint::from_bytes_be(&order.to_vec());
268        let one = BigUint::from(1u8);
269
270        let priv_uint = rng.gen_biguint_range(&one, &order_uint);
271        let priv_bn =
272            BigNum::from_slice(&priv_uint.to_be_bytes()).map_err(|_| Error::OutOfMemory)?;
273
274        let mut ephemeral_pub_point = EcPoint::new(&group).map_err(|_| Error::OutOfMemory)?;
275        ephemeral_pub_point
276            .mul_generator(&group, &priv_bn, &ctx)
277            .map_err(|_| Error::OperationFailed)?;
278        let ephemeral_key = EcKey::from_private_components(&group, &priv_bn, &ephemeral_pub_point)
279            .map_err(|_| Error::OutOfMemory)?;
280
281        let ephemeral_public_key =
282            PKey::from_ec_key(ephemeral_key).map_err(|_| Error::OutOfMemory)?;
283        let mut deriver = Deriver::new(&ephemeral_public_key).map_err(|_| Error::OutOfMemory)?;
284        deriver
285            .set_peer(&parent_public_key)
286            .map_err(|_| Error::OperationFailed)?;
287        let z = deriver
288            .derive_to_vec()
289            .map_err(|_| Error::OperationFailed)?;
290
291        let (ephemeral_x, ephemeral_y) =
292            crate::tpm_make_point(&ephemeral_pub_point, &group, &mut ctx)?;
293
294        let seed_bits = u16::try_from(name_alg.size() * 8).map_err(|_| Error::OperationFailed)?;
295        let context_u = ephemeral_x.as_ref();
296        let context_v = self.x.as_ref();
297
298        let seed = name_alg.kdfe(&z, KDF_LABEL_DUPLICATE, context_u, context_v, seed_bits)?;
299
300        let ephemeral_point_tpm = TpmsEccPoint {
301            x: ephemeral_x,
302            y: ephemeral_y,
303        };
304
305        Ok((seed, ephemeral_point_tpm))
306    }
307}