diff --git a/tss-esapi/Cargo.toml b/tss-esapi/Cargo.toml index 893b755a..23d96a76 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 = "0.10.7" +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 = ["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/src/abstraction/hashing.rs b/tss-esapi/src/abstraction/hashing.rs new file mode 100644 index 00000000..e0e6ced4 --- /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(all(feature = "rustcrypto", feature = "sha1"))] +impl AssociatedHashingAlgorithm for sha1::Sha1 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha1; +} + +#[cfg(all(feature = "rustcrypto", feature = "sha2"))] +impl AssociatedHashingAlgorithm for sha2::Sha256 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha256; +} + +#[cfg(all(feature = "rustcrypto", feature = "sha2"))] +impl AssociatedHashingAlgorithm for sha2::Sha384 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha384; +} + +#[cfg(all(feature = "rustcrypto", feature = "sha2"))] +impl AssociatedHashingAlgorithm for sha2::Sha512 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha512; +} + +#[cfg(all(feature = "rustcrypto", feature = "sm3"))] +impl AssociatedHashingAlgorithm for sm3::Sm3 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sm3_256; +} + +#[cfg(all(feature = "rustcrypto", feature = "sha3"))] +impl AssociatedHashingAlgorithm for sha3::Sha3_256 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha3_256; +} + +#[cfg(all(feature = "rustcrypto", feature = "sha3"))] +impl AssociatedHashingAlgorithm for sha3::Sha3_384 { + const TPM_DIGEST: HashingAlgorithm = HashingAlgorithm::Sha3_384; +} + +#[cfg(all(feature = "rustcrypto", 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..ec2cdd5d 100644 --- a/tss-esapi/src/abstraction/mod.rs +++ b/tss-esapi/src/abstraction/mod.rs @@ -9,6 +9,10 @@ pub mod pcr; pub mod public; pub mod transient; +mod hashing; +mod signatures; +pub use hashing::AssociatedHashingAlgorithm; + use std::convert::TryFrom; use crate::{ diff --git a/tss-esapi/src/abstraction/public.rs b/tss-esapi/src/abstraction/public.rs index 319278a4..2cc54bfb 100644 --- a/tss-esapi/src/abstraction/public.rs +++ b/tss-esapi/src/abstraction/public.rs @@ -2,127 +2,253 @@ // 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(all(feature = "rustcrypto", feature = "rsa"))] +use { + crate::structures::RsaExponent, + rsa::{BigUint, RsaPublicKey}, +}; + +#[cfg(all( + feature = "rustcrypto", + 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(all(feature = "rustcrypto", 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(all(feature = "rustcrypto", 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(all(feature = "rustcrypto", 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 { + ($curve:expr, $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(all(feature = "rustcrypto", feature = "p192"))] + read_key!(EccCurve::NistP192, p192::NistP192); + #[cfg(all(feature = "rustcrypto", feature = "p224"))] + read_key!(EccCurve::NistP224, p224::NistP224); + #[cfg(all(feature = "rustcrypto", feature = "p256"))] + read_key!(EccCurve::NistP256, p256::NistP256); + #[cfg(all(feature = "rustcrypto", feature = "p384"))] + read_key!(EccCurve::NistP384, p384::NistP384); + #[cfg(all(feature = "rustcrypto", feature = "p521"))] + read_key!(EccCurve::NistP521, p521::NistP521); + #[cfg(all(feature = "rustcrypto", feature = "sm2"))] + read_key!(EccCurve::Sm2P256, 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(all(feature = "rustcrypto", 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(all(feature = "rustcrypto", feature = "p192"))] +impl AssociatedTpmCurve for p192::NistP192 { + const TPM_CURVE: EccCurve = EccCurve::NistP192; +} + +#[cfg(all(feature = "rustcrypto", feature = "p224"))] +impl AssociatedTpmCurve for p224::NistP224 { + const TPM_CURVE: EccCurve = EccCurve::NistP224; +} + +#[cfg(all(feature = "rustcrypto", feature = "p256"))] +impl AssociatedTpmCurve for p256::NistP256 { + const TPM_CURVE: EccCurve = EccCurve::NistP256; +} + +#[cfg(all(feature = "rustcrypto", feature = "p384"))] +impl AssociatedTpmCurve for p384::NistP384 { + const TPM_CURVE: EccCurve = EccCurve::NistP384; +} + +#[cfg(all(feature = "rustcrypto", feature = "p521"))] +impl AssociatedTpmCurve for p521::NistP521 { + const TPM_CURVE: EccCurve = EccCurve::NistP521; +} + +#[cfg(all(feature = "rustcrypto", 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..1cc3077e --- /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(all(feature = "rustcrypto", 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(all(feature = "rustcrypto", 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(all(feature = "rustcrypto", 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/transient/mod.rs b/tss-esapi/src/abstraction/transient/mod.rs index f378383f..fe703b63 100644 --- a/tss-esapi/src/abstraction/transient/mod.rs +++ b/tss-esapi/src/abstraction/transient/mod.rs @@ -35,8 +35,14 @@ use zeroize::Zeroize; mod key_attestation; +#[cfg(feature = "rustcrypto")] +mod signer; + pub use key_attestation::MakeCredParams; +#[cfg(feature = "rustcrypto")] +pub use signer::EcSigner; + /// Parameters for the kinds of keys supported by the context #[derive(Debug, Clone, Copy)] pub enum KeyParams { diff --git a/tss-esapi/src/abstraction/transient/signer.rs b/tss-esapi/src/abstraction/transient/signer.rs new file mode 100644 index 00000000..7ba74231 --- /dev/null +++ b/tss-esapi/src/abstraction/transient/signer.rs @@ -0,0 +1,257 @@ +// 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 super::TransientKeyContext; +use crate::{ + abstraction::{ + public::AssociatedTpmCurve, + transient::{KeyMaterial, KeyParams}, + AssociatedHashingAlgorithm, + }, + interface_types::algorithm::EccSchemeAlgorithm, + structures::{Auth, Digest as TpmDigest, EccScheme, Signature as TpmSignature}, + 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 signature::{DigestSigner, Error as SigError, KeypairRef, Signer}; +use x509_cert::{ + der::asn1::AnyRef, + spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier, SignatureAlgorithmIdentifier}, +}; + +/// [`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 tss_esapi::{ +/// # abstraction::transient::{EcSigner, 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 (tpm_km, _tpm_auth) = context +/// .create_key(EcSigner::::key_params_default(), 0) +/// .expect("Failed to create a private keypair"); +/// +/// let signer = EcSigner::::new(&mut context, tpm_km, None) +/// .expect("Failed to create a signer"); +/// let signature: p256::ecdsa::Signature = signer.sign(b"Hello Bob, Alice here."); +/// ``` +#[derive(Debug)] +pub struct EcSigner<'ctx, C> +where + C: PrimeCurve + CurveArithmetic, +{ + context: Mutex<&'ctx mut TransientKeyContext>, + key_material: KeyMaterial, + key_auth: Option, + verifying_key: VerifyingKey, +} + +impl<'ctx, C> EcSigner<'ctx, C> +where + C: PrimeCurve + CurveArithmetic, + C: AssociatedTpmCurve, + FieldBytesSize: ModulusSize, + AffinePoint: FromEncodedPoint + ToEncodedPoint, +{ + pub fn new( + context: &'ctx mut TransientKeyContext, + key_material: KeyMaterial, + key_auth: Option, + ) -> Result { + let context = Mutex::new(context); + + let public_key = PublicKey::try_from(key_material.public())?; + let verifying_key = VerifyingKey::from(public_key); + + Ok(Self { + context, + key_material, + key_auth, + verifying_key, + }) + } +} + +impl EcSigner<'_, C> +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<'_, C> +where + C: PrimeCurve + CurveArithmetic, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, +{ + fn as_ref(&self) -> &VerifyingKey { + &self.verifying_key + } +} + +impl KeypairRef for EcSigner<'_, C> +where + C: PrimeCurve + CurveArithmetic, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, +{ + type VerifyingKey = VerifyingKey; +} + +impl DigestSigner> for EcSigner<'_, C> +where + C: PrimeCurve + CurveArithmetic, + C: AssociatedTpmCurve, + D: Digest + FixedOutput>, + D: AssociatedHashingAlgorithm, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, + TpmDigest: From>, +{ + fn try_sign_digest(&self, digest: D) -> Result, SigError> { + let digest = TpmDigest::from(digest.finalize_fixed()); + + let key_params = Self::key_params::(); + let mut context = self.context.lock().expect("Mutex got poisoned"); + let signature = context + .sign( + self.key_material.clone(), + key_params, + self.key_auth.clone(), + 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<'_, C> +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, +{ + 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<'_, C> +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + C: AssociatedTpmCurve, + ::Digest: AssociatedHashingAlgorithm, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, + TpmDigest: From::Digest>>, +{ + fn try_sign(&self, msg: &[u8]) -> Result, SigError> { + self.try_sign_digest(C::Digest::new_with_prefix(msg)) + } +} + +impl Signer> for EcSigner<'_, C> +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + C: AssociatedTpmCurve, + ::Digest: AssociatedHashingAlgorithm, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, + TpmDigest: From::Digest>>, + + MaxSize: ArrayLength, + as Add>::Output: Add + ArrayLength, +{ + fn try_sign(&self, msg: &[u8]) -> Result, SigError> { + self.try_sign_digest(C::Digest::new_with_prefix(msg)) + } +} + +impl SignatureAlgorithmIdentifier for EcSigner<'_, C> +where + C: PrimeCurve + CurveArithmetic, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, + Signature: AssociatedAlgorithmIdentifier>, +{ + type Params = AnyRef<'static>; + + const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier = + Signature::::ALGORITHM_IDENTIFIER; +} diff --git a/tss-esapi/src/structures/buffers.rs b/tss-esapi/src/structures/buffers.rs index acdc514c..477192e2 100644 --- a/tss-esapi/src/structures/buffers.rs +++ b/tss-esapi/src/structures/buffers.rs @@ -123,7 +123,13 @@ pub mod data { pub mod digest { use crate::tss2_esys::TPMU_HA; + use digest::{ + consts::{U20, U32, U48, U64}, + generic_array::GenericArray, + typenum::Unsigned, + }; use std::mem::size_of; + const TPM2B_DIGEST_BUFFER_SIZE: usize = size_of::(); buffer_type!(Digest, TPM2B_DIGEST_BUFFER_SIZE, TPM2B_DIGEST); @@ -220,6 +226,34 @@ pub mod digest { Digest(value_as_vec.into()) } } + + macro_rules! impl_from_digest { + ($($size:ty),+) => { + $(impl From> for Digest { + fn from(value: GenericArray) -> Self { + Digest(value.as_slice().to_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..75f551ff 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,8 +1,11 @@ // 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, +}; use tss_esapi::{ - abstraction::transient::{KeyParams, ObjectWrapper, TransientKeyContextBuilder}, + abstraction::transient::{EcSigner, KeyParams, ObjectWrapper, TransientKeyContextBuilder}, abstraction::{ek, AsymmetricAlgorithmSelection}, constants::return_code::{TpmFormatOneError, TpmFormatZeroError}, error::{TpmFormatZeroResponseCode, TpmResponseCode}, @@ -20,6 +23,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 +889,82 @@ 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 (tpm_km, _tpm_auth) = ctx + .create_key(EcSigner::::key_params_default(), 0) + .expect("create private key"); + + let subject = Name::from_str("CN=tpm.example").expect("Parse common name"); + let signer = EcSigner::::new(&mut ctx, tpm_km, 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 (tpm_km, _tpm_auth) = ctx + .create_key(EcSigner::::key_params::(), 0) + .expect("create private key"); + let signer = EcSigner::::new(&mut ctx, tpm_km, 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 (tpm_km, _tpm_auth) = ctx + .create_key(EcSigner::::key_params::(), 0) + .expect("create private key"); + let signer = EcSigner::::new(&mut ctx, tpm_km, 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()); +}