diff --git a/tss-esapi/Cargo.toml b/tss-esapi/Cargo.toml index 893b755a..48b0f5ca 100644 --- a/tss-esapi/Cargo.toml +++ b/tss-esapi/Cargo.toml @@ -22,6 +22,7 @@ required-features = ["abstraction"] [dependencies] bitfield = "0.17.0" serde = { version = "1.0.115", features = [ + "alloc", "derive", ], optional = true, default-features = false } malloced = "1.3.1" @@ -33,9 +34,23 @@ hostname-validator = "1.1.0" regex = "1.3.9" zeroize = { version = "1.5.7", features = ["zeroize_derive"] } tss-esapi-sys = { path = "../tss-esapi-sys", version = "0.5.0" } -oid = { version = "0.2.1", optional = true } -picky-asn1 = { version = "0.9.0", optional = true } -picky-asn1-x509 = { version = "0.13.0", optional = true } +x509-cert = { version = "0.2.0", optional = true } +ecdsa = { version = "0.16.9", features = ["der", "hazmat", "arithmetic", "verifying"], optional = true } +elliptic-curve = { version = "0.13.8", optional = true, features = ["alloc", "pkcs8"] } +p192 = { version = "0.13.0", optional = true } +p224 = { version = "0.13.2", optional = true } +p256 = { version = "0.13.2", optional = true } +p384 = { version = "0.13.0", optional = true } +p521 = { version = "0.13.3", optional = true } +pkcs8 = { version = "0.10.2", optional = true } +rsa = { version = "0.9", optional = true } +sha1 = { version = "0.10.6", optional = true } +sha2 = { version = "0.10.8", optional = true } +sha3 = { version = "0.10.8", optional = true } +sm2 = { version = "0.13.3", optional = true } +sm3 = { version = "0.4.2", optional = true } +digest = { version = "0.10.7", optional = true } +signature = { version = "2.2.0", features = ["std"], optional = true} cfg-if = "1.0.0" strum = { version = "0.26.3", optional = true } strum_macros = { version = "0.26.4", optional = true } @@ -44,14 +59,15 @@ getrandom = "0.2.11" [dev-dependencies] env_logger = "0.11.5" -sha2 = "0.10.1" serde_json = "^1.0.108" +sha2 = { version = "0.10.8", features = ["oid"] } tss-esapi = { path = ".", features = [ "integration-tests", "serde", "abstraction", + "rustcrypto-full", ] } - +x509-cert = { version = "0.2.0", features = ["builder"] } [build-dependencies] semver = "1.0.7" @@ -59,5 +75,8 @@ semver = "1.0.7" [features] default = ["abstraction"] generate-bindings = ["tss-esapi-sys/generate-bindings"] -abstraction = ["oid", "picky-asn1", "picky-asn1-x509"] +abstraction = ["rustcrypto"] integration-tests = ["strum", "strum_macros"] + +rustcrypto = ["digest", "ecdsa", "elliptic-curve", "pkcs8", "signature", "x509-cert"] +rustcrypto-full = ["rustcrypto", "p192", "p224", "p256", "p384", "p521", "rsa", "sha1", "sha2", "sha3", "sm2", "sm3"] diff --git a/tss-esapi/README.md b/tss-esapi/README.md index 046088e0..f8887da7 100644 --- a/tss-esapi/README.md +++ b/tss-esapi/README.md @@ -28,6 +28,9 @@ The crate currently offers the following features: on top of the basic Rust-native ESAPI API provided by the crate. This feature can be turned off to reduce the number of dependencies built. * `serde` - enable serde `Serialize`/`Deserialize` traits for types. +* `rustcrypto-full` (disabled by default) - provides conversion from all + supported elliptic curves, rsa or hashes. + Support for individual hash, rsa or curves can be pulled individually. ## Cross compiling diff --git a/tss-esapi/src/abstraction/hashing.rs b/tss-esapi/src/abstraction/hashing.rs new file mode 100644 index 00000000..803ac540 --- /dev/null +++ b/tss-esapi/src/abstraction/hashing.rs @@ -0,0 +1,50 @@ +// Copyright 2024 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 + +use crate::interface_types::algorithm::HashingAlgorithm; + +/// Provides the value of the digest used in this crate for the digest. +pub trait AssociatedHashingAlgorithm { + /// Value of the digest when interacting with the TPM. + const TPM_DIGEST: HashingAlgorithm; +} + +#[cfg(feature = "sha1")] +impl AssociatedHashingAlgorithm for sha1::Sha1 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha1; +} + +#[cfg(feature = "sha2")] +impl AssociatedHashingAlgorithm for sha2::Sha256 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha256; +} + +#[cfg(feature = "sha2")] +impl AssociatedHashingAlgorithm for sha2::Sha384 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha384; +} + +#[cfg(feature = "sha2")] +impl AssociatedHashingAlgorithm for sha2::Sha512 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha512; +} + +#[cfg(feature = "sm3")] +impl AssociatedHashingAlgorithm for sm3::Sm3 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sm3_256; +} + +#[cfg(feature = "sha3")] +impl AssociatedHashingAlgorithm for sha3::Sha3_256 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha3_256; +} + +#[cfg(feature = "sha3")] +impl AssociatedHashingAlgorithm for sha3::Sha3_384 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha3_384; +} + +#[cfg(feature = "sha3")] +impl AssociatedHashingAlgorithm for sha3::Sha3_512 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha3_512; +} diff --git a/tss-esapi/src/abstraction/mod.rs b/tss-esapi/src/abstraction/mod.rs index 0453ae19..b066351f 100644 --- a/tss-esapi/src/abstraction/mod.rs +++ b/tss-esapi/src/abstraction/mod.rs @@ -9,6 +9,14 @@ pub mod pcr; pub mod public; pub mod transient; +mod hashing; +mod signatures; +mod signer; +pub use hashing::AssociatedHashingAlgorithm; +pub use signer::EcSigner; +#[cfg(feature = "rsa")] +pub use signer::{RsaPkcsSigner, RsaPssSigner}; + use std::convert::TryFrom; use crate::{ diff --git a/tss-esapi/src/abstraction/public.rs b/tss-esapi/src/abstraction/public.rs index 319278a4..2f0d5bb7 100644 --- a/tss-esapi/src/abstraction/public.rs +++ b/tss-esapi/src/abstraction/public.rs @@ -2,127 +2,250 @@ // SPDX-License-Identifier: Apache-2.0 use crate::interface_types::ecc::EccCurve; -use crate::structures::{Public, RsaExponent}; +use crate::structures::Public; +use crate::utils::PublicKey as TpmPublicKey; use crate::{Error, WrapperErrorKind}; use core::convert::TryFrom; -use oid::ObjectIdentifier; -use picky_asn1::bit_string::BitString; -use picky_asn1::wrapper::{IntegerAsn1, OctetStringAsn1}; -use picky_asn1_x509::{ - AlgorithmIdentifier, EcParameters, EcPoint, PublicKey, RsaPublicKey, SubjectPublicKeyInfo, +use elliptic_curve::{ + generic_array::typenum::Unsigned, + sec1::{EncodedPoint, FromEncodedPoint, ModulusSize, ToEncodedPoint}, + AffinePoint, CurveArithmetic, FieldBytesSize, PublicKey, }; -/// Can be converted from [`crate::structures::Public`] when not a fully constructed -/// [`picky_asn1_x509::SubjectPublicKeyInfo`] is required. -/// -/// # Details -/// Holds either [`picky_asn1_x509::RsaPublicKey`] for [`crate::structures::Public::Rsa`] or -/// [`picky_asn1_x509::EcPoint`] for [`crate::structures::Public::Ecc`]. -/// -/// This object can be serialized and deserialized -/// using serde if the `serde` feature is enabled. -#[derive(Debug, PartialEq, Eq, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub enum DecodedKey { - RsaPublicKey(RsaPublicKey), - EcPoint(EcPoint), +use x509_cert::spki::SubjectPublicKeyInfoOwned; + +#[cfg(feature = "rsa")] +use { + crate::structures::RsaExponent, + rsa::{BigUint, RsaPublicKey}, +}; + +#[cfg(any( + feature = "p192", + feature = "p224", + feature = "p256", + feature = "p384", + feature = "p521", + feature = "rsa", + feature = "sm2" +))] +use pkcs8::EncodePublicKey; + +/// Default exponent for RSA keys. +// Also known as 0x10001 +#[cfg(feature = "rsa")] +const RSA_DEFAULT_EXP: u64 = 65537; + +impl TryFrom<&Public> for PublicKey +where + C: CurveArithmetic + AssociatedTpmCurve, + FieldBytesSize: ModulusSize, + AffinePoint: FromEncodedPoint + ToEncodedPoint, +{ + type Error = Error; + + fn try_from(value: &Public) -> Result { + match value { + Public::Ecc { + parameters, unique, .. + } => { + if parameters.ecc_curve() != C::TPM_CURVE { + return Err(Error::local_error(WrapperErrorKind::InvalidParam)); + } + + let x = unique.x().as_bytes(); + let y = unique.y().as_bytes(); + + if x.len() != FieldBytesSize::::USIZE { + return Err(Error::local_error(WrapperErrorKind::InvalidParam)); + } + if y.len() != FieldBytesSize::::USIZE { + return Err(Error::local_error(WrapperErrorKind::InvalidParam)); + } + + let encoded_point = + EncodedPoint::::from_affine_coordinates(x.into(), y.into(), false); + let public_key = PublicKey::::try_from(&encoded_point) + .map_err(|_| Error::local_error(WrapperErrorKind::InvalidParam))?; + + Ok(public_key) + } + _ => Err(Error::local_error(WrapperErrorKind::UnsupportedParam)), + } + } } -impl TryFrom for DecodedKey { +#[cfg(feature = "rsa")] +impl TryFrom<&Public> for RsaPublicKey { type Error = Error; - fn try_from(value: Public) -> Result { - public_to_decoded_key(&value) + fn try_from(value: &Public) -> Result { + match value { + Public::Rsa { + unique, parameters, .. + } => { + let exponent = match parameters.exponent() { + RsaExponent::ZERO_EXPONENT => BigUint::from(RSA_DEFAULT_EXP), + _ => BigUint::from(parameters.exponent().value()), + }; + let modulus = BigUint::from_bytes_be(unique.as_bytes()); + + let public_key = RsaPublicKey::new(modulus, exponent) + .map_err(|_| Error::local_error(WrapperErrorKind::InvalidParam))?; + + Ok(public_key) + } + _ => Err(Error::local_error(WrapperErrorKind::UnsupportedParam)), + } } } -impl TryFrom for SubjectPublicKeyInfo { +impl TryFrom<&Public> for SubjectPublicKeyInfoOwned { type Error = Error; - /// Converts [`crate::structures::Public::Rsa`] and [`crate::structures::Public::Ecc`] to [`picky_asn1_x509::SubjectPublicKeyInfo`]. + /// Converts [`crate::structures::Public::Rsa`] and [`crate::structures::Public::Ecc`] to [`x509_cert::spki::SubjectPublicKeyInfoOwned`]. /// /// # Details - /// The result can be used to convert TPM public keys to DER using `picky_asn1_der`. + /// The result can be used to convert TPM public keys to DER using `x509-cert`. /// /// # Errors /// * if other instances of [`crate::structures::Public`] are used `UnsupportedParam` will be returned. - fn try_from(value: Public) -> Result { - let decoded_key = public_to_decoded_key(&value)?; - - match (value, decoded_key) { - (Public::Rsa { .. }, DecodedKey::RsaPublicKey(key)) => Ok(SubjectPublicKeyInfo { - algorithm: AlgorithmIdentifier::new_rsa_encryption(), - subject_public_key: PublicKey::Rsa(key.into()), - }), - (Public::Ecc { parameters, .. }, DecodedKey::EcPoint(point)) => { - Ok(SubjectPublicKeyInfo { - algorithm: AlgorithmIdentifier::new_elliptic_curve(EcParameters::NamedCurve( - curve_oid(parameters.ecc_curve())?.into(), - )), - subject_public_key: PublicKey::Ec(BitString::with_bytes(point).into()), - }) + fn try_from(value: &Public) -> Result { + match value { + #[cfg(feature = "rsa")] + Public::Rsa { .. } => { + let public_key = RsaPublicKey::try_from(value)?; + + Ok(public_key + .to_public_key_der() + .map_err(|_| Error::local_error(WrapperErrorKind::InvalidParam))? + .decode_msg::() + .map_err(|_| Error::local_error(WrapperErrorKind::InvalidParam))?) + } + #[allow(unused)] + Public::Ecc { parameters, .. } => { + macro_rules! read_key { + ($key_type:ty) => { + if parameters.ecc_curve() == <$key_type>::TPM_CURVE { + let public_key = PublicKey::<$key_type>::try_from(value)?; + + return public_key + .to_public_key_der() + .map_err(|_| Error::local_error(WrapperErrorKind::InvalidParam))? + .decode_msg::() + .map_err(|_| Error::local_error(WrapperErrorKind::InvalidParam)); + } + }; + } + + #[cfg(feature = "p192")] + read_key!(p192::NistP192); + #[cfg(feature = "p224")] + read_key!(p224::NistP224); + #[cfg(feature = "p256")] + read_key!(p256::NistP256); + #[cfg(feature = "p384")] + read_key!(p384::NistP384); + #[cfg(feature = "p521")] + read_key!(p521::NistP521); + #[cfg(feature = "sm2")] + read_key!(sm2::Sm2); + + Err(Error::local_error(WrapperErrorKind::UnsupportedParam)) } _ => Err(Error::local_error(WrapperErrorKind::UnsupportedParam)), } } } -/// Converts [`crate::structures::Public::Rsa`] and [`crate::structures::Public::Ecc`] to [DecodedKey]. -/// -/// # Details -/// Does basic key conversion to either RSA or ECC. In RSA conversion the TPM zero exponent is replaced with `65537`. -/// -/// # Errors -/// * if other instances of [`crate::structures::Public`] are used `UnsupportedParam` will be returned. -fn public_to_decoded_key(public: &Public) -> Result { - match public { - Public::Rsa { - unique, parameters, .. - } => { - let exponent = match parameters.exponent() { - RsaExponent::ZERO_EXPONENT => 65537, - _ => parameters.exponent().value(), +impl TryFrom<&TpmPublicKey> for PublicKey +where + C: CurveArithmetic + AssociatedTpmCurve, + FieldBytesSize: ModulusSize, + AffinePoint: FromEncodedPoint + ToEncodedPoint, +{ + type Error = Error; + + fn try_from(value: &TpmPublicKey) -> Result { + match value { + TpmPublicKey::Ecc { x, y } => { + let x = x.as_slice(); + let y = y.as_slice(); + + // TODO: When elliptic_curve bumps to 0.14, we can use the TryFrom implementation instead + // of checking lengths manually + if x.len() != FieldBytesSize::::USIZE { + return Err(Error::local_error(WrapperErrorKind::InvalidParam)); + } + if y.len() != FieldBytesSize::::USIZE { + return Err(Error::local_error(WrapperErrorKind::InvalidParam)); + } + + let encoded_point = + EncodedPoint::::from_affine_coordinates(x.into(), y.into(), false); + let public_key = PublicKey::::try_from(&encoded_point) + .map_err(|_| Error::local_error(WrapperErrorKind::InvalidParam))?; + + Ok(public_key) } - .to_be_bytes(); - Ok(DecodedKey::RsaPublicKey(RsaPublicKey { - modulus: IntegerAsn1::from_bytes_be_unsigned(unique.as_bytes().to_vec()), - public_exponent: IntegerAsn1::from_bytes_be_signed(exponent.to_vec()), - })) - } - Public::Ecc { unique, .. } => { - let x = unique.x().as_bytes().to_vec(); - let y = unique.y().as_bytes().to_vec(); - Ok(DecodedKey::EcPoint(OctetStringAsn1( - elliptic_curve_point_to_octet_string(x, y), - ))) + _ => Err(Error::local_error(WrapperErrorKind::UnsupportedParam)), } + } +} + +#[cfg(feature = "rsa")] +impl TryFrom<&TpmPublicKey> for RsaPublicKey { + type Error = Error; - _ => Err(Error::local_error(WrapperErrorKind::UnsupportedParam)), + fn try_from(value: &TpmPublicKey) -> Result { + match value { + TpmPublicKey::Rsa(modulus) => { + let exponent = BigUint::from(RSA_DEFAULT_EXP); + let modulus = BigUint::from_bytes_be(modulus.as_slice()); + + let public_key = RsaPublicKey::new(modulus, exponent) + .map_err(|_| Error::local_error(WrapperErrorKind::InvalidParam))?; + + Ok(public_key) + } + _ => Err(Error::local_error(WrapperErrorKind::UnsupportedParam)), + } } } -// Taken from https://github.com/parallaxsecond/parsec/blob/561235f3cc37bcff3d9a6cb29c84eeae5d55100b/src/providers/tpm/utils.rs#L319 -// Points on elliptic curves are represented as defined in section 2.3.3 of https://www.secg.org/sec1-v2.pdf -// The (uncompressed) representation is [ 0x04 || x || y ] where x and y are the coordinates of the point -fn elliptic_curve_point_to_octet_string(mut x: Vec, mut y: Vec) -> Vec { - let mut octet_string = vec![0x04]; - octet_string.append(&mut x); - octet_string.append(&mut y); - octet_string +/// Provides the value of the curve used in this crate for the specific curve. +pub trait AssociatedTpmCurve { + /// Value of the curve when interacting with the TPM. + const TPM_CURVE: EccCurve; } -// Map TPM supported ECC curves to their respective OIDs -fn curve_oid(ecc_curve: EccCurve) -> Result { - match ecc_curve { - EccCurve::NistP192 => Ok(picky_asn1_x509::oids::secp192r1()), - EccCurve::NistP224 => Ok(picky_asn1_x509::oids::secp256r1()), - EccCurve::NistP256 => Ok(picky_asn1_x509::oids::secp256r1()), - EccCurve::NistP384 => Ok(picky_asn1_x509::oids::secp384r1()), - EccCurve::NistP521 => Ok(picky_asn1_x509::oids::secp521r1()), - // Barreto-Naehrig curves seem to not have any OIDs - EccCurve::BnP256 => Err(Error::local_error(WrapperErrorKind::UnsupportedParam)), - EccCurve::BnP638 => Err(Error::local_error(WrapperErrorKind::UnsupportedParam)), - EccCurve::Sm2P256 => Ok(ObjectIdentifier::try_from("1.2.156.10197.1.301").unwrap()), - } +#[cfg(feature = "p192")] +impl AssociatedTpmCurve for p192::NistP192 { + const TPM_CURVE: EccCurve = EccCurve::NistP192; +} + +#[cfg(feature = "p224")] +impl AssociatedTpmCurve for p224::NistP224 { + const TPM_CURVE: EccCurve = EccCurve::NistP224; +} + +#[cfg(feature = "p256")] +impl AssociatedTpmCurve for p256::NistP256 { + const TPM_CURVE: EccCurve = EccCurve::NistP256; +} + +#[cfg(feature = "p384")] +impl AssociatedTpmCurve for p384::NistP384 { + const TPM_CURVE: EccCurve = EccCurve::NistP384; +} + +#[cfg(feature = "p521")] +impl AssociatedTpmCurve for p521::NistP521 { + const TPM_CURVE: EccCurve = EccCurve::NistP521; +} + +#[cfg(feature = "sm2")] +impl AssociatedTpmCurve for sm2::Sm2 { + const TPM_CURVE: EccCurve = EccCurve::Sm2P256; } diff --git a/tss-esapi/src/abstraction/signatures.rs b/tss-esapi/src/abstraction/signatures.rs new file mode 100644 index 00000000..e4b8a578 --- /dev/null +++ b/tss-esapi/src/abstraction/signatures.rs @@ -0,0 +1,74 @@ +// Copyright 2024 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 + +use crate::{structures::EccSignature, Error, Result, WrapperErrorKind}; + +use std::convert::TryFrom; + +use ecdsa::SignatureSize; +use elliptic_curve::{ + generic_array::{typenum::Unsigned, ArrayLength}, + FieldBytes, FieldBytesSize, PrimeCurve, +}; + +#[cfg(feature = "rsa")] +use crate::structures::Signature; + +impl TryFrom for ecdsa::Signature +where + C: PrimeCurve, + SignatureSize: ArrayLength, +{ + type Error = Error; + + fn try_from(signature: EccSignature) -> Result { + let r = signature.signature_r().as_slice(); + let s = signature.signature_s().as_slice(); + + if r.len() != FieldBytesSize::::USIZE { + return Err(Error::local_error(WrapperErrorKind::InvalidParam)); + } + if s.len() != FieldBytesSize::::USIZE { + return Err(Error::local_error(WrapperErrorKind::InvalidParam)); + } + + let signature = ecdsa::Signature::from_scalars( + FieldBytes::::from_slice(r).clone(), + FieldBytes::::from_slice(s).clone(), + ) + .map_err(|_| Error::local_error(WrapperErrorKind::InvalidParam))?; + Ok(signature) + } +} + +// Note: this does not implement `TryFrom` because `RsaSignature` does not carry the +// information whether the signatures was generated using PKCS#1v1.5 or PSS. +#[cfg(feature = "rsa")] +impl TryFrom for rsa::pkcs1v15::Signature { + type Error = Error; + + fn try_from(signature: Signature) -> Result { + let Signature::RsaSsa(signature) = signature else { + return Err(Error::local_error(WrapperErrorKind::InvalidParam)); + }; + + Self::try_from(signature.signature().as_bytes()) + .map_err(|_| Error::local_error(WrapperErrorKind::InvalidParam)) + } +} + +// Note: this does not implement `TryFrom` because `RsaSignature` does not carry the +// information whether the signatures was generated using PKCS#1v1.5 or PSS. +#[cfg(feature = "rsa")] +impl TryFrom for rsa::pss::Signature { + type Error = Error; + + fn try_from(signature: Signature) -> Result { + let Signature::RsaPss(signature) = signature else { + return Err(Error::local_error(WrapperErrorKind::InvalidParam)); + }; + + Self::try_from(signature.signature().as_bytes()) + .map_err(|_| Error::local_error(WrapperErrorKind::InvalidParam)) + } +} diff --git a/tss-esapi/src/abstraction/signer.rs b/tss-esapi/src/abstraction/signer.rs new file mode 100644 index 00000000..7689e1a7 --- /dev/null +++ b/tss-esapi/src/abstraction/signer.rs @@ -0,0 +1,616 @@ +// Copyright 2024 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 + +//! Module for exposing a [`signature::Signer`] interface for keys +//! +//! This modules presents objects held in a TPM over a [`signature::DigestSigner`] interface. +use crate::{ + abstraction::{ + public::AssociatedTpmCurve, + transient::{KeyMaterial, KeyParams, TransientKeyContext}, + AssociatedHashingAlgorithm, + }, + handles::KeyHandle, + interface_types::algorithm::EccSchemeAlgorithm, + structures::{ + Auth, Digest as TpmDigest, EccScheme, Public, Signature as TpmSignature, SignatureScheme, + }, + utils::PublicKey as TpmPublicKey, + Context, Error, WrapperErrorKind, +}; + +use std::{convert::TryFrom, ops::Add, sync::Mutex}; + +use digest::{Digest, FixedOutput, Output}; +use ecdsa::{ + der::{MaxOverhead, MaxSize, Signature as DerSignature}, + hazmat::{DigestPrimitive, SignPrimitive}, + Signature, SignatureSize, VerifyingKey, +}; +use elliptic_curve::{ + generic_array::ArrayLength, + ops::Invert, + sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}, + subtle::CtOption, + AffinePoint, CurveArithmetic, FieldBytesSize, PrimeCurve, PublicKey, Scalar, +}; +use log::error; +use signature::{DigestSigner, Error as SigError, KeypairRef, Signer}; +use x509_cert::{ + der::asn1::AnyRef, + spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier, SignatureAlgorithmIdentifier}, +}; + +pub trait TpmSigner { + fn public(&self) -> crate::Result; + fn key_params(&self) -> crate::Result; + fn sign(&self, digest: TpmDigest) -> crate::Result; +} + +impl TpmSigner for (Mutex<&'_ mut Context>, KeyHandle) { + fn public(&self) -> crate::Result { + let mut context = self.0.lock().expect("Mutex got poisoned"); + let (public, _, _) = context.read_public(self.1)?; + + TpmPublicKey::try_from(public) + } + + fn key_params(&self) -> crate::Result { + let mut context = self.0.lock().expect("Mutex got poisoned"); + let (public, _, _) = context.read_public(self.1)?; + + match public { + Public::Rsa { parameters, .. } => Ok(KeyParams::Rsa { + size: parameters.key_bits(), + scheme: parameters.rsa_scheme(), + pub_exponent: parameters.exponent(), + }), + Public::Ecc { parameters, .. } => Ok(KeyParams::Ecc { + curve: parameters.ecc_curve(), + scheme: parameters.ecc_scheme(), + }), + other => { + error!("Unsupported key parameter used: {other:?}"); + Err(Error::local_error(WrapperErrorKind::InvalidParam)) + } + } + } + + fn sign(&self, digest: TpmDigest) -> crate::Result { + let mut context = self.0.lock().expect("Mutex got poisoned"); + context.sign(self.1, digest, SignatureScheme::Null, None) + } +} + +impl TpmSigner + for ( + Mutex<&'_ mut TransientKeyContext>, + KeyMaterial, + KeyParams, + Option, + ) +{ + fn public(&self) -> crate::Result { + Ok(self.1.public().clone()) + } + + fn key_params(&self) -> crate::Result { + Ok(self.2) + } + + fn sign(&self, digest: TpmDigest) -> crate::Result { + let mut context = self.0.lock().expect("Mutex got poisoned"); + context.sign(self.1.clone(), self.2, self.3.clone(), digest) + } +} + +/// [`EcSigner`] will sign a payload with an elliptic curve secret key stored on the TPM. +/// +/// # Parameters +/// +/// Parameter `C` describes the curve that is of use (Nist P-256, Nist P-384, ...) +/// +/// ```no_run +/// # use std::sync::Mutex; +/// # use tss_esapi::{ +/// # abstraction::{EcSigner, transient::TransientKeyContextBuilder}, +/// # TctiNameConf +/// # }; +/// use p256::NistP256; +/// use signature::Signer; +/// # +/// # // Create context +/// # let mut context = TransientKeyContextBuilder::new() +/// # .with_tcti( +/// # TctiNameConf::from_environment_variable().expect("Failed to get TCTI"), +/// # ) +/// # .build() +/// # .expect("Failed to create Context"); +/// +/// let key_params = EcSigner::::key_params_default(); +/// let (tpm_km, _tpm_auth) = context +/// .create_key(key_params, 0) +/// .expect("Failed to create a private keypair"); +/// +/// let signer = EcSigner::::new((Mutex::new(&mut context), tpm_km, key_params, None)) +/// .expect("Failed to create a signer"); +/// let signature: p256::ecdsa::Signature = signer.sign(b"Hello Bob, Alice here."); +/// ``` +#[derive(Debug)] +pub struct EcSigner +where + C: PrimeCurve + CurveArithmetic, +{ + context: Ctx, + verifying_key: VerifyingKey, +} + +impl EcSigner +where + C: PrimeCurve + CurveArithmetic, + C: AssociatedTpmCurve, + FieldBytesSize: ModulusSize, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + + Ctx: TpmSigner, +{ + pub fn new(context: Ctx) -> Result { + match context.key_params()? { + KeyParams::Ecc { curve, .. } if curve == C::TPM_CURVE => {} + other => { + error!( + "Unsupported key parameters: {other:?}, expected Ecc(curve: {:?})", + C::default() + ); + return Err(Error::local_error(WrapperErrorKind::InvalidParam)); + } + } + + let public_key = context.public()?; + let public_key = PublicKey::try_from(&public_key)?; + let verifying_key = VerifyingKey::from(public_key); + + Ok(Self { + context, + verifying_key, + }) + } +} + +impl EcSigner +where + C: PrimeCurve + CurveArithmetic, + C: AssociatedTpmCurve, +{ + /// Key parameters for this curve, selected digest is the one selected by DigestPrimitive + pub fn key_params_default() -> KeyParams + where + C: DigestPrimitive, + ::Digest: FixedOutput>, + ::Digest: AssociatedHashingAlgorithm, + { + Self::key_params::<::Digest>() + } + + /// Key parameters for this curve + /// + /// # Parameters + /// + /// The hashing algorithm `D` is the digest that will be used for signatures (SHA-256, SHA3-256, ...). + pub fn key_params() -> KeyParams + where + D: FixedOutput>, + D: AssociatedHashingAlgorithm, + { + KeyParams::Ecc { + curve: C::TPM_CURVE, + scheme: EccScheme::create(EccSchemeAlgorithm::EcDsa, Some(D::TPM_DIGEST), None) + .expect("Failed to create ecc scheme"), + } + } +} + +impl AsRef> for EcSigner +where + C: PrimeCurve + CurveArithmetic, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, +{ + fn as_ref(&self) -> &VerifyingKey { + &self.verifying_key + } +} + +impl KeypairRef for EcSigner +where + C: PrimeCurve + CurveArithmetic, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, +{ + type VerifyingKey = VerifyingKey; +} + +impl DigestSigner> for EcSigner +where + C: PrimeCurve + CurveArithmetic, + C: AssociatedTpmCurve, + D: Digest + FixedOutput>, + D: AssociatedHashingAlgorithm, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, + TpmDigest: From>, + Ctx: TpmSigner, +{ + fn try_sign_digest(&self, digest: D) -> Result, SigError> { + let digest = TpmDigest::from(digest.finalize_fixed()); + + //let key_params = Self::key_params::(); + let signature = self.context.sign(digest).map_err(SigError::from_source)?; + + let TpmSignature::EcDsa(signature) = signature else { + return Err(SigError::from_source(Error::local_error( + WrapperErrorKind::InvalidParam, + ))); + }; + + let signature = Signature::try_from(signature).map_err(SigError::from_source)?; + + Ok(signature) + } +} + +impl DigestSigner> for EcSigner +where + C: PrimeCurve + CurveArithmetic, + C: AssociatedTpmCurve, + D: Digest + FixedOutput>, + D: AssociatedHashingAlgorithm, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, + TpmDigest: From>, + + MaxSize: ArrayLength, + as Add>::Output: Add + ArrayLength, + + Ctx: TpmSigner, +{ + fn try_sign_digest(&self, digest: D) -> Result, SigError> { + let signature: Signature<_> = self.try_sign_digest(digest)?; + Ok(signature.to_der()) + } +} + +impl Signer> for EcSigner +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + C: AssociatedTpmCurve, + ::Digest: AssociatedHashingAlgorithm, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, + TpmDigest: From::Digest>>, + + Ctx: TpmSigner, +{ + fn try_sign(&self, msg: &[u8]) -> Result, SigError> { + self.try_sign_digest(C::Digest::new_with_prefix(msg)) + } +} + +impl Signer> for EcSigner +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + C: AssociatedTpmCurve, + ::Digest: AssociatedHashingAlgorithm, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, + TpmDigest: From::Digest>>, + + MaxSize: ArrayLength, + as Add>::Output: Add + ArrayLength, + + Ctx: TpmSigner, +{ + fn try_sign(&self, msg: &[u8]) -> Result, SigError> { + self.try_sign_digest(C::Digest::new_with_prefix(msg)) + } +} + +impl SignatureAlgorithmIdentifier for EcSigner +where + C: PrimeCurve + CurveArithmetic, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, + Signature: AssociatedAlgorithmIdentifier>, +{ + type Params = AnyRef<'static>; + + const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier = + Signature::::ALGORITHM_IDENTIFIER; +} + +#[cfg(feature = "rsa")] +mod rsa { + use super::TpmSigner; + + use crate::{ + abstraction::{signer::KeyParams, AssociatedHashingAlgorithm}, + structures::{Digest as TpmDigest, RsaScheme}, + Error, WrapperErrorKind, + }; + + use std::fmt; + + use digest::{Digest, FixedOutput, Output}; + use log::error; + use pkcs8::AssociatedOid; + use signature::{DigestSigner, Error as SigError, Keypair, Signer}; + use x509_cert::{ + der::asn1::AnyRef, + spki::{ + self, AlgorithmIdentifier, AlgorithmIdentifierOwned, AssociatedAlgorithmIdentifier, + DynSignatureAlgorithmIdentifier, SignatureAlgorithmIdentifier, + }, + }; + + use ::rsa::{pkcs1v15, pss, RsaPublicKey}; + + /// [`RsaPkcsSigner`] will sign a payload with an RSA secret key stored on the TPM. + /// + /// ```no_run + /// # use std::sync::Mutex; + /// # use tss_esapi::{ + /// # abstraction::{RsaPkcsSigner, transient::{TransientKeyContextBuilder, KeyParams}}, + /// # interface_types::{algorithm::{HashingAlgorithm, RsaSchemeAlgorithm}, key_bits::RsaKeyBits}, + /// # structures::{RsaExponent, RsaScheme}, + /// # TctiNameConf + /// # }; + /// use signature::Signer; + /// # + /// # // Create context + /// # let mut context = TransientKeyContextBuilder::new() + /// # .with_tcti( + /// # TctiNameConf::from_environment_variable().expect("Failed to get TCTI"), + /// # ) + /// # .build() + /// # .expect("Failed to create Context"); + /// + /// let key_params = KeyParams::Rsa { + /// size: RsaKeyBits::Rsa1024, + /// scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaSsa, Some(HashingAlgorithm::Sha256)) + /// .expect("Failed to create RSA scheme"), + /// pub_exponent: RsaExponent::default(), + /// }; + /// let (tpm_km, _tpm_auth) = context + /// .create_key(key_params, 0) + /// .expect("Failed to create a private keypair"); + /// + /// let signer = RsaPkcsSigner::<_, sha2::Sha256>::new((Mutex::new(&mut context), tpm_km, key_params, None)) + /// .expect("Failed to create a signer"); + /// let signature = signer.sign(b"Hello Bob, Alice here."); + /// ``` + #[derive(Debug)] + pub struct RsaPkcsSigner + where + D: Digest, + { + context: Ctx, + verifying_key: pkcs1v15::VerifyingKey, + } + + impl RsaPkcsSigner + where + Ctx: TpmSigner, + D: Digest + AssociatedOid + AssociatedHashingAlgorithm + fmt::Debug, + { + pub fn new(context: Ctx) -> Result { + match context.key_params()? { + KeyParams::Rsa { + scheme: RsaScheme::RsaSsa(hash), + .. + } if hash.hashing_algorithm() == D::TPM_DIGEST => {} + other => { + error!( + "Unsupported key parameters: {other:?}, expected RsaSsa({:?})", + D::new() + ); + return Err(Error::local_error(WrapperErrorKind::InvalidParam)); + } + } + + let public_key = context.public()?; + let public_key = RsaPublicKey::try_from(&public_key)?; + let verifying_key = pkcs1v15::VerifyingKey::new(public_key); + + Ok(Self { + context, + verifying_key, + }) + } + } + + impl Keypair for RsaPkcsSigner + where + D: Digest, + { + type VerifyingKey = pkcs1v15::VerifyingKey; + + fn verifying_key(&self) -> Self::VerifyingKey { + self.verifying_key.clone() + } + } + + impl DigestSigner for RsaPkcsSigner + where + D: Digest + FixedOutput, + D: AssociatedHashingAlgorithm, + TpmDigest: From>, + Ctx: TpmSigner, + { + fn try_sign_digest(&self, digest: D) -> Result { + let digest = TpmDigest::from(digest.finalize_fixed()); + + //let key_params = Self::key_params::(); + let signature = self.context.sign(digest).map_err(SigError::from_source)?; + + let signature = + pkcs1v15::Signature::try_from(signature).map_err(SigError::from_source)?; + + Ok(signature) + } + } + + impl Signer for RsaPkcsSigner + where + D: Digest + FixedOutput, + D: AssociatedHashingAlgorithm, + TpmDigest: From>, + Ctx: TpmSigner, + { + fn try_sign(&self, msg: &[u8]) -> Result { + let mut d = D::new(); + Digest::update(&mut d, msg); + + self.try_sign_digest(d) + } + } + + impl SignatureAlgorithmIdentifier for RsaPkcsSigner + where + D: Digest + pkcs1v15::RsaSignatureAssociatedOid, + { + type Params = AnyRef<'static>; + + const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier = + pkcs1v15::SigningKey::::ALGORITHM_IDENTIFIER; + } + + /// [`RsaPssSigner`] will sign a payload with an RSA secret key stored on the TPM. + /// + /// ```no_run + /// # use std::sync::Mutex; + /// # use tss_esapi::{ + /// # abstraction::{RsaPssSigner, transient::{TransientKeyContextBuilder, KeyParams}}, + /// # interface_types::{algorithm::{HashingAlgorithm, RsaSchemeAlgorithm}, key_bits::RsaKeyBits}, + /// # structures::{RsaExponent, RsaScheme}, + /// # TctiNameConf + /// # }; + /// use signature::Signer; + /// # + /// # // Create context + /// # let mut context = TransientKeyContextBuilder::new() + /// # .with_tcti( + /// # TctiNameConf::from_environment_variable().expect("Failed to get TCTI"), + /// # ) + /// # .build() + /// # .expect("Failed to create Context"); + /// + /// let key_params = KeyParams::Rsa { + /// size: RsaKeyBits::Rsa1024, + /// scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaPss, Some(HashingAlgorithm::Sha256)) + /// .expect("Failed to create RSA scheme"), + /// pub_exponent: RsaExponent::default(), + /// }; + /// let (tpm_km, _tpm_auth) = context + /// .create_key(key_params, 0) + /// .expect("Failed to create a private keypair"); + /// + /// let signer = RsaPssSigner::<_, sha2::Sha256>::new((Mutex::new(&mut context), tpm_km, key_params, None)) + /// .expect("Failed to create a signer"); + /// let signature = signer.sign(b"Hello Bob, Alice here."); + /// ``` + #[derive(Debug)] + pub struct RsaPssSigner + where + D: Digest, + { + context: Ctx, + verifying_key: pss::VerifyingKey, + } + + impl RsaPssSigner + where + Ctx: TpmSigner, + D: Digest + AssociatedHashingAlgorithm + fmt::Debug, + { + pub fn new(context: Ctx) -> Result { + match context.key_params()? { + KeyParams::Rsa { + scheme: RsaScheme::RsaPss(hash), + .. + } if hash.hashing_algorithm() == D::TPM_DIGEST => {} + other => { + error!( + "Unsupported key parameters: {other:?}, expected RsaSsa({:?})", + D::new() + ); + return Err(Error::local_error(WrapperErrorKind::InvalidParam)); + } + } + + let public_key = context.public()?; + let public_key = RsaPublicKey::try_from(&public_key)?; + let verifying_key = pss::VerifyingKey::new(public_key); + + Ok(Self { + context, + verifying_key, + }) + } + } + + impl Keypair for RsaPssSigner + where + D: Digest, + { + type VerifyingKey = pss::VerifyingKey; + + fn verifying_key(&self) -> Self::VerifyingKey { + self.verifying_key.clone() + } + } + + impl DigestSigner for RsaPssSigner + where + D: Digest + FixedOutput, + D: AssociatedHashingAlgorithm, + TpmDigest: From>, + Ctx: TpmSigner, + { + fn try_sign_digest(&self, digest: D) -> Result { + let digest = TpmDigest::from(digest.finalize_fixed()); + + let signature = self.context.sign(digest).map_err(SigError::from_source)?; + + let signature = pss::Signature::try_from(signature).map_err(SigError::from_source)?; + + Ok(signature) + } + } + + impl Signer for RsaPssSigner + where + D: Digest + FixedOutput, + D: AssociatedHashingAlgorithm, + TpmDigest: From>, + Ctx: TpmSigner, + { + fn try_sign(&self, msg: &[u8]) -> Result { + let mut d = D::new(); + Digest::update(&mut d, msg); + + self.try_sign_digest(d) + } + } + + impl DynSignatureAlgorithmIdentifier for RsaPssSigner + where + D: Digest + AssociatedOid, + { + fn signature_algorithm_identifier(&self) -> spki::Result { + pss::get_default_pss_signature_algo_id::() + } + } +} + +#[cfg(feature = "rsa")] +pub use self::rsa::{RsaPkcsSigner, RsaPssSigner}; diff --git a/tss-esapi/src/structures/buffers.rs b/tss-esapi/src/structures/buffers.rs index acdc514c..7e6ef094 100644 --- a/tss-esapi/src/structures/buffers.rs +++ b/tss-esapi/src/structures/buffers.rs @@ -124,6 +124,7 @@ pub mod data { pub mod digest { use crate::tss2_esys::TPMU_HA; use std::mem::size_of; + const TPM2B_DIGEST_BUFFER_SIZE: usize = size_of::(); buffer_type!(Digest, TPM2B_DIGEST_BUFFER_SIZE, TPM2B_DIGEST); @@ -220,6 +221,47 @@ pub mod digest { Digest(value_as_vec.into()) } } + + #[cfg(feature = "rustcrypto")] + mod rustcrypto { + use digest::{ + consts::{U20, U32, U48, U64}, + generic_array::GenericArray, + typenum::Unsigned, + }; + + use super::*; + + macro_rules! impl_from_digest { + ($($size:ty),+) => { + $(impl From> for Digest { + fn from(mut value: GenericArray) -> Self { + let value_as_vec = value.as_slice().to_vec(); + value.zeroize(); + Digest(value_as_vec.into()) + } + } + + impl TryFrom for GenericArray { + type Error = Error; + + fn try_from(value: Digest) -> Result { + if value.len() != <$size>::USIZE { + return Err(Error::local_error(WrapperErrorKind::WrongParamSize)); + } + + let mut result = [0; <$size>::USIZE]; + + result.copy_from_slice(value.as_bytes()); + + Ok(result.into()) + } + })+ + } + } + + impl_from_digest!(U20, U32, U48, U64); + } } pub mod ecc_parameter { diff --git a/tss-esapi/src/structures/tagged/public.rs b/tss-esapi/src/structures/tagged/public.rs index ebb7bc7c..e4580eaa 100644 --- a/tss-esapi/src/structures/tagged/public.rs +++ b/tss-esapi/src/structures/tagged/public.rs @@ -13,9 +13,9 @@ use crate::{ Error, Result, ReturnCode, WrapperErrorKind, }; +use self::rsa::PublicRsaParameters; use ecc::PublicEccParameters; use keyed_hash::PublicKeyedHashParameters; -use rsa::PublicRsaParameters; use log::error; use std::convert::{TryFrom, TryInto}; diff --git a/tss-esapi/tests/integration_tests/abstraction_tests/public_tests.rs b/tss-esapi/tests/integration_tests/abstraction_tests/public_tests.rs index 73e7f992..582d9817 100644 --- a/tss-esapi/tests/integration_tests/abstraction_tests/public_tests.rs +++ b/tss-esapi/tests/integration_tests/abstraction_tests/public_tests.rs @@ -2,11 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 mod public_rsa_test { - use picky_asn1::wrapper::IntegerAsn1; - use picky_asn1_x509::{AlgorithmIdentifier, PublicKey, SubjectPublicKeyInfo}; + use rsa::{pkcs1, traits::PublicKeyParts, BigUint}; use std::convert::TryFrom; use tss_esapi::{ - abstraction::public::DecodedKey, attributes::ObjectAttributesBuilder, interface_types::{ algorithm::{HashingAlgorithm, PublicAlgorithm, RsaSchemeAlgorithm}, @@ -14,6 +12,7 @@ mod public_rsa_test { }, structures::{Public, PublicBuilder, PublicKeyRsa, PublicRsaParametersBuilder, RsaScheme}, }; + use x509_cert::{der::referenced::RefToOwned, spki::SubjectPublicKeyInfoOwned}; const RSA_KEY: [u8; 256] = [ 0xc9, 0x75, 0xf8, 0xb2, 0x30, 0xf4, 0x24, 0x6e, 0x95, 0xb1, 0x3c, 0x55, 0x0f, 0xe4, 0x48, @@ -36,6 +35,8 @@ mod public_rsa_test { 0x3f, ]; + const RSA_DEFAULT_EXP: u64 = 65537; + pub fn get_ext_rsa_pub() -> Public { let object_attributes = ObjectAttributesBuilder::new() .with_user_with_auth(true) @@ -70,44 +71,38 @@ mod public_rsa_test { #[test] fn test_public_to_decoded_key_rsa() { let public_rsa = get_ext_rsa_pub(); - let default_exponent = IntegerAsn1::from_bytes_be_signed(65537_u32.to_be_bytes().to_vec()); - let decoded_key = DecodedKey::try_from(public_rsa) + let default_exponent = BigUint::from(RSA_DEFAULT_EXP); + let key = rsa::RsaPublicKey::try_from(&public_rsa) .expect("Failed to convert Public structure to DecodedKey (RSA)."); - match decoded_key { - DecodedKey::RsaPublicKey(key) => { - assert_eq!( - key.public_exponent, default_exponent, - "RSA exponents are not equal." - ); - assert_eq!(key.modulus.as_unsigned_bytes_be(), RSA_KEY); - } - DecodedKey::EcPoint(..) => panic!("RSA key was decoded to EcPoint!"), - } + assert_eq!(key.e(), &default_exponent, "RSA exponents are not equal."); + assert_eq!(key.n().to_bytes_be(), RSA_KEY); } #[test] fn test_public_to_subject_public_key_info_rsa() { let public_rsa = get_ext_rsa_pub(); - let default_exponent = IntegerAsn1::from_bytes_be_signed(65537_u32.to_be_bytes().to_vec()); - let key = SubjectPublicKeyInfo::try_from(public_rsa) + let key = SubjectPublicKeyInfoOwned::try_from(&public_rsa) .expect("Failed to convert Public structure to SubjectPublicKeyInfo (RSA)."); - assert_eq!(key.algorithm, AlgorithmIdentifier::new_rsa_encryption()); - match key.subject_public_key { - PublicKey::Rsa(key) => { - assert_eq!(key.public_exponent, default_exponent); - assert_eq!(key.modulus.as_unsigned_bytes_be(), RSA_KEY) - } - _ => panic!("PublicKey of SubjectPublicKeyInfo is not an instance for RSA"), - } + let default_exponent = BigUint::from(RSA_DEFAULT_EXP); + assert_eq!(key.algorithm, pkcs1::ALGORITHM_ID.ref_to_owned()); + let pkcs1_key = pkcs1::RsaPublicKey::try_from( + key.subject_public_key + .as_bytes() + .expect("non bitstring serialized"), + ) + .expect("non rsa key serialized"); + + assert_eq!( + pkcs1_key.public_exponent.as_bytes(), + default_exponent.to_bytes_be() + ); + assert_eq!(pkcs1_key.modulus.as_bytes(), RSA_KEY); } } mod public_ecc_test { - use picky_asn1::bit_string::BitString; - use picky_asn1_x509::{AlgorithmIdentifier, EcParameters, PublicKey, SubjectPublicKeyInfo}; use std::convert::TryFrom; use tss_esapi::{ - abstraction::public::DecodedKey, attributes::ObjectAttributesBuilder, interface_types::{ algorithm::{HashingAlgorithm, PublicAlgorithm}, @@ -118,6 +113,10 @@ mod public_ecc_test { PublicEccParametersBuilder, }, }; + use x509_cert::{ + der::referenced::OwnedToRef, + spki::{AssociatedAlgorithmIdentifier, SubjectPublicKeyInfoOwned}, + }; const EC_POINT: [u8; 65] = [ 0x04, 0x14, 0xd8, 0x59, 0xec, 0x31, 0xe5, 0x94, 0x0f, 0x2b, 0x3a, 0x08, 0x97, 0x64, 0xc4, @@ -166,35 +165,33 @@ mod public_ecc_test { #[test] fn test_public_to_decoded_key_ecc() { let public_ecc = get_ext_ecc_pub(); - let decoded_key = DecodedKey::try_from(public_ecc) + let key = p256::PublicKey::try_from(&public_ecc) .expect("Failed to convert Public structure to DecodedKey (ECC)."); - match decoded_key { - DecodedKey::RsaPublicKey(..) => panic!("ECC key was decoded to RsaPublicKey!"), - DecodedKey::EcPoint(ec_point) => { - assert_eq!(ec_point.to_vec(), EC_POINT.to_vec()); - } - } + let ec_point = p256::EncodedPoint::from(key); + assert_eq!(ec_point.as_bytes(), EC_POINT.to_vec()); } #[test] fn test_public_to_subject_public_key_info_ecc() { let public_ecc = get_ext_ecc_pub(); - let key = SubjectPublicKeyInfo::try_from(public_ecc) + let key = SubjectPublicKeyInfoOwned::try_from(&public_ecc) .expect("Failed to convert Public structure to SubjectPublicKeyInfo (ECC)."); - assert_eq!( - key.algorithm, - AlgorithmIdentifier::new_elliptic_curve(EcParameters::NamedCurve( - picky_asn1_x509::oids::secp256r1().into() - )) - ); - match key.subject_public_key { - PublicKey::Ec(ec_point) => { - let ec_point_bitstring: BitString = ec_point.into(); - let ec_point_vec: Vec = ec_point_bitstring.into(); - assert_eq!(ec_point_vec, EC_POINT.to_vec()); - } - _ => panic!("PublicKey of SubjectPublicKeyInfo is not an instance for ECC"), - } + + key.algorithm + .owned_to_ref() + .assert_oids( + p256::PublicKey::ALGORITHM_IDENTIFIER.oid, + p256::PublicKey::ALGORITHM_IDENTIFIER + .parameters + .expect("curve parameters are expected in NistP256"), + ) + .expect("Curve parameters should be the one for NistP256"); + + let ec_point = key + .subject_public_key + .as_bytes() + .expect("serialized EC point"); + assert_eq!(ec_point, EC_POINT); } } diff --git a/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs b/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs index 63487600..cd41a8c6 100644 --- a/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs +++ b/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs @@ -1,9 +1,13 @@ // Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 -use std::convert::{TryFrom, TryInto}; +use std::{ + convert::{TryFrom, TryInto}, + str::FromStr, + sync::Mutex, +}; use tss_esapi::{ abstraction::transient::{KeyParams, ObjectWrapper, TransientKeyContextBuilder}, - abstraction::{ek, AsymmetricAlgorithmSelection}, + abstraction::{ek, AsymmetricAlgorithmSelection, EcSigner}, constants::return_code::{TpmFormatOneError, TpmFormatZeroError}, error::{TpmFormatZeroResponseCode, TpmResponseCode}, interface_types::{ @@ -20,6 +24,17 @@ use tss_esapi::{ Error, ReturnCode, TransientKeyContext, WrapperErrorKind as ErrorKind, }; +use digest::Digest as _; +use p256::{ecdsa::VerifyingKey, NistP256}; +use sha2::Sha256; +use sha3::Sha3_256; +use signature::{DigestSigner, DigestVerifier}; +use x509_cert::{ + builder::{Builder, RequestBuilder}, + der::{pem::LineEnding, EncodePem}, + name::Name, +}; + use crate::common::create_tcti; const HASH: [u8; 32] = [ @@ -875,3 +890,81 @@ fn get_random_from_tkc() { .execute_without_session(|ctx| ctx.get_random(16)) .expect("Failed to get random bytes"); } + +#[test] +fn sign_csr() { + // Check that we can convert a reference from TKC to Context + let mut ctx = create_ctx(); + + let key_params = EcSigner::::key_params_default(); + let (tpm_km, _tpm_auth) = ctx.create_key(key_params, 0).expect("create private key"); + + let subject = Name::from_str("CN=tpm.example").expect("Parse common name"); + let signer = EcSigner::::new((Mutex::new(&mut ctx), tpm_km, key_params, None)) + .expect("Create a signer"); + let builder = RequestBuilder::new(subject, &signer).expect("Create certificate request"); + + let cert_req = builder + .build::() + .expect("Sign a CSR"); + + println!( + "{}", + cert_req + .to_pem(LineEnding::default()) + .expect("Serialize CSR") + ); +} + +#[test] +fn sign_p256_sha2_256() { + // Check that we can convert a reference from TKC to Context + let mut ctx = create_ctx(); + + let key_params = EcSigner::::key_params::(); + let (tpm_km, _tpm_auth) = ctx.create_key(key_params, 0).expect("create private key"); + let signer = EcSigner::::new((Mutex::new(&mut ctx), tpm_km, key_params, None)) + .expect("Create a signer"); + + let payload = b"Example of ECDSA with P-256"; + let mut hash = Sha256::new(); + hash.update(payload); + + let signature: p256::ecdsa::Signature = signer.sign_digest(hash.clone()); + let verifying_key: VerifyingKey = *signer.as_ref(); + assert!(verifying_key.verify_digest(hash, &signature).is_ok()); +} + +// NOTE(baloo): I believe this is a legitimate case, but support is not available yet in libtpms (or swtpm) +// +// See: https://github.com/stefanberger/libtpms/issues/206 +// +// This will throw: +// `Error in creating derived key: 0x000002C3` when trying to create a P-256 key with Sha3-256 associated hash. +// +// This is combination defined by NIST: +// https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/P256_SHA3-256.pdf +// +// This test is ignored for now to avoid issues with the CI. +#[ignore] +#[test] +fn sign_p256_sha3_256() { + // Check that we can convert a reference from TKC to Context + let mut ctx = create_ctx(); + + let key_params = EcSigner::::key_params::(); + let (tpm_km, _tpm_auth) = ctx.create_key(key_params, 0).expect("create private key"); + let signer = EcSigner::::new((Mutex::new(&mut ctx), tpm_km, key_params, None)) + .expect("Create a signer"); + + let payload = b"Example of ECDSA with P-256"; + let mut hash = Sha3_256::new(); + hash.update(payload); + + let signature = as DigestSigner>::sign_digest( + &signer, + hash.clone(), + ); + let verifying_key: VerifyingKey = *signer.as_ref(); + assert!(verifying_key.verify_digest(hash, &signature).is_ok()); +} diff --git a/tss-esapi/tests/integration_tests/context_tests/tpm_commands/signing_and_signature_verification_tests.rs b/tss-esapi/tests/integration_tests/context_tests/tpm_commands/signing_and_signature_verification_tests.rs index a5fd4d2c..499aa9fd 100644 --- a/tss-esapi/tests/integration_tests/context_tests/tpm_commands/signing_and_signature_verification_tests.rs +++ b/tss-esapi/tests/integration_tests/context_tests/tpm_commands/signing_and_signature_verification_tests.rs @@ -170,8 +170,33 @@ mod test_sign { use crate::common::{create_ctx_with_session, signing_key_pub, HASH}; use std::convert::TryFrom; use tss_esapi::{ - interface_types::reserved_handles::Hierarchy, - structures::{Auth, Digest, SignatureScheme}, + interface_types::{ + algorithm::RsaSchemeAlgorithm, key_bits::RsaKeyBits, reserved_handles::Hierarchy, + }, + structures::{Auth, Digest, RsaExponent, RsaScheme, SignatureScheme}, + }; + + use { + digest::Digest as _, + signature::{DigestVerifier, Keypair, Signer, Verifier}, + std::sync::Mutex, + }; + + #[cfg(feature = "p256")] + use { + p256::{ecdsa::Signature, NistP256}, + tss_esapi::{ + abstraction::EcSigner, + interface_types::{algorithm::HashingAlgorithm, ecc::EccCurve}, + structures::{EccScheme, HashScheme}, + utils, + }, + }; + + #[cfg(feature = "rsa")] + use { + rsa::{pkcs1v15, pss}, + tss_esapi::abstraction::{RsaPkcsSigner, RsaPssSigner}, }; #[test] @@ -260,4 +285,102 @@ mod test_sign { ) .unwrap_err(); } + + #[cfg(feature = "p256")] + #[test] + fn test_sign_signer() { + let public = utils::create_unrestricted_signing_ecc_public( + EccScheme::EcDsa(HashScheme::new(HashingAlgorithm::Sha256)), + EccCurve::NistP256, + ) + .expect("Create ecc public struct"); + + let mut context = create_ctx_with_session(); + let mut random_digest = vec![0u8; 16]; + getrandom::getrandom(&mut random_digest).unwrap(); + let key_auth = Auth::from_bytes(random_digest.as_slice()).unwrap(); + + let key_handle = context + .create_primary(Hierarchy::Owner, public, Some(key_auth), None, None, None) + .unwrap() + .key_handle; + + let mut random = vec![0u8; 47]; + getrandom::getrandom(&mut random).unwrap(); + + let signer = EcSigner::::new((Mutex::new(&mut context), key_handle)).unwrap(); + let verifying_key = signer.verifying_key(); + let signature: Signature = signer.sign(&random); + + verifying_key.verify(&random, &signature).unwrap(); + } + + #[cfg(feature = "rsa")] + #[test] + fn test_sign_signer_rsa_pkcs() { + let mut context = create_ctx_with_session(); + let mut random_digest = vec![0u8; 16]; + getrandom::getrandom(&mut random_digest).unwrap(); + let key_auth = Auth::from_bytes(random_digest.as_slice()).unwrap(); + + let key_handle = context + .create_primary( + Hierarchy::Owner, + signing_key_pub(), + Some(key_auth), + None, + None, + None, + ) + .unwrap() + .key_handle; + + let mut payload = vec![0u8; 47]; + getrandom::getrandom(&mut payload).unwrap(); + + let signer = + RsaPkcsSigner::<_, sha2::Sha256>::new((Mutex::new(&mut context), key_handle)).unwrap(); + let verifying_key = signer.verifying_key(); + let signature: pkcs1v15::Signature = signer.sign(&payload); + + verifying_key.verify(&payload, &signature).unwrap(); + + let d = sha2::Sha256::new_with_prefix(&payload); + verifying_key.verify_digest(d, &signature).unwrap(); + } + + #[cfg(feature = "rsa")] + #[test] + fn test_sign_signer_rsa_pss() { + let mut context = create_ctx_with_session(); + let mut random_digest = vec![0u8; 16]; + getrandom::getrandom(&mut random_digest).unwrap(); + let key_auth = Auth::from_bytes(random_digest.as_slice()).unwrap(); + + let rsa_pss = utils::create_unrestricted_signing_rsa_public( + RsaScheme::create(RsaSchemeAlgorithm::RsaPss, Some(HashingAlgorithm::Sha256)) + .expect("Failed to create RSA scheme"), + RsaKeyBits::Rsa2048, + RsaExponent::default(), + ) + .expect("Failed to create an unrestricted signing rsa public structure"); + + let key_handle = context + .create_primary(Hierarchy::Owner, rsa_pss, Some(key_auth), None, None, None) + .unwrap() + .key_handle; + + let mut payload = vec![0u8; 47]; + getrandom::getrandom(&mut payload).unwrap(); + + let signer = + RsaPssSigner::<_, sha2::Sha256>::new((Mutex::new(&mut context), key_handle)).unwrap(); + let verifying_key = signer.verifying_key(); + let signature: pss::Signature = signer.sign(&payload); + + verifying_key.verify(&payload, &signature).unwrap(); + + let d = sha2::Sha256::new_with_prefix(&payload); + verifying_key.verify_digest(d, &signature).unwrap(); + } }