Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adds a signature::Signer implementation for ecc keys
Browse files Browse the repository at this point in the history
Signed-off-by: Arthur Gautier <[email protected]>
baloo committed Aug 7, 2024
1 parent bbcb4cc commit edf67ed
Showing 4 changed files with 351 additions and 4 deletions.
6 changes: 4 additions & 2 deletions tss-esapi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
@@ -50,14 +51,15 @@ getrandom = "0.2.11"

[dev-dependencies]
env_logger = "0.9.0"
sha2 = "0.10.1"
serde_json = "^1.0.108"
sha2 = { version = "0.10.8", features = ["oid"] }
x509-cert = { version = "0.2.0", features = ["builder"] }

[build-dependencies]
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"]
2 changes: 2 additions & 0 deletions tss-esapi/src/abstraction/transient/mod.rs
Original file line number Diff line number Diff line change
@@ -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)]
254 changes: 254 additions & 0 deletions tss-esapi/src/abstraction/transient/signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
// 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};
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::<NistP256>::key_params_default(), 0)
/// .expect("Failed to create a private keypair");
///
/// let signer = EcSigner::<NistP256>::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<Auth>,
verifying_key: VerifyingKey<C>,
}

impl<'ctx, C> EcSigner<'ctx, C>
where
C: PrimeCurve + CurveArithmetic,
C: AssociatedTpmCurve,
FieldBytesSize<C>: ModulusSize,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
pub fn new(
context: &'ctx mut TransientKeyContext,
key_material: KeyMaterial,
key_auth: Option<Auth>,
) -> Result<Self, Error> {
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,
<C as DigestPrimitive>::Digest: FixedOutput<OutputSize = FieldBytesSize<C>>,
<C as DigestPrimitive>::Digest: AssociatedHashingAlgorithm,
{
Self::key_params::<<C as DigestPrimitive>::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<D>() -> KeyParams
where
D: FixedOutput<OutputSize = FieldBytesSize<C>>,
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<VerifyingKey<C>> for EcSigner<'ctx, C>
where
C: PrimeCurve + CurveArithmetic,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
fn as_ref(&self) -> &VerifyingKey<C> {
&self.verifying_key
}
}

impl<'ctx, C> KeypairRef for EcSigner<'ctx, C>
where
C: PrimeCurve + CurveArithmetic,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
type VerifyingKey = VerifyingKey<C>;
}

impl<'ctx, C, D> DigestSigner<D, Signature<C>> for EcSigner<'ctx, C>
where
C: PrimeCurve + CurveArithmetic,
C: AssociatedTpmCurve,
D: Digest + FixedOutput<OutputSize = FieldBytesSize<C>>,
D: AssociatedHashingAlgorithm,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
TpmDigest: From<Output<D>>,
{
fn try_sign_digest(&self, digest: D) -> Result<Signature<C>, SigError> {
let digest = TpmDigest::from(digest.finalize_fixed());

let key_params = Self::key_params::<D>();
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<D, DerSignature<C>> for EcSigner<'ctx, C>
where
C: PrimeCurve + CurveArithmetic,
C: AssociatedTpmCurve,
D: Digest + FixedOutput<OutputSize = FieldBytesSize<C>>,
D: AssociatedHashingAlgorithm,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
TpmDigest: From<Output<D>>,

MaxSize<C>: ArrayLength<u8>,
<FieldBytesSize<C> as Add>::Output: Add<MaxOverhead> + ArrayLength<u8>,
{
fn try_sign_digest(&self, digest: D) -> Result<DerSignature<C>, SigError> {
let signature: Signature<_> = self.try_sign_digest(digest)?;
Ok(signature.to_der())
}
}

impl<'ctx, C> Signer<Signature<C>> for EcSigner<'ctx, C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C: AssociatedTpmCurve,
<C as DigestPrimitive>::Digest: AssociatedHashingAlgorithm,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
TpmDigest: From<Output<<C as DigestPrimitive>::Digest>>,
{
fn try_sign(&self, msg: &[u8]) -> Result<Signature<C>, SigError> {
self.try_sign_digest(C::Digest::new_with_prefix(msg))
}
}

impl<'ctx, C> Signer<DerSignature<C>> for EcSigner<'ctx, C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C: AssociatedTpmCurve,
<C as DigestPrimitive>::Digest: AssociatedHashingAlgorithm,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
TpmDigest: From<Output<<C as DigestPrimitive>::Digest>>,

MaxSize<C>: ArrayLength<u8>,
<FieldBytesSize<C> as Add>::Output: Add<MaxOverhead> + ArrayLength<u8>,
{
fn try_sign(&self, msg: &[u8]) -> Result<DerSignature<C>, SigError> {
self.try_sign_digest(C::Digest::new_with_prefix(msg))
}
}

impl<'ctx, C> SignatureAlgorithmIdentifier for EcSigner<'ctx, C>
where
C: PrimeCurve + CurveArithmetic,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
Signature<C>: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
{
type Params = AnyRef<'static>;

const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
Signature::<C>::ALGORITHM_IDENTIFIER;
}
Original file line number Diff line number Diff line change
@@ -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,78 @@ 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::<NistP256>::key_params_default(), 0)
.expect("create private key");

let subject = Name::from_str("CN=tpm.example").expect("Parse common name");
let signer = EcSigner::<NistP256>::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::<p256::ecdsa::DerSignature>()
.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::<NistP256>::key_params::<sha2::Sha256>(), 0)
.expect("create private key");
let signer = EcSigner::<NistP256>::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 for a reason I don't understand yet
// the TPM will reply with `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
//
// I can't find where in the TPM spec this would be an illegal pair, we'll ignore for now.
#[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::<NistP256>::key_params::<Sha3_256>(), 0)
.expect("create private key");
let signer = EcSigner::<NistP256>::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 =
<EcSigner<'_, _> as DigestSigner<Sha3_256, p256::ecdsa::Signature>>::sign_digest(
&signer,
hash.clone(),
);
let verifying_key: VerifyingKey = *signer.as_ref();
assert!(verifying_key.verify_digest(hash, &signature).is_ok());
}

0 comments on commit edf67ed

Please sign in to comment.