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::{TpmCryptoError, TpmExternalKey, TpmHash, 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 TpmEllipticCurve {
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 TpmEllipticCurve {
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<TpmEllipticCurve> for TpmEccCurve {
74    fn from(curve: TpmEllipticCurve) -> Self {
75        match curve {
76            TpmEllipticCurve::NistP192 => Self::NistP192,
77            TpmEllipticCurve::NistP224 => Self::NistP224,
78            TpmEllipticCurve::NistP256 => Self::NistP256,
79            TpmEllipticCurve::NistP384 => Self::NistP384,
80            TpmEllipticCurve::NistP521 => Self::NistP521,
81            TpmEllipticCurve::BnP256 => Self::BnP256,
82            TpmEllipticCurve::BnP638 => Self::BnP638,
83            TpmEllipticCurve::Sm2P256 => Self::Sm2P256,
84            TpmEllipticCurve::BpP256R1 => Self::BpP256R1,
85            TpmEllipticCurve::BpP384R1 => Self::BpP384R1,
86            TpmEllipticCurve::BpP512R1 => Self::BpP512R1,
87            TpmEllipticCurve::Curve25519 => Self::Curve25519,
88            TpmEllipticCurve::Curve448 => Self::Curve448,
89            TpmEllipticCurve::None => Self::None,
90        }
91    }
92}
93
94impl From<TpmEllipticCurve> for Nid {
95    /// Maps a TPM ECC curve ID to an OpenSSL NID.
96    fn from(curve: TpmEllipticCurve) -> Self {
97        match curve {
98            TpmEllipticCurve::NistP192 => Nid::X9_62_PRIME192V1,
99            TpmEllipticCurve::NistP224 => Nid::SECP224R1,
100            TpmEllipticCurve::NistP256 => Nid::X9_62_PRIME256V1,
101            TpmEllipticCurve::NistP384 => Nid::SECP384R1,
102            TpmEllipticCurve::NistP521 => Nid::SECP521R1,
103            TpmEllipticCurve::BpP256R1 => Nid::BRAINPOOL_P256R1,
104            TpmEllipticCurve::BpP384R1 => Nid::BRAINPOOL_P384R1,
105            TpmEllipticCurve::BpP512R1 => Nid::BRAINPOOL_P512R1,
106            TpmEllipticCurve::Sm2P256 => Nid::SM2,
107            _ => Nid::UNDEF,
108        }
109    }
110}
111
112impl From<Nid> for TpmEllipticCurve {
113    fn from(nid: Nid) -> Self {
114        match nid {
115            Nid::X9_62_PRIME192V1 => TpmEllipticCurve::NistP192,
116            Nid::SECP224R1 => TpmEllipticCurve::NistP224,
117            Nid::X9_62_PRIME256V1 => TpmEllipticCurve::NistP256,
118            Nid::SECP384R1 => TpmEllipticCurve::NistP384,
119            Nid::SECP521R1 => TpmEllipticCurve::NistP521,
120            Nid::BRAINPOOL_P256R1 => TpmEllipticCurve::BpP256R1,
121            Nid::BRAINPOOL_P384R1 => TpmEllipticCurve::BpP384R1,
122            Nid::BRAINPOOL_P512R1 => TpmEllipticCurve::BpP512R1,
123            Nid::SM2 => TpmEllipticCurve::Sm2P256,
124            _ => TpmEllipticCurve::None,
125        }
126    }
127}
128
129/// ECC public key parameters.
130#[derive(Debug, Clone)]
131pub struct TpmEccExternalKey {
132    pub curve: TpmEllipticCurve,
133    pub x: Tpm2bEccParameter,
134    pub y: Tpm2bEccParameter,
135}
136
137impl TryFrom<&TpmtPublic> for TpmEccExternalKey {
138    type Error = TpmCryptoError;
139
140    fn try_from(public: &TpmtPublic) -> Result<Self, Self::Error> {
141        let params = match &public.parameters {
142            TpmuPublicParms::Ecc(params) => Ok(params),
143            _ => Err(TpmCryptoError::InvalidEccParameters),
144        }?;
145
146        let (x, y) = match &public.unique {
147            TpmuPublicId::Ecc(point) => Ok((point.x, point.y)),
148            _ => Err(TpmCryptoError::InvalidEccParameters),
149        }?;
150
151        Ok(Self {
152            curve: params.curve_id.into(),
153            x,
154            y,
155        })
156    }
157}
158
159impl TryFrom<&PKey<Private>> for TpmEccExternalKey {
160    type Error = TpmCryptoError;
161
162    fn try_from(pkey: &PKey<Private>) -> Result<Self, Self::Error> {
163        let ec_key = pkey
164            .ec_key()
165            .map_err(|_| TpmCryptoError::InvalidEccParameters)?;
166        let group = ec_key.group();
167        let nid = group
168            .curve_name()
169            .ok_or(TpmCryptoError::InvalidEccParameters)?;
170        let curve = TpmEllipticCurve::from(nid);
171
172        let mut ctx = BigNumContext::new().map_err(|_| TpmCryptoError::OutOfMemory)?;
173        let (x, y) = crate::tpm_make_point(ec_key.public_key(), group, &mut ctx)?;
174
175        Ok(Self { curve, x, y })
176    }
177}
178
179impl TpmExternalKey for TpmEccExternalKey {
180    fn from_der(bytes: &[u8]) -> Result<(Self, Vec<u8>), TpmCryptoError> {
181        let pkey =
182            PKey::private_key_from_der(bytes).map_err(|_| TpmCryptoError::OperationFailed)?;
183        let public_key = TpmEccExternalKey::try_from(&pkey)?;
184        let ec_key = pkey
185            .ec_key()
186            .map_err(|_| TpmCryptoError::InvalidEccParameters)?;
187        let sensitive = ec_key.private_key().to_vec();
188        Ok((public_key, sensitive))
189    }
190
191    fn to_public(
192        &self,
193        hash_alg: TpmAlgId,
194        object_attributes: TpmaObject,
195        symmetric: TpmtSymDefObject,
196    ) -> TpmtPublic {
197        tpm2_protocol::data::TpmtPublic {
198            object_type: TpmAlgId::Ecc,
199            name_alg: hash_alg,
200            object_attributes,
201            auth_policy: Tpm2bDigest::default(),
202            parameters: TpmuPublicParms::Ecc(TpmsEccParms {
203                symmetric,
204                scheme: TpmtEccScheme {
205                    scheme: TpmAlgId::Ecdh,
206                    details: TpmuAsymScheme::Hash(TpmsSchemeHash { hash_alg }),
207                },
208                curve_id: self.curve.into(),
209                kdf: TpmtKdfScheme::default(),
210            }),
211            unique: TpmuPublicId::Ecc(TpmsEccPoint {
212                x: self.x,
213                y: self.y,
214            }),
215        }
216    }
217
218    fn to_seed(
219        &self,
220        name_alg: TpmHash,
221        rng: &mut (impl RngCore + CryptoRng),
222    ) -> Result<(Vec<u8>, Tpm2bEncryptedSecret), TpmCryptoError> {
223        let (derived_seed, ephemeral_point) = self.ecdh(name_alg, rng)?;
224
225        let mut point_bytes_buf = [0u8; TPM_MAX_COMMAND_SIZE as usize];
226        let len = {
227            let mut writer = TpmWriter::new(&mut point_bytes_buf);
228            ephemeral_point
229                .marshal(&mut writer)
230                .map_err(TpmCryptoError::Marshal)?;
231            writer.len()
232        };
233        let point_bytes = &point_bytes_buf[..len];
234
235        let secret =
236            Tpm2bEncryptedSecret::try_from(point_bytes).map_err(TpmCryptoError::Unmarshal)?;
237
238        Ok((derived_seed, secret))
239    }
240}
241
242impl TpmEccExternalKey {
243    /// Performs ECDH and derives a seed using `KDFe` key derivation function from
244    /// TCG TPM 2.0 Architecture specification.
245    ///
246    /// # Errors
247    ///
248    /// Returns [`InvalidEccCurve`](crate::Error::InvalidEccCurve)
249    /// when the curve is not supported.
250    /// Returns [`InvalidHash`](crate::Error::InvalidHash)
251    /// when the hash algorithm is not recognized.
252    /// Returns [`OperationFailed`](crate::Error::OperationFailed) when an internal
253    /// cryptographic operation fails.
254    /// Returns [`OutOfMemory`](crate::Error::OutOfMemory) when an allocation fails.
255    fn ecdh(
256        &self,
257        name_alg: TpmHash,
258        rng: &mut (impl RngCore + CryptoRng),
259    ) -> Result<(Vec<u8>, TpmsEccPoint), TpmCryptoError> {
260        let nid = self.curve.into();
261        if nid == Nid::UNDEF {
262            return Err(TpmCryptoError::InvalidEccCurve);
263        }
264        let group = EcGroup::from_curve_name(nid).map_err(|_| TpmCryptoError::OutOfMemory)?;
265        let mut ctx = BigNumContext::new().map_err(|_| TpmCryptoError::OutOfMemory)?;
266
267        let parent_x =
268            BigNum::from_slice(self.x.as_ref()).map_err(|_| TpmCryptoError::OutOfMemory)?;
269        let parent_y =
270            BigNum::from_slice(self.y.as_ref()).map_err(|_| TpmCryptoError::OutOfMemory)?;
271        let parent_key = EcKey::from_public_key_affine_coordinates(&group, &parent_x, &parent_y)
272            .map_err(|_| TpmCryptoError::OperationFailed)?;
273        let parent_public_key =
274            PKey::from_ec_key(parent_key).map_err(|_| TpmCryptoError::OperationFailed)?;
275
276        let mut order = BigNum::new().map_err(|_| TpmCryptoError::OutOfMemory)?;
277        group
278            .order(&mut order, &mut ctx)
279            .map_err(|_| TpmCryptoError::OperationFailed)?;
280        let order_uint = BigUint::from_bytes_be(&order.to_vec());
281        let one = BigUint::from(1u8);
282
283        let priv_uint = rng.gen_biguint_range(&one, &order_uint);
284        let priv_bn = BigNum::from_slice(&priv_uint.to_be_bytes())
285            .map_err(|_| TpmCryptoError::OutOfMemory)?;
286
287        let mut ephemeral_pub_point =
288            EcPoint::new(&group).map_err(|_| TpmCryptoError::OutOfMemory)?;
289        ephemeral_pub_point
290            .mul_generator(&group, &priv_bn, &ctx)
291            .map_err(|_| TpmCryptoError::OperationFailed)?;
292        let ephemeral_key = EcKey::from_private_components(&group, &priv_bn, &ephemeral_pub_point)
293            .map_err(|_| TpmCryptoError::OutOfMemory)?;
294
295        let ephemeral_public_key =
296            PKey::from_ec_key(ephemeral_key).map_err(|_| TpmCryptoError::OutOfMemory)?;
297        let mut deriver =
298            Deriver::new(&ephemeral_public_key).map_err(|_| TpmCryptoError::OutOfMemory)?;
299        deriver
300            .set_peer(&parent_public_key)
301            .map_err(|_| TpmCryptoError::OperationFailed)?;
302        let z = deriver
303            .derive_to_vec()
304            .map_err(|_| TpmCryptoError::OperationFailed)?;
305
306        let (ephemeral_x, ephemeral_y) =
307            crate::tpm_make_point(&ephemeral_pub_point, &group, &mut ctx)?;
308
309        let seed_bits =
310            u16::try_from(name_alg.size() * 8).map_err(|_| TpmCryptoError::OperationFailed)?;
311        let context_u = ephemeral_x.as_ref();
312        let context_v = self.x.as_ref();
313
314        let seed = name_alg.kdfe(&z, KDF_LABEL_DUPLICATE, context_u, context_v, seed_bits)?;
315
316        let ephemeral_point_tpm = TpmsEccPoint {
317            x: ephemeral_x,
318            y: ephemeral_y,
319        };
320
321        Ok((seed, ephemeral_point_tpm))
322    }
323}