Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Parsec as a key backend #38

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,21 @@ jobs:
- name: Run clippy
run: |
cargo clippy --features key_tpm,key_openssl_pkey --all


test_parsec:
name: Test Parsec integration
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: |
curl -s -N -L https://github.com/parallaxsecond/parsec/releases/download/1.0.0/quickstart-1.0.0-linux_x86.tar.gz | tar xz --strip-components=1
export PARSEC_SERVICE_ENDPOINT=unix:$(pwd)/parsec.sock
./parsec &
sleep 5
./parsec-tool ping
cargo test --features key_parsec
pkill parsec

clippy:
name: Clippy
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ serde_bytes = { version = "0.11", features = ["std"] }
serde_with = { version = "1.5", default_features = false }
openssl = { version = "0.10", optional = true }
tss-esapi = { version = "6.1", optional = true }
parsec-client = { version = "0.13.0", optional = true }

[dependencies.serde]
version = "1.0"
Expand All @@ -28,3 +29,4 @@ hex = "0.4"
default = ["key_openssl_pkey"]
key_openssl_pkey = ["openssl"]
key_tpm = ["tss-esapi", "openssl"]
key_parsec = ["parsec-client"]
2 changes: 2 additions & 0 deletions src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub use self::openssl::Openssl;

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

Expand Down
115 changes: 115 additions & 0 deletions src/crypto/parsec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! PARSEC implementation for cryptography

use crate::{
crypto::{MessageDigest, SignatureAlgorithm, SigningPrivateKey, SigningPublicKey},
error::CoseError,
};
use parsec_client::{
auth::Authentication,
core::interface::{
operations::{
psa_algorithm::{Algorithm, AsymmetricSignature, Hash, SignHash},
psa_key_attributes::Attributes,
},
requests::ResponseStatus,
},
error::{ClientErrorKind, Error},
BasicClient,
};

/// A reference to a PARSEC service-backed key
pub struct ParsecKey {
parsec_client: BasicClient,
name: String,
algorithm: AsymmetricSignature,
parameters: (SignatureAlgorithm, MessageDigest),
}

impl ParsecKey {
/// Create a new [ParsecKey] based on its name within the Parsec
/// service.
pub fn new(name: String, parsec_auth: Option<Authentication>) -> Result<ParsecKey, CoseError> {
let parsec_client = match parsec_auth {
None => BasicClient::new(None)?,
Some(auth) => {
let mut client = BasicClient::new_naked();
client.set_auth_data(auth);
client.set_default_provider()?;
client
}
};

let keys = parsec_client.list_keys()?;
let key = keys
.into_iter()
.find(|key| key.name == name)
.ok_or(Error::Client(ClientErrorKind::NotFound))?;
let (parameters, algorithm) = attrs_to_params(&key.attributes)?;

Ok(ParsecKey {
parsec_client,
name,
algorithm,
parameters,
})
}
}

fn attrs_to_params(
attrs: &Attributes,
) -> Result<((SignatureAlgorithm, MessageDigest), AsymmetricSignature), CoseError> {
match attrs.policy.permitted_algorithms {
Algorithm::AsymmetricSignature(alg @ AsymmetricSignature::Ecdsa { hash_alg }) => {
match hash_alg {
SignHash::Specific(Hash::Sha256) => {
Ok(((SignatureAlgorithm::ES256, MessageDigest::Sha256), alg))
}
SignHash::Specific(Hash::Sha384) => {
Ok(((SignatureAlgorithm::ES384, MessageDigest::Sha384), alg))
}
SignHash::Specific(Hash::Sha512) => {
Ok(((SignatureAlgorithm::ES512, MessageDigest::Sha512), alg))
}
_ => Err(CoseError::UnsupportedError(format!(
"Hash algorithm {:?} is not supported",
hash_alg
))),
}
}
_ => Err(CoseError::UnsupportedError(format!(
"Key algorithm {:?} is not supported",
attrs.policy.permitted_algorithms
))),
}
}

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

fn verify(&self, digest: &[u8], signature: &[u8]) -> Result<bool, CoseError> {
match self
.parsec_client
.psa_verify_hash(&self.name, digest, self.algorithm, signature)
{
Ok(()) => Ok(true),
Err(Error::Service(ResponseStatus::PsaErrorInvalidSignature)) => Ok(false),
Err(e) => Err(CoseError::ParsecError(e)),
}
}
}

impl SigningPrivateKey for ParsecKey {
fn sign(&self, data: &[u8]) -> Result<Vec<u8>, CoseError> {
Ok(self
.parsec_client
.psa_sign_hash(&self.name, data, self.algorithm)?)
}
}

impl From<Error> for CoseError {
fn from(err: Error) -> CoseError {
CoseError::ParsecError(err)
}
}
5 changes: 5 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub enum CoseError {
/// TPM error occured
#[cfg(feature = "key_tpm")]
TpmError(tss_esapi::Error),
/// PARSEC error occured
#[cfg(feature = "key_parsec")]
ParsecError(parsec_client::error::Error),
}

impl fmt::Display for CoseError {
Expand All @@ -51,6 +54,8 @@ impl fmt::Display for CoseError {
CoseError::EncryptionError(e) => write!(f, "Encryption error: {}", e),
#[cfg(feature = "key_tpm")]
CoseError::TpmError(e) => write!(f, "TPM error: {}", e),
#[cfg(feature = "key_parsec")]
CoseError::ParsecError(e) => write!(f, "Parsec error: {}", e),
}
}
}
Expand Down
140 changes: 129 additions & 11 deletions src/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ mod tests {
let ec_public =
openssl::ec::EcKey::from_public_key_affine_coordinates(&alg, &x, &y).unwrap();
let ec_private =
openssl::ec::EcKey::from_private_components(&alg, &d, &ec_public.public_key())
openssl::ec::EcKey::from_private_components(&alg, &d, ec_public.public_key())
.unwrap();
(
PKey::from_ec_key(ec_private).unwrap(),
Expand Down Expand Up @@ -620,7 +620,7 @@ mod tests {
let ec_public =
openssl::ec::EcKey::from_public_key_affine_coordinates(&alg, &x, &y).unwrap();
let ec_private =
openssl::ec::EcKey::from_private_components(&alg, &d, &ec_public.public_key())
openssl::ec::EcKey::from_private_components(&alg, &d, ec_public.public_key())
.unwrap();
(
PKey::from_ec_key(ec_private).unwrap(),
Expand Down Expand Up @@ -652,7 +652,7 @@ mod tests {
let ec_public =
openssl::ec::EcKey::from_public_key_affine_coordinates(&alg, &x, &y).unwrap();
let ec_private =
openssl::ec::EcKey::from_private_components(&alg, &d, &ec_public.public_key())
openssl::ec::EcKey::from_private_components(&alg, &d, ec_public.public_key())
.unwrap();
(
PKey::from_ec_key(ec_private).unwrap(),
Expand Down Expand Up @@ -1209,11 +1209,11 @@ mod tests {
)
.expect("Unable to create primary key")
.key_handle;
let mut tpm_key = TpmKey::new(tpm_context, prim_key).expect("Error creating TpmKey");
let tpm_key = TpmKey::new(tpm_context, prim_key).expect("Error creating TpmKey");

let mut map = HeaderMap::new();
map.insert(CborValue::Integer(4), CborValue::Bytes(b"11".to_vec()));
let cose_doc1 = CoseSign1::new::<Openssl>(TEXT, &map, &mut tpm_key).unwrap();
let cose_doc1 = CoseSign1::new::<Openssl>(TEXT, &map, &tpm_key).unwrap();
let tagged_bytes = cose_doc1.as_bytes(true).unwrap();

// Tag 6.18 should be present
Expand All @@ -1222,9 +1222,7 @@ mod tests {

assert_eq!(
cose_doc1.get_payload::<Openssl>(None).unwrap(),
cose_doc2
.get_payload::<Openssl>(Some(&mut tpm_key))
.unwrap()
cose_doc2.get_payload::<Openssl>(Some(&tpm_key)).unwrap()
);
}

Expand Down Expand Up @@ -1267,19 +1265,19 @@ mod tests {
)
.expect("Unable to create primary key")
.key_handle;
let mut tpm_key = TpmKey::new(tpm_context, prim_key).expect("Error creating TpmKey");
let tpm_key = TpmKey::new(tpm_context, prim_key).expect("Error creating TpmKey");

let mut map = HeaderMap::new();
map.insert(CborValue::Integer(4), CborValue::Bytes(b"11".to_vec()));
let mut cose_doc1 = CoseSign1::new::<Openssl>(TEXT, &map, &mut tpm_key).unwrap();
let mut cose_doc1 = CoseSign1::new::<Openssl>(TEXT, &map, &tpm_key).unwrap();

// Mangle the signature
cose_doc1.signature[0] = 0;

let tagged_bytes = cose_doc1.as_bytes(true).unwrap();
let cose_doc2 = CoseSign1::from_bytes(&tagged_bytes).unwrap();

match cose_doc2.get_payload::<Openssl>(Some(&mut tpm_key)) {
match cose_doc2.get_payload::<Openssl>(Some(&tpm_key)) {
Ok(_) => panic!("Did not fail"),
Err(CoseError::UnverifiedSignature) => {}
Err(e) => {
Expand All @@ -1288,4 +1286,124 @@ mod tests {
}
}
}

#[cfg(feature = "key_parsec")]
mod parsec {
use super::TEXT;
use crate::crypto::parsec::ParsecKey;
use crate::crypto::Openssl;
use crate::sign::*;

use parsec_client::{
core::interface::operations::{
psa_algorithm::{Algorithm, AsymmetricSignature, Hash, SignHash},
psa_key_attributes::{Attributes, EccFamily, Lifetime, Policy, Type, UsageFlags},
},
BasicClient,
};

#[test]
fn cose_sign_parsec() {
let parsec_key_name = String::from("parsec-sign-key");

// create key through Parsec
let parsec_client = BasicClient::new(None).expect("Failed to start Parsec client");
let mut usage_flags = UsageFlags::default();
usage_flags.set_sign_hash().set_verify_hash();
parsec_client
.psa_generate_key(
&parsec_key_name,
Attributes {
lifetime: Lifetime::Persistent,
key_type: Type::EccKeyPair {
curve_family: EccFamily::SecpR1,
},
bits: 256,
policy: Policy {
usage_flags,
permitted_algorithms: Algorithm::AsymmetricSignature(
AsymmetricSignature::Ecdsa {
hash_alg: SignHash::Specific(Hash::Sha256),
},
),
},
},
)
.expect("Failed to create Parsec key");
let parsec_key = ParsecKey::new(parsec_key_name.clone(), None)
.expect("Failed to link to Parsec key");

let mut map = HeaderMap::new();
map.insert(CborValue::Integer(4), CborValue::Bytes(b"11".to_vec()));
let cose_doc1 = CoseSign1::new::<Openssl>(TEXT, &map, &parsec_key).unwrap();
let tagged_bytes = cose_doc1.as_bytes(true).unwrap();

// Tag 6.18 should be present
assert_eq!(tagged_bytes[0], 6 << 5 | 18);
let cose_doc2 = CoseSign1::from_bytes(&tagged_bytes).unwrap();

assert_eq!(
cose_doc1.get_payload::<Openssl>(None).unwrap(),
cose_doc2.get_payload::<Openssl>(Some(&parsec_key)).unwrap()
);

parsec_client
.psa_destroy_key(&parsec_key_name)
.expect("Failed to clean up key");
}

#[test]
fn cose_sign_parsec_invalid_signature() {
let parsec_key_name = String::from("parsec-sign-invalid-signature-key");

// create key through Parsec
let parsec_client = BasicClient::new(None).expect("Failed to start Parsec client");
let mut usage_flags = UsageFlags::default();
usage_flags.set_sign_hash().set_verify_hash();
parsec_client
.psa_generate_key(
&parsec_key_name,
Attributes {
lifetime: Lifetime::Persistent,
key_type: Type::EccKeyPair {
curve_family: EccFamily::SecpR1,
},
bits: 256,
policy: Policy {
usage_flags,
permitted_algorithms: Algorithm::AsymmetricSignature(
AsymmetricSignature::Ecdsa {
hash_alg: SignHash::Specific(Hash::Sha256),
},
),
},
},
)
.expect("Failed to create Parsec key");
let parsec_key = ParsecKey::new(parsec_key_name.clone(), None)
.expect("Failed to link to Parsec key");

let mut map = HeaderMap::new();
map.insert(CborValue::Integer(4), CborValue::Bytes(b"11".to_vec()));
let mut cose_doc1 = CoseSign1::new::<Openssl>(TEXT, &map, &parsec_key).unwrap();

// Mangle the signature
cose_doc1.signature[0] |= 0xFF;

let tagged_bytes = cose_doc1.as_bytes(true).unwrap();
let cose_doc2 = CoseSign1::from_bytes(&tagged_bytes).unwrap();

match cose_doc2.get_payload::<Openssl>(Some(&parsec_key)) {
Ok(_) => panic!("Did not fail"),
Err(CoseError::UnverifiedSignature) => {}
Err(e) => {
panic!("Unexpected error: {:?}", e)
}
}

parsec_client
.psa_destroy_key(&parsec_key_name)
.expect("Failed to clean up key");
}
}
}