cli/key/external_key/
ecc.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2// Copyright (c) 2025 Opinsys Oy
3
4#![allow(clippy::no_effect_underscore_binding)]
5
6use crate::{
7    crypto::UNCOMPRESSED_POINT_TAG,
8    key::{external_key::ExternalKey, KeyError},
9};
10
11use std::borrow::Cow;
12
13use p256::elliptic_curve::sec1::ToEncodedPoint;
14use rasn::{
15    types::{BitString, ObjectIdentifier, OctetString},
16    AsnType, Decode, Decoder, Encode, Encoder,
17};
18use rsa::traits::PrivateKeyParts;
19use tpm2_protocol::data::{
20    Tpm2bDigest, Tpm2bEccParameter, TpmAlgId, TpmEccCurve, TpmaObject, TpmsEccParms, TpmsEccPoint,
21    TpmtEccScheme, TpmtKdfScheme, TpmtPublic, TpmtSymDefObject, TpmuAsymScheme, TpmuPublicId,
22    TpmuPublicParms,
23};
24
25pub const SECP_256_R_1: ObjectIdentifier =
26    ObjectIdentifier::new_unchecked(Cow::Borrowed(&[1, 2, 840, 10045, 3, 1, 7]));
27pub const SECP_384_R_1: ObjectIdentifier =
28    ObjectIdentifier::new_unchecked(Cow::Borrowed(&[1, 3, 132, 0, 34]));
29pub const SECP_521_R_1: ObjectIdentifier =
30    ObjectIdentifier::new_unchecked(Cow::Borrowed(&[1, 3, 132, 0, 35]));
31
32#[allow(clippy::no_effect_underscore_binding)]
33#[derive(AsnType, Decode, Encode, Debug)]
34pub struct Sec1EcPrivateKey {
35    pub version: u8,
36    pub private_key: OctetString,
37    #[rasn(tag(explicit(context, 0)))]
38    pub parameters: Option<ObjectIdentifier>,
39    #[rasn(tag(explicit(context, 1)))]
40    pub public_key: Option<BitString>,
41}
42
43/// Parses a SEC1 DER-encoded ECC private key.
44///
45/// An optional `inherited_oid` can be provided, which is necessary when parsing
46/// a key from a PKCS#8 wrapper where the curve parameters are in the outer
47/// structure.
48///
49/// # Errors
50///
51/// Returns a `KeyError` if the DER data is malformed, the OID is unsupported,
52/// or the key is invalid for the specified curve.
53pub fn parse_ecc_from_der(
54    der_bytes: &[u8],
55    inherited_oid: Option<&ObjectIdentifier>,
56) -> Result<ExternalKey, KeyError> {
57    let sec1_key = rasn::der::decode::<Sec1EcPrivateKey>(der_bytes)?;
58
59    let oid =
60        sec1_key
61            .parameters
62            .as_ref()
63            .or(inherited_oid)
64            .ok_or(KeyError::ValueConversionFailed(
65                "missing ECC parameters".to_string(),
66            ))?;
67
68    let key_bytes = sec1_key.private_key.as_ref();
69
70    if oid == &SECP_256_R_1 {
71        Ok(ExternalKey::EccP256(Box::new(
72            p256::SecretKey::from_slice(key_bytes)
73                .map_err(|e| KeyError::ValueConversionFailed(e.to_string()))?,
74        )))
75    } else if oid == &SECP_384_R_1 {
76        Ok(ExternalKey::EccP384(Box::new(
77            p384::SecretKey::from_slice(key_bytes)
78                .map_err(|e| KeyError::ValueConversionFailed(e.to_string()))?,
79        )))
80    } else if oid == &SECP_521_R_1 {
81        Ok(ExternalKey::EccP521(Box::new(
82            p521::SecretKey::from_slice(key_bytes)
83                .map_err(|e| KeyError::ValueConversionFailed(e.to_string()))?,
84        )))
85    } else {
86        Err(KeyError::UnsupportedOid(oid.to_string()))
87    }
88}
89
90/// Converts ECC public key bytes to a `TpmtPublic` structure.
91///
92/// # Errors
93///
94/// Returns a `KeyError` if the public key bytes do not represent a valid
95/// uncompressed ECC point or if conversion to TPM types fails.
96pub fn ecc_to_public(
97    pub_bytes: &[u8],
98    curve_id: TpmEccCurve,
99    hash_alg: TpmAlgId,
100    symmetric: TpmtSymDefObject,
101) -> Result<TpmtPublic, KeyError> {
102    if pub_bytes.is_empty() || pub_bytes[0] != UNCOMPRESSED_POINT_TAG {
103        return Err(KeyError::InvalidEccPoint(hex::encode(pub_bytes)));
104    }
105
106    let coord_len = (pub_bytes.len() - 1) / 2;
107    let x = &pub_bytes[1..=coord_len];
108    let y = &pub_bytes[1 + coord_len..];
109
110    Ok(TpmtPublic {
111        object_type: TpmAlgId::Ecc,
112        name_alg: hash_alg,
113        object_attributes: TpmaObject::USER_WITH_AUTH | TpmaObject::DECRYPT,
114        auth_policy: Tpm2bDigest::default(),
115        parameters: TpmuPublicParms::Ecc(TpmsEccParms {
116            symmetric,
117            scheme: TpmtEccScheme {
118                scheme: TpmAlgId::Ecdh,
119                details: TpmuAsymScheme::Any(tpm2_protocol::data::TpmsSchemeHash { hash_alg }),
120            },
121            curve_id,
122            kdf: TpmtKdfScheme::default(),
123        }),
124        unique: TpmuPublicId::Ecc(TpmsEccPoint {
125            x: Tpm2bEccParameter::try_from(x)
126                .map_err(|e| KeyError::ValueConversionFailed(e.to_string()))?,
127            y: Tpm2bEccParameter::try_from(y)
128                .map_err(|e| KeyError::ValueConversionFailed(e.to_string()))?,
129        }),
130    })
131}
132
133impl ExternalKey {
134    /// Converts key to `TpmtPublic`.
135    ///
136    /// # Errors
137    ///
138    /// Returns a `KeyError` on failure.
139    pub fn to_public(&self, hash_alg: TpmAlgId) -> Result<TpmtPublic, KeyError> {
140        let symmetric = TpmtSymDefObject::default();
141
142        match self {
143            ExternalKey::Rsa2048(key) => super::rsa::rsa_to_public(key, 2048, hash_alg, symmetric),
144            ExternalKey::Rsa3072(key) => super::rsa::rsa_to_public(key, 3072, hash_alg, symmetric),
145            ExternalKey::Rsa4096(key) => super::rsa::rsa_to_public(key, 4096, hash_alg, symmetric),
146            ExternalKey::EccP256(secret_key) => {
147                let encoded_point = secret_key.public_key().to_encoded_point(false);
148                ecc_to_public(
149                    encoded_point.as_bytes(),
150                    TpmEccCurve::NistP256,
151                    hash_alg,
152                    symmetric,
153                )
154            }
155            ExternalKey::EccP384(secret_key) => {
156                let encoded_point = secret_key.public_key().to_encoded_point(false);
157                ecc_to_public(
158                    encoded_point.as_bytes(),
159                    TpmEccCurve::NistP384,
160                    hash_alg,
161                    symmetric,
162                )
163            }
164            ExternalKey::EccP521(secret_key) => {
165                let encoded_point = secret_key.public_key().to_encoded_point(false);
166                ecc_to_public(
167                    encoded_point.as_bytes(),
168                    TpmEccCurve::NistP521,
169                    hash_alg,
170                    symmetric,
171                )
172            }
173        }
174    }
175
176    /// Returns the sensitive part of the private key required for import.
177    #[must_use]
178    pub fn sensitive_blob(&self) -> Vec<u8> {
179        match self {
180            ExternalKey::Rsa2048(key) | ExternalKey::Rsa3072(key) | ExternalKey::Rsa4096(key) => {
181                key.primes()[0].to_bytes_be()
182            }
183            ExternalKey::EccP256(secret_key) => secret_key.to_bytes().to_vec(),
184            ExternalKey::EccP384(secret_key) => secret_key.to_bytes().to_vec(),
185            ExternalKey::EccP521(secret_key) => secret_key.to_bytes().to_vec(),
186        }
187    }
188}