diff --git a/Cargo.lock b/Cargo.lock index 293e56c81..96ff5e382 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -454,6 +454,25 @@ dependencies = [ "cache-padded", ] +[[package]] +name = "config" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f1667b8320afa80d69d8bbe40830df2c8a06003d86f73d8e003b2c48df416d" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini 0.18.0", + "serde", + "serde_json", + "toml 0.5.9", + "yaml-rust", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -549,9 +568,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ "block-buffer", "crypto-common", @@ -566,6 +585,12 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -818,6 +843,12 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "h2" version = "0.3.13" @@ -852,6 +883,15 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", +] + [[package]] name = "heck" version = "0.4.0" @@ -1046,6 +1086,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "keylime_agent" version = "0.1.0" @@ -1056,7 +1107,9 @@ dependencies = [ "cfg-if", "clap", "compress-tools", + "config", "futures", + "glob", "hex", "libc", "log", @@ -1065,7 +1118,7 @@ dependencies = [ "picky-asn1-x509", "pretty_env_logger", "reqwest", - "rust-ini", + "rust-ini 0.17.0", "serde", "serde_derive", "serde_json", @@ -1073,6 +1126,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", + "toml 0.5.9", "tss-esapi", "uuid", "wiremock", @@ -1097,6 +1151,12 @@ version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "local-channel" version = "0.1.3" @@ -1165,7 +1225,7 @@ checksum = "73b122901b3a675fac8cecf68dcb2f0d3036193bc861d1ac0e1c337f7d5254c2" dependencies = [ "error-chain", "pkg-config", - "toml", + "toml 0.2.1", ] [[package]] @@ -1174,6 +1234,12 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.5.3" @@ -1213,6 +1279,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -1318,10 +1394,20 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485" dependencies = [ - "dlv-list", + "dlv-list 0.2.3", "hashbrown 0.9.1", ] +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list 0.3.0", + "hashbrown 0.12.3", +] + [[package]] name = "os_str_bytes" version = "6.1.0" @@ -1363,6 +1449,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1371,13 +1463,48 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pest" -version = "2.1.3" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048" dependencies = [ + "thiserror", "ucd-trie", ] +[[package]] +name = "pest_derive" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502b62a6d0245378b04ffe0a7fb4f4419a4815fce813bd8a0ec89a56e07d67b1" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451e629bf49b750254da26132f1a5a9d11fd8a95a3df51d15c4abd1ba154cb6c" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcec162c71c45e269dfc3fc2916eaeb97feab22993a21bcce4721d08cd7801a6" +dependencies = [ + "once_cell", + "pest", + "sha1", +] + [[package]] name = "picky-asn1" version = "0.3.3" @@ -1665,6 +1792,17 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64", + "bitflags", + "serde", +] + [[package]] name = "rust-ini" version = "0.17.0" @@ -1672,7 +1810,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63471c4aa97a1cf8332a5f97709a79a4234698de6a1f5087faf66f2dae810e22" dependencies = [ "cfg-if", - "ordered-multimap", + "ordered-multimap 0.3.1", +] + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap 0.4.3", ] [[package]] @@ -1836,6 +1984,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -2069,6 +2228,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + [[package]] name = "tower-service" version = "0.3.1" @@ -2421,6 +2589,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zeroize" version = "1.5.5" diff --git a/Cargo.toml b/Cargo.toml index 705e6a196..ddcb2a29f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,9 @@ base64 = "0.13" cfg-if = "1" clap = { version = "~3.1.18", features = ["derive"] } compress-tools = "0.12" +config = { version = "0.13", features = ["toml"] } futures = "0.3.6" +glob = "0.3" hex = "0.4" libc = "0.2.43" log = "0.4" @@ -39,6 +41,7 @@ serde_json = { version = "1.0", features = ["raw_value"] } static_assertions = "1" tempfile = "3.0.4" tokio = {version = "1.13.1", features = ["full"]} +toml = "0.5" tss-esapi = "7.1.0" thiserror = "1.0" uuid = {version = "0.8", features = ["v4"]} diff --git a/GNUmakefile b/GNUmakefile index d5b5d7ba7..5f827229f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -27,7 +27,9 @@ $(programs): .PHONY: install install: all - cp ${CONFFILE} /etc/${CONFFILE} + mkdir -p /etc/keylime/ + mkdir -p /etc/keylime/agent.conf.d + cp ${CONFFILE} /etc/keylime/agent.conf for f in $(programs); do \ install -D -t ${DESTDIR}/usr/bin "$$f"; \ done diff --git a/keylime-agent.conf b/keylime-agent.conf index 1ebf4cc01..39eec3052 100644 --- a/keylime-agent.conf +++ b/keylime-agent.conf @@ -1,78 +1,104 @@ #============================================================================= -[general] +[agent] #============================================================================= -# Revocation IP & Port used by either the cloud_agent or keylime_ca to receive -# revocation events from the verifier. -receive_revocation_ip = 127.0.0.1 -receive_revocation_port = 8992 +# The configuration file version +version = "2.0" - -#============================================================================= -[cloud_agent] -#============================================================================= +# The agent's UUID. +# Set to "openstack", it will try to get the UUID from the metadata service. +# If you set this to "generate", Keylime will create a random UUID. +# If you set this to "hash_ek", Keylime will set the UUID to the result +# of 'SHA256(public EK in PEM format)'. +# If you set this to "dmidecode", Keylime will use the UUID from +# 'dmidecode -s system-uuid'. +# If you set this to "hostname", Keylime will use the full qualified domain +# name of current host as the agent id. +uuid = "d432fbb3-d2f1-4a97-9ef7-75bd81c00000" # The binding address and port for the agent server -cloudagent_ip = 127.0.0.1 -cloudagent_port = 9002 +ip = "127.0.0.1" +port = 9002 # Address and port where the verifier and tenant can connect to reach the agent. # These keys are optional. -agent_contact_ip = 127.0.0.1 -agent_contact_port = 9002 +contact_ip = "127.0.0.1" +contact_port = 9002 # The address and port of registrar server which agent communicate with -registrar_ip = 127.0.0.1 +registrar_ip = "127.0.0.1" registrar_port = 8890 +# Enable mTLS communication between agent, verifier and tenant. +# Details on why setting it to "false" is generally considered insecure can be found +# on https://github.com/keylime/keylime/security/advisories/GHSA-2m39-75g9-ff5r +enable_agent_mtls = true + # The keylime working directory. Can be overriden by setting the KEYLIME_DIR # environment variable. The default value is /var/lib/keylime -# keylime_dir = /var/lib/keylime +keylime_dir = "/var/lib/keylime" + +# The name of the file containing the Keylime agent TLS server private key. +# This private key is used to serve the Keylime agent REST API +# A new private key is generated in case it is not found. +# If set as "default", the "server-private.pem" value is used. +server_key = "default" + +# Set the password used to encrypt the private key file. +# This password will also be used to protect the generated private key used for +# mTLS authentication +# If left empty, the private key will not be encrypted. +server_key_password = "" + +# The name of the file containing the X509 certificate used as the Keylime agent +# server TLS certificate. +# This certificate must be self signed. +# If set as "default", the "server-cert.crt" value is used +server_cert = "default" # The CA that signs the client certificates of the tenant and verifier. -# If set to default it tries to use $keylime_dir/cv_ca/cacert.crt -keylime_ca = default +# If set as "default" the "cv_ca/cacert.crt" value is used +trusted_client_ca = "default" # The name that should be used for the encryption key, placed in the # $keylime_dir/secure/ directory. -enc_keyname = derived_tci_key +enc_keyname = "derived_tci_key" # The name that should be used for the optional decrypted payload, placed in # the $keylime_dir/secure directory. -dec_payload_file = decrypted_payload +dec_payload_file = "decrypted_payload" # The size of the memory-backed tmpfs partition where Keylime stores crypto keys. # Use syntax that the 'mount' command would accept as a size parameter for tmpfs. # The default below sets it to 1 megabyte. -secure_size = 1m +secure_size = "1m" # Whether to allow the cloud_agent to automatically extract a zip file in # the delivered payload after it has been decrypted, or not. Defaults to "true". # After decryption, the archive will be unzipped to a directory in $keylime_dir/secure. # Note: the limits on the size of the tmpfs partition set above with the 'secure_size' # option will affect this. -extract_payload_zip = True - -# The agent's UUID. -# Set to "openstack", it will try to get the UUID from the metadata service. -# If you set this to "generate", Keylime will create a random UUID. -# If you set this to "hash_ek", Keylime will set the UUID to the result -# of 'SHA256(public EK in PEM format)'. -# If you set this to "dmidecode", Keylime will use the UUID from -# 'dmidecode -s system-uuid'. -# If you set this to "hostname", Keylime will use the full qualified domain -# name of current host as the agent id. -agent_uuid = d432fbb3-d2f1-4a97-9ef7-75bd81c00000 +extract_payload_zip = true # Whether to listen for revocation notifications from the verifier or not. -listen_notifications = True +enable_revocation_notifications = true + +# The path to the directory containing the pre-installed revocation action +# scripts. Ideally should point to an fixed/immutable location subject to +# attestation. The default is /usr/libexec/keylime. +revocation_actions_dir = "/usr/libexec/keylime" + +# Revocation IP & Port used by either the cloud_agent to receive revocation +# notifications from the verifier. +revocation_notification_ip = "127.0.0.1" +revocation_notification_port = 8992 # The path to the certificate to verify revocation messages received from the # verifier. The path is relative to $keylime_dir unless an absolute path is # provided (i.e. starts with '/'). # If set to "default", Keylime will use the file RevocationNotifier-cert.crt # from the unzipped contents provided by the tenant. -revocation_cert = default +revocation_cert = "default" # A comma-separated list of executables to run upon receiving a revocation # message. Keylime will verify the signature first, then call these executables @@ -81,35 +107,24 @@ revocation_cert = default # # Keylime will also get the list of revocation actions from the file # action_list in the unzipped contents provided by the verifier. -revocation_actions= +revocation_actions = "" # A script to execute after unzipping the tenant payload. This is like # cloud-init lite =) Keylime will run it with a /bin/sh environment and # with a working directory of $keylime_dir/secure/unzipped. -payload_script=autorun.sh +payload_script = "autorun.sh" -# The path to the directory containing the pre-installed revocation action -# scripts. Ideally should point to an fixed/immutable location subject to -# attestation. The default is /usr/libexec/keylime. -revocation_actions_dir = /usr/libexec/keylime +# In case mTLS for the agent is disabled and the use of payloads is still +# required, this option has to be set to "true" in order to allow the agent +# to start. Details on why this configuration (mTLS disabled and payload enabled) +# is generally considered insecure can be found on +# https://github.com/keylime/keylime/security/advisories/GHSA-2m39-75g9-ff5r +enable_insecure_payload = false # Whether to allow running revocation actions sent as part of the payload. The -# default is True and setting as False will limit the revocation actions to the +# default is true and setting as false will limit the revocation actions to the # pre-installed ones. -allow_payload_revocation_actions = True - -# Jason @henn made be do it! He wanted a way for Keylime to measure the -# delivered payload into a pcr of choice. -# Specify a PCR number to turn it on. -# Set to -1 or any negative or out of range PCR value to turn off. -measure_payload_pcr=-1 - -# How long to wait between failed attempts to communicate with the TPM in -# seconds. Floating point values are accepted here. -retry_interval = 1 - -# Integer number of retries to communicate with the TPM before giving up. -max_retries = 10 +allow_payload_revocation_actions = true # TPM2-specific options, allows customizing default algorithms to use. # Specify the default crypto algorithms to use with a TPM2 for this agent. @@ -118,19 +133,19 @@ max_retries = 10 # - hashing: sha512, sha384, sha256 or sha1 # - encryption: ecc or rsa # - signing: rsassa, rsapss, ecdsa, ecdaa or ecschnorr -tpm_hash_alg = sha256 -tpm_encryption_alg = rsa -tpm_signing_alg = rsassa +tpm_hash_alg = "sha256" +tpm_encryption_alg = "rsa" +tpm_signing_alg = "rsassa" # If an EK is already present on the TPM (e.g., with "tpm2_createek") and # you require Keylime to use this EK, change "generate" to the actual EK # handle (e.g. "0x81000000"). The Keylime agent will then not attempt to # create a new EK upon startup, and neither will it flush the EK upon exit. -ek_handle = generate +ek_handle = "generate" # Use this option to state the existing TPM ownerpassword. This option should # be set only when ek_handle option points to an existing EK. -tpm_ownerpassword = +tpm_ownerpassword = "" # The user account to switch to to drop privileges when started as root # If left empty, the agent will keep running with high privileges. @@ -148,4 +163,11 @@ tpm_ownerpassword = # chown keylime /var/lib/keylime/cv_ca # chown keylime /var/lib/keylime/cv_ca/cacert.crt # -run_as = +run_as = "keylime:tss" + +# Path where to store the agent tpm data which can be loaded later +# If not an absolute path, it will be considered a relative path from the +# directory set by the keylime_dir option above +# If set as "default" Keylime will use "agent_data.json" +agent_data_path = "default" + diff --git a/src/common.rs b/src/common.rs index f803c1088..59e8f6993 100644 --- a/src/common.rs +++ b/src/common.rs @@ -4,7 +4,6 @@ use crate::algorithms::{EncryptionAlgorithm, HashAlgorithm, SignAlgorithm}; use crate::error::{Error, Result}; use crate::{permissions, tpm}; -use ini::Ini; use log::*; use openssl::{ hash::{hash, MessageDigest}, @@ -27,7 +26,6 @@ use tss_esapi::utils::PublicKey; use tss_esapi::{ structures::PcrSlot, traits::UnMarshall, utils::TpmsContext, }; -use uuid::Uuid; /* * Constants and static variables @@ -37,28 +35,12 @@ pub const STUB_VTPM: bool = false; pub const STUB_IMA: bool = true; pub const TPM_DATA_PCR: usize = 16; pub const IMA_PCR: usize = 10; -pub static DEFAULT_CONFIG: &str = "/etc/keylime-agent.conf"; pub static RSA_PUBLICKEY_EXPORTABLE: &str = "rsa placeholder"; -pub static TPM_TOOLS_PATH: &str = "/usr/local/bin/"; pub static IMA_ML: &str = "/sys/kernel/security/ima/ascii_runtime_measurements"; pub static MEASUREDBOOT_ML: &str = "/sys/kernel/security/tpm0/binary_bios_measurements"; -// The DEFAULT_CA_PATH is relative from WORK_DIR -pub static DEFAULT_CA_PATH: &str = "cv_ca/cacert.crt"; pub static KEY: &str = "secret"; -pub const MTLS_ENABLED: bool = true; -pub static WORK_DIR: &str = "/var/lib/keylime"; -pub static AGENT_DATA: &str = "agent_data.json"; -// Note: The revocation certificate name is generated inside the Python tenant and the -// certificate(s) can be generated by running the tenant with the --cert flag. For more -// information, check the README: https://github.com/keylime/keylime/#using-keylime-ca -pub static REV_CERT: &str = "RevocationNotifier-cert.crt"; -pub static REV_ACTIONS_DIR: &str = "/usr/libexec/keylime"; -pub static REV_ACTIONS: &str = ""; -pub static ALLOW_PAYLOAD_REV_ACTIONS: bool = true; -pub static ALLOW_INSECURE_PAYLOAD: bool = false; - pub const AGENT_UUID_LEN: usize = 36; pub const AUTH_TAG_LEN: usize = 96; pub const AES_128_KEY_LEN: usize = 16; @@ -184,9 +166,7 @@ pub(crate) struct AgentData { pub ak_sign_alg: SignAlgorithm, ak_public: Vec, ak_private: Vec, - nk_pub: Vec, - nk_priv: Vec, - mtls_cert: Option>, + ek_hash: Vec, } impl AgentData { @@ -194,24 +174,17 @@ impl AgentData { ak_hash_alg: HashAlgorithm, ak_sign_alg: SignAlgorithm, ak: &tpm::AKResult, - nk_pub: &PKey, - nk_priv: &PKey, - mtls_cert: &Option<&X509>, + ek_hash: &[u8], ) -> Result { let ak_public = ak.public.marshall()?; let ak_private: Vec = ak.private.to_vec(); - let mtls_cert = match mtls_cert { - Some(cert) => Some(cert.to_pem()?), - None => None, - }; + let ek_hash: Vec = ek_hash.to_vec(); Ok(Self { ak_hash_alg, ak_sign_alg, ak_public, ak_private, - nk_pub: nk_pub.public_key_to_pem()?, - nk_priv: nk_priv.private_key_to_pem_pkcs8()?, - mtls_cert, + ek_hash, }) } @@ -234,593 +207,29 @@ impl AgentData { Ok(tpm::AKResult { public, private }) } - pub(crate) fn get_nk( - &self, - ) -> Result<(PKey, PKey)> - { - let nk_pub = PKey::public_key_from_pem(&self.nk_pub)?; - let nk_priv = PKey::private_key_from_pem(&self.nk_priv)?; - Ok((nk_pub, nk_priv)) - } - - pub(crate) fn get_mtls_cert(&self) -> Result> { - match &self.mtls_cert { - Some(cert) => Ok(Some(X509::from_pem(cert)?)), - None => Ok(None), - } - } - pub(crate) fn valid( &self, hash_alg: HashAlgorithm, sign_alg: SignAlgorithm, + ek_hash: &[u8], ) -> bool { - hash_alg == self.ak_hash_alg && sign_alg == self.ak_sign_alg - } -} - -#[derive(Clone, Debug)] -pub(crate) struct KeylimeConfig { - pub agent_ip: String, - pub agent_port: String, - pub registrar_ip: String, - pub registrar_port: String, - pub agent_uuid: String, - pub agent_contact_ip: Option, - pub agent_contact_port: Option, - pub hash_alg: HashAlgorithm, - pub enc_alg: EncryptionAlgorithm, - pub sign_alg: SignAlgorithm, - pub agent_data: Option, - pub agent_data_path: String, - pub run_revocation: bool, - pub revocation_cert: String, - pub revocation_ip: String, - pub revocation_port: String, - pub secure_size: String, - pub payload_script: String, - pub dec_payload_filename: String, - pub key_filename: String, - pub extract_payload_zip: bool, - pub keylime_ca_path: String, - pub revocation_actions: String, - pub revocation_actions_dir: String, - pub allow_payload_revocation_actions: bool, - pub work_dir: String, - pub mtls_enabled: bool, - pub enable_insecure_payload: bool, - pub run_as: Option, - pub tpm_ownerpassword: Option, - pub ek_handle: Option, -} - -impl KeylimeConfig { - pub fn build() -> Result { - let conf_name = config_file_get(); - let conf = match Ini::load_from_file(&conf_name) { - Ok(file) => file, - Err(e) => { - error!( - "Could not load keylime config file: {} due to error: {}", - conf_name, e - ); - return Err(Error::Ini(e)); - } - }; - - let agent_ip = config_get_env( - &conf_name, - &conf, - "cloud_agent", - "cloudagent_ip", - "CLOUDAGENT_IP", - )?; - let agent_port = config_get_env( - &conf_name, - &conf, - "cloud_agent", - "cloudagent_port", - "CLOUDAGENT_PORT", - )?; - let registrar_ip = config_get_env( - &conf_name, - &conf, - "cloud_agent", - "registrar_ip", - "REGISTRAR_IP", - )?; - let registrar_port = config_get_env( - &conf_name, - &conf, - "cloud_agent", - "registrar_port", - "REGISTRAR_PORT", - )?; - let agent_uuid_config = - config_get(&conf_name, &conf, "cloud_agent", "agent_uuid")?; - let agent_uuid = get_uuid(&agent_uuid_config); - let agent_contact_ip = cloudagent_contact_ip_get(&conf_name, &conf); - let agent_contact_port = - cloudagent_contact_port_get(&conf_name, &conf)?; - let hash_alg = HashAlgorithm::try_from( - config_get(&conf_name, &conf, "cloud_agent", "tpm_hash_alg")? - .as_str(), - )?; - let enc_alg = EncryptionAlgorithm::try_from( - config_get( - &conf_name, - &conf, - "cloud_agent", - "tpm_encryption_alg", - )? - .as_str(), - )?; - let sign_alg = SignAlgorithm::try_from( - config_get(&conf_name, &conf, "cloud_agent", "tpm_signing_alg")? - .as_str(), - )?; - // There was a typo in Python Keylime and this accounts for having a version - // of Keylime installed that still has this typo. TODO: Remove later - let run_revocation = bool::from_str( - &config_get( - &conf_name, - &conf, - "cloud_agent", - "listen_notifications", - ) - .or_else(|_| { - config_get( - &conf_name, - &conf, - "cloud_agent", - "listen_notfications", - ) - })? - .to_lowercase(), - )?; - - let revocation_cert = - config_get(&conf_name, &conf, "cloud_agent", "revocation_cert")?; - let revocation_ip = config_get( - &conf_name, - &conf, - "general", - "receive_revocation_ip", - )?; - let revocation_port = config_get( - &conf_name, - &conf, - "general", - "receive_revocation_port", - )?; - - let secure_size = - config_get(&conf_name, &conf, "cloud_agent", "secure_size")?; - let payload_script = - config_get(&conf_name, &conf, "cloud_agent", "payload_script")?; - let dec_payload_filename = - config_get(&conf_name, &conf, "cloud_agent", "dec_payload_file")?; - - let key_filename = - config_get(&conf_name, &conf, "cloud_agent", "enc_keyname")?; - let extract_payload_zip = bool::from_str( - &config_get( - &conf_name, - &conf, - "cloud_agent", - "extract_payload_zip", - )? - .to_lowercase(), - )?; - - let work_dir = config_get_env( - &conf_name, - &conf, - "cloud_agent", - "keylime_dir", - "KEYLIME_DIR", - ) - .or_else::(|_| Ok(String::from(WORK_DIR)))?; - - let agent_data_path = PathBuf::from(&work_dir).join(AGENT_DATA); - let agent_data = if agent_data_path.exists() { - match AgentData::load(&agent_data_path) { - Ok(data) => Some(data), - Err(e) => { - warn!("Could not load TPM data"); - None - } - } - } else { - warn!( - "Agent Data not available under: {}", - agent_data_path.display() - ); - None - }; - - let mut keylime_ca_path = - config_get(&conf_name, &conf, "cloud_agent", "keylime_ca")?; - if keylime_ca_path == "default" { - keylime_ca_path = Path::new(&work_dir) - .join(DEFAULT_CA_PATH) - .display() - .to_string(); - } - let revocation_actions = config_get( - &conf_name, - &conf, - "cloud_agent", - "revocation_actions", - ) - .or_else::(|_| Ok(String::from(REV_ACTIONS)))?; - let revocation_actions_dir = config_get( - &conf_name, - &conf, - "cloud_agent", - "revocation_actions_dir", - ) - .or_else::(|_| Ok(String::from(REV_ACTIONS_DIR)))?; - let allow_payload_revocation_actions = match config_get( - &conf_name, - &conf, - "cloud_agent", - "allow_payload_revocation_actions", - ) { - Ok(s) => bool::from_str(&s.to_lowercase())?, - Err(_) => ALLOW_PAYLOAD_REV_ACTIONS, - }; - - let run_as = if permissions::get_euid() == 0 { - match config_get(&conf_name, &conf, "cloud_agent", "run_as") { - Ok(user_group) => { - if user_group.is_empty() { - warn!("Cannot drop privileges since 'run_as' is empty in 'cloud_agent' section of keylime-agent.conf."); - None - } else { - Some(user_group) - } - } - Err(_) => { - warn!("Cannot drop privileges since 'run_as' is missing in 'cloud_agent' section of keylime-agent.conf."); - None - } - } - } else { - None - }; - - let mtls_enabled = match config_get( - &conf_name, - &conf, - "cloud_agent", - "mtls_cert_enabled", - ) { - Ok(enabled) => bool::from_str(&enabled.to_lowercase()) - .or::(Ok(MTLS_ENABLED))?, - Err(_) => true, - }; - - let enable_insecure_payload = match config_get( - &conf_name, - &conf, - "cloud_agent", - "enable_insecure_payload", - ) { - Ok(allowed) => bool::from_str(&allowed.to_lowercase()) - .or::(Ok(ALLOW_INSECURE_PAYLOAD))?, - Err(_) => false, - }; - - let tpm_ownerpassword = - config_get(&conf_name, &conf, "cloud_agent", "tpm_ownerpassword") - .ok() - .filter(|s| s != "generate"); - - let ek_handle = - config_get(&conf_name, &conf, "cloud_agent", "ek_handle") - .ok() - .filter(|s| s != "generate"); - - Ok(KeylimeConfig { - agent_ip, - agent_port, - registrar_ip, - registrar_port, - agent_uuid, - agent_contact_ip, - agent_contact_port, - hash_alg, - enc_alg, - sign_alg, - agent_data, - agent_data_path: agent_data_path.display().to_string(), - run_revocation, - revocation_cert, - revocation_ip, - revocation_port, - secure_size, - payload_script, - dec_payload_filename, - key_filename, - extract_payload_zip, - keylime_ca_path, - revocation_actions, - revocation_actions_dir, - allow_payload_revocation_actions, - work_dir, - mtls_enabled, - enable_insecure_payload, - run_as, - tpm_ownerpassword, - ek_handle, - }) - } - - // Update function for the uuid if it is set to "hash_ek" - pub fn set_ek_uuid(&mut self, 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()?; - - let mut hash = hash(MessageDigest::sha256(), &pem)?; - self.agent_uuid = hex::encode(hash); - Ok(()) + hash_alg == self.ak_hash_alg + && sign_alg == self.ak_sign_alg + && ek_hash.to_vec() == self.ek_hash } } -// Default test configuration. This should match the defaults in keylime-agent.conf -#[cfg(any(test, feature = "testing"))] -impl Default for KeylimeConfig { - fn default() -> Self { - // In case the tests are executed by privileged user - let run_as = if permissions::get_euid() == 0 { - Some("keylime:tss".to_string()) - } else { - None - }; - - KeylimeConfig { - agent_ip: "127.0.0.1".to_string(), - agent_port: "9002".to_string(), - registrar_ip: "127.0.0.1".to_string(), - registrar_port: "8890".to_string(), - agent_uuid: "d432fbb3-d2f1-4a97-9ef7-75bd81c00000".to_string(), - agent_contact_ip: Some("127.0.0.1".to_string()), - agent_contact_port: Some(9002), - hash_alg: HashAlgorithm::Sha256, - enc_alg: EncryptionAlgorithm::Rsa, - sign_alg: SignAlgorithm::RsaSsa, - agent_data: None, - agent_data_path: Path::new(WORK_DIR) - .join(AGENT_DATA) - .display() - .to_string(), - run_revocation: true, - revocation_cert: "default".to_string(), - revocation_ip: "127.0.0.1".to_string(), - revocation_port: "8992".to_string(), - secure_size: "1m".to_string(), - payload_script: "autorun.sh".to_string(), - dec_payload_filename: "decrypted_payload".to_string(), - key_filename: "derived_tci_key".to_string(), - extract_payload_zip: true, - keylime_ca_path: DEFAULT_CA_PATH.to_string(), - revocation_actions: "".to_string(), - revocation_actions_dir: "/usr/libexec/keylime".to_string(), - allow_payload_revocation_actions: true, - work_dir: WORK_DIR.to_string(), - mtls_enabled: true, - enable_insecure_payload: false, - run_as, - tpm_ownerpassword: None, - ek_handle: None, - } - } -} +/// Calculate the SHA-256 hash of the TPM public key in PEM format +/// +/// 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()?; -fn get_uuid(agent_uuid_config: &str) -> String { - match agent_uuid_config { - "openstack" => { - info!("Openstack placeholder..."); - "openstack".into() - } - "hash_ek" => { - info!("Using hashed EK as UUID"); - // DO NOT change this to something else. It is used by KeylimeConfig to later set the correct value. - "hash_ek".into() - } - "generate" => { - let agent_uuid = Uuid::new_v4(); - info!("Generated a new UUID: {}", &agent_uuid); - agent_uuid.to_string() - } - uuid_config => match Uuid::parse_str(uuid_config) { - Ok(uuid_config) => uuid_config.to_string(), - Err(_) => { - info!("Misformatted UUID: {}", &uuid_config); - let agent_uuid = Uuid::new_v4(); - agent_uuid.to_string() - } - }, - } -} - -/* - * Return: Returns the configuration file provided in the environment variable - * KEYLIME_CONFIG or defaults to /etc/keylime-agent.conf - * - * Example call: - * let config = config_file_get(); - */ -fn config_file_get() -> String { - match env::var("KEYLIME_CONFIG") { - Ok(cfg) => { - // The variable length must be larger than 0 to accept - if !cfg.is_empty() { - cfg - } else { - String::from(DEFAULT_CONFIG) - } - } - _ => String::from(DEFAULT_CONFIG), - } -} - -/// Returns the contact ip for the agent if set -fn cloudagent_contact_ip_get(conf_name: &str, conf: &Ini) -> Option { - match config_get_env( - conf_name, - conf, - "cloud_agent", - "agent_contact_ip", - "KEYLIME_AGENT_CONTACT_IP", - ) { - Ok(ip) => Some(ip), - Err(_) => None, // Ignore errors because this option might not be set - } -} - -/// Returns the contact ip for the agent if set -fn cloudagent_contact_port_get( - conf_name: &str, - conf: &Ini, -) -> Result> { - match config_get_env( - conf_name, - conf, - "cloud_agent", - "agent_contact_port", - "KEYLIME_AGENT_CONTACT_PORT", - ) { - Ok(port_str) => match port_str.parse::() { - Ok(port) => Ok(Some(port)), - _ => Err(Error::Configuration(format!( - "Parse {} to a port number.", - port_str - ))), - }, - _ => Ok(None), // Ignore errors because this option might not be set - } -} - -/* - * Input: conf_name, conf, [section] and key - * Return: Returns the matched key - * - * Example call: - * let port = common::config_get(conf_file_name, file_Ini,"general","cloudagent_port"); - */ -fn config_get( - conf_name: &str, - conf: &Ini, - section: &str, - key: &str, -) -> Result { - let section = match conf.section(Some(section.to_owned())) { - Some(section) => section, - None => - // TODO: Make Error::Configuration an alternative with data instead of string - { - return Err(Error::Configuration(format!( - "Cannot find section called {} in file {}", - section, conf_name - ))) - } - }; - let value = match section.get(key) { - Some(value) => value.trim(), - None => - // TODO: Make Error::Configuration an alternative with data instead of string - { - return Err(Error::Configuration(format!( - "Cannot find key {} in file {}", - key, conf_name - ))) - } - }; - - if value.is_empty() { - warn!("Cannot find value for key {} in file {}", key, conf_name); - }; - - Ok(value.to_string()) -} - -/* - * Input: conf_name, conf,[section] and key and environment variable - * Return: Returns the matched key - * - * Example call: - * let port = common::config_get_env(conf_file_name, file_Ini, "general","cloudagent_port", "CLOUDAGENT_PORT"); - */ -fn config_get_env( - conf_name: &str, - conf: &Ini, - section: &str, - key: &str, - env: &str, -) -> Result { - match env::var(env) { - Ok(ip) => { - // The variable length must be larger than 0 to accept - if !ip.is_empty() { - Ok(ip) - } else { - config_get(conf_name, conf, section, key) - } - } - _ => config_get(conf_name, conf, section, key), - } -} - -// Unit Testing -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_config_get_parameters_exist() { - //let result = config_get("keylime-agent.conf", "general", "cloudagent_port"); - //assert_eq!(result, "9002"); - } - - #[test] - fn test_config_file_get() { - let conf_orig = option_env!("KEYLIME_CONFIG").or(Some("")).unwrap(); //#[allow_ci] - - // Test with no environment variable - env::set_var("KEYLIME_CONFIG", ""); - assert_eq!( - config_file_get(), - String::from("/etc/keylime-agent.conf") - ); - - // Test with an environment variable - env::set_var("KEYLIME_CONFIG", "/tmp/testing.conf"); - assert_eq!(config_file_get(), String::from("/tmp/testing.conf")); - // Reset environment - env::set_var("KEYLIME_CONFIG", conf_orig); - } - - #[test] - fn test_get_uuid() { - assert_eq!(get_uuid("openstack"), "openstack"); - assert_eq!(get_uuid("hash_ek"), "hash_ek"); - let _ = Uuid::parse_str(&get_uuid("generate")).unwrap(); //#[allow_ci] - assert_eq!( - get_uuid("D432FBB3-D2F1-4A97-9EF7-75BD81C00000"), - "d432fbb3-d2f1-4a97-9ef7-75bd81c00000" - ); - assert_ne!( - get_uuid("D432FBB3-D2F1-4A97-9EF7-75BD81C0000X"), - "d432fbb3-d2f1-4a97-9ef7-75bd81c0000X" - ); - let _ = Uuid::parse_str(&get_uuid( - "D432FBB3-D2F1-4A97-9EF7-75BD81C0000X", - )) - .unwrap(); //#[allow_ci] - } + // Calculate the SHA-256 hash of the public key in PEM format + let mut hash = hash(MessageDigest::sha256(), &pem)?; + Ok(hex::encode(hash)) } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 000000000..11f10c696 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,749 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Keylime Authors + +use crate::{ + algorithms::{EncryptionAlgorithm, HashAlgorithm, SignAlgorithm}, + error::Error, + permissions, tpm, +}; +use config::{ + builder::DefaultState, Config, ConfigBuilder, ConfigError, Environment, + File, FileFormat, Map, Source, Value, +}; +use glob::glob; +use log::*; +use serde::{Deserialize, Serialize}; +use std::{ + env, + path::{Path, PathBuf}, +}; +use toml::value::Table; +use uuid::Uuid; + +pub static CONFIG_VERSION: &str = "2.0"; +pub static DEFAULT_UUID: &str = "d432fbb3-d2f1-4a97-9ef7-75bd81c00000"; +pub static DEFAULT_IP: &str = "127.0.0.1"; +pub static DEFAULT_PORT: u32 = 9002; +pub static DEFAULT_CONTACT_IP: &str = "127.0.0.1"; +pub static DEFAULT_CONTACT_PORT: u32 = 9002; +pub static DEFAULT_REGISTRAR_IP: &str = "127.0.0.1"; +pub static DEFAULT_REGISTRAR_PORT: u32 = 8890; +pub static DEFAULT_ENABLE_AGENT_MTLS: bool = true; +pub static DEFAULT_KEYLIME_DIR: &str = "/var/lib/keylime"; +pub static DEFAULT_SERVER_KEY: &str = "server-private.pem"; +pub static DEFAULT_SERVER_CERT: &str = "server-cert.crt"; +pub static DEFAULT_SERVER_KEY_PASSWORD: &str = ""; +// The DEFAULT_TRUSTED_CLIENT_CA is relative from KEYLIME_DIR +pub static DEFAULT_TRUSTED_CLIENT_CA: &str = "cv_ca/cacert.crt"; +pub static DEFAULT_ENC_KEYNAME: &str = "derived_tci_key"; +pub static DEFAULT_DEC_PAYLOAD_FILE: &str = "decrypted_payload"; +pub static DEFAULT_SECURE_SIZE: &str = "1m"; +pub static DEFAULT_TPM_OWNERPASSWORD: &str = ""; +pub static DEFAULT_EXTRACT_PAYLOAD_ZIP: bool = true; +pub static DEFAULT_ENABLE_REVOCATION_NOTIFICATIONS: bool = true; +pub static DEFAULT_REVOCATION_ACTIONS_DIR: &str = "/usr/libexec/keylime"; +pub static DEFAULT_REVOCATION_NOTIFICATION_IP: &str = "127.0.0.1"; +pub static DEFAULT_REVOCATION_NOTIFICATION_PORT: u32 = 8992; +// Note: The revocation certificate name is generated inside the Python tenant and the +// certificate(s) can be generated by running the tenant with the --cert flag. For more +// information, check the README: https://github.com/keylime/keylime/#using-keylime-ca +pub static DEFAULT_REVOCATION_CERT: &str = "RevocationNotifier-cert.crt"; +pub static DEFAULT_REVOCATION_ACTIONS: &str = ""; +pub static DEFAULT_PAYLOAD_SCRIPT: &str = "autorun.sh"; +pub static DEFAULT_ENABLE_INSECURE_PAYLOAD: bool = false; +pub static DEFAULT_ALLOW_PAYLOAD_REVOCATION_ACTIONS: bool = true; +pub static DEFAULT_TPM_HASH_ALG: &str = "sha256"; +pub static DEFAULT_TPM_ENCRYPTION_ALG: &str = "rsa"; +pub static DEFAULT_TPM_SIGNING_ALG: &str = "rsassa"; +pub static DEFAULT_EK_HANDLE: &str = "generate"; +pub static DEFAULT_RUN_AS: &str = "keylime:tss"; +pub static DEFAULT_AGENT_DATA_PATH: &str = "agent_data.json"; +pub static DEFAULT_CONFIG: &str = "/etc/keylime/agent.conf"; +pub static DEFAULT_CONFIG_SYS: &str = "/usr/etc/keylime/agent.conf"; + +impl Source for KeylimeConfig { + fn collect(&self) -> Result, ConfigError> { + let agent: Map = Map::from([ + ("version".to_string(), self.agent.version.to_string().into()), + ("uuid".to_string(), self.agent.uuid.to_string().into()), + ("ip".to_string(), self.agent.ip.to_string().into()), + ("port".to_string(), Value::from(self.agent.port)), + ( + "contact_ip".to_string(), + if let Some(ref s) = self.agent.contact_ip { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "contact_port".to_string(), + if let Some(ref s) = self.agent.contact_port { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "registrar_ip".to_string(), + self.agent.registrar_ip.to_string().into(), + ), + ( + "registrar_port".to_string(), + self.agent.registrar_port.into(), + ), + ( + "enable_agent_mtls".to_string(), + self.agent.enable_agent_mtls.into(), + ), + ( + "keylime_dir".to_string(), + self.agent.keylime_dir.to_string().into(), + ), + ( + "server_key".to_string(), + if let Some(ref s) = self.agent.server_key { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "server_key_password".to_string(), + if let Some(ref s) = self.agent.server_key_password { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "server_cert".to_string(), + if let Some(ref s) = self.agent.server_cert { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "trusted_client_ca".to_string(), + if let Some(ref s) = self.agent.trusted_client_ca { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "enc_keyname".to_string(), + self.agent.enc_keyname.to_string().into(), + ), + ( + "dec_payload_file".to_string(), + self.agent.dec_payload_file.to_string().into(), + ), + ( + "secure_size".to_string(), + self.agent.secure_size.to_string().into(), + ), + ( + "tpm_ownerpassword".to_string(), + if let Some(ref s) = self.agent.tpm_ownerpassword { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "extract_payload_zip".to_string(), + self.agent.extract_payload_zip.into(), + ), + ( + "enable_revocation_notifications".to_string(), + self.agent.enable_revocation_notifications.into(), + ), + ( + "revocation_actions_dir".to_string(), + if let Some(ref s) = self.agent.revocation_actions_dir { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "revocation_notification_ip".to_string(), + if let Some(ref s) = self.agent.revocation_notification_ip { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "revocation_notification_port".to_string(), + if let Some(ref s) = self.agent.revocation_notification_port { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "revocation_cert".to_string(), + if let Some(ref s) = self.agent.revocation_cert { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "revocation_actions".to_string(), + if let Some(ref s) = self.agent.revocation_actions { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "payload_script".to_string(), + self.agent.payload_script.to_string().into(), + ), + ( + "enable_insecure_payload".to_string(), + self.agent.enable_insecure_payload.to_string().into(), + ), + ( + "allow_payload_revocation_actions".to_string(), + self.agent.allow_payload_revocation_actions.into(), + ), + ( + "tpm_hash_alg".to_string(), + self.agent.tpm_hash_alg.to_string().into(), + ), + ( + "tpm_encryption_alg".to_string(), + self.agent.tpm_encryption_alg.to_string().into(), + ), + ( + "tpm_signing_alg".to_string(), + self.agent.tpm_signing_alg.to_string().into(), + ), + ( + "ek_handle".to_string(), + if let Some(ref s) = self.agent.ek_handle { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "run_as".to_string(), + if let Some(ref s) = self.agent.run_as { + s.to_string().into() + } else { + "".into() + }, + ), + ( + "agent_data_path".to_string(), + if let Some(ref s) = self.agent.agent_data_path { + s.to_string().into() + } else { + "".into() + }, + ), + ]); + + Ok(Map::from([("agent".to_string(), agent.into())])) + } + + fn clone_into_box(&self) -> Box { + Box::new(self.clone()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub(crate) struct KeylimeConfig { + pub agent: AgentConfig, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub(crate) struct AgentConfig { + pub version: String, + pub uuid: String, + pub ip: String, + pub port: u32, + pub contact_ip: Option, + pub contact_port: Option, + pub registrar_ip: String, + pub registrar_port: u32, + pub enable_agent_mtls: bool, + pub keylime_dir: String, + pub server_key: Option, + pub server_cert: Option, + pub server_key_password: Option, + pub trusted_client_ca: Option, + pub enc_keyname: String, + pub dec_payload_file: String, + pub secure_size: String, + pub tpm_ownerpassword: Option, + pub extract_payload_zip: bool, + pub enable_revocation_notifications: bool, + pub revocation_actions_dir: Option, + pub revocation_notification_ip: Option, + pub revocation_notification_port: Option, + pub revocation_cert: Option, + pub revocation_actions: Option, + pub payload_script: String, + pub enable_insecure_payload: bool, + pub allow_payload_revocation_actions: bool, + pub tpm_hash_alg: String, + pub tpm_encryption_alg: String, + pub tpm_signing_alg: String, + pub ek_handle: Option, + pub run_as: Option, + pub agent_data_path: Option, +} + +impl Default for AgentConfig { + fn default() -> Self { + // In case the process is executed by privileged user + let run_as = if permissions::get_euid() == 0 { + Some(DEFAULT_RUN_AS.to_string()) + } else { + None + }; + + AgentConfig { + version: CONFIG_VERSION.to_string(), + ip: DEFAULT_IP.to_string(), + port: DEFAULT_PORT, + registrar_ip: DEFAULT_REGISTRAR_IP.to_string(), + registrar_port: DEFAULT_REGISTRAR_PORT, + uuid: DEFAULT_UUID.to_string(), + contact_ip: Some(DEFAULT_CONTACT_IP.to_string()), + contact_port: Some(DEFAULT_CONTACT_PORT), + tpm_hash_alg: DEFAULT_TPM_HASH_ALG.to_string(), + tpm_encryption_alg: DEFAULT_TPM_ENCRYPTION_ALG.to_string(), + tpm_signing_alg: DEFAULT_TPM_SIGNING_ALG.to_string(), + agent_data_path: Some("default".to_string()), + enable_revocation_notifications: + DEFAULT_ENABLE_REVOCATION_NOTIFICATIONS, + revocation_cert: Some("default".to_string()), + revocation_notification_ip: Some( + DEFAULT_REVOCATION_NOTIFICATION_IP.to_string(), + ), + revocation_notification_port: Some( + DEFAULT_REVOCATION_NOTIFICATION_PORT, + ), + secure_size: DEFAULT_SECURE_SIZE.to_string(), + payload_script: DEFAULT_PAYLOAD_SCRIPT.to_string(), + dec_payload_file: DEFAULT_DEC_PAYLOAD_FILE.to_string(), + enc_keyname: DEFAULT_ENC_KEYNAME.to_string(), + extract_payload_zip: DEFAULT_EXTRACT_PAYLOAD_ZIP, + server_key: Some("default".to_string()), + server_key_password: Some( + DEFAULT_SERVER_KEY_PASSWORD.to_string(), + ), + server_cert: Some("default".to_string()), + trusted_client_ca: Some("default".to_string()), + revocation_actions: Some(DEFAULT_REVOCATION_ACTIONS.to_string()), + revocation_actions_dir: Some( + DEFAULT_REVOCATION_ACTIONS_DIR.to_string(), + ), + allow_payload_revocation_actions: + DEFAULT_ALLOW_PAYLOAD_REVOCATION_ACTIONS, + keylime_dir: DEFAULT_KEYLIME_DIR.to_string(), + enable_agent_mtls: DEFAULT_ENABLE_AGENT_MTLS, + enable_insecure_payload: DEFAULT_ENABLE_INSECURE_PAYLOAD, + run_as, + tpm_ownerpassword: Some(DEFAULT_TPM_OWNERPASSWORD.to_string()), + ek_handle: Some(DEFAULT_EK_HANDLE.to_string()), + } + } +} + +impl Default for KeylimeConfig { + fn default() -> Self { + let c = KeylimeConfig { + agent: AgentConfig::default(), + }; + + // The default config should never fail to translate keywords + config_translate_keywords(&c).unwrap() //#[allow_ci] + } +} + +fn config_get_file_setting() -> Result, Error> { + let default_config = KeylimeConfig::default(); + + Ok(Config::builder() + // Default values + .add_source(default_config) + // Add system configuration file + .add_source( + File::new(DEFAULT_CONFIG_SYS, FileFormat::Toml).required(false), + ) + // Add system configuration snippets + .add_source( + glob("/usr/etc/keylime/agent.conf.d/*") + .map_err(Error::GlobPattern)? + .filter_map(|entry| entry.ok()) + .map(|path| { + File::new(&path.display().to_string(), FileFormat::Toml) + .required(false) + }) + .collect::>(), + ) + .add_source( + File::new(DEFAULT_CONFIG, FileFormat::Toml).required(false), + ) + // Add user configuration snippets + .add_source( + glob("/etc/keylime/agent.conf.d/*") + .map_err(Error::GlobPattern)? + .filter_map(|entry| entry.ok()) + .map(|path| { + File::new(&path.display().to_string(), FileFormat::Toml) + .required(false) + }) + .collect::>(), + ) + // Add environment variables overrides + .add_source( + Environment::with_prefix("KEYLIME") + .separator("_") + .prefix_separator("_"), + )) +} + +fn config_get_setting() -> Result, Error> { + if let Ok(env_cfg) = env::var("KEYLIME_AGENT_CONFIG") { + if !env_cfg.is_empty() { + let path = Path::new(&env_cfg); + if (path.exists()) { + return Ok(Config::builder() + .add_source( + File::new(&env_cfg, FileFormat::Toml).required(true), + ) + // Add environment variables overrides + .add_source( + Environment::with_prefix("KEYLIME") + .prefix_separator("_") + .separator("_"), + )); + } else { + warn!("Configuration set in KEYLIME_AGENT_CONFIG environment variable not found"); + return Err(Error::Configuration("Configuration set in KEYLIME_AGENT_CONFIG environment variable not found".to_string())); + } + } + } + config_get_file_setting() +} + +/// Replace the options that support keywords with the final value +fn config_translate_keywords( + config: &KeylimeConfig, +) -> Result { + let uuid = get_uuid(&config.agent.uuid); + + let env_keylime_dir = env::var("KEYLIME_DIR").ok(); + let keylime_dir = match env_keylime_dir { + Some(ref dir) => { + if !dir.is_empty() { + dir.to_string() + } else { + config.agent.keylime_dir.to_string() + } + } + None => config.agent.keylime_dir.to_string(), + }; + + let mut agent_data_path = config_get_file_path( + &config.agent.agent_data_path, + &keylime_dir, + DEFAULT_AGENT_DATA_PATH, + ); + + let mut server_key = config_get_file_path( + &config.agent.server_key, + &keylime_dir, + DEFAULT_SERVER_KEY, + ); + + let mut server_cert = config_get_file_path( + &config.agent.server_cert, + &keylime_dir, + DEFAULT_SERVER_CERT, + ); + + let mut trusted_client_ca = config_get_file_path( + &config.agent.trusted_client_ca, + &keylime_dir, + DEFAULT_TRUSTED_CLIENT_CA, + ); + + let mut revocation_cert = config_get_file_path( + &config.agent.revocation_cert, + &keylime_dir, + &format!("secure/unzipped/{}", DEFAULT_REVOCATION_CERT), + ); + + let tpm_ownerpassword = match config.agent.tpm_ownerpassword { + Some(ref s) => { + if s.as_str() != "generate" { + Some(s.to_string()) + } else { + None + } + } + None => None, + }; + + let ek_handle = match config.agent.ek_handle { + Some(ref s) => { + if s.as_str() != "generate" { + Some(s.to_string()) + } else { + None + } + } + None => None, + }; + + // Validate the configuration + + // If mTLS is enabled, the trusted client CA certificate is required + if config.agent.enable_agent_mtls + && config.agent.trusted_client_ca.is_none() + { + error!("The option 'enable_agent_mtls' is set as 'true' but no certificate was set in 'trusted_client_ca' option"); + return Err(Error::Configuration( + "The option 'enable_agent_mtls' is set as 'true' but no certificate was set in 'trusted_client_ca' option".to_string())); + } + + // If revocation notifications is enabled, verify all the required options for revocation + if config.agent.enable_revocation_notifications { + if config.agent.revocation_notification_ip.is_none() { + error!("The option 'enable_revocation_notifications' is set as 'true' but no IP was set in 'revocation_notification_ip'"); + return Err(Error::Configuration("The option 'enable_revocation_notifications' is set as 'true' but no IP was set in 'revocation_notification_ip'".to_string())); + } + if config.agent.revocation_notification_port.is_none() { + error!("The option 'enable_revocation_notifications' is set as 'true' but no port was set in 'revocation_notification_port'"); + return Err(Error::Configuration("The option 'enable_revocation_notifications' is set as 'true' but no port was set in 'revocation_notification_port'".to_string())); + } + if config.agent.revocation_cert.is_none() { + error!("The option 'enable_revocation_notifications' is set as 'true' but no certificate was set in 'revocation_cert'"); + return Err(Error::Configuration("The option 'enable_revocation_notifications' is set as 'true' but no certificate was set in 'revocation_notification_cert'".to_string())); + } + let actions_dir = match config.agent.revocation_actions_dir { + Some(ref dir) => dir.to_string(), + None => { + error!("The option 'enable_revocation_notifications' is set as 'true' but the revocation actions directory was not set in 'revocation_actions_dir'"); + return Err(Error::Configuration("The option 'enable_revocation_notifications' is set as 'true' but the revocation actions directory was not set in 'revocation_actions_dir'".to_string())); + } + }; + } + + Ok(KeylimeConfig { + agent: AgentConfig { + keylime_dir, + uuid, + server_key, + server_cert, + trusted_client_ca, + tpm_ownerpassword, + ek_handle, + agent_data_path, + revocation_cert, + ..config.agent.clone() + }, + }) +} + +impl KeylimeConfig { + pub fn new() -> Result { + // Get the base configuration file from the environment variable or the default locations + let setting = config_get_setting()?.build()?; + let config: KeylimeConfig = setting.try_deserialize()?; + + // Replace keywords with actual values + config_translate_keywords(&config) + } +} + +/// Expand a file path from the configuration file. +/// +/// If the option is None, return None +/// If the option string is empty, return None +/// If the option string is set as "default", return the provided default path relative from the provided work_dir. +/// If the option string is a relative path, return the path relative from the provided work_dir +/// If the option string is an absolute path, return the path without change. +fn config_get_file_path( + path: &Option, + work_dir: &str, + default: &str, +) -> Option { + if let Some(ref value) = path { + if value == "default" { + return Some( + Path::new(work_dir).join(default).display().to_string(), + ); + } else if value.is_empty() { + return None; + } else { + let value = Path::new(&value); + if value.is_relative() { + return Some( + Path::new(work_dir).join(value).display().to_string(), + ); + } else { + return Some(value.display().to_string()); + } + } + } + None +} + +fn get_uuid(agent_uuid_config: &str) -> String { + match agent_uuid_config { + "openstack" => { + info!("Openstack placeholder..."); + "openstack".into() + } + "hash_ek" => { + info!("Using hashed EK as UUID"); + // DO NOT change this to something else. It is used later to set the correct value. + "hash_ek".into() + } + "generate" => { + let agent_uuid = Uuid::new_v4(); + info!("Generated a new UUID: {}", &agent_uuid); + agent_uuid.to_string() + } + uuid_config => match Uuid::parse_str(uuid_config) { + Ok(uuid_config) => uuid_config.to_string(), + Err(_) => { + info!("Misformatted UUID: {}", &uuid_config); + let agent_uuid = Uuid::new_v4(); + agent_uuid.to_string() + } + }, + } +} + +// Unit Testing +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default() { + let default = KeylimeConfig::default(); + } + + #[test] + fn get_revocation_cert_path_default() { + let test_config = KeylimeConfig::default(); + let revocation_cert_path = + (test_config.agent.revocation_cert.clone()).unwrap(); //#[allow_ci] + let mut expected = Path::new(&test_config.agent.keylime_dir) + .join("secure/unzipped") + .join(DEFAULT_REVOCATION_CERT) + .display() + .to_string(); + assert_eq!(revocation_cert_path, expected); + } + + #[test] + fn get_revocation_cert_path_absolute() { + let mut test_config = KeylimeConfig { + agent: AgentConfig { + revocation_cert: Some(String::from("/test/cert.crt")), + ..Default::default() + }, + }; + let result = config_translate_keywords(&test_config); + assert!(result.is_ok()); + let test_config = result.unwrap(); //#[allow_ci] + let revocation_cert_path = + (test_config.agent.revocation_cert).unwrap(); //#[allow_ci] + let mut expected = Path::new("/test/cert.crt").display().to_string(); + assert_eq!(revocation_cert_path, expected); + } + + #[test] + fn get_revocation_cert_path_relative() { + let mut test_config = KeylimeConfig { + agent: AgentConfig { + revocation_cert: Some(String::from("cert.crt")), + ..Default::default() + }, + }; + let result = config_translate_keywords(&test_config); + assert!(result.is_ok()); + let test_config = result.unwrap(); //#[allow_ci] + let revocation_cert_path = + (test_config.agent.revocation_cert.clone()).unwrap(); //#[allow_ci] + let mut expected = Path::new(&test_config.agent.keylime_dir) + .join("cert.crt") + .display() + .to_string(); + assert_eq!(revocation_cert_path, expected); + } + + #[test] + fn get_revocation_cert_path_empty() { + let mut test_config = KeylimeConfig { + agent: AgentConfig { + revocation_cert: Some(String::from("")), + ..Default::default() + }, + }; + let result = config_translate_keywords(&test_config); + assert!(result.is_ok()); + let test_config = result.unwrap(); //#[allow_ci] + assert_eq!(test_config.agent.revocation_cert, None); + } + + #[test] + fn get_revocation_cert_path_none() { + let mut test_config = KeylimeConfig { + agent: AgentConfig { + revocation_cert: None, + ..Default::default() + }, + }; + let result = config_translate_keywords(&test_config); + // Due to enable_revocation_notifications being set + assert!(result.is_err()); + let mut test_config = KeylimeConfig { + agent: AgentConfig { + enable_revocation_notifications: false, + revocation_cert: None, + ..Default::default() + }, + }; + + // Now unset enable_revocation_notifications and check that is allowed + let result = config_translate_keywords(&test_config); + assert!(result.is_ok()); + let test_config = result.unwrap(); //#[allow_ci] + assert_eq!(test_config.agent.revocation_cert, None); + } + + #[test] + fn test_get_uuid() { + assert_eq!(get_uuid("openstack"), "openstack"); + assert_eq!(get_uuid("hash_ek"), "hash_ek"); + let _ = Uuid::parse_str(&get_uuid("generate")).unwrap(); //#[allow_ci] + assert_eq!( + get_uuid("D432FBB3-D2F1-4A97-9EF7-75BD81C00000"), + "d432fbb3-d2f1-4a97-9ef7-75bd81c00000" + ); + assert_ne!( + get_uuid("D432FBB3-D2F1-4A97-9EF7-75BD81C0000X"), + "d432fbb3-d2f1-4a97-9ef7-75bd81c0000X" + ); + let _ = Uuid::parse_str(&get_uuid( + "D432FBB3-D2F1-4A97-9EF7-75BD81C0000X", + )) + .unwrap(); //#[allow_ci] + } +} diff --git a/src/crypto.rs b/src/crypto.rs index 5c9af0214..247037869 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -16,9 +16,13 @@ use openssl::{ x509::store::X509StoreBuilder, x509::{X509Name, X509}, }; -use std::fs; -use std::path::Path; -use std::string::String; +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, @@ -26,7 +30,7 @@ use crate::{ // Read a X509 cert or cert chain and outputs the first certificate pub(crate) fn load_x509(input_cert_path: &Path) -> Result { - let contents = fs::read_to_string(&input_cert_path)?; + let contents = read_to_string(&input_cert_path)?; let mut cert_chain = X509::stack_from_pem(contents.as_bytes())?; if cert_chain.len() != 1 { @@ -40,6 +44,62 @@ pub(crate) fn load_x509(input_cert_path: &Path) -> Result { Ok(cert) } +/// 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(()) +} + +/// Read a PEM file and returns the public and private keys +pub(crate) fn load_key_pair( + key_path: &Path, + key_password: &Option, +) -> Result<(PKey, PKey)> { + let pem = std::fs::read(key_path)?; + let private = match key_password { + Some(ref 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, +) -> Result<()> { + // Write the generated key to the file + let mut file = std::fs::File::create(file_path)?; + match passphrase { + Some(ref 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(()) +} + pub(crate) fn rsa_generate(key_size: u32) -> Result> { PKey::from_rsa(Rsa::generate(key_size)?).map_err(Error::Crypto) } @@ -283,7 +343,7 @@ pub mod testing { pub(crate) fn rsa_import_pair( path: impl AsRef, ) -> Result<(PKey, PKey)> { - let contents = fs::read_to_string(path)?; + 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)) @@ -521,7 +581,7 @@ mod tests { .join("test-rsa.pem"); // Get RSA keys - let contents = fs::read_to_string(rsa_key_path); + 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] @@ -533,8 +593,70 @@ mod tests { .join("test-data") .join("test-rsa.sig"); - let signature = fs::read_to_string(signature_path).unwrap(); //#[allow_ci] + 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".to_string()) + ) + .is_ok()); + assert!(write_key_pair( + &private, + &empty_pw_path, + &Some("".to_string()) + ) + .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".to_string())) + .unwrap(); //#[allow_ci] + let (_, priv_from_empty) = + load_key_pair(&empty_pw_path, &Some("".to_string())).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] + } + } } diff --git a/src/error.rs b/src/error.rs index d43ce0528..403b9ed1e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,7 +21,7 @@ pub(crate) enum Error { #[allow(unused)] InvalidRequest, #[error("Configuration loading error: {0}")] - Ini(#[from] ini::Error), + Config(#[from] config::ConfigError), #[error("Infallible: {0}")] Infallible(#[from] std::convert::Infallible), #[error("Compress tools error: {0}")] @@ -38,6 +38,10 @@ pub(crate) enum Error { Serde(#[from] serde_json::Error), #[error("Permission error")] Permission, + #[error("Glob error")] + Glob(#[from] glob::GlobError), + #[error("Glob pattern error")] + GlobPattern(#[from] glob::PatternError), #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("Text decoding error: {0}")] diff --git a/src/keys_handler.rs b/src/keys_handler.rs index 0cec15d5b..ea1c39e93 100644 --- a/src/keys_handler.rs +++ b/src/keys_handler.rs @@ -273,14 +273,15 @@ pub async fn verify( #[cfg(test)] mod tests { use super::*; - use crate::common::{ - KeylimeConfig, AES_128_KEY_LEN, AES_256_KEY_LEN, API_VERSION, - }; - use crate::crypto::compute_hmac; #[cfg(feature = "testing")] use crate::crypto::testing::{ encrypt_aead, pkey_pub_from_pem, rsa_oaep_encrypt, }; + use crate::{ + common::{AES_128_KEY_LEN, AES_256_KEY_LEN, API_VERSION}, + config::KeylimeConfig, + crypto::compute_hmac, + }; use actix_rt::Arbiter; use actix_web::{test, web, App}; use openssl::{ @@ -363,7 +364,7 @@ mod tests { rsa_oaep_encrypt("edata.pub_key, u.bytes()).unwrap(); //#[allow_ci] let auth_tag = - compute_hmac(k.bytes(), test_config.agent_uuid.as_bytes()) + compute_hmac(k.bytes(), test_config.agent.uuid.as_bytes()) .unwrap(); //#[allow_ci] let ukey = KeylimeUKey { diff --git a/src/main.rs b/src/main.rs index ba14ecc28..2700d2c71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,6 +35,7 @@ mod algorithms; mod common; +mod config; mod crypto; mod error; mod errors_handler; @@ -58,7 +59,10 @@ use error::{Error, Result}; use futures::{future::TryFutureExt, try_join}; use ima::ImaMeasurementList; use log::*; -use openssl::pkey::{PKey, Private, Public}; +use openssl::{ + pkey::{PKey, Private, Public}, + x509::X509, +}; use std::{ convert::TryFrom, fs, @@ -103,9 +107,9 @@ pub struct QuoteData { enc_alg: algorithms::EncryptionAlgorithm, sign_alg: algorithms::SignAlgorithm, agent_uuid: String, - revocation_cert: PathBuf, - revocation_actions: String, - revocation_actions_dir: PathBuf, + revocation_cert: Option, + revocation_actions: Option, + revocation_actions_dir: Option, allow_payload_revocation_actions: bool, secure_size: String, work_dir: PathBuf, @@ -134,7 +138,7 @@ pub(crate) fn decrypt_payload( // writing out symmetric key and encrypted payload. returns file paths for // both. pub(crate) fn setup_unzipped( - config: &KeylimeConfig, + config: &config::KeylimeConfig, mount: &Path, ) -> Result<(PathBuf, PathBuf, PathBuf)> { let unzipped = mount.join("unzipped"); @@ -144,8 +148,8 @@ pub(crate) fn setup_unzipped( fs::remove_dir_all(&unzipped)?; } - let dec_payload_path = unzipped.join(&config.dec_payload_filename); - let key_path = unzipped.join(&config.key_filename); + let dec_payload_path = unzipped.join(&config.agent.dec_payload_file); + let key_path = unzipped.join(&config.agent.enc_keyname); fs::create_dir(&unzipped)?; @@ -177,7 +181,7 @@ pub(crate) fn write_out_key_and_payload( } // run a script (such as the init script, if any) and check the status -pub(crate) fn run(dir: &Path, script: &str, agent_uuid: &str) -> Result<()> { +pub(crate) fn run(dir: &Path, script: &str, uuid: &str) -> Result<()> { let script_path = dir.join(script); info!("Running script: {:?}", script_path); @@ -221,10 +225,10 @@ pub(crate) fn run(dir: &Path, script: &str, agent_uuid: &str) -> Result<()> { // the input string is the directory where the unzipped file(s) should be stored. pub(crate) fn optional_unzip_payload( unzipped: &Path, - config: &KeylimeConfig, + config: &config::KeylimeConfig, ) -> Result<()> { - if config.extract_payload_zip { - let zipped_payload = &config.dec_payload_filename; + if config.agent.extract_payload_zip { + let zipped_payload = &config.agent.dec_payload_file; let zipped_payload_path = unzipped.join(zipped_payload); info!("Unzipping payload {} to {:?}", &zipped_payload, unzipped); @@ -240,7 +244,7 @@ pub(crate) async fn run_encrypted_payload( symm_key: Arc>>, symm_key_cvar: Arc, payload: Arc>>, - config: &KeylimeConfig, + config: &config::KeylimeConfig, mount: &Path, ) -> Result<()> { // do nothing until actix server's handlers have updated the symmetric key @@ -264,13 +268,13 @@ pub(crate) async fn run_encrypted_payload( optional_unzip_payload(&unzipped, config)?; // there may also be also a separate init script - match config.payload_script.as_str() { + match config.agent.payload_script.as_str() { "" => { info!("No payload script specified, skipping"); } script => { info!("Payload init script indicated: {}", script); - run(&unzipped, script, config.agent_uuid.as_str())?; + run(&unzipped, script, config.agent.uuid.as_str())?; } } @@ -313,11 +317,12 @@ async fn worker( symm_key: Arc>>, symm_key_cvar: Arc, payload: Arc>>, - config: KeylimeConfig, + config: config::KeylimeConfig, mount: PathBuf, ) -> Result<()> { // Only run payload scripts if mTLS is enabled or 'enable_insecure_payload' option is set - if config.mtls_enabled || config.enable_insecure_payload { + if config.agent.enable_agent_mtls || config.agent.enable_insecure_payload + { run_encrypted_payload( symm_key, symm_key_cvar, @@ -332,7 +337,7 @@ async fn worker( // If with-zmq feature is enabled, run the service listening for ZeroMQ messages #[cfg(feature = "with-zmq")] - if config.run_revocation { + if config.agent.enable_revocation_notifications { return revocation::run_revocation_service(&config, &mount).await; } @@ -392,13 +397,13 @@ async fn main() -> Result<()> { }; // Load config - let mut config = KeylimeConfig::build()?; + let mut config = config::KeylimeConfig::new()?; // The agent cannot run when a payload script is defined, but mTLS is disabled and insecure // payloads are not explicitly enabled - if !&config.mtls_enabled - && !&config.enable_insecure_payload - && !&config.payload_script.is_empty() + if !&config.agent.enable_agent_mtls + && !&config.agent.enable_insecure_payload + && !&config.agent.payload_script.is_empty() { let message = "The agent mTLS is disabled and 'payload_script' is not empty. To allow the agent to run, 'enable_insecure_payload' has to be set to 'True'".to_string(); @@ -406,11 +411,25 @@ async fn main() -> Result<()> { return Err(Error::Configuration(message)); } - let work_dir = Path::new(&config.work_dir); - let mount = secure_mount::mount(work_dir, &config.secure_size)?; + let work_dir = Path::new(&config.agent.keylime_dir); + let mount = secure_mount::mount(work_dir, &config.agent.secure_size)?; + + let run_as = if permissions::get_euid() == 0 { + if let Some(ref run_as) = config.agent.run_as { + Some(run_as.to_string()) + } else { + warn!("Cannot drop privileges since 'run_as' is empty in 'agent' section of 'keylime-agent.conf'."); + None + } + } else { + error!("Cannot drop privileges: not enough permission"); + return Err(Error::Configuration( + "Cannot drop privileges: not enough permission".to_string(), + )); + }; // Drop privileges - if let Some(user_group) = &config.run_as { + if let Some(user_group) = &run_as { permissions::chown(user_group, &mount)?; if let Err(e) = permissions::run_as(user_group) { let message = "The user running the Keylime agent should be set in keylime-agent.conf, using the parameter `run_as`, with the format `user:group`".to_string(); @@ -435,15 +454,22 @@ async fn main() -> Result<()> { cfg_if::cfg_if! { if #[cfg(feature = "legacy-python-actions")] { - // Verify if the python shim is installed in the expected location - let python_shim = - PathBuf::from(&config.revocation_actions_dir).join("shim.py"); - if !python_shim.exists() { - error!("Could not find python shim at {}", python_shim.display()); - return Err(Error::Configuration(format!( - "Could not find python shim at {}", - python_shim.display() - ))); + match config.agent.revocation_actions_dir { + Some(ref actions_dir) => { + // Verify if the python shim is installed in the expected location + let python_shim = Path::new(&actions_dir).join("shim.py"); + if !python_shim.exists() { + error!("Could not find python shim at {}", python_shim.display()); + return Err(Error::Configuration(format!( + "Could not find python shim at {}", + python_shim.display() + ))); + } + }, + None => { + error!("The revocation actions directory was not set in 'revocation_actions_dir'"); + return Err(Error::Configuration("The revocation actions directory was not set in 'revocation_actions_dir'".to_string())); + } } } } @@ -451,7 +477,7 @@ async fn main() -> Result<()> { // When the tpm_ownerpassword is given, set auth for the Endorsement hierarchy. // Note in the Python implementation, tpm_ownerpassword option is also used for claiming // ownership of TPM access, which will not be implemented here. - if let Some(ref v) = config.tpm_ownerpassword { + if let Some(ref v) = config.agent.tpm_ownerpassword { let auth = Auth::try_from(v.as_bytes())?; ctx.tr_set_auth(Hierarchy::Endorsement.into(), auth) .map_err(|e| { @@ -462,46 +488,85 @@ async fn main() -> Result<()> { })?; }; + let tpm_encryption_alg = algorithms::EncryptionAlgorithm::try_from( + config.agent.tpm_encryption_alg.as_str(), + )?; + let tpm_hash_alg = algorithms::HashAlgorithm::try_from( + config.agent.tpm_hash_alg.as_str(), + )?; + let tpm_signing_alg = algorithms::SignAlgorithm::try_from( + config.agent.tpm_signing_alg.as_str(), + )?; + // Gather EK values and certs let ek_result = tpm::create_ek( &mut ctx, - config.enc_alg.into(), - config.ek_handle.as_deref(), + tpm_encryption_alg.into(), + config.agent.ek_handle.as_deref(), )?; + // Calculate the SHA-256 hash of the public key in PEM format + let ek_hash = hash_ek_pubkey(ek_result.public.clone())?; + // Try to load persistent Agent data - let agent_data = config.agent_data.clone().and_then(|data| - match data.valid(config.hash_alg, config.sign_alg) { - true => Some(data), - false => { - warn!( - "Not using old {} because it is not valid with current configuration", - AGENT_DATA - ); - None - }, - } - ); - - // Try to reuse old AK from Agent Data - let old_ak = match &agent_data { - Some(data) => { - let ak_result = data.get_ak()?; - match tpm::load_ak(&mut ctx, ek_result.key_handle, &ak_result) { - Ok(ak_handle) => { - info!("Loaded old AK key from {}", AGENT_DATA); - Some((ak_handle, ak_result)) - } - Err(e) => { - warn!( - "Loading old AK key from {} failed: {}", - AGENT_DATA, e - ); - None + let old_ak = match &config.agent.agent_data_path { + Some(path) => { + let path = Path::new(&path); + if path.exists() { + match AgentData::load(path) { + Ok(data) => { + match data.valid( + tpm_hash_alg, + tpm_signing_alg, + ek_hash.as_bytes(), + ) { + true => { + let ak_result = data.get_ak()?; + match tpm::load_ak( + &mut ctx, + ek_result.key_handle, + &ak_result, + ) { + Ok(ak_handle) => { + info!( + "Loaded old AK key from {}", + path.display() + ); + Some((ak_handle, ak_result)) + } + Err(e) => { + warn!( + "Loading old AK key from {} failed: {}", + path.display(), + e + ); + None + } + } + } + false => { + warn!( + "Not using old {} because it is not valid with current configuration", + path.display() + ); + None + } + } + } + Err(e) => { + warn!("Could not load agent data: {}", e); + None + } } + } else { + info!("Agent Data not found in: {}", path.display()); + None } } - None => None, + None => { + info!("Agent Data path not set in the configuration file"); + None + } }; // Use old AK or generate a new one and update the AgentData @@ -511,8 +576,8 @@ async fn main() -> Result<()> { let new_ak = tpm::create_ak( &mut ctx, ek_result.key_handle, - config.hash_alg.into(), - config.sign_alg.into(), + tpm_hash_alg.into(), + tpm_signing_alg.into(), )?; let ak_handle = tpm::load_ak(&mut ctx, ek_result.key_handle, &new_ak)?; @@ -520,11 +585,29 @@ async fn main() -> Result<()> { } }; - if config.agent_uuid == "hash_ek" { - config.set_ek_uuid(ek_result.public.clone())?; + // Store new AgentData + let agent_data_new = AgentData::create( + tpm_hash_alg, + tpm_signing_alg, + &ak, + ek_hash.as_bytes(), + )?; + + match &config.agent.agent_data_path { + Some(path) => { + agent_data_new.store(Path::new(&path))?; + } + None => { + info!("Agent Data not stored"); + } } - info!("Agent UUID: {}", config.agent_uuid); + let uuid = match config.agent.uuid.as_str() { + "hash_ek" => hash_ek_pubkey(ek_result.public.clone())?, + s => s.to_string(), + }; + + info!("Agent UUID: {}", uuid); // Generate key pair for secure transmission of u, v keys. The u, v // keys are two halves of the key used to decrypt the workload after @@ -534,34 +617,97 @@ async fn main() -> Result<()> { // Since we store the u key in memory, discarding this key, which // safeguards u and v keys in transit, is not part of the threat model. - let (nk_pub, nk_priv) = match &agent_data { - Some(data) => data.get_nk()?, - None => crypto::rsa_generate_pair(2048)?, + let (nk_pub, nk_priv) = match config.agent.server_key { + Some(ref path) => { + let key_path = Path::new(&path); + if key_path.exists() { + debug!( + "Loading existing key pair from {}", + key_path.display() + ); + crypto::load_key_pair( + key_path, + &config.agent.server_key_password, + )? + } else { + debug!("Generating new key pair"); + let (public, private) = crypto::rsa_generate_pair(2048)?; + // Write the generated key to the file + crypto::write_key_pair( + &private, + key_path, + &config.agent.server_key_password, + ); + (public, private) + } + } + None => { + debug!( + "The server_key option was not set in the configuration file" + ); + debug!("Generating new key pair"); + crypto::rsa_generate_pair(2048)? + } }; - let cert: openssl::x509::X509; + let cert: X509; let mtls_cert; let ssl_context; - if config.mtls_enabled { - let keylime_ca_cert = - match crypto::load_x509(Path::new(&config.keylime_ca_path)) { - Ok(t) => Ok(t), - Err(e) => { - error!( - "Certificate not installed: {}", - config.keylime_ca_path + if config.agent.enable_agent_mtls { + cert = match config.agent.server_cert { + Some(ref path) => { + let cert_path = Path::new(&path); + if cert_path.exists() { + debug!( + "Loading existing mTLS certificate from {}", + cert_path.display() ); - Err(e) + crypto::load_x509(cert_path)? + } else { + debug!("Generating new mTLS certificate"); + let cert = crypto::generate_x509(&nk_priv, &uuid)?; + // Write the generated certificate + crypto::write_x509(&cert, cert_path)?; + cert } - }?; - - cert = match &agent_data { - Some(data) => match data.get_mtls_cert()? { - Some(cert) => cert, - None => crypto::generate_x509(&nk_priv, &config.agent_uuid)?, - }, - None => crypto::generate_x509(&nk_priv, &config.agent_uuid)?, + } + None => { + debug!("The server_cert option was not set in the configuration file"); + crypto::generate_x509(&nk_priv, &uuid)? + } + }; + + let ca_cert_path = match config.agent.trusted_client_ca { + None => { + error!("Agent mTLS is enabled, but trusted_client_ca option was not provided"); + return Err(Error::Configuration("Agent mTLS is enabled, but trusted_client_ca option was not provided".to_string())); + } + Some(ref path) => PathBuf::from(&path), }; + + if !ca_cert_path.exists() { + error!( + "Trusted client CA certificate not found: {} does not exist", + ca_cert_path.display() + ); + return Err(Error::Configuration(format!( + "Trusted client CA certificate not found: {} does not exist", + ca_cert_path.display() + ))); + } + + let keylime_ca_cert = match crypto::load_x509(&ca_cert_path) { + Ok(t) => Ok(t), + Err(e) => { + error!( + "Failed to load trusted CA certificate {}: {}", + ca_cert_path.display(), + e + ); + Err(e) + } + }?; + mtls_cert = Some(&cert); ssl_context = Some(crypto::generate_mtls_context( &cert, @@ -574,32 +720,21 @@ async fn main() -> Result<()> { warn!("mTLS disabled, Tenant and Verifier will reach out to agent via HTTP"); } - // Store new AgentData - let agent_data_new = AgentData::create( - config.hash_alg, - config.sign_alg, - &ak, - &nk_pub, - &nk_priv, - &mtls_cert, - )?; - agent_data_new.store(Path::new(&config.agent_data_path))?; - { // Request keyblob material let keyblob = registrar_agent::do_register_agent( - &config.registrar_ip, - &config.registrar_port, - &config.agent_uuid, + &config.agent.registrar_ip, + &config.agent.registrar_port.to_string(), + &uuid, &PublicBuffer::try_from(ek_result.public.clone())?.marshall()?, ek_result.ek_cert, &PublicBuffer::try_from(ak.public)?.marshall()?, mtls_cert, - config.agent_contact_ip.clone(), - config.agent_contact_port, + config.agent.contact_ip.clone(), + config.agent.contact_port, ) .await?; - info!("SUCCESS: Agent {} registered", config.agent_uuid); + info!("SUCCESS: Agent {} registered", &uuid); let key = tpm::activate_credential( &mut ctx, @@ -608,24 +743,22 @@ async fn main() -> Result<()> { ek_result.key_handle, )?; // Flush EK if we created it - if config.ek_handle.is_none() { + if config.agent.ek_handle.is_none() { ctx.flush_context(ek_result.key_handle.into())?; } let mackey = base64::encode(key.value()); - let auth_tag = crypto::compute_hmac( - mackey.as_bytes(), - config.agent_uuid.as_bytes(), - )?; + let auth_tag = + crypto::compute_hmac(mackey.as_bytes(), uuid.as_bytes())?; let auth_tag = hex::encode(&auth_tag); registrar_agent::do_activate_agent( - &config.registrar_ip, - &config.registrar_port, - &config.agent_uuid, + &config.agent.registrar_ip, + &config.agent.registrar_port.to_string(), + &uuid, &auth_tag, ) .await?; - info!("SUCCESS: Agent {} activated", config.agent_uuid); + info!("SUCCESS: Agent {} activated", &uuid); } let mut encr_payload = Vec::new(); @@ -639,21 +772,21 @@ async fn main() -> Result<()> { let symm_key_cvar = Arc::clone(&symm_key_cvar_arc); let payload = Arc::clone(&encr_payload_arc); - let revocation_cert = revocation::get_revocation_cert_path(&config)?; - let actions_dir = Path::new(&config.revocation_actions_dir) + let revocation_cert = + config.agent.revocation_cert.as_ref().map(PathBuf::from); + + let actions_dir = config + .agent + .revocation_actions_dir + .as_ref() + .map(PathBuf::from); + + let work_dir = Path::new(&config.agent.keylime_dir) .canonicalize() .map_err(|e| { - Error::Configuration(format!( - "Path {} set in revocation_actions_dir not found: {}", - &config.revocation_actions_dir, e - )) - })?; - - let work_dir = - Path::new(&config.work_dir).canonicalize().map_err(|e| { Error::Configuration(format!( "Path {} set in keylime_dir not found: {}", - &config.work_dir, e + &config.agent.keylime_dir, e )) })?; @@ -668,16 +801,17 @@ async fn main() -> Result<()> { payload_symm_key_cvar: symm_key_cvar_arc, encr_payload: encr_payload_arc, auth_tag: Mutex::new([0u8; AUTH_TAG_LEN]), - hash_alg: config.hash_alg, - enc_alg: config.enc_alg, - sign_alg: config.sign_alg, - agent_uuid: config.agent_uuid.clone(), + hash_alg: tpm_hash_alg, + enc_alg: tpm_encryption_alg, + sign_alg: tpm_signing_alg, + agent_uuid: config.agent.uuid.clone(), revocation_cert, - revocation_actions: config.revocation_actions.clone(), + revocation_actions: config.agent.revocation_actions.clone(), revocation_actions_dir: actions_dir, allow_payload_revocation_actions: config + .agent .allow_payload_revocation_actions, - secure_size: config.secure_size.clone(), + secure_size: config.agent.secure_size.clone(), work_dir, ima_ml_file, measuredboot_ml_file, @@ -780,26 +914,26 @@ async fn main() -> Result<()> { .disable_signals(); let server; - if config.mtls_enabled && ssl_context.is_some() { + if config.agent.enable_agent_mtls && ssl_context.is_some() { server = actix_server .bind_openssl( - format!("{}:{}", config.agent_ip, config.agent_port), + format!("{}:{}", config.agent.ip, config.agent.port), ssl_context.unwrap(), //#[allow_ci] )? .run(); info!( "Listening on https://{}:{}", - config.agent_ip, config.agent_port + config.agent.ip, config.agent.port ); } else { server = actix_server - .bind(format!("{}:{}", config.agent_ip, config.agent_port))? + .bind(format!("{}:{}", config.agent.ip, config.agent.port))? .run(); info!( "Listening on http://{}:{}", - config.agent_ip, config.agent_port + config.agent.ip, config.agent.port ); }; @@ -839,21 +973,35 @@ fn read_in_file(path: String) -> std::io::Result { #[cfg(feature = "testing")] mod testing { use super::*; + use crate::config::KeylimeConfig; impl QuoteData { pub(crate) fn fixture() -> Result { let test_config = KeylimeConfig::default(); let mut ctx = tpm::get_tpm2_ctx()?; + let tpm_encryption_alg = + algorithms::EncryptionAlgorithm::try_from( + test_config.agent.tpm_encryption_alg.as_str(), + )?; + // Gather EK and AK key values and certs let ek_result = - tpm::create_ek(&mut ctx, test_config.enc_alg.into(), None)?; + tpm::create_ek(&mut ctx, tpm_encryption_alg.into(), None)?; + + let tpm_hash_alg = algorithms::HashAlgorithm::try_from( + test_config.agent.tpm_hash_alg.as_str(), + )?; + + let tpm_signing_alg = algorithms::SignAlgorithm::try_from( + test_config.agent.tpm_signing_alg.as_str(), + )?; let ak_result = tpm::create_ak( &mut ctx, ek_result.key_handle, - test_config.hash_alg.into(), - test_config.sign_alg.into(), + tpm_hash_alg.into(), + tpm_signing_alg.into(), )?; let ak_handle = tpm::load_ak(&mut ctx, ek_result.key_handle, &ak_result)?; @@ -879,10 +1027,11 @@ mod testing { let payload = Arc::clone(&encr_payload_arc); let revocation_cert = - revocation::get_revocation_cert_path(&test_config)?; + test_config.agent.revocation_cert.map(PathBuf::from); - let actions_dir = - Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/actions/"); + let actions_dir = Some( + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/actions/"), + ); let work_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests"); @@ -919,13 +1068,14 @@ mod testing { hash_alg: algorithms::HashAlgorithm::Sha256, enc_alg: algorithms::EncryptionAlgorithm::Rsa, sign_alg: algorithms::SignAlgorithm::RsaSsa, - agent_uuid: test_config.agent_uuid, + agent_uuid: test_config.agent.uuid, revocation_cert, - revocation_actions: String::from(""), + revocation_actions: None, revocation_actions_dir: actions_dir, allow_payload_revocation_actions: test_config + .agent .allow_payload_revocation_actions, - secure_size: test_config.secure_size, + secure_size: test_config.agent.secure_size, work_dir, ima_ml_file, measuredboot_ml_file, diff --git a/src/notifications_handler.rs b/src/notifications_handler.rs index 98d78d938..1d7e1a78e 100644 --- a/src/notifications_handler.rs +++ b/src/notifications_handler.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2021 Keylime Authors -use crate::{common::KeylimeConfig, revocation, Error, QuoteData, Result}; +use crate::{common::JsonWrapper, revocation, Error, QuoteData, Result}; use actix_web::{web, HttpRequest, HttpResponse, Responder}; use log::*; use serde::{Deserialize, Serialize}; @@ -21,33 +21,68 @@ pub async fn revocation( ) -> impl Responder { info!("Received revocation"); - let json_body = serde_json::from_slice(&body)?; - let revocation_cert = &data.revocation_cert; + let json_body = match serde_json::from_slice(&body) { + Ok(body) => body, + Err(e) => { + return HttpResponse::BadRequest().json(JsonWrapper::error( + 400, + format!("JSON parsing error: {}", e), + )); + } + }; + + let revocation_cert = match &data.revocation_cert { + Some(cert) => cert, + None => { + return HttpResponse::InternalServerError().json( + JsonWrapper::error(501, "Revocation certificate not set."), + ); + } + }; + let secure_size = &data.secure_size; - let revocation_actions = &data.revocation_actions; - let actions_dir = PathBuf::from(&data.revocation_actions_dir); + let revocation_actions = match &data.revocation_actions { + Some(actions) => actions, + None => "", + }; + + let actions_dir = match &data.revocation_actions_dir { + Some(dir) => dir, + None => { + return HttpResponse::InternalServerError().json( + JsonWrapper::error( + 501, + "Revocation actions directory not set", + ), + ); + } + }; + let payload_actions_allowed = data.allow_payload_revocation_actions; let work_dir = &data.work_dir; let mount = &data.secure_mount; - revocation::process_revocation( + match revocation::process_revocation( json_body, revocation_cert, secure_size, revocation_actions, - &actions_dir, + actions_dir, payload_actions_allowed, work_dir, mount, - )?; - - HttpResponse::Ok().await + ) { + Ok(_) => HttpResponse::Ok().json(JsonWrapper::success(())), + Err(e) => HttpResponse::InternalServerError().json( + JsonWrapper::error(501, "Revocation actions directory not set"), + ), + } } #[cfg(test)] mod tests { use super::*; - use crate::common::{KeylimeConfig, API_VERSION}; + use crate::common::API_VERSION; use actix_web::{test, web, App}; use serde_json::json; use std::{fs, path::Path}; @@ -55,11 +90,14 @@ mod tests { #[cfg(feature = "testing")] #[actix_rt::test] async fn test_revocation() { - let revocation_cert = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("test-data/test-cert.pem"); - - let revocation_actions_dir = - Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/actions"); + let revocation_cert = Some( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("test-data/test-cert.pem"), + ); + + let revocation_actions_dir = Some( + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/actions"), + ); let quotedata = web::Data::new(QuoteData { revocation_cert, diff --git a/src/revocation.rs b/src/revocation.rs index 2de4418d1..fa49cc7fa 100644 --- a/src/revocation.rs +++ b/src/revocation.rs @@ -4,7 +4,7 @@ #[macro_use] use log::*; -use crate::common::{KeylimeConfig, REV_CERT}; +use crate::config::{AgentConfig, KeylimeConfig}; use crate::crypto; use crate::error::*; use crate::secure_mount; @@ -229,43 +229,6 @@ pub(crate) fn run_revocation_actions( Ok(outputs) } -/// Get the revocation certificate path according to the revocation_cert entry -/// from the configuration file -/// -/// If the revocation_cert entry is "default", then use the default path; -/// If the revocation_cert entry is an absolute path, then use the specified path; -/// If the revocation_cert entry is a relative path, then expand from the WORK_DIR; -/// If the revocation_cert is empty, return error. -pub(crate) fn get_revocation_cert_path( - config: &KeylimeConfig, -) -> Result { - let default_path = - &format!("{}/secure/unzipped/{}", &config.work_dir, REV_CERT); - - // Unlike the python agent we do not attempt lazy loading. We either - // have the certificate, or we don't. If we don't have a key or can't load - // the key we return a Configuration error as the service will not work. - let mut cert_path_buf = match config.revocation_cert.trim() { - "default" => PathBuf::from(&default_path), - "" => { - error!("revocation_cert is not set in configuration"); - return Err(Error::Configuration(String::from( - "revocation_cert is not set in configuration", - ))); - } - _ => PathBuf::from(config.revocation_cert.trim()), - }; - - // If the path is not absolute, expand from the WORK_DIR - if cert_path_buf.as_path().is_relative() { - let rel_path = cert_path_buf; - cert_path_buf = PathBuf::from(&config.work_dir); - cert_path_buf.push(rel_path); - } - - Ok(cert_path_buf) -} - /// Process revocation message received from REST API or 0mq #[allow(clippy::too_many_arguments)] pub(crate) fn process_revocation( @@ -371,7 +334,7 @@ pub(crate) async fn run_revocation_service( config: &KeylimeConfig, mount: &Path, ) -> Result<()> { - let work_dir = Path::new(&config.work_dir); + let work_dir = Path::new(&config.agent.keylime_dir); // Connect to the service via 0mq let context = zmq::Context::new(); @@ -379,15 +342,57 @@ pub(crate) async fn run_revocation_service( mysock.set_subscribe(b"")?; - let endpoint = - format!("tcp://{}:{}", config.revocation_ip, config.revocation_port); + let ip = if let Some(i) = &config.agent.revocation_notification_ip { + i + } else { + error!("No IP set in 'revocation_notification_ip' option"); + return Err(Error::Configuration( + "No IP set in 'revocation_notification_ip' option".to_string(), + )); + }; + + let port = if let Some(p) = &config.agent.revocation_notification_port { + p + } else { + error!("No port set in 'revocation_notification_port' option"); + return Err(Error::Configuration( + "No port set in 'revocation_notification_port' option" + .to_string(), + )); + }; - info!("Connecting to revocation endpoint at {}...", endpoint); + let endpoint = format!("tcp://{}:{}", ip, port); + + info!( + "Connecting to revocation notification endpoint at {}...", + endpoint + ); mysock.connect(endpoint.as_str())?; - let revocation_cert = get_revocation_cert_path(config)?; - let actions_dir = PathBuf::from(&config.revocation_actions_dir.trim()); + let revocation_cert = if let Some(cert) = &config.agent.revocation_cert { + Path::new(cert) + } else { + error!("No revocation certificate set in 'revocation_cert' option"); + return Err(Error::Configuration( + "No revocation certificate set in 'revocation_cert' option" + .to_string(), + )); + }; + + let actions_dir = if let Some(dir) = &config.agent.revocation_actions_dir + { + Path::new(dir) + } else { + error!("No revocation actions directory set in 'revocation_actions_dir' option"); + return Err(Error::Configuration("No revocation actions directory set in 'revocation_actions_dir' option".to_string())); + }; + + let actions = if let Some(a) = &config.agent.revocation_actions { + a + } else { + "" + }; info!("Waiting for revocation messages on 0mq {}", endpoint); @@ -411,11 +416,11 @@ pub(crate) async fn run_revocation_service( let body: Value = serde_json::from_str(rawbody.as_str())?; let _ = process_revocation( body, - &revocation_cert, - &config.secure_size, - &config.revocation_actions, - &actions_dir, - config.allow_payload_revocation_actions, + revocation_cert, + &config.agent.secure_size, + actions, + actions_dir, + config.agent.allow_payload_revocation_actions, work_dir, mount, ); @@ -450,8 +455,8 @@ mod tests { symlink(unzipped_dir, tmpfs_dir.join("unzipped")).unwrap(); //#[allow_ci] let outputs = run_revocation_actions( json, - &test_config.secure_size, - &test_config.revocation_actions, + &test_config.agent.secure_size, + "", actions_dir, true, work_dir.path(), @@ -490,8 +495,8 @@ mod tests { symlink(unzipped_dir, tmpfs_dir.join("unzipped")).unwrap(); //#[allow_ci] let outputs = run_revocation_actions( json, - &test_config.secure_size, - &test_config.revocation_actions, + &test_config.agent.secure_size, + "", actions_dir, true, work_dir.path(), @@ -509,12 +514,9 @@ mod tests { ); cfg_if::cfg_if! { if #[cfg(feature = "legacy-python-actions")] { - test_config.revocation_actions = - String::from("local_action_hello, local_action_payload, local_action_stand_alone.py, local_action_rev_script1.py"); + let revocation_actions = "local_action_hello, local_action_payload, local_action_stand_alone.py, local_action_rev_script1.py"; } else { - test_config.revocation_actions = String::from( - "local_action_stand_alone.py, local_action_rev_script1.py", - ); + let revocation_actions = "local_action_stand_alone.py, local_action_rev_script1.py"; } } let json_str = std::fs::read_to_string(json_file).unwrap(); //#[allow_ci] @@ -529,8 +531,8 @@ mod tests { symlink(unzipped_dir, tmpfs_dir.join("unzipped")).unwrap(); //#[allow_ci] let outputs = run_revocation_actions( json, - &test_config.secure_size, - &test_config.revocation_actions, + &test_config.agent.secure_size, + revocation_actions, actions_dir, true, work_dir.path(), @@ -556,52 +558,6 @@ mod tests { } } - #[test] - fn get_revocation_cert_path_default() { - let test_config = KeylimeConfig::default(); - let revocation_cert_path = - get_revocation_cert_path(&test_config).unwrap(); //#[allow_ci] - let mut expected = PathBuf::from(&test_config.work_dir); - expected.push("secure/unzipped/"); - expected.push(REV_CERT); - assert_eq!(*revocation_cert_path, expected); - } - - #[test] - fn get_revocation_cert_path_absolute() { - let mut test_config = KeylimeConfig { - revocation_cert: String::from("/test/cert.crt"), - ..Default::default() - }; - let revocation_cert_path = - get_revocation_cert_path(&test_config).unwrap(); //#[allow_ci] - assert_eq!(revocation_cert_path, PathBuf::from("/test/cert.crt")); - } - - #[test] - fn get_revocation_cert_path_relative() { - let mut test_config = KeylimeConfig { - revocation_cert: String::from("cert.crt"), - ..Default::default() - }; - let revocation_cert_path = - get_revocation_cert_path(&test_config).unwrap(); //#[allow_ci] - let mut expected = Path::new(&test_config.work_dir).join("cert.crt"); - assert_eq!(revocation_cert_path, expected); - } - - #[test] - fn get_revocation_cert_path_empty() { - let mut test_config = KeylimeConfig { - revocation_cert: String::from(""), - ..Default::default() - }; - assert!( - get_revocation_cert_path(&test_config).is_err(), - "revocation_cert is not set in configuration" - ); - } - #[test] fn test_lookup_action() { let work_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests"); @@ -745,10 +701,10 @@ mod tests { let result = process_revocation( body, &cert_path, - &test_config.secure_size, - &test_config.revocation_actions, + &test_config.agent.secure_size, + "", &actions_dir, - test_config.allow_payload_revocation_actions, + test_config.agent.allow_payload_revocation_actions, &work_dir, &tmpfs_dir, );