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