diff --git a/tss-esapi/Cargo.toml b/tss-esapi/Cargo.toml index c26571c3..32509887 100644 --- a/tss-esapi/Cargo.toml +++ b/tss-esapi/Cargo.toml @@ -42,6 +42,7 @@ 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", optional = true} cfg-if = "1.0.0" strum = { version = "0.25.0", optional = true } strum_macros = { version = "0.25.0", optional = true } @@ -59,5 +60,5 @@ semver = "1.0.7" [features] default = ["abstraction"] generate-bindings = ["tss-esapi-sys/generate-bindings"] -abstraction = ["ecdsa", "elliptic-curve", "rsa", "x509-cert", "p192", "p224", "p256", "p384", "p521", "sha1", "sha2", "sha3", "sm2", "sm3"] +abstraction = ["ecdsa", "elliptic-curve", "signature", "rsa", "x509-cert", "p192", "p224", "p256", "p384", "p521", "sha1", "sha2", "sha3", "sm2", "sm3"] integration-tests = ["strum", "strum_macros"] diff --git a/tss-esapi/src/abstraction/transient/mod.rs b/tss-esapi/src/abstraction/transient/mod.rs index eea4a234..2d194928 100644 --- a/tss-esapi/src/abstraction/transient/mod.rs +++ b/tss-esapi/src/abstraction/transient/mod.rs @@ -34,8 +34,10 @@ use std::convert::{AsMut, AsRef, TryFrom, TryInto}; use zeroize::Zeroize; mod key_attestation; +mod signer; pub use key_attestation::MakeCredParams; +pub use signer::EcSigner; /// Parameters for the kinds of keys supported by the context #[derive(Debug, Clone, Copy)] diff --git a/tss-esapi/src/abstraction/transient/signer.rs b/tss-esapi/src/abstraction/transient/signer.rs new file mode 100644 index 00000000..4847e182 --- /dev/null +++ b/tss-esapi/src/abstraction/transient/signer.rs @@ -0,0 +1,235 @@ +// 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, +}; + +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}; + +/// [`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, ...) +/// +/// the hashing algorithm `D` is the digest that will be used for signatures (SHA-256, SHA3-256, ...). +/// +/// ```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<'ctx, C> EcSigner<'ctx, 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 + 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<'ctx, C> AsRef> for EcSigner<'ctx, C> +where + C: PrimeCurve + CurveArithmetic, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, +{ + fn as_ref(&self) -> &VerifyingKey { + &self.verifying_key + } +} + +impl<'ctx, C> KeypairRef for EcSigner<'ctx, C> +where + C: PrimeCurve + CurveArithmetic, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, +{ + type VerifyingKey = VerifyingKey; +} + +impl<'ctx, C, D> DigestSigner> for EcSigner<'ctx, 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 { + todo!(); + }; + + let signature = Signature::try_from(signature).map_err(SigError::from_source)?; + + Ok(signature) + } +} + +impl<'ctx, C, D> DigestSigner> for EcSigner<'ctx, 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<'ctx, C> Signer> for EcSigner<'ctx, 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<'ctx, C> Signer> for EcSigner<'ctx, 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)) + } +}