cli/key/external_key/
rsa.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::key::{external_key::ExternalKey, KeyError};
7use num_bigint::{BigUint, ToBigInt};
8use num_traits::ToPrimitive;
9use rasn::{
10    types::{Integer, SequenceOf},
11    AsnType, Decode, Decoder, Encode,
12};
13use rsa::{traits::PublicKeyParts, RsaPrivateKey};
14use tpm2_protocol::data::{
15    Tpm2bDigest, Tpm2bPublicKeyRsa, TpmAlgId, TpmaObject, TpmsRsaParms, TpmsSchemeHash,
16    TpmtRsaScheme, TpmtSymDefObject, TpmuAsymScheme, TpmuPublicId, TpmuPublicParms,
17};
18
19#[derive(AsnType, Decode, Encode, Debug)]
20pub struct OtherPrimeInfo {
21    pub prime: Integer,
22    pub exponent: Integer,
23    pub coefficient: Integer,
24}
25
26/// A struct representing the full 9-field PKCS#1 `RSAPrivateKey` structure.
27#[allow(clippy::no_effect_underscore_binding)]
28#[derive(AsnType, Decode, Encode, Debug)]
29pub struct RsaPrivateKeyAsn1 {
30    pub version: Integer,
31    pub modulus: Integer,
32    pub public_exponent: Integer,
33    pub private_exponent: Integer,
34    pub prime1: Integer,
35    pub prime2: Integer,
36    pub exponent1: Integer,
37    pub exponent2: Integer,
38    pub coefficient: Integer,
39    pub other_prime_infos: Option<SequenceOf<OtherPrimeInfo>>,
40}
41
42/// A struct representing an 8-field PKCS#1 `RSAPrivateKey` structure,
43/// for compatibility with encoders that omit the `version` field when it is 0.
44#[allow(clippy::no_effect_underscore_binding)]
45#[derive(AsnType, Decode, Encode, Debug)]
46pub struct RsaPrivateKeyPkcs1V0 {
47    pub modulus: Integer,
48    pub public_exponent: Integer,
49    pub private_exponent: Integer,
50    pub prime1: Integer,
51    pub prime2: Integer,
52    pub exponent1: Integer,
53    pub exponent2: Integer,
54    pub coefficient: Integer,
55    pub other_prime_infos: Option<SequenceOf<OtherPrimeInfo>>,
56}
57
58/// Builds an `ExternalKey` by deriving it from the public exponent and prime factors.
59fn build_external_key_from_primes(
60    public_exponent: &Integer,
61    prime1: &Integer,
62    prime2: &Integer,
63) -> Result<ExternalKey, KeyError> {
64    let e_num = public_exponent
65        .to_bigint()
66        .ok_or(KeyError::InvalidFormat)?
67        .to_biguint()
68        .ok_or(KeyError::InvalidFormat)?;
69    let p_num = prime1
70        .to_bigint()
71        .ok_or(KeyError::InvalidFormat)?
72        .to_biguint()
73        .ok_or(KeyError::InvalidFormat)?;
74    let q_num = prime2
75        .to_bigint()
76        .ok_or(KeyError::InvalidFormat)?
77        .to_biguint()
78        .ok_or(KeyError::InvalidFormat)?;
79
80    if e_num != BigUint::from(65537u32) {
81        return Err(KeyError::InvalidRsaExponent);
82    }
83
84    let e = rsa::BigUint::from_bytes_be(&e_num.to_bytes_be());
85    let p = rsa::BigUint::from_bytes_be(&p_num.to_bytes_be());
86    let q = rsa::BigUint::from_bytes_be(&q_num.to_bytes_be());
87
88    let key = RsaPrivateKey::from_p_q(p, q, e).map_err(|_| KeyError::InvalidFormat)?;
89
90    match key.size() * 8 {
91        2048 => Ok(ExternalKey::Rsa2048(Box::new(key))),
92        3072 => Ok(ExternalKey::Rsa3072(Box::new(key))),
93        4096 => Ok(ExternalKey::Rsa4096(Box::new(key))),
94        _ => Err(KeyError::InvalidRsaExponent),
95    }
96}
97
98/// Parses a PKCS#1 DER-encoded RSA private key with fallback logic.
99fn parse_pkcs1_rsa_from_der(der_bytes: &[u8]) -> Result<ExternalKey, KeyError> {
100    if let Ok(pkcs1_key) = rasn::der::decode::<RsaPrivateKeyAsn1>(der_bytes) {
101        let version = pkcs1_key.version.to_u8().ok_or(KeyError::InvalidFormat)?;
102        if version != 0 || pkcs1_key.other_prime_infos.is_some() {
103            return Err(KeyError::UnsupportedFileFormat);
104        }
105        return build_external_key_from_primes(
106            &pkcs1_key.public_exponent,
107            &pkcs1_key.prime1,
108            &pkcs1_key.prime2,
109        );
110    }
111
112    if let Ok(pkcs1_v0_key) = rasn::der::decode::<RsaPrivateKeyPkcs1V0>(der_bytes) {
113        if pkcs1_v0_key.other_prime_infos.is_some() {
114            return Err(KeyError::UnsupportedFileFormat);
115        }
116        return build_external_key_from_primes(
117            &pkcs1_v0_key.public_exponent,
118            &pkcs1_v0_key.prime1,
119            &pkcs1_v0_key.prime2,
120        );
121    }
122
123    Err(KeyError::InvalidFormat)
124}
125
126/// Parses a DER-encoded RSA private key, supporting only the PKCS#1 format.
127///
128/// # Errors
129///
130/// Returns a `KeyError` if the DER data is malformed or the key parameters are unsupported.
131pub fn parse_rsa_from_der(der_bytes: &[u8]) -> Result<ExternalKey, KeyError> {
132    parse_pkcs1_rsa_from_der(der_bytes)
133}
134
135/// Converts an `RsaPrivateKey` to a `TpmtPublic` structure.
136///
137/// # Errors
138///
139/// Returns a `KeyError` if the key's public modulus cannot be converted to the
140/// `Tpm2bPublicKeyRsa` type.
141pub fn rsa_to_public(
142    key: &RsaPrivateKey,
143    key_bits: u16,
144    hash_alg: TpmAlgId,
145    symmetric: TpmtSymDefObject,
146) -> Result<tpm2_protocol::data::TpmtPublic, KeyError> {
147    Ok(tpm2_protocol::data::TpmtPublic {
148        object_type: TpmAlgId::Rsa,
149        name_alg: hash_alg,
150        object_attributes: TpmaObject::USER_WITH_AUTH | TpmaObject::DECRYPT,
151        auth_policy: Tpm2bDigest::default(),
152        parameters: TpmuPublicParms::Rsa(TpmsRsaParms {
153            symmetric,
154            scheme: TpmtRsaScheme {
155                scheme: TpmAlgId::Oaep,
156                details: TpmuAsymScheme::Any(TpmsSchemeHash { hash_alg }),
157            },
158            key_bits,
159            exponent: 0,
160        }),
161        unique: TpmuPublicId::Rsa(
162            Tpm2bPublicKeyRsa::try_from(key.n().to_bytes_be().as_slice())
163                .map_err(|_| KeyError::InvalidRsaExponent)?,
164        ),
165    })
166}