diff --git a/Cargo.lock b/Cargo.lock index 76e95d3d..951460a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1149,6 +1149,8 @@ dependencies = [ "openssl", "pest", "pest_derive", + "picky-asn1-der", + "picky-asn1-x509", "serde", "serde_derive", "static_assertions", @@ -1174,8 +1176,6 @@ dependencies = [ "libc", "log", "openssl", - "picky-asn1-der", - "picky-asn1-x509", "pretty_env_logger", "reqwest", "serde", diff --git a/keylime-agent/Cargo.toml b/keylime-agent/Cargo.toml index ee067b89..512491c3 100644 --- a/keylime-agent/Cargo.toml +++ b/keylime-agent/Cargo.toml @@ -20,8 +20,6 @@ keylime.workspace = true libc.workspace = true log.workspace = true openssl.workspace = true -picky-asn1-der.workspace = true -picky-asn1-x509.workspace = true pretty_env_logger.workspace = true reqwest.workspace = true serde.workspace = true diff --git a/keylime-agent/src/common.rs b/keylime-agent/src/common.rs index 684357ff..e3bbfa33 100644 --- a/keylime-agent/src/common.rs +++ b/keylime-agent/src/common.rs @@ -1,19 +1,20 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2021 Keylime Authors -use crate::error::{Error, Result}; -use crate::permissions; +use crate::{ + error::{Error, Result}, + permissions, +}; + use keylime::algorithms::{ EncryptionAlgorithm, HashAlgorithm, SignAlgorithm, }; -use keylime::tpm; -use log::*; -use openssl::{ - hash::{hash, MessageDigest}, - pkey::PKey, - x509::X509, +use keylime::{ + crypto::{hash, tss_pubkey_to_pem, AES_128_KEY_LEN, AES_256_KEY_LEN}, + tpm, }; -use picky_asn1_x509::SubjectPublicKeyInfo; +use log::*; +use openssl::hash::MessageDigest; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::{ @@ -42,9 +43,6 @@ pub static RSA_PUBLICKEY_EXPORTABLE: &str = "rsa placeholder"; pub static KEY: &str = "secret"; pub const AGENT_UUID_LEN: usize = 36; pub const AUTH_TAG_LEN: usize = 48; -pub const AES_128_KEY_LEN: usize = 16; -pub const AES_256_KEY_LEN: usize = 32; -pub const AES_BLOCK_SIZE: usize = 16; #[derive(Serialize, Deserialize, Debug)] pub(crate) struct APIVersion { @@ -253,14 +251,9 @@ impl AgentData { /// /// This is used as the agent UUID when the configuration option 'uuid' is set as 'hash_ek' pub(crate) fn hash_ek_pubkey(ek_pub: Public) -> Result { - // Converting Public TPM key to PEM - let key = SubjectPublicKeyInfo::try_from(ek_pub)?; - let key_der = picky_asn1_der::to_vec(&key)?; - let openssl_key = PKey::public_key_from_der(&key_der)?; - let pem = openssl_key.public_key_to_pem()?; - // Calculate the SHA-256 hash of the public key in PEM format - let mut hash = hash(MessageDigest::sha256(), &pem)?; + let pem = tss_pubkey_to_pem(ek_pub)?; + let hash = hash(&pem, MessageDigest::sha256())?; Ok(hex::encode(hash)) } diff --git a/keylime-agent/src/crypto.rs b/keylime-agent/src/crypto.rs deleted file mode 100644 index ba08a687..00000000 --- a/keylime-agent/src/crypto.rs +++ /dev/null @@ -1,826 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2021 Keylime Authors - -use base64::{engine::general_purpose, Engine as _}; -use log::*; -use openssl::{ - asn1::Asn1Time, - encrypt::Decrypter, - hash::MessageDigest, - memcmp, - nid::Nid, - pkcs5, - pkey::{Id, PKey, PKeyRef, Private, Public}, - rsa::{Padding, Rsa}, - sign::{Signer, Verifier}, - ssl::{SslAcceptor, SslAcceptorBuilder, SslMethod, SslVerifyMode}, - symm::Cipher, - x509::store::X509StoreBuilder, - x509::{X509Name, X509}, -}; -use picky_asn1_x509::SubjectPublicKeyInfo; -use std::{ - fs::{read_to_string, set_permissions, File, Permissions}, - io::{Read, Write}, - os::unix::fs::PermissionsExt, - path::Path, - string::String, -}; - -use crate::{ - Error, Result, AES_128_KEY_LEN, AES_256_KEY_LEN, AES_BLOCK_SIZE, -}; - -// Read a X509 cert in DER format from path -pub(crate) fn load_x509_der(input_cert_path: &Path) -> Result { - let contents = std::fs::read(input_cert_path).map_err(Error::from)?; - - X509::from_der(&contents).map_err(Error::Crypto) -} - -pub(crate) fn load_x509_pem(input_cert_path: &Path) -> Result { - let contents = std::fs::read(input_cert_path).map_err(Error::from)?; - - X509::from_pem(&contents).map_err(Error::Crypto) -} - -// Read a X509 cert or cert chain and outputs the first certificate -pub(crate) fn load_x509(input_cert_path: &Path) -> Result { - let mut cert_chain = load_x509_cert_chain(input_cert_path)?; - - if cert_chain.len() != 1 { - return Err(Error::Other( - "More than one public key provided in revocation cert" - .to_string(), - )); - } - let cert = cert_chain.pop().unwrap(); //#[allow_ci] - - Ok(cert) -} - -fn load_x509_cert_chain(input_cert_path: &Path) -> Result> { - let contents = read_to_string(input_cert_path).map_err(Error::from)?; - - X509::stack_from_pem(contents.as_bytes()).map_err(Error::Crypto) -} - -pub(crate) fn load_x509_cert_list( - input_cert_list: Vec<&Path>, -) -> Result> { - let mut loaded = Vec::::new(); - for cert in input_cert_list { - match load_x509_cert_chain(cert) { - Ok(mut s) => { - loaded.append(&mut s); - } - Err(e) => { - warn!("Could not load certs from {}: {}", cert.display(), e); - } - } - } - Ok(loaded) -} - -/// Write a X509 certificate to a file in PEM format -pub(crate) fn write_x509(cert: &X509, file_path: &Path) -> Result<()> { - let mut file = std::fs::File::create(file_path)?; - _ = file.write(&cert.to_pem()?)?; - Ok(()) -} - -/// Check an x509 certificate contains a specific public key -pub(crate) fn check_x509_key( - cert: &X509, - tpm_key: tss_esapi::structures::Public, -) -> Result { - // Id:RSA_PSS only added in rust-openssl from v0.10.59; remove this let and use Id::RSA_PSS after update - // Id taken from https://boringssl.googlesource.com/boringssl/+/refs/heads/master/include/openssl/nid.h#4039 - let id_rsa_pss: Id = Id::from_raw(912); - match cert.public_key()?.id() { - Id::RSA => { - let cert_n = cert.public_key()?.rsa()?.n().to_vec(); - let mut cert_n_str = format!("{:?}", cert_n); - _ = cert_n_str.pop(); - _ = cert_n_str.remove(0); - let key = SubjectPublicKeyInfo::try_from(tpm_key)?; - let key_der = picky_asn1_der::to_vec(&key)?; - let key_der_str = format!("{:?}", key_der); - - Ok(key_der_str.contains(&cert_n_str)) - } - cert_id if cert_id == id_rsa_pss => { - let cert_n = cert.public_key()?.rsa()?.n().to_vec(); - let mut cert_n_str = format!("{:?}", cert_n); - _ = cert_n_str.pop(); - _ = cert_n_str.remove(0); - let key = SubjectPublicKeyInfo::try_from(tpm_key)?; - let key_der = picky_asn1_der::to_vec(&key)?; - let key_der_str = format!("{:?}", key_der); - - Ok(key_der_str.contains(&cert_n_str)) - } - Id::EC => { - let cert_n = cert.public_key()?.ec_key()?.public_key_to_der()?; - let mut cert_n_str = format!("{:?}", cert_n); - _ = cert_n_str.pop(); - _ = cert_n_str.remove(0); - let key = SubjectPublicKeyInfo::try_from(tpm_key)?; - let key_der = picky_asn1_der::to_vec(&key)?; - let key_der_str = format!("{:?}", key_der); - - Ok(key_der_str.contains(&cert_n_str)) - } - _ => Err(Error::Other( - "Certificate does not seem to have an RSA or EC key".to_string(), - )), - } -} - -/// Detect a template from a certificate -/// Templates defined in: TPM 2.0 Keys for Device Identity and Attestation at https://trustedcomputinggroup.org/wp-content/uploads/TPM-2p0-Keys-for-Device-Identity-and-Attestation_v1_r12_pub10082021.pdf -pub(crate) fn match_cert_to_template(cert: &X509) -> Result { - // Id:RSA_PSS only added in rust-openssl from v0.10.59; remove this let and use Id::RSA_PSS after update - // Id taken from https://boringssl.googlesource.com/boringssl/+/refs/heads/master/include/openssl/nid.h#4039 - let id_rsa_pss: Id = Id::from_raw(912); - match cert.public_key()?.id() { - Id::RSA => match cert.public_key()?.bits() { - 2048 => Ok("H-1".to_string()), - _ => Ok("".to_string()), - }, - cert_id if cert_id == id_rsa_pss => match cert.public_key()?.bits() { - 2048 => Ok("H-1".to_string()), - _ => Ok("".to_string()), - }, - Id::EC => match cert.public_key()?.bits() { - 256 => match cert.public_key()?.ec_key()?.group().curve_name() { - Some(Nid::SECP256K1) => Ok("H-2".to_string()), - _ => Ok("H-5".to_string()), - }, - 384 => Ok("H-3".to_string()), - 521 => Ok("H-4".to_string()), - _ => Ok("".to_string()), - }, - _ => Err(Error::Other( - "Certificate does not seem to have an RSA or EC key".to_string(), - )), - } -} - -/// Read a PEM file and returns the public and private keys -pub(crate) fn load_key_pair( - key_path: &Path, - key_password: Option<&str>, -) -> Result<(PKey, PKey)> { - let pem = std::fs::read(key_path)?; - let private = match key_password { - Some(pw) => { - if pw.is_empty() { - PKey::private_key_from_pem(&pem)? - } else { - PKey::private_key_from_pem_passphrase(&pem, pw.as_bytes())? - } - } - None => PKey::private_key_from_pem(&pem)?, - }; - let public = pkey_pub_from_priv(private.clone())?; - Ok((public, private)) -} - -/// Write a private key to a file. -/// -/// If a passphrase is provided, the key will be stored encrypted using AES-256-CBC -pub(crate) fn write_key_pair( - key: &PKey, - file_path: &Path, - passphrase: Option<&str>, -) -> Result<()> { - // Write the generated key to the file - let mut file = std::fs::File::create(file_path)?; - match passphrase { - Some(pw) => { - if pw.is_empty() { - _ = file.write(&key.private_key_to_pem_pkcs8()?)?; - } else { - _ = file.write(&key.private_key_to_pem_pkcs8_passphrase( - openssl::symm::Cipher::aes_256_cbc(), - pw.as_bytes(), - )?)?; - } - } - None => { - _ = file.write(&key.private_key_to_pem_pkcs8()?)?; - } - } - set_permissions(file_path, Permissions::from_mode(0o600))?; - Ok(()) -} - -fn rsa_generate(key_size: u32) -> Result> { - PKey::from_rsa(Rsa::generate(key_size)?).map_err(Error::Crypto) -} - -pub(crate) fn rsa_generate_pair( - key_size: u32, -) -> Result<(PKey, PKey)> { - let private = rsa_generate(key_size)?; - let public = pkey_pub_from_priv(private.clone())?; - Ok((public, private)) -} - -fn pkey_pub_from_priv(privkey: PKey) -> Result> { - match privkey.id() { - Id::RSA => { - let rsa = Rsa::from_public_components( - privkey.rsa()?.n().to_owned()?, - privkey.rsa()?.e().to_owned()?, - ) - .map_err(Error::Crypto)?; - PKey::from_rsa(rsa).map_err(Error::Crypto) - } - id => Err(Error::Other(format!( - "pkey_pub_from_priv not yet implemented for key type {id:?}" - ))), - } -} - -pub(crate) fn pkey_pub_to_pem(pubkey: &PKey) -> Result { - pubkey - .public_key_to_pem() - .map_err(Error::from) - .and_then(|s| String::from_utf8(s).map_err(Error::from)) -} - -pub(crate) fn generate_x509(key: &PKey, uuid: &str) -> Result { - let mut name = X509Name::builder()?; - name.append_entry_by_nid(Nid::COMMONNAME, uuid)?; - let name = name.build(); - - let valid_from = Asn1Time::days_from_now(0)?; - let valid_to = Asn1Time::days_from_now(356)?; - - let mut builder = X509::builder()?; - builder.set_version(2)?; - builder.set_subject_name(&name)?; - builder.set_issuer_name(&name)?; - builder.set_not_before(&valid_from)?; - builder.set_not_after(&valid_to)?; - builder.set_pubkey(key)?; - builder.sign(key, MessageDigest::sha256())?; - - Ok(builder.build()) -} - -pub(crate) fn generate_mtls_context( - mtls_cert: &X509, - key: &PKey, - keylime_ca_certs: Vec, -) -> Result { - let mut ssl_context_builder = - SslAcceptor::mozilla_intermediate(SslMethod::tls())?; - ssl_context_builder.set_certificate(mtls_cert); - ssl_context_builder.set_private_key(key); - - // Build verification cert store. - let mut mtls_store_builder = X509StoreBuilder::new()?; - for cert in keylime_ca_certs { - mtls_store_builder.add_cert(cert)?; - } - - let mtls_store = mtls_store_builder.build(); - ssl_context_builder.set_verify_cert_store(mtls_store); - - // Enable mTLS verification - let mut verify_mode = SslVerifyMode::empty(); - verify_mode.set(SslVerifyMode::PEER, true); - verify_mode.set(SslVerifyMode::FAIL_IF_NO_PEER_CERT, true); - ssl_context_builder.set_verify(verify_mode); - - Ok(ssl_context_builder) -} - -/* - * Inputs: password to derive key - * shared salt - * Output: derived key - * - * Take in a password and shared salt, and derive a key based on the - * PBKDF2-HMAC key derivation function. Parameters match that of - * Python-Keylime. - * - * NOTE: This uses SHA-1 as the KDF's hash function in order to match the - * implementation of PBKDF2 in the Python version of Keylime. PyCryptodome's - * PBKDF2 function defaults to SHA-1 unless otherwise specified, and - * Python-Keylime uses this default. - */ -pub(crate) fn kdf( - input_password: String, - input_salt: String, -) -> Result { - let password = input_password.as_bytes(); - let salt = input_salt.as_bytes(); - let count = 2000; - // PyCryptodome's PBKDF2 binding allows key length to be specified - // explicitly as a parameter; here, key length is implicitly defined in - // the length of the 'key' variable. - let mut key = [0; 32]; - pkcs5::pbkdf2_hmac( - password, - salt, - count, - MessageDigest::sha1(), - &mut key, - )?; - Ok(hex::encode(&key[..])) -} - -/* - * Input: Trusted public key, and remote message and signature - * Output: true if they are verified, otherwise false - * - * Verify a remote message and signature against a local rsa cert - */ -pub(crate) fn asym_verify( - keypair: &PKeyRef, - message: &str, - signature: &str, -) -> Result { - let mut verifier = Verifier::new(MessageDigest::sha256(), keypair)?; - verifier.set_rsa_padding(Padding::PKCS1_PSS)?; - verifier.set_rsa_mgf1_md(MessageDigest::sha256())?; - verifier - .set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::MAXIMUM_LENGTH)?; - verifier.update(message.as_bytes())?; - Ok(verifier - .verify(&general_purpose::STANDARD.decode(signature.as_bytes())?)?) -} - -/* - * Inputs: OpenSSL RSA key - * ciphertext to be decrypted - * Output: decrypted plaintext - * - * Take in an RSA-encrypted ciphertext and an RSA private key and decrypt the - * ciphertext based on PKCS1 OAEP. - */ -pub(crate) fn rsa_oaep_decrypt( - priv_key: &PKey, - data: &[u8], -) -> Result> { - let mut decrypter = Decrypter::new(priv_key)?; - - decrypter.set_rsa_padding(Padding::PKCS1_OAEP)?; - decrypter.set_rsa_mgf1_md(MessageDigest::sha1())?; - decrypter.set_rsa_oaep_md(MessageDigest::sha1())?; - - // Create an output buffer - let buffer_len = decrypter.decrypt_len(data)?; - let mut decrypted = vec![0; buffer_len]; - - // Decrypt and truncate the buffer - let decrypted_len = decrypter.decrypt(data, &mut decrypted)?; - decrypted.truncate(decrypted_len); - - Ok(decrypted) -} - -/* - * Inputs: secret key - * message to sign - * Output: signed HMAC result - * - * Sign message and return HMAC result string - */ -pub(crate) fn compute_hmac(key: &[u8], data: &[u8]) -> Result> { - let pkey = PKey::hmac(key)?; - // SHA-384 is used as the underlying hash algorithm. - // - // Reference: - // https://keylime-docs.readthedocs.io/en/latest/rest_apis.html#post--v1.0-keys-ukey - // https://github.com/keylime/keylime/blob/910b38b296038b187a020c095dc747e9c46cbef3/keylime/crypto.py#L151 - let mut signer = Signer::new(MessageDigest::sha384(), &pkey)?; - signer.update(data)?; - signer.sign_to_vec().map_err(Error::Crypto) -} - -pub(crate) fn verify_hmac( - key: &[u8], - data: &[u8], - hmac: &[u8], -) -> Result<()> { - let pkey = PKey::hmac(key)?; - // SHA-384 is used as the underlying hash algorithm. - // - // Reference: - // https://keylime-docs.readthedocs.io/en/latest/rest_apis.html#post--v1.0-keys-ukey - // https://github.com/keylime/keylime/blob/910b38b296038b187a020c095dc747e9c46cbef3/keylime/crypto.py#L151 - let mut signer = Signer::new(MessageDigest::sha384(), &pkey)?; - signer.update(data)?; - - if !memcmp::eq(&signer.sign_to_vec()?, hmac) { - return Err(Error::Other("hmac check failed".to_string())); - } - - Ok(()) -} - -pub(crate) fn decrypt_aead(key: &[u8], data: &[u8]) -> Result> { - let cipher = match key.len() { - AES_128_KEY_LEN => Cipher::aes_128_gcm(), - AES_256_KEY_LEN => Cipher::aes_256_gcm(), - other => { - return Err(Error::Other(format!( - "key length {other} does not correspond to valid GCM cipher" - ))) - } - }; - - // Parse out payload IV, tag, ciphertext. Note that Keylime - // currently uses 16-byte IV, while the recommendation in SP - // 800-38D is 12-byte. - // - // Reference: - // https://github.com/keylime/keylime/blob/1663a7702b3286152b38dbcb715a9eb6705e05e9/keylime/crypto.py#L191 - if data.len() < AES_BLOCK_SIZE * 2 { - return Err(Error::InvalidRequest); - } - let (iv, rest) = data.split_at(AES_BLOCK_SIZE); - let (ciphertext, tag) = rest.split_at(rest.len() - AES_BLOCK_SIZE); - - openssl::symm::decrypt_aead(cipher, key, Some(iv), &[], ciphertext, tag) - .map_err(Error::Crypto) -} - -pub mod testing { - use super::*; - use openssl::encrypt::Encrypter; - use std::path::Path; - - pub(crate) fn rsa_import_pair( - path: impl AsRef, - ) -> Result<(PKey, PKey)> { - let contents = read_to_string(path)?; - let private = PKey::private_key_from_pem(contents.as_bytes())?; - let public = pkey_pub_from_priv(private.clone())?; - Ok((public, private)) - } - - pub(crate) fn pkey_pub_from_pem(pem: &str) -> Result> { - PKey::::public_key_from_pem(pem.as_bytes()) - .map_err(Error::Crypto) - } - - pub(crate) fn rsa_oaep_encrypt( - pub_key: &PKey, - data: &[u8], - ) -> Result> { - let mut encrypter = Encrypter::new(pub_key)?; - - encrypter.set_rsa_padding(Padding::PKCS1_OAEP)?; - encrypter.set_rsa_mgf1_md(MessageDigest::sha1())?; - encrypter.set_rsa_oaep_md(MessageDigest::sha1())?; - - // Create an output buffer - let buffer_len = encrypter.encrypt_len(data)?; - let mut encrypted = vec![0; buffer_len]; - - // Encrypt and truncate the buffer - let encrypted_len = encrypter.encrypt(data, &mut encrypted)?; - encrypted.truncate(encrypted_len); - - Ok(encrypted) - } - - pub(crate) fn encrypt_aead( - key: &[u8], - iv: &[u8], - data: &[u8], - ) -> Result> { - let cipher = match key.len() { - AES_128_KEY_LEN => Cipher::aes_128_gcm(), - AES_256_KEY_LEN => Cipher::aes_256_gcm(), - other => { - return Err(Error::Other(format!( - "key length {other} does not correspond to valid GCM cipher" - ))) - } - }; - if iv.len() != AES_BLOCK_SIZE { - return Err(Error::Other(format!( - "IV length {} does not correspond to valid GCM cipher {}", - iv.len(), - AES_BLOCK_SIZE - ))); - } - let mut tag = vec![0u8; AES_BLOCK_SIZE]; - let ciphertext = openssl::symm::encrypt_aead( - cipher, - key, - Some(iv), - &[], - data, - &mut tag, - ) - .map_err(Error::Crypto)?; - let mut result = - Vec::with_capacity(iv.len() + ciphertext.len() + tag.len()); - result.extend(iv); - result.extend(ciphertext); - result.extend(tag); - Ok(result) - } - - pub(crate) fn rsa_generate(key_size: u32) -> Result> { - super::rsa_generate(key_size) - } -} - -// Unit Testing -#[cfg(test)] -mod tests { - use super::*; - use openssl::rsa::Rsa; - use std::{fs, path::Path}; - use testing::{encrypt_aead, rsa_import_pair, rsa_oaep_encrypt}; - - // compare with the result from python output - #[test] - fn test_compute_hmac() { - let key = String::from("mysecret"); - let message = String::from("hellothere"); - let mac = - compute_hmac(key.as_bytes(), message.as_bytes()).map(hex::encode); - assert_eq!( - format!( - "{}{}", - "b8558314f515931c8d9b329805978fe77b9bb020b05406c0e", - "f189d89846ff8f5f0ca10e387d2c424358171df7f896f9f" - ), - mac.unwrap() //#[allow_ci] - ); - } - - // Test KDF to ensure derived password matches result derived from Python - // functions. - #[test] - fn test_kdf() { - let password = String::from("myverysecretsecret"); - let salt = String::from("thesaltiestsalt"); - let key = kdf(password, salt); - assert_eq!( - "8a6de415abb8b27de5c572c8137bd14e5658395f9a2346e0b1ad8b9d8b9028af" - .to_string(), - key.unwrap() //#[allow_ci] - ); - } - - #[test] - fn test_hmac_verification() { - // Generate a keypair - let (pub_key, priv_key) = rsa_generate_pair(2048).unwrap(); //#[allow_ci] - let data = b"hello, world!"; - let data2 = b"hola, mundo!"; - - // Sign the data - let mut signer = - Signer::new(MessageDigest::sha256(), &priv_key).unwrap(); //#[allow_ci] - signer.update(data).unwrap(); //#[allow_ci] - signer.update(data2).unwrap(); //#[allow_ci] - let signature = signer.sign_to_vec().unwrap(); //#[allow_ci] - - // Verify the data - let mut verifier = - Verifier::new(MessageDigest::sha256(), &pub_key).unwrap(); //#[allow_ci] - verifier.update(data).unwrap(); //#[allow_ci] - verifier.update(data2).unwrap(); //#[allow_ci] - assert!(verifier.verify(&signature).unwrap()); //#[allow_ci] - } - - #[test] - fn test_rsa_oaep() { - // Import a keypair - let rsa_key_path = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("test-data") - .join("test-rsa.pem"); - - let (pub_key, priv_key) = rsa_import_pair(rsa_key_path) - .expect("unable to import RSA key pair"); - let plaintext = b"0123456789012345"; - let ciphertext = rsa_oaep_encrypt(&pub_key, &plaintext[..]) - .expect("unable to encrypt"); - - // We can't check against the fixed ciphertext, as OAEP - // involves randomness. Check with a round-trip instead. - let decrypted = rsa_oaep_decrypt(&priv_key, &ciphertext[..]) - .expect("unable to decrypt"); - assert_eq!(decrypted, plaintext); - } - - #[test] - fn test_encrypt_aead_short() { - let key = b"0123456789012345"; - let iv = b"ABCDEFGHIJKLMNOP"; - let plaintext = b"test string, longer than the block size"; - let ciphertext = encrypt_aead(&key[..], &iv[..], &plaintext[..]) - .expect("unable to encrypt"); - let expected = hex::decode("4142434445464748494A4B4C4D4E4F50B2198661586C9839CCDD0B1D5B4FF92FA9C0E6477C4E8E42C19ACD9E8061DD1E759401337DA285A70580E6A2E10B5D3A09994F46D90AB6").unwrap(); //#[allow_ci] - assert_eq!(ciphertext, expected); - } - - #[test] - fn test_decrypt_aead_short() { - let key = b"0123456789012345"; - let ciphertext = hex::decode("4142434445464748494A4B4C4D4E4F50B2198661586C9839CCDD0B1D5B4FF92FA9C0E6477C4E8E42C19ACD9E8061DD1E759401337DA285A70580E6A2E10B5D3A09994F46D90AB6").unwrap(); //#[allow_ci] - let plaintext = decrypt_aead(&key[..], &ciphertext[..]) - .expect("unable to decrypt"); - let expected = b"test string, longer than the block size"; - assert_eq!(plaintext, expected); - } - - #[test] - fn test_encrypt_aead_long() { - let key = b"01234567890123450123456789012345"; - let iv = b"ABCDEFGHIJKLMNOP"; - let plaintext = b"test string, longer than the block size"; - let ciphertext = encrypt_aead(&key[..], &iv[..], &plaintext[..]) - .expect("unable to encrypt"); - let expected = hex::decode("4142434445464748494A4B4C4D4E4F50FCE7CA78C08FB1D5E04DB3C4AA6B6ED2F09C4AD7985BD1DB9FF15F9FDA869D0C01B27FF4618737BB53C84D256455AAB53B9AC7EAF88C4B").unwrap(); //#[allow_ci] - assert_eq!(ciphertext, expected); - } - - #[test] - fn test_decrypt_aead_long() { - let key = b"01234567890123450123456789012345"; - let ciphertext = hex::decode("4142434445464748494A4B4C4D4E4F50FCE7CA78C08FB1D5E04DB3C4AA6B6ED2F09C4AD7985BD1DB9FF15F9FDA869D0C01B27FF4618737BB53C84D256455AAB53B9AC7EAF88C4B").unwrap(); //#[allow_ci] - let plaintext = decrypt_aead(&key[..], &ciphertext[..]) - .expect("unable to decrypt"); - let expected = b"test string, longer than the block size"; - assert_eq!(plaintext, expected); - } - - #[test] - fn test_encrypt_aead_invalid_key_length() { - let key = b"0123456789012345012345678901234"; - let iv = b"ABCDEFGHIJKLMNOP"; - let plaintext = b"test string, longer than the block size"; - let result = encrypt_aead(&key[..], &iv[..], &plaintext[..]); - assert!(result.is_err()) - } - - #[test] - fn test_encrypt_aead_invalid_iv_length() { - let key = b"01234567890123450123456789012345"; - let iv = b"ABCDEFGHIJKLMN"; - let plaintext = b"test string, longer than the block size"; - let result = encrypt_aead(&key[..], &iv[..], &plaintext[..]); - assert!(result.is_err()) - } - - #[test] - fn test_decrypt_aead_invalid_key_length() { - let key = b"0123456789012345012345678901234"; - let ciphertext = hex::decode("4142434445464748494A4B4C4D4E4F50FCE7CA78C08FB1D5E04DB3C4AA6B6ED2F09C4AD7985BD1DB9FF15F9FDA869D0C01B27FF4618737BB53C84D256455AAB53B9AC7EAF88C4B").unwrap(); //#[allow_ci] - let result = decrypt_aead(&key[..], &ciphertext[..]); - assert!(result.is_err()) - } - - #[test] - fn test_decrypt_aead_invalid_ciphertext_length() { - let key = b"0123456789012345"; - let ciphertext = hex::decode("41424344").unwrap(); //#[allow_ci] - let result = decrypt_aead(&key[..], &ciphertext[..]); - assert!(matches!(result, Err(Error::InvalidRequest))); - } - - #[test] - fn test_asym_verify() { - // Import test keypair - let rsa_key_path = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("test-data") - .join("test-rsa.pem"); - - // Get RSA keys - let contents = read_to_string(rsa_key_path); - let private = - PKey::private_key_from_pem(contents.unwrap().as_bytes()).unwrap(); //#[allow_ci] - let public = pkey_pub_from_priv(private).unwrap(); //#[allow_ci] - - let message = String::from("Hello World!"); - - // Get known valid signature - let signature_path = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("test-data") - .join("test-rsa.sig"); - - let signature = read_to_string(signature_path).unwrap(); //#[allow_ci] - - assert!(asym_verify(&public, &message, &signature).unwrap()) //#[allow_ci] - } - - #[test] - fn test_password() { - // Import test keypair - let rsa_key_path = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("test-data") - .join("test-rsa.pem"); - - // Get RSA keys - let (public, private) = rsa_import_pair(rsa_key_path).unwrap(); //#[allow_ci] - - // Create temporary directory and files names - let temp_dir = tempfile::tempdir().unwrap(); //#[allow_ci] - let encrypted_path = - Path::new(&temp_dir.path()).join("encrypted.pem"); - let empty_pw_path = Path::new(&temp_dir.path()).join("empty_pw.pem"); - let none_pw_path = Path::new(&temp_dir.path()).join("none_pw.pem"); - - let message = b"Hello World!"; - - // Write keys to files - assert!(write_key_pair(&private, &encrypted_path, Some("password")) - .is_ok()); - assert!(write_key_pair(&private, &empty_pw_path, Some("")).is_ok()); - assert!(write_key_pair(&private, &none_pw_path, None).is_ok()); - - // Read keys from files - let (_, priv_from_encrypted) = - load_key_pair(&encrypted_path, Some("password")).unwrap(); //#[allow_ci] - let (_, priv_from_empty) = - load_key_pair(&empty_pw_path, Some("")).unwrap(); //#[allow_ci] - let (_, priv_from_none) = load_key_pair(&none_pw_path, None).unwrap(); //#[allow_ci] - - for keypair in [ - priv_from_encrypted.as_ref(), - priv_from_empty.as_ref(), - priv_from_none.as_ref(), - ] { - // Sign the data - let mut signer = - Signer::new(MessageDigest::sha256(), keypair).unwrap(); //#[allow_ci] - signer.update(message).unwrap(); //#[allow_ci] - let signature = signer.sign_to_vec().unwrap(); //#[allow_ci] - - // Verify the data - let mut verifier = - Verifier::new(MessageDigest::sha256(), keypair).unwrap(); //#[allow_ci] - verifier.update(message).unwrap(); //#[allow_ci] - assert!(verifier.verify(&signature).unwrap()); //#[allow_ci] - } - } - - #[test] - fn test_x509() { - let tempdir = tempfile::tempdir().unwrap(); //#[allow_ci] - - let (pubkey, privkey) = rsa_generate_pair(2048).unwrap(); //#[allow_ci] - - let r = generate_x509(&privkey, "uuidA"); - assert!(r.is_ok()); - let cert_a = r.unwrap(); //#[allow_ci] - let cert_a_path = tempdir.path().join("cert_a.pem"); - let r = write_x509(&cert_a, &cert_a_path); - assert!(r.is_ok()); - assert!(cert_a_path.exists()); - - let r = generate_x509(&privkey, "uuidB"); - assert!(r.is_ok()); - let cert_b = r.unwrap(); //#[allow_ci] - let cert_b_path = tempdir.path().join("cert_b.pem"); - let r = write_x509(&cert_b, &cert_b_path); - assert!(r.is_ok()); - assert!(cert_b_path.exists()); - - let loaded_a = load_x509(&cert_a_path); - assert!(loaded_a.is_ok()); - let loaded_a = loaded_a.unwrap(); //#[allow_ci] - - let a_str = read_to_string(&cert_a_path).unwrap(); //#[allow_ci] - let b_str = read_to_string(&cert_b_path).unwrap(); //#[allow_ci] - let concat = a_str + &b_str; - let concat_path = tempdir.path().join("concat.pem"); - fs::write(&concat_path, concat).unwrap(); //#[allow_ci] - - // Expect error as there are more than one certificate - let r = load_x509(&concat_path); - assert!(r.is_err()); - - // Loading multiple certs should work when loading chain - let r = load_x509_cert_chain(&concat_path); - assert!(r.is_ok()); - let chain = r.unwrap(); //#[allow_ci] - assert!(chain.len() == 2); - - // Test adding loading certs from a list, including an non-existing file - let non_existing = - Path::new("/non_existing_path/non_existing_cert.pem"); - let cert_list: Vec<&Path> = - vec![&cert_a_path, non_existing, &cert_b_path]; - let r = load_x509_cert_list(cert_list); - assert!(r.is_ok()); - let loaded_list = r.unwrap(); //#[allow_ci] - assert!(loaded_list.len() == 2); - - let r = generate_mtls_context(&loaded_a, &privkey, loaded_list); - assert!(r.is_ok()); - } -} diff --git a/keylime-agent/src/error.rs b/keylime-agent/src/error.rs index 41392c05..23f4e4c2 100644 --- a/keylime-agent/src/error.rs +++ b/keylime-agent/src/error.rs @@ -59,7 +59,7 @@ pub(crate) enum Error { #[error("Number parsing error: {0}")] NumParse(#[from] std::num::ParseIntError), #[error("Crypto error: {0}")] - Crypto(#[from] openssl::error::ErrorStack), + Crypto(#[from] keylime::crypto::CryptoError), #[cfg(feature = "with-zmq")] #[error("ZMQ error: {0}")] Zmq(#[from] zmq::Error), @@ -81,14 +81,12 @@ pub(crate) enum Error { Persist(#[from] tempfile::PersistError), #[error("Error joining threads: {0}")] Join(#[from] tokio::task::JoinError), - #[error("Asn1DerError: {0}")] - PickyAsn1(#[from] picky_asn1_der::Asn1DerError), #[error("Error sending internal message: {0}")] Sender(String), #[error("Error receiving internal message: {0}")] Receiver(String), - #[error("List parser error: {0}")] - ListParser(#[from] keylime::list_parser::Error), + #[error("List parser error")] + ListParser(#[from] keylime::list_parser::ListParsingError), #[error("Zip error: {0}")] Zip(#[from] zip::result::ZipError), #[error("{0}")] diff --git a/keylime-agent/src/keys_handler.rs b/keylime-agent/src/keys_handler.rs index 929133cb..6f4489d3 100644 --- a/keylime-agent/src/keys_handler.rs +++ b/keylime-agent/src/keys_handler.rs @@ -4,8 +4,8 @@ use crate::crypto; use crate::{ common::{ - AuthTag, EncryptedData, JsonWrapper, KeySet, SymmKey, AES_BLOCK_SIZE, - AGENT_UUID_LEN, AUTH_TAG_LEN, + AuthTag, EncryptedData, JsonWrapper, KeySet, SymmKey, AGENT_UUID_LEN, + AUTH_TAG_LEN, }, config::KeylimeConfig, payloads::{Payload, PayloadMessage}, @@ -552,9 +552,9 @@ mod tests { encrypt_aead, pkey_pub_from_pem, rsa_oaep_encrypt, }; use crate::{ - common::{AES_128_KEY_LEN, AES_256_KEY_LEN, API_VERSION}, + common::API_VERSION, config::KeylimeConfig, - crypto::compute_hmac, + crypto::{compute_hmac, AES_128_KEY_LEN, AES_256_KEY_LEN}, payloads, }; use actix_rt::Arbiter; diff --git a/keylime-agent/src/main.rs b/keylime-agent/src/main.rs index d752dfba..56df4c5e 100644 --- a/keylime-agent/src/main.rs +++ b/keylime-agent/src/main.rs @@ -33,7 +33,6 @@ mod common; mod config; -mod crypto; mod error; mod errors_handler; mod keys_handler; @@ -56,7 +55,7 @@ use futures::{ future::{ok, TryFutureExt}, try_join, }; -use keylime::{ima::MeasurementList, list_parser::parse_list, tpm}; +use keylime::{crypto, ima::MeasurementList, list_parser::parse_list, tpm}; use log::*; use openssl::{ pkey::{PKey, Private, Public}, @@ -599,7 +598,7 @@ async fn main() -> Result<()> { "Loading existing mTLS certificate from {}", cert_path.display() ); - crypto::load_x509(cert_path)? + crypto::load_x509_pem(cert_path)? } else { debug!("Generating new mTLS certificate"); let cert = crypto::generate_x509(&nk_priv, &agent_uuid)?; @@ -641,7 +640,7 @@ async fn main() -> Result<()> { }?; mtls_cert = Some(&cert); - ssl_context = Some(crypto::generate_mtls_context( + ssl_context = Some(crypto::generate_tls_context( &cert, &nk_priv, keylime_ca_certs, @@ -1024,10 +1023,42 @@ fn read_in_file(path: String) -> std::io::Result { #[cfg(feature = "testing")] mod testing { use super::*; - use crate::config::KeylimeConfig; + use crate::{config::KeylimeConfig, crypto::CryptoError}; + use thiserror::Error; + + #[derive(Error, Debug)] + pub(crate) enum MainTestError { + /// Algorithm error + #[error("AlgorithmError")] + Error(#[from] keylime::algorithms::AlgorithmError), + + /// Crypto error + #[error("CryptoError")] + CryptoError(#[from] CryptoError), + + /// CryptoTest error + #[error("CryptoTestError")] + CryptoTestError(#[from] crate::crypto::testing::CryptoTestError), + + /// IO error + #[error("IOError")] + IoError(#[from] std::io::Error), + + /// OpenSSL error + #[error("IOError")] + OpenSSLError(#[from] openssl::error::ErrorStack), + + /// TPM error + #[error("TPMError")] + TPMError(#[from] keylime::tpm::TpmError), + + /// TSS esapi error + #[error("TSSError")] + TSSError(#[from] tss_esapi::Error), + } impl QuoteData { - pub(crate) fn fixture() -> Result { + pub(crate) fn fixture() -> std::result::Result { let test_config = KeylimeConfig::default(); let mut ctx = tpm::Context::new()?; diff --git a/keylime-agent/src/payloads.rs b/keylime-agent/src/payloads.rs index cf9789c2..5c40125f 100644 --- a/keylime-agent/src/payloads.rs +++ b/keylime-agent/src/payloads.rs @@ -322,8 +322,9 @@ mod tests { encrypt_aead, pkey_pub_from_pem, rsa_oaep_encrypt, }; use crate::{ - common::{AES_128_KEY_LEN, AES_256_KEY_LEN, API_VERSION}, + common::API_VERSION, config::KeylimeConfig, + crypto::{AES_128_KEY_LEN, AES_256_KEY_LEN}, payloads, }; use actix_rt::Arbiter; @@ -571,7 +572,6 @@ echo hello > test-output let result = payload_tx.send(PayloadMessage::Shutdown).await; assert!(result.is_ok()); - drop(payload_tx); arbiter.join(); } diff --git a/keylime-agent/src/quotes_handler.rs b/keylime-agent/src/quotes_handler.rs index 33ffb1c0..61a7fc25 100644 --- a/keylime-agent/src/quotes_handler.rs +++ b/keylime-agent/src/quotes_handler.rs @@ -340,8 +340,9 @@ pub async fn integrity( #[cfg(test)] mod tests { use super::*; - use crate::{common::API_VERSION, crypto::testing::pkey_pub_from_pem}; + use crate::common::API_VERSION; use actix_web::{test, web, App}; + use keylime::{crypto::testing::pkey_pub_from_pem, tpm}; #[actix_rt::test] async fn test_identity() { diff --git a/keylime-agent/src/registrar_agent.rs b/keylime-agent/src/registrar_agent.rs index f086f71a..be205d51 100644 --- a/keylime-agent/src/registrar_agent.rs +++ b/keylime-agent/src/registrar_agent.rs @@ -129,17 +129,17 @@ pub(crate) async fn do_register_agent( port: u32, ) -> crate::error::Result> { let mtls_cert = match mtls_cert_x509 { - Some(cert) => Some(String::from_utf8(cert.to_pem()?)?), + Some(cert) => Some(crate::crypto::x509_to_pem(cert)?), None => Some("disabled".to_string()), }; let idevid_cert = match idevid_cert_x509 { - Some(cert) => Some(cert.to_der()?), + Some(cert) => Some(crate::crypto::x509_to_der(&cert)?), None => None, }; let iak_cert = match iak_cert_x509 { - Some(cert) => Some(cert.to_der()?), + Some(cert) => Some(crate::crypto::x509_to_der(&cert)?), None => None, }; diff --git a/keylime-agent/src/revocation.rs b/keylime-agent/src/revocation.rs index 025a9299..e5ffaade 100644 --- a/keylime-agent/src/revocation.rs +++ b/keylime-agent/src/revocation.rs @@ -258,7 +258,7 @@ fn process_revocation( work_dir: &Path, mount: &Path, ) -> Result<()> { - let cert_key = revocation_cert.public_key()?; + let cert_key = crypto::x509_get_pubkey(revocation_cert)?; // Verify the message and signature with our key let mut verified = crypto::asym_verify( @@ -498,11 +498,10 @@ pub(crate) async fn worker( cert_absolute_path.display() ); - revocation_cert = match crypto::load_x509(&cert_absolute_path) - { - Ok(cert) => Some(cert), - Err(e) => None, - }; + // If successful, use the certificate, otherwise ignore the error and continue + // without the certificate + revocation_cert = + crypto::load_x509_pem(&cert_absolute_path).ok(); } RevocationMessage::Shutdown => { revocation_rx.close(); @@ -772,7 +771,7 @@ mod tests { let cert_path = Path::new(env!("CARGO_MANIFEST_DIR")) .join("test-data/test-cert.pem"); - let cert = crypto::load_x509(&cert_path).unwrap(); //#[allow_ci] + let cert = crypto::load_x509_pem(&cert_path).unwrap(); //#[allow_ci] let actions_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/actions"); diff --git a/keylime/Cargo.toml b/keylime/Cargo.toml index ead3e288..c35d5fd6 100644 --- a/keylime/Cargo.toml +++ b/keylime/Cargo.toml @@ -19,6 +19,8 @@ serde_derive.workspace = true static_assertions.workspace = true thiserror.workspace = true tss-esapi.workspace = true +picky-asn1-der.workspace = true +picky-asn1-x509.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/keylime/src/algorithms.rs b/keylime/src/algorithms.rs index c0774667..9c461954 100644 --- a/keylime/src/algorithms.rs +++ b/keylime/src/algorithms.rs @@ -15,12 +15,14 @@ use tss_esapi::{ // This error needs to be public because we implement TryFrom for public types #[derive(Error, Debug)] pub enum AlgorithmError { - #[error("{0}")] - Hash(String), - #[error("{0}")] - Encrypt(String), - #[error("{0}")] - Sign(String), + #[error("Hashing Algorithm {0} not supported")] + UnsupportedHashingAlgorithm(String), + + #[error("Encryption Algorithm {0} not supported")] + UnsupportedEncryptionAlgorithm(String), + + #[error("Signing algorithm {0} not supported")] + UnsupportedSigningAlgorithm(String), } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -42,9 +44,9 @@ impl TryFrom<&str> for HashAlgorithm { "sha384" => Ok(HashAlgorithm::Sha384), "sha512" => Ok(HashAlgorithm::Sha512), "sm3_256" => Ok(HashAlgorithm::Sm3_256), - _ => Err(AlgorithmError::Hash(format!( - "Hash algorithm {value} is not supported by Keylime" - ))), + _ => { + Err(AlgorithmError::UnsupportedHashingAlgorithm(value.into())) + } } } } @@ -107,9 +109,9 @@ impl TryFrom<&str> for EncryptionAlgorithm { match value { "rsa" => Ok(EncryptionAlgorithm::Rsa), "ecc" => Ok(EncryptionAlgorithm::Ecc), - _ => Err(AlgorithmError::Encrypt(format!( - "Encryption algorithm {value} not supported by Keylime" - ))), + _ => Err(AlgorithmError::UnsupportedEncryptionAlgorithm( + value.into(), + )), } } } @@ -173,9 +175,9 @@ impl TryFrom<&str> for SignAlgorithm { "ecdsa" => Ok(SignAlgorithm::EcDsa), // "ecdaa" => Ok(SignAlgorithm::EcDaa), "ecschnorr" => Ok(SignAlgorithm::EcSchnorr), - _ => Err(AlgorithmError::Sign(format!( - "Signing algorithm {value} not supported by Keylime" - ))), + _ => { + Err(AlgorithmError::UnsupportedSigningAlgorithm(value.into())) + } } } } @@ -200,15 +202,46 @@ mod tests { fn test_hash_tryfrom() { let result = HashAlgorithm::try_from("sha1"); assert!(result.is_ok()); + let result = HashAlgorithm::try_from("sha256"); + assert!(result.is_ok()); + let result = HashAlgorithm::try_from("sha384"); + assert!(result.is_ok()); + let result = HashAlgorithm::try_from("sha512"); + assert!(result.is_ok()); + let result = HashAlgorithm::try_from("sm3_256"); + assert!(result.is_ok()); + } + #[test] + fn test_unsupported_hash_tryfrom() { + let result = HashAlgorithm::try_from("unsupported"); + assert!(result.is_err()); } #[test] fn test_encrypt_try_from() { let result = EncryptionAlgorithm::try_from("rsa"); assert!(result.is_ok()); + let result = EncryptionAlgorithm::try_from("ecc"); + assert!(result.is_ok()); + } + #[test] + fn test_unsupported_encrypt_try_from() { + let result = EncryptionAlgorithm::try_from("unsupported"); + assert!(result.is_err()); } #[test] fn test_sign_tryfrom() { let result = SignAlgorithm::try_from("rsassa"); assert!(result.is_ok()); + let result = SignAlgorithm::try_from("rsapss"); + assert!(result.is_ok()); + let result = SignAlgorithm::try_from("ecdsa"); + assert!(result.is_ok()); + let result = SignAlgorithm::try_from("ecschnorr"); + assert!(result.is_ok()); + } + #[test] + fn test_unsupported_sign_tryfrom() { + let result = SignAlgorithm::try_from("unsupported"); + assert!(result.is_err()); } } diff --git a/keylime/src/crypto.rs b/keylime/src/crypto.rs new file mode 100644 index 00000000..ee14373d --- /dev/null +++ b/keylime/src/crypto.rs @@ -0,0 +1,1469 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2021 Keylime Authors + +use base64::{engine::general_purpose, Engine as _}; +use log::*; +use openssl::{ + asn1::Asn1Time, + encrypt::Decrypter, + hash::MessageDigest, + memcmp, + nid::Nid, + pkcs5, + pkey::{Id, PKey, PKeyRef, Private, Public}, + rsa::{Padding, Rsa}, + sign::{Signer, Verifier}, + ssl::{SslAcceptor, SslAcceptorBuilder, SslMethod, SslVerifyMode}, + symm::Cipher, + x509::store::X509StoreBuilder, + x509::{X509Name, X509}, +}; +use picky_asn1_x509::SubjectPublicKeyInfo; +use std::{ + fs::{read_to_string, set_permissions, Permissions}, + io::Write, + os::unix::fs::PermissionsExt, + path::Path, + string::{FromUtf8Error, String}, +}; +use thiserror::Error; + +pub const AES_128_KEY_LEN: usize = 16; +pub const AES_256_KEY_LEN: usize = 32; +pub const AES_BLOCK_SIZE: usize = 16; + +#[derive(Error, Debug)] +pub enum CryptoError { + /// Error getting ASN.1 Time from days from now + #[error("failed to get ASN.1 Time for {days} day(s) from now")] + ASN1TimeDaysFromNowError { + days: u32, + source: openssl::error::ErrorStack, + }, + + /// Error decoding base64 + #[error("failed to decode base64")] + Base64DecodeError(#[from] base64::DecodeError), + + /// Error decrypting AES GCM encrypted data + #[error("failed to decrypt AES GCM encrypted data")] + DecryptAEADError(#[source] openssl::error::ErrorStack), + + /// Error creating file + #[error("failed to create file {file}")] + FSCreateError { + file: String, + source: std::io::Error, + }, + + /// Error calculating hash + #[error("failed to calculate hash")] + HashError(#[source] openssl::error::ErrorStack), + + /// Error generating HMAC + #[error("Failed generating HMAC: {message}")] + HMACError { + message: String, + source: openssl::error::ErrorStack, + }, + + /// Infallible + #[error("Infallible")] + Infallible(#[source] std::convert::Infallible), + + /// Invalid HMAC + #[error("invalid HMAC")] + InvalidHMAC, + + /// Invalid input length + #[error("Invalid input length {length}")] + InvalidInputLength { length: usize }, + + /// Invalid key length + #[error("Invalid key length {length}")] + InvalidKeyLength { length: usize }, + + /// Read error + #[error("failed to read")] + IOReadError(#[source] std::io::Error), + + /// Write error + #[error("failed to write")] + IOWriteError(#[source] std::io::Error), + + /// Error setting file permission + #[error("failed to set file permission")] + IOSetPermissionError(#[source] std::io::Error), + + /// Error deriving key from password with PBKDF2 + #[error("failed to derive key from password with PBKDF2")] + PBKDF2Error(#[source] openssl::error::ErrorStack), + + /// Error creating PKey structure from RSA structure + #[error("failed to create PKey structure from RSA structure")] + PKeyFromRSAError(#[source] openssl::error::ErrorStack), + + /// Error creating PKey structure for HMAC key + #[error("failed to create PKey structure for HMAC key")] + PKeyHMACNewError(#[source] openssl::error::ErrorStack), + + /// Error encoding PKey structure in PKCS#8 format + #[error("failed to encode PKey structure in PKCS#8 format")] + PKeyToPKCS8(#[source] openssl::error::ErrorStack), + + /// Error decoding private key from PEM + #[error("failed to decode private key from PEM")] + PrivateKeyFromPEMError(#[source] openssl::error::ErrorStack), + + /// Error decoding public key from DER + #[error("failed to decode public key from DER")] + PublicKeyFromDERError(#[source] openssl::error::ErrorStack), + + /// Error obtaining ECC public key from structure + #[error("failed to get ECC public key from structure")] + PublicKeyGetECCError(#[source] openssl::error::ErrorStack), + + /// Error encoding public key in DER format + #[error("failed to encode public key in DER format")] + PublicKeyToDERError(#[source] openssl::error::ErrorStack), + + /// Error encoding public key in PEM format + #[error("failed to encode public key in PEM format")] + PublicKeyToPEMError(#[source] openssl::error::ErrorStack), + + /// Error composing RSA public key structure from public components + #[error( + "failed to compose RSA public key structure from public components" + )] + RSAFromComponents(#[source] openssl::error::ErrorStack), + + /// Error generating RSA key pair + #[error("failed to generate RSA key pair")] + RSAGenerateError(#[source] openssl::error::ErrorStack), + + /// Error obtaining RSA private key from structure + #[error("failed to get RSA private key from structure")] + RSAGetPrivateKeyError(#[source] openssl::error::ErrorStack), + + /// Error obtaining RSA public key from structure + #[error("failed to get RSA public key from structure")] + RSAGetPublicKeyError(#[source] openssl::error::ErrorStack), + + /// RSA OAEP decrypt error + #[error("RSA OAEP decrypt error: {message}")] + RSAOAEPDecryptError { + message: String, + source: openssl::error::ErrorStack, + }, + + /// Error signing data + #[error("failed to sign data: {message}")] + SignError { + message: String, + source: openssl::error::ErrorStack, + }, + + /// Error generating TLS context + #[error("Failed to generate TLS context: {message}")] + SSLContextBuilderError { + message: String, + source: openssl::error::ErrorStack, + }, + + /// Error getting String from UTF-8 Vec + #[error("failed to create String from UTF-8 Vec")] + StringFromVec(#[from] FromUtf8Error), + + /// Error converting ECC public key into TSS structure + #[error("failed to convert ECC public key into TSS structure")] + SubjectPublicKeyInfoFromECCError(#[source] tss_esapi::Error), + + /// Error converting RSA public key into TSS structure + #[error("failed to convert RSA public key into TSS structure")] + SubjectPublicKeyInfoFromRSAError(#[source] tss_esapi::Error), + + /// Error converting TSS Public structure into SubjectPublicKeyInfo + #[error( + "failed to convert TSS Public structure into SubjectPublicKeyInfo" + )] + SubjectPublicKeyInfoFromTSSPublicError(#[source] tss_esapi::Error), + + /// Error encoding SubjectPublicKeyInfo in DER format + #[error("failed to encode SubjectPublicKeyInfo in DER format")] + SubjectPublicKeyInfoToDERError(#[source] picky_asn1_der::Asn1DerError), + + /// Error taking object ownership + #[error("failed to take object ownership")] + ToOwnedError(#[source] openssl::error::ErrorStack), + + /// Unsupported key algorithm + #[error("unsupported key algorithm: {id}")] + UnsupportedKeyAlgorithm { id: String }, + + /// Error verifying signature + #[error("Signature verification failed: {message}")] + VerifyError { + message: String, + source: openssl::error::ErrorStack, + }, + + /// Trusted X509 certificate store builder error + #[error("Trusted certificate store builder error: {message}")] + X509StoreBuilderError { + message: String, + source: openssl::error::ErrorStack, + }, + + /// X509 certificate builder error + #[error("X509 certificate builder error: {message}")] + X509BuilderError { + message: String, + source: openssl::error::ErrorStack, + }, + + /// Error loading X509 certificate chain from PEM file + #[error("failed to load X509 certificate chain from PEM file")] + X509ChainFromPEMError(#[source] openssl::error::ErrorStack), + + /// Error loading X509 certificate from DER file + #[error("failed to load X509 certificate from DER file")] + X509FromDERError(#[source] openssl::error::ErrorStack), + + /// Error loading X509 certificate from PEM file + #[error("failed to load X509 certificate from PEM file")] + X509FromPEMError(#[source] openssl::error::ErrorStack), + + /// Error obtaining certificate public key + #[error("failed to get certificate public key")] + X509GetPublicError(#[source] openssl::error::ErrorStack), + + /// Error creating X509 Name + #[error("Error creating X509 Name: {message}")] + X509NameError { + message: String, + source: openssl::error::ErrorStack, + }, + + /// Error encoding X509 certificate in DER format + #[error("failed to encode X509 certificate in DER format")] + X509ToDERError(#[source] openssl::error::ErrorStack), + + /// Error encoding X509 certificate in PEM format + #[error("failed to encode X509 certificate in PEM format")] + X509ToPEMError(#[source] openssl::error::ErrorStack), + + /// Unsupported key algorithm in X509 certificate + #[error("unsupported key algorithm in X509 certificate: {id}")] + X509UnsupportedKeyAlgorithm { id: String }, +} + +/// Load a X509 certificate in DER format from file +pub fn load_x509_der(input_cert_path: &Path) -> Result { + let contents = + std::fs::read(input_cert_path).map_err(CryptoError::IOReadError)?; + + X509::from_der(&contents).map_err(CryptoError::X509FromDERError) +} + +/// Load X509 certificate in PEM format from file +pub fn load_x509_pem(input_cert_path: &Path) -> Result { + let contents = + std::fs::read(input_cert_path).map_err(CryptoError::IOReadError)?; + + X509::from_pem(&contents).map_err(CryptoError::X509FromPEMError) +} + +/// Load X509 certificate chain in PEM format from file +fn load_x509_cert_chain( + input_cert_path: &Path, +) -> Result, CryptoError> { + let contents = + read_to_string(input_cert_path).map_err(CryptoError::IOReadError)?; + + X509::stack_from_pem(contents.as_bytes()) + .map_err(CryptoError::X509ChainFromPEMError) +} + +/// Load X509 certificate chains in PEM format from a list of files +pub fn load_x509_cert_list( + input_cert_list: Vec<&Path>, +) -> Result, CryptoError> { + let mut loaded = Vec::::new(); + + // This is necessary to avoid choking on failures loading certs from a file + for cert in input_cert_list { + match load_x509_cert_chain(cert) { + Ok(mut s) => { + loaded.append(&mut s); + } + Err(e) => { + warn!("Could not load certs from {}: {}", cert.display(), e); + } + } + } + Ok(loaded) +} + +/// Write a X509 certificate to a file in PEM format +pub fn write_x509(cert: &X509, file_path: &Path) -> Result<(), CryptoError> { + let mut file = std::fs::File::create(file_path).map_err(|source| { + CryptoError::FSCreateError { + file: file_path.display().to_string(), + source, + } + })?; + _ = file + .write(&cert.to_pem().map_err(CryptoError::X509ToPEMError)?) + .map_err(CryptoError::IOWriteError)?; + Ok(()) +} + +/// Get the X509 certificate public key +pub fn x509_get_pubkey(cert: &X509) -> Result, CryptoError> { + cert.public_key().map_err(CryptoError::X509GetPublicError) +} + +/// Encode the X509 certificate in PEM format +/// +/// The certificate is returned as a String +pub fn x509_to_pem(cert: &X509) -> Result { + String::from_utf8(cert.to_pem().map_err(CryptoError::X509ToPEMError)?) + .map_err(CryptoError::StringFromVec) +} + +/// Encode the X509 certificate in DER format +/// +/// The certificate is returned as a Vec +pub fn x509_to_der(cert: &X509) -> Result, CryptoError> { + cert.to_der().map_err(CryptoError::X509ToDERError) +} + +/// Encode a TSS Public key in PEM format +/// +/// The public key is returned as a Vec +pub fn tss_pubkey_to_pem( + pubkey: tss_esapi::structures::Public, +) -> Result, CryptoError> { + // Converting Public TSS key to PEM + let key = SubjectPublicKeyInfo::try_from(pubkey) + .map_err(CryptoError::SubjectPublicKeyInfoFromTSSPublicError)?; + let key_der = picky_asn1_der::to_vec(&key) + .map_err(CryptoError::SubjectPublicKeyInfoToDERError)?; + let openssl_key = PKey::public_key_from_der(&key_der) + .map_err(CryptoError::PublicKeyFromDERError)?; + let pem = openssl_key + .public_key_to_pem() + .map_err(CryptoError::PublicKeyToPEMError)?; + + Ok(pem) +} + +/// Calculate the hash of the input data using the given Message Digest algorithm +pub fn hash( + data: &[u8], + algorithm: MessageDigest, +) -> Result, CryptoError> { + Ok(openssl::hash::hash(algorithm, data) + .map_err(CryptoError::HashError)? + .to_vec()) +} + +/// Check an x509 certificate contains a specific public key +pub fn check_x509_key( + cert: &X509, + tpm_key: tss_esapi::structures::Public, +) -> Result { + // Id:RSA_PSS only added in rust-openssl from v0.10.59; remove this let and use Id::RSA_PSS after update + // Id taken from https://boringssl.googlesource.com/boringssl/+/refs/heads/master/include/openssl/nid.h#4039 + let id_rsa_pss: Id = Id::from_raw(912); + match cert + .public_key() + .map_err(CryptoError::X509GetPublicError)? + .id() + { + Id::RSA => { + let cert_n = cert + .public_key() + .map_err(CryptoError::X509GetPublicError)? + .rsa() + .map_err(CryptoError::RSAGetPublicKeyError)? + .n() + .to_vec(); + let mut cert_n_str = format!("{:?}", cert_n); + _ = cert_n_str.pop(); + _ = cert_n_str.remove(0); + let key = SubjectPublicKeyInfo::try_from(tpm_key) + .map_err(CryptoError::SubjectPublicKeyInfoFromRSAError)?; + let key_der = picky_asn1_der::to_vec(&key) + .map_err(CryptoError::SubjectPublicKeyInfoToDERError)?; + let key_der_str = format!("{:?}", key_der); + + Ok(key_der_str.contains(&cert_n_str)) + } + cert_id if cert_id == id_rsa_pss => { + let cert_n = cert + .public_key() + .map_err(CryptoError::X509GetPublicError)? + .rsa() + .map_err(CryptoError::RSAGetPublicKeyError)? + .n() + .to_vec(); + let mut cert_n_str = format!("{:?}", cert_n); + _ = cert_n_str.pop(); + _ = cert_n_str.remove(0); + let key = SubjectPublicKeyInfo::try_from(tpm_key) + .map_err(CryptoError::SubjectPublicKeyInfoFromRSAError)?; + let key_der = picky_asn1_der::to_vec(&key) + .map_err(CryptoError::SubjectPublicKeyInfoToDERError)?; + let key_der_str = format!("{:?}", key_der); + + Ok(key_der_str.contains(&cert_n_str)) + } + Id::EC => { + let cert_n = cert + .public_key() + .map_err(CryptoError::X509GetPublicError)? + .ec_key() + .map_err(CryptoError::PublicKeyGetECCError)? + .public_key_to_der() + .map_err(CryptoError::PublicKeyToDERError)?; + let mut cert_n_str = format!("{:?}", cert_n); + _ = cert_n_str.pop(); + _ = cert_n_str.remove(0); + let key = SubjectPublicKeyInfo::try_from(tpm_key) + .map_err(CryptoError::SubjectPublicKeyInfoFromECCError)?; + let key_der = picky_asn1_der::to_vec(&key) + .map_err(CryptoError::SubjectPublicKeyInfoToDERError)?; + let key_der_str = format!("{:?}", key_der); + + Ok(key_der_str.contains(&cert_n_str)) + } + id => Err(CryptoError::X509UnsupportedKeyAlgorithm { + id: format!("{id:?}"), + }), + } +} + +/// Detect a template from a certificate +/// Templates defined in: TPM 2.0 Keys for Device Identity and Attestation at https://trustedcomputinggroup.org/wp-content/uploads/TPM-2p0-Keys-for-Device-Identity-and-Attestation_v1_r12_pub10082021.pdf +pub fn match_cert_to_template(cert: &X509) -> Result { + // Id:RSA_PSS only added in rust-openssl from v0.10.59; remove this let and use Id::RSA_PSS after update + // Id taken from https://boringssl.googlesource.com/boringssl/+/refs/heads/master/include/openssl/nid.h#4039 + let id_rsa_pss: Id = Id::from_raw(912); + match cert + .public_key() + .map_err(CryptoError::X509GetPublicError)? + .id() + { + Id::RSA => match cert + .public_key() + .map_err(CryptoError::X509GetPublicError)? + .bits() + { + 2048 => Ok("H-1".to_string()), + _ => Ok("".to_string()), + }, + cert_id if cert_id == id_rsa_pss => match cert + .public_key() + .map_err(CryptoError::X509GetPublicError)? + .bits() + { + 2048 => Ok("H-1".to_string()), + _ => Ok("".to_string()), + }, + Id::EC => match cert + .public_key() + .map_err(CryptoError::X509GetPublicError)? + .bits() + { + 256 => match cert + .public_key() + .map_err(CryptoError::X509GetPublicError)? + .ec_key() + .map_err(CryptoError::PublicKeyGetECCError)? + .group() + .curve_name() + { + Some(Nid::SECP256K1) => Ok("H-2".to_string()), + _ => Ok("H-5".to_string()), + }, + 384 => Ok("H-3".to_string()), + 521 => Ok("H-4".to_string()), + _ => Ok("".to_string()), + }, + id => Err(CryptoError::X509UnsupportedKeyAlgorithm { + id: format!("{id:?}"), + }), + } +} + +/// Read a PEM file and returns the public and private keys +pub fn load_key_pair( + key_path: &Path, + key_password: Option<&str>, +) -> Result<(PKey, PKey), CryptoError> { + let pem = std::fs::read(key_path).map_err(CryptoError::IOReadError)?; + let private = match key_password { + Some(pw) => { + if pw.is_empty() { + PKey::private_key_from_pem(&pem) + .map_err(CryptoError::PrivateKeyFromPEMError)? + } else { + PKey::private_key_from_pem_passphrase(&pem, pw.as_bytes()) + .map_err(CryptoError::PrivateKeyFromPEMError)? + } + } + None => PKey::private_key_from_pem(&pem) + .map_err(CryptoError::PrivateKeyFromPEMError)?, + }; + let public = pkey_pub_from_priv(private.clone())?; + Ok((public, private)) +} + +/// Write a private key to a file. +/// +/// If a passphrase is provided, the key will be stored encrypted using AES-256-CBC +pub fn write_key_pair( + key: &PKey, + file_path: &Path, + passphrase: Option<&str>, +) -> Result<(), CryptoError> { + // Write the generated key to the file + let mut file = std::fs::File::create(file_path).map_err(|source| { + CryptoError::FSCreateError { + file: file_path.display().to_string(), + source, + } + })?; + match passphrase { + Some(pw) => { + if pw.is_empty() { + _ = file + .write( + &key.private_key_to_pem_pkcs8() + .map_err(CryptoError::PKeyToPKCS8)?, + ) + .map_err(CryptoError::IOWriteError)?; + } else { + _ = file + .write( + &key.private_key_to_pem_pkcs8_passphrase( + openssl::symm::Cipher::aes_256_cbc(), + pw.as_bytes(), + ) + .map_err(CryptoError::PKeyToPKCS8)?, + ) + .map_err(CryptoError::IOWriteError)?; + } + } + None => { + _ = file + .write( + &key.private_key_to_pem_pkcs8() + .map_err(CryptoError::PKeyToPKCS8)?, + ) + .map_err(CryptoError::IOWriteError)?; + } + } + set_permissions(file_path, Permissions::from_mode(0o600)) + .map_err(CryptoError::IOSetPermissionError)?; + Ok(()) +} + +fn rsa_generate(key_size: u32) -> Result, CryptoError> { + PKey::from_rsa( + Rsa::generate(key_size).map_err(CryptoError::RSAGenerateError)?, + ) + .map_err(CryptoError::PKeyFromRSAError) +} + +pub fn rsa_generate_pair( + key_size: u32, +) -> Result<(PKey, PKey), CryptoError> { + let private = rsa_generate(key_size)?; + let public = pkey_pub_from_priv(private.clone())?; + Ok((public, private)) +} + +fn pkey_pub_from_priv( + privkey: PKey, +) -> Result, CryptoError> { + match privkey.id() { + Id::RSA => { + let rsa = Rsa::from_public_components( + privkey + .rsa() + .map_err(CryptoError::RSAGetPrivateKeyError)? + .n() + .to_owned() + .map_err(CryptoError::ToOwnedError)?, + privkey + .rsa() + .map_err(CryptoError::RSAGetPrivateKeyError)? + .e() + .to_owned() + .map_err(CryptoError::ToOwnedError)?, + ) + .map_err(CryptoError::RSAFromComponents)?; + PKey::from_rsa(rsa).map_err(CryptoError::PKeyFromRSAError) + } + id => Err(CryptoError::UnsupportedKeyAlgorithm { + id: format!("{id:?}"), + }), + } +} + +pub fn pkey_pub_to_pem(pubkey: &PKey) -> Result { + pubkey + .public_key_to_pem() + .map_err(CryptoError::PublicKeyToPEMError) + .and_then(|s| { + String::from_utf8(s).map_err(CryptoError::StringFromVec) + }) +} + +pub fn generate_x509( + key: &PKey, + uuid: &str, +) -> Result { + let mut name = + X509Name::builder().map_err(|source| CryptoError::X509NameError { + message: "failed to create X509 Name object".into(), + source, + })?; + name.append_entry_by_nid(Nid::COMMONNAME, uuid) + .map_err(|source| CryptoError::X509NameError { + message: "failed to append entry by NID to X509 Name".into(), + source, + })?; + let name = name.build(); + + let valid_from = Asn1Time::days_from_now(0).map_err(|source| { + CryptoError::ASN1TimeDaysFromNowError { days: 0, source } + })?; + let valid_to = Asn1Time::days_from_now(365).map_err(|source| { + CryptoError::ASN1TimeDaysFromNowError { days: 365, source } + })?; + + let mut builder = + X509::builder().map_err(|source| CryptoError::X509BuilderError { + message: "failed to create X509 certificate builder object" + .into(), + source, + })?; + builder.set_version(2).map_err(|source| { + CryptoError::X509BuilderError { + message: "failed to set X509 certificate version".into(), + source, + } + })?; + builder.set_subject_name(&name).map_err(|source| { + CryptoError::X509BuilderError { + message: "failed to set X509 certificate subject name".into(), + source, + } + })?; + builder.set_issuer_name(&name).map_err(|source| { + CryptoError::X509BuilderError { + message: "failed to set X509 issuer name".into(), + source, + } + })?; + builder.set_not_before(&valid_from).map_err(|source| { + CryptoError::X509BuilderError { + message: "failed to set X509 certificate Not Before date".into(), + source, + } + })?; + builder.set_not_after(&valid_to).map_err(|source| { + CryptoError::X509BuilderError { + message: "failed to set X509 certificate Nof After date".into(), + source, + } + })?; + builder.set_pubkey(key).map_err(|source| { + CryptoError::X509BuilderError { + message: "failed to set X509 certificate public key".into(), + source, + } + })?; + builder + .sign(key, MessageDigest::sha256()) + .map_err(|source| CryptoError::X509BuilderError { + message: "failed to sign X509 certificate".into(), + source, + })?; + + Ok(builder.build()) +} + +pub fn generate_tls_context( + tls_cert: &X509, + key: &PKey, + ca_certs: Vec, +) -> Result { + let mut ssl_context_builder = SslAcceptor::mozilla_intermediate( + SslMethod::tls(), + ) + .map_err(|source| CryptoError::SSLContextBuilderError { + message: "failed to create Context Builder object".into(), + source, + })?; + ssl_context_builder + .set_certificate(tls_cert) + .map_err(|source| CryptoError::SSLContextBuilderError { + message: "failed to set SSL server certificate".into(), + source, + })?; + ssl_context_builder.set_private_key(key).map_err(|source| { + CryptoError::SSLContextBuilderError { + message: "failed to set SSL server private key".into(), + source, + } + })?; + + // Build verification cert store. + let mut mtls_store_builder = + X509StoreBuilder::new().map_err(|source| { + CryptoError::X509StoreBuilderError { + message: + "failed to create X509 certificate store builder object" + .into(), + source, + } + })?; + for cert in ca_certs { + mtls_store_builder + .add_cert(cert) + .map_err(|source| CryptoError::X509StoreBuilderError{ + message: "failed to add certificate to X509 trusted certificate store".into(), + source, + })?; + } + + let mtls_store = mtls_store_builder.build(); + ssl_context_builder + .set_verify_cert_store(mtls_store) + .map_err(|source| CryptoError::SSLContextBuilderError { + message: "failed to set SSL server trusted certificate store" + .into(), + source, + })?; + + // Enable mutual TLS verification + let mut verify_mode = SslVerifyMode::empty(); + verify_mode.set(SslVerifyMode::PEER, true); + verify_mode.set(SslVerifyMode::FAIL_IF_NO_PEER_CERT, true); + ssl_context_builder.set_verify(verify_mode); + + Ok(ssl_context_builder) +} + +/* + * Inputs: password to derive key + * shared salt + * Output: derived key + * + * Take in a password and shared salt, and derive a key based on the + * PBKDF2-HMAC key derivation function. Parameters match that of + * Python-Keylime. + * + * NOTE: This uses SHA-1 as the KDF's hash function in order to match the + * implementation of PBKDF2 in the Python version of Keylime. PyCryptodome's + * PBKDF2 function defaults to SHA-1 unless otherwise specified, and + * Python-Keylime uses this default. + */ +pub fn pbkdf2( + input_password: String, + input_salt: String, +) -> Result { + let password = input_password.as_bytes(); + let salt = input_salt.as_bytes(); + let count = 2000; + // PyCryptodome's PBKDF2 binding allows key length to be specified + // explicitly as a parameter; here, key length is implicitly defined in + // the length of the 'key' variable. + let mut key = [0; 32]; + pkcs5::pbkdf2_hmac( + password, + salt, + count, + MessageDigest::sha1(), + &mut key, + ) + .map_err(CryptoError::PBKDF2Error)?; + Ok(hex::encode(&key[..])) +} + +/* + * Input: Trusted public key, and remote message and signature + * Output: true if they are verified, otherwise false + * + * Verify a remote message and signature against a local rsa cert + */ +pub fn asym_verify( + keypair: &PKeyRef, + message: &str, + signature: &str, +) -> Result { + let mut verifier = Verifier::new(MessageDigest::sha256(), keypair) + .map_err(|source| CryptoError::VerifyError { + message: "failed to create signature verifier object".into(), + source, + })?; + verifier + .set_rsa_padding(Padding::PKCS1_PSS) + .map_err(|source| CryptoError::VerifyError { + message: "failed to set signature verifier padding algorithm" + .into(), + source, + })?; + verifier + .set_rsa_mgf1_md(MessageDigest::sha256()) + .map_err(|source| CryptoError::VerifyError { + message: + "failed to set signature verifier Message Digest algorithm" + .into(), + source, + })?; + verifier + .set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::MAXIMUM_LENGTH) + .map_err(|source| CryptoError::VerifyError { + message: "failed to set signature verifier RSA PSS salt length" + .into(), + source, + })?; + verifier.update(message.as_bytes()).map_err(|source| { + CryptoError::VerifyError { + message: "failed adding input data to signature verifier".into(), + source, + } + })?; + verifier + .verify( + &general_purpose::STANDARD + .decode(signature.as_bytes()) + .map_err(CryptoError::Base64DecodeError)?, + ) + .map_err(|source| CryptoError::VerifyError { + message: "invalid signature".into(), + source, + }) +} + +/* + * Inputs: OpenSSL RSA key + * ciphertext to be decrypted + * Output: decrypted plaintext + * + * Take in an RSA-encrypted ciphertext and an RSA private key and decrypt the + * ciphertext based on PKCS1 OAEP. + */ +pub fn rsa_oaep_decrypt( + priv_key: &PKey, + data: &[u8], +) -> Result, CryptoError> { + let mut decrypter = Decrypter::new(priv_key).map_err(|source| { + CryptoError::RSAOAEPDecryptError { + message: "failed to create RSA decrypter object".into(), + source, + } + })?; + + decrypter + .set_rsa_padding(Padding::PKCS1_OAEP) + .map_err(|source| CryptoError::RSAOAEPDecryptError { + message: "failed to set RSA decrypter padding".into(), + source, + })?; + decrypter + .set_rsa_mgf1_md(MessageDigest::sha1()) + .map_err(|source| CryptoError::RSAOAEPDecryptError { + message: "failed to set RSA decrypter Message Digest algorithm" + .into(), + source, + })?; + decrypter + .set_rsa_oaep_md(MessageDigest::sha1()) + .map_err(|source| CryptoError::RSAOAEPDecryptError { + message: + "failed to set RSA decrypter OAEP Message Digest algorithm" + .into(), + source, + })?; + + // Create an output buffer + let buffer_len = decrypter.decrypt_len(data).map_err(|source| { + CryptoError::RSAOAEPDecryptError { + message: "failed to get RSA decrypter output length".into(), + source, + } + })?; + let mut decrypted = vec![0; buffer_len]; + + // Decrypt and truncate the buffer + let decrypted_len = + decrypter.decrypt(data, &mut decrypted).map_err(|source| { + CryptoError::RSAOAEPDecryptError { + message: "failed to decrypt data with RSA OAEP".into(), + source, + } + })?; + decrypted.truncate(decrypted_len); + + Ok(decrypted) +} + +/* + * Inputs: secret key + * message to sign + * Output: signed HMAC result + * + * Sign message and return HMAC result string + */ +pub fn compute_hmac(key: &[u8], data: &[u8]) -> Result, CryptoError> { + let pkey = PKey::hmac(key).map_err(CryptoError::PKeyHMACNewError)?; + // SHA-384 is used as the underlying hash algorithm. + // + // Reference: + // https://keylime-docs.readthedocs.io/en/latest/rest_apis.html#post--v1.0-keys-ukey + // https://github.com/keylime/keylime/blob/910b38b296038b187a020c095dc747e9c46cbef3/keylime/crypto.py#L151 + let mut signer = + Signer::new(MessageDigest::sha384(), &pkey).map_err(|source| { + CryptoError::SignError { + message: "failed creating Signer object".into(), + source, + } + })?; + signer + .update(data) + .map_err(|source| CryptoError::SignError { + message: "failed to add input data to Signer".into(), + source, + })?; + signer + .sign_to_vec() + .map_err(|source| CryptoError::SignError { + message: "failed to generate signature".into(), + source, + }) +} + +pub fn verify_hmac( + key: &[u8], + data: &[u8], + hmac: &[u8], +) -> Result<(), CryptoError> { + let pkey = PKey::hmac(key).map_err(CryptoError::PKeyHMACNewError)?; + // SHA-384 is used as the underlying hash algorithm. + // + // Reference: + // https://keylime-docs.readthedocs.io/en/latest/rest_apis.html#post--v1.0-keys-ukey + // https://github.com/keylime/keylime/blob/910b38b296038b187a020c095dc747e9c46cbef3/keylime/crypto.py#L151 + let mut signer = + Signer::new(MessageDigest::sha384(), &pkey).map_err(|source| { + CryptoError::HMACError { + message: "failed to create Signer object".into(), + source, + } + })?; + signer + .update(data) + .map_err(|source| CryptoError::HMACError { + message: "failed to add input data to Signer".into(), + source, + })?; + + if !memcmp::eq( + &signer + .sign_to_vec() + .map_err(|source| CryptoError::HMACError { + message: "failed to generate HMAC".into(), + source, + })?, + hmac, + ) { + return Err(CryptoError::InvalidHMAC); + } + + Ok(()) +} + +pub fn decrypt_aead(key: &[u8], data: &[u8]) -> Result, CryptoError> { + let cipher = match key.len() { + AES_128_KEY_LEN => Cipher::aes_128_gcm(), + AES_256_KEY_LEN => Cipher::aes_256_gcm(), + other => return Err(CryptoError::InvalidKeyLength { length: other }), + }; + + // Parse out payload IV, tag, ciphertext. Note that Keylime + // currently uses 16-byte IV, while the recommendation in SP + // 800-38D is 12-byte. + // + // Reference: + // https://github.com/keylime/keylime/blob/1663a7702b3286152b38dbcb715a9eb6705e05e9/keylime/crypto.py#L191 + let length = data.len(); + if length < AES_BLOCK_SIZE * 2 { + return Err(CryptoError::InvalidInputLength { length }); + } + let (iv, rest) = data.split_at(AES_BLOCK_SIZE); + let (ciphertext, tag) = rest.split_at(rest.len() - AES_BLOCK_SIZE); + + openssl::symm::decrypt_aead(cipher, key, Some(iv), &[], ciphertext, tag) + .map_err(CryptoError::DecryptAEADError) +} + +pub mod testing { + use super::*; + use openssl::encrypt::Encrypter; + use std::path::Path; + + #[derive(Error, Debug)] + pub enum CryptoTestError { + /// Crypto error + #[error("CryptoError")] + CryptoError(#[from] CryptoError), + + /// IO error + #[error("IOError")] + IoError(#[from] std::io::Error), + + /// OpenSSL error + #[error("IOError")] + OpenSSLError(#[from] openssl::error::ErrorStack), + + /// Invalid IV length + #[error("Invalid IV length: expected {expected} got {got}")] + InvalidIVLen { expected: usize, got: usize }, + } + + pub fn rsa_import_pair( + path: impl AsRef, + ) -> Result<(PKey, PKey), CryptoTestError> { + let contents = read_to_string(path)?; + let private = PKey::private_key_from_pem(contents.as_bytes())?; + let public = pkey_pub_from_priv(private.clone())?; + Ok((public, private)) + } + + pub fn pkey_pub_from_pem( + pem: &str, + ) -> Result, CryptoTestError> { + PKey::::public_key_from_pem(pem.as_bytes()) + .map_err(CryptoTestError::OpenSSLError) + } + + pub fn rsa_oaep_encrypt( + pub_key: &PKey, + data: &[u8], + ) -> Result, CryptoTestError> { + let mut encrypter = Encrypter::new(pub_key)?; + + encrypter.set_rsa_padding(Padding::PKCS1_OAEP)?; + encrypter.set_rsa_mgf1_md(MessageDigest::sha1())?; + encrypter.set_rsa_oaep_md(MessageDigest::sha1())?; + + // Create an output buffer + let buffer_len = encrypter.encrypt_len(data)?; + let mut encrypted = vec![0; buffer_len]; + + // Encrypt and truncate the buffer + let encrypted_len = encrypter.encrypt(data, &mut encrypted)?; + encrypted.truncate(encrypted_len); + + Ok(encrypted) + } + + pub fn encrypt_aead( + key: &[u8], + iv: &[u8], + data: &[u8], + ) -> Result, CryptoTestError> { + let cipher = match key.len() { + AES_128_KEY_LEN => Cipher::aes_128_gcm(), + AES_256_KEY_LEN => Cipher::aes_256_gcm(), + other => { + return Err( + CryptoError::InvalidKeyLength { length: other }.into() + ); + } + }; + let iv_len = iv.len(); + if iv_len != AES_BLOCK_SIZE { + return Err(CryptoTestError::InvalidIVLen { + expected: AES_BLOCK_SIZE, + got: iv_len, + }); + } + let mut tag = vec![0u8; AES_BLOCK_SIZE]; + let ciphertext = openssl::symm::encrypt_aead( + cipher, + key, + Some(iv), + &[], + data, + &mut tag, + )?; + + let mut result = + Vec::with_capacity(iv.len() + ciphertext.len() + tag.len()); + result.extend(iv); + result.extend(ciphertext); + result.extend(tag); + Ok(result) + } + + pub fn rsa_generate( + key_size: u32, + ) -> Result, CryptoTestError> { + super::rsa_generate(key_size).map_err(CryptoTestError::CryptoError) + } + + pub fn write_x509_der( + cert: &X509, + file_path: &Path, + ) -> Result<(), CryptoTestError> { + let mut file = + std::fs::File::create(file_path).map_err(|source| { + CryptoError::FSCreateError { + file: file_path.display().to_string(), + source, + } + })?; + _ = file + .write(&cert.to_der().map_err(CryptoError::X509ToDERError)?) + .map_err(CryptoError::IOWriteError)?; + Ok(()) + } +} + +// Unit Testing +#[cfg(test)] +mod tests { + use super::*; + use std::{fs, path::Path}; + use testing::{encrypt_aead, rsa_import_pair, rsa_oaep_encrypt}; + + // compare with the result from python output + #[test] + fn test_compute_hmac() { + let key = String::from("mysecret"); + let message = String::from("hellothere"); + let mac = + compute_hmac(key.as_bytes(), message.as_bytes()).map(hex::encode); + assert_eq!( + format!( + "{}{}", + "b8558314f515931c8d9b329805978fe77b9bb020b05406c0e", + "f189d89846ff8f5f0ca10e387d2c424358171df7f896f9f" + ), + mac.unwrap() //#[allow_ci] + ); + } + + // Test KDF to ensure derived password matches result derived from Python + // functions. + #[test] + fn test_kdf() { + let password = String::from("myverysecretsecret"); + let salt = String::from("thesaltiestsalt"); + let key = pbkdf2(password, salt); + assert_eq!( + "8a6de415abb8b27de5c572c8137bd14e5658395f9a2346e0b1ad8b9d8b9028af" + .to_string(), + key.unwrap() //#[allow_ci] + ); + } + + #[test] + fn test_hmac_verification() { + // Generate a keypair + let (pub_key, priv_key) = rsa_generate_pair(2048).unwrap(); //#[allow_ci] + let data = b"hello, world!"; + let data2 = b"hola, mundo!"; + + // Sign the data + let mut signer = + Signer::new(MessageDigest::sha256(), &priv_key).unwrap(); //#[allow_ci] + signer.update(data).unwrap(); //#[allow_ci] + signer.update(data2).unwrap(); //#[allow_ci] + let signature = signer.sign_to_vec().unwrap(); //#[allow_ci] + + // Verify the data + let mut verifier = + Verifier::new(MessageDigest::sha256(), &pub_key).unwrap(); //#[allow_ci] + verifier.update(data).unwrap(); //#[allow_ci] + verifier.update(data2).unwrap(); //#[allow_ci] + assert!(verifier.verify(&signature).unwrap()); //#[allow_ci] + } + + #[test] + fn test_rsa_oaep() { + // Import a keypair + let rsa_key_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("test-data") + .join("test-rsa.pem"); + + let (pub_key, priv_key) = rsa_import_pair(rsa_key_path) + .expect("unable to import RSA key pair"); + let plaintext = b"0123456789012345"; + let ciphertext = rsa_oaep_encrypt(&pub_key, &plaintext[..]) + .expect("unable to encrypt"); + + // We can't check against the fixed ciphertext, as OAEP + // involves randomness. Check with a round-trip instead. + let decrypted = rsa_oaep_decrypt(&priv_key, &ciphertext[..]) + .expect("unable to decrypt"); + assert_eq!(decrypted, plaintext); + } + + #[test] + fn test_encrypt_aead_short() { + let key = b"0123456789012345"; + let iv = b"ABCDEFGHIJKLMNOP"; + let plaintext = b"test string, longer than the block size"; + let ciphertext = encrypt_aead(&key[..], &iv[..], &plaintext[..]) + .expect("unable to encrypt"); + let expected = hex::decode("4142434445464748494A4B4C4D4E4F50B2198661586C9839CCDD0B1D5B4FF92FA9C0E6477C4E8E42C19ACD9E8061DD1E759401337DA285A70580E6A2E10B5D3A09994F46D90AB6").unwrap(); //#[allow_ci] + assert_eq!(ciphertext, expected); + } + + #[test] + fn test_decrypt_aead_short() { + let key = b"0123456789012345"; + let ciphertext = hex::decode("4142434445464748494A4B4C4D4E4F50B2198661586C9839CCDD0B1D5B4FF92FA9C0E6477C4E8E42C19ACD9E8061DD1E759401337DA285A70580E6A2E10B5D3A09994F46D90AB6").unwrap(); //#[allow_ci] + let plaintext = decrypt_aead(&key[..], &ciphertext[..]) + .expect("unable to decrypt"); + let expected = b"test string, longer than the block size"; + assert_eq!(plaintext, expected); + } + + #[test] + fn test_encrypt_aead_long() { + let key = b"01234567890123450123456789012345"; + let iv = b"ABCDEFGHIJKLMNOP"; + let plaintext = b"test string, longer than the block size"; + let ciphertext = encrypt_aead(&key[..], &iv[..], &plaintext[..]) + .expect("unable to encrypt"); + let expected = hex::decode("4142434445464748494A4B4C4D4E4F50FCE7CA78C08FB1D5E04DB3C4AA6B6ED2F09C4AD7985BD1DB9FF15F9FDA869D0C01B27FF4618737BB53C84D256455AAB53B9AC7EAF88C4B").unwrap(); //#[allow_ci] + assert_eq!(ciphertext, expected); + } + + #[test] + fn test_decrypt_aead_long() { + let key = b"01234567890123450123456789012345"; + let ciphertext = hex::decode("4142434445464748494A4B4C4D4E4F50FCE7CA78C08FB1D5E04DB3C4AA6B6ED2F09C4AD7985BD1DB9FF15F9FDA869D0C01B27FF4618737BB53C84D256455AAB53B9AC7EAF88C4B").unwrap(); //#[allow_ci] + let plaintext = decrypt_aead(&key[..], &ciphertext[..]) + .expect("unable to decrypt"); + let expected = b"test string, longer than the block size"; + assert_eq!(plaintext, expected); + } + + #[test] + fn test_encrypt_aead_invalid_key_length() { + let key = b"0123456789012345012345678901234"; + let iv = b"ABCDEFGHIJKLMNOP"; + let plaintext = b"test string, longer than the block size"; + let result = encrypt_aead(&key[..], &iv[..], &plaintext[..]); + assert!(result.is_err()) + } + + #[test] + fn test_encrypt_aead_invalid_iv_length() { + let key = b"01234567890123450123456789012345"; + let iv = b"ABCDEFGHIJKLMN"; + let plaintext = b"test string, longer than the block size"; + let result = encrypt_aead(&key[..], &iv[..], &plaintext[..]); + assert!(result.is_err()) + } + + #[test] + fn test_decrypt_aead_invalid_key_length() { + let key = b"0123456789012345012345678901234"; + let ciphertext = hex::decode("4142434445464748494A4B4C4D4E4F50FCE7CA78C08FB1D5E04DB3C4AA6B6ED2F09C4AD7985BD1DB9FF15F9FDA869D0C01B27FF4618737BB53C84D256455AAB53B9AC7EAF88C4B").unwrap(); //#[allow_ci] + let result = decrypt_aead(&key[..], &ciphertext[..]); + assert!(result.is_err()) + } + + #[test] + fn test_decrypt_aead_invalid_ciphertext_length() { + let key = b"0123456789012345"; + let ciphertext = hex::decode("41424344").unwrap(); //#[allow_ci] + let result = decrypt_aead(&key[..], &ciphertext[..]); + assert!(result.is_err()); + } + + #[test] + fn test_asym_verify() { + // Import test keypair + let rsa_key_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("test-data") + .join("test-rsa.pem"); + + // Get RSA keys + let contents = read_to_string(rsa_key_path); + let private = + PKey::private_key_from_pem(contents.unwrap().as_bytes()).unwrap(); //#[allow_ci] + let public = pkey_pub_from_priv(private).unwrap(); //#[allow_ci] + + let message = String::from("Hello World!"); + + // Get known valid signature + let signature_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("test-data") + .join("test-rsa.sig"); + + let signature = read_to_string(signature_path).unwrap(); //#[allow_ci] + + assert!(asym_verify(&public, &message, &signature).unwrap()) //#[allow_ci] + } + + #[test] + fn test_password() { + // Import test keypair + let rsa_key_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("test-data") + .join("test-rsa.pem"); + + // Get RSA keys + let (_public, private) = rsa_import_pair(rsa_key_path).unwrap(); //#[allow_ci] + + // Create temporary directory and files names + let temp_dir = tempfile::tempdir().unwrap(); //#[allow_ci] + let encrypted_path = + Path::new(&temp_dir.path()).join("encrypted.pem"); + let empty_pw_path = Path::new(&temp_dir.path()).join("empty_pw.pem"); + let none_pw_path = Path::new(&temp_dir.path()).join("none_pw.pem"); + + let message = b"Hello World!"; + + // Write keys to files + assert!(write_key_pair(&private, &encrypted_path, Some("password")) + .is_ok()); + assert!(write_key_pair(&private, &empty_pw_path, Some("")).is_ok()); + assert!(write_key_pair(&private, &none_pw_path, None).is_ok()); + + // Read keys from files + let (_, priv_from_encrypted) = + load_key_pair(&encrypted_path, Some("password")).unwrap(); //#[allow_ci] + let (_, priv_from_empty) = + load_key_pair(&empty_pw_path, Some("")).unwrap(); //#[allow_ci] + let (_, priv_from_none) = load_key_pair(&none_pw_path, None).unwrap(); //#[allow_ci] + + for keypair in [ + priv_from_encrypted.as_ref(), + priv_from_empty.as_ref(), + priv_from_none.as_ref(), + ] { + // Sign the data + let mut signer = + Signer::new(MessageDigest::sha256(), keypair).unwrap(); //#[allow_ci] + signer.update(message).unwrap(); //#[allow_ci] + let signature = signer.sign_to_vec().unwrap(); //#[allow_ci] + + // Verify the data + let mut verifier = + Verifier::new(MessageDigest::sha256(), keypair).unwrap(); //#[allow_ci] + verifier.update(message).unwrap(); //#[allow_ci] + assert!(verifier.verify(&signature).unwrap()); //#[allow_ci] + } + } + + #[test] + fn test_hash() { + let input = "hello world!".as_bytes(); + let h = hash(input, MessageDigest::sha256()); + assert!(h.is_ok()); + let hex = hex::encode(h.unwrap()); //#[allow_ci] + assert_eq!(hex, "7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9"); + + let h = hash(input, MessageDigest::sha384()); + assert!(h.is_ok()); + let hex = hex::encode(h.unwrap()); //#[allow_ci] + assert_eq!(hex, "d33d40f7010ce34aa86efd353630309ed5c3d7ffac66d988825cf699f4803ccdf3f033230612f0945332fb580d8af805"); + + let h = hash(input, MessageDigest::sha512()); + assert!(h.is_ok()); + let hex = hex::encode(h.unwrap()); //#[allow_ci] + assert_eq!(hex, "db9b1cd3262dee37756a09b9064973589847caa8e53d31a9d142ea2701b1b28abd97838bb9a27068ba305dc8d04a45a1fcf079de54d607666996b3cc54f6b67c"); + } + + #[test] + fn test_x509() { + let tempdir = tempfile::tempdir().unwrap(); //#[allow_ci] + + let (_pubkey, privkey) = rsa_generate_pair(2048).unwrap(); //#[allow_ci] + + let r = generate_x509(&privkey, "uuidA"); + assert!(r.is_ok()); + let cert_a = r.unwrap(); //#[allow_ci] + let cert_a_path = tempdir.path().join("cert_a.pem"); + let r = write_x509(&cert_a, &cert_a_path); + assert!(r.is_ok()); + assert!(cert_a_path.exists()); + + let r = generate_x509(&privkey, "uuidB"); + assert!(r.is_ok()); + let cert_b = r.unwrap(); //#[allow_ci] + let cert_b_path = tempdir.path().join("cert_b.pem"); + let r = write_x509(&cert_b, &cert_b_path); + assert!(r.is_ok()); + assert!(cert_b_path.exists()); + + let loaded_chain = load_x509_cert_chain(&cert_a_path); + assert!(loaded_chain.is_ok()); + let mut loaded_chain = loaded_chain.unwrap(); //#[allow_ci] + assert_eq!(loaded_chain.len(), 1); + let loaded_a = loaded_chain.pop().unwrap(); //#[allow_ci] + + let a_str = read_to_string(&cert_a_path).unwrap(); //#[allow_ci] + let b_str = read_to_string(&cert_b_path).unwrap(); //#[allow_ci] + let concat = a_str + &b_str; + let concat_path = tempdir.path().join("concat.pem"); + fs::write(&concat_path, concat).unwrap(); //#[allow_ci] + + // Load a single certificate from a file with multiple certificates + let r = load_x509_pem(&concat_path); + assert!(r.is_ok()); + + let cert = r.unwrap(); //#[allow_ci] + + // Test getting public key from cert + let r = x509_get_pubkey(&cert); + assert!(r.is_ok()); + + // Test converting certificate to DER + let r = x509_to_der(&cert); + assert!(r.is_ok()); + + // Test converting certificate to PEM + let r = x509_to_pem(&cert); + assert!(r.is_ok()); + + // Test loading DER certificate + let der_path = tempdir.path().join("cert.der"); + let r = testing::write_x509_der(&cert, &der_path); + assert!(r.is_ok()); + let r = load_x509_der(&der_path); + assert!(r.is_ok()); + + // Loading multiple PEM certs should work when loading chain + let r = load_x509_cert_chain(&concat_path); + assert!(r.is_ok()); + let chain = r.unwrap(); //#[allow_ci] + assert!(chain.len() == 2); + + // Test adding loading certs from a list, including an non-existing file + let non_existing = + Path::new("/non_existing_path/non_existing_cert.pem"); + let cert_list: Vec<&Path> = + vec![&cert_a_path, non_existing, &cert_b_path]; + let r = load_x509_cert_list(cert_list); + assert!(r.is_ok()); + let loaded_list = r.unwrap(); //#[allow_ci] + assert!(loaded_list.len() == 2); + + let r = generate_tls_context(&loaded_a, &privkey, loaded_list); + assert!(r.is_ok()); + } +} diff --git a/keylime/src/lib.rs b/keylime/src/lib.rs index b5374ae6..17fff909 100644 --- a/keylime/src/lib.rs +++ b/keylime/src/lib.rs @@ -1,4 +1,5 @@ pub mod algorithms; +pub mod crypto; pub mod ima; pub mod list_parser; pub mod tpm; diff --git a/keylime/src/list_parser.rs b/keylime/src/list_parser.rs index 1753ba0f..f6ded7c5 100644 --- a/keylime/src/list_parser.rs +++ b/keylime/src/list_parser.rs @@ -10,9 +10,9 @@ use thiserror::Error; pub struct ListParser; #[derive(Error, Debug)] -pub enum Error { - #[error("Parsing error: {0}")] - ParseError(Box>), +pub enum ListParsingError { + #[error("failed to parse list")] + ParseError(#[source] Box>), } fn get_inner_str(pair: Pair) -> Vec<&str> { @@ -81,9 +81,9 @@ fn get_inner_str(pair: Pair) -> Vec<&str> { /// * `[a b c]` => `["a", "b", "c"]` /// * `['a', "b", c]` => `["'a'", "\"b\"", "c"]` /// -pub fn parse_list(list: &str) -> Result, Error> { +pub fn parse_list(list: &str) -> Result, ListParsingError> { if let Some(pair) = ListParser::parse(Rule::list, list) - .map_err(|e| Error::ParseError(Box::new(e)))? + .map_err(|e| ListParsingError::ParseError(Box::new(e)))? .next() { return Ok(get_inner_str(pair)); diff --git a/keylime/src/tpm.rs b/keylime/src/tpm.rs index 66440acc..2a93f0df 100644 --- a/keylime/src/tpm.rs +++ b/keylime/src/tpm.rs @@ -114,118 +114,118 @@ pub enum TpmError { UnsupportedHashingAlgorithm { alg: HashingAlgorithm }, /// Error creating EK object - #[error("Error creating EK object: {e}")] - TSSCreateEKError { e: tss_esapi::Error }, + #[error("Error creating EK object")] + TSSCreateEKError { source: tss_esapi::Error }, /// Error creating AK object - #[error("Error creating AK object: {e}")] - TSSCreateAKError { e: tss_esapi::Error }, + #[error("Error creating AK object")] + TSSCreateAKError { source: tss_esapi::Error }, /// Error loading AK object - #[error("Error loading AK object: {e}")] - TSSLoadAKError { e: tss_esapi::Error }, + #[error("Error loading AK object")] + TSSLoadAKError { source: tss_esapi::Error }, /// Error creating new persistent TPM handle - #[error( - "Error creating handle for persistent TPM object in {handle}: {e}" - )] - TSSNewPersistentHandleError { handle: String, e: tss_esapi::Error }, + #[error("Error creating handle for persistent TPM object in {handle}")] + TSSNewPersistentHandleError { + handle: String, + source: tss_esapi::Error, + }, /// Error creating handle from persistent TPM handle - #[error( - "Error creating handle from persistent TPM handle {handle}: {e}" - )] - TSSHandleFromPersistentHandleError { handle: String, e: tss_esapi::Error }, + #[error("Error creating handle from persistent TPM handle {handle}")] + TSSHandleFromPersistentHandleError { + handle: String, + source: tss_esapi::Error, + }, /// Error returned in case of error creating new Primary Key - #[error("Error creating primary key: {e}")] - TSSCreatePrimaryError { e: tss_esapi::Error }, + #[error("Error creating primary key")] + TSSCreatePrimaryError { source: tss_esapi::Error }, /// Error building PCR Selection list - #[error("Error building PCR Selection list: {e}")] - TSSPCRSelectionBuildError { e: tss_esapi::Error }, + #[error("Error building PCR Selection list")] + TSSPCRSelectionBuildError { source: tss_esapi::Error }, /// Error obtaining digest value from an authorization policy - #[error( - "Error obtaining digest value from an authorization policy: {e}" - )] - TSSDigestFromAuthPolicyError { e: tss_esapi::Error }, + #[error("Error obtaining digest value from an authorization policy")] + TSSDigestFromAuthPolicyError { source: tss_esapi::Error }, /// Error obtaining RSA public key from IDevID - #[error("Error obtaining RSA public key from IDevID: {e}")] - TSSPublicKeyFromIDevID { e: tss_esapi::Error }, + #[error("Error obtaining RSA public key from IDevID")] + TSSPublicKeyFromIDevID { source: tss_esapi::Error }, /// Error obtaining RSA public key from IAK - #[error("Error obtaining RSA public key from IAK: {e}")] - TSSPublicKeyFromIAK { e: tss_esapi::Error }, + #[error("Error obtaining RSA public key from IAK")] + TSSPublicKeyFromIAK { source: tss_esapi::Error }, /// Error building Object Attributes - #[error("Error building Object Attributes: {e}")] - TSSObjectAttributesBuildError { e: tss_esapi::Error }, + #[error("Error building Object Attributes: {source}")] + TSSObjectAttributesBuildError { source: tss_esapi::Error }, /// Error building public RSA parameters - #[error("Error building public RSA parameters: {e}")] - TSSPublicRSAParametersBuildError { e: tss_esapi::Error }, + #[error("Error building public RSA parameters")] + TSSPublicRSAParametersBuildError { source: tss_esapi::Error }, /// Error building public ECC parameters - #[error("Error building public ECC parameters: {e}")] - TSSPublicECCParametersBuildError { e: tss_esapi::Error }, + #[error("Error building public ECC parameters")] + TSSPublicECCParametersBuildError { source: tss_esapi::Error }, /// Error building IDevID key - #[error("Error building IDevID key: {e}")] - TSSIDevIDKeyBuildError { e: tss_esapi::Error }, + #[error("Error building IDevID key")] + TSSIDevIDKeyBuildError { source: tss_esapi::Error }, /// Error building IAK key - #[error("Error building IAK key: {e}")] - TSSIAKKeyBuildError { e: tss_esapi::Error }, + #[error("Error building IAK key")] + TSSIAKKeyBuildError { source: tss_esapi::Error }, /// Error obtaining ECC parameter from IDevID - #[error("Error obtaining ECC parameter from IDevID: {e}")] - TSSECCParameterFromIDevIDError { e: tss_esapi::Error }, + #[error("Error obtaining ECC parameter from IDevID")] + TSSECCParameterFromIDevIDError { source: tss_esapi::Error }, /// Error obtaining ECC parameter from IAK - #[error("Error obtaining ECC parameter from IAK: {e}")] - TSSECCParameterFromIAKError { e: tss_esapi::Error }, + #[error("Error obtaining ECC parameter from IAK")] + TSSECCParameterFromIAKError { source: tss_esapi::Error }, /// Error returned in case of failure reading EK public information - #[error("Error reading EK public info: {e}")] - TSSReadPublicError { e: tss_esapi::Error }, + #[error("Error reading EK public info")] + TSSReadPublicError { source: tss_esapi::Error }, /// Error returned in case of failure obtaining SymmetricDefinition from Cipher - #[error("Error converting Cipher to SymmetricDefinition: {e}")] - TSSSymmetricDefinitionFromCipher { e: tss_esapi::Error }, + #[error("Error converting Cipher to SymmetricDefinition")] + TSSSymmetricDefinitionFromCipher { source: tss_esapi::Error }, /// Error returned in case of failure starting authentication session - #[error("Error starting authentication session: {e}")] - TSSStartAuthenticationSessionError { e: tss_esapi::Error }, + #[error("Error starting authentication session")] + TSSStartAuthenticationSessionError { source: tss_esapi::Error }, /// Error setting authentication session attributes - #[error("Error setting authentication session attributes: {e}")] - TSSSessionSetAttributesError { e: tss_esapi::Error }, + #[error("Error setting authentication session attributes")] + TSSSessionSetAttributesError { source: tss_esapi::Error }, /// Error converting to TSS Digest from digest value - #[error("Error converting to TSS Digest from digest value: {e}")] - TSSDigestFromValue { e: tss_esapi::Error }, + #[error("Error converting to TSS Digest from digest value")] + TSSDigestFromValue { source: tss_esapi::Error }, /// Error creating a TCTI context - #[error("Error creating TCTI context: {error:?}")] - TSSTctiContextError { error: tss_esapi::Error }, + #[error("Error creating TCTI context")] + TSSTctiContextError { source: tss_esapi::Error }, /// Error marshalling TPMS_ATTEST structure - #[error("Error marshalling TPMS_ATTEST structure: {e:?}")] - TSSMarshallAttestError { e: tss_esapi::Error }, + #[error("Error marshalling TPMS_ATTEST structure")] + TSSMarshallAttestError { source: tss_esapi::Error }, /// Error marshalling TPMT_SIGNATURE structure - #[error("Error marshalling TPMT_SIGNATURE structure: {e:?}")] - TSSMarshallSignatureError { e: tss_esapi::Error }, + #[error("Error marshalling TPMT_SIGNATURE structure")] + TSSMarshallSignatureError { source: tss_esapi::Error }, /// Error getting PCR data - #[error("Error getting PCR data from TPM: {e:?}")] - TSSPCRListError { e: tss_esapi::Error }, + #[error("Error getting PCR data from TPM")] + TSSPCRListError { source: tss_esapi::Error }, /// Error generating quote - #[error("Error generating quote: {e:?}")] - TSSQuoteError { e: tss_esapi::Error }, + #[error("Error generating quote")] + TSSQuoteError { source: tss_esapi::Error }, /// Unexpected attested type in quote #[error("Unexpected attested type in quote: expected {expected:?} got {got:?}")] @@ -249,10 +249,10 @@ pub enum TpmError { EmptyAuthenticationSessionError, /// Error parsing number from string - #[error("Number parsing error from string {origin}: {e}")] + #[error("Number parsing error from string {origin}")] NumParse { origin: String, - e: std::num::ParseIntError, + source: std::num::ParseIntError, }, /// Error converting TSS_MAGIC number from MakeCredential keyblob header to u32 @@ -296,38 +296,38 @@ pub enum TpmError { InvalidKeyblobVersion { expected: u32, got: u32 }, /// Error parsing the value in TCTI env var - #[error("Error parsing TCTI configuration from env var 'TCTI' {path}: {error:?}")] + #[error("Error parsing TCTI configuration from env var 'TCTI' {path}")] TctiNameError { path: String, - error: tss_esapi::Error, + source: tss_esapi::Error, }, /// Error getting RSA public key from PKey - #[error("Error getting RSA public key from PKey: {e}")] - OpenSSLRSAFromPKey { e: openssl::error::ErrorStack }, + #[error("Error getting RSA public key from PKey")] + OpenSSLRSAFromPKey { source: openssl::error::ErrorStack }, /// Error PEM encoding public key - #[error("Error encoding public key in PEM format: {e}")] - OpenSSLPublicKeyToPEM { e: openssl::error::ErrorStack }, + #[error("Error encoding public key in PEM format")] + OpenSSLPublicKeyToPEM { source: openssl::error::ErrorStack }, /// Error creating Hasher - #[error("Error creating Hasher : {e}")] - OpenSSLHasherNew { e: openssl::error::ErrorStack }, + #[error("Error creating Hasher")] + OpenSSLHasherNew { source: openssl::error::ErrorStack }, /// Error updating Hasher - #[error("Error updating Hasher : {e}")] - OpenSSLHasherUpdate { e: openssl::error::ErrorStack }, + #[error("Error updating Hasher")] + OpenSSLHasherUpdate { source: openssl::error::ErrorStack }, /// Error finishing Hasher - #[error("Error finishing Hasher : {e}")] - OpenSSLHasherFinish { e: openssl::error::ErrorStack }, + #[error("Error finishing Hasher")] + OpenSSLHasherFinish { source: openssl::error::ErrorStack }, /// Number conversion error - #[error("Error converting number: {0}")] + #[error("Error converting number")] TryFromInt(#[from] std::num::TryFromIntError), /// Base64 decoding error - #[error("base64 decode error: {0}")] + #[error("base64 decode error")] Base64Decode(#[from] base64::DecodeError), /// Malformed PCR selection mask @@ -339,15 +339,18 @@ pub enum TpmError { NotImplemented(String), /// Read IO error - #[error("Error reading {what}: {e}")] - IoReadError { what: String, e: std::io::Error }, + #[error("Error reading {what}")] + IoReadError { + what: String, + source: std::io::Error, + }, /// Invalid request #[error("Invalid request: {0}")] InvalidRequest(String), /// Infallible error - #[error("Infallible: {0}")] + #[error("Infallible")] Infallible(#[from] std::convert::Infallible), /// Generic catch-all TPM device error @@ -359,7 +362,7 @@ pub enum TpmError { }, /// Generic catch-all Algorithm error - #[error("AlgorithmError: {0}")] + #[error("AlgorithmError")] AlgorithmError(#[from] AlgorithmError), /// Generic catch-all error @@ -457,17 +460,28 @@ impl Context { let tcti = TctiNameConf::from_str(&tcti_path).map_err(|error| { TpmError::TctiNameError { path: tcti_path.to_string(), - error, + source: error, } })?; Ok(Self { - inner: tss_esapi::Context::new(tcti) - .map_err(|error| TpmError::TSSTctiContextError { error })?, + inner: tss_esapi::Context::new(tcti).map_err(|error| { + TpmError::TSSTctiContextError { source: error } + })?, }) } /// Creates an EK, returns the key handle and public certificate /// in `EKResult`. + /// + /// # Arguments + /// + /// `alg`: The EK algorithm + /// `handle`: Optional; if provided, the EK in the provided handle is used instead of creating + /// a new EK. + /// + /// # Returns + /// + /// An `EKResult` structure if successful, a TPMError otherwise pub fn create_ek( &mut self, alg: EncryptionAlgorithm, @@ -482,27 +496,29 @@ impl Context { alg.into(), DefaultKey, ) - .map_err(|e| TpmError::TSSCreateEKError { e })? + .map_err(|source| TpmError::TSSCreateEKError { source })? } else { let handle = u32::from_str_radix(v.trim_start_matches("0x"), 16) - .map_err(|e| TpmError::NumParse { + .map_err(|source| TpmError::NumParse { origin: v.to_string(), - e, + source, })?; self.inner .tr_from_tpm_public(TpmHandle::Persistent( PersistentTpmHandle::new(handle).map_err( - |e| TpmError::TSSNewPersistentHandleError { - handle: v.to_string(), - e, + |source| { + TpmError::TSSNewPersistentHandleError { + handle: v.to_string(), + source, + } }, )?, )) - .map_err(|e| { + .map_err(|source| { TpmError::TSSHandleFromPersistentHandleError { handle: v.to_string(), - e, + source, } })? .into() @@ -510,7 +526,7 @@ impl Context { } None => { ek::create_ek_object(&mut self.inner, alg.into(), DefaultKey) - .map_err(|e| TpmError::TSSCreateEKError { e })? + .map_err(|source| TpmError::TSSCreateEKError { source })? } }; let cert = match ek::retrieve_ek_pubcert(&mut self.inner, alg.into()) @@ -524,7 +540,7 @@ impl Context { let (tpm_pub, _, _) = self .inner .read_public(key_handle) - .map_err(|e| TpmError::TSSReadPublicError { e })?; + .map_err(|source| TpmError::TSSReadPublicError { source })?; Ok(EKResult { key_handle, ek_cert: cert, @@ -532,7 +548,15 @@ impl Context { }) } - /// Creates an AK. + /// Creates an AK + /// + /// # Arguments + /// + /// * `handle`: The associated EK handle + /// * `hash_alg`: The digest algorithm used for signing with the created AK + /// * `sign_alg`: The created AK signing algorithm + /// + /// Returns an `AKResult` structure if successful and a `TPMError` otherwise pub fn create_ak( &mut self, handle: KeyHandle, @@ -547,7 +571,7 @@ impl Context { None, DefaultKey, ) - .map_err(|e| TpmError::TSSCreateAKError { e })?; + .map_err(|source| TpmError::TSSCreateAKError { source })?; Ok(AKResult { public: ak.out_public, private: ak.out_private, @@ -555,6 +579,15 @@ impl Context { } /// Loads an existing AK associated with `handle` and `ak`. + /// + /// # Arguments + /// + /// `handle`: The associated EK handle + /// `ak`: The `AKResult` structure containing the private and public keys to load + /// + /// # Return + /// + /// The loaded AK KeyHandle if successful, a TPMError otherwise pub fn load_ak( &mut self, handle: KeyHandle, @@ -567,7 +600,7 @@ impl Context { ak.private.clone(), ak.public.clone(), ) - .map_err(|e| TpmError::TSSLoadAKError { e })?; + .map_err(|source| TpmError::TSSLoadAKError { source })?; Ok(ak_handle) } @@ -596,7 +629,9 @@ impl Context { ], ) .build() - .map_err(|e| TpmError::TSSPCRSelectionBuildError { e })?; + .map_err(|source| TpmError::TSSPCRSelectionBuildError { + source, + })?; let primary_key = self .inner @@ -610,7 +645,7 @@ impl Context { Some(pcr_selection_list), ) }) - .map_err(|e| TpmError::TSSCreatePrimaryError { e })?; + .map_err(|source| TpmError::TSSCreatePrimaryError { source })?; Ok(IDevIDResult { public: primary_key.out_public, @@ -637,9 +672,9 @@ impl Context { // restricted=0 for DevIDs .with_restricted(false); - let obj_attrs = obj_attrs_builder - .build() - .map_err(|e| TpmError::TSSObjectAttributesBuildError { e })?; + let obj_attrs = obj_attrs_builder.build().map_err(|source| { + TpmError::TSSObjectAttributesBuildError { source } + })?; let (auth_policy, key_bits, curve_id) = match name_alg { HashingAlgorithm::Sha256 => ( @@ -675,7 +710,9 @@ impl Context { .with_name_hashing_algorithm(name_alg) .with_object_attributes(obj_attrs) .with_auth_policy(Digest::try_from(auth_policy).map_err( - |e| TpmError::TSSDigestFromAuthPolicyError { e }, + |source| TpmError::TSSDigestFromAuthPolicyError { + source, + }, )?) .with_rsa_parameters( PublicRsaParametersBuilder::new() @@ -687,13 +724,15 @@ impl Context { .with_is_decryption_key(obj_attrs.decrypt()) .with_restricted(obj_attrs.restricted()) .build() - .map_err(|e| { - TpmError::TSSPublicRSAParametersBuildError { e } + .map_err(|source| { + TpmError::TSSPublicRSAParametersBuildError { + source, + } })?, ) .with_rsa_unique_identifier( PublicKeyRsa::try_from(&UNIQUE_IDEVID[0..6]).map_err( - |e| TpmError::TSSPublicKeyFromIDevID { e }, + |source| TpmError::TSSPublicKeyFromIDevID { source }, )?, ), AsymmetricAlgorithm::Ecc => PublicBuilder::new() @@ -701,7 +740,9 @@ impl Context { .with_name_hashing_algorithm(name_alg) .with_object_attributes(obj_attrs) .with_auth_policy(Digest::try_from(auth_policy).map_err( - |e| TpmError::TSSDigestFromAuthPolicyError { e }, + |source| TpmError::TSSDigestFromAuthPolicyError { + source, + }, )?) .with_ecc_parameters( PublicEccParametersBuilder::new() @@ -717,16 +758,22 @@ impl Context { .with_is_decryption_key(obj_attrs.decrypt()) .with_restricted(obj_attrs.restricted()) .build() - .map_err(|e| { - TpmError::TSSPublicECCParametersBuildError { e } + .map_err(|source| { + TpmError::TSSPublicECCParametersBuildError { + source, + } })?, ) .with_ecc_unique_identifier(EccPoint::new( EccParameter::try_from(&UNIQUE_IDEVID[0..6]).map_err( - |e| TpmError::TSSECCParameterFromIDevIDError { e }, + |source| TpmError::TSSECCParameterFromIDevIDError { + source, + }, )?, EccParameter::try_from(&UNIQUE_IDEVID[0..6]).map_err( - |e| TpmError::TSSECCParameterFromIDevIDError { e }, + |source| TpmError::TSSECCParameterFromIDevIDError { + source, + }, )?, )), // Defaulting to RSA on null @@ -738,8 +785,8 @@ impl Context { Digest::try_from( IDEVID_AUTH_POLICY_SHA256[0..32].to_vec(), ) - .map_err(|e| { - TpmError::TSSDigestFromAuthPolicyError { e } + .map_err(|source| { + TpmError::TSSDigestFromAuthPolicyError { source } })?, ) .with_rsa_parameters( @@ -752,21 +799,23 @@ impl Context { .with_is_decryption_key(obj_attrs.decrypt()) .with_restricted(obj_attrs.decrypt()) .build() - .map_err(|e| { - TpmError::TSSPublicRSAParametersBuildError { e } + .map_err(|source| { + TpmError::TSSPublicRSAParametersBuildError { + source, + } })?, ) .with_rsa_unique_identifier( PublicKeyRsa::try_from(&UNIQUE_IDEVID[0..6]).map_err( - |e| TpmError::TSSPublicKeyFromIDevID { e }, + |source| TpmError::TSSPublicKeyFromIDevID { source }, )?, ), }; Ok(IDevIDPublic { - public: key_builder - .build() - .map_err(|e| TpmError::TSSIDevIDKeyBuildError { e })?, + public: key_builder.build().map_err(|source| { + TpmError::TSSIDevIDKeyBuildError { source } + })?, }) } @@ -795,7 +844,9 @@ impl Context { ], ) .build() - .map_err(|e| TpmError::TSSPCRSelectionBuildError { e })?; + .map_err(|source| TpmError::TSSPCRSelectionBuildError { + source, + })?; let primary_key = self .inner @@ -809,7 +860,7 @@ impl Context { Some(pcr_selection_list), ) }) - .map_err(|e| TpmError::TSSCreatePrimaryError { e })?; + .map_err(|source| TpmError::TSSCreatePrimaryError { source })?; Ok(IAKResult { public: primary_key.out_public, @@ -836,9 +887,9 @@ impl Context { // restricted=1 for AKs .with_restricted(true); - let obj_attrs = obj_attrs_builder - .build() - .map_err(|e| TpmError::TSSObjectAttributesBuildError { e })?; + let obj_attrs = obj_attrs_builder.build().map_err(|source| { + TpmError::TSSObjectAttributesBuildError { source } + })?; let (auth_policy, key_bits, curve_id) = match name_alg { HashingAlgorithm::Sha256 => ( @@ -874,7 +925,9 @@ impl Context { .with_name_hashing_algorithm(name_alg) .with_object_attributes(obj_attrs) .with_auth_policy(Digest::try_from(auth_policy).map_err( - |e| TpmError::TSSDigestFromAuthPolicyError { e }, + |source| TpmError::TSSDigestFromAuthPolicyError { + source, + }, )?) .with_rsa_parameters( PublicRsaParametersBuilder::new() @@ -888,20 +941,25 @@ impl Context { .with_is_decryption_key(obj_attrs.decrypt()) .with_restricted(obj_attrs.restricted()) .build() - .map_err(|e| { - TpmError::TSSPublicRSAParametersBuildError { e } + .map_err(|source| { + TpmError::TSSPublicRSAParametersBuildError { + source, + } })?, ) .with_rsa_unique_identifier( - PublicKeyRsa::try_from(&UNIQUE_IAK[0..3]) - .map_err(|e| TpmError::TSSPublicKeyFromIAK { e })?, + PublicKeyRsa::try_from(&UNIQUE_IAK[0..3]).map_err( + |source| TpmError::TSSPublicKeyFromIAK { source }, + )?, ), AsymmetricAlgorithm::Ecc => PublicBuilder::new() .with_public_algorithm(PublicAlgorithm::Ecc) .with_name_hashing_algorithm(name_alg) .with_object_attributes(obj_attrs) .with_auth_policy(Digest::try_from(auth_policy).map_err( - |e| TpmError::TSSDigestFromAuthPolicyError { e }, + |source| TpmError::TSSDigestFromAuthPolicyError { + source, + }, )?) .with_ecc_parameters( PublicEccParametersBuilder::new() @@ -917,16 +975,22 @@ impl Context { .with_is_decryption_key(obj_attrs.decrypt()) .with_restricted(obj_attrs.restricted()) .build() - .map_err(|e| { - TpmError::TSSPublicECCParametersBuildError { e } + .map_err(|source| { + TpmError::TSSPublicECCParametersBuildError { + source, + } })?, ) .with_ecc_unique_identifier(EccPoint::new( EccParameter::try_from(&UNIQUE_IAK[0..3]).map_err( - |e| TpmError::TSSECCParameterFromIAKError { e }, + |source| TpmError::TSSECCParameterFromIAKError { + source, + }, )?, EccParameter::try_from(&UNIQUE_IAK[0..3]).map_err( - |e| TpmError::TSSECCParameterFromIAKError { e }, + |source| TpmError::TSSECCParameterFromIAKError { + source, + }, )?, )), AsymmetricAlgorithm::Null => PublicBuilder::new() @@ -935,8 +999,8 @@ impl Context { .with_object_attributes(obj_attrs) .with_auth_policy( Digest::try_from(IAK_AUTH_POLICY_SHA256[0..32].to_vec()) - .map_err(|e| { - TpmError::TSSDigestFromAuthPolicyError { e } + .map_err(|source| { + TpmError::TSSDigestFromAuthPolicyError { source } })?, ) .with_rsa_parameters( @@ -949,20 +1013,23 @@ impl Context { .with_is_decryption_key(obj_attrs.decrypt()) .with_restricted(obj_attrs.decrypt()) .build() - .map_err(|e| { - TpmError::TSSPublicRSAParametersBuildError { e } + .map_err(|source| { + TpmError::TSSPublicRSAParametersBuildError { + source, + } })?, ) .with_rsa_unique_identifier( - PublicKeyRsa::try_from(&UNIQUE_IAK[0..3]) - .map_err(|e| TpmError::TSSPublicKeyFromIAK { e })?, + PublicKeyRsa::try_from(&UNIQUE_IAK[0..3]).map_err( + |source| TpmError::TSSPublicKeyFromIAK { source }, + )?, ), }; Ok(IAKPublic { public: key_builder .build() - .map_err(|e| TpmError::TSSIAKKeyBuildError { e })?, + .map_err(|source| TpmError::TSSIAKKeyBuildError { source })?, }) } @@ -978,13 +1045,13 @@ impl Context { None, None, ses_type, - Cipher::aes_128_cfb().try_into().map_err(|e| { - TpmError::TSSSymmetricDefinitionFromCipher { e } + Cipher::aes_128_cfb().try_into().map_err(|source| { + TpmError::TSSSymmetricDefinitionFromCipher { source } })?, HashingAlgorithm::Sha256, ) - .map_err(|e| TpmError::TSSStartAuthenticationSessionError { - e, + .map_err(|source| { + TpmError::TSSStartAuthenticationSessionError { source } })? else { return Err(TpmError::EmptyAuthenticationSessionError); @@ -997,7 +1064,9 @@ impl Context { self.inner .tr_sess_set_attributes(session, ses_attrs, ses_attrs_mask) - .map_err(|e| TpmError::TSSSessionSetAttributesError { e })?; + .map_err(|source| TpmError::TSSSessionSetAttributesError { + source, + })?; Ok(session) } @@ -1266,9 +1335,9 @@ fn pubkey_to_tpm_digest( let keybytes = match pubkey.id() { Id::RSA => pubkey .rsa() - .map_err(|e| TpmError::OpenSSLRSAFromPKey { e })? + .map_err(|source| TpmError::OpenSSLRSAFromPKey { source })? .public_key_to_pem() - .map_err(|e| TpmError::OpenSSLPublicKeyToPEM { e })?, + .map_err(|source| TpmError::OpenSSLPublicKeyToPEM { source })?, other_id => { return Err(TpmError::NotImplemented(format!( "Converting to digest value for key type {other_id:?}" @@ -1277,18 +1346,19 @@ fn pubkey_to_tpm_digest( }; let hashing_algo = HashingAlgorithm::from(hash_algo); - let mut hasher = Hasher::new(hash_alg_to_message_digest(hashing_algo)?) - .map_err(|e| TpmError::OpenSSLHasherNew { e })?; + let mut hasher = + Hasher::new(hash_alg_to_message_digest(hashing_algo)?) + .map_err(|source| TpmError::OpenSSLHasherNew { source })?; hasher .update(&keybytes) - .map_err(|e| TpmError::OpenSSLHasherUpdate { e })?; + .map_err(|source| TpmError::OpenSSLHasherUpdate { source })?; let hashvec = hasher .finish() - .map_err(|e| TpmError::OpenSSLHasherFinish { e })?; + .map_err(|source| TpmError::OpenSSLHasherFinish { source })?; keydigest.set( hashing_algo, Digest::try_from(hashvec.as_ref()) - .map_err(|e| TpmError::TSSDigestFromValue { e })?, + .map_err(|source| TpmError::TSSDigestFromValue { source })?, ); Ok(keydigest) @@ -1369,10 +1439,10 @@ fn encode_quote_string( // dictated by tpm2_tools. let att_vec = att .marshall() - .map_err(|e| TpmError::TSSMarshallAttestError { e })?; + .map_err(|source| TpmError::TSSMarshallAttestError { source })?; let sig_vec = sig .marshall() - .map_err(|e| TpmError::TSSMarshallSignatureError { e })?; + .map_err(|source| TpmError::TSSMarshallSignatureError { source })?; let pcr_vec = pcrdata_to_vec(pcrs_read, pcr_data); // base64 encoding @@ -1411,7 +1481,7 @@ fn make_pcr_blob( ) -> Result<(PcrSelectionList, PcrData)> { let pcr_data = context .execute_without_session(|ctx| read_all(ctx, pcrlist.clone())) - .map_err(|e| TpmError::TSSPCRListError { e })?; + .map_err(|source| TpmError::TSSPCRListError { source })?; Ok((pcrlist, pcr_data)) } @@ -1451,18 +1521,18 @@ fn check_if_pcr_data_and_attestation_match( let attested_pcr = quote_info.pcr_digest().value(); let mut hasher = Hasher::new(hash_alg_to_message_digest(hash_algo)?) - .map_err(|e| TpmError::OpenSSLHasherNew { e })?; + .map_err(|source| TpmError::OpenSSLHasherNew { source })?; for tpml_digest in pcr_data { for i in 0..tpml_digest.count { let pcr = tpml_digest.digests[i as usize]; hasher .update(&pcr.buffer[..pcr.size as usize]) - .map_err(|e| TpmError::OpenSSLHasherUpdate { e })?; + .map_err(|source| TpmError::OpenSSLHasherUpdate { source })?; } } let pcr_digest = hasher .finish() - .map_err(|e| TpmError::OpenSSLHasherFinish { e })?; + .map_err(|source| TpmError::OpenSSLHasherFinish { source })?; log::trace!( "Attested to PCR digest: {:?}, read PCR digest: {:?}", @@ -1499,7 +1569,7 @@ fn perform_quote_and_pcr_read( // create quote let (attestation, sig) = context .quote(ak_handle, nonce.clone(), sign_scheme, pcrs_read.clone()) - .map_err(|e| TpmError::TSSQuoteError { e })?; + .map_err(|source| TpmError::TSSQuoteError { source })?; // Check whether the attestation and pcr_data match if check_if_pcr_data_and_attestation_match( @@ -1613,10 +1683,10 @@ pub mod testing { let mut reader = std::io::Cursor::new(pcrsel_vec); let mut count_vec = [0u8; 4]; - reader.read_exact(&mut count_vec).map_err(|e| { + reader.read_exact(&mut count_vec).map_err(|source| { TpmError::IoReadError { what: "PCR selection count from slice".into(), - e, + source, } })?; let count = u32::from_le_bytes(count_vec); @@ -1626,29 +1696,29 @@ pub mod testing { for selection in &mut pcr_selections { let mut hash_vec = [0u8; 2]; - reader.read_exact(&mut hash_vec).map_err(|e| { + reader.read_exact(&mut hash_vec).map_err(|source| { TpmError::IoReadError { what: "PCR selection hash from slice".into(), - e, + source, } })?; selection.hash = u16::from_le_bytes(hash_vec); let mut size_vec = [0u8; 1]; - reader.read_exact(&mut size_vec).map_err(|e| { + reader.read_exact(&mut size_vec).map_err(|source| { TpmError::IoReadError { what: "PCR selection size from slice".into(), - e, + source, } })?; selection.sizeofSelect = u8::from_le_bytes(size_vec); - reader.read_exact(&mut selection.pcrSelect).map_err(|e| { - TpmError::IoReadError { + reader.read_exact(&mut selection.pcrSelect).map_err( + |source| TpmError::IoReadError { what: "PCR selection from slice".into(), - e, - } - })?; + source, + }, + )?; } Ok(TPML_PCR_SELECTION { @@ -1671,10 +1741,10 @@ pub mod testing { let mut reader = std::io::Cursor::new(digest_vec); let mut count_vec = [0u8; 4]; - reader.read_exact(&mut count_vec).map_err(|e| { + reader.read_exact(&mut count_vec).map_err(|source| { TpmError::IoReadError { what: "Digest count from slice".into(), - e, + source, } })?; let count = u32::from_le_bytes(count_vec); @@ -1683,17 +1753,17 @@ pub mod testing { for digest in &mut digests { let mut size_vec = [0u8; 2]; - reader.read_exact(&mut size_vec).map_err(|e| { + reader.read_exact(&mut size_vec).map_err(|source| { TpmError::IoReadError { what: "Digest size from slice".into(), - e, + source, } })?; digest.size = u16::from_le_bytes(size_vec); - reader.read_exact(&mut digest.buffer).map_err(|e| { + reader.read_exact(&mut digest.buffer).map_err(|source| { TpmError::IoReadError { what: "Digest from slice".into(), - e, + source, } })?; } @@ -1704,10 +1774,10 @@ pub mod testing { fn vec_to_pcrdata(val: &[u8]) -> Result<(PcrSelectionList, PcrData)> { let mut reader = std::io::Cursor::new(val); let mut pcrsel_vec = [0u8; TPML_PCR_SELECTION_SIZE]; - reader.read_exact(&mut pcrsel_vec).map_err(|e| { + reader.read_exact(&mut pcrsel_vec).map_err(|source| { TpmError::IoReadError { what: "PCR selection size from slice".into(), - e, + source, } })?; @@ -1715,10 +1785,10 @@ pub mod testing { let pcrlist: PcrSelectionList = pcrsel.try_into()?; let mut count_vec = [0u8; 4]; - reader.read_exact(&mut count_vec).map_err(|e| { + reader.read_exact(&mut count_vec).map_err(|source| { TpmError::IoReadError { what: "PCR selection count from slice".into(), - e, + source, } })?; let count = u32::from_le_bytes(count_vec); @@ -1731,10 +1801,10 @@ pub mod testing { } let mut digest_vec = [0u8; TPML_DIGEST_SIZE]; - reader.read_exact(&mut digest_vec).map_err(|e| { + reader.read_exact(&mut digest_vec).map_err(|source| { TpmError::IoReadError { what: "Digest from slice".into(), - e, + source, } })?; let digest = deserialize_digest(&digest_vec)?; @@ -1784,11 +1854,11 @@ pub mod testing { Ok((att.try_into()?, sig, pcrsel, pcrdata)) } - // This performs the same checks as in tpm2_checkquote, namely: - // signature, nonce, and PCR digests from the quote. - // - // Reference: - // https://github.com/tpm2-software/tpm2-tools/blob/master/tools/tpm2_checkquote.c + /// This performs the same checks as in tpm2_checkquote, namely: + /// signature, nonce, and PCR digests from the quote. + /// + /// Reference: + /// https://github.com/tpm2-software/tpm2-tools/blob/master/tools/tpm2_checkquote.c pub fn check_quote( context: &mut tss_esapi::Context, ak_handle: KeyHandle, @@ -1801,13 +1871,13 @@ pub mod testing { // bother unmarshalling the AK to OpenSSL PKey, but just use // Esys_VerifySignature with loaded AK let mut hasher = Hasher::new(MessageDigest::sha256()) - .map_err(|e| TpmError::OpenSSLHasherNew { e })?; + .map_err(|source| TpmError::OpenSSLHasherNew { source })?; hasher .update(att.value()) - .map_err(|e| TpmError::OpenSSLHasherUpdate { e })?; + .map_err(|source| TpmError::OpenSSLHasherUpdate { source })?; let digest = hasher .finish() - .map_err(|e| TpmError::OpenSSLHasherFinish { e })?; + .map_err(|source| TpmError::OpenSSLHasherFinish { source })?; let digest: Digest = digest.as_ref().try_into().unwrap(); //#[allow_ci] match context.verify_signature(ak_handle, digest, sig) { Ok(ticket) if ticket.tag() == StructureTag::Verified => {} @@ -1829,19 +1899,19 @@ pub mod testing { .pcr_bank(HashingAlgorithm::Sha256) .ok_or_else(|| TpmError::Other("no SHA256 bank".to_string()))?; let mut hasher = Hasher::new(MessageDigest::sha256()) - .map_err(|e| TpmError::OpenSSLHasherNew { e })?; + .map_err(|source| TpmError::OpenSSLHasherNew { source })?; for &sel in pcrsel.get_selections() { for i in &sel.selected() { if let Some(digest) = pcrbank.get_digest(*i) { - hasher - .update(digest.value()) - .map_err(|e| TpmError::OpenSSLHasherUpdate { e })?; + hasher.update(digest.value()).map_err(|source| { + TpmError::OpenSSLHasherUpdate { source } + })?; } } } let digest = hasher .finish() - .map_err(|e| TpmError::OpenSSLHasherFinish { e })?; + .map_err(|source| TpmError::OpenSSLHasherFinish { source })?; let quote_info = match attestation.attested() { AttestInfo::Quote { info } => info, _ => { diff --git a/keylime/test-data/test-rsa.pem b/keylime/test-data/test-rsa.pem new file mode 100644 index 00000000..3088db14 --- /dev/null +++ b/keylime/test-data/test-rsa.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEA7cexb6YAL3BeOvlMFaBEwPiplq8z7ac2ovAU5heKDjl9xlzL +ab7YwxQmFb5m1FnkDfC6jASqCdDvZswfNhbV2QIIEjKCMvBopnKtE6kjQlhUbOtR +6CpZHiZOYHJoBtQ4jM0OgqfgO9Wiq4IXmsXuLeOCpD+mJXRelAFnbs1C96CUO0LE +IyFgOYtH2bvmck6iJqPTczdtCqjAmsLbwl48FJgOKlTxoJ3uktBoIAp03XeywG9e +f1z3sZrvjljB/KXH86eME5amq/3HyaSp2+Wmnpjs8MCIRDH6eIE318lSjD/t6gqe ++ysG1MlwyDFwVrLIoPOc+2hsFVJgNNK0M+URHwIDAQABAoIBAQCspZ8XAwAFceBp +j5OH7Eufla2FRIc+2neYTRvPiW3rMCE7wyrLCBBZbKrOhOYi73XgDVdVzRktcXAy +Qqmy21fAbnIvzE6u79H8cS1sJhX82SfLwf1BxmXYt1WXP9p6guLgkQ8lHQF6UH8B +ar762RY8aYH1AmX/sgPuESrpz838/0v4Tq52yJeSiGOS3MG6iRWQ6/K0CHT3olbH +RDN/48YrCSRP3RkAXoGcbev59+qOFJazC2av+qQM+59FwM2SrzrKa7RJLSQ1+wEz +gZEmmgJu6tOVUnAd7mg5VS+N7cXu6+Xl1jCg1N7XaQWh3nRCI1bysJ9UJQQZHl0B +gnIDXAU5AoGBAPex+wA1oaf4y22PIrVoLhD1HksdIxCOhmMYkojaIo3qZCVi8bS8 +JnUiHhvo25xYuUtB2Qw0VstzBaq6RDUnVgO3yVBYABc9MSmO+up/2ljNb9S5uWrm ++FzqjCRIWwABTWVyI7zvgVKSuSbvVLAQkgwAEBibp26V5E5SmuB4uKcNAoGBAPXA +m8n9Ajo+2Fzage0JZuW4uELbCqUwCkAlwJoSP42y0SIpB5fITkwRIwOat1MEhQ0I +yh68ZxGvqfE2fO78ZbE2bSlKkkadrZjPFAG2svd57MdHnR5c4iCm6ui7D0snCgcg +pUt2qlXYesABWb7o3tKOezDlbjFtv9JDJPOmnI3bAoGBAJw/RINsUW5BDiotWYqv +jieaSCK/3YerMHDAZmc3mwaErem7kZcd/PB0tiOK70Wf3jrv7be6KGosQ43f8/jH +uIWd4Lry2BPQwPtjOzrDrfvIk9vP0Hvz+QW72u1kSyskpyrwJkUfnCd3cJ5z6Ksr +uMUjIQQ05BhpK1yQ1Sv2WxzdAoGBAKkLwd5SzOp1+mz83azI7+ALjaxnck4o2pQ/ +o9oXvWHiZFuEL7Xn0nweuaAsF/jiPge2SRqVbKzM0jCb05qtQeKB1ts1caNjqVtY +7qEzJK55TzfRejG9oMrnJuXKbv26L/qxKSLc0NTWYbGb/DkHhOb/nZwH5iHYJcAj +8dIshLpLAoGBAK9RBWaP3KdDheUDOVpSSnOjVOtzq0T2qHPLklpdZzz2BHGKTNly +qek7gOTiy/DvON0F/gFhVi0+7RnCG+CnMXe2Ac/vn3QqZLAL5pSc+AUhmfE72hn2 +HNz0GFbYmFqr8QpzXG7b4aCF/yxcPrHGN3LdBKZvPPv7OmcCPGVEOKVk +-----END RSA PRIVATE KEY----- diff --git a/keylime-agent/test-data/test-rsa.sig b/keylime/test-data/test-rsa.sig similarity index 100% rename from keylime-agent/test-data/test-rsa.sig rename to keylime/test-data/test-rsa.sig diff --git a/tests/run.sh b/tests/run.sh index c08398b9..b120313f 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -47,4 +47,5 @@ cargo tarpaulin --verbose \ --ignore-panics --ignore-tests \ --out Html --out Json \ --all-features \ - --engine llvm + --engine llvm \ + -- --test-threads=1