├── tests ├── policy_broken.yaml ├── policy_working.yaml ├── integration-test.sh ├── test_policy └── integration_test.rs ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── .github └── workflows │ └── ci.yml └── src ├── utils.rs ├── tpm_objects.rs ├── cli.rs └── main.rs /tests/policy_broken.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - policy_ref: 3 | steps: 4 | - PCRs: 5 | hash_algorithm: sha256 6 | selection: 7 | - pcr_id: 21 8 | value: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" 9 | - pcr_id: 22 10 | value: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" 11 | -------------------------------------------------------------------------------- /tests/policy_working.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - policy_ref: 3 | steps: 4 | - PCRs: 5 | hash_algorithm: sha256 6 | selection: 7 | - pcr_id: 21 8 | value: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" 9 | - pcr_id: 22 10 | value: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" 11 | -------------------------------------------------------------------------------- /tests/integration-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | die() { 4 | echo "ERROR: ${1}" >&2 5 | exit 1 6 | } 7 | 8 | PLAINTEXT=foobar 9 | jwe="$(echo "${PLAINTEXT}" | ./target/debug/clevis-pin-tpm2 encrypt {})" 10 | 11 | dec="$(echo "$jwe" | ./target/debug/clevis-pin-tpm2 decrypt)" \ 12 | || die "Unable to decrypt JWE passed with newline added" 13 | 14 | [ "${dec}" = "${PLAINTEXT}" ] \ 15 | || die "Decrypted JWE (${dec}) does not match PLAINTEXT (${PLAINTEXT})" 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | #Added by cargo 14 | # 15 | #already existing elements are commented out 16 | 17 | /target 18 | #**/*.rs.bk 19 | 20 | # Policy test files 21 | /tests/policy_broken.json 22 | /tests/policy_working.json 23 | /tests/privatekey.pem 24 | /tests/publickey.json 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clevis-pin-tpm2" 3 | version = "0.5.3" 4 | description = "Clevis TPM2 PIN with policy support" 5 | authors = ["Patrick Uiterwijk "] 6 | edition = "2018" 7 | homepage = "https://github.com/fedora-iot/clevis-pin-tpm2" 8 | license = "MIT" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | anyhow = "1" 14 | tss-esapi = { version = "7.5", features = ["generate-bindings"] } 15 | serde = "1.0" 16 | josekit = "0.7.4" 17 | serde_json = "1.0" 18 | base64 = "0.22.0" 19 | atty = "0.2.14" 20 | tpm2-policy = "0.6.0" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Red Hat, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clevis-pin-tpm2 2 | Rewritten Clevis TPM2 PIN 3 | 4 | This rewrite supports all previously encrypted values of the PCR-only clevis TPM2 PIN. 5 | Additionally, it supports Authorized Policies to delegate authorization of PCR values to an external party. 6 | 7 | 8 | ## Creating policies 9 | 10 | A [reference implementation](https://github.com/puiterwijk/clevis-pin-tpm2-signtool) has been made available for creating policies as parsed by this pin. 11 | To use this, first create a policy (see instructions in the repository) and take the output signed policy and the public key JSON. 12 | These files need to be available when the PIN runs, so if the pin is used to encrypt the filesystem root, it will probably need to be in /boot. 13 | Then run: `$binary encrypt '{"policy_pubkey_path": "/boot/policy_pubkey.json", "policy_ref": "", "policy_path": "/boot/policy.json"}' policy_working.json 11 | ../../clevis-pin-tpm2-signtool/clevis-pin-tpm2-signtool policy_broken.json 12 | ) 13 | echo "Working: with Policy" | ./target/debug/clevis-pin-tpm2 encrypt '{"use_policy": true, "policy_pubkey_path":"./tests/publickey.json", "policy_ref": "", "policy_path": "./tests/policy_working.json"}' | ./target/debug/clevis-pin-tpm2 decrypt 14 | echo "Working: with Policy (no use_policy)" | ./target/debug/clevis-pin-tpm2 encrypt '{"policy_pubkey_path":"./tests/publickey.json", "policy_ref": "", "policy_path": "./tests/policy_working.json"}' | ./target/debug/clevis-pin-tpm2 decrypt 15 | # Negative test (non-valid policy) 16 | token=$(echo Failed | ./target/debug/clevis-pin-tpm2 encrypt '{"use_policy": true, "policy_pubkey_path":"./tests/publickey.json", "policy_ref": "", "policy_path": "./tests/policy_broken.json"}') 17 | res=$(echo "$token" | ./target/debug/clevis-pin-tpm2 decrypt 2>&1) 18 | ret=$? 19 | if [ $ret == 0 -a "$res" == "Failed" ] 20 | then 21 | echo "Managed to decrypt with invalid policy" 22 | exit 1 23 | elif [ $ret == 0 ]; 24 | then 25 | echo "Success returned but not decrypted" 26 | exit 1 27 | elif [[ $res =~ Esys_VerifySignature_Finish() ]] 28 | then 29 | echo "Working: with policy with invalid digest" 30 | else 31 | echo "Something went wrong: $res" 32 | exit 1 33 | fi 34 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: [push, pull_request] 3 | jobs: 4 | formatting: 5 | name: Check formatting 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - name: Check formatting 10 | run: cargo fmt --all -- --check 11 | 12 | tests: 13 | name: Perform tests 14 | runs-on: ubuntu-latest 15 | container: fedora:latest 16 | defaults: 17 | run: 18 | working-directory: ./clevis-pin-tpm2 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | path: clevis-pin-tpm2 23 | - name: Check out the policy signtool 24 | uses: actions/checkout@v4 25 | with: 26 | path: clevis-pin-tpm2-signtool 27 | repository: puiterwijk/clevis-pin-tpm2-signtool 28 | - name: Install dependencies 29 | run: | 30 | dnf install -y \ 31 | tpm2-tss-devel clevis \ 32 | swtpm swtpm-tools \ 33 | rust cargo clippy \ 34 | golang clang-devel \ 35 | git-core 36 | - name: Remove clevis-pin-tpm2 37 | run: | 38 | rm -f /usr/bin/clevis-pin-tpm2 /usr/bin/clevis-*-tpm2plus 39 | - name: Build 40 | run: cargo build 41 | - name: Start swtpm 42 | run: | 43 | mkdir /tmp/tpmdir 44 | swtpm_setup --tpm2 \ 45 | --tpmstate /tmp/tpmdir \ 46 | --createek --decryption --create-ek-cert \ 47 | --create-platform-cert \ 48 | --pcr-banks sha1,sha256 \ 49 | --display 50 | swtpm socket --tpm2 \ 51 | --tpmstate dir=/tmp/tpmdir \ 52 | --flags startup-clear \ 53 | --ctrl type=tcp,port=2322 \ 54 | --server type=tcp,port=2321 \ 55 | --daemon 56 | - name: Run integration tests 57 | run: | 58 | TCTI=swtpm: SKIP_CLEVIS=true cargo test -- --nocapture 59 | echo "### Shell integration tests" >&2 60 | TCTI=swtpm: SKIP_CLEVIS=true ./tests/integration-test.sh 61 | - name: Run policy tests 62 | run: | 63 | TCTI=swtpm: ./tests/test_policy 64 | - name: Run clippy 65 | run: cargo clippy -- -D warnings 66 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::str::FromStr; 4 | 5 | use anyhow::{Context as anyhow_context, Result}; 6 | use base64::Engine; 7 | use serde::Deserialize; 8 | use tpm2_policy::{PublicKey, SignedPolicyList, TPMPolicyStep}; 9 | use tss_esapi::{ 10 | handles::KeyHandle, 11 | interface_types::{algorithm::HashingAlgorithm, resource_handles::Hierarchy}, 12 | structures::Public, 13 | Context, Tcti, 14 | }; 15 | 16 | pub(crate) fn get_authorized_policy_step( 17 | policy_pubkey_path: &str, 18 | policy_path: &Option, 19 | policy_ref: &Option, 20 | ) -> Result { 21 | let policy_ref = match policy_ref { 22 | Some(policy_ref) => policy_ref.as_bytes().to_vec(), 23 | None => vec![], 24 | }; 25 | 26 | let signkey = { 27 | let contents = 28 | fs::read_to_string(policy_pubkey_path).context("Error reading policy signkey")?; 29 | serde_json::from_str::(&contents) 30 | .context("Error deserializing signing public key")? 31 | }; 32 | 33 | let policies = match policy_path { 34 | None => None, 35 | Some(policy_path) => { 36 | let contents = fs::read_to_string(policy_path).context("Error reading policy")?; 37 | Some( 38 | serde_json::from_str::(&contents) 39 | .context("Error deserializing policy")?, 40 | ) 41 | } 42 | }; 43 | 44 | Ok(TPMPolicyStep::Authorized { 45 | signkey, 46 | policy_ref, 47 | policies, 48 | next: Box::new(TPMPolicyStep::NoStep), 49 | }) 50 | } 51 | 52 | pub(crate) fn get_hash_alg_from_name(name: Option<&String>) -> HashingAlgorithm { 53 | match name { 54 | None => HashingAlgorithm::Sha256, 55 | Some(val) => match val.to_lowercase().as_str() { 56 | "sha1" => HashingAlgorithm::Sha1, 57 | "sha256" => HashingAlgorithm::Sha256, 58 | "sha384" => HashingAlgorithm::Sha384, 59 | "sha512" => HashingAlgorithm::Sha512, 60 | _ => panic!("Unsupported hash algo: {:?}", name), 61 | }, 62 | } 63 | } 64 | 65 | pub(crate) fn serialize_as_base64_url_no_pad( 66 | bytes: &[u8], 67 | serializer: S, 68 | ) -> Result 69 | where 70 | S: serde::Serializer, 71 | { 72 | serializer.serialize_str(&base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes)) 73 | } 74 | 75 | pub(crate) fn deserialize_as_base64_url_no_pad<'de, D>(deserializer: D) -> Result, D::Error> 76 | where 77 | D: serde::Deserializer<'de>, 78 | { 79 | String::deserialize(deserializer).and_then(|string| { 80 | base64::engine::general_purpose::URL_SAFE_NO_PAD 81 | .decode(string) 82 | .map_err(serde::de::Error::custom) 83 | }) 84 | } 85 | 86 | pub(crate) fn get_tpm2_ctx() -> Result { 87 | let tcti_path = match env::var("TCTI") { 88 | Ok(val) => val, 89 | Err(_) => { 90 | if std::path::Path::new("/dev/tpmrm0").exists() { 91 | "device:/dev/tpmrm0".to_string() 92 | } else { 93 | "device:/dev/tpm0".to_string() 94 | } 95 | } 96 | }; 97 | 98 | let tcti = Tcti::from_str(&tcti_path).context("Error parsing TCTI specification")?; 99 | Context::new(tcti).context("Error initializing TPM2 context") 100 | } 101 | 102 | pub(crate) fn get_tpm2_primary_key(ctx: &mut Context, pub_template: Public) -> Result { 103 | ctx.execute_with_nullauth_session(|ctx| { 104 | ctx.create_primary(Hierarchy::Owner, pub_template, None, None, None, None) 105 | .map(|r| r.key_handle) 106 | }) 107 | .map_err(|e| e.into()) 108 | } 109 | -------------------------------------------------------------------------------- /src/tpm_objects.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use anyhow::{anyhow, bail, Context, Result}; 4 | use tss_esapi::{ 5 | attributes::object::ObjectAttributesBuilder, 6 | constants::tss as tss_constants, 7 | interface_types::{ 8 | algorithm::{HashingAlgorithm, PublicAlgorithm}, 9 | ecc::EccCurve, 10 | }, 11 | structures::{Digest, Public, SymmetricDefinitionObject}, 12 | }; 13 | 14 | #[cfg(target_pointer_width = "64")] 15 | type Sizedu = u64; 16 | #[cfg(target_pointer_width = "32")] 17 | type Sizedu = u32; 18 | 19 | pub(super) fn get_key_public(key_type: &str, name_alg: HashingAlgorithm) -> Result { 20 | let object_attributes = ObjectAttributesBuilder::new() 21 | .with_fixed_tpm(true) 22 | .with_fixed_parent(true) 23 | .with_sensitive_data_origin(true) 24 | .with_user_with_auth(true) 25 | .with_decrypt(true) 26 | .with_sign_encrypt(false) 27 | .with_restricted(true) 28 | .build()?; 29 | 30 | let builder = tss_esapi::structures::PublicBuilder::new() 31 | .with_object_attributes(object_attributes) 32 | .with_name_hashing_algorithm(name_alg); 33 | 34 | match key_type { 35 | "ecc" => builder 36 | .with_public_algorithm(PublicAlgorithm::Ecc) 37 | .with_ecc_parameters( 38 | tss_esapi::structures::PublicEccParametersBuilder::new_restricted_decryption_key( 39 | SymmetricDefinitionObject::AES_128_CFB, 40 | EccCurve::NistP256, 41 | ) 42 | .build()?, 43 | ) 44 | .with_ecc_unique_identifier(Default::default()), 45 | "rsa" => builder 46 | .with_public_algorithm(PublicAlgorithm::Rsa) 47 | .with_rsa_parameters( 48 | tss_esapi::structures::PublicRsaParametersBuilder::new_restricted_decryption_key( 49 | SymmetricDefinitionObject::AES_128_CFB, 50 | tss_esapi::interface_types::key_bits::RsaKeyBits::Rsa2048, 51 | tss_esapi::structures::RsaExponent::ZERO_EXPONENT, 52 | ) 53 | .build()?, 54 | ) 55 | .with_rsa_unique_identifier(Default::default()), 56 | _ => return Err(anyhow!("Unsupported key type used")), 57 | } 58 | .build() 59 | .context("Error building public key") 60 | } 61 | 62 | pub(super) fn create_tpm2b_public_sealed_object( 63 | policy: Option, 64 | ) -> Result { 65 | let mut object_attributes = ObjectAttributesBuilder::new() 66 | .with_fixed_tpm(true) 67 | .with_fixed_parent(true) 68 | .with_no_da(true) 69 | .with_admin_with_policy(true); 70 | 71 | if policy.is_none() { 72 | object_attributes = object_attributes.with_user_with_auth(true); 73 | } 74 | let policy = match policy { 75 | Some(p) => p, 76 | None => Digest::try_from(vec![])?, 77 | }; 78 | 79 | let mut params: tss_esapi::tss2_esys::TPMU_PUBLIC_PARMS = Default::default(); 80 | params.keyedHashDetail.scheme.scheme = tss_constants::TPM2_ALG_NULL; 81 | 82 | Ok(tss_esapi::tss2_esys::TPM2B_PUBLIC { 83 | size: std::mem::size_of::() as u16, 84 | publicArea: tss_esapi::tss2_esys::TPMT_PUBLIC { 85 | type_: tss_constants::TPM2_ALG_KEYEDHASH, 86 | nameAlg: tss_constants::TPM2_ALG_SHA256, 87 | objectAttributes: object_attributes.build()?.0, 88 | authPolicy: tss_esapi::tss2_esys::TPM2B_DIGEST::from(policy), 89 | parameters: params, 90 | unique: Default::default(), 91 | }, 92 | }) 93 | } 94 | 95 | pub(super) fn get_tpm2b_public(val: tss_esapi::tss2_esys::TPM2B_PUBLIC) -> Result> { 96 | let mut offset = 0 as Sizedu; 97 | let mut resp = Vec::with_capacity((val.size + 4) as usize); 98 | 99 | unsafe { 100 | let res = tss_esapi::tss2_esys::Tss2_MU_TPM2B_PUBLIC_Marshal( 101 | &val, 102 | resp.as_mut_ptr(), 103 | resp.capacity() as Sizedu, 104 | &mut offset, 105 | ); 106 | if res != 0 { 107 | bail!("Marshalling tpm2b_public failed"); 108 | } 109 | resp.set_len(offset as usize); 110 | } 111 | 112 | Ok(resp) 113 | } 114 | 115 | pub(super) fn get_tpm2b_private(val: tss_esapi::tss2_esys::TPM2B_PRIVATE) -> Result> { 116 | let mut offset = 0 as Sizedu; 117 | let mut resp = Vec::with_capacity((val.size + 4) as usize); 118 | 119 | unsafe { 120 | let res = tss_esapi::tss2_esys::Tss2_MU_TPM2B_PRIVATE_Marshal( 121 | &val, 122 | resp.as_mut_ptr(), 123 | resp.capacity() as Sizedu, 124 | &mut offset, 125 | ); 126 | if res != 0 { 127 | bail!("Marshalling tpm2b_private failed"); 128 | } 129 | resp.set_len(offset as usize); 130 | } 131 | 132 | Ok(resp) 133 | } 134 | 135 | pub(super) fn build_tpm2b_private(val: &[u8]) -> Result { 136 | let mut resp = tss_esapi::tss2_esys::TPM2B_PRIVATE::default(); 137 | let mut offset = 0 as Sizedu; 138 | 139 | unsafe { 140 | let res = tss_esapi::tss2_esys::Tss2_MU_TPM2B_PRIVATE_Unmarshal( 141 | val[..].as_ptr(), 142 | val.len() as Sizedu, 143 | &mut offset, 144 | &mut resp, 145 | ); 146 | if res != 0 { 147 | bail!("Unmarshalling tpm2b_private failed"); 148 | } 149 | } 150 | 151 | Ok(resp) 152 | } 153 | 154 | pub(super) fn build_tpm2b_public(val: &[u8]) -> Result { 155 | let mut resp = tss_esapi::tss2_esys::TPM2B_PUBLIC::default(); 156 | let mut offset = 0 as Sizedu; 157 | 158 | unsafe { 159 | let res = tss_esapi::tss2_esys::Tss2_MU_TPM2B_PUBLIC_Unmarshal( 160 | val[..].as_ptr(), 161 | val.len() as Sizedu, 162 | &mut offset, 163 | &mut resp, 164 | ); 165 | if res != 0 { 166 | bail!("Unmarshalling tpm2b_public failed"); 167 | } 168 | } 169 | 170 | Ok(resp) 171 | } 172 | -------------------------------------------------------------------------------- /tests/integration_test.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Write, os::unix::process::CommandExt, process::Command}; 2 | 3 | use anyhow::{bail, Context, Result}; 4 | 5 | type CheckFunction = dyn Fn(&str) -> Result<()>; 6 | struct EncryptFunc { 7 | func: Box Result>, 8 | name: &'static str, 9 | } 10 | struct DecryptFunc { 11 | func: Box Result>, 12 | name: &'static str, 13 | } 14 | 15 | const EXENAME: &str = env!("CARGO_BIN_EXE_clevis-pin-tpm2"); 16 | 17 | const CONFIG_STRINGS: &[(&str, &CheckFunction)] = &[ 18 | // No sealing 19 | (r#"{}"#, &always_success), 20 | // No sealing, RSA 21 | (r#"{"key": "rsa"}"#, &always_success), 22 | // No sealing with sha1 name alg 23 | (r#"{"hash": "sha1"}"#, &always_success), 24 | // Sealed against PCR23 25 | (r#"{"pcr_ids": [23]}"#, &always_success), 26 | // sealed against SHA1 PCR23 27 | (r#"{"pcr_bank": "sha1", "pcr_ids": [23]}"#, &always_success), 28 | ]; 29 | 30 | // Check functions 31 | fn always_success(_token: &str) -> Result<()> { 32 | Ok(()) 33 | } 34 | 35 | fn call_cmd_and_get_output(cmd: &mut Command, input: &str) -> Result { 36 | if let Ok(val) = std::env::var("TCTI") { 37 | cmd.env("TCTI", &val); 38 | cmd.env("TPM2TOOLS_TCTI", &val); 39 | } 40 | 41 | let mut child = cmd 42 | .stdin(std::process::Stdio::piped()) 43 | .stdout(std::process::Stdio::piped()) 44 | .spawn() 45 | .context("Failed to spawn process")?; 46 | child 47 | .stdin 48 | .as_mut() 49 | .unwrap() 50 | .write_all(input.as_bytes()) 51 | .context("Failed to write input")?; 52 | let output = child 53 | .wait_with_output() 54 | .context("Failed to wait for process")?; 55 | if !output.status.success() { 56 | bail!("Command failed: {:?}", cmd); 57 | } 58 | Ok(String::from_utf8(output.stdout)?) 59 | } 60 | 61 | // Encrypt/Decrypt functions 62 | fn generate_encrypt_us(renamed: bool) -> EncryptFunc { 63 | EncryptFunc { 64 | name: if renamed { "us_renamed" } else { "us" }, 65 | func: Box::new(move |plaintext: &str, config: &str| -> Result { 66 | let mut cmd = Command::new(EXENAME); 67 | call_cmd_and_get_output( 68 | if renamed { 69 | cmd.arg0("clevis-encrypt-tpm2plus").arg(config) 70 | } else { 71 | cmd.arg("encrypt").arg(config) 72 | }, 73 | plaintext, 74 | ) 75 | }), 76 | } 77 | } 78 | 79 | fn generate_decrypt_us(renamed: bool) -> DecryptFunc { 80 | DecryptFunc { 81 | name: if renamed { "us_renamed" } else { "us" }, 82 | func: Box::new(move |input: &str| -> Result { 83 | let mut cmd = Command::new(EXENAME); 84 | call_cmd_and_get_output( 85 | if renamed { 86 | cmd.arg0("clevis-decrypt-tpm2plus") 87 | } else { 88 | cmd.arg("decrypt") 89 | }, 90 | input, 91 | ) 92 | }), 93 | } 94 | } 95 | 96 | fn generate_encrypt_clevis() -> EncryptFunc { 97 | EncryptFunc { 98 | name: "clevis", 99 | func: Box::new(move |plaintext: &str, config: &str| -> Result { 100 | call_cmd_and_get_output( 101 | Command::new("clevis") 102 | .arg("encrypt") 103 | .arg("tpm2") 104 | .arg(config), 105 | plaintext, 106 | ) 107 | }), 108 | } 109 | } 110 | 111 | fn generate_decrypt_clevis() -> DecryptFunc { 112 | DecryptFunc { 113 | name: "clevis", 114 | func: Box::new(move |input: &str| -> Result { 115 | call_cmd_and_get_output(Command::new("clevis").arg("decrypt"), input) 116 | }), 117 | } 118 | } 119 | 120 | const INPUT: &str = "some-static-content"; 121 | 122 | const FAIL_FAST: Option<&'static str> = option_env!("FAIL_FAST"); 123 | const SKIP_CLEVIS: Option<&'static str> = option_env!("SKIP_CLEVIS"); 124 | 125 | // Testing against clevis requires https://github.com/latchset/clevis/commit/c6fc63fc055c18927decc7bcaa07821d5ae37614 126 | #[test] 127 | fn pcr_tests() { 128 | let mut encrypters = vec![generate_encrypt_us(false), generate_encrypt_us(true)]; 129 | let mut decrypters = vec![generate_decrypt_us(false), generate_decrypt_us(true)]; 130 | if SKIP_CLEVIS.is_none() { 131 | encrypters.push(generate_encrypt_clevis()); 132 | decrypters.push(generate_decrypt_clevis()); 133 | } 134 | 135 | let mut failed: u64 = 0; 136 | 137 | for (config, checker) in CONFIG_STRINGS { 138 | for encrypt_fn in &encrypters { 139 | for decrypt_fn in &decrypters { 140 | if encrypt_fn.name == decrypt_fn.name && encrypt_fn.name == "clevis" { 141 | // This is a boring case we're not interested in 142 | continue; 143 | } 144 | 145 | if failed != 0 && FAIL_FAST.is_some() { 146 | panic!("At least one test failed, and fail-fast enabled"); 147 | } 148 | 149 | eprintln!( 150 | "Executing with encrypt: {}, decrypt: {}, config: '{}'", 151 | encrypt_fn.name, decrypt_fn.name, config, 152 | ); 153 | 154 | eprintln!("\tStarting encrypter"); 155 | let encrypted = (encrypt_fn.func)(INPUT, config); 156 | if let Err(e) = encrypted { 157 | eprintln!("FAILED: error: {:?}", e); 158 | failed += 1; 159 | continue; 160 | } 161 | let encrypted = encrypted.unwrap(); 162 | eprintln!("\tStarting checker"); 163 | if let Err(e) = checker(&encrypted) { 164 | eprintln!("FAILED: error: {:?}", e); 165 | failed += 1; 166 | continue; 167 | } 168 | eprintln!("\tStarting decrypter"); 169 | let decrypted = (decrypt_fn.func)(&encrypted); 170 | if let Err(e) = decrypted { 171 | eprintln!("FAILED: error: {:?}", e); 172 | failed += 1; 173 | continue; 174 | } 175 | let decrypted = decrypted.unwrap(); 176 | eprintln!("\tStarting contents checker"); 177 | if decrypted != INPUT { 178 | eprintln!("FAILED: '{}' (input) != '{}' (decrypted)", INPUT, decrypted); 179 | failed += 1; 180 | continue; 181 | } 182 | eprintln!("\tPASSED"); 183 | } 184 | } 185 | } 186 | 187 | if failed != 0 { 188 | panic!("{} tests failed", failed); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use anyhow::{anyhow, bail, Error, Result}; 4 | use serde::{Deserialize, Serialize}; 5 | use tpm2_policy::TPMPolicyStep; 6 | 7 | use crate::utils::get_authorized_policy_step; 8 | 9 | #[derive(Serialize, Deserialize, std::fmt::Debug)] 10 | pub(super) struct TPM2Config { 11 | pub hash: Option, 12 | pub key: Option, 13 | pub pcr_bank: Option, 14 | // PCR IDs can be passed in as comma-separated string or json array 15 | pub pcr_ids: Option, 16 | pub pcr_digest: Option, 17 | // Whether to use a policy. If this is specified without pubkey path or policy path, they get set to defaults 18 | pub use_policy: Option, 19 | // Public key (in JSON format) for a wildcard policy that's possibly OR'd with the PCR one 20 | pub policy_pubkey_path: Option, 21 | pub policy_ref: Option, 22 | pub policy_path: Option, 23 | } 24 | 25 | impl TryFrom<&TPM2Config> for TPMPolicyStep { 26 | type Error = Error; 27 | 28 | fn try_from(cfg: &TPM2Config) -> Result { 29 | if cfg.pcr_ids.is_some() && cfg.policy_pubkey_path.is_some() { 30 | Ok(TPMPolicyStep::Or([ 31 | Box::new(TPMPolicyStep::PCRs( 32 | cfg.get_pcr_hash_alg(), 33 | cfg.get_pcr_ids().unwrap(), 34 | Box::new(TPMPolicyStep::NoStep), 35 | )), 36 | Box::new(get_authorized_policy_step( 37 | cfg.policy_pubkey_path.as_ref().unwrap(), 38 | &None, 39 | &cfg.policy_ref, 40 | )?), 41 | Box::new(TPMPolicyStep::NoStep), 42 | Box::new(TPMPolicyStep::NoStep), 43 | Box::new(TPMPolicyStep::NoStep), 44 | Box::new(TPMPolicyStep::NoStep), 45 | Box::new(TPMPolicyStep::NoStep), 46 | Box::new(TPMPolicyStep::NoStep), 47 | ])) 48 | } else if cfg.pcr_ids.is_some() { 49 | Ok(TPMPolicyStep::PCRs( 50 | cfg.get_pcr_hash_alg(), 51 | cfg.get_pcr_ids().unwrap(), 52 | Box::new(TPMPolicyStep::NoStep), 53 | )) 54 | } else if cfg.policy_pubkey_path.is_some() { 55 | get_authorized_policy_step( 56 | cfg.policy_pubkey_path.as_ref().unwrap(), 57 | &None, 58 | &cfg.policy_ref, 59 | ) 60 | } else { 61 | Ok(TPMPolicyStep::NoStep) 62 | } 63 | } 64 | } 65 | 66 | pub(crate) const DEFAULT_POLICY_PATH: &str = "/boot/clevis_policy.json"; 67 | pub(crate) const DEFAULT_PUBKEY_PATH: &str = "/boot/clevis_pubkey.json"; 68 | pub(crate) const DEFAULT_POLICY_REF: &str = ""; 69 | 70 | impl TPM2Config { 71 | pub(super) fn get_pcr_hash_alg( 72 | &self, 73 | ) -> tss_esapi::interface_types::algorithm::HashingAlgorithm { 74 | crate::utils::get_hash_alg_from_name(self.pcr_bank.as_ref()) 75 | } 76 | 77 | pub(super) fn get_name_hash_alg( 78 | &self, 79 | ) -> tss_esapi::interface_types::algorithm::HashingAlgorithm { 80 | crate::utils::get_hash_alg_from_name(self.hash.as_ref()) 81 | } 82 | 83 | pub(super) fn get_pcr_ids(&self) -> Option> { 84 | match &self.pcr_ids { 85 | None => None, 86 | Some(serde_json::Value::Array(vals)) => { 87 | Some(vals.iter().map(|x| x.as_u64().unwrap()).collect()) 88 | } 89 | _ => panic!("Unexpected type found for pcr_ids"), 90 | } 91 | } 92 | 93 | pub(super) fn get_pcr_ids_str(&self) -> Option { 94 | match &self.pcr_ids { 95 | None => None, 96 | Some(serde_json::Value::Array(vals)) => Some( 97 | vals.iter() 98 | .map(|x| x.as_u64().unwrap().to_string()) 99 | .collect::>() 100 | .join(","), 101 | ), 102 | _ => panic!("Unexpected type found for pcr_ids"), 103 | } 104 | } 105 | 106 | fn normalize(mut self) -> Result { 107 | self.normalize_pcr_ids()?; 108 | if self.pcr_ids.is_some() && self.pcr_bank.is_none() { 109 | self.pcr_bank = Some("sha256".to_string()); 110 | } 111 | // Make use of the defaults if not specified 112 | if self.use_policy.is_some() && self.use_policy.unwrap() { 113 | if self.policy_path.is_none() { 114 | self.policy_path = Some(DEFAULT_POLICY_PATH.to_string()); 115 | } 116 | if self.policy_pubkey_path.is_none() { 117 | self.policy_pubkey_path = Some(DEFAULT_PUBKEY_PATH.to_string()); 118 | } 119 | if self.policy_ref.is_none() { 120 | self.policy_ref = Some(DEFAULT_POLICY_REF.to_string()); 121 | } 122 | } else if self.policy_pubkey_path.is_some() 123 | || self.policy_path.is_some() 124 | || self.policy_ref.is_some() 125 | { 126 | eprintln!("To use a policy, please specifiy use_policy: true. Not specifying this will be a fatal error in a next release"); 127 | } 128 | if (self.policy_pubkey_path.is_some() 129 | || self.policy_path.is_some() 130 | || self.policy_ref.is_some()) 131 | && (self.policy_pubkey_path.is_none() 132 | || self.policy_path.is_none() 133 | || self.policy_ref.is_none()) 134 | { 135 | bail!("Not all of policy pubkey, path and ref are specified",); 136 | } 137 | Ok(self) 138 | } 139 | 140 | fn normalize_pcr_ids(&mut self) -> Result<()> { 141 | // Normalize from array with one string to just string 142 | if let Some(serde_json::Value::Array(vals)) = &self.pcr_ids { 143 | if vals.len() == 1 { 144 | if let serde_json::Value::String(val) = &vals[0] { 145 | self.pcr_ids = Some(serde_json::Value::String(val.to_string())); 146 | } 147 | } 148 | } 149 | // Normalize pcr_ids from comma-separated string to array 150 | if let Some(serde_json::Value::String(val)) = &self.pcr_ids { 151 | // Was a string, do a split 152 | let newval: Vec = val 153 | .split(',') 154 | .map(|x| serde_json::Value::String(x.trim().to_string())) 155 | .collect(); 156 | self.pcr_ids = Some(serde_json::Value::Array(newval)); 157 | } 158 | // Normalize pcr_ids from array of Strings to array of Numbers 159 | if let Some(serde_json::Value::Array(vals)) = &self.pcr_ids { 160 | let newvals: Result, _> = vals 161 | .iter() 162 | .map(|x| match x { 163 | serde_json::Value::String(val) => { 164 | match val.trim().parse::() { 165 | Ok(res) => { 166 | let new = serde_json::Value::Number(res); 167 | if !new.is_u64() { 168 | bail!("Non-positive string int"); 169 | } 170 | Ok(new) 171 | } 172 | Err(_) => Err(anyhow!("Unparseable string int")), 173 | } 174 | } 175 | serde_json::Value::Number(n) => { 176 | let new = serde_json::Value::Number(n.clone()); 177 | if !new.is_u64() { 178 | return Err(anyhow!("Non-positive int")); 179 | } 180 | Ok(new) 181 | } 182 | _ => Err(anyhow!("Invalid value in pcr_ids")), 183 | }) 184 | .collect(); 185 | self.pcr_ids = Some(serde_json::Value::Array(newvals?)); 186 | } 187 | 188 | match &self.pcr_ids { 189 | None => Ok(()), 190 | // The normalization above would've caught any non-ints 191 | Some(serde_json::Value::Array(_)) => Ok(()), 192 | _ => Err(anyhow!("Invalid type")), 193 | } 194 | } 195 | } 196 | 197 | #[derive(Debug)] 198 | pub(super) enum ActionMode { 199 | Encrypt, 200 | Decrypt, 201 | Summary, 202 | Help, 203 | } 204 | 205 | pub(super) fn get_mode_and_cfg(args: &[String]) -> Result<(ActionMode, Option)> { 206 | if args.len() > 1 && args[1] == "--summary" { 207 | return Ok((ActionMode::Summary, None)); 208 | } 209 | if args.len() > 1 && args[1] == "--help" { 210 | return Ok((ActionMode::Help, None)); 211 | } 212 | if atty::is(atty::Stream::Stdin) { 213 | return Ok((ActionMode::Help, None)); 214 | } 215 | let (mode, cfgstr) = if args[0].contains("encrypt") && args.len() >= 2 { 216 | (ActionMode::Encrypt, Some(&args[1])) 217 | } else if args[0].contains("decrypt") { 218 | (ActionMode::Decrypt, None) 219 | } else if args.len() > 1 { 220 | if args[1] == "encrypt" && args.len() >= 3 { 221 | (ActionMode::Encrypt, Some(&args[2])) 222 | } else if args[1] == "decrypt" { 223 | (ActionMode::Decrypt, None) 224 | } else { 225 | bail!("No command specified"); 226 | } 227 | } else { 228 | bail!("No command specified"); 229 | }; 230 | 231 | let cfg: Option = match cfgstr { 232 | None => None, 233 | Some(cfgstr) => Some(serde_json::from_str::(cfgstr)?.normalize()?), 234 | }; 235 | 236 | Ok((mode, cfg)) 237 | } 238 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Patrick Uiterwijk 2 | // 3 | // Licensed under the MIT license 4 | 5 | use std::convert::{TryFrom, TryInto}; 6 | use std::env; 7 | use std::io::{self, Read, Write}; 8 | 9 | use anyhow::{bail, Context, Error, Result}; 10 | use josekit::jwe::{alg::direct::DirectJweAlgorithm::Dir, enc::A256GCM}; 11 | use serde::{Deserialize, Serialize}; 12 | use tpm2_policy::TPMPolicyStep; 13 | use tss_esapi::structures::SensitiveData; 14 | 15 | mod cli; 16 | mod tpm_objects; 17 | mod utils; 18 | 19 | use cli::TPM2Config; 20 | 21 | fn perform_encrypt(cfg: TPM2Config, input: Vec) -> Result<()> { 22 | let key_type = match &cfg.key { 23 | None => "ecc", 24 | Some(key_type) => key_type, 25 | }; 26 | let key_public = tpm_objects::get_key_public(key_type, cfg.get_name_hash_alg())?; 27 | 28 | let mut ctx = utils::get_tpm2_ctx()?; 29 | let key_handle = utils::get_tpm2_primary_key(&mut ctx, key_public)?; 30 | 31 | let policy_runner: TPMPolicyStep = TPMPolicyStep::try_from(&cfg)?; 32 | 33 | let pin_type = match policy_runner { 34 | TPMPolicyStep::NoStep => "tpm2", 35 | TPMPolicyStep::PCRs(_, _, _) => "tpm2", 36 | _ => "tpm2plus", 37 | }; 38 | 39 | let (_, policy_digest) = policy_runner.send_policy(&mut ctx, true)?; 40 | 41 | let mut jwk = josekit::jwk::Jwk::generate_oct_key(32).context("Error generating random JWK")?; 42 | jwk.set_key_operations(vec!["encrypt", "decrypt"]); 43 | let jwk_str = serde_json::to_string(&jwk.as_ref())?; 44 | 45 | let public = tpm_objects::create_tpm2b_public_sealed_object(policy_digest)?.try_into()?; 46 | let jwk_str = SensitiveData::try_from(jwk_str.as_bytes().to_vec())?; 47 | let jwk_result = ctx.execute_with_nullauth_session(|ctx| { 48 | ctx.create(key_handle, public, None, Some(jwk_str), None, None) 49 | })?; 50 | 51 | let jwk_priv = tpm_objects::get_tpm2b_private(jwk_result.out_private.into())?; 52 | 53 | let jwk_pub = tpm_objects::get_tpm2b_public(jwk_result.out_public.try_into()?)?; 54 | 55 | let private_hdr = ClevisInner { 56 | pin: pin_type.to_string(), 57 | tpm2: Tpm2Inner { 58 | hash: cfg.hash.as_ref().unwrap_or(&"sha256".to_string()).clone(), 59 | key: key_type.to_string(), 60 | jwk_pub, 61 | jwk_priv, 62 | pcr_bank: cfg.pcr_bank.clone(), 63 | pcr_ids: cfg.get_pcr_ids_str(), 64 | policy_pubkey_path: cfg.policy_pubkey_path, 65 | policy_ref: cfg.policy_ref, 66 | policy_path: cfg.policy_path, 67 | }, 68 | }; 69 | 70 | let mut hdr = josekit::jwe::JweHeader::new(); 71 | hdr.set_algorithm(Dir.name()); 72 | hdr.set_content_encryption(A256GCM.name()); 73 | hdr.set_claim( 74 | "clevis", 75 | Some(serde_json::value::to_value(private_hdr).context("Error serializing private header")?), 76 | ) 77 | .context("Error adding clevis claim")?; 78 | 79 | let encrypter = Dir 80 | .encrypter_from_jwk(&jwk) 81 | .context("Error creating direct encrypter")?; 82 | let jwe_token = josekit::jwe::serialize_compact(&input, &hdr, &encrypter) 83 | .context("Error serializing JWE token")?; 84 | 85 | io::stdout().write_all(jwe_token.as_bytes())?; 86 | 87 | Ok(()) 88 | } 89 | 90 | #[derive(Debug, Serialize, Deserialize, Clone)] 91 | struct Tpm2Inner { 92 | hash: String, 93 | #[serde( 94 | deserialize_with = "utils::deserialize_as_base64_url_no_pad", 95 | serialize_with = "utils::serialize_as_base64_url_no_pad" 96 | )] 97 | jwk_priv: Vec, 98 | #[serde( 99 | deserialize_with = "utils::deserialize_as_base64_url_no_pad", 100 | serialize_with = "utils::serialize_as_base64_url_no_pad" 101 | )] 102 | jwk_pub: Vec, 103 | key: String, 104 | 105 | // PCR Binding may be specified, may not 106 | #[serde(skip_serializing_if = "Option::is_none")] 107 | pcr_bank: Option, 108 | #[serde(skip_serializing_if = "Option::is_none")] 109 | pcr_ids: Option, 110 | 111 | // Public key (in PEM format) for a wildcard policy that's OR'd with the PCR one 112 | #[serde(skip_serializing_if = "Option::is_none")] 113 | policy_pubkey_path: Option, 114 | #[serde(skip_serializing_if = "Option::is_none")] 115 | policy_ref: Option, 116 | #[serde(skip_serializing_if = "Option::is_none")] 117 | policy_path: Option, 118 | } 119 | 120 | impl Tpm2Inner { 121 | fn get_pcr_ids(&self) -> Option> { 122 | Some( 123 | self.pcr_ids 124 | .as_ref()? 125 | .split(',') 126 | .map(|x| x.parse::().unwrap()) 127 | .collect(), 128 | ) 129 | } 130 | } 131 | 132 | impl TryFrom<&Tpm2Inner> for TPMPolicyStep { 133 | type Error = Error; 134 | 135 | fn try_from(cfg: &Tpm2Inner) -> Result { 136 | if cfg.pcr_ids.is_some() && cfg.policy_pubkey_path.is_some() { 137 | Ok(TPMPolicyStep::Or([ 138 | Box::new(TPMPolicyStep::PCRs( 139 | utils::get_hash_alg_from_name(cfg.pcr_bank.as_ref()), 140 | cfg.get_pcr_ids().unwrap(), 141 | Box::new(TPMPolicyStep::NoStep), 142 | )), 143 | Box::new(utils::get_authorized_policy_step( 144 | cfg.policy_pubkey_path.as_ref().unwrap(), 145 | &cfg.policy_path, 146 | &cfg.policy_ref, 147 | )?), 148 | Box::new(TPMPolicyStep::NoStep), 149 | Box::new(TPMPolicyStep::NoStep), 150 | Box::new(TPMPolicyStep::NoStep), 151 | Box::new(TPMPolicyStep::NoStep), 152 | Box::new(TPMPolicyStep::NoStep), 153 | Box::new(TPMPolicyStep::NoStep), 154 | ])) 155 | } else if cfg.pcr_ids.is_some() { 156 | Ok(TPMPolicyStep::PCRs( 157 | utils::get_hash_alg_from_name(cfg.pcr_bank.as_ref()), 158 | cfg.get_pcr_ids().unwrap(), 159 | Box::new(TPMPolicyStep::NoStep), 160 | )) 161 | } else if cfg.policy_pubkey_path.is_some() { 162 | utils::get_authorized_policy_step( 163 | cfg.policy_pubkey_path.as_ref().unwrap(), 164 | &cfg.policy_path, 165 | &cfg.policy_ref, 166 | ) 167 | } else { 168 | Ok(TPMPolicyStep::NoStep) 169 | } 170 | } 171 | } 172 | 173 | #[derive(Debug, Serialize, Deserialize, Clone)] 174 | struct ClevisInner { 175 | pin: String, 176 | tpm2: Tpm2Inner, 177 | } 178 | 179 | fn perform_decrypt(input: Vec) -> Result<()> { 180 | let input = String::from_utf8(input) 181 | .context("Error reading input")? 182 | .trim() 183 | .to_string(); 184 | let hdr = josekit::jwt::decode_header(&input).context("Error decoding header")?; 185 | let hdr_clevis = hdr.claim("clevis").context("Error getting clevis claim")?; 186 | let hdr_clevis: ClevisInner = 187 | serde_json::from_value(hdr_clevis.clone()).context("Error deserializing clevis header")?; 188 | 189 | if hdr_clevis.pin != "tpm2" && hdr_clevis.pin != "tpm2plus" { 190 | bail!("JWE pin mismatch"); 191 | } 192 | 193 | let jwkpub = tpm_objects::build_tpm2b_public(&hdr_clevis.tpm2.jwk_pub)?.try_into()?; 194 | let jwkpriv = tpm_objects::build_tpm2b_private(&hdr_clevis.tpm2.jwk_priv)?; 195 | 196 | let policy = TPMPolicyStep::try_from(&hdr_clevis.tpm2)?; 197 | 198 | let name_alg = crate::utils::get_hash_alg_from_name(Some(&hdr_clevis.tpm2.hash)); 199 | let key_public = tpm_objects::get_key_public(hdr_clevis.tpm2.key.as_str(), name_alg)?; 200 | 201 | let mut ctx = utils::get_tpm2_ctx()?; 202 | let key_handle = utils::get_tpm2_primary_key(&mut ctx, key_public)?; 203 | 204 | let key = 205 | ctx.execute_with_nullauth_session(|ctx| ctx.load(key_handle, jwkpriv.try_into()?, jwkpub))?; 206 | 207 | let (policy_session, _) = policy.send_policy(&mut ctx, false)?; 208 | 209 | let unsealed = ctx.execute_with_session(policy_session, |ctx| ctx.unseal(key.into()))?; 210 | let unsealed = &unsealed.value(); 211 | let mut jwk = josekit::jwk::Jwk::from_bytes(unsealed).context("Error unmarshaling JWK")?; 212 | jwk.set_parameter("alg", None) 213 | .context("Error removing the alg parameter")?; 214 | let decrypter = Dir 215 | .decrypter_from_jwk(&jwk) 216 | .context("Error creating decrypter")?; 217 | 218 | let (payload, _) = 219 | josekit::jwe::deserialize_compact(&input, &decrypter).context("Error decrypting JWE")?; 220 | 221 | io::stdout().write_all(&payload)?; 222 | 223 | Ok(()) 224 | } 225 | 226 | fn print_summary() { 227 | println!("Encrypts using a TPM2.0 chip binding policy"); 228 | } 229 | 230 | fn print_help() { 231 | eprintln!( 232 | " 233 | Usage (encryption): clevis encrypt tpm2 CONFIG < PLAINTEXT > JWE 234 | Usage (decryption): clevis decrypt tpm2 CONFIG < JWE > PLAINTEXT 235 | 236 | Encrypts or decrypts using a TPM2.0 chip binding policy 237 | 238 | This command uses the following configuration properties: 239 | 240 | hash: Hash algorithm used in the computation of the object name (default: sha256) 241 | 242 | key: Algorithm type for the generated key (options: ecc, rsa; default: ecc) 243 | 244 | pcr_bank: PCR algorithm bank to use for policy (default: sha256) 245 | 246 | pcr_ids: PCR list used for policy. If not present, no PCR policy is used 247 | 248 | use_policy: Whether to use a policy 249 | 250 | policy_ref: Reference to search for in signed policy file (default: {}) 251 | 252 | > For policies, the path is {}, and the public key is at {} 253 | ", 254 | cli::DEFAULT_POLICY_REF, 255 | cli::DEFAULT_POLICY_PATH, 256 | cli::DEFAULT_PUBKEY_PATH, 257 | ); 258 | 259 | std::process::exit(2); 260 | } 261 | 262 | fn main() -> Result<()> { 263 | let args: Vec = env::args().collect(); 264 | let (mode, cfg) = match cli::get_mode_and_cfg(&args) { 265 | Err(e) => { 266 | eprintln!("Error during parsing operation: {}", e); 267 | std::process::exit(1); 268 | } 269 | Ok((mode, cfg)) => (mode, cfg), 270 | }; 271 | 272 | match mode { 273 | cli::ActionMode::Summary => { 274 | print_summary(); 275 | return Ok(()); 276 | } 277 | cli::ActionMode::Help => { 278 | print_help(); 279 | return Ok(()); 280 | } 281 | _ => {} 282 | }; 283 | 284 | let mut input = Vec::new(); 285 | if let Err(e) = io::stdin().read_to_end(&mut input) { 286 | eprintln!("Error getting input token: {}", e); 287 | std::process::exit(1); 288 | } 289 | 290 | match mode { 291 | cli::ActionMode::Encrypt => perform_encrypt(cfg.unwrap(), input), 292 | cli::ActionMode::Decrypt => perform_decrypt(input), 293 | cli::ActionMode::Summary => unreachable!(), 294 | cli::ActionMode::Help => unreachable!(), 295 | } 296 | } 297 | --------------------------------------------------------------------------------