Skip to content

Commit

Permalink
Implement TPM cryptography (awslabs#22)
Browse files Browse the repository at this point in the history
* Implement TPM cryptography

This implements an abstracted signing crypto module that uses a local
TPM to sign and verify data.

Signed-off-by: Patrick Uiterwijk <patrick@puiterwijk.org>

* Add Fedora test

Add a test based on Fedora, to test the TPM implementation.

Signed-off-by: Patrick Uiterwijk <patrick@puiterwijk.org>
  • Loading branch information
puiterwijk committed Oct 22, 2021
1 parent 699aa93 commit 128bc42
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 17 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Expand Up @@ -26,6 +26,39 @@ jobs:
toolchain: ${{matrix.rust}}
- run: cargo test --all

test_fedora:
name: Test on Fedora
runs-on: ubuntu-latest
container: fedora:latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
dnf install -y \
tpm2-tss-devel \
swtpm swtpm-tools \
rust cargo clippy
- name: Start swtpm
run: |
mkdir /tmp/tpmdir
swtpm_setup --tpm2 \
--tpmstate /tmp/tpmdir \
--createek --decryption --create-ek-cert \
--create-platform-cert \
--display
swtpm socket --tpm2 \
--tpmstate dir=/tmp/tpmdir \
--flags startup-clear \
--ctrl type=tcp,port=2322 \
--server type=tcp,port=2321 \
--daemon
- name: Run tests
run: |
TCTI=swtpm: cargo test --features key_tpm,key_openssl_pkey
- name: Run clippy
run: |
cargo clippy --features key_tpm,key_openssl_pkey --all
clippy:
name: Clippy
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Expand Up @@ -15,6 +15,7 @@ serde_repr = "0.1"
serde_bytes = "0.11"
serde_with = { version = "1.5", default_features = false }
openssl = "0.10"
tss-esapi = { version = "6.1", optional = true }

[dependencies.serde]
version = "1.0"
Expand All @@ -23,3 +24,4 @@ features = ["derive"]
[features]
default = ["key_openssl_pkey"]
key_openssl_pkey = []
key_tpm = ["tss-esapi"]
21 changes: 21 additions & 0 deletions src/crypto/mod.rs
Expand Up @@ -6,6 +6,8 @@ use crate::{error::CoseError, sign::SignatureAlgorithm};

#[cfg(feature = "key_openssl_pkey")]
mod openssl_pkey;
#[cfg(feature = "key_tpm")]
pub mod tpm;

/// A public key that can verify an existing signature
pub trait SigningPublicKey {
Expand Down Expand Up @@ -45,6 +47,25 @@ pub fn ec_curve_to_parameters(
))
}

fn merge_ec_signature(bytes_r: &[u8], bytes_s: &[u8], key_length: usize) -> Vec<u8> {
assert!(bytes_r.len() <= key_length);
assert!(bytes_s.len() <= key_length);

let mut signature_bytes = vec![0u8; key_length * 2];

// This is big-endian encoding so padding might be added at the start if the factor is
// too short.
let offset_copy = key_length - bytes_r.len();
signature_bytes[offset_copy..offset_copy + bytes_r.len()].copy_from_slice(bytes_r);

// This is big-endian encoding so padding might be added at the start if the factor is
// too short.
let offset_copy = key_length - bytes_s.len() + key_length;
signature_bytes[offset_copy..offset_copy + bytes_s.len()].copy_from_slice(bytes_s);

signature_bytes
}

/// A private key that can produce new signatures
pub trait SigningPrivateKey: SigningPublicKey {
/// Given a digest, returns a signature
Expand Down
18 changes: 1 addition & 17 deletions src/crypto/openssl_pkey.rs
Expand Up @@ -100,22 +100,6 @@ where
let bytes_r = signature.r().to_vec();
let bytes_s = signature.s().to_vec();

// These should *never* exceed ceiling(key_length / 8)
assert!(bytes_r.len() <= key_length);
assert!(bytes_s.len() <= key_length);

let mut signature_bytes = vec![0u8; key_length * 2];

// This is big-endian encoding so padding might be added at the start if the factor is
// too short.
let offset_copy = key_length - bytes_r.len();
signature_bytes[offset_copy..offset_copy + bytes_r.len()].copy_from_slice(&bytes_r);

// This is big-endian encoding so padding might be added at the start if the factor is
// too short.
let offset_copy = key_length - bytes_s.len() + key_length;
signature_bytes[offset_copy..offset_copy + bytes_s.len()].copy_from_slice(&bytes_s);

Ok(signature_bytes)
Ok(super::merge_ec_signature(&bytes_r, &bytes_s, key_length))
}
}
184 changes: 184 additions & 0 deletions src/crypto/tpm.rs
@@ -0,0 +1,184 @@
//! TPM implementation for cryptography

use std::{cell::RefCell, convert::TryInto};

use openssl::hash::MessageDigest;
use tss_esapi::{
constants::{
self as tpm_constants,
response_code::{FormatOneResponseCode, Tss2ResponseCode},
},
handles::KeyHandle,
interface_types::algorithm::HashingAlgorithm,
tss2_esys::{TPMT_PUBLIC, TPMT_SIG_SCHEME, TPMT_TK_HASHCHECK},
utils::{AsymSchemeUnion, Signature, SignatureData},
Context, Error as tpm_error,
};

use crate::{
crypto::{SigningPrivateKey, SigningPublicKey},
error::CoseError,
sign::SignatureAlgorithm,
};

const TSS2_RC_SIGNATURE: u32 = tpm_constants::tss::TPM2_RC_SIGNATURE
| tpm_constants::tss::TPM2_RC_2
| tpm_constants::tss::TPM2_RC_P;

/// A reference to a TPM key and corresponding context
pub struct TpmKey {
context: RefCell<Context>,
key_handle: KeyHandle,

parameters: (SignatureAlgorithm, MessageDigest),
hash_alg: HashingAlgorithm,
key_length: usize,
}

impl TpmKey {
fn public_to_parameters(
public: TPMT_PUBLIC,
) -> Result<((SignatureAlgorithm, MessageDigest), HashingAlgorithm, usize), CoseError> {
match public.type_ {
tpm_constants::tss::TPM2_ALG_ECDSA => {}
tpm_constants::tss::TPM2_ALG_ECC => {}
type_ => {
return Err(CoseError::UnsupportedError(format!(
"Key algorithm {} is not supported, only ECDSA is currently supported",
type_
)))
}
}
// This is safe to do, because we checked the type above
let params = unsafe { public.parameters.eccDetail };
let (param_sig_alg, key_length) = match params.curveID {
tpm_constants::tss::TPM2_ECC_NIST_P256 => (SignatureAlgorithm::ES256, 32),
tpm_constants::tss::TPM2_ECC_NIST_P384 => (SignatureAlgorithm::ES384, 48),
tpm_constants::tss::TPM2_ECC_NIST_P521 => (SignatureAlgorithm::ES512, 66),
curve_id => {
return Err(CoseError::UnsupportedError(format!(
"Key curve {} is not supported",
curve_id
)))
}
};
match params.scheme.scheme {
tpm_constants::tss::TPM2_ALG_ECDSA => {}
scheme => {
return Err(CoseError::UnsupportedError(format!(
"Key scheme {} is not supported",
scheme
)))
}
}

let scheme = unsafe { params.scheme.details.ecdsa };
let param_hash_alg = match scheme.hashAlg {
tpm_constants::tss::TPM2_ALG_SHA256 => MessageDigest::sha256(),
tpm_constants::tss::TPM2_ALG_SHA384 => MessageDigest::sha384(),
tpm_constants::tss::TPM2_ALG_SHA512 => MessageDigest::sha512(),
hash_alg => {
return Err(CoseError::UnsupportedError(format!(
"Key hash alg {} is not supported",
hash_alg
)))
}
};
let hash_alg = scheme.hashAlg.try_into().map_err(|_| {
CoseError::UnsupportedError("Unsupported hashing algorithm".to_string())
})?;

Ok(((param_sig_alg, param_hash_alg), hash_alg, key_length))
}

/// Create a new TpmKey from a TPM Context and KeyHandle
pub fn new(mut context: Context, key_handle: KeyHandle) -> Result<TpmKey, CoseError> {
let (key_public, _, _) = context
.read_public(key_handle)
.map_err(CoseError::TpmError)?;
let (parameters, hash_alg, key_length) =
TpmKey::public_to_parameters(key_public.publicArea)?;

Ok(TpmKey {
context: RefCell::new(context),
key_handle,

parameters,
hash_alg,
key_length,
})
}
}

impl SigningPublicKey for TpmKey {
fn get_parameters(&self) -> Result<(SignatureAlgorithm, MessageDigest), CoseError> {
Ok(self.parameters)
}

fn verify(&self, data: &[u8], signature: &[u8]) -> Result<bool, CoseError> {
// Recover the R and S factors from the signature contained in the object
let (bytes_r, bytes_s) = signature.split_at(self.key_length);

let signature = Signature {
scheme: AsymSchemeUnion::ECDSA(self.hash_alg),
signature: SignatureData::EcdsaSignature {
r: bytes_r.to_vec(),
s: bytes_s.to_vec(),
},
};

let data = data.try_into().map_err(|_| {
CoseError::UnsupportedError("Invalid digest passed to verify".to_string())
})?;

let mut context = self.context.borrow_mut();

match context.verify_signature(self.key_handle, &data, signature) {
Ok(_) => Ok(true),
Err(tpm_error::Tss2Error(Tss2ResponseCode::FormatOne(FormatOneResponseCode(
TSS2_RC_SIGNATURE,
)))) => Ok(false),
Err(e) => Err(CoseError::TpmError(e)),
}
}
}

impl SigningPrivateKey for TpmKey {
fn sign(&self, data: &[u8]) -> Result<Vec<u8>, CoseError> {
let scheme = TPMT_SIG_SCHEME {
scheme: tpm_constants::tss::TPM2_ALG_NULL,
details: Default::default(),
};
let validation = TPMT_TK_HASHCHECK {
tag: tpm_constants::tss::TPM2_ST_HASHCHECK,
hierarchy: tpm_constants::tss::TPM2_RH_NULL,
digest: Default::default(),
};

let data = data
.try_into()
.map_err(|_| CoseError::UnsupportedError("Tried to sign invalid data".to_string()))?;

let signature = {
let mut context = self.context.borrow_mut();

context
.sign(
self.key_handle,
&data,
scheme,
validation.try_into().expect("Unable to convert validation"),
)
.map_err(CoseError::TpmError)?
};

match &signature.signature {
SignatureData::EcdsaSignature { r, s } => {
Ok(super::merge_ec_signature(r, s, self.key_length))
}
_ => Err(CoseError::UnsupportedError(
"Unsupported signature data returned".to_string(),
)),
}
}
}
5 changes: 5 additions & 0 deletions src/error.rs
Expand Up @@ -26,6 +26,9 @@ pub enum CoseError {
TagError(Option<u64>),
/// Encryption could not be performed due to OpenSSL error.
EncryptionError(openssl::error::ErrorStack),
/// TPM error occured
#[cfg(feature = "key_tpm")]
TpmError(tss_esapi::Error),
}

impl fmt::Display for CoseError {
Expand All @@ -40,6 +43,8 @@ impl fmt::Display for CoseError {
CoseError::TagError(Some(tag)) => write!(f, "Tag {} was not expected", tag),
CoseError::TagError(None) => write!(f, "Expected tag is missing"),
CoseError::EncryptionError(e) => write!(f, "Encryption error: {}", e),
#[cfg(feature = "key_tpm")]
CoseError::TpmError(e) => write!(f, "TPM error: {}", e),
}
}
}
Expand Down

0 comments on commit 128bc42

Please sign in to comment.